Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions TeXmacs/misc/images/floating-search/down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions TeXmacs/misc/images/floating-search/up.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions TeXmacs/misc/images/images.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
<file>llm-chat/cancel.svg</file>
<file>llm-chat/ellipsis.svg</file>

<file>floating-search/down.svg</file>
<file>floating-search/up.svg</file>

<!-- dark theme -->
<file>ocr-button/left-align-white.svg</file>
<file>ocr-button/middle-align-white.svg</file>
Expand Down
210 changes: 156 additions & 54 deletions TeXmacs/progs/generic/search-widgets.scm
Original file line number Diff line number Diff line change
Expand Up @@ -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 ")")
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)))

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -1112,6 +1134,53 @@
) ;when
) ;tm-define

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Chat tab search (floating search bar)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(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
(set-search-reference (cursor-path)))
(set-search-filter)
(set! search-filter-out? #f)
(qt-floating-search-init (url->string aux))
(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
(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-active? #f)
(set! chat-tab-search-target #f)
(set! chat-tab-search-aux #f)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Search and replace widget
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Expand Down Expand Up @@ -1626,19 +1695,52 @@

(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))))
(chat-tab-search-init
(string->url (string-append "tmfs://chat-message-" sid))))
((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))
) ;cond
) ;set!
(set-boolean-preference "search-and-replace" #f)
(open-search)
) ;unless
) ;with
) ;with
) ;tm-define

(tm-define (interactive-replace)
Expand Down
54 changes: 54 additions & 0 deletions devel/1042.md
Original file line number Diff line number Diff line change
@@ -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 显示搜索栏并初始化搜索
3 changes: 3 additions & 0 deletions src/Plugins/Qt/qt_chat_controller.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ class ChatController : public QObject {
*/
void destroyView ();

public:
QTChatTabWidget* view () const { return view_; }

private:
QTChatTabWidget* view_= nullptr; ///< View 指针,由 createView 创建
ChatSessionManager sessionManager_; ///< 会话管理器
Expand Down
3 changes: 3 additions & 0 deletions src/Plugins/Qt/qt_chat_tab_widget.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,9 @@ class QTChatTabWidget : public QWidget {
return activeConversation_;
}

// ---- 供外部组件访问 ----
QWidget* contentWidget () const { return contentWidget_; }

signals:
void cancelRequested (const string& sessionId);
void newChatRequested ();
Expand Down
Loading
Loading