From e5f462814e1c67899d8fddefd4519e064ad325f3 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 15 Mar 2022 21:22:57 +0100 Subject: [PATCH] paren marking --- repl/src/App.tsx | 5 +- repl/src/CodeMirror.tsx | 103 ++++++++++++++++++++++++++++++++++------ 2 files changed, 91 insertions(+), 17 deletions(-) diff --git a/repl/src/App.tsx b/repl/src/App.tsx index be9f8037..aaae4e75 100644 --- a/repl/src/App.tsx +++ b/repl/src/App.tsx @@ -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 CodeMirror, { markEvent } from './CodeMirror'; +import CodeMirror, { markEvent, markParens } from './CodeMirror'; import cx from './cx'; import { evaluate } from './evaluate'; import logo from './logo.svg'; @@ -156,6 +156,7 @@ function App() { styleSelectedText: true, cursorBlinkRate: 0, }} + onCursor={markParens} onChange={(_: any, __: any, value: any) => setCode(value)} /> diff --git a/repl/src/CodeMirror.tsx b/repl/src/CodeMirror.tsx index 5be1a9d7..9ee18798 100644 --- a/repl/src/CodeMirror.tsx +++ b/repl/src/CodeMirror.tsx @@ -6,7 +6,7 @@ import 'codemirror/mode/pegjs/pegjs.js'; import 'codemirror/lib/codemirror.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 || { mode: 'javascript', theme: 'material', @@ -14,7 +14,15 @@ export default function CodeMirror({ value, onChange, options, editorDidMount }: styleSelectedText: true, cursorBlinkRate: 500, }; - return ; + return ( + onCursor?.(editor, data)} + /> + ); } export const markEvent = (editor) => (time, event) => { @@ -24,23 +32,88 @@ export const markEvent = (editor) => (time, event) => { } // mark active event const marks = locs.map(({ start, end }) => - editor - .getDoc() - .markText( - { line: start.line - 1, ch: start.column }, - { line: end.line - 1, ch: end.column }, - { css: 'background-color: #FFCA28; color: black' } - ) + editor.getDoc().markText( + { line: start.line - 1, ch: start.column }, + { line: end.line - 1, ch: end.column }, + //{ css: 'background-color: #FFCA28; color: black' } // background-color is now used by parent marking + { css: 'outline: 1px solid #FFCA28; box-sizing:border-box' } + ) ); //Tone.Transport.schedule(() => { // problem: this can be cleared by scheduler... setTimeout(() => { marks.forEach((mark) => mark.clear()); // }, '+' + 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 -// the created events could then be used to highlight primitives as long as they are active -// this would create a less flickery output, with no duplications -// it would be seperated completely from the querying that happens to get the sound output -// it would also allow highlighting primitives that don't even end up in the sounding events (just for visual purposes) +let parenMark; +export const markParens = (editor, data) => { + const v = editor.getDoc().getValue(); + const marked = getCurrentParenArea(v, data); + 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)); +}