@@ -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,60 @@ 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
1996+ def read_input (self , prompt : str , allow_completion : bool = False ) -> str :
1997+ """
1998+ Read input from appropriate stdin value. Also allows you to disable tab completion while input is being read.
19941999
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
2000+ :param prompt: prompt to display to user
2001+ :param allow_completion: if True, then tab completion of commands is enabled. This generally should be
2002+ set to False unless reading the command line. Defaults to False.
2003+ :return: the line read from stdin with all trailing new lines removed
2004+ :raises whatever exceptions are raised by input()
19982005 """
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
2006+ completion_disabled = False
2007+ orig_completer = None
2008+
2009+ def disable_completion ():
2010+ """Turn off completion during the select input line"""
2011+ nonlocal orig_completer
2012+ nonlocal completion_disabled
2013+
2014+ if self ._completion_supported () and not completion_disabled :
2015+ orig_completer = readline .get_completer ()
2016+ readline .set_completer (lambda * args , ** kwargs : None )
2017+ completion_disabled = True
2018+
2019+ def enable_completion ():
2020+ """Restore tab completion when select is done reading input"""
2021+ nonlocal completion_disabled
2022+
2023+ if self ._completion_supported () and completion_disabled :
2024+ readline .set_completer (orig_completer )
2025+ completion_disabled = False
20082026
2027+ # Check we are reading from sys.stdin
2028+ if self .use_rawinput :
2029+ if sys .stdin .isatty ():
2030+ try :
20092031 # Deal with the vagaries of readline and ANSI escape codes
20102032 safe_prompt = rl_make_safe_prompt (prompt )
2033+
2034+ # Check if tab completion should be disabled
2035+ with self .sigint_protection :
2036+ if not allow_completion :
2037+ disable_completion ()
20112038 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 ()
2039+ finally :
2040+ # Check if we need to reenable tab completion
2041+ with self .sigint_protection :
2042+ if not allow_completion :
2043+ enable_completion ()
2044+ else :
2045+ line = input ()
2046+ if self .echo :
2047+ sys .stdout .write ('{}{}\n ' .format (prompt , line ))
2048+
2049+ # Otherwise read from self.stdin
20222050 else :
20232051 if self .stdin .isatty ():
20242052 # on a tty, print the prompt first, then read the line
@@ -2041,14 +2069,36 @@ def _pseudo_raw_input(self, prompt: str) -> str:
20412069
20422070 return line .rstrip ('\r \n ' )
20432071
2072+ def _read_command_line (self , prompt : str ) -> str :
2073+ """
2074+ Read command line from appropriate stdin
2075+
2076+ :param prompt: prompt to display to user
2077+ :return: command line text of 'eof' if an EOFError was caught
2078+ :raises whatever exceptions are raised by input() except for EOFError
2079+ """
2080+ try :
2081+ # Wrap in try since terminal_lock may not be locked
2082+ try :
2083+ # Command line is about to be drawn. Allow asynchronous changes to the terminal.
2084+ self .terminal_lock .release ()
2085+ except RuntimeError :
2086+ pass
2087+ return self .read_input (prompt , allow_completion = True )
2088+ except EOFError :
2089+ return 'eof'
2090+ finally :
2091+ # Command line is gone. Do not allow asynchronous changes to the terminal.
2092+ self .terminal_lock .acquire ()
2093+
20442094 def _set_up_cmd2_readline (self ) -> _SavedReadlineSettings :
20452095 """
20462096 Set up readline with cmd2-specific settings
20472097 :return: Class containing saved readline settings
20482098 """
20492099 readline_settings = _SavedReadlineSettings ()
20502100
2051- if self .use_rawinput and self . completekey and rl_type != RlType . NONE :
2101+ if self ._completion_supported () :
20522102
20532103 # Set up readline for our tab completion needs
20542104 if rl_type == RlType .GNU :
@@ -2080,7 +2130,7 @@ def _restore_readline(self, readline_settings: _SavedReadlineSettings):
20802130 Restore saved readline settings
20812131 :param readline_settings: the readline settings to restore
20822132 """
2083- if self .use_rawinput and self . completekey and rl_type != RlType . NONE :
2133+ if self ._completion_supported () :
20842134
20852135 # Restore what we changed in readline
20862136 readline .set_completer (readline_settings .completer )
@@ -2114,7 +2164,7 @@ def _cmdloop(self) -> None:
21142164 while not stop :
21152165 # Get commands from user
21162166 try :
2117- line = self ._pseudo_raw_input (self .prompt )
2167+ line = self ._read_command_line (self .prompt )
21182168 except KeyboardInterrupt as ex :
21192169 if self .quit_on_sigint :
21202170 raise ex
@@ -2693,27 +2743,6 @@ def select(self, opts: Union[str, List[str], List[Tuple[Any, Optional[str]]]],
26932743 that the return value can differ from
26942744 the text advertised to the user """
26952745
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-
27172746 local_opts = opts
27182747 if isinstance (opts , str ):
27192748 local_opts = list (zip (opts .split (), opts .split ()))
@@ -2730,18 +2759,14 @@ def enable_completion():
27302759 self .poutput (' %2d. %s' % (idx + 1 , text ))
27312760
27322761 while True :
2733- safe_prompt = rl_make_safe_prompt (prompt )
2734-
27352762 try :
2736- with self .sigint_protection :
2737- disable_completion ()
2738- response = input (safe_prompt )
2763+ response = self .read_input (prompt )
27392764 except EOFError :
27402765 response = ''
27412766 self .poutput ('\n ' , end = '' )
2742- finally :
2743- with self .sigint_protection :
2744- enable_completion ()
2767+ except KeyboardInterrupt as ex :
2768+ self .poutput ( '^C' )
2769+ raise ex
27452770
27462771 if not response :
27472772 continue
@@ -2921,7 +2946,7 @@ def _set_up_py_shell_env(self, interp: InteractiveConsole) -> _SavedCmd2Env:
29212946 for item in self ._py_history :
29222947 readline .add_history (item )
29232948
2924- if self .use_rawinput and self . completekey :
2949+ if self ._completion_supported () :
29252950 # Set up tab completion for the Python console
29262951 # rlcompleter relies on the default settings of the Python readline module
29272952 if rl_type == RlType .GNU :
@@ -2988,7 +3013,7 @@ def _restore_cmd2_env(self, cmd2_env: _SavedCmd2Env) -> None:
29883013 for item in cmd2_env .history :
29893014 readline .add_history (item )
29903015
2991- if self .use_rawinput and self . completekey :
3016+ if self ._completion_supported () :
29923017 # Restore cmd2's tab completion settings
29933018 readline .set_completer (cmd2_env .readline_settings .completer )
29943019 readline .set_completer_delims (cmd2_env .readline_settings .delims )
@@ -3715,7 +3740,7 @@ class TestMyAppCase(Cmd2TestCase):
37153740
37163741 def async_alert (self , alert_msg : str , new_prompt : Optional [str ] = None ) -> None : # pragma: no cover
37173742 """
3718- Display an important message to the user while they are at the prompt in between commands .
3743+ Display an important message to the user while they are at a command line prompt .
37193744 To the user it appears as if an alert message is printed above the prompt and their current input
37203745 text and cursor location is left alone.
37213746
@@ -3775,10 +3800,10 @@ def async_alert(self, alert_msg: str, new_prompt: Optional[str] = None) -> None:
37753800
37763801 def async_update_prompt (self , new_prompt : str ) -> None : # pragma: no cover
37773802 """
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
3803+ Update the command line prompt while the user is still typing at it. This is good for alerting the user to
3804+ system changes dynamically in between commands. For instance you could alter the color of the prompt to
3805+ indicate a system status or increase a counter to report an event. If you do alter the actual text of the
3806+ prompt, it is best to keep the prompt the same width as what's on screen. Otherwise the user's input text will
37823807 be shifted and the update will not be seamless.
37833808
37843809 Raises a `RuntimeError` if called while another thread holds `terminal_lock`.
@@ -3948,7 +3973,7 @@ def cmdloop(self, intro: Optional[str] = None) -> int:
39483973 original_sigint_handler = signal .getsignal (signal .SIGINT )
39493974 signal .signal (signal .SIGINT , self .sigint_handler )
39503975
3951- # Grab terminal lock before the prompt has been drawn by readline
3976+ # Grab terminal lock before the command line prompt has been drawn by readline
39523977 self .terminal_lock .acquire ()
39533978
39543979 # Always run the preloop first
0 commit comments