Overview

This documentation describes the design and implementation of the Ori formatter. The formatter produces canonical source code formatting with minimal configuration (only line width is configurable).

Reference: Go’s gofmt

The Ori formatter follows the philosophy established by Go’s gofmt:

“No one is 100% happy with gofmt, but people adapt surprisingly quickly to styles that at first seem foreign. We hope people will accept the output precisely because it puts an end to style debates.”

Key principles from gofmt:

PrinciplegofmtOri
ConfigurationNone — deliberately deniedWidth only (default 100)
Specification”Implementation is the spec”Formatter output is canonical
DeterminismIdempotent, same input → same outputSame guarantee
DebatesEliminated by designEliminated by design

Where Ori differs from gofmt:

  • Line width limit: gofmt has none; Ori enforces 100 characters
  • Width-based breaking: gofmt trusts source; Ori breaks automatically at width
  • Indentation: gofmt uses tabs; Ori uses 4 spaces
  • Always-stacked constructs: Ori has { } blocks, try, match, etc.

Design Principles

  1. Minimal configuration — Only line width is configurable (default 100)
  2. Deterministic — Same input always produces same output
  3. Idempotentformat(format(code)) == format(code)
  4. Semantic preservation — Only whitespace changes, never meaning
  5. Width-driven breaking — Lines break when exceeding the configured width

Core Rules

RuleValueConfigurable
Line width100 characters (default)Yes (--width=N)
Indentation4 spaces, no tabsNo
Trailing commasRequired in multi-line, forbidden in single-lineNo
Blank linesOne between top-level items, no consecutiveNo

Breaking Philosophy

The formatter follows a simple principle: inline until max width, then break.

There are no arbitrary thresholds like “break if more than 3 parameters.” Instead:

  • Measure the line width
  • If it fits within the configured width (default 100), keep it inline
  • If it exceeds the width limit, break according to construct-specific rules

Exceptions exist only for constructs that are always stacked regardless of width:

  • { } blocks / try — sequential blocks always stack
  • match — arms always stack (scrutinee on first line)

Breaking Rules (Layer 4)

The formatter implements 7 named breaking rules (plus shared helper functions) for constructs that need special treatment beyond simple packing:

  1. MethodChainRule — All chain elements break together (.method() on own lines)
  2. ShortBodyRule — ~20 char threshold for yield/do bodies
  3. BooleanBreakRule — 3+ || clauses break with leading ||
  4. ChainedElseIfRule — Kotlin-style (first if with assignment)
  5. NestedForRule — Rust-style indentation for nested for
  6. ParenthesesRule — Preserve user parens, add when needed for clarity
  7. FunctionSeq helpers — Query functions for try, match, generic FunctionSeq
  8. LoopRule — Complex body (try/match/for) forces break

Documentation Sections

Algorithm

Constructs

Layers

Comments

  • Comments — Comment handling and doc comment ordering

Implementation

Incremental Formatting

The formatter supports incremental formatting via incremental/mod.rs. When only a portion of a file has changed, format_incremental() can format only the affected regions rather than the entire file, returning FormattedRegion results that can be applied with apply_regions(). This is used by the LSP server for efficient format-on-change.

Appendices

Tooling

ComponentLocationPurpose
ori_fmtcompiler/ori_fmt/Core formatting logic
ori-lsptools/ori-lsp/LSP server (formatting, diagnostics, hover)

Playground: Format-on-Run — code formats automatically when user clicks Run. LSP compiled to WASM provides real-time diagnostics and hover in Monaco.

Editors: Same ori_lsp binary serves VS Code, Neovim, and other LSP-compatible editors.

See Tooling Integration for architecture details, or the LSP Design docs for full LSP documentation.

Relationship to Spec

The normative formatting rules are defined in spec/16-formatting.md. This design documentation explains how to implement those rules, with detailed algorithms and edge cases.

DocumentPurpose
spec/16-formatting.mdWhat the canonical format is (normative)
docs/tooling/formatter/design/How to implement the formatter (informative)

Quick Reference

Always Inline (unless >100 chars)

  • Function signatures
  • Type definitions (structs, sum types)
  • Generic parameters
  • Where clauses (single constraint)
  • Collections (lists, maps, tuples)
  • Struct literals
  • Function calls
  • Conditionals
  • Chains (≤100 chars)

Always Stacked

  • { } blocks / try blocks
  • match arms
  • recurse
  • parallel / spawn
  • nursery

Break Triggers

When a line exceeds 100 characters:

ConstructBreaking Behavior
Function paramsOne per line, trailing comma
Return typeOwn line if ) -> Type exceeds 100
Generic paramsOne per line
Where clauseswhere on new line, constraints indented
Function callsArguments one per line
CollectionsSimple items wrap multiple per line, complex items one per line
Struct literalsFields one per line
ChainsEach .method() on own line
Conditionalsif cond then expr together, else on new line
Binary expressionsBreak before operator
LambdasBreak after -> only for always-stacked patterns