@@ -28,7 +28,8 @@ class GetPassWarning(UserWarning): pass
2828
2929# Default POSIX control character mappings
3030_POSIX_CTRL_CHARS = frozendict ({
31- 'ERASE' : '\x7f ' , # DEL/Backspace
31+ 'BS' : '\x08 ' , # Backspace
32+ 'ERASE' : '\x7f ' , # DEL
3233 'KILL' : '\x15 ' , # Ctrl+U - kill line
3334 'WERASE' : '\x17 ' , # Ctrl+W - erase word
3435 'LNEXT' : '\x16 ' , # Ctrl+V - literal next
@@ -251,13 +252,18 @@ def __init__(self, stream, echo_char, ctrl_chars, prompt=""):
251252 self .literal_next = False
252253 self .ctrl = ctrl_chars
253254 self .dispatch = {
254- ctrl_chars ['SOH' ]: self .handle_move_start , # Ctrl+A
255- ctrl_chars ['ENQ' ]: self .handle_move_end , # Ctrl+E
256- ctrl_chars ['VT' ]: self .handle_kill_forward , # Ctrl+K
257- ctrl_chars ['KILL' ]: self .handle_kill_line , # Ctrl+U
258- ctrl_chars ['WERASE' ]: self .handle_erase_word , # Ctrl+W
259- ctrl_chars ['ERASE' ]: self .handle_erase , # DEL
260- '\b ' : self .handle_erase , # Backspace
255+ ctrl_chars ['SOH' ]: self .handle_move_start , # Ctrl+A
256+ ctrl_chars ['ENQ' ]: self .handle_move_end , # Ctrl+E
257+ ctrl_chars ['VT' ]: self .handle_kill_forward , # Ctrl+K
258+ ctrl_chars ['KILL' ]: self .handle_kill_line , # Ctrl+U
259+ ctrl_chars ['WERASE' ]: self .handle_erase_word , # Ctrl+W
260+ ctrl_chars ['ERASE' ]: self .handle_erase , # DEL
261+ ctrl_chars ['BS' ]: self .handle_erase , # Backspace
262+ # special characters
263+ ctrl_chars ['LNEXT' ]: self .handle_literal_next , # Ctrl+V
264+ ctrl_chars ['EOF' ]: self .handle_eof , # Ctrl+D
265+ ctrl_chars ['INTR' ]: self .handle_interrupt , # Ctrl+C
266+ '\x00 ' : self .handle_nop , # ignore NUL
261267 }
262268
263269 def refresh_display (self , prev_len = None ):
@@ -285,6 +291,14 @@ def insert_char(self, char):
285291 self .stream .write (self .echo_char )
286292 self .stream .flush ()
287293
294+ def is_eol (self , char ):
295+ """Check if *char* is a line terminator."""
296+ return char in ('\r ' , '\n ' )
297+
298+ def is_eof (self , char ):
299+ """Check if *char* is a file terminator."""
300+ return char == self .ctrl ['EOF' ]
301+
288302 def handle_move_start (self ):
289303 """Move cursor to beginning (Ctrl+A)."""
290304 self .cursor_pos = 0
@@ -332,6 +346,23 @@ def handle_erase_word(self):
332346 del self .password [self .cursor_pos :old_cursor ]
333347 self .refresh_display (prev_len )
334348
349+ def handle_literal_next (self ):
350+ """State transition to indicate that the next character is literal."""
351+ assert self .literal_next is False
352+ self .literal_next = True
353+
354+ def handle_eof (self ):
355+ """State transition to indicate that the pressed character was EOF."""
356+ assert self .eof_pressed is False
357+ self .eof_pressed = True
358+
359+ def handle_interrupt (self ):
360+ """Raise a KeyboardInterrupt after Ctrl+C has been received."""
361+ raise KeyboardInterrupt
362+
363+ def handle_nop (self ):
364+ """Handler for an ignored character."""
365+
335366 def handle (self , char ):
336367 """Handle a single character input. Returns True if handled."""
337368 handler = self .dispatch .get (char )
@@ -345,33 +376,25 @@ def readline(self, input):
345376 while True :
346377 assert self .cursor_pos >= 0
347378 char = input .read (1 )
348-
349- # Check for line terminators
350- if char in ('\n ' , '\r ' ):
379+ if self .is_eol (char ):
351380 break
381+ # Handle literal next mode first as Ctrl+V quotes characters.
352382 elif self .literal_next :
353- # Handle literal next mode first as Ctrl+V quotes characters.
354383 self .insert_char (char )
355384 self .literal_next = False
356- # Check if it's the LNEXT character
357- elif char == self .ctrl ['LNEXT' ]:
358- self .literal_next = True
359- # Check for special control characters
360- elif char == self .ctrl ['INTR' ]:
361- raise KeyboardInterrupt
362- elif char == self .ctrl ['EOF' ]:
385+ # Handle EOF now as Ctrl+D must be pressed twice
386+ # consecutively to stop reading from the input.
387+ elif self .is_eof (char ):
363388 if self .eof_pressed :
364389 break
365- elif char == '\x00 ' :
366- pass
367390 elif self .handle (char ):
368391 # Dispatched to handler.
369392 pass
370393 else :
371394 # Insert as normal character.
372395 self .insert_char (char )
373396
374- self .eof_pressed = ( char == self .ctrl [ 'EOF' ] )
397+ self .eof_pressed = self .is_eof ( char )
375398
376399 return '' .join (self .password )
377400
0 commit comments