@abreen/tada 1.13.7 → 1.14.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.
Files changed (49) hide show
  1. package/README.md +46 -0
  2. package/build/generate-content-assets.ts +39 -185
  3. package/build/pipeline.ts +1 -1
  4. package/build/source-model.ts +2 -14
  5. package/build/types.d.ts +18 -18
  6. package/build/utils/html-comments.ts +3 -0
  7. package/build/utils/jdi-runner/TraceRunner.java +227 -38
  8. package/build/utils/markdown-partials.ts +235 -0
  9. package/build/utils/markdown.ts +12 -1
  10. package/build/utils/python-runner/trace_runner.py +58 -13
  11. package/build/utils/render.ts +14 -12
  12. package/build/utils/trace-core.ts +136 -14
  13. package/build/utils/trace-heap.ts +34 -0
  14. package/build/utils/trace-java.ts +60 -7
  15. package/build/utils/trace-layout.ts +58 -13
  16. package/build/utils/trace-python.ts +6 -2
  17. package/build/utils/trace-svg.ts +54 -60
  18. package/build/utils/trace.ts +193 -78
  19. package/build/watch/build-full.ts +26 -52
  20. package/build/watch/build-helpers.ts +66 -0
  21. package/build/watch/build-incremental.ts +32 -60
  22. package/build/watch/build-result.ts +19 -0
  23. package/build/watch/compiler-types.d.ts +2 -2
  24. package/build/watch/compiler.ts +30 -62
  25. package/build/watch/config-paths.ts +56 -0
  26. package/build/watch/engine.ts +199 -0
  27. package/{watch → build/watch}/fs-commit.ts +1 -3
  28. package/build/watch/index.ts +1 -1
  29. package/build/watch/mutations.ts +23 -25
  30. package/build/watch/planner.ts +14 -78
  31. package/build/watch/runtime.ts +26 -38
  32. package/build/watch/snapshot.ts +26 -47
  33. package/build/watch/types.d.ts +84 -0
  34. package/build/watch/validation.ts +1 -1
  35. package/init/content/lectures/02/_pr1.md +1 -1
  36. package/init/content/lectures/02/index.md +2 -2
  37. package/init/content/lectures/02/subdir/_foobar.md +3 -0
  38. package/init/content/lectures/02/subdir/_pr2.md +3 -3
  39. package/package.json +1 -2
  40. package/src/slides/index.ts +6 -2
  41. package/src/trace/index.ts +49 -12
  42. package/src/trace/style.scss +10 -0
  43. package/src/trace/types.d.ts +16 -4
  44. package/build/utils/include.ts +0 -57
  45. package/build/watch-reachability-state.ts +0 -305
  46. package/init/content/lectures/02/subdir/_foobar.html +0 -3
  47. package/watch/engine.ts +0 -296
  48. package/watch/events.ts +0 -10
  49. package/watch/types.d.ts +0 -103
@@ -1,4 +1,4 @@
1
- import type { TraceManifest, TraceChunkEntry } from './types';
1
+ import type { TraceManifest, TraceChunkEntry, TraceOutputEvent } from './types';
2
2
  import { globals } from '../globals';
3
3
 
