4242from .console import Event , Console
4343from .trace import trace
4444from .utils import wlen
45+ from .windows_eventqueue import EventQueue
4546
4647try :
4748 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:
9495 0x83 : "f20" , # VK_F20
9596}
9697
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
98101ERASE_IN_LINE = "\x1b [K"
99102MOVE_LEFT = "\x1b [{}D"
100103MOVE_RIGHT = "\x1b [{}C"
@@ -106,6 +109,12 @@ def __init__(self, err: int | None, descr: str | None = None) -> None:
106109class _error (Exception ):
107110 pass
108111
112+ def _supports_vt ():
113+ try :
114+ import nt
115+ return nt ._supports_virtual_terminal ()
116+ except :
117+ return False
109118
110119class WindowsConsole (Console ):
111120 def __init__ (
@@ -117,17 +126,36 @@ def __init__(
117126 ):
118127 super ().__init__ (f_in , f_out , term , encoding )
119128
129+ self .__vt_support = _supports_vt ()
130+ self .__vt_bracketed_paste = False
131+
132+ if self .__vt_support :
133+ trace ('console supports virtual terminal' )
134+
135+ # Should make educated guess to determine the terminal type.
136+ # Currently enable bracketed-paste only if it's Windows Terminal.
137+ if 'WT_SESSION' in os .environ :
138+ trace ('console supports bracketed-paste sequence' )
139+ self .__vt_bracketed_paste = True
140+
141+ # Save original console modes so we can recover on cleanup.
142+ original_input_mode = DWORD ()
143+ GetConsoleMode (InHandle , original_input_mode )
144+ trace (f'saved original input mode 0x{ original_input_mode .value :x} ' )
145+ self .__original_input_mode = original_input_mode .value
146+
120147 SetConsoleMode (
121148 OutHandle ,
122149 ENABLE_WRAP_AT_EOL_OUTPUT
123150 | ENABLE_PROCESSED_OUTPUT
124151 | ENABLE_VIRTUAL_TERMINAL_PROCESSING ,
125152 )
153+
126154 self .screen : list [str ] = []
127155 self .width = 80
128156 self .height = 25
129157 self .__offset = 0
130- self .event_queue : deque [ Event ] = deque ( )
158+ self .event_queue = EventQueue ( encoding )
131159 try :
132160 self .out = io ._WindowsConsoleIO (self .output_fd , "w" ) # type: ignore[attr-defined]
133161 except ValueError :
@@ -291,6 +319,12 @@ def _enable_blinking(self):
291319 def _disable_blinking (self ):
292320 self .__write ("\x1b [?12l" )
293321
322+ def _enable_bracketed_paste (self ) -> None :
323+ self .__write ("\x1b [?2004h" )
324+
325+ def _disable_bracketed_paste (self ) -> None :
326+ self .__write ("\x1b [?2004l" )
327+
294328 def __write (self , text : str ) -> None :
295329 if "\x1a " in text :
296330 text = '' .join (["^Z" if x == '\x1a ' else x for x in text ])
@@ -320,8 +354,17 @@ def prepare(self) -> None:
320354 self .__gone_tall = 0
321355 self .__offset = 0
322356
357+ if self .__vt_support :
358+ SetConsoleMode (InHandle , self .__original_input_mode | ENABLE_VIRTUAL_TERMINAL_INPUT )
359+ if self .__vt_bracketed_paste :
360+ self ._enable_bracketed_paste ()
361+
323362 def restore (self ) -> None :
324- pass
363+ if self .__vt_support :
364+ # Recover to original mode before running REPL
365+ SetConsoleMode (InHandle , self .__original_input_mode )
366+ if self .__vt_bracketed_paste :
367+ self ._disable_bracketed_paste ()
325368
326369 def _move_relative (self , x : int , y : int ) -> None :
327370 """Moves relative to the current __posxy"""
@@ -342,7 +385,7 @@ def move_cursor(self, x: int, y: int) -> None:
342385 raise ValueError (f"Bad cursor position { x } , { y } " )
343386
344387 if y < self .__offset or y >= self .__offset + self .height :
345- self .event_queue .insert (0 , Event ("scroll" , "" ))
388+ self .event_queue .insert (Event ("scroll" , "" ))
346389 else :
347390 self ._move_relative (x , y )
348391 self .__posxy = x , y
@@ -386,10 +429,8 @@ def get_event(self, block: bool = True) -> Event | None:
386429 """Return an Event instance. Returns None if |block| is false
387430 and there is no event pending, otherwise waits for the
388431 completion of an event."""
389- if self .event_queue :
390- return self .event_queue .pop ()
391432
392- while True :
433+ while self . event_queue . empty () :
393434 rec = self ._read_input ()
394435 if rec is None :
395436 if block :
@@ -428,8 +469,13 @@ def get_event(self, block: bool = True) -> Event | None:
428469 continue
429470
430471 return None
472+ elif self .__vt_support :
473+ # If virtual terminal is enabled, scanning VT sequences
474+ self .event_queue .push (rec .Event .KeyEvent .uChar .UnicodeChar )
475+ continue
431476
432477 return Event (evt = "key" , data = key , raw = rec .Event .KeyEvent .uChar .UnicodeChar )
478+ return self .event_queue .get ()
433479
434480 def push_char (self , char : int | bytes ) -> None :
435481 """
@@ -551,6 +597,13 @@ class INPUT_RECORD(Structure):
551597MOUSE_EVENT = 0x02
552598WINDOW_BUFFER_SIZE_EVENT = 0x04
553599
600+ ENABLE_PROCESSED_INPUT = 0x0001
601+ ENABLE_LINE_INPUT = 0x0002
602+ ENABLE_ECHO_INPUT = 0x0004
603+ ENABLE_MOUSE_INPUT = 0x0010
604+ ENABLE_INSERT_MODE = 0x0020
605+ ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200
606+
554607ENABLE_PROCESSED_OUTPUT = 0x01
555608ENABLE_WRAP_AT_EOL_OUTPUT = 0x02
556609ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x04
@@ -582,6 +635,10 @@ class INPUT_RECORD(Structure):
582635 ]
583636 ScrollConsoleScreenBuffer .restype = BOOL
584637
638+ GetConsoleMode = _KERNEL32 .GetConsoleMode
639+ GetConsoleMode .argtypes = [HANDLE , POINTER (DWORD )]
640+ GetConsoleMode .restype = BOOL
641+
585642 SetConsoleMode = _KERNEL32 .SetConsoleMode
586643 SetConsoleMode .argtypes = [HANDLE , DWORD ]
587644 SetConsoleMode .restype = BOOL
@@ -600,6 +657,7 @@ def _win_only(*args, **kwargs):
600657 GetStdHandle = _win_only
601658 GetConsoleScreenBufferInfo = _win_only
602659 ScrollConsoleScreenBuffer = _win_only
660+ GetConsoleMode = _win_only
603661 SetConsoleMode = _win_only
604662 ReadConsoleInput = _win_only
605663 OutHandle = 0
0 commit comments