From 8e6c448342e0030d51dae0038b989450c6749e4e Mon Sep 17 00:00:00 2001 From: Paul Jolly Date: Wed, 1 Jan 2025 16:14:57 +0000 Subject: [PATCH] extension: use CUE as the source of truth for TypeScript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently we rely on keeping the TypeScript source and CUE in sync manually. This is obviously brittle. As a step in the right direction, generated a TypeScript file that contains an exported version of the CUE configuration for the extension (i.e. data, not schema). We do this via the new genTS command. In order to be able to use the command IDs from TypeScript, we factor out the list of commands required by the VSCode Extension schema into a map, keyed by ID, and template the VSCode form from that map. We also factor out the command prefix "CUE: " and modify the Extension.registerCommand method to use the new config struct. We also provide an Extension.commandID helper method to construct a command ID from a command name. This is used again in a later CL. Signed-off-by: Paul Jolly Change-Id: Ifd6c76f101025da242edabb69049a7bbf7090a20 Reviewed-on: https://review.gerrithub.io/c/cue-lang/vscode-cue/+/1206549 Reviewed-by: Daniel Martí TryBot-Result: CUEcueckoo --- extension/extension.cue | 46 ++++++++++++++++++++++--------- extension/extension_tool.cue | 15 ++++++++++ extension/manifest.txt | 1 + extension/package.json | 8 +++--- extension/src/extension.ts | 19 +++++++++---- extension/src/gen_userCommands.ts | 4 +++ 6 files changed, 71 insertions(+), 22 deletions(-) create mode 100644 extension/src/gen_userCommands.ts diff --git a/extension/extension.cue b/extension/extension.cue index a16d52f..a477d97 100644 --- a/extension/extension.cue +++ b/extension/extension.cue @@ -1,8 +1,29 @@ package extension +import ( + "list" +) + +// TODO(myitcv): generate a CUE schema, because there does not appear to be a +// JSON Schema or other schema for validating VSCode extensions. + +extension: commandTitlePrefix: "CUE: " + +extension: commands: [string]: { + // Title is the non-prefixed title to use for the command. + // commandTitlePrefix will be templated into the npm configuration. + title!: string +} + +extension: commands: { + welcome: title: "Welcome" + startlsp: title: "Start CUE LSP" + stoplsp: title: "Stop CUE LSP" +} + extension: npm: { name: "vscode-cue" - displayName: "vscode-cue" + displayName: name description: "CUE language support for Visual Studio Code" repository: "https://github.com/cue-lang/vscode-cue" version: "0.0.8" @@ -32,20 +53,19 @@ extension: npm: { path: "./syntaxes/cue.tmLanguage.json" embeddedLanguages: "source.cue.embedded": "source.cue" }] - commands: [ - { - command: "vscode-cue.welcome" - title: "CUE: Welcome" - }, - { - command: "vscode-cue.startlsp" - title: "CUE: Start CUE LSP" - }, - { - command: "vscode-cue.stoplsp" - title: "CUE: Stop CUE LSP" + + // sort the commands by title for stability + let _commands = [ + for k, v in extension.commands { + command: "\(npm.name).\(k)" + title: extension.commandTitlePrefix + v.title }, ] + commands: list.Sort(_commands, { + x: _ + y: _ + less: x.title < y.title + }) // TODO(myitcv): maintain this schema as CUE, and export to JSON Schema // when generating package.json when CUE can do this. Doing so will also diff --git a/extension/extension_tool.cue b/extension/extension_tool.cue index 57478f5..f196a49 100644 --- a/extension/extension_tool.cue +++ b/extension/extension_tool.cue @@ -3,6 +3,7 @@ package extension import ( "encoding/json" "list" + "path" "strings" "tool/cli" @@ -10,6 +11,8 @@ import ( "tool/file" ) +_os: string @tag(os, var=os) + // genManifest writes out a manifest.txt file that captures the contents // of the extension. This command should be run after vsce package. command: genManifest: { @@ -86,3 +89,15 @@ command: checkReleaseVersion: { } } } + +command: genTS: file.Create & { + filename: path.FromSlash("src/gen_userCommands.ts", _os) + + contents: """ + // Code generated by cue cmd genTS; DO NOT EDIT. + + let _config = \(json.Marshal(extension)); + export const config: Readonly = Object.freeze(_config); + + """ +} diff --git a/extension/manifest.txt b/extension/manifest.txt index 746b0b1..4c1b6a4 100644 --- a/extension/manifest.txt +++ b/extension/manifest.txt @@ -6,5 +6,6 @@ language-configuration.json media/white_circle_128.png package.json src/extension.ts +src/gen_userCommands.ts src/main.ts syntaxes/cue.tmLanguage.json diff --git a/extension/package.json b/extension/package.json index 40cd5fa..cdf3e09 100644 --- a/extension/package.json +++ b/extension/package.json @@ -42,10 +42,6 @@ } ], "commands": [ - { - "command": "vscode-cue.welcome", - "title": "CUE: Welcome" - }, { "command": "vscode-cue.startlsp", "title": "CUE: Start CUE LSP" @@ -53,6 +49,10 @@ { "command": "vscode-cue.stoplsp", "title": "CUE: Stop CUE LSP" + }, + { + "command": "vscode-cue.welcome", + "title": "CUE: Welcome" } ], "configuration": { diff --git a/extension/src/extension.ts b/extension/src/extension.ts index d3fa993..d877293 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -20,6 +20,7 @@ import * as vscode from 'vscode'; import * as lcnode from 'vscode-languageclient/node'; import * as cp from 'node:child_process'; import * as lc from 'vscode-languageclient'; +import { config } from './gen_userCommands'; let errTornDown = new Error('Extenssion instance already torn down'); @@ -92,9 +93,9 @@ export class Extension { let configChangeListener = vscode.workspace.onDidChangeConfiguration(this.extensionConfigurationChange); this.ctx.subscriptions.push(configChangeListener); - this.registerCommand('vscode-cue.welcome', this.cmdWelcomeCUE); - this.registerCommand('vscode-cue.startlsp', this.cmdStartLSP); - this.registerCommand('vscode-cue.stoplsp', this.cmdStopLSP); + this.registerCommand('welcome', this.cmdWelcomeCUE); + this.registerCommand('startlsp', this.cmdStartLSP); + this.registerCommand('stoplsp', this.cmdStopLSP); // TODO(myitcv): in the early days of 'cue lsp', it might be worthwhile // adding a command that toggles the enabled-ness of the LSP in the active @@ -120,7 +121,8 @@ export class Extension { }; // registerCommand is a light wrapper around the vscode API for registering a - // command but also simultaneously adding a dispose callback. + // command but also simultaneously adding a dispose callback. It also takes care + // of namespacing the command within the config.npm.name namespace. // // TODO(myitcv): it isn't really documented anywhere, but the expected signature // of callback is: @@ -140,14 +142,21 @@ export class Extension { // the command, such that the user would otherwise be left surprised if nothing // happened because of the error. registerCommand = (cmd: string, callback: (context?: any) => Promise) => { + let cmdID = this.commandID(cmd); if (this.tornDown) { throw errTornDown; } - let disposable = vscode.commands.registerCommand(cmd, callback); + let disposable = vscode.commands.registerCommand(cmdID, callback); this.ctx.subscriptions.push(disposable); }; + // commandID returns a config.npm.name-namespaced ID for cmd. e.g. with an + // argument of "startlsp" it will return "vscode-cue.startlsp". + commandID = (cmd: string): string => { + return config.npm.name + '.' + cmd; + }; + // extensionConfigurationChange is the callback that fires when the extension // instance's configuration has changed, including the initial configuration // change that happens at activation time. diff --git a/extension/src/gen_userCommands.ts b/extension/src/gen_userCommands.ts new file mode 100644 index 0000000..a273736 --- /dev/null +++ b/extension/src/gen_userCommands.ts @@ -0,0 +1,4 @@ +// Code generated by cue cmd genTS; DO NOT EDIT. + +let _config = {"commandTitlePrefix":"CUE: ","commands":{"welcome":{"title":"Welcome"},"startlsp":{"title":"Start CUE LSP"},"stoplsp":{"title":"Stop CUE LSP"}},"npm":{"name":"vscode-cue","displayName":"vscode-cue","description":"CUE language support for Visual Studio Code","repository":"https://github.com/cue-lang/vscode-cue","version":"0.0.8","icon":"media/white_circle_128.png","license":"MIT","publisher":"cuelangorg","engines":{"vscode":">=1.85.0"},"categories":["Programming Languages"],"activationEvents":["onLanguage:cue"],"main":"./dist/main.js","contributes":{"languages":[{"id":"cue","aliases":["CUE","cue"],"extensions":[".cue"],"configuration":"./language-configuration.json"}],"grammars":[{"language":"cue","scopeName":"source.cue","path":"./syntaxes/cue.tmLanguage.json","embeddedLanguages":{"source.cue.embedded":"source.cue"}}],"commands":[{"command":"vscode-cue.startlsp","title":"CUE: Start CUE LSP"},{"command":"vscode-cue.stoplsp","title":"CUE: Stop CUE LSP"},{"command":"vscode-cue.welcome","title":"CUE: Welcome"}],"configuration":{"type":"object","title":"CUE","properties":{"cue.useLanguageServer":{"type":"boolean","default":true,"description":"Enable cue lsp, the language server for CUE."},"cue.cueCommand":{"type":"string","default":"cue","description":"The command or path used to run the CUE command, cmd/cue"},"cue.languageServerFlags":{"type":"array","default":[],"description":"Flags like -rpc.trace and -logfile to be used while running the language server."}}}},"scripts":{"vscode:prepublish":"cue cmd genPackageJSON && npm run clean && npm run buildpackage","clean":"rm -rf dist","compile":"npm run check-types && npm run lint && node esbuild.js","watch":"npm-run-all -p watch:*","watch:esbuild":"node esbuild.js --watch","watch:tsc":"tsc --noEmit --watch --project tsconfig.json","buildpackage":"npm run check-types && npm run lint && node esbuild.js --production","package":"vsce package && cue cmd genManifest","publish":"vsce publish","compile-tests":"tsc -p . --outDir out","watch-tests":"tsc -p . -w --outDir out","pretest":"npm run compile-tests && npm run compile && npm run lint","check-types":"tsc --noEmit","lint":"eslint src --ext ts","test":"vscode-test","format":"prettier --write \"src/**/*.ts\" --ignore-path ../.prettierignore"},"devDependencies":{"@types/mocha":"10.0.7","@types/node":"22.9.1","@types/vscode":"1.85.0","@typescript-eslint/eslint-plugin":"7.14.1","@typescript-eslint/parser":"7.11.0","@vscode/test-cli":"0.0.9","@vscode/test-electron":"2.4.0","@vscode/vsce":"3.2.1","esbuild":"0.21.5","eslint":"8.57.0","npm-run-all":"4.1.5","typescript":"5.4.5","@types/which":"3.0.4","vscode-languageclient":"9.0.1","which":"5.0.0"}}}; +export const config: Readonly = Object.freeze(_config);