Buffee API Reference ==================== Instantiation ------------- const textBuffer = Buffe(element, { h, w, s }) const textEditor = Buffee(Buffe(element, { h, w, s })) Option Type Default Description ------ ------ ------- ---------------------- h number auto Fixed visible lines w number auto Fixed text columns s number 4 Tab width (0 = hard tabs) Versioning ---------- Each file tracks its own version. Versions must be >= buffe.js (the core): buffe.js coreVersion constant Core rendering engine buffee.js buffeeVersion constant Controller combinator style.css @version comment Stylesheet template.html @version HTML comment HTML structure Versions appear in Mode.ext: ['17.6.0-alpha.1', 'Buffee@17.6.0-alpha.1', ...] The changelog (dev/changelog.txt) tracks the highest version across all files. Pre-commit hook enforces version bumps when files change. Top-level properties -------------------- instance .$ Root DOM element .$lines Lines DOM element (for event listeners) .Model see Model namespace below .View see View namespace below .Span see Span namespace below .Mode see Mode namespace below .Ctrl Undefined by default. Controllers are added here by convention. instance.Model -------------------- Model ._ text buffer. Assumes sanitized '\n', '\t', zero/multi-width chars .ins primitive insert .del primitive del .end last coordinate {y, x} in model When updating buffer, call render if necessary. If you append to ._ and the new lines are out of view, then a render would not be necessary. While we could have added a setter for the model that would know to call render, this would mean that the Model has to be concerned with the view. The philosophy is that Model should be agnostic to existence of rendering. instance.View ------------------ View .first Get Model index of first line of viewport .first = 5 Set model index of first line of viewport .n Get logical Viewport size - number of lines .n = 20 Set viewport size .last Get Model index of last line viewport .render(delta) Render content only (delta = viewport size change) .RENDER(delta) Rebuild containers and render content You can set first and n but not last. An alternative would have been to make first and last,but not size. The latter is a more symmetrical API but not as intuitive and the implementation uglier. instance.Span ------------------ A continuous text span from a starting and end coordinate. Span ._ Get selected lines .bounds(value) returns bounds: truthy [start, end], falsey [head, tail] .dir orientation: 1 (forward), -1 (backward), 0 (cursor) .del() delete text Span-wise .ins(lines) insert lines (string[]) Span-wise .select(pos) make selection, optionally set head to {y, x} .cursor(pos) make cursor, optionally at {y, x} .dent(value) indent or unindent : 1 indent, -1 unindent .mvX(dir) move horizontally : 1 right , -1 left .mvY(dir, toEdge) move vertically : 1 down , -1 up. toEdge: go to edge .mvLn(dir) move to edge of line : 1 end, 0 or negative for beginning See BuffeeVimMotion combinator for more expressive vim-style motions such as move word, move line. Direct position manipulation ---------------------------- bounds() returns references to the internal position objects {y, x}. You can mutate these directly for surgical adjustments without going through the API. const [head, anchor] = editor.Span.bounds(); // [head, anchor] order const [start, end] = editor.Span.bounds(1); // document order // Move cursor to specific position head.y = 5; head.x = 10; editor.View.render(); // Expand selection by adjusting head editor.Span.select(); // detach head from anchor const [h, a] = editor.Span.bounds(); h.y = 10; // move head to line 10 editor.View.render(); // Or set head position directly via select() editor.Span.select(editor.Model.end); // select to end of document editor.View.render(); When dir === 0 (cursor mode), head and anchor are the same object reference. Mutating one automatically mutates the other. After select(), they become separate objects and can be moved independently. // In cursor mode: const [h, a] = editor.Span.bounds(); h === a // true - same object h.x = 5; // anchor.x is also now 5 // After select(): editor.Span.select(); const [h2, a2] = editor.Span.bounds(); h2 === a2 // false - separate objects h2.x = 10; // anchor.x unchanged instance.Mode ------------------ Mode (editor.Mode) .s Tab width .i Edit mode: 1=write, 0=navigate, -1=read .f Render frame counter .mx Max column for vertical cursor movement .ch Line height in pixels .cw Character width in pixels .sub subscriptions for render callback .ext Array: [coreVersion, ...extensions] - version string first, then extension names Controllers ------------------ Controllers handle user input (keyboard, clipboard, mouse, etc). Buffe core has no built-in controller - it's purely model/view. Contract: A Controller is a function that: 1. Receives an editor instance 2. Attaches event listeners to editor.$lines 3. Returns a cleanup function to remove listeners Convention: Store the cleanup function on editor.Ctrl so you can call it later. function MyController(editor) { const { Span, Mode, $lines } = editor; const onKeydown = e => { if (e.key === 'Enter') Span.ins(['', '']); }; $lines.addEventListener('keydown', onKeydown); return () => { $lines.removeEventListener('keydown', onKeydown); }; } // Usage editor.Ctrl = MyController(editor); // attach controller, store cleanup editor.Ctrl(); // call cleanup to remove listeners editor.Ctrl = OtherController(editor); BuffeeNativeController ---------------------- The default controller providing standard keyboard/clipboard handling: - Arrow keys: cursor movement (with Shift for selection) - Meta+Arrow: word/line jump - Home/End: line start/end - Backspace/Delete: character deletion - Enter: new line - Tab: indent (Shift+Tab to unindent) - Meta+Z: undo (Shift for redo, requires History combinator) - Meta+A: select all - Meta+C/X/V: copy/cut/paste Buffee() is a combinator that adds BuffeeNativeController: function Buffee(editorOrEl, opts) { const editor = editorOrEl instanceof Buffe ? editorOrEl : Buffe(editorOrEl, opts); editor.Ctrl = BuffeeNativeController(editor); return editor; } To use Buffe without the default controller: const editor = Buffe(element, opts); // no controller editor.Ctrl = MyVimController(editor); // set custom controller