@@ -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 (
+ aˇ
+ )
+ )"
+ ));
+}
+
#[gpui::test]
async fn test_autoindent_selections(cx: &mut TestAppContext) {
init_test(cx, |_| {});
@@ -2271,13 +2271,11 @@ 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 {
- prev_range.end = cmp::max(prev_range.end, range.end);
- *prev_text = format!("{prev_text}{new_text}").into();
- } else {
- edits.push((range, new_text));
- }
+ 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));
}
@@ -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,12 +2389,14 @@ impl Buffer {
})
.collect();
- self.autoindent_requests.push(Arc::new(AutoindentRequest {
- before_edit,
- entries,
- is_block_mode: matches!(mode, AutoindentMode::Block { .. }),
- ignore_empty_lines: false,
- }));
+ if !entries.is_empty() {
+ self.autoindent_requests.push(Arc::new(AutoindentRequest {
+ before_edit,
+ entries,
+ is_block_mode: matches!(mode, AutoindentMode::Block { .. }),
+ ignore_empty_lines: false,
+ }));
+ }
}
self.end_transaction(cx);
@@ -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,