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>) {
let currently_enabled = self.should_show_indent_guides(cx);
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(|| {
let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
self.buffer
.read(cx)
.settings_at(0, cx)
.indent_guides
.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>) {

View File

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

View File

@ -38,7 +38,7 @@ use gpui::{
};
use itertools::Itertools;
use language::language_settings::{
IndentGuideBackgroundColoring, IndentGuideColoring, ShowWhitespaceSetting,
IndentGuideBackgroundColoring, IndentGuideColoring, IndentGuideSettings, ShowWhitespaceSetting,
};
use lsp::DiagnosticSeverity;
use multi_buffer::{Anchor, MultiBufferPoint, MultiBufferRow};
@ -1438,6 +1438,7 @@ impl EditorElement {
single_indent_width,
depth: indent_guide.depth,
active: active_indent_guide_indices.contains(&i),
settings: indent_guide.settings,
})
} else {
None
@ -2730,14 +2731,6 @@ impl EditorElement {
return;
};
let settings = self
.editor
.read(cx)
.buffer()
.read(cx)
.settings_at(0, cx)
.indent_guides;
let faded_color = |color: Hsla, alpha: f32| {
let mut faded = color;
faded.a = alpha;
@ -2746,6 +2739,7 @@ impl EditorElement {
for indent_guide in indent_guides {
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
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_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::Fixed, false) => {
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::IndentAware, false) => Some(faded_color(
indent_accent_colors,
@ -5286,6 +5280,7 @@ pub struct IndentGuideLayout {
single_indent_width: Pixels,
depth: u32,
active: bool,
settings: IndentGuideSettings,
}
pub struct CursorLayout {

View File

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

View File

@ -6,7 +6,7 @@ pub use crate::{
};
use crate::{
diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
language_settings::{language_settings, LanguageSettings},
language_settings::{language_settings, IndentGuideSettings, LanguageSettings},
markdown::parse_markdown,
outline::OutlineItem,
syntax_map::{
@ -542,25 +542,10 @@ pub struct IndentGuide {
pub end_row: BufferRow,
pub depth: u32,
pub tab_size: u32,
pub settings: IndentGuideSettings,
}
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 {
self.depth * self.tab_size
}
@ -3151,9 +3136,15 @@ impl BufferSnapshot {
pub fn indent_guides_in_range(
&self,
range: Range<Anchor>,
ignore_disabled_for_language: bool,
cx: &AppContext,
) -> 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 end_row = range.end.to_point(self).row;
@ -3234,6 +3225,7 @@ impl BufferSnapshot {
end_row: last_row,
depth: next_depth,
tab_size,
settings,
});
}
}

View File

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