ep: Show empty predictions in rate predictions modal (#58829)

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 ...
This commit is contained in:
Ben Kunkle authored and GitHub committed 2026-06-08 13:08:35 +00:00
1 parent 53367edf64
commit 1575dbe27c
8 files changed
+263 -166

No files matched your search

+36 -7
View File
@@ -2386,6 +2386,7 @@ impl EditPredictionStore {
EditPredictionResult {
id: prediction_result.id,
prediction: Err(EditPredictionRejectReason::CurrentPreferred),
display_prediction: None,
model_version: prediction_result.model_version,
e2e_latency: prediction_result.e2e_latency,
}
@@ -2575,14 +2576,18 @@ impl EditPredictionStore {
let new_current_prediction = if !is_cancelled
&& let Some((prediction_result, requested_by)) = new_prediction_result
{
match prediction_result.prediction {
Ok(prediction) => {
match prediction_result {
EditPredictionResult {
prediction: Ok(prediction),
e2e_latency,
..
} => {
let new_prediction = CurrentEditPrediction {
requested_by,
prediction,
was_shown: false,
shown_with: None,
e2e_latency: prediction_result.e2e_latency,
e2e_latency,
};
if let Some(current_prediction) =
@@ -2612,15 +2617,39 @@ impl EditPredictionStore {
Some(new_prediction)
}
}
Err(reject_reason) => {
EditPredictionResult {
id,
prediction: Err(reject_reason),
display_prediction,
model_version,
e2e_latency,
} => {
let should_show_rejected_prediction = matches!(
reject_reason,
EditPredictionRejectReason::Empty
| EditPredictionRejectReason::InterpolatedEmpty
);
this.reject_prediction(
prediction_result.id,
id,
reject_reason,
false,
prediction_result.model_version,
Some(prediction_result.e2e_latency),
model_version,
Some(e2e_latency),
cx,
);
if should_show_rejected_prediction
&& let Some(display_prediction) = display_prediction
{
this.shown_predictions.push_front(display_prediction);
if this.shown_predictions.len() > 50
&& let Some(completion) = this.shown_predictions.pop_back()
{
this.rated_predictions.remove(&completion.id);
}
}
None
}
}
@@ -1471,7 +1471,7 @@ async fn test_empty_prediction(cx: &mut TestAppContext) {
project.clone(),
buffer.clone(),
position,
EditPredictionRequestTrigger::Other,
EditPredictionRequestTrigger::Explicit,
cx,
);
});
@@ -1490,6 +1490,15 @@ async fn test_empty_prediction(cx: &mut TestAppContext) {
.prediction_at(&buffer, None, &project, cx)
.is_none()
);
let shown_predictions = ep_store.shown_predictions().collect::<Vec<_>>();
assert_eq!(shown_predictions.len(), 1);
assert_eq!(shown_predictions[0].id.to_string(), id);
assert!(shown_predictions[0].edits.is_empty());
assert!(shown_predictions[0].editable_range.is_some());
assert!(matches!(
shown_predictions[0].trigger,
PredictEditsRequestTrigger::Explicit
));
});
// prediction is reported as rejected
@@ -1559,6 +1568,11 @@ async fn test_interpolated_empty(cx: &mut TestAppContext) {
.prediction_at(&buffer, None, &project, cx)
.is_none()
);
let shown_predictions = ep_store.shown_predictions().collect::<Vec<_>>();
assert_eq!(shown_predictions.len(), 1);
assert_eq!(shown_predictions[0].id.to_string(), id);
assert!(shown_predictions[0].edits.is_empty());
assert!(shown_predictions[0].editable_range.is_some());
});
// prediction is reported as rejected
@@ -1774,6 +1788,11 @@ async fn test_current_preferred(cx: &mut TestAppContext) {
.0,
first_id
);
let shown_prediction_ids = ep_store
.shown_predictions()
.map(|prediction| prediction.id.to_string())
.collect::<Vec<_>>();
assert!(shown_prediction_ids.is_empty());
});
// second is reported as rejected
@@ -2962,6 +2981,7 @@ async fn test_edit_prediction_basic_interpolation(cx: &mut TestAppContext) {
repo_url: None,
},
model_version: None,
trigger: PredictEditsRequestTrigger::Other,
};
cx.update(|cx| {
+2
View File
@@ -29,6 +29,7 @@ pub fn request_prediction(
snapshot,
position,
events,
trigger,
..
}: EditPredictionModelInput,
prompt_format: EditPredictionPromptFormat,
@@ -155,6 +156,7 @@ pub fn request_prediction(
Some(output.editable_range),
output.inputs,
None,
trigger,
cx.background_executor().now() - request_start,
cx,
)
+2
View File
@@ -50,6 +50,7 @@ impl Mercury {
events,
related_files,
debug_tx,
trigger,
..
}: EditPredictionModelInput,
credentials_provider: Arc<dyn CredentialsProvider>,
@@ -255,6 +256,7 @@ impl Mercury {
Some(editable_range),
inputs,
None,
trigger,
cx.background_executor().now() - request_start,
cx,
)
+46 -12
View File
@@ -1,6 +1,6 @@
use std::{ops::Range, sync::Arc};
use cloud_llm_client::EditPredictionRejectReason;
use cloud_llm_client::{EditPredictionRejectReason, PredictEditsRequestTrigger};
use edit_prediction_types::{PredictedCursorPosition, interpolate_edits};
use gpui::{AsyncApp, Entity, SharedString};
use language::{Anchor, Buffer, BufferSnapshot, EditPreview, TextBufferSnapshot};
@@ -25,6 +25,7 @@ impl std::fmt::Display for EditPredictionId {
pub struct EditPredictionResult {
pub id: EditPredictionId,
pub prediction: Result<EditPrediction, EditPredictionRejectReason>,
pub display_prediction: Option<EditPrediction>,
pub model_version: Option<String>,
pub e2e_latency: std::time::Duration,
}
@@ -39,36 +40,65 @@ impl EditPredictionResult {
editable_range: Option<Range<Anchor>>,
inputs: ZetaPromptInput,
model_version: Option<String>,
trigger: PredictEditsRequestTrigger,
e2e_latency: std::time::Duration,
cx: &mut AsyncApp,
) -> Self {
if edits.is_empty() {
let empty_edits = Arc::new([]);
return Self {
id,
id: id.clone(),
prediction: Err(EditPredictionRejectReason::Empty),
display_prediction: Some(EditPrediction {
id,
edits: empty_edits,
cursor_position: None,
editable_range,
snapshot: edited_buffer_snapshot.clone(),
edit_preview: EditPreview::unchanged(edited_buffer_snapshot),
buffer: edited_buffer.clone(),
inputs,
model_version: model_version.clone(),
trigger,
}),
model_version,
e2e_latency,
};
}
let Some((edits, snapshot, edit_preview_task)) =
edited_buffer.read_with(cx, |buffer, cx| {
let new_snapshot = buffer.snapshot();
let edits: Arc<[_]> =
interpolate_edits(&edited_buffer_snapshot, &new_snapshot, &edits)?.into();
let (edits, snapshot) = edited_buffer.read_with(cx, |buffer, _cx| {
let new_snapshot = buffer.snapshot();
let edits: Option<Arc<[(Range<Anchor>, Arc<str>)]>> =
interpolate_edits(&edited_buffer_snapshot, &new_snapshot, &edits).map(Arc::from);
Some((edits.clone(), new_snapshot, buffer.preview_edits(edits, cx)))
})
else {
(edits, new_snapshot)
});
let Some(edits) = edits else {
let empty_edits: Arc<[(Range<Anchor>, Arc<str>)]> = Vec::new().into();
return Self {
id,
id: id.clone(),
prediction: Err(EditPredictionRejectReason::InterpolatedEmpty),
display_prediction: Some(EditPrediction {
id,
edits: empty_edits,
cursor_position: None,
editable_range,
snapshot: edited_buffer_snapshot.clone(),
edit_preview: EditPreview::unchanged(edited_buffer_snapshot),
inputs,
buffer: edited_buffer.clone(),
model_version: model_version.clone(),
trigger,
}),
model_version,
e2e_latency,
};
};
let edit_preview = edit_preview_task.await;
let edit_preview = edited_buffer
.read_with(cx, |buffer, cx| buffer.preview_edits(edits.clone(), cx))
.await;
Self {
id: id.clone(),
@@ -82,7 +112,9 @@ impl EditPredictionResult {
inputs,
buffer: edited_buffer.clone(),
model_version: model_version.clone(),
trigger,
}),
display_prediction: None,
model_version,
e2e_latency,
}
@@ -100,6 +132,7 @@ pub struct EditPrediction {
pub buffer: Entity<Buffer>,
pub inputs: zeta_prompt::ZetaPromptInput,
pub model_version: Option<String>,
pub trigger: PredictEditsRequestTrigger,
}
impl EditPrediction {
@@ -153,6 +186,7 @@ mod tests {
buffer: buffer.clone(),
edit_preview,
model_version: None,
trigger: PredictEditsRequestTrigger::Other,
inputs: ZetaPromptInput {
events: vec![],
related_files: Some(vec![]),
+25 -2
View File
@@ -27,7 +27,8 @@ use zeta_prompt::{ParsedOutput, ZetaPromptInput};
use std::{ops::Range, path::Path, sync::Arc};
use zeta_prompt::{
ZetaFormat, format_zeta_prompt, get_prefill, parse_zeta2_model_output, stop_tokens_for_format,
ZetaFormat, excerpt_range_for_format, format_zeta_prompt, get_prefill,
parse_zeta2_model_output, stop_tokens_for_format,
zeta1::{self, EDITABLE_REGION_END_MARKER},
};
@@ -309,7 +310,27 @@ pub fn request_prediction_with_zeta(
cursor_offset_in_new_editable_region: cursor_offset_in_output,
}) = output
else {
return Ok((Some((request_id, None, model_version)), None));
let editable_range_in_excerpt =
excerpt_range_for_format(zeta_format, &prompt_input.excerpt_ranges).0;
let editable_range_in_buffer = editable_range_in_excerpt.start
+ full_context_offset_range.start
..editable_range_in_excerpt.end + full_context_offset_range.start;
return Ok((
Some((
request_id,
Some(Prediction {
prompt_input,
buffer,
snapshot: snapshot.clone(),
edits: Vec::new(),
cursor_position: None,
editable_range_in_buffer,
}),
model_version,
)),
usage,
));
};
let editable_range_in_buffer = editable_range_in_excerpt.start
@@ -381,6 +402,7 @@ pub fn request_prediction_with_zeta(
return Ok(Some(EditPredictionResult {
id,
prediction: Err(EditPredictionRejectReason::Empty),
display_prediction: None,
model_version,
e2e_latency: request_duration,
}));
@@ -395,6 +417,7 @@ pub fn request_prediction_with_zeta(
Some(edited_buffer_snapshot.anchor_range_inside(editable_range_in_buffer.clone())),
inputs,
model_version,
trigger,
request_duration,
cx,
)
@@ -1,4 +1,5 @@
use buffer_diff::BufferDiff;
use cloud_llm_client::PredictEditsRequestTrigger;
use edit_prediction::{EditPrediction, EditPredictionRating, EditPredictionStore};
use editor::{Editor, Inlay, MultiBuffer};
use feature_flags::{FeatureFlag, PresenceFlag, register_feature_flag};
@@ -7,7 +8,7 @@ use gpui::{
Length, StyleRefinement, TextStyleRefinement, Window, actions, prelude::*,
};
use language::{
Anchor, Bias, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry, Point, ToOffset, ToPoint,
Bias, Buffer, BufferSnapshot, CodeLabel, LanguageRegistry, Point, ToOffset, ToPoint,
language_settings::{self, InlayHintKind},
};
use markdown::{Markdown, MarkdownStyle};
@@ -67,10 +68,8 @@ struct ActivePrediction {
prediction: EditPrediction,
feedback_editor: Entity<Editor>,
expected_buffer: Entity<Buffer>,
expected_editable_range: Option<Range<Anchor>>,
expected_editor: Entity<Editor>,
expected_diff_editor: Entity<Editor>,
expected_patch_preview: bool,
_expected_buffer_subscription: gpui::Subscription,
formatted_inputs: Entity<Markdown>,
}
@@ -304,56 +303,36 @@ impl RatePredictionsModal {
self.select_completion(completion, true, window, cx);
}
fn update_diff_editor(
diff_editor: &Entity<Editor>,
new_buffer: Entity<Buffer>,
fn update_buffer_diff(
diff: &Entity<BufferDiff>,
new_buffer_snapshot: BufferSnapshot,
old_buffer_snapshot: BufferSnapshot,
visible_range: Range<Point>,
cx: &mut Context<Self>,
cx: &mut App,
) {
diff_editor.update(cx, |editor, cx| {
let new_buffer_snapshot = new_buffer.read(cx).snapshot();
let new_buffer_id = new_buffer_snapshot.remote_id();
let language = new_buffer_snapshot.language().cloned();
let diff = cx.new(|cx| BufferDiff::new(&new_buffer_snapshot.text, cx));
diff.update(cx, |diff, cx| {
let update = diff.update_diff(
new_buffer_snapshot.text.clone(),
Some(old_buffer_snapshot.text().into()),
Some(true),
language,
cx,
);
cx.spawn(async move |diff, cx| {
let update = update.await;
if let Some(task) = diff
.update(cx, |diff, cx| {
diff.set_snapshot(update, &new_buffer_snapshot.text, cx)
})
.ok()
{
task.await;
}
})
.detach();
});
editor.disable_header_for_buffer(new_buffer_id, cx);
editor.buffer().update(cx, |multibuffer, cx| {
multibuffer.clear(cx);
multibuffer.set_excerpts_for_buffer(new_buffer, [visible_range], 0, cx);
multibuffer.add_diff(diff, cx);
});
let language = new_buffer_snapshot.language().cloned();
diff.update(cx, |diff, cx| {
let update = diff.update_diff(
new_buffer_snapshot.text.clone(),
Some(old_buffer_snapshot.text().into()),
Some(true),
language,
cx,
);
cx.spawn(async move |diff, cx| {
let update = update.await;
if let Some(task) = diff
.update(cx, |diff, cx| {
diff.set_snapshot(update, &new_buffer_snapshot.text, cx)
})
.ok()
{
task.await;
}
})
.detach();
});
}
fn editable_range_for_prediction(prediction: &EditPrediction) -> Option<Range<Anchor>> {
prediction
.editable_range
.clone()
.or_else(|| Some(prediction.edits.first()?.0.start..prediction.edits.last()?.0.end))
}
fn insert_editable_region_markers(
editor: &Entity<Editor>,
buffer: &Entity<Buffer>,
@@ -471,23 +450,47 @@ impl RatePredictionsModal {
return;
}
let editable_range = Self::editable_range_for_prediction(&prediction);
let editable_range = prediction.editable_range.clone().or_else(|| {
Some(prediction.edits.first()?.0.start..prediction.edits.last()?.0.end)
});
let predicted_buffer = prediction.edit_preview.build_result_buffer(cx);
let predicted_buffer_snapshot = predicted_buffer.read(cx).snapshot();
let visible_range = prediction
.edit_preview
.compute_visible_range(&prediction.edits)
.or_else(|| {
editable_range.as_ref().map(|range| {
range.start.to_point(&prediction.snapshot)
..range.end.to_point(&prediction.snapshot)
})
})
.unwrap_or(Point::zero()..Point::zero());
let start = Point::new(visible_range.start.row.saturating_sub(5), 0);
let end =
Point::new(visible_range.end.row + 5, 0).min(predicted_buffer_snapshot.max_point());
Self::update_diff_editor(
&self.diff_editor,
predicted_buffer.clone(),
prediction.snapshot.clone(),
start..end,
cx,
);
let visible_range_with_context =
Point::new(visible_range.start.row.saturating_sub(5), 0)
..Point::new(visible_range.end.row.saturating_add(5), 0)
.min(predicted_buffer_snapshot.max_point());
self.diff_editor.update(cx, |editor, cx| {
let predicted_buffer_id = predicted_buffer_snapshot.remote_id();
let diff = cx.new(|cx| BufferDiff::new(&predicted_buffer_snapshot.text, cx));
Self::update_buffer_diff(
&diff,
predicted_buffer_snapshot.clone(),
prediction.snapshot.clone(),
cx,
);
editor.disable_header_for_buffer(predicted_buffer_id, cx);
editor.buffer().update(cx, |multibuffer, cx| {
multibuffer.clear(cx);
multibuffer.set_excerpts_for_buffer(
predicted_buffer.clone(),
[visible_range_with_context],
0,
cx,
);
multibuffer.add_diff(diff, cx);
});
});
if let Some(editable_range) = editable_range.as_ref() {
Self::insert_editable_region_markers(
@@ -621,7 +624,14 @@ impl RatePredictionsModal {
range.start.to_point(&expected_buffer_snapshot)
..range.end.to_point(&expected_buffer_snapshot)
})
.unwrap_or_else(|| visible_range.clone());
.unwrap_or(visible_range);
let expected_diff = cx.new(|cx| BufferDiff::new(&expected_buffer_snapshot.text, cx));
Self::update_buffer_diff(
&expected_diff,
expected_buffer_snapshot.clone(),
prediction.snapshot.clone(),
cx,
);
let expected_editor = cx.new(|cx| {
let multibuffer = cx.new(|cx| {
let mut multibuffer = MultiBuffer::new(language::Capability::ReadWrite);
@@ -631,12 +641,14 @@ impl RatePredictionsModal {
0,
cx,
);
multibuffer.add_diff(expected_diff.clone(), cx);
multibuffer
});
let mut editor = Editor::for_multibuffer(multibuffer, None, window, cx);
let expected_buffer_id = expected_buffer.read(cx).remote_id();
editor.disable_header_for_buffer(expected_buffer_id, cx);
editor.disable_inline_diagnostics();
editor.set_expand_all_diff_hunks(cx);
editor.set_show_git_diff_gutter(false, cx);
editor.set_show_code_actions(false, cx);
editor.set_show_runnables(false, cx);
@@ -646,14 +658,6 @@ impl RatePredictionsModal {
editor.set_show_edit_predictions(Some(false), window, cx);
editor
});
let expected_diff_editor = cx.new(|cx| {
let multibuffer = cx.new(|_| MultiBuffer::new(language::Capability::ReadOnly));
let mut editor = Editor::for_multibuffer(multibuffer, None, window, cx);
editor.disable_inline_diagnostics();
editor.set_expand_all_diff_hunks(cx);
editor.set_show_git_diff_gutter(false, cx);
editor
});
if let Some(expected_editable_range) = expected_editable_range.as_ref() {
let expected_buffer_snapshot = expected_buffer.read(cx).snapshot();
Self::insert_editable_region_markers(
@@ -669,6 +673,24 @@ impl RatePredictionsModal {
);
}
let expected_buffer_subscription = cx.subscribe(&expected_buffer, {
let expected_diff = expected_diff.clone();
let original_snapshot = prediction.snapshot.clone();
move |_this, buffer, event, cx| match event {
language::BufferEvent::Edited { .. }
| language::BufferEvent::LanguageChanged(_)
| language::BufferEvent::Reparsed => {
Self::update_buffer_diff(
&expected_diff,
buffer.read(cx).snapshot(),
original_snapshot.clone(),
cx,
);
}
_ => {}
}
});
self.active_prediction = Some(ActivePrediction {
prediction,
feedback_editor: cx.new(|cx| {
@@ -692,10 +714,8 @@ impl RatePredictionsModal {
editor
}),
expected_buffer,
expected_editable_range,
expected_editor,
expected_diff_editor,
expected_patch_preview: false,
_expected_buffer_subscription: expected_buffer_subscription,
formatted_inputs: cx.new(|cx| {
Markdown::new(
formatted_inputs.into(),
@@ -746,57 +766,10 @@ impl RatePredictionsModal {
)
}
fn toggle_expected_patch_preview(&mut self, cx: &mut Context<Self>) {
if let Some(active_prediction) = &mut self.active_prediction {
if active_prediction.expected_patch_preview {
active_prediction.expected_patch_preview = false;
} else {
let expected_buffer_snapshot =
active_prediction.expected_buffer.read(cx).snapshot();
let visible_range = active_prediction
.prediction
.edit_preview
.compute_visible_range(&active_prediction.prediction.edits)
.unwrap_or(Point::zero()..Point::zero());
let start = Point::new(visible_range.start.row.saturating_sub(5), 0);
let end = Point::new(visible_range.end.row + 5, 0)
.min(expected_buffer_snapshot.max_point());
Self::update_diff_editor(
&active_prediction.expected_diff_editor,
active_prediction.expected_buffer.clone(),
active_prediction.prediction.snapshot.clone(),
start..end,
cx,
);
if let Some(expected_editable_range) =
active_prediction.expected_editable_range.as_ref()
{
let expected_buffer_snapshot =
active_prediction.expected_buffer.read(cx).snapshot();
Self::insert_editable_region_markers(
&active_prediction.expected_diff_editor,
&active_prediction.expected_buffer,
expected_editable_range
.start
.to_offset(&expected_buffer_snapshot)
..expected_editable_range
.end
.to_offset(&expected_buffer_snapshot),
cx,
);
}
active_prediction.expected_patch_preview = true;
}
cx.notify();
}
}
fn render_suggested_edits(&self, cx: &mut Context<Self>) -> Option<gpui::Stateful<Div>> {
let bg_color = cx.theme().colors().editor_background;
let border_color = cx.theme().colors().border;
let active_prediction = self.active_prediction.as_ref()?;
let expected_patch_preview = active_prediction.expected_patch_preview;
Some(
v_flex()
@@ -840,22 +813,6 @@ impl RatePredictionsModal {
.gap_2()
.border_b_1()
.border_color(border_color)
.child(
Button::new(
"expected-patch-preview",
if expected_patch_preview {
"Edit"
} else {
"Preview"
},
)
.label_size(LabelSize::Small)
.on_click(cx.listener(
|this, _, _window, cx| {
this.toggle_expected_patch_preview(cx);
},
)),
)
.child(Label::new("Expected Patch").size(LabelSize::Small)),
)
.child(
@@ -866,14 +823,7 @@ impl RatePredictionsModal {
.min_h_0()
.overflow_scroll()
.whitespace_nowrap()
.child(if expected_patch_preview {
active_prediction
.expected_diff_editor
.clone()
.into_any_element()
} else {
active_prediction.expected_editor.clone().into_any_element()
}),
.child(active_prediction.expected_editor.clone()),
),
),
)
@@ -1186,6 +1136,28 @@ impl RatePredictionsModal {
(false, true) => (IconName::File, Color::Muted, "No Edits Produced"),
(false, false) => (IconName::FileDiff, Color::Accent, "Edits Available"),
};
let (trigger_icon, trigger_tooltip) = match completion.trigger {
PredictEditsRequestTrigger::Testing => (IconName::Debug, "Testing"),
PredictEditsRequestTrigger::Diagnostics => {
(IconName::ToolDiagnostics, "Diagnostics")
}
PredictEditsRequestTrigger::DiagnosticNavigation => {
(IconName::ArrowRight, "Diagnostic Navigation")
}
PredictEditsRequestTrigger::Cli => (IconName::Terminal, "CLI"),
PredictEditsRequestTrigger::Explicit => (IconName::Person, "Explicit"),
PredictEditsRequestTrigger::BufferEdit => (IconName::Pencil, "Buffer Edit"),
PredictEditsRequestTrigger::LSPCompletionAccepted => {
(IconName::Code, "LSP Completion Accepted")
}
PredictEditsRequestTrigger::PredictionAccepted => {
(IconName::ZedPredict, "Prediction Accepted")
}
PredictEditsRequestTrigger::PredictionPartiallyAccepted => {
(IconName::CheckDouble, "Prediction Partially Accepted")
}
PredictEditsRequestTrigger::Other => (IconName::CircleHelp, "Other"),
};
let file = completion.buffer.read(cx).file();
let file_name = file
@@ -1205,6 +1177,11 @@ impl RatePredictionsModal {
.id("completion-content")
.gap_3()
.child(Icon::new(icon_name).color(icon_color).size(IconSize::Small))
.child(
Icon::new(trigger_icon)
.color(Color::Muted)
.size(IconSize::XSmall),
)
.child(
v_flex().child(
h_flex()
@@ -1220,7 +1197,9 @@ impl RatePredictionsModal {
),
),
)
.tooltip(Tooltip::text(tooltip_text))
.tooltip(Tooltip::text(format!(
"{tooltip_text} • Trigger: {trigger_tooltip}"
)))
.on_click(cx.listener(move |this, _, window, cx| {
this.select_completion(Some(completion.clone()), true, window, cx);
}))
+8
View File
@@ -756,6 +756,14 @@ pub struct EditPreview {
}
impl EditPreview {
pub fn unchanged(snapshot: &BufferSnapshot) -> Self {
Self {
old_snapshot: snapshot.text.clone(),
applied_edits_snapshot: snapshot.text.clone(),
syntax_snapshot: snapshot.syntax.clone(),
}
}
pub fn as_unified_diff(
&self,
file: Option<&Arc<dyn File>>,