diff --git a/Lib/cmd.py b/Lib/cmd.py index 438b88aa1049cc..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 == '': + if not cmd: compfunc = self.completedefault else: try: diff --git a/Lib/test/test_cmd.py b/Lib/test/test_cmd.py index 46ec82b704963d..0ae44f3987d324 100644 --- a/Lib/test/test_cmd.py +++ b/Lib/test/test_cmd.py @@ -289,6 +289,30 @@ 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.replace(" ", "") == "!hello": + print('tab completion success') + else: + print('tab completion failure') + return True + + simplecmd().cmdloop() + """) + + # '! 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()) 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..d44c685e75e6c1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-05-03-21-55-33.gh-issue-133363.PTLnRP.rst @@ -0,0 +1,3 @@ +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 ``!``.