From e3fda13930b46820f2cbdf29b33d9ef3c99fea7f Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 9 Nov 2025 15:31:41 -0600 Subject: feat:buffer-diff: Add syntax-aware buffer diffing with difftastic Introduce enhanced buffer comparison with saved file using difftastic for syntax-aware diffing, with a fallback to regular unified diff if difftastic is unavailable. Output is displayed in separate buffers, leveraging ansi-color for improved readability. Also includes comprehensive integration tests covering the diff workflow, handling cases like added, removed, and modified lines, and ensuring graceful handling of special cases and errors. --- modules/custom-buffer-file.el | 67 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 5 deletions(-) (limited to 'modules/custom-buffer-file.el') diff --git a/modules/custom-buffer-file.el b/modules/custom-buffer-file.el index 105ed4ff..8825fdd6 100644 --- a/modules/custom-buffer-file.el +++ b/modules/custom-buffer-file.el @@ -251,14 +251,71 @@ Do not save the deleted text in the kill ring." (kill-new (buffer-name)) (message "Copied: %s" (buffer-name))) +(require 'system-lib) +(declare-function ansi-color-apply-on-region "ansi-color") + +(defun cj/--diff-with-difftastic (file1 file2 buffer) + "Run difftastic on FILE1 and FILE2, output to BUFFER. +Applies ANSI color and sets up special-mode for navigation." + (with-current-buffer buffer + (let ((inhibit-read-only t)) + (erase-buffer) + (insert (format "Difftastic diff: %s (saved) vs buffer (modified)\n\n" + (file-name-nondirectory file1))) + (call-process "difft" nil t nil + "--color" "always" + "--display" "side-by-side-show-both" + file1 file2) + (require 'ansi-color) + (ansi-color-apply-on-region (point-min) (point-max)) + (special-mode) + (goto-char (point-min))))) + +(defun cj/--diff-with-regular-diff (file1 file2 buffer) + "Run regular unified diff on FILE1 and FILE2, output to BUFFER. +Sets up diff-mode for navigation." + (with-current-buffer buffer + (let ((inhibit-read-only t)) + (erase-buffer) + (insert (format "Unified diff: %s (saved) vs buffer (modified)\n\n" + (file-name-nondirectory file1))) + (call-process "diff" nil t nil "-u" file1 file2) + (diff-mode) + (goto-char (point-min))))) + (defun cj/diff-buffer-with-file () - "Compare the current modified buffer with the saved version using ediff. -Uses the same ediff configuration from diff-config.el (horizontal split, j/k navigation). + "Compare the current modified buffer with the saved version. +Uses difftastic if available for syntax-aware diffing, falls back to regular diff. +Shows output in a separate buffer. Signal an error if the buffer is not visiting a file." (interactive) - (if (buffer-file-name) - (ediff-current-file) - (user-error "Current buffer is not visiting a file"))) + (unless (buffer-file-name) + (user-error "Current buffer is not visiting a file")) + (let* ((file (buffer-file-name)) + (file-ext (file-name-extension file t)) ; includes the dot + (temp-file (make-temp-file "buffer-diff-" nil file-ext)) + (buffer-content (buffer-string))) ; Capture BEFORE with-temp-file! + (unwind-protect + (progn + ;; Write current buffer content to temp file + (with-temp-file temp-file + (insert buffer-content)) + ;; Check if there are any differences first + (if (zerop (call-process "diff" nil nil nil "-q" file temp-file)) + (message "No differences between buffer and file") + ;; Run diff/difftastic and display in buffer + (let* ((using-difftastic (cj/executable-exists-p "difft")) + (buffer-name (if using-difftastic + "*Diff (difftastic)*" + "*Diff (unified)*")) + (diff-buffer (get-buffer-create buffer-name))) + (if using-difftastic + (cj/--diff-with-difftastic file temp-file diff-buffer) + (cj/--diff-with-regular-diff file temp-file diff-buffer)) + (display-buffer diff-buffer)))) + ;; Clean up temp file + (when (file-exists-p temp-file) + (delete-file temp-file))))) ;; --------------------------- Buffer And File Keymap -------------------------- -- cgit v1.2.3