Skip to content

Commit e4b6621

Browse files
committed
Improve client-side CSP workflow
- Enable exporting of CSP application files from ObjectScript Explorer - Import non-InterSystems CSP application files upon save. - Import individual web application text files from the file explorer using "Import ..." commands. - Import all text files in exported web application folder using "Import ..." commands. - Color .csr files
1 parent 8729450 commit e4b6621

File tree

6 files changed

+142
-44
lines changed

6 files changed

+142
-44
lines changed

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@
218218
"view/item/context": [
219219
{
220220
"command": "vscode-objectscript.explorer.export",
221-
"when": "view == ObjectScriptExplorer && viewItem =~ /^dataNode:(?!(cspApplication|cspFileNode))/"
221+
"when": "view == ObjectScriptExplorer && viewItem =~ /^dataNode:/"
222222
},
223223
{
224224
"command": "vscode-objectscript.explorer.export",
@@ -385,7 +385,8 @@
385385
"ObjectScript CSP"
386386
],
387387
"extensions": [
388-
"csp"
388+
".csp",
389+
".csr"
389390
]
390391
},
391392
{

src/commands/compile.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ import {
1111
OBJECTSCRIPT_FILE_SCHEMA,
1212
fileSystemProvider,
1313
workspaceState,
14-
schemas,
1514
} from "../extension";
1615
import { DocumentContentProvider } from "../providers/DocumentContentProvider";
17-
import { currentFile, CurrentFile, outputChannel } from "../utils";
16+
import { currentFile, CurrentFile, currentWorkspaceFolder, outputChannel, workspaceFolderUri } from "../utils";
1817
import { RootNode } from "../explorer/models/rootNode";
1918
import { PackageNode } from "../explorer/models/packageNode";
2019
import { ClassNode } from "../explorer/models/classNode";
@@ -41,7 +40,7 @@ async function compileFlags(): Promise<string> {
4140
* @return mtime timestamp or -1.
4241
*/
4342
export async function checkChangedOnServer(file: CurrentFile, force = false): Promise<number> {
44-
if (!file || !file.uri || schemas.includes(file.uri.scheme)) {
43+
if (!file || !file.uri) {
4544
return -1;
4645
}
4746
const api = new AtelierAPI(file.uri);
@@ -315,20 +314,31 @@ function importFiles(files, noCompile = false) {
315314
}
316315

317316
export async function importFolder(uri: vscode.Uri, noCompile = false): Promise<any> {
318-
const folder = uri.fsPath;
319-
if (fs.lstatSync(folder).isFile()) {
320-
return importFiles([folder], noCompile);
317+
const uripath = uri.fsPath;
318+
if (fs.lstatSync(uripath).isFile()) {
319+
return importFiles([uripath], noCompile);
320+
}
321+
var globpattern = "*.{cls,inc,int,mac}";
322+
const workspace = currentWorkspaceFolder();
323+
const workspacePath = workspaceFolderUri(workspace).fsPath;
324+
const { folder } = config("export", workspace);
325+
const folderPathNoWorkspaceArr = uripath.replace(workspacePath+path.sep,'').split(path.sep);
326+
if (folderPathNoWorkspaceArr[0] === folder) {
327+
// This folder is in the export tree, so import all files
328+
// We need to include eveything becuase CSP applications can
329+
// include non-InterSystems files
330+
globpattern = "*";
321331
}
322332
glob(
323-
"*.{cls,inc,mac,int}",
333+
globpattern,
324334
{
325-
cwd: folder,
335+
cwd: uripath,
326336
matchBase: true,
327337
nocase: true,
328338
},
329339
(_error, files) =>
330340
importFiles(
331-
files.map((name) => path.join(folder, name)),
341+
files.map((name) => path.join(uripath, name)),
332342
noCompile
333343
)
334344
);

src/commands/export.ts

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const filesFilter = (file: any) => {
1313
return true;
1414
};
1515

16-
const getCategory = (fileName: string, addCategory: any | boolean): string => {
16+
export const getCategory = (fileName: string, addCategory: any | boolean): string => {
1717
const fileExt = fileName.split(".").pop().toLowerCase();
1818
if (typeof addCategory === "object") {
1919
for (const pattern of Object.keys(addCategory)) {
@@ -45,22 +45,31 @@ export const getFileName = (
4545
[key: string]: string;
4646
}
4747
): string => {
48-
if (map) {
49-
for (const pattern of Object.keys(map)) {
50-
if (new RegExp(`^${pattern}$`).test(name)) {
51-
name = name.replace(new RegExp(`^${pattern}$`), map[pattern]);
52-
break;
48+
if (name.includes("/")) {
49+
// This is a file from a web application
50+
const nameArr: string[] = name.split("/");
51+
const cat = addCategory ? getCategory(name, addCategory) : null;
52+
return [folder, cat, ...nameArr].filter(notNull).join(path.sep);
53+
}
54+
else {
55+
// This is a class, routine or include file
56+
if (map) {
57+
for (const pattern of Object.keys(map)) {
58+
if (new RegExp(`^${pattern}$`).test(name)) {
59+
name = name.replace(new RegExp(`^${pattern}$`), map[pattern]);
60+
break;
61+
}
5362
}
5463
}
64+
const fileNameArray: string[] = name.split(".");
65+
const fileExt = fileNameArray.pop().toLowerCase();
66+
const cat = addCategory ? getCategory(name, addCategory) : null;
67+
if (split) {
68+
const fileName = [folder, cat, ...fileNameArray].filter(notNull).join(path.sep);
69+
return [fileName, fileExt].join(".");
70+
}
71+
return [folder, cat, name].filter(notNull).join(path.sep);
5572
}
56-
const fileNameArray: string[] = name.split(".");
57-
const fileExt = fileNameArray.pop().toLowerCase();
58-
const cat = addCategory ? getCategory(name, addCategory) : null;
59-
if (split) {
60-
const fileName = [folder, cat, ...fileNameArray].filter(notNull).join(path.sep);
61-
return [fileName, fileExt].join(".");
62-
}
63-
return [folder, cat, name].filter(notNull).join(path.sep);
6473
};
6574

6675
export const getFolderName = (folder: string, name: string, split: boolean, cat: string = null): string => {
@@ -122,26 +131,48 @@ export async function exportFile(
122131

123132
return promise
124133
.then((res: any) => {
125-
let joinedContent = content instanceof Buffer ? content.toString("utf-8") : (content || []).join("\n");
126-
let isSkipped = "";
134+
if (Buffer.isBuffer(content)) {
135+
// This is a binary file
136+
let isSkipped = "";
137+
138+
if (dontExportIfNoChanges && fs.existsSync(fileName)) {
139+
const existingContent = fs.readFileSync(fileName);
140+
if (content.equals(existingContent)) {
141+
fs.writeFileSync(fileName, content);
142+
}
143+
else {
144+
isSkipped = " => skipped - no changes.";
145+
}
146+
}
147+
else {
148+
fs.writeFileSync(fileName, content);
149+
}
127150

128-
if (res.found) {
129-
joinedContent = res.content.toString("utf8");
151+
log(`Success ${isSkipped}`);
130152
}
153+
else {
154+
// This is a text file
155+
let joinedContent = content.join("\n");
156+
let isSkipped = "";
131157

132-
if (dontExportIfNoChanges && fs.existsSync(fileName)) {
133-
const existingContent = fs.readFileSync(fileName, "utf8");
134-
// stringify to harmonise the text encoding.
135-
if (JSON.stringify(joinedContent) !== JSON.stringify(existingContent)) {
136-
fs.writeFileSync(fileName, joinedContent);
158+
if (res.found) {
159+
joinedContent = res.content.toString("utf8");
160+
}
161+
162+
if (dontExportIfNoChanges && fs.existsSync(fileName)) {
163+
const existingContent = fs.readFileSync(fileName, "utf8");
164+
// stringify to harmonise the text encoding.
165+
if (JSON.stringify(joinedContent) !== JSON.stringify(existingContent)) {
166+
fs.writeFileSync(fileName, joinedContent);
167+
} else {
168+
isSkipped = " => skipped - no changes.";
169+
}
137170
} else {
138-
isSkipped = " => skipped - no changes.";
171+
fs.writeFileSync(fileName, joinedContent);
139172
}
140-
} else {
141-
fs.writeFileSync(fileName, joinedContent);
142-
}
143173

144-
log(`Success ${isSkipped}`);
174+
log(`Success ${isSkipped}`);
175+
}
145176
})
146177
.catch((error) => {
147178
throw error;

src/explorer/models/rootNode.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ export class RootNode extends NodeBase {
155155
}
156156

157157
public getItems4Export(): Promise<string[]> {
158-
const path = this instanceof PackageNode ? this.fullName + "/" : "";
159-
return this.getList(path, "ALL", true).then((data) => data.map((el) => el.Name));
158+
const path = this instanceof PackageNode || this.isCsp ? this.fullName + "/" : "";
159+
const cat = this.isCsp ? "CSP" : "ALL";
160+
return this.getList(path, cat, true).then((data) => data.map((el) => el.Name));
160161
}
161162
}

src/extension.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ import {
7575
notNull,
7676
currentFile,
7777
InputBoxManager,
78+
isImportableLocalFile
7879
} from "./utils";
7980
import { ObjectScriptDiagnosticProvider } from "./providers/ObjectScriptDiagnosticProvider";
8081
import { DocumentRangeFormattingEditProvider } from "./providers/DocumentRangeFormattingEditProvider";
@@ -522,6 +523,13 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
522523
return importAndCompile(false, file);
523524
}
524525
}
526+
else if (file.uri.scheme === "file") {
527+
if (isImportableLocalFile(file)) {
528+
// This local file is in the exported file tree, so it's a non-InterSystems file that's
529+
// part of a CSP application, so import it on save
530+
return importFileOrFolder(file.uri, true);
531+
}
532+
}
525533
});
526534

527535
vscode.window.onDidChangeActiveTextEditor(async (textEditor: vscode.TextEditor) => {

src/utils/index.ts

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export function outputConsole(data: string[]): void {
3232
}
3333

3434
import { InputBoxManager } from "./inputBoxManager";
35+
import { getCategory } from "../commands/export";
3536
export { InputBoxManager };
3637

3738
// tslint:disable-next-line: interface-name
@@ -55,6 +56,44 @@ export interface ConnectionTarget {
5556
configName: string;
5657
}
5758

59+
/**
60+
* Determine the server name of a local non-ObjectScript file (any file that's not CLS,MAC,INT,INC).
61+
* @param localPath The full path to the file on disk.
62+
* @param workspace The workspace the file is in.
63+
*/
64+
function getServerDocName(localPath: string, workspace: string): string {
65+
var result = localPath;
66+
const workspacePath = workspaceFolderUri(workspace).fsPath;
67+
result = result.replace(workspacePath+path.sep,'');
68+
const { folder, addCategory } = config("export", workspace);
69+
result = result.replace(folder+path.sep,'');
70+
const cat = addCategory ? getCategory(localPath, addCategory) : null;
71+
if (cat !== null) {
72+
result = result.replace(cat+path.sep,'');
73+
}
74+
return result.replace(path.sep,"/");
75+
}
76+
77+
/**
78+
* Determine if this non-InterSystems local file is importable
79+
* (i.e. is part of a CSP application).
80+
* @param file The file to check.
81+
*/
82+
export function isImportableLocalFile(file: vscode.TextDocument): boolean {
83+
var result = false;
84+
const workspace = currentWorkspaceFolder(file);
85+
const workspacePath = workspaceFolderUri(workspace).fsPath;
86+
const { folder, addCategory } = config("export", workspace);
87+
const filePathNoWorkspaceArr = file.fileName.replace(workspacePath+path.sep,'').split(path.sep);
88+
const cat = addCategory ? getCategory(file.fileName, addCategory) : null;
89+
if (filePathNoWorkspaceArr[0] === folder) {
90+
if (cat === null || (cat !== null && filePathNoWorkspaceArr[1] === cat)) {
91+
result = true;
92+
}
93+
}
94+
return result;
95+
}
96+
5897
export function currentFile(document?: vscode.TextDocument): CurrentFile {
5998
document =
6099
document ||
@@ -72,9 +111,12 @@ export function currentFile(document?: vscode.TextDocument): CurrentFile {
72111
!document.fileName ||
73112
!document.languageId ||
74113
!document.languageId.startsWith("objectscript") ||
75-
fileExt.match(/(csp)/i)) // Skip local CSPs for now
114+
document.languageId === "objectscript-output")
76115
) {
77-
return null;
116+
// This is a non-InterSystems local file, so check if we can import it
117+
if (!isImportableLocalFile(document)) {
118+
return null;
119+
}
78120
}
79121
const eol = document.eol || vscode.EndOfLine.LF;
80122
const uri = redirectDotvscodeRoot(document.uri);
@@ -99,7 +141,12 @@ export function currentFile(document?: vscode.TextDocument): CurrentFile {
99141
[name, ext = "mac"] = path.basename(document.fileName).split(".");
100142
}
101143
} else {
102-
name = fileName;
144+
if (document.uri.scheme === "file") {
145+
name = getServerDocName(fileName,currentWorkspaceFolder(document));
146+
}
147+
else {
148+
name = fileName;
149+
}
103150
// Need to strip leading / for custom Studio documents which should not be treated as files.
104151
// e.g. For a custom Studio document Test.ZPM, the variable name would be /Test.ZPM which is
105152
// not the document name. The document name is Test.ZPM so requests made to the Atelier APIs

0 commit comments

Comments
 (0)