@@ -75,14 +75,14 @@ use code_context_menus::{
};
use git::blame::GitBlame;
use gpui::{
- div, impl_actions, linear_color_stop, linear_gradient, point, prelude::*, pulsating_between,
- px, relative, size, Action, Animation, AnimationExt, AnyElement, App, AsyncWindowContext,
- AvailableSpace, Background, Bounds, ClipboardEntry, ClipboardItem, Context, DispatchPhase,
- ElementId, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent, Focusable,
- FontId, FontWeight, Global, HighlightStyle, Hsla, InteractiveText, KeyContext, Modifiers,
- MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, SharedString, Size,
- Styled, StyledText, Subscription, Task, TextRun, TextStyle, TextStyleRefinement,
- UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
+ div, impl_actions, point, prelude::*, pulsating_between, px, relative, size, Action, Animation,
+ AnimationExt, AnyElement, App, AsyncWindowContext, AvailableSpace, Background, Bounds,
+ ClipboardEntry, ClipboardItem, Context, DispatchPhase, ElementId, Entity, EntityInputHandler,
+ EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global,
+ HighlightStyle, Hsla, InteractiveText, KeyContext, Modifiers, MouseButton, MouseDownEvent,
+ PaintQuad, ParentElement, Pixels, Render, SharedString, Size, Styled, StyledText, Subscription,
+ Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
+ WeakEntity, WeakFocusHandle, Window,
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
@@ -485,7 +485,6 @@ enum InlineCompletion {
},
Move {
target: Anchor,
- range_around_target: Range<text::Anchor>,
snapshot: BufferSnapshot,
},
}
@@ -521,6 +520,296 @@ pub enum MenuInlineCompletionsPolicy {
ByProvider,
}
+// TODO az do we need this?
+#[derive(Clone)]
+pub enum EditPredictionPreview {
+ /// Modifier is not pressed
+ Inactive,
+ /// Modifier pressed, animating to active
+ MovingTo {
+ animation: Range<Instant>,
+ scroll_position_at_start: Option<gpui::Point<f32>>,
+ target_point: DisplayPoint,
+ },
+ Arrived {
+ scroll_position_at_start: Option<gpui::Point<f32>>,
+ scroll_position_at_arrival: Option<gpui::Point<f32>>,
+ target_point: Option<DisplayPoint>,
+ },
+ /// Modifier released, animating from active
+ MovingFrom {
+ animation: Range<Instant>,
+ target_point: DisplayPoint,
+ },
+}
+
+impl EditPredictionPreview {
+ fn start(
+ &mut self,
+ completion: &InlineCompletion,
+ snapshot: &EditorSnapshot,
+ cursor: DisplayPoint,
+ ) -> bool {
+ if matches!(self, Self::MovingTo { .. } | Self::Arrived { .. }) {
+ return false;
+ }
+ (*self, _) = Self::start_now(completion, snapshot, cursor);
+ true
+ }
+
+ fn restart(
+ &mut self,
+ completion: &InlineCompletion,
+ snapshot: &EditorSnapshot,
+ cursor: DisplayPoint,
+ ) -> bool {
+ match self {
+ Self::Inactive => false,
+ Self::MovingTo { target_point, .. }
+ | Self::Arrived {
+ target_point: Some(target_point),
+ ..
+ } => {
+ let (new_preview, new_target_point) = Self::start_now(completion, snapshot, cursor);
+
+ if new_target_point != Some(*target_point) {
+ *self = new_preview;
+ return true;
+ }
+
+ false
+ }
+ Self::Arrived {
+ target_point: None, ..
+ } => {
+ let (new_preview, _) = Self::start_now(completion, snapshot, cursor);
+
+ *self = new_preview;
+ true
+ }
+ Self::MovingFrom { .. } => false,
+ }
+ }
+
+ fn start_now(
+ completion: &InlineCompletion,
+ snapshot: &EditorSnapshot,
+ cursor: DisplayPoint,
+ ) -> (Self, Option<DisplayPoint>) {
+ let now = Instant::now();
+ match completion {
+ InlineCompletion::Edit { .. } => (
+ Self::Arrived {
+ target_point: None,
+ scroll_position_at_start: None,
+ scroll_position_at_arrival: None,
+ },
+ None,
+ ),
+ InlineCompletion::Move { target, .. } => {
+ let target_point = target.to_display_point(&snapshot.display_snapshot);
+ let duration = Self::animation_duration(cursor, target_point);
+
+ (
+ Self::MovingTo {
+ animation: now..now + duration,
+ scroll_position_at_start: Some(snapshot.scroll_position()),
+ target_point,
+ },
+ Some(target_point),
+ )
+ }
+ }
+ }
+
+ fn animation_duration(a: DisplayPoint, b: DisplayPoint) -> Duration {
+ const SPEED: f32 = 8.0;
+
+ let row_diff = b.row().0.abs_diff(a.row().0);
+ let column_diff = b.column().abs_diff(a.column());
+ let distance = ((row_diff.pow(2) + column_diff.pow(2)) as f32).sqrt();
+ Duration::from_millis((distance * SPEED) as u64)
+ }
+
+ fn end(
+ &mut self,
+ cursor: DisplayPoint,
+ scroll_pixel_position: gpui::Point<Pixels>,
+ window: &mut Window,
+ cx: &mut Context<Editor>,
+ ) -> bool {
+ let (scroll_position, target_point) = match self {
+ Self::MovingTo {
+ scroll_position_at_start,
+ target_point,
+ ..
+ }
+ | Self::Arrived {
+ scroll_position_at_start,
+ scroll_position_at_arrival: None,
+ target_point: Some(target_point),
+ ..
+ } => (*scroll_position_at_start, target_point),
+ Self::Arrived {
+ scroll_position_at_start,
+ scroll_position_at_arrival: Some(scroll_at_arrival),
+ target_point: Some(target_point),
+ } => {
+ const TOLERANCE: f32 = 4.0;
+
+ let diff = *scroll_at_arrival - scroll_pixel_position.map(|p| p.0);
+
+ if diff.x.abs() < TOLERANCE && diff.y.abs() < TOLERANCE {
+ (*scroll_position_at_start, target_point)
+ } else {
+ (None, target_point)
+ }
+ }
+ Self::Arrived {
+ target_point: None, ..
+ } => {
+ *self = Self::Inactive;
+ return true;
+ }
+ Self::MovingFrom { .. } | Self::Inactive => return false,
+ };
+
+ let now = Instant::now();
+ let duration = Self::animation_duration(cursor, *target_point);
+ let target_point = *target_point;
+
+ *self = Self::MovingFrom {
+ animation: now..now + duration,
+ target_point,
+ };
+
+ if let Some(scroll_position) = scroll_position {
+ cx.spawn_in(window, |editor, mut cx| async move {
+ smol::Timer::after(duration).await;
+ editor
+ .update_in(&mut cx, |editor, window, cx| {
+ if let Self::MovingFrom { .. } | Self::Inactive =
+ editor.edit_prediction_preview
+ {
+ editor.set_scroll_position(scroll_position, window, cx)
+ }
+ })
+ .log_err();
+ })
+ .detach();
+ }
+
+ true
+ }
+
+ /// Whether the preview is active or we are animating to or from it.
+ fn is_active(&self) -> bool {
+ matches!(
+ self,
+ Self::MovingTo { .. } | Self::Arrived { .. } | Self::MovingFrom { .. }
+ )
+ }
+
+ /// Returns true if the preview is active, not cancelled, and the animation is settled.
+ fn is_active_settled(&self) -> bool {
+ matches!(self, Self::Arrived { .. })
+ }
+
+ #[allow(clippy::too_many_arguments)]
+ fn move_state(
+ &mut self,
+ snapshot: &EditorSnapshot,
+ visible_row_range: Range<DisplayRow>,
+ line_layouts: &[LineWithInvisibles],
+ scroll_pixel_position: gpui::Point<Pixels>,
+ line_height: Pixels,
+ target: Anchor,
+ cursor: Option<DisplayPoint>,
+ ) -> Option<EditPredictionMoveState> {
+ let delta = match self {
+ Self::Inactive => return None,
+ Self::Arrived { .. } => 1.,
+ Self::MovingTo {
+ animation,
+ scroll_position_at_start: original_scroll_position,
+ target_point,
+ } => {
+ let now = Instant::now();
+ if animation.end < now {
+ *self = Self::Arrived {
+ scroll_position_at_start: *original_scroll_position,
+ scroll_position_at_arrival: Some(scroll_pixel_position.map(|p| p.0)),
+ target_point: Some(*target_point),
+ };
+ 1.0
+ } else {
+ (now - animation.start).as_secs_f32()
+ / (animation.end - animation.start).as_secs_f32()
+ }
+ }
+ Self::MovingFrom { animation, .. } => {
+ let now = Instant::now();
+ if animation.end < now {
+ *self = Self::Inactive;
+ return None;
+ } else {
+ let delta = (now - animation.start).as_secs_f32()
+ / (animation.end - animation.start).as_secs_f32();
+ 1.0 - delta
+ }
+ }
+ };
+
+ let cursor = cursor?;
+
+ if !visible_row_range.contains(&cursor.row()) {
+ return None;
+ }
+
+ let target_position = target.to_display_point(&snapshot.display_snapshot);
+
+ if !visible_row_range.contains(&target_position.row()) {
+ return None;
+ }
+
+ let target_row_layout =
+ &line_layouts[target_position.row().minus(visible_row_range.start) as usize];
+ let target_column = target_position.column() as usize;
+
+ let target_character_x = target_row_layout.x_for_index(target_column);
+
+ let target_x = target_character_x - scroll_pixel_position.x;
+ let target_y =
+ (target_position.row().as_f32() - scroll_pixel_position.y / line_height) * line_height;
+
+ let origin_x = line_layouts[cursor.row().minus(visible_row_range.start) as usize]
+ .x_for_index(cursor.column() as usize);
+ let origin_y =
+ (cursor.row().as_f32() - scroll_pixel_position.y / line_height) * line_height;
+
+ let delta = 1.0 - (-10.0 * delta).exp2();
+
+ let x = origin_x + (target_x - origin_x) * delta;
+ let y = origin_y + (target_y - origin_y) * delta;
+
+ Some(EditPredictionMoveState {
+ delta,
+ position: point(x, y),
+ })
+ }
+}
+
+pub(crate) struct EditPredictionMoveState {
+ delta: f32,
+ position: gpui::Point<Pixels>,
+}
+
+impl EditPredictionMoveState {
+ pub fn is_animation_completed(&self) -> bool {
+ self.delta >= 1.
+ }
+}
+
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
struct EditorActionId(usize);
@@ -704,7 +993,7 @@ pub struct Editor {
inline_completions_hidden_for_vim_mode: bool,
show_inline_completions_override: Option<bool>,
menu_inline_completions_policy: MenuInlineCompletionsPolicy,
- previewing_inline_completion: bool,
+ edit_prediction_preview: EditPredictionPreview,
inlay_hint_cache: InlayHintCache,
next_inlay_id: usize,
_subscriptions: Vec<Subscription>,
@@ -1397,7 +1686,7 @@ impl Editor {
edit_prediction_provider: None,
active_inline_completion: None,
stale_inline_completion_in_menu: None,
- previewing_inline_completion: false,
+ edit_prediction_preview: EditPredictionPreview::Inactive,
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
gutter_hovered: false,
@@ -5112,11 +5401,11 @@ impl Editor {
true
}
- /// Returns true when we're displaying the inline completion popover below the cursor
+ /// Returns true when we're displaying the edit prediction popover below the cursor
/// like we are not previewing and the LSP autocomplete menu is visible
/// or we are in `when_holding_modifier` mode.
pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
- if self.previewing_inline_completion
+ if self.edit_prediction_preview.is_active()
|| !self.show_edit_predictions_in_menu()
|| !self.edit_predictions_enabled()
{
@@ -5138,15 +5427,7 @@ impl Editor {
cx: &mut Context<Self>,
) {
if self.show_edit_predictions_in_menu() {
- let accept_binding = self.accept_edit_prediction_keybind(window, cx);
- if let Some(accept_keystroke) = accept_binding.keystroke() {
- let was_previewing_inline_completion = self.previewing_inline_completion;
- self.previewing_inline_completion = modifiers == accept_keystroke.modifiers
- && accept_keystroke.modifiers.modified();
- if self.previewing_inline_completion != was_previewing_inline_completion {
- self.update_visible_inline_completion(window, cx);
- }
- }
+ self.update_edit_prediction_preview(&modifiers, position_map, window, cx);
}
let mouse_position = window.mouse_position();
@@ -5163,9 +5444,50 @@ impl Editor {
)
}
+ fn update_edit_prediction_preview(
+ &mut self,
+ modifiers: &Modifiers,
+ position_map: &PositionMap,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ let accept_keybind = self.accept_edit_prediction_keybind(window, cx);
+ let Some(accept_keystroke) = accept_keybind.keystroke() else {
+ return;
+ };
+
+ if &accept_keystroke.modifiers == modifiers {
+ if let Some(completion) = self.active_inline_completion.as_ref() {
+ if self.edit_prediction_preview.start(
+ &completion.completion,
+ &position_map.snapshot,
+ self.selections
+ .newest_anchor()
+ .head()
+ .to_display_point(&position_map.snapshot),
+ ) {
+ self.request_autoscroll(Autoscroll::fit(), cx);
+ self.update_visible_inline_completion(window, cx);
+ cx.notify();
+ }
+ }
+ } else if self.edit_prediction_preview.end(
+ self.selections
+ .newest_anchor()
+ .head()
+ .to_display_point(&position_map.snapshot),
+ position_map.scroll_pixel_position,
+ window,
+ cx,
+ ) {
+ self.update_visible_inline_completion(window, cx);
+ cx.notify();
+ }
+ }
+
fn update_visible_inline_completion(
&mut self,
- _window: &mut Window,
+ window: &mut Window,
cx: &mut Context<Self>,
) -> Option<()> {
let selection = self.selections.newest_anchor();
@@ -5252,25 +5574,11 @@ impl Editor {
invalidation_row_range =
move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
let target = first_edit_start;
- let target_point = text::ToPoint::to_point(&target.text_anchor, &snapshot);
- // TODO: Base this off of TreeSitter or word boundaries?
- let target_excerpt_begin = snapshot.anchor_before(snapshot.clip_point(
- Point::new(target_point.row, target_point.column.saturating_sub(20)),
- Bias::Left,
- ));
- let target_excerpt_end = snapshot.anchor_after(snapshot.clip_point(
- Point::new(target_point.row, target_point.column + 20),
- Bias::Right,
- ));
- let range_around_target = target_excerpt_begin..target_excerpt_end;
- InlineCompletion::Move {
- target,
- range_around_target,
- snapshot,
- }
+ InlineCompletion::Move { target, snapshot }
} else {
let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
&& !self.inline_completions_hidden_for_vim_mode;
+
if show_completions_in_buffer {
if edits
.iter()
@@ -5329,6 +5637,15 @@ impl Editor {
));
self.stale_inline_completion_in_menu = None;
+ let editor_snapshot = self.snapshot(window, cx);
+ if self.edit_prediction_preview.restart(
+ &completion,
+ &editor_snapshot,
+ cursor.to_display_point(&editor_snapshot),
+ ) {
+ self.request_autoscroll(Autoscroll::fit(), cx);
+ }
+
self.active_inline_completion = Some(InlineCompletionState {
inlay_ids,
completion,
@@ -5556,7 +5873,7 @@ impl Editor {
}
pub fn context_menu_visible(&self) -> bool {
- !self.previewing_inline_completion
+ !self.edit_prediction_preview.is_active()
&& self
.context_menu
.borrow()
@@ -5591,7 +5908,7 @@ impl Editor {
cursor_point: Point,
style: &EditorStyle,
accept_keystroke: &gpui::Keystroke,
- window: &Window,
+ _window: &Window,
cx: &mut Context<Editor>,
) -> Option<AnyElement> {
let provider = self.edit_prediction_provider.as_ref()?;
@@ -5646,20 +5963,51 @@ impl Editor {
}
let completion = match &self.active_inline_completion {
- Some(completion) => self.render_edit_prediction_cursor_popover_preview(
- completion,
- cursor_point,
- style,
- window,
- cx,
- )?,
+ Some(completion) => match &completion.completion {
+ InlineCompletion::Move {
+ target, snapshot, ..
+ } if !self.has_visible_completions_menu() => {
+ use text::ToPoint as _;
+
+ return Some(
+ h_flex()
+ .px_2()
+ .py_1()
+ .elevation_2(cx)
+ .border_color(cx.theme().colors().border)
+ .rounded_tl(px(0.))
+ .gap_2()
+ .child(
+ if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
+ Icon::new(IconName::ZedPredictDown)
+ } else {
+ Icon::new(IconName::ZedPredictUp)
+ },
+ )
+ .child(Label::new("Hold"))
+ .children(ui::render_modifiers(
+ &accept_keystroke.modifiers,
+ PlatformStyle::platform(),
+ Some(Color::Default),
+ None,
+ true,
+ ))
+ .into_any(),
+ );
+ }
+ _ => self.render_edit_prediction_cursor_popover_preview(
+ completion,
+ cursor_point,
+ style,
+ cx,
+ )?,
+ },
None if is_refreshing => match &self.stale_inline_completion_in_menu {
Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
stale_completion,
cursor_point,
style,
- window,
cx,
)?,
@@ -5671,9 +6019,6 @@ impl Editor {
None => pending_completion_container().child(Label::new("No Prediction")),
};
- let buffer_font = theme::ThemeSettings::get_global(cx).buffer_font.clone();
- let completion = completion.font(buffer_font.clone());
-
let completion = if is_refreshing {
completion
.with_animation(
@@ -5698,6 +6043,7 @@ impl Editor {
.px_2()
.py_1()
.elevation_2(cx)
+ .border_color(cx.theme().colors().border)
.child(completion)
.child(ui::Divider::vertical())
.child(
@@ -5705,19 +6051,22 @@ impl Editor {
.h_full()
.gap_1()
.pl_2()
- .child(h_flex().font(buffer_font.clone()).gap_1().children(
- ui::render_modifiers(
- &accept_keystroke.modifiers,
- PlatformStyle::platform(),
- Some(if !has_completion {
- Color::Muted
- } else {
- Color::Default
- }),
- None,
- true,
- ),
- ))
+ .child(
+ h_flex()
+ .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
+ .gap_1()
+ .children(ui::render_modifiers(
+ &accept_keystroke.modifiers,
+ PlatformStyle::platform(),
+ Some(if !has_completion {
+ Color::Muted
+ } else {
+ Color::Default
+ }),
+ None,
+ true,
+ )),
+ )
.child(Label::new("Preview").into_any_element())
.opacity(if has_completion { 1.0 } else { 0.4 }),
)
@@ -5730,7 +6079,6 @@ impl Editor {
completion: &InlineCompletionState,
cursor_point: Point,
style: &EditorStyle,
- window: &Window,
cx: &mut Context<Editor>,
) -> Option<Div> {
use text::ToPoint as _;
@@ -5756,6 +6104,23 @@ impl Editor {
}
match &completion.completion {
+ InlineCompletion::Move {
+ target, snapshot, ..
+ } => Some(
+ h_flex()
+ .px_2()
+ .gap_2()
+ .flex_1()
+ .child(
+ if target.text_anchor.to_point(&snapshot).row > cursor_point.row {
+ Icon::new(IconName::ZedPredictDown)
+ } else {
+ Icon::new(IconName::ZedPredictUp)
+ },
+ )
+ .child(Label::new("Jump to Edit")),
+ ),
+
InlineCompletion::Edit {
edits,
edit_preview,
@@ -5825,103 +6190,11 @@ impl Editor {
.gap_2()
.pr_1()
.overflow_x_hidden()
+ .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
.child(left)
.child(preview),
)
}
-
- InlineCompletion::Move {
- target,
- range_around_target,
- snapshot,
- } => {
- let highlighted_text = snapshot.highlighted_text_for_range(
- range_around_target.clone(),
- None,
- &style.syntax,
- );
- let base = h_flex().gap_3().flex_1().child(render_relative_row_jump(
- "Jump ",
- cursor_point.row,
- target.text_anchor.to_point(&snapshot).row,
- ));
-
- if highlighted_text.text.is_empty() {
- return Some(base);
- }
-
- let cursor_color = self.current_user_player_color(cx).cursor;
-
- let start_point = range_around_target.start.to_point(&snapshot);
- let end_point = range_around_target.end.to_point(&snapshot);
- let target_point = target.text_anchor.to_point(&snapshot);
-
- let styled_text = highlighted_text.to_styled_text(&style.text);
- let text_len = highlighted_text.text.len();
-
- let cursor_relative_position = window
- .text_system()
- .layout_line(
- highlighted_text.text,
- style.text.font_size.to_pixels(window.rem_size()),
- // We don't need to include highlights
- // because we are only using this for the cursor position
- &[TextRun {
- len: text_len,
- font: style.text.font(),
- color: style.text.color,
- background_color: None,
- underline: None,
- strikethrough: None,
- }],
- )
- .log_err()
- .map(|line| {
- line.x_for_index(
- target_point.column.saturating_sub(start_point.column) as usize
- )
- });
-
- let fade_before = start_point.column > 0;
- let fade_after = end_point.column < snapshot.line_len(end_point.row);
-
- let background = cx.theme().colors().elevated_surface_background;
-
- let preview = h_flex()
- .relative()
- .child(styled_text)
- .when(fade_before, |parent| {
- parent.child(div().absolute().top_0().left_0().w_4().h_full().bg(
- linear_gradient(
- 90.,
- linear_color_stop(background, 0.),
- linear_color_stop(background.opacity(0.), 1.),
- ),
- ))
- })
- .when(fade_after, |parent| {
- parent.child(div().absolute().top_0().right_0().w_4().h_full().bg(
- linear_gradient(
- -90.,
- linear_color_stop(background, 0.),
- linear_color_stop(background.opacity(0.), 1.),
- ),
- ))
- })
- .when_some(cursor_relative_position, |parent, position| {
- parent.child(
- div()
- .w(px(2.))
- .h_full()
- .bg(cursor_color)
- .absolute()
- .top_0()
- .left(position),
- )
- });
-
- Some(base.child(preview))
- }
}
}
@@ -13740,6 +14013,23 @@ impl Editor {
}
}
+ pub fn previewing_edit_prediction_move(
+ &mut self,
+ ) -> Option<(Anchor, &mut EditPredictionPreview)> {
+ if !self.edit_prediction_preview.is_active() {
+ return None;
+ };
+
+ self.active_inline_completion
+ .as_ref()
+ .and_then(|completion| match completion.completion {
+ InlineCompletion::Move { target, .. } => {
+ Some((target, &mut self.edit_prediction_preview))
+ }
+ _ => None,
+ })
+ }
+
pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
(self.read_only(cx) || self.blink_manager.read(cx).visible())
&& self.focus_handle.is_focused(window)
@@ -14572,7 +14862,7 @@ impl Editor {
}
pub fn has_visible_completions_menu(&self) -> bool {
- !self.previewing_inline_completion
+ !self.edit_prediction_preview.is_active()
&& self.context_menu.borrow().as_ref().map_or(false, |menu| {
menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
})
@@ -16,12 +16,12 @@ use crate::{
mouse_context_menu::{self, MenuPosition, MouseContextMenu},
scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair},
AcceptEditPrediction, BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayPoint,
- DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
- EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
- GoToPrevHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
- InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point,
- RevertSelectedHunks, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap,
- StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR,
+ DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode,
+ EditPredictionPreview, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
+ ExpandExcerpts, FocusedBlock, GoToHunk, GoToPrevHunk, GutterDimensions, HalfPageDown,
+ HalfPageUp, HandleInput, HoveredCursor, InlineCompletion, JumpData, LineDown, LineUp,
+ OpenExcerpts, PageDown, PageUp, Point, RevertSelectedHunks, RowExt, RowRangeExt, SelectPhase,
+ Selection, SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR,
EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT, FILE_HEADER_HEIGHT,
GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
};
@@ -1114,18 +1114,44 @@ impl EditorElement {
em_width: Pixels,
em_advance: Pixels,
autoscroll_containing_element: bool,
+ newest_selection_head: Option<DisplayPoint>,
window: &mut Window,
cx: &mut App,
) -> Vec<CursorLayout> {
let mut autoscroll_bounds = None;
let cursor_layouts = self.editor.update(cx, |editor, cx| {
let mut cursors = Vec::new();
+
+ let previewing_move =
+ if let Some((target, preview)) = editor.previewing_edit_prediction_move() {
+ cursors.extend(self.layout_edit_prediction_preview_cursor(
+ snapshot,
+ visible_display_row_range.clone(),
+ line_layouts,
+ content_origin,
+ scroll_pixel_position,
+ line_height,
+ em_advance,
+ preview,
+ target,
+ newest_selection_head,
+ window,
+ cx,
+ ));
+
+ true
+ } else {
+ false
+ };
+
+ let show_local_cursors = !previewing_move && editor.show_local_cursors(window, cx);
+
for (player_color, selections) in selections {
for selection in selections {
let cursor_position = selection.head;
let in_range = visible_display_row_range.contains(&cursor_position.row());
- if (selection.is_local && !editor.show_local_cursors(window, cx))
+ if (selection.is_local && !show_local_cursors)
|| !in_range
|| block_start_rows.contains(&cursor_position.row())
{
@@ -1249,6 +1275,7 @@ impl EditorElement {
cursors.push(cursor);
}
}
+
cursors
});
@@ -1259,6 +1286,50 @@ impl EditorElement {
cursor_layouts
}
+ #[allow(clippy::too_many_arguments)]
+ fn layout_edit_prediction_preview_cursor(
+ &self,
+ snapshot: &EditorSnapshot,
+ visible_row_range: Range<DisplayRow>,
+ line_layouts: &[LineWithInvisibles],
+ content_origin: gpui::Point<Pixels>,
+ scroll_pixel_position: gpui::Point<Pixels>,
+ line_height: Pixels,
+ em_advance: Pixels,
+ preview: &mut EditPredictionPreview,
+ target: Anchor,
+ cursor: Option<DisplayPoint>,
+ window: &mut Window,
+ cx: &mut App,
+ ) -> Option<CursorLayout> {
+ let state = preview.move_state(
+ snapshot,
+ visible_row_range,
+ line_layouts,
+ scroll_pixel_position,
+ line_height,
+ target,
+ cursor,
+ )?;
+
+ if !state.is_animation_completed() {
+ window.request_animation_frame();
+ }
+
+ let mut cursor = CursorLayout {
+ color: self.style.local_player.cursor,
+ block_width: em_advance,
+ origin: state.position,
+ line_height,
+ shape: CursorShape::Bar,
+ block_text: None,
+ cursor_name: None,
+ };
+
+ cursor.layout(content_origin, None, window, cx);
+ Some(cursor)
+ }
+
fn layout_scrollbars(
&self,
snapshot: &EditorSnapshot,
@@ -3531,7 +3602,7 @@ impl EditorElement {
}
#[allow(clippy::too_many_arguments)]
- fn layout_inline_completion_popover(
+ fn layout_edit_prediction_popover(
&self,
text_bounds: &Bounds<Pixels>,
editor_snapshot: &EditorSnapshot,
@@ -3559,6 +3630,49 @@ impl EditorElement {
match &active_inline_completion.completion {
InlineCompletion::Move { target, .. } => {
+ if editor.edit_prediction_requires_modifier() {
+ let cursor_position =
+ target.to_display_point(&editor_snapshot.display_snapshot);
+
+ if !editor.edit_prediction_preview.is_active_settled()
+ || !visible_row_range.contains(&cursor_position.row())
+ {
+ return None;
+ }
+
+ let accept_keybind = editor.accept_edit_prediction_keybind(window, cx);
+ let accept_keystroke = accept_keybind.keystroke()?;
+
+ let mut element = div()
+ .px_2()
+ .py_1()
+ .elevation_2(cx)
+ .border_color(cx.theme().colors().border)
+ .rounded_br(px(0.))
+ .child(Label::new(accept_keystroke.key.clone()).buffer_font(cx))
+ .into_any();
+
+ let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
+
+ let cursor_row_layout = &line_layouts
+ [cursor_position.row().minus(visible_row_range.start) as usize];
+ let cursor_column = cursor_position.column() as usize;
+
+ let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
+ let target_y = (cursor_position.row().as_f32()
+ - scroll_pixel_position.y / line_height)
+ * line_height;
+
+ let offset = point(
+ cursor_character_x - size.width,
+ target_y - size.height - PADDING_Y,
+ );
+
+ element.prepaint_at(text_bounds.origin + offset, window, cx);
+
+ return Some(element);
+ }
+
let target_display_point = target.to_display_point(editor_snapshot);
if target_display_point.row().as_f32() < scroll_top {
let mut element = inline_completion_accept_indicator(
@@ -5688,7 +5802,7 @@ fn inline_completion_accept_indicator(
.text_size(TextSize::XSmall.rems(cx))
.text_color(cx.theme().colors().text)
.gap_1()
- .when(!editor.previewing_inline_completion, |parent| {
+ .when(!editor.edit_prediction_preview.is_active(), |parent| {
parent.children(ui::render_modifiers(
&accept_keystroke.modifiers,
PlatformStyle::platform(),
@@ -7246,6 +7360,7 @@ impl Element for EditorElement {
em_width,
em_advance,
autoscroll_containing_element,
+ newest_selection_head,
window,
cx,
);
@@ -7397,7 +7512,7 @@ impl Element for EditorElement {
);
}
- let inline_completion_popover = self.layout_inline_completion_popover(
+ let inline_completion_popover = self.layout_edit_prediction_popover(
&text_hitbox.bounds,
&snapshot,
start_row..end_row,