@dodona/papyros
Version:
Scratchpad for multiple programming languages in the browser.
197 lines • 6.42 kB
JavaScript
import { StateField } from "@codemirror/state";
import { StateEffect } from "@codemirror/state";
import { gutter, GutterMarker } from "@codemirror/view";
import { EditorView } from "@codemirror/view";
import { appendClasses } from "../util/Rendering";
/**
* Helper class to create markers in the gutter
*/
class SimpleMarker extends GutterMarker {
constructor(
// Function to create the DOM element
createMarker) {
super();
this.createMarker = createMarker;
}
toDOM() {
return this.createMarker();
}
}
export class Gutters {
constructor(config) {
this.config = config;
this.effect = StateEffect.define();
this.state = StateField.define({
create: () => {
return new Map();
},
update: (current, transaction) => {
const updatedMap = new Map(current);
for (const e of transaction.effects) {
if (e.is(this.effect)) {
updatedMap.set(e.value.lineNr, e.value);
}
}
return updatedMap;
}
});
}
applyClasses(marker) {
const classes = { classNames: this.config.markerClasses };
appendClasses(classes, "_tw-px-1 papyros-gutter-marker");
marker.elementClass += classes.classNames;
return marker;
}
hasMarker(view, lineNr) {
const guttersInfo = view.state.field(this.state);
return guttersInfo.has(lineNr) && guttersInfo.get(lineNr).on;
}
/**
* Set a marker with the given info
* @param {EditorView} view View in which the Gutters live
* @param {Info} info Info used to render the marker
*/
setMarker(view, info) {
if (this.hasMarker(view, info.lineNr) !== info.on) {
view.dispatch({
effects: this.effect.of(info)
});
}
}
/**
* @param {EditorView} view The view in which the Gutters live
* @return {Set<number>} The 1-based line numbers with a breakpoint
*/
getMarkedLines(view) {
const markedLines = new Set();
const guttersInfo = view.state.field(this.state);
guttersInfo.forEach((info, lineNr) => {
if (info.on) {
markedLines.add(lineNr);
}
});
return markedLines;
}
/**
* @return {Extension} The Gutters as a CodeMirror Extension
*/
toExtension() {
// TODO correct type: https://github.com/codemirror/codemirror.next/issues/839
const handlers = {};
if (this.config.onClick) {
handlers["mousedown"] = (view, line) => {
const markings = view.state.field(this.state);
const lineNr = view.state.doc.lineAt(line.from).number;
const markerInfo = markings.get(lineNr);
// Line numbers start at 1
this.config.onClick(view, markerInfo);
};
}
return [
this.state,
gutter({
class: `cm-${this.config.name}-gutter`,
lineMarker: (view, line) => {
// Lookup whether the element should be drawn
const guttersInfo = view.state.field(this.state);
const lineNr = view.state.doc.lineAt(line.from).number;
if (guttersInfo.has(lineNr) && guttersInfo.get(lineNr).on) {
return this.applyClasses(this.marker(guttersInfo.get(lineNr)));
}
else {
return null;
}
},
lineMarkerChange: update => {
return update.startState.field(this.state) !== update.state.field(this.state);
},
initialSpacer: () => {
return this.applyClasses(this.marker({ lineNr: -1, on: true }));
},
domEventHandlers: handlers
}),
this.config.extraExtensions || []
];
}
}
/**
* Gutters to show and allow toggling of breakpoints
*/
export class BreakpointsGutter extends Gutters {
constructor() {
super({
name: "breakpoint",
onClick: (view, info) => {
info.on = !info.on;
this.setMarker(view, info);
},
extraExtensions: [
EditorView.baseTheme({
".cm-breakpoint-gutter .cm-gutterElement": {
color: "red",
paddingLeft: "5px",
cursor: "default"
}
})
]
});
}
marker() {
return new SimpleMarker(() => document.createTextNode("🔴"));
}
}
/**
* Gutters to show a checkmark for used input
*/
export class UsedInputGutters extends Gutters {
constructor() {
super({
name: "input"
});
}
marker(info) {
return new SimpleMarker(() => {
const node = document.createElement("div");
node.replaceChildren(document.createTextNode("✔"));
node.setAttribute("title", info.title);
// Text interface tells us that more complex node will be processed into Text nodes
return node;
});
}
}
class DebugMarker extends GutterMarker {
toDOM() {
const icon = document.createElement("i");
icon.classList.add("mdi", "mdi-arrow-right-bold", "mdi-18");
return icon;
}
}
/**
* shows the debugged line
*/
export class DebugLineGutter extends Gutters {
constructor() {
super({
name: "debugline",
extraExtensions: [
EditorView.baseTheme({
".cm-debugline-gutter .cm-gutterElement": {
lineHeight: "20px",
marginRight: "-5px",
fontSize: "18px"
}
})
]
});
this.activeLine = 1;
}
marker() {
return new DebugMarker();
}
markLine(view, lineNr) {
this.setMarker(view, { lineNr: this.activeLine, on: false });
this.setMarker(view, { lineNr, on: true });
this.activeLine = lineNr;
}
}
//# sourceMappingURL=Gutters.js.map