From 5cb2214d8876ecdc313649c304474a997167420a Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Thu, 22 Jan 2026 00:20:44 -0500 Subject: [PATCH 1/5] working --- packages/codemirror/codemirror.mjs | 61 ++++++++++++++++- packages/codemirror/labelJump.mjs | 106 +++++++++++++++++++++++++++-- 2 files changed, 157 insertions(+), 10 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index ec7eee85e..5dcd26455 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -24,7 +24,12 @@ import { sliderPlugin, updateSliderWidgets } from './slider.mjs'; import { activateTheme, initTheme, theme } from './themes.mjs'; import { isTooltipEnabled } from './tooltip.mjs'; import { updateWidgets, widgetPlugin } from './widget.mjs'; -import { jumpToCharacter } from './labelJump.mjs'; +import { + deleteAllInlineBeforeCharacter, + jumpToCharacter, + jumpToNextCharacter, + ToggleCharBeforeChar, +} from './labelJump.mjs'; export { toggleBlockComment, toggleBlockCommentByLine, toggleComment, toggleLineComment } from '@codemirror/commands'; @@ -74,6 +79,10 @@ export const codemirrorSettings = persistentAtom('codemirror-settings', defaultS decode: JSON.parse, }); +const ANON_LABEL = '$'; +const SOLO_LABEL = 'S'; +const MUTE_LABEL = '_'; + // https://codemirror.net/docs/guide/ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, root, mondo }) { const settings = codemirrorSettings.get(); @@ -122,12 +131,58 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo }, { key: 'Alt-w', - run: (view) => jumpToCharacter(view, '$', 1), + run: (view) => jumpToNextCharacter(view, ANON_LABEL, 1), }, { key: 'Alt-q', - run: (view) => jumpToCharacter(view, '$', -1), + run: (view) => { + return jumpToNextCharacter(view, ANON_LABEL, -1); + }, }, + // clear all muted + { + key: `Alt-Ctrl-0`, + run: (view) => { + return deleteAllInlineBeforeCharacter(view, MUTE_LABEL + ANON_LABEL); + }, + }, + // clear all solod + { + key: `Alt-Shift-0`, + run: (view) => { + return deleteAllInlineBeforeCharacter(view, SOLO_LABEL + ANON_LABEL); + }, + }, + ...Array.from({ length: 9 }).map((_, i) => { + let num = i + 1; + return { + key: `Alt-${num}`, + run: (view) => { + return jumpToCharacter(view, ANON_LABEL, i); + }, + }; + }), + // handle solo toggles 1-9 + ...Array.from({ length: 9 }).map((_, i) => { + let num = i + 1; + return { + key: `Alt-Shift-${num}`, + run: (view) => { + return ToggleCharBeforeChar(view, ANON_LABEL, SOLO_LABEL, i); + }, + }; + }), + // handle mute toggles 1-9 + ...Array.from({ length: 9 }).map((_, i) => { + let num = i + 1; + return { + key: `Alt-Ctrl-${num}`, + run: (view) => { + return ToggleCharBeforeChar(view, ANON_LABEL, MUTE_LABEL, i); + }, + }; + }), + /* { key: 'Ctrl-Shift-.', run: () => (onPanic ? onPanic() : onStop?.()), diff --git a/packages/codemirror/labelJump.mjs b/packages/codemirror/labelJump.mjs index 0aced4eb5..c0a0c8e59 100644 --- a/packages/codemirror/labelJump.mjs +++ b/packages/codemirror/labelJump.mjs @@ -1,16 +1,42 @@ import { EditorSelection } from '@codemirror/state'; import { SearchCursor } from '@codemirror/search'; +import { EditorView } from '@codemirror/view'; -export function jumpToCharacter(view, character, direction = 1) { - const { state, dispatch } = view; - const pos = state.selection.main.head; +function getCharacterPositions(state, character) { const cursor = new SearchCursor(state.doc, character); - - let characterPositions = []; - let jumpPos; + const characterPositions = []; while (!cursor.next().done) { characterPositions.push(cursor.value.to); } + return characterPositions; +} + +// function jumpToNextCharacter(view,character, direction = 1){ +// const { state, dispatch } = view; +// const pos = state.selection.main.head; +// let jumpPos; +// const characterPositions = getCharacterPositions(state, character) +// if (!characterPositions.length) { +// return false; +// } +// if (direction > 0) { +// jumpPos = characterPositions.find((x) => x > pos + 1) ?? characterPositions.at(0); // Loop back around for convenience +// } else { +// jumpPos = characterPositions.reverse().find((x) => x < pos + 1) ?? characterPositions.at(0); +// } + +// if (jumpPos == null) { +// return false; +// } +// const selection = EditorSelection.cursor(jumpPos - 1); +// } + +const COMMENT_STRING = '//'; +export function jumpToNextCharacter(view, character, direction = 1) { + const { state, dispatch } = view; + const pos = state.selection.main.head; + let jumpPos; + const characterPositions = getCharacterPositions(state, character); if (!characterPositions.length) { return false; } @@ -23,9 +49,75 @@ export function jumpToCharacter(view, character, direction = 1) { if (jumpPos == null) { return false; } + const selection = EditorSelection.cursor(jumpPos - 1); dispatch({ - selection: EditorSelection.cursor(jumpPos - 1), + selection, + scrollIntoView: true, + sequential: true, + }); + return true; +} + +export function jumpToCharacter(view, character, num) { + const { state, dispatch } = view; + const characterPositions = getCharacterPositions(state, character); + const pos = characterPositions.at(num) ?? characterPositions.at(-1); + if (pos == null) { + return false; + } + + const selection = EditorSelection.cursor(pos - 1); + dispatch({ + selection, scrollIntoView: true, }); return true; } +export function deleteAllInlineBeforeCharacter(view, character) { + const { state, dispatch } = view; + const characterPositions = getCharacterPositions(state, character); + + const changes = []; + characterPositions.forEach((pos) => { + const line = state.doc.lineAt(pos); + if (state.doc.sliceString(line.from, line.from + 1) === COMMENT_STRING) { + return; + } + changes.push({ + from: line.from, + to: pos - 1, + insert: '', + }); + }); + dispatch({ changes }); + return true; +} +export function ToggleCharBeforeChar(view, character, character2, num) { + const { state, dispatch } = view; + + const changes = []; + const characterPositions = getCharacterPositions(state, character); + const labelpos = characterPositions.at(num) ?? characterPositions.at(-1); + const line = state.doc.lineAt(labelpos); + + if (state.doc.sliceString(line.from, line.from + 1) === COMMENT_STRING) { + return false; + } + //delete preceeding characters + changes.push({ + from: line.from, + to: labelpos - 1, + insert: '', + }); + + const preceedingLabelPos = labelpos - 2; + if (line.from === labelpos - 1 || state.doc.sliceString(preceedingLabelPos, labelpos - 1) !== character2) { + changes.push({ + insert: character2, + from: line.from, + }); + } + + dispatch({ changes }); + return true; +} From b436ae789c6b0b28c25421c11005b8e48d562f33 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Thu, 22 Jan 2026 00:25:28 -0500 Subject: [PATCH 2/5] rm dead code --- packages/codemirror/labelJump.mjs | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/packages/codemirror/labelJump.mjs b/packages/codemirror/labelJump.mjs index c0a0c8e59..bdbaa55cd 100644 --- a/packages/codemirror/labelJump.mjs +++ b/packages/codemirror/labelJump.mjs @@ -1,6 +1,7 @@ import { EditorSelection } from '@codemirror/state'; import { SearchCursor } from '@codemirror/search'; -import { EditorView } from '@codemirror/view'; + +const COMMENT_STRING = '//'; function getCharacterPositions(state, character) { const cursor = new SearchCursor(state.doc, character); @@ -11,27 +12,6 @@ function getCharacterPositions(state, character) { return characterPositions; } -// function jumpToNextCharacter(view,character, direction = 1){ -// const { state, dispatch } = view; -// const pos = state.selection.main.head; -// let jumpPos; -// const characterPositions = getCharacterPositions(state, character) -// if (!characterPositions.length) { -// return false; -// } -// if (direction > 0) { -// jumpPos = characterPositions.find((x) => x > pos + 1) ?? characterPositions.at(0); // Loop back around for convenience -// } else { -// jumpPos = characterPositions.reverse().find((x) => x < pos + 1) ?? characterPositions.at(0); -// } - -// if (jumpPos == null) { -// return false; -// } -// const selection = EditorSelection.cursor(jumpPos - 1); -// } - -const COMMENT_STRING = '//'; export function jumpToNextCharacter(view, character, direction = 1) { const { state, dispatch } = view; const pos = state.selection.main.head; From 3c5afb32f33785f8652acb4e153698826b6a495e Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Thu, 22 Jan 2026 02:04:27 -0500 Subject: [PATCH 3/5] scroll to center --- packages/codemirror/labelJump.mjs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/codemirror/labelJump.mjs b/packages/codemirror/labelJump.mjs index bdbaa55cd..385ca5b20 100644 --- a/packages/codemirror/labelJump.mjs +++ b/packages/codemirror/labelJump.mjs @@ -1,5 +1,6 @@ import { EditorSelection } from '@codemirror/state'; import { SearchCursor } from '@codemirror/search'; +import { EditorView } from '@codemirror/view'; const COMMENT_STRING = '//'; @@ -49,7 +50,10 @@ export function jumpToCharacter(view, character, num) { const selection = EditorSelection.cursor(pos - 1); dispatch({ selection, - scrollIntoView: true, + effects: EditorView.scrollIntoView( + selection.head, + {y: "center"} + ) }); return true; } From 7e0eed87cb68f2ae128874904e4c76a035df2b8e Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Tue, 27 Jan 2026 17:21:36 -0500 Subject: [PATCH 4/5] normalize shortcuts --- packages/codemirror/codemirror.mjs | 22 +++++++++++++++++++-- packages/codemirror/labelJump.mjs | 31 ++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 9acd77c1c..1939ed759 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -26,6 +26,7 @@ import { isTooltipEnabled } from './tooltip.mjs'; import { updateWidgets, widgetPlugin } from './widget.mjs'; import { deleteAllInlineBeforeCharacter, + InsertCharBeforeChar, jumpToCharacter, jumpToNextCharacter, ToggleCharBeforeChar, @@ -153,6 +154,13 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo return deleteAllInlineBeforeCharacter(view, SOLO_LABEL + ANON_LABEL); }, }, + // clear all solo and mute + { + key: `Ctrl-Shift-0`, + run: (view) => { + return deleteAllInlineBeforeCharacter(view, SOLO_LABEL); + }, + }, ...Array.from({ length: 9 }).map((_, i) => { let num = i + 1; return { @@ -168,7 +176,7 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo return { key: `Alt-Shift-${num}`, run: (view) => { - return ToggleCharBeforeChar(view, ANON_LABEL, SOLO_LABEL, i); + return InsertCharBeforeChar(view, ANON_LABEL, SOLO_LABEL, i); }, }; }), @@ -178,7 +186,17 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo return { key: `Alt-Ctrl-${num}`, run: (view) => { - return ToggleCharBeforeChar(view, ANON_LABEL, MUTE_LABEL, i); + return InsertCharBeforeChar(view, ANON_LABEL, MUTE_LABEL, i); + }, + }; + }), + // Handle clearing mutes and solos 1-9 + ...Array.from({ length: 9 }).map((_, i) => { + let num = i + 1; + return { + key: `Ctrl-Shift-${num}`, + run: (view) => { + return InsertCharBeforeChar(view, ANON_LABEL,'', i); }, }; }), diff --git a/packages/codemirror/labelJump.mjs b/packages/codemirror/labelJump.mjs index 385ca5b20..b58f10098 100644 --- a/packages/codemirror/labelJump.mjs +++ b/packages/codemirror/labelJump.mjs @@ -57,6 +57,7 @@ export function jumpToCharacter(view, character, num) { }); return true; } + export function deleteAllInlineBeforeCharacter(view, character) { const { state, dispatch } = view; const characterPositions = getCharacterPositions(state, character); @@ -64,7 +65,7 @@ export function deleteAllInlineBeforeCharacter(view, character) { const changes = []; characterPositions.forEach((pos) => { const line = state.doc.lineAt(pos); - if (state.doc.sliceString(line.from, line.from + 1) === COMMENT_STRING) { + if (state.doc.sliceString(line.from, line.from + 2) === COMMENT_STRING) { return; } changes.push({ @@ -84,7 +85,7 @@ export function ToggleCharBeforeChar(view, character, character2, num) { const labelpos = characterPositions.at(num) ?? characterPositions.at(-1); const line = state.doc.lineAt(labelpos); - if (state.doc.sliceString(line.from, line.from + 1) === COMMENT_STRING) { + if (state.doc.sliceString(line.from, line.from + 2) === COMMENT_STRING) { return false; } //delete preceeding characters @@ -105,3 +106,29 @@ export function ToggleCharBeforeChar(view, character, character2, num) { dispatch({ changes }); return true; } +export function InsertCharBeforeChar(view, character, character2, num) { + const { state, dispatch } = view; + + const changes = []; + const characterPositions = getCharacterPositions(state, character); + const labelpos = characterPositions.at(num) ?? characterPositions.at(-1); + const line = state.doc.lineAt(labelpos); + + if (state.doc.sliceString(line.from, line.from + 2) === COMMENT_STRING) { + return false; + } + //delete preceeding characters + changes.push({ + from: line.from, + to: labelpos - 1, + insert: '', + }); + + changes.push({ + insert: character2, + from: line.from, + }); + + dispatch({ changes }); + return true; +} From 90c05ec38af7432379aaa483c0ea9b8650f659b9 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Sun, 8 Feb 2026 14:12:21 -0500 Subject: [PATCH 5/5] fix edge cases --- packages/codemirror/codemirror.mjs | 3 +- packages/codemirror/labelJump.mjs | 117 +++++++++++++++++------------ 2 files changed, 69 insertions(+), 51 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 1939ed759..51d9280b2 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -29,7 +29,6 @@ import { InsertCharBeforeChar, jumpToCharacter, jumpToNextCharacter, - ToggleCharBeforeChar, } from './labelJump.mjs'; export { toggleBlockComment, toggleBlockCommentByLine, toggleComment, toggleLineComment } from '@codemirror/commands'; @@ -158,7 +157,7 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, roo { key: `Ctrl-Shift-0`, run: (view) => { - return deleteAllInlineBeforeCharacter(view, SOLO_LABEL); + return deleteAllInlineBeforeCharacter(view, ANON_LABEL); }, }, ...Array.from({ length: 9 }).map((_, i) => { diff --git a/packages/codemirror/labelJump.mjs b/packages/codemirror/labelJump.mjs index b58f10098..12e5400db 100644 --- a/packages/codemirror/labelJump.mjs +++ b/packages/codemirror/labelJump.mjs @@ -1,25 +1,54 @@ import { EditorSelection } from '@codemirror/state'; import { SearchCursor } from '@codemirror/search'; import { EditorView } from '@codemirror/view'; +import { syntaxTree } from '@codemirror/language'; -const COMMENT_STRING = '//'; - +/** + * gets all of the positions of a character in a document, excluding commented out lines + * @param { EditorState} state + * @param {String} character + * @returns {number[]} + */ function getCharacterPositions(state, character) { const cursor = new SearchCursor(state.doc, character); + const characterPositions = []; while (!cursor.next().done) { - characterPositions.push(cursor.value.to); + + const linestartpos = state.doc.lineAt(cursor.value.to).from + if (!isLineCommentedOut(state, linestartpos)) { + + characterPositions.push(cursor.value.to); + } } return characterPositions; } +function isLineCommentedOut(state, pos) { + + const line = state.doc.lineAt(pos); + // remove white space + pos = line.from + line.text.search(/\S/) + + const tree = syntaxTree(state); + const node = tree.resolveInner(pos, 1) + return node.name.includes("Comment") +} + +/** + * jump to the next character in a document + * @param {EditorView} view + * @param {String} character + * @param {number} direction 0 or 1 + * @returns {boolean} + */ export function jumpToNextCharacter(view, character, direction = 1) { const { state, dispatch } = view; const pos = state.selection.main.head; let jumpPos; const characterPositions = getCharacterPositions(state, character); if (!characterPositions.length) { - return false; + return true; } if (direction > 0) { jumpPos = characterPositions.find((x) => x > pos + 1) ?? characterPositions.at(0); // Loop back around for convenience @@ -28,23 +57,31 @@ export function jumpToNextCharacter(view, character, direction = 1) { } if (jumpPos == null) { - return false; + return true; } const selection = EditorSelection.cursor(jumpPos - 1); dispatch({ selection, - scrollIntoView: true, - sequential: true, + effects: EditorView.scrollIntoView( + selection.head, + { y: "start" } + ) }); return true; } - -export function jumpToCharacter(view, character, num) { +/** + * + * @param {EditorView} view + * @param {String} character + * @param {number} index the instance of the character + * @returns + */ +export function jumpToCharacter(view, character, index) { const { state, dispatch } = view; const characterPositions = getCharacterPositions(state, character); - const pos = characterPositions.at(num) ?? characterPositions.at(-1); + const pos = characterPositions.at(index) ?? characterPositions.at(-1); if (pos == null) { - return false; + return true; } const selection = EditorSelection.cursor(pos - 1); @@ -52,12 +89,17 @@ export function jumpToCharacter(view, character, num) { selection, effects: EditorView.scrollIntoView( selection.head, - {y: "center"} + { y: "start" } ) }); return true; } - +/** + * + * @param {EditorView} view + * @param {String} character + * @returns {true} + */ export function deleteAllInlineBeforeCharacter(view, character) { const { state, dispatch } = view; const characterPositions = getCharacterPositions(state, character); @@ -77,46 +119,23 @@ export function deleteAllInlineBeforeCharacter(view, character) { dispatch({ changes }); return true; } -export function ToggleCharBeforeChar(view, character, character2, num) { + +/** + * + * @param {EditorView} view + * @param {String} character + * @param {String} character2 + * @param {number} index + * @returns + */ +export function InsertCharBeforeChar(view, character, character2, index) { const { state, dispatch } = view; const changes = []; const characterPositions = getCharacterPositions(state, character); - const labelpos = characterPositions.at(num) ?? characterPositions.at(-1); + const labelpos = characterPositions.at(index) ?? characterPositions.at(-1); const line = state.doc.lineAt(labelpos); - if (state.doc.sliceString(line.from, line.from + 2) === COMMENT_STRING) { - return false; - } - //delete preceeding characters - changes.push({ - from: line.from, - to: labelpos - 1, - insert: '', - }); - - const preceedingLabelPos = labelpos - 2; - if (line.from === labelpos - 1 || state.doc.sliceString(preceedingLabelPos, labelpos - 1) !== character2) { - changes.push({ - insert: character2, - from: line.from, - }); - } - - dispatch({ changes }); - return true; -} -export function InsertCharBeforeChar(view, character, character2, num) { - const { state, dispatch } = view; - - const changes = []; - const characterPositions = getCharacterPositions(state, character); - const labelpos = characterPositions.at(num) ?? characterPositions.at(-1); - const line = state.doc.lineAt(labelpos); - - if (state.doc.sliceString(line.from, line.from + 2) === COMMENT_STRING) { - return false; - } //delete preceeding characters changes.push({ from: line.from, @@ -125,8 +144,8 @@ export function InsertCharBeforeChar(view, character, character2, num) { }); changes.push({ - insert: character2, - from: line.from, + insert: character2, + from: line.from, }); dispatch({ changes });