diff --git a/.github/workflows/CI-nodejs.yml b/.github/workflows/CI-nodejs.yml index fc9160c..1f406ec 100644 --- a/.github/workflows/CI-nodejs.yml +++ b/.github/workflows/CI-nodejs.yml @@ -9,7 +9,7 @@ permissions: 'on': push: branches: - - npm + - nodejs tags-ignore: - '**' paths-ignore: @@ -190,6 +190,7 @@ jobs: # name: bindings-freebsd # path: jinja-lsp-nodejs/${{ env.APP_NAME }}.*.node # if-no-files-found: error + test-macOS-windows-binding: defaults: run: diff --git a/Cargo.lock b/Cargo.lock index d2b1df4..8ea09ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -320,6 +320,20 @@ dependencies = [ "walkdir", ] +[[package]] +name = "jinja-lsp-nodejs" +version = "0.1.80" +dependencies = [ + "jinja-lsp", + "jinja-lsp-queries", + "napi", + "napi-build", + "napi-derive", + "ropey", + "tower-lsp", + "tree-sitter", +] + [[package]] name = "jinja-lsp-queries" version = "0.1.80" @@ -976,15 +990,6 @@ dependencies = [ "serde", ] -[[package]] -name = "uros_jinja-lsp-nodejs" -version = "0.0.0" -dependencies = [ - "napi", - "napi-build", - "napi-derive", -] - [[package]] name = "walkdir" version = "2.5.0" diff --git a/act_script.sh b/act_script.sh new file mode 100644 index 0000000..6f957c3 --- /dev/null +++ b/act_script.sh @@ -0,0 +1,3 @@ + +# act -j show -P macos-latest=sickcodes/docker-osx -P windows-latest=dockurr/windows +act --env-file .env -W .github/workflows/CI-nodejs.yaml -P macos-latest=sickcodes/docker-osx -P windows-latest=dockurr/windows diff --git a/jinja-lsp-nodejs/Cargo.toml b/jinja-lsp-nodejs/Cargo.toml index 56c7ba5..80505ee 100644 --- a/jinja-lsp-nodejs/Cargo.toml +++ b/jinja-lsp-nodejs/Cargo.toml @@ -1,7 +1,10 @@ [package] edition = "2021" -name = "uros_jinja-lsp-nodejs" -version = "0.0.0" +name = "jinja-lsp-nodejs" +version = "0.1.80" +license = "MIT" +authors = ["uros-5"] +description = "Bindings for jinja-lsp" [lib] crate-type = ["cdylib"] @@ -10,6 +13,11 @@ crate-type = ["cdylib"] # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix napi = { version = "2.12.2", default-features = false, features = ["napi4"] } napi-derive = "2.12.2" +jinja-lsp-queries = { path = "../jinja-lsp-queries", version = "0.1.80"} +jinja-lsp = { path = "../jinja-lsp", version = "0.1.80"} +tree-sitter = "0.21.0" +tower-lsp = { version = "0.20.0", features = ["proposed"] } +ropey = "1.5.0" [build-dependencies] napi-build = "2.0.1" @@ -17,3 +25,4 @@ napi-build = "2.0.1" [profile.release] lto = true strip = "symbols" +opt-level = 3 diff --git a/jinja-lsp-nodejs/__test__/index.spec.mjs b/jinja-lsp-nodejs/__test__/index.spec.mjs index 1ade4ca..ebbb3b5 100644 --- a/jinja-lsp-nodejs/__test__/index.spec.mjs +++ b/jinja-lsp-nodejs/__test__/index.spec.mjs @@ -1,7 +1,7 @@ import test from 'ava' -import { sum } from '../index.js' +import { NodejsLspFiles } from '../index.js' -test('sum from native', (t) => { - t.is(sum(1, 2), 3) +test('main class', (t) => { + t.is(new NodejsLspFiles().getVariables("id", 11), null); }) diff --git a/jinja-lsp-nodejs/index.d.ts b/jinja-lsp-nodejs/index.d.ts index 6d85d51..5f3b749 100644 --- a/jinja-lsp-nodejs/index.d.ts +++ b/jinja-lsp-nodejs/index.d.ts @@ -3,4 +3,84 @@ /* auto-generated by NAPI-RS */ -export function sum(a: number, b: number): number +export function basic(content: string): number | null +export interface JsPosition { + line: number + character: number +} +export const enum JsIdentifierType { + ForLoopKey = 0, + ForLoopValue = 1, + ForLoopCount = 2, + SetVariable = 3, + WithVariable = 4, + MacroName = 5, + MacroParameter = 6, + TemplateBlock = 7, + BackendVariable = 8, + UndefinedVariable = 9, + JinjaTemplate = 10, + Link = 11 +} +export interface JsIdentifier { + start: JsPosition + end: JsPosition + name: string + identifierType: JsIdentifierType + error?: string +} +export interface JsHover { + kind: string + value: string + range?: JsRange + label?: string + documentaion?: string +} +export interface JsRange { + start: JsPosition + end: JsPosition +} +export interface JsLocation { + uri: string + range: JsRange + isBackend: boolean + name: string +} +export interface JsCompletionItem { + completionType: JsCompletionType + label: string + kind: Kind2 + description: string + newText?: string + insert?: JsRange + replace?: JsRange +} +export const enum Kind2 { + VARIABLE = 0, + FIELD = 1, + FUNCTION = 2, + MODULE = 3, + CONSTANT = 4, + FILE = 5, + TEXT = 6 +} +export const enum JsCompletionType { + Filter = 0, + Identifier = 1, + Snippets = 2 +} +export interface Action { + name: string + description: string +} +export class NodejsLspFiles { + constructor() + /** Actions can come from unsaved context. */ + addGlobalContext(uri: string, actions?: Array | undefined | null): void + deleteAll(filename: string): void + addOne(id: number, filename: string, content: string, line: number): Array + getVariables(id: string, line: number): Array | null + hover(id: number, filename: string, line: number, position: JsPosition): JsHover | null + complete(id: number, filename: string, line: number, position: JsPosition): Array | null + gotoDefinition(id: number, filename: string, line: number, position: JsPosition): Array | null +} diff --git a/jinja-lsp-nodejs/index.js b/jinja-lsp-nodejs/index.js index a7ab326..950e460 100644 --- a/jinja-lsp-nodejs/index.js +++ b/jinja-lsp-nodejs/index.js @@ -310,6 +310,10 @@ if (!nativeBinding) { throw new Error(`Failed to load native binding`) } -const { sum } = nativeBinding +const { basic, NodejsLspFiles, JsIdentifierType, Kind2, JsCompletionType } = nativeBinding -module.exports.sum = sum +module.exports.basic = basic +module.exports.NodejsLspFiles = NodejsLspFiles +module.exports.JsIdentifierType = JsIdentifierType +module.exports.Kind2 = Kind2 +module.exports.JsCompletionType = JsCompletionType diff --git a/jinja-lsp-nodejs/npm/android-arm-eabi/package.json b/jinja-lsp-nodejs/npm/android-arm-eabi/package.json index 1ce0c0c..6d31680 100644 --- a/jinja-lsp-nodejs/npm/android-arm-eabi/package.json +++ b/jinja-lsp-nodejs/npm/android-arm-eabi/package.json @@ -1,6 +1,6 @@ { "name": "@jinja-lsp/functions-android-arm-eabi", - "version": "0.0.2", + "version": "0.0.3", "os": [ "android" ], diff --git a/jinja-lsp-nodejs/npm/android-arm64/package.json b/jinja-lsp-nodejs/npm/android-arm64/package.json index 5962099..b484a5b 100644 --- a/jinja-lsp-nodejs/npm/android-arm64/package.json +++ b/jinja-lsp-nodejs/npm/android-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@jinja-lsp/functions-android-arm64", - "version": "0.0.2", + "version": "0.0.3", "os": [ "android" ], diff --git a/jinja-lsp-nodejs/npm/darwin-arm64/package.json b/jinja-lsp-nodejs/npm/darwin-arm64/package.json index d7a8c71..b32248e 100644 --- a/jinja-lsp-nodejs/npm/darwin-arm64/package.json +++ b/jinja-lsp-nodejs/npm/darwin-arm64/package.json @@ -1,6 +1,6 @@ { "name": "@jinja-lsp/functions-darwin-arm64", - "version": "0.0.2", + "version": "0.0.3", "os": [ "darwin" ], diff --git a/jinja-lsp-nodejs/npm/darwin-universal/package.json b/jinja-lsp-nodejs/npm/darwin-universal/package.json index 936b426..a3736ae 100644 --- a/jinja-lsp-nodejs/npm/darwin-universal/package.json +++ b/jinja-lsp-nodejs/npm/darwin-universal/package.json @@ -1,6 +1,6 @@ { "name": "@jinja-lsp/functions-darwin-universal", - "version": "0.0.2", + "version": "0.0.3", "os": [ "darwin" ], diff --git a/jinja-lsp-nodejs/npm/freebsd-x64/package.json b/jinja-lsp-nodejs/npm/freebsd-x64/package.json index f3a8ca4..c383f80 100644 --- a/jinja-lsp-nodejs/npm/freebsd-x64/package.json +++ b/jinja-lsp-nodejs/npm/freebsd-x64/package.json @@ -1,6 +1,6 @@ { "name": "@jinja-lsp/functions-freebsd-x64", - "version": "0.0.2", + "version": "0.0.3", "os": [ "freebsd" ], diff --git a/jinja-lsp-nodejs/npm/gnu-linux/package.json b/jinja-lsp-nodejs/npm/gnu-linux/package.json index 86eef40..087d805 100644 --- a/jinja-lsp-nodejs/npm/gnu-linux/package.json +++ b/jinja-lsp-nodejs/npm/gnu-linux/package.json @@ -1,6 +1,6 @@ { "name": "@jinja-lsp/functions-gnu-linux", - "version": "0.0.2", + "version": "0.0.3", "os": [ "gnu", "linux" diff --git a/jinja-lsp-nodejs/npm/linux-arm-gnueabihf/package.json b/jinja-lsp-nodejs/npm/linux-arm-gnueabihf/package.json index 42bd33a..6f05124 100644 --- a/jinja-lsp-nodejs/npm/linux-arm-gnueabihf/package.json +++ b/jinja-lsp-nodejs/npm/linux-arm-gnueabihf/package.json @@ -1,6 +1,6 @@ { "name": "@jinja-lsp/functions-linux-arm-gnueabihf", - "version": "0.0.2", + "version": "0.0.3", "os": [ "linux" ], diff --git a/jinja-lsp-nodejs/npm/linux-arm-musleabihf/package.json b/jinja-lsp-nodejs/npm/linux-arm-musleabihf/package.json index 336b84a..2fe366b 100644 --- a/jinja-lsp-nodejs/npm/linux-arm-musleabihf/package.json +++ b/jinja-lsp-nodejs/npm/linux-arm-musleabihf/package.json @@ -1,6 +1,6 @@ { "name": "@jinja-lsp/functions-linux-arm-musleabihf", - "version": "0.0.2", + "version": "0.0.3", "os": [ "linux" ], diff --git a/jinja-lsp-nodejs/npm/linux-arm64-gnu/package.json b/jinja-lsp-nodejs/npm/linux-arm64-gnu/package.json index a888469..a7bd6dc 100644 --- a/jinja-lsp-nodejs/npm/linux-arm64-gnu/package.json +++ b/jinja-lsp-nodejs/npm/linux-arm64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@jinja-lsp/functions-linux-arm64-gnu", - "version": "0.0.2", + "version": "0.0.3", "os": [ "linux" ], diff --git a/jinja-lsp-nodejs/npm/linux-arm64-musl/package.json b/jinja-lsp-nodejs/npm/linux-arm64-musl/package.json index 92501b4..4a122dd 100644 --- a/jinja-lsp-nodejs/npm/linux-arm64-musl/package.json +++ b/jinja-lsp-nodejs/npm/linux-arm64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@jinja-lsp/functions-linux-arm64-musl", - "version": "0.0.2", + "version": "0.0.3", "os": [ "linux" ], diff --git a/jinja-lsp-nodejs/npm/linux-riscv64-gnu/package.json b/jinja-lsp-nodejs/npm/linux-riscv64-gnu/package.json index b2e2c48..3875327 100644 --- a/jinja-lsp-nodejs/npm/linux-riscv64-gnu/package.json +++ b/jinja-lsp-nodejs/npm/linux-riscv64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@jinja-lsp/functions-linux-riscv64-gnu", - "version": "0.0.2", + "version": "0.0.3", "os": [ "linux" ], diff --git a/jinja-lsp-nodejs/npm/linux-x64-gnu/package.json b/jinja-lsp-nodejs/npm/linux-x64-gnu/package.json index d09db88..7688952 100644 --- a/jinja-lsp-nodejs/npm/linux-x64-gnu/package.json +++ b/jinja-lsp-nodejs/npm/linux-x64-gnu/package.json @@ -1,6 +1,6 @@ { "name": "@jinja-lsp/functions-linux-x64-gnu", - "version": "0.0.2", + "version": "0.0.3", "os": [ "linux" ], diff --git a/jinja-lsp-nodejs/npm/linux-x64-musl/package.json b/jinja-lsp-nodejs/npm/linux-x64-musl/package.json index 218814a..1442d6f 100644 --- a/jinja-lsp-nodejs/npm/linux-x64-musl/package.json +++ b/jinja-lsp-nodejs/npm/linux-x64-musl/package.json @@ -1,6 +1,6 @@ { "name": "@jinja-lsp/functions-linux-x64-musl", - "version": "0.0.2", + "version": "0.0.3", "os": [ "linux" ], diff --git a/jinja-lsp-nodejs/npm/win32-arm64-msvc/package.json b/jinja-lsp-nodejs/npm/win32-arm64-msvc/package.json index 85ddd25..2604054 100644 --- a/jinja-lsp-nodejs/npm/win32-arm64-msvc/package.json +++ b/jinja-lsp-nodejs/npm/win32-arm64-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@jinja-lsp/functions-win32-arm64-msvc", - "version": "0.0.2", + "version": "0.0.3", "os": [ "win32" ], diff --git a/jinja-lsp-nodejs/npm/win32-ia32-msvc/package.json b/jinja-lsp-nodejs/npm/win32-ia32-msvc/package.json index 6c58a8c..dec91ea 100644 --- a/jinja-lsp-nodejs/npm/win32-ia32-msvc/package.json +++ b/jinja-lsp-nodejs/npm/win32-ia32-msvc/package.json @@ -1,6 +1,6 @@ { "name": "@jinja-lsp/functions-win32-ia32-msvc", - "version": "0.0.2", + "version": "0.0.3", "os": [ "win32" ], diff --git a/jinja-lsp-nodejs/package-lock.json b/jinja-lsp-nodejs/package-lock.json index c850fd0..08efa31 100644 --- a/jinja-lsp-nodejs/package-lock.json +++ b/jinja-lsp-nodejs/package-lock.json @@ -1,12 +1,12 @@ { "name": "@jinja-lsp/functions", - "version": "0.0.2", + "version": "0.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@jinja-lsp/functions", - "version": "0.0.2", + "version": "0.0.3", "license": "MIT", "devDependencies": { "@napi-rs/cli": "^2.18.3", diff --git a/jinja-lsp-nodejs/package.json b/jinja-lsp-nodejs/package.json index 0b2f60f..ec634b9 100644 --- a/jinja-lsp-nodejs/package.json +++ b/jinja-lsp-nodejs/package.json @@ -1,6 +1,6 @@ { "name": "@jinja-lsp/functions", - "version": "0.0.2", + "version": "0.0.3", "main": "index.js", "types": "index.d.ts", "napi": { diff --git a/jinja-lsp-nodejs/src/lib.rs b/jinja-lsp-nodejs/src/lib.rs index b479ed7..99b64c3 100644 --- a/jinja-lsp-nodejs/src/lib.rs +++ b/jinja-lsp-nodejs/src/lib.rs @@ -1,9 +1,638 @@ #![deny(clippy::all)] +use std::collections::HashMap; + +use jinja_lsp::{ + filter::{init_filter_completions, FilterCompletion}, + lsp_files::LspFiles, +}; +use jinja_lsp_queries::{ + parsers::Parsers, + search::{ + objects::{objects_query, CompletionType, JinjaObject}, + queries::Queries, + snippets_completion::snippets, + Identifier, IdentifierType, + }, + to_input_edit::to_position2, +}; + +use tower_lsp::lsp_types::{ + CompletionContext, CompletionItem, CompletionItemKind, CompletionParams, CompletionTriggerKind, + DidOpenTextDocumentParams, GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverContents, + HoverParams, Location, MarkupContent, MarkupKind, PartialResultParams, Position, Range, + TextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, Url, + WorkDoneProgressParams, +}; +use tree_sitter::Point; + #[macro_use] extern crate napi_derive; #[napi] -pub fn sum(a: i32, b: i32) -> i32 { - a + b +pub fn basic(content: String) -> Option { + let queries = Queries::default(); + let mut parsers = Parsers::default(); + let tree = parsers.parse( + jinja_lsp_queries::tree_builder::LangType::Template, + &content, + None, + )?; + let query = &queries.jinja_objects; + let objects = objects_query(query, &tree, Point::new(0, 0), &content, true); + let count = objects.show(); + Some(count.len() as i32) +} + +#[napi] +#[derive(Default)] +pub struct NodejsLspFiles { + lsp_files: LspFiles, + counter: u32, + filters: Vec, + _snippets: Vec, + actions: HashMap>, + action_objects: HashMap>, +} + +#[napi] +impl NodejsLspFiles { + #[napi(constructor)] + pub fn new() -> Self { + Self { + lsp_files: LspFiles::default(), + counter: 0, + filters: init_filter_completions(), + _snippets: snippets(), + actions: HashMap::new(), + action_objects: HashMap::new(), + } + } + + /// Actions can come from unsaved context. + #[napi] + pub fn add_global_context(&mut self, uri: String, actions: Option>) { + if let Some(actions) = actions { + let mut identifiers = vec![]; + let mut action_objects = vec![]; + for action in &actions { + let mut identifier = Identifier::new(&action.name, Point::new(0, 0), Point::new(0, 0)); + identifier.identifier_type = IdentifierType::BackendVariable; + identifiers.push(identifier); + let action = ActionObject::from(action); + action_objects.push(action); + } + self.actions.insert(uri.to_string(), actions); + self.action_objects.insert(uri.to_string(), action_objects); + self.lsp_files.variables.insert(uri, identifiers); + } + } + + #[napi] + pub fn delete_all(&mut self, filename: String) { + self.lsp_files.delete_documents_with_id(filename); + self.counter = 0; + // self.lsp_files.main_channel + } + + #[napi] + pub fn add_one( + &mut self, + id: u32, + filename: String, + content: String, + line: u32, + ) -> Vec { + let mut all_identifiers = vec![]; + let params: DidOpenTextDocumentParams = DidOpenTextDocumentParams { + text_document: TextDocumentItem::new( + Url::parse(&format!("file:///home/{filename}.{id}.jinja")).unwrap(), + String::new(), + 0, + content, + ), + }; + self.lsp_files.did_open(params); + let objects = self + .lsp_files + .read_objects(Url::parse(&format!("file:///home/{filename}.{id}.jinja")).unwrap()); + if let Some(objects) = objects { + if let Some(global_actions) = self.action_objects.get(&filename.to_string()) { + for obj in &objects { + let action_object = ActionObject::from(obj); + for global_action in global_actions { + if global_action.compare(&action_object.fields) { + let mut start = JsPosition::from(obj.location.0); + let mut end = JsPosition::from(obj.last_field_end()); + start.line += line; + end.line += line; + let identifier = JsIdentifier { + start, + end, + name: obj.name.to_owned(), + identifier_type: JsIdentifierType::Link, + error: None, + }; + all_identifiers.push(identifier); + } + } + } + } + } + // let query = &self.lsp_files.queries.jinja_objects; + // let objects = objects_query(query, &tree, Point::new(0, 0), &content, true); + // let objects = objects.show(); + // if let Some(content) = content { + // match content { + // DiagnosticMessage::Errors(errors) => { + // for i in errors { + // for error in i.1 { + // let diagnostic = error.0.to_string(); + // let mut position = error.1; + // position.start.row += line as usize; + // position.end.row += line as usize; + // let mut identifier = JsIdentifier::from(&position); + // identifier.error = Some(diagnostic); + // all_errors.push(identifier); + // } + // } + // } + // DiagnosticMessage::Str(_) => {} + // } + // } + all_identifiers + } + + #[napi] + pub fn get_variables(&self, id: String, line: u32) -> Option> { + let variables = self.lsp_files.variables.get(&id)?; + let mut converted = vec![]; + for variable in variables { + let mut variable2 = JsIdentifier::from(variable); + variable2.start.line += line; + variable2.end.line += line; + converted.push(variable2); + } + Some(converted) + } + + #[napi] + pub fn hover( + &self, + id: u32, + filename: String, + line: u32, + mut position: JsPosition, + ) -> Option { + position.line -= line; + let uri = Url::parse(&format!("file:///home/{filename}.{id}.jinja")).unwrap(); + let params: HoverParams = HoverParams { + text_document_position_params: TextDocumentPositionParams::new( + TextDocumentIdentifier::new(uri.clone()), + Position::new(position.line, position.character), + ), + work_done_progress_params: WorkDoneProgressParams { + work_done_token: None, + }, + }; + let hover = self.lsp_files.hover(params)?; + let mut res = None; + let mut range = Range { + start: to_position2(hover.0.start), + end: to_position2(hover.0.end), + }; + range.start.line += line; + range.end.line += line; + let range = Some(range); + let full_name = hover.0.name.to_owned(); + if hover.1 { + let filter = self + .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, + }; + res = Some(hover); + } + } else if let Some(data_type) = self.lsp_files.data_type(uri.clone(), hover.0) { + let value = data_type.completion_detail().to_owned(); + let value = format!("{value}\n\n---\n**{}**", &full_name); + let actions = vec![]; + let actions = self.actions.get(&filename).unwrap_or(&actions); + let action = Action { + name: full_name.to_owned(), + description: value.to_owned(), + }; + let value = actions + .iter() + .find(|item| item.name.starts_with(&full_name)) + .unwrap_or(&action); + let markup_content = MarkupContent { + kind: MarkupKind::Markdown, + value: value.description.to_owned(), + }; + let hover_contents = HoverContents::Markup(markup_content); + let hover = Hover { + contents: hover_contents, + range, + }; + res = Some(hover); + } + if let Some(res) = res { + if let HoverContents::Markup(hover_contents) = res.contents { + if let Some(range) = res.range { + return Some(JsHover { + kind: "markdown".to_owned(), + value: hover_contents.value, + range: Some(JsRange::from(&range)), + label: None, + documentaion: None, + }); + } + } + } + None + } + + #[napi] + pub fn complete( + &self, + id: u32, + filename: String, + line: u32, + mut position: JsPosition, + ) -> Option> { + position.line -= line; + let uri = Url::parse(&format!("file:///home/{filename}.{id}.jinja")).unwrap(); + let position = Position::new(position.line, position.character); + let params: CompletionParams = CompletionParams { + text_document_position: TextDocumentPositionParams::new( + TextDocumentIdentifier::new(uri.clone()), + Position::new(position.line, position.character), + ), + work_done_progress_params: WorkDoneProgressParams { + work_done_token: None, + }, + partial_result_params: PartialResultParams { + ..Default::default() + }, + context: Some(CompletionContext { + trigger_kind: CompletionTriggerKind::TRIGGER_CHARACTER, + trigger_character: None, + }), + }; + let completion = self.lsp_files.completion(params)?; + let mut items = None; + + match completion { + CompletionType::Filter => { + let completions = self.filters.clone(); + let mut ret = Vec::with_capacity(completions.len()); + for item in completions.into_iter() { + ret.push(JsCompletionItem { + completion_type: JsCompletionType::Filter, + label: item.name, + kind: Kind2::FIELD, + description: item.desc.to_string(), + new_text: None, + insert: None, + replace: None, + }); + } + items = Some(ret); + } + CompletionType::Identifier => { + if let Some(variables) = self.lsp_files.read_variables(&uri, position) { + let mut ret = vec![]; + for item in variables { + ret.push(JsCompletionItem { + completion_type: JsCompletionType::Identifier, + label: item.label, + kind: Kind2::VARIABLE, + description: item.detail.unwrap_or(String::new()), + new_text: None, + insert: None, + replace: None, + }); + } + items = Some(ret); + } + } + CompletionType::IncludedTemplate { .. } => {} + CompletionType::Snippets { .. } => { + // let mut filtered = vec![]; + // for snippet in self.snippets.iter() { + // let mut snippet = snippet.clone(); + // if let Some(CompletionTextEdit::Edit(TextEdit { new_text, .. })) = snippet.text_edit { + // if !self.lsp_files.is_vscode { + // snippet.text_edit = Some(CompletionTextEdit::InsertAndReplace(InsertReplaceEdit { + // new_text, + // insert: range, + // replace: range, + // })); + // } else { + // snippet.text_edit = None; + // } + // } + // filtered.push(snippet); + // } + // } + + // if !filtered.is_empty() { + // items = Some(CompletionResponse::Array(filtered)); + // } + } + CompletionType::IncompleteIdentifier { name, mut range } => { + range.start.line += line; + range.end.line += line; + let variable = self.lsp_files.get_variable(name, uri.to_string())?; + let ret = vec![JsCompletionItem { + completion_type: JsCompletionType::Identifier, + label: variable.to_string(), + kind: Kind2::VARIABLE, + description: variable.to_string(), + new_text: Some(variable), + insert: Some(JsRange::from(&range)), + replace: Some(JsRange::from(&range)), + }]; + items = Some(ret); + } + }; + items + } + + #[napi] + pub fn goto_definition( + &self, + id: u32, + filename: String, + line: u32, + mut position: JsPosition, + ) -> Option> { + position.line -= line; + let uri = Url::parse(&format!("file:///home/{filename}.{id}.jinja")).unwrap(); + let params: GotoDefinitionParams = GotoDefinitionParams { + text_document_position_params: TextDocumentPositionParams::new( + TextDocumentIdentifier::new(uri.clone()), + Position::new(position.line, position.character), + ), + work_done_progress_params: WorkDoneProgressParams { + work_done_token: None, + }, + partial_result_params: PartialResultParams { + ..Default::default() + }, + }; + let defintion = self.lsp_files.goto_definition(params)?; + let mut definitions = vec![]; + match defintion { + GotoDefinitionResponse::Scalar(mut location) => { + let uri2 = location.uri.to_string(); + if uri2.contains(&filename) { + location.uri = Url::parse(&filename).unwrap(); + location.range.start.line += line; + location.range.end.line += line; + definitions.push(JsLocation::from(&location)); + } + } + GotoDefinitionResponse::Array(locations) => { + for mut location in locations { + print!("{}", location.uri); + let uri2 = location.uri.to_string(); + if uri2.contains(&filename) { + location.uri = Url::parse(&uri2).unwrap(); + location.range.start.line += line; + location.range.end.line += line; + let mut js_location = JsLocation::from(&location); + js_location.is_backend = true; + definitions.push(js_location); + } + } + } + _ => (), + } + Some(definitions) + } +} + +#[napi(object)] +#[derive(Default, Debug, Clone, PartialEq, PartialOrd, Ord, Eq)] +pub struct JsPosition { + pub line: u32, + pub character: u32, +} + +impl From for JsPosition { + fn from(value: Point) -> Self { + Self { + line: value.row as u32, + character: value.column as u32, + } + } +} + +impl From<&Position> for JsPosition { + fn from(value: &Position) -> Self { + Self { + line: value.line, + character: value.character, + } + } +} + +#[napi] +#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub enum JsIdentifierType { + ForLoopKey, + ForLoopValue, + ForLoopCount, + SetVariable, + WithVariable, + MacroName, + MacroParameter, + TemplateBlock, + BackendVariable, + #[default] + UndefinedVariable, + JinjaTemplate, + Link, +} + +#[napi(object)] +#[derive(Default, Debug, Clone, PartialEq, PartialOrd, Ord, Eq)] +pub struct JsIdentifier { + pub start: JsPosition, + pub end: JsPosition, + pub name: String, + pub identifier_type: JsIdentifierType, + pub error: Option, +} + +impl From<&Identifier> for JsIdentifier { + fn from(value: &Identifier) -> Self { + Self { + start: JsPosition::from(value.start), + end: JsPosition::from(value.end), + name: value.name.to_string(), + identifier_type: JsIdentifierType::from(&value.identifier_type), + error: None, + } + } +} + +impl From<&IdentifierType> for JsIdentifierType { + fn from(value: &IdentifierType) -> Self { + match value { + IdentifierType::ForLoopKey => JsIdentifierType::ForLoopKey, + IdentifierType::ForLoopValue => JsIdentifierType::ForLoopValue, + IdentifierType::ForLoopCount => JsIdentifierType::ForLoopCount, + IdentifierType::SetVariable => JsIdentifierType::SetVariable, + IdentifierType::WithVariable => JsIdentifierType::WithVariable, + IdentifierType::MacroName => JsIdentifierType::MacroName, + IdentifierType::MacroParameter => JsIdentifierType::MacroParameter, + IdentifierType::TemplateBlock => JsIdentifierType::TemplateBlock, + IdentifierType::BackendVariable => JsIdentifierType::BackendVariable, + IdentifierType::UndefinedVariable => JsIdentifierType::UndefinedVariable, + IdentifierType::JinjaTemplate => JsIdentifierType::JinjaTemplate, + } + } +} + +impl From<&Range> for JsRange { + fn from(value: &Range) -> Self { + Self { + start: JsPosition::from(&value.start), + end: JsPosition::from(&value.end), + } + } +} + +#[napi(object)] +pub struct JsHover { + pub kind: String, + pub value: String, + pub range: Option, + pub label: Option, + pub documentaion: Option, +} + +#[napi(object)] +pub struct JsRange { + pub start: JsPosition, + pub end: JsPosition, +} + +#[napi(object)] +pub struct JsLocation { + pub uri: String, + pub range: JsRange, + pub is_backend: bool, + pub name: String, +} + +impl From<&Location> for JsLocation { + fn from(value: &Location) -> Self { + Self { + uri: value.uri.to_string(), + range: JsRange::from(&value.range), + is_backend: false, + name: String::new(), + } + } +} + +#[napi(object)] +pub struct JsCompletionItem { + pub completion_type: JsCompletionType, + pub label: String, + pub kind: Kind2, + pub description: String, + pub new_text: Option, + pub insert: Option, + pub replace: Option, +} + +#[napi] +pub enum Kind2 { + VARIABLE, + FIELD, + FUNCTION, + MODULE, + CONSTANT, + FILE, + TEXT, +} + +#[napi] +pub enum JsCompletionType { + Filter, + Identifier, + Snippets, +} + +impl From for Kind2 { + fn from(value: CompletionItemKind) -> Self { + if value == CompletionItemKind::VARIABLE { + Kind2::VARIABLE + } else if value == CompletionItemKind::FIELD { + return Kind2::FIELD; + } else if value == CompletionItemKind::FUNCTION { + return Kind2::FUNCTION; + } else if value == CompletionItemKind::MODULE { + return Kind2::MODULE; + } else if value == CompletionItemKind::CONSTANT { + return Kind2::CONSTANT; + } else if value == CompletionItemKind::FILE { + return Kind2::FILE; + } else { + return Kind2::VARIABLE; + } + } +} + +#[napi(object)] +pub struct Action { + pub name: String, + pub description: String, +} + +pub struct ActionObject { + pub fields: Vec, +} + +impl From<&Action> for ActionObject { + fn from(value: &Action) -> Self { + let parts = value.name.split('.'); + let mut fields = vec![]; + for part in parts { + fields.push(part.to_owned()); + } + Self { fields } + } +} + +impl From<&JinjaObject> for ActionObject { + fn from(value: &JinjaObject) -> Self { + let mut fields = vec![]; + fields.push(value.name.to_owned()); + for field in &value.fields { + fields.push(field.0.to_owned()); + } + Self { fields } + } +} + +impl ActionObject { + pub fn compare(&self, fields: &Vec) -> bool { + &self.fields == fields + // self.fields == fields + } } diff --git a/jinja-lsp-queries/src/lsp_helper.rs b/jinja-lsp-queries/src/lsp_helper.rs index 2a23c6b..5908037 100644 --- a/jinja-lsp-queries/src/lsp_helper.rs +++ b/jinja-lsp-queries/src/lsp_helper.rs @@ -11,6 +11,7 @@ use crate::{ tree_builder::{JinjaDiagnostic, LangType}, }; +#[allow(clippy::too_many_arguments)] pub fn search_errors( root: &Tree, source: &str, @@ -19,6 +20,7 @@ pub fn search_errors( file_name: &String, templates: PathBuf, lang_type: LangType, + ignore_globals: bool, ) -> Option> { let mut diagnostics = vec![]; match lang_type { @@ -57,15 +59,17 @@ pub fn search_errors( to_warn = true; } 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 !ignore_globals { + 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; + } } } } diff --git a/jinja-lsp-queries/src/search/mod.rs b/jinja-lsp-queries/src/search/mod.rs index 47c2893..2137d7e 100644 --- a/jinja-lsp-queries/src/search/mod.rs +++ b/jinja-lsp-queries/src/search/mod.rs @@ -5,6 +5,7 @@ use self::objects::JinjaObject; pub mod definition; pub mod objects; +mod python_identifiers; pub mod queries; pub mod rust_identifiers; pub mod rust_template_completion; @@ -19,6 +20,7 @@ pub struct Identifier { pub name: String, pub scope_ends: (usize, Point), pub identifier_type: IdentifierType, + pub fields: Vec<(String, (Point, Point))>, } impl Identifier { @@ -29,13 +31,25 @@ impl Identifier { end, scope_ends: (0, Point::default()), identifier_type: IdentifierType::UndefinedVariable, + fields: Vec::new(), } } + + pub fn merge(&self) -> String { + let mut merged = self.name.to_string(); + for field in &self.fields { + merged.push('.'); + merged.push_str(&field.0); + } + merged + } } impl From<&JinjaObject> for Identifier { fn from(value: &JinjaObject) -> Self { - Identifier::new(&value.name, value.location.0, value.location.1) + let mut identifier = Identifier::new(&value.name, value.location.0, value.location.1); + identifier.fields.clone_from(&value.fields); + identifier } } @@ -43,7 +57,7 @@ pub fn completion_start(trigger_point: Point, identifier: &Identifier) -> Option let len = identifier.name.len(); let diff = identifier.end.column - trigger_point.column; if diff == 0 || diff == 1 { - return Some(""); + return Some(&identifier.name); } if diff > len { return None; diff --git a/jinja-lsp-queries/src/search/objects.rs b/jinja-lsp-queries/src/search/objects.rs index be59311..f087dd1 100644 --- a/jinja-lsp-queries/src/search/objects.rs +++ b/jinja-lsp-queries/src/search/objects.rs @@ -1,12 +1,14 @@ use tower_lsp::lsp_types::Range; use tree_sitter::{Point, Query, QueryCapture, QueryCursor, Tree}; +use super::{completion_start, to_range, Identifier}; + #[derive(Default, Debug, Clone)] pub struct JinjaObject { pub name: String, pub location: (Point, Point), pub is_filter: bool, - fields: Vec<(String, (Point, Point))>, + pub fields: Vec<(String, (Point, Point))>, } impl JinjaObject { @@ -22,6 +24,11 @@ impl JinjaObject { pub fn add_field(&mut self, field: String, start: Point, end: Point) { self.fields.push((field, (start, end))); } + + pub fn last_field_end(&self) -> Point { + let last = self.fields.last().map_or(self.location.1, |v| v.1 .1); + last + } } #[derive(Default, Debug)] @@ -104,7 +111,22 @@ impl JinjaObjects { 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) { + } + if self.in_expr(trigger_point) { + if trigger_point > self.ident.1 { + return Some(CompletionType::Identifier); + } + if let Some(ident_value) = self.is_ident(trigger_point) { + // if let Some(ident2) = self.objects.last().map(|last| last) { + let identifier = Identifier::new(&ident_value, self.ident.0, self.ident.1); + let start = completion_start(trigger_point, &identifier); + let range = to_range((self.ident.0, self.ident.1)); + return Some(CompletionType::IncompleteIdentifier { + name: start?.to_string(), + range, + }); + // } + } return Some(CompletionType::Identifier); } None @@ -115,7 +137,7 @@ impl JinjaObjects { } pub fn in_expr(&self, trigger_point: Point) -> bool { - trigger_point >= self.expr.0 && trigger_point <= self.expr.1 && trigger_point > self.ident.1 + trigger_point >= self.expr.0 && trigger_point <= self.expr.1 && trigger_point > self.ident.0 } pub fn is_ident(&self, trigger_point: Point) -> Option { @@ -176,6 +198,7 @@ pub enum CompletionType { Identifier, IncludedTemplate { name: String, range: Range }, Snippets { range: Range }, + IncompleteIdentifier { name: String, range: Range }, } static VALID_IDENTIFIERS: [&str; 8] = [ diff --git a/jinja-lsp-queries/src/search/python_identifiers.rs b/jinja-lsp-queries/src/search/python_identifiers.rs new file mode 100644 index 0000000..0e61728 --- /dev/null +++ b/jinja-lsp-queries/src/search/python_identifiers.rs @@ -0,0 +1,24 @@ +use tree_sitter::{Point, Query, QueryCursor, Tree}; + +pub fn _python_identifiers( + query: &Query, + tree: &Tree, + mut _trigger_point: Point, + text: &str, + all: bool, +) { + 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 { + // if check.is_none() { + // break; + // } + } +} diff --git a/jinja-lsp-queries/src/search/queries.rs b/jinja-lsp-queries/src/search/queries.rs index a532f3d..10670b1 100644 --- a/jinja-lsp-queries/src/search/queries.rs +++ b/jinja-lsp-queries/src/search/queries.rs @@ -8,6 +8,7 @@ pub struct Queries { pub backend_definitions: Query, pub backend_templates: Query, pub jinja_snippets: Query, + pub python_identifiers: Query, } impl Clone for Queries { @@ -26,6 +27,8 @@ impl Default for Queries { jinja_imports: Query::new(&tree_sitter_jinja2::language(), JINJA_IMPORTS).unwrap(), backend_templates: Query::new(&tree_sitter_rust::language(), RUST_TEMPLATES).unwrap(), jinja_snippets: Query::new(&tree_sitter_jinja2::language(), JINJA_SNIPPETS).unwrap(), + python_identifiers: Query::new(&tree_sitter_python::language(), PYTHON_IDENTIFIERS) + .unwrap(), } } } @@ -37,6 +40,8 @@ impl Queries { Query::new(&tree_sitter_python::language(), PYTHON_TEMPLATES).unwrap(); self.backend_definitions = Query::new(&tree_sitter_python::language(), PYTHON_DEFINITIONS).unwrap(); + self.python_identifiers = + Query::new(&tree_sitter_python::language(), PYTHON_IDENTIFIERS).unwrap(); } } } @@ -272,3 +277,13 @@ pub static PYTHON_DEFINITIONS: &str = r#" (ERROR) @error "#; + +const PYTHON_IDENTIFIERS: &str = r#" +(_ + (identifier) @identifier +) + +(attribute) @attribute + +(ERROR) @error +"#; diff --git a/jinja-lsp-queries/src/search/snippets_completion.rs b/jinja-lsp-queries/src/search/snippets_completion.rs index d672a09..207f682 100644 --- a/jinja-lsp-queries/src/search/snippets_completion.rs +++ b/jinja-lsp-queries/src/search/snippets_completion.rs @@ -14,7 +14,6 @@ pub struct Snippets { impl Snippets { pub fn check(&mut self, name: &str, capture: &QueryCapture<'_>) -> Option<()> { - dbg!(name); match name { "start" => { let start = capture.node.start_position(); diff --git a/jinja-lsp-queries/src/search/test_queries.rs b/jinja-lsp-queries/src/search/test_queries.rs index b4a8190..c475de8 100644 --- a/jinja-lsp-queries/src/search/test_queries.rs +++ b/jinja-lsp-queries/src/search/test_queries.rs @@ -1,6 +1,9 @@ #[cfg(test)] mod query_tests { - use crate::search::{objects::objects_query, snippets_completion::snippets_query}; + use crate::search::{ + objects::objects_query, python_identifiers::_python_identifiers, + snippets_completion::snippets_query, to_range, + }; use tree_sitter::{Parser, Point}; use crate::{ @@ -195,17 +198,43 @@ mod query_tests { {{ }} {{ "|" }} + {{ identifier }} "#; let cases = [ (Point::new(1, 27), Some(CompletionType::Filter)), - (Point::new(1, 48), None), + ( + Point::new(1, 48), + Some(CompletionType::IncompleteIdentifier { + name: "filter2".to_string(), + range: to_range((Point::new(1, 41), Point::new(1, 48))), + }), + ), (Point::new(1, 40), Some(CompletionType::Filter)), (Point::new(1, 50), Some(CompletionType::Identifier)), - (Point::new(3, 18), None), + ( + Point::new(3, 18), + None, // Some(CompletionType::IncompleteIdentifier { + // name: "something".to_owned(), + // range: to_range((Point::new(3, 18), Point::new(3, 27))), + // }), + ), (Point::new(4, 20), None), - (Point::new(3, 22), None), + ( + Point::new(3, 22), + None, // Some(CompletionType::IncompleteIdentifier { + // name: "something".to_owned(), + // range: to_range((Point::new(3, 18), Point::new(3, 27))), + // }), + ), (Point::new(8, 15), Some(CompletionType::Identifier)), (Point::new(9, 18), Some(CompletionType::Identifier)), + ( + Point::new(10, 18), + Some(CompletionType::IncompleteIdentifier { + name: "iden".to_string(), + range: to_range((Point::new(10, 15), Point::new(10, 25))), + }), + ), ]; for case in cases { let tree = prepare_jinja_tree(source); @@ -358,4 +387,19 @@ mod query_tests { assert_eq!(snippets.is_error, case.2); } } + + #[test] + fn test_python_identifiers() { + let cases = [r#" + [page.text + for page in retrieval.result.abc] + "#]; + + let query = Queries::default(); + let query = query.python_identifiers; + for _case in cases { + let tree = prepare_python_tree(cases[0]); + _python_identifiers(&query, &tree, Point::new(0, 0), cases[0], true); + } + } } diff --git a/jinja-lsp/Cargo.toml b/jinja-lsp/Cargo.toml index 799a3ff..e1ee29f 100644 --- a/jinja-lsp/Cargo.toml +++ b/jinja-lsp/Cargo.toml @@ -18,6 +18,7 @@ opt-level = 3 name = "jinja-lsp" path = "src/main.rs" + [dependencies] env_logger = "0.9.0" ropey = "1.5.0" diff --git a/jinja-lsp/src/backend.rs b/jinja-lsp/src/backend.rs index 8795484..e33a91a 100644 --- a/jinja-lsp/src/backend.rs +++ b/jinja-lsp/src/backend.rs @@ -154,7 +154,7 @@ impl LanguageServer for Backend { } impl Backend { - pub fn new(client: Client) -> Self { + pub fn _new(client: Client) -> Self { let (lsp_sender, lsp_recv) = mpsc::channel(50); let (diagnostic_sender, diagnostic_recv) = mpsc::channel(20); lsp_task( diff --git a/jinja-lsp/src/channels/lsp.rs b/jinja-lsp/src/channels/lsp.rs index 8ca7e85..69bbeaa 100644 --- a/jinja-lsp/src/channels/lsp.rs +++ b/jinja-lsp/src/channels/lsp.rs @@ -182,7 +182,8 @@ pub fn lsp_task( } } CompletionType::IncludedTemplate { name, range } => { - if let Some(templates) = lsp_data.read_templates(name, range) { + if let Some(templates) = lsp_data.read_templates(name, range, None) + { items = Some(CompletionResponse::Array(templates)); } } @@ -215,6 +216,7 @@ pub fn lsp_task( items = Some(CompletionResponse::Array(filtered)); } } + CompletionType::IncompleteIdentifier { .. } => {} }; } let _ = sender.send(items); diff --git a/jinja-lsp/src/lib.rs b/jinja-lsp/src/lib.rs new file mode 100644 index 0000000..0d6b1b8 --- /dev/null +++ b/jinja-lsp/src/lib.rs @@ -0,0 +1,5 @@ +mod backend; +pub mod channels; +mod config; +pub mod filter; +pub mod lsp_files; diff --git a/jinja-lsp/src/lsp_files.rs b/jinja-lsp/src/lsp_files.rs index 20e621b..52c8cfd 100644 --- a/jinja-lsp/src/lsp_files.rs +++ b/jinja-lsp/src/lsp_files.rs @@ -1,10 +1,15 @@ use jinja_lsp_queries::{ lsp_helper::{path_items, search_errors}, search::{ - completion_start, definition::definition_query, objects::objects_query, queries::Queries, + completion_start, + definition::definition_query, + objects::{objects_query, JinjaObject}, + queries::Queries, rust_identifiers::backend_definition_query, - rust_template_completion::backend_templates_query, snippets_completion::snippets_query, - templates::templates_query, to_range, Identifier, IdentifierType, + rust_template_completion::backend_templates_query, + snippets_completion::snippets_query, + templates::templates_query, + to_range, Identifier, IdentifierType, }, tree_builder::{JinjaDiagnostic, LangType}, }; @@ -47,11 +52,12 @@ pub struct LspFiles { pub parsers: Parsers, pub queries: Queries, pub config: JinjaConfig, - pub diagnostics_task: JoinHandle<()>, + pub diagnostics_task: Option>, pub main_channel: Option>, pub variables: HashMap>, pub code_actions: HashMap>, pub is_vscode: bool, + pub ignore_globals: bool, } impl LspFiles { @@ -184,14 +190,16 @@ impl LspFiles { text_document: TextDocumentIdentifier::new(params.text_document.uri), text: None, }; - self.diagnostics_task.abort(); + if let Some(task) = &self.diagnostics_task { + task.abort(); + } let channel = self.main_channel.clone(); - self.diagnostics_task = tokio::spawn(async move { + self.diagnostics_task = Some(tokio::spawn(async move { sleep(Duration::from_millis(200)).await; if let Some(channel) = channel { let _ = channel.send(LspMessage::DidSave(param)).await; } - }); + })); None } @@ -211,6 +219,7 @@ impl LspFiles { &name.to_string(), self.config.templates.clone(), lang_type, + self.ignore_globals, ) } @@ -418,9 +427,19 @@ impl LspFiles { if file.0 == &uri { continue; } - let variables = file.1.iter().filter(|item| item.name == current_ident); + let variables = file.1.iter().filter(|item| { + item.name.split('.').next().unwrap_or(&item.name) == current_ident + }); for variable in variables { - let uri = Url::parse(file.0).unwrap(); + let uri = { + if variable.start == Point::new(0, 0) + && variable.end == Point::new(0, 0) + { + Url::parse(&format!("{}-{}", &file.0, &variable.name)).unwrap() + } else { + Url::parse(file.0).unwrap() + } + }; let start = to_position2(variable.start); let end = to_position2(variable.end); let range = Range::new(start, end); @@ -618,7 +637,12 @@ impl LspFiles { Some(items) } - pub fn read_templates(&self, mut prefix: String, range: Range) -> Option> { + pub fn read_templates( + &self, + mut prefix: String, + range: Range, + _: Option, + ) -> Option> { let all_templates = self.trees.get(&LangType::Template)?; if prefix.is_empty() { prefix = String::from("file:///"); @@ -659,6 +683,16 @@ impl LspFiles { Some(abc) } + pub fn get_variable(&self, prefix: String, id: String) -> Option { + let variables = self.variables.get(&id)?; + for variable in variables { + if variable.name.contains(&prefix) { + return Some(variable.name.to_string()); + } + } + None + } + pub fn did_open(&mut self, params: DidOpenTextDocumentParams) -> Option { let name = params.text_document.uri.as_str(); let lang_type = self.config.file_ext(&Path::new(name))?; @@ -675,6 +709,25 @@ impl LspFiles { Some(msg) } + pub fn read_objects(&self, uri: Url) -> Option> { + let rope = self.documents.get(uri.as_str())?; + let mut writter = FileContent::default(); + let _ = rope.write_to(&mut writter); + let content = writter.content; + let lang_type = self.config.file_ext(&Path::new(uri.as_str()))?; + let trees = self.trees.get(&lang_type)?; + let tree = trees.get(uri.as_str())?; + let objects = objects_query( + &self.queries.jinja_objects, + tree, + Point::new(0, 0), + &content, + true, + ); + let objects = objects.show(); + Some(objects) + } + pub fn data_type(&self, uri: Url, hover: Identifier) -> Option { let this_file = self.variables.get(uri.as_str())?; let this_file = this_file @@ -686,8 +739,23 @@ impl LspFiles { let same_name = hover.name == variable.name; bigger && in_scope && same_name }) - .max()?; - Some(this_file.identifier_type.clone()) + .max(); + if let Some(this_file) = this_file { + return Some(this_file.identifier_type.clone()); + } + for file in &self.variables { + if file.0 == &uri.to_string() { + continue; + } + let variables = file + .1 + .iter() + .filter(|item| item.name.split('.').next().unwrap_or(&item.name) == hover.name); + if variables.count() > 0 { + return Some(IdentifierType::BackendVariable); + } + } + None } pub fn document_symbols( @@ -712,6 +780,23 @@ impl LspFiles { } Some(DocumentSymbolResponse::Nested(symbols)) } + + pub fn delete_documents(&mut self) { + self.documents.clear(); + } + + pub fn delete_documents_with_id(&mut self, id: String) { + let mut ids = vec![]; + for i in &self.documents { + if i.0.contains(&id) { + ids.push(i.0.clone()); + } + } + for i in ids { + self.documents.remove(&i); + self.variables.remove(&i); + } + } } impl Default for LspFiles { @@ -719,7 +804,7 @@ impl Default for LspFiles { let mut trees = HashMap::new(); trees.insert(LangType::Template, HashMap::new()); trees.insert(LangType::Backend, HashMap::new()); - let diagnostics_task = tokio::spawn(async move {}); + let diagnostics_task = None; let main_channel = None; Self { trees, @@ -732,6 +817,35 @@ impl Default for LspFiles { variables: HashMap::default(), is_vscode: false, code_actions: HashMap::default(), + ignore_globals: false, + } + } +} + +impl Clone for LspFiles { + fn clone(&self) -> Self { + let trees = self.trees.clone(); + let parsers = Parsers::default(); + let queries = Queries::default(); + let documents = self.documents.clone(); + let main_channel = self.main_channel.clone(); + let variables = self.variables.clone(); + let is_vscode = self.is_vscode; + let code_actions = self.code_actions.clone(); + let config = self.config.clone(); + let task = None; + Self { + trees, + documents, + parsers, + queries, + config, + main_channel, + variables, + code_actions, + is_vscode, + diagnostics_task: task, + ignore_globals: self.ignore_globals, } } } diff --git a/jinja-lsp/src/main.rs b/jinja-lsp/src/main.rs index 1f6209f..1541114 100644 --- a/jinja-lsp/src/main.rs +++ b/jinja-lsp/src/main.rs @@ -15,7 +15,7 @@ async fn main() { let stdin = tokio::io::stdin(); let stdout = tokio::io::stdout(); - let (service, socket) = LspService::build(Backend::new).finish(); + let (service, socket) = LspService::build(Backend::_new).finish(); Server::new(stdin, stdout, socket).serve(service).await; }