Skip to content

Commit 8f035a4

Browse files
authored
Merge pull request #422 from jneira/fix-ghc-version
- Add much more logging in the client side, configured with haskell.trace.client - Fix error handling of working out project ghc (See #421) - And dont use a shell to spawn the subprocess in non windows systems - Add commands Start Haskell LSP server and Stop Haskell LSP server
2 parents aafd659 + 61d8e3f commit 8f035a4

File tree

7 files changed

+216
-58
lines changed

7 files changed

+216
-58
lines changed

Changelog.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
- Add tracking of cabal files to work together with the incoming cabal formatter plugin
44

5+
### 1.5.1
6+
7+
- Add much more logging in the client side, configured with `haskell.trace.client`
8+
- Fix error handling of `working out project ghc` (See #421)
9+
- And dont use a shell to spawn the subprocess in non windows systems
10+
- Show the progress as a cancellable notification
11+
- Add commands `Start Haskell LSP server` and `Stop Haskell LSP server`
12+
513
### 1.5.0
614

715
- Emit warning about limited support for ghc-9.x on hls executable download

package-lock.json

Lines changed: 3 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "haskell",
33
"displayName": "Haskell",
44
"description": "Haskell language support powered by the Haskell Language Server",
5-
"version": "1.5.0",
5+
"version": "1.5.1",
66
"license": "MIT",
77
"publisher": "haskell",
88
"engines": {
@@ -103,6 +103,17 @@
103103
"default": "off",
104104
"description": "Traces the communication between VS Code and the language server."
105105
},
106+
"haskell.trace.client": {
107+
"scope": "resource",
108+
"type": "string",
109+
"enum": [
110+
"off",
111+
"error",
112+
"debug"
113+
],
114+
"default": "error",
115+
"description": "Traces the communication between VS Code and the language server."
116+
},
106117
"haskell.logFile": {
107118
"scope": "resource",
108119
"type": "string",
@@ -312,6 +323,16 @@
312323
"command": "haskell.commands.restartServer",
313324
"title": "Haskell: Restart Haskell LSP server",
314325
"description": "Restart the Haskell LSP server"
326+
},
327+
{
328+
"command": "haskell.commands.startServer",
329+
"title": "Haskell: Start Haskell LSP server",
330+
"description": "Start the Haskell LSP server"
331+
},
332+
{
333+
"command": "haskell.commands.stopServer",
334+
"title": "Haskell: Stop Haskell LSP server",
335+
"description": "Stop the Haskell LSP server"
315336
}
316337
]
317338
},

src/commands/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
export namespace CommandNames {
22
export const ImportIdentifierCommandName = 'haskell.commands.importIdentifier';
33
export const RestartServerCommandName = 'haskell.commands.restartServer';
4+
export const StartServerCommandName = 'haskell.commands.startServer';
5+
export const StopServerCommandName = 'haskell.commands.stopServer';
46
}

src/extension.ts

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
ExecutableOptions,
1616
LanguageClient,
1717
LanguageClientOptions,
18+
Logger,
1819
RevealOutputChannelOn,
1920
ServerOptions,
2021
TransportKind,
@@ -23,7 +24,7 @@ import { CommandNames } from './commands/constants';
2324
import { ImportIdentifier } from './commands/importIdentifier';
2425
import { DocsBrowser } from './docsBrowser';
2526
import { downloadHaskellLanguageServer } from './hlsBinaries';
26-
import { executableExists } from './utils';
27+
import { executableExists, ExtensionLogger } from './utils';
2728

