diff --git a/Cargo.lock b/Cargo.lock
index f50bd9b..088d3e1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -136,13 +136,6 @@ dependencies = [
"termcolor",
]
-[[package]]
-name = "example"
-version = "0.1.0"
-dependencies = [
- "minijinja",
-]
-
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@@ -286,7 +279,7 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "jinja-lsp"
-version = "0.1.71"
+version = "0.1.80"
dependencies = [
"anyhow",
"env_logger",
@@ -304,12 +297,13 @@ dependencies = [
[[package]]
name = "jinja-lsp-queries"
-version = "0.1.71"
+version = "0.1.80"
dependencies = [
"ropey",
"tower-lsp",
"tree-sitter",
"tree-sitter-jinja2",
+ "tree-sitter-python",
"tree-sitter-rust",
]
@@ -354,15 +348,6 @@ version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
-[[package]]
-name = "minijinja"
-version = "1.0.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fb3bf58a1ec4f3f228bec851a2066c7717ad308817cd8a08f67c10660c6ff7b"
-dependencies = [
- "serde",
-]
-
[[package]]
name = "miniz_oxide"
version = "0.7.2"
@@ -834,6 +819,16 @@ dependencies = [
"tree-sitter",
]
+[[package]]
+name = "tree-sitter-python"
+version = "0.20.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c93b1b1fbd0d399db3445f51fd3058e43d0b4dcff62ddbdb46e66550978aa5"
+dependencies = [
+ "cc",
+ "tree-sitter",
+]
+
[[package]]
name = "tree-sitter-rust"
version = "0.20.4"
diff --git a/Cargo.toml b/Cargo.toml
index 0bf687a..642ba5b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,6 @@ resolver = "2"
members = [
"jinja-lsp-queries",
"jinja-lsp",
- "example"
]
[profile.dev]
diff --git a/README.md b/README.md
index 0cc8d3d..11b0489 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,12 @@
-
+
jinja-lsp enhances minijinja development experience by providing Helix/Nvim users with advanced features such as autocomplete, syntax highlighting, hover, goto definition, code actions and linting.
+
+

+

+
+
## Installation
```sh
@@ -80,4 +85,4 @@ name = "jinja"
language-servers = ["jinja-lsp"]
```
-[VSCode support](https://marketplace.visualstudio.com/search?term=jinja-lsp&target=VSCode&category=Other&sortBy=Relevance)
+Supported languages: Python, Rust
diff --git a/editors/code/client/package.json b/editors/code/client/package.json
index 3640e0a..58411d6 100644
--- a/editors/code/client/package.json
+++ b/editors/code/client/package.json
@@ -3,7 +3,7 @@
"description": "VSCode part of a language server",
"author": "uros-5",
"license": "MIT",
- "version": "0.1.73",
+ "version": "0.1.80",
"publisher": "uros",
"repository": {
"type": "git",
diff --git a/editors/code/client/src/extension.ts b/editors/code/client/src/extension.ts
index c4ed615..1b2b6b6 100644
--- a/editors/code/client/src/extension.ts
+++ b/editors/code/client/src/extension.ts
@@ -20,7 +20,7 @@ export function activate(context: ExtensionContext) {
// The server is implemented in node
const serverModule = getServer();
if (!serverModule.valid) {
- throw new Error(serverModule.name);
+ throw new Error(serverModule.name);
}
let config: Record = JSON.parse(
@@ -30,7 +30,7 @@ export function activate(context: ExtensionContext) {
// If the extension is launched in debug mode then the debug server options are used
// Otherwise the run options are used
const serverOptions: ServerOptions = {
- run: { command: serverModule.name},
+ run: { command: serverModule.name },
debug: {
command: serverModule.name,
args: [],
@@ -40,11 +40,11 @@ export function activate(context: ExtensionContext) {
// Options to control the language client
const clientOptions: LanguageClientOptions = {
// Register the server for plain text documents
- documentSelector: [{ scheme: 'file', language: 'jinja-html' }, { scheme: 'file', language: 'rust' }],
+ documentSelector: [{ scheme: 'file', language: 'jinja-html' }, { scheme: 'file', language: 'rust' }, { scheme: 'file', language: 'python' }],
initializationOptions: config,
synchronize: {
// Notify the server about file changes to '.clientrc files contained in the workspace
- fileEvents: workspace.createFileSystemWatcher('**/.{jinja, rs}')
+ fileEvents: workspace.createFileSystemWatcher('**/.{jinja, rs, python}')
}
};
diff --git a/editors/code/package-lock.json b/editors/code/package-lock.json
index 02ee973..769d299 100644
--- a/editors/code/package-lock.json
+++ b/editors/code/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "jinja-lsp",
- "version": "0.1.72",
+ "version": "0.1.80",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "jinja-lsp",
- "version": "0.1.72",
+ "version": "0.1.8",
"license": "MIT",
"devDependencies": {
"@types/mocha": "^10.0.6",
diff --git a/editors/code/package.json b/editors/code/package.json
index 9a63a12..da25a4a 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -2,7 +2,7 @@
"name": "jinja-lsp",
"description": "jinja-lsp",
"license": "MIT",
- "version": "0.1.73",
+ "version": "0.1.80",
"repository": {
"url": "https://github.com/uros-5/jinja-lsp"
},
@@ -17,14 +17,17 @@
"multi-root ready",
"jinja",
"minijinja",
- "rust"
+ "rust",
+ "jinja-lsp",
+ "python"
],
"engines": {
"vscode": "^1.75.0"
},
"activationEvents": [
"onLanguage:jinja-html",
- "onLanguage:rust"
+ "onLanguage:rust",
+ "onLanguage:python"
],
"main": "./client/out/extension",
"contributes": {
@@ -47,7 +50,8 @@
"jinja-lsp.lang": {
"type": "string",
"enum": [
- "rust"
+ "rust",
+ "python"
],
"default": "rust",
"description": "Language that is used on backend"
diff --git a/jinja-lsp-queries/Cargo.toml b/jinja-lsp-queries/Cargo.toml
index 4b66a1d..88022c0 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.71"
+version = "0.1.80"
edition = "2021"
description = "TreeSitter queries for jinja-lsp"
license = "MIT"
@@ -11,4 +11,5 @@ tree-sitter-jinja2 = "0.0.6"
tree-sitter-rust = "0.20.4"
tower-lsp = { version = "0.20.0", features = ["proposed"] }
ropey = "1.5.0"
+tree-sitter-python = "=0.20.4"
diff --git a/jinja-lsp-queries/src/lsp_helper.rs b/jinja-lsp-queries/src/lsp_helper.rs
index 0d54f7a..75a1887 100644
--- a/jinja-lsp-queries/src/lsp_helper.rs
+++ b/jinja-lsp-queries/src/lsp_helper.rs
@@ -19,7 +19,7 @@ pub fn search_errors(
file_name: &String,
templates: &String,
lang_type: LangType,
-) -> Option> {
+) -> Option> {
let mut diagnostics = vec![];
match lang_type {
LangType::Template => {
@@ -70,11 +70,7 @@ pub fn search_errors(
}
}
if to_warn {
- let diagnostic = create_diagnostic(
- &Identifier::from(&object),
- err_type.severity(),
- err_type.to_string(),
- );
+ let diagnostic = (err_type, Identifier::from(&object));
diagnostics.push(diagnostic);
}
}
@@ -90,15 +86,13 @@ pub fn search_errors(
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());
+ let diagnostic = (err_type, i.to_owned());
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());
+ let diagnostic = (err_type, i.to_owned());
diagnostics.push(diagnostic);
}
}
@@ -115,11 +109,7 @@ pub fn search_errors(
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(),
- );
+ let diagnostic = (JinjaDiagnostic::TemplateNotFound, template.to_owned());
diagnostics.push(diagnostic);
}
}
diff --git a/jinja-lsp-queries/src/parsers.rs b/jinja-lsp-queries/src/parsers.rs
index 9748a4c..82c12c5 100644
--- a/jinja-lsp-queries/src/parsers.rs
+++ b/jinja-lsp-queries/src/parsers.rs
@@ -19,6 +19,13 @@ impl Parsers {
LangType::Backend => self.backend.parse(text, old_tree),
}
}
+
+ pub fn update_backend(&mut self, lang: &str) {
+ if lang == "python" {
+ self.backend = Parser::new();
+ let _ = self.backend.set_language(tree_sitter_python::language());
+ }
+ }
}
impl Default for Parsers {
diff --git a/jinja-lsp-queries/src/search/definition2.rs b/jinja-lsp-queries/src/search/definition2.rs
deleted file mode 100644
index 8b13789..0000000
--- a/jinja-lsp-queries/src/search/definition2.rs
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/jinja-lsp-queries/src/search/jinja_state.rs b/jinja-lsp-queries/src/search/jinja_state.rs
deleted file mode 100644
index 6f98966..0000000
--- a/jinja-lsp-queries/src/search/jinja_state.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-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 {
- jinja_definitions: JinjaDefinitions,
- jinja_objects: JinjaObjects,
- jinja_imports: JinjaImports,
-}
-
-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();
- self.jinja_imports = Default::default();
- }
-}
diff --git a/jinja-lsp-queries/src/search/mod.rs b/jinja-lsp-queries/src/search/mod.rs
index 5c05a27..47c2893 100644
--- a/jinja-lsp-queries/src/search/mod.rs
+++ b/jinja-lsp-queries/src/search/mod.rs
@@ -4,11 +4,9 @@ use tree_sitter::Point;
use self::objects::JinjaObject;
pub mod definition;
-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;
diff --git a/jinja-lsp-queries/src/search/objects.rs b/jinja-lsp-queries/src/search/objects.rs
index bd479d2..be59311 100644
--- a/jinja-lsp-queries/src/search/objects.rs
+++ b/jinja-lsp-queries/src/search/objects.rs
@@ -178,4 +178,6 @@ pub enum CompletionType {
Snippets { range: Range },
}
-static VALID_IDENTIFIERS: [&str; 6] = ["loop", "true", "false", "not", "as", "module"];
+static VALID_IDENTIFIERS: [&str; 8] = [
+ "loop", "true", "false", "not", "as", "module", "super", "url_for",
+];
diff --git a/jinja-lsp-queries/src/search/queries.rs b/jinja-lsp-queries/src/search/queries.rs
index 509700d..4127389 100644
--- a/jinja-lsp-queries/src/search/queries.rs
+++ b/jinja-lsp-queries/src/search/queries.rs
@@ -5,8 +5,8 @@ pub struct Queries {
pub jinja_definitions: Query,
pub jinja_objects: Query,
pub jinja_imports: Query,
- pub rust_definitions: Query,
- pub rust_templates: Query,
+ pub backend_definitions: Query,
+ pub backend_templates: Query,
pub jinja_snippets: Query,
}
@@ -21,14 +21,26 @@ impl Default for Queries {
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(),
+ backend_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(),
+ backend_templates: Query::new(tree_sitter_rust::language(), RUST_TEMPLATES).unwrap(),
jinja_snippets: Query::new(tree_sitter_jinja2::language(), JINJA_SNIPPETS).unwrap(),
}
}
}
+impl Queries {
+ pub fn update_backend(&mut self, lang: &str) {
+ if lang == "python" {
+ self.backend_templates =
+ Query::new(tree_sitter_python::language(), PYTHON_TEMPLATES).unwrap();
+ self.backend_definitions =
+ Query::new(tree_sitter_python::language(), PYTHON_DEFINITIONS).unwrap();
+ }
+ }
+}
+
const OBJECTS: &str = r#"
(
[
@@ -79,6 +91,8 @@ pub static RUST_DEFINITIONS: &str = r#"
(#match? @method "(add_global|add_filter|add_function)")
) @function
+
+ (ERROR) @error
])
"#;
@@ -197,3 +211,64 @@ const DEFINITIONS: &str = r#"
(ERROR) @error
"#;
+
+const PYTHON_TEMPLATES: &str = r#"
+(call
+ [
+ (attribute
+ (identifier) @method_name
+ )
+ (identifier) @method_name
+ (#any-of? @method_name "render_jinja" "get_template")
+ ]
+ (argument_list
+ (string)+ @template_name
+ )
+)
+"#;
+
+pub static PYTHON_DEFINITIONS: &str = r#"
+
+(
+ (subscript
+ (attribute
+ object: (identifier)* @object
+ attribute: (identifier) @field
+ (#match? @field "^(globals|filters)$")
+ (#eq? @object "jinja_env")
+ )
+ (string
+ (string_content) @key_id
+ )
+ )
+)
+
+(
+ [
+ (call
+ function: (identifier) @method
+ arguments: (argument_list
+ (keyword_argument
+ name: (identifier) @key_id
+ )
+ )
+ )
+
+ (call
+ function: (attribute
+ object: (identifier)
+ attribute: (identifier) @method
+ )
+ arguments: (argument_list
+ (keyword_argument
+ name: (identifier) @key_id
+ )
+ )
+ )
+ ]
+ (#match? @method "^(render_template|render)$")
+ )
+
+ (ERROR) @error
+
+"#;
diff --git a/jinja-lsp-queries/src/search/rust_identifiers.rs b/jinja-lsp-queries/src/search/rust_identifiers.rs
index a68d825..10454b6 100644
--- a/jinja-lsp-queries/src/search/rust_identifiers.rs
+++ b/jinja-lsp-queries/src/search/rust_identifiers.rs
@@ -10,35 +10,24 @@ pub enum Current {
}
#[derive(Default, Debug, Clone)]
-pub struct RustIdentifiers {
+pub struct BackendIdentifiers {
variables: Vec,
- current: Current,
}
-impl RustIdentifiers {
+impl BackendIdentifiers {
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 mut identifier = Identifier::new(name, start, end);
- identifier.identifier_type = IdentifierType::BackendVariable;
- self.variables.push(identifier);
- }
+ let start = capture.node.start_position();
+ let end = capture.node.end_position();
+ let name = capture.node.utf8_text(text.as_bytes()).ok()?;
+ let mut identifier = Identifier::new(name, start, end);
+ identifier.identifier_type = IdentifierType::BackendVariable;
+ self.variables.push(identifier);
}
"name" => {
let start = capture.node.start_position();
@@ -49,22 +38,25 @@ impl RustIdentifiers {
identifier.identifier_type = IdentifierType::BackendVariable;
self.variables.push(identifier);
}
+ "error" => {
+ return None;
+ }
_ => (),
}
- None
+ Some(())
}
}
-pub fn rust_definition_query(
+pub fn backend_definition_query(
query: &Query,
tree: &Tree,
trigger_point: Point,
text: &str,
all: bool,
-) -> RustIdentifiers {
+) -> BackendIdentifiers {
let closest_node = tree.root_node();
let mut cursor_qry = QueryCursor::new();
- let mut rust = RustIdentifiers::default();
+ let mut rust = BackendIdentifiers::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| {
@@ -74,7 +66,9 @@ pub fn rust_definition_query(
});
for capture in captures {
let name = &capture_names[capture.index as usize];
- rust.check(name, capture, text);
+ if rust.check(name, capture, text).is_none() {
+ break;
+ }
}
rust
}
diff --git a/jinja-lsp-queries/src/search/rust_state.rs b/jinja-lsp-queries/src/search/rust_state.rs
deleted file mode 100644
index 7232dc0..0000000
--- a/jinja-lsp-queries/src/search/rust_state.rs
+++ /dev/null
@@ -1,70 +0,0 @@
-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)]
-pub struct RustState {
- rust_identifiers: RustIdentifiers,
- rust_templates: RustTemplates,
-}
-
-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 41e4d78..ca42323 100644
--- a/jinja-lsp-queries/src/search/rust_template_completion.rs
+++ b/jinja-lsp-queries/src/search/rust_template_completion.rs
@@ -3,17 +3,12 @@ use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree};
use super::{Identifier, IdentifierType};
#[derive(Default, Debug, Clone, PartialEq, Eq)]
-pub struct RustTemplateCompletion {
- pub template_name: Identifier,
-}
-
-#[derive(Default, Debug, Clone, PartialEq, Eq)]
-pub struct RustTemplates {
+pub struct BackendTemplates {
pub templates: Vec,
in_method: bool,
}
-impl RustTemplates {
+impl BackendTemplates {
pub fn in_template(&self, trigger_point: Point) -> Option<&Identifier> {
let last = self.templates.last()?;
if trigger_point >= last.start && trigger_point <= last.end {
@@ -48,14 +43,14 @@ impl RustTemplates {
}
}
-pub fn rust_templates_query(
+pub fn backend_templates_query(
query: &Query,
tree: &Tree,
trigger_point: Point,
text: &str,
all: bool,
-) -> RustTemplates {
- let mut templates = RustTemplates::default();
+) -> BackendTemplates {
+ let mut templates = BackendTemplates::default();
let closest_node = tree.root_node();
let mut cursor_qry = QueryCursor::new();
let capture_names = query.capture_names();
diff --git a/jinja-lsp-queries/src/search/snippets_completion.rs b/jinja-lsp-queries/src/search/snippets_completion.rs
index 0824e6e..207f682 100644
--- a/jinja-lsp-queries/src/search/snippets_completion.rs
+++ b/jinja-lsp-queries/src/search/snippets_completion.rs
@@ -25,29 +25,8 @@ impl Snippets {
}
"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;
- }
+ self.end = capture.node.end_position();
+ return None;
}
"keyword" => {
self.keyword = capture.node.start_position();
@@ -58,6 +37,9 @@ impl Snippets {
}
pub fn to_complete(&self, trigger_point: Point) -> Option {
+ if self.is_error && trigger_point <= self.end {
+ return Some(Range::default());
+ }
if self.is_error && trigger_point >= self.start && trigger_point <= self.end {
if self.keyword >= self.start && self.keyword <= self.end {
return None;
diff --git a/jinja-lsp-queries/src/search/templates.rs b/jinja-lsp-queries/src/search/templates.rs
index 73b6084..b66fe43 100644
--- a/jinja-lsp-queries/src/search/templates.rs
+++ b/jinja-lsp-queries/src/search/templates.rs
@@ -67,7 +67,7 @@ impl Import {
}
}
-#[derive(Default)]
+#[derive(Default, Debug)]
pub enum Current {
Id(usize),
#[default]
diff --git a/jinja-lsp-queries/src/search/test_queries.rs b/jinja-lsp-queries/src/search/test_queries.rs
index 41cd8b5..e0d8178 100644
--- a/jinja-lsp-queries/src/search/test_queries.rs
+++ b/jinja-lsp-queries/src/search/test_queries.rs
@@ -7,8 +7,8 @@ mod query_tests {
search::objects::CompletionType,
search::{
completion_start, definition::definition_query, queries::Queries,
- rust_identifiers::rust_definition_query,
- rust_template_completion::rust_templates_query, templates::templates_query,
+ rust_identifiers::backend_definition_query,
+ rust_template_completion::backend_templates_query, templates::templates_query,
},
};
@@ -34,6 +34,17 @@ mod query_tests {
parser.parse(text, None).expect("not to fail")
}
+ fn prepare_python_tree(text: &str) -> tree_sitter::Tree {
+ let language = tree_sitter_python::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 = [
@@ -136,7 +147,7 @@ mod query_tests {
}
#[test]
- fn rust_macros() {
+ fn rust_definition() {
let case = r#"
let a = context!(name => 11 + abc, abc => "username");
let b = context!{name, username => "username" }
@@ -149,11 +160,29 @@ mod query_tests {
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);
+ let query = &query.backend_definitions;
+ let rust = backend_definition_query(query, &tree, trigger_point, case, true);
assert_eq!(rust.show().len(), 8);
}
+ #[test]
+ fn python_definition() {
+ let case = r#"
+ jinja_env.globals['a'] = 1
+ render_template(data=123)
+ some_obj.render_template(first_name = "John", last_name = "Doe")
+ render(a=11)
+ "#;
+
+ let tree = prepare_python_tree(case);
+ let trigger_point = Point::new(0, 0);
+ let mut query = Queries::default();
+ query.update_backend("python");
+ let query = &query.backend_definitions;
+ let rust = backend_definition_query(query, &tree, trigger_point, case, true);
+ assert_eq!(rust.show().len(), 5);
+ }
+
#[test]
fn find_jinja_completion() {
let source = r#"
@@ -240,8 +269,8 @@ mod query_tests {
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);
+ let query = &query.backend_templates;
+ let templates = backend_templates_query(query, &tree, trigger_point, source, true);
assert_eq!(templates.templates.len(), 3);
}
@@ -256,8 +285,29 @@ mod query_tests {
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);
+ let query = &query.backend_templates;
+ let templates = backend_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 template_completion_in_python() {
+ let source = r#"
+ tmp2 = jinja.get_template("account3");
+ tmp2 = jinja.get_template("account2");
+ tmp = jinja.get_template("account");
+ tmp = jinja.anything("account");
+ "#;
+ let tree = prepare_python_tree(source);
+ let trigger_point = Point::new(3, 47);
+ let mut query = Queries::default();
+ query.update_backend("python");
+ let query = &query.backend_templates;
+ let templates = backend_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-queries/src/tree_builder.rs b/jinja-lsp-queries/src/tree_builder.rs
index 896c830..572128e 100644
--- a/jinja-lsp-queries/src/tree_builder.rs
+++ b/jinja-lsp-queries/src/tree_builder.rs
@@ -6,6 +6,7 @@ pub enum LangType {
Backend,
}
+#[derive(PartialEq, Eq, Debug)]
pub enum JinjaDiagnostic {
DefinedSomewhere,
Undefined,
diff --git a/jinja-lsp/Cargo.toml b/jinja-lsp/Cargo.toml
index a175ab9..f92ae91 100644
--- a/jinja-lsp/Cargo.toml
+++ b/jinja-lsp/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "jinja-lsp"
-version = "0.1.71"
+version = "0.1.80"
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.70"}
+jinja-lsp-queries = { path = "../jinja-lsp-queries", version = "0.1.80"}
diff --git a/jinja-lsp/src/backend.rs b/jinja-lsp/src/backend.rs
index d95e1f4..f8fedf1 100644
--- a/jinja-lsp/src/backend.rs
+++ b/jinja-lsp/src/backend.rs
@@ -6,9 +6,10 @@ use tokio::sync::{
use tower_lsp::{
jsonrpc::Result,
lsp_types::{
- CompletionParams, CompletionResponse, DidChangeConfigurationParams,
- DidCloseTextDocumentParams, DidOpenTextDocumentParams, DocumentSymbolParams,
- DocumentSymbolResponse, InitializeParams, InitializeResult,
+ CompletionParams, CompletionResponse, CreateFile, CreateFileOptions,
+ DidChangeConfigurationParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
+ DocumentChangeOperation, DocumentChanges, DocumentSymbolParams, DocumentSymbolResponse,
+ InitializeParams, InitializeResult, ResourceOp, Url, WorkspaceEdit,
},
Client, LanguageServer,
};
@@ -153,22 +154,49 @@ impl LanguageServer for Backend {
}
}
-pub fn code_actions() -> Vec {
+pub fn code_actions(template: Option<(String, String)>) -> Vec {
let mut commands = vec![];
- for command in [
- ("Reset variables", "reset_variables"),
- ("Warn about unused", "warn"),
- ] {
- commands.push(CodeActionOrCommand::CodeAction(CodeAction {
- title: command.0.to_string(),
- kind: Some(CodeActionKind::EMPTY),
- command: Some(Command::new(
- command.1.to_string(),
- command.1.to_string(),
- None,
- )),
- ..Default::default()
- }));
+ if let Some((templates, template)) = template {
+ if let Ok(path) = std::fs::canonicalize(templates) {
+ let name = format!("file://{}/{template}", path.to_str().unwrap());
+ let cf = CreateFile {
+ uri: Url::parse(&name).unwrap(),
+ options: Some(CreateFileOptions {
+ overwrite: Some(false),
+ ignore_if_exists: Some(true),
+ }),
+ annotation_id: None,
+ };
+
+ commands.push(CodeActionOrCommand::CodeAction(CodeAction {
+ title: "Generate new template".to_string(),
+ kind: Some(CodeActionKind::QUICKFIX),
+ edit: Some(WorkspaceEdit {
+ changes: None,
+ document_changes: Some(DocumentChanges::Operations(vec![
+ DocumentChangeOperation::Op(ResourceOp::Create(cf)),
+ ])),
+ change_annotations: None,
+ }),
+ ..Default::default()
+ }));
+ }
+ } else {
+ for command in [
+ ("Reset variables", "reset_variables"),
+ ("Warn about unused", "warn"),
+ ] {
+ commands.push(CodeActionOrCommand::CodeAction(CodeAction {
+ title: command.0.to_string(),
+ kind: Some(CodeActionKind::EMPTY),
+ command: Some(Command::new(
+ command.1.to_string(),
+ command.1.to_string(),
+ None,
+ )),
+ ..Default::default()
+ }));
+ }
}
commands
}
@@ -182,7 +210,7 @@ impl Backend {
lsp_sender.clone(),
lsp_recv,
);
- diagnostics_task(client.clone(), diagnostic_recv);
+ diagnostics_task(client.clone(), diagnostic_recv, lsp_sender.clone());
Self {
main_channel: lsp_sender,
}
diff --git a/jinja-lsp/src/channels/diagnostics.rs b/jinja-lsp/src/channels/diagnostics.rs
index a9558d8..9b5dc06 100644
--- a/jinja-lsp/src/channels/diagnostics.rs
+++ b/jinja-lsp/src/channels/diagnostics.rs
@@ -1,28 +1,59 @@
use std::collections::HashMap;
-use tokio::sync::mpsc::Receiver;
+use jinja_lsp_queries::{
+ lsp_helper::create_diagnostic, search::Identifier, tree_builder::JinjaDiagnostic,
+};
+use tokio::sync::mpsc::{Receiver, Sender};
use tower_lsp::{
- lsp_types::{Diagnostic, MessageType, Url},
+ lsp_types::{MessageType, Url},
Client,
};
-pub fn diagnostics_task(client: Client, mut receiver: Receiver) {
+use super::lsp::LspMessage;
+
+pub fn diagnostics_task(
+ client: Client,
+ mut receiver: Receiver,
+ lsp_channel: Sender,
+) {
tokio::spawn(async move {
while let Some(msg) = receiver.recv().await {
match msg {
DiagnosticMessage::Str(msg) => client.log_message(MessageType::INFO, msg).await,
DiagnosticMessage::Errors(all_errors) => {
+ let mut code_actions = HashMap::new();
for (uri, errors) in all_errors.into_iter() {
+ let template_errors = errors
+ .iter()
+ .filter(|err| err.0 == JinjaDiagnostic::TemplateNotFound);
+ let mut v = vec![];
+ for err in template_errors {
+ v.push(err.1.to_owned());
+ }
+ code_actions.insert(uri.to_owned(), v);
+ let mut v = vec![];
+ for error in errors {
+ let diagnostic = create_diagnostic(
+ &error.1,
+ error.0.severity(),
+ error.0.to_string(),
+ );
+ v.push(diagnostic);
+ }
let uri = Url::parse(&uri).unwrap();
- client.publish_diagnostics(uri, errors, None).await;
+ client.publish_diagnostics(uri, v, None).await;
}
+ let _ = lsp_channel
+ .send(LspMessage::CodeActions(code_actions))
+ .await;
}
}
}
});
}
+#[derive(Debug)]
pub enum DiagnosticMessage {
- Errors(HashMap>),
+ Errors(HashMap>),
Str(String),
}
diff --git a/jinja-lsp/src/channels/lsp.rs b/jinja-lsp/src/channels/lsp.rs
index 4d06e7f..443e42c 100644
--- a/jinja-lsp/src/channels/lsp.rs
+++ b/jinja-lsp/src/channels/lsp.rs
@@ -1,18 +1,24 @@
-use jinja_lsp_queries::search::{objects::CompletionType, snippets_completion::snippets};
+use jinja_lsp_queries::search::{
+ objects::CompletionType, snippets_completion::snippets, Identifier,
+};
use serde_json::Value;
-use tokio::sync::{mpsc, oneshot};
+use std::{collections::HashMap, time::Duration};
+use tokio::{
+ sync::{mpsc, oneshot},
+ time::sleep,
+};
use tower_lsp::{
lsp_types::{
- CodeActionParams, CodeActionProviderCapability, CodeActionResponse, CompletionItem,
- CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse,
- CompletionTextEdit, DidChangeConfigurationParams, DidChangeTextDocumentParams,
- DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentSymbolParams,
- DocumentSymbolResponse, Documentation, ExecuteCommandOptions, ExecuteCommandParams,
- GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, HoverParams,
- HoverProviderCapability, InitializeParams, InitializeResult, InsertReplaceEdit,
- MarkupContent, MarkupKind, MessageType, OneOf, ServerCapabilities, ServerInfo,
- TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
- TextDocumentSyncSaveOptions, TextEdit,
+ CodeActionKind, CodeActionOptions, CodeActionParams, CodeActionProviderCapability,
+ CodeActionResponse, CompletionItem, CompletionItemKind, CompletionOptions,
+ CompletionParams, CompletionResponse, CompletionTextEdit, DidChangeConfigurationParams,
+ DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams,
+ DocumentSymbolParams, DocumentSymbolResponse, Documentation, ExecuteCommandOptions,
+ ExecuteCommandParams, GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents,
+ HoverParams, HoverProviderCapability, InitializeParams, InitializeResult,
+ InsertReplaceEdit, MarkupContent, MarkupKind, MessageType, OneOf, ServerCapabilities,
+ ServerInfo, TextDocumentIdentifier, TextDocumentSyncCapability, TextDocumentSyncKind,
+ TextDocumentSyncOptions, TextDocumentSyncSaveOptions, TextEdit, WorkDoneProgressOptions,
},
Client,
};
@@ -21,7 +27,7 @@ use crate::{
backend::code_actions,
config::{walkdir, JinjaConfig},
filter::init_filter_completions,
- lsp_files2::LspFiles,
+ lsp_files2::{JinjaCodeAction, LspFiles},
};
use super::diagnostics::DiagnosticMessage;
@@ -97,7 +103,7 @@ pub fn lsp_task(
},
server_info: Some(ServerInfo {
name: String::from("jinja-lsp"),
- version: Some(String::from("0.1.62")),
+ version: Some(String::from("0.1.80")),
}),
offset_encoding: None,
};
@@ -115,7 +121,7 @@ pub fn lsp_task(
.log_message(MessageType::WARNING, "Template directory not found")
.await;
}
- if config.lang != "rust" {
+ if !["rust", "python"].contains(&config.lang.as_str()) {
client
.log_message(MessageType::WARNING, "Backend language not supported")
.await;
@@ -262,9 +268,27 @@ pub fn lsp_task(
}
}
LspMessage::CodeAction(params, sender) => {
- if let Some(is_code_action) = lsp_data.code_action(params) {
- if is_code_action {
- let _ = sender.send(Some(code_actions()));
+ let param = DidSaveTextDocumentParams {
+ text_document: TextDocumentIdentifier::new(
+ params.text_document.uri.to_owned(),
+ ),
+ text: None,
+ };
+ if let Some(code_action) = lsp_data.code_action2(params) {
+ match code_action {
+ JinjaCodeAction::Reset => {
+ let _ = sender.send(Some(code_actions(None)));
+ }
+ JinjaCodeAction::CreateTemplate(template) => {
+ let templates = lsp_data.config.templates.to_owned();
+ let _ =
+ sender.send(Some(code_actions(Some((templates, template)))));
+ let lsp_channel = lsp_channel.clone();
+ tokio::spawn(async move {
+ sleep(Duration::from_millis(1400)).await;
+ let _ = lsp_channel.send(LspMessage::DidSave(param)).await;
+ });
+ }
}
}
}
@@ -298,6 +322,9 @@ pub fn lsp_task(
}
let _ = lsp_channel.send(LspMessage::Initialized(sender)).await;
}
+ LspMessage::CodeActions(code_actions) => {
+ lsp_data.add_code_actions(code_actions);
+ }
}
}
});
@@ -328,4 +355,5 @@ pub enum LspMessage {
oneshot::Sender