Skip to content

Commit c6c5628

Browse files
authored
Merge pull request #2505 from github/koesie10/split-helpers-2
Split `helpers.ts` [Part 2]
2 parents 019e377 + 50f9580 commit c6c5628

File tree

36 files changed

+661
-646
lines changed

36 files changed

+661
-646
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { AppCommandManager } from "./common/commands";
2+
import { Uri, workspace } from "vscode";
3+
import { join } from "path";
4+
import { pathExists } from "fs-extra";
5+
import { isCodespacesTemplate } from "./config";
6+
import { showBinaryChoiceDialog } from "./common/vscode/dialog";
7+
import { extLogger } from "./common";
8+
9+
/**
10+
* Check if the current workspace is the CodeTour and open the workspace folder.
11+
* Without this, we can't run the code tour correctly.
12+
**/
13+
export async function prepareCodeTour(
14+
commandManager: AppCommandManager,
15+
): Promise<void> {
16+
if (workspace.workspaceFolders?.length) {
17+
const currentFolder = workspace.workspaceFolders[0].uri.fsPath;
18+
19+
const tutorialWorkspacePath = join(
20+
currentFolder,
21+
"tutorial.code-workspace",
22+
);
23+
const toursFolderPath = join(currentFolder, ".tours");
24+
25+
/** We're opening the tutorial workspace, if we detect it.
26+
* This will only happen if the following three conditions are met:
27+
* - the .tours folder exists
28+
* - the tutorial.code-workspace file exists
29+
* - the CODESPACES_TEMPLATE setting doesn't exist (it's only set if the user has already opened
30+
* the tutorial workspace so it's a good indicator that the user is in the folder but has ignored
31+
* the prompt to open the workspace)
32+
*/
33+
if (
34+
(await pathExists(tutorialWorkspacePath)) &&
35+
(await pathExists(toursFolderPath)) &&
36+
!isCodespacesTemplate()
37+
) {
38+
const answer = await showBinaryChoiceDialog(
39+
"We've detected you're in the CodeQL Tour repo. We will need to open the workspace file to continue. Reload?",
40+
);
41+
42+
if (!answer) {
43+
return;
44+
}
45+
46+
const tutorialWorkspaceUri = Uri.file(tutorialWorkspacePath);
47+
48+
void extLogger.log(
49+
`In prepareCodeTour() method, going to open the tutorial workspace file: ${tutorialWorkspacePath}`,
50+
);
51+
52+
await commandManager.execute("vscode.openFolder", tutorialWorkspaceUri);
53+
}
54+
}
55+
}
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { env, Uri, window } from "vscode";
2+
3+
/**
4+
* Opens a modal dialog for the user to make a yes/no choice.
5+
*
6+
* @param message The message to show.
7+
* @param modal If true (the default), show a modal dialog box, otherwise dialog is non-modal and can
8+
* be closed even if the user does not make a choice.
9+
* @param yesTitle The text in the box indicating the affirmative choice.
10+
* @param noTitle The text in the box indicating the negative choice.
11+
*
12+
* @return
13+
* `true` if the user clicks 'Yes',
14+
* `false` if the user clicks 'No' or cancels the dialog,
15+
* `undefined` if the dialog is closed without the user making a choice.
16+
*/
17+
export async function showBinaryChoiceDialog(
18+
message: string,
19+
modal = true,
20+
yesTitle = "Yes",
21+
noTitle = "No",
22+
): Promise<boolean | undefined> {
23+
const yesItem = { title: yesTitle, isCloseAffordance: false };
24+
const noItem = { title: noTitle, isCloseAffordance: true };
25+
const chosenItem = await window.showInformationMessage(
26+
message,
27+
{ modal },
28+
yesItem,
29+
noItem,
30+
);
31+
if (!chosenItem) {
32+
return undefined;
33+
}
34+
return chosenItem?.title === yesItem.title;
35+
}
36+
37+
/**
38+
* Opens a modal dialog for the user to make a yes/no choice.
39+
*
40+
* @param message The message to show.
41+
* @param modal If true (the default), show a modal dialog box, otherwise dialog is non-modal and can
42+
* be closed even if the user does not make a choice.
43+
*
44+
* @return
45+
* `true` if the user clicks 'Yes',
46+
* `false` if the user clicks 'No' or cancels the dialog,
47+
* `undefined` if the dialog is closed without the user making a choice.
48+
*/
49+
export async function showBinaryChoiceWithUrlDialog(
50+
message: string,
51+
url: string,
52+
): Promise<boolean | undefined> {
53+
const urlItem = { title: "More Information", isCloseAffordance: false };
54+
const yesItem = { title: "Yes", isCloseAffordance: false };
55+
const noItem = { title: "No", isCloseAffordance: true };
56+
let chosenItem;
57+
58+
// Keep the dialog open as long as the user is clicking the 'more information' option.
59+
// To prevent an infinite loop, if the user clicks 'more information' 5 times, close the dialog and return cancelled
60+
let count = 0;
61+
do {
62+
chosenItem = await window.showInformationMessage(
63+
message,
64+
{ modal: true },
65+
urlItem,
66+
yesItem,
67+
noItem,
68+
);
69+
if (chosenItem === urlItem) {
70+
await env.openExternal(Uri.parse(url, true));
71+
}
72+
count++;
73+
} while (chosenItem === urlItem && count < 5);
74+
75+
if (!chosenItem || chosenItem.title === urlItem.title) {
76+
return undefined;
77+
}
78+
return chosenItem.title === yesItem.title;
79+
}
80+
81+
/**
82+
* Show an information message with a customisable action.
83+
* @param message The message to show.
84+
* @param actionMessage The call to action message.
85+
*
86+
* @return `true` if the user clicks the action, `false` if the user cancels the dialog.
87+
*/
88+
export async function showInformationMessageWithAction(
89+
message: string,
90+
actionMessage: string,
91+
): Promise<boolean> {
92+
const actionItem = { title: actionMessage, isCloseAffordance: false };
93+
const chosenItem = await window.showInformationMessage(message, actionItem);
94+
return chosenItem === actionItem;
95+
}
96+
97+
/**
98+
* Opens a modal dialog for the user to make a choice between yes/no/never be asked again.
99+
*
100+
* @param message The message to show.
101+
* @param modal If true (the default), show a modal dialog box, otherwise dialog is non-modal and can
102+
* be closed even if the user does not make a choice.
103+
* @param yesTitle The text in the box indicating the affirmative choice.
104+
* @param noTitle The text in the box indicating the negative choice.
105+
* @param neverTitle The text in the box indicating the opt out choice.
106+
*
107+
* @return
108+
* `Yes` if the user clicks 'Yes',
109+
* `No` if the user clicks 'No' or cancels the dialog,
110+
* `No, and never ask me again` if the user clicks 'No, and never ask me again',
111+
* `undefined` if the dialog is closed without the user making a choice.
112+
*/
113+
export async function showNeverAskAgainDialog(
114+
message: string,
115+
modal = true,
116+
yesTitle = "Yes",
117+
noTitle = "No",
118+
neverAskAgainTitle = "No, and never ask me again",
119+
): Promise<string | undefined> {
120+
const yesItem = { title: yesTitle, isCloseAffordance: true };
121+
const noItem = { title: noTitle, isCloseAffordance: false };
122+
const neverAskAgainItem = {
123+
title: neverAskAgainTitle,
124+
isCloseAffordance: false,
125+
};
126+
const chosenItem = await window.showInformationMessage(
127+
message,
128+
{ modal },
129+
yesItem,
130+
noItem,
131+
neverAskAgainItem,
132+
);
133+
134+
return chosenItem?.title;
135+
}

