Mercurial > hg > xemacs-beta
diff lisp/packages/diff.el @ 100:4be1180a9e89 r20-1b2
Import from CVS: tag r20-1b2
author | cvs |
---|---|
date | Mon, 13 Aug 2007 09:15:11 +0200 |
parents | 131b0175ea99 |
children | cf808b4c4290 |
line wrap: on
line diff
--- a/lisp/packages/diff.el Mon Aug 13 09:13:58 2007 +0200 +++ b/lisp/packages/diff.el Mon Aug 13 09:15:11 2007 +0200 @@ -1,305 +1,311 @@ -;;; diff.el --- Run `diff' in compilation-mode. - -;; Copyright (C) 1992, 1994 Free Software Foundation, Inc. - -;; Keywords: unix, tools - -;; This file is part of XEmacs. +;; -*-Emacs-Lisp-*- +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; File: diff.el +;; Version: $Revision: 1.2 $ +;; Author: This file is based on diff.el by +;; sunpitt!wpmstr!fbresz@Sun.COM 1/27/89. +;; It has been completely rewritten in July 1994 by +;; Sandy Rutherford <sandy@ibm550.sissa.it> +;; RCS: +;; Description: diff-mode for handling output from unix diff utility. +;; Modified: Wed Jul 17 10:26:57 1996 (Andy Norman) ange@hplb.hpl.hp.com +;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; XEmacs 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 2, or (at your option) -;; any later version. +;;; Copyright (C) 1990 Free Software Foundation, Inc. +;;; Copyright (C) 1994 Sandy Rutherford -;; XEmacs 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. +;;; This file is based on diff.el by sunpitt!wpmstr!fbresz@Sun.COM 1/27/89. +;;; It has been completely rewritten in July 1994 by +;;; Sandy Rutherford <sandy@ibm550.sissa.it> -;; You should have received a copy of the GNU General Public License -;; along with XEmacs; see the file COPYING. If not, write to the Free -;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA -;; 02111-1307, USA. - -;;; Synched up with: FSF 19.34. - -;;; Commentary: +;;; This program 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 1, or (at your option) +;;; any later version. +;;; +;;; This program 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. +;;; +;;; A copy of the GNU General Public License can be obtained from this +;;; program's author (send electronic mail to sandy@ibm550.sissa.it) or +;;; from the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, +;;; MA 02139, USA. -;; This package helps you explore differences between files, using the -;; UNIX command diff(1). The commands are `diff' and `diff-backup'. -;; You can specify options with `diff-switches'. +(provide 'diff) -;;; Code: - -(require 'compile) +;;; User Variables -;;; This is duplicated in vc.el. -;;;###autoload -(defvar diff-switches (purecopy "-c") - "*A string or list of strings specifying switches to be passed to diff.") +;; should be in to loaddefs.el now. +(defvar diff-switches nil + "*A list of switches (strings) to pass to the diff program.") -(defvar diff-command "diff" - "*The command to use to run diff.") +(defvar diff-do-narrow nil + "*If non-nil diff buffers are initialized narrowed to each difference.") -(defvar diff-regexp-alist - '( - ;; -u format: @@ -OLDSTART,OLDEND +NEWSTART,NEWEND @@ - ("^@@ -\\([0-9]+\\),[0-9]+ \\+\\([0-9]+\\),[0-9]+ @@$" 1 2) +(defvar diff-load-hooks nil + "Hooks to run after loading diff.el") + +;;; Internal variables - ;; -c format: *** OLDSTART,OLDEND **** - ("^\\*\\*\\* \\([0-9]+\\),[0-9]+ \\*\\*\\*\\*$" 1 nil) - ;; --- NEWSTART,NEWEND ---- - ("^--- \\([0-9]+\\),[0-9]+ ----$" nil 1) +(defconst diff-emacs-19-p + (let ((ver (string-to-int (substring emacs-version 0 2)))) + (>= ver 19))) - ;; plain diff format: OLDSTART[,OLDEND]{a,d,c}NEWSTART[,NEWEND] - ("^\\([0-9]+\\)\\(,[0-9]+\\)?[adc]\\([0-9]+\\)\\(,[0-9]+\\)?$" 1 3) - - ;; -e (ed) format: OLDSTART[,OLDEND]{a,d,c} - ("^\\([0-9]+\\)\\(,[0-9]+\\)?[adc]$" 1) +(or diff-emacs-19-p (require 'emacs-19)) - ;; -f format: {a,d,c}OLDSTART[ OLDEND] - ;; -n format: {a,d,c}OLDSTART LINES-CHANGED - ("^[adc]\\([0-9]+\\)\\( [0-9]+\\)?$" 1) - ) - "Alist (REGEXP OLD-IDX NEW-IDX) of regular expressions to match difference -sections in \\[diff] output. If REGEXP matches, the OLD-IDX'th -subexpression gives the line number in the old file, and NEW-IDX'th -subexpression gives the line number in the new file. If OLD-IDX or NEW-IDX -is nil, REGEXP matches only half a section.") +(defvar diff-old-file nil) +;; A list whose car is the name of the old file, and whose cdr indicates +;; whether we should delete the buffer on quit. +(defvar diff-new-file nil) +;; Same as diff-old-file, except for the new file. +(defvar diff-total-differences "0") +;; Total number of difference hunks as a string. +(defvar diff-current-difference "0") +;; Current difference hunk as a string. +(defvar diff-current-hunk 0) +;; Current difference hunk as an integer. +(defvar diff-total-hunks 0) +;; Total number of difference hunks as an integer. +(defvar diff-hunk-vector (vector 0)) +;; Vector storing the starting positions of the difference hunks. +(defvar diff-old-file-pattern nil) +(defvar diff-new-file-pattern nil) +(defvar diff-hunk-pattern nil) +;; Regexps to use when parsing file lines in difference hunks. -(defvar diff-old-file nil - "This is the old file name in the comparison in this buffer.") -(defvar diff-new-file nil - "This is the new file name in the comparison in this buffer.") -(defvar diff-old-temp-file nil - "This is the name of a temp file to be deleted after diff finishes.") -(defvar diff-new-temp-file nil - "This is the name of a temp file to be deleted after diff finishes.") + +(defvar diff-search-pattern-alist + (list + (list ?e "^[0-9]\\(,[0-9]+\\)?[acd]$" "^\\([0-9]+\\)" nil) + (list ?c "^\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\\*\n" + "^\\*+ \\([0-9]+\\)" "^-+ \\([0-9]+\\)") + (list ?u "^@@ " "^@@ -\\([0-9]+\\)" "^@@ -[0-9,]+ \\+\\([0-9]+\\)") + (list nil "^[0-9]+" "^\\([0-9]+\\)" "^[0-9,]+[acd]\\([0-9]+\\)"))) -;; See compilation-parse-errors-function (compile.el). -(defun diff-parse-differences (limit-search find-at-least) - (setq compilation-error-list nil) - (message "Parsing differences...") +(defvar diff-keymap-grokked nil) + +(defvar diff-temp-template "/tmp/diff") - ;; Don't reparse diffs already seen at last parse. - (if compilation-parsing-end (goto-char compilation-parsing-end)) +;; Initialize the keymap if it isn't already + +(defvar diff-mode-map nil + "Keymap used in diff mode.") - ;; Construct in REGEXP a regexp composed of all those in dired-regexp-alist. - (let ((regexp (mapconcat #'(lambda (elt) - (concat "\\(" (car elt) "\\)")) - diff-regexp-alist - "\\|")) - ;; (GROUP-IDX OLD-IDX NEW-IDX) - (groups (let ((subexpr 1)) - (mapcar #'(lambda (elt) - (prog1 - (cons subexpr - (mapcar #'(lambda (n) - (and n - (+ subexpr n))) - (cdr elt))) - (setq subexpr (+ subexpr 1 - ;;#### undefined?? - (count-regexp-groupings - (car elt)))))) - diff-regexp-alist))) +(if diff-mode-map + nil + (setq diff-mode-map (make-keymap)) + (suppress-keymap diff-mode-map) + (define-key diff-mode-map "?" 'describe-mode) + (define-key diff-mode-map "." 'diff-display-file) + (define-key diff-mode-map "N" 'diff-narrow) + (define-key diff-mode-map "W" 'widen) + (define-key diff-mode-map "f" 'diff-find-file) + (define-key diff-mode-map "h" 'diff-show-header) + (define-key diff-mode-map "j" 'diff-show-difference) + (define-key diff-mode-map "n" 'diff-next-difference) + (define-key diff-mode-map "o" 'diff-find-file-other-window) + (define-key diff-mode-map "p" 'diff-previous-difference) + (define-key diff-mode-map "q" 'diff-quit) + (define-key diff-mode-map "w" 'diff-find-file-other-frame) + (define-key diff-mode-map "\C-c\C-c" 'diff-find-file-other-window) + (define-key diff-mode-map " " 'diff-advertised-scroll-up) + (define-key diff-mode-map "\177" 'diff-advertised-scroll-down) + (define-key diff-mode-map "\C-n" 'diff-next-line) + (define-key diff-mode-map "\C-p" 'diff-previous-line) + (define-key diff-mode-map "\M->" 'diff-end-of-buffer) + (define-key diff-mode-map "\M-<" 'diff-beginning-of-buffer)) + +;;; Internal functions - (new-error - (function (lambda (file subexpr) - (setq compilation-error-list - (cons - (cons (save-excursion - ;; Report location of message - ;; at beginning of line. - (goto-char - (match-beginning subexpr)) - (beginning-of-line) - (point-marker)) - ;; Report location of corresponding text. - (let ((line (string-to-int - (buffer-substring - (match-beginning subexpr) - (match-end subexpr))))) - (save-excursion - (save-match-data - (set-buffer (find-file-noselect file))) - (save-excursion - (goto-line line) - (point-marker))))) - compilation-error-list))))) +(defun diff-grok-keys (to-command from-command) + ;; Assigns to TO-COMMAND the keys for the global binding of FROM-COMMAND. + ;; Does not clobber anything in the local keymap. + (let ((keys (where-is-internal from-command))) + (while keys + (condition-case nil + (if (eq (global-key-binding (car keys)) (key-binding (car keys))) + (local-set-key (car keys) to-command)) + (error nil)) + (setq keys (cdr keys))))) + +(defun diff-grok-keymap () + (if diff-keymap-grokked + () + (if (and term-setup-hook (boundp 'command-line-args-left)) + (progn + (if diff-emacs-19-p + (run-hooks term-setup-hook) + (funcall term-setup-hook)) + (setq term-setup-hook nil))) + (setq diff-keymap-grokked t) + (diff-grok-keys 'diff-next-line 'next-line) + (diff-grok-keys 'diff-previous-line 'previous-line) + (diff-grok-keys 'diff-forward-char 'forward-char) + (diff-grok-keys 'diff-backward-char 'backward-char) + (diff-grok-keys 'diff-scroll-up 'scroll-up) + (diff-grok-keys 'diff-scroll-down 'scroll-down) + (diff-grok-keys 'diff-beginning-of-buffer 'beginning-of-buffer) + (diff-grok-keys 'diff-end-of-buffer 'end-of-buffer))) - (found-desired nil) - (num-loci-found 0) - g) - - (while (and (not found-desired) - ;; We don't just pass LIMIT-SEARCH to re-search-forward - ;; because we want to find matches containing LIMIT-SEARCH - ;; but which extend past it. - (re-search-forward regexp nil t)) +(defun diff-buffer-narrowed-p () + ;; Returns t if the current buffer is narrowed + (save-restriction + (let ((min (point-min)) + (max (point-max))) + (widen) + (not (and (= min (point-min)) (= max (point-max))))))) - ;; Find which individual regexp matched. - (setq g groups) - (while (and g (null (match-beginning (car (car g))))) - (setq g (cdr g))) - (setq g (car g)) +(defun diff-current-hunk () + ;; Returns the current diff hunk. + (let ((pt (point)) + (start 0) + (end (1+ diff-total-hunks)) + m) + (while (> (- end start) 1) + (setq m (/ (+ start end) 2)) + (if (>= pt (aref diff-hunk-vector m)) + (setq start m) + (setq end m))) + (if (>= pt (aref diff-hunk-vector end)) + (setq m end) + (setq m start)) + ;; Don't treat end of buffer as start of next hunk + (if (eobp) (1- m) m))) - (if (nth 1 g) ;OLD-IDX - (funcall new-error diff-old-file (nth 1 g))) - (if (nth 2 g) ;NEW-IDX - (funcall new-error diff-new-file (nth 2 g))) +(defun diff-hunk-min (n) + ;; Returns the start of the current diff hunk. + (aref diff-hunk-vector n)) + +(defun diff-hunk-max (n) + ;; Returns the end of the current diff hunk. + (aref diff-hunk-vector (1+ n))) - (setq num-loci-found (1+ num-loci-found)) - (if (or (and find-at-least - (>= num-loci-found find-at-least)) - (and limit-search (>= (point) limit-search))) - ;; We have found as many new loci as the user wants, - ;; or the user wanted a specific diff, and we're past it. - (setq found-desired t))) - (if found-desired - (setq compilation-parsing-end (point)) - ;; Set to point-max, not point, so we don't perpetually - ;; parse the last bit of text when it isn't a diff header. - (setq compilation-parsing-end (point-max))) - (message "Parsing differences...done")) - (setq compilation-error-list (nreverse compilation-error-list))) +(defun diff-parse-hunks () + ;; Parses a buffer of diff output. + (save-excursion + (save-restriction + (message "Parsing differences...") + (widen) + (goto-char (point-min)) + (let ((hunks (list 1))) + (while (re-search-forward diff-hunk-pattern nil t) + (setq hunks (cons (match-beginning 0) hunks))) + (setq diff-total-hunks (1- (length hunks)) + diff-hunk-vector (apply 'vector + (nreverse (cons (point-max) hunks))))))) + (message "Parsing differences...done")) -;;;###autoload -(defun diff (old new &optional switches) - "Find and display the differences between OLD and NEW files. -Interactively the current buffer's file name is the default for NEW -and a backup file for NEW is the default for OLD. -With prefix arg, prompt for diff switches." - (interactive - (nconc - (let (oldf newf) - (nreverse - (list - (setq newf (buffer-file-name) +(defun diff-update-modeline () + ;; Updates the mode line to show current diff hunk. + (if (or (< (point) (diff-hunk-min diff-current-hunk)) + (>= (point) (diff-hunk-max diff-current-hunk))) + (progn + (setq diff-current-hunk (diff-current-hunk) + diff-current-difference (int-to-string diff-current-hunk)) + (set-buffer-modified-p (buffer-modified-p))))) + +(defun diff-read-args (oldprompt newprompt switchprompt + &optional file-for-backup) + ;; Grab the args for diff. OLDPROMPT and NEWPROMPT are the prompts + ;; for the old & new filenames, SWITCHPROMPT for the list of + ;; switches. If FILE_FOR_BACKUP is provided (it must be a string if + ;; so), then it will be used to try & work out a file & backup to + ;; diff, & in this case the prompting order is backwards. %s in a + ;; prompt has a guess substituted into it. This is nasty. + (let (oldf newf) + (if file-for-backup + (setq newf file-for-backup newf (if (and newf (file-exists-p newf)) (read-file-name - (concat "Diff new file: (" - (file-name-nondirectory newf) ") ") + (format newprompt (file-name-nondirectory newf)) nil newf t) - (read-file-name "Diff new file: " nil nil t))) - (setq oldf (file-newest-backup newf) + (read-file-name (format newprompt "") nil nil t)) + oldf (file-newest-backup newf) oldf (if (and oldf (file-exists-p oldf)) (read-file-name - (concat "Diff original file: (" - (file-name-nondirectory oldf) ") ") - (file-name-directory oldf) oldf t) - (read-file-name "Diff original file: " - (file-name-directory newf) nil t)))))) - (if current-prefix-arg - (list (read-string "Diff switches: " - (if (stringp diff-switches) - diff-switches - (mapconcat 'identity diff-switches " ")))) - nil))) - (setq new (expand-file-name new) - old (expand-file-name old)) - ;; XEmacs addition -- allow (diff "../old/" "new-file.el") to work - (cond ((file-directory-p old) - (or (file-directory-p new) - (setq old (expand-file-name (file-name-nondirectory new) - (file-name-as-directory old))))) - ((file-directory-p new) - (setq new (expand-file-name (file-name-nondirectory old) - (file-name-as-directory new))))) - (let ((old-alt (file-local-copy old)) - (new-alt (file-local-copy new)) - buf) - (unwind-protect - (let ((command - (mapconcat 'identity - (append (list diff-command) - ;; Use explicitly specified switches - (if switches - (if (consp switches) - switches (list switches)) - ;; If not specified, use default. - (if (consp diff-switches) - diff-switches - (list diff-switches))) - (if (or old-alt new-alt) - (list "-L" old "-L" new)) - (list - (shell-quote-argument (or old-alt old))) - (list - (shell-quote-argument (or new-alt new)))) - " "))) - (setq buf - (compile-internal command - "No more differences" "Diff" - 'diff-parse-differences)) - (pop-to-buffer buf) - ;; Avoid frightening people with "abnormally terminated" - ;; if diff finds differences. - (set (make-local-variable 'compilation-exit-message-function) - (lambda (status code msg) - (cond ((not (eq status 'exit)) - (cons msg code)) - ((zerop code) - '("finished (no differences)\n" . "no differences")) - ((= code 1) - '("finished\n" . "differences found")) - (t - (cons msg code))))) - (set (make-local-variable 'diff-old-file) old) - (set (make-local-variable 'diff-new-file) new) - (set (make-local-variable 'diff-old-temp-file) old-alt) - (set (make-local-variable 'diff-new-temp-file) new-alt) - (set (make-local-variable 'compilation-finish-function) - (function (lambda (buff msg) - (if diff-old-temp-file - (delete-file diff-old-temp-file)) - (if diff-new-temp-file - (delete-file diff-new-temp-file))))) - buf)))) + (format oldprompt (file-name-nondirectory oldf)) + nil oldf t) + (read-file-name (format oldprompt "") + (file-name-directory newf) nil t))) + ;; Else we aren't trying to be bright... + (setq oldf (read-file-name (format oldprompt "") nil nil t) + newf (read-file-name + (format newprompt (file-name-nondirectory oldf)) + nil (file-name-directory oldf) t))) + (list oldf newf (diff-read-switches switchprompt)))) + +(defun diff-read-switches (switchprompt) + ;; Read and return a list of switches + (if current-prefix-arg + (let ((default (if (listp diff-switches) + (mapconcat 'identity diff-switches " ") + diff-switches))) + (diff-fix-switches + (read-string (format switchprompt default) default))))) + +(defun diff-fix-switches (switch-spec) + ;; Parse a string into a list of switches or leave it be if it's + ;; not a string + (if (stringp switch-spec) + (let (result (start 0)) + (while (string-match "\\(\\S-+\\)" switch-spec start) + (setq result (cons (substring switch-spec (match-beginning 1) + (match-end 1)) + result) + start (match-end 0))) + (nreverse result)) + switch-spec)) -;;;###autoload -(defun diff-backup (file &optional switches) - "Diff this file with its backup file or vice versa. -Uses the latest backup, if there are several numerical backups. -If this file is a backup, diff it with its original. -The backup file is the first file given to `diff'." - (interactive (list (read-file-name "Diff (file with backup): ") - (if current-prefix-arg - (read-string "Diff switches: " - (if (stringp diff-switches) - diff-switches - (mapconcat 'identity - diff-switches " "))) - nil))) - (let (bak ori) - (if (backup-file-name-p file) - (setq bak file - ori (file-name-sans-versions file)) - (setq bak (or (diff-latest-backup-file file) - (error "No backup found for %s" file)) - ori file)) - (diff bak ori switches))) +(defun diff-get-file-buffer (file) + ;; Returns \(BUFFER . DEL-P\), where DEL-P is t if diff is expected + ;; to delete the buffer, and nil otherwise. + (let* ((buff (get-file-buffer file)) + (del-p (null buff))) + (if (and buff (buffer-modified-p buff)) + (progn + (message + "Buffer %s is modified. Diffing against buffer contents." + (buffer-name buff)) + (sit-for 1))) + ;; Call find-file-noselect even if we already have the buffer, + ;; as it will run verify-buffer-file-modtime. + (cons (find-file-noselect file) del-p))) -(defun diff-latest-backup-file (fn) ; actually belongs into files.el +(defun diff-cleanup-buffers () + ;; Cleans up diff buffers by deleting buffers that we don't expect + ;; the user to care about. + (let ((files (list diff-old-file diff-new-file))) + (while files + (let ((ent (car files)) + buff) + (and (cdr ent) + (setq buff (get-file-buffer (car ent))) + (not (buffer-modified-p buff)) + (kill-buffer buff))) + (setq files (cdr files))) + (if (get-buffer "*Diff Header*") + (kill-buffer "*Diff Header*")))) + +(defun diff-latest-backup-file (file) "Return the latest existing backup of FILE, or nil." - (let ((handler (find-file-name-handler fn 'diff-latest-backup-file))) + ;; First try simple backup, then the highest numbered of the + ;; numbered backups. + ;; Ignore the value of version-control because we look for existing + ;; backups, which maybe were made earlier or by another user with + ;; a different value of version-control. + (let* ((file (expand-file-name file)) + (handler (find-file-name-handler file 'diff-latest-backup-file))) (if handler - (funcall handler 'diff-latest-backup-file fn) - ;; First try simple backup, then the highest numbered of the - ;; numbered backups. - ;; Ignore the value of version-control because we look for existing - ;; backups, which maybe were made earlier or by another user with - ;; a different value of version-control. - (setq fn (file-chase-links (expand-file-name fn))) + (funcall handler 'diff-latest-backup-file file) (or - (let ((bak (make-backup-file-name fn))) + (let ((bak (make-backup-file-name file))) (if (file-exists-p bak) bak)) - ;; We use BACKUPNAME to cope with backups stored in a different dir. - (let* ((backupname (car (find-backup-file-name fn))) - (dir (file-name-directory backupname)) - (base-versions (concat (file-name-sans-versions - (file-name-nondirectory backupname)) - ".~")) + (let* ((dir (file-name-directory file)) + (base-versions (concat (file-name-nondirectory file) ".~")) (bv-length (length base-versions))) (concat dir (car (sort @@ -310,6 +316,375 @@ (> (backup-extract-version fn1) (backup-extract-version fn2)))))))))))) -(provide 'diff) +(defun diff-file-line (&optional old-file-p) + "Return line number of current hunk in `diff-new-file'. +With optional argument OLD-FILE-P, use `diff-old-file' instead." + (save-excursion + (let ((min (diff-hunk-min diff-current-hunk)) + (max (diff-hunk-max diff-current-hunk)) + (regexp (if old-file-p diff-old-file-pattern diff-new-file-pattern))) + (goto-char min) + (or (and regexp (re-search-forward regexp max t)) + (error "Unable to locate a file line for %s file." + (if old-file-p "old" "new"))) + (string-to-int (buffer-substring (match-beginning 1) (match-end 1)))))) + +(defun diff-run-diff (switches old old-temp new new-temp) + ;; Actually run the diff process with SWITCHES on OLD and NEW. + ;; OLD-TEMP and NEW-TEMP are names of temp files that can be used + ;; to dump the data out to. + (insert "diff " (mapconcat 'identity switches " ") " " old + " " new "\n") + (apply 'call-process "diff" nil t nil + (append switches (list old-temp new-temp)))) + + +(defun diff-fix-file-names (old old-temp new new-temp pattern) + ;; Replaces any temp file names with the real names of files. + (save-excursion + (save-restriction + (let ((files (list old new)) + (temps (list old-temp new-temp)) + buffer-read-only case-fold-search) + (goto-char (point-min)) + (if (re-search-forward pattern nil t) + (narrow-to-region (point-min) (match-beginning 0))) + (while files + (let ((regexp (concat "[ \t\n]\\(" + (regexp-quote (car temps)) + "\\)[ \t\n]"))) + (goto-char (point-min)) + (forward-line 1) + (while (re-search-forward regexp nil t) + (goto-char (match-beginning 1)) + (delete-region (point) (match-end 1)) + (insert (car files)))) + (setq files (cdr files) + temps (cdr temps))))))) + +;;;; User commands + +(defun diff-mode () + "Diff Mode is used by \\[diff] for perusing the output from the diff program. +All normal editing commands are turned off. Instead, these are available: +\\<diff-mode-map> +\\[diff-advertised-scroll-up] Scroll to next screen of this difference. +\\[diff-advertised-scroll-down] Scroll to previous screen of this difference. +\\[diff-next-difference] Move to Next Difference. +\\[diff-previous-difference] Move to Previous Difference. +\\[diff-show-difference] Jump to difference specified by numeric position. +\\[diff-find-file] Find current diff in file +\\[diff-find-file-other-window] Find current diff in file in other window +\\[diff-display-file] Display file in other window +\\[diff-narrow] Narrow diff buffer to current difference +\\[widen] Widen diff buffer +\\[diff-show-header] Show diff header describing file name etc. +\\[diff-quit] Quit diff +" + (interactive) + (use-local-map diff-mode-map) + (diff-grok-keymap) + (setq buffer-read-only t + major-mode 'diff-mode + mode-name "Diff" + mode-line-modified "--- " + mode-line-process + '(" " diff-current-difference "/" diff-total-differences)) + (diff-parse-hunks) + (setq diff-total-differences (int-to-string diff-total-hunks))) + +;;; Motion commands + +(defun diff-next-difference (n) + "In diff-mode go the the beginning of the next difference hunk." + (interactive "p") + (if (zerop n) + (goto-char (diff-hunk-min diff-current-hunk)) + (let ((narrow (diff-buffer-narrowed-p)) + (max (point-max)) + (min (point-min))) + (unwind-protect + (progn + (widen) + (setq diff-current-hunk (+ n diff-current-hunk)) + (cond ((> diff-current-hunk diff-total-hunks) + (setq diff-current-hunk diff-total-hunks) + (message "No following difference hunks.")) + ((< diff-current-hunk 0) + (setq diff-current-hunk 0) + (message "No preceding difference hunks."))) + (setq diff-current-difference (int-to-string diff-current-hunk) + min (goto-char (diff-hunk-min diff-current-hunk)) + max (diff-hunk-max diff-current-hunk))) + (if narrow (narrow-to-region min max)))) + (set-buffer-modified-p (buffer-modified-p)))) + +(defun diff-previous-difference (n) + "In diff-mode go the the beginning of the previous difference hunk." + (interactive "p") + (diff-next-difference (- n))) + +(defun diff-next-line (n) + "In diff-mode go to the next line." + (interactive "p") + (condition-case nil + (next-line n) + (error (if (> n 0) (message "End of difference hunk")))) + (diff-update-modeline)) + +(defun diff-previous-line (n) + "In diff-mode go to the previous line." + (interactive "p") + (diff-next-line (- n))) + +(defun diff-forward-char (n) + "In diff-mode move the point forward." + (interactive "p") + (forward-char n) + (diff-update-modeline)) + +(defun diff-backward-char (n) + "In diff-mode move the point backward." + (interactive "p") + (backward-char n) + (diff-update-modeline)) + +(defun diff-scroll-up (n) + "In diff-mode scroll the buffer up." + (interactive "P") + (scroll-up n) + (diff-update-modeline)) + +(fset 'diff-advertised-scroll-up 'diff-scroll-up) + +(defun diff-scroll-down (n) + "In diff-mode scroll the buffer down." + (interactive "P") + (scroll-down n) + (diff-update-modeline)) + +(fset 'diff-advertised-scroll-down 'diff-scroll-down) + +(defun diff-beginning-of-buffer (n) + "In diff-mode go to the beginning of the buffer." + (interactive "P") + (beginning-of-buffer n) + (diff-update-modeline)) + +(defun diff-end-of-buffer (n) + "In diff-mode go to the end of the buffer." + (interactive "P") + (end-of-buffer n) + (diff-update-modeline)) + +;;; The main command. -;;; diff.el ends here +(defun diff (old new &optional switches) + "Find and display the differences between OLD and NEW files. +Interactively you are prompted with the current buffer's file name for NEW +and what appears to be its backup for OLD." + ;; Support for diffing directories is rather limited. It needs work. + (interactive (diff-read-args "Diff original file (%s) " + "Diff new file (%s) " + "Switches for diff (%s) " + (buffer-file-name))) + (setq switches (diff-fix-switches (or switches diff-switches)) + old (expand-file-name old) + new (expand-file-name new)) + (let ((curr-buff (current-buffer)) + doing-dirs old-temp new-temp old-buffer new-buffer flag) + (let ((fdp-old (file-directory-p old)) + (fdp-new (file-directory-p new))) + (cond + ((null (or fdp-new fdp-old))) + ((null fdp-new) + (setq old (expand-file-name (file-name-nondirectory new) old))) + ((null fdp-old) + (setq new (expand-file-name (file-name-nondirectory old) new))) + (t (setq doing-dirs t)))) +;; (message "diff %s %s %s..." +;; (mapconcat (function identity) switches " ") new old) + (message "diff %s %s %s..." + (mapconcat (function identity) switches " ") old new) + (if doing-dirs + (setq diff-old-file nil + diff-new-file nil) + (setq old-temp (make-temp-name (concat diff-temp-template "1")) + new-temp (make-temp-name (concat diff-temp-template "2")) + old-buffer (diff-get-file-buffer old) + new-buffer (diff-get-file-buffer new) + diff-old-file (cons old (cdr old-buffer)) + diff-new-file (cons new (cdr new-buffer)))) + (let (case-fold-search) + (mapcar (function + (lambda (x) + (if (string-match "[ecu]" x) + (setq flag (aref x (match-beginning 0)))))) + switches)) + (unwind-protect + (let ((patterns (assq flag diff-search-pattern-alist))) + (set-buffer (get-buffer-create "*Diff Output*")) + (setq default-directory (file-name-directory new) + diff-old-file-pattern (nth 2 patterns) + diff-new-file-pattern (nth 3 patterns) + diff-hunk-pattern (nth 1 patterns)) + (let (buffer-read-only) + (if (fboundp 'buffer-disable-undo) + (buffer-disable-undo (current-buffer)) + ;; old style (Emacs 18.55 and earlier) + (buffer-disable-undo (current-buffer))) + (widen) + (erase-buffer) + (if doing-dirs + (progn + (diff-run-diff switches old old new new) + (setq diff-hunk-pattern (concat diff-hunk-pattern + "\\|^Only in "))) + (save-excursion + (set-buffer (car old-buffer)) + (write-region (point-min) (point-max) old-temp nil 'quiet) + (set-buffer (car new-buffer)) + (write-region (point-min) (point-max) new-temp nil 'quiet)) + (diff-run-diff switches old old-temp new new-temp)) + ;; Need to replace file names + (if (and (not doing-dirs) (memq flag '(?c ?u))) + (diff-fix-file-names old old-temp new new-temp + diff-hunk-pattern)) + (diff-mode) + (goto-char (point-min)) + (setq diff-current-difference "0" + diff-current-hunk 0) + (if (zerop diff-total-hunks) + (progn + (diff-cleanup-buffers) + (message "No differences")) + (if diff-do-narrow (narrow-to-region (point) (diff-hunk-max 0))) + (display-buffer (current-buffer)) + (message "%s difference hunk%s" diff-total-differences + (if (= diff-total-hunks 1) "" "s"))))) + (condition-case nil + (delete-file old-temp) + (error nil)) + (condition-case nil + (delete-file new-temp) + (error nil)) + (set-buffer curr-buff)))) + +(defun diff-backup (file &optional switches) + "Diff this file with its backup file or vice versa. +Uses the latest backup, if there are several numerical backups. +If this file is a backup, diff it with its original. +The backup file is the first file given to `diff'." + (interactive (list (read-file-name "Diff (file with backup): ") + (and current-prefix-arg + (diff-read-switches "Diff switches: ")))) + (let (bak ori) + (if (backup-file-name-p file) + (setq bak file + ori (file-name-sans-versions file)) + (setq bak (or (diff-latest-backup-file file) + (error "No backup found for %s" file)) + ori file)) + (diff bak ori switches))) + +(defun diff-show-difference (n) + "Show difference number N (prefix argument)." + (interactive "p") + (let ((narrowedp (diff-buffer-narrowed-p)) + (min (diff-hunk-min diff-current-hunk)) + (max (diff-hunk-max diff-current-hunk))) + (unwind-protect + (progn + (widen) + (cond + ((< n 0) + (message "No negative hunks.") + (setq n 0)) + ((> n diff-total-hunks) + (message "No hunk %d." n) + (setq n diff-total-hunks))) + (setq diff-current-hunk n + diff-current-difference (int-to-string diff-current-hunk) + min (diff-hunk-min n) + max (diff-hunk-max n)) + (goto-char min)) + (if narrowedp (narrow-to-region min max)) + (set-buffer-modified-p (buffer-modified-p))))) + +(defun diff-show-header () + "Show `diff-header'." + (interactive) + (with-output-to-temp-buffer "*Diff Header*" + (princ (save-restriction + (widen) + (buffer-substring (diff-hunk-min 0) (diff-hunk-max 0)))))) + + +(defun diff-find-file (old-file-p) + "Visit diffed file, at the point corresponding to the current hunk. +Default is to visit the new file; prefix means visit old file instead." + (interactive "P") + (let ((line (diff-file-line old-file-p))) + (find-file + (if old-file-p + (car diff-old-file) + (car diff-new-file))) + (goto-line line) + (recenter 0))) + +(defun diff-find-file-other-window (old-file-p) + "Visit the diffed file in other window, with the point at the current hunk. +Default is to visit the new file; prefix means visit the old file instead." + (interactive "P") + (let ((line (diff-file-line old-file-p))) + (find-file-other-window + (if old-file-p + (car diff-old-file) + (car diff-new-file))) + (goto-line line) + (recenter 0))) + +(defun diff-find-file-other-frame (old-file-p) + "Visit the diffed file in other frame, with point at the current hunk. +Default is to visit the new file; prefix means visit the old file instead." + (interactive "P") + (let ((line (diff-file-line old-file-p))) + (find-file-other-frame + (if old-file-p + (car diff-old-file) + (car diff-new-file))) + (goto-line line) + (recenter 0))) + +(defun diff-display-file (old-file-p) + "Display the diffed file in other window, with point at the current hunk. +Default is to visit the new file; prefix means visit the old file instead." + (interactive "P") + (let ((line (diff-file-line old-file-p)) + (wind (display-buffer (find-file-noselect (if old-file-p + (car diff-old-file) + (car diff-new-file))))) + (curr-wind (selected-window))) + (unwind-protect + (progn + (select-window wind) + (goto-line line) + (recenter 0)) + (select-window curr-wind)))) + +(defun diff-quit () + "Quit diff by killing the diff buffer." + (interactive) + (kill-buffer "*Diff Output*") + (diff-cleanup-buffers)) + +(defun diff-narrow () + "Narrow diff buffer to current difference hunk." + (interactive) + (narrow-to-region (diff-hunk-min diff-current-hunk) + (diff-hunk-max diff-current-hunk))) + +;;; Run any load hooks +(run-hooks 'diff-load-hook) + +;;; end of diff.el