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.
Undo/redo support with operation coalescing. Without this combinator, editors have no history.
BuffeeHistory(editor) editor.History.undo() editor.History.redo() editor.History.clear()
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
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
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
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
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
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)
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
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))
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
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:
Mode.s setting)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.
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
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) )
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))