Skip to content

Commit 07749c1

Browse files
authored
Move command to export Project contents to Command Palette (#1707)
1 parent 3be1083 commit 07749c1

File tree

4 files changed

+117
-162
lines changed

4 files changed

+117
-162
lines changed

package.json

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,12 @@
236236
"when": "resourceScheme == objectscript && vscode-objectscript.connectActive"
237237
},
238238
{
239-
"command": "vscode-objectscript.explorer.project.exportProjectContents",
240-
"when": "false"
239+
"command": "vscode-objectscript.export",
240+
"when": "workspaceFolderCount != 0"
241+
},
242+
{
243+
"command": "vscode-objectscript.exportProjectContents",
244+
"when": "workspaceFolderCount != 0"
241245
},
242246
{
243247
"command": "vscode-objectscript.explorer.project.compileProjectContents",
@@ -446,11 +450,6 @@
446450
"when": "view == ObjectScriptProjectsExplorer && viewItem == dataNode:projectNode",
447451
"group": "5_objectscript_prj@4"
448452
},
449-
{
450-
"command": "vscode-objectscript.explorer.project.exportProjectContents",
451-
"when": "view == ObjectScriptProjectsExplorer && viewItem == dataNode:projectNode",
452-
"group": "5_objectscript_prj@5"
453-
},
454453
{
455454
"command": "vscode-objectscript.explorer.project.compileProjectContents",
456455
"when": "view == ObjectScriptProjectsExplorer && viewItem == dataNode:projectNode",
@@ -837,7 +836,7 @@
837836
{
838837
"category": "ObjectScript",
839838
"command": "vscode-objectscript.export",
840-
"title": "Export Code from Server"
839+
"title": "Export Code from Server..."
841840
},
842841
{
843842
"category": "ObjectScript",
@@ -1068,8 +1067,8 @@
10681067
},
10691068
{
10701069
"category": "ObjectScript",
1071-
"command": "vscode-objectscript.explorer.project.exportProjectContents",
1072-
"title": "Export Project Contents"
1070+
"command": "vscode-objectscript.exportProjectContents",
1071+
"title": "Export Project Contents from Server..."
10731072
},
10741073
{
10751074
"category": "ObjectScript",

src/commands/export.ts

Lines changed: 59 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -165,93 +165,70 @@ export async function exportList(files: string[], workspaceFolder: string, names
165165
}
166166

167167
export async function exportAll(): Promise<any> {
168-
let workspaceFolder: string;
169-
const workspaceList = vscode.workspace.workspaceFolders
170-
.filter((folder) => !schemas.includes(folder.uri.scheme) && config("conn", folder.name).active)
171-
.map((el) => el.name);
172-
if (workspaceList.length > 1) {
173-
const selection = await vscode.window.showQuickPick(workspaceList, {
174-
title: "Pick the workspace folder to export files to.",
175-
});
176-
if (selection === undefined) {
168+
try {
169+
const wsFolder = await getWsFolder("Pick a workspace folder to export files to.", true, false, true, true);
170+
if (!wsFolder) {
171+
if (wsFolder === undefined) {
172+
// Strict equality needed because undefined == null
173+
vscode.window.showErrorMessage(
174+
"'Export Code from Server...' command requires a workspace folder with an active server connection.",
175+
"Dismiss"
176+
);
177+
}
177178
return;
178179
}
179-
workspaceFolder = selection;
180-
} else if (workspaceList.length === 1) {
181-
workspaceFolder = workspaceList.pop();
182-
} else {
183-
vscode.window.showInformationMessage(
184-
"There are no folders in the current workspace that code can be exported to.",
185-
"Dismiss"
186-
);
187-
return;
188-
}
189-
if (!config("conn", workspaceFolder).active) {
190-
return;
191-
}
192-
const api = new AtelierAPI(workspaceFolder);
193-
const { category, generated, filter, exactFilter, mapped } = config("export", workspaceFolder);
194-
// Replicate the behavior of getDocNames() but use StudioOpenDialog for better performance
195-
let filterStr = "";
196-
switch (category) {
197-
case "CLS":
198-
filterStr = "Type = 4";
199-
break;
200-
case "CSP":
201-
filterStr = "Type %INLIST $LISTFROMSTRING('5,6')";
202-
break;
203-
case "OTH":
204-
filterStr = "Type NOT %INLIST $LISTFROMSTRING('0,1,2,3,4,5,6,11,12')";
205-
break;
206-
case "RTN":
207-
filterStr = "Type %INLIST $LISTFROMSTRING('0,1,2,3,11,12')";
208-
break;
209-
}
210-
if (filter !== "" || exactFilter !== "") {
211-
if (exactFilter !== "") {
212-
if (filterStr !== "") {
213-
filterStr += " AND ";
214-
}
215-
filterStr += `Name LIKE '${exactFilter}'`;
216-
} else {
217-
if (filterStr !== "") {
218-
filterStr += " AND ";
219-
}
220-
filterStr += `Name LIKE '%${filter}%'`;
180+
const api = new AtelierAPI(wsFolder.uri);
181+
const { category, generated, filter, exactFilter, mapped } = config("export", wsFolder.name);
182+
const filters: string[] = [];
183+
switch (category) {
184+
case "CLS":
185+
filters.push("Type = 4");
186+
break;
187+
case "CSP":
188+
filters.push("Type %INLIST $LISTFROMSTRING('5,6')");
189+
break;
190+
case "OTH":
191+
filters.push("Type NOT %INLIST $LISTFROMSTRING('0,1,2,3,4,5,6,11,12')");
192+
break;
193+
case "RTN":
194+
filters.push("Type %INLIST $LISTFROMSTRING('0,1,2,3,11,12')");
195+
break;
221196
}
222-
}
223-
return api
224-
.actionQuery("SELECT Name FROM %Library.RoutineMgr_StudioOpenDialog(?,?,?,?,?,?,?,?,?,?)", [
225-
"*",
226-
"1",
227-
"1",
228-
api.config.ns.toLowerCase() === "%sys" ? "1" : "0",
229-
"1",
230-
"0",
231-
generated ? "1" : "0",
232-
filterStr,
233-
"0",
234-
mapped ? "1" : "0",
235-
])
236-
.then(async (data) => {
237-
let files: vscode.QuickPickItem[] = data.result.content.map((file) => {
238-
return { label: file.Name, picked: true };
239-
});
240-
files = await vscode.window.showQuickPick(files, {
241-
canPickMany: true,
242-
ignoreFocusOut: true,
243-
prompt: "Uncheck a file to exclude it. Press 'Escape' to cancel export.",
244-
title: "Files to Export",
245-
});
246-
if (files === undefined) {
247-
return;
248-
}
249-
return exportList(
250-
files.map((file) => file.label),
251-
workspaceFolder,
252-
api.config.ns
197+
/** Verify that a filter is non-empty and won't allow SQL injection */
198+
const filterIsValid = (f) => typeof f == "string" && /^(?:[^']|'')+$/.test(f);
199+
if (filterIsValid(exactFilter)) {
200+
filters.push(`Name LIKE '${exactFilter}'`);
201+
} else if (filterIsValid(filter)) {
202+
filters.push(`Name LIKE '%${filter}%'`);
203+
}
204+
let files: vscode.QuickPickItem[] = await api
205+
.actionQuery("SELECT Name FROM %Library.RoutineMgr_StudioOpenDialog('*',1,1,?,1,0,?,?,0,?)", [
206+
api.ns == "%SYS" ? "1" : "0",
207+
generated ? "1" : "0",
208+
filters.join(" AND "),
209+
mapped ? "1" : "0",
210+
])
211+
.then((data) =>
212+
data.result.content.map((file) => {
213+
return { label: file.Name, picked: true };
214+
})
253215
);
216+
if (!files?.length) return;
217+
files = await vscode.window.showQuickPick(files, {
218+
canPickMany: true,
219+
ignoreFocusOut: true,
220+
prompt: "Uncheck a file to exclude it. Press 'Escape' to cancel export.",
221+
title: "Files to Export",
254222
});
223+
if (!files?.length) return;
224+
await exportList(
225+
files.map((file) => file.label),
226+
wsFolder.name,
227+
api.ns
228+
);
229+
} catch (error) {
230+
handleError(error, "Error executing 'Export Code from Server...' command.");
231+
}
255232
}
256233

257234
export async function exportExplorerItems(nodes: NodeBase[]): Promise<any> {

src/commands/project.ts

Lines changed: 46 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import * as vscode from "vscode";
22
import { AtelierAPI } from "../api";
3-
import { config, filesystemSchemas, projectsExplorerProvider, schemas } from "../extension";
4-
import { compareConns } from "../providers/DocumentContentProvider";
3+
import { config, filesystemSchemas, projectsExplorerProvider } from "../extension";
54
import { isfsDocumentName } from "../providers/FileSystemProvider/FileSystemProvider";
6-
import { compileErrorMsg, getWsServerConnection, handleError, notIsfs, notNull } from "../utils";
5+
import { compileErrorMsg, getWsFolder, getWsServerConnection, handleError, notNull } from "../utils";
76
import { exportList } from "./export";
87
import { OtherStudioAction, StudioActions } from "./studio";
98
import { NodeBase, ProjectNode, ProjectRootNode, RoutineNode, CSPFileNode, ClassNode } from "../explorer/nodes";
@@ -14,31 +13,37 @@ export interface ProjectItem {
1413
Type: string;
1514
}
1615

17-
export async function pickProject(api: AtelierAPI): Promise<string | undefined> {
18-
const ns = api.config.ns.toUpperCase();
16+
export async function pickProject(api: AtelierAPI, allowCreate = true): Promise<string | undefined> {
1917
const projects: vscode.QuickPickItem[] = await api
2018
.actionQuery("SELECT Name, Description FROM %Studio.Project", [])
2119
.then((data) =>
2220
data.result.content.map((prj) => {
2321
return { label: prj.Name, detail: prj.Description };
2422
})
2523
);
26-
if (projects.length === 0) {
27-
const create = await vscode.window.showQuickPick(["Yes", "No"], {
28-
title: `Namespace ${ns} on server '${api.serverId}' contains no projects. Create one?`,
29-
});
30-
if (create == "Yes") {
31-
return createProject(undefined, api);
24+
if (projects.length == 0) {
25+
if (allowCreate) {
26+
const create = await vscode.window.showQuickPick(["Yes", "No"], {
27+
title: `Namespace ${api.ns} on server '${api.serverId}' contains no projects. Create one?`,
28+
});
29+
if (create == "Yes") {
30+
return createProject(undefined, api);
31+
}
32+
} else {
33+
vscode.window.showInformationMessage(
34+
`Namespace ${api.ns} on server '${api.serverId}' contains no projects.`,
35+
"Dismiss"
36+
);
3237
}
3338
return;
3439
}
3540
return new Promise<string | undefined>((resolve) => {
3641
let result: string;
3742
let resolveOnHide = true;
3843
const quickPick = vscode.window.createQuickPick();
39-
quickPick.title = `Select a project in namespace ${ns} on server '${api.serverId}', or click '+' to add one.`;
44+
quickPick.title = `Select a project in namespace ${api.ns} on server '${api.serverId}'${allowCreate ? ", or click '+' to add one" : ""}.`;
4045
quickPick.items = projects;
41-
quickPick.buttons = [{ iconPath: new vscode.ThemeIcon("add"), tooltip: "Create new project" }];
46+
quickPick.buttons = allowCreate ? [{ iconPath: new vscode.ThemeIcon("add"), tooltip: "Create new project" }] : [];
4247

4348
async function addAndResolve() {
4449
resolveOnHide = false;
@@ -879,64 +884,38 @@ export async function modifyProject(
879884
}
880885
}
881886

882-
export async function exportProjectContents(node: ProjectNode | undefined): Promise<any> {
883-
let workspaceFolder: string;
884-
const api = new AtelierAPI(node.workspaceFolderUri);
885-
api.setNamespace(node.namespace);
886-
const project = node.label;
887-
if (notIsfs(node.workspaceFolderUri)) {
888-
workspaceFolder = node.workspaceFolder;
889-
} else {
890-
const conn = config("conn", node.workspaceFolder);
891-
const workspaceList = vscode.workspace.workspaceFolders
892-
.filter((folder) => {
893-
if (schemas.includes(folder.uri.scheme)) {
894-
return false;
895-
}
896-
const wFolderConn = config("conn", folder.name);
897-
if (!compareConns(conn, wFolderConn)) {
898-
return false;
899-
}
900-
if (!wFolderConn.active) {
901-
return false;
902-
}
903-
if (wFolderConn.ns.toLowerCase() != node.namespace.toLowerCase()) {
904-
return false;
905-
}
906-
return true;
907-
})
908-
.map((el) => el.name);
909-
if (workspaceList.length > 1) {
910-
const selection = await vscode.window.showQuickPick(workspaceList, {
911-
title: "Pick the workspace folder to export files to.",
912-
});
913-
if (selection === undefined) {
914-
return;
887+
export async function exportProjectContents(): Promise<any> {
888+
try {
889+
const wsFolder = await getWsFolder("Pick a workspace folder to export files to.", true, false, true, true);
890+
if (!wsFolder) {
891+
if (wsFolder === undefined) {
892+
// Strict equality needed because undefined == null
893+
vscode.window.showErrorMessage(
894+
"'Export Project Contents from Server...' command requires a workspace folder with an active server connection.",
895+
"Dismiss"
896+
);
915897
}
916-
workspaceFolder = selection;
917-
} else if (workspaceList.length === 1) {
918-
workspaceFolder = workspaceList.pop();
919-
} else {
920-
vscode.window.showInformationMessage(
921-
"There are no folders in the current workspace that code can be exported to.",
922-
"Dismiss"
923-
);
924898
return;
925899
}
900+
const api = new AtelierAPI(wsFolder.uri);
901+
const project = await pickProject(api, false);
902+
if (!project) return;
903+
await exportList(
904+
await api
905+
.actionQuery(
906+
"SELECT CASE WHEN sod.Name %STARTSWITH '/' THEN SUBSTR(sod.Name,2) ELSE sod.Name END Name " +
907+
"FROM %Library.RoutineMgr_StudioOpenDialog('*',1,1,1,1,0,1) AS sod JOIN %Studio.Project_ProjectItemsList(?) AS pil " +
908+
"ON sod.Name = pil.Name OR (pil.Type = 'CLS' AND pil.Name||'.cls' = sod.Name) " +
909+
"OR (pil.Type = 'CSP' AND pil.Name = SUBSTR(sod.Name,2)) OR (pil.Type = 'DIR' AND sod.Name %STARTSWITH '/'||pil.Name||'/')",
910+
[project]
911+
)
912+
.then((data) => data.result.content.map((e) => e.Name)),
913+
wsFolder.name,
914+
api.ns
915+
);
916+
} catch (error) {
917+
handleError(error, "Error executing 'Export Project Contents from Server...' command.");
926918
}
927-
if (workspaceFolder === undefined) {
928-
return;
929-
}
930-
const exportFiles: string[] = await api
931-
.actionQuery(
932-
"SELECT CASE WHEN sod.Name %STARTSWITH '/' THEN SUBSTR(sod.Name,2) ELSE sod.Name END Name " +
933-
"FROM %Library.RoutineMgr_StudioOpenDialog('*',1,1,1,1,0,1) AS sod JOIN %Studio.Project_ProjectItemsList(?) AS pil " +
934-
"ON sod.Name = pil.Name OR (pil.Type = 'CLS' AND pil.Name||'.cls' = sod.Name) " +
935-
"OR (pil.Type = 'CSP' AND pil.Name = SUBSTR(sod.Name,2)) OR (pil.Type = 'DIR' AND sod.Name %STARTSWITH '/'||pil.Name||'/')",
936-
[project]
937-
)
938-
.then((data) => data.result.content.map((e) => e.Name));
939-
return exportList(exportFiles, workspaceFolder, node.namespace);
940919
}
941920

942921
export async function compileProjectContents(node: ProjectNode): Promise<any> {

src/extension.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1507,9 +1507,9 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
15071507
sendCommandTelemetryEvent("deleteProject");
15081508
deleteProject(node);
15091509
}),
1510-
vscode.commands.registerCommand("vscode-objectscript.explorer.project.exportProjectContents", (node) => {
1511-
sendCommandTelemetryEvent("explorer.project.exportProjectContents");
1512-
exportProjectContents(node);
1510+
vscode.commands.registerCommand("vscode-objectscript.exportProjectContents", () => {
1511+
sendCommandTelemetryEvent("exportProjectContents");
1512+
exportProjectContents();
15131513
}),
15141514
vscode.commands.registerCommand("vscode-objectscript.explorer.project.compileProjectContents", (node) => {
15151515
sendCommandTelemetryEvent("explorer.project.compileProjectContents");

0 commit comments

Comments
 (0)