Skip to content

Commit

Permalink
Initial copy and package install
Browse files Browse the repository at this point in the history
  • Loading branch information
allisonking committed Mar 3, 2025
1 parent ea1093b commit 970f079
Show file tree
Hide file tree
Showing 9 changed files with 1,693 additions and 2 deletions.
31 changes: 31 additions & 0 deletions packages/context-editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,37 @@
},
"dependencies": {
"@benrbray/prosemirror-math": "^1.0.0",
"@codemirror/autocomplete": "^6.18.6",
"@codemirror/commands": "^6.8.0",
"@codemirror/lang-cpp": "^6.0.2",
"@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-java": "^6.0.1",
"@codemirror/lang-javascript": "^6.2.3",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-lezer": "^6.0.1",
"@codemirror/lang-markdown": "^6.3.2",
"@codemirror/lang-php": "^6.0.1",
"@codemirror/lang-python": "^6.1.7",
"@codemirror/lang-rust": "^6.0.1",
"@codemirror/lang-sql": "^6.8.0",
"@codemirror/lang-wast": "^6.0.2",
"@codemirror/lang-xml": "^6.1.0",
"@codemirror/language": "^6.10.8",
"@codemirror/search": "^6.5.10",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.36.4",
"@lezer/cpp": "^1.1.2",
"@lezer/css": "^1.1.10",
"@lezer/html": "^1.3.10",
"@lezer/java": "^1.1.3",
"@lezer/javascript": "^1.4.21",
"@lezer/json": "^1.0.3",
"@lezer/lr": "^1.4.2",
"@lezer/markdown": "^1.4.2",
"@lezer/python": "^1.1.15",
"@lezer/rust": "^1.0.2",
"@lezer/xml": "^1.0.6",
"@nytimes/react-prosemirror": "^1.0.0",
"@prosemirror-adapter/react": "^0.4.0",
"deepmerge": "^4.3.1",
Expand Down
210 changes: 210 additions & 0 deletions packages/context-editor/src/plugins/code/codeMirrorBlockNodeView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/**
* Based on https://gitlab.com/emergence-engineering/prosemirror-codemirror-block/-/blob/main/src/codeMirrorBlockNodeView.ts
*
* Differences:
* * Includes `codeblock-wrapper` as dom
* * No theme or copy button
*/

import type { NodeView } from "prosemirror-view";

import {
autocompletion,
closeBrackets,
closeBracketsKeymap,
completionKeymap,
} from "@codemirror/autocomplete";
import { defaultKeymap, indentWithTab } from "@codemirror/commands";
import {
bracketMatching,
defaultHighlightStyle,
foldGutter,
foldKeymap,
indentOnInput,
syntaxHighlighting,
} from "@codemirror/language";
import { highlightSelectionMatches, selectNextOccurrence } from "@codemirror/search";
import { Compartment, EditorState } from "@codemirror/state";
import {
drawSelection,
EditorView,
highlightActiveLine,
highlightActiveLineGutter,
keymap,
lineNumbers,
rectangularSelection,
} from "@codemirror/view";
import { exitCode, selectAll } from "prosemirror-commands";
import { Node } from "prosemirror-model";
import { EditorView as PMEditorView } from "prosemirror-view";

import type { CodeBlockSettings } from "./types";
import {
backspaceHandler,
computeChange,
forwardSelection,
maybeEscape,
setMode,
valueChanged,
} from "./utils";

export const codeMirrorBlockNodeView = (settings: CodeBlockSettings) => {
return (pmNode: Node, view: PMEditorView, getPos: (() => number) | boolean): NodeView => {
let node = pmNode;
let updating = false;
const wrap = document.createElement("pre");
wrap.className = "codeblock-wrapper";
const dom = document.createElement("code");
wrap.append(dom);
dom.className = "codeblock-root";
const languageConf = new Compartment();
const state = EditorState.create({
extensions: [
EditorState.readOnly.of(settings.readOnly),
EditorView.editable.of(!settings.readOnly),
lineNumbers(),
highlightActiveLineGutter(),
foldGutter(),
bracketMatching(),
closeBrackets(),
highlightSelectionMatches(),
autocompletion(),
rectangularSelection(),
drawSelection({ cursorBlinkRate: 1000 }),
EditorState.allowMultipleSelections.of(true),
highlightActiveLine(),
syntaxHighlighting(defaultHighlightStyle),
languageConf.of([]),
indentOnInput(),
EditorView.domEventHandlers({
blur(event, cmView) {
cmView.dispatch({ selection: { anchor: 0 } });
},
}),
keymap.of([
{ key: "Mod-d", run: selectNextOccurrence, preventDefault: true },
{
key: "ArrowUp",
run: (cmView) => maybeEscape("line", -1, cmView, view, getPos),
},
{
key: "ArrowLeft",
run: (cmView) => maybeEscape("char", -1, cmView, view, getPos),
},
{
key: "ArrowDown",
run: (cmView) => maybeEscape("line", 1, cmView, view, getPos),
},
{
key: "ArrowRight",
run: (cmView) => maybeEscape("char", 1, cmView, view, getPos),
},
{
key: "Mod-z",
run: () => settings.undo?.(view.state, view.dispatch) || true,
shift: () => settings.redo?.(view.state, view.dispatch) || true,
},
{
key: "Mod-y",
run: () => settings.redo?.(view.state, view.dispatch) || true,
},
{ key: "Backspace", run: (cmView) => backspaceHandler(view, cmView) },
{
key: "Mod-Backspace",
run: (cmView) => backspaceHandler(view, cmView),
},
{
key: "Mod-a",
run: () => {
const result = selectAll(view.state, view.dispatch);
view.focus();
return result;
},
},
{
key: "Enter",
run: (cmView) => {
const sel = cmView.state.selection.main;
if (
cmView.state.doc.line(cmView.state.doc.lines).text === "" &&
sel.from === sel.to &&
sel.from === cmView.state.doc.length
) {
exitCode(view.state, view.dispatch);
view.focus();
return true;
}
return false;
},
},
...defaultKeymap,
...foldKeymap,
...closeBracketsKeymap,
...completionKeymap,
indentWithTab,
]),
...(settings.theme ? settings.theme : []),
],
doc: node.textContent,
});

const codeMirrorView = new EditorView({
state,
dispatch: (tr) => {
codeMirrorView.update([tr]);
if (!updating) {
const textUpdate = tr.state.toJSON().doc;
valueChanged(textUpdate, node, getPos, view);
forwardSelection(codeMirrorView, view, getPos);
}
},
});
dom.append(codeMirrorView.dom);

const selectDeleteCB = settings.createSelect(settings, dom, node, view, getPos);
setMode(node.attrs.lang, codeMirrorView, settings, languageConf);

return {
dom: wrap,
selectNode() {
codeMirrorView.focus();
},
stopEvent: (e: Event) => settings.stopEvent(e, node, getPos, view, dom),
setSelection: (anchor, head) => {
codeMirrorView.focus();
forwardSelection(codeMirrorView, view, getPos);
updating = true;
codeMirrorView.dispatch({
selection: { anchor, head },
});
updating = false;
},
update: (updateNode) => {
if (updateNode.type.name !== node.type.name) return false;
if (updateNode.attrs.lang !== node.attrs.lang)
setMode(updateNode.attrs.lang, codeMirrorView, settings, languageConf);
const oldNode = node;
node = updateNode;
const change = computeChange(codeMirrorView.state.doc.toString(), node.textContent);
if (change) {
updating = true;
codeMirrorView.dispatch({
changes: {
from: change.from,
to: change.to,
insert: change.text,
},
selection: { anchor: change.from + change.text.length },
});
updating = false;
}
settings.updateSelect(settings, dom, updateNode, view, getPos, oldNode);
return true;
},
ignoreMutation: () => true,
destroy: () => {
selectDeleteCB();
},
};
};
};
82 changes: 82 additions & 0 deletions packages/context-editor/src/plugins/code/defaults.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Based on https://gitlab.com/emergence-engineering/prosemirror-codemirror-block/-/blob/main/src/defaults.ts
* Differences:
* * No theme
* * No copy button
*/

import { Node } from "prosemirror-model";
import { EditorView } from "prosemirror-view";

import type { CodeBlockSettings } from "./types";

export const defaultCreateSelect = (
settings: CodeBlockSettings,
dom: HTMLElement,
node: Node,
view: EditorView,
getPos: (() => number) | boolean
) => {
if (!settings.languageLoaders) return () => {};
const { languageLoaders } = settings;
const select = document.createElement("select");
select.className = "codeblock-select";
const noneOption = document.createElement("option");
noneOption.value = "none";
noneOption.textContent = settings.languageNameMap?.none || "none";
select.append(noneOption);
Object.keys(languageLoaders)
.sort()
.forEach((lang) => {
if (settings.languageWhitelist && !settings.languageWhitelist.includes(lang)) return;
const option = document.createElement("option");
option.value = lang;
option.textContent = settings.languageNameMap?.[lang] || lang;
select.append(option);
});
select.value = node.attrs.lang || "none";
dom.prepend(select);
select.onchange = async (e) => {
if (!(e.target instanceof HTMLSelectElement)) return;
const lang = e.target.value;
if (typeof getPos === "function") {
view.dispatch(
view.state.tr.setNodeMarkup(
getPos(),
undefined,
{
...node.attrs,
lang,
},
node.marks
)
);
}
};
// Delete code.
return () => {};
};

const defaultUpdateSelect = (
settings: CodeBlockSettings,
dom: HTMLElement,
node: Node,
view: EditorView,
getPos: (() => number) | boolean,
oldNode: Node
) => {
if (oldNode.attrs.lang !== node.attrs.lang) {
const select = dom.querySelector(".codeblock-select");
if (!(select instanceof HTMLSelectElement)) return;
select.value = node.attrs.lang || "none";
}
};

const defaultStopEvent = () => true;

export const defaultSettings: CodeBlockSettings = {
createSelect: defaultCreateSelect,
updateSelect: defaultUpdateSelect,
stopEvent: defaultStopEvent,
readOnly: false,
};
24 changes: 24 additions & 0 deletions packages/context-editor/src/plugins/code/languageLoaders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Based on https://gitlab.com/emergence-engineering/prosemirror-codemirror-block/-/blob/main/src/languageLoaders.ts
*
* Differences:
* * No legacy languages
*/

import type { LanguageLoaders } from "./types";

const languageLoaders: LanguageLoaders = {
cpp: () => import("@codemirror/lang-cpp").then((i) => i.cpp()),
css: () => import("@codemirror/lang-css").then((i) => i.css()),
html: () => import("@codemirror/lang-html").then((i) => i.html()),
sql: () => import("@codemirror/lang-sql").then((i) => i.sql()),
xml: () => import("@codemirror/lang-xml").then((i) => i.xml()),
javascript: () => import("@codemirror/lang-javascript").then((i) => i.javascript()),
java: () => import("@codemirror/lang-java").then((i) => i.java()),
json: () => import("@codemirror/lang-json").then((i) => i.json()),
markdown: () => import("@codemirror/lang-markdown").then((i) => i.markdown()),
python: () => import("@codemirror/lang-python").then((i) => i.python()),
rust: () => import("@codemirror/lang-rust").then((i) => i.rust()),
};

export default languageLoaders;
21 changes: 21 additions & 0 deletions packages/context-editor/src/plugins/code/languages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Based on https://gitlab.com/emergence-engineering/prosemirror-codemirror-block/-/blob/main/src/languages.ts
*
* Differences:
* * No LegacyLanguages
* * const array instead of enum
*/

export const CodeBlockLanguages = [
"javascript",
"html",
"css",
"sql",
"python",
"rust",
"xml",
"json",
"markdown",
"java",
"cpp",
] as const;
Loading

0 comments on commit 970f079

Please sign in to comment.