From 7524bc00bcad9250ade2c2865dfcd541ce04f294 Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Thu, 4 Jun 2026 18:52:29 -0400 Subject: [PATCH] ep: Improve trigger granularity (#58561) Self-Review Checklist: - [x] I've reviewed my own diff for quality, security, and reliability - [x] Unsafe blocks (if any) have justifying comments - [x] The content is consistent with the [UI/UX checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) - [x] Tests cover the new/changed behavior - [x] Performance impact has been considered and is acceptable Closes #ISSUE Release Notes: - N/A or Added/Fixed/Improved ... --- .../cloud_llm_client/src/cloud_llm_client.rs | 6 + crates/codestral/src/codestral.rs | 2 + .../src/copilot_edit_prediction_delegate.rs | 19 +- crates/edit_prediction/src/edit_prediction.rs | 56 ++++- .../src/edit_prediction_tests.rs | 212 +++++++++++++++--- .../src/zed_edit_prediction_delegate.rs | 11 +- .../src/edit_prediction_types.rs | 18 +- crates/editor/src/completions.rs | 8 +- crates/editor/src/diagnostics.rs | 8 +- crates/editor/src/edit_prediction.rs | 58 ++++- crates/editor/src/edit_prediction_tests.rs | 29 ++- crates/editor/src/editor.rs | 51 ++++- crates/editor/src/input.rs | 16 +- crates/vim/src/normal/change.rs | 18 +- crates/vim/src/normal/delete.rs | 18 +- 15 files changed, 457 insertions(+), 73 deletions(-) diff --git a/crates/cloud_llm_client/src/cloud_llm_client.rs b/crates/cloud_llm_client/src/cloud_llm_client.rs index b9dd071bac30e2..5796fb7c1b0425 100644 --- a/crates/cloud_llm_client/src/cloud_llm_client.rs +++ b/crates/cloud_llm_client/src/cloud_llm_client.rs @@ -122,7 +122,13 @@ pub struct PredictEditsBody { pub enum PredictEditsRequestTrigger { Testing, Diagnostics, + DiagnosticNavigation, Cli, + Explicit, + BufferEdit, + LSPCompletionAccepted, + PredictionAccepted, + PredictionPartiallyAccepted, #[default] Other, } diff --git a/crates/codestral/src/codestral.rs b/crates/codestral/src/codestral.rs index 7685fa8f5b1eae..64de772aec1a2b 100644 --- a/crates/codestral/src/codestral.rs +++ b/crates/codestral/src/codestral.rs @@ -2,6 +2,7 @@ use anyhow::Result; use edit_prediction::cursor_excerpt; use edit_prediction_types::{ EditPrediction, EditPredictionDelegate, EditPredictionDiscardReason, EditPredictionIconSet, + EditPredictionRequestTrigger, }; use futures::AsyncReadExt; use gpui::{App, AppContext as _, Context, Entity, Global, SharedString, Task}; @@ -222,6 +223,7 @@ impl EditPredictionDelegate for CodestralEditPredictionDelegate { buffer: Entity, cursor_position: language::Anchor, debounce: bool, + _trigger: EditPredictionRequestTrigger, cx: &mut Context, ) { log::debug!("Codestral: Refresh called (debounce: {})", debounce); diff --git a/crates/copilot/src/copilot_edit_prediction_delegate.rs b/crates/copilot/src/copilot_edit_prediction_delegate.rs index 4b75feafe4b38b..d295d94198f5b7 100644 --- a/crates/copilot/src/copilot_edit_prediction_delegate.rs +++ b/crates/copilot/src/copilot_edit_prediction_delegate.rs @@ -8,7 +8,7 @@ use crate::{ use anyhow::Result; use edit_prediction_types::{ EditPrediction, EditPredictionDelegate, EditPredictionDiscardReason, EditPredictionIconSet, - interpolate_edits, + EditPredictionRequestTrigger, interpolate_edits, }; use gpui::{App, Context, Entity, Task, TaskExt}; use icons::IconName; @@ -78,6 +78,7 @@ impl EditPredictionDelegate for CopilotEditPredictionDelegate { buffer: Entity, cursor_position: language::Anchor, debounce: bool, + _trigger: EditPredictionRequestTrigger, cx: &mut Context, ) { let copilot = self.copilot.clone(); @@ -1041,7 +1042,13 @@ mod tests { editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| { selections.select_ranges([Point::new(0, 0)..Point::new(0, 0)]) }); - editor.refresh_edit_prediction(true, false, window, cx); + editor.refresh_edit_prediction( + true, + false, + EditPredictionRequestTrigger::BufferEdit, + window, + cx, + ); }); executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); @@ -1051,7 +1058,13 @@ mod tests { editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { s.select_ranges([Point::new(5, 0)..Point::new(5, 0)]) }); - editor.refresh_edit_prediction(true, false, window, cx); + editor.refresh_edit_prediction( + true, + false, + EditPredictionRequestTrigger::BufferEdit, + window, + cx, + ); }); executor.advance_clock(COPILOT_DEBOUNCE_TIMEOUT); diff --git a/crates/edit_prediction/src/edit_prediction.rs b/crates/edit_prediction/src/edit_prediction.rs index f76cece98a1afa..e71d9119cd2dcb 100644 --- a/crates/edit_prediction/src/edit_prediction.rs +++ b/crates/edit_prediction/src/edit_prediction.rs @@ -22,6 +22,7 @@ use copilot::{Copilot, Reinstall, SignIn, SignOut}; use credentials_provider::CredentialsProvider; use db::kvp::{Dismissable, KeyValueStore}; use edit_prediction_context::{RelatedExcerptStore, RelatedExcerptStoreEvent, RelatedFile}; +use edit_prediction_types::EditPredictionRequestTrigger; use feature_flags::{FeatureFlag, FeatureFlagAppExt as _, PresenceFlag, register_feature_flag}; use futures::{ AsyncReadExt as _, FutureExt as _, StreamExt as _, @@ -842,6 +843,28 @@ pub(crate) fn buffer_path_with_id_fallback( } } +fn predict_edits_request_trigger_from_editor_trigger( + trigger: EditPredictionRequestTrigger, +) -> PredictEditsRequestTrigger { + match trigger { + EditPredictionRequestTrigger::DiagnosticNavigation => { + PredictEditsRequestTrigger::DiagnosticNavigation + } + EditPredictionRequestTrigger::Explicit => PredictEditsRequestTrigger::Explicit, + EditPredictionRequestTrigger::BufferEdit => PredictEditsRequestTrigger::BufferEdit, + EditPredictionRequestTrigger::LSPCompletionAccepted => { + PredictEditsRequestTrigger::LSPCompletionAccepted + } + EditPredictionRequestTrigger::PredictionAccepted => { + PredictEditsRequestTrigger::PredictionAccepted + } + EditPredictionRequestTrigger::PredictionPartiallyAccepted => { + PredictEditsRequestTrigger::PredictionPartiallyAccepted + } + EditPredictionRequestTrigger::Other => PredictEditsRequestTrigger::Other, + } +} + impl EditPredictionStore { pub fn try_global(cx: &App) -> Option> { cx.try_global::() @@ -2215,21 +2238,25 @@ impl EditPredictionStore { project: Entity, buffer: Entity, position: language::Anchor, + trigger: EditPredictionRequestTrigger, cx: &mut Context, ) { + let trigger = predict_edits_request_trigger_from_editor_trigger(trigger); + self.queue_prediction_refresh( project.clone(), - PredictEditsRequestTrigger::Other, + trigger, buffer.entity_id(), cx, move |this, cx| { let Some(request_task) = this .update(cx, |this, cx| { - this.request_prediction( - &project, - &buffer, + this.request_prediction_internal( + project.clone(), + buffer.clone(), position, - PredictEditsRequestTrigger::Other, + trigger, + cx.has_flag::(), cx, ) }) @@ -2333,11 +2360,12 @@ impl EditPredictionStore { let Some(prediction_result) = this .update(cx, |this, cx| { - this.request_prediction( - &project, - &jump_buffer, + this.request_prediction_internal( + project.clone(), + jump_buffer.clone(), jump_position, PredictEditsRequestTrigger::Diagnostics, + cx.has_flag::(), cx, ) })? @@ -2454,7 +2482,8 @@ impl EditPredictionStore { request_trigger: PredictEditsRequestTrigger, ) -> &mut Option<(EntityId, Instant)> { match request_trigger { - PredictEditsRequestTrigger::Diagnostics => { + PredictEditsRequestTrigger::Diagnostics + | PredictEditsRequestTrigger::DiagnosticNavigation => { &mut project_state.last_jump_prediction_refresh } _ => &mut project_state.last_edit_prediction_refresh, @@ -2769,7 +2798,8 @@ impl EditPredictionStore { inputs.snapshot.clone(), inputs.position, match trigger { - PredictEditsRequestTrigger::Diagnostics => { + PredictEditsRequestTrigger::Diagnostics + | PredictEditsRequestTrigger::DiagnosticNavigation => { JumpExampleTrigger::Diagnostic } _ => JumpExampleTrigger::Prediction, @@ -2802,7 +2832,11 @@ impl EditPredictionStore { if prediction.is_none() && allow_jump && has_events - && !matches!(trigger, PredictEditsRequestTrigger::Diagnostics) + && !matches!( + trigger, + PredictEditsRequestTrigger::Diagnostics + | PredictEditsRequestTrigger::DiagnosticNavigation + ) { this.update(cx, |this, cx| { this.refresh_prediction_from_diagnostics( diff --git a/crates/edit_prediction/src/edit_prediction_tests.rs b/crates/edit_prediction/src/edit_prediction_tests.rs index 7d4a5f54c38df1..3648bdc8f44dab 100644 --- a/crates/edit_prediction/src/edit_prediction_tests.rs +++ b/crates/edit_prediction/src/edit_prediction_tests.rs @@ -9,10 +9,12 @@ use cloud_api_types::{ SubmitEditPredictionSettledResponse, }; use cloud_llm_client::{ - EditPredictionRejectReason, EditPredictionRejection, RejectEditPredictionsBody, + EditPredictionRejectReason, EditPredictionRejection, PredictEditsRequestTrigger, + RejectEditPredictionsBody, predict_edits_v3::{PredictEditsV3Request, PredictEditsV3Response}, }; use db::AppDatabase; +use edit_prediction_types::EditPredictionRequestTrigger; use settings::EditPredictionDataCollectionChoice; use futures::{ @@ -84,7 +86,13 @@ async fn test_current_state(cx: &mut TestAppContext) { // Prediction for current file ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer1.clone(), position, cx) + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer1.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ) }); let (request, respond_tx) = requests.predict.next().await.unwrap(); @@ -239,7 +247,13 @@ async fn test_diagnostics_refresh_suppressed_while_following(cx: &mut TestAppCon ep_store.update(cx, |ep_store, cx| { ep_store.register_project(&project, cx); ep_store.register_buffer(&buffer1, &project, cx); - ep_store.refresh_prediction_from_buffer(project.clone(), buffer1.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer1.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); let (request, respond_tx) = requests.predict.next().await.unwrap(); @@ -441,7 +455,13 @@ async fn test_simple_request(cx: &mut TestAppContext) { let position = snapshot.anchor_before(language::Point::new(1, 3)); let prediction_task = ep_store.update(cx, |ep_store, cx| { - ep_store.request_prediction(&project, &buffer, position, Default::default(), cx) + ep_store.request_prediction( + &project, + &buffer, + position, + PredictEditsRequestTrigger::Other, + cx, + ) }); let (request, respond_tx) = requests.predict.next().await.unwrap(); @@ -517,7 +537,13 @@ async fn test_request_events(cx: &mut TestAppContext) { let position = snapshot.anchor_before(language::Point::new(1, 3)); let prediction_task = ep_store.update(cx, |ep_store, cx| { - ep_store.request_prediction(&project, &buffer, position, Default::default(), cx) + ep_store.request_prediction( + &project, + &buffer, + position, + PredictEditsRequestTrigger::Other, + cx, + ) }); let (request, respond_tx) = requests.predict.next().await.unwrap(); @@ -1441,7 +1467,13 @@ async fn test_empty_prediction(cx: &mut TestAppContext) { let position = snapshot.anchor_before(language::Point::new(1, 3)); ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); let (request, respond_tx) = requests.predict.next().await.unwrap(); @@ -1499,7 +1531,13 @@ async fn test_interpolated_empty(cx: &mut TestAppContext) { let position = snapshot.anchor_before(language::Point::new(1, 3)); ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); let (request, respond_tx) = requests.predict.next().await.unwrap(); @@ -1572,7 +1610,13 @@ async fn test_replace_current(cx: &mut TestAppContext) { let position = snapshot.anchor_before(language::Point::new(1, 3)); ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); let (request, respond_tx) = requests.predict.next().await.unwrap(); @@ -1595,7 +1639,13 @@ async fn test_replace_current(cx: &mut TestAppContext) { // a second request is triggered ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); let (request, respond_tx) = requests.predict.next().await.unwrap(); @@ -1656,7 +1706,13 @@ async fn test_current_preferred(cx: &mut TestAppContext) { let position = snapshot.anchor_before(language::Point::new(1, 3)); ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); let (request, respond_tx) = requests.predict.next().await.unwrap(); @@ -1679,7 +1735,13 @@ async fn test_current_preferred(cx: &mut TestAppContext) { // a second request is triggered ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); let (request, respond_tx) = requests.predict.next().await.unwrap(); @@ -1754,13 +1816,25 @@ async fn test_cancel_earlier_pending_requests(cx: &mut TestAppContext) { // start two refresh tasks ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); let (request1, respond_first) = requests.predict.next().await.unwrap(); ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); let (request, respond_second) = requests.predict.next().await.unwrap(); @@ -1848,13 +1922,25 @@ async fn test_cancel_second_on_third_request(cx: &mut TestAppContext) { // start two refresh tasks ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); let (request1, respond_first) = requests.predict.next().await.unwrap(); ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); let (request2, respond_second) = requests.predict.next().await.unwrap(); @@ -1864,7 +1950,13 @@ async fn test_cancel_second_on_third_request(cx: &mut TestAppContext) { ep_store.update(cx, |ep_store, cx| { // start a third request - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); // 2 are pending, so 2nd is cancelled assert_eq!( @@ -1999,7 +2091,13 @@ async fn test_jump_and_edit_throttles_are_independent(cx: &mut TestAppContext) { // First edit request - no prior edit, so not throttled. ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); let (_edit_request, edit_response_tx) = requests.predict.next().await.unwrap(); edit_response_tx.send(empty_response()).unwrap(); @@ -2037,7 +2135,13 @@ async fn test_jump_and_edit_throttles_are_independent(cx: &mut TestAppContext) { // Second edit request - should be throttled by the first edit. ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); assert_no_predict_request_ready(&mut requests.predict); @@ -2096,14 +2200,26 @@ async fn test_cloud_timeout_backs_off_zeta_requests(cx: &mut TestAppContext) { }); ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); let (_request, respond_tx) = requests.predict.next().await.unwrap(); respond_tx.send(request_timeout_response()).unwrap(); cx.run_until_parked(); ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); cx.background_executor .advance_clock(EditPredictionStore::THROTTLE_TIMEOUT); @@ -2117,7 +2233,13 @@ async fn test_cloud_timeout_backs_off_zeta_requests(cx: &mut TestAppContext) { cx.run_until_parked(); ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); let (_request, respond_tx) = requests.predict.next().await.unwrap(); respond_tx.send(empty_response()).unwrap(); @@ -2152,8 +2274,20 @@ async fn test_same_frame_duplicate_requests_deduplicated(cx: &mut TestAppContext // capture the same `proceed_count_at_enqueue`. Only the first task should // pass the deduplication gate; the second should be skipped. ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); // Let both spawned tasks run to completion (including any throttle waits). @@ -3013,7 +3147,13 @@ async fn test_edit_prediction_no_spurious_trailing_newline(cx: &mut TestAppConte let position = snapshot.anchor_before(language::Point::new(0, 5)); ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); let (request, respond_tx) = requests.predict.next().await.unwrap(); @@ -3077,7 +3217,13 @@ async fn test_v3_prediction_strips_cursor_marker_from_edit_text(cx: &mut TestApp let position = snapshot.anchor_before(language::Point::new(0, 5)); ep_store.update(cx, |ep_store, cx| { - ep_store.refresh_prediction_from_buffer(project.clone(), buffer.clone(), position, cx); + ep_store.refresh_prediction_from_buffer( + project.clone(), + buffer.clone(), + position, + EditPredictionRequestTrigger::Other, + cx, + ); }); let (request, respond_tx) = requests.predict.next().await.unwrap(); @@ -3146,7 +3292,13 @@ async fn run_edit_prediction( }); cx.background_executor.run_until_parked(); let prediction_task = ep_store.update(cx, |ep_store, cx| { - ep_store.request_prediction(&project, buffer, cursor, Default::default(), cx) + ep_store.request_prediction( + &project, + buffer, + cursor, + PredictEditsRequestTrigger::Other, + cx, + ) }); prediction_task.await.unwrap().unwrap().prediction.unwrap() } @@ -3326,7 +3478,13 @@ async fn test_unauthenticated_without_custom_url_blocks_prediction_impl(cx: &mut let completion_task = ep_store.update(cx, |ep_store, cx| { ep_store.set_edit_prediction_model(EditPredictionModel::Zeta); - ep_store.request_prediction(&project, &buffer, cursor, Default::default(), cx) + ep_store.request_prediction( + &project, + &buffer, + cursor, + PredictEditsRequestTrigger::Other, + cx, + ) }); assert!(completion_task.await.unwrap().is_none()); diff --git a/crates/edit_prediction/src/zed_edit_prediction_delegate.rs b/crates/edit_prediction/src/zed_edit_prediction_delegate.rs index 072051a8de9c55..c3cb556c7b1b4b 100644 --- a/crates/edit_prediction/src/zed_edit_prediction_delegate.rs +++ b/crates/edit_prediction/src/zed_edit_prediction_delegate.rs @@ -4,7 +4,7 @@ use client::{Client, UserStore}; use cloud_llm_client::EditPredictionRejectReason; use edit_prediction_types::{ DataCollectionState, EditPredictionDelegate, EditPredictionDiscardReason, - EditPredictionIconSet, SuggestionDisplayType, + EditPredictionIconSet, EditPredictionRequestTrigger, SuggestionDisplayType, }; use feature_flags::FeatureFlagAppExt; use fs::Fs; @@ -143,6 +143,7 @@ impl EditPredictionDelegate for ZedEditPredictionDelegate { buffer: Entity, cursor_position: language::Anchor, _debounce: bool, + trigger: EditPredictionRequestTrigger, cx: &mut Context, ) { let store = self.store.read(cx); @@ -163,7 +164,13 @@ impl EditPredictionDelegate for ZedEditPredictionDelegate { } store.refresh_context(&self.project, &buffer, cursor_position, cx); - store.refresh_prediction_from_buffer(self.project.clone(), buffer, cursor_position, cx) + store.refresh_prediction_from_buffer( + self.project.clone(), + buffer, + cursor_position, + trigger, + cx, + ) }); } diff --git a/crates/edit_prediction_types/src/edit_prediction_types.rs b/crates/edit_prediction_types/src/edit_prediction_types.rs index 31caf628544ade..a285e8aa70a72e 100644 --- a/crates/edit_prediction_types/src/edit_prediction_types.rs +++ b/crates/edit_prediction_types/src/edit_prediction_types.rs @@ -8,6 +8,19 @@ pub enum EditPredictionDiscardReason { Rejected, Ignored, } + +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)] +pub enum EditPredictionRequestTrigger { + DiagnosticNavigation, + Explicit, + BufferEdit, + LSPCompletionAccepted, + PredictionAccepted, + PredictionPartiallyAccepted, + #[default] + Other, +} + use icons::IconName; use language::{Anchor, Buffer, OffsetRangeExt}; @@ -185,6 +198,7 @@ pub trait EditPredictionDelegate: 'static + Sized { buffer: Entity, cursor_position: language::Anchor, debounce: bool, + trigger: EditPredictionRequestTrigger, cx: &mut Context, ); fn accept(&mut self, cx: &mut Context); @@ -221,6 +235,7 @@ pub trait EditPredictionDelegateHandle { buffer: Entity, cursor_position: language::Anchor, debounce: bool, + trigger: EditPredictionRequestTrigger, cx: &mut App, ); fn did_show(&self, display_type: SuggestionDisplayType, cx: &mut App); @@ -296,10 +311,11 @@ where buffer: Entity, cursor_position: language::Anchor, debounce: bool, + trigger: EditPredictionRequestTrigger, cx: &mut App, ) { self.update(cx, |this, cx| { - this.refresh(buffer, cursor_position, debounce, cx) + this.refresh(buffer, cursor_position, debounce, trigger, cx) }) } diff --git a/crates/editor/src/completions.rs b/crates/editor/src/completions.rs index 0614f86a76a1ec..6ccb819f0803db 100644 --- a/crates/editor/src/completions.rs +++ b/crates/editor/src/completions.rs @@ -910,7 +910,13 @@ impl Editor { }); } linked_edits.apply(cx); - editor.refresh_edit_prediction(true, false, window, cx); + editor.refresh_edit_prediction( + true, + false, + EditPredictionRequestTrigger::LSPCompletionAccepted, + window, + cx, + ); }); self.invalidate_autoclose_regions( &self.selections.disjoint_anchors_arc(), diff --git a/crates/editor/src/diagnostics.rs b/crates/editor/src/diagnostics.rs index cdbb341a41cd85..37a0f209d709cc 100644 --- a/crates/editor/src/diagnostics.rs +++ b/crates/editor/src/diagnostics.rs @@ -191,7 +191,13 @@ impl Editor { s.select_ranges(vec![diagnostic.range.start..diagnostic.range.start]) }); self.activate_diagnostics(buffer_id, diagnostic, window, cx); - self.refresh_edit_prediction(false, true, window, cx); + self.refresh_edit_prediction( + false, + true, + EditPredictionRequestTrigger::DiagnosticNavigation, + window, + cx, + ); } pub fn go_to_diagnostic_in_direction( diff --git a/crates/editor/src/edit_prediction.rs b/crates/editor/src/edit_prediction.rs index bdaef3430eda18..7b2ee9c2746ef8 100644 --- a/crates/editor/src/edit_prediction.rs +++ b/crates/editor/src/edit_prediction.rs @@ -166,7 +166,13 @@ impl Editor { provider: Arc::new(provider), }); self.update_edit_prediction_settings(cx); - self.refresh_edit_prediction(false, false, window, cx); + self.refresh_edit_prediction( + false, + false, + EditPredictionRequestTrigger::Other, + window, + cx, + ); } pub fn set_edit_predictions_hidden_for_vim_mode( @@ -180,7 +186,13 @@ impl Editor { if hidden { self.update_visible_edit_prediction(window, cx); } else { - self.refresh_edit_prediction(true, false, window, cx); + self.refresh_edit_prediction( + true, + false, + EditPredictionRequestTrigger::Other, + window, + cx, + ); } } } @@ -211,7 +223,13 @@ impl Editor { if let Some(false) = show_edit_predictions { self.discard_edit_prediction(EditPredictionDiscardReason::Ignored, cx); } else { - self.refresh_edit_prediction(false, true, window, cx); + self.refresh_edit_prediction( + false, + true, + EditPredictionRequestTrigger::Explicit, + window, + cx, + ); } } @@ -219,6 +237,7 @@ impl Editor { &mut self, debounce: bool, user_requested: bool, + trigger: EditPredictionRequestTrigger, window: &mut Window, cx: &mut Context, ) -> Option<()> { @@ -251,8 +270,13 @@ impl Editor { return None; } - self.edit_prediction_provider()? - .refresh(buffer, cursor_buffer_position, debounce, cx); + self.edit_prediction_provider()?.refresh( + buffer, + cursor_buffer_position, + debounce, + trigger, + cx, + ); Some(()) } @@ -311,7 +335,13 @@ impl Editor { cx: &mut Context, ) { if !self.has_active_edit_prediction() { - self.refresh_edit_prediction(false, true, window, cx); + self.refresh_edit_prediction( + false, + true, + EditPredictionRequestTrigger::Explicit, + window, + cx, + ); return; } @@ -457,7 +487,13 @@ impl Editor { self.update_visible_edit_prediction(window, cx); if self.active_edit_prediction.is_none() { - self.refresh_edit_prediction(true, true, window, cx); + self.refresh_edit_prediction( + true, + true, + EditPredictionRequestTrigger::PredictionAccepted, + window, + cx, + ); } cx.notify(); } @@ -510,7 +546,13 @@ impl Editor { }); self.replace_selections(&text_to_insert, None, window, cx, false); - self.refresh_edit_prediction(true, true, window, cx); + self.refresh_edit_prediction( + true, + true, + EditPredictionRequestTrigger::PredictionPartiallyAccepted, + window, + cx, + ); cx.notify(); } else { self.accept_partial_edit_prediction( diff --git a/crates/editor/src/edit_prediction_tests.rs b/crates/editor/src/edit_prediction_tests.rs index 684fea4d54baf0..80849c4b79d99e 100644 --- a/crates/editor/src/edit_prediction_tests.rs +++ b/crates/editor/src/edit_prediction_tests.rs @@ -1,5 +1,6 @@ use edit_prediction_types::{ - EditPredictionDelegate, EditPredictionIconSet, PredictedCursorPosition, + EditPredictionDelegate, EditPredictionIconSet, EditPredictionRequestTrigger, + PredictedCursorPosition, }; use futures::StreamExt; use gpui::{ @@ -532,7 +533,13 @@ async fn test_edit_prediction_refresh_suppressed_while_following(cx: &mut gpui:: propose_edits(&provider, vec![(8..8, "42")], &mut cx); cx.update_editor(|editor, window, cx| { - editor.refresh_edit_prediction(false, false, window, cx); + editor.refresh_edit_prediction( + false, + false, + EditPredictionRequestTrigger::Other, + window, + cx, + ); editor.update_visible_edit_prediction(window, cx); }); @@ -548,7 +555,13 @@ async fn test_edit_prediction_refresh_suppressed_while_following(cx: &mut gpui:: cx.update_editor(|editor, window, cx| { editor.leader_id = Some(CollaboratorId::PeerId(PeerId::default())); - editor.refresh_edit_prediction(false, false, window, cx); + editor.refresh_edit_prediction( + false, + false, + EditPredictionRequestTrigger::Other, + window, + cx, + ); }); assert_eq!( @@ -563,7 +576,13 @@ async fn test_edit_prediction_refresh_suppressed_while_following(cx: &mut gpui:: cx.update_editor(|editor, window, cx| { editor.leader_id = None; - editor.refresh_edit_prediction(false, false, window, cx); + editor.refresh_edit_prediction( + false, + false, + EditPredictionRequestTrigger::Other, + window, + cx, + ); }); assert_eq!( @@ -1763,6 +1782,7 @@ impl EditPredictionDelegate for FakeEditPredictionDelegate { _buffer: gpui::Entity, _cursor_position: language::Anchor, _debounce: bool, + _trigger: edit_prediction_types::EditPredictionRequestTrigger, _cx: &mut gpui::Context, ) { self.refresh_count.fetch_add(1, atomic::Ordering::SeqCst); @@ -1841,6 +1861,7 @@ impl EditPredictionDelegate for FakeNonZedEditPredictionDelegate { _buffer: gpui::Entity, _cursor_position: language::Anchor, _debounce: bool, + _trigger: edit_prediction_types::EditPredictionRequestTrigger, _cx: &mut gpui::Context, ) { } diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 4104cabe814f7c..1931146eb170a9 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -93,6 +93,7 @@ pub(crate) use edit_prediction::{ EditPredictionKeybindAction, EditPredictionKeybindSurface, edit_prediction_edit_text, }; pub use edit_prediction_types::Direction; +pub use edit_prediction_types::EditPredictionRequestTrigger; pub use editor_settings::{ CompletionDetailAlignment, CompletionMenuItemKind, CurrentLineHighlight, DiffViewStyle, DocumentColorsRenderMode, EditorSettings, EditorSettingsScrollbarProxy, ScrollBeyondLastLine, @@ -4807,7 +4808,13 @@ impl Editor { this.change_selections(Default::default(), window, cx, |s| s.select(selections)); this.insert("", window, cx); linked_edits.apply_with_left_expansion(cx); - this.refresh_edit_prediction(true, false, window, cx); + this.refresh_edit_prediction( + true, + false, + EditPredictionRequestTrigger::BufferEdit, + window, + cx, + ); refresh_linked_ranges(this, window, cx); }); } @@ -4830,7 +4837,13 @@ impl Editor { let linked_edits = this.linked_edits_for_selections(Arc::from(""), cx); this.insert("", window, cx); linked_edits.apply(cx); - this.refresh_edit_prediction(true, false, window, cx); + this.refresh_edit_prediction( + true, + false, + EditPredictionRequestTrigger::BufferEdit, + window, + cx, + ); refresh_linked_ranges(this, window, cx); }); } @@ -5015,7 +5028,13 @@ impl Editor { self.transact(window, cx, |this, window, cx| { this.buffer.update(cx, |b, cx| b.edit(edits, None, cx)); this.change_selections(Default::default(), window, cx, |s| s.select(selections)); - this.refresh_edit_prediction(true, false, window, cx); + this.refresh_edit_prediction( + true, + false, + EditPredictionRequestTrigger::BufferEdit, + window, + cx, + ); }); } @@ -7326,7 +7345,13 @@ impl Editor { } self.request_autoscroll(Autoscroll::fit(), cx); self.unmark_text(window, cx); - self.refresh_edit_prediction(true, false, window, cx); + self.refresh_edit_prediction( + true, + false, + EditPredictionRequestTrigger::BufferEdit, + window, + cx, + ); cx.emit(EditorEvent::Edited { transaction_id }); cx.emit(EditorEvent::TransactionUndone { transaction_id }); } @@ -7354,7 +7379,13 @@ impl Editor { } self.request_autoscroll(Autoscroll::fit(), cx); self.unmark_text(window, cx); - self.refresh_edit_prediction(true, false, window, cx); + self.refresh_edit_prediction( + true, + false, + EditPredictionRequestTrigger::BufferEdit, + window, + cx, + ); cx.emit(EditorEvent::Edited { transaction_id }); } } @@ -8472,7 +8503,13 @@ impl Editor { (selection.range(), uuid.to_string()) }); this.edit(edits, cx); - this.refresh_edit_prediction(true, false, window, cx); + this.refresh_edit_prediction( + true, + false, + EditPredictionRequestTrigger::BufferEdit, + window, + cx, + ); }); } @@ -9491,7 +9528,7 @@ impl Editor { } self.refresh_runnables(None, window, cx); self.update_edit_prediction_settings(cx); - self.refresh_edit_prediction(true, false, window, cx); + self.refresh_edit_prediction(true, false, EditPredictionRequestTrigger::Other, window, cx); self.refresh_inline_values(cx); let old_cursor_shape = self.cursor_shape; diff --git a/crates/editor/src/input.rs b/crates/editor/src/input.rs index a894093c45afc4..ca228d6e669434 100644 --- a/crates/editor/src/input.rs +++ b/crates/editor/src/input.rs @@ -523,7 +523,13 @@ impl Editor { } this.trigger_completion_on_input(&text, trigger_in_words, window, cx); refresh_linked_ranges(this, window, cx); - this.refresh_edit_prediction(true, false, window, cx); + this.refresh_edit_prediction( + true, + false, + EditPredictionRequestTrigger::BufferEdit, + window, + cx, + ); jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx); }); } @@ -759,7 +765,13 @@ impl Editor { .collect(); this.change_selections(Default::default(), window, cx, |s| s.select(new_selections)); - this.refresh_edit_prediction(true, false, window, cx); + this.refresh_edit_prediction( + true, + false, + EditPredictionRequestTrigger::BufferEdit, + window, + cx, + ); if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) { task.detach_and_log_err(cx); } diff --git a/crates/vim/src/normal/change.rs b/crates/vim/src/normal/change.rs index 8872f23b7c2f3f..41299c8f5b9b53 100644 --- a/crates/vim/src/normal/change.rs +++ b/crates/vim/src/normal/change.rs @@ -5,7 +5,7 @@ use crate::{ state::Mode, }; use editor::{ - Bias, DisplayPoint, + Bias, DisplayPoint, EditPredictionRequestTrigger, display_map::{DisplaySnapshot, ToDisplayPoint}, movement::TextLayoutDetails, }; @@ -90,7 +90,13 @@ impl Vim { if let Some(kind) = motion_kind { vim.copy_selections_content(editor, kind, window, cx); editor.delete_selections_with_linked_edits(window, cx); - editor.refresh_edit_prediction(true, false, window, cx); + editor.refresh_edit_prediction( + true, + false, + EditPredictionRequestTrigger::BufferEdit, + window, + cx, + ); } }); }); @@ -127,7 +133,13 @@ impl Vim { }; vim.copy_selections_content(editor, kind, window, cx); editor.delete_selections_with_linked_edits(window, cx); - editor.refresh_edit_prediction(true, false, window, cx); + editor.refresh_edit_prediction( + true, + false, + EditPredictionRequestTrigger::BufferEdit, + window, + cx, + ); } }); }); diff --git a/crates/vim/src/normal/delete.rs b/crates/vim/src/normal/delete.rs index 1d2945012b00fa..d025e27b91e259 100644 --- a/crates/vim/src/normal/delete.rs +++ b/crates/vim/src/normal/delete.rs @@ -6,7 +6,7 @@ use crate::{ }; use collections::{HashMap, HashSet}; use editor::{ - Bias, DisplayPoint, + Bias, DisplayPoint, EditPredictionRequestTrigger, display_map::{DisplaySnapshot, ToDisplayPoint}, }; use gpui::{Context, Window}; @@ -84,7 +84,13 @@ impl Vim { selection.collapse_to(cursor, selection.goal) }); }); - editor.refresh_edit_prediction(true, false, window, cx); + editor.refresh_edit_prediction( + true, + false, + EditPredictionRequestTrigger::BufferEdit, + window, + cx, + ); }); }); } @@ -192,7 +198,13 @@ impl Vim { selection.collapse_to(cursor, selection.goal) }); }); - editor.refresh_edit_prediction(true, false, window, cx); + editor.refresh_edit_prediction( + true, + false, + EditPredictionRequestTrigger::BufferEdit, + window, + cx, + ); }); }); }