From 1259fb9a30140347f071be07eee7041774a40afb Mon Sep 17 00:00:00 2001 From: Luke Hines Date: Sat, 18 Jul 2026 20:33:09 +0100 Subject: [PATCH] Scroll search matches clear of the top-docked search panel --- qml/pages/TerminalPage.qml | 7 +++++++ src/terminalview.h | 8 ++++++++ src/terminalview_search.cpp | 38 +++++++++++++++++++++++++++++++------ tests/stubs/terminalview.h | 7 +++++++ 4 files changed, 54 insertions(+), 6 deletions(-) diff --git a/qml/pages/TerminalPage.qml b/qml/pages/TerminalPage.qml index 6ef6d0d..0a94d21 100644 --- a/qml/pages/TerminalPage.qml +++ b/qml/pages/TerminalPage.qml @@ -1017,6 +1017,13 @@ Page { if (!open && terminal) { terminal.closeSearch() } + updateSearchPanelHeight() + } + onHeightChanged: updateSearchPanelHeight() + + function updateSearchPanelHeight() { + if (terminal) + terminal.searchPanelHeight = open ? height : 0 } Row { diff --git a/src/terminalview.h b/src/terminalview.h index ec0848c..bc05691 100644 --- a/src/terminalview.h +++ b/src/terminalview.h @@ -24,6 +24,7 @@ class TerminalView : public QQuickItem Q_PROPERTY(QString selectedText READ selectedText NOTIFY selectedTextChanged) Q_PROPERTY(int searchMatchCount READ searchMatchCount NOTIFY searchMatchCountChanged) Q_PROPERTY(int currentMatchIndex READ currentMatchIndex NOTIFY currentMatchIndexChanged) + Q_PROPERTY(int searchPanelHeight READ searchPanelHeight WRITE setSearchPanelHeight NOTIFY searchPanelHeightChanged) Q_PROPERTY(int topPadding READ topPadding WRITE setTopPadding NOTIFY topPaddingChanged) Q_PROPERTY(QColor selectionHighlightColor READ selectionHighlightColor WRITE setSelectionHighlightColor NOTIFY selectionHighlightColorChanged) Q_PROPERTY(QColor selectionHandleColor READ selectionHandleColor WRITE setSelectionHandleColor NOTIFY selectionHandleColorChanged) @@ -58,6 +59,11 @@ class TerminalView : public QQuickItem int cellWidth() const { return m_cellWidth; } int cellHeight() const { return m_cellHeight; } + // Height of the top-docked search panel (0 when closed). Consumed by + // scrollToMatch() to exclude rows hidden behind it from the visible range. + int searchPanelHeight() const { return m_searchPanelHeight; } + void setSearchPanelHeight(int height); + // Session-swipe gate. False when only one session exists so the classifier // never kills the long-press (text-selection) timer for a gesture QML // would reject anyway. Bound from QML via SessionManager.sessionCount. @@ -152,6 +158,7 @@ class TerminalView : public QQuickItem void linkActivated(const QString &uri); void searchMatchCountChanged(); void currentMatchIndexChanged(); + void searchPanelHeightChanged(); void selectionHighlightColorChanged(); void selectionHandleColorChanged(); void selectionHandleBorderColorChanged(); @@ -354,6 +361,7 @@ private slots: QVector> m_cellMapping; // Per row: cell index → character index offset QVector m_searchMatches; int m_currentMatchIndex = -1; + int m_searchPanelHeight = 0; // --- Link detection (OSC 8 hyperlinks + regex URL scanning) --- QVector m_currentLinks; // Cached regex-detected links for viewport diff --git a/src/terminalview_search.cpp b/src/terminalview_search.cpp index 6dd5a88..2413076 100644 --- a/src/terminalview_search.cpp +++ b/src/terminalview_search.cpp @@ -170,6 +170,21 @@ void TerminalView::performSearch() Q_EMIT currentMatchIndexChanged(); } +void TerminalView::setSearchPanelHeight(int height) +{ + if (m_searchPanelHeight == height) + return; + m_searchPanelHeight = height; + Q_EMIT searchPanelHeightChanged(); + + // Panel height changed mid-search (e.g. navButtons appeared after the + // first match resolved). Re-run the scroll so the current match lands + // below the now-current panel size instead of the stale value used at + // setSearchPattern time. + if (m_searchActive && m_currentMatchIndex >= 0) + scrollToMatch(m_currentMatchIndex); +} + void TerminalView::scrollToMatch(int index) { if (index < 0 || index >= m_searchMatches.size() || !m_vt || !m_vt->terminal()) @@ -180,16 +195,27 @@ void TerminalView::scrollToMatch(int index) GhosttyTerminalScrollbar scrollbar = {}; ghostty_terminal_get(m_vt->terminal(), GHOSTTY_TERMINAL_DATA_SCROLLBAR, &scrollbar); - int matchRow = match.row; - int viewTop = static_cast(scrollbar.offset); - int viewLen = static_cast(scrollbar.len); + const int matchRow = match.row; + const int viewTop = static_cast(scrollbar.offset); + const int viewLen = static_cast(scrollbar.len); + + // The top-docked search panel overlays the terminal in QML, hiding the + // topmost rows even though ghostty still reports them as on-screen — + // exclude them from the visible range below. + const int obscuredRows = (m_searchPanelHeight > 0 && m_cellHeight > 0) + ? (m_searchPanelHeight + m_cellHeight - 1) / m_cellHeight + : 0; + const int visibleTop = viewTop + obscuredRows; - if (viewLen > 0 && matchRow >= viewTop && matchRow < viewTop + viewLen) + if (viewLen > 0 && matchRow >= visibleTop && matchRow < viewTop + viewLen) return; - // Center the match row in the viewport. + // Center the match in the viewport; when the panel covers more than half + // the viewport, place the match just below the panel so the very overlay + // we're compensating for doesn't re-hide it. const int halfView = viewLen / 2; - const int targetTop = std::max(0, matchRow - halfView); + const int desiredOffset = std::max(halfView, obscuredRows); + const int targetTop = std::max(0, matchRow - desiredOffset); const int delta = targetTop - viewTop; if (delta != 0) { diff --git a/tests/stubs/terminalview.h b/tests/stubs/terminalview.h index 7f2b4d1..578de50 100644 --- a/tests/stubs/terminalview.h +++ b/tests/stubs/terminalview.h @@ -18,6 +18,7 @@ class TerminalView : public QObject Q_PROPERTY(QString selectedText READ selectedText NOTIFY selectedTextChanged) Q_PROPERTY(int searchMatchCount READ searchMatchCount NOTIFY searchMatchCountChanged) Q_PROPERTY(int currentMatchIndex READ currentMatchIndex NOTIFY currentMatchIndexChanged) + Q_PROPERTY(int searchPanelHeight READ searchPanelHeight WRITE setSearchPanelHeight NOTIFY searchPanelHeightChanged) Q_PROPERTY(QColor selectionHighlightColor READ selectionHighlightColor WRITE setSelectionHighlightColor NOTIFY selectionHighlightColorChanged) Q_PROPERTY(QColor selectionHandleColor READ selectionHandleColor WRITE setSelectionHandleColor NOTIFY selectionHandleColorChanged) Q_PROPERTY(QColor selectionHandleBorderColor READ selectionHandleBorderColor WRITE setSelectionHandleBorderColor NOTIFY selectionHandleBorderColorChanged) @@ -47,6 +48,10 @@ class TerminalView : public QObject int searchMatchCount() const { return 0; } int currentMatchIndex() const { return m_currentMatchIndex; } bool searchActive() const { return m_searchActive; } + int searchPanelHeight() const { return m_searchPanelHeight; } + void setSearchPanelHeight(int height) { + if (m_searchPanelHeight != height) { m_searchPanelHeight = height; Q_EMIT searchPanelHeightChanged(); } + } QColor selectionHighlightColor() const { return m_selectionHighlightColor; } void setSelectionHighlightColor(const QColor &color) { @@ -141,6 +146,7 @@ class TerminalView : public QObject void selectedTextChanged(); void searchMatchCountChanged(); void currentMatchIndexChanged(); + void searchPanelHeightChanged(); void selectionHighlightColorChanged(); void selectionHandleColorChanged(); void selectionHandleBorderColorChanged(); @@ -178,6 +184,7 @@ class TerminalView : public QObject int m_currentMatchIndex = -1; bool m_searchActive = false; + int m_searchPanelHeight = 0; QColor m_selectionHighlightColor = QColor(255, 255, 255, 76); QColor m_selectionHandleColor = QColor(255, 255, 255, 200);