@@ -52,10 +52,7 @@ use multi_buffer::{
Anchor, AnchorRangeExt, ExcerptId, ExcerptInfo, ExpandExcerptDirection, MultiBufferPoint,
MultiBufferRow, MultiBufferSnapshot, ToOffset,
};
-use project::{
- project_settings::{GitGutterSetting, ProjectSettings},
- ProjectPath,
-};
+use project::project_settings::{GitGutterSetting, ProjectSettings};
use settings::Settings;
use smallvec::{smallvec, SmallVec};
use std::{
@@ -69,12 +66,13 @@ use std::{
sync::Arc,
};
use sum_tree::Bias;
+use text::BufferId;
use theme::{ActiveTheme, Appearance, PlayerColor};
-use ui::{h_flex, ButtonLike, ButtonStyle, ContextMenu, Tooltip};
-use ui::{prelude::*, POPOVER_Y_PADDING};
+use ui::{
+ prelude::*, ButtonLike, ButtonStyle, ContextMenu, KeyBinding, Tooltip, POPOVER_Y_PADDING,
+};
use unicode_segmentation::UnicodeSegmentation;
-use util::RangeExt;
-use util::ResultExt;
+use util::{RangeExt, ResultExt};
use workspace::{item::Item, Workspace};
struct SelectionLayout {
@@ -504,6 +502,7 @@ impl EditorElement {
)
}
+ #[allow(clippy::too_many_arguments)]
fn mouse_left_down(
editor: &mut Editor,
event: &MouseDownEvent,
@@ -511,6 +510,7 @@ impl EditorElement {
position_map: &PositionMap,
text_hitbox: &Hitbox,
gutter_hitbox: &Hitbox,
+ line_numbers: &HashMap<MultiBufferRow, (ShapedLine, Option<Hitbox>)>,
cx: &mut ViewContext<Editor>,
) {
if cx.default_prevented() {
@@ -530,7 +530,9 @@ impl EditorElement {
return;
}
- if click_count == 2 && !editor.buffer().read(cx).is_singleton() {
+ let is_singleton = editor.buffer().read(cx).is_singleton();
+
+ if click_count == 2 && !is_singleton {
match EditorSettings::get_global(cx).double_click_in_multibuffer {
DoubleClickInMultibuffer::Select => {
// do nothing special on double click, all selection logic is below
@@ -585,8 +587,27 @@ impl EditorElement {
cx,
);
}
-
cx.stop_propagation();
+
+ if !is_singleton {
+ let display_row = (((event.position - gutter_hitbox.bounds.origin).y
+ + position_map.scroll_pixel_position.y)
+ / position_map.line_height) as u32;
+ let multi_buffer_row = position_map
+ .snapshot
+ .display_point_to_point(DisplayPoint::new(DisplayRow(display_row), 0), Bias::Right)
+ .row;
+ if let Some((_, Some(hitbox))) = line_numbers.get(&MultiBufferRow(multi_buffer_row)) {
+ if hitbox.contains(&event.position) {
+ editor.open_excerpts_common(
+ Some(JumpData::MultiBufferRow(MultiBufferRow(multi_buffer_row))),
+ modifiers.alt,
+ cx,
+ );
+ cx.stop_propagation();
+ }
+ }
+ }
}
fn mouse_right_down(
@@ -1975,20 +1996,25 @@ impl EditorElement {
relative_rows
}
+ #[allow(clippy::too_many_arguments)]
fn layout_line_numbers(
&self,
+ gutter_hitbox: Option<&Hitbox>,
+ gutter_dimensions: GutterDimensions,
+ line_height: Pixels,
+ scroll_position: gpui::Point<f32>,
rows: Range<DisplayRow>,
buffer_rows: impl Iterator<Item = Option<MultiBufferRow>>,
active_rows: &BTreeMap<DisplayRow, bool>,
newest_selection_head: Option<DisplayPoint>,
snapshot: &EditorSnapshot,
cx: &mut WindowContext,
- ) -> Vec<Option<ShapedLine>> {
+ ) -> Arc<HashMap<MultiBufferRow, (ShapedLine, Option<Hitbox>)>> {
let include_line_numbers = snapshot.show_line_numbers.unwrap_or_else(|| {
EditorSettings::get_global(cx).gutter.line_numbers && snapshot.mode == EditorMode::Full
});
if !include_line_numbers {
- return Vec::new();
+ return Arc::default();
}
let (newest_selection_head, is_relative) = self.editor.update(cx, |editor, cx| {
@@ -2008,7 +2034,6 @@ impl EditorElement {
let is_relative = editor.should_use_relative_line_numbers(cx);
(newest_selection_head, is_relative)
});
- let font_size = self.style.text.font_size.to_pixels(cx.rem_size());
let relative_to = if is_relative {
Some(newest_selection_head.row())
@@ -2017,11 +2042,11 @@ impl EditorElement {
};
let relative_rows = self.calculate_relative_line_numbers(snapshot, &rows, relative_to);
let mut line_number = String::new();
- buffer_rows
+ let line_numbers = buffer_rows
.into_iter()
.enumerate()
- .map(|(ix, multibuffer_row)| {
- let multibuffer_row = multibuffer_row?;
+ .flat_map(|(ix, buffer_row)| {
+ let buffer_row = buffer_row?;
let display_row = DisplayRow(rows.start.0 + ix as u32);
let color = if active_rows.contains_key(&display_row) {
cx.theme().colors().editor_active_line_number
@@ -2029,26 +2054,43 @@ impl EditorElement {
cx.theme().colors().editor_line_number
};
line_number.clear();
- let default_number = multibuffer_row.0 + 1;
+ let default_number = buffer_row.0 + 1;
let number = relative_rows
.get(&DisplayRow(ix as u32 + rows.start.0))
.unwrap_or(&default_number);
write!(&mut line_number, "{number}").unwrap();
- let run = TextRun {
- len: line_number.len(),
- font: self.style.text.font(),
- color,
- background_color: None,
- underline: None,
- strikethrough: None,
+
+ let shaped_line = self
+ .shape_line_number(SharedString::from(&line_number), color, cx)
+ .log_err()?;
+ let scroll_top = scroll_position.y * line_height;
+ let line_origin = gutter_hitbox.map(|hitbox| {
+ hitbox.origin
+ + point(
+ hitbox.size.width - shaped_line.width - gutter_dimensions.right_padding,
+ ix as f32 * line_height - (scroll_top % line_height),
+ )
+ });
+
+ #[cfg(not(test))]
+ let hitbox = line_origin.map(|line_origin| {
+ cx.insert_hitbox(
+ Bounds::new(line_origin, size(shaped_line.width, line_height)),
+ false,
+ )
+ });
+ #[cfg(test)]
+ let hitbox = {
+ let _ = line_origin;
+ None
};
- let shaped_line = cx
- .text_system()
- .shape_line(line_number.clone().into(), font_size, &[run])
- .unwrap();
- Some(shaped_line)
+
+ let multi_buffer_row = DisplayPoint::new(display_row, 0).to_point(snapshot).row;
+ let multi_buffer_row = MultiBufferRow(multi_buffer_row);
+ Some((multi_buffer_row, (shaped_line, hitbox)))
})
- .collect()
+ .collect();
+ Arc::new(line_numbers)
}
fn layout_crease_toggles(
@@ -2209,6 +2251,7 @@ impl EditorElement {
scroll_width: &mut Pixels,
resized_blocks: &mut HashMap<CustomBlockId, u32>,
selections: &[Selection<Point>],
+ selected_buffer_ids: &Vec<BufferId>,
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
sticky_header_excerpt_id: Option<ExcerptId>,
cx: &mut WindowContext,
@@ -2268,46 +2311,43 @@ impl EditorElement {
show_excerpt_controls,
height,
} => {
- let block_start = DisplayPoint::new(block_row_start, 0).to_point(snapshot);
- let block_end = DisplayPoint::new(block_row_start + *height, 0).to_point(snapshot);
- let selected = selections
- .binary_search_by(|selection| {
- if selection.end <= block_start {
- Ordering::Less
- } else if selection.start >= block_end {
- Ordering::Greater
- } else {
- Ordering::Equal
- }
- })
- .is_ok();
+ let selected = selected_buffer_ids.contains(&first_excerpt.buffer_id);
let icon_offset = gutter_dimensions.width
- (gutter_dimensions.left_padding + gutter_dimensions.margin);
-
let mut result = v_flex().id(block_id).w_full();
+
if let Some(prev_excerpt) = prev_excerpt {
if *show_excerpt_controls {
result = result.child(
h_flex()
+ .id("expand_down_hit_area")
.w(icon_offset)
.h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height())
.flex_none()
.justify_end()
.child(self.render_expand_excerpt_button(
- prev_excerpt.id,
- ExpandExcerptDirection::Down,
IconName::ArrowDownFromLine,
+ None,
cx,
- )),
+ ))
+ .on_click(cx.listener_for(&self.editor, {
+ let excerpt_id = prev_excerpt.id;
+ let direction = ExpandExcerptDirection::Down;
+ move |editor, _, cx| {
+ editor.expand_excerpt(excerpt_id, direction, cx);
+ cx.stop_propagation();
+ }
+ })),
);
}
}
- let jump_data = jump_data(snapshot, block_row_start, *height, first_excerpt, cx);
+ let jump_data = header_jump_data(snapshot, block_row_start, *height, first_excerpt);
result
.child(self.render_buffer_header(first_excerpt, true, selected, jump_data, cx))
.into_any_element()
}
+
Block::ExcerptBoundary {
prev_excerpt,
next_excerpt,
@@ -2317,34 +2357,76 @@ impl EditorElement {
} => {
let icon_offset = gutter_dimensions.width
- (gutter_dimensions.left_padding + gutter_dimensions.margin);
+ let header_height = MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height();
+ let color = cx.theme().colors().clone();
+ let hover_color = color.border_variant.opacity(0.5);
+ let focus_handle = self.editor.focus_handle(cx).clone();
let mut result = v_flex().id(block_id).w_full();
+ let expand_area = |id: SharedString| {
+ h_flex()
+ .id(id)
+ .w_full()
+ .cursor_pointer()
+ .block_mouse_down()
+ .on_mouse_move(|_, cx| cx.stop_propagation())
+ .hover(|style| style.bg(hover_color))
+ .tooltip({
+ let focus_handle = focus_handle.clone();
+ move |cx| {
+ Tooltip::for_action_in(
+ "Expand Excerpt",
+ &ExpandExcerpts { lines: 0 },
+ &focus_handle,
+ cx,
+ )
+ }
+ })
+ };
+
if let Some(prev_excerpt) = prev_excerpt {
if *show_excerpt_controls {
+ let group_name = "expand-down";
+
result = result.child(
- h_flex()
- .w(icon_offset)
- .h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height())
- .flex_none()
- .justify_end()
- .child(self.render_expand_excerpt_button(
- prev_excerpt.id,
- ExpandExcerptDirection::Down,
- IconName::ArrowDownFromLine,
- cx,
- )),
+ expand_area(format!("block-{}-down", block_id).into())
+ .group(group_name)
+ .child(
+ h_flex()
+ .w(icon_offset)
+ .h(header_height)
+ .flex_none()
+ .justify_end()
+ .child(self.render_expand_excerpt_button(
+ IconName::ArrowDownFromLine,
+ Some(group_name.to_string()),
+ cx,
+ )),
+ )
+ .on_click(cx.listener_for(&self.editor, {
+ let excerpt_id = prev_excerpt.id;
+ let direction = ExpandExcerptDirection::Down;
+ move |editor, _, cx| {
+ editor.expand_excerpt(excerpt_id, direction, cx);
+ cx.stop_propagation();
+ }
+ })),
);
}
}
if let Some(next_excerpt) = next_excerpt {
- let jump_data = jump_data(snapshot, block_row_start, *height, next_excerpt, cx);
+ let jump_data =
+ header_jump_data(snapshot, block_row_start, *height, next_excerpt);
+
if *starts_new_buffer {
if sticky_header_excerpt_id != Some(next_excerpt.id) {
+ let selected = selected_buffer_ids.contains(&next_excerpt.buffer_id);
+
result = result.child(self.render_buffer_header(
next_excerpt,
false,
- false,
+ selected,
jump_data,
cx,
));
@@ -2354,125 +2436,103 @@ impl EditorElement {
}
if *show_excerpt_controls {
+ let group_name = "expand-up-first";
+
result = result.child(
- h_flex()
- .w(icon_offset)
- .h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height())
- .flex_none()
- .justify_end()
- .child(self.render_expand_excerpt_button(
- next_excerpt.id,
- ExpandExcerptDirection::Up,
- IconName::ArrowUpFromLine,
- cx,
- )),
+ h_flex().group(group_name).child(
+ expand_area(format!("block-{}-up-first", block_id).into())
+ .h(header_height)
+ .child(
+ h_flex()
+ .w(icon_offset)
+ .h(header_height)
+ .flex_none()
+ .justify_end()
+ .child(self.render_expand_excerpt_button(
+ IconName::ArrowUpFromLine,
+ Some(group_name.to_string()),
+ cx,
+ )),
+ )
+ .on_click(cx.listener_for(&self.editor, {
+ let excerpt_id = next_excerpt.id;
+ let direction = ExpandExcerptDirection::Up;
+ move |editor, _, cx| {
+ editor.expand_excerpt(excerpt_id, direction, cx);
+ cx.stop_propagation();
+ }
+ })),
+ ),
);
}
} else {
- let editor = self.editor.clone();
- result = result.child(
- h_flex()
- .id("excerpt header block")
- .group("excerpt-jump-action")
- .justify_start()
- .w_full()
- .h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32 * cx.line_height())
- .relative()
- .child(
- div()
- .top(px(0.))
- .absolute()
- .w_full()
- .h_px()
- .bg(cx.theme().colors().border_variant)
- .group_hover("excerpt-jump-action", |style| {
- style.bg(cx.theme().colors().border)
- }),
- )
- .cursor_pointer()
- .on_click({
- let jump_data = jump_data.clone();
- cx.listener_for(&self.editor, {
- let jump_data = jump_data.clone();
- move |editor, e: &ClickEvent, cx| {
- cx.stop_propagation();
- editor.open_excerpts_common(
- Some(jump_data.clone()),
- e.down.modifiers.secondary(),
- cx,
- );
- }
- })
- })
- .tooltip({
- let jump_data = jump_data.clone();
- move |cx| {
- let jump_message = format!(
- "Jump to {}:L{}",
- match &jump_data.path {
- Some(project_path) =>
- project_path.path.display().to_string(),
- None => {
- let editor = editor.read(cx);
- editor
- .file_at(jump_data.position, cx)
- .map(|file| {
- file.full_path(cx).display().to_string()
- })
- .or_else(|| {
- Some(
- editor
- .tab_description(0, cx)?
- .to_string(),
+ let group_name = "expand-up-subsequent";
+
+ if *show_excerpt_controls {
+ result = result.child(
+ h_flex()
+ .relative()
+ .group(group_name)
+ .child(
+ div()
+ .top(px(0.))
+ .absolute()
+ .w_full()
+ .h_px()
+ .bg(color.border_variant),
+ )
+ .child(
+ expand_area(format!("block-{}-up", block_id).into())
+ .h(header_height)
+ .child(
+ h_flex()
+ .w(icon_offset)
+ .h(header_height)
+ .flex_none()
+ .justify_end()
+ .child(if *show_excerpt_controls {
+ self.render_expand_excerpt_button(
+ IconName::ArrowUpFromLine,
+ Some(group_name.to_string()),
+ cx,
+ )
+ } else {
+ ButtonLike::new("jump-icon")
+ .style(ButtonStyle::Transparent)
+ .child(
+ svg()
+ .path(
+ IconName::ArrowUpRight
+ .path(),
+ )
+ .size(IconSize::XSmall.rems())
+ .text_color(
+ color.border_variant,
+ )
+ .group_hover(
+ group_name,
+ |style| {
+ style.text_color(
+ color.border,
+ )
+ },
+ ),
)
- })
- .unwrap_or_else(|| {
- "Unknown buffer".to_string()
- })
- }
- },
- jump_data.position.row + 1
- );
- Tooltip::for_action(jump_message, &OpenExcerpts, cx)
- }
- })
- .child(
- h_flex()
- .w(icon_offset)
- .h(MULTI_BUFFER_EXCERPT_HEADER_HEIGHT as f32
- * cx.line_height())
- .flex_none()
- .justify_end()
- .child(if *show_excerpt_controls {
- self.render_expand_excerpt_button(
- next_excerpt.id,
- ExpandExcerptDirection::Up,
- IconName::ArrowUpFromLine,
- cx,
+ }),
)
- } else {
- ButtonLike::new("jump-icon")
- .style(ButtonStyle::Transparent)
- .child(
- svg()
- .path(IconName::ArrowUpRight.path())
- .size(IconSize::XSmall.rems())
- .text_color(
- cx.theme().colors().border_variant,
- )
- .group_hover(
- "excerpt-jump-action",
- |style| {
- style.text_color(
- cx.theme().colors().border,
- )
- },
- ),
- )
- }),
- ),
- );
- }
+ .on_click(cx.listener_for(&self.editor, {
+ let excerpt_id = next_excerpt.id;
+ let direction = ExpandExcerptDirection::Up;
+ move |editor, _, cx| {
+ editor
+ .expand_excerpt(excerpt_id, direction, cx);
+ cx.stop_propagation();
+ }
+ })),
+ ),
+ );
+ }
+ };
}
result.into_any()
@@ -2524,8 +2584,8 @@ impl EditorElement {
let parent_path = path
.as_ref()
.and_then(|path| Some(path.parent()?.to_string_lossy().to_string() + "/"));
-
let focus_handle = self.editor.focus_handle(cx);
+ let colors = cx.theme().colors();
div()
.px_2()
@@ -2538,20 +2598,20 @@ impl EditorElement {
.gap_2()
.flex_basis(Length::Definite(DefiniteLength::Fraction(0.667)))
.pl_0p5()
- .pr_4()
+ .pr_5()
.rounded_md()
.shadow_md()
.border_1()
.map(|div| {
- let border_color = if is_selected {
- cx.theme().colors().border_focused
+ let border_color = if is_selected && is_folded {
+ colors.border_focused
} else {
- cx.theme().colors().border
+ colors.border
};
div.border_color(border_color)
})
- .bg(cx.theme().colors().editor_subheader_background)
- .hover(|style| style.bg(cx.theme().colors().element_hover))
+ .bg(colors.editor_subheader_background)
+ .hover(|style| style.bg(colors.element_hover))
.map(|header| {
let editor = self.editor.clone();
let buffer_id = for_excerpt.buffer_id;
@@ -2559,7 +2619,7 @@ impl EditorElement {
FileIcons::get_chevron_icon(!is_folded, cx).map(Icon::from_path);
header.child(
div()
- .hover(|style| style.bg(cx.theme().colors().element_selected))
+ .hover(|style| style.bg(colors.element_selected))
.rounded_sm()
.child(
ButtonLike::new("toggle-buffer-fold")
@@ -2594,6 +2654,7 @@ impl EditorElement {
})
.child(
h_flex()
+ .cursor_pointer()
.id("path header block")
.size_full()
.justify_between()
@@ -2606,25 +2667,24 @@ impl EditorElement {
.unwrap_or_else(|| "untitled".into()),
)
.when_some(parent_path, |then, path| {
- then.child(
- div()
- .child(path)
- .text_color(cx.theme().colors().text_muted),
- )
+ then.child(div().child(path).text_color(colors.text_muted))
}),
)
- .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small))
- .cursor_pointer()
- .tooltip({
- let focus_handle = focus_handle.clone();
- move |cx| {
- Tooltip::for_action_in(
- "Jump To File",
- &OpenExcerpts,
- &focus_handle,
- cx,
- )
- }
+ .when(is_selected, |el| {
+ el.child(
+ h_flex()
+ .id("jump-to-file-button")
+ .gap_2p5()
+ .child(Label::new("Jump To File"))
+ .children(
+ KeyBinding::for_action_in(
+ &OpenExcerpts,
+ &focus_handle,
+ cx,
+ )
+ .map(|binding| binding.into_any_element()),
+ ),
+ )
})
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
.on_click(cx.listener_for(&self.editor, {
@@ -2642,11 +2702,11 @@ impl EditorElement {
fn render_expand_excerpt_button(
&self,
- excerpt_id: ExcerptId,
- direction: ExpandExcerptDirection,
icon: IconName,
+ group_name: impl Into<Option<String>>,
cx: &mut WindowContext,
) -> ButtonLike {
+ let group_name = group_name.into();
ButtonLike::new("expand-icon")
.style(ButtonStyle::Transparent)
.child(
@@ -2654,17 +2714,12 @@ impl EditorElement {
.path(icon.path())
.size(IconSize::XSmall.rems())
.text_color(cx.theme().colors().editor_line_number)
- .group("")
- .hover(|style| style.text_color(cx.theme().colors().editor_active_line_number)),
+ .when_some(group_name, |svg, group_name| {
+ svg.group_hover(group_name, |style| {
+ style.text_color(cx.theme().colors().editor_active_line_number)
+ })
+ }),
)
- .on_click(cx.listener_for(&self.editor, {
- move |editor, _, cx| {
- editor.expand_excerpt(excerpt_id, direction, cx);
- }
- }))
- .tooltip({
- move |cx| Tooltip::for_action("Expand Excerpt", &ExpandExcerpts { lines: 0 }, cx)
- })
}
#[allow(clippy::too_many_arguments)]
@@ -2682,6 +2737,7 @@ impl EditorElement {
line_height: Pixels,
line_layouts: &[LineWithInvisibles],
selections: &[Selection<Point>],
+ selected_buffer_ids: &Vec<BufferId>,
is_row_soft_wrapped: impl Copy + Fn(usize) -> bool,
sticky_header_excerpt_id: Option<ExcerptId>,
cx: &mut WindowContext,
@@ -2721,6 +2777,7 @@ impl EditorElement {
scroll_width,
&mut resized_blocks,
selections,
+ selected_buffer_ids,
is_row_soft_wrapped,
sticky_header_excerpt_id,
cx,
@@ -2769,6 +2826,7 @@ impl EditorElement {
scroll_width,
&mut resized_blocks,
selections,
+ selected_buffer_ids,
is_row_soft_wrapped,
sticky_header_excerpt_id,
cx,
@@ -2817,6 +2875,7 @@ impl EditorElement {
scroll_width,
&mut resized_blocks,
selections,
+ selected_buffer_ids,
is_row_soft_wrapped,
sticky_header_excerpt_id,
cx,
@@ -2885,6 +2944,7 @@ impl EditorElement {
}
}
+ #[allow(clippy::too_many_arguments)]
fn layout_sticky_buffer_header(
&self,
StickyHeaderExcerpt {
@@ -2896,12 +2956,15 @@ impl EditorElement {
line_height: Pixels,
snapshot: &EditorSnapshot,
hitbox: &Hitbox,
+ selected_buffer_ids: &Vec<BufferId>,
cx: &mut WindowContext,
) -> AnyElement {
- let jump_data = jump_data(snapshot, DisplayRow(0), FILE_HEADER_HEIGHT, excerpt, cx);
+ let jump_data = header_jump_data(snapshot, DisplayRow(0), FILE_HEADER_HEIGHT, excerpt);
let editor_bg_color = cx.theme().colors().editor_background;
+ let selected = selected_buffer_ids.contains(&excerpt.buffer_id);
+
let mut header = v_flex()
.relative()
.child(
@@ -2917,7 +2980,7 @@ impl EditorElement {
.top_0(),
)
.child(
- self.render_buffer_header(excerpt, false, false, jump_data, cx)
+ self.render_buffer_header(excerpt, false, selected, jump_data, cx)
.into_any_element(),
)
.into_any_element();
@@ -3860,24 +3923,30 @@ impl EditorElement {
}
fn paint_line_numbers(&mut self, layout: &mut EditorLayout, cx: &mut WindowContext) {
- let line_height = layout.position_map.line_height;
- let scroll_position = layout.position_map.snapshot.scroll_position();
- let scroll_top = scroll_position.y * line_height;
+ let is_singleton = self.editor.read(cx).is_singleton(cx);
+ let line_height = layout.position_map.line_height;
cx.set_cursor_style(CursorStyle::Arrow, &layout.gutter_hitbox);
- for (ix, line) in layout.line_numbers.iter().enumerate() {
- if let Some(line) = line {
- let line_origin = layout.gutter_hitbox.origin
- + point(
- layout.gutter_hitbox.size.width
- - line.width
- - layout.gutter_dimensions.right_padding,
- ix as f32 * line_height - (scroll_top % line_height),
- );
-
- line.paint(line_origin, line_height, cx).log_err();
- }
+ for (_, (line, hitbox)) in layout.line_numbers.iter() {
+ let Some(hitbox) = hitbox else {
+ continue;
+ };
+ let color = if !is_singleton && hitbox.is_hovered(cx) {
+ cx.theme().colors().editor_active_line_number
+ } else {
+ cx.theme().colors().editor_line_number
+ };
+ let Some(line) = self
+ .shape_line_number(line.text.clone(), color, cx)
+ .log_err()
+ else {
+ continue;
+ };
+ let Some(()) = line.paint(hitbox.origin, line_height, cx).log_err() else {
+ continue;
+ };
+ cx.set_cursor_style(CursorStyle::PointingHand, hitbox);
}
}
@@ -4876,6 +4945,7 @@ impl EditorElement {
let editor = self.editor.clone();
let text_hitbox = layout.text_hitbox.clone();
let gutter_hitbox = layout.gutter_hitbox.clone();
+ let line_numbers = layout.line_numbers.clone();
move |event: &MouseDownEvent, phase, cx| {
if phase == DispatchPhase::Bubble {
@@ -4888,6 +4958,7 @@ impl EditorElement {
&position_map,
&text_hitbox,
&gutter_hitbox,
+ line_numbers.as_ref(),
cx,
);
}),
@@ -4984,21 +5055,37 @@ impl EditorElement {
let digit_count = (snapshot.widest_line_number() as f32).log10().floor() as usize + 1;
self.column_pixels(digit_count, cx)
}
+
+ fn shape_line_number(
+ &self,
+ text: SharedString,
+ color: Hsla,
+ cx: &WindowContext,
+ ) -> anyhow::Result<ShapedLine> {
+ let run = TextRun {
+ len: text.len(),
+ font: self.style.text.font(),
+ color,
+ background_color: None,
+ underline: None,
+ strikethrough: None,
+ };
+ cx.text_system().shape_line(
+ text,
+ self.style.text.font_size.to_pixels(cx.rem_size()),
+ &[run],
+ )
+ }
}
-fn jump_data(
+fn header_jump_data(
snapshot: &EditorSnapshot,
block_row_start: DisplayRow,
height: u32,
for_excerpt: &ExcerptInfo,
- cx: &mut WindowContext,
) -> JumpData {
let range = &for_excerpt.range;
let buffer = &for_excerpt.buffer;
- let jump_path = project::File::from_dyn(buffer.file()).map(|file| ProjectPath {
- worktree_id: file.worktree_id(cx),
- path: file.path.clone(),
- });
let jump_anchor = range
.primary
.as_ref()
@@ -5012,6 +5099,7 @@ fn jump_data(
let excerpt_start_row = language::ToPoint::to_point(&jump_anchor, buffer).row;
jump_position.row - excerpt_start_row
};
+
let line_offset_from_top = block_row_start.0
+ height
+ offset_from_excerpt_start.saturating_sub(
@@ -5020,11 +5108,11 @@ fn jump_data(
.scroll_position(&snapshot.display_snapshot)
.y as u32,
);
- JumpData {
+
+ JumpData::MultiBufferPoint {
excerpt_id: for_excerpt.id,
anchor: jump_anchor,
position: language::ToPoint::to_point(&jump_anchor, buffer),
- path: jump_path,
line_offset_from_top,
}
}
@@ -6079,14 +6167,37 @@ impl Element for EditorElement {
cx,
);
- let local_selections: Vec<Selection<Point>> =
- self.editor.update(cx, |editor, cx| {
- let mut selections = editor
- .selections
- .disjoint_in_range(start_anchor..end_anchor, cx);
- selections.extend(editor.selections.pending(cx));
- selections
- });
+ let (local_selections, selected_buffer_ids): (
+ Vec<Selection<Point>>,
+ Vec<BufferId>,
+ ) = self.editor.update(cx, |editor, cx| {
+ let all_selections = editor.selections.all::<Point>(cx);
+ let selected_buffer_ids = if editor.is_singleton(cx) {
+ Vec::new()
+ } else {
+ let mut selected_buffer_ids = Vec::with_capacity(all_selections.len());
+
+ for selection in all_selections {
+ for buffer_id in snapshot
+ .buffer_snapshot
+ .buffer_ids_in_selected_rows(selection)
+ {
+ if selected_buffer_ids.last() != Some(&buffer_id) {
+ selected_buffer_ids.push(buffer_id);
+ }
+ }
+ }
+
+ selected_buffer_ids
+ };
+
+ let mut selections = editor
+ .selections
+ .disjoint_in_range(start_anchor..end_anchor, cx);
+ selections.extend(editor.selections.pending(cx));
+
+ (selections, selected_buffer_ids)
+ });
let (selections, active_rows, newest_selection_head) = self.layout_selections(
start_anchor,
@@ -6099,6 +6210,10 @@ impl Element for EditorElement {
);
let line_numbers = self.layout_line_numbers(
+ Some(&gutter_hitbox),
+ gutter_dimensions,
+ line_height,
+ scroll_position,
start_row..end_row,
buffer_rows.iter().copied(),
&active_rows,
@@ -6188,6 +6303,7 @@ impl Element for EditorElement {
line_height,
&line_layouts,
&local_selections,
+ &selected_buffer_ids,
is_row_soft_wrapped,
sticky_header_excerpt_id,
cx,
@@ -6211,6 +6327,7 @@ impl Element for EditorElement {
line_height,
&snapshot,
&hitbox,
+ &selected_buffer_ids,
cx,
)
})
@@ -6610,7 +6727,6 @@ impl Element for EditorElement {
hitbox,
text_hitbox,
gutter_hitbox,
- gutter_dimensions,
display_hunks,
content_origin,
scrollbars_layout,
@@ -6652,6 +6768,7 @@ impl Element for EditorElement {
) {
let focus_handle = self.editor.focus_handle(cx);
let key_context = self.editor.update(cx, |editor, cx| editor.key_context(cx));
+
cx.set_key_context(key_context);
cx.handle_input(
&focus_handle,
@@ -6801,7 +6918,6 @@ pub struct EditorLayout {
hitbox: Hitbox,
text_hitbox: Hitbox,
gutter_hitbox: Hitbox,
- gutter_dimensions: GutterDimensions,
content_origin: gpui::Point<Pixels>,
scrollbars_layout: AxisPair<Option<ScrollbarLayout>>,
mode: EditorMode,
@@ -6811,7 +6927,7 @@ pub struct EditorLayout {
active_rows: BTreeMap<DisplayRow, bool>,
highlighted_rows: BTreeMap<DisplayRow, Hsla>,
line_elements: SmallVec<[AnyElement; 1]>,
- line_numbers: Vec<Option<ShapedLine>>,
+ line_numbers: Arc<HashMap<MultiBufferRow, (ShapedLine, Option<Hitbox>)>>,
display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)>,
blamed_display_rows: Option<Vec<AnyElement>>,
inline_blame: Option<AnyElement>,