extensions/ql-vscode/src/common/vscode/external-files.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { Uri, window } from "vscode";
22
import { AppCommandManager } from "../commands";
3-
import {
4-
showAndLogExceptionWithTelemetry,
5-
showBinaryChoiceDialog,
6-
} from "../../helpers";
3+
import { showAndLogExceptionWithTelemetry } from "../../helpers";
4+
import { showBinaryChoiceDialog } from "./dialog";
75
import { redactableError } from "../../pure/errors";
86
import {
97
asError,
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { dirname, join } from "path";
2+
import { workspace, WorkspaceFolder } from "vscode";
3+
4+
/** Returns true if the specified workspace folder is on the file system. */
5+
export function isWorkspaceFolderOnDisk(
6+
workspaceFolder: WorkspaceFolder,
7+
): boolean {
8+
return workspaceFolder.uri.scheme === "file";
9+
}
10+
11+
/** Gets all active workspace folders that are on the filesystem. */
12+
export function getOnDiskWorkspaceFoldersObjects() {
13+
const workspaceFolders = workspace.workspaceFolders ?? [];
14+
return workspaceFolders.filter(isWorkspaceFolderOnDisk);
15+
}
16+
17+
/** Gets all active workspace folders that are on the filesystem. */
18+
export function getOnDiskWorkspaceFolders() {
19+
return getOnDiskWorkspaceFoldersObjects().map((folder) => folder.uri.fsPath);
20+
}
21+
22+
/** Check if folder is already present in workspace */
23+
export function isFolderAlreadyInWorkspace(folderName: string) {
24+
const workspaceFolders = workspace.workspaceFolders || [];
25+
26+
return !!workspaceFolders.find(
27+
(workspaceFolder) => workspaceFolder.name === folderName,
28+
);
29+
}
30+
31+
/**
32+
* Returns the path of the first folder in the workspace.
33+
* This is used to decide where to create skeleton QL packs.
34+
*
35+
* If the first folder is a QL pack, then the parent folder is returned.
36+
* This is because the vscode-codeql-starter repo contains a ql pack in
37+
* the first folder.
38+
*
39+
* This is a temporary workaround until we can retire the
40+
* vscode-codeql-starter repo.
41+
*/
42+
export function getFirstWorkspaceFolder() {
43+
const workspaceFolders = getOnDiskWorkspaceFolders();
44+
45+
if (!workspaceFolders || workspaceFolders.length === 0) {
46+
throw new Error("No workspace folders found");
47+
}
48+
49+
const firstFolderFsPath = workspaceFolders[0];
50+
51+
// For the vscode-codeql-starter repo, the first folder will be a ql pack
52+
// so we need to get the parent folder
53+
if (
54+
firstFolderFsPath.includes(
55+
join("vscode-codeql-starter", "codeql-custom-queries"),
56+
)
57+
) {
58+
// return the parent folder
59+
return dirname(firstFolderFsPath);
60+
} else {
61+
// if the first folder is not a ql pack, then we are in a normal workspace
62+
return firstFolderFsPath;
63+
}
64+
}

extensions/ql-vscode/src/data-extensions-editor/extension-pack-picker.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import { dump as dumpYaml, load as loadYaml } from "js-yaml";
44
import { minimatch } from "minimatch";
55
import { CancellationToken, window } from "vscode";
66
import { CodeQLCliServer } from "../codeql-cli/cli";
7+
import { showAndLogErrorMessage } from "../helpers";
78
import {
89
getOnDiskWorkspaceFolders,
910
getOnDiskWorkspaceFoldersObjects,
10-
showAndLogErrorMessage,
11-
} from "../helpers";
11+
} from "../common/vscode/workspace-folders";
1212
import { ProgressCallback } from "../common/vscode/progress";
1313
import { DatabaseItem } from "../databases/local-databases";
1414
import { getQlPackPath, QLPACK_FILENAMES } from "../pure/ql";

extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ import { CoreCompletedQuery, QueryRunner } from "../query-server";
22
import { dir } from "tmp-promise";
33
import { writeFile } from "fs-extra";
44
import { dump as dumpYaml } from "js-yaml";
5-
import {
6-
getOnDiskWorkspaceFolders,
7-
showAndLogExceptionWithTelemetry,
8-
} from "../helpers";
5+
import { showAndLogExceptionWithTelemetry } from "../helpers";
6+
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
97
import { TeeLogger } from "../common";
108
import { isQueryLanguage } from "../common/query-language";
119
import { CancellationToken } from "vscode";

extensions/ql-vscode/src/data-extensions-editor/generate-flow-model.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@ import { CodeQLCliServer } from "../codeql-cli/cli";
66
import { TeeLogger } from "../common";
77
import { extensiblePredicateDefinitions } from "./predicates";
88
import { ProgressCallback } from "../common/vscode/progress";
9-
import {
10-
getOnDiskWorkspaceFolders,
11-
showAndLogExceptionWithTelemetry,
12-
} from "../helpers";
9+
import { showAndLogExceptionWithTelemetry } from "../helpers";
10+
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
1311
import {
1412
ModeledMethodType,
1513
ModeledMethodWithSignature,

extensions/ql-vscode/src/databases/local-databases/database-manager.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,12 @@ import {
1313
import { join } from "path";
1414
import { FullDatabaseOptions } from "./database-options";
1515
import { DatabaseItemImpl } from "./database-item-impl";
16+
import { showAndLogExceptionWithTelemetry } from "../../helpers";
17+
import { showNeverAskAgainDialog } from "../../common/vscode/dialog";
1618
import {
1719
getFirstWorkspaceFolder,
1820
isFolderAlreadyInWorkspace,
19-
showAndLogExceptionWithTelemetry,
20-
showNeverAskAgainDialog,
21-
} from "../../helpers";
21+
} from "../../common/vscode/workspace-folders";
2222
import { isQueryLanguage } from "../../common/query-language";
2323
import { existsSync } from "fs";
2424
import { QlPackGenerator } from "../../qlpack-generator";

extensions/ql-vscode/src/databases/qlpack.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { readFile } from "fs-extra";
66
import { getQlPackPath } from "../pure/ql";
77
import { CodeQLCliServer, QlpacksInfo } from "../codeql-cli/cli";
88
import { extLogger } from "../common";
9-
import { getOnDiskWorkspaceFolders } from "../helpers";
9+
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
1010

1111
export interface QlPacksForLanguage {
1212
/** The name of the pack containing the dbscheme. */

extensions/ql-vscode/src/debugger/debug-configuration.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import {
44
DebugConfigurationProvider,
55
WorkspaceFolder,
66
} from "vscode";
7-
import { getOnDiskWorkspaceFolders, showAndLogErrorMessage } from "../helpers";
7+
import { showAndLogErrorMessage } from "../helpers";
8+
import { getOnDiskWorkspaceFolders } from "../common/vscode/workspace-folders";
89
import { LocalQueries } from "../local-queries";
910
import { getQuickEvalContext, validateQueryPath } from "../run-queries-shared";
1011
import * as CodeQLProtocol from "./debug-protocol";

0 commit comments

Comments
 (0)