Detailed changes
@@ -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
});
@@ -415,6 +415,7 @@ pub struct Editor {
scroll_top_anchor: Option<Anchor>,
autoscroll_request: Option<Autoscroll>,
build_settings: BuildSettings,
+ workspace: Option<WeakViewHandle<Workspace>>,
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<Anchor>]>,
+ buffer: ModelHandle<Buffer>,
+ completions: Arc<[Completion]>,
match_candidates: Vec<StringMatchCandidate>,
matches: Arc<[StringMatch]>,
selected_item: usize,
@@ -750,7 +752,7 @@ impl Editor {
pub fn single_line(build_settings: BuildSettings, cx: &mut ViewContext<Self>) -> 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<MultiBuffer>,
build_settings: BuildSettings,
+ workspace: Option<WeakViewHandle<Workspace>>,
cx: &mut ViewContext<Self>,
) -> Self {
- Self::new(buffer, build_settings, cx)
+ Self::new(buffer, build_settings, workspace, cx)
}
pub fn clone(&self, cx: &mut ViewContext<Self>) -> 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<MultiBuffer>,
build_settings: BuildSettings,
+ workspace: Option<WeakViewHandle<Workspace>>,
cx: &mut ViewContext<Self>,
) -> 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<Self>) {
+ 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::<String>();
+ 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::<String>();
let selections = self.local_selections::<usize>(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<Workspace>,
- ) {
- let active_item = workspace.active_item(cx);
- let editor_handle = if let Some(editor) = active_item
- .as_ref()
- .and_then(|item| item.act_as::<Self>(cx))
- {
- editor
+ fn show_code_actions(&mut self, _: &ShowCodeActions, cx: &mut ViewContext<Self>) {
+ let head = if let Some(selection) = self.newest_anchor_selection() {
+ selection.head()
} else {
return;
};
-
- let editor = editor_handle.read(cx);
- let head = if let Some(selection) = editor.newest_anchor_selection() {
- selection.head()
+ let workspace = if let Some(workspace) = self.workspace.as_ref().and_then(|w| w.upgrade(cx))
+ {
+ workspace
} else {
return;
};
- let (buffer, head) = editor.buffer.read(cx).text_anchor_for_position(head, cx);
+
+ 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<Workspace>,
+ cx: &mut ViewContext<Self>,
) -> Option<Task<Result<()>>> {
- let active_item = workspace.active_item(cx)?;
- let editor = active_item.act_as::<Self>(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 {
- Editor::for_buffer(buffer, Arc::new(move |_| settings.clone()), cx)
+ Editor::for_buffer(buffer, Arc::new(move |_| settings.clone()), None, cx)
}
}
@@ -1298,6 +1298,7 @@ mod tests {
let settings = settings.clone();
Arc::new(move |_| settings.clone())
},
+ None,
cx,
)
});
@@ -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()));
@@ -861,41 +861,6 @@ impl MultiBuffer {
})
}
- pub fn completions<T>(
- &self,
- position: T,
- cx: &mut ModelContext<Self>,
- ) -> Task<Result<Vec<Completion<Anchor>>>>
- 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<T>(&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<Anchor>,
- cx: &mut ModelContext<Self>,
- ) -> Task<Result<()>> {
- 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<Language>> {
self.buffers
.borrow()
@@ -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| {
@@ -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<T> {
- pub old_range: Range<T>,
+pub struct Completion {
+ pub old_range: Range<Anchor>,
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<Task<Result<()>>>;
- fn completions(
- &self,
- buffer_id: u64,
- position: Anchor,
- language: Option<Arc<Language>>,
- cx: &mut MutableAppContext,
- ) -> Task<Result<Vec<Completion<Anchor>>>>;
-
- fn apply_additional_edits_for_completion(
- &self,
- buffer_id: u64,
- completion: Completion<Anchor>,
- cx: &mut MutableAppContext,
- ) -> Task<Result<Option<Transaction>>>;
-
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<Arc<Language>>,
- _: &mut MutableAppContext,
- ) -> Task<Result<Vec<Completion<Anchor>>>> {
- Task::ready(Ok(Default::default()))
- }
-
- fn apply_additional_edits_for_completion(
- &self,
- _: u64,
- _: Completion<Anchor>,
- _: &mut MutableAppContext,
- ) -> Task<Result<Option<Transaction>>> {
- 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<T>(
- &self,
- position: T,
- cx: &mut ModelContext<Self>,
- ) -> Task<Result<Vec<Completion<Anchor>>>>
- 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::request::Completion>(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<Anchor>,
- push_to_history: bool,
- cx: &mut ModelContext<Self>,
- ) -> Task<Result<Option<Transaction>>> {
- 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::<lsp::request::ResolveCompletionItem>(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<T> Completion<T> {
+impl Completion {
pub fn sort_key(&self) -> (usize, &str) {
let kind_key = match self.lsp_completion.kind {
Some(lsp::CompletionItemKind::VARIABLE) => 0,
@@ -394,7 +394,7 @@ pub fn lamport_timestamp_for_operation(operation: &proto::Operation) -> Option<c
})
}
-pub fn serialize_completion(completion: &Completion<Anchor>) -> 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<Anchor>) -> proto::Completio
pub fn deserialize_completion(
completion: proto::Completion,
language: Option<&Arc<Language>>,
-) -> Result<Completion<Anchor>> {
+) -> Result<Completion> {
let old_start = completion
.old_start
.and_then(deserialize_anchor)
@@ -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<T: ToOffset>(
+ pub fn definition<T: ToPointUtf16>(
&self,
source_buffer_handle: &ModelHandle<Buffer>,
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<T: ToPointUtf16>(
+ &self,
+ source_buffer_handle: &ModelHandle<Buffer>,
+ position: T,
+ cx: &mut ModelContext<Self>,
+ ) -> Task<Result<Vec<Completion>>> {
+ 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::request::Completion>(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<Buffer>,
+ completion: Completion,
+ push_to_history: bool,
+ cx: &mut ModelContext<Self>,
+ ) -> Task<Result<Option<Transaction>>> {
+ 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::<lsp::request::ResolveCompletionItem>(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<T: ToPointUtf16>(
&self,
source_buffer_handle: &ModelHandle<Buffer>,
@@ -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
{
@@ -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<Arc<Language>>,
- cx: &mut MutableAppContext,
- ) -> Task<Result<Vec<Completion<Anchor>>>> {
- 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<Anchor>,
- cx: &mut MutableAppContext,
- ) -> Task<Result<Option<Transaction>>> {
- 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);
@@ -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,
)
});
@@ -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