Mercurial > forks > helix
changeset 6832:556d271f26b9 draft
Add tags queries/pickers on top of tree-house bindings
(grafted from 30da575c7b798c44fce750cde5231cbd6e9666f3)
author | Michael Davis <mcarsondavis@gmail.com> |
---|---|
date | Thu, 27 Feb 2025 11:51:12 -0500 |
parents | 6ca2a7d9c116 |
children | a1c38f138388 |
files | Cargo.lock helix-core/src/syntax.rs helix-loader/src/lib.rs helix-term/Cargo.toml helix-term/src/commands.rs helix-term/src/commands/syntax.rs helix-term/src/keymap/default.rs languages.toml runtime/queries/_javascript/tags.scm runtime/queries/_typescript/tags.scm runtime/queries/bibtex/tags.scm runtime/queries/c-sharp/tags.scm runtime/queries/c/tags.scm runtime/queries/cpp/tags.scm runtime/queries/elisp/tags.scm runtime/queries/elixir/tags.scm runtime/queries/elm/tags.scm runtime/queries/erlang/tags.scm runtime/queries/gdscript/tags.scm runtime/queries/gjs/tags.scm runtime/queries/go/tags.scm runtime/queries/gts/tags.scm runtime/queries/javascript/tags.scm runtime/queries/jsx/tags.scm runtime/queries/markdown/tags.scm runtime/queries/php-only/tags.scm runtime/queries/php/tags.scm runtime/queries/python/tags.scm runtime/queries/ruby/tags.scm runtime/queries/rust/tags.scm runtime/queries/spicedb/tags.scm runtime/queries/tsx/tags.scm runtime/queries/typescript/tags.scm runtime/queries/typst/tags.scm xtask/src/main.rs |
diffstat | 34 files changed, 657 insertions(+), 357 deletions(-) [+] |
line wrap: on
line diff
--- a/Cargo.lock Tue May 13 20:10:14 2025 -0400 +++ b/Cargo.lock Thu Feb 27 11:51:12 2025 -0500 @@ -1749,6 +1749,7 @@ "chrono", "content_inspector", "crossterm", + "dashmap", "fern", "futures-util", "grep-regex",
--- a/helix-core/src/syntax.rs Tue May 13 20:10:14 2025 -0400 +++ b/helix-core/src/syntax.rs Thu Feb 27 11:51:12 2025 -0500 @@ -20,7 +20,7 @@ use ropey::RopeSlice; use tree_house::{ highlighter, - query_iter::{QueryIter, QueryIterEvent}, + query_iter::QueryIter, tree_sitter::{ query::{InvalidPredicateError, UserPredicate}, Capture, Grammar, InactiveQueryCursor, InputEdit, Node, Pattern, Query, RopeInput, Tree, @@ -32,6 +32,7 @@ pub use tree_house::{ highlighter::{Highlight, HighlightEvent}, + query_iter::QueryIterEvent, Error as HighlighterError, LanguageLoader, TreeCursor, TREE_SITTER_MATCH_LIMIT, }; @@ -42,6 +43,7 @@ indent_query: OnceCell<Option<IndentQuery>>, textobject_query: OnceCell<Option<TextObjectQuery>>, rainbow_query: OnceCell<Option<RainbowQuery>>, + tag_query: OnceCell<Option<Query>>, } impl LanguageData { @@ -52,6 +54,7 @@ indent_query: OnceCell::new(), textobject_query: OnceCell::new(), rainbow_query: OnceCell::new(), + tag_query: OnceCell::new(), } } @@ -190,6 +193,38 @@ .as_ref() } + /// Compiles the tags.scm query for a language. + /// This function should only be used by this module or the xtask crate. + pub fn compile_tag_query( + grammar: Grammar, + config: &LanguageConfiguration, + ) -> Result<Option<Query>> { + let name = &config.language_id; + let text = read_query(name, "tags.scm"); + if text.is_empty() { + return Ok(None); + } + let query = Query::new(grammar, &text, |_pattern, predicate| { + Err(InvalidPredicateError::unknown(predicate)) + }) + .with_context(|| format!("Failed to compile tags.scm query for '{name}'"))?; + Ok(Some(query)) + } + + fn tag_query(&self, loader: &Loader) -> Option<&Query> { + self.tag_query + .get_or_init(|| { + let grammar = self.syntax_config(loader)?.grammar; + Self::compile_tag_query(grammar, &self.config) + .map_err(|err| { + log::error!("{err}"); + }) + .ok() + .flatten() + }) + .as_ref() + } + fn reconfigure(&self, scopes: &[String]) { if let Some(Some(config)) = self.syntax.get() { reconfigure_highlights(config, scopes); @@ -379,6 +414,10 @@ self.language(lang).rainbow_query(self) } + pub fn tag_query(&self, lang: Language) -> Option<&Query> { + self.language(lang).tag_query(self) + } + pub fn language_server_configs(&self) -> &HashMap<String, LanguageServerConfiguration> { &self.language_server_configs } @@ -552,6 +591,15 @@ QueryIter::new(&self.inner, source, loader, range) } + pub fn tags<'a>( + &'a self, + source: RopeSlice<'a>, + loader: &'a Loader, + range: impl RangeBounds<u32>, + ) -> QueryIter<'a, 'a, impl FnMut(Language) -> Option<&'a Query> + 'a, ()> { + self.query_iter(source, |lang| loader.tag_query(lang), range) + } + pub fn rainbow_highlights( &self, source: RopeSlice,
--- a/helix-loader/src/lib.rs Tue May 13 20:10:14 2025 -0400 +++ b/helix-loader/src/lib.rs Thu Feb 27 11:51:12 2025 -0500 @@ -244,7 +244,12 @@ /// Otherwise (workspace, false) is returned pub fn find_workspace() -> (PathBuf, bool) { let current_dir = current_working_dir(); - for ancestor in current_dir.ancestors() { + find_workspace_in(current_dir) +} + +pub fn find_workspace_in(dir: impl AsRef<Path>) -> (PathBuf, bool) { + let dir = dir.as_ref(); + for ancestor in dir.ancestors() { if ancestor.join(".git").exists() || ancestor.join(".hg").exists() || ancestor.join(".svn").exists() @@ -255,7 +260,7 @@ } } - (current_dir, true) + (dir.to_owned(), true) } fn default_config_file() -> PathBuf {
--- a/helix-term/Cargo.toml Tue May 13 20:10:14 2025 -0400 +++ b/helix-term/Cargo.toml Thu Feb 27 11:51:12 2025 -0500 @@ -92,6 +92,8 @@ grep-regex = "0.1.13" grep-searcher = "0.1.14" +dashmap = "6.0" + [target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } libc = "0.2.172"
--- a/helix-term/src/commands.rs Tue May 13 20:10:14 2025 -0400 +++ b/helix-term/src/commands.rs Thu Feb 27 11:51:12 2025 -0500 @@ -1,5 +1,6 @@ pub(crate) mod dap; pub(crate) mod lsp; +pub(crate) mod syntax; pub(crate) mod typed; pub use dap::*; @@ -11,6 +12,7 @@ }; use helix_vcs::{FileChange, Hunk}; pub use lsp::*; +pub use syntax::*; use tui::{ text::{Span, Spans}, widgets::Cell, @@ -602,6 +604,10 @@ extend_to_word, "Extend to a two-character label", goto_next_tabstop, "goto next snippet placeholder", goto_prev_tabstop, "goto next snippet placeholder", + syntax_symbol_picker, "Open symbol picker from syntax information", + syntax_workspace_symbol_picker, "Open workspace symbol picker from syntax information", + lsp_or_syntax_symbol_picker, "Open symbol picker from LSP or syntax information", + lsp_or_syntax_workspace_symbol_picker, "Open workspace symbol picker from LSP or syntax information", ); } @@ -6881,3 +6887,34 @@ } jump_to_label(cx, words, behaviour) } + +fn lsp_or_syntax_symbol_picker(cx: &mut Context) { + let doc = doc!(cx.editor); + + if doc + .language_servers_with_feature(LanguageServerFeature::DocumentSymbols) + .next() + .is_some() + { + lsp::symbol_picker(cx); + } else if doc.syntax().is_some() { + syntax_symbol_picker(cx); + } else { + cx.editor + .set_error("No language server supporting document symbols or syntax info available"); + } +} + +fn lsp_or_syntax_workspace_symbol_picker(cx: &mut Context) { + let doc = doc!(cx.editor); + + if doc + .language_servers_with_feature(LanguageServerFeature::WorkspaceSymbols) + .next() + .is_some() + { + lsp::workspace_symbol_picker(cx); + } else { + syntax_workspace_symbol_picker(cx); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/helix-term/src/commands/syntax.rs Thu Feb 27 11:51:12 2025 -0500 @@ -0,0 +1,441 @@ +use std::{ + collections::HashSet, + iter, + path::{Path, PathBuf}, + sync::Arc, +}; + +use dashmap::DashMap; +use futures_util::FutureExt; +use grep_regex::RegexMatcherBuilder; +use grep_searcher::{sinks, BinaryDetection, SearcherBuilder}; +use helix_core::{ + syntax::{Loader, QueryIterEvent}, + Rope, RopeSlice, Selection, Syntax, Uri, +}; +use helix_stdx::{ + path, + rope::{self, RopeSliceExt}, +}; +use helix_view::{ + align_view, + document::{from_reader, SCRATCH_BUFFER_NAME}, + Align, Document, DocumentId, Editor, +}; +use ignore::{DirEntry, WalkBuilder, WalkState}; + +use crate::{ + filter_picker_entry, + ui::{ + overlay::overlaid, + picker::{Injector, PathOrId}, + Picker, PickerColumn, + }, +}; + +use super::Context; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +enum TagKind { + Function, + Macro, + Module, + Constant, + Struct, + Interface, + Type, + Class, +} + +impl TagKind { + fn as_str(&self) -> &'static str { + match self { + Self::Function => "function", + Self::Macro => "macro", + Self::Module => "module", + Self::Constant => "constant", + Self::Struct => "struct", + Self::Interface => "interface", + Self::Type => "type", + Self::Class => "class", + } + } + + fn from_name(name: &str) -> Option<Self> { + match name { + "function" => Some(TagKind::Function), + "macro" => Some(TagKind::Macro), + "module" => Some(TagKind::Module), + "constant" => Some(TagKind::Constant), + "struct" => Some(TagKind::Struct), + "interface" => Some(TagKind::Interface), + "type" => Some(TagKind::Type), + "class" => Some(TagKind::Class), + _ => None, + } + } +} + +// NOTE: Uri is cheap to clone and DocumentId is Copy +#[derive(Debug, Clone)] +enum UriOrDocumentId { + Uri(Uri), + Id(DocumentId), +} + +impl UriOrDocumentId { + fn path_or_id(&self) -> Option<PathOrId<'_>> { + match self { + Self::Id(id) => Some(PathOrId::Id(*id)), + Self::Uri(uri) => uri.as_path().map(PathOrId::Path), + } + } +} + +#[derive(Debug)] +struct Tag { + kind: TagKind, + name: String, + start: usize, + end: usize, + start_line: usize, + end_line: usize, + doc: UriOrDocumentId, +} + +fn tags_iter<'a>( + syntax: &'a Syntax, + loader: &'a Loader, + text: RopeSlice<'a>, + doc: UriOrDocumentId, + pattern: Option<&'a rope::Regex>, +) -> impl Iterator<Item = Tag> + 'a { + let mut tags_iter = syntax.tags(text, loader, ..); + + iter::from_fn(move || loop { + let QueryIterEvent::Match(mat) = tags_iter.next()? else { + continue; + }; + let query = loader + .tag_query(tags_iter.current_language()) + .expect("must have a tags query to emit matches"); + let Some(kind) = query + .capture_name(mat.capture) + .strip_prefix("definition.") + .and_then(TagKind::from_name) + else { + continue; + }; + let range = mat.node.byte_range(); + if pattern.is_some_and(|pattern| { + !pattern.is_match(text.regex_input_at_bytes(range.start as usize..range.end as usize)) + }) { + continue; + } + let start = text.byte_to_char(range.start as usize); + let end = text.byte_to_char(range.end as usize); + return Some(Tag { + kind, + name: text.slice(start..end).to_string(), + start, + end, + start_line: text.char_to_line(start), + end_line: text.char_to_line(end), + doc: doc.clone(), + }); + }) +} + +pub fn syntax_symbol_picker(cx: &mut Context) { + let doc = doc!(cx.editor); + let Some(syntax) = doc.syntax() else { + cx.editor + .set_error("Syntax tree is not available on this buffer"); + return; + }; + let doc_id = doc.id(); + let text = doc.text().slice(..); + let loader = cx.editor.syn_loader.load(); + let tags = tags_iter(syntax, &loader, text, UriOrDocumentId::Id(doc.id()), None); + + let columns = vec![ + PickerColumn::new("kind", |tag: &Tag, _| tag.kind.as_str().into()), + PickerColumn::new("name", |tag: &Tag, _| tag.name.as_str().into()), + ]; + + let picker = Picker::new( + columns, + 1, // name + tags, + (), + move |cx, tag, action| { + cx.editor.switch(doc_id, action); + let view = view_mut!(cx.editor); + let doc = doc_mut!(cx.editor, &doc_id); + doc.set_selection(view.id, Selection::single(tag.start, tag.end)); + if action.align_view(view, doc.id()) { + align_view(doc, view, Align::Center) + } + }, + ) + .with_preview(|_editor, tag| { + Some((tag.doc.path_or_id()?, Some((tag.start_line, tag.end_line)))) + }) + .truncate_start(false); + + cx.push_layer(Box::new(overlaid(picker))); +} + +pub fn syntax_workspace_symbol_picker(cx: &mut Context) { + #[derive(Debug)] + struct SearchState { + searcher_builder: SearcherBuilder, + walk_builder: WalkBuilder, + regex_matcher_builder: RegexMatcherBuilder, + search_root: PathBuf, + /// A cache of files that have been parsed in prior searches. + syntax_cache: DashMap<PathBuf, Option<(Rope, Syntax)>>, + } + + let mut searcher_builder = SearcherBuilder::new(); + searcher_builder.binary_detection(BinaryDetection::quit(b'\x00')); + + // Search from the workspace that the currently focused document is within. This behaves like global + // search most of the time but helps when you have two projects open in splits. + let search_root = if let Some(path) = doc!(cx.editor).path() { + helix_loader::find_workspace_in(path).0 + } else { + helix_loader::find_workspace().0 + }; + + let absolute_root = search_root + .canonicalize() + .unwrap_or_else(|_| search_root.clone()); + + let config = cx.editor.config(); + let dedup_symlinks = config.file_picker.deduplicate_links; + + let mut walk_builder = WalkBuilder::new(&search_root); + walk_builder + .hidden(config.file_picker.hidden) + .parents(config.file_picker.parents) + .ignore(config.file_picker.ignore) + .follow_links(config.file_picker.follow_symlinks) + .git_ignore(config.file_picker.git_ignore) + .git_global(config.file_picker.git_global) + .git_exclude(config.file_picker.git_exclude) + .max_depth(config.file_picker.max_depth) + .filter_entry(move |entry| filter_picker_entry(entry, &absolute_root, dedup_symlinks)) + .add_custom_ignore_filename(helix_loader::config_dir().join("ignore")) + .add_custom_ignore_filename(".helix/ignore"); + + let mut regex_matcher_builder = RegexMatcherBuilder::new(); + regex_matcher_builder.case_smart(config.search.smart_case); + let state = SearchState { + searcher_builder, + walk_builder, + regex_matcher_builder, + search_root, + syntax_cache: DashMap::default(), + }; + let reg = cx.register.unwrap_or('/'); + cx.editor.registers.last_search_register = reg; + let columns = vec![ + PickerColumn::new("kind", |tag: &Tag, _| tag.kind.as_str().into()), + PickerColumn::new("name", |tag: &Tag, _| tag.name.as_str().into()).without_filtering(), + PickerColumn::new("path", |tag: &Tag, state: &SearchState| { + match &tag.doc { + UriOrDocumentId::Uri(uri) => { + if let Some(path) = uri.as_path() { + let path = if let Ok(stripped) = path.strip_prefix(&state.search_root) { + stripped + } else { + path + }; + path.to_string_lossy().into() + } else { + uri.to_string().into() + } + } + // This picker only uses `Id` for scratch buffers for better display. + UriOrDocumentId::Id(_) => SCRATCH_BUFFER_NAME.into(), + } + }), + ]; + + let get_tags = |query: &str, + editor: &mut Editor, + state: Arc<SearchState>, + injector: &Injector<_, _>| { + if query.len() < 3 { + return async { Ok(()) }.boxed(); + } + // Attempt to find the tag in any open documents. + let pattern = match rope::Regex::new(query) { + Ok(pattern) => pattern, + Err(err) => return async { Err(anyhow::anyhow!(err)) }.boxed(), + }; + let loader = editor.syn_loader.load(); + for doc in editor.documents() { + let Some(syntax) = doc.syntax() else { continue }; + let text = doc.text().slice(..); + let uri_or_id = doc + .uri() + .map(UriOrDocumentId::Uri) + .unwrap_or_else(|| UriOrDocumentId::Id(doc.id())); + for tag in tags_iter(syntax, &loader, text.slice(..), uri_or_id, Some(&pattern)) { + if injector.push(tag).is_err() { + return async { Ok(()) }.boxed(); + } + } + } + if !state.search_root.exists() { + return async { Err(anyhow::anyhow!("Current working directory does not exist")) } + .boxed(); + } + let matcher = match state.regex_matcher_builder.build(query) { + Ok(matcher) => { + // Clear any "Failed to compile regex" errors out of the statusline. + editor.clear_status(); + matcher + } + Err(err) => { + log::info!( + "Failed to compile search pattern in workspace symbol search: {}", + err + ); + return async { Err(anyhow::anyhow!("Failed to compile regex")) }.boxed(); + } + }; + let pattern = Arc::from(pattern); + let injector = injector.clone(); + let loader = editor.syn_loader.load(); + let documents: HashSet<_> = editor + .documents() + .filter_map(Document::path) + .cloned() + .collect(); + async move { + let searcher = state.searcher_builder.build(); + state.walk_builder.build_parallel().run(|| { + let mut searcher = searcher.clone(); + let matcher = matcher.clone(); + let injector = injector.clone(); + let loader = loader.clone(); + let documents = &documents; + let pattern = pattern.clone(); + let syntax_cache = &state.syntax_cache; + Box::new(move |entry: Result<DirEntry, ignore::Error>| -> WalkState { + let entry = match entry { + Ok(entry) => entry, + Err(_) => return WalkState::Continue, + }; + match entry.file_type() { + Some(entry) if entry.is_file() => {} + // skip everything else + _ => return WalkState::Continue, + }; + let path = entry.path(); + // If this document is open, skip it because we've already processed it above. + if documents.contains(path) { + return WalkState::Continue; + }; + let mut quit = false; + let sink = sinks::UTF8(|_line, _content| { + if !syntax_cache.contains_key(path) { + // Read the file into a Rope and attempt to recognize the language + // and parse it with tree-sitter. Save the Rope and Syntax for future + // queries. + syntax_cache.insert(path.to_path_buf(), syntax_for_path(path, &loader)); + }; + let entry = syntax_cache.get(path).unwrap(); + let Some((text, syntax)) = entry.value() else { + // If the file couldn't be parsed, move on. + return Ok(false); + }; + let uri = Uri::from(path::normalize(path)); + for tag in tags_iter( + syntax, + &loader, + text.slice(..), + UriOrDocumentId::Uri(uri), + Some(&pattern), + ) { + if injector.push(tag).is_err() { + quit = true; + break; + } + } + // Quit after seeing the first regex match. We only care to find files + // that contain the pattern and then we run the tags query within + // those. The location and contents of a match are irrelevant - it's + // only important _if_ a file matches. + Ok(false) + }); + if let Err(err) = searcher.search_path(&matcher, path, sink) { + log::info!("Workspace syntax search error: {}, {}", path.display(), err); + } + if quit { + WalkState::Quit + } else { + WalkState::Continue + } + }) + }); + Ok(()) + } + .boxed() + }; + let picker = Picker::new( + columns, + 1, // name + [], + state, + move |cx, tag, action| { + let doc_id = match &tag.doc { + UriOrDocumentId::Id(id) => *id, + UriOrDocumentId::Uri(uri) => match cx.editor.open(uri.as_path().expect(""), action) { + Ok(id) => id, + Err(e) => { + cx.editor + .set_error(format!("Failed to open file '{uri:?}': {e}")); + return; + } + } + }; + let doc = doc_mut!(cx.editor, &doc_id); + let view = view_mut!(cx.editor); + let len_chars = doc.text().len_chars(); + if tag.start >= len_chars || tag.end > len_chars { + cx.editor.set_error("The location you jumped to does not exist anymore because the file has changed."); + return; + } + doc.set_selection(view.id, Selection::single(tag.start, tag.end)); + if action.align_view(view, doc.id()) { + align_view(doc, view, Align::Center) + } + }, + ) + .with_dynamic_query(get_tags, Some(275)) + .with_preview(move |_editor, tag| { + Some(( + tag.doc.path_or_id()?, + Some((tag.start_line, tag.end_line)), + )) + }) + .truncate_start(false); + cx.push_layer(Box::new(overlaid(picker))); +} + +/// Create a Rope and language config for a given existing path without creating a full Document. +fn syntax_for_path(path: &Path, loader: &Loader) -> Option<(Rope, Syntax)> { + let mut file = std::fs::File::open(path).ok()?; + let (rope, _encoding, _has_bom) = from_reader(&mut file, None).ok()?; + let text = rope.slice(..); + let language = loader + .language_for_filename(path) + .or_else(|| loader.language_for_shebang(text))?; + Syntax::new(text, language, loader) + .ok() + .map(|syntax| (rope, syntax)) +}
--- a/helix-term/src/keymap/default.rs Tue May 13 20:10:14 2025 -0400 +++ b/helix-term/src/keymap/default.rs Thu Feb 27 11:51:12 2025 -0500 @@ -227,8 +227,8 @@ "E" => file_explorer_in_current_buffer_directory, "b" => buffer_picker, "j" => jumplist_picker, - "s" => symbol_picker, - "S" => workspace_symbol_picker, + "s" => lsp_or_syntax_symbol_picker, + "S" => lsp_or_syntax_workspace_symbol_picker, "d" => diagnostics_picker, "D" => workspace_diagnostics_picker, "g" => document_change_picker,
--- a/languages.toml Tue May 13 20:10:14 2025 -0400 +++ b/languages.toml Thu Feb 27 11:51:12 2025 -0500 @@ -1986,7 +1986,10 @@ shebangs = ["escript"] comment-token = "%%" indent = { tab-width = 4, unit = " " } -language-servers = [ "erlang-ls", "elp" ] +language-servers = [ + { name = "erlang-ls", except-features = ["document-symbols", "workspace-symbols"] }, + { name = "elp", except-features = ["document-symbols", "workspace-symbols"] } +] [[grammar]] name = "erlang"
--- a/runtime/queries/_javascript/tags.scm Tue May 13 20:10:14 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,88 +0,0 @@ -( - (comment)* @doc - . - (method_definition - name: (property_identifier) @name) @definition.method - (#not-eq? @name "constructor") - (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") - (#select-adjacent! @doc @definition.method) -) - -( - (comment)* @doc - . - [ - (class - name: (_) @name) - (class_declaration - name: (_) @name) - ] @definition.class - (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") - (#select-adjacent! @doc @definition.class) -) - -( - (comment)* @doc - . - [ - (function - name: (identifier) @name) - (function_declaration - name: (identifier) @name) - (generator_function - name: (identifier) @name) - (generator_function_declaration - name: (identifier) @name) - ] @definition.function - (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") - (#select-adjacent! @doc @definition.function) -) - -( - (comment)* @doc - . - (lexical_declaration - (variable_declarator - name: (identifier) @name - value: [(arrow_function) (function)]) @definition.function) - (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") - (#select-adjacent! @doc @definition.function) -) - -( - (comment)* @doc - . - (variable_declaration - (variable_declarator - name: (identifier) @name - value: [(arrow_function) (function)]) @definition.function) - (#strip! @doc "^[\\s\\*/]+|^[\\s\\*/]$") - (#select-adjacent! @doc @definition.function) -) - -(assignment_expression - left: [ - (identifier) @name - (member_expression - property: (property_identifier) @name) - ] - right: [(arrow_function) (function)] -) @definition.function - -(pair - key: (property_identifier) @name - value: [(arrow_function) (function)]) @definition.function - -( - (call_expression - function: (identifier) @name) @reference.call - (#not-match? @name "^(require)$") -) - -(call_expression - function: (member_expression - property: (property_identifier) @name) - arguments: (_) @reference.call) - -(new_expression - constructor: (_) @name) @reference.class
--- a/runtime/queries/_typescript/tags.scm Tue May 13 20:10:14 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -(function_signature - name: (identifier) @name) @definition.function - -(method_signature - name: (property_identifier) @name) @definition.method - -(abstract_method_signature - name: (property_identifier) @name) @definition.method - -(abstract_class_declaration - name: (type_identifier) @name) @definition.class - -(module - name: (identifier) @name) @definition.module - -(interface_declaration - name: (type_identifier) @name) @definition.interface - -(type_annotation - (type_identifier) @name) @reference.type - -(new_expression - constructor: (identifier) @name) @reference.class
--- a/runtime/queries/c-sharp/tags.scm Tue May 13 20:10:14 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -(class_declaration name: (identifier) @name) @definition.class - -(class_declaration (base_list (_) @name)) @reference.class - -(interface_declaration name: (identifier) @name) @definition.interface - -(interface_declaration (base_list (_) @name)) @reference.interface - -(method_declaration name: (identifier) @name) @definition.method - -(object_creation_expression type: (identifier) @name) @reference.class - -(type_parameter_constraints_clause (identifier) @name) @reference.class - -(type_parameter_constraint (type type: (identifier) @name)) @reference.class - -(variable_declaration type: (identifier) @name) @reference.class - -(invocation_expression function: (member_access_expression name: (identifier) @name)) @reference.send - -(namespace_declaration name: (identifier) @name) @definition.module - -(namespace_declaration name: (identifier) @name) @module
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/runtime/queries/c/tags.scm Thu Feb 27 11:51:12 2025 -0500 @@ -0,0 +1,9 @@ +(function_declarator + declarator: [(identifier) (field_identifier)] @definition.function) + +(preproc_function_def name: (identifier) @definition.function) + +(type_definition + declarator: (type_identifier) @definition.type) + +(preproc_def name: (identifier) @definition.constant)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/runtime/queries/cpp/tags.scm Thu Feb 27 11:51:12 2025 -0500 @@ -0,0 +1,12 @@ +; inherits: c + +(function_declarator + declarator: (qualified_identifier name: (identifier) @definition.function)) + +(struct_specifier + name: (type_identifier) @definition.struct + body: (field_declaration_list)) + +(class_specifier + name: (type_identifier) @definition.class + body: (field_declaration_list))
--- a/runtime/queries/elisp/tags.scm Tue May 13 20:10:14 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -;; defun/defsubst -(function_definition name: (symbol) @name) @definition.function - -;; Treat macros as function definitions for the sake of TAGS. -(macro_definition name: (symbol) @name) @definition.function
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/runtime/queries/elixir/tags.scm Thu Feb 27 11:51:12 2025 -0500 @@ -0,0 +1,10 @@ +((call + target: (identifier) @_keyword + (arguments + [ + (call target: (identifier) @definition.function) + ; function has a guard + (binary_operator + left: (call target: (identifier) @definition.function)) + ])) + (#any-of? @_keyword "def" "defdelegate" "defguard" "defguardp" "defmacro" "defmacrop" "defn" "defnp" "defp"))
--- a/runtime/queries/elm/tags.scm Tue May 13 20:10:14 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -(value_declaration (function_declaration_left (lower_case_identifier) @name)) @definition.function - -(function_call_expr (value_expr (value_qid) @name)) @reference.function -(exposed_value (lower_case_identifier) @name) @reference.function -(type_annotation ((lower_case_identifier) @name) (colon)) @reference.function - -(type_declaration ((upper_case_identifier) @name) ) @definition.type - -(type_ref (upper_case_qid (upper_case_identifier) @name)) @reference.type -(exposed_type (upper_case_identifier) @name) @reference.type - -(type_declaration (union_variant (upper_case_identifier) @name)) @definition.union - -(value_expr (upper_case_qid (upper_case_identifier) @name)) @reference.union - - -(module_declaration - (upper_case_qid (upper_case_identifier)) @name -) @definition.module
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/runtime/queries/erlang/tags.scm Thu Feb 27 11:51:12 2025 -0500 @@ -0,0 +1,45 @@ +; Modules +(attribute + name: (atom) @_attr + (arguments (atom) @definition.module) + (#eq? @_attr "module")) + +; Constants +((attribute + name: (atom) @_attr + (arguments + . + [ + (atom) @definition.constant + (call function: [(variable) (atom)] @definition.macro) + ])) + (#eq? @_attr "define")) + +; Record definitions +((attribute + name: (atom) @_attr + (arguments + . + (atom) @definition.struct)) + (#eq? @_attr "record")) + +; Function specs +((attribute + name: (atom) @_attr + (stab_clause name: (atom) @definition.interface)) + (#eq? @_attr "spec")) + +; Types +((attribute + name: (atom) @_attr + (arguments + (binary_operator + left: [ + (atom) @definition.type + (call function: (atom) @definition.type) + ] + operator: "::"))) + (#any-of? @_attr "type" "opaque")) + +; Functions +(function_clause name: (atom) @definition.function)
--- a/runtime/queries/gdscript/tags.scm Tue May 13 20:10:14 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -(class_definition (name) @name) @definition.class - -(function_definition (name) @name) @definition.function - -(call (name) @name) @reference.call \ No newline at end of file
--- a/runtime/queries/gjs/tags.scm Tue May 13 20:10:14 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -; inherits: _gjs,_javascript,ecma
--- a/runtime/queries/go/tags.scm Tue May 13 20:10:14 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -( - (comment)* @doc - . - (function_declaration - name: (identifier) @name) @definition.function - (#strip! @doc "^//\\s*") - (#set-adjacent! @doc @definition.function) -) - -( - (comment)* @doc - . - (method_declaration - name: (field_identifier) @name) @definition.method - (#strip! @doc "^//\\s*") - (#set-adjacent! @doc @definition.method) -) - -(call_expression - function: [ - (identifier) @name - (parenthesized_expression (identifier) @name) - (selector_expression field: (field_identifier) @name) - (parenthesized_expression (selector_expression field: (field_identifier) @name)) - ]) @reference.call - -(type_spec - name: (type_identifier) @name) @definition.type - -(type_identifier) @name @reference.type
--- a/runtime/queries/gts/tags.scm Tue May 13 20:10:14 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -; inherits: _gjs,_typescript,ecma
--- a/runtime/queries/javascript/tags.scm Tue May 13 20:10:14 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -; See runtime/queries/ecma/README.md for more info. - -; inherits: _javascript,ecma
--- a/runtime/queries/jsx/tags.scm Tue May 13 20:10:14 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -; See runtime/queries/ecma/README.md for more info. - -; inherits: _jsx,_javascript,ecma
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/runtime/queries/markdown/tags.scm Thu Feb 27 11:51:12 2025 -0500 @@ -0,0 +1,2 @@ +; TODO: have symbol types for markup? +(atx_heading) @definition.class
--- a/runtime/queries/php-only/tags.scm Tue May 13 20:10:14 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -(namespace_definition - name: (namespace_name) @name) @module - -(interface_declaration - name: (name) @name) @definition.interface - -(trait_declaration - name: (name) @name) @definition.interface - -(class_declaration - name: (name) @name) @definition.class - -(class_interface_clause [(name) (qualified_name)] @name) @impl - -(property_declaration - (property_element (variable_name (name) @name))) @definition.field - -(function_definition - name: (name) @name) @definition.function - -(method_declaration - name: (name) @name) @definition.function - -(object_creation_expression - [ - (qualified_name (name) @name) - (variable_name (name) @name) - ]) @reference.class - -(function_call_expression - function: [ - (qualified_name (name) @name) - (variable_name (name)) @name - ]) @reference.call - -(scoped_call_expression - name: (name) @name) @reference.call - -(member_call_expression - name: (name) @name) @reference.call
--- a/runtime/queries/php/tags.scm Tue May 13 20:10:14 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -(class_declaration - name: (name) @name) @definition.class - -(function_definition - name: (name) @name) @definition.function - -(method_declaration - name: (name) @name) @definition.function - -(object_creation_expression - [ - (qualified_name (name) @name) - (variable_name (name) @name) - ]) @reference.class - -(function_call_expression - function: [ - (qualified_name (name) @name) - (variable_name (name)) @name - ]) @reference.call - -(scoped_call_expression - name: (name) @name) @reference.call - -(member_call_expression - name: (name) @name) @reference.call
--- a/runtime/queries/python/tags.scm Tue May 13 20:10:14 2025 -0400 +++ b/runtime/queries/python/tags.scm Thu Feb 27 11:51:12 2025 -0500 @@ -1,12 +1,5 @@ -(class_definition - name: (identifier) @name) @definition.class - (function_definition - name: (identifier) @name) @definition.function + name: (identifier) @definition.function) -(call - function: [ - (identifier) @name - (attribute - attribute: (identifier) @name) - ]) @reference.call +(class_definition + name: (identifier) @definition.class)
--- a/runtime/queries/ruby/tags.scm Tue May 13 20:10:14 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -; Method definitions - -( - (comment)* @doc - . - [ - (method - name: (_) @name) @definition.method - (singleton_method - name: (_) @name) @definition.method - ] - (#strip! @doc "^#\\s*") - (#select-adjacent! @doc @definition.method) -) - -(alias - name: (_) @name) @definition.method - -(setter - (identifier) @ignore) - -; Class definitions - -( - (comment)* @doc - . - [ - (class - name: [ - (constant) @name - (scope_resolution - name: (_) @name) - ]) @definition.class - (singleton_class - value: [ - (constant) @name - (scope_resolution - name: (_) @name) - ]) @definition.class - ] - (#strip! @doc "^#\\s*") - (#select-adjacent! @doc @definition.class) -) - -; Module definitions - -( - (module - name: [ - (constant) @name - (scope_resolution - name: (_) @name) - ]) @definition.module -) - -; Calls - -(call method: (identifier) @name) @reference.call - -( - [(identifier) (constant)] @name @reference.call - (#is-not? local) - (#not-match? @name "^(lambda|load|require|require_relative|__FILE__|__LINE__)$") -)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/runtime/queries/rust/tags.scm Thu Feb 27 11:51:12 2025 -0500 @@ -0,0 +1,26 @@ +(struct_item + name: (type_identifier) @definition.struct) + +(const_item + name: (identifier) @definition.constant) + +(trait_item + name: (type_identifier) @definition.interface) + +(function_item + name: (identifier) @definition.function) + +(function_signature_item + name: (identifier) @definition.function) + +(enum_item + name: (type_identifier) @definition.type) + +(enum_variant + name: (identifier) @definition.struct) + +(mod_item + name: (identifier) @definition.module) + +(macro_definition + name: (identifier) @definition.macro)
--- a/runtime/queries/spicedb/tags.scm Tue May 13 20:10:14 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ -(object_definition - name: (type_identifier) @name) @definition.type - -(type_identifier) @name @reference.type
--- a/runtime/queries/tsx/tags.scm Tue May 13 20:10:14 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -; See runtime/queries/ecma/README.md for more info. - -; inherits: _jsx,_typescript,ecma
--- a/runtime/queries/typescript/tags.scm Tue May 13 20:10:14 2025 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,3 +0,0 @@ -; See runtime/queries/ecma/README.md for more info. - -; inherits: _typescript,ecma
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/runtime/queries/typst/tags.scm Thu Feb 27 11:51:12 2025 -0500 @@ -0,0 +1,6 @@ +; should be a heading +(heading (text) @definition.class) + +; should be a label/reference/tag +(heading (label) @definition.function ) +(content (label) @definition.function)
--- a/xtask/src/main.rs Tue May 13 20:10:14 2025 -0400 +++ b/xtask/src/main.rs Thu Feb 27 11:51:12 2025 -0500 @@ -37,6 +37,7 @@ LanguageData::compile_indent_query(grammar, config)?; LanguageData::compile_textobject_query(grammar, config)?; LanguageData::compile_rainbow_query(grammar, config)?; + LanguageData::compile_tag_query(grammar, config)?; } println!("Query check succeeded");