* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
@ 2014-05-21 12:21 Dima Kogan
2016-02-24 2:33 ` Lars Ingebrigtsen
2016-09-03 11:05 ` Andreas Schwab
0 siblings, 2 replies; 24+ messages in thread
From: Dima Kogan @ 2014-05-21 12:21 UTC (permalink / raw)
To: 17544
[-- Attachment #1: Type: text/plain, Size: 1429 bytes --]
Navigation and use of diff buffers had several annoying corner cases that this
patch fixes. These corner cases were largely due to inconsistent treatment of
file headers. Say you have a diff such as this:
--- aaa
+++ bbb
@@ -52,7 +52,7 @@
hunk1
@@ -74,7 +74,7 @@
hunk2
--- ccc
+++ ddd
@@ -608,6 +608,6 @@
hunk3
@@ -654,7 +654,7 @@
hunk4
The file headers here are the '---' and '+++' lines. With the point on such a
line, hunk operations would sometimes refer to the next hunk and sometimes to
the previous hunk. Most of the time it would be the previous hunk, which is not
what the user would expect. This patch consistently treats such headers as the
next hunk. So with this patch, if the point is on the '--- ccc' line, the point
is seen as referring to hunk3.
Specific behaviors this fixes are:
1. It should be possible to place the point in the middle of a diff buffer, and
press M-k repeatedly to kill hunks in the order they appear in the buffer. With
the point on hunk1, M-k M-k would kill hunk1 then hunk2. With the point on
hunk3, it would kill hunk3 then hunk4; this is fine. However, with the point on
hunk2, it'd kill hunk2 then hunk1. This is fixed by this patch.
2. Similarly, it should be possible to apply hunks in order. Previously with the
point at the start, C-c C-a would apply the hunk1, then move the point to the
first @@ header, and thus C-c C-a would try to apply the same hunk again.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Improved-diff-mode-navigation-manipulation.patch --]
[-- Type: text/x-diff, Size: 7859 bytes --]
From 27d926293402c74eb3d83124e0287dd49cb4543a Mon Sep 17 00:00:00 2001
From: Dima Kogan <dima@secretsauce.net>
Date: Thu, 15 May 2014 00:24:00 -0700
Subject: [PATCH] Improved diff-mode navigation/manipulation
Navigation and use of diff buffers had several annoying corner cases that this
patch fixes. These corner cases were largely due to inconsistent treatment of
file headers. Say you have a diff such as this:
--- aaa
+++ bbb
@@ -52,7 +52,7 @@
hunk1
@@ -74,7 +74,7 @@
hunk2
--- ccc
+++ ddd
@@ -608,6 +608,6 @@
hunk3
@@ -654,7 +654,7 @@
hunk4
The file headers here are the '---' and '+++' lines. With the point on such a
line, hunk operations would sometimes refer to the next hunk and sometimes to
the previous hunk. Most of the time it would be the previous hunk, which is not
what the user would expect. This patch consistently treats such headers as the
next hunk. So with this patch, if the point is on the '--- ccc' line, the point
is seen as referring to hunk3.
Specific behaviors this fixes are:
1. It should be possible to place the point in the middle of a diff buffer, and
press M-k repeatedly to kill hunks in the order they appear in the buffer. With
the point on hunk1, M-k M-k would kill hunk1 then hunk2. With the point on
hunk3, it would kill hunk3 then hunk4; this is fine. However, with the point on
hunk2, it'd kill hunk2 then hunk1. This is fixed by this patch.
2. Similarly, it should be possible to apply hunks in order. Previously with the
point at the start, C-c C-a would apply the hunk1, then move the point to the
first @@ header, and thus C-c C-a would try to apply the same hunk again.
---
lisp/vc/diff-mode.el | 77 ++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 66 insertions(+), 11 deletions(-)
diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 923de9a..d334307 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -593,6 +593,58 @@ 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--wrap-hunk-navigation (isinteractive name orig count)
+ "I override the default diff-hunk-next/prev implementation by
+skipping any lines that are associated with this hunk but precede
+the hunk-start marker. For instance, a diff file could contain
+
+diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
+index 923de9a..6b1c24f 100644
+--- a/lisp/vc/diff-mode.el
++++ b/lisp/vc/diff-mode.el
+@@ -590,6 +590,22 @@
+.......
+
+If a point is on 'index', then the point is considered to be in
+this first hunk. Here I move the point to the @@... marker before
+executing the default diff-hunk-next/prev implementation to move
+to the NEXT marker"
+ (if (not isinteractive)
+ (funcall orig count)
+
+ (let ((start (point)))
+ (let ((hunk-bounds (diff-bounds-of-hunk)))
+ (goto-char (car hunk-bounds)))
+
+ (funcall orig count)
+
+ (when (not (looking-at diff-hunk-header-re))
+ (goto-char start)
+ (user-error (format "No %s hunk" name))))))
+
+(setq diff--hunk-prev-internal (symbol-function 'diff-hunk-prev))
+(defun diff-hunk-prev (&optional count)
+ "Go to the previous COUNT'th hunk"
+ (interactive "p")
+ (diff--wrap-hunk-navigation
+ (called-interactively-p 'interactive)
+ "prev"
+ diff--hunk-prev-internal
+ count))
+
+(setq diff--hunk-next-internal (symbol-function 'diff-hunk-next))
+(defun diff-hunk-next (&optional count)
+ "Go to the next COUNT'th hunk"
+ (interactive "p")
+ (diff--wrap-hunk-navigation
+ (called-interactively-p 'interactive)
+ "next"
+ diff--hunk-next-internal
+ count))
+
+
+
+
(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
@@ -602,12 +654,13 @@ point is in a file header, return the bounds of the next hunk."
(let ((pos (point))
(beg (diff-beginning-of-hunk t))
(end (diff-end-of-hunk)))
- (cond ((>= end pos)
+ (cond ((> end pos)
(list beg end))
;; If this hunk ends above POS, consider the next hunk.
((re-search-forward diff-hunk-header-re nil t)
(list (match-beginning 0) (diff-end-of-hunk)))
- (t (error "No hunk found"))))))
+ ;; There's no next hunk, so just take the one we have
+ (t (list beg end))))))
(defun diff-bounds-of-file ()
"Return the bounds of the file segment at point.
@@ -1683,8 +1736,9 @@ SRC and DST are the two variants of text as returned by `diff-hunk-text'.
SWITCHED is non-nil if the patch is already applied.
NOPROMPT, if non-nil, means not to prompt the user."
(save-excursion
- (let* ((other (diff-xor other-file diff-jump-to-old-file))
- (char-offset (- (point) (diff-beginning-of-hunk t)))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (other (diff-xor other-file diff-jump-to-old-file))
+ (char-offset (- (point) (goto-char (car hunk-bounds))))
;; Check that the hunk is well-formed. Otherwise diff-mode and
;; the user may disagree on what constitutes the hunk
;; (e.g. because an empty line truncates the hunk mid-course),
@@ -1693,7 +1747,7 @@ NOPROMPT, if non-nil, means not to prompt the user."
;; Suppress check when NOPROMPT is non-nil (Bug#3033).
(_ (unless noprompt (diff-sanity-check-hunk)))
(hunk (buffer-substring
- (point) (save-excursion (diff-end-of-hunk) (point))))
+ (point) (cadr hunk-bounds)))
(old (diff-hunk-text hunk reverse char-offset))
(new (diff-hunk-text hunk (not reverse) char-offset))
;; Find the location specification.
@@ -1802,7 +1856,7 @@ With a prefix argument, REVERSE the hunk."
(set-window-point (display-buffer buf) (+ (car pos) (cdr new)))
(diff-hunk-status-msg line-offset (diff-xor switched reverse) nil)
(when diff-advance-after-apply-hunk
- (diff-hunk-next))))))
+ (call-interactively 'diff-hunk-next))))))
(defun diff-test-hunk (&optional reverse)
@@ -1873,14 +1927,15 @@ For use in `add-log-current-defun-function'."
(defun diff-ignore-whitespace-hunk ()
"Re-diff the current hunk, ignoring whitespace differences."
(interactive)
- (let* ((char-offset (- (point) (diff-beginning-of-hunk t)))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (char-offset (- (point) (goto-char (car hunk-bounds))))
(opts (pcase (char-after) (?@ "-bu") (?* "-bc") (_ "-b")))
(line-nb (and (or (looking-at "[^0-9]+\\([0-9]+\\)")
(error "Can't find line number"))
(string-to-number (match-string 1))))
(inhibit-read-only t)
(hunk (delete-and-extract-region
- (point) (save-excursion (diff-end-of-hunk) (point))))
+ (point) (cadr hunk-bounds)))
(lead (make-string (1- line-nb) ?\n)) ;Line nums start at 1.
(file1 (make-temp-file "diff1"))
(file2 (make-temp-file "diff2"))
@@ -1967,8 +2022,8 @@ For use in `add-log-current-defun-function'."
(interactive)
(require 'smerge-mode)
(save-excursion
- (diff-beginning-of-hunk t)
- (let* ((start (point))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (start (goto-char (car hunk-bounds)))
(style (diff-hunk-style)) ;Skips the hunk header as well.
(beg (point))
(props-c '((diff-mode . fine) (face diff-refine-change)))
@@ -1976,7 +2031,7 @@ For use in `add-log-current-defun-function'."
(props-a '((diff-mode . fine) (face diff-refine-added)))
;; Be careful to go back to `start' so diff-end-of-hunk gets
;; to read the hunk header's line info.
- (end (progn (goto-char start) (diff-end-of-hunk) (point))))
+ (end (goto-char (cadr hunk-bounds))))
(remove-overlays beg end 'diff-mode 'fine)
--
2.0.0.rc0
^ permalink raw reply related [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2014-05-21 12:21 bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation Dima Kogan
@ 2016-02-24 2:33 ` Lars Ingebrigtsen
2016-09-03 9:17 ` Dima Kogan
2016-09-03 11:05 ` Andreas Schwab
1 sibling, 1 reply; 24+ messages in thread
From: Lars Ingebrigtsen @ 2016-02-24 2:33 UTC (permalink / raw)
To: Dima Kogan; +Cc: 17544
Dima Kogan <lists@dima.secretsauce.net> writes:
> Navigation and use of diff buffers had several annoying corner cases that this
> patch fixes. These corner cases were largely due to inconsistent treatment of
> file headers. Say you have a diff such as this:
>
> --- aaa
> +++ bbb
> @@ -52,7 +52,7 @@
> hunk1
> @@ -74,7 +74,7 @@
> hunk2
> --- ccc
> +++ ddd
> @@ -608,6 +608,6 @@
> hunk3
> @@ -654,7 +654,7 @@
> hunk4
>
> The file headers here are the '---' and '+++' lines. With the point on such a
> line, hunk operations would sometimes refer to the next hunk and sometimes to
> the previous hunk. Most of the time it would be the previous hunk, which is not
> what the user would expect. This patch consistently treats such headers as the
> next hunk. So with this patch, if the point is on the '--- ccc' line, the point
> is seen as referring to hunk3.
>
> Specific behaviors this fixes are:
>
> 1. It should be possible to place the point in the middle of a diff buffer, and
> press M-k repeatedly to kill hunks in the order they appear in the buffer. With
> the point on hunk1, M-k M-k would kill hunk1 then hunk2. With the point on
> hunk3, it would kill hunk3 then hunk4; this is fine. However, with the point on
> hunk2, it'd kill hunk2 then hunk1. This is fixed by this patch.
>
> 2. Similarly, it should be possible to apply hunks in order. Previously with the
> point at the start, C-c C-a would apply the hunk1, then move the point to the
> first @@ header, and thus C-c C-a would try to apply the same hunk again.
I think this makes sense, and the patch looks good to me. Does it still
apply to the Emacs trunk? Anybody got any objections to installing it?
It should also have an entry in NEWS, I think.
--
(domestic pets only, the antidote for overdose, milk.)
bloggy blog: http://lars.ingebrigtsen.no
^ permalink raw reply [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-02-24 2:33 ` Lars Ingebrigtsen
@ 2016-09-03 9:17 ` Dima Kogan
2016-09-03 10:14 ` Eli Zaretskii
0 siblings, 1 reply; 24+ messages in thread
From: Dima Kogan @ 2016-09-03 9:17 UTC (permalink / raw)
To: Lars Ingebrigtsen; +Cc: 17544
Lars Ingebrigtsen <larsi@gnus.org> writes:
> I think this makes sense, and the patch looks good to me. Does it
> still apply to the Emacs trunk? Anybody got any objections to
> installing it?
>
> It should also have an entry in NEWS, I think.
This is a gentle ping. Can we install this?
^ permalink raw reply [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-09-03 9:17 ` Dima Kogan
@ 2016-09-03 10:14 ` Eli Zaretskii
2016-09-03 21:24 ` Dima Kogan
0 siblings, 1 reply; 24+ messages in thread
From: Eli Zaretskii @ 2016-09-03 10:14 UTC (permalink / raw)
To: Dima Kogan; +Cc: larsi, 17544
> From: Dima Kogan <dima@secretsauce.net>
> Date: Sat, 03 Sep 2016 02:17:19 -0700
> Cc: 17544@debbugs.gnu.org
>
> Lars Ingebrigtsen <larsi@gnus.org> writes:
>
> > I think this makes sense, and the patch looks good to me. Does it
> > still apply to the Emacs trunk? Anybody got any objections to
> > installing it?
> >
> > It should also have an entry in NEWS, I think.
>
> This is a gentle ping. Can we install this?
I'd like to hear Stefan's comments.
Regardless, the doc string of diff--wrap-hunk-navigation is not
according to our coding style, so please fix it. Also, I don't quite
understand why you need changes like this one:
- (diff-hunk-next))))))
+ (call-interactively 'diff-hunk-next))))))
and the whole issue of testing called-interactively-p that goes with
it. Can you explain?
Thanks.
^ permalink raw reply [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2014-05-21 12:21 bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation Dima Kogan
2016-02-24 2:33 ` Lars Ingebrigtsen
@ 2016-09-03 11:05 ` Andreas Schwab
1 sibling, 0 replies; 24+ messages in thread
From: Andreas Schwab @ 2016-09-03 11:05 UTC (permalink / raw)
To: Dima Kogan; +Cc: 17544
On Mai 21 2014, Dima Kogan <lists@dima.secretsauce.net> wrote:
> +(setq diff--hunk-prev-internal (symbol-function 'diff-hunk-prev))
> +(defun diff-hunk-prev (&optional count)
This will break if diff-mode is reloaded.
Andreas.
--
Andreas Schwab, schwab@linux-m68k.org
GPG Key fingerprint = 58CA 54C7 6D53 942B 1756 01D3 44D5 214B 8276 4ED5
"And now for something completely different."
^ permalink raw reply [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-09-03 10:14 ` Eli Zaretskii
@ 2016-09-03 21:24 ` Dima Kogan
2016-09-04 3:27 ` npostavs
0 siblings, 1 reply; 24+ messages in thread
From: Dima Kogan @ 2016-09-03 21:24 UTC (permalink / raw)
To: Eli Zaretskii; +Cc: Andreas Schwab, 17544
I'd like to get high-level consensus before making any changes. So ...
Eli Zaretskii <eliz@gnu.org> writes:
> Regardless, the doc string of diff--wrap-hunk-navigation is not
> according to our coding style, so please fix it.
OK.
> Also, I don't quite understand why you need changes like this one:
>
> - (diff-hunk-next))))))
> + (call-interactively 'diff-hunk-next))))))
It's been quite a while since I wrote this, and looking at it just now I
can't tell why this is necessary. So let's say one can take out this
hunk
> and the whole issue of testing called-interactively-p that goes with
> it. Can you explain?
I'm guessing the interactivity checking in diff-hunk-next and
diff-hunk-prev was intended to keep scripts working as before. Again, it
has been too long to remember specifically.
Andreas Schwab <schwab@linux-m68k.org> writes:
> On Mai 21 2014, Dima Kogan <lists@dima.secretsauce.net> wrote:
>
>> +(setq diff--hunk-prev-internal (symbol-function 'diff-hunk-prev))
>> +(defun diff-hunk-prev (&optional count)
>
> This will break if diff-mode is reloaded.
Will it? diff-hunk-next and -prev are defined just above in the
(easy-mmode-define-navigation
diff-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
block. If one reloads diff-mode, wouldn't this
easy-mmode-define-navigation block define the old diff-hunk-prev, which
then gets redefined by the code in the patch? It would be nicer to
define these functions properly the first time instead of (effectively)
advising them. But (easy-mmode-define-navigation) allows arbitrary code
to be called after the template, but I need new stuff BEFORE it. Better
ideas welcome.
dima
^ permalink raw reply [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-09-03 21:24 ` Dima Kogan
@ 2016-09-04 3:27 ` npostavs
2016-09-07 7:14 ` Dima Kogan
2016-09-13 3:56 ` Dima Kogan
0 siblings, 2 replies; 24+ messages in thread
From: npostavs @ 2016-09-04 3:27 UTC (permalink / raw)
To: Dima Kogan; +Cc: Andreas Schwab, 17544
Dima Kogan <dima@secretsauce.net> writes:
>
>> and the whole issue of testing called-interactively-p that goes with
>> it. Can you explain?
>
> I'm guessing the interactivity checking in diff-hunk-next and
> diff-hunk-prev was intended to keep scripts working as before. Again, it
> has been too long to remember specifically.
IMO, it would make more sense to just define your new commands directly,
something like:
(defun diff-hunk-next-command (&optional count)
"<A useful description goes here>."
(interactive "p")
(let ((start (point)))
(let ((hunk-bounds (diff-bounds-of-hunk)))
(goto-char (car hunk-bounds)))
(diff-hunk-next count)
(when (not (looking-at diff-hunk-header-re))
(goto-char start)
(user-error "No next hunk"))))
And then just give the *binding* of `diff-hunk-next' to
`diff-hunk-next-command'. No need to make a higher order wrapper
function just for defining 2 functions.
^ permalink raw reply [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-09-04 3:27 ` npostavs
@ 2016-09-07 7:14 ` Dima Kogan
2016-09-14 22:31 ` Dima Kogan
2016-09-13 3:56 ` Dima Kogan
1 sibling, 1 reply; 24+ messages in thread
From: Dima Kogan @ 2016-09-07 7:14 UTC (permalink / raw)
To: npostavs; +Cc: Andreas Schwab, 17544
npostavs@users.sourceforge.net writes:
> Dima Kogan <dima@secretsauce.net> writes:
>
>>
>>> and the whole issue of testing called-interactively-p that goes with
>>> it. Can you explain?
>>
>> I'm guessing the interactivity checking in diff-hunk-next and
>> diff-hunk-prev was intended to keep scripts working as before. Again, it
>> has been too long to remember specifically.
>
> IMO, it would make more sense to just define your new commands directly,
> something like:
>
> (defun diff-hunk-next-command (&optional count)
> "<A useful description goes here>."
> (interactive "p")
> (let ((start (point)))
> (let ((hunk-bounds (diff-bounds-of-hunk)))
> (goto-char (car hunk-bounds)))
> (diff-hunk-next count)
> (when (not (looking-at diff-hunk-header-re))
> (goto-char start)
> (user-error "No next hunk"))))
>
> And then just give the *binding* of `diff-hunk-next' to
> `diff-hunk-next-command'. No need to make a higher order wrapper
> function just for defining 2 functions.
I've no problems with this. If I address this and the other comments, we
can talk about merging this?
Thanks
^ permalink raw reply [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-09-04 3:27 ` npostavs
2016-09-07 7:14 ` Dima Kogan
@ 2016-09-13 3:56 ` Dima Kogan
1 sibling, 0 replies; 24+ messages in thread
From: Dima Kogan @ 2016-09-13 3:56 UTC (permalink / raw)
To: npostavs; +Cc: Andreas Schwab, 17544
npostavs@users.sourceforge.net writes:
> Dima Kogan <dima@secretsauce.net> writes:
c>
>>
>>> and the whole issue of testing called-interactively-p that goes with
>>> it. Can you explain?
>>
>> I'm guessing the interactivity checking in diff-hunk-next and
>> diff-hunk-prev was intended to keep scripts working as before. Again, it
>> has been too long to remember specifically.
>
> IMO, it would make more sense to just define your new commands directly,
> something like:
>
> (defun diff-hunk-next-command (&optional count)
> "<A useful description goes here>."
> (interactive "p")
> (let ((start (point)))
> (let ((hunk-bounds (diff-bounds-of-hunk)))
> (goto-char (car hunk-bounds)))
> (diff-hunk-next count)
> (when (not (looking-at diff-hunk-header-re))
> (goto-char start)
> (user-error "No next hunk"))))
>
> And then just give the *binding* of `diff-hunk-next' to
> `diff-hunk-next-command'. No need to make a higher order wrapper
> function just for defining 2 functions.
^ permalink raw reply [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-09-07 7:14 ` Dima Kogan
@ 2016-09-14 22:31 ` Dima Kogan
2016-09-23 7:22 ` Dima Kogan
0 siblings, 1 reply; 24+ messages in thread
From: Dima Kogan @ 2016-09-14 22:31 UTC (permalink / raw)
To: npostavs; +Cc: Andreas Schwab, 17544
[-- Attachment #1: Type: text/plain, Size: 548 bytes --]
Here's a revised patch. I've been running with this for a few days, and
it feels delightful. Changes from the last time:
- Removed the unnecessary (call-interactively ...) pointed out by Eli
- Instead of using (easy-mmode-define-navigation) to create
diff-{hunk,file}-{next,prev} and then overriding that definition, I
now have (easy-mmode-define-navigation) create
diff--internal-{hunk,file}-{next,prev}, and then my own
(diff-{hunk,file}-{next,prev}) call those functions
- I now handle file navigation in addition to hunk navigation
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Improved-diff-mode-navigation-manipulation.patch --]
[-- Type: text/x-diff, Size: 9152 bytes --]
From 8070ca643f5467c3dcfeb8d45b76a897b347d518 Mon Sep 17 00:00:00 2001
From: Dima Kogan <Dmitriy.Kogan@jpl.nasa.gov>
Date: Wed, 14 Sep 2016 15:25:06 -0700
Subject: [PATCH] Improved diff-mode navigation/manipulation
Navigation and use of diff buffers had several annoying corner cases that this
patch fixes. These corner cases were largely due to inconsistent treatment of
file headers. Say you have a diff such as this:
--- aaa
+++ bbb
@@ -52,7 +52,7 @@
hunk1
@@ -74,7 +74,7 @@
hunk2
--- ccc
+++ ddd
@@ -608,6 +608,6 @@
hunk3
@@ -654,7 +654,7 @@
hunk4
The file headers here are the '---' and '+++' lines. With the point on such a
line, hunk operations would sometimes refer to the next hunk and sometimes to
the previous hunk. Most of the time it would be the previous hunk, which is not
what the user would expect. This patch consistently treats such headers as the
next hunk. So with this patch, if the point is on the '--- ccc' line, the point
is seen as referring to hunk3.
Specific behaviors this fixes are:
1. It should be possible to place the point in the middle of a diff buffer, and
press M-k repeatedly to kill hunks in the order they appear in the buffer. With
the point on hunk1, M-k M-k would kill hunk1 then hunk2. With the point on
hunk3, it would kill hunk3 then hunk4; this is fine. However, with the point on
hunk2, it'd kill hunk2 then hunk1. This is fixed by this patch.
2. Similarly, it should be possible to apply hunks in order. Previously with the
point at the start, C-c C-a would apply the hunk1, then move the point to the
first @@ header, and thus C-c C-a would try to apply the same hunk again.
---
lisp/vc/diff-mode.el | 110 +++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 98 insertions(+), 12 deletions(-)
diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 58498cb..6716f39 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -551,7 +551,7 @@ next hunk if TRY-HARDER is non-nil; otherwise signal an error."
;; Define diff-{hunk,file}-{prev,next}
(easy-mmode-define-navigation
- diff-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
+ diff--internal-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
(when diff-auto-refine-mode
(unless (prog1 diff--auto-refine-data
(setq diff--auto-refine-data
@@ -570,7 +570,88 @@ next hunk if TRY-HARDER is non-nil; otherwise signal an error."
(diff-refine-hunk))))))))))))
(easy-mmode-define-navigation
- diff-file diff-file-header-re "file" diff-end-of-file)
+ diff--internal-file diff-file-header-re "file" diff-end-of-file)
+
+(defun diff--wrap-navigation (isinteractive
+ what orig
+ header-re goto-start-func count)
+ "I override the default diff-{hunk,file}-{next,prev}
+implementation by skipping any lines that are associated with
+this hunk/file but precede the hunk-start marker. For instance, a
+diff file could contain
+
+diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
+index 923de9a..6b1c24f 100644
+--- a/lisp/vc/diff-mode.el
++++ b/lisp/vc/diff-mode.el
+@@ -590,6 +590,22 @@
+.......
+
+If a point is on 'index', then the point is considered to be in
+this first hunk. Here I move the point to the @@... marker before
+executing the default diff-hunk-next/prev implementation to move
+to the NEXT marker"
+ (if (not isinteractive)
+ (funcall orig count)
+
+ (let ((start (point)))
+ (funcall goto-start-func)
+
+ ;; I trap the error
+ (condition-case nil
+ (funcall orig count)
+ (error nil))
+
+ (when (not (looking-at header-re))
+ (goto-char start)
+ (user-error (format "No %s" what))))))
+
+(defun diff-hunk-prev (&optional count)
+ "Go to the previous COUNT'th hunk"
+ (interactive "p")
+ (diff--wrap-navigation
+ (called-interactively-p 'interactive)
+ "prev hunk"
+ 'diff--internal-hunk-prev
+ diff-hunk-header-re
+ (lambda () (goto-char (car (diff-bounds-of-hunk))))
+ count))
+
+(defun diff-hunk-next (&optional count)
+ "Go to the next COUNT'th hunk"
+ (interactive "p")
+ (diff--wrap-navigation
+ (called-interactively-p 'interactive)
+ "next hunk"
+ 'diff--internal-hunk-next
+ diff-hunk-header-re
+ (lambda () (goto-char (car (diff-bounds-of-hunk))))
+ count))
+
+(defun diff-file-prev (&optional count)
+ "Go to the previous COUNT'th file"
+ (interactive "p")
+ (diff--wrap-navigation
+ (called-interactively-p 'interactive)
+ "prev file"
+ 'diff--internal-file-prev
+ diff-file-header-re
+ (lambda () (goto-char (car (diff-bounds-of-file))) (diff--internal-hunk-next))
+ count))
+
+(defun diff-file-next (&optional count)
+ "Go to the next COUNT'th file"
+ (interactive "p")
+ (diff--wrap-navigation
+ (called-interactively-p 'interactive)
+ "next file"
+ 'diff--internal-file-next
+ diff-file-header-re
+ (lambda () (goto-char (car (diff-bounds-of-file))) (diff--internal-hunk-next))
+ count))
+
+
+
(defun diff-bounds-of-hunk ()
"Return the bounds of the diff hunk at point.
@@ -581,12 +662,13 @@ point is in a file header, return the bounds of the next hunk."
(let ((pos (point))
(beg (diff-beginning-of-hunk t))
(end (diff-end-of-hunk)))
- (cond ((>= end pos)
+ (cond ((> end pos)
(list beg end))
;; If this hunk ends above POS, consider the next hunk.
((re-search-forward diff-hunk-header-re nil t)
(list (match-beginning 0) (diff-end-of-hunk)))
- (t (error "No hunk found"))))))
+ ;; There's no next hunk, so just take the one we have
+ (t (list beg end))))))
(defun diff-bounds-of-file ()
"Return the bounds of the file segment at point.
@@ -1665,8 +1747,9 @@ SRC and DST are the two variants of text as returned by `diff-hunk-text'.
SWITCHED is non-nil if the patch is already applied.
NOPROMPT, if non-nil, means not to prompt the user."
(save-excursion
- (let* ((other (diff-xor other-file diff-jump-to-old-file))
- (char-offset (- (point) (diff-beginning-of-hunk t)))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (other (diff-xor other-file diff-jump-to-old-file))
+ (char-offset (- (point) (goto-char (car hunk-bounds))))
;; Check that the hunk is well-formed. Otherwise diff-mode and
;; the user may disagree on what constitutes the hunk
;; (e.g. because an empty line truncates the hunk mid-course),
@@ -1675,7 +1758,7 @@ NOPROMPT, if non-nil, means not to prompt the user."
;; Suppress check when NOPROMPT is non-nil (Bug#3033).
(_ (unless noprompt (diff-sanity-check-hunk)))
(hunk (buffer-substring
- (point) (save-excursion (diff-end-of-hunk) (point))))
+ (point) (cadr hunk-bounds)))
(old (diff-hunk-text hunk reverse char-offset))
(new (diff-hunk-text hunk (not reverse) char-offset))
;; Find the location specification.
@@ -1784,6 +1867,8 @@ With a prefix argument, REVERSE the hunk."
(set-window-point (display-buffer buf) (+ (car pos) (cdr new)))
(diff-hunk-status-msg line-offset (diff-xor switched reverse) nil)
(when diff-advance-after-apply-hunk
+ ;; old option:
+ ;; (call-interactively 'diff-hunk-next))))))
(diff-hunk-next))))))
@@ -1865,14 +1950,15 @@ For use in `add-log-current-defun-function'."
(defun diff-ignore-whitespace-hunk ()
"Re-diff the current hunk, ignoring whitespace differences."
(interactive)
- (let* ((char-offset (- (point) (diff-beginning-of-hunk t)))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (char-offset (- (point) (goto-char (car hunk-bounds))))
(opts (pcase (char-after) (?@ "-bu") (?* "-bc") (_ "-b")))
(line-nb (and (or (looking-at "[^0-9]+\\([0-9]+\\)")
(error "Can't find line number"))
(string-to-number (match-string 1))))
(inhibit-read-only t)
(hunk (delete-and-extract-region
- (point) (save-excursion (diff-end-of-hunk) (point))))
+ (point) (cadr hunk-bounds)))
(lead (make-string (1- line-nb) ?\n)) ;Line nums start at 1.
(file1 (make-temp-file "diff1"))
(file2 (make-temp-file "diff2"))
@@ -1959,8 +2045,8 @@ For use in `add-log-current-defun-function'."
(interactive)
(require 'smerge-mode)
(save-excursion
- (diff-beginning-of-hunk t)
- (let* ((start (point))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (start (goto-char (car hunk-bounds)))
(style (diff-hunk-style)) ;Skips the hunk header as well.
(beg (point))
(props-c '((diff-mode . fine) (face diff-refine-changed)))
@@ -1968,7 +2054,7 @@ For use in `add-log-current-defun-function'."
(props-a '((diff-mode . fine) (face diff-refine-added)))
;; Be careful to go back to `start' so diff-end-of-hunk gets
;; to read the hunk header's line info.
- (end (progn (goto-char start) (diff-end-of-hunk) (point))))
+ (end (goto-char (cadr hunk-bounds))))
(remove-overlays beg end 'diff-mode 'fine)
--
2.8.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-09-14 22:31 ` Dima Kogan
@ 2016-09-23 7:22 ` Dima Kogan
2016-10-22 15:47 ` npostavs
0 siblings, 1 reply; 24+ messages in thread
From: Dima Kogan @ 2016-09-23 7:22 UTC (permalink / raw)
To: npostavs; +Cc: Andreas Schwab, 17544
[-- Attachment #1: Type: text/plain, Size: 446 bytes --]
Here's yet another revised patch. The (call-interactively) at the end of
(diff-apply-hunk) was important, it turns out. This would force it to
use the new logic to move to the next hunk, instead of the legacy logic.
I purposely left the behavior of (diff-next-hunk) unchanged from before
when running non-interactively, and here I explicitly want the new
behavior.
This patch puts the (call-interactively) back in, and explains the
rationale.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Improved-diff-mode-navigation-manipulation.patch --]
[-- Type: text/x-diff, Size: 9162 bytes --]
From 21964781a87b615cdab65ab2643deaac8dbaa406 Mon Sep 17 00:00:00 2001
From: Dima Kogan <Dmitriy.Kogan@jpl.nasa.gov>
Date: Wed, 14 Sep 2016 15:25:06 -0700
Subject: [PATCH] Improved diff-mode navigation/manipulation
Navigation and use of diff buffers had several annoying corner cases that this
patch fixes. These corner cases were largely due to inconsistent treatment of
file headers. Say you have a diff such as this:
--- aaa
+++ bbb
@@ -52,7 +52,7 @@
hunk1
@@ -74,7 +74,7 @@
hunk2
--- ccc
+++ ddd
@@ -608,6 +608,6 @@
hunk3
@@ -654,7 +654,7 @@
hunk4
The file headers here are the '---' and '+++' lines. With the point on such a
line, hunk operations would sometimes refer to the next hunk and sometimes to
the previous hunk. Most of the time it would be the previous hunk, which is
not what the user would expect. This patch consistently treats such headers as
the next hunk. So with this patch, if the point is on the '--- ccc' line, the
point is seen as referring to hunk3.
Specific behaviors this fixes are:
1. It should be possible to place the point in the middle of a diff buffer,
and press M-k repeatedly to kill hunks in the order they appear in the buffer.
With the point on hunk1, M-k M-k would kill hunk1 then hunk2. With the point
on hunk3, it would kill hunk3 then hunk4; this is fine. However, with the
point on hunk2, it'd kill hunk2 then hunk1. This is fixed by this patch.
2. Similarly, it should be possible to apply hunks in order. Previously with
the point at the start, C-c C-a would apply the hunk1, then move the point to
the first @@ header, and thus C-c C-a would try to apply the same hunk again.
---
lisp/vc/diff-mode.el | 115 +++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 102 insertions(+), 13 deletions(-)
diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 58498cb..b37ceec 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -551,7 +551,7 @@ diff--auto-refine-data
;; Define diff-{hunk,file}-{prev,next}
(easy-mmode-define-navigation
- diff-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
+ diff--internal-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
(when diff-auto-refine-mode
(unless (prog1 diff--auto-refine-data
(setq diff--auto-refine-data
@@ -570,7 +570,88 @@ diff--auto-refine-data
(diff-refine-hunk))))))))))))
(easy-mmode-define-navigation
- diff-file diff-file-header-re "file" diff-end-of-file)
+ diff--internal-file diff-file-header-re "file" diff-end-of-file)
+
+(defun diff--wrap-navigation (isinteractive
+ what orig
+ header-re goto-start-func count)
+ "I override the default diff-{hunk,file}-{next,prev}
+implementation by skipping any lines that are associated with
+this hunk/file but precede the hunk-start marker. For instance, a
+diff file could contain
+
+diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
+index 923de9a..6b1c24f 100644
+--- a/lisp/vc/diff-mode.el
++++ b/lisp/vc/diff-mode.el
+@@ -590,6 +590,22 @@
+.......
+
+If a point is on 'index', then the point is considered to be in
+this first hunk. Here I move the point to the @@... marker before
+executing the default diff-hunk-next/prev implementation to move
+to the NEXT marker"
+ (if (not isinteractive)
+ (funcall orig count)
+
+ (let ((start (point)))
+ (funcall goto-start-func)
+
+ ;; I trap the error
+ (condition-case nil
+ (funcall orig count)
+ (error nil))
+
+ (when (not (looking-at header-re))
+ (goto-char start)
+ (user-error (format "No %s" what))))))
+
+(defun diff-hunk-prev (&optional count)
+ "Go to the previous COUNT'th hunk"
+ (interactive "p")
+ (diff--wrap-navigation
+ (called-interactively-p 'interactive)
+ "prev hunk"
+ 'diff--internal-hunk-prev
+ diff-hunk-header-re
+ (lambda () (goto-char (car (diff-bounds-of-hunk))))
+ count))
+
+(defun diff-hunk-next (&optional count)
+ "Go to the next COUNT'th hunk"
+ (interactive "p")
+ (diff--wrap-navigation
+ (called-interactively-p 'interactive)
+ "next hunk"
+ 'diff--internal-hunk-next
+ diff-hunk-header-re
+ (lambda () (goto-char (car (diff-bounds-of-hunk))))
+ count))
+
+(defun diff-file-prev (&optional count)
+ "Go to the previous COUNT'th file"
+ (interactive "p")
+ (diff--wrap-navigation
+ (called-interactively-p 'interactive)
+ "prev file"
+ 'diff--internal-file-prev
+ diff-file-header-re
+ (lambda () (goto-char (car (diff-bounds-of-file))) (diff--internal-hunk-next))
+ count))
+
+(defun diff-file-next (&optional count)
+ "Go to the next COUNT'th file"
+ (interactive "p")
+ (diff--wrap-navigation
+ (called-interactively-p 'interactive)
+ "next file"
+ 'diff--internal-file-next
+ diff-file-header-re
+ (lambda () (goto-char (car (diff-bounds-of-file))) (diff--internal-hunk-next))
+ count))
+
+
+
(defun diff-bounds-of-hunk ()
"Return the bounds of the diff hunk at point.
@@ -581,12 +662,13 @@ diff-bounds-of-hunk
(let ((pos (point))
(beg (diff-beginning-of-hunk t))
(end (diff-end-of-hunk)))
- (cond ((>= end pos)
+ (cond ((> end pos)
(list beg end))
;; If this hunk ends above POS, consider the next hunk.
((re-search-forward diff-hunk-header-re nil t)
(list (match-beginning 0) (diff-end-of-hunk)))
- (t (error "No hunk found"))))))
+ ;; There's no next hunk, so just take the one we have
+ (t (list beg end))))))
(defun diff-bounds-of-file ()
"Return the bounds of the file segment at point.
@@ -1665,8 +1747,9 @@ diff-find-source-location
SWITCHED is non-nil if the patch is already applied.
NOPROMPT, if non-nil, means not to prompt the user."
(save-excursion
- (let* ((other (diff-xor other-file diff-jump-to-old-file))
- (char-offset (- (point) (diff-beginning-of-hunk t)))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (other (diff-xor other-file diff-jump-to-old-file))
+ (char-offset (- (point) (goto-char (car hunk-bounds))))
;; Check that the hunk is well-formed. Otherwise diff-mode and
;; the user may disagree on what constitutes the hunk
;; (e.g. because an empty line truncates the hunk mid-course),
@@ -1675,7 +1758,7 @@ diff-find-source-location
;; Suppress check when NOPROMPT is non-nil (Bug#3033).
(_ (unless noprompt (diff-sanity-check-hunk)))
(hunk (buffer-substring
- (point) (save-excursion (diff-end-of-hunk) (point))))
+ (point) (cadr hunk-bounds)))
(old (diff-hunk-text hunk reverse char-offset))
(new (diff-hunk-text hunk (not reverse) char-offset))
;; Find the location specification.
@@ -1783,8 +1866,13 @@ diff-apply-hunk
;; Display BUF in a window
(set-window-point (display-buffer buf) (+ (car pos) (cdr new)))
(diff-hunk-status-msg line-offset (diff-xor switched reverse) nil)
+
+ ;; I advance to the next hunk interactively because I want the
+ ;; interactive behavior of moving to the next logical hunk, not
+ ;; the legacy behavior where were would sometimes sty on the
+ ;; curent hunk. See http://debbugs.gnu.org/17544
(when diff-advance-after-apply-hunk
- (diff-hunk-next))))))
+ (call-interactively 'diff-hunk-next))))))
(defun diff-test-hunk (&optional reverse)
@@ -1865,14 +1953,15 @@ diff-current-defun
(defun diff-ignore-whitespace-hunk ()
"Re-diff the current hunk, ignoring whitespace differences."
(interactive)
- (let* ((char-offset (- (point) (diff-beginning-of-hunk t)))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (char-offset (- (point) (goto-char (car hunk-bounds))))
(opts (pcase (char-after) (?@ "-bu") (?* "-bc") (_ "-b")))
(line-nb (and (or (looking-at "[^0-9]+\\([0-9]+\\)")
(error "Can't find line number"))
(string-to-number (match-string 1))))
(inhibit-read-only t)
(hunk (delete-and-extract-region
- (point) (save-excursion (diff-end-of-hunk) (point))))
+ (point) (cadr hunk-bounds)))
(lead (make-string (1- line-nb) ?\n)) ;Line nums start at 1.
(file1 (make-temp-file "diff1"))
(file2 (make-temp-file "diff2"))
@@ -1959,8 +2048,8 @@ diff-refine-hunk
(interactive)
(require 'smerge-mode)
(save-excursion
- (diff-beginning-of-hunk t)
- (let* ((start (point))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (start (goto-char (car hunk-bounds)))
(style (diff-hunk-style)) ;Skips the hunk header as well.
(beg (point))
(props-c '((diff-mode . fine) (face diff-refine-changed)))
@@ -1968,7 +2057,7 @@ diff-refine-hunk
(props-a '((diff-mode . fine) (face diff-refine-added)))
;; Be careful to go back to `start' so diff-end-of-hunk gets
;; to read the hunk header's line info.
- (end (progn (goto-char start) (diff-end-of-hunk) (point))))
+ (end (goto-char (cadr hunk-bounds))))
(remove-overlays beg end 'diff-mode 'fine)
--
2.8.0.rc3
^ permalink raw reply related [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-09-23 7:22 ` Dima Kogan
@ 2016-10-22 15:47 ` npostavs
2016-10-23 1:44 ` Dima Kogan
0 siblings, 1 reply; 24+ messages in thread
From: npostavs @ 2016-10-22 15:47 UTC (permalink / raw)
To: Dima Kogan; +Cc: Andreas Schwab, 17544
Dima Kogan <dima@secretsauce.net> writes:
> Here's yet another revised patch. The (call-interactively) at the end of
> (diff-apply-hunk) was important, it turns out. This would force it to
> use the new logic to move to the next hunk, instead of the legacy logic.
> I purposely left the behavior of (diff-next-hunk) unchanged from before
> when running non-interactively, and here I explicitly want the new
> behavior.
If both behaviours are needed, it would be much better if lisp code
could choose between them without having to use call-interactively,
that's quite an awkward interface.
^ permalink raw reply [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-10-22 15:47 ` npostavs
@ 2016-10-23 1:44 ` Dima Kogan
2016-10-23 2:49 ` npostavs
0 siblings, 1 reply; 24+ messages in thread
From: Dima Kogan @ 2016-10-23 1:44 UTC (permalink / raw)
To: npostavs; +Cc: Andreas Schwab, 17544
npostavs@users.sourceforge.net writes:
> Dima Kogan <dima@secretsauce.net> writes:
>
>> Here's yet another revised patch. The (call-interactively) at the end of
>> (diff-apply-hunk) was important, it turns out. This would force it to
>> use the new logic to move to the next hunk, instead of the legacy logic.
>> I purposely left the behavior of (diff-next-hunk) unchanged from before
>> when running non-interactively, and here I explicitly want the new
>> behavior.
>
> If both behaviours are needed, it would be much better if lisp code
> could choose between them without having to use call-interactively,
> that's quite an awkward interface.
Hi. I'm open to suggestions. The goal was to retain the previous logic
for any existing code, but to provide improved user-facing behavior.
Given this, it doesn't seem to me to be too awkward to pass-on the
"interactive-p" state to child functions. Am I wrong to want to preserve
existing behavior for elisp code? If so, then the entire old path can
simply go away unconditionally.
^ permalink raw reply [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-10-23 1:44 ` Dima Kogan
@ 2016-10-23 2:49 ` npostavs
2016-11-07 2:26 ` Dima Kogan
0 siblings, 1 reply; 24+ messages in thread
From: npostavs @ 2016-10-23 2:49 UTC (permalink / raw)
To: Dima Kogan; +Cc: Andreas Schwab, 17544
Dima Kogan <dima@secretsauce.net> writes:
> npostavs@users.sourceforge.net writes:
>
>> Dima Kogan <dima@secretsauce.net> writes:
>>
>>> Here's yet another revised patch. The (call-interactively) at the end of
>>> (diff-apply-hunk) was important, it turns out. This would force it to
>>> use the new logic to move to the next hunk, instead of the legacy logic.
>>> I purposely left the behavior of (diff-next-hunk) unchanged from before
>>> when running non-interactively, and here I explicitly want the new
>>> behavior.
>>
>> If both behaviours are needed, it would be much better if lisp code
>> could choose between them without having to use call-interactively,
>> that's quite an awkward interface.
>
> Hi. I'm open to suggestions. The goal was to retain the previous logic
> for any existing code, but to provide improved user-facing behavior.
> Given this, it doesn't seem to me to be too awkward to pass-on the
> "interactive-p" state to child functions.
>
But you're synthesizing interactiveness in diff-apply-hunk, so it looks
like interactiveness is not what you actually care about. You could do
something like this instead:
(defun diff-hunk-next (&optional count skip-hunk-start)
"Go to the next COUNT'th hunk"
(interactive (list (prefix-numeric-value current-prefix-arg) t))
(diff--wrap-navigation
skip-hunk-start
"next hunk"
'diff--internal-hunk-next
diff-hunk-header-re
(lambda () (goto-char (car (diff-bounds-of-hunk))))
count))
Then instead of (call-interactively 'diff-hunk-next) you can just do
(diff-hunk-next nil t)
> Am I wrong to want to preserve
> existing behavior for elisp code? If so, then the entire old path can
> simply go away unconditionally.
Maybe. What about the other calls to diff-hunk-next? Was there a
reason you kept them the same?
^ permalink raw reply [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-10-23 2:49 ` npostavs
@ 2016-11-07 2:26 ` Dima Kogan
2016-11-15 3:31 ` npostavs
0 siblings, 1 reply; 24+ messages in thread
From: Dima Kogan @ 2016-11-07 2:26 UTC (permalink / raw)
To: npostavs; +Cc: Andreas Schwab, 17544
[-- Attachment #1: Type: text/plain, Size: 1380 bytes --]
npostavs@users.sourceforge.net writes:
> Dima Kogan <dima@secretsauce.net> writes:
>
>> Hi. I'm open to suggestions. The goal was to retain the previous logic
>> for any existing code, but to provide improved user-facing behavior.
Ack! So this is what happens when you submit a patch, and then talk
about it more than 2 years later. Turns out preserving compatibility
with existing code wasn't my goal at all. Rather, the goal was to avoid
an infinite loop that results if (diff-hunk-next) unconditionally uses
the new logic:
diff-hunk-next calls
diff--wrap-navigation calls
diff-bounds-of-hunk calls
diff-beginning-of-hunk calls
diff-hunk-next
> it looks like interactiveness is not what you actually care about.
> You could do something like this instead:
>
> (defun diff-hunk-next (&optional count skip-hunk-start)
> "Go to the next COUNT'th hunk"
> (interactive (list (prefix-numeric-value current-prefix-arg) t))
> (diff--wrap-navigation
> skip-hunk-start
> "next hunk"
> 'diff--internal-hunk-next
> diff-hunk-header-re
> (lambda () (goto-char (car (diff-bounds-of-hunk))))
> count))
>
> Then instead of (call-interactively 'diff-hunk-next) you can just do
> (diff-hunk-next nil t)
Yes. Agreed. Revised patch attached. I'll try to respond to any comments
quickly so that we all can remember what this is about.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Improved-diff-mode-navigation-manipulation.patch --]
[-- Type: text/x-diff, Size: 9313 bytes --]
From 4cd4f839dc840493ca14ff13a4f27cedca12a562 Mon Sep 17 00:00:00 2001
From: Dima Kogan <Dmitriy.Kogan@jpl.nasa.gov>
Date: Wed, 14 Sep 2016 15:25:06 -0700
Subject: [PATCH] Improved diff-mode navigation/manipulation
Navigation and use of diff buffers had several annoying corner cases that this
patch fixes. These corner cases were largely due to inconsistent treatment of
file headers. Say you have a diff such as this:
--- aaa
+++ bbb
@@ -52,7 +52,7 @@
hunk1
@@ -74,7 +74,7 @@
hunk2
--- ccc
+++ ddd
@@ -608,6 +608,6 @@
hunk3
@@ -654,7 +654,7 @@
hunk4
The file headers here are the '---' and '+++' lines. With the point on such a
line, hunk operations would sometimes refer to the next hunk and sometimes to
the previous hunk. Most of the time it would be the previous hunk, which is
not what the user would expect. This patch consistently treats such headers as
the next hunk. So with this patch, if the point is on the '--- ccc' line, the
point is seen as referring to hunk3.
Specific behaviors this fixes are:
1. It should be possible to place the point in the middle of a diff buffer,
and press M-k repeatedly to kill hunks in the order they appear in the buffer.
With the point on hunk1, M-k M-k would kill hunk1 then hunk2. With the point
on hunk3, it would kill hunk3 then hunk4; this is fine. However, with the
point on hunk2, it'd kill hunk2 then hunk1. This is fixed by this patch.
2. Similarly, it should be possible to apply hunks in order. Previously with
the point at the start, C-c C-a would apply the hunk1, then move the point to
the first @@ header, and thus C-c C-a would try to apply the same hunk again.
---
lisp/vc/diff-mode.el | 115 +++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 102 insertions(+), 13 deletions(-)
diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 58498cb..16d3f46 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -551,7 +551,7 @@ diff--auto-refine-data
;; Define diff-{hunk,file}-{prev,next}
(easy-mmode-define-navigation
- diff-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
+ diff--internal-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
(when diff-auto-refine-mode
(unless (prog1 diff--auto-refine-data
(setq diff--auto-refine-data
@@ -570,7 +570,88 @@ diff--auto-refine-data
(diff-refine-hunk))))))))))))
(easy-mmode-define-navigation
- diff-file diff-file-header-re "file" diff-end-of-file)
+ diff--internal-file diff-file-header-re "file" diff-end-of-file)
+
+(defun diff--wrap-navigation (skip-hunk-start
+ what orig
+ header-re goto-start-func count)
+ "I override the default diff-{hunk,file}-{next,prev}
+implementation by skipping any lines that are associated with
+this hunk/file but precede the hunk-start marker. For instance, a
+diff file could contain
+
+diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
+index 923de9a..6b1c24f 100644
+--- a/lisp/vc/diff-mode.el
++++ b/lisp/vc/diff-mode.el
+@@ -590,6 +590,22 @@
+.......
+
+If a point is on 'index', then the point is considered to be in
+this first hunk. Here I move the point to the @@... marker before
+executing the default diff-hunk-next/prev implementation to move
+to the NEXT marker"
+ (if (not skip-hunk-start)
+ (funcall orig count)
+
+ (let ((start (point)))
+ (funcall goto-start-func)
+
+ ;; I trap the error
+ (condition-case nil
+ (funcall orig count)
+ (error nil))
+
+ (when (not (looking-at header-re))
+ (goto-char start)
+ (user-error (format "No %s" what))))))
+
+(defun diff-hunk-prev (&optional count skip-hunk-start)
+ "Go to the previous COUNT'th hunk"
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "prev hunk"
+ 'diff--internal-hunk-prev
+ diff-hunk-header-re
+ (lambda () (goto-char (car (diff-bounds-of-hunk))))
+ count))
+
+(defun diff-hunk-next (&optional count skip-hunk-start)
+ "Go to the next COUNT'th hunk"
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "next hunk"
+ 'diff--internal-hunk-next
+ diff-hunk-header-re
+ (lambda () (goto-char (car (diff-bounds-of-hunk))))
+ count))
+
+(defun diff-file-prev (&optional count skip-hunk-start)
+ "Go to the previous COUNT'th file"
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "prev file"
+ 'diff--internal-file-prev
+ diff-file-header-re
+ (lambda () (goto-char (car (diff-bounds-of-file))) (diff--internal-hunk-next))
+ count))
+
+(defun diff-file-next (&optional count skip-hunk-start)
+ "Go to the next COUNT'th file"
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "next file"
+ 'diff--internal-file-next
+ diff-file-header-re
+ (lambda () (goto-char (car (diff-bounds-of-file))) (diff--internal-hunk-next))
+ count))
+
+
+
(defun diff-bounds-of-hunk ()
"Return the bounds of the diff hunk at point.
@@ -581,12 +662,13 @@ diff-bounds-of-hunk
(let ((pos (point))
(beg (diff-beginning-of-hunk t))
(end (diff-end-of-hunk)))
- (cond ((>= end pos)
+ (cond ((> end pos)
(list beg end))
;; If this hunk ends above POS, consider the next hunk.
((re-search-forward diff-hunk-header-re nil t)
(list (match-beginning 0) (diff-end-of-hunk)))
- (t (error "No hunk found"))))))
+ ;; There's no next hunk, so just take the one we have
+ (t (list beg end))))))
(defun diff-bounds-of-file ()
"Return the bounds of the file segment at point.
@@ -1665,8 +1747,9 @@ diff-find-source-location
SWITCHED is non-nil if the patch is already applied.
NOPROMPT, if non-nil, means not to prompt the user."
(save-excursion
- (let* ((other (diff-xor other-file diff-jump-to-old-file))
- (char-offset (- (point) (diff-beginning-of-hunk t)))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (other (diff-xor other-file diff-jump-to-old-file))
+ (char-offset (- (point) (goto-char (car hunk-bounds))))
;; Check that the hunk is well-formed. Otherwise diff-mode and
;; the user may disagree on what constitutes the hunk
;; (e.g. because an empty line truncates the hunk mid-course),
@@ -1675,7 +1758,7 @@ diff-find-source-location
;; Suppress check when NOPROMPT is non-nil (Bug#3033).
(_ (unless noprompt (diff-sanity-check-hunk)))
(hunk (buffer-substring
- (point) (save-excursion (diff-end-of-hunk) (point))))
+ (point) (cadr hunk-bounds)))
(old (diff-hunk-text hunk reverse char-offset))
(new (diff-hunk-text hunk (not reverse) char-offset))
;; Find the location specification.
@@ -1783,8 +1866,13 @@ diff-apply-hunk
;; Display BUF in a window
(set-window-point (display-buffer buf) (+ (car pos) (cdr new)))
(diff-hunk-status-msg line-offset (diff-xor switched reverse) nil)
+
+ ;; I advance to the next hunk interactively because I want the
+ ;; interactive behavior of moving to the next logical hunk, not
+ ;; the legacy behavior where were would sometimes sty on the
+ ;; curent hunk. See http://debbugs.gnu.org/17544
(when diff-advance-after-apply-hunk
- (diff-hunk-next))))))
+ (diff-hunk-next nil t))))))
(defun diff-test-hunk (&optional reverse)
@@ -1865,14 +1953,15 @@ diff-current-defun
(defun diff-ignore-whitespace-hunk ()
"Re-diff the current hunk, ignoring whitespace differences."
(interactive)
- (let* ((char-offset (- (point) (diff-beginning-of-hunk t)))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (char-offset (- (point) (goto-char (car hunk-bounds))))
(opts (pcase (char-after) (?@ "-bu") (?* "-bc") (_ "-b")))
(line-nb (and (or (looking-at "[^0-9]+\\([0-9]+\\)")
(error "Can't find line number"))
(string-to-number (match-string 1))))
(inhibit-read-only t)
(hunk (delete-and-extract-region
- (point) (save-excursion (diff-end-of-hunk) (point))))
+ (point) (cadr hunk-bounds)))
(lead (make-string (1- line-nb) ?\n)) ;Line nums start at 1.
(file1 (make-temp-file "diff1"))
(file2 (make-temp-file "diff2"))
@@ -1959,8 +2048,8 @@ diff-refine-hunk
(interactive)
(require 'smerge-mode)
(save-excursion
- (diff-beginning-of-hunk t)
- (let* ((start (point))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (start (goto-char (car hunk-bounds)))
(style (diff-hunk-style)) ;Skips the hunk header as well.
(beg (point))
(props-c '((diff-mode . fine) (face diff-refine-changed)))
@@ -1968,7 +2057,7 @@ diff-refine-hunk
(props-a '((diff-mode . fine) (face diff-refine-added)))
;; Be careful to go back to `start' so diff-end-of-hunk gets
;; to read the hunk header's line info.
- (end (progn (goto-char start) (diff-end-of-hunk) (point))))
+ (end (goto-char (cadr hunk-bounds))))
(remove-overlays beg end 'diff-mode 'fine)
--
2.10.1
^ permalink raw reply related [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-11-07 2:26 ` Dima Kogan
@ 2016-11-15 3:31 ` npostavs
2016-11-17 4:15 ` Dima Kogan
0 siblings, 1 reply; 24+ messages in thread
From: npostavs @ 2016-11-15 3:31 UTC (permalink / raw)
To: Dima Kogan; +Cc: Andreas Schwab, 17544
Dima Kogan <dima@secretsauce.net> writes:
> npostavs@users.sourceforge.net writes:
>
>> Dima Kogan <dima@secretsauce.net> writes:
>>
>>> Hi. I'm open to suggestions. The goal was to retain the previous logic
>>> for any existing code, but to provide improved user-facing behavior.
>
> Ack! So this is what happens when you submit a patch, and then talk
> about it more than 2 years later. Turns out preserving compatibility
> with existing code wasn't my goal at all. Rather, the goal was to avoid
> an infinite loop that results if (diff-hunk-next) unconditionally uses
> the new logic:
>
> diff-hunk-next calls
> diff--wrap-navigation calls
> diff-bounds-of-hunk calls
> diff-beginning-of-hunk calls
> diff-hunk-next
>
> Revised patch attached. I'll try to respond to any comments
> quickly so that we all can remember what this is about.
[...]
> - (t (error "No hunk found"))))))
> + ;; There's no next hunk, so just take the one we have
> + (t (list beg end))))))
Indentation on the comment looks a bit off.
> +
> + ;; I advance to the next hunk interactively because I want the
> + ;; interactive behavior of moving to the next logical hunk, not
> + ;; the legacy behavior where were would sometimes sty on the
> + ;; curent hunk. See http://debbugs.gnu.org/17544
> (when diff-advance-after-apply-hunk
> - (diff-hunk-next))))))
> + (diff-hunk-next nil t))))))
Updating the comment here will be useful for the next person trying to
figure out what this is all about in a couple more years.
> (hunk (delete-and-extract-region
> - (point) (save-excursion (diff-end-of-hunk) (point))))
> + (point) (cadr hunk-bounds)))
Indentation looks off here.
^ permalink raw reply [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-11-15 3:31 ` npostavs
@ 2016-11-17 4:15 ` Dima Kogan
2016-11-17 4:33 ` npostavs
0 siblings, 1 reply; 24+ messages in thread
From: Dima Kogan @ 2016-11-17 4:15 UTC (permalink / raw)
To: npostavs; +Cc: Andreas Schwab, 17544
[-- Attachment #1: Type: text/plain, Size: 21 bytes --]
New patch attached.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Improved-diff-mode-navigation-manipulation.patch --]
[-- Type: text/x-diff, Size: 9441 bytes --]
From b02085abd998d2c1e1519973964295fb5b798e0c Mon Sep 17 00:00:00 2001
From: Dima Kogan <Dmitriy.Kogan@jpl.nasa.gov>
Date: Wed, 14 Sep 2016 15:25:06 -0700
Subject: [PATCH] Improved diff-mode navigation/manipulation
Navigation and use of diff buffers had several annoying corner cases that this
patch fixes. These corner cases were largely due to inconsistent treatment of
file headers. Say you have a diff such as this:
--- aaa
+++ bbb
@@ -52,7 +52,7 @@
hunk1
@@ -74,7 +74,7 @@
hunk2
--- ccc
+++ ddd
@@ -608,6 +608,6 @@
hunk3
@@ -654,7 +654,7 @@
hunk4
The file headers here are the '---' and '+++' lines. With the point on such a
line, hunk operations would sometimes refer to the next hunk and sometimes to
the previous hunk. Most of the time it would be the previous hunk, which is
not what the user would expect. This patch consistently treats such headers as
the next hunk. So with this patch, if the point is on the '--- ccc' line, the
point is seen as referring to hunk3.
Specific behaviors this fixes are:
1. It should be possible to place the point in the middle of a diff buffer,
and press M-k repeatedly to kill hunks in the order they appear in the buffer.
With the point on hunk1, M-k M-k would kill hunk1 then hunk2. With the point
on hunk3, it would kill hunk3 then hunk4; this is fine. However, with the
point on hunk2, it'd kill hunk2 then hunk1. This is fixed by this patch.
2. Similarly, it should be possible to apply hunks in order. Previously with
the point at the start, C-c C-a would apply the hunk1, then move the point to
the first @@ header, and thus C-c C-a would try to apply the same hunk again.
---
lisp/vc/diff-mode.el | 117 +++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 104 insertions(+), 13 deletions(-)
diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 58498cb..4f05b0c 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -551,7 +551,7 @@ diff--auto-refine-data
;; Define diff-{hunk,file}-{prev,next}
(easy-mmode-define-navigation
- diff-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
+ diff--internal-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
(when diff-auto-refine-mode
(unless (prog1 diff--auto-refine-data
(setq diff--auto-refine-data
@@ -570,7 +570,88 @@ diff--auto-refine-data
(diff-refine-hunk))))))))))))
(easy-mmode-define-navigation
- diff-file diff-file-header-re "file" diff-end-of-file)
+ diff--internal-file diff-file-header-re "file" diff-end-of-file)
+
+(defun diff--wrap-navigation (skip-hunk-start
+ what orig
+ header-re goto-start-func count)
+ "I override the default diff-{hunk,file}-{next,prev}
+implementation by skipping any lines that are associated with
+this hunk/file but precede the hunk-start marker. For instance, a
+diff file could contain
+
+diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
+index 923de9a..6b1c24f 100644
+--- a/lisp/vc/diff-mode.el
++++ b/lisp/vc/diff-mode.el
+@@ -590,6 +590,22 @@
+.......
+
+If a point is on 'index', then the point is considered to be in
+this first hunk. Here I move the point to the @@... marker before
+executing the default diff-hunk-next/prev implementation to move
+to the NEXT marker"
+ (if (not skip-hunk-start)
+ (funcall orig count)
+
+ (let ((start (point)))
+ (funcall goto-start-func)
+
+ ;; I trap the error
+ (condition-case nil
+ (funcall orig count)
+ (error nil))
+
+ (when (not (looking-at header-re))
+ (goto-char start)
+ (user-error (format "No %s" what))))))
+
+(defun diff-hunk-prev (&optional count skip-hunk-start)
+ "Go to the previous COUNT'th hunk"
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "prev hunk"
+ 'diff--internal-hunk-prev
+ diff-hunk-header-re
+ (lambda () (goto-char (car (diff-bounds-of-hunk))))
+ count))
+
+(defun diff-hunk-next (&optional count skip-hunk-start)
+ "Go to the next COUNT'th hunk"
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "next hunk"
+ 'diff--internal-hunk-next
+ diff-hunk-header-re
+ (lambda () (goto-char (car (diff-bounds-of-hunk))))
+ count))
+
+(defun diff-file-prev (&optional count skip-hunk-start)
+ "Go to the previous COUNT'th file"
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "prev file"
+ 'diff--internal-file-prev
+ diff-file-header-re
+ (lambda () (goto-char (car (diff-bounds-of-file))) (diff--internal-hunk-next))
+ count))
+
+(defun diff-file-next (&optional count skip-hunk-start)
+ "Go to the next COUNT'th file"
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "next file"
+ 'diff--internal-file-next
+ diff-file-header-re
+ (lambda () (goto-char (car (diff-bounds-of-file))) (diff--internal-hunk-next))
+ count))
+
+
+
(defun diff-bounds-of-hunk ()
"Return the bounds of the diff hunk at point.
@@ -581,12 +662,13 @@ diff-bounds-of-hunk
(let ((pos (point))
(beg (diff-beginning-of-hunk t))
(end (diff-end-of-hunk)))
- (cond ((>= end pos)
+ (cond ((> end pos)
(list beg end))
;; If this hunk ends above POS, consider the next hunk.
((re-search-forward diff-hunk-header-re nil t)
(list (match-beginning 0) (diff-end-of-hunk)))
- (t (error "No hunk found"))))))
+ ;; There's no next hunk, so just take the one we have
+ (t (list beg end))))))
(defun diff-bounds-of-file ()
"Return the bounds of the file segment at point.
@@ -1665,8 +1747,9 @@ diff-find-source-location
SWITCHED is non-nil if the patch is already applied.
NOPROMPT, if non-nil, means not to prompt the user."
(save-excursion
- (let* ((other (diff-xor other-file diff-jump-to-old-file))
- (char-offset (- (point) (diff-beginning-of-hunk t)))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (other (diff-xor other-file diff-jump-to-old-file))
+ (char-offset (- (point) (goto-char (car hunk-bounds))))
;; Check that the hunk is well-formed. Otherwise diff-mode and
;; the user may disagree on what constitutes the hunk
;; (e.g. because an empty line truncates the hunk mid-course),
@@ -1675,7 +1758,7 @@ diff-find-source-location
;; Suppress check when NOPROMPT is non-nil (Bug#3033).
(_ (unless noprompt (diff-sanity-check-hunk)))
(hunk (buffer-substring
- (point) (save-excursion (diff-end-of-hunk) (point))))
+ (point) (cadr hunk-bounds)))
(old (diff-hunk-text hunk reverse char-offset))
(new (diff-hunk-text hunk (not reverse) char-offset))
;; Find the location specification.
@@ -1783,8 +1866,15 @@ diff-apply-hunk
;; Display BUF in a window
(set-window-point (display-buffer buf) (+ (car pos) (cdr new)))
(diff-hunk-status-msg line-offset (diff-xor switched reverse) nil)
+
+ ;; I advance to the next hunk with skip-hunk-start set to t
+ ;; because I want the behavior of moving to the next logical
+ ;; hunk, not the legacy behavior where were would sometimes stay
+ ;; on the curent hunk. This is the behavior we get when
+ ;; navigating through hunks interactively, and we want it when
+ ;; applying hunks too. See http://debbugs.gnu.org/17544
(when diff-advance-after-apply-hunk
- (diff-hunk-next))))))
+ (diff-hunk-next nil t))))))
(defun diff-test-hunk (&optional reverse)
@@ -1865,14 +1955,15 @@ diff-current-defun
(defun diff-ignore-whitespace-hunk ()
"Re-diff the current hunk, ignoring whitespace differences."
(interactive)
- (let* ((char-offset (- (point) (diff-beginning-of-hunk t)))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (char-offset (- (point) (goto-char (car hunk-bounds))))
(opts (pcase (char-after) (?@ "-bu") (?* "-bc") (_ "-b")))
(line-nb (and (or (looking-at "[^0-9]+\\([0-9]+\\)")
(error "Can't find line number"))
(string-to-number (match-string 1))))
(inhibit-read-only t)
(hunk (delete-and-extract-region
- (point) (save-excursion (diff-end-of-hunk) (point))))
+ (point) (cadr hunk-bounds)))
(lead (make-string (1- line-nb) ?\n)) ;Line nums start at 1.
(file1 (make-temp-file "diff1"))
(file2 (make-temp-file "diff2"))
@@ -1959,8 +2050,8 @@ diff-refine-hunk
(interactive)
(require 'smerge-mode)
(save-excursion
- (diff-beginning-of-hunk t)
- (let* ((start (point))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (start (goto-char (car hunk-bounds)))
(style (diff-hunk-style)) ;Skips the hunk header as well.
(beg (point))
(props-c '((diff-mode . fine) (face diff-refine-changed)))
@@ -1968,7 +2059,7 @@ diff-refine-hunk
(props-a '((diff-mode . fine) (face diff-refine-added)))
;; Be careful to go back to `start' so diff-end-of-hunk gets
;; to read the hunk header's line info.
- (end (progn (goto-char start) (diff-end-of-hunk) (point))))
+ (end (goto-char (cadr hunk-bounds))))
(remove-overlays beg end 'diff-mode 'fine)
--
2.8.0.rc3
[-- Attachment #3: Type: text/plain, Size: 1278 bytes --]
npostavs@users.sourceforge.net writes:
> Dima Kogan <dima@secretsauce.net> writes:
>
>> npostavs@users.sourceforge.net writes:
>>
>> Revised patch attached. I'll try to respond to any comments
>> quickly so that we all can remember what this is about.
> [...]
>> - (t (error "No hunk found"))))))
>> + ;; There's no next hunk, so just take the one we have
>> + (t (list beg end))))))
>
> Indentation on the comment looks a bit off.
This file used tabs in some places and spaces in others. Indentation
updated to match.
>> +
>> + ;; I advance to the next hunk interactively because I want the
>> + ;; interactive behavior of moving to the next logical hunk, not
>> + ;; the legacy behavior where were would sometimes sty on the
>> + ;; curent hunk. See http://debbugs.gnu.org/17544
>> (when diff-advance-after-apply-hunk
>> - (diff-hunk-next))))))
>> + (diff-hunk-next nil t))))))
>
> Updating the comment here will be useful for the next person trying to
> figure out what this is all about in a couple more years.
done
>> (hunk (delete-and-extract-region
>> - (point) (save-excursion (diff-end-of-hunk) (point))))
>> + (point) (cadr hunk-bounds)))
>
> Indentation looks off here.
Same as above.
^ permalink raw reply related [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-11-17 4:15 ` Dima Kogan
@ 2016-11-17 4:33 ` npostavs
2016-11-17 8:05 ` Dima Kogan
0 siblings, 1 reply; 24+ messages in thread
From: npostavs @ 2016-11-17 4:33 UTC (permalink / raw)
To: Dima Kogan; +Cc: Andreas Schwab, 17544
Dima Kogan <dima@secretsauce.net> writes:
> New patch attached.
>
[...]
> +
> + ;; I advance to the next hunk with skip-hunk-start set to t
> + ;; because I want the behavior of moving to the next logical
> + ;; hunk, not the legacy behavior where were would sometimes stay
> + ;; on the curent hunk. This is the behavior we get when
> + ;; navigating through hunks interactively, and we want it when
> + ;; applying hunks too. See http://debbugs.gnu.org/17544
> (when diff-advance-after-apply-hunk
> - (diff-hunk-next))))))
> + (diff-hunk-next nil t))))))
Can you mention somewhere about avoiding an infinite loop that you were
talking about before? (that's what I meant when I said to update this
comment, but if it actually makes more sense to mention that somewhere
else, please do so)
Is it really a "legacy" behavior (considering that we *need* the "legacy"
behavior in order to function correctly)?
Also, I believe usual comment style is to use "we" not "I", and you
didn't end the last sentence with a period.
^ permalink raw reply [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-11-17 4:33 ` npostavs
@ 2016-11-17 8:05 ` Dima Kogan
2016-11-20 2:37 ` npostavs
0 siblings, 1 reply; 24+ messages in thread
From: Dima Kogan @ 2016-11-17 8:05 UTC (permalink / raw)
To: npostavs; +Cc: Andreas Schwab, 17544
[-- Attachment #1: Type: text/plain, Size: 20 bytes --]
New patch attached.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Improved-diff-mode-navigation-manipulation.patch --]
[-- Type: text/x-diff, Size: 9992 bytes --]
From 769915004c4eb523d9e39da7ac96ec2174a213f9 Mon Sep 17 00:00:00 2001
From: Dima Kogan <Dmitriy.Kogan@jpl.nasa.gov>
Date: Wed, 14 Sep 2016 15:25:06 -0700
Subject: [PATCH] Improved diff-mode navigation/manipulation
Navigation and use of diff buffers had several annoying corner cases that this
patch fixes. These corner cases were largely due to inconsistent treatment of
file headers. Say you have a diff such as this:
--- aaa
+++ bbb
@@ -52,7 +52,7 @@
hunk1
@@ -74,7 +74,7 @@
hunk2
--- ccc
+++ ddd
@@ -608,6 +608,6 @@
hunk3
@@ -654,7 +654,7 @@
hunk4
The file headers here are the '---' and '+++' lines. With the point on such a
line, hunk operations would sometimes refer to the next hunk and sometimes to
the previous hunk. Most of the time it would be the previous hunk, which is
not what the user would expect. This patch consistently treats such headers as
the next hunk. So with this patch, if the point is on the '--- ccc' line, the
point is seen as referring to hunk3.
Specific behaviors this fixes are:
1. It should be possible to place the point in the middle of a diff buffer,
and press M-k repeatedly to kill hunks in the order they appear in the buffer.
With the point on hunk1, M-k M-k would kill hunk1 then hunk2. With the point
on hunk3, it would kill hunk3 then hunk4; this is fine. However, with the
point on hunk2, it'd kill hunk2 then hunk1. This is fixed by this patch.
2. Similarly, it should be possible to apply hunks in order. Previously with
the point at the start, C-c C-a would apply the hunk1, then move the point to
the first @@ header, and thus C-c C-a would try to apply the same hunk again.
---
lisp/vc/diff-mode.el | 130 +++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 117 insertions(+), 13 deletions(-)
diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 58498cb..2a4c3d9 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -551,7 +551,7 @@ diff--auto-refine-data
;; Define diff-{hunk,file}-{prev,next}
(easy-mmode-define-navigation
- diff-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
+ diff--internal-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
(when diff-auto-refine-mode
(unless (prog1 diff--auto-refine-data
(setq diff--auto-refine-data
@@ -570,7 +570,101 @@ diff--auto-refine-data
(diff-refine-hunk))))))))))))
(easy-mmode-define-navigation
- diff-file diff-file-header-re "file" diff-end-of-file)
+ diff--internal-file diff-file-header-re "file" diff-end-of-file)
+
+(defun diff--wrap-navigation (skip-hunk-start
+ what orig
+ header-re goto-start-func count)
+ "I override the default diff-{hunk,file}-{next,prev}
+implementation by skipping any lines that are associated with
+this hunk/file but precede the hunk-start marker. For instance, a
+diff file could contain
+
+diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
+index 923de9a..6b1c24f 100644
+--- a/lisp/vc/diff-mode.el
++++ b/lisp/vc/diff-mode.el
+@@ -590,6 +590,22 @@
+.......
+
+If a point is on 'index', then the point is considered to be in
+this first hunk. Here I move the point to the @@... marker before
+executing the default diff-hunk-next/prev implementation to move
+to the NEXT marker"
+ (if (not skip-hunk-start)
+ (funcall orig count)
+
+ (let ((start (point)))
+ (funcall goto-start-func)
+
+ ;; I trap the error
+ (condition-case nil
+ (funcall orig count)
+ (error nil))
+
+ (when (not (looking-at header-re))
+ (goto-char start)
+ (user-error (format "No %s" what))))))
+
+;; These functions all take a skip-hunk-start argument which controls
+;; whether we skip pre-hunk-start text or not. In interactive uses we
+;; always want to do this, but the simple behavior is still necessary
+;; to, for example, avoid an infinite loop:
+;;
+;; diff-hunk-next calls
+;; diff--wrap-navigation calls
+;; diff-bounds-of-hunk calls
+;; diff-beginning-of-hunk calls
+;; diff-hunk-next
+;;
+;; Here the outer diff-hunk-next has skip-hunk-start set to t, but the
+;; inner one does not, which breaks the loop
+(defun diff-hunk-prev (&optional count skip-hunk-start)
+ "Go to the previous COUNT'th hunk"
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "prev hunk"
+ 'diff--internal-hunk-prev
+ diff-hunk-header-re
+ (lambda () (goto-char (car (diff-bounds-of-hunk))))
+ count))
+
+(defun diff-hunk-next (&optional count skip-hunk-start)
+ "Go to the next COUNT'th hunk"
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "next hunk"
+ 'diff--internal-hunk-next
+ diff-hunk-header-re
+ (lambda () (goto-char (car (diff-bounds-of-hunk))))
+ count))
+
+(defun diff-file-prev (&optional count skip-hunk-start)
+ "Go to the previous COUNT'th file"
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "prev file"
+ 'diff--internal-file-prev
+ diff-file-header-re
+ (lambda () (goto-char (car (diff-bounds-of-file))) (diff--internal-hunk-next))
+ count))
+
+(defun diff-file-next (&optional count skip-hunk-start)
+ "Go to the next COUNT'th file"
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "next file"
+ 'diff--internal-file-next
+ diff-file-header-re
+ (lambda () (goto-char (car (diff-bounds-of-file))) (diff--internal-hunk-next))
+ count))
+
+
+
(defun diff-bounds-of-hunk ()
"Return the bounds of the diff hunk at point.
@@ -581,12 +675,13 @@ diff-bounds-of-hunk
(let ((pos (point))
(beg (diff-beginning-of-hunk t))
(end (diff-end-of-hunk)))
- (cond ((>= end pos)
+ (cond ((> end pos)
(list beg end))
;; If this hunk ends above POS, consider the next hunk.
((re-search-forward diff-hunk-header-re nil t)
(list (match-beginning 0) (diff-end-of-hunk)))
- (t (error "No hunk found"))))))
+ ;; There's no next hunk, so just take the one we have
+ (t (list beg end))))))
(defun diff-bounds-of-file ()
"Return the bounds of the file segment at point.
@@ -1665,8 +1760,9 @@ diff-find-source-location
SWITCHED is non-nil if the patch is already applied.
NOPROMPT, if non-nil, means not to prompt the user."
(save-excursion
- (let* ((other (diff-xor other-file diff-jump-to-old-file))
- (char-offset (- (point) (diff-beginning-of-hunk t)))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (other (diff-xor other-file diff-jump-to-old-file))
+ (char-offset (- (point) (goto-char (car hunk-bounds))))
;; Check that the hunk is well-formed. Otherwise diff-mode and
;; the user may disagree on what constitutes the hunk
;; (e.g. because an empty line truncates the hunk mid-course),
@@ -1675,7 +1771,7 @@ diff-find-source-location
;; Suppress check when NOPROMPT is non-nil (Bug#3033).
(_ (unless noprompt (diff-sanity-check-hunk)))
(hunk (buffer-substring
- (point) (save-excursion (diff-end-of-hunk) (point))))
+ (point) (cadr hunk-bounds)))
(old (diff-hunk-text hunk reverse char-offset))
(new (diff-hunk-text hunk (not reverse) char-offset))
;; Find the location specification.
@@ -1783,8 +1879,15 @@ diff-apply-hunk
;; Display BUF in a window
(set-window-point (display-buffer buf) (+ (car pos) (cdr new)))
(diff-hunk-status-msg line-offset (diff-xor switched reverse) nil)
+
+ ;; We advance to the next hunk with skip-hunk-start set to t
+ ;; because we want the behavior of moving to the next logical
+ ;; hunk, not the original behavior where were would sometimes
+ ;; stay on the curent hunk. This is the behavior we get when
+ ;; navigating through hunks interactively, and we want it when
+ ;; applying hunks too. See http://debbugs.gnu.org/17544
(when diff-advance-after-apply-hunk
- (diff-hunk-next))))))
+ (diff-hunk-next nil t))))))
(defun diff-test-hunk (&optional reverse)
@@ -1865,14 +1968,15 @@ diff-current-defun
(defun diff-ignore-whitespace-hunk ()
"Re-diff the current hunk, ignoring whitespace differences."
(interactive)
- (let* ((char-offset (- (point) (diff-beginning-of-hunk t)))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (char-offset (- (point) (goto-char (car hunk-bounds))))
(opts (pcase (char-after) (?@ "-bu") (?* "-bc") (_ "-b")))
(line-nb (and (or (looking-at "[^0-9]+\\([0-9]+\\)")
(error "Can't find line number"))
(string-to-number (match-string 1))))
(inhibit-read-only t)
(hunk (delete-and-extract-region
- (point) (save-excursion (diff-end-of-hunk) (point))))
+ (point) (cadr hunk-bounds)))
(lead (make-string (1- line-nb) ?\n)) ;Line nums start at 1.
(file1 (make-temp-file "diff1"))
(file2 (make-temp-file "diff2"))
@@ -1959,8 +2063,8 @@ diff-refine-hunk
(interactive)
(require 'smerge-mode)
(save-excursion
- (diff-beginning-of-hunk t)
- (let* ((start (point))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (start (goto-char (car hunk-bounds)))
(style (diff-hunk-style)) ;Skips the hunk header as well.
(beg (point))
(props-c '((diff-mode . fine) (face diff-refine-changed)))
@@ -1968,7 +2072,7 @@ diff-refine-hunk
(props-a '((diff-mode . fine) (face diff-refine-added)))
;; Be careful to go back to `start' so diff-end-of-hunk gets
;; to read the hunk header's line info.
- (end (progn (goto-char start) (diff-end-of-hunk) (point))))
+ (end (goto-char (cadr hunk-bounds))))
(remove-overlays beg end 'diff-mode 'fine)
--
2.8.0.rc3
[-- Attachment #3: Type: text/plain, Size: 667 bytes --]
npostavs@users.sourceforge.net writes:
> Can you mention somewhere about avoiding an infinite loop that you were
> talking about before? (that's what I meant when I said to update this
> comment, but if it actually makes more sense to mention that somewhere
> else, please do so)
> Is it really a "legacy" behavior (considering that we *need* the "legacy"
> behavior in order to function correctly)?
Added comment, changed nomenclature.
> Also, I believe usual comment style is to use "we" not "I", and you
> didn't end the last sentence with a period.
Changed the I/we. I'm omitting the trailing . on purpose to make sure it
doesn't get confused with the URL.
^ permalink raw reply related [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-11-17 8:05 ` Dima Kogan
@ 2016-11-20 2:37 ` npostavs
2016-11-21 7:23 ` Dima Kogan
0 siblings, 1 reply; 24+ messages in thread
From: npostavs @ 2016-11-20 2:37 UTC (permalink / raw)
To: Dima Kogan; +Cc: Andreas Schwab, 17544
Dima Kogan <dima@secretsauce.net> writes:
> +(defun diff--wrap-navigation (skip-hunk-start
> + what orig
> + header-re goto-start-func count)
> + "I override the default diff-{hunk,file}-{next,prev}
> +implementation by skipping any lines that are associated with
The first line of a docstring should be a single sentence.
> +this hunk/file but precede the hunk-start marker. For instance, a
> +If a point is on 'index', then the point is considered to be in
> +this first hunk. Here I move the point to the @@... marker before
Double space end of sentence. No need to mention "I" here, it should be
phrased imperatively, as in "Override the default...", and "Move the point...".
> +executing the default diff-hunk-next/prev implementation to move
> +to the NEXT marker"
End sentences with a period.
> + ;; I trap the error
End sentences with a period, no need to mention "I".
> +;; Here the outer diff-hunk-next has skip-hunk-start set to t, but the
> +;; inner one does not, which breaks the loop
> +(defun diff-hunk-prev (&optional count skip-hunk-start)
> + "Go to the previous COUNT'th hunk"
> +(defun diff-hunk-next (&optional count skip-hunk-start)
> + "Go to the next COUNT'th hunk"
> +(defun diff-file-prev (&optional count skip-hunk-start)
> + "Go to the previous COUNT'th file"
> +(defun diff-file-next (&optional count skip-hunk-start)
> + "Go to the next COUNT'th file"
> + ;; There's no next hunk, so just take the one we have
End sentences with a period.
> + (t (list beg end))))))
> + ;; applying hunks too. See http://debbugs.gnu.org/17544
>> Also, I believe usual comment style is to use "we" not "I", and you
>> didn't end the last sentence with a period.
>
> Changed the I/we. I'm omitting the trailing . on purpose to make sure it
> doesn't get confused with the URL.
I suggest ";; applying hunks too (see http://debbugs.gnu.org/17544).",
referencing the bug with "Bug #17544" instead of a full URL could also
work.
^ permalink raw reply [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-11-20 2:37 ` npostavs
@ 2016-11-21 7:23 ` Dima Kogan
2016-11-23 0:42 ` npostavs
0 siblings, 1 reply; 24+ messages in thread
From: Dima Kogan @ 2016-11-21 7:23 UTC (permalink / raw)
To: npostavs; +Cc: Andreas Schwab, 17544
[-- Attachment #1: Type: text/plain, Size: 461 bytes --]
npostavs@users.sourceforge.net writes:
> The first line of a docstring should be a single sentence.
>
> Double space end of sentence. No need to mention "I" here, it should be
> phrased imperatively, as in "Override the default...", and "Move the point...".
>
> End sentences with a period.
>
> I suggest ";; applying hunks too (see http://debbugs.gnu.org/17544).",
> referencing the bug with "Bug #17544" instead of a full URL could also
> work.
Attached.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Improved-diff-mode-navigation-manipulation.patch --]
[-- Type: text/x-diff, Size: 10046 bytes --]
From d954bf87907ed2e3f94347c2249b305c7e231832 Mon Sep 17 00:00:00 2001
From: Dima Kogan <Dmitriy.Kogan@jpl.nasa.gov>
Date: Wed, 14 Sep 2016 15:25:06 -0700
Subject: [PATCH] Improved diff-mode navigation/manipulation
Navigation and use of diff buffers had several annoying corner cases that this
patch fixes. These corner cases were largely due to inconsistent treatment of
file headers. Say you have a diff such as this:
--- aaa
+++ bbb
@@ -52,7 +52,7 @@
hunk1
@@ -74,7 +74,7 @@
hunk2
--- ccc
+++ ddd
@@ -608,6 +608,6 @@
hunk3
@@ -654,7 +654,7 @@
hunk4
The file headers here are the '---' and '+++' lines. With the point on such a
line, hunk operations would sometimes refer to the next hunk and sometimes to
the previous hunk. Most of the time it would be the previous hunk, which is
not what the user would expect. This patch consistently treats such headers as
the next hunk. So with this patch, if the point is on the '--- ccc' line, the
point is seen as referring to hunk3.
Specific behaviors this fixes are:
1. It should be possible to place the point in the middle of a diff buffer,
and press M-k repeatedly to kill hunks in the order they appear in the buffer.
With the point on hunk1, M-k M-k would kill hunk1 then hunk2. With the point
on hunk3, it would kill hunk3 then hunk4; this is fine. However, with the
point on hunk2, it'd kill hunk2 then hunk1. This is fixed by this patch.
2. Similarly, it should be possible to apply hunks in order. Previously with
the point at the start, C-c C-a would apply the hunk1, then move the point to
the first @@ header, and thus C-c C-a would try to apply the same hunk again.
---
lisp/vc/diff-mode.el | 131 ++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 118 insertions(+), 13 deletions(-)
diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 58498cb..c9a5f89 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -551,7 +551,7 @@ diff--auto-refine-data
;; Define diff-{hunk,file}-{prev,next}
(easy-mmode-define-navigation
- diff-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
+ diff--internal-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
(when diff-auto-refine-mode
(unless (prog1 diff--auto-refine-data
(setq diff--auto-refine-data
@@ -570,7 +570,102 @@ diff--auto-refine-data
(diff-refine-hunk))))))))))))
(easy-mmode-define-navigation
- diff-file diff-file-header-re "file" diff-end-of-file)
+ diff--internal-file diff-file-header-re "file" diff-end-of-file)
+
+(defun diff--wrap-navigation (skip-hunk-start
+ what orig
+ header-re goto-start-func count)
+ "Wrap diff-{hunk,file}-{next,prev} for more intuitive behavior.
+Override the default diff-{hunk,file}-{next,prev} implementation
+by skipping any lines that are associated with this hunk/file but
+precede the hunk-start marker. For instance, a diff file could
+contain
+
+diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
+index 923de9a..6b1c24f 100644
+--- a/lisp/vc/diff-mode.el
++++ b/lisp/vc/diff-mode.el
+@@ -590,6 +590,22 @@
+.......
+
+If a point is on 'index', then the point is considered to be in
+this first hunk. Move the point to the @@... marker before
+executing the default diff-hunk-next/prev implementation to move
+to the NEXT marker."
+ (if (not skip-hunk-start)
+ (funcall orig count)
+
+ (let ((start (point)))
+ (funcall goto-start-func)
+
+ ;; Trap the error.
+ (condition-case nil
+ (funcall orig count)
+ (error nil))
+
+ (when (not (looking-at header-re))
+ (goto-char start)
+ (user-error (format "No %s" what))))))
+
+;; These functions all take a skip-hunk-start argument which controls
+;; whether we skip pre-hunk-start text or not. In interactive uses we
+;; always want to do this, but the simple behavior is still necessary
+;; to, for example, avoid an infinite loop:
+;;
+;; diff-hunk-next calls
+;; diff--wrap-navigation calls
+;; diff-bounds-of-hunk calls
+;; diff-beginning-of-hunk calls
+;; diff-hunk-next
+;;
+;; Here the outer diff-hunk-next has skip-hunk-start set to t, but the
+;; inner one does not, which breaks the loop.
+(defun diff-hunk-prev (&optional count skip-hunk-start)
+ "Go to the previous COUNT'th hunk."
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "prev hunk"
+ 'diff--internal-hunk-prev
+ diff-hunk-header-re
+ (lambda () (goto-char (car (diff-bounds-of-hunk))))
+ count))
+
+(defun diff-hunk-next (&optional count skip-hunk-start)
+ "Go to the next COUNT'th hunk."
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "next hunk"
+ 'diff--internal-hunk-next
+ diff-hunk-header-re
+ (lambda () (goto-char (car (diff-bounds-of-hunk))))
+ count))
+
+(defun diff-file-prev (&optional count skip-hunk-start)
+ "Go to the previous COUNT'th file."
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "prev file"
+ 'diff--internal-file-prev
+ diff-file-header-re
+ (lambda () (goto-char (car (diff-bounds-of-file))) (diff--internal-hunk-next))
+ count))
+
+(defun diff-file-next (&optional count skip-hunk-start)
+ "Go to the next COUNT'th file."
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "next file"
+ 'diff--internal-file-next
+ diff-file-header-re
+ (lambda () (goto-char (car (diff-bounds-of-file))) (diff--internal-hunk-next))
+ count))
+
+
+
(defun diff-bounds-of-hunk ()
"Return the bounds of the diff hunk at point.
@@ -581,12 +676,13 @@ diff-bounds-of-hunk
(let ((pos (point))
(beg (diff-beginning-of-hunk t))
(end (diff-end-of-hunk)))
- (cond ((>= end pos)
+ (cond ((> end pos)
(list beg end))
;; If this hunk ends above POS, consider the next hunk.
((re-search-forward diff-hunk-header-re nil t)
(list (match-beginning 0) (diff-end-of-hunk)))
- (t (error "No hunk found"))))))
+ ;; There's no next hunk, so just take the one we have.
+ (t (list beg end))))))
(defun diff-bounds-of-file ()
"Return the bounds of the file segment at point.
@@ -1665,8 +1761,9 @@ diff-find-source-location
SWITCHED is non-nil if the patch is already applied.
NOPROMPT, if non-nil, means not to prompt the user."
(save-excursion
- (let* ((other (diff-xor other-file diff-jump-to-old-file))
- (char-offset (- (point) (diff-beginning-of-hunk t)))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (other (diff-xor other-file diff-jump-to-old-file))
+ (char-offset (- (point) (goto-char (car hunk-bounds))))
;; Check that the hunk is well-formed. Otherwise diff-mode and
;; the user may disagree on what constitutes the hunk
;; (e.g. because an empty line truncates the hunk mid-course),
@@ -1675,7 +1772,7 @@ diff-find-source-location
;; Suppress check when NOPROMPT is non-nil (Bug#3033).
(_ (unless noprompt (diff-sanity-check-hunk)))
(hunk (buffer-substring
- (point) (save-excursion (diff-end-of-hunk) (point))))
+ (point) (cadr hunk-bounds)))
(old (diff-hunk-text hunk reverse char-offset))
(new (diff-hunk-text hunk (not reverse) char-offset))
;; Find the location specification.
@@ -1783,8 +1880,15 @@ diff-apply-hunk
;; Display BUF in a window
(set-window-point (display-buffer buf) (+ (car pos) (cdr new)))
(diff-hunk-status-msg line-offset (diff-xor switched reverse) nil)
+
+ ;; Advance to the next hunk with skip-hunk-start set to t
+ ;; because we want the behavior of moving to the next logical
+ ;; hunk, not the original behavior where were would sometimes
+ ;; stay on the curent hunk. This is the behavior we get when
+ ;; navigating through hunks interactively, and we want it when
+ ;; applying hunks too (see http://debbugs.gnu.org/17544).
(when diff-advance-after-apply-hunk
- (diff-hunk-next))))))
+ (diff-hunk-next nil t))))))
(defun diff-test-hunk (&optional reverse)
@@ -1865,14 +1969,15 @@ diff-current-defun
(defun diff-ignore-whitespace-hunk ()
"Re-diff the current hunk, ignoring whitespace differences."
(interactive)
- (let* ((char-offset (- (point) (diff-beginning-of-hunk t)))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (char-offset (- (point) (goto-char (car hunk-bounds))))
(opts (pcase (char-after) (?@ "-bu") (?* "-bc") (_ "-b")))
(line-nb (and (or (looking-at "[^0-9]+\\([0-9]+\\)")
(error "Can't find line number"))
(string-to-number (match-string 1))))
(inhibit-read-only t)
(hunk (delete-and-extract-region
- (point) (save-excursion (diff-end-of-hunk) (point))))
+ (point) (cadr hunk-bounds)))
(lead (make-string (1- line-nb) ?\n)) ;Line nums start at 1.
(file1 (make-temp-file "diff1"))
(file2 (make-temp-file "diff2"))
@@ -1959,8 +2064,8 @@ diff-refine-hunk
(interactive)
(require 'smerge-mode)
(save-excursion
- (diff-beginning-of-hunk t)
- (let* ((start (point))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (start (goto-char (car hunk-bounds)))
(style (diff-hunk-style)) ;Skips the hunk header as well.
(beg (point))
(props-c '((diff-mode . fine) (face diff-refine-changed)))
@@ -1968,7 +2073,7 @@ diff-refine-hunk
(props-a '((diff-mode . fine) (face diff-refine-added)))
;; Be careful to go back to `start' so diff-end-of-hunk gets
;; to read the hunk header's line info.
- (end (progn (goto-char start) (diff-end-of-hunk) (point))))
+ (end (goto-char (cadr hunk-bounds))))
(remove-overlays beg end 'diff-mode 'fine)
--
2.8.0.rc3
^ permalink raw reply related [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-11-21 7:23 ` Dima Kogan
@ 2016-11-23 0:42 ` npostavs
2016-11-23 21:11 ` Dima Kogan
0 siblings, 1 reply; 24+ messages in thread
From: npostavs @ 2016-11-23 0:42 UTC (permalink / raw)
To: Dima Kogan; +Cc: Andreas Schwab, 17544
> From: Dima Kogan <Dmitriy.Kogan@jpl.nasa.gov>
> Date: Wed, 14 Sep 2016 15:25:06 -0700
> Subject: [PATCH] Improved diff-mode navigation/manipulation
The commit message should also have double spaced sentences and should
end with a ChangeLog style entry. Apart from that I think is ready to
push to master.
^ permalink raw reply [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-11-23 0:42 ` npostavs
@ 2016-11-23 21:11 ` Dima Kogan
2016-11-29 4:10 ` npostavs
0 siblings, 1 reply; 24+ messages in thread
From: Dima Kogan @ 2016-11-23 21:11 UTC (permalink / raw)
To: npostavs; +Cc: Andreas Schwab, 17544
[-- Attachment #1: Type: text/plain, Size: 205 bytes --]
npostavs@users.sourceforge.net writes:
> The commit message should also have double spaced sentences and should
> end with a ChangeLog style entry. Apart from that I think is ready to
> push to master.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Improve-diff-mode-navigation-manipulation.patch --]
[-- Type: text/x-diff, Size: 10549 bytes --]
From d3950fca6cb1f3aa097cec0f524e38b9a7f05303 Mon Sep 17 00:00:00 2001
From: Dima Kogan <Dmitriy.Kogan@jpl.nasa.gov>
Date: Wed, 14 Sep 2016 15:25:06 -0700
Subject: [PATCH] Improve diff-mode navigation/manipulation
This is Bug #17544.
Navigation and use of diff buffers had several annoying corner cases
that this patch fixes. These corner cases were largely due to
inconsistent treatment of file headers. Say you have a diff such as
this:
--- aaa
+++ bbb
@@ -52,7 +52,7 @@
hunk1
@@ -74,7 +74,7 @@
hunk2
--- ccc
+++ ddd
@@ -608,6 +608,6 @@
hunk3
@@ -654,7 +654,7 @@
hunk4
The file headers here are the '---' and '+++' lines. With the point on
such a line, hunk operations would sometimes refer to the next hunk and
sometimes to the previous hunk. Most of the time it would be the
previous hunk, which is not what the user would expect. This patch
consistently treats such headers as the next hunk. So with this patch,
if the point is on the '--- ccc' line, the point is seen as referring to
hunk3.
Specific behaviors this fixes are:
1. It should be possible to place the point in the middle of a diff
buffer, and press M-k repeatedly to kill hunks in the order they appear
in the buffer. With the point on hunk1, M-k M-k would kill hunk1 then
hunk2. With the point on hunk3, it would kill hunk3 then hunk4; this is
fine. However, with the point on hunk2, it'd kill hunk2 then hunk1.
This is fixed by this patch.
2. Similarly, it should be possible to apply hunks in order. Previously
with the point at the start, C-c C-a would apply the hunk1, then move
the point to the first @@ header, and thus C-c C-a would try to apply
the same hunk again.
lisp/vc/diff-mode.el (diff--wrap-navigation): New function to add better
navigation logic to diff-{hunk,file}-{next,prev}.
lisp/vc/diff-mode.el (diff-hunk-next, diff-hunk-prev, diff-file-next,
diff-file-prev): Better navigation logic if skip-hunk-start is true,
which happens when called interactively.
lisp/vc/diff-mode.el (diff-bounds-of-hunk, diff-find-source-location,
diff-apply-hunk, diff-current-defun, diff-refine-hunk): Small tweaks to
improve hunk navigation.
---
lisp/vc/diff-mode.el | 131 ++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 118 insertions(+), 13 deletions(-)
diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 58498cb..c9a5f89 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -551,7 +551,7 @@ diff--auto-refine-data
;; Define diff-{hunk,file}-{prev,next}
(easy-mmode-define-navigation
- diff-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
+ diff--internal-hunk diff-hunk-header-re "hunk" diff-end-of-hunk diff-restrict-view
(when diff-auto-refine-mode
(unless (prog1 diff--auto-refine-data
(setq diff--auto-refine-data
@@ -570,7 +570,102 @@ diff--auto-refine-data
(diff-refine-hunk))))))))))))
(easy-mmode-define-navigation
- diff-file diff-file-header-re "file" diff-end-of-file)
+ diff--internal-file diff-file-header-re "file" diff-end-of-file)
+
+(defun diff--wrap-navigation (skip-hunk-start
+ what orig
+ header-re goto-start-func count)
+ "Wrap diff-{hunk,file}-{next,prev} for more intuitive behavior.
+Override the default diff-{hunk,file}-{next,prev} implementation
+by skipping any lines that are associated with this hunk/file but
+precede the hunk-start marker. For instance, a diff file could
+contain
+
+diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
+index 923de9a..6b1c24f 100644
+--- a/lisp/vc/diff-mode.el
++++ b/lisp/vc/diff-mode.el
+@@ -590,6 +590,22 @@
+.......
+
+If a point is on 'index', then the point is considered to be in
+this first hunk. Move the point to the @@... marker before
+executing the default diff-hunk-next/prev implementation to move
+to the NEXT marker."
+ (if (not skip-hunk-start)
+ (funcall orig count)
+
+ (let ((start (point)))
+ (funcall goto-start-func)
+
+ ;; Trap the error.
+ (condition-case nil
+ (funcall orig count)
+ (error nil))
+
+ (when (not (looking-at header-re))
+ (goto-char start)
+ (user-error (format "No %s" what))))))
+
+;; These functions all take a skip-hunk-start argument which controls
+;; whether we skip pre-hunk-start text or not. In interactive uses we
+;; always want to do this, but the simple behavior is still necessary
+;; to, for example, avoid an infinite loop:
+;;
+;; diff-hunk-next calls
+;; diff--wrap-navigation calls
+;; diff-bounds-of-hunk calls
+;; diff-beginning-of-hunk calls
+;; diff-hunk-next
+;;
+;; Here the outer diff-hunk-next has skip-hunk-start set to t, but the
+;; inner one does not, which breaks the loop.
+(defun diff-hunk-prev (&optional count skip-hunk-start)
+ "Go to the previous COUNT'th hunk."
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "prev hunk"
+ 'diff--internal-hunk-prev
+ diff-hunk-header-re
+ (lambda () (goto-char (car (diff-bounds-of-hunk))))
+ count))
+
+(defun diff-hunk-next (&optional count skip-hunk-start)
+ "Go to the next COUNT'th hunk."
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "next hunk"
+ 'diff--internal-hunk-next
+ diff-hunk-header-re
+ (lambda () (goto-char (car (diff-bounds-of-hunk))))
+ count))
+
+(defun diff-file-prev (&optional count skip-hunk-start)
+ "Go to the previous COUNT'th file."
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "prev file"
+ 'diff--internal-file-prev
+ diff-file-header-re
+ (lambda () (goto-char (car (diff-bounds-of-file))) (diff--internal-hunk-next))
+ count))
+
+(defun diff-file-next (&optional count skip-hunk-start)
+ "Go to the next COUNT'th file."
+ (interactive (list (prefix-numeric-value current-prefix-arg) t))
+ (diff--wrap-navigation
+ skip-hunk-start
+ "next file"
+ 'diff--internal-file-next
+ diff-file-header-re
+ (lambda () (goto-char (car (diff-bounds-of-file))) (diff--internal-hunk-next))
+ count))
+
+
+
(defun diff-bounds-of-hunk ()
"Return the bounds of the diff hunk at point.
@@ -581,12 +676,13 @@ diff-bounds-of-hunk
(let ((pos (point))
(beg (diff-beginning-of-hunk t))
(end (diff-end-of-hunk)))
- (cond ((>= end pos)
+ (cond ((> end pos)
(list beg end))
;; If this hunk ends above POS, consider the next hunk.
((re-search-forward diff-hunk-header-re nil t)
(list (match-beginning 0) (diff-end-of-hunk)))
- (t (error "No hunk found"))))))
+ ;; There's no next hunk, so just take the one we have.
+ (t (list beg end))))))
(defun diff-bounds-of-file ()
"Return the bounds of the file segment at point.
@@ -1665,8 +1761,9 @@ diff-find-source-location
SWITCHED is non-nil if the patch is already applied.
NOPROMPT, if non-nil, means not to prompt the user."
(save-excursion
- (let* ((other (diff-xor other-file diff-jump-to-old-file))
- (char-offset (- (point) (diff-beginning-of-hunk t)))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (other (diff-xor other-file diff-jump-to-old-file))
+ (char-offset (- (point) (goto-char (car hunk-bounds))))
;; Check that the hunk is well-formed. Otherwise diff-mode and
;; the user may disagree on what constitutes the hunk
;; (e.g. because an empty line truncates the hunk mid-course),
@@ -1675,7 +1772,7 @@ diff-find-source-location
;; Suppress check when NOPROMPT is non-nil (Bug#3033).
(_ (unless noprompt (diff-sanity-check-hunk)))
(hunk (buffer-substring
- (point) (save-excursion (diff-end-of-hunk) (point))))
+ (point) (cadr hunk-bounds)))
(old (diff-hunk-text hunk reverse char-offset))
(new (diff-hunk-text hunk (not reverse) char-offset))
;; Find the location specification.
@@ -1783,8 +1880,15 @@ diff-apply-hunk
;; Display BUF in a window
(set-window-point (display-buffer buf) (+ (car pos) (cdr new)))
(diff-hunk-status-msg line-offset (diff-xor switched reverse) nil)
+
+ ;; Advance to the next hunk with skip-hunk-start set to t
+ ;; because we want the behavior of moving to the next logical
+ ;; hunk, not the original behavior where were would sometimes
+ ;; stay on the curent hunk. This is the behavior we get when
+ ;; navigating through hunks interactively, and we want it when
+ ;; applying hunks too (see http://debbugs.gnu.org/17544).
(when diff-advance-after-apply-hunk
- (diff-hunk-next))))))
+ (diff-hunk-next nil t))))))
(defun diff-test-hunk (&optional reverse)
@@ -1865,14 +1969,15 @@ diff-current-defun
(defun diff-ignore-whitespace-hunk ()
"Re-diff the current hunk, ignoring whitespace differences."
(interactive)
- (let* ((char-offset (- (point) (diff-beginning-of-hunk t)))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (char-offset (- (point) (goto-char (car hunk-bounds))))
(opts (pcase (char-after) (?@ "-bu") (?* "-bc") (_ "-b")))
(line-nb (and (or (looking-at "[^0-9]+\\([0-9]+\\)")
(error "Can't find line number"))
(string-to-number (match-string 1))))
(inhibit-read-only t)
(hunk (delete-and-extract-region
- (point) (save-excursion (diff-end-of-hunk) (point))))
+ (point) (cadr hunk-bounds)))
(lead (make-string (1- line-nb) ?\n)) ;Line nums start at 1.
(file1 (make-temp-file "diff1"))
(file2 (make-temp-file "diff2"))
@@ -1959,8 +2064,8 @@ diff-refine-hunk
(interactive)
(require 'smerge-mode)
(save-excursion
- (diff-beginning-of-hunk t)
- (let* ((start (point))
+ (let* ((hunk-bounds (diff-bounds-of-hunk))
+ (start (goto-char (car hunk-bounds)))
(style (diff-hunk-style)) ;Skips the hunk header as well.
(beg (point))
(props-c '((diff-mode . fine) (face diff-refine-changed)))
@@ -1968,7 +2073,7 @@ diff-refine-hunk
(props-a '((diff-mode . fine) (face diff-refine-added)))
;; Be careful to go back to `start' so diff-end-of-hunk gets
;; to read the hunk header's line info.
- (end (progn (goto-char start) (diff-end-of-hunk) (point))))
+ (end (goto-char (cadr hunk-bounds))))
(remove-overlays beg end 'diff-mode 'fine)
--
2.8.0.rc3
^ permalink raw reply related [flat|nested] 24+ messages in thread
* bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation
2016-11-23 21:11 ` Dima Kogan
@ 2016-11-29 4:10 ` npostavs
0 siblings, 0 replies; 24+ messages in thread
From: npostavs @ 2016-11-29 4:10 UTC (permalink / raw)
To: Dima Kogan; +Cc: Andreas Schwab, 17544
close 17544 26.1
quit
Dima Kogan <dima@secretsauce.net> writes:
> npostavs@users.sourceforge.net writes:
>
>> The commit message should also have double spaced sentences and should
>> end with a ChangeLog style entry. Apart from that I think is ready to
>> push to master.
>
>>From d3950fca6cb1f3aa097cec0f524e38b9a7f05303 Mon Sep 17 00:00:00 2001
> From: Dima Kogan <Dmitriy.Kogan@jpl.nasa.gov>
> Date: Wed, 14 Sep 2016 15:25:06 -0700
> Subject: [PATCH] Improve diff-mode navigation/manipulation
Pushed as 2c8a7e50d24daf19ea7d86f1cfeaa98a41c56085.
[...]
>
> lisp/vc/diff-mode.el (diff--wrap-navigation): New function to add better
> navigation logic to diff-{hunk,file}-{next,prev}.
>
> lisp/vc/diff-mode.el (diff-hunk-next, diff-hunk-prev, diff-file-next,
> diff-file-prev): Better navigation logic if skip-hunk-start is true,
> which happens when called interactively.
>
> lisp/vc/diff-mode.el (diff-bounds-of-hunk, diff-find-source-location,
> diff-apply-hunk, diff-current-defun, diff-refine-hunk): Small tweaks to
> improve hunk navigation.
> ---
I fixed the formatting of the ChangeLog entry.
^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2016-11-29 4:10 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2014-05-21 12:21 bug#17544: 24.3; [PATCH] Improved diff-mode navigation/manipulation Dima Kogan
2016-02-24 2:33 ` Lars Ingebrigtsen
2016-09-03 9:17 ` Dima Kogan
2016-09-03 10:14 ` Eli Zaretskii
2016-09-03 21:24 ` Dima Kogan
2016-09-04 3:27 ` npostavs
2016-09-07 7:14 ` Dima Kogan
2016-09-14 22:31 ` Dima Kogan
2016-09-23 7:22 ` Dima Kogan
2016-10-22 15:47 ` npostavs
2016-10-23 1:44 ` Dima Kogan
2016-10-23 2:49 ` npostavs
2016-11-07 2:26 ` Dima Kogan
2016-11-15 3:31 ` npostavs
2016-11-17 4:15 ` Dima Kogan
2016-11-17 4:33 ` npostavs
2016-11-17 8:05 ` Dima Kogan
2016-11-20 2:37 ` npostavs
2016-11-21 7:23 ` Dima Kogan
2016-11-23 0:42 ` npostavs
2016-11-23 21:11 ` Dima Kogan
2016-11-29 4:10 ` npostavs
2016-09-13 3:56 ` Dima Kogan
2016-09-03 11:05 ` Andreas Schwab
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.