diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 27d6f52c6c..8ae491adaf 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -2350,7 +2350,7 @@ impl ContextEditor { editor.insert(&format!("/{name}"), cx); if command.requires_argument() { editor.insert(" ", cx); - editor.show_completions(&ShowCompletions, cx); + editor.show_completions(&ShowCompletions::default(), cx); } }); }); diff --git a/crates/assistant/src/slash_command.rs b/crates/assistant/src/slash_command.rs index bcc1d342c5..4bb4e01c2f 100644 --- a/crates/assistant/src/slash_command.rs +++ b/crates/assistant/src/slash_command.rs @@ -217,6 +217,7 @@ impl CompletionProvider for SlashCommandCompletionProvider { &self, buffer: &Model, buffer_position: Anchor, + _: editor::CompletionContext, cx: &mut ViewContext, ) -> Task>> { let Some((name, argument, command_range, argument_range)) = diff --git a/crates/collab/src/tests/random_project_collaboration_tests.rs b/crates/collab/src/tests/random_project_collaboration_tests.rs index e6698cc6fa..9b0b6f0617 100644 --- a/crates/collab/src/tests/random_project_collaboration_tests.rs +++ b/crates/collab/src/tests/random_project_collaboration_tests.rs @@ -14,7 +14,9 @@ use language::{ }; use lsp::FakeLanguageServer; use pretty_assertions::assert_eq; -use project::{search::SearchQuery, Project, ProjectPath, SearchResult}; +use project::{ + search::SearchQuery, Project, ProjectPath, SearchResult, DEFAULT_COMPLETION_CONTEXT, +}; use rand::{ distributions::{Alphanumeric, DistString}, prelude::*, @@ -829,7 +831,7 @@ impl RandomizedTest for ProjectCollaborationTest { .map_ok(|_| ()) .boxed(), LspRequestKind::Completion => project - .completions(&buffer, offset, cx) + .completions(&buffer, offset, DEFAULT_COMPLETION_CONTEXT, cx) .map_ok(|_| ()) .boxed(), LspRequestKind::CodeAction => project diff --git a/crates/collab_ui/src/chat_panel/message_editor.rs b/crates/collab_ui/src/chat_panel/message_editor.rs index 01074fd90f..b50fb6ffdf 100644 --- a/crates/collab_ui/src/chat_panel/message_editor.rs +++ b/crates/collab_ui/src/chat_panel/message_editor.rs @@ -46,6 +46,7 @@ impl CompletionProvider for MessageEditorCompletionProvider { &self, buffer: &Model, buffer_position: language::Anchor, + _: editor::CompletionContext, cx: &mut ViewContext, ) -> Task>> { let Some(handle) = self.0.upgrade() else { diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index e2a2c4d04f..4eb4a99528 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -125,6 +125,11 @@ pub struct ExpandExcerptsDown { #[serde(default)] pub(super) lines: u32, } +#[derive(PartialEq, Clone, Deserialize, Default)] +pub struct ShowCompletions { + #[serde(default)] + pub(super) trigger: Option, +} impl_actions!( editor, @@ -147,6 +152,7 @@ impl_actions!( SelectToBeginningOfLine, SelectToEndOfLine, SelectUpByLines, + ShowCompletions, ToggleCodeActions, ToggleComments, UnfoldAt, @@ -274,7 +280,6 @@ gpui::actions!( SelectToStartOfParagraph, SelectUp, ShowCharacterPalette, - ShowCompletions, ShowInlineCompletion, ShuffleLines, SortLinesCaseInsensitive, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 25975eaabe..404989b408 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -93,7 +93,8 @@ use linked_editing_ranges::refresh_linked_ranges; use task::{ResolvedTask, TaskTemplate, TaskVariables}; use hover_links::{HoverLink, HoveredLinkState, InlayHighlight}; -use lsp::{DiagnosticSeverity, LanguageServerId}; +pub use lsp::CompletionContext; +use lsp::{CompletionTriggerKind, DiagnosticSeverity, LanguageServerId}; use mouse_context_menu::MouseContextMenu; use movement::TextLayoutDetails; pub use multi_buffer::{ @@ -2300,7 +2301,7 @@ impl Editor { .detach(); if show_completions { - self.show_completions(&ShowCompletions, cx); + self.show_completions(&ShowCompletions { trigger: None }, cx); } } else { drop(context_menu); @@ -3494,7 +3495,12 @@ impl Editor { cx: &mut ViewContext, ) { if self.is_completion_trigger(text, trigger_in_words, cx) { - self.show_completions(&ShowCompletions, cx); + self.show_completions( + &ShowCompletions { + trigger: text.chars().last(), + }, + cx, + ); } else { self.hide_context_menu(cx); } @@ -3890,7 +3896,7 @@ impl Editor { })) } - pub fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext) { + pub fn show_completions(&mut self, options: &ShowCompletions, cx: &mut ViewContext) { if self.pending_rename.is_some() { return; } @@ -3908,7 +3914,29 @@ impl Editor { }; let query = Self::completion_query(&self.buffer.read(cx).read(cx), position); - let completions = provider.completions(&buffer, buffer_position, cx); + let is_followup_invoke = { + let context_menu_state = self.context_menu.read(); + matches!( + context_menu_state.deref(), + Some(ContextMenu::Completions(_)) + ) + }; + let trigger_kind = match (options.trigger, is_followup_invoke) { + (_, true) => CompletionTriggerKind::TRIGGER_FOR_INCOMPLETE_COMPLETIONS, + (Some(_), _) => CompletionTriggerKind::TRIGGER_CHARACTER, + _ => CompletionTriggerKind::INVOKED, + }; + let completion_context = CompletionContext { + trigger_character: options.trigger.and_then(|c| { + if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER { + Some(String::from(c)) + } else { + None + } + }), + trigger_kind, + }; + let completions = provider.completions(&buffer, buffer_position, completion_context, cx); let id = post_inc(&mut self.next_completion_id); let task = cx.spawn(|this, mut cx| { @@ -4166,7 +4194,7 @@ impl Editor { } if completion.show_new_completions_on_confirm { - self.show_completions(&ShowCompletions, cx); + self.show_completions(&ShowCompletions { trigger: None }, cx); } let provider = self.completion_provider.as_ref()?; @@ -11428,6 +11456,7 @@ pub trait CompletionProvider { &self, buffer: &Model, buffer_position: text::Anchor, + trigger: CompletionContext, cx: &mut ViewContext, ) -> Task>>; @@ -11462,10 +11491,11 @@ impl CompletionProvider for Model { &self, buffer: &Model, buffer_position: text::Anchor, + options: CompletionContext, cx: &mut ViewContext, ) -> Task>> { self.update(cx, |project, cx| { - project.completions(&buffer, buffer_position, cx) + project.completions(&buffer, buffer_position, options, cx) }) } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 45e24dc868..bc232a0170 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -6705,7 +6705,7 @@ async fn test_completion(cx: &mut gpui::TestAppContext) { cx.assert_editor_state("editor.cloˇ"); assert!(cx.editor(|e, _| e.context_menu.read().is_none())); cx.update_editor(|editor, cx| { - editor.show_completions(&ShowCompletions, cx); + editor.show_completions(&ShowCompletions { trigger: None }, cx); }); handle_completion_request( &mut cx, diff --git a/crates/extension/src/extension_store_test.rs b/crates/extension/src/extension_store_test.rs index 009b66ccca..6f068fc460 100644 --- a/crates/extension/src/extension_store_test.rs +++ b/crates/extension/src/extension_store_test.rs @@ -15,7 +15,7 @@ use http::{FakeHttpClient, Response}; use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName}; use node_runtime::FakeNodeRuntime; use parking_lot::Mutex; -use project::Project; +use project::{Project, DEFAULT_COMPLETION_CONTEXT}; use serde_json::json; use settings::{Settings as _, SettingsStore}; use std::{ @@ -657,7 +657,9 @@ async fn test_extension_store_with_gleam_extension(cx: &mut TestAppContext) { }); let completion_labels = project - .update(cx, |project, cx| project.completions(&buffer, 0, cx)) + .update(cx, |project, cx| { + project.completions(&buffer, 0, DEFAULT_COMPLETION_CONTEXT, cx) + }) .await .unwrap() .into_iter() diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index 7e2ae54952..3e3ac2557f 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -1061,7 +1061,7 @@ impl LanguageServer { select! { response = rx.fuse() => { let elapsed = started.elapsed(); - log::trace!("Took {elapsed:?} to receive response to {method:?} id {id}"); + log::info!("Took {elapsed:?} to receive response to {method:?} id {id}"); cancel_on_drop.abort(); response? } diff --git a/crates/project/src/lsp_command.rs b/crates/project/src/lsp_command.rs index caa2335ba3..e4d732bc8a 100644 --- a/crates/project/src/lsp_command.rs +++ b/crates/project/src/lsp_command.rs @@ -16,8 +16,9 @@ use language::{ OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped, }; use lsp::{ - CompletionListItemDefaultsEditRange, DocumentHighlightKind, LanguageServer, LanguageServerId, - LinkedEditingRangeServerCapabilities, OneOf, ServerCapabilities, + CompletionContext, CompletionListItemDefaultsEditRange, CompletionTriggerKind, + DocumentHighlightKind, LanguageServer, LanguageServerId, LinkedEditingRangeServerCapabilities, + OneOf, ServerCapabilities, }; use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc}; use text::{BufferId, LineEnding}; @@ -127,6 +128,7 @@ pub(crate) struct GetHover { pub(crate) struct GetCompletions { pub position: PointUtf16, + pub context: CompletionContext, } #[derive(Clone)] @@ -1464,7 +1466,7 @@ impl LspCommand for GetCompletions { lsp::TextDocumentIdentifier::new(lsp::Uri::from_file_path(path).unwrap().into()), point_to_lsp(self.position), ), - context: Default::default(), + context: Some(self.context.clone()), work_done_progress_params: Default::default(), partial_result_params: Default::default(), } @@ -1649,7 +1651,13 @@ impl LspCommand for GetCompletions { }) }) .ok_or_else(|| anyhow!("invalid position"))??; - Ok(Self { position }) + Ok(Self { + position, + context: CompletionContext { + trigger_kind: CompletionTriggerKind::INVOKED, + trigger_character: None, + }, + }) } fn response_to_proto( diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 05d0f4d4bc..1175fa1992 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -58,7 +58,7 @@ use language::{ }; use log::error; use lsp::{ - DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, + CompletionContext, DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, DocumentHighlightKind, Edit, FileSystemWatcher, LanguageServer, LanguageServerBinary, LanguageServerId, LspRequestFuture, MessageActionItem, OneOf, ServerCapabilities, ServerHealthStatus, ServerStatus, TextEdit, Uri, @@ -651,6 +651,12 @@ pub enum SearchResult { LimitReached, } +#[cfg(any(test, feature = "test-support"))] +pub const DEFAULT_COMPLETION_CONTEXT: CompletionContext = CompletionContext { + trigger_kind: lsp::CompletionTriggerKind::INVOKED, + trigger_character: None, +}; + impl Project { pub fn init_settings(cx: &mut AppContext) { WorktreeSettings::register(cx); @@ -5875,6 +5881,7 @@ impl Project { &self, buffer: &Model, position: PointUtf16, + context: CompletionContext, cx: &mut ModelContext, ) -> Task>> { let language_registry = self.languages.clone(); @@ -5908,7 +5915,10 @@ impl Project { this.request_lsp( buffer.clone(), LanguageServerToQuery::Other(server_id), - GetCompletions { position }, + GetCompletions { + position, + context: context.clone(), + }, cx, ), )); @@ -5935,7 +5945,7 @@ impl Project { let task = self.send_lsp_proto_request( buffer.clone(), project_id, - GetCompletions { position }, + GetCompletions { position, context }, cx, ); let language = buffer.read(cx).language().cloned(); @@ -5969,10 +5979,11 @@ impl Project { &self, buffer: &Model, position: T, + context: CompletionContext, cx: &mut ModelContext, ) -> Task>> { let position = position.to_point_utf16(buffer.read(cx)); - self.completions_impl(buffer, position, cx) + self.completions_impl(buffer, position, context, cx) } pub fn resolve_completions( diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 285065c324..293a8d5867 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -2499,7 +2499,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) { let text = "let a = b.fqn"; buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); let completions = project.update(cx, |project, cx| { - project.completions(&buffer, text.len(), cx) + project.completions(&buffer, text.len(), DEFAULT_COMPLETION_CONTEXT, cx) }); fake_server @@ -2526,7 +2526,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) { let text = "let a = \"atoms/cmp\""; buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); let completions = project.update(cx, |project, cx| { - project.completions(&buffer, text.len() - 1, cx) + project.completions(&buffer, text.len() - 1, DEFAULT_COMPLETION_CONTEXT, cx) }); fake_server @@ -2591,7 +2591,7 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) { let text = "let a = b.fqn"; buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); let completions = project.update(cx, |project, cx| { - project.completions(&buffer, text.len(), cx) + project.completions(&buffer, text.len(), DEFAULT_COMPLETION_CONTEXT, cx) }); fake_server