Skip to content

jeanpaulsio/codemirror-lang-ruby

Repository files navigation

codemirror-lang-ruby

Ruby language support for CodeMirror 6, built on a Lezer grammar.

Targets Ruby 3.0+ syntax (including endless methods and basic pattern matching).

Live Demo

Install

npm install codemirror-lang-ruby

Usage

import {EditorView, basicSetup} from "codemirror"
import {ruby} from "codemirror-lang-ruby"

new EditorView({
  doc: 'puts "hello"',
  extensions: [basicSetup, ruby()],
  parent: document.getElementById("editor")!,
})

Real-world parse accuracy

Tested against popular open source Ruby projects (large, representative files):

Project File Lines Accuracy
Faker internet.rb 579 98.4%
Devise devise.rb 534 98.3%
Jekyll site.rb 577 97.6%
Fastlane runner.rb 379 96.3%
Rails query_methods.rb 2291 96.0%
Grape api.rb 166 95.8%
Sidekiq config.rb 321 95.6%

What's supported

  • 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 with pin operators, hash patterns, find patterns)
  • Error handling: begin/rescue/ensure/raise, rescue with scoped constants (rescue Foo::Bar => e)
  • Strings: single-quoted, double-quoted with #{interpolation}, heredocs (<<~DELIM), %-literals with any delimiter
  • Literals: integers, floats, symbols, character literals (?a), arrays, hashes, regex (/pattern/flags), nil, true, false
  • Expressions: assignment (including ||=, &&=), multiple assignment (a, b = 1, 2), method calls (with receiver and keyword args like User.where(active: true), splat *args/**kwargs/&block), chained calls, binary/unary/ternary operators, lambdas, ranges, conditional modifiers
  • Blocks: brace blocks and do/end blocks attached to method calls (items.each { |x| x })
  • Operators: proper precedence (** > *// > +/- > comparison > logic), safe navigation (&.), scope resolution (:: including leading ::TopLevel)
  • Bare method calls: puts "hello", require "json", attr_reader :name, include Comparable, validates_presence_of :name, rescue_from, helper_method (49 common Ruby/Rails methods)
  • Variables: local, @instance, @@class, $global, Constants
  • Comments: line # and block =begin/=end
  • Editor features: smart indentation, code folding, bracket closing, keyword autocompletion (31 keywords)

Known limitations

  • Heredoc body highlighting with trailing codefoo(<<~SQL) and <<~HEREDOC.strip parse correctly (no error nodes), but the heredoc body is not highlighted as a string in these cases. Simple heredocs (x = <<~SQL) highlight the full body as a string. This is a Lezer architectural limitation: inline tokenizers always run before external tokenizers, preventing the body tokenizer from claiming the content.
  • Guard clauses in pattern matchingin x if x > 0 is not supported. The if/unless keyword conflicts with IfStatement/ConditionalModifier in the LR parser and cannot be resolved without an external tokenizer.
  • Heredoc and %-literal bodies are opaque tokens (no interpolation highlighting inside).
  • Inline rescue as a standalone expressionvalue = foo rescue nil works (rescue in assignments), but foo rescue bar as a standalone expression outside of assignment context is not supported.
  • Newline as statement separator — Ruby uses newlines to separate statements, but the grammar is whitespace-insensitive. An expression followed by if on the next line may be parsed as a conditional modifier (e.g., x = 1\nif cond parses as x = (1 if cond)).
  • Setter assignment with conditional modifierself.x = 1 if condition is not fully supported.
  • Bare method calls only work for a curated list of 49 common Ruby/Rails methods. Other methods need parentheses (e.g., assert_includes(errors, "msg") instead of assert_includes errors, "msg").
  • Line-based indentation — The indent engine uses regex line scanning rather than tree-based analysis. Most patterns work well, but complex multi-line expressions (e.g., multi-line method args followed by a body, trailing commas inside nested delimiters) may not indent perfectly.

Development

npm install          # Install dependencies
npm run build        # Build grammar + bundle to dist/
npm test             # Run 105 grammar tests (721 total across all suites)
npm run lint         # TypeScript type check
npm run demo:build   # Build the demo page

Built with Claude Code

This entire project — grammar, external tokenizers, tests, editor integration, and demo — was written by Claude Code, guided by @jeanpaulsio.

License

MIT