Skip to content

fix(indent): re-indent dedent keywords as they are typed (#2582) - #2644

Open
sinelaw wants to merge 1 commit into
masterfrom
claude/compassionate-newton-5qlbzs
Open

fix(indent): re-indent dedent keywords as they are typed (#2582)#2644
sinelaw wants to merge 1 commit into
masterfrom
claude/compassionate-newton-5qlbzs

Conversation

@sinelaw

@sinelaw sinelaw commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Fixes #2582.

Problem

Live "electric" dedent only fired for closing brackets (}). Typing a keyword that matches a language's decrease_indent_pattern — Python else:/elif/except/finally/case, Ruby/Lua/Fish/Pascal end, Bash fi/done/esac, or a user's custom token — left the line stuck at its inherited indent:

if a:
    x = 1
    else:      ← stays over-indented

The error then compounded: pressing Enter after the mis-indented else: indented the next line another level deeper, so the whole block drifted right. Dedent rules were applied only when Enter split text onto a new line, never when the keyword was typed — even though Fresh already gave } this treatment, and the docs' own decrease_indent_pattern examples (end, CLOSE) are tokens you type at the end of a block, never Enter-split.

Fix

  • Add IndentRules::calculate_dedent_for_typed_char in indent_rules.rs — the keyword counterpart of the existing calculate_dedent_for_delimiter (which handles brackets). It matches the line's leading content (after the just-typed char) against the language's decrease rule and returns the target indent, aligned to the reference line one level up.
  • Hook it into the character-insert path (insert_char_events) so a completed dedent keyword snaps the line one level shallower, mirroring the existing closing-bracket handle_auto_dedent.

The hook is deliberately conservative:

  • only when there's no active selection and the cursor is at the end of the line's content (the way these keywords are actually typed), so it never fires while editing mid-line;
  • idempotent — it no-ops when the line is already at the correct indent, so it adds no redundant undo step on subsequent keystrokes.

Only the per-language regex rules tier is touched; curly-brace languages keep their bracket-only decrease rule (their } is handled by the existing path), so nothing changes for them.

Testing

  • Unit tests (indent_rules.rs): else/except/finally/case (Python, incl. nested), end (Ruby/Lua), fi/done (Bash); plus negatives — an identifier like elsewhere, an ordinary statement, a curly-brace language, and a first line with no reference above.
  • End-to-end typing tests (actions.rs): drive the production Action::InsertChar pipeline key-by-key; verify else: dedents to column 0 and that the following Enter indents the body to column 4 (not the old drifted 8).
  • Manual validation: ran the built fresh binary in tmux on a Python file, typed else:, and confirmed via capture-pane that the line dedents to column 0 and its body lands at column 4.
  • cargo fmt, cargo clippy, and cargo check --all-targets all clean; existing indent / auto-pair / auto-indent scenario suites pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DvjnQtGxsMn3UKywougFDd


Generated by Claude Code

Live "electric" dedent only fired for closing brackets (`}`): typing a
keyword that matches a language's `decrease_indent_pattern` — Python
`else:`/`elif`/`except`/`finally`/`case`, Ruby/Lua/Fish/Pascal `end`,
Bash `fi`/`done`/`esac`, or a user's custom token — left the line stuck
at its inherited indent until the next Enter or save. The mis-indent then
compounded: pressing Enter after the over-indented keyword pushed the
whole block another level right.

Add `IndentRules::calculate_dedent_for_typed_char`, the keyword
counterpart of the existing `calculate_dedent_for_delimiter`, and hook it
into the character-insert path so a completed dedent keyword snaps the
line one level shallower — the same correction brackets already got. The
hook is gated to the unambiguous case (no active selection, cursor at end
of the line's content) and is idempotent, so it adds no redundant undo
step once the indent is correct.

Covered by unit tests on the rules method across families and by
end-to-end typing tests that drive the production insert pipeline.
@sinelaw
sinelaw force-pushed the claude/compassionate-newton-5qlbzs branch from e1934a7 to eac3c32 Compare July 17, 2026 11:56
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.

Typing a dedent trigger does not re-indent the line: Python else: and custom decrease_indent_pattern tokens stay over-indented (only } dedents live)

2 participants