Open tutorial workspace on extension start
When opening https://github.com/github/codespaces-codeql/ in a codespace, it's easy to miss the prompt that tells you to open the tutorial.code-workspace file. In fact people actively dismiss the alert to get it out of the way. If you miss that prompt, you end up with a single-rooted workspace, which causes various other problems. While there is an open issue to allow VS Code to open a default workspace [1], there doesn't seem to have been any progress on it in the last two years. So we're taking matters into our own hands and forcing the extension to open the tutorial workspace, if it detects it. This will only happen if the following three conditions are met: - the .tours folder exists - the tutorial.code-workspace file exists - the CODESPACES_TEMPLATE setting hasn't been set NB: the `CODESPACES_TEMPLATE` setting can only be found if the tutorial.code-workspace has already been opened. So it's a good indicator that we're in the folder, but the user has ignored the prompt. [1]: https://github.com/microsoft/vscode-remote-release/issues/3665
This commit is contained in:
@@ -70,6 +70,7 @@ import {
|
||||
showInformationMessageWithAction,
|
||||
tmpDir,
|
||||
tmpDirDisposal,
|
||||
prepareCodeTour,
|
||||
} from "./helpers";
|
||||
import {
|
||||
asError,
|
||||
@@ -344,6 +345,8 @@ export async function activate(
|
||||
codeQlExtension.variantAnalysisManager,
|
||||
);
|
||||
|
||||
await prepareCodeTour();
|
||||
|
||||
return codeQlExtension;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
ensureDir,
|
||||
writeFile,
|
||||
opendir,
|
||||
existsSync,
|
||||
} from "fs-extra";
|
||||
import { promise as glob } from "glob-promise";
|
||||
import { load } from "js-yaml";
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
window as Window,
|
||||
workspace,
|
||||
env,
|
||||
commands,
|
||||
} from "vscode";
|
||||
import { CodeQLCliServer, QlpacksInfo } from "./cli";
|
||||
import { UserCancellationException } from "./commandRunner";
|
||||
@@ -25,6 +27,7 @@ import { telemetryListener } from "./telemetry";
|
||||
import { RedactableError } from "./pure/errors";
|
||||
import { getQlPackPath } from "./pure/ql";
|
||||
import { dbSchemeToLanguage } from "./common/query-language";
|
||||
import { isCodespacesTemplate } from "./config";
|
||||
|
||||
// Shared temporary folder for the extension.
|
||||
export const tmpDir = dirSync({
|
||||
@@ -266,6 +269,36 @@ export function isFolderAlreadyInWorkspace(folderName: string) {
|
||||
);
|
||||
}
|
||||
|
||||
/** Check if the current workspace is the CodeTour and open the workspace folder.
|
||||
* Without this, we can't run the code tour correctly.
|
||||
**/
|
||||
export async function prepareCodeTour(): Promise<void> {
|
||||
if (workspace.workspaceFolders?.length) {
|
||||
const currentFolder = workspace.workspaceFolders[0].uri.fsPath;
|
||||
|
||||
// We need this path to check that the file exists on windows
|
||||
const tutorialWorkspacePath = join(
|
||||
currentFolder,
|
||||
"tutorial.code-workspace",
|
||||
);
|
||||
const toursFolderPath = join(currentFolder, ".tours");
|
||||
|
||||
if (
|
||||
existsSync(tutorialWorkspacePath) &&
|
||||
existsSync(toursFolderPath) &&
|
||||
!isCodespacesTemplate()
|
||||
) {
|
||||
const tutorialWorkspaceUri = Uri.parse(
|
||||
join(
|
||||
workspace.workspaceFolders[0].uri.fsPath,
|
||||
"tutorial.code-workspace",
|
||||
),
|
||||
);
|
||||
await commands.executeCommand("vscode.openFolder", tutorialWorkspaceUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a utility method to invoke a function only if a minimum time interval has elapsed since
|
||||
* the last invocation of that function.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
commands,
|
||||
EnvironmentVariableCollection,
|
||||
EnvironmentVariableMutator,
|
||||
Event,
|
||||
@@ -15,7 +16,14 @@ import {
|
||||
import { dump } from "js-yaml";
|
||||
import * as tmp from "tmp";
|
||||
import { join } from "path";
|
||||
import { writeFileSync, mkdirSync, ensureDirSync, symlinkSync } from "fs-extra";
|
||||
import {
|
||||
writeFileSync,
|
||||
mkdirSync,
|
||||
ensureDirSync,
|
||||
symlinkSync,
|
||||
writeFile,
|
||||
mkdir,
|
||||
} from "fs-extra";
|
||||
import { DirResult } from "tmp";
|
||||
|
||||
import {
|
||||
@@ -24,6 +32,7 @@ import {
|
||||
isFolderAlreadyInWorkspace,
|
||||
isLikelyDatabaseRoot,
|
||||
isLikelyDbLanguageFolder,
|
||||
prepareCodeTour,
|
||||
showBinaryChoiceDialog,
|
||||
showBinaryChoiceWithUrlDialog,
|
||||
showInformationMessageWithAction,
|
||||
@@ -31,6 +40,7 @@ import {
|
||||
} from "../../../src/helpers";
|
||||
import { reportStreamProgress } from "../../../src/commandRunner";
|
||||
import { QueryLanguage } from "../../../src/common/query-language";
|
||||
import { Setting } from "../../../src/config";
|
||||
|
||||
describe("helpers", () => {
|
||||
describe("Invocation rate limiter", () => {
|
||||
@@ -559,3 +569,103 @@ describe("isFolderAlreadyInWorkspace", () => {
|
||||
expect(isFolderAlreadyInWorkspace("/third/path")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("prepareCodeTour", () => {
|
||||
let dir: tmp.DirResult;
|
||||
|
||||
beforeEach(() => {
|
||||
dir = tmp.dirSync();
|
||||
|
||||
const mockWorkspaceFolders = [
|
||||
{
|
||||
uri: Uri.file(dir.name),
|
||||
name: "test",
|
||||
index: 0,
|
||||
},
|
||||
] as WorkspaceFolder[];
|
||||
|
||||
jest
|
||||
.spyOn(workspace, "workspaceFolders", "get")
|
||||
.mockReturnValue(mockWorkspaceFolders);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
dir.removeCallback();
|
||||
});
|
||||
|
||||
describe("if we're in the tour repo", () => {
|
||||
describe("if the workspace is not already open", () => {
|
||||
it("should open the tutorial workspace", async () => {
|
||||
// set up directory to have a 'tutorial.code-workspace' file
|
||||
const tutorialWorkspacePath = join(dir.name, "tutorial.code-workspace");
|
||||
await writeFile(tutorialWorkspacePath, "{}");
|
||||
|
||||
// set up a .tours directory to indicate we're in the tour codespace
|
||||
const tourDirPath = join(dir.name, ".tours");
|
||||
await mkdir(tourDirPath);
|
||||
|
||||
// spy that we open the workspace file by calling the 'vscode.openFolder' command
|
||||
const commandSpy = jest.spyOn(commands, "executeCommand");
|
||||
commandSpy.mockImplementation(() => Promise.resolve());
|
||||
|
||||
await prepareCodeTour();
|
||||
|
||||
expect(commandSpy).toHaveBeenCalledWith(
|
||||
"vscode.openFolder",
|
||||
expect.anything(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("if the workspace is already open", () => {
|
||||
it("should not open the tutorial workspace", async () => {
|
||||
// Set isCodespaceTemplate to true to indicate the workspace has already been opened
|
||||
jest.spyOn(Setting.prototype, "getValue").mockReturnValue(false);
|
||||
|
||||
// set up directory to have a 'tutorial.code-workspace' file
|
||||
const tutorialWorkspacePath = join(dir.name, "tutorial.code-workspace");
|
||||
await writeFile(tutorialWorkspacePath, "{}");
|
||||
|
||||
// set up a .tours directory to indicate we're in the tour codespace
|
||||
const tourDirPath = join(dir.name, ".tours");
|
||||
await mkdir(tourDirPath);
|
||||
|
||||
// spy that we open the workspace file by calling the 'vscode.openFolder' command
|
||||
const openFileSpy = jest.spyOn(commands, "executeCommand");
|
||||
openFileSpy.mockImplementation(() => Promise.resolve());
|
||||
|
||||
await prepareCodeTour();
|
||||
|
||||
expect(openFileSpy).not.toHaveBeenCalledWith("vscode.openFolder");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("if we're in a different tour repo", () => {
|
||||
it("should not open the tutorial workspace", async () => {
|
||||
// set up a .tours directory
|
||||
const tourDirPath = join(dir.name, ".tours");
|
||||
await mkdir(tourDirPath);
|
||||
|
||||
// spy that we open the workspace file by calling the 'vscode.openFolder' command
|
||||
const openFileSpy = jest.spyOn(commands, "executeCommand");
|
||||
openFileSpy.mockImplementation(() => Promise.resolve());
|
||||
|
||||
await prepareCodeTour();
|
||||
|
||||
expect(openFileSpy).not.toHaveBeenCalledWith("vscode.openFolder");
|
||||
});
|
||||
});
|
||||
|
||||
describe("if we're in a different repo with no tour", () => {
|
||||
it("should not open the tutorial workspace", async () => {
|
||||
// spy that we open the workspace file by calling the 'vscode.openFolder' command
|
||||
const openFileSpy = jest.spyOn(commands, "executeCommand");
|
||||
openFileSpy.mockImplementation(() => Promise.resolve());
|
||||
|
||||
await prepareCodeTour();
|
||||
|
||||
expect(openFileSpy).not.toHaveBeenCalledWith("vscode.openFolder");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user