edit predictions: Preview jumps by animating cursor to target (#24604)
https://github.com/user-attachments/assets/977d08fb-a2b1-4826-9d95-8f35c6cb9f13 Release Notes: - N/A --------- Co-authored-by: Danilo <danilo@zed.dev> Co-authored-by: Smit <smit@zed.dev> Co-authored-by: Max <max@zed.dev>
This commit is contained in:
parent
5778e1e6f0
commit
22e2b8e832
6 changed files with 611 additions and 170 deletions
5
assets/icons/zed_predict_down.svg
Normal file
5
assets/icons/zed_predict_down.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M15 9.33333L14.5 9.66667L12.5 11L10.5 9.66667L10 9.33333" stroke="black" stroke-width="1.5"/>
|
||||||
|
<path d="M12.5 11V4.5" stroke="black" stroke-width="1.5"/>
|
||||||
|
<path d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.5"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 375 B |
5
assets/icons/zed_predict_up.svg
Normal file
5
assets/icons/zed_predict_up.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10 6.66667L10.5 6.33333L12.5 5L14.5 6.33333L15 6.66667" stroke="black" stroke-width="1.5"/>
|
||||||
|
<path d="M12.5 11V5" stroke="black" stroke-width="1.5"/>
|
||||||
|
<path d="M7.5 8.9V11C5.43097 11 4.56903 11 2.5 11V10.4L7.5 5.6V5H2.5V7.1" stroke="black" stroke-width="1.5"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 372 B |
|
@ -75,14 +75,14 @@ use code_context_menus::{
|
||||||
};
|
};
|
||||||
use git::blame::GitBlame;
|
use git::blame::GitBlame;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, impl_actions, linear_color_stop, linear_gradient, point, prelude::*, pulsating_between,
|
div, impl_actions, point, prelude::*, pulsating_between, px, relative, size, Action, Animation,
|
||||||
px, relative, size, Action, Animation, AnimationExt, AnyElement, App, AsyncWindowContext,
|
AnimationExt, AnyElement, App, AsyncWindowContext, AvailableSpace, Background, Bounds,
|
||||||
AvailableSpace, Background, Bounds, ClipboardEntry, ClipboardItem, Context, DispatchPhase,
|
ClipboardEntry, ClipboardItem, Context, DispatchPhase, ElementId, Entity, EntityInputHandler,
|
||||||
ElementId, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent, Focusable,
|
EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global,
|
||||||
FontId, FontWeight, Global, HighlightStyle, Hsla, InteractiveText, KeyContext, Modifiers,
|
HighlightStyle, Hsla, InteractiveText, KeyContext, Modifiers, MouseButton, MouseDownEvent,
|
||||||
MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, SharedString, Size,
|
PaintQuad, ParentElement, Pixels, Render, SharedString, Size, Styled, StyledText, Subscription,
|
||||||
Styled, StyledText, Subscription, Task, TextRun, TextStyle, TextStyleRefinement,
|
Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
|
||||||
UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
|
WeakEntity, WeakFocusHandle, Window,
|
||||||
};
|
};
|
||||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||||
use hover_popover::{hide_hover, HoverState};
|
use hover_popover::{hide_hover, HoverState};
|
||||||
|
@ -485,7 +485,6 @@ enum InlineCompletion {
|
||||||
},
|
},
|
||||||
Move {
|
Move {
|
||||||
target: Anchor,
|
target: Anchor,
|
||||||
range_around_target: Range<text::Anchor>,
|
|
||||||
snapshot: BufferSnapshot,
|
snapshot: BufferSnapshot,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -521,6 +520,296 @@ pub enum MenuInlineCompletionsPolicy {
|
||||||
ByProvider,
|
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)]
|
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
|
||||||
struct EditorActionId(usize);
|
struct EditorActionId(usize);
|
||||||
|
|
||||||
|
@ -704,7 +993,7 @@ pub struct Editor {
|
||||||
inline_completions_hidden_for_vim_mode: bool,
|
inline_completions_hidden_for_vim_mode: bool,
|
||||||
show_inline_completions_override: Option<bool>,
|
show_inline_completions_override: Option<bool>,
|
||||||
menu_inline_completions_policy: MenuInlineCompletionsPolicy,
|
menu_inline_completions_policy: MenuInlineCompletionsPolicy,
|
||||||
previewing_inline_completion: bool,
|
edit_prediction_preview: EditPredictionPreview,
|
||||||
inlay_hint_cache: InlayHintCache,
|
inlay_hint_cache: InlayHintCache,
|
||||||
next_inlay_id: usize,
|
next_inlay_id: usize,
|
||||||
_subscriptions: Vec<Subscription>,
|
_subscriptions: Vec<Subscription>,
|
||||||
|
@ -1397,7 +1686,7 @@ 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,
|
||||||
previewing_inline_completion: false,
|
edit_prediction_preview: EditPredictionPreview::Inactive,
|
||||||
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
|
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
|
||||||
|
|
||||||
gutter_hovered: false,
|
gutter_hovered: false,
|
||||||
|
@ -5112,11 +5401,11 @@ impl Editor {
|
||||||
true
|
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
|
/// like we are not previewing and the LSP autocomplete menu is visible
|
||||||
/// or we are in `when_holding_modifier` mode.
|
/// or we are in `when_holding_modifier` mode.
|
||||||
pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
|
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.show_edit_predictions_in_menu()
|
||||||
|| !self.edit_predictions_enabled()
|
|| !self.edit_predictions_enabled()
|
||||||
{
|
{
|
||||||
|
@ -5138,15 +5427,7 @@ impl Editor {
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
if self.show_edit_predictions_in_menu() {
|
if self.show_edit_predictions_in_menu() {
|
||||||
let accept_binding = self.accept_edit_prediction_keybind(window, cx);
|
self.update_edit_prediction_preview(&modifiers, position_map, 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let mouse_position = window.mouse_position();
|
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(
|
fn update_visible_inline_completion(
|
||||||
&mut self,
|
&mut self,
|
||||||
_window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let selection = self.selections.newest_anchor();
|
let selection = self.selections.newest_anchor();
|
||||||
|
@ -5252,25 +5574,11 @@ impl Editor {
|
||||||
invalidation_row_range =
|
invalidation_row_range =
|
||||||
move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
|
move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
|
||||||
let target = first_edit_start;
|
let target = first_edit_start;
|
||||||
let target_point = text::ToPoint::to_point(&target.text_anchor, &snapshot);
|
InlineCompletion::Move { target, 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,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
|
let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
|
||||||
&& !self.inline_completions_hidden_for_vim_mode;
|
&& !self.inline_completions_hidden_for_vim_mode;
|
||||||
|
|
||||||
if show_completions_in_buffer {
|
if show_completions_in_buffer {
|
||||||
if edits
|
if edits
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -5329,6 +5637,15 @@ impl Editor {
|
||||||
));
|
));
|
||||||
|
|
||||||
self.stale_inline_completion_in_menu = None;
|
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 {
|
self.active_inline_completion = Some(InlineCompletionState {
|
||||||
inlay_ids,
|
inlay_ids,
|
||||||
completion,
|
completion,
|
||||||
|
@ -5556,7 +5873,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context_menu_visible(&self) -> bool {
|
pub fn context_menu_visible(&self) -> bool {
|
||||||
!self.previewing_inline_completion
|
!self.edit_prediction_preview.is_active()
|
||||||
&& self
|
&& self
|
||||||
.context_menu
|
.context_menu
|
||||||
.borrow()
|
.borrow()
|
||||||
|
@ -5591,7 +5908,7 @@ impl Editor {
|
||||||
cursor_point: Point,
|
cursor_point: Point,
|
||||||
style: &EditorStyle,
|
style: &EditorStyle,
|
||||||
accept_keystroke: &gpui::Keystroke,
|
accept_keystroke: &gpui::Keystroke,
|
||||||
window: &Window,
|
_window: &Window,
|
||||||
cx: &mut Context<Editor>,
|
cx: &mut Context<Editor>,
|
||||||
) -> Option<AnyElement> {
|
) -> Option<AnyElement> {
|
||||||
let provider = self.edit_prediction_provider.as_ref()?;
|
let provider = self.edit_prediction_provider.as_ref()?;
|
||||||
|
@ -5646,20 +5963,51 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
let completion = match &self.active_inline_completion {
|
let completion = match &self.active_inline_completion {
|
||||||
Some(completion) => self.render_edit_prediction_cursor_popover_preview(
|
Some(completion) => match &completion.completion {
|
||||||
completion,
|
InlineCompletion::Move {
|
||||||
cursor_point,
|
target, snapshot, ..
|
||||||
style,
|
} if !self.has_visible_completions_menu() => {
|
||||||
window,
|
use text::ToPoint as _;
|
||||||
cx,
|
|
||||||
)?,
|
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 {
|
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(
|
||||||
stale_completion,
|
stale_completion,
|
||||||
cursor_point,
|
cursor_point,
|
||||||
style,
|
style,
|
||||||
window,
|
|
||||||
cx,
|
cx,
|
||||||
)?,
|
)?,
|
||||||
|
|
||||||
|
@ -5671,9 +6019,6 @@ impl Editor {
|
||||||
None => pending_completion_container().child(Label::new("No Prediction")),
|
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 {
|
let completion = if is_refreshing {
|
||||||
completion
|
completion
|
||||||
.with_animation(
|
.with_animation(
|
||||||
|
@ -5698,6 +6043,7 @@ impl Editor {
|
||||||
.px_2()
|
.px_2()
|
||||||
.py_1()
|
.py_1()
|
||||||
.elevation_2(cx)
|
.elevation_2(cx)
|
||||||
|
.border_color(cx.theme().colors().border)
|
||||||
.child(completion)
|
.child(completion)
|
||||||
.child(ui::Divider::vertical())
|
.child(ui::Divider::vertical())
|
||||||
.child(
|
.child(
|
||||||
|
@ -5705,19 +6051,22 @@ impl Editor {
|
||||||
.h_full()
|
.h_full()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.pl_2()
|
.pl_2()
|
||||||
.child(h_flex().font(buffer_font.clone()).gap_1().children(
|
.child(
|
||||||
ui::render_modifiers(
|
h_flex()
|
||||||
&accept_keystroke.modifiers,
|
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
|
||||||
PlatformStyle::platform(),
|
.gap_1()
|
||||||
Some(if !has_completion {
|
.children(ui::render_modifiers(
|
||||||
Color::Muted
|
&accept_keystroke.modifiers,
|
||||||
} else {
|
PlatformStyle::platform(),
|
||||||
Color::Default
|
Some(if !has_completion {
|
||||||
}),
|
Color::Muted
|
||||||
None,
|
} else {
|
||||||
true,
|
Color::Default
|
||||||
),
|
}),
|
||||||
))
|
None,
|
||||||
|
true,
|
||||||
|
)),
|
||||||
|
)
|
||||||
.child(Label::new("Preview").into_any_element())
|
.child(Label::new("Preview").into_any_element())
|
||||||
.opacity(if has_completion { 1.0 } else { 0.4 }),
|
.opacity(if has_completion { 1.0 } else { 0.4 }),
|
||||||
)
|
)
|
||||||
|
@ -5730,7 +6079,6 @@ impl Editor {
|
||||||
completion: &InlineCompletionState,
|
completion: &InlineCompletionState,
|
||||||
cursor_point: Point,
|
cursor_point: Point,
|
||||||
style: &EditorStyle,
|
style: &EditorStyle,
|
||||||
window: &Window,
|
|
||||||
cx: &mut Context<Editor>,
|
cx: &mut Context<Editor>,
|
||||||
) -> Option<Div> {
|
) -> Option<Div> {
|
||||||
use text::ToPoint as _;
|
use text::ToPoint as _;
|
||||||
|
@ -5756,6 +6104,23 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
match &completion.completion {
|
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 {
|
InlineCompletion::Edit {
|
||||||
edits,
|
edits,
|
||||||
edit_preview,
|
edit_preview,
|
||||||
|
@ -5825,103 +6190,11 @@ impl Editor {
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.pr_1()
|
.pr_1()
|
||||||
.overflow_x_hidden()
|
.overflow_x_hidden()
|
||||||
|
.font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
|
||||||
.child(left)
|
.child(left)
|
||||||
.child(preview),
|
.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 {
|
pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
|
||||||
(self.read_only(cx) || self.blink_manager.read(cx).visible())
|
(self.read_only(cx) || self.blink_manager.read(cx).visible())
|
||||||
&& self.focus_handle.is_focused(window)
|
&& self.focus_handle.is_focused(window)
|
||||||
|
@ -14572,7 +14862,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_visible_completions_menu(&self) -> bool {
|
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| {
|
&& self.context_menu.borrow().as_ref().map_or(false, |menu| {
|
||||||
menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
|
menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,12 +16,12 @@ use crate::{
|
||||||
mouse_context_menu::{self, MenuPosition, MouseContextMenu},
|
mouse_context_menu::{self, MenuPosition, MouseContextMenu},
|
||||||
scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair},
|
scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair},
|
||||||
AcceptEditPrediction, BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayPoint,
|
AcceptEditPrediction, BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayPoint,
|
||||||
DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
|
DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode,
|
||||||
EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
|
EditPredictionPreview, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
|
||||||
GoToPrevHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
|
ExpandExcerpts, FocusedBlock, GoToHunk, GoToPrevHunk, GutterDimensions, HalfPageDown,
|
||||||
InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point,
|
HalfPageUp, HandleInput, HoveredCursor, InlineCompletion, JumpData, LineDown, LineUp,
|
||||||
RevertSelectedHunks, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap,
|
OpenExcerpts, PageDown, PageUp, Point, RevertSelectedHunks, RowExt, RowRangeExt, SelectPhase,
|
||||||
StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR,
|
Selection, SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR,
|
||||||
EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT, FILE_HEADER_HEIGHT,
|
EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT, FILE_HEADER_HEIGHT,
|
||||||
GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_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_width: Pixels,
|
||||||
em_advance: Pixels,
|
em_advance: Pixels,
|
||||||
autoscroll_containing_element: bool,
|
autoscroll_containing_element: bool,
|
||||||
|
newest_selection_head: Option<DisplayPoint>,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Vec<CursorLayout> {
|
) -> Vec<CursorLayout> {
|
||||||
let mut autoscroll_bounds = None;
|
let mut autoscroll_bounds = None;
|
||||||
let cursor_layouts = self.editor.update(cx, |editor, cx| {
|
let cursor_layouts = self.editor.update(cx, |editor, cx| {
|
||||||
let mut cursors = Vec::new();
|
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 (player_color, selections) in selections {
|
||||||
for selection in selections {
|
for selection in selections {
|
||||||
let cursor_position = selection.head;
|
let cursor_position = selection.head;
|
||||||
|
|
||||||
let in_range = visible_display_row_range.contains(&cursor_position.row());
|
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
|
|| !in_range
|
||||||
|| block_start_rows.contains(&cursor_position.row())
|
|| block_start_rows.contains(&cursor_position.row())
|
||||||
{
|
{
|
||||||
|
@ -1249,6 +1275,7 @@ impl EditorElement {
|
||||||
cursors.push(cursor);
|
cursors.push(cursor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cursors
|
cursors
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1259,6 +1286,50 @@ impl EditorElement {
|
||||||
cursor_layouts
|
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(
|
fn layout_scrollbars(
|
||||||
&self,
|
&self,
|
||||||
snapshot: &EditorSnapshot,
|
snapshot: &EditorSnapshot,
|
||||||
|
@ -3531,7 +3602,7 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn layout_inline_completion_popover(
|
fn layout_edit_prediction_popover(
|
||||||
&self,
|
&self,
|
||||||
text_bounds: &Bounds<Pixels>,
|
text_bounds: &Bounds<Pixels>,
|
||||||
editor_snapshot: &EditorSnapshot,
|
editor_snapshot: &EditorSnapshot,
|
||||||
|
@ -3559,6 +3630,49 @@ impl EditorElement {
|
||||||
|
|
||||||
match &active_inline_completion.completion {
|
match &active_inline_completion.completion {
|
||||||
InlineCompletion::Move { target, .. } => {
|
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);
|
let target_display_point = target.to_display_point(editor_snapshot);
|
||||||
if target_display_point.row().as_f32() < scroll_top {
|
if target_display_point.row().as_f32() < scroll_top {
|
||||||
let mut element = inline_completion_accept_indicator(
|
let mut element = inline_completion_accept_indicator(
|
||||||
|
@ -5688,7 +5802,7 @@ fn inline_completion_accept_indicator(
|
||||||
.text_size(TextSize::XSmall.rems(cx))
|
.text_size(TextSize::XSmall.rems(cx))
|
||||||
.text_color(cx.theme().colors().text)
|
.text_color(cx.theme().colors().text)
|
||||||
.gap_1()
|
.gap_1()
|
||||||
.when(!editor.previewing_inline_completion, |parent| {
|
.when(!editor.edit_prediction_preview.is_active(), |parent| {
|
||||||
parent.children(ui::render_modifiers(
|
parent.children(ui::render_modifiers(
|
||||||
&accept_keystroke.modifiers,
|
&accept_keystroke.modifiers,
|
||||||
PlatformStyle::platform(),
|
PlatformStyle::platform(),
|
||||||
|
@ -7246,6 +7360,7 @@ impl Element for EditorElement {
|
||||||
em_width,
|
em_width,
|
||||||
em_advance,
|
em_advance,
|
||||||
autoscroll_containing_element,
|
autoscroll_containing_element,
|
||||||
|
newest_selection_head,
|
||||||
window,
|
window,
|
||||||
cx,
|
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,
|
&text_hitbox.bounds,
|
||||||
&snapshot,
|
&snapshot,
|
||||||
start_row..end_row,
|
start_row..end_row,
|
||||||
|
|
|
@ -113,6 +113,7 @@ impl Editor {
|
||||||
target_bottom = target_top + 1.;
|
target_bottom = target_top + 1.;
|
||||||
} else {
|
} else {
|
||||||
let selections = self.selections.all::<Point>(cx);
|
let selections = self.selections.all::<Point>(cx);
|
||||||
|
|
||||||
target_top = selections
|
target_top = selections
|
||||||
.first()
|
.first()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -144,6 +145,29 @@ impl Editor {
|
||||||
target_top = newest_selection_top;
|
target_top = newest_selection_top;
|
||||||
target_bottom = newest_selection_top + 1.;
|
target_bottom = newest_selection_top + 1.;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.edit_prediction_preview.is_active() {
|
||||||
|
if let Some(completion) = self.active_inline_completion.as_ref() {
|
||||||
|
match completion.completion {
|
||||||
|
crate::InlineCompletion::Edit { .. } => {}
|
||||||
|
crate::InlineCompletion::Move { target, .. } => {
|
||||||
|
let target_row = target.to_display_point(&display_map).row().as_f32();
|
||||||
|
|
||||||
|
if target_row < target_top {
|
||||||
|
target_top = target_row;
|
||||||
|
} else if target_row >= target_bottom {
|
||||||
|
target_bottom = target_row + 1.;
|
||||||
|
}
|
||||||
|
|
||||||
|
let selections_fit = target_bottom - target_top <= visible_lines;
|
||||||
|
if !selections_fit {
|
||||||
|
target_top = target_row;
|
||||||
|
target_bottom = target_row + 1.;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let margin = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
|
let margin = if matches!(self.mode, EditorMode::AutoHeight { .. }) {
|
||||||
|
|
|
@ -324,6 +324,8 @@ pub enum IconName {
|
||||||
ZedAssistant2,
|
ZedAssistant2,
|
||||||
ZedAssistantFilled,
|
ZedAssistantFilled,
|
||||||
ZedPredict,
|
ZedPredict,
|
||||||
|
ZedPredictUp,
|
||||||
|
ZedPredictDown,
|
||||||
ZedPredictDisabled,
|
ZedPredictDisabled,
|
||||||
ZedXCopilot,
|
ZedXCopilot,
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue