Skip to content

Commit 8d3f45b

Browse files
committed
First iteration of the StatusBarItem
This 'StatusBarItem' improves the discoverability of the most important interactions between the user and the extension. This includes debug information, such as the version of the extension, showing the output panel to find the logs more easily, but also to restart all running Haskell Language Server binaries and the extension itself.
1 parent 273d401 commit 8d3f45b

File tree

4 files changed

+112
-25
lines changed

4 files changed

+112
-25
lines changed

package.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1310,9 +1310,9 @@
13101310
},
13111311
"commands": [
13121312
{
1313-
"command": "haskell.commands.importIdentifier",
1314-
"title": "Haskell: Import identifier",
1315-
"description": "Imports a function or type based on a Hoogle search"
1313+
"command": "haskell.commands.restartExtension",
1314+
"title": "Haskell: Restart vscode-haskell extension",
1315+
"description": "Restart the vscode-haskell extension. Reloads configuration."
13161316
},
13171317
{
13181318
"command": "haskell.commands.restartServer",

src/commands/constants.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
export const ImportIdentifierCommandName = 'haskell.commands.importIdentifier';
1+
export const RestartExtensionCommandName = 'haskell.commands.restartExtension';
22
export const RestartServerCommandName = 'haskell.commands.restartServer';
33
export const StartServerCommandName = 'haskell.commands.startServer';
44
export const StopServerCommandName = 'haskell.commands.stopServer';
5+
export const OpenLogsCommandName = 'haskell.commands.openLogs';
6+
export const ShowExtensionVersions = 'haskell.commands.showVersions';

src/extension.ts

+68-21
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,32 @@ import {
77
RevealOutputChannelOn,
88
ServerOptions,
99
} from 'vscode-languageclient/node';
10-
import { RestartServerCommandName, StartServerCommandName, StopServerCommandName } from './commands/constants';
10+
import * as constants from './commands/constants';
1111
import * as DocsBrowser from './docsBrowser';
1212
import { HlsError, MissingToolError, NoMatchingHls } from './errors';
1313
import { findHaskellLanguageServer, HlsExecutable, IEnvVars } from './hlsBinaries';
1414
import { addPathToProcessPath, comparePVP, callAsync } from './utils';
1515
import { Config, initConfig, initLoggerFromConfig, logConfig } from './config';
16+
import { HaskellStatusBar } from './statusBar';
17+
18+
/**
19+
* Global information about the running clients.
20+
*/
21+
type Client = {
22+
client: LanguageClient;
23+
config: Config;
24+
};
1625

1726
// The current map of documents & folders to language servers.
1827
// It may be null to indicate that we are in the process of launching a server,
1928
// in which case don't try to launch another one for that uri
20-
const clients: Map<string, LanguageClient | null> = new Map();
29+
const clients: Map<string, Client | null> = new Map();
2130

2231
// This is the entrypoint to our extension
2332
export async function activate(context: ExtensionContext) {
33+
const statusBar = new HaskellStatusBar(context.extension.packageJSON.version as string | undefined);
34+
context.subscriptions.push(statusBar);
35+
2436
// (Possibly) launch the language server every time a document is opened, so
2537
// it works across multiple workspace folders. Eventually, haskell-lsp should
2638
// just support
@@ -37,41 +49,69 @@ export async function activate(context: ExtensionContext) {
3749
const client = clients.get(folder.uri.toString());
3850
if (client) {
3951
const uri = folder.uri.toString();
40-
client.info(`Deleting folder for clients: ${uri}`);
52+
client.client.info(`Deleting folder for clients: ${uri}`);
4153
clients.delete(uri);
42-
client.info('Stopping the server');
43-
await client.stop();
54+
client.client.info('Stopping the server');
55+
await client.client.stop();
4456
}
4557
}
4658
});
4759

4860
// Register editor commands for HIE, but only register the commands once at activation.
49-
const restartCmd = commands.registerCommand(RestartServerCommandName, async () => {
61+
const restartCmd = commands.registerCommand(constants.RestartServerCommandName, async () => {
5062
for (const langClient of clients.values()) {
51-
langClient?.info('Stopping the server');
52-
await langClient?.stop();
53-
langClient?.info('Starting the server');
54-
await langClient?.start();
63+
langClient?.client.info('Stopping the server');
64+
await langClient?.client.stop();
65+
langClient?.client.info('Starting the server');
66+
await langClient?.client.start();
5567
}
5668
});
5769

5870
context.subscriptions.push(restartCmd);
5971

60-
const stopCmd = commands.registerCommand(StopServerCommandName, async () => {
72+
const openLogsCmd = commands.registerCommand(constants.OpenLogsCommandName, () => {
73+
for (const langClient of clients.values()) {
74+
langClient?.config.outputChannel.show();
75+
}
76+
});
77+
78+
context.subscriptions.push(openLogsCmd);
79+
80+
const restartExtensionCmd = commands.registerCommand(constants.RestartExtensionCommandName, async () => {
81+
for (const langClient of clients.values()) {
82+
langClient?.client.info('Stopping the server');
83+
await langClient?.client.stop();
84+
}
85+
clients.clear();
86+
87+
for (const document of workspace.textDocuments) {
88+
await activateServer(context, document);
89+
}
90+
});
91+
92+
context.subscriptions.push(restartExtensionCmd);
93+
94+
const showVersionsCmd = commands.registerCommand(constants.ShowExtensionVersions, () => {
95+
void window.showInformationMessage(`Extension Version: ${context.extension.packageJSON.version ?? '<unknown>'}`);
96+
});
97+
98+
context.subscriptions.push(showVersionsCmd);
99+
100+
const stopCmd = commands.registerCommand(constants.StopServerCommandName, async () => {
61101
for (const langClient of clients.values()) {
62-
langClient?.info('Stopping the server');
63-
await langClient?.stop();
64-
langClient?.info('Server stopped');
102+
langClient?.client.info('Stopping the server');
103+
await langClient?.client.stop();
104+
langClient?.client.info('Server stopped');
65105
}
66106
});
67107

68108
context.subscriptions.push(stopCmd);
69109

70-
const startCmd = commands.registerCommand(StartServerCommandName, async () => {
110+
const startCmd = commands.registerCommand(constants.StartServerCommandName, async () => {
71111
for (const langClient of clients.values()) {
72-
langClient?.info('Starting the server');
73-
await langClient?.start();
74-
langClient?.info('Server started');
112+
langClient?.client.info('Starting the server');
113+
await langClient?.client.start();
114+
langClient?.client.info('Server started');
75115
}
76116
});
77117

@@ -83,6 +123,9 @@ export async function activate(context: ExtensionContext) {
83123

84124
const openOnHackageDisposable = DocsBrowser.registerDocsOpenOnHackage();
85125
context.subscriptions.push(openOnHackageDisposable);
126+
127+
statusBar.refresh();
128+
statusBar.show();
86129
}
87130

88131
async function activateServer(context: ExtensionContext, document: TextDocument) {
@@ -178,7 +221,7 @@ async function activateServerForFolder(context: ExtensionContext, uri: Uri, fold
178221
logger.info(`Support for '.cabal' files: ${cabalFileSupport}`);
179222

180223
switch (cabalFileSupport) {
181-
case 'automatic':
224+
case 'automatic': {
182225
const hlsVersion = await callAsync(
183226
hlsExecutable.location,
184227
['--numeric-version'],
@@ -193,6 +236,7 @@ async function activateServerForFolder(context: ExtensionContext, uri: Uri, fold
193236
documentSelector.push(cabalDocumentSelector);
194237
}
195238
break;
239+
}
196240
case 'enable':
197241
documentSelector.push(cabalDocumentSelector);
198242
break;
@@ -230,7 +274,10 @@ async function activateServerForFolder(context: ExtensionContext, uri: Uri, fold
230274

231275
// Finally start the client and add it to the list of clients.
232276
logger.info('Starting language server');
233-
clients.set(clientsKey, langClient);
277+
clients.set(clientsKey, {
278+
client: langClient,
279+
config,
280+
});
234281
await langClient.start();
235282
}
236283

@@ -290,7 +337,7 @@ export async function deactivate() {
290337
const promises: Thenable<void>[] = [];
291338
for (const client of clients.values()) {
292339
if (client) {
293-
promises.push(client.stop());
340+
promises.push(client.client.stop());
294341
}
295342
}
296343
await Promise.all(promises);

src/statusBar.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as vscode from 'vscode';
2+
import * as constants from './commands/constants';
3+
4+
export class HaskellStatusBar {
5+
readonly item: vscode.StatusBarItem;
6+
constructor(readonly version?: string) {
7+
// Set up the status bar item.
8+
this.item = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
9+
}
10+
11+
refresh(): void {
12+
const version = this.version ?? '<unknown>';
13+
this.item.text = `Haskell`;
14+
15+
this.item.command = constants.OpenLogsCommandName;
16+
this.item.tooltip = new vscode.MarkdownString('', true);
17+
this.item.tooltip.isTrusted = true;
18+
this.item.tooltip.appendMarkdown(
19+
`[Extension Info](command:${constants.ShowExtensionVersions} "Show Extension Version"): Version ${version}\n\n` +
20+
`---\n\n` +
21+
`[$(terminal) Open Logs](command:${constants.OpenLogsCommandName} "Open the logs of the Server and Extension")\n\n` +
22+
`[$(debug-restart) Restart Server](command:${constants.RestartServerCommandName} "Restart Haskell Language Server")\n\n` +
23+
`[$(refresh) Restart Extension](command:${constants.RestartServerCommandName} "Restart vscode-haskell Extension")\n\n`,
24+
);
25+
}
26+
27+
show() {
28+
this.item.show();
29+
}
30+
31+
hide() {
32+
this.item.hide();
33+
}
34+
35+
dispose() {
36+
this.item.dispose();
37+
}
38+
}

0 commit comments

Comments
 (0)