From 08e11d04ef1150e5b4a862bf82179e2196daa850 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Fri, 31 Jul 2026 11:25:52 +0200 Subject: feat: add Ctrl+N and Ctrl+F shortcuts Ctrl+N starts a fresh conversation and puts the cursor in the input box. Ctrl+F jumps to the search field. The search field sits in the top bar, but its results render in the history panel, so focusing it reveals a hidden panel rather than leaving the search with nowhere to show its hits. Escape now backs out of the search field before it hides the window: first press clears the text, second returns to the input, third hides. Previously a stray Escape while filtering dismissed the whole window. That check reads focusWidget() rather than hasFocus(), so it still holds when the window is not the active one. Co-Authored-By: Claude Opus 5 --- test_llamachat.py | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) (limited to 'test_llamachat.py') diff --git a/test_llamachat.py b/test_llamachat.py index ae2b478..0986631 100755 --- a/test_llamachat.py +++ b/test_llamachat.py @@ -654,6 +654,113 @@ def test_sidebar_toggle(): print("ok sidebar toggle") +def test_shortcuts(): + """Ctrl+N, Ctrl+F and Escape behave as advertised.""" + os.environ.setdefault("QT_QPA_PLATFORM", "offscreen") + from PySide6.QtCore import QEvent, Qt + from PySide6.QtGui import QKeyEvent + from PySide6.QtWidgets import QApplication + + from llamachat import backend as _backend + from llamachat import config as _config + from llamachat.ui import ChatWindow + + app = QApplication.instance() or QApplication([]) + + with tempfile.TemporaryDirectory() as tmp: + cfg = _config.load(Path(tmp) / "config.toml") + cfg.prompts_dir = Path(tmp) / "prompts" + cfg.db_path = Path(tmp) / "t.db" + history = db.History(cfg.db_path) + window = ChatWindow( + cfg, + history, + _backend.Client(cfg.base_url), + _config.parse_presets(cfg.presets_path), + ) + window.resize(1000, 700) + window.show() + window.raise_() + window.activateWindow() + # The offscreen platform only grants focus to the active window, and + # a window left over from an earlier check can still hold it. + window.setFocus() + app.processEvents() + + registered = { + a.shortcut().toString() + for a in window.actions() + if not a.shortcut().isEmpty() + } + for wanted in ("Ctrl+N", "Ctrl+F", "Ctrl+\\"): + assert wanted in registered, (wanted, registered) + + # Ctrl+F puts the cursor in the search box. The offscreen platform + # only grants real focus to one window per process, so check where + # focus was directed rather than whether the platform granted it. + window.input.setFocus() + app.processEvents() + window.focus_search() + app.processEvents() + assert window.focusWidget() is window.search_box, window.focusWidget() + + # The search box lives in the top bar, so hiding the history panel + # must not take it away. + window.sidebar_button.setChecked(False) + app.processEvents() + assert not window.sidebar_visible() + assert window.search_box.isVisible() + + # Its results render in the panel, so focusing reveals the panel. + window.focus_search() + app.processEvents() + assert window.sidebar_visible() + + def press_escape() -> None: + window.keyPressEvent( + QKeyEvent(QEvent.KeyPress, Qt.Key_Escape, Qt.NoModifier) + ) + app.processEvents() + + # Escape backs out of the search box before hiding the window, so a + # stray press while filtering does not dismiss everything. + window.search_box.setFocus() + window.search_box.setText("otters") + app.processEvents() + assert window.focusWidget() is window.search_box + press_escape() + assert window.search_box.text() == "" + assert window.isVisible() + press_escape() + assert window.focusWidget() is window.input + assert window.isVisible() + press_escape() + assert not window.isVisible() + + # Ctrl+N clears the conversation and puts the cursor in the input. + window.show() + window.raise_() + window.activateWindow() + # The offscreen platform only grants focus to the active window, and + # a window left over from an earlier check can still hold it. + window.setFocus() + app.processEvents() + session = history.create_session("chat", "m", "old") + history.add_message(session, "user", "something") + window.open_session(session) + assert window.session_id == session + window.search_box.setFocus() + window.new_session() + app.processEvents() + assert window.session_id is None + assert window.transcript.toPlainText().strip() == "" + assert window.focusWidget() is window.input + + window.close() + history.close() + print("ok shortcuts") + + def test_version_matches_changelog(): """The package version must be the newest release in the changelog.""" import re @@ -758,6 +865,7 @@ if __name__ == "__main__": test_prompt_column_migration() test_markdown_rendering() test_sidebar_toggle() + test_shortcuts() test_system_qt_theme_guard() test_version_matches_changelog() test_venv_discovery() -- cgit v1.2.3