From 8ca12ee61acc6be7b5b7549b30c44544979eb73c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 9 Dec 2025 20:48:51 +0000 Subject: [PATCH] Simplify JS worker integration using tokenization detection Remove virtual model approach and use Monaco's built-in tokenization to detect when cursor is in embedded JavaScript region. With nextEmbedded, Monaco automatically syncs model content to the JS worker, so we can call the worker directly on the model's URI instead of creating a separate virtual model. --- .../monaco-json-interpolation/src/index.ts | 146 ++++++------------ 1 file changed, 47 insertions(+), 99 deletions(-) diff --git a/packages/monaco-json-interpolation/src/index.ts b/packages/monaco-json-interpolation/src/index.ts index 02dfc4de..a28f9ba6 100644 --- a/packages/monaco-json-interpolation/src/index.ts +++ b/packages/monaco-json-interpolation/src/index.ts @@ -174,6 +174,7 @@ async function getJSONWorker(resource: monaco.Uri): Promise { type JSWorker = { getCompletionsAtPosition(uri: string, offset: number): Promise; + getCompletionEntryDetails(uri: string, offset: number, name: string): Promise; getQuickInfoAtPosition(uri: string, offset: number): Promise; getSignatureHelpItems(uri: string, offset: number): Promise; }; @@ -195,82 +196,37 @@ async function getJavaScriptWorker(resource: monaco.Uri): Promise= tokenStart && offset <= tokenEnd) { + // Check if this token is in an embedded JavaScript region + // The embedded JS tokens won't have our language's postfix + return !token.type.endsWith('.json-interpolation') && token.type !== ''; } } + currentOffset += lineContent.length + 1; // +1 for newline } - // Extract the expression (content between ${ and }) - const expressionStart = lastStart + 2; - const expression = text.substring(expressionStart, endOffset); - const offsetInExpression = offset - expressionStart; - - const startPos = model.getPositionAt(lastStart); - - return { - expression, - interpolationStart: lastStart, - offsetInExpression, - startPosition: { - lineNumber: startPos.lineNumber, - column: startPos.column - } - }; -} - -// --- Virtual model for JavaScript worker --- - -const VIRTUAL_JS_URI = monaco.Uri.parse('file:///interpolation-context.js'); -let virtualJsModel: monaco.editor.ITextModel | null = null; - -function getOrCreateVirtualJsModel(): monaco.editor.ITextModel { - if (!virtualJsModel || virtualJsModel.isDisposed()) { - virtualJsModel = monaco.editor.createModel('', 'javascript', VIRTUAL_JS_URI); - } - return virtualJsModel; + return false; } // --- Providers --- @@ -285,25 +241,22 @@ function registerProviders(defaults: LanguageServiceDefaultsImpl): void { position: monaco.Position, context: monaco.languages.CompletionContext ): Promise { - // Check if we're inside an interpolation - const interpContext = getInterpolationContext(model, position); + const offset = model.getOffsetAt(position); - if (interpContext) { + // Check if we're inside an interpolation using tokenization + if (isInsideInterpolation(model, offset)) { // Use JavaScript worker for completions inside interpolation - const jsWorker = await getJavaScriptWorker(VIRTUAL_JS_URI); + // Monaco syncs the model content automatically with nextEmbedded + const jsWorker = await getJavaScriptWorker(model.uri); if (!jsWorker) { return null; } try { - // Update the virtual model with the expression - const virtualModel = getOrCreateVirtualJsModel(); - virtualModel.setValue(interpContext.expression); - - // Get completions from JavaScript worker + // Get completions from JavaScript worker directly on the model's URI const info = await jsWorker.getCompletionsAtPosition( - VIRTUAL_JS_URI.toString(), - interpContext.offsetInExpression + model.uri.toString(), + offset ); if (!info || !info.entries) { @@ -378,25 +331,21 @@ function registerProviders(defaults: LanguageServiceDefaultsImpl): void { model: monaco.editor.ITextModel, position: monaco.Position ): Promise { - // Check if we're inside an interpolation - const interpContext = getInterpolationContext(model, position); + const offset = model.getOffsetAt(position); - if (interpContext) { + // Check if we're inside an interpolation using tokenization + if (isInsideInterpolation(model, offset)) { // Use JavaScript worker for hover inside interpolation - const jsWorker = await getJavaScriptWorker(VIRTUAL_JS_URI); + const jsWorker = await getJavaScriptWorker(model.uri); if (!jsWorker) { return null; } try { - // Update the virtual model with the expression - const virtualModel = getOrCreateVirtualJsModel(); - virtualModel.setValue(interpContext.expression); - - // Get quick info from JavaScript worker + // Get quick info from JavaScript worker directly on the model's URI const info = await jsWorker.getQuickInfoAtPosition( - VIRTUAL_JS_URI.toString(), - interpContext.offsetInExpression + model.uri.toString(), + offset ); if (!info) { @@ -483,23 +432,22 @@ function registerProviders(defaults: LanguageServiceDefaultsImpl): void { model: monaco.editor.ITextModel, position: monaco.Position ): Promise { - const interpContext = getInterpolationContext(model, position); - if (!interpContext) { + const offset = model.getOffsetAt(position); + + // Only provide signature help inside interpolations + if (!isInsideInterpolation(model, offset)) { return null; } - const jsWorker = await getJavaScriptWorker(VIRTUAL_JS_URI); + const jsWorker = await getJavaScriptWorker(model.uri); if (!jsWorker) { return null; } try { - const virtualModel = getOrCreateVirtualJsModel(); - virtualModel.setValue(interpContext.expression); - const info = await jsWorker.getSignatureHelpItems( - VIRTUAL_JS_URI.toString(), - interpContext.offsetInExpression + model.uri.toString(), + offset ); if (!info || !info.items || info.items.length === 0) { @@ -837,7 +785,7 @@ function setupDiagnostics(defaults: LanguageServiceDefaultsImpl): void { monaco.editor.onDidCreateModel(onModelAdd); monaco.editor.onWillDisposeModel(onModelRemoved); - monaco.editor.onDidChangeModelLanguage((event) => { + monaco.editor.onDidChangeModelLanguage((event: { model: monaco.editor.ITextModel }) => { onModelRemoved(event.model); onModelAdd(event.model); });