4
4
  interface WidgetState {
@@ -9,7 +9,7 @@ interface WidgetState {
9
9
 
10
10
  interface WidgetElements {
11
11
  root: HTMLElement;
12
- source: HTMLElement;
12
+ sourceWrapper: HTMLElement;
13
13
  controls: HTMLElement;
14
14
  content: HTMLElement;
15
15
  diagram: HTMLElement;
@@ -77,6 +77,22 @@ function updateSourceHighlight(panel: HTMLElement, currentLine: number): void {
77
77
  }
78
78
  }
79
79
 
80
+ function updateVisibleSourcePanel(
81
+ sourceWrapper: HTMLElement,
82
+ currentFile: string,
83
+ ): HTMLElement | null {
84
+ let activePanel: HTMLElement | null = null;
85
+ sourceWrapper.querySelectorAll('.trace-source').forEach(panel => {
86
+ const sourcePanel = panel as HTMLElement;
87
+ const matches = sourcePanel.dataset.traceSourceFile === currentFile;
88
+ sourcePanel.hidden = !matches;
89
+ if (matches) {
90
+ activePanel = sourcePanel;
91
+ }
92
+ });
93
+ return activePanel;
94
+ }
95
+
80
96
  function updateStepControls(
81
97
  container: HTMLElement,
82
98
  currentStep: number,
@@ -202,13 +218,13 @@ function scaleDiagramSvg(diagram: HTMLElement, doc: Document): void {
202
218
 
203
219
  function updateOutput(
204
220
  content: HTMLElement,
205
- output: string,
221
+ output: TraceOutputEvent[],
206
222
  doc: Document,
207
223
  ): void {
208
224
  const existing = content.querySelector(
209
225
  ':scope > .trace-output',
210
226
  ) as HTMLPreElement | null;
211
- if (!output) {
227
+ if (output.length === 0) {
212
228
  existing?.remove();
213
229
  return;
214
230
  }
@@ -218,7 +234,16 @@ function updateOutput(
218
234
  outputElement.className = 'trace-output';
219
235
  content.append(outputElement);
220
236
  }
221
- outputElement.textContent = output;
237
+ outputElement.replaceChildren(
238
+ ...output.map(event => {
239
+ const span = doc.createElement('span');
240
+ if (event.stream === 'stderr') {
241
+ span.className = 'trace-output-stderr';
242
+ }
243
+ span.textContent = event.text;
244
+ return span;
245
+ }),
246
+ );
222
247
  }
223
248
 
224
249
  function renderWidgetState(
@@ -227,21 +252,25 @@ function renderWidgetState(
227
252
  doc: Document,
228
253
  ): void {
229
254
  const entry = getStep(state);
230
- updateSourceHighlight(elements.source, entry.line);
255
+ const sourcePanel = updateVisibleSourcePanel(
256
+ elements.sourceWrapper,
257
+ entry.file,
258
+ );
259
+ if (sourcePanel) {
260
+ updateSourceHighlight(sourcePanel, entry.line);
261
+ }
231
262
  updateStepControls(
232
263
  elements.controls,
233
264
  state.currentStep,
234
265
  state.manifest.totalSteps,
235
266
  doc,
236
267
  );
237
- let output = '';
268
+ const output: TraceOutputEvent[] = [];
238
269
  for (let i = 0; i <= state.currentStep; i++) {
239
270
  const ci = Math.floor(i / state.manifest.chunkSize);
240
271
  const off = i % state.manifest.chunkSize;
241
272
  const s = state.chunks.get(ci)![off];
242
- if (s.stdout) {
243
- output += s.stdout;
244
- }
273
+ output.push(...(s.output ?? []));
245
274
  }
246
275
  updateOutput(elements.content, output, doc);
247
276
 
@@ -277,7 +306,9 @@ async function initWidget(root: HTMLElement, doc: Document): Promise<void> {
277
306
 
278
307
  await loadChunk(state, manifestUrl, 0);
279
308
 
280
- const source = root.querySelector('.trace-source') as HTMLElement;
309
+ const sourceWrapper = root.querySelector(
310
+ '.trace-source-wrapper',
311
+ ) as HTMLElement;
281
312
  const content = root.querySelector('.trace-content') as HTMLElement;
282
313
  const diagram = root.querySelector('.trace-diagram') as HTMLElement;
283
314
 
@@ -321,7 +352,13 @@ async function initWidget(root: HTMLElement, doc: Document): Promise<void> {
321
352
  next.focus();
322
353
  });
323
354
 
324
- const elements: WidgetElements = { root, source, controls, content, diagram };
355
+ const elements: WidgetElements = {
356
+ root,
357
+ sourceWrapper,
358
+ controls,
359
+ content,
360
+ diagram,
361
+ };
325
362
 
326
363
  renderWidgetState(state, elements, doc);
327
364
  }
@@ -141,6 +141,16 @@
141
141
  content: 'Output';
142
142
  }
143
143
 
144
+ .trace-output-stderr {
145
+ color: hsl(0deg 70% 45%);
146
+ }
147
+
148
+ @media (prefers-color-scheme: dark) {
149
+ .trace-output-stderr {
150
+ color: hsl(0deg 85% 72%);
151
+ }
152
+ }
153
+
144
154
  .trace-memory {
145
155
  max-width: 100%;
146
156
  height: auto;
@@ -1,7 +1,12 @@
1
1
  export interface TraceManifest {
2
2
  totalSteps: number;
3
3
  chunkSize: number;
4
- sourceFile: string;
4
+ primaryFile: string;
5
+ sources: TraceSource[];
6
+ }
7
+
8
+ export interface TraceSource {
9
+ file: string;
5
10
  source: string;
6
11
  lineToSteps: Record<number, number[]>;
7
12
  }
@@ -11,7 +16,13 @@ export interface TraceStep {
11
16
  file: string;
12
17
  stack: TraceStackFrame[];
13
18
  heap: Record<string, TraceHeapObject>;
14
- stdout: string;
19
+ output?: TraceOutputEvent[];
20
+ transientHeapRoots?: string[];
21
+ }
22
+
23
+ export interface TraceOutputEvent {
24
+ stream: 'stdout' | 'stderr';
25
+ text: string;
15
26
  }
16
27
 
17
28
  export interface TraceStackFrame {
@@ -37,11 +48,12 @@ export type TraceValue =
37
48
  export type TraceHeapObject =
38
49
  | { type: string; elements: TraceValue[] }
39
50
  | { type: string; fields: Record<string, TraceValue> }
40
- | { type: string; value: string };
51
+ | { type: string; value: string | number | boolean };
41
52
 
42
53
  /** A single entry in a chunk file (new format with precomputed SVG). */
43
54
  export interface TraceChunkEntry {
55
+ file: string;
44
56
  line: number;
45
- stdout: string;
57
+ output?: TraceOutputEvent[];
46
58
  svg: string;
47
59
  }
@@ -1,57 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import _ from 'lodash';
4
- import { isPartial } from './file-types';
5
- import { stripHtmlComments } from './render';
6
- import type { RenderDependencyCollector } from '../types';
7
-
8
- const MAX_INCLUDE_DEPTH = 10;
9
-
10
- export function createIncludeFunction(
11
- callerFilePath: string,
12
- templateParams: Record<string, unknown>,
13
- collector?: RenderDependencyCollector,
14
- depth: number = 0,
15
- ): (relativePath: string) => string {
16
- return (relativePath: string): string => {
17
- const callerDir = path.dirname(callerFilePath);
18
- const resolved = path.resolve(callerDir, relativePath);
19
-
20
- if (!fs.existsSync(resolved)) {
21
- throw new Error(
22
- `${callerFilePath}: partial not found: ${relativePath} (resolved to ${resolved})`,
23
- );
24
- }
25
- if (!isPartial(resolved)) {
26
- throw new Error(
27
- `${callerFilePath}: include target must start with "_": ${relativePath}`,
28
- );
29
- }
30
- collector?.partials?.add(resolved);
31
- if (depth >= MAX_INCLUDE_DEPTH) {
32
- throw new Error(
33
- `${callerFilePath}: maximum include depth (${MAX_INCLUDE_DEPTH}) exceeded`,
34
- );
35
- }
36
-
37
- const raw = fs.readFileSync(resolved, 'utf-8');
38
- const content = stripHtmlComments(raw);
39
-
40
- const nestedInclude = createIncludeFunction(
41
- resolved,
42
- templateParams,
43
- collector,
44
- depth + 1,
45
- );
46
- const nestedParams = { ...templateParams, include: nestedInclude };
47
-
48
- try {
49
- return _.template(content)(nestedParams);
50
- } catch (err) {
51
- throw new Error(
52
- `${resolved}: Lodash template error in partial: ${(err as Error).message}`,
53
- { cause: err },
54
- );
55
- }
56
- };
57
- }
@@ -1,305 +0,0 @@
1
- import { collectOutgoingHtmlAssetPaths } from './reachability';
2
- import type { HtmlOutputAnalysis } from './types';
3
-
4
- type ReadAnalysisForAsset = (assetPath: string) => HtmlOutputAnalysis;
5
-
6
- function cloneSet(values: Iterable<string> | undefined): Set<string> {
7
- return new Set(values || []);
8
- }
9
-
10
- interface IncrementalOptions {
11
- changedAssetPaths?: Iterable<string>;
12
- removedAssetPaths?: Iterable<string>;
13
- readAnalysisForAsset: ReadAnalysisForAsset;
14
- }
15
-
16
- class WatchReachabilityState {
17
- private rootPath: string;
18
- private knownAssets: Set<string>;
19
- private outgoingBySource: Map<string, Set<string>>;
20
- private incomingByTarget: Map<string, Set<string>>;
21
- private reachable: Set<string>;
22
- private initialized: boolean;
23
-
24
- constructor({ rootPath = 'index.html' }: { rootPath?: string } = {}) {
25
- this.rootPath = rootPath;
26
- this.knownAssets = new Set();
27
- this.outgoingBySource = new Map();
28
- this.incomingByTarget = new Map();
29
- this.reachable = new Set();
30
- this.initialized = false;
31
- }
32
-
33
- reset(): void {
34
- this.knownAssets = new Set();
35
- this.outgoingBySource = new Map();
36
- this.incomingByTarget = new Map();
37
- this.reachable = new Set();
38
- this.initialized = false;
39
- }
40
-
41
- setKnownAssets(assetPaths: Iterable<string>): void {
42
- this.knownAssets = cloneSet(assetPaths);
43
- this.reachable = new Set(
44
- [...this.reachable].filter(assetPath => this.knownAssets.has(assetPath)),
45
- );
46
-
47
- const nextOutgoingBySource = new Map<string, Set<string>>();
48
- for (const [sourcePath, targets] of this.outgoingBySource) {
49
- if (!this.knownAssets.has(sourcePath)) {
50
- continue;
51
- }
52
-
53
- nextOutgoingBySource.set(
54
- sourcePath,
55
- new Set(
56
- [...targets].filter(targetPath => this.knownAssets.has(targetPath)),
57
- ),
58
- );
59
- }
60
-
61
- this.outgoingBySource = nextOutgoingBySource;
62
- this.rebuildIncomingByTarget();
63
- }
64
-
65
- rebuild(readAnalysisForAsset: ReadAnalysisForAsset): void {
66
- if (!this.knownAssets.has(this.rootPath)) {
67
- throw new Error(`Pagefind reachability root not found: ${this.rootPath}`);
68
- }
69
-
70
- this.outgoingBySource = new Map();
71
- this.incomingByTarget = new Map();
72
- this.reachable = new Set();
73
-
74
- const knownAssetPaths = [...this.knownAssets].sort();
75
- for (const sourcePath of knownAssetPaths) {
76
- const outgoingPaths = this.readOutgoingPaths(
77
- sourcePath,
78
- readAnalysisForAsset,
79
- );
80
- this.outgoingBySource.set(sourcePath, outgoingPaths);
81
- this.addIncomingEdges(sourcePath, outgoingPaths);
82
- }
83
-
84
- this.reachable = this.collectClosure(new Set([this.rootPath]));
85
- this.initialized = true;
86
- }
87
-
88
- applyIncremental({
89
- changedAssetPaths,
90
- removedAssetPaths,
91
- readAnalysisForAsset,
92
- }: IncrementalOptions): void {
93
- if (!this.initialized) {
94
- this.rebuild(readAnalysisForAsset);
95
- return;
96
- }
97
-
98
- const normalizedChanged = new Set<string>();
99
- for (const assetPath of changedAssetPaths || []) {
100
- if (this.knownAssets.has(assetPath)) {
101
- normalizedChanged.add(assetPath);
102
- }
103
- }
104
-
105
- const normalizedRemoved = new Set<string>();
106
- for (const assetPath of removedAssetPaths || []) {
107
- if (
108
- !this.knownAssets.has(assetPath) &&
109
- !normalizedChanged.has(assetPath)
110
- ) {
111
- normalizedRemoved.add(assetPath);
112
- }
113
- }
114
-
115
- const previouslyReachableRoots = new Set<string>();
116
- for (const assetPath of normalizedChanged) {
117
- if (this.reachable.has(assetPath)) {
118
- previouslyReachableRoots.add(assetPath);
119
- }
120
- }
121
- for (const assetPath of normalizedRemoved) {
122
- if (this.reachable.has(assetPath)) {
123
- previouslyReachableRoots.add(assetPath);
124
- }
125
- }
126
-
127
- const oldAffectedClosure = this.collectClosure(previouslyReachableRoots);
128
-
129
- for (const assetPath of normalizedRemoved) {
130
- this.removeAsset(assetPath);
131
- }
132
-
133
- const changedAssetList = [...normalizedChanged].sort();
134
- for (const assetPath of changedAssetList) {
135
- const outgoingPaths = this.readOutgoingPaths(
136
- assetPath,
137
- readAnalysisForAsset,
138
- );
139
- this.replaceOutgoing(assetPath, outgoingPaths);
140
- }
141
-
142
- const stillKnownRoots = new Set(
143
- [...previouslyReachableRoots].filter(assetPath =>
144
- this.knownAssets.has(assetPath),
145
- ),
146
- );
147
- const newAffectedClosure = this.collectClosure(stillKnownRoots);
148
- const affected = new Set([...oldAffectedClosure, ...newAffectedClosure]);
149
-
150
- if (affected.size === 0) {
151
- this.initialized = true;
152
- return;
153
- }
154
-
155
- for (const assetPath of affected) {
156
- this.reachable.delete(assetPath);
157
- }
158
-
159
- const seedPaths = new Set<string>();
160
- for (const assetPath of stillKnownRoots) {
161
- if (affected.has(assetPath)) {
162
- seedPaths.add(assetPath);
163
- }
164
- }
165
-
166
- if (this.knownAssets.has(this.rootPath) && affected.has(this.rootPath)) {
167
- seedPaths.add(this.rootPath);
168
- }
169
-
170
- for (const assetPath of affected) {
171
- const incomingPaths = this.incomingByTarget.get(assetPath);
172
- if (!incomingPaths) {
173
- continue;
174
- }
175
-
176
- for (const sourcePath of incomingPaths) {
177
- if (this.reachable.has(sourcePath) && !affected.has(sourcePath)) {
178
- seedPaths.add(assetPath);
179
- break;
180
- }
181
- }
182
- }
183
-
184
- const restoredReachable = this.collectClosure(seedPaths, affected);
185
- for (const assetPath of restoredReachable) {
186
- this.reachable.add(assetPath);
187
- }
188
-
189
- this.initialized = true;
190
- }
191
-
192
- getReachablePaths(): string[] {
193
- return [...this.reachable].sort();
194
- }
195
-
196
- private readOutgoingPaths(
197
- sourcePath: string,
198
- readAnalysisForAsset: ReadAnalysisForAsset,
199
- ): Set<string> {
200
- if (!this.knownAssets.has(sourcePath)) {
201
- return new Set();
202
- }
203
-
204
- const analysis = readAnalysisForAsset(sourcePath);
205
- return new Set(collectOutgoingHtmlAssetPaths(analysis, this.knownAssets));
206
- }
207
-
208
- private addIncomingEdges(
209
- sourcePath: string,
210
- outgoingPaths: Set<string>,
211
- ): void {
212
- for (const targetPath of outgoingPaths) {
213
- let incomingPaths = this.incomingByTarget.get(targetPath);
214
- if (!incomingPaths) {
215
- incomingPaths = new Set();
216
- this.incomingByTarget.set(targetPath, incomingPaths);
217
- }
218
- incomingPaths.add(sourcePath);
219
- }
220
- }
221
-
222
- private rebuildIncomingByTarget(): void {
223
- this.incomingByTarget = new Map();
224
- for (const [sourcePath, outgoingPaths] of this.outgoingBySource) {
225
- this.addIncomingEdges(sourcePath, outgoingPaths);
226
- }
227
- }
228
-
229
- private removeOutgoingEdges(sourcePath: string): void {
230
- const outgoingPaths = this.outgoingBySource.get(sourcePath);
231
- if (!outgoingPaths) {
232
- return;
233
- }
234
-
235
- for (const targetPath of outgoingPaths) {
236
- const incomingPaths = this.incomingByTarget.get(targetPath);
237
- if (!incomingPaths) {
238
- continue;
239
- }
240
-
241
- incomingPaths.delete(sourcePath);
242
- if (incomingPaths.size === 0) {
243
- this.incomingByTarget.delete(targetPath);
244
- }
245
- }
246
- }
247
-
248
- private replaceOutgoing(
249
- sourcePath: string,
250
- outgoingPaths: Set<string>,
251
- ): void {
252
- this.removeOutgoingEdges(sourcePath);
253
- if (!this.knownAssets.has(sourcePath)) {
254
- this.outgoingBySource.delete(sourcePath);
255
- return;
256
- }
257
-
258
- this.outgoingBySource.set(sourcePath, outgoingPaths);
259
- this.addIncomingEdges(sourcePath, outgoingPaths);
260
- }
261
-
262
- private removeAsset(assetPath: string): void {
263
- this.removeOutgoingEdges(assetPath);
264
- this.outgoingBySource.delete(assetPath);
265
- this.incomingByTarget.delete(assetPath);
266
- this.reachable.delete(assetPath);
267
- }
268
-
269
- private collectClosure(
270
- rootPaths: Set<string>,
271
- allowedPaths: Set<string> | null = null,
272
- ): Set<string> {
273
- const pending = [...rootPaths];
274
- const visited = new Set<string>();
275
-
276
- while (pending.length > 0) {
277
- const currentPath = pending.pop()!;
278
- if (visited.has(currentPath)) {
279
- continue;
280
- }
281
- if (allowedPaths && !allowedPaths.has(currentPath)) {
282
- continue;
283
- }
284
-
285
- visited.add(currentPath);
286
- const outgoingPaths = this.outgoingBySource.get(currentPath);
287
- if (!outgoingPaths) {
288
- continue;
289
- }
290
-
291
- for (const targetPath of outgoingPaths) {
292
- if (allowedPaths && !allowedPaths.has(targetPath)) {
293
- continue;
294
- }
295
- if (!visited.has(targetPath)) {
296
- pending.push(targetPath);
297
- }
298
- }
299
- }
300
-
301
- return visited;
302
- }
303
- }
304
-
305
- export default WatchReachabilityState;
@@ -1,3 +0,0 @@
1
- <p>Here's an HTML partial in a subdirectory.</p>
2
-
3
- <p>Here's the title of the page including this partial: <%= page.title %></p>