@tsplc/debug 0.2.17 → 0.2.18

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/src/ui/tree.ts CHANGED
@@ -3,154 +3,186 @@ import { joinPath, pathSegments, typeOf } from "../paths.ts";
3
3
 
4
4
  /** One row of the instance tree: a root, a record field, an array element or a folded constant. */
5
5
  export interface TreeNode {
6
- path: string;
7
- /** The last segment: a field name, `[3]`, or the whole path for a top-level node. */
8
- name: string;
9
- type: string;
10
- declaration?: SourceRange;
11
- /** Whether the target adapter can read it at runtime. */
12
- readable: boolean;
13
- generated: boolean;
14
- constant?: DebugConstant;
6
+ path: string;
7
+ /** The last segment: a field name, `[3]`, or the whole path for a top-level node. */
8
+ name: string;
9
+ type: string;
10
+ declaration?: SourceRange;
11
+ /** Whether the target adapter can read it at runtime. */
12
+ readable: boolean;
13
+ generated: boolean;
14
+ constant?: DebugConstant;
15
15
  }
16
16
 
17
17
  export interface TreeRow {
18
- node: TreeNode;
19
- depth: number;
18
+ node: TreeNode;
19
+ depth: number;
20
20
  }
21
21
 
22
22
  export const parentPath = (path: string) =>
23
- path.slice(0, Math.max(path.lastIndexOf("."), path.lastIndexOf("[")));
23
+ path.slice(0, Math.max(path.lastIndexOf("."), path.lastIndexOf("[")));
24
24
 
25
25
  /** Every proper prefix of `path` at a `.` or `[` boundary, shortest first. */
26
26
  export const ancestorPaths = (path: string) =>
