From 05f0421a9ec15ecb1920cb5ff822f1a6667c6a02 Mon Sep 17 00:00:00 2001 From: "xiarui.xr" Date: Tue, 19 Sep 2023 09:06:10 +0800 Subject: [PATCH 01/11] feat: lsp find references Signed-off-by: xiarui.xr --- kclvm/Cargo.lock | 4 +- kclvm/parser/src/lib.rs | 2 +- kclvm/tools/src/LSP/src/completion.rs | 10 +- kclvm/tools/src/LSP/src/find_ref/find_refs.rs | 28 -- kclvm/tools/src/LSP/src/find_ref/go_to_def.rs | 6 - kclvm/tools/src/LSP/src/find_ref/mod.rs | 134 ---------- .../src/LSP/src/find_ref/test_data/inherit.k | 10 - .../LSP/src/find_ref/test_data/inherit_pkg.k | 4 - .../test_data/test_word_workspace/inherit.k | 10 - .../test_word_workspace/inherit_pkg.k | 4 - .../test_word_workspace_map/inherit.k | 10 - .../test_word_workspace_map/inherit_pkg.k | 4 - kclvm/tools/src/LSP/src/find_ref/tests.rs | 252 ------------------ kclvm/tools/src/LSP/src/find_ref/word_map.rs | 123 --------- kclvm/tools/src/LSP/src/find_refs.rs | 116 ++++++++ kclvm/tools/src/LSP/src/goto_def.rs | 37 +-- kclvm/tools/src/LSP/src/hover.rs | 2 +- kclvm/tools/src/LSP/src/lib.rs | 2 +- kclvm/tools/src/LSP/src/main.rs | 1 + kclvm/tools/src/LSP/src/notification.rs | 2 +- kclvm/tools/src/LSP/src/request.rs | 24 +- kclvm/tools/src/LSP/src/state.rs | 10 +- .../LSP/src/test_data/find_refs_test/kcl.mod | 0 .../LSP/src/test_data/find_refs_test/main.k | 3 + kclvm/tools/src/LSP/src/util.rs | 165 +++++++++++- 25 files changed, 349 insertions(+), 614 deletions(-) delete mode 100644 kclvm/tools/src/LSP/src/find_ref/find_refs.rs delete mode 100644 kclvm/tools/src/LSP/src/find_ref/go_to_def.rs delete mode 100644 kclvm/tools/src/LSP/src/find_ref/mod.rs delete mode 100644 kclvm/tools/src/LSP/src/find_ref/test_data/inherit.k delete mode 100644 kclvm/tools/src/LSP/src/find_ref/test_data/inherit_pkg.k delete mode 100644 kclvm/tools/src/LSP/src/find_ref/test_data/test_word_workspace/inherit.k delete mode 100644 kclvm/tools/src/LSP/src/find_ref/test_data/test_word_workspace/inherit_pkg.k delete mode 100644 kclvm/tools/src/LSP/src/find_ref/test_data/test_word_workspace_map/inherit.k delete mode 100644 kclvm/tools/src/LSP/src/find_ref/test_data/test_word_workspace_map/inherit_pkg.k delete mode 100644 kclvm/tools/src/LSP/src/find_ref/tests.rs delete mode 100644 kclvm/tools/src/LSP/src/find_ref/word_map.rs create mode 100644 kclvm/tools/src/LSP/src/find_refs.rs create mode 100644 kclvm/tools/src/LSP/src/test_data/find_refs_test/kcl.mod create mode 100644 kclvm/tools/src/LSP/src/test_data/find_refs_test/main.k diff --git a/kclvm/Cargo.lock b/kclvm/Cargo.lock index f83b0d867..25201206c 100644 --- a/kclvm/Cargo.lock +++ b/kclvm/Cargo.lock @@ -1964,9 +1964,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.5.0" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" [[package]] name = "memmap2" diff --git a/kclvm/parser/src/lib.rs b/kclvm/parser/src/lib.rs index eed2eb690..84df8580e 100644 --- a/kclvm/parser/src/lib.rs +++ b/kclvm/parser/src/lib.rs @@ -313,7 +313,7 @@ impl Loader { fn _load_main(&mut self) -> Result { let compile_entries = get_compile_entries_from_paths(&self.paths, &self.opts)?; let mut pkgs = HashMap::new(); - let workdir = compile_entries.get_root_path().to_string(); + let workdir = compile_entries.get_root_path().to_string();// get package root debug_assert_eq!(compile_entries.len(), self.paths.len()); diff --git a/kclvm/tools/src/LSP/src/completion.rs b/kclvm/tools/src/LSP/src/completion.rs index 681b04c1c..7f6067316 100644 --- a/kclvm/tools/src/LSP/src/completion.rs +++ b/kclvm/tools/src/LSP/src/completion.rs @@ -83,7 +83,7 @@ fn completion_attr( if let Some((node, schema_expr)) = is_in_schema(program, pos) { let schema_def = find_def(node, &schema_expr.name.get_end_pos(), prog_scope); if let Some(schema) = schema_def { - if let Definition::Object(obj) = schema { + if let Definition::Object(obj, _) = schema { let schema_type = obj.ty.into_schema_type(); completions.extend(schema_type.attrs.keys().map(|attr| KCLCompletionItem { label: attr.clone(), @@ -178,7 +178,7 @@ pub(crate) fn get_completion( let def = find_def(stmt, pos, prog_scope); if let Some(def) = def { match def { - crate::goto_def::Definition::Object(obj) => { + crate::goto_def::Definition::Object(obj, _) => { match &obj.ty.kind { // builtin (str) functions kclvm_sema::ty::TypeKind::Str => { @@ -226,7 +226,7 @@ pub(crate) fn get_completion( _ => {} } } - crate::goto_def::Definition::Scope(s) => { + crate::goto_def::Definition::Scope(s, _) => { for (name, obj) in &s.elems { if let ScopeObjectKind::Module(_) = obj.borrow().kind { continue; @@ -259,7 +259,7 @@ pub(crate) fn get_completion( find_def(stmt, &schema_expr.name.get_end_pos(), prog_scope); if let Some(schema) = schema_def { match schema { - Definition::Object(obj) => { + Definition::Object(obj, _) => { let schema_type = obj.ty.into_schema_type(); items.extend( schema_type @@ -271,7 +271,7 @@ pub(crate) fn get_completion( .collect::>(), ); } - Definition::Scope(_) => {} + Definition::Scope(_, _) => {} } } } diff --git a/kclvm/tools/src/LSP/src/find_ref/find_refs.rs b/kclvm/tools/src/LSP/src/find_ref/find_refs.rs deleted file mode 100644 index f3926093c..000000000 --- a/kclvm/tools/src/LSP/src/find_ref/find_refs.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::find_ref; -use crate::find_ref::go_to_def::go_to_def; -use kclvm_error::Position; - -/// Find all references of the item at the cursor location. -pub fn find_refs(path: String, pos: Position) -> Vec { - let declaration = go_to_def(pos.clone()); - let search = { - move |decl: Position| { - let name = find_ref::word_at_pos(pos); - if name.is_none() { - return vec![]; - } - // Get identifiers with same name - let candidates = find_ref::match_word(path, name.unwrap()); - // Check if the definition of candidate and declartion are the same - let refs: Vec = candidates - .into_iter() - .filter(|x| go_to_def(x.clone()).as_ref() == Some(&decl)) - .collect(); - refs - } - }; - match declaration { - Some(decl) => search(decl), - None => Vec::new(), - } -} diff --git a/kclvm/tools/src/LSP/src/find_ref/go_to_def.rs b/kclvm/tools/src/LSP/src/find_ref/go_to_def.rs deleted file mode 100644 index 95b64b7eb..000000000 --- a/kclvm/tools/src/LSP/src/find_ref/go_to_def.rs +++ /dev/null @@ -1,6 +0,0 @@ -use kclvm_error::Position; - -/// Get the definition of an identifier. -pub fn go_to_def(pos: Position) -> Option { - Some(pos) -} diff --git a/kclvm/tools/src/LSP/src/find_ref/mod.rs b/kclvm/tools/src/LSP/src/find_ref/mod.rs deleted file mode 100644 index 86fe83f9a..000000000 --- a/kclvm/tools/src/LSP/src/find_ref/mod.rs +++ /dev/null @@ -1,134 +0,0 @@ -#![allow(dead_code)] - -use anyhow::Result; -use kclvm_driver::get_kcl_files; -use kclvm_error::Position; - -mod find_refs; -mod go_to_def; -mod word_map; - -#[cfg(test)] -mod tests; - -// LineWord describes an arbitrary word in a certain line including -// start position, end position and the word itself. -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct LineWord { - startpos: u64, - endpos: u64, - word: String, -} - -// Get the word of the position. -pub fn word_at_pos(pos: Position) -> Option { - let text = read_file(&pos.filename); - if text.is_err() { - return None; - } - let text = text.unwrap(); - let lines: Vec<&str> = text.lines().collect(); - if pos.line >= lines.len() as u64 { - return None; - } - pos.column?; - let col = pos.column.unwrap(); - let line_words = line_to_words(lines[pos.line as usize].to_string()); - if line_words.is_empty() - || col < line_words.first().unwrap().startpos - || col >= line_words.last().unwrap().endpos - { - return None; - } - for line_word in line_words.into_iter() { - if col >= line_word.startpos && col < line_word.endpos { - return Some(line_word.word); - } - } - None -} - -pub fn read_file(path: &String) -> Result { - let text = std::fs::read_to_string(path)?; - Ok(text) -} - -// Split one line into identifier words. -pub fn line_to_words(text: String) -> Vec { - let mut chars: Vec = text.chars().collect(); - chars.push('\n'); - let mut start_pos = usize::MAX; - let mut continue_pos = usize::MAX - 1; // avoid overflow - let mut prev_word = false; - let mut words: Vec = vec![]; - for (i, ch) in chars.iter().enumerate() { - let is_id_start = rustc_lexer::is_id_start(*ch); - let is_id_continue = rustc_lexer::is_id_continue(*ch); - // If the character is valid identfier start and the previous character is not valid identifier continue, mark the start position. - if is_id_start && !prev_word { - start_pos = i; - } - match is_id_continue { - true => { - // Continue searching for the end position. - if start_pos != usize::MAX { - continue_pos = i; - } - } - false => { - // Find out the end position. - if continue_pos + 1 == i { - words.push(LineWord { - startpos: start_pos as u64, - endpos: i as u64, - word: chars[start_pos..i].iter().collect::().clone(), - }); - } - // Reset the start position. - start_pos = usize::MAX; - } - } - prev_word = is_id_continue; - } - words -} - -// Get all occurrences of the word in the entire path. -pub fn match_word(path: String, name: String) -> Vec { - let mut res = vec![]; - if let Ok(files) = get_kcl_files(path, true) { - // Searching in all files. - for file in files.into_iter() { - let text = read_file(&file); - if text.is_err() { - continue; - } - let text = text.unwrap(); - let lines: Vec<&str> = text.lines().collect(); - for (li, line) in lines.into_iter().enumerate() { - // Get the matching results for each line. - let matched: Vec = line_to_words(line.to_string()) - .into_iter() - .filter(|x| x.word == name) - .map(|x| Position { - filename: file.clone(), - line: li as u64, - column: Some(x.startpos), - }) - .collect(); - res.extend(matched); - } - } - } - res -} - -// Convert pos format -// The position in lsp protocol is different with position in ast node whose line number is 1 based. -pub fn kcl_pos_to_lsp_pos(pos: Position) -> Position { - Position { - filename: pos.filename, - line: pos.line - 1, - column: pos.column, - } -} diff --git a/kclvm/tools/src/LSP/src/find_ref/test_data/inherit.k b/kclvm/tools/src/LSP/src/find_ref/test_data/inherit.k deleted file mode 100644 index a1bef7126..000000000 --- a/kclvm/tools/src/LSP/src/find_ref/test_data/inherit.k +++ /dev/null @@ -1,10 +0,0 @@ -schema Parent: - name: str - -schema Son(Parent): - age: int - son_name: str = name - -schema GrandSon(Son): - grand_son_name: str = name - a: str = name_not_exist diff --git a/kclvm/tools/src/LSP/src/find_ref/test_data/inherit_pkg.k b/kclvm/tools/src/LSP/src/find_ref/test_data/inherit_pkg.k deleted file mode 100644 index 39cdf9b1d..000000000 --- a/kclvm/tools/src/LSP/src/find_ref/test_data/inherit_pkg.k +++ /dev/null @@ -1,4 +0,0 @@ -import pkg - -schema Son(pkg.Parent): - son_name: str = name diff --git a/kclvm/tools/src/LSP/src/find_ref/test_data/test_word_workspace/inherit.k b/kclvm/tools/src/LSP/src/find_ref/test_data/test_word_workspace/inherit.k deleted file mode 100644 index a1bef7126..000000000 --- a/kclvm/tools/src/LSP/src/find_ref/test_data/test_word_workspace/inherit.k +++ /dev/null @@ -1,10 +0,0 @@ -schema Parent: - name: str - -schema Son(Parent): - age: int - son_name: str = name - -schema GrandSon(Son): - grand_son_name: str = name - a: str = name_not_exist diff --git a/kclvm/tools/src/LSP/src/find_ref/test_data/test_word_workspace/inherit_pkg.k b/kclvm/tools/src/LSP/src/find_ref/test_data/test_word_workspace/inherit_pkg.k deleted file mode 100644 index 39cdf9b1d..000000000 --- a/kclvm/tools/src/LSP/src/find_ref/test_data/test_word_workspace/inherit_pkg.k +++ /dev/null @@ -1,4 +0,0 @@ -import pkg - -schema Son(pkg.Parent): - son_name: str = name diff --git a/kclvm/tools/src/LSP/src/find_ref/test_data/test_word_workspace_map/inherit.k b/kclvm/tools/src/LSP/src/find_ref/test_data/test_word_workspace_map/inherit.k deleted file mode 100644 index a1bef7126..000000000 --- a/kclvm/tools/src/LSP/src/find_ref/test_data/test_word_workspace_map/inherit.k +++ /dev/null @@ -1,10 +0,0 @@ -schema Parent: - name: str - -schema Son(Parent): - age: int - son_name: str = name - -schema GrandSon(Son): - grand_son_name: str = name - a: str = name_not_exist diff --git a/kclvm/tools/src/LSP/src/find_ref/test_data/test_word_workspace_map/inherit_pkg.k b/kclvm/tools/src/LSP/src/find_ref/test_data/test_word_workspace_map/inherit_pkg.k deleted file mode 100644 index 39cdf9b1d..000000000 --- a/kclvm/tools/src/LSP/src/find_ref/test_data/test_word_workspace_map/inherit_pkg.k +++ /dev/null @@ -1,4 +0,0 @@ -import pkg - -schema Son(pkg.Parent): - son_name: str = name diff --git a/kclvm/tools/src/LSP/src/find_ref/tests.rs b/kclvm/tools/src/LSP/src/find_ref/tests.rs deleted file mode 100644 index d27969350..000000000 --- a/kclvm/tools/src/LSP/src/find_ref/tests.rs +++ /dev/null @@ -1,252 +0,0 @@ -use crate::find_ref; -use crate::find_ref::LineWord; -use kclvm_error::Position; - -#[cfg(test)] -mod tests { - use super::*; - use std::fs; - use std::path::Path; - use std::{collections::HashMap, hash::Hash}; - - fn check_line_to_words(code: &str, expect: Vec) { - assert_eq!(find_ref::line_to_words(code.to_string()), expect); - } - - fn test_eq_list(a: &[T], b: &[T]) -> bool - where - T: Eq + Hash, - { - fn count(items: &[T]) -> HashMap<&T, usize> - where - T: Eq + Hash, - { - let mut cnt = HashMap::new(); - for i in items { - *cnt.entry(i).or_insert(0) += 1 - } - cnt - } - count(a) == count(b) - } - - #[test] - fn test_line_to_words() { - let datas = [ - "alice_first_name = \"alice\"", - "0lice_first_name = \"alic0\"", - "alice = p.Parent { name: \"alice\" }", - ]; - let expect = vec![ - vec![ - LineWord { - startpos: 0, - endpos: 16, - word: "alice_first_name".to_string(), - }, - LineWord { - startpos: 20, - endpos: 25, - word: "alice".to_string(), - }, - ], - vec![LineWord { - startpos: 20, - endpos: 25, - word: "alic0".to_string(), - }], - vec![ - LineWord { - startpos: 0, - endpos: 5, - word: "alice".to_string(), - }, - LineWord { - startpos: 8, - endpos: 9, - word: "p".to_string(), - }, - LineWord { - startpos: 10, - endpos: 16, - word: "Parent".to_string(), - }, - LineWord { - startpos: 19, - endpos: 23, - word: "name".to_string(), - }, - LineWord { - startpos: 26, - endpos: 31, - word: "alice".to_string(), - }, - ], - ]; - for i in 0..datas.len() { - check_line_to_words(datas[i], expect[i].clone()); - } - } - - #[test] - fn test_word_at_pos() { - // use std::env; - // let parent_path = env::current_dir().unwrap(); - // println!("The current directory is {}", parent_path.display()); - let path_prefix = "./src/find_ref/".to_string(); - let datas = vec![ - Position { - filename: (path_prefix.clone() + "test_data/inherit.k"), - line: 0, - column: Some(0), - }, - Position { - filename: (path_prefix.clone() + "test_data/inherit.k"), - line: 1, - column: Some(5), - }, - Position { - filename: (path_prefix.clone() + "test_data/inherit.k"), - line: 3, - column: Some(7), - }, - Position { - filename: (path_prefix.clone() + "test_data/inherit.k"), - line: 3, - column: Some(10), - }, - Position { - filename: (path_prefix.clone() + "test_data/inherit.k"), - line: 4, - column: Some(8), - }, - Position { - filename: (path_prefix + "test_data/inherit.k"), - line: 4, - column: Some(100), - }, - ]; - let expect = vec![ - Some("schema".to_string()), - Some("name".to_string()), - Some("Son".to_string()), - None, - None, - None, - ]; - for i in 0..datas.len() { - assert_eq!(find_ref::word_at_pos(datas[i].clone()), expect[i]); - } - } - - fn test_word_workspace() -> String { - Path::new(".") - .join("src") - .join("find_ref") - .join("test_data") - .join("test_word_workspace") - .display() - .to_string() - } - - #[test] - fn test_match_word() { - let path = test_word_workspace(); - let datas = vec![String::from("Son")]; - let except = vec![vec![ - Position { - filename: Path::new(&test_word_workspace()) - .join("inherit_pkg.k") - .display() - .to_string(), - line: 2, - column: Some(7), - }, - Position { - filename: Path::new(&test_word_workspace()) - .join("inherit.k") - .display() - .to_string(), - line: 3, - column: Some(7), - }, - Position { - filename: Path::new(&test_word_workspace()) - .join("inherit.k") - .display() - .to_string(), - line: 7, - column: Some(16), - }, - ]]; - for i in 0..datas.len() { - assert!(test_eq_list( - &find_ref::match_word(path.clone(), datas[i].clone()), - &except[i] - )); - } - } - - fn test_word_workspace_map() -> String { - Path::new(".") - .join("src") - .join("find_ref") - .join("test_data") - .join("test_word_workspace_map") - .display() - .to_string() - } - - #[test] - fn test_word_map() { - let path = test_word_workspace_map(); - let mut mp = find_ref::word_map::WorkSpaceWordMap::new(path); - mp.build(); - let _res = fs::rename( - Path::new(&test_word_workspace_map()) - .join("inherit_pkg.k") - .display() - .to_string(), - Path::new(&test_word_workspace_map()) - .join("inherit_bak.k") - .display() - .to_string(), - ); - mp.rename_file( - Path::new(&test_word_workspace_map()) - .join("inherit_pkg.k") - .display() - .to_string(), - Path::new(&test_word_workspace_map()) - .join("inherit_bak.k") - .display() - .to_string(), - ); - mp.delete_file( - Path::new(&test_word_workspace_map()) - .join("inherit.k") - .display() - .to_string(), - ); - let _res = fs::rename( - Path::new(&test_word_workspace_map()) - .join("inherit_bak.k") - .display() - .to_string(), - Path::new(&test_word_workspace_map()) - .join("inherit_pkg.k") - .display() - .to_string(), - ); - - let except = vec![Position { - filename: Path::new(&test_word_workspace_map()) - .join("inherit_bak.k") - .display() - .to_string(), - line: 2, - column: Some(7), - }]; - assert_eq!(mp.get(&String::from("Son")), Some(except)); - } -} diff --git a/kclvm/tools/src/LSP/src/find_ref/word_map.rs b/kclvm/tools/src/LSP/src/find_ref/word_map.rs deleted file mode 100644 index 143bf1708..000000000 --- a/kclvm/tools/src/LSP/src/find_ref/word_map.rs +++ /dev/null @@ -1,123 +0,0 @@ -use crate::find_ref; - -use kclvm_driver::get_kcl_files; -use kclvm_error::Position; -use std::collections::HashMap; - -// Record all occurrences of the name in a file -#[derive(Default)] -pub struct FileWordMap { - file_name: String, - word_map: HashMap>, -} - -impl FileWordMap { - pub fn new(file_name: String) -> Self { - Self { - file_name, - word_map: HashMap::new(), - } - } - - // Clear records - pub fn clear(&mut self) { - self.word_map.clear(); - } - - // insert an occurrence of a name - pub fn insert(&mut self, name: String, pos: Position) { - self.word_map.entry(name).or_default().push(pos); - } - - // build the record map - // if text is missing, it will be read from the file system based on the filename - pub fn build(&mut self, text: Option) { - self.clear(); - let text = text.unwrap_or(find_ref::read_file(&self.file_name).unwrap()); - let lines: Vec<&str> = text.lines().collect(); - for (li, line) in lines.into_iter().enumerate() { - let words = find_ref::line_to_words(line.to_string()); - words.iter().for_each(|x| { - self.word_map - .entry(x.word.clone()) - .or_default() - .push(Position { - filename: self.file_name.clone(), - line: li as u64, - column: Some(x.startpos), - }) - }); - } - } - - // return all occurrence of a name - pub fn get(&self, name: &String) -> Option<&Vec> { - self.word_map.get(name) - } -} - -// Record all occurrences of the name in workspace -pub struct WorkSpaceWordMap { - path: String, - file_map: HashMap, -} - -impl WorkSpaceWordMap { - pub fn new(path: String) -> Self { - Self { - path, - file_map: HashMap::new(), - } - } - - // when user edit a file, the filemap of this file need to rebuild - pub fn change_file(&mut self, file_name: String, text: String) { - self.file_map - .entry(file_name.clone()) - .or_insert(FileWordMap::new(file_name)) - .build(Some(text)); - } - - // when user add a file, the workspacemap will add a new filemap for it - pub fn create_file(&mut self, file_name: String) { - self.file_map - .entry(file_name.clone()) - .or_insert(FileWordMap::new(file_name)) - .clear(); - } - - // when user delete a file, the workspacemap will remove the old filemap of it - pub fn delete_file(&mut self, file_name: String) { - self.file_map.remove(&file_name); - } - - // when user rename a file, the workspacemap will remove the old filemap of it and build a new filemap for it - pub fn rename_file(&mut self, old_name: String, new_name: String) { - self.delete_file(old_name); - self.create_file(new_name.clone()); - self.file_map.get_mut(&new_name).unwrap().build(None); - } - - // build & maintain the record map for each file under the path - pub fn build(&mut self) { - //TODO may use some cache from other component? - if let Ok(files) = get_kcl_files(&self.path, true) { - for file in files.into_iter() { - self.file_map - .insert(file.clone(), FileWordMap::new(file.clone())); - self.file_map.get_mut(&file).unwrap().build(None); - } - } - } - - // return all occurrence of a name in the workspace - pub fn get(self, name: &String) -> Option> { - let mut words = Vec::new(); - for (_, mp) in self.file_map.iter() { - if let Some(file_words) = mp.get(name) { - words.extend_from_slice(file_words); - } - } - Some(words) - } -} diff --git a/kclvm/tools/src/LSP/src/find_refs.rs b/kclvm/tools/src/LSP/src/find_refs.rs new file mode 100644 index 000000000..e8c8ee300 --- /dev/null +++ b/kclvm/tools/src/LSP/src/find_refs.rs @@ -0,0 +1,116 @@ +use anyhow; +use std::collections::HashMap; +use crate::{ + util::{build_word_index, parse_param_and_compile, Param}, + state::{LanguageServerSnapshot, Task, log_message}, + from_lsp::{self, file_path_from_url, kcl_pos}, + goto_def::{goto_definition, find_def,}, +}; +use lsp_types; +use crossbeam_channel::Sender; +use kclvm_config::modfile::get_pkg_root; +use kclvm_ast::ast::Stmt; + + +pub(crate) fn find_references ( + snapshot: LanguageServerSnapshot, + params: lsp_types::ReferenceParams, + sender: Sender, +) -> anyhow::Result>> { + // 1. find definition of current token + let file = file_path_from_url(¶ms.text_document_position.text_document.uri)?; + let path = from_lsp::abs_path(¶ms.text_document_position.text_document.uri)?; + let db = snapshot.get_db(&path.clone().into())?; + let pos = kcl_pos(&file, params.text_document_position.position); + + if let Some(def_resp) = goto_definition(&db.prog, &pos, &db.scope) { + match def_resp { + lsp_types::GotoDefinitionResponse::Scalar(def_loc) => { + // get the def location + if let Some(def_name) = match db.prog.pos_to_stmt(&pos) { + Some(node) => match node.node { + Stmt::Import(_) => None, + _ => match find_def(node.clone(), &pos, &db.scope) { + Some(def) => Some(def.get_name()), + None => None, + }, + }, + None => None, + } { + // 2. find all occurrence of current token + // todo: decide the scope by the workspace root and the kcl.mod both, use the narrower scope + if let Some(root) = get_pkg_root(path.display().to_string().as_str()) { + match build_word_index(root) { + Ok(word_index) => { + return find_refs(def_loc, def_name, word_index); + }, + Err(_) => { + let _ = log_message("build word index failed".to_string(), &sender); + return anyhow::Ok(None); + } + } + } else { + return Ok(None) + } + } + }, + _=> return Ok(None), + } + } else { + log_message("Definition item not found, result in no reference".to_string(), &sender)?; + } + + return Ok(None) +} + +pub(crate) fn find_refs(def_loc:lsp_types::Location, name: String, word_index: HashMap>) +-> anyhow::Result>>{ + if let Some(locs) = word_index.get(name.as_str()).cloned() { + return anyhow::Ok(Some(locs.into_iter().filter(|ref_loc|{ + // from location to real def + // return if the real def location matches the def_loc + let file_path = ref_loc.uri.path().to_string(); + match parse_param_and_compile( + Param { + file: file_path.clone(), + }, + None, + ) { + Ok((prog, scope, _)) => { + let ref_pos = kcl_pos(&file_path, ref_loc.range.start); + // find def from the ref_pos + if let Some(real_def) = goto_definition(&prog, &ref_pos, &scope) { + match real_def { + lsp_types::GotoDefinitionResponse::Scalar(real_def_loc) => { + real_def_loc == def_loc + }, + _ => false + } + } else { + false + } + + } + Err(_) => { + // todo log compilation error + return false; + }, + } + }).collect())); + } else { + return Ok(None) + } + +} + +#[cfg(test)] +mod tests { + //todo + // todo assert + #[test] + fn test_find_refs() { + + } + + +} \ No newline at end of file diff --git a/kclvm/tools/src/LSP/src/goto_def.rs b/kclvm/tools/src/LSP/src/goto_def.rs index ebbdb9f0a..ad641ef5f 100644 --- a/kclvm/tools/src/LSP/src/goto_def.rs +++ b/kclvm/tools/src/LSP/src/goto_def.rs @@ -70,18 +70,18 @@ pub(crate) fn goto_definition( #[derive(Debug)] pub enum Definition { - Object(ScopeObject), - Scope(Scope), + Object(ScopeObject, String), + Scope(Scope, String), } impl Definition { pub(crate) fn get_positions(&self) -> IndexSet<(KCLPos, KCLPos)> { let mut positions = IndexSet::new(); match self { - Definition::Object(obj) => { + Definition::Object(obj, _) => { positions.insert((obj.start.clone(), obj.end.clone())); } - Definition::Scope(scope) => match &scope.kind { + Definition::Scope(scope, _) => match &scope.kind { kclvm_sema::resolver::scope::ScopeKind::Package(filenames) => { for file in filenames { let dummy_pos = KCLPos { @@ -99,8 +99,15 @@ impl Definition { } positions } + pub(crate) fn get_name(&self) -> String { + match self { + Definition::Object(_, name) => name.clone(), + Definition::Scope(_, name) => name.clone(), + } + } } + pub(crate) fn find_def( node: Node, kcl_pos: &KCLPos, @@ -148,7 +155,7 @@ pub(crate) fn find_def( find_def(node, &schema_expr.name.get_end_pos(), prog_scope); if let Some(schema) = schema_def { match schema { - Definition::Object(obj) => match &obj.ty.kind { + Definition::Object(obj, _) => match &obj.ty.kind { kclvm_sema::ty::TypeKind::Schema(schema_type) => { return find_attr_in_schema( &schema_type, @@ -158,7 +165,7 @@ pub(crate) fn find_def( } _ => {} }, - Definition::Scope(_) => {} + Definition::Scope(_, _) => {} } } } @@ -180,7 +187,7 @@ pub(crate) fn find_def( let id = select_expr.attr; match value_def { Some(def) => match def { - Definition::Object(obj) => match &obj.ty.kind { + Definition::Object(obj, _) => match &obj.ty.kind { kclvm_sema::ty::TypeKind::Schema(schema_type) => { return find_attr_in_schema( &schema_type, @@ -190,7 +197,7 @@ pub(crate) fn find_def( } _ => {} }, - Definition::Scope(_) => {} + Definition::Scope(_, _) => {} }, None => { if let Some(inner_most_scope) = prog_scope.inner_most_scope(kcl_pos) { @@ -239,16 +246,16 @@ pub(crate) fn resolve_var( kclvm_sema::ty::TypeKind::Module(module_ty) => match module_ty.kind { kclvm_sema::ty::ModuleKind::User => scope_map .get(&pkgpath_without_prefix!(module_ty.pkgpath)) - .map(|scope| Definition::Scope(scope.borrow().clone())), + .map(|scope| Definition::Scope(scope.borrow().clone(), name)), kclvm_sema::ty::ModuleKind::System => { - Some(Definition::Object(obj.borrow().clone())) + Some(Definition::Object(obj.borrow().clone(), name)) } kclvm_sema::ty::ModuleKind::Plugin => None, }, _ => None, } } - _ => Some(Definition::Object(obj.borrow().clone())), + _ => Some(Definition::Object(obj.borrow().clone(), name)), }, None => match builtin_scope().lookup(&name) { Some(obj) => { @@ -263,7 +270,7 @@ pub(crate) fn resolve_var( obj.doc = doc; obj.start = node_names[0].get_pos(); obj.end = node_names[0].get_end_pos(); - Some(Definition::Object(obj)) + Some(Definition::Object(obj, name)) } None => None, }, @@ -297,13 +304,13 @@ pub(crate) fn resolve_var( match &ty.kind { kclvm_sema::ty::TypeKind::Function(func_ty) => { return Some(Definition::Object(ScopeObject { - name: func_name, + name: func_name.clone(), start: func_name_node.get_pos(), end: func_name_node.get_end_pos(), ty: ty.clone(), kind: ScopeObjectKind::FunctionCall, doc: Some(func_ty.doc.clone()), - })) + }, func_name)) } _ => return None, } @@ -348,7 +355,7 @@ pub(crate) fn resolve_var( ty: Rc::new(ty.clone()), kind: ScopeObjectKind::FunctionCall, doc: Some(func_ty.doc.clone()), - })) + }, func_name)) } // unreachable _ => {} diff --git a/kclvm/tools/src/LSP/src/hover.rs b/kclvm/tools/src/LSP/src/hover.rs index 5bd8cea5a..e200b3dc1 100644 --- a/kclvm/tools/src/LSP/src/hover.rs +++ b/kclvm/tools/src/LSP/src/hover.rs @@ -19,7 +19,7 @@ pub(crate) fn hover( Some(node) => { let mut docs: Vec = vec![]; if let Some(def) = find_def(node, kcl_pos, prog_scope) { - if let crate::goto_def::Definition::Object(obj) = def { + if let crate::goto_def::Definition::Object(obj, _) = def { match obj.kind { ScopeObjectKind::Definition => { docs.extend(build_schema_hover_content(&obj.ty.into_schema_type())) diff --git a/kclvm/tools/src/LSP/src/lib.rs b/kclvm/tools/src/LSP/src/lib.rs index ce4779dff..f773be282 100644 --- a/kclvm/tools/src/LSP/src/lib.rs +++ b/kclvm/tools/src/LSP/src/lib.rs @@ -5,7 +5,7 @@ mod config; mod db; mod dispatcher; mod document_symbol; -mod find_ref; +mod find_refs; mod formatting; mod from_lsp; mod goto_def; diff --git a/kclvm/tools/src/LSP/src/main.rs b/kclvm/tools/src/LSP/src/main.rs index 168a2307e..76153e92f 100644 --- a/kclvm/tools/src/LSP/src/main.rs +++ b/kclvm/tools/src/LSP/src/main.rs @@ -10,6 +10,7 @@ mod dispatcher; mod document_symbol; mod from_lsp; mod goto_def; +mod find_refs; mod hover; mod main_loop; mod notification; diff --git a/kclvm/tools/src/LSP/src/notification.rs b/kclvm/tools/src/LSP/src/notification.rs index 1098b1b09..b86c46e55 100644 --- a/kclvm/tools/src/LSP/src/notification.rs +++ b/kclvm/tools/src/LSP/src/notification.rs @@ -1,6 +1,6 @@ use lsp_types::notification::{ DidChangeTextDocument, DidChangeWatchedFiles, DidCloseTextDocument, DidOpenTextDocument, - DidSaveTextDocument, + DidSaveTextDocument, //Initialized, }; use crate::{ diff --git a/kclvm/tools/src/LSP/src/request.rs b/kclvm/tools/src/LSP/src/request.rs index f1a12d03a..4e41b9964 100644 --- a/kclvm/tools/src/LSP/src/request.rs +++ b/kclvm/tools/src/LSP/src/request.rs @@ -1,6 +1,6 @@ use anyhow::Ok; use crossbeam_channel::Sender; -use lsp_types::TextEdit; +use lsp_types::{TextEdit, Location}; use ra_ap_vfs::VfsPath; use std::time::Instant; @@ -12,6 +12,7 @@ use crate::{ formatting::format, from_lsp::{self, file_path_from_url, kcl_pos}, goto_def::goto_definition, + find_refs::find_references, hover, quick_fix, state::{log_message, LanguageServerSnapshot, LanguageServerState, Task}, }; @@ -42,7 +43,9 @@ impl LanguageServerState { state.shutdown_requested = true; Ok(()) })? + // .on::(handle_initialize)? .on::(handle_goto_definition)? + .on::(handle_reference)? .on::(handle_completion)? .on::(handle_hover)? .on::(handle_document_symbol)? @@ -69,6 +72,16 @@ impl LanguageServerSnapshot { } } +// pub(crate) fn handle_initialize( +// _snapshot: LanguageServerSnapshot, +// params: lsp_types::InitializeParams, +// _sender: Sender +// ) -> anyhow::Result{ +// if let Some(uri) = params.root_uri { +// self.word_index = build_word_index(uri.path().to_string()) +// } +// } + pub(crate) fn handle_formatting( _snapshot: LanguageServerSnapshot, params: lsp_types::DocumentFormattingParams, @@ -132,6 +145,15 @@ pub(crate) fn handle_goto_definition( Ok(res) } +/// Called when a `FindReferences` request was received +pub(crate) fn handle_reference ( + snapshot: LanguageServerSnapshot, + params: lsp_types::ReferenceParams, + sender: Sender, +) -> anyhow::Result>> { + find_references(snapshot, params, sender) +} + /// Called when a `Completion` request was received. pub(crate) fn handle_completion( snapshot: LanguageServerSnapshot, diff --git a/kclvm/tools/src/LSP/src/state.rs b/kclvm/tools/src/LSP/src/state.rs index a898a7d63..20ec3dec0 100644 --- a/kclvm/tools/src/LSP/src/state.rs +++ b/kclvm/tools/src/LSP/src/state.rs @@ -2,13 +2,14 @@ use crate::analysis::Analysis; use crate::config::Config; use crate::db::AnalysisDatabase; use crate::to_lsp::{kcl_diag_to_lsp_diags, url}; -use crate::util::{get_file_name, parse_param_and_compile, to_json, Param}; +use crate::util::{get_file_name, parse_param_and_compile, to_json, Param, self}; use crossbeam_channel::{select, unbounded, Receiver, Sender}; use indexmap::IndexSet; use lsp_server::{ReqQueue, Response}; use lsp_types::{ notification::{Notification, PublishDiagnostics}, Diagnostic, PublishDiagnosticsParams, + Location, }; use parking_lot::RwLock; use ra_ap_vfs::{FileId, Vfs}; @@ -67,6 +68,9 @@ pub(crate) struct LanguageServerState { /// The VFS loader pub vfs_handle: Box, + + /// The word index map + pub word_index: HashMap>, } /// A snapshot of the state of the language server @@ -78,6 +82,8 @@ pub(crate) struct LanguageServerSnapshot { pub db: HashMap, /// Documents that are currently kept in memory from the client pub opened_files: IndexSet, + /// The word index map + pub word_index: HashMap>, } #[allow(unused)] @@ -102,6 +108,7 @@ impl LanguageServerState { analysis: Analysis::default(), opened_files: IndexSet::new(), vfs_handle: handle, + word_index: HashMap::new(), } } @@ -245,6 +252,7 @@ impl LanguageServerState { vfs: self.vfs.clone(), db: self.analysis.db.clone(), opened_files: self.opened_files.clone(), + word_index: self.word_index.clone(), } } diff --git a/kclvm/tools/src/LSP/src/test_data/find_refs_test/kcl.mod b/kclvm/tools/src/LSP/src/test_data/find_refs_test/kcl.mod new file mode 100644 index 000000000..e69de29bb diff --git a/kclvm/tools/src/LSP/src/test_data/find_refs_test/main.k b/kclvm/tools/src/LSP/src/test_data/find_refs_test/main.k new file mode 100644 index 000000000..bf5812c28 --- /dev/null +++ b/kclvm/tools/src/LSP/src/test_data/find_refs_test/main.k @@ -0,0 +1,3 @@ +a = 1 +b = a +c = a \ No newline at end of file diff --git a/kclvm/tools/src/LSP/src/util.rs b/kclvm/tools/src/LSP/src/util.rs index e179d53a6..1e819eab5 100644 --- a/kclvm/tools/src/LSP/src/util.rs +++ b/kclvm/tools/src/LSP/src/util.rs @@ -16,7 +16,7 @@ use kclvm_sema::resolver::resolve_program_with_opts; use kclvm_sema::resolver::scope::ProgramScope; use kclvm_sema::resolver::scope::Scope; use kclvm_utils::pkgpath::rm_external_pkg_name; -use lsp_types::Url; +use lsp_types::{Location, Range, Position, Url}; use parking_lot::{RwLock, RwLockReadGuard}; use ra_ap_vfs::{FileId, Vfs}; use serde::{de::DeserializeOwned, Serialize}; @@ -24,6 +24,7 @@ use std::cell::RefCell; use std::path::{Path, PathBuf}; use std::rc::Rc; use std::{fs, sync::Arc}; +use std::collections::HashMap; use crate::from_lsp; @@ -744,3 +745,165 @@ pub(crate) fn get_pkg_scope( .borrow() .clone() } + +/// scan and build a word -> Locations index map +pub fn build_word_index(path: String)-> anyhow::Result>> { + let mut index: HashMap> = HashMap::new(); + if let Ok(files) = get_kcl_files(path.clone(), true) { + for file_path in &files { + // str path to url + if let Ok(url) = Url::from_file_path(file_path) { + // read file content and save the word to word index + let text = read_file(file_path).unwrap(); + let lines: Vec<&str> = text.lines().collect(); + for (li, line) in lines.into_iter().enumerate() { + let words = line_to_words(line.to_string()); + for (key, values) in words { + index.entry(key) + .or_insert_with(Vec::new) + .extend(values.iter().map(|w| { + Location { uri: url.clone(), range: Range { start: Position::new(li as u32, w.start_col), end: Position::new(li as u32, w.end_col) } } + })); + } + } + } + } + } + return Ok(index) +} + +// Word describes an arbitrary word in a certain line including +// start position, end position and the word itself. +#[derive(Debug, PartialEq, Eq, Clone)] +pub struct Word { + start_col: u32, + end_col: u32, + word: String, +} + +impl Word { + fn new(start_col: u32, end_col: u32, word: String) -> Self { + Self { + start_col, + end_col, + word + } + } +} + +fn read_file(path: &String) -> anyhow::Result { + let text = std::fs::read_to_string(path)?; + Ok(text) +} + +// Split one line into identifier words. +fn line_to_words(text: String) -> HashMap> { + let mut result = HashMap::new(); + let mut chars: Vec = text.chars().collect(); + chars.push('\n'); + let mut start_pos = usize::MAX; + let mut continue_pos = usize::MAX - 1; // avoid overflow + let mut prev_word = false; + let mut words: Vec = vec![]; + for (i, ch) in chars.iter().enumerate() { + let is_id_start = rustc_lexer::is_id_start(*ch); + let is_id_continue = rustc_lexer::is_id_continue(*ch); + // If the character is valid identfier start and the previous character is not valid identifier continue, mark the start position. + if is_id_start && !prev_word { + start_pos = i; + } + match is_id_continue { + true => { + // Continue searching for the end position. + if start_pos != usize::MAX { + continue_pos = i; + } + } + false => { + // Find out the end position. + if continue_pos + 1 == i { + words.push(Word::new(start_pos as u32,i as u32,chars[start_pos..i].iter().collect::().clone())); + } + // Reset the start position. + start_pos = usize::MAX; + } + } + prev_word = is_id_continue; + } + + for w in words { + result.entry(w.word.clone()).or_insert(vec![w.clone()]); + } + result +} + +#[cfg(test)] +mod tests { + use super::{line_to_words,build_word_index, Word}; + // todo assert + #[test] + fn test_build_word_index() { + let result = build_word_index("/Users/amy/work/open/catalog".to_string()); + println!("{:?}", result) + } + + // todo assert + #[test] + fn test_line_to_words() { + let datas = [ + "alice_first_name = \"alice\"", + "0lice_first_name = \"alic0\"", + "alice = p.Parent { name: \"alice\" }", + ]; + let expect = vec![ + vec![ + Word { + start_col: 0, + end_col: 16, + word: "alice_first_name".to_string(), + }, + Word { + start_col: 20, + end_col: 25, + word: "alice".to_string(), + }, + ], + vec![Word { + start_col: 20, + end_col: 25, + word: "alic0".to_string(), + }], + vec![ + Word { + start_col: 0, + end_col: 5, + word: "alice".to_string(), + }, + Word { + start_col: 8, + end_col: 9, + word: "p".to_string(), + }, + Word { + start_col: 10, + end_col: 16, + word: "Parent".to_string(), + }, + Word { + start_col: 19, + end_col: 23, + word: "name".to_string(), + }, + Word { + start_col: 26, + end_col: 31, + word: "alice".to_string(), + }, + ], + ]; + for i in 0..datas.len() { + // assert_eq!(line_to_words(datas[i].to_string()), expect[i].clone()); + let _ = line_to_words(datas[i].to_string()); + } + } +} From 4636aa6dd0fac09ee7697efbe29f4440bb8f798a Mon Sep 17 00:00:00 2001 From: "xiarui.xr" Date: Tue, 19 Sep 2023 09:09:05 +0800 Subject: [PATCH 02/11] find refs: add unit test; fix kcl_pos bug Signed-off-by: xiarui.xr --- kclvm/Cargo.lock | 4 +- kclvm/parser/src/lib.rs | 2 +- kclvm/tools/src/LSP/src/capabilities.rs | 1 + kclvm/tools/src/LSP/src/find_refs.rs | 326 ++++++++++++------ kclvm/tools/src/LSP/src/from_lsp.rs | 6 +- kclvm/tools/src/LSP/src/goto_def.rs | 1 - kclvm/tools/src/LSP/src/main.rs | 2 +- kclvm/tools/src/LSP/src/notification.rs | 5 +- kclvm/tools/src/LSP/src/request.rs | 55 ++- kclvm/tools/src/LSP/src/state.rs | 5 +- .../LSP/src/test_data/find_refs_test/main.k | 16 +- kclvm/tools/src/LSP/src/tests.rs | 83 +++++ kclvm/tools/src/LSP/src/util.rs | 29 +- 13 files changed, 395 insertions(+), 140 deletions(-) diff --git a/kclvm/Cargo.lock b/kclvm/Cargo.lock index 25201206c..f83b0d867 100644 --- a/kclvm/Cargo.lock +++ b/kclvm/Cargo.lock @@ -1964,9 +1964,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.6.3" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" diff --git a/kclvm/parser/src/lib.rs b/kclvm/parser/src/lib.rs index 84df8580e..eed2eb690 100644 --- a/kclvm/parser/src/lib.rs +++ b/kclvm/parser/src/lib.rs @@ -313,7 +313,7 @@ impl Loader { fn _load_main(&mut self) -> Result { let compile_entries = get_compile_entries_from_paths(&self.paths, &self.opts)?; let mut pkgs = HashMap::new(); - let workdir = compile_entries.get_root_path().to_string();// get package root + let workdir = compile_entries.get_root_path().to_string(); debug_assert_eq!(compile_entries.len(), self.paths.len()); diff --git a/kclvm/tools/src/LSP/src/capabilities.rs b/kclvm/tools/src/LSP/src/capabilities.rs index 801bcd91a..ed5c707b9 100644 --- a/kclvm/tools/src/LSP/src/capabilities.rs +++ b/kclvm/tools/src/LSP/src/capabilities.rs @@ -38,6 +38,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti ), document_formatting_provider: Some(OneOf::Left(true)), document_range_formatting_provider: Some(OneOf::Left(true)), + references_provider: Some(OneOf::Left(true)), ..Default::default() } } diff --git a/kclvm/tools/src/LSP/src/find_refs.rs b/kclvm/tools/src/LSP/src/find_refs.rs index e8c8ee300..a5828f47a 100644 --- a/kclvm/tools/src/LSP/src/find_refs.rs +++ b/kclvm/tools/src/LSP/src/find_refs.rs @@ -1,116 +1,244 @@ +use crate::from_lsp::kcl_pos; +use crate::goto_def::goto_definition; +use crate::util::{build_word_index, parse_param_and_compile, Param}; use anyhow; -use std::collections::HashMap; -use crate::{ - util::{build_word_index, parse_param_and_compile, Param}, - state::{LanguageServerSnapshot, Task, log_message}, - from_lsp::{self, file_path_from_url, kcl_pos}, - goto_def::{goto_definition, find_def,}, -}; -use lsp_types; -use crossbeam_channel::Sender; use kclvm_config::modfile::get_pkg_root; -use kclvm_ast::ast::Stmt; +use lsp_types::Location; - -pub(crate) fn find_references ( - snapshot: LanguageServerSnapshot, - params: lsp_types::ReferenceParams, - sender: Sender, -) -> anyhow::Result>> { - // 1. find definition of current token - let file = file_path_from_url(¶ms.text_document_position.text_document.uri)?; - let path = from_lsp::abs_path(¶ms.text_document_position.text_document.uri)?; - let db = snapshot.get_db(&path.clone().into())?; - let pos = kcl_pos(&file, params.text_document_position.position); - - if let Some(def_resp) = goto_definition(&db.prog, &pos, &db.scope) { - match def_resp { - lsp_types::GotoDefinitionResponse::Scalar(def_loc) => { - // get the def location - if let Some(def_name) = match db.prog.pos_to_stmt(&pos) { - Some(node) => match node.node { - Stmt::Import(_) => None, - _ => match find_def(node.clone(), &pos, &db.scope) { - Some(def) => Some(def.get_name()), - None => None, - }, - }, - None => None, - } { - // 2. find all occurrence of current token - // todo: decide the scope by the workspace root and the kcl.mod both, use the narrower scope - if let Some(root) = get_pkg_root(path.display().to_string().as_str()) { - match build_word_index(root) { - Ok(word_index) => { - return find_refs(def_loc, def_name, word_index); - }, - Err(_) => { - let _ = log_message("build word index failed".to_string(), &sender); - return anyhow::Ok(None); - } - } - } else { - return Ok(None) - } +pub(crate) fn find_refs Result<(), anyhow::Error>>( + def_loc: Location, + name: String, + cursor_path: String, + logger: F, +) -> anyhow::Result>> { + // todo: decide the scope by the workspace root and the kcl.mod both, use the narrower scope + // todo: should use the current file path + if let Some(root) = get_pkg_root(def_loc.uri.path()) { + match build_word_index(root) { + std::result::Result::Ok(word_index) => { + if let Some(locs) = word_index.get(name.as_str()).cloned() { + return anyhow::Ok(Some( + locs.into_iter() + .filter(|ref_loc| { + // from location to real def + // return if the real def location matches the def_loc + let file_path = ref_loc.uri.path().to_string(); + match parse_param_and_compile( + Param { + file: file_path.clone(), + }, + None, + ) { + Ok((prog, scope, _)) => { + let ref_pos = kcl_pos(&file_path, ref_loc.range.start); + // find def from the ref_pos + if let Some(real_def) = + goto_definition(&prog, &ref_pos, &scope) + { + match real_def { + lsp_types::GotoDefinitionResponse::Scalar( + real_def_loc, + ) => real_def_loc == def_loc, + _ => false, + } + } else { + false + } + } + Err(_) => { + let _ = logger(format!("{cursor_path} compilation failed")); + return false; + } + } + }) + .collect(), + )); + } else { + return Ok(None); } - }, - _=> return Ok(None), + } + Err(_) => { + logger("build word index failed".to_string())?; + return Ok(None); + } } } else { - log_message("Definition item not found, result in no reference".to_string(), &sender)?; + return Ok(None); } - - return Ok(None) } -pub(crate) fn find_refs(def_loc:lsp_types::Location, name: String, word_index: HashMap>) --> anyhow::Result>>{ - if let Some(locs) = word_index.get(name.as_str()).cloned() { - return anyhow::Ok(Some(locs.into_iter().filter(|ref_loc|{ - // from location to real def - // return if the real def location matches the def_loc - let file_path = ref_loc.uri.path().to_string(); - match parse_param_and_compile( - Param { - file: file_path.clone(), - }, - None, - ) { - Ok((prog, scope, _)) => { - let ref_pos = kcl_pos(&file_path, ref_loc.range.start); - // find def from the ref_pos - if let Some(real_def) = goto_definition(&prog, &ref_pos, &scope) { - match real_def { - lsp_types::GotoDefinitionResponse::Scalar(real_def_loc) => { - real_def_loc == def_loc - }, - _ => false - } - } else { - false - } +#[cfg(test)] +mod tests { + use super::find_refs; + use lsp_types::{Location, Position, Range}; + use proc_macro_crate::bench_test; + use std::{path::PathBuf, vec}; + + fn logger(msg: String) -> Result<(), anyhow::Error> { + println!("{}", msg); + anyhow::Ok(()) + } + fn check_locations_match(expect: Vec, actual: anyhow::Result>>) { + match actual { + Ok(act) => { + if let Some(locations) = act { + assert_eq!(expect, locations) + } else { + assert!(false, "got empty result. expect: {:?}", expect) } - Err(_) => { - // todo log compilation error - return false; - }, } - }).collect())); - } else { - return Ok(None) + Err(_) => assert!(false), + } } - -} -#[cfg(test)] -mod tests { - //todo - // todo assert #[test] - fn test_find_refs() { - + #[bench_test] + fn find_refs_from_variable_test() { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let mut path = root.clone(); + path.push("src/test_data/find_refs_test/main.k"); + let path = path.to_str().unwrap(); + match lsp_types::Url::from_file_path(path) { + Ok(url) => { + let def_loc = Location { + uri: url.clone(), + range: Range { + start: Position::new(0, 0), + end: Position::new(0, 1), + }, + }; + let expect = vec![ + Location { + uri: url.clone(), + range: Range { + start: Position::new(0, 0), + end: Position::new(0, 1), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(1, 4), + end: Position::new(1, 5), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(2, 4), + end: Position::new(2, 5), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(12, 14), + end: Position::new(12, 15), + }, + }, + ]; + check_locations_match( + expect, + find_refs(def_loc, "a".to_string(), path.to_string(), logger), + ); + } + Err(_) => assert!(false, "file not found"), + } } - -} \ No newline at end of file + #[test] + #[bench_test] + fn find_refs_from_schema_name_test() { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let mut path = root.clone(); + path.push("src/test_data/find_refs_test/main.k"); + let path = path.to_str().unwrap(); + match lsp_types::Url::from_file_path(path) { + Ok(url) => { + let def_loc = Location { + uri: url.clone(), + range: Range { + start: Position::new(4, 0), + end: Position::new(7, 0), + }, + }; + let expect = vec![ + Location { + uri: url.clone(), + range: Range { + start: Position::new(4, 7), + end: Position::new(4, 11), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(8, 4), + end: Position::new(8, 8), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(11, 7), + end: Position::new(11, 11), + }, + }, + ]; + check_locations_match( + expect, + find_refs(def_loc, "Name".to_string(), path.to_string(), logger), + ); + } + Err(_) => assert!(false, "file not found"), + } + } + #[test] + #[bench_test] + fn find_refs_from_schema_name_test() { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let mut path = root.clone(); + path.push("src/test_data/find_refs_test/main.k"); + let path = path.to_str().unwrap(); + match lsp_types::Url::from_file_path(path) { + Ok(url) => { + let def_loc = Location { + uri: url.clone(), + range: Range { + start: Position::new(4, 0), + end: Position::new(7, 0), + }, + }; + let expect = vec![ + Location { + uri: url.clone(), + range: Range { + start: Position::new(4, 7), + end: Position::new(4, 11), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(8, 4), + end: Position::new(8, 8), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(11, 7), + end: Position::new(11, 11), + }, + }, + ]; + check_locations_match( + expect, + find_refs(def_loc, "Name".to_string(), path.to_string(), logger), + ); + } + Err(_) => assert!(false, "file not found"), + } + } +} diff --git a/kclvm/tools/src/LSP/src/from_lsp.rs b/kclvm/tools/src/LSP/src/from_lsp.rs index 09f103810..eab37b973 100644 --- a/kclvm/tools/src/LSP/src/from_lsp.rs +++ b/kclvm/tools/src/LSP/src/from_lsp.rs @@ -19,11 +19,7 @@ pub(crate) fn kcl_pos(file: &str, pos: Position) -> KCLPos { KCLPos { filename: file.to_string(), line: (pos.line + 1) as u64, - column: if pos.character == 0 { - None - } else { - Some(pos.character as u64) - }, + column: Some(pos.character as u64), } } diff --git a/kclvm/tools/src/LSP/src/goto_def.rs b/kclvm/tools/src/LSP/src/goto_def.rs index ad641ef5f..af8e7ff24 100644 --- a/kclvm/tools/src/LSP/src/goto_def.rs +++ b/kclvm/tools/src/LSP/src/goto_def.rs @@ -107,7 +107,6 @@ impl Definition { } } - pub(crate) fn find_def( node: Node, kcl_pos: &KCLPos, diff --git a/kclvm/tools/src/LSP/src/main.rs b/kclvm/tools/src/LSP/src/main.rs index 76153e92f..6c87df6f4 100644 --- a/kclvm/tools/src/LSP/src/main.rs +++ b/kclvm/tools/src/LSP/src/main.rs @@ -8,9 +8,9 @@ mod config; mod db; mod dispatcher; mod document_symbol; +mod find_refs; mod from_lsp; mod goto_def; -mod find_refs; mod hover; mod main_loop; mod notification; diff --git a/kclvm/tools/src/LSP/src/notification.rs b/kclvm/tools/src/LSP/src/notification.rs index b86c46e55..8b4d2761b 100644 --- a/kclvm/tools/src/LSP/src/notification.rs +++ b/kclvm/tools/src/LSP/src/notification.rs @@ -1,5 +1,8 @@ use lsp_types::notification::{ - DidChangeTextDocument, DidChangeWatchedFiles, DidCloseTextDocument, DidOpenTextDocument, + DidChangeTextDocument, + DidChangeWatchedFiles, + DidCloseTextDocument, + DidOpenTextDocument, DidSaveTextDocument, //Initialized, }; diff --git a/kclvm/tools/src/LSP/src/request.rs b/kclvm/tools/src/LSP/src/request.rs index 4e41b9964..e694c74a3 100644 --- a/kclvm/tools/src/LSP/src/request.rs +++ b/kclvm/tools/src/LSP/src/request.rs @@ -1,6 +1,7 @@ use anyhow::Ok; use crossbeam_channel::Sender; -use lsp_types::{TextEdit, Location}; +use kclvm_ast::ast::Stmt; +use lsp_types::{Location, TextEdit}; use ra_ap_vfs::VfsPath; use std::time::Instant; @@ -9,10 +10,10 @@ use crate::{ db::AnalysisDatabase, dispatcher::RequestDispatcher, document_symbol::document_symbol, + find_refs::find_refs, formatting::format, from_lsp::{self, file_path_from_url, kcl_pos}, - goto_def::goto_definition, - find_refs::find_references, + goto_def::{find_def, goto_definition}, hover, quick_fix, state::{log_message, LanguageServerSnapshot, LanguageServerState, Task}, }; @@ -72,16 +73,6 @@ impl LanguageServerSnapshot { } } -// pub(crate) fn handle_initialize( -// _snapshot: LanguageServerSnapshot, -// params: lsp_types::InitializeParams, -// _sender: Sender -// ) -> anyhow::Result{ -// if let Some(uri) = params.root_uri { -// self.word_index = build_word_index(uri.path().to_string()) -// } -// } - pub(crate) fn handle_formatting( _snapshot: LanguageServerSnapshot, params: lsp_types::DocumentFormattingParams, @@ -146,12 +137,46 @@ pub(crate) fn handle_goto_definition( } /// Called when a `FindReferences` request was received -pub(crate) fn handle_reference ( +pub(crate) fn handle_reference( snapshot: LanguageServerSnapshot, params: lsp_types::ReferenceParams, sender: Sender, ) -> anyhow::Result>> { - find_references(snapshot, params, sender) + // 1. find definition of current token + let file = file_path_from_url(¶ms.text_document_position.text_document.uri)?; + let path = from_lsp::abs_path(¶ms.text_document_position.text_document.uri)?; + let db = snapshot.get_db(&path.clone().into())?; + let pos = kcl_pos(&file, params.text_document_position.position); + + let log = |msg: String| log_message(msg, &sender); + + if let Some(def_resp) = goto_definition(&db.prog, &pos, &db.scope) { + match def_resp { + lsp_types::GotoDefinitionResponse::Scalar(def_loc) => { + // get the def location + if let Some(def_name) = match db.prog.pos_to_stmt(&pos) { + Some(node) => match node.node { + Stmt::Import(_) => None, + _ => match find_def(node.clone(), &pos, &db.scope) { + Some(def) => Some(def.get_name()), + None => None, + }, + }, + None => None, + } { + return find_refs(def_loc, def_name, file, log); + } + } + _ => return Ok(None), + } + } else { + log_message( + "Definition item not found, result in no reference".to_string(), + &sender, + )?; + } + + return Ok(None); } /// Called when a `Completion` request was received. diff --git a/kclvm/tools/src/LSP/src/state.rs b/kclvm/tools/src/LSP/src/state.rs index 20ec3dec0..e64a485dd 100644 --- a/kclvm/tools/src/LSP/src/state.rs +++ b/kclvm/tools/src/LSP/src/state.rs @@ -2,14 +2,13 @@ use crate::analysis::Analysis; use crate::config::Config; use crate::db::AnalysisDatabase; use crate::to_lsp::{kcl_diag_to_lsp_diags, url}; -use crate::util::{get_file_name, parse_param_and_compile, to_json, Param, self}; +use crate::util::{self, get_file_name, parse_param_and_compile, to_json, Param}; use crossbeam_channel::{select, unbounded, Receiver, Sender}; use indexmap::IndexSet; use lsp_server::{ReqQueue, Response}; use lsp_types::{ notification::{Notification, PublishDiagnostics}, - Diagnostic, PublishDiagnosticsParams, - Location, + Diagnostic, Location, PublishDiagnosticsParams, }; use parking_lot::RwLock; use ra_ap_vfs::{FileId, Vfs}; diff --git a/kclvm/tools/src/LSP/src/test_data/find_refs_test/main.k b/kclvm/tools/src/LSP/src/test_data/find_refs_test/main.k index bf5812c28..ad2c3f8d9 100644 --- a/kclvm/tools/src/LSP/src/test_data/find_refs_test/main.k +++ b/kclvm/tools/src/LSP/src/test_data/find_refs_test/main.k @@ -1,3 +1,15 @@ -a = 1 +a = "demo" b = a -c = a \ No newline at end of file +c = a + +schema Name: + name: str + +schema Person: + n: Name + +p2 = Person { + n: Name{ + name: a + } +} \ No newline at end of file diff --git a/kclvm/tools/src/LSP/src/tests.rs b/kclvm/tools/src/LSP/src/tests.rs index dca11158d..8dccd981d 100644 --- a/kclvm/tools/src/LSP/src/tests.rs +++ b/kclvm/tools/src/LSP/src/tests.rs @@ -17,6 +17,8 @@ use lsp_types::HoverContents; use lsp_types::HoverParams; use lsp_types::MarkedString; use lsp_types::PublishDiagnosticsParams; +use lsp_types::ReferenceContext; +use lsp_types::ReferenceParams; use lsp_types::TextDocumentIdentifier; use lsp_types::TextDocumentItem; use lsp_types::TextDocumentPositionParams; @@ -1303,3 +1305,84 @@ fn lsp_invalid_subcommand_test() { }, } } + +#[test] +fn test_find_refs() { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let mut path = root.clone(); + path.push("src/test_data/find_refs_test/main.k"); + + let path = path.to_str().unwrap(); + let src = std::fs::read_to_string(path.clone()).unwrap(); + let server = Project {}.server(); + let url = Url::from_file_path(path).unwrap(); + + // Mock open file + server.notification::( + lsp_types::DidOpenTextDocumentParams { + text_document: TextDocumentItem { + uri: url.clone(), + language_id: "KCL".to_string(), + version: 0, + text: src, + }, + }, + ); + + let id = server.next_request_id.get(); + server.next_request_id.set(id.wrapping_add(1)); + + let r: Request = Request::new( + id.into(), + "textDocument/references".to_string(), + ReferenceParams { + text_document_position: TextDocumentPositionParams { + text_document: TextDocumentIdentifier { uri: url.clone() }, + position: Position::new(0, 1), + }, + work_done_progress_params: Default::default(), + partial_result_params: Default::default(), + context: ReferenceContext { + include_declaration: true, + }, + }, + ); + + // Send request and wait for it's response + let res = server.send_and_receive(r); + + assert_eq!( + res.result.unwrap(), + to_json(vec![ + Location { + uri: url.clone(), + range: Range { + start: Position::new(0, 0), + end: Position::new(0, 1), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(1, 4), + end: Position::new(1, 5), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(2, 4), + end: Position::new(2, 5), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(12, 14), + end: Position::new(12, 15), + }, + }, + ]) + .unwrap() + ); +} diff --git a/kclvm/tools/src/LSP/src/util.rs b/kclvm/tools/src/LSP/src/util.rs index 1e819eab5..75729003a 100644 --- a/kclvm/tools/src/LSP/src/util.rs +++ b/kclvm/tools/src/LSP/src/util.rs @@ -16,15 +16,15 @@ use kclvm_sema::resolver::resolve_program_with_opts; use kclvm_sema::resolver::scope::ProgramScope; use kclvm_sema::resolver::scope::Scope; use kclvm_utils::pkgpath::rm_external_pkg_name; -use lsp_types::{Location, Range, Position, Url}; +use lsp_types::{Location, Position, Range, Url}; use parking_lot::{RwLock, RwLockReadGuard}; use ra_ap_vfs::{FileId, Vfs}; use serde::{de::DeserializeOwned, Serialize}; use std::cell::RefCell; +use std::collections::HashMap; use std::path::{Path, PathBuf}; use std::rc::Rc; use std::{fs, sync::Arc}; -use std::collections::HashMap; use crate::from_lsp; @@ -747,7 +747,7 @@ pub(crate) fn get_pkg_scope( } /// scan and build a word -> Locations index map -pub fn build_word_index(path: String)-> anyhow::Result>> { +pub fn build_word_index(path: String) -> anyhow::Result>> { let mut index: HashMap> = HashMap::new(); if let Ok(files) = get_kcl_files(path.clone(), true) { for file_path in &files { @@ -759,17 +759,22 @@ pub fn build_word_index(path: String)-> anyhow::Result HashMap> { false => { // Find out the end position. if continue_pos + 1 == i { - words.push(Word::new(start_pos as u32,i as u32,chars[start_pos..i].iter().collect::().clone())); + words.push(Word::new( + start_pos as u32, + i as u32, + chars[start_pos..i].iter().collect::().clone(), + )); } // Reset the start position. start_pos = usize::MAX; @@ -839,7 +848,7 @@ fn line_to_words(text: String) -> HashMap> { #[cfg(test)] mod tests { - use super::{line_to_words,build_word_index, Word}; + use super::{build_word_index, line_to_words, Word}; // todo assert #[test] fn test_build_word_index() { From 511b1e8adb401fb753bd56fc4028bda8a396279b Mon Sep 17 00:00:00 2001 From: "xiarui.xr" Date: Tue, 19 Sep 2023 22:02:31 +0800 Subject: [PATCH 03/11] find references: add more tests Signed-off-by: xiarui.xr --- kclvm/tools/src/LSP/src/find_refs.rs | 53 +-- .../LSP/src/test_data/find_refs_test/main.k | 2 +- kclvm/tools/src/LSP/src/util.rs | 349 +++++++++++++++--- 3 files changed, 297 insertions(+), 107 deletions(-) diff --git a/kclvm/tools/src/LSP/src/find_refs.rs b/kclvm/tools/src/LSP/src/find_refs.rs index a5828f47a..e8413be42 100644 --- a/kclvm/tools/src/LSP/src/find_refs.rs +++ b/kclvm/tools/src/LSP/src/find_refs.rs @@ -72,7 +72,7 @@ mod tests { use super::find_refs; use lsp_types::{Location, Position, Range}; use proc_macro_crate::bench_test; - use std::{path::PathBuf, vec}; + use std::path::PathBuf; fn logger(msg: String) -> Result<(), anyhow::Error> { println!("{}", msg); @@ -174,55 +174,8 @@ mod tests { Location { uri: url.clone(), range: Range { - start: Position::new(8, 4), - end: Position::new(8, 8), - }, - }, - Location { - uri: url.clone(), - range: Range { - start: Position::new(11, 7), - end: Position::new(11, 11), - }, - }, - ]; - check_locations_match( - expect, - find_refs(def_loc, "Name".to_string(), path.to_string(), logger), - ); - } - Err(_) => assert!(false, "file not found"), - } - } - #[test] - #[bench_test] - fn find_refs_from_schema_name_test() { - let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let mut path = root.clone(); - path.push("src/test_data/find_refs_test/main.k"); - let path = path.to_str().unwrap(); - match lsp_types::Url::from_file_path(path) { - Ok(url) => { - let def_loc = Location { - uri: url.clone(), - range: Range { - start: Position::new(4, 0), - end: Position::new(7, 0), - }, - }; - let expect = vec![ - Location { - uri: url.clone(), - range: Range { - start: Position::new(4, 7), - end: Position::new(4, 11), - }, - }, - Location { - uri: url.clone(), - range: Range { - start: Position::new(8, 4), - end: Position::new(8, 8), + start: Position::new(8, 7), + end: Position::new(8, 11), }, }, Location { diff --git a/kclvm/tools/src/LSP/src/test_data/find_refs_test/main.k b/kclvm/tools/src/LSP/src/test_data/find_refs_test/main.k index ad2c3f8d9..496868958 100644 --- a/kclvm/tools/src/LSP/src/test_data/find_refs_test/main.k +++ b/kclvm/tools/src/LSP/src/test_data/find_refs_test/main.k @@ -6,7 +6,7 @@ schema Name: name: str schema Person: - n: Name + n: Name p2 = Person { n: Name{ diff --git a/kclvm/tools/src/LSP/src/util.rs b/kclvm/tools/src/LSP/src/util.rs index 75729003a..45c7b3e23 100644 --- a/kclvm/tools/src/LSP/src/util.rs +++ b/kclvm/tools/src/LSP/src/util.rs @@ -841,7 +841,7 @@ fn line_to_words(text: String) -> HashMap> { } for w in words { - result.entry(w.word.clone()).or_insert(vec![w.clone()]); + result.entry(w.word.clone()).or_insert(Vec::new()).push(w); } result } @@ -849,70 +849,307 @@ fn line_to_words(text: String) -> HashMap> { #[cfg(test)] mod tests { use super::{build_word_index, line_to_words, Word}; - // todo assert + use lsp_types::{Location, Position, Range}; + use std::collections::HashMap; + use std::path::PathBuf; #[test] fn test_build_word_index() { - let result = build_word_index("/Users/amy/work/open/catalog".to_string()); - println!("{:?}", result) + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let mut path = root.clone(); + path.push("src/test_data/find_refs_test/main.k"); + + let url = lsp_types::Url::from_file_path(path.clone()).unwrap(); + let path = path.to_str().unwrap(); + let expect: HashMap> = vec![ + ( + "a".to_string(), + vec![ + Location { + uri: url.clone(), + range: Range { + start: Position::new(0, 0), + end: Position::new(0, 1), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(1, 4), + end: Position::new(1, 5), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(2, 4), + end: Position::new(2, 5), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(12, 14), + end: Position::new(12, 15), + }, + }, + ], + ), + ( + "c".to_string(), + vec![Location { + uri: url.clone(), + range: Range { + start: Position::new(2, 0), + end: Position::new(2, 1), + }, + }], + ), + ( + "b".to_string(), + vec![Location { + uri: url.clone(), + range: Range { + start: Position::new(1, 0), + end: Position::new(1, 1), + }, + }], + ), + ( + "n".to_string(), + vec![ + Location { + uri: url.clone(), + range: Range { + start: Position::new(8, 4), + end: Position::new(8, 5), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(11, 4), + end: Position::new(11, 5), + }, + }, + ], + ), + ( + "schema".to_string(), + vec![ + Location { + uri: url.clone(), + range: Range { + start: Position::new(4, 0), + end: Position::new(4, 6), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(7, 0), + end: Position::new(7, 6), + }, + }, + ], + ), + ( + "b".to_string(), + vec![Location { + uri: url.clone(), + range: Range { + start: Position::new(1, 0), + end: Position::new(1, 1), + }, + }], + ), + ( + "Name".to_string(), + vec![ + Location { + uri: url.clone(), + range: Range { + start: Position::new(4, 7), + end: Position::new(4, 11), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(8, 7), + end: Position::new(8, 11), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(11, 7), + end: Position::new(11, 11), + }, + }, + ], + ), + ( + "name".to_string(), + vec![ + Location { + uri: url.clone(), + range: Range { + start: Position::new(5, 4), + end: Position::new(5, 8), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(12, 8), + end: Position::new(12, 12), + }, + }, + ], + ), + ( + "demo".to_string(), + vec![Location { + uri: url.clone(), + range: Range { + start: Position::new(0, 5), + end: Position::new(0, 9), + }, + }], + ), + ( + "str".to_string(), + vec![Location { + uri: url.clone(), + range: Range { + start: Position::new(5, 10), + end: Position::new(5, 13), + }, + }], + ), + ( + "Person".to_string(), + vec![ + Location { + uri: url.clone(), + range: Range { + start: Position::new(7, 7), + end: Position::new(7, 13), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(10, 5), + end: Position::new(10, 11), + }, + }, + ], + ), + ( + "p2".to_string(), + vec![Location { + uri: url.clone(), + range: Range { + start: Position::new(10, 0), + end: Position::new(10, 2), + }, + }], + ), + ] + .into_iter() + .collect(); + match build_word_index(path.to_string()) { + Ok(actual) => { + assert_eq!(expect, actual) + } + Err(_) => assert!(false, "build word index failed. expect: {:?}", expect), + } } - // todo assert #[test] fn test_line_to_words() { - let datas = [ - "alice_first_name = \"alice\"", - "0lice_first_name = \"alic0\"", - "alice = p.Parent { name: \"alice\" }", - ]; - let expect = vec![ + let lines = ["schema Person:", "name. name again", "some_word word !word"]; + + let expects: Vec>> = vec![ vec![ - Word { - start_col: 0, - end_col: 16, - word: "alice_first_name".to_string(), - }, - Word { - start_col: 20, - end_col: 25, - word: "alice".to_string(), - }, - ], - vec![Word { - start_col: 20, - end_col: 25, - word: "alic0".to_string(), - }], + ( + "schema".to_string(), + vec![Word { + start_col: 0, + end_col: 6, + word: "schema".to_string(), + }], + ), + ( + "Person".to_string(), + vec![Word { + start_col: 7, + end_col: 13, + word: "Person".to_string(), + }], + ), + ] + .into_iter() + .collect(), vec![ - Word { - start_col: 0, - end_col: 5, - word: "alice".to_string(), - }, - Word { - start_col: 8, - end_col: 9, - word: "p".to_string(), - }, - Word { - start_col: 10, - end_col: 16, - word: "Parent".to_string(), - }, - Word { - start_col: 19, - end_col: 23, - word: "name".to_string(), - }, - Word { - start_col: 26, - end_col: 31, - word: "alice".to_string(), - }, - ], + ( + "name".to_string(), + vec![ + Word { + start_col: 0, + end_col: 4, + word: "name".to_string(), + }, + Word { + start_col: 6, + end_col: 10, + word: "name".to_string(), + }, + ], + ), + ( + "again".to_string(), + vec![Word { + start_col: 11, + end_col: 16, + word: "again".to_string(), + }], + ), + ] + .into_iter() + .collect(), + vec![ + ( + "some_word".to_string(), + vec![Word { + start_col: 0, + end_col: 9, + word: "some_word".to_string(), + }], + ), + ( + "word".to_string(), + vec![ + Word { + start_col: 10, + end_col: 14, + word: "word".to_string(), + }, + Word { + start_col: 16, + end_col: 20, + word: "word".to_string(), + }, + ], + ), + ] + .into_iter() + .collect(), ]; - for i in 0..datas.len() { - // assert_eq!(line_to_words(datas[i].to_string()), expect[i].clone()); - let _ = line_to_words(datas[i].to_string()); + for i in 0..lines.len() { + let got = line_to_words(lines[i].to_string()); + assert_eq!(expects[i], got) } } } From 2e8bfc9229c05679fd6be6d1d9766ab5005b20ea Mon Sep 17 00:00:00 2001 From: "xiarui.xr" Date: Wed, 20 Sep 2023 10:02:24 +0800 Subject: [PATCH 04/11] remove unused comment Signed-off-by: xiarui.xr --- kclvm/tools/src/LSP/src/notification.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kclvm/tools/src/LSP/src/notification.rs b/kclvm/tools/src/LSP/src/notification.rs index 8b4d2761b..078cb67f7 100644 --- a/kclvm/tools/src/LSP/src/notification.rs +++ b/kclvm/tools/src/LSP/src/notification.rs @@ -3,7 +3,7 @@ use lsp_types::notification::{ DidChangeWatchedFiles, DidCloseTextDocument, DidOpenTextDocument, - DidSaveTextDocument, //Initialized, + DidSaveTextDocument, }; use crate::{ From a73af53db6ab9323fcb408fab7894ba4efb1e9e5 Mon Sep 17 00:00:00 2001 From: "xiarui.xr" Date: Wed, 20 Sep 2023 10:18:59 +0800 Subject: [PATCH 05/11] chore: cargo fmt Signed-off-by: xiarui.xr --- kclvm/tools/src/LSP/src/notification.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/kclvm/tools/src/LSP/src/notification.rs b/kclvm/tools/src/LSP/src/notification.rs index 078cb67f7..1098b1b09 100644 --- a/kclvm/tools/src/LSP/src/notification.rs +++ b/kclvm/tools/src/LSP/src/notification.rs @@ -1,8 +1,5 @@ use lsp_types::notification::{ - DidChangeTextDocument, - DidChangeWatchedFiles, - DidCloseTextDocument, - DidOpenTextDocument, + DidChangeTextDocument, DidChangeWatchedFiles, DidCloseTextDocument, DidOpenTextDocument, DidSaveTextDocument, }; From 864baec718b410b8229b72c09b606fd29a460137 Mon Sep 17 00:00:00 2001 From: "xiarui.xr" Date: Wed, 20 Sep 2023 11:49:54 +0800 Subject: [PATCH 06/11] find refs: revert bench test temporarily Signed-off-by: xiarui.xr --- kclvm/tools/src/LSP/src/find_refs.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/kclvm/tools/src/LSP/src/find_refs.rs b/kclvm/tools/src/LSP/src/find_refs.rs index e8413be42..c3e190053 100644 --- a/kclvm/tools/src/LSP/src/find_refs.rs +++ b/kclvm/tools/src/LSP/src/find_refs.rs @@ -71,7 +71,6 @@ pub(crate) fn find_refs Result<(), anyhow::Error>>( mod tests { use super::find_refs; use lsp_types::{Location, Position, Range}; - use proc_macro_crate::bench_test; use std::path::PathBuf; fn logger(msg: String) -> Result<(), anyhow::Error> { @@ -93,7 +92,6 @@ mod tests { } #[test] - #[bench_test] fn find_refs_from_variable_test() { let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let mut path = root.clone(); @@ -148,7 +146,6 @@ mod tests { } #[test] - #[bench_test] fn find_refs_from_schema_name_test() { let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let mut path = root.clone(); From a736fefef3ec02d56ebe37e4ba4449f96e28d485 Mon Sep 17 00:00:00 2001 From: "xiarui.xr" Date: Thu, 21 Sep 2023 14:45:41 +0800 Subject: [PATCH 07/11] minor fix Signed-off-by: xiarui.xr --- kclvm/tools/src/LSP/src/goto_def.rs | 19 +++++++++++-------- kclvm/tools/src/LSP/src/request.rs | 1 - kclvm/tools/src/LSP/src/util.rs | 2 +- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/kclvm/tools/src/LSP/src/goto_def.rs b/kclvm/tools/src/LSP/src/goto_def.rs index af8e7ff24..a7b5a7dd9 100644 --- a/kclvm/tools/src/LSP/src/goto_def.rs +++ b/kclvm/tools/src/LSP/src/goto_def.rs @@ -302,14 +302,17 @@ pub(crate) fn resolve_var( let ty = get_system_member_function_ty(&name, &func_name); match &ty.kind { kclvm_sema::ty::TypeKind::Function(func_ty) => { - return Some(Definition::Object(ScopeObject { - name: func_name.clone(), - start: func_name_node.get_pos(), - end: func_name_node.get_end_pos(), - ty: ty.clone(), - kind: ScopeObjectKind::FunctionCall, - doc: Some(func_ty.doc.clone()), - }, func_name)) + return Some(Definition::Object( + ScopeObject { + name: func_name.clone(), + start: func_name_node.get_pos(), + end: func_name_node.get_end_pos(), + ty: ty.clone(), + kind: ScopeObjectKind::FunctionCall, + doc: Some(func_ty.doc.clone()), + }, + func_name, + )) } _ => return None, } diff --git a/kclvm/tools/src/LSP/src/request.rs b/kclvm/tools/src/LSP/src/request.rs index e694c74a3..1d4a3112d 100644 --- a/kclvm/tools/src/LSP/src/request.rs +++ b/kclvm/tools/src/LSP/src/request.rs @@ -44,7 +44,6 @@ impl LanguageServerState { state.shutdown_requested = true; Ok(()) })? - // .on::(handle_initialize)? .on::(handle_goto_definition)? .on::(handle_reference)? .on::(handle_completion)? diff --git a/kclvm/tools/src/LSP/src/util.rs b/kclvm/tools/src/LSP/src/util.rs index 45c7b3e23..53ab11618 100644 --- a/kclvm/tools/src/LSP/src/util.rs +++ b/kclvm/tools/src/LSP/src/util.rs @@ -754,7 +754,7 @@ pub fn build_word_index(path: String) -> anyhow::Result = text.lines().collect(); for (li, line) in lines.into_iter().enumerate() { let words = line_to_words(line.to_string()); From ad36882e0fbce83488b22343051f35f9b30ff847 Mon Sep 17 00:00:00 2001 From: "xiarui.xr" Date: Sat, 7 Oct 2023 15:03:01 +0800 Subject: [PATCH 08/11] minor fix: use if-else instead of match statement Signed-off-by: xiarui.xr --- kclvm/tools/src/LSP/src/util.rs | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/kclvm/tools/src/LSP/src/util.rs b/kclvm/tools/src/LSP/src/util.rs index 53ab11618..6976d9d9e 100644 --- a/kclvm/tools/src/LSP/src/util.rs +++ b/kclvm/tools/src/LSP/src/util.rs @@ -817,25 +817,22 @@ fn line_to_words(text: String) -> HashMap> { if is_id_start && !prev_word { start_pos = i; } - match is_id_continue { - true => { - // Continue searching for the end position. - if start_pos != usize::MAX { - continue_pos = i; - } + if is_id_continue { + // Continue searching for the end position. + if start_pos != usize::MAX { + continue_pos = i; } - false => { - // Find out the end position. - if continue_pos + 1 == i { - words.push(Word::new( - start_pos as u32, - i as u32, - chars[start_pos..i].iter().collect::().clone(), - )); - } - // Reset the start position. - start_pos = usize::MAX; + } else { + // Find out the end position. + if continue_pos + 1 == i { + words.push(Word::new( + start_pos as u32, + i as u32, + chars[start_pos..i].iter().collect::().clone(), + )); } + // Reset the start position. + start_pos = usize::MAX; } prev_word = is_id_continue; } From c1261ea5ceb217a6d479c942d8d59ea285fb3f49 Mon Sep 17 00:00:00 2001 From: "xiarui.xr" Date: Sat, 7 Oct 2023 17:55:17 +0800 Subject: [PATCH 09/11] lsp: cache word index in lsp state Signed-off-by: xiarui.xr --- kclvm/tools/src/LSP/src/find_refs.rs | 105 +++++++++++++-------------- kclvm/tools/src/LSP/src/main.rs | 5 +- kclvm/tools/src/LSP/src/main_loop.rs | 5 +- kclvm/tools/src/LSP/src/request.rs | 3 +- kclvm/tools/src/LSP/src/state.rs | 31 ++++++-- kclvm/tools/src/LSP/src/tests.rs | 30 +++++--- 6 files changed, 103 insertions(+), 76 deletions(-) diff --git a/kclvm/tools/src/LSP/src/find_refs.rs b/kclvm/tools/src/LSP/src/find_refs.rs index c3e190053..52fddf571 100644 --- a/kclvm/tools/src/LSP/src/find_refs.rs +++ b/kclvm/tools/src/LSP/src/find_refs.rs @@ -1,11 +1,12 @@ use crate::from_lsp::kcl_pos; use crate::goto_def::goto_definition; -use crate::util::{build_word_index, parse_param_and_compile, Param}; +use crate::util::{parse_param_and_compile, Param}; use anyhow; -use kclvm_config::modfile::get_pkg_root; use lsp_types::Location; +use std::collections::HashMap; pub(crate) fn find_refs Result<(), anyhow::Error>>( + word_index_map: HashMap>>, def_loc: Location, name: String, cursor_path: String, @@ -13,65 +14,59 @@ pub(crate) fn find_refs Result<(), anyhow::Error>>( ) -> anyhow::Result>> { // todo: decide the scope by the workspace root and the kcl.mod both, use the narrower scope // todo: should use the current file path - if let Some(root) = get_pkg_root(def_loc.uri.path()) { - match build_word_index(root) { - std::result::Result::Ok(word_index) => { - if let Some(locs) = word_index.get(name.as_str()).cloned() { - return anyhow::Ok(Some( - locs.into_iter() - .filter(|ref_loc| { - // from location to real def - // return if the real def location matches the def_loc - let file_path = ref_loc.uri.path().to_string(); - match parse_param_and_compile( - Param { - file: file_path.clone(), - }, - None, - ) { - Ok((prog, scope, _)) => { - let ref_pos = kcl_pos(&file_path, ref_loc.range.start); - // find def from the ref_pos - if let Some(real_def) = - goto_definition(&prog, &ref_pos, &scope) - { - match real_def { - lsp_types::GotoDefinitionResponse::Scalar( - real_def_loc, - ) => real_def_loc == def_loc, - _ => false, - } - } else { - false - } - } - Err(_) => { - let _ = logger(format!("{cursor_path} compilation failed")); - return false; - } + + let mut ref_locations = vec![]; + + for (_, word_index) in word_index_map { + if let Some(locs) = word_index.get(name.as_str()).cloned() { + let matched_locs: Vec = locs.into_iter() + .filter(|ref_loc| { + // from location to real def + // return if the real def location matches the def_loc + let file_path = ref_loc.uri.path().to_string(); + match parse_param_and_compile( + Param { + file: file_path.clone(), + }, + None, + ) { + Ok((prog, scope, _)) => { + let ref_pos = kcl_pos(&file_path, ref_loc.range.start); + // find def from the ref_pos + if let Some(real_def) = + goto_definition(&prog, &ref_pos, &scope) + { + match real_def { + lsp_types::GotoDefinitionResponse::Scalar( + real_def_loc, + ) => real_def_loc == def_loc, + _ => false, } - }) - .collect(), - )); - } else { - return Ok(None); - } - } - Err(_) => { - logger("build word index failed".to_string())?; - return Ok(None); - } + } else { + false + } + } + Err(_) => { + let _ = logger(format!("{cursor_path} compilation failed")); + return false; + } + } + }) + .collect(); + ref_locations.extend(matched_locs); } - } else { - return Ok(None); } + anyhow::Ok(Some(ref_locations)) } #[cfg(test)] mod tests { use super::find_refs; use lsp_types::{Location, Position, Range}; + use std::ops::Index; use std::path::PathBuf; + use std::collections::HashMap; + use crate::util::build_word_index; fn logger(msg: String) -> Result<(), anyhow::Error> { println!("{}", msg); @@ -91,6 +86,10 @@ mod tests { } } + fn setup_word_index_map(root: &str) -> HashMap>> { + HashMap::from([("default".to_string(), build_word_index(root.to_string()).unwrap())]) + } + #[test] fn find_refs_from_variable_test() { let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); @@ -138,7 +137,7 @@ mod tests { ]; check_locations_match( expect, - find_refs(def_loc, "a".to_string(), path.to_string(), logger), + find_refs(setup_word_index_map(path), def_loc, "a".to_string(), path.to_string(), logger), ); } Err(_) => assert!(false, "file not found"), @@ -185,7 +184,7 @@ mod tests { ]; check_locations_match( expect, - find_refs(def_loc, "Name".to_string(), path.to_string(), logger), + find_refs(setup_word_index_map(path), def_loc, "Name".to_string(), path.to_string(), logger), ); } Err(_) => assert!(false, "file not found"), diff --git a/kclvm/tools/src/LSP/src/main.rs b/kclvm/tools/src/LSP/src/main.rs index 6c87df6f4..62e576694 100644 --- a/kclvm/tools/src/LSP/src/main.rs +++ b/kclvm/tools/src/LSP/src/main.rs @@ -1,6 +1,8 @@ use crate::main_loop::main_loop; use config::Config; use main_loop::app; +use std::collections::HashMap; + mod analysis; mod capabilities; mod completion; @@ -78,9 +80,8 @@ fn run_server() -> anyhow::Result<()> { .map_err(|_| anyhow::anyhow!("Initialize result error"))?; connection.initialize_finish(initialize_id, initialize_result)?; - let config = Config::default(); - main_loop(connection, config)?; + main_loop(connection, config, initialize_params)?; io_threads.join()?; Ok(()) } diff --git a/kclvm/tools/src/LSP/src/main_loop.rs b/kclvm/tools/src/LSP/src/main_loop.rs index 4e2efddcb..d3ea01386 100644 --- a/kclvm/tools/src/LSP/src/main_loop.rs +++ b/kclvm/tools/src/LSP/src/main_loop.rs @@ -2,11 +2,12 @@ use crate::config::Config; use crate::state::LanguageServerState; use clap::Command; use lsp_server::Connection; +use lsp_types::InitializeParams; #[allow(dead_code)] /// Runs the main loop of the language server. This will receive requests and handle them. -pub(crate) fn main_loop(connection: Connection, config: Config) -> anyhow::Result<()> { - LanguageServerState::new(connection.sender, config).run(connection.receiver) +pub(crate) fn main_loop(connection: Connection, config: Config, initialize_params: InitializeParams) -> anyhow::Result<()> { + LanguageServerState::new(connection.sender, config, initialize_params).run(connection.receiver) } #[allow(dead_code)] diff --git a/kclvm/tools/src/LSP/src/request.rs b/kclvm/tools/src/LSP/src/request.rs index 1d4a3112d..6f386f973 100644 --- a/kclvm/tools/src/LSP/src/request.rs +++ b/kclvm/tools/src/LSP/src/request.rs @@ -146,6 +146,7 @@ pub(crate) fn handle_reference( let path = from_lsp::abs_path(¶ms.text_document_position.text_document.uri)?; let db = snapshot.get_db(&path.clone().into())?; let pos = kcl_pos(&file, params.text_document_position.position); + let word_index_map = snapshot.word_index_map.clone(); let log = |msg: String| log_message(msg, &sender); @@ -163,7 +164,7 @@ pub(crate) fn handle_reference( }, None => None, } { - return find_refs(def_loc, def_name, file, log); + return find_refs(word_index_map, def_loc, def_name, file, log); } } _ => return Ok(None), diff --git a/kclvm/tools/src/LSP/src/state.rs b/kclvm/tools/src/LSP/src/state.rs index e64a485dd..c51365826 100644 --- a/kclvm/tools/src/LSP/src/state.rs +++ b/kclvm/tools/src/LSP/src/state.rs @@ -2,13 +2,14 @@ use crate::analysis::Analysis; use crate::config::Config; use crate::db::AnalysisDatabase; use crate::to_lsp::{kcl_diag_to_lsp_diags, url}; -use crate::util::{self, get_file_name, parse_param_and_compile, to_json, Param}; +use crate::util::{get_file_name, parse_param_and_compile, to_json, Param, build_word_index}; use crossbeam_channel::{select, unbounded, Receiver, Sender}; use indexmap::IndexSet; use lsp_server::{ReqQueue, Response}; use lsp_types::{ notification::{Notification, PublishDiagnostics}, Diagnostic, Location, PublishDiagnosticsParams, + InitializeParams, }; use parking_lot::RwLock; use ra_ap_vfs::{FileId, Vfs}; @@ -69,7 +70,7 @@ pub(crate) struct LanguageServerState { pub vfs_handle: Box, /// The word index map - pub word_index: HashMap>, + pub word_index_map: HashMap>>, } /// A snapshot of the state of the language server @@ -82,19 +83,35 @@ pub(crate) struct LanguageServerSnapshot { /// Documents that are currently kept in memory from the client pub opened_files: IndexSet, /// The word index map - pub word_index: HashMap>, + pub word_index_map: HashMap>>, } #[allow(unused)] impl LanguageServerState { - pub fn new(sender: Sender, config: Config) -> Self { + pub fn new(sender: Sender, config: Config, initialize_params: InitializeParams) -> Self { let (task_sender, task_receiver) = unbounded::(); let (vfs_sender, receiver) = unbounded::(); let handle: NotifyHandle = ra_ap_vfs::loader::Handle::spawn(Box::new(move |msg| vfs_sender.send(msg).unwrap())); let handle = Box::new(handle) as Box; - + + // build word index for all the workspace folders + // todo: async + let mut word_index_map = HashMap::new(); + if let Some(workspace_folders) = initialize_params.workspace_folders { + for folder in workspace_folders { + let path = folder.uri.path(); + if let Ok(word_index) = build_word_index(path.to_string()) { + word_index_map.insert(folder.name, word_index); + } + } + } else if let Some(root_uri) = initialize_params.root_uri { + let path = root_uri.path(); + if let Ok(word_index) = build_word_index(path.to_string()) { + word_index_map.insert("default".to_string(), word_index); + } + } LanguageServerState { sender, request_queue: ReqQueue::default(), @@ -107,7 +124,7 @@ impl LanguageServerState { analysis: Analysis::default(), opened_files: IndexSet::new(), vfs_handle: handle, - word_index: HashMap::new(), + word_index_map: word_index_map, } } @@ -251,7 +268,7 @@ impl LanguageServerState { vfs: self.vfs.clone(), db: self.analysis.db.clone(), opened_files: self.opened_files.clone(), - word_index: self.word_index.clone(), + word_index_map: self.word_index_map.clone(), } } diff --git a/kclvm/tools/src/LSP/src/tests.rs b/kclvm/tools/src/LSP/src/tests.rs index 8dccd981d..d8cc6b53c 100644 --- a/kclvm/tools/src/LSP/src/tests.rs +++ b/kclvm/tools/src/LSP/src/tests.rs @@ -23,6 +23,10 @@ use lsp_types::TextDocumentIdentifier; use lsp_types::TextDocumentItem; use lsp_types::TextDocumentPositionParams; use lsp_types::TextEdit; +use lsp_types::InitializeParams; +use lsp_types::WorkspaceFolder; +use lsp_types::Url; + use serde::Serialize; use std::cell::Cell; use std::cell::RefCell; @@ -43,7 +47,6 @@ use lsp_types::DiagnosticRelatedInformation; use lsp_types::DiagnosticSeverity; use lsp_types::Location; use lsp_types::NumberOrString; -use lsp_types::Url; use lsp_types::{Position, Range, TextDocumentContentChangeEvent}; use parking_lot::RwLock; use proc_macro_crate::bench_test; @@ -415,9 +418,9 @@ pub struct Project {} impl Project { /// Instantiates a language server for this project. - pub fn server(self) -> Server { + pub fn server(self, initialize_params: InitializeParams) -> Server { let config = Config::default(); - Server::new(config) + Server::new(config, initialize_params) } } @@ -432,11 +435,11 @@ pub struct Server { impl Server { /// Constructs and initializes a new `Server` - pub fn new(config: Config) -> Self { + pub fn new(config: Config, initialize_params: InitializeParams) -> Self { let (connection, client) = Connection::memory(); let worker = std::thread::spawn(move || { - main_loop(connection, config).unwrap(); + main_loop(connection, config, initialize_params).unwrap(); }); Self { @@ -539,7 +542,7 @@ fn notification_test() { let path = path.to_str().unwrap(); let src = std::fs::read_to_string(path.clone()).unwrap(); - let server = Project {}.server(); + let server = Project {}.server(InitializeParams::default()); // Mock open file server.notification::( @@ -588,7 +591,7 @@ fn goto_def_test() { let path = path.to_str().unwrap(); let src = std::fs::read_to_string(path.clone()).unwrap(); - let server = Project {}.server(); + let server = Project {}.server(InitializeParams::default()); // Mock open file server.notification::( @@ -645,7 +648,7 @@ fn complete_test() { let path = path.to_str().unwrap(); let src = std::fs::read_to_string(path.clone()).unwrap(); - let server = Project {}.server(); + let server = Project {}.server(InitializeParams::default()); // Mock open file server.notification::( @@ -709,7 +712,7 @@ fn hover_test() { let path = path.to_str().unwrap(); let src = std::fs::read_to_string(path.clone()).unwrap(); - let server = Project {}.server(); + let server = Project {}.server(InitializeParams::default()); // Mock open file server.notification::( @@ -821,7 +824,7 @@ fn formatting_test() { let path = path.to_str().unwrap(); let src = std::fs::read_to_string(path.clone()).unwrap(); - let server = Project {}.server(); + let server = Project {}.server(InitializeParams::default()); // Mock open file server.notification::( @@ -1314,7 +1317,12 @@ fn test_find_refs() { let path = path.to_str().unwrap(); let src = std::fs::read_to_string(path.clone()).unwrap(); - let server = Project {}.server(); + let mut initialize_params = InitializeParams::default(); + initialize_params.workspace_folders = Some(vec![WorkspaceFolder{ + uri: Url::from_file_path(root.clone()).unwrap(), + name: "test".to_string(), + }]); + let server = Project {}.server(initialize_params); let url = Url::from_file_path(path).unwrap(); // Mock open file From 55040b779867345e3fb441b2665fb1345357721c Mon Sep 17 00:00:00 2001 From: "xiarui.xr" Date: Mon, 9 Oct 2023 14:12:50 +0800 Subject: [PATCH 10/11] chore: format code Signed-off-by: xiarui.xr --- kclvm/tools/src/LSP/src/find_refs.rs | 38 +++++++++++++++++++--------- kclvm/tools/src/LSP/src/main_loop.rs | 6 ++++- kclvm/tools/src/LSP/src/state.rs | 15 ++++++----- kclvm/tools/src/LSP/src/tests.rs | 6 ++--- 4 files changed, 43 insertions(+), 22 deletions(-) diff --git a/kclvm/tools/src/LSP/src/find_refs.rs b/kclvm/tools/src/LSP/src/find_refs.rs index 52fddf571..21a207406 100644 --- a/kclvm/tools/src/LSP/src/find_refs.rs +++ b/kclvm/tools/src/LSP/src/find_refs.rs @@ -19,7 +19,8 @@ pub(crate) fn find_refs Result<(), anyhow::Error>>( for (_, word_index) in word_index_map { if let Some(locs) = word_index.get(name.as_str()).cloned() { - let matched_locs: Vec = locs.into_iter() + let matched_locs: Vec = locs + .into_iter() .filter(|ref_loc| { // from location to real def // return if the real def location matches the def_loc @@ -33,13 +34,11 @@ pub(crate) fn find_refs Result<(), anyhow::Error>>( Ok((prog, scope, _)) => { let ref_pos = kcl_pos(&file_path, ref_loc.range.start); // find def from the ref_pos - if let Some(real_def) = - goto_definition(&prog, &ref_pos, &scope) - { + if let Some(real_def) = goto_definition(&prog, &ref_pos, &scope) { match real_def { - lsp_types::GotoDefinitionResponse::Scalar( - real_def_loc, - ) => real_def_loc == def_loc, + lsp_types::GotoDefinitionResponse::Scalar(real_def_loc) => { + real_def_loc == def_loc + } _ => false, } } else { @@ -62,11 +61,11 @@ pub(crate) fn find_refs Result<(), anyhow::Error>>( #[cfg(test)] mod tests { use super::find_refs; + use crate::util::build_word_index; use lsp_types::{Location, Position, Range}; + use std::collections::HashMap; use std::ops::Index; use std::path::PathBuf; - use std::collections::HashMap; - use crate::util::build_word_index; fn logger(msg: String) -> Result<(), anyhow::Error> { println!("{}", msg); @@ -87,7 +86,10 @@ mod tests { } fn setup_word_index_map(root: &str) -> HashMap>> { - HashMap::from([("default".to_string(), build_word_index(root.to_string()).unwrap())]) + HashMap::from([( + "default".to_string(), + build_word_index(root.to_string()).unwrap(), + )]) } #[test] @@ -137,7 +139,13 @@ mod tests { ]; check_locations_match( expect, - find_refs(setup_word_index_map(path), def_loc, "a".to_string(), path.to_string(), logger), + find_refs( + setup_word_index_map(path), + def_loc, + "a".to_string(), + path.to_string(), + logger, + ), ); } Err(_) => assert!(false, "file not found"), @@ -184,7 +192,13 @@ mod tests { ]; check_locations_match( expect, - find_refs(setup_word_index_map(path), def_loc, "Name".to_string(), path.to_string(), logger), + find_refs( + setup_word_index_map(path), + def_loc, + "Name".to_string(), + path.to_string(), + logger, + ), ); } Err(_) => assert!(false, "file not found"), diff --git a/kclvm/tools/src/LSP/src/main_loop.rs b/kclvm/tools/src/LSP/src/main_loop.rs index d3ea01386..b0f8b16c1 100644 --- a/kclvm/tools/src/LSP/src/main_loop.rs +++ b/kclvm/tools/src/LSP/src/main_loop.rs @@ -6,7 +6,11 @@ use lsp_types::InitializeParams; #[allow(dead_code)] /// Runs the main loop of the language server. This will receive requests and handle them. -pub(crate) fn main_loop(connection: Connection, config: Config, initialize_params: InitializeParams) -> anyhow::Result<()> { +pub(crate) fn main_loop( + connection: Connection, + config: Config, + initialize_params: InitializeParams, +) -> anyhow::Result<()> { LanguageServerState::new(connection.sender, config, initialize_params).run(connection.receiver) } diff --git a/kclvm/tools/src/LSP/src/state.rs b/kclvm/tools/src/LSP/src/state.rs index c51365826..cccf948ad 100644 --- a/kclvm/tools/src/LSP/src/state.rs +++ b/kclvm/tools/src/LSP/src/state.rs @@ -2,14 +2,13 @@ use crate::analysis::Analysis; use crate::config::Config; use crate::db::AnalysisDatabase; use crate::to_lsp::{kcl_diag_to_lsp_diags, url}; -use crate::util::{get_file_name, parse_param_and_compile, to_json, Param, build_word_index}; +use crate::util::{build_word_index, get_file_name, parse_param_and_compile, to_json, Param}; use crossbeam_channel::{select, unbounded, Receiver, Sender}; use indexmap::IndexSet; use lsp_server::{ReqQueue, Response}; use lsp_types::{ notification::{Notification, PublishDiagnostics}, - Diagnostic, Location, PublishDiagnosticsParams, - InitializeParams, + Diagnostic, InitializeParams, Location, PublishDiagnosticsParams, }; use parking_lot::RwLock; use ra_ap_vfs::{FileId, Vfs}; @@ -88,14 +87,18 @@ pub(crate) struct LanguageServerSnapshot { #[allow(unused)] impl LanguageServerState { - pub fn new(sender: Sender, config: Config, initialize_params: InitializeParams) -> Self { + pub fn new( + sender: Sender, + config: Config, + initialize_params: InitializeParams, + ) -> Self { let (task_sender, task_receiver) = unbounded::(); let (vfs_sender, receiver) = unbounded::(); let handle: NotifyHandle = ra_ap_vfs::loader::Handle::spawn(Box::new(move |msg| vfs_sender.send(msg).unwrap())); let handle = Box::new(handle) as Box; - + // build word index for all the workspace folders // todo: async let mut word_index_map = HashMap::new(); @@ -124,7 +127,7 @@ impl LanguageServerState { analysis: Analysis::default(), opened_files: IndexSet::new(), vfs_handle: handle, - word_index_map: word_index_map, + word_index_map, } } diff --git a/kclvm/tools/src/LSP/src/tests.rs b/kclvm/tools/src/LSP/src/tests.rs index d8cc6b53c..57c5f06a3 100644 --- a/kclvm/tools/src/LSP/src/tests.rs +++ b/kclvm/tools/src/LSP/src/tests.rs @@ -15,6 +15,7 @@ use lsp_types::GotoDefinitionResponse; use lsp_types::Hover; use lsp_types::HoverContents; use lsp_types::HoverParams; +use lsp_types::InitializeParams; use lsp_types::MarkedString; use lsp_types::PublishDiagnosticsParams; use lsp_types::ReferenceContext; @@ -23,9 +24,8 @@ use lsp_types::TextDocumentIdentifier; use lsp_types::TextDocumentItem; use lsp_types::TextDocumentPositionParams; use lsp_types::TextEdit; -use lsp_types::InitializeParams; -use lsp_types::WorkspaceFolder; use lsp_types::Url; +use lsp_types::WorkspaceFolder; use serde::Serialize; use std::cell::Cell; @@ -1318,7 +1318,7 @@ fn test_find_refs() { let path = path.to_str().unwrap(); let src = std::fs::read_to_string(path.clone()).unwrap(); let mut initialize_params = InitializeParams::default(); - initialize_params.workspace_folders = Some(vec![WorkspaceFolder{ + initialize_params.workspace_folders = Some(vec![WorkspaceFolder { uri: Url::from_file_path(root.clone()).unwrap(), name: "test".to_string(), }]); From eae36e245dddf7b79e15ab79df958b29336066c1 Mon Sep 17 00:00:00 2001 From: "xiarui.xr" Date: Thu, 12 Oct 2023 16:25:33 +0800 Subject: [PATCH 11/11] find refs: incrementally update word_index after file changes Signed-off-by: xiarui.xr --- kclvm/tools/src/LSP/src/find_refs.rs | 19 ++-- kclvm/tools/src/LSP/src/goto_def.rs | 19 ++-- kclvm/tools/src/LSP/src/notification.rs | 47 +++++++++- kclvm/tools/src/LSP/src/request.rs | 9 +- kclvm/tools/src/LSP/src/state.rs | 9 +- kclvm/tools/src/LSP/src/tests.rs | 101 +++++++++++++++++++- kclvm/tools/src/LSP/src/util.rs | 117 ++++++++++++++++++++---- 7 files changed, 278 insertions(+), 43 deletions(-) diff --git a/kclvm/tools/src/LSP/src/find_refs.rs b/kclvm/tools/src/LSP/src/find_refs.rs index 21a207406..38e2db1ad 100644 --- a/kclvm/tools/src/LSP/src/find_refs.rs +++ b/kclvm/tools/src/LSP/src/find_refs.rs @@ -2,11 +2,15 @@ use crate::from_lsp::kcl_pos; use crate::goto_def::goto_definition; use crate::util::{parse_param_and_compile, Param}; use anyhow; -use lsp_types::Location; +use lsp_types::{Location, Url}; +use parking_lot::RwLock; +use ra_ap_vfs::Vfs; use std::collections::HashMap; +use std::sync::Arc; pub(crate) fn find_refs Result<(), anyhow::Error>>( - word_index_map: HashMap>>, + vfs: Option>>, + word_index_map: HashMap>>, def_loc: Location, name: String, cursor_path: String, @@ -29,7 +33,7 @@ pub(crate) fn find_refs Result<(), anyhow::Error>>( Param { file: file_path.clone(), }, - None, + vfs.clone(), ) { Ok((prog, scope, _)) => { let ref_pos = kcl_pos(&file_path, ref_loc.range.start); @@ -62,9 +66,8 @@ pub(crate) fn find_refs Result<(), anyhow::Error>>( mod tests { use super::find_refs; use crate::util::build_word_index; - use lsp_types::{Location, Position, Range}; + use lsp_types::{Location, Position, Range, Url}; use std::collections::HashMap; - use std::ops::Index; use std::path::PathBuf; fn logger(msg: String) -> Result<(), anyhow::Error> { @@ -85,9 +88,9 @@ mod tests { } } - fn setup_word_index_map(root: &str) -> HashMap>> { + fn setup_word_index_map(root: &str) -> HashMap>> { HashMap::from([( - "default".to_string(), + Url::from_file_path(root).unwrap(), build_word_index(root.to_string()).unwrap(), )]) } @@ -140,6 +143,7 @@ mod tests { check_locations_match( expect, find_refs( + None, setup_word_index_map(path), def_loc, "a".to_string(), @@ -193,6 +197,7 @@ mod tests { check_locations_match( expect, find_refs( + None, setup_word_index_map(path), def_loc, "Name".to_string(), diff --git a/kclvm/tools/src/LSP/src/goto_def.rs b/kclvm/tools/src/LSP/src/goto_def.rs index a7b5a7dd9..c8dd2693e 100644 --- a/kclvm/tools/src/LSP/src/goto_def.rs +++ b/kclvm/tools/src/LSP/src/goto_def.rs @@ -350,14 +350,17 @@ pub(crate) fn resolve_var( if let Some(ty) = STRING_MEMBER_FUNCTIONS.get(&func_name) { match &ty.kind { kclvm_sema::ty::TypeKind::Function(func_ty) => { - return Some(Definition::Object(ScopeObject { - name: func_name, - start: func_name_node.get_pos(), - end: func_name_node.get_end_pos(), - ty: Rc::new(ty.clone()), - kind: ScopeObjectKind::FunctionCall, - doc: Some(func_ty.doc.clone()), - }, func_name)) + return Some(Definition::Object( + ScopeObject { + name: func_name.clone(), + start: func_name_node.get_pos(), + end: func_name_node.get_end_pos(), + ty: Rc::new(ty.clone()), + kind: ScopeObjectKind::FunctionCall, + doc: Some(func_ty.doc.clone()), + }, + func_name, + )) } // unreachable _ => {} diff --git a/kclvm/tools/src/LSP/src/notification.rs b/kclvm/tools/src/LSP/src/notification.rs index 1098b1b09..a4a279ec3 100644 --- a/kclvm/tools/src/LSP/src/notification.rs +++ b/kclvm/tools/src/LSP/src/notification.rs @@ -1,11 +1,18 @@ use lsp_types::notification::{ - DidChangeTextDocument, DidChangeWatchedFiles, DidCloseTextDocument, DidOpenTextDocument, - DidSaveTextDocument, + DidChangeTextDocument, + DidChangeWatchedFiles, + DidCloseTextDocument, + DidOpenTextDocument, + DidSaveTextDocument, //DidDeleteFiles, DidRenameFiles, DidCreateFiles, //todo more }; +use std::path::Path; use crate::{ - dispatcher::NotificationDispatcher, from_lsp, state::LanguageServerState, + dispatcher::NotificationDispatcher, + from_lsp, + state::LanguageServerState, util::apply_document_changes, + util::{build_word_index_for_file_content, word_index_add, word_index_subtract}, }; impl LanguageServerState { @@ -71,14 +78,46 @@ impl LanguageServerState { let path = from_lsp::abs_path(&text_document.uri)?; self.log_message(format!("on did_change file: {:?}", path)); + // update vfs let vfs = &mut *self.vfs.write(); let file_id = vfs .file_id(&path.clone().into()) .ok_or(anyhow::anyhow!("Already checked that the file_id exists!"))?; let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec())?; + let old_text = text.clone(); apply_document_changes(&mut text, content_changes); - vfs.set_file_contents(path.into(), Some(text.into_bytes())); + vfs.set_file_contents(path.into(), Some(text.clone().into_bytes())); + + // update word index + let old_word_index = build_word_index_for_file_content(old_text, &text_document.uri); + let new_word_index = build_word_index_for_file_content(text.clone(), &text_document.uri); + let binding = text_document.uri.path(); + let file_path = Path::new(binding); //todo rename + for (key, value) in &mut self.word_index_map { + let workspace_folder_path = Path::new(key.path()); + if file_path.starts_with(workspace_folder_path) { + word_index_subtract(value, old_word_index.clone()); + word_index_add(value, new_word_index.clone()); + } + } + // let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + // let url = lsp_types::Url::from_file_path(root.clone()).unwrap(); + // let mm = self.word_index_map.get(&url).unwrap(); + // println!("word_index_map: {:?}", mm); + + // let file = from_lsp::file_path_from_url(&text_document.uri)?; + // let old_word_index = build_word_index_for_file_content(old_text, &text_document.uri); + // let new_word_index = build_word_index_for_file_content(text.clone(), &text_document.uri); + + // let file_path = Path::new(&text_document.uri.path()); + // for (key, mut value) in &self.word_index_map { + // let workspace_folder_path = Path::new(key.path()); + // if file_path.starts_with(workspace_folder_path) { + // value = &word_index_subtract(value, old_word_index.clone()); + // value = &word_index_add(value, new_word_index.clone()); + // } + // } Ok(()) } diff --git a/kclvm/tools/src/LSP/src/request.rs b/kclvm/tools/src/LSP/src/request.rs index 6f386f973..4c24e3dfa 100644 --- a/kclvm/tools/src/LSP/src/request.rs +++ b/kclvm/tools/src/LSP/src/request.rs @@ -164,7 +164,14 @@ pub(crate) fn handle_reference( }, None => None, } { - return find_refs(word_index_map, def_loc, def_name, file, log); + return find_refs( + Some(snapshot.vfs), + word_index_map, + def_loc, + def_name, + file, + log, + ); } } _ => return Ok(None), diff --git a/kclvm/tools/src/LSP/src/state.rs b/kclvm/tools/src/LSP/src/state.rs index cccf948ad..5dd8e0b62 100644 --- a/kclvm/tools/src/LSP/src/state.rs +++ b/kclvm/tools/src/LSP/src/state.rs @@ -6,6 +6,7 @@ use crate::util::{build_word_index, get_file_name, parse_param_and_compile, to_j use crossbeam_channel::{select, unbounded, Receiver, Sender}; use indexmap::IndexSet; use lsp_server::{ReqQueue, Response}; +use lsp_types::Url; use lsp_types::{ notification::{Notification, PublishDiagnostics}, Diagnostic, InitializeParams, Location, PublishDiagnosticsParams, @@ -69,7 +70,7 @@ pub(crate) struct LanguageServerState { pub vfs_handle: Box, /// The word index map - pub word_index_map: HashMap>>, + pub word_index_map: HashMap>>, } /// A snapshot of the state of the language server @@ -82,7 +83,7 @@ pub(crate) struct LanguageServerSnapshot { /// Documents that are currently kept in memory from the client pub opened_files: IndexSet, /// The word index map - pub word_index_map: HashMap>>, + pub word_index_map: HashMap>>, } #[allow(unused)] @@ -106,13 +107,13 @@ impl LanguageServerState { for folder in workspace_folders { let path = folder.uri.path(); if let Ok(word_index) = build_word_index(path.to_string()) { - word_index_map.insert(folder.name, word_index); + word_index_map.insert(folder.uri, word_index); } } } else if let Some(root_uri) = initialize_params.root_uri { let path = root_uri.path(); if let Ok(word_index) = build_word_index(path.to_string()) { - word_index_map.insert("default".to_string(), word_index); + word_index_map.insert(root_uri, word_index); } } LanguageServerState { diff --git a/kclvm/tools/src/LSP/src/tests.rs b/kclvm/tools/src/LSP/src/tests.rs index 57c5f06a3..c2979ac11 100644 --- a/kclvm/tools/src/LSP/src/tests.rs +++ b/kclvm/tools/src/LSP/src/tests.rs @@ -771,7 +771,7 @@ fn hover_assign_in_lambda_test() { let path = path.to_str().unwrap(); let src = std::fs::read_to_string(path.clone()).unwrap(); - let server = Project {}.server(); + let server = Project {}.server(InitializeParams::default()); // Mock open file server.notification::( @@ -1310,7 +1310,7 @@ fn lsp_invalid_subcommand_test() { } #[test] -fn test_find_refs() { +fn find_refs_test() { let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let mut path = root.clone(); path.push("src/test_data/find_refs_test/main.k"); @@ -1394,3 +1394,100 @@ fn test_find_refs() { .unwrap() ); } + +#[test] +fn find_refs_with_file_change_test() { + let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let mut path = root.clone(); + path.push("src/test_data/find_refs_test/main.k"); + + let path = path.to_str().unwrap(); + let src = std::fs::read_to_string(path.clone()).unwrap(); + let mut initialize_params = InitializeParams::default(); + initialize_params.workspace_folders = Some(vec![WorkspaceFolder { + uri: Url::from_file_path(root.clone()).unwrap(), + name: "test".to_string(), + }]); + let server = Project {}.server(initialize_params); + let url = Url::from_file_path(path).unwrap(); + + // Mock open file + server.notification::( + lsp_types::DidOpenTextDocumentParams { + text_document: TextDocumentItem { + uri: url.clone(), + language_id: "KCL".to_string(), + version: 0, + text: src, + }, + }, + ); + // Mock change file content + server.notification::( + lsp_types::DidChangeTextDocumentParams { + text_document: lsp_types::VersionedTextDocumentIdentifier { + uri: url.clone(), + version: 1, + }, + content_changes: vec![lsp_types::TextDocumentContentChangeEvent { + range: None, + range_length: None, + text: r#"a = "demo" + +schema Name: + name: str + +schema Person: + n: Name + +p2 = Person { + n: Name{ + name: a + } +}"# + .to_string(), + }], + }, + ); + let id = server.next_request_id.get(); + server.next_request_id.set(id.wrapping_add(1)); + // Mock trigger find references + let r: Request = Request::new( + id.into(), + "textDocument/references".to_string(), + ReferenceParams { + text_document_position: TextDocumentPositionParams { + text_document: TextDocumentIdentifier { uri: url.clone() }, + position: Position::new(0, 1), + }, + work_done_progress_params: Default::default(), + partial_result_params: Default::default(), + context: ReferenceContext { + include_declaration: true, + }, + }, + ); + + // Send request and wait for it's response + let res = server.send_and_receive(r); + assert_eq!( + res.result.unwrap(), + to_json(vec![ + Location { + uri: url.clone(), + range: Range { + start: Position::new(0, 0), + end: Position::new(0, 1), + }, + }, + Location { + uri: url.clone(), + range: Range { + start: Position::new(10, 14), + end: Position::new(10, 15), + }, + }, + ]) + .unwrap() + ); +} diff --git a/kclvm/tools/src/LSP/src/util.rs b/kclvm/tools/src/LSP/src/util.rs index 6976d9d9e..3a8535450 100644 --- a/kclvm/tools/src/LSP/src/util.rs +++ b/kclvm/tools/src/LSP/src/util.rs @@ -755,21 +755,8 @@ pub fn build_word_index(path: String) -> anyhow::Result = text.lines().collect(); - for (li, line) in lines.into_iter().enumerate() { - let words = line_to_words(line.to_string()); - for (key, values) in words { - index - .entry(key) - .or_insert_with(Vec::new) - .extend(values.iter().map(|w| Location { - uri: url.clone(), - range: Range { - start: Position::new(li as u32, w.start_col), - end: Position::new(li as u32, w.end_col), - }, - })); - } + for (key, values) in build_word_index_for_file_content(text, &url) { + index.entry(key).or_insert_with(Vec::new).extend(values); } } } @@ -777,6 +764,52 @@ pub fn build_word_index(path: String) -> anyhow::Result HashMap> { + let mut index: HashMap> = HashMap::new(); + let lines: Vec<&str> = content.lines().collect(); + for (li, line) in lines.into_iter().enumerate() { + let words = line_to_words(line.to_string()); + for (key, values) in words { + index + .entry(key) + .or_insert_with(Vec::new) + .extend(values.iter().map(|w| Location { + uri: url.clone(), + range: Range { + start: Position::new(li as u32, w.start_col), + end: Position::new(li as u32, w.end_col), + }, + })); + } + } + index +} + +pub fn word_index_add( + from: &mut HashMap>, + add: HashMap>, +) { + for (key, value) in add { + from.entry(key).or_insert_with(Vec::new).extend(value); + } +} + +pub fn word_index_subtract( + from: &mut HashMap>, + remove: HashMap>, +) { + for (key, value) in remove { + for v in value { + from.entry(key.clone()).and_modify(|locations| { + locations.retain(|loc| loc != &v); + }); + } + } +} + // Word describes an arbitrary word in a certain line including // start position, end position and the word itself. #[derive(Debug, PartialEq, Eq, Clone)] @@ -845,8 +878,8 @@ fn line_to_words(text: String) -> HashMap> { #[cfg(test)] mod tests { - use super::{build_word_index, line_to_words, Word}; - use lsp_types::{Location, Position, Range}; + use super::{build_word_index, line_to_words, word_index_add, word_index_subtract, Word}; + use lsp_types::{Location, Position, Range, Url}; use std::collections::HashMap; use std::path::PathBuf; #[test] @@ -1064,6 +1097,56 @@ mod tests { } } + #[test] + fn test_word_index_add() { + let loc1 = Location { + uri: Url::parse("file:///path/to/file.k").unwrap(), + range: Range { + start: Position::new(0, 0), + end: Position::new(0, 4), + }, + }; + let loc2 = Location { + uri: Url::parse("file:///path/to/file.k").unwrap(), + range: Range { + start: Position::new(1, 0), + end: Position::new(1, 4), + }, + }; + let mut from = HashMap::from([("name".to_string(), vec![loc1.clone()])]); + let add = HashMap::from([("name".to_string(), vec![loc2.clone()])]); + word_index_add(&mut from, add); + assert_eq!( + from, + HashMap::from([("name".to_string(), vec![loc1.clone(), loc2.clone()],)]) + ); + } + + #[test] + fn test_word_index_subtract() { + let loc1 = Location { + uri: Url::parse("file:///path/to/file.k").unwrap(), + range: Range { + start: Position::new(0, 0), + end: Position::new(0, 4), + }, + }; + let loc2 = Location { + uri: Url::parse("file:///path/to/file.k").unwrap(), + range: Range { + start: Position::new(1, 0), + end: Position::new(1, 4), + }, + }; + let mut from = HashMap::from([("name".to_string(), vec![loc1.clone(), loc2.clone()])]); + let remove = HashMap::from([("name".to_string(), vec![loc2.clone()])]); + word_index_subtract(&mut from, remove); + assert_eq!( + from, + HashMap::from([("name".to_string(), vec![loc1.clone()],)]) + ); + } + #[test] fn test_line_to_words() { let lines = ["schema Person:", "name. name again", "some_word word !word"];