mirror of
https://codeberg.org/uzu/strudel
synced 2026-08-02 13:46:06 -04:00
use codemirror 6
This commit is contained in:
+37
-24
@@ -1,5 +1,6 @@
|
||||
import React, { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
||||
import CodeMirror, { markEvent, markParens } from './CodeMirror';
|
||||
import React, { useCallback, useLayoutEffect, useRef, useState, useEffect } from 'react';
|
||||
// import CodeMirror, { markEvent, markParens } from './CodeMirror';
|
||||
import CodeMirror6, { highlightEvent } from './CodeMirror6';
|
||||
import cx from './cx';
|
||||
import logo from './logo.svg';
|
||||
import playStatic from './static.mjs';
|
||||
@@ -70,13 +71,32 @@ const randomTune = getRandomTune();
|
||||
const defaultSynth = getDefaultSynth();
|
||||
|
||||
function App() {
|
||||
const [editor, setEditor] = useState();
|
||||
const { setCode, setPattern, error, code, cycle, dirty, log, togglePlay, activateCode, pattern, pushLog, pending } =
|
||||
useRepl({
|
||||
tune: decoded || randomTune,
|
||||
defaultSynth,
|
||||
onDraw: useCallback((time, event) => markEvent(editor)(time, event), [editor]),
|
||||
});
|
||||
// const [editor, setEditor] = useState();
|
||||
const [view, setView] = useState();
|
||||
const [codeToHighlight, setCodeToHighlight] = useState();
|
||||
const {
|
||||
setCode,
|
||||
setPattern,
|
||||
error,
|
||||
code,
|
||||
cycle,
|
||||
dirty,
|
||||
log,
|
||||
togglePlay,
|
||||
activeCode,
|
||||
activateCode,
|
||||
pattern,
|
||||
pushLog,
|
||||
pending,
|
||||
} = useRepl({
|
||||
tune: decoded || randomTune,
|
||||
defaultSynth,
|
||||
// onDraw: useCallback((time, event) => markEvent(editor)(time, event), [editor]),
|
||||
onDraw: useCallback((_, e) => highlightEvent(e, view, codeToHighlight), [view, codeToHighlight]),
|
||||
});
|
||||
useEffect(() => {
|
||||
setCodeToHighlight(activeCode);
|
||||
}, [activeCode]);
|
||||
const [uiHidden, setUiHidden] = useState(false);
|
||||
const logBox = useRef();
|
||||
// scroll log box to bottom when log changes
|
||||
@@ -179,19 +199,13 @@ function App() {
|
||||
<button>
|
||||
<a href="./tutorial">📚 tutorial</a>
|
||||
</button>
|
||||
<button onClick={() => setUiHidden((c) => !c)}>👀 {uiHidden ? 'show ui' : 'hide ui'}</button>
|
||||
{/* <button onClick={() => setUiHidden((c) => !c)}>👀 {uiHidden ? 'show ui' : 'hide ui'}</button> */}
|
||||
</div>
|
||||
</header>
|
||||
<section className="grow flex flex-col text-gray-100">
|
||||
<div className="grow relative" id="code">
|
||||
<div
|
||||
className={cx(
|
||||
'h-full transition-opacity',
|
||||
error ? 'focus:ring-red-500' : 'focus:ring-slate-800',
|
||||
uiHidden ? 'opacity-0' : 'opacity-100',
|
||||
)}
|
||||
>
|
||||
<CodeMirror
|
||||
<div className="grow relative flex" id="code">
|
||||
<CodeMirror6 value={code} onChange={setCode} onViewChanged={setView} />
|
||||
{/* <CodeMirror
|
||||
value={code}
|
||||
editorDidMount={setEditor}
|
||||
options={{
|
||||
@@ -203,11 +217,10 @@ function App() {
|
||||
}}
|
||||
onCursor={markParens}
|
||||
onChange={(_, __, value) => setCode(value)}
|
||||
/>
|
||||
<span className="p-4 absolute top-0 right-0 text-xs whitespace-pre text-right pointer-events-none">
|
||||
{!cycle.started ? `press ctrl+enter to play\n` : dirty ? `ctrl+enter to update\n` : 'no changes\n'}
|
||||
</span>
|
||||
</div>
|
||||
/> */}
|
||||
<span className="p-4 absolute top-0 right-0 text-xs whitespace-pre text-right pointer-events-none">
|
||||
{!cycle.started ? `press ctrl+enter to play\n` : dirty ? `ctrl+enter to update\n` : 'no changes\n'}
|
||||
</span>
|
||||
{error && (
|
||||
<div className={cx('absolute right-2 bottom-2 px-2', 'text-red-500')}>
|
||||
{error?.message || 'unknown error'}
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
import React from 'react';
|
||||
import { CodeMirror as _CodeMirror } from 'react-codemirror6';
|
||||
import { EditorView, Decoration } from '@codemirror/view';
|
||||
import { StateField, StateEffect } from '@codemirror/state';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { materialPalenight } from 'codemirror6-themes';
|
||||
|
||||
// EditorView.theme(materialPalenight);
|
||||
|
||||
const highlightMark = Decoration.mark({ class: 'cm-highlight' });
|
||||
const addHighlight = StateEffect.define();
|
||||
const removeHighlight = StateEffect.define();
|
||||
const highlightTheme = EditorView.baseTheme({
|
||||
'.cm-highlight': { outline: '1px solid #FFCA28' },
|
||||
// '.cm-highlight': { background: '#FFCA28' },
|
||||
});
|
||||
const highlightField = StateField.define({
|
||||
create() {
|
||||
return Decoration.none;
|
||||
},
|
||||
update(highlights, tr) {
|
||||
highlights = highlights.map(tr.changes);
|
||||
for (let e of tr.effects) {
|
||||
if (e.is(addHighlight)) {
|
||||
highlights = highlights.update({
|
||||
add: [highlightMark.range(e.value.from, e.value.to)],
|
||||
});
|
||||
}
|
||||
if (e.is(removeHighlight)) {
|
||||
highlights = highlights.update({
|
||||
filter: (f, t, value) => {
|
||||
if (f === e.value.from && t === e.value.to) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
// console.log('filter', f,t,value, e.value.from, e.value.to);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return highlights;
|
||||
},
|
||||
provide: (f) => EditorView.decorations.from(f),
|
||||
});
|
||||
|
||||
// let timeouts = [];
|
||||
|
||||
export const highlightEvent = (event, view, code) => {
|
||||
if (!view) {
|
||||
return;
|
||||
}
|
||||
const ranges = event.context?.locations?.map(({ start, end }) => {
|
||||
return [start, end].map(({ line, column }) => positionToOffset({ line: line - 1, ch: column }, code));
|
||||
});
|
||||
const effects = ranges.map(([from, to]) => addHighlight.of({ from, to }));
|
||||
|
||||
if (!effects.length) return false;
|
||||
if (!view.state.field(highlightField, false)) {
|
||||
effects.push(StateEffect.appendConfig.of([highlightField, highlightTheme]));
|
||||
}
|
||||
view.dispatch({ effects });
|
||||
// const index = timeouts.length;
|
||||
// timeouts = timeouts.filter(time)
|
||||
/* const timeout = */ setTimeout(() => {
|
||||
const effects = ranges.map(([from, to]) => removeHighlight.of({ from, to }));
|
||||
view.dispatch({ effects });
|
||||
// timeouts.splice(index, 1);
|
||||
}, event.duration * 1000);
|
||||
// timeouts.pusn({timeout,);
|
||||
};
|
||||
|
||||
export default function CodeMirror({ value, onChange, onViewChanged, onCursor, options, editorDidMount }) {
|
||||
return (
|
||||
<>
|
||||
<_CodeMirror
|
||||
onViewChange={onViewChanged}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: '1 0 auto',
|
||||
}}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
extensions={[
|
||||
javascript(),
|
||||
materialPalenight
|
||||
// theme, language, ...
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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: #00007720' }); //
|
||||
};
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
/*
|
||||
export const markEvent = (editor) => (time, event) => {
|
||||
const locs = event.context.locations;
|
||||
if (!locs || !editor) {
|
||||
return;
|
||||
}
|
||||
const col = event.context?.color || '#FFCA28';
|
||||
// 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' } // background-color is now used by parent marking
|
||||
{ css: 'outline: 1px solid ' + col + '; box-sizing:border-box' },
|
||||
//{ css: `background-color: ${col};border-radius:5px` },
|
||||
),
|
||||
);
|
||||
//Tone.Transport.schedule(() => { // problem: this can be cleared by scheduler...
|
||||
setTimeout(() => {
|
||||
marks.forEach((mark) => mark.clear());
|
||||
// }, '+' + event.duration * 0.5);
|
||||
}, event.duration * 1000);
|
||||
}; */
|
||||
Reference in New Issue
Block a user