Skip to content

Commit afdb0e1

Browse files
authored
Add logging for file change events in client-side folders (#1643)
1 parent 25e884d commit afdb0e1

File tree

9 files changed

+71
-42
lines changed

9 files changed

+71
-42
lines changed

src/commands/compile.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,13 +181,9 @@ What do you want to do?`,
181181
// Overwrite
182182
return importFile(file, true, true);
183183
case "Pull Server Changes":
184-
outputChannel.appendLine(`${file.name}: Loading changes from server`);
185-
outputChannel.show(true);
186184
loadChanges([file]);
187185
return Promise.reject();
188186
case "Cancel":
189-
outputChannel.appendLine(`${file.name}: Import and Compile canceled by user`);
190-
outputChannel.show(true);
191187
return Promise.reject();
192188
}
193189
return Promise.reject();

src/commands/connectFolderToServerNamespace.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
serverManagerApi,
88
resolveUsernameAndPassword,
99
} from "../extension";
10-
import { handleError, isUnauthenticated, notIsfs } from "../utils";
10+
import { handleError, isUnauthenticated, notIsfs, displayableUri } from "../utils";
1111

1212
interface ConnSettings {
1313
server: string;
@@ -131,8 +131,8 @@ export async function connectFolderToServerNamespace(): Promise<void> {
131131
// the server may be configured at the workspace folder level.
132132
const answer = await vscode.window.showQuickPick(
133133
[
134-
{ label: `Workspace Folder ${folder.name}`, detail: folder.uri.toString(true) },
135-
{ label: "Workspace File", detail: vscode.workspace.workspaceFile.toString(true) },
134+
{ label: `Workspace Folder ${folder.name}`, detail: displayableUri(folder.uri) },
135+
{ label: "Workspace File", detail: displayableUri(vscode.workspace.workspaceFile) },
136136
],
137137
{ title: "Store the server connection at the workspace or folder level?" }
138138
);

src/commands/export.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
lastUsedLocalUri,
1212
notNull,
1313
outputChannel,
14+
displayableUri,
1415
RateLimiter,
1516
replaceFile,
1617
stringifyError,
@@ -96,7 +97,7 @@ async function exportFile(wsFolderUri: vscode.Uri, namespace: string, name: stri
9697
let fileUri = vscode.Uri.file(fileName);
9798
if (wsFolderUri.scheme != "file") fileUri = wsFolderUri.with({ path: fileUri.path });
9899
const log = (status: string) =>
99-
outputChannel.appendLine(`Export '${name}' to '${fileUri.toString(true)}' - ${status}`);
100+
outputChannel.appendLine(`Export '${name}' to '${displayableUri(fileUri)}' - ${status}`);
100101

101102
try {
102103
const data = await api.getDoc(name, wsFolderUri);
@@ -380,7 +381,7 @@ export async function exportDocumentsToXMLFile(): Promise<void> {
380381
const xmlContent = await api.actionXMLExport(documents).then((data) => data.result.content);
381382
// Save the file
382383
await replaceFile(uri, xmlContent);
383-
outputChannel.appendLine(`Exported to ${uri.scheme == "file" ? uri.fsPath : uri.toString(true)}`);
384+
outputChannel.appendLine(`Exported to ${displayableUri(uri)}`);
384385
}
385386
} catch (error) {
386387
handleError(error, "Error executing 'Export Documents to XML File...' command.");

src/commands/newFile.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import path = require("path");
33
import { AtelierAPI } from "../api";
44
import { FILESYSTEM_SCHEMA } from "../extension";
55
import { DocumentContentProvider } from "../providers/DocumentContentProvider";
6-
import { replaceFile, getWsFolder, handleError } from "../utils";
6+
import { replaceFile, getWsFolder, handleError, displayableUri } from "../utils";
77
import { getFileName } from "./export";
88
import { getUrisForDocument } from "../utils/documentIndex";
99

@@ -949,7 +949,7 @@ Parameter ENSPURGE As BOOLEAN = 1;
949949
const inputBox = vscode.window.createInputBox();
950950
inputBox.ignoreFocusOut = true;
951951
inputBox.buttons = [{ iconPath: new vscode.ThemeIcon("save-as"), tooltip: "Show 'Save As' dialog" }];
952-
inputBox.prompt = `The path is relative to the workspace folder root (${wsFolder.uri.toString(true)}). Intermediate folders that do not exist will be created. Click the 'Save As' icon to open the standard save dialog instead.`;
952+
inputBox.prompt = `The path is relative to the workspace folder root (${displayableUri(wsFolder.uri)}). Intermediate folders that do not exist will be created. Click the 'Save As' icon to open the standard save dialog instead.`;
953953
inputBox.title = "Enter a file path for the new class";
954954
inputBox.value = localUri.path.slice(wsFolder.uri.path.length);
955955
inputBox.valueSelection = [inputBox.value.length, inputBox.value.length];

src/commands/unitTest.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
handleError,
77
methodOffsetToLine,
88
notIsfs,
9+
displayableUri,
910
stripClassMemberNameQuotes,
1011
uriIsParentOf,
1112
} from "../utils";
@@ -396,7 +397,7 @@ async function runHandler(
396397
roots.map((i) => {
397398
return {
398399
label: i.label,
399-
detail: i.uri.toString(true),
400+
detail: displayableUri(i.uri),
400401
item: i,
401402
};
402403
}),

src/commands/xmlToUdl.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import * as vscode from "vscode";
22
import path = require("path");
33
import { config, OBJECTSCRIPTXML_FILE_SCHEMA, xmlContentProvider } from "../extension";
44
import { AtelierAPI } from "../api";
5-
import { replaceFile, fileExists, getWsFolder, handleError, notIsfs, outputChannel } from "../utils";
5+
import { replaceFile, fileExists, getWsFolder, handleError, notIsfs, outputChannel, displayableUri } from "../utils";
66
import { getFileName } from "./export";
77

88
const exportHeader = /^\s*<Export generator="(Cache|IRIS)" version="\d+"/;
99

1010
export async function previewXMLAsUDL(textEditor: vscode.TextEditor, auto = false): Promise<void> {
1111
const uri = textEditor.document.uri;
1212
const content = textEditor.document.getText();
13+
const uriString = displayableUri(uri);
1314
if (notIsfs(uri) && uri.path.toLowerCase().endsWith("xml") && textEditor.document.lineCount > 2) {
1415
if (exportHeader.test(textEditor.document.lineAt(1).text)) {
1516
const api = new AtelierAPI(uri);
@@ -23,10 +24,7 @@ export async function previewXMLAsUDL(textEditor: vscode.TextEditor, auto = fals
2324
.cvtXmlUdl(content)
2425
.then((data) => data.result.content);
2526
if (udlDocs.length == 0) {
26-
vscode.window.showErrorMessage(
27-
`File '${uri.toString(true)}' contains no documents that can be previewed.`,
28-
"Dismiss"
29-
);
27+
vscode.window.showErrorMessage(`File '${uriString}' contains no documents that can be previewed.`, "Dismiss");
3028
return;
3129
}
3230
// Prompt the user for documents to preview
@@ -77,7 +75,7 @@ export async function previewXMLAsUDL(textEditor: vscode.TextEditor, auto = fals
7775
handleError(error, "Error executing 'Preview XML as UDL' command.");
7876
}
7977
} else if (!auto) {
80-
vscode.window.showErrorMessage(`XML file '${uri.toString(true)}' is not an InterSystems export.`, "Dismiss");
78+
vscode.window.showErrorMessage(`XML file '${uriString}' is not an InterSystems export.`, "Dismiss");
8179
}
8280
}
8381
}
@@ -137,19 +135,17 @@ export async function extractXMLFileContents(xmlUri?: vscode.Uri): Promise<void>
137135
}
138136
// Read the XML file
139137
const xmlContent = new TextDecoder().decode(await vscode.workspace.fs.readFile(xmlUri)).split(/\r?\n/);
138+
const xmlUriString = displayableUri(xmlUri);
140139
if (xmlContent.length < 3 || !exportHeader.test(xmlContent[1])) {
141-
vscode.window.showErrorMessage(`XML file '${xmlUri.toString(true)}' is not an InterSystems export.`, "Dismiss");
140+
vscode.window.showErrorMessage(`XML file '${xmlUriString}' is not an InterSystems export.`, "Dismiss");
142141
return;
143142
}
144143
// Convert the file
145144
const udlDocs: { name: string; content: string[] }[] = await api
146145
.cvtXmlUdl(xmlContent.join("\n"))
147146
.then((data) => data.result.content);
148147
if (udlDocs.length == 0) {
149-
vscode.window.showErrorMessage(
150-
`File '${xmlUri.toString(true)}' contains no documents that can be extracted.`,
151-
"Dismiss"
152-
);
148+
vscode.window.showErrorMessage(`File '${xmlUriString}' contains no documents that can be extracted.`, "Dismiss");
153149
return;
154150
}
155151
// Prompt the user for documents to extract
@@ -177,7 +173,7 @@ export async function extractXMLFileContents(xmlUri?: vscode.Uri): Promise<void>
177173
if (!docWhitelist.includes(udlDoc.name)) continue; // This file wasn't selected
178174
const fileUri = wsFolder.uri.with({ path: getFileName(rootFolder, udlDoc.name, atelier, addCategory, map, "/") });
179175
if (await fileExists(fileUri)) {
180-
outputChannel.appendLine(`File '${fileUri.toString(true)}' already exists.`);
176+
outputChannel.appendLine(`File '${displayableUri(fileUri)}' already exists.`);
181177
errs++;
182178
continue;
183179
}

src/extension.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ import {
108108
addWsServerRootFolderData,
109109
getWsFolder,
110110
exportedUris,
111+
displayableUri,
111112
} from "./utils";
112113
import { ObjectScriptDiagnosticProvider } from "./providers/ObjectScriptDiagnosticProvider";
113114
import { DocumentLinkProvider } from "./providers/DocumentLinkProvider";
@@ -1163,7 +1164,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<any> {
11631164
} catch (error) {
11641165
handleError(
11651166
error,
1166-
`Failed to overwrite contents of file '${file.uri.toString(true)}' with server copy of '${file.fileName}'.`
1167+
`Failed to overwrite contents of file '${displayableUri(file.uri)}' with server copy of '${file.fileName}'.`
11671168
);
11681169
}
11691170
}),

src/utils/documentIndex.ts

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
notIsfs,
1212
openLowCodeEditors,
1313
outputChannel,
14+
displayableUri,
1415
} from ".";
1516
import { isText } from "istextorbinary";
1617
import { AtelierAPI } from "../api";
@@ -67,11 +68,11 @@ async function getCurrentFile(
6768
// or a TypeError from decode(). Don't log TypeError
6869
// since the file may be a non-text file that has
6970
// an extension that we interpret as text (like cls or mac).
70-
// Also don't log "FileNotFound" errors, which are probably
71-
// caused by concurrency issues. We should ignore such files
72-
// rather than alerting the user.
73-
if (error instanceof vscode.FileSystemError && error.code != "FileNotFound") {
74-
outputChannel.appendLine(`Failed to read contents of '${uri.toString(true)}': ${error.toString()}`);
71+
// Don't log "FileNotFound" errors, which are probably
72+
// caused by concurrency issues, or "FileIsADirectory"
73+
// issues, since we don't care about directories.
74+
if (error instanceof vscode.FileSystemError && !["FileNotFound", "FileIsADirectory"].includes(error.code)) {
75+
outputChannel.appendLine(`Failed to read contents of '${displayableUri(uri)}': ${error.toString()}`);
7576
}
7677
}
7778
}
@@ -114,17 +115,9 @@ function generateDeleteFn(wsFolderUri: vscode.Uri): (doc: string) => void {
114115
docs.length = 0;
115116
api.deleteDocs(docsCopy).then((data) => {
116117
let failed = 0;
118+
const ts = tsString();
117119
for (const doc of data.result) {
118-
if (doc.status != "" && !doc.status.includes("#16005:")) {
119-
// The document was not deleted, so log the error.
120-
// Error 16005 means we tried to delete a document
121-
// that didn't exist. Since the purpose of this
122-
// call was to delete the document, and at the
123-
// end the document isn't there, we should ignore
124-
// this error so the user doesn't get confused.
125-
failed++;
126-
outputChannel.appendLine(`${failed == 1 ? "\n" : ""}${doc.status}`);
127-
}
120+
failed += outputDelete(doc.name, doc.status, ts);
128121
}
129122
if (failed > 0) {
130123
outputChannel.show(true);
@@ -151,6 +144,37 @@ export function storeTouchedByVSCode(uri: vscode.Uri): void {
151144
}
152145
}
153146

147+
/** Create a timestamp string for use in a log entry */
148+
function tsString(): string {
149+
const date = new Date();
150+
return `${date.toISOString().split("T").shift()} ${date.toLocaleTimeString(undefined, { hour12: false })}`;
151+
}
152+
153+
/** Output a log entry */
154+
function output(docName: string, msg: string, ts?: string): void {
155+
outputChannel.appendLine(`${ts ?? tsString()} [${docName}] ${msg}`);
156+
}
157+
158+
/** Output a log entry for a successful import */
159+
function outputImport(docName: string, uri: vscode.Uri): void {
160+
output(docName, `Imported from '${displayableUri(uri)}'`);
161+
}
162+
163+
/**
164+
* Output a log entry for a successful or failed delete.
165+
* Does not output a log entry if the file did not exist on the server.
166+
* Returns `1` if the deleton failed, else `0`.
167+
*/
168+
function outputDelete(docName: string, status: string, ts: string): number {
169+
if (status == "") {
170+
output(docName, "Deleted", ts);
171+
} else if (!status.includes("#16005:")) {
172+
output(docName, `Deletion failed: ${status}`, ts);
173+
return 1;
174+
}
175+
return 0;
176+
}
177+
154178
/** Create index of `wsFolder` and set up a `FileSystemWatcher` to keep the index up to date */
155179
export async function indexWorkspaceFolder(wsFolder: vscode.WorkspaceFolder): Promise<void> {
156180
if (!notIsfs(wsFolder.uri)) return;
@@ -186,6 +210,10 @@ export async function indexWorkspaceFolder(wsFolder: vscode.WorkspaceFolder): Pr
186210
// safely ignore the event.
187211
return;
188212
}
213+
if (!uri.path.split("/").pop().includes(".")) {
214+
// Ignore creation and change events for folders
215+
return;
216+
}
189217
const uriString = uri.toString();
190218
if (!created) {
191219
const stat = await vscode.workspace.fs.stat(uri).then(undefined, () => {});
@@ -237,6 +265,7 @@ export async function indexWorkspaceFolder(wsFolder: vscode.WorkspaceFolder): Pr
237265
// Create or update the document on the server
238266
importFile(change.addedOrChanged)
239267
.then(() => {
268+
outputImport(change.addedOrChanged.name, uri);
240269
if (conf.get("compileOnSave")) {
241270
// Compile right away if this document is in the active text editor.
242271
// This is needed to avoid noticeable latency when a user is editing

src/utils/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -966,7 +966,7 @@ export async function getWsFolder(
966966
return vscode.window
967967
.showQuickPick(
968968
folders.map((f) => {
969-
return { label: f.name, detail: f.uri.toString(true), f };
969+
return { label: f.name, detail: displayableUri(f.uri), f };
970970
}),
971971
{
972972
canPickMany: false,
@@ -1006,7 +1006,7 @@ export async function replaceFile(uri: vscode.Uri, content: string | string[] |
10061006
: new TextEncoder().encode(Array.isArray(content) ? content.join("\n") : content),
10071007
});
10081008
const success = await vscode.workspace.applyEdit(wsEdit);
1009-
if (!success) throw `Failed to create or replace contents of file '${uri.toString(true)}'`;
1009+
if (!success) throw `Failed to create or replace contents of file '${displayableUri(uri)}'`;
10101010
}
10111011

10121012
/** Show the compilation failure error message if required. */
@@ -1025,6 +1025,11 @@ export function compileErrorMsg(conf: vscode.WorkspaceConfiguration): void {
10251025
});
10261026
}
10271027

1028+
/** Return a string containing the displayable form of `uri` */
1029+
export function displayableUri(uri: vscode.Uri): string {
1030+
return uri.scheme == "file" ? uri.fsPath : uri.toString(true);
1031+
}
1032+
10281033
class Semaphore {
10291034
/** Queue of tasks waiting to acquire the semaphore */
10301035
private _tasks: (() => void)[] = [];

0 commit comments

Comments
 (0)