Skip to content

Commit 036f36c

Browse files
authored
Support Save As... for server-side documents (#1774)
1 parent eef4d57 commit 036f36c

2 files changed

Lines changed: 56 additions & 41 deletions

File tree

src/extension.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1833,14 +1833,24 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
18331833
sendCommandTelemetryEvent("showAllClassMembers");
18341834
if (uri instanceof vscode.Uri) showAllClassMembers(uri);
18351835
}),
1836-
vscode.workspace.onDidSaveTextDocument((d) => {
1836+
vscode.workspace.onDidSaveTextDocument(async (d) => {
18371837
// If the document just saved is a server-side document that needs to be updated in the UI,
18381838
// then force VS Code to update the document's contents. This is needed if the document has
18391839
// been changed during a save, for example by adding or changing the Storage definition.
18401840
if (notIsfs(d.uri)) return;
18411841
const uriString = d.uri.toString();
18421842
if (fileSystemProvider.needsUpdate(uriString)) {
1843-
const activeDoc = vscode.window.activeTextEditor?.document;
1843+
let activeDoc = vscode.window.activeTextEditor?.document;
1844+
if (activeDoc?.uri.toString() != uriString) {
1845+
// The active text editor (if any) does not contain this document. Wait a short time and
1846+
// check again in case this document was saved using the "Save As..." command. In that
1847+
// case VS Code saves the document once with no content and then saves it again with content
1848+
// from the source document. During that second save our FileSystemProvider will likely
1849+
// change the content of the document so the header matches the new URI. We need to force
1850+
// VS Code to reflect that change in the editor UI.
1851+
await new Promise((resolve) => setTimeout(resolve, 50));
1852+
activeDoc = vscode.window.activeTextEditor?.document;
1853+
}
18441854
if (activeDoc && !activeDoc.isDirty && !activeDoc.isClosed && activeDoc.uri.toString() == uriString) {
18451855
// Force VS Code to refresh the file's contents in the editor tab
18461856
vscode.commands.executeCommand("workbench.action.files.revert");

src/providers/FileSystemProvider/FileSystemProvider.ts

Lines changed: 44 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
openLowCodeEditors,
1919
compileErrorMsg,
2020
isCompilable,
21+
currentFileFromContent,
2122
} from "../../utils";
2223
import { FILESYSTEM_READONLY_SCHEMA, FILESYSTEM_SCHEMA, intLangId, macLangId } from "../../extension";
2324
import { addIsfsFileToProject, modifyProject } from "../../commands/project";
@@ -65,6 +66,11 @@ class Directory implements vscode.FileStat {
6566

6667
type Entry = File | Directory;
6768

69+
/**
70+
* Generates stub content for document `fileName` in file `uri`.
71+
* If `sourceContent` is supplied, the name of the document in
72+
* that content is modified to match `fileName`.
73+
*/
6874
export function generateFileContent(
6975
uri: vscode.Uri,
7076
fileName: string,
@@ -89,7 +95,6 @@ export function generateFileContent(
8995
const className = fileName.split(".").slice(0, -1).join(".");
9096
let content: string[] = [];
9197
const preamble: string[] = [];
92-
9398
if (sourceLines.length) {
9499
if (notIsfs(uri) && (fileName.includes(path.sep) || fileName.includes(" "))) {
95100
// We couldn't resolve a class name from the file path,
@@ -100,11 +105,9 @@ export function generateFileContent(
100105
// Replace that with one to match fileName.
101106
while (sourceLines.length > 0) {
102107
const nextLine = sourceLines.shift();
103-
if (nextLine.toLowerCase().startsWith("class ")) {
104-
const classLine = nextLine.split(" ");
105-
classLine[0] = "Class";
106-
classLine[1] = className;
107-
content.push(...preamble, classLine.join(" "), ...sourceLines);
108+
const classNameMatch = nextLine.match(classNameRegex);
109+
if (classNameMatch) {
110+
content.push(...preamble, nextLine.replace(classNameMatch[1], fileName.slice(0, -4)), ...sourceLines);
108111
break;
109112
}
110113
preamble.push(nextLine);
@@ -117,7 +120,6 @@ export function generateFileContent(
117120
} else {
118121
content = [`Class ${className} Extends %RegisteredObject`, "{", "}"];
119122
}
120-
121123
return {
122124
content,
123125
enc: false,
@@ -133,8 +135,8 @@ export function generateFileContent(
133135
eol,
134136
};
135137
} else {
136-
sourceLines.shift();
137-
const routineName = fileName.split(".").slice(0, -1).join(".");
138+
if (sourceLines[0]?.startsWith("ROUTINE ")) sourceLines.shift();
139+
const routineName = fileName.slice(0, -4);
138140
const routineType = fileExt != "mac" ? `[Type=${fileExt.toUpperCase()}]` : "";
139141
if (sourceLines.length === 0 && fileExt !== "inc") {
140142
const languageId = fileExt === "mac" ? macLangId : intLangId;
@@ -156,7 +158,7 @@ export function generateFileContent(
156158
};
157159
}
158160
} else if (csp && sourceContent.length == 0) {
159-
// Some IRIS versions do not allow empty content to be PUT for CSP files, so add a newline if the content is empty. See DP-442552.
161+
// Some IRIS versions do not allow empty content to be PUT for web app files, so add a newline if the content is empty
160162
return {
161163
content: [""],
162164
enc: false,
@@ -532,38 +534,12 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
532534
const api = new AtelierAPI(uri);
533535
let created = false;
534536
let update = false;
535-
const isCls = !csp && fileName.split(".").pop().toLowerCase() == "cls";
537+
const fileExt = fileName.split(".").pop().toLowerCase();
536538
// Use _lookup() instead of _lookupAsFile() so we send
537539
// our cached mtime with the GET /doc request if we have it
538540
return this._lookup(uri)
539541
.then(
540542
async (entry: File) => {
541-
// Check cases for which we should fail the write and leave the document dirty if changed
542-
if (isCls) {
543-
// Check if the class name and file name match
544-
let clsname = "";
545-
const match = new TextDecoder().decode(content).match(classNameRegex);
546-
if (match) {
547-
[, clsname] = match;
548-
}
549-
if (clsname == "") {
550-
throw new vscode.FileSystemError("Cannot save a malformed class");
551-
}
552-
if (fileName.slice(0, -4) != clsname) {
553-
throw new vscode.FileSystemError(
554-
"Cannot save an isfs class where the class name and file name do not match"
555-
);
556-
}
557-
if (openLowCodeEditors.has(uri.toString())) {
558-
// This class is open in a low-code editor, so any
559-
// updates to the class will be handled by that editor
560-
return;
561-
}
562-
// Check if the class is deployed
563-
if (await isClassDeployed(fileName, api)) {
564-
throw new vscode.FileSystemError("Cannot overwrite a deployed class");
565-
}
566-
}
567543
const contentBuffer = Buffer.from(content);
568544
const putContent = isText(uri.path.split("/").pop(), contentBuffer)
569545
? {
@@ -574,6 +550,35 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
574550
content: base64EncodeContent(contentBuffer),
575551
enc: true,
576552
};
553+
if (!csp && ["cls", "mac", "int", "inc"].includes(fileExt)) {
554+
const curFile = currentFileFromContent(uri, putContent.enc ? contentBuffer : putContent.content.join("\n"));
555+
if (!curFile) {
556+
throw new vscode.FileSystemError(
557+
`Cannot save a malformed ${fileExt == "cls" ? "class" : fileExt == "inc" ? "include file" : "routine"}`
558+
);
559+
}
560+
if (curFile.name != fileName) {
561+
// Update the content so the name in text matches the URI
562+
const newContent = generateFileContent(uri, fileName, content);
563+
putContent.content = newContent.content;
564+
putContent.enc = newContent.enc;
565+
// Make sure the editor tab is updated to show the new content
566+
update = true;
567+
}
568+
if (fileExt == "cls") {
569+
if (openLowCodeEditors.has(uri.toString())) {
570+
// This class is open in a low-code editor, so any
571+
// updates to the class will be handled by that editor
572+
return;
573+
}
574+
// Check if the class is deployed
575+
if (await isClassDeployed(fileName, api)) {
576+
throw new vscode.FileSystemError("Cannot overwrite a deployed class");
577+
}
578+
// Always update the editor tab for a class after saving
579+
update = true;
580+
}
581+
}
577582
if (
578583
csp &&
579584
!putContent.enc &&
@@ -595,7 +600,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
595600
true
596601
)
597602
.then((data) => {
598-
update = isCls || data.result.content.length > 0;
603+
update = update || data.result.content.length > 0;
599604
return entry;
600605
})
601606
.catch((error) => {
@@ -641,7 +646,7 @@ export class FileSystemProvider implements vscode.FileSystemProvider {
641646
// Create an entry in our cache for the document
642647
return this._lookupAsFile(uri).then((entry) => {
643648
created = true;
644-
update = isCls || data.result.content.length > 0;
649+
update = (!csp && fileExt == "cls") || data.result.content.length > 0;
645650
this._fireSoon({ type: vscode.FileChangeType.Created, uri });
646651
return entry;
647652
});

0 commit comments

Comments
 (0)