Skip to content

Commit 7429c74

Browse files
author
Tyler Deemer
committed
Added studio actions
1 parent f465aca commit 7429c74

File tree

3 files changed

+218
-69
lines changed

3 files changed

+218
-69
lines changed

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@
156156
"command": "vscode-objectscript.explorer.compile",
157157
"when": "view == ObjectScriptExplorer && viewItem =~ /^dataNode:/"
158158
},
159+
{
160+
"command": "vscode-objectscript.studio.contextActions",
161+
"when": "view == ObjectScriptExplorer && viewItem =~ /^dataNode:/"
162+
},
159163
{
160164
"command": "vscode-objectscript.explorer.otherNamespace",
161165
"when": "view == ObjectScriptExplorer && viewItem =~ /^serverNode((?!:extra:).)*$/",
@@ -410,6 +414,11 @@
410414
"category": "ObjectScript",
411415
"command": "vscode-objectscript.studio.actions",
412416
"title": "Studio actions"
417+
},
418+
{
419+
"category": "ObjectScript",
420+
"command": "vscode-objectscript.studio.contextActions",
421+
"title": "Studio context actions"
413422
}
414423
],
415424
"keybindings": [

src/commands/studio.ts

Lines changed: 202 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ import * as vscode from "vscode";
22
import { AtelierAPI } from "../api";
33
import { config, FILESYSTEM_SCHEMA } from "../extension";
44
import { outputChannel } from "../utils";
5+
import { DocumentContentProvider } from "../providers/DocumentContentProvider";
6+
import { ClassNode } from "../explorer/models/classesNode";
7+
import { PackageNode } from "../explorer/models/packageNode";
8+
import { RoutineNode } from "../explorer/models/routineNode";
9+
import { NodeBase } from "../explorer/models/nodeBase";
510

611
interface StudioAction extends vscode.QuickPickItem {
712
name: string;
@@ -13,10 +18,19 @@ class StudioActions {
1318
private api: AtelierAPI;
1419
private name: string;
1520

16-
public constructor(uri: vscode.Uri) {
17-
this.uri = uri;
18-
this.name = this.uri.path.slice(1).replace(/\//g, ".");
19-
this.api = new AtelierAPI(uri.authority);
21+
public constructor(uriOrNode: vscode.Uri | PackageNode | ClassNode | RoutineNode) {
22+
if(uriOrNode instanceof vscode.Uri) {
23+
const uri: vscode.Uri = uriOrNode;
24+
this.uri = uri;
25+
this.name = this.uri.path.slice(1).replace(/\//g, ".");
26+
this.api = new AtelierAPI(uri.authority);
27+
} else {
28+
const node: NodeBase = uriOrNode;
29+
this.api = new AtelierAPI();
30+
this.name = (node instanceof PackageNode)
31+
? node.fullName + ".PKG"
32+
: node.fullName;
33+
}
2034
}
2135

2236
public processUserAction(userAction): Thenable<any> {
@@ -36,79 +50,133 @@ class StudioActions {
3650
.showWarningMessage(target, { modal: true }, "Yes", "No")
3751
.then(answer => (answer === "Yes" ? "1" : answer === "No" ? "0" : "2"));
3852
case 2: // Run a CSP page/Template. The Target is the full url to the CSP page/Template
39-
// Open the target URL in a webview
40-
const conn = config().conn;
41-
const column = vscode.window.activeTextEditor
42-
? vscode.window.activeTextEditor.viewColumn
43-
: undefined;
44-
const panel = vscode.window.createWebviewPanel(
45-
'studioactionwebview',
46-
'CSP Page',
47-
column || vscode.ViewColumn.One,
48-
{
49-
enableScripts: true,
50-
}
51-
);
52-
panel.webview.html = `
53-
<!DOCTYPE html>
54-
<html lang="en">
55-
<head>
56-
<style type="text/css">
57-
body, html
58-
{
59-
margin: 0; padding: 0; height: 100%; overflow: hidden;
60-
}
61-
#content
62-
{
63-
position:absolute; left: 0; right: 0; bottom: 0; top: 0px;
64-
}
65-
</style>
66-
</head>
67-
<body>
68-
<div id="content">
69-
<iframe src="http://${conn.host}:${conn.port}${target}" onLoad="checkForCancelState()" width="100%" height="100%" frameborder="0"></iframe>
70-
</div>
71-
<script>
72-
function checkForCancelState() {
73-
var x = document.getElementsByTagName("BODY")[0];
74-
console.log(x);
75-
}
76-
</script>
77-
</body>
78-
</html>
79-
`;
80-
panel.onDidDispose(
81-
() => {
82-
// fire a cancel answer if the user closes the webview
83-
return "2";
84-
}
85-
);
86-
// TODO: use panel.dispose() when the cancel text is sent back in the iframe
87-
break;
88-
// throw new Error("Not suppoorted");
53+
return new Promise((resolve) => {
54+
let answer = "2";
55+
const conn = config().conn;
56+
const column = vscode.window.activeTextEditor
57+
? vscode.window.activeTextEditor.viewColumn
58+
: undefined;
59+
const panel = vscode.window.createWebviewPanel(
60+
"studioactionwebview",
61+
"Studio Extension Page",
62+
column || vscode.ViewColumn.One,
63+
{
64+
enableScripts: true,
65+
}
66+
);
67+
panel.webview.onDidReceiveMessage(message => {
68+
if(message.result && message.result === "done") {
69+
answer = "1";
70+
panel.dispose();
71+
}
72+
});
73+
panel.onDidDispose(() => resolve(answer));
74+
75+
const url = new URL(`http://${conn.host}:${conn.port}${target}`);
76+
const api = new AtelierAPI();
77+
api.actionQuery("select %Atelier_v1_Utils.General_GetCSPToken(?) token", [url.toString()]).then(tokenObj => {
78+
const csptoken = tokenObj.result.content[0].token;
79+
url.searchParams.set('CSPCHD', csptoken);
80+
url.searchParams.set('Namespace', conn.ns);
81+
panel.webview.html = `
82+
<!DOCTYPE html>
83+
<html lang="en">
84+
<head>
85+
<style type="text/css">
86+
body, html {
87+
margin: 0; padding: 0; height: 100%; overflow: hidden;
88+
background-color: white;
89+
}
90+
#content {
91+
position:absolute; left: 0; right: 0; bottom: 0; top: 0px;
92+
}
93+
</style>
94+
</head>
95+
<body>
96+
<div id="content">
97+
<iframe src="${url.toString()}" width="100%" height="100%" frameborder="0"></iframe>
98+
</div>
99+
<script>
100+
const vscode = acquireVsCodeApi();
101+
window.addEventListener("message", receiveMessage, false);
102+
function receiveMessage(event) {
103+
vscode.postMessage(event.data);
104+
}
105+
</script>
106+
</body>
107+
</html>
108+
`;
109+
});
110+
});
89111
case 3: // Run an EXE on the client.
90112
throw new Error("Not suppoorted");
91113
case 4: // Insert the text in Target in the current document at the current selection point
92-
throw new Error("Not suppoorted");
114+
const editor = vscode.window.activeTextEditor;
115+
if(editor) {
116+
editor.edit(editBuilder => {
117+
editBuilder.replace(editor.selection, target);
118+
});
119+
}
120+
return;
93121
case 5: // Studio will open the documents listed in Target
94-
throw new Error("Not suppoorted");
122+
target.split(",").forEach(element => {
123+
let classname = element;
124+
let method: string;
125+
let offset = 0;
126+
if(element.includes(":")) {
127+
[classname, method] = element.split(":");
128+
if(method.includes("+")) {
129+
offset = +method.split("+")[1];
130+
method = method.split("+")[0];
131+
}
132+
}
133+
134+
const splitClassname = classname.split(".");
135+
const filetype = splitClassname[splitClassname.length - 1];
136+
const isCorrectMethod = (text: string) => (filetype === "cls")
137+
? text.match("Method " + method)
138+
: text.startsWith(method)
139+
140+
const uri = DocumentContentProvider.getUri(classname);
141+
vscode.window.showTextDocument(uri, {"preview": false}).then(newEditor => {
142+
if(method) {
143+
const document = newEditor.document;
144+
for(let i = 0; i < document.lineCount; i++) {
145+
const line = document.lineAt(i);
146+
if(isCorrectMethod(line.text)) {
147+
if(!line.text.endsWith("{")) offset++;
148+
const cursor = newEditor.selection.active;
149+
const newPosition = cursor.with(i + offset, 0);
150+
newEditor.selection = new vscode.Selection(newPosition, newPosition);
151+
break;
152+
}
153+
}
154+
}
155+
});
156+
});
157+
return;
95158
case 6: // Display an alert dialog in Studio with the text from the Target variable.
96159
return vscode.window.showWarningMessage(target, { modal: true });
97160
case 7: // Display a dialog with a textbox and Yes/No/Cancel buttons.
98161
return vscode.window.showInputBox({
99162
prompt: target,
163+
}).then(msg => {
164+
return {
165+
"msg": (msg ? msg : ""),
166+
"answer": (msg ? 1 : 2)
167+
}
100168
});
101169
default:
102170
throw new Error("Not suppoorted");
103171
}
104172
}
105173

106-
private userAction(action, afterUserAction = false, answer = "", msg = ""): Thenable<void> {
174+
private userAction(action, afterUserAction = false, answer = "", msg = "", type = 0): Thenable<void> {
107175
if (!action) {
108176
return;
109177
}
110-
const func = afterUserAction ? "AfterUserAction" : "UserAction";
111-
const query = `select * from %Atelier_v1_Utils.Extension_${func}(?, ?, ?, ?)`;
178+
const func = afterUserAction ? "AfterUserAction(?, ?, ?, ?, ?)" : "UserAction(?, ?, ?, ?)";
179+
const query = `select * from %Atelier_v1_Utils.Extension_${func}`;
112180
let selectedText = "";
113181
const editor = vscode.window.activeTextEditor;
114182
if (!editor) {
@@ -118,8 +186,9 @@ class StudioActions {
118186
selectedText = editor.document.getText(selection);
119187

120188
const parameters = afterUserAction
121-
? ["0", action.id, this.name, answer]
122-
: ["0", action.id, this.name, selectedText];
189+
? [type.toString(), action.id, this.name, answer, msg]
190+
: [type.toString(), action.id, this.name, selectedText];
191+
123192
return vscode.window.withProgress(
124193
{
125194
cancellable: false,
@@ -129,34 +198,44 @@ class StudioActions {
129198
() =>
130199
this.api
131200
.actionQuery(query, parameters)
132-
.then(data => data.result.content.pop())
201+
.then(data => {
202+
const actionInfo = data.result.content.pop();
203+
actionInfo.save = action.save;
204+
return actionInfo;
205+
})
206+
.then(this.processSaveFlag)
133207
.then(this.processUserAction)
134208
.then(answer => {
135209
if (answer) {
136-
return this.userAction(action, true, answer);
210+
return (answer.msg || answer.msg === "")
211+
? this.userAction(action, true, answer.answer, answer.msg, type)
212+
: this.userAction(action, true, answer, "", type);
137213
}
138214
})
139215
.catch(err => {
216+
console.log(err);
140217
outputChannel.appendLine(`Studio Action "${action.label}" not supported`);
141218
outputChannel.show();
142219
})
143220
);
144221
}
145222

146-
private constructMenu(menu): any[] {
223+
private constructMenu(menu, contextOnly = false): any[] {
147224
return menu
225+
.filter(menuGroup => !(contextOnly && menuGroup.type === "main"))
148226
.reduce(
149227
(list, sub) =>
150228
list.concat(
151229
sub.items
152230
.filter(el => el.id !== "" && el.separator == 0)
153-
// .filter(el => el.enabled == 1)
231+
.filter(el => el.enabled == 1)
154232
.map(el => ({
155233
...el,
156234
id: `${sub.id},${el.id}`,
157235
label: el.name.replace("&", ""),
158236
itemId: el.id,
159237
type: sub.type,
238+
description: sub.name.replace("&", ""),
160239
}))
161240
),
162241
[]
@@ -170,19 +249,57 @@ class StudioActions {
170249
});
171250
}
172251

173-
public getMenu(menuType: string): Thenable<any> {
252+
public getMenu(menuType: string, contextOnly = false): Thenable<any> {
253+
let selectedText = "";
254+
const editor = vscode.window.activeTextEditor;
255+
if(this.uri && editor) {
256+
const selection = editor.selection;
257+
selectedText = editor.document.getText(selection);
258+
}
259+
174260
const query = "select * from %Atelier_v1_Utils.Extension_GetMenus(?,?,?)";
175-
const parameters = [menuType, this.name, ""];
261+
const parameters = [menuType, this.name, selectedText];
176262

177263
return this.api
178264
.actionQuery(query, parameters)
179265
.then(data => data.result.content)
180-
.then(this.constructMenu)
266+
.then(menu => this.constructMenu(menu, contextOnly))
181267
.then(menuItems => {
182268
return vscode.window.showQuickPick<StudioAction>(menuItems, { canPickMany: false });
183269
})
184270
.then(action => this.userAction(action));
185271
}
272+
273+
public attemptedEdit() {
274+
const query = "select * from %Atelier_v1_Utils.Extension_GetStatus(?)";
275+
this.api.actionQuery(query, [this.name]).then(statusObj => {
276+
const docStatus = statusObj.result.content.pop();
277+
// if(!docStatus.editable && docStatus.inSourceControl && !docStatus.isCheckedOut) {
278+
if(!docStatus.editable) {
279+
const attemptedEditAction = {
280+
id: "0",
281+
label: "Attempted Edit"
282+
};
283+
vscode.commands.executeCommand('undo');
284+
this.userAction(attemptedEditAction, false, "", "", 1);
285+
} // else if(!docStatus.editable && docStatus.in)
286+
});
287+
}
288+
289+
private async processSaveFlag(userAction) {
290+
if(userAction.save) {
291+
const bitString = userAction.save.toString().padStart(3, "0");
292+
// Save the current document
293+
if(bitString.charAt(0) === "1") {
294+
await vscode.window.activeTextEditor.document.save();
295+
}
296+
// Save all documents
297+
if(bitString.charAt(2) === "1") {
298+
await vscode.workspace.saveAll();
299+
}
300+
}
301+
return userAction;
302+
}
186303
}
187304

188305
// export function contextMenu(uri: vscode.Uri): Promise<void> {
@@ -197,3 +314,20 @@ export async function mainMenu(uri: vscode.Uri) {
197314
const studioActions = new StudioActions(uri);
198315
return studioActions && studioActions.getMenu("");
199316
}
317+
318+
export async function fireAttemptedEdit(uri: vscode.Uri) {
319+
if(!uri || uri.scheme !== FILESYSTEM_SCHEMA) {
320+
return;
321+
}
322+
const studioActions = new StudioActions(uri);
323+
studioActions.attemptedEdit();
324+
}
325+
326+
export async function contextMenu(node: PackageNode | ClassNode | RoutineNode): Promise<any> {
327+
const nodeOrUri = node || vscode.window.activeTextEditor.document.uri;
328+
if(!nodeOrUri || (nodeOrUri instanceof vscode.Uri && nodeOrUri.scheme !== FILESYSTEM_SCHEMA)) {
329+
return;
330+
}
331+
const studioActions = new StudioActions(nodeOrUri);
332+
return studioActions && studioActions.getMenu("", true);
333+
}

0 commit comments

Comments
 (0)