Combinators

Buffee's extension system is with combinators - functions that take as input a buffee instance and return a buffee instance with more functionality. This is novel because, unlike other text editors, Buffee is just a JavaScript function that you can readily access and reprogram.

Moreover, you can chain and combine these extensions together. Hence, combinator.

History

combinators/history.js

Undo/redo support with operation coalescing. Without this combinator, editors have no history.

BuffeeHistory(editor)
editor.History.undo()
editor.History.redo()
editor.History.clear()

Demo →

UndoTree

combinators/undotree.js

Tree-based undo/redo that preserves all branches. When you undo and make a new edit, you create a new branch instead of losing history.

BuffeeUndoTree(editor)
editor.UndoTree.undo()
editor.UndoTree.redo()         // Follow most recent branch
editor.UndoTree.redo(0)        // Follow specific branch
editor.UndoTree.branches()     // Get available branches
editor.UndoTree.goToNode(id)   // Jump to any node
editor.UndoTree.getTree()      // Get tree for visualization

Demo →

Syntax

combinators/syntax.js

Regex-based syntax highlighting with incremental state caching. Ships with JavaScript, HTML, CSS, JSON, and Python.

const editor = BuffeeSyntax(Buffee(container, config))
editor.Syntax.setLanguage('javascript')
editor.Syntax.enabled = true

Demo →

Elementals

combinators/elementals.js

DOM-based UI elements in a layer above text. Buttons, inputs, labels with character-unit positioning.

const editor = BuffeeElementals(Buffee(container, config))
editor.Elementals.addButton({ row: 1, col: 5, label: 'OK' })
editor.Elementals.enabled = true

Demo →

TUI Legacy

combinators/tui.js

Terminal UI via text manipulation. Buttons, prompts, scrollboxes using box-drawing characters.

const editor = BuffeeTUI(Buffee(container, config))
editor.TUI.addButton({ row: 1, col: 2, label: 'OK', border: true })
editor.TUI.enabled = true

Demo →

FileLoader

combinators/fileloader.js

Multiple file loading strategies optimized for different file sizes. Choose the right loader based on your file size and memory constraints.

Note: FileLoader does not sanitize text. If files may contain tabs, wrap with BuffeeSanitize:

// With tab sanitization
const editor = BuffeeFileLoader(BuffeeSanitize(Buffee(container, config)))

await editor.FileLoader.naiveLoad(file)           // <10M lines
await editor.FileLoader.chunkedBlobLoad(file)     // <70M lines
await editor.FileLoader.chunkedFileReaderLoad(file)
await editor.FileLoader.streamLoad(file)
await editor.FileLoader.streamMaterializedLoad(file) // <75M lines
await editor.FileLoader.streamGcHintsLoad(file)   // <90M lines

Demo →

UltraHighCapacity

combinators/ultrahighcapacity.js

Gzip-compressed chunked storage for massive files (1B+ lines). Decompress on scroll. Forces editor into navigation-only mode.

const editor = BuffeeUltraHighCapacity(Buffee(container, config))
editor.UltraHighCapacity.activate(50000)
await editor.UltraHighCapacity.appendLines(lines)

Demo →

iOS

combinators/ios.js

Touch interactions and on-screen keyboard input for iOS devices.

let editor = Buffee(container, config)
if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
  editor = BuffeeIOS(editor)
}
editor.iOS.focus()   // Show keyboard
editor.iOS.destroy() // Cleanup

Demo →

StatusLine

combinators/statusline.js

Updates status bar elements (row, col, line count, spaces) on each render. Looks for elements with classes .buffee-head-row, .buffee-head-col, .buffee-linecount, .buffee-spaces.

const editor = BuffeeStatusLine(new Buffee(container, config))

Demo →

VimMotion

combinators/vimmotion.js

Vim-style motion commands with count support. Adds a move() method for programmatic cursor movement.

const editor = BuffeeVimMotion(new Buffee(container, config))

// Basic motions
editor.VimMotion.move('h')    // left
editor.VimMotion.move('l')    // right
editor.VimMotion.move('j')    // down
editor.VimMotion.move('k')    // up

// With counts
editor.VimMotion.move('5j')   // down 5 lines
editor.VimMotion.move('3w')   // forward 3 words

// Word motions
editor.VimMotion.move('w')    // next word start
editor.VimMotion.move('b')    // previous word start
editor.VimMotion.move('e')    // end of word
editor.VimMotion.move('W')    // next WORD (non-whitespace)
editor.VimMotion.move('B')    // previous WORD
editor.VimMotion.move('E')    // end of WORD

// Line motions
editor.VimMotion.move('0')    // start of line
editor.VimMotion.move('$')    // end of line
editor.VimMotion.move('^')    // first non-blank
editor.VimMotion.move('+')    // next line, first non-blank
editor.VimMotion.move('-')    // prev line, first non-blank

// Go to line
editor.VimMotion.move('gg')   // first line
editor.VimMotion.move('G')    // last line
editor.VimMotion.move('10G')  // line 10

Demo →

Sanitize

combinators/sanitize.js

Text sanitization for content set via Model.s or Span.ins(). Buffee core does not sanitize text—tabs and problematic Unicode characters are inserted as-is. This combinator automatically cleans input text.

Handles:

const editor = BuffeeSanitize(new Buffee(container, config))

// Automatic sanitization on Model.ins() calls
editor.Span.ins("text\twith\ttabs")  // tabs become spaces

// Use sanitization utilities when setting Model._ directly
editor.Model._ = editor.Sanitize.lines(["a\tb", "c\td"])
editor.View.render()

// Manual sanitization utilities
editor.Sanitize.line("hello\tworld")   // "hello    world"
editor.Sanitize.text("a\tb\nc\td")     // sanitize multi-line string
editor.Sanitize.lines(["a\tb", "c\td"]) // sanitize array of lines

Why opt-in? Buffee's core is minimal. Automatic sanitization adds overhead and may not suit all use cases. Some applications may want to handle tabs differently, preserve certain Unicode, or sanitize upstream.

Demo →


Using Multiple Combinators

Compose combinators using the decorator pattern.

const editor = BuffeeElementals(
  BuffeeSyntax(
    Buffee(container, config)
  )
)

editor.Syntax.setLanguage('javascript')
editor.Syntax.enabled = true

editor.Elementals.addButton({
  row: 0, col: 40,
  label: 'Run',
  onActivate: () => console.log('Running...')
})
editor.Elementals.enabled = true

Syntactic Sugar: reduce

For many combinators, use reduce to avoid deep nesting:

const combinators = [BuffeeHistory, BuffeeSyntax, BuffeeElementals]

const editor = combinators.reduce(
  (ed, comb) => comb(ed),
  new Buffee(container, config)
)

Syntactic Sugar: pipe

A pipe helper reads left-to-right:

const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x)

const editor = pipe(
  BuffeeHistory,
  BuffeeSyntax,
  BuffeeElementals
)(new Buffee(container, config))