diff --git a/Cargo.lock b/Cargo.lock index 712ec9191a..d6e3fa05bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -388,7 +388,7 @@ dependencies = [ "ctor", "db", "editor", - "env_logger 0.11.5", + "env_logger 0.11.6", "feature_flags", "fs", "futures 0.3.31", @@ -579,9 +579,9 @@ dependencies = [ [[package]] name = "async-broadcast" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cd0e2e25ea8e5f7e9df04578dc6cf5c83577fd09b1a46aaf5c85e1c33f2a7e" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" dependencies = [ "event-listener 5.3.1", "event-listener-strategy", @@ -2141,7 +2141,7 @@ dependencies = [ "cap-primitives", "cap-std", "io-lifetimes 2.0.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2169,7 +2169,7 @@ dependencies = [ "ipnet", "maybe-owned", "rustix 0.38.42", - "windows-sys 0.59.0", + "windows-sys 0.52.0", "winx", ] @@ -2655,7 +2655,7 @@ dependencies = [ "debugger_ui", "derive_more", "editor", - "env_logger 0.11.5", + "env_logger 0.11.6", "envy", "extension", "file_finder", @@ -2811,7 +2811,7 @@ dependencies = [ "command_palette_hooks", "ctor", "editor", - "env_logger 0.11.5", + "env_logger 0.11.6", "fuzzy", "go_to_line", "gpui", @@ -3553,7 +3553,7 @@ dependencies = [ "client", "collections", "dap-types", - "env_logger 0.11.5", + "env_logger 0.11.6", "fs", "futures 0.3.31", "gpui", @@ -3699,7 +3699,7 @@ dependencies = [ "command_palette_hooks", "dap", "editor", - "env_logger 0.11.5", + "env_logger 0.11.6", "futures 0.3.31", "fuzzy", "gpui", @@ -3790,7 +3790,7 @@ dependencies = [ "collections", "ctor", "editor", - "env_logger 0.11.5", + "env_logger 0.11.6", "feature_flags", "gpui", "language", @@ -3995,7 +3995,7 @@ dependencies = [ "ctor", "db", "emojis", - "env_logger 0.11.5", + "env_logger 0.11.6", "feature_flags", "file_icons", "fs", @@ -4193,9 +4193,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" dependencies = [ "anstream", "anstyle", @@ -4247,7 +4247,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4299,7 +4299,7 @@ dependencies = [ "client", "clock", "collections", - "env_logger 0.11.5", + "env_logger 0.11.6", "feature_flags", "fs", "git", @@ -4415,7 +4415,7 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", - "env_logger 0.11.5", + "env_logger 0.11.6", "extension", "fs", "language", @@ -4443,7 +4443,7 @@ dependencies = [ "collections", "context_server_settings", "ctor", - "env_logger 0.11.5", + "env_logger 0.11.6", "extension", "fs", "futures 0.3.31", @@ -4636,7 +4636,7 @@ dependencies = [ "collections", "ctor", "editor", - "env_logger 0.11.5", + "env_logger 0.11.6", "file_icons", "futures 0.3.31", "fuzzy", @@ -4914,7 +4914,7 @@ checksum = "5e2e6123af26f0f2c51cc66869137080199406754903cc926a7690401ce09cb4" dependencies = [ "io-lifetimes 2.0.4", "rustix 0.38.42", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -5437,7 +5437,7 @@ dependencies = [ "ctor", "derive_more", "embed-resource", - "env_logger 0.11.5", + "env_logger 0.11.6", "etagere", "filedescriptor", "flume", @@ -6488,7 +6488,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" dependencies = [ "io-lifetimes 2.0.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -6791,7 +6791,7 @@ dependencies = [ "collections", "ctor", "ec4rs", - "env_logger 0.11.5", + "env_logger 0.11.6", "fs", "futures 0.3.31", "fuzzy", @@ -6958,7 +6958,7 @@ dependencies = [ "collections", "copilot", "editor", - "env_logger 0.11.5", + "env_logger 0.11.6", "futures 0.3.31", "gpui", "itertools 0.13.0", @@ -7056,9 +7056,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.168" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libdbus-sys" @@ -7099,7 +7099,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -7431,7 +7431,7 @@ dependencies = [ "async-pipe", "collections", "ctor", - "env_logger 0.11.5", + "env_logger 0.11.6", "futures 0.3.31", "gpui", "log", @@ -7494,7 +7494,7 @@ version = "0.1.0" dependencies = [ "anyhow", "assets", - "env_logger 0.11.5", + "env_logger 0.11.6", "futures 0.3.31", "gpui", "language", @@ -7609,7 +7609,7 @@ dependencies = [ "clap", "clap_complete", "elasticlunr-rs", - "env_logger 0.11.5", + "env_logger 0.11.6", "futures-util", "handlebars 6.2.0", "ignore", @@ -7798,7 +7798,7 @@ dependencies = [ "clock", "collections", "ctor", - "env_logger 0.11.5", + "env_logger 0.11.6", "futures 0.3.31", "gpui", "itertools 0.13.0", @@ -9304,7 +9304,7 @@ dependencies = [ "anyhow", "ctor", "editor", - "env_logger 0.11.5", + "env_logger 0.11.6", "gpui", "menu", "serde", @@ -9663,7 +9663,7 @@ dependencies = [ "collections", "dap", "dap_adapters", - "env_logger 0.11.5", + "env_logger 0.11.6", "fancy-regex 0.14.0", "fs", "futures 0.3.31", @@ -10045,7 +10045,7 @@ dependencies = [ "once_cell", "socket2 0.5.8", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -10420,7 +10420,7 @@ dependencies = [ "clap", "client", "clock", - "env_logger 0.11.5", + "env_logger 0.11.6", "extension", "extension_host", "fork", @@ -10479,7 +10479,7 @@ dependencies = [ "collections", "command_palette_hooks", "editor", - "env_logger 0.11.5", + "env_logger 0.11.6", "feature_flags", "file_icons", "futures 0.3.31", @@ -10754,7 +10754,7 @@ dependencies = [ "arrayvec", "criterion", "ctor", - "env_logger 0.11.5", + "env_logger 0.11.6", "gpui", "log", "rand 0.8.5", @@ -10780,7 +10780,7 @@ dependencies = [ "base64 0.22.1", "chrono", "collections", - "env_logger 0.11.5", + "env_logger 0.11.6", "futures 0.3.31", "gpui", "parking_lot", @@ -10946,7 +10946,7 @@ dependencies = [ "libc", "linux-raw-sys 0.4.14", "once_cell", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -11364,7 +11364,7 @@ dependencies = [ "client", "clock", "collections", - "env_logger 0.11.5", + "env_logger 0.11.6", "feature_flags", "fs", "futures 0.3.31", @@ -12367,7 +12367,7 @@ version = "0.1.0" dependencies = [ "arrayvec", "ctor", - "env_logger 0.11.5", + "env_logger 0.11.6", "log", "rand 0.8.5", "rayon", @@ -12381,7 +12381,7 @@ dependencies = [ "client", "collections", "editor", - "env_logger 0.11.5", + "env_logger 0.11.6", "futures 0.3.31", "gpui", "http_client", @@ -12667,7 +12667,7 @@ dependencies = [ "fd-lock", "io-lifetimes 2.0.4", "rustix 0.38.42", - "windows-sys 0.59.0", + "windows-sys 0.52.0", "winx", ] @@ -12679,7 +12679,7 @@ dependencies = [ "collections", "ctor", "editor", - "env_logger 0.11.5", + "env_logger 0.11.6", "gpui", "language", "menu", @@ -12800,7 +12800,7 @@ dependencies = [ "fastrand 2.3.0", "once_cell", "rustix 0.38.42", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -12901,7 +12901,7 @@ dependencies = [ "clock", "collections", "ctor", - "env_logger 0.11.5", + "env_logger 0.11.6", "gpui", "http_client", "log", @@ -15103,7 +15103,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -15552,7 +15552,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" dependencies = [ "bitflags 2.6.0", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -15700,7 +15700,7 @@ dependencies = [ "collections", "db", "derive_more", - "env_logger 0.11.5", + "env_logger 0.11.6", "fs", "futures 0.3.31", "gpui", @@ -15736,7 +15736,7 @@ dependencies = [ "anyhow", "clock", "collections", - "env_logger 0.11.5", + "env_logger 0.11.6", "fs", "futures 0.3.31", "fuzzy", @@ -16135,7 +16135,7 @@ dependencies = [ "debugger_ui", "diagnostics", "editor", - "env_logger 0.11.5", + "env_logger 0.11.6", "extension", "extension_host", "extensions_ui", @@ -16561,7 +16561,7 @@ dependencies = [ "collections", "ctor", "editor", - "env_logger 0.11.5", + "env_logger 0.11.6", "futures 0.3.31", "gpui", "http_client", diff --git a/crates/assistant/src/slash_command.rs b/crates/assistant/src/slash_command.rs index 73f5cfc2e6..f9fb4fa803 100644 --- a/crates/assistant/src/slash_command.rs +++ b/crates/assistant/src/slash_command.rs @@ -149,6 +149,7 @@ impl SlashCommandCompletionProvider { server_id: LanguageServerId(0), lsp_completion: Default::default(), confirm, + resolved: true, }) }) .collect() @@ -242,6 +243,7 @@ impl SlashCommandCompletionProvider { server_id: LanguageServerId(0), lsp_completion: Default::default(), confirm, + resolved: true, } }) .collect()) @@ -330,16 +332,6 @@ impl CompletionProvider for SlashCommandCompletionProvider { Task::ready(Ok(true)) } - fn apply_additional_edits_for_completion( - &self, - _: Model, - _: project::Completion, - _: bool, - _: &mut ViewContext, - ) -> Task>> { - Task::ready(Ok(None)) - } - fn is_completion_trigger( &self, buffer: &Model, diff --git a/crates/collab_ui/src/chat_panel/message_editor.rs b/crates/collab_ui/src/chat_panel/message_editor.rs index 1dd1b4e0bc..7a05e6bce4 100644 --- a/crates/collab_ui/src/chat_panel/message_editor.rs +++ b/crates/collab_ui/src/chat_panel/message_editor.rs @@ -79,16 +79,6 @@ impl CompletionProvider for MessageEditorCompletionProvider { Task::ready(Ok(false)) } - fn apply_additional_edits_for_completion( - &self, - _buffer: Model, - _completion: Completion, - _push_to_history: bool, - _cx: &mut ViewContext, - ) -> Task>> { - Task::ready(Ok(None)) - } - fn is_completion_trigger( &self, _buffer: &Model, @@ -319,6 +309,7 @@ impl MessageEditor { server_id: LanguageServerId(0), // TODO: Make this optional or something? lsp_completion: Default::default(), // TODO: Make this optional or something? confirm: None, + resolved: true, } }) .collect() diff --git a/crates/debugger_ui/src/console.rs b/crates/debugger_ui/src/console.rs index de20f079d6..1906a33d6c 100644 --- a/crates/debugger_ui/src/console.rs +++ b/crates/debugger_ui/src/console.rs @@ -249,7 +249,8 @@ impl CompletionProvider for ConsoleQueryBarCompletionProvider { fn apply_additional_edits_for_completion( &self, _buffer: Model, - _completion: project::Completion, + _completions: Rc>>, + _completion_index: usize, _push_to_history: bool, _cx: &mut ViewContext, ) -> gpui::Task>> { @@ -336,6 +337,7 @@ impl ConsoleQueryBarCompletionProvider { documentation: None, lsp_completion: Default::default(), confirm: None, + resolved: true, }) }) .collect()) @@ -381,6 +383,7 @@ impl ConsoleQueryBarCompletionProvider { documentation: None, lsp_completion: Default::default(), confirm: None, + resolved: true, }) .collect()) }) diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index fbd9a4d94f..9f591368f0 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -224,6 +224,7 @@ impl CompletionsMenu { documentation: None, lsp_completion: Default::default(), confirm: None, + resolved: true, }) .collect(); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index da10da145f..214ba8bf47 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -3851,8 +3851,11 @@ impl Editor { }; let buffer_handle = completions_menu.buffer; - let completions = completions_menu.completions.borrow_mut(); - let completion = completions.get(mat.candidate_id)?; + let completion = completions_menu + .completions + .borrow() + .get(mat.candidate_id)? + .clone(); cx.stop_propagation(); let snippet; @@ -3996,9 +3999,11 @@ impl Editor { } let provider = self.completion_provider.as_ref()?; + drop(completion); let apply_edits = provider.apply_additional_edits_for_completion( buffer_handle, - completion.clone(), + completions_menu.completions.clone(), + mat.candidate_id, true, cx, ); @@ -5404,7 +5409,7 @@ impl Editor { })) } - #[cfg(feature = "test-support")] + #[cfg(any(feature = "test-support", test))] pub fn context_menu_visible(&self) -> bool { self.context_menu .borrow() @@ -13918,11 +13923,14 @@ pub trait CompletionProvider { fn apply_additional_edits_for_completion( &self, - buffer: Model, - completion: Completion, - push_to_history: bool, - cx: &mut ViewContext, - ) -> Task>>; + _buffer: Model, + _completions: Rc>>, + _completion_index: usize, + _push_to_history: bool, + _cx: &mut ViewContext, + ) -> Task>> { + Task::ready(Ok(None)) + } fn is_completion_trigger( &self, @@ -14081,6 +14089,7 @@ fn snippet_completions( Some(Completion { old_range: range, new_text: snippet.body.clone(), + resolved: false, label: CodeLabel { text: matching_prefix.clone(), runs: vec![], @@ -14146,19 +14155,30 @@ impl CompletionProvider for Model { cx: &mut ViewContext, ) -> Task> { self.update(cx, |project, cx| { - project.resolve_completions(buffer, completion_indices, completions, cx) + project.lsp_store().update(cx, |lsp_store, cx| { + lsp_store.resolve_completions(buffer, completion_indices, completions, cx) + }) }) } fn apply_additional_edits_for_completion( &self, buffer: Model, - completion: Completion, + completions: Rc>>, + completion_index: usize, push_to_history: bool, cx: &mut ViewContext, ) -> Task>> { self.update(cx, |project, cx| { - project.apply_additional_edits_for_completion(buffer, completion, push_to_history, cx) + project.lsp_store().update(cx, |lsp_store, cx| { + lsp_store.apply_additional_edits_for_completion( + buffer, + completions, + completion_index, + push_to_history, + cx, + ) + }) }) } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index cc7d612bbf..7377b6809b 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -8402,7 +8402,6 @@ async fn test_completion(cx: &mut gpui::TestAppContext) { additional edit "}); - handle_resolve_completion_request(&mut cx, None).await; apply_additional_edits.await.unwrap(); update_test_language_settings(&mut cx, |settings| { @@ -10698,10 +10697,14 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches( ..lsp::CompletionItem::default() }; - cx.handle_request::(move |_, _, _| { + let item1 = item1.clone(); + cx.handle_request::({ let item1 = item1.clone(); - let item2 = item2.clone(); - async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) } + move |_, _, _| { + let item1 = item1.clone(); + let item2 = item2.clone(); + async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) } + } }) .next() .await; @@ -10728,43 +10731,41 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches( } }); - cx.handle_request::(move |_, _, _| async move { - Ok(lsp::CompletionItem { - label: "method id()".to_string(), - filter_text: Some("id".to_string()), - detail: Some("Now resolved!".to_string()), - documentation: Some(lsp::Documentation::String("Docs".to_string())), - text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { - range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)), - new_text: ".id".to_string(), - })), - ..lsp::CompletionItem::default() - }) + cx.handle_request::({ + let item1 = item1.clone(); + move |_, item_to_resolve, _| { + let item1 = item1.clone(); + async move { + if item1 == item_to_resolve { + Ok(lsp::CompletionItem { + label: "method id()".to_string(), + filter_text: Some("id".to_string()), + detail: Some("Now resolved!".to_string()), + documentation: Some(lsp::Documentation::String("Docs".to_string())), + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + range: lsp::Range::new( + lsp::Position::new(0, 22), + lsp::Position::new(0, 22), + ), + new_text: ".id".to_string(), + })), + ..lsp::CompletionItem::default() + }) + } else { + Ok(item_to_resolve) + } + } + } }) .next() - .await; + .await + .unwrap(); cx.run_until_parked(); cx.update_editor(|editor, cx| { editor.context_menu_next(&Default::default(), cx); }); - cx.handle_request::(move |_, _, _| async move { - Ok(lsp::CompletionItem { - label: "invalid changed label".to_string(), - detail: Some("Now resolved!".to_string()), - documentation: Some(lsp::Documentation::String("Docs".to_string())), - text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { - range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)), - new_text: ".id".to_string(), - })), - ..lsp::CompletionItem::default() - }) - }) - .next() - .await; - cx.run_until_parked(); - cx.update_editor(|editor, _| { let context_menu = editor.context_menu.borrow_mut(); let context_menu = context_menu @@ -10787,6 +10788,172 @@ async fn test_completions_resolve_updates_labels_if_filter_text_matches( }); } +#[gpui::test] +async fn test_completions_resolve_happens_once(cx: &mut gpui::TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorLspTestContext::new_rust( + lsp::ServerCapabilities { + completion_provider: Some(lsp::CompletionOptions { + trigger_characters: Some(vec![".".to_string()]), + resolve_provider: Some(true), + ..Default::default() + }), + ..Default::default() + }, + cx, + ) + .await; + + cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"}); + cx.simulate_keystroke("."); + + let unresolved_item_1 = lsp::CompletionItem { + label: "id".to_string(), + filter_text: Some("id".to_string()), + detail: None, + documentation: None, + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)), + new_text: ".id".to_string(), + })), + ..lsp::CompletionItem::default() + }; + let resolved_item_1 = lsp::CompletionItem { + additional_text_edits: Some(vec![lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)), + new_text: "!!".to_string(), + }]), + ..unresolved_item_1.clone() + }; + let unresolved_item_2 = lsp::CompletionItem { + label: "other".to_string(), + filter_text: Some("other".to_string()), + detail: None, + documentation: None, + text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)), + new_text: ".other".to_string(), + })), + ..lsp::CompletionItem::default() + }; + let resolved_item_2 = lsp::CompletionItem { + additional_text_edits: Some(vec![lsp::TextEdit { + range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)), + new_text: "??".to_string(), + }]), + ..unresolved_item_2.clone() + }; + + let resolve_requests_1 = Arc::new(AtomicUsize::new(0)); + let resolve_requests_2 = Arc::new(AtomicUsize::new(0)); + cx.lsp + .server + .on_request::({ + let unresolved_item_1 = unresolved_item_1.clone(); + let resolved_item_1 = resolved_item_1.clone(); + let unresolved_item_2 = unresolved_item_2.clone(); + let resolved_item_2 = resolved_item_2.clone(); + let resolve_requests_1 = resolve_requests_1.clone(); + let resolve_requests_2 = resolve_requests_2.clone(); + move |unresolved_request, _| { + let unresolved_item_1 = unresolved_item_1.clone(); + let resolved_item_1 = resolved_item_1.clone(); + let unresolved_item_2 = unresolved_item_2.clone(); + let resolved_item_2 = resolved_item_2.clone(); + let resolve_requests_1 = resolve_requests_1.clone(); + let resolve_requests_2 = resolve_requests_2.clone(); + async move { + if unresolved_request == unresolved_item_1 { + resolve_requests_1.fetch_add(1, atomic::Ordering::Release); + Ok(resolved_item_1.clone()) + } else if unresolved_request == unresolved_item_2 { + resolve_requests_2.fetch_add(1, atomic::Ordering::Release); + Ok(resolved_item_2.clone()) + } else { + panic!("Unexpected completion item {unresolved_request:?}") + } + } + } + }) + .detach(); + + cx.handle_request::(move |_, _, _| { + let unresolved_item_1 = unresolved_item_1.clone(); + let unresolved_item_2 = unresolved_item_2.clone(); + async move { + Ok(Some(lsp::CompletionResponse::Array(vec![ + unresolved_item_1, + unresolved_item_2, + ]))) + } + }) + .next() + .await; + + cx.condition(|editor, _| editor.context_menu_visible()) + .await; + cx.update_editor(|editor, _| { + let context_menu = editor.context_menu.borrow_mut(); + let context_menu = context_menu + .as_ref() + .expect("Should have the context menu deployed"); + match context_menu { + CodeContextMenu::Completions(completions_menu) => { + let completions = completions_menu.completions.borrow_mut(); + assert_eq!( + completions + .iter() + .map(|completion| &completion.label.text) + .collect::>(), + vec!["id", "other"] + ) + } + CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"), + } + }); + cx.run_until_parked(); + + cx.update_editor(|editor, cx| { + editor.context_menu_next(&ContextMenuNext, cx); + }); + cx.run_until_parked(); + cx.update_editor(|editor, cx| { + editor.context_menu_prev(&ContextMenuPrev, cx); + }); + cx.run_until_parked(); + cx.update_editor(|editor, cx| { + editor.context_menu_next(&ContextMenuNext, cx); + }); + cx.run_until_parked(); + cx.update_editor(|editor, cx| { + editor + .compose_completion(&ComposeCompletion::default(), cx) + .expect("No task returned") + }) + .await + .expect("Completion failed"); + cx.run_until_parked(); + + cx.update_editor(|editor, cx| { + assert_eq!( + resolve_requests_1.load(atomic::Ordering::Acquire), + 1, + "Should always resolve once despite multiple selections" + ); + assert_eq!( + resolve_requests_2.load(atomic::Ordering::Acquire), + 1, + "Should always resolve once after multiple selections and applying the completion" + ); + assert_eq!( + editor.text(cx), + "fn main() { let a = ??.other; }", + "Should use resolved data when applying the completion" + ); + }); +} + #[gpui::test] async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppContext) { init_test(cx, |_| {}); @@ -10950,15 +11117,10 @@ async fn test_completions_default_resolve_data_handling(cx: &mut gpui::TestAppCo // Completions that have already been resolved are skipped. assert_eq!( *resolved_items.lock(), - [ - // Selected item is always resolved even if it was resolved before. - &items_out[items_out.len() - 1..items_out.len()], - &items_out[items_out.len() - 16..items_out.len() - 4] - ] - .concat() - .iter() - .cloned() - .collect::>() + items_out[items_out.len() - 16..items_out.len() - 4] + .iter() + .cloned() + .collect::>() ); resolved_items.lock().clear(); } diff --git a/crates/gpui/examples/shadow.rs b/crates/gpui/examples/shadow.rs index c4f379325c..5519b4b839 100644 --- a/crates/gpui/examples/shadow.rs +++ b/crates/gpui/examples/shadow.rs @@ -1,25 +1,574 @@ use gpui::{ - div, prelude::*, px, rgb, size, App, AppContext, Bounds, ViewContext, WindowBounds, - WindowOptions, + div, hsla, point, prelude::*, px, relative, rgb, size, App, AppContext, Bounds, BoxShadow, Div, + SharedString, ViewContext, WindowBounds, WindowOptions, }; +use smallvec::smallvec; + struct Shadow {} +impl Shadow { + fn base() -> Div { + div() + .size_16() + .bg(rgb(0xffffff)) + .rounded_full() + .border_1() + .border_color(hsla(0.0, 0.0, 0.0, 0.1)) + } + + fn square() -> Div { + div() + .size_16() + .bg(rgb(0xffffff)) + .border_1() + .border_color(hsla(0.0, 0.0, 0.0, 0.1)) + } + + fn rounded_small() -> Div { + div() + .size_16() + .bg(rgb(0xffffff)) + .rounded(px(4.)) + .border_1() + .border_color(hsla(0.0, 0.0, 0.0, 0.1)) + } + + fn rounded_medium() -> Div { + div() + .size_16() + .bg(rgb(0xffffff)) + .rounded(px(8.)) + .border_1() + .border_color(hsla(0.0, 0.0, 0.0, 0.1)) + } + + fn rounded_large() -> Div { + div() + .size_16() + .bg(rgb(0xffffff)) + .rounded(px(12.)) + .border_1() + .border_color(hsla(0.0, 0.0, 0.0, 0.1)) + } +} + +fn example(label: impl Into, example: impl IntoElement) -> impl IntoElement { + let label = label.into(); + + div() + .flex() + .flex_col() + .justify_center() + .items_center() + .w(relative(1. / 6.)) + .border_r_1() + .border_color(hsla(0.0, 0.0, 0.0, 1.0)) + .child( + div() + .flex() + .items_center() + .justify_center() + .flex_1() + .py_12() + .child(example), + ) + .child( + div() + .w_full() + .border_t_1() + .border_color(hsla(0.0, 0.0, 0.0, 1.0)) + .p_1() + .flex() + .items_center() + .child(label), + ) +} + impl Render for Shadow { fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { div() - .flex() + .id("shadow-example") + .overflow_y_scroll() .bg(rgb(0xffffff)) .size_full() - .justify_center() - .items_center() - .child(div().size_8().shadow_sm()) + .text_xs() + .child(div().flex().flex_col().w_full().children(vec![ + div() + .border_b_1() + .border_color(hsla(0.0, 0.0, 0.0, 1.0)) + .flex() + .flex_row() + .children(vec![ + example( + "Square", + Shadow::square() + .shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.5, 0.5, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + example( + "Rounded 4", + Shadow::rounded_small() + .shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.5, 0.5, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + example( + "Rounded 8", + Shadow::rounded_medium() + .shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.5, 0.5, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + example( + "Rounded 16", + Shadow::rounded_large() + .shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.5, 0.5, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + example( + "Circle", + Shadow::base() + .shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.5, 0.5, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + ]), + div() + .border_b_1() + .border_color(hsla(0.0, 0.0, 0.0, 1.0)) + .flex() + .w_full() + .children(vec![ + example("None", Shadow::base()), + // Small shadow + example("Small", Shadow::base().shadow_sm()), + // Medium shadow + example("Medium", Shadow::base().shadow_md()), + // Large shadow + example("Large", Shadow::base().shadow_lg()), + example("Extra Large", Shadow::base().shadow_xl()), + example("2X Large", Shadow::base().shadow_2xl()), + ]), + // Horizontal list of increasing blur radii + div() + .border_b_1() + .border_color(hsla(0.0, 0.0, 0.0, 1.0)) + .flex() + .children(vec![ + example( + "Blur 0", + Shadow::base().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(0.), + spread_radius: px(0.), + }]), + ), + example( + "Blur 2", + Shadow::base().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(2.), + spread_radius: px(0.), + }]), + ), + example( + "Blur 4", + Shadow::base().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(4.), + spread_radius: px(0.), + }]), + ), + example( + "Blur 8", + Shadow::base().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + example( + "Blur 16", + Shadow::base().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(16.), + spread_radius: px(0.), + }]), + ), + ]), + // Horizontal list of increasing spread radii + div() + .border_b_1() + .border_color(hsla(0.0, 0.0, 0.0, 1.0)) + .flex() + .children(vec![ + example( + "Spread 0", + Shadow::base().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + example( + "Spread 2", + Shadow::base().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(2.), + }]), + ), + example( + "Spread 4", + Shadow::base().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(4.), + }]), + ), + example( + "Spread 8", + Shadow::base().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(8.), + }]), + ), + example( + "Spread 16", + Shadow::base().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(16.), + }]), + ), + ]), + // Square spread examples + div() + .border_b_1() + .border_color(hsla(0.0, 0.0, 0.0, 1.0)) + .flex() + .children(vec![ + example( + "Square Spread 0", + Shadow::square().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + example( + "Square Spread 8", + Shadow::square().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(8.), + }]), + ), + example( + "Square Spread 16", + Shadow::square().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(16.), + }]), + ), + ]), + // Rounded large spread examples + div() + .border_b_1() + .border_color(hsla(0.0, 0.0, 0.0, 1.0)) + .flex() + .children(vec![ + example( + "Rounded Large Spread 0", + Shadow::rounded_large().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + example( + "Rounded Large Spread 8", + Shadow::rounded_large().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(8.), + }]), + ), + example( + "Rounded Large Spread 16", + Shadow::rounded_large().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.0, 0.0, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(16.), + }]), + ), + ]), + // Directional shadows + div() + .border_b_1() + .border_color(hsla(0.0, 0.0, 0.0, 1.0)) + .flex() + .children(vec![ + example( + "Left", + Shadow::base().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.5, 0.5, 0.3), + offset: point(px(-8.), px(0.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + example( + "Right", + Shadow::base().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.5, 0.5, 0.3), + offset: point(px(8.), px(0.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + example( + "Top", + Shadow::base().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.5, 0.5, 0.3), + offset: point(px(0.), px(-8.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + example( + "Bottom", + Shadow::base().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.5, 0.5, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + ]), + // Square directional shadows + div() + .border_b_1() + .border_color(hsla(0.0, 0.0, 0.0, 1.0)) + .flex() + .children(vec![ + example( + "Square Left", + Shadow::square().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.5, 0.5, 0.3), + offset: point(px(-8.), px(0.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + example( + "Square Right", + Shadow::square().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.5, 0.5, 0.3), + offset: point(px(8.), px(0.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + example( + "Square Top", + Shadow::square().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.5, 0.5, 0.3), + offset: point(px(0.), px(-8.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + example( + "Square Bottom", + Shadow::square().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.5, 0.5, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + ]), + // Rounded large directional shadows + div() + .border_b_1() + .border_color(hsla(0.0, 0.0, 0.0, 1.0)) + .flex() + .children(vec![ + example( + "Rounded Large Left", + Shadow::rounded_large().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.5, 0.5, 0.3), + offset: point(px(-8.), px(0.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + example( + "Rounded Large Right", + Shadow::rounded_large().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.5, 0.5, 0.3), + offset: point(px(8.), px(0.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + example( + "Rounded Large Top", + Shadow::rounded_large().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.5, 0.5, 0.3), + offset: point(px(0.), px(-8.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + example( + "Rounded Large Bottom", + Shadow::rounded_large().shadow(smallvec![BoxShadow { + color: hsla(0.0, 0.5, 0.5, 0.3), + offset: point(px(0.), px(8.)), + blur_radius: px(8.), + spread_radius: px(0.), + }]), + ), + ]), + // Multiple shadows for different shapes + div() + .border_b_1() + .border_color(hsla(0.0, 0.0, 0.0, 1.0)) + .flex() + .children(vec![ + example( + "Circle Multiple", + Shadow::base().shadow(smallvec![ + BoxShadow { + color: hsla(0.0 / 360., 1.0, 0.5, 0.3), // Red + offset: point(px(0.), px(-12.)), + blur_radius: px(8.), + spread_radius: px(2.), + }, + BoxShadow { + color: hsla(60.0 / 360., 1.0, 0.5, 0.3), // Yellow + offset: point(px(12.), px(0.)), + blur_radius: px(8.), + spread_radius: px(2.), + }, + BoxShadow { + color: hsla(120.0 / 360., 1.0, 0.5, 0.3), // Green + offset: point(px(0.), px(12.)), + blur_radius: px(8.), + spread_radius: px(2.), + }, + BoxShadow { + color: hsla(240.0 / 360., 1.0, 0.5, 0.3), // Blue + offset: point(px(-12.), px(0.)), + blur_radius: px(8.), + spread_radius: px(2.), + }, + ]), + ), + example( + "Square Multiple", + Shadow::square().shadow(smallvec![ + BoxShadow { + color: hsla(0.0 / 360., 1.0, 0.5, 0.3), // Red + offset: point(px(0.), px(-12.)), + blur_radius: px(8.), + spread_radius: px(2.), + }, + BoxShadow { + color: hsla(60.0 / 360., 1.0, 0.5, 0.3), // Yellow + offset: point(px(12.), px(0.)), + blur_radius: px(8.), + spread_radius: px(2.), + }, + BoxShadow { + color: hsla(120.0 / 360., 1.0, 0.5, 0.3), // Green + offset: point(px(0.), px(12.)), + blur_radius: px(8.), + spread_radius: px(2.), + }, + BoxShadow { + color: hsla(240.0 / 360., 1.0, 0.5, 0.3), // Blue + offset: point(px(-12.), px(0.)), + blur_radius: px(8.), + spread_radius: px(2.), + }, + ]), + ), + example( + "Rounded Large Multiple", + Shadow::rounded_large().shadow(smallvec![ + BoxShadow { + color: hsla(0.0 / 360., 1.0, 0.5, 0.3), // Red + offset: point(px(0.), px(-12.)), + blur_radius: px(8.), + spread_radius: px(2.), + }, + BoxShadow { + color: hsla(60.0 / 360., 1.0, 0.5, 0.3), // Yellow + offset: point(px(12.), px(0.)), + blur_radius: px(8.), + spread_radius: px(2.), + }, + BoxShadow { + color: hsla(120.0 / 360., 1.0, 0.5, 0.3), // Green + offset: point(px(0.), px(12.)), + blur_radius: px(8.), + spread_radius: px(2.), + }, + BoxShadow { + color: hsla(240.0 / 360., 1.0, 0.5, 0.3), // Blue + offset: point(px(-12.), px(0.)), + blur_radius: px(8.), + spread_radius: px(2.), + }, + ]), + ), + ]), + ])) } } fn main() { App::new().run(|cx: &mut AppContext| { - let bounds = Bounds::centered(None, size(px(300.0), px(300.0)), cx); + let bounds = Bounds::centered(None, size(px(1000.0), px(800.0)), cx); cx.open_window( WindowOptions { window_bounds: Some(WindowBounds::Windowed(bounds)), @@ -28,5 +577,7 @@ fn main() { |cx| cx.new_view(|_cx| Shadow {}), ) .unwrap(); + + cx.activate(true); }); } diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index b68e22051f..5b8f7759c0 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -1918,6 +1918,7 @@ impl LspCommand for GetCompletions { new_text, server_id, lsp_completion, + resolved: false, } }) .collect()) diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 45b5a6a4bf..dffb4cc31c 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -4160,38 +4160,27 @@ impl LspStore { let mut did_resolve = false; if let Some((client, project_id)) = client { for completion_index in completion_indices { - let (server_id, completion) = { - let completions = completions.borrow_mut(); - let completion = &completions[completion_index]; - did_resolve = true; - let server_id = completion.server_id; - let completion = completion.lsp_completion.clone(); + let server_id = completions.borrow()[completion_index].server_id; - (server_id, completion) - }; - - Self::resolve_completion_remote( + if Self::resolve_completion_remote( project_id, server_id, buffer_id, completions.clone(), completion_index, - completion, client.clone(), language_registry.clone(), ) - .await; + .await + .log_err() + .is_some() + { + did_resolve = true; + } } } else { for completion_index in completion_indices { - let (server_id, completion) = { - let completions = completions.borrow_mut(); - let completion = &completions[completion_index]; - let server_id = completion.server_id; - let completion = completion.lsp_completion.clone(); - - (server_id, completion) - }; + let server_id = completions.borrow()[completion_index].server_id; let server_and_adapter = this .read_with(&cx, |lsp_store, _| { @@ -4206,17 +4195,27 @@ impl LspStore { continue; }; - did_resolve = true; - Self::resolve_completion_local( + let resolved = Self::resolve_completion_local( server, - adapter, &buffer_snapshot, completions.clone(), completion_index, - completion, - language_registry.clone(), ) - .await; + .await + .log_err() + .is_some(); + if resolved { + Self::regenerate_completion_labels( + adapter, + &buffer_snapshot, + completions.clone(), + completion_index, + language_registry.clone(), + ) + .await + .log_err(); + did_resolve = true; + } } } @@ -4226,13 +4225,10 @@ impl LspStore { async fn resolve_completion_local( server: Arc, - adapter: Arc, snapshot: &BufferSnapshot, completions: Rc>>, completion_index: usize, - completion: lsp::CompletionItem, - language_registry: Arc, - ) { + ) -> Result<()> { let can_resolve = server .capabilities() .completion_provider @@ -4240,30 +4236,17 @@ impl LspStore { .and_then(|options| options.resolve_provider) .unwrap_or(false); if !can_resolve { - return; + return Ok(()); } - let request = server.request::(completion); - let Some(completion_item) = request.await.log_err() else { - return; + let request = { + let completion = &completions.borrow()[completion_index]; + if completion.resolved { + return Ok(()); + } + server.request::(completion.lsp_completion.clone()) }; - - if let Some(lsp_documentation) = completion_item.documentation.as_ref() { - let documentation = language::prepare_completion_documentation( - lsp_documentation, - &language_registry, - snapshot.language().cloned(), - ) - .await; - - let mut completions = completions.borrow_mut(); - let completion = &mut completions[completion_index]; - completion.documentation = Some(documentation); - } else { - let mut completions = completions.borrow_mut(); - let completion = &mut completions[completion_index]; - completion.documentation = Some(Documentation::Undocumented); - } + let completion_item = request.await?; if let Some(text_edit) = completion_item.text_edit.as_ref() { // Technically we don't have to parse the whole `text_edit`, since the only @@ -4291,28 +4274,61 @@ impl LspStore { } } + let mut completions = completions.borrow_mut(); + let completion = &mut completions[completion_index]; + completion.lsp_completion = completion_item; + completion.resolved = true; + Ok(()) + } + + async fn regenerate_completion_labels( + adapter: Arc, + snapshot: &BufferSnapshot, + completions: Rc>>, + completion_index: usize, + language_registry: Arc, + ) -> Result<()> { + let completion_item = completions.borrow()[completion_index] + .lsp_completion + .clone(); + if let Some(lsp_documentation) = completion_item.documentation.as_ref() { + let documentation = language::prepare_completion_documentation( + lsp_documentation, + &language_registry, + snapshot.language().cloned(), + ) + .await; + + let mut completions = completions.borrow_mut(); + let completion = &mut completions[completion_index]; + completion.documentation = Some(documentation); + } else { + let mut completions = completions.borrow_mut(); + let completion = &mut completions[completion_index]; + completion.documentation = Some(Documentation::Undocumented); + } + // NB: Zed does not have `details` inside the completion resolve capabilities, but certain language servers violate the spec and do not return `details` immediately, e.g. https://github.com/yioneko/vtsls/issues/213 // So we have to update the label here anyway... let new_label = match snapshot.language() { - Some(language) => adapter - .labels_for_completions(&[completion_item.clone()], language) - .await - .log_err() - .unwrap_or_default(), + Some(language) => { + adapter + .labels_for_completions(&[completion_item.clone()], language) + .await? + } None => Vec::new(), } .pop() .flatten() .unwrap_or_else(|| { CodeLabel::plain( - completion_item.label.clone(), + completion_item.label, completion_item.filter_text.as_deref(), ) }); let mut completions = completions.borrow_mut(); let completion = &mut completions[completion_index]; - completion.lsp_completion = completion_item; if completion.label.filter_text() == new_label.filter_text() { completion.label = new_label; } else { @@ -4325,6 +4341,8 @@ impl LspStore { new_label.filter_text() ); } + + Ok(()) } #[allow(clippy::too_many_arguments)] @@ -4334,29 +4352,30 @@ impl LspStore { buffer_id: BufferId, completions: Rc>>, completion_index: usize, - completion: lsp::CompletionItem, client: AnyProtoClient, language_registry: Arc, - ) { + ) -> Result<()> { + let lsp_completion = { + let completion = &completions.borrow()[completion_index]; + if completion.resolved { + return Ok(()); + } + serde_json::to_string(&completion.lsp_completion) + .unwrap() + .into_bytes() + }; let request = proto::ResolveCompletionDocumentation { project_id, language_server_id: server_id.0 as u64, - lsp_completion: serde_json::to_string(&completion).unwrap().into_bytes(), + lsp_completion, buffer_id: buffer_id.into(), }; - let Some(response) = client + let response = client .request(request) .await - .context("completion documentation resolve proto request") - .log_err() - else { - return; - }; - let Some(lsp_completion) = serde_json::from_slice(&response.lsp_completion).log_err() - else { - return; - }; + .context("completion documentation resolve proto request")?; + let lsp_completion = serde_json::from_slice(&response.lsp_completion)?; let documentation = if response.documentation.is_empty() { Documentation::Undocumented @@ -4374,6 +4393,7 @@ impl LspStore { let completion = &mut completions[completion_index]; completion.documentation = Some(documentation); completion.lsp_completion = lsp_completion; + completion.resolved = true; let old_range = response .old_start @@ -4385,12 +4405,15 @@ impl LspStore { completion.old_range = old_start..old_end; } } + + Ok(()) } pub fn apply_additional_edits_for_completion( &self, buffer_handle: Model, - completion: Completion, + completions: Rc>>, + completion_index: usize, push_to_history: bool, cx: &mut ModelContext, ) -> Task>> { @@ -4399,8 +4422,9 @@ impl LspStore { if let Some((client, project_id)) = self.upstream_client() { cx.spawn(move |_, mut cx| async move { - let response = client - .request(proto::ApplyCompletionAdditionalEdits { + let request = { + let completion = completions.borrow()[completion_index].clone(); + proto::ApplyCompletionAdditionalEdits { project_id, buffer_id: buffer_id.into(), completion: Some(Self::serialize_completion(&CoreCompletion { @@ -4408,9 +4432,13 @@ impl LspStore { new_text: completion.new_text, server_id: completion.server_id, lsp_completion: completion.lsp_completion, + resolved: completion.resolved, })), - }) - .await?; + } + }; + + let response = client.request(request).await?; + completions.borrow_mut()[completion_index].resolved = true; if let Some(transaction) = response.transaction { let transaction = language::proto::deserialize_transaction(transaction)?; @@ -4430,34 +4458,31 @@ impl LspStore { } }) } else { - let server_id = completion.server_id; - let lang_server = match self.language_server_for_local_buffer(buffer, server_id, cx) { + let server_id = completions.borrow()[completion_index].server_id; + let server = match self.language_server_for_local_buffer(buffer, server_id, cx) { Some((_, server)) => server.clone(), - _ => return Task::ready(Ok(Default::default())), + _ => return Task::ready(Ok(None)), }; + let snapshot = buffer_handle.read(&cx).snapshot(); cx.spawn(move |this, mut cx| async move { - let can_resolve = lang_server - .capabilities() - .completion_provider - .as_ref() - .and_then(|options| options.resolve_provider) - .unwrap_or(false); - let additional_text_edits = if can_resolve { - lang_server - .request::(completion.lsp_completion) - .await? - .additional_text_edits - } else { - completion.lsp_completion.additional_text_edits - }; + Self::resolve_completion_local( + server.clone(), + &snapshot, + completions.clone(), + completion_index, + ) + .await + .context("resolving completion")?; + let completion = completions.borrow()[completion_index].clone(); + let additional_text_edits = completion.lsp_completion.additional_text_edits; if let Some(edits) = additional_text_edits { let edits = this .update(&mut cx, |this, cx| { this.as_local_mut().unwrap().edits_from_lsp( &buffer_handle, edits, - lang_server.server_id(), + server.server_id(), None, cx, ) @@ -6811,7 +6836,7 @@ impl LspStore { let apply_additional_edits = this.update(&mut cx, |this, cx| { this.apply_additional_edits_for_completion( buffer, - Completion { + Rc::new(RefCell::new(Box::new([Completion { old_range: completion.old_range, new_text: completion.new_text, lsp_completion: completion.lsp_completion, @@ -6823,7 +6848,9 @@ impl LspStore { filter_range: Default::default(), }, confirm: None, - }, + resolved: completion.resolved, + }]))), + 0, false, cx, ) @@ -7788,6 +7815,7 @@ impl LspStore { new_text: completion.new_text.clone(), server_id: completion.server_id.0 as u64, lsp_completion: serde_json::to_vec(&completion.lsp_completion).unwrap(), + resolved: completion.resolved, } } @@ -7807,6 +7835,7 @@ impl LspStore { new_text: completion.new_text, server_id: LanguageServerId(completion.server_id as usize), lsp_completion, + resolved: completion.resolved, }) } @@ -7908,6 +7937,7 @@ async fn populate_labels_for_completions( documentation, lsp_completion, confirm: None, + resolved: false, }) } } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 1ec0191e1d..f5967df9a4 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -83,10 +83,8 @@ use snippet::Snippet; use snippet_provider::SnippetProvider; use std::{ borrow::Cow, - cell::RefCell, ops::Range, path::{Component, Path, PathBuf}, - rc::Rc, str, sync::Arc, time::Duration, @@ -380,6 +378,8 @@ pub struct Completion { pub documentation: Option, /// The raw completion provided by the language server. pub lsp_completion: lsp::CompletionItem, + /// Whether this completion has been resolved, to ensure it happens once per completion. + pub resolved: bool, /// An optional callback to invoke when this completion is confirmed. /// Returns, whether new completions should be retriggered after the current one. /// If `true` is returned, the editor will show a new completion menu after this completion is confirmed. @@ -407,6 +407,7 @@ pub(crate) struct CoreCompletion { new_text: String, server_id: LanguageServerId, lsp_completion: lsp::CompletionItem, + resolved: bool, } /// A code action provided by a language server. @@ -3238,35 +3239,6 @@ impl Project { }) } - pub fn resolve_completions( - &self, - buffer: Model, - completion_indices: Vec, - completions: Rc>>, - cx: &mut ModelContext, - ) -> Task> { - self.lsp_store.update(cx, |lsp_store, cx| { - lsp_store.resolve_completions(buffer, completion_indices, completions, cx) - }) - } - - pub fn apply_additional_edits_for_completion( - &self, - buffer_handle: Model, - completion: Completion, - push_to_history: bool, - cx: &mut ModelContext, - ) -> Task>> { - self.lsp_store.update(cx, |lsp_store, cx| { - lsp_store.apply_additional_edits_for_completion( - buffer_handle, - completion, - push_to_history, - cx, - ) - }) - } - pub fn code_actions( &mut self, buffer_handle: &Model, diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index 83f5cfca47..7f33c0328d 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -937,6 +937,7 @@ message Completion { string new_text = 3; uint64 server_id = 4; bytes lsp_completion = 5; + bool resolved = 6; } message GetCodeActions { diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index 08ebe4dc3c..fb729e96b0 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -1637,6 +1637,7 @@ Or to set a `socks5` proxy: 2. `prefer_line` (deprecated, same as `none`) 3. `editor_width` to wrap lines that overflow the editor width 4. `preferred_line_length` to wrap lines that overflow `preferred_line_length` config value +5. `bounded` to wrap lines at the minimum of `editor_width` and `preferred_line_length` ## Wrap Guides (Vertical Rulers)