]> code.delx.au - gnu-emacs-elpa/blobdiff - packages/vlf/vlf-ediff.el
* packages/vlf: Version 1.4. Add Ediff integration.
[gnu-emacs-elpa] / packages / vlf / vlf-ediff.el
diff --git a/packages/vlf/vlf-ediff.el b/packages/vlf/vlf-ediff.el
new file mode 100644 (file)
index 0000000..51c8aec
--- /dev/null
@@ -0,0 +1,325 @@
+;;; vlf-ediff.el --- VLF ediff functionality
+
+;; Copyright (C) 2014 Free Software Foundation, Inc.
+
+;; Keywords: large files, compare, ediff
+;; Author: Andrey Kotlarski <m00naticus@gmail.com>
+;; URL: https://github.com/m00natic/vlfi
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+
+;; This file is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs; see the file COPYING.  If not, write to
+;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+;; This package provides ediff functionality for VLF managed buffers
+;; in face of the `vlf-ediff-buffers' and `vlf-ediff-files' commands.
+
+;;; Code:
+
+(require 'vlf)
+(require 'ediff)
+
+(defvar vlf-ediff-session nil
+  "If non nil, specifies that ediff is done over VLF buffers.")
+(make-variable-buffer-local 'vlf-ediff-session)
+
+;;;###autoload
+(defun vlf-ediff-buffers (buffer-A buffer-B)
+  "Run batch by batch ediff over VLF buffers BUFFER-A and BUFFER-B.
+Batch size is determined by the size in BUFFER-A.
+Requesting next or previous difference at the end or beginning
+respectively of difference list, runs ediff over the adjacent chunks."
+  (interactive
+   (let (bf)
+     (list (setq bf (read-buffer "Buffer A to compare: "
+                                 (ediff-other-buffer "") t))
+           (read-buffer "Buffer B to compare: "
+                        (progn
+                          ;; realign buffers so that two visible bufs will be
+                          ;; at the top
+                          (save-window-excursion (other-window 1))
+                          (ediff-other-buffer bf))
+                        t))))
+  (set-buffer buffer-A)
+  (setq buffer-A (current-buffer)) ;names change, so reference by buffer object
+  (let ((batch-size vlf-batch-size))
+    (set-buffer buffer-B)
+    (setq buffer-B (current-buffer))
+    (vlf-set-batch-size batch-size))
+  (ediff-buffers buffer-A buffer-B
+                 '((lambda () (setq vlf-ediff-session t)
+                     (vlf-ediff-next ediff-buffer-A ediff-buffer-B
+                                     ediff-control-buffer
+                                     'vlf-next-chunk)))))
+
+;;;###autoload
+(defun vlf-ediff-files (file-A file-B batch-size)
+  "Run batch by batch ediff over FILE-A and FILE-B.
+Files are processed with VLF with BATCH-SIZE chunks.
+Requesting next or previous difference at the end or beginning
+respectively of difference list, runs ediff over the adjacent chunks."
+  (interactive
+   (let ((dir-A (if ediff-use-last-dir
+                    ediff-last-dir-A
+                  default-directory))
+         dir-B f)
+     (list (setq f (ediff-read-file-name
+                    "File A to compare"
+                    dir-A
+                    (ediff-get-default-file-name)
+                    'no-dirs))
+           (ediff-read-file-name "File B to compare"
+                                 (setq dir-B
+                                       (if ediff-use-last-dir
+                                           ediff-last-dir-B
+                                         (file-name-directory f)))
+                                 (progn
+                                   (ediff-add-to-history
+                                    'file-name-history
+                                    (ediff-abbreviate-file-name
+                                     (expand-file-name
+                                      (file-name-nondirectory f)
+                                      dir-B)))
+                                   (ediff-get-default-file-name f 1)))
+           (read-number "Batch size (in bytes): " vlf-batch-size))))
+  (let ((buffer-A (vlf file-A)))
+    (set-buffer buffer-A)
+    (vlf-set-batch-size batch-size)
+    (let ((buffer-B (vlf file-B)))
+      (vlf-ediff-buffers buffer-A buffer-B))))
+
+(defadvice ediff-next-difference (around vlf-ediff-next-difference
+                                         compile activate)
+  "Move to the next VLF chunk and search for difference if at the end\
+of difference list."
+  (if (and vlf-ediff-session
+           (<= (1- ediff-number-of-differences)
+               ediff-current-difference))
+      (let ((buffer-A ediff-buffer-A)
+            (buffer-B ediff-buffer-B)
+            (ediff-buffer (current-buffer)))
+        (save-excursion
+          (set-buffer buffer-A)
+          (vlf-next-chunk)
+          (set-buffer buffer-B)
+          (vlf-next-chunk)
+          (vlf-ediff-next buffer-A buffer-B ediff-buffer
+                          'vlf-next-chunk))
+        (or (zerop ediff-number-of-differences)
+            (ediff-jump-to-difference 1)))
+    ad-do-it))
+
+(defadvice ediff-previous-difference (around vlf-ediff-prev-difference
+                                             compile activate)
+  "Move to the previous VLF chunk and search for difference if at the\
+beginning of difference list."
+  (if (and vlf-ediff-session
+           (<= ediff-current-difference 0))
+      (let ((buffer-A ediff-buffer-A)
+            (buffer-B ediff-buffer-B)
+            (ediff-buffer (current-buffer)))
+        (save-excursion
+          (set-buffer buffer-A)
+          (vlf-prev-chunk)
+          (set-buffer buffer-B)
+          (vlf-prev-chunk)
+          (vlf-ediff-next buffer-A buffer-B ediff-buffer
+                          'vlf-prev-chunk))
+        (or (zerop ediff-number-of-differences)
+            (ediff-jump-to-difference -1)))
+    ad-do-it))
+
+(defun vlf-next-chunk ()
+  "Move to next chunk."
+  (vlf-move-to-chunk vlf-end-pos (+ vlf-end-pos vlf-batch-size) t))
+
+(defun vlf-prev-chunk ()
+  "Move to previous chunk."
+  (vlf-move-to-chunk (- vlf-start-pos vlf-batch-size) vlf-start-pos t))
+
+(defun vlf-ediff-next (buffer-A buffer-B ediff-buffer
+                                &optional next-func)
+  "Find next pair of chunks that differ in BUFFER-A and BUFFER-B\
+governed by EDIFF-BUFFER.  NEXT-FUNC is used to jump to the next
+logical chunks in case there is no difference at the current ones."
+  (set-buffer buffer-A)
+  (setq buffer-A (current-buffer)) ;names change, so reference by buffer object
+  (let ((end-A (= vlf-start-pos vlf-end-pos))
+        (chunk-A (cons vlf-start-pos vlf-end-pos))
+        (point-max-A (point-max))
+        (font-lock-A font-lock-mode)
+        (min-file-size vlf-file-size)
+        (forward-p (eq next-func 'vlf-next-chunk)))
+    (font-lock-mode 0)
+    (set-buffer buffer-B)
+    (setq buffer-B (current-buffer)
+          min-file-size (min min-file-size vlf-file-size))
+    (let ((tramp-verbose (min 2 tramp-verbose))
+          (end-B (= vlf-start-pos vlf-end-pos))
+          (chunk-B (cons vlf-start-pos vlf-end-pos))
+          (font-lock-B font-lock-mode)
+          (done nil)
+          (reporter (make-progress-reporter
+                     "Searching for difference..."
+                     (if forward-p vlf-start-pos
+                       (- min-file-size vlf-end-pos))
+                     min-file-size)))
+      (font-lock-mode 0)
+      (unwind-protect
+          (progn
+            (while (and (or (not end-A) (not end-B))
+                        (or (zerop (compare-buffer-substrings
+                                    buffer-A (point-min) point-max-A
+                                    buffer-B (point-min) (point-max)))
+                            (with-current-buffer ediff-buffer
+                              (ediff-update-diffs)
+                              (and (not end-A) (not end-B)
+                                   (vlf-ediff-refine buffer-A
+                                                     buffer-B))
+                              (zerop ediff-number-of-differences))))
+              (funcall next-func)
+              (setq end-B (= vlf-start-pos vlf-end-pos))
+              (with-current-buffer buffer-A
+                (funcall next-func)
+                (setq end-A (= vlf-start-pos vlf-end-pos)
+                      point-max-A (point-max)))
+              (progress-reporter-update reporter
+                                        (if forward-p vlf-end-pos
+                                          (- vlf-file-size
+                                             vlf-start-pos))))
+            (progress-reporter-done reporter)
+            (if (or (not end-A) (not end-B))
+                (progn (vlf-update-buffer-name)
+                       (set-buffer buffer-A)
+                       (vlf-update-buffer-name))
+              (if forward-p
+                  (let ((max-file-size vlf-file-size))
+                    (vlf-move-to-chunk (- max-file-size vlf-batch-size)
+                                       max-file-size)
+                    (set-buffer buffer-A)
+                    (setq max-file-size (max max-file-size
+                                             vlf-file-size))
+                    (vlf-move-to-chunk (- max-file-size
+                                          vlf-batch-size)
+                                       max-file-size))
+                (vlf-beginning-of-file)
+                (set-buffer buffer-A)
+                (vlf-beginning-of-file))
+              (set-buffer ediff-buffer)
+              (ediff-update-diffs)
+              (if (or (not forward-p)
+                      (and (not end-A) (not end-B)))
+                  (vlf-ediff-refine buffer-A buffer-B)))
+            (setq done t))
+        (when font-lock-A
+          (set-buffer buffer-A)
+          (font-lock-mode 1))
+        (when font-lock-B
+          (set-buffer buffer-B)
+          (font-lock-mode 1))
+        (unless done
+          (set-buffer buffer-A)
+          (set-buffer-modified-p nil)
+          (vlf-move-to-chunk (car chunk-A) (cdr chunk-A))
+          (set-buffer buffer-B)
+          (set-buffer-modified-p nil)
+          (vlf-move-to-chunk (car chunk-B) (cdr chunk-B))
+          (set-buffer ediff-buffer)
+          (ediff-update-diffs)
+          (vlf-ediff-refine buffer-A buffer-B))))))
+
+(defun vlf-ediff-refine (buffer-A buffer-B)
+  "Try to minimize differences between BUFFER-A and BUFFER-B.
+This can happen if first or last difference is at the start/end of
+buffer."
+  (or (zerop ediff-number-of-differences)
+      (let ((adjust-p (vlf-ediff-adjust buffer-A buffer-B)))
+        (setq adjust-p (or (vlf-ediff-adjust buffer-A buffer-B t)
+                           adjust-p))
+        (if adjust-p (ediff-update-diffs)))))
+
+(defun vlf-ediff-adjust (buf-A buf-B &optional end)
+  "Additionally adjust buffer borders for BUF-A and BUF-B.
+Adjust beginning if END is nil.  Return t if refining is needed,
+nil otherwise."
+  (let* ((diff-num (if end (1- ediff-number-of-differences) 0))
+         (diff-A (ediff-get-diff-overlay diff-num 'A))
+         (diff-B (ediff-get-diff-overlay diff-num 'B))
+         diff-A-str diff-B-str adjust-p)
+    (with-current-buffer buf-A
+      (setq adjust-p (if end (= (overlay-end diff-A) (point-max))
+                       (= (overlay-start diff-A) (point-min)))
+            diff-A-str (and adjust-p (buffer-substring-no-properties
+                                      (overlay-start diff-A)
+                                      (overlay-end diff-A))))
+      (set-buffer buf-B)
+      (setq adjust-p (and adjust-p
+                          (if end (= (overlay-end diff-B) (point-max))
+                            (= (overlay-start diff-B) (point-min))))
+            diff-B-str (and adjust-p (buffer-substring-no-properties
+                                      (overlay-start diff-B)
+                                      (overlay-end diff-B))))
+      (if adjust-p
+          (let ((len-A (length diff-A-str))
+                (len-B (length diff-B-str))
+                (adjust-func (if end 'vlf-ediff-adjust-end
+                               'vlf-ediff-adjust-start)))
+            (cond
+             ((< len-A len-B)
+              (or (funcall adjust-func diff-A-str diff-B-str buf-B)
+                  (setq adjust-p nil)))
+             ((< len-B len-A)
+              (or (funcall adjust-func diff-B-str diff-A-str buf-A)
+                  (setq adjust-p nil)))
+             (t (setq adjust-p nil))))))
+    adjust-p))
+
+(defun vlf-ediff-adjust-start (diff-short diff-long vlf-buffer)
+  "Remove difference between DIFF-SHORT and DIFF-LONG from beginning\
+of VLF-BUFFER."
+  (when (string-suffix-p diff-short diff-long)
+    (set-buffer vlf-buffer)
+    (vlf-move-to-chunk (+ vlf-start-pos
+                          (length (encode-coding-string
+                                   (substring diff-long 0
+                                              (- (length diff-long)
+                                                 (length diff-short)))
+                                   buffer-file-coding-system t)))
+                       vlf-end-pos)))
+
+(defun vlf-ediff-adjust-end (diff-short diff-long vlf-buffer)
+  "Remove difference between DIFF-SHORT and DIFF-LONG from the end of\
+VLF-BUFFER."
+  (when (string-prefix-p diff-short diff-long)
+    (set-buffer vlf-buffer)
+    (vlf-move-to-chunk vlf-start-pos
+                       (- vlf-end-pos
+                          (length (encode-coding-string
+                                   (substring diff-long
+                                              (length diff-short))
+                                   buffer-file-coding-system t))))))
+
+(unless (fboundp 'string-suffix-p)
+  (defun string-suffix-p (suffix string  &optional ignore-case)
+    "Return non-nil if SUFFIX is a suffix of STRING.
+If IGNORE-CASE is non-nil, the comparison is done without paying
+attention to case differences."
+    (let ((start-pos (- (length string) (length suffix))))
+      (and (>= start-pos 0)
+           (eq t (compare-strings suffix nil nil string start-pos nil
+                                  ignore-case))))))
+
+(provide 'vlf-ediff)
+
+;;; vlf-ediff.el ends here