From 12e2495c3b84a94f50f61312ba708163f972eeda Mon Sep 17 00:00:00 2001 From: Matt Wozniski Date: Sat, 3 May 2025 21:36:10 -0400 Subject: [PATCH 1/4] Fix Cmd completion for lines beginning with `! ` When a line begins with `!` and there's no `do_shell` method defined, `parsecmd` returns `None` as the `cmd`, which incorrectly leads to `None` being concatenated to `complete_` and triggering a `TypeError`. Instead, recognize `None` as a sentinel that means we should call `completedefault`, as an empty string already is. --- Lib/cmd.py | 2 +- Lib/test/test_cmd.py | 25 +++++++++++++++++++ ...-05-03-21-55-33.gh-issue-133363.PTLnRP.rst | 5 ++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2025-05-03-21-55-33.gh-issue-133363.PTLnRP.rst diff --git a/Lib/cmd.py b/Lib/cmd.py index 438b88aa1049cc..9ae81178aa5feb 100644 --- a/Lib/cmd.py +++ b/Lib/cmd.py @@ -273,7 +273,7 @@ def complete(self, text, state): endidx = readline.get_endidx() - stripped if begidx>0: cmd, args, foo = self.parseline(line) - if cmd == '': + if cmd in ('', None): compfunc = self.completedefault else: try: diff --git a/Lib/test/test_cmd.py b/Lib/test/test_cmd.py index 46ec82b704963d..727a164b7f9306 100644 --- a/Lib/test/test_cmd.py +++ b/Lib/test/test_cmd.py @@ -289,6 +289,31 @@ def do_tab_completion_test(self, args): self.assertIn(b'ab_completion_test', output) self.assertIn(b'tab completion success', output) + def test_bang_completion_without_do_shell(self): + script = textwrap.dedent(""" + import cmd + class simplecmd(cmd.Cmd): + def completedefault(self, text, line, begidx, endidx): + return ["hello"] + + def default(self, line): + if line == "! hello": + print('tab completion success') + else: + print('tab completion failure') + return True + + simplecmd().cmdloop() + """) + + # '! h' and complete 'ello' to '! hello' + input = b"! h\t\n" + + output = run_pty(script, input) + + self.assertIn(b'ello', output) + self.assertIn(b'tab completion success', output) + def load_tests(loader, tests, pattern): tests.addTest(doctest.DocTestSuite()) return tests diff --git a/Misc/NEWS.d/next/Library/2025-05-03-21-55-33.gh-issue-133363.PTLnRP.rst b/Misc/NEWS.d/next/Library/2025-05-03-21-55-33.gh-issue-133363.PTLnRP.rst new file mode 100644 index 00000000000000..379482d6369a87 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-03-21-55-33.gh-issue-133363.PTLnRP.rst @@ -0,0 +1,5 @@ +The :class:`cmd.Cmd` class has been fixed to call the ``completedefault`` +method whenever the ``do_shell`` method is not defined and tab completion is +requested for a line beginning with ``!``. Previously ``completedefault`` +was called only if there were no spaces between the ``!`` and the cursor +position where tab completion was attempted. From 973bef66152640abb820841b20b2a948ff00cd03 Mon Sep 17 00:00:00 2001 From: Matt Wozniski Date: Sat, 3 May 2025 22:00:06 -0400 Subject: [PATCH 2/4] Check falsiness instead --- Lib/cmd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/cmd.py b/Lib/cmd.py index 9ae81178aa5feb..51495fb32160b0 100644 --- a/Lib/cmd.py +++ b/Lib/cmd.py @@ -273,7 +273,7 @@ def complete(self, text, state): endidx = readline.get_endidx() - stripped if begidx>0: cmd, args, foo = self.parseline(line) - if cmd in ('', None): + if not cmd: compfunc = self.completedefault else: try: From 1c6248e427184be34f41011310099d40b76fdb4c Mon Sep 17 00:00:00 2001 From: Matt Wozniski Date: Sat, 3 May 2025 22:07:06 -0400 Subject: [PATCH 3/4] Make news entry less descriptive --- .../Library/2025-05-03-21-55-33.gh-issue-133363.PTLnRP.rst | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2025-05-03-21-55-33.gh-issue-133363.PTLnRP.rst b/Misc/NEWS.d/next/Library/2025-05-03-21-55-33.gh-issue-133363.PTLnRP.rst index 379482d6369a87..d44c685e75e6c1 100644 --- a/Misc/NEWS.d/next/Library/2025-05-03-21-55-33.gh-issue-133363.PTLnRP.rst +++ b/Misc/NEWS.d/next/Library/2025-05-03-21-55-33.gh-issue-133363.PTLnRP.rst @@ -1,5 +1,3 @@ -The :class:`cmd.Cmd` class has been fixed to call the ``completedefault`` +The :class:`cmd.Cmd` class has been fixed to reliably call the ``completedefault`` method whenever the ``do_shell`` method is not defined and tab completion is -requested for a line beginning with ``!``. Previously ``completedefault`` -was called only if there were no spaces between the ``!`` and the cursor -position where tab completion was attempted. +requested for a line beginning with ``!``. From c259bef79c6a6eae676b65510bfe50e8a37829f4 Mon Sep 17 00:00:00 2001 From: Matt Wozniski Date: Sat, 3 May 2025 22:16:23 -0400 Subject: [PATCH 4/4] Test with and without a space after ! --- Lib/test/test_cmd.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_cmd.py b/Lib/test/test_cmd.py index 727a164b7f9306..0ae44f3987d324 100644 --- a/Lib/test/test_cmd.py +++ b/Lib/test/test_cmd.py @@ -297,7 +297,7 @@ def completedefault(self, text, line, begidx, endidx): return ["hello"] def default(self, line): - if line == "! hello": + if line.replace(" ", "") == "!hello": print('tab completion success') else: print('tab completion failure') @@ -306,13 +306,12 @@ def default(self, line): simplecmd().cmdloop() """) - # '! h' and complete 'ello' to '! hello' - input = b"! h\t\n" - - output = run_pty(script, input) - - self.assertIn(b'ello', output) - self.assertIn(b'tab completion success', output) + # '! h' or '!h' and complete 'ello' to 'hello' + for input in [b"! h\t\n", b"!h\t\n"]: + with self.subTest(input=input): + output = run_pty(script, input) + self.assertIn(b'hello', output) + self.assertIn(b'tab completion success', output) def load_tests(loader, tests, pattern): tests.addTest(doctest.DocTestSuite())