@@ -4,6 +4,7 @@ pub mod copilot_responses;
pub mod request;
mod sign_in;
+use crate::request::NextEditSuggestions;
use crate::sign_in::initiate_sign_out;
use ::fs::Fs;
use anyhow::{Context as _, Result, anyhow};
@@ -18,7 +19,7 @@ use http_client::HttpClient;
use language::language_settings::CopilotSettings;
use language::{
Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16, ToPointUtf16,
- language_settings::{EditPredictionProvider, all_language_settings, language_settings},
+ language_settings::{EditPredictionProvider, all_language_settings},
point_from_lsp, point_to_lsp,
};
use lsp::{LanguageServer, LanguageServerBinary, LanguageServerId, LanguageServerName};
@@ -40,7 +41,7 @@ use std::{
sync::Arc,
};
use sum_tree::Dimensions;
-use util::{ResultExt, fs::remove_matching, rel_path::RelPath};
+use util::{ResultExt, fs::remove_matching};
use workspace::Workspace;
pub use crate::copilot_edit_prediction_delegate::CopilotEditPredictionDelegate;
@@ -315,6 +316,15 @@ struct GlobalCopilot(Entity<Copilot>);
impl Global for GlobalCopilot {}
+/// Copilot's NextEditSuggestion response, with coordinates converted to Anchors.
+struct CopilotEditPrediction {
+ buffer: Entity<Buffer>,
+ range: Range<Anchor>,
+ text: String,
+ command: Option<lsp::Command>,
+ snapshot: BufferSnapshot,
+}
+
impl Copilot {
pub fn global(cx: &App) -> Option<Entity<Self>> {
cx.try_global::<GlobalCopilot>()
@@ -873,101 +883,19 @@ impl Copilot {
}
}
- pub fn completions<T>(
- &mut self,
- buffer: &Entity<Buffer>,
- position: T,
- cx: &mut Context<Self>,
- ) -> Task<Result<Vec<Completion>>>
- where
- T: ToPointUtf16,
- {
- self.request_completions::<request::GetCompletions, _>(buffer, position, cx)
- }
-
- pub fn completions_cycling<T>(
+ pub(crate) fn completions(
&mut self,
buffer: &Entity<Buffer>,
- position: T,
+ position: Anchor,
cx: &mut Context<Self>,
- ) -> Task<Result<Vec<Completion>>>
- where
- T: ToPointUtf16,
- {
- self.request_completions::<request::GetCompletionsCycling, _>(buffer, position, cx)
- }
-
- pub fn accept_completion(
- &mut self,
- completion: &Completion,
- cx: &mut Context<Self>,
- ) -> Task<Result<()>> {
- let server = match self.server.as_authenticated() {
- Ok(server) => server,
- Err(error) => return Task::ready(Err(error)),
- };
- let request =
- server
- .lsp
- .request::<request::NotifyAccepted>(request::NotifyAcceptedParams {
- uuid: completion.uuid.clone(),
- });
- cx.background_spawn(async move {
- request
- .await
- .into_response()
- .context("copilot: notify accepted")?;
- Ok(())
- })
- }
-
- pub fn discard_completions(
- &mut self,
- completions: &[Completion],
- cx: &mut Context<Self>,
- ) -> Task<Result<()>> {
- let server = match self.server.as_authenticated() {
- Ok(server) => server,
- Err(_) => return Task::ready(Ok(())),
- };
- let request =
- server
- .lsp
- .request::<request::NotifyRejected>(request::NotifyRejectedParams {
- uuids: completions
- .iter()
- .map(|completion| completion.uuid.clone())
- .collect(),
- });
- cx.background_spawn(async move {
- request
- .await
- .into_response()
- .context("copilot: notify rejected")?;
- Ok(())
- })
- }
-
- fn request_completions<R, T>(
- &mut self,
- buffer: &Entity<Buffer>,
- position: T,
- cx: &mut Context<Self>,
- ) -> Task<Result<Vec<Completion>>>
- where
- R: 'static
- + lsp::request::Request<
- Params = request::GetCompletionsParams,
- Result = request::GetCompletionsResult,
- >,
- T: ToPointUtf16,
- {
+ ) -> Task<Result<Vec<CopilotEditPrediction>>> {
self.register_buffer(buffer, cx);
let server = match self.server.as_authenticated() {
Ok(server) => server,
Err(error) => return Task::ready(Err(error)),
};
+ let buffer_entity = buffer.clone();
let lsp = server.lsp.clone();
let registered_buffer = server
.registered_buffers
@@ -977,46 +905,31 @@ impl Copilot {
let buffer = buffer.read(cx);
let uri = registered_buffer.uri.clone();
let position = position.to_point_utf16(buffer);
- let settings = language_settings(
- buffer.language_at(position).map(|l| l.name()),
- buffer.file(),
- cx,
- );
- let tab_size = settings.tab_size;
- let hard_tabs = settings.hard_tabs;
- let relative_path = buffer
- .file()
- .map_or(RelPath::empty().into(), |file| file.path().clone());
cx.background_spawn(async move {
let (version, snapshot) = snapshot.await?;
let result = lsp
- .request::<R>(request::GetCompletionsParams {
- doc: request::GetCompletionsDocument {
- uri,
- tab_size: tab_size.into(),
- indent_size: 1,
- insert_spaces: !hard_tabs,
- relative_path: relative_path.to_proto(),
- position: point_to_lsp(position),
- version: version.try_into().unwrap(),
- },
+ .request::<NextEditSuggestions>(request::NextEditSuggestionsParams {
+ text_document: lsp::VersionedTextDocumentIdentifier { uri, version },
+ position: point_to_lsp(position),
})
.await
.into_response()
.context("copilot: get completions")?;
let completions = result
- .completions
+ .edits
.into_iter()
.map(|completion| {
let start = snapshot
.clip_point_utf16(point_from_lsp(completion.range.start), Bias::Left);
let end =
snapshot.clip_point_utf16(point_from_lsp(completion.range.end), Bias::Left);
- Completion {
- uuid: completion.uuid,
+ CopilotEditPrediction {
+ buffer: buffer_entity.clone(),
range: snapshot.anchor_before(start)..snapshot.anchor_after(end),
text: completion.text,
+ command: completion.command,
+ snapshot: snapshot.clone(),
}
})
.collect();
@@ -1024,6 +937,35 @@ impl Copilot {
})
}
+ pub(crate) fn accept_completion(
+ &mut self,
+ completion: &CopilotEditPrediction,
+ cx: &mut Context<Self>,
+ ) -> Task<Result<()>> {
+ let server = match self.server.as_authenticated() {
+ Ok(server) => server,
+ Err(error) => return Task::ready(Err(error)),
+ };
+ if let Some(command) = &completion.command {
+ let request = server
+ .lsp
+ .request::<lsp::ExecuteCommand>(lsp::ExecuteCommandParams {
+ command: command.command.clone(),
+ arguments: command.arguments.clone().unwrap_or_default(),
+ ..Default::default()
+ });
+ cx.background_spawn(async move {
+ request
+ .await
+ .into_response()
+ .context("copilot: notify accepted")?;
+ Ok(())
+ })
+ } else {
+ Task::ready(Ok(()))
+ }
+ }
+
pub fn status(&self) -> Status {
match &self.server {
CopilotServer::Starting { task } => Status::Starting { task: task.clone() },
@@ -1260,7 +1202,11 @@ async fn get_copilot_lsp(fs: Arc<dyn Fs>, node_runtime: NodeRuntime) -> anyhow::
mod tests {
use super::*;
use gpui::TestAppContext;
- use util::{path, paths::PathStyle, rel_path::rel_path};
+ use util::{
+ path,
+ paths::PathStyle,
+ rel_path::{RelPath, rel_path},
+ };
#[gpui::test(iterations = 10)]
async fn test_buffer_management(cx: &mut TestAppContext) {
@@ -1,49 +1,29 @@
-use crate::{Completion, Copilot};
+use crate::{Copilot, CopilotEditPrediction};
use anyhow::Result;
-use edit_prediction_types::{Direction, EditPrediction, EditPredictionDelegate};
-use gpui::{App, Context, Entity, EntityId, Task};
-use language::{Buffer, OffsetRangeExt, ToOffset, language_settings::AllLanguageSettings};
-use settings::Settings;
-use std::{path::Path, time::Duration};
+use edit_prediction_types::{EditPrediction, EditPredictionDelegate, interpolate_edits};
+use gpui::{App, Context, Entity, Task};
+use language::{Anchor, Buffer, EditPreview, OffsetRangeExt};
+use std::{ops::Range, sync::Arc, time::Duration};
pub const COPILOT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(75);
pub struct CopilotEditPredictionDelegate {
- cycled: bool,
- buffer_id: Option<EntityId>,
- completions: Vec<Completion>,
- active_completion_index: usize,
- file_extension: Option<String>,
+ completion: Option<(CopilotEditPrediction, EditPreview)>,
pending_refresh: Option<Task<Result<()>>>,
- pending_cycling_refresh: Option<Task<Result<()>>>,
copilot: Entity<Copilot>,
}
impl CopilotEditPredictionDelegate {
pub fn new(copilot: Entity<Copilot>) -> Self {
Self {
- cycled: false,
- buffer_id: None,
- completions: Vec::new(),
- active_completion_index: 0,
- file_extension: None,
+ completion: None,
pending_refresh: None,
- pending_cycling_refresh: None,
copilot,
}
}
- fn active_completion(&self) -> Option<&Completion> {
- self.completions.get(self.active_completion_index)
- }
-
- fn push_completion(&mut self, new_completion: Completion) {
- for completion in &self.completions {
- if completion.text == new_completion.text && completion.range == new_completion.range {
- return;
- }
- }
- self.completions.push(new_completion);
+ fn active_completion(&self) -> Option<&(CopilotEditPrediction, EditPreview)> {
+ self.completion.as_ref()
}
}
@@ -64,12 +44,8 @@ impl EditPredictionDelegate for CopilotEditPredictionDelegate {
true
}
- fn supports_jump_to_edit() -> bool {
- false
- }
-
fn is_refreshing(&self, _cx: &App) -> bool {
- self.pending_refresh.is_some() && self.completions.is_empty()
+ self.pending_refresh.is_some() && self.completion.is_none()
}
fn is_enabled(
@@ -102,160 +78,96 @@ impl EditPredictionDelegate for CopilotEditPredictionDelegate {
})?
.await?;
- this.update(cx, |this, cx| {
- if !completions.is_empty() {
- this.cycled = false;
+ if let Some(mut completion) = completions.into_iter().next()
+ && let Some(trimmed_completion) = cx
+ .update(|cx| trim_completion(&completion, cx))
+ .ok()
+ .flatten()
+ {
+ let preview = buffer
+ .update(cx, |this, cx| {
+ this.preview_edits(Arc::from(std::slice::from_ref(&trimmed_completion)), cx)
+ })?
+ .await;
+ this.update(cx, |this, cx| {
this.pending_refresh = None;
- this.pending_cycling_refresh = None;
- this.completions.clear();
- this.active_completion_index = 0;
- this.buffer_id = Some(buffer.entity_id());
- this.file_extension = buffer.read(cx).file().and_then(|file| {
- Some(
- Path::new(file.file_name(cx))
- .extension()?
- .to_str()?
- .to_string(),
- )
- });
-
- for completion in completions {
- this.push_completion(completion);
- }
+ completion.range = trimmed_completion.0;
+ completion.text = trimmed_completion.1.to_string();
+ this.completion = Some((completion, preview));
+
cx.notify();
- }
- })?;
+ })?;
+ }
Ok(())
}));
}
- fn cycle(
- &mut self,
- buffer: Entity<Buffer>,
- cursor_position: language::Anchor,
- direction: Direction,
- cx: &mut Context<Self>,
- ) {
- if self.cycled {
- match direction {
- Direction::Prev => {
- self.active_completion_index = if self.active_completion_index == 0 {
- self.completions.len().saturating_sub(1)
- } else {
- self.active_completion_index - 1
- };
- }
- Direction::Next => {
- if self.completions.is_empty() {
- self.active_completion_index = 0
- } else {
- self.active_completion_index =
- (self.active_completion_index + 1) % self.completions.len();
- }
- }
- }
-
- cx.notify();
- } else {
- let copilot = self.copilot.clone();
- self.pending_cycling_refresh = Some(cx.spawn(async move |this, cx| {
- let completions = copilot
- .update(cx, |copilot, cx| {
- copilot.completions_cycling(&buffer, cursor_position, cx)
- })?
- .await?;
-
- this.update(cx, |this, cx| {
- this.cycled = true;
- this.file_extension = buffer.read(cx).file().and_then(|file| {
- Some(
- Path::new(file.file_name(cx))
- .extension()?
- .to_str()?
- .to_string(),
- )
- });
- for completion in completions {
- this.push_completion(completion);
- }
- this.cycle(buffer, cursor_position, direction, cx);
- })?;
-
- Ok(())
- }));
- }
- }
-
fn accept(&mut self, cx: &mut Context<Self>) {
- if let Some(completion) = self.active_completion() {
+ if let Some((completion, _)) = self.active_completion() {
self.copilot
.update(cx, |copilot, cx| copilot.accept_completion(completion, cx))
.detach_and_log_err(cx);
}
}
- fn discard(&mut self, cx: &mut Context<Self>) {
- let settings = AllLanguageSettings::get_global(cx);
-
- let copilot_enabled = settings.show_edit_predictions(None, cx);
-
- if !copilot_enabled {
- return;
- }
-
- self.copilot
- .update(cx, |copilot, cx| {
- copilot.discard_completions(&self.completions, cx)
- })
- .detach_and_log_err(cx);
- }
+ fn discard(&mut self, _: &mut Context<Self>) {}
fn suggest(
&mut self,
buffer: &Entity<Buffer>,
- cursor_position: language::Anchor,
+ _: language::Anchor,
cx: &mut Context<Self>,
) -> Option<EditPrediction> {
let buffer_id = buffer.entity_id();
let buffer = buffer.read(cx);
- let completion = self.active_completion()?;
- if Some(buffer_id) != self.buffer_id
+ let (completion, edit_preview) = self.active_completion()?;
+
+ if Some(buffer_id) != Some(completion.buffer.entity_id())
|| !completion.range.start.is_valid(buffer)
|| !completion.range.end.is_valid(buffer)
{
return None;
}
+ let edits = vec![(
+ completion.range.clone(),
+ Arc::from(completion.text.as_ref()),
+ )];
+ let edits = interpolate_edits(&completion.snapshot, &buffer.snapshot(), &edits)
+ .filter(|edits| !edits.is_empty())?;
+
+ Some(EditPrediction::Local {
+ id: None,
+ edits,
+ edit_preview: Some(edit_preview.clone()),
+ })
+ }
+}
- let mut completion_range = completion.range.to_offset(buffer);
- let prefix_len = common_prefix(
- buffer.chars_for_range(completion_range.clone()),
- completion.text.chars(),
- );
- completion_range.start += prefix_len;
- let suffix_len = common_prefix(
- buffer.reversed_chars_for_range(completion_range.clone()),
- completion.text[prefix_len..].chars().rev(),
- );
- completion_range.end = completion_range.end.saturating_sub(suffix_len);
-
- if completion_range.is_empty()
- && completion_range.start == cursor_position.to_offset(buffer)
- {
- let completion_text = &completion.text[prefix_len..completion.text.len() - suffix_len];
- if completion_text.trim().is_empty() {
- None
- } else {
- let position = cursor_position.bias_right(buffer);
- Some(EditPrediction::Local {
- id: None,
- edits: vec![(position..position, completion_text.into())],
- edit_preview: None,
- })
- }
- } else {
- None
- }
+fn trim_completion(
+ completion: &CopilotEditPrediction,
+ cx: &mut App,
+) -> Option<(Range<Anchor>, Arc<str>)> {
+ let buffer = completion.buffer.read(cx);
+ let mut completion_range = completion.range.to_offset(buffer);
+ let prefix_len = common_prefix(
+ buffer.chars_for_range(completion_range.clone()),
+ completion.text.chars(),
+ );
+ completion_range.start += prefix_len;
+ let suffix_len = common_prefix(
+ buffer.reversed_chars_for_range(completion_range.clone()),
+ completion.text[prefix_len..].chars().rev(),
+ );
+ completion_range.end = completion_range.end.saturating_sub(suffix_len);
+ let completion_text = &completion.text[prefix_len..completion.text.len() - suffix_len];
+ if completion_text.trim().is_empty() {
+ None
+ } else {
+ let completion_range =
+ buffer.anchor_after(completion_range.start)..buffer.anchor_after(completion_range.end);
+
+ Some((completion_range, Arc::from(completion_text)))
}
}
@@ -282,6 +194,7 @@ mod tests {
Point,
language_settings::{CompletionSettingsContent, LspInsertMode, WordsCompletionMode},
};
+ use lsp::Uri;
use project::Project;
use serde_json::json;
use settings::{AllLanguageSettingsContent, SettingsStore};
@@ -337,12 +250,15 @@ mod tests {
));
handle_copilot_completion_request(
&copilot_lsp,
- vec![crate::request::Completion {
+ vec![crate::request::NextEditSuggestion {
text: "one.copilot1".into(),
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
- ..Default::default()
+ command: None,
+ text_document: lsp::VersionedTextDocumentIdentifier {
+ uri: Uri::from_file_path(path!("/root/dir/file.rs")).unwrap(),
+ version: 0,
+ },
}],
- vec![],
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, window, cx| {
@@ -383,12 +299,15 @@ mod tests {
));
handle_copilot_completion_request(
&copilot_lsp,
- vec![crate::request::Completion {
+ vec![crate::request::NextEditSuggestion {
text: "one.copilot1".into(),
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
- ..Default::default()
+ command: None,
+ text_document: lsp::VersionedTextDocumentIdentifier {
+ uri: Uri::from_file_path(path!("/root/dir/file.rs")).unwrap(),
+ version: 0,
+ },
}],
- vec![],
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, _, cx| {
@@ -412,12 +331,15 @@ mod tests {
// After debouncing, new Copilot completions should be requested.
handle_copilot_completion_request(
&copilot_lsp,
- vec![crate::request::Completion {
+ vec![crate::request::NextEditSuggestion {
text: "one.copilot2".into(),
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 5)),
- ..Default::default()
+ command: None,
+ text_document: lsp::VersionedTextDocumentIdentifier {
+ uri: Uri::from_file_path(path!("/root/dir/file.rs")).unwrap(),
+ version: 0,
+ },
}],
- vec![],
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, window, cx| {
@@ -479,45 +401,6 @@ mod tests {
assert_eq!(editor.display_text(cx), "one.cop\ntwo\nthree\n");
assert_eq!(editor.text(cx), "one.cop\ntwo\nthree\n");
});
-
- // Reset the editor to verify how suggestions behave when tabbing on leading indentation.
- cx.update_editor(|editor, window, cx| {
- editor.set_text("fn foo() {\n \n}", window, cx);
- editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
- s.select_ranges([Point::new(1, 2)..Point::new(1, 2)])
- });
- });
- handle_copilot_completion_request(
- &copilot_lsp,
- vec![crate::request::Completion {
- text: " let x = 4;".into(),
- range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
- ..Default::default()
- }],
- vec![],
- );
-
- cx.update_editor(|editor, window, cx| {
- editor.next_edit_prediction(&Default::default(), window, cx)
- });
- executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
- cx.update_editor(|editor, window, cx| {
- assert!(editor.has_active_edit_prediction());
- assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
- assert_eq!(editor.text(cx), "fn foo() {\n \n}");
-
- // Tabbing inside of leading whitespace inserts indentation without accepting the suggestion.
- editor.tab(&Default::default(), window, cx);
- assert!(editor.has_active_edit_prediction());
- assert_eq!(editor.text(cx), "fn foo() {\n \n}");
- assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
-
- // Using AcceptEditPrediction again accepts the suggestion.
- editor.accept_edit_prediction(&Default::default(), window, cx);
- assert!(!editor.has_active_edit_prediction());
- assert_eq!(editor.text(cx), "fn foo() {\n let x = 4;\n}");
- assert_eq!(editor.display_text(cx), "fn foo() {\n let x = 4;\n}");
- });
}
#[gpui::test(iterations = 10)]
@@ -570,12 +453,15 @@ mod tests {
));
handle_copilot_completion_request(
&copilot_lsp,
- vec![crate::request::Completion {
+ vec![crate::request::NextEditSuggestion {
text: "one.copilot1".into(),
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
- ..Default::default()
+ command: None,
+ text_document: lsp::VersionedTextDocumentIdentifier {
+ uri: Uri::from_file_path(path!("/root/dir/file.rs")).unwrap(),
+ version: 0,
+ },
}],
- vec![],
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, window, cx| {
@@ -614,12 +500,15 @@ mod tests {
));
handle_copilot_completion_request(
&copilot_lsp,
- vec![crate::request::Completion {
+ vec![crate::request::NextEditSuggestion {
text: "one.123. copilot\n 456".into(),
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 4)),
- ..Default::default()
+ command: None,
+ text_document: lsp::VersionedTextDocumentIdentifier {
+ uri: Uri::from_file_path(path!("/root/dir/file.rs")).unwrap(),
+ version: 0,
+ },
}],
- vec![],
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, window, cx| {
@@ -686,15 +575,18 @@ mod tests {
handle_copilot_completion_request(
&copilot_lsp,
- vec![crate::request::Completion {
+ vec![crate::request::NextEditSuggestion {
text: "two.foo()".into(),
range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
- ..Default::default()
+ command: None,
+ text_document: lsp::VersionedTextDocumentIdentifier {
+ uri: Uri::from_file_path(path!("/root/dir/file.rs")).unwrap(),
+ version: 0,
+ },
}],
- vec![],
);
cx.update_editor(|editor, window, cx| {
- editor.next_edit_prediction(&Default::default(), window, cx)
+ editor.show_edit_prediction(&Default::default(), window, cx)
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, window, cx| {
@@ -703,15 +595,22 @@ mod tests {
assert_eq!(editor.text(cx), "one\ntw\nthree\n");
editor.backspace(&Default::default(), window, cx);
+ });
+ executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
+ cx.run_until_parked();
+ cx.update_editor(|editor, window, cx| {
assert!(editor.has_active_edit_prediction());
assert_eq!(editor.display_text(cx), "one\ntwo.foo()\nthree\n");
assert_eq!(editor.text(cx), "one\nt\nthree\n");
editor.backspace(&Default::default(), window, cx);
+ });
+ executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
+ cx.run_until_parked();
+ cx.update_editor(|editor, window, cx| {
assert!(editor.has_active_edit_prediction());
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(), window, cx);
assert!(!editor.has_active_edit_prediction());
@@ -765,19 +664,22 @@ mod tests {
handle_copilot_completion_request(
&copilot_lsp,
- vec![crate::request::Completion {
+ vec![crate::request::NextEditSuggestion {
text: "b = 2 + a".into(),
range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 5)),
- ..Default::default()
+ command: None,
+ text_document: lsp::VersionedTextDocumentIdentifier {
+ uri: Uri::from_file_path(path!("/root/dir/file.rs")).unwrap(),
+ version: 0,
+ },
}],
- vec![],
);
_ = editor.update(cx, |editor, window, cx| {
// Ensure copilot suggestions are shown for the first excerpt.
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
s.select_ranges([Point::new(1, 5)..Point::new(1, 5)])
});
- editor.next_edit_prediction(&Default::default(), window, cx);
+ editor.show_edit_prediction(&Default::default(), window, cx);
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
_ = editor.update(cx, |editor, _, cx| {
@@ -791,12 +693,15 @@ mod tests {
handle_copilot_completion_request(
&copilot_lsp,
- vec![crate::request::Completion {
+ vec![crate::request::NextEditSuggestion {
text: "d = 4 + c".into(),
range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 6)),
- ..Default::default()
+ command: None,
+ text_document: lsp::VersionedTextDocumentIdentifier {
+ uri: Uri::from_file_path(path!("/root/dir/file.rs")).unwrap(),
+ version: 0,
+ },
}],
- vec![],
);
_ = editor.update(cx, |editor, window, cx| {
// Move to another excerpt, ensuring the suggestion gets cleared.
@@ -873,15 +778,18 @@ mod tests {
));
handle_copilot_completion_request(
&copilot_lsp,
- vec![crate::request::Completion {
+ vec![crate::request::NextEditSuggestion {
text: "two.foo()".into(),
range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 2)),
- ..Default::default()
+ command: None,
+ text_document: lsp::VersionedTextDocumentIdentifier {
+ uri: Uri::from_file_path(path!("/root/dir/file.rs")).unwrap(),
+ version: 0,
+ },
}],
- vec![],
);
cx.update_editor(|editor, window, cx| {
- editor.next_edit_prediction(&Default::default(), window, cx)
+ editor.show_edit_prediction(&Default::default(), window, cx)
});
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, _, cx| {
@@ -903,12 +811,15 @@ mod tests {
));
handle_copilot_completion_request(
&copilot_lsp,
- vec![crate::request::Completion {
+ vec![crate::request::NextEditSuggestion {
text: "two.foo()".into(),
range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 3)),
- ..Default::default()
+ command: None,
+ text_document: lsp::VersionedTextDocumentIdentifier {
+ uri: Uri::from_file_path(path!("/root/dir/file.rs")).unwrap(),
+ version: 0,
+ },
}],
- vec![],
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, _, cx| {
@@ -930,12 +841,15 @@ mod tests {
));
handle_copilot_completion_request(
&copilot_lsp,
- vec![crate::request::Completion {
+ vec![crate::request::NextEditSuggestion {
text: "two.foo()".into(),
range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 4)),
- ..Default::default()
+ command: None,
+ text_document: lsp::VersionedTextDocumentIdentifier {
+ uri: Uri::from_file_path(path!("/root/dir/file.rs")).unwrap(),
+ version: 0,
+ },
}],
- vec![],
);
executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT);
cx.update_editor(|editor, _, cx| {
@@ -1011,16 +925,20 @@ mod tests {
.unwrap();
let mut copilot_requests = copilot_lsp
- .set_request_handler::<crate::request::GetCompletions, _, _>(
+ .set_request_handler::<crate::request::NextEditSuggestions, _, _>(
move |_params, _cx| async move {
- Ok(crate::request::GetCompletionsResult {
- completions: vec![crate::request::Completion {
+ Ok(crate::request::NextEditSuggestionsResult {
+ edits: vec![crate::request::NextEditSuggestion {
text: "next line".into(),
range: lsp::Range::new(
lsp::Position::new(1, 0),
lsp::Position::new(1, 0),
),
- ..Default::default()
+ command: None,
+ text_document: lsp::VersionedTextDocumentIdentifier {
+ uri: Uri::from_file_path(path!("/root/dir/file.rs")).unwrap(),
+ version: 0,
+ },
}],
})
},
@@ -1049,23 +967,14 @@ mod tests {
fn handle_copilot_completion_request(
lsp: &lsp::FakeLanguageServer,
- completions: Vec<crate::request::Completion>,
- completions_cycling: Vec<crate::request::Completion>,
+ completions: Vec<crate::request::NextEditSuggestion>,
) {
- lsp.set_request_handler::<crate::request::GetCompletions, _, _>(move |_params, _cx| {
- let completions = completions.clone();
- async move {
- Ok(crate::request::GetCompletionsResult {
- completions: completions.clone(),
- })
- }
- });
- lsp.set_request_handler::<crate::request::GetCompletionsCycling, _, _>(
+ lsp.set_request_handler::<crate::request::NextEditSuggestions, _, _>(
move |_params, _cx| {
- let completions_cycling = completions_cycling.clone();
+ let completions = completions.clone();
async move {
- Ok(crate::request::GetCompletionsResult {
- completions: completions_cycling.clone(),
+ Ok(crate::request::NextEditSuggestionsResult {
+ edits: completions.clone(),
})
}
},