From 57066924ab1e08d0fed29c1ec7bd1cda11ae635c Mon Sep 17 00:00:00 2001 From: Uros Mrkobrada Date: Sun, 24 Mar 2024 19:06:45 +0100 Subject: [PATCH 01/10] adding different queries --- jinja-lsp-queries/src/lib.rs | 1 + jinja-lsp-queries/src/search/definition.rs | 515 ++++++++++++++++++ .../src/search/jinja_completion.rs | 0 jinja-lsp-queries/src/search/jinja_state.rs | 16 + jinja-lsp-queries/src/search/mod.rs | 45 ++ jinja-lsp-queries/src/search/objects.rs | 134 +++++ jinja-lsp-queries/src/search/parsers.rs | 41 ++ jinja-lsp-queries/src/search/queries.rs | 288 ++++++++++ .../src/search/rust_identifiers.rs | 79 +++ jinja-lsp-queries/src/search/rust_state.rs | 14 + .../src/search/rust_template_completion.rs | 62 +++ jinja-lsp-queries/src/search/templates.rs | 214 ++++++++ jinja-lsp-queries/src/search/test_queries.rs | 259 +++++++++ jinja-lsp/src/lsp_files.rs | 19 +- 14 files changed, 1686 insertions(+), 1 deletion(-) create mode 100644 jinja-lsp-queries/src/search/definition.rs create mode 100644 jinja-lsp-queries/src/search/jinja_completion.rs create mode 100644 jinja-lsp-queries/src/search/jinja_state.rs create mode 100644 jinja-lsp-queries/src/search/mod.rs create mode 100644 jinja-lsp-queries/src/search/objects.rs create mode 100644 jinja-lsp-queries/src/search/parsers.rs create mode 100644 jinja-lsp-queries/src/search/queries.rs create mode 100644 jinja-lsp-queries/src/search/rust_identifiers.rs create mode 100644 jinja-lsp-queries/src/search/rust_state.rs create mode 100644 jinja-lsp-queries/src/search/rust_template_completion.rs create mode 100644 jinja-lsp-queries/src/search/templates.rs create mode 100644 jinja-lsp-queries/src/search/test_queries.rs diff --git a/jinja-lsp-queries/src/lib.rs b/jinja-lsp-queries/src/lib.rs index 02fbfa3..ca76fa8 100644 --- a/jinja-lsp-queries/src/lib.rs +++ b/jinja-lsp-queries/src/lib.rs @@ -2,6 +2,7 @@ pub mod capturer; pub mod lsp_helper; pub mod parsers; pub mod queries; +pub mod search; pub mod test_queries; pub mod to_input_edit; pub mod tree_builder; diff --git a/jinja-lsp-queries/src/search/definition.rs b/jinja-lsp-queries/src/search/definition.rs new file mode 100644 index 0000000..e7446fc --- /dev/null +++ b/jinja-lsp-queries/src/search/definition.rs @@ -0,0 +1,515 @@ +use std::collections::HashMap; + +use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; + +use super::Identifier; + +macro_rules! range_start { + ($self: ident, $capture: ident, $member: ident) => { + let def = $self.last()?; + if let Definition::$member { start, .. } = def { + let end_point = $capture.node.end_position(); + *start = end_point; + $self.current = Current::NoDefinition; + } + }; +} + +macro_rules! range_end { + ($self: ident, $capture: ident, $member: ident) => { + let id = $capture.node.id(); + $self.current = Current::NoDefinition; + let v = $self.end.get_mut(&Current::$member)?; + let item = v.iter().find(|i| i.0 == id); + if item.is_none() { + v.push(($capture.node.id(), $capture.node.start_position())); + } + }; +} + +#[derive(Debug, Clone)] +pub enum Definition { + ForLoop { + id: usize, + key: Identifier, + value: Option, + start: Point, + end: Point, + open_par: bool, + comma: bool, + closed_par: bool, + }, + Set { + id: usize, + key: Identifier, + start: Point, + end: Point, + equals: bool, + }, + With { + id: usize, + keys: Vec, + start: Point, + end: Point, + }, + Macro { + id: usize, + keys: Vec, + start: Point, + end: Point, + }, + Block { + id: usize, + name: Identifier, + start: Point, + end: Point, + }, +} + +impl Definition { + pub fn id(&self) -> &usize { + match &self { + Definition::ForLoop { id, .. } => id, + Definition::Set { id, .. } => id, + Definition::With { id, .. } => id, + Definition::Macro { id, .. } => id, + Definition::Block { id, .. } => id, + } + } + + fn key<'a>(&'a self, ids: &mut Vec<&'a Identifier>) { + match self { + Definition::ForLoop { key, .. } => { + ids.push(key); + } + Definition::Set { key, .. } => { + ids.push(key); + } + Definition::With { keys, .. } => { + for key in keys { + ids.push(key); + } + } + Definition::Macro { keys, .. } => { + for key in keys { + ids.push(key); + } + } + Definition::Block { name, .. } => { + ids.push(name); + } + } + } +} + +#[derive(Default, Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub enum Current { + For, + Set, + With, + Macro, + Block, + #[default] + NoDefinition, +} + +#[derive(Default)] +pub struct JinjaDefinitions { + pub definitions: Vec, + pub current: Current, + pub end: HashMap>, +} + +impl JinjaDefinitions { + pub fn init_end(&mut self) { + self.end.insert(Current::For, vec![]); + self.end.insert(Current::Set, vec![]); + self.end.insert(Current::With, vec![]); + self.end.insert(Current::Macro, vec![]); + self.end.insert(Current::Block, vec![]); + self.end.insert(Current::NoDefinition, vec![]); + } + pub fn last(&mut self) -> Option<&mut Definition> { + self.definitions.last_mut() + } + + pub fn exist(&self, id: usize) -> bool { + self.definitions.iter().any(|item| item.id() == &id) + } + + pub fn add(&mut self, id: usize, current: Current) { + self.current = current; + match current { + Current::For => { + let d = Definition::ForLoop { + id, + key: Identifier::default(), + value: None, + start: Point::default(), + end: Point::default(), + open_par: false, + comma: false, + closed_par: false, + }; + self.definitions.push(d); + } + Current::Set => { + let d = Definition::Set { + id, + key: Identifier::default(), + start: Point::default(), + end: Point::default(), + equals: false, + }; + self.definitions.push(d); + } + Current::With => { + let d = Definition::With { + id, + keys: vec![], + start: Point::default(), + end: Point::default(), + }; + self.definitions.push(d); + } + Current::Macro => { + let d = Definition::Macro { + id, + keys: vec![], + start: Point::default(), + end: Point::default(), + }; + self.definitions.push(d); + } + Current::Block => { + let d = Definition::Block { + id, + name: Identifier::default(), + start: Point::default(), + end: Point::default(), + }; + self.definitions.push(d); + } + Current::NoDefinition => {} + } + } + + fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option<()> { + match name { + "for_start" => { + if !self.exist(capture.node.id()) { + self.add(capture.node.id(), Current::For); + } else { + self.current = Current::NoDefinition; + // + } + } + "for_key" => { + if self.current == Current::For { + let def = self.last()?; + if let Definition::ForLoop { key, .. } = def { + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let content = capture.node.utf8_text(text.as_bytes()).ok()?; + key.name = content.to_string(); + key.start = start; + key.end = end; + } + } + } + "for_value" => { + if self.current == Current::For { + let def = self.last()?; + if let Definition::ForLoop { value, .. } = def { + let mut identifier = Identifier::default(); + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let content = capture.node.utf8_text(text.as_bytes()).ok()?; + identifier.name = content.to_owned(); + identifier.start = start; + identifier.end = end; + *value = Some(identifier); + } + } + } + "for_end" => { + let hm = self.end.get(&Current::For)?; + let all = hm.get(capture.node.id()); + if all.is_none() { + self.current = Current::For; + } else { + self.current = Current::NoDefinition; + } + } + "set" => { + if !self.exist(capture.node.id()) { + self.add(capture.node.id(), Current::Set); + } else { + self.current = Current::NoDefinition; + } + } + "set_identifier" => { + if self.current == Current::Set { + let def = self.last()?; + if let Definition::Set { key, .. } = def { + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let content = capture.node.utf8_text(text.as_bytes()).ok()?; + key.name = content.to_string(); + key.start = start; + key.end = end; + } + } + } + "equals" => { + if self.current == Current::Set { + let def = self.last()?; + if let Definition::Set { equals, .. } = def { + *equals = true; + } + } + } + "endset" => { + let hm = self.end.get(&Current::Set)?; + let all = hm.get(capture.node.id()); + if all.is_none() { + self.current = Current::Set; + } else { + self.current = Current::NoDefinition; + } + } + "with" => { + if !self.exist(capture.node.id()) { + self.add(capture.node.id(), Current::With); + } else { + // self.current = Current::NoDefinition; + } + } + "with_identifier" => { + if self.current == Current::With { + let def = self.last()?; + if let Definition::With { keys, .. } = def { + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let content = capture.node.utf8_text(text.as_bytes()).ok()?; + let key = Identifier::new(content, start, end); + keys.push(key); + } + } + } + "endwith" => { + let hm = self.end.get(&Current::With)?; + let all = hm.get(capture.node.id()); + if all.is_none() { + self.current = Current::With; + } else { + self.current = Current::NoDefinition; + } + } + "block" => { + if !self.exist(capture.node.id()) { + self.add(capture.node.id(), Current::Block); + } else { + self.current = Current::NoDefinition; + } + } + "block_identifier" => { + if self.current == Current::Block { + let def = self.last()?; + if let Definition::Block { name, .. } = def { + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let content = capture.node.utf8_text(text.as_bytes()).ok()?; + name.name = content.to_string(); + name.start = start; + name.end = end; + } + } + } + "endblock" => { + let hm = self.end.get(&Current::Block)?; + let all = hm.get(capture.node.id()); + if all.is_none() { + self.current = Current::Block; + } else { + self.current = Current::NoDefinition; + } + } + "macro" => { + if !self.exist(capture.node.id()) { + self.add(capture.node.id(), Current::Macro); + } else { + self.current = Current::Macro; + // self.current = Current::NoDefinition; + } + } + "macro_identifier" => { + if self.current == Current::Macro { + let def = self.last()?; + if let Definition::Macro { keys, .. } = def { + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let content = capture.node.utf8_text(text.as_bytes()).ok()?; + let key = Identifier::new(content, start, end); + keys.push(key); + } + } + } + "endmacro" => { + let hm = self.end.get(&Current::Macro)?; + let all = hm.get(capture.node.id()); + if all.is_none() { + self.current = Current::Macro; + } else { + self.current = Current::NoDefinition; + } + } + "range_start" => { + if self.current == Current::For { + range_start!(self, capture, ForLoop); + } else if self.current == Current::Set { + range_start!(self, capture, Set); + } else if self.current == Current::With { + range_start!(self, capture, With); + } else if self.current == Current::Block { + range_start!(self, capture, Block); + } else if self.current == Current::Macro { + range_start!(self, capture, Macro); + } + } + "range_end" => { + if self.current == Current::For { + range_end!(self, capture, For); + } else if self.current == Current::Set { + range_end!(self, capture, Set); + } else if self.current == Current::With { + range_end!(self, capture, With); + } else if self.current == Current::Block { + range_end!(self, capture, Block); + } else if self.current == Current::Macro { + range_end!(self, capture, Macro); + } + } + _ => (), + }; + + None + } + + pub fn fix_end(&mut self) { + self.fix_for_loop(); + self.fix_set(); + self.fix_with(); + self.fix_block(); + self.fix_macro(); + } + + fn fix_for_loop(&mut self) { + let mut for_loops: Vec<_> = self + .definitions + .iter_mut() + .filter(|item| matches!(item, Definition::ForLoop { .. })) + .collect(); + for_loops.reverse(); + let ended = self.end.get_mut(&Current::For).unwrap(); + for (index, reversed) in ended.iter().enumerate() { + if let Some(Definition::ForLoop { end, .. }) = for_loops.get_mut(index) { + *end = reversed.1; + } + } + } + + fn fix_set(&mut self) { + let mut set_block: Vec<_> = self + .definitions + .iter_mut() + .filter(|item| matches!(item, Definition::Set { equals: false, .. })) + .collect(); + set_block.reverse(); + let ended = self.end.get_mut(&Current::Set).unwrap(); + for (index, reversed) in ended.iter().enumerate() { + if let Some(Definition::Set { end, .. }) = set_block.get_mut(index) { + *end = reversed.1; + } + } + } + + fn fix_with(&mut self) { + let mut with_block: Vec<_> = self + .definitions + .iter_mut() + .filter(|item| matches!(item, Definition::With { .. })) + .collect(); + with_block.reverse(); + let ended = self.end.get_mut(&Current::With).unwrap(); + for (index, reversed) in ended.iter().enumerate() { + if let Some(Definition::With { end, .. }) = with_block.get_mut(index) { + *end = reversed.1; + } + } + } + + fn fix_block(&mut self) { + let mut block: Vec<_> = self + .definitions + .iter_mut() + .filter(|item| matches!(item, Definition::Block { .. })) + .collect(); + block.reverse(); + let ended = self.end.get_mut(&Current::Block).unwrap(); + for (index, reversed) in ended.iter().enumerate() { + if let Some(Definition::Block { end, .. }) = block.get_mut(index) { + *end = reversed.1; + } + } + } + + fn fix_macro(&mut self) { + let mut macro_block: Vec<_> = self + .definitions + .iter_mut() + .filter(|item| matches!(item, Definition::Macro { .. })) + .collect(); + macro_block.reverse(); + let ended = self.end.get_mut(&Current::Macro).unwrap(); + for (index, reversed) in ended.iter().enumerate() { + if let Some(Definition::Macro { end, .. }) = macro_block.get_mut(index) { + *end = reversed.1; + } + } + } + + pub fn identifiers(&self) -> Vec<&Identifier> { + let mut ids = vec![]; + for id in &self.definitions { + id.key(&mut ids); + } + ids + } +} + +pub fn definition_query( + query: &Query, + tree: Tree, + trigger_point: Point, + text: &str, + all: bool, +) -> JinjaDefinitions { + let closest_node = tree.root_node(); + let mut definitions = JinjaDefinitions::default(); + definitions.init_end(); + let mut cursor_qry = QueryCursor::new(); + let capture_names = query.capture_names(); + let matches = cursor_qry.matches(&query, closest_node, text.as_bytes()); + let captures = matches.into_iter().flat_map(|m| { + m.captures + .iter() + .filter(|capture| all || capture.node.start_position() <= trigger_point) + }); + for capture in captures { + let name = &capture_names[capture.index as usize]; + definitions.check(name, capture, text); + } + definitions.fix_end(); + definitions +} diff --git a/jinja-lsp-queries/src/search/jinja_completion.rs b/jinja-lsp-queries/src/search/jinja_completion.rs new file mode 100644 index 0000000..e69de29 diff --git a/jinja-lsp-queries/src/search/jinja_state.rs b/jinja-lsp-queries/src/search/jinja_state.rs new file mode 100644 index 0000000..9732927 --- /dev/null +++ b/jinja-lsp-queries/src/search/jinja_state.rs @@ -0,0 +1,16 @@ +use super::{definition::JinjaDefinitions, objects::JinjaObjects, templates::JinjaImports}; + +#[derive(Default)] +pub struct JinjaState { + jinja_definitions: JinjaDefinitions, + jinja_objects: JinjaObjects, + jinja_imports: JinjaImports, +} + +impl JinjaState { + pub fn reset(&mut self) { + self.jinja_definitions = Default::default(); + self.jinja_objects = Default::default(); + self.jinja_imports = Default::default(); + } +} diff --git a/jinja-lsp-queries/src/search/mod.rs b/jinja-lsp-queries/src/search/mod.rs new file mode 100644 index 0000000..52a9a57 --- /dev/null +++ b/jinja-lsp-queries/src/search/mod.rs @@ -0,0 +1,45 @@ +use tree_sitter::Point; + +pub mod definition; +pub mod jinja_completion; +pub mod jinja_state; +pub mod objects; +pub mod parsers; +pub mod queries; +pub mod rust_identifiers; +pub mod rust_state; +pub mod rust_template_completion; +pub mod templates; +pub mod test_queries; + +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct Identifier { + pub start: Point, + pub end: Point, + pub name: String, +} + +impl Identifier { + pub fn new(name: &str, start: Point, end: Point) -> Self { + Self { + name: String::from(name), + start, + end, + } + } +} + +#[derive(PartialEq, Debug)] +pub enum CompletionType { + Filter, + Identifier, + IncludedTemplate { name: String, range: (Point, Point) }, +} + +pub fn completion_start(trigger_point: Point, identifier: &Identifier) -> Option<&str> { + let len = identifier.name.len(); + let diff = identifier.end.column - trigger_point.column; + let to = len - diff; + let s = identifier.name.get(0..to); + s +} diff --git a/jinja-lsp-queries/src/search/objects.rs b/jinja-lsp-queries/src/search/objects.rs new file mode 100644 index 0000000..b4a4465 --- /dev/null +++ b/jinja-lsp-queries/src/search/objects.rs @@ -0,0 +1,134 @@ +use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; + +use super::CompletionType; + +#[derive(Default, Debug, Clone)] +pub struct JinjaObject { + pub name: String, + pub location: (Point, Point), + fields: Vec<(String, (Point, Point))>, +} + +impl JinjaObject { + pub fn new(name: String, start: Point, end: Point) -> Self { + Self { + name, + location: (start, end), + fields: vec![], + } + } + + pub fn add_field(&mut self, field: String, start: Point, end: Point) { + self.fields.push((field, (start, end))); + } +} + +#[derive(Default, Debug)] +pub struct JinjaObjects { + objects: Vec, + dot: (Point, Point), + pipe: (Point, Point), + expr: (Point, Point), + ident: (Point, Point), +} + +impl JinjaObjects { + fn check(&mut self, name: &str, capture: &QueryCapture<'_>, source: &str) -> Option<()> { + let start = capture.node.start_position(); + let end = capture.node.end_position(); + match name { + "just_id" => { + self.build_object(capture, source); + } + "dot" => { + self.dot = (start, end); + } + "pipe" => { + let content = capture.node.utf8_text(source.as_bytes()).ok()?; + if content.starts_with('|') { + self.pipe = (start, end); + } + } + "expr" => { + self.expr = (start, end); + } + _ => (), + } + None + } + + pub fn build_object(&mut self, capture: &QueryCapture<'_>, source: &str) { + let value = capture.node.utf8_text(source.as_bytes()); + let start = capture.node.start_position(); + let end = capture.node.end_position(); + if let Ok(value) = value { + if start.row == self.dot.1.row && start.column == self.dot.1.column { + match self + .objects + .last_mut() + .map(|last| { + last.fields.push((String::from(value), (start, end))); + self.ident = (start, end); + }) + .is_none() + { + true => { + self.objects + .push(JinjaObject::new(String::from(value), start, end)); + self.ident = (start, end); + } + false => (), + } + } else { + self.objects + .push(JinjaObject::new(String::from(value), start, end)); + self.ident = (start, end); + } + } + } + + pub fn completion(&self, trigger_point: Point) -> Option { + if self.in_pipe(trigger_point) { + return Some(CompletionType::Filter); + } else if self.in_expr(trigger_point) { + return Some(CompletionType::Identifier); + } + None + } + + pub fn in_pipe(&self, trigger_point: Point) -> bool { + trigger_point >= self.pipe.0 && trigger_point <= self.pipe.1 + } + + pub fn in_expr(&self, trigger_point: Point) -> bool { + trigger_point >= self.expr.0 && trigger_point <= self.expr.1 && trigger_point > self.ident.1 + } + + pub fn show(&self) -> Vec { + self.objects.clone() + } +} + +pub fn objects_query( + query: &Query, + tree: Tree, + trigger_point: Point, + text: &str, + all: bool, +) -> JinjaObjects { + let closest_node = tree.root_node(); + let mut objects = JinjaObjects::default(); + let mut cursor_qry = QueryCursor::new(); + let capture_names = query.capture_names(); + let matches = cursor_qry.matches(&query, closest_node, text.as_bytes()); + let captures = matches.into_iter().flat_map(|m| { + m.captures + .iter() + .filter(|capture| all || capture.node.start_position() <= trigger_point) + }); + for capture in captures { + let name = &capture_names[capture.index as usize]; + objects.check(name, capture, text); + } + objects +} diff --git a/jinja-lsp-queries/src/search/parsers.rs b/jinja-lsp-queries/src/search/parsers.rs new file mode 100644 index 0000000..c2b8b46 --- /dev/null +++ b/jinja-lsp-queries/src/search/parsers.rs @@ -0,0 +1,41 @@ +use tree_sitter::{Parser, Tree}; + +pub enum LangType { + Template, + Backend, +} + +pub struct Parsers { + jinja: Parser, + backend: Parser, +} + +impl Parsers { + pub fn parse( + &mut self, + lang_type: LangType, + text: &str, + old_tree: Option<&Tree>, + ) -> Option { + match lang_type { + LangType::Template => self.jinja.parse(text, old_tree), + LangType::Backend => self.backend.parse(text, old_tree), + } + } +} + +impl Default for Parsers { + fn default() -> Self { + let mut jinja = Parser::new(); + let _ = jinja.set_language(tree_sitter_jinja2::language()); + let mut backend = Parser::new(); + let _ = backend.set_language(tree_sitter_rust::language()); + Self { jinja, backend } + } +} + +impl Clone for Parsers { + fn clone(&self) -> Self { + Self::default() + } +} diff --git a/jinja-lsp-queries/src/search/queries.rs b/jinja-lsp-queries/src/search/queries.rs new file mode 100644 index 0000000..5bde320 --- /dev/null +++ b/jinja-lsp-queries/src/search/queries.rs @@ -0,0 +1,288 @@ +use tree_sitter::Query; + +#[derive(Debug)] +pub struct Queries { + pub jinja_definitions: Query, + pub jinja_objects: Query, + pub jinja_imports: Query, + pub rust_definitions: Query, + pub rust_templates: Query, +} + +impl Clone for Queries { + fn clone(&self) -> Self { + Self::default() + } +} + +impl Default for Queries { + fn default() -> Self { + Self { + jinja_definitions: Query::new(tree_sitter_jinja2::language(), DEFINITIONS).unwrap(), + jinja_objects: Query::new(tree_sitter_jinja2::language(), OBJECTS).unwrap(), + rust_definitions: Query::new(tree_sitter_rust::language(), RUST_DEFINITIONS).unwrap(), + jinja_imports: Query::new(tree_sitter_jinja2::language(), JINJA_IMPORTS).unwrap(), + rust_templates: Query::new(tree_sitter_rust::language(), RUST_TEMPLATES).unwrap(), + } + } +} + +const DEFINITIONS: &str = r#" + +( + [ + (statement + (statement_begin) + (keyword) @for_keyword + [ + ( + (operator)? @open_par + (identifier)? @for_key + . + (operator)? @comma + . + (identifier)? @for_value + . + (operator)? @close_par + (_). + ) @for2 + + ( + (identifier) @for_key + ) @for1 + ] + + + (#eq? @open_par "\(") + (#match-eq? @comma ",") + (#eq? @close_par "\)") + (#not-match? @for_key "(^\\d+$)") + (#not-match? @for_value "(^\\d+$)") + + (keyword) @in + (#eq @in "in") + (#eq? @for_keyword "for") + (identifier) @for_items + (_)? @other + (statement_end) @range_start + ) @for_start + + ( + (statement + (statement_begin) @range_end + (keyword) @end_keyword + (statement_end) + (#eq? @end_keyword "endfor") + ) + ) @for_end + + + ( + (statement + (statement_begin) + (keyword) @set_keyword + (identifier) @set_identifier + (operator)? @equals + (_)? @others + (statement_end) @range_start + + (#eq? @set_keyword "set") + (#not-match? @set_identifier "(^\\d+$)") + (#eq? @equals "= ") + ) + ) @set + + ( + (statement + (statement_begin) @range_end + (keyword) @endset_keyword + (statement_end) + (#eq? @endset_keyword "endset") + ) + ) @endset + + (statement + (statement_begin) + (keyword) @with_keyword + (identifier) @with_identifier + (#eq? @with_keyword "with") + (#not-match? @with_identifier "(^\\d+$)") + (statement_end) @range_start + ) @with + + (statement + (statement_begin) @range_end + (keyword) @end_with + (#eq? @end_with "endwith") + (statement_end) + ) @endwith + + (statement + (statement_begin) + (keyword) @macro_keyword + (identifier) @macro_identifier + (#eq? @macro_keyword "macro") + (#not-match? @macro_identifier "(^\\d+$)") + (statement_end) @range_start + ) @macro + + (statement + (statement_begin) @range_end + (keyword) @endmacro_keyword + (#eq? @endmacro_keyword "endmacro") + (statement_end) + ) @endmacro + + (statement + (statement_begin) + (keyword) @block_keyword + (identifier) @block_identifier + (#eq? @block_keyword "block") + (#not-match? @block_identifier "(^\\d+$)") + (statement_end) @range_start + ) @block + + (statement + (statement_begin) @range_end + (keyword) @end_block_keyword + (#eq? @end_block_keyword "endblock") + (statement_end) + ) @endblock + ] +) + +"#; + +const OBJECTS: &str = r#" + ( + [ + ( + (operator) @dot + (#eq? @dot "\.") + ) + + ( + (identifier) @just_id + (#not-match? @just_id "(^\\d+$)") + ) + + ( + (operator) @pipe + ) + + (expression) @expr + + ] +) +"#; + +pub static RUST_DEFINITIONS: &str = r#" +([ + (macro_invocation + (identifier) @context + (#eq? @context "context") + ) @macro + + (token_tree + (identifier) @key_id + (#not-eq? @key_id "context") + ) + + ( + (field_expression + (identifier) @jinja + (field_identifier) @method + ) + (arguments + (string_literal) @name + ) + + (#eq? @jinja "jinja") + (#any-of? @method "add_global" add_filter" add_function") + + ) @function +]) +"#; + +const JINJA_IMPORTS: &str = r#" + +( + [ + + (statement + (statement_begin) + (keyword) @extends_keyword + (string) @template_name + (statement_end) + (#eq? @extends_keyword "extends") + ) @extends + + + (statement + (statement_begin) + (keyword) @include_keyword + (string) @template_name + (statement_end) + (#eq? @include_keyword "include") + ) @include + + (statement + (statement_begin) + (keyword) @from_keyword + (string) @template_name + (keyword)? @import_keyword + (identifier)? @import_identifier + (#not-match? @import_identifier "(^\\d)") + (statement_end) + (#eq? @from_keyword "from") + (#eq? @import_keyword "import") + ) @from + + + (statement + (statement_begin) + (keyword) @import_keyword + (string) @template_name + (identifier)? @as_keyword + (identifier)? @import_identifier + (#not-match? @import_identifier "(^\\d)") + (#eq? @import_keyword "import") + (#eq? @as_keyword "as") + (statement_end) + ) @import + ] +) +"#; + +const RUST_TEMPLATES: &str = r#" +(call_expression + [ + (field_expression + (field_identifier) @method_name + ) + (identifier) @method_name + ] + (arguments + (string_literal)+ @template_name + ) + (#any-of? @method_name "render_jinja" "get_template") +) +"#; + +const JINJA_SNIPPETS: &str = r#" +[ + (statement) @block + (ERROR + (ERROR) + ) @error1 + + ( + (keyword) @missing + (#eq? @missing "") + ) + + ( + (keyword) @longer_keyword + ) +] +"#; diff --git a/jinja-lsp-queries/src/search/rust_identifiers.rs b/jinja-lsp-queries/src/search/rust_identifiers.rs new file mode 100644 index 0000000..2d1b5c1 --- /dev/null +++ b/jinja-lsp-queries/src/search/rust_identifiers.rs @@ -0,0 +1,79 @@ +use std::collections::HashMap; + +use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; + +use super::Identifier; + +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub enum Current { + InMacro(Point), + #[default] + Free, +} + +#[derive(Default, Debug, Clone)] +pub struct RustIdentifiers { + variables: Vec, + current: Current, +} + +impl RustIdentifiers { + pub fn show(&self) -> &Vec { + &self.variables + } + + pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option<()> { + match name { + "macro" => { + let end = capture.node.end_position(); + self.current = Current::InMacro(end); + } + "key_id" => { + if let Current::InMacro(end_macro) = self.current { + let start = capture.node.start_position(); + let end = capture.node.end_position(); + if start > end_macro { + self.current = Current::Free; + return None; + } + let name = capture.node.utf8_text(text.as_bytes()).ok()?; + let identifier = Identifier::new(name, start, end); + self.variables.push(identifier); + } + } + "name" => { + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let name = capture.node.utf8_text(text.as_bytes()).ok()?; + let identifier = Identifier::new(name, start, end); + self.variables.push(identifier); + } + _ => (), + } + None + } +} + +pub fn rust_definition_query( + query: &Query, + tree: Tree, + trigger_point: Point, + text: &str, + all: bool, +) -> RustIdentifiers { + let closest_node = tree.root_node(); + let mut cursor_qry = QueryCursor::new(); + let mut rust = RustIdentifiers::default(); + let capture_names = query.capture_names(); + let matches = cursor_qry.matches(&query, closest_node, text.as_bytes()); + let captures = matches.into_iter().flat_map(|m| { + m.captures + .iter() + .filter(|capture| all || capture.node.start_position() <= trigger_point) + }); + for capture in captures { + let name = &capture_names[capture.index as usize]; + rust.check(name, capture, text); + } + rust +} diff --git a/jinja-lsp-queries/src/search/rust_state.rs b/jinja-lsp-queries/src/search/rust_state.rs new file mode 100644 index 0000000..6b29dca --- /dev/null +++ b/jinja-lsp-queries/src/search/rust_state.rs @@ -0,0 +1,14 @@ +use super::{rust_identifiers::RustIdentifiers, rust_template_completion::RustTemplates}; + +#[derive(Default)] +pub struct RustState { + rust_identifiers: RustIdentifiers, + rust_templates: RustTemplates, +} + +impl RustState { + pub fn reset(&mut self) { + self.rust_identifiers = RustIdentifiers::default(); + self.rust_templates = RustTemplates::default(); + } +} diff --git a/jinja-lsp-queries/src/search/rust_template_completion.rs b/jinja-lsp-queries/src/search/rust_template_completion.rs new file mode 100644 index 0000000..8c5f3b5 --- /dev/null +++ b/jinja-lsp-queries/src/search/rust_template_completion.rs @@ -0,0 +1,62 @@ +use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; + +use super::Identifier; + +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct RustTemplateCompletion { + pub template_name: Identifier, +} + +#[derive(Default, Debug, Clone, PartialEq, Eq)] +pub struct RustTemplates { + pub templates: Vec, +} + +impl RustTemplates { + pub fn in_template(&self, trigger_point: Point) -> Option<&Identifier> { + let last = self.templates.last()?; + if trigger_point >= last.start && trigger_point <= last.end { + Some(last) + } else { + None + } + } + + pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option<()> { + if name == "template_name" { + let template = capture.node.utf8_text(text.as_bytes()).ok()?; + let template = template.replace(['\"', '\''], ""); + let mut start = capture.node.start_position(); + start.column += 1; + let mut end = capture.node.end_position(); + end.column -= 1; + let identifer = Identifier::new(&template, start, end); + self.templates.push(identifer); + } + None + } +} + +pub fn rust_templates_query( + query: &Query, + tree: Tree, + trigger_point: Point, + text: &str, + all: bool, +) -> RustTemplates { + let mut templates = RustTemplates::default(); + let closest_node = tree.root_node(); + let mut cursor_qry = QueryCursor::new(); + let capture_names = query.capture_names(); + let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); + let captures = matches.into_iter().flat_map(|m| { + m.captures + .iter() + .filter(|capture| all || capture.node.start_position() <= trigger_point) + }); + for capture in captures { + let name = &capture_names[capture.index as usize]; + templates.check(name, capture, text); + } + templates +} diff --git a/jinja-lsp-queries/src/search/templates.rs b/jinja-lsp-queries/src/search/templates.rs new file mode 100644 index 0000000..a3df1ad --- /dev/null +++ b/jinja-lsp-queries/src/search/templates.rs @@ -0,0 +1,214 @@ +use std::collections::HashMap; + +use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; + +use super::Identifier; + +#[derive(Debug)] +pub enum Import { + Extends { + template: Identifier, + }, + Include { + templates: Vec, + }, + From { + template: Identifier, + identifiers: Vec, + }, + Import { + template: Identifier, + identifier: Identifier, + }, +} + +impl Import { + pub fn get_name(&self, trigger_point: Point) -> Option<&str> { + match &self { + Import::Extends { template } + | Import::From { template, .. } + | Import::Import { template, .. } => { + if trigger_point >= template.start && trigger_point <= template.end { + Some(&template.name) + } else { + None + } + } + Import::Include { templates } => { + let template = templates.iter().find(|template| { + trigger_point >= template.start && trigger_point <= template.end + })?; + Some(&template.name) + } + } + } +} + +#[derive(Default)] +pub enum Current { + Id(usize), + #[default] + Nothing, +} + +#[derive(Default)] +pub struct JinjaImports { + pub imports: HashMap, + pub current: Current, + pub last_id: usize, +} + +impl JinjaImports { + pub fn in_template(&self, trigger_point: Point) -> Option<&Import> { + if let Current::Id(id) = self.current { + let last = self.imports.get(&id)?; + return Some(last); + } + None + } + pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option<()> { + match name { + "extends" => { + let id = capture.node.id(); + let last = self.imports.get_mut(&id); + if last.is_some() { + self.current = Current::Nothing; + } else { + let import = Import::Extends { + template: Identifier::default(), + }; + self.imports.insert(id, import); + self.current = Current::Id(id); + self.last_id = id; + } + } + "include" => { + let id = capture.node.id(); + let last = self.imports.get_mut(&id); + if last.is_some() { + self.current = Current::Id(id); + } else { + let import = Import::Include { templates: vec![] }; + self.imports.insert(id, import); + self.current = Current::Id(id); + self.last_id = id; + } + } + "import" => { + let id = capture.node.id(); + let last = self.imports.get_mut(&id); + if last.is_some() { + self.current = Current::Id(id); + } else { + let import = Import::Import { + template: Identifier::default(), + identifier: Identifier::default(), + }; + self.imports.insert(id, import); + self.current = Current::Id(id); + self.last_id = id; + } + } + "from" => { + let id = capture.node.id(); + let last = self.imports.get_mut(&id); + if last.is_some() { + self.current = Current::Id(id); + } else { + let import = Import::From { + template: Identifier::default(), + identifiers: vec![], + }; + self.imports.insert(id, import); + self.current = Current::Id(id); + self.last_id = id; + } + } + "template_name" => { + if let Current::Id(id) = self.current { + let last = self.imports.get_mut(&id)?; + let name = capture.node.utf8_text(text.as_bytes()).ok()?; + let name = name.replace(['\"', '\''], ""); + let mut start = capture.node.start_position(); + start.column += 1; + let mut end = capture.node.end_position(); + end.column -= 1; + match last { + Import::Extends { template } => { + if template.name.is_empty() { + template.name = name; + template.start = start; + template.end = end; + } + } + Import::Include { templates } => { + let template = Identifier::new(&name, start, end); + templates.push(template); + } + Import::From { template, .. } => { + if template.name.is_empty() { + template.name = name; + template.start = start; + template.end = end; + } + } + Import::Import { template, .. } => { + if template.name.is_empty() { + template.name = name; + template.start = start; + template.end = end; + } + } + } + } + } + "import_identifier" => { + if let Current::Id(id) = self.current { + let name = capture.node.utf8_text(text.as_bytes()).ok()?; + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let last = self.imports.get_mut(&id)?; + match last { + Import::From { identifiers, .. } => { + let identifier = Identifier::new(name, start, end); + identifiers.push(identifier); + } + Import::Import { identifier, .. } => { + if identifier.name.is_empty() { + identifier.name = String::from(name); + self.current = Current::Nothing; + } + } + _ => self.current = Current::Nothing, + } + } + } + + _ => (), + } + None + } +} +pub fn templates_query( + query: &Query, + tree: Tree, + trigger_point: Point, + text: &str, + all: bool, +) -> JinjaImports { + let closest_node = tree.root_node(); + let mut imports = JinjaImports::default(); + let mut cursor_qry = QueryCursor::new(); + let capture_names = query.capture_names(); + let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); + let captures = matches.into_iter().flat_map(|m| { + m.captures + .iter() + .filter(|capture| all || capture.node.start_position() <= trigger_point) + }); + for capture in captures { + let name = &capture_names[capture.index as usize]; + imports.check(name, capture, text); + } + imports +} diff --git a/jinja-lsp-queries/src/search/test_queries.rs b/jinja-lsp-queries/src/search/test_queries.rs new file mode 100644 index 0000000..b1157f3 --- /dev/null +++ b/jinja-lsp-queries/src/search/test_queries.rs @@ -0,0 +1,259 @@ +#[cfg(test)] +mod query_tests { + use tree_sitter::{Parser, Point}; + + use crate::search::{ + completion_start, + definition::definition_query, + objects::{objects_query, JinjaObjects}, + queries::Queries, + rust_identifiers::rust_definition_query, + rust_template_completion::rust_templates_query, + templates::templates_query, + CompletionType, + }; + + fn prepare_jinja_tree(text: &str) -> tree_sitter::Tree { + let language = tree_sitter_jinja2::language(); + let mut parser = Parser::new(); + + parser + .set_language(language) + .expect("could not load jinja grammar"); + + parser.parse(text, None).expect("not to fail") + } + + fn prepare_rust_tree(text: &str) -> tree_sitter::Tree { + let language = tree_sitter_rust::language(); + let mut parser = Parser::new(); + + parser + .set_language(language) + .expect("could not load rust grammar"); + + parser.parse(text, None).expect("not to fail") + } + + #[test] + fn jinja_definitions() { + let cases = [ + ( + r#" + {% macro do_something(a, b,c) %} +

