From 47ed27d397cf0816aeba6a46531c337487b908ca Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 24 Nov 2023 00:54:57 +0100 Subject: [PATCH 1/3] this might be useful --- src/prompt_toolkit/history.py | 58 ++++++++++++++++++++++++++- src/prompt_toolkit/layout/controls.py | 4 +- tests/test_history.py | 34 +++++++++++++++- 3 files changed, 92 insertions(+), 4 deletions(-) diff --git a/src/prompt_toolkit/history.py b/src/prompt_toolkit/history.py index 553918e58..844b62665 100644 --- a/src/prompt_toolkit/history.py +++ b/src/prompt_toolkit/history.py @@ -256,11 +256,13 @@ def append_string(self, string: str) -> None: class FileHistory(History): """ - :class:`.History` class that stores all strings in a file. + :class:`.History` class that stores all strings in a file. You can optionally specify the + maximum amount of initially loaded commands. """ - def __init__(self, filename: str) -> None: + def __init__(self, filename: str, max_loaded_history: None | int = None) -> None: self.filename = filename + self.max_loaded_history = max_loaded_history super().__init__() def load_history_strings(self) -> Iterable[str]: @@ -287,6 +289,8 @@ def add() -> None: add() + if self.max_loaded_history is not None: + return list(reversed(strings))[: len(strings) - self.max_loaded_history] # Reverse the order, because newest items have to go first. return reversed(strings) @@ -300,3 +304,53 @@ def write(t: str) -> None: write("\n# %s\n" % datetime.datetime.now()) for line in string.split("\n"): write("+%s\n" % line) + + +class BoundedFileHistory(FileHistory): + """ + :class:`.History` class that stores all strings in a file but also limits the total number of + contained history items. The file will be re-written with the specified bound number as the + number of most recent history items when re-loading the history strings. + """ + + def __init__(self, filename: str, bound: int) -> None: + self.bound = bound + super().__init__(filename) + + def load_history_strings(self) -> Iterable[str]: + strings: list[str] = [] + date_lines: list[bytes] = [] + lines: list[str] = [] + + def add() -> None: + if lines: + # Join and drop trailing newline. + string = "".join(lines)[:-1] + + strings.append(string) + + if os.path.exists(self.filename): + with open(self.filename, "rb") as f: + for line_bytes in f: + line = line_bytes.decode("utf-8", errors="replace") + if line.startswith("+"): + lines.append(line[1:]) + else: + if line.startswith("#"): + date_lines.append(line_bytes) + add() + lines = [] + + add() + + if len(strings) > self.bound: + assert len(date_lines) == len(strings) + # Reverse the order, because newest items have to go first. + list_of_strings = list(reversed(strings))[: self.bound] + # Re-write the truncated file. + with open(self.filename, "wb") as f: + for date_str, string in zip(date_lines, strings): + f.write(date_str) + f.write(f"{string}\n".encode()) + return list_of_strings + return reversed(strings) diff --git a/src/prompt_toolkit/layout/controls.py b/src/prompt_toolkit/layout/controls.py index c30c0effa..5fa1024f0 100644 --- a/src/prompt_toolkit/layout/controls.py +++ b/src/prompt_toolkit/layout/controls.py @@ -477,7 +477,9 @@ def create_content(self, width: int, height: int) -> UIContent: def get_line(i: int) -> StyleAndTextTuples: return [] - return UIContent(get_line=get_line, line_count=100**100) # Something very big. + return UIContent( + get_line=get_line, line_count=100**100 + ) # Something very big. def is_focusable(self) -> bool: return False diff --git a/tests/test_history.py b/tests/test_history.py index 500b7f119..0ffd1774e 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -2,7 +2,12 @@ from asyncio import run -from prompt_toolkit.history import FileHistory, InMemoryHistory, ThreadedHistory +from prompt_toolkit.history import ( + BoundedFileHistory, + FileHistory, + InMemoryHistory, + ThreadedHistory, +) def _call_history_load(history): @@ -60,6 +65,33 @@ def test_file_history(tmpdir): assert _call_history_load(history2) == ["test3", "world", "hello"] +def test_bounded_file_history(tmpdir): + histfile = tmpdir.join("history") + + history = BoundedFileHistory(histfile, bound=4) + + history.append_string("hello") + history.append_string("world") + + # Newest should yield first. + assert _call_history_load(history) == ["world", "hello"] + + # Test another call. + assert _call_history_load(history) == ["world", "hello"] + + history.append_string("test3") + assert _call_history_load(history) == ["test3", "world", "hello"] + history.append_string("test4") + assert _call_history_load(history) == ["test4", "test3", "world", "hello"] + history.append_string("test5") + # In-memory history still can contain more files. + assert _call_history_load(history) == ["test5", "test4", "test3", "world", "hello"] + + # The newly loaded history will now only get four files. + new_history = BoundedFileHistory(histfile, bound=4) + assert _call_history_load(new_history) == ["test5", "test4", "test3", "world"] + + def test_threaded_file_history(tmpdir): histfile = tmpdir.join("history") From 5333b1fa9555574f7bc1d2bad434990ad947c9c6 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 24 Nov 2023 09:38:36 +0100 Subject: [PATCH 2/3] why does it change this? --- src/prompt_toolkit/layout/controls.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/prompt_toolkit/layout/controls.py b/src/prompt_toolkit/layout/controls.py index 5fa1024f0..c30c0effa 100644 --- a/src/prompt_toolkit/layout/controls.py +++ b/src/prompt_toolkit/layout/controls.py @@ -477,9 +477,7 @@ def create_content(self, width: int, height: int) -> UIContent: def get_line(i: int) -> StyleAndTextTuples: return [] - return UIContent( - get_line=get_line, line_count=100**100 - ) # Something very big. + return UIContent(get_line=get_line, line_count=100**100) # Something very big. def is_focusable(self) -> bool: return False From c333f85d11dac4c2b5ae21a98bf2912ca40a5a75 Mon Sep 17 00:00:00 2001 From: Robin Mueller Date: Fri, 24 Nov 2023 15:33:20 +0100 Subject: [PATCH 3/3] remove some old stuff --- src/prompt_toolkit/history.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/prompt_toolkit/history.py b/src/prompt_toolkit/history.py index 844b62665..3f1b69249 100644 --- a/src/prompt_toolkit/history.py +++ b/src/prompt_toolkit/history.py @@ -260,9 +260,8 @@ class FileHistory(History): maximum amount of initially loaded commands. """ - def __init__(self, filename: str, max_loaded_history: None | int = None) -> None: + def __init__(self, filename: str) -> None: self.filename = filename - self.max_loaded_history = max_loaded_history super().__init__() def load_history_strings(self) -> Iterable[str]: @@ -289,8 +288,6 @@ def add() -> None: add() - if self.max_loaded_history is not None: - return list(reversed(strings))[: len(strings) - self.max_loaded_history] # Reverse the order, because newest items have to go first. return reversed(strings)