220 lines
6.0 KiB
Svelte
220 lines
6.0 KiB
Svelte
<script lang="ts">
|
|
import { onMount, onDestroy } from 'svelte';
|
|
import { EditorView } from 'codemirror';
|
|
import { Compartment } from '@codemirror/state';
|
|
import { lineNumbers, highlightActiveLineGutter, highlightSpecialChars, drawSelection, dropCursor, rectangularSelection, crosshairCursor, highlightActiveLine, keymap } from '@codemirror/view';
|
|
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
|
|
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
|
|
import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
|
|
import { foldGutter, indentOnInput, syntaxHighlighting, defaultHighlightStyle, bracketMatching, foldKeymap } from '@codemirror/language';
|
|
import { lintKeymap } from '@codemirror/lint';
|
|
import { csoundMode } from '@hlolli/codemirror-lang-csound';
|
|
import { oneDark } from '@codemirror/theme-one-dark';
|
|
import { vim } from '@replit/codemirror-vim';
|
|
import type { EditorSettingsStore } from '../../stores/editorSettings';
|
|
import {
|
|
flashField,
|
|
flash,
|
|
getSelection,
|
|
getBlock,
|
|
getDocument
|
|
} from '../../editor/block-eval';
|
|
|
|
interface Props {
|
|
value: string;
|
|
onChange?: (value: string) => void;
|
|
onExecute?: (code: string, source: 'selection' | 'block' | 'document') => void;
|
|
editorSettings: EditorSettingsStore;
|
|
mode: 'composition' | 'livecoding';
|
|
}
|
|
|
|
let {
|
|
value = '',
|
|
onChange,
|
|
onExecute,
|
|
editorSettings,
|
|
mode = 'composition'
|
|
}: Props = $props();
|
|
|
|
let editorContainer: HTMLDivElement;
|
|
let editorView: EditorView | null = null;
|
|
|
|
const lineNumbersCompartment = new Compartment();
|
|
const lineWrappingCompartment = new Compartment();
|
|
const vimCompartment = new Compartment();
|
|
|
|
function handleExecute() {
|
|
if (!editorView) return;
|
|
|
|
if (mode === 'composition') {
|
|
const doc = getDocument(editorView.state);
|
|
flash(editorView, doc.from, doc.to);
|
|
onExecute?.(doc.text, 'document');
|
|
return;
|
|
}
|
|
|
|
const selection = getSelection(editorView.state);
|
|
if (selection.text) {
|
|
flash(editorView, selection.from, selection.to);
|
|
onExecute?.(selection.text, 'selection');
|
|
return;
|
|
}
|
|
|
|
const block = getBlock(editorView.state);
|
|
if (block.text) {
|
|
flash(editorView, block.from, block.to);
|
|
onExecute?.(block.text, 'block');
|
|
return;
|
|
}
|
|
|
|
const doc = getDocument(editorView.state);
|
|
flash(editorView, doc.from, doc.to);
|
|
onExecute?.(doc.text, 'document');
|
|
}
|
|
|
|
const evaluateKeymap = keymap.of([
|
|
{
|
|
key: 'Mod-e',
|
|
run: () => {
|
|
handleExecute();
|
|
return true;
|
|
}
|
|
}
|
|
]);
|
|
|
|
onMount(() => {
|
|
const baseExtensions = [
|
|
highlightActiveLineGutter(),
|
|
highlightSpecialChars(),
|
|
history(),
|
|
foldGutter(),
|
|
drawSelection(),
|
|
dropCursor(),
|
|
indentOnInput(),
|
|
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
|
bracketMatching(),
|
|
closeBrackets(),
|
|
autocompletion(),
|
|
rectangularSelection(),
|
|
crosshairCursor(),
|
|
highlightSelectionMatches(),
|
|
keymap.of([
|
|
...closeBracketsKeymap,
|
|
...defaultKeymap,
|
|
...searchKeymap,
|
|
...historyKeymap,
|
|
...foldKeymap,
|
|
...completionKeymap,
|
|
...lintKeymap
|
|
])
|
|
];
|
|
|
|
const initSettings = $editorSettings;
|
|
|
|
editorView = new EditorView({
|
|
doc: value,
|
|
extensions: [
|
|
...baseExtensions,
|
|
csoundMode({ fileType: 'csd' }),
|
|
oneDark,
|
|
evaluateKeymap,
|
|
flashField(),
|
|
lineNumbersCompartment.of(initSettings.showLineNumbers ? lineNumbers() : []),
|
|
lineWrappingCompartment.of(initSettings.enableLineWrapping ? EditorView.lineWrapping : []),
|
|
vimCompartment.of(initSettings.vimMode ? vim() : []),
|
|
EditorView.updateListener.of((update) => {
|
|
if (update.docChanged && onChange) {
|
|
onChange(update.state.doc.toString());
|
|
}
|
|
}),
|
|
EditorView.theme({
|
|
'&': {
|
|
fontSize: `${initSettings.fontSize}px`,
|
|
fontFamily: initSettings.fontFamily
|
|
}
|
|
})
|
|
],
|
|
parent: editorContainer
|
|
});
|
|
});
|
|
|
|
$effect(() => {
|
|
const settings = $editorSettings;
|
|
if (!editorView) return;
|
|
|
|
editorView.dispatch({
|
|
effects: [
|
|
lineNumbersCompartment.reconfigure(settings.showLineNumbers ? lineNumbers() : []),
|
|
lineWrappingCompartment.reconfigure(settings.enableLineWrapping ? EditorView.lineWrapping : []),
|
|
vimCompartment.reconfigure(settings.vimMode ? vim() : [])
|
|
]
|
|
});
|
|
|
|
editorView.dom.style.fontSize = `${settings.fontSize}px`;
|
|
editorView.dom.style.fontFamily = settings.fontFamily;
|
|
});
|
|
|
|
onDestroy(() => {
|
|
if (editorView) {
|
|
editorView.destroy();
|
|
}
|
|
});
|
|
|
|
$effect(() => {
|
|
if (editorView && value !== editorView.state.doc.toString()) {
|
|
editorView.dispatch({
|
|
changes: { from: 0, to: editorView.state.doc.length, insert: value }
|
|
});
|
|
}
|
|
});
|
|
|
|
export function getSelectedText(): string | null {
|
|
if (!editorView) return null;
|
|
const { text } = getSelection(editorView.state);
|
|
return text || null;
|
|
}
|
|
|
|
export function getCurrentBlock(): string | null {
|
|
if (!editorView) return null;
|
|
const { text } = getBlock(editorView.state);
|
|
return text || null;
|
|
}
|
|
|
|
export function getFullDocument(): string {
|
|
if (!editorView) return '';
|
|
const { text } = getDocument(editorView.state);
|
|
return text;
|
|
}
|
|
|
|
export function evaluateWithFlash(text: string, from: number | null, to: number | null) {
|
|
if (editorView && from !== null && to !== null) {
|
|
flash(editorView, from, to);
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<div class="editor-wrapper">
|
|
<div bind:this={editorContainer} class="editor-container"></div>
|
|
</div>
|
|
|
|
<style>
|
|
.editor-wrapper {
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.editor-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
:global(.cm-editor) {
|
|
height: 100%;
|
|
}
|
|
|
|
:global(.cm-scroller) {
|
|
overflow: auto;
|
|
}
|
|
</style>
|