Hello world

+ {% set class = "button" -%} + {% with name = 55 %} +

Hello {{ name }}

+ {% endwith %} + {% endmacro %} + + {% for i in 10 -%} + {%- endfor %} + + {{ point }} + {{ point }} + "#, + 7, + ), + ( + r#" + {% with name = 55 %} +

Hello {{ name }}

+ {% set a = "hello world" %} + {% set b %} + some content + {% endset %} + {% endwith %} + {% for i in 10 -%} + {%- endfor %} + + {{ point }} + {{ point }} + "#, + 4, + ), + ]; + let query = Queries::default(); + let query = query.jinja_definitions; + + for case in cases { + let tree = prepare_jinja_tree(case.0); + let trigger_point = Point::new(0, 0); + // let closest_node = tree.root_node(); + let definitions = definition_query(&query, tree, trigger_point, case.0, true); + assert_eq!(definitions.identifiers().len(), case.1); + } + } + + #[test] + fn jinja_identifiers() { + let query = Queries::default(); + let query = query.jinja_objects; + let cases = [ + ( + r#" + {{ user.id }} + {% for i in 10 -%} + {{ i }} + {%- endfor %} + {% set class = "button" -%} + "#, + 4, + ), + ( + r#" + {{ obj.abc obj2.abc2 }} + + {{ obj3.field.something.something == obj4.something }} + + {% if obj5.field -%} + 111 {{ abc == def.abc }} + {% endif %} + "#, + 7, + ), + ( + r#" +

{{ something }}

+

{{ something | some_filter(a, b,c) }}

+ {% for i in something -%} + {{ i }} + {%- endfor %} + {% if something %} + {{ something }} + {% endif %} + "#, + 11, + ), + ]; + for case in cases { + let tree = prepare_jinja_tree(case.0); + let trigger_point = Point::new(0, 0); + let objects = objects_query(&query, tree, trigger_point, case.0, true); + let len = objects.show().len(); + assert_eq!(len, case.1); + } + } + + #[test] + fn rust_macros() { + let case = r#" + let a = context!(name => 11 + abc, abc => "username"); + let b = context!{name, username => "username" } + let price = 100; + let c = context!{ price }; + jinja.add_filter("running_locally", true); + jinja.add_function("some_fn", some_fn); + "#; + + let tree = prepare_rust_tree(case); + let trigger_point = Point::new(0, 0); + let query = Queries::default(); + let query = &query.rust_definitions; + let rust = rust_definition_query(query, tree, trigger_point, case, true); + assert_eq!(rust.show().len(), 8); + } + + #[test] + fn find_jinja_completion() { + let source = r#" + {{ something | filter1 | filter2 }} + + {% if something == 11 -%} + {% macro example(a, b, c) -%} +

hello world

