42
42
from .console import Event , Console
43
43
from .trace import trace
44
44
from .utils import wlen
45
+ from .windows_eventqueue import EventQueue
45
46
46
47
try :
47
48
from ctypes import GetLastError , WinDLL , windll , WinError # type: ignore[attr-defined]
@@ -94,7 +95,9 @@ def __init__(self, err: int | None, descr: str | None = None) -> None:
94
95
0x83 : "f20" , # VK_F20
95
96
}
96
97
97
- # Console escape codes: https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
98
+ # Virtual terminal output sequences
99
+ # Reference: https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#output-sequences
100
+ # Check `windows_eventqueue.py` for input sequences
98
101
ERASE_IN_LINE = "\x1b [K"
99
102
MOVE_LEFT = "\x1b [{}D"
100
103
MOVE_RIGHT = "\x1b [{}C"
@@ -117,17 +120,37 @@ def __init__(
117
120
):
118
121
super ().__init__ (f_in , f_out , term , encoding )
119
122
123
+ import nt
124
+ self .__vt_support = nt ._supports_virtual_terminal ()
125
+ self .__vt_bracketed_paste = False
126
+
127
+ if self .__vt_support :
128
+ trace ('console supports virtual terminal' )
129
+
130
+ # Should make educated guess to determine the terminal type.
131
+ # Currently enable bracketed-paste only if it's Windows Terminal.
132
+ if 'WT_SESSION' in os .environ :
133
+ trace ('console supports bracketed-paste sequence' )
134
+ self .__vt_bracketed_paste = True
135
+
136
+ # Save original console modes so we can recover on cleanup.
137
+ original_input_mode = DWORD ()
138
+ GetConsoleMode (InHandle , original_input_mode )
139
+ trace (f'saved original input mode 0x{ original_input_mode .value :x} ' )
140
+ self .__original_input_mode = original_input_mode .value
141
+
120
142
SetConsoleMode (
121
143
OutHandle ,
122
144
ENABLE_WRAP_AT_EOL_OUTPUT
123
145
| ENABLE_PROCESSED_OUTPUT
124
146
| ENABLE_VIRTUAL_TERMINAL_PROCESSING ,
125
147
)
148
+
126
149
self .screen : list [str ] = []
127
150
self .width = 80
128
151
self .height = 25
129
152
self .__offset = 0
130
- self .event_queue : deque [ Event ] = deque ( )
153
+ self .event_queue = EventQueue ( encoding )
131
154
try :
132
155
self .out = io ._WindowsConsoleIO (self .output_fd , "w" ) # type: ignore[attr-defined]
133
156
except ValueError :
@@ -291,6 +314,12 @@ def _enable_blinking(self):
291
314
def _disable_blinking (self ):
292
315
self .__write ("\x1b [?12l" )
293
316
317
+ def _enable_bracketed_paste (self ) -> None :
318
+ self .__write ("\x1b [?2004h" )
319
+
320
+ def _disable_bracketed_paste (self ) -> None :
321
+ self .__write ("\x1b [?2004l" )
322
+
294
323
def __write (self , text : str ) -> None :
295
324
if "\x1a " in text :
296
325
text = '' .join (["^Z" if x == '\x1a ' else x for x in text ])
@@ -320,8 +349,17 @@ def prepare(self) -> None:
320
349
self .__gone_tall = 0
321
350
self .__offset = 0
322
351
352
+ if self .__vt_support :
353
+ SetConsoleMode (InHandle , self .__original_input_mode | ENABLE_VIRTUAL_TERMINAL_INPUT )
354
+ if self .__vt_bracketed_paste :
355
+ self ._enable_bracketed_paste ()
356
+
323
357
def restore (self ) -> None :
324
- pass
358
+ if self .__vt_support :
359
+ # Recover to original mode before running REPL
360
+ SetConsoleMode (InHandle , self .__original_input_mode )
361
+ if self .__vt_bracketed_paste :
362
+ self ._disable_bracketed_paste ()
325
363
326
364
def _move_relative (self , x : int , y : int ) -> None :
327
365
"""Moves relative to the current __posxy"""
@@ -342,7 +380,7 @@ def move_cursor(self, x: int, y: int) -> None:
342
380
raise ValueError (f"Bad cursor position { x } , { y } " )
343
381
344
382
if y < self .__offset or y >= self .__offset + self .height :
345
- self .event_queue .insert (0 , Event ("scroll" , "" ))
383
+ self .event_queue .insert (Event ("scroll" , "" ))
346
384
else :
347
385
self ._move_relative (x , y )
348
386
self .__posxy = x , y
@@ -386,10 +424,8 @@ def get_event(self, block: bool = True) -> Event | None:
386
424
"""Return an Event instance. Returns None if |block| is false
387
425
and there is no event pending, otherwise waits for the
388
426
completion of an event."""
389
- if self .event_queue :
390
- return self .event_queue .pop ()
391
427
392
- while True :
428
+ while self . event_queue . empty () :
393
429
rec = self ._read_input ()
394
430
if rec is None :
395
431
if block :
@@ -428,8 +464,13 @@ def get_event(self, block: bool = True) -> Event | None:
428
464
continue
429
465
430
466
return None
467
+ elif self .__vt_support :
468
+ # If virtual terminal is enabled, scanning VT sequences
469
+ self .event_queue .push (rec .Event .KeyEvent .uChar .UnicodeChar )
470
+ continue
431
471
432
472
return Event (evt = "key" , data = key , raw = rec .Event .KeyEvent .uChar .UnicodeChar )
473
+ return self .event_queue .get ()
433
474
434
475
def push_char (self , char : int | bytes ) -> None :
435
476
"""
@@ -551,6 +592,13 @@ class INPUT_RECORD(Structure):
551
592
MOUSE_EVENT = 0x02
552
593
WINDOW_BUFFER_SIZE_EVENT = 0x04
553
594
595
+ ENABLE_PROCESSED_INPUT = 0x0001
596
+ ENABLE_LINE_INPUT = 0x0002
597
+ ENABLE_ECHO_INPUT = 0x0004
598
+ ENABLE_MOUSE_INPUT = 0x0010
599
+ ENABLE_INSERT_MODE = 0x0020
600
+ ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200
601
+
554
602
ENABLE_PROCESSED_OUTPUT = 0x01
555
603
ENABLE_WRAP_AT_EOL_OUTPUT = 0x02
556
604
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x04
@@ -582,6 +630,10 @@ class INPUT_RECORD(Structure):
582
630
]
583
631
ScrollConsoleScreenBuffer .restype = BOOL
584
632
633
+ GetConsoleMode = _KERNEL32 .GetConsoleMode
634
+ GetConsoleMode .argtypes = [HANDLE , POINTER (DWORD )]
635
+ GetConsoleMode .restype = BOOL
636
+
585
637
SetConsoleMode = _KERNEL32 .SetConsoleMode
586
638
SetConsoleMode .argtypes = [HANDLE , DWORD ]
587
639
SetConsoleMode .restype = BOOL
@@ -600,6 +652,7 @@ def _win_only(*args, **kwargs):
600
652
GetStdHandle = _win_only
601
653
GetConsoleScreenBufferInfo = _win_only
602
654
ScrollConsoleScreenBuffer = _win_only
655
+ GetConsoleMode = _win_only
603
656
SetConsoleMode = _win_only
604
657
ReadConsoleInput = _win_only
605
658
OutHandle = 0
0 commit comments