From e2b0b96c8ff46b8296f97c3284beb9077eed0e46 Mon Sep 17 00:00:00 2001 From: alexanderdickie <75994927+AlexanderDickie@users.noreply.github.com> Date: Wed, 3 Jul 2024 09:21:32 +0100 Subject: [PATCH] refactor logic so handlers::copilot::copilot_completion only effects updating the completion state, and no longer effects rendering --- helix-term/src/commands.rs | 10 ++-- helix-term/src/handlers/copilot.rs | 54 ++++++++------------- helix-view/src/copilot.rs | 76 ++++++++++++++++++++++++++++++ helix-view/src/document.rs | 47 ++++-------------- helix-view/src/editor.rs | 2 - helix-view/src/handlers.rs | 2 +- helix-view/src/handlers/lsp.rs | 5 +- helix-view/src/lib.rs | 1 + 8 files changed, 114 insertions(+), 83 deletions(-) create mode 100644 helix-view/src/copilot.rs diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 36c6ee9b850c..bb1c885164e3 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -6123,16 +6123,16 @@ fn copilot_apply_completion(cx: &mut Context) { fn copilot_show_completion(cx: &mut Context) { let (_, doc) = current!(cx.editor); - if let Some(copilot) = doc.copilot.as_mut() { - copilot.should_render = true; - } + doc.copilot.show_completion(); } fn copilot_toggle_auto_render(cx: &mut Context) { - cx.editor.auto_render_copilot = !cx.editor.auto_render_copilot; + let (_, doc) = current!(cx.editor); + let auto_render = doc.copilot.toggle_auto_render(); + cx.editor.set_status(format!( "copilot-auto-render = {}", - cx.editor.auto_render_copilot + auto_render, )); } diff --git a/helix-term/src/handlers/copilot.rs b/helix-term/src/handlers/copilot.rs index 8d06c656ae81..65d23b2666a3 100644 --- a/helix-term/src/handlers/copilot.rs +++ b/helix-term/src/handlers/copilot.rs @@ -5,63 +5,54 @@ use helix_event::{ }; use helix_lsp::copilot_types::DocCompletion; use helix_lsp::util::{lsp_pos_to_pos, lsp_range_to_range}; -use helix_view::document::{Copilot, Mode}; +use helix_view::document::Mode; use helix_view::Editor; use helix_view::events::DocumentDidChange; use tokio::time::Instant; use crate::events::OnModeSwitch; use crate::handlers::Handlers; -use helix_view::{handlers::lsp::CopilotEvent, DocumentId}; +use helix_view::handlers::lsp::CopilotRequestCompletionEvent; use crate::job::{dispatch, dispatch_blocking}; pub struct CopilotHandler { - doc_id: Option, cancel: Option, } impl CopilotHandler { pub fn new() -> Self { Self { - doc_id: None, cancel: None, } } } impl helix_event::AsyncHook for CopilotHandler { - type Event = CopilotEvent; + type Event = CopilotRequestCompletionEvent; fn handle_event( &mut self, - event: Self::Event, + _: Self::Event, _: Option, ) -> Option { - match event { - CopilotEvent::RequestCompletion { doc_id } => { - self.doc_id = Some(doc_id); - self.cancel.take(); - Some(Instant::now() + Duration::from_millis(100)) - } - CopilotEvent::CancelInFlightCompletion => { - self.cancel.take(); - None - } - } + self.cancel.take(); + Some(Instant::now() + Duration::from_millis(100)) } fn finish_debounce(&mut self) { - let Some(doc_id) = self.doc_id else {return;}; let (tx, rx) = cancelation(); self.cancel = Some(tx); dispatch_blocking(move |editor, _| { - copilot_completion(editor, doc_id, rx); + copilot_completion(editor, rx); }); } } -fn copilot_completion(editor: &mut Editor, doc_id: DocumentId, cancel: CancelRx) { +fn copilot_completion(editor: &mut Editor, cancel: CancelRx) { let (view, doc) = current_ref!(editor); - if doc.id() != doc_id || editor.mode() != Mode::Insert { return; } + // check editor mode since we request a completion on DocumentDidChange even when not in Insert Mode + // (this cannot be checked within try_register_hooks unforunately) + // (the completion will not render, but there is still not point sending the request to the copilot lsp) + if editor.mode() != Mode::Insert { return; } let Some(copilot_ls) = doc .language_servers() @@ -86,9 +77,8 @@ fn copilot_completion(editor: &mut Editor, doc_id: DocumentId, cancel: CancelRx) return; }; - if editor.mode() != Mode::Insert { return; } let (view, doc) = current!(editor); - let doc_completion = completions + let doc_completions = completions .into_iter() .filter_map(|completion| { /* @@ -139,12 +129,7 @@ fn copilot_completion(editor: &mut Editor, doc_id: DocumentId, cancel: CancelRx) }) .collect::>(); - doc.copilot = Some(Copilot { - should_render: editor.auto_render_copilot, - completions: doc_completion, - idx: 0, - offset_encoding, - }); + doc.copilot.fill_with_completions(doc_completions, offset_encoding); }) .await; } @@ -157,19 +142,20 @@ pub(super) fn try_register_hooks(handlers: &Handlers) { let tx = copilot_handler.clone(); register_hook!(move |event: &mut DocumentDidChange<'_>| { - event.doc.clear_copilot_completions(); - send_blocking(&tx, CopilotEvent::RequestCompletion { doc_id: event.doc.id() }); + event.doc.copilot.delete_state_and_reset_should_render(); + send_blocking(&tx, CopilotRequestCompletionEvent); Ok(()) }); let tx = copilot_handler.clone(); register_hook!(move |event: &mut OnModeSwitch<'_, '_>| { let (_, doc) = current!(event.cx.editor); - doc.clear_copilot_completions(); + if event.old_mode == Mode::Insert { - send_blocking(&tx, CopilotEvent::CancelInFlightCompletion); + doc.copilot.delete_state_and_should_not_render(); } else if event.new_mode == Mode::Insert { - send_blocking(&tx, CopilotEvent::RequestCompletion { doc_id: doc.id() }); + doc.copilot.delete_state_and_reset_should_render(); + send_blocking(&tx, CopilotRequestCompletionEvent); } Ok(()) }); diff --git a/helix-view/src/copilot.rs b/helix-view/src/copilot.rs new file mode 100644 index 000000000000..e94d6430621c --- /dev/null +++ b/helix-view/src/copilot.rs @@ -0,0 +1,76 @@ +use helix_lsp::{copilot_types::DocCompletion, OffsetEncoding}; +use parking_lot::Mutex; +use std::sync::Arc; + +static GLOBAL_AUTO_RENDER: once_cell::sync::OnceCell>> = once_cell::sync::OnceCell::new(); + +#[derive(Clone, Debug)] +pub struct Copilot { + completion_response: Option<(Vec, OffsetEncoding)> , + render: Render +} + +#[derive(Clone, Debug)] +struct Render { + global_auto_render: Arc>, + should_render: Option, +} + +impl Render { + pub fn reset(& mut self) { + let lock = self.global_auto_render.lock(); + self.should_render = if *lock {Some(0)} else {None}; + } + pub fn should_not_render(&mut self) { + self.should_render = None; + } + } + +impl Copilot { + pub fn new(editor_auto_render: bool) -> Copilot { + let global_auto_render_completion = GLOBAL_AUTO_RENDER.get_or_init(|| Arc::new(Mutex::new(editor_auto_render))).clone(); + + return Self { + completion_response: None, + render: Render { + global_auto_render: global_auto_render_completion, + should_render: None + } + } + } + + pub fn delete_state_and_reset_should_render(&mut self) { + self.render.reset(); + self.completion_response = None; + } + + pub fn delete_state_and_should_not_render(&mut self) { + self.render.should_not_render(); + self.completion_response = None; + } + + pub fn show_completion(&mut self) { + self.render.should_render = Some(0); + } + + pub fn fill_with_completions(&mut self, completions: Vec, offset_encoding: OffsetEncoding) { + self.completion_response = Some((completions, offset_encoding)); + } + + pub fn get_completion_if_should_render(&self) -> Option<&DocCompletion> { + let idx = self.render.should_render?; + let completions = &self.completion_response.as_ref()?.0; + completions.get(idx) + } + + pub fn offset_encoding(&self) -> Option { + Some(self.completion_response.as_ref()?.1) + } + + pub fn toggle_auto_render(&self) -> bool { + let mut lock = self.render.global_auto_render.lock(); + *lock = !(*lock); + return *lock; + } + +} diff --git a/helix-view/src/document.rs b/helix-view/src/document.rs index 0d6c3e985a20..39990ae1665d 100644 --- a/helix-view/src/document.rs +++ b/helix-view/src/document.rs @@ -40,6 +40,7 @@ use helix_core::{ ChangeSet, Diagnostic, LineEnding, Rope, RopeBuilder, Selection, Syntax, Transaction, }; +use crate::copilot::Copilot; use crate::editor::Config; use crate::events::{DocumentDidChange, SelectionDidChange}; use crate::{DocumentId, Editor, Theme, View, ViewId}; @@ -129,16 +130,8 @@ pub enum DocumentOpenError { IoError(#[from] io::Error), } -#[derive(Clone, Debug)] -pub struct Copilot { - pub should_render: bool, - pub completions: Vec, - pub idx: usize, - pub offset_encoding: OffsetEncoding, -} - pub struct Document { - pub copilot: Option, + pub copilot: Copilot, pub(crate) id: DocumentId, text: Rope, selections: HashMap, @@ -649,46 +642,25 @@ use helix_lsp::{copilot_types, Client, LanguageServerId, LanguageServerName}; use url::Url; impl Document { - pub fn clear_copilot_completions(&mut self) { - self.copilot = None; - } - pub fn get_copilot_completion_for_rendering(&self) -> Option<&DocCompletion> { - let Some(copilot) = self.copilot.as_ref() else { - return None; - }; - if !copilot.should_render { - return None; - } + let completion = self.copilot.get_completion_if_should_render()?; - let completion = copilot.completions.get(copilot.idx)?; if self.version as usize != completion.doc_version { return None; } - return Some(completion); + Some(completion) } - pub fn apply_copilot_completion(&mut self, view_id: ViewId) { - if let None = self.get_copilot_completion_for_rendering() { - return; - } - let Some(copilot) = self.copilot.as_ref() else { - return; - }; - - let Some(completion) = copilot.completions.get(copilot.idx) else { - return; - }; - if completion.doc_version != self.version as usize { - return; - } + pub fn apply_copilot_completion(&mut self, view_id: ViewId) { + let Some(completion) = self.copilot.get_completion_if_should_render() else { return; }; + let Some(offset_encoding) = self.copilot.offset_encoding() else { return; }; let edit = lsp::TextEdit { range: completion.lsp_range, new_text: completion.text.clone(), }; let transaction = - generate_transaction_from_edits(self.text(), vec![edit], copilot.offset_encoding); + generate_transaction_from_edits(self.text(), vec![edit], offset_encoding); self.apply(&transaction, view_id); } @@ -702,9 +674,10 @@ impl Document { let line_ending = config.load().default_line_ending.into(); let changes = ChangeSet::new(text.slice(..)); let old_state = None; + let copilot_auto_render = config.load().copilot_auto_render; Self { - copilot: None, + copilot: Copilot::new(copilot_auto_render), id: DocumentId::default(), path: None, encoding, diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 7a5dc718abfd..eb141ae2b913 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -1013,7 +1013,6 @@ pub struct Breakpoint { use futures_util::stream::{Flatten, Once}; pub struct Editor { - pub auto_render_copilot: bool, /// Current editing mode. pub mode: Mode, pub tree: Tree, @@ -1164,7 +1163,6 @@ impl Editor { area.height -= 1; Self { - auto_render_copilot: conf.copilot_auto_render, mode: Mode::Normal, tree: Tree::new(area), next_document_id: DocumentId::default(), diff --git a/helix-view/src/handlers.rs b/helix-view/src/handlers.rs index f2d89ed4cef1..8bff45d90012 100644 --- a/helix-view/src/handlers.rs +++ b/helix-view/src/handlers.rs @@ -18,7 +18,7 @@ pub struct Handlers { pub completions: Sender, pub signature_hints: Sender, pub auto_save: Sender, - pub copilot: Option>, + pub copilot: Option>, } impl Handlers { diff --git a/helix-view/src/handlers/lsp.rs b/helix-view/src/handlers/lsp.rs index 47b60eb41188..2d4f2a297b15 100644 --- a/helix-view/src/handlers/lsp.rs +++ b/helix-view/src/handlers/lsp.rs @@ -4,10 +4,7 @@ use crate::{DocumentId, ViewId}; use helix_lsp::util::generate_transaction_from_edits; use helix_lsp::{lsp, OffsetEncoding}; -pub enum CopilotEvent { - RequestCompletion { doc_id: DocumentId }, - CancelInFlightCompletion, -} +pub struct CopilotRequestCompletionEvent; pub enum CompletionEvent { /// Auto completion was triggered by typing a word char diff --git a/helix-view/src/lib.rs b/helix-view/src/lib.rs index 5628c830cfb7..782c26d08720 100644 --- a/helix-view/src/lib.rs +++ b/helix-view/src/lib.rs @@ -17,6 +17,7 @@ pub mod register; pub mod theme; pub mod tree; pub mod view; +pub mod copilot; use std::num::NonZeroUsize;