From b07638083a44e18fba8c6c7c27ee93adaff3c8ca Mon Sep 17 00:00:00 2001 From: Yuki Date: Wed, 27 May 2026 20:59:48 +0800 Subject: [PATCH 01/35] wip --- TeXmacs/progs/generic/search-widgets.scm | 87 ++++++++- devel/1042.md | 54 ++++++ src/Plugins/Qt/qt_chat_controller.hpp | 3 + src/Plugins/Qt/qt_chat_tab_widget.hpp | 3 + src/Plugins/Qt/qt_floating_search_bar.cpp | 216 ++++++++++++++++++++++ src/Plugins/Qt/qt_floating_search_bar.hpp | 62 +++++++ src/Scheme/L5/glue_widget.lua | 8 + src/Scheme/L5/init_glue_l5.cpp | 1 + 8 files changed, 424 insertions(+), 10 deletions(-) create mode 100644 devel/1042.md create mode 100644 src/Plugins/Qt/qt_floating_search_bar.cpp create mode 100644 src/Plugins/Qt/qt_floating_search_bar.hpp diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index 0c281c7a28..ab71f7842e 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -1112,6 +1112,42 @@ ) ;when ) ;tm-define +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Chat tab search (floating search bar) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(define chat-tab-search-target #f) + +(define (chat-tab-search-init target-buf) + (set! chat-tab-search-target target-buf) + (let* ((aux (search-buffer))) + (buffer-set-master aux target-buf) + (set-search-window-state #t #t) + (with-buffer target-buf + (set-search-reference (cursor-path))) + (set-search-filter) + (set! search-filter-out? #f))) + +(tm-define (chat-tab-set-query text) + (when (and chat-tab-search-target (buffer-exists? (search-buffer))) + (buffer-set-body (search-buffer) `(document ,text)) + (with-buffer chat-tab-search-target + (perform-search*)))) + +(tm-define (chat-tab-search-next forward?) + (when chat-tab-search-target + (with-buffer chat-tab-search-target + (search-next-match forward? chat-tab-search-target)))) + +(tm-define (chat-tab-search-close) + (when chat-tab-search-target + (search-show-all) + (set! search-serial (+ search-serial 1)) + (with-buffer chat-tab-search-target + (cancel-alt-selection "alternate")) + (set-search-window-state #f #f) + (set! chat-tab-search-target #f))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Search and replace widget ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1626,19 +1662,50 @@ (define-preferences ("toolbar search" "on" noop) ("toolbar replace" "on" noop)) +(define (chat-message-buffer? buf) + (string-starts? (url->system buf) "tmfs://chat-message-")) + +(define (chat-input-buffer? buf) + (string-starts? (url->system buf) "tmfs://chat-input-")) + +(define (chat-buffer-session-id buf) + (with s (url->system buf) + (cond ((chat-message-buffer? buf) + (substring s (string-length "tmfs://chat-message-"))) + ((chat-input-buffer? buf) + (substring s (string-length "tmfs://chat-input-"))) + (else #f)))) + +(define (chat-message-buffer-has-content? msg-buf) + (and (buffer-exists? msg-buf) + (with body (buffer-get-body msg-buf) + (not (and (tm-func? body 'document 1) + (tree-empty? (tm-ref body 0))))))) + (tm-define (interactive-search) (:interactive #t) - (unless (string-starts? (url->system (current-buffer)) "tmfs:") - (set! search-replace-text - (cond ((in-math?) "Only search in math mode") - ((in-prog?) "Only search in Program mode") - ((in-graphics?) "Graphics mode cannot search") - (else "Only search in text mode") + (with buf (current-buffer) + (with sid (chat-buffer-session-id buf) + (cond + ((and sid + (chat-message-buffer-has-content? + (string->url (string-append "tmfs://chat-message-" sid)))) + (with msg-buf (string->url (string-append "tmfs://chat-message-" sid)) + (chat-tab-search-init msg-buf) + (qt-floating-search "true"))) + ((not (string-starts? (url->system buf) "tmfs:")) + (set! search-replace-text + (cond ((in-math?) "Only search in math mode") + ((in-prog?) "Only search in Program mode") + ((in-graphics?) "Graphics mode cannot search") + (else "Only search in text mode") + ) ;cond + ) ;set! + (set-boolean-preference "search-and-replace" #f) + (open-search)) ) ;cond - ) ;set! - (set-boolean-preference "search-and-replace" #f) - (open-search) - ) ;unless + ) ;with + ) ;with ) ;tm-define (tm-define (interactive-replace) diff --git a/devel/1042.md b/devel/1042.md new file mode 100644 index 0000000000..32cd479fa8 --- /dev/null +++ b/devel/1042.md @@ -0,0 +1,54 @@ +# [1042] Chat Tab 搜索功能 + +## 1 相关文档 +- [dddd.md](dddd.md) - 任务文档模板 +- [0228.md](0228.md) - 禁用 Chat Tab 搜索(本任务修复并替代) + +## 2 任务相关的代码文件 +- `TeXmacs/progs/generic/search-widgets.scm` - 搜索/替换入口函数 +- `src/Plugins/Qt/qt_chat_tab_widget.hpp` - Chat tab widget 头文件 +- `src/Plugins/Qt/qt_chat_tab_widget.cpp` - Chat tab widget 实现 + +## 3 如何测试 + +### 3.1 确定性测试(单元测试) +无单元测试,需手动验证。 + +### 3.2 非确定性测试(文档验证) +``` +1. 打开 Chat Tab,在 message buffer 中按 Cmd+F → 右上角出现悬浮搜索框 +2. 输入文本 → 高亮匹配项,显示匹配计数 +3. 点击上一个/下一个按钮 → 跳转到对应匹配 +4. 按 Esc 或点击关闭 → 搜索框消失,高亮清除 +5. 在 Chat Tab 的 input buffer 中按 Cmd+F → 自动定位到 message buffer 搜索 +6. 在 Chat Tab 中按 Cmd+H(替换) → 无反应 +7. 普通文档 Tab 中按 Cmd+F → 侧边栏搜索正常工作 +``` + +## 4 如何提交 + +提交前执行以下最少步骤: + +```bash +xmake b stem +gf fmt --changed-since=main +``` + +## 5 What +Chat Tab 的 message buffer 是嵌入式 TeXmacs widget,没有标准 `tm_window`,无法使用主窗口的 auxiliary-widget 侧边栏搜索机制。需要为 Chat Tab 实现独立的搜索功能。 + +1. 在 QTChatTabWidget 右上角实现 VSCode 风格的悬浮搜索栏(公共组件类) +2. 搜索栏包含:输入框、上一个/下一个按钮、关闭按钮、匹配计数 +3. Scheme 端新增 chat-tab 专用搜索初始化和导航函数 +4. 修改 `interactive-search` 在嵌入式 chat buffer 时使用新的搜索 UI +5. 修改 `interactive-replace` 在 chat tab 中禁用替换 + +## 6 Why +commit [0228] 通过 blanket `tmfs:` 检查禁用了所有 tmfs:// 缓冲区的搜索来避免 crash。用户需要在 Chat Tab 的消息输出框中搜索对话内容。Chat Tab 的嵌入式 buffer 不支持 auxiliary-widget 机制,需要独立的搜索 UI。 + +## 7 How +1. 实现一个可复用的悬浮搜索栏 Qt 组件(`QTMFloatingSearchBar`),包含输入框、导航按钮、关闭按钮、匹配计数标签 +2. 在 QTChatTabWidget 的 content 区域右上角放置该组件,初始隐藏 +3. Scheme 端新增 `chat-tab-search-init`、`chat-tab-perform-search`、`chat-tab-search-next`、`chat-tab-search-close` 函数 +4. C++ 按钮回调通过 `eval_scheme` 调用 Scheme 搜索函数 +5. `interactive-search` 检测到嵌入式 chat buffer 时调 C++ glue 显示搜索栏并初始化搜索 diff --git a/src/Plugins/Qt/qt_chat_controller.hpp b/src/Plugins/Qt/qt_chat_controller.hpp index 06d51c6482..66aa110500 100644 --- a/src/Plugins/Qt/qt_chat_controller.hpp +++ b/src/Plugins/Qt/qt_chat_controller.hpp @@ -133,6 +133,9 @@ class ChatController : public QObject { */ void destroyView (); +public: + QTChatTabWidget* view () const { return view_; } + private: QTChatTabWidget* view_= nullptr; ///< View 指针,由 createView 创建 ChatSessionManager sessionManager_; ///< 会话管理器 diff --git a/src/Plugins/Qt/qt_chat_tab_widget.hpp b/src/Plugins/Qt/qt_chat_tab_widget.hpp index 4a5e5ab74b..1d34b4f7a9 100644 --- a/src/Plugins/Qt/qt_chat_tab_widget.hpp +++ b/src/Plugins/Qt/qt_chat_tab_widget.hpp @@ -355,6 +355,9 @@ class QTChatTabWidget : public QWidget { void setSidebarVisible (bool visible); void setCloseSidebarButtonVisible (bool visible); + // ---- 供外部组件访问 ---- + QWidget* contentWidget () const { return contentWidget_; } + signals: void cancelRequested (const string& sessionId); void newChatRequested (); diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp new file mode 100644 index 0000000000..fbfac47923 --- /dev/null +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -0,0 +1,216 @@ + +/****************************************************************************** + * MODULE : qt_floating_search_bar.cpp + * DESCRIPTION: A VSCode-style floating search bar widget + * COPYRIGHT : (C) 2026 Mogan STEM + ****************************************************************************** + * This software falls under the GNU general public license version 3 or later. + * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE + * in the root directory or . + ******************************************************************************/ + +#include "qt_floating_search_bar.hpp" +#include "qt_chat_controller.hpp" +#include "qt_chat_tab_widget.hpp" +#include "qt_dpi_utils.hpp" +#include "qt_utilities.hpp" + +#include "s7_tm.hpp" + +#include +#include +#include +#include + +/****************************************************************************** + * QTMFloatingSearchBar + ******************************************************************************/ + +QTMFloatingSearchBar::QTMFloatingSearchBar (QWidget* parent) + : QWidget (parent), edit_ (nullptr), infoLbl_ (nullptr) { + setObjectName ("floating_search_bar"); + setWindowFlags (Qt::Widget); + setAttribute (Qt::WA_StyledBackground); + setFixedHeight (DpiUtils::scaled (32)); + + QHBoxLayout* lay= new QHBoxLayout (this); + lay->setContentsMargins (DpiUtils::scaled (4), 0, DpiUtils::scaled (4), 0); + lay->setSpacing (DpiUtils::scaled (2)); + + // 搜索输入框 + edit_= new QLineEdit (this); + edit_->setPlaceholderText (tr ("Search")); + edit_->setFixedHeight (DpiUtils::scaled (24)); + lay->addWidget (edit_, 1); + + // 上一个按钮 + QPushButton* prevBtn= new QPushButton (this); + prevBtn->setIcon (style ()->standardIcon (QStyle::SP_ArrowUp)); + prevBtn->setFixedSize (DpiUtils::scaled (24), DpiUtils::scaled (24)); + prevBtn->setToolTip (tr ("Previous (Shift+Enter)")); + lay->addWidget (prevBtn); + + // 下一个按钮 + QPushButton* nextBtn= new QPushButton (this); + nextBtn->setIcon (style ()->standardIcon (QStyle::SP_ArrowDown)); + nextBtn->setFixedSize (DpiUtils::scaled (24), DpiUtils::scaled (24)); + nextBtn->setToolTip (tr ("Next (Enter)")); + lay->addWidget (nextBtn); + + // 关闭按钮 + QPushButton* closeBtn= new QPushButton (this); + closeBtn->setIcon (style ()->standardIcon (QStyle::SP_DialogCloseButton)); + closeBtn->setFixedSize (DpiUtils::scaled (24), DpiUtils::scaled (24)); + closeBtn->setToolTip (tr ("Close (Esc)")); + lay->addWidget (closeBtn); + + // 匹配计数标签 + infoLbl_= new QLabel (this); + infoLbl_->setFixedHeight (DpiUtils::scaled (24)); + infoLbl_->setMinimumWidth (DpiUtils::scaled (40)); + lay->addWidget (infoLbl_); + + // 信号连接 + QObject::connect (edit_, &QLineEdit::textChanged, this, + &QTMFloatingSearchBar::queryChanged); + QObject::connect (nextBtn, &QPushButton::clicked, this, + &QTMFloatingSearchBar::findNextRequested); + QObject::connect (prevBtn, &QPushButton::clicked, this, + &QTMFloatingSearchBar::findPreviousRequested); + QObject::connect (closeBtn, &QPushButton::clicked, this, + &QTMFloatingSearchBar::closeRequested); + + hide (); +} + +void +QTMFloatingSearchBar::activate () { + show (); + raise (); + edit_->clear (); + infoLbl_->clear (); + edit_->setFocus (); +} + +QString +QTMFloatingSearchBar::queryText () const { + return edit_->text (); +} + +void +QTMFloatingSearchBar::setMatchInfo (const QString& info) { + infoLbl_->setText (info); +} + +void +QTMFloatingSearchBar::keyPressEvent (QKeyEvent* event) { + if (event->key () == Qt::Key_Escape) { + emit closeRequested (); + event->accept (); + } + else if (event->key () == Qt::Key_Return || event->key () == Qt::Key_Enter) { + if (event->modifiers () & Qt::ShiftModifier) emit findPreviousRequested (); + else emit findNextRequested (); + event->accept (); + } + else { + QWidget::keyPressEvent (event); + } +} + +/****************************************************************************** + * 浮动搜索栏管理(独立于 QTChatTabWidget) + ******************************************************************************/ + +static QTMFloatingSearchBar* g_search_bar = nullptr; +static QWidget* g_search_bar_parent= nullptr; + +// 事件过滤器:parent resize 时重新定位搜索栏 +class SearchBarResizeFilter : public QObject { +public: + SearchBarResizeFilter (QObject* parent) : QObject (parent) {} + +protected: + bool eventFilter (QObject* watched, QEvent* event) override { + if (event->type () == QEvent::Resize && g_search_bar && + g_search_bar->isVisible ()) { + QWidget* w= qobject_cast (watched); + if (w) { + int x= w->width () - g_search_bar->width () - DpiUtils::scaled (8); + int y= DpiUtils::scaled (4); + g_search_bar->move (x, y); + } + } + return QObject::eventFilter (watched, event); + } +}; + +static SearchBarResizeFilter* g_resize_filter= nullptr; + +static void +position_search_bar (QWidget* content) { + int x= content->width () - g_search_bar->width () - DpiUtils::scaled (8); + int y= DpiUtils::scaled (4); + g_search_bar->move (x, y); +} + +static void +connect_search_bar_signals (QTMFloatingSearchBar* bar) { + QObject::connect (bar, &QTMFloatingSearchBar::queryChanged, bar, + [bar] (const QString& text) { + eval_scheme ("(chat-tab-set-query " * + qt_scheme_quote (text) * ")"); + }); + QObject::connect (bar, &QTMFloatingSearchBar::findNextRequested, bar, + [] () { eval_scheme ("(chat-tab-search-next #t)"); }); + QObject::connect (bar, &QTMFloatingSearchBar::findPreviousRequested, bar, + [] () { eval_scheme ("(chat-tab-search-next #f)"); }); + QObject::connect (bar, &QTMFloatingSearchBar::closeRequested, bar, [] () { + eval_scheme ("(chat-tab-search-close)"); + if (g_search_bar) g_search_bar->hide (); + }); +} + +/****************************************************************************** + * Scheme 胶水函数 + ******************************************************************************/ + +void +qt_floating_search (string flag) { + ChatController* ctrl= get_chat_controller (); + if (!ctrl) return; + QTChatTabWidget* view= ctrl->view (); + if (!view) return; + QWidget* content= view->contentWidget (); + if (!content) return; + + if (flag == "true" || flag == "#t") { + // 按需创建搜索栏 + if (!g_search_bar || g_search_bar_parent != content) { + // 删除旧实例(parent 变了) + if (g_search_bar) { + delete g_search_bar; + g_search_bar= nullptr; + } + if (g_resize_filter) { + if (g_search_bar_parent) + g_search_bar_parent->removeEventFilter (g_resize_filter); + delete g_resize_filter; + g_resize_filter= nullptr; + } + + g_search_bar = new QTMFloatingSearchBar (content); + g_search_bar_parent= content; + g_resize_filter = new SearchBarResizeFilter (content); + content->installEventFilter (g_resize_filter); + + g_search_bar->setFixedWidth (DpiUtils::scaled (320)); + connect_search_bar_signals (g_search_bar); + } + g_search_bar->activate (); + position_search_bar (content); + } + else { + if (g_search_bar) g_search_bar->hide (); + } +} diff --git a/src/Plugins/Qt/qt_floating_search_bar.hpp b/src/Plugins/Qt/qt_floating_search_bar.hpp new file mode 100644 index 0000000000..ee28bd9f70 --- /dev/null +++ b/src/Plugins/Qt/qt_floating_search_bar.hpp @@ -0,0 +1,62 @@ + +/****************************************************************************** + * MODULE : qt_floating_search_bar.hpp + * DESCRIPTION: A VSCode-style floating search bar widget for TeXmacs + * COPYRIGHT : (C) 2026 Mogan STEM + ****************************************************************************** + * This software falls under the GNU general public license version 3 or later. + * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE + * in the root directory or . + ******************************************************************************/ + +#ifndef QT_FLOATING_SEARCH_BAR_HPP +#define QT_FLOATING_SEARCH_BAR_HPP + +#include +#include +#include +#include + +#include "string.hpp" + +/** + * 轻量级悬浮搜索栏,布局类似 VSCode 的 Ctrl+F 搜索框。 + * + * 布局: [搜索输入框] [上一个] [下一个] [关闭] [匹配计数] + * 通过信号通知宿主执行搜索操作。 + */ +class QTMFloatingSearchBar : public QWidget { + Q_OBJECT + +public: + QTMFloatingSearchBar (QWidget* parent= nullptr); + + /// 显示搜索栏,清空输入并聚焦。 + void activate (); + /// 获取当前搜索文本。 + QString queryText () const; + /// 设置匹配信息标签(如 "3/12" 或 "无结果")。 + void setMatchInfo (const QString& info); + +signals: + /// 搜索文本变化时触发(实时搜索)。 + void queryChanged (const QString& text); + /// 用户点击下一个或按 Enter 时触发。 + void findNextRequested (); + /// 用户点击上一个或按 Shift+Enter 时触发。 + void findPreviousRequested (); + /// 用户关闭搜索栏时触发。 + void closeRequested (); + +protected: + void keyPressEvent (QKeyEvent* event) override; + +private: + QLineEdit* edit_; ///< 搜索输入框 + QLabel* infoLbl_; ///< 匹配计数标签 +}; + +/// Scheme 胶水函数:显示 ("true"/"#t") 或隐藏悬浮搜索栏。 +void qt_floating_search (string flag); + +#endif // QT_FLOATING_SEARCH_BAR_HPP diff --git a/src/Scheme/L5/glue_widget.lua b/src/Scheme/L5/glue_widget.lua index 30cc2501e3..9231c149ef 100644 --- a/src/Scheme/L5/glue_widget.lua +++ b/src/Scheme/L5/glue_widget.lua @@ -526,6 +526,14 @@ function main() "int", "string" } + }, + { + scm_name = "qt-floating-search", + cpp_name = "qt_floating_search", + ret_type = "void", + arg_list = { + "string" + } } } } diff --git a/src/Scheme/L5/init_glue_l5.cpp b/src/Scheme/L5/init_glue_l5.cpp index 4bd3a48a7c..aae47442c5 100644 --- a/src/Scheme/L5/init_glue_l5.cpp +++ b/src/Scheme/L5/init_glue_l5.cpp @@ -29,6 +29,7 @@ #include "preferences.hpp" #include "promise.hpp" #include "qt_chat_controller.hpp" +#include "qt_floating_search_bar.hpp" #include "tm_debug.hpp" #include "tm_locale.hpp" #include "tree_observer.hpp" From a928811265e25f6cc033ea237b528349723d5827 Mon Sep 17 00:00:00 2001 From: Yuki Date: Wed, 27 May 2026 22:21:05 +0800 Subject: [PATCH 02/35] wip --- TeXmacs/misc/images/floating-search/down.svg | 12 ++ TeXmacs/misc/images/floating-search/up.svg | 12 ++ TeXmacs/misc/images/images.qrc | 3 + TeXmacs/progs/generic/search-widgets.scm | 32 ++-- src/Plugins/Qt/qt_floating_search_bar.cpp | 184 +++++++++++++------ src/Plugins/Qt/qt_floating_search_bar.hpp | 36 ++-- src/Scheme/L5/glue_widget.lua | 8 + 7 files changed, 197 insertions(+), 90 deletions(-) create mode 100644 TeXmacs/misc/images/floating-search/down.svg create mode 100644 TeXmacs/misc/images/floating-search/up.svg diff --git a/TeXmacs/misc/images/floating-search/down.svg b/TeXmacs/misc/images/floating-search/down.svg new file mode 100644 index 0000000000..c6115207a6 --- /dev/null +++ b/TeXmacs/misc/images/floating-search/down.svg @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/TeXmacs/misc/images/floating-search/up.svg b/TeXmacs/misc/images/floating-search/up.svg new file mode 100644 index 0000000000..95ef9e9fc9 --- /dev/null +++ b/TeXmacs/misc/images/floating-search/up.svg @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/TeXmacs/misc/images/images.qrc b/TeXmacs/misc/images/images.qrc index 1669da501a..3276c7b7e1 100644 --- a/TeXmacs/misc/images/images.qrc +++ b/TeXmacs/misc/images/images.qrc @@ -25,6 +25,9 @@ llm-chat/thinking.svg llm-chat/thinking-white.svg + floating-search/down.svg + floating-search/up.svg + ocr-button/left-align-white.svg ocr-button/middle-align-white.svg diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index ab71f7842e..f509cfc693 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -1117,25 +1117,35 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define chat-tab-search-target #f) +(define chat-tab-search-aux #f) (define (chat-tab-search-init target-buf) (set! chat-tab-search-target target-buf) - (let* ((aux (search-buffer))) + (let ((aux (search-buffer))) + (set! chat-tab-search-aux aux) (buffer-set-master aux target-buf) (set-search-window-state #t #t) (with-buffer target-buf (set-search-reference (cursor-path))) (set-search-filter) - (set! search-filter-out? #f))) + (set! search-filter-out? #f) + (qt-floating-search-init (url->string aux)) + (qt-floating-search "true") + (with-buffer target-buf + (perform-search*)))) -(tm-define (chat-tab-set-query text) - (when (and chat-tab-search-target (buffer-exists? (search-buffer))) - (buffer-set-body (search-buffer) `(document ,text)) +(define (chat-tab-perform-search) + (when (and chat-tab-search-target chat-tab-search-aux + (buffer-exists? chat-tab-search-aux)) (with-buffer chat-tab-search-target - (perform-search*)))) + (set-search-reference (cursor-path))) + (set-search-filter) + (with-buffer chat-tab-search-target + (perform-search)))) (tm-define (chat-tab-search-next forward?) - (when chat-tab-search-target + (when (and chat-tab-search-target chat-tab-search-aux) + (chat-tab-perform-search) (with-buffer chat-tab-search-target (search-next-match forward? chat-tab-search-target)))) @@ -1146,7 +1156,8 @@ (with-buffer chat-tab-search-target (cancel-alt-selection "alternate")) (set-search-window-state #f #f) - (set! chat-tab-search-target #f))) + (set! chat-tab-search-target #f) + (set! chat-tab-search-aux #f))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Search and replace widget @@ -1690,9 +1701,8 @@ ((and sid (chat-message-buffer-has-content? (string->url (string-append "tmfs://chat-message-" sid)))) - (with msg-buf (string->url (string-append "tmfs://chat-message-" sid)) - (chat-tab-search-init msg-buf) - (qt-floating-search "true"))) + (chat-tab-search-init + (string->url (string-append "tmfs://chat-message-" sid)))) ((not (string-starts? (url->system buf) "tmfs:")) (set! search-replace-text (cond ((in-math?) "Only search in math mode") diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index fbfac47923..3d392cdf46 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -14,65 +14,97 @@ #include "qt_chat_tab_widget.hpp" #include "qt_dpi_utils.hpp" #include "qt_utilities.hpp" +#include "qt_widget.hpp" #include "s7_tm.hpp" +#include "tm_window.hpp" +#include "widget.hpp" +#include + +#include #include -#include #include #include +using namespace moebius; + /****************************************************************************** * QTMFloatingSearchBar ******************************************************************************/ QTMFloatingSearchBar::QTMFloatingSearchBar (QWidget* parent) - : QWidget (parent), edit_ (nullptr), infoLbl_ (nullptr) { + : QWidget (parent) { setObjectName ("floating_search_bar"); setWindowFlags (Qt::Widget); setAttribute (Qt::WA_StyledBackground); - setFixedHeight (DpiUtils::scaled (32)); + setMinimumHeight (DpiUtils::scaled (44)); + + // 圆角、去边框、悬浮阴影 + QString btnStyle= QString ("QPushButton {" + " border: none;" + " border-radius: %1px;" + " background: transparent;" + "}" + "QPushButton:hover {" + " background: rgba(0,0,0,30);" + "}" + "QPushButton:pressed {" + " background: rgba(0,0,0,50);" + "}") + .arg (DpiUtils::scaled (12)); + setStyleSheet (QString ("#floating_search_bar {" + " background: #f3f3f3;" + " border: none;" + " border-radius: %1px;" + "}") + .arg (DpiUtils::scaled (4))); + QGraphicsDropShadowEffect* shadow= new QGraphicsDropShadowEffect (this); + shadow->setBlurRadius (DpiUtils::scaled (8)); + shadow->setOffset (0, DpiUtils::scaled (1)); + shadow->setColor (QColor (0, 0, 0, 30)); + setGraphicsEffect (shadow); - QHBoxLayout* lay= new QHBoxLayout (this); - lay->setContentsMargins (DpiUtils::scaled (4), 0, DpiUtils::scaled (4), 0); - lay->setSpacing (DpiUtils::scaled (2)); + layout_= new QHBoxLayout (this); + layout_->setContentsMargins (DpiUtils::scaled (6), DpiUtils::scaled (6), + DpiUtils::scaled (6), DpiUtils::scaled (6)); + layout_->setSpacing (DpiUtils::scaled (4)); - // 搜索输入框 - edit_= new QLineEdit (this); - edit_->setPlaceholderText (tr ("Search")); - edit_->setFixedHeight (DpiUtils::scaled (24)); - lay->addWidget (edit_, 1); + // 布局顺序: [输入区] [匹配计数] [上一个] [下一个] [关闭] + // 输入区由 setSearchInput 动态插入 index 0 + + // 匹配计数标签 + infoLbl_= new QLabel (this); + infoLbl_->setFixedHeight (DpiUtils::scaled (24)); + infoLbl_->setMinimumWidth (DpiUtils::scaled (80)); + infoLbl_->setAlignment (Qt::AlignCenter); + infoLbl_->setText (QString::fromUtf8 ("无匹配")); + layout_->addWidget (infoLbl_); // 上一个按钮 QPushButton* prevBtn= new QPushButton (this); - prevBtn->setIcon (style ()->standardIcon (QStyle::SP_ArrowUp)); + prevBtn->setIcon (QIcon (":floating-search/up.svg")); prevBtn->setFixedSize (DpiUtils::scaled (24), DpiUtils::scaled (24)); prevBtn->setToolTip (tr ("Previous (Shift+Enter)")); - lay->addWidget (prevBtn); + prevBtn->setStyleSheet (btnStyle); + layout_->addWidget (prevBtn); // 下一个按钮 QPushButton* nextBtn= new QPushButton (this); - nextBtn->setIcon (style ()->standardIcon (QStyle::SP_ArrowDown)); + nextBtn->setIcon (QIcon (":floating-search/down.svg")); nextBtn->setFixedSize (DpiUtils::scaled (24), DpiUtils::scaled (24)); nextBtn->setToolTip (tr ("Next (Enter)")); - lay->addWidget (nextBtn); + nextBtn->setStyleSheet (btnStyle); + layout_->addWidget (nextBtn); // 关闭按钮 QPushButton* closeBtn= new QPushButton (this); - closeBtn->setIcon (style ()->standardIcon (QStyle::SP_DialogCloseButton)); + closeBtn->setIcon (QIcon (":tabpage/close.svg")); closeBtn->setFixedSize (DpiUtils::scaled (24), DpiUtils::scaled (24)); closeBtn->setToolTip (tr ("Close (Esc)")); - lay->addWidget (closeBtn); - - // 匹配计数标签 - infoLbl_= new QLabel (this); - infoLbl_->setFixedHeight (DpiUtils::scaled (24)); - infoLbl_->setMinimumWidth (DpiUtils::scaled (40)); - lay->addWidget (infoLbl_); + closeBtn->setStyleSheet (btnStyle); + layout_->addWidget (closeBtn); - // 信号连接 - QObject::connect (edit_, &QLineEdit::textChanged, this, - &QTMFloatingSearchBar::queryChanged); QObject::connect (nextBtn, &QPushButton::clicked, this, &QTMFloatingSearchBar::findNextRequested); QObject::connect (prevBtn, &QPushButton::clicked, this, @@ -84,38 +116,26 @@ QTMFloatingSearchBar::QTMFloatingSearchBar (QWidget* parent) } void -QTMFloatingSearchBar::activate () { - show (); - raise (); - edit_->clear (); - infoLbl_->clear (); - edit_->setFocus (); -} - -QString -QTMFloatingSearchBar::queryText () const { - return edit_->text (); +QTMFloatingSearchBar::setSearchInput (QWidget* input) { + if (inputQW_) { + layout_->removeWidget (inputQW_); + delete inputQW_; + } + inputQW_= input; + layout_->insertWidget (0, input, 1); } void -QTMFloatingSearchBar::setMatchInfo (const QString& info) { - infoLbl_->setText (info); +QTMFloatingSearchBar::activate () { + show (); + raise (); + if (inputQW_) inputQW_->setFocus (); } void -QTMFloatingSearchBar::keyPressEvent (QKeyEvent* event) { - if (event->key () == Qt::Key_Escape) { - emit closeRequested (); - event->accept (); - } - else if (event->key () == Qt::Key_Return || event->key () == Qt::Key_Enter) { - if (event->modifiers () & Qt::ShiftModifier) emit findPreviousRequested (); - else emit findNextRequested (); - event->accept (); - } - else { - QWidget::keyPressEvent (event); - } +QTMFloatingSearchBar::setMatchInfo (int current, int total) { + if (total == 0) infoLbl_->setText (tr ("No matches")); + else infoLbl_->setText (tr ("%1 of %2").arg (current).arg (total)); } /****************************************************************************** @@ -156,11 +176,6 @@ position_search_bar (QWidget* content) { static void connect_search_bar_signals (QTMFloatingSearchBar* bar) { - QObject::connect (bar, &QTMFloatingSearchBar::queryChanged, bar, - [bar] (const QString& text) { - eval_scheme ("(chat-tab-set-query " * - qt_scheme_quote (text) * ")"); - }); QObject::connect (bar, &QTMFloatingSearchBar::findNextRequested, bar, [] () { eval_scheme ("(chat-tab-search-next #t)"); }); QObject::connect (bar, &QTMFloatingSearchBar::findPreviousRequested, bar, @@ -185,9 +200,8 @@ qt_floating_search (string flag) { if (!content) return; if (flag == "true" || flag == "#t") { - // 按需创建搜索栏 + // 按需创建浮动栏容器 if (!g_search_bar || g_search_bar_parent != content) { - // 删除旧实例(parent 变了) if (g_search_bar) { delete g_search_bar; g_search_bar= nullptr; @@ -204,9 +218,10 @@ qt_floating_search (string flag) { g_resize_filter = new SearchBarResizeFilter (content); content->installEventFilter (g_resize_filter); - g_search_bar->setFixedWidth (DpiUtils::scaled (320)); + g_search_bar->setFixedWidth (DpiUtils::scaled (360)); connect_search_bar_signals (g_search_bar); } + g_search_bar->activate (); position_search_bar (content); } @@ -214,3 +229,52 @@ qt_floating_search (string flag) { if (g_search_bar) g_search_bar->hide (); } } + +void +qt_floating_search_init (string aux_url_str) { + ChatController* ctrl= get_chat_controller (); + if (!ctrl) return; + QTChatTabWidget* view= ctrl->view (); + if (!view) return; + QWidget* content= view->contentWidget (); + if (!content) return; + + // 确保浮动栏容器存在 + if (!g_search_bar || g_search_bar_parent != content) { + if (g_search_bar) { + delete g_search_bar; + g_search_bar= nullptr; + } + if (g_resize_filter) { + if (g_search_bar_parent) + g_search_bar_parent->removeEventFilter (g_resize_filter); + delete g_resize_filter; + g_resize_filter= nullptr; + } + + g_search_bar = new QTMFloatingSearchBar (content); + g_search_bar_parent= content; + g_resize_filter = new SearchBarResizeFilter (content); + content->installEventFilter (g_resize_filter); + + g_search_bar->setFixedWidth (DpiUtils::scaled (420)); + connect_search_bar_signals (g_search_bar); + } + + // 创建 texmacs_input_widget 绑定到 search-buffer + url aux_url= url_system (aux_url_str); + tree doc (DOCUMENT, ""); + tree sty = compound ("style", tree (TUPLE, "generic")); + widget tw = texmacs_input_widget (doc, sty, aux_url); + QWidget* inputW= concrete (tw)->as_qwidget (); + if (inputW) { + inputW->setStyleSheet ("QWidget {" + " background: white;" + " border: 1px solid #d0d0d0;" + "}" + "QWidget:focus {" + " border: 1px solid #215a6a;" + "}"); + g_search_bar->setSearchInput (inputW); + } +} diff --git a/src/Plugins/Qt/qt_floating_search_bar.hpp b/src/Plugins/Qt/qt_floating_search_bar.hpp index ee28bd9f70..d7231cb3f0 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.hpp +++ b/src/Plugins/Qt/qt_floating_search_bar.hpp @@ -12,18 +12,19 @@ #ifndef QT_FLOATING_SEARCH_BAR_HPP #define QT_FLOATING_SEARCH_BAR_HPP +#include #include -#include #include #include #include "string.hpp" /** - * 轻量级悬浮搜索栏,布局类似 VSCode 的 Ctrl+F 搜索框。 + * 悬浮搜索栏容器。 * - * 布局: [搜索输入框] [上一个] [下一个] [关闭] [匹配计数] - * 通过信号通知宿主执行搜索操作。 + * 布局: [TeXmacs 输入区] [上一个] [下一个] [关闭] [匹配计数] + * 输入区是嵌入的 texmacs_input_widget,绑定到 search-buffer, + * 搜索逻辑与底部搜索面板完全一致。 */ class QTMFloatingSearchBar : public QWidget { Q_OBJECT @@ -31,32 +32,29 @@ class QTMFloatingSearchBar : public QWidget { public: QTMFloatingSearchBar (QWidget* parent= nullptr); - /// 显示搜索栏,清空输入并聚焦。 + /// 设置嵌入的 TeXmacs 搜索输入 widget。 + void setSearchInput (QWidget* input); + /// 显示搜索栏并聚焦输入区。 void activate (); - /// 获取当前搜索文本。 - QString queryText () const; - /// 设置匹配信息标签(如 "3/12" 或 "无结果")。 - void setMatchInfo (const QString& info); + /// 设置匹配信息(current=0, total=0 显示"无匹配")。 + void setMatchInfo (int current, int total); signals: - /// 搜索文本变化时触发(实时搜索)。 - void queryChanged (const QString& text); - /// 用户点击下一个或按 Enter 时触发。 void findNextRequested (); - /// 用户点击上一个或按 Shift+Enter 时触发。 void findPreviousRequested (); - /// 用户关闭搜索栏时触发。 void closeRequested (); -protected: - void keyPressEvent (QKeyEvent* event) override; - private: - QLineEdit* edit_; ///< 搜索输入框 - QLabel* infoLbl_; ///< 匹配计数标签 + QHBoxLayout* layout_ = nullptr; + QWidget* inputQW_= nullptr; ///< 嵌入的 TeXmacs 输入 QWidget + QLabel* infoLbl_= nullptr; ///< 匹配计数标签 }; /// Scheme 胶水函数:显示 ("true"/"#t") 或隐藏悬浮搜索栏。 void qt_floating_search (string flag); +/// Scheme 胶水函数:传入 search-buffer URL,创建 texmacs-input +/// 并嵌入浮动搜索栏。 +void qt_floating_search_init (string aux_url_str); + #endif // QT_FLOATING_SEARCH_BAR_HPP diff --git a/src/Scheme/L5/glue_widget.lua b/src/Scheme/L5/glue_widget.lua index 9231c149ef..5999d68cc9 100644 --- a/src/Scheme/L5/glue_widget.lua +++ b/src/Scheme/L5/glue_widget.lua @@ -534,6 +534,14 @@ function main() arg_list = { "string" } + }, + { + scm_name = "qt-floating-search-init", + cpp_name = "qt_floating_search_init", + ret_type = "void", + arg_list = { + "string" + } } } } From 72122f463176a8ce8b91da4e1eab2e357b83e0d2 Mon Sep 17 00:00:00 2001 From: Yuki Date: Wed, 27 May 2026 22:31:37 +0800 Subject: [PATCH 03/35] wip --- src/Plugins/Qt/qt_floating_search_bar.cpp | 217 ++++++++++++---------- src/Plugins/Qt/qt_floating_search_bar.hpp | 10 +- 2 files changed, 126 insertions(+), 101 deletions(-) diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index 3d392cdf46..b0de9b28be 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -18,17 +18,36 @@ #include "s7_tm.hpp" #include "tm_window.hpp" -#include "widget.hpp" #include #include #include #include -#include +#include using namespace moebius; +// ---- 尺寸常量(单位:逻辑像素,经 DpiUtils::scaled 缩放) ---- // +constexpr int kBarMinHeight= 64; +constexpr int kBarWidth = 420; +constexpr int kBarRadius = 4; +constexpr int kBarMargin = 6; +constexpr int kBarSpacing = 4; + +constexpr int kBtnSize = 24; +constexpr int kBtnRadius= 12; + +constexpr int kInfoHeight = 24; +constexpr int kInfoMinWidth= 80; + +constexpr int kShadowBlur = 8; +constexpr int kShadowOffsetY= 1; +constexpr int kShadowAlpha = 30; + +constexpr int kPosRightPad= 8; +constexpr int kPosTopPad = 4; + /****************************************************************************** * QTMFloatingSearchBar ******************************************************************************/ @@ -38,72 +57,89 @@ QTMFloatingSearchBar::QTMFloatingSearchBar (QWidget* parent) setObjectName ("floating_search_bar"); setWindowFlags (Qt::Widget); setAttribute (Qt::WA_StyledBackground); - setMinimumHeight (DpiUtils::scaled (44)); - - // 圆角、去边框、悬浮阴影 - QString btnStyle= QString ("QPushButton {" - " border: none;" - " border-radius: %1px;" - " background: transparent;" - "}" - "QPushButton:hover {" - " background: rgba(0,0,0,30);" - "}" - "QPushButton:pressed {" - " background: rgba(0,0,0,50);" - "}") - .arg (DpiUtils::scaled (12)); + setMinimumHeight (DpiUtils::scaled (kBarMinHeight)); + setStyleSheet (QString ("#floating_search_bar {" " background: #f3f3f3;" " border: none;" " border-radius: %1px;" "}") - .arg (DpiUtils::scaled (4))); + .arg (DpiUtils::scaled (kBarRadius))); + QGraphicsDropShadowEffect* shadow= new QGraphicsDropShadowEffect (this); - shadow->setBlurRadius (DpiUtils::scaled (8)); - shadow->setOffset (0, DpiUtils::scaled (1)); - shadow->setColor (QColor (0, 0, 0, 30)); + shadow->setBlurRadius (DpiUtils::scaled (kShadowBlur)); + shadow->setOffset (0, DpiUtils::scaled (kShadowOffsetY)); + shadow->setColor (QColor (0, 0, 0, kShadowAlpha)); setGraphicsEffect (shadow); - layout_= new QHBoxLayout (this); - layout_->setContentsMargins (DpiUtils::scaled (6), DpiUtils::scaled (6), - DpiUtils::scaled (6), DpiUtils::scaled (6)); - layout_->setSpacing (DpiUtils::scaled (4)); - - // 布局顺序: [输入区] [匹配计数] [上一个] [下一个] [关闭] - // 输入区由 setSearchInput 动态插入 index 0 + const QString btnStyle= QString ("QPushButton {" + " border: none;" + " border-radius: %1px;" + " background: transparent;" + "}" + "QPushButton:hover {" + " background: rgba(0,0,0,30);" + "}" + "QPushButton:pressed {" + " background: rgba(0,0,0,50);" + "}") + .arg (DpiUtils::scaled (kBtnRadius)); + + // 外层水平布局:左边 [输入区],右边 [按钮 + 匹配信息] + QHBoxLayout* mainLayout= new QHBoxLayout (this); + mainLayout->setContentsMargins ( + DpiUtils::scaled (kBarMargin), DpiUtils::scaled (kBarMargin), + DpiUtils::scaled (kBarMargin), DpiUtils::scaled (kBarMargin)); + mainLayout->setSpacing (DpiUtils::scaled (kBarSpacing)); + + // 左侧:输入区(由 setSearchInput 动态插入,占满左侧) + rowLayout_= new QHBoxLayout (); + rowLayout_->setSpacing (0); + + // 右侧:垂直布局 [按钮行] + [匹配信息] + QVBoxLayout* rightLayout= new QVBoxLayout (); + rightLayout->setSpacing (DpiUtils::scaled (4)); + + // 右侧上层:按钮行 + QHBoxLayout* btnRow= new QHBoxLayout (); + btnRow->setSpacing (DpiUtils::scaled (4)); - // 匹配计数标签 - infoLbl_= new QLabel (this); - infoLbl_->setFixedHeight (DpiUtils::scaled (24)); - infoLbl_->setMinimumWidth (DpiUtils::scaled (80)); - infoLbl_->setAlignment (Qt::AlignCenter); - infoLbl_->setText (QString::fromUtf8 ("无匹配")); - layout_->addWidget (infoLbl_); - - // 上一个按钮 QPushButton* prevBtn= new QPushButton (this); prevBtn->setIcon (QIcon (":floating-search/up.svg")); - prevBtn->setFixedSize (DpiUtils::scaled (24), DpiUtils::scaled (24)); + prevBtn->setFixedSize (DpiUtils::scaled (kBtnSize), + DpiUtils::scaled (kBtnSize)); prevBtn->setToolTip (tr ("Previous (Shift+Enter)")); prevBtn->setStyleSheet (btnStyle); - layout_->addWidget (prevBtn); + btnRow->addWidget (prevBtn); - // 下一个按钮 QPushButton* nextBtn= new QPushButton (this); nextBtn->setIcon (QIcon (":floating-search/down.svg")); - nextBtn->setFixedSize (DpiUtils::scaled (24), DpiUtils::scaled (24)); + nextBtn->setFixedSize (DpiUtils::scaled (kBtnSize), + DpiUtils::scaled (kBtnSize)); nextBtn->setToolTip (tr ("Next (Enter)")); nextBtn->setStyleSheet (btnStyle); - layout_->addWidget (nextBtn); + btnRow->addWidget (nextBtn); - // 关闭按钮 QPushButton* closeBtn= new QPushButton (this); closeBtn->setIcon (QIcon (":tabpage/close.svg")); - closeBtn->setFixedSize (DpiUtils::scaled (24), DpiUtils::scaled (24)); + closeBtn->setFixedSize (DpiUtils::scaled (kBtnSize), + DpiUtils::scaled (kBtnSize)); closeBtn->setToolTip (tr ("Close (Esc)")); closeBtn->setStyleSheet (btnStyle); - layout_->addWidget (closeBtn); + btnRow->addWidget (closeBtn); + + rightLayout->addLayout (btnRow); + + // 右侧下层:匹配信息 + infoLbl_= new QLabel (this); + infoLbl_->setFixedHeight (DpiUtils::scaled (kInfoHeight)); + infoLbl_->setAlignment (Qt::AlignCenter); + infoLbl_->setText (QString::fromUtf8 ("无匹配")); + rightLayout->addWidget (infoLbl_); + + // 组装:左输入(stretch=1) + 右面板 + mainLayout->addLayout (rowLayout_, 1); + mainLayout->addLayout (rightLayout); QObject::connect (nextBtn, &QPushButton::clicked, this, &QTMFloatingSearchBar::findNextRequested); @@ -118,11 +154,11 @@ QTMFloatingSearchBar::QTMFloatingSearchBar (QWidget* parent) void QTMFloatingSearchBar::setSearchInput (QWidget* input) { if (inputQW_) { - layout_->removeWidget (inputQW_); + rowLayout_->removeWidget (inputQW_); delete inputQW_; } inputQW_= input; - layout_->insertWidget (0, input, 1); + rowLayout_->insertWidget (0, input, 1); } void @@ -139,7 +175,7 @@ QTMFloatingSearchBar::setMatchInfo (int current, int total) { } /****************************************************************************** - * 浮动搜索栏管理(独立于 QTChatTabWidget) + * 浮动搜索栏管理 ******************************************************************************/ static QTMFloatingSearchBar* g_search_bar = nullptr; @@ -156,8 +192,9 @@ class SearchBarResizeFilter : public QObject { g_search_bar->isVisible ()) { QWidget* w= qobject_cast (watched); if (w) { - int x= w->width () - g_search_bar->width () - DpiUtils::scaled (8); - int y= DpiUtils::scaled (4); + int x= w->width () - g_search_bar->width () - + DpiUtils::scaled (kPosRightPad); + int y= DpiUtils::scaled (kPosTopPad); g_search_bar->move (x, y); } } @@ -168,9 +205,11 @@ class SearchBarResizeFilter : public QObject { static SearchBarResizeFilter* g_resize_filter= nullptr; static void -position_search_bar (QWidget* content) { - int x= content->width () - g_search_bar->width () - DpiUtils::scaled (8); - int y= DpiUtils::scaled (4); +position_search_bar () { + if (!g_search_bar || !g_search_bar_parent) return; + int x= g_search_bar_parent->width () - g_search_bar->width () - + DpiUtils::scaled (kPosRightPad); + int y= DpiUtils::scaled (kPosTopPad); g_search_bar->move (x, y); } @@ -186,6 +225,31 @@ connect_search_bar_signals (QTMFloatingSearchBar* bar) { }); } +/// 创建或重建浮动栏容器,确保 attach 到 content。 +static void +ensure_search_bar (QWidget* content) { + if (g_search_bar && g_search_bar_parent == content) return; + + if (g_search_bar) { + delete g_search_bar; + g_search_bar= nullptr; + } + if (g_resize_filter) { + if (g_search_bar_parent) + g_search_bar_parent->removeEventFilter (g_resize_filter); + delete g_resize_filter; + g_resize_filter= nullptr; + } + + g_search_bar = new QTMFloatingSearchBar (content); + g_search_bar_parent= content; + g_resize_filter = new SearchBarResizeFilter (content); + content->installEventFilter (g_resize_filter); + + g_search_bar->setFixedWidth (DpiUtils::scaled (kBarWidth)); + connect_search_bar_signals (g_search_bar); +} + /****************************************************************************** * Scheme 胶水函数 ******************************************************************************/ @@ -200,30 +264,9 @@ qt_floating_search (string flag) { if (!content) return; if (flag == "true" || flag == "#t") { - // 按需创建浮动栏容器 - if (!g_search_bar || g_search_bar_parent != content) { - if (g_search_bar) { - delete g_search_bar; - g_search_bar= nullptr; - } - if (g_resize_filter) { - if (g_search_bar_parent) - g_search_bar_parent->removeEventFilter (g_resize_filter); - delete g_resize_filter; - g_resize_filter= nullptr; - } - - g_search_bar = new QTMFloatingSearchBar (content); - g_search_bar_parent= content; - g_resize_filter = new SearchBarResizeFilter (content); - content->installEventFilter (g_resize_filter); - - g_search_bar->setFixedWidth (DpiUtils::scaled (360)); - connect_search_bar_signals (g_search_bar); - } - + ensure_search_bar (content); g_search_bar->activate (); - position_search_bar (content); + position_search_bar (); } else { if (g_search_bar) g_search_bar->hide (); @@ -239,27 +282,7 @@ qt_floating_search_init (string aux_url_str) { QWidget* content= view->contentWidget (); if (!content) return; - // 确保浮动栏容器存在 - if (!g_search_bar || g_search_bar_parent != content) { - if (g_search_bar) { - delete g_search_bar; - g_search_bar= nullptr; - } - if (g_resize_filter) { - if (g_search_bar_parent) - g_search_bar_parent->removeEventFilter (g_resize_filter); - delete g_resize_filter; - g_resize_filter= nullptr; - } - - g_search_bar = new QTMFloatingSearchBar (content); - g_search_bar_parent= content; - g_resize_filter = new SearchBarResizeFilter (content); - content->installEventFilter (g_resize_filter); - - g_search_bar->setFixedWidth (DpiUtils::scaled (420)); - connect_search_bar_signals (g_search_bar); - } + ensure_search_bar (content); // 创建 texmacs_input_widget 绑定到 search-buffer url aux_url= url_system (aux_url_str); diff --git a/src/Plugins/Qt/qt_floating_search_bar.hpp b/src/Plugins/Qt/qt_floating_search_bar.hpp index d7231cb3f0..6805bceb8f 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.hpp +++ b/src/Plugins/Qt/qt_floating_search_bar.hpp @@ -22,7 +22,9 @@ /** * 悬浮搜索栏容器。 * - * 布局: [TeXmacs 输入区] [上一个] [下一个] [关闭] [匹配计数] + * 布局: + * 上层: [TeXmacs 输入区] [上一个] [下一个] [关闭] + * 下层: [TeXmacs 输入区] [匹配计数] * 输入区是嵌入的 texmacs_input_widget,绑定到 search-buffer, * 搜索逻辑与底部搜索面板完全一致。 */ @@ -45,9 +47,9 @@ class QTMFloatingSearchBar : public QWidget { void closeRequested (); private: - QHBoxLayout* layout_ = nullptr; - QWidget* inputQW_= nullptr; ///< 嵌入的 TeXmacs 输入 QWidget - QLabel* infoLbl_= nullptr; ///< 匹配计数标签 + QHBoxLayout* rowLayout_= nullptr; ///< 上层水平布局(输入+按钮) + QWidget* inputQW_ = nullptr; ///< 嵌入的 TeXmacs 输入 QWidget + QLabel* infoLbl_ = nullptr; ///< 匹配计数标签 }; /// Scheme 胶水函数:显示 ("true"/"#t") 或隐藏悬浮搜索栏。 From 47b5b18f968089449ed0dca30561435b8b9a1743 Mon Sep 17 00:00:00 2001 From: Yuki Date: Wed, 27 May 2026 22:43:25 +0800 Subject: [PATCH 04/35] wip --- TeXmacs/progs/generic/search-widgets.scm | 10 +++++----- src/Plugins/Qt/qt_floating_search_bar.cpp | 16 ++++++++++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index f509cfc693..ae90a6df54 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -1130,9 +1130,7 @@ (set-search-filter) (set! search-filter-out? #f) (qt-floating-search-init (url->string aux)) - (qt-floating-search "true") - (with-buffer target-buf - (perform-search*)))) + (qt-floating-search "true"))) (define (chat-tab-perform-search) (when (and chat-tab-search-target chat-tab-search-aux @@ -1145,7 +1143,6 @@ (tm-define (chat-tab-search-next forward?) (when (and chat-tab-search-target chat-tab-search-aux) - (chat-tab-perform-search) (with-buffer chat-tab-search-target (search-next-match forward? chat-tab-search-target)))) @@ -1703,7 +1700,10 @@ (string->url (string-append "tmfs://chat-message-" sid)))) (chat-tab-search-init (string->url (string-append "tmfs://chat-message-" sid)))) - ((not (string-starts? (url->system buf) "tmfs:")) + ((string-starts? (url->system buf) "tmfs:") + ;; 其他 tmfs:// 缓冲区不支持搜索 + (noop)) + (else (set! search-replace-text (cond ((in-math?) "Only search in math mode") ((in-prog?) "Only search in Program mode") diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index b0de9b28be..cb9a8ef38f 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -246,6 +246,13 @@ ensure_search_bar (QWidget* content) { g_resize_filter = new SearchBarResizeFilter (content); content->installEventFilter (g_resize_filter); + // content 被 Qt 销毁时自动清理全局指针,避免悬空 + QObject::connect (content, &QObject::destroyed, [] () { + g_search_bar = nullptr; + g_search_bar_parent= nullptr; + g_resize_filter = nullptr; + }); + g_search_bar->setFixedWidth (DpiUtils::scaled (kBarWidth)); connect_search_bar_signals (g_search_bar); } @@ -285,10 +292,11 @@ qt_floating_search_init (string aux_url_str) { ensure_search_bar (content); // 创建 texmacs_input_widget 绑定到 search-buffer - url aux_url= url_system (aux_url_str); - tree doc (DOCUMENT, ""); - tree sty = compound ("style", tree (TUPLE, "generic")); - widget tw = texmacs_input_widget (doc, sty, aux_url); + url aux_url= url_system (aux_url_str); + tree doc (DOCUMENT, ""); + tree sty= compound ("style", tree (TUPLE, "generic")); + widget tw = texmacs_input_widget (doc, sty, aux_url); + if (is_nil (tw)) return; QWidget* inputW= concrete (tw)->as_qwidget (); if (inputW) { inputW->setStyleSheet ("QWidget {" From d86a6e9b4e11c2ff361ac9861e549a34d425d2ac Mon Sep 17 00:00:00 2001 From: Yuki Date: Wed, 27 May 2026 23:03:03 +0800 Subject: [PATCH 05/35] wip --- TeXmacs/progs/generic/search-widgets.scm | 113 +++++++++++++--------- src/Plugins/Qt/qt_floating_search_bar.cpp | 5 + src/Plugins/Qt/qt_floating_search_bar.hpp | 3 + src/Scheme/L5/glue_widget.lua | 9 ++ 4 files changed, 86 insertions(+), 44 deletions(-) diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index ae90a6df54..b5fc7a06ad 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -111,6 +111,15 @@ (when isreplace? (set! isreplace? #f) ) ;when + ;; 更新浮动搜索栏的匹配计数 + (when chat-tab-search-target + (if (== index-str "") + (qt-floating-search-set-match-info 0 0) + (let* ((parts (string-split index-str #\/)) + (cur (string->number (car parts))) + (tot (string->number (cadr parts)))) + (when (and cur tot) + (qt-floating-search-set-match-info cur tot))))) (if (== index-str "") (set-auxiliary-widget-title (translate search-replace-text)) (set-auxiliary-widget-title (string-append (translate search-replace-text) " (" index-str ")") @@ -158,20 +167,24 @@ ;; ---- ;; 此函数用于管理搜索辅助缓冲区的生命周期,确保每个主文档视图有唯一的搜索缓冲区。 (tm-define (search-buffer) - (with u - (current-buffer) - (if (and (url-rooted-tmfs? u) - (== (url-head (url-head u)) (string->url "tmfs://aux/search")) - ) ;and - u - (string->url (string-append "tmfs://aux/search/" - (md5 (url->string (current-view-url))) - "/" - (url->string (url-tail (current-window))) - ) ;string-append - ) ;string->url - ) ;if - ) ;with + ;; chat tab 搜索激活期间,直接返回保存的 aux buffer + (if chat-tab-search-active? + chat-tab-search-aux + (with u + (current-buffer) + (if (and (url-rooted-tmfs? u) + (== (url-head (url-head u)) (string->url "tmfs://aux/search")) + ) ;and + u + (string->url (string-append "tmfs://aux/search/" + (md5 (url->string (current-view-url))) + "/" + (url->string (url-tail (current-window))) + ) ;string-append + ) ;string->url + ) ;if + ) ;with + ) ;if ) ;tm-define ;; replace-buffer @@ -329,29 +342,36 @@ ) ;tm-define (tm-define (master-buffer) - (and (buffer-exists? (search-buffer)) - (with mas - (buffer-get-master (search-buffer)) - (cond ((nnull? (buffer->windows mas)) mas) - ((in? search-window (window-list)) - (buffer-set-master (search-buffer) (window->buffer search-window)) - (with-buffer (buffer-get-master (search-buffer)) - (set-search-reference (cursor-path)) - (set-search-filter) - ) ;with-buffer - (master-buffer) - ) ; - ((nnull? (window-list)) - (set! search-window (car (window-list))) - (master-buffer) - ) ; - (else #f) - ) ;cond - ) ;with - ) ;and + ;; chat tab 搜索激活期间,直接返回保存的 target buffer + (if chat-tab-search-active? + chat-tab-search-target + (and (buffer-exists? (search-buffer)) + (with mas + (buffer-get-master (search-buffer)) + (cond ((nnull? (buffer->windows mas)) mas) + ((in? search-window (window-list)) + (buffer-set-master (search-buffer) (window->buffer search-window)) + (with-buffer (buffer-get-master (search-buffer)) + (set-search-reference (cursor-path)) + (set-search-filter) + ) ;with-buffer + (master-buffer) + ) ; + ((nnull? (window-list)) + (set! search-window (car (window-list))) + (master-buffer) + ) ; + (else #f) + ) ;cond + ) ;with + ) ;and + ) ;if ) ;tm-define -(tm-define (inside-search-buffer?) (== (current-buffer) (search-buffer))) +(tm-define (inside-search-buffer?) + (if chat-tab-search-active? + (== (current-buffer) chat-tab-search-aux) + (== (current-buffer) (search-buffer)))) (tm-define (inside-replace-buffer?) (== (current-buffer) (replace-buffer))) @@ -418,15 +438,17 @@ (define (accept-search-result? p) (or (== (get-init "mode") "src") - (let* ((buf (buffer-tree)) - (rel (path-strip (cDr p) (tree->path buf))) - (initial (cons 'attr (get-main-attrs get-init))) - (old-env (get-search-filter)) - (new-env (tree-descendant-env* buf rel initial)) - ) ; - ;; (display* p " ~> " new-env "\n") - (check-same? (tm-children new-env) (tm-children old-env)) - ) ;let* + (catch #t + (lambda () + (let* ((buf (buffer-tree)) + (rel (path-strip (cDr p) (tree->path buf)))) + (if (not rel) #t + (let* ((initial (cons 'attr (get-main-attrs get-init))) + (old-env (get-search-filter)) + (new-env (tree-descendant-env* buf rel initial))) + (if (not new-env) #t + (check-same? (tm-children new-env) (tm-children old-env))))))) + (lambda (key msg . rest) #t)) ) ;or ) ;define @@ -1118,11 +1140,13 @@ (define chat-tab-search-target #f) (define chat-tab-search-aux #f) +(define chat-tab-search-active? #f) (define (chat-tab-search-init target-buf) (set! chat-tab-search-target target-buf) (let ((aux (search-buffer))) (set! chat-tab-search-aux aux) + (set! chat-tab-search-active? #t) (buffer-set-master aux target-buf) (set-search-window-state #t #t) (with-buffer target-buf @@ -1153,6 +1177,7 @@ (with-buffer chat-tab-search-target (cancel-alt-selection "alternate")) (set-search-window-state #f #f) + (set! chat-tab-search-active? #f) (set! chat-tab-search-target #f) (set! chat-tab-search-aux #f))) diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index cb9a8ef38f..f3691b9726 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -309,3 +309,8 @@ qt_floating_search_init (string aux_url_str) { g_search_bar->setSearchInput (inputW); } } + +void +qt_floating_search_set_match_info (int current, int total) { + if (g_search_bar) g_search_bar->setMatchInfo (current, total); +} diff --git a/src/Plugins/Qt/qt_floating_search_bar.hpp b/src/Plugins/Qt/qt_floating_search_bar.hpp index 6805bceb8f..939a8ff256 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.hpp +++ b/src/Plugins/Qt/qt_floating_search_bar.hpp @@ -59,4 +59,7 @@ void qt_floating_search (string flag); /// 并嵌入浮动搜索栏。 void qt_floating_search_init (string aux_url_str); +/// Scheme 胶水函数:更新浮动搜索栏的匹配计数显示。 +void qt_floating_search_set_match_info (int current, int total); + #endif // QT_FLOATING_SEARCH_BAR_HPP diff --git a/src/Scheme/L5/glue_widget.lua b/src/Scheme/L5/glue_widget.lua index 5999d68cc9..fe6b3735a4 100644 --- a/src/Scheme/L5/glue_widget.lua +++ b/src/Scheme/L5/glue_widget.lua @@ -542,6 +542,15 @@ function main() arg_list = { "string" } + }, + { + scm_name = "qt-floating-search-set-match-info", + cpp_name = "qt_floating_search_set_match_info", + ret_type = "void", + arg_list = { + "int", + "int" + } } } } From f477ceb622f4bf97c4d5014573932800e153193d Mon Sep 17 00:00:00 2001 From: Yuki Date: Thu, 28 May 2026 16:30:31 +0800 Subject: [PATCH 06/35] fix --- .../images/floating-search/down-white.svg | 12 ++ .../misc/images/floating-search/up-white.svg | 12 ++ TeXmacs/misc/images/images.qrc | 3 + TeXmacs/progs/generic/search-widgets.scm | 2 + devel/1042.md | 124 +++++++++++++++++- src/Plugins/Qt/qt_chat_controller.cpp | 7 + src/Plugins/Qt/qt_chat_controller.hpp | 3 - src/Scheme/L5/glue_widget.lua | 10 ++ 8 files changed, 163 insertions(+), 10 deletions(-) create mode 100644 TeXmacs/misc/images/floating-search/down-white.svg create mode 100644 TeXmacs/misc/images/floating-search/up-white.svg diff --git a/TeXmacs/misc/images/floating-search/down-white.svg b/TeXmacs/misc/images/floating-search/down-white.svg new file mode 100644 index 0000000000..a8bc6f93eb --- /dev/null +++ b/TeXmacs/misc/images/floating-search/down-white.svg @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/TeXmacs/misc/images/floating-search/up-white.svg b/TeXmacs/misc/images/floating-search/up-white.svg new file mode 100644 index 0000000000..74ec95b068 --- /dev/null +++ b/TeXmacs/misc/images/floating-search/up-white.svg @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/TeXmacs/misc/images/images.qrc b/TeXmacs/misc/images/images.qrc index 3276c7b7e1..d3215e4865 100644 --- a/TeXmacs/misc/images/images.qrc +++ b/TeXmacs/misc/images/images.qrc @@ -46,6 +46,9 @@ pdf-reader/open-white.svg pdf-reader/screenshot-white.svg + floating-search/down-white.svg + floating-search/up-white.svg + tutorial/ocr-tutorial.gif tutorial/magic-paste-tutorial.gif diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index b5fc7a06ad..71909ffbf2 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -1153,6 +1153,8 @@ (set-search-reference (cursor-path))) (set-search-filter) (set! search-filter-out? #f) + (qt-floating-search-set-callbacks + "(chat-tab-search-next #t)" "(chat-tab-search-next #f)" "(chat-tab-search-close)") (qt-floating-search-init (url->string aux)) (qt-floating-search "true"))) diff --git a/devel/1042.md b/devel/1042.md index 32cd479fa8..ccf5539938 100644 --- a/devel/1042.md +++ b/devel/1042.md @@ -6,8 +6,11 @@ ## 2 任务相关的代码文件 - `TeXmacs/progs/generic/search-widgets.scm` - 搜索/替换入口函数 +- `src/Plugins/Qt/qt_floating_search_bar.hpp` - 悬浮搜索栏通用组件 +- `src/Plugins/Qt/qt_floating_search_bar.cpp` - 悬浮搜索栏实现与管理器 +- `src/Plugins/Qt/qt_chat_controller.cpp` - Chat controller(注册 parent provider) - `src/Plugins/Qt/qt_chat_tab_widget.hpp` - Chat tab widget 头文件 -- `src/Plugins/Qt/qt_chat_tab_widget.cpp` - Chat tab widget 实现 +- `src/Scheme/L5/glue_widget.lua` - Scheme 胶水函数声明 ## 3 如何测试 @@ -37,7 +40,7 @@ gf fmt --changed-since=main ## 5 What Chat Tab 的 message buffer 是嵌入式 TeXmacs widget,没有标准 `tm_window`,无法使用主窗口的 auxiliary-widget 侧边栏搜索机制。需要为 Chat Tab 实现独立的搜索功能。 -1. 在 QTChatTabWidget 右上角实现 VSCode 风格的悬浮搜索栏(公共组件类) +1. 实现可复用的悬浮搜索栏 Qt 组件 `QTMFloatingSearchBar`,支持任意 QWidget parent 2. 搜索栏包含:输入框、上一个/下一个按钮、关闭按钮、匹配计数 3. Scheme 端新增 chat-tab 专用搜索初始化和导航函数 4. 修改 `interactive-search` 在嵌入式 chat buffer 时使用新的搜索 UI @@ -47,8 +50,115 @@ Chat Tab 的 message buffer 是嵌入式 TeXmacs widget,没有标准 `tm_windo commit [0228] 通过 blanket `tmfs:` 检查禁用了所有 tmfs:// 缓冲区的搜索来避免 crash。用户需要在 Chat Tab 的消息输出框中搜索对话内容。Chat Tab 的嵌入式 buffer 不支持 auxiliary-widget 机制,需要独立的搜索 UI。 ## 7 How -1. 实现一个可复用的悬浮搜索栏 Qt 组件(`QTMFloatingSearchBar`),包含输入框、导航按钮、关闭按钮、匹配计数标签 -2. 在 QTChatTabWidget 的 content 区域右上角放置该组件,初始隐藏 -3. Scheme 端新增 `chat-tab-search-init`、`chat-tab-perform-search`、`chat-tab-search-next`、`chat-tab-search-close` 函数 -4. C++ 按钮回调通过 `eval_scheme` 调用 Scheme 搜索函数 -5. `interactive-search` 检测到嵌入式 chat buffer 时调 C++ glue 显示搜索栏并初始化搜索 +1. 实现通用悬浮搜索栏 Qt 组件 `QTMFloatingSearchBar`,包含输入框、导航按钮、关闭按钮、匹配计数标签 +2. 通过 `QHash` 按 parent widget 管理实例,parent 销毁时自动清理 +3. `QTMFloatingSearchBar` 自身安装 eventFilter 处理 parent resize,自动重新定位到右上角 +4. Scheme 端新增 `chat-tab-search-init`、`chat-tab-perform-search`、`chat-tab-search-next`、`chat-tab-search-close` 函数 +5. C++ 按钮回调通过 `eval_scheme` 调用 Scheme 搜索函数,回调命令由 `qt-floating-search-set-callbacks` 注入,不再硬编码 +6. `interactive-search` 检测到嵌入式 chat buffer 时调 C++ glue 显示搜索栏并初始化搜索 + +--- + +## 8 组件复用方法(供后续开发者参考) + +`QTMFloatingSearchBar` 已设计为**通用组件**,不依赖 ChatController,可 attach 到任意 `QWidget`。 + +### 8.1 复用架构 + +``` +┌─────────────────────────────────────┐ +│ Scheme 侧 (search-widgets.scm) │ +│ ── (qt-floating-search-set-callbacks ...) +│ ── (qt-floating-search-init aux-url) +│ ── (qt-floating-search "true") +└─────────────┬───────────────────────┘ + │ glue_widget.lua +┌─────────────▼───────────────────────┐ +│ C++ 兼容层 (qt_floating_search_bar.cpp) +│ ── 通过注册的 parent provider 获取默认 parent +└─────────────┬───────────────────────┘ + │ 底层通用 API +┌─────────────▼───────────────────────┐ +│ QTMFloatingSearchBar 管理器 │ +│ ── QHash 管理生命周期 │ +└─────────────────────────────────────┘ +``` + +### 8.2 在新页面中使用(以 XXX Tab 为例) + +#### 步骤 1:C++ 侧注册 parent provider + +在负责该页面的 Controller 初始化时(如 `createView` 中),注册一个返回 content widget 的 provider: + +```cpp +#include "qt_floating_search_bar.hpp" + +void +XXXController::createView (QWidget* parent) { + view_ = new XXXTabWidget (...); + // ... 其他初始化 ... + + // 注册浮动搜索栏的 parent provider + qt_floating_search_set_parent_provider ([this] () -> QWidget* { + if (!view_) return nullptr; + return view_->contentWidget (); // 或任意目标 QWidget + }); +} +``` + +> **注意**:同一时间只能有一个活跃的 provider。如果多个页面都想使用浮动搜索栏,需要确保切换页面时重新注册 provider,或直接调用底层通用 API(见 8.3)。 + +#### 步骤 2:Scheme 侧调用胶水函数 + +```scheme +;; 设置按钮对应的 Scheme 回调命令 +(qt-floating-search-set-callbacks + "(my-page-search-next #t)" + "(my-page-search-next #f)" + "(my-page-search-close)") + +;; 初始化:创建 texmacs_input_widget 并嵌入搜索栏 +(qt-floating-search-init (url->string aux-buffer-url)) + +;; 显示搜索栏 +(qt-floating-search "true") + +;; 隐藏搜索栏 +(qt-floating-search "false") + +;; 更新匹配计数(通常在搜索回调中调用) +(qt-floating-search-set-match-info 3 10) +``` + +#### 步骤 3:实现对应的 Scheme 搜索函数 + +参考 `search-widgets.scm` 中的 `chat-tab-search-init` / `chat-tab-search-next` / `chat-tab-search-close` 实现即可。核心逻辑: +- `init`:绑定 search-buffer 到目标 buffer,设置搜索起点 +- `next`:调用 `search-next-match` 在目标 buffer 中跳转 +- `close`:清除高亮、重置搜索状态、隐藏搜索栏 + +### 8.3 直接使用底层 C++ API(不走 Scheme 胶水层) + +如果场景更适合纯 C++ 控制(如非 TeXmacs buffer 的纯 Qt 页面),可以直接调用: + +```cpp +#include "qt_floating_search_bar.hpp" + +// 在某个 QWidget* content 上创建/显示搜索栏 +qt_floating_search_bar_show (content, true); + +// 初始化输入框(需要 aux_url 作为 texmacs_input_widget 的绑定) +bool ok = qt_floating_search_bar_init (content, "tmfs://aux/search/..."); + +// 设置匹配计数 +qt_floating_search_bar_set_match_info (content, 3, 10); + +// 设置 Scheme 回调(可选,如果不需要 Scheme 交互可跳过) +qt_floating_search_bar_set_callbacks ( + content, "(my-next)", "(my-prev)", "(my-close)"); + +// 手动销毁(parent 销毁时会自动清理,通常不需要手动调用) +qt_floating_search_bar_destroy (content); +``` + +底层 API 的 parent 参数可以是任意 `QWidget*`,组件会自动安装 eventFilter 处理 resize 并定位到 parent 的右上角。 diff --git a/src/Plugins/Qt/qt_chat_controller.cpp b/src/Plugins/Qt/qt_chat_controller.cpp index 9cc701de62..64ac343820 100644 --- a/src/Plugins/Qt/qt_chat_controller.cpp +++ b/src/Plugins/Qt/qt_chat_controller.cpp @@ -11,6 +11,7 @@ #include "qt_chat_controller.hpp" #include "qt_chat_tab_widget.hpp" +#include "qt_floating_search_bar.hpp" #include "new_buffer.hpp" #include "s7_tm.hpp" @@ -139,6 +140,12 @@ ChatController::createView (QWidget* parent, qt_tm_widget_rep* tm) { } } + // 6. 注册浮动搜索栏的 parent provider + qt_floating_search_set_parent_provider ([this] () -> QWidget* { + if (!view_) return nullptr; + return view_->contentWidget (); + }); + return view_; } diff --git a/src/Plugins/Qt/qt_chat_controller.hpp b/src/Plugins/Qt/qt_chat_controller.hpp index 66aa110500..06d51c6482 100644 --- a/src/Plugins/Qt/qt_chat_controller.hpp +++ b/src/Plugins/Qt/qt_chat_controller.hpp @@ -133,9 +133,6 @@ class ChatController : public QObject { */ void destroyView (); -public: - QTChatTabWidget* view () const { return view_; } - private: QTChatTabWidget* view_= nullptr; ///< View 指针,由 createView 创建 ChatSessionManager sessionManager_; ///< 会话管理器 diff --git a/src/Scheme/L5/glue_widget.lua b/src/Scheme/L5/glue_widget.lua index fe6b3735a4..4e1756ecd9 100644 --- a/src/Scheme/L5/glue_widget.lua +++ b/src/Scheme/L5/glue_widget.lua @@ -551,6 +551,16 @@ function main() "int", "int" } + }, + { + scm_name = "qt-floating-search-set-callbacks", + cpp_name = "qt_floating_search_set_callbacks", + ret_type = "void", + arg_list = { + "string", + "string", + "string" + } } } } From 051ae233ef1ae4dac7dd6263dad18fbc01f3c986 Mon Sep 17 00:00:00 2001 From: Yuki Date: Thu, 28 May 2026 16:43:04 +0800 Subject: [PATCH 07/35] css --- TeXmacs/misc/themes/liii-night.css | 47 +++ TeXmacs/misc/themes/liii.css | 46 +++ TeXmacs/plugins/lang/dic/en_US/zh_CN.scm | 1 + src/Plugins/Qt/qt_floating_search_bar.cpp | 365 +++++++++++++--------- src/Plugins/Qt/qt_floating_search_bar.hpp | 84 ++++- 5 files changed, 376 insertions(+), 167 deletions(-) diff --git a/TeXmacs/misc/themes/liii-night.css b/TeXmacs/misc/themes/liii-night.css index b1e75cbd4a..68ffa62261 100644 --- a/TeXmacs/misc/themes/liii-night.css +++ b/TeXmacs/misc/themes/liii-night.css @@ -1491,3 +1491,50 @@ QPushButton#chat-tab-conversation-btn:checked { QWidget#centralWidget QPushButton#chat-tab-conversation-btn:checked { background-color: #1a3a5a; } + +/**************************************************************************** +* 悬浮搜索栏样式 +****************************************************************************/ + +QWidget#centralWidget QWidget#floating_search_bar { + background: #2d2d2d; + border: none; +} + +QWidget#floating_search_bar QToolButton { + border: none; + background: transparent; +} + +QWidget#floating_search_bar QToolButton:hover { + background: rgba(255, 255, 255, 30); +} + +QWidget#floating_search_bar QToolButton:pressed { + background: rgba(255, 255, 255, 50); +} + +QWidget#floating_search_bar QToolButton#floating-search-prev { + qproperty-icon: url(":/floating-search/up-white.svg"); +} + +QWidget#floating_search_bar QToolButton#floating-search-next { + qproperty-icon: url(":/floating-search/down-white.svg"); +} + +QWidget#floating_search_bar QToolButton#floating-search-close { + qproperty-icon: url(":/tabpage/close-white.svg"); +} + +QWidget#floating_search_bar QWidget#floating-search-input { + border: 1px solid #555555; +} + +QWidget#floating_search_bar QWidget#floating-search-input:focus { + border: 1px solid #215a6a; +} + +QWidget#floating_search_bar QLabel#floating-search-info { + background: transparent; + color: #ffffff; +} diff --git a/TeXmacs/misc/themes/liii.css b/TeXmacs/misc/themes/liii.css index 0a9fd19a7d..9640c6f3fa 100644 --- a/TeXmacs/misc/themes/liii.css +++ b/TeXmacs/misc/themes/liii.css @@ -1410,3 +1410,49 @@ QPushButton#chat-tab-conversation-btn:checked { font-weight: 600; background-color: #e8eefc; } + +/**************************************************************************** +* 悬浮搜索栏样式 +****************************************************************************/ +QWidget#floating_search_bar { + background: #f3f3f3; + border: none; +} + +QWidget#floating_search_bar QToolButton { + border: none; + background: transparent; +} + +QWidget#floating_search_bar QToolButton:hover { + background: rgba(0, 0, 0, 30); +} + +QWidget#floating_search_bar QToolButton:pressed { + background: rgba(0, 0, 0, 50); +} + +QWidget#floating_search_bar QToolButton#floating-search-prev { + qproperty-icon: url(":/floating-search/up.svg"); +} + +QWidget#floating_search_bar QToolButton#floating-search-next { + qproperty-icon: url(":/floating-search/down.svg"); +} + +QWidget#floating_search_bar QToolButton#floating-search-close { + qproperty-icon: url(":/tabpage/close.svg"); +} + +QWidget#floating_search_bar QWidget#floating-search-input { + border: 1px solid #d0d0d0; +} + +QWidget#floating_search_bar QWidget#floating-search-input:focus { + border: 1px solid #215a6a; +} + +QWidget#floating_search_bar QLabel#floating-search-info { + background: transparent; + color: #2c2c2c; +} diff --git a/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm b/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm index 77b467f4bf..024f1bfa32 100644 --- a/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm +++ b/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm @@ -1608,6 +1608,7 @@ ("no line break" "禁止换行") ("no matches found for" "") ("no matches found" "") +("No matches" "无匹配") ("no more matches for" "") ("no more redo information available" "") ("no more undo information available" "") diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index f3691b9726..50dbdbaaeb 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -10,8 +10,6 @@ ******************************************************************************/ #include "qt_floating_search_bar.hpp" -#include "qt_chat_controller.hpp" -#include "qt_chat_tab_widget.hpp" #include "qt_dpi_utils.hpp" #include "qt_utilities.hpp" #include "qt_widget.hpp" @@ -23,12 +21,14 @@ #include #include +#include #include +#include #include using namespace moebius; -// ---- 尺寸常量(单位:逻辑像素,经 DpiUtils::scaled 缩放) ---- // +// ---- 尺寸常量(逻辑像素,经 DpiUtils 缩放) ---- constexpr int kBarMinHeight= 64; constexpr int kBarWidth = 420; constexpr int kBarRadius = 4; @@ -38,8 +38,7 @@ constexpr int kBarSpacing = 4; constexpr int kBtnSize = 24; constexpr int kBtnRadius= 12; -constexpr int kInfoHeight = 24; -constexpr int kInfoMinWidth= 80; +constexpr int kInfoHeight= 24; constexpr int kShadowBlur = 8; constexpr int kShadowOffsetY= 1; @@ -49,7 +48,7 @@ constexpr int kPosRightPad= 8; constexpr int kPosTopPad = 4; /****************************************************************************** - * QTMFloatingSearchBar + * QTMFloatingSearchBar 实现 ******************************************************************************/ QTMFloatingSearchBar::QTMFloatingSearchBar (QWidget* parent) @@ -59,34 +58,20 @@ QTMFloatingSearchBar::QTMFloatingSearchBar (QWidget* parent) setAttribute (Qt::WA_StyledBackground); setMinimumHeight (DpiUtils::scaled (kBarMinHeight)); + // border-radius 由代码动态计算,支持 DPI 缩放 setStyleSheet (QString ("#floating_search_bar {" - " background: #f3f3f3;" - " border: none;" " border-radius: %1px;" "}") .arg (DpiUtils::scaled (kBarRadius))); - QGraphicsDropShadowEffect* shadow= new QGraphicsDropShadowEffect (this); + auto* shadow= new QGraphicsDropShadowEffect (this); shadow->setBlurRadius (DpiUtils::scaled (kShadowBlur)); shadow->setOffset (0, DpiUtils::scaled (kShadowOffsetY)); shadow->setColor (QColor (0, 0, 0, kShadowAlpha)); setGraphicsEffect (shadow); - const QString btnStyle= QString ("QPushButton {" - " border: none;" - " border-radius: %1px;" - " background: transparent;" - "}" - "QPushButton:hover {" - " background: rgba(0,0,0,30);" - "}" - "QPushButton:pressed {" - " background: rgba(0,0,0,50);" - "}") - .arg (DpiUtils::scaled (kBtnRadius)); - // 外层水平布局:左边 [输入区],右边 [按钮 + 匹配信息] - QHBoxLayout* mainLayout= new QHBoxLayout (this); + auto* mainLayout= new QHBoxLayout (this); mainLayout->setContentsMargins ( DpiUtils::scaled (kBarMargin), DpiUtils::scaled (kBarMargin), DpiUtils::scaled (kBarMargin), DpiUtils::scaled (kBarMargin)); @@ -97,68 +82,83 @@ QTMFloatingSearchBar::QTMFloatingSearchBar (QWidget* parent) rowLayout_->setSpacing (0); // 右侧:垂直布局 [按钮行] + [匹配信息] - QVBoxLayout* rightLayout= new QVBoxLayout (); + auto* rightLayout= new QVBoxLayout (); rightLayout->setSpacing (DpiUtils::scaled (4)); // 右侧上层:按钮行 - QHBoxLayout* btnRow= new QHBoxLayout (); + auto* btnRow= new QHBoxLayout (); btnRow->setSpacing (DpiUtils::scaled (4)); - QPushButton* prevBtn= new QPushButton (this); - prevBtn->setIcon (QIcon (":floating-search/up.svg")); + const QString btnRadiusStyle= + QString ( + "QToolButton { border-radius: %1px; padding: 0px; margin: 0px; }") + .arg (DpiUtils::scaled (kBtnRadius)); + + auto* prevBtn= new QToolButton (this); + prevBtn->setObjectName ("floating-search-prev"); prevBtn->setFixedSize (DpiUtils::scaled (kBtnSize), DpiUtils::scaled (kBtnSize)); - prevBtn->setToolTip (tr ("Previous (Shift+Enter)")); - prevBtn->setStyleSheet (btnStyle); + prevBtn->setToolTip (qt_translate ("Previous (Shift+Enter)")); + prevBtn->setStyleSheet (btnRadiusStyle); btnRow->addWidget (prevBtn); - QPushButton* nextBtn= new QPushButton (this); - nextBtn->setIcon (QIcon (":floating-search/down.svg")); + auto* nextBtn= new QToolButton (this); + nextBtn->setObjectName ("floating-search-next"); nextBtn->setFixedSize (DpiUtils::scaled (kBtnSize), DpiUtils::scaled (kBtnSize)); - nextBtn->setToolTip (tr ("Next (Enter)")); - nextBtn->setStyleSheet (btnStyle); + nextBtn->setToolTip (qt_translate ("Next (Enter)")); + nextBtn->setStyleSheet (btnRadiusStyle); btnRow->addWidget (nextBtn); - QPushButton* closeBtn= new QPushButton (this); - closeBtn->setIcon (QIcon (":tabpage/close.svg")); + auto* closeBtn= new QToolButton (this); + closeBtn->setObjectName ("floating-search-close"); closeBtn->setFixedSize (DpiUtils::scaled (kBtnSize), DpiUtils::scaled (kBtnSize)); - closeBtn->setToolTip (tr ("Close (Esc)")); - closeBtn->setStyleSheet (btnStyle); + closeBtn->setToolTip (qt_translate ("Close (Esc)")); + closeBtn->setStyleSheet (btnRadiusStyle); btnRow->addWidget (closeBtn); rightLayout->addLayout (btnRow); // 右侧下层:匹配信息 infoLbl_= new QLabel (this); + infoLbl_->setObjectName ("floating-search-info"); infoLbl_->setFixedHeight (DpiUtils::scaled (kInfoHeight)); infoLbl_->setAlignment (Qt::AlignCenter); - infoLbl_->setText (QString::fromUtf8 ("无匹配")); + infoLbl_->setText (qt_translate ("No matches")); rightLayout->addWidget (infoLbl_); // 组装:左输入(stretch=1) + 右面板 mainLayout->addLayout (rowLayout_, 1); mainLayout->addLayout (rightLayout); - QObject::connect (nextBtn, &QPushButton::clicked, this, - &QTMFloatingSearchBar::findNextRequested); - QObject::connect (prevBtn, &QPushButton::clicked, this, - &QTMFloatingSearchBar::findPreviousRequested); - QObject::connect (closeBtn, &QPushButton::clicked, this, - &QTMFloatingSearchBar::closeRequested); + connect (nextBtn, &QToolButton::clicked, this, + &QTMFloatingSearchBar::findNextRequested); + connect (prevBtn, &QToolButton::clicked, this, + &QTMFloatingSearchBar::findPreviousRequested); + connect (closeBtn, &QToolButton::clicked, this, + &QTMFloatingSearchBar::closeRequested); + + if (parent) parent->installEventFilter (this); hide (); } +QTMFloatingSearchBar::~QTMFloatingSearchBar () { + if (parent ()) parent ()->removeEventFilter (this); +} + void QTMFloatingSearchBar::setSearchInput (QWidget* input) { if (inputQW_) { rowLayout_->removeWidget (inputQW_); - delete inputQW_; + inputQW_->deleteLater (); } inputQW_= input; - rowLayout_->insertWidget (0, input, 1); + if (input) { + input->setObjectName ("floating-search-input"); + rowLayout_->insertWidget (0, input, 1); + } } void @@ -170,147 +170,206 @@ QTMFloatingSearchBar::activate () { void QTMFloatingSearchBar::setMatchInfo (int current, int total) { - if (total == 0) infoLbl_->setText (tr ("No matches")); - else infoLbl_->setText (tr ("%1 of %2").arg (current).arg (total)); + if (total == 0) infoLbl_->setText (qt_translate ("No matches")); + else infoLbl_->setText (qt_translate ("%1 of %2").arg (current).arg (total)); } -/****************************************************************************** - * 浮动搜索栏管理 - ******************************************************************************/ +void +QTMFloatingSearchBar::setSchemeCallbacks (const string& next_cmd, + const string& prev_cmd, + const string& close_cmd) { + next_cmd_ = next_cmd; + prev_cmd_ = prev_cmd; + close_cmd_= close_cmd; + if (!callbacksConnected_) { + connectSignals (); + callbacksConnected_= true; + } +} -static QTMFloatingSearchBar* g_search_bar = nullptr; -static QWidget* g_search_bar_parent= nullptr; - -// 事件过滤器:parent resize 时重新定位搜索栏 -class SearchBarResizeFilter : public QObject { -public: - SearchBarResizeFilter (QObject* parent) : QObject (parent) {} - -protected: - bool eventFilter (QObject* watched, QEvent* event) override { - if (event->type () == QEvent::Resize && g_search_bar && - g_search_bar->isVisible ()) { - QWidget* w= qobject_cast (watched); - if (w) { - int x= w->width () - g_search_bar->width () - - DpiUtils::scaled (kPosRightPad); - int y= DpiUtils::scaled (kPosTopPad); - g_search_bar->move (x, y); - } - } - return QObject::eventFilter (watched, event); +void +QTMFloatingSearchBar::connectSignals () { + if (!is_empty (next_cmd_)) { + connect (this, &QTMFloatingSearchBar::findNextRequested, this, + [this] () { eval_scheme (next_cmd_); }); } -}; + if (!is_empty (prev_cmd_)) { + connect (this, &QTMFloatingSearchBar::findPreviousRequested, this, + [this] () { eval_scheme (prev_cmd_); }); + } + if (!is_empty (close_cmd_)) { + connect (this, &QTMFloatingSearchBar::closeRequested, this, [this] () { + eval_scheme (close_cmd_); + hide (); + }); + } +} -static SearchBarResizeFilter* g_resize_filter= nullptr; +bool +QTMFloatingSearchBar::eventFilter (QObject* watched, QEvent* event) { + if (event->type () == QEvent::Resize && watched == parent () && + isVisible ()) { + reposition (); + } + return QWidget::eventFilter (watched, event); +} + +void +QTMFloatingSearchBar::showEvent (QShowEvent* event) { + QWidget::showEvent (event); + reposition (); +} -static void -position_search_bar () { - if (!g_search_bar || !g_search_bar_parent) return; - int x= g_search_bar_parent->width () - g_search_bar->width () - - DpiUtils::scaled (kPosRightPad); +void +QTMFloatingSearchBar::reposition () { + QWidget* p= qobject_cast (parent ()); + if (!p) return; + int x= p->width () - width () - DpiUtils::scaled (kPosRightPad); int y= DpiUtils::scaled (kPosTopPad); - g_search_bar->move (x, y); + move (x, y); } -static void -connect_search_bar_signals (QTMFloatingSearchBar* bar) { - QObject::connect (bar, &QTMFloatingSearchBar::findNextRequested, bar, - [] () { eval_scheme ("(chat-tab-search-next #t)"); }); - QObject::connect (bar, &QTMFloatingSearchBar::findPreviousRequested, bar, - [] () { eval_scheme ("(chat-tab-search-next #f)"); }); - QObject::connect (bar, &QTMFloatingSearchBar::closeRequested, bar, [] () { - eval_scheme ("(chat-tab-search-close)"); - if (g_search_bar) g_search_bar->hide (); +/****************************************************************************** + * 搜索栏管理器 + ******************************************************************************/ + +static QHash& +searchBars () { + static QHash bars; + return bars; +} + +static QTMFloatingSearchBar* +get_or_create_bar (QWidget* parent) { + if (!parent) return nullptr; + auto& bars= searchBars (); + auto it = bars.find (parent); + if (it != bars.end () && *it) return *it; + + auto* bar = new QTMFloatingSearchBar (parent); + bars[parent]= bar; + + QObject::connect (parent, &QObject::destroyed, [parent] () { + auto& b= searchBars (); + auto i= b.find (parent); + if (i != b.end ()) b.erase (i); }); + + bar->setFixedWidth (DpiUtils::scaled (kBarWidth)); + return bar; +} + +void +qt_floating_search_bar_show (QWidget* parent, bool show) { + if (!parent) return; + auto* bar= get_or_create_bar (parent); + if (!bar) return; + if (show) bar->show (); + else bar->hide (); } -/// 创建或重建浮动栏容器,确保 attach 到 content。 -static void -ensure_search_bar (QWidget* content) { - if (g_search_bar && g_search_bar_parent == content) return; +bool +qt_floating_search_bar_init (QWidget* parent, const string& aux_url_str) { + if (!parent) return false; + auto* bar= get_or_create_bar (parent); - if (g_search_bar) { - delete g_search_bar; - g_search_bar= nullptr; + url aux_url= url_system (aux_url_str); + tree doc (DOCUMENT, ""); + tree sty= compound ("style", tree (TUPLE, "generic")); + widget tw = texmacs_input_widget (doc, sty, aux_url); + if (is_nil (tw)) { + bar->hide (); + return false; } - if (g_resize_filter) { - if (g_search_bar_parent) - g_search_bar_parent->removeEventFilter (g_resize_filter); - delete g_resize_filter; - g_resize_filter= nullptr; + QWidget* inputW= concrete (tw)->as_qwidget (); + if (!inputW) { + bar->hide (); + return false; } + bar->setSearchInput (inputW); + return true; +} - g_search_bar = new QTMFloatingSearchBar (content); - g_search_bar_parent= content; - g_resize_filter = new SearchBarResizeFilter (content); - content->installEventFilter (g_resize_filter); +void +qt_floating_search_bar_set_match_info (QWidget* parent, int current, + int total) { + if (!parent) return; + auto* bar= searchBars ().value (parent); + if (bar) bar->setMatchInfo (current, total); +} - // content 被 Qt 销毁时自动清理全局指针,避免悬空 - QObject::connect (content, &QObject::destroyed, [] () { - g_search_bar = nullptr; - g_search_bar_parent= nullptr; - g_resize_filter = nullptr; - }); +void +qt_floating_search_bar_set_callbacks (QWidget* parent, const string& next_cmd, + const string& prev_cmd, + const string& close_cmd) { + if (!parent) return; + auto* bar= get_or_create_bar (parent); + bar->setSchemeCallbacks (next_cmd, prev_cmd, close_cmd); +} - g_search_bar->setFixedWidth (DpiUtils::scaled (kBarWidth)); - connect_search_bar_signals (g_search_bar); +void +qt_floating_search_bar_destroy (QWidget* parent) { + if (!parent) return; + auto& bars= searchBars (); + auto it = bars.find (parent); + if (it != bars.end ()) { + (*it)->setParent (nullptr); + delete *it; + bars.erase (it); + } } /****************************************************************************** - * Scheme 胶水函数 + * 兼容层胶水函数(通过 provider 代理) ******************************************************************************/ +static qt_floating_search_parent_provider g_parent_provider; + +void +qt_floating_search_set_parent_provider ( + qt_floating_search_parent_provider provider) { + g_parent_provider= provider; +} + +static QWidget* +get_provider_parent () { + return g_parent_provider ? g_parent_provider () : nullptr; +} + void qt_floating_search (string flag) { - ChatController* ctrl= get_chat_controller (); - if (!ctrl) return; - QTChatTabWidget* view= ctrl->view (); - if (!view) return; - QWidget* content= view->contentWidget (); - if (!content) return; - - if (flag == "true" || flag == "#t") { - ensure_search_bar (content); - g_search_bar->activate (); - position_search_bar (); + QWidget* parent= get_provider_parent (); + if (!parent) return; + bool show= (flag == "true" || flag == "#t"); + if (show) { + auto* bar= get_or_create_bar (parent); + if (bar) bar->activate (); } else { - if (g_search_bar) g_search_bar->hide (); + qt_floating_search_bar_show (parent, false); } } void qt_floating_search_init (string aux_url_str) { - ChatController* ctrl= get_chat_controller (); - if (!ctrl) return; - QTChatTabWidget* view= ctrl->view (); - if (!view) return; - QWidget* content= view->contentWidget (); - if (!content) return; - - ensure_search_bar (content); - - // 创建 texmacs_input_widget 绑定到 search-buffer - url aux_url= url_system (aux_url_str); - tree doc (DOCUMENT, ""); - tree sty= compound ("style", tree (TUPLE, "generic")); - widget tw = texmacs_input_widget (doc, sty, aux_url); - if (is_nil (tw)) return; - QWidget* inputW= concrete (tw)->as_qwidget (); - if (inputW) { - inputW->setStyleSheet ("QWidget {" - " background: white;" - " border: 1px solid #d0d0d0;" - "}" - "QWidget:focus {" - " border: 1px solid #215a6a;" - "}"); - g_search_bar->setSearchInput (inputW); - } + QWidget* parent= get_provider_parent (); + if (!parent) return; + if (!qt_floating_search_bar_init (parent, aux_url_str)) return; + auto* bar= get_or_create_bar (parent); + if (bar) bar->activate (); } void qt_floating_search_set_match_info (int current, int total) { - if (g_search_bar) g_search_bar->setMatchInfo (current, total); + QWidget* parent= get_provider_parent (); + if (!parent) return; + qt_floating_search_bar_set_match_info (parent, current, total); +} + +void +qt_floating_search_set_callbacks (string next_cmd, string prev_cmd, + string close_cmd) { + QWidget* parent= get_provider_parent (); + if (!parent) return; + qt_floating_search_bar_set_callbacks (parent, next_cmd, prev_cmd, close_cmd); } diff --git a/src/Plugins/Qt/qt_floating_search_bar.hpp b/src/Plugins/Qt/qt_floating_search_bar.hpp index 939a8ff256..aaef43d6f0 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.hpp +++ b/src/Plugins/Qt/qt_floating_search_bar.hpp @@ -14,44 +14,96 @@ #include #include -#include #include #include "string.hpp" +#include + /** - * 悬浮搜索栏容器。 + * VSCode 风格的悬浮搜索栏组件。 * - * 布局: - * 上层: [TeXmacs 输入区] [上一个] [下一个] [关闭] - * 下层: [TeXmacs 输入区] [匹配计数] - * 输入区是嵌入的 texmacs_input_widget,绑定到 search-buffer, - * 搜索逻辑与底部搜索面板完全一致。 + * 布局: + * 左侧:嵌入的输入框(如 texmacs_input_widget) + * 右侧:[上一个] [下一个] [关闭] 按钮 + 匹配计数 */ class QTMFloatingSearchBar : public QWidget { Q_OBJECT public: - QTMFloatingSearchBar (QWidget* parent= nullptr); + explicit QTMFloatingSearchBar (QWidget* parent= nullptr); + ~QTMFloatingSearchBar (); - /// 设置嵌入的 TeXmacs 搜索输入 widget。 + /// 设置嵌入的搜索输入框。旧的输入框(如有)会被移除并 deleteLater。 void setSearchInput (QWidget* input); - /// 显示搜索栏并聚焦输入区。 + /// 显示搜索栏并聚焦输入框。 void activate (); - /// 设置匹配信息(current=0, total=0 显示"无匹配")。 + /// 设置匹配信息(current=0, total=0 时显示"无匹配")。 void setMatchInfo (int current, int total); + /// 配置按钮点击时求值的 Scheme 命令。 + void setSchemeCallbacks (const string& next_cmd, const string& prev_cmd, + const string& close_cmd); + signals: void findNextRequested (); void findPreviousRequested (); void closeRequested (); +protected: + bool eventFilter (QObject* watched, QEvent* event) override; + void showEvent (QShowEvent* event) override; + private: - QHBoxLayout* rowLayout_= nullptr; ///< 上层水平布局(输入+按钮) - QWidget* inputQW_ = nullptr; ///< 嵌入的 TeXmacs 输入 QWidget - QLabel* infoLbl_ = nullptr; ///< 匹配计数标签 + void reposition (); + void connectSignals (); + + QHBoxLayout* rowLayout_= nullptr; + QWidget* inputQW_ = nullptr; + QLabel* infoLbl_ = nullptr; + + string next_cmd_; + string prev_cmd_; + string close_cmd_; + bool callbacksConnected_= false; }; +/****************************************************************************** + * 通用管理 API(基于 parent widget,不依赖 ChatController) + ******************************************************************************/ + +/// 显示或隐藏 attach 到 \a parent 的悬浮搜索栏。 +void qt_floating_search_bar_show (QWidget* parent, bool show); + +/// 为 \a parent 创建/attach 搜索栏,并用绑定到 \a aux_url_str 的 +/// texmacs 输入框初始化。失败时返回 false。 +bool qt_floating_search_bar_init (QWidget* parent, const string& aux_url_str); + +/// 更新 attach 到 \a parent 的搜索栏的匹配计数。 +void qt_floating_search_bar_set_match_info (QWidget* parent, int current, + int total); + +/// 为 attach 到 \a parent 的搜索栏设置 Scheme 回调。 +void qt_floating_search_bar_set_callbacks (QWidget* parent, + const string& next_cmd, + const string& prev_cmd, + const string& close_cmd); + +/// 销毁 attach 到 \a parent 的搜索栏。 +void qt_floating_search_bar_destroy (QWidget* parent); + +/****************************************************************************** + * 兼容层胶水函数(保留向后兼容)。 + * 通过注册的 parent provider 代理到上面的通用 API。 + ******************************************************************************/ + +using qt_floating_search_parent_provider= std::function; + +/// 注册一个返回默认 parent widget 的函数,供兼容层胶水函数使用。 +/// 通常在 chat controller 初始化时调用。 +void qt_floating_search_set_parent_provider ( + qt_floating_search_parent_provider provider); + /// Scheme 胶水函数:显示 ("true"/"#t") 或隐藏悬浮搜索栏。 void qt_floating_search (string flag); @@ -62,4 +114,8 @@ void qt_floating_search_init (string aux_url_str); /// Scheme 胶水函数:更新浮动搜索栏的匹配计数显示。 void qt_floating_search_set_match_info (int current, int total); +/// Scheme 胶水函数:设置搜索栏按钮点击时求值的 Scheme 回调命令。 +void qt_floating_search_set_callbacks (string next_cmd, string prev_cmd, + string close_cmd); + #endif // QT_FLOATING_SEARCH_BAR_HPP From 5ecb5e8aaa2dd7197b09c89c31668fab99cc472c Mon Sep 17 00:00:00 2001 From: Yuki Date: Thu, 28 May 2026 17:14:23 +0800 Subject: [PATCH 08/35] translate --- TeXmacs/plugins/lang/dic/en_US/zh_CN.scm | 4 ++++ src/Plugins/Qt/qt_floating_search_bar.cpp | 9 +++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm b/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm index 024f1bfa32..da05aaf957 100644 --- a/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm +++ b/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm @@ -465,6 +465,7 @@ ("close replace tool (Esc)" "关闭替换窗 (Esc)") ("close window" "关闭窗口") ("close" "关闭") +("Close (Esc)" "关闭 (Esc)") ("closed bezier" "闭合贝塞尔曲线") ("closed curve" "闭合曲线") ("closed smooth" "闭合平滑曲线") @@ -1600,6 +1601,7 @@ ("next screen" "前一屏") ("next similar" "相似的(后一个)") ("next" "后一个") +("Next (Enter)" "下一个 (Enter)") ("no changes need to be saved" "没有任何更改需要保存") ("no dictionary for" "") ("no first indentation" "") @@ -1609,6 +1611,7 @@ ("no matches found for" "") ("no matches found" "") ("No matches" "无匹配") +("%1 of %2" "%1 / %2") ("no more matches for" "") ("no more redo information available" "") ("no more undo information available" "") @@ -1871,6 +1874,7 @@ ("previous screen" "后一屏") ("previous similar" "相似的(前一个)") ("previous" "前一个") +("Previous (Shift+Enter)" "上一个 (Shift+Enter)") ("primary" "") ("prime" "") ("print all to file" "全部打印为文件") diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index 50dbdbaaeb..d94781baac 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -44,8 +44,9 @@ constexpr int kShadowBlur = 8; constexpr int kShadowOffsetY= 1; constexpr int kShadowAlpha = 30; -constexpr int kPosRightPad= 8; -constexpr int kPosTopPad = 4; +constexpr int kPosRightPad = 8; +constexpr int kPosTopPad = 4; +constexpr int kInnerSpacing= 4; /****************************************************************************** * QTMFloatingSearchBar 实现 @@ -83,11 +84,11 @@ QTMFloatingSearchBar::QTMFloatingSearchBar (QWidget* parent) // 右侧:垂直布局 [按钮行] + [匹配信息] auto* rightLayout= new QVBoxLayout (); - rightLayout->setSpacing (DpiUtils::scaled (4)); + rightLayout->setSpacing (DpiUtils::scaled (kInnerSpacing)); // 右侧上层:按钮行 auto* btnRow= new QHBoxLayout (); - btnRow->setSpacing (DpiUtils::scaled (4)); + btnRow->setSpacing (DpiUtils::scaled (kInnerSpacing)); const QString btnRadiusStyle= QString ( From 64c0edeb3605691cb5f3aaf561e9fb089e94223c Mon Sep 17 00:00:00 2001 From: Yuki Date: Thu, 28 May 2026 17:38:53 +0800 Subject: [PATCH 09/35] next --- TeXmacs/progs/generic/search-widgets.scm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index 71909ffbf2..2fdff4c2e2 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -1170,7 +1170,7 @@ (tm-define (chat-tab-search-next forward?) (when (and chat-tab-search-target chat-tab-search-aux) (with-buffer chat-tab-search-target - (search-next-match forward? chat-tab-search-target)))) + (search-rotate-match forward?)))) (tm-define (chat-tab-search-close) (when chat-tab-search-target From 0ee4cecc939dd4c206f7bddb5cab455a38a241d1 Mon Sep 17 00:00:00 2001 From: Yuki Date: Fri, 29 May 2026 14:11:14 +0800 Subject: [PATCH 10/35] =?UTF-8?q?=E8=BE=93=E5=85=A5=E6=A1=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Plugins/Qt/qt_floating_search_bar.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index d94781baac..04051960a8 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -275,9 +275,12 @@ qt_floating_search_bar_init (QWidget* parent, const string& aux_url_str) { auto* bar= get_or_create_bar (parent); url aux_url= url_system (aux_url_str); - tree doc (DOCUMENT, ""); + qreal searchZoom= DpiUtils::scaled (100) / 100.0; + tree doc (WITH, "font", "sys-chinese", "zoom-factor", as_string (searchZoom), + tree (DOCUMENT, "")); tree sty= compound ("style", tree (TUPLE, "generic")); widget tw = texmacs_input_widget (doc, sty, aux_url); + set_zoom_factor (tw, searchZoom); if (is_nil (tw)) { bar->hide (); return false; From 2a28afa2a5723f90dfbf259b49cbe94da5db6b5a Mon Sep 17 00:00:00 2001 From: Yuki Date: Fri, 29 May 2026 14:23:39 +0800 Subject: [PATCH 11/35] viewport --- src/Plugins/Qt/qt_floating_search_bar.cpp | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index 04051960a8..5e0c25e30e 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -19,9 +19,11 @@ #include +#include #include #include #include +#include #include #include #include @@ -158,6 +160,10 @@ QTMFloatingSearchBar::setSearchInput (QWidget* input) { inputQW_= input; if (input) { input->setObjectName ("floating-search-input"); + QAbstractScrollArea* scrollArea= input->findChild (); + if (scrollArea) { + scrollArea->viewport ()->setBackgroundRole (QPalette::Base); + } rowLayout_->insertWidget (0, input, 1); } } @@ -274,12 +280,12 @@ qt_floating_search_bar_init (QWidget* parent, const string& aux_url_str) { if (!parent) return false; auto* bar= get_or_create_bar (parent); - url aux_url= url_system (aux_url_str); - qreal searchZoom= DpiUtils::scaled (100) / 100.0; - tree doc (WITH, "font", "sys-chinese", "zoom-factor", as_string (searchZoom), - tree (DOCUMENT, "")); - tree sty= compound ("style", tree (TUPLE, "generic")); - widget tw = texmacs_input_widget (doc, sty, aux_url); + url aux_url = url_system (aux_url_str); + qreal searchZoom= DpiUtils::scaled (100) / 100.0; + tree doc (WITH, "font", "sys-chinese", "zoom-factor", as_string (searchZoom), + tree (DOCUMENT, "")); + tree sty= compound ("style", tree (TUPLE, "generic")); + widget tw= texmacs_input_widget (doc, sty, aux_url); set_zoom_factor (tw, searchZoom); if (is_nil (tw)) { bar->hide (); From d3852680e593992f2c58a6b3c2134eecf7636e55 Mon Sep 17 00:00:00 2001 From: Yuki Date: Fri, 29 May 2026 15:00:39 +0800 Subject: [PATCH 12/35] =?UTF-8?q?=E8=BE=93=E5=85=A5=E6=A1=86=E9=80=82?= =?UTF-8?q?=E9=85=8D=E9=BB=91=E5=A4=9C=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/progs/generic/search-widgets.scm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index 2fdff4c2e2..09d1b57838 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -1156,6 +1156,11 @@ (qt-floating-search-set-callbacks "(chat-tab-search-next #t)" "(chat-tab-search-next #f)" "(chat-tab-search-close)") (qt-floating-search-init (url->string aux)) + ;; 同步暗色样式到搜索缓冲区 + (when (== (get-preference "gui theme") "liii-night") + (with-buffer aux + (when (not (has-style-package? "dark")) + (add-style-package "dark")))) (qt-floating-search "true"))) (define (chat-tab-perform-search) From 68e50cc36671dd83af6612723711db801b901e14 Mon Sep 17 00:00:00 2001 From: Yuki Date: Fri, 29 May 2026 15:09:56 +0800 Subject: [PATCH 13/35] devel --- devel/1042.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/devel/1042.md b/devel/1042.md index ccf5539938..9fa87c1dd1 100644 --- a/devel/1042.md +++ b/devel/1042.md @@ -45,6 +45,7 @@ Chat Tab 的 message buffer 是嵌入式 TeXmacs widget,没有标准 `tm_windo 3. Scheme 端新增 chat-tab 专用搜索初始化和导航函数 4. 修改 `interactive-search` 在嵌入式 chat buffer 时使用新的搜索 UI 5. 修改 `interactive-replace` 在 chat tab 中禁用替换 +6. 搜索框 DPI 缩放、系统字体、暗色主题跟随 ## 6 Why commit [0228] 通过 blanket `tmfs:` 检查禁用了所有 tmfs:// 缓冲区的搜索来避免 crash。用户需要在 Chat Tab 的消息输出框中搜索对话内容。Chat Tab 的嵌入式 buffer 不支持 auxiliary-widget 机制,需要独立的搜索 UI。 @@ -56,6 +57,8 @@ commit [0228] 通过 blanket `tmfs:` 检查禁用了所有 tmfs:// 缓冲区的 4. Scheme 端新增 `chat-tab-search-init`、`chat-tab-perform-search`、`chat-tab-search-next`、`chat-tab-search-close` 函数 5. C++ 按钮回调通过 `eval_scheme` 调用 Scheme 搜索函数,回调命令由 `qt-floating-search-set-callbacks` 注入,不再硬编码 6. `interactive-search` 检测到嵌入式 chat buffer 时调 C++ glue 显示搜索栏并初始化搜索 +7. 搜索框的 `texmacs_input_widget` 参照 chat 组件,应用 `DpiUtils::scaled` 缩放、`"font" "sys-chinese"` 字体、viewport 背景色 `QPalette::Base` +8. Scheme 端在 `chat-tab-search-init` 中检测 `liii-night` 主题,自动给搜索缓冲区添加 `"dark"` 样式包 --- From 1328fc6c1a7117ec1cb721e906924c2c4d5fa20f Mon Sep 17 00:00:00 2001 From: Yuki Date: Fri, 29 May 2026 15:31:57 +0800 Subject: [PATCH 14/35] fix --- TeXmacs/progs/generic/search-widgets.scm | 17 +++++------------ src/Plugins/Qt/qt_floating_search_bar.cpp | 6 ++---- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index 09d1b57838..c55e2c371c 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -168,7 +168,7 @@ ;; 此函数用于管理搜索辅助缓冲区的生命周期,确保每个主文档视图有唯一的搜索缓冲区。 (tm-define (search-buffer) ;; chat tab 搜索激活期间,直接返回保存的 aux buffer - (if chat-tab-search-active? + (if (and chat-tab-search-active? chat-tab-search-aux) chat-tab-search-aux (with u (current-buffer) @@ -343,7 +343,7 @@ (tm-define (master-buffer) ;; chat tab 搜索激活期间,直接返回保存的 target buffer - (if chat-tab-search-active? + (if (and chat-tab-search-active? chat-tab-search-target) chat-tab-search-target (and (buffer-exists? (search-buffer)) (with mas @@ -448,7 +448,9 @@ (new-env (tree-descendant-env* buf rel initial))) (if (not new-env) #t (check-same? (tm-children new-env) (tm-children old-env))))))) - (lambda (key msg . rest) #t)) + (lambda (key msg . rest) + (display* "Warning: accept-search-result? error: " msg "\n") + #t)) ) ;or ) ;define @@ -1163,15 +1165,6 @@ (add-style-package "dark")))) (qt-floating-search "true"))) -(define (chat-tab-perform-search) - (when (and chat-tab-search-target chat-tab-search-aux - (buffer-exists? chat-tab-search-aux)) - (with-buffer chat-tab-search-target - (set-search-reference (cursor-path))) - (set-search-filter) - (with-buffer chat-tab-search-target - (perform-search)))) - (tm-define (chat-tab-search-next forward?) (when (and chat-tab-search-target chat-tab-search-aux) (with-buffer chat-tab-search-target diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index 5e0c25e30e..15939a88c5 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -256,10 +256,8 @@ get_or_create_bar (QWidget* parent) { auto* bar = new QTMFloatingSearchBar (parent); bars[parent]= bar; - QObject::connect (parent, &QObject::destroyed, [parent] () { - auto& b= searchBars (); - auto i= b.find (parent); - if (i != b.end ()) b.erase (i); + QObject::connect (parent, &QObject::destroyed, [] (QObject* obj) { + searchBars ().remove (static_cast (obj)); }); bar->setFixedWidth (DpiUtils::scaled (kBarWidth)); From 7ed314996b32d3a0b78215e4327790224c8f9f9b Mon Sep 17 00:00:00 2001 From: Yuki Date: Fri, 29 May 2026 16:05:15 +0800 Subject: [PATCH 15/35] =?UTF-8?q?kbd=E6=96=87=E6=9C=AC=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/plugins/lang/dic/en_US/zh_CN.scm | 3 ++- src/Plugins/Qt/qt_floating_search_bar.cpp | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm b/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm index da05aaf957..0fc2b0f681 100644 --- a/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm +++ b/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm @@ -1874,7 +1874,8 @@ ("previous screen" "后一屏") ("previous similar" "相似的(前一个)") ("previous" "前一个") -("Previous (Shift+Enter)" "上一个 (Shift+Enter)") +("Previous (Ctrl+Enter)" "上一个 (Ctrl+Enter)") +("Previous (Cmd+Enter)" "上一个 (Cmd+Enter)") ("primary" "") ("prime" "") ("print all to file" "全部打印为文件") diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index 15939a88c5..2c39e0a64a 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -101,7 +101,11 @@ QTMFloatingSearchBar::QTMFloatingSearchBar (QWidget* parent) prevBtn->setObjectName ("floating-search-prev"); prevBtn->setFixedSize (DpiUtils::scaled (kBtnSize), DpiUtils::scaled (kBtnSize)); - prevBtn->setToolTip (qt_translate ("Previous (Shift+Enter)")); +#ifdef Q_OS_MAC + prevBtn->setToolTip (qt_translate ("Previous (Cmd+Enter)")); +#else + prevBtn->setToolTip (qt_translate ("Previous (Ctrl+Enter)")); +#endif prevBtn->setStyleSheet (btnRadiusStyle); btnRow->addWidget (prevBtn); From 5258bb66e04f090e325235aabec2400f42b047c0 Mon Sep 17 00:00:00 2001 From: Yuki Date: Fri, 29 May 2026 17:00:06 +0800 Subject: [PATCH 16/35] fix --- TeXmacs/progs/generic/search-widgets.scm | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index c55e2c371c..21dd6be756 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -167,8 +167,10 @@ ;; ---- ;; 此函数用于管理搜索辅助缓冲区的生命周期,确保每个主文档视图有唯一的搜索缓冲区。 (tm-define (search-buffer) - ;; chat tab 搜索激活期间,直接返回保存的 aux buffer - (if (and chat-tab-search-active? chat-tab-search-aux) + ;; chat tab 搜索激活且当前在 chat tab 上下文中时,返回保存的 aux buffer + (if (and chat-tab-search-active? chat-tab-search-aux + (or (== (current-buffer) chat-tab-search-aux) + (== (current-buffer) chat-tab-search-target))) chat-tab-search-aux (with u (current-buffer) @@ -342,8 +344,10 @@ ) ;tm-define (tm-define (master-buffer) - ;; chat tab 搜索激活期间,直接返回保存的 target buffer - (if (and chat-tab-search-active? chat-tab-search-target) + ;; chat tab 搜索激活且当前在 chat tab 上下文中时,返回保存的 target buffer + (if (and chat-tab-search-active? chat-tab-search-target + (or (== (current-buffer) chat-tab-search-aux) + (== (current-buffer) chat-tab-search-target))) chat-tab-search-target (and (buffer-exists? (search-buffer)) (with mas @@ -369,7 +373,9 @@ ) ;tm-define (tm-define (inside-search-buffer?) - (if chat-tab-search-active? + (if (and chat-tab-search-active? + (or (== (current-buffer) chat-tab-search-aux) + (== (current-buffer) chat-tab-search-target))) (== (current-buffer) chat-tab-search-aux) (== (current-buffer) (search-buffer)))) @@ -661,7 +667,7 @@ (set! search-serial (+ search-serial 1)) (with-buffer (master-buffer) (cancel-alt-selection "alternate")) (set-search-window-state #f #f) - (buffer-focus u #t) + (when u (buffer-focus u #t)) ) ;tm-define ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1462,6 +1468,7 @@ ) ;tm-define (tm-define (toolbar-search-end) + (when chat-tab-search-active? (chat-tab-search-close)) (cancel-alt-selection "alternate") (search-show-all) (set! search-filter-out? #f) From 0296847be214d41ec4f4c79a97ba79aee2c4cdbc Mon Sep 17 00:00:00 2001 From: Yuki Date: Fri, 29 May 2026 21:03:51 +0800 Subject: [PATCH 17/35] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=BC=80=E5=85=B3?= =?UTF-8?q?=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/progs/generic/search-widgets.scm | 27 ++++++++++++++------ src/Plugins/Qt/qt_chat_controller.cpp | 16 ++++++++++++ src/Plugins/Qt/qt_chat_controller.hpp | 4 +++ src/Plugins/Qt/qt_floating_search_bar.cpp | 30 +++++++++++++++++------ src/Scheme/L5/glue_widget.lua | 7 ++++++ 5 files changed, 70 insertions(+), 14 deletions(-) diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index 21dd6be756..25c6784507 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -456,7 +456,7 @@ (check-same? (tm-children new-env) (tm-children old-env))))))) (lambda (key msg . rest) (display* "Warning: accept-search-result? error: " msg "\n") - #t)) + (if chat-tab-search-active? #t #f))) ) ;or ) ;define @@ -1183,6 +1183,14 @@ (with-buffer chat-tab-search-target (cancel-alt-selection "alternate")) (set-search-window-state #f #f) + (let* ((msg-url (url->system chat-tab-search-target)) + (in-url (if (string-starts? msg-url "tmfs://chat-message-") + (string-append "tmfs://chat-input-" + (substring msg-url + (string-length "tmfs://chat-message-"))) + ""))) + (when (not (== in-url "")) + (buffer-focus (string->url in-url) #t))) (set! chat-tab-search-active? #f) (set! chat-tab-search-target #f) (set! chat-tab-search-aux #f))) @@ -1727,13 +1735,18 @@ (with buf (current-buffer) (with sid (chat-buffer-session-id buf) (cond - ((and sid - (chat-message-buffer-has-content? - (string->url (string-append "tmfs://chat-message-" sid)))) - (chat-tab-search-init - (string->url (string-append "tmfs://chat-message-" sid)))) + ((string-starts? (url->system buf) "tmfs://chat-") + ;; chat tab 任何缓冲区:通过 sid 或胶水函数找到消息缓冲区 + (let* ((msg-url (if sid + (string-append "tmfs://chat-message-" sid) + (qt-chat-tab-active-message-buffer-url))) + (msg-u (and msg-url (not (== msg-url "")) + (string->url msg-url)))) + (if (and msg-u (chat-message-buffer-has-content? msg-u)) + (chat-tab-search-init msg-u) + (noop)))) ((string-starts? (url->system buf) "tmfs:") - ;; 其他 tmfs:// 缓冲区不支持搜索 + ;; 其他 tmfs:// 缓冲区保持禁用搜索 (noop)) (else (set! search-replace-text diff --git a/src/Plugins/Qt/qt_chat_controller.cpp b/src/Plugins/Qt/qt_chat_controller.cpp index 64ac343820..9652d2ab34 100644 --- a/src/Plugins/Qt/qt_chat_controller.cpp +++ b/src/Plugins/Qt/qt_chat_controller.cpp @@ -712,3 +712,19 @@ qt_chat_tab_restore_session (string sessionId, string title, string model, get_chat_controller ()->restoreSessionMeta ( sessionId, title, model, isArchived, createdAt, expandCount, isThinking); } + +string +ChatController::activeSessionMessageBufferUrl () const { + if (!view_) return ""; + ChatSidebar* sidebar= view_->sidebar (); + if (!sidebar) return ""; + string activeId= sidebar->activeSessionId (); + if (is_empty (activeId)) return ""; + url msgBufUrl= ChatSessionManager::messageBufferUrl (activeId); + return as_string (msgBufUrl); +} + +string +qt_chat_tab_active_message_buffer_url () { + return get_chat_controller ()->activeSessionMessageBufferUrl (); +} diff --git a/src/Plugins/Qt/qt_chat_controller.hpp b/src/Plugins/Qt/qt_chat_controller.hpp index 06d51c6482..651263f7eb 100644 --- a/src/Plugins/Qt/qt_chat_controller.hpp +++ b/src/Plugins/Qt/qt_chat_controller.hpp @@ -133,6 +133,8 @@ class ChatController : public QObject { */ void destroyView (); + string activeSessionMessageBufferUrl () const; + private: QTChatTabWidget* view_= nullptr; ///< View 指针,由 createView 创建 ChatSessionManager sessionManager_; ///< 会话管理器 @@ -234,4 +236,6 @@ void qt_chat_tab_restore_session (string sessionId, string title, string model, string archived, string createdAt, int defaultExpandCount, string thinking); +string qt_chat_tab_active_message_buffer_url (); + #endif // QT_CHAT_CONTROLLER_HPP diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index 2c39e0a64a..38a90a7349 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -158,6 +159,8 @@ QTMFloatingSearchBar::~QTMFloatingSearchBar () { void QTMFloatingSearchBar::setSearchInput (QWidget* input) { if (inputQW_) { + QAbstractScrollArea* oldArea= inputQW_->findChild (); + if (oldArea) oldArea->removeEventFilter (this); rowLayout_->removeWidget (inputQW_); inputQW_->deleteLater (); } @@ -167,6 +170,7 @@ QTMFloatingSearchBar::setSearchInput (QWidget* input) { QAbstractScrollArea* scrollArea= input->findChild (); if (scrollArea) { scrollArea->viewport ()->setBackgroundRole (QPalette::Base); + scrollArea->installEventFilter (this); } rowLayout_->insertWidget (0, input, 1); } @@ -176,7 +180,11 @@ void QTMFloatingSearchBar::activate () { show (); raise (); - if (inputQW_) inputQW_->setFocus (); + if (inputQW_) { + QAbstractScrollArea* sa= inputQW_->findChild (); + if (sa) sa->setFocus (); + else inputQW_->setFocus (); + } } void @@ -222,6 +230,16 @@ QTMFloatingSearchBar::eventFilter (QObject* watched, QEvent* event) { isVisible ()) { reposition (); } + if (event->type () == QEvent::KeyPress && isVisible () && inputQW_) { + auto* ke= static_cast (event); + if (ke->key () == Qt::Key_Escape) { + QAbstractScrollArea* sa= inputQW_->findChild (); + if (sa && watched == sa) { + emit closeRequested (); + return true; + } + } + } return QWidget::eventFilter (watched, event); } @@ -260,9 +278,9 @@ get_or_create_bar (QWidget* parent) { auto* bar = new QTMFloatingSearchBar (parent); bars[parent]= bar; - QObject::connect (parent, &QObject::destroyed, [] (QObject* obj) { - searchBars ().remove (static_cast (obj)); - }); + QWidget* pw= parent; + QObject::connect (parent, &QObject::destroyed, + [pw] () { searchBars ().remove (pw); }); bar->setFixedWidth (DpiUtils::scaled (kBarWidth)); return bar; @@ -366,9 +384,7 @@ void qt_floating_search_init (string aux_url_str) { QWidget* parent= get_provider_parent (); if (!parent) return; - if (!qt_floating_search_bar_init (parent, aux_url_str)) return; - auto* bar= get_or_create_bar (parent); - if (bar) bar->activate (); + qt_floating_search_bar_init (parent, aux_url_str); } void diff --git a/src/Scheme/L5/glue_widget.lua b/src/Scheme/L5/glue_widget.lua index 4e1756ecd9..ba9da6918b 100644 --- a/src/Scheme/L5/glue_widget.lua +++ b/src/Scheme/L5/glue_widget.lua @@ -527,6 +527,13 @@ function main() "string" } }, + { + scm_name = "qt-chat-tab-active-message-buffer-url", + cpp_name = "qt_chat_tab_active_message_buffer_url", + ret_type = "string", + arg_list = {} + }, + { scm_name = "qt-floating-search", cpp_name = "qt_floating_search", From 87f30062d10d897361a2e6ec7f23560d586b1fe4 Mon Sep 17 00:00:00 2001 From: Yuki Date: Sat, 30 May 2026 01:08:05 +0800 Subject: [PATCH 18/35] img --- .../misc/images/floating-search/math-mode-white.svg | 9 +++++++++ TeXmacs/misc/images/floating-search/math-mode.svg | 9 +++++++++ .../misc/images/floating-search/text-mode-white.svg | 10 ++++++++++ TeXmacs/misc/images/floating-search/text-mode.svg | 10 ++++++++++ TeXmacs/misc/images/images.qrc | 4 ++++ 5 files changed, 42 insertions(+) create mode 100644 TeXmacs/misc/images/floating-search/math-mode-white.svg create mode 100644 TeXmacs/misc/images/floating-search/math-mode.svg create mode 100644 TeXmacs/misc/images/floating-search/text-mode-white.svg create mode 100644 TeXmacs/misc/images/floating-search/text-mode.svg diff --git a/TeXmacs/misc/images/floating-search/math-mode-white.svg b/TeXmacs/misc/images/floating-search/math-mode-white.svg new file mode 100644 index 0000000000..c88232ab2b --- /dev/null +++ b/TeXmacs/misc/images/floating-search/math-mode-white.svg @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/TeXmacs/misc/images/floating-search/math-mode.svg b/TeXmacs/misc/images/floating-search/math-mode.svg new file mode 100644 index 0000000000..d78999d32e --- /dev/null +++ b/TeXmacs/misc/images/floating-search/math-mode.svg @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/TeXmacs/misc/images/floating-search/text-mode-white.svg b/TeXmacs/misc/images/floating-search/text-mode-white.svg new file mode 100644 index 0000000000..476050b613 --- /dev/null +++ b/TeXmacs/misc/images/floating-search/text-mode-white.svg @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/TeXmacs/misc/images/floating-search/text-mode.svg b/TeXmacs/misc/images/floating-search/text-mode.svg new file mode 100644 index 0000000000..7ff9c9e988 --- /dev/null +++ b/TeXmacs/misc/images/floating-search/text-mode.svg @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/TeXmacs/misc/images/images.qrc b/TeXmacs/misc/images/images.qrc index d3215e4865..c08353c8d1 100644 --- a/TeXmacs/misc/images/images.qrc +++ b/TeXmacs/misc/images/images.qrc @@ -27,6 +27,8 @@ floating-search/down.svg floating-search/up.svg + floating-search/text-mode.svg + floating-search/math-mode.svg ocr-button/left-align-white.svg @@ -48,6 +50,8 @@ floating-search/down-white.svg floating-search/up-white.svg + floating-search/text-mode-white.svg + floating-search/math-mode-white.svg tutorial/ocr-tutorial.gif From f739634b3dc13ac12333456538f48b88030cc6e5 Mon Sep 17 00:00:00 2001 From: Yuki Date: Sat, 30 May 2026 01:12:14 +0800 Subject: [PATCH 19/35] =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E4=B8=B4=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/misc/themes/liii-night.css | 8 ++++++ TeXmacs/misc/themes/liii.css | 8 ++++++ TeXmacs/progs/generic/search-widgets.scm | 33 ++++++++++++++++++++--- src/Plugins/Qt/qt_floating_search_bar.cpp | 24 +++++++++++++++++ src/Plugins/Qt/qt_floating_search_bar.hpp | 6 ++++- 5 files changed, 75 insertions(+), 4 deletions(-) diff --git a/TeXmacs/misc/themes/liii-night.css b/TeXmacs/misc/themes/liii-night.css index 68ffa62261..e2bbc1f0cf 100644 --- a/TeXmacs/misc/themes/liii-night.css +++ b/TeXmacs/misc/themes/liii-night.css @@ -1526,6 +1526,14 @@ QWidget#floating_search_bar QToolButton#floating-search-close { qproperty-icon: url(":/tabpage/close-white.svg"); } +QWidget#floating_search_bar QToolButton#floating-search-mode-text { + qproperty-icon: url(":/floating-search/text-mode-white.svg"); +} + +QWidget#floating_search_bar QToolButton#floating-search-mode-math { + qproperty-icon: url(":/floating-search/math-mode-white.svg"); +} + QWidget#floating_search_bar QWidget#floating-search-input { border: 1px solid #555555; } diff --git a/TeXmacs/misc/themes/liii.css b/TeXmacs/misc/themes/liii.css index 9640c6f3fa..26a3b513f4 100644 --- a/TeXmacs/misc/themes/liii.css +++ b/TeXmacs/misc/themes/liii.css @@ -1444,6 +1444,14 @@ QWidget#floating_search_bar QToolButton#floating-search-close { qproperty-icon: url(":/tabpage/close.svg"); } +QWidget#floating_search_bar QToolButton#floating-search-mode-text { + qproperty-icon: url(":/floating-search/text-mode.svg"); +} + +QWidget#floating_search_bar QToolButton#floating-search-mode-math { + qproperty-icon: url(":/floating-search/math-mode.svg"); +} + QWidget#floating_search_bar QWidget#floating-search-input { border: 1px solid #d0d0d0; } diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index 25c6784507..2a4047aadf 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -443,7 +443,10 @@ ) ;define (define (accept-search-result? p) - (or (== (get-init "mode") "src") + (or (and chat-tab-search-active? + (== chat-tab-search-mode "math") + (search-path-inside-math? p)) + (== (get-init "mode") "src") (catch #t (lambda () (let* ((buf (buffer-tree)) @@ -481,8 +484,11 @@ (define (tree-perform-search t what p limit) (let* ((source-mode 2) (old-mode (get-access-mode)) - (new-mode (if (== (get-init "mode") "src") source-mode old-mode)) - ) ; + (new-mode (if (or (== (get-init "mode") "src") + (and chat-tab-search-active? + (== chat-tab-search-mode "math"))) + source-mode old-mode)) + ) ; (set-access-mode new-mode) (let* ((cp (cDr (cursor-path))) (pos (if (list-starts? cp p) (list-tail cp (length p)) (list))) @@ -1149,6 +1155,27 @@ (define chat-tab-search-target #f) (define chat-tab-search-aux #f) (define chat-tab-search-active? #f) +(define chat-tab-search-mode "text") + +(define (chat-tab-search-toggle-mode) + (set! chat-tab-search-mode + (if (== chat-tab-search-mode "text") "math" "text")) + (with-buffer (master-buffer) + (if (== chat-tab-search-mode "math") + (begin (set-access-mode 2) (init-env "mode" "math")) + (begin (set-access-mode 0) (init-default "mode")))) + (perform-search*)) + +(define (search-path-inside-math? p) + (with-buffer (master-buffer) + (let* ((buf (buffer-tree)) + (dr (cDr p)) + (len (length dr))) + (let loop ((i (- len 1))) + (if (< i 0) #f + (let ((node (subtree buf (sublist dr 0 (- i 1))))) + (if (tm-func? node 'math) #t + (loop (- i 1))))))))) (define (chat-tab-search-init target-buf) (set! chat-tab-search-target target-buf) diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index 38a90a7349..df281c077e 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -98,6 +99,14 @@ QTMFloatingSearchBar::QTMFloatingSearchBar (QWidget* parent) "QToolButton { border-radius: %1px; padding: 0px; margin: 0px; }") .arg (DpiUtils::scaled (kBtnRadius)); + modeBtn_= new QToolButton (this); + modeBtn_->setObjectName ("floating-search-mode-text"); + modeBtn_->setFixedSize (DpiUtils::scaled (kBtnSize), + DpiUtils::scaled (kBtnSize)); + modeBtn_->setToolTip (qt_translate ("Toggle search mode (text/math)")); + modeBtn_->setStyleSheet (btnRadiusStyle); + btnRow->addWidget (modeBtn_); + auto* prevBtn= new QToolButton (this); prevBtn->setObjectName ("floating-search-prev"); prevBtn->setFixedSize (DpiUtils::scaled (kBtnSize), @@ -146,6 +155,12 @@ QTMFloatingSearchBar::QTMFloatingSearchBar (QWidget* parent) &QTMFloatingSearchBar::findPreviousRequested); connect (closeBtn, &QToolButton::clicked, this, &QTMFloatingSearchBar::closeRequested); + connect (modeBtn_, &QToolButton::clicked, this, [this] () { + bool isMath= (modeBtn_->objectName () == + QStringLiteral ("floating-search-mode-text")); + setModeIcon (isMath); + eval_scheme ("(chat-tab-search-toggle-mode)"); + }); if (parent) parent->installEventFilter (this); @@ -224,6 +239,15 @@ QTMFloatingSearchBar::connectSignals () { } } +void +QTMFloatingSearchBar::setModeIcon (bool mathMode) { + if (!modeBtn_) return; + modeBtn_->setObjectName (mathMode ? "floating-search-mode-math" + : "floating-search-mode-text"); + modeBtn_->style ()->unpolish (modeBtn_); + modeBtn_->style ()->polish (modeBtn_); +} + bool QTMFloatingSearchBar::eventFilter (QObject* watched, QEvent* event) { if (event->type () == QEvent::Resize && watched == parent () && diff --git a/src/Plugins/Qt/qt_floating_search_bar.hpp b/src/Plugins/Qt/qt_floating_search_bar.hpp index aaef43d6f0..024865274d 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.hpp +++ b/src/Plugins/Qt/qt_floating_search_bar.hpp @@ -32,7 +32,7 @@ class QTMFloatingSearchBar : public QWidget { public: explicit QTMFloatingSearchBar (QWidget* parent= nullptr); - ~QTMFloatingSearchBar (); + ~QTMFloatingSearchBar () override; /// 设置嵌入的搜索输入框。旧的输入框(如有)会被移除并 deleteLater。 void setSearchInput (QWidget* input); @@ -44,11 +44,13 @@ class QTMFloatingSearchBar : public QWidget { /// 配置按钮点击时求值的 Scheme 命令。 void setSchemeCallbacks (const string& next_cmd, const string& prev_cmd, const string& close_cmd); + void setModeIcon (bool mathMode); signals: void findNextRequested (); void findPreviousRequested (); void closeRequested (); + void modeToggled (); protected: bool eventFilter (QObject* watched, QEvent* event) override; @@ -61,10 +63,12 @@ class QTMFloatingSearchBar : public QWidget { QHBoxLayout* rowLayout_= nullptr; QWidget* inputQW_ = nullptr; QLabel* infoLbl_ = nullptr; + QToolButton* modeBtn_ = nullptr; string next_cmd_; string prev_cmd_; string close_cmd_; + string mode_cmd_; bool callbacksConnected_= false; }; From ba0cccdf3c8809c1825e963461d2aae61ff332da Mon Sep 17 00:00:00 2001 From: Yuki Date: Sat, 30 May 2026 21:20:04 +0800 Subject: [PATCH 20/35] fix-include --- src/Plugins/Qt/qt_floating_search_bar.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Plugins/Qt/qt_floating_search_bar.hpp b/src/Plugins/Qt/qt_floating_search_bar.hpp index 024865274d..ad6e37e656 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.hpp +++ b/src/Plugins/Qt/qt_floating_search_bar.hpp @@ -14,6 +14,7 @@ #include #include +#include #include #include "string.hpp" From 9accdff3ac535181e01acc2fa6ad0f5e473018fb Mon Sep 17 00:00:00 2001 From: Yuki Date: Sat, 30 May 2026 22:50:59 +0800 Subject: [PATCH 21/35] math-search --- TeXmacs/progs/generic/search-widgets.scm | 282 ++++++++++++++-------- src/Plugins/Qt/qt_floating_search_bar.cpp | 22 +- src/Plugins/Qt/qt_floating_search_bar.hpp | 12 +- src/Scheme/L5/glue_widget.lua | 1 + 4 files changed, 210 insertions(+), 107 deletions(-) diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index 2a4047aadf..2444b48892 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -117,9 +117,14 @@ (qt-floating-search-set-match-info 0 0) (let* ((parts (string-split index-str #\/)) (cur (string->number (car parts))) - (tot (string->number (cadr parts)))) + (tot (string->number (cadr parts))) + ) ; (when (and cur tot) - (qt-floating-search-set-match-info cur tot))))) + (qt-floating-search-set-match-info cur tot) + ) ;when + ) ;let* + ) ;if + ) ;when (if (== index-str "") (set-auxiliary-widget-title (translate search-replace-text)) (set-auxiliary-widget-title (string-append (translate search-replace-text) " (" index-str ")") @@ -168,9 +173,12 @@ ;; 此函数用于管理搜索辅助缓冲区的生命周期,确保每个主文档视图有唯一的搜索缓冲区。 (tm-define (search-buffer) ;; chat tab 搜索激活且当前在 chat tab 上下文中时,返回保存的 aux buffer - (if (and chat-tab-search-active? chat-tab-search-aux - (or (== (current-buffer) chat-tab-search-aux) - (== (current-buffer) chat-tab-search-target))) + (if (and chat-tab-search-active? + chat-tab-search-aux + (or (== (current-buffer) chat-tab-search-aux) + (== (current-buffer) chat-tab-search-target) + ) ;or + ) ;and chat-tab-search-aux (with u (current-buffer) @@ -345,9 +353,12 @@ (tm-define (master-buffer) ;; chat tab 搜索激活且当前在 chat tab 上下文中时,返回保存的 target buffer - (if (and chat-tab-search-active? chat-tab-search-target - (or (== (current-buffer) chat-tab-search-aux) - (== (current-buffer) chat-tab-search-target))) + (if (and chat-tab-search-active? + chat-tab-search-target + (or (== (current-buffer) chat-tab-search-aux) + (== (current-buffer) chat-tab-search-target) + ) ;or + ) ;and chat-tab-search-target (and (buffer-exists? (search-buffer)) (with mas @@ -374,10 +385,14 @@ (tm-define (inside-search-buffer?) (if (and chat-tab-search-active? - (or (== (current-buffer) chat-tab-search-aux) - (== (current-buffer) chat-tab-search-target))) + (or (== (current-buffer) chat-tab-search-aux) + (== (current-buffer) chat-tab-search-target) + ) ;or + ) ;and (== (current-buffer) chat-tab-search-aux) - (== (current-buffer) (search-buffer)))) + (== (current-buffer) (search-buffer)) + ) ;if +) ;tm-define (tm-define (inside-replace-buffer?) (== (current-buffer) (replace-buffer))) @@ -444,22 +459,29 @@ (define (accept-search-result? p) (or (and chat-tab-search-active? - (== chat-tab-search-mode "math") - (search-path-inside-math? p)) - (== (get-init "mode") "src") + (== chat-tab-search-mode "math") + (search-path-inside-math? p) + ) ;and + (== (get-init "mode") "src") (catch #t (lambda () - (let* ((buf (buffer-tree)) - (rel (path-strip (cDr p) (tree->path buf)))) - (if (not rel) #t + (let* ((buf (buffer-tree)) (rel (path-strip (cDr p) (tree->path buf)))) + (if (not rel) + #t (let* ((initial (cons 'attr (get-main-attrs get-init))) (old-env (get-search-filter)) - (new-env (tree-descendant-env* buf rel initial))) - (if (not new-env) #t - (check-same? (tm-children new-env) (tm-children old-env))))))) + (new-env (tree-descendant-env* buf rel initial)) + ) ; + (if (not new-env) #t (check-same? (tm-children new-env) (tm-children old-env))) + ) ;let* + ) ;if + ) ;let* + ) ;lambda (lambda (key msg . rest) (display* "Warning: accept-search-result? error: " msg "\n") - (if chat-tab-search-active? #t #f))) + (if chat-tab-search-active? #t #f) + ) ;lambda + ) ;catch ) ;or ) ;define @@ -485,10 +507,13 @@ (let* ((source-mode 2) (old-mode (get-access-mode)) (new-mode (if (or (== (get-init "mode") "src") - (and chat-tab-search-active? - (== chat-tab-search-mode "math"))) - source-mode old-mode)) - ) ; + (and chat-tab-search-active? (== chat-tab-search-mode "math")) + ) ;or + source-mode + old-mode + ) ;if + ) ;new-mode + ) ; (set-access-mode new-mode) (let* ((cp (cDr (cursor-path))) (pos (if (list-starts? cp p) (list-tail cp (length p)) (list))) @@ -673,7 +698,9 @@ (set! search-serial (+ search-serial 1)) (with-buffer (master-buffer) (cancel-alt-selection "alternate")) (set-search-window-state #f #f) - (when u (buffer-focus u #t)) + (when u + (buffer-focus u #t) + ) ;when ) ;tm-define ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1153,29 +1180,61 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define chat-tab-search-target #f) + (define chat-tab-search-aux #f) + (define chat-tab-search-active? #f) + (define chat-tab-search-mode "text") -(define (chat-tab-search-toggle-mode) - (set! chat-tab-search-mode - (if (== chat-tab-search-mode "text") "math" "text")) - (with-buffer (master-buffer) - (if (== chat-tab-search-mode "math") - (begin (set-access-mode 2) (init-env "mode" "math")) - (begin (set-access-mode 0) (init-default "mode")))) - (perform-search*)) +(tm-define (chat-tab-search-toggle-mode) + (set! chat-tab-search-mode (if (== chat-tab-search-mode "text") "math" "text")) + ;; 重建 texmacs_input_widget,使其拥有正确的数学/文本环境 + ;; (与侧边栏 (with mode "math" ...) 行为一致) + (when chat-tab-search-aux + (with-buffer chat-tab-search-aux + (if (== chat-tab-search-mode "math") + (init-env "mode" "math") + (init-default "mode") + ) ;if + ) ;with-buffer + (qt-floating-search-init (url->string chat-tab-search-aux) chat-tab-search-mode) + ;; 重建 widget 后重新同步暗色样式 + (when (== (get-preference "gui theme") "liii-night") + (with-buffer chat-tab-search-aux + (when (not (has-style-package? "dark")) + (add-style-package "dark") + ) ;when + ) ;with-buffer + ) ;when + ) ;when + ;; 更新搜索过滤器(在 target buffer 上下文中) + ;; accept-search-result? 对 math 模式有特殊处理绕过 filter, + ;; 但对 text 模式需要正确的 filter + (when chat-tab-search-target + (with-buffer chat-tab-search-target (set-search-filter)) + ) ;when + ;; tree-perform-search 已通过 chat-tab-search-active? + chat-tab-search-mode + ;; 正确处理 access mode,无需手动修改 master buffer + (perform-search*) +) ;tm-define (define (search-path-inside-math? p) (with-buffer (master-buffer) - (let* ((buf (buffer-tree)) - (dr (cDr p)) - (len (length dr))) - (let loop ((i (- len 1))) - (if (< i 0) #f - (let ((node (subtree buf (sublist dr 0 (- i 1))))) - (if (tm-func? node 'math) #t - (loop (- i 1))))))))) + (let* ((buf (buffer-tree)) (dr (cDr p)) (len (length dr))) + ;; 从父节点向上遍历祖先,检查是否有 math 节点 + (let loop + ((i (- len 1))) + (if (< i 1) + #f + (let ((node (subtree buf (sublist dr 0 i)))) + (if (tm-func? node 'math) #t (loop (- i 1))) + ) ;let + ) ;if + ) ;let + ) ;let* + ) ;with-buffer +) ;define (define (chat-tab-search-init target-buf) (set! chat-tab-search-target target-buf) @@ -1184,43 +1243,58 @@ (set! chat-tab-search-active? #t) (buffer-set-master aux target-buf) (set-search-window-state #t #t) - (with-buffer target-buf - (set-search-reference (cursor-path))) + (with-buffer target-buf (set-search-reference (cursor-path))) (set-search-filter) (set! search-filter-out? #f) - (qt-floating-search-set-callbacks - "(chat-tab-search-next #t)" "(chat-tab-search-next #f)" "(chat-tab-search-close)") - (qt-floating-search-init (url->string aux)) + ;; 设置搜索缓冲区的初始 init env(与侧边栏 texmacs-input 行为一致) + (with-buffer aux (init-env "mode" chat-tab-search-mode)) + (qt-floating-search-set-callbacks "(chat-tab-search-next #t)" + "(chat-tab-search-next #f)" + "(chat-tab-search-close)" + ) ;qt-floating-search-set-callbacks + (qt-floating-search-init (url->string aux) chat-tab-search-mode) ;; 同步暗色样式到搜索缓冲区 (when (== (get-preference "gui theme") "liii-night") (with-buffer aux (when (not (has-style-package? "dark")) - (add-style-package "dark")))) - (qt-floating-search "true"))) + (add-style-package "dark") + ) ;when + ) ;with-buffer + ) ;when + (qt-floating-search "true") + ) ;let +) ;define (tm-define (chat-tab-search-next forward?) (when (and chat-tab-search-target chat-tab-search-aux) - (with-buffer chat-tab-search-target - (search-rotate-match forward?)))) + (with-buffer chat-tab-search-target (search-rotate-match forward?)) + ) ;when +) ;tm-define (tm-define (chat-tab-search-close) (when chat-tab-search-target (search-show-all) (set! search-serial (+ search-serial 1)) - (with-buffer chat-tab-search-target - (cancel-alt-selection "alternate")) + (with-buffer chat-tab-search-target (cancel-alt-selection "alternate")) (set-search-window-state #f #f) (let* ((msg-url (url->system chat-tab-search-target)) (in-url (if (string-starts? msg-url "tmfs://chat-message-") (string-append "tmfs://chat-input-" - (substring msg-url - (string-length "tmfs://chat-message-"))) - ""))) + (substring msg-url (string-length "tmfs://chat-message-")) + ) ;string-append + "" + ) ;if + ) ;in-url + ) ; (when (not (== in-url "")) - (buffer-focus (string->url in-url) #t))) + (buffer-focus (string->url in-url) #t) + ) ;when + ) ;let* (set! chat-tab-search-active? #f) (set! chat-tab-search-target #f) - (set! chat-tab-search-aux #f))) + (set! chat-tab-search-aux #f) + ) ;when +) ;tm-define ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Search and replace widget @@ -1503,7 +1577,9 @@ ) ;tm-define (tm-define (toolbar-search-end) - (when chat-tab-search-active? (chat-tab-search-close)) + (when chat-tab-search-active? + (chat-tab-search-close) + ) ;when (cancel-alt-selection "alternate") (search-show-all) (set! search-filter-out? #f) @@ -1738,53 +1814,69 @@ (define-preferences ("toolbar search" "on" noop) ("toolbar replace" "on" noop)) (define (chat-message-buffer? buf) - (string-starts? (url->system buf) "tmfs://chat-message-")) + (string-starts? (url->system buf) "tmfs://chat-message-") +) ;define (define (chat-input-buffer? buf) - (string-starts? (url->system buf) "tmfs://chat-input-")) + (string-starts? (url->system buf) "tmfs://chat-input-") +) ;define (define (chat-buffer-session-id buf) - (with s (url->system buf) + (with s + (url->system buf) (cond ((chat-message-buffer? buf) - (substring s (string-length "tmfs://chat-message-"))) - ((chat-input-buffer? buf) - (substring s (string-length "tmfs://chat-input-"))) - (else #f)))) + (substring s (string-length "tmfs://chat-message-")) + ) ; + ((chat-input-buffer? buf) (substring s (string-length "tmfs://chat-input-"))) + (else #f) + ) ;cond + ) ;with +) ;define (define (chat-message-buffer-has-content? msg-buf) (and (buffer-exists? msg-buf) - (with body (buffer-get-body msg-buf) - (not (and (tm-func? body 'document 1) - (tree-empty? (tm-ref body 0))))))) + (with body + (buffer-get-body msg-buf) + (not (and (tm-func? body 'document 1) (tree-empty? (tm-ref body 0)))) + ) ;with + ) ;and +) ;define (tm-define (interactive-search) (:interactive #t) - (with buf (current-buffer) - (with sid (chat-buffer-session-id buf) - (cond - ((string-starts? (url->system buf) "tmfs://chat-") - ;; chat tab 任何缓冲区:通过 sid 或胶水函数找到消息缓冲区 - (let* ((msg-url (if sid - (string-append "tmfs://chat-message-" sid) - (qt-chat-tab-active-message-buffer-url))) - (msg-u (and msg-url (not (== msg-url "")) - (string->url msg-url)))) - (if (and msg-u (chat-message-buffer-has-content? msg-u)) - (chat-tab-search-init msg-u) - (noop)))) - ((string-starts? (url->system buf) "tmfs:") - ;; 其他 tmfs:// 缓冲区保持禁用搜索 - (noop)) - (else - (set! search-replace-text - (cond ((in-math?) "Only search in math mode") - ((in-prog?) "Only search in Program mode") - ((in-graphics?) "Graphics mode cannot search") - (else "Only search in text mode") - ) ;cond - ) ;set! - (set-boolean-preference "search-and-replace" #f) - (open-search)) + (with buf + (current-buffer) + (with sid + (chat-buffer-session-id buf) + (cond ((string-starts? (url->system buf) "tmfs://chat-") + ;; chat tab 任何缓冲区:通过 sid 或胶水函数找到消息缓冲区 + (let* ((msg-url (if sid + (string-append "tmfs://chat-message-" sid) + (qt-chat-tab-active-message-buffer-url) + ) ;if + ) ;msg-url + (msg-u (and msg-url (not (== msg-url "")) (string->url msg-url))) + ) ; + (if (and msg-u (chat-message-buffer-has-content? msg-u)) + (chat-tab-search-init msg-u) + (noop) + ) ;if + ) ;let* + ) ; + ((string-starts? (url->system buf) "tmfs:") + ;; 其他 tmfs:// 缓冲区保持禁用搜索 + (noop) + ) ; + (else (set! search-replace-text + (cond ((in-math?) "Only search in math mode") + ((in-prog?) "Only search in Program mode") + ((in-graphics?) "Graphics mode cannot search") + (else "Only search in text mode") + ) ;cond + ) ;set! + (set-boolean-preference "search-and-replace" #f) + (open-search) + ) ;else ) ;cond ) ;with ) ;with diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index df281c077e..213050d546 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -320,16 +320,24 @@ qt_floating_search_bar_show (QWidget* parent, bool show) { } bool -qt_floating_search_bar_init (QWidget* parent, const string& aux_url_str) { +qt_floating_search_bar_init (QWidget* parent, const string& aux_url_str, + const string& mode) { if (!parent) return false; auto* bar= get_or_create_bar (parent); url aux_url = url_system (aux_url_str); qreal searchZoom= DpiUtils::scaled (100) / 100.0; - tree doc (WITH, "font", "sys-chinese", "zoom-factor", as_string (searchZoom), - tree (DOCUMENT, "")); - tree sty= compound ("style", tree (TUPLE, "generic")); - widget tw= texmacs_input_widget (doc, sty, aux_url); + tree doc; + if (mode == "math") { + doc= tree (WITH, "font", "sys-chinese", "zoom-factor", + as_string (searchZoom), "mode", "math", tree (DOCUMENT, "")); + } + else { + doc= tree (WITH, "font", "sys-chinese", "zoom-factor", + as_string (searchZoom), tree (DOCUMENT, "")); + } + tree sty= compound ("style", tree (TUPLE, "generic")); + widget tw = texmacs_input_widget (doc, sty, aux_url); set_zoom_factor (tw, searchZoom); if (is_nil (tw)) { bar->hide (); @@ -405,10 +413,10 @@ qt_floating_search (string flag) { } void -qt_floating_search_init (string aux_url_str) { +qt_floating_search_init (string aux_url_str, string mode) { QWidget* parent= get_provider_parent (); if (!parent) return; - qt_floating_search_bar_init (parent, aux_url_str); + qt_floating_search_bar_init (parent, aux_url_str, mode); } void diff --git a/src/Plugins/Qt/qt_floating_search_bar.hpp b/src/Plugins/Qt/qt_floating_search_bar.hpp index ad6e37e656..325f99b83a 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.hpp +++ b/src/Plugins/Qt/qt_floating_search_bar.hpp @@ -81,8 +81,10 @@ class QTMFloatingSearchBar : public QWidget { void qt_floating_search_bar_show (QWidget* parent, bool show); /// 为 \a parent 创建/attach 搜索栏,并用绑定到 \a aux_url_str 的 -/// texmacs 输入框初始化。失败时返回 false。 -bool qt_floating_search_bar_init (QWidget* parent, const string& aux_url_str); +/// texmacs 输入框初始化。\a mode 为 "text" 或 "math",决定输入框的数学环境。 +/// 失败时返回 false。 +bool qt_floating_search_bar_init (QWidget* parent, const string& aux_url_str, + const string& mode); /// 更新 attach 到 \a parent 的搜索栏的匹配计数。 void qt_floating_search_bar_set_match_info (QWidget* parent, int current, @@ -112,9 +114,9 @@ void qt_floating_search_set_parent_provider ( /// Scheme 胶水函数:显示 ("true"/"#t") 或隐藏悬浮搜索栏。 void qt_floating_search (string flag); -/// Scheme 胶水函数:传入 search-buffer URL,创建 texmacs-input -/// 并嵌入浮动搜索栏。 -void qt_floating_search_init (string aux_url_str); +/// Scheme 胶水函数:传入 search-buffer URL 和 mode ("text"/"math"), +/// 创建 texmacs-input 并嵌入浮动搜索栏。 +void qt_floating_search_init (string aux_url_str, string mode); /// Scheme 胶水函数:更新浮动搜索栏的匹配计数显示。 void qt_floating_search_set_match_info (int current, int total); diff --git a/src/Scheme/L5/glue_widget.lua b/src/Scheme/L5/glue_widget.lua index ba9da6918b..5624668ee5 100644 --- a/src/Scheme/L5/glue_widget.lua +++ b/src/Scheme/L5/glue_widget.lua @@ -547,6 +547,7 @@ function main() cpp_name = "qt_floating_search_init", ret_type = "void", arg_list = { + "string", "string" } }, From 434ffc5d9346b62ce21a20d93b87669772b6d124 Mon Sep 17 00:00:00 2001 From: Yuki Date: Sat, 30 May 2026 23:00:40 +0800 Subject: [PATCH 22/35] =?UTF-8?q?=E4=BC=98=E5=8C=96chat-tab-search-toggle-?= =?UTF-8?q?mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/progs/generic/search-widgets.scm | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index 2444b48892..22e41895d2 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -1189,8 +1189,7 @@ (tm-define (chat-tab-search-toggle-mode) (set! chat-tab-search-mode (if (== chat-tab-search-mode "text") "math" "text")) - ;; 重建 texmacs_input_widget,使其拥有正确的数学/文本环境 - ;; (与侧边栏 (with mode "math" ...) 行为一致) + ;; 更新搜索缓冲区的 init env (when chat-tab-search-aux (with-buffer chat-tab-search-aux (if (== chat-tab-search-mode "math") @@ -1198,24 +1197,11 @@ (init-default "mode") ) ;if ) ;with-buffer - (qt-floating-search-init (url->string chat-tab-search-aux) chat-tab-search-mode) - ;; 重建 widget 后重新同步暗色样式 - (when (== (get-preference "gui theme") "liii-night") - (with-buffer chat-tab-search-aux - (when (not (has-style-package? "dark")) - (add-style-package "dark") - ) ;when - ) ;with-buffer - ) ;when ) ;when - ;; 更新搜索过滤器(在 target buffer 上下文中) - ;; accept-search-result? 对 math 模式有特殊处理绕过 filter, - ;; 但对 text 模式需要正确的 filter + ;; 更新搜索过滤器 (when chat-tab-search-target (with-buffer chat-tab-search-target (set-search-filter)) ) ;when - ;; tree-perform-search 已通过 chat-tab-search-active? + chat-tab-search-mode - ;; 正确处理 access mode,无需手动修改 master buffer (perform-search*) ) ;tm-define From cdea716ed15c13476e6cc3a1430ad29782eaec97 Mon Sep 17 00:00:00 2001 From: Yuki Date: Sat, 30 May 2026 23:35:44 +0800 Subject: [PATCH 23/35] =?UTF-8?q?=E9=87=8D=E5=91=BD=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/progs/generic/search-widgets.scm | 139 ++++++++++------------ src/Plugins/Qt/qt_floating_search_bar.cpp | 2 +- 2 files changed, 63 insertions(+), 78 deletions(-) diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index 22e41895d2..7d3865c190 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -112,7 +112,7 @@ (set! isreplace? #f) ) ;when ;; 更新浮动搜索栏的匹配计数 - (when chat-tab-search-target + (when floating-search-target (if (== index-str "") (qt-floating-search-set-match-info 0 0) (let* ((parts (string-split index-str #\/)) @@ -173,13 +173,13 @@ ;; 此函数用于管理搜索辅助缓冲区的生命周期,确保每个主文档视图有唯一的搜索缓冲区。 (tm-define (search-buffer) ;; chat tab 搜索激活且当前在 chat tab 上下文中时,返回保存的 aux buffer - (if (and chat-tab-search-active? - chat-tab-search-aux - (or (== (current-buffer) chat-tab-search-aux) - (== (current-buffer) chat-tab-search-target) + (if (and floating-search-active? + floating-search-aux + (or (== (current-buffer) floating-search-aux) + (== (current-buffer) floating-search-target) ) ;or ) ;and - chat-tab-search-aux + floating-search-aux (with u (current-buffer) (if (and (url-rooted-tmfs? u) @@ -353,13 +353,13 @@ (tm-define (master-buffer) ;; chat tab 搜索激活且当前在 chat tab 上下文中时,返回保存的 target buffer - (if (and chat-tab-search-active? - chat-tab-search-target - (or (== (current-buffer) chat-tab-search-aux) - (== (current-buffer) chat-tab-search-target) + (if (and floating-search-active? + floating-search-target + (or (== (current-buffer) floating-search-aux) + (== (current-buffer) floating-search-target) ) ;or ) ;and - chat-tab-search-target + floating-search-target (and (buffer-exists? (search-buffer)) (with mas (buffer-get-master (search-buffer)) @@ -384,12 +384,12 @@ ) ;tm-define (tm-define (inside-search-buffer?) - (if (and chat-tab-search-active? - (or (== (current-buffer) chat-tab-search-aux) - (== (current-buffer) chat-tab-search-target) + (if (and floating-search-active? + (or (== (current-buffer) floating-search-aux) + (== (current-buffer) floating-search-target) ) ;or ) ;and - (== (current-buffer) chat-tab-search-aux) + (== (current-buffer) floating-search-aux) (== (current-buffer) (search-buffer)) ) ;if ) ;tm-define @@ -458,30 +458,19 @@ ) ;define (define (accept-search-result? p) - (or (and chat-tab-search-active? - (== chat-tab-search-mode "math") - (search-path-inside-math? p) - ) ;and + (or (and floating-search-active? + (== floating-search-mode "math") + (search-path-inside-math? p)) (== (get-init "mode") "src") - (catch #t - (lambda () - (let* ((buf (buffer-tree)) (rel (path-strip (cDr p) (tree->path buf)))) - (if (not rel) - #t - (let* ((initial (cons 'attr (get-main-attrs get-init))) - (old-env (get-search-filter)) - (new-env (tree-descendant-env* buf rel initial)) - ) ; - (if (not new-env) #t (check-same? (tm-children new-env) (tm-children old-env))) - ) ;let* - ) ;if - ) ;let* - ) ;lambda - (lambda (key msg . rest) - (display* "Warning: accept-search-result? error: " msg "\n") - (if chat-tab-search-active? #t #f) - ) ;lambda - ) ;catch + (let* ((buf (buffer-tree)) + (rel (path-strip (cDr p) (tree->path buf))) + (initial (cons 'attr (get-main-attrs get-init))) + (old-env (get-search-filter)) + (new-env (tree-descendant-env* buf rel initial)) + ) ; + ;; (display* p " ~> " new-env "\n") + (check-same? (tm-children new-env) (tm-children old-env)) + ) ;let* ) ;or ) ;define @@ -507,12 +496,8 @@ (let* ((source-mode 2) (old-mode (get-access-mode)) (new-mode (if (or (== (get-init "mode") "src") - (and chat-tab-search-active? (== chat-tab-search-mode "math")) - ) ;or - source-mode - old-mode - ) ;if - ) ;new-mode + (and floating-search-active? (== floating-search-mode "math"))) + source-mode old-mode)) ) ; (set-access-mode new-mode) (let* ((cp (cDr (cursor-path))) @@ -1179,28 +1164,28 @@ ;; Chat tab search (floating search bar) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(define chat-tab-search-target #f) +(define floating-search-target #f) -(define chat-tab-search-aux #f) +(define floating-search-aux #f) -(define chat-tab-search-active? #f) +(define floating-search-active? #f) -(define chat-tab-search-mode "text") +(define floating-search-mode "text") -(tm-define (chat-tab-search-toggle-mode) - (set! chat-tab-search-mode (if (== chat-tab-search-mode "text") "math" "text")) +(tm-define (floating-search-toggle-mode) + (set! floating-search-mode (if (== floating-search-mode "text") "math" "text")) ;; 更新搜索缓冲区的 init env - (when chat-tab-search-aux - (with-buffer chat-tab-search-aux - (if (== chat-tab-search-mode "math") + (when floating-search-aux + (with-buffer floating-search-aux + (if (== floating-search-mode "math") (init-env "mode" "math") (init-default "mode") ) ;if ) ;with-buffer ) ;when ;; 更新搜索过滤器 - (when chat-tab-search-target - (with-buffer chat-tab-search-target (set-search-filter)) + (when floating-search-target + (with-buffer floating-search-target (set-search-filter)) ) ;when (perform-search*) ) ;tm-define @@ -1222,23 +1207,23 @@ ) ;with-buffer ) ;define -(define (chat-tab-search-init target-buf) - (set! chat-tab-search-target target-buf) +(define (floating-search-init target-buf) + (set! floating-search-target target-buf) (let ((aux (search-buffer))) - (set! chat-tab-search-aux aux) - (set! chat-tab-search-active? #t) + (set! floating-search-aux aux) + (set! floating-search-active? #t) (buffer-set-master aux target-buf) (set-search-window-state #t #t) (with-buffer target-buf (set-search-reference (cursor-path))) (set-search-filter) (set! search-filter-out? #f) ;; 设置搜索缓冲区的初始 init env(与侧边栏 texmacs-input 行为一致) - (with-buffer aux (init-env "mode" chat-tab-search-mode)) - (qt-floating-search-set-callbacks "(chat-tab-search-next #t)" - "(chat-tab-search-next #f)" - "(chat-tab-search-close)" + (with-buffer aux (init-env "mode" floating-search-mode)) + (qt-floating-search-set-callbacks "(floating-search-next #t)" + "(floating-search-next #f)" + "(floating-search-close)" ) ;qt-floating-search-set-callbacks - (qt-floating-search-init (url->string aux) chat-tab-search-mode) + (qt-floating-search-init (url->string aux) floating-search-mode) ;; 同步暗色样式到搜索缓冲区 (when (== (get-preference "gui theme") "liii-night") (with-buffer aux @@ -1251,19 +1236,19 @@ ) ;let ) ;define -(tm-define (chat-tab-search-next forward?) - (when (and chat-tab-search-target chat-tab-search-aux) - (with-buffer chat-tab-search-target (search-rotate-match forward?)) +(tm-define (floating-search-next forward?) + (when (and floating-search-target floating-search-aux) + (with-buffer floating-search-target (search-rotate-match forward?)) ) ;when ) ;tm-define -(tm-define (chat-tab-search-close) - (when chat-tab-search-target +(tm-define (floating-search-close) + (when floating-search-target (search-show-all) (set! search-serial (+ search-serial 1)) - (with-buffer chat-tab-search-target (cancel-alt-selection "alternate")) + (with-buffer floating-search-target (cancel-alt-selection "alternate")) (set-search-window-state #f #f) - (let* ((msg-url (url->system chat-tab-search-target)) + (let* ((msg-url (url->system floating-search-target)) (in-url (if (string-starts? msg-url "tmfs://chat-message-") (string-append "tmfs://chat-input-" (substring msg-url (string-length "tmfs://chat-message-")) @@ -1276,9 +1261,9 @@ (buffer-focus (string->url in-url) #t) ) ;when ) ;let* - (set! chat-tab-search-active? #f) - (set! chat-tab-search-target #f) - (set! chat-tab-search-aux #f) + (set! floating-search-active? #f) + (set! floating-search-target #f) + (set! floating-search-aux #f) ) ;when ) ;tm-define @@ -1563,8 +1548,8 @@ ) ;tm-define (tm-define (toolbar-search-end) - (when chat-tab-search-active? - (chat-tab-search-close) + (when floating-search-active? + (floating-search-close) ) ;when (cancel-alt-selection "alternate") (search-show-all) @@ -1844,7 +1829,7 @@ (msg-u (and msg-url (not (== msg-url "")) (string->url msg-url))) ) ; (if (and msg-u (chat-message-buffer-has-content? msg-u)) - (chat-tab-search-init msg-u) + (floating-search-init msg-u) (noop) ) ;if ) ;let* diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index 213050d546..b0187df9a9 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -159,7 +159,7 @@ QTMFloatingSearchBar::QTMFloatingSearchBar (QWidget* parent) bool isMath= (modeBtn_->objectName () == QStringLiteral ("floating-search-mode-text")); setModeIcon (isMath); - eval_scheme ("(chat-tab-search-toggle-mode)"); + eval_scheme ("(floating-search-toggle-mode)"); }); if (parent) parent->installEventFilter (this); From 7e1d3c3408b95c1eae67a8c24c36ed38ce581a12 Mon Sep 17 00:00:00 2001 From: Yuki Date: Sun, 31 May 2026 00:29:46 +0800 Subject: [PATCH 24/35] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/progs/generic/search-widgets.scm | 76 +++++++++++++----------- src/Plugins/Qt/qt_chat_controller.cpp | 3 + 2 files changed, 44 insertions(+), 35 deletions(-) diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index 7d3865c190..6f1011803d 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -172,9 +172,10 @@ ;; ---- ;; 此函数用于管理搜索辅助缓冲区的生命周期,确保每个主文档视图有唯一的搜索缓冲区。 (tm-define (search-buffer) - ;; chat tab 搜索激活且当前在 chat tab 上下文中时,返回保存的 aux buffer + ;; 悬浮搜索激活时直接返回保存的 aux buffer (if (and floating-search-active? floating-search-aux + (buffer-exists? floating-search-aux) (or (== (current-buffer) floating-search-aux) (== (current-buffer) floating-search-target) ) ;or @@ -352,9 +353,10 @@ ) ;tm-define (tm-define (master-buffer) - ;; chat tab 搜索激活且当前在 chat tab 上下文中时,返回保存的 target buffer + ;; 悬浮搜索激活时直接返回保存的 target buffer (if (and floating-search-active? floating-search-target + (buffer-exists? floating-search-target) (or (== (current-buffer) floating-search-aux) (== (current-buffer) floating-search-target) ) ;or @@ -460,7 +462,8 @@ (define (accept-search-result? p) (or (and floating-search-active? (== floating-search-mode "math") - (search-path-inside-math? p)) + (search-path-inside-math? p) + ) ;and (== (get-init "mode") "src") (let* ((buf (buffer-tree)) (rel (path-strip (cDr p) (tree->path buf))) @@ -474,6 +477,22 @@ ) ;or ) ;define +(define (search-path-inside-math? p) + (with-buffer (master-buffer) + (let* ((buf (buffer-tree)) (rel (path-strip (cDr p) (tree->path buf)))) + (and rel + (let* ((initial (cons 'attr (get-main-attrs get-init))) + (env (tree-descendant-env* buf rel initial)) + ) ; + (and env + (with env-attrs (tm-children env) (check-same-sub? env-attrs "mode" "math")) + ) ;and + ) ;let* + ) ;and + ) ;let* + ) ;with-buffer +) ;define + (define (filter-search-results sels) (if (or (null? sels) (null? (cdr sels))) (list) @@ -496,8 +515,12 @@ (let* ((source-mode 2) (old-mode (get-access-mode)) (new-mode (if (or (== (get-init "mode") "src") - (and floating-search-active? (== floating-search-mode "math"))) - source-mode old-mode)) + (and floating-search-active? (== floating-search-mode "math")) + ) ;or + source-mode + old-mode + ) ;if + ) ;new-mode ) ; (set-access-mode new-mode) (let* ((cp (cDr (cursor-path))) @@ -1174,39 +1197,20 @@ (tm-define (floating-search-toggle-mode) (set! floating-search-mode (if (== floating-search-mode "text") "math" "text")) - ;; 更新搜索缓冲区的 init env - (when floating-search-aux - (with-buffer floating-search-aux - (if (== floating-search-mode "math") - (init-env "mode" "math") - (init-default "mode") - ) ;if - ) ;with-buffer - ) ;when - ;; 更新搜索过滤器 + ;; 更新 filter (when floating-search-target (with-buffer floating-search-target (set-search-filter)) ) ;when - (perform-search*) + ;; 重建 widget 以切换数学/文本输入环境 + (when floating-search-aux + (qt-floating-search-init (url->string floating-search-aux) floating-search-mode) + ) ;when + ;; 在 floating-search-aux 上下文中搜索,确保 guards 通过 + (when floating-search-aux + (with-buffer floating-search-aux (perform-search*)) + ) ;when ) ;tm-define -(define (search-path-inside-math? p) - (with-buffer (master-buffer) - (let* ((buf (buffer-tree)) (dr (cDr p)) (len (length dr))) - ;; 从父节点向上遍历祖先,检查是否有 math 节点 - (let loop - ((i (- len 1))) - (if (< i 1) - #f - (let ((node (subtree buf (sublist dr 0 i)))) - (if (tm-func? node 'math) #t (loop (- i 1))) - ) ;let - ) ;if - ) ;let - ) ;let* - ) ;with-buffer -) ;define - (define (floating-search-init target-buf) (set! floating-search-target target-buf) (let ((aux (search-buffer))) @@ -1214,8 +1218,10 @@ (set! floating-search-active? #t) (buffer-set-master aux target-buf) (set-search-window-state #t #t) - (with-buffer target-buf (set-search-reference (cursor-path))) - (set-search-filter) + (with-buffer target-buf + (set-search-reference (cursor-path)) + (set-search-filter) + ) ;with-buffer (set! search-filter-out? #f) ;; 设置搜索缓冲区的初始 init env(与侧边栏 texmacs-input 行为一致) (with-buffer aux (init-env "mode" floating-search-mode)) diff --git a/src/Plugins/Qt/qt_chat_controller.cpp b/src/Plugins/Qt/qt_chat_controller.cpp index 9652d2ab34..83bc13aee3 100644 --- a/src/Plugins/Qt/qt_chat_controller.cpp +++ b/src/Plugins/Qt/qt_chat_controller.cpp @@ -457,6 +457,9 @@ void ChatController::activateSession (const string& sessionId) { if (!view_) return; + // 切换 session 时隐藏悬浮搜索栏 + qt_floating_search_bar_show (view_->contentWidget (), false); + ChatConversationPanel* panel= getOrCreatePanel (sessionId); if (!panel) return; From da523352acb67f60926bda025999464c375abcd2 Mon Sep 17 00:00:00 2001 From: Yuki Date: Sun, 31 May 2026 01:07:59 +0800 Subject: [PATCH 25/35] =?UTF-8?q?=E6=95=B0=E5=AD=A6=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/plugins/lang/dic/en_US/zh_CN.scm | 2 +- TeXmacs/progs/generic/search-widgets.scm | 61 ++++++++++++------------ 2 files changed, 31 insertions(+), 32 deletions(-) diff --git a/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm b/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm index 0fc2b0f681..5bd66c51bf 100644 --- a/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm +++ b/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm @@ -1611,7 +1611,7 @@ ("no matches found for" "") ("no matches found" "") ("No matches" "无匹配") -("%1 of %2" "%1 / %2") +("%1 of %2" "第%1项,共%2项") ("no more matches for" "") ("no more redo information available" "") ("no more undo information available" "") diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index 6f1011803d..fb115ef933 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -460,34 +460,31 @@ ) ;define (define (accept-search-result? p) - (or (and floating-search-active? - (== floating-search-mode "math") - (search-path-inside-math? p) - ) ;and - (== (get-init "mode") "src") - (let* ((buf (buffer-tree)) - (rel (path-strip (cDr p) (tree->path buf))) - (initial (cons 'attr (get-main-attrs get-init))) - (old-env (get-search-filter)) - (new-env (tree-descendant-env* buf rel initial)) - ) ; - ;; (display* p " ~> " new-env "\n") - (check-same? (tm-children new-env) (tm-children old-env)) - ) ;let* - ) ;or + (if (and floating-search-active? (== floating-search-mode "math")) + (search-path-inside-math? p) + (or (== (get-init "mode") "src") + (let* ((buf (buffer-tree)) + (rel (path-strip (cDr p) (tree->path buf))) + (initial (cons 'attr (get-main-attrs get-init))) + (old-env (get-search-filter)) + (new-env (tree-descendant-env* buf rel initial)) + ) ; + ;; (display* p " ~> " new-env "\n") + (check-same? (tm-children new-env) (tm-children old-env)) + ) ;let* + ) ;or + ) ;if ) ;define (define (search-path-inside-math? p) (with-buffer (master-buffer) - (let* ((buf (buffer-tree)) (rel (path-strip (cDr p) (tree->path buf)))) - (and rel - (let* ((initial (cons 'attr (get-main-attrs get-init))) - (env (tree-descendant-env* buf rel initial)) - ) ; - (and env - (with env-attrs (tm-children env) (check-same-sub? env-attrs "mode" "math")) - ) ;and - ) ;let* + (let* ((buf (buffer-tree)) + (rel (path-strip (cDr p) (tree->path buf))) + (initial (cons 'attr (get-main-attrs get-init))) + (env (and rel (tree-descendant-env* buf rel initial))) + ) ; + (and env + (with env-attrs (tm-children env) (check-same-sub? env-attrs "mode" "math")) ) ;and ) ;let* ) ;with-buffer @@ -513,13 +510,12 @@ (define (tree-perform-search t what p limit) (let* ((source-mode 2) + (math-mode 1) (old-mode (get-access-mode)) - (new-mode (if (or (== (get-init "mode") "src") - (and floating-search-active? (== floating-search-mode "math")) - ) ;or - source-mode - old-mode - ) ;if + (new-mode (cond ((== (get-init "mode") "src") source-mode) + ((and floating-search-active? (== floating-search-mode "math")) math-mode) + (else old-mode) + ) ;cond ) ;new-mode ) ; (set-access-mode new-mode) @@ -535,7 +531,10 @@ (define (go-to* p) (go-to p) - (when (and (not (cursor-accessible?)) (not (in-source?))) + (when (and (not (cursor-accessible?)) + (not (in-source?)) + (not floating-search-active?) + ) ;and (cursor-show-hidden) (delayed (:pause 50) (set! search-serial (+ search-serial 1)) From 4472ad7d31476923026fde00b3bfc0223ade8f20 Mon Sep 17 00:00:00 2001 From: Yuki Date: Sun, 31 May 2026 01:25:11 +0800 Subject: [PATCH 26/35] =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/plugins/lang/dic/en_US/zh_CN.scm | 2 ++ src/Plugins/Qt/qt_floating_search_bar.cpp | 35 ++++++++++++++++++----- src/Plugins/Qt/qt_floating_search_bar.hpp | 1 + 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm b/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm index 5bd66c51bf..9540c4dce1 100644 --- a/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm +++ b/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm @@ -1601,6 +1601,7 @@ ("next screen" "前一屏") ("next similar" "相似的(后一个)") ("next" "后一个") +("math mode (Ctrl+Tab)" "数学模式 (Ctrl+Tab)") ("Next (Enter)" "下一个 (Enter)") ("no changes need to be saved" "没有任何更改需要保存") ("no dictionary for" "") @@ -2402,6 +2403,7 @@ ("Text for note" "用于笔记的文本") ("text height correction" "文本高度修正") ("text input" "文本输入") +("text mode (Ctrl+Tab)" "文本模式 (Ctrl+Tab)") ("text mode" "文本模式") ("text width" "") ("text" "文本") diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index b0187df9a9..83540d3c72 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -103,7 +103,7 @@ QTMFloatingSearchBar::QTMFloatingSearchBar (QWidget* parent) modeBtn_->setObjectName ("floating-search-mode-text"); modeBtn_->setFixedSize (DpiUtils::scaled (kBtnSize), DpiUtils::scaled (kBtnSize)); - modeBtn_->setToolTip (qt_translate ("Toggle search mode (text/math)")); + modeBtn_->setToolTip (qt_translate ("text mode (Ctrl+Tab)")); modeBtn_->setStyleSheet (btnRadiusStyle); btnRow->addWidget (modeBtn_); @@ -155,12 +155,8 @@ QTMFloatingSearchBar::QTMFloatingSearchBar (QWidget* parent) &QTMFloatingSearchBar::findPreviousRequested); connect (closeBtn, &QToolButton::clicked, this, &QTMFloatingSearchBar::closeRequested); - connect (modeBtn_, &QToolButton::clicked, this, [this] () { - bool isMath= (modeBtn_->objectName () == - QStringLiteral ("floating-search-mode-text")); - setModeIcon (isMath); - eval_scheme ("(floating-search-toggle-mode)"); - }); + connect (modeBtn_, &QToolButton::clicked, this, + &QTMFloatingSearchBar::toggleMode); if (parent) parent->installEventFilter (this); @@ -188,6 +184,17 @@ QTMFloatingSearchBar::setSearchInput (QWidget* input) { scrollArea->installEventFilter (this); } rowLayout_->insertWidget (0, input, 1); + QMetaObject::invokeMethod ( + this, + [this] { + if (inputQW_) { + QAbstractScrollArea* sa= + inputQW_->findChild (); + if (sa) sa->setFocus (); + else inputQW_->setFocus (); + } + }, + Qt::QueuedConnection); } } @@ -246,6 +253,16 @@ QTMFloatingSearchBar::setModeIcon (bool mathMode) { : "floating-search-mode-text"); modeBtn_->style ()->unpolish (modeBtn_); modeBtn_->style ()->polish (modeBtn_); + modeBtn_->setToolTip (mathMode ? qt_translate ("math mode (Ctrl+Tab)") + : qt_translate ("text mode (Ctrl+Tab)")); +} + +void +QTMFloatingSearchBar::toggleMode () { + bool isMath= + (modeBtn_->objectName () == QStringLiteral ("floating-search-mode-text")); + setModeIcon (isMath); + eval_scheme ("(floating-search-toggle-mode)"); } bool @@ -263,6 +280,10 @@ QTMFloatingSearchBar::eventFilter (QObject* watched, QEvent* event) { return true; } } + if (ke->key () == Qt::Key_Tab && (ke->modifiers () & Qt::ControlModifier)) { + toggleMode (); + return true; + } } return QWidget::eventFilter (watched, event); } diff --git a/src/Plugins/Qt/qt_floating_search_bar.hpp b/src/Plugins/Qt/qt_floating_search_bar.hpp index 325f99b83a..4813275781 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.hpp +++ b/src/Plugins/Qt/qt_floating_search_bar.hpp @@ -46,6 +46,7 @@ class QTMFloatingSearchBar : public QWidget { void setSchemeCallbacks (const string& next_cmd, const string& prev_cmd, const string& close_cmd); void setModeIcon (bool mathMode); + void toggleMode (); signals: void findNextRequested (); From c701c287ea449c6a2ea420d2e95512dfba78b36f Mon Sep 17 00:00:00 2001 From: Yuki Date: Sun, 31 May 2026 01:57:25 +0800 Subject: [PATCH 27/35] text --- TeXmacs/progs/generic/search-widgets.scm | 25 +++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index fb115ef933..cb02ae4e4e 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -462,17 +462,20 @@ (define (accept-search-result? p) (if (and floating-search-active? (== floating-search-mode "math")) (search-path-inside-math? p) - (or (== (get-init "mode") "src") - (let* ((buf (buffer-tree)) - (rel (path-strip (cDr p) (tree->path buf))) - (initial (cons 'attr (get-main-attrs get-init))) - (old-env (get-search-filter)) - (new-env (tree-descendant-env* buf rel initial)) - ) ; - ;; (display* p " ~> " new-env "\n") - (check-same? (tm-children new-env) (tm-children old-env)) - ) ;let* - ) ;or + (if floating-search-active? + #t ;; text mode: tree-perform-search 中 access=0 已限制只搜文本节点,无需额外 filter + (or (== (get-init "mode") "src") + (let* ((buf (buffer-tree)) + (rel (path-strip (cDr p) (tree->path buf))) + (initial (cons 'attr (get-main-attrs get-init))) + (old-env (get-search-filter)) + (new-env (tree-descendant-env* buf rel initial)) + ) ; + ;; (display* p " ~> " new-env "\n") + (check-same? (tm-children new-env) (tm-children old-env)) + ) ;let* + ) ;or + ) ;if ) ;if ) ;define From c9439d6494f8f37a6d3270b7532f9756585ce2d8 Mon Sep 17 00:00:00 2001 From: Yuki Date: Sun, 31 May 2026 02:04:10 +0800 Subject: [PATCH 28/35] fix --- TeXmacs/progs/generic/search-widgets.scm | 12 +++++++++--- src/Plugins/Qt/qt_floating_search_bar.cpp | 15 +++++++-------- src/Plugins/Qt/qt_floating_search_bar.hpp | 2 +- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index cb02ae4e4e..31a169ccc4 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -463,7 +463,8 @@ (if (and floating-search-active? (== floating-search-mode "math")) (search-path-inside-math? p) (if floating-search-active? - #t ;; text mode: tree-perform-search 中 access=0 已限制只搜文本节点,无需额外 filter + #t + ;; text mode: tree-perform-search 中 access=0 已限制只搜文本节点,无需额外 filter (or (== (get-init "mode") "src") (let* ((buf (buffer-tree)) (rel (path-strip (cDr p) (tree->path buf))) @@ -1203,9 +1204,14 @@ (when floating-search-target (with-buffer floating-search-target (set-search-filter)) ) ;when - ;; 重建 widget 以切换数学/文本输入环境 + ;; 重建 widget 以切换数学/文本输入环境,保留已输入文本 (when floating-search-aux - (qt-floating-search-init (url->string floating-search-aux) floating-search-mode) + (let ((saved-body (buffer-get-body floating-search-aux))) + (qt-floating-search-init (url->string floating-search-aux) floating-search-mode) + (when (not (tree-empty? saved-body)) + (buffer-set-body floating-search-aux saved-body) + ) ;when + ) ;let ) ;when ;; 在 floating-search-aux 上下文中搜索,确保 guards 通过 (when floating-search-aux diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index 83540d3c72..ca55b2b6d9 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -238,17 +238,17 @@ QTMFloatingSearchBar::connectSignals () { connect (this, &QTMFloatingSearchBar::findPreviousRequested, this, [this] () { eval_scheme (prev_cmd_); }); } - if (!is_empty (close_cmd_)) { - connect (this, &QTMFloatingSearchBar::closeRequested, this, [this] () { - eval_scheme (close_cmd_); - hide (); - }); - } + // close: 始终 hide,callback 非空时才调 eval_scheme + connect (this, &QTMFloatingSearchBar::closeRequested, this, [this] () { + if (!is_empty (close_cmd_)) eval_scheme (close_cmd_); + hide (); + }); } void QTMFloatingSearchBar::setModeIcon (bool mathMode) { if (!modeBtn_) return; + mathMode_= mathMode; modeBtn_->setObjectName (mathMode ? "floating-search-mode-math" : "floating-search-mode-text"); modeBtn_->style ()->unpolish (modeBtn_); @@ -259,8 +259,7 @@ QTMFloatingSearchBar::setModeIcon (bool mathMode) { void QTMFloatingSearchBar::toggleMode () { - bool isMath= - (modeBtn_->objectName () == QStringLiteral ("floating-search-mode-text")); + bool isMath= !mathMode_; setModeIcon (isMath); eval_scheme ("(floating-search-toggle-mode)"); } diff --git a/src/Plugins/Qt/qt_floating_search_bar.hpp b/src/Plugins/Qt/qt_floating_search_bar.hpp index 4813275781..39ea58cfd6 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.hpp +++ b/src/Plugins/Qt/qt_floating_search_bar.hpp @@ -70,7 +70,7 @@ class QTMFloatingSearchBar : public QWidget { string next_cmd_; string prev_cmd_; string close_cmd_; - string mode_cmd_; + bool mathMode_ = false; bool callbacksConnected_= false; }; From 5cd2a1f8b5ff35b4b496e0f2969ba2c71987c08c Mon Sep 17 00:00:00 2001 From: Yuki Date: Sun, 31 May 2026 02:06:36 +0800 Subject: [PATCH 29/35] devel --- devel/1042.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/devel/1042.md b/devel/1042.md index 9fa87c1dd1..585e6e0fd8 100644 --- a/devel/1042.md +++ b/devel/1042.md @@ -51,14 +51,19 @@ Chat Tab 的 message buffer 是嵌入式 TeXmacs widget,没有标准 `tm_windo commit [0228] 通过 blanket `tmfs:` 检查禁用了所有 tmfs:// 缓冲区的搜索来避免 crash。用户需要在 Chat Tab 的消息输出框中搜索对话内容。Chat Tab 的嵌入式 buffer 不支持 auxiliary-widget 机制,需要独立的搜索 UI。 ## 7 How -1. 实现通用悬浮搜索栏 Qt 组件 `QTMFloatingSearchBar`,包含输入框、导航按钮、关闭按钮、匹配计数标签 +1. 实现通用悬浮搜索栏 Qt 组件 `QTMFloatingSearchBar`,包含输入框、导航按钮、关闭按钮、匹配计数标签、模式切换按钮 2. 通过 `QHash` 按 parent widget 管理实例,parent 销毁时自动清理 3. `QTMFloatingSearchBar` 自身安装 eventFilter 处理 parent resize,自动重新定位到右上角 -4. Scheme 端新增 `chat-tab-search-init`、`chat-tab-perform-search`、`chat-tab-search-next`、`chat-tab-search-close` 函数 +4. Scheme 端新增 `floating-search-init`、`floating-search-next`、`floating-search-close`、`floating-search-toggle-mode` 函数 5. C++ 按钮回调通过 `eval_scheme` 调用 Scheme 搜索函数,回调命令由 `qt-floating-search-set-callbacks` 注入,不再硬编码 6. `interactive-search` 检测到嵌入式 chat buffer 时调 C++ glue 显示搜索栏并初始化搜索 7. 搜索框的 `texmacs_input_widget` 参照 chat 组件,应用 `DpiUtils::scaled` 缩放、`"font" "sys-chinese"` 字体、viewport 背景色 `QPalette::Base` -8. Scheme 端在 `chat-tab-search-init` 中检测 `liii-night` 主题,自动给搜索缓冲区添加 `"dark"` 样式包 +8. Scheme 端在 `floating-search-init` 中检测 `liii-night` 主题,自动给搜索缓冲区添加 `"dark"` 样式包 +9. text 模式:`accept-search-result?` 直接返回 `#t`,依赖 `tree-perform-search` 中 `access=0` 引擎层过滤文本节点;math 模式:独立 `search-path-inside-math?` 检查路径是否在数学环境内 +10. `connectSignals` 中 `closeRequested` 始终连接 `hide()`,不依赖 `close_cmd_` 是否为空;callback 非空时才额外调 `eval_scheme` +11. `QTMFloatingSearchBar` 用 `mathMode_` 成员变量追踪当前模式,`setModeIcon` 同步写入,`toggleMode` 直接读取,避免依赖 `objectName` 推断导致 C++/Scheme 端状态漂移 +12. 暗色主题 CSS 选择器 `QWidget#floating_search_bar` 与亮色主题统一,去掉多余的 `#centralWidget` 祖先约束 +13. `floating-search-toggle-mode` 切换 text/math 模式时保存并恢复 aux buffer body,保留用户已输入的搜索文本 --- From 5eb257eac267cce408cf9b7078cd943838e97aa0 Mon Sep 17 00:00:00 2001 From: Yuki Date: Sun, 31 May 2026 02:25:15 +0800 Subject: [PATCH 30/35] =?UTF-8?q?=E8=AE=A1=E6=95=B0=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/progs/generic/search-widgets.scm | 14 ++++++ src/Plugins/Qt/qt_floating_search_bar.cpp | 57 ++++++++++++++++++----- src/Plugins/Qt/qt_floating_search_bar.hpp | 11 +++-- 3 files changed, 66 insertions(+), 16 deletions(-) diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index 31a169ccc4..3e60aad584 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -1198,6 +1198,8 @@ (define floating-search-mode "text") +(define floating-search-last-content "") + (tm-define (floating-search-toggle-mode) (set! floating-search-mode (if (== floating-search-mode "text") "math" "text")) ;; 更新 filter @@ -1221,6 +1223,7 @@ (define (floating-search-init target-buf) (set! floating-search-target target-buf) + (set! floating-search-last-content "") (let ((aux (search-buffer))) (set! floating-search-aux aux) (set! floating-search-active? #t) @@ -1256,6 +1259,17 @@ ) ;when ) ;tm-define +(tm-define (floating-search-on-input) + (when (and floating-search-active? floating-search-aux) + (let ((current (tree->string (buffer-get-body floating-search-aux)))) + (when (not (== current floating-search-last-content)) + (set! floating-search-last-content current) + (with-buffer floating-search-aux (perform-search*)) + ) ;when + ) ;let + ) ;when +) ;tm-define + (tm-define (floating-search-close) (when floating-search-target (search-show-all) diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index ca55b2b6d9..8649e818d6 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -170,28 +170,35 @@ QTMFloatingSearchBar::~QTMFloatingSearchBar () { void QTMFloatingSearchBar::setSearchInput (QWidget* input) { if (inputQW_) { - QAbstractScrollArea* oldArea= inputQW_->findChild (); - if (oldArea) oldArea->removeEventFilter (this); + if (inputScrollArea_) { + inputScrollArea_->removeEventFilter (this); + inputScrollArea_= nullptr; + } rowLayout_->removeWidget (inputQW_); inputQW_->deleteLater (); } inputQW_= input; if (input) { input->setObjectName ("floating-search-input"); - QAbstractScrollArea* scrollArea= input->findChild (); - if (scrollArea) { - scrollArea->viewport ()->setBackgroundRole (QPalette::Base); - scrollArea->installEventFilter (this); - } rowLayout_->insertWidget (0, input, 1); + // texmacs_input_widget 内部的 QAbstractScrollArea 可能延迟创建(show 时), + // 所有 scroll area 设置统一延迟到事件循环处理 QMetaObject::invokeMethod ( this, [this] { if (inputQW_) { QAbstractScrollArea* sa= inputQW_->findChild (); - if (sa) sa->setFocus (); - else inputQW_->setFocus (); + if (sa) { + sa->viewport ()->setBackgroundRole (QPalette::Base); + sa->installEventFilter (this); + sa->setFocus (); + inputScrollArea_= sa; + } + else { + inputScrollArea_= nullptr; + inputQW_->setFocus (); + } } }, Qt::QueuedConnection); @@ -203,7 +210,8 @@ QTMFloatingSearchBar::activate () { show (); raise (); if (inputQW_) { - QAbstractScrollArea* sa= inputQW_->findChild (); + QAbstractScrollArea* sa= inputScrollArea_; + if (!sa) sa= inputQW_->findChild (); if (sa) sa->setFocus (); else inputQW_->setFocus (); } @@ -271,9 +279,10 @@ QTMFloatingSearchBar::eventFilter (QObject* watched, QEvent* event) { reposition (); } if (event->type () == QEvent::KeyPress && isVisible () && inputQW_) { - auto* ke= static_cast (event); + auto* ke= static_cast (event); + QAbstractScrollArea* sa= inputScrollArea_; + if (ke->key () == Qt::Key_Escape) { - QAbstractScrollArea* sa= inputQW_->findChild (); if (sa && watched == sa) { emit closeRequested (); return true; @@ -283,6 +292,30 @@ QTMFloatingSearchBar::eventFilter (QObject* watched, QEvent* event) { toggleMode (); return true; } + + // 输入区内非修饰键/非导航键按键:触发实时搜索更新 + if (sa && watched == sa) { + switch (ke->key ()) { + case Qt::Key_Shift: + case Qt::Key_Control: + case Qt::Key_Meta: + case Qt::Key_Alt: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + break; + default: + QMetaObject::invokeMethod ( + this, [this] () { eval_scheme ("(floating-search-on-input)"); }, + Qt::QueuedConnection); + break; + } + } } return QWidget::eventFilter (watched, event); } diff --git a/src/Plugins/Qt/qt_floating_search_bar.hpp b/src/Plugins/Qt/qt_floating_search_bar.hpp index 39ea58cfd6..11d75679b2 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.hpp +++ b/src/Plugins/Qt/qt_floating_search_bar.hpp @@ -21,6 +21,8 @@ #include +class QAbstractScrollArea; + /** * VSCode 风格的悬浮搜索栏组件。 * @@ -62,10 +64,11 @@ class QTMFloatingSearchBar : public QWidget { void reposition (); void connectSignals (); - QHBoxLayout* rowLayout_= nullptr; - QWidget* inputQW_ = nullptr; - QLabel* infoLbl_ = nullptr; - QToolButton* modeBtn_ = nullptr; + QHBoxLayout* rowLayout_ = nullptr; + QWidget* inputQW_ = nullptr; + QAbstractScrollArea* inputScrollArea_= nullptr; + QLabel* infoLbl_ = nullptr; + QToolButton* modeBtn_ = nullptr; string next_cmd_; string prev_cmd_; From 4f0ac7b7995bcc8aced4aba9777528365e821519 Mon Sep 17 00:00:00 2001 From: Yuki Date: Sun, 31 May 2026 02:42:08 +0800 Subject: [PATCH 31/35] test --- TeXmacs/tests/1042.scm | 99 ++++++++++++++ src/Plugins/Qt/qt_floating_search_bar.cpp | 2 +- src/Plugins/Qt/qt_floating_search_bar.hpp | 2 +- .../Qt/qt_floating_search_bar_test.cpp | 121 ++++++++++++++++++ 4 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 TeXmacs/tests/1042.scm create mode 100644 tests/Plugins/Qt/qt_floating_search_bar_test.cpp diff --git a/TeXmacs/tests/1042.scm b/TeXmacs/tests/1042.scm new file mode 100644 index 0000000000..63b16eb94a --- /dev/null +++ b/TeXmacs/tests/1042.scm @@ -0,0 +1,99 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; MODULE : 1042.scm +;; DESCRIPTION : Unit tests for floating search on-input dedup logic +;; COPYRIGHT : (C) 2026 Yuki Lu +;; +;; This software falls under the GNU general public license version 3 or later. +;; It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE +;; in the root directory or . +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(import (liii check)) + +(load "./TeXmacs/progs/generic/search-widgets.scm") + +(check-set-mode! 'report-failed) + +(define (test-floating-search-on-input-dedup) + (display "Testing floating-search-on-input dedup logic...\n") + + ;; 创建临时 buffer,使用原子字符串作为 body(tree->string 才能返回值) + (let* ((buf (buffer-new)) (initial-content "hello")) + + ;; 设置初始内容 + (buffer-set-body buf initial-content) + + ;; 初始化 floating search 状态 + (set! floating-search-active? #t) + (set! floating-search-aux buf) + ;; 同步 last-content 为当前 buffer 内容 + (set! floating-search-last-content (tree->string (buffer-get-body buf))) + + ;; Test 1: 内容未变 → floating-search-last-content 保持不变 + (let ((before floating-search-last-content)) + (floating-search-on-input) + (check floating-search-last-content => before) + ) ;let + + ;; Test 2: 内容改变 → floating-search-last-content 应更新为新内容 + (buffer-set-body buf "hello world") + (floating-search-on-input) + (check floating-search-last-content => "hello world") + + ;; Test 3: 再次用相同内容调用 → 不变 + (let ((before floating-search-last-content)) + (floating-search-on-input) + (check floating-search-last-content => before) + ) ;let + + ;; Test 4: 清空内容 → last-content 应更新为空串 + (buffer-set-body buf "") + (floating-search-on-input) + (check floating-search-last-content => "") + + ;; Test 5: floating-search-active? 为 #f 时 → last-content 不变 + (set! floating-search-active? #f) + (buffer-set-body buf "should be ignored") + (let ((before floating-search-last-content)) + (floating-search-on-input) + (check floating-search-last-content => before) + ) ;let + + ;; 恢复 + (set! floating-search-active? #t) + + (display "floating-search-on-input dedup tests passed!\n") + ) ;let* +) ;define + +(define (test-floating-search-toggle-mode) + (display "Testing floating-search-toggle-mode...\n") + + ;; 记录原始 mode + (let ((orig-mode floating-search-mode)) + ;; 确保从已知状态开始 + (set! floating-search-mode "text") + (check floating-search-mode => "text") + + ;; 切换到 math + (set! floating-search-mode "math") + (check floating-search-mode => "math") + + ;; 切回 text + (set! floating-search-mode "text") + (check floating-search-mode => "text") + + ;; 恢复 + (set! floating-search-mode orig-mode) + (display "floating-search-toggle-mode tests passed!\n") + ) ;let +) ;define + +(tm-define (test_1042) + (display "Running test_1042...\n") + (test-floating-search-on-input-dedup) + (test-floating-search-toggle-mode) + (check-report) +) ;tm-define diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index 8649e818d6..0a433a7f18 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -2,7 +2,7 @@ /****************************************************************************** * MODULE : qt_floating_search_bar.cpp * DESCRIPTION: A VSCode-style floating search bar widget - * COPYRIGHT : (C) 2026 Mogan STEM + * COPYRIGHT : (C) 2026 Yuki Lu ****************************************************************************** * This software falls under the GNU general public license version 3 or later. * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE diff --git a/src/Plugins/Qt/qt_floating_search_bar.hpp b/src/Plugins/Qt/qt_floating_search_bar.hpp index 11d75679b2..65fcf8af74 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.hpp +++ b/src/Plugins/Qt/qt_floating_search_bar.hpp @@ -2,7 +2,7 @@ /****************************************************************************** * MODULE : qt_floating_search_bar.hpp * DESCRIPTION: A VSCode-style floating search bar widget for TeXmacs - * COPYRIGHT : (C) 2026 Mogan STEM + * COPYRIGHT : (C) 2026 Yuki Lu ****************************************************************************** * This software falls under the GNU general public license version 3 or later. * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE diff --git a/tests/Plugins/Qt/qt_floating_search_bar_test.cpp b/tests/Plugins/Qt/qt_floating_search_bar_test.cpp new file mode 100644 index 0000000000..298b168939 --- /dev/null +++ b/tests/Plugins/Qt/qt_floating_search_bar_test.cpp @@ -0,0 +1,121 @@ + +/****************************************************************************** + * MODULE : qt_floating_search_bar_test.cpp + * DESCRIPTION: Tests for QTMFloatingSearchBar widget + * COPYRIGHT : (C) 2026 Yuki Lu + ******************************************************************************* + * This software falls under the GNU general public license version 3 or later. + * It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE + * in the root directory or . + ******************************************************************************/ + +#include "Qt/qt_floating_search_bar.hpp" +#include "base.hpp" +#include +#include +#include + +class TestFloatingSearchBar : public QObject { + Q_OBJECT + +private slots: + void init () { init_lolly (); } + + // === 构造 === + void test_constructor (); + + // === setMatchInfo === + void test_matchInfo_default_no_matches (); + void test_matchInfo_with_matches (); + void test_matchInfo_zero_matches (); + + // === setModeIcon === + void test_modeIcon_default_text_mode (); + void test_modeIcon_switch_to_math (); + void test_modeIcon_switch_back_to_text (); +}; + +/****************************************************************************** + * 构造 + ******************************************************************************/ + +void +TestFloatingSearchBar::test_constructor () { + QTMFloatingSearchBar bar; + QCOMPARE (bar.objectName (), QString ("floating_search_bar")); + QVERIFY (bar.findChild ("floating-search-info") != nullptr); + QVERIFY (bar.findChild ("floating-search-mode-text") != nullptr); + QVERIFY (bar.findChild ("floating-search-prev") != nullptr); + QVERIFY (bar.findChild ("floating-search-next") != nullptr); + QVERIFY (bar.findChild ("floating-search-close") != nullptr); +} + +/****************************************************************************** + * setMatchInfo + ******************************************************************************/ + +void +TestFloatingSearchBar::test_matchInfo_default_no_matches () { + QTMFloatingSearchBar bar; + auto* info= bar.findChild ("floating-search-info"); + QVERIFY (info != nullptr); + // 构造后默认应显示 "No matches"(英文环境下) + QVERIFY (info->text ().contains ("No matches")); +} + +void +TestFloatingSearchBar::test_matchInfo_with_matches () { + QTMFloatingSearchBar bar; + bar.setMatchInfo (3, 10); + auto* info= bar.findChild ("floating-search-info"); + QVERIFY (info != nullptr); + QVERIFY (info->text ().contains ("3")); + QVERIFY (info->text ().contains ("10")); +} + +void +TestFloatingSearchBar::test_matchInfo_zero_matches () { + QTMFloatingSearchBar bar; + // 先设置为有匹配 + bar.setMatchInfo (1, 5); + // 再清零 + bar.setMatchInfo (0, 0); + auto* info= bar.findChild ("floating-search-info"); + QVERIFY (info != nullptr); + QVERIFY (info->text ().contains ("No matches")); +} + +/****************************************************************************** + * setModeIcon + ******************************************************************************/ + +void +TestFloatingSearchBar::test_modeIcon_default_text_mode () { + QTMFloatingSearchBar bar; + auto* modeBtn= bar.findChild (); + QVERIFY (modeBtn != nullptr); + // 默认是 text mode,objectName 应为 floating-search-mode-text + QCOMPARE (modeBtn->objectName (), QString ("floating-search-mode-text")); +} + +void +TestFloatingSearchBar::test_modeIcon_switch_to_math () { + QTMFloatingSearchBar bar; + bar.setModeIcon (true); // math mode + auto* modeBtn= bar.findChild (); + QVERIFY (modeBtn != nullptr); + QCOMPARE (modeBtn->objectName (), QString ("floating-search-mode-math")); +} + +void +TestFloatingSearchBar::test_modeIcon_switch_back_to_text () { + QTMFloatingSearchBar bar; + bar.setModeIcon (true); // math + bar.setModeIcon (false); // back to text + auto* modeBtn= bar.findChild (); + QVERIFY (modeBtn != nullptr); + QCOMPARE (modeBtn->objectName (), QString ("floating-search-mode-text")); +} + +QTEST_MAIN (TestFloatingSearchBar) +#include "qt_floating_search_bar_test.moc" From 00a29f93a6828e90640c93a9c8dba0d1eb3112bc Mon Sep 17 00:00:00 2001 From: Yuki Date: Sun, 31 May 2026 02:42:56 +0800 Subject: [PATCH 32/35] =?UTF-8?q?[1042]=20=E6=9B=B4=E6=96=B0=20devel?= =?UTF-8?q?=EF=BC=9A=E8=A1=A5=E5=85=85=E5=AE=9E=E6=97=B6=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E3=80=81=E6=B5=8B=E8=AF=95=E5=92=8C=E4=BB=A3=E7=A0=81=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E8=AF=B4=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devel/1042.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/devel/1042.md b/devel/1042.md index 585e6e0fd8..f543fd3e12 100644 --- a/devel/1042.md +++ b/devel/1042.md @@ -15,7 +15,20 @@ ## 3 如何测试 ### 3.1 确定性测试(单元测试) -无单元测试,需手动验证。 + +**C++ 测试** (`tests/Plugins/Qt/qt_floating_search_bar_test.cpp`): +- 构造与布局完整性(objectName、子 widget 存在性) +- `setMatchInfo`:默认、有匹配、零匹配三种状态 +- `setModeIcon`:默认 text / 切换 math / 切回 text 三种状态 +- 运行:`xmake b qt_floating_search_bar_test && xmake r qt_floating_search_bar_test` + +**Scheme 测试** (`TeXmacs/tests/1042.scm`): +- `floating-search-on-input` 去重逻辑: + - 内容不变 → `floating-search-last-content` 不变 + - 内容改变 → `floating-search-last-content` 更新为新值 + - `floating-search-active?` 为 `#f` → 不做任何操作 +- `floating-search-toggle-mode` 模式切换 +- 运行:`./bin/test_only 1042` 或 `xmake r 1042` ### 3.2 非确定性测试(文档验证) ``` @@ -46,6 +59,8 @@ Chat Tab 的 message buffer 是嵌入式 TeXmacs widget,没有标准 `tm_windo 4. 修改 `interactive-search` 在嵌入式 chat buffer 时使用新的搜索 UI 5. 修改 `interactive-replace` 在 chat tab 中禁用替换 6. 搜索框 DPI 缩放、系统字体、暗色主题跟随 +7. **实时搜索**:输入框内每输入/删除字符即触发 `floating-search-on-input` 自动重新搜索,无需回车 +8. **缓存 `inputScrollArea_` 指针**:避免每次按键调用 `findChild`,减少事件过滤器中的开销 ## 6 Why commit [0228] 通过 blanket `tmfs:` 检查禁用了所有 tmfs:// 缓冲区的搜索来避免 crash。用户需要在 Chat Tab 的消息输出框中搜索对话内容。Chat Tab 的嵌入式 buffer 不支持 auxiliary-widget 机制,需要独立的搜索 UI。 @@ -64,6 +79,9 @@ commit [0228] 通过 blanket `tmfs:` 检查禁用了所有 tmfs:// 缓冲区的 11. `QTMFloatingSearchBar` 用 `mathMode_` 成员变量追踪当前模式,`setModeIcon` 同步写入,`toggleMode` 直接读取,避免依赖 `objectName` 推断导致 C++/Scheme 端状态漂移 12. 暗色主题 CSS 选择器 `QWidget#floating_search_bar` 与亮色主题统一,去掉多余的 `#centralWidget` 祖先约束 13. `floating-search-toggle-mode` 切换 text/math 模式时保存并恢复 aux buffer body,保留用户已输入的搜索文本 +14. **实时搜索**:`QTMFloatingSearchBar::eventFilter` 在输入区(QAbstractScrollArea)收到非修饰键/非导航键按键时,通过 `QMetaObject::invokeMethod` + `Qt::QueuedConnection` 异步调用 `(floating-search-on-input)`,Scheme 端比较 `tree->string (buffer-get-body aux)` 与 `floating-search-last-content`,仅内容真变化时才执行 `perform-search*`,避免重复搜索 +15. **缓存 scroll area**:`inputScrollArea_` 成员在 `setSearchInput` 的延迟 lambda 中赋值、replace 旧输入时清除,`eventFilter` 和 `activate` 复用缓存指针,避免每次事件都 `findChild` +16. **导航键过滤**:方向键、Home/End/PageUp/PageDown 不触发实时搜索,避免无内容变化的输入事件产生多余 Scheme 调用 --- From bf200d913c9b9148744f8113c03ab974bcf4efb3 Mon Sep 17 00:00:00 2001 From: Yuki Date: Sun, 31 May 2026 03:18:12 +0800 Subject: [PATCH 33/35] =?UTF-8?q?mode=E5=88=87=E6=8D=A2=E5=BF=AB=E6=8D=B7?= =?UTF-8?q?=E6=94=B9=E4=B8=BAalt+tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/plugins/lang/dic/en_US/zh_CN.scm | 6 ++++-- src/Plugins/Qt/qt_floating_search_bar.cpp | 17 +++++++++++++---- .../Plugins/Qt/qt_floating_search_bar_test.cpp | 3 ++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm b/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm index 9540c4dce1..9351863b3c 100644 --- a/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm +++ b/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm @@ -1601,7 +1601,8 @@ ("next screen" "前一屏") ("next similar" "相似的(后一个)") ("next" "后一个") -("math mode (Ctrl+Tab)" "数学模式 (Ctrl+Tab)") +("math mode (Alt+Tab)" "数学模式 (Alt+Tab)") +("math mode (Option+Tab)" "数学模式 (Option+Tab)") ("Next (Enter)" "下一个 (Enter)") ("no changes need to be saved" "没有任何更改需要保存") ("no dictionary for" "") @@ -2403,7 +2404,8 @@ ("Text for note" "用于笔记的文本") ("text height correction" "文本高度修正") ("text input" "文本输入") -("text mode (Ctrl+Tab)" "文本模式 (Ctrl+Tab)") +("text mode (Alt+Tab)" "文本模式 (Alt+Tab)") +("text mode (Option+Tab)" "文本模式 (Option+Tab)") ("text mode" "文本模式") ("text width" "") ("text" "文本") diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index 0a433a7f18..62cfdf74ab 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -103,7 +103,11 @@ QTMFloatingSearchBar::QTMFloatingSearchBar (QWidget* parent) modeBtn_->setObjectName ("floating-search-mode-text"); modeBtn_->setFixedSize (DpiUtils::scaled (kBtnSize), DpiUtils::scaled (kBtnSize)); - modeBtn_->setToolTip (qt_translate ("text mode (Ctrl+Tab)")); +#ifdef Q_OS_MAC + modeBtn_->setToolTip (qt_translate ("text mode (Option+Tab)")); +#else + modeBtn_->setToolTip (qt_translate ("text mode (Alt+Tab)")); +#endif modeBtn_->setStyleSheet (btnRadiusStyle); btnRow->addWidget (modeBtn_); @@ -261,8 +265,13 @@ QTMFloatingSearchBar::setModeIcon (bool mathMode) { : "floating-search-mode-text"); modeBtn_->style ()->unpolish (modeBtn_); modeBtn_->style ()->polish (modeBtn_); - modeBtn_->setToolTip (mathMode ? qt_translate ("math mode (Ctrl+Tab)") - : qt_translate ("text mode (Ctrl+Tab)")); +#ifdef Q_OS_MAC + modeBtn_->setToolTip (mathMode ? qt_translate ("math mode (Option+Tab)") + : qt_translate ("text mode (Option+Tab)")); +#else + modeBtn_->setToolTip (mathMode ? qt_translate ("math mode (Alt+Tab)") + : qt_translate ("text mode (Alt+Tab)")); +#endif } void @@ -288,7 +297,7 @@ QTMFloatingSearchBar::eventFilter (QObject* watched, QEvent* event) { return true; } } - if (ke->key () == Qt::Key_Tab && (ke->modifiers () & Qt::ControlModifier)) { + if (ke->key () == Qt::Key_Tab && (ke->modifiers () & Qt::AltModifier)) { toggleMode (); return true; } diff --git a/tests/Plugins/Qt/qt_floating_search_bar_test.cpp b/tests/Plugins/Qt/qt_floating_search_bar_test.cpp index 298b168939..e8604e36af 100644 --- a/tests/Plugins/Qt/qt_floating_search_bar_test.cpp +++ b/tests/Plugins/Qt/qt_floating_search_bar_test.cpp @@ -44,7 +44,8 @@ TestFloatingSearchBar::test_constructor () { QTMFloatingSearchBar bar; QCOMPARE (bar.objectName (), QString ("floating_search_bar")); QVERIFY (bar.findChild ("floating-search-info") != nullptr); - QVERIFY (bar.findChild ("floating-search-mode-text") != nullptr); + QVERIFY (bar.findChild ("floating-search-mode-text") != + nullptr); QVERIFY (bar.findChild ("floating-search-prev") != nullptr); QVERIFY (bar.findChild ("floating-search-next") != nullptr); QVERIFY (bar.findChild ("floating-search-close") != nullptr); From babc681b1e0a7f2ff6a4e0b4577c1f5a04cbe1ab Mon Sep 17 00:00:00 2001 From: Yuki Date: Sun, 31 May 2026 03:33:05 +0800 Subject: [PATCH 34/35] =?UTF-8?q?mode=E5=88=87=E6=8D=A2=E6=97=B6theme?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/progs/generic/search-widgets.scm | 10 +++------- TeXmacs/progs/texmacs/texmacs/tm-files.scm | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/TeXmacs/progs/generic/search-widgets.scm b/TeXmacs/progs/generic/search-widgets.scm index 3e60aad584..2967ed6756 100644 --- a/TeXmacs/progs/generic/search-widgets.scm +++ b/TeXmacs/progs/generic/search-widgets.scm @@ -1213,6 +1213,8 @@ (when (not (tree-empty? saved-body)) (buffer-set-body floating-search-aux saved-body) ) ;when + ;; 重建 widget 后同步暗色样式 + (sync-buffer-dark-style-with-gui-theme floating-search-aux) ) ;let ) ;when ;; 在 floating-search-aux 上下文中搜索,确保 guards 通过 @@ -1242,13 +1244,7 @@ ) ;qt-floating-search-set-callbacks (qt-floating-search-init (url->string aux) floating-search-mode) ;; 同步暗色样式到搜索缓冲区 - (when (== (get-preference "gui theme") "liii-night") - (with-buffer aux - (when (not (has-style-package? "dark")) - (add-style-package "dark") - ) ;when - ) ;with-buffer - ) ;when + (sync-buffer-dark-style-with-gui-theme aux) (qt-floating-search "true") ) ;let ) ;define diff --git a/TeXmacs/progs/texmacs/texmacs/tm-files.scm b/TeXmacs/progs/texmacs/texmacs/tm-files.scm index c885d1e4d1..5c215ddc00 100644 --- a/TeXmacs/progs/texmacs/texmacs/tm-files.scm +++ b/TeXmacs/progs/texmacs/texmacs/tm-files.scm @@ -118,7 +118,7 @@ (with t (tree->stree (get-style-tree)) (and (pair? t) (== (car t) 'tuple) (null? (cdr t))))) -(define (sync-buffer-dark-style-with-gui-theme . opt-buf) +(tm-define (sync-buffer-dark-style-with-gui-theme . opt-buf) (with buf (if (null? opt-buf) (current-buffer) (car opt-buf)) (with-buffer buf (if (== (get-preference "gui theme") "liii-night") From 28cf60111267a02dacc7619bdeffeaa391aff0d9 Mon Sep 17 00:00:00 2001 From: Yuki Date: Mon, 1 Jun 2026 11:04:57 +0800 Subject: [PATCH 35/35] =?UTF-8?q?=E9=9D=9Emacos=E5=BF=AB=E6=8D=B7=E9=94=AE?= =?UTF-8?q?=E6=94=B9=E4=B8=BActrl=20+=20tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TeXmacs/plugins/lang/dic/en_US/zh_CN.scm | 4 ++-- src/Plugins/Qt/qt_floating_search_bar.cpp | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm b/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm index 9351863b3c..b5249f88dc 100644 --- a/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm +++ b/TeXmacs/plugins/lang/dic/en_US/zh_CN.scm @@ -1601,7 +1601,7 @@ ("next screen" "前一屏") ("next similar" "相似的(后一个)") ("next" "后一个") -("math mode (Alt+Tab)" "数学模式 (Alt+Tab)") +("math mode (Ctrl+Tab)" "数学模式 (Ctrl+Tab)") ("math mode (Option+Tab)" "数学模式 (Option+Tab)") ("Next (Enter)" "下一个 (Enter)") ("no changes need to be saved" "没有任何更改需要保存") @@ -2404,7 +2404,7 @@ ("Text for note" "用于笔记的文本") ("text height correction" "文本高度修正") ("text input" "文本输入") -("text mode (Alt+Tab)" "文本模式 (Alt+Tab)") +("text mode (Ctrl+Tab)" "文本模式 (Ctrl+Tab)") ("text mode (Option+Tab)" "文本模式 (Option+Tab)") ("text mode" "文本模式") ("text width" "") diff --git a/src/Plugins/Qt/qt_floating_search_bar.cpp b/src/Plugins/Qt/qt_floating_search_bar.cpp index 62cfdf74ab..5eaf772006 100644 --- a/src/Plugins/Qt/qt_floating_search_bar.cpp +++ b/src/Plugins/Qt/qt_floating_search_bar.cpp @@ -106,7 +106,7 @@ QTMFloatingSearchBar::QTMFloatingSearchBar (QWidget* parent) #ifdef Q_OS_MAC modeBtn_->setToolTip (qt_translate ("text mode (Option+Tab)")); #else - modeBtn_->setToolTip (qt_translate ("text mode (Alt+Tab)")); + modeBtn_->setToolTip (qt_translate ("text mode (Ctrl+Tab)")); #endif modeBtn_->setStyleSheet (btnRadiusStyle); btnRow->addWidget (modeBtn_); @@ -269,8 +269,8 @@ QTMFloatingSearchBar::setModeIcon (bool mathMode) { modeBtn_->setToolTip (mathMode ? qt_translate ("math mode (Option+Tab)") : qt_translate ("text mode (Option+Tab)")); #else - modeBtn_->setToolTip (mathMode ? qt_translate ("math mode (Alt+Tab)") - : qt_translate ("text mode (Alt+Tab)")); + modeBtn_->setToolTip (mathMode ? qt_translate ("math mode (Ctrl+Tab)") + : qt_translate ("text mode (Ctrl+Tab)")); #endif } @@ -297,7 +297,11 @@ QTMFloatingSearchBar::eventFilter (QObject* watched, QEvent* event) { return true; } } +#ifdef Q_OS_MAC if (ke->key () == Qt::Key_Tab && (ke->modifiers () & Qt::AltModifier)) { +#else + if (ke->key () == Qt::Key_Tab && (ke->modifiers () & Qt::ControlModifier)) { +#endif toggleMode (); return true; }