+ {%- endmacro %} + + {{ }} + {{ "|" }} + "#; + let cases = [ + (Point::new(1, 27), Some(CompletionType::Filter)), + (Point::new(1, 48), None), + (Point::new(1, 40), Some(CompletionType::Filter)), + (Point::new(1, 50), Some(CompletionType::Identifier)), + (Point::new(3, 18), None), + (Point::new(4, 20), None), + (Point::new(3, 22), None), + (Point::new(8, 15), Some(CompletionType::Identifier)), + (Point::new(9, 18), Some(CompletionType::Identifier)), + ]; + for case in cases { + let tree = prepare_jinja_tree(source); + let trigger_point = case.0; + let query = Queries::default(); + let query = &query.jinja_objects; + let objects = objects_query(query, tree, trigger_point, source, false); + assert_eq!(objects.completion(trigger_point), case.1); + // assert!(false); + } + } + + #[test] + fn check_jinja_templates() { + let source = r#" +
+ {% include 'header.jinja' %} + {% include 'customization.jinja' ignore missing %} + {% include ['page_detailed.jinja', 'page.jinja'] %} + {% import "header.jinja" %} + {% from "page_detailed.jinja" import a %} +
+ "#; + + let cases = [ + (Point::new(2, 25), "header.jinja"), + (Point::new(3, 30), "customization.jinja"), + (Point::new(4, 36), "page_detailed.jinja"), + (Point::new(4, 50), "page.jinja"), + (Point::new(5, 34), "header.jinja"), + (Point::new(6, 39), "page_detailed.jinja"), + ]; + for case in cases { + let tree = prepare_jinja_tree(source); + let trigger_point = case.0; + let query = Queries::default(); + let query = &query.jinja_imports; + let templates = templates_query(query, tree, trigger_point, source, false); + let template = templates.in_template(trigger_point); + assert!(template.is_some()); + assert_eq!(template.unwrap().get_name(trigger_point).unwrap(), case.1); + } + } + + #[test] + fn jinja_templates_in_rust() { + let source = r#" + get_template("",11); + render_jinja("some_template.jinja"); + render_jinja(1,2, 3, "some_template.jinja"); + render_jinja(1,2, 3); + "#; + let tree = prepare_rust_tree(source); + let trigger_point = Point::default(); + let query = Queries::default(); + let query = &query.rust_templates; + let templates = rust_templates_query(query, tree, trigger_point, source, true); + assert_eq!(templates.templates.len(), 3); + } + + #[test] + fn template_completion_in_rust() { + let source = r#" + let tmp2 = jinja.get_template("account3"); + let tmp2 = jinja.get_template("account2"); + let tmp = jinja.get_template("account"); + "#; + let tree = prepare_rust_tree(source); + let trigger_point = Point::new(3, 47); + let query = Queries::default(); + let query = &query.rust_templates; + let templates = rust_templates_query(query, tree, trigger_point, source, false); + if let Some(template) = templates.in_template(trigger_point) { + if let Some(completion) = completion_start(trigger_point, template) { + assert_eq!(completion, "accou"); + } + } + } +} diff --git a/jinja-lsp/src/lsp_files.rs b/jinja-lsp/src/lsp_files.rs index 58c04cb..ad7c2c1 100644 --- a/jinja-lsp/src/lsp_files.rs +++ b/jinja-lsp/src/lsp_files.rs @@ -1,4 +1,7 @@ -use jinja_lsp_queries::tree_builder::{DataType, JinjaDiagnostic, JinjaVariable, LangType}; +use jinja_lsp_queries::{ + search::{jinja_state::JinjaState, rust_state::RustState}, + tree_builder::{DataType, JinjaDiagnostic, JinjaVariable, LangType}, +}; use std::{collections::HashMap, fs::read_to_string, path::Path, time::Duration}; use tokio::{sync::mpsc, task::JoinHandle, time::sleep}; use tower_lsp::lsp_types::{ @@ -41,6 +44,8 @@ pub struct LspFiles { pub config: JinjaConfig, pub diagnostics_task: JoinHandle<()>, pub main_channel: Option>, + pub jinja_variables: HashMap, + pub rust_variables: HashMap, } impl LspFiles { @@ -52,6 +57,7 @@ impl LspFiles { let name = format!("file://{}", name); let adding = name.clone(); self.delete_variables(&name); + self.delete_variables2(&name, lang_type); self.documents.insert(name.to_string(), rope); self.add_tree(&name, lang_type, &file_content); self.add_variables(&adding, lang_type, &file_content); @@ -88,6 +94,15 @@ impl LspFiles { Some(()) } + fn delete_variables2(&mut self, name: &str, t: LangType) -> Option<()> { + self.variables.get_mut(name)?.clear(); + match t { + LangType::Template => self.jinja_variables.get_mut(name)?.reset(), + LangType::Backend => self.rust_variables.get_mut(name)?.reset(), + }; + Some(()) + } + fn add_variables(&mut self, name: &str, lang_type: LangType, file_content: &str) -> Option<()> { let trees = self.trees.get(&lang_type).unwrap(); let tree = trees.get(name)?; @@ -578,6 +593,8 @@ impl Default for LspFiles { config: JinjaConfig::default(), diagnostics_task, main_channel, + jinja_variables: HashMap::default(), + rust_variables: HashMap::default(), } } } From d685094ebeb6b218f7cdc9c2b52c0d4a929be073 Mon Sep 17 00:00:00 2001 From: Uros Mrkobrada Date: Thu, 28 Mar 2024 22:36:00 +0100 Subject: [PATCH 02/10] checking for variable scope --- jinja-lsp-queries/Cargo.toml | 8 +- jinja-lsp-queries/src/capturer/capturer.rs | 277 --------- jinja-lsp-queries/src/capturer/included.rs | 172 ------ jinja-lsp-queries/src/capturer/init.rs | 42 -- jinja-lsp-queries/src/capturer/mod.rs | 26 - jinja-lsp-queries/src/capturer/object.rs | 145 ----- jinja-lsp-queries/src/capturer/rust.rs | 100 --- jinja-lsp-queries/src/lib.rs | 4 - jinja-lsp-queries/src/lsp_helper.rs | 181 ++++-- jinja-lsp-queries/src/parsers.rs | 6 +- jinja-lsp-queries/src/queries.rs | 146 ----- jinja-lsp-queries/src/search/definition.rs | 578 +++++++++--------- jinja-lsp-queries/src/search/definition2.rs | 1 + jinja-lsp-queries/src/search/jinja_state.rs | 25 +- jinja-lsp-queries/src/search/mod.rs | 79 ++- jinja-lsp-queries/src/search/objects.rs | 60 +- jinja-lsp-queries/src/search/parsers.rs | 41 -- jinja-lsp-queries/src/search/queries.rs | 106 ++-- .../src/search/rust_identifiers.rs | 19 +- jinja-lsp-queries/src/search/rust_state.rs | 56 ++ .../src/search/rust_template_completion.rs | 29 +- jinja-lsp-queries/src/search/templates.rs | 53 +- jinja-lsp-queries/src/search/test_queries.rs | 106 ++-- jinja-lsp-queries/src/test_queries.rs | 258 -------- jinja-lsp-queries/src/to_input_edit.rs | 15 - jinja-lsp-queries/src/tree_builder.rs | 348 +---------- jinja-lsp/Cargo.toml | 8 +- jinja-lsp/src/channels/diagnostics.rs | 73 +-- jinja-lsp/src/channels/lsp.rs | 15 +- jinja-lsp/src/config.rs | 12 +- jinja-lsp/src/{lsp_files.rs => lsp_files2.rs} | 541 ++++++++-------- jinja-lsp/src/main.rs | 2 +- 32 files changed, 1112 insertions(+), 2420 deletions(-) delete mode 100644 jinja-lsp-queries/src/capturer/capturer.rs delete mode 100644 jinja-lsp-queries/src/capturer/included.rs delete mode 100644 jinja-lsp-queries/src/capturer/init.rs delete mode 100644 jinja-lsp-queries/src/capturer/mod.rs delete mode 100644 jinja-lsp-queries/src/capturer/object.rs delete mode 100644 jinja-lsp-queries/src/capturer/rust.rs delete mode 100644 jinja-lsp-queries/src/queries.rs create mode 100644 jinja-lsp-queries/src/search/definition2.rs delete mode 100644 jinja-lsp-queries/src/search/parsers.rs delete mode 100644 jinja-lsp-queries/src/test_queries.rs rename jinja-lsp/src/{lsp_files.rs => lsp_files2.rs} (56%) diff --git a/jinja-lsp-queries/Cargo.toml b/jinja-lsp-queries/Cargo.toml index b7b42a5..eb7d99a 100644 --- a/jinja-lsp-queries/Cargo.toml +++ b/jinja-lsp-queries/Cargo.toml @@ -1,14 +1,16 @@ [package] name = "jinja-lsp-queries" -version = "0.1.61" +version = "0.1.62" edition = "2021" description = "TreeSitter queries for jinja-lsp" license = "MIT" [dependencies] -tree-sitter = "0.20.10" -tree-sitter-jinja2 = "0.0.5" +tree-sitter = "~0.20.10" +tree-sitter-jinja2 = "0.0.6" tree-sitter-rust = "0.20.4" tower-lsp = { version = "0.20.0", features = ["proposed"] } ropey = "1.5.0" +[build-dependencies] +cc="*" diff --git a/jinja-lsp-queries/src/capturer/capturer.rs b/jinja-lsp-queries/src/capturer/capturer.rs deleted file mode 100644 index 80c2a6c..0000000 --- a/jinja-lsp-queries/src/capturer/capturer.rs +++ /dev/null @@ -1,277 +0,0 @@ -use std::collections::HashMap; - -use tree_sitter::{Node, Point, QueryCapture}; - -use crate::{ - test_queries::CompletionType, - tree_builder::{IdentifierState, JinjaVariable}, -}; - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct CaptureDetails { - pub start_position: Point, - pub end_position: Point, - pub value: String, -} - -pub trait Capturer { - fn save_by( - &mut self, - capture: &QueryCapture<'_>, - hm: &mut HashMap, - capture_names: &[String], - source: &str, - ); - - fn value(&self, capture: &QueryCapture<'_>, source: &str) -> String { - let value = if let Ok(capture_value) = capture.node.utf8_text(source.as_bytes()) { - capture_value.to_owned() - } else { - "".to_owned() - }; - value - } -} - -#[derive(Default)] -pub struct JinjaInitCapturer { - pub data: bool, - pub state: IdentifierState, - pub states: Vec, -} - -impl JinjaInitCapturer { - pub fn id_exist(&self, capture: &QueryCapture<'_>) -> bool { - let id = capture.node.id(); - self.states.iter().any(|item| item.id == id) - } - - pub fn to_vec(&self) -> Vec { - let mut all = vec![]; - for state in &self.states { - state.keyword.get_data(&mut all, state); - } - all - } -} - -impl Capturer for JinjaInitCapturer { - fn save_by( - &mut self, - capture: &QueryCapture<'_>, - hm: &mut HashMap, - capture_names: &[String], - source: &str, - ) { - let key = capture_names[capture.index as usize].to_owned(); - if key == "start_statement" { - if !self.id_exist(capture) { - let mut state = IdentifierState::default(); - state.parse_start_statement(capture, source); - self.states.push(state); - } - } else if key == "end_statement" { - self.state.parse_end_statement(capture, source); - } - } -} - -#[derive(Default, Debug)] -pub struct JinjaObjectCapturer { - objects: Vec, - dot: (Point, Point), - pipe: (Point, Point), - expr: (Point, Point), - ident: (Point, Point), -} - -impl JinjaObjectCapturer { - pub fn show(&self) -> Vec { - self.objects.clone() - } - - fn add_operator(&mut self, capture: &QueryCapture<'_>, dot: u8) { - let start = capture.node.start_position(); - let end = capture.node.end_position(); - if dot == 0 { - self.dot = (start, end); - } else if dot == 1 { - self.pipe = (start, end); - } else if dot == 2 { - self.expr = (start, end); - } - } - - pub fn in_pipe(&self, trigger_point: Point) -> bool { - trigger_point >= self.pipe.0 && trigger_point <= self.pipe.1 - } - - pub fn in_expr(&self, trigger_point: Point) -> bool { - trigger_point >= self.expr.0 && trigger_point <= self.expr.1 && trigger_point > self.ident.1 - } - - pub fn completion(&self, trigger_point: Point) -> Option { - if self.in_pipe(trigger_point) { - return Some(CompletionType::Pipe); - } else if self.in_expr(trigger_point) { - return Some(CompletionType::Identifier); - } - None - } - - pub fn build_object(&mut self, capture: &QueryCapture<'_>, source: &str) { - let value = capture.node.utf8_text(source.as_bytes()); - let start = capture.node.start_position(); - let end = capture.node.end_position(); - if let Ok(value) = value { - if start.row == self.dot.1.row && start.column == self.dot.1.column { - match self - .objects - .last_mut() - .map(|last| { - last.fields.push((String::from(value), (start, end))); - self.ident = (start, end); - }) - .is_none() - { - true => { - self.objects - .push(JinjaObject::new(String::from(value), start, end)); - self.ident = (start, end); - } - false => (), - } - } else { - self.objects - .push(JinjaObject::new(String::from(value), start, end)); - self.ident = (start, end); - } - } - } -} - -impl Capturer for JinjaObjectCapturer { - fn save_by( - &mut self, - capture: &QueryCapture<'_>, - hm: &mut HashMap, - capture_names: &[String], - source: &str, - ) { - let key = capture_names[capture.index as usize].to_owned(); - if key == "just_id" { - self.build_object(capture, source); - } else if key == "dot" { - self.add_operator(capture, 0); - } else if key == "pipe" { - self.add_operator(capture, 1); - } else if key == "expr" { - self.add_operator(capture, 2); - } - } -} - -#[derive(Default, Debug, Clone)] -pub struct JinjaObject { - name: String, - location: (Point, Point), - fields: Vec<(String, (Point, Point))>, -} - -impl JinjaObject { - pub fn new(name: String, start: Point, end: Point) -> Self { - Self { - name, - location: (start, end), - fields: vec![], - } - } - - pub fn add_field(&mut self, field: String, start: Point, end: Point) { - self.fields.push((field, (start, end))); - } -} - -#[derive(Default, Debug, Clone)] -pub struct RustMacro { - variables: HashMap, - id: usize, -} - -impl RustMacro { - pub fn show(&self) -> &HashMap { - &self.variables - } -} - -#[derive(Default, Debug, Clone)] -pub struct RustCapturer { - macros: HashMap, -} - -impl RustCapturer { - pub fn add_macro(&mut self, capture: &QueryCapture<'_>, source: &str) { - let id = capture.node.id(); - if self.macros.get(&id).is_none() { - let mut context_macro = RustMacro::default(); - let mut walker = capture.node.walk(); - let children = capture.node.children(&mut walker); - let mut current = 0; - for child in children { - match child.kind_id() { - 1 => current = 1, - 55 => current = 2, - 152 => { - if current == 2 { - self.check_token_tree(child, &mut context_macro, source); - self.macros.insert(id, context_macro); - break; - } - } - _ => (), - } - } - } - } - - pub fn check_token_tree( - &mut self, - node: Node<'_>, - context_macro: &mut RustMacro, - source: &str, - ) { - let mut walker = node.walk(); - let children = node.children(&mut walker); - for child in children { - if child.kind_id() == 1 { - let text = child.utf8_text(source.as_bytes()); - if let Ok(id) = text { - if context_macro.variables.get(id).is_none() { - let start = child.start_position(); - let end = child.end_position(); - context_macro.variables.insert(id.to_string(), (start, end)); - } - } - } - } - } - - pub fn show(&self) -> &HashMap { - &self.macros - } -} - -impl Capturer for RustCapturer { - fn save_by( - &mut self, - capture: &QueryCapture<'_>, - hm: &mut HashMap, - capture_names: &[String], - source: &str, - ) { - let key = capture_names[capture.index as usize].to_owned(); - if key == "context_macro" { - self.add_macro(capture, source); - } - } -} diff --git a/jinja-lsp-queries/src/capturer/included.rs b/jinja-lsp-queries/src/capturer/included.rs deleted file mode 100644 index 18ebe44..0000000 --- a/jinja-lsp-queries/src/capturer/included.rs +++ /dev/null @@ -1,172 +0,0 @@ -use tower_lsp::lsp_types::{Position, Range, Url}; -use tree_sitter::Point; - -use super::{object::CompletionType, Capturer}; - -#[derive(Default, Debug)] -pub struct IncludeCapturer { - pub included: Vec, -} - -impl IncludeCapturer { - pub fn in_template(&self, trigger_point: Point) -> Option<&IncludedTemplate> { - if let Some(last) = self.included.last() { - if trigger_point >= last.template.0 && trigger_point <= last.template.1 { - return Some(last); - } - } - None - } - - pub fn find(&self, trigger_point: Point) -> Option<(&IncludedTemplate, Include)> { - if let Some(string) = self.in_template(trigger_point) { - return Some((string, Include::Template)); - } - None - } - - pub fn completion(&self, trigger_point: Point) -> Option { - let part = self.find(trigger_point)?; - let location = part.1.location(trigger_point, part.0); - Some(CompletionType::IncludedTemplate { - name: location.0, - range: location.1, - }) - } - - pub fn add_template(&mut self, name: String, range: (Point, Point)) { - self.included.push(IncludedTemplate { - name, - template: range, - ..Default::default() - }); - } - - pub fn last(&self) -> Option<&String> { - Some(&self.included.last()?.name) - } -} - -impl Capturer for IncludeCapturer { - fn save_by( - &mut self, - capture: &tree_sitter::QueryCapture<'_>, - capture_names: &[String], - source: &str, - ) { - let key = capture_names[capture.index as usize].to_owned(); - if key == "keyword" { - self.add_template("".to_owned(), (Point::default(), Point::default())); - } else if key == "error" { - if let Some(last) = self.included.last_mut() { - let start = capture.node.start_position(); - let end = capture.node.end_position(); - last.error = (start, end); - } - } else if key == "id" { - if let Some(last) = self.included.last_mut() { - let start = capture.node.start_position(); - let end = capture.node.end_position(); - last.identifier = (start, end); - if let Ok(value) = capture.node.utf8_text(source.as_bytes()) { - let name = value.replace(['\'', '\"'], ""); - last.name = name; - } - } - } else if key == "template" { - if let Ok(value) = capture.node.utf8_text(source.as_bytes()) { - if let Some(last) = self.included.last_mut() { - let start = capture.node.start_position(); - let end = capture.node.end_position(); - let name = value.replace(['\'', '\"'], ""); - last.name = name; - last.template = (start, end); - } - } - } - } -} - -#[derive(Default, Debug)] -pub struct IncludedTemplate { - pub name: String, - pub template: (Point, Point), - pub error: (Point, Point), - pub identifier: (Point, Point), -} - -impl IncludedTemplate { - pub fn new(name: &str) -> Self { - Self { - name: name.to_owned(), - template: (Point::default(), Point::default()), - ..Default::default() - } - } - - pub fn is_template(&self, root: &str) -> Option { - let template = format!("{}/{}", root, &self.name); - let template = std::fs::canonicalize(template).ok()?; - let url = format!("file://{}", template.to_str()?); - let uri = Url::parse(&url).ok()?; - Some(uri) - } -} - -#[derive(Debug)] -pub enum Include { - Id, - Template, - Error, -} - -impl Include { - pub fn location(&self, trigger_point: Point, part: &IncludedTemplate) -> (String, Range) { - match self { - Include::Error => { - let range = self.to_range(part.error); - (String::from(""), range) - } - Include::Id => { - let l1 = part.identifier.1.column - trigger_point.column; - if part.name.len() < l1 { - let range = self.to_range(part.identifier); - return (String::from(""), range); - } - let end = part.name.len() - l1; - let mut name = String::new(); - for (i, item) in part.name.char_indices() { - name.push(item); - if i == end { - break; - } - } - let range = self.to_range(part.identifier); - (name, range) - } - Include::Template => { - let l1 = part.template.1.column - trigger_point.column; - if part.name.len() < l1 || part.name.is_empty() { - let range = self.to_range(part.template); - return (String::from(""), range); - } - let end = part.name.len() - l1; - let mut name = String::new(); - for (i, item) in part.name.char_indices() { - name.push(item); - if i == end { - break; - } - } - let range = self.to_range(part.template); - (name, range) - } - } - } - - pub fn to_range(&self, points: (Point, Point)) -> Range { - let start = Position::new(points.0.row as u32, points.0.column as u32); - let end = Position::new(points.1.row as u32, points.1.column as u32); - Range::new(start, end) - } -} diff --git a/jinja-lsp-queries/src/capturer/init.rs b/jinja-lsp-queries/src/capturer/init.rs deleted file mode 100644 index f19d9ba..0000000 --- a/jinja-lsp-queries/src/capturer/init.rs +++ /dev/null @@ -1,42 +0,0 @@ -use tree_sitter::QueryCapture; - -use crate::tree_builder::{IdentifierState, JinjaVariable}; - -use super::Capturer; - -#[derive(Default, Debug)] -pub struct JinjaInitCapturer { - pub data: bool, - pub state: IdentifierState, - pub states: Vec, -} - -impl JinjaInitCapturer { - pub fn id_exist(&self, capture: &QueryCapture<'_>) -> bool { - let id = capture.node.id(); - self.states.iter().any(|item| item.id == id) - } - - pub fn to_vec(&self) -> Vec { - let mut all = vec![]; - for state in &self.states { - state.keyword.get_data(&mut all, state); - } - all - } -} - -impl Capturer for JinjaInitCapturer { - fn save_by(&mut self, capture: &QueryCapture<'_>, capture_names: &[String], source: &str) { - let key = capture_names[capture.index as usize].to_owned(); - if key == "start_statement" { - if !self.id_exist(capture) { - let mut state = IdentifierState::default(); - state.parse_start_statement(capture, source); - self.states.push(state); - } - } else if key == "end_statement" { - self.state.parse_end_statement(capture, source); - } - } -} diff --git a/jinja-lsp-queries/src/capturer/mod.rs b/jinja-lsp-queries/src/capturer/mod.rs deleted file mode 100644 index a3a00aa..0000000 --- a/jinja-lsp-queries/src/capturer/mod.rs +++ /dev/null @@ -1,26 +0,0 @@ -pub mod included; -pub mod init; -pub mod object; -pub mod rust; - -use tree_sitter::{Point, QueryCapture}; - -pub trait Capturer { - fn save_by(&mut self, capture: &QueryCapture<'_>, capture_names: &[String], source: &str); - - fn value(&self, capture: &QueryCapture<'_>, source: &str) -> String { - let value = if let Ok(capture_value) = capture.node.utf8_text(source.as_bytes()) { - capture_value.to_owned() - } else { - "".to_owned() - }; - value - } -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct CaptureDetails { - pub start_position: Point, - pub end_position: Point, - pub value: String, -} diff --git a/jinja-lsp-queries/src/capturer/object.rs b/jinja-lsp-queries/src/capturer/object.rs deleted file mode 100644 index 4f167b6..0000000 --- a/jinja-lsp-queries/src/capturer/object.rs +++ /dev/null @@ -1,145 +0,0 @@ -use tower_lsp::lsp_types::Range; -use tree_sitter::{Point, QueryCapture}; - -use super::Capturer; - -#[derive(Default, Debug)] -pub struct JinjaObjectCapturer { - objects: Vec, - dot: (Point, Point), - pipe: (Point, Point), - expr: (Point, Point), - ident: (Point, Point), -} - -impl JinjaObjectCapturer { - pub fn show(&self) -> Vec { - self.objects.clone() - } - - fn add_operator(&mut self, capture: &QueryCapture<'_>, dot: u8) { - let start = capture.node.start_position(); - let end = capture.node.end_position(); - if dot == 0 { - self.dot = (start, end); - } else if dot == 1 { - self.pipe = (start, end); - } else if dot == 2 { - self.expr = (start, end); - } - } - - pub fn in_pipe(&self, trigger_point: Point) -> bool { - trigger_point >= self.pipe.0 && trigger_point <= self.pipe.1 - } - - pub fn in_expr(&self, trigger_point: Point) -> bool { - trigger_point >= self.expr.0 && trigger_point <= self.expr.1 && trigger_point > self.ident.1 - } - - pub fn is_hover(&self, trigger_point: Point) -> bool { - trigger_point >= self.ident.0 - && trigger_point <= self.ident.1 - && self.pipe.1 == self.ident.0 - } - - pub fn is_ident(&self, trigger_point: Point) -> Option { - if trigger_point >= self.ident.0 && trigger_point <= self.ident.1 { - self.objects.last().map(|last| last.name.to_string()) - } else { - None - } - } - - pub fn completion(&self, trigger_point: Point) -> Option { - if self.in_pipe(trigger_point) { - return Some(CompletionType::Filter); - } else if self.in_expr(trigger_point) { - return Some(CompletionType::Identifier); - } - None - } - - pub fn build_object(&mut self, capture: &QueryCapture<'_>, source: &str) { - let value = capture.node.utf8_text(source.as_bytes()); - let start = capture.node.start_position(); - let end = capture.node.end_position(); - if let Ok(value) = value { - if start.row == self.dot.1.row && start.column == self.dot.1.column { - if let Some(last) = self.objects.last_mut() { - last.fields.push((String::from(value), (start, end))); - self.ident = (start, end); - } else { - // TODO: in future add those to main library - if VALID_IDENTIFIERS.contains(&value) { - return; - } - self.ident = (start, end); - let is_filter = self.is_hover(start); - self.objects - .push(JinjaObject::new(String::from(value), start, end, is_filter)); - } - } else { - // TODO: in future add those to main library - if VALID_IDENTIFIERS.contains(&value) { - return; - } - self.ident = (start, end); - let is_filter = self.is_hover(start); - self.objects - .push(JinjaObject::new(String::from(value), start, end, is_filter)); - } - } - } - - pub fn get_last_id(&self) -> Option { - self.objects.last().map(|last| last.name.to_string()) - } -} - -impl Capturer for JinjaObjectCapturer { - fn save_by(&mut self, capture: &QueryCapture<'_>, capture_names: &[String], source: &str) { - let key = capture_names[capture.index as usize].to_owned(); - if key == "just_id" { - self.build_object(capture, source); - } else if key == "dot" { - self.add_operator(capture, 0); - } else if key == "pipe" { - self.add_operator(capture, 1); - } else if key == "expr" { - self.add_operator(capture, 2); - } - } -} - -#[derive(Default, Debug, Clone)] -pub struct JinjaObject { - pub location: (Point, Point), - pub name: String, - pub fields: Vec<(String, (Point, Point))>, - pub is_filter: bool, -} - -impl JinjaObject { - pub fn new(name: String, start: Point, end: Point, is_filter: bool) -> Self { - Self { - name, - location: (start, end), - fields: vec![], - is_filter, - } - } - - pub fn add_field(&mut self, field: String, start: Point, end: Point) { - self.fields.push((field, (start, end))); - } -} - -#[derive(PartialEq, Debug)] -pub enum CompletionType { - Filter, - Identifier, - IncludedTemplate { name: String, range: Range }, -} - -static VALID_IDENTIFIERS: [&str; 4] = ["loop", "true", "false", "not"]; diff --git a/jinja-lsp-queries/src/capturer/rust.rs b/jinja-lsp-queries/src/capturer/rust.rs deleted file mode 100644 index 66c510f..0000000 --- a/jinja-lsp-queries/src/capturer/rust.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::collections::HashMap; -use tree_sitter::Node; - -use tree_sitter::{Point, QueryCapture}; - -use super::Capturer; - -#[derive(Default, Debug, Clone)] -pub struct RustVariables { - variables: HashMap, -} - -impl RustVariables { - pub fn variables(&self) -> &HashMap { - &self.variables - } -} - -#[derive(Default, Debug, Clone)] -pub struct RustCapturer { - macros: HashMap, - variables: Vec<(String, (Point, Point))>, -} - -impl RustCapturer { - pub fn add_macro(&mut self, capture: &QueryCapture<'_>, source: &str) { - let id = capture.node.id(); - if self.macros.get(&id).is_none() { - let mut context_macro = RustVariables::default(); - let mut walker = capture.node.walk(); - let children = capture.node.children(&mut walker); - let mut current = 0; - for child in children { - match child.kind_id() { - 1 => current = 1, - 55 => current = 2, - 152 => { - if current == 2 { - self.check_token_tree(child, &mut context_macro, source); - self.macros.insert(id, context_macro); - break; - } - } - _ => (), - } - } - } - } - - pub fn check_token_tree( - &mut self, - node: Node<'_>, - context_macro: &mut RustVariables, - source: &str, - ) { - let mut walker = node.walk(); - let children = node.children(&mut walker); - for child in children { - if child.kind_id() == 1 { - let text = child.utf8_text(source.as_bytes()); - if let Ok(id) = text { - if context_macro.variables.get(id).is_none() { - let start = child.start_position(); - let end = child.end_position(); - context_macro.variables.insert(id.to_string(), (start, end)); - } - } - } - } - } - - pub fn macros(&self) -> &HashMap { - &self.macros - } - - pub fn variables(&self) -> &Vec<(String, (Point, Point))> { - &self.variables - } - - fn add_name(&mut self, capture: &QueryCapture<'_>, source: &str) { - if let Ok(id) = capture.node.utf8_text(source.as_bytes()) { - let start = capture.node.start_position(); - let end = capture.node.end_position(); - let id = id.to_string(); - let id = id.replace('"', ""); - self.variables.push((id.to_string(), (start, end))); - } - } -} - -impl Capturer for RustCapturer { - fn save_by(&mut self, capture: &QueryCapture<'_>, capture_names: &[String], source: &str) { - let key = capture_names[capture.index as usize].to_owned(); - if key == "context_macro" { - self.add_macro(capture, source); - } else if key == "name" { - self.add_name(capture, source); - } - } -} diff --git a/jinja-lsp-queries/src/lib.rs b/jinja-lsp-queries/src/lib.rs index ca76fa8..db0bb54 100644 --- a/jinja-lsp-queries/src/lib.rs +++ b/jinja-lsp-queries/src/lib.rs @@ -1,9 +1,5 @@ -pub mod capturer; pub mod lsp_helper; pub mod parsers; -pub mod queries; pub mod search; -pub mod test_queries; pub mod to_input_edit; pub mod tree_builder; -pub mod types; diff --git a/jinja-lsp-queries/src/lsp_helper.rs b/jinja-lsp-queries/src/lsp_helper.rs index 0472018..a53aaf3 100644 --- a/jinja-lsp-queries/src/lsp_helper.rs +++ b/jinja-lsp-queries/src/lsp_helper.rs @@ -1,83 +1,138 @@ use std::{collections::HashMap, io::ErrorKind}; -use tree_sitter::{Node, Point}; +use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range}; +use tree_sitter::{Point, Tree}; use crate::{ - capturer::object::JinjaObjectCapturer, - queries::{query_props, Queries}, - tree_builder::{DataType, JinjaDiagnostic, JinjaVariable}, + search::{objects::objects_query, queries::Queries2, Identifier, IdentifierType}, + tree_builder::{JinjaDiagnostic, LangType}, }; -pub fn search_errors( - root: Node<'_>, +pub fn search_errors2( + root: &Tree, source: &str, - query: &Queries, - variables: &HashMap>, + query: &Queries2, + variables: &HashMap>, file_name: &String, templates: &String, -) -> Option> { - let trigger_point = Point::new(0, 0); - let query = &query.jinja_idents; - let capturer = JinjaObjectCapturer::default(); - let props = query_props(root, source, trigger_point, query, true, capturer); - let props = props.show(); - let mut diags = vec![]; - for object in props { - if object.is_filter { - continue; - } - let jinja_variables = variables.get(file_name)?; - let mut exist = false; - let mut err_type = JinjaDiagnostic::Undefined; - let mut to_warn = false; - // variable definition is in this file - let located = jinja_variables - .iter() - .filter(|variable| variable.name == object.name) - .filter(|variable| { - exist = true; - object.location.0 >= variable.location.0 - }); - let empty = located.count() == 0; - if empty && exist { - to_warn = true; - } else if empty { - to_warn = true; - for i in variables { - let temp = i.1.iter().filter(|variable| variable.name == object.name); - - if temp.count() != 0 { - err_type = JinjaDiagnostic::DefinedSomewhere; + lang_type: LangType, +) -> Option> { + let mut diagnostics = vec![]; + match lang_type { + LangType::Template => { + let trigger_point = Point::new(0, 0); + let query = &query.jinja_objects; + let objects = objects_query(query, root, trigger_point, source, true); + let objects = objects.show(); + let this_file = variables.get(file_name)?; + for object in objects { + if object.is_filter { + continue; + } + let mut exist = false; + let mut err_type = JinjaDiagnostic::Undefined; + let mut to_warn = false; + let located = this_file + .iter() + .filter(|variable| { + variable.name == object.name + && variable.identifier_type != IdentifierType::TemplateBlock + }) + .filter(|variable| { + exist = true; + let bigger = object.location.1 >= variable.start; + let global = variable.scope_ends.1 == Point::default(); + let in_scope = object.location.0 < variable.scope_ends.1; + if bigger && global { + true + } else { + bigger && in_scope + } + }); + let empty = located.count() == 0; + if empty && exist { to_warn = true; - break; + } else if empty { + to_warn = true; + for file in variables { + let temp = file + .1 + .iter() + .filter(|variable| variable.name == object.name); + if temp.count() != 0 { + err_type = JinjaDiagnostic::DefinedSomewhere; + to_warn = true; + break; + } + } + } + if to_warn { + let diagnostic = create_diagnostic( + &Identifier::from(&object), + err_type.severity(), + err_type.to_string(), + ); + diagnostics.push(diagnostic); } } + let id_templates = this_file + .iter() + .filter(|identifier| identifier.identifier_type == IdentifierType::JinjaTemplate); + for i in id_templates { + let err_type = JinjaDiagnostic::TemplateNotFound; + if i.name.is_empty() { + let diagnostic = + create_diagnostic(i, err_type.severity(), err_type.to_string()); + diagnostics.push(diagnostic); + } else { + let path = format!("{templates}/{}", i.name); + if let Err(err) = std::fs::canonicalize(path) { + if err.kind() == ErrorKind::NotFound { + let diagnostic = + create_diagnostic(i, err_type.severity(), err_type.to_string()); + diagnostics.push(diagnostic); + } + } + } + } + Some(diagnostics) } - if to_warn { - let variable = JinjaVariable::new(&object.name, object.location, DataType::Variable); - diags.push((variable, err_type)); - } - } - let jinja_variables = variables.get(file_name)?; - let abc = jinja_variables - .iter() - .filter(|variable| variable.data_type == DataType::Template); - for i in abc { - if i.name.is_empty() { - diags.push((i.clone(), JinjaDiagnostic::TemplateNotFound)); - } else { - let path = format!("{templates}/{}", i.name); - if let Err(err) = std::fs::canonicalize(path) { - if err.kind() == ErrorKind::NotFound { - diags.push((i.clone(), JinjaDiagnostic::TemplateNotFound)); + LangType::Backend => { + let all_variables = variables.get(file_name)?; + let templates2 = all_variables + .iter() + .filter(|id| id.identifier_type == IdentifierType::JinjaTemplate); + for template in templates2 { + let path = format!("{templates}/{}", template.name); + if let Err(err) = std::fs::canonicalize(path) { + if err.kind() == ErrorKind::NotFound { + let diagnostic = create_diagnostic( + template, + DiagnosticSeverity::WARNING, + "Template not found".to_string(), + ); + diagnostics.push(diagnostic); + } } } + Some(diagnostics) } } +} - if diags.is_empty() { - None - } else { - Some(diags) +pub fn create_diagnostic( + template: &Identifier, + severity: DiagnosticSeverity, + message: String, +) -> Diagnostic { + Diagnostic { + range: Range::new( + Position::new(template.start.row as u32, template.start.column as u32), + Position::new(template.end.row as u32, template.end.column as u32), + ), + severity: Some(severity), + message, + source: Some(String::from("jinja-lsp")), + ..Default::default() } } diff --git a/jinja-lsp-queries/src/parsers.rs b/jinja-lsp-queries/src/parsers.rs index 5579bc0..9748a4c 100644 --- a/jinja-lsp-queries/src/parsers.rs +++ b/jinja-lsp-queries/src/parsers.rs @@ -12,11 +12,11 @@ impl Parsers { &mut self, lang_type: LangType, text: &str, - _old_tree: Option<&Tree>, + old_tree: Option<&Tree>, ) -> Option { match lang_type { - LangType::Template => self.jinja.parse(text, None), - LangType::Backend => self.backend.parse(text, None), + LangType::Template => self.jinja.parse(text, old_tree), + LangType::Backend => self.backend.parse(text, old_tree), } } } diff --git a/jinja-lsp-queries/src/queries.rs b/jinja-lsp-queries/src/queries.rs deleted file mode 100644 index 8ab69d0..0000000 --- a/jinja-lsp-queries/src/queries.rs +++ /dev/null @@ -1,146 +0,0 @@ -use tree_sitter::{Node, Point, Query, QueryCursor}; - -use crate::capturer::Capturer; - -#[derive(Debug)] -pub struct Queries { - pub jinja_init: Query, - pub jinja_idents: Query, - pub jinja_imports: Query, - pub rust_idents: Query, -} - -impl Clone for Queries { - fn clone(&self) -> Self { - Self::default() - } -} - -impl Default for Queries { - fn default() -> Self { - Self { - jinja_init: Query::new(tree_sitter_jinja2::language(), INIT).unwrap(), - jinja_idents: Query::new(tree_sitter_jinja2::language(), OBJECTS).unwrap(), - rust_idents: Query::new(tree_sitter_rust::language(), RUST).unwrap(), - jinja_imports: Query::new(tree_sitter_jinja2::language(), IMPORTS).unwrap(), - } - } -} - -pub fn query_props( - node: Node<'_>, - source: &str, - trigger_point: Point, - query: &Query, - all: bool, - mut capturer: T, -) -> T { - let mut cursor_qry = QueryCursor::new(); - let capture_names = query.capture_names(); - let matches = cursor_qry.matches(query, node, source.as_bytes()); - matches - .into_iter() - .flat_map(|m| { - m.captures - .iter() - .filter(|capture| all || capture.node.start_position() <= trigger_point) - }) - .for_each(|capture| { - capturer.save_by(capture, capture_names, source); - }); - capturer -} - -pub static INIT: &str = r#" -( - [ - - (statement - (statement_begin) - (keyword) - (identifier)? @variable - (#not-match? @variable "^(\\d)") - _ - ) @start_statement - - (statement - (statement_begin) - (keyword) @end_keyword - (#match? @end_keyword "^end") - (statement_end) - ) @end_statement - ] -) -"#; - -pub static OBJECTS: &str = r#" -( - [ - ( - (operator) @dot - (#eq? @dot "\.") - ) - - ( - (identifier) @just_id - (#not-match? @just_id "(^\\d+$)") - ) - - ( - (operator) @pipe - (#match? @pipe "\\|") - ) - - (expression) @expr - - ] -) -"#; - -pub static RUST: &str = r#" -([ - - (macro_invocation - (identifier) @context - (token_tree - (identifier) @key_id - ) - (#eq? @context "context") - ) @context_macro - - ( - (field_expression - (identifier) @jinja - (field_identifier) @method - ) - (arguments - (string_literal) @name - ) - - (#eq? @jinja "jinja") - (#match? @method "^(add_global|add_filter|add_function)$") - - ) @function -]) -"#; - -pub static IMPORTS: &str = r#" -[ - (statement - (statement_begin) - (keyword) @keyword - (string)? @template - (ERROR)? @error - (identifier)? @id - (statement_end)? @end - (#match? @keyword "^(include|from)$") - ) - (statement_begin) - (keyword) @keyword - (string)? @template - (ERROR)? @error - (identifier)? @id - (statement_end)? @end - (#match? @keyword "^(include|from)$") -] -"#; diff --git a/jinja-lsp-queries/src/search/definition.rs b/jinja-lsp-queries/src/search/definition.rs index e7446fc..2df66c7 100644 --- a/jinja-lsp-queries/src/search/definition.rs +++ b/jinja-lsp-queries/src/search/definition.rs @@ -1,83 +1,33 @@ -use std::collections::HashMap; +use std::collections::{HashSet, LinkedList}; use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; -use super::Identifier; - -macro_rules! range_start { - ($self: ident, $capture: ident, $member: ident) => { - let def = $self.last()?; - if let Definition::$member { start, .. } = def { - let end_point = $capture.node.end_position(); - *start = end_point; - $self.current = Current::NoDefinition; - } - }; -} - -macro_rules! range_end { - ($self: ident, $capture: ident, $member: ident) => { - let id = $capture.node.id(); - $self.current = Current::NoDefinition; - let v = $self.end.get_mut(&Current::$member)?; - let item = v.iter().find(|i| i.0 == id); - if item.is_none() { - v.push(($capture.node.id(), $capture.node.start_position())); - } - }; -} +use super::{Identifier, IdentifierType}; #[derive(Debug, Clone)] pub enum Definition { ForLoop { - id: usize, key: Identifier, value: Option, - start: Point, - end: Point, - open_par: bool, - comma: bool, - closed_par: bool, }, Set { - id: usize, key: Identifier, - start: Point, - end: Point, equals: bool, }, With { - id: usize, keys: Vec, - start: Point, - end: Point, }, Macro { - id: usize, keys: Vec, - start: Point, - end: Point, + scope: usize, }, Block { - id: usize, name: Identifier, - start: Point, - end: Point, }, } impl Definition { - pub fn id(&self) -> &usize { - match &self { - Definition::ForLoop { id, .. } => id, - Definition::Set { id, .. } => id, - Definition::With { id, .. } => id, - Definition::Macro { id, .. } => id, - Definition::Block { id, .. } => id, - } - } - - fn key<'a>(&'a self, ids: &mut Vec<&'a Identifier>) { + fn collect(self, ids: &mut Vec) { match self { Definition::ForLoop { key, .. } => { ids.push(key); @@ -109,104 +59,150 @@ pub enum Current { With, Macro, Block, + If, + Else, + Filter, + Autoescape, + Raw, #[default] NoDefinition, } -#[derive(Default)] +impl Current { + fn from_end(name: &str) -> Self { + match name { + "endfor" => Self::For, + "endset" => Self::Set, + "endwith" => Self::With, + "endmacro" => Self::Macro, + "endblock" => Self::Block, + "endif" => Self::If, + "endelse" => Self::Else, + "endfilter" => Self::Filter, + "endautoescape" => Self::Autoescape, + "endraw" => Self::Raw, + _ => Self::NoDefinition, + } + } + + fn from_rest(name: &str) -> Self { + match name { + "for" => Current::For, + "set" => Current::Set, + "with" => Current::With, + "macro" => Current::Macro, + "block" => Current::Block, + "if" | "elseif" => Current::If, + "else" => Current::Else, + "filter" => Current::Filter, + "autoescape" => Current::Autoescape, + "raw" => Current::Raw, + _ => Current::NoDefinition, + } + } +} + +#[derive(Default, Debug)] +pub struct Scope { + id: usize, + start: Point, + end: Point, +} + +impl Scope { + pub fn new(end: Point) -> Self { + Self { + id: 0, + start: Point::default(), + end, + } + } +} + +#[derive(Default, Debug)] pub struct JinjaDefinitions { pub definitions: Vec, - pub current: Current, - pub end: HashMap>, + pub current_definition: Current, + pub current_scope: LinkedList, + pub all_scopes: Vec, + all_ids: HashSet, + pub in_end: bool, } impl JinjaDefinitions { - pub fn init_end(&mut self) { - self.end.insert(Current::For, vec![]); - self.end.insert(Current::Set, vec![]); - self.end.insert(Current::With, vec![]); - self.end.insert(Current::Macro, vec![]); - self.end.insert(Current::Block, vec![]); - self.end.insert(Current::NoDefinition, vec![]); - } - pub fn last(&mut self) -> Option<&mut Definition> { - self.definitions.last_mut() - } - pub fn exist(&self, id: usize) -> bool { - self.definitions.iter().any(|item| item.id() == &id) + self.all_ids.iter().any(|item| item == &id) } pub fn add(&mut self, id: usize, current: Current) { - self.current = current; + self.current_definition = current; + let mut def = None; + let mut add_scope = false; match current { Current::For => { - let d = Definition::ForLoop { - id, + def = Some(Definition::ForLoop { key: Identifier::default(), value: None, - start: Point::default(), - end: Point::default(), - open_par: false, - comma: false, - closed_par: false, - }; - self.definitions.push(d); + }); + add_scope = true; } Current::Set => { - let d = Definition::Set { - id, + // if !self.all_ids.contains(&id) { + self.all_ids.insert(id); + def = Some(Definition::Set { key: Identifier::default(), - start: Point::default(), - end: Point::default(), equals: false, - }; - self.definitions.push(d); + }); + // } } Current::With => { - let d = Definition::With { - id, - keys: vec![], - start: Point::default(), - end: Point::default(), - }; - self.definitions.push(d); + def = Some(Definition::With { keys: vec![] }); + add_scope = true; } Current::Macro => { - let d = Definition::Macro { - id, + def = Some(Definition::Macro { keys: vec![], - start: Point::default(), - end: Point::default(), - }; - self.definitions.push(d); + scope: self.current_scope.front().unwrap_or(&Scope::default()).id, + }); + add_scope = true; } Current::Block => { - let d = Definition::Block { - id, + def = Some(Definition::Block { name: Identifier::default(), - start: Point::default(), - end: Point::default(), - }; - self.definitions.push(d); + }); + add_scope = true; + } + Current::NoDefinition => (), + _ => { + add_scope = true; } - Current::NoDefinition => {} + } + if let Some(def) = def { + // let same_def = self.definitions.iter().find(|item| item.) + self.definitions.push(def); + self.all_ids.insert(id); + } + if add_scope { + self.current_scope.push_front(Scope { + id, + ..Default::default() + }); } } fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option<()> { match name { - "for_start" => { + "for" => { if !self.exist(capture.node.id()) { self.add(capture.node.id(), Current::For); } else { - self.current = Current::NoDefinition; - // + self.current_definition = Current::NoDefinition; } } + "for_key" => { - if self.current == Current::For { - let def = self.last()?; + if self.current_definition == Current::For { + let def = self.definitions.last_mut()?; if let Definition::ForLoop { key, .. } = def { let start = capture.node.start_position(); let end = capture.node.end_position(); @@ -214,12 +210,16 @@ impl JinjaDefinitions { key.name = content.to_string(); key.start = start; key.end = end; + key.identifier_type = IdentifierType::ForLoopKey; + key.scope_ends.0 = + self.current_scope.front().unwrap_or(&Scope::default()).id; } } } + "for_value" => { - if self.current == Current::For { - let def = self.last()?; + if self.current_definition == Current::For { + let def = self.definitions.last_mut()?; if let Definition::ForLoop { value, .. } = def { let mut identifier = Identifier::default(); let start = capture.node.start_position(); @@ -228,94 +228,87 @@ impl JinjaDefinitions { identifier.name = content.to_owned(); identifier.start = start; identifier.end = end; + identifier.identifier_type = IdentifierType::ForLoopValue; + identifier.scope_ends.0 = + self.current_scope.front().unwrap_or(&Scope::default()).id; *value = Some(identifier); } } } - "for_end" => { - let hm = self.end.get(&Current::For)?; - let all = hm.get(capture.node.id()); - if all.is_none() { - self.current = Current::For; - } else { - self.current = Current::NoDefinition; - } - } + "set" => { if !self.exist(capture.node.id()) { self.add(capture.node.id(), Current::Set); } else { - self.current = Current::NoDefinition; + self.current_definition = Current::NoDefinition; } } + "set_identifier" => { - if self.current == Current::Set { - let def = self.last()?; + if self.current_definition == Current::Set { + let def = self.definitions.last_mut()?; if let Definition::Set { key, .. } = def { let start = capture.node.start_position(); let end = capture.node.end_position(); let content = capture.node.utf8_text(text.as_bytes()).ok()?; + if content == key.name { + return None; + } key.name = content.to_string(); key.start = start; key.end = end; + let scope = self.current_scope.front().unwrap_or(&Scope::default()).id; + key.scope_ends.0 = scope; + key.identifier_type = IdentifierType::SetVariable; } } } + "equals" => { - if self.current == Current::Set { - let def = self.last()?; - if let Definition::Set { equals, .. } = def { + if self.current_definition == Current::Set { + let def = self.definitions.last_mut()?; + if let Definition::Set { equals, key } = def { *equals = true; + key.scope_ends.0 = + self.current_scope.front().unwrap_or(&Scope::default()).id; } } } - "endset" => { - let hm = self.end.get(&Current::Set)?; - let all = hm.get(capture.node.id()); - if all.is_none() { - self.current = Current::Set; - } else { - self.current = Current::NoDefinition; - } - } + "with" => { if !self.exist(capture.node.id()) { self.add(capture.node.id(), Current::With); } else { - // self.current = Current::NoDefinition; + // self.current_definition = Current::NoDefinition; } } + "with_identifier" => { - if self.current == Current::With { - let def = self.last()?; + if self.current_definition == Current::With { + let def = self.definitions.last_mut()?; if let Definition::With { keys, .. } = def { let start = capture.node.start_position(); let end = capture.node.end_position(); let content = capture.node.utf8_text(text.as_bytes()).ok()?; - let key = Identifier::new(content, start, end); + let mut key = Identifier::new(content, start, end); + key.identifier_type = IdentifierType::WithVariable; + key.scope_ends.0 = + self.current_scope.front().unwrap_or(&Scope::default()).id; keys.push(key); } } } - "endwith" => { - let hm = self.end.get(&Current::With)?; - let all = hm.get(capture.node.id()); - if all.is_none() { - self.current = Current::With; - } else { - self.current = Current::NoDefinition; - } - } "block" => { if !self.exist(capture.node.id()) { self.add(capture.node.id(), Current::Block); } else { - self.current = Current::NoDefinition; + self.current_definition = Current::NoDefinition; } } + "block_identifier" => { - if self.current == Current::Block { - let def = self.last()?; + if self.current_definition == Current::Block { + let def = self.definitions.last_mut()?; if let Definition::Block { name, .. } = def { let start = capture.node.start_position(); let end = capture.node.end_position(); @@ -323,184 +316,200 @@ impl JinjaDefinitions { name.name = content.to_string(); name.start = start; name.end = end; + name.identifier_type = IdentifierType::TemplateBlock; + name.scope_ends.0 = + self.current_scope.front().unwrap_or(&Scope::default()).id; } } } - "endblock" => { - let hm = self.end.get(&Current::Block)?; - let all = hm.get(capture.node.id()); - if all.is_none() { - self.current = Current::Block; - } else { - self.current = Current::NoDefinition; - } - } + "macro" => { if !self.exist(capture.node.id()) { self.add(capture.node.id(), Current::Macro); } else { - self.current = Current::Macro; - // self.current = Current::NoDefinition; + self.current_definition = Current::Macro; + // self.current_definition = Current::NoDefinition; } } + "macro_identifier" => { - if self.current == Current::Macro { - let def = self.last()?; + if self.current_definition == Current::Macro { + let def = self.definitions.last_mut()?; if let Definition::Macro { keys, .. } = def { let start = capture.node.start_position(); let end = capture.node.end_position(); let content = capture.node.utf8_text(text.as_bytes()).ok()?; - let key = Identifier::new(content, start, end); + + let mut key = Identifier::new(content, start, end); + key.scope_ends.0 = + self.current_scope.front().unwrap_or(&Scope::default()).id; + if keys.is_empty() { + key.identifier_type = IdentifierType::MacroName; + } else { + key.identifier_type = IdentifierType::MacroParameter; + } keys.push(key); } } } - "endmacro" => { - let hm = self.end.get(&Current::Macro)?; - let all = hm.get(capture.node.id()); - if all.is_none() { - self.current = Current::Macro; - } else { - self.current = Current::NoDefinition; - } + + "if" | "elif" | "else" | "filter" | "autoescape" | "raw" => { + let current = Current::from_rest(name); + self.add(capture.node.id(), current); } - "range_start" => { - if self.current == Current::For { - range_start!(self, capture, ForLoop); - } else if self.current == Current::Set { - range_start!(self, capture, Set); - } else if self.current == Current::With { - range_start!(self, capture, With); - } else if self.current == Current::Block { - range_start!(self, capture, Block); - } else if self.current == Current::Macro { - range_start!(self, capture, Macro); - } + "ended" => { + self.in_end = true; } + "range_end" => { - if self.current == Current::For { - range_end!(self, capture, For); - } else if self.current == Current::Set { - range_end!(self, capture, Set); - } else if self.current == Current::With { - range_end!(self, capture, With); - } else if self.current == Current::Block { - range_end!(self, capture, Block); - } else if self.current == Current::Macro { - range_end!(self, capture, Macro); + if self.in_end { + self.in_end = false; + let mut last = self.current_scope.pop_front()?; + if last.end == Point::default() { + last.end = capture.node.start_position(); + self.current_definition = Current::NoDefinition; + self.all_scopes.push(last); + } } } - _ => (), - }; - - None - } - - pub fn fix_end(&mut self) { - self.fix_for_loop(); - self.fix_set(); - self.fix_with(); - self.fix_block(); - self.fix_macro(); - } - fn fix_for_loop(&mut self) { - let mut for_loops: Vec<_> = self - .definitions - .iter_mut() - .filter(|item| matches!(item, Definition::ForLoop { .. })) - .collect(); - for_loops.reverse(); - let ended = self.end.get_mut(&Current::For).unwrap(); - for (index, reversed) in ended.iter().enumerate() { - if let Some(Definition::ForLoop { end, .. }) = for_loops.get_mut(index) { - *end = reversed.1; - } - } - } + "range_start" => { + let mut can_add = true; + let mut is_set = false; + if let Some(last) = self.current_scope.front_mut() { + if let Some(Definition::Set { equals, .. }) = self.definitions.last() { + if self.current_definition == Current::Set && *equals { + can_add = false; + } + } + if can_add { + last.start = capture.node.end_position(); + } + } else if self.current_definition != Current::NoDefinition { + if let Some(Definition::Set { equals, .. }) = self.definitions.last() { + if self.current_definition == Current::Set && !(*equals) { + can_add = true; + } + } - fn fix_set(&mut self) { - let mut set_block: Vec<_> = self - .definitions - .iter_mut() - .filter(|item| matches!(item, Definition::Set { equals: false, .. })) - .collect(); - set_block.reverse(); - let ended = self.end.get_mut(&Current::Set).unwrap(); - for (index, reversed) in ended.iter().enumerate() { - if let Some(Definition::Set { end, .. }) = set_block.get_mut(index) { - *end = reversed.1; + if can_add { + self.current_scope.push_front(Scope { + id: capture.node.id(), + start: capture.node.end_position(), + ..Default::default() + }); + } + } } - } - } - fn fix_with(&mut self) { - let mut with_block: Vec<_> = self - .definitions - .iter_mut() - .filter(|item| matches!(item, Definition::With { .. })) - .collect(); - with_block.reverse(); - let ended = self.end.get_mut(&Current::With).unwrap(); - for (index, reversed) in ended.iter().enumerate() { - if let Some(Definition::With { end, .. }) = with_block.get_mut(index) { - *end = reversed.1; - } + // "range_end" => { + // let last = self.definitions.last()?; + // match last { + // Definition::ForLoop { .. } => { + // self.current = Current::For; + // } + // Definition::Set { .. } => { + // self.current = Current::Set; + // } + // Definition::With { .. } => { + // self.current = Current::With; + // } + // Definition::Macro { .. } => { + // self.current = Current::Macro; + // } + // Definition::Block { .. } => { + // self.current = Current::Block; + // } + // } + // } + _ => {} } + None } - fn fix_block(&mut self) { - let mut block: Vec<_> = self - .definitions - .iter_mut() - .filter(|item| matches!(item, Definition::Block { .. })) - .collect(); - block.reverse(); - let ended = self.end.get_mut(&Current::Block).unwrap(); - for (index, reversed) in ended.iter().enumerate() { - if let Some(Definition::Block { end, .. }) = block.get_mut(index) { - *end = reversed.1; - } + pub fn identifiers(self) -> Vec { + let mut ids = vec![]; + for id in self.definitions { + id.collect(&mut ids); } + ids } - fn fix_macro(&mut self) { - let mut macro_block: Vec<_> = self - .definitions - .iter_mut() - .filter(|item| matches!(item, Definition::Macro { .. })) - .collect(); - macro_block.reverse(); - let ended = self.end.get_mut(&Current::Macro).unwrap(); - for (index, reversed) in ended.iter().enumerate() { - if let Some(Definition::Macro { end, .. }) = macro_block.get_mut(index) { - *end = reversed.1; + fn fix_end(&mut self, last_line: Point) { + let global_scope = Scope::new(last_line); + for def in self.definitions.iter_mut() { + match def { + Definition::ForLoop { key, value } => { + let scope = self + .all_scopes + .iter() + .find(|item| item.id == key.scope_ends.0); + let scope = scope.unwrap_or(&global_scope); + key.scope_ends.1 = scope.end; + if let Some(value) = value { + value.scope_ends.1 = scope.end; + } + } + Definition::Set { key, .. } => { + let scope = self + .all_scopes + .iter() + .find(|item| item.id == key.scope_ends.0); + let scope = scope.unwrap_or(&global_scope); + key.scope_ends.1 = scope.end; + } + Definition::With { keys } => { + for key in keys { + let scope = self + .all_scopes + .iter() + .find(|item| item.id == key.scope_ends.0); + let scope = scope.unwrap_or(&global_scope); + key.scope_ends.1 = scope.end; + } + } + Definition::Macro { keys, scope } => { + let scope = self.all_scopes.iter().find(|item| item.id == *scope); + let scope = scope.unwrap_or(&global_scope); + for (index, key) in keys.iter_mut().enumerate() { + if index == 0 { + key.scope_ends.1 = scope.end; + } else { + let scope = self + .all_scopes + .iter() + .find(|item| item.id == key.scope_ends.0); + let scope = scope.unwrap_or(&global_scope); + key.scope_ends.1 = scope.end; + } + } + } + Definition::Block { name } => { + let scope = self + .all_scopes + .iter() + .find(|item| item.id == name.scope_ends.0); + let scope = scope.unwrap_or(&global_scope); + name.scope_ends.1 = scope.end; + } } + // let id = self.all_scopes.iter().find(|scope| scope.id == ) } } - - pub fn identifiers(&self) -> Vec<&Identifier> { - let mut ids = vec![]; - for id in &self.definitions { - id.key(&mut ids); - } - ids - } } pub fn definition_query( query: &Query, - tree: Tree, + tree: &Tree, trigger_point: Point, text: &str, all: bool, ) -> JinjaDefinitions { let closest_node = tree.root_node(); let mut definitions = JinjaDefinitions::default(); - definitions.init_end(); let mut cursor_qry = QueryCursor::new(); let capture_names = query.capture_names(); - let matches = cursor_qry.matches(&query, closest_node, text.as_bytes()); + let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); let captures = matches.into_iter().flat_map(|m| { m.captures .iter() @@ -510,6 +519,7 @@ pub fn definition_query( let name = &capture_names[capture.index as usize]; definitions.check(name, capture, text); } - definitions.fix_end(); + let root = tree.root_node().end_position(); + definitions.fix_end(root); definitions } diff --git a/jinja-lsp-queries/src/search/definition2.rs b/jinja-lsp-queries/src/search/definition2.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/jinja-lsp-queries/src/search/definition2.rs @@ -0,0 +1 @@ + diff --git a/jinja-lsp-queries/src/search/jinja_state.rs b/jinja-lsp-queries/src/search/jinja_state.rs index 9732927..6f98966 100644 --- a/jinja-lsp-queries/src/search/jinja_state.rs +++ b/jinja-lsp-queries/src/search/jinja_state.rs @@ -1,4 +1,10 @@ -use super::{definition::JinjaDefinitions, objects::JinjaObjects, templates::JinjaImports}; +use tree_sitter::{Point, Query, Tree}; + +use super::{ + definition::{definition_query, JinjaDefinitions}, + objects::{objects_query, JinjaObjects}, + templates::{templates_query, JinjaImports}, +}; #[derive(Default)] pub struct JinjaState { @@ -8,6 +14,23 @@ pub struct JinjaState { } impl JinjaState { + pub fn init( + trigger_point: Point, + query: (&Query, &Query, &Query), + tree: &Tree, + source: &str, + all: bool, + ) -> Self { + let definitions = definition_query(query.0, tree, trigger_point, source, all); + let objects = objects_query(query.1, tree, trigger_point, source, all); + let imports = templates_query(query.2, tree, trigger_point, source, all); + Self { + jinja_definitions: definitions, + jinja_objects: objects, + jinja_imports: imports, + } + } + pub fn reset(&mut self) { self.jinja_definitions = Default::default(); self.jinja_objects = Default::default(); diff --git a/jinja-lsp-queries/src/search/mod.rs b/jinja-lsp-queries/src/search/mod.rs index 52a9a57..2e3e45e 100644 --- a/jinja-lsp-queries/src/search/mod.rs +++ b/jinja-lsp-queries/src/search/mod.rs @@ -1,10 +1,12 @@ +use tower_lsp::lsp_types::{CompletionItemKind, Position, Range}; use tree_sitter::Point; +use self::objects::JinjaObject; + pub mod definition; pub mod jinja_completion; pub mod jinja_state; pub mod objects; -pub mod parsers; pub mod queries; pub mod rust_identifiers; pub mod rust_state; @@ -12,11 +14,13 @@ pub mod rust_template_completion; pub mod templates; pub mod test_queries; -#[derive(Default, Debug, Clone, PartialEq, Eq)] +#[derive(Default, Debug, Clone, PartialEq, PartialOrd, Ord, Eq)] pub struct Identifier { pub start: Point, pub end: Point, pub name: String, + pub scope_ends: (usize, Point), + pub identifier_type: IdentifierType, } impl Identifier { @@ -25,21 +29,80 @@ impl Identifier { name: String::from(name), start, end, + scope_ends: (0, Point::default()), + identifier_type: IdentifierType::UndefinedVariable, } } } -#[derive(PartialEq, Debug)] -pub enum CompletionType { - Filter, - Identifier, - IncludedTemplate { name: String, range: (Point, Point) }, +impl From<&JinjaObject> for Identifier { + fn from(value: &JinjaObject) -> Self { + Identifier::new(&value.name, value.location.0, value.location.1) + } } pub fn completion_start(trigger_point: Point, identifier: &Identifier) -> Option<&str> { let len = identifier.name.len(); let diff = identifier.end.column - trigger_point.column; + if diff > len { + return None; + } let to = len - diff; - let s = identifier.name.get(0..to); + let s = identifier.name.get(0..to + 1); s } +pub fn to_range(points: (Point, Point)) -> Range { + let start = Position::new(points.0.row as u32, points.0.column as u32); + let end = Position::new(points.1.row as u32, points.1.column as u32); + Range::new(start, end) +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum IdentifierType { + ForLoopKey, + ForLoopValue, + ForLoopCount, + SetVariable, + WithVariable, + MacroName, + MacroParameter, + TemplateBlock, + BackendVariable, + #[default] + UndefinedVariable, + JinjaTemplate, +} + +impl IdentifierType { + pub fn completion_detail(&self) -> &'static str { + match self { + IdentifierType::ForLoopKey => "For loop key", + IdentifierType::ForLoopValue => "For loop value", + IdentifierType::ForLoopCount => "For loop count", + IdentifierType::SetVariable => "Set variable", + IdentifierType::WithVariable => "With variable", + IdentifierType::MacroName => "Macro", + IdentifierType::MacroParameter => "Macro parameter", + IdentifierType::TemplateBlock => "Template block", + IdentifierType::BackendVariable => "Backend variable", + IdentifierType::UndefinedVariable => "Undefined variable", + IdentifierType::JinjaTemplate => "Jinja template", + } + } + + pub fn completion_kind(&self) -> CompletionItemKind { + match self { + IdentifierType::ForLoopKey => CompletionItemKind::VARIABLE, + IdentifierType::ForLoopValue => CompletionItemKind::VARIABLE, + IdentifierType::ForLoopCount => CompletionItemKind::FIELD, + IdentifierType::SetVariable => CompletionItemKind::VARIABLE, + IdentifierType::WithVariable => CompletionItemKind::VARIABLE, + IdentifierType::MacroName => CompletionItemKind::FUNCTION, + IdentifierType::MacroParameter => CompletionItemKind::FIELD, + IdentifierType::TemplateBlock => CompletionItemKind::MODULE, + IdentifierType::BackendVariable => CompletionItemKind::VARIABLE, + IdentifierType::UndefinedVariable => CompletionItemKind::CONSTANT, + IdentifierType::JinjaTemplate => CompletionItemKind::FILE, + } + } +} diff --git a/jinja-lsp-queries/src/search/objects.rs b/jinja-lsp-queries/src/search/objects.rs index b4a4465..cb2e7a6 100644 --- a/jinja-lsp-queries/src/search/objects.rs +++ b/jinja-lsp-queries/src/search/objects.rs @@ -1,20 +1,21 @@ +use tower_lsp::lsp_types::Range; use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; -use super::CompletionType; - #[derive(Default, Debug, Clone)] pub struct JinjaObject { pub name: String, pub location: (Point, Point), + pub is_filter: bool, fields: Vec<(String, (Point, Point))>, } impl JinjaObject { - pub fn new(name: String, start: Point, end: Point) -> Self { + pub fn new(name: String, start: Point, end: Point, is_filter: bool) -> Self { Self { name, location: (start, end), fields: vec![], + is_filter, } } @@ -73,16 +74,30 @@ impl JinjaObjects { .is_none() { true => { - self.objects - .push(JinjaObject::new(String::from(value), start, end)); + // TODO: in future add those to main library + if VALID_IDENTIFIERS.contains(&value) { + return; + } self.ident = (start, end); + let is_filter = self.is_hover(start); + self.objects.push(JinjaObject::new( + String::from(value), + start, + end, + is_filter, + )); } false => (), } } else { - self.objects - .push(JinjaObject::new(String::from(value), start, end)); + // TODO: in future add those to main library + if VALID_IDENTIFIERS.contains(&value) { + return; + } self.ident = (start, end); + let is_filter = self.is_hover(start); + self.objects + .push(JinjaObject::new(String::from(value), start, end, is_filter)); } } } @@ -104,6 +119,24 @@ impl JinjaObjects { trigger_point >= self.expr.0 && trigger_point <= self.expr.1 && trigger_point > self.ident.1 } + pub fn is_ident(&self, trigger_point: Point) -> Option { + if trigger_point >= self.ident.0 && trigger_point <= self.ident.1 { + self.objects.last().map(|last| last.name.to_string()) + } else { + None + } + } + + pub fn is_hover(&self, trigger_point: Point) -> bool { + trigger_point >= self.ident.0 + && trigger_point <= self.ident.1 + && self.pipe.1 == self.ident.0 + } + + pub fn get_last_id(&self) -> Option<&JinjaObject> { + self.objects.last() + } + pub fn show(&self) -> Vec { self.objects.clone() } @@ -111,7 +144,7 @@ impl JinjaObjects { pub fn objects_query( query: &Query, - tree: Tree, + tree: &Tree, trigger_point: Point, text: &str, all: bool, @@ -120,7 +153,7 @@ pub fn objects_query( let mut objects = JinjaObjects::default(); let mut cursor_qry = QueryCursor::new(); let capture_names = query.capture_names(); - let matches = cursor_qry.matches(&query, closest_node, text.as_bytes()); + let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); let captures = matches.into_iter().flat_map(|m| { m.captures .iter() @@ -132,3 +165,12 @@ pub fn objects_query( } objects } + +#[derive(PartialEq, Debug)] +pub enum CompletionType { + Filter, + Identifier, + IncludedTemplate { name: String, range: Range }, +} + +static VALID_IDENTIFIERS: [&str; 4] = ["loop", "true", "false", "not"]; diff --git a/jinja-lsp-queries/src/search/parsers.rs b/jinja-lsp-queries/src/search/parsers.rs deleted file mode 100644 index c2b8b46..0000000 --- a/jinja-lsp-queries/src/search/parsers.rs +++ /dev/null @@ -1,41 +0,0 @@ -use tree_sitter::{Parser, Tree}; - -pub enum LangType { - Template, - Backend, -} - -pub struct Parsers { - jinja: Parser, - backend: Parser, -} - -impl Parsers { - pub fn parse( - &mut self, - lang_type: LangType, - text: &str, - old_tree: Option<&Tree>, - ) -> Option { - match lang_type { - LangType::Template => self.jinja.parse(text, old_tree), - LangType::Backend => self.backend.parse(text, old_tree), - } - } -} - -impl Default for Parsers { - fn default() -> Self { - let mut jinja = Parser::new(); - let _ = jinja.set_language(tree_sitter_jinja2::language()); - let mut backend = Parser::new(); - let _ = backend.set_language(tree_sitter_rust::language()); - Self { jinja, backend } - } -} - -impl Clone for Parsers { - fn clone(&self) -> Self { - Self::default() - } -} diff --git a/jinja-lsp-queries/src/search/queries.rs b/jinja-lsp-queries/src/search/queries.rs index 5bde320..4600b6a 100644 --- a/jinja-lsp-queries/src/search/queries.rs +++ b/jinja-lsp-queries/src/search/queries.rs @@ -1,7 +1,7 @@ use tree_sitter::Query; #[derive(Debug)] -pub struct Queries { +pub struct Queries2 { pub jinja_definitions: Query, pub jinja_objects: Query, pub jinja_imports: Query, @@ -9,13 +9,13 @@ pub struct Queries { pub rust_templates: Query, } -impl Clone for Queries { +impl Clone for Queries2 { fn clone(&self) -> Self { Self::default() } } -impl Default for Queries { +impl Default for Queries2 { fn default() -> Self { Self { jinja_definitions: Query::new(tree_sitter_jinja2::language(), DEFINITIONS).unwrap(), @@ -29,6 +29,7 @@ impl Default for Queries { const DEFINITIONS: &str = r#" + ( [ (statement @@ -65,17 +66,7 @@ const DEFINITIONS: &str = r#" (identifier) @for_items (_)? @other (statement_end) @range_start - ) @for_start - - ( - (statement - (statement_begin) @range_end - (keyword) @end_keyword - (statement_end) - (#eq? @end_keyword "endfor") - ) - ) @for_end - + ) @for ( (statement @@ -92,15 +83,6 @@ const DEFINITIONS: &str = r#" ) ) @set - ( - (statement - (statement_begin) @range_end - (keyword) @endset_keyword - (statement_end) - (#eq? @endset_keyword "endset") - ) - ) @endset - (statement (statement_begin) (keyword) @with_keyword @@ -110,13 +92,6 @@ const DEFINITIONS: &str = r#" (statement_end) @range_start ) @with - (statement - (statement_begin) @range_end - (keyword) @end_with - (#eq? @end_with "endwith") - (statement_end) - ) @endwith - (statement (statement_begin) (keyword) @macro_keyword @@ -126,13 +101,6 @@ const DEFINITIONS: &str = r#" (statement_end) @range_start ) @macro - (statement - (statement_begin) @range_end - (keyword) @endmacro_keyword - (#eq? @endmacro_keyword "endmacro") - (statement_end) - ) @endmacro - (statement (statement_begin) (keyword) @block_keyword @@ -141,15 +109,60 @@ const DEFINITIONS: &str = r#" (#not-match? @block_identifier "(^\\d+$)") (statement_end) @range_start ) @block + + + (statement + (statement_begin) + (keyword) @ifkeyword + (#eq? @ifkeyword "if") + (statement_end) @range_start + ) @if + + (statement + (statement_begin) + (keyword) @elifkeyword + (#eq? @elifkeyword "elif") + (statement_end) @range_start + ) @elif (statement - (statement_begin) @range_end - (keyword) @end_block_keyword - (#eq? @end_block_keyword "endblock") - (statement_end) - ) @endblock + (statement_begin) + (keyword) @elsekeyword + (#eq? @elsekeyword "else") + (statement_end) @range_start + ) @else + + + (statement + (statement_begin) + (keyword) @filterkeyword + (#eq? @filterkeyword "filter") + (statement_end) @range_start + ) @filter + + (statement + (statement_begin) + (keyword) @autokeyword + (#eq? @autokeyword "autoescape") + (statement_end) @range_start + ) @autoescape + + (statement + (statement_begin) + (keyword) @rawkeyword + (#eq? @rawkeyword "raw") + (statement_end) @range_start + ) @raw ] ) +[ + (statement + (statement_begin) @range_end + (keyword) @endkeyword + (#match? @endkeyword "^end") + (statement_end) + ) @ended +] "#; @@ -194,11 +207,11 @@ pub static RUST_DEFINITIONS: &str = r#" (field_identifier) @method ) (arguments - (string_literal) @name + (string_literal)+ @name ) (#eq? @jinja "jinja") - (#any-of? @method "add_global" add_filter" add_function") + (#match? @method "(add_global|add_filter|add_function)") ) @function ]) @@ -261,15 +274,16 @@ const RUST_TEMPLATES: &str = r#" (field_identifier) @method_name ) (identifier) @method_name + (#any-of? @method_name "render_jinja" "get_template") + ;;(#match? @method_name "(render_jinja|get_template)") ] (arguments (string_literal)+ @template_name ) - (#any-of? @method_name "render_jinja" "get_template") ) "#; -const JINJA_SNIPPETS: &str = r#" +const _JINJA_SNIPPETS: &str = r#" [ (statement) @block (ERROR diff --git a/jinja-lsp-queries/src/search/rust_identifiers.rs b/jinja-lsp-queries/src/search/rust_identifiers.rs index 2d1b5c1..a68d825 100644 --- a/jinja-lsp-queries/src/search/rust_identifiers.rs +++ b/jinja-lsp-queries/src/search/rust_identifiers.rs @@ -1,8 +1,6 @@ -use std::collections::HashMap; - use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; -use super::Identifier; +use super::{Identifier, IdentifierType}; #[derive(Default, Debug, Clone, PartialEq, Eq)] pub enum Current { @@ -18,8 +16,8 @@ pub struct RustIdentifiers { } impl RustIdentifiers { - pub fn show(&self) -> &Vec { - &self.variables + pub fn show(self) -> Vec { + self.variables } pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option<()> { @@ -37,7 +35,8 @@ impl RustIdentifiers { return None; } let name = capture.node.utf8_text(text.as_bytes()).ok()?; - let identifier = Identifier::new(name, start, end); + let mut identifier = Identifier::new(name, start, end); + identifier.identifier_type = IdentifierType::BackendVariable; self.variables.push(identifier); } } @@ -45,7 +44,9 @@ impl RustIdentifiers { let start = capture.node.start_position(); let end = capture.node.end_position(); let name = capture.node.utf8_text(text.as_bytes()).ok()?; - let identifier = Identifier::new(name, start, end); + let name = name.replace(['\"', '\''], ""); + let mut identifier = Identifier::new(&name, start, end); + identifier.identifier_type = IdentifierType::BackendVariable; self.variables.push(identifier); } _ => (), @@ -56,7 +57,7 @@ impl RustIdentifiers { pub fn rust_definition_query( query: &Query, - tree: Tree, + tree: &Tree, trigger_point: Point, text: &str, all: bool, @@ -65,7 +66,7 @@ pub fn rust_definition_query( let mut cursor_qry = QueryCursor::new(); let mut rust = RustIdentifiers::default(); let capture_names = query.capture_names(); - let matches = cursor_qry.matches(&query, closest_node, text.as_bytes()); + let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); let captures = matches.into_iter().flat_map(|m| { m.captures .iter() diff --git a/jinja-lsp-queries/src/search/rust_state.rs b/jinja-lsp-queries/src/search/rust_state.rs index 6b29dca..7232dc0 100644 --- a/jinja-lsp-queries/src/search/rust_state.rs +++ b/jinja-lsp-queries/src/search/rust_state.rs @@ -1,3 +1,10 @@ +use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range, Url}; +use tree_sitter::{Point, Query, Tree}; + +use crate::search::{ + rust_identifiers::rust_definition_query, rust_template_completion::rust_templates_query, +}; + use super::{rust_identifiers::RustIdentifiers, rust_template_completion::RustTemplates}; #[derive(Default)] @@ -7,8 +14,57 @@ pub struct RustState { } impl RustState { + pub fn init( + trigger_point: Point, + query: (&Query, &Query), + tree: &Tree, + source: &str, + all: bool, + ) -> Self { + let ids = rust_definition_query(query.0, tree, trigger_point, source, all); + let templates = rust_templates_query(query.1, tree, trigger_point, source, all); + RustState { + rust_identifiers: ids, + rust_templates: templates, + } + } + pub fn reset(&mut self) { self.rust_identifiers = RustIdentifiers::default(); self.rust_templates = RustTemplates::default(); } + + pub fn template_errors(&self, root: &str) -> Option> { + let mut diagnostics = vec![]; + for id in &self.rust_templates.templates { + let name = &id.name; + let template = format!("{}/{}", root, name); + let template = std::fs::canonicalize(template); + let mut is_error = false; + if template.is_err() { + is_error = true; + } else { + let buffer = template.ok()?; + let url = format!("file://{}", buffer.to_str()?); + let url = Url::parse(&url).ok(); + if url.is_none() { + is_error = true; + } + } + if is_error { + let diagnostic = Diagnostic { + range: Range::new( + Position::new(id.start.row as u32, id.start.column as u32), + Position::new(id.end.row as u32, id.end.column as u32), + ), + severity: Some(DiagnosticSeverity::WARNING), + message: "Template not found".to_owned(), + source: Some(String::from("jinja-lsp")), + ..Default::default() + }; + diagnostics.push(diagnostic); + } + } + Some(diagnostics) + } } diff --git a/jinja-lsp-queries/src/search/rust_template_completion.rs b/jinja-lsp-queries/src/search/rust_template_completion.rs index 8c5f3b5..41e4d78 100644 --- a/jinja-lsp-queries/src/search/rust_template_completion.rs +++ b/jinja-lsp-queries/src/search/rust_template_completion.rs @@ -1,6 +1,6 @@ use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; -use super::Identifier; +use super::{Identifier, IdentifierType}; #[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct RustTemplateCompletion { @@ -10,6 +10,7 @@ pub struct RustTemplateCompletion { #[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct RustTemplates { pub templates: Vec, + in_method: bool, } impl RustTemplates { @@ -23,23 +24,33 @@ impl RustTemplates { } pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option<()> { - if name == "template_name" { + if name == "template_name" && self.in_method { let template = capture.node.utf8_text(text.as_bytes()).ok()?; let template = template.replace(['\"', '\''], ""); - let mut start = capture.node.start_position(); - start.column += 1; - let mut end = capture.node.end_position(); - end.column -= 1; - let identifer = Identifier::new(&template, start, end); + let start = capture.node.start_position(); + let end = capture.node.end_position(); + let mut identifer = Identifier::new(&template, start, end); + identifer.identifier_type = IdentifierType::JinjaTemplate; self.templates.push(identifer); + self.in_method = false; + } else if name == "method_name" { + let content = capture.node.utf8_text(text.as_bytes()).ok()?; + if !METHODS.contains(&content) { + return None; + } + self.in_method = true; } None } + + pub fn collect(self) -> Vec { + self.templates + } } pub fn rust_templates_query( query: &Query, - tree: Tree, + tree: &Tree, trigger_point: Point, text: &str, all: bool, @@ -60,3 +71,5 @@ pub fn rust_templates_query( } templates } + +static METHODS: [&str; 2] = ["render_jinja", "get_template"]; diff --git a/jinja-lsp-queries/src/search/templates.rs b/jinja-lsp-queries/src/search/templates.rs index a3df1ad..8836047 100644 --- a/jinja-lsp-queries/src/search/templates.rs +++ b/jinja-lsp-queries/src/search/templates.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; -use super::Identifier; +use super::{Identifier, IdentifierType}; #[derive(Debug)] pub enum Import { @@ -23,13 +23,13 @@ pub enum Import { } impl Import { - pub fn get_name(&self, trigger_point: Point) -> Option<&str> { + pub fn get_identifier(&self, trigger_point: Point) -> Option<&Identifier> { match &self { Import::Extends { template } | Import::From { template, .. } | Import::Import { template, .. } => { if trigger_point >= template.start && trigger_point <= template.end { - Some(&template.name) + Some(template) } else { None } @@ -38,7 +38,30 @@ impl Import { let template = templates.iter().find(|template| { trigger_point >= template.start && trigger_point <= template.end })?; - Some(&template.name) + Some(template) + } + } + } + + fn collect(self, ids: &mut Vec) { + match self { + Import::Extends { template } => ids.push(template), + Import::Include { templates } => { + for i in templates { + ids.push(i); + } + } + Import::From { + template, + identifiers, + } => { + ids.push(template); + for i in identifiers { + ids.push(i); + } + } + Import::Import { template, .. } => { + ids.push(template); } } } @@ -59,7 +82,7 @@ pub struct JinjaImports { } impl JinjaImports { - pub fn in_template(&self, trigger_point: Point) -> Option<&Import> { + pub fn in_template(&self, _: Point) -> Option<&Import> { if let Current::Id(id) = self.current { let last = self.imports.get(&id)?; return Some(last); @@ -129,20 +152,20 @@ impl JinjaImports { let last = self.imports.get_mut(&id)?; let name = capture.node.utf8_text(text.as_bytes()).ok()?; let name = name.replace(['\"', '\''], ""); - let mut start = capture.node.start_position(); - start.column += 1; - let mut end = capture.node.end_position(); - end.column -= 1; + let start = capture.node.start_position(); + let end = capture.node.end_position(); match last { Import::Extends { template } => { if template.name.is_empty() { template.name = name; template.start = start; template.end = end; + template.identifier_type = IdentifierType::JinjaTemplate; } } Import::Include { templates } => { - let template = Identifier::new(&name, start, end); + let mut template = Identifier::new(&name, start, end); + template.identifier_type = IdentifierType::JinjaTemplate; templates.push(template); } Import::From { template, .. } => { @@ -150,6 +173,7 @@ impl JinjaImports { template.name = name; template.start = start; template.end = end; + template.identifier_type = IdentifierType::JinjaTemplate; } } Import::Import { template, .. } => { @@ -157,6 +181,7 @@ impl JinjaImports { template.name = name; template.start = start; template.end = end; + template.identifier_type = IdentifierType::JinjaTemplate; } } } @@ -188,10 +213,16 @@ impl JinjaImports { } None } + + pub fn collect(self, ids: &mut Vec) { + for i in self.imports { + i.1.collect(ids); + } + } } pub fn templates_query( query: &Query, - tree: Tree, + tree: &Tree, trigger_point: Point, text: &str, all: bool, diff --git a/jinja-lsp-queries/src/search/test_queries.rs b/jinja-lsp-queries/src/search/test_queries.rs index b1157f3..4fb630e 100644 --- a/jinja-lsp-queries/src/search/test_queries.rs +++ b/jinja-lsp-queries/src/search/test_queries.rs @@ -1,16 +1,15 @@ #[cfg(test)] mod query_tests { + use crate::search::objects::objects_query; use tree_sitter::{Parser, Point}; - use crate::search::{ - completion_start, - definition::definition_query, - objects::{objects_query, JinjaObjects}, - queries::Queries, - rust_identifiers::rust_definition_query, - rust_template_completion::rust_templates_query, - templates::templates_query, - CompletionType, + use crate::{ + search::objects::CompletionType, + search::{ + completion_start, definition::definition_query, queries::Queries2, + rust_identifiers::rust_definition_query, + rust_template_completion::rust_templates_query, templates::templates_query, + }, }; fn prepare_jinja_tree(text: &str) -> tree_sitter::Tree { @@ -58,37 +57,38 @@ mod query_tests { ), ( r#" - {% with name = 55 %} -

