paren marking

This commit is contained in:
Felix Roos 2022-03-15 21:22:57 +01:00
parent 82b75e2690
commit e5f462814e
2 changed files with 91 additions and 17 deletions

View file

@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
import * as Tone from 'tone'; import * as Tone from 'tone';
import CodeMirror, { markEvent } from './CodeMirror'; import CodeMirror, { markEvent, markParens } from './CodeMirror';
import cx from './cx'; import cx from './cx';
import { evaluate } from './evaluate'; import { evaluate } from './evaluate';
import logo from './logo.svg'; import logo from './logo.svg';
@ -156,6 +156,7 @@ function App() {
styleSelectedText: true, styleSelectedText: true,
cursorBlinkRate: 0, cursorBlinkRate: 0,
}} }}
onCursor={markParens}
onChange={(_: any, __: any, value: any) => setCode(value)} onChange={(_: any, __: any, value: any) => setCode(value)}
/> />
<span className="p-4 absolute top-0 right-0 text-xs whitespace-pre text-right pointer-events-none"> <span className="p-4 absolute top-0 right-0 text-xs whitespace-pre text-right pointer-events-none">

View file

@ -6,7 +6,7 @@ import 'codemirror/mode/pegjs/pegjs.js';
import 'codemirror/lib/codemirror.css'; import 'codemirror/lib/codemirror.css';
import 'codemirror/theme/material.css'; import 'codemirror/theme/material.css';
export default function CodeMirror({ value, onChange, options, editorDidMount }: any) { export default function CodeMirror({ value, onChange, onCursor, options, editorDidMount }: any) {
options = options || { options = options || {
mode: 'javascript', mode: 'javascript',
theme: 'material', theme: 'material',
@ -14,7 +14,15 @@ export default function CodeMirror({ value, onChange, options, editorDidMount }:
styleSelectedText: true, styleSelectedText: true,
cursorBlinkRate: 500, cursorBlinkRate: 500,
}; };
return <CodeMirror2 value={value} options={options} onBeforeChange={onChange} editorDidMount={editorDidMount} />; return (
<CodeMirror2
value={value}
options={options}
onBeforeChange={onChange}
editorDidMount={editorDidMount}
onCursor={(editor, data) => onCursor?.(editor, data)}
/>
);
} }
export const markEvent = (editor) => (time, event) => { export const markEvent = (editor) => (time, event) => {
@ -24,23 +32,88 @@ export const markEvent = (editor) => (time, event) => {
} }
// mark active event // mark active event
const marks = locs.map(({ start, end }) => const marks = locs.map(({ start, end }) =>
editor editor.getDoc().markText(
.getDoc() { line: start.line - 1, ch: start.column },
.markText( { line: end.line - 1, ch: end.column },
{ line: start.line - 1, ch: start.column }, //{ css: 'background-color: #FFCA28; color: black' } // background-color is now used by parent marking
{ line: end.line - 1, ch: end.column }, { css: 'outline: 1px solid #FFCA28; box-sizing:border-box' }
{ css: 'background-color: #FFCA28; color: black' } )
)
); );
//Tone.Transport.schedule(() => { // problem: this can be cleared by scheduler... //Tone.Transport.schedule(() => { // problem: this can be cleared by scheduler...
setTimeout(() => { setTimeout(() => {
marks.forEach((mark) => mark.clear()); marks.forEach((mark) => mark.clear());
// }, '+' + event.duration * 0.5); // }, '+' + event.duration * 0.5);
}, event.duration * 0.9 * 1000); }, event.duration /* * 0.9 */ * 1000);
}; };
// idea: to improve highlighting, all patterns that appear anywhere in the code could be queried seperately let parenMark;
// the created events could then be used to highlight primitives as long as they are active export const markParens = (editor, data) => {
// this would create a less flickery output, with no duplications const v = editor.getDoc().getValue();
// it would be seperated completely from the querying that happens to get the sound output const marked = getCurrentParenArea(v, data);
// it would also allow highlighting primitives that don't even end up in the sounding events (just for visual purposes) parenMark?.clear();
parenMark = editor.getDoc().markText(...marked, { css: 'background-color: #00000020' }); //
};
// returns { line, ch } from absolute character offset
export function offsetToPosition(offset, code) {
const lines = code.split('\n');
let line = 0;
let ch = 0;
for (let i = 0; i < offset; i++) {
if (ch === lines[line].length) {
line++;
ch = 0;
} else {
ch++;
}
}
return { line, ch };
}
// returns absolute character offset from { line, ch }
export function positionToOffset(position, code) {
const lines = code.split('\n');
let offset = 0;
for (let i = 0; i < position.line; i++) {
offset += lines[i].length + 1;
}
offset += position.ch;
return offset;
}
// given code and caret position, the functions returns the indices of the parens we are in
export function getCurrentParenArea(code, caretPosition) {
const caret = positionToOffset(caretPosition, code);
let open, i, begin, end;
// walk left
i = caret;
open = 0;
while (i > 0) {
if (code[i - 1] === '(') {
open--;
} else if (code[i - 1] === ')') {
open++;
}
if(open === -1) {
break;
}
i--;
}
begin = i;
// walk right
i = caret;
open = 0;
while (i < code.length) {
if (code[i] === '(') {
open--;
} else if (code[i] === ')') {
open++;
}
if(open === 1) {
break;
}
i++;
}
end = i;
return [begin, end].map((o) => offsetToPosition(o, code));
}