@scidian/scrimp
Version:
Customized CodeMirror editor for use with the Onsight Engine.
219 lines (184 loc) • 7.8 kB
JavaScript
// https://codemirror.net/examples/bundle/
// https://codemirror.net/docs/migration/
// https://unpkg.com/browse/codemirror@6.0.1/dist/index.js
// https://github.com/RPGillespie6/codemirror-quickstart
import { EditorState, StateEffect, Compartment } from '@codemirror/state';
import { EditorView } from '@codemirror/view';
import { lineNumbers, highlightActiveLineGutter, highlightSpecialChars, drawSelection, dropCursor, rectangularSelection, crosshairCursor, highlightActiveLine, keymap } from '@codemirror/view';
import { foldGutter, indentOnInput, indentUnit, syntaxHighlighting, defaultHighlightStyle, bracketMatching, foldKeymap } from '@codemirror/language';
import { history, defaultKeymap, historyKeymap, insertTab, indentLess } from '@codemirror/commands';
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search';
import { closeBrackets, autocompletion, closeBracketsKeymap, completionKeymap } from '@codemirror/autocomplete';
import { lintKeymap, linter, lintGutter } from '@codemirror/lint';
// Language
import { javascript, esLint } from '@codemirror/lang-javascript';
import { python } from '@codemirror/lang-python';
// Linter
import * as esb from 'eslint-linter-browserify';
// Theme
// - Editor: https://thememirror.net/create
import { oneDark } from '@codemirror/theme-one-dark';
import { themeSuey } from './theme/theme-suey.js';
// Linter Config
const jsConfig = {
languageOptions: {
parserOptions: {
ecmaVersion: 2023,
sourceType: "module",
},
},
rules: {
// semi: [ "error", "never" ],
},
};
class Scrimp extends EditorView {
static defaultExtensions() {
return [
lineNumbers(),
highlightActiveLineGutter(),
highlightSpecialChars(),
foldGutter(),
// drawSelection(), //NOTE: Don't want, allows browser to draw native selection color
dropCursor(),
EditorState.allowMultipleSelections.of(true),
indentOnInput(),
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
bracketMatching(),
closeBrackets(),
autocompletion(),
rectangularSelection(),
crosshairCursor(),
highlightActiveLine(),
highlightSelectionMatches(),
keymap.of([
...closeBracketsKeymap,
...defaultKeymap,
...searchKeymap,
...historyKeymap,
...foldKeymap,
...completionKeymap,
...lintKeymap,
{ key: 'Tab', preventDefault: true, run: insertTab },
{ key: 'Shift-Tab', preventDefault: true, run: indentLess },
]),
];
}
constructor(parent, options = {}) {
if (typeof options !== 'object') options = {};
if (options.initialContents == null) options.initialContents = '';
if (options.theme == null) options.theme = 'none';
if (options.tabSize == null) options.tabSize = 4;
if (options.language == null) options.language = 'javascript';
if (options.linter == null) options.linter = true;
// EditorView
super({ parent });
// Extensions
const extensions = Scrimp.defaultExtensions();
// Compartments
this.historyCompartment = new Compartment();
this.languageCompartment = new Compartment();
this.gutterCompartment = new Compartment();
this.linterCompartment = new Compartment();
// History
extensions.push(this.historyCompartment.of(history()));
// Language
if (options.language === 'javascript') {
extensions.push(this.languageCompartment.of(javascript()));
if (options.linter) {
extensions.push(this.gutterCompartment.of(lintGutter()));
extensions.push(this.linterCompartment.of(linter(esLint(new esb.Linter(), jsConfig))));
}
} else if (options.language === 'python') {
extensions.push(this.languageCompartment.of(python()));
if (options.linter) {
extensions.push(this.gutterCompartment.of());
extensions.push(this.linterCompartment.of());
}
} else if (!options.linter) {
extensions.push(this.gutterCompartment.of());
extensions.push(this.linterCompartment.of());
}
// Theme
if (options.theme === 'dark') { extensions.push(oneDark); }
if (options.theme === 'suey') { extensions.push(themeSuey); }
// Tab Size
if (options.tabSize) extensions.push(indentUnit.of(" ".repeat(options.tabSize)));
// State
const state = EditorState.create({
doc: options.initialContents,
extensions,
});
this.setState(state);
}
/******************** CALLBACK */
addKeymap(key = '', callback) {
if (key && key !== '') {
const newKeymap = keymap.of([ {
key, // i.e. 'Ctrl-Enter', 'Ctrl-S', etc.
run: (view) => {
if (typeof callback === 'function') callback(view);
return true;
},
}, ]);
this.dispatch({ effects: StateEffect.appendConfig.of(newKeymap) });
}
return this;
}
addUpdate(callback) {
if (typeof callback === 'function') {
const newUpdate = EditorView.updateListener.of(callback);
this.dispatch({ effects: StateEffect.appendConfig.of(newUpdate) });
}
return this;
}
/******************** CONTENT */
getContent() {
return this.state.doc.toString();
}
setContent(content, from = 0, to = this.state.doc.length) {
this.dispatch({ changes: { from, to, insert: content } });
return this;
}
/******************** COMMANDS */
// https://github.com/codemirror/commands/blob/main/src/commands.ts
getCursor() {
return this.state.selection.main.head;
}
setCursor(pos) {
this.dispatch({ selection: { anchor: pos } });
return this;
}
setSelection(from, to) {
if (Number.isFinite(from) && Number.isFinite(to)) {
this.dispatch({ selection: { anchor: from, head: to } });
}
return this;
}
selectAll() {
this.dispatch(this.state.update({ selection: { anchor: 0, head: this.state.doc.length }, userEvent: 'select' }));
return this;
}
/******************** HISTORY */
clearHistory() {
this.dispatch({ effects: [ this.historyCompartment.reconfigure([]) ] });
this.dispatch({ effects: [ this.historyCompartment.reconfigure([ history() ]) ] });
}
/******************** LANGUAGE */
setLanguage(language = 'javascript', includeLinter = true) {
this.dispatch({ effects: [ this.languageCompartment.reconfigure([]) ] });
if (language === 'javascript') {
this.dispatch({ effects: [ this.languageCompartment.reconfigure([ javascript() ]) ] });
if (includeLinter) {
this.dispatch({ effects: [ this.gutterCompartment.reconfigure([ lintGutter() ]) ] });
this.dispatch({ effects: [ this.linterCompartment.reconfigure([ linter(esLint(new esb.Linter(), jsConfig)) ]) ] });
}
} else if (language === 'python') {
this.dispatch({ effects: [ this.languageCompartment.reconfigure([ python() ]) ] });
if (includeLinter) {
this.dispatch({ effects: [ this.gutterCompartment.reconfigure([]) ] });
this.dispatch({ effects: [ this.linterCompartment.reconfigure([]) ] });
}
}
}
}
export { Scrimp };