Skip to content

Commit

Permalink
extension: use CUE as the source of truth for TypeScript
Browse files Browse the repository at this point in the history
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 <[email protected]>
Change-Id: Ifd6c76f101025da242edabb69049a7bbf7090a20
Reviewed-on: https://review.gerrithub.io/c/cue-lang/vscode-cue/+/1206549
Reviewed-by: Daniel Martí <[email protected]>
TryBot-Result: CUEcueckoo <[email protected]>
  • Loading branch information
myitcv committed Jan 2, 2025
1 parent ed48151 commit 8e6c448
Show file tree
Hide file tree
Showing 6 changed files with 71 additions and 22 deletions.
46 changes: 33 additions & 13 deletions extension/extension.cue
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions extension/extension_tool.cue
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ package extension
import (
"encoding/json"
"list"
"path"
"strings"

"tool/cli"
"tool/exec"
"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: {
Expand Down Expand Up @@ -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<typeof _config> = Object.freeze(_config);
"""
}
1 change: 1 addition & 0 deletions extension/manifest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 4 additions & 4 deletions extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,17 @@
}
],
"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"
},
{
"command": "vscode-cue.welcome",
"title": "CUE: Welcome"
}
],
"configuration": {
Expand Down
19 changes: 14 additions & 5 deletions extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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<void>) => {
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.
Expand Down
4 changes: 4 additions & 0 deletions extension/src/gen_userCommands.ts
Original file line number Diff line number Diff line change
@@ -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<typeof _config> = Object.freeze(_config);

0 comments on commit 8e6c448

Please sign in to comment.