Hello {{ name }}

- {% set a = "hello world" %} - {% set b %} - some content - {% endset %} - {% endwith %} - {% for i in 10 -%} - {%- endfor %} + {% with name = 55 %} +

Hello {{ name }}

+ {% set a = "hello world" %} + {% set b %} + some content + {% endset %} + {% endwith %} + {% for i in 10 -%} + {%- endfor %} - {{ point }} - {{ point }} - "#, + {{ point }} + {{ point }} + "#, 4, ), ]; - let query = Queries::default(); + let query = Queries2::default(); let query = query.jinja_definitions; for case in cases { let tree = prepare_jinja_tree(case.0); let trigger_point = Point::new(0, 0); // let closest_node = tree.root_node(); - let definitions = definition_query(&query, tree, trigger_point, case.0, true); - assert_eq!(definitions.identifiers().len(), case.1); + let definitions = definition_query(&query, &tree, trigger_point, case.0, true); + // assert_eq!(definitions.identifiers().len(), case.1); + assert!(false); } } #[test] fn jinja_identifiers() { - let query = Queries::default(); + let query = Queries2::default(); let query = query.jinja_objects; let cases = [ ( @@ -130,7 +130,7 @@ mod query_tests { for case in cases { let tree = prepare_jinja_tree(case.0); let trigger_point = Point::new(0, 0); - let objects = objects_query(&query, tree, trigger_point, case.0, true); + let objects = objects_query(&query, &tree, trigger_point, case.0, true); let len = objects.show().len(); assert_eq!(len, case.1); } @@ -149,9 +149,9 @@ mod query_tests { let tree = prepare_rust_tree(case); let trigger_point = Point::new(0, 0); - let query = Queries::default(); + let query = Queries2::default(); let query = &query.rust_definitions; - let rust = rust_definition_query(query, tree, trigger_point, case, true); + let rust = rust_definition_query(query, &tree, trigger_point, case, true); assert_eq!(rust.show().len(), 8); } @@ -182,11 +182,10 @@ mod query_tests { for case in cases { let tree = prepare_jinja_tree(source); let trigger_point = case.0; - let query = Queries::default(); + let query = Queries2::default(); let query = &query.jinja_objects; - let objects = objects_query(query, tree, trigger_point, source, false); + let objects = objects_query(query, &tree, trigger_point, source, false); assert_eq!(objects.completion(trigger_point), case.1); - // assert!(false); } } @@ -213,12 +212,19 @@ mod query_tests { for case in cases { let tree = prepare_jinja_tree(source); let trigger_point = case.0; - let query = Queries::default(); + let query = Queries2::default(); let query = &query.jinja_imports; - let templates = templates_query(query, tree, trigger_point, source, false); + let templates = templates_query(query, &tree, trigger_point, source, false); let template = templates.in_template(trigger_point); assert!(template.is_some()); - assert_eq!(template.unwrap().get_name(trigger_point).unwrap(), case.1); + assert_eq!( + template + .unwrap() + .get_identifier(trigger_point) + .unwrap() + .name, + case.1 + ); } } @@ -229,12 +235,14 @@ mod query_tests { render_jinja("some_template.jinja"); render_jinja(1,2, 3, "some_template.jinja"); render_jinja(1,2, 3); + add_global("PROJECT_NAME", "Example"); + "#; let tree = prepare_rust_tree(source); let trigger_point = Point::default(); - let query = Queries::default(); + let query = Queries2::default(); let query = &query.rust_templates; - let templates = rust_templates_query(query, tree, trigger_point, source, true); + let templates = rust_templates_query(query, &tree, trigger_point, source, true); assert_eq!(templates.templates.len(), 3); } @@ -244,16 +252,38 @@ mod query_tests { let tmp2 = jinja.get_template("account3"); let tmp2 = jinja.get_template("account2"); let tmp = jinja.get_template("account"); + let tmp = jinja.anything("account"); "#; let tree = prepare_rust_tree(source); let trigger_point = Point::new(3, 47); - let query = Queries::default(); + let query = Queries2::default(); let query = &query.rust_templates; - let templates = rust_templates_query(query, tree, trigger_point, source, false); + let templates = rust_templates_query(query, &tree, trigger_point, source, false); if let Some(template) = templates.in_template(trigger_point) { if let Some(completion) = completion_start(trigger_point, template) { assert_eq!(completion, "accou"); } } } + + #[test] + fn jinja_definition_scope() { + let source = r#" + {% macro hello_world(parameter) -%} +

