diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index ac197a7456..b12d77426b 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -145,7 +145,12 @@ impl ProjectDiagnosticsEditor { let excerpts = cx.add_model(|cx| MultiBuffer::new(project.read(cx).replica_id())); let build_settings = editor::settings_builder(excerpts.downgrade(), settings.clone()); let editor = cx.add_view(|cx| { - let mut editor = Editor::for_buffer(excerpts.clone(), build_settings.clone(), cx); + let mut editor = Editor::for_buffer( + excerpts.clone(), + build_settings.clone(), + Some(workspace.clone()), + cx, + ); editor.set_vertical_scroll_margin(5, cx); editor }); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ae9381d2f0..d8117fb494 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -415,6 +415,7 @@ pub struct Editor { scroll_top_anchor: Option, autoscroll_request: Option, build_settings: BuildSettings, + workspace: Option>, focused: bool, show_local_cursors: bool, blink_epoch: usize, @@ -515,7 +516,8 @@ impl ContextMenu { struct CompletionsMenu { id: CompletionId, initial_position: Anchor, - completions: Arc<[Completion]>, + buffer: ModelHandle, + completions: Arc<[Completion]>, match_candidates: Vec, matches: Arc<[StringMatch]>, selected_item: usize, @@ -750,7 +752,7 @@ impl Editor { pub fn single_line(build_settings: BuildSettings, cx: &mut ViewContext) -> Self { let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let mut view = Self::for_buffer(buffer, build_settings, cx); + let mut view = Self::for_buffer(buffer, build_settings, None, cx); view.mode = EditorMode::SingleLine; view } @@ -762,7 +764,7 @@ impl Editor { ) -> Self { let buffer = cx.add_model(|cx| Buffer::new(0, String::new(), cx)); let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx)); - let mut view = Self::for_buffer(buffer, build_settings, cx); + let mut view = Self::for_buffer(buffer, build_settings, None, cx); view.mode = EditorMode::AutoHeight { max_lines }; view } @@ -770,13 +772,19 @@ impl Editor { pub fn for_buffer( buffer: ModelHandle, build_settings: BuildSettings, + workspace: Option>, cx: &mut ViewContext, ) -> Self { - Self::new(buffer, build_settings, cx) + Self::new(buffer, build_settings, workspace, cx) } pub fn clone(&self, cx: &mut ViewContext) -> Self { - let mut clone = Self::new(self.buffer.clone(), self.build_settings.clone(), cx); + let mut clone = Self::new( + self.buffer.clone(), + self.build_settings.clone(), + self.workspace.clone(), + cx, + ); clone.scroll_position = self.scroll_position; clone.scroll_top_anchor = self.scroll_top_anchor.clone(); clone.nav_history = self @@ -789,6 +797,7 @@ impl Editor { pub fn new( buffer: ModelHandle, build_settings: BuildSettings, + workspace: Option>, cx: &mut ViewContext, ) -> Self { let settings = build_settings(cx); @@ -823,6 +832,7 @@ impl Editor { select_larger_syntax_node_stack: Vec::new(), active_diagnostics: None, build_settings, + workspace, scroll_position: Vector2F::zero(), scroll_top_anchor: None, autoscroll_request: None, @@ -1872,16 +1882,26 @@ impl Editor { } fn show_completions(&mut self, _: &ShowCompletions, cx: &mut ViewContext) { + let project = if let Some(workspace) = self.workspace.as_ref().and_then(|w| w.upgrade(cx)) { + workspace.read(cx).project().clone() + } else { + return; + }; + let position = if let Some(selection) = self.newest_anchor_selection() { selection.head() } else { return; }; + let (buffer, buffer_position) = self + .buffer + .read(cx) + .text_anchor_for_position(position.clone(), cx); let query = Self::completion_query(&self.buffer.read(cx).read(cx), position.clone()); - let completions = self - .buffer - .update(cx, |buffer, cx| buffer.completions(position.clone(), cx)); + let completions = project.update(cx, |project, cx| { + project.completions(&buffer, buffer_position.clone(), cx) + }); let id = post_inc(&mut self.next_completion_id); let task = cx.spawn_weak(|this, mut cx| { @@ -1904,6 +1924,7 @@ impl Editor { ) }) .collect(), + buffer, completions: completions.into(), matches: Vec::new().into(), selected_item: 0, @@ -1953,6 +1974,7 @@ impl Editor { let mat = completions_menu .matches .get(completion_ix.unwrap_or(completions_menu.selected_item))?; + let buffer_handle = completions_menu.buffer; let completion = completions_menu.completions.get(mat.candidate_id)?; let snippet; @@ -1964,11 +1986,9 @@ impl Editor { snippet = None; text = completion.new_text.clone(); }; - let snapshot = self.buffer.read(cx).snapshot(cx); - let old_range = completion.old_range.to_offset(&snapshot); - let old_text = snapshot - .text_for_range(old_range.clone()) - .collect::(); + let buffer = buffer_handle.read(cx); + let old_range = completion.old_range.to_offset(&buffer); + let old_text = buffer.text_for_range(old_range.clone()).collect::(); let selections = self.local_selections::(cx); let newest_selection = selections.iter().max_by_key(|s| s.id)?; @@ -1982,7 +2002,7 @@ impl Editor { let mut ranges = Vec::new(); for selection in &selections { - if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) { + if buffer.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) { let start = selection.start.saturating_sub(lookbehind); let end = selection.end + lookahead; ranges.push(start + common_prefix_len..end); @@ -2017,41 +2037,51 @@ impl Editor { } self.end_transaction(cx); - Some(self.buffer.update(cx, |buffer, cx| { - buffer.apply_additional_edits_for_completion(completion.clone(), cx) + let project = self + .workspace + .as_ref()? + .upgrade(cx)? + .read(cx) + .project() + .clone(); + let apply_edits = project.update(cx, |project, cx| { + project.apply_additional_edits_for_completion( + buffer_handle, + completion.clone(), + true, + cx, + ) + }); + Some(cx.foreground().spawn(async move { + apply_edits.await?; + Ok(()) })) } - fn show_code_actions( - workspace: &mut Workspace, - _: &ShowCodeActions, - cx: &mut ViewContext, - ) { - let active_item = workspace.active_item(cx); - let editor_handle = if let Some(editor) = active_item - .as_ref() - .and_then(|item| item.act_as::(cx)) - { - editor - } else { - return; - }; - - let editor = editor_handle.read(cx); - let head = if let Some(selection) = editor.newest_anchor_selection() { + fn show_code_actions(&mut self, _: &ShowCodeActions, cx: &mut ViewContext) { + let head = if let Some(selection) = self.newest_anchor_selection() { selection.head() } else { return; }; - let (buffer, head) = editor.buffer.read(cx).text_anchor_for_position(head, cx); + let workspace = if let Some(workspace) = self.workspace.as_ref().and_then(|w| w.upgrade(cx)) + { + workspace + } else { + return; + }; + + let (buffer, head) = self.buffer.read(cx).text_anchor_for_position(head, cx); let actions = workspace + .read(cx) .project() + .clone() .update(cx, |project, cx| project.code_actions(&buffer, head, cx)); - cx.spawn(|_, mut cx| async move { + cx.spawn(|this, mut cx| async move { let actions = actions.await?; if !actions.is_empty() { - editor_handle.update(&mut cx, |this, cx| { + this.update(&mut cx, |this, cx| { if this.focused { this.show_context_menu( ContextMenu::CodeActions(CodeActionsMenu { @@ -2071,29 +2101,31 @@ impl Editor { } fn confirm_code_action( - workspace: &mut Workspace, + &mut self, ConfirmCodeAction(action_ix): &ConfirmCodeAction, - cx: &mut ViewContext, + cx: &mut ViewContext, ) -> Option>> { - let active_item = workspace.active_item(cx)?; - let editor = active_item.act_as::(cx)?; - let (buffer, action) = editor.update(cx, |editor, cx| { - let actions_menu = - if let ContextMenu::CodeActions(menu) = editor.hide_context_menu(cx)? { - menu - } else { - return None; - }; - let action_ix = action_ix.unwrap_or(actions_menu.selected_item); - let action = actions_menu.actions.get(action_ix)?.clone(); - Some((actions_menu.buffer, action)) - })?; + let workspace = self.workspace.as_ref()?.upgrade(cx)?; - let apply_code_actions = workspace.project().update(cx, |project, cx| { - project.apply_code_action(buffer, action, true, cx) - }); - Some(cx.spawn(|workspace, mut cx| async move { + let actions_menu = if let ContextMenu::CodeActions(menu) = self.hide_context_menu(cx)? { + menu + } else { + return None; + }; + let action_ix = action_ix.unwrap_or(actions_menu.selected_item); + let action = actions_menu.actions.get(action_ix)?.clone(); + let buffer = actions_menu.buffer; + + let apply_code_actions = workspace + .read(cx) + .project() + .clone() + .update(cx, |project, cx| { + project.apply_code_action(buffer, action, true, cx) + }); + Some(cx.spawn(|_, mut cx| async move { let project_transaction = apply_code_actions.await?; + // TODO: replace this with opening a single tab that is a multibuffer workspace.update(&mut cx, |workspace, cx| { for (buffer, _) in project_transaction.0 { @@ -7527,6 +7559,7 @@ mod tests { three " .unindent(); + let buffer = cx.add_model(|cx| { Buffer::from_file( 0, @@ -8217,7 +8250,7 @@ mod tests { settings: EditorSettings, cx: &mut ViewContext, ) -> Editor { - Editor::for_buffer(buffer, Arc::new(move |_| settings.clone()), cx) + Editor::for_buffer(buffer, Arc::new(move |_| settings.clone()), None, cx) } } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 37b53b0141..1f347b09a5 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1298,6 +1298,7 @@ mod tests { let settings = settings.clone(); Arc::new(move |_| settings.clone()) }, + None, cx, ) }); diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 97ce056152..d9c7e180f5 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -55,6 +55,7 @@ impl ItemHandle for BufferItemHandle { let mut editor = Editor::for_buffer( buffer, crate::settings_builder(weak_buffer, workspace.settings()), + Some(workspace.weak_handle()), cx, ); editor.nav_history = Some(ItemNavHistory::new(nav_history, &cx.handle())); diff --git a/crates/editor/src/multi_buffer.rs b/crates/editor/src/multi_buffer.rs index 9a33de3a0f..2dd84d2c99 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/editor/src/multi_buffer.rs @@ -861,41 +861,6 @@ impl MultiBuffer { }) } - pub fn completions( - &self, - position: T, - cx: &mut ModelContext, - ) -> Task>>> - where - T: ToOffset, - { - let anchor = self.read(cx).anchor_before(position); - let buffer = self.buffers.borrow()[&anchor.buffer_id].buffer.clone(); - let completions = - buffer.update(cx, |buffer, cx| buffer.completions(anchor.text_anchor, cx)); - cx.spawn(|this, cx| async move { - completions.await.map(|completions| { - let snapshot = this.read_with(&cx, |buffer, cx| buffer.snapshot(cx)); - completions - .into_iter() - .map(|completion| Completion { - old_range: snapshot.anchor_in_excerpt( - anchor.excerpt_id.clone(), - completion.old_range.start, - ) - ..snapshot.anchor_in_excerpt( - anchor.excerpt_id.clone(), - completion.old_range.end, - ), - new_text: completion.new_text, - label: completion.label, - lsp_completion: completion.lsp_completion, - }) - .collect() - }) - }) - } - pub fn is_completion_trigger(&self, position: T, text: &str, cx: &AppContext) -> bool where T: ToOffset, @@ -924,40 +889,6 @@ impl MultiBuffer { .any(|string| string == text) } - pub fn apply_additional_edits_for_completion( - &self, - completion: Completion, - cx: &mut ModelContext, - ) -> Task> { - let buffer = if let Some(buffer_state) = self - .buffers - .borrow() - .get(&completion.old_range.start.buffer_id) - { - buffer_state.buffer.clone() - } else { - return Task::ready(Ok(())); - }; - - let apply_edits = buffer.update(cx, |buffer, cx| { - buffer.apply_additional_edits_for_completion( - Completion { - old_range: completion.old_range.start.text_anchor - ..completion.old_range.end.text_anchor, - new_text: completion.new_text, - label: completion.label, - lsp_completion: completion.lsp_completion, - }, - true, - cx, - ) - }); - cx.foreground().spawn(async move { - apply_edits.await?; - Ok(()) - }) - } - pub fn language<'a>(&self, cx: &'a AppContext) -> Option<&'a Arc> { self.buffers .borrow() diff --git a/crates/find/src/find.rs b/crates/find/src/find.rs index 8844f2cf90..cb50c80123 100644 --- a/crates/find/src/find.rs +++ b/crates/find/src/find.rs @@ -655,7 +655,7 @@ mod tests { ) }); let editor = cx.add_view(Default::default(), |cx| { - Editor::new(buffer.clone(), Arc::new(EditorSettings::test), cx) + Editor::new(buffer.clone(), Arc::new(EditorSettings::test), None, cx) }); let find_bar = cx.add_view(Default::default(), |cx| { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 2b5c979455..7b903c67b0 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -38,7 +38,7 @@ use text::{operation_queue::OperationQueue, rope::TextDimension}; pub use text::{Buffer as TextBuffer, Operation as _, *}; use theme::SyntaxTheme; use tree_sitter::{InputEdit, QueryCursor, Tree}; -use util::{post_inc, ResultExt, TryFutureExt as _}; +use util::{post_inc, TryFutureExt as _}; #[cfg(any(test, feature = "test-support"))] pub use tree_sitter_rust; @@ -111,8 +111,8 @@ pub struct Diagnostic { } #[derive(Clone, Debug)] -pub struct Completion { - pub old_range: Range, +pub struct Completion { + pub old_range: Range, pub new_text: String, pub label: CompletionLabel, pub lsp_completion: lsp::CompletionItem, @@ -201,21 +201,6 @@ pub trait File { fn format_remote(&self, buffer_id: u64, cx: &mut MutableAppContext) -> Option>>; - fn completions( - &self, - buffer_id: u64, - position: Anchor, - language: Option>, - cx: &mut MutableAppContext, - ) -> Task>>>; - - fn apply_additional_edits_for_completion( - &self, - buffer_id: u64, - completion: Completion, - cx: &mut MutableAppContext, - ) -> Task>>; - fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext); fn buffer_removed(&self, buffer_id: u64, cx: &mut MutableAppContext); @@ -285,25 +270,6 @@ impl File for FakeFile { None } - fn completions( - &self, - _: u64, - _: Anchor, - _: Option>, - _: &mut MutableAppContext, - ) -> Task>>> { - Task::ready(Ok(Default::default())) - } - - fn apply_additional_edits_for_completion( - &self, - _: u64, - _: Completion, - _: &mut MutableAppContext, - ) -> Task>> { - Task::ready(Ok(Default::default())) - } - fn buffer_updated(&self, _: u64, _: Operation, _: &mut MutableAppContext) {} fn buffer_removed(&self, _: u64, _: &mut MutableAppContext) {} @@ -1762,157 +1728,6 @@ impl Buffer { } } - pub fn completions( - &self, - position: T, - cx: &mut ModelContext, - ) -> Task>>> - where - T: ToOffset, - { - let file = if let Some(file) = self.file.as_ref() { - file - } else { - return Task::ready(Ok(Default::default())); - }; - let language = self.language.clone(); - - if let Some(file) = file.as_local() { - let server = if let Some(language_server) = self.language_server.as_ref() { - language_server.server.clone() - } else { - return Task::ready(Ok(Default::default())); - }; - let abs_path = file.abs_path(cx); - let position = self.offset_to_point_utf16(position.to_offset(self)); - - cx.spawn(|this, cx| async move { - let completions = server - .request::(lsp::CompletionParams { - text_document_position: lsp::TextDocumentPositionParams::new( - lsp::TextDocumentIdentifier::new( - lsp::Url::from_file_path(abs_path).unwrap(), - ), - position.to_lsp_position(), - ), - context: Default::default(), - work_done_progress_params: Default::default(), - partial_result_params: Default::default(), - }) - .await?; - - let completions = if let Some(completions) = completions { - match completions { - lsp::CompletionResponse::Array(completions) => completions, - lsp::CompletionResponse::List(list) => list.items, - } - } else { - Default::default() - }; - - this.read_with(&cx, |this, _| { - Ok(completions.into_iter().filter_map(|lsp_completion| { - let (old_range, new_text) = match lsp_completion.text_edit.as_ref()? { - lsp::CompletionTextEdit::Edit(edit) => (range_from_lsp(edit.range), edit.new_text.clone()), - lsp::CompletionTextEdit::InsertAndReplace(_) => { - log::info!("received an insert and replace completion but we don't yet support that"); - return None - }, - }; - - let clipped_start = this.clip_point_utf16(old_range.start, Bias::Left); - let clipped_end = this.clip_point_utf16(old_range.end, Bias::Left) ; - if clipped_start == old_range.start && clipped_end == old_range.end { - Some(Completion { - old_range: this.anchor_before(old_range.start)..this.anchor_after(old_range.end), - new_text, - label: language.as_ref().and_then(|l| l.label_for_completion(&lsp_completion)).unwrap_or_else(|| CompletionLabel::plain(&lsp_completion)), - lsp_completion, - }) - } else { - None - } - }).collect()) - }) - }) - } else { - file.completions( - self.remote_id(), - self.anchor_before(position), - language, - cx.as_mut(), - ) - } - } - - pub fn apply_additional_edits_for_completion( - &mut self, - completion: Completion, - push_to_history: bool, - cx: &mut ModelContext, - ) -> Task>> { - let file = if let Some(file) = self.file.as_ref() { - file - } else { - return Task::ready(Ok(Default::default())); - }; - - if file.is_local() { - let server = if let Some(lang) = self.language_server.as_ref() { - lang.server.clone() - } else { - return Task::ready(Ok(Default::default())); - }; - - cx.spawn(|this, mut cx| async move { - let resolved_completion = server - .request::(completion.lsp_completion) - .await?; - if let Some(additional_edits) = resolved_completion.additional_text_edits { - this.update(&mut cx, |this, cx| { - this.finalize_last_transaction(); - this.start_transaction(); - this.apply_lsp_edits(additional_edits, None, cx).log_err(); - let transaction = if this.end_transaction(cx).is_some() { - let transaction = this.finalize_last_transaction().unwrap().clone(); - if !push_to_history { - this.forget_transaction(transaction.id); - } - Some(transaction) - } else { - None - }; - Ok(transaction) - }) - } else { - Ok(None) - } - }) - } else { - let apply_edits = file.apply_additional_edits_for_completion( - self.remote_id(), - completion, - cx.as_mut(), - ); - cx.spawn(|this, mut cx| async move { - if let Some(transaction) = apply_edits.await? { - this.update(&mut cx, |this, _| { - this.wait_for_edits(transaction.edit_ids.iter().copied()) - }) - .await; - if push_to_history { - this.update(&mut cx, |this, _| { - this.push_transaction(transaction.clone(), Instant::now()); - }); - } - Ok(Some(transaction)) - } else { - Ok(None) - } - }) - } - } - pub fn completion_triggers(&self) -> &[String] { &self.completion_triggers } @@ -2737,7 +2552,7 @@ impl Default for Diagnostic { } } -impl Completion { +impl Completion { pub fn sort_key(&self) -> (usize, &str) { let kind_key = match self.lsp_completion.kind { Some(lsp::CompletionItemKind::VARIABLE) => 0, diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index fbcd5efa78..44cbba47fb 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -394,7 +394,7 @@ pub fn lamport_timestamp_for_operation(operation: &proto::Operation) -> Option) -> proto::Completion { +pub fn serialize_completion(completion: &Completion) -> proto::Completion { proto::Completion { old_start: Some(serialize_anchor(&completion.old_range.start)), old_end: Some(serialize_anchor(&completion.old_range.end)), @@ -406,7 +406,7 @@ pub fn serialize_completion(completion: &Completion) -> proto::Completio pub fn deserialize_completion( completion: proto::Completion, language: Option<&Arc>, -) -> Result> { +) -> Result { let old_start = completion .old_start .and_then(deserialize_anchor) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 77bea77d10..2d15275f22 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -15,8 +15,9 @@ use gpui::{ use language::{ point_from_lsp, proto::{deserialize_anchor, serialize_anchor}, - range_from_lsp, Bias, Buffer, CodeAction, Diagnostic, DiagnosticEntry, File as _, Language, - LanguageRegistry, PointUtf16, ToLspPosition, ToOffset, ToPointUtf16, + range_from_lsp, Bias, Buffer, CodeAction, Completion, CompletionLabel, Diagnostic, + DiagnosticEntry, File as _, Language, LanguageRegistry, PointUtf16, ToLspPosition, + ToPointUtf16, Transaction, }; use lsp::{DiagnosticSeverity, LanguageServer}; use postage::{prelude::Stream, watch}; @@ -1035,7 +1036,7 @@ impl Project { Ok(()) } - pub fn definition( + pub fn definition( &self, source_buffer_handle: &ModelHandle, position: T, @@ -1052,8 +1053,9 @@ impl Project { return Task::ready(Err(anyhow!("buffer does not belong to any worktree"))); }; + let position = position.to_point_utf16(source_buffer); + if worktree.read(cx).as_local().is_some() { - let point = source_buffer.offset_to_point_utf16(position.to_offset(source_buffer)); let buffer_abs_path = buffer_abs_path.unwrap(); let lang_name; let lang_server; @@ -1078,7 +1080,7 @@ impl Project { text_document: lsp::TextDocumentIdentifier::new( lsp::Url::from_file_path(&buffer_abs_path).unwrap(), ), - position: lsp::Position::new(point.row, point.column), + position: lsp::Position::new(position.row, position.column), }, work_done_progress_params: Default::default(), partial_result_params: Default::default(), @@ -1171,6 +1173,193 @@ impl Project { } } + pub fn completions( + &self, + source_buffer_handle: &ModelHandle, + position: T, + cx: &mut ModelContext, + ) -> Task>> { + let source_buffer_handle = source_buffer_handle.clone(); + let source_buffer = source_buffer_handle.read(cx); + let buffer_id = source_buffer.remote_id(); + let language = source_buffer.language().cloned(); + let worktree; + let buffer_abs_path; + if let Some(file) = File::from_dyn(source_buffer.file()) { + worktree = file.worktree.clone(); + buffer_abs_path = file.as_local().map(|f| f.abs_path(cx)); + } else { + return Task::ready(Err(anyhow!("buffer does not belong to any worktree"))); + }; + + let position = position.to_point_utf16(source_buffer); + let anchor = source_buffer.anchor_after(position); + + if worktree.read(cx).as_local().is_some() { + let buffer_abs_path = buffer_abs_path.unwrap(); + let lang_name; + let lang_server; + if let Some(lang) = &language { + lang_name = lang.name().to_string(); + if let Some(server) = self + .language_servers + .get(&(worktree.read(cx).id(), lang_name.clone())) + { + lang_server = server.clone(); + } else { + return Task::ready(Err(anyhow!("buffer does not have a language server"))); + }; + } else { + return Task::ready(Err(anyhow!("buffer does not have a language"))); + } + + cx.spawn(|_, cx| async move { + let completions = lang_server + .request::(lsp::CompletionParams { + text_document_position: lsp::TextDocumentPositionParams::new( + lsp::TextDocumentIdentifier::new( + lsp::Url::from_file_path(buffer_abs_path).unwrap(), + ), + position.to_lsp_position(), + ), + context: Default::default(), + work_done_progress_params: Default::default(), + partial_result_params: Default::default(), + }) + .await?; + + let completions = if let Some(completions) = completions { + match completions { + lsp::CompletionResponse::Array(completions) => completions, + lsp::CompletionResponse::List(list) => list.items, + } + } else { + Default::default() + }; + + source_buffer_handle.read_with(&cx, |this, _| { + Ok(completions.into_iter().filter_map(|lsp_completion| { + let (old_range, new_text) = match lsp_completion.text_edit.as_ref()? { + lsp::CompletionTextEdit::Edit(edit) => (range_from_lsp(edit.range), edit.new_text.clone()), + lsp::CompletionTextEdit::InsertAndReplace(_) => { + log::info!("received an insert and replace completion but we don't yet support that"); + return None + }, + }; + + let clipped_start = this.clip_point_utf16(old_range.start, Bias::Left); + let clipped_end = this.clip_point_utf16(old_range.end, Bias::Left) ; + if clipped_start == old_range.start && clipped_end == old_range.end { + Some(Completion { + old_range: this.anchor_before(old_range.start)..this.anchor_after(old_range.end), + new_text, + label: language.as_ref().and_then(|l| l.label_for_completion(&lsp_completion)).unwrap_or_else(|| CompletionLabel::plain(&lsp_completion)), + lsp_completion, + }) + } else { + None + } + }).collect()) + }) + + }) + } else if let Some(project_id) = self.remote_id() { + let rpc = self.client.clone(); + cx.foreground().spawn(async move { + let response = rpc + .request(proto::GetCompletions { + project_id, + buffer_id, + position: Some(language::proto::serialize_anchor(&anchor)), + }) + .await?; + response + .completions + .into_iter() + .map(|completion| { + language::proto::deserialize_completion(completion, language.as_ref()) + }) + .collect() + }) + } else { + Task::ready(Err(anyhow!("project does not have a remote id"))) + } + } + + pub fn apply_additional_edits_for_completion( + &self, + buffer_handle: ModelHandle, + completion: Completion, + push_to_history: bool, + cx: &mut ModelContext, + ) -> Task>> { + let buffer = buffer_handle.read(cx); + let buffer_id = buffer.remote_id(); + + if self.is_local() { + let lang_server = if let Some(language_server) = buffer.language_server() { + language_server.clone() + } else { + return Task::ready(Ok(Default::default())); + }; + + cx.spawn(|_, mut cx| async move { + let resolved_completion = lang_server + .request::(completion.lsp_completion) + .await?; + if let Some(additional_edits) = resolved_completion.additional_text_edits { + buffer_handle.update(&mut cx, |buffer, cx| { + buffer.finalize_last_transaction(); + buffer.start_transaction(); + buffer.apply_lsp_edits(additional_edits, None, cx).log_err(); + let transaction = if buffer.end_transaction(cx).is_some() { + let transaction = buffer.finalize_last_transaction().unwrap().clone(); + if !push_to_history { + buffer.forget_transaction(transaction.id); + } + Some(transaction) + } else { + None + }; + Ok(transaction) + }) + } else { + Ok(None) + } + }) + } else if let Some(project_id) = self.remote_id() { + let client = self.client.clone(); + cx.spawn(|_, mut cx| async move { + let response = client + .request(proto::ApplyCompletionAdditionalEdits { + project_id, + buffer_id, + completion: Some(language::proto::serialize_completion(&completion)), + }) + .await?; + + if let Some(transaction) = response.transaction { + let transaction = language::proto::deserialize_transaction(transaction)?; + buffer_handle + .update(&mut cx, |buffer, _| { + buffer.wait_for_edits(transaction.edit_ids.iter().copied()) + }) + .await; + if push_to_history { + buffer_handle.update(&mut cx, |buffer, _| { + buffer.push_transaction(transaction.clone(), Instant::now()); + }); + } + Ok(Some(transaction)) + } else { + Ok(None) + } + }) + } else { + Task::ready(Err(anyhow!("project does not have a remote id"))) + } + } + pub fn code_actions( &self, source_buffer_handle: &ModelHandle, @@ -2023,9 +2212,9 @@ impl Project { .position .and_then(language::proto::deserialize_anchor) .ok_or_else(|| anyhow!("invalid position"))?; - cx.spawn(|_, mut cx| async move { - match buffer - .update(&mut cx, |buffer, cx| buffer.completions(position, cx)) + cx.spawn(|this, mut cx| async move { + match this + .update(&mut cx, |this, cx| this.completions(&buffer, position, cx)) .await { Ok(completions) => rpc.respond( @@ -2070,10 +2259,10 @@ impl Project { .ok_or_else(|| anyhow!("invalid completion"))?, language, )?; - cx.spawn(|_, mut cx| async move { - match buffer - .update(&mut cx, |buffer, cx| { - buffer.apply_additional_edits_for_completion(completion, false, cx) + cx.spawn(|this, mut cx| async move { + match this + .update(&mut cx, |this, cx| { + this.apply_additional_edits_for_completion(buffer, completion, false, cx) }) .await { diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 049ebc4e16..f15423a7b7 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -14,9 +14,7 @@ use gpui::{ executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task, }; -use language::{ - Anchor, Buffer, Completion, DiagnosticEntry, Language, Operation, PointUtf16, Rope, Transaction, -}; +use language::{Buffer, DiagnosticEntry, Operation, PointUtf16, Rope}; use lazy_static::lazy_static; use parking_lot::Mutex; use postage::{ @@ -1406,74 +1404,6 @@ impl language::File for File { })) } - fn completions( - &self, - buffer_id: u64, - position: Anchor, - language: Option>, - cx: &mut MutableAppContext, - ) -> Task>>> { - let worktree = self.worktree.read(cx); - let worktree = if let Some(worktree) = worktree.as_remote() { - worktree - } else { - return Task::ready(Err(anyhow!( - "remote completions requested on a local worktree" - ))); - }; - let rpc = worktree.client.clone(); - let project_id = worktree.project_id; - cx.foreground().spawn(async move { - let response = rpc - .request(proto::GetCompletions { - project_id, - buffer_id, - position: Some(language::proto::serialize_anchor(&position)), - }) - .await?; - response - .completions - .into_iter() - .map(|completion| { - language::proto::deserialize_completion(completion, language.as_ref()) - }) - .collect() - }) - } - - fn apply_additional_edits_for_completion( - &self, - buffer_id: u64, - completion: Completion, - cx: &mut MutableAppContext, - ) -> Task>> { - let worktree = self.worktree.read(cx); - let worktree = if let Some(worktree) = worktree.as_remote() { - worktree - } else { - return Task::ready(Err(anyhow!( - "remote additional edits application requested on a local worktree" - ))); - }; - let rpc = worktree.client.clone(); - let project_id = worktree.project_id; - cx.foreground().spawn(async move { - let response = rpc - .request(proto::ApplyCompletionAdditionalEdits { - project_id, - buffer_id, - completion: Some(language::proto::serialize_completion(&completion)), - }) - .await?; - - if let Some(transaction) = response.transaction { - Ok(Some(language::proto::deserialize_transaction(transaction)?)) - } else { - Ok(None) - } - }) - } - fn buffer_updated(&self, buffer_id: u64, operation: Operation, cx: &mut MutableAppContext) { self.worktree.update(cx, |worktree, cx| { worktree.send_buffer_update(buffer_id, operation, cx); diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index 8314bec3db..76b50139fc 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -1349,7 +1349,7 @@ mod tests { .unwrap(); let editor_b = cx_b.add_view(window_b, |cx| { - Editor::for_buffer(buffer_b, Arc::new(|cx| EditorSettings::test(cx)), cx) + Editor::for_buffer(buffer_b, Arc::new(|cx| EditorSettings::test(cx)), None, cx) }); // TODO @@ -2401,6 +2401,7 @@ mod tests { Editor::for_buffer( cx.add_model(|cx| MultiBuffer::singleton(buffer_b.clone(), cx)), Arc::new(|cx| EditorSettings::test(cx)), + None, cx, ) }); diff --git a/script/drop-test-dbs b/script/drop-test-dbs new file mode 100755 index 0000000000..72d6ff5f79 --- /dev/null +++ b/script/drop-test-dbs @@ -0,0 +1,16 @@ +#!/bin/bash + +databases=$(psql --tuples-only --command " + SELECT + datname + FROM + pg_database + WHERE + datistemplate = false + AND datname like 'zed-test-%' +") + +for database in $databases; do + echo $database + dropdb $database +done \ No newline at end of file