commit 7105b71acb35228513bc1ee1535d81d79f47ef14
parent 997ba7a55f128d8a1aaaa2054683e03262a9dfeb
Author: Hunter
Date:   Wed,  6 Aug 2025 16:24:15 -0400

first pass at line wrapping (press F2 to toggle)

Diffstat:
Mindex.html | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 109 insertions(+), 5 deletions(-)

diff --git a/index.html b/index.html @@ -7,8 +7,8 @@ <link rel="icon" href="resources/rollerskate.png"> <link rel="manifest" href="manifest.json"> <script type="module"> - import {EditorView, keymap, placeholder, lineNumbers} from "https://esm.sh/@codemirror/view@6" - import {EditorState, Compartment} from "https://esm.sh/@codemirror/state@6" + import {EditorView, keymap, placeholder, lineNumbers, Decoration} from "https://esm.sh/@codemirror/view@6" + import {EditorState, Compartment, StateField, StateEffect} from "https://esm.sh/@codemirror/state@6" import {defaultKeymap, indentWithTab, undo, redo, undoDepth, redoDepth, history, historyKeymap} from "https://esm.sh/@codemirror/commands@6" import {closeBrackets, closeBracketsKeymap} from "https://esm.sh/@codemirror/autocomplete@6" import {html} from "https://esm.sh/@codemirror/lang-html@6" @@ -16,7 +16,7 @@ import {indentUnit} from "https://esm.sh/@codemirror/language@6" import {search, searchKeymap, closeSearchPanel, openSearchPanel} from "https://esm.sh/@codemirror/search@6" - window.CodeMirror = {EditorView, EditorState, Compartment, keymap, defaultKeymap, indentWithTab, html, githubDark, indentUnit, placeholder, undo, redo, undoDepth, redoDepth, history, historyKeymap, closeBrackets, closeBracketsKeymap, search, searchKeymap, closeSearchPanel, openSearchPanel, lineNumbers}; + window.CodeMirror = {EditorView, EditorState, Compartment, keymap, defaultKeymap, indentWithTab, html, githubDark, indentUnit, placeholder, undo, redo, undoDepth, redoDepth, history, historyKeymap, closeBrackets, closeBracketsKeymap, search, searchKeymap, closeSearchPanel, openSearchPanel, lineNumbers, Decoration, StateField, StateEffect}; </script> <style> :root { @@ -198,6 +198,8 @@ let isFullscreen = false; let showLineNumbers = false; let lineNumbersCompartment; + let showLineWrapping = false; + let lineWrappingCompartment; function toggleFullscreen() { const previewPane = document.querySelector('.preview-pane'); @@ -382,9 +384,11 @@ function loadEditorSettings() { try { showLineNumbers = localStorage.getItem('editor-line-numbers') === 'true'; + showLineWrapping = localStorage.getItem('editor-line-wrapping') === 'true'; } catch (e) { console.warn('Could not load editor settings from localStorage:', e); showLineNumbers = false; + showLineWrapping = false; } } @@ -406,6 +410,95 @@ }); } + function createLineWrappingPlugin() { + const {EditorView, Decoration, StateField, StateEffect} = window.CodeMirror; + + // Line wrapping functionality + const CharacterWidthEffect = StateEffect.define({}); + const extra_cycle_character_width = StateField.define({ + create() { + return null; + }, + update(value, tr) { + for (let effect of tr.effects) { + if (effect.is(CharacterWidthEffect)) return effect.value; + } + return value; + } + }); + + const character_width_listener = EditorView.updateListener.of((viewupdate) => { + const width = viewupdate.view.defaultCharacterWidth; + const current_width = viewupdate.view.state.field(extra_cycle_character_width, false); + + if (current_width !== width) { + viewupdate.view.dispatch({ + effects: [CharacterWidthEffect.of(width)] + }); + } + }); + + const ARBITRARY_INDENT_LINE_WRAP_LIMIT = 48; + const line_wrapping_decorations = StateField.define({ + create() { + return Decoration.none; + }, + update(deco, tr) { + const tabSize = tr.state.tabSize; + const charWidth = tr.state.field(extra_cycle_character_width, false); + if (charWidth == null) return Decoration.none; + + if (!tr.docChanged && deco !== Decoration.none) return deco; + + const decorations = []; + + for (let i = 0; i < tr.state.doc.lines; i++) { + const line = tr.state.doc.line(i + 1); + if (line.length === 0) continue; + + let indented_chars = 0; + for (const ch of line.text) { + if (ch === '\t') { + indented_chars = indented_chars + tabSize; + } else if (ch === ' ') { + indented_chars = indented_chars + 1; + } else { + break; + } + } + + const offset = Math.min(indented_chars, ARBITRARY_INDENT_LINE_WRAP_LIMIT) * charWidth; + + const rules = document.createElement('span').style; + rules.setProperty('--idented', `${offset}px`); + rules.setProperty('text-indent', 'calc(-1 * var(--idented) - 1px)'); + rules.setProperty('padding-left', 'calc(var(--idented) + 2px)'); + + const linerwapper = Decoration.line({ + attributes: { style: rules.cssText } + }); + + decorations.push(linerwapper.range(line.from, line.from)); + } + return Decoration.set(decorations, true); + }, + provide: (f) => EditorView.decorations.from(f) + }); + + return [extra_cycle_character_width, character_width_listener, line_wrapping_decorations]; + } + + function toggleLineWrapping() { + const {EditorView} = window.CodeMirror; + showLineWrapping = !showLineWrapping; + saveEditorSetting('editor-line-wrapping', showLineWrapping); + + const wrapExtensions = showLineWrapping ? [EditorView.lineWrapping, ...createLineWrappingPlugin()] : []; + editorView.dispatch({ + effects: lineWrappingCompartment.reconfigure(wrapExtensions) + }); + } + // File operations window.saveFile = function() { @@ -446,7 +539,7 @@ return; } - const {EditorView, EditorState, Compartment, keymap, defaultKeymap, indentWithTab, html, githubDark, indentUnit, placeholder, undo, redo, undoDepth, redoDepth, history, historyKeymap, closeBrackets, closeBracketsKeymap, search, searchKeymap, closeSearchPanel, openSearchPanel, lineNumbers} = window.CodeMirror; + const {EditorView, EditorState, Compartment, keymap, defaultKeymap, indentWithTab, html, githubDark, indentUnit, placeholder, undo, redo, undoDepth, redoDepth, history, historyKeymap, closeBrackets, closeBracketsKeymap, search, searchKeymap, closeSearchPanel, openSearchPanel, lineNumbers, Decoration, StateField, StateEffect} = window.CodeMirror; // Load saved content and editor settings const savedContent = loadFromStorage(); @@ -454,6 +547,7 @@ // Create compartments for dynamic extensions lineNumbersCompartment = new Compartment(); + lineWrappingCompartment = new Compartment(); // Create CodeMirror editor editorView = new EditorView({ @@ -470,6 +564,7 @@ {key: "Mod-o", run: () => { window.loadFile(); return true; }}, {key: "Mod-s", run: () => { window.saveFile(); return true; }}, {key: "F1", run: () => { toggleLineNumbers(); return true; }}, + {key: "F2", run: () => { toggleLineWrapping(); return true; }}, indentWithTab, ...searchKeymap.filter(binding => binding.key !== "Mod-f"), ...defaultKeymap @@ -492,7 +587,8 @@ 'autocapitalize': 'off', 'spellcheck': 'false' }), - lineNumbersCompartment.of(showLineNumbers ? lineNumbers() : []) + lineNumbersCompartment.of(showLineNumbers ? lineNumbers() : []), + lineWrappingCompartment.of([]) ] }), parent: document.getElementById('editor') @@ -509,6 +605,14 @@ handleViewportChange(); // Exit fullscreen mode if keyboard is open }); + // Initialize line wrapping if it was enabled + if (showLineWrapping) { + const wrapExtensions = [EditorView.lineWrapping, ...createLineWrappingPlugin()]; + editorView.dispatch({ + effects: lineWrappingCompartment.reconfigure(wrapExtensions) + }); + } + // Initial render updatePreview();