indent guides: Respect language specific settings in multibuffers (#12528)

Indent guides can be configured per language, meaning that in a multi
buffer we can get excerpts where indent guides should be
disabled/enabled/styled differently than other excerpts.

Imagine the following scenario, i have indent guides disabled in my
settings, but want to enable them for JS and Python. I also want to use
a different line width for python files. Something like this is now
supported:

<img width="445" alt="image"
src="https://github.com/zed-industries/zed/assets/53836821/0c91411c-145c-4210-a883-4c469d5cb828">

And the relevant settings for the example above:
```json
"indent_guides": {
  "enabled": false
},
"languages": {
  "JavaScript": {
    "indent_guides": {
      "enabled": true
    }
  },
  "Python": {
    "indent_guides": {
      "enabled": true,
      "line_width": 5
    }
  }
}
```



Release Notes:

- Respect language specific settings when showing indent guides in a
multibuffer
- Fixes an issue where indent guide specific settings were not
recognized when specified in local settings
This commit is contained in:
Bennet Bo Fenner 2024-06-01 20:33:32 +02:00 committed by GitHub
parent 95e360b170
commit ab8d25e0a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 113 additions and 101 deletions

View file

@ -9770,19 +9770,19 @@ impl Editor {
} }
pub fn toggle_indent_guides(&mut self, _: &ToggleIndentGuides, cx: &mut ViewContext<Self>) { pub fn toggle_indent_guides(&mut self, _: &ToggleIndentGuides, cx: &mut ViewContext<Self>) {
let currently_enabled = self.should_show_indent_guides(cx); let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
self.show_indent_guides = Some(!currently_enabled);
cx.notify();
}
fn should_show_indent_guides(&self, cx: &mut ViewContext<Self>) -> bool {
self.show_indent_guides.unwrap_or_else(|| {
self.buffer self.buffer
.read(cx) .read(cx)
.settings_at(0, cx) .settings_at(0, cx)
.indent_guides .indent_guides
.enabled .enabled
}) });
self.show_indent_guides = Some(!currently_enabled);
cx.notify();
}
fn should_show_indent_guides(&self) -> Option<bool> {
self.show_indent_guides
} }
pub fn toggle_line_numbers(&mut self, _: &ToggleLineNumbers, cx: &mut ViewContext<Self>) { pub fn toggle_line_numbers(&mut self, _: &ToggleLineNumbers, cx: &mut ViewContext<Self>) {

View file

@ -20,6 +20,7 @@ use language::{
FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override, FakeLspAdapter, IndentGuide, LanguageConfig, LanguageConfigOverride, LanguageMatcher, Override,
Point, Point,
}; };
use language_settings::IndentGuideSettings;
use multi_buffer::MultiBufferIndentGuide; use multi_buffer::MultiBufferIndentGuide;
use parking_lot::Mutex; use parking_lot::Mutex;
use project::project_settings::{LspSettings, ProjectSettings}; use project::project_settings::{LspSettings, ProjectSettings};
@ -11505,6 +11506,7 @@ fn assert_indent_guides(
let snapshot = editor.snapshot(cx).display_snapshot; let snapshot = editor.snapshot(cx).display_snapshot;
let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range( let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
MultiBufferRow(range.start)..MultiBufferRow(range.end), MultiBufferRow(range.start)..MultiBufferRow(range.end),
true,
&snapshot, &snapshot,
cx, cx,
); );
@ -11543,6 +11545,21 @@ fn assert_indent_guides(
assert_eq!(indent_guides, expected, "Indent guides do not match"); assert_eq!(indent_guides, expected, "Indent guides do not match");
} }
fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
IndentGuide {
buffer_id,
start_row,
end_row,
depth,
tab_size: 4,
settings: IndentGuideSettings {
enabled: true,
line_width: 1,
..Default::default()
},
}
}
#[gpui::test] #[gpui::test]
async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) { async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
let (buffer_id, mut cx) = setup_indent_guides_editor( let (buffer_id, mut cx) = setup_indent_guides_editor(
@ -11555,12 +11572,7 @@ async fn test_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
) )
.await; .await;
assert_indent_guides( assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
0..3,
vec![IndentGuide::new(buffer_id, 1, 1, 0, 4)],
None,
&mut cx,
);
} }
#[gpui::test] #[gpui::test]
@ -11576,12 +11588,7 @@ async fn test_indent_guide_simple_block(cx: &mut gpui::TestAppContext) {
) )
.await; .await;
assert_indent_guides( assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
0..4,
vec![IndentGuide::new(buffer_id, 1, 2, 0, 4)],
None,
&mut cx,
);
} }
#[gpui::test] #[gpui::test]
@ -11604,9 +11611,9 @@ async fn test_indent_guide_nested(cx: &mut gpui::TestAppContext) {
assert_indent_guides( assert_indent_guides(
0..8, 0..8,
vec![ vec![
IndentGuide::new(buffer_id, 1, 6, 0, 4), indent_guide(buffer_id, 1, 6, 0),
IndentGuide::new(buffer_id, 3, 3, 1, 4), indent_guide(buffer_id, 3, 3, 1),
IndentGuide::new(buffer_id, 5, 5, 1, 4), indent_guide(buffer_id, 5, 5, 1),
], ],
None, None,
&mut cx, &mut cx,
@ -11630,8 +11637,8 @@ async fn test_indent_guide_tab(cx: &mut gpui::TestAppContext) {
assert_indent_guides( assert_indent_guides(
0..5, 0..5,
vec![ vec![
IndentGuide::new(buffer_id, 1, 3, 0, 4), indent_guide(buffer_id, 1, 3, 0),
IndentGuide::new(buffer_id, 2, 2, 1, 4), indent_guide(buffer_id, 2, 2, 1),
], ],
None, None,
&mut cx, &mut cx,
@ -11652,12 +11659,7 @@ async fn test_indent_guide_continues_on_empty_line(cx: &mut gpui::TestAppContext
) )
.await; .await;
assert_indent_guides( assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
0..5,
vec![IndentGuide::new(buffer_id, 1, 3, 0, 4)],
None,
&mut cx,
);
} }
#[gpui::test] #[gpui::test]
@ -11683,9 +11685,9 @@ async fn test_indent_guide_complex(cx: &mut gpui::TestAppContext) {
assert_indent_guides( assert_indent_guides(
0..11, 0..11,
vec![ vec![
IndentGuide::new(buffer_id, 1, 9, 0, 4), indent_guide(buffer_id, 1, 9, 0),
IndentGuide::new(buffer_id, 6, 6, 1, 4), indent_guide(buffer_id, 6, 6, 1),
IndentGuide::new(buffer_id, 8, 8, 1, 4), indent_guide(buffer_id, 8, 8, 1),
], ],
None, None,
&mut cx, &mut cx,
@ -11715,9 +11717,9 @@ async fn test_indent_guide_starts_off_screen(cx: &mut gpui::TestAppContext) {
assert_indent_guides( assert_indent_guides(
1..11, 1..11,
vec![ vec![
IndentGuide::new(buffer_id, 1, 9, 0, 4), indent_guide(buffer_id, 1, 9, 0),
IndentGuide::new(buffer_id, 6, 6, 1, 4), indent_guide(buffer_id, 6, 6, 1),
IndentGuide::new(buffer_id, 8, 8, 1, 4), indent_guide(buffer_id, 8, 8, 1),
], ],
None, None,
&mut cx, &mut cx,
@ -11747,9 +11749,9 @@ async fn test_indent_guide_ends_off_screen(cx: &mut gpui::TestAppContext) {
assert_indent_guides( assert_indent_guides(
1..10, 1..10,
vec![ vec![
IndentGuide::new(buffer_id, 1, 9, 0, 4), indent_guide(buffer_id, 1, 9, 0),
IndentGuide::new(buffer_id, 6, 6, 1, 4), indent_guide(buffer_id, 6, 6, 1),
IndentGuide::new(buffer_id, 8, 8, 1, 4), indent_guide(buffer_id, 8, 8, 1),
], ],
None, None,
&mut cx, &mut cx,
@ -11775,9 +11777,9 @@ async fn test_indent_guide_without_brackets(cx: &mut gpui::TestAppContext) {
assert_indent_guides( assert_indent_guides(
1..10, 1..10,
vec![ vec![
IndentGuide::new(buffer_id, 1, 4, 0, 4), indent_guide(buffer_id, 1, 4, 0),
IndentGuide::new(buffer_id, 2, 3, 1, 4), indent_guide(buffer_id, 2, 3, 1),
IndentGuide::new(buffer_id, 3, 3, 2, 4), indent_guide(buffer_id, 3, 3, 2),
], ],
None, None,
&mut cx, &mut cx,
@ -11802,8 +11804,8 @@ async fn test_indent_guide_ends_before_empty_line(cx: &mut gpui::TestAppContext)
assert_indent_guides( assert_indent_guides(
0..6, 0..6,
vec![ vec![
IndentGuide::new(buffer_id, 1, 2, 0, 4), indent_guide(buffer_id, 1, 2, 0),
IndentGuide::new(buffer_id, 2, 2, 1, 4), indent_guide(buffer_id, 2, 2, 1),
], ],
None, None,
&mut cx, &mut cx,
@ -11825,12 +11827,7 @@ async fn test_indent_guide_continuing_off_screen(cx: &mut gpui::TestAppContext)
) )
.await; .await;
assert_indent_guides( assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
0..1,
vec![IndentGuide::new(buffer_id, 1, 1, 0, 4)],
None,
&mut cx,
);
} }
#[gpui::test] #[gpui::test]
@ -11852,8 +11849,8 @@ async fn test_indent_guide_tabs(cx: &mut gpui::TestAppContext) {
assert_indent_guides( assert_indent_guides(
0..6, 0..6,
vec![ vec![
IndentGuide::new(buffer_id, 1, 6, 0, 4), indent_guide(buffer_id, 1, 6, 0),
IndentGuide::new(buffer_id, 3, 4, 1, 4), indent_guide(buffer_id, 3, 4, 1),
], ],
None, None,
&mut cx, &mut cx,
@ -11880,7 +11877,7 @@ async fn test_active_indent_guide_single_line(cx: &mut gpui::TestAppContext) {
assert_indent_guides( assert_indent_guides(
0..3, 0..3,
vec![IndentGuide::new(buffer_id, 1, 1, 0, 4)], vec![indent_guide(buffer_id, 1, 1, 0)],
Some(vec![0]), Some(vec![0]),
&mut cx, &mut cx,
); );
@ -11909,8 +11906,8 @@ async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppC
assert_indent_guides( assert_indent_guides(
0..4, 0..4,
vec![ vec![
IndentGuide::new(buffer_id, 1, 3, 0, 4), indent_guide(buffer_id, 1, 3, 0),
IndentGuide::new(buffer_id, 2, 2, 1, 4), indent_guide(buffer_id, 2, 2, 1),
], ],
Some(vec![1]), Some(vec![1]),
&mut cx, &mut cx,
@ -11925,8 +11922,8 @@ async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppC
assert_indent_guides( assert_indent_guides(
0..4, 0..4,
vec![ vec![
IndentGuide::new(buffer_id, 1, 3, 0, 4), indent_guide(buffer_id, 1, 3, 0),
IndentGuide::new(buffer_id, 2, 2, 1, 4), indent_guide(buffer_id, 2, 2, 1),
], ],
Some(vec![1]), Some(vec![1]),
&mut cx, &mut cx,
@ -11941,8 +11938,8 @@ async fn test_active_indent_guide_respect_indented_range(cx: &mut gpui::TestAppC
assert_indent_guides( assert_indent_guides(
0..4, 0..4,
vec![ vec![
IndentGuide::new(buffer_id, 1, 3, 0, 4), indent_guide(buffer_id, 1, 3, 0),
IndentGuide::new(buffer_id, 2, 2, 1, 4), indent_guide(buffer_id, 2, 2, 1),
], ],
Some(vec![0]), Some(vec![0]),
&mut cx, &mut cx,
@ -11971,7 +11968,7 @@ async fn test_active_indent_guide_empty_line(cx: &mut gpui::TestAppContext) {
assert_indent_guides( assert_indent_guides(
0..5, 0..5,
vec![IndentGuide::new(buffer_id, 1, 3, 0, 4)], vec![indent_guide(buffer_id, 1, 3, 0)],
Some(vec![0]), Some(vec![0]),
&mut cx, &mut cx,
); );
@ -11997,7 +11994,7 @@ async fn test_active_indent_guide_non_matching_indent(cx: &mut gpui::TestAppCont
assert_indent_guides( assert_indent_guides(
0..3, 0..3,
vec![IndentGuide::new(buffer_id, 1, 2, 0, 4)], vec![indent_guide(buffer_id, 1, 2, 0)],
Some(vec![0]), Some(vec![0]),
&mut cx, &mut cx,
); );

View file

@ -38,7 +38,7 @@ use gpui::{
}; };
use itertools::Itertools; use itertools::Itertools;
use language::language_settings::{ use language::language_settings::{
IndentGuideBackgroundColoring, IndentGuideColoring, ShowWhitespaceSetting, IndentGuideBackgroundColoring, IndentGuideColoring, IndentGuideSettings, ShowWhitespaceSetting,
}; };
use lsp::DiagnosticSeverity; use lsp::DiagnosticSeverity;
use multi_buffer::{Anchor, MultiBufferPoint, MultiBufferRow}; use multi_buffer::{Anchor, MultiBufferPoint, MultiBufferRow};
@ -1438,6 +1438,7 @@ impl EditorElement {
single_indent_width, single_indent_width,
depth: indent_guide.depth, depth: indent_guide.depth,
active: active_indent_guide_indices.contains(&i), active: active_indent_guide_indices.contains(&i),
settings: indent_guide.settings,
}) })
} else { } else {
None None
@ -2730,14 +2731,6 @@ impl EditorElement {
return; return;
}; };
let settings = self
.editor
.read(cx)
.buffer()
.read(cx)
.settings_at(0, cx)
.indent_guides;
let faded_color = |color: Hsla, alpha: f32| { let faded_color = |color: Hsla, alpha: f32| {
let mut faded = color; let mut faded = color;
faded.a = alpha; faded.a = alpha;
@ -2746,6 +2739,7 @@ impl EditorElement {
for indent_guide in indent_guides { for indent_guide in indent_guides {
let indent_accent_colors = cx.theme().accents().color_for_index(indent_guide.depth); let indent_accent_colors = cx.theme().accents().color_for_index(indent_guide.depth);
let settings = indent_guide.settings;
// TODO fixed for now, expose them through themes later // TODO fixed for now, expose them through themes later
const INDENT_AWARE_ALPHA: f32 = 0.2; const INDENT_AWARE_ALPHA: f32 = 0.2;
@ -2753,7 +2747,7 @@ impl EditorElement {
const INDENT_AWARE_BACKGROUND_ALPHA: f32 = 0.1; const INDENT_AWARE_BACKGROUND_ALPHA: f32 = 0.1;
const INDENT_AWARE_BACKGROUND_ACTIVE_ALPHA: f32 = 0.2; const INDENT_AWARE_BACKGROUND_ACTIVE_ALPHA: f32 = 0.2;
let line_color = match (&settings.coloring, indent_guide.active) { let line_color = match (settings.coloring, indent_guide.active) {
(IndentGuideColoring::Disabled, _) => None, (IndentGuideColoring::Disabled, _) => None,
(IndentGuideColoring::Fixed, false) => { (IndentGuideColoring::Fixed, false) => {
Some(cx.theme().colors().editor_indent_guide) Some(cx.theme().colors().editor_indent_guide)
@ -2769,7 +2763,7 @@ impl EditorElement {
} }
}; };
let background_color = match (&settings.background_coloring, indent_guide.active) { let background_color = match (settings.background_coloring, indent_guide.active) {
(IndentGuideBackgroundColoring::Disabled, _) => None, (IndentGuideBackgroundColoring::Disabled, _) => None,
(IndentGuideBackgroundColoring::IndentAware, false) => Some(faded_color( (IndentGuideBackgroundColoring::IndentAware, false) => Some(faded_color(
indent_accent_colors, indent_accent_colors,
@ -5286,6 +5280,7 @@ pub struct IndentGuideLayout {
single_indent_width: Pixels, single_indent_width: Pixels,
depth: u32, depth: u32,
active: bool, active: bool,
settings: IndentGuideSettings,
} }
pub struct CursorLayout { pub struct CursorLayout {

View file

@ -2,7 +2,7 @@ use std::{ops::Range, time::Duration};
use collections::HashSet; use collections::HashSet;
use gpui::{AppContext, Task}; use gpui::{AppContext, Task};
use language::BufferRow; use language::{language_settings::language_settings, BufferRow};
use multi_buffer::{MultiBufferIndentGuide, MultiBufferRow}; use multi_buffer::{MultiBufferIndentGuide, MultiBufferRow};
use text::{BufferId, LineIndent, Point}; use text::{BufferId, LineIndent, Point};
use ui::ViewContext; use ui::ViewContext;
@ -37,13 +37,26 @@ impl Editor {
snapshot: &DisplaySnapshot, snapshot: &DisplaySnapshot,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) -> Option<Vec<MultiBufferIndentGuide>> { ) -> Option<Vec<MultiBufferIndentGuide>> {
let enabled = self.should_show_indent_guides(cx); let show_indent_guides = self.should_show_indent_guides().unwrap_or_else(|| {
if let Some(buffer) = self.buffer().read(cx).as_singleton() {
if enabled { language_settings(buffer.read(cx).language(), buffer.read(cx).file(), cx)
Some(indent_guides_in_range(visible_buffer_range, snapshot, cx)) .indent_guides
.enabled
} else { } else {
None true
} }
});
if !show_indent_guides {
return None;
}
Some(indent_guides_in_range(
visible_buffer_range,
self.should_show_indent_guides() == Some(true),
snapshot,
cx,
))
} }
pub fn find_active_indent_guide_indices( pub fn find_active_indent_guide_indices(
@ -77,9 +90,14 @@ impl Editor {
if state.should_refresh() { if state.should_refresh() {
state.cursor_row = cursor_row; state.cursor_row = cursor_row;
let snapshot = snapshot.clone();
state.dirty = false; state.dirty = false;
if indent_guides.is_empty() {
return None;
}
let snapshot = snapshot.clone();
let task = cx let task = cx
.background_executor() .background_executor()
.spawn(resolve_indented_range(snapshot, cursor_row)); .spawn(resolve_indented_range(snapshot, cursor_row));
@ -131,6 +149,7 @@ impl Editor {
pub fn indent_guides_in_range( pub fn indent_guides_in_range(
visible_buffer_range: Range<MultiBufferRow>, visible_buffer_range: Range<MultiBufferRow>,
ignore_disabled_for_language: bool,
snapshot: &DisplaySnapshot, snapshot: &DisplaySnapshot,
cx: &AppContext, cx: &AppContext,
) -> Vec<MultiBufferIndentGuide> { ) -> Vec<MultiBufferIndentGuide> {
@ -143,7 +162,7 @@ pub fn indent_guides_in_range(
snapshot snapshot
.buffer_snapshot .buffer_snapshot
.indent_guides_in_range(start_anchor..end_anchor, cx) .indent_guides_in_range(start_anchor..end_anchor, ignore_disabled_for_language, cx)
.into_iter() .into_iter()
.filter(|indent_guide| { .filter(|indent_guide| {
// Filter out indent guides that are inside a fold // Filter out indent guides that are inside a fold

View file

@ -6,7 +6,7 @@ pub use crate::{
}; };
use crate::{ use crate::{
diagnostic_set::{DiagnosticEntry, DiagnosticGroup}, diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
language_settings::{language_settings, LanguageSettings}, language_settings::{language_settings, IndentGuideSettings, LanguageSettings},
markdown::parse_markdown, markdown::parse_markdown,
outline::OutlineItem, outline::OutlineItem,
syntax_map::{ syntax_map::{
@ -542,25 +542,10 @@ pub struct IndentGuide {
pub end_row: BufferRow, pub end_row: BufferRow,
pub depth: u32, pub depth: u32,
pub tab_size: u32, pub tab_size: u32,
pub settings: IndentGuideSettings,
} }
impl IndentGuide { impl IndentGuide {
pub fn new(
buffer_id: BufferId,
start_row: BufferRow,
end_row: BufferRow,
depth: u32,
tab_size: u32,
) -> Self {
Self {
buffer_id,
start_row,
end_row,
depth,
tab_size,
}
}
pub fn indent_level(&self) -> u32 { pub fn indent_level(&self) -> u32 {
self.depth * self.tab_size self.depth * self.tab_size
} }
@ -3151,9 +3136,15 @@ impl BufferSnapshot {
pub fn indent_guides_in_range( pub fn indent_guides_in_range(
&self, &self,
range: Range<Anchor>, range: Range<Anchor>,
ignore_disabled_for_language: bool,
cx: &AppContext, cx: &AppContext,
) -> Vec<IndentGuide> { ) -> Vec<IndentGuide> {
let tab_size = language_settings(self.language(), None, cx).tab_size.get() as u32; let language_settings = language_settings(self.language(), self.file.as_ref(), cx);
let settings = language_settings.indent_guides;
if !ignore_disabled_for_language && !settings.enabled {
return Vec::new();
}
let tab_size = language_settings.tab_size.get() as u32;
let start_row = range.start.to_point(self).row; let start_row = range.start.to_point(self).row;
let end_row = range.end.to_point(self).row; let end_row = range.end.to_point(self).row;
@ -3234,6 +3225,7 @@ impl BufferSnapshot {
end_row: last_row, end_row: last_row,
depth: next_depth, depth: next_depth,
tab_size, tab_size,
settings,
}); });
} }
} }

View file

@ -3289,12 +3289,17 @@ impl MultiBufferSnapshot {
pub fn indent_guides_in_range( pub fn indent_guides_in_range(
&self, &self,
range: Range<Anchor>, range: Range<Anchor>,
ignore_disabled_for_language: bool,
cx: &AppContext, cx: &AppContext,
) -> Vec<MultiBufferIndentGuide> { ) -> Vec<MultiBufferIndentGuide> {
// Fast path for singleton buffers, we can skip the conversion between offsets. // Fast path for singleton buffers, we can skip the conversion between offsets.
if let Some((_, _, snapshot)) = self.as_singleton() { if let Some((_, _, snapshot)) = self.as_singleton() {
return snapshot return snapshot
.indent_guides_in_range(range.start.text_anchor..range.end.text_anchor, cx) .indent_guides_in_range(
range.start.text_anchor..range.end.text_anchor,
ignore_disabled_for_language,
cx,
)
.into_iter() .into_iter()
.map(|guide| MultiBufferIndentGuide { .map(|guide| MultiBufferIndentGuide {
multibuffer_row_range: MultiBufferRow(guide.start_row) multibuffer_row_range: MultiBufferRow(guide.start_row)
@ -3314,7 +3319,11 @@ impl MultiBufferSnapshot {
excerpt excerpt
.buffer .buffer
.indent_guides_in_range(excerpt.range.context.clone(), cx) .indent_guides_in_range(
excerpt.range.context.clone(),
ignore_disabled_for_language,
cx,
)
.into_iter() .into_iter()
.map(move |indent_guide| { .map(move |indent_guide| {
let start_row = excerpt_offset_row let start_row = excerpt_offset_row