Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Changelog

All notable changes to this project will be documented in this file.

This project follows [Semantic Versioning](https://semver.org/).

## [0.2.0] - 2026-03-26

### Added

- **Comprehensive indentation** — 119 programmatic tests covering all Ruby indent/dedent patterns:
- Block openers (def, class, module, if, unless, while, until, for, case, begin, do, braces)
- Mid-block keywords (else, elsif, when, rescue, ensure)
- Closing delimiters (}, ], ))
- Continuation lines (trailing +, -, &&, ||, \, comma)
- Method chaining with leading dots
- Assignment with block openers (`x = if`, `@foo ||= begin`)
- Modifier forms and single-line bodies (no indent change)
- Nested blocks at arbitrary depth
- **Subscript expressions** — `arr[0]`, `hash[:key]`, `matrix[i][j]`
- **Subscript assignment** — `hash[:key] = value`
- **Setter method definitions** — `def name=(value)`
- **Class method definitions** — `def self.method_name`
- Tab key support in demo editor
- Cmd+Shift+Enter inserts properly indented line above in demo editor

### Fixed

- Single-line forms (`class Foo; end`, `def foo; end`, endless methods) no longer incorrectly indent the next line
- `end`, `else`, `elsif`, `when`, `rescue`, `ensure` correctly deindent to match their opening keyword at any nesting depth
- Empty lines inside blocks preserve surrounding indent context
- Closing delimiters align with their matching opener

### Changed

- Indentation engine rewritten from tree-based `indentNodeProp` to text-based `indentService` for robust handling of incomplete code while typing
- Updated parse accuracy benchmarks with larger, more representative real-world files

## [0.1.0] - 2026-03-26

### Added

