@abreen/tada 1.8.1 → 1.10.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 +24 -34
- package/bin/tada.ts +1 -1
- package/bin/validators.ts +1 -1
- package/build/apply-base-path-plugin.ts +79 -17
- package/build/bundle.ts +9 -1
- package/build/content-watch.ts +3 -3
- package/build/copy.ts +30 -0
- package/build/generate-content-assets.ts +45 -59
- package/build/pagefind.ts +0 -7
- package/build/serve.ts +4 -0
- package/build/templates.ts +0 -2
- package/build/types.d.ts +19 -1
- package/build/util.ts +3 -0
- package/build/utils/content-files.ts +98 -0
- package/build/utils/include.ts +4 -0
- package/build/utils/python-runner/trace_runner.py +272 -0
- package/build/utils/render.ts +47 -15
- package/build/utils/source-template.ts +27 -0
- package/build/utils/trace-core.ts +203 -0
- package/build/utils/trace-java.ts +132 -0
- package/build/utils/trace-layout.ts +31 -3
- package/build/utils/trace-python.ts +26 -0
- package/build/utils/trace.ts +174 -294
- package/build/validate-internal-links-plugin.ts +16 -4
- package/build/watch/assets.ts +111 -0
- package/build/watch/build-full.ts +124 -0
- package/build/watch/build-incremental.ts +139 -0
- package/build/watch/compiler-types.d.ts +19 -0
- package/build/watch/compiler.ts +112 -0
- package/build/watch/index.ts +22 -0
- package/build/watch/mutations.ts +75 -0
- package/build/watch/planner.ts +230 -0
- package/build/watch/records.ts +190 -0
- package/build/watch/runtime.ts +143 -0
- package/build/watch/snapshot.ts +602 -0
- package/build/watch/validation.ts +36 -0
- package/build/watch-reload-client.ts +10 -5
- package/init/content/labs/01/index.md +6 -0
- package/init/content/labs/01/trace_demo.py +16 -0
- package/init/content/lectures/01/Rectangle.java +2 -0
- package/init/content/lectures/01/index.md +6 -0
- package/init/content/lectures/01/rectangle.py +5 -0
- package/package.json +42 -23
- package/python/command.ts +95 -0
- package/python/module.ts +18 -0
- package/src/_alerts.scss +11 -8
- package/src/_base.scss +25 -29
- package/src/_layout.scss +17 -14
- package/src/_mixins.scss +10 -9
- package/src/anchor/style.scss +4 -2
- package/src/code/index.ts +9 -2
- package/src/code/style.scss +14 -13
- package/src/code.scss +14 -13
- package/src/header/_base.scss +21 -24
- package/src/header/style.scss +16 -12
- package/src/index.ts +3 -0
- package/src/literate/style.scss +4 -4
- package/src/navigate/index.ts +43 -279
- package/src/navigate/lifecycle.ts +1 -1
- package/src/navigate/runtime.ts +341 -0
- package/src/page-update/README.md +4 -0
- package/src/page-update/index.ts +280 -0
- package/src/page-update/style.scss +53 -0
- package/src/print/style.scss +4 -4
- package/src/question/style.scss +10 -6
- package/src/search/index.ts +9 -12
- package/src/search/style.scss +44 -42
- package/src/style.scss +63 -58
- package/src/timezone/style.scss +5 -5
- package/src/toc/style.scss +17 -16
- package/src/trace/style.scss +27 -27
- package/src/validators.ts +45 -0
- package/templates/_footer.html +1 -1
- package/templates/_theme.scss +8 -19
- package/watch/engine.ts +296 -0
- package/watch/events.ts +10 -0
- package/watch/fs-commit.ts +102 -0
- package/watch/types.d.ts +103 -0
- package/build/watch.ts +0 -493
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import type { SiteVariables } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Applies Lodash template syntax to a source code file's contents so that
|
|
6
|
+
* authors can interpolate site variables (like `<%= vars.fullCourseName %>`)
|
|
7
|
+
* in their source code. The template context mirrors the one used for JSON
|
|
8
|
+
* data files in `build/templates.ts`: `vars` and `site`. Invoked from
|
|
9
|
+
* `renderCodePageAsset` and `renderCopiedContentAsset` only when
|
|
10
|
+
* `features.code` is enabled.
|
|
11
|
+
*/
|
|
12
|
+
export function applySourceTemplate(
|
|
13
|
+
source: string,
|
|
14
|
+
siteVariables: SiteVariables,
|
|
15
|
+
filePath: string,
|
|
16
|
+
): string {
|
|
17
|
+
const params = { vars: siteVariables.vars || {}, site: siteVariables };
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
return _.template(source)(params);
|
|
21
|
+
} catch (err: unknown) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`${filePath}: Lodash template error in source code: ${(err as Error).message}`,
|
|
24
|
+
{ cause: err },
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { renderCodeSegment } from './code';
|
|
4
|
+
import { splitLines } from './literate-java';
|
|
5
|
+
import { normalizeOutputPath } from './paths';
|
|
6
|
+
import { computeLayout } from './trace-layout';
|
|
7
|
+
import { generateStepSvg } from './trace-svg';
|
|
8
|
+
import type { TraceChunkEntry, TraceManifest, TraceStep } from '../types';
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_CHUNK_SIZE = 50;
|
|
11
|
+
|
|
12
|
+
export interface ChunkTraceOutputOptions {
|
|
13
|
+
chunkSize?: number;
|
|
14
|
+
ignoreFields?: Record<string, string[]>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function chunkTraceOutput(
|
|
18
|
+
output: string,
|
|
19
|
+
traceOutputDir: string,
|
|
20
|
+
sourceFile: string,
|
|
21
|
+
source: string,
|
|
22
|
+
options: ChunkTraceOutputOptions = {},
|
|
23
|
+
): TraceManifest {
|
|
24
|
+
const chunkSize = options.chunkSize ?? DEFAULT_CHUNK_SIZE;
|
|
25
|
+
const ignoreFields = options.ignoreFields ?? {};
|
|
26
|
+
|
|
27
|
+
fs.mkdirSync(traceOutputDir, { recursive: true });
|
|
28
|
+
|
|
29
|
+
const lines = output
|
|
30
|
+
.trim()
|
|
31
|
+
.split('\n')
|
|
32
|
+
.filter(l => l.length > 0);
|
|
33
|
+
|
|
34
|
+
const allSteps: TraceStep[] = lines.map(l => JSON.parse(l));
|
|
35
|
+
|
|
36
|
+
const lineToSteps: Record<number, number[]> = {};
|
|
37
|
+
for (let i = 0; i < allSteps.length; i++) {
|
|
38
|
+
const step = allSteps[i];
|
|
39
|
+
if (!lineToSteps[step.line]) {
|
|
40
|
+
lineToSteps[step.line] = [];
|
|
41
|
+
}
|
|
42
|
+
lineToSteps[step.line].push(i);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const layout = computeLayout(allSteps, ignoreFields);
|
|
46
|
+
|
|
47
|
+
let chunkEntries: TraceChunkEntry[] = [];
|
|
48
|
+
let chunkIndex = 0;
|
|
49
|
+
|
|
50
|
+
for (const step of allSteps) {
|
|
51
|
+
const svg = generateStepSvg(step, layout);
|
|
52
|
+
chunkEntries.push({ line: step.line, stdout: step.stdout, svg });
|
|
53
|
+
|
|
54
|
+
if (chunkEntries.length >= chunkSize) {
|
|
55
|
+
fs.writeFileSync(
|
|
56
|
+
path.join(traceOutputDir, `chunk-${chunkIndex}.json`),
|
|
57
|
+
JSON.stringify(chunkEntries),
|
|
58
|
+
);
|
|
59
|
+
chunkEntries = [];
|
|
60
|
+
chunkIndex++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (chunkEntries.length > 0) {
|
|
65
|
+
fs.writeFileSync(
|
|
66
|
+
path.join(traceOutputDir, `chunk-${chunkIndex}.json`),
|
|
67
|
+
JSON.stringify(chunkEntries),
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const manifest: TraceManifest = {
|
|
72
|
+
totalSteps: allSteps.length,
|
|
73
|
+
chunkSize,
|
|
74
|
+
sourceFile,
|
|
75
|
+
source,
|
|
76
|
+
lineToSteps,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
fs.writeFileSync(
|
|
80
|
+
path.join(traceOutputDir, 'manifest.json'),
|
|
81
|
+
JSON.stringify(manifest),
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return manifest;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function highlightTraceSource(
|
|
88
|
+
source: string,
|
|
89
|
+
language: 'java' | 'python',
|
|
90
|
+
): string {
|
|
91
|
+
return renderCodeSegment(splitLines(source), 1, language, {
|
|
92
|
+
linkLineNumbers: false,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getTraceOutputPaths(
|
|
97
|
+
relDir: string,
|
|
98
|
+
traceName: string,
|
|
99
|
+
totalSteps: number,
|
|
100
|
+
): string[] {
|
|
101
|
+
const baseDir = [relDir, '_traces', traceName].filter(Boolean).join('/');
|
|
102
|
+
const outputPaths = [`${baseDir}/manifest.json`];
|
|
103
|
+
for (
|
|
104
|
+
let chunkIndex = 0;
|
|
105
|
+
chunkIndex * DEFAULT_CHUNK_SIZE < totalSteps;
|
|
106
|
+
chunkIndex++
|
|
107
|
+
) {
|
|
108
|
+
outputPaths.push(`${baseDir}/chunk-${chunkIndex}.json`);
|
|
109
|
+
}
|
|
110
|
+
return outputPaths;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function linkOrCopyFile(sourcePath: string, targetPath: string): void {
|
|
114
|
+
try {
|
|
115
|
+
fs.linkSync(sourcePath, targetPath);
|
|
116
|
+
} catch {
|
|
117
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function materializeTraceOutputs({
|
|
122
|
+
outputPaths,
|
|
123
|
+
targetDistDir,
|
|
124
|
+
sourceDistDir,
|
|
125
|
+
}: {
|
|
126
|
+
outputPaths: string[];
|
|
127
|
+
targetDistDir: string;
|
|
128
|
+
sourceDistDir: string;
|
|
129
|
+
}): boolean {
|
|
130
|
+
for (const relPath of outputPaths) {
|
|
131
|
+
const targetPath = path.join(targetDistDir, relPath);
|
|
132
|
+
if (fs.existsSync(targetPath)) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const sourcePath = path.join(sourceDistDir, relPath);
|
|
137
|
+
if (!fs.existsSync(sourcePath)) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
142
|
+
linkOrCopyFile(sourcePath, targetPath);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function buildManifestUrl({
|
|
149
|
+
relDir,
|
|
150
|
+
traceName,
|
|
151
|
+
applyBasePath,
|
|
152
|
+
}: {
|
|
153
|
+
relDir: string;
|
|
154
|
+
traceName: string;
|
|
155
|
+
applyBasePath: (subPath: string) => string;
|
|
156
|
+
}): string {
|
|
157
|
+
return applyBasePath(
|
|
158
|
+
normalizeOutputPath(
|
|
159
|
+
'/' +
|
|
160
|
+
[relDir, '_traces', traceName, 'manifest.json']
|
|
161
|
+
.filter(Boolean)
|
|
162
|
+
.join('/'),
|
|
163
|
+
),
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function renderTraceWidgetHtml({
|
|
168
|
+
highlightedSource,
|
|
169
|
+
manifestUrl,
|
|
170
|
+
totalSteps,
|
|
171
|
+
}: {
|
|
172
|
+
highlightedSource: string;
|
|
173
|
+
manifestUrl?: string;
|
|
174
|
+
totalSteps?: number;
|
|
175
|
+
}): string {
|
|
176
|
+
const svgAttrs = `aria-hidden='true' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'`;
|
|
177
|
+
const iconFirst = `<svg xmlns='http://www.w3.org/2000/svg' ${svgAttrs}><line x1='2' y1='6' x2='2' y2='18'/><polyline points='10 6 4 12 10 18'/><line x1='4' y1='12' x2='22' y2='12'/></svg>`;
|
|
178
|
+
const iconPrev = `<svg xmlns='http://www.w3.org/2000/svg' ${svgAttrs}><polyline points='10 6 4 12 10 18'/><line x1='4' y1='12' x2='22' y2='12'/></svg>`;
|
|
179
|
+
const iconNext = `<svg xmlns='http://www.w3.org/2000/svg' ${svgAttrs}><line x1='2' y1='12' x2='20' y2='12'/><polyline points='14 6 20 12 14 18'/></svg>`;
|
|
180
|
+
const iconLast = `<svg xmlns='http://www.w3.org/2000/svg' ${svgAttrs}><line x1='2' y1='12' x2='20' y2='12'/><polyline points='14 6 20 12 14 18'/><line x1='22' y1='6' x2='22' y2='18'/></svg>`;
|
|
181
|
+
const disabled = totalSteps === undefined;
|
|
182
|
+
const counterContent = disabled ? '--' : `1/${totalSteps}`;
|
|
183
|
+
const counterStyle = disabled
|
|
184
|
+
? ''
|
|
185
|
+
: ` style="min-width: ${String(totalSteps).length * 2 + 1}ch"`;
|
|
186
|
+
const controls =
|
|
187
|
+
`<button class="trace-btn trace-first" disabled tabindex="-1" aria-label="First step" title="First step">${iconFirst}</button>` +
|
|
188
|
+
`<button class="trace-btn trace-prev" disabled tabindex="-1" aria-label="Previous step" title="Previous step">${iconPrev}</button>` +
|
|
189
|
+
`<span class="trace-step-counter"${counterStyle}>${counterContent}</span>` +
|
|
190
|
+
(disabled
|
|
191
|
+
? `<button class="trace-btn trace-next" disabled tabindex="-1" aria-label="Next step" title="Next step">${iconNext}</button>`
|
|
192
|
+
: `<button class="trace-btn trace-next" aria-label="Next step" title="Next step">${iconNext}</button>`) +
|
|
193
|
+
(disabled
|
|
194
|
+
? `<button class="trace-btn trace-last" disabled tabindex="-1" aria-label="Last step" title="Last step">${iconLast}</button>`
|
|
195
|
+
: `<button class="trace-btn trace-last" tabindex="-1" aria-label="Last step" title="Last step">${iconLast}</button>`);
|
|
196
|
+
const wrapperAttrs = manifestUrl
|
|
197
|
+
? ` data-trace-manifest="${manifestUrl}"`
|
|
198
|
+
: '';
|
|
199
|
+
const wrapperClass = disabled
|
|
200
|
+
? 'trace-widget trace-disabled'
|
|
201
|
+
: 'trace-widget';
|
|
202
|
+
return `<div class="${wrapperClass}"${wrapperAttrs}><noscript><p>This interactive trace requires JavaScript.</p></noscript><div class="trace-body"><div class="trace-toolbar"><div class="trace-controls" role="toolbar" aria-label="Trace navigation">${controls}</div></div><div class="trace-content"><div class="trace-diagram"></div><div class="trace-source-wrapper"><div class="trace-source">${highlightedSource}</div></div><pre class="trace-output"></pre></div></div></div>`;
|
|
203
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { execFileSync } from 'child_process';
|
|
5
|
+
import { makeLogger } from '../log';
|
|
6
|
+
import { hasMainMethod, runJavac } from './literate-java';
|
|
7
|
+
|
|
8
|
+
const log = makeLogger(import.meta.url);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parse // @trace-ignore comments from Java source code.
|
|
12
|
+
* Returns a map of className -> fieldName[] for fields that should be
|
|
13
|
+
* excluded from layout consideration.
|
|
14
|
+
*/
|
|
15
|
+
export function parseIgnoreFields(source: string): Record<string, string[]> {
|
|
16
|
+
const result: Record<string, string[]> = {};
|
|
17
|
+
const lines = source.split('\n');
|
|
18
|
+
const classStack: { name: string; depth: number }[] = [];
|
|
19
|
+
let braceDepth = 0;
|
|
20
|
+
|
|
21
|
+
for (const line of lines) {
|
|
22
|
+
const stripped = line
|
|
23
|
+
.replace(/"(?:[^"\\]|\\.)*"/g, '')
|
|
24
|
+
.replace(/\/\/(?!.*@trace-ignore).*/g, '');
|
|
25
|
+
|
|
26
|
+
const classMatch = stripped.match(/\bclass\s+(\w+)/);
|
|
27
|
+
if (classMatch) {
|
|
28
|
+
classStack.push({ name: classMatch[1], depth: braceDepth });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const ch of stripped) {
|
|
32
|
+
if (ch === '{') {
|
|
33
|
+
braceDepth++;
|
|
34
|
+
}
|
|
35
|
+
if (ch === '}') {
|
|
36
|
+
braceDepth--;
|
|
37
|
+
while (
|
|
38
|
+
classStack.length > 0 &&
|
|
39
|
+
braceDepth <= classStack[classStack.length - 1].depth
|
|
40
|
+
) {
|
|
41
|
+
classStack.pop();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const currentClass =
|
|
47
|
+
classStack.length > 0 ? classStack.map(c => c.name).join('$') : null;
|
|
48
|
+
|
|
49
|
+
if (currentClass && line.includes('// @trace-ignore')) {
|
|
50
|
+
const before = line.split('//')[0].trim();
|
|
51
|
+
const withoutSemicolon = before.endsWith(';')
|
|
52
|
+
? before.slice(0, -1).trim()
|
|
53
|
+
: before;
|
|
54
|
+
const parts = withoutSemicolon.split(/\s+/);
|
|
55
|
+
if (parts.length >= 2) {
|
|
56
|
+
const fieldName = parts[parts.length - 1];
|
|
57
|
+
if (!result[currentClass]) {
|
|
58
|
+
result[currentClass] = [];
|
|
59
|
+
}
|
|
60
|
+
result[currentClass].push(fieldName);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let tracerClassDir: string | null = null;
|
|
69
|
+
|
|
70
|
+
function ensureTracerCompiled(): string {
|
|
71
|
+
if (tracerClassDir) {
|
|
72
|
+
return tracerClassDir;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const sourceDir = path.join(import.meta.dir, 'jdi-runner');
|
|
76
|
+
const sourceFile = path.join(sourceDir, 'TraceRunner.java');
|
|
77
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tada-tracer-'));
|
|
78
|
+
|
|
79
|
+
log.debug`Compiling TraceRunner.java into ${tempDir}`;
|
|
80
|
+
|
|
81
|
+
runJavac(['-d', tempDir, sourceFile], {
|
|
82
|
+
tempDir,
|
|
83
|
+
label: 'Failed to compile TraceRunner.java',
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
tracerClassDir = tempDir;
|
|
87
|
+
return tempDir;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function compileTargetFile(javaFilePath: string): string {
|
|
91
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tada-trace-target-'));
|
|
92
|
+
|
|
93
|
+
runJavac(
|
|
94
|
+
[
|
|
95
|
+
'-g',
|
|
96
|
+
'-d',
|
|
97
|
+
tempDir,
|
|
98
|
+
'-sourcepath',
|
|
99
|
+
path.dirname(javaFilePath),
|
|
100
|
+
javaFilePath,
|
|
101
|
+
],
|
|
102
|
+
{ tempDir, label: `Compilation failed for ${javaFilePath}` },
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return tempDir;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function validateJavaTraceTarget(
|
|
109
|
+
javaFilePath: string,
|
|
110
|
+
source: string,
|
|
111
|
+
): void {
|
|
112
|
+
if (!hasMainMethod(source)) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`${javaFilePath}: no main() method found (required for tracing)`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function runJavaTrace(javaFilePath: string, className: string): string {
|
|
120
|
+
const targetClassDir = compileTargetFile(javaFilePath);
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const tracerDir = ensureTracerCompiled();
|
|
124
|
+
return execFileSync(
|
|
125
|
+
'java',
|
|
126
|
+
['-cp', tracerDir, 'TraceRunner', className, targetClassDir],
|
|
127
|
+
{ timeout: 60000, encoding: 'utf-8', maxBuffer: 50 * 1024 * 1024 },
|
|
128
|
+
);
|
|
129
|
+
} finally {
|
|
130
|
+
fs.rmSync(targetClassDir, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -789,13 +789,41 @@ function layoutOrphans(
|
|
|
789
789
|
result: Record<string, TraceObjectLayout>,
|
|
790
790
|
yOffset: number,
|
|
791
791
|
) {
|
|
792
|
+
type OrphanSlot = { ids: string[]; lastStep: number; maxHeight: number };
|
|
793
|
+
|
|
794
|
+
const orphanIds = [...objects.keys()]
|
|
795
|
+
.filter(id => !(id in result))
|
|
796
|
+
.sort((a, b) => {
|
|
797
|
+
const aInfo = objects.get(a)!;
|
|
798
|
+
const bInfo = objects.get(b)!;
|
|
799
|
+
return (
|
|
800
|
+
aInfo.firstStep - bInfo.firstStep || aInfo.lastStep - bInfo.lastStep
|
|
801
|
+
);
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
const slots: OrphanSlot[] = [];
|
|
805
|
+
for (const id of orphanIds) {
|
|
806
|
+
const info = objects.get(id)!;
|
|
807
|
+
const size = sizes.get(id)!;
|
|
808
|
+
|
|
809
|
+
let slot = slots.find(existing => existing.lastStep < info.firstStep);
|
|
810
|
+
if (!slot) {
|
|
811
|
+
slot = { ids: [], lastStep: info.lastStep, maxHeight: size.height };
|
|
812
|
+
slots.push(slot);
|
|
813
|
+
} else {
|
|
814
|
+
slot.lastStep = info.lastStep;
|
|
815
|
+
slot.maxHeight = Math.max(slot.maxHeight, size.height);
|
|
816
|
+
}
|
|
817
|
+
slot.ids.push(id);
|
|
818
|
+
}
|
|
819
|
+
|
|
792
820
|
let y = yOffset;
|
|
793
|
-
for (const
|
|
794
|
-
|
|
821
|
+
for (const slot of slots) {
|
|
822
|
+
for (const id of slot.ids) {
|
|
795
823
|
const size = sizes.get(id)!;
|
|
796
824
|
result[id] = makeLayout(0, Math.round(y), size);
|
|
797
|
-
y += size.height + OBJ_GAP;
|
|
798
825
|
}
|
|
826
|
+
y += slot.maxHeight + OBJ_GAP;
|
|
799
827
|
}
|
|
800
828
|
}
|
|
801
829
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { execFileSyncPython, resolvePythonCommand } from '../../python/command';
|
|
3
|
+
|
|
4
|
+
export function runPythonTrace(pythonFilePath: string): string {
|
|
5
|
+
const pythonCommand = resolvePythonCommand();
|
|
6
|
+
if (!pythonCommand) {
|
|
7
|
+
throw new Error('Python is required to generate Python traces');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const runnerPath = path.join(
|
|
11
|
+
import.meta.dir,
|
|
12
|
+
'python-runner',
|
|
13
|
+
'trace_runner.py',
|
|
14
|
+
);
|
|
15
|
+
const options = {
|
|
16
|
+
timeout: 60000,
|
|
17
|
+
encoding: 'utf-8' as const,
|
|
18
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
return execFileSyncPython(
|
|
22
|
+
[runnerPath, pythonFilePath],
|
|
23
|
+
options,
|
|
24
|
+
pythonCommand,
|
|
25
|
+
);
|
|
26
|
+
}
|