Skip to content

Commit

Permalink
extension: add CUE status bar
Browse files Browse the repository at this point in the history
Signed-off-by: Paul Jolly <[email protected]>
Change-Id: I8d521e02dca79ac4cb193974984b67d6cfe8e838
Dispatch-Trailer: {"type":"trybot","CL":1206540,"patchset":7,"ref":"refs/changes/40/1206540/7","targetBranch":"master"}
  • Loading branch information
myitcv authored and cueckoo committed Jan 1, 2025
1 parent 83e9ddc commit 491c61a
Showing 1 changed file with 104 additions and 2 deletions.
106 changes: 104 additions & 2 deletions extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import { config } from './gen_userCommands';

let errTornDown = new Error('Extenssion instance already torn down');

const CopyStatusVersionToClipboardCmd = 'copyversiontoclipboard';

// An instance of Extension represents the active instance (!!) of the VSCode
// extension that is this project. An instance of Extension is created when the
// extension is activated, and tearDown-ed when the extension is deactivated.
Expand Down Expand Up @@ -82,6 +84,16 @@ export class Extension {
// methods, throwing errors in case we get callbacks after tearDown.
private tornDown: boolean = false;

// cueCommand keeps track of the last output from 'cue version' using the
// configured cueCommand command as a proxy for cmd/cue. An
// empty string means that we were unable to interrogate the output of 'cue
// version'.
private cueVersion: string = '';

// statusBarItem shows the CUE extension status, including version and a
// :zap: icon in case the LSP is running.
private statusBarItem: vscode.StatusBarItem;

constructor(
ctx: vscode.ExtensionContext,
output: vscode.LogOutputChannel,
Expand All @@ -97,6 +109,11 @@ export class Extension {
this.registerCommand('startlsp', this.cmdStartLSP);
this.registerCommand('stoplsp', this.cmdStopLSP);

// Not visible to users via the command palette
this.registerCommand(CopyStatusVersionToClipboardCmd, this.copyStatusVersionToClipboard);

this.statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);

// 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
// workspace/folder, i.e. sets 'cue.useLanguageServer' in either workspace or
Expand Down Expand Up @@ -142,15 +159,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>) => {
cmd = config.npm.name + '.' + cmd;
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 Expand Up @@ -188,6 +211,11 @@ export class Extension {
let vscodeConfig = vscode.workspace.getConfiguration('cue');
let newConfig = JSON.parse(JSON.stringify(vscodeConfig)) as CueConfiguration;

// We need to re-run 'cue version' in case the cue command implied by
// languageServerCommand[0] changes.
let currentCueCmd = this.config?.cueCommand ?? '';
let newCueCmd = newConfig.cueCommand;

this.config = newConfig;
this.output.info(`configuration updated to: ${JSON.stringify(this.config, null, 2)}`);

Expand Down Expand Up @@ -243,6 +271,43 @@ export class Extension {
return;
}

// We have a valid value for cueCommand. Update the version string if the
// value of cueCommand changed.
if (currentCueCmd !== newCueCmd) {
// We need to run 'cue version' (according to the config cueCommand)
// for the updated version string.
let [cueCommand, err] = await ve(this.absCueCommand(this.config!.cueCommand));
if (err !== null) {
return Promise.reject(err);
}
let cueVersion: Cmd = {
Args: [cueCommand!, 'version']
};
[, err] = await ve(osexecRun(cueVersion));
if (err !== null) {
let msgSuffix = '';
if (isErrnoException(err)) {
msgSuffix = `: ${err}`;
} else {
msgSuffix = cueVersion.Stderr!;
}
return Promise.reject(new Error(`failed to run ${JSON.stringify(cueVersion)}: ${msgSuffix}`));
}
let versionOutput = cueVersion.Stdout!.trim();
const versionRegex = /^cue version (.*)/m;
let match = versionOutput.match(versionRegex);
if (!match) {
return Promise.reject(
new Error(`failed to parse version output from ${JSON.stringify(cueVersion)}: ${JSON.stringify(versionOutput)}`)
);
}
this.cueVersion = match[1];
}

// Update the status bar item
this.updateStatus();

// Run the LSP as required
if (this.config.useLanguageServer) {
// TODO: we might want to revisit just blindly restarting the LSP, for
// example in case the configuration for the LSP client or server hasn't
Expand All @@ -262,6 +327,38 @@ export class Extension {
vscode.window.showErrorMessage(message, ...items);
};

// updateStatus ensures that the status bar item reflects the current state
// of the extension.
updateStatus = (): Promise<void> => {
let version = this.cueVersion;
let tooltip = 'Click to copy version';
let command = this.commandID(CopyStatusVersionToClipboardCmd);
if (version === '') {
version = '??'; // TODO(myitcv): do we need to do better here?
tooltip = '';
command = '';
}
let status = version;
if (this.client !== undefined) {
status += ' $(zap)';
}
this.statusBarItem.text = status;
this.statusBarItem.tooltip = tooltip;
this.statusBarItem.command = command;
this.statusBarItem.show();
return Promise.resolve();
};

// copyStatusVersionToClipboard is the target of the
// CopyStatusVersionToClipboardCmd hidden command (i.e. not visible via the
// Command Palette) that is triggered by the user clicking on the CUE status
// bar item.
copyStatusVersionToClipboard = async (): Promise<void> => {
// This method is Thenable, not a Promise.
await vscode.env.clipboard.writeText(this.cueVersion);
vscode.window.showInformationMessage(`Copied to clipboard: ${this.cueVersion}`);
};

// cmdWelcomeCUE is a basic command that can be used to verify whether the
// vscode-cue extension is loaded at all (beyond checking output logs).
cmdWelcomeCUE = async (context?: any): Promise<void> => {
Expand Down Expand Up @@ -403,6 +500,7 @@ export class Extension {

// At this point, all events happend via callbacks in terms of state changes,
// or the client-server interaction of the LSP protocol.
this.updateStatus();
};

// stopCueLsp kills the running LSP client, if there is one.
Expand Down Expand Up @@ -430,6 +528,10 @@ export class Extension {
[, err] = await ve(this.client.stop());
this.client = undefined;
this.clientStateChangeHandler = undefined;

// Update the status
this.updateStatus();

if (err !== null) {
// TODO: we get an error message here relating to the process for stopping
// the server timing out, when providing an argument to stop(). Why? And
Expand Down

0 comments on commit 491c61a

Please sign in to comment.