From e5086f306e730d01f6a5fffab16476e6057f56de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Aleixo?= Date: Mon, 20 Apr 2026 16:51:36 +0100 Subject: [PATCH] editor: Fix auto indent adding trailing whitespace on repeated enter (#52628) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update how auto indentation is handled by `Editor::newline`, in the case where auto-indentation is not `AutoIndentationMode::None`, so as to extend the range of the edits being applied to the beginning of the line, in case the line only contains the indentation whitespace or there's only whitespace indentation before the cursor position This ensures that newlines are not left with whitespace. Closes #34316 Release Notes: - Fixed auto indent leaving trailing whitespace on blank indented lines when creating new lines.⁠ --------- Co-authored-by: dino --- crates/editor/src/editor.rs | 16 ++++++- crates/editor/src/editor_tests.rs | 75 +++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f379d051e150f114227484dba380938f493ae550..d41db01368430b9273686dc9eff2d6d925410451 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5303,8 +5303,9 @@ impl Editor { let start_point = selection.start.to_point(&buffer); let mut existing_indent = buffer.indent_size_for_line(MultiBufferRow(start_point.row)); + let full_indent_len = existing_indent.len; existing_indent.len = cmp::min(existing_indent.len, start_point.column); - let start = selection.start; + let mut start = selection.start; let end = selection.end; let selection_is_empty = start == end; let language_scope = buffer.language_scope_at(start); @@ -5454,6 +5455,19 @@ impl Editor { } new_text.extend(extra_indent.chars()); } + // Extend the edit to the beginning of the line + // to clear auto-indent whitespace that would + // otherwise remain as trailing whitespace. This + // applies to blank lines and lines where only + // indentation remains before the cursor. + if selection_is_empty + && preserve_indent + && full_indent_len > 0 + && start_point.column == full_indent_len + { + start = buffer.point_to_offset(Point::new(start_point.row, 0)); + } + ( start, new_text, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 297a943455f0b3b6acb26af7879c949d6b308afe..66c97276c34221f11cc2ddbab338f49626714f89 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -3553,6 +3553,81 @@ fn test_newline(cx: &mut TestAppContext) { }); } +#[gpui::test] +fn test_newline_trailing_whitespace(cx: &mut TestAppContext) { + init_test(cx, |settings| { + settings.defaults.auto_indent = Some(settings::AutoIndentMode::PreserveIndent); + }); + + let buffer = cx.update(|cx| MultiBuffer::build_simple(" hello\n world\n", cx)); + let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx)); + + editor + .update(cx, |editor, window, cx| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_display_ranges([ + DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9) + ]) + }); + + editor.newline(&Newline, window, cx); + assert_eq!(editor.text(cx), " hello\n \n world\n"); + + editor.newline(&Newline, window, cx); + assert_eq!(editor.text(cx), " hello\n\n \n world\n"); + }) + .unwrap(); + + buffer.update(cx, |buffer, cx| { + let start = MultiBufferOffset(0); + let end = buffer.len(cx); + buffer.edit([(start..end, " hello\n world\n")], None, cx); + }); + + editor + .update(cx, |editor, window, cx| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_display_ranges([ + DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7) + ]) + }); + + editor.newline(&Newline, window, cx); + assert_eq!(editor.text(cx), " hel\n lo\n world\n"); + + editor.newline(&Newline, window, cx); + assert_eq!(editor.text(cx), " hel\n\n lo\n world\n"); + }) + .unwrap(); + + update_test_language_settings(cx, &|settings| { + settings.defaults.tab_size = NonZeroU32::new(4); + settings.defaults.hard_tabs = Some(true); + }); + + buffer.update(cx, |buffer, cx| { + let start = MultiBufferOffset(0); + let end = buffer.len(cx); + buffer.edit([(start..end, "\thello\n\tworld\n")], None, cx); + }); + + editor + .update(cx, |editor, window, cx| { + editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| { + s.select_display_ranges([ + DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9) + ]) + }); + + editor.newline(&Newline, window, cx); + assert_eq!(editor.text(cx), "\thello\n\t\n\tworld\n"); + + editor.newline(&Newline, window, cx); + assert_eq!(editor.text(cx), "\thello\n\n\t\n\tworld\n"); + }) + .unwrap(); +} + #[gpui::test] async fn test_newline_yaml(cx: &mut TestAppContext) { init_test(cx, |_| {});