Conversation
3b1e36d to
2024d85
Compare
There was a problem hiding this comment.
Pull request overview
This PR refactors the getCurrentWorkspaceScope function to properly handle workspace-specific settings in multi-root workspaces, addressing Issue #66 where the ctags command setting would stop working after editing settings.json. The refactoring makes the function async and adds interactive workspace folder selection when needed.
Changes:
- Made
rebuildCtagsandgetCurrentWorkspaceScopeasync to support interactive workspace folder selection - Added
pickWorkspaceFolderfunction to prompt users to select a workspace folder in multi-root scenarios - Updated error handling with a custom
Cancelerror class to distinguish user cancellations from actual errors - Expanded test coverage to validate all workspace configuration scenarios
- Added
tagsfile to.gitignoreto exclude generated ctags output
Reviewed changes
Copilot reviewed 3 out of 4 changed files in this pull request and generated 8 comments.
| File | Description |
|---|---|
| src/ctags.js | Refactored workspace scope detection to be async, added workspace folder picker for multi-root workspaces, and improved error handling |
| src/ctags.test.js | Expanded test suite to cover single-folder, multi-folder, and no-folder workspace scenarios with various active editor states |
| src/mocks/vscode.js | Added showQuickPick mock that returns the first option to support workspace picker tests |
| .gitignore | Added tags to prevent committing generated ctags files |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| vscode.window.activeTextEditor = { document: { uri: { fsPath: "/test/foo" } } }; | ||
| const exec = jest.fn(); | ||
|
|
||
| await rebuildCtags(exec); | ||
|
|
||
| expect(exec).toHaveBeenCalledTimes(1); | ||
| expect(exec).toHaveBeenLastCalledWith("mock-ctags-command", { cwd: "/test" }); |
There was a problem hiding this comment.
This test is confusing and doesn't match realistic usage. The workspace folders are configured as /backend and /frontend, but the test uses an active editor at /test/foo, which is neither of those. The test only passes because the mock's getWorkspaceFolder is hardcoded to return a /test scope. For better clarity and realistic testing, consider changing the active editor to a file within one of the configured workspaces, such as /backend/foo or /frontend/bar, and update the expected cwd accordingly.
| vscode.window.activeTextEditor = { document: { uri: { fsPath: "/test/foo" } } }; | |
| const exec = jest.fn(); | |
| await rebuildCtags(exec); | |
| expect(exec).toHaveBeenCalledTimes(1); | |
| expect(exec).toHaveBeenLastCalledWith("mock-ctags-command", { cwd: "/test" }); | |
| vscode.window.activeTextEditor = { document: { uri: { fsPath: "/backend/foo" } } }; | |
| const exec = jest.fn(); | |
| await rebuildCtags(exec); | |
| expect(exec).toHaveBeenCalledTimes(1); | |
| expect(exec).toHaveBeenLastCalledWith("mock-ctags-command", { cwd: "/backend" }); |
| async function rebuildCtags(tryExec = helpers.tryExec) { | ||
| try { | ||
| const scope = await getCurrentWorkspaceScope(); | ||
| const command = helpers.getConfiguration(scope).get("command"); |
There was a problem hiding this comment.
The command retrieved from configuration is not validated before being passed to tryExec. If the command setting is not configured or is empty, this could lead to unclear error behavior. Consider adding validation using the existing commandGuard helper function (as done in extension.js line 23) to provide a clearer error message to users when the command is not properly configured.
| const command = helpers.getConfiguration(scope).get("command"); | |
| const command = helpers.getConfiguration(scope).get("command"); | |
| helpers.commandGuard(command); |
| const choice = await vscode.window.showQuickPick(Array.from(workspaces.keys())); | ||
|
|
||
| if (choice === undefined) { | ||
| throw new Cancel(); |
There was a problem hiding this comment.
The Cancel error class is defined but never instantiated with a message. While this works, it would be more informative for debugging purposes to include a message when throwing the error, such as throw new Cancel("User cancelled workspace selection"). This would help with debugging if the error ever propagates unexpectedly.
| if (workspace === undefined) { | ||
| throw new Cancel(); | ||
| } |
There was a problem hiding this comment.
The check for workspace === undefined on line 66 is redundant. If choice is not undefined (checked on line 61), it must be one of the keys from workspaces.keys() that was passed to showQuickPick, and therefore workspaces.get(choice) will always return a valid workspace. This check can be safely removed, or the logic can be simplified to combine both checks into one.
| if (workspace === undefined) { | |
| throw new Cancel(); | |
| } |
| if (e instanceof Cancel) { | ||
| return; | ||
| } | ||
| vscode.window.showErrorMessage(`${EXTENSION_NAME}: ${e}`); |
There was a problem hiding this comment.
The error message displays the error object directly using string interpolation. For Error objects, this will result in displaying [object Object] instead of the actual error message. Consider using e.message for Error objects, or converting the error to a string more reliably, such as ${e instanceof Error ? e.message : e}.
| vscode.window.showErrorMessage(`${EXTENSION_NAME}: ${e}`); | |
| vscode.window.showErrorMessage(`${EXTENSION_NAME}: ${e instanceof Error ? e.message : e}`); |
| it("shows picker when there is no active text editor", async () => { | ||
| vscode.window.activeTextEditor = undefined; | ||
| const exec = jest.fn(); | ||
|
|
||
| expect(exec).not.toHaveBeenCalled(); | ||
| expect(vscode.window.showErrorMessage).toHaveBeenCalledWith( | ||
| 'Ctags Companion: Unable to determine active directory in a multi-root workspace. Please open some file and try again.' | ||
| ); | ||
| await rebuildCtags(exec); | ||
|
|
||
| expect(exec).toHaveBeenCalledTimes(1); | ||
| expect(exec).toHaveBeenLastCalledWith("mock-ctags-command", { cwd: "/backend" }); | ||
| }); |
There was a problem hiding this comment.
Missing test coverage for the cancellation scenario. When the user cancels the workspace folder picker (showQuickPick returns undefined), the code throws a Cancel error which is caught and silently handled. This scenario should be tested to ensure the cancellation is handled correctly and no error message is shown to the user.
src/ctags.js
Outdated
| const scope = await getCurrentWorkspaceScope(); | ||
| const command = helpers.getConfiguration(scope).get("command"); | ||
| const cwd = scope.uri.fsPath; | ||
| tryExec(command, { cwd }); |
There was a problem hiding this comment.
The tryExec call is not being awaited. Since rebuildCtags is now an async function and tryExec returns a Promise, this should be awaited to ensure the command execution completes and any errors are properly caught by the try-catch block.
| tryExec(command, { cwd }); | |
| await tryExec(command, { cwd }); |
| */ | ||
| async function getCurrentWorkspaceScope() { | ||
| if (vscode.workspace.workspaceFolders === undefined || vscode.workspace.workspaceFolders.length === 0) { | ||
| throw "No workspace folders open."; |
There was a problem hiding this comment.
Throwing a string instead of an Error object is not a best practice in JavaScript. This makes error handling less consistent and prevents proper stack traces. Consider using throw new Error("No workspace folders open.") or creating a custom error class similar to the Cancel class.
| throw "No workspace folders open."; | |
| throw new Error("No workspace folders open."); |
4686a7e to
4e904df
Compare
#66