mirror of
https://github.com/zed-industries/zed.git
synced 2026-06-01 03:14:56 +07:00
edit_prediction: Add Mercury accept/reject tracking (#48306)
### Summary Adds accept/reject tracking for Mercury edit predictions. ### Changes Sends events to https://api-feedback.inceptionlabs.ai/feedback when: Accept — user presses Tab Reject — user presses Escape Ignore — prediction dismissed implicitly (typing, cursor move, etc.) Added `discard_explicit` method to the delegate trait to distinguish explicit vs implicit dismissal. Updated `reject_prediction` and `reject_current_prediction` methods with an `explicit` bool parameter to thread this through to the Mercury feedback logic. Other providers are unaffected—they use the default implementation. Feedback is fire-and-forget in a background thread, only sent for predictions that were shown. ### Data Collected - Request ID (returned from Inception API) - User action (either accept/reject/ignore) - Client Zed version (to track updates made to Zed client which could potentially affect nextedit implementation) Release Notes: - N/A --------- Co-authored-by: Ben Kunkle <ben@zed.dev>
This commit is contained in:
parent
477bb89f10
commit
e39c1906e1
10 changed files with 219 additions and 43 deletions
|
|
@ -1,6 +1,8 @@
|
|||
use anyhow::Result;
|
||||
use edit_prediction::cursor_excerpt;
|
||||
use edit_prediction_types::{EditPrediction, EditPredictionDelegate, EditPredictionIconSet};
|
||||
use edit_prediction_types::{
|
||||
EditPrediction, EditPredictionDelegate, EditPredictionDismissReason, EditPredictionIconSet,
|
||||
};
|
||||
use futures::AsyncReadExt;
|
||||
use gpui::{App, Context, Entity, Task};
|
||||
use http_client::HttpClient;
|
||||
|
|
@ -313,7 +315,7 @@ impl EditPredictionDelegate for CodestralEditPredictionDelegate {
|
|||
self.current_completion = None;
|
||||
}
|
||||
|
||||
fn discard(&mut self, _cx: &mut Context<Self>) {
|
||||
fn discard(&mut self, _reason: EditPredictionDismissReason, _cx: &mut Context<Self>) {
|
||||
log::debug!("Codestral: Completion discarded");
|
||||
self.pending_request = None;
|
||||
self.current_completion = None;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ use crate::{
|
|||
};
|
||||
use anyhow::Result;
|
||||
use edit_prediction_types::{
|
||||
EditPrediction, EditPredictionDelegate, EditPredictionIconSet, interpolate_edits,
|
||||
EditPrediction, EditPredictionDelegate, EditPredictionDismissReason, EditPredictionIconSet,
|
||||
interpolate_edits,
|
||||
};
|
||||
use gpui::{App, Context, Entity, Task};
|
||||
use icons::IconName;
|
||||
|
|
@ -128,7 +129,7 @@ impl EditPredictionDelegate for CopilotEditPredictionDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
fn discard(&mut self, _: &mut Context<Self>) {}
|
||||
fn discard(&mut self, _reason: EditPredictionDismissReason, _: &mut Context<Self>) {}
|
||||
|
||||
fn suggest(
|
||||
&mut self,
|
||||
|
|
|
|||
|
|
@ -307,11 +307,13 @@ impl ProjectState {
|
|||
return;
|
||||
};
|
||||
|
||||
this.update(cx, |this, _cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.reject_prediction(
|
||||
prediction_id,
|
||||
EditPredictionRejectReason::Canceled,
|
||||
false,
|
||||
edit_prediction_types::EditPredictionDismissReason::Ignored,
|
||||
cx,
|
||||
);
|
||||
})
|
||||
.ok();
|
||||
|
|
@ -1214,7 +1216,14 @@ impl EditPredictionStore {
|
|||
EditPredictionModel::Sweep => {
|
||||
sweep_ai::edit_prediction_accepted(self, current_prediction, cx)
|
||||
}
|
||||
EditPredictionModel::Mercury | EditPredictionModel::Ollama => {}
|
||||
EditPredictionModel::Mercury => {
|
||||
mercury::edit_prediction_accepted(
|
||||
current_prediction.prediction.id,
|
||||
self.client.http_client(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
EditPredictionModel::Ollama => {}
|
||||
EditPredictionModel::Zeta1 | EditPredictionModel::Zeta2 { .. } => {
|
||||
zeta2::edit_prediction_accepted(self, current_prediction, cx)
|
||||
}
|
||||
|
|
@ -1284,11 +1293,19 @@ impl EditPredictionStore {
|
|||
&mut self,
|
||||
reason: EditPredictionRejectReason,
|
||||
project: &Entity<Project>,
|
||||
dismiss_reason: edit_prediction_types::EditPredictionDismissReason,
|
||||
cx: &App,
|
||||
) {
|
||||
if let Some(project_state) = self.projects.get_mut(&project.entity_id()) {
|
||||
project_state.pending_predictions.clear();
|
||||
if let Some(prediction) = project_state.current_prediction.take() {
|
||||
self.reject_prediction(prediction.prediction.id, reason, prediction.was_shown);
|
||||
self.reject_prediction(
|
||||
prediction.prediction.id,
|
||||
reason,
|
||||
prediction.was_shown,
|
||||
dismiss_reason,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -1347,25 +1364,32 @@ impl EditPredictionStore {
|
|||
prediction_id: EditPredictionId,
|
||||
reason: EditPredictionRejectReason,
|
||||
was_shown: bool,
|
||||
dismiss_reason: edit_prediction_types::EditPredictionDismissReason,
|
||||
cx: &App,
|
||||
) {
|
||||
match self.edit_prediction_model {
|
||||
EditPredictionModel::Zeta1 | EditPredictionModel::Zeta2 { .. } => {
|
||||
if self.custom_predict_edits_url.is_some() {
|
||||
return;
|
||||
if self.custom_predict_edits_url.is_none() {
|
||||
self.reject_predictions_tx
|
||||
.unbounded_send(EditPredictionRejection {
|
||||
request_id: prediction_id.to_string(),
|
||||
reason,
|
||||
was_shown,
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
}
|
||||
EditPredictionModel::Sweep
|
||||
| EditPredictionModel::Mercury
|
||||
| EditPredictionModel::Ollama => return,
|
||||
EditPredictionModel::Sweep | EditPredictionModel::Ollama => {}
|
||||
EditPredictionModel::Mercury => {
|
||||
mercury::edit_prediction_rejected(
|
||||
prediction_id,
|
||||
was_shown,
|
||||
dismiss_reason,
|
||||
self.client.http_client(),
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.reject_predictions_tx
|
||||
.unbounded_send(EditPredictionRejection {
|
||||
request_id: prediction_id.to_string(),
|
||||
reason,
|
||||
was_shown,
|
||||
})
|
||||
.log_err();
|
||||
}
|
||||
|
||||
fn is_refreshing(&self, project: &Entity<Project>) -> bool {
|
||||
|
|
@ -1614,6 +1638,8 @@ impl EditPredictionStore {
|
|||
this.reject_current_prediction(
|
||||
EditPredictionRejectReason::Replaced,
|
||||
&project,
|
||||
edit_prediction_types::EditPredictionDismissReason::Ignored,
|
||||
cx,
|
||||
);
|
||||
|
||||
Some(new_prediction)
|
||||
|
|
@ -1622,6 +1648,8 @@ impl EditPredictionStore {
|
|||
new_prediction.prediction.id,
|
||||
EditPredictionRejectReason::CurrentPreferred,
|
||||
false,
|
||||
edit_prediction_types::EditPredictionDismissReason::Ignored,
|
||||
cx,
|
||||
);
|
||||
None
|
||||
}
|
||||
|
|
@ -1630,7 +1658,13 @@ impl EditPredictionStore {
|
|||
}
|
||||
}
|
||||
Err(reject_reason) => {
|
||||
this.reject_prediction(prediction_result.id, reject_reason, false);
|
||||
this.reject_prediction(
|
||||
prediction_result.id,
|
||||
reject_reason,
|
||||
false,
|
||||
edit_prediction_types::EditPredictionDismissReason::Ignored,
|
||||
cx,
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,8 +93,13 @@ async fn test_current_state(cx: &mut TestAppContext) {
|
|||
assert_matches!(prediction, BufferEditPrediction::Local { .. });
|
||||
});
|
||||
|
||||
ep_store.update(cx, |ep_store, _cx| {
|
||||
ep_store.reject_current_prediction(EditPredictionRejectReason::Discarded, &project);
|
||||
ep_store.update(cx, |ep_store, cx| {
|
||||
ep_store.reject_current_prediction(
|
||||
EditPredictionRejectReason::Discarded,
|
||||
&project,
|
||||
edit_prediction_types::EditPredictionDismissReason::Ignored,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
// Prediction for diagnostic in another file
|
||||
|
|
@ -1125,16 +1130,20 @@ async fn test_cancel_second_on_third_request(cx: &mut TestAppContext) {
|
|||
async fn test_rejections_flushing(cx: &mut TestAppContext) {
|
||||
let (ep_store, mut requests) = init_test_with_fake_client(cx);
|
||||
|
||||
ep_store.update(cx, |ep_store, _cx| {
|
||||
ep_store.update(cx, |ep_store, cx| {
|
||||
ep_store.reject_prediction(
|
||||
EditPredictionId("test-1".into()),
|
||||
EditPredictionRejectReason::Discarded,
|
||||
false,
|
||||
edit_prediction_types::EditPredictionDismissReason::Ignored,
|
||||
cx,
|
||||
);
|
||||
ep_store.reject_prediction(
|
||||
EditPredictionId("test-2".into()),
|
||||
EditPredictionRejectReason::Canceled,
|
||||
true,
|
||||
edit_prediction_types::EditPredictionDismissReason::Ignored,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -1164,12 +1173,14 @@ async fn test_rejections_flushing(cx: &mut TestAppContext) {
|
|||
);
|
||||
|
||||
// Reaching batch size limit sends without debounce
|
||||
ep_store.update(cx, |ep_store, _cx| {
|
||||
ep_store.update(cx, |ep_store, cx| {
|
||||
for i in 0..70 {
|
||||
ep_store.reject_prediction(
|
||||
EditPredictionId(format!("batch-{}", i).into()),
|
||||
EditPredictionRejectReason::Discarded,
|
||||
false,
|
||||
edit_prediction_types::EditPredictionDismissReason::Ignored,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
@ -1195,11 +1206,13 @@ async fn test_rejections_flushing(cx: &mut TestAppContext) {
|
|||
assert_eq!(reject_request.rejections[19].request_id, "batch-69");
|
||||
|
||||
// Request failure
|
||||
ep_store.update(cx, |ep_store, _cx| {
|
||||
ep_store.update(cx, |ep_store, cx| {
|
||||
ep_store.reject_prediction(
|
||||
EditPredictionId("retry-1".into()),
|
||||
EditPredictionRejectReason::Discarded,
|
||||
false,
|
||||
edit_prediction_types::EditPredictionDismissReason::Ignored,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -1213,11 +1226,13 @@ async fn test_rejections_flushing(cx: &mut TestAppContext) {
|
|||
drop(_respond_tx);
|
||||
|
||||
// Add another rejection
|
||||
ep_store.update(cx, |ep_store, _cx| {
|
||||
ep_store.update(cx, |ep_store, cx| {
|
||||
ep_store.reject_prediction(
|
||||
EditPredictionId("retry-2".into()),
|
||||
EditPredictionRejectReason::Discarded,
|
||||
false,
|
||||
edit_prediction_types::EditPredictionDismissReason::Ignored,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,18 @@ use crate::{
|
|||
prediction::EditPredictionResult, zeta1::compute_edits,
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use edit_prediction_types::EditPredictionDismissReason;
|
||||
use futures::AsyncReadExt as _;
|
||||
use gpui::{
|
||||
App, AppContext as _, Entity, Global, SharedString, Task,
|
||||
http_client::{self, AsyncBody, Method},
|
||||
http_client::{self, AsyncBody, HttpClient, Method},
|
||||
};
|
||||
use language::{OffsetRangeExt as _, ToOffset, ToPoint as _};
|
||||
use language_model::{ApiKeyState, EnvVar, env_var};
|
||||
use release_channel::AppVersion;
|
||||
use serde::Serialize;
|
||||
use std::{mem, ops::Range, path::Path, sync::Arc, time::Instant};
|
||||
|
||||
use zeta_prompt::ZetaPromptInput;
|
||||
|
||||
const MERCURY_API_URL: &str = "https://api.inceptionlabs.ai/v1/edit/completions";
|
||||
|
|
@ -324,3 +328,92 @@ pub fn load_mercury_api_token(cx: &mut App) -> Task<Result<(), language_model::A
|
|||
key_state.load_if_needed(MERCURY_CREDENTIALS_URL, |s| s, cx)
|
||||
})
|
||||
}
|
||||
|
||||
const FEEDBACK_API_URL: &str = "https://api-feedback.inceptionlabs.ai/feedback";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
enum MercuryUserAction {
|
||||
Accept,
|
||||
Reject,
|
||||
Ignore,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct FeedbackRequest {
|
||||
request_id: SharedString,
|
||||
provider_name: &'static str,
|
||||
user_action: MercuryUserAction,
|
||||
provider_version: String,
|
||||
}
|
||||
|
||||
pub(crate) fn edit_prediction_accepted(
|
||||
prediction_id: EditPredictionId,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
cx: &App,
|
||||
) {
|
||||
send_feedback(prediction_id, MercuryUserAction::Accept, http_client, cx);
|
||||
}
|
||||
|
||||
pub(crate) fn edit_prediction_rejected(
|
||||
prediction_id: EditPredictionId,
|
||||
was_shown: bool,
|
||||
dismiss_reason: EditPredictionDismissReason,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
cx: &App,
|
||||
) {
|
||||
if !was_shown {
|
||||
return;
|
||||
}
|
||||
let action = match dismiss_reason {
|
||||
EditPredictionDismissReason::Rejected => MercuryUserAction::Reject,
|
||||
EditPredictionDismissReason::Ignored => MercuryUserAction::Ignore,
|
||||
};
|
||||
send_feedback(prediction_id, action, http_client, cx);
|
||||
}
|
||||
|
||||
fn send_feedback(
|
||||
prediction_id: EditPredictionId,
|
||||
action: MercuryUserAction,
|
||||
http_client: Arc<dyn HttpClient>,
|
||||
cx: &App,
|
||||
) {
|
||||
let request_id = prediction_id.0;
|
||||
let app_version = AppVersion::global(cx);
|
||||
cx.background_spawn(async move {
|
||||
if !request_id.starts_with("cmpl-") {
|
||||
log::warn!(
|
||||
"Mercury feedback: invalid request_id '{}' - must start with 'cmpl-'",
|
||||
request_id
|
||||
);
|
||||
return anyhow::Ok(());
|
||||
}
|
||||
|
||||
let body = FeedbackRequest {
|
||||
request_id,
|
||||
provider_name: "zed",
|
||||
user_action: action,
|
||||
provider_version: app_version.to_string(),
|
||||
};
|
||||
|
||||
let request = http_client::Request::builder()
|
||||
.uri(FEEDBACK_API_URL)
|
||||
.method(Method::POST)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(AsyncBody::from(serde_json::to_vec(&body)?))?;
|
||||
|
||||
let response = http_client.send(request).await?;
|
||||
if !response.status().is_success() {
|
||||
anyhow::bail!("Feedback API returned status: {}", response.status());
|
||||
}
|
||||
|
||||
log::debug!(
|
||||
"Mercury feedback sent: request_id={}, action={:?}",
|
||||
body.request_id,
|
||||
body.user_action
|
||||
);
|
||||
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ use std::{cmp, sync::Arc};
|
|||
use client::{Client, UserStore};
|
||||
use cloud_llm_client::EditPredictionRejectReason;
|
||||
use edit_prediction_types::{
|
||||
DataCollectionState, EditPredictionDelegate, EditPredictionIconSet, SuggestionDisplayType,
|
||||
DataCollectionState, EditPredictionDelegate, EditPredictionDismissReason,
|
||||
EditPredictionIconSet, SuggestionDisplayType,
|
||||
};
|
||||
use gpui::{App, Entity, prelude::*};
|
||||
use language::{Buffer, ToPoint as _};
|
||||
|
|
@ -167,9 +168,14 @@ impl EditPredictionDelegate for ZedEditPredictionDelegate {
|
|||
});
|
||||
}
|
||||
|
||||
fn discard(&mut self, cx: &mut Context<Self>) {
|
||||
self.store.update(cx, |store, _cx| {
|
||||
store.reject_current_prediction(EditPredictionRejectReason::Discarded, &self.project);
|
||||
fn discard(&mut self, reason: EditPredictionDismissReason, cx: &mut Context<Self>) {
|
||||
self.store.update(cx, |store, cx| {
|
||||
store.reject_current_prediction(
|
||||
EditPredictionRejectReason::Discarded,
|
||||
&self.project,
|
||||
reason,
|
||||
cx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -207,6 +213,8 @@ impl EditPredictionDelegate for ZedEditPredictionDelegate {
|
|||
store.reject_current_prediction(
|
||||
EditPredictionRejectReason::InterpolatedEmpty,
|
||||
&self.project,
|
||||
EditPredictionDismissReason::Ignored,
|
||||
cx,
|
||||
);
|
||||
return None;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,12 @@ use std::{ops::Range, sync::Arc};
|
|||
|
||||
use client::EditPredictionUsage;
|
||||
use gpui::{App, Context, Entity, SharedString};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum EditPredictionDismissReason {
|
||||
Rejected,
|
||||
Ignored,
|
||||
}
|
||||
use icons::IconName;
|
||||
use language::{Anchor, Buffer, OffsetRangeExt};
|
||||
|
||||
|
|
@ -178,7 +184,7 @@ pub trait EditPredictionDelegate: 'static + Sized {
|
|||
cx: &mut Context<Self>,
|
||||
);
|
||||
fn accept(&mut self, cx: &mut Context<Self>);
|
||||
fn discard(&mut self, cx: &mut Context<Self>);
|
||||
fn discard(&mut self, reason: EditPredictionDismissReason, cx: &mut Context<Self>);
|
||||
fn did_show(&mut self, _display_type: SuggestionDisplayType, _cx: &mut Context<Self>) {}
|
||||
fn suggest(
|
||||
&mut self,
|
||||
|
|
@ -214,7 +220,7 @@ pub trait EditPredictionDelegateHandle {
|
|||
);
|
||||
fn did_show(&self, display_type: SuggestionDisplayType, cx: &mut App);
|
||||
fn accept(&self, cx: &mut App);
|
||||
fn discard(&self, cx: &mut App);
|
||||
fn discard(&self, reason: EditPredictionDismissReason, cx: &mut App);
|
||||
fn suggest(
|
||||
&self,
|
||||
buffer: &Entity<Buffer>,
|
||||
|
|
@ -292,8 +298,8 @@ where
|
|||
self.update(cx, |this, cx| this.accept(cx))
|
||||
}
|
||||
|
||||
fn discard(&self, cx: &mut App) {
|
||||
self.update(cx, |this, cx| this.discard(cx))
|
||||
fn discard(&self, reason: EditPredictionDismissReason, cx: &mut App) {
|
||||
self.update(cx, |this, cx| this.discard(reason, cx))
|
||||
}
|
||||
|
||||
fn did_show(&self, display_type: SuggestionDisplayType, cx: &mut App) {
|
||||
|
|
|
|||
|
|
@ -623,7 +623,12 @@ impl EditPredictionDelegate for FakeEditPredictionDelegate {
|
|||
|
||||
fn accept(&mut self, _cx: &mut gpui::Context<Self>) {}
|
||||
|
||||
fn discard(&mut self, _cx: &mut gpui::Context<Self>) {}
|
||||
fn discard(
|
||||
&mut self,
|
||||
_reason: edit_prediction_types::EditPredictionDismissReason,
|
||||
_cx: &mut gpui::Context<Self>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn suggest<'a>(
|
||||
&mut self,
|
||||
|
|
@ -694,7 +699,12 @@ impl EditPredictionDelegate for FakeNonZedEditPredictionDelegate {
|
|||
|
||||
fn accept(&mut self, _cx: &mut gpui::Context<Self>) {}
|
||||
|
||||
fn discard(&mut self, _cx: &mut gpui::Context<Self>) {}
|
||||
fn discard(
|
||||
&mut self,
|
||||
_reason: edit_prediction_types::EditPredictionDismissReason,
|
||||
_cx: &mut gpui::Context<Self>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn suggest<'a>(
|
||||
&mut self,
|
||||
|
|
|
|||
|
|
@ -96,8 +96,8 @@ use convert_case::{Case, Casing};
|
|||
use dap::TelemetrySpawnLocation;
|
||||
use display_map::*;
|
||||
use edit_prediction_types::{
|
||||
EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionGranularity,
|
||||
SuggestionDisplayType,
|
||||
EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionDismissReason,
|
||||
EditPredictionGranularity, SuggestionDisplayType,
|
||||
};
|
||||
use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
|
||||
use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
|
||||
|
|
@ -8086,7 +8086,12 @@ impl Editor {
|
|||
}
|
||||
|
||||
if let Some(provider) = self.edit_prediction_provider() {
|
||||
provider.discard(cx);
|
||||
let reason = if should_report_edit_prediction_event {
|
||||
EditPredictionDismissReason::Rejected
|
||||
} else {
|
||||
EditPredictionDismissReason::Ignored
|
||||
};
|
||||
provider.discard(reason, cx);
|
||||
}
|
||||
|
||||
self.take_active_edit_prediction(cx)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
use crate::{Supermaven, SupermavenCompletionStateId};
|
||||
use anyhow::Result;
|
||||
use edit_prediction_types::{EditPrediction, EditPredictionDelegate, EditPredictionIconSet};
|
||||
use edit_prediction_types::{
|
||||
EditPrediction, EditPredictionDelegate, EditPredictionDismissReason, EditPredictionIconSet,
|
||||
};
|
||||
use futures::StreamExt as _;
|
||||
use gpui::{App, Context, Entity, EntityId, Task};
|
||||
use language::{Anchor, Buffer, BufferSnapshot};
|
||||
|
|
@ -201,7 +203,7 @@ impl EditPredictionDelegate for SupermavenEditPredictionDelegate {
|
|||
reset_completion_cache(self, _cx);
|
||||
}
|
||||
|
||||
fn discard(&mut self, _cx: &mut Context<Self>) {
|
||||
fn discard(&mut self, _reason: EditPredictionDismissReason, _cx: &mut Context<Self>) {
|
||||
reset_completion_cache(self, _cx);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue