diff --git a/editors/vscode/src/completion.js b/editors/vscode/src/completion.js index 626ee3e4..151d82ab 100644 --- a/editors/vscode/src/completion.js +++ b/editors/vscode/src/completion.js @@ -570,6 +570,41 @@ function isInsideWatch(document, position) { return depth > 0; } +/** + * Whether an offset sits inside a `view { }` block. + * + * The language server owns completion there and returns a merged list, so this + * provider stands down to avoid VS Code concatenating two independent lists. + * Quotes are only tracked inside a tag: `
it's
` would otherwise open a + * string that never closes. + */ +function isInsideViewBlock(text, offset) { + const pattern = /\bview\s*\{/g; + let match; + while ((match = pattern.exec(text))) { + const start = match.index + match[0].length; + let depth = 1; + let inTag = false; + let quote = null; + let index = start; + for (; index < text.length && depth > 0; index += 1) { + const char = text[index]; + if (quote) { + if (char === quote) quote = null; + continue; + } + if (inTag && (char === '"' || char === "'")) quote = char; + else if (char === "<") inTag = true; + else if (char === ">") inTag = false; + else if (char === "{") depth += 1; + else if (char === "}") depth -= 1; + } + if (offset >= start && offset <= index) return true; + pattern.lastIndex = index; + } + return false; +} + function isAfterWatchKeyword(document, position) { const linePrefix = document.lineAt(position.line).text.slice(0, position.character); @@ -634,6 +669,8 @@ function addFunctionCompletions(items, document) { } function provideCompletionItems(document, position) { + if (isInsideViewBlock(document.getText(), document.offsetAt(position))) return []; + const items = []; const linePrefix = document.lineAt(position.line).text.slice(0, position.character); @@ -707,6 +744,7 @@ module.exports = { extractProps, extractRouteParams, extractStates, + isInsideViewBlock, provideCompletionItems, registerCompletionProvider, }; diff --git a/editors/vscode/src/extension.bundle.cjs b/editors/vscode/src/extension.bundle.cjs index 63a01f03..72c8be9f 100644 --- a/editors/vscode/src/extension.bundle.cjs +++ b/editors/vscode/src/extension.bundle.cjs @@ -1,4 +1,4 @@ -// WRN editor extension source hash: 875fa63e96381e6a0112442a3fce92717fb52e85f4e331e2b3e3487837e00a4d +// WRN editor extension source hash: 2012cf8721073e3d9df81af2d3207fdf070119fb3f85ed832ce23c3ee4307662 // WRN editor extension generator hash: 456d1d614e44e5fb1f19b784176c09cf2ade9b64ef73a17934c2698150b62728 "use strict"; var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports); diff --git a/editors/vscode/test/completion-scope.test.js b/editors/vscode/test/completion-scope.test.js new file mode 100644 index 00000000..b8033608 --- /dev/null +++ b/editors/vscode/test/completion-scope.test.js @@ -0,0 +1,32 @@ +"use strict"; + +const test = require("node:test"); +const assert = require("node:assert"); +const Module = require("node:module"); + +// Mock the vscode module for unit tests +const originalLoad = Module._load; +Module._load = function load(request, parent, isMain) { + if (request === "vscode") return {}; + return originalLoad.call(this, request, parent, isMain); +}; +const { isInsideViewBlock } = require("../src/completion.js"); +Module._load = originalLoad; + +const PAGE = `page Home { + view { +