From d85e9637a4c08e8d8758ac4bb5680f530dfc6dd2 Mon Sep 17 00:00:00 2001 From: AXeonV <2607343351@qq.com> Date: Thu, 16 Jul 2026 09:29:35 +0800 Subject: [PATCH 1/8] 1st wip --- TeXmacs/progs/generic/diff-text.scm | 147 ++++++++++++++++++++++++++ TeXmacs/progs/generic/generic-kbd.scm | 1 + src/Edit/Interface/edit_interface.hpp | 2 + src/Edit/Interface/edit_mouse.cpp | 18 ++++ src/Edit/editor.hpp | 2 + src/Plugins/Qt/QTMUserPromptPopup.cpp | 43 +++++++- src/Plugins/Qt/QTMUserPromptPopup.hpp | 20 +++- src/Plugins/Qt/QTMWidget.cpp | 1 + src/Plugins/Qt/qt_simple_widget.cpp | 37 +++++++ src/Plugins/Qt/qt_simple_widget.hpp | 8 ++ src/Scheme/Glue/glue_editor.lua | 10 ++ 11 files changed, 284 insertions(+), 5 deletions(-) create mode 100644 TeXmacs/progs/generic/diff-text.scm diff --git a/TeXmacs/progs/generic/diff-text.scm b/TeXmacs/progs/generic/diff-text.scm new file mode 100644 index 0000000000..eef3ce84b6 --- /dev/null +++ b/TeXmacs/progs/generic/diff-text.scm @@ -0,0 +1,147 @@ +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; MODULE : diff-text.scm +;; DESCRIPTION : Diff Text feature for MoganSTEM +;; COPYRIGHT : (C) 2026 AcceleratorX +;; +;; 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 . +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(texmacs-module (generic diff-text) + (:use (kernel texmacs tm-define) (utils library cursor)) +) ;texmacs-module + +;; ============================================================================= +;; Helper functions +;; ============================================================================= + +(define (string-reverse s) + (list->string (reverse (string->list s))) +) ;define + +(define (find-version-both-in-tree t) + (cond ((not t) #f) + ((tree-is? t 'version-both) t) + ((tree-atomic? t) #f) + ((== (tree-arity t) 0) #f) + (else + (let loop ((i 0)) + (if (>= i (tree-arity t)) + #f + (or (find-version-both-in-tree (tree-ref t i)) + (loop (+ i 1))) + ) ;if + ) ;let + ) ;else + ) ;cond +) ;define + +(define (get-diff-tree) + (let ((t (tree-innermost 'version-both))) + (if t + t + (find-version-both-in-tree (root-tree)) + ) ;if + ) ;let +) ;define + +;; ============================================================================= +;; State variables for Diff Text +;; ============================================================================= + +(define diff-active? #f) + +(tm-define (is-diff-active?) + diff-active? +) ;tm-define + +(tm-define (diff-enable?) + (not (community-stem?)) +) ;tm-define + +;; ============================================================================= +;; Model evaluation & Feedback functions +;; ============================================================================= + +(tm-define (diff-feedback action) + ;; Action can be 'accept, 'reject, 'ignore + (noop) +) ;tm-define + +;; ============================================================================= +;; Diff Text core control flow +;; ============================================================================= + +(tm-define (trigger-diff-text) + (let* ((sel (selection-tree)) + (origin_str (tm->string sel)) + (suggested_str (string-reverse origin_str)) + (origin_text `(framed ,sel)) + (suggested_text `(framed ,suggested_str))) + (clipboard-cut "primary") + (insert `(version-both ,origin_text ,suggested_text)) + (insert-return) + (set! diff-active? #t) + (show-diff-popup) + ) ;let* +) ;tm-define + +(tm-define (accept-diff) + (let ((t (get-diff-tree))) + (when t + (let* ((framed-node (tree-ref t 1)) + (new-val (tree-ref framed-node 0))) + (tree-assign! t new-val) + ) ;let* + ) ;when + ) ;let + (set! diff-active? #f) + (hide-diff-popup) + (diff-feedback 'accept) +) ;tm-define + +(tm-define (reject-diff) + (let ((t (get-diff-tree))) + (when t + (let* ((framed-node (tree-ref t 0)) + (old-val (tree-ref framed-node 0))) + (tree-assign! t old-val) + ) ;let* + ) ;when + ) ;let + (set! diff-active? #f) + (hide-diff-popup) + (diff-feedback 'reject) +) ;tm-define + +(tm-define (ignore-diff) + (reject-diff) +) ;tm-define + +;; ============================================================================= +;; Keyboard Hook +;; ============================================================================= + +(tm-define (kbd-tab) + (:require (and (diff-enable?) (selection-active?))) + (trigger-diff-text) +) ;tm-define + +(tm-define (keyboard-press key time) + (:require (is-diff-active?)) + (cond ((== key "return") (accept-diff)) + ((== key "backspace") (reject-diff)) + (else (ignore-diff) (delayed (:idle 0) (keyboard-press key time))) + ) ;cond +) ;tm-define + +(tm-define (mouse-event key x y mods time data) + (:require (is-diff-active?)) + (former key x y mods time data) + (when (not (== key "move")) + (ignore-diff) + ) ;when +) ;tm-define diff --git a/TeXmacs/progs/generic/generic-kbd.scm b/TeXmacs/progs/generic/generic-kbd.scm index f0abef03b1..85150399a5 100644 --- a/TeXmacs/progs/generic/generic-kbd.scm +++ b/TeXmacs/progs/generic/generic-kbd.scm @@ -20,6 +20,7 @@ (generic document-edit) (generic generic-edit) (generic ghost-text) + (generic diff-text) (generic format-edit) (generic format-geometry-edit) (source source-edit) diff --git a/src/Edit/Interface/edit_interface.hpp b/src/Edit/Interface/edit_interface.hpp index 20a050479c..9811d0ee59 100644 --- a/src/Edit/Interface/edit_interface.hpp +++ b/src/Edit/Interface/edit_interface.hpp @@ -275,6 +275,8 @@ class edit_interface_rep : virtual public editor_rep { void show_ghost_popup (); void hide_ghost_popup (); + void show_diff_popup (); + void hide_diff_popup (); /* the footer */ tree get_shortcut_suffix (string cmd_s); diff --git a/src/Edit/Interface/edit_mouse.cpp b/src/Edit/Interface/edit_mouse.cpp index c0e5d6b5c5..5800ee0e65 100644 --- a/src/Edit/Interface/edit_mouse.cpp +++ b/src/Edit/Interface/edit_mouse.cpp @@ -1332,6 +1332,24 @@ edit_interface_rep::hide_ghost_popup () { #endif } +void +edit_interface_rep::show_diff_popup () { +#ifdef QTTEXMACS + if (qt_simple_widget_rep* qsw= dynamic_cast (this)) { + qsw->show_diff_popup (); + } +#endif +} + +void +edit_interface_rep::hide_diff_popup () { +#ifdef QTTEXMACS + if (qt_simple_widget_rep* qsw= dynamic_cast (this)) { + qsw->hide_diff_popup (); + } +#endif +} + bool edit_interface_rep::is_point_in_text_popup (SI x, SI y) { #ifdef QTTEXMACS diff --git a/src/Edit/editor.hpp b/src/Edit/editor.hpp index 36ea9c4da1..a1dc694c4d 100644 --- a/src/Edit/editor.hpp +++ b/src/Edit/editor.hpp @@ -189,6 +189,8 @@ class editor_rep : public simple_widget_rep { virtual void source_complete_try () = 0; virtual void show_ghost_popup () = 0; virtual void hide_ghost_popup () = 0; + virtual void show_diff_popup () = 0; + virtual void hide_diff_popup () = 0; virtual void complete_start (string prefix, array compls) = 0; virtual bool complete_keypress (string key) = 0; virtual bool source_complete_keypress (string key) = 0; diff --git a/src/Plugins/Qt/QTMUserPromptPopup.cpp b/src/Plugins/Qt/QTMUserPromptPopup.cpp index 2e510658cf..ab9f4bae75 100644 --- a/src/Plugins/Qt/QTMUserPromptPopup.cpp +++ b/src/Plugins/Qt/QTMUserPromptPopup.cpp @@ -18,7 +18,9 @@ // QTMUserPromptPopup: 用于处理用户与 AI 生成方案的交互,父类是 QTMBasePopup // ============================================================================= QTMUserPromptPopup::QTMUserPromptPopup (QWidget* parent, - qt_simple_widget_rep* owner) + qt_simple_widget_rep* owner, + const QString& acceptText, + const QString& rejectText) : QWidget (parent), owner (owner), layout (nullptr) { setObjectName ("user_prompt_popup"); @@ -70,7 +72,7 @@ QTMUserPromptPopup::QTMUserPromptPopup (QWidget* parent, container->setLayout (innerLayout); // 1. 接受按钮 (现代祖母绿配色) - acceptBtn= new QPushButton ("接受 →", container); + acceptBtn= new QPushButton (acceptText, container); acceptBtn->setObjectName ("accept_btn"); acceptBtn->setStyleSheet ("QPushButton#accept_btn { " "background-color: #10b981; " @@ -92,7 +94,7 @@ QTMUserPromptPopup::QTMUserPromptPopup (QWidget* parent, innerLayout->addWidget (acceptBtn); // 2. 拒绝按钮 (轻柔番茄红配色) - rejectBtn= new QPushButton ("拒绝 Esc", container); + rejectBtn= new QPushButton (rejectText, container); rejectBtn->setObjectName ("reject_btn"); rejectBtn->setStyleSheet ("QPushButton#reject_btn { " "background-color: #ef4444; " @@ -255,7 +257,7 @@ QTMUserPromptPopup::eventFilter (QObject* obj, QEvent* event) { // ============================================================================= QTMGhostTextPopup::QTMGhostTextPopup (QWidget* parent, qt_simple_widget_rep* owner) - : QTMUserPromptPopup (parent, owner) { + : QTMUserPromptPopup (parent, owner, "接受 →", "拒绝 Esc") { setObjectName ("ghost_text_popup"); } @@ -282,3 +284,36 @@ void QTMGhostTextPopup::onBadClicked () { call ("ghost-feedback", "bad"); } + +// ============================================================================= +// QTMDiffTextPopup: Diff Text AI建议的悬浮操作框,父类是 QTMUserPromptPopup +// ============================================================================= +QTMDiffTextPopup::QTMDiffTextPopup (QWidget* parent, + qt_simple_widget_rep* owner) + : QTMUserPromptPopup (parent, owner, "接受 Enter", "拒绝 Backspace") { + setObjectName ("diff_text_popup"); +} + +QTMDiffTextPopup::~QTMDiffTextPopup () {} + +void +QTMDiffTextPopup::onAcceptClicked () { + hide (); + call ("accept-diff"); +} + +void +QTMDiffTextPopup::onRejectClicked () { + hide (); + call ("reject-diff"); +} + +void +QTMDiffTextPopup::onGoodClicked () { + call ("diff-feedback", "good"); +} + +void +QTMDiffTextPopup::onBadClicked () { + call ("diff-feedback", "bad"); +} diff --git a/src/Plugins/Qt/QTMUserPromptPopup.hpp b/src/Plugins/Qt/QTMUserPromptPopup.hpp index 9fe81a7220..90fbe64eb7 100644 --- a/src/Plugins/Qt/QTMUserPromptPopup.hpp +++ b/src/Plugins/Qt/QTMUserPromptPopup.hpp @@ -31,7 +31,8 @@ class QTMUserPromptPopup : public QWidget { QPushButton* badBtn; public: - QTMUserPromptPopup (QWidget* parent, qt_simple_widget_rep* owner); + QTMUserPromptPopup (QWidget* parent, qt_simple_widget_rep* owner, + const QString& acceptText, const QString& rejectText); virtual ~QTMUserPromptPopup (); // 显示悬浮框并定位 @@ -77,4 +78,21 @@ class QTMGhostTextPopup : public QTMUserPromptPopup { void onBadClicked () override; }; +// ============================================================================= +// QTMDiffTextPopup: Diff Text AI建议的悬浮操作框,父类是 QTMUserPromptPopup +// ============================================================================= +class QTMDiffTextPopup : public QTMUserPromptPopup { + Q_OBJECT + +public: + QTMDiffTextPopup (QWidget* parent, qt_simple_widget_rep* owner); + ~QTMDiffTextPopup () override; + +protected: + void onAcceptClicked () override; + void onRejectClicked () override; + void onGoodClicked () override; + void onBadClicked () override; +}; + #endif // QT_USER_PROMPT_POPUP_HPP diff --git a/src/Plugins/Qt/QTMWidget.cpp b/src/Plugins/Qt/QTMWidget.cpp index a29dabe9c2..14eb04441b 100644 --- a/src/Plugins/Qt/QTMWidget.cpp +++ b/src/Plugins/Qt/QTMWidget.cpp @@ -163,6 +163,7 @@ QTMWidget::scrollContentsBy (int dx, int dy) { tm_widget ()->scroll_image_popup_by (dx, dy); tm_widget ()->scroll_text_popup_by (dx, dy); tm_widget ()->scroll_ghost_popup_by (dx, dy); + tm_widget ()->scroll_diff_popup_by (dx, dy); if (edit_interface_rep* ed= dynamic_cast (tm_widget ())) { ed->update_text_popup (); diff --git a/src/Plugins/Qt/qt_simple_widget.cpp b/src/Plugins/Qt/qt_simple_widget.cpp index 2a84a892cb..a53b393889 100644 --- a/src/Plugins/Qt/qt_simple_widget.cpp +++ b/src/Plugins/Qt/qt_simple_widget.cpp @@ -909,3 +909,40 @@ qt_simple_widget_rep::scroll_ghost_popup_by (SI x, SI y) { ghostTextPopup->updatePosition (); } } + +void +qt_simple_widget_rep::ensure_diff_popup () { + if (diffTextPopup) { + if (diffTextPopup->parent () != canvas ()) { + diffTextPopup->setParent (canvas ()); + } + return; + } + diffTextPopup= new QTMDiffTextPopup (canvas (), this); + if (is_empty (tm_style_sheet)) { + diffTextPopup->setStyle (qtmstyle ()); + } +} + +void +qt_simple_widget_rep::show_diff_popup () { + ensure_diff_popup (); + diffTextPopup->showPopup (); +} + +void +qt_simple_widget_rep::hide_diff_popup () { + if (diffTextPopup) { + diffTextPopup->hide (); + } +} + +void +qt_simple_widget_rep::scroll_diff_popup_by (SI x, SI y) { + if (diffTextPopup) { + QPoint qp (x, y); + coord2 p= from_qpoint (qp); + diffTextPopup->scrollBy (p.x1, p.x2); + diffTextPopup->updatePosition (); + } +} diff --git a/src/Plugins/Qt/qt_simple_widget.hpp b/src/Plugins/Qt/qt_simple_widget.hpp index 2b03d7cfdc..0ddaf30309 100644 --- a/src/Plugins/Qt/qt_simple_widget.hpp +++ b/src/Plugins/Qt/qt_simple_widget.hpp @@ -26,6 +26,7 @@ class QTMMathCompletionPopup; class QTMImagePopup; class QTMTextPopup; class QTMGhostTextPopup; +class QTMDiffTextPopup; /*! A widget containing a TeXmacs canvas. @@ -138,6 +139,12 @@ class qt_simple_widget_rep : public qt_widget_rep { void hide_ghost_popup (); void scroll_ghost_popup_by (SI x, SI y); + ////////////////////// Diff text popup support + void ensure_diff_popup (); + void show_diff_popup (); + void hide_diff_popup (); + void scroll_diff_popup_by (SI x, SI y); + ////////////////////// backing store management static void repaint_all (); // called by qt_gui_rep::update() @@ -150,6 +157,7 @@ class qt_simple_widget_rep : public qt_widget_rep { QPointer imagePopUp; QPointer textPopup; QPointer ghostTextPopup; + QPointer diffTextPopup; #ifdef USE_MUPDF_RENDERER double bs_zoomf; picture backing_store; diff --git a/src/Scheme/Glue/glue_editor.lua b/src/Scheme/Glue/glue_editor.lua index 05f276eff6..6da3294237 100644 --- a/src/Scheme/Glue/glue_editor.lua +++ b/src/Scheme/Glue/glue_editor.lua @@ -1114,6 +1114,16 @@ function main() cpp_name = "hide_ghost_popup", ret_type = "void" }, + { + scm_name = "show-diff-popup", + cpp_name = "show_diff_popup", + ret_type = "void" + }, + { + scm_name = "hide-diff-popup", + cpp_name = "hide_diff_popup", + ret_type = "void" + }, { scm_name = "get-input-mode", cpp_name = "get_input_mode", From cf4f314122073e46efffd370cffe90137bb648c4 Mon Sep 17 00:00:00 2001 From: AXeonV <2607343351@qq.com> Date: Thu, 16 Jul 2026 11:21:06 +0800 Subject: [PATCH 2/8] 2nd wip --- TeXmacs/progs/generic/diff-text.scm | 103 ++++++++++++++-------------- 1 file changed, 52 insertions(+), 51 deletions(-) diff --git a/TeXmacs/progs/generic/diff-text.scm b/TeXmacs/progs/generic/diff-text.scm index eef3ce84b6..52302a3265 100644 --- a/TeXmacs/progs/generic/diff-text.scm +++ b/TeXmacs/progs/generic/diff-text.scm @@ -11,7 +11,7 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (texmacs-module (generic diff-text) - (:use (kernel texmacs tm-define) (utils library cursor)) + (:use (kernel texmacs tm-define) (utils library cursor) (version version-compare)) ) ;texmacs-module ;; ============================================================================= @@ -22,30 +22,27 @@ (list->string (reverse (string->list s))) ) ;define -(define (find-version-both-in-tree t) - (cond ((not t) #f) - ((tree-is? t 'version-both) t) - ((tree-atomic? t) #f) - ((== (tree-arity t) 0) #f) - (else - (let loop ((i 0)) - (if (>= i (tree-arity t)) - #f - (or (find-version-both-in-tree (tree-ref t i)) - (loop (+ i 1))) - ) ;if - ) ;let - ) ;else - ) ;cond +(define (diff-check-popup) + (if (tree-innermost 'version-both) + (begin + (set! diff-active? #t) + (show-diff-popup) + ) ;begin + (begin + (set! diff-active? #f) + (hide-diff-popup) + ) ;begin + ) ;if ) ;define -(define (get-diff-tree) - (let ((t (tree-innermost 'version-both))) - (if t - t - (find-version-both-in-tree (root-tree)) - ) ;if - ) ;let +(define (diff-scan-next) + (if (not (tree-innermost 'version-both)) + (if (tree-is? (cursor-tree) 'version-both) + (tree-go-to (tree-ref (cursor-tree) 0) :start) ; 单差异特判:直接送入第一个子段落内部 + (go-to-next-tag 'version-both) ; 多差异常规流程:向后搜寻 + ) ;if + ) ;if + (delayed (:idle 1) (diff-check-popup)) ) ;define ;; ============================================================================= @@ -67,7 +64,6 @@ ;; ============================================================================= (tm-define (diff-feedback action) - ;; Action can be 'accept, 'reject, 'ignore (noop) ) ;tm-define @@ -77,52 +73,53 @@ (tm-define (trigger-diff-text) (let* ((sel (selection-tree)) + (origin_stree (tree->stree sel)) (origin_str (tm->string sel)) (suggested_str (string-reverse origin_str)) - (origin_text `(framed ,sel)) - (suggested_text `(framed ,suggested_str))) + ;; 精度选项:"detailed" (字符级)、"block" (块级)、"rough" (粗粒度) + (diff-stree (compare-versions origin_stree suggested_str)) + (diff-tree (stree->tree diff-stree)) + (p (cursor-path))) (clipboard-cut "primary") - (insert `(version-both ,origin_text ,suggested_text)) + (insert diff-tree) (insert-return) - (set! diff-active? #t) - (show-diff-popup) + (go-to p) + (diff-scan-next) ) ;let* ) ;tm-define (tm-define (accept-diff) - (let ((t (get-diff-tree))) + (let ((t (tree-innermost 'version-both))) (when t - (let* ((framed-node (tree-ref t 1)) - (new-val (tree-ref framed-node 0))) - (tree-assign! t new-val) + (let* ((new-val (tree-ref t 1)) + (p (tree-up t)) + (i (tree-index t))) + (tree-remove! p i 1) + (insert new-val) ) ;let* ) ;when ) ;let - (set! diff-active? #f) - (hide-diff-popup) (diff-feedback 'accept) + (diff-scan-next) ) ;tm-define (tm-define (reject-diff) - (let ((t (get-diff-tree))) + (let ((t (tree-innermost 'version-both))) (when t - (let* ((framed-node (tree-ref t 0)) - (old-val (tree-ref framed-node 0))) - (tree-assign! t old-val) + (let* ((old-val (tree-ref t 0)) + (p (tree-up t)) + (i (tree-index t))) + (tree-remove! p i 1) + (insert old-val) ) ;let* ) ;when ) ;let - (set! diff-active? #f) - (hide-diff-popup) (diff-feedback 'reject) -) ;tm-define - -(tm-define (ignore-diff) - (reject-diff) + (diff-scan-next) ) ;tm-define ;; ============================================================================= -;; Keyboard Hook +;; Keyboard and Mouse Hooks ;; ============================================================================= (tm-define (kbd-tab) @@ -134,14 +131,18 @@ (:require (is-diff-active?)) (cond ((== key "return") (accept-diff)) ((== key "backspace") (reject-diff)) - (else (ignore-diff) (delayed (:idle 0) (keyboard-press key time))) + (else (former key time)) ) ;cond ) ;tm-define +(tm-define (keyboard-press key time) + (:require (diff-enable?)) + (former key time) + (delayed (:idle 1) (diff-check-popup)) +) ;tm-define + (tm-define (mouse-event key x y mods time data) - (:require (is-diff-active?)) + (:require (diff-enable?)) (former key x y mods time data) - (when (not (== key "move")) - (ignore-diff) - ) ;when + (delayed (:idle 1) (diff-check-popup)) ) ;tm-define From e2d6bf23cf2405b4ea6cf2d4e37c10347b84cd8d Mon Sep 17 00:00:00 2001 From: AXeonV <2607343351@qq.com> Date: Thu, 16 Jul 2026 11:38:07 +0800 Subject: [PATCH 3/8] 3rd wip --- TeXmacs/progs/generic/diff-text.scm | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/TeXmacs/progs/generic/diff-text.scm b/TeXmacs/progs/generic/diff-text.scm index 52302a3265..853b05ccfb 100644 --- a/TeXmacs/progs/generic/diff-text.scm +++ b/TeXmacs/progs/generic/diff-text.scm @@ -42,7 +42,7 @@ (go-to-next-tag 'version-both) ; 多差异常规流程:向后搜寻 ) ;if ) ;if - (delayed (:idle 1) (diff-check-popup)) + (delayed (:idle 0) (diff-check-popup)) ) ;define ;; ============================================================================= @@ -100,6 +100,8 @@ ) ;when ) ;let (diff-feedback 'accept) + (set! diff-active? #f) + (hide-diff-popup) (diff-scan-next) ) ;tm-define @@ -115,6 +117,8 @@ ) ;when ) ;let (diff-feedback 'reject) + (set! diff-active? #f) + (hide-diff-popup) (diff-scan-next) ) ;tm-define @@ -138,11 +142,11 @@ (tm-define (keyboard-press key time) (:require (diff-enable?)) (former key time) - (delayed (:idle 1) (diff-check-popup)) + (delayed (:idle 0) (diff-check-popup)) ) ;tm-define (tm-define (mouse-event key x y mods time data) (:require (diff-enable?)) (former key x y mods time data) - (delayed (:idle 1) (diff-check-popup)) + (delayed (:idle 0) (diff-check-popup)) ) ;tm-define From 11a99ea8f8c216ab4cebefcb5b92ff0517cadde6 Mon Sep 17 00:00:00 2001 From: AXeonV <2607343351@qq.com> Date: Thu, 16 Jul 2026 12:53:32 +0800 Subject: [PATCH 4/8] 4th wip --- TeXmacs/progs/generic/diff-text.scm | 121 ++++++++++++++++++++-------- 1 file changed, 89 insertions(+), 32 deletions(-) diff --git a/TeXmacs/progs/generic/diff-text.scm b/TeXmacs/progs/generic/diff-text.scm index 853b05ccfb..53bcf4da93 100644 --- a/TeXmacs/progs/generic/diff-text.scm +++ b/TeXmacs/progs/generic/diff-text.scm @@ -18,28 +18,80 @@ ;; Helper functions ;; ============================================================================= -(define (string-reverse s) - (list->string (reverse (string->list s))) +(define (remove-random-thes words count) + (cond ((null? words) '()) + ((<= count 0) words) + ((== (car words) "the") + (cond ((<= (random 3) 1) + (remove-random-thes (cdr words) (- count 1))) + (else (cons (car words) (remove-random-thes (cdr words) count))) + ) ;cond + ) ; + (else (cons (car words) (remove-random-thes (cdr words) count))) + ) ;cond +) ;define + +(define (insert-random-as words count) + (cond ((null? words) + (if (> count 0) + (cons "a" (insert-random-as '() (- count 1))) + '() + ) ;if + ) ; + ((<= count 0) words) + (else + (if (== (random 5) 0) + (cons "a" (insert-random-as words (- count 1))) + (cons (car words) (insert-random-as (cdr words) count)) + ) ;if + ) ; + ) ;cond +) ;define + +(define (upcase-random-words words count) + (cond ((or (null? words) (<= count 0)) words) + ((> (string-length (car words)) 1) + (if (== (random 4) 0) + (cons (upcase-all (car words)) (upcase-random-words (cdr words) (- count 1))) + (cons (car words) (upcase-random-words (cdr words) count)) + ) ;if + ) ; + (else (cons (car words) (upcase-random-words (cdr words) count))) + ) ;cond +) ;define + +(define (demo-suggest t) + (cond ((not t) #f) + ((string? t) + (let* ((words (string-split t #\space)) + (words-no-the (remove-random-thes words 2)) + (words-with-a (insert-random-as words-no-the 2)) + (final-words (upcase-random-words words-with-a 3))) + (string-recompose final-words " ") + ) ;let* + ) ; + ((pair? t) (cons (car t) (map demo-suggest (cdr t)))) + (else t) + ) ;cond ) ;define (define (diff-check-popup) - (if (tree-innermost 'version-both) - (begin - (set! diff-active? #t) - (show-diff-popup) - ) ;begin - (begin - (set! diff-active? #f) - (hide-diff-popup) - ) ;begin - ) ;if + ;; 先隐藏弹窗,避免位置错乱 + (set! diff-active? #f) + (hide-diff-popup) + (when (tree-innermost 'version-both) + (set! diff-active? #t) + (show-diff-popup) + ) ;when ) ;define (define (diff-scan-next) (if (not (tree-innermost 'version-both)) (if (tree-is? (cursor-tree) 'version-both) - (tree-go-to (tree-ref (cursor-tree) 0) :start) ; 单差异特判:直接送入第一个子段落内部 - (go-to-next-tag 'version-both) ; 多差异常规流程:向后搜寻 + ;; 单差异特判:直接送入第一个子段落内部 + (tree-go-to (tree-ref (cursor-tree) 0) :start) + ;; 多差异常规流程:向后搜寻 + (go-to-next-tag 'version-both) ) ;if ) ;if (delayed (:idle 0) (diff-check-popup)) @@ -74,17 +126,20 @@ (tm-define (trigger-diff-text) (let* ((sel (selection-tree)) (origin_stree (tree->stree sel)) - (origin_str (tm->string sel)) - (suggested_str (string-reverse origin_str)) - ;; 精度选项:"detailed" (字符级)、"block" (块级)、"rough" (粗粒度) - (diff-stree (compare-versions origin_stree suggested_str)) - (diff-tree (stree->tree diff-stree)) - (p (cursor-path))) - (clipboard-cut "primary") - (insert diff-tree) - (insert-return) - (go-to p) - (diff-scan-next) + (suggested_stree (demo-suggest origin_stree)) + (pre-cur (cursor-path)) + (pre-grain (get-preference "versioning grain"))) + ;; 设为字符级精度 "detailed";其余选项:"block" (块级)、"rough" (粗粒度) + (set-preference "versioning grain" "detailed") + (let* ((diff-stree (compare-versions origin_stree suggested_stree)) + (diff-tree (stree->tree diff-stree))) + (clipboard-cut "primary") + (insert diff-tree) + (go-to pre-cur) + (diff-scan-next) + ) ;let* + ;; 还原精度 + (set-preference "versioning grain" pre-grain) ) ;let* ) ;tm-define @@ -95,13 +150,13 @@ (p (tree-up t)) (i (tree-index t))) (tree-remove! p i 1) - (insert new-val) + (when (not (tree-is? new-val 'version-suppressed)) + (insert new-val) + ) ;when ) ;let* ) ;when ) ;let (diff-feedback 'accept) - (set! diff-active? #f) - (hide-diff-popup) (diff-scan-next) ) ;tm-define @@ -112,13 +167,13 @@ (p (tree-up t)) (i (tree-index t))) (tree-remove! p i 1) - (insert old-val) + (when (not (tree-is? old-val 'version-suppressed)) + (insert old-val) + ) ;when ) ;let* ) ;when ) ;let (diff-feedback 'reject) - (set! diff-active? #f) - (hide-diff-popup) (diff-scan-next) ) ;tm-define @@ -148,5 +203,7 @@ (tm-define (mouse-event key x y mods time data) (:require (diff-enable?)) (former key x y mods time data) - (delayed (:idle 0) (diff-check-popup)) + (when (not (== key "move")) + (delayed (:idle 0) (diff-check-popup)) + ) ;when ) ;tm-define From 4a6871a8bd460158a1ee437e4f26b8b36327c7a9 Mon Sep 17 00:00:00 2001 From: AXeonV <2607343351@qq.com> Date: Thu, 16 Jul 2026 12:58:01 +0800 Subject: [PATCH 5/8] gf fmt --- TeXmacs/progs/generic/diff-text.scm | 69 +++++++++++++---------------- 1 file changed, 30 insertions(+), 39 deletions(-) diff --git a/TeXmacs/progs/generic/diff-text.scm b/TeXmacs/progs/generic/diff-text.scm index 53bcf4da93..9ba7628796 100644 --- a/TeXmacs/progs/generic/diff-text.scm +++ b/TeXmacs/progs/generic/diff-text.scm @@ -11,7 +11,10 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (texmacs-module (generic diff-text) - (:use (kernel texmacs tm-define) (utils library cursor) (version version-compare)) + (:use (kernel texmacs tm-define) + (utils library cursor) + (version version-compare) + ) ;:use ) ;texmacs-module ;; ============================================================================= @@ -22,8 +25,7 @@ (cond ((null? words) '()) ((<= count 0) words) ((== (car words) "the") - (cond ((<= (random 3) 1) - (remove-random-thes (cdr words) (- count 1))) + (cond ((<= (random 3) 1) (remove-random-thes (cdr words) (- count 1))) (else (cons (car words) (remove-random-thes (cdr words) count))) ) ;cond ) ; @@ -33,18 +35,14 @@ (define (insert-random-as words count) (cond ((null? words) - (if (> count 0) - (cons "a" (insert-random-as '() (- count 1))) - '() - ) ;if + (if (> count 0) (cons "a" (insert-random-as '() (- count 1))) '()) ) ; ((<= count 0) words) - (else - (if (== (random 5) 0) - (cons "a" (insert-random-as words (- count 1))) - (cons (car words) (insert-random-as (cdr words) count)) - ) ;if - ) ; + (else (if (== (random 5) 0) + (cons "a" (insert-random-as words (- count 1))) + (cons (car words) (insert-random-as (cdr words) count)) + ) ;if + ) ;else ) ;cond ) ;define @@ -52,8 +50,8 @@ (cond ((or (null? words) (<= count 0)) words) ((> (string-length (car words)) 1) (if (== (random 4) 0) - (cons (upcase-all (car words)) (upcase-random-words (cdr words) (- count 1))) - (cons (car words) (upcase-random-words (cdr words) count)) + (cons (upcase-all (car words)) (upcase-random-words (cdr words) (- count 1))) + (cons (car words) (upcase-random-words (cdr words) count)) ) ;if ) ; (else (cons (car words) (upcase-random-words (cdr words) count))) @@ -66,7 +64,8 @@ (let* ((words (string-split t #\space)) (words-no-the (remove-random-thes words 2)) (words-with-a (insert-random-as words-no-the 2)) - (final-words (upcase-random-words words-with-a 3))) + (final-words (upcase-random-words words-with-a 3)) + ) ; (string-recompose final-words " ") ) ;let* ) ; @@ -87,12 +86,12 @@ (define (diff-scan-next) (if (not (tree-innermost 'version-both)) - (if (tree-is? (cursor-tree) 'version-both) - ;; 单差异特判:直接送入第一个子段落内部 - (tree-go-to (tree-ref (cursor-tree) 0) :start) - ;; 多差异常规流程:向后搜寻 - (go-to-next-tag 'version-both) - ) ;if + (if (tree-is? (cursor-tree) 'version-both) + ;; 单差异特判:直接送入第一个子段落内部 + (tree-go-to (tree-ref (cursor-tree) 0) :start) + ;; 多差异常规流程:向后搜寻 + (go-to-next-tag 'version-both) + ) ;if ) ;if (delayed (:idle 0) (diff-check-popup)) ) ;define @@ -103,21 +102,15 @@ (define diff-active? #f) -(tm-define (is-diff-active?) - diff-active? -) ;tm-define +(tm-define (is-diff-active?) diff-active?) -(tm-define (diff-enable?) - (not (community-stem?)) -) ;tm-define +(tm-define (diff-enable?) (not (community-stem?))) ;; ============================================================================= ;; Model evaluation & Feedback functions ;; ============================================================================= -(tm-define (diff-feedback action) - (noop) -) ;tm-define +(tm-define (diff-feedback action) (noop)) ;; ============================================================================= ;; Diff Text core control flow @@ -128,11 +121,13 @@ (origin_stree (tree->stree sel)) (suggested_stree (demo-suggest origin_stree)) (pre-cur (cursor-path)) - (pre-grain (get-preference "versioning grain"))) + (pre-grain (get-preference "versioning grain")) + ) ; ;; 设为字符级精度 "detailed";其余选项:"block" (块级)、"rough" (粗粒度) (set-preference "versioning grain" "detailed") (let* ((diff-stree (compare-versions origin_stree suggested_stree)) - (diff-tree (stree->tree diff-stree))) + (diff-tree (stree->tree diff-stree)) + ) ; (clipboard-cut "primary") (insert diff-tree) (go-to pre-cur) @@ -146,9 +141,7 @@ (tm-define (accept-diff) (let ((t (tree-innermost 'version-both))) (when t - (let* ((new-val (tree-ref t 1)) - (p (tree-up t)) - (i (tree-index t))) + (let* ((new-val (tree-ref t 1)) (p (tree-up t)) (i (tree-index t))) (tree-remove! p i 1) (when (not (tree-is? new-val 'version-suppressed)) (insert new-val) @@ -163,9 +156,7 @@ (tm-define (reject-diff) (let ((t (tree-innermost 'version-both))) (when t - (let* ((old-val (tree-ref t 0)) - (p (tree-up t)) - (i (tree-index t))) + (let* ((old-val (tree-ref t 0)) (p (tree-up t)) (i (tree-index t))) (tree-remove! p i 1) (when (not (tree-is? old-val 'version-suppressed)) (insert old-val) From e33a668084510b6bf552765bb76654bfa25e783a Mon Sep 17 00:00:00 2001 From: AXeonV <2607343351@qq.com> Date: Thu, 16 Jul 2026 13:20:18 +0800 Subject: [PATCH 6/8] add devel --- TeXmacs/tests/tmu/0840.tmu | 42 ++++++++++++++++++++++++++++++++ devel/0840.md | 50 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 TeXmacs/tests/tmu/0840.tmu create mode 100644 devel/0840.md diff --git a/TeXmacs/tests/tmu/0840.tmu b/TeXmacs/tests/tmu/0840.tmu new file mode 100644 index 0000000000..564580ee72 --- /dev/null +++ b/TeXmacs/tests/tmu/0840.tmu @@ -0,0 +1,42 @@ +> + +> + +<\body> + + + Southern University of Science and Technology (SUSTech) is an innovation-oriented public university founded by Shenzhen government in the background of China's higher education reform. It aspires to be a model and pioneer for promoting higher education reform. It is committed to serving the mission of promoting Shenzhen as a modern, international, and innovative city and China as a creative country. + + First, SUSTech is widely regarded as a trailblazer and innovator in advancing China's higher education. It was officially approved by the Ministry of Education in April 2012. SUSTech bears the responsibility for exploring and developing a modern university system with Chinese characteristics to serve as a model for cultivating innovative talents. SUSTech aims at a globally renowned university that contributes to the advancement of science and technology. It nurtures promising and creative leaders who excel in interdisciplinary research and creating knowledge for the world. + + Second, SUSTech draws on the experience of world-class science and engineering universities for its disciplinary establishment and governance. It focuses on science, engineering, and medicine in conjunction with distinctive disciplines, including business, humanities, and social sciences. SUSTech offers undergraduate and postgraduate education while conducting research in a series of innovative disciplines. All of those practices shape SUSTech into a think tank for social progress and a generator of new knowledge and new technology. + + Third, SUSTech is building interdisciplinary research centers to generate new scientific and technological wisdom in cross-disciplinary fields such as artificial intelligence, life sciences, Internet of things, robotics, new energy, and intelligent manufacturing. + + In the spirit of "For Truth, Innovation, Reform and Excellence with Diligence and Courage," SUSTech highlights "research, innovation, and entrepreneurship" and dedicates to facilitating innovative projects across China and turning Shenzhen into an innovative, modern, and international metropolis. + + SUSTech also seeks to become an international high-level research university that gathers first-class faculty and nurtures top-notch innovative talents. It aims to produce internationally recognized academic achievements and advance scientific and technological applications. + + +<\initial> + <\collection> + + + + + + + +<\references> + <\collection> + > + + + +<\auxiliary> + <\collection> + <\associate|toc> + |math-font-series||GENERAL INFORMATION>|.>>>>|> + + + diff --git a/devel/0840.md b/devel/0840.md new file mode 100644 index 0000000000..6e25246a10 --- /dev/null +++ b/devel/0840.md @@ -0,0 +1,50 @@ +# [0840] AI Suggestion 功能初步实现 + +## 1 相关文档 +- [0834](0834.md) - 自动补全初步实现 + +## 2 任务相关的代码文件 +- `TeXmacs/progs/generic/generic-kbd.scm` — 注册 `kbd-tab` 键盘钩子以引入 `diff-text` 模块 +- `TeXmacs/progs/generic/diff-text.scm` — 新增 `diff-text` 模块,实现富文本 AST 树级递归修改生成、自愈式双重重载按键拦截及鼠标环境自适应检测逻辑 +- `src/Edit/editor.hpp` / `src/Edit/Interface/edit_interface.hpp` — 添加 `show_diff_popup`/`hide_diff_popup` 的编辑器接口声明 +- `src/Edit/Interface/edit_mouse.cpp` — 实现编辑器接口转调至 Qt 侧简易部件弹窗接口 +- `src/Scheme/Glue/glue_editor.lua` — 为 `show_diff_popup`/`hide_diff_popup` 注册 Scheme 与 C++ 黏合绑定 +- `src/Plugins/Qt/QTMUserPromptPopup.hpp` / `QTMUserPromptPopup.cpp` — 重构基类 `QTMUserPromptPopup` 支持子类自定义按钮文本(“接受 Enter”/“拒绝 Backspace”),并实现派生子类 `QTMDiffTextPopup` +- `src/Plugins/Qt/qt_simple_widget.hpp` / `qt_simple_widget.cpp` — 管理 `diffTextPopup` 弹窗实例的生命周期、显示隐藏与滚动 +- `src/Plugins/Qt/QTMWidget.cpp` — 增加滚动事件监听,确保页面滚动时差异对比弹窗同步跟随滚动 + +## 3 如何测试 + +### 3.1 确定性测试(单元测试) +```bash +xmake b stem +xmake r stem +``` + +### 3.2 非确定性测试(交互验证) +1. 使用商业版(`xmake f --is_community=false && xmake b stem`)构建 MoganSTEM +2. 打开 `TeXmacs\tests\tmu\0840.tmu` +3. 选中**一个**段落,按下 `tab` 触发一个 AI Suggestion 的 demo(随机删除一些 `the`,插入一些 `a`,大写一些单词) +4. 选中**多个**段落,按下 `tab` 也会触发 AI Suggestion,逻辑相同 +5. 鼠标可以在不同的 `version-both` 节点间跳转,弹窗会自动贴合光标位置 +6. 也可以通过键盘移动光标到不同的 `version-both` 节点,弹窗会自动贴合光标位置 +7. 光标进入 `version-both` 节点后,可以通过快捷键 `Enter`/`Backspace` 来同意或拒绝当前的差异对比,会立刻生效 +8. 也可以通过鼠标点击弹窗上的按钮来同意或拒绝当前的差异对比,会立刻生效 +9. 使用社区版(`xmake f --is_community=true && xmake b stem`)构建 MoganSTEM,上述功能不被触发 + +## 4 What +在自动补全框架下进一步设计并实现了基于富文本 AST 对照的差异对比预览(Diff Text)交互系统: +- 支持对多段落、富文本及数学公式等复杂节点在保留原有排版骨架的前提下进行细粒度字符比对。 +- 实现一处一处跳转确认的冲突决策(Conflict-by-conflict)流转。 +- 引入无状态自愈式全局监听器,支持弹窗位置随光标的移动而自动贴合、隐藏和显示。 +- 在 C++ 侧重构了气泡弹窗,支持在子类中自定义采纳和拒绝的快捷键文案。 + +## 5 How +1. **树级递归建议生成**: + 在 `diff-text.scm` 中,设计 `demo-suggest` 函数递归扫描选区树,保持原有树节点拓扑外壳,仅在叶子文本节点上应用随机的删除、增加与大写变换逻辑。 +2. **编辑器级删除与插入**: + 在同意(`accept-diff`)或拒绝(`reject-diff`)时,通过 `tree-remove!` 在位移除当前的 `version-both` 节点,并在光标前执行 `(insert ...)` 原地写入纯文本内容。 +3. **逐处跳转与弹窗自适应**: + 通过在 `keyboard-press` 增加双重函数重载分发,区分高优先级决策按键拦截(回车/退格)与低优先级常驻事件监听。当光标被流转至下一处或通过鼠标、方向键位移至 `version-both` 节点内部时,延迟调用 `diff-check-popup`。在其中先隐后显,确保弹窗位置始终自适应贴合最新光标。 +4. **自定义按钮文案**: + 重构基类 `QTMUserPromptPopup` 构造函数接收 `acceptText` 与 `rejectText` 参数,并在子类 `QTMGhostTextPopup` 与 `QTMDiffTextPopup` 中调用基构造函数定义特定的提示文案。 From ac47461b5ea9376d00f9d45b57b36eb34bf98425 Mon Sep 17 00:00:00 2001 From: AXeonV <2607343351@qq.com> Date: Thu, 16 Jul 2026 15:32:22 +0800 Subject: [PATCH 7/8] 5th wip --- TeXmacs/packages/standard/std-fold.ts | 14 +++++++++----- devel/0840.md | 7 ++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/TeXmacs/packages/standard/std-fold.ts b/TeXmacs/packages/standard/std-fold.ts index 5e176fcad4..96c510ff79 100644 --- a/TeXmacs/packages/standard/std-fold.ts +++ b/TeXmacs/packages/standard/std-fold.ts @@ -661,13 +661,17 @@ - + - + - |>>> + - |>>> + + + |text-bg-color||>>> + + |text-bg-color||>>> >>> @@ -683,7 +687,7 @@ >|document>|version-both-big|version-both-small>||>>> - >>>> + > <\initial> diff --git a/devel/0840.md b/devel/0840.md index 6e25246a10..4fdfc93f83 100644 --- a/devel/0840.md +++ b/devel/0840.md @@ -6,6 +6,7 @@ ## 2 任务相关的代码文件 - `TeXmacs/progs/generic/generic-kbd.scm` — 注册 `kbd-tab` 键盘钩子以引入 `diff-text` 模块 - `TeXmacs/progs/generic/diff-text.scm` — 新增 `diff-text` 模块,实现富文本 AST 树级递归修改生成、自愈式双重重载按键拦截及鼠标环境自适应检测逻辑 +- `TeXmacs/packages/standard/std-fold.ts` — 将 `version-both` 差异配色及背景色升级为对齐弹窗的现代淡色调,并将 `version-suppressed` 空白占位符设置为空白隐藏 - `src/Edit/editor.hpp` / `src/Edit/Interface/edit_interface.hpp` — 添加 `show_diff_popup`/`hide_diff_popup` 的编辑器接口声明 - `src/Edit/Interface/edit_mouse.cpp` — 实现编辑器接口转调至 Qt 侧简易部件弹窗接口 - `src/Scheme/Glue/glue_editor.lua` — 为 `show_diff_popup`/`hide_diff_popup` 注册 Scheme 与 C++ 黏合绑定 @@ -38,13 +39,17 @@ xmake r stem - 实现一处一处跳转确认的冲突决策(Conflict-by-conflict)流转。 - 引入无状态自愈式全局监听器,支持弹窗位置随光标的移动而自动贴合、隐藏和显示。 - 在 C++ 侧重构了气泡弹窗,支持在子类中自定义采纳和拒绝的快捷键文案。 +- 重构了 `version-both` 样式与配色高亮,使其底色更为淡雅柔和。 +- 隐藏了 `version-suppressed` 占位节点原本的乘号(`×`)图标,使其在对比时完全隐形以改善观感。 ## 5 How 1. **树级递归建议生成**: 在 `diff-text.scm` 中,设计 `demo-suggest` 函数递归扫描选区树,保持原有树节点拓扑外壳,仅在叶子文本节点上应用随机的删除、增加与大写变换逻辑。 2. **编辑器级删除与插入**: - 在同意(`accept-diff`)或拒绝(`reject-diff`)时,通过 `tree-remove!` 在位移除当前的 `version-both` 节点,并在光标前执行 `(insert ...)` 原地写入纯文本内容。 + 在同意(`accept-diff`)或拒绝(`reject-diff`)时,通过 `tree-remove!` 在位移除当前的 `version-both` 节点,并在光标前执行 `(insert ...)` 原地写入纯文本内容。对于纯删除或增加所产生的 `version-suppressed` 占位符节点做空插入特判。 3. **逐处跳转与弹窗自适应**: 通过在 `keyboard-press` 增加双重函数重载分发,区分高优先级决策按键拦截(回车/退格)与低优先级常驻事件监听。当光标被流转至下一处或通过鼠标、方向键位移至 `version-both` 节点内部时,延迟调用 `diff-check-popup`。在其中先隐后显,确保弹窗位置始终自适应贴合最新光标。 4. **自定义按钮文案**: 重构基类 `QTMUserPromptPopup` 构造函数接收 `acceptText` 与 `rejectText` 参数,并在子类 `QTMGhostTextPopup` 与 `QTMDiffTextPopup` 中调用基构造函数定义特定的提示文案。 +5. **样式高亮与占位隐藏**: + 在 `std-fold.ts` 中,将新旧版本差异配色对齐到弹窗按键的 `#10b981` (绿) 与 `#ef4444` (红),并使用 `text-bg-color` 附带现代极淡底色高亮(`#ecfdf5` 和 `#fef2f2`)。同时重构 `version-suppressed` 宏为空宏使其完全隐形。 From 80354feafd8d1bd580524fbf51e4f72b3c1c2327 Mon Sep 17 00:00:00 2001 From: AXeonV <2607343351@qq.com> Date: Fri, 17 Jul 2026 11:48:27 +0800 Subject: [PATCH 8/8] +refresh --- TeXmacs/progs/generic/diff-text.scm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TeXmacs/progs/generic/diff-text.scm b/TeXmacs/progs/generic/diff-text.scm index 9ba7628796..9272cc23c1 100644 --- a/TeXmacs/progs/generic/diff-text.scm +++ b/TeXmacs/progs/generic/diff-text.scm @@ -150,6 +150,7 @@ ) ;when ) ;let (diff-feedback 'accept) + (refresh-window) (diff-scan-next) ) ;tm-define @@ -165,6 +166,7 @@ ) ;when ) ;let (diff-feedback 'reject) + (refresh-window) (diff-scan-next) ) ;tm-define