hello world

+ {{ PROJECT_NAME | length }} + {{ parameter }} + {% macro primer(parameter2) %} + {{ parameter }} + {% endmacro %} + {% set b = 11 %} + {% endmacro %} + "#; + let query = Queries2::default(); + let query = query.jinja_definitions; + let tree = prepare_jinja_tree(source); + let trigger_point = Point::new(0, 0); + let objects = definition_query(&query, &tree, trigger_point, source, true); + assert!(true); + } } diff --git a/jinja-lsp-queries/src/test_queries.rs b/jinja-lsp-queries/src/test_queries.rs deleted file mode 100644 index 664a93b..0000000 --- a/jinja-lsp-queries/src/test_queries.rs +++ /dev/null @@ -1,258 +0,0 @@ -#[cfg(test)] -mod query_tests { - - use crate::capturer::included::IncludeCapturer; - use tree_sitter::{Parser, Point}; - - use crate::{ - capturer::{ - init::JinjaInitCapturer, - object::{CompletionType, JinjaObjectCapturer}, - rust::RustCapturer, - }, - queries::{query_props, Queries}, - }; - - fn prepare_jinja_tree(text: &str) -> tree_sitter::Tree { - let language = tree_sitter_jinja2::language(); - let mut parser = Parser::new(); - - parser - .set_language(language) - .expect("could not load jinja grammar"); - - parser.parse(text, None).expect("not to fail") - } - - fn prepare_rust_tree(text: &str) -> tree_sitter::Tree { - let language = tree_sitter_rust::language(); - let mut parser = Parser::new(); - - parser - .set_language(language) - .expect("could not load rust grammar"); - - parser.parse(text, None).expect("not to fail") - } - - #[test] - fn find_ident_definition() { - let case = r#" - {% macro do_something(a, b,c) %} -

Hello world

- {% set class = "button" -%} - {% with name = 55 %} -

Hello {{ name }}

- {% endwith %} - - {% endmacro %} - - - {% for i in 10 -%} - {%- endfor %} - - {{ point }} - {{ point }} - "#; - let tree = prepare_jinja_tree(case); - let trigger_point = Point::new(0, 0); - let closest_node = tree.root_node(); - let query = Queries::default(); - let query = &query.jinja_init; - let capturer = JinjaInitCapturer::default(); - let capturer = query_props(closest_node, case, trigger_point, query, true, capturer); - assert_eq!(capturer.to_vec().len(), 7); - } - - #[test] - fn find_identifiers() { - let case = r#" - {{ user.id }} - {% for i in 10 -%} - {{ i }} - {%- endfor %} - {% set class = "button" -%} - "#; - let tree = prepare_jinja_tree(case); - let trigger_point = Point::new(0, 0); - let closest_node = tree.root_node(); - let query = Queries::default(); - let query = &query.jinja_idents; - let capturer = JinjaObjectCapturer::default(); - let props = query_props(closest_node, case, trigger_point, query, true, capturer); - assert_eq!(props.show().len(), 4); - } - - #[test] - fn find_identifiers_with_statements_and_expressions() { - let case = r#" - {{ obj.abc obj2.abc2 }} - - {{ obj3.field.something.something == obj4.something }} - - {% if obj5.field -%} - 111 {{ abc == def.abc }} - {% endif %} - "#; - let tree = prepare_jinja_tree(case); - let trigger_point = Point::new(0, 0); - let closest_node = tree.root_node(); - let query = Queries::default(); - let query = &query.jinja_idents; - let capturer = JinjaObjectCapturer::default(); - let props = query_props(closest_node, case, trigger_point, query, true, capturer); - assert_eq!(props.show().len(), 7); - } - - #[test] - fn find_identifiers_quick() { - let case = r#" -

{{ something }}

-

{{ something | some_filter(a, b,c) }}

- {% for i in something -%} - {{ i }} - {%- endfor %} - {% if something %} - {{ something }} - {% endif %} - "#; - let tree = prepare_jinja_tree(case); - let trigger_point = Point::new(0, 0); - let closest_node = tree.root_node(); - let query = Queries::default(); - let query = &query.jinja_idents; - let capturer = JinjaObjectCapturer::default(); - let props = query_props(closest_node, case, trigger_point, query, true, capturer); - assert_eq!(props.show().len(), 11); - } - - #[test] - fn find_identifiers_in_macro() { - let case = r#" - let a = context!(name => 11 + abc, abc => "username"); - let b = context!{name, username => "username" } - let price = 100; - let c = context!{ price }; - jinja.add_filter("running_locally", true); - jinja.add_function("some_fn", some_fn); - "#; - - let tree = prepare_rust_tree(case); - let trigger_point = Point::new(0, 0); - let closest_node = tree.root_node(); - let query = Queries::default(); - let query = &query.rust_idents; - let capturer = RustCapturer::default(); - let props = query_props(closest_node, case, trigger_point, query, true, capturer); - let macros = props.macros(); - assert_eq!(macros.len(), 3); - let mut count = 0; - for context in macros { - count += context.1.variables().len(); - } - let variables = props.variables(); - count += variables.len(); - assert_eq!(count, 7); - } - - #[test] - fn find_jinja_completion() { - let source = r#" - {{ something | filter1 | filter2 }} - - {% if something == 11 -%} - {% macro example(a, b, c) -%} -

hello world

