Skip to content

Conversation

@tleonhardt
Copy link
Member

@tleonhardt tleonhardt commented Dec 29, 2025

This PR migrates from using GNU Readline for user input, tab-completion, and history to using prompt-toolkit for the same purpose.

prompt-toolkit is a pure-Python readline replacement that is fully cross-platform compatible and doesn't rely on the presence of underlying C dynamic libraries. It also opens the door to some advanced features in the future.

All use of readline is removed and the cmd2.rl_utils module has been deleted. There is a new cmd2.pt_utils module in its place. Currently all linting is passing and the documentation builds. This is essentially final draft that needs more testing. There are likely a some things that need to be considered related to asynicio support, signal handlers, and blocking popen calls.

All tests are passing on all platforms. I have done some manual testing on both macOS and Linux. But I don't have a Windows VM to do any manual testing on Windows however.

NOTE FOR REVIEWERS: The substantive code changes are in the 3 following files (all in the cmd2 directory):

  • argparse_completer.py (modified)
  • cmd2.py (modified)
  • pt_utils.py (new)

TODO:

  • Fix so tab-completion hints shown up above or below the prompt but not in the the bottom bar
  • Fix tab completion for for command/sub-command names and arguments so it uses prompt-toolkit completion
  • Fix subcommand suggestion for alias create <TAB> - currently showing nothing
  • Increaes code coverage for new code
  • Modify getting_started.py example to show how to use the optional bottom toolbar feature
  • Update documentation
  • Self review and code / comment cleeanup
  • Check type-hints for new/modified methods and functions
  • Do a bunch of manual testing related to edge cases on all platforms
    • Windows
    • macOS
    • Linux
  • Think about asyncio cases
    • Fix examples/async_call.py example to account for prompt-toolkit starting an asyncio event loop
  • Think about interactions with signal handlers
  • Thank about blocking calls to things like popen

Tons of tests failing and some even getting stuck.
…f the correct arguments to app.complete()

This fixes all of the test_argparse_comleter.py tests.

There are still failing tests in test_cmd2.py, test_history.py, and test_run_pyscript.py as well as a test in test_cmd2.py that gets stuck.
TODO:
- prompt-toolkit history isn't properly initialized with history from a persistent history file, as shown by the remaining failing history test
… of mocking the built-in input function.

There are still 3 failing and 1 skipped test in test_cmd2.py

Additionally, some tests in test_run_pyscript.py are getting stuck.All tests in other files are passing.
Also:
- Fixed make clean so it cleans up code coverage file artifacts
Also added a bottom toolboar for displaying these type hints.
@tleonhardt tleonhardt added this to the 4.0.0 milestone Dec 29, 2025
@tleonhardt tleonhardt added enhancement major dependencies Pull requests that update a dependency file labels Dec 29, 2025
@github-actions
Copy link
Contributor

🤖 Hi @tleonhardt, I've received your request, and I'm working on it now! You can track my progress in the logs for more details.

@codecov
Copy link

codecov bot commented Dec 29, 2025

Codecov Report

❌ Patch coverage is 99.65986% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 99.12%. Comparing base (0da9b6d) to head (9c57b6b).

Files with missing lines Patch % Lines
cmd2/cmd2.py 99.20% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1553      +/-   ##
==========================================
+ Coverage   98.94%   99.12%   +0.17%     
==========================================
  Files          21       21              
  Lines        4942     4901      -41     
