I have a web application containing a Monaco Editor. Previously, I used react-monaco-editor, now for some reasons, I'm thinking of switching to @monaco-editor/react.
With react-monaco-editor, I was able to code a DiagnosticsAdapter
class to provide real-time error checking and suggestions in a code editor. When a user types code into the editor, the adapter will validate the code and provide feedback on any issues it finds.
Now, with @monaco-editor/react, the DiagnosticsAdapter
does not work anymore: code in onModelAdd
and code in onModelRemoved
is never executed.
Does anyone know how to set up these listeners for @monaco-editor/react and make DiagnosticsAdapter
work?
Here is CodeSandBox: https://codesandbox.io/s/aged-voice-5rz8z2?file=/src/App.js:0-542
Here is language-feature.ts
:
import * as monaco from "monaco-editor";
export class DiagnosticsAdapter {
private _disposables: monaco.IDisposable[] = [];
private _listener = Object.create(null);
constructor() {
console.log("herehere in constructor");
const onModelAdd = (model: monaco.editor.IModel): void => {
console.log("herehere onModelAdd");
let handle;
const changeSubscription = model.onDidChangeContent(() => {
clearTimeout(handle);
handle = setTimeout(() => console.log("call validation"), 500);
});
this._listener[model.uri.toString()] = {
dispose() {
changeSubscription.dispose();
clearTimeout(handle);
}
};
console.log("call vailidation");
};
const onModelRemoved = (model: monaco.editor.IModel): void => {
console.log("herehere onModelRemoved");
monaco.editor.setModelMarkers(model, "abc", []);
const key = model.uri.toString();
if (this._listener[key]) {
this._listener[key].dispose();
delete this._listener[key];
}
};
console.log("herehere 5");
this._disposables.push(monaco.editor.onDidCreateModel(onModelAdd));
console.log("herehere 6");
this._disposables.push(monaco.editor.onWillDisposeModel(onModelRemoved));
console.log("herehere 7");
this._disposables.push(
monaco.editor.onDidChangeModelLanguage((event) => {
console.log("herehere 8");
onModelRemoved(event.model);
console.log("herehere 9");
onModelAdd(event.model);
})
);
console.log("herehere 10");
this._disposables.push({
dispose() {
for (const model of monaco.editor.getModels()) {
onModelRemoved(model);
}
}
});
console.log("herehere 11");
monaco.editor.getModels().forEach(onModelAdd);
}
public dispose(): void {
this._disposables.forEach((d) => d && d.dispose());
this._disposables = [];
}
}
Here is App.js
:
import React from "react";
import Editor from "@monaco-editor/react";
import { DiagnosticsAdapter } from "./language-feature";
function App() {
function handleEditorDidMount(editor, monaco) {
monaco.languages.register({ id: "mySpecialLanguage" });
let x = new DiagnosticsAdapter();
}
return (
<div className="App">
<Editor
height="90vh"
defaultLanguage="mySpecialLanguage"
defaultValue="// some comment"
onMount={handleEditorDidMount}
/>
</div>
);
}
export default App;
To manipulate models with @monaco-editor/react
, you should use the handleEditorDidMount
method to access the editor instance and the editor object itself, which has the methods you need for handling models.
Here's an example:
function App() {
function handleEditorDidMount(editor, monaco) {
monaco.languages.register({ id: "mySpecialLanguage" });
// Initialize the DiagnosticAdapter here.
let diagnosticAdapter = new DiagnosticsAdapter(editor, monaco);
}
return (
<div className="App">
<Editor
height="90vh"
defaultLanguage="mySpecialLanguage"
defaultValue="// some comment"
onMount={handleEditorDidMount}
/>
</div>
);
}
export default App;
Here's an updated DiagnosticsAdapter class:
import * as monaco from "monaco-editor";
export class DiagnosticsAdapter {
private _disposables: monaco.IDisposable[] = [];
private _listener = Object.create(null);
private _editor: monaco.editor.IStandaloneCodeEditor;
private _monaco: typeof monaco;
constructor(editor: monaco.editor.IStandaloneCodeEditor, monaco: typeof monaco) {
this._editor = editor;
this._monaco = monaco;
console.log("herehere in constructor");
const onModelAdd = (model: monaco.editor.IModel): void => {
console.log("herehere onModelAdd");
let handle;
const changeSubscription = model.onDidChangeContent(() => {
clearTimeout(handle);
handle = setTimeout(() => console.log("call validation"), 500);
});
this._listener[model.uri.toString()] = {
dispose() {
changeSubscription.dispose();
clearTimeout(handle);
}
};
console.log("call vailidation");
};
const onModelRemoved = (model: monaco.editor.IModel): void => {
console.log("herehere onModelRemoved");
this._monaco.editor.setModelMarkers(model, "abc", []);
const key = model.uri.toString();
if (this._listener[key]) {
this._listener[key].dispose();
delete this._listener[key];
}
};
// Get the model for the current editor instance.
let model = this._editor.getModel();
if (model) {
onModelAdd(model);
}
console.log("herehere 5");
this._disposables.push(model.onWillDispose(() => onModelRemoved(model)));
console.log("herehere 6");
this._disposables.push(
this._editor.onDidChangeModel((event) => {
console.log("herehere 8");
onModelRemoved(event.oldModel);
console.log("herehere 9");
onModelAdd(event.newModel);
})
);
console.log("herehere 10");
this._disposables.push({
dispose() {
onModelRemoved(model);
}
});
console.log("herehere 11");
}
public dispose(): void {
this._disposables.forEach((d) => d && d.dispose());
this._disposables = [];
}
}
Edit: Note that editor.onDidChangeModel
only fires when the model associated with the editor changes, not when any model in the system changes.