Skip to content

Commit 68e1ab8

Browse files
Merge branch 'topic/auto-reload' into 'master'
Display a popup when saving a loaded GPR file See merge request eng/ide/ada_language_server!2177
2 parents 5686749 + 6911096 commit 68e1ab8

File tree

6 files changed

+204
-1
lines changed

6 files changed

+204
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ section below it for the last release. -->
66

77
* Reload `e3-testsuite` tests automatically after configuration changes
88
* Provide completion for tool switches (e.g., `Default_Switches`) in GPR files
9+
* Automatically reload the project when GPR files are saved
910

1011
## 2026.0.202510141
1112

integration/vscode/ada/src/commands.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
CMD_BUILD_AND_RUN_GNATEMULATOR,
1414
CMD_BUILD_AND_RUN_MAIN,
1515
CMD_GPR_PROJECT_ARGS,
16+
CMD_RELOAD_PROJECT,
1617
CMD_RESTART_LANG_SERVERS,
1718
CMD_OPEN_USERS_GUIDE,
1819
CMD_SHOW_ADA_LS_OUTPUT,
@@ -741,6 +742,51 @@ export async function checkSrcDirectories(atStartup = false, displayYesNoPopup =
741742
}
742743
}
743744

745+
/**
746+
* Display a popup asking the user if they want to reload the GPR project.
747+
* The popup has four options:
748+
* - "Yes": executes CMD_RELOAD_PROJECT
749+
* - "Always": executes CMD_RELOAD_PROJECT and stores a preference to always reload without asking
750+
* - "No": does nothing
751+
* - "Never": does nothing and stores a preference to never ask again
752+
*/
753+
export async function autoReloadProject() {
754+
const alwaysReloadKey = 'ada.autoReloadProject.alwaysReload';
755+
const neverReloadKey = 'ada.autoReloadProject.neverReload';
756+
const alwaysReload = adaExtState.context.workspaceState.get(alwaysReloadKey);
757+
const neverReload = adaExtState.context.workspaceState.get(neverReloadKey);
758+
759+
// If the user previously clicked "Always", reload without asking
760+
if (alwaysReload) {
761+
await vscode.commands.executeCommand(CMD_RELOAD_PROJECT);
762+
return;
763+
}
764+
765+
// If the user previously clicked "Never", don't ask and don't reload
766+
if (neverReload) {
767+
return;
768+
}
769+
770+
// Ask the user if they want to reload the project
771+
const answer = await vscode.window.showInformationMessage(
772+
'Do you want to reload the project after saving?',
773+
'Yes',
774+
'Always',
775+
'No',
776+
'Never',
777+
);
778+
779+
if (answer === 'Yes') {
780+
await vscode.commands.executeCommand(CMD_RELOAD_PROJECT);
781+
} else if (answer === 'Always') {
782+
await vscode.commands.executeCommand(CMD_RELOAD_PROJECT);
783+
await adaExtState.context.workspaceState.update(alwaysReloadKey, true);
784+
} else if (answer === 'Never') {
785+
await adaExtState.context.workspaceState.update(neverReloadKey, true);
786+
}
787+
// If answer is 'No' or undefined (user dismissed the dialog), do nothing
788+
}
789+
744790
/*
745791
* This is a command handler that builds and runs the main given as parameter.
746792
* If the given URI does not match one of the project Mains an error is

integration/vscode/ada/src/constants.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ export const CMD_SHOW_GPR_LS_OUTPUT = 'ada.showGprLSOutput';
8181
*/
8282
export const CMD_RELOAD_PROJECT = 'als-reload-project';
8383