==========================================
- Hits         4890     4858      -32     
+ Misses         52       43       -9     
Flag Coverage Δ
unittests 99.12% <99.65%> (+0.17%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@github-actions
Copy link
Contributor

🤖 I'm sorry @tleonhardt, but I was unable to process your request. Please see the logs for more details.

@tleonhardt tleonhardt mentioned this pull request Dec 29, 2025
12 tasks
Updated Cmd._bottom_toolbar to display the application name in green on the left and the current ISO timestamp in blue on the right when include_bottom_toolbar is enabled. Used padding to achieve right-alignment of the timestamp. Updated associated tests to match the new return format.
Modified getting_started.py to spawn a background thread that triggers a UI redraw twice a second. This ensures that dynamic content in the bottom toolbar, such as the timestamp, stays current while waiting for user input.
Updated Cmd._bottom_toolbar to format the current time with 0.01s precision (two decimal places for fractional seconds). Maintained the ISO-like format including the timezone offset.
Updated documentation throughout the docs/ directory to reflect the migration from GNU Readline to prompt-toolkit and the addition of the new bottom toolbar feature. Added an upgrade guide section for 4.x, updated the history and prompt feature pages, and adjusted mkdocs configuration to allow documentation of the _bottom_toolbar method.
@tleonhardt tleonhardt marked this pull request as ready for review January 17, 2026 04:48
@tleonhardt tleonhardt changed the title [DRAFT] Migrate from using readline to prompt-toolkit Migrate from using readline to prompt-toolkit Jan 17, 2026
…pletion

Added a unit test to verify that visible commands, aliases, and macros are correctly returned as CompletionItem objects for tab completion. Specifically addressed the case where a command has no docstring.
@tleonhardt
Copy link
Member Author

@kmvanbrunt This is ready for your review when you get a chance.

@anselor @kotfu @theagilehacker @joqual If any of you have feedback, I'd also greatly appreciate it.

@theagilehacker
Copy link
Contributor

theagilehacker commented Jan 17, 2026 via email

Modified ArgparseCompleter to only fallback to flag completion when the current positional argument is full (reached its max nargs) instead of just reaching its minimum. This fixes an issue where commands with optional positional arguments (like 'history') would automatically complete '-' and show flags instead of the more useful positional hint text when tab-completed with no input.
Modified ArgparseCompleter to return only one completion result per action, grouping all matching option strings (short and long forms) together in the display text. This prevents duplicate entries for the same flag from appearing separately in the tab completion menu, providing a cleaner and more concise user experience.
Added a unit test 'test_autcomp_fallback_to_flags_nargs0' to cover the logic in 'ArgparseCompleter' that falls back to flag completion when a positional argument has reached its maximum number of values and the current input is empty. This was achieved by manually patching the 'nargs' attribute of a positional action to 0.
Added a unit test 'test_multiline_complete_statement_eof' to cover the logic in '_complete_statement' where an EOF is encountered during multiline command input. The test verifies that 'eof' is correctly handled by converting it to a newline terminator and printing it.
…lict

Updated examples/async_call.py to use a synchronous main entry point. This prevents a RuntimeError caused by prompt-toolkit attempting to start its own asyncio loop while one was already running in the same thread. The example's core functionality of bridging sync and async code remains intact through its use of a background thread for asynchronous tasks.
Also improved some docstring whitespace in examples/async_call.py
Warn that cmd2 starts its own asyncio event loop due to prompt-toolkit using it natively.
When raising a CompletionError with apply_style=True during tab completion
in prompt_toolkit mode, the error message was printed directly to stdout using
ANSI codes. prompt_toolkit's patch_stdout would capture this but fail to
interpret the ANSI codes, resulting in raw escape sequences being displayed.

This commit fixes the issue by:
1.  Modifying cmd2.complete to capture the styled error message into
    self.completion_header instead of printing directly to stdout.
2.  Updating Cmd2Completer.get_completions (in pt_utils.py) to print
    completion_header using print_formatted_text wrapped in ANSI,
    which correctly renders the styled text above the prompt.
3.  Updating tests/conftest.py complete_tester to print completion_header
    so existing tests (like test_completion_error) continue to pass by
    capturing the error output.
The previous commit introduced accessing self.cmd_app.completion_header
in Cmd2Completer.get_completions, which caused AttributeError in tests
using MockCmd because it lacked this attribute.

This commit adds completion_header to MockCmd and includes a new test case
test_get_completions_with_header to verify that the completion header is
correctly printed when present.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file enhancement major

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants