@teammates/consolonia 0.7.1 → 0.7.2
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/dist/__tests__/ansi.test.js +147 -26
- package/dist/ansi/win32-console.d.ts +28 -0
- package/dist/ansi/win32-console.js +122 -0
- package/dist/app.js +16 -8
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/package.json +4 -1
|
@@ -4,6 +4,7 @@ import * as esc from "../ansi/esc.js";
|
|
|
4
4
|
import { AnsiOutput } from "../ansi/output.js";
|
|
5
5
|
import { stripAnsi, truncateAnsi, visibleLength } from "../ansi/strip.js";
|
|
6
6
|
import { detectTerminal } from "../ansi/terminal-env.js";
|
|
7
|
+
import { enableWin32Mouse, restoreWin32Console, } from "../ansi/win32-console.js";
|
|
7
8
|
// ── Helpers ────────────────────────────────────────────────────────
|
|
8
9
|
const ESC = "\x1b[";
|
|
9
10
|
/** Build a mock writable stream that accumulates output to a string. */
|
|
@@ -534,7 +535,10 @@ describe("detectTerminal", () => {
|
|
|
534
535
|
}
|
|
535
536
|
Object.assign(process.env, origEnv);
|
|
536
537
|
Object.defineProperty(process, "platform", { value: origPlatform });
|
|
537
|
-
Object.defineProperty(process.stdout, "isTTY", {
|
|
538
|
+
Object.defineProperty(process.stdout, "isTTY", {
|
|
539
|
+
value: origIsTTY,
|
|
540
|
+
writable: true,
|
|
541
|
+
});
|
|
538
542
|
});
|
|
539
543
|
function setEnv(overrides) {
|
|
540
544
|
for (const [k, v] of Object.entries(overrides)) {
|
|
@@ -545,7 +549,10 @@ describe("detectTerminal", () => {
|
|
|
545
549
|
}
|
|
546
550
|
}
|
|
547
551
|
it("returns pipe caps when stdout is not a TTY", () => {
|
|
548
|
-
Object.defineProperty(process.stdout, "isTTY", {
|
|
552
|
+
Object.defineProperty(process.stdout, "isTTY", {
|
|
553
|
+
value: false,
|
|
554
|
+
writable: true,
|
|
555
|
+
});
|
|
549
556
|
const caps = detectTerminal();
|
|
550
557
|
expect(caps.isTTY).toBe(false);
|
|
551
558
|
expect(caps.mouse).toBe(false);
|
|
@@ -554,7 +561,10 @@ describe("detectTerminal", () => {
|
|
|
554
561
|
});
|
|
555
562
|
it("detects Windows Terminal via WT_SESSION", () => {
|
|
556
563
|
Object.defineProperty(process, "platform", { value: "win32" });
|
|
557
|
-
Object.defineProperty(process.stdout, "isTTY", {
|
|
564
|
+
Object.defineProperty(process.stdout, "isTTY", {
|
|
565
|
+
value: true,
|
|
566
|
+
writable: true,
|
|
567
|
+
});
|
|
558
568
|
setEnv({ WT_SESSION: "some-guid" });
|
|
559
569
|
const caps = detectTerminal();
|
|
560
570
|
expect(caps.name).toBe("windows-terminal");
|
|
@@ -563,7 +573,10 @@ describe("detectTerminal", () => {
|
|
|
563
573
|
});
|
|
564
574
|
it("detects VS Code terminal on Windows", () => {
|
|
565
575
|
Object.defineProperty(process, "platform", { value: "win32" });
|
|
566
|
-
Object.defineProperty(process.stdout, "isTTY", {
|
|
576
|
+
Object.defineProperty(process.stdout, "isTTY", {
|
|
577
|
+
value: true,
|
|
578
|
+
writable: true,
|
|
579
|
+
});
|
|
567
580
|
setEnv({ WT_SESSION: undefined, TERM_PROGRAM: "vscode" });
|
|
568
581
|
const caps = detectTerminal();
|
|
569
582
|
expect(caps.name).toBe("vscode");
|
|
@@ -571,21 +584,40 @@ describe("detectTerminal", () => {
|
|
|
571
584
|
});
|
|
572
585
|
it("detects ConEmu on Windows", () => {
|
|
573
586
|
Object.defineProperty(process, "platform", { value: "win32" });
|
|
574
|
-
Object.defineProperty(process.stdout, "isTTY", {
|
|
575
|
-
|
|
587
|
+
Object.defineProperty(process.stdout, "isTTY", {
|
|
588
|
+
value: true,
|
|
589
|
+
writable: true,
|
|
590
|
+
});
|
|
591
|
+
setEnv({
|
|
592
|
+
WT_SESSION: undefined,
|
|
593
|
+
TERM_PROGRAM: undefined,
|
|
594
|
+
ConEmuPID: "1234",
|
|
595
|
+
});
|
|
576
596
|
const caps = detectTerminal();
|
|
577
597
|
expect(caps.name).toBe("conemu");
|
|
578
598
|
});
|
|
579
599
|
it("detects mintty via TERM + MSYSTEM", () => {
|
|
580
600
|
Object.defineProperty(process, "platform", { value: "win32" });
|
|
581
|
-
Object.defineProperty(process.stdout, "isTTY", {
|
|
582
|
-
|
|
601
|
+
Object.defineProperty(process.stdout, "isTTY", {
|
|
602
|
+
value: true,
|
|
603
|
+
writable: true,
|
|
604
|
+
});
|
|
605
|
+
setEnv({
|
|
606
|
+
WT_SESSION: undefined,
|
|
607
|
+
TERM_PROGRAM: undefined,
|
|
608
|
+
ConEmuPID: undefined,
|
|
609
|
+
TERM: "xterm-256color",
|
|
610
|
+
MSYSTEM: "MINGW64",
|
|
611
|
+
});
|
|
583
612
|
const caps = detectTerminal();
|
|
584
613
|
expect(caps.name).toBe("mintty");
|
|
585
614
|
});
|
|
586
615
|
it("detects tmux on Unix", () => {
|
|
587
616
|
Object.defineProperty(process, "platform", { value: "linux" });
|
|
588
|
-
Object.defineProperty(process.stdout, "isTTY", {
|
|
617
|
+
Object.defineProperty(process.stdout, "isTTY", {
|
|
618
|
+
value: true,
|
|
619
|
+
writable: true,
|
|
620
|
+
});
|
|
589
621
|
setEnv({ TMUX: "/tmp/tmux-1000/default,1234,0", TERM: "screen-256color" });
|
|
590
622
|
const caps = detectTerminal();
|
|
591
623
|
expect(caps.name).toBe("tmux");
|
|
@@ -593,8 +625,16 @@ describe("detectTerminal", () => {
|
|
|
593
625
|
});
|
|
594
626
|
it("detects GNU screen with limited caps", () => {
|
|
595
627
|
Object.defineProperty(process, "platform", { value: "linux" });
|
|
596
|
-
Object.defineProperty(process.stdout, "isTTY", {
|
|
597
|
-
|
|
628
|
+
Object.defineProperty(process.stdout, "isTTY", {
|
|
629
|
+
value: true,
|
|
630
|
+
writable: true,
|
|
631
|
+
});
|
|
632
|
+
setEnv({
|
|
633
|
+
TMUX: undefined,
|
|
634
|
+
TERM: "screen",
|
|
635
|
+
TERM_PROGRAM: undefined,
|
|
636
|
+
ITERM_SESSION_ID: undefined,
|
|
637
|
+
});
|
|
598
638
|
const caps = detectTerminal();
|
|
599
639
|
expect(caps.name).toBe("screen");
|
|
600
640
|
expect(caps.sgrMouse).toBe(false);
|
|
@@ -602,24 +642,48 @@ describe("detectTerminal", () => {
|
|
|
602
642
|
});
|
|
603
643
|
it("detects iTerm2", () => {
|
|
604
644
|
Object.defineProperty(process, "platform", { value: "darwin" });
|
|
605
|
-
Object.defineProperty(process.stdout, "isTTY", {
|
|
606
|
-
|
|
645
|
+
Object.defineProperty(process.stdout, "isTTY", {
|
|
646
|
+
value: true,
|
|
647
|
+
writable: true,
|
|
648
|
+
});
|
|
649
|
+
setEnv({
|
|
650
|
+
TMUX: undefined,
|
|
651
|
+
TERM: "xterm-256color",
|
|
652
|
+
TERM_PROGRAM: "iTerm.app",
|
|
653
|
+
});
|
|
607
654
|
const caps = detectTerminal();
|
|
608
655
|
expect(caps.name).toBe("iterm2");
|
|
609
656
|
expect(caps.truecolor).toBe(true);
|
|
610
657
|
});
|
|
611
658
|
it("detects xterm-compatible with COLORTERM truecolor", () => {
|
|
612
659
|
Object.defineProperty(process, "platform", { value: "linux" });
|
|
613
|
-
Object.defineProperty(process.stdout, "isTTY", {
|
|
614
|
-
|
|
660
|
+
Object.defineProperty(process.stdout, "isTTY", {
|
|
661
|
+
value: true,
|
|
662
|
+
writable: true,
|
|
663
|
+
});
|
|
664
|
+
setEnv({
|
|
665
|
+
TMUX: undefined,
|
|
666
|
+
TERM: "xterm-256color",
|
|
667
|
+
TERM_PROGRAM: undefined,
|
|
668
|
+
ITERM_SESSION_ID: undefined,
|
|
669
|
+
COLORTERM: "truecolor",
|
|
670
|
+
});
|
|
615
671
|
const caps = detectTerminal();
|
|
616
672
|
expect(caps.truecolor).toBe(true);
|
|
617
673
|
expect(caps.color256).toBe(true);
|
|
618
674
|
});
|
|
619
675
|
it("detects dumb terminal with minimal caps", () => {
|
|
620
676
|
Object.defineProperty(process, "platform", { value: "linux" });
|
|
621
|
-
Object.defineProperty(process.stdout, "isTTY", {
|
|
622
|
-
|
|
677
|
+
Object.defineProperty(process.stdout, "isTTY", {
|
|
678
|
+
value: true,
|
|
679
|
+
writable: true,
|
|
680
|
+
});
|
|
681
|
+
setEnv({
|
|
682
|
+
TMUX: undefined,
|
|
683
|
+
TERM: "dumb",
|
|
684
|
+
TERM_PROGRAM: undefined,
|
|
685
|
+
ITERM_SESSION_ID: undefined,
|
|
686
|
+
});
|
|
623
687
|
const caps = detectTerminal();
|
|
624
688
|
expect(caps.name).toBe("dumb");
|
|
625
689
|
expect(caps.mouse).toBe(false);
|
|
@@ -667,7 +731,10 @@ describe("esc environment-aware sequences", () => {
|
|
|
667
731
|
describe("initSequence", () => {
|
|
668
732
|
it("includes all features for a full-caps terminal", () => {
|
|
669
733
|
const caps = makeCaps();
|
|
670
|
-
const seq = esc.initSequence(caps, {
|
|
734
|
+
const seq = esc.initSequence(caps, {
|
|
735
|
+
alternateScreen: true,
|
|
736
|
+
mouse: true,
|
|
737
|
+
});
|
|
671
738
|
expect(seq).toContain(esc.alternateScreenOn);
|
|
672
739
|
expect(seq).toContain(esc.hideCursor);
|
|
673
740
|
expect(seq).toContain(esc.bracketedPasteOn);
|
|
@@ -676,34 +743,52 @@ describe("esc environment-aware sequences", () => {
|
|
|
676
743
|
});
|
|
677
744
|
it("skips alternate screen when not supported", () => {
|
|
678
745
|
const caps = makeCaps({ alternateScreen: false });
|
|
679
|
-
const seq = esc.initSequence(caps, {
|
|
746
|
+
const seq = esc.initSequence(caps, {
|
|
747
|
+
alternateScreen: true,
|
|
748
|
+
mouse: false,
|
|
749
|
+
});
|
|
680
750
|
expect(seq).not.toContain(esc.alternateScreenOn);
|
|
681
751
|
});
|
|
682
752
|
it("skips alternate screen when app opts out", () => {
|
|
683
753
|
const caps = makeCaps();
|
|
684
|
-
const seq = esc.initSequence(caps, {
|
|
754
|
+
const seq = esc.initSequence(caps, {
|
|
755
|
+
alternateScreen: false,
|
|
756
|
+
mouse: false,
|
|
757
|
+
});
|
|
685
758
|
expect(seq).not.toContain(esc.alternateScreenOn);
|
|
686
759
|
});
|
|
687
760
|
it("skips bracketed paste when not supported", () => {
|
|
688
761
|
const caps = makeCaps({ bracketedPaste: false });
|
|
689
|
-
const seq = esc.initSequence(caps, {
|
|
762
|
+
const seq = esc.initSequence(caps, {
|
|
763
|
+
alternateScreen: false,
|
|
764
|
+
mouse: false,
|
|
765
|
+
});
|
|
690
766
|
expect(seq).not.toContain(esc.bracketedPasteOn);
|
|
691
767
|
});
|
|
692
768
|
it("skips mouse when app opts out even if caps support it", () => {
|
|
693
769
|
const caps = makeCaps();
|
|
694
|
-
const seq = esc.initSequence(caps, {
|
|
770
|
+
const seq = esc.initSequence(caps, {
|
|
771
|
+
alternateScreen: false,
|
|
772
|
+
mouse: false,
|
|
773
|
+
});
|
|
695
774
|
expect(seq).not.toContain("?1000h");
|
|
696
775
|
});
|
|
697
776
|
it("returns empty string for non-TTY", () => {
|
|
698
777
|
const caps = makeCaps({ isTTY: false });
|
|
699
|
-
const seq = esc.initSequence(caps, {
|
|
778
|
+
const seq = esc.initSequence(caps, {
|
|
779
|
+
alternateScreen: true,
|
|
780
|
+
mouse: true,
|
|
781
|
+
});
|
|
700
782
|
expect(seq).toBe("");
|
|
701
783
|
});
|
|
702
784
|
});
|
|
703
785
|
describe("restoreSequence", () => {
|
|
704
786
|
it("includes all restore features for a full-caps terminal", () => {
|
|
705
787
|
const caps = makeCaps();
|
|
706
|
-
const seq = esc.restoreSequence(caps, {
|
|
788
|
+
const seq = esc.restoreSequence(caps, {
|
|
789
|
+
alternateScreen: true,
|
|
790
|
+
mouse: true,
|
|
791
|
+
});
|
|
707
792
|
expect(seq).toContain(esc.reset);
|
|
708
793
|
expect(seq).toContain(esc.mouseTrackingOff);
|
|
709
794
|
expect(seq).toContain(esc.bracketedPasteOff);
|
|
@@ -712,15 +797,51 @@ describe("esc environment-aware sequences", () => {
|
|
|
712
797
|
});
|
|
713
798
|
it("mirrors initSequence — skips what init skipped", () => {
|
|
714
799
|
const caps = makeCaps({ bracketedPaste: false, alternateScreen: false });
|
|
715
|
-
const seq = esc.restoreSequence(caps, {
|
|
800
|
+
const seq = esc.restoreSequence(caps, {
|
|
801
|
+
alternateScreen: true,
|
|
802
|
+
mouse: false,
|
|
803
|
+
});
|
|
716
804
|
expect(seq).not.toContain(esc.bracketedPasteOff);
|
|
717
805
|
expect(seq).not.toContain(esc.alternateScreenOff);
|
|
718
806
|
expect(seq).not.toContain(esc.mouseTrackingOff);
|
|
719
807
|
});
|
|
720
808
|
it("returns empty string for non-TTY", () => {
|
|
721
809
|
const caps = makeCaps({ isTTY: false });
|
|
722
|
-
const seq = esc.restoreSequence(caps, {
|
|
810
|
+
const seq = esc.restoreSequence(caps, {
|
|
811
|
+
alternateScreen: true,
|
|
812
|
+
mouse: true,
|
|
813
|
+
});
|
|
723
814
|
expect(seq).toBe("");
|
|
724
815
|
});
|
|
725
816
|
});
|
|
726
817
|
});
|
|
818
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
819
|
+
// win32-console.ts
|
|
820
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
821
|
+
describe("win32-console", () => {
|
|
822
|
+
const originalPlatform = process.platform;
|
|
823
|
+
afterEach(() => {
|
|
824
|
+
Object.defineProperty(process, "platform", { value: originalPlatform });
|
|
825
|
+
});
|
|
826
|
+
describe("enableWin32Mouse", () => {
|
|
827
|
+
it("returns false on non-win32 platforms", () => {
|
|
828
|
+
Object.defineProperty(process, "platform", { value: "linux" });
|
|
829
|
+
expect(enableWin32Mouse()).toBe(false);
|
|
830
|
+
});
|
|
831
|
+
it("returns false on darwin", () => {
|
|
832
|
+
Object.defineProperty(process, "platform", { value: "darwin" });
|
|
833
|
+
expect(enableWin32Mouse()).toBe(false);
|
|
834
|
+
});
|
|
835
|
+
});
|
|
836
|
+
describe("restoreWin32Console", () => {
|
|
837
|
+
it("returns false on non-win32 platforms", () => {
|
|
838
|
+
Object.defineProperty(process, "platform", { value: "linux" });
|
|
839
|
+
expect(restoreWin32Console()).toBe(false);
|
|
840
|
+
});
|
|
841
|
+
it("returns false when no original mode was saved", () => {
|
|
842
|
+
// Even on win32, if enableWin32Mouse was never called, restore is a no-op
|
|
843
|
+
Object.defineProperty(process, "platform", { value: "win32" });
|
|
844
|
+
expect(restoreWin32Console()).toBe(false);
|
|
845
|
+
});
|
|
846
|
+
});
|
|
847
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Win32 Console Mode — enables mouse input on Windows terminals.
|
|
3
|
+
*
|
|
4
|
+
* Node.js `setRawMode(true)` enables ENABLE_VIRTUAL_TERMINAL_INPUT but
|
|
5
|
+
* does NOT disable ENABLE_QUICK_EDIT_MODE (which intercepts mouse clicks
|
|
6
|
+
* for text selection) or enable ENABLE_MOUSE_INPUT. This module uses
|
|
7
|
+
* koffi to call the Win32 API directly and set the correct flags.
|
|
8
|
+
*
|
|
9
|
+
* Only loaded on win32 — no-ops on other platforms.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Configure the Windows console for mouse input.
|
|
13
|
+
*
|
|
14
|
+
* Disables Quick Edit Mode (which swallows mouse clicks) and enables
|
|
15
|
+
* ENABLE_MOUSE_INPUT + ENABLE_EXTENDED_FLAGS + ENABLE_WINDOW_INPUT.
|
|
16
|
+
* Saves the original mode so it can be restored later.
|
|
17
|
+
*
|
|
18
|
+
* No-op on non-Windows platforms or if koffi is not available.
|
|
19
|
+
* Returns true if the mode was successfully changed.
|
|
20
|
+
*/
|
|
21
|
+
export declare function enableWin32Mouse(): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Restore the original Windows console mode saved by enableWin32Mouse().
|
|
24
|
+
*
|
|
25
|
+
* No-op if enableWin32Mouse() was never called or failed.
|
|
26
|
+
* Returns true if the mode was successfully restored.
|
|
27
|
+
*/
|
|
28
|
+
export declare function restoreWin32Console(): boolean;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Win32 Console Mode — enables mouse input on Windows terminals.
|
|
3
|
+
*
|
|
4
|
+
* Node.js `setRawMode(true)` enables ENABLE_VIRTUAL_TERMINAL_INPUT but
|
|
5
|
+
* does NOT disable ENABLE_QUICK_EDIT_MODE (which intercepts mouse clicks
|
|
6
|
+
* for text selection) or enable ENABLE_MOUSE_INPUT. This module uses
|
|
7
|
+
* koffi to call the Win32 API directly and set the correct flags.
|
|
8
|
+
*
|
|
9
|
+
* Only loaded on win32 — no-ops on other platforms.
|
|
10
|
+
*/
|
|
11
|
+
import { createRequire } from "node:module";
|
|
12
|
+
// ── Console mode flag constants ─────────────────────────────────────
|
|
13
|
+
const ENABLE_PROCESSED_INPUT = 0x0001;
|
|
14
|
+
const ENABLE_LINE_INPUT = 0x0002;
|
|
15
|
+
const ENABLE_ECHO_INPUT = 0x0004;
|
|
16
|
+
const ENABLE_WINDOW_INPUT = 0x0008;
|
|
17
|
+
const ENABLE_MOUSE_INPUT = 0x0010;
|
|
18
|
+
const ENABLE_QUICK_EDIT_MODE = 0x0040;
|
|
19
|
+
const ENABLE_EXTENDED_FLAGS = 0x0080;
|
|
20
|
+
const ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200;
|
|
21
|
+
const STD_INPUT_HANDLE = -10;
|
|
22
|
+
// ── State ───────────────────────────────────────────────────────────
|
|
23
|
+
let originalMode = null;
|
|
24
|
+
let _kernel32;
|
|
25
|
+
function getKernel32() {
|
|
26
|
+
if (_kernel32 !== undefined)
|
|
27
|
+
return _kernel32;
|
|
28
|
+
try {
|
|
29
|
+
// koffi is an optional native dependency — dynamic require so the
|
|
30
|
+
// module loads cleanly even when koffi is absent.
|
|
31
|
+
const require = createRequire(import.meta.url);
|
|
32
|
+
const koffi = require("koffi");
|
|
33
|
+
const lib = koffi.load("kernel32.dll");
|
|
34
|
+
_kernel32 = {
|
|
35
|
+
GetStdHandle: lib.func("void* __stdcall GetStdHandle(int nStdHandle)"),
|
|
36
|
+
GetConsoleMode: lib.func("bool __stdcall GetConsoleMode(void* hConsoleHandle, _Out_ uint32_t* lpMode)"),
|
|
37
|
+
SetConsoleMode: lib.func("bool __stdcall SetConsoleMode(void* hConsoleHandle, uint32_t dwMode)"),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
_kernel32 = null;
|
|
42
|
+
}
|
|
43
|
+
return _kernel32;
|
|
44
|
+
}
|
|
45
|
+
// ── Public API ──────────────────────────────────────────────────────
|
|
46
|
+
/**
|
|
47
|
+
* Configure the Windows console for mouse input.
|
|
48
|
+
*
|
|
49
|
+
* Disables Quick Edit Mode (which swallows mouse clicks) and enables
|
|
50
|
+
* ENABLE_MOUSE_INPUT + ENABLE_EXTENDED_FLAGS + ENABLE_WINDOW_INPUT.
|
|
51
|
+
* Saves the original mode so it can be restored later.
|
|
52
|
+
*
|
|
53
|
+
* No-op on non-Windows platforms or if koffi is not available.
|
|
54
|
+
* Returns true if the mode was successfully changed.
|
|
55
|
+
*/
|
|
56
|
+
export function enableWin32Mouse() {
|
|
57
|
+
if (process.platform !== "win32")
|
|
58
|
+
return false;
|
|
59
|
+
const k32 = getKernel32();
|
|
60
|
+
if (!k32)
|
|
61
|
+
return false;
|
|
62
|
+
try {
|
|
63
|
+
const handle = k32.GetStdHandle(STD_INPUT_HANDLE);
|
|
64
|
+
if (!handle)
|
|
65
|
+
return false;
|
|
66
|
+
// Read current mode
|
|
67
|
+
const modeBuffer = Buffer.alloc(4);
|
|
68
|
+
if (!k32.GetConsoleMode(handle, modeBuffer))
|
|
69
|
+
return false;
|
|
70
|
+
originalMode = modeBuffer.readUInt32LE(0);
|
|
71
|
+
// Build new mode:
|
|
72
|
+
// - Keep ENABLE_VIRTUAL_TERMINAL_INPUT (set by Node raw mode)
|
|
73
|
+
// - Add ENABLE_MOUSE_INPUT + ENABLE_WINDOW_INPUT + ENABLE_EXTENDED_FLAGS
|
|
74
|
+
// - Remove ENABLE_QUICK_EDIT_MODE
|
|
75
|
+
// - Remove line/echo/processed (already cleared by raw mode)
|
|
76
|
+
let newMode = originalMode;
|
|
77
|
+
newMode |= ENABLE_MOUSE_INPUT;
|
|
78
|
+
newMode |= ENABLE_WINDOW_INPUT;
|
|
79
|
+
newMode |= ENABLE_EXTENDED_FLAGS;
|
|
80
|
+
newMode &= ~ENABLE_QUICK_EDIT_MODE;
|
|
81
|
+
newMode &= ~ENABLE_LINE_INPUT;
|
|
82
|
+
newMode &= ~ENABLE_ECHO_INPUT;
|
|
83
|
+
newMode &= ~ENABLE_PROCESSED_INPUT;
|
|
84
|
+
// Preserve VT input if it was set
|
|
85
|
+
if (originalMode & ENABLE_VIRTUAL_TERMINAL_INPUT) {
|
|
86
|
+
newMode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
|
|
87
|
+
}
|
|
88
|
+
return k32.SetConsoleMode(handle, newMode);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Restore the original Windows console mode saved by enableWin32Mouse().
|
|
96
|
+
*
|
|
97
|
+
* No-op if enableWin32Mouse() was never called or failed.
|
|
98
|
+
* Returns true if the mode was successfully restored.
|
|
99
|
+
*/
|
|
100
|
+
export function restoreWin32Console() {
|
|
101
|
+
if (process.platform !== "win32" || originalMode === null)
|
|
102
|
+
return false;
|
|
103
|
+
const k32 = getKernel32();
|
|
104
|
+
if (!k32) {
|
|
105
|
+
originalMode = null;
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const handle = k32.GetStdHandle(STD_INPUT_HANDLE);
|
|
110
|
+
if (!handle) {
|
|
111
|
+
originalMode = null;
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
const result = k32.SetConsoleMode(handle, originalMode);
|
|
115
|
+
originalMode = null;
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
originalMode = null;
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
package/dist/app.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import * as esc from "./ansi/esc.js";
|
|
9
9
|
import { AnsiOutput } from "./ansi/output.js";
|
|
10
|
-
import { detectTerminal
|
|
10
|
+
import { detectTerminal } from "./ansi/terminal-env.js";
|
|
11
|
+
import { enableWin32Mouse, restoreWin32Console } from "./ansi/win32-console.js";
|
|
11
12
|
import { DrawingContext } from "./drawing/context.js";
|
|
12
13
|
import { createInputProcessor } from "./input/processor.js";
|
|
13
14
|
import { disableRawMode, enableRawMode } from "./input/raw-mode.js";
|
|
@@ -97,25 +98,30 @@ export class App {
|
|
|
97
98
|
const stdout = process.stdout;
|
|
98
99
|
// 1. Enable raw mode
|
|
99
100
|
enableRawMode();
|
|
100
|
-
// 2.
|
|
101
|
+
// 2. On Windows, configure console mode for mouse input
|
|
102
|
+
// (must happen after raw mode so we modify the right base flags)
|
|
103
|
+
if (this._mouse) {
|
|
104
|
+
enableWin32Mouse();
|
|
105
|
+
}
|
|
106
|
+
// 3. Create ANSI output
|
|
101
107
|
this._output = new AnsiOutput(stdout);
|
|
102
|
-
//
|
|
108
|
+
// 4. Prepare terminal (custom sequence instead of prepareTerminal()
|
|
103
109
|
// so we can conditionally enable mouse tracking)
|
|
104
110
|
this._prepareTerminal();
|
|
105
|
-
//
|
|
111
|
+
// 5. Set terminal title
|
|
106
112
|
if (this._title) {
|
|
107
113
|
stdout.write(esc.setTitle(this._title));
|
|
108
114
|
}
|
|
109
|
-
//
|
|
115
|
+
// 6. Create pixel buffer at terminal dimensions
|
|
110
116
|
const cols = stdout.columns || 80;
|
|
111
117
|
const rows = stdout.rows || 24;
|
|
112
118
|
this._createRenderPipeline(cols, rows);
|
|
113
|
-
//
|
|
119
|
+
// 7. Wire up input
|
|
114
120
|
this._setupInput();
|
|
115
|
-
//
|
|
121
|
+
// 8. Wire up resize
|
|
116
122
|
this._resizeListener = () => this._handleResize();
|
|
117
123
|
stdout.on("resize", this._resizeListener);
|
|
118
|
-
//
|
|
124
|
+
// 9. SIGINT fallback
|
|
119
125
|
this._sigintListener = () => this.stop();
|
|
120
126
|
process.on("SIGINT", this._sigintListener);
|
|
121
127
|
}
|
|
@@ -294,6 +300,8 @@ export class App {
|
|
|
294
300
|
}
|
|
295
301
|
// Restore terminal
|
|
296
302
|
this._restoreTerminal();
|
|
303
|
+
// Restore Win32 console mode (before disabling raw mode)
|
|
304
|
+
restoreWin32Console();
|
|
297
305
|
// Disable raw mode
|
|
298
306
|
disableRawMode();
|
|
299
307
|
// Resolve the run() promise
|
package/dist/index.d.ts
CHANGED
|
@@ -16,7 +16,8 @@ export type { Constraint, Point, Rect, Size, } from "./layout/types.js";
|
|
|
16
16
|
export * as esc from "./ansi/esc.js";
|
|
17
17
|
export { AnsiOutput } from "./ansi/output.js";
|
|
18
18
|
export { stripAnsi, truncateAnsi, visibleLength, } from "./ansi/strip.js";
|
|
19
|
-
export { type TerminalCaps,
|
|
19
|
+
export { detectTerminal, type TerminalCaps, } from "./ansi/terminal-env.js";
|
|
20
|
+
export { enableWin32Mouse, restoreWin32Console, } from "./ansi/win32-console.js";
|
|
20
21
|
export { DirtyRegions, DirtySnapshot } from "./render/regions.js";
|
|
21
22
|
export { RenderTarget } from "./render/render-target.js";
|
|
22
23
|
export { EscapeMatcher } from "./input/escape-matcher.js";
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ export * as esc from "./ansi/esc.js";
|
|
|
24
24
|
export { AnsiOutput } from "./ansi/output.js";
|
|
25
25
|
export { stripAnsi, truncateAnsi, visibleLength, } from "./ansi/strip.js";
|
|
26
26
|
export { detectTerminal, } from "./ansi/terminal-env.js";
|
|
27
|
+
export { enableWin32Mouse, restoreWin32Console, } from "./ansi/win32-console.js";
|
|
27
28
|
// ── Render pipeline ─────────────────────────────────────────────────
|
|
28
29
|
export { DirtyRegions, DirtySnapshot } from "./render/regions.js";
|
|
29
30
|
export { RenderTarget } from "./render/render-target.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teammates/consolonia",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
4
4
|
"description": "Terminal UI rendering engine inspired by Consolonia. Pixel-level compositing with ANSI output.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -41,5 +41,8 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"marked": "^17.0.4"
|
|
44
|
+
},
|
|
45
|
+
"optionalDependencies": {
|
|
46
|
+
"koffi": "^2.9.0"
|
|
44
47
|
}
|
|
45
48
|
}
|