edit predictions: Polish up ⌥ preview experience (#24380)
- Do not accept with just `tab` in `when_holding_modifer` mode - Fix fake cursor for jumps when destination row is outside viewport - Use current preview state for deciding whether to show modifiers in popovers - Stay in preview state if ⌥ isn't released after accepting a jump Release Notes: - N/A
This commit is contained in:
parent
c24f22cd14
commit
13089d7ec6
4 changed files with 110 additions and 125 deletions
|
@ -510,7 +510,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && inline_completion && !showing_completions",
|
"context": "Editor && inline_completion && !inline_completion_requires_modifier",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"tab": "editor::AcceptInlineCompletion"
|
"tab": "editor::AcceptInlineCompletion"
|
||||||
|
|
|
@ -587,7 +587,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"context": "Editor && inline_completion && !showing_completions",
|
"context": "Editor && inline_completion && !inline_completion_requires_modifier",
|
||||||
"use_key_equivalents": true,
|
"use_key_equivalents": true,
|
||||||
"bindings": {
|
"bindings": {
|
||||||
"tab": "editor::AcceptInlineCompletion"
|
"tab": "editor::AcceptInlineCompletion"
|
||||||
|
|
|
@ -82,8 +82,8 @@ use gpui::{
|
||||||
Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId,
|
Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent, Focusable, FontId,
|
||||||
FontWeight, Global, HighlightStyle, Hsla, InteractiveText, KeyContext, Modifiers, MouseButton,
|
FontWeight, Global, HighlightStyle, Hsla, InteractiveText, KeyContext, Modifiers, MouseButton,
|
||||||
MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, SharedString, Size, Styled,
|
MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, SharedString, Size, Styled,
|
||||||
StyledText, Subscription, Task, TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle,
|
StyledText, Subscription, Task, TextRun, TextStyle, TextStyleRefinement, UTF16Selection,
|
||||||
UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
|
UnderlineStyle, UniformListScrollHandle, 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};
|
||||||
|
@ -468,7 +468,7 @@ pub fn make_suggestion_styles(cx: &mut App) -> InlineCompletionStyles {
|
||||||
type CompletionId = usize;
|
type CompletionId = usize;
|
||||||
|
|
||||||
pub(crate) enum EditDisplayMode {
|
pub(crate) enum EditDisplayMode {
|
||||||
TabAccept(bool),
|
TabAccept,
|
||||||
DiffPopover,
|
DiffPopover,
|
||||||
Inline,
|
Inline,
|
||||||
}
|
}
|
||||||
|
@ -493,15 +493,6 @@ struct InlineCompletionState {
|
||||||
invalidation_range: Range<Anchor>,
|
invalidation_range: Range<Anchor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InlineCompletionState {
|
|
||||||
pub fn is_move(&self) -> bool {
|
|
||||||
match &self.completion {
|
|
||||||
InlineCompletion::Move { .. } => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum InlineCompletionHighlight {}
|
enum InlineCompletionHighlight {}
|
||||||
|
|
||||||
pub enum MenuInlineCompletionsPolicy {
|
pub enum MenuInlineCompletionsPolicy {
|
||||||
|
@ -1499,10 +1490,14 @@ impl Editor {
|
||||||
if self.pending_rename.is_some() {
|
if self.pending_rename.is_some() {
|
||||||
key_context.add("renaming");
|
key_context.add("renaming");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut showing_completions = false;
|
||||||
|
|
||||||
match self.context_menu.borrow().as_ref() {
|
match self.context_menu.borrow().as_ref() {
|
||||||
Some(CodeContextMenu::Completions(_)) => {
|
Some(CodeContextMenu::Completions(_)) => {
|
||||||
key_context.add("menu");
|
key_context.add("menu");
|
||||||
key_context.add("showing_completions");
|
key_context.add("showing_completions");
|
||||||
|
showing_completions = true;
|
||||||
}
|
}
|
||||||
Some(CodeContextMenu::CodeActions(_)) => {
|
Some(CodeContextMenu::CodeActions(_)) => {
|
||||||
key_context.add("menu");
|
key_context.add("menu");
|
||||||
|
@ -1532,6 +1527,10 @@ impl Editor {
|
||||||
if self.has_active_inline_completion() {
|
if self.has_active_inline_completion() {
|
||||||
key_context.add("copilot_suggestion");
|
key_context.add("copilot_suggestion");
|
||||||
key_context.add("inline_completion");
|
key_context.add("inline_completion");
|
||||||
|
|
||||||
|
if showing_completions || self.inline_completion_requires_modifier(cx) {
|
||||||
|
key_context.add("inline_completion_requires_modifier");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.selection_mark_mode {
|
if self.selection_mark_mode {
|
||||||
|
@ -4664,7 +4663,7 @@ impl Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inline_completion_preview_mode(&self, cx: &App) -> language::InlineCompletionPreviewMode {
|
fn inline_completion_requires_modifier(&self, cx: &App) -> bool {
|
||||||
let cursor = self.selections.newest_anchor().head();
|
let cursor = self.selections.newest_anchor().head();
|
||||||
|
|
||||||
self.buffer
|
self.buffer
|
||||||
|
@ -4672,8 +4671,9 @@ impl Editor {
|
||||||
.text_anchor_for_position(cursor, cx)
|
.text_anchor_for_position(cursor, cx)
|
||||||
.map(|(buffer, _)| {
|
.map(|(buffer, _)| {
|
||||||
all_language_settings(buffer.read(cx).file(), cx).inline_completions_preview_mode()
|
all_language_settings(buffer.read(cx).file(), cx).inline_completions_preview_mode()
|
||||||
|
== InlineCompletionPreviewMode::WhenHoldingModifier
|
||||||
})
|
})
|
||||||
.unwrap_or_default()
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn should_show_inline_completions_in_buffer(
|
fn should_show_inline_completions_in_buffer(
|
||||||
|
@ -5042,9 +5042,7 @@ impl Editor {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
has_completion
|
has_completion && self.inline_completion_requires_modifier(cx)
|
||||||
&& self.inline_completion_preview_mode(cx)
|
|
||||||
== InlineCompletionPreviewMode::WhenHoldingModifier
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_inline_completion_preview(
|
fn update_inline_completion_preview(
|
||||||
|
@ -5053,23 +5051,13 @@ impl Editor {
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
// Moves jump directly without a preview step
|
|
||||||
if self
|
|
||||||
.active_inline_completion
|
|
||||||
.as_ref()
|
|
||||||
.map_or(true, |c| c.is_move())
|
|
||||||
{
|
|
||||||
self.previewing_inline_completion = false;
|
|
||||||
cx.notify();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.show_inline_completions_in_menu(cx) {
|
if !self.show_inline_completions_in_menu(cx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.previewing_inline_completion = modifiers.alt;
|
self.previewing_inline_completion = modifiers.alt;
|
||||||
self.update_visible_inline_completion(window, cx);
|
self.update_visible_inline_completion(window, cx);
|
||||||
|
cx.notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_visible_inline_completion(
|
fn update_visible_inline_completion(
|
||||||
|
@ -5198,7 +5186,7 @@ impl Editor {
|
||||||
|
|
||||||
let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
|
let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
|
||||||
if provider.show_tab_accept_marker() {
|
if provider.show_tab_accept_marker() {
|
||||||
EditDisplayMode::TabAccept(self.previewing_inline_completion)
|
EditDisplayMode::TabAccept
|
||||||
} else {
|
} else {
|
||||||
EditDisplayMode::Inline
|
EditDisplayMode::Inline
|
||||||
}
|
}
|
||||||
|
@ -5494,8 +5482,6 @@ impl Editor {
|
||||||
min_width: Pixels,
|
min_width: Pixels,
|
||||||
max_width: Pixels,
|
max_width: Pixels,
|
||||||
cursor_point: Point,
|
cursor_point: Point,
|
||||||
start_row: DisplayRow,
|
|
||||||
line_layouts: &[LineWithInvisibles],
|
|
||||||
style: &EditorStyle,
|
style: &EditorStyle,
|
||||||
accept_keystroke: &gpui::Keystroke,
|
accept_keystroke: &gpui::Keystroke,
|
||||||
window: &Window,
|
window: &Window,
|
||||||
|
@ -5556,9 +5542,8 @@ impl Editor {
|
||||||
Some(completion) => self.render_edit_prediction_cursor_popover_preview(
|
Some(completion) => self.render_edit_prediction_cursor_popover_preview(
|
||||||
completion,
|
completion,
|
||||||
cursor_point,
|
cursor_point,
|
||||||
start_row,
|
|
||||||
line_layouts,
|
|
||||||
style,
|
style,
|
||||||
|
window,
|
||||||
cx,
|
cx,
|
||||||
)?,
|
)?,
|
||||||
|
|
||||||
|
@ -5566,9 +5551,8 @@ impl Editor {
|
||||||
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,
|
||||||
start_row,
|
|
||||||
line_layouts,
|
|
||||||
style,
|
style,
|
||||||
|
window,
|
||||||
cx,
|
cx,
|
||||||
)?,
|
)?,
|
||||||
|
|
||||||
|
@ -5599,19 +5583,6 @@ impl Editor {
|
||||||
|
|
||||||
let has_completion = self.active_inline_completion.is_some();
|
let has_completion = self.active_inline_completion.is_some();
|
||||||
|
|
||||||
let is_move = self
|
|
||||||
.active_inline_completion
|
|
||||||
.as_ref()
|
|
||||||
.map_or(false, |c| c.is_move());
|
|
||||||
|
|
||||||
let modifier_color = if !has_completion {
|
|
||||||
Color::Muted
|
|
||||||
} else if window.modifiers() == accept_keystroke.modifiers {
|
|
||||||
Color::Accent
|
|
||||||
} else {
|
|
||||||
Color::Default
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
h_flex()
|
h_flex()
|
||||||
.h(self.edit_prediction_cursor_popover_height())
|
.h(self.edit_prediction_cursor_popover_height())
|
||||||
|
@ -5631,18 +5602,15 @@ impl Editor {
|
||||||
ui::render_modifiers(
|
ui::render_modifiers(
|
||||||
&accept_keystroke.modifiers,
|
&accept_keystroke.modifiers,
|
||||||
PlatformStyle::platform(),
|
PlatformStyle::platform(),
|
||||||
Some(modifier_color),
|
Some(if !has_completion {
|
||||||
!is_move,
|
Color::Muted
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
}),
|
||||||
|
true,
|
||||||
),
|
),
|
||||||
))
|
))
|
||||||
.child(if is_move {
|
.child(Label::new("Preview").into_any_element())
|
||||||
div()
|
|
||||||
.child(ui::Key::new(&accept_keystroke.key, None))
|
|
||||||
.font(buffer_font.clone())
|
|
||||||
.into_any()
|
|
||||||
} else {
|
|
||||||
Label::new("Preview").into_any_element()
|
|
||||||
})
|
|
||||||
.opacity(if has_completion { 1.0 } else { 0.4 }),
|
.opacity(if has_completion { 1.0 } else { 0.4 }),
|
||||||
)
|
)
|
||||||
.into_any(),
|
.into_any(),
|
||||||
|
@ -5653,9 +5621,8 @@ impl Editor {
|
||||||
&self,
|
&self,
|
||||||
completion: &InlineCompletionState,
|
completion: &InlineCompletionState,
|
||||||
cursor_point: Point,
|
cursor_point: Point,
|
||||||
start_row: DisplayRow,
|
|
||||||
line_layouts: &[LineWithInvisibles],
|
|
||||||
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 _;
|
||||||
|
@ -5732,6 +5699,7 @@ impl Editor {
|
||||||
|
|
||||||
let preview = h_flex()
|
let preview = h_flex()
|
||||||
.gap_1()
|
.gap_1()
|
||||||
|
.min_w_16()
|
||||||
.child(styled_text)
|
.child(styled_text)
|
||||||
.when(len_total > first_line_len, |parent| parent.child("…"));
|
.when(len_total > first_line_len, |parent| parent.child("…"));
|
||||||
|
|
||||||
|
@ -5764,18 +5732,46 @@ impl Editor {
|
||||||
None,
|
None,
|
||||||
&style.syntax,
|
&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 cursor_color = self.current_user_player_color(cx).cursor;
|
||||||
|
|
||||||
let start_point = range_around_target.start.to_point(&snapshot);
|
let start_point = range_around_target.start.to_point(&snapshot);
|
||||||
let end_point = range_around_target.end.to_point(&snapshot);
|
let end_point = range_around_target.end.to_point(&snapshot);
|
||||||
let target_point = target.text_anchor.to_point(&snapshot);
|
let target_point = target.text_anchor.to_point(&snapshot);
|
||||||
|
|
||||||
let cursor_relative_position = line_layouts
|
let styled_text = highlighted_text.to_styled_text(&style.text);
|
||||||
.get(start_point.row.saturating_sub(start_row.0) as usize)
|
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| {
|
.map(|line| {
|
||||||
let start_column_x = line.x_for_index(start_point.column as usize);
|
line.x_for_index(
|
||||||
let target_column_x = line.x_for_index(target_point.column as usize);
|
target_point.column.saturating_sub(start_point.column) as usize
|
||||||
target_column_x - start_column_x
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let fade_before = start_point.column > 0;
|
let fade_before = start_point.column > 0;
|
||||||
|
@ -5783,56 +5779,40 @@ impl Editor {
|
||||||
|
|
||||||
let background = cx.theme().colors().elevated_surface_background;
|
let background = cx.theme().colors().elevated_surface_background;
|
||||||
|
|
||||||
Some(
|
let preview = h_flex()
|
||||||
h_flex()
|
.relative()
|
||||||
.gap_3()
|
.child(styled_text)
|
||||||
.flex_1()
|
.when(fade_before, |parent| {
|
||||||
.child(render_relative_row_jump(
|
parent.child(div().absolute().top_0().left_0().w_4().h_full().bg(
|
||||||
"Jump ",
|
linear_gradient(
|
||||||
cursor_point.row,
|
90.,
|
||||||
target.text_anchor.to_point(&snapshot).row,
|
linear_color_stop(background, 0.),
|
||||||
|
linear_color_stop(background.opacity(0.), 1.),
|
||||||
|
),
|
||||||
))
|
))
|
||||||
.when(!highlighted_text.text.is_empty(), |parent| {
|
})
|
||||||
parent.child(
|
.when(fade_after, |parent| {
|
||||||
h_flex()
|
parent.child(div().absolute().top_0().right_0().w_4().h_full().bg(
|
||||||
.relative()
|
linear_gradient(
|
||||||
.child(highlighted_text.to_styled_text(&style.text))
|
-90.,
|
||||||
.when(fade_before, |parent| {
|
linear_color_stop(background, 0.),
|
||||||
parent.child(
|
linear_color_stop(background.opacity(0.), 1.),
|
||||||
div().absolute().top_0().left_0().w_4().h_full().bg(
|
),
|
||||||
linear_gradient(
|
))
|
||||||
90.,
|
})
|
||||||
linear_color_stop(background, 0.),
|
.when_some(cursor_relative_position, |parent, position| {
|
||||||
linear_color_stop(background.opacity(0.), 1.),
|
parent.child(
|
||||||
),
|
div()
|
||||||
),
|
.w(px(2.))
|
||||||
)
|
.h_full()
|
||||||
})
|
.bg(cursor_color)
|
||||||
.when(fade_after, |parent| {
|
.absolute()
|
||||||
parent.child(
|
.top_0()
|
||||||
div().absolute().top_0().right_0().w_4().h_full().bg(
|
.left(position),
|
||||||
linear_gradient(
|
)
|
||||||
-90.,
|
});
|
||||||
linear_color_stop(background, 0.),
|
|
||||||
linear_color_stop(background.opacity(0.), 1.),
|
Some(base.child(preview))
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.when_some(cursor_relative_position, |parent, position| {
|
|
||||||
parent.child(
|
|
||||||
div()
|
|
||||||
.w(px(2.))
|
|
||||||
.h_full()
|
|
||||||
.bg(cursor_color)
|
|
||||||
.absolute()
|
|
||||||
.top_0()
|
|
||||||
.left(position),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1653,7 +1653,7 @@ impl EditorElement {
|
||||||
if let Some(inline_completion) = editor.active_inline_completion.as_ref() {
|
if let Some(inline_completion) = editor.active_inline_completion.as_ref() {
|
||||||
match &inline_completion.completion {
|
match &inline_completion.completion {
|
||||||
InlineCompletion::Edit {
|
InlineCompletion::Edit {
|
||||||
display_mode: EditDisplayMode::TabAccept(_),
|
display_mode: EditDisplayMode::TabAccept,
|
||||||
..
|
..
|
||||||
} => padding += INLINE_ACCEPT_SUGGESTION_EM_WIDTHS,
|
} => padding += INLINE_ACCEPT_SUGGESTION_EM_WIDTHS,
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -3238,8 +3238,6 @@ impl EditorElement {
|
||||||
min_width,
|
min_width,
|
||||||
max_width,
|
max_width,
|
||||||
cursor_point,
|
cursor_point,
|
||||||
start_row,
|
|
||||||
&line_layouts,
|
|
||||||
style,
|
style,
|
||||||
accept_keystroke.as_ref()?,
|
accept_keystroke.as_ref()?,
|
||||||
window,
|
window,
|
||||||
|
@ -3714,8 +3712,7 @@ impl EditorElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
match display_mode {
|
match display_mode {
|
||||||
EditDisplayMode::TabAccept(previewing) => {
|
EditDisplayMode::TabAccept => {
|
||||||
let previewing = *previewing;
|
|
||||||
let range = &edits.first()?.0;
|
let range = &edits.first()?.0;
|
||||||
let target_display_point = range.end.to_display_point(editor_snapshot);
|
let target_display_point = range.end.to_display_point(editor_snapshot);
|
||||||
|
|
||||||
|
@ -3723,14 +3720,22 @@ impl EditorElement {
|
||||||
target_display_point.row(),
|
target_display_point.row(),
|
||||||
editor_snapshot.line_len(target_display_point.row()),
|
editor_snapshot.line_len(target_display_point.row()),
|
||||||
);
|
);
|
||||||
let origin = self.editor.update(cx, |editor, _cx| {
|
let (previewing_inline_completion, origin) =
|
||||||
editor.display_to_pixel_point(target_line_end, editor_snapshot, window)
|
self.editor.update(cx, |editor, _cx| {
|
||||||
})?;
|
Some((
|
||||||
|
editor.previewing_inline_completion,
|
||||||
|
editor.display_to_pixel_point(
|
||||||
|
target_line_end,
|
||||||
|
editor_snapshot,
|
||||||
|
window,
|
||||||
|
)?,
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
let mut element = inline_completion_accept_indicator(
|
let mut element = inline_completion_accept_indicator(
|
||||||
"Accept",
|
"Accept",
|
||||||
None,
|
None,
|
||||||
previewing,
|
previewing_inline_completion,
|
||||||
self.editor.focus_handle(cx),
|
self.editor.focus_handle(cx),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue