Skip to content
128 changes: 128 additions & 0 deletions TeXmacs/progs/convert/latex/init-latex.scm
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,131 @@

(converter latex-tree texmacs-tree
(:function latex->texmacs))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Post-processing imported LaTeX: insert space between d and differential
;; variables so they are not merged into a single operator in math mode.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(define (is-letter-char? c)
(and (char? c)
(or (and (char>=? c #\a) (char<=? c #\z))
(and (char>=? c #\A) (char<=? c #\Z)))))

(define (is-word-boundary-before? s i)
(or (= i 0)
(not (is-letter-char? (string-ref s (- i 1))))))

(define (is-word-boundary-after? s i)
(or (= i (- (string-length s) 1))
(not (is-letter-char? (string-ref s (+ i 1))))))

(define (match-differential s i)
(and (< i (- (string-length s) 1))
(char=? (string-ref s i) #\d)
(char=? (string-ref s (+ i 1)) #\*)
(let ((rest (substring s (+ i 2) (string-length s))))
(cond ((or (string-starts? rest "x")
(string-starts? rest "y")
(string-starts? rest "z")
(string-starts? rest "r"))
(cons 1 (substring rest 0 1)))
((string-starts? rest "<rho>")
(cons 5 "<rho>"))
((string-starts? rest "<varrho>")
(cons 8 "<varrho>"))
((string-starts? rest "<theta>")
(cons 7 "<theta>"))
((string-starts? rest "<vartheta>")
(cons 10 "<vartheta>"))
(else #f)))))

(define (transform-math-string s)
(let* ((n (string-length s))
(res '()))
(let loop ((i 0) (last-idx 0))
(cond ((>= i n)
(if (null? res) s
(begin
(if (< last-idx n)
(set! res (append res (list (substring s last-idx n)))))
(cons 'concat res))))
(else
(let ((match (match-differential s i)))
(if (and match
(is-word-boundary-before? s i)
(is-word-boundary-after? s (+ i 1 (car match))))
(let* ((match-len (car match))
(var (cdr match)))
(if (> i last-idx)
(set! res (append res (list (substring s last-idx i)))))
(set! res (append res (list "d" " " var)))
(loop (+ i 2 match-len) (+ i 2 match-len)))
(loop (+ i 1) last-idx))))))))

(define (transform-concat-children children)
(cond ((null? children) '())
((and (pair? children) (pair? (cdr children)))
(let* ((c1 (car children))
(c2 (cadr children)))
(if (and (string? c1) (string? c2)
(or (string=? c2 "<rho>") (string=? c2 "<varrho>")
(string=? c2 "<theta>") (string=? c2 "<vartheta>"))
(let ((len (string-length c1)))
(and (> len 0)
(char=? (string-ref c1 (- len 1)) #\d)
(or (= len 1)
(not (is-letter-char? (string-ref c1 (- len 2))))))))
(let* ((len (string-length c1))
(prefix (if (> len 1) (substring c1 0 (- len 1)) #f))
(spaced-part (if prefix (list prefix "d" " " c2) (list "d" " " c2))))
(append spaced-part (transform-concat-children (cddr children))))
(cons (car children) (transform-concat-children (cdr children))))))
(else children)))

(define math-environments
'(math equation equation* eqnarray eqnarray* align align* multline multline*))

(define (upgrade-latex-differentials-stree t in-math)
(cond ((string? t)
(if in-math
(transform-math-string t)
t))
((pair? t)
(let* ((head (car t))
(next-in-math (or in-math (memq head math-environments))))
(if (and next-in-math (eq? head 'concat))
(let* ((new-children (map (lambda (x) (upgrade-latex-differentials-stree x #t)) (cdr t)))
(transformed-children (transform-concat-children new-children)))
(cons 'concat transformed-children))
(cons head (map (lambda (x) (upgrade-latex-differentials-stree x next-in-math)) (cdr t))))))
(else t)))

(define latex->texmacs-original latex->texmacs)

(tm-define (latex->texmacs t)
(let* ((res (latex->texmacs-original t))
(st (tree->stree res))
(new-st (upgrade-latex-differentials-stree st #f)))
(stree->tree new-st)))

(define latex-document->texmacs-original latex-document->texmacs)

(tm-define (latex-document->texmacs x . opts)
(let* ((res (apply latex-document->texmacs-original (cons x opts)))
(st (tree->stree res))
(new-st (upgrade-latex-differentials-stree st #f)))
(stree->tree new-st)))

;; Re-register converters so that `converter-function` table points
;; to our wrapper definitions. The `converter` macro resolves the
;; function symbol at registration time; simply redefining the symbol
;; afterwards leaves the old reference in the table.
(converter latex-tree texmacs-tree
(:function latex->texmacs))
(converter latex-document texmacs-tree
(:function-with-options latex-document->texmacs)
(:option "latex->texmacs:fallback-on-pictures" "on")
(:option "latex->texmacs:source-tracking" "off")
(:option "latex->texmacs:conservative" "off")
(:option "latex->texmacs:transparent-source-tracking" "off"))
20 changes: 19 additions & 1 deletion TeXmacs/progs/math/math-kbd.scm
Original file line number Diff line number Diff line change
Expand Up @@ -2881,7 +2881,25 @@
;;("l l" "<mathlambda>")
;;("p p" "<mathpi>")
("R E" "<Re>")
("I M" "<Im>"))
("I M" "<Im>")
("d x" "<mathd>x")
("d x var" "<mathd><xi>")
("d x var var" "dx")
("d y" "<mathd>y")
("d y var" "<mathd><psi>")
("d y var var" "dy")
("d z" "<mathd>z")
("d z var" "<mathd><zeta>")
("d z var var" "dz")
("d r" "<mathd>r")
("d r var" "<mathd><rho>")
("d r var var" "<mathd><varrho>")
("d r var var var" "dr")
("d j" "<mathd>j")
("d j var" "<mathd><theta>")
("d j var var" "<mathd><jmath>")
("d j var var var" "<mathd><vartheta>")
("d j var var var var" "dj"))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Textual operators
Expand Down
60 changes: 60 additions & 0 deletions TeXmacs/tests/0616.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; MODULE : 0616.scm
;; DESCRIPTION : Integration tests for PR 0616 differential conversion (LaTeX)
;; 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 <http://www.gnu.org/licenses/gpl-3.0.html>.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(import (liii check))

(load "./TeXmacs/progs/convert/latex/init-latex.scm")

(check-set-mode! 'report-failed)

(define (stree-has-spaced-differential? t)
;; Check for pattern: "d" followed by " " followed by x/y/z/r/<rho>/<theta> etc.
(define (check-list lst)
(if (or (null? lst) (null? (cdr lst)) (null? (cddr lst)))
#f
(let ((a (car lst))
(b (cadr lst))
(c (caddr lst)))
(if (and (string? a) (string=? a "d")
(string? b) (string=? b " ")
(string? c)
(or (string=? c "x") (string=? c "y") (string=? c "z") (string=? c "r")
(string=? c "<rho>") (string=? c "<varrho>")
(string=? c "<theta>") (string=? c "<vartheta>")))
#t
(check-list (cdr lst))))))
(define (check-children lst)
(if (null? lst)
#f
(or (stree-has-spaced-differential? (car lst))
(check-children (cdr lst)))))
(cond ((not (pair? t)) #f)
((eq? (car t) 'concat)
(or (check-list (cdr t))
(check-children (cdr t))))
(else (check-children (cdr t)))))

(define (load-latex path)
(with path (string-append "$TEXMACS_PATH/tests/tex/" path)
(string-replace (string-load path) "\r\n" "\n")))

(define (test-latex-document-differentials)
(display "Testing space insertion for differentials in LaTeX document import...\n")
(let* ((latex-content (load-latex "0616_differential_test.tex"))
(texmacs-tree (latex-document->texmacs latex-content))
(st (tree->stree texmacs-tree)))
(display* "LaTeX Document converted tree TMU: " (serialize-tmu texmacs-tree) "\n")
(check (stree-has-spaced-differential? st) => #t)))

(tm-define (test_0616)
(test-latex-document-differentials)
(check-report))
14 changes: 14 additions & 0 deletions TeXmacs/tests/tex/0616_differential_test.tex
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
\documentclass{article}
\begin{document}

Here is a LaTeX math test for differentials:

\( dx + dy = dz \)

\[ dr = d\rho d\theta \]

Inline math: $x dx$ and $\sin x dx$.

Greek variants: $d\varrho + d\vartheta$.

\end{document}
53 changes: 53 additions & 0 deletions devel/0616.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# [0616] 任务描述

数学模式下直接敲击 `dx`, `dy`, `dz`, `dr` 等时自动转换为 `<mathd>x`, `<mathd>y`, `<mathd>z`, `<mathd>r`,并且支持 `d rho`, `d theta` 等常用微分格式。且转换后依然支持用户在中间手动输入空格或进行正常编辑。

## 1 相关文档
- [dddd.md](dddd.md) - 任务文档模板

## 2 任务相关的代码文件
- [TeXmacs/progs/math/math-kbd.scm](TeXmacs/progs/math/math-kbd.scm)

## 3 如何测试

### 3.1 确定性测试(单元测试)

### 3.2 非确定性测试(文档验证)
启动 Mogan Stem,进入数学模式:
1. 输入 `d` 再输入 `x`,验证是否自动转为 `<mathd>x`,且光标能移动到中间输入空格或进行编辑。
2. 输入 `d` 再输入 `y`,验证是否自动转为 `<mathd>y`。
3. 输入 `d` 再输入 `z`,验证是否自动转为 `<mathd>z`。
4. 输入 `d` 再输入 `r`,验证是否自动转为 `<mathd>r`。
5. 输入 `d` 再输入 `r` 和 `Tab` 键,验证是否自动转为 `<mathd><rho>`。
6. 输入 `d` 再输入 `j` 和 `Tab` 键,验证是否自动转为 `<mathd><theta>`。

## 4 如何提交

提交前执行以下最少步骤:
1. 检查 `TeXmacs/progs/math/math-kbd.scm` 的 Scheme 语法和括号匹配是否正确。

## 5 What
1. 实现了在数学模式下连续敲击 `dx`, `dy`, `dz`, `dr` 自动转换为 `"<mathd>x"`, `"<mathd>y"`, `"<mathd>z"`, `"<mathd>r"` 的键盘映射。
2. 实现了对希腊字母 `rho` (`r var` / `r var var`) 和 `theta` (`j var` / `j var var` / `j var var var`) 微分形式的自动转换。
3. 保持了字符的独立性,转换后的 `<mathd>` 与变量是两个单独的符号,用户可以直接在中间插入空格或进行自由编辑。
4. 实现了对外部 LaTeX 代码导入(包括粘贴)时的自动转换。对于形如 `dx`, `dy`, `dz`, `dr` 以及 `d\rho`, `d\theta` 且中间没有空格的序列,也会在导入时被自动转换为正确的微分形式。

## 6 Why
此前数学模式下敲击 `dx` 会被理解为多字母算子(类似 LaTeX 的 `\mathrm{dx}`),不便输入和修改。外部 LaTeX 导入时这些也会被解析为普通字符,导致排版不正确。本次改动为常用微分项提供了智能快捷纠正与完美的外部代码导入转换,提供了绝佳的用户体验。

## 7 How
1. 在 `TeXmacs/progs/math/math-kbd.scm` 的 `(:mode in-math-not-hybrid?)` 快捷键块中增加以下按键序列映射:
- `("d x" "<mathd>x")`
- `("d y" "<mathd>y")`
- `("d z" "<mathd>z")`
- `("d r" "<mathd>r")`
- `("d r var" "<mathd><rho>")`
- `("d r var var" "<mathd><varrho>")`
- `("d j var" "<mathd><theta>")`
- `("d j var var" "<mathd><jmath>")`
- `("d j var var var" "<mathd><vartheta>")`

2. 在 `TeXmacs/progs/convert/latex/init-latex.scm` 中封装 `latex->texmacs` 和 `latex-document->texmacs` 转换函数。在导入解析完成后,通过 Scheme 递归遍历生成的文档树:
- 将满足边界条件的单个字符串中的 `"dx"`, `"dy"`, `"dz"`, `"dr"` 子串拆分并转换为 `"<mathd>"` 与变量。
- 在 `concat` 节点内查找结尾为 `"d"` 且前一字符非字母的字符串,其下一节点如果为希腊字母 `"<rho>"`, `"<theta>"` 等,则自动转换为直立微分符 `"<mathd>"` 形式。
Loading