Notify LSP when Copilot suggestions are accepted/rejected

This commit is contained in:
Antonio Scandurra 2023-04-20 10:12:13 +02:00
parent 5d57167302
commit 4d207981ae
3 changed files with 123 additions and 27 deletions

View file

@ -224,8 +224,9 @@ impl RegisteredBuffer {
} }
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug)]
pub struct Completion { pub struct Completion {
uuid: String,
pub range: Range<Anchor>, pub range: Range<Anchor>,
pub text: String, pub text: String,
} }
@ -684,6 +685,51 @@ impl Copilot {
self.request_completions::<request::GetCompletionsCycling, _>(buffer, position, cx) self.request_completions::<request::GetCompletionsCycling, _>(buffer, position, cx)
} }
pub fn accept_completion(
&mut self,
completion: &Completion,
cx: &mut ModelContext<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?;
Ok(())
})
}
pub fn discard_completions(
&mut self,
completions: &[Completion],
cx: &mut ModelContext<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::NotifyRejected>(request::NotifyRejectedParams {
uuids: completions
.iter()
.map(|completion| completion.uuid.clone())
.collect(),
});
cx.background().spawn(async move {
request.await?;
Ok(())
})
}
fn request_completions<R, T>( fn request_completions<R, T>(
&mut self, &mut self,
buffer: &ModelHandle<Buffer>, buffer: &ModelHandle<Buffer>,

View file

@ -195,3 +195,31 @@ pub struct EditorPluginInfo {
pub name: String, pub name: String,
pub version: String, pub version: String,
} }
pub enum NotifyAccepted {}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NotifyAcceptedParams {
pub uuid: String,
}
impl lsp::request::Request for NotifyAccepted {
type Params = NotifyAcceptedParams;
type Result = String;
const METHOD: &'static str = "notifyAccepted";
}
pub enum NotifyRejected {}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NotifyRejectedParams {
pub uuids: Vec<String>,
}
impl lsp::request::Request for NotifyRejected {
type Params = NotifyRejectedParams;
type Result = String;
const METHOD: &'static str = "notifyRejected";
}

View file

@ -52,7 +52,7 @@ pub use language::{char_kind, CharKind};
use language::{ use language::{
AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape, AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape,
Diagnostic, DiagnosticSeverity, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16, Diagnostic, DiagnosticSeverity, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16,
Point, Rope, Selection, SelectionGoal, TransactionId, Point, Selection, SelectionGoal, TransactionId,
}; };
use link_go_to_definition::{ use link_go_to_definition::{
hide_link_definition, show_link_definition, LinkDefinitionKind, LinkGoToDefinitionState, hide_link_definition, show_link_definition, LinkDefinitionKind, LinkGoToDefinitionState,
@ -1037,6 +1037,10 @@ impl Default for CopilotState {
} }
impl CopilotState { impl CopilotState {
fn active_completion(&self) -> Option<&copilot::Completion> {
self.completions.get(self.active_completion_index)
}
fn text_for_active_completion( fn text_for_active_completion(
&self, &self,
cursor: Anchor, cursor: Anchor,
@ -1044,7 +1048,7 @@ impl CopilotState {
) -> Option<&str> { ) -> Option<&str> {
use language::ToOffset as _; use language::ToOffset as _;
let completion = self.completions.get(self.active_completion_index)?; let completion = self.active_completion()?;
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 excerpt_id != cursor.excerpt_id if excerpt_id != cursor.excerpt_id
@ -1097,7 +1101,7 @@ impl CopilotState {
fn push_completion(&mut self, new_completion: copilot::Completion) { fn push_completion(&mut self, new_completion: copilot::Completion) {
for completion in &self.completions { for completion in &self.completions {
if *completion == new_completion { if completion.text == new_completion.text && completion.range == new_completion.range {
return; return;
} }
} }
@ -1496,7 +1500,7 @@ impl Editor {
self.refresh_code_actions(cx); self.refresh_code_actions(cx);
self.refresh_document_highlights(cx); self.refresh_document_highlights(cx);
refresh_matching_bracket_highlights(self, cx); refresh_matching_bracket_highlights(self, cx);
self.hide_copilot_suggestion(cx); self.discard_copilot_suggestion(cx);
} }
self.blink_manager.update(cx, BlinkManager::pause_blinking); self.blink_manager.update(cx, BlinkManager::pause_blinking);
@ -1870,7 +1874,7 @@ impl Editor {
return; return;
} }
if self.hide_copilot_suggestion(cx).is_some() { if self.discard_copilot_suggestion(cx) {
return; return;
} }
@ -2969,7 +2973,7 @@ impl Editor {
Some(()) Some(())
} }
fn cycle_suggestions( fn cycle_copilot_suggestions(
&mut self, &mut self,
direction: Direction, direction: Direction,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
@ -3020,7 +3024,7 @@ impl Editor {
fn next_copilot_suggestion(&mut self, _: &copilot::NextSuggestion, cx: &mut ViewContext<Self>) { fn next_copilot_suggestion(&mut self, _: &copilot::NextSuggestion, cx: &mut ViewContext<Self>) {
if self.has_active_copilot_suggestion(cx) { if self.has_active_copilot_suggestion(cx) {
self.cycle_suggestions(Direction::Next, cx); self.cycle_copilot_suggestions(Direction::Next, cx);
} else { } else {
self.refresh_copilot_suggestions(false, cx); self.refresh_copilot_suggestions(false, cx);
} }
@ -3032,15 +3036,45 @@ impl Editor {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) { ) {
if self.has_active_copilot_suggestion(cx) { if self.has_active_copilot_suggestion(cx) {
self.cycle_suggestions(Direction::Prev, cx); self.cycle_copilot_suggestions(Direction::Prev, cx);
} else { } else {
self.refresh_copilot_suggestions(false, cx); self.refresh_copilot_suggestions(false, cx);
} }
} }
fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool { fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
if let Some(text) = self.hide_copilot_suggestion(cx) { if let Some(suggestion) = self
self.insert_with_autoindent_mode(&text.to_string(), None, cx); .display_map
.update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx))
{
if let Some((copilot, completion)) =
Copilot::global(cx).zip(self.copilot_state.active_completion())
{
copilot
.update(cx, |copilot, cx| copilot.accept_completion(completion, cx))
.detach_and_log_err(cx);
}
self.insert_with_autoindent_mode(&suggestion.text.to_string(), None, cx);
cx.notify();
true
} else {
false
}
}
fn discard_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
if self.has_active_copilot_suggestion(cx) {
if let Some(copilot) = Copilot::global(cx) {
copilot
.update(cx, |copilot, cx| {
copilot.discard_completions(&self.copilot_state.completions, cx)
})
.detach_and_log_err(cx);
}
self.display_map
.update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx));
cx.notify();
true true
} else { } else {
false false
@ -3051,18 +3085,6 @@ impl Editor {
self.display_map.read(cx).has_suggestion() self.display_map.read(cx).has_suggestion()
} }
fn hide_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> Option<Rope> {
if self.has_active_copilot_suggestion(cx) {
let old_suggestion = self
.display_map
.update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx));
cx.notify();
old_suggestion.map(|suggestion| suggestion.text)
} else {
None
}
}
fn update_visible_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) { fn update_visible_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) {
let snapshot = self.buffer.read(cx).snapshot(cx); let snapshot = self.buffer.read(cx).snapshot(cx);
let selection = self.selections.newest_anchor(); let selection = self.selections.newest_anchor();
@ -3072,7 +3094,7 @@ impl Editor {
|| !self.completion_tasks.is_empty() || !self.completion_tasks.is_empty()
|| selection.start != selection.end || selection.start != selection.end
{ {
self.hide_copilot_suggestion(cx); self.discard_copilot_suggestion(cx);
} else if let Some(text) = self } else if let Some(text) = self
.copilot_state .copilot_state
.text_for_active_completion(cursor, &snapshot) .text_for_active_completion(cursor, &snapshot)
@ -3088,13 +3110,13 @@ impl Editor {
}); });
cx.notify(); cx.notify();
} else { } else {
self.hide_copilot_suggestion(cx); self.discard_copilot_suggestion(cx);
} }
} }
fn clear_copilot_suggestions(&mut self, cx: &mut ViewContext<Self>) { fn clear_copilot_suggestions(&mut self, cx: &mut ViewContext<Self>) {
self.copilot_state = Default::default(); self.copilot_state = Default::default();
self.hide_copilot_suggestion(cx); self.discard_copilot_suggestion(cx);
} }
pub fn render_code_actions_indicator( pub fn render_code_actions_indicator(
@ -3212,7 +3234,7 @@ impl Editor {
self.completion_tasks.clear(); self.completion_tasks.clear();
} }
self.context_menu = Some(menu); self.context_menu = Some(menu);
self.hide_copilot_suggestion(cx); self.discard_copilot_suggestion(cx);
cx.notify(); cx.notify();
} }