edit predictions: Split layout_edit_prediction
popover (#25463)
This function has grown a lot and it was getting really hard to navigate. This PR splits it into smaller methods and moves it into `Editor` with the rest of the edit prediction popovers' code. I think there are opportunities to consolidate the many popovers we have, but we'll do that separately. Release Notes: - N/A
This commit is contained in:
parent
d8694510b5
commit
ceb7fc2cb2
2 changed files with 542 additions and 465 deletions
|
@ -63,7 +63,7 @@ pub use editor_settings::{
|
|||
CurrentLineHighlight, EditorSettings, ScrollBeyondLastLine, SearchSettings, ShowScrollbar,
|
||||
};
|
||||
pub use editor_settings_controls::*;
|
||||
use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap};
|
||||
use element::{layout_line, AcceptEditPredictionBinding, LineWithInvisibles, PositionMap};
|
||||
pub use element::{
|
||||
CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
|
||||
};
|
||||
|
@ -82,7 +82,7 @@ use git::blame::GitBlame;
|
|||
use gpui::{
|
||||
div, impl_actions, point, prelude::*, pulsating_between, px, relative, size, Action, Animation,
|
||||
AnimationExt, AnyElement, App, AsyncWindowContext, AvailableSpace, Background, Bounds,
|
||||
ClipboardEntry, ClipboardItem, Context, DispatchPhase, Entity, EntityInputHandler,
|
||||
ClipboardEntry, ClipboardItem, Context, DispatchPhase, Edges, Entity, EntityInputHandler,
|
||||
EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId, FontWeight, Global,
|
||||
HighlightStyle, Hsla, KeyContext, Modifiers, MouseButton, MouseDownEvent, PaintQuad,
|
||||
ParentElement, Pixels, Render, SharedString, Size, Styled, StyledText, Subscription, Task,
|
||||
|
@ -113,6 +113,7 @@ use persistence::DB;
|
|||
pub use proposed_changes_editor::{
|
||||
ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
|
||||
};
|
||||
use smallvec::smallvec;
|
||||
use std::iter::Peekable;
|
||||
use task::{ResolvedTask, TaskTemplate, TaskVariables};
|
||||
|
||||
|
@ -5782,6 +5783,524 @@ impl Editor {
|
|||
.map(|menu| menu.origin())
|
||||
}
|
||||
|
||||
const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
|
||||
const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_edit_prediction_popover(
|
||||
&mut self,
|
||||
text_bounds: &Bounds<Pixels>,
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
editor_snapshot: &EditorSnapshot,
|
||||
visible_row_range: Range<DisplayRow>,
|
||||
scroll_top: f32,
|
||||
scroll_bottom: f32,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
line_height: Pixels,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
newest_selection_head: Option<DisplayPoint>,
|
||||
editor_width: Pixels,
|
||||
style: &EditorStyle,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<(AnyElement, gpui::Point<Pixels>)> {
|
||||
let active_inline_completion = self.active_inline_completion.as_ref()?;
|
||||
|
||||
if self.edit_prediction_visible_in_cursor_popover(true) {
|
||||
return None;
|
||||
}
|
||||
|
||||
match &active_inline_completion.completion {
|
||||
InlineCompletion::Move { target, .. } => {
|
||||
let target_display_point = target.to_display_point(editor_snapshot);
|
||||
|
||||
if self.edit_prediction_requires_modifier() {
|
||||
if !self.edit_prediction_preview_is_active() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.render_edit_prediction_modifier_jump_popover(
|
||||
text_bounds,
|
||||
content_origin,
|
||||
visible_row_range,
|
||||
line_layouts,
|
||||
line_height,
|
||||
scroll_pixel_position,
|
||||
newest_selection_head,
|
||||
target_display_point,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
self.render_edit_prediction_eager_jump_popover(
|
||||
text_bounds,
|
||||
content_origin,
|
||||
editor_snapshot,
|
||||
visible_row_range,
|
||||
scroll_top,
|
||||
scroll_bottom,
|
||||
line_height,
|
||||
scroll_pixel_position,
|
||||
target_display_point,
|
||||
editor_width,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}
|
||||
InlineCompletion::Edit {
|
||||
display_mode: EditDisplayMode::Inline,
|
||||
..
|
||||
} => None,
|
||||
InlineCompletion::Edit {
|
||||
display_mode: EditDisplayMode::TabAccept,
|
||||
edits,
|
||||
..
|
||||
} => {
|
||||
let range = &edits.first()?.0;
|
||||
let target_display_point = range.end.to_display_point(editor_snapshot);
|
||||
|
||||
self.render_edit_prediction_end_of_line_popover(
|
||||
"Accept",
|
||||
editor_snapshot,
|
||||
visible_row_range,
|
||||
target_display_point,
|
||||
line_height,
|
||||
scroll_pixel_position,
|
||||
content_origin,
|
||||
editor_width,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
InlineCompletion::Edit {
|
||||
edits,
|
||||
edit_preview,
|
||||
display_mode: EditDisplayMode::DiffPopover,
|
||||
snapshot,
|
||||
} => self.render_edit_prediction_diff_popover(
|
||||
text_bounds,
|
||||
content_origin,
|
||||
editor_snapshot,
|
||||
visible_row_range,
|
||||
line_layouts,
|
||||
line_height,
|
||||
scroll_pixel_position,
|
||||
newest_selection_head,
|
||||
editor_width,
|
||||
style,
|
||||
edits,
|
||||
edit_preview,
|
||||
snapshot,
|
||||
window,
|
||||
cx,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_edit_prediction_modifier_jump_popover(
|
||||
&mut self,
|
||||
text_bounds: &Bounds<Pixels>,
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
visible_row_range: Range<DisplayRow>,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
line_height: Pixels,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
newest_selection_head: Option<DisplayPoint>,
|
||||
target_display_point: DisplayPoint,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<(AnyElement, gpui::Point<Pixels>)> {
|
||||
let scrolled_content_origin =
|
||||
content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
|
||||
|
||||
const SCROLL_PADDING_Y: Pixels = px(12.);
|
||||
|
||||
if target_display_point.row() < visible_row_range.start {
|
||||
return self.render_edit_prediction_scroll_popover(
|
||||
|_| SCROLL_PADDING_Y,
|
||||
IconName::ArrowUp,
|
||||
visible_row_range,
|
||||
line_layouts,
|
||||
newest_selection_head,
|
||||
scrolled_content_origin,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
} else if target_display_point.row() >= visible_row_range.end {
|
||||
return self.render_edit_prediction_scroll_popover(
|
||||
|size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
|
||||
IconName::ArrowDown,
|
||||
visible_row_range,
|
||||
line_layouts,
|
||||
newest_selection_head,
|
||||
scrolled_content_origin,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
|
||||
const POLE_WIDTH: Pixels = px(2.);
|
||||
|
||||
let mut element = v_flex()
|
||||
.items_end()
|
||||
.child(
|
||||
self.render_edit_prediction_line_popover("Jump", None, window, cx)?
|
||||
.rounded_br(px(0.))
|
||||
.rounded_tr(px(0.))
|
||||
.border_r_2(),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(POLE_WIDTH)
|
||||
.bg(Editor::edit_prediction_callout_popover_border_color(cx))
|
||||
.h(line_height),
|
||||
)
|
||||
.into_any();
|
||||
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
|
||||
let line_layout =
|
||||
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 mut origin = scrolled_content_origin + point(target_x, target_y)
|
||||
- point(size.width - POLE_WIDTH, size.height - line_height);
|
||||
|
||||
origin.x = origin.x.max(content_origin.x);
|
||||
|
||||
element.prepaint_at(origin, window, cx);
|
||||
|
||||
Some((element, origin))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_edit_prediction_scroll_popover(
|
||||
&mut self,
|
||||
to_y: impl Fn(Size<Pixels>) -> Pixels,
|
||||
scroll_icon: IconName,
|
||||
visible_row_range: Range<DisplayRow>,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
newest_selection_head: Option<DisplayPoint>,
|
||||
scrolled_content_origin: gpui::Point<Pixels>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<(AnyElement, gpui::Point<Pixels>)> {
|
||||
let mut element = self
|
||||
.render_edit_prediction_line_popover("Scroll", Some(scroll_icon), 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);
|
||||
|
||||
let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
|
||||
|
||||
element.prepaint_at(origin, window, cx);
|
||||
Some((element, origin))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_edit_prediction_eager_jump_popover(
|
||||
&mut self,
|
||||
text_bounds: &Bounds<Pixels>,
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
editor_snapshot: &EditorSnapshot,
|
||||
visible_row_range: Range<DisplayRow>,
|
||||
scroll_top: f32,
|
||||
scroll_bottom: f32,
|
||||
line_height: Pixels,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
target_display_point: DisplayPoint,
|
||||
editor_width: Pixels,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<(AnyElement, gpui::Point<Pixels>)> {
|
||||
if target_display_point.row().as_f32() < scroll_top {
|
||||
let mut element = self
|
||||
.render_edit_prediction_line_popover(
|
||||
"Jump to Edit",
|
||||
Some(IconName::ArrowUp),
|
||||
window,
|
||||
cx,
|
||||
)?
|
||||
.into_any();
|
||||
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
let offset = point(
|
||||
(text_bounds.size.width - size.width) / 2.,
|
||||
Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
|
||||
);
|
||||
|
||||
let origin = text_bounds.origin + offset;
|
||||
element.prepaint_at(origin, window, cx);
|
||||
Some((element, origin))
|
||||
} else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
|
||||
let mut element = self
|
||||
.render_edit_prediction_line_popover(
|
||||
"Jump to Edit",
|
||||
Some(IconName::ArrowDown),
|
||||
window,
|
||||
cx,
|
||||
)?
|
||||
.into_any();
|
||||
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
let offset = point(
|
||||
(text_bounds.size.width - size.width) / 2.,
|
||||
text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
|
||||
);
|
||||
|
||||
let origin = text_bounds.origin + offset;
|
||||
element.prepaint_at(origin, window, cx);
|
||||
Some((element, origin))
|
||||
} else {
|
||||
self.render_edit_prediction_end_of_line_popover(
|
||||
"Jump to Edit",
|
||||
editor_snapshot,
|
||||
visible_row_range,
|
||||
target_display_point,
|
||||
line_height,
|
||||
scroll_pixel_position,
|
||||
content_origin,
|
||||
editor_width,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_edit_prediction_end_of_line_popover(
|
||||
self: &mut Editor,
|
||||
label: &'static str,
|
||||
editor_snapshot: &EditorSnapshot,
|
||||
visible_row_range: Range<DisplayRow>,
|
||||
target_display_point: DisplayPoint,
|
||||
line_height: Pixels,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
editor_width: Pixels,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<(AnyElement, gpui::Point<Pixels>)> {
|
||||
let target_line_end = DisplayPoint::new(
|
||||
target_display_point.row(),
|
||||
editor_snapshot.line_len(target_display_point.row()),
|
||||
);
|
||||
|
||||
let mut element = self
|
||||
.render_edit_prediction_line_popover(label, None, window, cx)?
|
||||
.into_any();
|
||||
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
|
||||
let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
|
||||
|
||||
let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
|
||||
let mut origin = start_point
|
||||
+ line_origin
|
||||
+ point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
|
||||
origin.x = origin.x.max(content_origin.x);
|
||||
|
||||
let max_x = content_origin.x + editor_width - size.width;
|
||||
|
||||
if origin.x > max_x {
|
||||
let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
|
||||
|
||||
let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
|
||||
origin.y += offset;
|
||||
IconName::ArrowUp
|
||||
} else {
|
||||
origin.y -= offset;
|
||||
IconName::ArrowDown
|
||||
};
|
||||
|
||||
element = self
|
||||
.render_edit_prediction_line_popover(label, Some(icon), window, cx)?
|
||||
.into_any();
|
||||
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
|
||||
origin.x = content_origin.x + editor_width - size.width - px(2.);
|
||||
}
|
||||
|
||||
element.prepaint_at(origin, window, cx);
|
||||
Some((element, origin))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_edit_prediction_diff_popover(
|
||||
self: &Editor,
|
||||
text_bounds: &Bounds<Pixels>,
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
editor_snapshot: &EditorSnapshot,
|
||||
visible_row_range: Range<DisplayRow>,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
line_height: Pixels,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
newest_selection_head: Option<DisplayPoint>,
|
||||
editor_width: Pixels,
|
||||
style: &EditorStyle,
|
||||
edits: &Vec<(Range<Anchor>, String)>,
|
||||
edit_preview: &Option<language::EditPreview>,
|
||||
snapshot: &language::BufferSnapshot,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<(AnyElement, gpui::Point<Pixels>)> {
|
||||
let edit_start = edits
|
||||
.first()
|
||||
.unwrap()
|
||||
.0
|
||||
.start
|
||||
.to_display_point(editor_snapshot);
|
||||
let edit_end = edits
|
||||
.last()
|
||||
.unwrap()
|
||||
.0
|
||||
.end
|
||||
.to_display_point(editor_snapshot);
|
||||
|
||||
let is_visible = visible_row_range.contains(&edit_start.row())
|
||||
|| visible_row_range.contains(&edit_end.row());
|
||||
if !is_visible {
|
||||
return None;
|
||||
}
|
||||
|
||||
let highlighted_edits =
|
||||
crate::inline_completion_edit_text(&snapshot, edits, edit_preview.as_ref()?, false, cx);
|
||||
|
||||
let styled_text = highlighted_edits.to_styled_text(&style.text);
|
||||
let line_count = highlighted_edits.text.lines().count();
|
||||
|
||||
const BORDER_WIDTH: Pixels = px(1.);
|
||||
|
||||
let mut element = h_flex()
|
||||
.items_start()
|
||||
.child(
|
||||
h_flex()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.border(BORDER_WIDTH)
|
||||
.shadow_sm()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_l_lg()
|
||||
.when(line_count > 1, |el| el.rounded_br_lg())
|
||||
.pr_1()
|
||||
.child(styled_text),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.h(line_height + BORDER_WIDTH * px(2.))
|
||||
.px_1p5()
|
||||
.gap_1()
|
||||
// Workaround: For some reason, there's a gap if we don't do this
|
||||
.ml(-BORDER_WIDTH)
|
||||
.shadow(smallvec![gpui::BoxShadow {
|
||||
color: gpui::black().opacity(0.05),
|
||||
offset: point(px(1.), px(1.)),
|
||||
blur_radius: px(2.),
|
||||
spread_radius: px(0.),
|
||||
}])
|
||||
.bg(Editor::edit_prediction_line_popover_bg_color(cx))
|
||||
.border(BORDER_WIDTH)
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_r_lg()
|
||||
.children(self.render_edit_prediction_accept_keybind(window, cx)),
|
||||
)
|
||||
.into_any();
|
||||
|
||||
let longest_row =
|
||||
editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
|
||||
let longest_line_width = if visible_row_range.contains(&longest_row) {
|
||||
line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
|
||||
} else {
|
||||
layout_line(
|
||||
longest_row,
|
||||
editor_snapshot,
|
||||
style,
|
||||
editor_width,
|
||||
|_| false,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.width
|
||||
};
|
||||
|
||||
let viewport_bounds =
|
||||
Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
|
||||
right: -EditorElement::SCROLLBAR_WIDTH,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let x_after_longest =
|
||||
text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X
|
||||
- scroll_pixel_position.x;
|
||||
|
||||
let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
|
||||
// Fully visible if it can be displayed within the window (allow overlapping other
|
||||
// panes). However, this is only allowed if the popover starts within text_bounds.
|
||||
let can_position_to_the_right = x_after_longest < text_bounds.right()
|
||||
&& x_after_longest + element_bounds.width < viewport_bounds.right();
|
||||
|
||||
let mut origin = if can_position_to_the_right {
|
||||
point(
|
||||
x_after_longest,
|
||||
text_bounds.origin.y + edit_start.row().as_f32() * line_height
|
||||
- scroll_pixel_position.y,
|
||||
)
|
||||
} else {
|
||||
let cursor_row = newest_selection_head.map(|head| head.row());
|
||||
let above_edit = edit_start
|
||||
.row()
|
||||
.0
|
||||
.checked_sub(line_count as u32)
|
||||
.map(DisplayRow);
|
||||
let below_edit = Some(edit_end.row() + 1);
|
||||
let above_cursor =
|
||||
cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
|
||||
let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
|
||||
|
||||
// Place the edit popover adjacent to the edit if there is a location
|
||||
// available that is onscreen and does not obscure the cursor. Otherwise,
|
||||
// place it adjacent to the cursor.
|
||||
let row_target = [above_edit, below_edit, above_cursor, below_cursor]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.find(|&start_row| {
|
||||
let end_row = start_row + line_count as u32;
|
||||
visible_row_range.contains(&start_row)
|
||||
&& visible_row_range.contains(&end_row)
|
||||
&& cursor_row.map_or(true, |cursor_row| {
|
||||
!((start_row..end_row).contains(&cursor_row))
|
||||
})
|
||||
})?;
|
||||
|
||||
content_origin
|
||||
+ point(
|
||||
-scroll_pixel_position.x,
|
||||
row_target.as_f32() * line_height - scroll_pixel_position.y,
|
||||
)
|
||||
};
|
||||
|
||||
origin.x -= BORDER_WIDTH;
|
||||
|
||||
window.defer_draw(element, origin, 1);
|
||||
|
||||
// Do not return an element, since it will already be drawn due to defer_draw.
|
||||
None
|
||||
}
|
||||
|
||||
fn edit_prediction_cursor_popover_height(&self) -> Pixels {
|
||||
px(30.)
|
||||
}
|
||||
|
|
|
@ -3711,451 +3711,6 @@ impl EditorElement {
|
|||
}
|
||||
}
|
||||
|
||||
const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = Pixels(24.);
|
||||
const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = Pixels(2.);
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_edit_prediction_popover(
|
||||
&self,
|
||||
text_bounds: &Bounds<Pixels>,
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
editor_snapshot: &EditorSnapshot,
|
||||
visible_row_range: Range<DisplayRow>,
|
||||
scroll_top: f32,
|
||||
scroll_bottom: f32,
|
||||
line_layouts: &[LineWithInvisibles],
|
||||
line_height: Pixels,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
newest_selection_head: Option<DisplayPoint>,
|
||||
editor_width: Pixels,
|
||||
style: &EditorStyle,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<(AnyElement, gpui::Point<Pixels>)> {
|
||||
let editor = self.editor.read(cx);
|
||||
let active_inline_completion = editor.active_inline_completion.as_ref()?;
|
||||
|
||||
if editor.edit_prediction_visible_in_cursor_popover(true) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Adjust text origin for horizontal scrolling (in some cases here)
|
||||
let start_point = content_origin - gpui::Point::new(scroll_pixel_position.x, Pixels(0.0));
|
||||
|
||||
// Clamp left offset after extreme scrollings
|
||||
let clamp_start = |point: gpui::Point<Pixels>| gpui::Point {
|
||||
x: point.x.max(content_origin.x),
|
||||
y: point.y,
|
||||
};
|
||||
|
||||
match &active_inline_completion.completion {
|
||||
InlineCompletion::Move { target, .. } => {
|
||||
let target_display_point = target.to_display_point(editor_snapshot);
|
||||
|
||||
if editor.edit_prediction_requires_modifier() {
|
||||
if !editor.edit_prediction_preview_is_active() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if target_display_point.row() < visible_row_range.start {
|
||||
let mut element = editor
|
||||
.render_edit_prediction_line_popover(
|
||||
"Scroll",
|
||||
Some(IconName::ArrowUp),
|
||||
window,
|
||||
cx,
|
||||
)?
|
||||
.into_any();
|
||||
|
||||
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(12.);
|
||||
|
||||
let origin = start_point + point(cursor_character_x, PADDING_Y);
|
||||
|
||||
element.prepaint_at(origin, window, cx);
|
||||
return Some((element, origin));
|
||||
} else if target_display_point.row() >= visible_row_range.end {
|
||||
let mut element = editor
|
||||
.render_edit_prediction_line_popover(
|
||||
"Scroll",
|
||||
Some(IconName::ArrowDown),
|
||||
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(12.);
|
||||
|
||||
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, origin));
|
||||
} else {
|
||||
const POLE_WIDTH: Pixels = px(2.);
|
||||
|
||||
let mut element = v_flex()
|
||||
.items_end()
|
||||
.child(
|
||||
editor
|
||||
.render_edit_prediction_line_popover("Jump", None, window, cx)?
|
||||
.rounded_br(px(0.))
|
||||
.rounded_tr(px(0.))
|
||||
.border_r_2(),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.w(POLE_WIDTH)
|
||||
.bg(Editor::edit_prediction_callout_popover_border_color(cx))
|
||||
.h(line_height),
|
||||
)
|
||||
.into_any();
|
||||
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
|
||||
let line_layout =
|
||||
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, origin));
|
||||
}
|
||||
}
|
||||
|
||||
if target_display_point.row().as_f32() < scroll_top {
|
||||
let mut element = editor
|
||||
.render_edit_prediction_line_popover(
|
||||
"Jump to Edit",
|
||||
Some(IconName::ArrowUp),
|
||||
window,
|
||||
cx,
|
||||
)?
|
||||
.into_any();
|
||||
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
let offset = point(
|
||||
(text_bounds.size.width - size.width) / 2.,
|
||||
Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
|
||||
);
|
||||
|
||||
let origin = text_bounds.origin + offset;
|
||||
element.prepaint_at(origin, window, cx);
|
||||
Some((element, origin))
|
||||
} else if (target_display_point.row().as_f32() + 1.) > scroll_bottom {
|
||||
let mut element = editor
|
||||
.render_edit_prediction_line_popover(
|
||||
"Jump to Edit",
|
||||
Some(IconName::ArrowDown),
|
||||
window,
|
||||
cx,
|
||||
)?
|
||||
.into_any();
|
||||
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
let offset = point(
|
||||
(text_bounds.size.width - size.width) / 2.,
|
||||
text_bounds.size.height
|
||||
- size.height
|
||||
- Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
|
||||
);
|
||||
|
||||
let origin = text_bounds.origin + offset;
|
||||
element.prepaint_at(origin, window, cx);
|
||||
Some((element, origin))
|
||||
} else {
|
||||
return self.layout_edit_prediction_end_of_line_popover(
|
||||
"Jump to Edit",
|
||||
editor_snapshot,
|
||||
visible_row_range,
|
||||
target_display_point,
|
||||
line_height,
|
||||
scroll_pixel_position,
|
||||
content_origin,
|
||||
editor_width,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
InlineCompletion::Edit {
|
||||
edits,
|
||||
edit_preview,
|
||||
display_mode,
|
||||
snapshot,
|
||||
} => {
|
||||
if self.editor.read(cx).has_visible_completions_menu() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let edit_start = edits
|
||||
.first()
|
||||
.unwrap()
|
||||
.0
|
||||
.start
|
||||
.to_display_point(editor_snapshot);
|
||||
let edit_end = edits
|
||||
.last()
|
||||
.unwrap()
|
||||
.0
|
||||
.end
|
||||
.to_display_point(editor_snapshot);
|
||||
|
||||
let is_visible = visible_row_range.contains(&edit_start.row())
|
||||
|| visible_row_range.contains(&edit_end.row());
|
||||
if !is_visible {
|
||||
return None;
|
||||
}
|
||||
|
||||
match display_mode {
|
||||
EditDisplayMode::TabAccept => {
|
||||
let range = &edits.first()?.0;
|
||||
let target_display_point = range.end.to_display_point(editor_snapshot);
|
||||
|
||||
return self.layout_edit_prediction_end_of_line_popover(
|
||||
"Accept",
|
||||
editor_snapshot,
|
||||
visible_row_range,
|
||||
target_display_point,
|
||||
line_height,
|
||||
scroll_pixel_position,
|
||||
content_origin,
|
||||
editor_width,
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
EditDisplayMode::Inline => return None,
|
||||
EditDisplayMode::DiffPopover => {}
|
||||
}
|
||||
|
||||
let highlighted_edits = crate::inline_completion_edit_text(
|
||||
&snapshot,
|
||||
edits,
|
||||
edit_preview.as_ref()?,
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
|
||||
let styled_text = highlighted_edits.to_styled_text(&style.text);
|
||||
let line_count = highlighted_edits.text.lines().count();
|
||||
|
||||
const BORDER_WIDTH: Pixels = px(1.);
|
||||
|
||||
let mut element = h_flex()
|
||||
.items_start()
|
||||
.child(
|
||||
h_flex()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.border(BORDER_WIDTH)
|
||||
.shadow_sm()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_l_lg()
|
||||
.when(line_count > 1, |el| el.rounded_br_lg())
|
||||
.pr_1()
|
||||
.child(styled_text),
|
||||
)
|
||||
.child(
|
||||
h_flex()
|
||||
.h(line_height + BORDER_WIDTH * px(2.))
|
||||
.px_1p5()
|
||||
.gap_1()
|
||||
// Workaround: For some reason, there's a gap if we don't do this
|
||||
.ml(-BORDER_WIDTH)
|
||||
.shadow(smallvec![gpui::BoxShadow {
|
||||
color: gpui::black().opacity(0.05),
|
||||
offset: point(px(1.), px(1.)),
|
||||
blur_radius: px(2.),
|
||||
spread_radius: px(0.),
|
||||
}])
|
||||
.bg(Editor::edit_prediction_line_popover_bg_color(cx))
|
||||
.border(BORDER_WIDTH)
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_r_lg()
|
||||
.children(editor.render_edit_prediction_accept_keybind(window, cx)),
|
||||
)
|
||||
.into_any();
|
||||
|
||||
let longest_row =
|
||||
editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
|
||||
let longest_line_width = if visible_row_range.contains(&longest_row) {
|
||||
line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
|
||||
} else {
|
||||
layout_line(
|
||||
longest_row,
|
||||
editor_snapshot,
|
||||
style,
|
||||
editor_width,
|
||||
|_| false,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.width
|
||||
};
|
||||
|
||||
let viewport_bounds = Bounds::new(Default::default(), window.viewport_size())
|
||||
.extend(Edges {
|
||||
right: -Self::SCROLLBAR_WIDTH,
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let x_after_longest = text_bounds.origin.x
|
||||
+ longest_line_width
|
||||
+ Self::EDIT_PREDICTION_POPOVER_PADDING_X
|
||||
- scroll_pixel_position.x;
|
||||
|
||||
let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
|
||||
// Fully visible if it can be displayed within the window (allow overlapping other
|
||||
// panes). However, this is only allowed if the popover starts within text_bounds.
|
||||
let can_position_to_the_right = x_after_longest < text_bounds.right()
|
||||
&& x_after_longest + element_bounds.width < viewport_bounds.right();
|
||||
|
||||
let mut origin = if can_position_to_the_right {
|
||||
point(
|
||||
x_after_longest,
|
||||
text_bounds.origin.y + edit_start.row().as_f32() * line_height
|
||||
- scroll_pixel_position.y,
|
||||
)
|
||||
} else {
|
||||
let cursor_row = newest_selection_head.map(|head| head.row());
|
||||
let above_edit = edit_start
|
||||
.row()
|
||||
.0
|
||||
.checked_sub(line_count as u32)
|
||||
.map(DisplayRow);
|
||||
let below_edit = Some(edit_end.row() + 1);
|
||||
let above_cursor = cursor_row
|
||||
.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
|
||||
let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
|
||||
|
||||
// Place the edit popover adjacent to the edit if there is a location
|
||||
// available that is onscreen and does not obscure the cursor. Otherwise,
|
||||
// place it adjacent to the cursor.
|
||||
let row_target = [above_edit, below_edit, above_cursor, below_cursor]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.find(|&start_row| {
|
||||
let end_row = start_row + line_count as u32;
|
||||
visible_row_range.contains(&start_row)
|
||||
&& visible_row_range.contains(&end_row)
|
||||
&& cursor_row.map_or(true, |cursor_row| {
|
||||
!((start_row..end_row).contains(&cursor_row))
|
||||
})
|
||||
})?;
|
||||
|
||||
content_origin
|
||||
+ point(
|
||||
-scroll_pixel_position.x,
|
||||
row_target.as_f32() * line_height - scroll_pixel_position.y,
|
||||
)
|
||||
};
|
||||
|
||||
origin.x -= BORDER_WIDTH;
|
||||
|
||||
window.defer_draw(element, origin, 1);
|
||||
|
||||
// Do not return an element, since it will already be drawn due to defer_draw.
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn layout_edit_prediction_end_of_line_popover(
|
||||
&self,
|
||||
label: &'static str,
|
||||
editor_snapshot: &EditorSnapshot,
|
||||
visible_row_range: Range<DisplayRow>,
|
||||
target_display_point: DisplayPoint,
|
||||
line_height: Pixels,
|
||||
scroll_pixel_position: gpui::Point<Pixels>,
|
||||
content_origin: gpui::Point<Pixels>,
|
||||
editor_width: Pixels,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<(AnyElement, gpui::Point<Pixels>)> {
|
||||
let target_line_end = DisplayPoint::new(
|
||||
target_display_point.row(),
|
||||
editor_snapshot.line_len(target_display_point.row()),
|
||||
);
|
||||
|
||||
let mut element = self
|
||||
.editor
|
||||
.read(cx)
|
||||
.render_edit_prediction_line_popover(label, None, window, cx)?
|
||||
.into_any();
|
||||
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
|
||||
let line_origin = self.editor.update(cx, |editor, _cx| {
|
||||
editor.display_to_pixel_point(target_line_end, editor_snapshot, window)
|
||||
})?;
|
||||
|
||||
let start_point = content_origin - point(scroll_pixel_position.x, Pixels::ZERO);
|
||||
let mut origin = start_point
|
||||
+ line_origin
|
||||
+ point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
|
||||
origin.x = origin.x.max(content_origin.x);
|
||||
|
||||
let max_x = content_origin.x + editor_width - size.width;
|
||||
|
||||
if origin.x > max_x {
|
||||
let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
|
||||
|
||||
let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
|
||||
origin.y += offset;
|
||||
IconName::ArrowUp
|
||||
} else {
|
||||
origin.y -= offset;
|
||||
IconName::ArrowDown
|
||||
};
|
||||
|
||||
element = self
|
||||
.editor
|
||||
.read(cx)
|
||||
.render_edit_prediction_line_popover(label, Some(icon), window, cx)?
|
||||
.into_any();
|
||||
|
||||
let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
|
||||
|
||||
origin.x = content_origin.x + editor_width - size.width - px(2.);
|
||||
}
|
||||
|
||||
element.prepaint_at(origin, window, cx);
|
||||
Some((element, origin))
|
||||
}
|
||||
|
||||
fn layout_mouse_context_menu(
|
||||
&self,
|
||||
editor_snapshot: &EditorSnapshot,
|
||||
|
@ -6298,7 +5853,7 @@ pub(crate) struct LineWithInvisibles {
|
|||
fragments: SmallVec<[LineFragment; 1]>,
|
||||
invisibles: Vec<Invisible>,
|
||||
len: usize,
|
||||
width: Pixels,
|
||||
pub(crate) width: Pixels,
|
||||
font_size: Pixels,
|
||||
}
|
||||
|
||||
|
@ -7446,22 +7001,25 @@ impl Element for EditorElement {
|
|||
});
|
||||
|
||||
let (inline_completion_popover, inline_completion_popover_origin) = self
|
||||
.layout_edit_prediction_popover(
|
||||
&text_hitbox.bounds,
|
||||
content_origin,
|
||||
&snapshot,
|
||||
start_row..end_row,
|
||||
scroll_position.y,
|
||||
scroll_position.y + height_in_lines,
|
||||
&line_layouts,
|
||||
line_height,
|
||||
scroll_pixel_position,
|
||||
newest_selection_head,
|
||||
editor_width,
|
||||
&style,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
.editor
|
||||
.update(cx, |editor, cx| {
|
||||
editor.render_edit_prediction_popover(
|
||||
&text_hitbox.bounds,
|
||||
content_origin,
|
||||
&snapshot,
|
||||
start_row..end_row,
|
||||
scroll_position.y,
|
||||
scroll_position.y + height_in_lines,
|
||||
&line_layouts,
|
||||
line_height,
|
||||
scroll_pixel_position,
|
||||
newest_selection_head,
|
||||
editor_width,
|
||||
&style,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.unzip();
|
||||
|
||||
let mut inline_diagnostics = self.layout_inline_diagnostics(
|
||||
|
@ -8267,7 +7825,7 @@ struct BlockLayout {
|
|||
style: BlockStyle,
|
||||
}
|
||||
|
||||
fn layout_line(
|
||||
pub fn layout_line(
|
||||
row: DisplayRow,
|
||||
snapshot: &EditorSnapshot,
|
||||
style: &EditorStyle,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue