@memlab/cli 1.0.10 → 1.0.11
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/bin/memlab.js +1 -1
- package/dist/commands/heap/interactive/InteractiveHeapExploreCommand.d.ts +1 -2
- package/dist/commands/heap/interactive/InteractiveHeapExploreCommand.js +49 -25
- package/dist/commands/heap/interactive/ui-components/CliScreen.d.ts +5 -5
- package/dist/commands/heap/interactive/ui-components/CliScreen.js +45 -26
- package/dist/commands/heap/interactive/ui-components/HeapViewController.d.ts +21 -6
- package/dist/commands/heap/interactive/ui-components/HeapViewController.js +124 -17
- package/dist/commands/heap/interactive/ui-components/HeapViewUtils.d.ts +4 -1
- package/dist/commands/heap/interactive/ui-components/HeapViewUtils.js +123 -2
- package/dist/commands/heap/interactive/ui-components/ListComponent.d.ts +4 -1
- package/dist/commands/heap/interactive/ui-components/ListComponent.js +21 -4
- package/package.json +5 -5
package/bin/memlab.js
CHANGED
|
@@ -7,9 +7,8 @@
|
|
|
7
7
|
* @format
|
|
8
8
|
* @oncall ws_labs
|
|
9
9
|
*/
|
|
10
|
-
import type { CLIOptions } from '@memlab/core';
|
|
10
|
+
import type { BaseOption, CLIOptions } from '@memlab/core';
|
|
11
11
|
import BaseCommand, { CommandCategory } from '../../../BaseCommand';
|
|
12
|
-
import { BaseOption } from '@memlab/core';
|
|
13
12
|
export default class InteractiveHeapViewCommand extends BaseCommand {
|
|
14
13
|
getCommandName(): string;
|
|
15
14
|
getDescription(): string;
|
|
@@ -45,12 +45,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
45
45
|
};
|
|
46
46
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
47
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
48
|
-
const BaseCommand_1 = __importStar(require("../../../BaseCommand"));
|
|
49
48
|
const core_1 = require("@memlab/core");
|
|
49
|
+
const heap_analysis_1 = require("@memlab/heap-analysis");
|
|
50
|
+
const BaseCommand_1 = __importStar(require("../../../BaseCommand"));
|
|
50
51
|
const SnapshotFileOption_1 = __importDefault(require("../../../options/heap/SnapshotFileOption"));
|
|
51
52
|
const JSEngineOption_1 = __importDefault(require("../../../options/heap/JSEngineOption"));
|
|
52
|
-
const core_2 = require("@memlab/core");
|
|
53
|
-
const heap_analysis_1 = require("@memlab/heap-analysis");
|
|
54
53
|
const CliScreen_1 = __importDefault(require("./ui-components/CliScreen"));
|
|
55
54
|
const HeapNodeIdOption_1 = __importDefault(require("../../../options/HeapNodeIdOption"));
|
|
56
55
|
class InteractiveHeapViewCommand extends BaseCommand_1.default {
|
|
@@ -89,18 +88,34 @@ class InteractiveHeapViewCommand extends BaseCommand_1.default {
|
|
|
89
88
|
});
|
|
90
89
|
}
|
|
91
90
|
getNodesToFocus(heap) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
92
|
+
const ret = new Map();
|
|
93
|
+
const nodes = this.getNodesWithLargestRetainedSize(heap);
|
|
94
|
+
ret.set('large-object', nodes);
|
|
95
|
+
const detachedNodes = yield this.getDetachedNodes(heap);
|
|
96
|
+
ret.set('detached', detachedNodes);
|
|
97
|
+
return ret;
|
|
98
|
+
});
|
|
95
99
|
}
|
|
96
100
|
getDetachedNodes(heap) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
102
|
+
const ret = [];
|
|
103
|
+
const idSet = new Set();
|
|
104
|
+
heap.nodes.forEach(node => {
|
|
105
|
+
if (core_1.utils.isDetachedDOMNode(node) || core_1.utils.isDetachedFiberNode(node)) {
|
|
106
|
+
idSet.add(node.id);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
// get a minimal set of objects to represent all the detached DOM elements
|
|
110
|
+
const dominatorIds = core_1.utils.getConditionalDominatorIds(idSet, heap, () => true);
|
|
111
|
+
dominatorIds.forEach(id => {
|
|
112
|
+
const node = heap.getNodeById(id);
|
|
113
|
+
if (node) {
|
|
114
|
+
ret.push({ tag: 'Detached', heapObject: node });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
return ret;
|
|
102
118
|
});
|
|
103
|
-
return ret;
|
|
104
119
|
}
|
|
105
120
|
getNodesWithLargestRetainedSize(heap) {
|
|
106
121
|
const sizeThreshold = 2 * 1024 * 1024; // 2MB
|
|
@@ -117,27 +132,36 @@ class InteractiveHeapViewCommand extends BaseCommand_1.default {
|
|
|
117
132
|
}
|
|
118
133
|
// get heap node to focus on
|
|
119
134
|
getHeapNodes(heap) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
135
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
136
|
+
if (core_1.config.focusFiberNodeId >= 0) {
|
|
137
|
+
const node = heap.getNodeById(core_1.config.focusFiberNodeId);
|
|
138
|
+
if (node) {
|
|
139
|
+
const map = new Map();
|
|
140
|
+
map.set('Chosen', [
|
|
141
|
+
{
|
|
142
|
+
tag: 'Chosen',
|
|
143
|
+
heapObject: node,
|
|
144
|
+
},
|
|
145
|
+
]);
|
|
146
|
+
return map;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const category = yield this.getNodesToFocus(heap);
|
|
150
|
+
if (category.size === 0) {
|
|
151
|
+
throw core_1.utils.haltOrThrow('please specify a heap node ' +
|
|
152
|
+
`via --${new HeapNodeIdOption_1.default().getOptionName()}`);
|
|
124
153
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (nodes.length === 0) {
|
|
128
|
-
throw core_1.utils.haltOrThrow('please specify a heap node ' +
|
|
129
|
-
`via --${new HeapNodeIdOption_1.default().getOptionName()}`);
|
|
130
|
-
}
|
|
131
|
-
return nodes;
|
|
154
|
+
return category;
|
|
155
|
+
});
|
|
132
156
|
}
|
|
133
157
|
run(options) {
|
|
134
158
|
var _a;
|
|
135
159
|
return __awaiter(this, void 0, void 0, function* () {
|
|
136
160
|
const workDir = (_a = options.configFromOptions) === null || _a === void 0 ? void 0 : _a.workDir;
|
|
137
|
-
const reportOutDir =
|
|
161
|
+
const reportOutDir = core_1.fileManager.getReportOutDir({ workDir });
|
|
138
162
|
fs_extra_1.default.emptyDirSync(reportOutDir);
|
|
139
163
|
const heap = yield this.getHeap(options);
|
|
140
|
-
const nodes = this.getHeapNodes(heap);
|
|
164
|
+
const nodes = yield this.getHeapNodes(heap);
|
|
141
165
|
new CliScreen_1.default('memlab heap viewer', heap, nodes).start();
|
|
142
166
|
});
|
|
143
167
|
}
|
|
@@ -3,7 +3,7 @@ import { ComponentDataItem } from './HeapViewUtils';
|
|
|
3
3
|
export default class CliScreen {
|
|
4
4
|
private screen;
|
|
5
5
|
private objectBox;
|
|
6
|
-
private
|
|
6
|
+
private clusteredObjectBox;
|
|
7
7
|
private referrerBox;
|
|
8
8
|
private referenceBox;
|
|
9
9
|
private objectPropertyBox;
|
|
@@ -11,7 +11,8 @@ export default class CliScreen {
|
|
|
11
11
|
private currentFocuseKey;
|
|
12
12
|
private keyToComponent;
|
|
13
13
|
private heapController;
|
|
14
|
-
constructor(title: string, heap: IHeapSnapshot,
|
|
14
|
+
constructor(title: string, heap: IHeapSnapshot, objectCategory: Map<string, ComponentDataItem[]>);
|
|
15
|
+
private setFirstObjectAsCurrrent;
|
|
15
16
|
private initScreen;
|
|
16
17
|
private initCallbacks;
|
|
17
18
|
start(): void;
|
|
@@ -21,9 +22,8 @@ export default class CliScreen {
|
|
|
21
22
|
private registerKeys;
|
|
22
23
|
private addComponentToFocusKeyMap;
|
|
23
24
|
private getNextFocusKey;
|
|
24
|
-
private
|
|
25
|
-
private
|
|
26
|
-
private getParentObjectBoxSize;
|
|
25
|
+
private initClusteredObjectBox;
|
|
26
|
+
private getClusteredObjectBoxSize;
|
|
27
27
|
private initReferrerBox;
|
|
28
28
|
private getReferrerBoxSize;
|
|
29
29
|
private initObjectBox;
|
|
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const HeapViewUtils_1 = require("./HeapViewUtils");
|
|
7
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
8
7
|
const blessed_1 = __importDefault(require("blessed"));
|
|
9
8
|
const ListComponent_1 = __importDefault(require("./ListComponent"));
|
|
10
9
|
const HeapViewController_1 = __importDefault(require("./HeapViewController"));
|
|
@@ -16,7 +15,7 @@ function positionToNumber(info) {
|
|
|
16
15
|
* all the UI components in CLI.
|
|
17
16
|
*
|
|
18
17
|
* Screen Layout:
|
|
19
|
-
* ┌─Referrers
|
|
18
|
+
* ┌─Referrers ────────┐┌─Clustered Objects ┐┌─References ───────┐
|
|
20
19
|
* │ ││ ││ │
|
|
21
20
|
* │ ││ ││ │
|
|
22
21
|
* │ ││ ││ │
|
|
@@ -26,7 +25,7 @@ function positionToNumber(info) {
|
|
|
26
25
|
* │ ││ ││ │
|
|
27
26
|
* │ ││ ││ │
|
|
28
27
|
* └───────────────────┘│ ││ │
|
|
29
|
-
* ┌─
|
|
28
|
+
* ┌─Objects ──────────┐│ ││ │
|
|
30
29
|
* │ ││ ││ │
|
|
31
30
|
* │ │└───────────────────┘│ │
|
|
32
31
|
* │ │┌─Object Detail ────┐│ │
|
|
@@ -38,18 +37,18 @@ function positionToNumber(info) {
|
|
|
38
37
|
* └───────────────────┘└───────────────────┘└───────────────────┘
|
|
39
38
|
*/
|
|
40
39
|
class CliScreen {
|
|
41
|
-
constructor(title, heap,
|
|
40
|
+
constructor(title, heap, objectCategory) {
|
|
42
41
|
this.currentFocuseKey = 1;
|
|
43
|
-
this.heapController = new HeapViewController_1.default(heap,
|
|
42
|
+
this.heapController = new HeapViewController_1.default(heap, objectCategory);
|
|
44
43
|
this.screen = this.initScreen(title);
|
|
45
44
|
const callbacks = this.initCallbacks(this.heapController, this.screen);
|
|
46
45
|
this.keyToComponent = new Map();
|
|
47
46
|
this.referrerBox = this.initReferrerBox(callbacks);
|
|
48
47
|
this.heapController.setReferrerBox(this.referrerBox);
|
|
49
|
-
this.parentObjectBox = this.initParentObjectBox(callbacks);
|
|
50
|
-
this.heapController.setParentBox(this.parentObjectBox);
|
|
51
48
|
this.objectBox = this.initObjectBox(callbacks);
|
|
52
49
|
this.heapController.setObjectBox(this.objectBox);
|
|
50
|
+
this.clusteredObjectBox = this.initClusteredObjectBox(callbacks);
|
|
51
|
+
this.heapController.setClusteredBox(this.clusteredObjectBox);
|
|
53
52
|
this.referenceBox = this.initReferenceBox(callbacks);
|
|
54
53
|
this.heapController.setReferenceBox(this.referenceBox);
|
|
55
54
|
this.objectPropertyBox = this.initObjectPropertyBox(callbacks);
|
|
@@ -57,6 +56,17 @@ class CliScreen {
|
|
|
57
56
|
this.retainerTraceBox = this.initRetainerTraceBox(callbacks);
|
|
58
57
|
this.heapController.setRetainerTraceBox(this.retainerTraceBox);
|
|
59
58
|
this.registerEvents();
|
|
59
|
+
this.setFirstObjectAsCurrrent(objectCategory);
|
|
60
|
+
}
|
|
61
|
+
setFirstObjectAsCurrrent(objectCategory) {
|
|
62
|
+
const keys = Array.from(objectCategory.keys());
|
|
63
|
+
if (keys.length === 0) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const nodes = objectCategory.get(keys[0]);
|
|
67
|
+
if (!nodes || nodes.length === 0) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
60
70
|
this.heapController.setCurrentHeapObject((0, HeapViewUtils_1.getHeapObjectAt)(nodes, 0));
|
|
61
71
|
}
|
|
62
72
|
initScreen(title) {
|
|
@@ -97,7 +107,7 @@ class CliScreen {
|
|
|
97
107
|
const screen = this.screen;
|
|
98
108
|
screen.on('resize', () => {
|
|
99
109
|
// all boxes/lists needs to resize
|
|
100
|
-
this.updateComponentSize(this.
|
|
110
|
+
this.updateComponentSize(this.clusteredObjectBox, this.getClusteredObjectBoxSize());
|
|
101
111
|
this.updateComponentSize(this.referrerBox, this.getReferrerBoxSize());
|
|
102
112
|
this.updateComponentSize(this.objectBox, this.getObjectBoxSize());
|
|
103
113
|
this.updateComponentSize(this.objectPropertyBox, this.getObjectPropertyBoxSize());
|
|
@@ -138,26 +148,26 @@ class CliScreen {
|
|
|
138
148
|
getNextFocusKey() {
|
|
139
149
|
return `${this.currentFocuseKey}`;
|
|
140
150
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const box = new ListComponent_1.default([], callbacks, Object.assign(Object.assign({}, this.getParentObjectBoxSize()), { label: this.getLabel('Referrers of Current', this.getNextFocusKey()) }));
|
|
151
|
+
initClusteredObjectBox(callbacks) {
|
|
152
|
+
const box = new ListComponent_1.default([], callbacks, Object.assign({}, this.getClusteredObjectBoxSize()));
|
|
153
|
+
box.setFocusKey(this.getNextFocusKey());
|
|
154
|
+
box.setLabel('Clustered Objects');
|
|
146
155
|
this.screen.append(box.element);
|
|
147
156
|
this.addComponentToFocusKeyMap(box);
|
|
148
157
|
return box;
|
|
149
158
|
}
|
|
150
|
-
|
|
159
|
+
getClusteredObjectBoxSize() {
|
|
151
160
|
return {
|
|
152
161
|
width: Math.floor(positionToNumber(this.screen.width) / 3),
|
|
153
|
-
height: positionToNumber(this.screen.height)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
left: 0,
|
|
162
|
+
height: Math.floor((2 * positionToNumber(this.screen.height)) / 3),
|
|
163
|
+
top: 0,
|
|
164
|
+
left: Math.floor(positionToNumber(this.screen.width) / 3),
|
|
157
165
|
};
|
|
158
166
|
}
|
|
159
167
|
initReferrerBox(callbacks) {
|
|
160
|
-
const box = new ListComponent_1.default([], callbacks, Object.assign(
|
|
168
|
+
const box = new ListComponent_1.default([], callbacks, Object.assign({}, this.getReferrerBoxSize()));
|
|
169
|
+
box.setFocusKey(this.getNextFocusKey());
|
|
170
|
+
box.setLabel('Referrers');
|
|
161
171
|
this.screen.append(box.element);
|
|
162
172
|
this.addComponentToFocusKeyMap(box);
|
|
163
173
|
return box;
|
|
@@ -171,7 +181,9 @@ class CliScreen {
|
|
|
171
181
|
};
|
|
172
182
|
}
|
|
173
183
|
initObjectBox(callbacks) {
|
|
174
|
-
const box = new ListComponent_1.default([], callbacks, Object.assign(
|
|
184
|
+
const box = new ListComponent_1.default([], callbacks, Object.assign({}, this.getObjectBoxSize()));
|
|
185
|
+
box.setFocusKey(this.getNextFocusKey());
|
|
186
|
+
box.setLabel('Objects');
|
|
175
187
|
this.screen.append(box.element);
|
|
176
188
|
this.addComponentToFocusKeyMap(box);
|
|
177
189
|
return box;
|
|
@@ -179,13 +191,16 @@ class CliScreen {
|
|
|
179
191
|
getObjectBoxSize() {
|
|
180
192
|
return {
|
|
181
193
|
width: Math.floor(positionToNumber(this.screen.width) / 3),
|
|
182
|
-
height:
|
|
183
|
-
|
|
184
|
-
|
|
194
|
+
height: positionToNumber(this.screen.height) -
|
|
195
|
+
Math.floor(positionToNumber(this.screen.height) / 2),
|
|
196
|
+
top: Math.floor(positionToNumber(this.screen.height) / 2),
|
|
197
|
+
left: 0,
|
|
185
198
|
};
|
|
186
199
|
}
|
|
187
200
|
initObjectPropertyBox(callbacks) {
|
|
188
|
-
const box = new ListComponent_1.default([], callbacks, Object.assign(
|
|
201
|
+
const box = new ListComponent_1.default([], callbacks, Object.assign({}, this.getObjectPropertyBoxSize()));
|
|
202
|
+
box.setFocusKey(this.getNextFocusKey());
|
|
203
|
+
box.setLabel('Objects Detail');
|
|
189
204
|
this.screen.append(box.element);
|
|
190
205
|
this.addComponentToFocusKeyMap(box);
|
|
191
206
|
return box;
|
|
@@ -200,7 +215,9 @@ class CliScreen {
|
|
|
200
215
|
};
|
|
201
216
|
}
|
|
202
217
|
initReferenceBox(callbacks) {
|
|
203
|
-
const box = new ListComponent_1.default([], callbacks, Object.assign(
|
|
218
|
+
const box = new ListComponent_1.default([], callbacks, Object.assign({}, this.getReferenceBoxSize()));
|
|
219
|
+
box.setFocusKey(this.getNextFocusKey());
|
|
220
|
+
box.setLabel('References');
|
|
204
221
|
this.screen.append(box.element);
|
|
205
222
|
this.addComponentToFocusKeyMap(box);
|
|
206
223
|
return box;
|
|
@@ -215,7 +232,9 @@ class CliScreen {
|
|
|
215
232
|
};
|
|
216
233
|
}
|
|
217
234
|
initRetainerTraceBox(callbacks) {
|
|
218
|
-
const box = new ListComponent_1.default([], callbacks, Object.assign(
|
|
235
|
+
const box = new ListComponent_1.default([], callbacks, Object.assign({}, this.getRetainerTraceBoxSize()));
|
|
236
|
+
box.setFocusKey(this.getNextFocusKey());
|
|
237
|
+
box.setLabel('Retainer Trace');
|
|
219
238
|
this.screen.append(box.element);
|
|
220
239
|
this.addComponentToFocusKeyMap(box);
|
|
221
240
|
return box;
|
|
@@ -15,7 +15,9 @@ declare type SelectHeapObjectOption = {
|
|
|
15
15
|
noChangeInReferrerBox?: boolean;
|
|
16
16
|
noChangeInRetainerTraceBox?: boolean;
|
|
17
17
|
noChangeInObjectPropertyBox?: boolean;
|
|
18
|
+
componentDataItem?: ComponentDataItem;
|
|
18
19
|
};
|
|
20
|
+
export declare type ObjectCategory = Map<string, ComponentDataItem[]>;
|
|
19
21
|
/**
|
|
20
22
|
* HeapViewController managers all the data associated with each
|
|
21
23
|
* UI components in CLI and coordinates the events/interaction
|
|
@@ -25,18 +27,24 @@ export default class HeapViewController {
|
|
|
25
27
|
private currentHeapObject;
|
|
26
28
|
private selectedHeapObject;
|
|
27
29
|
private currentHeapObjectsInfo;
|
|
30
|
+
private currentClusteredObjectsInfo;
|
|
28
31
|
private componentIdToDataMap;
|
|
29
32
|
private componentIdToComponentMap;
|
|
30
33
|
private heap;
|
|
31
|
-
private
|
|
34
|
+
private clusteredBox;
|
|
32
35
|
private referrerBox;
|
|
33
36
|
private objectBox;
|
|
34
37
|
private referenceBox;
|
|
35
38
|
private objectPropertyBox;
|
|
36
39
|
private retainerTracePropertyBox;
|
|
37
|
-
constructor(heap: IHeapSnapshot,
|
|
40
|
+
constructor(heap: IHeapSnapshot, objectCategory: ObjectCategory);
|
|
41
|
+
private getFlattenHeapObjectsInfo;
|
|
42
|
+
private getFlattenClusteredObjectsInfo;
|
|
43
|
+
private shouldClusterCategory;
|
|
44
|
+
private clusterComponentDataItems;
|
|
38
45
|
getContent(componentId: number): string[];
|
|
39
|
-
|
|
46
|
+
setClusteredBox(component: ListComponent): void;
|
|
47
|
+
getClusteredBoxData(): ComponentData;
|
|
40
48
|
setReferrerBox(component: ListComponent): void;
|
|
41
49
|
getReferrerBoxData(node?: IHeapNode): ComponentData;
|
|
42
50
|
setObjectBox(component: ListComponent): void;
|
|
@@ -44,12 +52,19 @@ export default class HeapViewController {
|
|
|
44
52
|
setReferenceBox(component: ListComponent): void;
|
|
45
53
|
getReferenceBoxData(): ComponentData;
|
|
46
54
|
setObjectPropertyBox(component: ListComponent): void;
|
|
47
|
-
getObjectPropertyData(
|
|
55
|
+
getObjectPropertyData(options?: {
|
|
56
|
+
details?: Map<string, string>;
|
|
57
|
+
}): ComponentData;
|
|
58
|
+
private getReadableString;
|
|
48
59
|
private getKeyValuePairString;
|
|
49
60
|
setRetainerTraceBox(component: ListComponent): void;
|
|
50
61
|
getRetainerTraceData(): ComponentData;
|
|
51
|
-
setCurrentHeapObjectFromComponent(componentId: number, itemIndex: number
|
|
52
|
-
|
|
62
|
+
setCurrentHeapObjectFromComponent(componentId: number, itemIndex: number, options?: {
|
|
63
|
+
skipFocus?: boolean;
|
|
64
|
+
}): void;
|
|
65
|
+
setCurrentHeapObject(node: IHeapNode, options?: {
|
|
66
|
+
skipFocus?: boolean;
|
|
67
|
+
}): void;
|
|
53
68
|
focusOnComponent(componentId: number): void;
|
|
54
69
|
setSelectedHeapObjectFromComponent(componentId: number, itemIndex: number): void;
|
|
55
70
|
setSelectedHeapObject(node: IHeapNode, options?: SelectHeapObjectOption): void;
|
|
@@ -12,13 +12,60 @@ const HeapViewUtils_1 = require("./HeapViewUtils");
|
|
|
12
12
|
* among all UI components.
|
|
13
13
|
*/
|
|
14
14
|
class HeapViewController {
|
|
15
|
-
constructor(heap,
|
|
15
|
+
constructor(heap, objectCategory) {
|
|
16
16
|
this.heap = heap;
|
|
17
|
-
this.
|
|
18
|
-
|
|
17
|
+
this.currentHeapObjectsInfo =
|
|
18
|
+
this.getFlattenHeapObjectsInfo(objectCategory);
|
|
19
|
+
this.currentClusteredObjectsInfo =
|
|
20
|
+
this.getFlattenClusteredObjectsInfo(objectCategory);
|
|
21
|
+
this.currentHeapObject = (0, HeapViewUtils_1.getHeapObjectAt)(this.currentHeapObjectsInfo, 0);
|
|
19
22
|
this.componentIdToDataMap = new Map();
|
|
20
23
|
this.componentIdToComponentMap = new Map();
|
|
21
24
|
}
|
|
25
|
+
getFlattenHeapObjectsInfo(objectCategory) {
|
|
26
|
+
let ret = [];
|
|
27
|
+
for (const category of objectCategory.keys()) {
|
|
28
|
+
const nodes = objectCategory.get(category);
|
|
29
|
+
ret = [...ret, ...nodes];
|
|
30
|
+
}
|
|
31
|
+
return ret;
|
|
32
|
+
}
|
|
33
|
+
getFlattenClusteredObjectsInfo(objectCategory) {
|
|
34
|
+
let ret = [];
|
|
35
|
+
for (const category of objectCategory.keys()) {
|
|
36
|
+
let nodes = objectCategory.get(category);
|
|
37
|
+
if (this.shouldClusterCategory(category)) {
|
|
38
|
+
nodes = this.clusterComponentDataItems(nodes);
|
|
39
|
+
}
|
|
40
|
+
ret = [...ret, ...nodes];
|
|
41
|
+
}
|
|
42
|
+
return ret;
|
|
43
|
+
}
|
|
44
|
+
shouldClusterCategory(category) {
|
|
45
|
+
return category === 'detached';
|
|
46
|
+
}
|
|
47
|
+
clusterComponentDataItems(nodes) {
|
|
48
|
+
const ret = [];
|
|
49
|
+
const nodeIds = new Set(nodes
|
|
50
|
+
.filter(node => node.heapObject)
|
|
51
|
+
.map(node => { var _a, _b; return (_b = (_a = node.heapObject) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : -1; }));
|
|
52
|
+
const clusters = core_1.analysis.clusterHeapObjects(nodeIds, this.heap);
|
|
53
|
+
clusters.forEach(cluster => {
|
|
54
|
+
var _a;
|
|
55
|
+
const id = (_a = cluster.id) !== null && _a !== void 0 ? _a : -1;
|
|
56
|
+
let node = null;
|
|
57
|
+
node = this.heap.getNodeById(id);
|
|
58
|
+
const details = new Map();
|
|
59
|
+
if (cluster.count) {
|
|
60
|
+
details.set('# of clusters', `${cluster.count}`);
|
|
61
|
+
}
|
|
62
|
+
if (cluster.retainedSize) {
|
|
63
|
+
details.set('aggregated retained size', `${core_1.utils.getReadableBytes(cluster.retainedSize)}`);
|
|
64
|
+
}
|
|
65
|
+
ret.push({ tag: 'Cluster', heapObject: node, details });
|
|
66
|
+
});
|
|
67
|
+
return ret;
|
|
68
|
+
}
|
|
22
69
|
getContent(componentId) {
|
|
23
70
|
const ret = [];
|
|
24
71
|
const data = this.componentIdToDataMap.get(componentId);
|
|
@@ -29,11 +76,17 @@ class HeapViewController {
|
|
|
29
76
|
}
|
|
30
77
|
return ret;
|
|
31
78
|
}
|
|
32
|
-
|
|
79
|
+
setClusteredBox(component) {
|
|
33
80
|
this.componentIdToComponentMap.set(component.id, component);
|
|
34
|
-
this.
|
|
81
|
+
this.clusteredBox = component;
|
|
35
82
|
this.componentIdToDataMap.set(component.id, new HeapViewUtils_1.ComponentData());
|
|
36
83
|
}
|
|
84
|
+
getClusteredBoxData() {
|
|
85
|
+
const data = new HeapViewUtils_1.ComponentData();
|
|
86
|
+
data.selectedIdx = 0;
|
|
87
|
+
data.items = this.currentClusteredObjectsInfo;
|
|
88
|
+
return data;
|
|
89
|
+
}
|
|
37
90
|
setReferrerBox(component) {
|
|
38
91
|
this.componentIdToComponentMap.set(component.id, component);
|
|
39
92
|
this.referrerBox = component;
|
|
@@ -82,6 +135,7 @@ class HeapViewController {
|
|
|
82
135
|
data.items.push({ referrerEdge: ref, heapObject: ref.toNode });
|
|
83
136
|
return { stop: false };
|
|
84
137
|
});
|
|
138
|
+
data.items.sort((i1, i2) => { var _a, _b, _c, _d; return ((_b = (_a = i2.heapObject) === null || _a === void 0 ? void 0 : _a.retainedSize) !== null && _b !== void 0 ? _b : 0) - ((_d = (_c = i1.heapObject) === null || _c === void 0 ? void 0 : _c.retainedSize) !== null && _d !== void 0 ? _d : 0); });
|
|
85
139
|
data.selectedIdx = data.items.length > 0 ? 0 : -1;
|
|
86
140
|
return data;
|
|
87
141
|
}
|
|
@@ -90,7 +144,8 @@ class HeapViewController {
|
|
|
90
144
|
this.objectPropertyBox = component;
|
|
91
145
|
this.componentIdToDataMap.set(component.id, new HeapViewUtils_1.ComponentData());
|
|
92
146
|
}
|
|
93
|
-
getObjectPropertyData() {
|
|
147
|
+
getObjectPropertyData(options = {}) {
|
|
148
|
+
var _a, _b;
|
|
94
149
|
const data = new HeapViewUtils_1.ComponentData();
|
|
95
150
|
const node = this.selectedHeapObject;
|
|
96
151
|
data.items.push({
|
|
@@ -109,10 +164,10 @@ class HeapViewController {
|
|
|
109
164
|
stringContent: this.getKeyValuePairString('retained size', core_1.utils.getReadableBytes(node.retainedSize)),
|
|
110
165
|
});
|
|
111
166
|
data.items.push({
|
|
112
|
-
stringContent: this.getKeyValuePairString('# of references',
|
|
167
|
+
stringContent: this.getKeyValuePairString('# of references', node.edge_count),
|
|
113
168
|
});
|
|
114
169
|
data.items.push({
|
|
115
|
-
stringContent: this.getKeyValuePairString('# of referrers',
|
|
170
|
+
stringContent: this.getKeyValuePairString('# of referrers', node.referrers.length),
|
|
116
171
|
});
|
|
117
172
|
if (node.dominatorNode) {
|
|
118
173
|
data.items.push({
|
|
@@ -120,9 +175,46 @@ class HeapViewController {
|
|
|
120
175
|
heapObject: node.dominatorNode,
|
|
121
176
|
});
|
|
122
177
|
}
|
|
178
|
+
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
179
|
+
const self = this;
|
|
180
|
+
(_a = options.details) === null || _a === void 0 ? void 0 : _a.forEach((value, key) => data.items.push({
|
|
181
|
+
stringContent: self.getKeyValuePairString(key, value),
|
|
182
|
+
}));
|
|
183
|
+
// inject additional node information
|
|
184
|
+
if (node.isString) {
|
|
185
|
+
const stringNode = node.toStringNode();
|
|
186
|
+
if (stringNode) {
|
|
187
|
+
const value = this.getReadableString(stringNode.stringValue);
|
|
188
|
+
data.items.push({
|
|
189
|
+
stringContent: this.getKeyValuePairString('string value', value),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (node.type === 'number') {
|
|
194
|
+
data.items.push({
|
|
195
|
+
stringContent: this.getKeyValuePairString('numeric value', (_b = core_1.utils.getNumberNodeValue(node)) !== null && _b !== void 0 ? _b : '<error>'),
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (node.type === 'closure') {
|
|
199
|
+
const contextNode = node.getReferenceNode('context', 'internal');
|
|
200
|
+
if (contextNode) {
|
|
201
|
+
contextNode.forEachReference(edge => {
|
|
202
|
+
data.items.push({
|
|
203
|
+
tag: chalk_1.default.grey('Scope Variable'),
|
|
204
|
+
referrerEdge: edge,
|
|
205
|
+
heapObject: edge.toNode,
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
123
210
|
data.selectedIdx = data.items.length > 0 ? 0 : -1;
|
|
124
211
|
return data;
|
|
125
212
|
}
|
|
213
|
+
getReadableString(value) {
|
|
214
|
+
return value.length > 300
|
|
215
|
+
? value.substring(0, 300) + chalk_1.default.grey('...')
|
|
216
|
+
: value;
|
|
217
|
+
}
|
|
126
218
|
getKeyValuePairString(key, value) {
|
|
127
219
|
return key + chalk_1.default.grey(': ') + chalk_1.default.green(value);
|
|
128
220
|
}
|
|
@@ -150,7 +242,7 @@ class HeapViewController {
|
|
|
150
242
|
data.selectedIdx = data.items.length > 0 ? 0 : -1;
|
|
151
243
|
return data;
|
|
152
244
|
}
|
|
153
|
-
setCurrentHeapObjectFromComponent(componentId, itemIndex) {
|
|
245
|
+
setCurrentHeapObjectFromComponent(componentId, itemIndex, options = {}) {
|
|
154
246
|
const data = this.componentIdToDataMap.get(componentId);
|
|
155
247
|
if (!data) {
|
|
156
248
|
return;
|
|
@@ -163,27 +255,33 @@ class HeapViewController {
|
|
|
163
255
|
if (!heapObject) {
|
|
164
256
|
return;
|
|
165
257
|
}
|
|
166
|
-
this.setCurrentHeapObject(heapObject);
|
|
258
|
+
this.setCurrentHeapObject(heapObject, options);
|
|
167
259
|
}
|
|
168
|
-
setCurrentHeapObject(node) {
|
|
260
|
+
setCurrentHeapObject(node, options = {}) {
|
|
169
261
|
this.currentHeapObject = node;
|
|
170
262
|
// set parent box's data and content
|
|
171
|
-
const
|
|
172
|
-
this.componentIdToDataMap.set(this.
|
|
173
|
-
this.
|
|
174
|
-
this.
|
|
263
|
+
const clusteredBoxData = this.getClusteredBoxData();
|
|
264
|
+
this.componentIdToDataMap.set(this.clusteredBox.id, clusteredBoxData);
|
|
265
|
+
this.clusteredBox.setContent(this.getContent(this.clusteredBox.id));
|
|
266
|
+
this.clusteredBox.selectIndex(clusteredBoxData.selectedIdx);
|
|
175
267
|
// set object box's data and content
|
|
176
268
|
const objectBoxData = this.getObjectBoxData();
|
|
177
269
|
this.componentIdToDataMap.set(this.objectBox.id, objectBoxData);
|
|
178
270
|
this.objectBox.setContent(this.getContent(this.objectBox.id));
|
|
179
271
|
this.objectBox.selectIndex(objectBoxData.selectedIdx);
|
|
180
272
|
this.setSelectedHeapObject(node);
|
|
181
|
-
|
|
273
|
+
if (!options.skipFocus) {
|
|
274
|
+
this.focusOnComponent(this.objectBox.id);
|
|
275
|
+
}
|
|
182
276
|
}
|
|
183
277
|
focusOnComponent(componentId) {
|
|
278
|
+
var _a;
|
|
184
279
|
for (const component of this.componentIdToComponentMap.values()) {
|
|
185
280
|
if (component.id === componentId) {
|
|
186
281
|
component.focus();
|
|
282
|
+
const data = this.componentIdToDataMap.get(componentId);
|
|
283
|
+
const selectIndex = (_a = (data && data.selectedIdx)) !== null && _a !== void 0 ? _a : -1;
|
|
284
|
+
this.setSelectedHeapObjectFromComponent(componentId, selectIndex);
|
|
187
285
|
}
|
|
188
286
|
else {
|
|
189
287
|
component.loseFocus();
|
|
@@ -214,9 +312,11 @@ class HeapViewController {
|
|
|
214
312
|
noChangeInReferrerBox,
|
|
215
313
|
noChangeInRetainerTraceBox,
|
|
216
314
|
noChangeInObjectPropertyBox,
|
|
315
|
+
componentDataItem: item,
|
|
217
316
|
});
|
|
218
317
|
}
|
|
219
318
|
setSelectedHeapObject(node, options = {}) {
|
|
319
|
+
var _a;
|
|
220
320
|
this.selectedHeapObject = node;
|
|
221
321
|
// set referrer box's data and content
|
|
222
322
|
if (!options.noChangeInReferrerBox) {
|
|
@@ -224,6 +324,7 @@ class HeapViewController {
|
|
|
224
324
|
this.componentIdToDataMap.set(this.referrerBox.id, data);
|
|
225
325
|
this.referrerBox.setContent(this.getContent(this.referrerBox.id));
|
|
226
326
|
this.referrerBox.selectIndex(data.selectedIdx);
|
|
327
|
+
this.referrerBox.setLabel(`Referrers of @${node.id}`);
|
|
227
328
|
}
|
|
228
329
|
// set reference box's data and content
|
|
229
330
|
if (!options.noChangeInReferenceBox) {
|
|
@@ -231,13 +332,18 @@ class HeapViewController {
|
|
|
231
332
|
this.componentIdToDataMap.set(this.referenceBox.id, data);
|
|
232
333
|
this.referenceBox.setContent(this.getContent(this.referenceBox.id));
|
|
233
334
|
this.referenceBox.selectIndex(data.selectedIdx);
|
|
335
|
+
this.referenceBox.setLabel(`References of @${node.id}`);
|
|
234
336
|
}
|
|
235
337
|
// set object property box's data and content
|
|
236
338
|
if (!options.noChangeInObjectPropertyBox) {
|
|
237
|
-
const
|
|
339
|
+
const propertyOption = ((_a = options === null || options === void 0 ? void 0 : options.componentDataItem) === null || _a === void 0 ? void 0 : _a.details)
|
|
340
|
+
? { details: options.componentDataItem.details }
|
|
341
|
+
: {};
|
|
342
|
+
const data = this.getObjectPropertyData(propertyOption);
|
|
238
343
|
this.componentIdToDataMap.set(this.objectPropertyBox.id, data);
|
|
239
344
|
this.objectPropertyBox.setContent(this.getContent(this.objectPropertyBox.id));
|
|
240
345
|
this.objectPropertyBox.selectIndex(data.selectedIdx);
|
|
346
|
+
this.objectPropertyBox.setLabel(`Object: @${node.id}`);
|
|
241
347
|
}
|
|
242
348
|
// set retainer trace box's data and content
|
|
243
349
|
if (!options.noChangeInRetainerTraceBox) {
|
|
@@ -245,6 +351,7 @@ class HeapViewController {
|
|
|
245
351
|
this.componentIdToDataMap.set(this.retainerTracePropertyBox.id, data);
|
|
246
352
|
this.retainerTracePropertyBox.setContent(this.getContent(this.retainerTracePropertyBox.id));
|
|
247
353
|
this.retainerTracePropertyBox.selectIndex(data.selectedIdx);
|
|
354
|
+
this.retainerTracePropertyBox.setLabel(`Retainer Trace of @${node.id}`);
|
|
248
355
|
}
|
|
249
356
|
}
|
|
250
357
|
}
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*
|
|
7
|
-
* @emails oncall+ws_labs
|
|
8
7
|
* @format
|
|
8
|
+
* @oncall ws_labs
|
|
9
9
|
*/
|
|
10
10
|
import type { IHeapEdge, IHeapNode } from '@memlab/core';
|
|
11
11
|
export declare class ComponentDataItem {
|
|
@@ -15,7 +15,9 @@ export declare class ComponentDataItem {
|
|
|
15
15
|
heapObject?: IHeapNode;
|
|
16
16
|
referenceEdge?: IHeapEdge;
|
|
17
17
|
type?: string;
|
|
18
|
+
details?: Map<string, string>;
|
|
18
19
|
static getTextForDisplay(data: ComponentDataItem): string;
|
|
20
|
+
private static getTextContent;
|
|
19
21
|
}
|
|
20
22
|
export declare class ComponentData {
|
|
21
23
|
selectedIdx: number;
|
|
@@ -23,6 +25,7 @@ export declare class ComponentData {
|
|
|
23
25
|
}
|
|
24
26
|
export declare function throwIfNodesEmpty(nodes: ComponentDataItem[]): boolean;
|
|
25
27
|
export declare function getHeapObjectAt(nodes: ComponentDataItem[], index: number): IHeapNode;
|
|
28
|
+
export declare function substringWithColor(input: string, begin: number): string;
|
|
26
29
|
export declare type DebounceCallback = () => void;
|
|
27
30
|
export declare type DebounceFunction = (callback: DebounceCallback) => void;
|
|
28
31
|
export declare function debounce(timeInMs: number): DebounceFunction;
|
|
@@ -3,11 +3,70 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.debounce = exports.getHeapObjectAt = exports.throwIfNodesEmpty = exports.ComponentData = exports.ComponentDataItem = void 0;
|
|
6
|
+
exports.debounce = exports.substringWithColor = exports.getHeapObjectAt = exports.throwIfNodesEmpty = exports.ComponentData = exports.ComponentDataItem = void 0;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const core_1 = require("@memlab/core");
|
|
9
|
+
const lessUsefulEdgeTypeForDebugging = new Set([
|
|
10
|
+
'internal',
|
|
11
|
+
'hidden',
|
|
12
|
+
'shortcut',
|
|
13
|
+
'weak',
|
|
14
|
+
]);
|
|
15
|
+
const reactEdgeNames = new Set([
|
|
16
|
+
'alternate',
|
|
17
|
+
'firstEffect',
|
|
18
|
+
'lastEffect',
|
|
19
|
+
'concurrentQueues',
|
|
20
|
+
'child',
|
|
21
|
+
'return',
|
|
22
|
+
'sibling',
|
|
23
|
+
]);
|
|
24
|
+
function isUsefulEdgeForDebugging(edge) {
|
|
25
|
+
if (lessUsefulEdgeTypeForDebugging.has(edge.type)) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const edgeStr = `${edge.name_or_index}`;
|
|
29
|
+
if (reactEdgeNames.has(edgeStr)) {
|
|
30
|
+
if (core_1.utils.isFiberNode(edge.fromNode) || core_1.utils.isFiberNode(edge.toNode)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (edgeStr.startsWith('__reactProps$')) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
const lessUsefulObjectTypeForDebugging = new Set([
|
|
40
|
+
'native',
|
|
41
|
+
'hidden',
|
|
42
|
+
'array',
|
|
43
|
+
'code',
|
|
44
|
+
'synthetic',
|
|
45
|
+
]);
|
|
46
|
+
function isUsefulObjectForDebugging(object) {
|
|
47
|
+
if (lessUsefulObjectTypeForDebugging.has(object.type)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
return !core_1.utils.isFiberNode(object);
|
|
51
|
+
}
|
|
9
52
|
class ComponentDataItem {
|
|
10
53
|
static getTextForDisplay(data) {
|
|
54
|
+
const content = ComponentDataItem.getTextContent(data);
|
|
55
|
+
if (data.referrerEdge && isUsefulEdgeForDebugging(data.referrerEdge)) {
|
|
56
|
+
return content;
|
|
57
|
+
}
|
|
58
|
+
if (data.referenceEdge && isUsefulEdgeForDebugging(data.referenceEdge)) {
|
|
59
|
+
return content;
|
|
60
|
+
}
|
|
61
|
+
if (data.heapObject && isUsefulObjectForDebugging(data.heapObject)) {
|
|
62
|
+
return content;
|
|
63
|
+
}
|
|
64
|
+
if (!data.referenceEdge && !data.heapObject && !data.referrerEdge) {
|
|
65
|
+
return content;
|
|
66
|
+
}
|
|
67
|
+
return chalk_1.default.grey(content);
|
|
68
|
+
}
|
|
69
|
+
static getTextContent(data) {
|
|
11
70
|
let ret = '';
|
|
12
71
|
if (data.tag) {
|
|
13
72
|
ret += `[${data.tag}] `;
|
|
@@ -28,7 +87,11 @@ class ComponentDataItem {
|
|
|
28
87
|
const size = core_1.utils.getReadableBytes(data.heapObject.retainedSize);
|
|
29
88
|
const sizeInfo = chalk_1.default.grey(' [') + chalk_1.default.bold(chalk_1.default.blue(size)) + chalk_1.default.grey(']');
|
|
30
89
|
ret +=
|
|
31
|
-
chalk_1.default.green(
|
|
90
|
+
chalk_1.default.green('[') +
|
|
91
|
+
(isUsefulObjectForDebugging(data.heapObject)
|
|
92
|
+
? chalk_1.default.green(data.heapObject.name)
|
|
93
|
+
: chalk_1.default.bold(chalk_1.default.grey(data.heapObject.name))) +
|
|
94
|
+
chalk_1.default.green(']') +
|
|
32
95
|
objectType +
|
|
33
96
|
objectId +
|
|
34
97
|
sizeInfo;
|
|
@@ -69,6 +132,64 @@ function getHeapObjectAt(nodes, index) {
|
|
|
69
132
|
return nodes[index].heapObject;
|
|
70
133
|
}
|
|
71
134
|
exports.getHeapObjectAt = getHeapObjectAt;
|
|
135
|
+
// eslint-disable-next-line no-control-regex
|
|
136
|
+
const colorBegin = /^\u001b\[(\d+)m/;
|
|
137
|
+
// eslint-disable-next-line no-control-regex
|
|
138
|
+
const colorEnd = /^\u001b\](\d+)m/;
|
|
139
|
+
function stripColorCodeIfAny(input) {
|
|
140
|
+
const matchBegin = input.match(colorBegin);
|
|
141
|
+
const matchEnd = input.match(colorEnd);
|
|
142
|
+
const match = matchBegin || matchEnd;
|
|
143
|
+
if (!match) {
|
|
144
|
+
return { str: input, code: -1, isBegin: false };
|
|
145
|
+
}
|
|
146
|
+
const isBegin = !!matchBegin;
|
|
147
|
+
const code = parseInt(match[1], 10);
|
|
148
|
+
const str = input.substring(match[0].length);
|
|
149
|
+
return { str, code, isBegin };
|
|
150
|
+
}
|
|
151
|
+
function toColorControlChar(code, isBegin) {
|
|
152
|
+
const colorSpecialChar = '\u001b';
|
|
153
|
+
return colorSpecialChar + (isBegin ? '[' : ']') + code + 'm';
|
|
154
|
+
}
|
|
155
|
+
function substringWithColor(input, begin) {
|
|
156
|
+
const codeQueue = [];
|
|
157
|
+
let curIndex = 0;
|
|
158
|
+
let curStr = input;
|
|
159
|
+
while (curIndex < begin) {
|
|
160
|
+
// enqueue all control characters
|
|
161
|
+
let strip;
|
|
162
|
+
do {
|
|
163
|
+
strip = stripColorCodeIfAny(curStr);
|
|
164
|
+
curStr = strip.str;
|
|
165
|
+
if (strip.code >= 0) {
|
|
166
|
+
// pop if control begin meets control ends
|
|
167
|
+
const last = codeQueue[codeQueue.length - 1];
|
|
168
|
+
if (!last ||
|
|
169
|
+
last.code !== strip.code ||
|
|
170
|
+
strip.isBegin === true ||
|
|
171
|
+
last.isBegin === false) {
|
|
172
|
+
codeQueue.push({ code: strip.code, isBegin: strip.isBegin });
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
codeQueue.pop();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} while (strip.code >= 0);
|
|
179
|
+
// strip one actual content character
|
|
180
|
+
curStr = curStr.substring(1);
|
|
181
|
+
++curIndex;
|
|
182
|
+
}
|
|
183
|
+
// prepend control characters
|
|
184
|
+
while (codeQueue.length > 0) {
|
|
185
|
+
const last = codeQueue.pop();
|
|
186
|
+
if (last) {
|
|
187
|
+
curStr = toColorControlChar(last === null || last === void 0 ? void 0 : last.code, last === null || last === void 0 ? void 0 : last.isBegin) + curStr;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return curStr;
|
|
191
|
+
}
|
|
192
|
+
exports.substringWithColor = substringWithColor;
|
|
72
193
|
function debounce(timeInMs) {
|
|
73
194
|
let id = null;
|
|
74
195
|
return (callback) => {
|
|
@@ -13,7 +13,7 @@ export declare type ListComponentOption = {
|
|
|
13
13
|
height: number;
|
|
14
14
|
left: number;
|
|
15
15
|
top: number;
|
|
16
|
-
label
|
|
16
|
+
label?: string;
|
|
17
17
|
};
|
|
18
18
|
export declare type ListItemSelectInfo = {
|
|
19
19
|
keyName: string;
|
|
@@ -38,6 +38,7 @@ export default class ListComponent {
|
|
|
38
38
|
private horizonScrollPositionMap;
|
|
39
39
|
private displayedItems;
|
|
40
40
|
private moreEntryIndex;
|
|
41
|
+
private focusKey;
|
|
41
42
|
private static readonly ListContentLimit;
|
|
42
43
|
private static readonly loadMore;
|
|
43
44
|
private static nextComponentId;
|
|
@@ -51,6 +52,8 @@ export default class ListComponent {
|
|
|
51
52
|
focus(): void;
|
|
52
53
|
loseFocus(): void;
|
|
53
54
|
selectIndex(index: number): void;
|
|
55
|
+
setFocusKey(key: string): void;
|
|
56
|
+
setLabel(label: string): void;
|
|
54
57
|
setContent(content: string[]): void;
|
|
55
58
|
loadMoreContent(): void;
|
|
56
59
|
private removeDisplayMoreEntry;
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const blessed_1 = __importDefault(require("blessed"));
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const string_width_1 = __importDefault(require("string-width"));
|
|
9
|
+
const HeapViewUtils_1 = require("./HeapViewUtils");
|
|
9
10
|
/**
|
|
10
11
|
* A ListComponent is an UI list component in CLI.
|
|
11
12
|
* It managers all the UI events related to the
|
|
@@ -16,11 +17,12 @@ class ListComponent {
|
|
|
16
17
|
this.listIndex = 0;
|
|
17
18
|
this.content = [];
|
|
18
19
|
this.moreEntryIndex = -1;
|
|
20
|
+
this.focusKey = '';
|
|
19
21
|
this.id = ListComponent.nextId();
|
|
20
22
|
this.horizonScrollPositionMap = new Map();
|
|
21
23
|
this.callbacks = callbacks;
|
|
22
24
|
// init list element
|
|
23
|
-
this.element = blessed_1.default.list(Object.assign(Object.assign({}, options), { tags:
|
|
25
|
+
this.element = blessed_1.default.list(Object.assign(Object.assign({}, options), { tags: false, scrollable: true, keys: true, border: {
|
|
24
26
|
fg: 'grey',
|
|
25
27
|
type: 'line',
|
|
26
28
|
}, style: {
|
|
@@ -106,7 +108,7 @@ class ListComponent {
|
|
|
106
108
|
return;
|
|
107
109
|
}
|
|
108
110
|
this.horizonScrollPositionMap.set(this.listIndex, --offset);
|
|
109
|
-
let newContent =
|
|
111
|
+
let newContent = (0, HeapViewUtils_1.substringWithColor)(selectedContent, offset);
|
|
110
112
|
if (offset > 0) {
|
|
111
113
|
newContent = chalk_1.default.grey('...') + newContent;
|
|
112
114
|
}
|
|
@@ -124,7 +126,7 @@ class ListComponent {
|
|
|
124
126
|
offset = (_a = this.horizonScrollPositionMap.get(this.listIndex)) !== null && _a !== void 0 ? _a : 0;
|
|
125
127
|
}
|
|
126
128
|
this.horizonScrollPositionMap.set(this.listIndex, ++offset);
|
|
127
|
-
let newContent =
|
|
129
|
+
let newContent = (0, HeapViewUtils_1.substringWithColor)(selectedContent, offset);
|
|
128
130
|
if (offset > 0) {
|
|
129
131
|
newContent = chalk_1.default.grey('...') + newContent;
|
|
130
132
|
}
|
|
@@ -134,10 +136,18 @@ class ListComponent {
|
|
|
134
136
|
focus() {
|
|
135
137
|
this.element.focus();
|
|
136
138
|
this.element.style.border.fg = 'white';
|
|
139
|
+
this.element.style.selected = {
|
|
140
|
+
bg: 'grey',
|
|
141
|
+
bold: true,
|
|
142
|
+
};
|
|
137
143
|
this.getFocus();
|
|
138
144
|
}
|
|
139
145
|
loseFocus() {
|
|
140
146
|
this.element.style.border.fg = 'grey';
|
|
147
|
+
this.element.style.selected = {
|
|
148
|
+
bg: 'black',
|
|
149
|
+
bold: false,
|
|
150
|
+
};
|
|
141
151
|
}
|
|
142
152
|
selectIndex(index) {
|
|
143
153
|
while (this.displayedItems <= index &&
|
|
@@ -147,6 +157,13 @@ class ListComponent {
|
|
|
147
157
|
this.listIndex = index;
|
|
148
158
|
this.element.select(index);
|
|
149
159
|
}
|
|
160
|
+
setFocusKey(key) {
|
|
161
|
+
this.focusKey = key;
|
|
162
|
+
}
|
|
163
|
+
setLabel(label) {
|
|
164
|
+
const componentLabel = label + chalk_1.default.grey(` (press ${chalk_1.default.inverse(this.focusKey)} to focus)`);
|
|
165
|
+
this.element.setLabel(componentLabel);
|
|
166
|
+
}
|
|
150
167
|
setContent(content) {
|
|
151
168
|
const oldContent = this.content;
|
|
152
169
|
this.element.clearItems();
|
|
@@ -216,5 +233,5 @@ class ListComponent {
|
|
|
216
233
|
}
|
|
217
234
|
exports.default = ListComponent;
|
|
218
235
|
ListComponent.ListContentLimit = 100;
|
|
219
|
-
ListComponent.loadMore =
|
|
236
|
+
ListComponent.loadMore = 100;
|
|
220
237
|
ListComponent.nextComponentId = 0;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memlab/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "command line interface for memlab",
|
|
6
6
|
"author": "Liang Gong <lgong@fb.com>",
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
"access": "public"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@memlab/api": "^1.0.
|
|
28
|
-
"@memlab/core": "^1.1.
|
|
29
|
-
"@memlab/e2e": "^1.0.
|
|
30
|
-
"@memlab/heap-analysis": "^1.0.
|
|
27
|
+
"@memlab/api": "^1.0.11",
|
|
28
|
+
"@memlab/core": "^1.1.10",
|
|
29
|
+
"@memlab/e2e": "^1.0.11",
|
|
30
|
+
"@memlab/heap-analysis": "^1.0.7",
|
|
31
31
|
"ansi": "^0.3.1",
|
|
32
32
|
"babar": "^0.2.0",
|
|
33
33
|
"blessed": "^0.1.81",
|