diff --git a/news/fix-ptk-force-quit.rst b/news/fix-ptk-force-quit.rst new file mode 100644 index 000000000..e56e441bc --- /dev/null +++ b/news/fix-ptk-force-quit.rst @@ -0,0 +1,24 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Empty/comment-only commands no longer get added to the history +* On prompt-toolkit, when there is a job like `sleep 500 &` running in the background, pressing Ctrl+D twice to force quit now works properly + +**Security:** + +* diff --git a/tests/test_base_shell.py b/tests/test_base_shell.py index 2335d6ba6..a2f229e72 100644 --- a/tests/test_base_shell.py +++ b/tests/test_base_shell.py @@ -1,6 +1,8 @@ """(A down payment on) Testing for ``xonsh.base_shell.BaseShell`` and associated classes""" import os +import pytest + from xonsh.base_shell import BaseShell from xonsh.shell import transform_command @@ -36,3 +38,28 @@ def test_transform(xession): assert transform_command("spam") == "egg" assert transform_command("egg") == "egg" assert transform_command("foo") == "foo" + + +@pytest.mark.parametrize( + "cmd,exp_append_history", + [ + ("", False), + ("# a comment", False), + ("print('yes')", True), + ], +) +def test_default_append_history(cmd, exp_append_history, xonsh_session, monkeypatch): + """Test that running an empty line or a comment does not append to history""" + append_history_calls = [] + + def mock_append_history(**info): + append_history_calls.append(info) + + monkeypatch.setattr( + xonsh_session.shell.shell, "_append_history", mock_append_history + ) + xonsh_session.shell.default(cmd) + if exp_append_history: + assert len(append_history_calls) == 1 + else: + assert len(append_history_calls) == 0 diff --git a/tests/test_imphooks.py b/tests/test_imphooks.py index 232a8d3a5..50118e20b 100644 --- a/tests/test_imphooks.py +++ b/tests/test_imphooks.py @@ -20,6 +20,12 @@ def test_import(): assert "hello mom jawaka\n" == sample.x +def test_import_empty(): + from xpack import empty_xsh + + assert empty_xsh + + def test_absolute_import(): from xpack import sample diff --git a/tests/test_ptk_shell.py b/tests/test_ptk_shell.py index cd794f08c..3dcfe1745 100644 --- a/tests/test_ptk_shell.py +++ b/tests/test_ptk_shell.py @@ -121,3 +121,28 @@ def test_ptk_prompt(line, exp, ptk_shell, capsys): out = screen.display[0].strip() assert out.strip() == (exp or line) + + +@pytest.mark.parametrize( + "cmd,exp_append_history", + [ + ("", False), + ("# a comment", False), + ("print('yes')", True), + ], +) +def test_ptk_default_append_history(cmd, exp_append_history, ptk_shell, monkeypatch): + """Test that running an empty line or a comment does not append to history. + This test is necessary because the prompt-toolkit shell uses a custom _push() method that is different from the base shell's push() method.""" + inp, out, shell = ptk_shell + append_history_calls = [] + + def mock_append_history(**info): + append_history_calls.append(info) + + monkeypatch.setattr(shell, "_append_history", mock_append_history) + shell.default(cmd) + if exp_append_history: + assert len(append_history_calls) == 1 + else: + assert len(append_history_calls) == 0 diff --git a/tests/xpack/empty_xsh.xsh b/tests/xpack/empty_xsh.xsh new file mode 100644 index 000000000..e69de29bb diff --git a/xonsh/base_shell.py b/xonsh/base_shell.py index 5b7ff04d6..5ff192b3f 100644 --- a/xonsh/base_shell.py +++ b/xonsh/base_shell.py @@ -511,7 +511,12 @@ class BaseShell: return src, None try: code = self.execer.compile( - src, mode="single", glbs=self.ctx, locs=None, filename="" + src, + mode="single", + glbs=self.ctx, + locs=None, + filename="", + compile_empty_tree=False, ) if _cache: update_cache(code, cachefname) diff --git a/xonsh/execer.py b/xonsh/execer.py index 5dbf61a9d..4d5fda834 100644 --- a/xonsh/execer.py +++ b/xonsh/execer.py @@ -112,6 +112,7 @@ class Execer: stacklevel=2, filename=None, transform=True, + compile_empty_tree=True, ): """Compiles xonsh code into a Python code object, which may then be execed or evaled. @@ -128,7 +129,9 @@ class Execer: ctx = set(dir(builtins)) | set(glbs.keys()) | set(locs.keys()) tree = self.parse(input, ctx, mode=mode, filename=filename, transform=transform) if tree is None: - return compile("pass", filename, mode) # handles comment only input + return ( + compile("pass", filename, mode) if compile_empty_tree else None + ) # handles comment only input try: code = compile(tree, filename, mode) except SyntaxError as e: diff --git a/xonsh/ptk_shell/shell.py b/xonsh/ptk_shell/shell.py index a20869999..a92663a22 100644 --- a/xonsh/ptk_shell/shell.py +++ b/xonsh/ptk_shell/shell.py @@ -188,6 +188,7 @@ class PromptToolkitShell(BaseShell): winutils.enable_virtual_terminal_processing() self._first_prompt = True self.history = ThreadedHistory(PromptToolkitHistory()) + self.push = self._push ptk_args.setdefault("history", self.history) if not XSH.env.get("XONSH_COPY_ON_DELETE", False): @@ -380,7 +381,9 @@ class PromptToolkitShell(BaseShell): src = "".join(self.buffer) src = transform_command(src) try: - code = self.execer.compile(src, mode="single", glbs=self.ctx, locs=None) + code = self.execer.compile( + src, mode="single", glbs=self.ctx, locs=None, compile_empty_tree=False + ) self.reset_buffer() except Exception: # pylint: disable=broad-except self.reset_buffer() @@ -393,7 +396,6 @@ class PromptToolkitShell(BaseShell): if intro: print(intro) auto_suggest = AutoSuggestFromHistory() - self.push = self._push while not XSH.exit: try: line = self.singleline(auto_suggest=auto_suggest)