diff --git a/queries/query/hover.scm b/queries/query/hover.scm index 8498d7d..3822e24 100644 --- a/queries/query/hover.scm +++ b/queries/query/hover.scm @@ -21,11 +21,21 @@ "]" ] @alternation +(field_definition + (identifier) @field) + +(negated_field + (identifier) @field) + (negated_field "!" @negation) (definition/named_node) @capture +(anonymous_node + (string + (string_content)) @anonymous) + (named_node (identifier) @identifier.node) diff --git a/src/handlers/diagnostic.rs b/src/handlers/diagnostic.rs index 7380a1a..3f3fc6e 100644 --- a/src/handlers/diagnostic.rs +++ b/src/handlers/diagnostic.rs @@ -27,7 +27,10 @@ use ts_query_ls::{ use crate::{ Backend, DocumentData, ImportedUri, LanguageData, LspClient, QUERY_LANGUAGE, SymbolInfo, - util::{CAPTURES_QUERY, NodeUtil as _, TextProviderRope, uri_to_basename}, + util::{ + CAPTURES_QUERY, NodeUtil as _, TextProviderRope, remove_unnecessary_escapes, + uri_to_basename, + }, }; use super::code_action::CodeActions; @@ -462,12 +465,12 @@ async fn get_diagnostics_recursively( remove_unnecessary_escapes(&capture_text) }; let sym = SymbolInfo { - label: capture_text.clone(), + label: capture_text, named, }; if !symbols.contains(&sym) { diagnostics.push(Diagnostic { - message: format!("Invalid node type: \"{capture_text}\""), + message: format!("Invalid node type: \"{}\"", sym.label), severity: ERROR_SEVERITY, range, code: DiagnosticCode::InvalidNode.into(), @@ -860,30 +863,6 @@ async fn get_imported_query_diagnostics( items } -fn remove_unnecessary_escapes(input: &str) -> String { - let mut result = String::new(); - let mut chars = input.chars().peekable(); - - while let Some(c) = chars.next() { - if c == '\\' { - match chars.next() { - Some(char @ ('\"' | '\\' | 'n' | 'r' | 't' | '0')) => { - result.push('\\'); - result.push(char); - } - Some(char) => { - result.push(char); - } - None => {} - } - } else { - result.push(c); - } - } - - result -} - fn validate_predicate<'a>( diagnostics: &mut Vec, tree_cursor: &mut TreeCursor<'a>, diff --git a/src/handlers/hover.rs b/src/handlers/hover.rs index af1e0dc..d0aefcd 100644 --- a/src/handlers/hover.rs +++ b/src/handlers/hover.rs @@ -12,7 +12,7 @@ use crate::{ Backend, LspClient, QUERY_LANGUAGE, SymbolInfo, util::{ FORMAT_IGNORE_REGEX, INHERITS_REGEX, NodeUtil, PosUtil, capture_at_pos, - get_imported_module_under_cursor, uri_to_basename, + get_imported_module_under_cursor, remove_unnecessary_escapes, uri_to_basename, }, }; @@ -130,6 +130,93 @@ pub async fn hover( value, }), }) + } else if let Some(language) = language_data.as_ref().map(|ld| &ld.language) { + let syms = (0..language.node_kind_count() as u16) + .filter(|&id| { + if !(language.node_kind_is_visible(id) + || language.node_kind_is_supertype(id)) + || !language.node_kind_is_named(id) + { + return false; + } + language + .node_kind_for_id(id) + .is_some_and(|kind| kind == sym.label) + }) + .collect::>(); + if syms.is_empty() { + None + } else { + Some(Hover { + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value: format!( + "Symbol IDs: {}", + syms.iter() + .map(ToString::to_string) + .collect::>() + .join(", ") + ), + }), + range, + }) + } + } else { + None + } + } + "anonymous" => { + if let Some(language) = language_data.as_ref().map(|ld| &ld.language) { + let string_content = + remove_unnecessary_escapes(&capture_text[1..capture_text.len() - 1]); + let syms = (0..language.node_kind_count() as u16) + .filter(|&id| { + if !language.node_kind_is_visible(id) + || language.node_kind_is_supertype(id) + || language.node_kind_is_named(id) + { + return false; + } + language + .node_kind_for_id(id) + .is_some_and(|kind| kind == string_content) + }) + .collect::>(); + if syms.is_empty() { + None + } else { + Some(Hover { + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value: format!( + "Symbol IDs: {}", + syms.iter() + .map(ToString::to_string) + .collect::>() + .join(", ") + ), + }), + range, + }) + } + } else { + None + } + } + "field" => { + if let Some(language) = language_data.as_ref().map(|ld| &ld.language) { + let sym = (1..=language.field_count() as u16).find(|&id| { + language + .field_name_for_id(id) + .is_some_and(|name| name == capture_text) + }); + sym.map(|sym| Hover { + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value: format!("Field ID: {sym}"), + }), + range, + }) } else { None } @@ -448,6 +535,38 @@ An error node", BTreeMap::from([(String::from("error"), String::from("An error n end: Position::new(0, 21) }, "*Document not found*", BTreeMap::default())] + #[case("(named_node)", Position { line: 0, character: 4 }, Range { + start: Position::new(0, 1), + end: Position::new(0, 11) + }, "Symbol IDs: 39", BTreeMap::default())] + #[case("(MISSING identifier)", Position { line: 0, character: 12 }, Range { + start: Position::new(0, 9), + end: Position::new(0, 19) + }, "Symbol IDs: 6, 7", BTreeMap::default())] + #[case("(definition/named_node)", Position { line: 0, character: 12 }, Range { + start: Position::new(0, 12), + end: Position::new(0, 22) + }, "Symbol IDs: 39", BTreeMap::default())] + #[case("\"MISSING\"", Position { line: 0, character: 0 }, Range { + start: Position::new(0, 0), + end: Position::new(0, 9) + }, "Symbol IDs: 18", BTreeMap::default())] + #[case(r#""MISSING""#, Position { line: 0, character: 2 }, Range { + start: Position::new(0, 0), + end: Position::new(0, 9) + }, "Symbol IDs: 18", BTreeMap::default())] + #[case(r#""MIS\SING""#, Position { line: 0, character: 4 }, Range { + start: Position::new(0, 0), + end: Position::new(0, 10) + }, "Symbol IDs: 18", BTreeMap::default())] + #[case("(missing_node name: (identifier) @variable !type)", Position { line: 0, character: 15 }, Range { + start: Position::new(0, 14), + end: Position::new(0, 18) + }, "Field ID: 1", BTreeMap::default())] + #[case("(missing_node name: (identifier) @variable !type)", Position { line: 0, character: 46 }, Range { + start: Position::new(0, 44), + end: Position::new(0, 48) + }, "Field ID: 5", BTreeMap::default())] #[tokio::test(flavor = "current_thread")] async fn hover( #[case] source: &str, diff --git a/src/util.rs b/src/util.rs index adcd3b5..3719f1e 100644 --- a/src/util.rs +++ b/src/util.rs @@ -727,3 +727,28 @@ pub async fn get_work_done_token( None } } + +/// Remove unnecessary backslashes from the given string content. +pub fn remove_unnecessary_escapes(input: &str) -> String { + let mut result = String::new(); + let mut chars = input.chars().peekable(); + + while let Some(c) = chars.next() { + if c == '\\' { + match chars.next() { + Some(char @ ('\"' | '\\' | 'n' | 'r' | 't' | '0')) => { + result.push('\\'); + result.push(char); + } + Some(char) => { + result.push(char); + } + None => {} + } + } else { + result.push(c); + } + } + + result +}