diff --git a/src/TestExplorer/LSPTestDiscovery.ts b/src/TestExplorer/LSPTestDiscovery.ts
index 439c3f807..fa2ebf88f 100644
--- a/src/TestExplorer/LSPTestDiscovery.ts
+++ b/src/TestExplorer/LSPTestDiscovery.ts
@@ -16,29 +16,12 @@ import * as vscode from "vscode";
import * as TestDiscovery from "./TestDiscovery";
import {
LSPTestItem,
- textDocumentTestsRequest,
- workspaceTestsRequest,
-} from "../sourcekit-lsp/lspExtensions";
-import { InitializeResult, RequestType } from "vscode-languageclient/node";
+ TextDocumentTestsRequest,
+ WorkspaceTestsRequest,
+} from "../sourcekit-lsp/extensions";
import { SwiftPackage, TargetType } from "../SwiftPackage";
-import { Converter } from "vscode-languageclient/lib/common/protocolConverter";
-
-interface ILanguageClient {
- get initializeResult(): InitializeResult | undefined;
- get protocol2CodeConverter(): Converter;
-
- sendRequest
(
- type: RequestType
,
- params: P,
- token?: vscode.CancellationToken
- ): Promise;
-}
-
-interface ILanguageClientManager {
- useLanguageClient(process: {
- (client: ILanguageClient, cancellationToken: vscode.CancellationToken): Promise;
- }): Promise;
-}
+import { LanguageClientManager } from "../sourcekit-lsp/LanguageClientManager";
+import { LanguageClient } from "vscode-languageclient/node";
/**
* Used to augment test discovery via `swift test --list-tests`.
@@ -49,7 +32,7 @@ interface ILanguageClientManager {
* these results.
*/
export class LSPTestDiscovery {
- constructor(private languageClient: ILanguageClientManager) {}
+ constructor(private languageClient: LanguageClientManager) {}
/**
* Return a list of tests in the supplied document.
@@ -62,15 +45,15 @@ export class LSPTestDiscovery {
return await this.languageClient.useLanguageClient(async (client, token) => {
// Only use the lsp for this request if it supports the
// textDocument/tests method, and is at least version 2.
- if (this.checkExperimentalCapability(client, textDocumentTestsRequest.method, 2)) {
+ if (this.checkExperimentalCapability(client, TextDocumentTestsRequest.method, 2)) {
const testsInDocument = await client.sendRequest(
- textDocumentTestsRequest,
+ TextDocumentTestsRequest.type,
{ textDocument: { uri: document.toString() } },
token
);
return this.transformToTestClass(client, swiftPackage, testsInDocument);
} else {
- throw new Error(`${textDocumentTestsRequest.method} requests not supported`);
+ throw new Error(`${TextDocumentTestsRequest.method} requests not supported`);
}
});
}
@@ -83,11 +66,11 @@ export class LSPTestDiscovery {
return await this.languageClient.useLanguageClient(async (client, token) => {
// Only use the lsp for this request if it supports the
// workspace/tests method, and is at least version 2.
- if (this.checkExperimentalCapability(client, workspaceTestsRequest.method, 2)) {
- const tests = await client.sendRequest(workspaceTestsRequest, {}, token);
+ if (this.checkExperimentalCapability(client, WorkspaceTestsRequest.method, 2)) {
+ const tests = await client.sendRequest(WorkspaceTestsRequest.type, token);
return this.transformToTestClass(client, swiftPackage, tests);
} else {
- throw new Error(`${workspaceTestsRequest.method} requests not supported`);
+ throw new Error(`${WorkspaceTestsRequest.method} requests not supported`);
}
});
}
@@ -97,7 +80,7 @@ export class LSPTestDiscovery {
* above the supplied `minVersion`.
*/
private checkExperimentalCapability(
- client: ILanguageClient,
+ client: LanguageClient,
method: string,
minVersion: number
) {
@@ -114,7 +97,7 @@ export class LSPTestDiscovery {
* updating the format of the location.
*/
private transformToTestClass(
- client: ILanguageClient,
+ client: LanguageClient,
swiftPackage: SwiftPackage,
input: LSPTestItem[]
): TestDiscovery.TestClass[] {
diff --git a/src/TestExplorer/SPMTestDiscovery.ts b/src/TestExplorer/SPMTestDiscovery.ts
index ce5a7323e..8616e1910 100644
--- a/src/TestExplorer/SPMTestDiscovery.ts
+++ b/src/TestExplorer/SPMTestDiscovery.ts
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-import { TestStyle } from "../sourcekit-lsp/lspExtensions";
+import { TestStyle } from "../sourcekit-lsp/extensions";
import { TestClass } from "./TestDiscovery";
/*
diff --git a/src/TestExplorer/TestDiscovery.ts b/src/TestExplorer/TestDiscovery.ts
index 5da5d7dcd..c0017897b 100644
--- a/src/TestExplorer/TestDiscovery.ts
+++ b/src/TestExplorer/TestDiscovery.ts
@@ -14,7 +14,7 @@
import * as vscode from "vscode";
import { SwiftPackage, TargetType } from "../SwiftPackage";
-import { LSPTestItem } from "../sourcekit-lsp/lspExtensions";
+import { LSPTestItem } from "../sourcekit-lsp/extensions";
import { reduceTestItemChildren } from "./TestUtils";
/** Test class definition */
diff --git a/src/commands/reindexProject.ts b/src/commands/reindexProject.ts
index ffb34e6f4..95242c7fd 100644
--- a/src/commands/reindexProject.ts
+++ b/src/commands/reindexProject.ts
@@ -14,7 +14,7 @@
import * as vscode from "vscode";
import { WorkspaceContext } from "../WorkspaceContext";
-import { reindexProjectRequest } from "../sourcekit-lsp/lspExtensions";
+import { ReIndexProjectRequest } from "../sourcekit-lsp/extensions";
/**
* Request that the SourceKit-LSP server reindexes the workspace.
@@ -22,7 +22,7 @@ import { reindexProjectRequest } from "../sourcekit-lsp/lspExtensions";
export function reindexProject(workspaceContext: WorkspaceContext): Promise {
return workspaceContext.languageClientManager.useLanguageClient(async (client, token) => {
try {
- await client.sendRequest(reindexProjectRequest, {}, token);
+ await client.sendRequest(ReIndexProjectRequest.type, token);
const result = await vscode.window.showWarningMessage(
"Re-indexing a project should never be necessary and indicates a bug in SourceKit-LSP. Please file an issue describing which symbol was out-of-date and how you got into the state.",
"Report Issue",
diff --git a/src/sourcekit-lsp/LanguageClientFactory.ts b/src/sourcekit-lsp/LanguageClientFactory.ts
new file mode 100644
index 000000000..ef22cbcf9
--- /dev/null
+++ b/src/sourcekit-lsp/LanguageClientFactory.ts
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the VS Code Swift open source project
+//
+// Copyright (c) 2024 the VS Code Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+import { LanguageClient, LanguageClientOptions, ServerOptions } from "vscode-languageclient/node";
+
+/**
+ * Used to create a {@link LanguageClient} for use in VS Code.
+ *
+ * This is primarily used to make unit testing easier so that we don't have to
+ * mock out a constructor in the `vscode-languageclient` module.
+ */
+export class LanguageClientFactory {
+ /**
+ * Create a new {@link LanguageClient} for use in VS Code.
+ *
+ * @param name the human-readable name for the client
+ * @param id the identifier for the client (used in settings)
+ * @param serverOptions the {@link ServerOptions}
+ * @param clientOptions the {@link LanguageClientOptions}
+ * @returns the newly created {@link LanguageClient}
+ */
+ createLanguageClient(
+ name: string,
+ id: string,
+ serverOptions: ServerOptions,
+ clientOptions: LanguageClientOptions
+ ): LanguageClient {
+ return new LanguageClient(name, id, serverOptions, clientOptions);
+ }
+}
diff --git a/src/sourcekit-lsp/LanguageClientManager.ts b/src/sourcekit-lsp/LanguageClientManager.ts
index 239062740..2d20815b0 100644
--- a/src/sourcekit-lsp/LanguageClientManager.ts
+++ b/src/sourcekit-lsp/LanguageClientManager.ts
@@ -14,7 +14,20 @@
import * as vscode from "vscode";
import * as path from "path";
-import * as langclient from "vscode-languageclient/node";
+import {
+ CloseAction,
+ CloseHandlerResult,
+ DidChangeWorkspaceFoldersNotification,
+ ErrorAction,
+ ErrorHandler,
+ ErrorHandlerResult,
+ LanguageClientOptions,
+ Message,
+ MessageType,
+ RevealOutputChannelOn,
+ State,
+ vsdiag,
+} from "vscode-languageclient";
import configuration from "../configuration";
import { swiftRuntimeEnv } from "../utilities/utilities";
import { isPathInsidePath } from "../utilities/filesystem";
@@ -23,7 +36,7 @@ import { FolderOperation, WorkspaceContext } from "../WorkspaceContext";
import { activateLegacyInlayHints } from "./inlayHints";
import { activatePeekDocuments } from "./peekDocuments";
import { FolderContext } from "../FolderContext";
-import { LanguageClient } from "vscode-languageclient/node";
+import { Executable, LanguageClient, ServerOptions } from "vscode-languageclient/node";
import { ArgumentFilter, BuildFlags } from "../toolchain/BuildFlags";
import { DiagnosticsManager } from "../DiagnosticsManager";
import { LSPLogger, LSPOutputChannel } from "./LSPOutputChannel";
@@ -31,15 +44,14 @@ import { SwiftOutputChannel } from "../ui/SwiftOutputChannel";
import { promptForDiagnostics } from "../commands/captureDiagnostics";
import { activateGetReferenceDocument } from "./getReferenceDocument";
import { uriConverters } from "./uriConverters";
+import { LanguageClientFactory } from "./LanguageClientFactory";
+import { SourceKitLogMessageNotification, SourceKitLogMessageParams } from "./extensions";
-interface SourceKitLogMessageParams extends langclient.LogMessageParams {
- logName?: string;
-}
-
-/** Manages the creation and destruction of Language clients as we move between
+/**
+ * Manages the creation and destruction of Language clients as we move between
* workspace folders
*/
-export class LanguageClientManager {
+export class LanguageClientManager implements vscode.Disposable {
// known log names
static indexingLogName = "SourceKit-LSP: Indexing";
@@ -110,7 +122,7 @@ export class LanguageClientManager {
* undefined means not setup
* null means in the process of restarting
*/
- private languageClient: langclient.LanguageClient | null | undefined;
+ private languageClient: LanguageClient | null | undefined;
private cancellationToken?: vscode.CancellationTokenSource;
private legacyInlayHints?: vscode.Disposable;
private peekDocuments?: vscode.Disposable;
@@ -130,14 +142,17 @@ export class LanguageClientManager {
public subFolderWorkspaces: vscode.Uri[];
private namedOutputChannels: Map = new Map();
/** Get the current state of the underlying LanguageClient */
- public get state(): langclient.State {
+ public get state(): State {
if (!this.languageClient) {
- return langclient.State.Stopped;
+ return State.Stopped;
}
return this.languageClient.state;
}
- constructor(public workspaceContext: WorkspaceContext) {
+ constructor(
+ public workspaceContext: WorkspaceContext,
+ private languageClientFactory: LanguageClientFactory = new LanguageClientFactory()
+ ) {
this.namedOutputChannels.set(
LanguageClientManager.indexingLogName,
new LSPOutputChannel(LanguageClientManager.indexingLogName, false, true)
@@ -196,7 +211,7 @@ export class LanguageClientManager {
// Enabling/Disabling sourcekit-lsp shows a special notification
if (event.affectsConfiguration("swift.sourcekit-lsp.disable")) {
if (configuration.lsp.disable) {
- if (this.state === langclient.State.Stopped) {
+ if (this.state === State.Stopped) {
// Language client is already stopped
return;
}
@@ -204,7 +219,7 @@ export class LanguageClientManager {
"You have disabled the Swift language server, but it is still running. Would you like to stop it now?";
restartLSPButton = "Stop Language Server";
} else {
- if (this.state !== langclient.State.Stopped) {
+ if (this.state !== State.Stopped) {
// Langauge client is already running
return;
}
@@ -212,7 +227,7 @@ export class LanguageClientManager {
"You have enabled the Swift language server. Would you like to start it now?";
restartLSPButton = "Start Language Server";
}
- } else if (configuration.lsp.disable && this.state === langclient.State.Stopped) {
+ } else if (configuration.lsp.disable && this.state === State.Stopped) {
// Ignore configuration changes if SourceKit-LSP is disabled
return;
}
@@ -248,7 +263,7 @@ export class LanguageClientManager {
// The language client stops asnyhronously, so we need to wait for it to stop
// instead of doing it in dispose, which must be synchronous.
async stop() {
- if (this.languageClient && this.languageClient.state === langclient.State.Running) {
+ if (this.languageClient && this.languageClient.state === State.Running) {
await this.languageClient.dispose();
}
}
@@ -271,10 +286,7 @@ export class LanguageClientManager {
* @returns result of process
*/
async useLanguageClient(process: {
- (
- client: langclient.LanguageClient,
- cancellationToken: vscode.CancellationToken
- ): Promise;
+ (client: LanguageClient, cancellationToken: vscode.CancellationToken): Promise;
}): Promise {
if (!this.languageClient || !this.clientReadyPromise) {
throw new Error(LanguageClientError.LanguageClientUnavailable);
@@ -310,7 +322,7 @@ export class LanguageClientManager {
uri: client.code2ProtocolConverter.asUri(uri),
name: FolderContext.uriName(uri),
};
- client.sendNotification(langclient.DidChangeWorkspaceFoldersNotification.type, {
+ client.sendNotification(DidChangeWorkspaceFoldersNotification.type, {
event: { added: [workspaceFolder], removed: [] },
});
});
@@ -327,7 +339,7 @@ export class LanguageClientManager {
uri: client.code2ProtocolConverter.asUri(uri),
name: FolderContext.uriName(uri),
};
- client.sendNotification(langclient.DidChangeWorkspaceFoldersNotification.type, {
+ client.sendNotification(DidChangeWorkspaceFoldersNotification.type, {
event: { added: [], removed: [workspaceFolder] },
});
});
@@ -340,7 +352,7 @@ export class LanguageClientManager {
uri: client.code2ProtocolConverter.asUri(uri),
name: FolderContext.uriName(uri),
};
- client.sendNotification(langclient.DidChangeWorkspaceFoldersNotification.type, {
+ client.sendNotification(DidChangeWorkspaceFoldersNotification.type, {
event: { added: [workspaceFolder], removed: [] },
});
}
@@ -453,7 +465,7 @@ export class LanguageClientManager {
}
private createLSPClient(folder?: vscode.Uri): {
- client: langclient.LanguageClient;
+ client: LanguageClient;
errorHandler: SourceKitLSPErrorHandler;
} {
const toolchainSourceKitLSP =
@@ -471,7 +483,7 @@ export class LanguageClientManager {
),
];
- const sourcekit: langclient.Executable = {
+ const sourcekit: Executable = {
command: serverPath,
args: lspConfig.serverArguments.concat(sdkArguments),
options: {
@@ -499,16 +511,16 @@ export class LanguageClientManager {
};
}
- const serverOptions: langclient.ServerOptions = sourcekit;
+ const serverOptions: ServerOptions = sourcekit;
let workspaceFolder = undefined;
if (folder) {
workspaceFolder = { uri: folder, name: FolderContext.uriName(folder), index: 0 };
}
const errorHandler = new SourceKitLSPErrorHandler(5);
- const clientOptions: langclient.LanguageClientOptions = {
+ const clientOptions: LanguageClientOptions = {
documentSelector: LanguageClientManager.documentSelector,
- revealOutputChannelOn: langclient.RevealOutputChannelOn.Never,
+ revealOutputChannelOn: RevealOutputChannelOn.Never,
workspaceFolder: workspaceFolder,
outputChannel: new SwiftOutputChannel("SourceKit Language Server", false),
middleware: {
@@ -545,7 +557,7 @@ export class LanguageClientManager {
},
provideDiagnostics: async (uri, previousResultId, token, next) => {
const result = await next(uri, previousResultId, token);
- if (result?.kind === langclient.vsdiag.DocumentDiagnosticReportKind.unChanged) {
+ if (result?.kind === vsdiag.DocumentDiagnosticReportKind.unChanged) {
return undefined;
}
const document = uri as vscode.TextDocument;
@@ -590,7 +602,7 @@ export class LanguageClientManager {
};
return {
- client: new langclient.LanguageClient(
+ client: this.languageClientFactory.createLanguageClient(
"swift.sourcekit-lsp",
"SourceKit Language Server",
serverOptions,
@@ -622,21 +634,14 @@ export class LanguageClientManager {
}
return options;
}
- /* eslint-enable @typescript-eslint/no-explicit-any */
- private async startClient(
- client: langclient.LanguageClient,
- errorHandler: SourceKitLSPErrorHandler
- ) {
+ private async startClient(client: LanguageClient, errorHandler: SourceKitLSPErrorHandler) {
client.onDidChangeState(e => {
// if state is now running add in any sub-folder workspaces that
// we have cached. If this is the first time we are starting then
// we won't have any sub folder workspaces, but if the server crashed
// or we forced a restart then we need to do this
- if (
- e.oldState === langclient.State.Starting &&
- e.newState === langclient.State.Running
- ) {
+ if (e.oldState === State.Starting && e.newState === State.Running) {
this.addSubFolderWorkspaces(client);
}
});
@@ -650,7 +655,7 @@ export class LanguageClientManager {
this.workspaceContext.outputChannel.log(`SourceKit-LSP setup`);
}
- client.onNotification(langclient.LogMessageNotification.type, params => {
+ client.onNotification(SourceKitLogMessageNotification.type, params => {
this.logMessage(client, params as SourceKitLogMessageParams);
});
@@ -683,7 +688,7 @@ export class LanguageClientManager {
return this.clientReadyPromise;
}
- private logMessage(client: langclient.LanguageClient, params: SourceKitLogMessageParams) {
+ private logMessage(client: LanguageClient, params: SourceKitLogMessageParams) {
let logger: LSPLogger = client;
if (params.logName) {
const outputChannel =
@@ -693,19 +698,19 @@ export class LanguageClientManager {
logger = outputChannel;
}
switch (params.type) {
- case langclient.MessageType.Info:
+ case MessageType.Info:
logger.info(params.message);
break;
- case langclient.MessageType.Debug:
+ case MessageType.Debug:
logger.debug(params.message);
break;
- case langclient.MessageType.Warning:
+ case MessageType.Warning:
logger.warn(params.message);
break;
- case langclient.MessageType.Error:
+ case MessageType.Error:
logger.error(params.message);
break;
- case langclient.MessageType.Log:
+ case MessageType.Log:
logger.info(params.message);
break;
}
@@ -717,7 +722,7 @@ export class LanguageClientManager {
* an error message that asks if you want to restart the sourcekit-lsp server again
* after so many crashes
*/
-export class SourceKitLSPErrorHandler implements langclient.ErrorHandler {
+export class SourceKitLSPErrorHandler implements ErrorHandler {
private restarts: number[];
private enabled: boolean = false;
@@ -740,32 +745,32 @@ export class SourceKitLSPErrorHandler implements langclient.ErrorHandler {
*/
error(
_error: Error,
- _message: langclient.Message | undefined,
+ _message: Message | undefined,
count: number | undefined
- ): langclient.ErrorHandlerResult | Promise {
+ ): ErrorHandlerResult | Promise {
if (count && count <= 3) {
- return { action: langclient.ErrorAction.Continue };
+ return { action: ErrorAction.Continue };
}
- return { action: langclient.ErrorAction.Shutdown };
+ return { action: ErrorAction.Shutdown };
}
/**
* The connection to the server got closed.
*/
- closed(): langclient.CloseHandlerResult | Promise {
+ closed(): CloseHandlerResult | Promise {
if (!this.enabled) {
return {
- action: langclient.CloseAction.DoNotRestart,
+ action: CloseAction.DoNotRestart,
handled: true,
};
}
this.restarts.push(Date.now());
if (this.restarts.length <= this.maxRestartCount) {
- return { action: langclient.CloseAction.Restart };
+ return { action: CloseAction.Restart };
} else {
const diff = this.restarts[this.restarts.length - 1] - this.restarts[0];
if (diff <= 3 * 60 * 1000) {
- return new Promise(resolve => {
+ return new Promise(resolve => {
vscode.window
.showErrorMessage(
`The SourceKit-LSP server crashed ${
@@ -777,15 +782,15 @@ export class SourceKitLSPErrorHandler implements langclient.ErrorHandler {
.then(result => {
if (result === "Yes") {
this.restarts = [];
- resolve({ action: langclient.CloseAction.Restart });
+ resolve({ action: CloseAction.Restart });
} else {
- resolve({ action: langclient.CloseAction.DoNotRestart });
+ resolve({ action: CloseAction.DoNotRestart });
}
});
});
} else {
this.restarts.shift();
- return { action: langclient.CloseAction.Restart };
+ return { action: CloseAction.Restart };
}
}
}
diff --git a/src/sourcekit-lsp/extensions/GetReferenceDocumentRequest.ts b/src/sourcekit-lsp/extensions/GetReferenceDocumentRequest.ts
new file mode 100644
index 000000000..eff0c4b9a
--- /dev/null
+++ b/src/sourcekit-lsp/extensions/GetReferenceDocumentRequest.ts
@@ -0,0 +1,55 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the VS Code Swift open source project
+//
+// Copyright (c) 2021-2024 the VS Code Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+// We use namespaces to store request information just like vscode-languageclient
+/* eslint-disable @typescript-eslint/no-namespace */
+
+import { DocumentUri, MessageDirection, RequestType } from "vscode-languageclient";
+
+/** Parameters used to make a {@link GetReferenceDocumentRequest}. */
+export interface GetReferenceDocumentParams {
+ /** The {@link DocumentUri} of the custom scheme url for which content is required. */
+ uri: DocumentUri;
+}
+
+/** Response containing `content` of a {@link GetReferenceDocumentRequest}. */
+export interface GetReferenceDocumentResult {
+ content: string;
+}
+
+/**
+ * Request from the client to the server asking for contents of a URI having a custom scheme **(LSP Extension)**
+ * For example: "sourcekit-lsp:"
+ *
+ * - Parameters:
+ * - uri: The `DocumentUri` of the custom scheme url for which content is required
+ *
+ * - Returns: `GetReferenceDocumentResponse` which contains the `content` to be displayed.
+ *
+ * ### LSP Extension
+ *
+ * This request is an extension to LSP supported by SourceKit-LSP.
+ *
+ * Enable the experimental client capability `"workspace/getReferenceDocument"` so that the server responds with
+ * reference document URLs for certain requests or commands whenever possible.
+ */
+export namespace GetReferenceDocumentRequest {
+ export const method = "workspace/getReferenceDocument" as const;
+ export const messageDirection: MessageDirection = MessageDirection.clientToServer;
+ export const type = new RequestType<
+ GetReferenceDocumentParams,
+ GetReferenceDocumentResult,
+ never
+ >(method);
+}
diff --git a/src/sourcekit-lsp/extensions/GetTestsRequest.ts b/src/sourcekit-lsp/extensions/GetTestsRequest.ts
new file mode 100644
index 000000000..21da8317e
--- /dev/null
+++ b/src/sourcekit-lsp/extensions/GetTestsRequest.ts
@@ -0,0 +1,114 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the VS Code Swift open source project
+//
+// Copyright (c) 2021-2024 the VS Code Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+// We use namespaces to store request information just like vscode-languageclient
+/* eslint-disable @typescript-eslint/no-namespace */
+
+import {
+ Location,
+ TextDocumentIdentifier,
+ MessageDirection,
+ RequestType0,
+ RequestType,
+} from "vscode-languageclient";
+
+/** Test styles where test-target represents a test target that contains tests. */
+export type TestStyle = "XCTest" | "swift-testing" | "test-target";
+
+/** Represents a single test returned from a {@link WorkspaceTestsRequest} or {@link TextDocumentTestsRequest}. */
+export interface LSPTestItem {
+ /**
+ * This identifier uniquely identifies the test case or test suite. It can be used to run an individual test (suite).
+ */
+ id: string;
+
+ /**
+ * Display name describing the test.
+ */
+ label: string;
+
+ /**
+ * Optional description that appears next to the label.
+ */
+ description?: string;
+
+ /**
+ * A string that should be used when comparing this item with other items.
+ *
+ * When `undefined` the `label` is used.
+ */
+ sortText?: string;
+
+ /**
+ * Whether the test is disabled.
+ */
+ disabled: boolean;
+
+ /**
+ * The type of test, eg. the testing framework that was used to declare the test.
+ */
+ style: TestStyle;
+
+ /**
+ * The location of the test item in the source code.
+ */
+ location: Location;
+
+ /**
+ * The children of this test item.
+ *
+ * For a test suite, this may contain the individual test cases or nested suites.
+ */
+ children: LSPTestItem[];
+
+ /**
+ * Tags associated with this test item.
+ */
+ tags: { id: string }[];
+}
+
+/**
+ * A request that returns symbols for all the test classes and test methods within the current workspace.
+ *
+ * ### LSP Extension
+ *
+ * This request is an extension to LSP supported by SourceKit-LSP.
+ *
+ * It requires the experimental client capability `"workspace/tests"` to use.
+ */
+export namespace WorkspaceTestsRequest {
+ export const method = "workspace/tests" as const;
+ export const messageDirection: MessageDirection = MessageDirection.clientToServer;
+ export const type = new RequestType0(method);
+}
+
+/** Parameters used to make a {@link TextDocumentTestsRequest}. */
+export interface TextDocumentTestsParams {
+ textDocument: TextDocumentIdentifier;
+}
+
+/**
+ * A request that returns symbols for all the test classes and test methods within a file.
+ *
+ * ### LSP Extension
+ *
+ * This request is an extension to LSP supported by SourceKit-LSP.
+ *
+ * It requires the experimental client capability `"textDocument/tests"` to use.
+ */
+export namespace TextDocumentTestsRequest {
+ export const method = "textDocument/tests" as const;
+ export const messageDirection: MessageDirection = MessageDirection.clientToServer;
+ export const type = new RequestType(method);
+}
diff --git a/src/sourcekit-lsp/extensions/LegacyInlayHintRequest.ts b/src/sourcekit-lsp/extensions/LegacyInlayHintRequest.ts
new file mode 100644
index 000000000..1c23386b6
--- /dev/null
+++ b/src/sourcekit-lsp/extensions/LegacyInlayHintRequest.ts
@@ -0,0 +1,72 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the VS Code Swift open source project
+//
+// Copyright (c) 2021-2024 the VS Code Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+// We use namespaces to store request information just like vscode-languageclient
+/* eslint-disable @typescript-eslint/no-namespace */
+
+import {
+ TextDocumentIdentifier,
+ Range,
+ Position,
+ MessageDirection,
+ RequestType,
+} from "vscode-languageclient";
+
+/** Parameters used to make a {@link LegacyInlayHintRequest}. */
+export interface LegacyInlayHintsParams {
+ /**
+ * The text document.
+ */
+ textDocument: TextDocumentIdentifier;
+
+ /**
+ * If set, the reange for which inlay hints are
+ * requested. If unset, hints for the entire document
+ * are returned.
+ */
+ range?: Range;
+
+ /**
+ * The categories of inlay hints that are requested.
+ * If unset, all categories are returned.
+ */
+ only?: string[];
+}
+
+/** Inlay Hint (pre Swift 5.6) */
+export interface LegacyInlayHint {
+ /**
+ * The position within the code that this hint is
+ * attached to.
+ */
+ position: Position;
+
+ /**
+ * The hint's kind, used for more flexible client-side
+ * styling of the hint.
+ */
+ category?: string;
+
+ /**
+ * The hint's rendered label.
+ */
+ label: string;
+}
+
+/** Inlay Hints (pre Swift 5.6) */
+export namespace LegacyInlayHintRequest {
+ export const method = "sourcekit-lsp/inlayHints" as const;
+ export const messageDirection: MessageDirection = MessageDirection.clientToServer;
+ export const type = new RequestType(method);
+}
diff --git a/src/sourcekit-lsp/extensions/PeekDocumentsRequest.ts b/src/sourcekit-lsp/extensions/PeekDocumentsRequest.ts
new file mode 100644
index 000000000..ea1302b40
--- /dev/null
+++ b/src/sourcekit-lsp/extensions/PeekDocumentsRequest.ts
@@ -0,0 +1,68 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the VS Code Swift open source project
+//
+// Copyright (c) 2021-2024 the VS Code Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+// We use namespaces to store request information just like vscode-languageclient
+/* eslint-disable @typescript-eslint/no-namespace */
+
+import { DocumentUri, Position, MessageDirection, RequestType } from "vscode-languageclient";
+
+/** Parameters used to make a {@link PeekDocumentsRequest}. */
+export interface PeekDocumentsParams {
+ /**
+ * The `DocumentUri` of the text document in which to show the "peeked" editor
+ */
+ uri: DocumentUri;
+
+ /**
+ * The `Position` in the given text document in which to show the "peeked editor"
+ */
+ position: Position;
+
+ /**
+ * An array `DocumentUri` of the documents to appear inside the "peeked" editor
+ */
+ locations: DocumentUri[];
+}
+
+/** Response to indicate the `success` of the {@link PeekDocumentsRequest}. */
+export interface PeekDocumentsResponse {
+ success: boolean;
+}
+
+/**
+ * Request from the server to the client to show the given documents in a "peeked" editor **(LSP Extension)**
+ *
+ * This request is handled by the client to show the given documents in a
+ * "peeked" editor (i.e. inline with / inside the editor canvas). This is
+ * similar to VS Code's built-in "editor.action.peekLocations" command.
+ *
+ * - Parameters:
+ * - uri: The {@link DocumentUri} of the text document in which to show the "peeked" editor
+ * - position: The {@link Position} in the given text document in which to show the "peeked editor"
+ * - locations: The {@link DocumentUri} of documents to appear inside the "peeked" editor
+ *
+ * - Returns: {@link PeekDocumentsResponse} which indicates the `success` of the request.
+ *
+ * ### LSP Extension
+ *
+ * This request is an extension to LSP supported by SourceKit-LSP.
+ *
+ * It requires the experimental client capability `"workspace/peekDocuments"` to use.
+ * It also needs the client to handle the request and present the "peeked" editor.
+ */
+export namespace PeekDocumentsRequest {
+ export const method = "workspace/peekDocuments" as const;
+ export const messageDirection: MessageDirection = MessageDirection.clientToServer;
+ export const type = new RequestType(method);
+}
diff --git a/src/sourcekit-lsp/extensions/ReIndexProjectRequest.ts b/src/sourcekit-lsp/extensions/ReIndexProjectRequest.ts
new file mode 100644
index 000000000..184ef12ab
--- /dev/null
+++ b/src/sourcekit-lsp/extensions/ReIndexProjectRequest.ts
@@ -0,0 +1,35 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the VS Code Swift open source project
+//
+// Copyright (c) 2021-2024 the VS Code Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+// We use namespaces to store request information just like vscode-languageclient
+/* eslint-disable @typescript-eslint/no-namespace */
+
+import { MessageDirection, RequestType0 } from "vscode-languageclient";
+
+/**
+ * Re-index all files open in the SourceKit-LSP server.
+ *
+ * Users should not need to rely on this request. The index should always be updated automatically in the background.
+ * Having to invoke this request means there is a bug in SourceKit-LSP's automatic re-indexing. It does, however, offer
+ * a workaround to re-index files when such a bug occurs where otherwise there would be no workaround.
+ *
+ * ### LSP Extension
+ *
+ * This request is an extension to LSP supported by SourceKit-LSP.
+ */
+export namespace ReIndexProjectRequest {
+ export const method = "workspace/triggerReindex" as const;
+ export const messageDirection: MessageDirection = MessageDirection.clientToServer;
+ export const type = new RequestType0("workspace/triggerReindex");
+}
diff --git a/src/sourcekit-lsp/extensions/SourceKitLogMessageNotification.ts b/src/sourcekit-lsp/extensions/SourceKitLogMessageNotification.ts
new file mode 100644
index 000000000..f81412817
--- /dev/null
+++ b/src/sourcekit-lsp/extensions/SourceKitLogMessageNotification.ts
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the VS Code Swift open source project
+//
+// Copyright (c) 2021-2024 the VS Code Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+// We use namespaces to store request information just like vscode-languageclient
+/* eslint-disable @typescript-eslint/no-namespace */
+
+import { LogMessageParams, MessageDirection, NotificationType } from "vscode-languageclient";
+
+/** Parameters sent in a {@link SourceKitLogMessageNotification}. */
+export interface SourceKitLogMessageParams extends LogMessageParams {
+ logName?: string;
+}
+
+/**
+ * The log message notification is sent from the server to the client to ask the client to
+ * log a particular message.
+ *
+ * ### LSP Extension
+ *
+ * This notification has the same behaviour as the `window/logMessage` notification built
+ * into the LSP. However, SourceKit-LSP adds extra information to the parameters.
+ */
+export namespace SourceKitLogMessageNotification {
+ export const method = "window/logMessage" as const;
+ export const messageDirection: MessageDirection = MessageDirection.serverToClient;
+ export const type = new NotificationType(method);
+}
diff --git a/src/sourcekit-lsp/extensions/SymbolInfoRequest.ts b/src/sourcekit-lsp/extensions/SymbolInfoRequest.ts
new file mode 100644
index 000000000..0fdcbbf96
--- /dev/null
+++ b/src/sourcekit-lsp/extensions/SymbolInfoRequest.ts
@@ -0,0 +1,156 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the VS Code Swift open source project
+//
+// Copyright (c) 2021-2024 the VS Code Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+// We use namespaces to store request information just like vscode-languageclient
+/* eslint-disable @typescript-eslint/no-namespace */
+
+import {
+ TextDocumentIdentifier,
+ Position,
+ Location,
+ SymbolKind,
+ MessageDirection,
+ RequestType,
+} from "vscode-languageclient";
+
+/** Parameters used to make a {@link SymbolInfoRequest}. */
+export interface SymbolInfoParams {
+ /** The document in which to lookup the symbol location. */
+ textDocument: TextDocumentIdentifier;
+
+ /** The document location at which to lookup symbol information. */
+ position: Position;
+}
+
+/** Information about which module a symbol is defined in. */
+export interface ModuleInfo {
+ /** The name of the module in which the symbol is defined. */
+ moduleName: string;
+
+ /** If the symbol is defined within a subgroup of a module, the name of the group. */
+ groupName?: string;
+}
+
+/** Detailed information about a symbol, such as the response to a {@link SymbolInfoRequest}. */
+export interface SymbolDetails {
+ /** The name of the symbol, if any. */
+ name?: string;
+
+ /**
+ * The name of the containing type for the symbol, if any.
+ *
+ * For example, in the following snippet, the `containerName` of `foo()` is `C`.
+ *
+ * ```c++
+ * class C {
+ * void foo() {}
+ * }
+ * ```
+ */
+ containerName?: string;
+
+ /** The USR of the symbol, if any. */
+ usr?: string;
+
+ /**
+ * Best known declaration or definition location without global knowledge.
+ *
+ * For a local or private variable, this is generally the canonical definition location -
+ * appropriate as a response to a `textDocument/definition` request. For global symbols this is
+ * the best known location within a single compilation unit. For example, in C++ this might be
+ * the declaration location from a header as opposed to the definition in some other
+ * translation unit.
+ * */
+ bestLocalDeclaration?: Location;
+
+ /** The kind of the symbol */
+ kind?: SymbolKind;
+
+ /**
+ * Whether the symbol is a dynamic call for which it isn't known which method will be invoked at runtime. This is
+ * the case for protocol methods and class functions.
+ *
+ * Optional because `clangd` does not return whether a symbol is dynamic.
+ */
+ isDynamic?: boolean;
+
+ /**
+ * Whether this symbol is defined in the SDK or standard library.
+ *
+ * This property only applies to Swift symbols.
+ */
+ isSystem?: boolean;
+
+ /**
+ * If the symbol is dynamic, the USRs of the types that might be called.
+ *
+ * This is relevant in the following cases:
+ * ```swift
+ * class A {
+ * func doThing() {}
+ * }
+ * class B: A {}
+ * class C: B {
+ * override func doThing() {}
+ * }
+ * class D: A {
+ * override func doThing() {}
+ * }
+ * func test(value: B) {
+ * value.doThing()
+ * }
+ * ```
+ *
+ * The USR of the called function in `value.doThing` is `A.doThing` (or its
+ * mangled form) but it can never call `D.doThing`. In this case, the
+ * receiver USR would be `B`, indicating that only overrides of subtypes in
+ * `B` may be called dynamically.
+ */
+ receiverUsrs?: string[];
+
+ /**
+ * If the symbol is defined in a module that doesn't have source information associated with it, the name and group
+ * and group name that defines this symbol.
+ *
+ * This property only applies to Swift symbols.
+ */
+ systemModule?: ModuleInfo;
+}
+
+/**
+ * Request for semantic information about the symbol at a given location **(LSP Extension)**.
+ *
+ * This request looks up the symbol (if any) at a given text document location and returns
+ * SymbolDetails for that location, including information such as the symbol's USR. The symbolInfo
+ * request is not primarily designed for editors, but instead as an implementation detail of how
+ * one LSP implementation (e.g. SourceKit) gets information from another (e.g. clangd) to use in
+ * performing index queries or otherwise implementing the higher level requests such as definition.
+ *
+ * - Parameters:
+ * - textDocument: The document in which to lookup the symbol location.
+ * - position: The document location at which to lookup symbol information.
+ *
+ * - Returns: `[SymbolDetails]` for the given location, which may have multiple elements if there are
+ * multiple references, or no elements if there is no symbol at the given location.
+ *
+ * ### LSP Extension
+ *
+ * This request is an extension to LSP supported by SourceKit-LSP and clangd. It does *not* require
+ * any additional client or server capabilities to use.
+ */
+export namespace SymbolInfoRequest {
+ export const method = "textDocument/documentSymbol" as const;
+ export const messageDirection: MessageDirection = MessageDirection.clientToServer;
+ export const type = new RequestType(method);
+}
diff --git a/src/sourcekit-lsp/extensions/index.ts b/src/sourcekit-lsp/extensions/index.ts
new file mode 100644
index 000000000..93553f663
--- /dev/null
+++ b/src/sourcekit-lsp/extensions/index.ts
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// This source file is part of the VS Code Swift open source project
+//
+// Copyright (c) 2021-2024 the VS Code Swift project authors
+// Licensed under Apache License v2.0
+//
+// See LICENSE.txt for license information
+// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+//===----------------------------------------------------------------------===//
+
+// Definitions for non-standard requests used by sourcekit-lsp
+
+export * from "./GetReferenceDocumentRequest";
+export * from "./GetTestsRequest";
+export * from "./LegacyInlayHintRequest";
+export * from "./PeekDocumentsRequest";
+export * from "./ReIndexProjectRequest";
+export * from "./SourceKitLogMessageNotification";
+export * from "./SymbolInfoRequest";
diff --git a/src/sourcekit-lsp/getReferenceDocument.ts b/src/sourcekit-lsp/getReferenceDocument.ts
index b913a18cf..7a29b98be 100644
--- a/src/sourcekit-lsp/getReferenceDocument.ts
+++ b/src/sourcekit-lsp/getReferenceDocument.ts
@@ -14,7 +14,7 @@
import * as vscode from "vscode";
import * as langclient from "vscode-languageclient/node";
-import { GetReferenceDocumentParams, GetReferenceDocumentRequest } from "./lspExtensions";
+import { GetReferenceDocumentParams, GetReferenceDocumentRequest } from "./extensions";
export function activateGetReferenceDocument(client: langclient.LanguageClient): vscode.Disposable {
const getReferenceDocument = vscode.workspace.registerTextDocumentContentProvider(
@@ -25,7 +25,11 @@ export function activateGetReferenceDocument(client: langclient.LanguageClient):
uri: client.code2ProtocolConverter.asUri(uri),
};
- const result = await client.sendRequest(GetReferenceDocumentRequest, params, token);
+ const result = await client.sendRequest(
+ GetReferenceDocumentRequest.type,
+ params,
+ token
+ );
if (result) {
return result.content;
diff --git a/src/sourcekit-lsp/inlayHints.ts b/src/sourcekit-lsp/inlayHints.ts
index 400329ee1..c73eebf42 100644
--- a/src/sourcekit-lsp/inlayHints.ts
+++ b/src/sourcekit-lsp/inlayHints.ts
@@ -16,7 +16,7 @@ import * as vscode from "vscode";
import * as langclient from "vscode-languageclient/node";
import configuration from "../configuration";
import { LanguageClientManager } from "./LanguageClientManager";
-import { legacyInlayHintsRequest } from "./lspExtensions";
+import { LegacyInlayHintRequest } from "./extensions";
/** Provide Inlay Hints using sourcekit-lsp */
class SwiftLegacyInlayHintsProvider implements vscode.InlayHintsProvider {
@@ -37,7 +37,7 @@ class SwiftLegacyInlayHintsProvider implements vscode.InlayHintsProvider {
textDocument: this.client.code2ProtocolConverter.asTextDocumentIdentifier(document),
range: { start: range.start, end: range.end },
};
- const result = this.client.sendRequest(legacyInlayHintsRequest, params, token);
+ const result = this.client.sendRequest(LegacyInlayHintRequest.type, params, token);
return result.then(
hints => {
return hints.map(hint => {
diff --git a/src/sourcekit-lsp/lspExtensions.ts b/src/sourcekit-lsp/lspExtensions.ts
deleted file mode 100644
index af5cda62a..000000000
--- a/src/sourcekit-lsp/lspExtensions.ts
+++ /dev/null
@@ -1,205 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// This source file is part of the VS Code Swift open source project
-//
-// Copyright (c) 2021-2024 the VS Code Swift project authors
-// Licensed under Apache License v2.0
-//
-// See LICENSE.txt for license information
-// See CONTRIBUTORS.txt for the list of VS Code Swift project authors
-//
-// SPDX-License-Identifier: Apache-2.0
-//
-//===----------------------------------------------------------------------===//
-
-import * as ls from "vscode-languageserver-protocol";
-import * as langclient from "vscode-languageclient/node";
-import * as vscode from "vscode";
-
-// Definitions for non-standard requests used by sourcekit-lsp
-
-// Peek Documents
-export interface PeekDocumentsParams {
- /**
- * The `DocumentUri` of the text document in which to show the "peeked" editor
- */
- uri: langclient.DocumentUri;
-
- /**
- * The `Position` in the given text document in which to show the "peeked editor"
- */
- position: vscode.Position;
-
- /**
- * An array `DocumentUri` of the documents to appear inside the "peeked" editor
- */
- locations: langclient.DocumentUri[];
-}
-
-/**
- * Response to indicate the `success` of the `PeekDocumentsRequest`
- */
-export interface PeekDocumentsResult {
- success: boolean;
-}
-
-/**
- * Request from the server to the client to show the given documents in a "peeked" editor.
- *
- * This request is handled by the client to show the given documents in a "peeked" editor (i.e. inline with / inside the editor canvas).
- *
- * It requires the experimental client capability `"workspace/peekDocuments"` to use.
- */
-export const PeekDocumentsRequest = new langclient.RequestType<
- PeekDocumentsParams,
- PeekDocumentsResult,
- unknown
->("workspace/peekDocuments");
-
-// Get Reference Document
-export interface GetReferenceDocumentParams {
- /**
- * The `DocumentUri` of the custom scheme url for which content is required
- */
- uri: langclient.DocumentUri;
-}
-
-/**
- * Response containing `content` of `GetReferenceDocumentRequest`
- */
-export interface GetReferenceDocumentResult {
- content: string;
-}
-
-/**
- * Request from the client to the server asking for contents of a URI having a custom scheme
- * For example: "sourcekit-lsp:"
- */
-export const GetReferenceDocumentRequest = new langclient.RequestType<
- GetReferenceDocumentParams,
- GetReferenceDocumentResult,
- unknown
->("workspace/getReferenceDocument");
-
-// Inlay Hints (pre Swift 5.6)
-export interface LegacyInlayHintsParams {
- /**
- * The text document.
- */
- textDocument: langclient.TextDocumentIdentifier;
-
- /**
- * If set, the reange for which inlay hints are
- * requested. If unset, hints for the entire document
- * are returned.
- */
- range?: langclient.Range;
-
- /**
- * The categories of inlay hints that are requested.
- * If unset, all categories are returned.
- */
- only?: string[];
-}
-
-export interface LegacyInlayHint {
- /**
- * The position within the code that this hint is
- * attached to.
- */
- position: langclient.Position;
-
- /**
- * The hint's kind, used for more flexible client-side
- * styling of the hint.
- */
- category?: string;
-
- /**
- * The hint's rendered label.
- */
- label: string;
-}
-
-export const legacyInlayHintsRequest = new langclient.RequestType<
- LegacyInlayHintsParams,
- LegacyInlayHint[],
- unknown
->("sourcekit-lsp/inlayHints");
-
-// Test styles where test-target represents a test target that contains tests
-export type TestStyle = "XCTest" | "swift-testing" | "test-target";
-
-// Listing tests
-export interface LSPTestItem {
- /**
- * This identifier uniquely identifies the test case or test suite. It can be used to run an individual test (suite).
- */
- id: string;
-
- /**
- * Display name describing the test.
- */
- label: string;
-
- /**
- * Optional description that appears next to the label.
- */
- description?: string;
-
- /**
- * A string that should be used when comparing this item with other items.
- *
- * When `undefined` the `label` is used.
- */
- sortText?: string;
-
- /**
- * Whether the test is disabled.
- */
- disabled: boolean;
-
- /**
- * The type of test, eg. the testing framework that was used to declare the test.
- */
- style: TestStyle;
-
- /**
- * The location of the test item in the source code.
- */
- location: ls.Location;
-
- /**
- * The children of this test item.
- *
- * For a test suite, this may contain the individual test cases or nested suites.
- */
- children: LSPTestItem[];
-
- /**
- * Tags associated with this test item.
- */
- tags: { id: string }[];
-}
-
-export const workspaceTestsRequest = new langclient.RequestType<
- Record,
- LSPTestItem[],
- unknown
->("workspace/tests");
-
-interface DocumentTestsParams {
- textDocument: {
- uri: ls.URI;
- };
-}
-
-export const textDocumentTestsRequest = new langclient.RequestType<
- DocumentTestsParams,
- LSPTestItem[],
- unknown
->("textDocument/tests");
-
-export const reindexProjectRequest = new langclient.RequestType(
- "workspace/triggerReindex"
-);
diff --git a/src/sourcekit-lsp/peekDocuments.ts b/src/sourcekit-lsp/peekDocuments.ts
index 26b818176..51bc5a99b 100644
--- a/src/sourcekit-lsp/peekDocuments.ts
+++ b/src/sourcekit-lsp/peekDocuments.ts
@@ -14,7 +14,7 @@
import * as vscode from "vscode";
import * as langclient from "vscode-languageclient/node";
-import { PeekDocumentsParams, PeekDocumentsRequest } from "./lspExtensions";
+import { PeekDocumentsParams, PeekDocumentsRequest } from "./extensions";
/**
* Opens a peeked editor in `uri` at `position` having contents from `locations`.
diff --git a/test/integration-tests/testexplorer/LSPTestDiscovery.test.ts b/test/integration-tests/testexplorer/LSPTestDiscovery.test.ts
index 88625afd9..48a60ccc4 100644
--- a/test/integration-tests/testexplorer/LSPTestDiscovery.test.ts
+++ b/test/integration-tests/testexplorer/LSPTestDiscovery.test.ts
@@ -14,34 +14,34 @@
import * as assert from "assert";
import * as vscode from "vscode";
-import * as ls from "vscode-languageserver-protocol";
-import * as p2c from "vscode-languageclient/lib/common/protocolConverter";
import { beforeEach } from "mocha";
-import { InitializeResult, RequestType } from "vscode-languageclient";
+import {
+ LanguageClient,
+ MessageSignature,
+ RequestType0,
+ RequestType,
+ Location,
+ Range,
+ Position,
+} from "vscode-languageclient/node";
+import * as p2c from "vscode-languageclient/lib/common/protocolConverter";
import { LSPTestDiscovery } from "../../../src/TestExplorer/LSPTestDiscovery";
import { SwiftPackage, Target, TargetType } from "../../../src/SwiftPackage";
import { TestClass } from "../../../src/TestExplorer/TestDiscovery";
import { SwiftToolchain } from "../../../src/toolchain/toolchain";
import {
LSPTestItem,
- textDocumentTestsRequest,
- workspaceTestsRequest,
-} from "../../../src/sourcekit-lsp/lspExtensions";
+ TextDocumentTestsRequest,
+ WorkspaceTestsRequest,
+} from "../../../src/sourcekit-lsp/extensions";
+import { instance, mockFn, mockObject } from "../../MockUtils";
+import { LanguageClientManager } from "../../../src/sourcekit-lsp/LanguageClientManager";
class TestLanguageClient {
private responses = new Map();
private responseVersions = new Map();
-
- setResponse(type: RequestType
, response: R) {
- this.responses.set(type.method, response);
- }
-
- setResponseVersion
(type: RequestType
, version: number) {
- this.responseVersions.set(type.method, version);
- }
-
- get initializeResult(): InitializeResult | undefined {
- return {
+ private client = mockObject({
+ initializeResult: {
capabilities: {
experimental: {
"textDocument/tests": {
@@ -52,15 +52,30 @@ class TestLanguageClient {
},
},
},
- };
+ },
+ protocol2CodeConverter: p2c.createConverter(undefined, true, true),
+ sendRequest: mockFn(s =>
+ s.callsFake((type: MessageSignature): Promise => {
+ const response = this.responses.get(type.method);
+ return response
+ ? Promise.resolve(response)
+ : Promise.reject("Method not implemented");
+ })
+ ),
+ });
+
+ public get languageClient(): LanguageClient {
+ return instance(this.client);
}
- get protocol2CodeConverter(): p2c.Converter {
- return p2c.createConverter(undefined, true, true);
+
+ setResponse(type: RequestType0, response: R): void;
+ setResponse(type: RequestType
, response: R): void;
+ setResponse(type: MessageSignature, response: unknown) {
+ this.responses.set(type.method, response);
}
- sendRequest
(type: RequestType
): Promise {
- const response = this.responses.get(type.method) as R | undefined;
- return response ? Promise.resolve(response) : Promise.reject("Method not implemented");
+ setResponseVersion(type: MessageSignature, version: number) {
+ this.responseVersions.set(type.method, version);
}
}
@@ -70,27 +85,37 @@ suite("LSPTestDiscovery Suite", () => {
let pkg: SwiftPackage;
const file = vscode.Uri.file("file:///some/file.swift");
- beforeEach(async () => {
+ beforeEach(async function () {
+ this.timeout(10000000);
pkg = await SwiftPackage.create(file, await SwiftToolchain.create());
client = new TestLanguageClient();
- discoverer = new LSPTestDiscovery({
- useLanguageClient(process) {
- return process(client, new vscode.CancellationTokenSource().token);
- },
- });
+ discoverer = new LSPTestDiscovery(
+ instance(
+ mockObject({
+ useLanguageClient: mockFn(s =>
+ s.callsFake(process => {
+ return process(
+ client.languageClient,
+ new vscode.CancellationTokenSource().token
+ );
+ })
+ ),
+ })
+ )
+ );
});
suite("Empty responses", () => {
- test(textDocumentTestsRequest.method, async () => {
- client.setResponse(textDocumentTestsRequest, []);
+ test(TextDocumentTestsRequest.method, async () => {
+ client.setResponse(TextDocumentTestsRequest.type, []);
const testClasses = await discoverer.getDocumentTests(pkg, file);
assert.deepStrictEqual(testClasses, []);
});
- test(workspaceTestsRequest.method, async () => {
- client.setResponse(workspaceTestsRequest, []);
+ test(WorkspaceTestsRequest.method, async () => {
+ client.setResponse(WorkspaceTestsRequest.type, []);
const testClasses = await discoverer.getWorkspaceTests(pkg);
@@ -99,14 +124,14 @@ suite("LSPTestDiscovery Suite", () => {
});
suite("Unsupported LSP version", () => {
- test(textDocumentTestsRequest.method, async () => {
- client.setResponseVersion(textDocumentTestsRequest, 0);
+ test(TextDocumentTestsRequest.method, async () => {
+ client.setResponseVersion(TextDocumentTestsRequest.type, 0);
await assert.rejects(() => discoverer.getDocumentTests(pkg, file));
});
- test(workspaceTestsRequest.method, async () => {
- client.setResponseVersion(workspaceTestsRequest, 0);
+ test(WorkspaceTestsRequest.method, async () => {
+ client.setResponseVersion(WorkspaceTestsRequest.type, 0);
await assert.rejects(() => discoverer.getWorkspaceTests(pkg));
});
@@ -140,9 +165,9 @@ suite("LSPTestDiscovery Suite", () => {
disabled: false,
style: "swift-testing",
tags: [],
- location: ls.Location.create(
+ location: Location.create(
file.fsPath,
- ls.Range.create(ls.Position.create(1, 0), ls.Position.create(2, 0))
+ Range.create(Position.create(1, 0), Position.create(2, 0))
),
children: [],
},
@@ -150,21 +175,21 @@ suite("LSPTestDiscovery Suite", () => {
expected = items.map(item => ({
...item,
- location: client.protocol2CodeConverter.asLocation(item.location),
+ location: client.languageClient.protocol2CodeConverter.asLocation(item.location),
children: [],
}));
});
- test(textDocumentTestsRequest.method, async () => {
- client.setResponse(textDocumentTestsRequest, items);
+ test(TextDocumentTestsRequest.method, async () => {
+ client.setResponse(TextDocumentTestsRequest.type, items);
const testClasses = await discoverer.getDocumentTests(pkg, file);
assert.deepStrictEqual(testClasses, expected);
});
- test(workspaceTestsRequest.method, async () => {
- client.setResponse(workspaceTestsRequest, items);
+ test(WorkspaceTestsRequest.method, async () => {
+ client.setResponse(WorkspaceTestsRequest.type, items);
const testClasses = await discoverer.getWorkspaceTests(pkg);
@@ -179,7 +204,7 @@ suite("LSPTestDiscovery Suite", () => {
style: "XCTest",
}));
- client.setResponse(workspaceTestsRequest, items);
+ client.setResponse(WorkspaceTestsRequest.type, items);
const testClasses = await discoverer.getWorkspaceTests(pkg);
@@ -193,7 +218,7 @@ suite("LSPTestDiscovery Suite", () => {
id: `${testTargetName}.topLevelTest()`,
}));
- client.setResponse(workspaceTestsRequest, items);
+ client.setResponse(WorkspaceTestsRequest.type, items);
const target: Target = {
c99name: testTargetName,
diff --git a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts
index a1e563862..c775d3bc3 100644
--- a/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts
+++ b/test/unit-tests/sourcekit-lsp/LanguageClientManager.test.ts
@@ -31,7 +31,6 @@ import {
mockGlobalValue,
mockFn,
} from "../../MockUtils";
-import * as langClient from "vscode-languageclient/node";
import {
Code2ProtocolConverter,
DidChangeWorkspaceFoldersNotification,
@@ -43,8 +42,10 @@ import {
import { LanguageClientManager } from "../../../src/sourcekit-lsp/LanguageClientManager";
import configuration from "../../../src/configuration";
import { FolderContext } from "../../../src/FolderContext";
+import { LanguageClientFactory } from "../../../src/sourcekit-lsp/LanguageClientFactory";
suite("LanguageClientManager Suite", () => {
+ let languageClientFactoryMock: MockedObject;
let languageClientMock: MockedObject;
let mockedConverter: MockedObject;
let changeStateEmitter: AsyncEventEmitter;
@@ -54,7 +55,6 @@ suite("LanguageClientManager Suite", () => {
let mockedToolchain: MockedObject;
let mockedBuildFlags: MockedObject;
- const mockedLangClientModule = mockGlobalModule(langClient);
const mockedConfig = mockGlobalModule(configuration);
const mockedEnvironment = mockGlobalValue(process, "env");
const mockedLspConfig = mockGlobalObject(configuration, "lsp");
@@ -149,7 +149,9 @@ suite("LanguageClientManager Suite", () => {
onDidChangeState: mockFn(s => s.callsFake(changeStateEmitter.event)),
});
// `new LanguageClient()` will always return the mocked LanguageClient
- mockedLangClientModule.LanguageClient.returns(instance(languageClientMock));
+ languageClientFactoryMock = mockObject({
+ createLanguageClient: mockFn(s => s.returns(instance(languageClientMock))),
+ });
// LSP configuration defaults
mockedConfig.path = "";
mockedConfig.buildArguments = [];
@@ -164,11 +166,11 @@ suite("LanguageClientManager Suite", () => {
});
test("launches SourceKit-LSP on startup", async () => {
- const sut = new LanguageClientManager(instance(mockedWorkspace));
+ const sut = new LanguageClientManager(instance(mockedWorkspace), languageClientFactoryMock);
await waitForReturnedPromises(languageClientMock.start);
expect(sut.state).to.equal(State.Running);
- expect(mockedLangClientModule.LanguageClient).to.have.been.calledOnceWith(
+ expect(languageClientFactoryMock.createLanguageClient).to.have.been.calledOnceWith(
/* id */ match.string,
/* name */ match.string,
/* serverOptions */ match.has("command", "/path/to/toolchain/bin/sourcekit-lsp"),
@@ -198,7 +200,7 @@ suite("LanguageClientManager Suite", () => {
},
workspaceContext: instance(mockedWorkspace),
});
- new LanguageClientManager(instance(mockedWorkspace));
+ new LanguageClientManager(instance(mockedWorkspace), languageClientFactoryMock);
await waitForReturnedPromises(languageClientMock.start);
// Add the first folder
@@ -259,21 +261,21 @@ suite("LanguageClientManager Suite", () => {
test("doesn't launch SourceKit-LSP if disabled by the user", async () => {
mockedLspConfig.disable = true;
- const sut = new LanguageClientManager(instance(mockedWorkspace));
+ const sut = new LanguageClientManager(instance(mockedWorkspace), languageClientFactoryMock);
await waitForReturnedPromises(languageClientMock.start);
expect(sut.state).to.equal(State.Stopped);
- expect(mockedLangClientModule.LanguageClient).to.not.have.been.called;
+ expect(languageClientFactoryMock.createLanguageClient).to.not.have.been.called;
expect(languageClientMock.start).to.not.have.been.called;
});
test("user can provide a custom SourceKit-LSP executable", async () => {
mockedLspConfig.serverPath = "/path/to/my/custom/sourcekit-lsp";
- const sut = new LanguageClientManager(instance(mockedWorkspace));
+ const sut = new LanguageClientManager(instance(mockedWorkspace), languageClientFactoryMock);
await waitForReturnedPromises(languageClientMock.start);
expect(sut.state).to.equal(State.Running);
- expect(mockedLangClientModule.LanguageClient).to.have.been.calledOnceWith(
+ expect(languageClientFactoryMock.createLanguageClient).to.have.been.calledOnceWith(
/* id */ match.string,
/* name */ match.string,
/* serverOptions */ match.has("command", "/path/to/my/custom/sourcekit-lsp"),
@@ -312,11 +314,14 @@ suite("LanguageClientManager Suite", () => {
});
test("doesn't launch SourceKit-LSP on startup", async () => {
- const sut = new LanguageClientManager(instance(mockedWorkspace));
+ const sut = new LanguageClientManager(
+ instance(mockedWorkspace),
+ languageClientFactoryMock
+ );
await waitForReturnedPromises(languageClientMock.start);
expect(sut.state).to.equal(State.Stopped);
- expect(mockedLangClientModule.LanguageClient).to.not.have.been.called;
+ expect(languageClientFactoryMock.createLanguageClient).to.not.have.been.called;
expect(languageClientMock.start).to.not.have.been.called;
});
@@ -330,7 +335,10 @@ suite("LanguageClientManager Suite", () => {
),
})
);
- const sut = new LanguageClientManager(instance(mockedWorkspace));
+ const sut = new LanguageClientManager(
+ instance(mockedWorkspace),
+ languageClientFactoryMock
+ );
await waitForReturnedPromises(languageClientMock.start);
// Add the folder to the workspace
@@ -341,7 +349,7 @@ suite("LanguageClientManager Suite", () => {
});
expect(sut.state).to.equal(State.Running);
- expect(mockedLangClientModule.LanguageClient).to.have.been.calledOnceWith(
+ expect(languageClientFactoryMock.createLanguageClient).to.have.been.calledOnceWith(
/* id */ match.string,
/* name */ match.string,
/* serverOptions */ match.object,
@@ -359,7 +367,10 @@ suite("LanguageClientManager Suite", () => {
document: instance(mockedTextDocument),
})
);
- const sut = new LanguageClientManager(instance(mockedWorkspace));
+ const sut = new LanguageClientManager(
+ instance(mockedWorkspace),
+ languageClientFactoryMock
+ );
await waitForReturnedPromises(languageClientMock.start);
// Add the first folder to the workspace
@@ -379,8 +390,8 @@ suite("LanguageClientManager Suite", () => {
});
expect(sut.state).to.equal(State.Running);
- expect(mockedLangClientModule.LanguageClient).to.have.been.calledTwice;
- expect(mockedLangClientModule.LanguageClient).to.have.been.calledWith(
+ expect(languageClientFactoryMock.createLanguageClient).to.have.been.calledTwice;
+ expect(languageClientFactoryMock.createLanguageClient).to.have.been.calledWith(
/* id */ match.string,
/* name */ match.string,
/* serverOptions */ match.object,