Skip to content

Commit 2bdaabe

Browse files
authored
Prompt to restart SourceKit-LSP on config changes (#1759)
* Prompt to restart SourceKit-LSP on config changes Issue: #1744 * Update CHANGELOG.md * Also prompt if a config.json is added or delete * Add a test for new functionality Check that a LSP restart prompt is shown when the config.json file is changed and update existing tests that modify the file to ignore the LSP restart prompts.
1 parent bf7c770 commit 2bdaabe

File tree

3 files changed

+103
-16
lines changed

3 files changed

+103
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- New `swift.outputChannelLogLevel` setting to control the verbosity of the `Swift` output channel ([#1746](https://github.com/swiftlang/vscode-swift/pull/1746))
99
- New `swift.debugTestsMultipleTimes` and `swift.debugTestsUntilFailure` commands for debugging tests over multiple runs ([#1763](https://github.com/swiftlang/vscode-swift/pull/1763))
1010
- Optionally include LLDB DAP logs in the Swift diagnostics bundle ([#1768](https://github.com/swiftlang/vscode-swift/pull/1758))
11+
- Prompt to restart `SourceKit-LSP` after changing `.sourcekit-lsp/config.json` files ([#1744](https://github.com/swiftlang/vscode-swift/issues/1744))
1112
- Prompt to cancel and replace the active test run if one is in flight ([#1774](https://github.com/swiftlang/vscode-swift/pull/1774))
1213

1314
### Changed

src/commands/generateSourcekitConfiguration.ts

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ import { FolderContext } from "../FolderContext";
1818
import { selectFolder } from "../ui/SelectFolderQuickPick";
1919
import { WorkspaceContext } from "../WorkspaceContext";
2020
import configuration from "../configuration";
21+
import restartLSPServer from "./restartLSPServer";
22+
23+
export const sourcekitDotFolder: string = ".sourcekit-lsp";
24+
export const sourcekitConfigFileName: string = "config.json";
2125

2226
export async function generateSourcekitConfiguration(ctx: WorkspaceContext): Promise<boolean> {
2327
if (ctx.folders.length === 0) {
@@ -46,9 +50,9 @@ export async function generateSourcekitConfiguration(ctx: WorkspaceContext): Pro
4650
).reduceRight((prev, curr) => prev || curr);
4751
}
4852

49-
export const sourcekitFolderPath = (f: FolderContext) => join(f.folder.fsPath, ".sourcekit-lsp");
53+
export const sourcekitFolderPath = (f: FolderContext) => join(f.folder.fsPath, sourcekitDotFolder);
5054
export const sourcekitConfigFilePath = (f: FolderContext) =>
51-
join(sourcekitFolderPath(f), "config.json");
55+
join(sourcekitFolderPath(f), sourcekitConfigFileName);
5256

5357
async function createSourcekitConfiguration(
5458
workspaceContext: WorkspaceContext,
@@ -134,14 +138,33 @@ export async function determineSchemaURL(folderContext: FolderContext): Promise<
134138
return schemaURL(branch);
135139
}
136140

137-
async function checkDocumentSchema(doc: vscode.TextDocument, workspaceContext: WorkspaceContext) {
138-
const folder = await workspaceContext.getPackageFolder(doc.uri);
141+
async function getValidatedFolderContext(
142+
uri: vscode.Uri,
143+
workspaceContext: WorkspaceContext
144+
): Promise<FolderContext | null> {
145+
const folder = await workspaceContext.getPackageFolder(uri);
139146
if (!folder) {
140-
return;
147+
return null;
141148
}
142149
const folderContext = folder as FolderContext;
143150
if (!folderContext.name) {
144-
return; // Not a FolderContext if no "name"
151+
return null; // Not a FolderContext if no "name"
152+
}
153+
if (
154+
!(
155+
basename(dirname(uri.fsPath)) === sourcekitDotFolder &&
156+
basename(uri.fsPath) === sourcekitConfigFileName
157+
)
158+
) {
159+
return null;
160+
}
161+
return folderContext;
162+
}
163+
164+
async function checkDocumentSchema(doc: vscode.TextDocument, workspaceContext: WorkspaceContext) {
165+
const folderContext = await getValidatedFolderContext(doc.uri, workspaceContext);
166+
if (!folderContext) {
167+
return;
145168
}
146169
let buffer: Uint8Array;
147170
try {
@@ -191,13 +214,7 @@ export async function handleSchemaUpdate(
191214
doc: vscode.TextDocument,
192215
workspaceContext: WorkspaceContext
193216
) {
194-
if (
195-
!configuration.checkLspConfigurationSchema ||
196-
!(
197-
basename(dirname(doc.uri.fsPath)) === ".sourcekit-lsp" &&
198-
basename(doc.uri.fsPath) === "config.json"
199-
)
200-
) {
217+
if (!configuration.checkLspConfigurationSchema) {
201218
return;
202219
}
203220
await checkDocumentSchema(doc, workspaceContext);
@@ -206,7 +223,44 @@ export async function handleSchemaUpdate(
206223
export function registerSourceKitSchemaWatcher(
207224
workspaceContext: WorkspaceContext
208225
): vscode.Disposable {
209-
return vscode.workspace.onDidOpenTextDocument(doc => {
226+
const onDidOpenDisposable = vscode.workspace.onDidOpenTextDocument(doc => {
210227
void handleSchemaUpdate(doc, workspaceContext);
211228
});
229+
const configFileWatcher = vscode.workspace.createFileSystemWatcher(
230+
`**/${sourcekitDotFolder}/${sourcekitConfigFileName}`
231+
);
232+
const onDidChangeDisposable = configFileWatcher.onDidChange(async uri => {
233+
await handleConfigFileChange(uri, workspaceContext);
234+
});
235+
const onDidDeleteDisposable = configFileWatcher.onDidDelete(async uri => {
236+
await handleConfigFileChange(uri, workspaceContext);
237+
});
238+
const onDidCreateDisposable = configFileWatcher.onDidCreate(async uri => {
239+
await handleConfigFileChange(uri, workspaceContext);
240+
});
241+
return vscode.Disposable.from(
242+
onDidOpenDisposable,
243+
configFileWatcher,
244+
onDidChangeDisposable,
245+
onDidDeleteDisposable,
246+
onDidCreateDisposable
247+
);
248+
}
249+
250+
export async function handleConfigFileChange(
251+
configUri: vscode.Uri,
252+
workspaceContext: WorkspaceContext
253+
): Promise<void> {
254+
const folderContext = await getValidatedFolderContext(configUri, workspaceContext);
255+
if (!folderContext) {
256+
return;
257+
}
258+
const result = await vscode.window.showInformationMessage(
259+
`The SourceKit-LSP configuration file has been modified. Would you like to restart the language server to apply the changes?`,
260+
"Restart LSP Server",
261+
"Not Now"
262+
);
263+
if (result === "Restart LSP Server") {
264+
await restartLSPServer(workspaceContext);
265+
}
212266
}

test/integration-tests/commands/generateSourcekitConfiguration.test.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@ import {
2525
import { closeAllEditors } from "../../utilities/commands";
2626
import {
2727
determineSchemaURL,
28+
handleConfigFileChange,
2829
handleSchemaUpdate,
2930
sourcekitConfigFilePath,
3031
sourcekitFolderPath,
3132
} from "../../../src/commands/generateSourcekitConfiguration";
3233
import { Version } from "../../../src/utilities/version";
33-
import { mockGlobalObject } from "../../MockUtils";
34+
import { mockGlobalObject, mockGlobalModule } from "../../MockUtils";
35+
import * as restartLSPServerModule from "../../../src/commands/restartLSPServer";
3436

3537
suite("Generate SourceKit-LSP configuration Command", function () {
3638
let folderContext: FolderContext;
@@ -112,6 +114,7 @@ suite("Generate SourceKit-LSP configuration Command", function () {
112114

113115
suite("handleSchemaUpdate", async () => {
114116
const mockWindow = mockGlobalObject(vscode, "window");
117+
const mockRestartLSPServerModule = mockGlobalModule(restartLSPServerModule);
115118

116119
test("Updates to new schema version", async () => {
117120
await vscode.workspace.fs.writeFile(
@@ -156,7 +159,12 @@ suite("Generate SourceKit-LSP configuration Command", function () {
156159

157160
await handleSchemaUpdate(document, workspaceContext);
158161

159-
expect(mockWindow.showInformationMessage).to.have.not.been.called;
162+
expect(mockWindow.showInformationMessage).to.have.not.been.calledWith(
163+
`The $schema property for ${configFileUri.fsPath} is not set to the version of the Swift toolchain that you are using. Would you like to update the $schema property?`,
164+
"Yes",
165+
"No",
166+
"Don't Ask Again"
167+
);
160168
});
161169

162170
test("Don't update schema version", async () => {
@@ -180,5 +188,29 @@ suite("Generate SourceKit-LSP configuration Command", function () {
180188
"https://raw.githubusercontent.com/swiftlang/sourcekit-lsp/refs/heads/main/config.schema.json"
181189
);
182190
});
191+
192+
test("Check LSP restart prompt for config.json modifications", async () => {
193+
await vscode.workspace.fs.writeFile(
194+
configFileUri,
195+
Buffer.from(
196+
JSON.stringify({
197+
$schema: "invalid schema",
198+
})
199+
)
200+
);
201+
await handleConfigFileChange(configFileUri, workspaceContext);
202+
203+
expect(mockWindow.showInformationMessage).to.have.been.called;
204+
expect(mockWindow.showInformationMessage).to.have.been.calledWith(
205+
`The SourceKit-LSP configuration file has been modified. Would you like to restart the language server to apply the changes?`,
206+
"Restart LSP Server",
207+
"Not Now"
208+
);
209+
210+
mockWindow.showInformationMessage.resolves("Restart LSP Server" as any);
211+
212+
await handleConfigFileChange(configFileUri, workspaceContext);
213+
expect(mockRestartLSPServerModule.default).to.have.been.calledWith(workspaceContext);
214+
});
183215
});
184216
});

0 commit comments

Comments
 (0)