dmux 5.3.0 → 5.5.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/README.md +2 -1
- package/dist/DmuxApp.d.ts.map +1 -1
- package/dist/DmuxApp.js +226 -51
- package/dist/DmuxApp.js.map +1 -1
- package/dist/FileBrowserApp.d.ts +4 -0
- package/dist/FileBrowserApp.d.ts.map +1 -0
- package/dist/FileBrowserApp.js +693 -0
- package/dist/FileBrowserApp.js.map +1 -0
- package/dist/actions/implementations/closeAction.d.ts.map +1 -1
- package/dist/actions/implementations/closeAction.js +47 -5
- package/dist/actions/implementations/closeAction.js.map +1 -1
- package/dist/actions/implementations/mergeAction.d.ts.map +1 -1
- package/dist/actions/implementations/mergeAction.js +51 -16
- package/dist/actions/implementations/mergeAction.js.map +1 -1
- package/dist/actions/implementations/viewAction.d.ts.map +1 -1
- package/dist/actions/implementations/viewAction.js +7 -0
- package/dist/actions/implementations/viewAction.js.map +1 -1
- package/dist/actions/index.d.ts.map +1 -1
- package/dist/actions/index.js +24 -0
- package/dist/actions/index.js.map +1 -1
- package/dist/actions/merge/multiMergeOrchestrator.js +1 -1
- package/dist/actions/merge/multiMergeOrchestrator.js.map +1 -1
- package/dist/actions/types.d.ts +19 -4
- package/dist/actions/types.d.ts.map +1 -1
- package/dist/actions/types.js +91 -0
- package/dist/actions/types.js.map +1 -1
- package/dist/components/indicators/Spinner.d.ts +2 -0
- package/dist/components/indicators/Spinner.d.ts.map +1 -1
- package/dist/components/indicators/Spinner.js +4 -4
- package/dist/components/indicators/Spinner.js.map +1 -1
- package/dist/components/panes/KebabMenu.d.ts +2 -2
- package/dist/components/panes/KebabMenu.d.ts.map +1 -1
- package/dist/components/panes/KebabMenu.js +9 -4
- package/dist/components/panes/KebabMenu.js.map +1 -1
- package/dist/components/panes/PaneCard.d.ts.map +1 -1
- package/dist/components/panes/PaneCard.js +20 -4
- package/dist/components/panes/PaneCard.js.map +1 -1
- package/dist/components/panes/PanesGrid.d.ts +1 -0
- package/dist/components/panes/PanesGrid.d.ts.map +1 -1
- package/dist/components/panes/PanesGrid.js +11 -3
- package/dist/components/panes/PanesGrid.js.map +1 -1
- package/dist/components/popups/agentChoicePopup.js +29 -24
- package/dist/components/popups/agentChoicePopup.js.map +1 -1
- package/dist/components/popups/agentChoiceSelection.d.ts +8 -0
- package/dist/components/popups/agentChoiceSelection.d.ts.map +1 -0
- package/dist/components/popups/agentChoiceSelection.js +14 -0
- package/dist/components/popups/agentChoiceSelection.js.map +1 -0
- package/dist/components/popups/kebabMenuPopup.js +9 -4
- package/dist/components/popups/kebabMenuPopup.js.map +1 -1
- package/dist/components/popups/notificationSoundsPopup.d.ts +25 -0
- package/dist/components/popups/notificationSoundsPopup.d.ts.map +1 -0
- package/dist/components/popups/notificationSoundsPopup.js +165 -0
- package/dist/components/popups/notificationSoundsPopup.js.map +1 -0
- package/dist/components/popups/settingsPopup.js +361 -26
- package/dist/components/popups/settingsPopup.js.map +1 -1
- package/dist/components/popups/shortcutsPopup.js +11 -5
- package/dist/components/popups/shortcutsPopup.js.map +1 -1
- package/dist/constants/layout.d.ts +9 -0
- package/dist/constants/layout.d.ts.map +1 -0
- package/dist/constants/layout.js +9 -0
- package/dist/constants/layout.js.map +1 -0
- package/dist/hooks/useActionSystem.d.ts +9 -5
- package/dist/hooks/useActionSystem.d.ts.map +1 -1
- package/dist/hooks/useActionSystem.js +21 -19
- package/dist/hooks/useActionSystem.js.map +1 -1
- package/dist/hooks/useInputHandling.d.ts +1 -0
- package/dist/hooks/useInputHandling.d.ts.map +1 -1
- package/dist/hooks/useInputHandling.js +499 -79
- package/dist/hooks/useInputHandling.js.map +1 -1
- package/dist/hooks/usePaneCreation.d.ts +4 -2
- package/dist/hooks/usePaneCreation.d.ts.map +1 -1
- package/dist/hooks/usePaneCreation.js +6 -0
- package/dist/hooks/usePaneCreation.js.map +1 -1
- package/dist/hooks/usePaneLoading.d.ts +1 -0
- package/dist/hooks/usePaneLoading.d.ts.map +1 -1
- package/dist/hooks/usePaneLoading.js +10 -7
- package/dist/hooks/usePaneLoading.js.map +1 -1
- package/dist/hooks/usePaneSync.js +2 -2
- package/dist/hooks/usePaneSync.js.map +1 -1
- package/dist/hooks/usePanes.d.ts.map +1 -1
- package/dist/hooks/usePanes.js +18 -4
- package/dist/hooks/usePanes.js.map +1 -1
- package/dist/hooks/useProjectActivity.d.ts +7 -0
- package/dist/hooks/useProjectActivity.d.ts.map +1 -0
- package/dist/hooks/useProjectActivity.js +79 -0
- package/dist/hooks/useProjectActivity.js.map +1 -0
- package/dist/hooks/useServices.d.ts +3 -0
- package/dist/hooks/useServices.d.ts.map +1 -1
- package/dist/hooks/useServices.js +4 -0
- package/dist/hooks/useServices.js.map +1 -1
- package/dist/index.js +59 -15
- package/dist/index.js.map +1 -1
- package/dist/layout/LayoutCalculator.d.ts.map +1 -1
- package/dist/layout/LayoutCalculator.js +4 -1
- package/dist/layout/LayoutCalculator.js.map +1 -1
- package/dist/services/DmuxAttentionService.d.ts +33 -0
- package/dist/services/DmuxAttentionService.d.ts.map +1 -0
- package/dist/services/DmuxAttentionService.js +172 -0
- package/dist/services/DmuxAttentionService.js.map +1 -0
- package/dist/services/DmuxFocusService.d.ts +58 -0
- package/dist/services/DmuxFocusService.d.ts.map +1 -0
- package/dist/services/DmuxFocusService.js +671 -0
- package/dist/services/DmuxFocusService.js.map +1 -0
- package/dist/services/PaneAnalyzer.d.ts +11 -2
- package/dist/services/PaneAnalyzer.d.ts.map +1 -1
- package/dist/services/PaneAnalyzer.js +88 -22
- package/dist/services/PaneAnalyzer.js.map +1 -1
- package/dist/services/PopupManager.d.ts +31 -14
- package/dist/services/PopupManager.d.ts.map +1 -1
- package/dist/services/PopupManager.js +147 -68
- package/dist/services/PopupManager.js.map +1 -1
- package/dist/services/StatusDetector.d.ts +14 -0
- package/dist/services/StatusDetector.d.ts.map +1 -1
- package/dist/services/StatusDetector.js +60 -12
- package/dist/services/StatusDetector.js.map +1 -1
- package/dist/services/TmuxHookManager.d.ts.map +1 -1
- package/dist/services/TmuxHookManager.js +4 -2
- package/dist/services/TmuxHookManager.js.map +1 -1
- package/dist/services/TmuxService.d.ts +37 -2
- package/dist/services/TmuxService.d.ts.map +1 -1
- package/dist/services/TmuxService.js +138 -16
- package/dist/services/TmuxService.js.map +1 -1
- package/dist/types/activity.d.ts +4 -0
- package/dist/types/activity.d.ts.map +1 -0
- package/dist/types/activity.js +2 -0
- package/dist/types/activity.js.map +1 -0
- package/dist/types.d.ts +18 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/attachAgent.d.ts +4 -0
- package/dist/utils/attachAgent.d.ts.map +1 -1
- package/dist/utils/attachAgent.js +18 -7
- package/dist/utils/attachAgent.js.map +1 -1
- package/dist/utils/controlPaneRecovery.d.ts +2 -0
- package/dist/utils/controlPaneRecovery.d.ts.map +1 -0
- package/dist/utils/controlPaneRecovery.js +156 -0
- package/dist/utils/controlPaneRecovery.js.map +1 -0
- package/dist/utils/devWatchExit.d.ts +2 -0
- package/dist/utils/devWatchExit.d.ts.map +1 -0
- package/dist/utils/devWatchExit.js +10 -0
- package/dist/utils/devWatchExit.js.map +1 -0
- package/dist/utils/dmuxCommand.d.ts +3 -0
- package/dist/utils/dmuxCommand.d.ts.map +1 -0
- package/dist/utils/dmuxCommand.js +18 -0
- package/dist/utils/dmuxCommand.js.map +1 -0
- package/dist/utils/fileBrowser.d.ts +61 -0
- package/dist/utils/fileBrowser.d.ts.map +1 -0
- package/dist/utils/fileBrowser.js +567 -0
- package/dist/utils/fileBrowser.js.map +1 -0
- package/dist/utils/focusDetection.d.ts +38 -0
- package/dist/utils/focusDetection.d.ts.map +1 -0
- package/dist/utils/focusDetection.js +57 -0
- package/dist/utils/focusDetection.js.map +1 -0
- package/dist/utils/generated-agents-doc.d.ts +1 -1
- package/dist/utils/generated-agents-doc.js +1 -1
- package/dist/utils/git.d.ts +4 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +15 -0
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/layoutManager.d.ts +5 -1
- package/dist/utils/layoutManager.d.ts.map +1 -1
- package/dist/utils/layoutManager.js +103 -26
- package/dist/utils/layoutManager.js.map +1 -1
- package/dist/utils/mergeTargets.d.ts +17 -0
- package/dist/utils/mergeTargets.d.ts.map +1 -0
- package/dist/utils/mergeTargets.js +132 -0
- package/dist/utils/mergeTargets.js.map +1 -0
- package/dist/utils/mergeValidation.d.ts.map +1 -1
- package/dist/utils/mergeValidation.js +12 -5
- package/dist/utils/mergeValidation.js.map +1 -1
- package/dist/utils/notificationSoundPreview.d.ts +10 -0
- package/dist/utils/notificationSoundPreview.d.ts.map +1 -0
- package/dist/utils/notificationSoundPreview.js +54 -0
- package/dist/utils/notificationSoundPreview.js.map +1 -0
- package/dist/utils/notificationSounds.d.ts +17 -0
- package/dist/utils/notificationSounds.d.ts.map +1 -0
- package/dist/utils/notificationSounds.js +123 -0
- package/dist/utils/notificationSounds.js.map +1 -0
- package/dist/utils/paneAttentionHeuristics.d.ts +4 -0
- package/dist/utils/paneAttentionHeuristics.d.ts.map +1 -0
- package/dist/utils/paneAttentionHeuristics.js +135 -0
- package/dist/utils/paneAttentionHeuristics.js.map +1 -0
- package/dist/utils/paneCreation.d.ts +3 -1
- package/dist/utils/paneCreation.d.ts.map +1 -1
- package/dist/utils/paneCreation.js +23 -5
- package/dist/utils/paneCreation.js.map +1 -1
- package/dist/utils/paneVisibility.d.ts +12 -0
- package/dist/utils/paneVisibility.d.ts.map +1 -0
- package/dist/utils/paneVisibility.js +60 -0
- package/dist/utils/paneVisibility.js.map +1 -0
- package/dist/utils/processShutdown.d.ts +4 -0
- package/dist/utils/processShutdown.d.ts.map +1 -0
- package/dist/utils/processShutdown.js +27 -0
- package/dist/utils/processShutdown.js.map +1 -0
- package/dist/utils/promptStore.d.ts.map +1 -1
- package/dist/utils/promptStore.js +6 -0
- package/dist/utils/promptStore.js.map +1 -1
- package/dist/utils/reopenWorktree.d.ts.map +1 -1
- package/dist/utils/reopenWorktree.js +8 -0
- package/dist/utils/reopenWorktree.js.map +1 -1
- package/dist/utils/runtimePaths.d.ts +1 -0
- package/dist/utils/runtimePaths.d.ts.map +1 -1
- package/dist/utils/runtimePaths.js +3 -0
- package/dist/utils/runtimePaths.js.map +1 -1
- package/dist/utils/settingsManager.d.ts +3 -0
- package/dist/utils/settingsManager.d.ts.map +1 -1
- package/dist/utils/settingsManager.js +203 -11
- package/dist/utils/settingsManager.js.map +1 -1
- package/dist/utils/tmux.d.ts +5 -1
- package/dist/utils/tmux.d.ts.map +1 -1
- package/dist/utils/tmux.js +23 -5
- package/dist/utils/tmux.js.map +1 -1
- package/dist/utils/tmuxConfigOnboarding.js +1 -1
- package/dist/utils/tmuxConfigOnboarding.js.map +1 -1
- package/dist/utils/tmuxHookCommands.d.ts +14 -0
- package/dist/utils/tmuxHookCommands.d.ts.map +1 -0
- package/dist/utils/tmuxHookCommands.js +30 -0
- package/dist/utils/tmuxHookCommands.js.map +1 -0
- package/dist/utils/tmuxRuntimeCompatibility.d.ts +11 -0
- package/dist/utils/tmuxRuntimeCompatibility.d.ts.map +1 -0
- package/dist/utils/tmuxRuntimeCompatibility.js +71 -0
- package/dist/utils/tmuxRuntimeCompatibility.js.map +1 -0
- package/dist/utils/worktreeMetadata.d.ts +9 -0
- package/dist/utils/worktreeMetadata.d.ts.map +1 -0
- package/dist/utils/worktreeMetadata.js +60 -0
- package/dist/utils/worktreeMetadata.js.map +1 -0
- package/dist/workers/PaneWorker.js +64 -128
- package/dist/workers/PaneWorker.js.map +1 -1
- package/dist/workers/WorkerMessages.d.ts +4 -1
- package/dist/workers/WorkerMessages.d.ts.map +1 -1
- package/dist/workers/WorkerMessages.js.map +1 -1
- package/native/macos/dmux-helper-Info.plist +30 -0
- package/native/macos/dmux-helper-icon.png +0 -0
- package/native/macos/dmux-helper.swift +831 -0
- package/native/macos/sounds/dmux-braam.caf +0 -0
- package/native/macos/sounds/dmux-brass.caf +0 -0
- package/native/macos/sounds/dmux-ding-bell.caf +0 -0
- package/native/macos/sounds/dmux-future.caf +0 -0
- package/native/macos/sounds/dmux-harp.caf +0 -0
- package/native/macos/sounds/dmux-quiet-bells.caf +0 -0
- package/native/macos/sounds/dmux-sonar.caf +0 -0
- package/native/macos/sounds/dmux-success.caf +0 -0
- package/native/macos/sounds/dmux-triumphant-trumpet.caf +0 -0
- package/native/macos/sounds/dmux-war-horn.caf +0 -0
- package/package.json +3 -1
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { Box, Text, useInput, useStdout } from 'ink';
|
|
3
|
+
import stringWidth from 'string-width';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import { buildBrowserSearchEntries, buildBrowserTree, computeModifiedTimes, flattenBrowserTree, getAncestorPaths, getCurrentDirectoryPath, getStatusColor, loadBrowserSnapshot, loadCodePreview, loadDiffPreview, } from './utils/fileBrowser.js';
|
|
7
|
+
import { POPUP_CONFIG } from './components/popups/config.js';
|
|
8
|
+
import { COLORS } from './theme/colors.js';
|
|
9
|
+
const SORT_OPTIONS = [
|
|
10
|
+
{ id: 'sort-name', label: 'Sort by name', description: 'Alphabetical tree order' },
|
|
11
|
+
{ id: 'sort-modified', label: 'Sort by modified time', description: 'Recently touched files first' },
|
|
12
|
+
{ id: 'sort-status', label: 'Sort by git status', description: 'Changed files first' },
|
|
13
|
+
{ id: 'filter-all', label: 'Show all files', description: 'Tracked and untracked files' },
|
|
14
|
+
{ id: 'filter-diffed', label: 'Show changed files only', description: 'Only files with git changes' },
|
|
15
|
+
];
|
|
16
|
+
function clipToWidth(value, maxWidth) {
|
|
17
|
+
if (maxWidth <= 0 || stringWidth(value) <= maxWidth) {
|
|
18
|
+
return maxWidth <= 0 ? '' : value;
|
|
19
|
+
}
|
|
20
|
+
let clipped = '';
|
|
21
|
+
let width = 0;
|
|
22
|
+
for (const char of value) {
|
|
23
|
+
const charWidth = stringWidth(char);
|
|
24
|
+
if (width + charWidth > Math.max(1, maxWidth - 1)) {
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
clipped += char;
|
|
28
|
+
width += charWidth;
|
|
29
|
+
}
|
|
30
|
+
return `${clipped}…`;
|
|
31
|
+
}
|
|
32
|
+
function clipFromLeft(value, maxWidth) {
|
|
33
|
+
if (maxWidth <= 0 || stringWidth(value) <= maxWidth) {
|
|
34
|
+
return maxWidth <= 0 ? '' : value;
|
|
35
|
+
}
|
|
36
|
+
const ellipsis = '…';
|
|
37
|
+
let clipped = '';
|
|
38
|
+
for (const char of Array.from(value).reverse()) {
|
|
39
|
+
if (stringWidth(`${ellipsis}${clipped}`) + stringWidth(char) > maxWidth) {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
clipped = `${char}${clipped}`;
|
|
43
|
+
}
|
|
44
|
+
return `${ellipsis}${clipped}`;
|
|
45
|
+
}
|
|
46
|
+
function clipPathToWidth(value, maxWidth) {
|
|
47
|
+
if (maxWidth <= 0 || stringWidth(value) <= maxWidth) {
|
|
48
|
+
return maxWidth <= 0 ? '' : value;
|
|
49
|
+
}
|
|
50
|
+
const segments = value.split('/');
|
|
51
|
+
let visibleTail = segments.pop() || value;
|
|
52
|
+
while (segments.length > 0) {
|
|
53
|
+
const candidate = `${segments[segments.length - 1]}/${visibleTail}`;
|
|
54
|
+
if (stringWidth(`…/${candidate}`) > maxWidth) {
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
visibleTail = candidate;
|
|
58
|
+
segments.pop();
|
|
59
|
+
}
|
|
60
|
+
const prefixed = `…/${visibleTail}`;
|
|
61
|
+
if (stringWidth(prefixed) <= maxWidth) {
|
|
62
|
+
return prefixed;
|
|
63
|
+
}
|
|
64
|
+
return clipFromLeft(value, maxWidth);
|
|
65
|
+
}
|
|
66
|
+
function getVisibleRange(selectedIndex, totalItems, maxVisible) {
|
|
67
|
+
if (totalItems <= maxVisible) {
|
|
68
|
+
return { start: 0, end: totalItems };
|
|
69
|
+
}
|
|
70
|
+
const half = Math.floor(maxVisible / 2);
|
|
71
|
+
let start = Math.max(0, selectedIndex - half);
|
|
72
|
+
let end = Math.min(totalItems, start + maxVisible);
|
|
73
|
+
if (end - start < maxVisible) {
|
|
74
|
+
start = Math.max(0, end - maxVisible);
|
|
75
|
+
}
|
|
76
|
+
return { start, end };
|
|
77
|
+
}
|
|
78
|
+
function getTrailingRowCount(totalRows, renderedRows) {
|
|
79
|
+
return Math.max(0, totalRows - renderedRows);
|
|
80
|
+
}
|
|
81
|
+
function renderSingleLine(value) {
|
|
82
|
+
return value.length > 0 ? value : ' ';
|
|
83
|
+
}
|
|
84
|
+
function readTerminalSize(stdout) {
|
|
85
|
+
return {
|
|
86
|
+
columns: process.stdout.columns ?? stdout?.columns ?? 120,
|
|
87
|
+
rows: process.stdout.rows ?? stdout?.rows ?? 40,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function openInSystem(targetPath) {
|
|
91
|
+
return new Promise((resolve, reject) => {
|
|
92
|
+
let command = '';
|
|
93
|
+
let args = [];
|
|
94
|
+
if (process.platform === 'darwin') {
|
|
95
|
+
command = 'open';
|
|
96
|
+
args = [targetPath];
|
|
97
|
+
}
|
|
98
|
+
else if (process.platform === 'win32') {
|
|
99
|
+
command = 'cmd';
|
|
100
|
+
args = ['/c', 'start', '', targetPath];
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
command = 'xdg-open';
|
|
104
|
+
args = [targetPath];
|
|
105
|
+
}
|
|
106
|
+
const child = spawn(command, args, {
|
|
107
|
+
detached: true,
|
|
108
|
+
stdio: 'ignore',
|
|
109
|
+
});
|
|
110
|
+
child.once('error', reject);
|
|
111
|
+
child.once('spawn', resolve);
|
|
112
|
+
child.unref();
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function getSortOptionIndex(sortMode, filterMode) {
|
|
116
|
+
if (filterMode === 'diffed') {
|
|
117
|
+
return SORT_OPTIONS.findIndex((option) => option.id === 'filter-diffed');
|
|
118
|
+
}
|
|
119
|
+
return SORT_OPTIONS.findIndex((option) => option.id === `sort-${sortMode}`);
|
|
120
|
+
}
|
|
121
|
+
function isFilterTypingInput(input, key) {
|
|
122
|
+
if (!input) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
if (key.ctrl || key.meta || key.return || key.tab || key.escape) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
if (key.upArrow
|
|
129
|
+
|| key.downArrow
|
|
130
|
+
|| key.leftArrow
|
|
131
|
+
|| key.rightArrow
|
|
132
|
+
|| key.pageUp
|
|
133
|
+
|| key.pageDown) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
function getSearchEntryIcon(entry) {
|
|
139
|
+
if (entry.type === 'directory') {
|
|
140
|
+
return '';
|
|
141
|
+
}
|
|
142
|
+
return '';
|
|
143
|
+
}
|
|
144
|
+
function getSearchEntryColor(entry) {
|
|
145
|
+
if (!entry.exists) {
|
|
146
|
+
return COLORS.error;
|
|
147
|
+
}
|
|
148
|
+
if (entry.type === 'directory') {
|
|
149
|
+
return 'blue';
|
|
150
|
+
}
|
|
151
|
+
return 'white';
|
|
152
|
+
}
|
|
153
|
+
const FileBrowserApp = () => {
|
|
154
|
+
const rootPath = process.cwd();
|
|
155
|
+
const projectLabel = path.basename(rootPath);
|
|
156
|
+
const { stdout } = useStdout();
|
|
157
|
+
const [terminalSize, setTerminalSize] = useState(() => readTerminalSize(stdout));
|
|
158
|
+
const terminalHeight = terminalSize.rows;
|
|
159
|
+
const terminalWidth = terminalSize.columns;
|
|
160
|
+
const [snapshot, setSnapshot] = useState(() => loadBrowserSnapshot(rootPath));
|
|
161
|
+
const [sortMode, setSortMode] = useState('name');
|
|
162
|
+
const [filterMode, setFilterMode] = useState('all');
|
|
163
|
+
const [filterQuery, setFilterQuery] = useState('');
|
|
164
|
+
const [listFocused, setListFocused] = useState(false);
|
|
165
|
+
const [expandedPaths, setExpandedPaths] = useState(() => new Set());
|
|
166
|
+
const [selectedPath, setSelectedPath] = useState(null);
|
|
167
|
+
const [sortMenuOpen, setSortMenuOpen] = useState(false);
|
|
168
|
+
const [sortMenuIndex, setSortMenuIndex] = useState(() => getSortOptionIndex('name', 'all'));
|
|
169
|
+
const [viewerPath, setViewerPath] = useState(null);
|
|
170
|
+
const [viewerMode, setViewerMode] = useState('code');
|
|
171
|
+
const [viewerScroll, setViewerScroll] = useState(0);
|
|
172
|
+
const [statusMessage, setStatusMessage] = useState('');
|
|
173
|
+
const [modifiedTimes, setModifiedTimes] = useState(null);
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
if (!statusMessage) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const timer = setTimeout(() => setStatusMessage(''), 2500);
|
|
179
|
+
return () => clearTimeout(timer);
|
|
180
|
+
}, [statusMessage]);
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
const refreshTerminalSize = () => {
|
|
183
|
+
setTerminalSize((current) => {
|
|
184
|
+
const next = readTerminalSize(stdout);
|
|
185
|
+
if (current.columns === next.columns && current.rows === next.rows) {
|
|
186
|
+
return current;
|
|
187
|
+
}
|
|
188
|
+
return next;
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
// A freshly split tmux pane can report a transient width on first paint.
|
|
192
|
+
// Refresh once immediately and again shortly after mount to pick up the final size
|
|
193
|
+
// without waiting for user input.
|
|
194
|
+
refreshTerminalSize();
|
|
195
|
+
const refreshTimers = [16, 80, 180].map((delay) => setTimeout(refreshTerminalSize, delay));
|
|
196
|
+
process.stdout.on('resize', refreshTerminalSize);
|
|
197
|
+
return () => {
|
|
198
|
+
refreshTimers.forEach((timer) => clearTimeout(timer));
|
|
199
|
+
process.stdout.off('resize', refreshTerminalSize);
|
|
200
|
+
};
|
|
201
|
+
}, [stdout]);
|
|
202
|
+
const filterActive = filterQuery.trim().length > 0;
|
|
203
|
+
const treeNodes = useMemo(() => buildBrowserTree(snapshot, {
|
|
204
|
+
sortMode,
|
|
205
|
+
filterMode,
|
|
206
|
+
modifiedTimes: sortMode === 'modified' ? modifiedTimes || undefined : undefined,
|
|
207
|
+
filterQuery: '',
|
|
208
|
+
activePath: viewerPath || selectedPath,
|
|
209
|
+
}), [snapshot, sortMode, filterMode, modifiedTimes, viewerPath, selectedPath]);
|
|
210
|
+
const visibleEntries = useMemo(() => {
|
|
211
|
+
if (filterActive) {
|
|
212
|
+
return buildBrowserSearchEntries(snapshot, {
|
|
213
|
+
sortMode,
|
|
214
|
+
filterMode,
|
|
215
|
+
modifiedTimes: sortMode === 'modified' ? modifiedTimes || undefined : undefined,
|
|
216
|
+
filterQuery,
|
|
217
|
+
activePath: viewerPath || selectedPath,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return flattenBrowserTree(treeNodes, expandedPaths);
|
|
221
|
+
}, [
|
|
222
|
+
filterActive,
|
|
223
|
+
snapshot,
|
|
224
|
+
sortMode,
|
|
225
|
+
filterMode,
|
|
226
|
+
modifiedTimes,
|
|
227
|
+
filterQuery,
|
|
228
|
+
viewerPath,
|
|
229
|
+
selectedPath,
|
|
230
|
+
treeNodes,
|
|
231
|
+
expandedPaths,
|
|
232
|
+
]);
|
|
233
|
+
const entryByPath = useMemo(() => new Map(visibleEntries.map((entry) => [entry.path, entry])), [visibleEntries]);
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
if (visibleEntries.length === 0) {
|
|
236
|
+
setSelectedPath(null);
|
|
237
|
+
setListFocused(false);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (!selectedPath || !entryByPath.has(selectedPath)) {
|
|
241
|
+
setSelectedPath(visibleEntries[0].path);
|
|
242
|
+
}
|
|
243
|
+
}, [visibleEntries, selectedPath, entryByPath]);
|
|
244
|
+
const selectedIndex = selectedPath
|
|
245
|
+
? Math.max(0, visibleEntries.findIndex((entry) => entry.path === selectedPath))
|
|
246
|
+
: 0;
|
|
247
|
+
const selectedEntry = visibleEntries[selectedIndex];
|
|
248
|
+
const viewerFile = useMemo(() => snapshot.files.find((file) => file.path === viewerPath), [snapshot, viewerPath]);
|
|
249
|
+
const previewLines = useMemo(() => {
|
|
250
|
+
if (!viewerPath) {
|
|
251
|
+
return [];
|
|
252
|
+
}
|
|
253
|
+
if (viewerMode === 'diff') {
|
|
254
|
+
return loadDiffPreview(rootPath, viewerPath, viewerFile?.statusLabel || '');
|
|
255
|
+
}
|
|
256
|
+
return loadCodePreview(rootPath, viewerPath);
|
|
257
|
+
}, [rootPath, viewerPath, viewerMode, viewerFile?.statusLabel]);
|
|
258
|
+
const headerRows = 2;
|
|
259
|
+
const searchRows = 3;
|
|
260
|
+
const footerRows = 2;
|
|
261
|
+
const contentBoxHeight = Math.max(10, terminalHeight - headerRows - searchRows - footerRows);
|
|
262
|
+
const contentRows = Math.max(6, contentBoxHeight);
|
|
263
|
+
const listBodyRows = Math.max(1, contentRows - 1);
|
|
264
|
+
const viewerBodyRows = Math.max(4, contentRows - 2);
|
|
265
|
+
const currentViewerMaxOffset = Math.max(0, previewLines.length - viewerBodyRows);
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
setViewerScroll((current) => Math.min(current, currentViewerMaxOffset));
|
|
268
|
+
}, [currentViewerMaxOffset]);
|
|
269
|
+
const refreshSnapshot = () => {
|
|
270
|
+
const nextSnapshot = loadBrowserSnapshot(rootPath);
|
|
271
|
+
setSnapshot(nextSnapshot);
|
|
272
|
+
if (sortMode === 'modified') {
|
|
273
|
+
setModifiedTimes(computeModifiedTimes(rootPath, nextSnapshot.files.map((file) => file.path)));
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
setModifiedTimes(null);
|
|
277
|
+
}
|
|
278
|
+
setStatusMessage('Refreshed');
|
|
279
|
+
};
|
|
280
|
+
const ensureModifiedSortData = (nextSnapshot = snapshot) => {
|
|
281
|
+
if (modifiedTimes) {
|
|
282
|
+
return modifiedTimes;
|
|
283
|
+
}
|
|
284
|
+
const nextTimes = computeModifiedTimes(rootPath, nextSnapshot.files.map((file) => file.path));
|
|
285
|
+
setModifiedTimes(nextTimes);
|
|
286
|
+
return nextTimes;
|
|
287
|
+
};
|
|
288
|
+
const selectPathAndExpand = (nextPath) => {
|
|
289
|
+
const ancestors = getAncestorPaths(nextPath);
|
|
290
|
+
setExpandedPaths((current) => {
|
|
291
|
+
const next = new Set(current);
|
|
292
|
+
ancestors.forEach((ancestor) => next.add(ancestor));
|
|
293
|
+
return next;
|
|
294
|
+
});
|
|
295
|
+
setSelectedPath(nextPath);
|
|
296
|
+
};
|
|
297
|
+
const revealDirectoryFromSearch = (directoryPath) => {
|
|
298
|
+
setExpandedPaths((current) => {
|
|
299
|
+
const next = new Set(current);
|
|
300
|
+
getAncestorPaths(directoryPath).forEach((ancestor) => next.add(ancestor));
|
|
301
|
+
next.add(directoryPath);
|
|
302
|
+
return next;
|
|
303
|
+
});
|
|
304
|
+
setSelectedPath(directoryPath);
|
|
305
|
+
setFilterQuery('');
|
|
306
|
+
setListFocused(true);
|
|
307
|
+
};
|
|
308
|
+
const openViewer = (nextPath) => {
|
|
309
|
+
const file = snapshot.files.find((candidate) => candidate.path === nextPath);
|
|
310
|
+
selectPathAndExpand(nextPath);
|
|
311
|
+
setViewerPath(nextPath);
|
|
312
|
+
setViewerMode(file?.exists === false ? 'diff' : 'code');
|
|
313
|
+
setViewerScroll(0);
|
|
314
|
+
setSortMenuOpen(false);
|
|
315
|
+
setListFocused(true);
|
|
316
|
+
};
|
|
317
|
+
const toggleExpanded = (entry) => {
|
|
318
|
+
if (entry.type !== 'directory') {
|
|
319
|
+
openViewer(entry.path);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (filterActive) {
|
|
323
|
+
revealDirectoryFromSearch(entry.path);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
setExpandedPaths((current) => {
|
|
327
|
+
const next = new Set(current);
|
|
328
|
+
if (next.has(entry.path)) {
|
|
329
|
+
next.delete(entry.path);
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
next.add(entry.path);
|
|
333
|
+
}
|
|
334
|
+
return next;
|
|
335
|
+
});
|
|
336
|
+
};
|
|
337
|
+
const handleOpenCurrentDirectory = async () => {
|
|
338
|
+
const activeEntry = viewerPath
|
|
339
|
+
? {
|
|
340
|
+
path: viewerPath,
|
|
341
|
+
parentPath: viewerFile?.parentPath || null,
|
|
342
|
+
type: 'file',
|
|
343
|
+
}
|
|
344
|
+
: selectedEntry;
|
|
345
|
+
const targetPath = getCurrentDirectoryPath(rootPath, activeEntry);
|
|
346
|
+
try {
|
|
347
|
+
await openInSystem(targetPath);
|
|
348
|
+
setStatusMessage(`Opened ${targetPath}`);
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
setStatusMessage(`Failed to open ${targetPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
const applySortSelection = (optionId) => {
|
|
355
|
+
if (optionId === 'sort-name') {
|
|
356
|
+
setSortMode('name');
|
|
357
|
+
setSortMenuOpen(false);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (optionId === 'sort-modified') {
|
|
361
|
+
ensureModifiedSortData();
|
|
362
|
+
setSortMode('modified');
|
|
363
|
+
setSortMenuOpen(false);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (optionId === 'sort-status') {
|
|
367
|
+
setSortMode('status');
|
|
368
|
+
setSortMenuOpen(false);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
if (optionId === 'filter-all') {
|
|
372
|
+
setFilterMode('all');
|
|
373
|
+
setSortMenuOpen(false);
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
if (optionId === 'filter-diffed') {
|
|
377
|
+
setFilterMode('diffed');
|
|
378
|
+
setSortMenuOpen(false);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
const backOutFromTree = () => {
|
|
382
|
+
if (filterActive) {
|
|
383
|
+
setFilterQuery('');
|
|
384
|
+
setListFocused(false);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (!listFocused) {
|
|
388
|
+
setStatusMessage('File browser stays open. Use pane controls to close it.');
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (!selectedEntry) {
|
|
392
|
+
setListFocused(false);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
if (selectedEntry.type === 'directory' && selectedEntry.isExpanded) {
|
|
396
|
+
setExpandedPaths((current) => {
|
|
397
|
+
const next = new Set(current);
|
|
398
|
+
next.delete(selectedEntry.path);
|
|
399
|
+
return next;
|
|
400
|
+
});
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
if (selectedEntry.parentPath) {
|
|
404
|
+
setSelectedPath(selectedEntry.parentPath);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
setListFocused(false);
|
|
408
|
+
};
|
|
409
|
+
useInput(async (input, key) => {
|
|
410
|
+
if (key.ctrl && input === 'c') {
|
|
411
|
+
process.exit(0);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (viewerPath) {
|
|
415
|
+
if (key.escape) {
|
|
416
|
+
setViewerPath(null);
|
|
417
|
+
setViewerScroll(0);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
if (input === 'd' || key.tab) {
|
|
421
|
+
setViewerMode((current) => (current === 'code' ? 'diff' : 'code'));
|
|
422
|
+
setViewerScroll(0);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (input === 'o') {
|
|
426
|
+
await handleOpenCurrentDirectory();
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
if (key.upArrow) {
|
|
430
|
+
setViewerScroll((current) => Math.max(0, current - 1));
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (key.downArrow) {
|
|
434
|
+
setViewerScroll((current) => Math.min(currentViewerMaxOffset, current + 1));
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
if (key.pageUp) {
|
|
438
|
+
setViewerScroll((current) => Math.max(0, current - viewerBodyRows));
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (key.pageDown) {
|
|
442
|
+
setViewerScroll((current) => Math.min(currentViewerMaxOffset, current + viewerBodyRows));
|
|
443
|
+
}
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
446
|
+
if (sortMenuOpen) {
|
|
447
|
+
if (key.escape) {
|
|
448
|
+
setSortMenuOpen(false);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
if (key.upArrow) {
|
|
452
|
+
setSortMenuIndex((current) => Math.max(0, current - 1));
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
if (key.downArrow) {
|
|
456
|
+
setSortMenuIndex((current) => Math.min(SORT_OPTIONS.length - 1, current + 1));
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (key.return) {
|
|
460
|
+
applySortSelection(SORT_OPTIONS[sortMenuIndex]?.id || '');
|
|
461
|
+
}
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
if (key.backspace || key.delete || input === '\x7f' || input === '\x08') {
|
|
465
|
+
if (filterQuery.length > 0) {
|
|
466
|
+
setFilterQuery((current) => current.slice(0, -1));
|
|
467
|
+
}
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (key.ctrl && input === 'u') {
|
|
471
|
+
setFilterQuery('');
|
|
472
|
+
setListFocused(false);
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
if (key.escape) {
|
|
476
|
+
backOutFromTree();
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (input === 'S') {
|
|
480
|
+
setSortMenuIndex(getSortOptionIndex(sortMode, filterMode));
|
|
481
|
+
setSortMenuOpen(true);
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (input === 'O') {
|
|
485
|
+
await handleOpenCurrentDirectory();
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
if (input === 'R') {
|
|
489
|
+
refreshSnapshot();
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
if (input === 'P') {
|
|
493
|
+
setListFocused(false);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
if (isFilterTypingInput(input, key)) {
|
|
497
|
+
setFilterQuery((current) => `${current}${input}`);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (!selectedEntry) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (key.upArrow) {
|
|
504
|
+
if (!listFocused) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (selectedIndex <= 0) {
|
|
508
|
+
setListFocused(false);
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
setSelectedPath(visibleEntries[selectedIndex - 1]?.path || selectedEntry.path);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (key.downArrow) {
|
|
515
|
+
if (!listFocused) {
|
|
516
|
+
if (visibleEntries.length > 0) {
|
|
517
|
+
setListFocused(true);
|
|
518
|
+
setSelectedPath(visibleEntries[0].path);
|
|
519
|
+
}
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const nextIndex = Math.min(visibleEntries.length - 1, selectedIndex + 1);
|
|
523
|
+
setSelectedPath(visibleEntries[nextIndex]?.path || selectedEntry.path);
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (key.leftArrow) {
|
|
527
|
+
if (!listFocused) {
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (filterActive) {
|
|
531
|
+
setListFocused(false);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
if (selectedEntry.type === 'directory' && selectedEntry.isExpanded) {
|
|
535
|
+
setExpandedPaths((current) => {
|
|
536
|
+
const next = new Set(current);
|
|
537
|
+
next.delete(selectedEntry.path);
|
|
538
|
+
return next;
|
|
539
|
+
});
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (selectedEntry.parentPath) {
|
|
543
|
+
setSelectedPath(selectedEntry.parentPath);
|
|
544
|
+
}
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
if (key.rightArrow) {
|
|
548
|
+
if (!listFocused) {
|
|
549
|
+
if (visibleEntries.length > 0) {
|
|
550
|
+
setListFocused(true);
|
|
551
|
+
}
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (selectedEntry.type === 'directory') {
|
|
555
|
+
if (filterActive) {
|
|
556
|
+
revealDirectoryFromSearch(selectedEntry.path);
|
|
557
|
+
}
|
|
558
|
+
else if (!selectedEntry.isExpanded) {
|
|
559
|
+
setExpandedPaths((current) => new Set(current).add(selectedEntry.path));
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
const firstChild = visibleEntries[selectedIndex + 1];
|
|
563
|
+
if (firstChild && firstChild.parentPath === selectedEntry.path) {
|
|
564
|
+
setSelectedPath(firstChild.path);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
else {
|
|
569
|
+
openViewer(selectedEntry.path);
|
|
570
|
+
}
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (key.return) {
|
|
574
|
+
if (!listFocused) {
|
|
575
|
+
if (visibleEntries.length > 0) {
|
|
576
|
+
setListFocused(true);
|
|
577
|
+
}
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
toggleExpanded(selectedEntry);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
const listRange = getVisibleRange(selectedIndex, visibleEntries.length, listBodyRows);
|
|
584
|
+
const visibleListItems = visibleEntries.slice(listRange.start, listRange.end);
|
|
585
|
+
const visiblePreviewLines = previewLines.slice(viewerScroll, viewerScroll + viewerBodyRows);
|
|
586
|
+
const frameWidth = Math.max(30, terminalWidth - 2);
|
|
587
|
+
const searchWidth = frameWidth;
|
|
588
|
+
const searchInnerWidth = Math.max(24, searchWidth - 2);
|
|
589
|
+
const contentWidth = frameWidth;
|
|
590
|
+
const rowMarkerWidth = 2;
|
|
591
|
+
const searchIconWidth = filterActive ? 2 : 0;
|
|
592
|
+
const statusColumnWidth = 4;
|
|
593
|
+
const itemLabelWidth = Math.max(10, contentWidth - rowMarkerWidth - searchIconWidth - statusColumnWidth);
|
|
594
|
+
const filterFocused = !viewerPath && !sortMenuOpen && !listFocused;
|
|
595
|
+
const filterDisplay = filterQuery || 'Search files and directories';
|
|
596
|
+
const filterCursor = filterFocused ? '|' : '';
|
|
597
|
+
const listTrailingRows = getTrailingRowCount(listBodyRows, visibleListItems.length);
|
|
598
|
+
const viewerTrailingRows = getTrailingRowCount(viewerBodyRows, visiblePreviewLines.length);
|
|
599
|
+
const sortMenuRenderedRows = SORT_OPTIONS.length + 1;
|
|
600
|
+
const sortTrailingRows = getTrailingRowCount(contentRows, sortMenuRenderedRows);
|
|
601
|
+
const sectionTitle = viewerPath
|
|
602
|
+
? ' Quick View'
|
|
603
|
+
: sortMenuOpen
|
|
604
|
+
? ' Sort and Filter'
|
|
605
|
+
: filterActive
|
|
606
|
+
? ' Search Results'
|
|
607
|
+
: ' Explorer';
|
|
608
|
+
const sectionSummary = viewerPath
|
|
609
|
+
? `${viewerMode === 'code' ? 'Code view' : 'Diff view'} • Lines ${Math.min(viewerScroll + 1, previewLines.length)}-${Math.min(viewerScroll + viewerBodyRows, previewLines.length)} of ${previewLines.length}`
|
|
610
|
+
: sortMenuOpen
|
|
611
|
+
? 'Choose a sort or filter mode'
|
|
612
|
+
: `${visibleEntries.length} ${filterActive ? 'matches' : visibleEntries.length === 1 ? 'item' : 'items'} • sort: ${sortMode} • ${filterMode === 'diffed' ? 'changed only' : 'all files'}`;
|
|
613
|
+
const footerHelp = viewerPath
|
|
614
|
+
? 'Esc back • d toggle code/diff • o open directory • PgUp/PgDn scroll'
|
|
615
|
+
: sortMenuOpen
|
|
616
|
+
? '↑↓ choose • Enter apply • Esc back'
|
|
617
|
+
: 'Type to filter • ↓ focus list • Enter open • Shift+S sort • Shift+O open dir • Shift+R refresh • Esc back';
|
|
618
|
+
return (React.createElement(Box, { flexDirection: "column", paddingX: 1, width: terminalWidth },
|
|
619
|
+
React.createElement(Box, { flexDirection: "column", width: frameWidth },
|
|
620
|
+
React.createElement(Box, { width: frameWidth },
|
|
621
|
+
React.createElement(Text, { bold: true, color: COLORS.accent, wrap: "truncate-end" }, renderSingleLine(`Files: ${projectLabel}`))),
|
|
622
|
+
React.createElement(Box, { width: frameWidth },
|
|
623
|
+
React.createElement(Text, { dimColor: true, wrap: "truncate-end" }, renderSingleLine(clipFromLeft(rootPath, frameWidth))))),
|
|
624
|
+
React.createElement(Box, { borderStyle: POPUP_CONFIG.borderStyle, borderColor: filterFocused ? POPUP_CONFIG.inputBorderColor : POPUP_CONFIG.borderColor, width: searchWidth, height: searchRows, flexDirection: "column" },
|
|
625
|
+
React.createElement(Box, { width: searchInnerWidth },
|
|
626
|
+
React.createElement(Text, { bold: true, color: filterFocused ? POPUP_CONFIG.inputBorderColor : POPUP_CONFIG.borderColor },
|
|
627
|
+
"\uF002",
|
|
628
|
+
' '),
|
|
629
|
+
React.createElement(Text, { color: filterFocused ? 'white' : undefined, dimColor: !filterQuery, wrap: "truncate-end" }, renderSingleLine(clipToWidth(`${filterDisplay}${filterQuery ? filterCursor : filterFocused ? filterCursor : ''}`, searchInnerWidth - 2))))),
|
|
630
|
+
viewerPath ? (React.createElement(Box, { flexDirection: "column", width: contentWidth, height: contentBoxHeight },
|
|
631
|
+
React.createElement(Box, { width: contentWidth },
|
|
632
|
+
React.createElement(Text, { bold: true, color: "yellow", wrap: "truncate-end" }, renderSingleLine(clipToWidth(`${sectionTitle} • ${viewerPath}`, contentWidth)))),
|
|
633
|
+
React.createElement(Box, { width: contentWidth },
|
|
634
|
+
React.createElement(Text, { dimColor: true, wrap: "truncate-end" }, renderSingleLine(clipToWidth(sectionSummary, contentWidth)))),
|
|
635
|
+
visiblePreviewLines.map((line, index) => (React.createElement(Box, { key: `${viewerScroll + index}`, width: contentWidth },
|
|
636
|
+
React.createElement(Text, { wrap: "truncate-end" }, line.length > 0 ? line : ' ')))),
|
|
637
|
+
Array.from({ length: viewerTrailingRows }, (_, index) => (React.createElement(Box, { key: `viewer-pad-${index}`, width: contentWidth },
|
|
638
|
+
React.createElement(Text, null, " ")))))) : sortMenuOpen ? (React.createElement(Box, { flexDirection: "column", width: contentWidth, height: contentBoxHeight },
|
|
639
|
+
React.createElement(Box, { width: contentWidth },
|
|
640
|
+
React.createElement(Text, { bold: true, color: "cyan", wrap: "truncate-end" }, sectionTitle)),
|
|
641
|
+
SORT_OPTIONS.map((option, index) => {
|
|
642
|
+
const selected = index === sortMenuIndex;
|
|
643
|
+
return (React.createElement(Box, { key: option.id, width: contentWidth },
|
|
644
|
+
React.createElement(Text, { bold: selected, color: selected ? 'black' : 'white', backgroundColor: selected ? COLORS.accent : undefined, wrap: "truncate-end" }, renderSingleLine(clipToWidth(`${selected ? '▌ ' : ' '}${option.label} • ${option.description}`, contentWidth)))));
|
|
645
|
+
}),
|
|
646
|
+
Array.from({ length: sortTrailingRows }, (_, index) => (React.createElement(Box, { key: `sort-pad-${index}`, width: contentWidth },
|
|
647
|
+
React.createElement(Text, null, " ")))))) : (React.createElement(Box, { flexDirection: "column", width: contentWidth, height: contentBoxHeight },
|
|
648
|
+
React.createElement(Box, { width: contentWidth },
|
|
649
|
+
React.createElement(Text, { bold: true, color: listFocused ? POPUP_CONFIG.borderColor : 'gray', wrap: "truncate-end" }, renderSingleLine(clipToWidth(`${sectionTitle} • ${sectionSummary}`, contentWidth)))),
|
|
650
|
+
visibleEntries.length === 0 ? (React.createElement(React.Fragment, null,
|
|
651
|
+
React.createElement(Box, { width: contentWidth },
|
|
652
|
+
React.createElement(Text, { dimColor: true, wrap: "truncate-end" }, "No files match the current filter.")),
|
|
653
|
+
Array.from({ length: Math.max(0, listBodyRows - 1) }, (_, index) => (React.createElement(Box, { key: `empty-pad-${index}`, width: contentWidth },
|
|
654
|
+
React.createElement(Text, null, " ")))))) : (React.createElement(React.Fragment, null,
|
|
655
|
+
visibleListItems.map((entry) => {
|
|
656
|
+
const selected = listFocused && entry.path === selectedPath;
|
|
657
|
+
const rowBackground = selected ? COLORS.accent : undefined;
|
|
658
|
+
const statusText = statusColumnWidth > 1
|
|
659
|
+
? (entry.statusLabel || '').padStart(statusColumnWidth - 1, ' ')
|
|
660
|
+
: entry.statusLabel || '';
|
|
661
|
+
const selectionMarker = selected ? '▌ ' : ' ';
|
|
662
|
+
const entryLabel = filterActive
|
|
663
|
+
? clipPathToWidth(entry.type === 'directory' ? `${entry.path}/` : entry.path, itemLabelWidth)
|
|
664
|
+
: clipToWidth(entry.displayLabel, itemLabelWidth);
|
|
665
|
+
const entryColor = selected
|
|
666
|
+
? 'black'
|
|
667
|
+
: filterActive
|
|
668
|
+
? getSearchEntryColor(entry)
|
|
669
|
+
: entry.type === 'directory'
|
|
670
|
+
? 'blue'
|
|
671
|
+
: entry.exists
|
|
672
|
+
? 'white'
|
|
673
|
+
: COLORS.error;
|
|
674
|
+
return (React.createElement(Box, { key: entry.path, width: contentWidth },
|
|
675
|
+
React.createElement(Box, { width: rowMarkerWidth },
|
|
676
|
+
React.createElement(Text, { color: selected ? 'black' : POPUP_CONFIG.borderColor, backgroundColor: rowBackground }, selectionMarker)),
|
|
677
|
+
filterActive ? (React.createElement(Box, { width: searchIconWidth },
|
|
678
|
+
React.createElement(Text, { color: entryColor, backgroundColor: rowBackground }, getSearchEntryIcon(entry)))) : null,
|
|
679
|
+
React.createElement(Box, { width: itemLabelWidth },
|
|
680
|
+
React.createElement(Text, { bold: selected || entry.type === 'directory', color: entryColor, backgroundColor: rowBackground, wrap: "truncate-end" }, renderSingleLine(entryLabel))),
|
|
681
|
+
React.createElement(Box, { width: statusColumnWidth, justifyContent: "flex-end" },
|
|
682
|
+
React.createElement(Text, { color: selected ? 'black' : getStatusColor(entry.statusLabel), backgroundColor: rowBackground }, statusText))));
|
|
683
|
+
}),
|
|
684
|
+
Array.from({ length: listTrailingRows }, (_, index) => (React.createElement(Box, { key: `list-pad-${index}`, width: contentWidth },
|
|
685
|
+
React.createElement(Text, null, " ")))))))),
|
|
686
|
+
React.createElement(Box, { flexDirection: "column", width: frameWidth },
|
|
687
|
+
React.createElement(Box, { width: frameWidth },
|
|
688
|
+
React.createElement(Text, { color: statusMessage ? 'green' : undefined, wrap: "truncate-end" }, renderSingleLine(clipToWidth(statusMessage || ' ', frameWidth)))),
|
|
689
|
+
React.createElement(Box, { width: frameWidth },
|
|
690
|
+
React.createElement(Text, { dimColor: true, wrap: "truncate-end" }, renderSingleLine(clipToWidth(footerHelp, frameWidth)))))));
|
|
691
|
+
};
|
|
692
|
+
export default FileBrowserApp;
|
|
693
|
+
//# sourceMappingURL=FileBrowserApp.js.map
|