mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-25 12:38:35 +00:00
wip: adaptive highlighting
This commit is contained in:
parent
f7bd373ce6
commit
63c23736ad
@ -44,12 +44,89 @@ export const flash = (view) => {
|
|||||||
}, 200);
|
}, 200);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setMiniLocations = StateEffect.define();
|
||||||
|
export const showMiniLocations = StateEffect.define();
|
||||||
|
export const updateMiniLocations = (view, locations) => {
|
||||||
|
view.dispatch({ effects: setMiniLocations.of(locations) });
|
||||||
|
};
|
||||||
|
export const highlightMiniLocations = (view, haps) => {
|
||||||
|
view.dispatch({ effects: showMiniLocations.of(haps) });
|
||||||
|
};
|
||||||
|
|
||||||
|
const miniLocations = StateField.define({
|
||||||
|
create() {
|
||||||
|
return Decoration.none;
|
||||||
|
},
|
||||||
|
update(locations, tr) {
|
||||||
|
locations = locations.map(tr.changes);
|
||||||
|
|
||||||
|
for (let e of tr.effects) {
|
||||||
|
if (e.is(setMiniLocations)) {
|
||||||
|
// this is called on eval, with the mini locations obtained from the transpiler
|
||||||
|
// codemirror will automatically remap the marks when the document is edited
|
||||||
|
// create a mark for each mini location, adding the range to the spec to find it later
|
||||||
|
const marks = e.value.map(
|
||||||
|
(range) =>
|
||||||
|
Decoration.mark({
|
||||||
|
range,
|
||||||
|
// this green is only to verify that the decoration moves when the document is edited
|
||||||
|
// it will be removed later, so the mark is not visible by default
|
||||||
|
attributes: { style: `background-color: #00CA2880` },
|
||||||
|
}), // -> Decoration
|
||||||
|
);
|
||||||
|
//
|
||||||
|
const decorations = marks
|
||||||
|
.map((mark) => {
|
||||||
|
let { range } = mark.spec;
|
||||||
|
range = range.map((v) => Math.min(v, tr.newDoc.length));
|
||||||
|
const [from, to] = range;
|
||||||
|
if (from < to) {
|
||||||
|
return mark.range(from, to); // -> Range<Decoration>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
locations = Decoration.set(decorations); // -> DecorationSet === RangeSet<Decoration>
|
||||||
|
}
|
||||||
|
if (e.is(showMiniLocations)) {
|
||||||
|
// this is called every frame to show the locations that are currently active
|
||||||
|
// we can NOT create new marks because the context.locations haven't changed since eval time
|
||||||
|
// this is why we need to find a way to update the existing decorations, showing the ones that have an active range
|
||||||
|
const visible = e.value
|
||||||
|
.map((hap) => hap.context.locations.map(({ start, end }) => `${start.offset}:${end.offset}`))
|
||||||
|
.flat()
|
||||||
|
.filter((v, i, a) => a.indexOf(v) === i);
|
||||||
|
console.log('visible', visible); // e.g. [ "1:3", "8:9", "4:6" ]
|
||||||
|
|
||||||
|
// TODO: iterate over "locations" variable, get access to underlying mark.spec.range
|
||||||
|
// for each mark that is visible, change color (later remove green color...)
|
||||||
|
// How to iterate over DecorationSet ???
|
||||||
|
|
||||||
|
/* console.log('iter', iter.value.spec.range);
|
||||||
|
while (iter.next().value) {
|
||||||
|
console.log('iter', iter.value);
|
||||||
|
} */
|
||||||
|
/* locations = locations.update({
|
||||||
|
filter: (from, to) => {
|
||||||
|
//console.log('filter', from, to);
|
||||||
|
// const id = `${from}:${to}`;
|
||||||
|
//return visible.includes(`${from}:${to}`);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
}); */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return locations;
|
||||||
|
},
|
||||||
|
provide: (f) => EditorView.decorations.from(f),
|
||||||
|
});
|
||||||
|
|
||||||
export const setHighlights = StateEffect.define();
|
export const setHighlights = StateEffect.define();
|
||||||
const highlightField = StateField.define({
|
const highlightField = StateField.define({
|
||||||
create() {
|
create() {
|
||||||
return Decoration.none;
|
return Decoration.none;
|
||||||
},
|
},
|
||||||
update(highlights, tr) {
|
update(highlights, tr) {
|
||||||
|
highlights = highlights.map(tr.changes);
|
||||||
try {
|
try {
|
||||||
for (let e of tr.effects) {
|
for (let e of tr.effects) {
|
||||||
if (e.is(setHighlights)) {
|
if (e.is(setHighlights)) {
|
||||||
@ -88,13 +165,14 @@ const highlightField = StateField.define({
|
|||||||
provide: (f) => EditorView.decorations.from(f),
|
provide: (f) => EditorView.decorations.from(f),
|
||||||
});
|
});
|
||||||
|
|
||||||
const staticExtensions = [javascript(), highlightField, flashField];
|
const staticExtensions = [javascript(), highlightField, flashField, miniLocations];
|
||||||
|
|
||||||
export default function CodeMirror({
|
export default function CodeMirror({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
onViewChanged,
|
onViewChanged,
|
||||||
onSelectionChange,
|
onSelectionChange,
|
||||||
|
onDocChange,
|
||||||
theme,
|
theme,
|
||||||
keybindings,
|
keybindings,
|
||||||
isLineNumbersDisplayed,
|
isLineNumbersDisplayed,
|
||||||
@ -121,6 +199,9 @@ export default function CodeMirror({
|
|||||||
|
|
||||||
const handleOnUpdate = useCallback(
|
const handleOnUpdate = useCallback(
|
||||||
(viewUpdate) => {
|
(viewUpdate) => {
|
||||||
|
if (viewUpdate.docChanged && onDocChange) {
|
||||||
|
onDocChange?.(viewUpdate);
|
||||||
|
}
|
||||||
if (viewUpdate.selectionSet && onSelectionChange) {
|
if (viewUpdate.selectionSet && onSelectionChange) {
|
||||||
onSelectionChange?.(viewUpdate.state.selection);
|
onSelectionChange?.(viewUpdate.state.selection);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import { setHighlights } from '../components/CodeMirror6';
|
import { setHighlights, highlightMiniLocations } from '../components/CodeMirror6';
|
||||||
const round = (x) => Math.round(x * 1000) / 1000;
|
const round = (x) => Math.round(x * 1000) / 1000;
|
||||||
|
|
||||||
function useHighlighting({ view, pattern, active, getTime }) {
|
function useHighlighting({ view, pattern, active, getTime }) {
|
||||||
@ -21,6 +21,7 @@ function useHighlighting({ view, pattern, active, getTime }) {
|
|||||||
const haps = pattern.queryArc(...span).filter((hap) => hap.hasOnset());
|
const haps = pattern.queryArc(...span).filter((hap) => hap.hasOnset());
|
||||||
highlights.current = highlights.current.concat(haps); // add potential new onsets
|
highlights.current = highlights.current.concat(haps); // add potential new onsets
|
||||||
view.dispatch({ effects: setHighlights.of({ haps: highlights.current }) }); // highlight all still active + new active haps
|
view.dispatch({ effects: setHighlights.of({ haps: highlights.current }) }); // highlight all still active + new active haps
|
||||||
|
highlightMiniLocations(view, highlights.current); // <- new method, replaces above line when done
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
view.dispatch({ effects: setHighlights.of({ haps: [] }) });
|
view.dispatch({ effects: setHighlights.of({ haps: [] }) });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// import 'tailwindcss/tailwind.css';
|
// import 'tailwindcss/tailwind.css';
|
||||||
|
|
||||||
export { default as CodeMirror, flash } from './components/CodeMirror6'; // !SSR
|
export { default as CodeMirror, flash, updateMiniLocations, highlightMiniLocations } from './components/CodeMirror6'; // !SSR
|
||||||
export * from './components/MiniRepl'; // !SSR
|
export * from './components/MiniRepl'; // !SSR
|
||||||
export { default as useHighlighting } from './hooks/useHighlighting'; // !SSR
|
export { default as useHighlighting } from './hooks/useHighlighting'; // !SSR
|
||||||
export { default as useStrudel } from './hooks/useStrudel'; // !SSR
|
export { default as useStrudel } from './hooks/useStrudel'; // !SSR
|
||||||
|
|||||||
@ -5,7 +5,15 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { cleanupDraw, cleanupUi, controls, evalScope, getDrawContext, logger } from '@strudel.cycles/core';
|
import { cleanupDraw, cleanupUi, controls, evalScope, getDrawContext, logger } from '@strudel.cycles/core';
|
||||||
import { CodeMirror, cx, flash, useHighlighting, useStrudel, useKeydown } from '@strudel.cycles/react';
|
import {
|
||||||
|
CodeMirror,
|
||||||
|
cx,
|
||||||
|
flash,
|
||||||
|
useHighlighting,
|
||||||
|
useStrudel,
|
||||||
|
useKeydown,
|
||||||
|
updateMiniLocations,
|
||||||
|
} from '@strudel.cycles/react';
|
||||||
import { getAudioContext, initAudioOnFirstClick, resetLoadedSounds, webaudioOutput } from '@strudel.cycles/webaudio';
|
import { getAudioContext, initAudioOnFirstClick, resetLoadedSounds, webaudioOutput } from '@strudel.cycles/webaudio';
|
||||||
import { createClient } from '@supabase/supabase-js';
|
import { createClient } from '@supabase/supabase-js';
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
@ -101,12 +109,13 @@ const { code: randomTune, name } = getRandomTune();
|
|||||||
|
|
||||||
export const ReplContext = createContext(null);
|
export const ReplContext = createContext(null);
|
||||||
|
|
||||||
|
let init = false; // this is bad! only for testing!
|
||||||
|
|
||||||
export function Repl({ embedded = false }) {
|
export function Repl({ embedded = false }) {
|
||||||
const isEmbedded = embedded || window.location !== window.parent.location;
|
const isEmbedded = embedded || window.location !== window.parent.location;
|
||||||
const [view, setView] = useState(); // codemirror view
|
const [view, setView] = useState(); // codemirror view
|
||||||
const [lastShared, setLastShared] = useState();
|
const [lastShared, setLastShared] = useState();
|
||||||
const [pending, setPending] = useState(true);
|
const [pending, setPending] = useState(true);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
theme,
|
theme,
|
||||||
keybindings,
|
keybindings,
|
||||||
@ -129,7 +138,9 @@ export function Repl({ embedded = false }) {
|
|||||||
cleanupDraw();
|
cleanupDraw();
|
||||||
},
|
},
|
||||||
afterEval: ({ code, meta }) => {
|
afterEval: ({ code, meta }) => {
|
||||||
console.log('miniLocations', meta.miniLocations);
|
console.log('miniLocations', meta.miniLocations, view);
|
||||||
|
// TODO: find a way to get hold of the codemirror view
|
||||||
|
// then call updateMiniLocations
|
||||||
setPending(false);
|
setPending(false);
|
||||||
setLatestCode(code);
|
setLatestCode(code);
|
||||||
window.location.hash = '#' + encodeURIComponent(btoa(code));
|
window.location.hash = '#' + encodeURIComponent(btoa(code));
|
||||||
@ -201,6 +212,30 @@ export function Repl({ embedded = false }) {
|
|||||||
// TODO: scroll to selected function in reference
|
// TODO: scroll to selected function in reference
|
||||||
// console.log('selectino change', selection.ranges[0].from);
|
// console.log('selectino change', selection.ranges[0].from);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleDocChanged = useCallback(
|
||||||
|
(v) => {
|
||||||
|
if (!init) {
|
||||||
|
// this is only for testing! try this pattern:
|
||||||
|
/*
|
||||||
|
stack(
|
||||||
|
s("bd"),
|
||||||
|
s("hh oh*<2 3>")
|
||||||
|
)
|
||||||
|
*/
|
||||||
|
updateMiniLocations(view, [
|
||||||
|
[12, 14],
|
||||||
|
[23, 25],
|
||||||
|
[26, 28],
|
||||||
|
[30, 31],
|
||||||
|
[32, 33],
|
||||||
|
]);
|
||||||
|
init = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[view],
|
||||||
|
);
|
||||||
|
|
||||||
const handleTogglePlay = async () => {
|
const handleTogglePlay = async () => {
|
||||||
await getAudioContext().resume(); // fixes no sound in ios webkit
|
await getAudioContext().resume(); // fixes no sound in ios webkit
|
||||||
if (!started) {
|
if (!started) {
|
||||||
@ -295,6 +330,7 @@ export function Repl({ embedded = false }) {
|
|||||||
onChange={handleChangeCode}
|
onChange={handleChangeCode}
|
||||||
onViewChanged={handleViewChanged}
|
onViewChanged={handleViewChanged}
|
||||||
onSelectionChange={handleSelectionChange}
|
onSelectionChange={handleSelectionChange}
|
||||||
|
onDocChange={handleDocChanged}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
{error && (
|
{error && (
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user