- Initial release
- Lezer grammar for Ruby with 89 grammar tests
- **Definitions**: methods (with params, endless `def f(x) = expr`), classes (with inheritance), modules
- **Control flow**: if/elsif/else, unless, while, until, for/in, case/when, case/in (pattern matching)
- **Error handling**: begin/rescue/ensure/raise
- **Strings**: single-quoted, double-quoted with `#{interpolation}`, heredocs (`<<~DELIM`), `%`-literals
- **Literals**: integers, floats, symbols, character literals, arrays, hashes, regex, nil, true, false
- **Expressions**: assignment (including `||=`, `&&=`), multiple assignment, method calls, chained calls, binary/unary/ternary operators, lambdas, ranges, conditional modifiers
- **Blocks**: brace blocks and do/end blocks attached to method calls
- **Operators**: proper precedence, safe navigation (`&.`), scope resolution (`::`)
- **Bare method calls**: 25 common Ruby methods (puts, require, attr_reader, include, etc.)
- **Variables**: local, @instance, @@class, $global, Constants
- **Comments**: line `#` and block `=begin`/`=end`
- 5 external tokenizers for context-dependent tokens (regex/division, heredoc/less-than, percent-literal/modulo, symbol/colon, string interpolation)
- Code folding, bracket closing, keyword autocompletion (31 keywords)
- Demo page with One Dark theme
- GitHub Actions CI
31 changes: 16 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@ new EditorView({

## Real-world parse accuracy

Tested against popular open source Ruby projects:

| Project | Stars | Lines | Accuracy |
|---------|-------|-------|----------|
| [Rails](https://github.com/rails/rails) | 56k | 338 | **99.7%** |
| [Jekyll](https://github.com/jekyll/jekyll) | 49k | 196 | **99.6%** |
| [Fastlane](https://github.com/fastlane/fastlane) | 40k | 54 | **99.3%** |
| [Devise](https://github.com/heartcombo/devise) | 24k | 534 | **98.0%** |
| [Faker](https://github.com/faker-ruby/faker) | 11k | 282 | **97.5%** |
| [Sidekiq](https://github.com/sidekiq/sidekiq) | 13k | 162 | **97.5%** |
| [Grape](https://github.com/ruby-grape/grape) | 10k | 75 | **95.3%** |
Tested against popular open source Ruby projects (large, representative files):

| Project | File | Lines | Accuracy |
|---------|------|-------|----------|
| [Fastlane](https://github.com/fastlane/fastlane) | runner.rb | 379 | **91.3%** |
| [Grape](https://github.com/ruby-grape/grape) | api.rb | 166 | **90.1%** |
| [Jekyll](https://github.com/jekyll/jekyll) | site.rb | 577 | **88.4%** |
| [Devise](https://github.com/heartcombo/devise) | devise.rb | 534 | **87.9%** |
| [Sidekiq](https://github.com/sidekiq/sidekiq) | config.rb | 321 | **87.5%** |
| [Rails](https://github.com/rails/rails) | query_methods.rb | 2291 | **86.6%** |
| [Faker](https://github.com/faker-ruby/faker) | internet.rb | 579 | **85.3%** |

## What's supported

Expand All @@ -50,21 +50,22 @@ Tested against popular open source Ruby projects:
- **Bare method calls**: `puts "hello"`, `require "json"`, `attr_reader :name`, `include Comparable` (25 common Ruby methods)
- **Variables**: local, `@instance`, `@@class`, `$global`, `Constants`
- **Comments**: line `#` and block `=begin`/`=end`
- **Editor features**: indentation, code folding, bracket closing, keyword autocompletion (31 keywords)
- **Editor features**: smart indentation (119 test cases), code folding, bracket closing, keyword autocompletion (31 keywords)

## Known limitations

- Blank lines inside class/method bodies can produce spurious error nodes (~38% of parse errors)
- Multi-arg bare method calls (`raise ArgumentError, "msg"`) not fully supported
- Compound assignment operators (`+=`, `-=`, `|=`) in some contexts
- Heredoc and `%`-literal bodies are opaque tokens (no interpolation highlighting inside)
- Hash symbol-key shorthand (`{ name: "Alice" }`) not supported (`:` conflicts with Symbol token when blocks are expressions)
- Setter method definitions (`def name=(val)`) and subscript access (`arr[0]`) not yet supported
- `<<` left-shift operator not distinguished from heredoc start

## Development

```bash
npm install # Install dependencies
npm run build # Build grammar + bundle to dist/
npm test # Run all 83 grammar tests
npm test # Run all 89 grammar tests
npm run lint # TypeScript type check
npm run demo:build # Build the demo page
```
Expand Down
297 changes: 252 additions & 45 deletions demo/demo.js

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions demo/demo.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import {EditorView, basicSetup} from "codemirror"
import {keymap} from "@codemirror/view"
import {indentWithTab} from "@codemirror/commands"
import {getIndentation} from "@codemirror/language"
import {oneDark} from "@codemirror/theme-one-dark"
import {ruby} from "../src/index"

Expand Down Expand Up @@ -73,6 +76,16 @@ new EditorView({
doc: sampleCode,
extensions: [
basicSetup,
keymap.of([
indentWithTab,
{key: "Mod-Shift-Enter", run: (view) => {
const line = view.state.doc.lineAt(view.state.selection.main.head)
const indent = getIndentation(view.state, line.from)
const pad = indent != null && indent > 0 ? " ".repeat(indent) : ""
view.dispatch({changes: {from: line.from, insert: pad + "\n"}, selection: {anchor: line.from + pad.length}})
return true
}},
]),
ruby(),
oneDark,
EditorView.theme({
Expand Down
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codemirror-lang-ruby",
"version": "0.1.0",
"version": "0.2.0",
"description": "Ruby language support for CodeMirror 6, built on a Lezer grammar",
"type": "module",
"main": "dist/index.cjs",
Expand Down Expand Up @@ -45,8 +45,10 @@
},
"devDependencies": {
"@codemirror/autocomplete": "^6.20.1",
"@codemirror/commands": "^6.10.3",
"@codemirror/language": "^6.0.0",
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.40.0",
"@lezer/common": "^1.0.0",
"@lezer/generator": "^1.0.0",
"@lezer/highlight": "^1.0.0",
Expand Down
Loading
Loading