diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index e69ee5d7de..eab2bf3798 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1592,6 +1592,7 @@ impl EditorElement { &self, display_row: DisplayRow, display_snapshot: &DisplaySnapshot, + buffer_snapshot: &MultiBufferSnapshot, line_layout: &LineWithInvisibles, crease_trailer: Option<&CreaseTrailerLayout>, em_width: Pixels, @@ -1617,7 +1618,28 @@ impl EditorElement { let display_point = DisplayPoint::new(display_row, 0); let buffer_row = MultiBufferRow(display_point.to_point(display_snapshot).row); - let blame = self.editor.read(cx).blame.clone()?; + let editor = self.editor.read(cx); + let blame = editor.blame.clone()?; + let padding = { + const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.; + const INLINE_ACCEPT_SUGGESTION_EM_WIDTHS: f32 = 14.; + + let mut padding = INLINE_BLAME_PADDING_EM_WIDTHS; + + if let Some(inline_completion) = editor.active_inline_completion.as_ref() { + match &inline_completion.completion { + InlineCompletion::Edit(edits) + if single_line_edit(&edits, buffer_snapshot).is_some() => + { + padding += INLINE_ACCEPT_SUGGESTION_EM_WIDTHS + } + _ => {} + } + } + + padding * em_width + }; + let blame_entry = blame .update(cx, |blame, cx| { blame.blame_for_rows([Some(buffer_row)], cx).next() @@ -1631,14 +1653,13 @@ impl EditorElement { + line_height * (display_row.as_f32() - scroll_pixel_position.y / line_height); let start_x = { - const INLINE_BLAME_PADDING_EM_WIDTHS: f32 = 6.; - let line_end = if let Some(crease_trailer) = crease_trailer { crease_trailer.bounds.right() } else { content_origin.x - scroll_pixel_position.x + line_layout.width }; - let padded_line_end = line_end + em_width * INLINE_BLAME_PADDING_EM_WIDTHS; + + let padded_line_end = line_end + padding; let min_column_in_pixels = ProjectSettings::get_global(cx) .git @@ -3326,49 +3347,23 @@ impl EditorElement { match &active_inline_completion.completion { InlineCompletion::Move(target_position) => { - let tab_kbd = h_flex() - .px_0p5() - .font(theme::ThemeSettings::get_global(cx).buffer_font.clone()) - .text_size(TextSize::XSmall.rems(cx)) - .text_color(cx.theme().colors().text) - .child("tab"); - - let icon_container = div().mt(px(2.5)); // For optical alignment - - let container_element = h_flex() - .items_center() - .py_0p5() - .px_1() - .gap_1() - .bg(cx.theme().colors().text_accent.opacity(0.15)) - .border_1() - .border_color(cx.theme().colors().text_accent.opacity(0.8)) - .rounded_md() - .shadow_sm(); - let target_display_point = target_position.to_display_point(editor_snapshot); if target_display_point.row().as_f32() < scroll_top { - let mut element = container_element - .child(tab_kbd) - .child(Label::new("Jump to Edit").size(LabelSize::Small)) - .child( - icon_container - .child(Icon::new(IconName::ArrowUp).size(IconSize::Small)), - ) - .into_any(); + let mut element = inline_completion_tab_indicator( + "Jump to Edit", + Some(IconName::ArrowUp), + cx, + ); let size = element.layout_as_root(AvailableSpace::min_size(), cx); let offset = point((text_bounds.size.width - size.width) / 2., PADDING_Y); element.prepaint_at(text_bounds.origin + offset, cx); Some(element) } else if (target_display_point.row().as_f32() + 1.) > scroll_bottom { - let mut element = container_element - .child(tab_kbd) - .child(Label::new("Jump to Edit").size(LabelSize::Small)) - .child( - icon_container - .child(Icon::new(IconName::ArrowDown).size(IconSize::Small)), - ) - .into_any(); + let mut element = inline_completion_tab_indicator( + "Jump to Edit", + Some(IconName::ArrowDown), + cx, + ); let size = element.layout_as_root(AvailableSpace::min_size(), cx); let offset = point( (text_bounds.size.width - size.width) / 2., @@ -3377,10 +3372,7 @@ impl EditorElement { element.prepaint_at(text_bounds.origin + offset, cx); Some(element) } else { - let mut element = container_element - .child(tab_kbd) - .child(Label::new("Jump to Edit").size(LabelSize::Small)) - .into_any(); + let mut element = inline_completion_tab_indicator("Jump to Edit", None, cx); let target_line_end = DisplayPoint::new( target_display_point.row(), @@ -3421,9 +3413,28 @@ impl EditorElement { return None; } - if !hard_to_spot_single_edit(&edits, &editor_snapshot.buffer_snapshot) - && all_edits_insertions_or_deletions(edits, &editor_snapshot.buffer_snapshot) - { + if let Some(range) = single_line_edit(&edits, &editor_snapshot.buffer_snapshot) { + let mut element = inline_completion_tab_indicator("Accept", None, cx); + + let target_display_point = range.end.to_display_point(editor_snapshot); + let target_line_end = DisplayPoint::new( + target_display_point.row(), + editor_snapshot.line_len(target_display_point.row()), + ); + let origin = self.editor.update(cx, |editor, cx| { + editor.display_to_pixel_point(target_line_end, editor_snapshot, cx) + })?; + + element.prepaint_as_root( + text_bounds.origin + origin + point(PADDING_X, px(0.)), + AvailableSpace::min_size(), + cx, + ); + + return Some(element); + } + + if all_edits_insertions_or_deletions(edits, &editor_snapshot.buffer_snapshot) { return None; } @@ -5203,23 +5214,55 @@ fn header_jump_data( } } -/// Returns true if there's a single edit, that's either a single character -/// insertion or a single line deletion. -fn hard_to_spot_single_edit( - edits: &[(Range, String)], +fn inline_completion_tab_indicator( + label: impl Into, + icon: Option, + cx: &WindowContext, +) -> AnyElement { + let tab_kbd = h_flex() + .px_0p5() + .font(theme::ThemeSettings::get_global(cx).buffer_font.clone()) + .text_size(TextSize::XSmall.rems(cx)) + .text_color(cx.theme().colors().text) + .child("tab"); + + let padding_right = if icon.is_some() { px(4.) } else { px(8.) }; + + h_flex() + .py_0p5() + .pl_1() + .pr(padding_right) + .gap_1() + .bg(cx.theme().colors().text_accent.opacity(0.15)) + .border_1() + .border_color(cx.theme().colors().text_accent.opacity(0.8)) + .rounded_md() + .shadow_sm() + .child(tab_kbd) + .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() +} + +fn single_line_edit<'a>( + edits: &'a [(Range, String)], snapshot: &MultiBufferSnapshot, -) -> bool { - if let [(range, new_text)] = edits { - match new_text.len() { - 0 => { - let range = range.to_point(&snapshot); - range.start.row == range.end.row - } - 1 => true, - _ => false, - } +) -> Option<&'a Range> { + let [(range, _)] = edits else { + return None; + }; + + let point_range = range.to_point(&snapshot); + if point_range.start.row == point_range.end.row { + Some(range) } else { - false + None } } @@ -6507,6 +6550,7 @@ impl Element for EditorElement { inline_blame = self.layout_inline_blame( display_row, &snapshot.display_snapshot, + &snapshot.buffer_snapshot, line_layout, crease_trailer_layout, em_width,