Go with react-codemirror lib

This commit is contained in:
Silvano Cerza 2025-02-20 10:30:22 +01:00
parent d1b980f031
commit 6ff48b9517
5 changed files with 132 additions and 64 deletions

View file

@ -27,6 +27,7 @@
"@codemirror/lang-markdown": "^6.3.2",
"@codemirror/state": "^6.5.2",
"@codemirror/view": "^6.36.2",
"@uiw/react-codemirror": "^4.23.8",
"codemirror": "^6.0.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"

View file

@ -17,6 +17,9 @@ importers:
'@codemirror/view':
specifier: ^6.36.2
version: 6.36.2
'@uiw/react-codemirror':
specifier: ^4.23.8
version: 4.23.8(@babel/runtime@7.26.9)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.9)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.2)(codemirror@6.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
codemirror:
specifier: ^6.0.1
version: 6.0.1
@ -68,6 +71,10 @@ packages:
resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==}
engines: {node: '>=6.9.0'}
'@babel/runtime@7.26.9':
resolution: {integrity: sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==}
engines: {node: '>=6.9.0'}
'@codemirror/autocomplete@6.18.6':
resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==}
@ -98,6 +105,9 @@ packages:
'@codemirror/state@6.5.2':
resolution: {integrity: sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==}
'@codemirror/theme-one-dark@6.1.2':
resolution: {integrity: sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==}
'@codemirror/view@6.36.2':
resolution: {integrity: sha512-DZ6ONbs8qdJK0fdN7AB82CgI6tYXf4HWk1wSVa0+9bhVznCuuvhQtX8bFBoy3dv8rZSQqUd8GvhVAcielcidrA==}
@ -420,6 +430,28 @@ packages:
resolution: {integrity: sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@uiw/codemirror-extensions-basic-setup@4.23.8':
resolution: {integrity: sha512-XJR/8AEVcE7ufy1BhW2nCN9qSVDYEdCtYLfvhaMwl6Q3qcaYYCGE2K5QbFCy7LsdP/3uZKvc1OskuqatoOPdhQ==}
peerDependencies:
'@codemirror/autocomplete': '>=6.0.0'
'@codemirror/commands': '>=6.0.0'
'@codemirror/language': '>=6.0.0'
'@codemirror/lint': '>=6.0.0'
'@codemirror/search': '>=6.0.0'
'@codemirror/state': '>=6.0.0'
'@codemirror/view': '>=6.0.0'
'@uiw/react-codemirror@4.23.8':
resolution: {integrity: sha512-/NA5Pj4MmXkLSlmlUm4yfEmRLntrNq5TkQKBSINn7TukXQ4fc+C6Bk0U60Qa4rkvCSgwzZdQ2exyP0t0+2GtqA==}
peerDependencies:
'@babel/runtime': '>=7.11.0'
'@codemirror/state': '>=6.0.0'
'@codemirror/theme-one-dark': '>=6.0.0'
'@codemirror/view': '>=6.0.0'
codemirror: '>=6.0.0'
react: '>=16.8.0'
react-dom: '>=16.8.0'
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@ -755,6 +787,9 @@ packages:
resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==}
engines: {node: '>=0.10.0'}
regenerator-runtime@0.14.1:
resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@ -861,6 +896,10 @@ snapshots:
'@babel/helper-validator-identifier@7.25.9':
optional: true
'@babel/runtime@7.26.9':
dependencies:
regenerator-runtime: 0.14.1
'@codemirror/autocomplete@6.18.6':
dependencies:
'@codemirror/language': 6.10.8
@ -940,6 +979,13 @@ snapshots:
dependencies:
'@marijn/find-cluster-break': 1.0.2
'@codemirror/theme-one-dark@6.1.2':
dependencies:
'@codemirror/language': 6.10.8
'@codemirror/state': 6.5.2
'@codemirror/view': 6.36.2
'@lezer/highlight': 1.2.1
'@codemirror/view@6.36.2':
dependencies:
'@codemirror/state': 6.5.2
@ -1237,6 +1283,33 @@ snapshots:
'@typescript-eslint/types': 8.19.0
eslint-visitor-keys: 4.2.0
'@uiw/codemirror-extensions-basic-setup@4.23.8(@codemirror/autocomplete@6.18.6)(@codemirror/commands@6.8.0)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.9)(@codemirror/state@6.5.2)(@codemirror/view@6.36.2)':
dependencies:
'@codemirror/autocomplete': 6.18.6
'@codemirror/commands': 6.8.0
'@codemirror/language': 6.10.8
'@codemirror/lint': 6.8.4
'@codemirror/search': 6.5.9
'@codemirror/state': 6.5.2
'@codemirror/view': 6.36.2
'@uiw/react-codemirror@4.23.8(@babel/runtime@7.26.9)(@codemirror/autocomplete@6.18.6)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.9)(@codemirror/state@6.5.2)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.2)(codemirror@6.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
dependencies:
'@babel/runtime': 7.26.9
'@codemirror/commands': 6.8.0
'@codemirror/state': 6.5.2
'@codemirror/theme-one-dark': 6.1.2
'@codemirror/view': 6.36.2
'@uiw/codemirror-extensions-basic-setup': 4.23.8(@codemirror/autocomplete@6.18.6)(@codemirror/commands@6.8.0)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.9)(@codemirror/state@6.5.2)(@codemirror/view@6.36.2)
codemirror: 6.0.1
react: 19.0.0
react-dom: 19.0.0(react@19.0.0)
transitivePeerDependencies:
- '@codemirror/autocomplete'
- '@codemirror/language'
- '@codemirror/lint'
- '@codemirror/search'
acorn-jsx@5.3.2(acorn@8.14.0):
dependencies:
acorn: 8.14.0
@ -1591,6 +1664,8 @@ snapshots:
react@19.0.0: {}
regenerator-runtime@0.14.1: {}
resolve-from@4.0.0: {}
reusify@1.0.4: {}

