diff options
| author | Hizbu Azka <hanifan.azka@gmail.com> | 2026-10-23 10:45:25 +0700 |
|---|---|---|
| committer | Hizbu Azka <hanifan.azka@gmail.com> | 2026-10-23 10:46:41 +0700 |
| commit | 272b2b94a268e1e342dab2daae4031f4eabdbb8e (patch) | |
| tree | 8e5c49b6e528e5a1a7aa4190a21fbffeebdde165 /_posts | |
| parent | 2bd366b984bb0ac3ac98691a51f837731af67b2a (diff) | |
run prettier
Diffstat (limited to '_posts')
| -rw-r--r-- | _posts/2026-10-17-regex.html | 746 |
1 files changed, 410 insertions, 336 deletions
diff --git a/_posts/2026-10-17-regex.html b/_posts/2026-10-17-regex.html index b03779a..bb0ceb4 100644 --- a/_posts/2026-10-17-regex.html +++ b/_posts/2026-10-17-regex.html @@ -3,7 +3,7 @@ layout: post --- <label> - <input type="checkbox" id="fullscreen"> + <input type="checkbox" id="fullscreen" /> Fullscreen </label> @@ -11,19 +11,18 @@ layout: post const body = document.body; const checkbox = document.getElementById("fullscreen"); checkbox.addEventListener("change", () => { - if (checkbox.checked) { - body.style.setProperty("margin", "0"); - body.style.setProperty("max-width", "none"); - body.style.setProperty("padding", "0"); - } else { - body.style.removeProperty("margin"); - body.style.removeProperty("max-width"); - body.style.removeProperty("padding"); - } + if (checkbox.checked) { + body.style.setProperty("margin", "0"); + body.style.setProperty("max-width", "none"); + body.style.setProperty("padding", "0"); + } else { + body.style.removeProperty("margin"); + body.style.removeProperty("max-width"); + body.style.removeProperty("padding"); + } }); </script> - <div id="loading"> <div class="spinner"></div> <div>Loading editor…</div> @@ -31,155 +30,194 @@ layout: post <style> :root { - --border-color: #d0d7de; - --tab-bg: #f6f8fa; - --tab-active-bg: #ffffff; - --editor-bg: #ffffff; - --rename-bg: #ffffff; - --rename-fg: #000000; + --border-color: #d0d7de; + --tab-bg: #f6f8fa; + --tab-active-bg: #ffffff; + --editor-bg: #ffffff; + --rename-bg: #ffffff; + --rename-fg: #000000; } @media (prefers-color-scheme: dark) { - :root { - --border-color: #2f2821; - --tab-bg: #090705; - --tab-active-bg: #000000; - --editor-bg: #0d1117; - --rename-bg: #0d1117; - --rename-fg: #ffffff; - } + :root { + --border-color: #2f2821; + --tab-bg: #090705; + --tab-active-bg: #000000; + --editor-bg: #0d1117; + --rename-bg: #0d1117; + --rename-fg: #ffffff; + } } #loading { - position: absolute; - inset: 0; - z-index: 10; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 12px; - background: var(--bg-color); - font-size: 14px; + position: absolute; + inset: 0; + z-index: 10; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; + background: var(--bg-color); + font-size: 14px; } #loading .spinner { - width: 28px; - height: 28px; - border: 3px solid var(--border-color); - border-top-color: #0969da; - border-radius: 50%; - animation: spin 0.8s linear infinite; + width: 28px; + height: 28px; + border: 3px solid var(--border-color); + border-top-color: #0969da; + border-radius: 50%; + animation: spin 0.8s linear infinite; + } + @keyframes spin { + to { + transform: rotate(360deg); + } } - @keyframes spin { to { transform: rotate(360deg); } } /* Match highlights (bottom editor) */ - .cm-regex-match-0 { background-color: rgba(9, 105, 218, 0.15); border-bottom: 2px solid #0969da; border-radius: 2px; } - .cm-regex-match-1 { background-color: rgba(45, 164, 78, 0.15); border-bottom: 2px solid #2da44e; border-radius: 2px; } - .cm-regex-match-2 { background-color: rgba(207, 34, 46, 0.15); border-bottom: 2px solid #cf222e; border-radius: 2px; } - .cm-regex-match-3 { background-color: rgba(154, 103, 0, 0.15); border-bottom: 2px solid #9a6700; border-radius: 2px; } - .cm-regex-match-4 { background-color: rgba(130, 80, 223, 0.15); border-bottom: 2px solid #8250df; border-radius: 2px; } + .cm-regex-match-0 { + background-color: rgba(9, 105, 218, 0.15); + border-bottom: 2px solid #0969da; + border-radius: 2px; + } + .cm-regex-match-1 { + background-color: rgba(45, 164, 78, 0.15); + border-bottom: 2px solid #2da44e; + border-radius: 2px; + } + .cm-regex-match-2 { + background-color: rgba(207, 34, 46, 0.15); + border-bottom: 2px solid #cf222e; + border-radius: 2px; + } + .cm-regex-match-3 { + background-color: rgba(154, 103, 0, 0.15); + border-bottom: 2px solid #9a6700; + border-radius: 2px; + } + .cm-regex-match-4 { + background-color: rgba(130, 80, 223, 0.15); + border-bottom: 2px solid #8250df; + border-radius: 2px; + } /* Line fringe highlights (top editor) */ - .cm-regex-line-0 { border-left: 4px solid #0969da; background-color: rgba(9, 105, 218, 0.05); } - .cm-regex-line-1 { border-left: 4px solid #2da44e; background-color: rgba(45, 164, 78, 0.05); } - .cm-regex-line-2 { border-left: 4px solid #cf222e; background-color: rgba(207, 34, 46, 0.05); } - .cm-regex-line-3 { border-left: 4px solid #9a6700; background-color: rgba(154, 103, 0, 0.05); } - .cm-regex-line-4 { border-left: 4px solid #8250df; background-color: rgba(130, 80, 223, 0.05); } + .cm-regex-line-0 { + border-left: 4px solid #0969da; + background-color: rgba(9, 105, 218, 0.05); + } + .cm-regex-line-1 { + border-left: 4px solid #2da44e; + background-color: rgba(45, 164, 78, 0.05); + } + .cm-regex-line-2 { + border-left: 4px solid #cf222e; + background-color: rgba(207, 34, 46, 0.05); + } + .cm-regex-line-3 { + border-left: 4px solid #9a6700; + background-color: rgba(154, 103, 0, 0.05); + } + .cm-regex-line-4 { + border-left: 4px solid #8250df; + background-color: rgba(130, 80, 223, 0.05); + } .tab-bar-container { - display: flex; - align-items: center; - background: var(--border-color); - padding: 1px 1px 0 1px; - overflow: hidden; + display: flex; + align-items: center; + background: var(--border-color); + padding: 1px 1px 0 1px; + overflow: hidden; } .tab-bar { - display: flex; - gap: 1px; - overflow-x: auto; - flex: 1; + display: flex; + gap: 1px; + overflow-x: auto; + flex: 1; } .tab-btn { - background: var(--tab-bg); - color: var(--text-color); - border: none; - padding: 6px 10px 6px 14px; - cursor: pointer; - font-size: 13px; - display: flex; - align-items: center; - gap: 8px; - white-space: nowrap; - user-select: none; + background: var(--tab-bg); + color: var(--text-color); + border: none; + padding: 6px 10px 6px 14px; + cursor: pointer; + font-size: 13px; + display: flex; + align-items: center; + gap: 8px; + white-space: nowrap; + user-select: none; } .tab-btn input { - font: inherit; - padding: 0 2px; - border: 1px solid #0969da; - outline: none; - background: var(--rename-bg); - color: var(--rename-fg); + font: inherit; + padding: 0 2px; + border: 1px solid #0969da; + outline: none; + background: var(--rename-bg); + color: var(--rename-fg); } .tab-btn.active { - background: var(--tab-active-bg); - border-bottom: 2px solid #0969da; - font-weight: 500; + background: var(--tab-active-bg); + border-bottom: 2px solid #0969da; + font-weight: 500; } .tab-close { - background: transparent; - border: none; - color: #8c959f; - cursor: pointer; - font-size: 14px; - padding: 0 4px; - border-radius: 4px; + background: transparent; + border: none; + color: #8c959f; + cursor: pointer; + font-size: 14px; + padding: 0 4px; + border-radius: 4px; } .tab-close:hover { - background: rgba(128, 128, 128, 0.25); - color: var(--text-color); + background: rgba(128, 128, 128, 0.25); + color: var(--text-color); } .tab-add-btn { - background: var(--tab-bg); - color: var(--text-color); - border: none; - cursor: pointer; - font-size: 16px; - font-weight: bold; + background: var(--tab-bg); + color: var(--text-color); + border: none; + cursor: pointer; + font-size: 16px; + font-weight: bold; } .tab-add-btn:hover { - background: var(--tab-active-bg); + background: var(--tab-active-bg); } #regex-editor { - margin-bottom: 1rem; - border: 1px solid var(--border-color); - overflow: hidden; + margin-bottom: 1rem; + border: 1px solid var(--border-color); + overflow: hidden; background: var(--editor-bg); } #cm6-editor { - flex: 1; - display: flex; - flex-direction: column; - border: 1px solid var(--border-color); - overflow: hidden; - background: var(--editor-bg); + flex: 1; + display: flex; + flex-direction: column; + border: 1px solid var(--border-color); + overflow: hidden; + background: var(--editor-bg); } #regex-editor .cm-editor, #cm6-editor .cm-editor { - height: 100%; + height: 100%; } #regex-editor .cm-scroller, #cm6-editor .cm-scroller { - overflow: auto; + overflow: auto; } - .cm-editor.cm-focused, - .cm-scroller:focus, + .cm-editor.cm-focused, + .cm-scroller:focus, .cm-content:focus { - outline: none !important; + outline: none !important; } </style> @@ -196,50 +234,74 @@ layout: post {% include cm-importmap.html %} <script type="module"> - import { EditorState, StateField, StateEffect, RangeSetBuilder, Compartment } from '@codemirror/state'; - import { EditorView, Decoration, keymap, lineNumbers, highlightActiveLineGutter, highlightSpecialChars, drawSelection, highlightActiveLine } from '@codemirror/view'; - import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'; - import { syntaxHighlighting, defaultHighlightStyle, bracketMatching } from '@codemirror/language'; + import { + EditorState, + StateField, + StateEffect, + RangeSetBuilder, + Compartment, + } from "@codemirror/state"; + import { + EditorView, + Decoration, + keymap, + lineNumbers, + highlightActiveLineGutter, + highlightSpecialChars, + drawSelection, + highlightActiveLine, + } from "@codemirror/view"; + import { defaultKeymap, history, historyKeymap } from "@codemirror/commands"; + import { + syntaxHighlighting, + defaultHighlightStyle, + bracketMatching, + } from "@codemirror/language"; import { githubLight, githubDark } from "@uiw/codemirror-theme-github"; - const darkQuery = window.matchMedia('(prefers-color-scheme: dark)'); - const themeFor = dark => dark ? githubDark : githubLight; + const darkQuery = window.matchMedia("(prefers-color-scheme: dark)"); + const themeFor = (dark) => (dark ? githubDark : githubLight); const regexTheme = new Compartment(); - const mainTheme = new Compartment(); + const mainTheme = new Compartment(); // Top Editor Line Fringe Decorations const topEditorLineDecos = [ - Decoration.line({ class: 'cm-regex-line-0' }), - Decoration.line({ class: 'cm-regex-line-1' }), - Decoration.line({ class: 'cm-regex-line-2' }), - Decoration.line({ class: 'cm-regex-line-3' }), - Decoration.line({ class: 'cm-regex-line-4' }) + Decoration.line({ class: "cm-regex-line-0" }), + Decoration.line({ class: "cm-regex-line-1" }), + Decoration.line({ class: "cm-regex-line-2" }), + Decoration.line({ class: "cm-regex-line-3" }), + Decoration.line({ class: "cm-regex-line-4" }), ]; const topEditorField = StateField.define({ - create: state => buildTopDecorations(state), - update: (decorations, tr) => tr.docChanged ? buildTopDecorations(tr.state) : decorations, - provide: f => EditorView.decorations.from(f) + create: (state) => buildTopDecorations(state), + update: (decorations, tr) => + tr.docChanged ? buildTopDecorations(tr.state) : decorations, + provide: (f) => EditorView.decorations.from(f), }); function buildTopDecorations(state) { - const builder = new RangeSetBuilder(); - for (let i = 1; i <= state.doc.lines; i++) { - const line = state.doc.line(i); - builder.add(line.from, line.from, topEditorLineDecos[(i - 1) % topEditorLineDecos.length]); - } - return builder.finish(); - } - - + const builder = new RangeSetBuilder(); + for (let i = 1; i <= state.doc.lines; i++) { + const line = state.doc.line(i); + builder.add( + line.from, + line.from, + topEditorLineDecos[(i - 1) % topEditorLineDecos.length], + ); + } + return builder.finish(); + } - // Load saved regex patterns or fallback to default - const savedRegex = localStorage.getItem('regex_patterns') || "/([a-zA-Z0-9._-]+)@([a-zA-Z0-9._-]+)\\.([a-zA-Z]{2,5})/\n/([0-9]{4}-[0-9]{2}-[0-9]{2})/"; + // Load saved regex patterns or fallback to default + const savedRegex = + localStorage.getItem("regex_patterns") || + "/([a-zA-Z0-9._-]+)@([a-zA-Z0-9._-]+)\\.([a-zA-Z]{2,5})/\n/([0-9]{4}-[0-9]{2}-[0-9]{2})/"; - // Load saved tabs/documents or fallback to defaults - let documents = JSON.parse(localStorage.getItem('regex_documents')) || [ - `Welcome to CodeMirror 6 Regex Visualizer! + // Load saved tabs/documents or fallback to defaults + let documents = JSON.parse(localStorage.getItem("regex_documents")) || [ + `Welcome to CodeMirror 6 Regex Visualizer! Test email addresses: - Support Team: support@example.com @@ -251,7 +313,7 @@ layout: post - User ID: 1042 | Session Token: abc123XYZ - Created on: 2026-10-17`, - `function testAuth() { + `function testAuth() { const token = "Bearer eyJhbGciOiJIUzI1NiIsIn"; let userId = 9921; // TODO: sanitize input @@ -259,268 +321,280 @@ layout: post } const date = "2026-10-18";`, - `[2026-10-18 10:15:30] INFO: User login success (uid=402, ip=192.168.1.50) + `[2026-10-18 10:15:30] INFO: User login success (uid=402, ip=192.168.1.50) [2026-10-18 10:16:02] ERROR: Invalid token provided (token=xyz_9988_abc) - [2026-10-18 10:17:11] WARN: Rate limit approaching for service@api.net` - ]; + [2026-10-18 10:17:11] WARN: Rate limit approaching for service@api.net`, + ]; - let tabTitles = JSON.parse(localStorage.getItem('regex_tab_titles')) || ["Emails & IDs", "Code Snippet", "Server Logs"]; - let activeTabIndex = parseInt(localStorage.getItem('regex_active_tab')) || 0; - if (activeTabIndex >= documents.length) activeTabIndex = 0; + let tabTitles = JSON.parse(localStorage.getItem("regex_tab_titles")) || [ + "Emails & IDs", + "Code Snippet", + "Server Logs", + ]; + let activeTabIndex = parseInt(localStorage.getItem("regex_active_tab")) || 0; + if (activeTabIndex >= documents.length) activeTabIndex = 0; - function saveToStorage() { + function saveToStorage() { // Keep current active tab text up to date in the array before saving documents[activeTabIndex] = mainView.state.doc.toString(); - - localStorage.setItem('regex_patterns', regexView.state.doc.toString()); - localStorage.setItem('regex_documents', JSON.stringify(documents)); - localStorage.setItem('regex_tab_titles', JSON.stringify(tabTitles)); - localStorage.setItem('regex_active_tab', activeTabIndex.toString()); - } - const initialRegex = "/([a-zA-Z0-9.\-_]+)@([a-zA-Z0-9.\-_]+)\\.([a-zA-Z]{2,5})/\n/([0-9]{4}-[0-9]{2}-[0-9]{2})/"; + localStorage.setItem("regex_patterns", regexView.state.doc.toString()); + localStorage.setItem("regex_documents", JSON.stringify(documents)); + localStorage.setItem("regex_tab_titles", JSON.stringify(tabTitles)); + localStorage.setItem("regex_active_tab", activeTabIndex.toString()); + } - const regexView = new EditorView({ + const initialRegex = + "/([a-zA-Z0-9.\-_]+)@([a-zA-Z0-9.\-_]+)\\.([a-zA-Z]{2,5})/\n/([0-9]{4}-[0-9]{2}-[0-9]{2})/"; + + const regexView = new EditorView({ state: EditorState.create({ - doc: savedRegex, - extensions: [ - lineNumbers(), - drawSelection(), - keymap.of(defaultKeymap), - regexTheme.of(themeFor(darkQuery.matches)), - topEditorField, - EditorView.updateListener.of(update => { - if (update.docChanged) { - dispatchRegexUpdate(); - saveToStorage(); - } - }) - ] + doc: savedRegex, + extensions: [ + lineNumbers(), + drawSelection(), + keymap.of(defaultKeymap), + regexTheme.of(themeFor(darkQuery.matches)), + topEditorField, + EditorView.updateListener.of((update) => { + if (update.docChanged) { + dispatchRegexUpdate(); + saveToStorage(); + } + }), + ], }), - parent: document.getElementById('regex-editor') - }); + parent: document.getElementById("regex-editor"), + }); - const tabBarEl = document.getElementById('tab-bar'); + const tabBarEl = document.getElementById("tab-bar"); - function renderTabs() { - tabBarEl.innerHTML = ''; + function renderTabs() { + tabBarEl.innerHTML = ""; documents.forEach((_, index) => { - const tab = document.createElement('div'); - tab.className = 'tab-btn' + (index === activeTabIndex ? ' active' : ''); - tab.setAttribute('role', 'tab'); - - const label = document.createElement('span'); - label.textContent = tabTitles[index] || `Tab ${index + 1}`; - label.title = 'Double-click to rename'; - label.addEventListener('dblclick', () => renameTab(index, label)); - tab.appendChild(label); - - if (documents.length > 1) { - const closeBtn = document.createElement('span'); - closeBtn.className = 'tab-close'; - closeBtn.setAttribute('role', 'button'); - closeBtn.title = 'Close Tab'; - closeBtn.textContent = '×'; - closeBtn.addEventListener('click', e => { e.stopPropagation(); closeTab(index); }); - tab.appendChild(closeBtn); - } + const tab = document.createElement("div"); + tab.className = "tab-btn" + (index === activeTabIndex ? " active" : ""); + tab.setAttribute("role", "tab"); + + const label = document.createElement("span"); + label.textContent = tabTitles[index] || `Tab ${index + 1}`; + label.title = "Double-click to rename"; + label.addEventListener("dblclick", () => renameTab(index, label)); + tab.appendChild(label); + + if (documents.length > 1) { + const closeBtn = document.createElement("span"); + closeBtn.className = "tab-close"; + closeBtn.setAttribute("role", "button"); + closeBtn.title = "Close Tab"; + closeBtn.textContent = "×"; + closeBtn.addEventListener("click", (e) => { + e.stopPropagation(); + closeTab(index); + }); + tab.appendChild(closeBtn); + } - tab.addEventListener('click', () => switchTab(index)); - tabBarEl.appendChild(tab); + tab.addEventListener("click", () => switchTab(index)); + tabBarEl.appendChild(tab); }); - } + } - function renameTab(index, label) { - const input = document.createElement('input'); - input.value = tabTitles[index] || ''; + function renameTab(index, label) { + const input = document.createElement("input"); + input.value = tabTitles[index] || ""; input.size = Math.max(6, input.value.length); label.replaceWith(input); input.focus(); input.select(); let done = false; - const finish = save => { - if (done) return; // blur also fires when the input is removed - done = true; - const name = input.value.trim(); - if (save && name) tabTitles[index] = name; // empty name keeps the old one - renderTabs(); - saveToStorage(); + const finish = (save) => { + if (done) return; // blur also fires when the input is removed + done = true; + const name = input.value.trim(); + if (save && name) tabTitles[index] = name; // empty name keeps the old one + renderTabs(); + saveToStorage(); }; - input.addEventListener('keydown', e => { - e.stopPropagation(); - if (e.key === 'Enter') finish(true); - if (e.key === 'Escape') finish(false); + input.addEventListener("keydown", (e) => { + e.stopPropagation(); + if (e.key === "Enter") finish(true); + if (e.key === "Escape") finish(false); }); - input.addEventListener('blur', () => finish(true)); - input.addEventListener('click', e => e.stopPropagation()); // don't trigger switchTab - } + input.addEventListener("blur", () => finish(true)); + input.addEventListener("click", (e) => e.stopPropagation()); // don't trigger switchTab + } function applyTheme() { - const t = themeFor(darkQuery.matches); - regexView.dispatch({ effects: regexTheme.reconfigure(t) }); - mainView.dispatch({ effects: mainTheme.reconfigure(t) }); + const t = themeFor(darkQuery.matches); + regexView.dispatch({ effects: regexTheme.reconfigure(t) }); + mainView.dispatch({ effects: mainTheme.reconfigure(t) }); } - darkQuery.addEventListener('change', applyTheme); + darkQuery.addEventListener("change", applyTheme); - function loadActive() { + function loadActive() { renderTabs(); mainView.setState(states[activeTabIndex]); - applyTheme(); - dispatchRegexUpdate(); // a stashed state may hold stale regex queries - saveToStorage(); // must run after setState so it saves the new tab's text - } + applyTheme(); + dispatchRegexUpdate(); // a stashed state may hold stale regex queries + saveToStorage(); // must run after setState so it saves the new tab's text + } - function switchTab(index) { + function switchTab(index) { if (index === activeTabIndex) return; - states[activeTabIndex] = mainView.state; // stash undo history + cursor + states[activeTabIndex] = mainView.state; // stash undo history + cursor activeTabIndex = index; loadActive(); - } + } - function closeTab(index) { + function closeTab(index) { const tabName = tabTitles[index] || `Tab ${index + 1}`; if (!window.confirm(`Are you sure you want to close "${tabName}"?`)) return; states[activeTabIndex] = mainView.state; - [documents, tabTitles, states].forEach(a => a.splice(index, 1)); - - if (index < activeTabIndex) activeTabIndex--; // also fixes the wrong-tab bug - else activeTabIndex = Math.min(activeTabIndex, documents.length - 1); - loadActive(); - } - - document.getElementById('add-tab-btn').addEventListener('click', () => { - const text = "// Type or paste text here...\n"; - states[activeTabIndex] = mainView.state; - documents.push(text); - tabTitles.push(`Dataset ${documents.length}`); - states.push(makeState(text)); - activeTabIndex = documents.length - 1; - loadActive(); - }); + [documents, tabTitles, states].forEach((a) => a.splice(index, 1)); - // Initial Render Call - renderTabs(); - - const updateRegexEffect = StateEffect.define(); - - const regexMatchDecos = [ - Decoration.mark({ class: 'cm-regex-match-0' }), - Decoration.mark({ class: 'cm-regex-match-1' }), - Decoration.mark({ class: 'cm-regex-match-2' }), - Decoration.mark({ class: 'cm-regex-match-3' }), - Decoration.mark({ class: 'cm-regex-match-4' }) - ]; - - const regexHighlightField = StateField.define({ - create: () => ({ decorations: Decoration.none, query: [] }), - update(value, tr) { + if (index < activeTabIndex) + activeTabIndex--; // also fixes the wrong-tab bug + else activeTabIndex = Math.min(activeTabIndex, documents.length - 1); + loadActive(); + } + + document.getElementById("add-tab-btn").addEventListener("click", () => { + const text = "// Type or paste text here...\n"; + states[activeTabIndex] = mainView.state; + documents.push(text); + tabTitles.push(`Dataset ${documents.length}`); + states.push(makeState(text)); + activeTabIndex = documents.length - 1; + loadActive(); + }); + + // Initial Render Call + renderTabs(); + + const updateRegexEffect = StateEffect.define(); + + const regexMatchDecos = [ + Decoration.mark({ class: "cm-regex-match-0" }), + Decoration.mark({ class: "cm-regex-match-1" }), + Decoration.mark({ class: "cm-regex-match-2" }), + Decoration.mark({ class: "cm-regex-match-3" }), + Decoration.mark({ class: "cm-regex-match-4" }), + ]; + + const regexHighlightField = StateField.define({ + create: () => ({ decorations: Decoration.none, query: [] }), + update(value, tr) { let currentQueries = value.query; for (let effect of tr.effects) { - if (effect.is(updateRegexEffect)) currentQueries = effect.value; + if (effect.is(updateRegexEffect)) currentQueries = effect.value; } if (!tr.docChanged && tr.effects.length === 0) return value; const docText = tr.state.doc.toString(); const ranges = []; - currentQueries.forEach(q => { - if (!q.pattern) return; - let activeRegex; - try { - activeRegex = new RegExp(q.pattern, q.flags); - } catch (err) { - return; - } - - let match; - const deco = regexMatchDecos[q.lineIndex % regexMatchDecos.length]; - - if (q.flags.includes('g')) { - while ((match = activeRegex.exec(docText)) !== null) { - const fullStart = match.index; - const fullEnd = fullStart + match[0].length; - if (fullStart !== fullEnd) { - ranges.push({ from: fullStart, to: fullEnd, deco }); - } - if (match.index === activeRegex.lastIndex) { - activeRegex.lastIndex++; - } - } - } else { - match = activeRegex.exec(docText); - if (match) { - const fullStart = match.index; - const fullEnd = fullStart + match[0].length; - if (fullStart !== fullEnd) { - ranges.push({ from: fullStart, to: fullEnd, deco }); - } - } - } + currentQueries.forEach((q) => { + if (!q.pattern) return; + let activeRegex; + try { + activeRegex = new RegExp(q.pattern, q.flags); + } catch (err) { + return; + } + + let match; + const deco = regexMatchDecos[q.lineIndex % regexMatchDecos.length]; + + if (q.flags.includes("g")) { + while ((match = activeRegex.exec(docText)) !== null) { + const fullStart = match.index; + const fullEnd = fullStart + match[0].length; + if (fullStart !== fullEnd) { + ranges.push({ from: fullStart, to: fullEnd, deco }); + } + if (match.index === activeRegex.lastIndex) { + activeRegex.lastIndex++; + } + } + } else { + match = activeRegex.exec(docText); + if (match) { + const fullStart = match.index; + const fullEnd = fullStart + match[0].length; + if (fullStart !== fullEnd) { + ranges.push({ from: fullStart, to: fullEnd, deco }); + } + } + } }); ranges.sort((a, b) => a.from - b.from || a.to - b.to); const builder = new RangeSetBuilder(); - ranges.forEach(r => builder.add(r.from, r.to, r.deco)); + ranges.forEach((r) => builder.add(r.from, r.to, r.deco)); return { decorations: builder.finish(), query: currentQueries }; - }, - provide: f => EditorView.decorations.from(f, val => val.decorations) - }); + }, + provide: (f) => EditorView.decorations.from(f, (val) => val.decorations), + }); - const mainExtensions = [ - lineNumbers(), - highlightActiveLineGutter(), - highlightSpecialChars(), - history(), - drawSelection(), - highlightActiveLine(), - bracketMatching(), - mainTheme.of(themeFor(darkQuery.matches)), - // syntaxHighlighting(defaultHighlightStyle, { fallback: true }), - keymap.of([...defaultKeymap, ...historyKeymap]), - regexHighlightField, - EditorView.updateListener.of(u => { if (u.docChanged) saveToStorage(); }) - ]; - - const makeState = doc => EditorState.create({ doc, extensions: mainExtensions }); - const states = documents.map(makeState); // one EditorState per tab, parallel to documents/tabTitles - - const mainView = new EditorView({ - state: states[activeTabIndex], - parent: document.getElementById('cm6-editor') - }); + const mainExtensions = [ + lineNumbers(), + highlightActiveLineGutter(), + highlightSpecialChars(), + history(), + drawSelection(), + highlightActiveLine(), + bracketMatching(), + mainTheme.of(themeFor(darkQuery.matches)), + // syntaxHighlighting(defaultHighlightStyle, { fallback: true }), + keymap.of([...defaultKeymap, ...historyKeymap]), + regexHighlightField, + EditorView.updateListener.of((u) => { + if (u.docChanged) saveToStorage(); + }), + ]; + + const makeState = (doc) => + EditorState.create({ doc, extensions: mainExtensions }); + const states = documents.map(makeState); // one EditorState per tab, parallel to documents/tabTitles - function dispatchRegexUpdate() { - const lines = regexView.state.doc.toString().split('\n'); - const queries = []; + const mainView = new EditorView({ + state: states[activeTabIndex], + parent: document.getElementById("cm6-editor"), + }); - lines.forEach((lineText, index) => { + function dispatchRegexUpdate() { + const lines = regexView.state.doc.toString().split("\n"); + const queries = []; + + lines.forEach((lineText, index) => { const trimmed = lineText.trim(); if (!trimmed) return; let pattern = trimmed; - let flags = 'g'; + let flags = "g"; - const lastSlashIndex = trimmed.lastIndexOf('/'); - if (trimmed.startsWith('/') && lastSlashIndex > 0) { - pattern = trimmed.substring(1, lastSlashIndex); - flags = trimmed.substring(lastSlashIndex + 1) || 'g'; + const lastSlashIndex = trimmed.lastIndexOf("/"); + if (trimmed.startsWith("/") && lastSlashIndex > 0) { + pattern = trimmed.substring(1, lastSlashIndex); + flags = trimmed.substring(lastSlashIndex + 1) || "g"; } queries.push({ pattern, flags, lineIndex: index }); - }); + }); - mainView.dispatch({ - effects: updateRegexEffect.of(queries) - }); - } + mainView.dispatch({ + effects: updateRegexEffect.of(queries), + }); + } - // Initial setup call - dispatchRegexUpdate(); + // Initial setup call + dispatchRegexUpdate(); - document.getElementById('loading').remove(); + document.getElementById("loading").remove(); </script> |