Repository: xonsh/xonsh

Issue:
Save history when the shell is exiting abnormally
When using the JSON backend, the history is not saved unless the shell is exited properly (for example, by ^D). When the `exec` alias is used, or a crash is induced (by for example adding invalid colors into `$BOTTOM_TOOLBAR`), the history file is not saved, and is left locked without commands being written into it.

My suspicion is that this is due to the fact that exit  handlers are not ran when exec is used. This doesn't happen with SQLite for obvious reasons.

## For community
⬇️  **Please click the 👍 reaction instead of leaving a `+1` or 👍  comment**



Discussion:
For reference, `xonfig` says:

```
+------------------+-----------------+
| xonsh            | 0.6.3           |
| Python           | 3.6.5           |
| PLY              | 3.11            |
| have readline    | True            |
| prompt toolkit   | 1.0.15          |
| shell type       | prompt_toolkit  |
| pygments         | 2.2.0           |
| on posix         | True            |
| on linux         | True            |
| distro           | debian          |
| on darwin        | False           |
| on windows       | False           |
| on cygwin        | False           |
| on msys2         | False           |
| is superuser     | False           |
| default encoding | utf-8           |
| xonsh encoding   | utf-8           |
| encoding errors  | surrogateescape |
+------------------+-----------------+
```
Thanks for reporting @dratini0!  I can confirm this behavior.  
> My suspicion is that this is due to the fact that exit handlers are not ran when exec is used.

Really? I thought that atexit was always run...
This still is bugged fyi
For others wishing to move to SQLite, use this once:

[How to convert history to sqlite?](https://github.com/xonsh/xonsh/discussions/4132)


```python
from xonsh.history.json import JsonHistory
from xonsh.history.sqlite import SqliteHistory

class History(SqliteHistory):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.clear()

        jh = JsonHistory(**kwargs)
        for item in jh.all_items():
            self.append(item)
```

Resolving patch:
diff --git a/xonsh/environ.py b/xonsh/environ.py
--- a/xonsh/environ.py
+++ b/xonsh/environ.py
@@ -1655,6 +1655,11 @@ class PromptHistorySetting(Xettings):
         "Example: ``$XONSH_HISTORY_IGNORE_REGEX = '(^echo|^.*\\#\\#\\#\\s*|.*\\#\\#\\#\\s*$)'``"
         " - skip commands that start from ``echo`` or ``###``, or end from ``###``.",
     )
+    XONSH_HISTORY_SIGINT_FLUSH = Var.with_default(
+        True,
+        "Save history after getting SIGINT (Ctrl+C).",
+        doc_default="True",
+    )
 
 
 class PTKSetting(PromptSetting):  # sub-classing -> sub-group
diff --git a/xonsh/ptk_shell/shell.py b/xonsh/ptk_shell/shell.py
--- a/xonsh/ptk_shell/shell.py
+++ b/xonsh/ptk_shell/shell.py
@@ -421,6 +421,15 @@ def cmdloop(self, intro=None):
                     self.default(line, raw_line)
             except (KeyboardInterrupt, SystemExit) as e:
                 self.reset_buffer()
+                if isinstance(e, KeyboardInterrupt):
+                    if XSH.env.get("XONSH_HISTORY_SIGINT_FLUSH", True):
+                        """
+                        Development tools like PyCharm send SIGINT before SIGKILL.
+                        This is the last chance to save history in this case.
+                        """
+                        if XSH.env.get("XONSH_DEBUG", False):
+                            print("Flushing history after SIGINT.", file=sys.stderr)
+                        XSH.history.flush()
                 if isinstance(e, SystemExit):
                     get_app().reset()  # Reset TTY mouse and keys handlers.
                     self.restore_tty_sanity()  # Reset TTY SIGINT handlers.


Test patch: