6363 cast ,
6464)
6565
66+ import prompt_toolkit as pt
6667import rich .box
6768from rich .console import Group , RenderableType
6869from rich .highlighter import ReprHighlighter
137138)
138139from .styles import Cmd2Style
139140
140- # NOTE: When using gnureadline with Python 3.13, start_ipython needs to be imported before any readline-related stuff
141141with contextlib .suppress (ImportError ):
142142 from IPython import start_ipython
143143
@@ -254,6 +254,8 @@ class Cmd:
254254 Line-oriented command interpreters are often useful for test harnesses, internal tools, and rapid prototypes.
255255 """
256256
257+ DEFAULT_COMPLETEKEY = 'tab'
258+
257259 DEFAULT_EDITOR = utils .find_editor ()
258260
259261 # Sorting keys for strings
@@ -267,7 +269,7 @@ class Cmd:
267269
268270 def __init__ (
269271 self ,
270- completekey : str = 'tab' ,
272+ completekey : str = DEFAULT_COMPLETEKEY ,
271273 stdin : TextIO | None = None ,
272274 stdout : TextIO | None = None ,
273275 * ,
@@ -291,7 +293,7 @@ def __init__(
291293 ) -> None :
292294 """Easy but powerful framework for writing line-oriented command interpreters, extends Python's cmd package.
293295
294- :param completekey: readline name of a completion key, default to Tab
296+ :param completekey: name of a completion key, default to Tab
295297 :param stdin: alternate input file object, if not specified, sys.stdin is used
296298 :param stdout: alternate output file object, if not specified, sys.stdout is used
297299 :param persistent_history_file: file path to load a persistent cmd2 command history from
@@ -369,6 +371,9 @@ def __init__(
369371
370372 # Key used for tab completion
371373 self .completekey = completekey
374+ if self .completekey != self .DEFAULT_COMPLETEKEY :
375+ # TODO(T or K): Configure prompt_toolkit `KeyBindings` with the custom key for completion # noqa: FIX002, TD003
376+ pass
372377
373378 # Attributes which should NOT be dynamically settable via the set command at runtime
374379 self .default_to_shell = False # Attempt to run unrecognized commands as shell commands
@@ -588,14 +593,14 @@ def __init__(
588593 # An optional hint which prints above tab completion suggestions
589594 self .completion_hint : str = ''
590595
591- # Normally cmd2 uses readline 's formatter to columnize the list of completion suggestions.
596+ # Normally cmd2 uses prompt-toolkit 's formatter to columnize the list of completion suggestions.
592597 # If a custom format is preferred, write the formatted completions to this string. cmd2 will
593- # then print it instead of the readline format. ANSI style sequences and newlines are supported
598+ # then print it instead of the prompt-toolkit format. ANSI style sequences and newlines are supported
594599 # when using this value. Even when using formatted_completions, the full matches must still be returned
595600 # from your completer function. ArgparseCompleter writes its tab completion tables to this string.
596601 self .formatted_completions : str = ''
597602
598- # Used by complete() for readline tab completion
603+ # Used by complete() for prompt-toolkit tab completion
599604 self .completion_matches : list [str ] = []
600605
601606 # Use this list if you need to display tab completion suggestions that are different than the actual text
@@ -1574,7 +1579,7 @@ def ppaged(
15741579 def _reset_completion_defaults (self ) -> None :
15751580 """Reset tab completion settings.
15761581
1577- Needs to be called each time readline runs tab completion.
1582+ Needs to be called each time prompt-toolkit runs tab completion.
15781583 """
15791584 self .allow_appended_space = True
15801585 self .allow_closing_quote = True
@@ -1970,12 +1975,12 @@ def complete_users() -> list[str]:
19701975 matches [index ] += os .path .sep
19711976 self .display_matches [index ] += os .path .sep
19721977
1973- # Remove cwd if it was added to match the text readline expects
1978+ # Remove cwd if it was added to match the text prompt-toolkit expects
19741979 if cwd_added :
19751980 to_replace = cwd if cwd == os .path .sep else cwd + os .path .sep
19761981 matches = [cur_path .replace (to_replace , '' , 1 ) for cur_path in matches ]
19771982
1978- # Restore the tilde string if we expanded one to match the text readline expects
1983+ # Restore the tilde string if we expanded one to match the text prompt-toolkit expects
19791984 if expanded_tilde_path :
19801985 matches = [cur_path .replace (expanded_tilde_path , orig_tilde_path , 1 ) for cur_path in matches ]
19811986
@@ -2213,11 +2218,11 @@ def _perform_completion(
22132218 # Save the quote so we can add a matching closing quote later.
22142219 completion_token_quote = raw_completion_token [0 ]
22152220
2216- # readline still performs word breaks after a quote. Therefore, something like quoted search
2221+ # prompt-toolkit still performs word breaks after a quote. Therefore, something like quoted search
22172222 # text with a space would have resulted in begidx pointing to the middle of the token we
22182223 # we want to complete. Figure out where that token actually begins and save the beginning
2219- # portion of it that was not part of the text readline gave us. We will remove it from the
2220- # completions later since readline expects them to start with the original text.
2224+ # portion of it that was not part of the text prompt-toolkit gave us. We will remove it from the
2225+ # completions later since prompt-toolkit expects them to start with the original text.
22212226 actual_begidx = line [:endidx ].rfind (tokens [- 1 ])
22222227
22232228 if actual_begidx != begidx :
@@ -2240,7 +2245,7 @@ def _perform_completion(
22402245 if not self .display_matches :
22412246 # Since self.display_matches is empty, set it to self.completion_matches
22422247 # before we alter them. That way the suggestions will reflect how we parsed
2243- # the token being completed and not how readline did.
2248+ # the token being completed and not how prompt-toolkit did.
22442249 import copy
22452250
22462251 self .display_matches = copy .copy (self .completion_matches )
@@ -2290,12 +2295,12 @@ def complete(
22902295 ) -> str | None :
22912296 """Override of cmd's complete method which returns the next possible completion for 'text'.
22922297
2293- This completer function is called by readline as complete(text, state), for state in 0, 1, 2, …,
2298+ This completer function is called by prompt-toolkit as complete(text, state), for state in 0, 1, 2, …,
22942299 until it returns a non-string value. It should return the next possible completion starting with text.
22952300
2296- Since readline suppresses any exception raised in completer functions, they can be difficult to debug.
2301+ Since prompt-toolkit suppresses any exception raised in completer functions, they can be difficult to debug.
22972302 Therefore, this function wraps the actual tab completion logic and prints to stderr any exception that
2298- occurs before returning control to readline .
2303+ occurs before returning control to prompt-toolkit .
22992304
23002305 :param text: the current word that user is typing
23012306 :param state: non-negative integer
@@ -2575,7 +2580,7 @@ def postloop(self) -> None:
25752580 def parseline (self , line : str ) -> tuple [str , str , str ]:
25762581 """Parse the line into a command name and a string containing the arguments.
25772582
2578- :param line: line read by readline
2583+ :param line: line read by prompt-toolkit
25792584 :return: tuple containing (command, args, line)
25802585 """
25812586 statement = self .statement_parser .parse_command_only (line )
@@ -3225,15 +3230,13 @@ def get_prompt() -> Any:
32253230 # Otherwise read from self.stdin
32263231 elif self .stdin .isatty ():
32273232 # on a tty, print the prompt first, then read the line
3228- self .poutput (prompt , end = '' )
3229- self .stdout .flush ()
3230- line = self .stdin .readline ()
3233+ line = pt .prompt (prompt )
32313234 if len (line ) == 0 :
32323235 raise EOFError
32333236 return line .rstrip ('\n ' )
32343237 else :
32353238 # not a tty, just read the line
3236- line = self . stdin . readline ()
3239+ line = pt . prompt ()
32373240 if len (line ) == 0 :
32383241 raise EOFError
32393242 return line .rstrip ('\n ' )
@@ -4736,7 +4739,7 @@ def do_history(self, args: argparse.Namespace) -> bool | None:
47364739 if args .clear :
47374740 self .last_result = True
47384741
4739- # Clear command and readline history
4742+ # Clear command and prompt-toolkit history
47404743 self .history .clear ()
47414744
47424745 if self .persistent_history_file :
@@ -5333,8 +5336,8 @@ def async_refresh_prompt(self) -> None: # pragma: no cover
53335336
53345337 One case where the onscreen prompt and self.prompt can get out of sync is
53355338 when async_alert() is called while a user is in search mode (e.g. Ctrl-r).
5336- To prevent overwriting readline 's onscreen search prompt, self.prompt is updated
5337- but readline 's saved prompt isn't.
5339+ To prevent overwriting prompt-toolkit 's onscreen search prompt, self.prompt is updated
5340+ but prompt-toolkit 's saved prompt isn't.
53385341
53395342 Therefore when a user aborts a search, the old prompt is still on screen until they
53405343 press Enter or this method is called. Call need_prompt_refresh() in an async print
@@ -5494,7 +5497,7 @@ def cmdloop(self, intro: RenderableType = '') -> int:
54945497 original_sigterm_handler = signal .getsignal (signal .SIGTERM )
54955498 signal .signal (signal .SIGTERM , self .termination_signal_handler )
54965499
5497- # Grab terminal lock before the command line prompt has been drawn by readline
5500+ # Grab terminal lock before the command line prompt has been drawn by prompt-toolkit
54985501 self .terminal_lock .acquire ()
54995502
55005503 # Always run the preloop first
0 commit comments