From f5075906e2166361dc58c9c16b8d4fdb81eaaff1 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Fri, 5 May 2023 15:13:17 +0200 Subject: [PATCH] add codemirror package + use it in vite-vanilla-repl-cm6 --- packages/codemirror/README.md | 3 + .../codemirror.mjs} | 32 +++- packages/codemirror/package.json | 46 ++++++ packages/codemirror/themes/one-dark.mjs | 139 ++++++++++++++++++ .../examples/vite-vanilla-repl-cm6/main.js | 12 +- .../vite-vanilla-repl-cm6/package.json | 7 +- 6 files changed, 222 insertions(+), 17 deletions(-) create mode 100644 packages/codemirror/README.md rename packages/{core/examples/vite-vanilla-repl-cm6/codemirror.js => codemirror/codemirror.mjs} (84%) create mode 100644 packages/codemirror/package.json create mode 100644 packages/codemirror/themes/one-dark.mjs diff --git a/packages/codemirror/README.md b/packages/codemirror/README.md new file mode 100644 index 00000000..300e30b3 --- /dev/null +++ b/packages/codemirror/README.md @@ -0,0 +1,3 @@ +# @strudel/codemirror + +This package contains helpers and extensions to use codemirror6. See [vite-vanilla-repl-cm6](../core/examples/vite-vanilla-repl-cm6/main.js) as an example of using it. diff --git a/packages/core/examples/vite-vanilla-repl-cm6/codemirror.js b/packages/codemirror/codemirror.mjs similarity index 84% rename from packages/core/examples/vite-vanilla-repl-cm6/codemirror.js rename to packages/codemirror/codemirror.mjs index 103edd92..1d874aff 100644 --- a/packages/core/examples/vite-vanilla-repl-cm6/codemirror.js +++ b/packages/codemirror/codemirror.mjs @@ -4,14 +4,14 @@ import { defaultKeymap } from '@codemirror/commands'; import { syntaxHighlighting, defaultHighlightStyle } from '@codemirror/language'; import { javascript } from '@codemirror/lang-javascript'; import { StateField, StateEffect } from '@codemirror/state'; -import { oneDark } from './one-dark'; +import { oneDark } from './themes/one-dark'; // https://codemirror.net/docs/guide/ -export function initEditor({ initialCode, onChange, onEvaluate, onStop }) { +export function initEditor({ initialCode = '', onChange, onEvaluate, onStop, theme = oneDark, root }) { let state = EditorState.create({ doc: initialCode, extensions: [ - oneDark, + theme, javascript(), lineNumbers(), highlightField, @@ -35,7 +35,7 @@ export function initEditor({ initialCode, onChange, onEvaluate, onStop }) { return new EditorView({ state, - parent: document.getElementById('editor'), + parent: root, }); } @@ -119,9 +119,29 @@ const flashField = StateField.define({ provide: (f) => EditorView.decorations.from(f), }); -export const flash = (view) => { +export const flash = (view, ms = 200) => { view.dispatch({ effects: setFlash.of(true) }); setTimeout(() => { view.dispatch({ effects: setFlash.of(false) }); - }, 200); + }, ms); }; + +export class StrudelMirror { + constructor({ root, initialCode = '', onEvaluate, onStop }) { + this.view = initEditor({ + root, + initialCode, + onChange: (v) => { + this.code = v.state.doc.toString(); + }, + onEvaluate, + onStop, + }); + } + flash(ms) { + flash(this.view, ms); + } + highlight(haps) { + highlightHaps(this.view, haps); + } +} diff --git a/packages/codemirror/package.json b/packages/codemirror/package.json new file mode 100644 index 00000000..6df43e80 --- /dev/null +++ b/packages/codemirror/package.json @@ -0,0 +1,46 @@ +{ + "name": "@strudel/codemirror", + "version": "0.8.0", + "description": "Codemirror Extensions for Strudel", + "main": "codemirror.mjs", + "publishConfig": { + "main": "dist/index.js", + "module": "dist/index.mjs" + }, + "scripts": { + "build": "vite build", + "prepublishOnly": "npm run build" + }, + "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/tidalcycles/strudel.git" + }, + "keywords": [ + "tidalcycles", + "strudel", + "pattern", + "livecoding", + "algorave" + ], + "author": "Felix Roos ", + "contributors": [ + "Alex McLean " + ], + "license": "AGPL-3.0-or-later", + "bugs": { + "url": "https://github.com/tidalcycles/strudel/issues" + }, + "homepage": "https://github.com/tidalcycles/strudel#readme", + "dependencies": { + "@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" + }, + "devDependencies": { + "vite": "^4.3.3" + } +} diff --git a/packages/codemirror/themes/one-dark.mjs b/packages/codemirror/themes/one-dark.mjs new file mode 100644 index 00000000..cce83699 --- /dev/null +++ b/packages/codemirror/themes/one-dark.mjs @@ -0,0 +1,139 @@ +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/core/examples/vite-vanilla-repl-cm6/main.js b/packages/core/examples/vite-vanilla-repl-cm6/main.js index a73ccff3..7b19596d 100644 --- a/packages/core/examples/vite-vanilla-repl-cm6/main.js +++ b/packages/core/examples/vite-vanilla-repl-cm6/main.js @@ -1,9 +1,9 @@ // moved from sandbox: https://codesandbox.io/s/vanilla-codemirror-strudel-2wb7yw?file=/index.html:114-186 -import { initEditor, highlightHaps, flash } from './codemirror'; +import { StrudelMirror } from '@strudel/codemirror'; import { initStrudel } from './strudel'; import { Drawer } from './drawer'; -import { bumpStreet, trafficFlam, funk42 } from './tunes'; +import { funk42 } from './tunes'; import { pianoroll, getDrawOptions } from '@strudel.cycles/core'; import './style.css'; @@ -13,7 +13,8 @@ const canvas = document.getElementById('roll'); canvas.width = canvas.width * 2; canvas.height = canvas.height * 2; -const view = initEditor({ +const editor = new StrudelMirror({ + root: document.getElementById('editor'), initialCode: code, onChange: (v) => { code = v.state.doc.toString(); @@ -24,7 +25,7 @@ const view = initEditor({ async function onEvaluate() { const { evaluate, scheduler } = await repl; - flash(view); + editor.flash(); if (!scheduler.started) { scheduler.stop(); await evaluate(code); @@ -40,11 +41,12 @@ async function onStop() { scheduler.stop(); drawer.stop(); } + const ctx = canvas.getContext('2d'); let drawer = new Drawer( (haps, time, { drawTime }) => { const currentFrame = haps.filter((hap) => time >= hap.whole.begin && time <= hap.whole.end); - highlightHaps(view, currentFrame); + editor.highlight(currentFrame); pianoroll({ ctx, time, haps, ...getDrawOptions(drawTime, { fold: 0 }) }); }, [-2, 2], diff --git a/packages/core/examples/vite-vanilla-repl-cm6/package.json b/packages/core/examples/vite-vanilla-repl-cm6/package.json index 0251bf96..4e093d10 100644 --- a/packages/core/examples/vite-vanilla-repl-cm6/package.json +++ b/packages/core/examples/vite-vanilla-repl-cm6/package.json @@ -12,12 +12,7 @@ "vite": "^4.3.2" }, "dependencies": { - "@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/codemirror": "workspace:*", "@strudel.cycles/core": "workspace:*", "@strudel.cycles/mini": "workspace:*", "@strudel.cycles/soundfonts": "workspace:*",