implement better line wrapping (#8)
Hunter
Wednesday, August 6th, 2025 at 9:12 PM
7 <link rel="icon" href="resources/rollerskate.png">
8 <link rel="manifest" href="manifest.json">
9 <script type="module">
- import {EditorView, keymap, placeholder, lineNumbers} from "https://esm.sh/@codemirror/view@6"
10 + import {EditorView, keymap, placeholder, lineNumbers, Decoration} from "https://esm.sh/@codemirror/view@6"
11 import {EditorState, Compartment} from "https://esm.sh/@codemirror/state@6"
12 import {defaultKeymap, indentWithTab, undo, redo, undoDepth, redoDepth, history, historyKeymap} from "https://esm.sh/@codemirror/commands@6"
13 import {closeBrackets, closeBracketsKeymap} from "https://esm.sh/@codemirror/autocomplete@6"
16 import {indentUnit} from "https://esm.sh/@codemirror/language@6"
17 import {search, searchKeymap, closeSearchPanel, openSearchPanel} from "https://esm.sh/@codemirror/search@6"
18
- 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};
19 + 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};
20 </script>
21 <style>
22 :root {
197 const storageKey = 'html-lab-content';
198 let isFullscreen = false;
199 let showLineNumbers = false;
200 + let enableLineWrapping = false;
201 let lineNumbersCompartment;
202 + let lineWrappingCompartment;
203
204 function toggleFullscreen() {
205 const previewPane = document.querySelector('.preview-pane');
384 function loadEditorSettings() {
385 try {
386 showLineNumbers = localStorage.getItem('editor-line-numbers') === 'true';
387 + enableLineWrapping = localStorage.getItem('editor-line-wrapping') === 'true';
388 } catch (e) {
389 console.warn('Could not load editor settings from localStorage:', e);
390 showLineNumbers = false;
391 + enableLineWrapping = false;
392 }
393 }
394
410 });
411 }
412
413 + function createLineWrappingExtension() {
414 + const {EditorView, Decoration} = window.CodeMirror;
415 +
416 + return [
417 + EditorView.lineWrapping,
418 + EditorView.decorations.of((view) => {
419 + const decorations = [];
420 +
421 + for (let {from, to} of view.visibleRanges) {
422 + for (let pos = from; pos <= to;) {
423 + const line = view.state.doc.lineAt(pos);
424 + const lineText = line.text;
425 +
426 + // Calculate indentation level (count leading whitespace)
427 + let indentChars = 0;
428 + for (let i = 0; i < lineText.length; i++) {
429 + if (lineText[i] === '\t') {
430 + indentChars += 2; // Convert tab to 2 spaces for calculation
431 + } else if (lineText[i] === ' ') {
432 + indentChars += 1;
433 + } else {
434 + break;
435 + }
436 + }
437 +
438 + // Apply hanging indent if line has indentation
439 + if (indentChars > 0) {
440 + const indentDecoration = Decoration.line({
441 + attributes: {
442 + style: `text-indent: -${indentChars}ch; padding-left: calc(${indentChars}ch + 6px);`
443 + }
444 + });
445 + decorations.push(indentDecoration.range(line.from));
446 + }
447 +
448 + pos = line.to + 1;
449 + }
450 + }
451 +
452 + return decorations.length > 0 ? Decoration.set(decorations) : Decoration.none;
453 + }),
454 + ];
455 + }
456 +
457 + function toggleLineWrapping() {
458 + enableLineWrapping = !enableLineWrapping;
459 + saveEditorSetting('editor-line-wrapping', enableLineWrapping);
460 +
461 + const lineWrappingExtension = enableLineWrapping ? createLineWrappingExtension() : [];
462 +
463 + editorView.dispatch({
464 + effects: lineWrappingCompartment.reconfigure(lineWrappingExtension)
465 + });
466 + }
467
468 // File operations
469 window.saveFile = function() {
504 return;
505 }
506
- 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;
507 + 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, DecorationSet} = window.CodeMirror;
508
509 // Load saved content and editor settings
510 const savedContent = loadFromStorage();
512
513 // Create compartments for dynamic extensions
514 lineNumbersCompartment = new Compartment();
515 + lineWrappingCompartment = new Compartment();
516 +
517 + const initialLineWrappingExtension = enableLineWrapping ? createLineWrappingExtension() : [];
518
519 // Create CodeMirror editor
520 editorView = new EditorView({
531 {key: "Mod-o", run: () => { window.loadFile(); return true; }},
532 {key: "Mod-s", run: () => { window.saveFile(); return true; }},
533 {key: "F1", run: () => { toggleLineNumbers(); return true; }},
534 + {key: "F2", run: () => { toggleLineWrapping(); return true; }},
535 indentWithTab,
536 ...searchKeymap.filter(binding => binding.key !== "Mod-f"),
537 ...defaultKeymap
554 'autocapitalize': 'off',
555 'spellcheck': 'false'
556 }),
- lineNumbersCompartment.of(showLineNumbers ? lineNumbers() : [])
557 + lineNumbersCompartment.of(showLineNumbers ? lineNumbers() : []),
558 + lineWrappingCompartment.of(initialLineWrappingExtension)
559 ]
560 }),
561 parent: document.getElementById('editor')