/
docToPreviewGenerator.ts
172 lines (142 loc) · 4.66 KB
/
docToPreviewGenerator.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import { ExecException, spawnSync } from "child_process";
import { readFileSync, unlink, writeFileSync } from "fs";
import * as path from "path";
import * as temp from "temp";
import { TextDocument, window } from "vscode";
import { BrowserWindow } from "./browserWindow";
import { outputChannel, ws } from "./extension";
import { RefreshTimer } from "./refreshTimer";
import { NameToThemeNumber } from "./themePicker";
/**
* D2P - Document to Preview. This tracks the connection
* between the D2 document and to the preview window.
*
* Stores the temp file string.
**/
export class D2P {
inputDoc?: TextDocument;
outputDoc?: BrowserWindow;
timer?: RefreshTimer;
}
/**
* DocToPreviewGenerator - Keeper of the map of D2P objects
* that allow for associating a document to it's preview
* information.
**/
export class DocToPreviewGenerator {
mapOfConnection: Map<TextDocument, D2P> = new Map<TextDocument, D2P>();
createObjectToTrack(inDoc: TextDocument): D2P {
const trk = new D2P();
trk.inputDoc = inDoc;
this.mapOfConnection.set(inDoc, trk);
trk.timer = new RefreshTimer(() => {
// If there is a document to update, update it.
if (trk.outputDoc) {
this.generate(inDoc);
}
});
trk.timer?.start(false);
return trk;
}
deleteObjectToTrack(inDoc: TextDocument): void {
this.mapOfConnection.delete(inDoc);
}
getTrackObject(inDoc: TextDocument): D2P | undefined {
return this.mapOfConnection.get(inDoc);
}
generate(inDoc: TextDocument): void {
const trkObj = this.getTrackObject(inDoc);
// if we can't find our tracking info, no sense doing anything
if (!trkObj) {
return;
}
// No input document? How did we get here?
if (!trkObj.inputDoc) {
return;
}
const fileText = trkObj.inputDoc.getText();
if (!fileText) {
// Empty document, do nothing
return;
}
const data: string = this.generateFromText(fileText);
// If we don't have a preview window already, create one
if (!trkObj.outputDoc) {
trkObj.outputDoc = new BrowserWindow(trkObj);
}
if (data.length > 0) {
trkObj.outputDoc.setSvg(data);
const p = path.parse(trkObj.inputDoc.fileName);
outputChannel.appendInfo(`Preview for ${p.base} updated.`);
}
}
/**
* Take the d2 document text and pass it to the D2 executable
* and then retreive the output to render in our preveiw window.
*/
generateFromText(text: string): string {
const inFile = temp.path({ suffix: "in.d2.temp" });
const outFile = temp.path({ suffix: "out.d2.temp" });
// Write out our document so the D2 executable can read it.
writeFileSync(inFile, text);
try {
const layout: string = ws.get("previewLayout", "dagre");
const theme: string = ws.get("previewTheme", "default");
const sketch: boolean = ws.get("previewSketch", false);
const themeNumber: number = NameToThemeNumber(theme);
const d2Path: string = ws.get("execPath", "d2");
const proc = spawnSync(d2Path, [
`--layout=${layout}`,
`--theme=${themeNumber}`,
`--sketch=${sketch}`,
inFile,
outFile,
]);
if (proc.pid === 0) {
this.showErrorToolsNotFound(proc.error?.message ?? "");
return "";
} else {
outputChannel.appendError(proc.stderr?.toString() ?? "Unknown Error");
}
} catch (error) {
const ex: ExecException = error as ExecException;
outputChannel.appendError(ex.message);
}
// Get the the contents of the output file
const data: string = readFileSync(outFile, "utf-8");
// No longer need our temp files, get rid of them.
// The existence of these files should not escape this function.
unlink(inFile, (err) => {
if (err) {
outputChannel.appendWarning(
`Temp File ${err?.message} could not be deleted.`
);
}
});
unlink(outFile, (err) => {
if (err) {
outputChannel.appendWarning(
`Temp File ${err?.message} could not be deleted.`
);
}
});
return data;
}
private strBreak =
"************************************************************";
private showErrorToolsNotFound(msg: string): void {
const errorMsgs: string[] = [
"D2 executable not found.",
"Make sure the D2 executable is installed and on system PATH.",
"https://d2lang.com/tour/install",
`${msg}`,
];
outputChannel.appendError(this.strBreak);
for (const m of errorMsgs) {
outputChannel.appendError(m);
}
outputChannel.appendError(this.strBreak);
// Popup some toast to alert to the error
window.showErrorMessage(errorMsgs.join("\n"));
}
}