Rework edit prediction preview mode (#24700)
Don't animate the cursor when previewing jumps. Instead, display the jump popover with a line that resembles a cursor, indicating the jump destination. If the jump destination is outside of the view port, there is an extra step in which `tab` scrolls the viewport to reveal the jump destination. Release Notes: - N/A --------- Co-authored-by: danilo-leal <daniloleal09@gmail.com> Co-authored-by: agu-z <hi@aguz.me>
This commit is contained in:
parent
5293f5724c
commit
148547ecd1
5 changed files with 245 additions and 519 deletions
|
@ -520,294 +520,13 @@ pub enum MenuInlineCompletionsPolicy {
|
||||||
ByProvider,
|
ByProvider,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO az do we need this?
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub enum EditPredictionPreview {
|
pub enum EditPredictionPreview {
|
||||||
/// Modifier is not pressed
|
/// Modifier is not pressed
|
||||||
Inactive,
|
Inactive,
|
||||||
/// Modifier pressed, animating to active
|
/// Modifier pressed
|
||||||
MovingTo {
|
Active {
|
||||||
animation: Range<Instant>,
|
previous_scroll_position: Option<ScrollAnchor>,
|
||||||
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)]
|
||||||
|
@ -1786,6 +1505,15 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
|
fn key_context(&self, window: &Window, cx: &App) -> KeyContext {
|
||||||
|
self.key_context_internal(self.has_active_inline_completion(), window, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn key_context_internal(
|
||||||
|
&self,
|
||||||
|
has_active_edit_prediction: bool,
|
||||||
|
window: &Window,
|
||||||
|
cx: &App,
|
||||||
|
) -> KeyContext {
|
||||||
let mut key_context = KeyContext::new_with_defaults();
|
let mut key_context = KeyContext::new_with_defaults();
|
||||||
key_context.add("Editor");
|
key_context.add("Editor");
|
||||||
let mode = match self.mode {
|
let mode = match self.mode {
|
||||||
|
@ -1836,10 +1564,9 @@ impl Editor {
|
||||||
key_context.set("extension", extension.to_string());
|
key_context.set("extension", extension.to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.has_active_inline_completion() {
|
if has_active_edit_prediction {
|
||||||
key_context.add("copilot_suggestion");
|
key_context.add("copilot_suggestion");
|
||||||
key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
|
key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
|
||||||
|
|
||||||
if showing_completions || self.edit_prediction_requires_modifier() {
|
if showing_completions || self.edit_prediction_requires_modifier() {
|
||||||
key_context.add(EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT);
|
key_context.add(EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT);
|
||||||
}
|
}
|
||||||
|
@ -1857,12 +1584,10 @@ impl Editor {
|
||||||
window: &Window,
|
window: &Window,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> AcceptEditPredictionBinding {
|
) -> AcceptEditPredictionBinding {
|
||||||
let mut context = self.key_context(window, cx);
|
let key_context = self.key_context_internal(true, window, cx);
|
||||||
context.add(EDIT_PREDICTION_KEY_CONTEXT);
|
|
||||||
|
|
||||||
AcceptEditPredictionBinding(
|
AcceptEditPredictionBinding(
|
||||||
window
|
window
|
||||||
.bindings_for_action_in_context(&AcceptEditPrediction, context)
|
.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.rev()
|
.rev()
|
||||||
.next(),
|
.next(),
|
||||||
|
@ -5067,6 +4792,15 @@ impl Editor {
|
||||||
self.snippet_stack.is_empty() && self.edit_predictions_enabled()
|
self.snippet_stack.is_empty() && self.edit_predictions_enabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn edit_prediction_preview_is_active(&self) -> bool {
|
||||||
|
match self.edit_prediction_preview {
|
||||||
|
EditPredictionPreview::Inactive => false,
|
||||||
|
EditPredictionPreview::Active { .. } => {
|
||||||
|
self.edit_prediction_requires_modifier() || self.has_visible_completions_menu()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inline_completions_enabled(&self, cx: &App) -> bool {
|
pub fn inline_completions_enabled(&self, cx: &App) -> bool {
|
||||||
let cursor = self.selections.newest_anchor().head();
|
let cursor = self.selections.newest_anchor().head();
|
||||||
if let Some((buffer, cursor_position)) =
|
if let Some((buffer, cursor_position)) =
|
||||||
|
@ -5232,10 +4966,40 @@ impl Editor {
|
||||||
match &active_inline_completion.completion {
|
match &active_inline_completion.completion {
|
||||||
InlineCompletion::Move { target, .. } => {
|
InlineCompletion::Move { target, .. } => {
|
||||||
let target = *target;
|
let target = *target;
|
||||||
// Note that this is also done in vim's handler of the Tab action.
|
|
||||||
self.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
|
if let Some(position_map) = &self.last_position_map {
|
||||||
selections.select_anchor_ranges([target..target]);
|
if position_map
|
||||||
});
|
.visible_row_range
|
||||||
|
.contains(&target.to_display_point(&position_map.snapshot).row())
|
||||||
|
|| !self.edit_prediction_preview_is_active()
|
||||||
|
{
|
||||||
|
// Note that this is also done in vim's handler of the Tab action.
|
||||||
|
self.change_selections(
|
||||||
|
Some(Autoscroll::newest()),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
|selections| {
|
||||||
|
selections.select_anchor_ranges([target..target]);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.clear_row_highlights::<EditPredictionPreview>();
|
||||||
|
|
||||||
|
self.edit_prediction_preview = EditPredictionPreview::Active {
|
||||||
|
previous_scroll_position: None,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
self.edit_prediction_preview = EditPredictionPreview::Active {
|
||||||
|
previous_scroll_position: Some(position_map.snapshot.scroll_anchor),
|
||||||
|
};
|
||||||
|
self.highlight_rows::<EditPredictionPreview>(
|
||||||
|
target..target,
|
||||||
|
cx.theme().colors().editor_highlighted_line_background,
|
||||||
|
true,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
self.request_autoscroll(Autoscroll::fit(), cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
InlineCompletion::Edit { edits, .. } => {
|
InlineCompletion::Edit { edits, .. } => {
|
||||||
if let Some(provider) = self.edit_prediction_provider() {
|
if let Some(provider) = self.edit_prediction_provider() {
|
||||||
|
@ -5403,7 +5167,7 @@ impl Editor {
|
||||||
/// 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.edit_prediction_preview.is_active()
|
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()
|
||||||
{
|
{
|
||||||
|
@ -5425,7 +5189,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() {
|
||||||
self.update_edit_prediction_preview(&modifiers, position_map, window, cx);
|
self.update_edit_prediction_preview(&modifiers, window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mouse_position = window.mouse_position();
|
let mouse_position = window.mouse_position();
|
||||||
|
@ -5445,7 +5209,6 @@ impl Editor {
|
||||||
fn update_edit_prediction_preview(
|
fn update_edit_prediction_preview(
|
||||||
&mut self,
|
&mut self,
|
||||||
modifiers: &Modifiers,
|
modifiers: &Modifiers,
|
||||||
position_map: &PositionMap,
|
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
|
@ -5455,37 +5218,31 @@ impl Editor {
|
||||||
};
|
};
|
||||||
|
|
||||||
if &accept_keystroke.modifiers == modifiers {
|
if &accept_keystroke.modifiers == modifiers {
|
||||||
let Some(completion) = self.active_inline_completion.as_ref() else {
|
if !self.edit_prediction_preview_is_active() {
|
||||||
return;
|
self.edit_prediction_preview = EditPredictionPreview::Active {
|
||||||
};
|
previous_scroll_position: None,
|
||||||
|
};
|
||||||
|
|
||||||
if !self.edit_prediction_requires_modifier() && !self.has_visible_completions_menu() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let transitioned = self.edit_prediction_preview.start(
|
|
||||||
&completion.completion,
|
|
||||||
&position_map.snapshot,
|
|
||||||
self.selections
|
|
||||||
.newest_anchor()
|
|
||||||
.head()
|
|
||||||
.to_display_point(&position_map.snapshot),
|
|
||||||
);
|
|
||||||
|
|
||||||
if transitioned {
|
|
||||||
self.request_autoscroll(Autoscroll::fit(), cx);
|
|
||||||
self.update_visible_inline_completion(window, cx);
|
self.update_visible_inline_completion(window, cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
} else if self.edit_prediction_preview.end(
|
} else if let EditPredictionPreview::Active {
|
||||||
self.selections
|
previous_scroll_position,
|
||||||
.newest_anchor()
|
} = self.edit_prediction_preview
|
||||||
.head()
|
{
|
||||||
.to_display_point(&position_map.snapshot),
|
if let (Some(previous_scroll_position), Some(position_map)) =
|
||||||
position_map.scroll_pixel_position,
|
(previous_scroll_position, self.last_position_map.as_ref())
|
||||||
window,
|
{
|
||||||
cx,
|
self.set_scroll_position(
|
||||||
) {
|
previous_scroll_position
|
||||||
|
.scroll_position(&position_map.snapshot.display_snapshot),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.edit_prediction_preview = EditPredictionPreview::Inactive;
|
||||||
|
self.clear_row_highlights::<EditPredictionPreview>();
|
||||||
self.update_visible_inline_completion(window, cx);
|
self.update_visible_inline_completion(window, cx);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
@ -5493,7 +5250,7 @@ impl Editor {
|
||||||
|
|
||||||
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();
|
||||||
|
@ -5643,15 +5400,6 @@ 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,
|
||||||
|
@ -5879,7 +5627,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn context_menu_visible(&self) -> bool {
|
pub fn context_menu_visible(&self) -> bool {
|
||||||
!self.edit_prediction_preview.is_active()
|
!self.edit_prediction_preview_is_active()
|
||||||
&& self
|
&& self
|
||||||
.context_menu
|
.context_menu
|
||||||
.borrow()
|
.borrow()
|
||||||
|
@ -5990,12 +5738,12 @@ impl Editor {
|
||||||
Icon::new(IconName::ZedPredictUp)
|
Icon::new(IconName::ZedPredictUp)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.child(Label::new("Hold"))
|
.child(Label::new("Hold").size(LabelSize::Small))
|
||||||
.children(ui::render_modifiers(
|
.children(ui::render_modifiers(
|
||||||
&accept_keystroke.modifiers,
|
&accept_keystroke.modifiers,
|
||||||
PlatformStyle::platform(),
|
PlatformStyle::platform(),
|
||||||
Some(Color::Default),
|
Some(Color::Default),
|
||||||
None,
|
Some(IconSize::Small.rems().into()),
|
||||||
true,
|
true,
|
||||||
))
|
))
|
||||||
.into_any(),
|
.into_any(),
|
||||||
|
@ -14056,23 +13804,6 @@ 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)
|
||||||
|
@ -14905,7 +14636,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn has_visible_completions_menu(&self) -> bool {
|
pub fn has_visible_completions_menu(&self) -> bool {
|
||||||
!self.edit_prediction_preview.is_active()
|
!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,
|
DisplayRow, DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
|
||||||
EditPredictionPreview, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
|
EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GoToHunk,
|
||||||
ExpandExcerpts, FocusedBlock, GoToHunk, GoToPrevHunk, GutterDimensions, HalfPageDown,
|
GoToPrevHunk, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor,
|
||||||
HalfPageUp, HandleInput, HoveredCursor, InlineCompletion, JumpData, LineDown, LineUp,
|
InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point,
|
||||||
OpenExcerpts, PageDown, PageUp, Point, RevertSelectedHunks, RowExt, RowRangeExt, SelectPhase,
|
RevertSelectedHunks, RowExt, RowRangeExt, SelectPhase, Selection, SoftWrap,
|
||||||
Selection, SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR,
|
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,
|
||||||
};
|
};
|
||||||
|
@ -1116,7 +1116,6 @@ 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> {
|
||||||
|
@ -1124,29 +1123,7 @@ impl EditorElement {
|
||||||
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 =
|
let show_local_cursors = editor.show_local_cursors(window, cx);
|
||||||
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 {
|
||||||
|
@ -1288,50 +1265,6 @@ 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,
|
||||||
|
@ -3607,6 +3540,7 @@ impl EditorElement {
|
||||||
fn layout_edit_prediction_popover(
|
fn layout_edit_prediction_popover(
|
||||||
&self,
|
&self,
|
||||||
text_bounds: &Bounds<Pixels>,
|
text_bounds: &Bounds<Pixels>,
|
||||||
|
content_origin: gpui::Point<Pixels>,
|
||||||
editor_snapshot: &EditorSnapshot,
|
editor_snapshot: &EditorSnapshot,
|
||||||
visible_row_range: Range<DisplayRow>,
|
visible_row_range: Range<DisplayRow>,
|
||||||
scroll_top: f32,
|
scroll_top: f32,
|
||||||
|
@ -3631,61 +3565,118 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adjust text origin for horizontal scrolling (in some cases here)
|
// Adjust text origin for horizontal scrolling (in some cases here)
|
||||||
let start_point =
|
let start_point = content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
|
||||||
text_bounds.origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
|
|
||||||
|
|
||||||
// Clamp left offset after extreme scrollings
|
// Clamp left offset after extreme scrollings
|
||||||
let clamp_start = |point: gpui::Point<Pixels>| gpui::Point {
|
let clamp_start = |point: gpui::Point<Pixels>| gpui::Point {
|
||||||
x: point.x.max(text_bounds.origin.x),
|
x: point.x.max(content_origin.x),
|
||||||
y: point.y,
|
y: point.y,
|
||||||
};
|
};
|
||||||
|
|
||||||
match &active_inline_completion.completion {
|
match &active_inline_completion.completion {
|
||||||
InlineCompletion::Move { target, .. } => {
|
InlineCompletion::Move { target, .. } => {
|
||||||
if editor.edit_prediction_requires_modifier() {
|
let target_display_point = target.to_display_point(editor_snapshot);
|
||||||
let cursor_position =
|
|
||||||
target.to_display_point(&editor_snapshot.display_snapshot);
|
|
||||||
|
|
||||||
if !editor.edit_prediction_preview.is_active_settled()
|
if editor.edit_prediction_requires_modifier() {
|
||||||
|| !visible_row_range.contains(&cursor_position.row())
|
if !editor.edit_prediction_preview_is_active() {
|
||||||
{
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let accept_keybind = editor.accept_edit_prediction_keybind(window, cx);
|
if target_display_point.row() < visible_row_range.start {
|
||||||
let accept_keystroke = accept_keybind.keystroke()?;
|
let mut element = inline_completion_accept_indicator(
|
||||||
|
"Scroll",
|
||||||
let mut element = div()
|
Some(IconName::ZedPredictUp),
|
||||||
.px_2()
|
editor,
|
||||||
.py_1()
|
window,
|
||||||
.elevation_2(cx)
|
cx,
|
||||||
.border_color(cx.theme().colors().border)
|
)?
|
||||||
.rounded_br(px(0.))
|
|
||||||
.child(Label::new(accept_keystroke.key.clone()).buffer_font(cx))
|
|
||||||
.into_any();
|
.into_any();
|
||||||
|
|
||||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||||
|
|
||||||
let cursor_row_layout = &line_layouts
|
let cursor = newest_selection_head?;
|
||||||
[cursor_position.row().minus(visible_row_range.start) as usize];
|
let cursor_row_layout = line_layouts
|
||||||
let cursor_column = cursor_position.column() as usize;
|
.get(cursor.row().minus(visible_row_range.start) as usize)?;
|
||||||
|
let cursor_column = cursor.column() as usize;
|
||||||
|
|
||||||
let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
|
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(
|
const PADDING_Y: Pixels = px(24.);
|
||||||
cursor_character_x - size.width,
|
|
||||||
target_y - size.height - PADDING_Y,
|
|
||||||
);
|
|
||||||
|
|
||||||
element.prepaint_at(text_bounds.origin + offset, window, cx);
|
let origin = start_point + point(cursor_character_x, PADDING_Y);
|
||||||
|
|
||||||
return Some(element);
|
element.prepaint_at(origin, window, cx);
|
||||||
|
return Some(element);
|
||||||
|
} else if target_display_point.row() >= visible_row_range.end {
|
||||||
|
let mut element = inline_completion_accept_indicator(
|
||||||
|
"Scroll",
|
||||||
|
Some(IconName::ZedPredictDown),
|
||||||
|
editor,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
)?
|
||||||
|
.into_any();
|
||||||
|
|
||||||
|
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||||
|
|
||||||
|
let cursor = newest_selection_head?;
|
||||||
|
let cursor_row_layout = line_layouts
|
||||||
|
.get(cursor.row().minus(visible_row_range.start) as usize)?;
|
||||||
|
let cursor_column = cursor.column() as usize;
|
||||||
|
|
||||||
|
let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
|
||||||
|
const PADDING_Y: Pixels = px(24.);
|
||||||
|
|
||||||
|
let origin = start_point
|
||||||
|
+ point(
|
||||||
|
cursor_character_x,
|
||||||
|
text_bounds.size.height - size.height - PADDING_Y,
|
||||||
|
);
|
||||||
|
|
||||||
|
element.prepaint_at(origin, window, cx);
|
||||||
|
return Some(element);
|
||||||
|
} else {
|
||||||
|
const POLE_WIDTH: Pixels = px(2.);
|
||||||
|
|
||||||
|
let mut element = v_flex()
|
||||||
|
.child(
|
||||||
|
inline_completion_accept_indicator(
|
||||||
|
"Jump", None, editor, window, cx,
|
||||||
|
)?
|
||||||
|
.rounded_br(px(0.)),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.w(POLE_WIDTH)
|
||||||
|
.bg(cx.theme().colors().border)
|
||||||
|
.h(line_height),
|
||||||
|
)
|
||||||
|
.items_end()
|
||||||
|
.into_any();
|
||||||
|
|
||||||
|
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||||
|
|
||||||
|
let line_layout =
|
||||||
|
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_x = line_layout.x_for_index(target_column);
|
||||||
|
let target_y = (target_display_point.row().as_f32() * line_height)
|
||||||
|
- scroll_pixel_position.y;
|
||||||
|
|
||||||
|
let origin = clamp_start(
|
||||||
|
start_point + point(target_x, target_y)
|
||||||
|
- point(size.width - POLE_WIDTH, size.height - line_height),
|
||||||
|
);
|
||||||
|
|
||||||
|
element.prepaint_at(origin, window, cx);
|
||||||
|
|
||||||
|
return Some(element);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
||||||
"Jump to Edit",
|
"Jump to Edit",
|
||||||
|
@ -3693,7 +3684,8 @@ impl EditorElement {
|
||||||
editor,
|
editor,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)?;
|
)?
|
||||||
|
.into_any();
|
||||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||||
let offset = point((text_bounds.size.width - size.width) / 2., PADDING_Y);
|
let offset = point((text_bounds.size.width - size.width) / 2., PADDING_Y);
|
||||||
|
|
||||||
|
@ -3706,7 +3698,8 @@ impl EditorElement {
|
||||||
editor,
|
editor,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)?;
|
)?
|
||||||
|
.into_any();
|
||||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||||
let offset = point(
|
let offset = point(
|
||||||
(text_bounds.size.width - size.width) / 2.,
|
(text_bounds.size.width - size.width) / 2.,
|
||||||
|
@ -3722,7 +3715,8 @@ impl EditorElement {
|
||||||
editor,
|
editor,
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
)?;
|
)?
|
||||||
|
.into_any();
|
||||||
let target_line_end = DisplayPoint::new(
|
let target_line_end = DisplayPoint::new(
|
||||||
target_display_point.row(),
|
target_display_point.row(),
|
||||||
editor_snapshot.line_len(target_display_point.row()),
|
editor_snapshot.line_len(target_display_point.row()),
|
||||||
|
@ -3782,7 +3776,8 @@ impl EditorElement {
|
||||||
Some((
|
Some((
|
||||||
inline_completion_accept_indicator(
|
inline_completion_accept_indicator(
|
||||||
"Accept", None, editor, window, cx,
|
"Accept", None, editor, window, cx,
|
||||||
)?,
|
)?
|
||||||
|
.into_any(),
|
||||||
editor.display_to_pixel_point(
|
editor.display_to_pixel_point(
|
||||||
target_line_end,
|
target_line_end,
|
||||||
editor_snapshot,
|
editor_snapshot,
|
||||||
|
@ -5805,7 +5800,7 @@ fn inline_completion_accept_indicator(
|
||||||
editor: &Editor,
|
editor: &Editor,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
) -> Option<AnyElement> {
|
) -> Option<Div> {
|
||||||
let accept_binding = editor.accept_edit_prediction_keybind(window, cx);
|
let accept_binding = editor.accept_edit_prediction_keybind(window, cx);
|
||||||
let accept_keystroke = accept_binding.keystroke()?;
|
let accept_keystroke = accept_binding.keystroke()?;
|
||||||
|
|
||||||
|
@ -5815,7 +5810,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.edit_prediction_preview.is_active(), |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(),
|
||||||
|
@ -5826,33 +5821,44 @@ fn inline_completion_accept_indicator(
|
||||||
})
|
})
|
||||||
.child(accept_keystroke.key.clone());
|
.child(accept_keystroke.key.clone());
|
||||||
|
|
||||||
let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
|
let result = h_flex()
|
||||||
let accent_color = cx.theme().colors().text_accent;
|
.gap_1()
|
||||||
let editor_bg_color = cx.theme().colors().editor_background;
|
.border_1()
|
||||||
let bg_color = editor_bg_color.blend(accent_color.opacity(0.2));
|
.rounded_md()
|
||||||
|
.shadow_sm()
|
||||||
|
.child(accept_key)
|
||||||
|
.child(Label::new(label).size(LabelSize::Small))
|
||||||
|
.when_some(icon, |element, icon| {
|
||||||
|
element.child(
|
||||||
|
div()
|
||||||
|
.mt(px(1.5))
|
||||||
|
.child(Icon::new(icon).size(IconSize::Small)),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
Some(
|
let colors = cx.theme().colors();
|
||||||
h_flex()
|
|
||||||
|
let result = if editor.edit_prediction_requires_modifier() {
|
||||||
|
result
|
||||||
|
.py_1()
|
||||||
|
.px_2()
|
||||||
|
.elevation_2(cx)
|
||||||
|
.border_color(colors.border)
|
||||||
|
} else {
|
||||||
|
let accent_color = colors.text_accent;
|
||||||
|
let editor_bg_color = colors.editor_background;
|
||||||
|
let bg_color = editor_bg_color.blend(accent_color.opacity(0.2));
|
||||||
|
let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
|
||||||
|
|
||||||
|
result
|
||||||
|
.bg(bg_color)
|
||||||
|
.border_color(colors.text_accent.opacity(0.8))
|
||||||
.py_0p5()
|
.py_0p5()
|
||||||
.pl_1()
|
.pl_1()
|
||||||
.pr(padding_right)
|
.pr(padding_right)
|
||||||
.gap_1()
|
};
|
||||||
.bg(bg_color)
|
|
||||||
.border_1()
|
Some(result)
|
||||||
.border_color(cx.theme().colors().text_accent.opacity(0.8))
|
|
||||||
.rounded_md()
|
|
||||||
.shadow_sm()
|
|
||||||
.child(accept_key)
|
|
||||||
.child(Label::new(label).size(LabelSize::Small))
|
|
||||||
.when_some(icon, |element, icon| {
|
|
||||||
element.child(
|
|
||||||
div()
|
|
||||||
.mt(px(1.5))
|
|
||||||
.child(Icon::new(icon).size(IconSize::Small)),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.into_any(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>);
|
pub struct AcceptEditPredictionBinding(pub(crate) Option<gpui::KeyBinding>);
|
||||||
|
@ -7357,7 +7363,7 @@ impl Element for EditorElement {
|
||||||
let visible_row_range = start_row..end_row;
|
let visible_row_range = start_row..end_row;
|
||||||
let non_visible_cursors = cursors
|
let non_visible_cursors = cursors
|
||||||
.iter()
|
.iter()
|
||||||
.any(move |c| !visible_row_range.contains(&c.0.row()));
|
.any(|c| !visible_row_range.contains(&c.0.row()));
|
||||||
|
|
||||||
let visible_cursors = self.layout_visible_cursors(
|
let visible_cursors = self.layout_visible_cursors(
|
||||||
&snapshot,
|
&snapshot,
|
||||||
|
@ -7373,7 +7379,6 @@ 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,
|
||||||
);
|
);
|
||||||
|
@ -7527,6 +7532,7 @@ impl Element for EditorElement {
|
||||||
|
|
||||||
let inline_completion_popover = self.layout_edit_prediction_popover(
|
let inline_completion_popover = self.layout_edit_prediction_popover(
|
||||||
&text_hitbox.bounds,
|
&text_hitbox.bounds,
|
||||||
|
content_origin,
|
||||||
&snapshot,
|
&snapshot,
|
||||||
start_row..end_row,
|
start_row..end_row,
|
||||||
scroll_position.y,
|
scroll_position.y,
|
||||||
|
@ -7598,6 +7604,7 @@ impl Element for EditorElement {
|
||||||
|
|
||||||
let position_map = Rc::new(PositionMap {
|
let position_map = Rc::new(PositionMap {
|
||||||
size: bounds.size,
|
size: bounds.size,
|
||||||
|
visible_row_range,
|
||||||
scroll_pixel_position,
|
scroll_pixel_position,
|
||||||
scroll_max,
|
scroll_max,
|
||||||
line_layouts,
|
line_layouts,
|
||||||
|
@ -7991,6 +7998,7 @@ pub(crate) struct PositionMap {
|
||||||
pub scroll_max: gpui::Point<f32>,
|
pub scroll_max: gpui::Point<f32>,
|
||||||
pub em_width: Pixels,
|
pub em_width: Pixels,
|
||||||
pub em_advance: Pixels,
|
pub em_advance: Pixels,
|
||||||
|
pub visible_row_range: Range<DisplayRow>,
|
||||||
pub line_layouts: Vec<LineWithInvisibles>,
|
pub line_layouts: Vec<LineWithInvisibles>,
|
||||||
pub snapshot: EditorSnapshot,
|
pub snapshot: EditorSnapshot,
|
||||||
pub text_hitbox: Hitbox,
|
pub text_hitbox: Hitbox,
|
||||||
|
|
|
@ -3,6 +3,7 @@ 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,
|
||||||
|
@ -495,6 +496,15 @@ 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 {
|
||||||
|
previous_scroll_position,
|
||||||
|
} = &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,
|
||||||
&display_map,
|
&display_map,
|
||||||
|
|
|
@ -145,29 +145,6 @@ 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 { .. }) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ use gpui::{hsla, App, Styled};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::ElevationIndex;
|
use crate::ElevationIndex;
|
||||||
|
|
||||||
fn elevated<E: Styled>(this: E, cx: &mut App, index: ElevationIndex) -> E {
|
fn elevated<E: Styled>(this: E, cx: &App, index: ElevationIndex) -> E {
|
||||||
this.bg(cx.theme().colors().elevated_surface_background)
|
this.bg(cx.theme().colors().elevated_surface_background)
|
||||||
.rounded_lg()
|
.rounded_lg()
|
||||||
.border_1()
|
.border_1()
|
||||||
|
@ -54,7 +54,7 @@ pub trait StyledExt: Styled + Sized {
|
||||||
/// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
|
/// Sets `bg()`, `rounded_lg()`, `border()`, `border_color()`, `shadow()`
|
||||||
///
|
///
|
||||||
/// Examples: Notifications, Palettes, Detached/Floating Windows, Detached/Floating Panels
|
/// Examples: Notifications, Palettes, Detached/Floating Windows, Detached/Floating Panels
|
||||||
fn elevation_2(self, cx: &mut App) -> Self {
|
fn elevation_2(self, cx: &App) -> Self {
|
||||||
elevated(self, cx, ElevationIndex::ElevatedSurface)
|
elevated(self, cx, ElevationIndex::ElevatedSurface)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue