commit view: Fix layout shift while loading commit (#44548)

Fixes a few cases where the commit view would layout shift as the diff
loaded. This was caused by:
- Adding the commit message buffer after all the diff files
- Using the gutter dimensions from the last frame for the avatar spacing

Release Notes:

- commit view: Fix layout shift while loading commit

---------

Co-authored-by: MrSubidubi <dev@bahn.sh>
This commit is contained in:
Agus Zubiaga
2025-12-10 15:01:49 +00:00
committed by GitHub
co-authored by MrSubidubi
parent dd431631b4
commit 21f7e6a9e6
10 changed files with 316 additions and 308 deletions
+190 -154
View File
@@ -108,7 +108,7 @@ use gpui::{
DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, Render,
ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle,
ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextRun, TextStyle,
TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity,
WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative, size,
};
@@ -575,7 +575,7 @@ impl Default for EditorStyle {
}
}
pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
let show_background = language_settings::language_settings(None, None, cx)
.inlay_hints
.show_background;
@@ -598,7 +598,7 @@ pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
style
}
pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
EditPredictionStyles {
insertion: HighlightStyle {
color: Some(cx.theme().status().predictive),
@@ -1249,6 +1249,7 @@ impl NextScrollCursorCenterTopBottom {
pub struct EditorSnapshot {
pub mode: EditorMode,
show_gutter: bool,
offset_content: bool,
show_line_numbers: Option<bool>,
show_git_diff_gutter: Option<bool>,
show_code_actions: Option<bool>,
@@ -1825,7 +1826,11 @@ impl Editor {
Editor::new_internal(mode, buffer, project, None, window, cx)
}
pub fn sticky_headers(&self, cx: &App) -> Option<Vec<OutlineItem<Anchor>>> {
pub fn sticky_headers(
&self,
style: &EditorStyle,
cx: &App,
) -> Option<Vec<OutlineItem<Anchor>>> {
let multi_buffer = self.buffer().read(cx);
let multi_buffer_snapshot = multi_buffer.snapshot(cx);
let multi_buffer_visible_start = self
@@ -1843,7 +1848,7 @@ impl Editor {
.outline_items_containing(
Point::new(start_row, 0)..Point::new(end_row, 0),
true,
self.style().map(|style| style.syntax.as_ref()),
Some(style.syntax.as_ref()),
)
.into_iter()
.map(|outline_item| OutlineItem {
@@ -2935,6 +2940,7 @@ impl Editor {
EditorSnapshot {
mode: self.mode.clone(),
show_gutter: self.show_gutter,
offset_content: self.offset_content,
show_line_numbers: self.show_line_numbers,
show_git_diff_gutter: self.show_git_diff_gutter,
show_code_actions: self.show_code_actions,
@@ -6895,7 +6901,7 @@ impl Editor {
};
let anchor = self.selections.newest_anchor().head();
let position = self.to_pixel_point(anchor, &snapshot, window);
let position = self.to_pixel_point(anchor, &snapshot, window, cx);
if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
self.show_blame_popover(
buffer,
@@ -9208,7 +9214,8 @@ impl Editor {
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 line_origin =
self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
let mut origin = start_point
@@ -9950,8 +9957,7 @@ impl Editor {
}
pub fn render_context_menu(
&self,
style: &EditorStyle,
&mut self,
max_height_in_lines: u32,
window: &mut Window,
cx: &mut Context<Editor>,
@@ -9961,7 +9967,9 @@ impl Editor {
if !menu.visible() {
return None;
};
Some(menu.render(style, max_height_in_lines, window, cx))
self.style
.as_ref()
.map(|style| menu.render(style, max_height_in_lines, window, cx))
}
fn render_context_menu_aside(
@@ -20364,8 +20372,11 @@ impl Editor {
self.style = Some(style);
}
pub fn style(&self) -> Option<&EditorStyle> {
self.style.as_ref()
pub fn style(&mut self, cx: &App) -> &EditorStyle {
if self.style.is_none() {
self.style = Some(self.create_style(cx));
}
self.style.as_ref().unwrap()
}
// Called by the element. This method is not designed to be called outside of the editor
@@ -22989,22 +23000,24 @@ impl Editor {
}
pub fn to_pixel_point(
&self,
&mut self,
source: multi_buffer::Anchor,
editor_snapshot: &EditorSnapshot,
window: &mut Window,
cx: &App,
) -> Option<gpui::Point<Pixels>> {
let source_point = source.to_display_point(editor_snapshot);
self.display_to_pixel_point(source_point, editor_snapshot, window)
self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
}
pub fn display_to_pixel_point(
&self,
&mut self,
source: DisplayPoint,
editor_snapshot: &EditorSnapshot,
window: &mut Window,
cx: &App,
) -> Option<gpui::Point<Pixels>> {
let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
let text_layout_details = self.text_layout_details(window);
let scroll_top = text_layout_details
.scroll_anchor
@@ -23068,10 +23081,6 @@ impl Editor {
}
}
pub fn last_gutter_dimensions(&self) -> &GutterDimensions {
&self.gutter_dimensions
}
pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
self.load_diff_task.clone()
}
@@ -23171,6 +23180,57 @@ impl Editor {
// skip any LSP updates for it.
self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
}
fn create_style(&self, cx: &App) -> EditorStyle {
let settings = ThemeSettings::get_global(cx);
let mut text_style = match self.mode {
EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
color: cx.theme().colors().editor_foreground,
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_fallbacks: settings.ui_font.fallbacks.clone(),
font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight,
line_height: relative(settings.buffer_line_height.value()),
..Default::default()
},
EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
color: cx.theme().colors().editor_foreground,
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: settings.buffer_font_size(cx).into(),
font_weight: settings.buffer_font.weight,
line_height: relative(settings.buffer_line_height.value()),
..Default::default()
},
};
if let Some(text_style_refinement) = &self.text_style_refinement {
text_style.refine(text_style_refinement)
}
let background = match self.mode {
EditorMode::SingleLine => cx.theme().system().transparent,
EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
EditorMode::Full { .. } => cx.theme().colors().editor_background,
EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
};
EditorStyle {
background,
border: cx.theme().colors().border,
local_player: cx.theme().players().local(),
text: text_style,
scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
syntax: cx.theme().syntax().clone(),
status: cx.theme().status().clone(),
inlay_hints_style: make_inlay_hints_style(cx),
edit_prediction_styles: make_suggestion_styles(cx),
unnecessary_code_fade: settings.unnecessary_code_fade,
show_underlines: self.diagnostics_enabled(),
}
}
}
fn edit_for_markdown_paste<'a>(
@@ -24698,94 +24758,98 @@ impl EditorSnapshot {
self.scroll_anchor.scroll_position(&self.display_snapshot)
}
fn gutter_dimensions(
pub fn gutter_dimensions(
&self,
font_id: FontId,
font_size: Pixels,
max_line_number_width: Pixels,
style: &EditorStyle,
window: &mut Window,
cx: &App,
) -> Option<GutterDimensions> {
if !self.show_gutter {
return None;
) -> GutterDimensions {
if self.show_gutter
&& let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
&& let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
{
let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
matches!(
ProjectSettings::get_global(cx).git.git_gutter,
GitGutterSetting::TrackedFiles
)
});
let gutter_settings = EditorSettings::get_global(cx).gutter;
let show_line_numbers = self
.show_line_numbers
.unwrap_or(gutter_settings.line_numbers);
let line_gutter_width = if show_line_numbers {
// Avoid flicker-like gutter resizes when the line number gains another digit by
// only resizing the gutter on files with > 10**min_line_number_digits lines.
let min_width_for_number_on_gutter =
ch_advance * gutter_settings.min_line_number_digits as f32;
self.max_line_number_width(style, window)
.max(min_width_for_number_on_gutter)
} else {
0.0.into()
};
let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
let git_blame_entries_width =
self.git_blame_gutter_max_author_length
.map(|max_author_length| {
let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
/// The number of characters to dedicate to gaps and margins.
const SPACING_WIDTH: usize = 4;
let max_char_count = max_author_length.min(renderer.max_author_length())
+ ::git::SHORT_SHA_LENGTH
+ MAX_RELATIVE_TIMESTAMP.len()
+ SPACING_WIDTH;
ch_advance * max_char_count
});
let is_singleton = self.buffer_snapshot().is_singleton();
let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
left_padding += if !is_singleton {
ch_width * 4.0
} else if show_runnables || show_breakpoints {
ch_width * 3.0
} else if show_git_gutter && show_line_numbers {
ch_width * 2.0
} else if show_git_gutter || show_line_numbers {
ch_width
} else {
px(0.)
};
let shows_folds = is_singleton && gutter_settings.folds;
let right_padding = if shows_folds && show_line_numbers {
ch_width * 4.0
} else if shows_folds || (!is_singleton && show_line_numbers) {
ch_width * 3.0
} else if show_line_numbers {
ch_width
} else {
px(0.)
};
GutterDimensions {
left_padding,
right_padding,
width: line_gutter_width + left_padding + right_padding,
margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
git_blame_entries_width,
}
} else if self.offset_content {
GutterDimensions::default_with_margin(font_id, font_size, cx)
} else {
GutterDimensions::default()
}
let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
matches!(
ProjectSettings::get_global(cx).git.git_gutter,
GitGutterSetting::TrackedFiles
)
});
let gutter_settings = EditorSettings::get_global(cx).gutter;
let show_line_numbers = self
.show_line_numbers
.unwrap_or(gutter_settings.line_numbers);
let line_gutter_width = if show_line_numbers {
// Avoid flicker-like gutter resizes when the line number gains another digit by
// only resizing the gutter on files with > 10**min_line_number_digits lines.
let min_width_for_number_on_gutter =
ch_advance * gutter_settings.min_line_number_digits as f32;
max_line_number_width.max(min_width_for_number_on_gutter)
} else {
0.0.into()
};
let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
let git_blame_entries_width =
self.git_blame_gutter_max_author_length
.map(|max_author_length| {
let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
/// The number of characters to dedicate to gaps and margins.
const SPACING_WIDTH: usize = 4;
let max_char_count = max_author_length.min(renderer.max_author_length())
+ ::git::SHORT_SHA_LENGTH
+ MAX_RELATIVE_TIMESTAMP.len()
+ SPACING_WIDTH;
ch_advance * max_char_count
});
let is_singleton = self.buffer_snapshot().is_singleton();
let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
left_padding += if !is_singleton {
ch_width * 4.0
} else if show_runnables || show_breakpoints {
ch_width * 3.0
} else if show_git_gutter && show_line_numbers {
ch_width * 2.0
} else if show_git_gutter || show_line_numbers {
ch_width
} else {
px(0.)
};
let shows_folds = is_singleton && gutter_settings.folds;
let right_padding = if shows_folds && show_line_numbers {
ch_width * 4.0
} else if shows_folds || (!is_singleton && show_line_numbers) {
ch_width * 3.0
} else if show_line_numbers {
ch_width
} else {
px(0.)
};
Some(GutterDimensions {
left_padding,
right_padding,
width: line_gutter_width + left_padding + right_padding,
margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
git_blame_entries_width,
})
}
pub fn render_crease_toggle(
@@ -24868,6 +24932,28 @@ impl EditorSnapshot {
None
}
}
pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
let digit_count = self.widest_line_number().ilog10() + 1;
column_pixels(style, digit_count as usize, window)
}
}
pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
let font_size = style.text.font_size.to_pixels(window.rem_size());
let layout = window.text_system().shape_line(
SharedString::from(" ".repeat(column)),
font_size,
&[TextRun {
len: column,
font: style.text.font(),
color: Hsla::default(),
..Default::default()
}],
None,
);
layout.width
}
impl Deref for EditorSnapshot {
@@ -24948,57 +25034,7 @@ impl Focusable for Editor {
impl Render for Editor {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let settings = ThemeSettings::get_global(cx);
let mut text_style = match self.mode {
EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
color: cx.theme().colors().editor_foreground,
font_family: settings.ui_font.family.clone(),
font_features: settings.ui_font.features.clone(),
font_fallbacks: settings.ui_font.fallbacks.clone(),
font_size: rems(0.875).into(),
font_weight: settings.ui_font.weight,
line_height: relative(settings.buffer_line_height.value()),
..Default::default()
},
EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
color: cx.theme().colors().editor_foreground,
font_family: settings.buffer_font.family.clone(),
font_features: settings.buffer_font.features.clone(),
font_fallbacks: settings.buffer_font.fallbacks.clone(),
font_size: settings.buffer_font_size(cx).into(),
font_weight: settings.buffer_font.weight,
line_height: relative(settings.buffer_line_height.value()),
..Default::default()
},
};
if let Some(text_style_refinement) = &self.text_style_refinement {
text_style.refine(text_style_refinement)
}
let background = match self.mode {
EditorMode::SingleLine => cx.theme().system().transparent,
EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
EditorMode::Full { .. } => cx.theme().colors().editor_background,
EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
};
EditorElement::new(
&cx.entity(),
EditorStyle {
background,
border: cx.theme().colors().border,
local_player: cx.theme().players().local(),
text: text_style,
scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
syntax: cx.theme().syntax().clone(),
status: cx.theme().status().clone(),
inlay_hints_style: make_inlay_hints_style(cx),
edit_prediction_styles: make_suggestion_styles(cx),
unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
show_underlines: self.diagnostics_enabled(),
},
)
EditorElement::new(&cx.entity(), self.create_style(cx))
}
}
+11 -15
View File
@@ -2218,10 +2218,9 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext)
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
let line_height = cx.editor(|editor, window, _| {
let line_height = cx.update_editor(|editor, window, cx| {
editor
.style()
.unwrap()
.style(cx)
.text
.line_height_in_pixels(window.rem_size())
});
@@ -2334,10 +2333,9 @@ async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext)
async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
let line_height = cx.editor(|editor, window, _| {
let line_height = cx.update_editor(|editor, window, cx| {
editor
.style()
.unwrap()
.style(cx)
.text
.line_height_in_pixels(window.rem_size())
});
@@ -2400,8 +2398,7 @@ async fn test_autoscroll(cx: &mut TestAppContext) {
let line_height = cx.update_editor(|editor, window, cx| {
editor.set_vertical_scroll_margin(2, cx);
editor
.style()
.unwrap()
.style(cx)
.text
.line_height_in_pixels(window.rem_size())
});
@@ -2480,10 +2477,9 @@ async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
init_test(cx, |_| {});
let mut cx = EditorTestContext::new(cx).await;
let line_height = cx.editor(|editor, window, _cx| {
let line_height = cx.update_editor(|editor, window, cx| {
editor
.style()
.unwrap()
.style(cx)
.text
.line_height_in_pixels(window.rem_size())
});
@@ -28311,7 +28307,8 @@ async fn test_sticky_scroll(cx: &mut TestAppContext) {
let mut sticky_headers = |offset: ScrollOffset| {
cx.update_editor(|e, window, cx| {
e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
EditorElement::sticky_headers(&e, &e.snapshot(window, cx), cx)
let style = e.style(cx).clone();
EditorElement::sticky_headers(&e, &e.snapshot(window, cx), &style, cx)
.into_iter()
.map(
|StickyHeader {
@@ -28365,10 +28362,9 @@ async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
});
let mut cx = EditorTestContext::new(cx).await;
let line_height = cx.editor(|editor, window, _cx| {
let line_height = cx.update_editor(|editor, window, cx| {
editor
.style()
.unwrap()
.style(cx)
.text
.line_height_in_pixels(window.rem_size())
});
+29 -59
View File
@@ -11,6 +11,7 @@ use crate::{
SelectedTextHighlight, Selection, SelectionDragState, SelectionEffects, SizingBehavior,
SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, ToggleFoldAll,
code_context_menus::{CodeActionsMenu, MENU_ASIDE_MAX_WIDTH, MENU_ASIDE_MIN_WIDTH, MENU_GAP},
column_pixels,
display_map::{
Block, BlockContext, BlockStyle, ChunkRendererId, DisplaySnapshot, EditorMargins,
HighlightKey, HighlightedChunk, ToDisplayPoint,
@@ -2269,7 +2270,8 @@ impl EditorElement {
};
let padding = ProjectSettings::get_global(cx).diagnostics.inline.padding as f32 * em_width;
let min_x = self.column_pixels(
let min_x = column_pixels(
&self.style,
ProjectSettings::get_global(cx)
.diagnostics
.inline
@@ -2572,7 +2574,8 @@ impl EditorElement {
let padded_line_end = line_end + padding;
let min_column_in_pixels = self.column_pixels(
let min_column_in_pixels = column_pixels(
&self.style,
ProjectSettings::get_global(cx).git.inline_blame.min_column as usize,
window,
);
@@ -2796,7 +2799,7 @@ impl EditorElement {
.enumerate()
.filter_map(|(i, indent_guide)| {
let single_indent_width =
self.column_pixels(indent_guide.tab_size as usize, window);
column_pixels(&self.style, indent_guide.tab_size as usize, window);
let total_width = single_indent_width * indent_guide.depth as f32;
let start_x = Pixels::from(
ScrollOffset::from(content_origin.x + total_width)
@@ -2853,7 +2856,7 @@ impl EditorElement {
.wrap_guides(cx)
.into_iter()
.flat_map(|(guide, active)| {
let wrap_position = self.column_pixels(guide, window);
let wrap_position = column_pixels(&self.style, guide, window);
let wrap_guide_x = wrap_position + horizontal_offset;
let display_wrap_guide = wrap_guide_x >= content_origin
&& wrap_guide_x <= hitbox.bounds.right() - vertical_scrollbar_width;
@@ -4619,6 +4622,7 @@ impl EditorElement {
gutter_dimensions: &GutterDimensions,
gutter_hitbox: &Hitbox,
text_hitbox: &Hitbox,
style: &EditorStyle,
window: &mut Window,
cx: &mut App,
) -> Option<StickyHeaders> {
@@ -4626,7 +4630,7 @@ impl EditorElement {
.show_line_numbers
.unwrap_or_else(|| EditorSettings::get_global(cx).gutter.line_numbers);
let rows = Self::sticky_headers(self.editor.read(cx), snapshot, cx);
let rows = Self::sticky_headers(self.editor.read(cx), snapshot, style, cx);
let mut lines = Vec::<StickyHeaderLine>::new();
@@ -4685,6 +4689,7 @@ impl EditorElement {
pub(crate) fn sticky_headers(
editor: &Editor,
snapshot: &EditorSnapshot,
style: &EditorStyle,
cx: &App,
) -> Vec<StickyHeader> {
let scroll_top = snapshot.scroll_position().y;
@@ -4692,7 +4697,7 @@ impl EditorElement {
let mut end_rows = Vec::<DisplayRow>::new();
let mut rows = Vec::<StickyHeader>::new();
let items = editor.sticky_headers(cx).unwrap_or_default();
let items = editor.sticky_headers(style, cx).unwrap_or_default();
for item in items {
let start_point = item.range.start.to_point(snapshot.buffer_snapshot());
@@ -5255,7 +5260,7 @@ impl EditorElement {
) -> Option<AnyElement> {
let max_height_in_lines = ((height - POPOVER_Y_PADDING) / line_height).floor() as u32;
self.editor.update(cx, |editor, cx| {
editor.render_context_menu(&self.style, max_height_in_lines, window, cx)
editor.render_context_menu(max_height_in_lines, window, cx)
})
}
@@ -5282,16 +5287,18 @@ impl EditorElement {
window: &mut Window,
cx: &mut App,
) -> Option<AnyElement> {
let position = self.editor.update(cx, |editor, _cx| {
let position = self.editor.update(cx, |editor, cx| {
let visible_start_point = editor.display_to_pixel_point(
DisplayPoint::new(visible_range.start, 0),
editor_snapshot,
window,
cx,
)?;
let visible_end_point = editor.display_to_pixel_point(
DisplayPoint::new(visible_range.end, 0),
editor_snapshot,
window,
cx,
)?;
let mouse_context_menu = editor.mouse_context_menu.as_ref()?;
@@ -5299,7 +5306,8 @@ impl EditorElement {
MenuPosition::PinnedToScreen(point) => (None, point),
MenuPosition::PinnedToEditor { source, offset } => {
let source_display_point = source.to_display_point(editor_snapshot);
let source_point = editor.to_pixel_point(source, editor_snapshot, window)?;
let source_point =
editor.to_pixel_point(source, editor_snapshot, window, cx)?;
let position = content_origin + source_point + offset;
(Some(source_display_point), position)
}
@@ -7773,29 +7781,6 @@ impl EditorElement {
});
}
fn column_pixels(&self, column: usize, window: &Window) -> Pixels {
let style = &self.style;
let font_size = style.text.font_size.to_pixels(window.rem_size());
let layout = window.text_system().shape_line(
SharedString::from(" ".repeat(column)),
font_size,
&[TextRun {
len: column,
font: style.text.font(),
color: Hsla::default(),
..Default::default()
}],
None,
);
layout.width
}
fn max_line_number_width(&self, snapshot: &EditorSnapshot, window: &mut Window) -> Pixels {
let digit_count = snapshot.widest_line_number().ilog10() + 1;
self.column_pixels(digit_count as usize, window)
}
fn shape_line_number(
&self,
text: SharedString,
@@ -8943,8 +8928,6 @@ impl Element for EditorElement {
max_lines,
} => {
let editor_handle = cx.entity();
let max_line_number_width =
self.max_line_number_width(&editor.snapshot(window, cx), window);
window.request_measured_layout(
Style::default(),
move |known_dimensions, available_space, window, cx| {
@@ -8954,7 +8937,6 @@ impl Element for EditorElement {
editor,
min_lines,
max_lines,
max_line_number_width,
known_dimensions,
available_space.width,
window,
@@ -9041,15 +9023,10 @@ impl Element for EditorElement {
.gutter_dimensions(
font_id,
font_size,
self.max_line_number_width(&snapshot, window),
style,
window,
cx,
)
.or_else(|| {
self.editor.read(cx).offset_content.then(|| {
GutterDimensions::default_with_margin(font_id, font_size, cx)
})
})
.unwrap_or_default();
);
let text_width = bounds.size.width - gutter_dimensions.width;
let settings = EditorSettings::get_global(cx);
@@ -9740,6 +9717,7 @@ impl Element for EditorElement {
&gutter_dimensions,
&gutter_hitbox,
&text_hitbox,
&style,
window,
cx,
)
@@ -11456,7 +11434,6 @@ fn compute_auto_height_layout(
editor: &mut Editor,
min_lines: usize,
max_lines: Option<usize>,
max_line_number_width: Pixels,
known_dimensions: Size<Option<Pixels>>,
available_width: AvailableSpace,
window: &mut Window,
@@ -11480,14 +11457,7 @@ fn compute_auto_height_layout(
let em_width = window.text_system().em_width(font_id, font_size).unwrap();
let mut snapshot = editor.snapshot(window, cx);
let gutter_dimensions = snapshot
.gutter_dimensions(font_id, font_size, max_line_number_width, cx)
.or_else(|| {
editor
.offset_content
.then(|| GutterDimensions::default_with_margin(font_id, font_size, cx))
})
.unwrap_or_default();
let gutter_dimensions = snapshot.gutter_dimensions(font_id, font_size, style, window, cx);
editor.gutter_dimensions = gutter_dimensions;
let text_width = width - gutter_dimensions.width;
@@ -11550,7 +11520,7 @@ mod tests {
});
let cx = &mut VisualTestContext::from_window(*window, cx);
let editor = window.root(cx).unwrap();
let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
let style = cx.update(|_, cx| editor.update(cx, |editor, cx| editor.style(cx).clone()));
for x in 1..=100 {
let (_, state) = cx.draw(
@@ -11578,7 +11548,7 @@ mod tests {
});
let cx = &mut VisualTestContext::from_window(*window, cx);
let editor = window.root(cx).unwrap();
let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
let style = cx.update(|_, cx| editor.update(cx, |editor, cx| editor.style(cx).clone()));
for x in 1..=100 {
let (_, state) = cx.draw(
@@ -11603,7 +11573,7 @@ mod tests {
});
let editor = window.root(cx).unwrap();
let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
let style = editor.update(cx, |editor, cx| editor.style(cx).clone());
let line_height = window
.update(cx, |_, window, _| {
style.text.line_height_in_pixels(window.rem_size())
@@ -11751,7 +11721,7 @@ mod tests {
});
let editor = window.root(cx).unwrap();
let style = cx.update(|cx| editor.read(cx).style().unwrap().clone());
let style = editor.update(cx, |editor, cx| editor.style(cx).clone());
let line_height = window
.update(cx, |_, window, _| {
style.text.line_height_in_pixels(window.rem_size())
@@ -11878,7 +11848,7 @@ mod tests {
});
let cx = &mut VisualTestContext::from_window(*window, cx);
let editor = window.root(cx).unwrap();
let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
let style = cx.update(|_, cx| editor.update(cx, |editor, cx| editor.style(cx).clone()));
window
.update(cx, |editor, window, cx| {
@@ -11949,7 +11919,7 @@ mod tests {
});
let cx = &mut VisualTestContext::from_window(*window, cx);
let editor = window.root(cx).unwrap();
let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
let style = cx.update(|_, cx| editor.update(cx, |editor, cx| editor.style(cx).clone()));
window
.update(cx, |editor, window, cx| {
editor.set_placeholder_text("hello", window, cx);
@@ -12189,7 +12159,7 @@ mod tests {
let cx = &mut VisualTestContext::from_window(*window, cx);
let editor = window.root(cx).unwrap();
let style = cx.update(|_, cx| editor.read(cx).style().unwrap().clone());
let style = editor.update(cx, |editor, cx| editor.style(cx).clone());
window
.update(cx, |editor, _, cx| {
editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
+1 -1
View File
@@ -59,7 +59,7 @@ impl MouseContextMenu {
x: editor.gutter_dimensions.width,
y: Pixels::ZERO,
};
let source_position = editor.to_pixel_point(source, &editor_snapshot, window)?;
let source_position = editor.to_pixel_point(source, &editor_snapshot, window, cx)?;
let menu_position = MenuPosition::PinnedToEditor {
source,
offset: position - (source_position + content_origin),
@@ -283,8 +283,7 @@ impl EditorTestContext {
.head();
let pixel_position = editor.pixel_position_of_newest_cursor.unwrap();
let line_height = editor
.style()
.unwrap()
.style(cx)
.text
.line_height_in_pixels(window.rem_size());
let snapshot = editor.snapshot(window, cx);
+73 -60
View File
@@ -1,9 +1,7 @@
use anyhow::{Context as _, Result};
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
use editor::display_map::{BlockPlacement, BlockProperties, BlockStyle};
use editor::{
Editor, EditorEvent, ExcerptId, ExcerptRange, MultiBuffer, multibuffer_context_lines,
};
use editor::{Editor, EditorEvent, ExcerptRange, MultiBuffer, multibuffer_context_lines};
use git::repository::{CommitDetails, CommitDiff, RepoPath};
use git::{GitHostingProviderRegistry, GitRemote, parse_git_remote_url};
use gpui::{
@@ -13,7 +11,7 @@ use gpui::{
};
use language::{
Anchor, Buffer, Capability, DiskState, File, LanguageRegistry, LineEnding, OffsetRangeExt as _,
ReplicaId, Rope, TextBuffer,
Point, ReplicaId, Rope, TextBuffer,
};
use multi_buffer::PathKey;
use project::{Project, WorktreeId, git_store::Repository};
@@ -70,6 +68,7 @@ struct GitBlob {
display_name: Arc<str>,
}
const COMMIT_MESSAGE_SORT_PREFIX: u64 = 0;
const FILE_NAMESPACE_SORT_PREFIX: u64 = 1;
impl CommitView {
@@ -147,6 +146,32 @@ impl CommitView {
) -> Self {
let language_registry = project.read(cx).languages().clone();
let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadOnly));
let message_buffer = cx.new(|cx| {
let mut buffer = Buffer::local(commit.message.clone(), cx);
buffer.set_capability(Capability::ReadOnly, cx);
buffer
});
multibuffer.update(cx, |multibuffer, cx| {
let snapshot = message_buffer.read(cx).snapshot();
let full_range = Point::zero()..snapshot.max_point();
let range = ExcerptRange {
context: full_range.clone(),
primary: full_range,
};
multibuffer.set_excerpt_ranges_for_path(
PathKey::with_sort_prefix(
COMMIT_MESSAGE_SORT_PREFIX,
RelPath::unix("commit message").unwrap().into(),
),
message_buffer.clone(),
&snapshot,
vec![range],
cx,
)
});
let editor = cx.new(|cx| {
let mut editor =
Editor::for_multibuffer(multibuffer.clone(), Some(project.clone()), window, cx);
@@ -154,9 +179,38 @@ impl CommitView {
editor.disable_inline_diagnostics();
editor.set_show_breakpoints(false, cx);
editor.set_expand_all_diff_hunks(cx);
editor.disable_header_for_buffer(message_buffer.read(cx).remote_id(), cx);
editor.disable_indent_guides_for_buffer(message_buffer.read(cx).remote_id(), cx);
editor.insert_blocks(
[BlockProperties {
placement: BlockPlacement::Above(editor::Anchor::min()),
height: Some(1),
style: BlockStyle::Sticky,
render: Arc::new(|_| gpui::Empty.into_any_element()),
priority: 0,
}]
.into_iter()
.chain(
editor
.buffer()
.read(cx)
.buffer_anchor_to_anchor(&message_buffer, Anchor::MAX, cx)
.map(|anchor| BlockProperties {
placement: BlockPlacement::Below(anchor),
height: Some(1),
style: BlockStyle::Sticky,
render: Arc::new(|_| gpui::Empty.into_any_element()),
priority: 0,
}),
),
None,
cx,
);
editor
});
let commit_sha = Arc::<str>::from(commit.sha.as_ref());
let first_worktree_id = project
@@ -166,7 +220,6 @@ impl CommitView {
.map(|worktree| worktree.read(cx).id());
let repository_clone = repository.clone();
let commit_message = commit.message.clone();
cx.spawn(async move |this, cx| {
for file in commit_diff.files {
@@ -228,59 +281,6 @@ impl CommitView {
})?;
}
let message_buffer = cx.new(|cx| {
let mut buffer = Buffer::local(commit_message, cx);
buffer.set_capability(Capability::ReadOnly, cx);
buffer
})?;
this.update(cx, |this, cx| {
this.multibuffer.update(cx, |multibuffer, cx| {
let range = ExcerptRange {
context: Anchor::MIN..Anchor::MAX,
primary: Anchor::MIN..Anchor::MAX,
};
multibuffer.insert_excerpts_after(
ExcerptId::min(),
message_buffer.clone(),
[range],
cx,
)
});
this.editor.update(cx, |editor, cx| {
editor.disable_header_for_buffer(message_buffer.read(cx).remote_id(), cx);
editor
.disable_indent_guides_for_buffer(message_buffer.read(cx).remote_id(), cx);
editor.insert_blocks(
[BlockProperties {
placement: BlockPlacement::Above(editor::Anchor::min()),
height: Some(1),
style: BlockStyle::Sticky,
render: Arc::new(|_| gpui::Empty.into_any_element()),
priority: 0,
}]
.into_iter()
.chain(
editor
.buffer()
.read(cx)
.buffer_anchor_to_anchor(&message_buffer, Anchor::MAX, cx)
.map(|anchor| BlockProperties {
placement: BlockPlacement::Below(anchor),
height: Some(1),
style: BlockStyle::Sticky,
render: Arc::new(|_| gpui::Empty.into_any_element()),
priority: 0,
}),
),
None,
cx,
)
});
})?;
anyhow::Ok(())
})
.detach();
@@ -417,12 +417,23 @@ impl CommitView {
None
};
let gutter_width = self.editor.update(cx, |editor, cx| {
let snapshot = editor.snapshot(window, cx);
let style = editor.style(cx);
let font_id = window.text_system().resolve_font(&style.text.font());
let font_size = style.text.font_size.to_pixels(window.rem_size());
snapshot
.gutter_dimensions(font_id, font_size, style, window, cx)
.full_width()
});
h_flex()
.border_b_1()
.border_color(cx.theme().colors().border_variant)
.w_full()
.child(
h_flex()
.w(self.editor.read(cx).last_gutter_dimensions().full_width())
.w(gutter_width)
.justify_center()
.child(self.render_commit_avatar(&commit.sha, rems_from_px(48.), window, cx)),
)
@@ -1011,7 +1022,9 @@ impl Render for CommitView {
.size_full()
.bg(cx.theme().colors().editor_background)
.child(self.render_header(window, cx))
.child(div().flex_grow().child(self.editor.clone()))
.when(!self.editor.read(cx).is_empty(cx), |this| {
this.child(div().flex_grow().child(self.editor.clone()))
})
}
}
+2 -3
View File
@@ -294,11 +294,10 @@ mod test {
async fn test_scroll(cx: &mut gpui::TestAppContext) {
let mut cx = VimTestContext::new(cx, true).await;
let (line_height, visible_line_count) = cx.editor(|editor, window, _cx| {
let (line_height, visible_line_count) = cx.update_editor(|editor, window, cx| {
(
editor
.style()
.unwrap()
.style(cx)
.text
.line_height_in_pixels(window.rem_size()),
editor.visible_line_count().unwrap(),
+1 -1
View File
@@ -2399,7 +2399,7 @@ async fn test_clipping_on_mode_change(cx: &mut gpui::TestAppContext) {
.end;
editor.last_bounds().unwrap().origin
+ editor
.display_to_pixel_point(current_head, &snapshot, window)
.display_to_pixel_point(current_head, &snapshot, window, cx)
.unwrap()
});
pixel_position.x += px(100.);
@@ -304,11 +304,10 @@ impl NeovimBackedTestContext {
self.neovim.set_option(&format!("scrolloff={}", 3)).await;
// +2 to account for the vim command UI at the bottom.
self.neovim.set_option(&format!("lines={}", rows + 2)).await;
let (line_height, visible_line_count) = self.editor(|editor, window, _cx| {
let (line_height, visible_line_count) = self.update_editor(|editor, window, cx| {
(
editor
.style()
.unwrap()
.style(cx)
.text
.line_height_in_pixels(window.rem_size()),
editor.visible_line_count().unwrap(),
+6 -10
View File
@@ -174,17 +174,13 @@ impl Render for QuickActionBar {
.as_ref()
.is_some_and(|menu| matches!(menu.origin(), ContextMenuOrigin::QuickActionBar))
};
let code_action_element = if is_deployed {
editor.update(cx, |editor, cx| {
if let Some(style) = editor.style() {
editor.render_context_menu(style, MAX_CODE_ACTION_MENU_LINES, window, cx)
} else {
None
}
let code_action_element = is_deployed
.then(|| {
editor.update(cx, |editor, cx| {
editor.render_context_menu(MAX_CODE_ACTION_MENU_LINES, window, cx)
})
})
} else {
None
};
.flatten();
v_flex()
.child(
IconButton::new("toggle_code_actions_icon", IconName::BoltOutlined)