FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Indentation, folding, and symbol list for modern Elixir by aesmail · Pull Request #4 · kaffeinated-dev/elixir-tmbundle · GitHub

Indentation, folding, and symbol list for modern Elixir - #4

Merged
aesmail merged 4 commits into
masterfrom
editing-behavior
Sep 27, 2026
Merged

aesmail merged 4 commits into
masterfrom
editing-behavior

Conversation

aesmail commented Sep 27, 2026 •
edited
Loading

Copy link
Copy Markdown

Step 2 of the plan: editing behavior. Each change was measured against the formatted code of Elixir 1.20.4, Phoenix, LiveView and Ecto (1,037 files, 281k code lines), using ports of TextMate's own algorithms. It's covered by a new test that runs in CI.

Indentation

  • Indent: do, -> and open brackets at the end of a line indent the next line. Before, ( never indented and ) never dedented.
  • Dedent: end and closing brackets dedent, including when more follows them (end), },, ])).
  • Branches: else, rescue, catch and after close one branch and open the next.
  • Continuations: a binary operator at the end of a line (x =, <>, and, when, ::) continues the expression on the next line only.
  • Fewer false matches: trailing comments are allowed. alias MyApp.Todo no longer indents the next line (it did because "Todo" ends in "do"), and a line ending in ~r/.../ isn't treated as ending in /.
  • Comments: comment lines don't affect indentation, so # what to do doesn't indent the next line.
  • Templates: HEEx and HTML (EEx) templates indent inside open tags, multi-line open tags and <%= … do %> blocks.

Result: a typed line gets the formatter's indentation 87.3% of the time, up from 82.3%. Inside ~H templates it's 94.4%, up from 49%. The misses are layouts TextMate's indentation can't express:

  • the next clause after a multi-line clause body;
  • statements after an x = continuation that spans several lines;
  • arguments the formatter aligns to a column.

Folding

  • Blocks by indentation: a line that opens a block folds the lines indented below it, which suits how formatted Elixir nests. It adds folds that were missing before:
    • the if branch before else (before, only the else branch folded);
    • case clauses;
    • multi-line brackets closed by }, or ]).
  • Heredocs, docs and templates: they fold from the opening line to the closing delimiter. The opening line is told apart from the closing one by the scope at the end of the line.
  • Templates: template lines fold by their tags.

Result:

Line ending in Folds
do 99.9%
-> 99.98%
open bracket 97%

Heredocs whose closing delimiter is aligned with the opening line fold 98% of the time. Gutter markers that fold nothing drop from 23% to 3.9%. TextMate shows a marker on every line that matches a start pattern, so block starts are specific patterns, not "any line".

The grammar's foldingStartMarker/foldingStopMarker are removed. TextMate only used them when no preference sets fold patterns.

Symbol list

  • ExUnit: describe and test blocks are listed by name, with tests indented. The grammar gives their names a meta.describe/meta.test scope.
  • Types: @type, @typep and @opaque names are listed.
  • Protocols: protocols and implementations show as Size (protocol) and Size for Map (implementation). Before, they only set symbolIndexTransformation, which TextMate doesn't read, so they showed the raw defimpl … do.

How TextMate picks these preferences

  • Indentation patterns are chosen by the scope at the start of the line. Folding patterns use the scope at the end of the line (L: for the start). The symbol list uses each symbol's own scope.
  • Each key is looked up on its own, so every preference sets all the keys it overrides. Otherwise the HTML bundle's foldingStartMarker would apply to template lines. The new test caught exactly that.
  • I checked which preference wins for 34 kinds of lines using TextMate's own selector code, compiled against the fork's build. The test's JavaScript port of that ranking matches it on 66 selector/scope pairs.

Tests

test/editing/test.js (run by npm test) computes indentation, folding and symbols the way TextMate does, with Oniguruma regexes:

  • Indentation: every code line of the fixtures (module.ex, module_test.exs, template.heex) must be indented as it is.
  • Folds and symbols: they must match the reviewed .folds and .symbols snapshots.

Test plan

  • npm test: 12 grammar fixtures and 3 editing fixtures pass
  • Corpus measurements above (exact TextMate semantics)
  • TextMate parity for the new describe/test rule (51 files, identical)
  • All 17 preference regexes compile with Onigmo
  • CI
  • In TextMate: type a module with do/else/end, case, maps and a pipeline; fold an if/else and a @doc; open the symbol list in a test file. The live clone is on this branch.

🤖 Generated with Claude Code

aesmail and others added 4 commits September 27, 2026 11:38
do, ->, and open brackets at the end of a line indent the next line
(( never did); end and closing brackets dedent, also when more follows
(end), }, ]). else, rescue, catch, and after close one branch and open
the next. A binary operator at the end of a line (x =, <>, and, when,
::) continues the expression on the next line only. The patterns allow
a trailing comment, and "Todo" or ~r/.../ no longer look like `do` or
an operator.

Comment lines don't change the indentation, so "# what to do" doesn't
indent the next line. HEEx and HTML (EEx) templates indent inside open
tags, multi-line open tags, and <%= ... do %> blocks.

On the formatted code of Elixir, Phoenix, LiveView, and Ecto, a typed
line gets the formatter's indentation 87.3% of the time, up from 82.3%,
and 94.4% in ~H templates, up from 49%. The rest are layouts TextMate's
indentation can't express, such as clauses after a multi-line clause
body and arguments aligned to a column.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Formatted Elixir nests by indentation, so a line that opens a block (do,
->, an open bracket, else/rescue/catch/after, or a continued expression)
folds the lines indented below it. Before, only the else branch of an
if folded, clauses and brackets closed by `},`, `])`, and the like
never did, and heredocs and docs didn't fold.

Heredocs, docs, and ~H/~L templates fold from the line that opens them
to their closing delimiter. The opening line is told apart from the
closing one by the scope at the end of the line. Template lines fold by
their tags. Comment lines have no fold marker.

The grammar's foldingStartMarker/foldingStopMarker are removed; they
were only used when no preference sets fold patterns.

On the formatted corpus, a line ending in do, ->, or an open bracket
that is followed by an indented block folds 99.9%, 99.98%, and 97% of
the time, and 3.9% of fold markers fold nothing, down from 23%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ExUnit describe and test blocks are listed by name, with tests
  indented; the grammar gives their names a meta.describe/meta.test
  scope.
- @type, @TypeP, and @opaque names are listed as types.
- Protocols and implementations are listed as "Size (protocol)" and
  "Size for Map (implementation)". They set only symbolIndexTransformation,
  which TextMate doesn't read, so they showed the raw `defimpl ... do`.
- Module names no longer lose a "do" inside them ("Todo").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test/editing computes them from the preferences the way TextMate does
(indent::fsm_t, folds_t::foldable_ranges, symbols_t, and the ranking of
scope selectors in scope::selector_t, checked against TextMate's own
code), with Oniguruma regexes. Every code line of the fixtures must be
indented as it is, and the folds, fold markers without a fold, and
symbols must match the snapshots. npm test runs it after the grammar
tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
aesmail merged commit 871f6b8 into master Sep 27, 2026
1 check passed
aesmail deleted the editing-behavior branch September 27, 2026 08:44
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
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.

1 participant


Back | FazBrowse Home | New Git URL