Replace diff subprocesses with in-process (std text diff), use number->padded-string

ober

734ab0a748e551ca9ef0f5a4bb7e0aac62cb22a4

diff --git a/lib/jerboa-emacs/qt/commands-config.sls b/lib/jerboa-emacs/qt/commands-config.sls
index 18b351c..04269bb 100644
--- a/lib/jerboa-emacs/qt/commands-config.sls
+++ b/lib/jerboa-emacs/qt/commands-config.sls
@@ -29,7 +29,7 @@
      getenv path-extension path-absolute? thread? make-mutex
      mutex? mutex-name sort sort!)
    (std sugar) (chez-scintilla constants) (std sort)
-   (std srfi srfi-13) (std text base64)
+   (std srfi srfi-13) (std text base64) (std text diff)
    (jerboa-emacs qt sci-shim) (jerboa-emacs core)
    (jerboa-emacs async) (jerboa-emacs snippets)
    (only (jerboa-emacs persist) record-face-customization!
@@ -1221,27 +1221,33 @@
                      (echo-error!
                        (app-state-echo app)
                        "One or both files not found")
-                     (let* ([proc (open-process
-                                    (list 'path: "/usr/bin/diff" 'arguments:
-                                      (list "-u" path-a path-b)
-                                      'stdout-redirection: #t))]
-                            [output (read-line proc #f)]
+                     (let* ([text-a (read-file-as-string path-a)]
+                            [text-b (read-file-as-string path-b)]
+                            [lines-a (string-split text-a #\newline)]
+                            [lines-b (string-split text-b #\newline)]
+                            [output (diff-unified
+                                      path-a
+                                      path-b
+                                      lines-a
+                                      lines-b)]
                             [ed (current-qt-editor app)]
                             [fr (app-state-frame app)]
                             [diff-buf (qt-buffer-create! "*Ediff*" ed #f)])
-                       (close-port proc)
                        (qt-buffer-attach! ed diff-buf)
                        (qt-edit-window-buffer-set!
                          (qt-current-window fr)
                          diff-buf)
                        (qt-plain-text-edit-set-text!
                          ed
-                         (or output "No differences"))
+                         (if (and output (> (string-length output) 0))
+                             output
+                             "No differences"))
                        (qt-text-document-set-modified!
                          (buffer-doc-pointer diff-buf)
                          #f)
                        (qt-plain-text-edit-set-cursor-position! ed 0)
-                       (when output (qt-highlight-diff! ed))
+                       (when (and output (> (string-length output) 0))
+                         (qt-highlight-diff! ed))
                        (echo-message!
                          (app-state-echo app)
                          "Diff complete")))))))))
diff --git a/lib/jerboa-emacs/qt/commands-search.sls b/lib/jerboa-emacs/qt/commands-search.sls
index c78a859..2a3f913 100644
--- a/lib/jerboa-emacs/qt/commands-search.sls
+++ b/lib/jerboa-emacs/qt/commands-search.sls
@@ -45,7 +45,7 @@
      getenv path-extension path-absolute? thread? make-mutex
      mutex? mutex-name sort sort!)
    (std sugar) (chez-scintilla constants) (std sort)
-   (std srfi srfi-13) (std text base64)
+   (std srfi srfi-13) (std text base64) (std text diff)
    (jerboa-emacs qt sci-shim) (jerboa-emacs core)
    (jerboa-emacs async) (jerboa-emacs subprocess)
    (jerboa-emacs gsh-subprocess) (jerboa-emacs editor)
@@ -1228,26 +1228,13 @@
              (let* ([ed (current-qt-editor app)]
                     [current-text (qt-plain-text-edit-text ed)]
                     [file-text (read-file-as-string path)]
-                    [tmp-path (string-append
-                                "/tmp/jemacs-diff-"
-                                (number->string (random-integer 100000)))]
-                    [_ (write-string-to-file tmp-path current-text)]
-                    [result (with-catch
-                              (lambda (e) "Error running diff")
-                              (lambda ()
-                                (let ([port (open-process
-                                              (list 'path: "/usr/bin/diff"
-                                                'arguments:
-                                                (list "-u" path tmp-path)
-                                                'stdout-redirection: #t
-                                                'stderr-redirection: #t
-                                                'pseudo-terminal: #f))])
-                                  (let ([output (read-line port #f)])
-                                    (close-port port)
-                                    (or output "No differences")))))])
-               (with-catch
-                 (lambda (_e) (void))
-                 (lambda () (delete-file tmp-path)))
+                    [file-lines (string-split file-text #\newline)]
+                    [buf-lines (string-split current-text #\newline)]
+                    [result (diff-unified
+                              path
+                              "(buffer)"
+                              file-lines
+                              buf-lines)])
                (let* ([fr (app-state-frame app)]
                       [diff-buf (or (buffer-by-name "*Diff*")
                                     (qt-buffer-create! "*Diff*" ed #f))])
@@ -1257,7 +1244,9 @@
                    diff-buf)
                  (qt-plain-text-edit-set-text!
                    ed
-                   (if (string=? result "") "No differences\n" result))
+                   (if (or (not result) (string=? result ""))
+                       "No differences\n"
+                       result))
                  (qt-text-document-set-modified!
                    (buffer-doc-pointer diff-buf)
                    #f)
diff --git a/lib/jerboa-emacs/qt/commands-sexp.sls b/lib/jerboa-emacs/qt/commands-sexp.sls
index 959696f..d8176bc 100644
--- a/lib/jerboa-emacs/qt/commands-sexp.sls
+++ b/lib/jerboa-emacs/qt/commands-sexp.sls
@@ -37,7 +37,7 @@
      getenv path-extension path-absolute? thread? make-mutex
      mutex? mutex-name sort sort!)
    (std sugar) (chez-scintilla constants) (std sort)
-   (std srfi srfi-13) (std text base64)
+   (std srfi srfi-13) (std text base64) (std text diff)
    (jerboa-emacs qt sci-shim) (jerboa-emacs core)
    (jerboa-emacs editor) (jerboa-emacs repl)
    (jerboa-emacs eshell) (jerboa-emacs shell)
@@ -1046,67 +1046,50 @@
                                bufs)])
                  (if (or (not buf-a) (not buf-b))
                      (echo-error! (app-state-echo app) "Buffer not found")
-                     (let* ([tmp-a (path-expand
-                                     "ediff-a"
-                                     (or (getenv "TMPDIR") "/tmp"))]
-                            [tmp-b (path-expand
-                                     "ediff-b"
-                                     (or (getenv "TMPDIR") "/tmp"))])
-                       (let ([text-a ""] [text-b ""])
-                         (let ([fr (app-state-frame app)])
-                           (for-each
-                             (lambda (win)
-                               (when (eq? (qt-edit-window-buffer win)
-                                          buf-a)
-                                 (set! text-a
-                                   (qt-plain-text-edit-text
-                                     (qt-edit-window-editor win))))
-                               (when (eq? (qt-edit-window-buffer win)
-                                          buf-b)
-                                 (set! text-b
-                                   (qt-plain-text-edit-text
-                                     (qt-edit-window-editor win)))))
-                             (qt-frame-windows fr)))
-                         (call-with-output-file
-                           tmp-a
-                           (lambda (p) (display text-a p)))
-                         (call-with-output-file
-                           tmp-b
-                           (lambda (p) (display text-b p)))
-                         (let* ([proc (open-process
-                                        (list 'path: "/usr/bin/diff"
-                                          'arguments:
-                                          (list "-u" tmp-a tmp-b)
-                                          'stdout-redirection: #t))]
-                                [output (read-line proc #f)]
-                                [ed (current-qt-editor app)]
-                                [fr (app-state-frame app)]
-                                [diff-buf (qt-buffer-create!
-                                            "*Ediff*"
-                                            ed
-                                            #f)])
-                           (close-port proc)
-                           (qt-buffer-attach! ed diff-buf)
-                           (qt-edit-window-buffer-set!
-                             (qt-current-window fr)
-                             diff-buf)
-                           (qt-plain-text-edit-set-text!
-                             ed
-                             (or output "No differences"))
-                           (qt-text-document-set-modified!
-                             (buffer-doc-pointer diff-buf)
-                             #f)
-                           (qt-plain-text-edit-set-cursor-position! ed 0)
-                           (when output (qt-highlight-diff! ed))
-                           (with-catch
-                             (lambda (_e) (void))
-                             (lambda () (delete-file tmp-a)))
-                           (with-catch
-                             (lambda (_e) (void))
-                             (lambda () (delete-file tmp-b)))
-                           (echo-message!
-                             (app-state-echo app)
-                             "Diff complete")))))))))))
+                     (let ([text-a ""] [text-b ""])
+                       (let ([fr (app-state-frame app)])
+                         (for-each
+                           (lambda (win)
+                             (when (eq? (qt-edit-window-buffer win) buf-a)
+                               (set! text-a
+                                 (qt-plain-text-edit-text
+                                   (qt-edit-window-editor win))))
+                             (when (eq? (qt-edit-window-buffer win) buf-b)
+                               (set! text-b
+                                 (qt-plain-text-edit-text
+                                   (qt-edit-window-editor win)))))
+                           (qt-frame-windows fr)))
+                       (let* ([lines-a (string-split text-a #\newline)]
+                              [lines-b (string-split text-b #\newline)]
+                              [output (diff-unified
+                                        a-name
+                                        b-name
+                                        lines-a
+                                        lines-b)]
+                              [ed (current-qt-editor app)]
+                              [fr (app-state-frame app)]
+                              [diff-buf (qt-buffer-create!
+                                          "*Ediff*"
+                                          ed
+                                          #f)])
+                         (qt-buffer-attach! ed diff-buf)
+                         (qt-edit-window-buffer-set!
+                           (qt-current-window fr)
+                           diff-buf)
+                         (qt-plain-text-edit-set-text!
+                           ed
+                           (if (and output (> (string-length output) 0))
+                               output
+                               "No differences"))
+                         (qt-text-document-set-modified!
+                           (buffer-doc-pointer diff-buf)
+                           #f)
+                         (qt-plain-text-edit-set-cursor-position! ed 0)
+                         (when (and output (> (string-length output) 0))
+                           (qt-highlight-diff! ed))
+                         (echo-message!
+                           (app-state-echo app)
+                           "Diff complete"))))))))))
   (def (word-at-point ed)
        "Extract the word at the cursor position. Returns (values word start end) or (values #f 0 0)."
        (let* ([text (qt-plain-text-edit-text ed)]
diff --git a/lib/jerboa-emacs/qt/commands-shell2.sls b/lib/jerboa-emacs/qt/commands-shell2.sls
index f6915c2..a108d08 100644
--- a/lib/jerboa-emacs/qt/commands-shell2.sls
+++ b/lib/jerboa-emacs/qt/commands-shell2.sls
@@ -37,7 +37,7 @@
      getenv path-extension path-absolute? thread? make-mutex
      mutex? mutex-name sort sort!)
    (std sugar) (chez-scintilla constants) (std sort)
-   (std srfi srfi-13) (std text base64)
+   (std srfi srfi-13) (std text base64) (std text diff)
    (jerboa-emacs qt sci-shim) (jerboa-emacs core)
    (only (jerboa-emacs persist) theme-settings-save!
      theme-settings-load! mx-history-save! mx-history-load!)
@@ -379,55 +379,39 @@
                    (app-state-echo app)
                    (string-append "Buffer not found: " other-name))
                  (let* ([ed (current-qt-editor app)]
-                        [text1 (qt-plain-text-edit-text ed)]
-                        [tmp1 "/tmp/jemacs-ediff-1.txt"]
-                        [tmp2 "/tmp/jemacs-ediff-2.txt"])
-                   (call-with-output-file
-                     tmp1
-                     (lambda (p) (display text1 p)))
+                        [text1 (qt-plain-text-edit-text ed)])
                    (qt-buffer-attach! ed other-buf)
                    (let ([text2 (qt-plain-text-edit-text ed)])
-                     (call-with-output-file
-                       tmp2
-                       (lambda (p) (display text2 p)))
                      (qt-buffer-attach! ed cur-buf)
-                     (with-catch
-                       (lambda (e)
-                         (echo-error! (app-state-echo app) "diff failed"))
-                       (lambda ()
-                         (let* ([proc (open-process
-                                        (list 'path: "diff" 'arguments:
-                                          (list "-u"
-                                            (string-append
-                                              "--label="
-                                              cur-name)
-                                            (string-append
-                                              "--label="
-                                              other-name)
-                                            tmp1 tmp2)
-                                          'stdout-redirection: #t
-                                          'stderr-redirection: #t))]
-                                [output (read-line proc #f)]
-                                [_ (close-port proc)]
-                                [fr (app-state-frame app)]
-                                [diff-buf (or (buffer-by-name
-                                                "*Ediff Regions*")
-                                              (qt-buffer-create!
-                                                "*Ediff Regions*"
-                                                ed
-                                                #f))])
-                           (qt-buffer-attach! ed diff-buf)
-                           (qt-edit-window-buffer-set!
-                             (qt-current-window fr)
-                             diff-buf)
-                           (qt-plain-text-edit-set-text!
-                             ed
-                             (or output "Buffers are identical"))
-                           (qt-text-document-set-modified!
-                             (buffer-doc-pointer diff-buf)
-                             #f)
-                           (qt-plain-text-edit-set-cursor-position! ed 0)
-                           (qt-highlight-diff! ed)))))))))))
+                     (let* ([lines1 (string-split text1 #\newline)]
+                            [lines2 (string-split text2 #\newline)]
+                            [output (diff-unified
+                                      cur-name
+                                      other-name
+                                      lines1
+                                      lines2)]
+                            [fr (app-state-frame app)]
+                            [diff-buf (or (buffer-by-name
+                                            "*Ediff Regions*")
+                                          (qt-buffer-create!
+                                            "*Ediff Regions*"
+                                            ed
+                                            #f))])
+                       (qt-buffer-attach! ed diff-buf)
+                       (qt-edit-window-buffer-set!
+                         (qt-current-window fr)
+                         diff-buf)
+                       (qt-plain-text-edit-set-text!
+                         ed
+                         (if (and output (> (string-length output) 0))
+                             output
+                             "Buffers are identical"))
+                       (qt-text-document-set-modified!
+                         (buffer-doc-pointer diff-buf)
+                         #f)
+                       (qt-plain-text-edit-set-cursor-position! ed 0)
+                       (when (and output (> (string-length output) 0))
+                         (qt-highlight-diff! ed))))))))))
   (def (cmd-show-paren-mode app)
        "Toggle show-paren-mode (bracket matching highlight). Enabled by default.\nWhen disabled, cursor-adjacent braces are no longer highlighted."
        (set! *qt-show-paren-enabled* (not *qt-show-paren-enabled*))
diff --git a/src/jerboa-emacs/editor-advanced.ss b/src/jerboa-emacs/editor-advanced.ss
index cf31e60..c8ed898 100644
--- a/src/jerboa-emacs/editor-advanced.ss
+++ b/src/jerboa-emacs/editor-advanced.ss
@@ -10,6 +10,7 @@
         :std/srfi/13
         :std/text/base64
         :std/text/hex
+        :std/text/diff
         :std/crypto/digest
         :chez-scintilla/constants
         :chez-scintilla/scintilla
@@ -581,34 +582,19 @@
         (echo-error! echo (string-append "File not found: " path))
         (let* ((file-text (read-file-as-string path))
                (buf-text (editor-get-text ed))
-               ;; Write both to temp files and run diff
-               (pid (number->string (getpid)))
-               (tmp1 (string-append "/tmp/jemacs-diff-file-" pid))
-               (tmp2 (string-append "/tmp/jemacs-diff-buf-" pid)))
-          (write-string-to-file file-text tmp1)
-          (write-string-to-file buf-text tmp2)
-          (let* ((proc (open-process
-                         (list path: "/usr/bin/diff"
-                               arguments: (list "-u" tmp1 tmp2)
-                               stdin-redirection: #f
-                               stdout-redirection: #t
-                               stderr-redirection: #t)))
-                 (output (read-line proc #f))
-                 (status (process-status proc)))
-            ;; Clean up temp files
-            (with-catch (lambda (_e) (void)) (lambda () (delete-file tmp1)))
-            (with-catch (lambda (_e) (void)) (lambda () (delete-file tmp2)))
-            (if (and output (> (string-length output) 0))
-              ;; Show diff in *Diff* buffer
-              (let ((diff-buf (or (buffer-by-name "*Diff*")
-                                  (buffer-create! "*Diff*" ed #f))))
-                (buffer-attach! ed diff-buf)
-                (set! (edit-window-buffer (current-window fr)) diff-buf)
-                (editor-set-text ed output)
-                (editor-set-save-point ed)
-                (editor-goto-pos ed 0)
-                (echo-message! echo "*Diff*"))
-              (echo-message! echo "No differences"))))))))
+               (file-lines (string-split file-text #\newline))
+               (buf-lines (string-split buf-text #\newline))
+               (output (diff-unified path "(buffer)" file-lines buf-lines)))
+          (if (and output (> (string-length output) 0))
+            (let ((diff-buf (or (buffer-by-name "*Diff*")
+                                (buffer-create! "*Diff*" ed #f))))
+              (buffer-attach! ed diff-buf)
+              (set! (edit-window-buffer (current-window fr)) diff-buf)
+              (editor-set-text ed output)
+              (editor-set-save-point ed)
+              (editor-goto-pos ed 0)
+              (echo-message! echo "*Diff*"))
+            (echo-message! echo "No differences")))))))
 
 ;;;============================================================================
 ;;; Checksum: SHA256
@@ -950,55 +936,38 @@
                  (buf-b (and name-b (buffer-by-name name-b))))
             (if (not buf-b)
               (echo-error! echo (string-append "No buffer: " (or name-b "")))
-              ;; Get text from both buffers, write to temp files, diff
-              (let* ((pid (number->string (getpid)))
-                     (tmp-a (string-append "/tmp/gerbil-ediff-a-" pid))
-                     (tmp-b (string-append "/tmp/gerbil-ediff-b-" pid)))
-                ;; We need the text from those buffers — find their windows
-                (let ((text-a #f) (text-b #f))
-                  (for-each
-                    (lambda (win)
-                      (let ((wb (edit-window-buffer win)))
-                        (when (eq? wb buf-a)
-                          (set! text-a (editor-get-text (edit-window-editor win))))
-                        (when (eq? wb buf-b)
-                          (set! text-b (editor-get-text (edit-window-editor win))))))
-                    (frame-windows fr))
-                  ;; Fallback: if buffer not in a window, use current editor temporarily
-                  (unless text-a
-                    (buffer-attach! ed buf-a)
-                    (set! text-a (editor-get-text ed)))
-                  (unless text-b
-                    (buffer-attach! ed buf-b)
-                    (set! text-b (editor-get-text ed)))
-                  ;; Write to temp files and diff
-                  (write-string-to-file tmp-a text-a)
-                  (write-string-to-file tmp-b text-b)
-                  (let* ((proc (open-process
-                                 (list path: "/usr/bin/diff"
-                                       arguments: (list "-u"
-                                                        (string-append "--label=" name-a)
-                                                        (string-append "--label=" name-b)
-                                                        tmp-a tmp-b)
-                                       stdout-redirection: #t
-                                       stderr-redirection: #t)))
-                         (output (read-line proc #f))
-                         (status (process-status proc)))
-                    ;; Cleanup temp files
-                    (with-catch (lambda (_e) (void)) (lambda () (delete-file tmp-a)))
-                    (with-catch (lambda (_e) (void)) (lambda () (delete-file tmp-b)))
-                    ;; Show diff in buffer
-                    (let ((diff-buf (buffer-create! "*Diff*" ed #f)))
-                      (buffer-attach! ed diff-buf)
-                      (set! (edit-window-buffer (current-window fr)) diff-buf)
-                      (if (and (string? output) (> (string-length output) 0))
-                        (begin
-                          (editor-set-text ed output)
-                          (setup-diff-highlighting! ed))
-                        (editor-set-text ed "(no differences)\n"))
-                      (editor-set-save-point ed)
-                      (editor-goto-pos ed 0)
-                      (editor-set-read-only ed #t))))))))))))
+              ;; Get text from both buffers
+              (let ((text-a #f) (text-b #f))
+                (for-each
+                  (lambda (win)
+                    (let ((wb (edit-window-buffer win)))
+                      (when (eq? wb buf-a)
+                        (set! text-a (editor-get-text (edit-window-editor win))))
+                      (when (eq? wb buf-b)
+                        (set! text-b (editor-get-text (edit-window-editor win))))))
+                  (frame-windows fr))
+                ;; Fallback: if buffer not in a window, use current editor temporarily
+                (unless text-a
+                  (buffer-attach! ed buf-a)
+                  (set! text-a (editor-get-text ed)))
+                (unless text-b
+                  (buffer-attach! ed buf-b)
+                  (set! text-b (editor-get-text ed)))
+                ;; In-process diff
+                (let* ((lines-a (string-split text-a #\newline))
+                       (lines-b (string-split text-b #\newline))
+                       (output (diff-unified name-a name-b lines-a lines-b))
+                       (diff-buf (buffer-create! "*Diff*" ed #f)))
+                  (buffer-attach! ed diff-buf)
+                  (set! (edit-window-buffer (current-window fr)) diff-buf)
+                  (if (and (string? output) (> (string-length output) 0))
+                    (begin
+                      (editor-set-text ed output)
+                      (setup-diff-highlighting! ed))
+                    (editor-set-text ed "(no differences)\n"))
+                  (editor-set-save-point ed)
+                  (editor-goto-pos ed 0)
+                  (editor-set-read-only ed #t))))))))))
 
 ;;;============================================================================
 ;;; Simple calculator
diff --git a/src/jerboa-emacs/org-parse.ss b/src/jerboa-emacs/org-parse.ss
index 8249f9d..3295b81 100644
--- a/src/jerboa-emacs/org-parse.ss
+++ b/src/jerboa-emacs/org-parse.ss
@@ -13,6 +13,7 @@
                  date-hour date-minute date-second
                  date->time-utc time-difference time-second
                  date-week-day make-time time-utc)
+        (only-in :std/misc/number number->padded-string)
         ./pregexp-compat
         :std/misc/string)
 
@@ -113,9 +114,7 @@ Returns an org-timestamp struct or #f on parse failure."
 
 (def (pad-02 n)
   "Pad an integer to 2-digit zero-padded string."
-  (if (< n 10)
-    (string-append "0" (number->string n))
-    (number->string n)))
+  (number->padded-string n 2))
 
 (def (org-timestamp->date ts)
   "Convert an org-timestamp to a SRFI-19 date for arithmetic."
diff --git a/src/jerboa-emacs/qt/commands-config.ss b/src/jerboa-emacs/qt/commands-config.ss
index 5ea792b..d75a6c0 100644
--- a/src/jerboa-emacs/qt/commands-config.ss
+++ b/src/jerboa-emacs/qt/commands-config.ss
@@ -9,6 +9,7 @@
         :std/sort
         :std/srfi/13
         :std/text/base64
+        :std/text/diff
         :jerboa-emacs/qt/sci-shim
         :jerboa-emacs/core
         :jerboa-emacs/async
@@ -1043,22 +1044,22 @@ modified so the next save uses the new encoding."
                 (path-b (path-expand file-b)))
             (if (not (and (file-exists? path-a) (file-exists? path-b)))
               (echo-error! (app-state-echo app) "One or both files not found")
-              (let* ((proc (open-process
-                             (list path: "/usr/bin/diff"
-                                   arguments: (list "-u" path-a path-b)
-                                   stdout-redirection: #t)))
-                     (output (read-line proc #f))
-                     ;; Omit process-status (Qt SIGCHLD race)
+              (let* ((text-a (read-file-as-string path-a))
+                     (text-b (read-file-as-string path-b))
+                     (lines-a (string-split text-a #\newline))
+                     (lines-b (string-split text-b #\newline))
+                     (output (diff-unified path-a path-b lines-a lines-b))
                      (ed (current-qt-editor app))
                      (fr (app-state-frame app))
                      (diff-buf (qt-buffer-create! "*Ediff*" ed #f)))
-                (close-port proc)
                 (qt-buffer-attach! ed diff-buf)
                 (set! (qt-edit-window-buffer (qt-current-window fr)) diff-buf)
-                (qt-plain-text-edit-set-text! ed (or output "No differences"))
+                (qt-plain-text-edit-set-text! ed
+                  (if (and output (> (string-length output) 0)) output "No differences"))
                 (qt-text-document-set-modified! (buffer-doc-pointer diff-buf) #f)
                 (qt-plain-text-edit-set-cursor-position! ed 0)
-                (when output (qt-highlight-diff! ed))
+                (when (and output (> (string-length output) 0))
+                  (qt-highlight-diff! ed))
                 (echo-message! (app-state-echo app) "Diff complete")))))))))
 
 ;;;============================================================================
diff --git a/src/jerboa-emacs/qt/commands-search.ss b/src/jerboa-emacs/qt/commands-search.ss
index eb66b7c..7df30ac 100644
--- a/src/jerboa-emacs/qt/commands-search.ss
+++ b/src/jerboa-emacs/qt/commands-search.ss
@@ -9,6 +9,7 @@
         :std/sort
         :std/srfi/13
         :std/text/base64
+        :std/text/diff
         :jerboa-emacs/qt/sci-shim
         :jerboa-emacs/core
         :jerboa-emacs/async
@@ -1092,23 +1093,9 @@ Supports Gerbil (.ss), Python, JS/TS, Go, Shell, C/C++, Ruby."
       (let* ((ed (current-qt-editor app))
              (current-text (qt-plain-text-edit-text ed))
              (file-text (read-file-as-string path))
-             ;; Write current buffer to temp file for diff
-             (tmp-path (string-append "/tmp/jemacs-diff-" (number->string (random-integer 100000))))
-             (_ (write-string-to-file tmp-path current-text))
-             (result (with-catch
-                       (lambda (e) "Error running diff")
-                       (lambda ()
-                         (let ((port (open-process
-                                       (list path: "/usr/bin/diff"
-                                             arguments: ["-u" path tmp-path]
-                                             stdout-redirection: #t
-                                             stderr-redirection: #t
-                                             pseudo-terminal: #f))))
-                           (let ((output (read-line port #f)))
-                             (close-port port)
-                             (or output "No differences")))))))
-        ;; Clean up temp file
-        (with-catch (lambda (_e) (void)) (lambda () (delete-file tmp-path)))
+             (file-lines (string-split file-text #\newline))
+             (buf-lines (string-split current-text #\newline))
+             (result (diff-unified path "(buffer)" file-lines buf-lines)))
         ;; Show diff in buffer
         (let* ((fr (app-state-frame app))
                (diff-buf (or (buffer-by-name "*Diff*")
@@ -1116,7 +1103,7 @@ Supports Gerbil (.ss), Python, JS/TS, Go, Shell, C/C++, Ruby."
           (qt-buffer-attach! ed diff-buf)
           (set! (qt-edit-window-buffer (qt-current-window fr)) diff-buf)
           (qt-plain-text-edit-set-text! ed
-            (if (string=? result "") "No differences\n" result))
+            (if (or (not result) (string=? result "")) "No differences\n" result))
           (qt-text-document-set-modified! (buffer-doc-pointer diff-buf) #f)
           (qt-plain-text-edit-set-cursor-position! ed 0)
           (qt-highlight-diff! ed)))
diff --git a/src/jerboa-emacs/qt/commands-sexp.ss b/src/jerboa-emacs/qt/commands-sexp.ss
index 19dcaf0..a6a6e95 100644
--- a/src/jerboa-emacs/qt/commands-sexp.ss
+++ b/src/jerboa-emacs/qt/commands-sexp.ss
@@ -9,6 +9,7 @@
         :std/sort
         :std/srfi/13
         :std/text/base64
+        :std/text/diff
         :jerboa-emacs/qt/sci-shim
         :jerboa-emacs/core
         :jerboa-emacs/editor
@@ -908,43 +909,33 @@
                  (buf-b (find (lambda (b) (string=? (buffer-name b) b-name)) bufs)))
             (if (or (not buf-a) (not buf-b))
               (echo-error! (app-state-echo app) "Buffer not found")
-              ;; Write both to temp files and diff
-              (let* ((tmp-a (path-expand "ediff-a" (or (getenv "TMPDIR") "/tmp")))
-                     (tmp-b (path-expand "ediff-b" (or (getenv "TMPDIR") "/tmp"))))
-                ;; Get text from buffers
-                (let ((text-a "")
-                      (text-b ""))
-                  ;; Find editors showing these buffers
-                  (let ((fr (app-state-frame app)))
-                    (for-each
-                      (lambda (win)
-                        (when (eq? (qt-edit-window-buffer win) buf-a)
-                          (set! text-a (qt-plain-text-edit-text (qt-edit-window-editor win))))
-                        (when (eq? (qt-edit-window-buffer win) buf-b)
-                          (set! text-b (qt-plain-text-edit-text (qt-edit-window-editor win)))))
-                      (qt-frame-windows fr)))
-                  (call-with-output-file tmp-a (lambda (p) (display text-a p)))
-                  (call-with-output-file tmp-b (lambda (p) (display text-b p)))
-                  (let* ((proc (open-process
-                                 (list path: "/usr/bin/diff"
-                                       arguments: (list "-u" tmp-a tmp-b)
-                                       stdout-redirection: #t)))
-                         (output (read-line proc #f))
-                         ;; Omit process-status (Qt SIGCHLD race)
-                         (ed (current-qt-editor app))
-                         (fr (app-state-frame app))
-                         (diff-buf (qt-buffer-create! "*Ediff*" ed #f)))
-                    (close-port proc)
-                    (qt-buffer-attach! ed diff-buf)
-                    (set! (qt-edit-window-buffer (qt-current-window fr)) diff-buf)
-                    (qt-plain-text-edit-set-text! ed (or output "No differences"))
-                    (qt-text-document-set-modified! (buffer-doc-pointer diff-buf) #f)
-                    (qt-plain-text-edit-set-cursor-position! ed 0)
-                    (when output (qt-highlight-diff! ed))
-                    ;; Clean up temp files
-                    (with-catch (lambda (_e) (void)) (lambda () (delete-file tmp-a)))
-                    (with-catch (lambda (_e) (void)) (lambda () (delete-file tmp-b)))
-                    (echo-message! (app-state-echo app) "Diff complete")))))))))))
+              ;; Get text from buffers
+              (let ((text-a "")
+                    (text-b ""))
+                (let ((fr (app-state-frame app)))
+                  (for-each
+                    (lambda (win)
+                      (when (eq? (qt-edit-window-buffer win) buf-a)
+                        (set! text-a (qt-plain-text-edit-text (qt-edit-window-editor win))))
+                      (when (eq? (qt-edit-window-buffer win) buf-b)
+                        (set! text-b (qt-plain-text-edit-text (qt-edit-window-editor win)))))
+                    (qt-frame-windows fr)))
+                ;; In-process diff
+                (let* ((lines-a (string-split text-a #\newline))
+                       (lines-b (string-split text-b #\newline))
+                       (output (diff-unified a-name b-name lines-a lines-b))
+                       (ed (current-qt-editor app))
+                       (fr (app-state-frame app))
+                       (diff-buf (qt-buffer-create! "*Ediff*" ed #f)))
+                  (qt-buffer-attach! ed diff-buf)
+                  (set! (qt-edit-window-buffer (qt-current-window fr)) diff-buf)
+                  (qt-plain-text-edit-set-text! ed
+                    (if (and output (> (string-length output) 0)) output "No differences"))
+                  (qt-text-document-set-modified! (buffer-doc-pointer diff-buf) #f)
+                  (qt-plain-text-edit-set-cursor-position! ed 0)
+                  (when (and output (> (string-length output) 0))
+                    (qt-highlight-diff! ed))
+                  (echo-message! (app-state-echo app) "Diff complete"))))))))))
 
 ;;;============================================================================
 ;;; Highlight symbol / clear highlight
diff --git a/src/jerboa-emacs/qt/commands-shell2.ss b/src/jerboa-emacs/qt/commands-shell2.ss
index 793e2a8..84284ad 100644
--- a/src/jerboa-emacs/qt/commands-shell2.ss
+++ b/src/jerboa-emacs/qt/commands-shell2.ss
@@ -9,6 +9,7 @@
         :std/sort
         :std/srfi/13
         :std/text/base64
+        :std/text/diff
         :jerboa-emacs/qt/sci-shim
         :jerboa-emacs/core
         (only-in :jerboa-emacs/persist theme-settings-save! theme-settings-load!
@@ -255,39 +256,27 @@
         (if (not other-buf)
           (echo-error! (app-state-echo app) (string-append "Buffer not found: " other-name))
           (let* ((ed (current-qt-editor app))
-                 (text1 (qt-plain-text-edit-text ed))
-                 (tmp1 "/tmp/jemacs-ediff-1.txt")
-                 (tmp2 "/tmp/jemacs-ediff-2.txt"))
-            (call-with-output-file tmp1 (lambda (p) (display text1 p)))
+                 (text1 (qt-plain-text-edit-text ed)))
             ;; Get other buffer text by temporarily switching
             (qt-buffer-attach! ed other-buf)
             (let ((text2 (qt-plain-text-edit-text ed)))
-              (call-with-output-file tmp2 (lambda (p) (display text2 p)))
               ;; Switch back
               (qt-buffer-attach! ed cur-buf)
-              (with-catch
-                (lambda (e) (echo-error! (app-state-echo app) "diff failed"))
-                (lambda ()
-                  (let* ((proc (open-process
-                                 (list path: "diff"
-                                       arguments: (list "-u"
-                                                    (string-append "--label=" cur-name)
-                                                    (string-append "--label=" other-name)
-                                                    tmp1 tmp2)
-                                       stdout-redirection: #t
-                                       stderr-redirection: #t)))
-                         (output (read-line proc #f))
-                         (_ (close-port proc))
-                         (fr (app-state-frame app))
-                         (diff-buf (or (buffer-by-name "*Ediff Regions*")
-                                       (qt-buffer-create! "*Ediff Regions*" ed #f))))
-                    (qt-buffer-attach! ed diff-buf)
-                    (set! (qt-edit-window-buffer (qt-current-window fr)) diff-buf)
-                    (qt-plain-text-edit-set-text! ed
-                      (or output "Buffers are identical"))
-                    (qt-text-document-set-modified! (buffer-doc-pointer diff-buf) #f)
-                    (qt-plain-text-edit-set-cursor-position! ed 0)
-                    (qt-highlight-diff! ed)))))))))))
+              ;; In-process diff
+              (let* ((lines1 (string-split text1 #\newline))
+                     (lines2 (string-split text2 #\newline))
+                     (output (diff-unified cur-name other-name lines1 lines2))
+                     (fr (app-state-frame app))
+                     (diff-buf (or (buffer-by-name "*Ediff Regions*")
+                                   (qt-buffer-create! "*Ediff Regions*" ed #f))))
+                (qt-buffer-attach! ed diff-buf)
+                (set! (qt-edit-window-buffer (qt-current-window fr)) diff-buf)
+                (qt-plain-text-edit-set-text! ed
+                  (if (and output (> (string-length output) 0)) output "Buffers are identical"))
+                (qt-text-document-set-modified! (buffer-doc-pointer diff-buf) #f)
+                (qt-plain-text-edit-set-cursor-position! ed 0)
+                (when (and output (> (string-length output) 0))
+                  (qt-highlight-diff! ed))))))))))
 
 ;;;============================================================================
 ;;; Mode toggles (Emacs compatibility aliases)