Merge pull request #396 from zed-industries/fix-autocomplete-bugs

Refine autocomplete
This commit is contained in:
Antonio Scandurra 2022-02-04 16:13:16 +01:00 committed by GitHub
commit bf043fe3fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 210 additions and 79 deletions

View file

@ -383,6 +383,8 @@ pub enum SoftWrap {
Column(u32), Column(u32),
} }
type CompletionId = usize;
pub type BuildSettings = Arc<dyn 'static + Send + Sync + Fn(&AppContext) -> EditorSettings>; pub type BuildSettings = Arc<dyn 'static + Send + Sync + Fn(&AppContext) -> EditorSettings>;
pub struct Editor { pub struct Editor {
@ -416,7 +418,8 @@ pub struct Editor {
highlighted_ranges: BTreeMap<TypeId, (Color, Vec<Range<Anchor>>)>, highlighted_ranges: BTreeMap<TypeId, (Color, Vec<Range<Anchor>>)>,
nav_history: Option<ItemNavHistory>, nav_history: Option<ItemNavHistory>,
completion_state: Option<CompletionState>, completion_state: Option<CompletionState>,
completions_task: Option<Task<Option<()>>>, completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
next_completion_id: CompletionId,
} }
pub struct EditorSnapshot { pub struct EditorSnapshot {
@ -457,6 +460,7 @@ struct SnippetState {
struct InvalidationStack<T>(Vec<T>); struct InvalidationStack<T>(Vec<T>);
struct CompletionState { struct CompletionState {
id: CompletionId,
initial_position: Anchor, initial_position: Anchor,
completions: Arc<[Completion<Anchor>]>, completions: Arc<[Completion<Anchor>]>,
match_candidates: Vec<StringMatchCandidate>, match_candidates: Vec<StringMatchCandidate>,
@ -617,7 +621,8 @@ impl Editor {
highlighted_ranges: Default::default(), highlighted_ranges: Default::default(),
nav_history: None, nav_history: None,
completion_state: None, completion_state: None,
completions_task: None, completion_tasks: Default::default(),
next_completion_id: 0,
}; };
let selection = Selection { let selection = Selection {
id: post_inc(&mut this.next_selection_id), id: post_inc(&mut this.next_selection_id),
@ -1660,11 +1665,13 @@ impl Editor {
.buffer .buffer
.update(cx, |buffer, cx| buffer.completions(position.clone(), cx)); .update(cx, |buffer, cx| buffer.completions(position.clone(), cx));
self.completions_task = Some(cx.spawn_weak(|this, mut cx| { let id = post_inc(&mut self.next_completion_id);
let task = cx.spawn_weak(|this, mut cx| {
async move { async move {
let completions = completions.await?; let completions = completions.await?;
let mut completion_state = CompletionState { let mut completion_state = CompletionState {
id,
initial_position: position, initial_position: position,
match_candidates: completions match_candidates: completions
.iter() .iter()
@ -1688,6 +1695,14 @@ impl Editor {
if let Some(this) = cx.read(|cx| this.upgrade(cx)) { if let Some(this) = cx.read(|cx| this.upgrade(cx)) {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
if let Some(prev_completion_state) = this.completion_state.as_ref() {
if prev_completion_state.id > completion_state.id {
return;
}
}
this.completion_tasks
.retain(|(id, _)| *id > completion_state.id);
if completion_state.matches.is_empty() { if completion_state.matches.is_empty() {
this.hide_completions(cx); this.hide_completions(cx);
} else if this.focused { } else if this.focused {
@ -1700,12 +1715,13 @@ impl Editor {
Ok::<_, anyhow::Error>(()) Ok::<_, anyhow::Error>(())
} }
.log_err() .log_err()
})); });
self.completion_tasks.push((id, task));
} }
fn hide_completions(&mut self, cx: &mut ViewContext<Self>) -> Option<CompletionState> { fn hide_completions(&mut self, cx: &mut ViewContext<Self>) -> Option<CompletionState> {
cx.notify(); cx.notify();
self.completions_task.take(); self.completion_tasks.clear();
self.completion_state.take() self.completion_state.take()
} }
@ -1731,31 +1747,39 @@ impl Editor {
}; };
let snapshot = self.buffer.read(cx).snapshot(cx); let snapshot = self.buffer.read(cx).snapshot(cx);
let old_range = completion.old_range.to_offset(&snapshot); let old_range = completion.old_range.to_offset(&snapshot);
let old_text = snapshot
.text_for_range(old_range.clone())
.collect::<String>();
let selections = self.local_selections::<usize>(cx); let selections = self.local_selections::<usize>(cx);
let mut common_prefix_len = None; let newest_selection = selections.iter().max_by_key(|s| s.id)?;
let lookbehind = newest_selection.start.saturating_sub(old_range.start);
let lookahead = old_range.end.saturating_sub(newest_selection.end);
let mut common_prefix_len = old_text
.bytes()
.zip(text.bytes())
.take_while(|(a, b)| a == b)
.count();
let mut ranges = Vec::new(); let mut ranges = Vec::new();
for selection in &selections { for selection in &selections {
let start = selection.start.saturating_sub(old_range.len()); if snapshot.contains_str_at(selection.start.saturating_sub(lookbehind), &old_text) {
let prefix_len = snapshot let start = selection.start.saturating_sub(lookbehind);
.bytes_at(start) let end = selection.end + lookahead;
.zip(completion.new_text.bytes()) ranges.push(start + common_prefix_len..end);
.take_while(|(a, b)| a == b)
.count();
if common_prefix_len.is_none() {
common_prefix_len = Some(prefix_len);
}
if common_prefix_len == Some(prefix_len) {
ranges.push(start + prefix_len..selection.end);
} else { } else {
common_prefix_len.take(); common_prefix_len = 0;
ranges.clear(); ranges.clear();
ranges.extend(selections.iter().map(|s| s.start..s.end)); ranges.extend(selections.iter().map(|s| {
if s.id == newest_selection.id {
old_range.clone()
} else {
s.start..s.end
}
}));
break; break;
} }
} }
let common_prefix_len = common_prefix_len.unwrap_or(0);
let text = &text[common_prefix_len..]; let text = &text[common_prefix_len..];
self.start_transaction(cx); self.start_transaction(cx);
@ -4024,7 +4048,8 @@ impl Editor {
if kind == Some(CharKind::Word) && word_range.to_inclusive().contains(&cursor_position) if kind == Some(CharKind::Word) && word_range.to_inclusive().contains(&cursor_position)
{ {
let query = Self::completion_query(&buffer, cursor_position); let query = Self::completion_query(&buffer, cursor_position);
smol::block_on(completion_state.filter(query.as_deref(), cx.background().clone())); cx.background()
.block(completion_state.filter(query.as_deref(), cx.background().clone()));
self.show_completions(&ShowCompletions, cx); self.show_completions(&ShowCompletions, cx);
} else { } else {
self.hide_completions(cx); self.hide_completions(cx);
@ -4954,6 +4979,7 @@ fn styled_runs_for_completion_label<'a>(
mod tests { mod tests {
use super::*; use super::*;
use language::{FakeFile, LanguageConfig}; use language::{FakeFile, LanguageConfig};
use lsp::FakeLanguageServer;
use std::{cell::RefCell, path::Path, rc::Rc, time::Instant}; use std::{cell::RefCell, path::Path, rc::Rc, time::Instant};
use text::Point; use text::Point;
use unindent::Unindent; use unindent::Unindent;
@ -7269,41 +7295,20 @@ mod tests {
let (_, editor) = cx.add_window(|cx| build_editor(buffer, settings, cx)); let (_, editor) = cx.add_window(|cx| build_editor(buffer, settings, cx));
editor.update(&mut cx, |editor, cx| { editor.update(&mut cx, |editor, cx| {
editor.select_ranges([3..3], None, cx); editor.select_ranges([Point::new(0, 3)..Point::new(0, 3)], None, cx);
editor.handle_input(&Input(".".to_string()), cx); editor.handle_input(&Input(".".to_string()), cx);
}); });
let (id, params) = fake.receive_request::<lsp::request::Completion>().await; handle_completion_request(
assert_eq!( &mut fake,
params.text_document_position.text_document.uri, "/the/file",
lsp::Url::from_file_path("/the/file").unwrap() Point::new(0, 4),
); &[
assert_eq!( (Point::new(0, 4)..Point::new(0, 4), "first_completion"),
params.text_document_position.position, (Point::new(0, 4)..Point::new(0, 4), "second_completion"),
lsp::Position::new(0, 4) ],
);
fake.respond(
id,
Some(lsp::CompletionResponse::Array(vec![
lsp::CompletionItem {
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
new_text: "first_completion".to_string(),
})),
..Default::default()
},
lsp::CompletionItem {
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(lsp::Position::new(0, 4), lsp::Position::new(0, 4)),
new_text: "second_completion".to_string(),
})),
..Default::default()
},
])),
) )
.await; .await;
editor.next_notification(&cx).await; editor.next_notification(&cx).await;
let apply_additional_edits = editor.update(&mut cx, |editor, cx| { let apply_additional_edits = editor.update(&mut cx, |editor, cx| {
@ -7321,21 +7326,11 @@ mod tests {
apply_additional_edits apply_additional_edits
}); });
let (id, _) = fake handle_resolve_completion_request(
.receive_request::<lsp::request::ResolveCompletionItem>() &mut fake,
.await; Some((Point::new(2, 5)..Point::new(2, 5), "\nadditional edit")),
fake.respond(
id,
lsp::CompletionItem {
additional_text_edits: Some(vec![lsp::TextEdit::new(
lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 5)),
"\nadditional edit".to_string(),
)]),
..Default::default()
},
) )
.await; .await;
apply_additional_edits.await.unwrap(); apply_additional_edits.await.unwrap();
assert_eq!( assert_eq!(
editor.read_with(&cx, |editor, cx| editor.text(cx)), editor.read_with(&cx, |editor, cx| editor.text(cx)),
@ -7347,6 +7342,130 @@ mod tests {
" "
.unindent() .unindent()
); );
editor.update(&mut cx, |editor, cx| {
editor.select_ranges(
[
Point::new(1, 3)..Point::new(1, 3),
Point::new(2, 5)..Point::new(2, 5),
],
None,
cx,
);
editor.handle_input(&Input(" ".to_string()), cx);
assert!(editor.completion_state.is_none());
editor.handle_input(&Input("s".to_string()), cx);
assert!(editor.completion_state.is_none());
});
handle_completion_request(
&mut fake,
"/the/file",
Point::new(2, 7),
&[
(Point::new(2, 6)..Point::new(2, 7), "fourth_completion"),
(Point::new(2, 6)..Point::new(2, 7), "fifth_completion"),
(Point::new(2, 6)..Point::new(2, 7), "sixth_completion"),
],
)
.await;
editor
.condition(&cx, |editor, _| editor.completion_state.is_some())
.await;
editor.update(&mut cx, |editor, cx| {
editor.handle_input(&Input("i".to_string()), cx);
});
handle_completion_request(
&mut fake,
"/the/file",
Point::new(2, 8),
&[
(Point::new(2, 6)..Point::new(2, 8), "fourth_completion"),
(Point::new(2, 6)..Point::new(2, 8), "fifth_completion"),
(Point::new(2, 6)..Point::new(2, 8), "sixth_completion"),
],
)
.await;
editor.next_notification(&cx).await;
let apply_additional_edits = editor.update(&mut cx, |editor, cx| {
let apply_additional_edits = editor.confirm_completion(None, cx).unwrap();
assert_eq!(
editor.text(cx),
"
one.second_completion
two sixth_completion
three sixth_completion
additional edit
"
.unindent()
);
apply_additional_edits
});
handle_resolve_completion_request(&mut fake, None).await;
apply_additional_edits.await.unwrap();
async fn handle_completion_request(
fake: &mut FakeLanguageServer,
path: &str,
position: Point,
completions: &[(Range<Point>, &str)],
) {
let (id, params) = fake.receive_request::<lsp::request::Completion>().await;
assert_eq!(
params.text_document_position.text_document.uri,
lsp::Url::from_file_path(path).unwrap()
);
assert_eq!(
params.text_document_position.position,
lsp::Position::new(position.row, position.column)
);
let completions = completions
.iter()
.map(|(range, new_text)| lsp::CompletionItem {
label: new_text.to_string(),
text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
range: lsp::Range::new(
lsp::Position::new(range.start.row, range.start.column),
lsp::Position::new(range.start.row, range.start.column),
),
new_text: new_text.to_string(),
})),
..Default::default()
})
.collect();
fake.respond(id, Some(lsp::CompletionResponse::Array(completions)))
.await;
}
async fn handle_resolve_completion_request(
fake: &mut FakeLanguageServer,
edit: Option<(Range<Point>, &str)>,
) {
let (id, _) = fake
.receive_request::<lsp::request::ResolveCompletionItem>()
.await;
fake.respond(
id,
lsp::CompletionItem {
additional_text_edits: edit.map(|(range, new_text)| {
vec![lsp::TextEdit::new(
lsp::Range::new(
lsp::Position::new(range.start.row, range.start.column),
lsp::Position::new(range.end.row, range.end.column),
),
new_text.to_string(),
)]
}),
..Default::default()
},
)
.await;
}
} }
#[gpui::test] #[gpui::test]

View file

@ -1314,12 +1314,6 @@ impl MultiBufferSnapshot {
} }
} }
pub fn bytes_at<'a, T: ToOffset>(&'a self, position: T) -> impl 'a + Iterator<Item = u8> {
self.bytes_in_range(position.to_offset(self)..self.len())
.flatten()
.copied()
}
pub fn buffer_rows<'a>(&'a self, start_row: u32) -> MultiBufferRows<'a> { pub fn buffer_rows<'a>(&'a self, start_row: u32) -> MultiBufferRows<'a> {
let mut result = MultiBufferRows { let mut result = MultiBufferRows {
buffer_row_range: 0..0, buffer_row_range: 0..0,

View file

@ -236,16 +236,14 @@ impl Deterministic {
} }
} }
fn block_on(&self, future: &mut AnyLocalFuture) -> Option<Box<dyn Any>> { fn block<F, T>(&self, future: &mut F, max_ticks: usize) -> Option<T>
where
F: Unpin + Future<Output = T>,
{
let unparker = self.parker.lock().unparker(); let unparker = self.parker.lock().unparker();
let waker = waker_fn(move || { let waker = waker_fn(move || {
unparker.unpark(); unparker.unpark();
}); });
let max_ticks = {
let mut state = self.state.lock();
let range = state.block_on_ticks.clone();
state.rng.gen_range(range)
};
let mut cx = Context::from_waker(&waker); let mut cx = Context::from_waker(&waker);
for _ in 0..max_ticks { for _ in 0..max_ticks {
@ -258,7 +256,7 @@ impl Deterministic {
runnable.run(); runnable.run();
} else { } else {
drop(state); drop(state);
if let Poll::Ready(result) = future.as_mut().poll(&mut cx) { if let Poll::Ready(result) = future.poll(&mut cx) {
return Some(result); return Some(result);
} }
let mut state = self.state.lock(); let mut state = self.state.lock();
@ -488,6 +486,19 @@ impl Background {
Task::send(any_task) Task::send(any_task)
} }
pub fn block<F, T>(&self, future: F) -> T
where
F: Future<Output = T>,
{
smol::pin!(future);
match self {
Self::Production { .. } => smol::block_on(&mut future),
Self::Deterministic { executor, .. } => {
executor.block(&mut future, usize::MAX).unwrap()
}
}
}
pub fn block_with_timeout<F, T>( pub fn block_with_timeout<F, T>(
&self, &self,
timeout: Duration, timeout: Duration,
@ -501,7 +512,14 @@ impl Background {
if !timeout.is_zero() { if !timeout.is_zero() {
let output = match self { let output = match self {
Self::Production { .. } => smol::block_on(util::timeout(timeout, &mut future)).ok(), Self::Production { .. } => smol::block_on(util::timeout(timeout, &mut future)).ok(),
Self::Deterministic { executor, .. } => executor.block_on(&mut future), Self::Deterministic { executor, .. } => {
let max_ticks = {
let mut state = executor.state.lock();
let range = state.block_on_ticks.clone();
state.rng.gen_range(range)
};
executor.block(&mut future, max_ticks)
}
}; };
if let Some(output) = output { if let Some(output) = output {
return Ok(*output.downcast().unwrap()); return Ok(*output.downcast().unwrap());