Fix soft_wrap setting not applying to buffers starting with a different language (#25880)

Closes #22999 
# Problem

Currently, the default soft wrap mode of an editor is determined by
reading the language-specific settings of the language _at offset zero_
in the editor's (multi)buffer. While this provides a way to pick a
single soft wrap mode for a multi-language multibuffer, it's a bad
choice for a single-buffer multibuffer that begins with a different
embedded language. For example, Markdown with frontmatter:

```markdown
---
my_front_matter
---

# Hello World
```

Setting this in config:

```json
  "languages": {
    "Markdown": { "soft_wrap": "bounded" }
  },
```

Will not soft wrap the Markdown file as the language at offset zero is
YAML.

# Solution

Instead of using the language at offset zero, use the language of the
first buffer in the multibuffer (the buffer at offset zero). This gives
better behavior for single-buffer editors, and a similar default for
multi-language multibuffers as before.

# Testing

All existing `editor` crate tests pass, but I would appreciate any
guidance for where best to add additional testing.

Release Notes:

- Fixed soft_wrap setting not applying to buffers starting with a
different language

---------

Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
Alex Ozer 2025-03-04 10:55:27 -05:00 committed by GitHub
parent 909de2ca6f
commit 0ec15d6b02
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 64 additions and 25 deletions

View file

@ -2877,7 +2877,7 @@ impl Editor {
} }
if let Some(bracket_pair) = bracket_pair { if let Some(bracket_pair) = bracket_pair {
let snapshot_settings = snapshot.settings_at(selection.start, cx); let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
let autoclose = self.use_autoclose && snapshot_settings.use_autoclose; let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
let auto_surround = let auto_surround =
self.use_auto_surround && snapshot_settings.use_auto_surround; self.use_auto_surround && snapshot_settings.use_auto_surround;
@ -2942,7 +2942,7 @@ impl Editor {
} }
let always_treat_brackets_as_autoclosed = snapshot let always_treat_brackets_as_autoclosed = snapshot
.settings_at(selection.start, cx) .language_settings_at(selection.start, cx)
.always_treat_brackets_as_autoclosed; .always_treat_brackets_as_autoclosed;
if always_treat_brackets_as_autoclosed if always_treat_brackets_as_autoclosed
&& is_bracket_pair_end && is_bracket_pair_end
@ -3225,7 +3225,7 @@ impl Editor {
return None; return None;
} }
if !multi_buffer.settings_at(0, cx).extend_comment_on_newline { if !multi_buffer.language_settings(cx).extend_comment_on_newline {
return None; return None;
} }
@ -3554,7 +3554,7 @@ impl Editor {
} }
let always_treat_brackets_as_autoclosed = buffer let always_treat_brackets_as_autoclosed = buffer
.settings_at(selection.start, cx) .language_settings_at(selection.start, cx)
.always_treat_brackets_as_autoclosed; .always_treat_brackets_as_autoclosed;
if !always_treat_brackets_as_autoclosed { if !always_treat_brackets_as_autoclosed {
@ -7328,7 +7328,7 @@ impl Editor {
} }
// Otherwise, insert a hard or soft tab. // Otherwise, insert a hard or soft tab.
let settings = buffer.settings_at(cursor, cx); let settings = buffer.language_settings_at(cursor, cx);
let tab_size = if settings.hard_tabs { let tab_size = if settings.hard_tabs {
IndentSize::tab() IndentSize::tab()
} else { } else {
@ -7392,7 +7392,7 @@ impl Editor {
delta_for_start_row: u32, delta_for_start_row: u32,
cx: &App, cx: &App,
) -> u32 { ) -> u32 {
let settings = buffer.settings_at(selection.start, cx); let settings = buffer.language_settings_at(selection.start, cx);
let tab_size = settings.tab_size.get(); let tab_size = settings.tab_size.get();
let indent_kind = if settings.hard_tabs { let indent_kind = if settings.hard_tabs {
IndentKind::Tab IndentKind::Tab
@ -7472,7 +7472,7 @@ impl Editor {
let buffer = self.buffer.read(cx); let buffer = self.buffer.read(cx);
let snapshot = buffer.snapshot(cx); let snapshot = buffer.snapshot(cx);
for selection in &selections { for selection in &selections {
let settings = buffer.settings_at(selection.start, cx); let settings = buffer.language_settings_at(selection.start, cx);
let tab_size = settings.tab_size.get(); let tab_size = settings.tab_size.get();
let mut rows = selection.spanned_rows(false, &display_map); let mut rows = selection.spanned_rows(false, &display_map);
@ -8484,7 +8484,7 @@ impl Editor {
continue; continue;
} }
let tab_size = buffer.settings_at(selection.head(), cx).tab_size; let tab_size = buffer.language_settings_at(selection.head(), cx).tab_size;
// Since not all lines in the selection may be at the same indent // Since not all lines in the selection may be at the same indent
// level, choose the indent size that is the most common between all // level, choose the indent size that is the most common between all
@ -8530,7 +8530,7 @@ impl Editor {
inside_comment = true; inside_comment = true;
} }
let language_settings = buffer.settings_at(selection.head(), cx); let language_settings = buffer.language_settings_at(selection.head(), cx);
let allow_rewrap_based_on_language = match language_settings.allow_rewrap { let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
RewrapBehavior::InComments => inside_comment, RewrapBehavior::InComments => inside_comment,
RewrapBehavior::InSelections => !selection.is_empty(), RewrapBehavior::InSelections => !selection.is_empty(),
@ -8586,7 +8586,7 @@ impl Editor {
}; };
let wrap_column = buffer let wrap_column = buffer
.settings_at(Point::new(start_row, 0), cx) .language_settings_at(Point::new(start_row, 0), cx)
.preferred_line_length as usize; .preferred_line_length as usize;
let wrapped_text = wrap_with_prefix( let wrapped_text = wrap_with_prefix(
line_prefix, line_prefix,
@ -8772,8 +8772,9 @@ impl Editor {
this.buffer.update(cx, |buffer, cx| { this.buffer.update(cx, |buffer, cx| {
let snapshot = buffer.read(cx); let snapshot = buffer.read(cx);
auto_indent_on_paste = auto_indent_on_paste = snapshot
snapshot.settings_at(cursor_offset, cx).auto_indent_on_paste; .language_settings_at(cursor_offset, cx)
.auto_indent_on_paste;
let mut start_offset = 0; let mut start_offset = 0;
let mut edits = Vec::new(); let mut edits = Vec::new();
@ -14082,13 +14083,17 @@ impl Editor {
return wrap_guides; return wrap_guides;
} }
let settings = self.buffer.read(cx).settings_at(0, cx); let settings = self.buffer.read(cx).language_settings(cx);
if settings.show_wrap_guides { if settings.show_wrap_guides {
if let SoftWrap::Column(soft_wrap) = self.soft_wrap_mode(cx) { match self.soft_wrap_mode(cx) {
SoftWrap::Column(soft_wrap) => {
wrap_guides.push((soft_wrap as usize, true)); wrap_guides.push((soft_wrap as usize, true));
} else if let SoftWrap::Bounded(soft_wrap) = self.soft_wrap_mode(cx) { }
SoftWrap::Bounded(soft_wrap) => {
wrap_guides.push((soft_wrap as usize, true)); wrap_guides.push((soft_wrap as usize, true));
} }
SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
}
wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false))) wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
} }
@ -14096,7 +14101,7 @@ impl Editor {
} }
pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap { pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
let settings = self.buffer.read(cx).settings_at(0, cx); let settings = self.buffer.read(cx).language_settings(cx);
let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap); let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
match mode { match mode {
language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => { language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
@ -14195,7 +14200,7 @@ impl Editor {
let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| { let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
self.buffer self.buffer
.read(cx) .read(cx)
.settings_at(0, cx) .language_settings(cx)
.indent_guides .indent_guides
.enabled .enabled
}); });
@ -15844,7 +15849,7 @@ impl Editor {
let copilot_enabled_for_language = self let copilot_enabled_for_language = self
.buffer .buffer
.read(cx) .read(cx)
.settings_at(0, cx) .language_settings(cx)
.show_edit_predictions; .show_edit_predictions;
let project = project.read(cx); let project = project.read(cx);

View file

@ -4685,7 +4685,7 @@ impl EditorElement {
.read(cx) .read(cx)
.buffer .buffer
.read(cx) .read(cx)
.settings_at(0, cx) .language_settings(cx)
.show_whitespaces; .show_whitespaces;
for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() { for (ix, line_with_invisibles) in layout.position_map.line_layouts.iter().enumerate() {

View file

@ -2307,7 +2307,27 @@ impl MultiBuffer {
.and_then(|(buffer, offset)| buffer.read(cx).language_at(offset)) .and_then(|(buffer, offset)| buffer.read(cx).language_at(offset))
} }
pub fn settings_at<'a, T: ToOffset>(&self, point: T, cx: &'a App) -> Cow<'a, LanguageSettings> { pub fn language_settings<'a>(&'a self, cx: &'a App) -> Cow<'a, LanguageSettings> {
let buffer_id = self
.snapshot
.borrow()
.excerpts
.first()
.map(|excerpt| excerpt.buffer.remote_id());
buffer_id
.and_then(|buffer_id| self.buffer(buffer_id))
.map(|buffer| {
let buffer = buffer.read(cx);
language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
})
.unwrap_or_else(move || self.language_settings_at(0, cx))
}
pub fn language_settings_at<'a, T: ToOffset>(
&'a self,
point: T,
cx: &'a App,
) -> Cow<'a, LanguageSettings> {
let mut language = None; let mut language = None;
let mut file = None; let mut file = None;
if let Some((buffer, offset)) = self.point_to_buffer_offset(point, cx) { if let Some((buffer, offset)) = self.point_to_buffer_offset(point, cx) {
@ -4259,7 +4279,7 @@ impl MultiBufferSnapshot {
pub fn indent_and_comment_for_line(&self, row: MultiBufferRow, cx: &App) -> String { pub fn indent_and_comment_for_line(&self, row: MultiBufferRow, cx: &App) -> String {
let mut indent = self.indent_size_for_line(row).chars().collect::<String>(); let mut indent = self.indent_size_for_line(row).chars().collect::<String>();
if self.settings_at(0, cx).extend_comment_on_newline { if self.language_settings(cx).extend_comment_on_newline {
if let Some(language_scope) = self.language_scope_at(Point::new(row.0, 0)) { if let Some(language_scope) = self.language_scope_at(Point::new(row.0, 0)) {
let delimiters = language_scope.line_comment_prefixes(); let delimiters = language_scope.line_comment_prefixes();
for delimiter in delimiters { for delimiter in delimiters {
@ -5592,7 +5612,21 @@ impl MultiBufferSnapshot {
.and_then(|(buffer, offset)| buffer.language_at(offset)) .and_then(|(buffer, offset)| buffer.language_at(offset))
} }
pub fn settings_at<'a, T: ToOffset>( fn language_settings<'a>(&'a self, cx: &'a App) -> Cow<'a, LanguageSettings> {
self.excerpts
.first()
.map(|excerpt| &excerpt.buffer)
.map(|buffer| {
language_settings(
buffer.language().map(|language| language.name()),
buffer.file(),
cx,
)
})
.unwrap_or_else(move || self.language_settings_at(0, cx))
}
pub fn language_settings_at<'a, T: ToOffset>(
&'a self, &'a self,
point: T, point: T,
cx: &'a App, cx: &'a App,

View file

@ -160,7 +160,7 @@ impl Vim {
.buffer() .buffer()
.read(cx) .read(cx)
.snapshot(cx) .snapshot(cx)
.settings_at(cursor_offset, cx) .language_settings_at(cursor_offset, cx)
.auto_indent_on_paste .auto_indent_on_paste
{ {
editor.edit_with_block_indent(edits, original_start_columns, cx); editor.edit_with_block_indent(edits, original_start_columns, cx);