lsp: Provide completion reason in the request (#12893)

This should help LS make a better call about the completions it should
return back to the caller. For example, it speeds up import completions
for typescript.
Before: 


https://github.com/zed-industries/zed/assets/24362066/b38fd565-f9ff-4db7-a87f-c3b31a9fdc96

after: 


https://github.com/zed-industries/zed/assets/24362066/d4fbc9ae-9aab-4543-b9f6-16acf1619576


This should be merged after 06.12 Preview to give it some time on
Nightly.

Release Notes:

- N/A
This commit is contained in:
Piotr Osiewicz 2024-06-13 14:38:34 +02:00 committed by GitHub
parent eb7b5a7131
commit 0a13b9ee01
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 86 additions and 26 deletions

View file

@ -2350,7 +2350,7 @@ impl ContextEditor {
editor.insert(&format!("/{name}"), cx); editor.insert(&format!("/{name}"), cx);
if command.requires_argument() { if command.requires_argument() {
editor.insert(" ", cx); editor.insert(" ", cx);
editor.show_completions(&ShowCompletions, cx); editor.show_completions(&ShowCompletions::default(), cx);
} }
}); });
}); });

View file

@ -217,6 +217,7 @@ impl CompletionProvider for SlashCommandCompletionProvider {
&self, &self,
buffer: &Model<Buffer>, buffer: &Model<Buffer>,
buffer_position: Anchor, buffer_position: Anchor,
_: editor::CompletionContext,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) -> Task<Result<Vec<project::Completion>>> { ) -> Task<Result<Vec<project::Completion>>> {
let Some((name, argument, command_range, argument_range)) = let Some((name, argument, command_range, argument_range)) =

View file

@ -14,7 +14,9 @@ use language::{
}; };
use lsp::FakeLanguageServer; use lsp::FakeLanguageServer;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use project::{search::SearchQuery, Project, ProjectPath, SearchResult}; use project::{
search::SearchQuery, Project, ProjectPath, SearchResult, DEFAULT_COMPLETION_CONTEXT,
};
use rand::{ use rand::{
distributions::{Alphanumeric, DistString}, distributions::{Alphanumeric, DistString},
prelude::*, prelude::*,
@ -829,7 +831,7 @@ impl RandomizedTest for ProjectCollaborationTest {
.map_ok(|_| ()) .map_ok(|_| ())
.boxed(), .boxed(),
LspRequestKind::Completion => project LspRequestKind::Completion => project
.completions(&buffer, offset, cx) .completions(&buffer, offset, DEFAULT_COMPLETION_CONTEXT, cx)
.map_ok(|_| ()) .map_ok(|_| ())
.boxed(), .boxed(),
LspRequestKind::CodeAction => project LspRequestKind::CodeAction => project

View file

@ -46,6 +46,7 @@ impl CompletionProvider for MessageEditorCompletionProvider {
&self, &self,
buffer: &Model<Buffer>, buffer: &Model<Buffer>,
buffer_position: language::Anchor, buffer_position: language::Anchor,
_: editor::CompletionContext,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) -> Task<anyhow::Result<Vec<Completion>>> { ) -> Task<anyhow::Result<Vec<Completion>>> {
let Some(handle) = self.0.upgrade() else { let Some(handle) = self.0.upgrade() else {

View file

@ -125,6 +125,11 @@ pub struct ExpandExcerptsDown {
#[serde(default)] #[serde(default)]
pub(super) lines: u32, pub(super) lines: u32,
} }
#[derive(PartialEq, Clone, Deserialize, Default)]
pub struct ShowCompletions {
#[serde(default)]
pub(super) trigger: Option<char>,
}
impl_actions!( impl_actions!(
editor, editor,
@ -147,6 +152,7 @@ impl_actions!(
SelectToBeginningOfLine, SelectToBeginningOfLine,
SelectToEndOfLine, SelectToEndOfLine,
SelectUpByLines, SelectUpByLines,
ShowCompletions,
ToggleCodeActions, ToggleCodeActions,
ToggleComments, ToggleComments,
UnfoldAt, UnfoldAt,
@ -274,7 +280,6 @@ gpui::actions!(
SelectToStartOfParagraph, SelectToStartOfParagraph,
SelectUp, SelectUp,
ShowCharacterPalette, ShowCharacterPalette,
ShowCompletions,
ShowInlineCompletion, ShowInlineCompletion,
ShuffleLines, ShuffleLines,
SortLinesCaseInsensitive, SortLinesCaseInsensitive,

View file

@ -93,7 +93,8 @@ use linked_editing_ranges::refresh_linked_ranges;
use task::{ResolvedTask, TaskTemplate, TaskVariables}; use task::{ResolvedTask, TaskTemplate, TaskVariables};
use hover_links::{HoverLink, HoveredLinkState, InlayHighlight}; 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 mouse_context_menu::MouseContextMenu;
use movement::TextLayoutDetails; use movement::TextLayoutDetails;
pub use multi_buffer::{ pub use multi_buffer::{
@ -2300,7 +2301,7 @@ impl Editor {
.detach(); .detach();
if show_completions { if show_completions {
self.show_completions(&ShowCompletions, cx); self.show_completions(&ShowCompletions { trigger: None }, cx);
} }
} else { } else {
drop(context_menu); drop(context_menu);
@ -3494,7 +3495,12 @@ impl Editor {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
if self.is_completion_trigger(text, trigger_in_words, cx) { 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 { } else {
self.hide_context_menu(cx); self.hide_context_menu(cx);
} }
@ -3890,7 +3896,7 @@ impl Editor {
})) }))
} }
pub fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext<Self>) { pub fn show_completions(&mut self, options: &ShowCompletions, cx: &mut ViewContext<Self>) {
if self.pending_rename.is_some() { if self.pending_rename.is_some() {
return; return;
} }
@ -3908,7 +3914,29 @@ impl Editor {
}; };
let query = Self::completion_query(&self.buffer.read(cx).read(cx), position); 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 id = post_inc(&mut self.next_completion_id);
let task = cx.spawn(|this, mut cx| { let task = cx.spawn(|this, mut cx| {
@ -4166,7 +4194,7 @@ impl Editor {
} }
if completion.show_new_completions_on_confirm { 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()?; let provider = self.completion_provider.as_ref()?;
@ -11428,6 +11456,7 @@ pub trait CompletionProvider {
&self, &self,
buffer: &Model<Buffer>, buffer: &Model<Buffer>,
buffer_position: text::Anchor, buffer_position: text::Anchor,
trigger: CompletionContext,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) -> Task<Result<Vec<Completion>>>; ) -> Task<Result<Vec<Completion>>>;
@ -11462,10 +11491,11 @@ impl CompletionProvider for Model<Project> {
&self, &self,
buffer: &Model<Buffer>, buffer: &Model<Buffer>,
buffer_position: text::Anchor, buffer_position: text::Anchor,
options: CompletionContext,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) -> Task<Result<Vec<Completion>>> { ) -> Task<Result<Vec<Completion>>> {
self.update(cx, |project, cx| { self.update(cx, |project, cx| {
project.completions(&buffer, buffer_position, cx) project.completions(&buffer, buffer_position, options, cx)
}) })
} }

View file

@ -6705,7 +6705,7 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
cx.assert_editor_state("editor.cloˇ"); cx.assert_editor_state("editor.cloˇ");
assert!(cx.editor(|e, _| e.context_menu.read().is_none())); assert!(cx.editor(|e, _| e.context_menu.read().is_none()));
cx.update_editor(|editor, cx| { cx.update_editor(|editor, cx| {
editor.show_completions(&ShowCompletions, cx); editor.show_completions(&ShowCompletions { trigger: None }, cx);
}); });
handle_completion_request( handle_completion_request(
&mut cx, &mut cx,

View file

@ -15,7 +15,7 @@ use http::{FakeHttpClient, Response};
use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName}; use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName};
use node_runtime::FakeNodeRuntime; use node_runtime::FakeNodeRuntime;
use parking_lot::Mutex; use parking_lot::Mutex;
use project::Project; use project::{Project, DEFAULT_COMPLETION_CONTEXT};
use serde_json::json; use serde_json::json;
use settings::{Settings as _, SettingsStore}; use settings::{Settings as _, SettingsStore};
use std::{ use std::{
@ -657,7 +657,9 @@ async fn test_extension_store_with_gleam_extension(cx: &mut TestAppContext) {
}); });
let completion_labels = project 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 .await
.unwrap() .unwrap()
.into_iter() .into_iter()

View file

@ -1061,7 +1061,7 @@ impl LanguageServer {
select! { select! {
response = rx.fuse() => { response = rx.fuse() => {
let elapsed = started.elapsed(); 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(); cancel_on_drop.abort();
response? response?
} }

View file

@ -16,8 +16,9 @@ use language::{
OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped, OffsetRangeExt, PointUtf16, ToOffset, ToPointUtf16, Transaction, Unclipped,
}; };
use lsp::{ use lsp::{
CompletionListItemDefaultsEditRange, DocumentHighlightKind, LanguageServer, LanguageServerId, CompletionContext, CompletionListItemDefaultsEditRange, CompletionTriggerKind,
LinkedEditingRangeServerCapabilities, OneOf, ServerCapabilities, DocumentHighlightKind, LanguageServer, LanguageServerId, LinkedEditingRangeServerCapabilities,
OneOf, ServerCapabilities,
}; };
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc}; use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
use text::{BufferId, LineEnding}; use text::{BufferId, LineEnding};
@ -127,6 +128,7 @@ pub(crate) struct GetHover {
pub(crate) struct GetCompletions { pub(crate) struct GetCompletions {
pub position: PointUtf16, pub position: PointUtf16,
pub context: CompletionContext,
} }
#[derive(Clone)] #[derive(Clone)]
@ -1464,7 +1466,7 @@ impl LspCommand for GetCompletions {
lsp::TextDocumentIdentifier::new(lsp::Uri::from_file_path(path).unwrap().into()), lsp::TextDocumentIdentifier::new(lsp::Uri::from_file_path(path).unwrap().into()),
point_to_lsp(self.position), point_to_lsp(self.position),
), ),
context: Default::default(), context: Some(self.context.clone()),
work_done_progress_params: Default::default(), work_done_progress_params: Default::default(),
partial_result_params: Default::default(), partial_result_params: Default::default(),
} }
@ -1649,7 +1651,13 @@ impl LspCommand for GetCompletions {
}) })
}) })
.ok_or_else(|| anyhow!("invalid position"))??; .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( fn response_to_proto(

View file

@ -58,7 +58,7 @@ use language::{
}; };
use log::error; use log::error;
use lsp::{ use lsp::{
DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions, CompletionContext, DiagnosticSeverity, DiagnosticTag, DidChangeWatchedFilesRegistrationOptions,
DocumentHighlightKind, Edit, FileSystemWatcher, LanguageServer, LanguageServerBinary, DocumentHighlightKind, Edit, FileSystemWatcher, LanguageServer, LanguageServerBinary,
LanguageServerId, LspRequestFuture, MessageActionItem, OneOf, ServerCapabilities, LanguageServerId, LspRequestFuture, MessageActionItem, OneOf, ServerCapabilities,
ServerHealthStatus, ServerStatus, TextEdit, Uri, ServerHealthStatus, ServerStatus, TextEdit, Uri,
@ -651,6 +651,12 @@ pub enum SearchResult {
LimitReached, LimitReached,
} }
#[cfg(any(test, feature = "test-support"))]
pub const DEFAULT_COMPLETION_CONTEXT: CompletionContext = CompletionContext {
trigger_kind: lsp::CompletionTriggerKind::INVOKED,
trigger_character: None,
};
impl Project { impl Project {
pub fn init_settings(cx: &mut AppContext) { pub fn init_settings(cx: &mut AppContext) {
WorktreeSettings::register(cx); WorktreeSettings::register(cx);
@ -5875,6 +5881,7 @@ impl Project {
&self, &self,
buffer: &Model<Buffer>, buffer: &Model<Buffer>,
position: PointUtf16, position: PointUtf16,
context: CompletionContext,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<Vec<Completion>>> { ) -> Task<Result<Vec<Completion>>> {
let language_registry = self.languages.clone(); let language_registry = self.languages.clone();
@ -5908,7 +5915,10 @@ impl Project {
this.request_lsp( this.request_lsp(
buffer.clone(), buffer.clone(),
LanguageServerToQuery::Other(server_id), LanguageServerToQuery::Other(server_id),
GetCompletions { position }, GetCompletions {
position,
context: context.clone(),
},
cx, cx,
), ),
)); ));
@ -5935,7 +5945,7 @@ impl Project {
let task = self.send_lsp_proto_request( let task = self.send_lsp_proto_request(
buffer.clone(), buffer.clone(),
project_id, project_id,
GetCompletions { position }, GetCompletions { position, context },
cx, cx,
); );
let language = buffer.read(cx).language().cloned(); let language = buffer.read(cx).language().cloned();
@ -5969,10 +5979,11 @@ impl Project {
&self, &self,
buffer: &Model<Buffer>, buffer: &Model<Buffer>,
position: T, position: T,
context: CompletionContext,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Task<Result<Vec<Completion>>> { ) -> Task<Result<Vec<Completion>>> {
let position = position.to_point_utf16(buffer.read(cx)); 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( pub fn resolve_completions(

View file

@ -2499,7 +2499,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
let text = "let a = b.fqn"; let text = "let a = b.fqn";
buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
let completions = project.update(cx, |project, 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 fake_server
@ -2526,7 +2526,7 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
let text = "let a = \"atoms/cmp\""; let text = "let a = \"atoms/cmp\"";
buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
let completions = project.update(cx, |project, 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 fake_server
@ -2591,7 +2591,7 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
let text = "let a = b.fqn"; let text = "let a = b.fqn";
buffer.update(cx, |buffer, cx| buffer.set_text(text, cx)); buffer.update(cx, |buffer, cx| buffer.set_text(text, cx));
let completions = project.update(cx, |project, 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 fake_server