startall 0.0.12 → 0.0.14
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 +106 -45
- package/index.js +744 -129
- package/package.json +3 -2
- package/screenshot.png +0 -0
package/index.js
CHANGED
|
@@ -10,11 +10,32 @@ import stripAnsi from 'strip-ansi';
|
|
|
10
10
|
// Configuration
|
|
11
11
|
const CONFIG_FILE = process.argv[2] || 'startall.json';
|
|
12
12
|
const COUNTDOWN_SECONDS = 10;
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
// Read version from package.json
|
|
15
|
+
function getAppVersion() {
|
|
16
|
+
try {
|
|
17
|
+
const packagePath = new URL('./package.json', import.meta.url);
|
|
18
|
+
const pkg = JSON.parse(readFileSync(packagePath, 'utf8'));
|
|
19
|
+
return `v${pkg.version}`;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
return 'v0.0.0'; // Fallback if package.json can't be read
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const APP_VERSION = getAppVersion();
|
|
14
25
|
|
|
15
26
|
// Detect if running inside VS Code's integrated terminal
|
|
16
27
|
const IS_VSCODE = process.env.TERM_PROGRAM === 'vscode';
|
|
17
28
|
|
|
29
|
+
// VSCode-specific optimizations
|
|
30
|
+
const VSCODE_CONFIG = {
|
|
31
|
+
// VSCode terminal has better mouse support
|
|
32
|
+
enhancedMouse: IS_VSCODE,
|
|
33
|
+
// VSCode can detect and linkify file paths (file:///path/to/file.js:line:col)
|
|
34
|
+
fileLinking: IS_VSCODE,
|
|
35
|
+
// Some key combinations are captured by VSCode
|
|
36
|
+
remapKeys: IS_VSCODE,
|
|
37
|
+
};
|
|
38
|
+
|
|
18
39
|
// Pane ID generator
|
|
19
40
|
let paneIdCounter = 0;
|
|
20
41
|
function generatePaneId() {
|
|
@@ -297,10 +318,10 @@ function loadConfig() {
|
|
|
297
318
|
try {
|
|
298
319
|
return JSON.parse(readFileSync(CONFIG_FILE, 'utf8'));
|
|
299
320
|
} catch {
|
|
300
|
-
return { defaultSelection: [], ignore: [] };
|
|
321
|
+
return { defaultSelection: [], ignore: [], shortcuts: {} };
|
|
301
322
|
}
|
|
302
323
|
}
|
|
303
|
-
return { defaultSelection: [], ignore: [] };
|
|
324
|
+
return { defaultSelection: [], ignore: [], shortcuts: {} };
|
|
304
325
|
}
|
|
305
326
|
|
|
306
327
|
// Save config
|
|
@@ -337,14 +358,27 @@ class ProcessManager {
|
|
|
337
358
|
this.isFilterMode = false; // Whether in filter input mode
|
|
338
359
|
this.isNamingMode = false; // Whether in pane naming input mode
|
|
339
360
|
this.namingModeText = ''; // Text being typed for pane name
|
|
361
|
+
this.showLineNumbers = this.config.showLineNumbers !== undefined ? this.config.showLineNumbers : true; // Whether to show line numbers
|
|
362
|
+
this.showTimestamps = this.config.showTimestamps !== undefined ? this.config.showTimestamps : false; // Whether to show timestamps
|
|
363
|
+
this.isInputMode = false; // Whether in stdin input mode
|
|
364
|
+
this.inputModeText = ''; // Text being typed for stdin
|
|
340
365
|
|
|
341
366
|
// Settings menu state
|
|
342
|
-
this.settingsSection = '
|
|
367
|
+
this.settingsSection = 'display'; // 'display' | 'ignore' | 'include' | 'scripts' | 'shortcuts'
|
|
343
368
|
this.settingsIndex = 0; // Current selection index within section
|
|
344
369
|
this.isAddingPattern = false; // Whether typing a new pattern
|
|
345
370
|
this.newPatternText = ''; // Text being typed for new pattern
|
|
371
|
+
this.isAssigningShortcut = false; // Whether waiting for a key to assign as shortcut
|
|
372
|
+
this.shortcutScriptName = ''; // Script name being assigned a shortcut
|
|
346
373
|
this.settingsContainer = null; // UI reference
|
|
347
374
|
this.previousPhase = 'selection'; // Track where we came from
|
|
375
|
+
|
|
376
|
+
// Command execution overlay state
|
|
377
|
+
this.showCommandOverlay = false; // Whether command output overlay is visible
|
|
378
|
+
this.commandOverlayOutput = []; // Output lines from command
|
|
379
|
+
this.commandOverlayScript = ''; // Script name being executed
|
|
380
|
+
this.commandOverlayStatus = 'running'; // 'running' | 'exited' | 'crashed'
|
|
381
|
+
this.commandOverlayProcess = null; // Process reference
|
|
348
382
|
this.outputBox = null; // Reference to the output container
|
|
349
383
|
this.destroyed = false; // Flag to prevent operations after cleanup
|
|
350
384
|
this.lastRenderedLineCount = 0; // Track how many lines we've rendered
|
|
@@ -427,23 +461,73 @@ class ProcessManager {
|
|
|
427
461
|
clearInterval(this.countdownInterval);
|
|
428
462
|
this.previousPhase = 'selection';
|
|
429
463
|
this.phase = 'settings';
|
|
430
|
-
this.settingsSection = '
|
|
464
|
+
this.settingsSection = 'display';
|
|
431
465
|
this.settingsIndex = 0;
|
|
432
466
|
this.buildSettingsUI();
|
|
433
467
|
return;
|
|
468
|
+
} else if (keyName >= '1' && keyName <= '9') {
|
|
469
|
+
// Toggle script by number (1-9)
|
|
470
|
+
const index = parseInt(keyName) - 1;
|
|
471
|
+
if (index >= 0 && index < this.scripts.length) {
|
|
472
|
+
const scriptName = this.scripts[index]?.name;
|
|
473
|
+
if (scriptName) {
|
|
474
|
+
if (this.selectedScripts.has(scriptName)) {
|
|
475
|
+
this.selectedScripts.delete(scriptName);
|
|
476
|
+
} else {
|
|
477
|
+
this.selectedScripts.add(scriptName);
|
|
478
|
+
}
|
|
479
|
+
// Reset countdown when selection changes
|
|
480
|
+
this.countdown = COUNTDOWN_SECONDS;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
434
483
|
}
|
|
435
484
|
} else if (this.phase === 'settings') {
|
|
436
485
|
this.handleSettingsInput(keyName, keyEvent);
|
|
437
486
|
return;
|
|
438
487
|
} else if (this.phase === 'running') {
|
|
488
|
+
// Handle command overlay
|
|
489
|
+
if (this.showCommandOverlay) {
|
|
490
|
+
if (keyName === 'escape') {
|
|
491
|
+
this.closeCommandOverlay();
|
|
492
|
+
this.buildRunningUI();
|
|
493
|
+
}
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
439
497
|
// Handle split menu
|
|
440
498
|
if (this.showSplitMenu) {
|
|
441
499
|
this.handleSplitMenuInput(keyName, keyEvent);
|
|
442
500
|
return;
|
|
443
501
|
}
|
|
444
502
|
|
|
503
|
+
// If in input mode (stdin), handle stdin input
|
|
504
|
+
if (this.isInputMode) {
|
|
505
|
+
const scriptName = this.scripts[this.selectedIndex]?.name;
|
|
506
|
+
if (keyName === 'escape') {
|
|
507
|
+
this.isInputMode = false;
|
|
508
|
+
this.inputModeText = '';
|
|
509
|
+
this.buildRunningUI();
|
|
510
|
+
} else if (keyName === 'enter' || keyName === 'return') {
|
|
511
|
+
// Send the input to the selected process
|
|
512
|
+
if (scriptName && this.inputModeText.trim()) {
|
|
513
|
+
this.sendInputToProcess(scriptName, this.inputModeText + '\n');
|
|
514
|
+
}
|
|
515
|
+
this.isInputMode = false;
|
|
516
|
+
this.inputModeText = '';
|
|
517
|
+
this.buildRunningUI();
|
|
518
|
+
} else if (keyName === 'backspace') {
|
|
519
|
+
this.inputModeText = this.inputModeText.slice(0, -1);
|
|
520
|
+
this.buildRunningUI();
|
|
521
|
+
} else if (keyName === 'space') {
|
|
522
|
+
this.inputModeText += ' ';
|
|
523
|
+
this.buildRunningUI();
|
|
524
|
+
} else if (keyName && keyName.length === 1 && !keyEvent.ctrl && !keyEvent.meta) {
|
|
525
|
+
this.inputModeText += keyName;
|
|
526
|
+
this.buildRunningUI();
|
|
527
|
+
}
|
|
528
|
+
}
|
|
445
529
|
// If in naming mode, handle name input
|
|
446
|
-
if (this.isNamingMode) {
|
|
530
|
+
else if (this.isNamingMode) {
|
|
447
531
|
const pane = findPaneById(this.paneRoot, this.focusedPaneId);
|
|
448
532
|
if (keyName === 'escape') {
|
|
449
533
|
this.isNamingMode = false;
|
|
@@ -549,12 +633,15 @@ class ProcessManager {
|
|
|
549
633
|
this.selectedIndex = Math.min(this.scripts.length - 1, this.selectedIndex + 1);
|
|
550
634
|
this.buildRunningUI(); // Rebuild to show selection change
|
|
551
635
|
} else if (keyName === 'left' || keyName === 'h') {
|
|
552
|
-
// Navigate processes left
|
|
553
|
-
this.selectedIndex =
|
|
636
|
+
// Navigate processes left with wrapping
|
|
637
|
+
this.selectedIndex = this.selectedIndex - 1;
|
|
638
|
+
if (this.selectedIndex < 0) {
|
|
639
|
+
this.selectedIndex = this.scripts.length - 1;
|
|
640
|
+
}
|
|
554
641
|
this.buildRunningUI(); // Rebuild to show selection change
|
|
555
642
|
} else if (keyName === 'right' || keyName === 'l') {
|
|
556
|
-
// Navigate processes right
|
|
557
|
-
this.selectedIndex =
|
|
643
|
+
// Navigate processes right with wrapping
|
|
644
|
+
this.selectedIndex = (this.selectedIndex + 1) % this.scripts.length;
|
|
558
645
|
this.buildRunningUI(); // Rebuild to show selection change
|
|
559
646
|
} else if (keyName === 'r') {
|
|
560
647
|
const scriptName = this.scripts[this.selectedIndex]?.name;
|
|
@@ -568,13 +655,13 @@ class ProcessManager {
|
|
|
568
655
|
this.toggleProcess(scriptName);
|
|
569
656
|
}
|
|
570
657
|
} else if (keyName === 'o') {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
658
|
+
// Open settings (options)
|
|
659
|
+
this.previousPhase = 'running';
|
|
660
|
+
this.phase = 'settings';
|
|
661
|
+
this.settingsSection = 'display';
|
|
662
|
+
this.settingsIndex = 0;
|
|
663
|
+
this.buildSettingsUI();
|
|
664
|
+
return;
|
|
578
665
|
} else if (keyName === 'c') {
|
|
579
666
|
// Cycle color filter on focused pane
|
|
580
667
|
const pane = findPaneById(this.paneRoot, this.focusedPaneId);
|
|
@@ -592,6 +679,46 @@ class ProcessManager {
|
|
|
592
679
|
// Navigate to previous pane
|
|
593
680
|
this.navigateToNextPane(-1);
|
|
594
681
|
this.buildRunningUI();
|
|
682
|
+
} else if (keyName === 'home') {
|
|
683
|
+
// Scroll to top of focused pane
|
|
684
|
+
this.scrollFocusedPane('home');
|
|
685
|
+
} else if (keyName === 'end') {
|
|
686
|
+
// Scroll to bottom of focused pane
|
|
687
|
+
this.scrollFocusedPane('end');
|
|
688
|
+
} else if (keyName === 'pageup') {
|
|
689
|
+
// Scroll up one page in focused pane
|
|
690
|
+
this.scrollFocusedPane('pageup');
|
|
691
|
+
} else if (keyName === 'pagedown') {
|
|
692
|
+
// Scroll down one page in focused pane
|
|
693
|
+
this.scrollFocusedPane('pagedown');
|
|
694
|
+
} else if (keyName >= '1' && keyName <= '9') {
|
|
695
|
+
// Toggle process by number (1-9)
|
|
696
|
+
const index = parseInt(keyName) - 1;
|
|
697
|
+
if (index >= 0 && index < this.scripts.length) {
|
|
698
|
+
this.selectedIndex = index;
|
|
699
|
+
this.toggleProcessVisibility();
|
|
700
|
+
this.buildRunningUI();
|
|
701
|
+
}
|
|
702
|
+
} else if (keyName === 'i') {
|
|
703
|
+
// Enter input mode to send stdin to selected process
|
|
704
|
+
const scriptName = this.scripts[this.selectedIndex]?.name;
|
|
705
|
+
const proc = this.processes.get(scriptName);
|
|
706
|
+
if (scriptName && proc?.status === 'running') {
|
|
707
|
+
this.isInputMode = true;
|
|
708
|
+
this.inputModeText = '';
|
|
709
|
+
this.buildRunningUI();
|
|
710
|
+
}
|
|
711
|
+
} else if (keyName && keyName.length === 1 && !keyEvent.ctrl && !keyEvent.meta && !keyEvent.shift) {
|
|
712
|
+
// Check if this key is a custom shortcut
|
|
713
|
+
const shortcuts = this.config.shortcuts || {};
|
|
714
|
+
const scriptName = shortcuts[keyName];
|
|
715
|
+
if (scriptName) {
|
|
716
|
+
// Find the script in allScripts (since it might be ignored/filtered out)
|
|
717
|
+
const script = this.allScripts.find(s => s.name === scriptName);
|
|
718
|
+
if (script) {
|
|
719
|
+
this.executeCommand(scriptName);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
595
722
|
}
|
|
596
723
|
}
|
|
597
724
|
}
|
|
@@ -788,8 +915,55 @@ class ProcessManager {
|
|
|
788
915
|
this.startProcess(scriptName);
|
|
789
916
|
}
|
|
790
917
|
}
|
|
918
|
+
|
|
919
|
+
sendInputToProcess(scriptName, input) {
|
|
920
|
+
const proc = this.processRefs.get(scriptName);
|
|
921
|
+
if (proc && proc.stdin && proc.stdin.writable) {
|
|
922
|
+
try {
|
|
923
|
+
proc.stdin.write(input);
|
|
924
|
+
// Echo the input in the output for visibility
|
|
925
|
+
this.addOutputLine(scriptName, `> ${input.trim()}`);
|
|
926
|
+
} catch (err) {
|
|
927
|
+
this.addOutputLine(scriptName, `Error sending input: ${err.message}`);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
791
931
|
|
|
792
932
|
handleSettingsInput(keyName, keyEvent) {
|
|
933
|
+
// Handle shortcut assignment mode
|
|
934
|
+
if (this.isAssigningShortcut) {
|
|
935
|
+
if (keyName === 'escape') {
|
|
936
|
+
this.isAssigningShortcut = false;
|
|
937
|
+
this.shortcutScriptName = '';
|
|
938
|
+
this.buildSettingsUI();
|
|
939
|
+
return;
|
|
940
|
+
} else if (keyName && keyName.length === 1 && !keyEvent.ctrl && !keyEvent.meta && !keyEvent.shift) {
|
|
941
|
+
// Assign this key to the script
|
|
942
|
+
if (!this.config.shortcuts) this.config.shortcuts = {};
|
|
943
|
+
|
|
944
|
+
// Remove any existing shortcut for this script (one key per script)
|
|
945
|
+
for (const [key, scriptName] of Object.entries(this.config.shortcuts)) {
|
|
946
|
+
if (scriptName === this.shortcutScriptName) {
|
|
947
|
+
delete this.config.shortcuts[key];
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// Also remove this key if it's assigned to another script (one script per key)
|
|
952
|
+
if (this.config.shortcuts[keyName]) {
|
|
953
|
+
delete this.config.shortcuts[keyName];
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// Assign the new shortcut
|
|
957
|
+
this.config.shortcuts[keyName] = this.shortcutScriptName;
|
|
958
|
+
saveConfig(this.config);
|
|
959
|
+
this.isAssigningShortcut = false;
|
|
960
|
+
this.shortcutScriptName = '';
|
|
961
|
+
this.buildSettingsUI();
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
|
|
793
967
|
// Handle text input mode for adding patterns
|
|
794
968
|
if (this.isAddingPattern) {
|
|
795
969
|
if (keyName === 'escape') {
|
|
@@ -824,6 +998,14 @@ class ProcessManager {
|
|
|
824
998
|
|
|
825
999
|
// Normal settings navigation
|
|
826
1000
|
if (keyName === 'escape' || keyName === 'q') {
|
|
1001
|
+
// Apply filters before returning (updates this.scripts)
|
|
1002
|
+
this.applyFilters();
|
|
1003
|
+
|
|
1004
|
+
// Ensure selectedIndex is within bounds after filter changes
|
|
1005
|
+
if (this.selectedIndex >= this.scripts.length) {
|
|
1006
|
+
this.selectedIndex = Math.max(0, this.scripts.length - 1);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
827
1009
|
// Return to previous phase
|
|
828
1010
|
if (this.previousPhase === 'running') {
|
|
829
1011
|
this.phase = 'running';
|
|
@@ -836,46 +1018,78 @@ class ProcessManager {
|
|
|
836
1018
|
}
|
|
837
1019
|
} else if (keyName === 'tab' || keyName === 'right') {
|
|
838
1020
|
// Switch section
|
|
839
|
-
const sections = ['ignore', 'include', 'scripts'];
|
|
1021
|
+
const sections = ['display', 'ignore', 'include', 'shortcuts', 'scripts'];
|
|
840
1022
|
const idx = sections.indexOf(this.settingsSection);
|
|
841
1023
|
this.settingsSection = sections[(idx + 1) % sections.length];
|
|
842
1024
|
this.settingsIndex = 0;
|
|
843
1025
|
this.buildSettingsUI();
|
|
844
1026
|
} else if (keyEvent.shift && keyName === 'tab') {
|
|
845
1027
|
// Switch section backwards
|
|
846
|
-
const sections = ['ignore', 'include', 'scripts'];
|
|
1028
|
+
const sections = ['display', 'ignore', 'include', 'shortcuts', 'scripts'];
|
|
847
1029
|
const idx = sections.indexOf(this.settingsSection);
|
|
848
1030
|
this.settingsSection = sections[(idx - 1 + sections.length) % sections.length];
|
|
849
1031
|
this.settingsIndex = 0;
|
|
850
1032
|
this.buildSettingsUI();
|
|
851
1033
|
} else if (keyName === 'left') {
|
|
852
1034
|
// Switch section backwards
|
|
853
|
-
const sections = ['ignore', 'include', 'scripts'];
|
|
1035
|
+
const sections = ['display', 'ignore', 'include', 'shortcuts', 'scripts'];
|
|
854
1036
|
const idx = sections.indexOf(this.settingsSection);
|
|
855
1037
|
this.settingsSection = sections[(idx - 1 + sections.length) % sections.length];
|
|
856
1038
|
this.settingsIndex = 0;
|
|
857
1039
|
this.buildSettingsUI();
|
|
858
1040
|
} else if (keyName === 'up') {
|
|
859
|
-
|
|
860
|
-
|
|
1041
|
+
if (this.settingsIndex > 0) {
|
|
1042
|
+
this.settingsIndex--;
|
|
1043
|
+
this.buildSettingsUI();
|
|
1044
|
+
} else {
|
|
1045
|
+
// Move to previous section
|
|
1046
|
+
const sections = ['display', 'ignore', 'include', 'shortcuts', 'scripts'];
|
|
1047
|
+
const idx = sections.indexOf(this.settingsSection);
|
|
1048
|
+
if (idx > 0) {
|
|
1049
|
+
this.settingsSection = sections[idx - 1];
|
|
1050
|
+
this.settingsIndex = this.getSettingsMaxIndex();
|
|
1051
|
+
this.buildSettingsUI();
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
861
1054
|
} else if (keyName === 'down') {
|
|
862
1055
|
const maxIndex = this.getSettingsMaxIndex();
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
} else if (keyName === 'a') {
|
|
866
|
-
// Add new pattern (only for ignore/include sections)
|
|
867
|
-
if (this.settingsSection === 'ignore' || this.settingsSection === 'include') {
|
|
868
|
-
this.isAddingPattern = true;
|
|
869
|
-
this.newPatternText = '';
|
|
1056
|
+
if (this.settingsIndex < maxIndex) {
|
|
1057
|
+
this.settingsIndex++;
|
|
870
1058
|
this.buildSettingsUI();
|
|
1059
|
+
} else {
|
|
1060
|
+
// Move to next section
|
|
1061
|
+
const sections = ['display', 'ignore', 'include', 'shortcuts', 'scripts'];
|
|
1062
|
+
const idx = sections.indexOf(this.settingsSection);
|
|
1063
|
+
if (idx < sections.length - 1) {
|
|
1064
|
+
this.settingsSection = sections[idx + 1];
|
|
1065
|
+
this.settingsIndex = 0;
|
|
1066
|
+
this.buildSettingsUI();
|
|
1067
|
+
}
|
|
871
1068
|
}
|
|
1069
|
+
} else if (keyName === 'i') {
|
|
1070
|
+
// Add new ignore pattern
|
|
1071
|
+
this.settingsSection = 'ignore';
|
|
1072
|
+
this.isAddingPattern = true;
|
|
1073
|
+
this.newPatternText = '';
|
|
1074
|
+
this.buildSettingsUI();
|
|
1075
|
+
} else if (keyName === 'n') {
|
|
1076
|
+
// Add new include pattern
|
|
1077
|
+
this.settingsSection = 'include';
|
|
1078
|
+
this.isAddingPattern = true;
|
|
1079
|
+
this.newPatternText = '';
|
|
1080
|
+
this.buildSettingsUI();
|
|
872
1081
|
} else if (keyName === 'd' || keyName === 'backspace') {
|
|
873
|
-
// Delete selected pattern or
|
|
1082
|
+
// Delete selected pattern or shortcut
|
|
874
1083
|
this.deleteSelectedItem();
|
|
875
1084
|
this.buildSettingsUI();
|
|
876
1085
|
} else if (keyName === 'space' || keyName === 'enter' || keyName === 'return') {
|
|
877
|
-
// Toggle
|
|
878
|
-
if (this.settingsSection === '
|
|
1086
|
+
// Toggle display options, script visibility, or assign shortcut
|
|
1087
|
+
if (this.settingsSection === 'display') {
|
|
1088
|
+
this.toggleDisplayOption();
|
|
1089
|
+
this.buildSettingsUI();
|
|
1090
|
+
} else if (this.settingsSection === 'shortcuts') {
|
|
1091
|
+
this.assignShortcut();
|
|
1092
|
+
} else if (this.settingsSection === 'scripts') {
|
|
879
1093
|
this.toggleScriptIgnore();
|
|
880
1094
|
this.buildSettingsUI();
|
|
881
1095
|
}
|
|
@@ -883,16 +1097,33 @@ class ProcessManager {
|
|
|
883
1097
|
}
|
|
884
1098
|
|
|
885
1099
|
getSettingsMaxIndex() {
|
|
886
|
-
if (this.settingsSection === '
|
|
887
|
-
return
|
|
1100
|
+
if (this.settingsSection === 'display') {
|
|
1101
|
+
return 1; // 2 display options (line numbers, timestamps)
|
|
1102
|
+
} else if (this.settingsSection === 'ignore') {
|
|
1103
|
+
const count = this.config.ignore?.length || 0;
|
|
1104
|
+
return count > 0 ? count - 1 : 0;
|
|
888
1105
|
} else if (this.settingsSection === 'include') {
|
|
889
|
-
|
|
1106
|
+
const count = this.config.include?.length || 0;
|
|
1107
|
+
return count > 0 ? count - 1 : 0;
|
|
1108
|
+
} else if (this.settingsSection === 'shortcuts') {
|
|
1109
|
+
return Math.max(0, this.allScripts.length - 1);
|
|
890
1110
|
} else if (this.settingsSection === 'scripts') {
|
|
891
1111
|
return Math.max(0, this.allScripts.length - 1);
|
|
892
1112
|
}
|
|
893
1113
|
return 0;
|
|
894
1114
|
}
|
|
895
1115
|
|
|
1116
|
+
toggleDisplayOption() {
|
|
1117
|
+
if (this.settingsIndex === 0) {
|
|
1118
|
+
this.showLineNumbers = !this.showLineNumbers;
|
|
1119
|
+
this.config.showLineNumbers = this.showLineNumbers;
|
|
1120
|
+
} else if (this.settingsIndex === 1) {
|
|
1121
|
+
this.showTimestamps = !this.showTimestamps;
|
|
1122
|
+
this.config.showTimestamps = this.showTimestamps;
|
|
1123
|
+
}
|
|
1124
|
+
saveConfig(this.config);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
896
1127
|
deleteSelectedItem() {
|
|
897
1128
|
if (this.settingsSection === 'ignore' && this.config.ignore?.length > 0) {
|
|
898
1129
|
this.config.ignore.splice(this.settingsIndex, 1);
|
|
@@ -906,6 +1137,27 @@ class ProcessManager {
|
|
|
906
1137
|
saveConfig(this.config);
|
|
907
1138
|
this.applyFilters();
|
|
908
1139
|
this.settingsIndex = Math.max(0, Math.min(this.settingsIndex, (this.config.include?.length || 1) - 1));
|
|
1140
|
+
} else if (this.settingsSection === 'shortcuts') {
|
|
1141
|
+
const script = this.allScripts[this.settingsIndex];
|
|
1142
|
+
if (script && this.config.shortcuts) {
|
|
1143
|
+
// Find and delete any shortcut assigned to this script
|
|
1144
|
+
for (const [key, scriptName] of Object.entries(this.config.shortcuts)) {
|
|
1145
|
+
if (scriptName === script.name) {
|
|
1146
|
+
delete this.config.shortcuts[key];
|
|
1147
|
+
saveConfig(this.config);
|
|
1148
|
+
break;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
assignShortcut() {
|
|
1156
|
+
const script = this.allScripts[this.settingsIndex];
|
|
1157
|
+
if (script) {
|
|
1158
|
+
this.isAssigningShortcut = true;
|
|
1159
|
+
this.shortcutScriptName = script.name;
|
|
1160
|
+
this.buildSettingsUI();
|
|
909
1161
|
}
|
|
910
1162
|
}
|
|
911
1163
|
|
|
@@ -1027,6 +1279,9 @@ class ProcessManager {
|
|
|
1027
1279
|
items.push({ label: 'Previous Pane', shortcut: 'Shift+Tab', action: () => this.navigateToNextPane(-1) });
|
|
1028
1280
|
}
|
|
1029
1281
|
|
|
1282
|
+
items.push({ label: 'Toggle Line Numbers', shortcut: '#', action: () => { this.showLineNumbers = !this.showLineNumbers; } });
|
|
1283
|
+
items.push({ label: 'Toggle Timestamps', shortcut: 't', action: () => { this.showTimestamps = !this.showTimestamps; } });
|
|
1284
|
+
|
|
1030
1285
|
return items;
|
|
1031
1286
|
}
|
|
1032
1287
|
|
|
@@ -1178,6 +1433,42 @@ class ProcessManager {
|
|
|
1178
1433
|
saveConfig(this.config);
|
|
1179
1434
|
}
|
|
1180
1435
|
|
|
1436
|
+
// Scroll the focused pane
|
|
1437
|
+
scrollFocusedPane(direction) {
|
|
1438
|
+
if (!this.focusedPaneId) return;
|
|
1439
|
+
|
|
1440
|
+
const scrollBox = this.paneScrollBoxes.get(this.focusedPaneId);
|
|
1441
|
+
if (!scrollBox || !scrollBox.scrollTo) return;
|
|
1442
|
+
|
|
1443
|
+
const currentY = scrollBox.scrollTop || 0;
|
|
1444
|
+
const viewportHeight = scrollBox.height || 20;
|
|
1445
|
+
const contentHeight = scrollBox.contentHeight || 0;
|
|
1446
|
+
|
|
1447
|
+
let newY = currentY;
|
|
1448
|
+
|
|
1449
|
+
if (direction === 'home') {
|
|
1450
|
+
newY = 0;
|
|
1451
|
+
} else if (direction === 'end') {
|
|
1452
|
+
newY = Number.MAX_SAFE_INTEGER;
|
|
1453
|
+
} else if (direction === 'pageup') {
|
|
1454
|
+
newY = Math.max(0, currentY - viewportHeight);
|
|
1455
|
+
} else if (direction === 'pagedown') {
|
|
1456
|
+
newY = Math.min(contentHeight - viewportHeight, currentY + viewportHeight);
|
|
1457
|
+
}
|
|
1458
|
+
|
|
1459
|
+
scrollBox.scrollTo({ x: 0, y: newY });
|
|
1460
|
+
|
|
1461
|
+
// Save the new scroll position
|
|
1462
|
+
this.paneScrollPositions.set(this.focusedPaneId, { x: 0, y: newY });
|
|
1463
|
+
|
|
1464
|
+
// Auto-pause when manually scrolling (unless going to end)
|
|
1465
|
+
if (direction !== 'end' && !this.isPaused) {
|
|
1466
|
+
this.isPaused = true;
|
|
1467
|
+
this.updateStreamPauseState();
|
|
1468
|
+
this.buildRunningUI();
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1181
1472
|
// Check if a process is visible in the focused pane
|
|
1182
1473
|
isProcessVisibleInPane(scriptName, pane) {
|
|
1183
1474
|
if (!pane) return true;
|
|
@@ -1302,54 +1593,115 @@ class ProcessManager {
|
|
|
1302
1593
|
this.settingsContainer.add(inputBar);
|
|
1303
1594
|
}
|
|
1304
1595
|
|
|
1305
|
-
//
|
|
1306
|
-
|
|
1307
|
-
|
|
1596
|
+
// Input prompt if assigning shortcut
|
|
1597
|
+
if (this.isAssigningShortcut) {
|
|
1598
|
+
const inputBar = new BoxRenderable(this.renderer, {
|
|
1599
|
+
id: 'input-bar',
|
|
1600
|
+
border: ['left'],
|
|
1601
|
+
borderStyle: 'single',
|
|
1602
|
+
borderColor: COLORS.accent,
|
|
1603
|
+
paddingLeft: 1,
|
|
1604
|
+
marginBottom: 1,
|
|
1605
|
+
});
|
|
1606
|
+
const inputText = new TextRenderable(this.renderer, {
|
|
1607
|
+
id: 'input-text',
|
|
1608
|
+
content: t`${fg(COLORS.textDim)('Press a key to assign as shortcut for')} ${fg(COLORS.accent)(this.shortcutScriptName)} ${fg(COLORS.textDim)('(esc to cancel)')}`,
|
|
1609
|
+
});
|
|
1610
|
+
inputBar.add(inputText);
|
|
1611
|
+
this.settingsContainer.add(inputBar);
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// Combined content panel with all sections
|
|
1615
|
+
const contentPanel = new BoxRenderable(this.renderer, {
|
|
1616
|
+
id: 'content-panel',
|
|
1308
1617
|
flexDirection: 'row',
|
|
1309
|
-
|
|
1310
|
-
|
|
1618
|
+
flexGrow: 1,
|
|
1619
|
+
gap: 1,
|
|
1311
1620
|
});
|
|
1312
1621
|
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1622
|
+
// Left column - Display options, Ignore, Include
|
|
1623
|
+
const leftColumn = new BoxRenderable(this.renderer, {
|
|
1624
|
+
id: 'left-column',
|
|
1625
|
+
flexDirection: 'column',
|
|
1626
|
+
flexGrow: 1,
|
|
1627
|
+
gap: 1,
|
|
1628
|
+
});
|
|
1318
1629
|
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1630
|
+
// Display options section
|
|
1631
|
+
const displayBox = new BoxRenderable(this.renderer, {
|
|
1632
|
+
id: 'display-box',
|
|
1633
|
+
flexDirection: 'column',
|
|
1634
|
+
border: true,
|
|
1635
|
+
borderStyle: 'rounded',
|
|
1636
|
+
borderColor: this.settingsSection === 'display' ? COLORS.borderFocused : COLORS.border,
|
|
1637
|
+
title: ' Display Options ',
|
|
1638
|
+
titleAlignment: 'left',
|
|
1639
|
+
padding: 1,
|
|
1328
1640
|
});
|
|
1641
|
+
this.buildDisplaySectionContent(displayBox);
|
|
1642
|
+
leftColumn.add(displayBox);
|
|
1329
1643
|
|
|
1330
|
-
|
|
1644
|
+
// Ignore patterns section
|
|
1645
|
+
const ignoreBox = new BoxRenderable(this.renderer, {
|
|
1646
|
+
id: 'ignore-box',
|
|
1647
|
+
flexDirection: 'column',
|
|
1648
|
+
border: true,
|
|
1649
|
+
borderStyle: 'rounded',
|
|
1650
|
+
borderColor: this.settingsSection === 'ignore' ? COLORS.borderFocused : COLORS.border,
|
|
1651
|
+
title: ' Ignore Patterns (i) ',
|
|
1652
|
+
titleAlignment: 'left',
|
|
1653
|
+
padding: 1,
|
|
1654
|
+
flexGrow: 1,
|
|
1655
|
+
});
|
|
1656
|
+
this.buildIgnoreSectionContent(ignoreBox);
|
|
1657
|
+
leftColumn.add(ignoreBox);
|
|
1331
1658
|
|
|
1332
|
-
//
|
|
1333
|
-
const
|
|
1334
|
-
id: '
|
|
1659
|
+
// Include patterns section
|
|
1660
|
+
const includeBox = new BoxRenderable(this.renderer, {
|
|
1661
|
+
id: 'include-box',
|
|
1335
1662
|
flexDirection: 'column',
|
|
1336
1663
|
border: true,
|
|
1337
1664
|
borderStyle: 'rounded',
|
|
1338
|
-
borderColor: COLORS.border,
|
|
1339
|
-
title:
|
|
1665
|
+
borderColor: this.settingsSection === 'include' ? COLORS.borderFocused : COLORS.border,
|
|
1666
|
+
title: ' Include Patterns (n) ',
|
|
1667
|
+
titleAlignment: 'left',
|
|
1668
|
+
padding: 1,
|
|
1669
|
+
flexGrow: 1,
|
|
1670
|
+
});
|
|
1671
|
+
this.buildIncludeSectionContent(includeBox);
|
|
1672
|
+
leftColumn.add(includeBox);
|
|
1673
|
+
|
|
1674
|
+
contentPanel.add(leftColumn);
|
|
1675
|
+
|
|
1676
|
+
// Middle column - Quick Commands
|
|
1677
|
+
const shortcutsBox = new BoxRenderable(this.renderer, {
|
|
1678
|
+
id: 'shortcuts-box',
|
|
1679
|
+
flexDirection: 'column',
|
|
1680
|
+
border: true,
|
|
1681
|
+
borderStyle: 'rounded',
|
|
1682
|
+
borderColor: this.settingsSection === 'shortcuts' ? COLORS.borderFocused : COLORS.border,
|
|
1683
|
+
title: ' Quick Commands ',
|
|
1340
1684
|
titleAlignment: 'left',
|
|
1341
1685
|
flexGrow: 1,
|
|
1342
1686
|
padding: 1,
|
|
1343
1687
|
});
|
|
1688
|
+
this.buildShortcutsSectionContent(shortcutsBox);
|
|
1689
|
+
contentPanel.add(shortcutsBox);
|
|
1344
1690
|
|
|
1345
|
-
//
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
this.
|
|
1352
|
-
|
|
1691
|
+
// Right column - Script List
|
|
1692
|
+
const scriptsBox = new BoxRenderable(this.renderer, {
|
|
1693
|
+
id: 'scripts-box',
|
|
1694
|
+
flexDirection: 'column',
|
|
1695
|
+
border: true,
|
|
1696
|
+
borderStyle: 'rounded',
|
|
1697
|
+
borderColor: this.settingsSection === 'scripts' ? COLORS.borderFocused : COLORS.border,
|
|
1698
|
+
title: ' Script List ',
|
|
1699
|
+
titleAlignment: 'left',
|
|
1700
|
+
flexGrow: 1,
|
|
1701
|
+
padding: 1,
|
|
1702
|
+
});
|
|
1703
|
+
this.buildScriptsSectionContent(scriptsBox);
|
|
1704
|
+
contentPanel.add(scriptsBox);
|
|
1353
1705
|
|
|
1354
1706
|
this.settingsContainer.add(contentPanel);
|
|
1355
1707
|
|
|
@@ -1366,18 +1718,27 @@ class ProcessManager {
|
|
|
1366
1718
|
gap: 2,
|
|
1367
1719
|
});
|
|
1368
1720
|
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1721
|
+
let shortcuts;
|
|
1722
|
+
if (this.isAddingPattern) {
|
|
1723
|
+
shortcuts = [
|
|
1724
|
+
{ key: 'enter', desc: 'save' },
|
|
1725
|
+
{ key: 'esc', desc: 'cancel' },
|
|
1726
|
+
];
|
|
1727
|
+
} else if (this.isAssigningShortcut) {
|
|
1728
|
+
shortcuts = [
|
|
1729
|
+
{ key: 'any key', desc: 'assign' },
|
|
1730
|
+
{ key: 'esc', desc: 'cancel' },
|
|
1731
|
+
];
|
|
1732
|
+
} else {
|
|
1733
|
+
shortcuts = [
|
|
1734
|
+
{ key: 'tab', desc: 'section' },
|
|
1735
|
+
{ key: 'space', desc: this.settingsSection === 'shortcuts' ? 'assign' : 'toggle' },
|
|
1736
|
+
{ key: 'i', desc: 'add ignore' },
|
|
1737
|
+
{ key: 'n', desc: 'add include' },
|
|
1738
|
+
{ key: 'd', desc: 'delete' },
|
|
1739
|
+
{ key: 'esc', desc: 'back' },
|
|
1740
|
+
];
|
|
1741
|
+
}
|
|
1381
1742
|
|
|
1382
1743
|
shortcuts.forEach(({ key, desc }) => {
|
|
1383
1744
|
const shortcut = new TextRenderable(this.renderer, {
|
|
@@ -1392,26 +1753,38 @@ class ProcessManager {
|
|
|
1392
1753
|
this.renderer.root.add(this.settingsContainer);
|
|
1393
1754
|
}
|
|
1394
1755
|
|
|
1395
|
-
|
|
1396
|
-
const
|
|
1397
|
-
id: '
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
container.add(desc);
|
|
1401
|
-
|
|
1402
|
-
container.add(new TextRenderable(this.renderer, { id: 'spacer', content: '' }));
|
|
1756
|
+
buildDisplaySectionContent(container) {
|
|
1757
|
+
const options = [
|
|
1758
|
+
{ id: 'lineNumbers', label: 'Show Line Numbers', value: this.showLineNumbers },
|
|
1759
|
+
{ id: 'timestamps', label: 'Show Timestamps', value: this.showTimestamps },
|
|
1760
|
+
];
|
|
1403
1761
|
|
|
1762
|
+
options.forEach((option, idx) => {
|
|
1763
|
+
const isFocused = this.settingsSection === 'display' && idx === this.settingsIndex;
|
|
1764
|
+
const indicator = isFocused ? '>' : ' ';
|
|
1765
|
+
const checkbox = option.value ? '[x]' : '[ ]';
|
|
1766
|
+
const checkColor = option.value ? COLORS.success : COLORS.textDim;
|
|
1767
|
+
|
|
1768
|
+
const line = new TextRenderable(this.renderer, {
|
|
1769
|
+
id: `display-option-${idx}`,
|
|
1770
|
+
content: t`${fg(isFocused ? COLORS.accent : COLORS.textDim)(indicator)} ${fg(checkColor)(checkbox)} ${fg(COLORS.text)(option.label)}`,
|
|
1771
|
+
});
|
|
1772
|
+
container.add(line);
|
|
1773
|
+
});
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
buildIgnoreSectionContent(container) {
|
|
1404
1777
|
const patterns = this.config.ignore || [];
|
|
1405
1778
|
|
|
1406
1779
|
if (patterns.length === 0) {
|
|
1407
1780
|
const empty = new TextRenderable(this.renderer, {
|
|
1408
1781
|
id: 'ignore-empty',
|
|
1409
|
-
content: t`${fg(COLORS.textDim)('
|
|
1782
|
+
content: t`${fg(COLORS.textDim)('Press i to add')}`,
|
|
1410
1783
|
});
|
|
1411
1784
|
container.add(empty);
|
|
1412
1785
|
} else {
|
|
1413
1786
|
patterns.forEach((pattern, idx) => {
|
|
1414
|
-
const isFocused = idx === this.settingsIndex;
|
|
1787
|
+
const isFocused = this.settingsSection === 'ignore' && idx === this.settingsIndex;
|
|
1415
1788
|
const indicator = isFocused ? '>' : ' ';
|
|
1416
1789
|
|
|
1417
1790
|
const line = new TextRenderable(this.renderer, {
|
|
@@ -1424,25 +1797,17 @@ class ProcessManager {
|
|
|
1424
1797
|
}
|
|
1425
1798
|
|
|
1426
1799
|
buildIncludeSectionContent(container) {
|
|
1427
|
-
const desc = new TextRenderable(this.renderer, {
|
|
1428
|
-
id: 'include-desc',
|
|
1429
|
-
content: t`${fg(COLORS.textDim)('Only show scripts matching these patterns. Use * as wildcard.')}`,
|
|
1430
|
-
});
|
|
1431
|
-
container.add(desc);
|
|
1432
|
-
|
|
1433
|
-
container.add(new TextRenderable(this.renderer, { id: 'spacer', content: '' }));
|
|
1434
|
-
|
|
1435
1800
|
const patterns = this.config.include || [];
|
|
1436
1801
|
|
|
1437
1802
|
if (patterns.length === 0) {
|
|
1438
1803
|
const empty = new TextRenderable(this.renderer, {
|
|
1439
1804
|
id: 'include-empty',
|
|
1440
|
-
content: t`${fg(COLORS.textDim)('
|
|
1805
|
+
content: t`${fg(COLORS.textDim)('Press n to add')}`,
|
|
1441
1806
|
});
|
|
1442
1807
|
container.add(empty);
|
|
1443
1808
|
} else {
|
|
1444
1809
|
patterns.forEach((pattern, idx) => {
|
|
1445
|
-
const isFocused = idx === this.settingsIndex;
|
|
1810
|
+
const isFocused = this.settingsSection === 'include' && idx === this.settingsIndex;
|
|
1446
1811
|
const indicator = isFocused ? '>' : ' ';
|
|
1447
1812
|
|
|
1448
1813
|
const line = new TextRenderable(this.renderer, {
|
|
@@ -1454,29 +1819,60 @@ class ProcessManager {
|
|
|
1454
1819
|
}
|
|
1455
1820
|
}
|
|
1456
1821
|
|
|
1457
|
-
|
|
1458
|
-
const
|
|
1459
|
-
id: 'scripts-desc',
|
|
1460
|
-
content: t`${fg(COLORS.textDim)('Toggle individual scripts. Ignored scripts are hidden from selection.')}`,
|
|
1461
|
-
});
|
|
1462
|
-
container.add(desc);
|
|
1822
|
+
buildShortcutsSectionContent(container) {
|
|
1823
|
+
const shortcuts = this.config.shortcuts || {};
|
|
1463
1824
|
|
|
1464
|
-
|
|
1825
|
+
// Helper to find shortcut key for a script
|
|
1826
|
+
const getShortcutKey = (scriptName) => {
|
|
1827
|
+
for (const [key, name] of Object.entries(shortcuts)) {
|
|
1828
|
+
if (name === scriptName) return key;
|
|
1829
|
+
}
|
|
1830
|
+
return null;
|
|
1831
|
+
};
|
|
1465
1832
|
|
|
1833
|
+
this.allScripts.forEach((script, idx) => {
|
|
1834
|
+
const isFocused = this.settingsSection === 'shortcuts' && idx === this.settingsIndex;
|
|
1835
|
+
const indicator = isFocused ? '>' : ' ';
|
|
1836
|
+
const shortcutKey = getShortcutKey(script.name);
|
|
1837
|
+
const processColor = this.processColors.get(script.name) || COLORS.text;
|
|
1838
|
+
|
|
1839
|
+
let content;
|
|
1840
|
+
if (shortcutKey) {
|
|
1841
|
+
content = t`${fg(isFocused ? COLORS.accent : COLORS.textDim)(indicator)} ${fg(COLORS.warning)(`[${shortcutKey}]`)} ${fg(processColor)(script.displayName)}`;
|
|
1842
|
+
} else {
|
|
1843
|
+
content = t`${fg(isFocused ? COLORS.accent : COLORS.textDim)(indicator)} ${fg(COLORS.textDim)('[ ]')} ${fg(processColor)(script.displayName)}`;
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
const line = new TextRenderable(this.renderer, {
|
|
1847
|
+
id: `shortcut-item-${idx}`,
|
|
1848
|
+
content: content,
|
|
1849
|
+
});
|
|
1850
|
+
container.add(line);
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
buildScriptsSectionContent(container) {
|
|
1466
1855
|
const ignorePatterns = this.config.ignore || [];
|
|
1467
1856
|
|
|
1468
1857
|
this.allScripts.forEach((script, idx) => {
|
|
1469
1858
|
const isIgnored = ignorePatterns.includes(script.name);
|
|
1470
|
-
const isFocused = idx === this.settingsIndex;
|
|
1859
|
+
const isFocused = this.settingsSection === 'scripts' && idx === this.settingsIndex;
|
|
1471
1860
|
const indicator = isFocused ? '>' : ' ';
|
|
1472
1861
|
const checkbox = isIgnored ? '[x]' : '[ ]';
|
|
1473
1862
|
const checkColor = isIgnored ? COLORS.error : COLORS.success;
|
|
1474
1863
|
const processColor = this.processColors.get(script.name) || COLORS.text;
|
|
1475
1864
|
const nameColor = isIgnored ? COLORS.textDim : processColor;
|
|
1476
1865
|
|
|
1866
|
+
let content;
|
|
1867
|
+
if (isIgnored) {
|
|
1868
|
+
content = t`${fg(isFocused ? COLORS.accent : COLORS.textDim)(indicator)} ${fg(checkColor)(checkbox)} ${fg(nameColor)(script.displayName)} ${fg(COLORS.textDim)('(ignored)')}`;
|
|
1869
|
+
} else {
|
|
1870
|
+
content = t`${fg(isFocused ? COLORS.accent : COLORS.textDim)(indicator)} ${fg(checkColor)(checkbox)} ${fg(nameColor)(script.displayName)}`;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1477
1873
|
const line = new TextRenderable(this.renderer, {
|
|
1478
1874
|
id: `script-toggle-${idx}`,
|
|
1479
|
-
content:
|
|
1875
|
+
content: content,
|
|
1480
1876
|
});
|
|
1481
1877
|
container.add(line);
|
|
1482
1878
|
});
|
|
@@ -1506,6 +1902,15 @@ class ProcessManager {
|
|
|
1506
1902
|
this.countdownInterval = null;
|
|
1507
1903
|
}
|
|
1508
1904
|
|
|
1905
|
+
// Clean up command overlay process if running
|
|
1906
|
+
if (this.commandOverlayProcess && this.commandOverlayProcess.pid) {
|
|
1907
|
+
try {
|
|
1908
|
+
kill(this.commandOverlayProcess.pid, 'SIGKILL');
|
|
1909
|
+
} catch (err) {
|
|
1910
|
+
// Ignore
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1509
1914
|
for (const [scriptName, proc] of this.processRefs.entries()) {
|
|
1510
1915
|
try {
|
|
1511
1916
|
if (proc.pid) {
|
|
@@ -1516,6 +1921,75 @@ class ProcessManager {
|
|
|
1516
1921
|
}
|
|
1517
1922
|
}
|
|
1518
1923
|
}
|
|
1924
|
+
|
|
1925
|
+
executeCommand(scriptName) {
|
|
1926
|
+
// Initialize overlay state
|
|
1927
|
+
this.showCommandOverlay = true;
|
|
1928
|
+
this.commandOverlayOutput = [];
|
|
1929
|
+
this.commandOverlayScript = scriptName;
|
|
1930
|
+
this.commandOverlayStatus = 'running';
|
|
1931
|
+
|
|
1932
|
+
// Spawn the process
|
|
1933
|
+
const proc = spawn('npm', ['run', scriptName], {
|
|
1934
|
+
env: {
|
|
1935
|
+
...process.env,
|
|
1936
|
+
FORCE_COLOR: '1',
|
|
1937
|
+
COLORTERM: 'truecolor',
|
|
1938
|
+
},
|
|
1939
|
+
shell: true,
|
|
1940
|
+
});
|
|
1941
|
+
|
|
1942
|
+
this.commandOverlayProcess = proc;
|
|
1943
|
+
|
|
1944
|
+
proc.stdout.on('data', (data) => {
|
|
1945
|
+
const text = data.toString();
|
|
1946
|
+
const lines = text.split('\n');
|
|
1947
|
+
lines.forEach(line => {
|
|
1948
|
+
if (line.trim()) {
|
|
1949
|
+
this.commandOverlayOutput.push(line);
|
|
1950
|
+
this.buildRunningUI();
|
|
1951
|
+
}
|
|
1952
|
+
});
|
|
1953
|
+
});
|
|
1954
|
+
|
|
1955
|
+
proc.stderr.on('data', (data) => {
|
|
1956
|
+
const text = data.toString();
|
|
1957
|
+
const lines = text.split('\n');
|
|
1958
|
+
lines.forEach(line => {
|
|
1959
|
+
if (line.trim()) {
|
|
1960
|
+
this.commandOverlayOutput.push(line);
|
|
1961
|
+
this.buildRunningUI();
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1964
|
+
});
|
|
1965
|
+
|
|
1966
|
+
proc.on('exit', (code) => {
|
|
1967
|
+
this.commandOverlayStatus = code === 0 ? 'exited' : 'crashed';
|
|
1968
|
+
this.commandOverlayOutput.push('');
|
|
1969
|
+
this.commandOverlayOutput.push(`Process exited with code ${code}`);
|
|
1970
|
+
this.commandOverlayProcess = null;
|
|
1971
|
+
this.buildRunningUI();
|
|
1972
|
+
});
|
|
1973
|
+
|
|
1974
|
+
this.buildRunningUI();
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
closeCommandOverlay() {
|
|
1978
|
+
// Kill the process if still running
|
|
1979
|
+
if (this.commandOverlayProcess && this.commandOverlayProcess.pid) {
|
|
1980
|
+
try {
|
|
1981
|
+
kill(this.commandOverlayProcess.pid, 'SIGKILL');
|
|
1982
|
+
} catch (err) {
|
|
1983
|
+
// Ignore
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
this.showCommandOverlay = false;
|
|
1988
|
+
this.commandOverlayOutput = [];
|
|
1989
|
+
this.commandOverlayScript = '';
|
|
1990
|
+
this.commandOverlayStatus = 'running';
|
|
1991
|
+
this.commandOverlayProcess = null;
|
|
1992
|
+
}
|
|
1519
1993
|
|
|
1520
1994
|
buildSelectionUI() {
|
|
1521
1995
|
// Remove old containers if they exist - use destroyRecursively to clean up all children
|
|
@@ -1563,14 +2037,22 @@ class ProcessManager {
|
|
|
1563
2037
|
this.scriptLines = this.scripts.map((script, index) => {
|
|
1564
2038
|
const isSelected = this.selectedScripts.has(script.name);
|
|
1565
2039
|
const isFocused = index === this.selectedIndex;
|
|
1566
|
-
const checkIcon = isSelected ? '●' : '○';
|
|
1567
|
-
const checkColor = isSelected ? COLORS.success : COLORS.textDim;
|
|
1568
2040
|
const processColor = this.processColors.get(script.name) || COLORS.text;
|
|
1569
2041
|
const nameColor = isFocused ? COLORS.text : processColor;
|
|
2042
|
+
const numberColor = processColor;
|
|
2043
|
+
const bracketColor = processColor;
|
|
1570
2044
|
const bgColor = isFocused ? COLORS.bgHighlight : null;
|
|
1571
2045
|
|
|
1572
|
-
//
|
|
1573
|
-
const
|
|
2046
|
+
// Show number for first 9 scripts
|
|
2047
|
+
const numberLabel = index < 9 ? ` ${index + 1}` : ' ';
|
|
2048
|
+
|
|
2049
|
+
// Build checkbox with colored brackets and white x (like running screen)
|
|
2050
|
+
let content;
|
|
2051
|
+
if (isSelected) {
|
|
2052
|
+
content = t`${fg(numberColor)(numberLabel)} ${fg(bracketColor)('[')}${fg(COLORS.text)('x')}${fg(bracketColor)(']')} ${fg(nameColor)(script.displayName)}`;
|
|
2053
|
+
} else {
|
|
2054
|
+
content = t`${fg(numberColor)(numberLabel)} ${fg(bracketColor)('[ ]')} ${fg(nameColor)(script.displayName)}`;
|
|
2055
|
+
}
|
|
1574
2056
|
|
|
1575
2057
|
const lineContainer = new BoxRenderable(this.renderer, {
|
|
1576
2058
|
id: `script-box-${index}`,
|
|
@@ -1835,11 +2317,25 @@ class ProcessManager {
|
|
|
1835
2317
|
const line = newLines[i];
|
|
1836
2318
|
const processColor = this.processColors.get(line.process) || COLORS.text;
|
|
1837
2319
|
const trimmedText = line.text.trim();
|
|
1838
|
-
|
|
2320
|
+
|
|
2321
|
+
// Build content with proper template literal
|
|
2322
|
+
const lineNumber = this.showLineNumbers ? String(line.lineNumber).padStart(4, ' ') : '';
|
|
2323
|
+
const timestamp = this.showTimestamps ? new Date(line.timestamp).toLocaleTimeString('en-US', { hour12: false }) : '';
|
|
2324
|
+
|
|
2325
|
+
let content;
|
|
2326
|
+
if (this.showLineNumbers && this.showTimestamps) {
|
|
2327
|
+
content = t`${fg(COLORS.textDim)(lineNumber)} ${fg(COLORS.textDim)(`[${timestamp}]`)} ${fg(processColor)(`[${line.process}]`)} ${trimmedText}`;
|
|
2328
|
+
} else if (this.showLineNumbers) {
|
|
2329
|
+
content = t`${fg(COLORS.textDim)(lineNumber)} ${fg(processColor)(`[${line.process}]`)} ${trimmedText}`;
|
|
2330
|
+
} else if (this.showTimestamps) {
|
|
2331
|
+
content = t`${fg(COLORS.textDim)(`[${timestamp}]`)} ${fg(processColor)(`[${line.process}]`)} ${trimmedText}`;
|
|
2332
|
+
} else {
|
|
2333
|
+
content = t`${fg(processColor)(`[${line.process}]`)} ${trimmedText}`;
|
|
2334
|
+
}
|
|
1839
2335
|
|
|
1840
2336
|
const outputLine = new TextRenderable(this.renderer, {
|
|
1841
2337
|
id: `output-${pane.id}-${line.lineNumber}`,
|
|
1842
|
-
content:
|
|
2338
|
+
content: content,
|
|
1843
2339
|
bg: '#000000',
|
|
1844
2340
|
});
|
|
1845
2341
|
|
|
@@ -1883,10 +2379,25 @@ class ProcessManager {
|
|
|
1883
2379
|
|
|
1884
2380
|
// Trim whitespace and let text wrap naturally - ScrollBox will handle overflow
|
|
1885
2381
|
const trimmedText = line.text.trim();
|
|
1886
|
-
|
|
2382
|
+
|
|
2383
|
+
// Build content with proper template literal
|
|
2384
|
+
const lineNumber = this.showLineNumbers ? String(line.lineNumber).padStart(4, ' ') : '';
|
|
2385
|
+
const timestamp = this.showTimestamps ? new Date(line.timestamp).toLocaleTimeString('en-US', { hour12: false }) : '';
|
|
2386
|
+
|
|
2387
|
+
let content;
|
|
2388
|
+
if (this.showLineNumbers && this.showTimestamps) {
|
|
2389
|
+
content = t`${fg(COLORS.textDim)(lineNumber)} ${fg(COLORS.textDim)(`[${timestamp}]`)} ${fg(processColor)(`[${line.process}]`)} ${trimmedText}`;
|
|
2390
|
+
} else if (this.showLineNumbers) {
|
|
2391
|
+
content = t`${fg(COLORS.textDim)(lineNumber)} ${fg(processColor)(`[${line.process}]`)} ${trimmedText}`;
|
|
2392
|
+
} else if (this.showTimestamps) {
|
|
2393
|
+
content = t`${fg(COLORS.textDim)(`[${timestamp}]`)} ${fg(processColor)(`[${line.process}]`)} ${trimmedText}`;
|
|
2394
|
+
} else {
|
|
2395
|
+
content = t`${fg(processColor)(`[${line.process}]`)} ${trimmedText}`;
|
|
2396
|
+
}
|
|
2397
|
+
|
|
1887
2398
|
const outputLine = new TextRenderable(this.renderer, {
|
|
1888
2399
|
id: `output-${pane.id}-${i}`,
|
|
1889
|
-
content:
|
|
2400
|
+
content: content,
|
|
1890
2401
|
bg: '#000000', // Black background for pane content
|
|
1891
2402
|
});
|
|
1892
2403
|
|
|
@@ -2081,6 +2592,88 @@ class ProcessManager {
|
|
|
2081
2592
|
parent.add(overlay);
|
|
2082
2593
|
}
|
|
2083
2594
|
|
|
2595
|
+
// Build command output overlay
|
|
2596
|
+
buildCommandOverlay(parent) {
|
|
2597
|
+
const statusIcon = this.commandOverlayStatus === 'running' ? '●' :
|
|
2598
|
+
this.commandOverlayStatus === 'exited' ? '✓' : '✖';
|
|
2599
|
+
const statusColor = this.commandOverlayStatus === 'running' ? COLORS.warning :
|
|
2600
|
+
this.commandOverlayStatus === 'exited' ? COLORS.success : COLORS.error;
|
|
2601
|
+
const title = ` ${statusIcon} ${this.commandOverlayScript} `;
|
|
2602
|
+
|
|
2603
|
+
// Create centered overlay with scrollable content
|
|
2604
|
+
const overlay = new BoxRenderable(this.renderer, {
|
|
2605
|
+
id: 'command-overlay',
|
|
2606
|
+
position: 'absolute',
|
|
2607
|
+
top: '10%',
|
|
2608
|
+
left: '10%',
|
|
2609
|
+
width: '80%',
|
|
2610
|
+
height: '80%',
|
|
2611
|
+
backgroundColor: COLORS.bg,
|
|
2612
|
+
border: true,
|
|
2613
|
+
borderStyle: 'rounded',
|
|
2614
|
+
borderColor: statusColor,
|
|
2615
|
+
title: title,
|
|
2616
|
+
padding: 0,
|
|
2617
|
+
flexDirection: 'column',
|
|
2618
|
+
});
|
|
2619
|
+
|
|
2620
|
+
// Scrollable output content
|
|
2621
|
+
const outputBox = new ScrollBoxRenderable(this.renderer, {
|
|
2622
|
+
id: 'command-output',
|
|
2623
|
+
height: Math.floor(this.renderer.height * 0.8) - 4,
|
|
2624
|
+
scrollX: false,
|
|
2625
|
+
scrollY: true,
|
|
2626
|
+
focusable: true,
|
|
2627
|
+
style: {
|
|
2628
|
+
rootOptions: {
|
|
2629
|
+
flexGrow: 1,
|
|
2630
|
+
paddingLeft: 1,
|
|
2631
|
+
paddingRight: 1,
|
|
2632
|
+
backgroundColor: COLORS.bg,
|
|
2633
|
+
},
|
|
2634
|
+
contentOptions: {
|
|
2635
|
+
backgroundColor: COLORS.bg,
|
|
2636
|
+
width: '100%',
|
|
2637
|
+
},
|
|
2638
|
+
},
|
|
2639
|
+
});
|
|
2640
|
+
|
|
2641
|
+
// Add output lines
|
|
2642
|
+
this.commandOverlayOutput.forEach((line, idx) => {
|
|
2643
|
+
const outputLine = new TextRenderable(this.renderer, {
|
|
2644
|
+
id: `cmd-output-${idx}`,
|
|
2645
|
+
content: line,
|
|
2646
|
+
});
|
|
2647
|
+
outputBox.content.add(outputLine);
|
|
2648
|
+
});
|
|
2649
|
+
|
|
2650
|
+
// Auto-scroll to bottom
|
|
2651
|
+
if (outputBox.scrollTo) {
|
|
2652
|
+
outputBox.scrollTo({ x: 0, y: Number.MAX_SAFE_INTEGER });
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
overlay.add(outputBox);
|
|
2656
|
+
|
|
2657
|
+
// Footer hint
|
|
2658
|
+
const hintBar = new BoxRenderable(this.renderer, {
|
|
2659
|
+
id: 'command-hint-bar',
|
|
2660
|
+
border: ['top'],
|
|
2661
|
+
borderStyle: 'single',
|
|
2662
|
+
borderColor: COLORS.border,
|
|
2663
|
+
paddingTop: 1,
|
|
2664
|
+
paddingLeft: 1,
|
|
2665
|
+
});
|
|
2666
|
+
|
|
2667
|
+
const hint = new TextRenderable(this.renderer, {
|
|
2668
|
+
id: 'command-hint',
|
|
2669
|
+
content: t`${fg(COLORS.textDim)('Press')} ${fg(COLORS.accent)('Esc')} ${fg(COLORS.textDim)('to close')}`,
|
|
2670
|
+
});
|
|
2671
|
+
hintBar.add(hint);
|
|
2672
|
+
overlay.add(hintBar);
|
|
2673
|
+
|
|
2674
|
+
parent.add(overlay);
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2084
2677
|
buildRunningUI() {
|
|
2085
2678
|
// Save scroll positions before destroying
|
|
2086
2679
|
for (const [paneId, scrollBox] of this.paneScrollBoxes.entries()) {
|
|
@@ -2132,15 +2725,7 @@ class ProcessManager {
|
|
|
2132
2725
|
paddingLeft: 1,
|
|
2133
2726
|
});
|
|
2134
2727
|
|
|
2135
|
-
|
|
2136
|
-
const allPanes = getAllPaneIds(this.paneRoot);
|
|
2137
|
-
if (allPanes.length > 1) {
|
|
2138
|
-
const paneIndicator = new TextRenderable(this.renderer, {
|
|
2139
|
-
id: 'pane-indicator',
|
|
2140
|
-
content: t`${fg(COLORS.cyan)(`[${allPanes.length} panes]`)} `,
|
|
2141
|
-
});
|
|
2142
|
-
processBar.add(paneIndicator);
|
|
2143
|
-
}
|
|
2728
|
+
|
|
2144
2729
|
|
|
2145
2730
|
// Add each process with checkbox showing visibility in focused pane
|
|
2146
2731
|
const focusedPane = findPaneById(this.paneRoot, this.focusedPaneId);
|
|
@@ -2153,13 +2738,25 @@ class ProcessManager {
|
|
|
2153
2738
|
const processColor = this.processColors.get(script.name) || COLORS.text;
|
|
2154
2739
|
const isSelected = this.selectedIndex === index;
|
|
2155
2740
|
const isVisible = this.isProcessVisibleInPane(script.name, focusedPane);
|
|
2156
|
-
const checkbox = isVisible ? '[x]' : '[ ]';
|
|
2157
2741
|
const nameColor = isSelected ? COLORS.accent : (isVisible ? processColor : COLORS.textDim);
|
|
2742
|
+
const numberColor = isVisible ? processColor : COLORS.textDim;
|
|
2158
2743
|
const indicator = isSelected ? '>' : ' ';
|
|
2744
|
+
const bracketColor = isVisible ? processColor : COLORS.textDim;
|
|
2745
|
+
|
|
2746
|
+
// Show number for first 9 processes
|
|
2747
|
+
const numberLabel = index < 9 ? `${index + 1}` : ' ';
|
|
2748
|
+
|
|
2749
|
+
// Build content - can't nest template literals, so build entire thing at once
|
|
2750
|
+
let content;
|
|
2751
|
+
if (isVisible) {
|
|
2752
|
+
content = t`${fg(numberColor)(numberLabel)} ${fg(isSelected ? COLORS.accent : COLORS.textDim)(indicator)}${fg(bracketColor)('[')}${fg(COLORS.text)('x')}${fg(bracketColor)(']')} ${fg(statusColor)(statusIcon)} ${fg(nameColor)(script.displayName)}`;
|
|
2753
|
+
} else {
|
|
2754
|
+
content = t`${fg(numberColor)(numberLabel)} ${fg(isSelected ? COLORS.accent : COLORS.textDim)(indicator)}${fg(bracketColor)('[ ]')} ${fg(statusColor)(statusIcon)} ${fg(nameColor)(script.displayName)}`;
|
|
2755
|
+
}
|
|
2159
2756
|
|
|
2160
2757
|
const processItem = new TextRenderable(this.renderer, {
|
|
2161
2758
|
id: `process-item-${index}`,
|
|
2162
|
-
content:
|
|
2759
|
+
content: content,
|
|
2163
2760
|
});
|
|
2164
2761
|
processBar.add(processItem);
|
|
2165
2762
|
});
|
|
@@ -2230,6 +2827,17 @@ class ProcessManager {
|
|
|
2230
2827
|
leftSide.add(filterIndicator);
|
|
2231
2828
|
}
|
|
2232
2829
|
|
|
2830
|
+
// Input mode indicator if active
|
|
2831
|
+
if (this.isInputMode) {
|
|
2832
|
+
const scriptName = this.scripts[this.selectedIndex]?.displayName || '';
|
|
2833
|
+
const inputText = `[${scriptName}]> ${this.inputModeText}_`;
|
|
2834
|
+
const inputIndicator = new TextRenderable(this.renderer, {
|
|
2835
|
+
id: 'input-indicator',
|
|
2836
|
+
content: t`${fg(COLORS.success)(inputText)}`,
|
|
2837
|
+
});
|
|
2838
|
+
leftSide.add(inputIndicator);
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2233
2841
|
// Color filter indicator if active on focused pane
|
|
2234
2842
|
if (focusedPane?.colorFilter) {
|
|
2235
2843
|
const colorMap = {
|
|
@@ -2258,13 +2866,15 @@ class ProcessManager {
|
|
|
2258
2866
|
|
|
2259
2867
|
const shortcuts = [
|
|
2260
2868
|
{ key: '\\', desc: 'panes', color: COLORS.cyan },
|
|
2261
|
-
{ key: '
|
|
2869
|
+
{ key: '1-9', desc: 'toggle', color: COLORS.success },
|
|
2870
|
+
{ key: 'i', desc: 'input', color: COLORS.success },
|
|
2262
2871
|
{ key: 'n', desc: 'name', color: COLORS.accent },
|
|
2263
2872
|
{ key: 'p', desc: 'pause', color: COLORS.warning },
|
|
2264
2873
|
{ key: '/', desc: 'filter', color: COLORS.cyan },
|
|
2265
2874
|
{ key: 'c', desc: 'color', color: COLORS.magenta },
|
|
2266
2875
|
{ key: 's', desc: 'stop', color: COLORS.error },
|
|
2267
2876
|
{ key: 'r', desc: 'restart', color: COLORS.success },
|
|
2877
|
+
{ key: 'o', desc: 'settings', color: COLORS.magenta },
|
|
2268
2878
|
{ key: 'q', desc: 'quit', color: COLORS.error },
|
|
2269
2879
|
];
|
|
2270
2880
|
|
|
@@ -2291,6 +2901,11 @@ class ProcessManager {
|
|
|
2291
2901
|
this.buildSplitMenuOverlay(mainContainer);
|
|
2292
2902
|
}
|
|
2293
2903
|
|
|
2904
|
+
// Add command output overlay if active
|
|
2905
|
+
if (this.showCommandOverlay) {
|
|
2906
|
+
this.buildCommandOverlay(mainContainer);
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2294
2909
|
this.renderer.root.add(mainContainer);
|
|
2295
2910
|
this.runningContainer = mainContainer;
|
|
2296
2911
|
}
|