diff --git a/packages/codemirror/Autocomplete.jsx b/packages/codemirror/Autocomplete.jsx
new file mode 100644
index 000000000..cbc7b666f
--- /dev/null
+++ b/packages/codemirror/Autocomplete.jsx
@@ -0,0 +1,85 @@
+import { createRoot } from 'react-dom/client';
+import jsdoc from '../../doc.json';
+import { javascriptLanguage } from '@codemirror/lang-javascript';
+import { autocompletion } from '@codemirror/autocomplete';
+
+const getDocLabel = (doc) => doc.name || doc.longname;
+const getInnerText = (html) => {
+ var div = document.createElement('div');
+ div.innerHTML = html;
+ return div.textContent || div.innerText || '';
+};
+
+export function Autocomplete({ doc }) {
+ return (
+
+
{getDocLabel(doc)}
+
+
+ {doc.params?.map(({ name, type, description }, i) => (
+ -
+ {name} : {type.names?.join(' | ')} {description ? <> - {getInnerText(description)}> : ''}
+
+ ))}
+
+
+ {doc.examples?.map((example, i) => (
+
+
{
+ console.log('ola!');
+ navigator.clipboard.writeText(example);
+ e.stopPropagation();
+ }}
+ >
+ {example}
+
+
+ ))}
+
+
+ );
+}
+
+const jsdocCompletions = jsdoc.docs
+ .filter(
+ (doc) =>
+ getDocLabel(doc) &&
+ !getDocLabel(doc).startsWith('_') &&
+ !['package'].includes(doc.kind) &&
+ !['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag)),
+ )
+ // https://codemirror.net/docs/ref/#autocomplete.Completion
+ .map((doc) /*: Completion */ => ({
+ label: getDocLabel(doc),
+ // detail: 'xxx', // An optional short piece of information to show (with a different style) after the label.
+ info: () => {
+ const node = document.createElement('div');
+ // if Autocomplete is non-interactive, it could also be rendered at build time..
+ // .. using renderToStaticMarkup
+ createRoot(node).render();
+ return node;
+ },
+ type: 'function', // https://codemirror.net/docs/ref/#autocomplete.Completion.type
+ }));
+
+export const strudelAutocomplete = (context /* : CompletionContext */) => {
+ let word = context.matchBefore(/\w*/);
+ console.log('word', word);
+ if (word.from == word.to && !context.explicit) return null;
+ return {
+ from: word.from,
+ options: jsdocCompletions,
+ /* options: [
+ { label: 'match', type: 'keyword' },
+ { label: 'hello', type: 'variable', info: '(World)' },
+ { label: 'magic', type: 'text', apply: '⠁⭒*.✩.*⭒⠁', detail: 'macro' },
+ ], */
+ };
+};
+
+export function isAutoCompletionEnabled(on) {
+ console.log('autocomplete on',on);
+ return on ? javascriptLanguage.data.of({ autocomplete: strudelAutocomplete }) : autocompletion({ override: [] });
+}
diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs
index aa90d7768..22b91f92f 100644
--- a/packages/codemirror/codemirror.mjs
+++ b/packages/codemirror/codemirror.mjs
@@ -4,25 +4,37 @@ import { defaultHighlightStyle, syntaxHighlighting } from '@codemirror/language'
import { EditorState, Compartment } from '@codemirror/state';
import { EditorView, highlightActiveLineGutter, keymap, lineNumbers } from '@codemirror/view';
import { Drawer, repl } from '@strudel.cycles/core';
-import { flashField, flash } from './flash.mjs';
-import { highlightExtension, highlightMiniLocations, updateMiniLocations } from './highlight.mjs';
-import { oneDark } from './themes/one-dark';
+import { isFlashEnabled, flash } from './flash.mjs';
+import { highlightMiniLocations, updateMiniLocations, isPatternHighlightingEnabled } from './highlight.mjs';
+import { theme } from './themes.mjs';
+import { isAutoCompletionEnabled } from './Autocomplete';
+import { keybindings } from './keybindings.mjs';
-const themeComparment = new Compartment();
+const extensions = {
+ isLineWrappingEnabled: (on) => (on ? EditorView.lineWrapping : []),
+ isLineNumbersDisplayed: (on) => (on ? lineNumbers() : lineNumbers({ formatNumber: () => '' })),
+ theme,
+ isAutoCompletionEnabled,
+ isPatternHighlightingEnabled,
+ isFlashEnabled,
+ keybindings,
+};
+const compartments = Object.fromEntries(Object.keys(extensions).map((key) => [key, new Compartment()]));
// https://codemirror.net/docs/guide/
-export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, theme = oneDark, root }) {
+export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, settings, root }) {
+ const initialSettings = Object.keys(compartments).map((key) =>
+ compartments[key].of(extensions[key](parseBooleans(settings[key]))),
+ );
+ console.log('settings', settings);
let state = EditorState.create({
doc: initialCode,
extensions: [
- themeComparment.of(theme),
+ ...initialSettings,
javascript(),
- lineNumbers(),
- highlightExtension,
highlightActiveLineGutter(),
syntaxHighlighting(defaultHighlightStyle),
keymap.of(defaultKeymap),
- flashField,
EditorView.updateListener.of((v) => onChange(v)),
keymap.of([
{
@@ -64,8 +76,10 @@ export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, the
export class StrudelMirror {
constructor(options) {
- const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, theme, ...replOptions } = options;
+ const { root, initialCode = '', onDraw, drawTime = [-2, 2], prebake, settings, ...replOptions } = options;
this.code = initialCode;
+ this.root = root;
+ this.miniLocations = [];
this.drawer = new Drawer((haps, time) => {
const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.endClipped);
@@ -100,14 +114,16 @@ export class StrudelMirror {
await prebaked;
},
afterEval: (options) => {
- updateMiniLocations(this.editor, options.meta?.miniLocations);
+ // remember for when highlighting is toggled on
+ this.miniLocations = options.meta?.miniLocations;
+ updateMiniLocations(this.editor, this.miniLocations);
replOptions?.afterEval?.(options);
this.drawer.invalidate();
},
});
this.editor = initEditor({
root,
- theme,
+ settings,
initialCode,
onChange: (v) => {
if (v.docChanged) {
@@ -139,13 +155,58 @@ export class StrudelMirror {
highlight(haps, time) {
highlightMiniLocations(this.editor, time, haps);
}
- setTheme(theme) {
+ setFontSize(size) {
+ this.root.style.fontSize = size + 'px';
+ }
+ setFontFamily(family) {
+ this.root.style.fontFamily = family;
+ }
+ reconfigureExtension(key, value) {
+ if (!extensions[key]) {
+ console.warn(`extension ${key} is not known`);
+ return;
+ }
+ value = parseBooleans(value);
+ const newValue = extensions[key](value, this);
this.editor.dispatch({
- effects: themeComparment.reconfigure(theme),
+ effects: compartments[key].reconfigure(newValue),
});
}
+ setLineWrappingEnabled(enabled) {
+ this.reconfigureExtension('isLineWrappingEnabled', enabled);
+ }
+ setLineNumbersDisplayed(enabled) {
+ this.reconfigureExtension('isLineNumbersDisplayed', enabled);
+ }
+ setTheme(theme) {
+ this.reconfigureExtension('theme', theme);
+ }
+ setAutocompletionEnabled(enabled) {
+ this.reconfigureExtension('isAutoCompletionEnabled', enabled);
+ }
+ updateSettings(settings) {
+ this.setFontSize(settings.fontSize);
+ this.setFontFamily(settings.fontFamily);
+ for (let key in extensions) {
+ this.reconfigureExtension(key, settings[key]);
+ }
+ }
+ changeSetting(key, value) {
+ if (extensions[key]) {
+ this.reconfigureExtension(key, value);
+ return;
+ } else if (key === 'fontFamily') {
+ this.setFontFamily(value);
+ } else if (key === 'fontSize') {
+ this.setFontSize(value);
+ }
+ }
setCode(code) {
const changes = { from: 0, to: this.editor.state.doc.length, insert: code };
this.editor.dispatch({ changes });
}
}
+
+function parseBooleans(value) {
+ return { true: true, false: false }[value] ?? value;
+}
diff --git a/packages/codemirror/flash.mjs b/packages/codemirror/flash.mjs
index 9bc5c5936..6b37038ff 100644
--- a/packages/codemirror/flash.mjs
+++ b/packages/codemirror/flash.mjs
@@ -33,3 +33,5 @@ export const flash = (view, ms = 200) => {
view.dispatch({ effects: setFlash.of(false) });
}, ms);
};
+
+export const isFlashEnabled = (on) => (on ? flashField : []);
diff --git a/packages/codemirror/highlight.mjs b/packages/codemirror/highlight.mjs
index 317c5fdf1..79724f8f4 100644
--- a/packages/codemirror/highlight.mjs
+++ b/packages/codemirror/highlight.mjs
@@ -124,3 +124,12 @@ const miniLocationHighlights = EditorView.decorations.compute([miniLocations, vi
});
export const highlightExtension = [miniLocations, visibleMiniLocations, miniLocationHighlights];
+
+export const isPatternHighlightingEnabled = (on, config) => {
+ on &&
+ config &&
+ setTimeout(() => {
+ updateMiniLocations(config.editor, config.miniLocations);
+ }, 100);
+ return on ? highlightExtension : [];
+};
diff --git a/packages/codemirror/keybindings.mjs b/packages/codemirror/keybindings.mjs
new file mode 100644
index 000000000..636350e95
--- /dev/null
+++ b/packages/codemirror/keybindings.mjs
@@ -0,0 +1,29 @@
+import { Prec } from '@codemirror/state';
+import { keymap, ViewPlugin } from '@codemirror/view';
+import { emacs } from '@replit/codemirror-emacs';
+import { vim } from '@replit/codemirror-vim';
+import { vscodeKeymap } from '@replit/codemirror-vscode-keymap';
+
+const vscodePlugin = ViewPlugin.fromClass(
+ class {
+ constructor() {}
+ },
+ {
+ provide: () => {
+ return Prec.highest(keymap.of([...vscodeKeymap]));
+ },
+ },
+);
+
+const vscodeExtension = (options) => [vscodePlugin].concat(options ?? []);
+
+const keymaps = {
+ vim,
+ emacs,
+ vscode: vscodeExtension,
+};
+
+export function keybindings(name) {
+ const keymap = keymaps[name];
+ return keymap ? keymap() : [];
+}
diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json
index 4e443648e..8a9fbc67d 100644
--- a/packages/codemirror/package.json
+++ b/packages/codemirror/package.json
@@ -33,13 +33,20 @@
},
"homepage": "https://github.com/tidalcycles/strudel#readme",
"dependencies": {
+ "@codemirror/autocomplete": "^6.6.0",
"@codemirror/commands": "^6.2.4",
"@codemirror/lang-javascript": "^6.1.7",
"@codemirror/language": "^6.6.0",
"@codemirror/state": "^6.2.0",
"@codemirror/view": "^6.10.0",
"@lezer/highlight": "^1.1.4",
- "@strudel.cycles/core": "workspace:*"
+ "@replit/codemirror-emacs": "^6.0.1",
+ "@replit/codemirror-vim": "^6.0.14",
+ "@replit/codemirror-vscode-keymap": "^6.0.2",
+ "@strudel.cycles/core": "workspace:*",
+ "@uiw/codemirror-themes": "^4.19.16",
+ "@uiw/codemirror-themes-all": "^4.19.16",
+ "react-dom": "^18.2.0"
},
"devDependencies": {
"vite": "^4.3.3"
diff --git a/packages/codemirror/themes.mjs b/packages/codemirror/themes.mjs
new file mode 100644
index 000000000..1f520623b
--- /dev/null
+++ b/packages/codemirror/themes.mjs
@@ -0,0 +1,484 @@
+import {
+ abcdef,
+ androidstudio,
+ atomone,
+ aura,
+ bespin,
+ darcula,
+ dracula,
+ duotoneDark,
+ eclipse,
+ githubDark,
+ gruvboxDark,
+ materialDark,
+ nord,
+ okaidia,
+ solarizedDark,
+ sublime,
+ tokyoNight,
+ tokyoNightStorm,
+ vscodeDark,
+ xcodeDark,
+ bbedit,
+ duotoneLight,
+ githubLight,
+ gruvboxLight,
+ materialLight,
+ noctisLilac,
+ solarizedLight,
+ tokyoNightDay,
+ xcodeLight,
+} from '@uiw/codemirror-themes-all';
+
+import strudelTheme from './themes/strudel-theme';
+import bluescreen, { settings as bluescreenSettings } from './themes/bluescreen';
+import blackscreen, { settings as blackscreenSettings } from './themes/blackscreen';
+import whitescreen, { settings as whitescreenSettings } from './themes/whitescreen';
+import teletext, { settings as teletextSettings } from './themes/teletext';
+import algoboy, { settings as algoboySettings } from './themes/algoboy';
+import terminal, { settings as terminalSettings } from './themes/terminal';
+
+export const themes = {
+ strudelTheme,
+ bluescreen,
+ blackscreen,
+ whitescreen,
+ teletext,
+ algoboy,
+ terminal,
+ abcdef,
+ androidstudio,
+ atomone,
+ aura,
+ bespin,
+ darcula,
+ dracula,
+ duotoneDark,
+ eclipse,
+ githubDark,
+ gruvboxDark,
+ materialDark,
+ nord,
+ okaidia,
+ solarizedDark,
+ sublime,
+ tokyoNight,
+ tokyoNightStorm,
+ vscodeDark,
+ xcodeDark,
+ bbedit,
+ duotoneLight,
+ githubLight,
+ gruvboxLight,
+ materialLight,
+ noctisLilac,
+ solarizedLight,
+ tokyoNightDay,
+ xcodeLight,
+};
+
+// lineBackground is background with 50% opacity, to make sure the selection below is visible
+
+export const settings = {
+ strudelTheme: {
+ background: '#222',
+ lineBackground: '#22222299',
+ foreground: '#fff',
+ // foreground: '#75baff',
+ caret: '#ffcc00',
+ selection: 'rgba(128, 203, 196, 0.5)',
+ selectionMatch: '#036dd626',
+ // lineHighlight: '#8a91991a', // original
+ lineHighlight: '#00000050',
+ gutterBackground: 'transparent',
+ // gutterForeground: '#8a919966',
+ gutterForeground: '#8a919966',
+ },
+ bluescreen: bluescreenSettings,
+ blackscreen: blackscreenSettings,
+ whitescreen: whitescreenSettings,
+ teletext: teletextSettings,
+ algoboy: algoboySettings,
+ terminal: terminalSettings,
+ abcdef: {
+ background: '#0f0f0f',
+ lineBackground: '#0f0f0f99',
+ foreground: '#defdef',
+ caret: '#00FF00',
+ selection: '#515151',
+ selectionMatch: '#515151',
+ gutterBackground: '#555',
+ gutterForeground: '#FFFFFF',
+ lineHighlight: '#314151',
+ },
+ androidstudio: {
+ background: '#282b2e',
+ lineBackground: '#282b2e99',
+ foreground: '#a9b7c6',
+ caret: '#00FF00',
+ selection: '#343739',
+ selectionMatch: '#343739',
+ lineHighlight: '#343739',
+ },
+ atomone: {
+ background: '#272C35',
+ lineBackground: '#272C3599',
+ foreground: '#9d9b97',
+ caret: '#797977',
+ selection: '#ffffff30',
+ selectionMatch: '#2B323D',
+ gutterBackground: '#272C35',
+ gutterForeground: '#465063',
+ gutterBorder: 'transparent',
+ lineHighlight: '#2B323D',
+ },
+ aura: {
+ background: '#21202e',
+ lineBackground: '#21202e99',
+ foreground: '#edecee',
+ caret: '#a277ff',
+ selection: '#3d375e7f',
+ selectionMatch: '#3d375e7f',
+ gutterBackground: '#21202e',
+ gutterForeground: '#edecee',
+ gutterBorder: 'transparent',
+ lineHighlight: '#a394f033',
+ },
+ bbedit: {
+ light: true,
+ background: '#FFFFFF',
+ lineBackground: '#FFFFFF99',
+ foreground: '#000000',
+ caret: '#FBAC52',
+ selection: '#FFD420',
+ selectionMatch: '#FFD420',
+ gutterBackground: '#f5f5f5',
+ gutterForeground: '#4D4D4C',
+ gutterBorder: 'transparent',
+ lineHighlight: '#00000012',
+ },
+ bespin: {
+ background: '#28211c',
+ lineBackground: '#28211c99',
+ foreground: '#9d9b97',
+ caret: '#797977',
+ selection: '#36312e',
+ selectionMatch: '#4f382b',
+ gutterBackground: '#28211c',
+ gutterForeground: '#666666',
+ lineHighlight: 'rgba(255, 255, 255, 0.1)',
+ },
+ darcula: {
+ background: '#2B2B2B',
+ lineBackground: '#2B2B2B99',
+ foreground: '#f8f8f2',
+ caret: '#FFFFFF',
+ selection: 'rgba(255, 255, 255, 0.1)',
+ selectionMatch: 'rgba(255, 255, 255, 0.2)',
+ gutterBackground: 'rgba(255, 255, 255, 0.1)',
+ gutterForeground: '#999',
+ gutterBorder: 'transparent',
+ lineHighlight: 'rgba(255, 255, 255, 0.1)',
+ },
+ dracula: {
+ background: '#282a36',
+ lineBackground: '#282a3699',
+ foreground: '#f8f8f2',
+ caret: '#f8f8f0',
+ selection: 'rgba(255, 255, 255, 0.1)',
+ selectionMatch: 'rgba(255, 255, 255, 0.2)',
+ gutterBackground: '#282a36',
+ gutterForeground: '#6D8A88',
+ gutterBorder: 'transparent',
+ lineHighlight: 'rgba(255, 255, 255, 0.1)',
+ },
+ duotoneLight: {
+ light: true,
+ background: '#faf8f5',
+ lineBackground: '#faf8f599',
+ foreground: '#b29762',
+ caret: '#93abdc',
+ selection: '#e3dcce',
+ selectionMatch: '#e3dcce',
+ gutterBackground: '#faf8f5',
+ gutterForeground: '#cdc4b1',
+ gutterBorder: 'transparent',
+ lineHighlight: '#EFEFEF',
+ },
+ duotoneDark: {
+ background: '#2a2734',
+ lineBackground: '#2a273499',
+ foreground: '#6c6783',
+ caret: '#ffad5c',
+ selection: 'rgba(255, 255, 255, 0.1)',
+ gutterBackground: '#2a2734',
+ gutterForeground: '#545167',
+ lineHighlight: '#36334280',
+ },
+ eclipse: {
+ light: true,
+ background: '#fff',
+ lineBackground: '#ffffff99',
+ foreground: '#000',
+ caret: '#FFFFFF',
+ selection: '#d7d4f0',
+ selectionMatch: '#d7d4f0',
+ gutterBackground: '#f7f7f7',
+ gutterForeground: '#999',
+ lineHighlight: '#e8f2ff',
+ gutterBorder: 'transparent',
+ },
+ githubLight: {
+ light: true,
+ background: '#fff',
+ lineBackground: '#ffffff99',
+ foreground: '#24292e',
+ selection: '#BBDFFF',
+ selectionMatch: '#BBDFFF',
+ gutterBackground: '#fff',
+ gutterForeground: '#6e7781',
+ },
+ githubDark: {
+ background: '#0d1117',
+ lineBackground: '#0d111799',
+ foreground: '#c9d1d9',
+ caret: '#c9d1d9',
+ selection: '#003d73',
+ selectionMatch: '#003d73',
+ lineHighlight: '#36334280',
+ },
+ gruvboxDark: {
+ background: '#282828',
+ lineBackground: '#28282899',
+ foreground: '#ebdbb2',
+ caret: '#ebdbb2',
+ selection: '#bdae93',
+ selectionMatch: '#bdae93',
+ lineHighlight: '#3c3836',
+ gutterBackground: '#282828',
+ gutterForeground: '#7c6f64',
+ },
+ gruvboxLight: {
+ light: true,
+ background: '#fbf1c7',
+ lineBackground: '#fbf1c799',
+ foreground: '#3c3836',
+ caret: '#af3a03',
+ selection: '#ebdbb2',
+ selectionMatch: '#bdae93',
+ lineHighlight: '#ebdbb2',
+ gutterBackground: '#ebdbb2',
+ gutterForeground: '#665c54',
+ gutterBorder: 'transparent',
+ },
+ materialDark: {
+ background: '#2e3235',
+ lineBackground: '#2e323599',
+ foreground: '#bdbdbd',
+ caret: '#a0a4ae',
+ selection: '#d7d4f0',
+ selectionMatch: '#d7d4f0',
+ gutterBackground: '#2e3235',
+ gutterForeground: '#999',
+ gutterActiveForeground: '#4f5b66',
+ lineHighlight: '#545b61',
+ },
+ materialLight: {
+ light: true,
+ background: '#FAFAFA',
+ lineBackground: '#FAFAFA99',
+ foreground: '#90A4AE',
+ caret: '#272727',
+ selection: '#80CBC440',
+ selectionMatch: '#FAFAFA',
+ gutterBackground: '#FAFAFA',
+ gutterForeground: '#90A4AE',
+ gutterBorder: 'transparent',
+ lineHighlight: '#CCD7DA50',
+ },
+ noctisLilac: {
+ light: true,
+ background: '#f2f1f8',
+ lineBackground: '#f2f1f899',
+ foreground: '#0c006b',
+ caret: '#5c49e9',
+ selection: '#d5d1f2',
+ selectionMatch: '#d5d1f2',
+ gutterBackground: '#f2f1f8',
+ gutterForeground: '#0c006b70',
+ lineHighlight: '#e1def3',
+ },
+ nord: {
+ background: '#2e3440',
+ lineBackground: '#2e344099',
+ foreground: '#FFFFFF',
+ caret: '#FFFFFF',
+ selection: '#3b4252',
+ selectionMatch: '#e5e9f0',
+ gutterBackground: '#2e3440',
+ gutterForeground: '#4c566a',
+ gutterActiveForeground: '#d8dee9',
+ lineHighlight: '#4c566a',
+ },
+ okaidia: {
+ background: '#272822',
+ lineBackground: '#27282299',
+ foreground: '#FFFFFF',
+ caret: '#FFFFFF',
+ selection: '#49483E',
+ selectionMatch: '#49483E',
+ gutterBackground: '#272822',
+ gutterForeground: '#FFFFFF70',
+ lineHighlight: '#00000059',
+ },
+ solarizedLight: {
+ light: true,
+ background: '#fdf6e3',
+ lineBackground: '#fdf6e399',
+ foreground: '#657b83',
+ caret: '#586e75',
+ selection: '#dfd9c8',
+ selectionMatch: '#dfd9c8',
+ gutterBackground: '#00000010',
+ gutterForeground: '#657b83',
+ lineHighlight: '#dfd9c8',
+ },
+ solarizedDark: {
+ background: '#002b36',
+ lineBackground: '#002b3699',
+ foreground: '#93a1a1',
+ caret: '#839496',
+ selection: '#173541',
+ selectionMatch: '#aafe661a',
+ gutterBackground: '#00252f',
+ gutterForeground: '#839496',
+ lineHighlight: '#173541',
+ },
+ sublime: {
+ background: '#303841',
+ lineBackground: '#30384199',
+ foreground: '#FFFFFF',
+ caret: '#FBAC52',
+ selection: '#4C5964',
+ selectionMatch: '#3A546E',
+ gutterBackground: '#303841',
+ gutterForeground: '#FFFFFF70',
+ lineHighlight: '#00000059',
+ },
+ tokyoNightDay: {
+ light: true,
+ background: '#e1e2e7',
+ lineBackground: '#e1e2e799',
+ foreground: '#3760bf',
+ caret: '#3760bf',
+ selection: '#99a7df',
+ selectionMatch: '#99a7df',
+ gutterBackground: '#e1e2e7',
+ gutterForeground: '#3760bf',
+ gutterBorder: 'transparent',
+ lineHighlight: '#5f5faf11',
+ },
+ tokyoNightStorm: {
+ background: '#24283b',
+ lineBackground: '#24283b99',
+ foreground: '#7982a9',
+ caret: '#c0caf5',
+ selection: '#6f7bb630',
+ selectionMatch: '#1f2335',
+ gutterBackground: '#24283b',
+ gutterForeground: '#7982a9',
+ gutterBorder: 'transparent',
+ lineHighlight: '#292e42',
+ },
+ tokyoNight: {
+ background: '#1a1b26',
+ lineBackground: '#1a1b2699',
+ foreground: '#787c99',
+ caret: '#c0caf5',
+ selection: '#515c7e40',
+ selectionMatch: '#16161e',
+ gutterBackground: '#1a1b26',
+ gutterForeground: '#787c99',
+ gutterBorder: 'transparent',
+ lineHighlight: '#1e202e',
+ },
+ vscodeDark: {
+ background: '#1e1e1e',
+ lineBackground: '#1e1e1e99',
+ foreground: '#9cdcfe',
+ caret: '#c6c6c6',
+ selection: '#6199ff2f',
+ selectionMatch: '#72a1ff59',
+ lineHighlight: '#ffffff0f',
+ gutterBackground: '#1e1e1e',
+ gutterForeground: '#838383',
+ gutterActiveForeground: '#fff',
+ },
+ xcodeLight: {
+ light: true,
+ background: '#fff',
+ lineBackground: '#ffffff99',
+ foreground: '#3D3D3D',
+ selection: '#BBDFFF',
+ selectionMatch: '#BBDFFF',
+ gutterBackground: '#fff',
+ gutterForeground: '#AFAFAF',
+ lineHighlight: '#EDF4FF',
+ },
+ xcodeDark: {
+ background: '#292A30',
+ lineBackground: '#292A3099',
+ foreground: '#CECFD0',
+ caret: '#fff',
+ selection: '#727377',
+ selectionMatch: '#727377',
+ lineHighlight: '#2F3239',
+ },
+};
+
+function getColors(str) {
+ const colorRegex = /#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})/g;
+ const colors = [];
+
+ let match;
+ while ((match = colorRegex.exec(str)) !== null) {
+ const color = match[0];
+ if (!colors.includes(color)) {
+ colors.push(color);
+ }
+ }
+
+ return colors;
+}
+
+// TODO: remove
+export function themeColors(theme) {
+ return getColors(stringifySafe(theme));
+}
+
+function getCircularReplacer() {
+ const seen = new WeakSet();
+ return (key, value) => {
+ if (typeof value === 'object' && value !== null) {
+ if (seen.has(value)) {
+ return;
+ }
+ seen.add(value);
+ }
+ return value;
+ };
+}
+
+function stringifySafe(json) {
+ return JSON.stringify(json, getCircularReplacer());
+}
+
+export function injectStyle(rule) {
+ const newStyle = document.createElement('style');
+ document.head.appendChild(newStyle);
+ const styleSheet = newStyle.sheet;
+ const ruleIndex = styleSheet.insertRule(rule, 0);
+ return () => styleSheet.deleteRule(ruleIndex);
+}
+
+export const theme = (theme) => themes[theme] || themes.strudelTheme;
diff --git a/packages/codemirror/themes/algoboy.js b/packages/codemirror/themes/algoboy.js
new file mode 100644
index 000000000..399370e19
--- /dev/null
+++ b/packages/codemirror/themes/algoboy.js
@@ -0,0 +1,41 @@
+import { tags as t } from '@lezer/highlight';
+import { createTheme } from '@uiw/codemirror-themes';
+export const settings = {
+ background: '#9bbc0f',
+ foreground: '#0f380f', // whats that?
+ caret: '#0f380f',
+ selection: '#306230',
+ selectionMatch: '#ffffff26',
+ lineHighlight: '#8bac0f',
+ lineBackground: '#9bbc0f50',
+ //lineBackground: 'transparent',
+ gutterBackground: 'transparent',
+ gutterForeground: '#0f380f',
+ light: true,
+ customStyle: '.cm-line { line-height: 1 }',
+};
+export default createTheme({
+ theme: 'light',
+ settings,
+ styles: [
+ { tag: t.keyword, color: '#0f380f' },
+ { tag: t.operator, color: '#0f380f' },
+ { tag: t.special(t.variableName), color: '#0f380f' },
+ { tag: t.typeName, color: '#0f380f' },
+ { tag: t.atom, color: '#0f380f' },
+ { tag: t.number, color: '#0f380f' },
+ { tag: t.definition(t.variableName), color: '#0f380f' },
+ { tag: t.string, color: '#0f380f' },
+ { tag: t.special(t.string), color: '#0f380f' },
+ { tag: t.comment, color: '#0f380f' },
+ { tag: t.variableName, color: '#0f380f' },
+ { tag: t.tagName, color: '#0f380f' },
+ { tag: t.bracket, color: '#0f380f' },
+ { tag: t.meta, color: '#0f380f' },
+ { tag: t.attributeName, color: '#0f380f' },
+ { tag: t.propertyName, color: '#0f380f' },
+ { tag: t.className, color: '#0f380f' },
+ { tag: t.invalid, color: '#0f380f' },
+ { tag: [t.unit, t.punctuation], color: '#0f380f' },
+ ],
+});
diff --git a/packages/codemirror/themes/blackscreen.js b/packages/codemirror/themes/blackscreen.js
new file mode 100644
index 000000000..135285a37
--- /dev/null
+++ b/packages/codemirror/themes/blackscreen.js
@@ -0,0 +1,38 @@
+import { tags as t } from '@lezer/highlight';
+import { createTheme } from '@uiw/codemirror-themes';
+export const settings = {
+ background: 'black',
+ foreground: 'white', // whats that?
+ caret: 'white',
+ selection: '#ffffff20',
+ selectionMatch: '#036dd626',
+ lineHighlight: '#ffffff10',
+ lineBackground: '#00000050',
+ gutterBackground: 'transparent',
+ gutterForeground: '#8a919966',
+};
+export default createTheme({
+ theme: 'dark',
+ settings,
+ styles: [
+ { tag: t.keyword, color: 'white' },
+ { tag: t.operator, color: 'white' },
+ { tag: t.special(t.variableName), color: 'white' },
+ { tag: t.typeName, color: 'white' },
+ { tag: t.atom, color: 'white' },
+ { tag: t.number, color: 'white' },
+ { tag: t.definition(t.variableName), color: 'white' },
+ { tag: t.string, color: 'white' },
+ { tag: t.special(t.string), color: 'white' },
+ { tag: t.comment, color: 'white' },
+ { tag: t.variableName, color: 'white' },
+ { tag: t.tagName, color: 'white' },
+ { tag: t.bracket, color: 'white' },
+ { tag: t.meta, color: 'white' },
+ { tag: t.attributeName, color: 'white' },
+ { tag: t.propertyName, color: 'white' },
+ { tag: t.className, color: 'white' },
+ { tag: t.invalid, color: 'white' },
+ { tag: [t.unit, t.punctuation], color: 'white' },
+ ],
+});
diff --git a/packages/codemirror/themes/bluescreen.js b/packages/codemirror/themes/bluescreen.js
new file mode 100644
index 000000000..aa6489d6f
--- /dev/null
+++ b/packages/codemirror/themes/bluescreen.js
@@ -0,0 +1,41 @@
+import { tags as t } from '@lezer/highlight';
+import { createTheme } from '@uiw/codemirror-themes';
+export const settings = {
+ background: '#051DB5',
+ lineBackground: '#051DB550',
+ foreground: 'white', // whats that?
+ caret: 'white',
+ selection: 'rgba(128, 203, 196, 0.5)',
+ selectionMatch: '#036dd626',
+ // lineHighlight: '#8a91991a', // original
+ lineHighlight: '#00000050',
+ gutterBackground: 'transparent',
+ // gutterForeground: '#8a919966',
+ gutterForeground: '#8a919966',
+};
+
+export default createTheme({
+ theme: 'dark',
+ settings,
+ styles: [
+ { tag: t.keyword, color: 'white' },
+ { tag: t.operator, color: 'white' },
+ { tag: t.special(t.variableName), color: 'white' },
+ { tag: t.typeName, color: 'white' },
+ { tag: t.atom, color: 'white' },
+ { tag: t.number, color: 'white' },
+ { tag: t.definition(t.variableName), color: 'white' },
+ { tag: t.string, color: 'white' },
+ { tag: t.special(t.string), color: 'white' },
+ { tag: t.comment, color: 'white' },
+ { tag: t.variableName, color: 'white' },
+ { tag: t.tagName, color: 'white' },
+ { tag: t.bracket, color: 'white' },
+ { tag: t.meta, color: 'white' },
+ { tag: t.attributeName, color: 'white' },
+ { tag: t.propertyName, color: 'white' },
+ { tag: t.className, color: 'white' },
+ { tag: t.invalid, color: 'white' },
+ { tag: [t.unit, t.punctuation], color: 'white' },
+ ],
+});
diff --git a/packages/codemirror/themes/one-dark.mjs b/packages/codemirror/themes/one-dark.mjs
deleted file mode 100644
index cce836994..000000000
--- a/packages/codemirror/themes/one-dark.mjs
+++ /dev/null
@@ -1,139 +0,0 @@
-import { EditorView } from '@codemirror/view';
-import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
-import { tags as t } from '@lezer/highlight';
-
-// Using https://github.com/one-dark/vscode-one-dark-theme/ as reference for the colors
-
-const chalky = '#e5c07b',
- coral = '#e06c75',
- cyan = '#56b6c2',
- invalid = '#ffffff',
- ivory = '#abb2bf',
- stone = '#7d8799', // Brightened compared to original to increase contrast
- malibu = '#61afef',
- sage = '#98c379',
- whiskey = '#d19a66',
- violet = '#c678dd',
- darkBackground = '#21252b',
- highlightBackground = '#2c313a',
- background = '#282c34',
- tooltipBackground = '#353a42',
- selection = '#3E4451',
- cursor = '#528bff';
-
-/// The colors used in the theme, as CSS color strings.
-export const color = {
- chalky,
- coral,
- cyan,
- invalid,
- ivory,
- stone,
- malibu,
- sage,
- whiskey,
- violet,
- darkBackground,
- highlightBackground,
- background,
- tooltipBackground,
- selection,
- cursor,
-};
-
-/// The editor theme styles for One Dark.
-export const oneDarkTheme = EditorView.theme(
- {
- '&': {
- color: ivory,
- backgroundColor: background,
- },
-
- '.cm-content': {
- caretColor: cursor,
- },
-
- '.cm-cursor, .cm-dropCursor': { borderLeftColor: cursor },
- '&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':
- { backgroundColor: selection },
-
- '.cm-panels': { backgroundColor: darkBackground, color: ivory },
- '.cm-panels.cm-panels-top': { borderBottom: '2px solid black' },
- '.cm-panels.cm-panels-bottom': { borderTop: '2px solid black' },
-
- '.cm-searchMatch': {
- backgroundColor: '#72a1ff59',
- outline: '1px solid #457dff',
- },
- '.cm-searchMatch.cm-searchMatch-selected': {
- backgroundColor: '#6199ff2f',
- },
-
- '.cm-activeLine': { backgroundColor: '#6699ff0b' },
- '.cm-selectionMatch': { backgroundColor: '#aafe661a' },
-
- '&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket': {
- backgroundColor: '#bad0f847',
- },
-
- '.cm-gutters': {
- backgroundColor: background,
- color: stone,
- border: 'none',
- },
-
- '.cm-activeLineGutter': {
- backgroundColor: highlightBackground,
- },
-
- '.cm-foldPlaceholder': {
- backgroundColor: 'transparent',
- border: 'none',
- color: '#ddd',
- },
-
- '.cm-tooltip': {
- border: 'none',
- backgroundColor: tooltipBackground,
- },
- '.cm-tooltip .cm-tooltip-arrow:before': {
- borderTopColor: 'transparent',
- borderBottomColor: 'transparent',
- },
- '.cm-tooltip .cm-tooltip-arrow:after': {
- borderTopColor: tooltipBackground,
- borderBottomColor: tooltipBackground,
- },
- '.cm-tooltip-autocomplete': {
- '& > ul > li[aria-selected]': {
- backgroundColor: highlightBackground,
- color: ivory,
- },
- },
- },
- { dark: true },
-);
-
-/// The highlighting style for code in the One Dark theme.
-export const oneDarkHighlightStyle = HighlightStyle.define([
- { tag: t.keyword, color: violet },
- { tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName], color: coral },
- { tag: [t.function(t.variableName), t.labelName], color: malibu },
- { tag: [t.color, t.constant(t.name), t.standard(t.name)], color: whiskey },
- { tag: [t.definition(t.name), t.separator], color: ivory },
- { tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace], color: chalky },
- { tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)], color: cyan },
- { tag: [t.meta, t.comment], color: stone },
- { tag: t.strong, fontWeight: 'bold' },
- { tag: t.emphasis, fontStyle: 'italic' },
- { tag: t.strikethrough, textDecoration: 'line-through' },
- { tag: t.link, color: stone, textDecoration: 'underline' },
- { tag: t.heading, fontWeight: 'bold', color: coral },
- { tag: [t.atom, t.bool, t.special(t.variableName)], color: whiskey },
- { tag: [t.processingInstruction, t.string, t.inserted], color: sage },
- { tag: t.invalid, color: invalid },
-]);
-
-/// Extension to enable the One Dark theme (both the editor theme and
-/// the highlight style).
-export const oneDark = [oneDarkTheme, syntaxHighlighting(oneDarkHighlightStyle)];
diff --git a/packages/codemirror/themes/strudel-theme.js b/packages/codemirror/themes/strudel-theme.js
new file mode 100644
index 000000000..4ae310609
--- /dev/null
+++ b/packages/codemirror/themes/strudel-theme.js
@@ -0,0 +1,45 @@
+import { tags as t } from '@lezer/highlight';
+import { createTheme } from '@uiw/codemirror-themes';
+export default createTheme({
+ theme: 'dark',
+ settings: {
+ background: '#222',
+ foreground: '#75baff', // whats that?
+ caret: '#ffcc00',
+ selection: 'rgba(128, 203, 196, 0.5)',
+ selectionMatch: '#036dd626',
+ // lineHighlight: '#8a91991a', // original
+ lineHighlight: '#00000050',
+ gutterBackground: 'transparent',
+ // gutterForeground: '#8a919966',
+ gutterForeground: '#8a919966',
+ },
+ styles: [
+ { tag: t.keyword, color: '#c792ea' },
+ { tag: t.operator, color: '#89ddff' },
+ { tag: t.special(t.variableName), color: '#eeffff' },
+ // { tag: t.typeName, color: '#f07178' }, // original
+ { tag: t.typeName, color: '#c3e88d' },
+ { tag: t.atom, color: '#f78c6c' },
+ // { tag: t.number, color: '#ff5370' }, // original
+ { tag: t.number, color: '#c3e88d' },
+ { tag: t.definition(t.variableName), color: '#82aaff' },
+ { tag: t.string, color: '#c3e88d' },
+ // { tag: t.special(t.string), color: '#f07178' }, // original
+ { tag: t.special(t.string), color: '#c3e88d' },
+ { tag: t.comment, color: '#7d8799' },
+ // { tag: t.variableName, color: '#f07178' }, // original
+ { tag: t.variableName, color: '#c792ea' },
+ // { tag: t.tagName, color: '#ff5370' }, // original
+ { tag: t.tagName, color: '#c3e88d' },
+ { tag: t.bracket, color: '#525154' },
+ // { tag: t.bracket, color: '#a2a1a4' }, // original
+ { tag: t.meta, color: '#ffcb6b' },
+ { tag: t.attributeName, color: '#c792ea' },
+ { tag: t.propertyName, color: '#c792ea' },
+
+ { tag: t.className, color: '#decb6b' },
+ { tag: t.invalid, color: '#ffffff' },
+ { tag: [t.unit, t.punctuation], color: '#82aaff' },
+ ],
+});
diff --git a/packages/codemirror/themes/teletext.js b/packages/codemirror/themes/teletext.js
new file mode 100644
index 000000000..5fd9a5570
--- /dev/null
+++ b/packages/codemirror/themes/teletext.js
@@ -0,0 +1,50 @@
+import { tags as t } from '@lezer/highlight';
+import { createTheme } from '@uiw/codemirror-themes';
+
+let colorA = '#6edee4';
+//let colorB = 'magenta';
+let colorB = 'white';
+let colorC = 'red';
+let colorD = '#f8fc55';
+
+export const settings = {
+ background: '#000000',
+ foreground: colorA, // whats that?
+ caret: colorC,
+ selection: colorD,
+ selectionMatch: colorA,
+ lineHighlight: '#6edee440', // panel bg
+ lineBackground: '#00000040',
+ gutterBackground: 'transparent',
+ gutterForeground: '#8a919966',
+ customStyle: '.cm-line { line-height: 1 }',
+};
+
+let punctuation = colorD;
+let mini = colorB;
+
+export default createTheme({
+ theme: 'dark',
+ settings,
+ styles: [
+ { tag: t.keyword, color: colorA },
+ { tag: t.operator, color: mini },
+ { tag: t.special(t.variableName), color: colorA },
+ { tag: t.typeName, color: colorA },
+ { tag: t.atom, color: colorA },
+ { tag: t.number, color: mini },
+ { tag: t.definition(t.variableName), color: colorA },
+ { tag: t.string, color: mini },
+ { tag: t.special(t.string), color: mini },
+ { tag: t.comment, color: punctuation },
+ { tag: t.variableName, color: colorA },
+ { tag: t.tagName, color: colorA },
+ { tag: t.bracket, color: punctuation },
+ { tag: t.meta, color: colorA },
+ { tag: t.attributeName, color: colorA },
+ { tag: t.propertyName, color: colorA }, // methods
+ { tag: t.className, color: colorA },
+ { tag: t.invalid, color: colorC },
+ { tag: [t.unit, t.punctuation], color: punctuation },
+ ],
+});
diff --git a/packages/codemirror/themes/terminal.js b/packages/codemirror/themes/terminal.js
new file mode 100644
index 000000000..1374bb867
--- /dev/null
+++ b/packages/codemirror/themes/terminal.js
@@ -0,0 +1,36 @@
+import { tags as t } from '@lezer/highlight';
+import { createTheme } from '@uiw/codemirror-themes';
+export const settings = {
+ background: 'black',
+ foreground: '#41FF00', // whats that?
+ caret: '#41FF00',
+ selection: '#ffffff20',
+ selectionMatch: '#036dd626',
+ lineHighlight: '#ffffff10',
+ gutterBackground: 'transparent',
+ gutterForeground: '#8a919966',
+};
+export default createTheme({
+ theme: 'dark',
+ settings,
+ styles: [
+ { tag: t.keyword, color: '#41FF00' },
+ { tag: t.operator, color: '#41FF00' },
+ { tag: t.special(t.variableName), color: '#41FF00' },
+ { tag: t.typeName, color: '#41FF00' },
+ { tag: t.atom, color: '#41FF00' },
+ { tag: t.number, color: '#41FF00' },
+ { tag: t.definition(t.variableName), color: '#41FF00' },
+ { tag: t.string, color: '#41FF00' },
+ { tag: t.special(t.string), color: '#41FF00' },
+ { tag: t.comment, color: '#41FF00' },
+ { tag: t.variableName, color: '#41FF00' },
+ { tag: t.tagName, color: '#41FF00' },
+ { tag: t.bracket, color: '#41FF00' },
+ { tag: t.meta, color: '#41FF00' },
+ { tag: t.attributeName, color: '#41FF00' },
+ { tag: t.propertyName, color: '#41FF00' },
+ { tag: t.className, color: '#41FF00' },
+ { tag: t.invalid, color: '#41FF00' },
+ ],
+});
diff --git a/packages/codemirror/themes/whitescreen.js b/packages/codemirror/themes/whitescreen.js
new file mode 100644
index 000000000..22abad9e9
--- /dev/null
+++ b/packages/codemirror/themes/whitescreen.js
@@ -0,0 +1,38 @@
+import { tags as t } from '@lezer/highlight';
+import { createTheme } from '@uiw/codemirror-themes';
+export const settings = {
+ background: 'white',
+ foreground: 'black', // whats that?
+ caret: 'black',
+ selection: 'rgba(128, 203, 196, 0.5)',
+ selectionMatch: '#ffffff26',
+ lineHighlight: '#cccccc50',
+ lineBackground: '#ffffff50',
+ gutterBackground: 'transparent',
+ gutterForeground: 'black',
+ light: true,
+};
+export default createTheme({
+ theme: 'light',
+ settings,
+ styles: [
+ { tag: t.keyword, color: 'black' },
+ { tag: t.operator, color: 'black' },
+ { tag: t.special(t.variableName), color: 'black' },
+ { tag: t.typeName, color: 'black' },
+ { tag: t.atom, color: 'black' },
+ { tag: t.number, color: 'black' },
+ { tag: t.definition(t.variableName), color: 'black' },
+ { tag: t.string, color: 'black' },
+ { tag: t.special(t.string), color: 'black' },
+ { tag: t.comment, color: 'black' },
+ { tag: t.variableName, color: 'black' },
+ { tag: t.tagName, color: 'black' },
+ { tag: t.bracket, color: 'black' },
+ { tag: t.meta, color: 'black' },
+ { tag: t.attributeName, color: 'black' },
+ { tag: t.propertyName, color: 'black' },
+ { tag: t.className, color: 'black' },
+ { tag: t.invalid, color: 'black' },
+ ],
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e436542cd..60a91f14b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -71,6 +71,9 @@ importers:
packages/codemirror:
dependencies:
+ '@codemirror/autocomplete':
+ specifier: ^6.6.0
+ version: 6.6.0(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)(@lezer/common@1.0.2)
'@codemirror/commands':
specifier: ^6.2.4
version: 6.2.4
@@ -89,9 +92,27 @@ importers:
'@lezer/highlight':
specifier: ^1.1.4
version: 1.1.4
+ '@replit/codemirror-emacs':
+ specifier: ^6.0.1
+ version: 6.0.1(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
+ '@replit/codemirror-vim':
+ specifier: ^6.0.14
+ version: 6.0.14(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
+ '@replit/codemirror-vscode-keymap':
+ specifier: ^6.0.2
+ version: 6.0.2(@codemirror/autocomplete@6.6.0)(@codemirror/commands@6.2.4)(@codemirror/language@6.6.0)(@codemirror/lint@6.1.0)(@codemirror/search@6.2.3)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
'@strudel.cycles/core':
specifier: workspace:*
version: link:../core
+ '@uiw/codemirror-themes':
+ specifier: ^4.19.16
+ version: 4.19.16(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
+ '@uiw/codemirror-themes-all':
+ specifier: ^4.19.16
+ version: 4.19.16(@codemirror/language@6.6.0)(@codemirror/state@6.2.0)(@codemirror/view@6.10.0)
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.2.0(react@18.2.0)
devDependencies:
vite:
specifier: ^4.3.3
diff --git a/website/src/repl/Footer.jsx b/website/src/repl/Footer.jsx
index 9fb783a49..8423315fe 100644
--- a/website/src/repl/Footer.jsx
+++ b/website/src/repl/Footer.jsx
@@ -385,6 +385,8 @@ function SettingsTab({ scheduler }) {
keybindings,
isLineNumbersDisplayed,
isAutoCompletionEnabled,
+ isFlashEnabled,
+ isPatternHighlightingEnabled,
isLineWrappingEnabled,
fontSize,
fontFamily,
@@ -462,6 +464,16 @@ function SettingsTab({ scheduler }) {
onChange={(cbEvent) => settingsMap.setKey('isLineWrappingEnabled', cbEvent.target.checked)}
value={isLineWrappingEnabled}
/>
+ settingsMap.setKey('isPatternHighlightingEnabled', cbEvent.target.checked)}
+ value={isPatternHighlightingEnabled}
+ />
+ settingsMap.setKey('isFlashEnabled', cbEvent.target.checked)}
+ value={isFlashEnabled}
+ />
Try clicking the logo in the top left!
diff --git a/website/src/repl/vanillarepl.mjs b/website/src/repl/vanillarepl.mjs
index 42c5ebc08..ac5708d70 100644
--- a/website/src/repl/vanillarepl.mjs
+++ b/website/src/repl/vanillarepl.mjs
@@ -3,24 +3,53 @@ import { getAudioContext, webaudioOutput } from '@strudel.cycles/webaudio';
import { transpiler } from '@strudel.cycles/transpiler';
import { prebake } from './prebake.mjs';
import { settingsMap } from '@src/settings.mjs';
-import { themes } from '@src/repl/themes.mjs';
import { setLatestCode } from '../settings.mjs';
import { hash2code, code2hash } from './helpers.mjs';
import { createClient } from '@supabase/supabase-js';
import * as tunes from './tunes.mjs';
-const onEvent = (key, callback) => {
- const listener = (e) => {
- if (e.data === key) {
- callback();
- }
- };
- window.addEventListener('message', listener);
- return () => window.removeEventListener('message', listener);
-};
+const supabase = createClient(
+ 'https://pidxdsxphlhzjnzmifth.supabase.co',
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM',
+);
-function run() {
- const { latestCode } = settingsMap.get();
+async function initCodeFromUrl() {
+ // load code from url hash (either short hash from database or decode long hash)
+ try {
+ const initialUrl = window.location.href;
+ const hash = initialUrl.split('?')[1]?.split('#')?.[0];
+ const codeParam = window.location.href.split('#')[1] || '';
+ // looking like https://strudel.cc/?J01s5i1J0200 (fixed hash length)
+ if (codeParam) {
+ // looking like https://strudel.cc/#ImMzIGUzIg%3D%3D (hash length depends on code length)
+ return hash2code(codeParam);
+ } else if (hash) {
+ return supabase
+ .from('code')
+ .select('code')
+ .eq('hash', hash)
+ .then(({ data, error }) => {
+ if (error) {
+ console.warn('failed to load hash', error);
+ }
+ if (data.length) {
+ //console.log('load hash from database', hash);
+ return data[0].code;
+ }
+ });
+ }
+ } catch (err) {
+ console.warn('failed to decode', err);
+ }
+}
+const initialCode = initCodeFromUrl();
+
+async function run() {
+ const container = document.getElementById('code');
+ if (!container) {
+ console.warn('could not init: no container found');
+ return;
+ }
/* let clearCanvas;
if (typeof window !== 'undefined') {
@@ -29,54 +58,18 @@ if (typeof window !== 'undefined') {
} */
// Create a single supabase client for interacting with your database
- const supabase = createClient(
- 'https://pidxdsxphlhzjnzmifth.supabase.co',
- 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBpZHhkc3hwaGxoempuem1pZnRoIiwicm9sZSI6ImFub24iLCJpYXQiOjE2NTYyMzA1NTYsImV4cCI6MTk3MTgwNjU1Nn0.bqlw7802fsWRnqU5BLYtmXk_k-D1VFmbkHMywWc15NM',
- );
- async function initCode() {
- // load code from url hash (either short hash from database or decode long hash)
- try {
- const initialUrl = window.location.href;
- const hash = initialUrl.split('?')[1]?.split('#')?.[0];
- const codeParam = window.location.href.split('#')[1] || '';
- // looking like https://strudel.cc/?J01s5i1J0200 (fixed hash length)
- if (codeParam) {
- // looking like https://strudel.cc/#ImMzIGUzIg%3D%3D (hash length depends on code length)
- return hash2code(codeParam);
- } else if (hash) {
- return supabase
- .from('code')
- .select('code')
- .eq('hash', hash)
- .then(({ data, error }) => {
- if (error) {
- console.warn('failed to load hash', error);
- }
- if (data.length) {
- //console.log('load hash from database', hash);
- return data[0].code;
- }
- });
- }
- } catch (err) {
- console.warn('failed to decode', err);
- }
- }
- const container = document.getElementById('code');
- const setFontSize = (size) => (container.style.fontSize = size + 'px');
- const setFontFamily = (family) => (container.style.fontFamily = family);
+ const settings = settingsMap.get();
/* const drawContext = getDrawContext()
const drawTime = [-2, 2]; */
- console.log('container', container);
const editor = new StrudelMirror({
- theme: themes.strudelTheme,
defaultOutput: webaudioOutput,
getTime: () => getAudioContext().currentTime,
transpiler,
root: container,
initialCode: '// LOADING',
+ settings,
/* drawTime,
onDraw: (haps, time) =>
drawPianoroll({ haps, time, ctx: drawContext, drawTime, fold: 1 }), */
@@ -87,56 +80,27 @@ const drawTime = [-2, 2]; */
},
});
- function getRandomTune() {
- const allTunes = Object.entries(tunes);
- const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)];
- const [name, code] = randomItem(allTunes);
- return { name, code };
- }
-
const { code: randomTune, name } = getRandomTune();
- function init() {
- if (!container) {
- console.warn('could not init: no container found');
- return;
- }
- const settings = settingsMap.get();
- setFontSize(settings.fontSize);
- setFontFamily(settings.fontFamily);
- initCode().then((decoded) => {
- let msg;
- if (decoded) {
- editor.setCode(decoded);
- msg = `I have loaded the code from the URL.`;
- } else if (latestCode) {
- editor.setCode(latestCode);
- msg = `Your last session has been loaded!`;
- } /* if(randomTune) */ else {
- editor.setCode(randomTune);
- msg = `A random code snippet named "${name}" has been loaded!`;
- }
- console.log('msg', msg);
- /* logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight');
- setPending(false); */
- });
-
- editor.setTheme(themes[settings.theme || 'strudelTheme']);
+ // init settings
+ editor.updateSettings(settings);
+ const decoded = await initialCode;
+ let msg;
+ if (decoded) {
+ editor.setCode(decoded);
+ msg = `I have loaded the code from the URL.`;
+ } else if (settings.latestCode) {
+ editor.setCode(settings.latestCode);
+ msg = `Your last session has been loaded!`;
+ } /* if(randomTune) */ else {
+ editor.setCode(randomTune);
+ msg = `A random code snippet named "${name}" has been loaded!`;
}
- init();
+ console.log('msg', msg);
+ /* logger(`Welcome to Strudel! ${msg} Press play or hit ctrl+enter to run it!`, 'highlight');
+ setPending(false); */
- settingsMap.listen((settings, key) => {
- const value = settings[key];
- if (key === 'theme') {
- editor.setTheme(themes[value]);
- } else if (key === 'fontFamily') {
- // console.log('change fontFamily', value);
- setFontFamily(value);
- } else if (key === 'fontSize') {
- // console.log('change fontSize', value);
- setFontSize(value);
- }
- });
+ settingsMap.listen((settings, key) => editor.changeSetting(key, settings[key]));
onEvent('strudel-toggle-play', () => editor.toggle());
@@ -150,3 +114,20 @@ onEvent('strudel-container', () => {
run();
}
});
+
+function onEvent(key, callback) {
+ const listener = (e) => {
+ if (e.data === key) {
+ callback();
+ }
+ };
+ window.addEventListener('message', listener);
+ return () => window.removeEventListener('message', listener);
+}
+
+function getRandomTune() {
+ const allTunes = Object.entries(tunes);
+ const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)];
+ const [name, code] = randomItem(allTunes);
+ return { name, code };
+}
diff --git a/website/src/settings.mjs b/website/src/settings.mjs
index 5608b8943..2de6e4d79 100644
--- a/website/src/settings.mjs
+++ b/website/src/settings.mjs
@@ -7,6 +7,8 @@ export const defaultSettings = {
keybindings: 'codemirror',
isLineNumbersDisplayed: true,
isAutoCompletionEnabled: false,
+ isPatternHighlightingEnabled: true,
+ isFlashEnabled: true,
isLineWrappingEnabled: false,
theme: 'strudelTheme',
fontFamily: 'monospace',
@@ -27,6 +29,8 @@ export function useSettings() {
isLineNumbersDisplayed: [true, 'true'].includes(state.isLineNumbersDisplayed) ? true : false,
isAutoCompletionEnabled: [true, 'true'].includes(state.isAutoCompletionEnabled) ? true : false,
isLineWrappingEnabled: [true, 'true'].includes(state.isLineWrappingEnabled) ? true : false,
+ isPatternHighlightingEnabled: [true, 'true'].includes(state.isPatternHighlightingEnabled) ? true : false,
+ isFlashEnabled: [true, 'true'].includes(state.isFlashEnabled) ? true : false,
fontSize: Number(state.fontSize),
panelPosition: state.activeFooter !== '' ? state.panelPosition : 'bottom',
};
diff --git a/website/tsconfig.json b/website/tsconfig.json
index 90aa524fd..ed79dc036 100644
--- a/website/tsconfig.json
+++ b/website/tsconfig.json
@@ -12,6 +12,7 @@
"paths": {
"@components/*": ["src/components/*"],
"@src/*": ["src/*"],
+ "~/*": ["/*"],
}
}
}
\ No newline at end of file