Files
kandev/apps/web/vitest.monaco-editor.ts
2026-08-28 17:04:34 +00:00

175 lines
4.7 KiB
TypeScript

// @ts-expect-error -- Monaco does not publish declarations for its internal URI module.
import { URI } from "monaco-editor/esm/vs/base/common/uri.js";
import type { Uri as MonacoUri } from "monaco-editor";
export const __KANDEV_VITEST_STUB__ = true;
type StubUri = { toString(): string };
type StubPosition = { lineNumber: number; column: number };
type StubRange = {
startLineNumber: number;
startColumn: number;
endLineNumber: number;
endColumn: number;
};
type StubModel = {
uri: StubUri;
setValue(value: string): void;
getValue(): string;
getLineContent(lineNumber: number): string;
getWordUntilPosition(position: StubPosition): {
startColumn: number;
endColumn: number;
word: string;
};
getValueInRange(range: StubRange): string;
onDidChangeContent(listener: () => void): { dispose(): void };
dispose(): void;
};
const noop = (..._args: unknown[]) => undefined;
const disposable = (..._args: unknown[]) => ({ dispose: noop });
const createLanguageDefaults = () => ({
setCompilerOptions: noop,
setDiagnosticsOptions: noop,
});
const models = new Map<string, StubModel>();
const modelMarkers = new Map<string, unknown[]>();
let nextModelId = 0;
function modelKey(uri: StubUri): string {
return uri.toString();
}
function nextModelUri(): StubUri {
nextModelId += 1;
return URI.parse(`inmemory://vitest/model-${nextModelId}`) as StubUri;
}
function createModel(value = "", _language?: string, uri = nextModelUri()): StubModel {
let currentValue = value;
const listeners = new Set<() => void>();
const key = modelKey(uri);
const model: StubModel = {
uri,
setValue(nextValue) {
currentValue = nextValue;
for (const listener of listeners) listener();
},
getValue() {
return currentValue;
},
getLineContent(lineNumber) {
return currentValue.split(/\r?\n/)[lineNumber - 1] ?? "";
},
getWordUntilPosition({ lineNumber, column }) {
const line = currentValue.split(/\r?\n/)[lineNumber - 1] ?? "";
const beforeCursor = line.slice(0, column - 1);
const match = beforeCursor.match(/[\w$]+$/);
const word = match?.[0] ?? "";
return { startColumn: column - word.length, endColumn: column, word };
},
getValueInRange(range) {
const lines = currentValue
.split(/\r?\n/)
.slice(range.startLineNumber - 1, range.endLineNumber);
if (lines.length === 0) return "";
lines[0] = lines[0].slice(range.startColumn - 1);
const lastLine = lines.length - 1;
lines[lastLine] = lines[lastLine].slice(0, range.endColumn - 1);
return lines.join("\n");
},
onDidChangeContent(listener) {
listeners.add(listener);
return { dispose: () => listeners.delete(listener) };
},
dispose() {
models.delete(key);
for (const markerKey of modelMarkers.keys()) {
if (markerKey.startsWith(`${key}:`)) modelMarkers.delete(markerKey);
}
},
};
models.set(key, model);
return model;
}
function getModel(uri: StubUri): StubModel | null {
return models.get(modelKey(uri)) ?? null;
}
function setModelMarkers(model: StubModel, owner: string, markers: unknown[]): void {
const key = `${modelKey(model.uri)}:${owner}`;
if (markers.length === 0) {
modelMarkers.delete(key);
return;
}
modelMarkers.set(key, [...markers]);
}
function getModelMarkers(filter: { resource?: StubUri } = {}): unknown[] {
const resourceKey = filter.resource ? `${modelKey(filter.resource)}:` : null;
return [...modelMarkers.entries()]
.filter(([key]) => resourceKey === null || key.startsWith(resourceKey))
.flatMap(([, markers]) => markers);
}
const completionItemKind = {
Method: 0,
Function: 1,
Constructor: 2,
Field: 3,
Variable: 4,
Class: 5,
Struct: 6,
Interface: 7,
Module: 8,
Property: 9,
Event: 10,
Operator: 11,
Unit: 12,
Value: 13,
Constant: 14,
Enum: 15,
EnumMember: 16,
Keyword: 17,
Text: 18,
Color: 19,
File: 20,
Reference: 21,
Folder: 23,
TypeParameter: 24,
Snippet: 28,
};
export const Uri = URI as typeof MonacoUri;
export const editor = {
defineTheme: noop,
registerEditorOpener: disposable,
createModel,
getModels: () => [...models.values()],
getModel,
setModelMarkers,
getModelMarkers,
};
export const languages = {
CompletionItemKind: completionItemKind,
registerCompletionItemProvider: disposable,
registerDefinitionProvider: disposable,
registerHoverProvider: disposable,
registerReferenceProvider: disposable,
registerSignatureHelpProvider: disposable,
typescript: {
javascriptDefaults: createLanguageDefaults(),
typescriptDefaults: createLanguageDefaults(),
JsxEmit: { ReactJSX: 4 },
ModuleKind: { ESNext: 99 },
ModuleResolutionKind: { NodeJs: 2 },
ScriptTarget: { ESNext: 99 },
},
};