View file

@ -61,10 +61,10 @@ const DiffView: React.FC<DiffViewProps> = ({
<div>
<EditorPane
content={oldText}
highlightPlugin={createDiffHighlightPlugin({
highlightPluginSpec={{
diff: diffs,
isOriginal: true,
})}
}}
onEditorUpdate={handleEditorReady}
onContentChange={onOldTextChange}
/>
@ -79,10 +79,10 @@ const DiffView: React.FC<DiffViewProps> = ({
<div>
<EditorPane
content={newText}
highlightPlugin={createDiffHighlightPlugin({
highlightPluginSpec={{
diff: diffs,
isOriginal: false,
})}
}}
onContentChange={onNewTextChange}
/>
</div>

View file

@ -7,7 +7,7 @@ import {
} from "@codemirror/view";
import { DiffChunk } from "./diff";
interface DiffHighlightPluginSpec {
export interface DiffHighlightPluginSpec {
diff: DiffChunk[];
isOriginal: boolean;
}
@ -21,12 +21,6 @@ export function createDiffHighlightPlugin(spec: DiffHighlightPluginSpec) {
this.decorations = this.buildDecorations(view);
}
update(update: ViewUpdate) {
if (update.docChanged) {
this.decorations = this.buildDecorations(update.view);
}
}
buildDecorations(view: EditorView): DecorationSet {
const decorations = [];
const doc = view.state.doc;

View file

@ -1,71 +1,69 @@
import { markdown } from "@codemirror/lang-markdown";
import { EditorState, StateEffect } from "@codemirror/state";
import { EditorView, ViewPlugin } from "@codemirror/view";
import { EditorView, ViewUpdate } from "@codemirror/view";
import * as React from "react";
import {
DiffHighlightPluginSpec,
createDiffHighlightPlugin,
} from "./diff-highlight-plugin";
import CodeMirror from "@uiw/react-codemirror";
interface EditorPaneProps {
content: string;
highlightPlugin: ViewPlugin<any>;
highlightPluginSpec: DiffHighlightPluginSpec;
onEditorUpdate?: (editor: EditorView) => void;
onContentChange: (content: string) => void;
}
const EditorPane: React.FC<EditorPaneProps> = ({
content,
highlightPlugin,
highlightPluginSpec,
onEditorUpdate,
onContentChange,
}) => {
const editorRef = React.useRef<HTMLDivElement>(null);
const extensions = [
// basicSetup minus line numbers
// EditorView.lineWrapping,
EditorView.editable.of(true),
createDiffHighlightPlugin(highlightPluginSpec),
EditorView.theme({
"&": {
backgroundColor: "var(--background-primary)",
color: "var(--text-normal)",
},
".cm-content": {
padding: 0,
caretColor: "var(--caret-color)",
fontSize: "var(--font-text-size)",
fontFamily: "var(--font-text)",
},
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground": {
background: "var(--text-selection)",
},
"&.cm-focused": {
outline: 0,
},
"&.cm-focused .cm-cursor": {
borderLeftColor: "var(--text-normal)",
},
}),
markdown(),
];
React.useEffect(() => {
if (editorRef.current) {
const editorState = EditorState.create({
doc: content,
extensions: [
// basicSetup minus line numbers
// EditorView.lineWrapping,
EditorView.editable.of(true),
highlightPlugin,
EditorView.theme({
"&": {
backgroundColor: "var(--background-primary)",
color: "var(--text-normal)",
},
".cm-content": {
padding: 0,
},
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground": {
background: "var(--text-selection)",
},
"&.cm-focused .cm-cursor": {
borderLeftColor: "var(--text-normal)",
},
}),
markdown(),
EditorView.updateListener.of((update) => {
if (update.docChanged) {
onContentChange(update.state.doc.toString());
}
// We want to know when it updates in case the line height changes
onEditorUpdate?.(update.view);
}),
],
});
const editor = new EditorView({
parent: editorRef.current,
state: editorState,
});
return () => {
// Cleanup
editor.destroy();
};
}
}, []);
return <div ref={editorRef} />;
return (
<CodeMirror
value={content}
theme={"none"}
basicSetup={false}
extensions={extensions}
onUpdate={(viewUpdate: ViewUpdate) => {
if (viewUpdate.docChanged) {
onContentChange(viewUpdate.state.doc.toString());
}
// We want to know when it updates in case the line height changes
onEditorUpdate?.(viewUpdate.view);
}}
/>
);
};
export default EditorPane;