numux 2.16.2 → 2.17.1
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 -0
- package/dist/man/numux.1 +15 -1
- package/dist/numux.js +720 -110
- package/dist/types.d.ts +8 -0
- package/dist/utils/color.d.ts +3 -3
- package/dist/utils/theme.d.ts +75 -0
- package/package.json +3 -6
package/dist/numux.js
CHANGED
|
@@ -246,6 +246,7 @@ export default defineConfig({
|
|
|
246
246
|
| \`--no-watch\` | Disable file watching even if config has watch patterns |
|
|
247
247
|
| \`-t,\` \`--timestamps\` \`[<format>]\` | Add timestamps to output (default HH:mm:ss.SSS, or pass a format string) |
|
|
248
248
|
| \`--log-dir\` \`<path>\` | Write per-process logs to directory |
|
|
249
|
+
| \`--theme\` \`<light|dark|auto>\` | TUI theme (auto detects terminal background) |
|
|
249
250
|
| \`--debug\` | Enable debug logging to .numux/debug.log |
|
|
250
251
|
| \`-h,\` \`--help\` | Show this help |
|
|
251
252
|
| \`-v,\` \`--version\` | Show version |
|
|
@@ -290,6 +291,7 @@ numux logs api | tail -f # Follow process log output
|
|
|
290
291
|
| \`killOthersOnFail\` | \`boolean\` | Kill all processes when any one exits with a non-zero exit code |
|
|
291
292
|
| \`noWatch\` | \`boolean\` | Disable file watching even if processes have watch patterns |
|
|
292
293
|
| \`logDir\` | \`string\` | Directory to write per-process log files |
|
|
294
|
+
| \`theme\` | \`ThemePref\` | TUI color theme. \`'auto'\` detects the terminal background via OSC 11 (falling back to \`COLORFGBG\` then dark). \`'light'\`/\`'dark'\` skip detection. |
|
|
293
295
|
<!-- /generated:config-global -->
|
|
294
296
|
|
|
295
297
|
\`\`\`ts
|
|
@@ -502,6 +504,341 @@ Search mode (after pressing \`F\`):
|
|
|
502
504
|
};
|
|
503
505
|
});
|
|
504
506
|
|
|
507
|
+
// node_modules/clone/clone.js
|
|
508
|
+
var require_clone = __commonJS((exports, module) => {
|
|
509
|
+
var clone = function() {
|
|
510
|
+
function clone2(parent, circular, depth, prototype) {
|
|
511
|
+
var filter;
|
|
512
|
+
if (typeof circular === "object") {
|
|
513
|
+
depth = circular.depth;
|
|
514
|
+
prototype = circular.prototype;
|
|
515
|
+
filter = circular.filter;
|
|
516
|
+
circular = circular.circular;
|
|
517
|
+
}
|
|
518
|
+
var allParents = [];
|
|
519
|
+
var allChildren = [];
|
|
520
|
+
var useBuffer = typeof Buffer != "undefined";
|
|
521
|
+
if (typeof circular == "undefined")
|
|
522
|
+
circular = true;
|
|
523
|
+
if (typeof depth == "undefined")
|
|
524
|
+
depth = Infinity;
|
|
525
|
+
function _clone(parent2, depth2) {
|
|
526
|
+
if (parent2 === null)
|
|
527
|
+
return null;
|
|
528
|
+
if (depth2 == 0)
|
|
529
|
+
return parent2;
|
|
530
|
+
var child;
|
|
531
|
+
var proto;
|
|
532
|
+
if (typeof parent2 != "object") {
|
|
533
|
+
return parent2;
|
|
534
|
+
}
|
|
535
|
+
if (clone2.__isArray(parent2)) {
|
|
536
|
+
child = [];
|
|
537
|
+
} else if (clone2.__isRegExp(parent2)) {
|
|
538
|
+
child = new RegExp(parent2.source, __getRegExpFlags(parent2));
|
|
539
|
+
if (parent2.lastIndex)
|
|
540
|
+
child.lastIndex = parent2.lastIndex;
|
|
541
|
+
} else if (clone2.__isDate(parent2)) {
|
|
542
|
+
child = new Date(parent2.getTime());
|
|
543
|
+
} else if (useBuffer && Buffer.isBuffer(parent2)) {
|
|
544
|
+
if (Buffer.allocUnsafe) {
|
|
545
|
+
child = Buffer.allocUnsafe(parent2.length);
|
|
546
|
+
} else {
|
|
547
|
+
child = new Buffer(parent2.length);
|
|
548
|
+
}
|
|
549
|
+
parent2.copy(child);
|
|
550
|
+
return child;
|
|
551
|
+
} else {
|
|
552
|
+
if (typeof prototype == "undefined") {
|
|
553
|
+
proto = Object.getPrototypeOf(parent2);
|
|
554
|
+
child = Object.create(proto);
|
|
555
|
+
} else {
|
|
556
|
+
child = Object.create(prototype);
|
|
557
|
+
proto = prototype;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (circular) {
|
|
561
|
+
var index = allParents.indexOf(parent2);
|
|
562
|
+
if (index != -1) {
|
|
563
|
+
return allChildren[index];
|
|
564
|
+
}
|
|
565
|
+
allParents.push(parent2);
|
|
566
|
+
allChildren.push(child);
|
|
567
|
+
}
|
|
568
|
+
for (var i in parent2) {
|
|
569
|
+
var attrs;
|
|
570
|
+
if (proto) {
|
|
571
|
+
attrs = Object.getOwnPropertyDescriptor(proto, i);
|
|
572
|
+
}
|
|
573
|
+
if (attrs && attrs.set == null) {
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
child[i] = _clone(parent2[i], depth2 - 1);
|
|
577
|
+
}
|
|
578
|
+
return child;
|
|
579
|
+
}
|
|
580
|
+
return _clone(parent, depth);
|
|
581
|
+
}
|
|
582
|
+
clone2.clonePrototype = function clonePrototype(parent) {
|
|
583
|
+
if (parent === null)
|
|
584
|
+
return null;
|
|
585
|
+
var c = function() {};
|
|
586
|
+
c.prototype = parent;
|
|
587
|
+
return new c;
|
|
588
|
+
};
|
|
589
|
+
function __objToStr(o) {
|
|
590
|
+
return Object.prototype.toString.call(o);
|
|
591
|
+
}
|
|
592
|
+
clone2.__objToStr = __objToStr;
|
|
593
|
+
function __isDate(o) {
|
|
594
|
+
return typeof o === "object" && __objToStr(o) === "[object Date]";
|
|
595
|
+
}
|
|
596
|
+
clone2.__isDate = __isDate;
|
|
597
|
+
function __isArray(o) {
|
|
598
|
+
return typeof o === "object" && __objToStr(o) === "[object Array]";
|
|
599
|
+
}
|
|
600
|
+
clone2.__isArray = __isArray;
|
|
601
|
+
function __isRegExp(o) {
|
|
602
|
+
return typeof o === "object" && __objToStr(o) === "[object RegExp]";
|
|
603
|
+
}
|
|
604
|
+
clone2.__isRegExp = __isRegExp;
|
|
605
|
+
function __getRegExpFlags(re) {
|
|
606
|
+
var flags = "";
|
|
607
|
+
if (re.global)
|
|
608
|
+
flags += "g";
|
|
609
|
+
if (re.ignoreCase)
|
|
610
|
+
flags += "i";
|
|
611
|
+
if (re.multiline)
|
|
612
|
+
flags += "m";
|
|
613
|
+
return flags;
|
|
614
|
+
}
|
|
615
|
+
clone2.__getRegExpFlags = __getRegExpFlags;
|
|
616
|
+
return clone2;
|
|
617
|
+
}();
|
|
618
|
+
if (typeof module === "object" && module.exports) {
|
|
619
|
+
module.exports = clone;
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
// node_modules/defaults/index.js
|
|
624
|
+
var require_defaults = __commonJS((exports, module) => {
|
|
625
|
+
var clone = require_clone();
|
|
626
|
+
module.exports = function(options, defaults) {
|
|
627
|
+
options = options || {};
|
|
628
|
+
Object.keys(defaults).forEach(function(key) {
|
|
629
|
+
if (typeof options[key] === "undefined") {
|
|
630
|
+
options[key] = clone(defaults[key]);
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
return options;
|
|
634
|
+
};
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
// node_modules/wcwidth/combining.js
|
|
638
|
+
var require_combining = __commonJS((exports, module) => {
|
|
639
|
+
module.exports = [
|
|
640
|
+
[768, 879],
|
|
641
|
+
[1155, 1158],
|
|
642
|
+
[1160, 1161],
|
|
643
|
+
[1425, 1469],
|
|
644
|
+
[1471, 1471],
|
|
645
|
+
[1473, 1474],
|
|
646
|
+
[1476, 1477],
|
|
647
|
+
[1479, 1479],
|
|
648
|
+
[1536, 1539],
|
|
649
|
+
[1552, 1557],
|
|
650
|
+
[1611, 1630],
|
|
651
|
+
[1648, 1648],
|
|
652
|
+
[1750, 1764],
|
|
653
|
+
[1767, 1768],
|
|
654
|
+
[1770, 1773],
|
|
655
|
+
[1807, 1807],
|
|
656
|
+
[1809, 1809],
|
|
657
|
+
[1840, 1866],
|
|
658
|
+
[1958, 1968],
|
|
659
|
+
[2027, 2035],
|
|
660
|
+
[2305, 2306],
|
|
661
|
+
[2364, 2364],
|
|
662
|
+
[2369, 2376],
|
|
663
|
+
[2381, 2381],
|
|
664
|
+
[2385, 2388],
|
|
665
|
+
[2402, 2403],
|
|
666
|
+
[2433, 2433],
|
|
667
|
+
[2492, 2492],
|
|
668
|
+
[2497, 2500],
|
|
669
|
+
[2509, 2509],
|
|
670
|
+
[2530, 2531],
|
|
671
|
+
[2561, 2562],
|
|
672
|
+
[2620, 2620],
|
|
673
|
+
[2625, 2626],
|
|
674
|
+
[2631, 2632],
|
|
675
|
+
[2635, 2637],
|
|
676
|
+
[2672, 2673],
|
|
677
|
+
[2689, 2690],
|
|
678
|
+
[2748, 2748],
|
|
679
|
+
[2753, 2757],
|
|
680
|
+
[2759, 2760],
|
|
681
|
+
[2765, 2765],
|
|
682
|
+
[2786, 2787],
|
|
683
|
+
[2817, 2817],
|
|
684
|
+
[2876, 2876],
|
|
685
|
+
[2879, 2879],
|
|
686
|
+
[2881, 2883],
|
|
687
|
+
[2893, 2893],
|
|
688
|
+
[2902, 2902],
|
|
689
|
+
[2946, 2946],
|
|
690
|
+
[3008, 3008],
|
|
691
|
+
[3021, 3021],
|
|
692
|
+
[3134, 3136],
|
|
693
|
+
[3142, 3144],
|
|
694
|
+
[3146, 3149],
|
|
695
|
+
[3157, 3158],
|
|
696
|
+
[3260, 3260],
|
|
697
|
+
[3263, 3263],
|
|
698
|
+
[3270, 3270],
|
|
699
|
+
[3276, 3277],
|
|
700
|
+
[3298, 3299],
|
|
701
|
+
[3393, 3395],
|
|
702
|
+
[3405, 3405],
|
|
703
|
+
[3530, 3530],
|
|
704
|
+
[3538, 3540],
|
|
705
|
+
[3542, 3542],
|
|
706
|
+
[3633, 3633],
|
|
707
|
+
[3636, 3642],
|
|
708
|
+
[3655, 3662],
|
|
709
|
+
[3761, 3761],
|
|
710
|
+
[3764, 3769],
|
|
711
|
+
[3771, 3772],
|
|
712
|
+
[3784, 3789],
|
|
713
|
+
[3864, 3865],
|
|
714
|
+
[3893, 3893],
|
|
715
|
+
[3895, 3895],
|
|
716
|
+
[3897, 3897],
|
|
717
|
+
[3953, 3966],
|
|
718
|
+
[3968, 3972],
|
|
719
|
+
[3974, 3975],
|
|
720
|
+
[3984, 3991],
|
|
721
|
+
[3993, 4028],
|
|
722
|
+
[4038, 4038],
|
|
723
|
+
[4141, 4144],
|
|
724
|
+
[4146, 4146],
|
|
725
|
+
[4150, 4151],
|
|
726
|
+
[4153, 4153],
|
|
727
|
+
[4184, 4185],
|
|
728
|
+
[4448, 4607],
|
|
729
|
+
[4959, 4959],
|
|
730
|
+
[5906, 5908],
|
|
731
|
+
[5938, 5940],
|
|
732
|
+
[5970, 5971],
|
|
733
|
+
[6002, 6003],
|
|
734
|
+
[6068, 6069],
|
|
735
|
+
[6071, 6077],
|
|
736
|
+
[6086, 6086],
|
|
737
|
+
[6089, 6099],
|
|
738
|
+
[6109, 6109],
|
|
739
|
+
[6155, 6157],
|
|
740
|
+
[6313, 6313],
|
|
741
|
+
[6432, 6434],
|
|
742
|
+
[6439, 6440],
|
|
743
|
+
[6450, 6450],
|
|
744
|
+
[6457, 6459],
|
|
745
|
+
[6679, 6680],
|
|
746
|
+
[6912, 6915],
|
|
747
|
+
[6964, 6964],
|
|
748
|
+
[6966, 6970],
|
|
749
|
+
[6972, 6972],
|
|
750
|
+
[6978, 6978],
|
|
751
|
+
[7019, 7027],
|
|
752
|
+
[7616, 7626],
|
|
753
|
+
[7678, 7679],
|
|
754
|
+
[8203, 8207],
|
|
755
|
+
[8234, 8238],
|
|
756
|
+
[8288, 8291],
|
|
757
|
+
[8298, 8303],
|
|
758
|
+
[8400, 8431],
|
|
759
|
+
[12330, 12335],
|
|
760
|
+
[12441, 12442],
|
|
761
|
+
[43014, 43014],
|
|
762
|
+
[43019, 43019],
|
|
763
|
+
[43045, 43046],
|
|
764
|
+
[64286, 64286],
|
|
765
|
+
[65024, 65039],
|
|
766
|
+
[65056, 65059],
|
|
767
|
+
[65279, 65279],
|
|
768
|
+
[65529, 65531],
|
|
769
|
+
[68097, 68099],
|
|
770
|
+
[68101, 68102],
|
|
771
|
+
[68108, 68111],
|
|
772
|
+
[68152, 68154],
|
|
773
|
+
[68159, 68159],
|
|
774
|
+
[119143, 119145],
|
|
775
|
+
[119155, 119170],
|
|
776
|
+
[119173, 119179],
|
|
777
|
+
[119210, 119213],
|
|
778
|
+
[119362, 119364],
|
|
779
|
+
[917505, 917505],
|
|
780
|
+
[917536, 917631],
|
|
781
|
+
[917760, 917999]
|
|
782
|
+
];
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
// node_modules/wcwidth/index.js
|
|
786
|
+
var require_wcwidth = __commonJS((exports, module) => {
|
|
787
|
+
var defaults = require_defaults();
|
|
788
|
+
var combining = require_combining();
|
|
789
|
+
var DEFAULTS = {
|
|
790
|
+
nul: 0,
|
|
791
|
+
control: 0
|
|
792
|
+
};
|
|
793
|
+
module.exports = function wcwidth2(str) {
|
|
794
|
+
return wcswidth(str, DEFAULTS);
|
|
795
|
+
};
|
|
796
|
+
module.exports.config = function(opts) {
|
|
797
|
+
opts = defaults(opts || {}, DEFAULTS);
|
|
798
|
+
return function wcwidth2(str) {
|
|
799
|
+
return wcswidth(str, opts);
|
|
800
|
+
};
|
|
801
|
+
};
|
|
802
|
+
function wcswidth(str, opts) {
|
|
803
|
+
if (typeof str !== "string")
|
|
804
|
+
return wcwidth(str, opts);
|
|
805
|
+
var s = 0;
|
|
806
|
+
for (var i = 0;i < str.length; i++) {
|
|
807
|
+
var n = wcwidth(str.charCodeAt(i), opts);
|
|
808
|
+
if (n < 0)
|
|
809
|
+
return -1;
|
|
810
|
+
s += n;
|
|
811
|
+
}
|
|
812
|
+
return s;
|
|
813
|
+
}
|
|
814
|
+
function wcwidth(ucs, opts) {
|
|
815
|
+
if (ucs === 0)
|
|
816
|
+
return opts.nul;
|
|
817
|
+
if (ucs < 32 || ucs >= 127 && ucs < 160)
|
|
818
|
+
return opts.control;
|
|
819
|
+
if (bisearch(ucs))
|
|
820
|
+
return 0;
|
|
821
|
+
return 1 + (ucs >= 4352 && (ucs <= 4447 || ucs == 9001 || ucs == 9002 || ucs >= 11904 && ucs <= 42191 && ucs != 12351 || ucs >= 44032 && ucs <= 55203 || ucs >= 63744 && ucs <= 64255 || ucs >= 65040 && ucs <= 65049 || ucs >= 65072 && ucs <= 65135 || ucs >= 65280 && ucs <= 65376 || ucs >= 65504 && ucs <= 65510 || ucs >= 131072 && ucs <= 196605 || ucs >= 196608 && ucs <= 262141));
|
|
822
|
+
}
|
|
823
|
+
function bisearch(ucs) {
|
|
824
|
+
var min = 0;
|
|
825
|
+
var max = combining.length - 1;
|
|
826
|
+
var mid;
|
|
827
|
+
if (ucs < combining[0][0] || ucs > combining[max][1])
|
|
828
|
+
return false;
|
|
829
|
+
while (max >= min) {
|
|
830
|
+
mid = Math.floor((min + max) / 2);
|
|
831
|
+
if (ucs > combining[mid][1])
|
|
832
|
+
min = mid + 1;
|
|
833
|
+
else if (ucs < combining[mid][0])
|
|
834
|
+
max = mid - 1;
|
|
835
|
+
else
|
|
836
|
+
return true;
|
|
837
|
+
}
|
|
838
|
+
return false;
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
|
|
505
842
|
// src/help.ts
|
|
506
843
|
var exports_help = {};
|
|
507
844
|
__export(exports_help, {
|
|
@@ -548,7 +885,7 @@ var init_help = __esm(() => {
|
|
|
548
885
|
var require_package = __commonJS((exports, module) => {
|
|
549
886
|
module.exports = {
|
|
550
887
|
name: "numux",
|
|
551
|
-
version: "2.
|
|
888
|
+
version: "2.17.1",
|
|
552
889
|
description: "Terminal multiplexer with dependency orchestration",
|
|
553
890
|
type: "module",
|
|
554
891
|
license: "MIT",
|
|
@@ -593,8 +930,8 @@ var require_package = __commonJS((exports, module) => {
|
|
|
593
930
|
"dist/"
|
|
594
931
|
],
|
|
595
932
|
dependencies: {
|
|
596
|
-
"@opentui/core": "^0.1
|
|
597
|
-
"ghostty-opentui": "^1.
|
|
933
|
+
"@opentui/core": "^0.4.1",
|
|
934
|
+
"ghostty-opentui": "^1.5.0"
|
|
598
935
|
},
|
|
599
936
|
devDependencies: {
|
|
600
937
|
"@biomejs/biome": "^2.4.4",
|
|
@@ -602,9 +939,6 @@ var require_package = __commonJS((exports, module) => {
|
|
|
602
939
|
"@commitlint/config-conventional": "^20.4.2",
|
|
603
940
|
"@types/bun": "^1.3.9",
|
|
604
941
|
"marked-man": "^2.1.0"
|
|
605
|
-
},
|
|
606
|
-
patchedDependencies: {
|
|
607
|
-
"ghostty-opentui@1.4.7": "patches/ghostty-opentui@1.4.7.patch"
|
|
608
942
|
}
|
|
609
943
|
};
|
|
610
944
|
});
|
|
@@ -876,6 +1210,20 @@ var FLAGS = [
|
|
|
876
1210
|
valueName: "<path>",
|
|
877
1211
|
completionHint: "directory"
|
|
878
1212
|
},
|
|
1213
|
+
{
|
|
1214
|
+
type: "value",
|
|
1215
|
+
long: "--theme",
|
|
1216
|
+
key: "theme",
|
|
1217
|
+
description: "TUI theme (auto detects terminal background)",
|
|
1218
|
+
valueName: "<light|dark|auto>",
|
|
1219
|
+
completionHint: "none",
|
|
1220
|
+
parse(raw, flag) {
|
|
1221
|
+
if (raw !== "light" && raw !== "dark" && raw !== "auto") {
|
|
1222
|
+
throw new Error(`${flag} must be light, dark, or auto. Got "${raw}"`);
|
|
1223
|
+
}
|
|
1224
|
+
return raw;
|
|
1225
|
+
}
|
|
1226
|
+
},
|
|
879
1227
|
{
|
|
880
1228
|
type: "boolean",
|
|
881
1229
|
long: "--debug",
|
|
@@ -1704,10 +2052,11 @@ function resolveColor(color) {
|
|
|
1704
2052
|
return color[0];
|
|
1705
2053
|
return;
|
|
1706
2054
|
}
|
|
1707
|
-
function buildProcessColorMap(names, config) {
|
|
2055
|
+
function buildProcessColorMap(names, config, palette = DEFAULT_PALETTE) {
|
|
1708
2056
|
const map = new Map;
|
|
1709
2057
|
if ("NO_COLOR" in process.env)
|
|
1710
2058
|
return map;
|
|
2059
|
+
const ansiPalette = palette === DEFAULT_PALETTE ? DEFAULT_ANSI_COLORS : palette.map(hexToAnsi);
|
|
1711
2060
|
let paletteIndex = 0;
|
|
1712
2061
|
for (const name of names) {
|
|
1713
2062
|
const explicit = resolveColor(config.processes[name]?.color);
|
|
@@ -1716,15 +2065,15 @@ function buildProcessColorMap(names, config) {
|
|
|
1716
2065
|
if (hex)
|
|
1717
2066
|
map.set(name, hexToAnsi(hex));
|
|
1718
2067
|
else
|
|
1719
|
-
map.set(name,
|
|
2068
|
+
map.set(name, ansiPalette[paletteIndex++ % ansiPalette.length]);
|
|
1720
2069
|
} else {
|
|
1721
|
-
map.set(name,
|
|
2070
|
+
map.set(name, ansiPalette[paletteIndex % ansiPalette.length]);
|
|
1722
2071
|
paletteIndex++;
|
|
1723
2072
|
}
|
|
1724
2073
|
}
|
|
1725
2074
|
return map;
|
|
1726
2075
|
}
|
|
1727
|
-
function buildProcessHexColorMap(names, config) {
|
|
2076
|
+
function buildProcessHexColorMap(names, config, palette = DEFAULT_PALETTE) {
|
|
1728
2077
|
const map = new Map;
|
|
1729
2078
|
if ("NO_COLOR" in process.env)
|
|
1730
2079
|
return map;
|
|
@@ -1736,9 +2085,9 @@ function buildProcessHexColorMap(names, config) {
|
|
|
1736
2085
|
if (hex)
|
|
1737
2086
|
map.set(name, hex);
|
|
1738
2087
|
else
|
|
1739
|
-
map.set(name,
|
|
2088
|
+
map.set(name, palette[paletteIndex++ % palette.length]);
|
|
1740
2089
|
} else {
|
|
1741
|
-
map.set(name,
|
|
2090
|
+
map.set(name, palette[paletteIndex % palette.length]);
|
|
1742
2091
|
paletteIndex++;
|
|
1743
2092
|
}
|
|
1744
2093
|
}
|
|
@@ -1783,6 +2132,7 @@ function validateConfig(raw, _warnings) {
|
|
|
1783
2132
|
const killOthersOnFail = config.killOthersOnFail === true ? true : undefined;
|
|
1784
2133
|
const noWatch = config.noWatch === true ? true : undefined;
|
|
1785
2134
|
const logDir = typeof config.logDir === "string" && config.logDir.trim() ? config.logDir.trim() : undefined;
|
|
2135
|
+
const theme = validateTheme(config.theme);
|
|
1786
2136
|
const validated = {};
|
|
1787
2137
|
for (const name of names) {
|
|
1788
2138
|
let proc = processes[name];
|
|
@@ -1881,9 +2231,19 @@ function validateConfig(raw, _warnings) {
|
|
|
1881
2231
|
...killOthersOnFail ? { killOthersOnFail } : {},
|
|
1882
2232
|
...noWatch ? { noWatch } : {},
|
|
1883
2233
|
...logDir ? { logDir } : {},
|
|
2234
|
+
...theme ? { theme } : {},
|
|
1884
2235
|
processes: validated
|
|
1885
2236
|
};
|
|
1886
2237
|
}
|
|
2238
|
+
var VALID_THEME_VALUES = new Set(["light", "dark", "auto"]);
|
|
2239
|
+
function validateTheme(value) {
|
|
2240
|
+
if (value === undefined)
|
|
2241
|
+
return;
|
|
2242
|
+
if (typeof value !== "string" || !VALID_THEME_VALUES.has(value)) {
|
|
2243
|
+
throw new Error(`theme must be one of: light, dark, auto. Got "${String(value)}"`);
|
|
2244
|
+
}
|
|
2245
|
+
return value;
|
|
2246
|
+
}
|
|
1887
2247
|
function validateStringOrStringArray(value) {
|
|
1888
2248
|
if (typeof value === "string")
|
|
1889
2249
|
return value;
|
|
@@ -2949,6 +3309,178 @@ function setupShutdownHandlers(app, logWriter) {
|
|
|
2949
3309
|
});
|
|
2950
3310
|
}
|
|
2951
3311
|
|
|
3312
|
+
// src/utils/theme.ts
|
|
3313
|
+
var DARK_THEME = {
|
|
3314
|
+
mode: "dark",
|
|
3315
|
+
statusBarBg: "#1a1a1a",
|
|
3316
|
+
statusBarText: "#cccccc",
|
|
3317
|
+
helpBackdropBg: "#000000",
|
|
3318
|
+
helpBoxBg: "#1a1a2e",
|
|
3319
|
+
helpBorder: "#444444",
|
|
3320
|
+
helpText: "#cccccc",
|
|
3321
|
+
sidebarBg: "#1a1a1a",
|
|
3322
|
+
sidebarBorder: "#444444",
|
|
3323
|
+
tabSelectedBg: "#334455",
|
|
3324
|
+
tabSelectedText: "#ffffff",
|
|
3325
|
+
tabText: "#888888",
|
|
3326
|
+
tabDescriptionText: "#888888",
|
|
3327
|
+
tabSelectedDescriptionText: "#cccccc",
|
|
3328
|
+
scrollTrackBg: "#252527",
|
|
3329
|
+
scrollThumbBg: "#9a9ea3",
|
|
3330
|
+
searchCurrentBg: "#b58900",
|
|
3331
|
+
searchMatchBg: "#073642",
|
|
3332
|
+
palette: ["#00cccc", "#cccc00", "#cc00cc", "#5577ff", "#00cc00", "#ff5555", "#ffa500", "#cc88ff"],
|
|
3333
|
+
status: {
|
|
3334
|
+
ready: "#00cc00",
|
|
3335
|
+
failed: "#ff5555",
|
|
3336
|
+
stopped: "#888888",
|
|
3337
|
+
finished: "#66aa66",
|
|
3338
|
+
skipped: "#888888"
|
|
3339
|
+
},
|
|
3340
|
+
inputWaiting: "#ffaa00",
|
|
3341
|
+
errorIndicator: "#ff5555",
|
|
3342
|
+
searchMatchTab: "#b58900",
|
|
3343
|
+
iconDefault: "#888888"
|
|
3344
|
+
};
|
|
3345
|
+
var LIGHT_THEME = {
|
|
3346
|
+
mode: "light",
|
|
3347
|
+
statusBarBg: "#e8e8e8",
|
|
3348
|
+
statusBarText: "#000000",
|
|
3349
|
+
helpBackdropBg: "#ffffff",
|
|
3350
|
+
helpBoxBg: "#f5f5f5",
|
|
3351
|
+
helpBorder: "#aaaaaa",
|
|
3352
|
+
helpText: "#1a1a1a",
|
|
3353
|
+
sidebarBg: "#f0f0f0",
|
|
3354
|
+
sidebarBorder: "#aaaaaa",
|
|
3355
|
+
tabSelectedBg: "#7a9bbf",
|
|
3356
|
+
tabSelectedText: "#ffffff",
|
|
3357
|
+
tabText: "#444444",
|
|
3358
|
+
tabDescriptionText: "#666666",
|
|
3359
|
+
tabSelectedDescriptionText: "#e8e8e8",
|
|
3360
|
+
scrollTrackBg: "#d0d0d0",
|
|
3361
|
+
scrollThumbBg: "#888888",
|
|
3362
|
+
searchCurrentBg: "#ffaa33",
|
|
3363
|
+
searchMatchBg: "#d0e4b8",
|
|
3364
|
+
palette: ["#008888", "#886600", "#880088", "#0033aa", "#006600", "#aa0000", "#cc5500", "#6622aa"],
|
|
3365
|
+
status: {
|
|
3366
|
+
ready: "#006600",
|
|
3367
|
+
failed: "#aa0000",
|
|
3368
|
+
stopped: "#666666",
|
|
3369
|
+
finished: "#2a7a2a",
|
|
3370
|
+
skipped: "#666666"
|
|
3371
|
+
},
|
|
3372
|
+
inputWaiting: "#cc7a00",
|
|
3373
|
+
errorIndicator: "#aa0000",
|
|
3374
|
+
searchMatchTab: "#cc7a00",
|
|
3375
|
+
iconDefault: "#666666"
|
|
3376
|
+
};
|
|
3377
|
+
function themeFor(mode) {
|
|
3378
|
+
return mode === "light" ? LIGHT_THEME : DARK_THEME;
|
|
3379
|
+
}
|
|
3380
|
+
function relativeLuminance(r, g, b) {
|
|
3381
|
+
const norm = (c) => {
|
|
3382
|
+
const s = c / 255;
|
|
3383
|
+
return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
3384
|
+
};
|
|
3385
|
+
return 0.2126 * norm(r) + 0.7152 * norm(g) + 0.0722 * norm(b);
|
|
3386
|
+
}
|
|
3387
|
+
function isLightRgb(r, g, b) {
|
|
3388
|
+
return relativeLuminance(r, g, b) > 0.5;
|
|
3389
|
+
}
|
|
3390
|
+
function parseOSC11Response(data) {
|
|
3391
|
+
const match = data.match(/rgb:([0-9a-f]+)\/([0-9a-f]+)\/([0-9a-f]+)/i);
|
|
3392
|
+
if (!match)
|
|
3393
|
+
return null;
|
|
3394
|
+
const scale = (hex) => {
|
|
3395
|
+
if (hex.length === 0 || hex.length > 4)
|
|
3396
|
+
return Number.NaN;
|
|
3397
|
+
const val = Number.parseInt(hex, 16);
|
|
3398
|
+
if (Number.isNaN(val))
|
|
3399
|
+
return Number.NaN;
|
|
3400
|
+
const max = 16 ** hex.length - 1;
|
|
3401
|
+
return Math.round(val / max * 255);
|
|
3402
|
+
};
|
|
3403
|
+
const r = scale(match[1]);
|
|
3404
|
+
const g = scale(match[2]);
|
|
3405
|
+
const b = scale(match[3]);
|
|
3406
|
+
if (Number.isNaN(r) || Number.isNaN(g) || Number.isNaN(b))
|
|
3407
|
+
return null;
|
|
3408
|
+
return { r, g, b };
|
|
3409
|
+
}
|
|
3410
|
+
function parseColorFgBg(value) {
|
|
3411
|
+
if (!value)
|
|
3412
|
+
return null;
|
|
3413
|
+
const parts = value.split(";");
|
|
3414
|
+
if (parts.length < 2)
|
|
3415
|
+
return null;
|
|
3416
|
+
const bgRaw = parts[parts.length - 1].trim();
|
|
3417
|
+
const bg = Number.parseInt(bgRaw, 10);
|
|
3418
|
+
if (Number.isNaN(bg))
|
|
3419
|
+
return null;
|
|
3420
|
+
return bg >= 7 && bg <= 15 ? "light" : "dark";
|
|
3421
|
+
}
|
|
3422
|
+
function queryOSC11(timeoutMs = 100) {
|
|
3423
|
+
const stdin = process.stdin;
|
|
3424
|
+
const stdout = process.stdout;
|
|
3425
|
+
if (!(stdin.isTTY && stdout.isTTY))
|
|
3426
|
+
return Promise.resolve(null);
|
|
3427
|
+
return new Promise((resolve8) => {
|
|
3428
|
+
let settled = false;
|
|
3429
|
+
let buf = "";
|
|
3430
|
+
let timer = null;
|
|
3431
|
+
const wasRaw = stdin.isRaw;
|
|
3432
|
+
const finish = (result) => {
|
|
3433
|
+
if (settled)
|
|
3434
|
+
return;
|
|
3435
|
+
settled = true;
|
|
3436
|
+
if (timer)
|
|
3437
|
+
clearTimeout(timer);
|
|
3438
|
+
stdin.off("data", onData);
|
|
3439
|
+
try {
|
|
3440
|
+
if (!wasRaw)
|
|
3441
|
+
stdin.setRawMode(false);
|
|
3442
|
+
} catch {}
|
|
3443
|
+
stdin.pause();
|
|
3444
|
+
resolve8(result);
|
|
3445
|
+
};
|
|
3446
|
+
const onData = (chunk) => {
|
|
3447
|
+
buf += chunk.toString("utf8");
|
|
3448
|
+
const match = buf.match(/\x1b\]1[01];rgb:[0-9a-f/]+(?:\x07|\x1b\\)/i);
|
|
3449
|
+
if (!match)
|
|
3450
|
+
return;
|
|
3451
|
+
const parsed = parseOSC11Response(match[0]);
|
|
3452
|
+
if (!parsed) {
|
|
3453
|
+
finish(null);
|
|
3454
|
+
return;
|
|
3455
|
+
}
|
|
3456
|
+
finish(isLightRgb(parsed.r, parsed.g, parsed.b) ? "light" : "dark");
|
|
3457
|
+
};
|
|
3458
|
+
try {
|
|
3459
|
+
stdin.setRawMode(true);
|
|
3460
|
+
stdin.resume();
|
|
3461
|
+
stdin.on("data", onData);
|
|
3462
|
+
timer = setTimeout(() => finish(null), timeoutMs);
|
|
3463
|
+
stdout.write("\x1B]11;?\x1B\\");
|
|
3464
|
+
} catch {
|
|
3465
|
+
finish(null);
|
|
3466
|
+
}
|
|
3467
|
+
});
|
|
3468
|
+
}
|
|
3469
|
+
async function detectThemeMode(timeoutMs = 100) {
|
|
3470
|
+
const osc = await queryOSC11(timeoutMs);
|
|
3471
|
+
if (osc)
|
|
3472
|
+
return osc;
|
|
3473
|
+
return parseColorFgBg(process.env.COLORFGBG);
|
|
3474
|
+
}
|
|
3475
|
+
async function resolveTheme(pref = "auto") {
|
|
3476
|
+
if (pref === "light")
|
|
3477
|
+
return LIGHT_THEME;
|
|
3478
|
+
if (pref === "dark")
|
|
3479
|
+
return DARK_THEME;
|
|
3480
|
+
const detected = await detectThemeMode();
|
|
3481
|
+
return themeFor(detected ?? "dark");
|
|
3482
|
+
}
|
|
3483
|
+
|
|
2952
3484
|
// src/ui/help-overlay.ts
|
|
2953
3485
|
import { BoxRenderable, TextRenderable } from "@opentui/core";
|
|
2954
3486
|
|
|
@@ -3002,7 +3534,7 @@ var STATUS_BAR_TEXT = STATUS_HINTS_COMPACT.map((h) => {
|
|
|
3002
3534
|
class HelpOverlay {
|
|
3003
3535
|
renderable;
|
|
3004
3536
|
textRenderable;
|
|
3005
|
-
constructor(renderer) {
|
|
3537
|
+
constructor(renderer, theme = DARK_THEME) {
|
|
3006
3538
|
this.renderable = new BoxRenderable(renderer, {
|
|
3007
3539
|
id: "help-overlay",
|
|
3008
3540
|
position: "absolute",
|
|
@@ -3018,7 +3550,7 @@ class HelpOverlay {
|
|
|
3018
3550
|
position: "absolute",
|
|
3019
3551
|
width: "100%",
|
|
3020
3552
|
height: "100%",
|
|
3021
|
-
backgroundColor:
|
|
3553
|
+
backgroundColor: theme.helpBackdropBg,
|
|
3022
3554
|
opacity: 0.7
|
|
3023
3555
|
});
|
|
3024
3556
|
const box = new BoxRenderable(renderer, {
|
|
@@ -3026,9 +3558,9 @@ class HelpOverlay {
|
|
|
3026
3558
|
flexDirection: "column",
|
|
3027
3559
|
padding: 1,
|
|
3028
3560
|
paddingX: 5,
|
|
3029
|
-
backgroundColor:
|
|
3561
|
+
backgroundColor: theme.helpBoxBg,
|
|
3030
3562
|
border: true,
|
|
3031
|
-
borderColor:
|
|
3563
|
+
borderColor: theme.helpBorder,
|
|
3032
3564
|
zIndex: 101
|
|
3033
3565
|
});
|
|
3034
3566
|
const lines = [
|
|
@@ -3045,7 +3577,7 @@ class HelpOverlay {
|
|
|
3045
3577
|
id: "help-text",
|
|
3046
3578
|
content: lines.join(`
|
|
3047
3579
|
`),
|
|
3048
|
-
fg:
|
|
3580
|
+
fg: theme.helpText
|
|
3049
3581
|
});
|
|
3050
3582
|
box.add(this.textRenderable);
|
|
3051
3583
|
this.renderable.add(backdrop);
|
|
@@ -3085,6 +3617,7 @@ function resolveTimestampFormat(timestamps) {
|
|
|
3085
3617
|
}
|
|
3086
3618
|
|
|
3087
3619
|
// node_modules/ghostty-opentui/src/terminal-buffer.ts
|
|
3620
|
+
var import_wcwidth = __toESM(require_wcwidth(), 1);
|
|
3088
3621
|
import {
|
|
3089
3622
|
TextBufferRenderable,
|
|
3090
3623
|
StyledText,
|
|
@@ -3093,6 +3626,22 @@ import {
|
|
|
3093
3626
|
import { ptyToJson, PersistentTerminal, hasPersistentTerminalSupport, StyleFlags } from "ghostty-opentui";
|
|
3094
3627
|
var DEFAULT_FG = RGBA.fromHex("#d4d4d4");
|
|
3095
3628
|
var DEFAULT_BG = RGBA.fromHex("#1e1e1e");
|
|
3629
|
+
function getChunkCellWidth(chunk) {
|
|
3630
|
+
return "cellWidth" in chunk && typeof chunk.cellWidth === "number" ? chunk.cellWidth : import_wcwidth.default(chunk.text);
|
|
3631
|
+
}
|
|
3632
|
+
function cellColToStringIndex(text, cellCol) {
|
|
3633
|
+
if (cellCol <= 0)
|
|
3634
|
+
return 0;
|
|
3635
|
+
let col = 0;
|
|
3636
|
+
let strIdx = 0;
|
|
3637
|
+
for (const ch of text) {
|
|
3638
|
+
if (col >= cellCol)
|
|
3639
|
+
break;
|
|
3640
|
+
col += import_wcwidth.default(ch);
|
|
3641
|
+
strIdx += ch.length;
|
|
3642
|
+
}
|
|
3643
|
+
return strIdx;
|
|
3644
|
+
}
|
|
3096
3645
|
var TextAttributes = {
|
|
3097
3646
|
BOLD: 1 << 0,
|
|
3098
3647
|
DIM: 1 << 1,
|
|
@@ -3103,8 +3652,11 @@ var TextAttributes = {
|
|
|
3103
3652
|
HIDDEN: 1 << 6,
|
|
3104
3653
|
STRIKETHROUGH: 1 << 7
|
|
3105
3654
|
};
|
|
3655
|
+
function getLineStarts(lineInfo) {
|
|
3656
|
+
return lineInfo.lineStarts ?? lineInfo.lineStartCols ?? [];
|
|
3657
|
+
}
|
|
3106
3658
|
function convertSpanToChunk(span) {
|
|
3107
|
-
const { text, fg, bg, flags } = span;
|
|
3659
|
+
const { text, fg, bg, flags, width } = span;
|
|
3108
3660
|
let fgColor = fg ? RGBA.fromHex(fg) : DEFAULT_FG;
|
|
3109
3661
|
let bgColor = bg ? RGBA.fromHex(bg) : undefined;
|
|
3110
3662
|
if (flags & StyleFlags.INVERSE) {
|
|
@@ -3123,7 +3675,7 @@ function convertSpanToChunk(span) {
|
|
|
3123
3675
|
attributes |= TextAttributes.STRIKETHROUGH;
|
|
3124
3676
|
if (flags & StyleFlags.FAINT)
|
|
3125
3677
|
attributes |= TextAttributes.DIM;
|
|
3126
|
-
return { __isChunk: true, text, fg: fgColor, bg: bgColor, attributes };
|
|
3678
|
+
return { __isChunk: true, text, fg: fgColor, bg: bgColor, attributes, cellWidth: width };
|
|
3127
3679
|
}
|
|
3128
3680
|
function applyHighlightsToLine(chunks, highlights) {
|
|
3129
3681
|
if (highlights.length === 0)
|
|
@@ -3131,63 +3683,56 @@ function applyHighlightsToLine(chunks, highlights) {
|
|
|
3131
3683
|
const result = [];
|
|
3132
3684
|
let col = 0;
|
|
3133
3685
|
for (const chunk of chunks) {
|
|
3686
|
+
const w = getChunkCellWidth(chunk);
|
|
3134
3687
|
const chunkStart = col;
|
|
3135
|
-
const chunkEnd = col +
|
|
3136
|
-
const
|
|
3137
|
-
if (
|
|
3688
|
+
const chunkEnd = col + w;
|
|
3689
|
+
const overlapping = highlights.filter((hl) => hl.start < chunkEnd && hl.end > chunkStart).sort((a, b) => a.start - b.start);
|
|
3690
|
+
if (overlapping.length === 0) {
|
|
3138
3691
|
result.push(chunk);
|
|
3139
3692
|
col = chunkEnd;
|
|
3140
3693
|
continue;
|
|
3141
3694
|
}
|
|
3142
|
-
let
|
|
3143
|
-
const
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
result.push({
|
|
3150
|
-
__isChunk: true,
|
|
3151
|
-
text: text.slice(pos, hlStartInChunk),
|
|
3152
|
-
fg: chunk.fg,
|
|
3153
|
-
bg: chunk.bg,
|
|
3154
|
-
attributes: chunk.attributes
|
|
3155
|
-
});
|
|
3695
|
+
let cellPos = 0;
|
|
3696
|
+
for (const hl of overlapping) {
|
|
3697
|
+
const hlStartLocal = Math.max(0, hl.start - chunkStart);
|
|
3698
|
+
const hlEndLocal = Math.min(w, hl.end - chunkStart);
|
|
3699
|
+
if (cellPos < hlStartLocal) {
|
|
3700
|
+
const startStr = cellColToStringIndex(chunk.text, cellPos);
|
|
3701
|
+
const endStr = cellColToStringIndex(chunk.text, hlStartLocal);
|
|
3702
|
+
result.push({ ...chunk, text: chunk.text.slice(startStr, endStr), cellWidth: hlStartLocal - cellPos });
|
|
3156
3703
|
}
|
|
3157
|
-
if (
|
|
3158
|
-
const
|
|
3159
|
-
const
|
|
3704
|
+
if (hlStartLocal < hlEndLocal) {
|
|
3705
|
+
const startStr = cellColToStringIndex(chunk.text, hlStartLocal);
|
|
3706
|
+
const endStr = cellColToStringIndex(chunk.text, hlEndLocal);
|
|
3707
|
+
const hlText = chunk.text.slice(startStr, endStr);
|
|
3708
|
+
const cellWidth = hlEndLocal - hlStartLocal;
|
|
3160
3709
|
result.push({
|
|
3161
|
-
|
|
3162
|
-
text:
|
|
3163
|
-
fg: chunk.fg,
|
|
3710
|
+
...chunk,
|
|
3711
|
+
text: hl.replaceWithX ? "x".repeat(cellWidth) : hlText,
|
|
3164
3712
|
bg: RGBA.fromHex(hl.backgroundColor),
|
|
3165
|
-
|
|
3713
|
+
cellWidth
|
|
3166
3714
|
});
|
|
3167
3715
|
}
|
|
3168
|
-
|
|
3169
|
-
}
|
|
3170
|
-
if (
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
text: text.slice(pos),
|
|
3174
|
-
fg: chunk.fg,
|
|
3175
|
-
bg: chunk.bg,
|
|
3176
|
-
attributes: chunk.attributes
|
|
3177
|
-
});
|
|
3716
|
+
cellPos = hlEndLocal;
|
|
3717
|
+
}
|
|
3718
|
+
if (cellPos < w) {
|
|
3719
|
+
const startStr = cellColToStringIndex(chunk.text, cellPos);
|
|
3720
|
+
result.push({ ...chunk, text: chunk.text.slice(startStr), cellWidth: w - cellPos });
|
|
3178
3721
|
}
|
|
3179
3722
|
col = chunkEnd;
|
|
3180
3723
|
}
|
|
3181
3724
|
return result;
|
|
3182
3725
|
}
|
|
3183
3726
|
function makeCursorChunk(char, style, original) {
|
|
3727
|
+
const cellWidth = Math.max(1, import_wcwidth.default(char));
|
|
3184
3728
|
if (style === "block") {
|
|
3185
3729
|
return {
|
|
3186
3730
|
__isChunk: true,
|
|
3187
3731
|
text: char,
|
|
3188
3732
|
fg: original?.bg || RGBA.fromHex("#1e1e1e"),
|
|
3189
3733
|
bg: original?.fg || DEFAULT_FG,
|
|
3190
|
-
attributes: original?.attributes ?? 0
|
|
3734
|
+
attributes: original?.attributes ?? 0,
|
|
3735
|
+
cellWidth
|
|
3191
3736
|
};
|
|
3192
3737
|
}
|
|
3193
3738
|
return {
|
|
@@ -3195,30 +3740,35 @@ function makeCursorChunk(char, style, original) {
|
|
|
3195
3740
|
text: char,
|
|
3196
3741
|
fg: original?.fg || DEFAULT_FG,
|
|
3197
3742
|
bg: original?.bg,
|
|
3198
|
-
attributes: (original?.attributes ?? 0) | TextAttributes.UNDERLINE
|
|
3743
|
+
attributes: (original?.attributes ?? 0) | TextAttributes.UNDERLINE,
|
|
3744
|
+
cellWidth
|
|
3199
3745
|
};
|
|
3200
3746
|
}
|
|
3201
3747
|
function applyCursorToLine(chunks, cursorX, cursorStyle) {
|
|
3202
|
-
const totalLen = chunks.reduce((sum,
|
|
3748
|
+
const totalLen = chunks.reduce((sum, chunk) => sum + getChunkCellWidth(chunk), 0);
|
|
3203
3749
|
if (cursorX >= totalLen) {
|
|
3204
3750
|
const gap = cursorX - totalLen;
|
|
3205
3751
|
if (gap > 0) {
|
|
3206
|
-
return [...chunks, { __isChunk: true, text: " ".repeat(gap), attributes: 0 }, makeCursorChunk(" ", cursorStyle)];
|
|
3752
|
+
return [...chunks, { __isChunk: true, text: " ".repeat(gap), attributes: 0, cellWidth: gap }, makeCursorChunk(" ", cursorStyle)];
|
|
3207
3753
|
}
|
|
3208
3754
|
return [...chunks, makeCursorChunk(" ", cursorStyle)];
|
|
3209
3755
|
}
|
|
3210
3756
|
const result = [];
|
|
3211
3757
|
let col = 0;
|
|
3212
3758
|
for (const chunk of chunks) {
|
|
3213
|
-
const
|
|
3759
|
+
const w = getChunkCellWidth(chunk);
|
|
3760
|
+
const chunkEnd = col + w;
|
|
3214
3761
|
if (cursorX >= col && cursorX < chunkEnd) {
|
|
3215
|
-
const
|
|
3216
|
-
|
|
3217
|
-
|
|
3762
|
+
const localCol = cursorX - col;
|
|
3763
|
+
const strIdx = cellColToStringIndex(chunk.text, localCol);
|
|
3764
|
+
const cursorChar = String.fromCodePoint(chunk.text.codePointAt(strIdx));
|
|
3765
|
+
const strEnd = strIdx + cursorChar.length;
|
|
3766
|
+
if (strIdx > 0) {
|
|
3767
|
+
result.push({ ...chunk, text: chunk.text.slice(0, strIdx) });
|
|
3218
3768
|
}
|
|
3219
|
-
result.push(makeCursorChunk(
|
|
3220
|
-
if (
|
|
3221
|
-
result.push({ ...chunk, text: chunk.text.slice(
|
|
3769
|
+
result.push(makeCursorChunk(cursorChar, cursorStyle, chunk));
|
|
3770
|
+
if (strEnd < chunk.text.length) {
|
|
3771
|
+
result.push({ ...chunk, text: chunk.text.slice(strEnd) });
|
|
3222
3772
|
}
|
|
3223
3773
|
} else {
|
|
3224
3774
|
result.push(chunk);
|
|
@@ -3273,7 +3823,13 @@ class GhosttyTerminalRenderable extends TextBufferRenderable {
|
|
|
3273
3823
|
_ansiDirty = false;
|
|
3274
3824
|
_lineCount = 0;
|
|
3275
3825
|
_showCursor = false;
|
|
3276
|
-
_cursorStyle =
|
|
3826
|
+
_cursorStyle = undefined;
|
|
3827
|
+
_renderCursor = {
|
|
3828
|
+
x: 0,
|
|
3829
|
+
y: 0,
|
|
3830
|
+
visible: false,
|
|
3831
|
+
style: "default"
|
|
3832
|
+
};
|
|
3277
3833
|
_persistent = false;
|
|
3278
3834
|
_persistentTerminal = null;
|
|
3279
3835
|
constructor(ctx, options) {
|
|
@@ -3290,7 +3846,10 @@ class GhosttyTerminalRenderable extends TextBufferRenderable {
|
|
|
3290
3846
|
this._highlights = options.highlights;
|
|
3291
3847
|
this._persistent = options.persistent ?? false;
|
|
3292
3848
|
this._showCursor = options.showCursor ?? false;
|
|
3293
|
-
this._cursorStyle = options.cursorStyle
|
|
3849
|
+
this._cursorStyle = options.cursorStyle;
|
|
3850
|
+
if (options.focusable) {
|
|
3851
|
+
this._focusable = true;
|
|
3852
|
+
}
|
|
3294
3853
|
if (this._persistent && hasPersistentTerminalSupport()) {
|
|
3295
3854
|
this._persistentTerminal = new PersistentTerminal({
|
|
3296
3855
|
cols: this._cols,
|
|
@@ -3434,6 +3993,35 @@ class GhosttyTerminalRenderable extends TextBufferRenderable {
|
|
|
3434
3993
|
}
|
|
3435
3994
|
super.destroy();
|
|
3436
3995
|
}
|
|
3996
|
+
onRemove() {
|
|
3997
|
+
if (this._focused || !this._focusable) {
|
|
3998
|
+
this.hideTerminalCursor();
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
hideTerminalCursor() {
|
|
4002
|
+
this.ctx.setCursorPosition(0, 0, false);
|
|
4003
|
+
}
|
|
4004
|
+
renderTerminalCursor() {
|
|
4005
|
+
if (!this._renderCursor.visible || this._focusable && !this._focused) {
|
|
4006
|
+
this.hideTerminalCursor();
|
|
4007
|
+
return;
|
|
4008
|
+
}
|
|
4009
|
+
const style = this._cursorStyle ?? this._renderCursor.style;
|
|
4010
|
+
this.ctx.setCursorStyle({
|
|
4011
|
+
style,
|
|
4012
|
+
blinking: false
|
|
4013
|
+
});
|
|
4014
|
+
this.ctx.setCursorPosition(this.x + this._renderCursor.x + 1, this.y + this._renderCursor.y + 1, true);
|
|
4015
|
+
}
|
|
4016
|
+
focus() {
|
|
4017
|
+
super.focus();
|
|
4018
|
+
this.requestRender();
|
|
4019
|
+
}
|
|
4020
|
+
blur() {
|
|
4021
|
+
super.blur();
|
|
4022
|
+
this.hideTerminalCursor();
|
|
4023
|
+
this.requestRender();
|
|
4024
|
+
}
|
|
3437
4025
|
renderSelf(buffer) {
|
|
3438
4026
|
if (this._ansiDirty) {
|
|
3439
4027
|
let data;
|
|
@@ -3457,26 +4045,29 @@ class GhosttyTerminalRenderable extends TextBufferRenderable {
|
|
|
3457
4045
|
data.lines.pop();
|
|
3458
4046
|
}
|
|
3459
4047
|
}
|
|
3460
|
-
|
|
3461
|
-
x: data.cursor[0],
|
|
3462
|
-
y: Math.max(0, data.totalLines - data.rows + data.cursor[1] - data.offset),
|
|
3463
|
-
style: this._cursorStyle
|
|
3464
|
-
} : undefined;
|
|
3465
|
-
const styledText = terminalDataToStyledText(data, this._highlights, cursor);
|
|
3466
|
-
this.textBuffer.setStyledText(styledText);
|
|
4048
|
+
this.textBuffer.setStyledText(terminalDataToStyledText(data, this._highlights));
|
|
3467
4049
|
this.updateTextInfo();
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
this.
|
|
4050
|
+
if (this._showCursor) {
|
|
4051
|
+
const cursorY = Math.max(0, data.totalLines - data.rows + data.cursor[1] - data.offset);
|
|
4052
|
+
this._renderCursor.x = data.cursor[0];
|
|
4053
|
+
this._renderCursor.y = cursorY;
|
|
4054
|
+
this._renderCursor.visible = data.cursorVisible && cursorY < data.lines.length;
|
|
4055
|
+
const ts = data.cursorStyle;
|
|
4056
|
+
this._renderCursor.style = ts === "default" ? "default" : ts === "bar" ? "line" : ts === "underline" ? "underline" : "block";
|
|
4057
|
+
} else {
|
|
4058
|
+
this._renderCursor.visible = false;
|
|
3471
4059
|
}
|
|
4060
|
+
const lineInfo = this.textBufferView.logicalLineInfo;
|
|
4061
|
+
this._lineCount = getLineStarts(lineInfo).length;
|
|
3472
4062
|
this._ansiDirty = false;
|
|
3473
4063
|
}
|
|
3474
4064
|
super.renderSelf(buffer);
|
|
4065
|
+
this.renderTerminalCursor();
|
|
3475
4066
|
}
|
|
3476
4067
|
getScrollPositionForLine(lineNumber) {
|
|
3477
4068
|
const clampedLine = Math.max(0, Math.min(lineNumber, this._lineCount - 1));
|
|
3478
4069
|
const lineInfo = this.textBufferView.logicalLineInfo;
|
|
3479
|
-
const lineStarts = lineInfo
|
|
4070
|
+
const lineStarts = getLineStarts(lineInfo);
|
|
3480
4071
|
let lineYOffset = clampedLine;
|
|
3481
4072
|
if (lineStarts && lineStarts.length > clampedLine) {
|
|
3482
4073
|
lineYOffset = lineStarts[clampedLine];
|
|
@@ -3484,13 +4075,6 @@ class GhosttyTerminalRenderable extends TextBufferRenderable {
|
|
|
3484
4075
|
return this.y + lineYOffset;
|
|
3485
4076
|
}
|
|
3486
4077
|
}
|
|
3487
|
-
var EMPTY_LINE_INFO = { lineStarts: [], lineStartCols: [], lineWidthColsMax: 0, lineSources: [] };
|
|
3488
|
-
Object.defineProperty(GhosttyTerminalRenderable.prototype, "lineInfo", {
|
|
3489
|
-
get() {
|
|
3490
|
-
return this.textBufferView?.logicalLineInfo ?? EMPTY_LINE_INFO;
|
|
3491
|
-
},
|
|
3492
|
-
configurable: true
|
|
3493
|
-
});
|
|
3494
4078
|
|
|
3495
4079
|
// src/ui/tailing-terminal.ts
|
|
3496
4080
|
class TailingTerminal extends GhosttyTerminalRenderable {
|
|
@@ -3576,8 +4160,10 @@ class Pane {
|
|
|
3576
4160
|
_onScroll = null;
|
|
3577
4161
|
_onCopy = null;
|
|
3578
4162
|
_onLinkClick = null;
|
|
3579
|
-
|
|
4163
|
+
theme;
|
|
4164
|
+
constructor(renderer, name, cols, rows, interactive = false, theme = DARK_THEME) {
|
|
3580
4165
|
this.renderer = renderer;
|
|
4166
|
+
this.theme = theme;
|
|
3581
4167
|
this.scrollBox = new ScrollBoxRenderable(renderer, {
|
|
3582
4168
|
id: `pane-${name}`,
|
|
3583
4169
|
flexGrow: 1,
|
|
@@ -3585,7 +4171,13 @@ class Pane {
|
|
|
3585
4171
|
stickyScroll: true,
|
|
3586
4172
|
stickyStart: "bottom",
|
|
3587
4173
|
visible: false,
|
|
3588
|
-
onMouseScroll: () => this._onScroll?.()
|
|
4174
|
+
onMouseScroll: () => this._onScroll?.(),
|
|
4175
|
+
scrollbarOptions: {
|
|
4176
|
+
trackOptions: {
|
|
4177
|
+
backgroundColor: theme.scrollTrackBg,
|
|
4178
|
+
foregroundColor: theme.scrollThumbBg
|
|
4179
|
+
}
|
|
4180
|
+
}
|
|
3589
4181
|
});
|
|
3590
4182
|
this.terminal = new TailingTerminal(renderer, {
|
|
3591
4183
|
id: `term-${name}`,
|
|
@@ -3721,7 +4313,7 @@ class Pane {
|
|
|
3721
4313
|
line: m.line,
|
|
3722
4314
|
start: m.start,
|
|
3723
4315
|
end: m.end,
|
|
3724
|
-
backgroundColor: i === currentIndex ?
|
|
4316
|
+
backgroundColor: i === currentIndex ? this.theme.searchCurrentBg : this.theme.searchMatchBg
|
|
3725
4317
|
});
|
|
3726
4318
|
}
|
|
3727
4319
|
this.terminal.highlights = regions;
|
|
@@ -4026,11 +4618,11 @@ class StatusBar {
|
|
|
4026
4618
|
_tempMessage = null;
|
|
4027
4619
|
_tempTimer = null;
|
|
4028
4620
|
_inputMode = false;
|
|
4029
|
-
constructor(renderer) {
|
|
4621
|
+
constructor(renderer, theme = DARK_THEME) {
|
|
4030
4622
|
this.renderable = new BoxRenderable2(renderer, {
|
|
4031
4623
|
id: "status-bar",
|
|
4032
4624
|
width: "100%",
|
|
4033
|
-
backgroundColor:
|
|
4625
|
+
backgroundColor: theme.statusBarBg,
|
|
4034
4626
|
paddingX: 1,
|
|
4035
4627
|
minHeight: 1
|
|
4036
4628
|
});
|
|
@@ -4038,6 +4630,7 @@ class StatusBar {
|
|
|
4038
4630
|
id: "status-bar-text",
|
|
4039
4631
|
width: "100%",
|
|
4040
4632
|
wrapMode: "word",
|
|
4633
|
+
fg: theme.statusBarText,
|
|
4041
4634
|
content: this.buildContent()
|
|
4042
4635
|
});
|
|
4043
4636
|
this.text.selectable = false;
|
|
@@ -4123,13 +4716,15 @@ var STATUS_ICONS = {
|
|
|
4123
4716
|
failed: "\u2716",
|
|
4124
4717
|
skipped: "\u2298"
|
|
4125
4718
|
};
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4719
|
+
function getStatusIconHex(theme) {
|
|
4720
|
+
return {
|
|
4721
|
+
ready: theme.status.ready,
|
|
4722
|
+
finished: theme.status.finished,
|
|
4723
|
+
failed: theme.status.failed,
|
|
4724
|
+
stopped: theme.status.stopped,
|
|
4725
|
+
skipped: theme.status.skipped
|
|
4726
|
+
};
|
|
4727
|
+
}
|
|
4133
4728
|
var TERMINAL_STATUSES = new Set(["finished", "stopped", "failed", "skipped"]);
|
|
4134
4729
|
function getDisplayOrder(originalNames, statuses) {
|
|
4135
4730
|
const active = originalNames.filter((n) => !TERMINAL_STATUSES.has(statuses.get(n)));
|
|
@@ -4149,16 +4744,17 @@ function formatDescription(status, exitCode, restartCount) {
|
|
|
4149
4744
|
}
|
|
4150
4745
|
return desc;
|
|
4151
4746
|
}
|
|
4152
|
-
function resolveOptionColors(names, statuses, processColors, inputWaiting, erroredProcesses, searchMatchProcesses) {
|
|
4747
|
+
function resolveOptionColors(names, statuses, processColors, inputWaiting, erroredProcesses, theme, searchMatchProcesses) {
|
|
4748
|
+
const statusIconHex = getStatusIconHex(theme);
|
|
4153
4749
|
return names.map((name) => {
|
|
4154
4750
|
const status = statuses.get(name);
|
|
4155
4751
|
const waiting = inputWaiting.has(name);
|
|
4156
4752
|
const errored = erroredProcesses.has(name);
|
|
4157
4753
|
const hasSearchMatch = searchMatchProcesses?.has(name);
|
|
4158
|
-
const statusHex = hasSearchMatch ?
|
|
4754
|
+
const statusHex = hasSearchMatch ? theme.searchMatchTab : waiting ? theme.inputWaiting : errored ? theme.errorIndicator : statusIconHex[status];
|
|
4159
4755
|
const processHex = processColors.get(name);
|
|
4160
4756
|
return {
|
|
4161
|
-
iconHex: statusHex ?? processHex ??
|
|
4757
|
+
iconHex: statusHex ?? processHex ?? theme.iconDefault,
|
|
4162
4758
|
nameHex: processHex ?? null
|
|
4163
4759
|
};
|
|
4164
4760
|
});
|
|
@@ -4242,13 +4838,15 @@ class TabBar {
|
|
|
4242
4838
|
inputWaiting = new Set;
|
|
4243
4839
|
erroredProcesses = new Set;
|
|
4244
4840
|
searchMatchCounts = new Map;
|
|
4245
|
-
|
|
4841
|
+
theme;
|
|
4842
|
+
constructor(renderer, names, colors, theme = DARK_THEME, reorderByStatus = false) {
|
|
4246
4843
|
this.originalNames = names;
|
|
4247
4844
|
this.names = [...names];
|
|
4248
4845
|
this.reorderByStatus = reorderByStatus;
|
|
4249
4846
|
this.statuses = new Map(names.map((n) => [n, "pending"]));
|
|
4250
4847
|
this.baseDescriptions = new Map(names.map((n) => [n, "pending"]));
|
|
4251
4848
|
this.processColors = colors ?? new Map;
|
|
4849
|
+
this.theme = theme;
|
|
4252
4850
|
this.renderable = new ColoredSelectRenderable(renderer, {
|
|
4253
4851
|
id: "tab-bar",
|
|
4254
4852
|
width: "100%",
|
|
@@ -4257,9 +4855,13 @@ class TabBar {
|
|
|
4257
4855
|
name: formatTab(n, "pending"),
|
|
4258
4856
|
description: "pending"
|
|
4259
4857
|
})),
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4858
|
+
backgroundColor: theme.sidebarBg,
|
|
4859
|
+
focusedBackgroundColor: theme.sidebarBg,
|
|
4860
|
+
selectedBackgroundColor: theme.tabSelectedBg,
|
|
4861
|
+
selectedTextColor: theme.tabSelectedText,
|
|
4862
|
+
textColor: theme.tabText,
|
|
4863
|
+
descriptionColor: theme.tabDescriptionText,
|
|
4864
|
+
selectedDescriptionColor: theme.tabSelectedDescriptionText,
|
|
4263
4865
|
showDescription: true,
|
|
4264
4866
|
wrapSelection: true
|
|
4265
4867
|
});
|
|
@@ -4343,7 +4945,7 @@ class TabBar {
|
|
|
4343
4945
|
}
|
|
4344
4946
|
updateOptionColors() {
|
|
4345
4947
|
const searchProcesses = this.searchMatchCounts.size > 0 ? new Set(this.searchMatchCounts.keys()) : undefined;
|
|
4346
|
-
const resolved = resolveOptionColors(this.names, this.statuses, this.processColors, this.inputWaiting, this.erroredProcesses, searchProcesses);
|
|
4948
|
+
const resolved = resolveOptionColors(this.names, this.statuses, this.processColors, this.inputWaiting, this.erroredProcesses, this.theme, searchProcesses);
|
|
4347
4949
|
const colors = resolved.map((c) => ({
|
|
4348
4950
|
icon: parseColor(c.iconHex),
|
|
4349
4951
|
name: c.nameHex ? parseColor(c.nameHex) : null
|
|
@@ -4376,6 +4978,7 @@ class App {
|
|
|
4376
4978
|
sidebarWidth = 20;
|
|
4377
4979
|
config;
|
|
4378
4980
|
logWriter;
|
|
4981
|
+
theme = DARK_THEME;
|
|
4379
4982
|
resizeTimer = null;
|
|
4380
4983
|
inputWaitTimers = new Map;
|
|
4381
4984
|
awaitingInput = new Set;
|
|
@@ -4386,6 +4989,9 @@ class App {
|
|
|
4386
4989
|
this.names = manager.getProcessNames();
|
|
4387
4990
|
}
|
|
4388
4991
|
async start() {
|
|
4992
|
+
log(`theme detect: pref=${this.config.theme ?? "auto"} stdin.isTTY=${process.stdin.isTTY} stdout.isTTY=${process.stdout.isTTY} COLORFGBG=${process.env.COLORFGBG ?? "(unset)"}`);
|
|
4993
|
+
this.theme = await resolveTheme(this.config.theme);
|
|
4994
|
+
log(`theme resolved: ${this.theme.mode}`);
|
|
4389
4995
|
this.renderer = await createCliRenderer({
|
|
4390
4996
|
exitOnCtrlC: false,
|
|
4391
4997
|
useMouse: true,
|
|
@@ -4404,8 +5010,8 @@ class App {
|
|
|
4404
5010
|
height: "100%",
|
|
4405
5011
|
border: false
|
|
4406
5012
|
});
|
|
4407
|
-
const processHexColors = buildProcessHexColorMap(this.names, this.config);
|
|
4408
|
-
this.tabBar = new TabBar(this.renderer, this.names, processHexColors, this.config.sort === "status");
|
|
5013
|
+
const processHexColors = buildProcessHexColorMap(this.names, this.config, this.theme.palette);
|
|
5014
|
+
this.tabBar = new TabBar(this.renderer, this.names, processHexColors, this.theme, this.config.sort === "status");
|
|
4409
5015
|
const contentRow = new BoxRenderable3(this.renderer, {
|
|
4410
5016
|
id: "content-row",
|
|
4411
5017
|
flexDirection: "row",
|
|
@@ -4418,7 +5024,8 @@ class App {
|
|
|
4418
5024
|
width: this.sidebarWidth,
|
|
4419
5025
|
height: "100%",
|
|
4420
5026
|
border: ["right"],
|
|
4421
|
-
borderColor:
|
|
5027
|
+
borderColor: this.theme.sidebarBorder,
|
|
5028
|
+
backgroundColor: this.theme.sidebarBg
|
|
4422
5029
|
});
|
|
4423
5030
|
sidebar.add(this.tabBar.renderable);
|
|
4424
5031
|
const paneContainer = new BoxRenderable3(this.renderer, {
|
|
@@ -4426,8 +5033,8 @@ class App {
|
|
|
4426
5033
|
flexGrow: 1,
|
|
4427
5034
|
border: false
|
|
4428
5035
|
});
|
|
4429
|
-
this.statusBar = new StatusBar(this.renderer);
|
|
4430
|
-
this.helpOverlay = new HelpOverlay(this.renderer);
|
|
5036
|
+
this.statusBar = new StatusBar(this.renderer, this.theme);
|
|
5037
|
+
this.helpOverlay = new HelpOverlay(this.renderer, this.theme);
|
|
4431
5038
|
this.search = new SearchController({
|
|
4432
5039
|
logWriter: this.logWriter,
|
|
4433
5040
|
statusBar: this.statusBar,
|
|
@@ -4437,7 +5044,7 @@ class App {
|
|
|
4437
5044
|
});
|
|
4438
5045
|
for (const name of this.names) {
|
|
4439
5046
|
const interactive = this.config.processes[name].interactive === true;
|
|
4440
|
-
const pane = new Pane(this.renderer, name, termCols, termRows, interactive);
|
|
5047
|
+
const pane = new Pane(this.renderer, name, termCols, termRows, interactive, this.theme);
|
|
4441
5048
|
if (this.config.timestamps) {
|
|
4442
5049
|
pane.setTimestamps(this.config.timestamps);
|
|
4443
5050
|
}
|
|
@@ -5455,6 +6062,9 @@ async function main() {
|
|
|
5455
6062
|
if (parsed.only || parsed.exclude) {
|
|
5456
6063
|
config = filterConfig(config, parsed.only, parsed.exclude);
|
|
5457
6064
|
}
|
|
6065
|
+
if (parsed.theme) {
|
|
6066
|
+
config.theme = parsed.theme;
|
|
6067
|
+
}
|
|
5458
6068
|
if (parsed.autoColors) {
|
|
5459
6069
|
for (const [name, proc] of Object.entries(config.processes)) {
|
|
5460
6070
|
if (!proc.color) {
|