Invalidate copilot suggestion on backspaces

Restore an observation on initialization

co-authored-by: antonio <antonio@zed.dev>
This commit is contained in:
Mikayla Maki 2023-04-05 08:48:39 -07:00
parent e2c690cece
commit b585470518
4 changed files with 103 additions and 1 deletions

View File

@ -50,6 +50,27 @@ pub fn init(http: Arc<dyn HttpClient>, node_runtime: Arc<NodeRuntime>, cx: &mut
}); });
cx.set_global(copilot.clone()); cx.set_global(copilot.clone());
cx.observe(&copilot, |handle, cx| {
let status = handle.read(cx).status();
cx.update_global::<collections::CommandPaletteFilter, _, _>(
move |filter, _cx| match status {
Status::Disabled => {
filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
filter.filtered_namespaces.insert(COPILOT_AUTH_NAMESPACE);
}
Status::Authorized => {
filter.filtered_namespaces.remove(COPILOT_NAMESPACE);
filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
}
_ => {
filter.filtered_namespaces.insert(COPILOT_NAMESPACE);
filter.filtered_namespaces.remove(COPILOT_AUTH_NAMESPACE);
}
},
);
})
.detach();
sign_in::init(cx); sign_in::init(cx);
cx.add_global_action(|_: &SignIn, cx| { cx.add_global_action(|_: &SignIn, cx| {
if let Some(copilot) = Copilot::global(cx) { if let Some(copilot) = Copilot::global(cx) {

View File

@ -1037,6 +1037,11 @@ impl CopilotState {
let completion = self.completions.get(self.active_completion_index)?; let completion = self.completions.get(self.active_completion_index)?;
let excerpt_id = self.excerpt_id?; let excerpt_id = self.excerpt_id?;
let completion_buffer = buffer.buffer_for_excerpt(excerpt_id)?; let completion_buffer = buffer.buffer_for_excerpt(excerpt_id)?;
if !completion.range.start.is_valid(completion_buffer)
|| !completion.range.end.is_valid(completion_buffer)
{
return None;
}
let mut completion_range = completion.range.to_offset(&completion_buffer); let mut completion_range = completion.range.to_offset(&completion_buffer);
let prefix_len = Self::common_prefix( let prefix_len = Self::common_prefix(

View File

@ -6098,6 +6098,65 @@ async fn test_copilot(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppC
}); });
} }
#[gpui::test]
async fn test_copilot_completion_invalidation(
deterministic: Arc<Deterministic>,
cx: &mut gpui::TestAppContext,
) {
let (copilot, copilot_lsp) = Copilot::fake(cx);
cx.update(|cx| cx.set_global(copilot));
let mut cx = EditorLspTestContext::new_rust(
lsp::ServerCapabilities {
completion_provider: Some(lsp::CompletionOptions {
trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
..Default::default()
}),
..Default::default()
},
cx,
)
.await;
cx.set_state(indoc! {"
one
twˇ
three
"});
handle_copilot_completion_request(
&copilot_lsp,
vec![copilot::request::Completion {
text: "two.foo()".into(),
range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
..Default::default()
}],
vec![],
);
cx.update_editor(|editor, cx| editor.next_copilot_suggestion(&Default::default(), cx));
deterministic.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, cx| {
assert!(editor.has_active_copilot_suggestion(cx));
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
assert_eq!(editor.text(cx), "one\ntw\nthree\n");
editor.backspace(&Default::default(), cx);
assert!(editor.has_active_copilot_suggestion(cx));
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
assert_eq!(editor.text(cx), "one\nt\nthree\n");
editor.backspace(&Default::default(), cx);
assert!(editor.has_active_copilot_suggestion(cx));
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
assert_eq!(editor.text(cx), "one\n\nthree\n");
// Deleting across the original suggestion range invalidates it.
editor.backspace(&Default::default(), cx);
assert!(!editor.has_active_copilot_suggestion(cx));
assert_eq!(editor.display_text(cx), "one\nthree\n");
assert_eq!(editor.text(cx), "one\nthree\n");
});
}
fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> { fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
let point = DisplayPoint::new(row as u32, column as u32); let point = DisplayPoint::new(row as u32, column as u32);
point..point point..point

View File

@ -1,4 +1,7 @@
use crate::{BufferSnapshot, Point, PointUtf16, TextDimension, ToOffset, ToPoint, ToPointUtf16}; use crate::{
locator::Locator, BufferSnapshot, Point, PointUtf16, TextDimension, ToOffset, ToPoint,
ToPointUtf16,
};
use anyhow::Result; use anyhow::Result;
use std::{cmp::Ordering, fmt::Debug, ops::Range}; use std::{cmp::Ordering, fmt::Debug, ops::Range};
use sum_tree::Bias; use sum_tree::Bias;
@ -86,6 +89,20 @@ impl Anchor {
{ {
content.summary_for_anchor(self) content.summary_for_anchor(self)
} }
/// Returns true when the [Anchor] is located inside a visible fragment.
pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool {
if *self == Anchor::MIN || *self == Anchor::MAX {
true
} else {
let fragment_id = buffer.fragment_id_for_anchor(self);
let mut fragment_cursor = buffer.fragments.cursor::<(Option<&Locator>, usize)>();
fragment_cursor.seek(&Some(fragment_id), Bias::Left, &None);
fragment_cursor
.item()
.map_or(false, |fragment| fragment.visible)
}
}
} }
pub trait OffsetRangeExt { pub trait OffsetRangeExt {