UNPKG

@memlab/cli

Version:

command line interface for memlab

351 lines (350 loc) 15.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const HeapViewUtils_1 = require("./HeapViewUtils"); const blessed_1 = __importDefault(require("blessed")); const ListComponent_1 = __importDefault(require("./ListComponent")); const HeapViewController_1 = __importDefault(require("./HeapViewController")); const chalk_1 = __importDefault(require("chalk")); function positionToNumber(info) { return parseInt(`${info}`); } /* * The CliScreen component managers the screen layout * all the UI components in CLI. * * Screen Layout: * ┌─Referrers ────────┐┌─Clustered Objects ┐┌─References ───────┐ * │ ││ ││ │ * │ ││ ││ │ * │ ││ ││ │ * │ ││ ││ │ * │ ││ │└───────────────────┘ * │ ││ │┌─Retainer Trace ───┐ * │ ││ ││ │ * │ ││ ││ │ * └───────────────────┘│ ││ │ * ┌─Objects ──────────┐│ ││ │ * │ ││ ││ │ * │ │└───────────────────┘│ │ * │ │┌─Object Detail ────┐│ │ * │ ││ ││ │ * │ ││ ││ │ * │ ││ ││ │ * │ ││ ││ │ * │ ││ ││ │ * └───────────────────┘└───────────────────┘└───────────────────┘ */ class CliScreen { constructor(title, heap, objectCategory) { this.currentFocuseKey = 1; this.fullScreenComponent = null; this.heapController = new HeapViewController_1.default(heap, objectCategory); this.screen = this.initScreen(title); const callbacks = this.initCallbacks(this.heapController, this.screen); this.keyToComponent = new Map(); this.referrerBox = this.initReferrerBox(callbacks); this.heapController.setReferrerBox(this.referrerBox); this.objectBox = this.initObjectBox(callbacks); this.heapController.setObjectBox(this.objectBox); this.clusteredObjectBox = this.initClusteredObjectBox(callbacks); this.heapController.setClusteredBox(this.clusteredObjectBox); this.referenceBox = this.initReferenceBox(callbacks); this.heapController.setReferenceBox(this.referenceBox); this.objectPropertyBox = this.initObjectPropertyBox(callbacks); this.heapController.setObjectPropertyBox(this.objectPropertyBox); this.retainerTraceBox = this.initRetainerTraceBox(callbacks); this.helperTextElement = this.initHelperText(); this.heapController.setRetainerTraceBox(this.retainerTraceBox); this.registerEvents(); this.setFirstObjectAsCurrrent(objectCategory); } setFirstObjectAsCurrrent(objectCategory) { const keys = Array.from(objectCategory.keys()); if (keys.length === 0) { return; } let nodes; for (const key of keys) { nodes = objectCategory.get(key); if (nodes && nodes.length > 0) { break; } } if (nodes && nodes.length > 0) { this.heapController.setCurrentHeapObject((0, HeapViewUtils_1.getHeapObjectAt)(nodes, 0)); } } initScreen(title) { return blessed_1.default.screen({ smartCSR: true, title: title, }); } initCallbacks(controller, screen) { const selectDebounce = (0, HeapViewUtils_1.debounce)(150); const selectCallback = (componentId, index, content, selectInfo) => { if (selectInfo.keyName === 'd' || selectInfo.keyName === 'D') { selectDebounce(() => { controller.displaySourceCode(componentId, index); screen.render(); }); } else if (selectInfo.keyName === 'enter') { selectDebounce(() => { controller.setCurrentHeapObjectFromComponent(componentId, index); screen.render(); }); } else if (selectInfo.keyName === 'up' || selectInfo.keyName === 'down') { selectDebounce(() => { controller.setSelectedHeapObjectFromComponent(componentId, index); screen.render(); }); } }; return { selectCallback, render: () => screen.render(), }; } start() { this.screen.render(); } registerEvents() { this.registerKeys(); this.registerScreenResize(); } registerScreenResize() { const screen = this.screen; screen.on('resize', this.updateAllComponentsSize.bind(this)); } updateAllComponentsSize() { // all boxes/lists needs to resize this.updateComponentSize(this.clusteredObjectBox, this.getClusteredObjectBoxSize()); this.updateComponentSize(this.referrerBox, this.getReferrerBoxSize()); this.updateComponentSize(this.objectBox, this.getObjectBoxSize()); this.updateComponentSize(this.objectPropertyBox, this.getObjectPropertyBoxSize()); this.updateComponentSize(this.referenceBox, this.getReferenceBoxSize()); this.updateComponentSize(this.retainerTraceBox, this.getRetainerTraceBoxSize()); if (this.fullScreenComponent != null) { this.updateComponentSize(this.fullScreenComponent, this.getComponentFullScreenSize()); } this.updateElementSize(this.helperTextElement, this.getHelperTextSize()); } updateComponentSize(component, size) { this.updateElementSize(component.element, size); } updateElementSize(element, size) { element.width = size.width; element.height = size.height; element.top = size.top; element.left = size.left; } registerKeys() { const screen = this.screen; // Quit on Escape, q, or Control-C. screen.key(['escape', 'q', 'C-c'], () => { if (this.fullScreenComponent != null) { // exit component full screen mode this.makeNoComponentFullScreen(); } else { // quit the program process.exit(0); } }); const keyToComponent = this.keyToComponent; const heapController = this.heapController; // eslint-disable-next-line @typescript-eslint/no-unused-vars const callback = (char, key) => { if (keyToComponent.has(char)) { // focus on the selected element const component = keyToComponent.get(char); if (component) { if (component !== this.fullScreenComponent) { // quit full screen mode if the component to focus // is not the current full screen component this.makeNoComponentFullScreen(); } heapController.focusOnComponent(component.id); screen.render(); } } // enter full screen if (char === 'f') { this.makeComponentFullScreen(heapController.getFocusedComponent()); } }; screen.on('keypress', callback); } makeComponentFullScreen(component) { this.fullScreenComponent = component; this.updateAllComponentsSize(); } makeNoComponentFullScreen() { this.fullScreenComponent = null; this.updateAllComponentsSize(); } addComponentToFocusKeyMap(component) { const key = `${this.currentFocuseKey++}`; this.keyToComponent.set(key, component); return key; } getNextFocusKey() { return `${this.currentFocuseKey}`; } initClusteredObjectBox(callbacks) { const box = new ListComponent_1.default([], callbacks, Object.assign({}, this.getClusteredObjectBoxSize())); box.setController(this.heapController); box.setFocusKey(this.getNextFocusKey()); box.setLabel('Clustered Objects'); this.screen.append(box.element); this.addComponentToFocusKeyMap(box); return box; } getClusteredObjectBoxSize() { return { width: Math.floor(positionToNumber(this.screen.width) / 3), height: Math.floor((2 * positionToNumber(this.screen.height)) / 3), top: 0, left: Math.floor(positionToNumber(this.screen.width) / 3), }; } initReferrerBox(callbacks) { const box = new ListComponent_1.default([], callbacks, Object.assign({}, this.getReferrerBoxSize())); box.setController(this.heapController); box.setFocusKey(this.getNextFocusKey()); box.setLabel('Referrers'); this.screen.append(box.element); this.addComponentToFocusKeyMap(box); return box; } getReferrerBoxSize() { return { width: Math.floor(positionToNumber(this.screen.width) / 3), height: Math.floor(positionToNumber(this.screen.height) / 2), top: 0, left: 0, }; } initObjectBox(callbacks) { const box = new ListComponent_1.default([], callbacks, Object.assign({}, this.getObjectBoxSize())); box.setController(this.heapController); box.setFocusKey(this.getNextFocusKey()); box.setLabel('Objects'); this.screen.append(box.element); this.addComponentToFocusKeyMap(box); return box; } getComponentFullScreenSize() { return { width: positionToNumber(this.screen.width) - 4, height: positionToNumber(this.screen.height) - 5, top: 2, left: 2, }; } getObjectBoxSize() { return { width: Math.floor(positionToNumber(this.screen.width) / 3), height: positionToNumber(this.screen.height) - Math.floor(positionToNumber(this.screen.height) / 2) - 1, top: Math.floor(positionToNumber(this.screen.height) / 2), left: 0, }; } initObjectPropertyBox(callbacks) { const box = new ListComponent_1.default([], callbacks, Object.assign({}, this.getObjectPropertyBoxSize())); box.setController(this.heapController); box.setFocusKey(this.getNextFocusKey()); box.setLabel('Objects Detail'); this.screen.append(box.element); this.addComponentToFocusKeyMap(box); return box; } getObjectPropertyBoxSize() { return { width: Math.floor(positionToNumber(this.screen.width) / 3), height: positionToNumber(this.screen.height) - Math.floor((2 * positionToNumber(this.screen.height)) / 3) - 1, top: Math.floor((2 * positionToNumber(this.screen.height)) / 3), left: Math.floor(positionToNumber(this.screen.width) / 3), }; } initReferenceBox(callbacks) { const box = new ListComponent_1.default([], callbacks, Object.assign({}, this.getReferenceBoxSize())); box.setController(this.heapController); box.setFocusKey(this.getNextFocusKey()); box.setLabel('References'); this.screen.append(box.element); this.addComponentToFocusKeyMap(box); return box; } getReferenceBoxSize() { return { width: positionToNumber(this.screen.width) - Math.floor((2 * positionToNumber(this.screen.width)) / 3), height: Math.floor(positionToNumber(this.screen.height) / 3), top: 0, left: Math.floor((2 * positionToNumber(this.screen.width)) / 3), }; } initRetainerTraceBox(callbacks) { const box = new ListComponent_1.default([], callbacks, Object.assign({}, this.getRetainerTraceBoxSize())); box.setController(this.heapController); box.setFocusKey(this.getNextFocusKey()); box.setLabel('Retainer Trace'); this.screen.append(box.element); this.addComponentToFocusKeyMap(box); return box; } getRetainerTraceBoxSize() { return { width: positionToNumber(this.screen.width) - Math.floor((2 * positionToNumber(this.screen.width)) / 3), height: positionToNumber(this.screen.height) - Math.floor(positionToNumber(this.screen.height) / 3) - 1, top: Math.floor(positionToNumber(this.screen.height) / 3), left: Math.floor((2 * positionToNumber(this.screen.width)) / 3), }; } getHelperTextContent() { const keys = { '↑': '', // empty description won't be displayed '↓': '', '←': '', '→': '', Enter: 'select', f: 'full screen', q: 'quit', }; const keysToFocus = Array.from(this.keyToComponent.keys()); const descArr = [...Object.keys(keys), ...keysToFocus].map((key) => { const description = keys[key]; return description ? chalk_1.default.inverse(key) + `(${description})` : chalk_1.default.inverse(key); }); return chalk_1.default.grey('Keys: ' + descArr.join(' ')); } initHelperText() { const text = blessed_1.default.text(Object.assign(Object.assign({}, this.getHelperTextSize()), { content: this.getHelperTextContent() })); this.screen.append(text); return text; } getHelperTextSize() { return { width: positionToNumber(this.screen.width), height: 1, top: positionToNumber(this.screen.height) - 1, left: 1, }; } } exports.default = CliScreen;