2829
// The current map of documents & folders to language servers.
2930
// It may be null to indicate that we are in the process of launching a server,
@@ -45,7 +46,10 @@ export async function activate(context: ExtensionContext) {
4546
for (const folder of event.removed) {
4647
const client = clients.get(folder.uri.toString());
4748
if (client) {
48-
clients.delete(folder.uri.toString());
49+
const uri = folder.uri.toString();
50+
client.info(`Deleting folder for clients: ${uri}`);
51+
clients.delete(uri);
52+
client.info('Stopping the server');
4953
client.stop();
5054
}
5155
}
@@ -54,12 +58,35 @@ export async function activate(context: ExtensionContext) {
5458
// Register editor commands for HIE, but only register the commands once at activation.
5559
const restartCmd = commands.registerCommand(CommandNames.RestartServerCommandName, async () => {
5660
for (const langClient of clients.values()) {
61+
langClient?.info('Stopping the server');
5762
await langClient?.stop();
63+
langClient?.info('Starting the server');
5864
langClient?.start();
5965
}
6066
});
67+
6168
context.subscriptions.push(restartCmd);
6269

70+
const stopCmd = commands.registerCommand(CommandNames.StopServerCommandName, async () => {
71+
for (const langClient of clients.values()) {
72+
langClient?.info('Stopping the server');
73+
await langClient?.stop();
74+
langClient?.info('Server stopped');
75+
}
76+
});
77+
78+
context.subscriptions.push(stopCmd);
79+
80+
const startCmd = commands.registerCommand(CommandNames.StartServerCommandName, async () => {
81+
for (const langClient of clients.values()) {
82+
langClient?.info('Starting the server');
83+
langClient?.start();
84+
langClient?.info('Server started');
85+
}
86+
});
87+
88+
context.subscriptions.push(startCmd);
89+
6390
context.subscriptions.push(ImportIdentifier.registerCommand());
6491

6592
// Set up the documentation browser.
@@ -70,30 +97,31 @@ export async function activate(context: ExtensionContext) {
7097
context.subscriptions.push(openOnHackageDisposable);
7198
}
7299

73-
function findManualExecutable(uri: Uri, folder?: WorkspaceFolder): string | null {
100+
function findManualExecutable(logger: Logger, uri: Uri, folder?: WorkspaceFolder): string | null {
74101
let exePath = workspace.getConfiguration('haskell', uri).serverExecutablePath;
75102
if (exePath === '') {
76103
return null;
77104
}
78-
105+
logger.info(`Trying to find the server executable in: ${exePath}`);
79106
// Substitute path variables with their corresponding locations.
80107
exePath = exePath.replace('${HOME}', os.homedir).replace('${home}', os.homedir).replace(/^~/, os.homedir);
81108
if (folder) {
82109
exePath = exePath.replace('${workspaceFolder}', folder.uri.path).replace('${workspaceRoot}', folder.uri.path);
83110
}
84-
111+
logger.info(`Location after path variables subsitution: ${exePath}`);
85112
if (!executableExists(exePath)) {
86-
throw new Error(`serverExecutablePath is set to ${exePath} but it doesn't exist and is not on the PATH`);
113+
throw new Error(`serverExecutablePath is set to ${exePath} but it doesn't exist and it is not on the PATH`);
87114
}
88115
return exePath;
89116
}
90117

91118
/** Searches the PATH for whatever is set in serverVariant */
92-
function findLocalServer(context: ExtensionContext, uri: Uri, folder?: WorkspaceFolder): string | null {
119+
function findLocalServer(context: ExtensionContext, logger: Logger, uri: Uri, folder?: WorkspaceFolder): string | null {
93120
const exes: string[] = ['haskell-language-server-wrapper', 'haskell-language-server'];
94-
121+
logger.info(`Searching for server executables ${exes.join(',')} in $PATH`);
95122
for (const exe of exes) {
96123
if (executableExists(exe)) {
124+
logger.info(`Found server executable in $PATH: ${exe}`);
97125
return exe;
98126
}
99127
}
@@ -120,6 +148,9 @@ async function activeServer(context: ExtensionContext, document: TextDocument) {
120148

121149
async function activateServerForFolder(context: ExtensionContext, uri: Uri, folder?: WorkspaceFolder) {
122150
const clientsKey = folder ? folder.uri.toString() : uri.toString();
151+
// Set a unique name per workspace folder (useful for multi-root workspaces).
152+
const langName = 'Haskell' + (folder ? ` (${folder.name})` : '');
153+
const outputChannel: OutputChannel = window.createOutputChannel(langName);
123154

124155
// If the client already has an LSP server for this uri/folder, then don't start a new one.
125156
if (clients.has(clientsKey)) {
@@ -129,21 +160,25 @@ async function activateServerForFolder(context: ExtensionContext, uri: Uri, fold
129160
clients.set(clientsKey, null);
130161

131162
const logLevel = workspace.getConfiguration('haskell', uri).trace.server;
163+
const clientLogLevel = workspace.getConfiguration('haskell', uri).trace.client;
132164
const logFile = workspace.getConfiguration('haskell', uri).logFile;
133165

166+
const logger: Logger = new ExtensionLogger('client', clientLogLevel, outputChannel);
167+
134168
let serverExecutable;
135169
try {
136170
// Try and find local installations first
137-
serverExecutable = findManualExecutable(uri, folder) ?? findLocalServer(context, uri, folder);
171+
serverExecutable = findManualExecutable(logger, uri, folder) ?? findLocalServer(context, logger, uri, folder);
138172
if (serverExecutable === null) {
139173
// If not, then try to download haskell-language-server binaries if it's selected
140-
serverExecutable = await downloadHaskellLanguageServer(context, uri, folder);
174+
serverExecutable = await downloadHaskellLanguageServer(context, logger, uri, folder);
141175
if (!serverExecutable) {
142176
return;
143177
}
144178
}
145179
} catch (e) {
146180
if (e instanceof Error) {
181+
logger.error(`Error getting the server executable: ${e.message}`);
147182
window.showErrorMessage(e.message);
148183
}
149184
return;
@@ -162,6 +197,12 @@ async function activateServerForFolder(context: ExtensionContext, uri: Uri, fold
162197
// If we're operating on a standalone file (i.e. not in a folder) then we need
163198
// to launch the server in a reasonable current directory. Otherwise the cradle
164199
// guessing logic in hie-bios will be wrong!
200+
if (folder) {
201+
logger.info(`Activating the language server in the workspace folder: ${folder?.uri.fsPath}`);
202+
} else {
203+
logger.info(`Activating the language server in the parent dir of the file: ${uri.fsPath}`);
204+
}
205+
165206
const exeOptions: ExecutableOptions = {
166207
cwd: folder ? undefined : path.dirname(uri.fsPath),
167208
};
@@ -173,15 +214,14 @@ async function activateServerForFolder(context: ExtensionContext, uri: Uri, fold
173214
debug: { command: serverExecutable, transport: TransportKind.stdio, args, options: exeOptions },
174215
};
175216

176-
// Set a unique name per workspace folder (useful for multi-root workspaces).
177-
const langName = 'Haskell' + (folder ? ` (${folder.name})` : '');
178-
const outputChannel: OutputChannel = window.createOutputChannel(langName);
179-
outputChannel.appendLine('[client] run command: "' + serverExecutable + ' ' + args.join(' ') + '"');
180-
outputChannel.appendLine('[client] debug command: "' + serverExecutable + ' ' + args.join(' ') + '"');
181-
182-
outputChannel.appendLine(`[client] server cwd: ${exeOptions.cwd}`);
217+
logger.info(`run command: ${serverExecutable} ${args.join(' ')}`);
218+
logger.info(`debug command: ${serverExecutable} ${args.join(' ')}`);
219+
if (exeOptions.cwd) {
220+
logger.info(`server cwd: ${exeOptions.cwd}`);
221+
}
183222

184223
const pat = folder ? `${folder.uri.fsPath}/**/*` : '**/*';
224+
logger.info(`document selector patten: ${pat}`);
185225
const clientOptions: LanguageClientOptions = {
186226
// Use the document selector to only notify the LSP on files inside the folder
187227
// path for the specific workspace.
@@ -213,6 +253,7 @@ async function activateServerForFolder(context: ExtensionContext, uri: Uri, fold
213253
langClient.registerProposedFeatures();
214254

215255
// Finally start the client and add it to the list of clients.
256+
logger.info('Starting language server');
216257
langClient.start();
217258
clients.set(clientsKey, langClient);
218259
}

0 commit comments

Comments
 (0)