Skip to content

fix(indent): don't pull deeper indent onto blank line above unindented sibling - #1922

Merged
sinelaw merged 4 commits into
masterfrom
claude/wonderful-archimedes-0e5SR
Aug 10, 2026
Merged

fix(indent): don't pull deeper indent onto blank line above unindented sibling#1922
sinelaw merged 4 commits into
masterfrom
claude/wonderful-archimedes-0e5SR

Conversation

@sinelaw

@sinelaw sinelaw commented May 9, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the corner case reported on issue #1425 after the original fix shipped.

Pressing Enter on an empty line that sits between a deeply-indented block above and an unindented line below was auto-indenting the new line to the previous block's indent — even though the very next non-empty line is already at column 0:

    line1
    line2
        line3
        line4
|                    ← cursor here, press Enter
unindented line

Before this PR the new line was indented to column 8 (matching line4); after this PR it stays at column 0.

Why the previous fix didn't catch this

The original #1425 fix (indent_for_cursor_in_leading_ws) only fires when the cursor is on a non-empty line whose leading whitespace would otherwise be displaced. On a fully empty line the heuristic in calculate_indent_pattern looked only backwards for a reference line and copied its indent (or +tab_size if the previous line ended with {/[/(/:). It never consulted what comes after the cursor, so the deeper context above always won.

Fix

Add a forward look-ahead in the empty-line branch of calculate_indent_pattern: find the first non-empty line at or after the cursor and, if its first non-whitespace byte is not a closing delimiter (}, ], )) and its indent is strictly smaller than the previous-line reference, use the smaller indent instead.

Closing delimiters are excluded so that the existing test_indent_on_empty_line_uses_reference case still indents into the function body:

fn main() {
    let x = 1;
|                    ← still indents to 4 (next non-empty is `}`, ignored)
}

Test plan

  • New regression test test_enter_on_blank_line_above_unindented_uses_following_indent fails (returns 8) before the fix, passes after
  • All 33 existing primitives::indent tests still pass — including test_indent_on_empty_line_uses_reference (the inside-a-block case), test_indent_after_empty_line_incomplete_syntax, and the original Auto Indent is too diligent #1425 regression test_enter_at_start_of_unindented_line_after_blank_does_not_indent
  • Full cargo test -p fresh-editor --lib passes (2428 tests)
  • cargo fmt --all -- --check clean
  • cargo clippy -p fresh-editor --lib --no-deps introduces no new warnings on indent.rs
  • Manual validation in tmux with the actual fresh binary:
    • Plain text reproducer (the user's exact case): cursor lands at Ln N+1, Col 1 (not Col 9) ✓
    • Rust file fn main() { let x = 1; <empty> }: pressing Enter on the empty line still indents to Col 5 (4-space body indent) ✓
    • Original Auto Indent is too diligent #1425 case (Enter at start of unindented line after blank): unindented line is not displaced ✓

https://claude.ai/code/session_01Rf7NU26qRrBim3DPt12eqw


Generated by Claude Code

@sinelaw
sinelaw force-pushed the claude/wonderful-archimedes-0e5SR branch from 654d9df to d787128 Compare June 3, 2026 19:05
@sinelaw
sinelaw force-pushed the claude/wonderful-archimedes-0e5SR branch 3 times, most recently from 4739853 to 46ec8c3 Compare June 23, 2026 04:52
claude added 2 commits July 17, 2026 11:53
…d sibling

Pressing Enter on an empty line that sits between a deeply-indented block
above and an unindented line below was inserting indentation matching the
previous block, displacing the new cursor to a column the user had already
clearly exited. The empty-line heuristic in `calculate_indent_pattern`
looked only backwards for a reference line; if that reference happened to
be inside a nested block while the *next* non-empty line was at column 0,
the new line still inherited the deeper indent.

Add a forward look-ahead: if the next non-empty line is at a strictly
smaller indent and does not start with a closing delimiter (`}`, `]`,
`)`), use that smaller indent instead. Closing delimiters are excluded so
that the existing "cursor on blank line inside a still-open block" case
(e.g. `fn main() { … <empty> … }`) still indents into the body.

Reported as a corner case on issue #1425 after the original fix shipped.
…or's own indent

Address review feedback on the previous fix. The empty-line branch in
`calculate_indent_pattern` previously combined two heuristics — look back
for a "reference" indent from the previous non-empty line, then
look ahead for a less-indented sibling (excluding hardcoded `}`/`]`/`)`
closers) — that fought each other. Both guess what the user "really
meant" from context they have not actually typed on the cursor's line,
and the C-family-only closer list is poorly motivated for plain text
and for languages with non-bracket closers (Lua `end`, Bash `fi`,
HTML `</tag>`, etc.).

Replace both with the simpler rule: when the cursor is on an empty or
whitespace-only line, the new line copies that line's own indent. For
a truly empty line that is 0; for a line with N columns of leading
whitespace it is N.

This removes the C-family token list entirely and is fully
language-agnostic. The well-formed "still inside an open block" case
remains handled upstream by tree-sitter; for incomplete syntax the user
can press Tab when they want indentation, instead of the editor
guessing wrong half the time.

Two existing assertions that pinned the old guess (`fn main() { let x =
1;\n\n}` and the same with no closing brace) are updated to reflect the
new semantic and renamed to describe what they actually pin down.
@sinelaw
sinelaw force-pushed the claude/wonderful-archimedes-0e5SR branch from 46ec8c3 to 6bb700d Compare July 17, 2026 11:53
claude added 2 commits August 10, 2026 12:00
…use cursor's own indent"

This reverts commit 6bb700d.

The refactor went beyond the targeted #1425 corner-case fix and changed
the empty-line indent contract: it expected Enter on an empty line inside
a function body to produce indent 0, while the tree-sitter indent path
(by design, per the refactor's own comment) still returns the body indent.
That contradiction broke the refactor's own unit tests
(test_indent_on_empty_line_does_not_pull_block_indent,
test_indent_after_empty_line_incomplete_syntax_does_not_guess_body)
and the pre-existing e2e test
test_indent_after_empty_line_in_function_body on all CI platforms.

Keeping the first commit's forward look-ahead fix, which addresses the
reported corner case without changing the inside-a-block behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSg2p6UXLuJwXSs79Ko2Ma
@sinelaw
sinelaw merged commit 9a65d77 into master Aug 10, 2026
10 checks passed
@sinelaw
sinelaw deleted the claude/wonderful-archimedes-0e5SR branch August 10, 2026 14:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants