Skip to content

Commit 058e1a6

Browse files
authored
Improve error reporting when SourceKit-LSP fails to start (#982)
* Improve error reporting when SourceKit-LSP fails to start If SourceKit-LSP fails to initialize, typically because of unrecognized launch arguments, don't attempt to restart it and limit the number of erorr dialogs to just one descriptive one. Clicking the "Go to Output" button on this dialog takes you to the SourceKit-LSP Output panel which now shows the SourceKit-LSP launch error. Issue: #976 * Address comments
1 parent dd4464f commit 058e1a6

File tree

3 files changed

+70
-18
lines changed

3 files changed

+70
-18
lines changed

Diff for: src/sourcekit-lsp/LanguageClientManager.ts

+42-13
Original file line numberDiff line numberDiff line change
@@ -434,11 +434,14 @@ export class LanguageClientManager {
434434
this.languageClient = undefined;
435435
return;
436436
}
437-
const client = this.createLSPClient(folder);
438-
return this.startClient(client);
437+
const { client, errorHandler } = this.createLSPClient(folder);
438+
return this.startClient(client, errorHandler);
439439
}
440440

441-
private createLSPClient(folder?: vscode.Uri): langclient.LanguageClient {
441+
private createLSPClient(folder?: vscode.Uri): {
442+
client: langclient.LanguageClient;
443+
errorHandler: SourceKitLSPErrorHandler;
444+
} {
442445
const toolchainSourceKitLSP =
443446
this.workspaceContext.toolchain.getToolchainExecutable("sourcekit-lsp");
444447
const lspConfig = configuration.lsp;
@@ -488,6 +491,7 @@ export class LanguageClientManager {
488491
workspaceFolder = { uri: folder, name: FolderContext.uriName(folder), index: 0 };
489492
}
490493

494+
const errorHandler = new SourceKitLSPErrorHandler(5);
491495
const clientOptions: langclient.LanguageClientOptions = {
492496
documentSelector: LanguageClientManager.documentSelector,
493497
revealOutputChannelOn: langclient.RevealOutputChannelOn.Never,
@@ -563,7 +567,10 @@ export class LanguageClientManager {
563567
};
564568
})(),
565569
},
566-
errorHandler: new SourceKitLSPErrorHandler(5),
570+
errorHandler,
571+
// Avoid attempting to reinitialize multiple times. If we fail to initialize
572+
// we aren't doing anything different the second time and so will fail again.
573+
initializationFailedHandler: () => false,
567574
initializationOptions: {
568575
"workspace/peekDocuments": true, // workaround for client capability to handle `PeekDocumentsRequest`
569576
"textDocument/codeLens": {
@@ -575,15 +582,21 @@ export class LanguageClientManager {
575582
},
576583
};
577584

578-
return new langclient.LanguageClient(
579-
"swift.sourcekit-lsp",
580-
"SourceKit Language Server",
581-
serverOptions,
582-
clientOptions
583-
);
585+
return {
586+
client: new langclient.LanguageClient(
587+
"swift.sourcekit-lsp",
588+
"SourceKit Language Server",
589+
serverOptions,
590+
clientOptions
591+
),
592+
errorHandler,
593+
};
584594
}
585595