84+
/**
85+
* Identifier for the command that retrieves the list of dependencies of the specified
86+
* GPR project.
87+
*/
88+
export const CMD_GPR_PROJECT_DEPENDENCIES = 'als-gpr-dependencies';
89+
8490
/**
8591
* Identifier for the command that restarts all the language servers spawned by the extension
8692
* (Ada and GPR).

integration/vscode/ada/src/extension.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ import Transport from 'winston-transport';
2424
import { ExtensionState } from './ExtensionState';
2525
import { ALSClientFeatures } from './alsClientFeatures';
2626
import { alsCommandExecutor } from './alsExecuteCommand';
27-
import { registerCommands } from './commands';
27+
import { autoReloadProject, registerCommands } from './commands';
2828
import {
2929
TERMINAL_ENV_SETTING_NAME,
3030
assertSupportedEnvironments,
31+
belongsToLoadedProject,
3132
getEvaluatedTerminalEnv,
3233
startedInDebugMode,
3334
} from './helpers';
@@ -167,6 +168,16 @@ async function activateExtension(context: vscode.ExtensionContext) {
167168
vscode.languages.onDidChangeDiagnostics(adaExtState.updateStatusBarItem),
168169
);
169170

171+
// Subscribe to the didSaveTextDocument event to reload the project when .gpr files
172+
// that belong to the loaded project tree are saved
173+
context.subscriptions.push(
174+
vscode.workspace.onDidSaveTextDocument(async (document) => {
175+
if (await belongsToLoadedProject(document, logger)) {
176+
void autoReloadProject();
177+
}
178+
}),
179+
);
180+
170181
const alsMiddleware: Middleware = {
171182
executeCommand: alsCommandExecutor(adaExtState.adaClient),
172183
};

integration/vscode/ada/src/helpers.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ import winston from 'winston';
2424

2525
import { existsSync } from 'fs';
2626
import { EXTENSION_NAME, adaExtState, logger, mainOutputChannel } from './extension';
27+
import {
28+
ALS_GprDependencyDirection,
29+
ALS_GprDependencyItem,
30+
ALS_GprDependencyParam,
31+
} from './visualizerTypes';
2732

2833
/* Whether we are under Windows */
2934
const isWindows = process.platform === 'win32';
@@ -727,3 +732,54 @@ export function getMatchingPrefixes(
727732
* environment.
728733
*/
729734
export const inTesting = process.env['ALS_VSCODE_TEST_ENV'] === '1';
735+
736+
/**
737+
* Check if a document is a .gpr file that belongs to the currently loaded project tree.
738+
* Returns true if the document is either the root project file or part of its dependencies.
739+
*
740+
* @param document - the document to check
741+
* @param logger - a logger instance to log errors
742+
* @returns true if the document is a .gpr file that belongs to the loaded project tree
743+
*/
744+
export async function belongsToLoadedProject(
745+
document: vscode.TextDocument,
746+
logger: winston.Logger,
747+
): Promise<boolean> {
748+
// Only check GPR files
749+
if (document.languageId !== 'gpr') {
750+
return false;
751+
}
752+
753+
try {
754+
// Get the currently loaded project file
755+
const currentProjectFile = await adaExtState.getProjectFile();
756+
const currentProjectUri = vscode.Uri.file(currentProjectFile);
757+
758+
// Check if the document is the current project file
759+
if (document.uri.fsPath === currentProjectUri.fsPath) {
760+
return true;
761+
}
762+
763+
// Get the project dependencies
764+
const dependencies = await vscode.commands.executeCommand<ALS_GprDependencyItem[]>(
765+
'als-gpr-dependencies',
766+
{
767+
uri: currentProjectUri.toString(),
768+
direction: ALS_GprDependencyDirection.SHOW_OUTGOING,
769+
} as ALS_GprDependencyParam,
770+
);
771+
772+
if (dependencies) {
773+
// Check if the document is in the dependencies
774+
return dependencies.some((dep) => {
775+
const depUri = vscode.Uri.parse(dep.uri);
776+
return depUri.fsPath === document.uri.fsPath;
777+
});
778+
}
779+
780+
return false;
781+
} catch (error) {
782+
logger.error('Failed to check if document belongs to loaded project: ' + String(error));
783+
return false;
784+
}
785+
}

integration/vscode/ada/test/general/extension.test.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,89 @@ suite('Extensions Test Suite', function () {
213213
}
214214
});
215215

216+
test('Auto Reload Project On Save', async () => {
217+
if (vscode.workspace.workspaceFolders !== undefined) {
218+
// Get the workspace root folder
219+
const folder = vscode.workspace.workspaceFolders[0].uri;
220+
221+
// Set the 'Always' preference for auto-reload, just for this test
222+
const alwaysReloadKey = 'ada.autoReloadProject.alwaysReload';
223+
await adaExtState.context.workspaceState.update(alwaysReloadKey, true);
224+
225+
try {
226+
// Check that Exec_Dir is not initially set (should be empty or default)
227+
const initialExecDir = await adaExtState.getProjectAttributeValue('Exec_Dir');
228+
assert.strictEqual(initialExecDir, 'obj', 'Exec_Dir should be initially "obj"');
229+
230+
// Open the GPR file
231+
const fileUri = vscode.Uri.joinPath(folder, 'prj.gpr');
232+
const document = await vscode.workspace.openTextDocument(fileUri);
233+
const editor = await vscode.window.showTextDocument(document);
234+
235+
// Store the original content
236+
const contentBefore = document.getText();
237+
238+
// Edit the document to add Exec_Dir attribute
239+
const text = document.getText();
240+
const modifiedText = text.replace(
241+
' for Object_Dir use "obj";',
242+
' for Object_Dir use "obj";\n for Exec_Dir use "bin";',
243+
);
244+
245+
await editor.edit((editBuilder) => {
246+
const fullRange = new vscode.Range(
247+
document.positionAt(0),
248+
document.positionAt(text.length),
249+
);
250+
editBuilder.replace(fullRange, modifiedText);
251+
});
252+
253+
// Save the document to trigger the auto-reload
254+
await document.save();
255+
256+
// Wait a bit for the auto-reload to complete
257+
await new Promise((resolve) => setTimeout(resolve, 1000));
258+
259+
// Check that Exec_Dir has been set to "bin"
260+
const newExecDir = await adaExtState.getProjectAttributeValue('Exec_Dir');
261+
assert.strictEqual(
262+
newExecDir,
263+
'bin',
264+
'Exec_Dir should be set to "bin" after auto-reload',
265+
);
266+
267+
// Restore the original content using the VS Code API
268+
const currentText = document.getText();
269+
await editor.edit((editBuilder) => {
270+
const fullRange = new vscode.Range(
271+
document.positionAt(0),
272+
document.positionAt(currentText.length),
273+
);
274+
editBuilder.replace(fullRange, contentBefore);
275+
});
276+
277+
// Save again to trigger another auto-reload
278+
await document.save();
279+
280+
// Wait for the auto-reload
281+
await new Promise((resolve) => setTimeout(resolve, 1000));
282+
283+
// Check that Exec_Dir is back to its initial value
284+
const restoredExecDir = await adaExtState.getProjectAttributeValue('Exec_Dir');
285+
assert.strictEqual(
286+
restoredExecDir,
287+
initialExecDir,
288+
'Exec_Dir should be restored to its initial value',
289+
);
290+
} finally {
291+
// Clear the 'Always' preference
292+
await adaExtState.context.workspaceState.update(alwaysReloadKey, undefined);
293+
}
294+
} else {
295+
throw new Error('No workspace folder found for the specified URI');
296+
}
297+
});
298+
216299
test('Split long comments on ENTER', async () => {
217300
if (vscode.workspace.workspaceFolders !== undefined) {
218301
// Get a file with a long comment (>80 chars)

0 commit comments

Comments
 (0)