99import tty
1010from codecs import getincrementaldecoder
1111from threading import Event , Thread
12- from typing import TYPE_CHECKING , Any
13-
14- if TYPE_CHECKING :
15- from rich .console import Console
12+ from typing import IO , Any
1613
1714import rich .repr
1815
@@ -29,13 +26,22 @@ class LinuxDriver(Driver):
2926
3027 def __init__ (
3128 self ,
32- console : "Console" ,
29+ file : IO [ str ] ,
3330 target : "MessageTarget" ,
3431 * ,
3532 debug : bool = False ,
3633 size : tuple [int , int ] | None = None ,
3734 ) -> None :
38- super ().__init__ (console , target , debug = debug , size = size )
35+ """Initialize a driver.
36+
37+ Args:
38+ file: A file-like object open for writing unicode.
39+ target: The message target (expected to be the app).
40+ debug: Enabled debug mode.
41+ size: Initial size of the terminal or `None` to detect.
42+ """
43+ super ().__init__ (file , target , debug = debug , size = size )
44+ self ._file = file
3945 self .fileno = sys .stdin .fileno ()
4046 self .attrs_before : list [Any ] | None = None
4147 self .exit_event = Event ()
@@ -45,6 +51,11 @@ def __rich_repr__(self) -> rich.repr.Result:
4551 yield "debug" , self ._debug
4652
4753 def _get_terminal_size (self ) -> tuple [int , int ]:
54+ """Detect the terminal size.
55+
56+ Returns:
57+ The size of the terminal as a tuple of (WIDTH, HEIGHT).
58+ """
4859 width : int | None = 80
4960 height : int | None = 25
5061 import shutil
@@ -61,35 +72,46 @@ def _get_terminal_size(self) -> tuple[int, int]:
6172 return width , height
6273
6374 def _enable_mouse_support (self ) -> None :
64- write = self .console .file .write
75+ """Enable reporting of mouse events."""
76+ write = self .write
6577 write ("\x1b [?1000h" ) # SET_VT200_MOUSE
6678 write ("\x1b [?1003h" ) # SET_ANY_EVENT_MOUSE
6779 write ("\x1b [?1015h" ) # SET_VT200_HIGHLIGHT_MOUSE
6880 write ("\x1b [?1006h" ) # SET_SGR_EXT_MODE_MOUSE
6981
7082 # write("\x1b[?1007h")
71- self .console . file . flush ()
83+ self .flush ()
7284
7385 # Note: E.g. lxterminal understands 1000h, but not the urxvt or sgr
7486 # extensions.
7587
7688 def _enable_bracketed_paste (self ) -> None :
7789 """Enable bracketed paste mode."""
78- self .console . file . write ("\x1b [?2004h" )
90+ self .write ("\x1b [?2004h" )
7991
8092 def _disable_bracketed_paste (self ) -> None :
8193 """Disable bracketed paste mode."""
82- self .console . file . write ("\x1b [?2004l" )
94+ self .write ("\x1b [?2004l" )
8395
8496 def _disable_mouse_support (self ) -> None :
85- write = self .console .file .write
97+ """Disable reporting of mouse events."""
98+ write = self .write
8699 write ("\x1b [?1000l" ) #
87100 write ("\x1b [?1003l" ) #
88101 write ("\x1b [?1015l" )
89102 write ("\x1b [?1006l" )
90- self .console .file .flush ()
103+ self .flush ()
104+
105+ def write (self , data : str ) -> None :
106+ """Write data to the output device.
107+
108+ Args:
109+ data: Raw data.
110+ """
111+ self ._file .write (data )
91112
92113 def start_application_mode (self ):
114+ """Start application mode."""
93115 loop = asyncio .get_running_loop ()
94116
95117 def send_size_event ():
@@ -107,7 +129,8 @@ def on_terminal_resize(signum, stack) -> None:
107129
108130 signal .signal (signal .SIGWINCH , on_terminal_resize )
109131
110- self .console .set_alt_screen (True )
132+ self .write ("\x1b [?1049h" ) # Alt screen
133+
111134 self ._enable_mouse_support ()
112135 try :
113136 self .attrs_before = termios .tcgetattr (self .fileno )
@@ -132,10 +155,10 @@ def on_terminal_resize(signum, stack) -> None:
132155
133156 termios .tcsetattr (self .fileno , termios .TCSANOW , newattr )
134157
135- self .console . show_cursor ( False )
136- self .console . file . write ("\033 [?1003h\n " )
137- self .console . file . flush ()
138- self ._key_thread = Thread (target = self .run_input_thread , args = ( loop ,) )
158+ self .write ( " \x1b [?25l" ) # Hide cursor
159+ self .write ("\033 [?1003h\n " )
160+ self .flush ()
161+ self ._key_thread = Thread (target = self .run_input_thread )
139162 send_size_event ()
140163 self ._key_thread .start ()
141164 self ._request_terminal_sync_mode_support ()
@@ -146,9 +169,9 @@ def _request_terminal_sync_mode_support(self) -> None:
146169 # Terminals should ignore this sequence if not supported.
147170 # Apple terminal doesn't, and writes a single 'p' in to the terminal,
148171 # so we will make a special case for Apple terminal (which doesn't support sync anyway).
149- if self . console . _environ .get ("TERM_PROGRAM" , "" ) != "Apple_Terminal" :
150- self .console . file . write ("\033 [?2026$p" )
151- self .console . file . flush ()
172+ if os . environ .get ("TERM_PROGRAM" , "" ) != "Apple_Terminal" :
173+ self .write ("\033 [?2026$p" )
174+ self .flush ()
152175
153176 @classmethod
154177 def _patch_lflag (cls , attrs : int ) -> int :
@@ -170,6 +193,7 @@ def _patch_iflag(cls, attrs: int) -> int:
170193 )
171194
172195 def disable_input (self ) -> None :
196+ """Disable further input."""
173197 try :
174198 if not self .exit_event .is_set ():
175199 signal .signal (signal .SIGWINCH , signal .SIG_DFL )
@@ -184,6 +208,7 @@ def disable_input(self) -> None:
184208 pass
185209
186210 def stop_application_mode (self ) -> None :
211+ """Stop application mode, restore state."""
187212 self ._disable_bracketed_paste ()
188213 self .disable_input ()
189214
@@ -193,17 +218,12 @@ def stop_application_mode(self) -> None:
193218 except termios .error :
194219 pass
195220
196- with self .console :
197- self .console .set_alt_screen (False )
198- self .console .show_cursor (True )
199-
200- def run_input_thread (self , loop ) -> None :
201- try :
202- self ._run_input_thread (loop )
203- except Exception :
204- pass # TODO: log
221+ # Alt screen false, show cursor
222+ self .write ("\x1b [?1049l" + "\x1b [?25h" )
223+ self .flush ()
205224
206- def _run_input_thread (self , loop ) -> None :
225+ def run_input_thread (self ) -> None :
226+ """Wait for input and dispatch events."""
207227 selector = selectors .DefaultSelector ()
208228 selector .register (self .fileno , selectors .EVENT_READ )
209229
0 commit comments