From ce3df56ecc0e16b11106e2f7c6c9246345cfca15 Mon Sep 17 00:00:00 2001 From: Davis Herring Date: Tue, 12 Jul 2016 22:35:01 -0600 Subject: [PATCH 6/6] Set point in diff to correspond to point in file * lisp/vc/diff-mode.el (diff-location, diff-goto-line): New functions. * lisp/vc/diff.el (diff-sentinel): Add BUF and FUNC arguments. Move point in (some) window showing the diff. (diff, diff-no-select): Add FUNC argument to pass to `diff-sentinel'. (diff-no-select): Call `diff-sentinel' only on exit. Pass buffer rather than selecting it. (diff-buffer-current): New function. (diff-backup): Arrange to call `diff-goto-line' if a buffer is current. (diff-buffer-with-file): Arrange to call it if buffer is visible. * lisp/vc/vc.el (vc-diff-finish): Use it with new LOC argument. (vc-diff-internal): Note location and pass it to `vc-diff-finish'. * doc/emacs/files.texi, doc/emacs/maintaining.texi: Document point placement. --- doc/emacs/files.texi | 7 +++- doc/emacs/maintaining.texi | 10 +++-- etc/NEWS | 7 +++- lisp/vc/diff-mode.el | 65 ++++++++++++++++++++++++++++++++++++++ lisp/vc/diff.el | 75 +++++++++++++++++++++++++++++++------------- lisp/vc/vc.el | 36 +++++++++++++++++++-- 6 files changed, 168 insertions(+), 32 deletions(-) diff --git a/doc/emacs/files.texi b/doc/emacs/files.texi index f195a41..ebfe2df 100644 --- a/doc/emacs/files.texi +++ b/doc/emacs/files.texi @@ -1291,12 +1291,17 @@ called Diff mode. @xref{Diff Mode}. The command @kbd{M-x diff-backup} compares a specified file with its most recent backup. If you specify the name of a backup file, @code{diff-backup} compares it with the source file that it is a -backup of. In all other respects, this behaves like @kbd{M-x diff}. +backup of. If there is a visible, up-to-date buffer visiting the +file, point is placed in the @file{*diff*} buffer at the place +corresponding to point in that buffer. In all other respects, this +behaves like @kbd{M-x diff}. @findex diff-buffer-with-file The command @kbd{M-x diff-buffer-with-file} compares a specified buffer with its corresponding file. This shows you what changes you would make to the file if you save the buffer. +Point is placed in @file{*diff*} to correspond to point in the buffer +if it is visible. @findex compare-windows The command @kbd{M-x compare-windows} compares the text in the diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi index 1037bd1..11bf19c 100644 --- a/doc/emacs/maintaining.texi +++ b/doc/emacs/maintaining.texi @@ -792,10 +792,12 @@ the latest revision in which it was modified (@code{vc-annotate}). @kbd{C-x v =} (@code{vc-diff}) displays a @dfn{diff} which compares each work file in the current VC fileset to the version(s) from which you started editing. The diff is displayed in another window, in a -Diff mode buffer (@pxref{Diff Mode}) named @file{*vc-diff*}. The -usual Diff mode commands are available in this buffer. In particular, -the @kbd{g} (@code{revert-buffer}) command performs the file -comparison again, generating a new diff. +Diff mode buffer (@pxref{Diff Mode}) named @file{*vc-diff*}, with +point placed at the location corresponding to point in a visible, +up-to-date buffer visiting one of the files being compared (preferring +the current buffer). The usual Diff mode commands are available in +this buffer. In particular, the @kbd{g} (@code{revert-buffer}) +command performs the file comparison again, generating a new diff. @kindex C-u C-x v = To compare two arbitrary revisions of the current VC fileset, call diff --git a/etc/NEWS b/etc/NEWS index c58349c..5b446cd 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -319,11 +319,16 @@ provided: 'image-property'. directory suffixes (gnu, gnu/lib, gnu/lib/emacs, emacs, lib, lib/emacs) when searching for info directories. +** VC and related modes +++ -** The commands that add ChangeLog entries now prefer a VCS root directory +*** The commands that add ChangeLog entries now prefer a VCS root directory for the ChangeLog file, if none already exists. Customize 'change-log-directory-files' to nil for the old behavior. ++++ +*** Diffs showing the changes made in a visible file are displayed +with point set to correspond to point in the visiting buffer. + --- ** Support for non-string values of 'time-stamp-format' has been removed. diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el index b859c5e..b715d75 100644 --- a/lisp/vc/diff-mode.el +++ b/lisp/vc/diff-mode.el @@ -580,6 +580,71 @@ next hunk if TRY-HARDER is non-nil; otherwise signal an error." (easy-mmode-define-navigation diff-file diff-file-header-re "file" diff-end-of-file) +(defun diff-location (&optional pwd) + "Return location information for `diff-goto-line'. +The result looks like (FILE LINE COLUMN). +FILE is relative to PWD if it is non-nil." + (save-restriction + (widen) + (list (if pwd (file-relative-name buffer-file-name pwd) + buffer-file-name) + (line-number-at-pos) + (- (point) (line-beginning-position))))) + +(defun diff-goto-line (file line column) + "Go to the place in this diff producing LINE and COLUMN in FILE. +If LINE appears in a hunk (as an added, changed, or context line), +move to it and then COLUMN characters forward. If it is absent, go to +the first hunk starting after LINE, or to the end if none does. +If FILE isn't mentioned, go to the beginning of the buffer." + (goto-char (point-min)) + (while (and (re-search-forward diff-file-header-re nil 'move) + (not (string-equal (save-match-data (diff-find-file-name nil t)) + file)))) + (if (eobp) (goto-char (point-min)) + (let ((limit (save-match-data + (save-excursion + (and (re-search-forward diff-file-header-re nil t) + (match-beginning 0))))) + diff-auto-refine-mode) + (goto-char (match-beginning 0)) + (while + (and + (if (progn (forward-line) + (re-search-forward diff-hunk-header-re limit t)) + (goto-char (match-beginning 0)) + (diff-end-of-hunk) nil) ; end of the line + ;; FIXME: assuming that the endpoint is always known from header + (let (start end unified (beg (point))) + (cond + ((looking-at diff-hunk-header-re-unified) + (setq start (string-to-number (match-string 3)) + end (+ start (string-to-number (match-string 4)) -1) + unified t)) + ((looking-at diff-hunk-header-re-context) + (re-search-forward diff-context-mid-hunk-header-re) + (setq start (string-to-number (match-string 1)) + end (string-to-number (match-string 2)))) + ((looking-at diff-hunk-header-re-normal) + (setq start (string-to-number (match-string 4)) + end (let ((e (match-string 5))) + (if e (string-to-number e) start))) + (pcase (match-string 3) + ("d" (setq end (1- end))) + ("c" (re-search-forward diff-normal-mid-hunk-header-re)))) + (t (error "Bad header"))) + (cond + ((< line start) (goto-char beg) nil) ; nothing found + ((<= line end) ; this is our stop + (let ((dist (- line start -1))) + (if unified + (dotimes (_ dist) + (while (progn (forward-line) (eq (char-after) ?-)))) + (forward-line dist))) + (forward-char (+ column (if unified 1 2))) + nil) + (t)))))))) ; keep looking + (defun diff-bounds-of-hunk () "Return the bounds of the diff hunk at point. The return value is a list (BEG END), which are the hunk's start diff --git a/lisp/vc/diff.el b/lisp/vc/diff.el index 6b316c4..515c8e1 100644 --- a/lisp/vc/diff.el +++ b/lisp/vc/diff.el @@ -32,6 +32,8 @@ ;;; Code: (declare-function diff-setup-whitespace "diff-mode" ()) +(declare-function diff-location "diff-mode" (&optional pwd)) +(declare-function diff-goto-line "diff-mode") (defgroup diff nil "Comparing files with `diff'." @@ -57,32 +59,45 @@ diff-switches (mapconcat 'identity diff-switches " "))))) -(defun diff-sentinel (code &optional old-temp-file new-temp-file) +(defun diff-sentinel (buf code &optional old-temp-file new-temp-file func) "Code run when the diff process exits. +BUF is the diff output buffer. CODE is the exit code of the process. It should be 0 only if no diffs were found. If optional args OLD-TEMP-FILE and/or NEW-TEMP-FILE are non-nil, -delete the temporary files so named." +delete the temporary files so named. +Call FUNC with no arguments if it is non-nil and BUF has not been killed. +BUF and its window (if any) are current when FUNC is called." (if old-temp-file (delete-file old-temp-file)) (if new-temp-file (delete-file new-temp-file)) - (diff-setup-whitespace) - (goto-char (point-min)) - (save-excursion - (goto-char (point-max)) - (let ((inhibit-read-only t)) - (insert (format "\nDiff finished%s. %s\n" - (cond ((equal 0 code) " (no differences)") - ((equal 2 code) " (diff error)") - (t "")) - (current-time-string)))))) + (when (buffer-name buf) + ;; Select a (nearby) window showing BUF to make point changes permanent. + (save-selected-window ; also buffer + (let ((w (or (and (eq (window-buffer) buf) (selected-window)) + (get-buffer-window buf) + (get-buffer-window buf 'visible) + (get-buffer-window buf t)))) + (if w (select-window w) (set-buffer buf))) + (diff-setup-whitespace) + (goto-char (point-min)) + (save-excursion + (goto-char (point-max)) + (let ((inhibit-read-only t)) + (insert (format "\nDiff finished%s. %s\n" + (cond ((equal 0 code) " (no differences)") + ((equal 2 code) " (diff error)") + (t "")) + (current-time-string))))) + (when func (funcall func))))) ;;;###autoload -(defun diff (old new &optional switches no-async) +(defun diff (old new &optional switches no-async func) "Find and display the differences between OLD and NEW files. When called interactively, read NEW, then OLD, using the minibuffer. The default for NEW is the current buffer's file name, and the default for OLD is a backup file for NEW, if one exists. If NO-ASYNC is non-nil, call diff synchronously. +See `diff-sentinel' for the meaning of FUNC. When called interactively with a prefix argument, prompt interactively for diff switches. Otherwise, the switches @@ -104,7 +119,7 @@ specified in the variable `diff-switches' are passed to the diff command." (file-name-directory newf) nil t))) (list oldf newf (diff-switches)))) (display-buffer - (diff-no-select old new switches no-async))) + (diff-no-select old new switches no-async nil func))) (defun diff-file-local-copy (file-or-buf) (if (bufferp file-or-buf) @@ -121,7 +136,7 @@ Possible values are: nil -- no, it does not check -- try to probe whether it does") -(defun diff-no-select (old new &optional switches no-async buf) +(defun diff-no-select (old new &optional switches no-async buf func) ;; Noninteractive helper for creating and reverting diff buffers (unless (bufferp new) (setq new (expand-file-name new))) (unless (bufferp old) (setq old (expand-file-name old))) @@ -163,7 +178,8 @@ Possible values are: (diff-mode) (set (make-local-variable 'revert-buffer-function) (lambda (_ignore-auto _noconfirm) - (diff-no-select old new switches no-async (current-buffer)))) + (diff-no-select old new switches no-async + (current-buffer) func))) (setq default-directory thisdir) (let ((inhibit-read-only t)) (insert command "\n")) @@ -173,15 +189,17 @@ Possible values are: (set-process-filter proc 'diff-process-filter) (set-process-sentinel proc (lambda (proc _msg) - (with-current-buffer (process-buffer proc) - (diff-sentinel (process-exit-status proc) - old-alt new-alt))))) + (unless (process-live-p proc) + (diff-sentinel (process-buffer proc) + (process-exit-status proc) + old-alt new-alt func))))) ;; Async processes aren't available. (let ((inhibit-read-only t)) (diff-sentinel + (current-buffer) (call-process shell-file-name nil buf nil shell-command-switch command) - old-alt new-alt)))) + old-alt new-alt func)))) buf)) (defun diff-process-filter (proc string) @@ -195,6 +213,12 @@ Possible values are: (set-marker (process-mark proc) (point))) (if moving (goto-char (process-mark proc)))))) +(defun diff-buffer-current (&optional buffer) + "Return non-nil if BUFFER is displayed and corresponds to its file." + (and (not (buffer-modified-p buffer)) + (get-buffer-window buffer 'visible) + (verify-visited-file-modtime buffer))) + ;;;###autoload (defun diff-backup (file &optional switches) "Diff this file with its backup file or vice versa. @@ -211,7 +235,11 @@ With prefix arg, prompt for diff switches." (setq bak (or (diff-latest-backup-file file) (error "No backup found for %s" file)) ori file)) - (diff bak ori switches))) + (diff bak ori switches nil + (let ((b (find-buffer-visiting ori))) + (and b (diff-buffer-current b) + (let ((loc (with-current-buffer b (diff-location)))) + (lambda () (apply 'diff-goto-line loc)))))))) ;;;###autoload (defun diff-latest-backup-file (fn) @@ -227,7 +255,10 @@ With prefix arg, prompt for diff switches." This requires the external program `diff' to be in your `exec-path'." (interactive "bBuffer: ") (with-current-buffer (get-buffer (or buffer (current-buffer))) - (diff buffer-file-name (current-buffer) nil 'noasync))) + (diff buffer-file-name (current-buffer) nil 'noasync + (and (get-buffer-window nil 'visible) + (let ((loc (diff-location))) + (lambda () (apply 'diff-goto-line loc))))))) (provide 'diff) diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index bb5cdd5..589cdac 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -709,7 +709,10 @@ (require 'vc-dispatcher) (require 'cl-lib) +(declare-function diff-buffer-current "diff" (&optional buf)) (declare-function diff-setup-whitespace "diff-mode" ()) +(declare-function diff-location "diff-mode" (pwd)) +(declare-function diff-goto-line "diff-mode") (eval-when-compile (require 'dired)) @@ -1645,7 +1648,7 @@ to override the value of `vc-diff-switches' and `diff-switches'." (declare (obsolete vc-switches "22.1")) `(vc-switches ',backend 'diff)) -(defun vc-diff-finish (buffer messages) +(defun vc-diff-finish (buffer messages loc) ;; The empty sync output case has already been handled, so the only ;; possibility of an empty output is for an async process. (when (buffer-live-p buffer) @@ -1659,7 +1662,8 @@ to override the value of `vc-diff-switches' and `diff-switches'." (diff-setup-whitespace) (goto-char (point-min)) (when window - (shrink-window-if-larger-than-buffer window))) + (shrink-window-if-larger-than-buffer window)) + (when loc (apply #'diff-goto-line loc))) (when (and messages (not emptyp)) (message "%sdone" (car messages)))))) @@ -1683,7 +1687,9 @@ Return t if the buffer had changes, nil otherwise." ;; but the only way to set it for each file included would ;; be to call the back end separately for each file. (coding-system-for-read - (if files (vc-coding-system-for-diff (car files)) 'undecided))) + (if files (vc-coding-system-for-diff (car files)) 'undecided)) + (from (current-buffer)) + loc) ;; On MS-Windows and MS-DOS, Diff is likely to produce DOS-style ;; EOLs, which will look ugly if (car files) happens to have Unix ;; EOLs. @@ -1720,6 +1726,28 @@ Return t if the buffer had changes, nil otherwise." (if async 'async 1) "diff" file (append (vc-switches nil 'diff) '("/dev/null")))))) (setq files (nreverse filtered)))) + (unless rev2 + (require 'diff) + (require 'diff-mode) + ;; Remember position in the most recent relevant buffer. + ;; Put the (original) current buffer first like `select-window' does. + (let (regs dirs + (bufs (cons from (buffer-list))) + (pwd default-directory)) + (dolist (f files) + (push (file-truename f) (if (file-directory-p f) dirs regs))) + (while bufs + (with-current-buffer (car bufs) + (let ((f buffer-file-truename)) + (and (vc-backend f) + (or (member f regs) ; visiting a file in the set + (let ((d dirs)) ; or a file in a directory in the set + (while (and d (not (string-prefix-p (car d) f))) + (setq d (cdr d))) + d)) + (diff-buffer-current) + (setq loc (diff-location pwd))))) + (setq bufs (unless loc (cdr bufs)))))) (vc-call-backend (car vc-fileset) 'diff files rev1 rev2 buffer async) (set-buffer buffer) (diff-mode) @@ -1743,7 +1771,7 @@ Return t if the buffer had changes, nil otherwise." ;; after `pop-to-buffer'; the former assumes the diff buffer is ;; shown in some window. (let ((buf (current-buffer))) - (vc-run-delayed (vc-diff-finish buf (when verbose messages)))) + (vc-run-delayed (vc-diff-finish buf (when verbose messages) loc))) ;; In the async case, we return t even if there are no differences ;; because we don't know that yet. t))) -- 1.7.1