- {%- endmacro %} - - {{ }} - {{ "|" }} - "#; - let cases = [ - (Point::new(1, 27), Some(CompletionType::Filter)), - (Point::new(1, 48), None), - (Point::new(1, 40), Some(CompletionType::Filter)), - (Point::new(1, 50), Some(CompletionType::Identifier)), - (Point::new(3, 18), None), - (Point::new(4, 20), None), - (Point::new(3, 22), None), - (Point::new(8, 15), Some(CompletionType::Identifier)), - (Point::new(9, 18), Some(CompletionType::Identifier)), - ]; - for case in cases { - let tree = prepare_jinja_tree(source); - let trigger_point = case.0; - let closest_node = tree.root_node(); - let query = Queries::default(); - - let query = &query.jinja_idents; - let capturer = JinjaObjectCapturer::default(); - let props = query_props(closest_node, source, trigger_point, query, false, capturer); - assert_eq!(props.completion(trigger_point), case.1); - } - } - - #[test] - fn find_includes() { - let source = r#" -
- {% include 'header.jinja' %} - {% include 'customization.jinja' ignore missing %} - {% include ['page_detailed.jinja', 'page.jinja'] %} -
- "#; - let cases = [ - (Point::new(2, 31), "header.jinja"), - (Point::new(3, 23), "customization.jinja"), - (Point::new(4, 62), "page.jinja"), - ]; - for case in cases { - let tree = prepare_jinja_tree(source); - let trigger_point = case; - let closest_node = tree.root_node(); - let query = Queries::default(); - - let query = &query.jinja_imports; - let capturer = IncludeCapturer::default(); - let props = query_props( - closest_node, - source, - trigger_point.0, - query, - false, - capturer, - ); - let template = props.in_template(case.0); - assert!(template.is_some()); - assert_eq!(&template.unwrap().name, &case.1); - } - } - - #[test] - fn import() { - let source = r#" - {% set a = 11 %} - {% from "some_template" import b %} - "#; - let tree = prepare_jinja_tree(source); - let trigger_point = Point::default(); - let closest_node = tree.root_node(); - let query = Queries::default(); - - let query = &query.jinja_init; - let capturer = JinjaInitCapturer::default(); - let props = query_props(closest_node, source, trigger_point, query, true, capturer); - assert_eq!(props.states.len(), 2); - } - - // #[test] - // fn included_template_completion() { - // let source = r#" - //
- // {% include " - //
- // "#; - - // let cases = [(Point::new(2, 28), false, true, false)]; - // } -} diff --git a/jinja-lsp-queries/src/to_input_edit.rs b/jinja-lsp-queries/src/to_input_edit.rs index cbf790a..a672b53 100644 --- a/jinja-lsp-queries/src/to_input_edit.rs +++ b/jinja-lsp-queries/src/to_input_edit.rs @@ -2,8 +2,6 @@ use ropey::Rope; use tower_lsp::lsp_types::{Position, Range}; use tree_sitter::{InputEdit, Point}; -use crate::tree_builder::JinjaVariable; - // use crate::lsp_files::JinjaVariable; pub trait ToInputEdit { @@ -71,19 +69,6 @@ impl ToInputEdit for Rope { } } -pub fn to_position(variable: &JinjaVariable) -> (Position, Position) { - ( - Position::new( - variable.location.0.row as u32, - variable.location.0.column as u32, - ), - Position::new( - variable.location.1.row as u32, - variable.location.1.column as u32, - ), - ) -} - pub fn to_position2(point: Point) -> Position { Position::new(point.row as u32, point.column as u32) } diff --git a/jinja-lsp-queries/src/tree_builder.rs b/jinja-lsp-queries/src/tree_builder.rs index 0e8c400..896c830 100644 --- a/jinja-lsp-queries/src/tree_builder.rs +++ b/jinja-lsp-queries/src/tree_builder.rs @@ -1,4 +1,4 @@ -use tree_sitter::{Node, Point, QueryCapture}; +use tower_lsp::lsp_types::DiagnosticSeverity; #[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)] pub enum LangType { @@ -6,348 +6,22 @@ pub enum LangType { Backend, } -#[derive(Clone, Debug)] -pub enum JinjaKeyword { - For { - key: String, - value: String, - passed_open_paren: bool, - }, - Macro { - name: String, - parameters: Vec<(String, (Point, Point))>, - }, - Block { - name: String, - }, - Set { - name: String, - equals: bool, - }, - From { - name: String, - from: String, - }, - With { - name: String, - }, - NoKeyword, -} - -#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum DataType { - Macro, - MacroParameter, - Variable, - BackendVariable, - WithVariable, - Block, - Template, -} - -#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct JinjaVariable { - pub location: (Point, Point), - pub name: String, - pub data_type: DataType, -} - -impl JinjaVariable { - pub fn new(name: &String, location: (Point, Point), data_type: DataType) -> Self { - Self { - name: String::from(name), - location, - data_type, - } - } -} - -impl JinjaKeyword { - pub fn add_identifier(&mut self, identifier: &str, range: (Point, Point)) -> Option<()> { - match self { - JinjaKeyword::For { key, value, .. } => { - if key.is_empty() { - *key = String::from(identifier); - Some(()) - } else if value.is_empty() { - *value = String::from(identifier); - Some(()) - } else { - None - } - } - JinjaKeyword::Macro { name, parameters } => { - if name.is_empty() { - *name = String::from(identifier); - Some(()) - } else { - parameters.push((String::from(identifier), range)); - Some(()) - } - } - JinjaKeyword::Block { name } => { - if name.is_empty() { - *name = String::from(identifier); - Some(()) - } else { - None - } - } - JinjaKeyword::Set { name, .. } => { - if name.is_empty() { - *name = String::from(identifier); - Some(()) - } else { - None - } - } - JinjaKeyword::From { name, from } => { - let from2 = identifier.replace(['"', '\''], ""); - if from.is_empty() { - *from = from2; - Some(()) - } - //else if name.is_empty() { - else { - None - } - // *name = String::from(identifier); - // Some(()) - // } else { - // None - // } - } - JinjaKeyword::With { name } => { - if name.is_empty() { - *name = String::from(identifier); - Some(()) - } else { - None - } - } - JinjaKeyword::NoKeyword => None, - } - } - - pub fn add_operator(&mut self, operator: &str) -> Option<()> { - match self { - JinjaKeyword::For { - passed_open_paren, .. - } => { - if !passed_open_paren.to_owned() && operator.starts_with('(') { - *passed_open_paren = true; - } - None - } - JinjaKeyword::Set { equals, .. } => { - if !*equals && operator.starts_with('=') { - *equals = true; - Some(()) - } else { - None - } - } - _ => Some(()), - } - } - - pub fn get_data(&self, all: &mut Vec, data: &IdentifierState) { - match self { - JinjaKeyword::For { key, value, .. } => { - let key = JinjaVariable::new(key, data.location, DataType::Variable); - all.push(key); - if !value.is_empty() { - let value = JinjaVariable::new(value, data.location, DataType::Variable); - all.push(value); - } - } - JinjaKeyword::Macro { name, parameters } => { - let name = JinjaVariable::new(name, data.location, DataType::Macro); - all.push(name); - for param in parameters { - let param = JinjaVariable::new(¶m.0, param.1, DataType::MacroParameter); - all.push(param); - } - } - JinjaKeyword::Block { name } => { - let name = JinjaVariable::new(name, data.location, DataType::Block); - all.push(name); - } - JinjaKeyword::Set { name, equals: _ } => { - let name = JinjaVariable::new(name, data.location, DataType::Variable); - all.push(name); - } - JinjaKeyword::From { name: _, from } => { - let from = JinjaVariable::new(from, data.location, DataType::Template); - all.push(from); - } - JinjaKeyword::With { name } => { - let name = JinjaVariable::new(name, data.location, DataType::WithVariable); - all.push(name); - } - JinjaKeyword::NoKeyword => { - // - } - } - } -} - -pub static KEYWORDS: [&str; 8] = [ - "for", "macro", "block", "set", "from", "import", "with", "include", -]; - -#[derive(Clone, Default, Debug)] -pub struct IdentifierState { - pub keyword: JinjaKeyword, - pub location: (Point, Point), - pub statement_started: bool, - pub statement_ended: bool, - pub id: usize, - pub have_keyword: bool, -} - -impl IdentifierState { - pub fn parse_start_statement(&mut self, capture: &QueryCapture<'_>, source: &str) { - let mut walker = capture.node.walk(); - let children = capture.node.children(&mut walker); - self.id = capture.node.id(); - for child in children { - match child.kind_id() { - 57 => self.statement_started = true, - 58 => self.statement_ended = true, - 63 => self.add_keyword(child, source), - 1 => self.add_identifier(child, source), - 50 => self.add_operator(child, source), - 51 => self.add_identifier(child, source), - _ => (), - } - } - } - - pub fn parse_end_statement(&mut self, _capture: &QueryCapture<'_>, _source: &str) { - // let mut c2 = capture.node.walk(); - // let children = capture.node.children(&mut c2); - // for child in children { - // match child.kind_id() { - // 26 => (), - // _ => (), - // } - // } - } - - pub fn add_keyword(&mut self, child: Node<'_>, source: &str) { - if self.have_keyword || !self.statement_started || self.statement_ended { - return; - } - let kw = child.utf8_text(source.as_bytes()); - if let Ok(kw) = kw { - if !KEYWORDS.contains(&kw) { - return; - } - let kw = JinjaKeyword::try_from(kw); - if let Ok(kw) = kw { - self.keyword = kw - } - self.have_keyword = true; - } - } - - pub fn add_identifier(&mut self, child: Node<'_>, source: &str) { - if !self.have_keyword || !self.statement_started || self.statement_ended { - return; - } - let identifier = child.utf8_text(source.as_bytes()); - let start = child.start_position(); - let end = child.end_position(); - if let Some(_s) = identifier - .ok() - .and_then(|id| { - if id.parse::().is_ok() { - None - } else { - Some(id) - } - }) - .and_then(|id| -> Option<()> { self.keyword.add_identifier(id, (start, end)) }) - { - self.have_keyword = true; - if self.location.0.row == 0 && self.location.0.column == 0 { - self.location = (start, end); - } - } - } - - fn add_operator(&mut self, child: Node<'_>, source: &str) { - if !self.have_keyword || !self.statement_started || self.statement_ended { - return; - } - let operator = child.utf8_text(source.as_bytes()); - operator - .ok() - .and_then(|operator| { - if operator.starts_with('(') - || operator.starts_with(',') - || operator.starts_with(')') - || operator.starts_with('=') - { - Some(operator) - } else { - None - } - }) - .and_then(|operator| self.keyword.add_operator(operator)); - } -} - -impl Default for JinjaKeyword { - fn default() -> Self { - Self::NoKeyword - } -} - -impl TryFrom<&str> for JinjaKeyword { - type Error = (); - - fn try_from(value: &str) -> Result { - let keyword = match value { - "for" => Some(JinjaKeyword::For { - key: String::new(), - value: String::new(), - passed_open_paren: false, - }), - "macro" => Some(JinjaKeyword::Macro { - name: String::new(), - parameters: vec![], - }), - "block" => Some(JinjaKeyword::Block { - name: String::new(), - }), - "set" => Some(JinjaKeyword::Set { - name: String::new(), - equals: false, - }), - "from" | "include" => Some(JinjaKeyword::From { - name: String::new(), - from: String::new(), - }), - "with" => Some(JinjaKeyword::With { - name: String::new(), - }), - _ => None, - }; - match keyword.is_none() { - true => Err(()), - false => Ok(keyword.unwrap()), - } - } -} - pub enum JinjaDiagnostic { DefinedSomewhere, Undefined, TemplateNotFound, } +impl JinjaDiagnostic { + pub fn severity(&self) -> DiagnosticSeverity { + match &self { + JinjaDiagnostic::DefinedSomewhere => DiagnosticSeverity::INFORMATION, + JinjaDiagnostic::Undefined => DiagnosticSeverity::WARNING, + JinjaDiagnostic::TemplateNotFound => DiagnosticSeverity::WARNING, + } + } +} + impl ToString for JinjaDiagnostic { fn to_string(&self) -> String { match self { diff --git a/jinja-lsp/Cargo.toml b/jinja-lsp/Cargo.toml index c810992..68ca2c8 100644 --- a/jinja-lsp/Cargo.toml +++ b/jinja-lsp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jinja-lsp" -version = "0.1.61" +version = "0.1.62" edition = "2021" license = "MIT" authors = ["uros-5"] @@ -25,9 +25,9 @@ serde_json = "1.0.78" tokio = { version = "1.17.0", features = ["full"] } tower-lsp = { version = "0.20.0", features = ["proposed"]} serde = { version = "1.0", features = ["derive"] } -tree-sitter = "0.20.10" +tree-sitter = "^0.20.10" walkdir = "2.4.0" anyhow = "1.0.75" -tree-sitter-jinja2 = "0.0.5" -tree-sitter-rust = "0.20.4" +tree-sitter-jinja2 = "0.0.6" +tree-sitter-rust = "^0.20.4" jinja-lsp-queries = { path = "../jinja-lsp-queries", version = "0.1.61"} diff --git a/jinja-lsp/src/channels/diagnostics.rs b/jinja-lsp/src/channels/diagnostics.rs index 34ba383..96a83e6 100644 --- a/jinja-lsp/src/channels/diagnostics.rs +++ b/jinja-lsp/src/channels/diagnostics.rs @@ -1,9 +1,8 @@ use std::collections::HashMap; -use jinja_lsp_queries::tree_builder::{JinjaDiagnostic, JinjaVariable}; use tokio::sync::mpsc::Receiver; use tower_lsp::{ - lsp_types::{Diagnostic, DiagnosticSeverity, MessageType, Position, Range, Url}, + lsp_types::{Diagnostic, MessageType, Url}, Client, }; @@ -11,77 +10,19 @@ pub fn diagnostics_task(client: Client, mut receiver: Receiver { - let mut hm: HashMap> = HashMap::new(); - let mut added = false; - for (file, diags) in diagnostics { - for (variable, diag2) in diags { - let severity = { - match diag2 { - JinjaDiagnostic::DefinedSomewhere => { - DiagnosticSeverity::INFORMATION - } - JinjaDiagnostic::Undefined => DiagnosticSeverity::WARNING, - JinjaDiagnostic::TemplateNotFound => { - DiagnosticSeverity::WARNING - } - } - }; - added = true; - - let diagnostic = Diagnostic { - range: Range::new( - Position::new( - variable.location.0.row as u32, - variable.location.0.column as u32, - ), - Position::new( - variable.location.1.row as u32, - variable.location.1.column as u32, - ), - ), - severity: Some(severity), - message: diag2.to_string(), - source: Some(String::from("jinja-lsp")), - ..Default::default() - }; - - if hm.contains_key(&file) { - let _ = hm.get_mut(&file).is_some_and(|d| { - d.push(diagnostic); - false - }); - } else { - hm.insert(String::from(&file), vec![diagnostic]); - } - } - } - - for (url, diagnostics) in hm { - if let Ok(uri) = Url::parse(&url) { - client.publish_diagnostics(uri, diagnostics, None).await; - } - } - if let Some(uri) = current_file { - if !added { - let uri = Url::parse(&uri).unwrap(); - client.publish_diagnostics(uri, vec![], None).await; - } + DiagnosticMessage::Str(msg) => client.log_message(MessageType::INFO, msg).await, + DiagnosticMessage::Errors2(all_errors) => { + for (uri, errors) in all_errors.into_iter() { + let uri = Url::parse(&uri).unwrap(); + client.publish_diagnostics(uri, errors, None).await; } } - DiagnosticMessage::Str(msg) => client.log_message(MessageType::INFO, msg).await, } } }); } pub enum DiagnosticMessage { - Errors { - diagnostics: HashMap>, - current_file: Option, - }, + Errors2(HashMap>), Str(String), } diff --git a/jinja-lsp/src/channels/lsp.rs b/jinja-lsp/src/channels/lsp.rs index 20dbaf0..98c48ea 100644 --- a/jinja-lsp/src/channels/lsp.rs +++ b/jinja-lsp/src/channels/lsp.rs @@ -1,4 +1,4 @@ -use jinja_lsp_queries::capturer::object::CompletionType; +use jinja_lsp_queries::search::objects::CompletionType; use serde_json::Value; use tokio::sync::{mpsc, oneshot}; use tower_lsp::{ @@ -19,7 +19,7 @@ use crate::{ backend::code_actions, config::{walkdir, JinjaConfig}, filter::init_filter_completions, - lsp_files::LspFiles, + lsp_files2::LspFiles2, }; use super::diagnostics::DiagnosticMessage; @@ -33,7 +33,7 @@ pub fn lsp_task( // let mut documents = HashMap::new(); let mut can_complete = false; let mut config = JinjaConfig::default(); - let mut lsp_data = LspFiles::default(); + let mut lsp_data = LspFiles2::default(); let filters = init_filter_completions(); tokio::spawn(async move { while let Some(msg) = lsp_recv.recv().await { @@ -127,10 +127,7 @@ pub fn lsp_task( match walkdir(&config) { Ok(errors) => { let _ = diagnostics_channel - .send(DiagnosticMessage::Errors { - diagnostics: errors.0, - current_file: None, - }) + .send(DiagnosticMessage::Errors2(errors.0)) .await; lsp_data = errors.1; lsp_data.config = config.clone(); @@ -197,7 +194,9 @@ pub fn lsp_task( LspMessage::Hover(params, sender) => { let mut res = None; if let Some(hover) = lsp_data.hover(params) { - let filter = filters.iter().find(|name| name.name == hover); + let filter = filters + .iter() + .find(|name| name.name == hover.0.name && hover.1); if let Some(filter) = filter { let markup_content = MarkupContent { kind: MarkupKind::Markdown, diff --git a/jinja-lsp/src/config.rs b/jinja-lsp/src/config.rs index db5476d..f2e23b2 100644 --- a/jinja-lsp/src/config.rs +++ b/jinja-lsp/src/config.rs @@ -1,10 +1,11 @@ use std::{collections::HashMap, path::Path}; -use jinja_lsp_queries::tree_builder::{JinjaDiagnostic, JinjaVariable, LangType}; +use jinja_lsp_queries::tree_builder::LangType; use serde::{Deserialize, Serialize}; +use tower_lsp::lsp_types::Diagnostic; use walkdir::WalkDir; -use crate::lsp_files::LspFiles; +use crate::lsp_files2::LspFiles2; /// Jinja configuration /// `templates` can be absolute and relative path @@ -35,16 +36,13 @@ impl JinjaConfig { } } -pub type InitLsp = ( - HashMap>, - LspFiles, -); +pub type InitLsp = (HashMap>, LspFiles2); pub fn walkdir(config: &JinjaConfig) -> anyhow::Result { let mut all = vec![config.templates.clone()]; let mut backend = config.backend.clone(); all.append(&mut backend); - let mut lsp_files = LspFiles::default(); + let mut lsp_files = LspFiles2::default(); let mut diags = HashMap::new(); for dir in all { let walk = WalkDir::new(dir); diff --git a/jinja-lsp/src/lsp_files.rs b/jinja-lsp/src/lsp_files2.rs similarity index 56% rename from jinja-lsp/src/lsp_files.rs rename to jinja-lsp/src/lsp_files2.rs index ad7c2c1..f70d748 100644 --- a/jinja-lsp/src/lsp_files.rs +++ b/jinja-lsp/src/lsp_files2.rs @@ -1,25 +1,28 @@ use jinja_lsp_queries::{ - search::{jinja_state::JinjaState, rust_state::RustState}, - tree_builder::{DataType, JinjaDiagnostic, JinjaVariable, LangType}, + lsp_helper::search_errors2, + search::{ + completion_start, definition::definition_query, objects::objects_query, queries::Queries2, + rust_identifiers::rust_definition_query, rust_template_completion::rust_templates_query, + templates::templates_query, to_range, Identifier, IdentifierType, + }, + tree_builder::LangType, +}; +use std::{ + collections::{HashMap, HashSet}, + fs::read_to_string, + path::Path, + time::Duration, }; -use std::{collections::HashMap, fs::read_to_string, path::Path, time::Duration}; use tokio::{sync::mpsc, task::JoinHandle, time::sleep}; use tower_lsp::lsp_types::{ - CompletionItemKind, CompletionTextEdit, DidOpenTextDocumentParams, TextDocumentIdentifier, - TextEdit, + CompletionItemKind, CompletionTextEdit, Diagnostic, DidOpenTextDocumentParams, + TextDocumentIdentifier, TextEdit, }; use jinja_lsp_queries::{ - capturer::{ - included::IncludeCapturer, - init::JinjaInitCapturer, - object::{CompletionType, JinjaObjectCapturer}, - rust::RustCapturer, - }, - lsp_helper::search_errors, parsers::Parsers, - queries::{query_props, Queries}, - to_input_edit::{to_position, to_position2, ToInputEdit}, + search::objects::CompletionType, + to_input_edit::{to_position2, ToInputEdit}, }; use ropey::Rope; @@ -35,20 +38,18 @@ use crate::{ config::JinjaConfig, }; -pub struct LspFiles { +pub struct LspFiles2 { trees: HashMap>, documents: HashMap, pub parsers: Parsers, - pub variables: HashMap>, - pub queries: Queries, + pub queries2: Queries2, pub config: JinjaConfig, pub diagnostics_task: JoinHandle<()>, pub main_channel: Option>, - pub jinja_variables: HashMap, - pub rust_variables: HashMap, + pub variables: HashMap>, } -impl LspFiles { +impl LspFiles2 { pub fn read_file(&mut self, path: &&Path, lang_type: LangType) -> Option<()> { if let Ok(name) = std::fs::canonicalize(path) { let name = name.to_str()?; @@ -56,8 +57,6 @@ impl LspFiles { let rope = Rope::from_str(&file_content); let name = format!("file://{}", name); let adding = name.clone(); - self.delete_variables(&name); - self.delete_variables2(&name, lang_type); self.documents.insert(name.to_string(), rope); self.add_tree(&name, lang_type, &file_content); self.add_variables(&adding, lang_type, &file_content); @@ -65,6 +64,38 @@ impl LspFiles { None } + fn add_variables(&mut self, name: &str, lang_type: LangType, file_content: &str) -> Option<()> { + let trees = self.trees.get(&lang_type).unwrap(); + let tree = trees.get(name)?; + let trigger_point = Point::new(0, 0); + match lang_type { + LangType::Backend => { + let mut variables = vec![]; + let query_defs = &self.queries2.rust_definitions; + let query_templates = &self.queries2.rust_templates; + let mut ids = + rust_definition_query(query_defs, tree, trigger_point, file_content, true) + .show(); + let mut templates = + rust_templates_query(query_templates, tree, trigger_point, file_content, true) + .collect(); + variables.append(&mut ids); + variables.append(&mut templates); + self.variables.insert(String::from(name), variables); + } + LangType::Template => { + let mut variables = vec![]; + let query_defs = &self.queries2.jinja_definitions; + let mut definitions = + definition_query(query_defs, tree, trigger_point, file_content, true) + .identifiers(); + variables.append(&mut definitions); + self.variables.insert(String::from(name), variables); + } + } + Some(()) + } + pub fn add_tree( &mut self, file_name: &str, @@ -89,74 +120,6 @@ impl LspFiles { None } - fn delete_variables(&mut self, name: &str) -> Option<()> { - self.variables.get_mut(name)?.clear(); - Some(()) - } - - fn delete_variables2(&mut self, name: &str, t: LangType) -> Option<()> { - self.variables.get_mut(name)?.clear(); - match t { - LangType::Template => self.jinja_variables.get_mut(name)?.reset(), - LangType::Backend => self.rust_variables.get_mut(name)?.reset(), - }; - Some(()) - } - - fn add_variables(&mut self, name: &str, lang_type: LangType, file_content: &str) -> Option<()> { - let trees = self.trees.get(&lang_type).unwrap(); - let tree = trees.get(name)?; - let trigger_point = Point::new(0, 0); - let closest_node = tree.root_node(); - match lang_type { - LangType::Backend => { - let query = &self.queries.rust_idents; - let capturer = RustCapturer::default(); - let mut variables = vec![]; - let capturer = query_props( - closest_node, - file_content, - trigger_point, - query, - true, - capturer, - ); - - for variable in capturer.variables() { - variables.push(JinjaVariable::new( - &variable.0, - variable.1, - DataType::BackendVariable, - )); - } - for macros in capturer.macros() { - for variable in macros.1.variables() { - variables.push(JinjaVariable::new( - variable.0, - *variable.1, - DataType::BackendVariable, - )); - } - } - self.variables.insert(name.to_string(), variables); - } - LangType::Template => { - let query = &self.queries.jinja_init; - let capturer = JinjaInitCapturer::default(); - let capturer = query_props( - closest_node, - file_content, - trigger_point, - query, - true, - capturer, - ); - self.variables.insert(name.to_string(), capturer.to_vec()); - } - } - Some(()) - } - pub fn input_edit( &mut self, file: &String, @@ -174,24 +137,6 @@ impl LspFiles { None } - pub fn read_tree(&self, name: &str) -> Option> { - let rope = self.documents.get(name)?; - let mut writter = FileWriter::default(); - let _ = rope.write_to(&mut writter); - let content = writter.content; - let trees = self.trees.get(&LangType::Template)?; - let tree = trees.get(name)?; - let closest_node = tree.root_node(); - search_errors( - closest_node, - &content, - &self.queries, - &self.variables, - &name.to_string(), - &self.config.templates, - ) - } - pub fn did_change(&mut self, params: DidChangeTextDocumentParams) -> Option<()> { let uri = params.text_document.uri.to_string(); let rope = self.documents.get_mut(&uri)?; @@ -212,7 +157,7 @@ impl LspFiles { let start = rope.to_byte(range.start); rope.insert(start, &change.text); } - let mut w = FileWriter::default(); + let mut w = FileContent::default(); let _ = rope.write_to(&mut w); changes.push((w.content, input_edit)); } @@ -234,12 +179,31 @@ impl LspFiles { None } + pub fn read_tree(&self, name: &str) -> Option> { + let rope = self.documents.get(name)?; + let mut writter = FileContent::default(); + let _ = rope.write_to(&mut writter); + let content = writter.content; + let lang_type = self.config.file_ext(&Path::new(name))?; + let trees = self.trees.get(&lang_type)?; + let tree = trees.get(name)?; + search_errors2( + tree, + &content, + &self.queries2, + &self.variables, + &name.to_string(), + &self.config.templates, + lang_type, + ) + } + pub fn did_save(&mut self, params: DidSaveTextDocumentParams) -> Option { let uri = params.text_document.uri.as_str(); let path = Path::new(&uri); let lang_type = self.config.file_ext(&path)?; let doc = self.documents.get(uri)?; - let mut contents = FileWriter::default(); + let mut contents = FileContent::default(); let _ = doc.write_to(&mut contents); let content = contents.content; self.delete_variables(uri); @@ -251,10 +215,7 @@ impl LspFiles { } else { hm.insert(uri.to_owned(), vec![]); } - let message = DiagnosticMessage::Errors { - diagnostics: hm, - current_file: Some(uri.to_owned()), - }; + let message = DiagnosticMessage::Errors2(hm); Some(message) } @@ -288,42 +249,53 @@ impl LspFiles { let column = params.text_document_position.position.character; let point = Point::new(row as usize, column as usize); let ext = self.config.file_ext(&Path::new(&uri))?; - if ext != LangType::Template { - return None; - } - let trees = self.trees.get(&LangType::Template)?; + let trees = self.trees.get(&ext)?; let tree = trees.get(&uri)?; - let closest_node = tree.root_node(); - let query = &self.queries.jinja_idents; - let capturer = JinjaObjectCapturer::default(); let doc = self.documents.get(&uri)?; - let mut writter = FileWriter::default(); + let mut writter = FileContent::default(); let _ = doc.write_to(&mut writter); - let props = query_props( - closest_node, - &writter.content, - point, - query, - false, - capturer, - ); - if let Some(completion) = props.completion(point) { - return Some(completion); + match ext { + LangType::Template => { + let query = &self.queries2.jinja_objects; + let objects = objects_query(query, tree, point, &writter.content, false); + if let Some(completion) = objects.completion(point) { + return Some(completion); + } + let query = &self.queries2.jinja_imports; + let query = templates_query(query, tree, point, &writter.content, false); + let identifier = query.in_template(point)?.get_identifier(point)?; + let start = completion_start(point, identifier)?; + let range = to_range((identifier.start, identifier.end)); + Some(CompletionType::IncludedTemplate { + name: start.to_owned(), + range, + }) + } + LangType::Backend => { + let rust_templates = rust_templates_query( + &self.queries2.rust_templates, + tree, + point, + &writter.content, + false, + ); + let identifier = rust_templates.in_template(point)?; + let start = completion_start(point, identifier)?; + let range = to_range((identifier.start, identifier.end)); + Some(CompletionType::IncludedTemplate { + name: start.to_owned(), + range, + }) + } } - let query = &self.queries.jinja_imports; - let capturer = IncludeCapturer::default(); - let props = query_props( - closest_node, - &writter.content, - point, - query, - false, - capturer, - ); - props.completion(point) } - pub fn hover(&self, params: HoverParams) -> Option { + fn delete_variables(&mut self, uri: &str) -> Option<()> { + self.variables.get_mut(uri)?.clear(); + Some(()) + } + + pub fn hover(&self, params: HoverParams) -> Option<(Identifier, bool)> { let uri = ¶ms .text_document_position_params .text_document @@ -342,27 +314,23 @@ impl LspFiles { .to_string(); let row = params.text_document_position_params.position.line; let column = params.text_document_position_params.position.character; - let point = Point::new(row as usize, column as usize); + let trigger_point = Point::new(row as usize, column as usize); let trees = self.trees.get(&LangType::Template)?; let tree = trees.get(&uri)?; - let closest_node = tree.root_node(); - let query = &self.queries.jinja_idents; - let capturer = JinjaObjectCapturer::default(); + let query = &self.queries2.jinja_objects; let doc = self.documents.get(&uri)?; let mut writter = FileWriter::default(); let _ = doc.write_to(&mut writter); - let props = query_props( - closest_node, - &writter.content, - point, - query, - false, - capturer, - ); - if props.is_hover(point) { - let id = props.get_last_id()?; - return Some(id); + let objects = objects_query(query, tree, trigger_point, &writter.content, false); + if objects.is_hover(trigger_point) { + let object = objects.get_last_id()?; + if object.is_filter { + return Some((Identifier::from(object), true)); + } } + // else if objects.is_ident(point) { + + // } None } @@ -377,80 +345,92 @@ impl LspFiles { .text_document .uri .clone(); + let lang_type = self.config.file_ext(&Path::new(&uri))?; + let trees = self.trees.get(&lang_type)?; + let tree = trees.get(&uri)?; let row = params.text_document_position_params.position.line; let column = params.text_document_position_params.position.character; let point = Point::new(row as usize, column as usize); - let trees = self.trees.get(&LangType::Template)?; - let tree = trees.get(&uri)?; - let closest_node = tree.root_node(); - let mut current_ident = String::new(); - - let query = &self.queries.jinja_idents; - let capturer = JinjaObjectCapturer::default(); let doc = self.documents.get(&uri)?; let mut writter = FileWriter::default(); let _ = doc.write_to(&mut writter); - let props = query_props( - closest_node, - &writter.content, - point, - query, - false, - capturer, - ); - let mut res = props.is_ident(point).and_then(|ident| { - current_ident = ident.to_string(); - let variables = self.variables.get(&uri)?; - let max = variables - .iter() - .filter(|item| item.name == ident && item.location.0 <= point) - .max()?; - let (start, end) = to_position(max); - let range = Range::new(start, end); - Some(GotoDefinitionResponse::Scalar(Location { - uri: uri2.clone(), - range, - })) - }); - res.is_none().then(|| -> Option<()> { - let query = &self.queries.jinja_imports; - let capturer = IncludeCapturer::default(); - let doc = self.documents.get(&uri)?; - let mut writter = FileWriter::default(); - let _ = doc.write_to(&mut writter); - let props = query_props( - closest_node, - &writter.content, - point, - query, - false, - capturer, - ); - if let Some(last) = props.in_template(point) { - let uri = last.is_template(&self.config.templates)?; - let start = to_position2(Point::new(0, 0)); - let end = to_position2(Point::new(0, 0)); - let range = Range::new(start, end); - let location = Location { uri, range }; - res = Some(GotoDefinitionResponse::Scalar(location)); - None - } else { - let mut all: Vec = vec![]; - for i in &self.variables { - let idents = i.1.iter().filter(|item| item.name == current_ident); - for id in idents { - let uri = Url::parse(i.0).unwrap(); - let (start, end) = to_position(id); - let range = Range::new(start, end); - let location = Location { uri, range }; - all.push(location); + + let mut current_ident = String::new(); + + match lang_type { + LangType::Template => { + let query = &self.queries2.jinja_objects; + let objects = objects_query(query, tree, point, &writter.content, false); + let mut res = objects.is_ident(point).and_then(|ident| { + current_ident = ident.to_owned(); + let variables = self.variables.get(&uri)?; + let max = variables + .iter() + .filter(|item| { + item.name == ident && item.start <= point && point <= item.scope_ends.1 + }) + .max()?; + let (start, end) = (to_position2(max.start), to_position2(max.end)); + let range = Range::new(start, end); + Some(GotoDefinitionResponse::Scalar(Location { + uri: uri2.clone(), + range, + })) + }); + res.is_none().then(|| -> Option<()> { + let query = &self.queries2.jinja_imports; + let query = templates_query(query, tree, point, &writter.content, false); + let identifier = query.in_template(point)?.get_identifier(point)?; + let dir = &self.config.templates; + let path = format!("{dir}/{}", identifier.name); + let buffer = std::fs::canonicalize(path).ok()?; + let url = format!("file://{}", buffer.to_str()?); + let url = Url::parse(&url).ok()?; + let start = to_position2(identifier.start); + let end = to_position2(identifier.end); + let range = Range::new(start, end); + let location = Location::new(url, range); + res = Some(GotoDefinitionResponse::Scalar(location)); + None + }); + res.is_none().then(|| -> Option<()> { + let mut all: Vec = vec![]; + for file in &self.variables { + if file.0 == &uri { + continue; + } + let variables = file.1.iter().filter(|item| item.name == current_ident); + for variable in variables { + let uri = Url::parse(file.0).unwrap(); + let start = to_position2(variable.start); + let end = to_position2(variable.end); + let range = Range::new(start, end); + let location = Location::new(uri, range); + all.push(location); + } } - } - res = Some(GotoDefinitionResponse::Array(all)); - None + res = Some(GotoDefinitionResponse::Array(all)); + None + }); + res } - }); - res + + LangType::Backend => { + let query = &self.queries2.rust_templates; + let templates = rust_templates_query(query, tree, point, &writter.content, false); + let template = templates.in_template(point)?; + let dir = &self.config.templates; + let path = format!("{dir}/{}", template.name); + let buffer = std::fs::canonicalize(path).ok()?; + let url = format!("file://{}", buffer.to_str()?); + let url = Url::parse(&url).ok()?; + let start = to_position2(template.start); + let end = to_position2(template.end); + let range = Range::new(start, end); + let location = Location::new(url, range); + Some(GotoDefinitionResponse::Scalar(location)) + } + } } pub fn code_action(&self, action_params: CodeActionParams) -> Option { @@ -465,25 +445,15 @@ impl LspFiles { let point = Point::new(row as usize, column as usize); let trees = self.trees.get(&LangType::Template)?; let tree = trees.get(&uri)?; - let closest_node = tree.root_node(); - let _current_ident = String::new(); - let query = &self.queries.jinja_idents; - let capturer = JinjaObjectCapturer::default(); + let query = &self.queries2.jinja_objects; let doc = self.documents.get(&uri)?; let mut writter = FileWriter::default(); let _ = doc.write_to(&mut writter); - let props = query_props( - closest_node, - &writter.content, - point, - query, - false, - capturer, - ); - Some(props.in_expr(point)) + let objects = objects_query(query, tree, point, &writter.content, false); + Some(objects.in_expr(point)) } - pub fn read_trees(&self, diags: &mut HashMap>) { + pub fn read_trees(&self, diags: &mut HashMap>) { for tree in self.trees.get(&LangType::Template).unwrap() { let errors = self.read_tree(tree.0); if let Some(errors) = errors { @@ -493,39 +463,45 @@ impl LspFiles { } pub fn read_variables(&self, uri: &Url, position: Position) -> Option> { + let mut items = vec![]; let start = position.line as usize; let end = position.character as usize; let position = Point::new(start, end); let uri = &uri.to_string(); - let variables = self.variables.get(uri)?; - let mut items = vec![]; - for variable in variables.iter() { - if position < variable.location.1 { - continue; - } - items.push(CompletionItem { - label: variable.name.to_string(), - detail: Some(completion_detail(variable.data_type).to_string()), - kind: Some(completion_kind(variable.data_type)), - ..Default::default() + let mut names = HashSet::new(); + let this_file = self.variables.get(uri)?; + let this_file = this_file + .iter() + .filter(|variable| variable.identifier_type != IdentifierType::TemplateBlock) + .filter(|variable| { + let bigger = position >= variable.end; + let in_scope = position <= variable.scope_ends.1; + bigger && in_scope }); + for identifier in this_file { + if !names.contains(&identifier.name) { + names.insert(&identifier.name); + items.push(CompletionItem { + label: identifier.name.to_string(), + detail: Some(identifier.identifier_type.completion_detail().to_owned()), + kind: Some(identifier.identifier_type.completion_kind()), + ..Default::default() // detail: Some() + }); + } } for file in self.variables.iter() { for variable in file.1 { - if variable.data_type == DataType::BackendVariable { + if variable.identifier_type == IdentifierType::BackendVariable { items.push(CompletionItem { label: variable.name.to_string(), - detail: Some(completion_detail(variable.data_type).to_string()), - kind: Some(completion_kind(variable.data_type)), - ..Default::default() + detail: Some(variable.identifier_type.completion_detail().to_owned()), + kind: Some(variable.identifier_type.completion_kind()), + ..Default::default() // detail: Some() }); } } } - if !items.is_empty() { - return Some(items); - } - None + Some(items) } pub fn read_templates(&self, mut prefix: String, range: Range) -> Option> { @@ -569,15 +545,12 @@ impl LspFiles { let diagnostics = self.read_tree(name)?; let mut hm = HashMap::new(); hm.insert(name.to_owned(), diagnostics); - let msg = DiagnosticMessage::Errors { - diagnostics: hm, - current_file: Some(name.to_owned()), - }; + let msg = DiagnosticMessage::Errors2(hm); Some(msg) } } -impl Default for LspFiles { +impl Default for LspFiles2 { fn default() -> Self { let mut trees = HashMap::new(); trees.insert(LangType::Template, HashMap::new()); @@ -587,24 +560,22 @@ impl Default for LspFiles { Self { trees, parsers: Parsers::default(), - variables: HashMap::new(), - queries: Queries::default(), + queries2: Queries2::default(), documents: HashMap::new(), config: JinjaConfig::default(), diagnostics_task, main_channel, - jinja_variables: HashMap::default(), - rust_variables: HashMap::default(), + variables: HashMap::default(), } } } #[derive(Default, Debug)] -pub struct FileWriter { +pub struct FileContent { pub content: String, } -impl std::io::Write for FileWriter { +impl std::io::Write for FileContent { fn write(&mut self, buf: &[u8]) -> std::io::Result { if let Ok(b) = std::str::from_utf8(buf) { self.content.push_str(b); @@ -617,26 +588,20 @@ impl std::io::Write for FileWriter { } } -pub fn completion_kind(variable_type: DataType) -> CompletionItemKind { - match variable_type { - DataType::Macro => CompletionItemKind::FUNCTION, - DataType::MacroParameter => CompletionItemKind::FIELD, - DataType::Variable => CompletionItemKind::VARIABLE, - DataType::BackendVariable => CompletionItemKind::VARIABLE, - DataType::WithVariable => CompletionItemKind::VARIABLE, - DataType::Block => CompletionItemKind::UNIT, - DataType::Template => CompletionItemKind::FILE, - } +#[derive(Default, Debug)] +pub struct FileWriter { + pub content: String, } -pub fn completion_detail(variable_type: DataType) -> &'static str { - match variable_type { - DataType::Macro => "Macro from this file", - DataType::MacroParameter => "Macro parameter from this file", - DataType::Variable => "Variable from this file", - DataType::BackendVariable => "Backend variable.", - DataType::WithVariable => "With variable from this file", - DataType::Block => "Block from this file", - DataType::Template => "Template", +impl std::io::Write for FileWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + if let Ok(b) = std::str::from_utf8(buf) { + self.content.push_str(b); + } + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) } } diff --git a/jinja-lsp/src/main.rs b/jinja-lsp/src/main.rs index 1f6209f..27a9c8a 100644 --- a/jinja-lsp/src/main.rs +++ b/jinja-lsp/src/main.rs @@ -2,7 +2,7 @@ mod backend; pub mod channels; mod config; mod filter; -pub mod lsp_files; +pub mod lsp_files2; use backend::Backend; use tower_lsp::LspService; From b781ae3c4614f3ae64e29aaa1cbbd2a33548a441 Mon Sep 17 00:00:00 2001 From: Uros Mrkobrada Date: Fri, 29 Mar 2024 20:44:28 +0100 Subject: [PATCH 03/10] finished snippets completion, added document symbols --- .../src/search/jinja_completion.rs | 0 jinja-lsp-queries/src/search/mod.rs | 25 ++- jinja-lsp-queries/src/search/objects.rs | 14 +- jinja-lsp-queries/src/search/queries.rs | 4 +- .../src/search/snippets_completion.rs | 153 ++++++++++++++++++ jinja-lsp-queries/src/search/test_queries.rs | 40 ++++- jinja-lsp/src/backend.rs | 36 +++-- jinja-lsp/src/channels/lsp.rs | 106 +++++++++--- jinja-lsp/src/lsp_files2.rs | 72 +++++++-- 9 files changed, 394 insertions(+), 56 deletions(-) delete mode 100644 jinja-lsp-queries/src/search/jinja_completion.rs create mode 100644 jinja-lsp-queries/src/search/snippets_completion.rs diff --git a/jinja-lsp-queries/src/search/jinja_completion.rs b/jinja-lsp-queries/src/search/jinja_completion.rs deleted file mode 100644 index e69de29..0000000 diff --git a/jinja-lsp-queries/src/search/mod.rs b/jinja-lsp-queries/src/search/mod.rs index 2e3e45e..dfaa72e 100644 --- a/jinja-lsp-queries/src/search/mod.rs +++ b/jinja-lsp-queries/src/search/mod.rs @@ -1,16 +1,16 @@ -use tower_lsp::lsp_types::{CompletionItemKind, Position, Range}; +use tower_lsp::lsp_types::{CompletionItemKind, Position, Range, SymbolKind}; use tree_sitter::Point; use self::objects::JinjaObject; pub mod definition; -pub mod jinja_completion; pub mod jinja_state; pub mod objects; pub mod queries; pub mod rust_identifiers; pub mod rust_state; pub mod rust_template_completion; +pub mod snippets_completion; pub mod templates; pub mod test_queries; @@ -43,7 +43,10 @@ impl From<&JinjaObject> for Identifier { pub fn completion_start(trigger_point: Point, identifier: &Identifier) -> Option<&str> { let len = identifier.name.len(); - let diff = identifier.end.column - trigger_point.column; + let diff = identifier.end.column - 1 - trigger_point.column; + if diff == 0 { + return Some(""); + } if diff > len { return None; } @@ -105,4 +108,20 @@ impl IdentifierType { IdentifierType::JinjaTemplate => CompletionItemKind::FILE, } } + + pub fn symbol_kind(&self) -> SymbolKind { + match self { + IdentifierType::ForLoopKey => SymbolKind::VARIABLE, + IdentifierType::ForLoopValue => SymbolKind::VARIABLE, + IdentifierType::ForLoopCount => SymbolKind::FIELD, + IdentifierType::SetVariable => SymbolKind::VARIABLE, + IdentifierType::WithVariable => SymbolKind::VARIABLE, + IdentifierType::MacroName => SymbolKind::FUNCTION, + IdentifierType::MacroParameter => SymbolKind::FIELD, + IdentifierType::TemplateBlock => SymbolKind::MODULE, + IdentifierType::BackendVariable => SymbolKind::VARIABLE, + IdentifierType::UndefinedVariable => SymbolKind::CONSTANT, + IdentifierType::JinjaTemplate => SymbolKind::FILE, + } + } } diff --git a/jinja-lsp-queries/src/search/objects.rs b/jinja-lsp-queries/src/search/objects.rs index cb2e7a6..7f5ec36 100644 --- a/jinja-lsp-queries/src/search/objects.rs +++ b/jinja-lsp-queries/src/search/objects.rs @@ -79,7 +79,7 @@ impl JinjaObjects { return; } self.ident = (start, end); - let is_filter = self.is_hover(start); + let is_filter = self.is_hover(start) && self.is_filter(start); self.objects.push(JinjaObject::new( String::from(value), start, @@ -95,7 +95,7 @@ impl JinjaObjects { return; } self.ident = (start, end); - let is_filter = self.is_hover(start); + let is_filter = self.is_hover(start) && self.is_filter(start); self.objects .push(JinjaObject::new(String::from(value), start, end, is_filter)); } @@ -128,9 +128,12 @@ impl JinjaObjects { } pub fn is_hover(&self, trigger_point: Point) -> bool { - trigger_point >= self.ident.0 - && trigger_point <= self.ident.1 - && self.pipe.1 == self.ident.0 + let in_id = trigger_point >= self.ident.0 && trigger_point <= self.ident.1; + in_id + } + + pub fn is_filter(&self, trigger_point: Point) -> bool { + self.pipe.1 == self.ident.0 } pub fn get_last_id(&self) -> Option<&JinjaObject> { @@ -171,6 +174,7 @@ pub enum CompletionType { Filter, Identifier, IncludedTemplate { name: String, range: Range }, + Snippets { name: String, range: Range }, } static VALID_IDENTIFIERS: [&str; 4] = ["loop", "true", "false", "not"]; diff --git a/jinja-lsp-queries/src/search/queries.rs b/jinja-lsp-queries/src/search/queries.rs index 4600b6a..3e1e0e3 100644 --- a/jinja-lsp-queries/src/search/queries.rs +++ b/jinja-lsp-queries/src/search/queries.rs @@ -7,6 +7,7 @@ pub struct Queries2 { pub jinja_imports: Query, pub rust_definitions: Query, pub rust_templates: Query, + pub jinja_snippets: Query, } impl Clone for Queries2 { @@ -23,6 +24,7 @@ impl Default for Queries2 { rust_definitions: Query::new(tree_sitter_rust::language(), RUST_DEFINITIONS).unwrap(), jinja_imports: Query::new(tree_sitter_jinja2::language(), JINJA_IMPORTS).unwrap(), rust_templates: Query::new(tree_sitter_rust::language(), RUST_TEMPLATES).unwrap(), + jinja_snippets: Query::new(tree_sitter_jinja2::language(), JINJA_SNIPPETS).unwrap(), } } } @@ -283,7 +285,7 @@ const RUST_TEMPLATES: &str = r#" ) "#; -const _JINJA_SNIPPETS: &str = r#" +const JINJA_SNIPPETS: &str = r#" [ (statement) @block (ERROR diff --git a/jinja-lsp-queries/src/search/snippets_completion.rs b/jinja-lsp-queries/src/search/snippets_completion.rs new file mode 100644 index 0000000..75f1437 --- /dev/null +++ b/jinja-lsp-queries/src/search/snippets_completion.rs @@ -0,0 +1,153 @@ +use tower_lsp::lsp_types::{ + CompletionItem, CompletionItemKind, CompletionTextEdit, InsertReplaceEdit, Position, Range, + TextEdit, +}; +use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; + +use crate::to_input_edit::to_position2; + +use super::Identifier; + +#[derive(Default, Debug)] +pub struct Snippets { + keyword: Identifier, + block: (Identifier, usize, bool), +} + +impl Snippets { + pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>, source: &str) -> Option<()> { + let id = capture.node.id(); + let start = capture.node.start_position(); + let end = capture.node.end_position(); + + if name == "block" || name == "error1" && self.block.1 != id { + self.block.0.start = start; + self.block.0.end = end; + self.block.1 = id; + self.block.2 = true; + } else if name == "longer_keyword" { + let keyword = capture.node.utf8_text(source.as_bytes()).ok()?; + let keyword = Identifier::new(keyword, start, end); + self.keyword = keyword; + } + None + } + + pub fn at_keyword(&self, trigger_point: Point) -> Option { + if trigger_point >= self.keyword.start + && trigger_point == self.keyword.end + && trigger_point >= self.block.0.start + && trigger_point <= self.block.0.end + { + Some(self.keyword.name.to_owned()) + } else { + None + } + } + + pub fn last_range(&self) -> Range { + let start = to_position2(self.block.0.start); + let end = to_position2(self.block.0.end); + Range { start, end } + } +} + +pub fn snippets_query( + query: &Query, + tree: &Tree, + trigger_point: Point, + text: &str, + all: bool, +) -> Snippets { + let closest_node = tree.root_node(); + let mut snippets = Snippets::default(); + let mut cursor_qry = QueryCursor::new(); + let capture_names = query.capture_names(); + let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); + let captures = matches.into_iter().flat_map(|m| { + m.captures + .iter() + .filter(|capture| all || capture.node.start_position() <= trigger_point) + }); + for capture in captures { + let name = &capture_names[capture.index as usize]; + snippets.check(name, capture, text); + } + snippets +} + +/// Range will be updated +pub fn snippets() -> Vec { + // label detail text + let all = [ + ( + "for1", + "Basic for loop", + r#"{% for ${1:i} in ${2:items} %} +{% endfor %}"#, + ), + ( + "for2", + "For loop with key and value", + r#"{% for (${1:key}, ${2:value}) in ${3:items} %} +{% endfor %}"#, + ), + ( + "with", + "With block", + r#"{% with $1 %} +{% endwith %}"#, + ), + ("set1", "Set variable", r#"{% set ${1:key} = ${2:"0"} %}"#), + ( + "set2", + "Set with scope", + r#"{% set ${1:data} %} +{% endset %}"#, + ), + ("include", "Include template", r#"{% include "$1" %}"#), + ( + "from", + "Import from other template", + r#"{% from "$1" import ${2:module} %}"#, + ), + ( + "import", + "Import entire template as module", + r#" {% import "$1" as ${2:module} %}"#, + ), + ("extends", "Extend parent template", r#"{% extends "$1" %}"#), + ( + "if1", + "If statement", + r#"{% if $1 %} +{% endif %}"#, + ), + ( + "if2", + "If statement", + r#"{% if $1 %} +{% elif $2 %} +{% endif %}"#, + ), + ]; + + let mut snippets = vec![]; + + for snippet in all { + let edit = TextEdit { + new_text: snippet.2.to_owned(), + ..Default::default() + }; + let text_edit = CompletionTextEdit::Edit(edit); + let item = CompletionItem { + label: snippet.0.to_owned(), + detail: Some(snippet.1.to_owned()), + kind: Some(CompletionItemKind::SNIPPET), + text_edit: Some(text_edit), + ..Default::default() + }; + snippets.push(item); + } + snippets +} diff --git a/jinja-lsp-queries/src/search/test_queries.rs b/jinja-lsp-queries/src/search/test_queries.rs index 4fb630e..4ec2759 100644 --- a/jinja-lsp-queries/src/search/test_queries.rs +++ b/jinja-lsp-queries/src/search/test_queries.rs @@ -1,6 +1,6 @@ #[cfg(test)] mod query_tests { - use crate::search::objects::objects_query; + use crate::search::{objects::objects_query, snippets_completion::snippets_query}; use tree_sitter::{Parser, Point}; use crate::{ @@ -81,8 +81,7 @@ mod query_tests { let trigger_point = Point::new(0, 0); // let closest_node = tree.root_node(); let definitions = definition_query(&query, &tree, trigger_point, case.0, true); - // assert_eq!(definitions.identifiers().len(), case.1); - assert!(false); + assert_eq!(definitions.identifiers().len(), case.1); } } @@ -261,7 +260,7 @@ mod query_tests { let templates = rust_templates_query(query, &tree, trigger_point, source, false); if let Some(template) = templates.in_template(trigger_point) { if let Some(completion) = completion_start(trigger_point, template) { - assert_eq!(completion, "accou"); + assert_eq!(completion, "accoun"); } } } @@ -283,7 +282,36 @@ mod query_tests { let query = query.jinja_definitions; let tree = prepare_jinja_tree(source); let trigger_point = Point::new(0, 0); - let objects = definition_query(&query, &tree, trigger_point, source, true); - assert!(true); + let definitions = definition_query(&query, &tree, trigger_point, source, true); + let keys = definitions.identifiers(); + if let Some(last) = keys.last() { + assert_eq!(last.scope_ends.1, Point::new(9, 4)); + } + } + + #[test] + fn snippets() { + let cases = [ + ( + "{% if} {% if a == 123 %} {{ a }} {% endif %}", + Point::new(0, 5), + true, + Some("if".to_owned()), + ), + ( + "{% with} {{ var }} {% with %}", + Point::new(0, 26), + true, + Some("with".to_owned()), + ), + ("{% with ", Point::new(0, 9), false, None), + ]; + let query = Queries2::default(); + let query = query.jinja_snippets; + for case in cases { + let tree = prepare_jinja_tree(case.0); + let snippets = snippets_query(&query, &tree, case.1, case.0, false); + assert_eq!(snippets.at_keyword(case.1), case.3); + } } } diff --git a/jinja-lsp/src/backend.rs b/jinja-lsp/src/backend.rs index 7212732..6b3466f 100644 --- a/jinja-lsp/src/backend.rs +++ b/jinja-lsp/src/backend.rs @@ -6,8 +6,9 @@ use tokio::sync::{ use tower_lsp::{ jsonrpc::Result, lsp_types::{ - CompletionParams, CompletionResponse, DidCloseTextDocumentParams, - DidOpenTextDocumentParams, InitializeParams, InitializeResult, + CompletionParams, CompletionResponse, DidChangeConfigurationParams, + DidCloseTextDocumentParams, DidOpenTextDocumentParams, DocumentSymbolParams, + DocumentSymbolResponse, InitializeParams, InitializeResult, }, Client, LanguageServer, }; @@ -48,13 +49,7 @@ impl LanguageServer for Backend { .main_channel .send(LspMessage::Initialized(sender)) .await; - if let Ok(msg) = rx.await { - if !msg { - let _ = self.shutdown().await; - } - } else { - let _ = self.shutdown().await; - } + if (rx.await).is_ok() {} } async fn did_open(&self, params: DidOpenTextDocumentParams) { @@ -131,6 +126,29 @@ impl LanguageServer for Backend { Ok(None) } + async fn document_symbol( + &self, + params: DocumentSymbolParams, + ) -> Result> { + let (sender, tx) = oneshot::channel(); + let _ = self + .main_channel + .send(LspMessage::DocumentSymbol(params, sender)) + .await; + if let Ok(symbols) = tx.await { + return Ok(symbols); + } + + Ok(None) + } + + async fn did_change_configuration(&self, params: DidChangeConfigurationParams) { + let _ = self + .main_channel + .send(LspMessage::DidChangeConfiguration(params)) + .await; + } + async fn shutdown(&self) -> Result<()> { Ok(()) } diff --git a/jinja-lsp/src/channels/lsp.rs b/jinja-lsp/src/channels/lsp.rs index 98c48ea..f7b02b4 100644 --- a/jinja-lsp/src/channels/lsp.rs +++ b/jinja-lsp/src/channels/lsp.rs @@ -1,16 +1,17 @@ -use jinja_lsp_queries::search::objects::CompletionType; +use jinja_lsp_queries::search::{objects::CompletionType, snippets_completion::snippets}; use serde_json::Value; use tokio::sync::{mpsc, oneshot}; use tower_lsp::{ lsp_types::{ CodeActionParams, CodeActionProviderCapability, CodeActionResponse, CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse, - DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, - Documentation, ExecuteCommandOptions, ExecuteCommandParams, GotoDefinitionParams, - GotoDefinitionResponse, Hover, HoverContents, HoverParams, HoverProviderCapability, - InitializeParams, InitializeResult, MarkupContent, MarkupKind, MessageType, OneOf, - ServerCapabilities, ServerInfo, TextDocumentSyncCapability, TextDocumentSyncKind, - TextDocumentSyncOptions, TextDocumentSyncSaveOptions, + CompletionTextEdit, DidChangeConfigurationParams, DidChangeTextDocumentParams, + DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentSymbolParams, + DocumentSymbolResponse, Documentation, ExecuteCommandOptions, ExecuteCommandParams, + GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams, + HoverProviderCapability, InitializeParams, InitializeResult, MarkupContent, MarkupKind, + MessageType, OneOf, ServerCapabilities, ServerInfo, TextDocumentSyncCapability, + TextDocumentSyncKind, TextDocumentSyncOptions, TextDocumentSyncSaveOptions, TextEdit, }, Client, }; @@ -35,6 +36,7 @@ pub fn lsp_task( let mut config = JinjaConfig::default(); let mut lsp_data = LspFiles2::default(); let filters = init_filter_completions(); + let snippets = snippets(); tokio::spawn(async move { while let Some(msg) = lsp_recv.recv().await { match msg { @@ -67,6 +69,7 @@ pub fn lsp_task( commands: vec!["reset_variables".to_string(), "warn".to_string()], ..Default::default() }); + let document_symbol_provider = Some(OneOf::Left(true)); let msg = InitializeResult { capabilities: ServerCapabilities { @@ -93,12 +96,13 @@ pub fn lsp_task( references_provider, code_action_provider, execute_command_provider, + document_symbol_provider, hover_provider, ..ServerCapabilities::default() }, server_info: Some(ServerInfo { name: String::from("jinja-lsp"), - version: Some(String::from("0.1.61")), + version: Some(String::from("0.1.62")), }), offset_encoding: None, }; @@ -108,21 +112,18 @@ pub fn lsp_task( client.log_message(MessageType::INFO, "Initialized").await; if !config.user_defined { client - .log_message(MessageType::INFO, "Config doesn't exist.") + .log_message(MessageType::WARNING, "Config doesn't exist.") .await; - break; } if config.templates.is_empty() { client - .log_message(MessageType::INFO, "Template directory not found") + .log_message(MessageType::WARNING, "Template directory not found") .await; - break; } if config.lang != "rust" { client - .log_message(MessageType::INFO, "Backend language not supported") + .log_message(MessageType::WARNING, "Backend language not supported") .await; - break; } else { match walkdir(&config) { Ok(errors) => { @@ -138,7 +139,6 @@ pub fn lsp_task( let msg = err.to_string(); client.log_message(MessageType::INFO, msg).await; let _ = sender.send(false); - break; } } } @@ -187,20 +187,62 @@ pub fn lsp_task( items = Some(CompletionResponse::Array(templates)); } } + CompletionType::Snippets { name, range } => { + let mut filtered = vec![]; + for snippet in + snippets.iter().filter(|item| item.label.starts_with(&name)) + { + let mut snippet = snippet.clone(); + if let Some(CompletionTextEdit::Edit(TextEdit { + new_text, + .. + })) = snippet.text_edit + { + snippet.text_edit = + Some(CompletionTextEdit::Edit(TextEdit { + range, + new_text, + })); + } + filtered.push(snippet); + } + + if !filtered.is_empty() { + items = Some(CompletionResponse::Array(filtered)); + } + } }; } let _ = sender.send(items); } LspMessage::Hover(params, sender) => { + let uri = params + .text_document_position_params + .text_document + .uri + .clone(); let mut res = None; if let Some(hover) = lsp_data.hover(params) { - let filter = filters - .iter() - .find(|name| name.name == hover.0.name && hover.1); - if let Some(filter) = filter { + if hover.1 { + let filter = filters + .iter() + .find(|name| name.name == hover.0.name && hover.1); + if let Some(filter) = filter { + let markup_content = MarkupContent { + kind: MarkupKind::Markdown, + value: filter.desc.to_string(), + }; + let hover_contents = HoverContents::Markup(markup_content); + let hover = Hover { + contents: hover_contents, + range: None, + }; + res = Some(hover); + } + } else if let Some(data_type) = lsp_data.data_type(uri, hover.0) { let markup_content = MarkupContent { kind: MarkupKind::Markdown, - value: filter.desc.to_string(), + value: data_type.completion_detail().to_owned(), }; let hover_contents = HoverContents::Markup(markup_content); let hover = Hover { @@ -237,6 +279,25 @@ pub fn lsp_task( let _ = diagnostics_channel.send(errors).await; } } + LspMessage::DocumentSymbol(params, sender) => { + if let Some(symbols) = lsp_data.document_symbols(params) { + let _ = sender.send(Some(symbols)); + } + } + LspMessage::DidChangeConfiguration(params) => { + let (sender, rx) = oneshot::channel(); + if let Ok(c) = serde_json::from_value(params.settings) { + config = c; + config.user_defined = true; + } + + if !config.user_defined { + continue; + } + let _ = lsp_channel.send(LspMessage::Initialized(sender)).await; + + if (rx.await).is_ok() {} + } } } }); @@ -262,4 +323,9 @@ pub enum LspMessage { oneshot::Sender>, ), ExecuteCommand(ExecuteCommandParams, oneshot::Sender>), + DocumentSymbol( + DocumentSymbolParams, + oneshot::Sender>, + ), + DidChangeConfiguration(DidChangeConfigurationParams), } diff --git a/jinja-lsp/src/lsp_files2.rs b/jinja-lsp/src/lsp_files2.rs index f70d748..3b18d5c 100644 --- a/jinja-lsp/src/lsp_files2.rs +++ b/jinja-lsp/src/lsp_files2.rs @@ -3,7 +3,8 @@ use jinja_lsp_queries::{ search::{ completion_start, definition::definition_query, objects::objects_query, queries::Queries2, rust_identifiers::rust_definition_query, rust_template_completion::rust_templates_query, - templates::templates_query, to_range, Identifier, IdentifierType, + snippets_completion::snippets_query, templates::templates_query, to_range, Identifier, + IdentifierType, }, tree_builder::LangType, }; @@ -15,8 +16,8 @@ use std::{ }; use tokio::{sync::mpsc, task::JoinHandle, time::sleep}; use tower_lsp::lsp_types::{ - CompletionItemKind, CompletionTextEdit, Diagnostic, DidOpenTextDocumentParams, - TextDocumentIdentifier, TextEdit, + CompletionItemKind, CompletionTextEdit, Diagnostic, DidOpenTextDocumentParams, DocumentSymbol, + DocumentSymbolResponse, SymbolKind, TextDocumentIdentifier, TextEdit, }; use jinja_lsp_queries::{ @@ -145,16 +146,14 @@ impl LspFiles2 { for change in params.content_changes { let range = change.range?; let input_edit = rope.to_input_edit(range, &change.text); - if change.text.is_empty() { - let start = rope.to_byte(range.start); - let end = rope.to_byte(range.end); - if start <= end { - rope.remove(start..end); - } else { - rope.remove(end..start); - } + let start = rope.to_byte(range.start); + let end = rope.to_byte(range.end); + if start <= end { + rope.remove(start..end); } else { - let start = rope.to_byte(range.start); + rope.remove(end..start); + } + if !change.text.is_empty() { rope.insert(start, &change.text); } let mut w = FileContent::default(); @@ -256,6 +255,14 @@ impl LspFiles2 { let _ = doc.write_to(&mut writter); match ext { LangType::Template => { + let query = &self.queries2.jinja_snippets; + let snippets = snippets_query(query, tree, point, &writter.content, false); + if let Some(name) = snippets.at_keyword(point) { + return Some(CompletionType::Snippets { + name, + range: snippets.last_range(), + }); + } let query = &self.queries2.jinja_objects; let objects = objects_query(query, tree, point, &writter.content, false); if let Some(completion) = objects.completion(point) { @@ -326,6 +333,8 @@ impl LspFiles2 { let object = objects.get_last_id()?; if object.is_filter { return Some((Identifier::from(object), true)); + } else { + return Some((Identifier::from(object), false)); } } // else if objects.is_ident(point) { @@ -548,6 +557,45 @@ impl LspFiles2 { let msg = DiagnosticMessage::Errors2(hm); Some(msg) } + + pub fn data_type(&self, uri: Url, hover: Identifier) -> Option { + let mut data_type = IdentifierType::UndefinedVariable; + let this_file = self.variables.get(&uri.as_str().to_string())?; + let this_file = this_file + .iter() + .filter(|variable| variable.identifier_type != IdentifierType::TemplateBlock) + .filter(|variable| { + let bigger = hover.start >= variable.end; + let in_scope = hover.start <= variable.scope_ends.1; + let same_name = hover.name == variable.name; + bigger && in_scope && same_name + }) + .max()?; + Some(this_file.identifier_type.clone()) + } + + pub fn document_symbols( + &self, + params: tower_lsp::lsp_types::DocumentSymbolParams, + ) -> Option { + let mut symbols = vec![]; + let variables = self.variables.get(params.text_document.uri.as_str())?; + for variable in variables { + #[allow(deprecated)] + let symbol = DocumentSymbol { + name: variable.name.to_owned(), + detail: None, + kind: variable.identifier_type.symbol_kind(), + range: to_range((variable.start, variable.end)), + selection_range: to_range((variable.start, variable.end)), + children: None, + deprecated: None, + tags: None, + }; + symbols.push(symbol); + } + Some(DocumentSymbolResponse::Nested(symbols)) + } } impl Default for LspFiles2 { From 008ea4729dfc1c43b1c6b0963e223fe30cc244aa Mon Sep 17 00:00:00 2001 From: Uros Mrkobrada Date: Sat, 30 Mar 2024 00:05:28 +0100 Subject: [PATCH 04/10] improved vscode debugging --- Cargo.lock | 24 +++++++------- jinja-lsp-queries/Cargo.toml | 2 +- jinja-lsp-queries/src/search/definition.rs | 14 ++++++-- jinja-lsp-queries/src/search/objects.rs | 2 +- .../src/search/snippets_completion.rs | 4 +-- jinja-lsp/Cargo.toml | 4 +-- jinja-lsp/src/channels/lsp.rs | 33 +++++++++---------- jinja-lsp/src/lsp_files2.rs | 31 ++++++++--------- 8 files changed, 60 insertions(+), 54 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5cebd3..22a53f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,12 +101,9 @@ checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.83" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" -dependencies = [ - "libc", -] +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" [[package]] name = "cfg-if" @@ -290,7 +287,7 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "jinja-lsp" -version = "0.1.61" +version = "0.1.7" dependencies = [ "anyhow", "env_logger", @@ -308,8 +305,9 @@ dependencies = [ [[package]] name = "jinja-lsp-queries" -version = "0.1.61" +version = "0.1.7" dependencies = [ + "cc", "ropey", "tower-lsp", "tree-sitter", @@ -526,9 +524,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -538,9 +536,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", @@ -865,9 +863,9 @@ dependencies = [ [[package]] name = "tree-sitter-jinja2" -version = "0.0.5" +version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c01a7e67bf0ea5ed41c90edc788ecc7c45453f590bba2f9684810b7db0e822f" +checksum = "06e13eeb9c161d95d0a4326f84aee63006820b1b5bb0206d86fa347cd2f0258b" dependencies = [ "cc", "tree-sitter", diff --git a/jinja-lsp-queries/Cargo.toml b/jinja-lsp-queries/Cargo.toml index eb7d99a..82b979e 100644 --- a/jinja-lsp-queries/Cargo.toml +++ b/jinja-lsp-queries/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jinja-lsp-queries" -version = "0.1.62" +version = "0.1.7" edition = "2021" description = "TreeSitter queries for jinja-lsp" license = "MIT" diff --git a/jinja-lsp-queries/src/search/definition.rs b/jinja-lsp-queries/src/search/definition.rs index 2df66c7..1cf6cba 100644 --- a/jinja-lsp-queries/src/search/definition.rs +++ b/jinja-lsp-queries/src/search/definition.rs @@ -29,8 +29,11 @@ pub enum Definition { impl Definition { fn collect(self, ids: &mut Vec) { match self { - Definition::ForLoop { key, .. } => { + Definition::ForLoop { key, value, .. } => { ids.push(key); + if let Some(value) = value { + ids.push(value); + } } Definition::Set { key, .. } => { ids.push(key); @@ -378,11 +381,18 @@ impl JinjaDefinitions { let mut is_set = false; if let Some(last) = self.current_scope.front_mut() { if let Some(Definition::Set { equals, .. }) = self.definitions.last() { + is_set = true; if self.current_definition == Current::Set && *equals { can_add = false; } } - if can_add { + if is_set && can_add { + self.current_scope.push_front(Scope { + id: capture.node.id(), + start: capture.node.end_position(), + ..Default::default() + }); + } else if can_add { last.start = capture.node.end_position(); } } else if self.current_definition != Current::NoDefinition { diff --git a/jinja-lsp-queries/src/search/objects.rs b/jinja-lsp-queries/src/search/objects.rs index 7f5ec36..fc96825 100644 --- a/jinja-lsp-queries/src/search/objects.rs +++ b/jinja-lsp-queries/src/search/objects.rs @@ -177,4 +177,4 @@ pub enum CompletionType { Snippets { name: String, range: Range }, } -static VALID_IDENTIFIERS: [&str; 4] = ["loop", "true", "false", "not"]; +static VALID_IDENTIFIERS: [&str; 6] = ["loop", "true", "false", "not", "as", "module"]; diff --git a/jinja-lsp-queries/src/search/snippets_completion.rs b/jinja-lsp-queries/src/search/snippets_completion.rs index 75f1437..a3aacdf 100644 --- a/jinja-lsp-queries/src/search/snippets_completion.rs +++ b/jinja-lsp-queries/src/search/snippets_completion.rs @@ -98,7 +98,7 @@ pub fn snippets() -> Vec { r#"{% with $1 %} {% endwith %}"#, ), - ("set1", "Set variable", r#"{% set ${1:key} = ${2:"0"} %}"#), + ("set1", "Set variable", r#"{% set ${1:key} = ${2:value} %}"#), ( "set2", "Set with scope", @@ -114,7 +114,7 @@ pub fn snippets() -> Vec { ( "import", "Import entire template as module", - r#" {% import "$1" as ${2:module} %}"#, + r#"{% import "$1" as ${2:module} %}"#, ), ("extends", "Extend parent template", r#"{% extends "$1" %}"#), ( diff --git a/jinja-lsp/Cargo.toml b/jinja-lsp/Cargo.toml index 68ca2c8..174a446 100644 --- a/jinja-lsp/Cargo.toml +++ b/jinja-lsp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jinja-lsp" -version = "0.1.62" +version = "0.1.7" edition = "2021" license = "MIT" authors = ["uros-5"] @@ -30,4 +30,4 @@ walkdir = "2.4.0" anyhow = "1.0.75" tree-sitter-jinja2 = "0.0.6" tree-sitter-rust = "^0.20.4" -jinja-lsp-queries = { path = "../jinja-lsp-queries", version = "0.1.61"} +jinja-lsp-queries = { path = "../jinja-lsp-queries", version = "0.1.7"} diff --git a/jinja-lsp/src/channels/lsp.rs b/jinja-lsp/src/channels/lsp.rs index f7b02b4..c471aaa 100644 --- a/jinja-lsp/src/channels/lsp.rs +++ b/jinja-lsp/src/channels/lsp.rs @@ -31,8 +31,6 @@ pub fn lsp_task( lsp_channel: mpsc::Sender, mut lsp_recv: mpsc::Receiver, ) { - // let mut documents = HashMap::new(); - let mut can_complete = false; let mut config = JinjaConfig::default(); let mut lsp_data = LspFiles2::default(); let filters = init_filter_completions(); @@ -42,8 +40,8 @@ pub fn lsp_task( match msg { LspMessage::Initialize(params, sender) => { if let Some(client_info) = params.client_info { - if client_info.name == "helix" { - can_complete = true; + if client_info.name == "Visual Studio Code" { + lsp_data.is_vscode = true; } } params @@ -56,11 +54,6 @@ pub fn lsp_task( None }); - if !config.user_defined { - drop(sender); - continue; - } - let definition_provider = Some(OneOf::Left(true)); let references_provider = None; let code_action_provider = Some(CodeActionProviderCapability::Simple(true)); @@ -130,7 +123,9 @@ pub fn lsp_task( let _ = diagnostics_channel .send(DiagnosticMessage::Errors2(errors.0)) .await; + let vscode = lsp_data.is_vscode; lsp_data = errors.1; + lsp_data.is_vscode = vscode; lsp_data.config = config.clone(); lsp_data.main_channel = Some(lsp_channel.clone()); let _ = sender.send(true); @@ -154,7 +149,7 @@ pub fn lsp_task( LspMessage::Completion(params, sender) => { let position = params.text_document_position.position; let uri = params.text_document_position.text_document.uri.clone(); - let completion = lsp_data.completion(params, can_complete); + let completion = lsp_data.completion(params); let mut items = None; if let Some(completion) = completion { @@ -198,11 +193,15 @@ pub fn lsp_task( .. })) = snippet.text_edit { - snippet.text_edit = - Some(CompletionTextEdit::Edit(TextEdit { - range, - new_text, - })); + if !lsp_data.is_vscode { + snippet.text_edit = + Some(CompletionTextEdit::Edit(TextEdit { + range, + new_text, + })); + } else { + snippet.text_edit = None; + } } filtered.push(snippet); } @@ -285,7 +284,7 @@ pub fn lsp_task( } } LspMessage::DidChangeConfiguration(params) => { - let (sender, rx) = oneshot::channel(); + let (sender, _) = oneshot::channel(); if let Ok(c) = serde_json::from_value(params.settings) { config = c; config.user_defined = true; @@ -295,8 +294,6 @@ pub fn lsp_task( continue; } let _ = lsp_channel.send(LspMessage::Initialized(sender)).await; - - if (rx.await).is_ok() {} } } } diff --git a/jinja-lsp/src/lsp_files2.rs b/jinja-lsp/src/lsp_files2.rs index 3b18d5c..8c75873 100644 --- a/jinja-lsp/src/lsp_files2.rs +++ b/jinja-lsp/src/lsp_files2.rs @@ -48,6 +48,7 @@ pub struct LspFiles2 { pub diagnostics_task: JoinHandle<()>, pub main_channel: Option>, pub variables: HashMap>, + pub is_vscode: bool, } impl LspFiles2 { @@ -218,11 +219,7 @@ impl LspFiles2 { Some(message) } - pub fn completion( - &self, - params: CompletionParams, - can_complete2: bool, - ) -> Option { + pub fn completion(&self, params: CompletionParams) -> Option { let can_complete = { matches!( params.context, @@ -236,13 +233,6 @@ impl LspFiles2 { ) }; - if !can_complete { - let can_complete = can_complete2; - if !can_complete { - return None; - } - } - let uri = params.text_document_position.text_document.uri.to_string(); let row = params.text_document_position.position.line; let column = params.text_document_position.position.character; @@ -513,7 +503,11 @@ impl LspFiles2 { Some(items) } - pub fn read_templates(&self, mut prefix: String, range: Range) -> Option> { + pub fn read_templates( + &self, + mut prefix: String, + mut range: Range, + ) -> Option> { let all_templates = self.trees.get(&LangType::Template)?; if prefix.is_empty() { prefix = String::from("file:///"); @@ -528,12 +522,18 @@ impl LspFiles2 { parts.next(); let label = parts.next()?.replacen('/', "", 1); let new_text = format!("\"{label}\""); - let text_edit = CompletionTextEdit::Edit(TextEdit::new(range, new_text)); + let text_edit = { + if self.is_vscode { + None + } else { + Some(CompletionTextEdit::Edit(TextEdit::new(range, new_text))) + } + }; let item = CompletionItem { label, detail: Some("Jinja template".to_string()), kind: Some(CompletionItemKind::FILE), - text_edit: Some(text_edit), + text_edit, ..Default::default() }; abc.push(item); @@ -614,6 +614,7 @@ impl Default for LspFiles2 { diagnostics_task, main_channel, variables: HashMap::default(), + is_vscode: false, } } } From b29ce478e21c880c57c1de51c12a3a33aa7ea47e Mon Sep 17 00:00:00 2001 From: Uros Mrkobrada Date: Sat, 30 Mar 2024 11:31:44 +0100 Subject: [PATCH 05/10] exit early if client doesn't support completion --- jinja-lsp/src/lsp_files2.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jinja-lsp/src/lsp_files2.rs b/jinja-lsp/src/lsp_files2.rs index 8c75873..323b4dd 100644 --- a/jinja-lsp/src/lsp_files2.rs +++ b/jinja-lsp/src/lsp_files2.rs @@ -233,6 +233,10 @@ impl LspFiles2 { ) }; + if !can_complete { + return None; + } + let uri = params.text_document_position.text_document.uri.to_string(); let row = params.text_document_position.position.line; let column = params.text_document_position.position.character; From cf9e170432354497761158c35928b13e6abea45a Mon Sep 17 00:00:00 2001 From: Uros Mrkobrada Date: Sat, 30 Mar 2024 15:20:37 +0100 Subject: [PATCH 06/10] updated dependencies --- jinja-lsp-queries/src/search/definition.rs | 12 +++++++++--- jinja-lsp-queries/src/search/queries.rs | 4 ++-- jinja-lsp/src/backend.rs | 3 +-- jinja-lsp/src/channels/diagnostics.rs | 4 ++-- jinja-lsp/src/channels/lsp.rs | 2 +- jinja-lsp/src/lsp_files2.rs | 4 ++-- 6 files changed, 17 insertions(+), 12 deletions(-) diff --git a/jinja-lsp-queries/src/search/definition.rs b/jinja-lsp-queries/src/search/definition.rs index 1cf6cba..ec601a2 100644 --- a/jinja-lsp-queries/src/search/definition.rs +++ b/jinja-lsp-queries/src/search/definition.rs @@ -193,8 +193,11 @@ impl JinjaDefinitions { } } - fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option<()> { + fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option { match name { + "error" => { + return None; + } "for" => { if !self.exist(capture.node.id()) { self.add(capture.node.id(), Current::For); @@ -434,7 +437,7 @@ impl JinjaDefinitions { // } _ => {} } - None + Some(true) } pub fn identifiers(self) -> Vec { @@ -527,7 +530,10 @@ pub fn definition_query( }); for capture in captures { let name = &capture_names[capture.index as usize]; - definitions.check(name, capture, text); + let err = definitions.check(name, capture, text); + if err.is_none() { + break; + } } let root = tree.root_node().end_position(); definitions.fix_end(root); diff --git a/jinja-lsp-queries/src/search/queries.rs b/jinja-lsp-queries/src/search/queries.rs index 3e1e0e3..d95a142 100644 --- a/jinja-lsp-queries/src/search/queries.rs +++ b/jinja-lsp-queries/src/search/queries.rs @@ -30,8 +30,6 @@ impl Default for Queries2 { } const DEFINITIONS: &str = r#" - - ( [ (statement @@ -155,6 +153,7 @@ const DEFINITIONS: &str = r#" (#eq? @rawkeyword "raw") (statement_end) @range_start ) @raw + (ERROR) @error ] ) [ @@ -164,6 +163,7 @@ const DEFINITIONS: &str = r#" (#match? @endkeyword "^end") (statement_end) ) @ended + ] "#; diff --git a/jinja-lsp/src/backend.rs b/jinja-lsp/src/backend.rs index 6b3466f..687e2e5 100644 --- a/jinja-lsp/src/backend.rs +++ b/jinja-lsp/src/backend.rs @@ -44,12 +44,11 @@ impl LanguageServer for Backend { } async fn initialized(&self, _params: InitializedParams) { - let (sender, rx) = oneshot::channel(); + let (sender, _) = oneshot::channel(); let _ = self .main_channel .send(LspMessage::Initialized(sender)) .await; - if (rx.await).is_ok() {} } async fn did_open(&self, params: DidOpenTextDocumentParams) { diff --git a/jinja-lsp/src/channels/diagnostics.rs b/jinja-lsp/src/channels/diagnostics.rs index 96a83e6..a9558d8 100644 --- a/jinja-lsp/src/channels/diagnostics.rs +++ b/jinja-lsp/src/channels/diagnostics.rs @@ -11,7 +11,7 @@ pub fn diagnostics_task(client: Client, mut receiver: Receiver client.log_message(MessageType::INFO, msg).await, - DiagnosticMessage::Errors2(all_errors) => { + DiagnosticMessage::Errors(all_errors) => { for (uri, errors) in all_errors.into_iter() { let uri = Url::parse(&uri).unwrap(); client.publish_diagnostics(uri, errors, None).await; @@ -23,6 +23,6 @@ pub fn diagnostics_task(client: Client, mut receiver: Receiver>), + Errors(HashMap>), Str(String), } diff --git a/jinja-lsp/src/channels/lsp.rs b/jinja-lsp/src/channels/lsp.rs index c471aaa..e37b6a6 100644 --- a/jinja-lsp/src/channels/lsp.rs +++ b/jinja-lsp/src/channels/lsp.rs @@ -121,7 +121,7 @@ pub fn lsp_task( match walkdir(&config) { Ok(errors) => { let _ = diagnostics_channel - .send(DiagnosticMessage::Errors2(errors.0)) + .send(DiagnosticMessage::Errors(errors.0)) .await; let vscode = lsp_data.is_vscode; lsp_data = errors.1; diff --git a/jinja-lsp/src/lsp_files2.rs b/jinja-lsp/src/lsp_files2.rs index 323b4dd..8274c1c 100644 --- a/jinja-lsp/src/lsp_files2.rs +++ b/jinja-lsp/src/lsp_files2.rs @@ -215,7 +215,7 @@ impl LspFiles2 { } else { hm.insert(uri.to_owned(), vec![]); } - let message = DiagnosticMessage::Errors2(hm); + let message = DiagnosticMessage::Errors(hm); Some(message) } @@ -558,7 +558,7 @@ impl LspFiles2 { let diagnostics = self.read_tree(name)?; let mut hm = HashMap::new(); hm.insert(name.to_owned(), diagnostics); - let msg = DiagnosticMessage::Errors2(hm); + let msg = DiagnosticMessage::Errors(hm); Some(msg) } From 7c5b16ffc7ece194ff50a3049cc77681cb7dc0ad Mon Sep 17 00:00:00 2001 From: Uros Mrkobrada Date: Sun, 31 Mar 2024 17:33:52 +0200 Subject: [PATCH 07/10] renamed search structs, better handling of errors in tree --- jinja-lsp-queries/src/lsp_helper.rs | 19 ++- jinja-lsp-queries/src/search/definition.rs | 4 +- jinja-lsp-queries/src/search/mod.rs | 4 +- jinja-lsp-queries/src/search/objects.rs | 21 ++- jinja-lsp-queries/src/search/queries.rs | 24 +-- .../src/search/snippets_completion.rs | 161 +++++++++++------- jinja-lsp-queries/src/search/templates.rs | 10 +- jinja-lsp-queries/src/search/test_queries.rs | 32 ++-- jinja-lsp/src/backend.rs | 2 +- jinja-lsp/src/channels/lsp.rs | 29 ++-- jinja-lsp/src/config.rs | 7 +- jinja-lsp/src/lsp_files2.rs | 47 ++--- 12 files changed, 215 insertions(+), 145 deletions(-) diff --git a/jinja-lsp-queries/src/lsp_helper.rs b/jinja-lsp-queries/src/lsp_helper.rs index a53aaf3..0d54f7a 100644 --- a/jinja-lsp-queries/src/lsp_helper.rs +++ b/jinja-lsp-queries/src/lsp_helper.rs @@ -4,14 +4,17 @@ use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity, Position, Range}; use tree_sitter::{Point, Tree}; use crate::{ - search::{objects::objects_query, queries::Queries2, Identifier, IdentifierType}, + search::{ + objects::objects_query, queries::Queries, templates::templates_query, Identifier, + IdentifierType, + }, tree_builder::{JinjaDiagnostic, LangType}, }; -pub fn search_errors2( +pub fn search_errors( root: &Tree, source: &str, - query: &Queries2, + queries: &Queries, variables: &HashMap>, file_name: &String, templates: &String, @@ -21,7 +24,7 @@ pub fn search_errors2( match lang_type { LangType::Template => { let trigger_point = Point::new(0, 0); - let query = &query.jinja_objects; + let query = &queries.jinja_objects; let objects = objects_query(query, root, trigger_point, source, true); let objects = objects.show(); let this_file = variables.get(file_name)?; @@ -75,7 +78,13 @@ pub fn search_errors2( diagnostics.push(diagnostic); } } - let id_templates = this_file + + let mut variables = vec![]; + let query_templates = &queries.jinja_imports; + let jinja_imports = templates_query(query_templates, root, trigger_point, source, true); + jinja_imports.collect(&mut variables); + + let id_templates = variables .iter() .filter(|identifier| identifier.identifier_type == IdentifierType::JinjaTemplate); for i in id_templates { diff --git a/jinja-lsp-queries/src/search/definition.rs b/jinja-lsp-queries/src/search/definition.rs index ec601a2..cb9ff7e 100644 --- a/jinja-lsp-queries/src/search/definition.rs +++ b/jinja-lsp-queries/src/search/definition.rs @@ -72,7 +72,7 @@ pub enum Current { } impl Current { - fn from_end(name: &str) -> Self { + fn _from_end(name: &str) -> Self { match name { "endfor" => Self::For, "endset" => Self::Set, @@ -384,7 +384,7 @@ impl JinjaDefinitions { let mut is_set = false; if let Some(last) = self.current_scope.front_mut() { if let Some(Definition::Set { equals, .. }) = self.definitions.last() { - is_set = true; + is_set = !equals; if self.current_definition == Current::Set && *equals { can_add = false; } diff --git a/jinja-lsp-queries/src/search/mod.rs b/jinja-lsp-queries/src/search/mod.rs index dfaa72e..5c05a27 100644 --- a/jinja-lsp-queries/src/search/mod.rs +++ b/jinja-lsp-queries/src/search/mod.rs @@ -43,8 +43,8 @@ impl From<&JinjaObject> for Identifier { pub fn completion_start(trigger_point: Point, identifier: &Identifier) -> Option<&str> { let len = identifier.name.len(); - let diff = identifier.end.column - 1 - trigger_point.column; - if diff == 0 { + let diff = identifier.end.column - trigger_point.column; + if diff == 0 || diff == 1 { return Some(""); } if diff > len { diff --git a/jinja-lsp-queries/src/search/objects.rs b/jinja-lsp-queries/src/search/objects.rs index fc96825..f7aa7a4 100644 --- a/jinja-lsp-queries/src/search/objects.rs +++ b/jinja-lsp-queries/src/search/objects.rs @@ -38,6 +38,9 @@ impl JinjaObjects { let start = capture.node.start_position(); let end = capture.node.end_position(); match name { + "error" => { + return None; + } "just_id" => { self.build_object(capture, source); } @@ -55,7 +58,7 @@ impl JinjaObjects { } _ => (), } - None + Some(()) } pub fn build_object(&mut self, capture: &QueryCapture<'_>, source: &str) { @@ -79,7 +82,7 @@ impl JinjaObjects { return; } self.ident = (start, end); - let is_filter = self.is_hover(start) && self.is_filter(start); + let is_filter = self.is_hover(start) && self.is_filter(); self.objects.push(JinjaObject::new( String::from(value), start, @@ -95,7 +98,7 @@ impl JinjaObjects { return; } self.ident = (start, end); - let is_filter = self.is_hover(start) && self.is_filter(start); + let is_filter = self.is_hover(start) && self.is_filter(); self.objects .push(JinjaObject::new(String::from(value), start, end, is_filter)); } @@ -128,11 +131,10 @@ impl JinjaObjects { } pub fn is_hover(&self, trigger_point: Point) -> bool { - let in_id = trigger_point >= self.ident.0 && trigger_point <= self.ident.1; - in_id + trigger_point >= self.ident.0 && trigger_point <= self.ident.1 } - pub fn is_filter(&self, trigger_point: Point) -> bool { + pub fn is_filter(&self) -> bool { self.pipe.1 == self.ident.0 } @@ -164,7 +166,10 @@ pub fn objects_query( }); for capture in captures { let name = &capture_names[capture.index as usize]; - objects.check(name, capture, text); + let checked = objects.check(name, capture, text); + if checked.is_none() { + break; + } } objects } @@ -174,7 +179,7 @@ pub enum CompletionType { Filter, Identifier, IncludedTemplate { name: String, range: Range }, - Snippets { name: String, range: Range }, + Snippets { range: Range }, } static VALID_IDENTIFIERS: [&str; 6] = ["loop", "true", "false", "not", "as", "module"]; diff --git a/jinja-lsp-queries/src/search/queries.rs b/jinja-lsp-queries/src/search/queries.rs index d95a142..2668103 100644 --- a/jinja-lsp-queries/src/search/queries.rs +++ b/jinja-lsp-queries/src/search/queries.rs @@ -1,7 +1,7 @@ use tree_sitter::Query; #[derive(Debug)] -pub struct Queries2 { +pub struct Queries { pub jinja_definitions: Query, pub jinja_objects: Query, pub jinja_imports: Query, @@ -10,13 +10,13 @@ pub struct Queries2 { pub jinja_snippets: Query, } -impl Clone for Queries2 { +impl Clone for Queries { fn clone(&self) -> Self { Self::default() } } -impl Default for Queries2 { +impl Default for Queries { fn default() -> Self { Self { jinja_definitions: Query::new(tree_sitter_jinja2::language(), DEFINITIONS).unwrap(), @@ -187,6 +187,8 @@ const OBJECTS: &str = r#" (expression) @expr + (ERROR) @error + ] ) "#; @@ -265,6 +267,8 @@ const JINJA_IMPORTS: &str = r#" (#eq? @as_keyword "as") (statement_end) ) @import + + (ERROR) @error ] ) "#; @@ -287,18 +291,14 @@ const RUST_TEMPLATES: &str = r#" const JINJA_SNIPPETS: &str = r#" [ - (statement) @block + (statement_begin) @start + (statement_end) @end (ERROR - (ERROR) - ) @error1 + (ERROR) @error + ) @error_block ( - (keyword) @missing - (#eq? @missing "") - ) - - ( - (keyword) @longer_keyword + (keyword) @keyword ) ] "#; diff --git a/jinja-lsp-queries/src/search/snippets_completion.rs b/jinja-lsp-queries/src/search/snippets_completion.rs index a3aacdf..9db4531 100644 --- a/jinja-lsp-queries/src/search/snippets_completion.rs +++ b/jinja-lsp-queries/src/search/snippets_completion.rs @@ -1,61 +1,80 @@ +use crate::to_input_edit::to_position2; use tower_lsp::lsp_types::{ - CompletionItem, CompletionItemKind, CompletionTextEdit, InsertReplaceEdit, Position, Range, - TextEdit, + CompletionItem, CompletionItemKind, CompletionTextEdit, Range, TextEdit, }; use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; -use crate::to_input_edit::to_position2; - -use super::Identifier; - #[derive(Default, Debug)] pub struct Snippets { - keyword: Identifier, - block: (Identifier, usize, bool), + start: Point, + end: Point, + keyword: Point, + pub is_error: bool, } impl Snippets { - pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>, source: &str) -> Option<()> { - let id = capture.node.id(); - let start = capture.node.start_position(); - let end = capture.node.end_position(); - - if name == "block" || name == "error1" && self.block.1 != id { - self.block.0.start = start; - self.block.0.end = end; - self.block.1 = id; - self.block.2 = true; - } else if name == "longer_keyword" { - let keyword = capture.node.utf8_text(source.as_bytes()).ok()?; - let keyword = Identifier::new(keyword, start, end); - self.keyword = keyword; + pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>) -> Option<()> { + match name { + "start" => { + let start = capture.node.start_position(); + self.start = start; + } + "end" => { + let end = capture.node.end_position(); + self.end = end; + } + "error_block" => { + self.is_error = true; + let count = capture.node.child_count(); + if self.start == Point::default() { + self.start = capture.node.start_position(); + } + if count == 0 { + self.start = capture.node.start_position(); + self.end = capture.node.end_position(); + self.end.column += 1; + return None; + } else { + let end = capture.node.start_position(); + let first = capture.node.child(0).unwrap(); + if self.start.row != end.row { + self.start = capture.node.start_position(); + self.end = first.end_position(); + self.end.column += 1; + } else { + let first = capture.node.child(0).unwrap(); + self.end = first.start_position(); + self.end.column += 1; + } + return None; + } + } + "keyword" => { + self.keyword = capture.node.start_position(); + } + _ => (), } - None + Some(()) } - pub fn at_keyword(&self, trigger_point: Point) -> Option { - if trigger_point >= self.keyword.start - && trigger_point == self.keyword.end - && trigger_point >= self.block.0.start - && trigger_point <= self.block.0.end - { - Some(self.keyword.name.to_owned()) - } else { - None + pub fn to_complete(&self, trigger_point: Point) -> Option { + if self.is_error && trigger_point >= self.start && trigger_point <= self.end { + if self.keyword >= self.start && self.keyword <= self.end { + return None; + } + let start_position = to_position2(trigger_point); + let mut end_position = to_position2(trigger_point); + end_position.character += 1; + return Some(Range::new(start_position, end_position)); } - } - - pub fn last_range(&self) -> Range { - let start = to_position2(self.block.0.start); - let end = to_position2(self.block.0.end); - Range { start, end } + None } } pub fn snippets_query( query: &Query, tree: &Tree, - trigger_point: Point, + mut trigger_point: Point, text: &str, all: bool, ) -> Snippets { @@ -64,6 +83,7 @@ pub fn snippets_query( let mut cursor_qry = QueryCursor::new(); let capture_names = query.capture_names(); let matches = cursor_qry.matches(query, closest_node, text.as_bytes()); + trigger_point.column += 2; let captures = matches.into_iter().flat_map(|m| { m.captures .iter() @@ -71,7 +91,10 @@ pub fn snippets_query( }); for capture in captures { let name = &capture_names[capture.index as usize]; - snippets.check(name, capture, text); + let check = snippets.check(name, capture); + if check.is_none() { + break; + } } snippets } @@ -83,52 +106,76 @@ pub fn snippets() -> Vec { ( "for1", "Basic for loop", - r#"{% for ${1:i} in ${2:items} %} -{% endfor %}"#, + r#" for ${1:i} in ${2:items} %} +{% endfor %} +"#, ), ( "for2", "For loop with key and value", - r#"{% for (${1:key}, ${2:value}) in ${3:items} %} -{% endfor %}"#, + r#" for (${1:key}, ${2:value}) in ${3:items} %} +{% endfor %} +"#, ), ( "with", "With block", - r#"{% with $1 %} -{% endwith %}"#, + r#" with $1 %} +{% endwith %} +"#, + ), + ( + "set1", + "Set variable", + r#" set ${1:key} = ${2:value} %} + "#, ), - ("set1", "Set variable", r#"{% set ${1:key} = ${2:value} %}"#), ( "set2", "Set with scope", - r#"{% set ${1:data} %} -{% endset %}"#, + r#" set ${1:data} %} +{% endset %} + +"#, + ), + ( + "include", + "Include template", + r#" include "$1" %} + "#, ), - ("include", "Include template", r#"{% include "$1" %}"#), ( "from", "Import from other template", - r#"{% from "$1" import ${2:module} %}"#, + r#" from "$1" import ${2:module} %} + "#, ), ( "import", "Import entire template as module", - r#"{% import "$1" as ${2:module} %}"#, + r#" import "$1" as ${2:module} %} + "#, + ), + ( + "extends", + "Extend parent template", + r#" extends "$1" %} + "#, ), - ("extends", "Extend parent template", r#"{% extends "$1" %}"#), ( "if1", "If statement", - r#"{% if $1 %} -{% endif %}"#, + r#" if $1 %} +{% endif %} +"#, ), ( "if2", "If statement", - r#"{% if $1 %} + r#" if $1 %} {% elif $2 %} -{% endif %}"#, +{% endif %} +"#, ), ]; diff --git a/jinja-lsp-queries/src/search/templates.rs b/jinja-lsp-queries/src/search/templates.rs index 8836047..73b6084 100644 --- a/jinja-lsp-queries/src/search/templates.rs +++ b/jinja-lsp-queries/src/search/templates.rs @@ -91,6 +91,9 @@ impl JinjaImports { } pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>, text: &str) -> Option<()> { match name { + "error" => { + return None; + } "extends" => { let id = capture.node.id(); let last = self.imports.get_mut(&id); @@ -211,7 +214,7 @@ impl JinjaImports { _ => (), } - None + Some(()) } pub fn collect(self, ids: &mut Vec) { @@ -239,7 +242,10 @@ pub fn templates_query( }); for capture in captures { let name = &capture_names[capture.index as usize]; - imports.check(name, capture, text); + let res = imports.check(name, capture, text); + if res.is_none() { + break; + } } imports } diff --git a/jinja-lsp-queries/src/search/test_queries.rs b/jinja-lsp-queries/src/search/test_queries.rs index 4ec2759..90c2e96 100644 --- a/jinja-lsp-queries/src/search/test_queries.rs +++ b/jinja-lsp-queries/src/search/test_queries.rs @@ -6,7 +6,7 @@ mod query_tests { use crate::{ search::objects::CompletionType, search::{ - completion_start, definition::definition_query, queries::Queries2, + completion_start, definition::definition_query, queries::Queries, rust_identifiers::rust_definition_query, rust_template_completion::rust_templates_query, templates::templates_query, }, @@ -73,7 +73,7 @@ mod query_tests { 4, ), ]; - let query = Queries2::default(); + let query = Queries::default(); let query = query.jinja_definitions; for case in cases { @@ -87,7 +87,7 @@ mod query_tests { #[test] fn jinja_identifiers() { - let query = Queries2::default(); + let query = Queries::default(); let query = query.jinja_objects; let cases = [ ( @@ -148,7 +148,7 @@ mod query_tests { let tree = prepare_rust_tree(case); let trigger_point = Point::new(0, 0); - let query = Queries2::default(); + let query = Queries::default(); let query = &query.rust_definitions; let rust = rust_definition_query(query, &tree, trigger_point, case, true); assert_eq!(rust.show().len(), 8); @@ -181,7 +181,7 @@ mod query_tests { for case in cases { let tree = prepare_jinja_tree(source); let trigger_point = case.0; - let query = Queries2::default(); + let query = Queries::default(); let query = &query.jinja_objects; let objects = objects_query(query, &tree, trigger_point, source, false); assert_eq!(objects.completion(trigger_point), case.1); @@ -211,7 +211,7 @@ mod query_tests { for case in cases { let tree = prepare_jinja_tree(source); let trigger_point = case.0; - let query = Queries2::default(); + let query = Queries::default(); let query = &query.jinja_imports; let templates = templates_query(query, &tree, trigger_point, source, false); let template = templates.in_template(trigger_point); @@ -239,7 +239,7 @@ mod query_tests { "#; let tree = prepare_rust_tree(source); let trigger_point = Point::default(); - let query = Queries2::default(); + let query = Queries::default(); let query = &query.rust_templates; let templates = rust_templates_query(query, &tree, trigger_point, source, true); assert_eq!(templates.templates.len(), 3); @@ -255,7 +255,7 @@ mod query_tests { "#; let tree = prepare_rust_tree(source); let trigger_point = Point::new(3, 47); - let query = Queries2::default(); + let query = Queries::default(); let query = &query.rust_templates; let templates = rust_templates_query(query, &tree, trigger_point, source, false); if let Some(template) = templates.in_template(trigger_point) { @@ -278,7 +278,7 @@ mod query_tests { {% set b = 11 %} {% endmacro %} "#; - let query = Queries2::default(); + let query = Queries::default(); let query = query.jinja_definitions; let tree = prepare_jinja_tree(source); let trigger_point = Point::new(0, 0); @@ -296,22 +296,16 @@ mod query_tests { "{% if} {% if a == 123 %} {{ a }} {% endif %}", Point::new(0, 5), true, - Some("if".to_owned()), ), - ( - "{% with} {{ var }} {% with %}", - Point::new(0, 26), - true, - Some("with".to_owned()), - ), - ("{% with ", Point::new(0, 9), false, None), + ("{% with} {{ var }} {% with %}", Point::new(0, 26), true), + ("{% with ", Point::new(0, 9), false), ]; - let query = Queries2::default(); + let query = Queries::default(); let query = query.jinja_snippets; for case in cases { let tree = prepare_jinja_tree(case.0); let snippets = snippets_query(&query, &tree, case.1, case.0, false); - assert_eq!(snippets.at_keyword(case.1), case.3); + assert_eq!(snippets.is_error, case.2); } } } diff --git a/jinja-lsp/src/backend.rs b/jinja-lsp/src/backend.rs index 687e2e5..d95e1f4 100644 --- a/jinja-lsp/src/backend.rs +++ b/jinja-lsp/src/backend.rs @@ -174,7 +174,7 @@ pub fn code_actions() -> Vec { } impl Backend { pub fn new(client: Client) -> Self { - let (lsp_sender, lsp_recv) = mpsc::channel(20); + let (lsp_sender, lsp_recv) = mpsc::channel(50); let (diagnostic_sender, diagnostic_recv) = mpsc::channel(20); lsp_task( client.clone(), diff --git a/jinja-lsp/src/channels/lsp.rs b/jinja-lsp/src/channels/lsp.rs index e37b6a6..4d06e7f 100644 --- a/jinja-lsp/src/channels/lsp.rs +++ b/jinja-lsp/src/channels/lsp.rs @@ -9,9 +9,10 @@ use tower_lsp::{ DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentSymbolParams, DocumentSymbolResponse, Documentation, ExecuteCommandOptions, ExecuteCommandParams, GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams, - HoverProviderCapability, InitializeParams, InitializeResult, MarkupContent, MarkupKind, - MessageType, OneOf, ServerCapabilities, ServerInfo, TextDocumentSyncCapability, - TextDocumentSyncKind, TextDocumentSyncOptions, TextDocumentSyncSaveOptions, TextEdit, + HoverProviderCapability, InitializeParams, InitializeResult, InsertReplaceEdit, + MarkupContent, MarkupKind, MessageType, OneOf, ServerCapabilities, ServerInfo, + TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, + TextDocumentSyncSaveOptions, TextEdit, }, Client, }; @@ -20,7 +21,7 @@ use crate::{ backend::code_actions, config::{walkdir, JinjaConfig}, filter::init_filter_completions, - lsp_files2::LspFiles2, + lsp_files2::LspFiles, }; use super::diagnostics::DiagnosticMessage; @@ -32,7 +33,7 @@ pub fn lsp_task( mut lsp_recv: mpsc::Receiver, ) { let mut config = JinjaConfig::default(); - let mut lsp_data = LspFiles2::default(); + let mut lsp_data = LspFiles::default(); let filters = init_filter_completions(); let snippets = snippets(); tokio::spawn(async move { @@ -80,6 +81,7 @@ pub fn lsp_task( "-".to_string(), "\"".to_string(), " ".to_string(), + "%".to_string(), ]), all_commit_characters: None, work_done_progress_options: Default::default(), @@ -182,11 +184,9 @@ pub fn lsp_task( items = Some(CompletionResponse::Array(templates)); } } - CompletionType::Snippets { name, range } => { + CompletionType::Snippets { range } => { let mut filtered = vec![]; - for snippet in - snippets.iter().filter(|item| item.label.starts_with(&name)) - { + for snippet in snippets.iter() { let mut snippet = snippet.clone(); if let Some(CompletionTextEdit::Edit(TextEdit { new_text, @@ -195,10 +195,13 @@ pub fn lsp_task( { if !lsp_data.is_vscode { snippet.text_edit = - Some(CompletionTextEdit::Edit(TextEdit { - range, - new_text, - })); + Some(CompletionTextEdit::InsertAndReplace( + InsertReplaceEdit { + new_text, + insert: range, + replace: range, + }, + )); } else { snippet.text_edit = None; } diff --git a/jinja-lsp/src/config.rs b/jinja-lsp/src/config.rs index f2e23b2..6939250 100644 --- a/jinja-lsp/src/config.rs +++ b/jinja-lsp/src/config.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use tower_lsp::lsp_types::Diagnostic; use walkdir::WalkDir; -use crate::lsp_files2::LspFiles2; +use crate::lsp_files2::LspFiles; /// Jinja configuration /// `templates` can be absolute and relative path @@ -36,13 +36,13 @@ impl JinjaConfig { } } -pub type InitLsp = (HashMap>, LspFiles2); +pub type InitLsp = (HashMap>, LspFiles); pub fn walkdir(config: &JinjaConfig) -> anyhow::Result { let mut all = vec![config.templates.clone()]; let mut backend = config.backend.clone(); all.append(&mut backend); - let mut lsp_files = LspFiles2::default(); + let mut lsp_files = LspFiles::default(); let mut diags = HashMap::new(); for dir in all { let walk = WalkDir::new(dir); @@ -59,6 +59,7 @@ pub fn walkdir(config: &JinjaConfig) -> anyhow::Result { } } + lsp_files.config = config.clone(); lsp_files.read_trees(&mut diags); Ok((diags, lsp_files)) } diff --git a/jinja-lsp/src/lsp_files2.rs b/jinja-lsp/src/lsp_files2.rs index 8274c1c..7bcfaf1 100644 --- a/jinja-lsp/src/lsp_files2.rs +++ b/jinja-lsp/src/lsp_files2.rs @@ -1,7 +1,7 @@ use jinja_lsp_queries::{ - lsp_helper::search_errors2, + lsp_helper::search_errors, search::{ - completion_start, definition::definition_query, objects::objects_query, queries::Queries2, + completion_start, definition::definition_query, objects::objects_query, queries::Queries, rust_identifiers::rust_definition_query, rust_template_completion::rust_templates_query, snippets_completion::snippets_query, templates::templates_query, to_range, Identifier, IdentifierType, @@ -17,7 +17,7 @@ use std::{ use tokio::{sync::mpsc, task::JoinHandle, time::sleep}; use tower_lsp::lsp_types::{ CompletionItemKind, CompletionTextEdit, Diagnostic, DidOpenTextDocumentParams, DocumentSymbol, - DocumentSymbolResponse, SymbolKind, TextDocumentIdentifier, TextEdit, + DocumentSymbolResponse, TextDocumentIdentifier, TextEdit, }; use jinja_lsp_queries::{ @@ -39,11 +39,11 @@ use crate::{ config::JinjaConfig, }; -pub struct LspFiles2 { +pub struct LspFiles { trees: HashMap>, documents: HashMap, pub parsers: Parsers, - pub queries2: Queries2, + pub queries2: Queries, pub config: JinjaConfig, pub diagnostics_task: JoinHandle<()>, pub main_channel: Option>, @@ -51,7 +51,7 @@ pub struct LspFiles2 { pub is_vscode: bool, } -impl LspFiles2 { +impl LspFiles { pub fn read_file(&mut self, path: &&Path, lang_type: LangType) -> Option<()> { if let Ok(name) = std::fs::canonicalize(path) { let name = name.to_str()?; @@ -87,6 +87,12 @@ impl LspFiles2 { } LangType::Template => { let mut variables = vec![]; + let query_snippets = &self.queries2.jinja_snippets; + let snippets = + snippets_query(query_snippets, tree, trigger_point, file_content, true); + if snippets.is_error { + return None; + } let query_defs = &self.queries2.jinja_definitions; let mut definitions = definition_query(query_defs, tree, trigger_point, file_content, true) @@ -187,7 +193,7 @@ impl LspFiles2 { let lang_type = self.config.file_ext(&Path::new(name))?; let trees = self.trees.get(&lang_type)?; let tree = trees.get(name)?; - search_errors2( + search_errors( tree, &content, &self.queries2, @@ -251,11 +257,12 @@ impl LspFiles2 { LangType::Template => { let query = &self.queries2.jinja_snippets; let snippets = snippets_query(query, tree, point, &writter.content, false); - if let Some(name) = snippets.at_keyword(point) { - return Some(CompletionType::Snippets { - name, - range: snippets.last_range(), - }); + if snippets.to_complete(point).is_some() { + let start = to_position2(point); + let mut end = to_position2(point); + end.character += 1; + let range = Range::new(start, end); + return Some(CompletionType::Snippets { range }); } let query = &self.queries2.jinja_objects; let objects = objects_query(query, tree, point, &writter.content, false); @@ -475,7 +482,10 @@ impl LspFiles2 { let this_file = self.variables.get(uri)?; let this_file = this_file .iter() - .filter(|variable| variable.identifier_type != IdentifierType::TemplateBlock) + .filter(|variable| { + variable.identifier_type != IdentifierType::TemplateBlock + && variable.identifier_type != IdentifierType::JinjaTemplate + }) .filter(|variable| { let bigger = position >= variable.end; let in_scope = position <= variable.scope_ends.1; @@ -507,11 +517,7 @@ impl LspFiles2 { Some(items) } - pub fn read_templates( - &self, - mut prefix: String, - mut range: Range, - ) -> Option> { + pub fn read_templates(&self, mut prefix: String, range: Range) -> Option> { let all_templates = self.trees.get(&LangType::Template)?; if prefix.is_empty() { prefix = String::from("file:///"); @@ -563,7 +569,6 @@ impl LspFiles2 { } pub fn data_type(&self, uri: Url, hover: Identifier) -> Option { - let mut data_type = IdentifierType::UndefinedVariable; let this_file = self.variables.get(&uri.as_str().to_string())?; let this_file = this_file .iter() @@ -602,7 +607,7 @@ impl LspFiles2 { } } -impl Default for LspFiles2 { +impl Default for LspFiles { fn default() -> Self { let mut trees = HashMap::new(); trees.insert(LangType::Template, HashMap::new()); @@ -612,7 +617,7 @@ impl Default for LspFiles2 { Self { trees, parsers: Parsers::default(), - queries2: Queries2::default(), + queries2: Queries::default(), documents: HashMap::new(), config: JinjaConfig::default(), diagnostics_task, From a5fca608a83cf550135f7e9f5e50ff7b2962f3e6 Mon Sep 17 00:00:00 2001 From: Uros Mrkobrada Date: Sun, 31 Mar 2024 18:57:48 +0200 Subject: [PATCH 08/10] renamed struct for handling lsp requests --- example/src/main.rs | 2 +- jinja-lsp-queries/src/search/objects.rs | 18 ++++------- .../src/search/snippets_completion.rs | 2 +- jinja-lsp/src/lsp_files2.rs | 32 +++++++++---------- 4 files changed, 25 insertions(+), 29 deletions(-) diff --git a/example/src/main.rs b/example/src/main.rs index e8e76ae..daaa8b2 100644 --- a/example/src/main.rs +++ b/example/src/main.rs @@ -9,7 +9,7 @@ fn main() { phone_number => "(123) 456-7890", street => "123 Main St", city => "Dallas", - header_info => "This is some information about the user." + header_info => "This is some information about the user.", }; jinja.add_global("PROJECT_NAME", "Example"); } diff --git a/jinja-lsp-queries/src/search/objects.rs b/jinja-lsp-queries/src/search/objects.rs index f7aa7a4..bd479d2 100644 --- a/jinja-lsp-queries/src/search/objects.rs +++ b/jinja-lsp-queries/src/search/objects.rs @@ -67,16 +67,13 @@ impl JinjaObjects { let end = capture.node.end_position(); if let Ok(value) = value { if start.row == self.dot.1.row && start.column == self.dot.1.column { - match self - .objects - .last_mut() - .map(|last| { - last.fields.push((String::from(value), (start, end))); - self.ident = (start, end); - }) - .is_none() - { - true => { + let last_object = self.objects.last_mut().map(|last| { + last.fields.push((String::from(value), (start, end))); + self.ident = (start, end); + }); + match last_object { + Some(_) => {} + None => { // TODO: in future add those to main library if VALID_IDENTIFIERS.contains(&value) { return; @@ -90,7 +87,6 @@ impl JinjaObjects { is_filter, )); } - false => (), } } else { // TODO: in future add those to main library diff --git a/jinja-lsp-queries/src/search/snippets_completion.rs b/jinja-lsp-queries/src/search/snippets_completion.rs index 9db4531..0824e6e 100644 --- a/jinja-lsp-queries/src/search/snippets_completion.rs +++ b/jinja-lsp-queries/src/search/snippets_completion.rs @@ -126,7 +126,7 @@ pub fn snippets() -> Vec { ), ( "set1", - "Set variable", + "Set variable that is current scope", r#" set ${1:key} = ${2:value} %} "#, ), diff --git a/jinja-lsp/src/lsp_files2.rs b/jinja-lsp/src/lsp_files2.rs index 7bcfaf1..094c0cf 100644 --- a/jinja-lsp/src/lsp_files2.rs +++ b/jinja-lsp/src/lsp_files2.rs @@ -43,7 +43,7 @@ pub struct LspFiles { trees: HashMap>, documents: HashMap, pub parsers: Parsers, - pub queries2: Queries, + pub queries: Queries, pub config: JinjaConfig, pub diagnostics_task: JoinHandle<()>, pub main_channel: Option>, @@ -73,8 +73,8 @@ impl LspFiles { match lang_type { LangType::Backend => { let mut variables = vec![]; - let query_defs = &self.queries2.rust_definitions; - let query_templates = &self.queries2.rust_templates; + let query_defs = &self.queries.rust_definitions; + let query_templates = &self.queries.rust_templates; let mut ids = rust_definition_query(query_defs, tree, trigger_point, file_content, true) .show(); @@ -87,13 +87,13 @@ impl LspFiles { } LangType::Template => { let mut variables = vec![]; - let query_snippets = &self.queries2.jinja_snippets; + let query_snippets = &self.queries.jinja_snippets; let snippets = snippets_query(query_snippets, tree, trigger_point, file_content, true); if snippets.is_error { return None; } - let query_defs = &self.queries2.jinja_definitions; + let query_defs = &self.queries.jinja_definitions; let mut definitions = definition_query(query_defs, tree, trigger_point, file_content, true) .identifiers(); @@ -196,7 +196,7 @@ impl LspFiles { search_errors( tree, &content, - &self.queries2, + &self.queries, &self.variables, &name.to_string(), &self.config.templates, @@ -255,7 +255,7 @@ impl LspFiles { let _ = doc.write_to(&mut writter); match ext { LangType::Template => { - let query = &self.queries2.jinja_snippets; + let query = &self.queries.jinja_snippets; let snippets = snippets_query(query, tree, point, &writter.content, false); if snippets.to_complete(point).is_some() { let start = to_position2(point); @@ -264,12 +264,12 @@ impl LspFiles { let range = Range::new(start, end); return Some(CompletionType::Snippets { range }); } - let query = &self.queries2.jinja_objects; + let query = &self.queries.jinja_objects; let objects = objects_query(query, tree, point, &writter.content, false); if let Some(completion) = objects.completion(point) { return Some(completion); } - let query = &self.queries2.jinja_imports; + let query = &self.queries.jinja_imports; let query = templates_query(query, tree, point, &writter.content, false); let identifier = query.in_template(point)?.get_identifier(point)?; let start = completion_start(point, identifier)?; @@ -281,7 +281,7 @@ impl LspFiles { } LangType::Backend => { let rust_templates = rust_templates_query( - &self.queries2.rust_templates, + &self.queries.rust_templates, tree, point, &writter.content, @@ -325,7 +325,7 @@ impl LspFiles { let trigger_point = Point::new(row as usize, column as usize); let trees = self.trees.get(&LangType::Template)?; let tree = trees.get(&uri)?; - let query = &self.queries2.jinja_objects; + let query = &self.queries.jinja_objects; let doc = self.documents.get(&uri)?; let mut writter = FileWriter::default(); let _ = doc.write_to(&mut writter); @@ -369,7 +369,7 @@ impl LspFiles { match lang_type { LangType::Template => { - let query = &self.queries2.jinja_objects; + let query = &self.queries.jinja_objects; let objects = objects_query(query, tree, point, &writter.content, false); let mut res = objects.is_ident(point).and_then(|ident| { current_ident = ident.to_owned(); @@ -388,7 +388,7 @@ impl LspFiles { })) }); res.is_none().then(|| -> Option<()> { - let query = &self.queries2.jinja_imports; + let query = &self.queries.jinja_imports; let query = templates_query(query, tree, point, &writter.content, false); let identifier = query.in_template(point)?.get_identifier(point)?; let dir = &self.config.templates; @@ -426,7 +426,7 @@ impl LspFiles { } LangType::Backend => { - let query = &self.queries2.rust_templates; + let query = &self.queries.rust_templates; let templates = rust_templates_query(query, tree, point, &writter.content, false); let template = templates.in_template(point)?; let dir = &self.config.templates; @@ -455,7 +455,7 @@ impl LspFiles { let point = Point::new(row as usize, column as usize); let trees = self.trees.get(&LangType::Template)?; let tree = trees.get(&uri)?; - let query = &self.queries2.jinja_objects; + let query = &self.queries.jinja_objects; let doc = self.documents.get(&uri)?; let mut writter = FileWriter::default(); let _ = doc.write_to(&mut writter); @@ -617,7 +617,7 @@ impl Default for LspFiles { Self { trees, parsers: Parsers::default(), - queries2: Queries::default(), + queries: Queries::default(), documents: HashMap::new(), config: JinjaConfig::default(), diagnostics_task, From 3feccd775ca4753f95d3ed845e056dca77f9be17 Mon Sep 17 00:00:00 2001 From: Uros Mrkobrada Date: Sun, 31 Mar 2024 19:07:23 +0200 Subject: [PATCH 09/10] crates.io fix --- jinja-lsp-queries/Cargo.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/jinja-lsp-queries/Cargo.toml b/jinja-lsp-queries/Cargo.toml index 82b979e..72f07b1 100644 --- a/jinja-lsp-queries/Cargo.toml +++ b/jinja-lsp-queries/Cargo.toml @@ -6,11 +6,9 @@ description = "TreeSitter queries for jinja-lsp" license = "MIT" [dependencies] -tree-sitter = "~0.20.10" +tree-sitter = "0.20.10" tree-sitter-jinja2 = "0.0.6" tree-sitter-rust = "0.20.4" tower-lsp = { version = "0.20.0", features = ["proposed"] } ropey = "1.5.0" -[build-dependencies] -cc="*" From 69ebc82f1d53b280d7fdf6b519db44f5673fedd3 Mon Sep 17 00:00:00 2001 From: Uros Mrkobrada Date: Sun, 31 Mar 2024 19:21:41 +0200 Subject: [PATCH 10/10] updated version --- Cargo.lock | 325 +++++++++++++++++++---------------- jinja-lsp-queries/Cargo.toml | 2 +- jinja-lsp/Cargo.toml | 4 +- 3 files changed, 177 insertions(+), 154 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 22a53f3..73f6ddf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,28 +19,28 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anyhow" -version = "1.0.76" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59d2a3357dde987206219e78ecfbbb6e8dad06cbb65292758d3270e6254f7355" +checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" [[package]] name = "async-trait" -version = "0.1.75" +version = "0.1.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98" +checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] @@ -56,27 +56,26 @@ dependencies = [ [[package]] name = "auto_impl" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ - "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -95,9 +94,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" @@ -155,9 +154,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -169,9 +168,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -179,44 +178,44 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -253,9 +252,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "httparse" @@ -281,13 +280,13 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jinja-lsp" -version = "0.1.7" +version = "0.1.70" dependencies = [ "anyhow", "env_logger", @@ -305,9 +304,8 @@ dependencies = [ [[package]] name = "jinja-lsp-queries" -version = "0.1.7" +version = "0.1.70" dependencies = [ - "cc", "ropey", "tower-lsp", "tree-sitter", @@ -317,9 +315,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.151" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "lock_api" @@ -333,9 +331,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "lsp-types" @@ -352,24 +350,24 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "minijinja" -version = "1.0.10" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "208758577ef2c86cf5dd3e85730d161413ec3284e2d73b2ef65d9a24d9971bcb" +checksum = "3fb3bf58a1ec4f3f228bec851a2066c7717ad308817cd8a08f67c10660c6ff7b" dependencies = [ "serde", ] [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] @@ -382,7 +380,7 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "wasi", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -391,15 +389,15 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.3.3", + "hermit-abi 0.3.9", "libc", ] [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] @@ -430,7 +428,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-targets", + "windows-targets 0.48.5", ] [[package]] @@ -441,29 +439,29 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -471,44 +469,20 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" -version = "1.0.71" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -547,9 +521,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "ropey" @@ -569,9 +543,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "same-file" @@ -590,29 +564,29 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ "itoa", "ryu", @@ -621,13 +595,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3081f5ffbb02284dda55132aa26daecedd7372a42417bbbab6f14ab7d6bb9145" +checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] @@ -650,18 +624,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -672,20 +646,9 @@ checksum = "e9557cb6521e8d009c51a8666f09356f4b817ba9ba0981a305bd86aee47bd35c" [[package]] name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.42" +version = "2.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b7d0a2c048d661a1a59fcd7355baa232f7ed34e0ee4df2eef3c1c1c0d3852d8" +checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" dependencies = [ "proc-macro2", "quote", @@ -694,9 +657,9 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] @@ -718,9 +681,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -732,7 +695,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.48.0", ] [[package]] @@ -743,7 +706,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] @@ -811,7 +774,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] @@ -839,7 +802,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.42", + "syn", ] [[package]] @@ -883,9 +846,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -895,9 +858,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] @@ -914,17 +877,11 @@ dependencies = [ "serde", ] -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -973,7 +930,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", ] [[package]] @@ -982,13 +948,28 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -997,38 +978,80 @@ version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" diff --git a/jinja-lsp-queries/Cargo.toml b/jinja-lsp-queries/Cargo.toml index 72f07b1..fab7465 100644 --- a/jinja-lsp-queries/Cargo.toml +++ b/jinja-lsp-queries/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jinja-lsp-queries" -version = "0.1.7" +version = "0.1.70" edition = "2021" description = "TreeSitter queries for jinja-lsp" license = "MIT" diff --git a/jinja-lsp/Cargo.toml b/jinja-lsp/Cargo.toml index 174a446..f2babdb 100644 --- a/jinja-lsp/Cargo.toml +++ b/jinja-lsp/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jinja-lsp" -version = "0.1.7" +version = "0.1.70" edition = "2021" license = "MIT" authors = ["uros-5"] @@ -30,4 +30,4 @@ walkdir = "2.4.0" anyhow = "1.0.75" tree-sitter-jinja2 = "0.0.6" tree-sitter-rust = "^0.20.4" -jinja-lsp-queries = { path = "../jinja-lsp-queries", version = "0.1.7"} +jinja-lsp-queries = { path = "../jinja-lsp-queries", version = "0.1.70"}