diff --git a/Cargo.lock b/Cargo.lock index fee539c78..a36ae52a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -257,9 +257,9 @@ dependencies = [ [[package]] name = "molc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ddf7a75ab924d95e6dcf73f24cdc820968008956a56ea76489a937be41ac7ba" +checksum = "76b41715c0dba18256b97ed045313b93baf8de0a665280220247390a7e801842" dependencies = [ "lsp-types", "serde", @@ -385,7 +385,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.37", ] [[package]] @@ -407,7 +407,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.33", + "syn 2.0.37", ] [[package]] @@ -442,9 +442,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "syn" @@ -459,9 +459,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.33" +version = "2.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" dependencies = [ "proc-macro2", "quote", diff --git a/crates/els/completion.rs b/crates/els/completion.rs index 6c1f43493..686455917 100644 --- a/crates/els/completion.rs +++ b/crates/els/completion.rs @@ -32,7 +32,7 @@ use lsp_types::{ }; use crate::server::{ELSResult, RedirectableStdout, Server}; -use crate::util::{self, NormalizedUrl}; +use crate::util::{self, loc_to_pos, NormalizedUrl}; fn comp_item_kind(vi: &VarInfo) -> CompletionItemKind { match &vi.t { @@ -51,20 +51,21 @@ fn comp_item_kind(vi: &VarInfo) -> CompletionItemKind { #[derive(Debug, PartialEq, Eq)] pub enum CompletionKind { + RetriggerLocal, Local, - Space, LParen, Method, + RetriggerMethod, // Colon, // :, Type ascription or private access `::` } impl CompletionKind { pub const fn should_be_local(&self) -> bool { - matches!(self, Self::Local | Self::Space | Self::LParen) + matches!(self, Self::RetriggerLocal | Self::Local | Self::LParen) } pub const fn should_be_method(&self) -> bool { - matches!(self, Self::Method) + matches!(self, Self::Method | Self::RetriggerMethod) } pub const fn _is_lparen(&self) -> bool { @@ -493,7 +494,7 @@ impl Server { self.send_log(format!("completion requested: {params:?}"))?; let uri = NormalizedUrl::new(params.text_document_position.text_document.uri); let path = util::uri_to_path(&uri); - let pos = params.text_document_position.position; + let mut pos = params.text_document_position.position; // ignore comments // TODO: multiline comments if self @@ -510,32 +511,39 @@ impl Server { let comp_kind = match trigger { Some(".") => CompletionKind::Method, Some(":") => CompletionKind::Method, - Some(" ") => CompletionKind::Space, + Some(" ") => CompletionKind::Local, Some("(") => CompletionKind::LParen, - _ => CompletionKind::Local, + _ => { + let offset = match self.file_cache.get_token(&uri, pos).map(|tk| tk.kind) { + Some(TokenKind::Newline | TokenKind::EOF) => -2, + _ => -1, + }; + let prev_token = self.file_cache.get_token_relatively(&uri, pos, offset); + match prev_token { + Some(prev) if matches!(prev.kind, Dot | DblColon) => { + if let Some(p) = loc_to_pos(prev.loc()) { + pos = p; + } + CompletionKind::RetriggerMethod + } + _ => CompletionKind::RetriggerLocal, + } + } }; self.send_log(format!("CompletionKind: {comp_kind:?}"))?; let mut result: Vec = vec![]; let mut already_appeared = Set::new(); let contexts = if comp_kind.should_be_local() { - let prev_token = self.file_cache.get_token_relatively(&uri, pos, -1); - match prev_token { - Some(prev) if matches!(prev.kind, Dot | DblColon) => { - let Some(dot_pos) = util::loc_to_pos(prev.loc()) else { - return Ok(None); - }; - self.get_receiver_ctxs(&uri, dot_pos)? - } - _ => self.get_local_ctx(&uri, pos), - } + self.get_local_ctx(&uri, pos) } else { self.get_receiver_ctxs(&uri, pos)? }; let offset = match comp_kind { - CompletionKind::Local => 0, + CompletionKind::RetriggerLocal => 0, CompletionKind::Method => -1, - CompletionKind::Space => -1, + CompletionKind::Local => -1, CompletionKind::LParen => 0, + CompletionKind::RetriggerMethod => -1, }; let arg_pt = self .get_min_expr(&uri, pos, offset) @@ -547,7 +555,7 @@ impl Server { let nth = nth + additional; sig_t.non_default_params()?.get(nth).cloned() } - other if comp_kind == CompletionKind::Space => { + other if comp_kind == CompletionKind::Local => { match other.show_acc().as_deref() { Some("import") => { let insert = other diff --git a/crates/els/server.rs b/crates/els/server.rs index f08e7e83b..3c7c5e580 100644 --- a/crates/els/server.rs +++ b/crates/els/server.rs @@ -918,6 +918,11 @@ impl Server { self.shared.clear(&path); } + #[allow(unused)] + pub fn get_file_cache(&self) -> &FileCache { + &self.file_cache + } + pub fn remove_module_entry(&mut self, uri: &NormalizedUrl) -> Option { let path = uri.to_file_path().ok()?; self.shared.mod_cache.remove(&path) diff --git a/crates/els/tests/retrigger.er b/crates/els/tests/retrigger.er new file mode 100644 index 000000000..f4fd10713 --- /dev/null +++ b/crates/els/tests/retrigger.er @@ -0,0 +1,4 @@ +l = [1, 2] +for! l, i => + pri + print! i.bi diff --git a/crates/els/tests/test.rs b/crates/els/tests/test.rs index f8f9f8a15..d5924c989 100644 --- a/crates/els/tests/test.rs +++ b/crates/els/tests/test.rs @@ -2,18 +2,18 @@ use std::path::Path; use lsp_types::{ CompletionResponse, DiagnosticSeverity, DocumentSymbolResponse, FoldingRange, FoldingRangeKind, - GotoDefinitionResponse, HoverContents, InlayHintLabel, MarkedString, PublishDiagnosticsParams, + GotoDefinitionResponse, HoverContents, InlayHintLabel, MarkedString, }; const FILE_A: &str = "tests/a.er"; const FILE_B: &str = "tests/b.er"; const FILE_C: &str = "tests/c.er"; const FILE_IMPORTS: &str = "tests/imports.er"; const FILE_INVALID_SYNTAX: &str = "tests/invalid_syntax.er"; +const FILE_RETRIGGER: &str = "tests/retrigger.er"; use els::{NormalizedUrl, Server}; use erg_proc_macros::exec_new_thread; use molc::{add_char, delete_line, oneline_range}; -use serde::Deserialize; #[test] fn test_open() -> Result<(), Box> { @@ -68,6 +68,34 @@ fn test_neighbor_completion() -> Result<(), Box> { } } +#[test] +fn test_completion_retrigger() -> Result<(), Box> { + let mut client = Server::bind_fake_client(); + client.request_initialize()?; + client.notify_initialized()?; + let uri = NormalizedUrl::from_file_path(Path::new(FILE_RETRIGGER).canonicalize()?)?; + client.notify_open(FILE_RETRIGGER)?; + let _ = client.wait_diagnostics()?; + client.notify_change(uri.clone().raw(), add_char(2, 7, "n"))?; + let resp = client.request_completion(uri.clone().raw(), 2, 7, "n")?; + if let Some(CompletionResponse::Array(items)) = resp { + assert!(!items.is_empty()); + assert!(items.iter().any(|item| item.label == "print!")); + } else { + return Err(format!("not items: {resp:?}").into()); + } + client.notify_change(uri.clone().raw(), add_char(3, 15, "t"))?; + let resp = client.request_completion(uri.raw(), 3, 15, "t")?; + if let Some(CompletionResponse::Array(items)) = resp { + assert!(!items.is_empty()); + assert!(items.iter().any(|item| item.label == "bit_count")); + assert!(items.iter().any(|item| item.label == "bit_length")); + } else { + return Err(format!("not items: {resp:?}").into()); + } + Ok(()) +} + #[test] fn test_rename() -> Result<(), Box> { let mut client = Server::bind_fake_client(); @@ -251,11 +279,9 @@ fn test_dependents_check() -> Result<(), Box> { client.wait_messages(2)?; client.responses.clear(); client.notify_save(uri_b.clone().raw())?; - client.wait_messages(9)?; - assert!(client.responses.iter().any(|resp| resp - .to_string() - .contains("tests/b.er passed, found warns: 0"))); - let diags = PublishDiagnosticsParams::deserialize(&client.responses.last().unwrap()["params"])?; + let diags = client.wait_diagnostics()?; + assert!(diags.diagnostics.is_empty()); + let diags = client.wait_diagnostics()?; assert_eq!(diags.diagnostics.len(), 1); assert_eq!( diags.diagnostics[0].severity, @@ -269,24 +295,17 @@ fn test_fix_error() -> Result<(), Box> { let mut client = Server::bind_fake_client(); client.request_initialize()?; client.notify_initialized()?; - client.wait_messages(3)?; - client.responses.clear(); client.notify_open(FILE_INVALID_SYNTAX)?; - client.wait_messages(6)?; - let msg = client.responses.last().unwrap(); - let diags = PublishDiagnosticsParams::deserialize(&msg["params"])?; + let diags = client.wait_diagnostics()?; assert_eq!(diags.diagnostics.len(), 1); assert_eq!( diags.diagnostics[0].severity, Some(DiagnosticSeverity::ERROR) ); - client.responses.clear(); let uri = NormalizedUrl::from_file_path(Path::new(FILE_INVALID_SYNTAX).canonicalize()?)?; client.notify_change(uri.clone().raw(), add_char(0, 10, " 1"))?; client.notify_save(uri.clone().raw())?; - client.wait_messages(4)?; - let msg = client.responses.last().unwrap(); - let diags = PublishDiagnosticsParams::deserialize(&msg["params"])?; + let diags = client.wait_diagnostics()?; assert_eq!(diags.diagnostics.len(), 0); Ok(()) }