diff --git a/docs/tutorial_hist.rst b/docs/tutorial_hist.rst index ba44cde72c..05f89e6cf0 100644 --- a/docs/tutorial_hist.rst +++ b/docs/tutorial_hist.rst @@ -255,6 +255,13 @@ may be useful to share entries between shell sessions. In such a case, one can u the ``flush`` action to immediately save the session history to disk and make it accessible from other shell sessions. +``pull`` action +================ +Tries to pull the history from parallel sessions and add to the current session. + +For example if there are two parallel terminal windows the run of ``history pull`` +command from the second terminal window will get the commands from the first terminal. + ``clear`` action ================ Deletes the history from the current session up until this point. Later commands diff --git a/news/history_pull.rst b/news/history_pull.rst new file mode 100644 index 0000000000..e3377fbf4a --- /dev/null +++ b/news/history_pull.rst @@ -0,0 +1,23 @@ +**Added:** + +* Added ``history pull`` command to SQLite history backend to pull the history from parallel sessions and add to the current session. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/xonsh/history/base.py b/xonsh/history/base.py index 323f7bef32..75b20a8cdf 100644 --- a/xonsh/history/base.py +++ b/xonsh/history/base.py @@ -130,6 +130,10 @@ def append(self, cmd): """ pass + def pull(self, **kwargs): + """Pull history from other parallel sessions.""" + raise NotImplementedError + def flush(self, **kwargs): """Flush the history items to disk from a buffer.""" pass diff --git a/xonsh/history/main.py b/xonsh/history/main.py index 6eac341cbe..efe0489515 100644 --- a/xonsh/history/main.py +++ b/xonsh/history/main.py @@ -281,6 +281,31 @@ def id_cmd(_stdout): return print(str(hist.sessionid), file=_stdout) + @staticmethod + def pull(show_commands=False, _stdout=None): + """Pull history from other parallel sessions. + + Parameters + ---------- + show_commands: -c, --show-commands + show pulled commands + """ + + hist = XSH.history + + if hist.pull.__module__ == "xonsh.history.base": + backend = XSH.env.get("XONSH_HISTORY_BACKEND", "unknown") + print( + f"Pull method is not supported in {backend} history backend.", + file=_stdout, + ) + + lines_added = hist.pull(show_commands) + if lines_added: + print(f"Added {lines_added} records!", file=_stdout) + else: + print(f"No records found!", file=_stdout) + @staticmethod def flush(_stdout): """Flush the current history to disk""" @@ -428,7 +453,7 @@ def transfer( dest.flush() - self.out("done.") + self.out("Done") def build(self): parser = self.create_parser(prog="history") @@ -436,6 +461,7 @@ def build(self): parser.add_command(self.id_cmd, prog="id") parser.add_command(self.file) parser.add_command(self.info) + parser.add_command(self.pull) parser.add_command(self.flush) parser.add_command(self.off) parser.add_command(self.on) diff --git a/xonsh/history/sqlite.py b/xonsh/history/sqlite.py index 1fa25d9265..5a3a14443a 100644 --- a/xonsh/history/sqlite.py +++ b/xonsh/history/sqlite.py @@ -204,6 +204,15 @@ def xh_sqlite_delete_items(size_to_keep, filename=None): return _xh_sqlite_delete_records(c, size_to_keep) +def xh_sqlite_pull(filename, last_pull_time, current_sessionid): + sql = f"SELECT inp FROM xonsh_history WHERE tsb > ? AND sessionid != ? ORDER BY tsb" + params = [last_pull_time, current_sessionid] + with _xh_sqlite_get_conn(filename=filename) as conn: + c = conn.cursor() + c.execute(sql, tuple(params)) + return c.fetchall() + + def xh_sqlite_wipe_session(sessionid=None, filename=None): """Wipe the current session's entries from the database.""" sql = "DELETE FROM xonsh_history WHERE sessionid = ?" @@ -256,6 +265,7 @@ def __init__(self, gc=True, filename=None, save_cwd=None, **kwargs): if filename is None: filename = _xh_sqlite_get_file_name() self.filename = filename + self.last_pull_time = time.time() self.gc = SqliteHistoryGC() if gc else None self._last_hist_inp = None self.inps = [] @@ -345,6 +355,22 @@ def info(self): data["gc options"] = envs.get("XONSH_HISTORY_SIZE") return data + def pull(self, show_commands=False): + if not hasattr(XSH.shell.shell, "prompter"): + print(f"Shell type {XSH.shell.shell} is not supported.") + return 0 + + cnt = 0 + for r in xh_sqlite_pull( + self.filename, self.last_pull_time, str(self.sessionid) + ): + if show_commands: + print(r[0]) + XSH.shell.shell.prompter.history.append_string(r[0]) + cnt += 1 + self.last_pull_time = time.time() + return cnt + def run_gc(self, size=None, blocking=True): self.gc = SqliteHistoryGC(wait_for_shell=False, size=size) if blocking: