diff --git a/.vscode/cozy-and-windy/README.md b/.vscode/cozy-and-windy/README.md new file mode 100644 index 0000000..b2ae547 --- /dev/null +++ b/.vscode/cozy-and-windy/README.md @@ -0,0 +1,26 @@ +# Cozy and Windy + +Editor theme ported from the Rider scheme of the same name. + +It colors the editor surface, C/C++ syntax, and tape-atom DSL keywords +emitted by `local.tape-atom-syntax`. It does not change workbench chrome. + +## Install + +```powershell +cd C:\projects\Pikuma\ps1\.vscode\cozy-and-windy +npm run package +code --install-extension .\cozy-and-windy-0.1.0.vsix --force +``` + +Reload the window. Select **Cozy and Windy** as the color theme, or set +`workbench.colorTheme` to `Cozy and Windy` in the PS1 workspace settings. + +Keep `local.tape-atom-syntax` installed. This theme colors those token +types; it does not classify them. + +## Inspect + +Open `hello_camera.atom.c` and run **Developer: Inspect Editor Tokens and Scopes** +on `MipsAtom_`, an atom name, `atom_info`, `R_PrimCursor`, a `gte_*` call, +and a `mac_*` call. diff --git a/.vscode/cozy-and-windy/cozy-and-windy-0.1.0.vsix b/.vscode/cozy-and-windy/cozy-and-windy-0.1.0.vsix new file mode 100644 index 0000000..79a3381 Binary files /dev/null and b/.vscode/cozy-and-windy/cozy-and-windy-0.1.0.vsix differ diff --git a/.vscode/cozy-and-windy/package.json b/.vscode/cozy-and-windy/package.json new file mode 100644 index 0000000..065c993 --- /dev/null +++ b/.vscode/cozy-and-windy/package.json @@ -0,0 +1,25 @@ +{ + "name": "cozy-and-windy", + "displayName": "Cozy and Windy", + "description": "Editor theme ported from the Rider Cozy and Windy scheme. Colors C/C++ and tape-atom DSL keywords.", + "publisher": "local", + "version": "0.1.0", + "engines": { + "vscode": "^1.80.0" + }, + "categories": [ + "Themes" + ], + "scripts": { + "package": "npx --yes @vscode/vsce@3.6.1 package --allow-missing-repository --skip-license --out cozy-and-windy-0.1.0.vsix" + }, + "contributes": { + "themes": [ + { + "label": "Cozy and Windy", + "uiTheme": "vs-dark", + "path": "./themes/cozy-and-windy-color-theme.json" + } + ] + } +} diff --git a/.vscode/cozy-and-windy/themes/cozy-and-windy-color-theme.json b/.vscode/cozy-and-windy/themes/cozy-and-windy-color-theme.json new file mode 100644 index 0000000..f1d95de --- /dev/null +++ b/.vscode/cozy-and-windy/themes/cozy-and-windy-color-theme.json @@ -0,0 +1,122 @@ +{ + "name": "Cozy and Windy", + "type": "dark", + "semanticHighlighting": true, + "colors": { + "editor.background": "#121212", + "editor.foreground": "#D25A6EFF", + "editor.lineHighlightBackground": "#1c1c1c", + "editor.selectionBackground": "#164371", + "editor.selectionForeground": "#c8c8c8", + "editorLineNumber.foreground": "#43c3c3", + "editorLineNumber.activeForeground": "#00fff4", + "editorIndentGuide.background1": "#181818", + "editorIndentGuide.activeBackground1": "#202020", + "editorRuler.foreground": "#505050", + "editorGutter.background": "#121212", + "editorBracketMatch.background": "#3b514d", + "editor.foldBackground": "#0c0c0c", + "editor.wordHighlightBackground": "#121212", + "editor.wordHighlightStrongBackground": "#303030", + "editorCursor.foreground": "#00fff4", + "editorWhitespace.foreground": "#181818", + "editorLineHighlightBorder": "#1c1c1c", + "editorWidget.background": "#121212", + "editorSuggestWidget.background": "#2c334b", + "editorHoverWidget.background": "#2c334b" + }, + "semanticTokenColors": { + "comment": { "foreground": "#868686", "fontStyle": "italic" }, + "keyword": { "foreground": "#d8bd5b" }, + "string": { "foreground": "#d46a54" }, + "number": { "foreground": "#b5cea8" }, + "operator": { "foreground": "#00ffff" }, + "class": { "foreground": "#54a4d6" }, + "struct": { "foreground": "#54a4d6" }, + "enum": { "foreground": "#54a4d6" }, + "type": { "foreground": "#54a4d6" }, + "interface": { "foreground": "#7984ab" }, + "function": { "foreground": "#cccab5" }, + "method": { "foreground": "#6090a9" }, + "variable": { "foreground": "#bc966c" }, + "parameter": { "foreground": "#867660", "fontStyle": "underline" }, + "property": { "foreground": "#acb8c8" }, + "*.static": { "foreground": "#9e95c6" }, + "macro": { "foreground": "#5ea852" }, + "namespace": { "foreground": "#8e8e8e" }, + "typeParameter": { "foreground": "#b8d7a3" }, + "enumMember": { "foreground": "#a373b0" }, + "label": { "foreground": "#c8c8c8", "fontStyle": "bold" }, + "tapeAtomKeyword": { "foreground": "#d8bd5b", "fontStyle": "bold" }, + "tapeAtomName": { "foreground": "#cccab5", "fontStyle": "bold" }, + "tapeComponentKeyword": { "foreground": "#d68a36", "fontStyle": "bold" }, + "tapeComponentName": { "foreground": "#6090a9", "fontStyle": "bold" }, + "tapeAnnotation": { "foreground": "#d8bd5b" }, + "tapeBindType": { "foreground": "#54a4d6" }, + "tapePhase": { "foreground": "#b8d7a3", "fontStyle": "italic" }, + "tapeLabel": { "foreground": "#c8c8c8", "fontStyle": "bold" }, + "tapeCpuInstruction": { "foreground": "#6090a9" }, + "tapeGteInstruction": { "foreground": "#9e95c6" }, + "tapeGpuInstruction": { "foreground": "#bf7dac" }, + "tapeComponentInstruction": { "foreground": "#8baa5d" }, + "tapeGprRegister": { "foreground": "#bc966c" }, + "tapeCop2Register": { "foreground": "#9e95c6" }, + "tapeDuffleType": { "foreground": "#54a4d6" }, + "tapeAttribute": { "foreground": "#73a07c" }, + "tapeGprRegister.tapeRead": { "foreground": "#8baa5d", "fontStyle": "italic" }, + "tapeGprRegister.tapeWrite": { "foreground": "#d68a36", "fontStyle": "bold" }, + "tapeCop2Register.tapeRead": { "foreground": "#a373b0", "fontStyle": "italic" }, + "tapeCop2Register.tapeWrite": { "foreground": "#d46a54", "fontStyle": "bold" }, + "*.tapeAuto": { "fontStyle": "underline" }, + "tapeControlFlow": { "foreground": "#76ff7d", "fontStyle": "bold" }, + "tapeDelaySlot": { "foreground": "#ff5647" } + }, + "tokenColors": [ + { "scope": ["comment", "comment.block", "comment.line", "comment.block.documentation"], "settings": { "foreground": "#868686", "fontStyle": "italic" } }, + { "scope": ["keyword", "keyword.control", "keyword.other"], "settings": { "foreground": "#d8bd5b" } }, + { "scope": ["string", "string.quoted"], "settings": { "foreground": "#d46a54" } }, + { "scope": ["string.quoted.other"], "settings": { "foreground": "#d69d85" } }, + { "scope": ["constant.numeric"], "settings": { "foreground": "#b5cea8" } }, + { "scope": ["punctuation", "keyword.operator"], "settings": { "foreground": "#00ffff" } }, + { "scope": ["keyword.operator.overload"], "settings": { "foreground": "#00ff64" } }, + { "scope": ["entity.name.type", "entity.name.type.class", "entity.name.type.struct", "entity.name.type.enum"], "settings": { "foreground": "#54a4d6" } }, + { "scope": ["entity.name.type.interface"], "settings": { "foreground": "#7984ab" } }, + { "scope": ["entity.name.function"], "settings": { "foreground": "#cccab5" } }, + { "scope": ["entity.name.function.member"], "settings": { "foreground": "#6090a9" } }, + { "scope": ["variable.other.local"], "settings": { "foreground": "#bc966c" } }, + { "scope": ["variable.parameter"], "settings": { "foreground": "#867660", "fontStyle": "underline" } }, + { "scope": ["variable.other.property"], "settings": { "foreground": "#acb8c8" } }, + { "scope": ["variable.other.constant"], "settings": { "foreground": "#9e95c6" } }, + { "scope": ["variable.other.global", "variable.other.defaultLibrary"], "settings": { "foreground": "#bf7dac" } }, + { "scope": ["support.type", "support.function"], "settings": { "foreground": "#8baa5d" } }, + { "scope": ["meta.preprocessor"], "settings": { "foreground": "#5ea852" } }, + { "scope": ["variable.parameter.preprocessor"], "settings": { "foreground": "#636363" } }, + { "scope": ["keyword.control.directive"], "settings": { "foreground": "#d68a36" } }, + { "scope": ["entity.name.namespace"], "settings": { "foreground": "#8e8e8e" } }, + { "scope": ["entity.name.type.parameter"], "settings": { "foreground": "#b8d7a3" } }, + { "scope": ["variable.other.enummember"], "settings": { "foreground": "#a373b0" } }, + { "scope": ["entity.name.type.concept"], "settings": { "foreground": "#76ff7d" } }, + { "scope": ["entity.name.type.dependent"], "settings": { "foreground": "#448b5a", "fontStyle": "bold" } }, + { "scope": ["entity.name.label"], "settings": { "foreground": "#c8c8c8", "fontStyle": "bold" } }, + { "scope": ["invalid"], "settings": { "foreground": "#ff5647" } }, + { "scope": ["keyword.codetag.todo"], "settings": { "foreground": "#c10000", "fontStyle": "bold italic" } }, + { "scope": ["keyword.control.duffle.atom"], "settings": { "foreground": "#d8bd5b", "fontStyle": "bold" } }, + { "scope": ["entity.name.function.duffle.atom"], "settings": { "foreground": "#cccab5", "fontStyle": "bold" } }, + { "scope": ["keyword.control.duffle.component"], "settings": { "foreground": "#d68a36", "fontStyle": "bold" } }, + { "scope": ["entity.name.function.duffle.component"], "settings": { "foreground": "#6090a9", "fontStyle": "bold" } }, + { "scope": ["support.function.duffle.annotation"], "settings": { "foreground": "#d8bd5b" } }, + { "scope": ["entity.name.type.duffle.bind"], "settings": { "foreground": "#54a4d6" } }, + { "scope": ["entity.name.tag.duffle.phase"], "settings": { "foreground": "#b8d7a3", "fontStyle": "italic" } }, + { "scope": ["entity.name.label.duffle.atom"], "settings": { "foreground": "#c8c8c8", "fontStyle": "bold" } }, + { "scope": ["support.function.duffle.cpu"], "settings": { "foreground": "#6090a9" } }, + { "scope": ["support.function.duffle.gte"], "settings": { "foreground": "#9e95c6" } }, + { "scope": ["support.function.duffle.gpu"], "settings": { "foreground": "#bf7dac" } }, + { "scope": ["support.function.duffle.component"], "settings": { "foreground": "#8baa5d" } }, + { "scope": ["keyword.control.duffle.branch"], "settings": { "foreground": "#76ff7d", "fontStyle": "bold" } }, + { "scope": ["keyword.operator.duffle.delayslot"], "settings": { "foreground": "#ff5647" } }, + { "scope": ["variable.other.constant.duffle.gpr"], "settings": { "foreground": "#bc966c" } }, + { "scope": ["variable.other.constant.duffle.cop2"], "settings": { "foreground": "#9e95c6" } }, + { "scope": ["storage.type.duffle.type"], "settings": { "foreground": "#54a4d6" } }, + { "scope": ["storage.modifier.duffle.attr"], "settings": { "foreground": "#73a07c" } } + ] +} diff --git a/.vscode/tape-atom-syntax/classifier.js b/.vscode/tape-atom-syntax/classifier.js index a3ccdd5..89f1ec2 100644 --- a/.vscode/tape-atom-syntax/classifier.js +++ b/.vscode/tape-atom-syntax/classifier.js @@ -64,7 +64,9 @@ function instructionType(name, index) { if (domain === "component") return "tapeComponentInstruction"; if (/^gte_(?!cr_)/.test(name)) return "tapeGteInstruction"; if (/^gp[01]_/.test(name)) return "tapeGpuInstruction"; - if (/^mac_/.test(name)) return "tapeComponentInstruction"; + if (/^mac_gte_/.test(name)) return "tapeGteInstruction"; + if (/^mac_gp/.test(name)) return "tapeGpuInstruction"; + if (/^mac_/.test(name)) return "tapeCpuInstruction"; return null; } @@ -82,6 +84,8 @@ function isRegUseAccess(tokens, tokenIndex) { if (!prev || prev.text !== ".") return false; const prevPrev = tokens[tokenIndex - 2]; if (!prevPrev || prevPrev.kind !== "identifier") return false; + const next = tokens[tokenIndex + 1]; + if (next && next.text === ".") return false; if (prevPrev.text === "r") return true; const prev3 = tokens[tokenIndex - 3]; const prev4 = tokens[tokenIndex - 4]; diff --git a/.vscode/tape-atom-syntax/package.json b/.vscode/tape-atom-syntax/package.json index c58e883..5f9689c 100644 --- a/.vscode/tape-atom-syntax/package.json +++ b/.vscode/tape-atom-syntax/package.json @@ -21,24 +21,24 @@ }, "contributes": { "semanticTokenTypes": [ - { "id": "tapeAtomKeyword", "description": "Tape atom declaration keyword" }, - { "id": "tapeAtomName", "description": "Tape atom name" }, - { "id": "tapeComponentKeyword", "description": "Tape atom component declaration keyword" }, - { "id": "tapeComponentName", "description": "Tape atom component name" }, - { "id": "tapeAnnotation", "description": "Tape atom annotation" }, - { "id": "tapeBindType", "description": "Tape bind structure type" }, - { "id": "tapePhase", "description": "Tape atom phase" }, - { "id": "tapeLabel", "description": "Tape atom branch label" }, - { "id": "tapeCpuInstruction", "description": "MIPS CPU instruction emitter" }, - { "id": "tapeControlFlow", "description": "MIPS branch or jump instruction" }, - { "id": "tapeGteInstruction", "description": "GTE instruction emitter" }, - { "id": "tapeGpuInstruction", "description": "GPU command emitter" }, - { "id": "tapeComponentInstruction", "description": "Tape atom component invocation" }, - { "id": "tapeDelaySlot", "description": "Load or branch delay slot annotation" }, - { "id": "tapeGprRegister", "description": "MIPS GPR alias" }, - { "id": "tapeCop2Register", "description": "COP2 data or control register alias" }, - { "id": "tapeDuffleType", "description": "Duffle type or type constructor" }, - { "id": "tapeAttribute", "description": "Duffle linkage or storage attribute" } + { "id": "tapeAtomKeyword", "superType": "keyword", "description": "Tape atom declaration keyword" }, + { "id": "tapeAtomName", "superType": "function", "description": "Tape atom name" }, + { "id": "tapeComponentKeyword", "superType": "keyword", "description": "Tape atom component declaration keyword" }, + { "id": "tapeComponentName", "superType": "function", "description": "Tape atom component name" }, + { "id": "tapeAnnotation", "superType": "macro", "description": "Tape atom annotation" }, + { "id": "tapeBindType", "superType": "type", "description": "Tape bind structure type" }, + { "id": "tapePhase", "superType": "label", "description": "Tape atom phase" }, + { "id": "tapeLabel", "superType": "label", "description": "Tape atom branch label" }, + { "id": "tapeCpuInstruction", "superType": "macro", "description": "MIPS CPU instruction emitter" }, + { "id": "tapeControlFlow", "superType": "keyword", "description": "MIPS branch or jump instruction" }, + { "id": "tapeGteInstruction", "superType": "macro", "description": "GTE instruction emitter" }, + { "id": "tapeGpuInstruction", "superType": "macro", "description": "GPU command emitter" }, + { "id": "tapeComponentInstruction", "superType": "macro", "description": "Tape atom component invocation" }, + { "id": "tapeDelaySlot", "superType": "keyword", "description": "Load or branch delay slot annotation" }, + { "id": "tapeGprRegister", "superType": "variable", "description": "MIPS GPR alias" }, + { "id": "tapeCop2Register", "superType": "variable", "description": "COP2 data or control register alias" }, + { "id": "tapeDuffleType", "superType": "type", "description": "Duffle type or type constructor" }, + { "id": "tapeAttribute", "superType": "keyword", "description": "Duffle linkage or storage attribute" } ], "semanticTokenModifiers": [ { "id": "tapeRead", "description": "Register declared in atom_reads" }, diff --git a/.vscode/tape-atom-syntax/source-index.js b/.vscode/tape-atom-syntax/source-index.js index bd0f0b6..a148336 100644 --- a/.vscode/tape-atom-syntax/source-index.js +++ b/.vscode/tape-atom-syntax/source-index.js @@ -100,13 +100,19 @@ function scanSource(source, filePath) { declarations.set(token.start, { role, modifiers }); } + function componentDomain(name) { + if (name.startsWith("ac_gte_") || name.startsWith("mac_gte_")) return "gte"; + if (name.startsWith("ac_gp_") || name.startsWith("mac_gp_")) return "gpu"; + return "cpu"; + } + function addComponent(token) { index.components.add(token.text); mark(token, "componentName"); const alias = componentAlias(token.text); if (alias) { index.componentAliases.add(alias); - index.macros.set(alias, domain); + index.macros.set(alias, componentDomain(token.text)); } } @@ -161,7 +167,13 @@ function scanSource(source, filePath) { if (token.text === "define" && tokens[tokenIndex - 1] && tokens[tokenIndex - 1].text === "#") { const name = tokens[tokenIndex + 1]; - if (name && name.kind === "identifier" && name.line === token.line) index.macros.set(name.text, domain); + if (name && name.kind === "identifier" && name.line === token.line) { + if (/^(?:RegUse_|Struct_|Enum_|Union_|TypeR_|TypeV_|Relative_|Binds_)/.test(name.text)) { + index.types.add(name.text); + } else { + index.macros.set(name.text, domain); + } + } } if (token.text === "typedef") { diff --git a/.vscode/tape-atom-syntax/syntaxes/tape_atom.tmLanguage.json b/.vscode/tape-atom-syntax/syntaxes/tape_atom.tmLanguage.json index 7edf983..1b6af23 100644 --- a/.vscode/tape-atom-syntax/syntaxes/tape_atom.tmLanguage.json +++ b/.vscode/tape-atom-syntax/syntaxes/tape_atom.tmLanguage.json @@ -6,8 +6,7 @@ { "include": "#component-declarations" }, { "include": "#annotation-arguments" }, { "include": "#annotations" }, - { "include": "#instructions" }, - { "include": "#registers" }, + { "include": "#delay-slots" }, { "include": "#types" }, { "include": "#attributes" } ], @@ -34,15 +33,11 @@ "2": { "name": "entity.name.function.duffle.component" } } }, - { "match": "\\bMipsAtomComp_Proc_\\b", "name": "keyword.control.duffle.component" }, - { "match": "\\bac_[A-Za-z0-9_]+\\b", "name": "entity.name.function.duffle.component" } + { "match": "\\bMipsAtomComp_Proc_\\b", "name": "keyword.control.duffle.component" } ] }, "annotation-arguments": { "patterns": [ - { "match": "(?<=\\batom_bind\\()\\s*Binds_[A-Za-z0-9_]+", "name": "entity.name.type.duffle.bind" }, - { "match": "(?<=\\batom_phase\\()\\s*[A-Za-z_][A-Za-z0-9_]*", "name": "entity.name.tag.duffle.phase" }, - { "match": "(?<=\\batom_label\\()\\s*[A-Za-z_][A-Za-z0-9_]*", "name": "entity.name.label.duffle.atom" }, { "match": "\\b(atom_offset)\\s*\\(\\s*([A-Za-z_][A-Za-z0-9_]*)\\s*,\\s*([A-Za-z_][A-Za-z0-9_]*)", "captures": { @@ -50,31 +45,22 @@ "2": { "name": "entity.name.label.duffle.atom" }, "3": { "name": "entity.name.label.duffle.atom" } } - } + }, + { "match": "(?<=\\batom_bind\\()\\s*Binds_[A-Za-z0-9_]+", "name": "entity.name.type.duffle.bind" }, + { "match": "(?<=\\batom_phase\\()\\s*[A-Za-z_][A-Za-z0-9_]*", "name": "entity.name.tag.duffle.phase" }, + { "match": "(?<=\\batom_label\\()\\s*[A-Za-z_][A-Za-z0-9_]*", "name": "entity.name.label.duffle.atom" } ] }, "annotations": { "match": "\\b(atom_info|atom_bind|atom_reads|atom_writes|atom_label|atom_offset|atom_reg|atom_type|atom_ctx|atom_phase|atom_auto_reg|phase_auto_reg|atom_dbg_skip)\\b", "name": "support.function.duffle.annotation" }, - "instructions": { - "patterns": [ - { "match": "\\b(branch_|jump_|jump_rel|call_)[A-Za-z0-9_]*\\b", "name": "keyword.control.duffle.branch" }, - { "match": "\\b(LdSlot_|BdSlot_)\\b", "name": "keyword.operator.duffle.delayslot" }, - { "match": "\\b(load_|store_|add_|sub_|shift_|set_lt_|and_i|or_i|xor_i|nop[0-9]*)[A-Za-z0-9_]*\\b", "name": "support.function.duffle.cpu" }, - { "match": "\\bgte_(?!cr_)[A-Za-z0-9_]+\\b", "name": "support.function.duffle.gte" }, - { "match": "\\bgp[01]_[A-Za-z0-9_]+\\b", "name": "support.function.duffle.gpu" }, - { "match": "\\bmac_[A-Za-z0-9_]+\\b", "name": "support.function.duffle.component" } - ] - }, - "registers": { - "patterns": [ - { "match": "\\bR_[A-Za-z0-9_]+\\b", "name": "variable.other.constant.duffle.gpr" }, - { "match": "\\b(?:C2_|gte_cr_)[A-Za-z0-9_]+\\b", "name": "variable.other.constant.duffle.cop2" } - ] + "delay-slots": { + "match": "\\b(LdSlot_|BdSlot_)\\b", + "name": "keyword.operator.duffle.delayslot" }, "types": { - "match": "\\b(?:[USFB][1248]|MipsAtom|MipsCode|Reg|Binds_[A-Za-z0-9_]+|Struct_|Enum_|Union_|TypeR_|TypeV_)\\b", + "match": "\\b(?:Binds_[A-Za-z0-9_]+|RegUse_[A-Za-z0-9_]+)\\b", "name": "storage.type.duffle.type" }, "attributes": { diff --git a/.vscode/tape-atom-syntax/tape-atom-syntax-0.2.0.vsix b/.vscode/tape-atom-syntax/tape-atom-syntax-0.2.0.vsix index b15f1cc..c3fdb5f 100644 Binary files a/.vscode/tape-atom-syntax/tape-atom-syntax-0.2.0.vsix and b/.vscode/tape-atom-syntax/tape-atom-syntax-0.2.0.vsix differ diff --git a/.vscode/tape-atom-syntax/test/classifier.test.js b/.vscode/tape-atom-syntax/test/classifier.test.js index c19275d..12628c0 100644 --- a/.vscode/tape-atom-syntax/test/classifier.test.js +++ b/.vscode/tape-atom-syntax/test/classifier.test.js @@ -67,7 +67,7 @@ test("document-local declarations override an empty workspace index", () => { const result = classifyDocument(source, "C:/x/code/duffle/math.atom.c", createIndex()); assert.equal(byText(result, "ac_new_component")[0].type, "tapeComponentName"); - assert.equal(byText(result, "mac_new_component")[0].type, "tapeComponentInstruction"); + assert.equal(byText(result, "mac_new_component")[0].type, "tapeCpuInstruction"); }); test("classifier returns ordered non-overlapping spans and partial malformed output", () => { diff --git a/.vscode/tape-atom-syntax/test/contract.test.js b/.vscode/tape-atom-syntax/test/contract.test.js index 05b0d31..4ca6ce3 100644 --- a/.vscode/tape-atom-syntax/test/contract.test.js +++ b/.vscode/tape-atom-syntax/test/contract.test.js @@ -10,7 +10,9 @@ const { TOKEN_MODIFIERS, TOKEN_TYPES } = require("../classifier"); const ROOT = path.resolve(__dirname, ".."); function readJson(filePath) { - return JSON.parse(fs.readFileSync(filePath, "utf8")); + const raw = fs.readFileSync(filePath, "utf8"); + const stripped = raw.replace(/\/\/.*$/gm, "").replace(/,\s*([}\]])/g, "$1"); + return JSON.parse(stripped); } function collectScopeNames(value, output = new Set()) { @@ -48,15 +50,23 @@ test("package includes runtime files only and acknowledges local-only metadata", assert.equal(packageJson.scripts.package.includes("--skip-license"), true); }); -test("every semantic token has a TextMate fallback scope and grammar scope", () => { +test("every semantic token has a scope mapping; DSL-specific tokens also have grammar scopes", () => { const packageJson = readJson(path.join(ROOT, "package.json")); const grammar = readJson(path.join(ROOT, "syntaxes", "tape_atom.tmLanguage.json")); const mappings = packageJson.contributes.semanticTokenScopes[0].scopes; const grammarScopes = collectScopeNames(grammar); + const grammarRequired = new Set([ + "tapeAtomKeyword", "tapeAtomName", "tapeComponentKeyword", "tapeComponentName", + "tapeAnnotation", "tapeBindType", "tapePhase", "tapeLabel", + "tapeDelaySlot", "tapeDuffleType", "tapeAttribute", + ]); + for (const tokenType of TOKEN_TYPES) { assert.equal(Array.isArray(mappings[tokenType]), true, `missing scope mapping: ${tokenType}`); - assert.equal(mappings[tokenType].some((scope) => grammarScopes.has(scope)), true, `grammar does not emit: ${tokenType}`); + if (grammarRequired.has(tokenType)) { + assert.equal(mappings[tokenType].some((scope) => grammarScopes.has(scope)), true, `grammar does not emit: ${tokenType}`); + } } }); @@ -72,22 +82,10 @@ test("TextMate offset labels stay scoped to atom_offset calls", () => { assert.equal(offsetRule.captures[3].name, "entity.name.label.duffle.atom"); }); -test("workspace enables semantic highlighting and colors every custom token", () => { +test("workspace enables semantic highlighting", () => { const settings = readJson(path.resolve(ROOT, "..", "settings.json")); - const semantic = settings["editor.semanticTokenColorCustomizations"]; assert.equal(settings["editor.semanticHighlighting.enabled"], true); + const semantic = settings["editor.semanticTokenColorCustomizations"]; assert.equal(semantic.enabled, true); - for (const tokenType of TOKEN_TYPES) { - assert.equal(Object.hasOwn(semantic.rules, tokenType), true, `missing color: ${tokenType}`); - } - for (const rule of [ - "tapeGprRegister.tapeRead", - "tapeGprRegister.tapeWrite", - "tapeCop2Register.tapeRead", - "tapeCop2Register.tapeWrite", - "*.tapeAuto", - ]) { - assert.equal(Object.hasOwn(semantic.rules, rule), true, `missing modifier color: ${rule}`); - } });