@abreen/tada 1.4.1 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/bin/tada.ts +81 -57
- package/build/apply-base-path-plugin.ts +22 -6
- package/build/code.test.ts +108 -1
- package/build/colors.test.ts +66 -0
- package/build/columns-plugin.ts +116 -0
- package/build/content-watch.ts +30 -21
- package/build/copy.ts +1 -1
- package/build/external-links-plugin.ts +1 -1
- package/build/generate-content-assets.ts +48 -6
- package/build/generate-favicon.ts +1 -1
- package/build/generate-fonts.ts +1 -1
- package/build/generate-katex-assets.ts +1 -1
- package/build/log.test.ts +7 -1
- package/build/log.ts +4 -1
- package/build/markdown-plugins.test.ts +65 -3
- package/build/pagefind.ts +1 -1
- package/build/pipeline.ts +4 -20
- package/build/serve.test.ts +59 -0
- package/build/serve.ts +29 -63
- package/build/templates.ts +2 -2
- package/build/toc-plugin.test.ts +40 -25
- package/build/toc-plugin.ts +9 -2
- package/build/{types.ts → types.d.ts} +88 -1
- package/build/util.ts +1 -1
- package/build/utils/code.ts +76 -3
- package/build/utils/content-files.test.ts +56 -0
- package/build/utils/content-files.ts +15 -11
- package/build/utils/file-types.test.ts +18 -0
- package/build/utils/file-types.ts +4 -0
- package/build/utils/include.test.ts +117 -0
- package/build/utils/include.ts +53 -0
- package/build/utils/jdi-runner/TraceRunner.java +416 -0
- package/build/utils/literate-java.ts +2 -2
- package/build/utils/markdown.ts +32 -14
- package/build/utils/paths.ts +1 -1
- package/build/utils/render.ts +102 -11
- package/build/utils/shiki-highlighter.ts +1 -1
- package/build/utils/trace-layout.test.ts +570 -0
- package/build/utils/trace-layout.ts +848 -0
- package/build/utils/trace-svg.test.ts +425 -0
- package/build/utils/trace-svg.ts +905 -0
- package/build/utils/trace.test.ts +169 -0
- package/build/utils/trace.ts +382 -0
- package/build/validate-internal-links-plugin.test.ts +30 -2
- package/build/validate-internal-links-plugin.ts +3 -3
- package/build/watch-reload-client.ts +4 -20
- package/build/watch.ts +436 -448
- package/init/content/index.md +10 -5
- package/init/content/labs/00/index.md +1 -1
- package/init/content/labs/01/SearchTreeDemo.java +31 -0
- package/init/content/labs/01/TraceDemo.java +22 -0
- package/init/content/labs/01/index.md +27 -0
- package/init/content/labs/index.md +2 -0
- package/init/content/lectures/01/Rectangle.java +2 -0
- package/init/content/lectures/01/index.md +2 -1
- package/init/content/lectures/01/rectangle.py +45 -0
- package/init/content/lectures/02/_pr1.md +5 -0
- package/init/content/lectures/02/index.md +11 -0
- package/init/content/lectures/02/subdir/_foobar.html +3 -0
- package/init/content/lectures/02/subdir/_pr2.md +6 -0
- package/init/content/lectures/index.md +3 -0
- package/init/content/markdown.md +30 -0
- package/package.json +21 -4
- package/src/_base.scss +13 -2
- package/src/_layout.scss +6 -5
- package/src/anchor/index.test.ts +64 -0
- package/src/code/index.test.ts +71 -0
- package/src/code/style.scss +23 -0
- package/src/code.scss +5 -3
- package/src/header/_base.scss +48 -9
- package/src/header/index.test.ts +89 -0
- package/src/header/index.ts +1 -1
- package/src/header/style.scss +1 -5
- package/src/index.ts +18 -20
- package/src/literate/style.scss +15 -7
- package/src/navigate/eligible.test.ts +101 -0
- package/src/navigate/eligible.ts +62 -0
- package/src/navigate/index.test.ts +82 -0
- package/src/navigate/index.ts +365 -0
- package/src/navigate/lifecycle.test.ts +26 -0
- package/src/navigate/lifecycle.ts +65 -0
- package/src/navigate/style.scss +61 -0
- package/src/print/index.test.ts +71 -0
- package/src/question/index.test.ts +82 -0
- package/src/question/style.scss +12 -0
- package/src/search/index.ts +38 -14
- package/src/search/style.scss +23 -14
- package/src/style.scss +25 -12
- package/src/timezone/index.test.ts +301 -1
- package/src/timezone/index.ts +68 -56
- package/src/toc/index.test.ts +237 -0
- package/src/toc/index.ts +29 -69
- package/src/toc/style.scss +17 -0
- package/src/top/index.test.ts +46 -0
- package/src/top/index.ts +2 -2
- package/src/trace/index.test.ts +554 -0
- package/src/trace/index.ts +258 -0
- package/src/trace/style.scss +210 -0
- package/src/trace/types.d.ts +47 -0
- package/src/util.test.ts +135 -0
- package/templates/_top.html +23 -22
- package/types/jsdom.d.ts +9 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import com.sun.jdi.*;
|
|
2
|
+
import com.sun.jdi.connect.*;
|
|
3
|
+
import com.sun.jdi.event.*;
|
|
4
|
+
import com.sun.jdi.request.*;
|
|
5
|
+
import java.io.*;
|
|
6
|
+
import java.util.*;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Runs a Java class under JDI, stepping through execution line by line
|
|
10
|
+
* and emitting a JSONL trace to stdout. Each line is a self-contained
|
|
11
|
+
* JSON object representing one execution step with stack frames,
|
|
12
|
+
* local variables, and reachable heap objects.
|
|
13
|
+
*
|
|
14
|
+
* Usage: java TraceRunner <className> <classPath>
|
|
15
|
+
*/
|
|
16
|
+
public class TraceRunner {
|
|
17
|
+
|
|
18
|
+
private static final Map<Long, String> objectIdMap = new HashMap<>();
|
|
19
|
+
private static long nextObjId = 1;
|
|
20
|
+
private static Map<String, ObjectReference> previousReachable = new LinkedHashMap<>();
|
|
21
|
+
|
|
22
|
+
public static void main(String[] args) throws Exception {
|
|
23
|
+
if (args.length < 2) {
|
|
24
|
+
System.err.println("Usage: java TraceRunner <className> <classPath>");
|
|
25
|
+
System.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
String className = args[0];
|
|
29
|
+
String classPath = args[1];
|
|
30
|
+
|
|
31
|
+
LaunchingConnector connector =
|
|
32
|
+
Bootstrap.virtualMachineManager().defaultConnector();
|
|
33
|
+
Map<String, Connector.Argument> connArgs = connector.defaultArguments();
|
|
34
|
+
connArgs.get("main").setValue(className);
|
|
35
|
+
connArgs.get("options").setValue("-cp " + classPath);
|
|
36
|
+
|
|
37
|
+
VirtualMachine vm = connector.launch(connArgs);
|
|
38
|
+
Process process = vm.process();
|
|
39
|
+
InputStream targetStdout = process.getInputStream();
|
|
40
|
+
InputStream targetStderr = process.getErrorStream();
|
|
41
|
+
|
|
42
|
+
Thread stderrThread = new Thread(() -> {
|
|
43
|
+
try {
|
|
44
|
+
targetStderr.transferTo(System.err);
|
|
45
|
+
} catch (IOException ignored) {
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
stderrThread.setDaemon(true);
|
|
49
|
+
stderrThread.start();
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
ClassPrepareRequest cpr =
|
|
53
|
+
vm.eventRequestManager().createClassPrepareRequest();
|
|
54
|
+
cpr.addClassFilter(className);
|
|
55
|
+
cpr.setSuspendPolicy(EventRequest.SUSPEND_ALL);
|
|
56
|
+
cpr.enable();
|
|
57
|
+
|
|
58
|
+
vm.eventQueue().remove().resume(); // consume VMStartEvent
|
|
59
|
+
|
|
60
|
+
boolean running = true;
|
|
61
|
+
while (running) {
|
|
62
|
+
EventSet eventSet = vm.eventQueue().remove();
|
|
63
|
+
for (Event event : eventSet) {
|
|
64
|
+
if (event instanceof ClassPrepareEvent cpe) {
|
|
65
|
+
Method mainMethod = findMainMethod(cpe.referenceType());
|
|
66
|
+
if (mainMethod != null) {
|
|
67
|
+
var locs = mainMethod.allLineLocations();
|
|
68
|
+
if (!locs.isEmpty()) {
|
|
69
|
+
var bp = vm.eventRequestManager()
|
|
70
|
+
.createBreakpointRequest(locs.get(0));
|
|
71
|
+
bp.setSuspendPolicy(EventRequest.SUSPEND_ALL);
|
|
72
|
+
bp.enable();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
cpr.disable();
|
|
76
|
+
|
|
77
|
+
} else if (event instanceof BreakpointEvent bpe) {
|
|
78
|
+
emitState(bpe.thread(), targetStdout);
|
|
79
|
+
|
|
80
|
+
var step = vm.eventRequestManager().createStepRequest(
|
|
81
|
+
bpe.thread(),
|
|
82
|
+
StepRequest.STEP_LINE,
|
|
83
|
+
StepRequest.STEP_INTO);
|
|
84
|
+
step.addClassExclusionFilter("java.*");
|
|
85
|
+
step.addClassExclusionFilter("javax.*");
|
|
86
|
+
step.addClassExclusionFilter("jdk.*");
|
|
87
|
+
step.addClassExclusionFilter("sun.*");
|
|
88
|
+
step.addClassExclusionFilter("com.sun.*");
|
|
89
|
+
step.setSuspendPolicy(EventRequest.SUSPEND_ALL);
|
|
90
|
+
step.enable();
|
|
91
|
+
bpe.request().disable();
|
|
92
|
+
|
|
93
|
+
} else if (event instanceof StepEvent se) {
|
|
94
|
+
emitState(se.thread(), targetStdout);
|
|
95
|
+
|
|
96
|
+
} else if (event instanceof VMDeathEvent
|
|
97
|
+
|| event instanceof VMDisconnectEvent) {
|
|
98
|
+
running = false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (running) {
|
|
102
|
+
eventSet.resume();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (VMDisconnectedException ignored) {
|
|
106
|
+
} finally {
|
|
107
|
+
try {
|
|
108
|
+
vm.dispose();
|
|
109
|
+
} catch (VMDisconnectedException ignored) {
|
|
110
|
+
}
|
|
111
|
+
stderrThread.join(1000);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private static Method findMainMethod(ReferenceType type) {
|
|
116
|
+
for (Method m : type.methods()) {
|
|
117
|
+
if (m.name().equals("main") && m.returnTypeName().equals("void")) {
|
|
118
|
+
var argTypes = m.argumentTypeNames();
|
|
119
|
+
if (argTypes.isEmpty()
|
|
120
|
+
|| (argTypes.size() == 1
|
|
121
|
+
&& argTypes.get(0).equals("java.lang.String[]"))) {
|
|
122
|
+
return m;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private static void emitState(ThreadReference thread,
|
|
130
|
+
InputStream targetStdout) throws Exception {
|
|
131
|
+
Thread.sleep(1);
|
|
132
|
+
String newStdout = drainStream(targetStdout);
|
|
133
|
+
|
|
134
|
+
Location loc = thread.frame(0).location();
|
|
135
|
+
int line = loc.lineNumber();
|
|
136
|
+
String file = loc.sourceName();
|
|
137
|
+
|
|
138
|
+
var reachable = new LinkedHashMap<String, ObjectReference>();
|
|
139
|
+
String stackJson = serializeStack(thread, reachable);
|
|
140
|
+
|
|
141
|
+
// Carry forward objects from the previous step that are still alive.
|
|
142
|
+
// This keeps in-flight objects (e.g., on the operand stack during
|
|
143
|
+
// chained constructor calls) visible in the diagram.
|
|
144
|
+
for (var entry : previousReachable.entrySet()) {
|
|
145
|
+
if (!reachable.containsKey(entry.getKey())) {
|
|
146
|
+
try {
|
|
147
|
+
if (!entry.getValue().isCollected()) {
|
|
148
|
+
reachable.put(entry.getKey(), entry.getValue());
|
|
149
|
+
}
|
|
150
|
+
} catch (Exception ignored) {
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
String heapJson = serializeHeap(reachable);
|
|
156
|
+
previousReachable = new LinkedHashMap<>(reachable);
|
|
157
|
+
|
|
158
|
+
System.out.println(
|
|
159
|
+
"{\"line\":" + line
|
|
160
|
+
+ ",\"file\":\"" + jsonEscape(file)
|
|
161
|
+
+ "\",\"stack\":" + stackJson
|
|
162
|
+
+ ",\"heap\":" + heapJson
|
|
163
|
+
+ ",\"stdout\":\"" + jsonEscape(newStdout) + "\"}");
|
|
164
|
+
System.out.flush();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private static String serializeStack(ThreadReference thread,
|
|
168
|
+
Map<String, ObjectReference> reachable) throws Exception {
|
|
169
|
+
var sb = new StringBuilder("[");
|
|
170
|
+
boolean first = true;
|
|
171
|
+
for (StackFrame frame : thread.frames()) {
|
|
172
|
+
ReferenceType declType = frame.location().declaringType();
|
|
173
|
+
if (!isUserClass(declType)) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (!first) {
|
|
177
|
+
sb.append(',');
|
|
178
|
+
}
|
|
179
|
+
first = false;
|
|
180
|
+
sb.append("{\"method\":\"")
|
|
181
|
+
.append(jsonEscape(frame.location().method().name()))
|
|
182
|
+
.append("\",\"class\":\"")
|
|
183
|
+
.append(jsonEscape(declType.name()))
|
|
184
|
+
.append("\",\"line\":")
|
|
185
|
+
.append(frame.location().lineNumber())
|
|
186
|
+
.append(",\"locals\":{");
|
|
187
|
+
|
|
188
|
+
boolean firstVar = true;
|
|
189
|
+
|
|
190
|
+
// Emit 'this' for instance methods
|
|
191
|
+
if (!frame.location().method().isStatic()) {
|
|
192
|
+
try {
|
|
193
|
+
ObjectReference thisObj = frame.thisObject();
|
|
194
|
+
if (thisObj != null) {
|
|
195
|
+
sb.append("\"this\":")
|
|
196
|
+
.append(serializeValue(thisObj, reachable));
|
|
197
|
+
firstVar = false;
|
|
198
|
+
}
|
|
199
|
+
} catch (Exception ignored) {
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
// Get ALL locals declared in the method
|
|
205
|
+
var allVars = frame.location().method().variables();
|
|
206
|
+
// Get the set of currently visible (initialized) variables
|
|
207
|
+
var visibleVars = new HashSet<LocalVariable>();
|
|
208
|
+
try {
|
|
209
|
+
visibleVars.addAll(frame.visibleVariables());
|
|
210
|
+
} catch (AbsentInformationException ignored) {
|
|
211
|
+
}
|
|
212
|
+
// Track names we've already emitted (avoid duplicates from scopes)
|
|
213
|
+
var emittedNames = new HashSet<String>();
|
|
214
|
+
for (LocalVariable lv : allVars) {
|
|
215
|
+
if (emittedNames.contains(lv.name())) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
emittedNames.add(lv.name());
|
|
219
|
+
if (!firstVar) {
|
|
220
|
+
sb.append(',');
|
|
221
|
+
}
|
|
222
|
+
firstVar = false;
|
|
223
|
+
sb.append("\"").append(jsonEscape(lv.name())).append("\":");
|
|
224
|
+
if (visibleVars.contains(lv)) {
|
|
225
|
+
Value val = frame.getValue(lv);
|
|
226
|
+
sb.append(serializeValue(val, reachable));
|
|
227
|
+
} else {
|
|
228
|
+
// Variable declared but not yet initialized
|
|
229
|
+
sb.append("{\"type\":\"uninitialized\"}");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
} catch (AbsentInformationException ignored) {
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
sb.append("}}");
|
|
236
|
+
}
|
|
237
|
+
sb.append(']');
|
|
238
|
+
return sb.toString();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private static String serializeValue(Value val,
|
|
242
|
+
Map<String, ObjectReference> reachable) {
|
|
243
|
+
if (val == null) {
|
|
244
|
+
return "{\"type\":\"null\"}";
|
|
245
|
+
}
|
|
246
|
+
if (val instanceof BooleanValue v) {
|
|
247
|
+
return "{\"type\":\"boolean\",\"value\":" + v.value() + "}";
|
|
248
|
+
}
|
|
249
|
+
if (val instanceof ByteValue v) {
|
|
250
|
+
return "{\"type\":\"byte\",\"value\":" + v.value() + "}";
|
|
251
|
+
}
|
|
252
|
+
if (val instanceof CharValue v) {
|
|
253
|
+
return "{\"type\":\"char\",\"value\":\""
|
|
254
|
+
+ jsonEscape(String.valueOf(v.value())) + "\"}";
|
|
255
|
+
}
|
|
256
|
+
if (val instanceof DoubleValue v) {
|
|
257
|
+
return "{\"type\":\"double\",\"value\":" + v.value() + "}";
|
|
258
|
+
}
|
|
259
|
+
if (val instanceof FloatValue v) {
|
|
260
|
+
return "{\"type\":\"float\",\"value\":" + v.value() + "}";
|
|
261
|
+
}
|
|
262
|
+
if (val instanceof IntegerValue v) {
|
|
263
|
+
return "{\"type\":\"int\",\"value\":" + v.value() + "}";
|
|
264
|
+
}
|
|
265
|
+
if (val instanceof LongValue v) {
|
|
266
|
+
return "{\"type\":\"long\",\"value\":" + v.value() + "}";
|
|
267
|
+
}
|
|
268
|
+
if (val instanceof ShortValue v) {
|
|
269
|
+
return "{\"type\":\"short\",\"value\":" + v.value() + "}";
|
|
270
|
+
}
|
|
271
|
+
if (val instanceof ObjectReference obj) {
|
|
272
|
+
String id = getObjectId(obj);
|
|
273
|
+
reachable.putIfAbsent(id, obj);
|
|
274
|
+
return "{\"type\":\"ref\",\"id\":\"" + id + "\"}";
|
|
275
|
+
}
|
|
276
|
+
return "{\"type\":\"unknown\"}";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private static String serializeHeap(
|
|
280
|
+
Map<String, ObjectReference> reachable) {
|
|
281
|
+
var sb = new StringBuilder("{");
|
|
282
|
+
var processed = new HashSet<String>();
|
|
283
|
+
var queue = new ArrayList<>(reachable.keySet());
|
|
284
|
+
boolean first = true;
|
|
285
|
+
int idx = 0;
|
|
286
|
+
|
|
287
|
+
while (idx < queue.size()) {
|
|
288
|
+
String id = queue.get(idx++);
|
|
289
|
+
if (processed.contains(id)) {
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
processed.add(id);
|
|
293
|
+
|
|
294
|
+
ObjectReference obj = reachable.get(id);
|
|
295
|
+
if (!first) {
|
|
296
|
+
sb.append(',');
|
|
297
|
+
}
|
|
298
|
+
first = false;
|
|
299
|
+
sb.append("\"").append(id).append("\":");
|
|
300
|
+
sb.append(serializeHeapObject(obj, reachable));
|
|
301
|
+
|
|
302
|
+
// Discover newly reachable objects
|
|
303
|
+
for (String newId : reachable.keySet()) {
|
|
304
|
+
if (!processed.contains(newId) && !queue.contains(newId)) {
|
|
305
|
+
queue.add(newId);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
sb.append('}');
|
|
310
|
+
return sb.toString();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private static String serializeHeapObject(ObjectReference obj,
|
|
314
|
+
Map<String, ObjectReference> reachable) {
|
|
315
|
+
if (obj instanceof StringReference str) {
|
|
316
|
+
return "{\"type\":\"String\",\"value\":\""
|
|
317
|
+
+ jsonEscape(str.value()) + "\"}";
|
|
318
|
+
}
|
|
319
|
+
if (obj instanceof ArrayReference arr) {
|
|
320
|
+
var sb = new StringBuilder("{\"type\":\"")
|
|
321
|
+
.append(jsonEscape(arr.referenceType().name()))
|
|
322
|
+
.append("\",\"elements\":[");
|
|
323
|
+
int limit = Math.min(arr.length(), 100);
|
|
324
|
+
for (int i = 0; i < limit; i++) {
|
|
325
|
+
if (i > 0) {
|
|
326
|
+
sb.append(',');
|
|
327
|
+
}
|
|
328
|
+
sb.append(serializeValue(arr.getValue(i), reachable));
|
|
329
|
+
}
|
|
330
|
+
if (arr.length() > limit) {
|
|
331
|
+
sb.append(",{\"type\":\"truncated\",\"remaining\":")
|
|
332
|
+
.append(arr.length() - limit).append('}');
|
|
333
|
+
}
|
|
334
|
+
sb.append("]}");
|
|
335
|
+
return sb.toString();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
ReferenceType type = obj.referenceType();
|
|
339
|
+
if (!isUserClass(type)) {
|
|
340
|
+
// JDK object: show type name and toString value
|
|
341
|
+
try {
|
|
342
|
+
return "{\"type\":\"" + jsonEscape(type.name())
|
|
343
|
+
+ "\",\"value\":\"" + jsonEscape(obj.toString()) + "\"}";
|
|
344
|
+
} catch (Exception e) {
|
|
345
|
+
return "{\"type\":\"" + jsonEscape(type.name()) + "\"}";
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// User object: show fields
|
|
350
|
+
var sb = new StringBuilder("{\"type\":\"")
|
|
351
|
+
.append(jsonEscape(type.name()))
|
|
352
|
+
.append("\",\"fields\":{");
|
|
353
|
+
boolean first = true;
|
|
354
|
+
try {
|
|
355
|
+
for (Field field : type.visibleFields()) {
|
|
356
|
+
if (field.isStatic()) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (!first) {
|
|
360
|
+
sb.append(',');
|
|
361
|
+
}
|
|
362
|
+
first = false;
|
|
363
|
+
sb.append("\"").append(jsonEscape(field.name())).append("\":");
|
|
364
|
+
sb.append(serializeValue(obj.getValue(field), reachable));
|
|
365
|
+
}
|
|
366
|
+
} catch (Exception ignored) {
|
|
367
|
+
}
|
|
368
|
+
sb.append("}}");
|
|
369
|
+
return sb.toString();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private static String getObjectId(ObjectReference obj) {
|
|
373
|
+
return objectIdMap.computeIfAbsent(
|
|
374
|
+
obj.uniqueID(), k -> "obj_" + nextObjId++);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
private static boolean isUserClass(ReferenceType type) {
|
|
378
|
+
String name = type.name();
|
|
379
|
+
return !name.startsWith("java.")
|
|
380
|
+
&& !name.startsWith("javax.")
|
|
381
|
+
&& !name.startsWith("jdk.")
|
|
382
|
+
&& !name.startsWith("sun.")
|
|
383
|
+
&& !name.startsWith("com.sun.");
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
private static String drainStream(InputStream stream) throws IOException {
|
|
387
|
+
var sb = new StringBuilder();
|
|
388
|
+
while (stream.available() > 0) {
|
|
389
|
+
sb.append((char) stream.read());
|
|
390
|
+
}
|
|
391
|
+
return sb.toString();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private static String jsonEscape(String s) {
|
|
395
|
+
var sb = new StringBuilder();
|
|
396
|
+
for (int i = 0; i < s.length(); i++) {
|
|
397
|
+
char c = s.charAt(i);
|
|
398
|
+
switch (c) {
|
|
399
|
+
case '"' -> sb.append("\\\"");
|
|
400
|
+
case '\\' -> sb.append("\\\\");
|
|
401
|
+
case '\n' -> sb.append("\\n");
|
|
402
|
+
case '\r' -> sb.append("\\r");
|
|
403
|
+
case '\t' -> sb.append("\\t");
|
|
404
|
+
default -> {
|
|
405
|
+
if (c < 0x20) {
|
|
406
|
+
sb.append("\\u")
|
|
407
|
+
.append(String.format("%04x", (int) c));
|
|
408
|
+
} else {
|
|
409
|
+
sb.append(c);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return sb.toString();
|
|
415
|
+
}
|
|
416
|
+
}
|
|
@@ -12,7 +12,7 @@ import type {
|
|
|
12
12
|
LiterateRunnerEntry,
|
|
13
13
|
} from '../types';
|
|
14
14
|
|
|
15
|
-
const log = makeLogger(
|
|
15
|
+
const log = makeLogger(import.meta.url);
|
|
16
16
|
|
|
17
17
|
let runnerClassDir: string | null = null;
|
|
18
18
|
|
|
@@ -121,7 +121,7 @@ function ensureRunnerCompiled(): string {
|
|
|
121
121
|
return runnerClassDir;
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
const sourceDir = path.join(
|
|
124
|
+
const sourceDir = path.join(import.meta.dir, 'jdi-runner');
|
|
125
125
|
const sourceFile = path.join(sourceDir, 'LiterateRunner.java');
|
|
126
126
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tada-runner-'));
|
|
127
127
|
|
package/build/utils/markdown.ts
CHANGED
|
@@ -15,6 +15,7 @@ import externalLinksPlugin from '../external-links-plugin';
|
|
|
15
15
|
import validateInternalLinksPlugin from '../validate-internal-links-plugin';
|
|
16
16
|
import applyBasePathPlugin from '../apply-base-path-plugin';
|
|
17
17
|
import { tocPlugin } from '../toc-plugin';
|
|
18
|
+
import columnsPlugin from '../columns-plugin';
|
|
18
19
|
import markdownItKatex from '@vscode/markdown-it-katex';
|
|
19
20
|
import katex from 'katex';
|
|
20
21
|
import renderA11yString from 'katex/contrib/render-a11y-string';
|
|
@@ -22,6 +23,8 @@ import type { SiteVariables } from '../types';
|
|
|
22
23
|
|
|
23
24
|
interface CreateMarkdownOptions {
|
|
24
25
|
validatorOptions?: Record<string, unknown>;
|
|
26
|
+
literateJavaOutputPaths?: Set<string>;
|
|
27
|
+
sourceUrlPath?: string;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
function capitalize(str: string): string {
|
|
@@ -36,7 +39,11 @@ export function createMarkdown(
|
|
|
36
39
|
siteVariables: SiteVariables,
|
|
37
40
|
options: CreateMarkdownOptions = {},
|
|
38
41
|
): MarkdownIt {
|
|
39
|
-
const {
|
|
42
|
+
const {
|
|
43
|
+
validatorOptions = {},
|
|
44
|
+
literateJavaOutputPaths,
|
|
45
|
+
sourceUrlPath,
|
|
46
|
+
} = options;
|
|
40
47
|
const markdown = new MarkdownIt({ html: true, typographer: true })
|
|
41
48
|
.use(headingSubtitlePlugin)
|
|
42
49
|
.use(markdownItAnchor, { tabIndex: false })
|
|
@@ -45,8 +52,12 @@ export function createMarkdown(
|
|
|
45
52
|
.use(deflistIdPlugin)
|
|
46
53
|
.use(externalLinksPlugin, siteVariables)
|
|
47
54
|
.use(validateInternalLinksPlugin, validatorOptions)
|
|
48
|
-
.use(applyBasePathPlugin, siteVariables
|
|
55
|
+
.use(applyBasePathPlugin, siteVariables, {
|
|
56
|
+
literateJavaOutputPaths,
|
|
57
|
+
sourceUrlPath,
|
|
58
|
+
})
|
|
49
59
|
.use(tocPlugin)
|
|
60
|
+
.use(columnsPlugin)
|
|
50
61
|
.use(markdownItKatex)
|
|
51
62
|
.use(markdownItContainer, 'details', {
|
|
52
63
|
marker: '<',
|
|
@@ -116,7 +127,12 @@ export function createMarkdown(
|
|
|
116
127
|
validate: function (params: string) {
|
|
117
128
|
return !!params.trim().match(/^(note|warning)\s*"?(.+)?"?$/);
|
|
118
129
|
},
|
|
119
|
-
render: function (
|
|
130
|
+
render: function (
|
|
131
|
+
tokens: Token[],
|
|
132
|
+
idx: number,
|
|
133
|
+
_options: unknown,
|
|
134
|
+
env: Record<string, unknown>,
|
|
135
|
+
) {
|
|
120
136
|
const matches = tokens[idx].info
|
|
121
137
|
.trim()
|
|
122
138
|
.match(/^(note|warning)\s*"?(.+)?"?$/);
|
|
@@ -129,19 +145,21 @@ export function createMarkdown(
|
|
|
129
145
|
}
|
|
130
146
|
|
|
131
147
|
const title = matches && matches[2]?.trim();
|
|
148
|
+
const displayTitle = title
|
|
149
|
+
? markdown.utils.escapeHtml(curlyQuote(title))
|
|
150
|
+
: capitalize(type || '');
|
|
151
|
+
const baseId = textToId(title || type || '');
|
|
152
|
+
const count = (usedIds.get(baseId) ?? 0) + 1;
|
|
153
|
+
usedIds.set(baseId, count);
|
|
154
|
+
const titleId = count === 1 ? baseId : `${baseId}-${count}`;
|
|
132
155
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const baseId = textToId(title);
|
|
136
|
-
const count = (usedIds.get(baseId) ?? 0) + 1;
|
|
137
|
-
usedIds.set(baseId, count);
|
|
138
|
-
const titleId = count === 1 ? baseId : `${baseId}-${count}`;
|
|
139
|
-
const renderedTitle = markdown.utils.escapeHtml(curlyQuote(title));
|
|
140
|
-
html += `<p class="title" id="${titleId}">${renderedTitle}</p>\n`;
|
|
141
|
-
} else {
|
|
142
|
-
const defaultTitle = capitalize(type || '');
|
|
143
|
-
html += `<p class="title">${defaultTitle}</p>\n`;
|
|
156
|
+
if (!env.alertIds) {
|
|
157
|
+
env.alertIds = [];
|
|
144
158
|
}
|
|
159
|
+
(env.alertIds as string[]).push(titleId);
|
|
160
|
+
|
|
161
|
+
let html = `<div class="${classNames.join(' ')}">`;
|
|
162
|
+
html += `<p class="title" id="${titleId}">${displayTitle}</p>\n`;
|
|
145
163
|
html += '<div class="content">\n';
|
|
146
164
|
return html;
|
|
147
165
|
} else {
|
package/build/utils/paths.ts
CHANGED