@@ -416,6 +416,10 @@ def allow_ansi(self, new_val: str) -> None:
416416 self .perror ('Invalid value: {} (valid values: {}, {}, {})' .format (new_val , ansi .ANSI_TERMINAL ,
417417 ansi .ANSI_ALWAYS , ansi .ANSI_NEVER ))
418418
419+ def _completion_supported (self ) -> bool :
420+ """Return whether tab completion is supported"""
421+ return self .use_rawinput and self .completekey and rl_type != RlType .NONE
422+
419423 @property
420424 def visible_prompt (self ) -> str :
421425 """Read-only property to get the visible prompt with any ANSI escape codes stripped.
@@ -1322,7 +1326,7 @@ def complete(self, text: str, state: int) -> Optional[str]:
13221326 """
13231327 # noinspection PyBroadException
13241328 try :
1325- if state == 0 and rl_type != RlType . NONE :
1329+ if state == 0 :
13261330 self ._reset_completion_defaults ()
13271331
13281332 # Check if we are completing a multiline command
@@ -1649,7 +1653,7 @@ def _complete_statement(self, line: str) -> Statement:
16491653 """Keep accepting lines of input until the command is complete.
16501654
16511655 There is some pretty hacky code here to handle some quirks of
1652- self._pseudo_raw_input (). It returns a literal 'eof' if the input
1656+ self._read_command_line (). It returns a literal 'eof' if the input
16531657 pipe runs out. We can't refactor it because we need to retain
16541658 backwards compatibility with the standard library version of cmd.
16551659
@@ -1683,7 +1687,7 @@ def _complete_statement(self, line: str) -> Statement:
16831687 # Save the command line up to this point for tab completion
16841688 self ._multiline_in_progress = line + '\n '
16851689
1686- nextline = self ._pseudo_raw_input (self .continuation_prompt )
1690+ nextline = self ._read_command_line (self .continuation_prompt )
16871691 if nextline == 'eof' :
16881692 # they entered either a blank line, or we hit an EOF
16891693 # for some other reason. Turn the literal 'eof'
@@ -1989,36 +1993,59 @@ def default(self, statement: Statement) -> Optional[bool]:
19891993 # Set apply_style to False so default_error's style is not overridden
19901994 self .perror (err_msg , apply_style = False )
19911995
1992- def _pseudo_raw_input (self , prompt : str ) -> str :
1993- """Began life as a copy of cmd's cmdloop; like raw_input but
1994-
1995- - accounts for changed stdin, stdout
1996- - if input is a pipe (instead of a tty), look at self.echo
1997- to decide whether to print the prompt and the input
1996+ def read_input (self , prompt : str , * , allow_completion : bool = False ) -> str :
19981997 """
1999- if self .use_rawinput :
2000- try :
2001- if sys .stdin .isatty ():
2002- # Wrap in try since terminal_lock may not be locked when this function is called from unit tests
2003- try :
2004- # A prompt is about to be drawn. Allow asynchronous changes to the terminal.
2005- self .terminal_lock .release ()
2006- except RuntimeError :
2007- pass
1998+ Read input from appropriate stdin value. Also allows you to disable tab completion while input is being read.
1999+ :param prompt: prompt to display to user
2000+ :param allow_completion: if True, then tab completion of commands is enabled. This generally should be
2001+ set to False unless reading the command line. Defaults to False.
2002+ :return: the line read from stdin with all trailing new lines removed
2003+ :raises any exceptions raised by input() and stdin.readline()
2004+ """
2005+ completion_disabled = False
2006+ orig_completer = None
2007+
2008+ def disable_completion ():
2009+ """Turn off completion while entering input"""
2010+ nonlocal orig_completer
2011+ nonlocal completion_disabled
2012+
2013+ if self ._completion_supported () and not completion_disabled :
2014+ orig_completer = readline .get_completer ()
2015+ readline .set_completer (lambda * args , ** kwargs : None )
2016+ completion_disabled = True
2017+
2018+ def enable_completion ():
2019+ """Restore tab completion when finished entering input"""
2020+ nonlocal completion_disabled
2021+
2022+ if self ._completion_supported () and completion_disabled :
2023+ readline .set_completer (orig_completer )
2024+ completion_disabled = False
20082025
2026+ # Check we are reading from sys.stdin
2027+ if self .use_rawinput :
2028+ if sys .stdin .isatty ():
2029+ try :
20092030 # Deal with the vagaries of readline and ANSI escape codes
20102031 safe_prompt = rl_make_safe_prompt (prompt )
2032+
2033+ with self .sigint_protection :
2034+ # Check if tab completion should be disabled
2035+ if not allow_completion :
2036+ disable_completion ()
20112037 line = input (safe_prompt )
2012- else :
2013- line = input ()
2014- if self .echo :
2015- sys .stdout .write ('{}{}\n ' .format (prompt , line ))
2016- except EOFError :
2017- line = 'eof'
2018- finally :
2019- if sys .stdin .isatty ():
2020- # The prompt is gone. Do not allow asynchronous changes to the terminal.
2021- self .terminal_lock .acquire ()
2038+ finally :
2039+ with self .sigint_protection :
2040+ # Check if we need to re-enable tab completion
2041+ if not allow_completion :
2042+ enable_completion ()
2043+ else :
2044+ line = input ()
2045+ if self .echo :
2046+ sys .stdout .write ('{}{}\n ' .format (prompt , line ))
2047+
2048+ # Otherwise read from self.stdin
20222049 else :
20232050 if self .stdin .isatty ():
20242051 # on a tty, print the prompt first, then read the line
@@ -2041,14 +2068,36 @@ def _pseudo_raw_input(self, prompt: str) -> str:
20412068
20422069 return line .rstrip ('\r \n ' )
20432070
2071+ def _read_command_line (self , prompt : str ) -> str :
2072+ """
2073+ Read command line from appropriate stdin
2074+
2075+ :param prompt: prompt to display to user
2076+ :return: command line text of 'eof' if an EOFError was caught
2077+ :raises whatever exceptions are raised by input() except for EOFError
2078+ """
2079+ try :
2080+ # Wrap in try since terminal_lock may not be locked
2081+ try :
2082+ # Command line is about to be drawn. Allow asynchronous changes to the terminal.
2083+ self .terminal_lock .release ()
2084+ except RuntimeError :
2085+ pass
2086+ return self .read_input (prompt , allow_completion = True )
2087+ except EOFError :
2088+ return 'eof'
2089+ finally :
2090+ # Command line is gone. Do not allow asynchronous changes to the terminal.
2091+ self .terminal_lock .acquire ()
2092+
20442093 def _set_up_cmd2_readline (self ) -> _SavedReadlineSettings :
20452094 """
20462095 Set up readline with cmd2-specific settings
20472096 :return: Class containing saved readline settings
20482097 """
20492098 readline_settings = _SavedReadlineSettings ()
20502099
2051- if self .use_rawinput and self . completekey and rl_type != RlType . NONE :
2100+ if self ._completion_supported () :
20522101
20532102 # Set up readline for our tab completion needs
20542103 if rl_type == RlType .GNU :
@@ -2080,7 +2129,7 @@ def _restore_readline(self, readline_settings: _SavedReadlineSettings):
20802129 Restore saved readline settings
20812130 :param readline_settings: the readline settings to restore
20822131 """
2083- if self .use_rawinput and self . completekey and rl_type != RlType . NONE :
2132+ if self ._completion_supported () :
20842133
20852134 # Restore what we changed in readline
20862135 readline .set_completer (readline_settings .completer )
@@ -2114,7 +2163,7 @@ def _cmdloop(self) -> None:
21142163 while not stop :
21152164 # Get commands from user
21162165 try :
2117- line = self ._pseudo_raw_input (self .prompt )
2166+ line = self ._read_command_line (self .prompt )
21182167 except KeyboardInterrupt as ex :
21192168 if self .quit_on_sigint :
21202169 raise ex
@@ -2693,27 +2742,6 @@ def select(self, opts: Union[str, List[str], List[Tuple[Any, Optional[str]]]],
26932742 that the return value can differ from
26942743 the text advertised to the user """
26952744
2696- completion_disabled = False
2697- orig_completer = None
2698-
2699- def disable_completion ():
2700- """Turn off completion during the select input line"""
2701- nonlocal orig_completer
2702- nonlocal completion_disabled
2703-
2704- if rl_type != RlType .NONE and not completion_disabled :
2705- orig_completer = readline .get_completer ()
2706- readline .set_completer (lambda * args , ** kwargs : None )
2707- completion_disabled = True
2708-
2709- def enable_completion ():
2710- """Restore tab completion when select is done reading input"""
2711- nonlocal completion_disabled
2712-
2713- if rl_type != RlType .NONE and completion_disabled :
2714- readline .set_completer (orig_completer )
2715- completion_disabled = False
2716-
27172745 local_opts = opts
27182746 if isinstance (opts , str ):
27192747 local_opts = list (zip (opts .split (), opts .split ()))
@@ -2730,18 +2758,14 @@ def enable_completion():
27302758 self .poutput (' %2d. %s' % (idx + 1 , text ))
27312759
27322760 while True :
2733- safe_prompt = rl_make_safe_prompt (prompt )
2734-
27352761 try :
2736- with self .sigint_protection :
2737- disable_completion ()
2738- response = input (safe_prompt )
2762+ response = self .read_input (prompt )
27392763 except EOFError :
27402764 response = ''
27412765 self .poutput ('\n ' , end = '' )
2742- finally :
2743- with self .sigint_protection :
2744- enable_completion ()
2766+ except KeyboardInterrupt as ex :
2767+ self .poutput ( '^C' )
2768+ raise ex
27452769
27462770 if not response :
27472771 continue
@@ -2921,7 +2945,7 @@ def _set_up_py_shell_env(self, interp: InteractiveConsole) -> _SavedCmd2Env:
29212945 for item in self ._py_history :
29222946 readline .add_history (item )
29232947
2924- if self .use_rawinput and self . completekey :
2948+ if self ._completion_supported () :
29252949 # Set up tab completion for the Python console
29262950 # rlcompleter relies on the default settings of the Python readline module
29272951 if rl_type == RlType .GNU :
@@ -2988,7 +3012,7 @@ def _restore_cmd2_env(self, cmd2_env: _SavedCmd2Env) -> None:
29883012 for item in cmd2_env .history :
29893013 readline .add_history (item )
29903014
2991- if self .use_rawinput and self . completekey :
3015+ if self ._completion_supported () :
29923016 # Restore cmd2's tab completion settings
29933017 readline .set_completer (cmd2_env .readline_settings .completer )
29943018 readline .set_completer_delims (cmd2_env .readline_settings .delims )
@@ -3715,7 +3739,7 @@ class TestMyAppCase(Cmd2TestCase):
37153739
37163740 def async_alert (self , alert_msg : str , new_prompt : Optional [str ] = None ) -> None : # pragma: no cover
37173741 """
3718- Display an important message to the user while they are at the prompt in between commands .
3742+ Display an important message to the user while they are at a command line prompt .
37193743 To the user it appears as if an alert message is printed above the prompt and their current input
37203744 text and cursor location is left alone.
37213745
@@ -3775,10 +3799,10 @@ def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None:
37753799
37763800 def async_update_prompt (self , new_prompt : str ) -> None : # pragma: no cover
37773801 """
3778- Update the prompt while the user is still typing at it. This is good for alerting the user to system
3779- changes dynamically in between commands. For instance you could alter the color of the prompt to indicate
3780- a system status or increase a counter to report an event. If you do alter the actual text of the prompt,
3781- it is best to keep the prompt the same width as what's on screen. Otherwise the user's input text will
3802+ Update the command line prompt while the user is still typing at it. This is good for alerting the user to
3803+ system changes dynamically in between commands. For instance you could alter the color of the prompt to
3804+ indicate a system status or increase a counter to report an event. If you do alter the actual text of the
3805+ prompt, it is best to keep the prompt the same width as what's on screen. Otherwise the user's input text will
37823806 be shifted and the update will not be seamless.
37833807
37843808 Raises a `RuntimeError` if called while another thread holds `terminal_lock`.
@@ -3948,7 +3972,7 @@ def cmdloop(self, intro: Optional[str] = None) -> int:
39483972 original_sigint_handler = signal .getsignal (signal .SIGINT )
39493973 signal .signal (signal .SIGINT , self .sigint_handler )
39503974
3951- # Grab terminal lock before the prompt has been drawn by readline
3975+ # Grab terminal lock before the command line prompt has been drawn by readline
39523976 self .terminal_lock .acquire ()
39533977
39543978 # Always run the preloop first
0 commit comments