mirror of
https://github.com/microsoft/monaco-editor.git
synced 2025-12-22 19:42:56 +01:00
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.
This commit is contained in:
parent
a96c488593
commit
8ca12ee61a
1 changed files with 47 additions and 99 deletions
|
|
@ -174,6 +174,7 @@ async function getJSONWorker(resource: monaco.Uri): Promise<JSONWorker | null> {
|
||||||
|
|
||||||
type JSWorker = {
|
type JSWorker = {
|
||||||
getCompletionsAtPosition(uri: string, offset: number): Promise<any>;
|
getCompletionsAtPosition(uri: string, offset: number): Promise<any>;
|
||||||
|
getCompletionEntryDetails(uri: string, offset: number, name: string): Promise<any>;
|
||||||
getQuickInfoAtPosition(uri: string, offset: number): Promise<any>;
|
getQuickInfoAtPosition(uri: string, offset: number): Promise<any>;
|
||||||
getSignatureHelpItems(uri: string, offset: number): Promise<any>;
|
getSignatureHelpItems(uri: string, offset: number): Promise<any>;
|
||||||
};
|
};
|
||||||
|
|
@ -195,82 +196,37 @@ async function getJavaScriptWorker(resource: monaco.Uri): Promise<JSWorker | nul
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Interpolation context helpers ---
|
// --- Check if position is inside interpolation using tokenization ---
|
||||||
|
|
||||||
interface InterpolationContext {
|
function isInsideInterpolation(model: monaco.editor.ITextModel, offset: number): boolean {
|
||||||
/** The full expression inside ${...} */
|
// Tokenize the content and check if we're in an embedded JS region
|
||||||
expression: string;
|
const source = model.getValue();
|
||||||
/** Offset of ${ in the document */
|
const tokenLines = monaco.editor.tokenize(source, LANGUAGE_ID);
|
||||||
interpolationStart: number;
|
|
||||||
/** Offset within the expression where cursor is */
|
|
||||||
offsetInExpression: number;
|
|
||||||
/** Start line/column of the interpolation */
|
|
||||||
startPosition: { lineNumber: number; column: number };
|
|
||||||
}
|
|
||||||
|
|
||||||
function getInterpolationContext(
|
// Flatten tokens and find which one contains our offset
|
||||||
model: monaco.editor.ITextModel,
|
let currentOffset = 0;
|
||||||
position: monaco.Position
|
for (let lineIdx = 0; lineIdx < tokenLines.length; lineIdx++) {
|
||||||
): InterpolationContext | null {
|
const lineTokens = tokenLines[lineIdx];
|
||||||
const text = model.getValue();
|
const lineContent = model.getLineContent(lineIdx + 1);
|
||||||
const offset = model.getOffsetAt(position);
|
|
||||||
|
|
||||||
// Find the ${...} that contains the current position
|
for (let tokenIdx = 0; tokenIdx < lineTokens.length; tokenIdx++) {
|
||||||
const beforeCursor = text.substring(0, offset);
|
const token = lineTokens[tokenIdx];
|
||||||
const lastStart = beforeCursor.lastIndexOf('${');
|
const nextToken = lineTokens[tokenIdx + 1];
|
||||||
|
const tokenStart = currentOffset + token.offset;
|
||||||
|
const tokenEnd = nextToken
|
||||||
|
? currentOffset + nextToken.offset
|
||||||
|
: currentOffset + lineContent.length;
|
||||||
|
|
||||||
if (lastStart === -1) {
|
if (offset >= tokenStart && offset <= tokenEnd) {
|
||||||
return null;
|
// 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 !== '';
|
||||||
// Check if there's a closing } between ${ and cursor
|
|
||||||
const betweenStartAndCursor = text.substring(lastStart + 2, offset);
|
|
||||||
if (betweenStartAndCursor.includes('}')) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the closing }
|
|
||||||
let depth = 1;
|
|
||||||
let endOffset = offset;
|
|
||||||
for (let i = offset; i < text.length; i++) {
|
|
||||||
if (text[i] === '{') depth++;
|
|
||||||
else if (text[i] === '}') {
|
|
||||||
depth--;
|
|
||||||
if (depth === 0) {
|
|
||||||
endOffset = i;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
currentOffset += lineContent.length + 1; // +1 for newline
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the expression (content between ${ and })
|
return false;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Providers ---
|
// --- Providers ---
|
||||||
|
|
@ -285,25 +241,22 @@ function registerProviders(defaults: LanguageServiceDefaultsImpl): void {
|
||||||
position: monaco.Position,
|
position: monaco.Position,
|
||||||
context: monaco.languages.CompletionContext
|
context: monaco.languages.CompletionContext
|
||||||
): Promise<monaco.languages.CompletionList | null> {
|
): Promise<monaco.languages.CompletionList | null> {
|
||||||
// Check if we're inside an interpolation
|
const offset = model.getOffsetAt(position);
|
||||||
const interpContext = getInterpolationContext(model, position);
|
|
||||||
|
|
||||||
if (interpContext) {
|
// Check if we're inside an interpolation using tokenization
|
||||||
|
if (isInsideInterpolation(model, offset)) {
|
||||||
// Use JavaScript worker for completions inside interpolation
|
// 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) {
|
if (!jsWorker) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Update the virtual model with the expression
|
// Get completions from JavaScript worker directly on the model's URI
|
||||||
const virtualModel = getOrCreateVirtualJsModel();
|
|
||||||
virtualModel.setValue(interpContext.expression);
|
|
||||||
|
|
||||||
// Get completions from JavaScript worker
|
|
||||||
const info = await jsWorker.getCompletionsAtPosition(
|
const info = await jsWorker.getCompletionsAtPosition(
|
||||||
VIRTUAL_JS_URI.toString(),
|
model.uri.toString(),
|
||||||
interpContext.offsetInExpression
|
offset
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!info || !info.entries) {
|
if (!info || !info.entries) {
|
||||||
|
|
@ -378,25 +331,21 @@ function registerProviders(defaults: LanguageServiceDefaultsImpl): void {
|
||||||
model: monaco.editor.ITextModel,
|
model: monaco.editor.ITextModel,
|
||||||
position: monaco.Position
|
position: monaco.Position
|
||||||
): Promise<monaco.languages.Hover | null> {
|
): Promise<monaco.languages.Hover | null> {
|
||||||
// Check if we're inside an interpolation
|
const offset = model.getOffsetAt(position);
|
||||||
const interpContext = getInterpolationContext(model, position);
|
|
||||||
|
|
||||||
if (interpContext) {
|
// Check if we're inside an interpolation using tokenization
|
||||||
|
if (isInsideInterpolation(model, offset)) {
|
||||||
// Use JavaScript worker for hover inside interpolation
|
// Use JavaScript worker for hover inside interpolation
|
||||||
const jsWorker = await getJavaScriptWorker(VIRTUAL_JS_URI);
|
const jsWorker = await getJavaScriptWorker(model.uri);
|
||||||
if (!jsWorker) {
|
if (!jsWorker) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Update the virtual model with the expression
|
// Get quick info from JavaScript worker directly on the model's URI
|
||||||
const virtualModel = getOrCreateVirtualJsModel();
|
|
||||||
virtualModel.setValue(interpContext.expression);
|
|
||||||
|
|
||||||
// Get quick info from JavaScript worker
|
|
||||||
const info = await jsWorker.getQuickInfoAtPosition(
|
const info = await jsWorker.getQuickInfoAtPosition(
|
||||||
VIRTUAL_JS_URI.toString(),
|
model.uri.toString(),
|
||||||
interpContext.offsetInExpression
|
offset
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!info) {
|
if (!info) {
|
||||||
|
|
@ -483,23 +432,22 @@ function registerProviders(defaults: LanguageServiceDefaultsImpl): void {
|
||||||
model: monaco.editor.ITextModel,
|
model: monaco.editor.ITextModel,
|
||||||
position: monaco.Position
|
position: monaco.Position
|
||||||
): Promise<monaco.languages.SignatureHelpResult | null> {
|
): Promise<monaco.languages.SignatureHelpResult | null> {
|
||||||
const interpContext = getInterpolationContext(model, position);
|
const offset = model.getOffsetAt(position);
|
||||||
if (!interpContext) {
|
|
||||||
|
// Only provide signature help inside interpolations
|
||||||
|
if (!isInsideInterpolation(model, offset)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsWorker = await getJavaScriptWorker(VIRTUAL_JS_URI);
|
const jsWorker = await getJavaScriptWorker(model.uri);
|
||||||
if (!jsWorker) {
|
if (!jsWorker) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const virtualModel = getOrCreateVirtualJsModel();
|
|
||||||
virtualModel.setValue(interpContext.expression);
|
|
||||||
|
|
||||||
const info = await jsWorker.getSignatureHelpItems(
|
const info = await jsWorker.getSignatureHelpItems(
|
||||||
VIRTUAL_JS_URI.toString(),
|
model.uri.toString(),
|
||||||
interpContext.offsetInExpression
|
offset
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!info || !info.items || info.items.length === 0) {
|
if (!info || !info.items || info.items.length === 0) {
|
||||||
|
|
@ -837,7 +785,7 @@ function setupDiagnostics(defaults: LanguageServiceDefaultsImpl): void {
|
||||||
|
|
||||||
monaco.editor.onDidCreateModel(onModelAdd);
|
monaco.editor.onDidCreateModel(onModelAdd);
|
||||||
monaco.editor.onWillDisposeModel(onModelRemoved);
|
monaco.editor.onWillDisposeModel(onModelRemoved);
|
||||||
monaco.editor.onDidChangeModelLanguage((event) => {
|
monaco.editor.onDidChangeModelLanguage((event: { model: monaco.editor.ITextModel }) => {
|
||||||
onModelRemoved(event.model);
|
onModelRemoved(event.model);
|
||||||
onModelAdd(event.model);
|
onModelAdd(event.model);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue