edit prediction: Refine the stealth mode (#25599)

Release Notes:

- N/A

---------

Co-authored-by: Agus Zubiaga <agus@zed.dev>
This commit is contained in:
Danilo Leal 2025-02-26 12:50:11 -03:00 committed by GitHub
parent c0b6d86c41
commit bab65011b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 119 additions and 64 deletions

View file

@ -494,13 +494,33 @@ pub enum MenuInlineCompletionsPolicy {
pub enum EditPredictionPreview { pub enum EditPredictionPreview {
/// Modifier is not pressed /// Modifier is not pressed
Inactive, Inactive { released_too_fast: bool },
/// Modifier pressed /// Modifier pressed
Active { Active {
since: Instant,
previous_scroll_position: Option<ScrollAnchor>, previous_scroll_position: Option<ScrollAnchor>,
}, },
} }
impl EditPredictionPreview {
pub fn released_too_fast(&self) -> bool {
match self {
EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
EditPredictionPreview::Active { .. } => false,
}
}
pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
if let EditPredictionPreview::Active {
previous_scroll_position,
..
} = self
{
*previous_scroll_position = scroll_position;
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)] #[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
struct EditorActionId(usize); struct EditorActionId(usize);
@ -1395,7 +1415,9 @@ impl Editor {
edit_prediction_provider: None, edit_prediction_provider: None,
active_inline_completion: None, active_inline_completion: None,
stale_inline_completion_in_menu: None, stale_inline_completion_in_menu: None,
edit_prediction_preview: EditPredictionPreview::Inactive, edit_prediction_preview: EditPredictionPreview::Inactive {
released_too_fast: false,
},
inline_diagnostics_enabled: mode == EditorMode::Full, inline_diagnostics_enabled: mode == EditorMode::Full,
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings), inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
@ -5123,13 +5145,14 @@ impl Editor {
); );
self.clear_row_highlights::<EditPredictionPreview>(); self.clear_row_highlights::<EditPredictionPreview>();
self.edit_prediction_preview = EditPredictionPreview::Active { self.edit_prediction_preview
previous_scroll_position: None, .set_previous_scroll_position(None);
};
} else { } else {
self.edit_prediction_preview = EditPredictionPreview::Active { self.edit_prediction_preview
previous_scroll_position: Some(position_map.snapshot.scroll_anchor), .set_previous_scroll_position(Some(
}; position_map.snapshot.scroll_anchor,
));
self.highlight_rows::<EditPredictionPreview>( self.highlight_rows::<EditPredictionPreview>(
target..target, target..target,
cx.theme().colors().editor_highlighted_line_background, cx.theme().colors().editor_highlighted_line_background,
@ -5389,10 +5412,11 @@ impl Editor {
if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() { if &accept_keystroke.modifiers == modifiers && accept_keystroke.modifiers.modified() {
if matches!( if matches!(
self.edit_prediction_preview, self.edit_prediction_preview,
EditPredictionPreview::Inactive EditPredictionPreview::Inactive { .. }
) { ) {
self.edit_prediction_preview = EditPredictionPreview::Active { self.edit_prediction_preview = EditPredictionPreview::Active {
previous_scroll_position: None, previous_scroll_position: None,
since: Instant::now(),
}; };
self.update_visible_inline_completion(window, cx); self.update_visible_inline_completion(window, cx);
@ -5400,6 +5424,7 @@ impl Editor {
} }
} else if let EditPredictionPreview::Active { } else if let EditPredictionPreview::Active {
previous_scroll_position, previous_scroll_position,
since,
} = self.edit_prediction_preview } = self.edit_prediction_preview
{ {
if let (Some(previous_scroll_position), Some(position_map)) = if let (Some(previous_scroll_position), Some(position_map)) =
@ -5413,7 +5438,9 @@ impl Editor {
); );
} }
self.edit_prediction_preview = EditPredictionPreview::Inactive; self.edit_prediction_preview = EditPredictionPreview::Inactive {
released_too_fast: since.elapsed() < Duration::from_millis(200),
};
self.clear_row_highlights::<EditPredictionPreview>(); self.clear_row_highlights::<EditPredictionPreview>();
self.update_visible_inline_completion(window, cx); self.update_visible_inline_completion(window, cx);
cx.notify(); cx.notify();
@ -5983,24 +6010,6 @@ impl Editor {
const POLE_WIDTH: Pixels = px(2.); const POLE_WIDTH: Pixels = px(2.);
let mut element = v_flex()
.items_end()
.child(
self.render_edit_prediction_line_popover("Jump", None, window, cx)?
.rounded_br(px(0.))
.rounded_tr(px(0.))
.border_r_2(),
)
.child(
div()
.w(POLE_WIDTH)
.bg(Editor::edit_prediction_callout_popover_border_color(cx))
.h(line_height),
)
.into_any();
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
let line_layout = let line_layout =
line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?; line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
let target_column = target_display_point.column() as usize; let target_column = target_display_point.column() as usize;
@ -6009,8 +6018,41 @@ impl Editor {
let target_y = let target_y =
(target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y; (target_display_point.row().as_f32() * line_height) - scroll_pixel_position.y;
let flag_on_right = target_x < text_bounds.size.width / 2.;
let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
border_color.l += 0.001;
let mut element = v_flex()
.items_end()
.when(flag_on_right, |el| el.items_start())
.child(if flag_on_right {
self.render_edit_prediction_line_popover("Jump", None, window, cx)?
.rounded_bl(px(0.))
.rounded_tl(px(0.))
.border_l_2()
.border_color(border_color)
} else {
self.render_edit_prediction_line_popover("Jump", None, window, cx)?
.rounded_br(px(0.))
.rounded_tr(px(0.))
.border_r_2()
.border_color(border_color)
})
.child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
.into_any();
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
let mut origin = scrolled_content_origin + point(target_x, target_y) let mut origin = scrolled_content_origin + point(target_x, target_y)
- point(size.width - POLE_WIDTH, size.height - line_height); - point(
if flag_on_right {
POLE_WIDTH
} else {
size.width - POLE_WIDTH
},
size.height - line_height,
);
origin.x = origin.x.max(content_origin.x); origin.x = origin.x.max(content_origin.x);
@ -6499,46 +6541,66 @@ impl Editor {
} }
let completion = match &self.active_inline_completion { let completion = match &self.active_inline_completion {
Some(completion) => match &completion.completion { Some(prediction) => {
InlineCompletion::Move { if !self.has_visible_completions_menu() {
target, snapshot, .. const RADIUS: Pixels = px(6.);
} if !self.has_visible_completions_menu() => { const BORDER_WIDTH: Pixels = px(1.);
use text::ToPoint as _;
return Some( return Some(
h_flex() h_flex()
.px_2()
.py_1()
.gap_2()
.elevation_2(cx) .elevation_2(cx)
.border(BORDER_WIDTH)
.border_color(cx.theme().colors().border) .border_color(cx.theme().colors().border)
.rounded(px(6.)) .rounded(RADIUS)
.rounded_tl(px(0.)) .rounded_tl(px(0.))
.overflow_hidden()
.child(div().px_1p5().child(match &prediction.completion {
InlineCompletion::Move { target, snapshot } => {
use text::ToPoint as _;
if target.text_anchor.to_point(&snapshot).row > cursor_point.row
{
Icon::new(IconName::ZedPredictDown)
} else {
Icon::new(IconName::ZedPredictUp)
}
}
InlineCompletion::Edit { .. } => Icon::new(IconName::ZedPredict),
}))
.child( .child(
if target.text_anchor.to_point(&snapshot).row > cursor_point.row { h_flex()
Icon::new(IconName::ZedPredictDown) .gap_1()
} else { .py_1()
Icon::new(IconName::ZedPredictUp) .px_2()
}, .rounded_r(RADIUS - BORDER_WIDTH)
.border_l_1()
.border_color(cx.theme().colors().border)
.bg(Self::edit_prediction_line_popover_bg_color(cx))
.when(self.edit_prediction_preview.released_too_fast(), |el| {
el.child(
Label::new("Hold")
.size(LabelSize::Small)
.line_height_style(LineHeightStyle::UiLabel),
)
})
.child(h_flex().children(ui::render_modifiers(
&accept_keystroke?.modifiers,
PlatformStyle::platform(),
Some(Color::Default),
Some(IconSize::XSmall.rems().into()),
false,
))),
) )
.child(Label::new("Hold").size(LabelSize::Small))
.child(h_flex().children(ui::render_modifiers(
&accept_keystroke?.modifiers,
PlatformStyle::platform(),
Some(Color::Default),
Some(IconSize::Small.rems().into()),
false,
)))
.into_any(), .into_any(),
); );
} }
_ => self.render_edit_prediction_cursor_popover_preview(
completion, self.render_edit_prediction_cursor_popover_preview(
prediction,
cursor_point, cursor_point,
style, style,
cx, cx,
)?, )?
}, }
None if is_refreshing => match &self.stale_inline_completion_in_menu { None if is_refreshing => match &self.stale_inline_completion_in_menu {
Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview( Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(

View file

@ -3,7 +3,6 @@ pub(crate) mod autoscroll;
pub(crate) mod scroll_amount; pub(crate) mod scroll_amount;
use crate::editor_settings::{ScrollBeyondLastLine, ScrollbarAxes}; use crate::editor_settings::{ScrollBeyondLastLine, ScrollbarAxes};
use crate::EditPredictionPreview;
use crate::{ use crate::{
display_map::{DisplaySnapshot, ToDisplayPoint}, display_map::{DisplaySnapshot, ToDisplayPoint},
hover_popover::hide_hover, hover_popover::hide_hover,
@ -496,14 +495,8 @@ impl Editor {
hide_hover(self, cx); hide_hover(self, cx);
let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1); let workspace_id = self.workspace.as_ref().and_then(|workspace| workspace.1);
if let EditPredictionPreview::Active { self.edit_prediction_preview
previous_scroll_position, .set_previous_scroll_position(None);
} = &mut self.edit_prediction_preview
{
if !autoscroll {
previous_scroll_position.take();
}
}
self.scroll_manager.set_scroll_position( self.scroll_manager.set_scroll_position(
scroll_position, scroll_position,