Skip to content

Commit 0b8a725

Browse files
committed
Merge remote-tracking branch 'upstream/master' into open-hidden
2 parents 6359f43 + 969b08d commit 0b8a725

File tree

4 files changed

+135
-9
lines changed

4 files changed

+135
-9
lines changed

package.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,10 @@
239239
"command": "vscode-objectscript.loadStudioColors",
240240
"when": "isWindows"
241241
},
242+
{
243+
"command": "vscode-objectscript.newFile.class",
244+
"when": "false"
245+
},
242246
{
243247
"command": "vscode-objectscript.newFile.businessOperation",
244248
"when": "false"
@@ -591,6 +595,11 @@
591595
}
592596
],
593597
"file/newFile": [
598+
{
599+
"command": "vscode-objectscript.newFile.class",
600+
"when": "workspaceFolderCount != 0",
601+
"group": "file"
602+
},
594603
{
595604
"command": "vscode-objectscript.newFile.kpi",
596605
"when": "workspaceFolderCount != 0",
@@ -1021,6 +1030,11 @@
10211030
"command": "vscode-objectscript.loadStudioColors",
10221031
"title": "Load Studio Syntax Colors"
10231032
},
1033+
{
1034+
"category": "ObjectScript",
1035+
"command": "vscode-objectscript.newFile.class",
1036+
"title": "Class"
1037+
},
10241038
{
10251039
"category": "ObjectScript",
10261040
"command": "vscode-objectscript.newFile.kpi",

src/commands/newFile.ts

Lines changed: 92 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { DocumentContentProvider } from "../providers/DocumentContentProvider";
66
import { replaceFile, getWsFolder, handleError, displayableUri } from "../utils";
77
import { getFileName } from "./export";
88
import { getUrisForDocument } from "../utils/documentIndex";
9+
import { pickDocument } from "../utils/documentPicker";
910

1011
interface InputStepItem extends vscode.QuickPickItem {
1112
value?: string;
@@ -25,7 +26,13 @@ interface QuickPickStepOptions {
2526
items: InputStepItem[];
2627
}
2728

28-
type InputStepOptions = InputBoxStepOptions | QuickPickStepOptions;
29+
interface ClassPickStepOptions {
30+
type: "classPick";
31+
title: string;
32+
api: AtelierAPI | undefined;
33+
}
34+
35+
type InputStepOptions = InputBoxStepOptions | QuickPickStepOptions | ClassPickStepOptions;
2936

3037
/**
3138
* Get input from the user using multiple steps.
@@ -101,6 +108,63 @@ async function multiStepInput(steps: InputStepOptions[]): Promise<string[] | und
101108
});
102109
inputBox.show();
103110
});
111+
} else if (stepOptions.type == "classPick") {
112+
// Optional step: escape = skip (store ""), back = go back one step, pick = store class name
113+
let picked: string | undefined;
114+
if (stepOptions.api) {
115+
picked = await pickDocument(stepOptions.api, stepOptions.title, "cls", step + 1, steps.length);
116+
} else {
117+
// Fallback InputBox when there's no server connection
118+
picked = await new Promise<string | undefined>((resolve) => {
119+
let settled = false;
120+
const settle = (v: string | undefined) => {
121+
if (!settled) {
122+
settled = true;
123+
resolve(v);
124+
}
125+
};
126+
const inputBox = vscode.window.createInputBox();
127+
inputBox.ignoreFocusOut = true;
128+
inputBox.step = step + 1;
129+
inputBox.totalSteps = steps.length;
130+
inputBox.buttons = step > 0 ? [vscode.QuickInputButtons.Back] : [];
131+
inputBox.title = stepOptions.title;
132+
inputBox.placeholder = "Package.Subpackage.Class";
133+
inputBox.onDidTriggerButton(() => {
134+
settle(undefined); // Back was pressed
135+
inputBox.hide();
136+
});
137+
inputBox.onDidAccept(() => {
138+
if (typeof inputBox.validationMessage != "string") {
139+
settle(inputBox.value); // "" = skip, or a valid class name
140+
inputBox.hide();
141+
}
142+
});
143+
inputBox.onDidHide(() => {
144+
settle(""); // Escape = skip this optional step
145+
inputBox.dispose();
146+
});
147+
inputBox.onDidChangeValue((value) => {
148+
inputBox.validationMessage = value ? validateClassName(value) : undefined;
149+
});
150+
inputBox.show();
151+
});
152+
}
153+
if (picked === "") {
154+
// Back button was pressed: go back one step
155+
step--;
156+
} else {
157+
// undefined = skipped, or a class name was entered/picked
158+
if (typeof picked == "undefined") {
159+
picked = "";
160+
} else if (picked.slice(-4) == ".cls") {
161+
picked = picked.slice(0, -4);
162+
}
163+
results[step] = picked;
164+
step++;
165+
}
166+
// This is an optional step; never cancel the wizard on escape
167+
escape = false;
104168
} else {
105169
// Show the QuickPick
106170
escape = await new Promise<boolean>((resolve) => {
@@ -222,6 +286,7 @@ function getAdapterPrompt(adapters: InputStepItem[], type: AdapaterClassType): I
222286

223287
/** The types of classes we can create */
224288
export enum NewFileType {
289+
Class = "Class",
225290
BusinessOperation = "Business Operation",
226291
BusinessService = "Business Service",
227292
BPL = "Business Process",
@@ -262,7 +327,7 @@ export async function newFile(type: NewFileType): Promise<void> {
262327
api = undefined;
263328
}
264329

265-
if (type != NewFileType.KPI) {
330+
if (type != NewFileType.KPI && type != NewFileType.Class) {
266331
// Check if we're connected to an Interoperability namespace
267332
const ensemble: boolean = api
268333
? await api.getNamespace().then((data) => data.result.content.features[0].enabled)
@@ -402,7 +467,7 @@ export async function newFile(type: NewFileType): Promise<void> {
402467
inputSteps.push(
403468
{
404469
type: "inputBox",
405-
title: `Enter a name for the new ${type} class`,
470+
title: `Enter a name for the new ${type == NewFileType.Class ? "class" : type + " class"}`,
406471
placeholder: "Package.Subpackage.Class",
407472
validateInput: (value: string) => {
408473
const valid = validateClassName(value);
@@ -933,6 +998,30 @@ Parameter RESPONSECLASSNAME As CLASSNAME = "${respClass}";`
933998
/// InterSystems IRIS purges message bodies based on the class when the option to purge message bodies is enabled
934999
Parameter ENSPURGE As BOOLEAN = 1;
9351000
1001+
}
1002+
`;
1003+
} else if (type == NewFileType.Class) {
1004+
// Add the superclass picker as the third step
1005+
inputSteps.push({
1006+
type: "classPick",
1007+
title: "Pick an optional superclass or press 'Escape' for none",
1008+
api: api,
1009+
});
1010+
1011+
// Prompt the user
1012+
const results = await multiStepInput(inputSteps);
1013+
if (!results) {
1014+
return;
1015+
}
1016+
cls = results[0];
1017+
const [, desc, superclass] = results;
1018+
1019+
// Generate the file's content
1020+
clsContent = `
1021+
${typeof desc == "string" ? "/// " + desc.replace(/\n/g, "\n/// ") : ""}
1022+
Class ${cls}${superclass ? ` Extends ${superclass}` : ""}
1023+
{
1024+
9361025
}
9371026
`;
9381027
}

src/extension.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1607,6 +1607,10 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
16071607
sendCommandTelemetryEvent("loadStudioColors");
16081608
loadStudioColors(languageServerExt);
16091609
}),
1610+
vscode.commands.registerCommand("vscode-objectscript.newFile.class", () => {
1611+
sendCommandTelemetryEvent("newFile.class");
1612+
newFile(NewFileType.Class);
1613+
}),
16101614
vscode.commands.registerCommand("vscode-objectscript.newFile.businessOperation", () => {
16111615
sendCommandTelemetryEvent("newFile.businessOperation");
16121616
newFile(NewFileType.BusinessOperation);

src/utils/documentPicker.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -285,14 +285,23 @@ export async function pickDocuments(api: AtelierAPI, prompt?: string): Promise<s
285285

286286
/**
287287
* Prompts the user to select a single document in server-namespace `api`
288-
* using a custom QuickPick. An optional prompt will customize the title.
288+
* using a custom QuickPick. An optional `prompt` will customize the title.
289+
* `typeSuffix` can be provided to filter for specific types of documents (e.g. "cls").
290+
* If `step` is provided and greater than 1, a back button will be included that resolves to an empty string when pressed.
291+
* If `numberOfSteps` is provided, the title will be suffixed with the current step and total number of steps (e.g. "(2/4)") instead of the namespace and server information.
289292
*/
290-
export async function pickDocument(api: AtelierAPI, prompt?: string): Promise<string> {
293+
export async function pickDocument(
294+
api: AtelierAPI,
295+
prompt?: string,
296+
typeSuffix?: string,
297+
step?: number,
298+
numberOfSteps?: number
299+
): Promise<string> {
291300
let sys: "0" | "1" = "0";
292301
let gen: "0" | "1" = "0";
293302
let map: "0" | "1" = "1";
294303
const query = "SELECT Name, Type FROM %Library.RoutineMgr_StudioOpenDialog(?,1,1,?,0,0,?,,0,?)";
295-
const webApps = cspAppsForApi(api);
304+
const webApps = (typeSuffix ?? "csp") == "csp" ? cspAppsForApi(api) : [];
296305
const webAppRootItems = webApps.map((app: string) => {
297306
return {
298307
label: app,
@@ -302,11 +311,12 @@ export async function pickDocument(api: AtelierAPI, prompt?: string): Promise<st
302311

303312
return new Promise<string>((resolve) => {
304313
const quickPick = vscode.window.createQuickPick<DocumentPickerItem>();
305-
quickPick.title = `${prompt ? prompt : "Select a document"} in namespace '${api.ns}' on server '${api.serverId}'`;
306314
quickPick.prompt =
307315
"Select a package or folder to view its contents. Selecting '..' shows the previous level's contents. You may also type a full document name into the filter box and press 'Enter' to select that document.";
316+
quickPick.title = `${prompt ? prompt : "Select a document"} ${numberOfSteps ? `(${step}/${numberOfSteps})` : `in namespace '${api.ns}' on server '${api.serverId}'`}`;
308317
quickPick.ignoreFocusOut = true;
309318
quickPick.buttons = [
319+
...((step ?? 0) > 1 ? [vscode.QuickInputButtons.Back] : []),
310320
{
311321
iconPath: new vscode.ThemeIcon("library"),
312322
tooltip: "System",
@@ -329,7 +339,12 @@ export async function pickDocument(api: AtelierAPI, prompt?: string): Promise<st
329339

330340
const getRootItems = (): Promise<void> => {
331341
return api
332-
.actionQuery(`${query} WHERE Type != 5 AND Type != 10`, ["*,'*.prj", sys, gen, map])
342+
.actionQuery(`${query} WHERE Type != 5 AND Type != 10`, [
343+
typeSuffix ? `*.${typeSuffix}` : "*,'*.prj",
344+
sys,
345+
gen,
346+
map,
347+
])
333348
.then((data) => {
334349
const rootitems: DocumentPickerItem[] = data.result.content.map((i) => createSingleSelectItem(i));
335350
const findLastIndex = (): number => {
@@ -358,6 +373,10 @@ export async function pickDocument(api: AtelierAPI, prompt?: string): Promise<st
358373
quickPick.onDidTriggerButton((button) => {
359374
quickPick.busy = true;
360375
quickPick.enabled = false;
376+
if (button === vscode.QuickInputButtons.Back) {
377+
resolve(""); // signal "go back" to the caller
378+
quickPick.hide();
379+
}
361380
if (button.tooltip == "System") {
362381
sys = button.toggle.checked ? "1" : "0";
363382
} else if (button.tooltip == "Generated") {
@@ -437,7 +456,7 @@ export async function pickDocument(api: AtelierAPI, prompt?: string): Promise<st
437456
getRootItems();
438457
} else {
439458
api
440-
.actionQuery(query, [`${item.fullName}/*`, sys, gen, map])
459+
.actionQuery(query, [`${item.fullName}/*${typeSuffix ? `.${typeSuffix}` : ""}`, sys, gen, map])
441460
.then((data) => {
442461
const delim = item.fullName.includes("/") ? "/" : ".";
443462
const newItems: DocumentPickerItem[] = data.result.content.map((i) =>

0 commit comments

Comments
 (0)