@dodona/papyros
Version:
Scratchpad for multiple programming languages in the browser.
213 lines • 7.83 kB
JavaScript
import { ProgrammingLanguage } from "../ProgrammingLanguage";
import { t } from "../util/Util";
import { acceptCompletion, autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap } from "@codemirror/autocomplete";
import { defaultKeymap, history, historyKeymap, indentWithTab, insertBlankLine } from "@codemirror/commands";
import { javascript } from "@codemirror/lang-javascript";
import { python } from "@codemirror/lang-python";
import { bracketMatching, defaultHighlightStyle, foldGutter, indentOnInput, indentUnit, syntaxHighlighting } from "@codemirror/language";
import { highlightSelectionMatches, searchKeymap } from "@codemirror/search";
import { EditorState } from "@codemirror/state";
import { oneDarkHighlightStyle } from "@codemirror/theme-one-dark";
import { drawSelection, highlightActiveLine, highlightActiveLineGutter, highlightSpecialChars, keymap, lineNumbers, rectangularSelection, showPanel } from "@codemirror/view";
import { linter, lintGutter, lintKeymap } from "@codemirror/lint";
import { CodeMirrorEditor } from "./CodeMirrorEditor";
import { darkTheme } from "./DarkTheme";
import { TestCodeExtension } from "./TestCodeExtension";
import { DebugExtension } from "./DebugExtension";
const tabCompletionKeyMap = [{ key: "Tab", run: acceptCompletion }];
/**
* Component that provides useful features to users writing code
*/
export class CodeEditor extends CodeMirrorEditor {
/**
* Construct a new CodeEditor
* @param {Function} onRunRequest Callback for when the user wants to run the code
* @param {string} initialCode The initial code to display
* @param {number} indentLength The length in spaces for the indent unit
*/
constructor(onRunRequest, initialCode = "", indentLength = 4) {
super(new Set([
CodeEditor.PROGRAMMING_LANGUAGE, CodeEditor.INDENTATION, CodeEditor.DEBUGGING,
CodeEditor.PANEL, CodeEditor.AUTOCOMPLETION, CodeEditor.LINTING
]), {
classes: ["papyros-code-editor", "_tw-overflow-auto",
"_tw-border-solid", "_tw-border-gray-200", "_tw-border-2",
"_tw-rounded-lg", "dark:_tw-border-dark-mode-content"],
minHeight: "20vh",
maxHeight: "72vh",
theme: {}
});
this.debugExtension = new DebugExtension(this.editorView);
this.addExtension([
keymap.of([
{
key: "Mod-Enter", run: () => {
onRunRequest();
return true;
}
},
// The original Ctrl-Enter keybind gets assigned to Shift-Enter
{
key: "Shift-Enter", run: insertBlankLine
}
]),
...CodeEditor.getExtensions()
]);
this.setText(initialCode);
this.setIndentLength(indentLength);
this.testCodeExtension = new TestCodeExtension(this.editorView);
this.addExtension(this.testCodeExtension.toExtension());
this.debugMode = false;
}
set debugMode(value) {
if (value) {
this.reconfigure([CodeEditor.DEBUGGING, [
this.debugExtension.toExtension(),
]]);
this.debugExtension.reset();
}
else {
this.reconfigure([CodeEditor.DEBUGGING, [
highlightActiveLineGutter(),
lintGutter(),
highlightActiveLine()
]]);
}
}
set testCode(code) {
this.testCodeExtension.testCode = code;
}
getText() {
if (this.testCodeExtension) {
return this.testCodeExtension.getNonTestCode();
}
else {
return super.getText();
}
}
getCode() {
return super.getText();
}
setDarkMode(darkMode) {
let styleExtensions = [];
if (darkMode) {
styleExtensions = [darkTheme, syntaxHighlighting(oneDarkHighlightStyle)];
}
else {
styleExtensions = syntaxHighlighting(defaultHighlightStyle);
}
this.reconfigure([CodeMirrorEditor.STYLE, styleExtensions]);
}
/**
* @param {ProgrammingLanguage} language The language to use
*/
setProgrammingLanguage(language) {
this.reconfigure([CodeEditor.PROGRAMMING_LANGUAGE, CodeEditor.getLanguageSupport(language)]);
this.setPlaceholder(t("Papyros.code_placeholder", { programmingLanguage: language }));
}
/**
* @param {LintSource} lintSource Function to obtain linting results
*/
setLintingSource(lintSource) {
this.reconfigure([
CodeEditor.LINTING,
linter(lintSource)
]);
}
/**
* @param {number} indentLength The number of spaces to use for indentation
*/
setIndentLength(indentLength) {
this.reconfigure([CodeEditor.INDENTATION, indentUnit.of(CodeEditor.getIndentUnit(indentLength))]);
}
/**
* @param {HTMLElement} panel The panel to display at the bottom of the editor
*/
setPanel(panel) {
this.reconfigure([CodeEditor.PANEL, showPanel.of(() => {
return { dom: panel };
})]);
}
/**
* @param {number} indentLength The amount of spaces to use
* @return {string} The indentation unit to be used by CodeMirror
*/
static getIndentUnit(indentLength) {
return new Array(indentLength).fill(" ").join("");
}
/**
* @param {ProgrammingLanguage} language The language to support
* @return {LanguageSupport} CodeMirror LanguageSupport for the language
*/
static getLanguageSupport(language) {
switch (language) {
case ProgrammingLanguage.Python: {
return python();
}
case ProgrammingLanguage.JavaScript: {
return javascript();
}
default: {
throw new Error(`${language} is not yet supported.`);
}
}
}
/**
* - line numbers
* - special character highlighting
* - the undo history
* - a fold gutter
* - custom selection drawing
* - multiple selections
* - reindentation on input
* - bracket matching
* - bracket closing
* - autocompletion
* - rectangular selection
* - active line highlighting
* - active line gutter highlighting
* - selection match highlighting
* - gutter for linting
* Keymaps:
* - the default command bindings
* - bracket closing
* - searching
* - linting
* - completion
* - indenting with tab
* @return {Array<Extension} Default extensions to use
*/
static getExtensions() {
return [
lineNumbers(),
highlightSpecialChars(),
history(),
foldGutter(),
drawSelection(),
EditorState.allowMultipleSelections.of(true),
indentOnInput(),
bracketMatching(),
closeBrackets(),
autocompletion(),
rectangularSelection(),
highlightSelectionMatches(),
keymap.of([
...closeBracketsKeymap,
...defaultKeymap,
...searchKeymap,
...historyKeymap,
...completionKeymap,
...tabCompletionKeyMap,
...lintKeymap,
indentWithTab
]),
];
}
}
CodeEditor.PROGRAMMING_LANGUAGE = "programming_language";
CodeEditor.INDENTATION = "indentation";
CodeEditor.PANEL = "panel";
CodeEditor.AUTOCOMPLETION = "autocompletion";
CodeEditor.LINTING = "linting";
CodeEditor.DEBUGGING = "debugging";
//# sourceMappingURL=CodeEditor.js.map