Skip to content

Commit 58f5a2c

Browse files
committed
merge (helix-editor#12275) syntax symbol pickers
1 parent bccc19b commit 58f5a2c

File tree

17 files changed

+709
-6
lines changed

17 files changed

+709
-6
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

book/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@
2626
- [Adding indent queries](./guides/indent.md)
2727
- [Adding injection queries](./guides/injection.md)
2828
- [Adding rainbow bracket queries](./guides/rainbow_bracket_queries.md)
29+
- [Adding symbols queries](./guides/symbols.md)

book/src/generated/static-cmd.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,10 @@
103103
| `code_action` | Perform code action | normal: `` <space>a ``, select: `` <space>a `` |
104104
| `buffer_picker` | Open buffer picker | normal: `` <space>b ``, select: `` <space>b `` |
105105
| `jumplist_picker` | Open jumplist picker | normal: `` <space>j ``, select: `` <space>j `` |
106-
| `symbol_picker` | Open symbol picker | normal: `` <space>s ``, select: `` <space>s `` |
106+
| `symbol_picker` | Open symbol picker | |
107107
| `changed_file_picker` | Open changed file picker | normal: `` <space>g ``, select: `` <space>g `` |
108108
| `select_references_to_symbol_under_cursor` | Select symbol references | normal: `` <space>h ``, select: `` <space>h `` |
109-
| `workspace_symbol_picker` | Open workspace symbol picker | normal: `` <space>S ``, select: `` <space>S `` |
109+
| `workspace_symbol_picker` | Open workspace symbol picker | |
110110
| `diagnostics_picker` | Open diagnostic picker | normal: `` <space>d ``, select: `` <space>d `` |
111111
| `workspace_diagnostics_picker` | Open workspace diagnostic picker | normal: `` <space>D ``, select: `` <space>D `` |
112112
| `last_picker` | Open last picker | normal: `` <space>' ``, select: `` <space>' `` |
@@ -294,3 +294,7 @@
294294
| `extend_to_word` | Extend to a two-character label | select: `` gw `` |
295295
| `goto_next_tabstop` | goto next snippet placeholder | |
296296
| `goto_prev_tabstop` | goto next snippet placeholder | |
297+
| `syntax_symbol_picker` | Open a picker of symbols from the syntax tree | |
298+
| `syntax_workspace_symbol_picker` | Open a picker of symbols for the workspace based on syntax trees | |
299+
| `lsp_or_syntax_symbol_picker` | Open an LSP symbol picker if available, or syntax otherwise | normal: `` <space>s ``, select: `` <space>s `` |
300+
| `lsp_or_syntax_workspace_symbol_picker` | Open a workspace LSP symbol picker if available, or syntax workspace symbol picker otherwise | normal: `` <space>S ``, select: `` <space>S `` |

book/src/guides/symbols.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
## Adding symbols queries
2+
3+
Helix provides LSP-like features such as document and workspace symbol pickers
4+
which extract symbols only from the syntax of source files. To be analyzed a
5+
language must have a tree-sitter grammar and a `symbols.scm` query file which
6+
pattern matches symbols.
7+
8+
Query files should be placed in `runtime/queries/{language}/symbols.scm`
9+
when contributing to Helix. You may place these under your local runtime
10+
directory (`~/.config/helix/runtime` in Linux for example) for the sake of
11+
testing.
12+
13+
The following [captures][tree-sitter-captures] are recognized:
14+
15+
| Capture name |
16+
|--- |
17+
| `definition.function` |
18+
| `definition.macro` |
19+
| `definition.module` |
20+
| `definition.constant` |
21+
| `definition.struct` |
22+
| `definition.interface` |
23+
| `definition.type` |
24+
| `definition.class` |
25+
26+
[Example query files][example-queries] can be found in the Helix GitHub
27+
repository.
28+
29+
[tree-sitter-captures]: https://tree-sitter.github.io/tree-sitter/using-parsers#capturing-nodes
30+
[example-queries]: https://github.com/search?q=repo%3Ahelix-editor%2Fhelix+path%3A%2A%2A/symbols.scm&type=Code&ref=advsearch&l=&l=

helix-core/src/syntax.rs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ pub struct LanguageConfiguration {
154154
#[serde(skip)]
155155
pub(crate) indent_query: OnceCell<Option<Query>>,
156156
#[serde(skip)]
157+
symbols_query: OnceCell<Option<Query>>,
158+
#[serde(skip)]
157159
pub(crate) textobject_query: OnceCell<Option<TextObjectQuery>>,
158160
#[serde(skip_serializing_if = "Option::is_none")]
159161
pub debugger: Option<DebugAdapterConfig>,
@@ -804,6 +806,12 @@ impl LanguageConfiguration {
804806
.as_ref()
805807
}
806808

809+
pub fn symbols_query(&self) -> Option<&Query> {
810+
self.symbols_query
811+
.get_or_init(|| self.load_query("symbols.scm"))
812+
.as_ref()
813+
}
814+
807815
pub fn textobject_query(&self) -> Option<&TextObjectQuery> {
808816
self.textobject_query
809817
.get_or_init(|| {
@@ -1491,6 +1499,51 @@ impl Syntax {
14911499
QueryIter { layers }
14921500
}
14931501

1502+
pub fn captures<'a>(
1503+
&'a self,
1504+
query: &'a Query,
1505+
source: RopeSlice<'a>,
1506+
range: Option<std::ops::Range<usize>>,
1507+
) -> impl Iterator<Item = (QueryMatch<'a, 'a>, usize)> + 'a {
1508+
struct Captures<'a> {
1509+
// The query cursor must live as long as the captures iterator so
1510+
// we need to bind them together in this struct.
1511+
_cursor: QueryCursor,
1512+
captures: QueryCaptures<'a, 'a, RopeProvider<'a>, &'a [u8]>,
1513+
}
1514+
1515+
impl<'a> Iterator for Captures<'a> {
1516+
type Item = (QueryMatch<'a, 'a>, usize);
1517+
1518+
fn next(&mut self) -> Option<Self::Item> {
1519+
self.captures.next()
1520+
}
1521+
}
1522+
1523+
let mut cursor = PARSER.with(|ts_parser| {
1524+
let highlighter = &mut ts_parser.borrow_mut();
1525+
highlighter.cursors.pop().unwrap_or_default()
1526+
});
1527+
1528+
// The `captures` iterator borrows the `Tree` and the `QueryCursor`, which
1529+
// prevents them from being moved. But both of these values are really just
1530+
// pointers, so it's actually ok to move them.
1531+
let cursor_ref = unsafe {
1532+
mem::transmute::<&mut tree_sitter::QueryCursor, &mut tree_sitter::QueryCursor>(
1533+
&mut cursor,
1534+
)
1535+
};
1536+
1537+
cursor_ref.set_byte_range(range.clone().unwrap_or(0..usize::MAX));
1538+
cursor_ref.set_match_limit(TREE_SITTER_MATCH_LIMIT);
1539+
1540+
let captures = cursor_ref.captures(query, self.tree().root_node(), RopeProvider(source));
1541+
Captures {
1542+
_cursor: cursor,
1543+
captures,
1544+
}
1545+
}
1546+
14941547
/// Iterate over the highlighted regions for a given slice of source code.
14951548
pub fn highlight_iter<'a>(
14961549
&'a self,

helix-loader/src/lib.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,12 @@ pub fn merge_toml_values(left: toml::Value, right: toml::Value, merge_depth: usi
230230
/// Otherwise (workspace, false) is returned
231231
pub fn find_workspace() -> (PathBuf, bool) {
232232
let current_dir = current_working_dir();
233-
for ancestor in current_dir.ancestors() {
233+
find_workspace_in(current_dir)
234+
}
235+
236+
pub fn find_workspace_in(dir: impl AsRef<Path>) -> (PathBuf, bool) {
237+
let dir = dir.as_ref();
238+
for ancestor in dir.ancestors() {
234239
if ancestor.join(".git").exists()
235240
|| ancestor.join(".svn").exists()
236241
|| ancestor.join(".jj").exists()
@@ -240,7 +245,7 @@ pub fn find_workspace() -> (PathBuf, bool) {
240245
}
241246
}
242247

243-
(current_dir, true)
248+
(dir.to_owned(), true)
244249
}
245250

246251
fn default_config_file() -> PathBuf {

helix-term/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ serde = { version = "1.0", features = ["derive"] }
7272
grep-regex = "0.1.13"
7373
grep-searcher = "0.1.14"
7474

75+
dashmap = "6.0"
76+
7577
[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
7678
signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
7779
libc = "0.2.169"

helix-term/src/commands.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub(crate) mod dap;
22
pub(crate) mod lsp;
3+
pub(crate) mod syntax;
34
pub(crate) mod typed;
45

56
pub use dap::*;
@@ -11,6 +12,7 @@ use helix_stdx::{
1112
};
1213
use helix_vcs::{FileChange, Hunk};
1314
pub use lsp::*;
15+
pub use syntax::*;
1416
use tui::text::Span;
1517
pub use typed::*;
1618

@@ -587,6 +589,10 @@ impl MappableCommand {
587589
extend_to_word, "Extend to a two-character label",
588590
goto_next_tabstop, "goto next snippet placeholder",
589591
goto_prev_tabstop, "goto next snippet placeholder",
592+
syntax_symbol_picker, "Open a picker of symbols from the syntax tree",
593+
syntax_workspace_symbol_picker, "Open a picker of symbols for the workspace based on syntax trees",
594+
lsp_or_syntax_symbol_picker, "Open an LSP symbol picker if available, or syntax otherwise",
595+
lsp_or_syntax_workspace_symbol_picker, "Open a workspace LSP symbol picker if available, or syntax workspace symbol picker otherwise",
590596
);
591597
}
592598

@@ -6526,3 +6532,38 @@ fn jump_to_word(cx: &mut Context, behaviour: Movement) {
65266532
}
65276533
jump_to_label(cx, words, behaviour)
65286534
}
6535+
6536+
fn lsp_or_syntax_symbol_picker(cx: &mut Context) {
6537+
let doc = doc!(cx.editor);
6538+
6539+
if doc
6540+
.language_servers_with_feature(LanguageServerFeature::DocumentSymbols)
6541+
.next()
6542+
.is_some()
6543+
{
6544+
lsp::symbol_picker(cx);
6545+
} else if doc.syntax().is_some()
6546+
&& doc
6547+
.language_config()
6548+
.is_some_and(|config| config.symbols_query().is_some())
6549+
{
6550+
syntax_symbol_picker(cx);
6551+
} else {
6552+
cx.editor
6553+
.set_error("No language server supporting document symbols or syntax info available");
6554+
}
6555+
}
6556+
6557+
fn lsp_or_syntax_workspace_symbol_picker(cx: &mut Context) {
6558+
let doc = doc!(cx.editor);
6559+
6560+
if doc
6561+
.language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols)
6562+
.next()
6563+
.is_some()
6564+
{
6565+
lsp::workspace_symbol_picker(cx);
6566+
} else {
6567+
syntax_workspace_symbol_picker(cx);
6568+
}
6569+
}

0 commit comments

Comments
 (0)