586-
private async startClient(client: langclient.LanguageClient) {
596+
private async startClient(
597+
client: langclient.LanguageClient,
598+
errorHandler: SourceKitLSPErrorHandler
599+
) {
587600
client.onDidChangeState(e => {
588601
// if state is now running add in any sub-folder workspaces that
589602
// we have cached. If this is the first time we are starting then
@@ -595,7 +608,6 @@ export class LanguageClientManager {
595608
) {
596609
this.addSubFolderWorkspaces(client);
597610
}
598-
//console.log(e);
599611
});
600612
if (client.clientOptions.workspaceFolder) {
601613
this.workspaceContext.outputChannel.log(
@@ -615,6 +627,10 @@ export class LanguageClientManager {
615627
this.clientReadyPromise = client
616628
.start()
617629
.then(() => {
630+
// Now that we've started up correctly, start the error handler to auto-restart
631+
// if sourcekit-lsp crashes during normal operation.
632+
errorHandler.enable();
633+
618634
if (this.workspaceContext.swiftVersion.isLessThan(new Version(5, 7, 0))) {
619635
this.legacyInlayHints = activateLegacyInlayHints(client);
620636
}
@@ -623,7 +639,6 @@ export class LanguageClientManager {
623639
})
624640
.catch(reason => {
625641
this.workspaceContext.outputChannel.log(`${reason}`);
626-
// if language client failed to initialise then shutdown and set to undefined
627642
this.languageClient?.stop();
628643
this.languageClient = undefined;
629644
throw reason;
@@ -671,10 +686,17 @@ export class LanguageClientManager {
671686
*/
672687
class SourceKitLSPErrorHandler implements langclient.ErrorHandler {
673688
private restarts: number[];
689+
private enabled: boolean = false;
674690

675691
constructor(private maxRestartCount: number) {
676692
this.restarts = [];
677693
}
694+
/**
695+
* Start listening for errors and requesting to restart the LSP server when appropriate.
696+
*/
697+
enable() {
698+
this.enabled = true;
699+
}
678700
/**
679701
* An error has occurred while writing or reading from the connection.
680702
*
@@ -697,6 +719,13 @@ class SourceKitLSPErrorHandler implements langclient.ErrorHandler {
697719
* The connection to the server got closed.
698720
*/
699721
closed(): langclient.CloseHandlerResult | Promise<langclient.CloseHandlerResult> {
722+
if (!this.enabled) {
723+
return {
724+
action: langclient.CloseAction.DoNotRestart,
725+
handled: true,
726+
};
727+
}
728+
700729
this.restarts.push(Date.now());
701730
if (this.restarts.length <= this.maxRestartCount) {
702731
return { action: langclient.CloseAction.Restart };

Diff for: src/ui/SwiftOutputChannel.ts

+11-5
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ class RollingLog {
115115
private startIndex: number = 0;
116116
private endIndex: number = 0;
117117
private logCount: number = 0;
118+
private appending: boolean = false;
118119

119120
constructor(private maxLogs: number) {
120121
this._logs = new Array(maxLogs).fill(null);
@@ -133,23 +134,28 @@ class RollingLog {
133134
}
134135

135136
appendLine(log: string) {
137+
// Writing to a new line that isn't the very first, increment the end index
138+
if (this.logCount > 0) {
139+
this.endIndex = this.incrementIndex(this.endIndex);
140+
}
141+
142+
// We're over the window size, move the start index
136143
if (this.logCount === this.maxLogs) {
137144
this.startIndex = this.incrementIndex(this.startIndex);
138145
} else {
139146
this.logCount++;
140147
}
141148

142149
this._logs[this.endIndex] = log;
143-
this.endIndex = this.incrementIndex(this.endIndex);
144150
}
145151

146152
append(log: string) {
147-
const endIndex = this.endIndex - 1 < 0 ? Math.max(this.logCount - 1, 0) : this.endIndex;
148-
if (this._logs[endIndex] === null) {
153+
if (this.logCount === 0) {
149154
this.logCount = 1;
150155
}
151-
const newLogLine = (this._logs[endIndex] ?? "") + log;
152-
this._logs[endIndex] = newLogLine;
156+
const newLogLine = (this._logs[this.endIndex] ?? "") + log;
157+
this._logs[this.endIndex] = newLogLine;
158+
this.appending = true;
153159
}
154160

155161
replace(log: string) {

Diff for: test/suite/ui/SwiftOutputChannel.test.ts

+17
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ suite("SwiftOutputChannel", function () {
5656
assert.deepEqual(channel.logs, ["b", "cd", "e"]);
5757
});
5858

59+
test("Appends after rolling over", () => {
60+
channel.appendLine("a");
61+
channel.appendLine("b");
62+
channel.appendLine("c");
63+
channel.appendLine("d");
64+
channel.append("e");
65+
channel.appendLine("f");
66+
assert.deepEqual(channel.logs, ["c", "de", "f"]);
67+
});
68+
5969
test("Replaces", () => {
6070
channel.appendLine("a");
6171
channel.appendLine("b");
@@ -64,4 +74,11 @@ suite("SwiftOutputChannel", function () {
6474
channel.replace("e");
6575
assert.deepEqual(channel.logs, ["e"]);
6676
});
77+
78+
test("AppendLine after append terminates appending line", () => {
79+
channel.append("a");
80+
channel.append("b");
81+
channel.appendLine("c");
82+
assert.deepEqual(channel.logs, ["ab", "c"]);
83+
});
6784
});

0 commit comments

Comments
 (0)