import * as monaco from 'monaco-editor' import type { LogWorker } from './worker/logWorker' export type WorkerAccessor = (...uris: monaco.Uri[]) => Promise export abstract class Adapter { constructor(protected _worker: (...uris: monaco.Uri[]) => Promise) {} } //#region DiagnosticsAdapter export interface ILanguageWorkerWithDiagnostics { doValidation(uri: string): Promise } export class DiagnosticsAdapter { protected readonly _disposables: monaco.IDisposable[] = [] private readonly _listener: { [uri: string]: monaco.IDisposable } = Object.create(null) constructor( private readonly _languageId: string, protected readonly _worker: WorkerAccessor, ) { const onModelAdd = (model: monaco.editor.IModel): void => { let modeId = model.getLanguageId() if (modeId !== this._languageId) { return } let handle: number this._listener[model.uri.toString()] = model.onDidChangeContent(() => { window.clearTimeout(handle) handle = window.setTimeout(() => this._doValidate(model.uri, modeId), 500) }) this._doValidate(model.uri, modeId) } const onModelRemoved = (model: monaco.editor.IModel): void => { monaco.editor.setModelMarkers(model, this._languageId, []) let uriStr = model.uri.toString() let listener = this._listener[uriStr] if (listener) { listener.dispose() delete this._listener[uriStr] } } this._disposables.push(monaco.editor.onDidCreateModel(onModelAdd)) this._disposables.push(monaco.editor.onWillDisposeModel(onModelRemoved)) this._disposables.push( monaco.editor.onDidChangeModelLanguage((event) => { onModelRemoved(event.model) onModelAdd(event.model) }), ) this._disposables.push({ dispose: () => { monaco.editor.getModels().forEach(onModelRemoved) for (let key in this._listener) { this._listener[key].dispose() } }, }) monaco.editor.getModels().forEach(onModelAdd) } public dispose(): void { this._disposables.forEach((d) => d && d.dispose()) this._disposables.length = 0 } private _doValidate(resource: monaco.Uri, languageId: string): void { this._worker(resource) .then((worker) => { return worker.doValidation(resource.toString()) }) .then((diagnostics) => { const markers = diagnostics let model = monaco.editor.getModel(resource) if (model && model.getLanguageId() === languageId) { monaco.editor.setModelMarkers(model, 'LogWorker', markers) } }) .then(undefined, (err) => { console.error(err) }) } } // --- inlay hints ---- export class InlayHintsAdapter extends Adapter implements monaco.languages.InlayHintsProvider { public async provideInlayHints( model: monaco.editor.ITextModel, range: monaco.Range, token: monaco.CancellationToken, ): Promise { const resource = model.uri const fileName = resource.toString() const start = model.getOffsetAt({ lineNumber: range.startLineNumber, column: range.startColumn, }) const end = model.getOffsetAt({ lineNumber: range.endLineNumber, column: range.endColumn, }) const worker = await this._worker(resource) if (model.isDisposed()) { return null } const logHints = await worker.provideInlayHints(fileName, start, end) const hints: monaco.languages.InlayHint[] = logHints.map((hint) => { return { ...hint, label: hint.label, position: hint.position, kind: hint.kind, } }) return { hints, dispose: () => {} } } }