@pellux/goodvibes-tui 1.1.0 → 1.9.0
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/CHANGELOG.md +85 -21
- package/README.md +28 -18
- package/docs/foundation-artifacts/operator-contract.json +4045 -1763
- package/package.json +2 -2
- package/src/audio/spoken-turn-controller.ts +12 -2
- package/src/audio/spoken-turn-wiring.ts +2 -1
- package/src/cli/help.ts +8 -1
- package/src/cli/service-posture.ts +2 -1
- package/src/cli/status.ts +2 -1
- package/src/cli/surface-command.ts +2 -2
- package/src/core/alert-gating.ts +67 -0
- package/src/core/approval-alert.ts +72 -0
- package/src/core/budget-breach-notifier.ts +106 -0
- package/src/core/composer-state.ts +11 -3
- package/src/core/conversation-line-cache.ts +27 -3
- package/src/core/conversation-rendering.ts +90 -14
- package/src/core/conversation.ts +18 -0
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/long-task-notifier.ts +33 -6
- package/src/core/stream-event-wiring.ts +20 -1
- package/src/core/system-message-noise.ts +87 -0
- package/src/core/system-message-router.ts +104 -51
- package/src/core/turn-cancellation.ts +25 -0
- package/src/core/turn-event-wiring.ts +72 -3
- package/src/daemon/cli.ts +29 -2
- package/src/daemon/handlers/register.ts +8 -1
- package/src/daemon/service-commands.ts +329 -0
- package/src/export/cost-utils.ts +36 -0
- package/src/input/autocomplete.ts +27 -1
- package/src/input/command-registry.ts +84 -5
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +232 -0
- package/src/input/commands/config.ts +43 -3
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +10 -2
- package/src/input/commands/image-runtime.ts +112 -0
- package/src/input/commands/intelligence-runtime.ts +11 -1
- package/src/input/commands/local-auth-runtime.ts +4 -1
- package/src/input/commands/local-runtime.ts +9 -3
- package/src/input/commands/marketplace-runtime.ts +2 -2
- package/src/input/commands/memory.ts +73 -35
- package/src/input/commands/operator-panel-runtime.ts +71 -31
- package/src/input/commands/planning-runtime.ts +116 -4
- package/src/input/commands/platform-sandbox-runtime.ts +1 -6
- package/src/input/commands/plugin-runtime.ts +2 -2
- package/src/input/commands/policy-dispatch.ts +21 -0
- package/src/input/commands/provider-accounts-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +26 -6
- package/src/input/commands/recall-review.ts +35 -0
- package/src/input/commands/remote-runtime-setup.ts +5 -3
- package/src/input/commands/remote-runtime.ts +3 -3
- package/src/input/commands/runtime-services.ts +54 -13
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-content.ts +20 -9
- package/src/input/commands/session-workflow.ts +16 -1
- package/src/input/commands/settings-sync-runtime.ts +16 -4
- package/src/input/commands/shell-core.ts +25 -8
- package/src/input/commands/skills-runtime.ts +2 -8
- package/src/input/commands/subscription-runtime.ts +2 -2
- package/src/input/commands/test-runtime.ts +277 -0
- package/src/input/commands/websearch-runtime.ts +101 -0
- package/src/input/commands/work-plan-runtime.ts +36 -10
- package/src/input/commands/workstream-runtime.ts +488 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +175 -0
- package/src/input/config-modal.ts +592 -0
- package/src/input/feed-context-factory.ts +10 -8
- package/src/input/handler-command-route.ts +37 -20
- package/src/input/handler-content-actions.ts +17 -2
- package/src/input/handler-feed-routes.ts +74 -114
- package/src/input/handler-feed.ts +79 -27
- package/src/input/handler-interactions.ts +1 -3
- package/src/input/handler-modal-routes.ts +136 -6
- package/src/input/handler-modal-stack.ts +14 -7
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-onboarding-daemon-adopt.ts +149 -0
- package/src/input/handler-onboarding.ts +71 -59
- package/src/input/handler-picker-routes.ts +26 -102
- package/src/input/handler-shortcuts.ts +74 -14
- package/src/input/handler-types.ts +2 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +12 -29
- package/src/input/keybindings.ts +23 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +77 -2
- package/src/input/onboarding/onboarding-runtime-status.ts +10 -1
- package/src/input/onboarding/onboarding-wizard-apply.ts +8 -1
- package/src/input/onboarding/onboarding-wizard-constants.ts +4 -1
- package/src/input/onboarding/onboarding-wizard-network-adopt.ts +136 -0
- package/src/input/onboarding/onboarding-wizard-steps.ts +9 -6
- package/src/input/onboarding/onboarding-wizard-types.ts +3 -1
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/panel-mouse-geometry.ts +97 -0
- package/src/input/panel-paste-flood-guard.ts +86 -0
- package/src/input/selection-modal.ts +11 -0
- package/src/input/session-picker-modal.ts +44 -4
- package/src/input/settings-modal-data.ts +146 -2
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/input/settings-modal-types.ts +4 -2
- package/src/main.ts +49 -51
- package/src/panels/agent-inspector-shared.ts +2 -1
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +15 -61
- package/src/panels/builtin/knowledge.ts +8 -32
- package/src/panels/builtin/operations.ts +84 -428
- package/src/panels/builtin/session.ts +21 -112
- package/src/panels/builtin/shared.ts +18 -46
- package/src/panels/builtin-modals.ts +218 -0
- package/src/panels/builtin-panels.ts +5 -0
- package/src/panels/cost-tracker-panel.ts +36 -10
- package/src/panels/diff-panel.ts +12 -43
- package/src/panels/eval-registry.ts +60 -0
- package/src/panels/fleet-deep-link.ts +31 -0
- package/src/panels/fleet-panel-format.ts +62 -0
- package/src/panels/fleet-panel-worktree-detail.ts +48 -0
- package/src/panels/fleet-panel.ts +758 -0
- package/src/panels/fleet-read-model.ts +520 -0
- package/src/panels/fleet-steer.ts +125 -0
- package/src/panels/fleet-stop.ts +153 -0
- package/src/panels/fleet-tabs.ts +230 -0
- package/src/panels/fleet-transcript.ts +356 -0
- package/src/panels/index.ts +7 -31
- package/src/panels/modals/hooks-modal.ts +187 -0
- package/src/panels/modals/keybindings-modal.ts +166 -0
- package/src/panels/modals/knowledge-modal.ts +158 -0
- package/src/panels/modals/local-auth-modal.ts +132 -0
- package/src/panels/modals/marketplace-modal.ts +218 -0
- package/src/panels/modals/memory-modal.ts +180 -0
- package/src/panels/modals/modal-surface-helpers.ts +54 -0
- package/src/panels/modals/modal-theme.ts +42 -0
- package/src/panels/modals/pairing-modal.ts +163 -0
- package/src/panels/modals/planning-modal.ts +304 -0
- package/src/panels/modals/plugins-modal.ts +174 -0
- package/src/panels/modals/policy-modal.ts +256 -0
- package/src/panels/modals/provider-health-modal.ts +136 -0
- package/src/panels/modals/remote-modal.ts +115 -0
- package/src/panels/modals/sandbox-modal.ts +150 -0
- package/src/panels/modals/security-modal.ts +217 -0
- package/src/panels/modals/services-modal.ts +179 -0
- package/src/panels/modals/settings-sync-modal.ts +142 -0
- package/src/panels/modals/skills-modal.ts +189 -0
- package/src/panels/modals/subscription-modal.ts +172 -0
- package/src/panels/modals/work-plan-modal.ts +177 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +123 -9
- package/src/panels/polish-core.ts +38 -25
- package/src/panels/project-planning-answer-actions.ts +8 -11
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/types.ts +37 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +216 -13
- package/src/renderer/autocomplete-overlay.ts +28 -2
- package/src/renderer/compaction-history-modal.ts +19 -3
- package/src/renderer/compaction-preview.ts +13 -2
- package/src/renderer/compaction-quality.ts +100 -0
- package/src/renderer/compositor.ts +2 -3
- package/src/renderer/config-modal.ts +95 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +15 -3
- package/src/renderer/fullscreen-primitives.ts +32 -22
- package/src/renderer/git-status.ts +43 -1
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/layout.ts +0 -4
- package/src/renderer/markdown.ts +7 -3
- package/src/renderer/modal-factory.ts +25 -20
- package/src/renderer/model-workspace.ts +62 -4
- package/src/renderer/overlay-box.ts +21 -17
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/process-indicator.ts +14 -3
- package/src/renderer/selection-modal-overlay.ts +6 -1
- package/src/renderer/session-picker-modal.ts +196 -3
- package/src/renderer/settings-modal-helpers.ts +2 -0
- package/src/renderer/settings-modal.ts +7 -0
- package/src/renderer/shell-surface.ts +8 -1
- package/src/renderer/status-glyphs.ts +14 -15
- package/src/renderer/system-message.ts +15 -3
- package/src/renderer/terminal-bg-probe.ts +339 -0
- package/src/renderer/terminal-escapes.ts +20 -0
- package/src/renderer/theme-mode-config.ts +67 -0
- package/src/renderer/theme.ts +91 -1
- package/src/renderer/thinking.ts +11 -3
- package/src/renderer/tool-call.ts +15 -9
- package/src/renderer/tool-result-summary.ts +148 -0
- package/src/renderer/turn-injection.ts +133 -0
- package/src/renderer/ui-factory.ts +154 -85
- package/src/renderer/ui-primitives.ts +30 -129
- package/src/runtime/bootstrap-command-context.ts +27 -1
- package/src/runtime/bootstrap-command-parts.ts +41 -10
- package/src/runtime/bootstrap-core.ts +47 -15
- package/src/runtime/bootstrap-hook-bridge.ts +7 -0
- package/src/runtime/bootstrap-shell.ts +48 -17
- package/src/runtime/bootstrap.ts +129 -22
- package/src/runtime/code-index-services.ts +135 -0
- package/src/runtime/legacy-daemon-migration.ts +516 -0
- package/src/runtime/memory-fold.ts +26 -0
- package/src/runtime/onboarding/derivation.ts +7 -2
- package/src/runtime/onboarding/snapshot.ts +27 -1
- package/src/runtime/onboarding/types.ts +31 -1
- package/src/runtime/operator-token-cleanup.ts +82 -1
- package/src/runtime/orchestrator-core-services.ts +59 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/resume-notice.ts +209 -0
- package/src/runtime/services.ts +98 -46
- package/src/runtime/session-inbound-inputs.ts +252 -0
- package/src/runtime/session-spine-transport.ts +64 -0
- package/src/runtime/terminal-output-guard.ts +15 -8
- package/src/runtime/ui-services.ts +27 -3
- package/src/runtime/workstream-services.ts +327 -0
- package/src/runtime/wrfc-persistence.ts +124 -17
- package/src/shell/blocking-input.ts +68 -4
- package/src/shell/recovery-input-helpers.ts +170 -1
- package/src/shell/ui-openers.ts +171 -26
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- package/src/utils/terminal-width.ts +52 -0
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -786
- package/src/panels/approval-panel.ts +0 -252
- package/src/panels/automation-control-panel.ts +0 -479
- package/src/panels/cockpit-panel.ts +0 -481
- package/src/panels/cockpit-read-model.ts +0 -238
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -615
- package/src/panels/docs-panel.ts +0 -384
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -673
- package/src/panels/file-preview-panel.ts +0 -517
- package/src/panels/hooks-panel.ts +0 -401
- package/src/panels/incident-review-panel.ts +0 -406
- package/src/panels/intelligence-panel.ts +0 -383
- package/src/panels/knowledge-graph-panel.ts +0 -515
- package/src/panels/marketplace-panel.ts +0 -399
- package/src/panels/memory-panel.ts +0 -558
- package/src/panels/ops-control-panel.ts +0 -329
- package/src/panels/ops-strategy-panel.ts +0 -321
- package/src/panels/orchestration-panel.ts +0 -465
- package/src/panels/panel-list-panel.ts +0 -566
- package/src/panels/plan-dashboard-panel.ts +0 -707
- package/src/panels/policy-panel.ts +0 -517
- package/src/panels/project-planning-panel.ts +0 -731
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -310
- package/src/panels/provider-health-views.ts +0 -567
- package/src/panels/qr-panel.ts +0 -280
- package/src/panels/remote-panel.ts +0 -534
- package/src/panels/routes-panel.ts +0 -241
- package/src/panels/sandbox-panel.ts +0 -456
- package/src/panels/security-panel.ts +0 -447
- package/src/panels/services-panel.ts +0 -329
- package/src/panels/session-browser-panel.ts +0 -496
- package/src/panels/settings-sync-panel.ts +0 -398
- package/src/panels/subscription-panel.ts +0 -342
- package/src/panels/symbol-outline-panel.ts +0 -619
- package/src/panels/system-messages-panel.ts +0 -364
- package/src/panels/tasks-panel.ts +0 -608
- package/src/panels/thinking-panel.ts +0 -333
- package/src/panels/tool-inspector-panel.ts +0 -537
- package/src/panels/work-plan-panel.ts +0 -540
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -466
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -671
- package/src/renderer/qr-renderer.ts +0 -120
|
@@ -1,619 +0,0 @@
|
|
|
1
|
-
import { BasePanel } from './base-panel.ts';
|
|
2
|
-
import type { Line } from '../types/grid.ts';
|
|
3
|
-
import {
|
|
4
|
-
buildEmptyState,
|
|
5
|
-
buildKeyboardHints,
|
|
6
|
-
buildPanelLine,
|
|
7
|
-
buildTreeRow,
|
|
8
|
-
buildPanelWorkspace,
|
|
9
|
-
resolveScrollablePanelSection,
|
|
10
|
-
DEFAULT_PANEL_PALETTE,
|
|
11
|
-
extendPalette,
|
|
12
|
-
} from './polish.ts';
|
|
13
|
-
import { TreeSitterService } from '@pellux/goodvibes-sdk/platform/intelligence';
|
|
14
|
-
import type { Node, Tree, Language } from 'web-tree-sitter';
|
|
15
|
-
import { logger, summarizeError } from '@pellux/goodvibes-sdk/platform/utils';
|
|
16
|
-
|
|
17
|
-
// ── Symbol types ────────────────────────────────────────────────────────────
|
|
18
|
-
|
|
19
|
-
export type SymbolKind = 'function' | 'class' | 'interface' | 'type' | 'const' | 'method' | 'namespace';
|
|
20
|
-
|
|
21
|
-
export interface SymbolEntry {
|
|
22
|
-
kind: SymbolKind;
|
|
23
|
-
name: string;
|
|
24
|
-
line: number;
|
|
25
|
-
/** If set, this symbol is a child of a parent container (class/namespace). */
|
|
26
|
-
parentName?: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// ── Rendering constants ──────────────────────────────────────────────────────
|
|
30
|
-
|
|
31
|
-
const C = extendPalette(DEFAULT_PANEL_PALETTE, {
|
|
32
|
-
selectedBg: '236',
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
/** ANSI 256-color fg codes per symbol kind. */
|
|
36
|
-
const KIND_COLORS: Record<SymbolKind, string> = {
|
|
37
|
-
function: '87', // cyan
|
|
38
|
-
method: '87', // cyan
|
|
39
|
-
class: '141', // purple
|
|
40
|
-
namespace: '141', // purple
|
|
41
|
-
interface: '219', // pink
|
|
42
|
-
type: '228', // yellow
|
|
43
|
-
const: '245', // grey
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
/** Single-char type icon for the tree row (kept ASCII for column safety). */
|
|
47
|
-
const KIND_ICONS: Record<SymbolKind, string> = {
|
|
48
|
-
function: 'ƒ',
|
|
49
|
-
method: 'ƒ',
|
|
50
|
-
class: 'C',
|
|
51
|
-
namespace: 'N',
|
|
52
|
-
interface: 'I',
|
|
53
|
-
type: 'T',
|
|
54
|
-
const: 'k',
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
// ── Tree-sitter symbol extraction ────────────────────────────────────────────
|
|
58
|
-
|
|
59
|
-
type LangId = 'typescript' | 'tsx' | 'javascript';
|
|
60
|
-
|
|
61
|
-
/** Map a file extension to the tree-sitter grammar id used to parse it. */
|
|
62
|
-
function detectLangId(filePath: string): LangId | null {
|
|
63
|
-
const dot = filePath.lastIndexOf('.');
|
|
64
|
-
const ext = dot >= 0 ? filePath.slice(dot + 1).toLowerCase() : '';
|
|
65
|
-
if (ext === 'ts' || ext === 'mts' || ext === 'cts') return 'typescript';
|
|
66
|
-
if (ext === 'tsx') return 'tsx';
|
|
67
|
-
if (ext === 'js' || ext === 'mjs' || ext === 'cjs' || ext === 'jsx') return 'javascript';
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Tree-sitter queries capturing the declaration shapes the outline surfaces.
|
|
72
|
-
// Class members (methods, getters/setters, decorated members, and
|
|
73
|
-
// arrow-function class fields) are captured structurally rather than by
|
|
74
|
-
// indentation, so decorators and modifiers never throw off matching.
|
|
75
|
-
const TS_QUERY = `
|
|
76
|
-
(class_declaration name: (type_identifier) @class.name) @class.def
|
|
77
|
-
(abstract_class_declaration name: (type_identifier) @class.name) @class.def
|
|
78
|
-
(interface_declaration name: (type_identifier) @interface.name) @interface.def
|
|
79
|
-
(type_alias_declaration name: (type_identifier) @type.name) @type.def
|
|
80
|
-
(internal_module name: (identifier) @namespace.name) @namespace.def
|
|
81
|
-
(function_declaration name: (identifier) @function.name) @function.def
|
|
82
|
-
(method_definition name: (property_identifier) @method.name) @method.def
|
|
83
|
-
(public_field_definition
|
|
84
|
-
name: (property_identifier) @field.name
|
|
85
|
-
value: (arrow_function)) @field.def
|
|
86
|
-
(lexical_declaration
|
|
87
|
-
(variable_declarator name: (identifier) @const.name) @const.declarator) @const.def
|
|
88
|
-
`;
|
|
89
|
-
|
|
90
|
-
const JS_QUERY = `
|
|
91
|
-
(class_declaration name: (identifier) @class.name) @class.def
|
|
92
|
-
(function_declaration name: (identifier) @function.name) @function.def
|
|
93
|
-
(method_definition name: (property_identifier) @method.name) @method.def
|
|
94
|
-
(field_definition
|
|
95
|
-
property: (property_identifier) @field.name
|
|
96
|
-
value: (arrow_function)) @field.def
|
|
97
|
-
(lexical_declaration
|
|
98
|
-
(variable_declarator name: (identifier) @const.name) @const.declarator) @const.def
|
|
99
|
-
`;
|
|
100
|
-
|
|
101
|
-
const QUERY_BY_LANG: Record<LangId, string> = {
|
|
102
|
-
typescript: TS_QUERY,
|
|
103
|
-
tsx: TS_QUERY,
|
|
104
|
-
javascript: JS_QUERY,
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
const CLASS_NODE_TYPES = new Set(['class_declaration', 'abstract_class_declaration', 'class_expression']);
|
|
108
|
-
|
|
109
|
-
/** True when `node` sits directly at module scope (optionally wrapped in `export`). */
|
|
110
|
-
function isTopLevelDeclaration(node: Node): boolean {
|
|
111
|
-
const parent = node.parent;
|
|
112
|
-
if (!parent) return true;
|
|
113
|
-
if (parent.type === 'program') return true;
|
|
114
|
-
if (parent.type === 'export_statement') return parent.parent?.type === 'program';
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Walk up from a class member to its nearest enclosing class. Returns the
|
|
120
|
-
* class name only when that class is itself a top-level declaration — this
|
|
121
|
-
* keeps the outline to a class + its direct members (no deep nesting), the
|
|
122
|
-
* same depth the previous regex-based parser supported.
|
|
123
|
-
*/
|
|
124
|
-
function findEnclosingClassName(node: Node): string | null {
|
|
125
|
-
let cursor: Node | null = node.parent;
|
|
126
|
-
while (cursor) {
|
|
127
|
-
if (CLASS_NODE_TYPES.has(cursor.type)) {
|
|
128
|
-
if (!isTopLevelDeclaration(cursor)) return null;
|
|
129
|
-
const nameNode = cursor.childForFieldName('name');
|
|
130
|
-
return nameNode ? nameNode.text : null;
|
|
131
|
-
}
|
|
132
|
-
cursor = cursor.parent;
|
|
133
|
-
}
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Extract a flat, line-ordered list of symbols from a parsed tree using a
|
|
139
|
-
* tree-sitter query over the real AST (no regex heuristics). Class members
|
|
140
|
-
* (methods, getters/setters, decorated members, arrow-function fields) carry
|
|
141
|
-
* `parentName` so the renderer can group them under their class header.
|
|
142
|
-
*/
|
|
143
|
-
function extractSymbolsFromTree(tree: Tree, language: Language, langId: LangId, service: TreeSitterService): SymbolEntry[] {
|
|
144
|
-
const query = QUERY_BY_LANG[langId];
|
|
145
|
-
const matches = service.query(tree, language, query);
|
|
146
|
-
const result: SymbolEntry[] = [];
|
|
147
|
-
|
|
148
|
-
for (const match of matches) {
|
|
149
|
-
const captures = new Map<string, Node>(match.captures.map((c) => [c.name, c.node]));
|
|
150
|
-
|
|
151
|
-
const classDef = captures.get('class.def');
|
|
152
|
-
if (classDef) {
|
|
153
|
-
const nameNode = captures.get('class.name');
|
|
154
|
-
if (nameNode && isTopLevelDeclaration(classDef)) {
|
|
155
|
-
result.push({ kind: 'class', name: nameNode.text, line: classDef.startPosition.row + 1 });
|
|
156
|
-
}
|
|
157
|
-
continue;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const interfaceDef = captures.get('interface.def');
|
|
161
|
-
if (interfaceDef) {
|
|
162
|
-
const nameNode = captures.get('interface.name');
|
|
163
|
-
if (nameNode && isTopLevelDeclaration(interfaceDef)) {
|
|
164
|
-
result.push({ kind: 'interface', name: nameNode.text, line: interfaceDef.startPosition.row + 1 });
|
|
165
|
-
}
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const typeDef = captures.get('type.def');
|
|
170
|
-
if (typeDef) {
|
|
171
|
-
const nameNode = captures.get('type.name');
|
|
172
|
-
if (nameNode && isTopLevelDeclaration(typeDef)) {
|
|
173
|
-
result.push({ kind: 'type', name: nameNode.text, line: typeDef.startPosition.row + 1 });
|
|
174
|
-
}
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const namespaceDef = captures.get('namespace.def');
|
|
179
|
-
if (namespaceDef) {
|
|
180
|
-
const nameNode = captures.get('namespace.name');
|
|
181
|
-
if (nameNode && isTopLevelDeclaration(namespaceDef)) {
|
|
182
|
-
result.push({ kind: 'namespace', name: nameNode.text, line: namespaceDef.startPosition.row + 1 });
|
|
183
|
-
}
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const functionDef = captures.get('function.def');
|
|
188
|
-
if (functionDef) {
|
|
189
|
-
const nameNode = captures.get('function.name');
|
|
190
|
-
if (nameNode && isTopLevelDeclaration(functionDef)) {
|
|
191
|
-
result.push({ kind: 'function', name: nameNode.text, line: functionDef.startPosition.row + 1 });
|
|
192
|
-
}
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Methods, getters/setters, and constructors — all `method_definition`
|
|
197
|
-
// regardless of decorators (decorators are preceding siblings, not
|
|
198
|
-
// wrappers, so they never block the match).
|
|
199
|
-
const methodDef = captures.get('method.def');
|
|
200
|
-
if (methodDef) {
|
|
201
|
-
const nameNode = captures.get('method.name');
|
|
202
|
-
const parentName = findEnclosingClassName(methodDef);
|
|
203
|
-
if (nameNode && parentName) {
|
|
204
|
-
result.push({ kind: 'method', name: nameNode.text, line: methodDef.startPosition.row + 1, parentName });
|
|
205
|
-
}
|
|
206
|
-
continue;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Arrow-function class fields (`onClick = () => {...}`), decorated or not.
|
|
210
|
-
const fieldDef = captures.get('field.def');
|
|
211
|
-
if (fieldDef) {
|
|
212
|
-
const nameNode = captures.get('field.name');
|
|
213
|
-
const parentName = findEnclosingClassName(fieldDef);
|
|
214
|
-
if (nameNode && parentName) {
|
|
215
|
-
result.push({ kind: 'method', name: nameNode.text, line: fieldDef.startPosition.row + 1, parentName });
|
|
216
|
-
}
|
|
217
|
-
continue;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const constDef = captures.get('const.def');
|
|
221
|
-
if (constDef) {
|
|
222
|
-
const nameNode = captures.get('const.name');
|
|
223
|
-
const declarator = captures.get('const.declarator');
|
|
224
|
-
if (nameNode && isTopLevelDeclaration(constDef)) {
|
|
225
|
-
const valueNode = declarator?.childForFieldName('value') ?? null;
|
|
226
|
-
const isFunctionValued = valueNode?.type === 'arrow_function' || valueNode?.type === 'function_expression';
|
|
227
|
-
result.push({
|
|
228
|
-
kind: isFunctionValued ? 'function' : 'const',
|
|
229
|
-
name: nameNode.text,
|
|
230
|
-
line: constDef.startPosition.row + 1,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
result.sort((a, b) => a.line - b.line);
|
|
238
|
-
return result;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// ── Panel ────────────────────────────────────────────────────────────────────
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* SymbolOutlinePanel — renders a hierarchical symbol outline of the current
|
|
245
|
-
* file. Symbols are parsed from the real AST via a tree-sitter query (the
|
|
246
|
-
* same parser infrastructure syntax-highlighter.ts uses), not regex
|
|
247
|
-
* heuristics — so class fields assigned arrow functions, getters/setters,
|
|
248
|
-
* and decorated members all appear correctly.
|
|
249
|
-
*/
|
|
250
|
-
export class SymbolOutlinePanel extends BasePanel {
|
|
251
|
-
/** Flat list of parsed symbols (methods nested after their parent class). */
|
|
252
|
-
private symbols: SymbolEntry[] = [];
|
|
253
|
-
|
|
254
|
-
/** Index of the currently highlighted row in the visible flat list. */
|
|
255
|
-
private selectedIndex: number = 0;
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* The row under the cursor. This panel owns its own selection state
|
|
259
|
-
* (`selectedIndex` navigates the filtered `_visibleRows()` flat list), so
|
|
260
|
-
* every selected-row read routes through this one accessor — indexing the
|
|
261
|
-
* `_visibleRows()` list by the cursor directly is banned by the
|
|
262
|
-
* no-raw-selectedindex-read architecture rule.
|
|
263
|
-
*/
|
|
264
|
-
private selectedRow(): VisibleRow | undefined {
|
|
265
|
-
return this._visibleRows().at(this.selectedIndex);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/** Set of container names (class/namespace) that are collapsed. */
|
|
269
|
-
private collapsed: Set<string> = new Set();
|
|
270
|
-
|
|
271
|
-
/** Scroll offset (top-visible row index in the flat rendered list). */
|
|
272
|
-
private scrollOffset: number = 0;
|
|
273
|
-
|
|
274
|
-
/** Path of the file currently loaded. */
|
|
275
|
-
private currentPath: string = '';
|
|
276
|
-
|
|
277
|
-
/** Shared tree-sitter service for this panel instance (mirrors syntax-highlighter.ts). */
|
|
278
|
-
private readonly treeSitter = new TreeSitterService();
|
|
279
|
-
|
|
280
|
-
/** Bumped on every loadFile() call so a superseded async parse is discarded. */
|
|
281
|
-
private parseGeneration: number = 0;
|
|
282
|
-
|
|
283
|
-
constructor() {
|
|
284
|
-
super('symbols', 'Symbols', 'S', 'development');
|
|
285
|
-
this.treeSitter.initialize().catch((err: unknown) => {
|
|
286
|
-
logger.warn('SymbolOutlinePanel: tree-sitter init failed', { error: summarizeError(err) });
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// ── Public API ─────────────────────────────────────────────────────────────
|
|
291
|
-
|
|
292
|
-
/**
|
|
293
|
-
* Load and parse symbols from the given file source text via tree-sitter.
|
|
294
|
-
* Call this when the active file changes in the file-preview panel, and
|
|
295
|
-
* again whenever the preview reloads the same file so the outline re-syncs
|
|
296
|
-
* with on-disk edits.
|
|
297
|
-
*/
|
|
298
|
-
loadFile(path: string, source: string): void {
|
|
299
|
-
this.currentPath = path;
|
|
300
|
-
this.symbols = [];
|
|
301
|
-
this.selectedIndex = 0;
|
|
302
|
-
this.scrollOffset = 0;
|
|
303
|
-
this.collapsed.clear();
|
|
304
|
-
this.markDirty();
|
|
305
|
-
|
|
306
|
-
const generation = ++this.parseGeneration;
|
|
307
|
-
const langId = detectLangId(path);
|
|
308
|
-
if (!langId) return; // unsupported language — honest "no symbols" empty state
|
|
309
|
-
|
|
310
|
-
void this._parseAndSync(path, source, langId, generation);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Returns the { path, line } for the currently selected symbol so the
|
|
315
|
-
* caller can jump to it in the file-preview panel.
|
|
316
|
-
*/
|
|
317
|
-
getSelectedLocation(): { path: string; line: number } | null {
|
|
318
|
-
const row = this.selectedRow();
|
|
319
|
-
if (!row || row.kind === 'header') return null;
|
|
320
|
-
return { path: this.currentPath, line: row.symbol.line };
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
// ── Input ──────────────────────────────────────────────────────────────────
|
|
324
|
-
|
|
325
|
-
handleInput(key: string): boolean {
|
|
326
|
-
const visible = this._visibleRows();
|
|
327
|
-
|
|
328
|
-
if (key === 'up' || key === 'k') {
|
|
329
|
-
if (this.selectedIndex > 0) {
|
|
330
|
-
this.selectedIndex--;
|
|
331
|
-
this._clampScroll(visible.length);
|
|
332
|
-
this.markDirty();
|
|
333
|
-
}
|
|
334
|
-
return true;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
if (key === 'down' || key === 'j') {
|
|
338
|
-
if (this.selectedIndex < visible.length - 1) {
|
|
339
|
-
this.selectedIndex++;
|
|
340
|
-
this._clampScroll(visible.length);
|
|
341
|
-
this.markDirty();
|
|
342
|
-
}
|
|
343
|
-
return true;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
if (key === 'return' || key === 'enter') {
|
|
347
|
-
// Only consume Enter when there is a real symbol to jump to — otherwise
|
|
348
|
-
// let the key fall through instead of swallowing it for nothing.
|
|
349
|
-
return this.getSelectedLocation() !== null;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (key === 'space' || key === 'right' || key === 'left') {
|
|
353
|
-
const row = this.selectedRow();
|
|
354
|
-
if (row?.kind === 'header') {
|
|
355
|
-
const name = row.name;
|
|
356
|
-
if (this.collapsed.has(name)) {
|
|
357
|
-
this.collapsed.delete(name);
|
|
358
|
-
} else {
|
|
359
|
-
this.collapsed.add(name);
|
|
360
|
-
}
|
|
361
|
-
// Clamp selection so it doesn't point into a now-hidden row
|
|
362
|
-
const newVisible = this._visibleRows();
|
|
363
|
-
if (this.selectedIndex >= newVisible.length) {
|
|
364
|
-
this.selectedIndex = Math.max(0, newVisible.length - 1);
|
|
365
|
-
}
|
|
366
|
-
this._clampScroll(newVisible.length);
|
|
367
|
-
this.markDirty();
|
|
368
|
-
}
|
|
369
|
-
return true;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// ── Rendering ──────────────────────────────────────────────────────────────
|
|
376
|
-
|
|
377
|
-
render(width: number, height: number): Line[] {
|
|
378
|
-
this.needsRender = false;
|
|
379
|
-
|
|
380
|
-
if (this.symbols.length === 0) {
|
|
381
|
-
return buildPanelWorkspace(width, height, {
|
|
382
|
-
title: ' Symbols',
|
|
383
|
-
intro: 'Outline the current file into navigable symbols and lightweight parent/child structure.',
|
|
384
|
-
sections: [
|
|
385
|
-
{
|
|
386
|
-
lines: buildEmptyState(
|
|
387
|
-
width,
|
|
388
|
-
this.currentPath ? ' No symbols found' : ' No file loaded',
|
|
389
|
-
this.currentPath
|
|
390
|
-
? 'The current file did not produce outline entries from the tree-sitter parse.'
|
|
391
|
-
: 'Load a file in the preview panel to populate its outline here.',
|
|
392
|
-
this.currentPath
|
|
393
|
-
? []
|
|
394
|
-
: [{ command: '/panel open explorer', summary: 'pick a file in the explorer, then Enter to preview and outline it' }],
|
|
395
|
-
C,
|
|
396
|
-
),
|
|
397
|
-
},
|
|
398
|
-
],
|
|
399
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
400
|
-
});
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const visible = this._visibleRows();
|
|
404
|
-
const outlineSection = resolveScrollablePanelSection(width, height, {
|
|
405
|
-
intro: this.currentPath ? this.currentPath : 'Outline the current file into navigable symbols and lightweight parent/child structure.',
|
|
406
|
-
footerLines: [
|
|
407
|
-
buildKeyboardHints(width, [{ keys: '↑/↓', label: 'navigate' }, { keys: 'Space/←/→', label: 'collapse' }, { keys: 'Enter', label: 'jump to source →' }], DEFAULT_PANEL_PALETTE),
|
|
408
|
-
],
|
|
409
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
410
|
-
beforeSections: [
|
|
411
|
-
{
|
|
412
|
-
title: 'Summary',
|
|
413
|
-
lines: [
|
|
414
|
-
buildPanelLine(width, [
|
|
415
|
-
[' Symbols ', DEFAULT_PANEL_PALETTE.label],
|
|
416
|
-
[String(this.symbols.length), DEFAULT_PANEL_PALETTE.value],
|
|
417
|
-
[' Collapsed ', DEFAULT_PANEL_PALETTE.label],
|
|
418
|
-
[String(this.collapsed.size), this.collapsed.size > 0 ? DEFAULT_PANEL_PALETTE.warn : DEFAULT_PANEL_PALETTE.dim],
|
|
419
|
-
]),
|
|
420
|
-
],
|
|
421
|
-
},
|
|
422
|
-
],
|
|
423
|
-
section: {
|
|
424
|
-
title: 'Outline',
|
|
425
|
-
scrollableLines: visible.map((row, i) => {
|
|
426
|
-
const isSelected = i === this.selectedIndex;
|
|
427
|
-
const bgColor = isSelected ? '236' : '';
|
|
428
|
-
return row.kind === 'header'
|
|
429
|
-
? _renderHeader(width, row, isSelected, bgColor, this.collapsed)
|
|
430
|
-
: _renderSymbol(width, row, isSelected, bgColor);
|
|
431
|
-
}),
|
|
432
|
-
selectedIndex: this.selectedIndex,
|
|
433
|
-
scrollOffset: this.scrollOffset,
|
|
434
|
-
minRows: 8,
|
|
435
|
-
},
|
|
436
|
-
afterSections: [
|
|
437
|
-
{
|
|
438
|
-
title: 'Selected',
|
|
439
|
-
lines: (() => {
|
|
440
|
-
const selected = this.selectedRow();
|
|
441
|
-
return selected
|
|
442
|
-
? [
|
|
443
|
-
buildPanelLine(width, [
|
|
444
|
-
[' Kind ', DEFAULT_PANEL_PALETTE.label],
|
|
445
|
-
[selected.kind === 'header' ? selected.symbolKind : selected.symbol.kind, DEFAULT_PANEL_PALETTE.info],
|
|
446
|
-
[' Line ', DEFAULT_PANEL_PALETTE.label],
|
|
447
|
-
[String(selected.kind === 'header' ? selected.line : selected.symbol.line), DEFAULT_PANEL_PALETTE.value],
|
|
448
|
-
]),
|
|
449
|
-
buildPanelLine(width, [
|
|
450
|
-
[' Name ', DEFAULT_PANEL_PALETTE.label],
|
|
451
|
-
[selected.kind === 'header' ? selected.name : selected.symbol.name, DEFAULT_PANEL_PALETTE.value],
|
|
452
|
-
]),
|
|
453
|
-
]
|
|
454
|
-
: [];
|
|
455
|
-
})(),
|
|
456
|
-
},
|
|
457
|
-
],
|
|
458
|
-
});
|
|
459
|
-
this.scrollOffset = outlineSection.scrollOffset;
|
|
460
|
-
|
|
461
|
-
const selected = this.selectedRow();
|
|
462
|
-
return buildPanelWorkspace(width, height, {
|
|
463
|
-
title: ' Symbols',
|
|
464
|
-
intro: this.currentPath ? this.currentPath : 'Outline the current file into navigable symbols and lightweight parent/child structure.',
|
|
465
|
-
sections: [
|
|
466
|
-
{
|
|
467
|
-
title: 'Summary',
|
|
468
|
-
lines: [
|
|
469
|
-
buildPanelLine(width, [
|
|
470
|
-
[' Symbols ', DEFAULT_PANEL_PALETTE.label],
|
|
471
|
-
[String(this.symbols.length), DEFAULT_PANEL_PALETTE.value],
|
|
472
|
-
[' Collapsed ', DEFAULT_PANEL_PALETTE.label],
|
|
473
|
-
[String(this.collapsed.size), this.collapsed.size > 0 ? DEFAULT_PANEL_PALETTE.warn : DEFAULT_PANEL_PALETTE.dim],
|
|
474
|
-
]),
|
|
475
|
-
],
|
|
476
|
-
},
|
|
477
|
-
outlineSection.section,
|
|
478
|
-
{
|
|
479
|
-
title: 'Selected',
|
|
480
|
-
lines: selected
|
|
481
|
-
? [
|
|
482
|
-
buildPanelLine(width, [
|
|
483
|
-
[' Kind ', DEFAULT_PANEL_PALETTE.label],
|
|
484
|
-
[selected.kind === 'header' ? selected.symbolKind : selected.symbol.kind, DEFAULT_PANEL_PALETTE.info],
|
|
485
|
-
[' Line ', DEFAULT_PANEL_PALETTE.label],
|
|
486
|
-
[String(selected.kind === 'header' ? selected.line : selected.symbol.line), DEFAULT_PANEL_PALETTE.value],
|
|
487
|
-
]),
|
|
488
|
-
buildPanelLine(width, [
|
|
489
|
-
[' Name ', DEFAULT_PANEL_PALETTE.label],
|
|
490
|
-
[selected.kind === 'header' ? selected.name : selected.symbol.name, DEFAULT_PANEL_PALETTE.value],
|
|
491
|
-
]),
|
|
492
|
-
]
|
|
493
|
-
: [],
|
|
494
|
-
},
|
|
495
|
-
],
|
|
496
|
-
footerLines: [
|
|
497
|
-
buildKeyboardHints(width, [{ keys: '↑/↓', label: 'navigate' }, { keys: 'Space/←/→', label: 'collapse' }, { keys: 'Enter', label: 'jump to source →' }], DEFAULT_PANEL_PALETTE),
|
|
498
|
-
],
|
|
499
|
-
palette: DEFAULT_PANEL_PALETTE,
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// ── Private helpers ────────────────────────────────────────────────────────
|
|
504
|
-
|
|
505
|
-
private async _parseAndSync(path: string, source: string, langId: LangId, generation: number): Promise<void> {
|
|
506
|
-
try {
|
|
507
|
-
await this.treeSitter.initialize();
|
|
508
|
-
const language = await this.treeSitter.loadLanguage(langId);
|
|
509
|
-
if (!language) return;
|
|
510
|
-
const tree = await this.treeSitter.parse(path, source, langId);
|
|
511
|
-
if (!tree) return;
|
|
512
|
-
// A newer loadFile() call superseded this one — discard the stale result.
|
|
513
|
-
if (generation !== this.parseGeneration) return;
|
|
514
|
-
this.symbols = extractSymbolsFromTree(tree, language, langId, this.treeSitter);
|
|
515
|
-
this.selectedIndex = 0;
|
|
516
|
-
this.scrollOffset = 0;
|
|
517
|
-
this.markDirty();
|
|
518
|
-
} catch (err) {
|
|
519
|
-
logger.warn('SymbolOutlinePanel: tree-sitter parse failed', { path, error: summarizeError(err) });
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
private _clampScroll(totalRows: number): void {
|
|
524
|
-
// Ensure selected is within scroll view (assumes last known height ~ 20)
|
|
525
|
-
// We keep a conservative viewport window; render() uses this.scrollOffset.
|
|
526
|
-
const GUARD = 3;
|
|
527
|
-
if (this.selectedIndex < this.scrollOffset + GUARD) {
|
|
528
|
-
this.scrollOffset = Math.max(0, this.selectedIndex - GUARD);
|
|
529
|
-
}
|
|
530
|
-
// We don't know height here, so we defer bottom-clamp to render().
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
private _visibleRows(): VisibleRow[] {
|
|
534
|
-
return buildVisibleRows(this.symbols, this.collapsed);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
// ── Row types for rendering ──────────────────────────────────────────────────
|
|
539
|
-
|
|
540
|
-
type VisibleRow =
|
|
541
|
-
| { kind: 'header'; name: string; symbolKind: SymbolKind; line: number; hasChildren: boolean }
|
|
542
|
-
| { kind: 'symbol'; symbol: SymbolEntry; depth: number };
|
|
543
|
-
|
|
544
|
-
// ── Pure helpers ─────────────────────────────────────────────────────────────
|
|
545
|
-
|
|
546
|
-
/**
|
|
547
|
-
* Build the flat list of rows to render, respecting collapse state.
|
|
548
|
-
* Container symbols become header rows; their children are indented below.
|
|
549
|
-
*/
|
|
550
|
-
function buildVisibleRows(symbols: SymbolEntry[], collapsed: Set<string>): VisibleRow[] {
|
|
551
|
-
const rows: VisibleRow[] = [];
|
|
552
|
-
|
|
553
|
-
// Pre-compute children map to avoid O(n^2) scans inside the loop
|
|
554
|
-
const childrenByParent = new Map<string, SymbolEntry[]>();
|
|
555
|
-
for (const sym of symbols) {
|
|
556
|
-
if (sym.parentName) {
|
|
557
|
-
const arr = childrenByParent.get(sym.parentName);
|
|
558
|
-
if (arr) arr.push(sym);
|
|
559
|
-
else childrenByParent.set(sym.parentName, [sym]);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
for (const sym of symbols) {
|
|
564
|
-
if (sym.kind === 'class' || sym.kind === 'namespace') {
|
|
565
|
-
const children = childrenByParent.get(sym.name) ?? [];
|
|
566
|
-
const hasChildren = children.length > 0;
|
|
567
|
-
rows.push({ kind: 'header', name: sym.name, symbolKind: sym.kind, line: sym.line, hasChildren });
|
|
568
|
-
// If collapsed, skip children
|
|
569
|
-
if (collapsed.has(sym.name)) continue;
|
|
570
|
-
// Add children immediately after header
|
|
571
|
-
for (const child of children) {
|
|
572
|
-
rows.push({ kind: 'symbol', symbol: child, depth: 1 });
|
|
573
|
-
}
|
|
574
|
-
} else if (!sym.parentName) {
|
|
575
|
-
// Top-level non-container symbol
|
|
576
|
-
rows.push({ kind: 'symbol', symbol: sym, depth: 0 });
|
|
577
|
-
}
|
|
578
|
-
// Children with parentName are rendered under their header above
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
return rows;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
/** Render a container header row (class / namespace) via the shared tree row. */
|
|
585
|
-
function _renderHeader(
|
|
586
|
-
width: number,
|
|
587
|
-
row: Extract<VisibleRow, { kind: 'header' }>,
|
|
588
|
-
isSelected: boolean,
|
|
589
|
-
bgColor: string,
|
|
590
|
-
collapsed: Set<string>,
|
|
591
|
-
): Line {
|
|
592
|
-
const isCollapsed = collapsed.has(row.name);
|
|
593
|
-
return buildTreeRow(width, {
|
|
594
|
-
depth: 0,
|
|
595
|
-
label: row.name,
|
|
596
|
-
icon: KIND_ICONS[row.symbolKind],
|
|
597
|
-
expandable: row.hasChildren,
|
|
598
|
-
expanded: !isCollapsed,
|
|
599
|
-
labelColor: KIND_COLORS[row.symbolKind],
|
|
600
|
-
metadata: [{ text: `:${row.line}`, fg: DEFAULT_PANEL_PALETTE.dim }],
|
|
601
|
-
}, C, { selected: isSelected, selectedBg: bgColor || C.selectedBg });
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
/** Render a regular symbol row via the shared tree row. */
|
|
605
|
-
function _renderSymbol(
|
|
606
|
-
width: number,
|
|
607
|
-
row: Extract<VisibleRow, { kind: 'symbol' }>,
|
|
608
|
-
isSelected: boolean,
|
|
609
|
-
bgColor: string,
|
|
610
|
-
): Line {
|
|
611
|
-
const { symbol, depth } = row;
|
|
612
|
-
return buildTreeRow(width, {
|
|
613
|
-
depth,
|
|
614
|
-
label: symbol.name,
|
|
615
|
-
icon: KIND_ICONS[symbol.kind],
|
|
616
|
-
labelColor: KIND_COLORS[symbol.kind],
|
|
617
|
-
metadata: [{ text: `:${symbol.line}`, fg: DEFAULT_PANEL_PALETTE.dim }],
|
|
618
|
-
}, C, { selected: isSelected, selectedBg: bgColor || C.selectedBg });
|
|
619
|
-
}
|