27
- [...path.matchAll(/[.[]/g)].map((match) => path.slice(0, match.index));
27
+ [...path.matchAll(/[.[]/g)].map((match) => path.slice(0, match.index));
28
28
 
29
29
  /** The manifest's instances as a lazily expanded tree, addressed by TypeScript path. */
30
30
  export class ManifestTree {
31
- readonly top: TreeNode[];
32
- private readonly constantsByParent = new Map<string, DebugConstant[]>();
33
- private readonly children = new Map<string, TreeNode[]>();
31
+ readonly top: TreeNode[];
32
+ private readonly constantsByParent = new Map<string, DebugConstant[]>();
33
+ private readonly children = new Map<string, TreeNode[]>();
34
34
 
35
- constructor(readonly manifest: DebugManifest) {
36
- const roots = manifest.roots.map((root) => ({
37
- path: root.path,
38
- name: root.path,
39
- type: root.type,
40
- declaration: root.declaration,
41
- readable: root.readable,
42
- generated: root.generated === true,
43
- }));
44
- for (const constant of manifest.constants) {
45
- const parent = parentPath(constant.path);
46
- this.constantsByParent.set(parent, [...(this.constantsByParent.get(parent) ?? []), constant]);
47
- }
48
- const orphans = manifest.constants.filter(
49
- (constant) => !this.resolveVariable(roots, parentPath(constant.path)),
50
- );
51
- this.top = [...roots, ...orphans.map((constant) => this.constantNode(constant, constant.path))];
52
- }
35
+ constructor(readonly manifest: DebugManifest) {
36
+ const roots = manifest.roots.map((root) => ({
37
+ path: root.path,
38
+ name: root.path,
39
+ type: root.type,
40
+ declaration: root.declaration,
41
+ readable: root.readable,
42
+ generated: root.generated === true,
43
+ }));
44
+ for (const constant of manifest.constants) {
45
+ const parent = parentPath(constant.path);
46
+ this.constantsByParent.set(parent, [
47
+ ...(this.constantsByParent.get(parent) ?? []),
48
+ constant,
49
+ ]);
50
+ }
51
+ const orphans = manifest.constants.filter(
52
+ (constant) => !this.resolveVariable(roots, parentPath(constant.path)),
53
+ );
54
+ this.top = [
55
+ ...roots,
56
+ ...orphans.map((constant) => this.constantNode(constant, constant.path)),
57
+ ];
58
+ }
53
59
 
54
- expandable(node: TreeNode) {
55
- if (node.constant) return false;
56
- const kind = typeOf(this.manifest, node.type).kind;
57
- return kind === "record" || kind === "array";
58
- }
60
+ expandable(node: TreeNode) {
61
+ if (node.constant) {
62
+ return false;
63
+ }
64
+ const kind = typeOf(this.manifest, node.type).kind;
65
+ return kind === "record" || kind === "array";
66
+ }
59
67
 
60
- childrenOf(node: TreeNode): TreeNode[] {
61
- const cached = this.children.get(node.path);
62
- if (cached) return cached;
63
- const type = typeOf(this.manifest, node.type);
64
- const variables: TreeNode[] =
65
- type.kind === "record"
66
- ? type.fields.map((field) => ({
67
- path: `${node.path}.${field.name}`,
68
- name: field.name,
69
- type: field.type,
70
- declaration: field.declaration,
71
- readable: node.readable,
72
- generated: node.generated || field.generated === true,
73
- }))
74
- : type.kind === "array"
75
- ? Array.from({ length: type.length }, (_, offset) => ({
76
- path: `${node.path}[${type.lower + offset}]`,
77
- name: `[${type.lower + offset}]`,
78
- type: type.element,
79
- declaration: node.declaration,
80
- readable: node.readable,
81
- generated: node.generated,
82
- }))
83
- : [];
84
- const constants = (this.constantsByParent.get(node.path) ?? []).map((constant) =>
85
- this.constantNode(constant, constant.path.slice(node.path.length + 1)),
86
- );
87
- const children = [...variables, ...constants];
88
- this.children.set(node.path, children);
89
- return children;
90
- }
68
+ childrenOf(node: TreeNode): TreeNode[] {
69
+ const cached = this.children.get(node.path);
70
+ if (cached) {
71
+ return cached;
72
+ }
73
+ const constants = (this.constantsByParent.get(node.path) ?? []).map(
74
+ (constant) =>
75
+ this.constantNode(constant, constant.path.slice(node.path.length + 1)),
76
+ );
77
+ const children = [...this.variablesOf(node), ...constants];
78
+ this.children.set(node.path, children);
79
+ return children;
80
+ }
91
81
 
92
- /** The rows shown for an expansion state, depth first. */
93
- rows(expanded: ReadonlySet<string>, showGenerated: boolean): TreeRow[] {
94
- const rows: TreeRow[] = [];
95
- const visit = (nodes: TreeNode[], depth: number) => {
96
- for (const node of nodes) {
97
- if (node.generated && !showGenerated) continue;
98
- rows.push({ node, depth });
99
- if (expanded.has(node.path) && this.expandable(node))
100
- visit(this.childrenOf(node), depth + 1);
101
- }
102
- };
103
- visit(this.top, 0);
104
- return rows;
105
- }
82
+ /** The rows shown for an expansion state, depth first. */
83
+ rows(expanded: ReadonlySet<string>, showGenerated: boolean): TreeRow[] {
84
+ const rows: TreeRow[] = [];
85
+ const visit = (nodes: TreeNode[], depth: number) => {
86
+ for (const node of nodes) {
87
+ if (node.generated && !showGenerated) {
88
+ continue;
89
+ }
90
+ rows.push({ node, depth });
91
+ if (expanded.has(node.path) && this.expandable(node)) {
92
+ visit(this.childrenOf(node), depth + 1);
93
+ }
94
+ }
95
+ };
96
+ visit(this.top, 0);
97
+ return rows;
98
+ }
106
99
 
107
- /** Nodes whose path contains `query` (case-insensitive), in tree order, at most `limit`. */
108
- search(query: string, showGenerated: boolean, limit: number) {
109
- const needle = query.toLowerCase();
110
- const found: TreeNode[] = [];
111
- let total = 0;
112
- const visit = (nodes: TreeNode[]) => {
113
- for (const node of nodes) {
114
- if (node.generated && !showGenerated) continue;
115
- if (node.path.toLowerCase().includes(needle) && total++ < limit) found.push(node);
116
- if (this.expandable(node)) visit(this.childrenOf(node));
117
- }
118
- };
119
- visit(this.top);
120
- return { nodes: found, total };
121
- }
100
+ /** Nodes whose path contains `query` (case-insensitive), in tree order, at most `limit`. */
101
+ search(query: string, showGenerated: boolean, limit: number) {
102
+ const needle = query.toLowerCase();
103
+ const found: TreeNode[] = [];
104
+ let total = 0;
105
+ const visit = (nodes: TreeNode[]) => {
106
+ for (const node of nodes) {
107
+ if (node.generated && !showGenerated) {
108
+ continue;
109
+ }
110
+ if (node.path.toLowerCase().includes(needle) && total++ < limit) {
111
+ found.push(node);
112
+ }
113
+ if (this.expandable(node)) {
114
+ visit(this.childrenOf(node));
115
+ }
116
+ }
117
+ };
118
+ visit(this.top);
119
+ return { nodes: found, total };
120
+ }
122
121
 
123
- resolve(path: string): TreeNode | undefined {
124
- const constant = this.manifest.constants.find((entry) => entry.path === path);
125
- if (constant) return this.constantNode(constant, path);
126
- return this.resolveVariable(this.top, path);
127
- }
122
+ resolve(path: string): TreeNode | undefined {
123
+ const constant = this.manifest.constants.find(
124
+ (entry) => entry.path === path,
125
+ );
126
+ if (constant) {
127
+ return this.constantNode(constant, path);
128
+ }
129
+ return this.resolveVariable(this.top, path);
130
+ }
128
131
 
129
- private resolveVariable(top: TreeNode[], path: string): TreeNode | undefined {
130
- let node = top.find(
131
- (root) =>
132
- path === root.path ||
133
- (path.startsWith(root.path) && /^[.[]/.test(path.slice(root.path.length))),
134
- );
135
- while (node && node.path !== path) {
136
- const rest = path.slice(node.path.length);
137
- const [segment] = pathSegments(rest);
138
- if (!segment) return undefined;
139
- const childPath = joinPath(node.path, segment);
140
- node = this.childrenOf(node).find((child) => child.path === childPath);
141
- }
142
- return node;
143
- }
132
+ private resolveVariable(top: TreeNode[], path: string): TreeNode | undefined {
133
+ let node = top.find(
134
+ (root) =>
135
+ path === root.path ||
136
+ (path.startsWith(root.path) &&
137
+ /^[.[]/.test(path.slice(root.path.length))),
138
+ );
139
+ while (node && node.path !== path) {
140
+ const rest = path.slice(node.path.length);
141
+ const [segment] = pathSegments(rest);
142
+ if (!segment) {
143
+ return undefined;
144
+ }
145
+ const childPath = joinPath(node.path, segment);
146
+ node = this.childrenOf(node).find((child) => child.path === childPath);
147
+ }
148
+ return node;
149
+ }
144
150
 
145
- private constantNode(constant: DebugConstant, name: string): TreeNode {
146
- return {
147
- path: constant.path,
148
- name,
149
- type: constant.type,
150
- declaration: constant.declaration,
151
- readable: false,
152
- generated: false,
153
- constant,
154
- };
155
- }
151
+ /** A record's fields or an array's elements, which share its readability and generated mark. */
152
+ private variablesOf(node: TreeNode): TreeNode[] {
153
+ const type = typeOf(this.manifest, node.type);
154
+ if (type.kind === "record") {
155
+ return type.fields.map((field) => ({
156
+ path: `${node.path}.${field.name}`,
157
+ name: field.name,
158
+ type: field.type,
159
+ declaration: field.declaration,
160
+ readable: node.readable,
161
+ generated: node.generated || field.generated === true,
162
+ }));
163
+ }
164
+ if (type.kind === "array") {
165
+ return Array.from({ length: type.length }, (_, offset) => ({
166
+ path: `${node.path}[${type.lower + offset}]`,
167
+ name: `[${type.lower + offset}]`,
168
+ type: type.element,
169
+ declaration: node.declaration,
170
+ readable: node.readable,
171
+ generated: node.generated,
172
+ }));
173
+ }
174
+ return [];
175
+ }
176
+
177
+ private constantNode(constant: DebugConstant, name: string): TreeNode {
178
+ return {
179
+ path: constant.path,
180
+ name,
181
+ type: constant.type,
182
+ declaration: constant.declaration,
183
+ readable: false,
184
+ generated: false,
185
+ constant,
186
+ };
187
+ }
156
188
  }