Add option to disable auto indentation (#36259)

Closes https://github.com/zed-industries/zed/issues/11780

While auto indentation is generally nice to have, there are cases where
it is currently just not good enough for some languages (e.g. Haskell)
or users just straight up do not want their editor to auto indent for
them. Hence, this PR adds the possibilty to disable auto indentation for
either all language or on a per-language basis. Manual invocation via
the `editor: auto indent` action will continue to work.

Also takes a similar approach as
https://github.com/zed-industries/zed/pull/31569 to ensure performance
is fine for larger multicursor edits.

Release Notes:

- Added the possibility to configure auto indentation for all languages
and per language. Add `"auto_indent": false"` to your settings or
desired language to disable the feature.
This commit is contained in:
Finn Evers 2025-08-18 14:21:33 +02:00 committed by GitHub
parent 5225844c9e
commit 1add1d042d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 250 additions and 14 deletions

View file

@ -286,6 +286,8 @@
// bracket, brace, single or double quote characters.
// For example, when you select text and type (, Zed will surround the text with ().
"use_auto_surround": true,
/// Whether indentation should be adjusted based on the context whilst typing.
"auto_indent": true,
// Whether indentation of pasted content should be adjusted based on the context.
"auto_indent_on_paste": true,
// Controls how the editor handles the autoclosed characters.

View file

@ -8214,6 +8214,216 @@ async fn test_autoindent(cx: &mut TestAppContext) {
});
}
#[gpui::test]
async fn test_autoindent_disabled(cx: &mut TestAppContext) {
init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
let language = Arc::new(
Language::new(
LanguageConfig {
brackets: BracketPairConfig {
pairs: vec![
BracketPair {
start: "{".to_string(),
end: "}".to_string(),
close: false,
surround: false,
newline: true,
},
BracketPair {
start: "(".to_string(),
end: ")".to_string(),
close: false,
surround: false,
newline: true,
},
],
..Default::default()
},
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_indents_query(
r#"
(_ "(" ")" @end) @indent
(_ "{" "}" @end) @indent
"#,
)
.unwrap(),
);
let text = "fn a() {}";
let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
editor
.condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
.await;
editor.update_in(cx, |editor, window, cx| {
editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
s.select_ranges([5..5, 8..8, 9..9])
});
editor.newline(&Newline, window, cx);
assert_eq!(
editor.text(cx),
indoc!(
"
fn a(
) {
}
"
)
);
assert_eq!(
editor.selections.ranges(cx),
&[
Point::new(1, 0)..Point::new(1, 0),
Point::new(3, 0)..Point::new(3, 0),
Point::new(5, 0)..Point::new(5, 0)
]
);
});
}
#[gpui::test]
async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
init_test(cx, |settings| {
settings.defaults.auto_indent = Some(true);
settings.languages.0.insert(
"python".into(),
LanguageSettingsContent {
auto_indent: Some(false),
..Default::default()
},
);
});
let mut cx = EditorTestContext::new(cx).await;
let injected_language = Arc::new(
Language::new(
LanguageConfig {
brackets: BracketPairConfig {
pairs: vec![
BracketPair {
start: "{".to_string(),
end: "}".to_string(),
close: false,
surround: false,
newline: true,
},
BracketPair {
start: "(".to_string(),
end: ")".to_string(),
close: true,
surround: false,
newline: true,
},
],
..Default::default()
},
name: "python".into(),
..Default::default()
},
Some(tree_sitter_python::LANGUAGE.into()),
)
.with_indents_query(
r#"
(_ "(" ")" @end) @indent
(_ "{" "}" @end) @indent
"#,
)
.unwrap(),
);
let language = Arc::new(
Language::new(
LanguageConfig {
brackets: BracketPairConfig {
pairs: vec![
BracketPair {
start: "{".to_string(),
end: "}".to_string(),
close: false,
surround: false,
newline: true,
},
BracketPair {
start: "(".to_string(),
end: ")".to_string(),
close: true,
surround: false,
newline: true,
},
],
..Default::default()
},
name: LanguageName::new("rust"),
..Default::default()
},
Some(tree_sitter_rust::LANGUAGE.into()),
)
.with_indents_query(
r#"
(_ "(" ")" @end) @indent
(_ "{" "}" @end) @indent
"#,
)
.unwrap()
.with_injection_query(
r#"
(macro_invocation
macro: (identifier) @_macro_name
(token_tree) @injection.content
(#set! injection.language "python"))
"#,
)
.unwrap(),
);
cx.language_registry().add(injected_language);
cx.language_registry().add(language.clone());
cx.update_buffer(|buffer, cx| {
buffer.set_language(Some(language), cx);
});
cx.set_state(&r#"struct A {ˇ}"#);
cx.update_editor(|editor, window, cx| {
editor.newline(&Default::default(), window, cx);
});
cx.assert_editor_state(indoc!(
"struct A {
ˇ
}"
));
cx.set_state(&r#"select_biased!(ˇ)"#);
cx.update_editor(|editor, window, cx| {
editor.newline(&Default::default(), window, cx);
editor.handle_input("def ", window, cx);
editor.handle_input("(", window, cx);
editor.newline(&Default::default(), window, cx);
editor.handle_input("a", window, cx);
});
cx.assert_editor_state(indoc!(
"select_biased!(
def (
)
)"
));
}
#[gpui::test]
async fn test_autoindent_selections(cx: &mut TestAppContext) {
init_test(cx, |_| {});

View file

@ -2271,16 +2271,14 @@ impl Buffer {
}
let new_text = new_text.into();
if !new_text.is_empty() || !range.is_empty() {
if let Some((prev_range, prev_text)) = edits.last_mut() {
if prev_range.end >= range.start {
if let Some((prev_range, prev_text)) = edits.last_mut()
&& prev_range.end >= range.start
{
prev_range.end = cmp::max(prev_range.end, range.end);
*prev_text = format!("{prev_text}{new_text}").into();
} else {
edits.push((range, new_text));
}
} else {
edits.push((range, new_text));
}
}
}
if edits.is_empty() {
@ -2297,10 +2295,27 @@ impl Buffer {
if let Some((before_edit, mode)) = autoindent_request {
let mut delta = 0isize;
let entries = edits
let mut previous_setting = None;
let entries: Vec<_> = edits
.into_iter()
.enumerate()
.zip(&edit_operation.as_edit().unwrap().new_text)
.filter(|((_, (range, _)), _)| {
let language = before_edit.language_at(range.start);
let language_id = language.map(|l| l.id());
if let Some((cached_language_id, auto_indent)) = previous_setting
&& cached_language_id == language_id
{
auto_indent
} else {
// The auto-indent setting is not present in editorconfigs, hence
// we can avoid passing the file here.
let auto_indent =
language_settings(language.map(|l| l.name()), None, cx).auto_indent;
previous_setting = Some((language_id, auto_indent));
auto_indent
}
})
.map(|((ix, (range, _)), new_text)| {
let new_text_length = new_text.len();
let old_start = range.start.to_point(&before_edit);
@ -2374,6 +2389,7 @@ impl Buffer {
})
.collect();
if !entries.is_empty() {
self.autoindent_requests.push(Arc::new(AutoindentRequest {
before_edit,
entries,
@ -2381,6 +2397,7 @@ impl Buffer {
ignore_empty_lines: false,
}));
}
}
self.end_transaction(cx);
self.send_operation(Operation::Buffer(edit_operation), true, cx);

View file

@ -133,6 +133,8 @@ pub struct LanguageSettings {
/// Whether to use additional LSP queries to format (and amend) the code after
/// every "trigger" symbol input, defined by LSP server capabilities.
pub use_on_type_format: bool,
/// Whether indentation should be adjusted based on the context whilst typing.
pub auto_indent: bool,
/// Whether indentation of pasted content should be adjusted based on the context.
pub auto_indent_on_paste: bool,
/// Controls how the editor handles the autoclosed characters.
@ -561,6 +563,10 @@ pub struct LanguageSettingsContent {
///
/// Default: true
pub linked_edits: Option<bool>,
/// Whether indentation should be adjusted based on the context whilst typing.
///
/// Default: true
pub auto_indent: Option<bool>,
/// Whether indentation of pasted content should be adjusted based on the context.
///
/// Default: true
@ -1517,6 +1523,7 @@ fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent
merge(&mut settings.use_autoclose, src.use_autoclose);
merge(&mut settings.use_auto_surround, src.use_auto_surround);
merge(&mut settings.use_on_type_format, src.use_on_type_format);
merge(&mut settings.auto_indent, src.auto_indent);
merge(&mut settings.auto_indent_on_paste, src.auto_indent_on_paste);
merge(
&mut settings.always_treat_brackets_as_autoclosed,