diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index 6014b9b0a..493c13fde 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -25,6 +25,12 @@ import { getSliderWidgets, sliderPlugin, updateSliderWidgets } from './slider.mj import { activateTheme, initTheme, theme } from './themes.mjs'; import { isTooltipEnabled } from './tooltip.mjs'; import { getActiveWidgets, updateWidgets, widgetPlugin } from './widget.mjs'; +import { + deleteAllInlineBeforeCharacter, + InsertCharBeforeChar, + jumpToCharacter, + jumpToNextCharacter, +} from './labelJump.mjs'; export { toggleBlockComment, toggleBlockCommentByLine, toggleComment, toggleLineComment } from '@codemirror/commands'; @@ -44,9 +50,9 @@ export const extensions = { isMultiCursorEnabled: (on) => on ? [ - EditorState.allowMultipleSelections.of(true), - EditorView.clickAddsSelectionRange.of((ev) => ev.metaKey || ev.ctrlKey), - ] + EditorState.allowMultipleSelections.of(true), + EditorView.clickAddsSelectionRange.of((ev) => ev.metaKey || ev.ctrlKey), + ] : [], }; export const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()])); @@ -75,6 +81,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, strudelMirror }) { const settings = codemirrorSettings.get(); @@ -138,12 +148,75 @@ 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); + }, + }, + // clear all solo and mute + { + key: `Ctrl-Shift-0`, + run: (view) => { + return deleteAllInlineBeforeCharacter(view, 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 InsertCharBeforeChar(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 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); + }, + }; + }), + /* { key: 'Ctrl-Shift-.', run: () => (onPanic ? onPanic() : onStop?.()), diff --git a/packages/codemirror/labelJump.mjs b/packages/codemirror/labelJump.mjs index 0aced4eb5..12e5400db 100644 --- a/packages/codemirror/labelJump.mjs +++ b/packages/codemirror/labelJump.mjs @@ -1,18 +1,54 @@ import { EditorSelection } from '@codemirror/state'; import { SearchCursor } from '@codemirror/search'; +import { EditorView } from '@codemirror/view'; +import { syntaxTree } from '@codemirror/language'; -export function jumpToCharacter(view, character, direction = 1) { +/** + * 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) { + + 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; - const cursor = new SearchCursor(state.doc, character); - - let characterPositions = []; let jumpPos; - while (!cursor.next().done) { - characterPositions.push(cursor.value.to); - } + 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 @@ -21,11 +57,97 @@ export function jumpToCharacter(view, character, direction = 1) { } if (jumpPos == null) { - return false; + return true; } + const selection = EditorSelection.cursor(jumpPos - 1); dispatch({ - selection: EditorSelection.cursor(jumpPos - 1), - scrollIntoView: true, + selection, + effects: EditorView.scrollIntoView( + selection.head, + { y: "start" } + ) }); return true; } +/** + * + * @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(index) ?? characterPositions.at(-1); + if (pos == null) { + return true; + } + + const selection = EditorSelection.cursor(pos - 1); + dispatch({ + selection, + effects: EditorView.scrollIntoView( + selection.head, + { 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); + + const changes = []; + characterPositions.forEach((pos) => { + const line = state.doc.lineAt(pos); + if (state.doc.sliceString(line.from, line.from + 2) === COMMENT_STRING) { + return; + } + changes.push({ + from: line.from, + to: pos - 1, + insert: '', + }); + }); + dispatch({ changes }); + return true; +} + +/** + * + * @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(index) ?? characterPositions.at(-1); + const line = state.doc.lineAt(labelpos); + + //delete preceeding characters + changes.push({ + from: line.from, + to: labelpos - 1, + insert: '', + }); + + changes.push({ + insert: character2, + from: line.from, + }); + + dispatch({ changes }); + return true; +}