# HG changeset patch # User Davis Herring # Date 1634243435 -7200 # Thu Oct 14 22:30:35 2021 +0200 # Node ID 18f4bbf01c0247e973b636163991fe1590459f1b # Parent 0745c5efa8df0977a457656484c15ca4d53392c6 Add a navigation feature to vc and diff mode: diff-goto-line * lisp/vc/vc.el (diff-goto-line): new decalare function to include the new diff-goto-line-functionality. * lisp/vc/vc.el (vc-diff-finish): Modify the funcion, to suppor the new diff-goto-line-functionality. * lisp/vc/diff-mode.el (diff-goto-line): New function that allows to jump from a specific line in the source buffer to the corresponding line in the buffer. * lisp/vc/diff-mode.el (diff-hunk-header-re): modify the const, so that it can be used with the new diff-goto-line-functionality. * lisp/vc/diff-mode.el (diff-hunk-header-re-context): modify the const, so that it can be used with the new diff-goto-line-functionality. * lisp/vc/diff-mode.el (diff-hunk-header-re): Modify the const, to be used with new diff-goto-line-functionality. * lisp/vc/diff-mode.el: (diff-hunk-header-re-context): New variable that allows to jump from a specific line in the source buffer to the corresponding line in the diff buffer. diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el --- a/lisp/vc/diff-mode.el +++ b/lisp/vc/diff-mode.el @@ -442,8 +442,10 @@ (defconst diff-hunk-header-re-unified "^@@ -\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? \\+\\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? @@") +(defconst diff-hunk-header-re-context + "\\*\\{15\\}\\(?: .*\\)?\n\\*\\*\\* \\(\\([0-9]+\\)\\(?:,\\(-?[0-9]+\\)\\)?\\) \\*\\*\\*\\*") (defconst diff-context-mid-hunk-header-re - "--- \\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? ----$") + "^--- \\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? ----$") (defvar diff-use-changed-face (and (face-differs-from-default-p 'diff-changed) (not (face-equal 'diff-changed 'diff-added)) @@ -526,7 +528,9 @@ See https://lists.gnu.org/r/emacs-devel/2007-11/msg01990.html") (defconst diff-hunk-header-re - (concat "^\\(?:" diff-hunk-header-re-unified ".*\\|\\*\\{15\\}.*\n\\*\\*\\* .+ \\*\\*\\*\\*\\|[0-9]+\\(,[0-9]+\\)?[acd][0-9]+\\(,[0-9]+\\)?\\)$")) + (concat "^\\(?:" diff-hunk-header-re-unified ".*\\|" + diff-hunk-header-re-context + "\\|[0-9]+\\(,[0-9]+\\)?[acd][0-9]+\\(,[0-9]+\\)?\\)$")) (defconst diff-file-header-re (concat "^\\(--- .+\n\\+\\+\\+ \\|\\*\\*\\* .+\n--- \\|[^-+!<>0-9@* \n]\\).+\n" (substring diff-hunk-header-re 1))) (defconst diff-separator-re "^--+ ?$") @@ -716,6 +720,46 @@ (easy-mmode-define-navigation diff-file diff-file-header-re "file" diff-end-of-file) +(defun diff-goto-line (file line column) + "Go to the place in this diff producing LINE and COLUMN in FILE. +If LINE (in the new version of FILE) is included (in the diff +buffer as +/! line or a 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 (diff-find-file-name) file)))) + (if (eobp) (goto-char (point-min)) + (forward-line -1) + (while + (progn + (condition-case nil (diff-hunk-next) + (error (goto-char (point-max)))) + (cond ((eobp) nil) ; end of the line + ((looking-at diff-hunk-header-re-unified) + (let ((start (string-to-number (match-string 3))) + ;; FIXME: assuming that we have the length + (len (string-to-number (match-string 4)))) + (cond ((< line start) nil) ; nothing found + ((< line (+ start len)) ; this is our stop + (dotimes (i (- line start -1)) + (while (progn (forward-line 1) + (eq (char-after) ?-)))) + (forward-char (1+ column))) + (t)))) ; keep looking + ((looking-at diff-hunk-header-re-context) + (re-search-forward diff-context-mid-hunk-header-re) + (let ((start (string-to-number (match-string 2))) + ;; FIXME: assuming that we have the length + (end (string-to-number (match-string 3)))) + (cond ((< line start) nil) ; nothing found + ((<= line end) ; this is our stop + (forward-line (- line start -1)) + (forward-char (+ column 2))) + (t)))) ; keep looking + (t (error "Unified or context diffs only"))))))) + (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 @@ -1186,7 +1230,11 @@ (inhibit-read-only t)) (save-excursion (goto-char start) - (while (and (re-search-forward "^\\(\\(\\*\\*\\*\\) .+\n\\(---\\) .+\\|\\*\\{15\\}.*\n\\*\\*\\* \\([0-9]+\\),\\(-?[0-9]+\\) \\*\\*\\*\\*\\)\\(?: \\(.*\\)\\|$\\)" nil t) + (while (and (re-search-forward + (concat "^\\(\\(\\*\\*\\*\\) .+\n\\(---\\) .+\\|" + diff-hunk-header-re-context + "\\)\\(?: \\(.*\\)\\|$\\)") + nil t) (< (point) end)) (combine-after-change-calls (if (match-beginning 2) @@ -1196,15 +1244,15 @@ (replace-match "+++" t t nil 3) (replace-match "---" t t nil 2)) ;; we matched a hunk header - (let ((line1s (match-string 4)) - (line1e (match-string 5)) + (let ((line1s (match-string 5)) + (line1e (match-string 6)) (pt1 (match-beginning 0)) ;; Variables to use the special undo function. (old-undo buffer-undo-list) (old-end (marker-position end)) ;; We currently throw away the comment that can follow ;; the hunk header. FIXME: Preserve it instead! - (reversible (not (match-end 6)))) + (reversible (not (match-end 7)))) (replace-match "") (unless (re-search-forward diff-context-mid-hunk-header-re nil t) @@ -1282,7 +1330,11 @@ (inhibit-read-only t)) (save-excursion (goto-char start) - (while (and (re-search-forward "^\\(\\([-*][-*][-*] \\)\\(.+\\)\n\\([-+][-+][-+] \\)\\(.+\\)\\|\\*\\{15\\}.*\n\\*\\*\\* \\(.+\\) \\*\\*\\*\\*\\|@@ -\\([0-9,]+\\) \\+\\([0-9,]+\\) @@.*\\)$" nil t) + (while (and (re-search-forward + (concat "^\\(\\([-*][-*][-*] \\)\\(.+\\)\n\\([-+][-+][-+] \\)\\(.+\\)\\|" + diff-hunk-header-re-context + "\\|@@ -\\([0-9,]+\\) \\+\\([0-9,]+\\) @@.*\\)$") + nil t) (< (point) end)) (combine-after-change-calls (cond @@ -1646,13 +1698,13 @@ ;; A context diff. ((eq (char-after) ?*) - (if (not (looking-at "\\*\\{15\\}\\(?: .*\\)?\n\\*\\*\\* \\([0-9]+\\)\\(?:,\\([0-9]+\\)\\)? \\*\\*\\*\\*")) + (if (not (looking-at diff-hunk-header-re-context)) (error "Unrecognized context diff first hunk header format") (forward-line 2) (diff-sanity-check-context-hunk-half - (if (match-end 2) - (1+ (- (string-to-number (match-string 2)) - (string-to-number (match-string 1)))) + (if (match-end 3) + (1+ (- (string-to-number (match-string 3)) + (string-to-number (match-string 2)))) 1)) (if (not (looking-at diff-context-mid-hunk-header-re)) (error "Unrecognized context diff second hunk header format") diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -739,6 +739,7 @@ (require 'cl-lib) (declare-function diff-setup-whitespace "diff-mode" ()) +(declare-function diff-goto-line "diff-mode") (eval-when-compile (require 'dired)) @@ -1716,7 +1717,7 @@ ;; any switches in diff-switches. (when (listp switches) switches)))) -(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) @@ -1730,7 +1731,8 @@ (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)))))) @@ -1754,7 +1756,8 @@ ;; 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)) + 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. @@ -1791,6 +1794,19 @@ (if async 'async 1) "diff" file (append (vc-switches nil 'diff) `(,(null-device))))))) (setq files (nreverse filtered)))) + (unless rev2 ; remember the position in the current buffer + (let ((f files)) + (while f + (let ((buf (find-buffer-visiting (car f)))) + (when buf + (setq loc + (with-current-buffer buf + (save-restriction + (widen) + (list (file-relative-name (car f)) + (line-number-at-pos) + (- (point) (line-beginning-position))))))) + (setq f (unless (eq buf (current-buffer)) (cdr f))))))) (vc-call-backend (car vc-fileset) 'diff files rev1 rev2 buffer async) (set-buffer buffer) (diff-mode) @@ -1815,7 +1831,7 @@ ;; 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)))