diff --git a/assets/settings/default.json b/assets/settings/default.json index f8c5e4b15e..ea2e6ff99e 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -140,6 +140,14 @@ // Whether to show diagnostic indicators in the scrollbar. "diagnostics": true }, + "gutter": { + // Whether to show line numbers in the gutter. + "line_numbers": true, + // Whether to show code action buttons in the gutter. + "code_actions": true, + // Whether to show fold buttons in the gutter. + "folds": true + }, // The number of lines to keep above/below the cursor when scrolling. "vertical_scroll_margin": 3, "relative_line_numbers": false, diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 4e861c9d3e..2ce3e4e551 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -365,7 +365,7 @@ impl AssistantPanel { move |cx: &mut BlockContext| { measurements.set(BlockMeasurements { anchor_x: cx.anchor_x, - gutter_width: cx.gutter_width, + gutter_width: cx.gutter_dimensions.width, }); inline_assistant.clone().into_any_element() } diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index e737b69717..98004360ee 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -885,7 +885,7 @@ mod tests { use super::*; use editor::{ display_map::{BlockContext, TransformBlock}, - DisplayPoint, + DisplayPoint, GutterDimensions, }; use gpui::{px, TestAppContext, VisualTestContext, WindowContext}; use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped}; @@ -1599,8 +1599,7 @@ mod tests { .render(&mut BlockContext { context: cx, anchor_x: px(0.), - gutter_padding: px(0.), - gutter_width: px(0.), + gutter_dimensions: &GutterDimensions::default(), line_height: px(0.), em_width: px(0.), max_width: px(0.), diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index a17dc4a6c6..3c2dc598a2 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -2,7 +2,7 @@ use super::{ wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot}, Highlights, }; -use crate::{Anchor, Editor, EditorStyle, ExcerptId, ExcerptRange, ToPoint as _}; +use crate::{Anchor, Editor, EditorStyle, ExcerptId, ExcerptRange, GutterDimensions, ToPoint as _}; use collections::{Bound, HashMap, HashSet}; use gpui::{AnyElement, ElementContext, Pixels, View}; use language::{BufferSnapshot, Chunk, Patch, Point}; @@ -88,8 +88,7 @@ pub struct BlockContext<'a, 'b> { pub view: View, pub anchor_x: Pixels, pub max_width: Pixels, - pub gutter_width: Pixels, - pub gutter_padding: Pixels, + pub gutter_dimensions: &'b GutterDimensions, pub em_width: Pixels, pub line_height: Pixels, pub block_id: usize, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 206bcc60a1..c1b01c195a 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -88,6 +88,7 @@ pub use multi_buffer::{ }; use ordered_float::OrderedFloat; use parking_lot::{Mutex, RwLock}; +use project::project_settings::{GitGutterSetting, ProjectSettings}; use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction}; use rand::prelude::*; use rpc::proto::*; @@ -443,7 +444,8 @@ pub struct EditorSnapshot { } pub struct GutterDimensions { - pub padding: Pixels, + pub left_padding: Pixels, + pub right_padding: Pixels, pub width: Pixels, pub margin: Pixels, } @@ -451,7 +453,8 @@ pub struct GutterDimensions { impl Default for GutterDimensions { fn default() -> Self { Self { - padding: Pixels::ZERO, + left_padding: Pixels::ZERO, + right_padding: Pixels::ZERO, width: Pixels::ZERO, margin: Pixels::ZERO, } @@ -4058,7 +4061,8 @@ impl Editor { if self.available_code_actions.is_some() { Some( IconButton::new("code_actions_indicator", ui::IconName::Bolt) - .icon_size(IconSize::Small) + .icon_size(IconSize::XSmall) + .size(ui::ButtonSize::None) .icon_color(Color::Muted) .selected(is_active) .on_click(cx.listener(|editor, _e, cx| { @@ -9636,23 +9640,50 @@ impl EditorSnapshot { max_line_number_width: Pixels, cx: &AppContext, ) -> GutterDimensions { - if self.show_gutter { - let descent = cx.text_system().descent(font_id, font_size); - let gutter_padding_factor = 4.0; - let gutter_padding = (em_width * gutter_padding_factor).round(); + if !self.show_gutter { + return GutterDimensions::default(); + } + let descent = cx.text_system().descent(font_id, font_size); + + let show_git_gutter = matches!( + ProjectSettings::get_global(cx).git.git_gutter, + Some(GitGutterSetting::TrackedFiles) + ); + let gutter_settings = EditorSettings::get_global(cx).gutter; + + let line_gutter_width = if gutter_settings.line_numbers { // Avoid flicker-like gutter resizes when the line number gains another digit and only resize the gutter on files with N*10^5 lines. let min_width_for_number_on_gutter = em_width * 4.0; - let gutter_width = - max_line_number_width.max(min_width_for_number_on_gutter) + gutter_padding * 2.0; - let gutter_margin = -descent; - - GutterDimensions { - padding: gutter_padding, - width: gutter_width, - margin: gutter_margin, - } + max_line_number_width.max(min_width_for_number_on_gutter) } else { - GutterDimensions::default() + 0.0.into() + }; + + let left_padding = if gutter_settings.code_actions { + em_width * 3.0 + } else if show_git_gutter && gutter_settings.line_numbers { + em_width * 2.0 + } else if show_git_gutter || gutter_settings.line_numbers { + em_width + } else { + px(0.) + }; + + let right_padding = if gutter_settings.folds && gutter_settings.line_numbers { + em_width * 4.0 + } else if gutter_settings.folds { + em_width * 3.0 + } else if gutter_settings.line_numbers { + em_width + } else { + px(0.) + }; + + GutterDimensions { + left_padding, + right_padding, + width: line_gutter_width + left_padding + right_padding, + margin: -descent, } } } @@ -10159,9 +10190,14 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, _is_valid: bool) -> Ren .group(group_id.clone()) .relative() .size_full() - .pl(cx.gutter_width) - .w(cx.max_width + cx.gutter_width) - .child(div().flex().w(cx.anchor_x - cx.gutter_width).flex_shrink()) + .pl(cx.gutter_dimensions.width) + .w(cx.max_width + cx.gutter_dimensions.width) + .child( + div() + .flex() + .w(cx.anchor_x - cx.gutter_dimensions.width) + .flex_shrink(), + ) .child(div().flex().flex_shrink_0().child( StyledText::new(text_without_backticks.clone()).with_highlights( &text_style, diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 074003492f..cff68e689e 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -12,6 +12,7 @@ pub struct EditorSettings { pub use_on_type_format: bool, pub toolbar: Toolbar, pub scrollbar: Scrollbar, + pub gutter: Gutter, pub vertical_scroll_margin: f32, pub relative_line_numbers: bool, pub seed_search_query_from_cursor: SeedQuerySetting, @@ -45,6 +46,13 @@ pub struct Scrollbar { pub diagnostics: bool, } +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct Gutter { + pub line_numbers: bool, + pub code_actions: bool, + pub folds: bool, +} + /// When to show the scrollbar in the editor. /// /// Default: auto @@ -97,6 +105,8 @@ pub struct EditorSettingsContent { pub toolbar: Option, /// Scrollbar related settings pub scrollbar: Option, + /// Gutter related settings + pub gutter: Option, /// The number of lines to keep above/below the cursor when auto-scrolling. /// @@ -157,6 +167,23 @@ pub struct ScrollbarContent { pub diagnostics: Option, } +/// Gutter related settings +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +pub struct GutterContent { + /// Whether to show line numbers in the gutter. + /// + /// Default: true + pub line_numbers: Option, + /// Whether to show code action buttons in the gutter. + /// + /// Default: true + pub code_actions: Option, + /// Whether to show fold buttons in the gutter. + /// + /// Default: true + pub folds: Option, +} + impl Settings for EditorSettings { const KEY: Option<&'static str> = None; diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 6c5635400b..c965d3abdb 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -12,9 +12,9 @@ use crate::{ mouse_context_menu, scroll::scroll_amount::ScrollAmount, CursorShape, DisplayPoint, DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, - EditorSettings, EditorSnapshot, EditorStyle, HalfPageDown, HalfPageUp, HoveredCursor, LineDown, - LineUp, OpenExcerpts, PageDown, PageUp, Point, SelectPhase, Selection, SoftWrap, ToPoint, - CURSORS_VISIBLE_FOR, MAX_LINE_LEN, + EditorSettings, EditorSnapshot, EditorStyle, GutterDimensions, HalfPageDown, HalfPageUp, + HoveredCursor, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, SelectPhase, Selection, + SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, MAX_LINE_LEN, }; use anyhow::Result; use collections::{BTreeMap, HashMap}; @@ -716,20 +716,22 @@ impl EditorElement { let scroll_position = layout.position_map.snapshot.scroll_position(); let scroll_top = scroll_position.y * line_height; - let show_gutter = matches!( + let show_git_gutter = matches!( ProjectSettings::get_global(cx).git.git_gutter, Some(GitGutterSetting::TrackedFiles) ); - if show_gutter { + if show_git_gutter { Self::paint_diff_hunks(bounds, layout, cx); } + let gutter_settings = EditorSettings::get_global(cx).gutter; + for (ix, line) in layout.line_numbers.iter().enumerate() { if let Some(line) = line { let line_origin = bounds.origin + point( - bounds.size.width - line.width - layout.gutter_padding, + bounds.size.width - line.width - layout.gutter_dimensions.right_padding, ix as f32 * line_height - (scroll_top % line_height), ); @@ -740,6 +742,7 @@ impl EditorElement { cx.with_z_index(1, |cx| { for (ix, fold_indicator) in layout.fold_indicators.drain(..).enumerate() { if let Some(fold_indicator) = fold_indicator { + debug_assert!(gutter_settings.folds); let mut fold_indicator = fold_indicator.into_any_element(); let available_space = size( AvailableSpace::MinContent, @@ -748,11 +751,12 @@ impl EditorElement { let fold_indicator_size = fold_indicator.measure(available_space, cx); let position = point( - bounds.size.width - layout.gutter_padding, + bounds.size.width - layout.gutter_dimensions.right_padding, ix as f32 * line_height - (scroll_top % line_height), ); let centering_offset = point( - (layout.gutter_padding + layout.gutter_margin - fold_indicator_size.width) + (layout.gutter_dimensions.right_padding + layout.gutter_dimensions.margin + - fold_indicator_size.width) / 2., (line_height - fold_indicator_size.height) / 2., ); @@ -762,6 +766,7 @@ impl EditorElement { } if let Some(indicator) = layout.code_actions_indicator.take() { + debug_assert!(gutter_settings.code_actions); let mut button = indicator.button.into_any_element(); let available_space = size( AvailableSpace::MinContent, @@ -772,7 +777,9 @@ impl EditorElement { let mut x = Pixels::ZERO; let mut y = indicator.row as f32 * line_height - scroll_top; // Center indicator. - x += ((layout.gutter_padding + layout.gutter_margin) - indicator_size.width) / 2.; + x += (layout.gutter_dimensions.margin + layout.gutter_dimensions.left_padding + - indicator_size.width) + / 2.; y += (line_height - indicator_size.height) / 2.; button.draw(bounds.origin + point(x, y), available_space, cx); @@ -887,7 +894,8 @@ impl EditorElement { cx: &mut ElementContext, ) { let start_row = layout.visible_display_row_range.start; - let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO); + let content_origin = + text_bounds.origin + point(layout.gutter_dimensions.margin, Pixels::ZERO); let line_end_overshoot = 0.15 * layout.position_map.line_height; let whitespace_setting = self .editor @@ -1156,7 +1164,8 @@ impl EditorElement { layout: &LayoutState, cx: &mut ElementContext, ) { - let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO); + let content_origin = + text_bounds.origin + point(layout.gutter_dimensions.margin, Pixels::ZERO); let line_end_overshoot = layout.line_end_overshoot(); // A softer than perfect black @@ -1182,7 +1191,8 @@ impl EditorElement { layout: &mut LayoutState, cx: &mut ElementContext, ) { - let content_origin = text_bounds.origin + point(layout.gutter_margin, Pixels::ZERO); + let content_origin = + text_bounds.origin + point(layout.gutter_dimensions.margin, Pixels::ZERO); let start_row = layout.visible_display_row_range.start; if let Some((position, mut context_menu)) = layout.context_menu.take() { let available_space = size(AvailableSpace::MinContent, AvailableSpace::MinContent); @@ -1819,7 +1829,10 @@ impl EditorElement { Vec>, ) { let font_size = self.style.text.font_size.to_pixels(cx.rem_size()); - let include_line_numbers = snapshot.mode == EditorMode::Full; + let include_line_numbers = + EditorSettings::get_global(cx).gutter.line_numbers && snapshot.mode == EditorMode::Full; + let include_fold_statuses = + EditorSettings::get_global(cx).gutter.folds && snapshot.mode == EditorMode::Full; let mut shaped_line_numbers = Vec::with_capacity(rows.len()); let mut fold_statuses = Vec::with_capacity(rows.len()); let mut line_number = String::new(); @@ -1864,6 +1877,8 @@ impl EditorElement { .shape_line(line_number.clone().into(), font_size, &[run]) .unwrap(); shaped_line_numbers.push(Some(shaped_line)); + } + if include_fold_statuses { fold_statuses.push( is_singleton .then(|| { @@ -1960,7 +1975,13 @@ impl EditorElement { .unwrap() .width; - let gutter_dimensions = snapshot.gutter_dimensions(font_id, font_size, em_width, self.max_line_number_width(&snapshot, cx), cx); + let gutter_dimensions = snapshot.gutter_dimensions( + font_id, + font_size, + em_width, + self.max_line_number_width(&snapshot, cx), + cx, + ); editor.gutter_width = gutter_dimensions.width; @@ -2213,8 +2234,7 @@ impl EditorElement { bounds.size.width, scroll_width, text_width, - gutter_dimensions.padding, - gutter_dimensions.width, + &gutter_dimensions, em_width, gutter_dimensions.width + gutter_dimensions.margin, line_height, @@ -2251,6 +2271,8 @@ impl EditorElement { snapshot = editor.snapshot(cx); } + let gutter_settings = EditorSettings::get_global(cx).gutter; + let mut context_menu = None; let mut code_actions_indicator = None; if let Some(newest_selection_head) = newest_selection_head { @@ -2272,12 +2294,14 @@ impl EditorElement { Some(crate::ContextMenu::CodeActions(_)) ); - code_actions_indicator = editor - .render_code_actions_indicator(&style, active, cx) - .map(|element| CodeActionsIndicator { - row: newest_selection_head.row(), - button: element, - }); + if gutter_settings.code_actions { + code_actions_indicator = editor + .render_code_actions_indicator(&style, active, cx) + .map(|element| CodeActionsIndicator { + row: newest_selection_head.row(), + button: element, + }); + } } } @@ -2295,29 +2319,32 @@ impl EditorElement { None } else { editor.hover_state.render( - &snapshot, - &style, - visible_rows, - max_size, - editor.workspace.as_ref().map(|(w, _)| w.clone()), - cx, - ) + &snapshot, + &style, + visible_rows, + max_size, + editor.workspace.as_ref().map(|(w, _)| w.clone()), + cx, + ) }; let editor_view = cx.view().clone(); - let fold_indicators = cx.with_element_context(|cx| { - - cx.with_element_id(Some("gutter_fold_indicators"), |_cx| { - editor.render_fold_indicators( - fold_statuses, - &style, - editor.gutter_hovered, - line_height, - gutter_dimensions.margin, - editor_view, - ) - }) - }); + let fold_indicators = if gutter_settings.folds { + cx.with_element_context(|cx| { + cx.with_element_id(Some("gutter_fold_indicators"), |_cx| { + editor.render_fold_indicators( + fold_statuses, + &style, + editor.gutter_hovered, + line_height, + gutter_dimensions.margin, + editor_view, + ) + }) + }) + } else { + Vec::new() + }; let invisible_symbol_font_size = font_size / 2.; let tab_invisible = cx @@ -2370,13 +2397,12 @@ impl EditorElement { visible_display_row_range: start_row..end_row, wrap_guides, gutter_size, - gutter_padding: gutter_dimensions.padding, + gutter_dimensions, text_size, scrollbar_row_range, show_scrollbars, is_singleton, max_row, - gutter_margin: gutter_dimensions.margin, active_rows, highlighted_rows, highlighted_ranges, @@ -2403,8 +2429,7 @@ impl EditorElement { editor_width: Pixels, scroll_width: Pixels, text_width: Pixels, - gutter_padding: Pixels, - gutter_width: Pixels, + gutter_dimensions: &GutterDimensions, em_width: Pixels, text_x: Pixels, line_height: Pixels, @@ -2447,9 +2472,8 @@ impl EditorElement { block.render(&mut BlockContext { context: cx, anchor_x, - gutter_padding, + gutter_dimensions, line_height, - gutter_width, em_width, block_id, max_width: scroll_width.max(text_width), @@ -2553,12 +2577,14 @@ impl EditorElement { h_flex() .id(("collapsed context", block_id)) .size_full() - .gap(gutter_padding) + .gap(gutter_dimensions.left_padding + gutter_dimensions.right_padding) .child( h_flex() .justify_end() .flex_none() - .w(gutter_width - gutter_padding) + .w(gutter_dimensions.width + - (gutter_dimensions.left_padding + + gutter_dimensions.right_padding)) .h_full() .text_buffer(cx) .text_color(cx.theme().colors().editor_line_number) @@ -2619,7 +2645,7 @@ impl EditorElement { BlockStyle::Sticky => editor_width, BlockStyle::Flex => editor_width .max(fixed_block_max_width) - .max(gutter_width + scroll_width), + .max(gutter_dimensions.width + scroll_width), BlockStyle::Fixed => unreachable!(), }; let available_space = size( @@ -2636,7 +2662,7 @@ impl EditorElement { }); } ( - scroll_width.max(fixed_block_max_width - gutter_width), + scroll_width.max(fixed_block_max_width - gutter_dimensions.width), blocks, ) } @@ -3153,8 +3179,7 @@ type BufferRow = u32; pub struct LayoutState { position_map: Arc, gutter_size: Size, - gutter_padding: Pixels, - gutter_margin: Pixels, + gutter_dimensions: GutterDimensions, text_size: gpui::Size, mode: EditorMode, wrap_guides: SmallVec<[(Pixels, bool); 2]>,