[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[elpa] externals/diff-hl 679f557 01/10: New feature: diff-hl-show-hunk
From: |
ELPA Syncer |
Subject: |
[elpa] externals/diff-hl 679f557 01/10: New feature: diff-hl-show-hunk |
Date: |
Mon, 22 Feb 2021 22:57:13 -0500 (EST) |
branch: externals/diff-hl
commit 679f557305030d14cba001954acaf358363ebbfe
Author: Álvaro González <alvarogonzalezsotillo@gmail.com>
Commit: Dmitry Gutov <dgutov@yandex.ru>
New feature: diff-hl-show-hunk
Resolves #147, resolves #112.
This adds new commands, an optional minor mode, and two backends, one
of which depends on`posframe`, an external package.
See https://github.com/dgutov/diff-hl/pull/147 for general discussion.
---
README.md | 3 +
diff-hl-flydiff.el | 64 +-----
diff-hl-inline-popup.el | 275 ++++++++++++++++++++++++++
diff-hl-show-hunk-posframe.el | 238 +++++++++++++++++++++++
diff-hl-show-hunk.el | 439 ++++++++++++++++++++++++++++++++++++++++++
diff-hl.el | 95 +++++++--
6 files changed, 1044 insertions(+), 70 deletions(-)
diff --git a/README.md b/README.md
index b64d228..13c63d3 100644
--- a/README.md
+++ b/README.md
@@ -19,6 +19,9 @@ The package also contains auxiliary modes:
it to any revision, see its docstring for details.
* `diff-hl-flydiff-mode` implements highlighting changes on the fly.
It requires Emacs 24.4 or newer.
+* `diff-hl-show-hunk-mouse-mode` makes fringe and margin react to
+ mouse clicks to show the curresponding hunk. That's the alternative
+ to using the `diff-hl-show-hunk` family of commands.
Usage
=====
diff --git a/diff-hl-flydiff.el b/diff-hl-flydiff.el
index 3f8c926..51ba0af 100644
--- a/diff-hl-flydiff.el
+++ b/diff-hl-flydiff.el
@@ -78,65 +78,11 @@
(advice-add 'vc-git-mode-line-string :override
#'diff-hl-flydiff/vc-git-mode-line-string)))
-(defun diff-hl-flydiff/working-revision (file)
- "Like vc-working-revision, but always up-to-date"
- (vc-file-setprop file 'vc-working-revision
- (vc-call-backend (vc-backend file) 'working-revision file)))
-
-(defun diff-hl-flydiff-make-temp-file-name (file rev &optional manual)
- "Return a backup file name for REV or the current version of FILE.
-If MANUAL is non-nil it means that a name for backups created by
-the user should be returned."
- (let* ((auto-save-file-name-transforms
- `((".*" ,temporary-file-directory t))))
- (expand-file-name
- (concat (make-auto-save-file-name)
- ".~" (subst-char-in-string
- ?/ ?_ rev)
- (unless manual ".") "~")
- temporary-file-directory)))
-
-(defun diff-hl-flydiff-create-revision (file revision)
- "Read REVISION of FILE into a buffer and return the buffer."
- (let ((automatic-backup (diff-hl-flydiff-make-temp-file-name file revision))
- (filebuf (get-file-buffer file))
- (filename (diff-hl-flydiff-make-temp-file-name file revision 'manual)))
- (unless (file-exists-p filename)
- (if (file-exists-p automatic-backup)
- (rename-file automatic-backup filename nil)
- (with-current-buffer filebuf
- (let ((coding-system-for-read 'no-conversion)
- (coding-system-for-write 'no-conversion))
- (condition-case nil
- (with-temp-file filename
- (let ((outbuf (current-buffer)))
- ;; Change buffer to get local value of
- ;; vc-checkout-switches.
- (with-current-buffer filebuf
- (vc-call find-revision file revision outbuf))))
- (error
- (when (file-exists-p filename)
- (delete-file filename))))))))
- filename))
-
-(defun diff-hl-flydiff-buffer-with-head (file &optional backend)
- "View the differences between BUFFER and its associated file.
-This requires the external program `diff' to be in your `exec-path'."
- (interactive)
- (vc-ensure-vc-buffer)
- (setq diff-hl-flydiff-modified-tick (buffer-chars-modified-tick))
- (save-current-buffer
- (let* ((temporary-file-directory
- (if (file-directory-p "/dev/shm/")
- "/dev/shm/"
- temporary-file-directory))
- (rev (diff-hl-flydiff-create-revision
- file
- (or diff-hl-reference-revision
- (diff-hl-flydiff/working-revision file)))))
- ;; FIXME: When against staging, do it differently!
- (diff-no-select rev (current-buffer) "-U 0 --strip-trailing-cr" 'noasync
- (get-buffer-create " *diff-hl-diff*")))))
+(defun diff-hl-flydiff-buffer-with-head (file &optional _backend)
+ "View the differences between FILE and its associated file in HEAD revision.
+This requires the external program `diff' to be in your
+`exec-path'."
+ (diff-hl-diff-buffer-with-head file " *diff-hl-diff*"))
(defun diff-hl-flydiff-update ()
(unless (or
diff --git a/diff-hl-inline-popup.el b/diff-hl-inline-popup.el
new file mode 100644
index 0000000..dee159e
--- /dev/null
+++ b/diff-hl-inline-popup.el
@@ -0,0 +1,275 @@
+;;; diff-hl-inline-popup.el --- inline popup using phantom overlays -*-
lexical-binding: t -*-
+
+;; Copyright (C) 2020-2021 Free Software Foundation, Inc.
+
+;; Author: Álvaro González <alvarogonzalezsotillo@gmail.com>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;; Shows inline popups using phantom overlays. The lines of the popup
+;; can be scrolled.
+;;; Code:
+
+(require 'subr-x)
+
+(defvar diff-hl-inline-popup--current-popup nil "The overlay of the current
inline popup.")
+(defvar diff-hl-inline-popup--current-lines nil "A list of the lines to show
in the popup.")
+(defvar diff-hl-inline-popup--current-index nil "First line showed in popup.")
+(defvar diff-hl-inline-popup--invokinkg-command nil "Command that invoked the
popup.")
+(defvar diff-hl-inline-popup--current-footer nil "String to be displayed in
the footer.")
+(defvar diff-hl-inline-popup--current-header nil "String to be displayed in
the header.")
+(defvar diff-hl-inline-popup--height nil "Height of the popup.")
+(defvar diff-hl-inline-popup--current-custom-keymap nil "Keymap to be added to
the keymap of the inline popup.")
+(defvar diff-hl-inline-popup--close-hook nil "Function to be called when the
popup closes.")
+
+(make-variable-buffer-local 'diff-hl-inline-popup--current-popup)
+(make-variable-buffer-local 'diff-hl-inline-popup--current-lines)
+(make-variable-buffer-local 'diff-hl-inline-popup--current-index)
+(make-variable-buffer-local 'diff-hl-inline-popup--current-header)
+(make-variable-buffer-local 'diff-hl-inline-popup--current-footer)
+(make-variable-buffer-local 'diff-hl-inline-popup--invokinkg-command)
+(make-variable-buffer-local 'diff-hl-inline-popup--current-custom-keymap)
+(make-variable-buffer-local 'diff-hl-inline-popup--height)
+(make-variable-buffer-local 'diff-hl-inline-popup--close-hook)
+
+(defun diff-hl-inline-popup--splice (list offset length)
+ "Compute a sublist of LIST starting at OFFSET, of LENGTH."
+ (butlast
+ (nthcdr offset list)
+ (- (length list) length offset)))
+
+(defun diff-hl-inline-popup--first-visible-line-in-window ()
+ "Return first visible line in current window."
+ (line-number-at-pos (window-start)))
+
+(defun diff-hl-inline-popup--ensure-enough-lines (pos content-height)
+ "Ensure there is enough lines below POS to show the inline popup with
CONTENT-HEIGHT height."
+ (let* ((line (line-number-at-pos pos))
+ (end (line-number-at-pos (window-end nil t)))
+ (height (+ 6 content-height))
+ (overflow (- (+ line height) end)))
+ (when (< 0 overflow)
+ (run-with-timer 0.1 nil #'scroll-up overflow))))
+
+(defun diff-hl-inline-popup--compute-content-height (&optional content-size)
+ "Compute the height of the inline popup.
+Default for CONTENT-SIZE is the size of the current lines"
+ (let ((content-size (or content-size (length
diff-hl-inline-popup--current-lines)))
+ (max-size (- (/(window-height) 2) 3)))
+ (min content-size max-size)))
+
+(defun diff-hl-inline-popup--compute-content-lines (lines index window-size)
+ "Compute the lines to show in the popup, from LINES starting at INDEX with a
WINDOW-SIZE."
+ (let* ((len (length lines))
+ (window-size (min window-size len))
+ (index (min index (- len window-size))))
+ (diff-hl-inline-popup--splice lines index window-size)))
+
+(defun diff-hl-inline-popup--compute-header (width &optional header)
+ "Compute the header of the popup, with some WIDTH, and some optional HEADER
text."
+ (let* ((scroll-indicator (if (eq diff-hl-inline-popup--current-index 0) "
" " ⬆ "))
+ (header (or header ""))
+ (new-width (- width (length header) (length scroll-indicator)))
+ (header (if (< new-width 0) "" header))
+ (new-width (- width (length header) (length scroll-indicator)))
+ (line (propertize (concat (diff-hl-inline-popup--separator new-width)
header scroll-indicator ) 'face '(:underline t))))
+ (concat line "\n") ))
+
+(defun diff-hl-inline-popup--compute-footer (width &optional footer)
+ "Compute the header of the popup, with some WIDTH, and some optional FOOTER
text."
+ (let* ((scroll-indicator (if (>= diff-hl-inline-popup--current-index (-
(length diff-hl-inline-popup--current-lines) diff-hl-inline-popup--height)) "
" " ⬇ "))
+ (footer (or footer ""))
+ (new-width (- width (length footer) (length scroll-indicator)))
+ (footer (if (< new-width 0) "" footer))
+ (new-width (- width (length footer) (length scroll-indicator)))
+ (blank-line (if (display-graphic-p)
+ ""
+ (propertize (concat "\n"
(diff-hl-inline-popup--separator width)) 'face '(:underline t))))
+ (line (propertize (concat (diff-hl-inline-popup--separator new-width)
footer scroll-indicator) 'face '(:overline t))))
+ (concat blank-line "\n" line)))
+
+(defun diff-hl-inline-popup--separator (width &optional sep)
+ "Return the horizontal separator with character SEP and a WIDTH."
+ (let ((sep (or sep ?\s)))
+ (make-string width sep)))
+
+(defun diff-hl-inline-popup--available-width ()
+ "Compute the available width in chars."
+ (let ((magic-adjust 3))
+ (if (not (display-graphic-p))
+ (let* ((linumber-width (line-number-display-width nil))
+ (width (- (window-body-width) linumber-width magic-adjust)))
+ width)
+ (let* ((font-width (window-font-width))
+ (window-width (window-body-width nil t))
+ (linenumber-width (line-number-display-width t))
+ (available-pixels (- window-width linenumber-width))
+ (width (- (/ available-pixels font-width) magic-adjust)))
+
+ ;;
https://emacs.stackexchange.com/questions/5495/how-can-i-determine-the-width-of-characters-on-the-screen
+ width))))
+
+
+(defun diff-hl-inline-popup--compute-popup-str (lines index window-size header
footer)
+ "Compute the string that represents the popup.
+There are some content LINES starting at INDEX, with a WINDOW-SIZE. HEADER and
+FOOTER are showed at start and end."
+ (let* ((width (diff-hl-inline-popup--available-width))
+ (content-lines (diff-hl-inline-popup--compute-content-lines lines
index window-size))
+ (header (diff-hl-inline-popup--compute-header width header))
+ (footer (diff-hl-inline-popup--compute-footer width footer)))
+ (concat header (string-join content-lines "\n" ) footer "\n")))
+
+(defun diff-hl-inline-popup-scroll-to (index)
+ "Scroll the inline popup to make visible the line at position INDEX."
+ (when diff-hl-inline-popup--current-popup
+ (setq diff-hl-inline-popup--current-index (max 0 (min index (- (length
diff-hl-inline-popup--current-lines) diff-hl-inline-popup--height))))
+ (let* ((str (diff-hl-inline-popup--compute-popup-str
+ diff-hl-inline-popup--current-lines
+ diff-hl-inline-popup--current-index
+ diff-hl-inline-popup--height
+ diff-hl-inline-popup--current-header
+ diff-hl-inline-popup--current-footer)))
+ (overlay-put diff-hl-inline-popup--current-popup 'after-string str))))
+
+(defun diff-hl-inline-popup--popup-down()
+ "Scrolls one line down."
+ (interactive)
+ (diff-hl-inline-popup-scroll-to (1+ diff-hl-inline-popup--current-index) ))
+
+(defun diff-hl-inline-popup--popup-up()
+ "Scrolls one line up."
+ (interactive)
+ (diff-hl-inline-popup-scroll-to (1- diff-hl-inline-popup--current-index) ))
+
+(defun diff-hl-inline-popup--popup-pagedown()
+ "Scrolls one page down."
+ (interactive)
+ (diff-hl-inline-popup-scroll-to (+ diff-hl-inline-popup--current-index
diff-hl-inline-popup--height) ))
+
+(defun diff-hl-inline-popup--popup-pageup()
+ "Scrolls one page up."
+ (interactive)
+ (diff-hl-inline-popup-scroll-to (- diff-hl-inline-popup--current-index
diff-hl-inline-popup--height) ))
+
+(defvar diff-hl-inline-popup-transient-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "<prior>") #'diff-hl-inline-popup--popup-pageup)
+ (define-key map (kbd "M-v") #'diff-hl-inline-popup--popup-pageup)
+ (define-key map (kbd "<next>") #'diff-hl-inline-popup--popup-pagedown)
+ (define-key map (kbd "C-v") #'diff-hl-inline-popup--popup-pagedown)
+ (define-key map (kbd "<up>") #'diff-hl-inline-popup--popup-up)
+ (define-key map (kbd "C-p") #'diff-hl-inline-popup--popup-up)
+ (define-key map (kbd "<down>") #'diff-hl-inline-popup--popup-down)
+ (define-key map (kbd "C-n") #'diff-hl-inline-popup--popup-down)
+ (define-key map (kbd "C-g") #'diff-hl-inline-popup-hide)
+ (define-key map [escape] #'diff-hl-inline-popup-hide)
+ (define-key map (kbd "q") #'diff-hl-inline-popup-hide)
+ ;;http://ergoemacs.org/emacs/emacs_mouse_wheel_config.html
+ (define-key map (kbd "<mouse-4>") #'diff-hl-inline-popup--popup-up)
+ (define-key map (kbd "<wheel-up>") #'diff-hl-inline-popup--popup-up)
+ (define-key map (kbd "<mouse-5>") #'diff-hl-inline-popup--popup-down)
+ (define-key map (kbd "<wheel-down>") #'diff-hl-inline-popup--popup-down)
+ map)
+ "Keymap for command `diff-hl-inline-popup-transient-mode'.
+Capture all the vertical movement of the point, and converts it
+to scroll in the popup")
+
+(defun diff-hl-inline-popup--ignorable-command-p (command)
+ "Decide if COMMAND is a command allowed while showing an inline popup."
+ ;;
https://emacs.stackexchange.com/questions/653/how-can-i-find-out-in-which-keymap-a-key-is-bound
+ (let ((keys (where-is-internal command (list
diff-hl-inline-popup--current-custom-keymap
diff-hl-inline-popup-transient-mode-map ) t))
+ (invoking (eq command diff-hl-inline-popup--invokinkg-command)))
+ (or keys invoking)))
+
+(defun diff-hl-inline-popup--post-command-hook ()
+ "Called each time a command is executed."
+ (let ((allowed-command (or
+ (string-match-p "diff-hl-inline-popup-" (symbol-name
this-command))
+ (diff-hl-inline-popup--ignorable-command-p
this-command))))
+ (unless allowed-command
+ (diff-hl-inline-popup-hide))))
+
+(define-minor-mode diff-hl-inline-popup-transient-mode
+ "Temporal minor mode to control an inline popup"
+ :global nil
+ (remove-hook 'post-command-hook #'diff-hl-inline-popup--post-command-hook t)
+ (set-keymap-parent diff-hl-inline-popup-transient-mode-map nil)
+
+ (when diff-hl-inline-popup-transient-mode
+ (set-keymap-parent diff-hl-inline-popup-transient-mode-map
diff-hl-inline-popup--current-custom-keymap)
+ (add-hook 'post-command-hook #'diff-hl-inline-popup--post-command-hook 0
t)))
+
+;;;###autoload
+(defun diff-hl-inline-popup-hide()
+ "Hide the current inline popup."
+ (interactive)
+ (when diff-hl-inline-popup-transient-mode
+ (diff-hl-inline-popup-transient-mode -1))
+ (when diff-hl-inline-popup--close-hook
+ (funcall diff-hl-inline-popup--close-hook)
+ (setq diff-hl-inline-popup--close-hook nil))
+ (when diff-hl-inline-popup--current-popup
+ (delete-overlay diff-hl-inline-popup--current-popup)
+ (setq diff-hl-inline-popup--current-popup nil)))
+
+;;;###autoload
+(defun diff-hl-inline-popup-show (lines &optional header footer keymap
close-hook point height)
+ "Create a phantom overlay to show the inline popup, with some
+content LINES, and a HEADER and a FOOTER, at POINT. KEYMAP is
+added to the current keymaps. CLOSE-HOOK is called when the popup
+is closed."
+ (when diff-hl-inline-popup--current-popup
+ (delete-overlay diff-hl-inline-popup--current-popup)
+ (setq diff-hl-inline-popup--current-popup nil))
+
+ (when (< (diff-hl-inline-popup--compute-content-height 99) 2)
+ (user-error "There is no enough vertical space to show the inline popup"))
+ (let* ((the-point (or point (point-at-eol)))
+ (the-buffer (current-buffer))
+ (overlay (make-overlay the-point the-point the-buffer)))
+ (overlay-put overlay 'phantom t)
+ (overlay-put overlay 'diff-hl-inline-popup t)
+ (setq diff-hl-inline-popup--current-popup overlay)
+
+ (setq diff-hl-inline-popup--current-lines
+ (mapcar (lambda (s) (replace-regexp-in-string "\n" " " s)) lines))
+ (setq diff-hl-inline-popup--current-header header)
+ (setq diff-hl-inline-popup--current-footer footer)
+ (setq diff-hl-inline-popup--invokinkg-command this-command)
+ (setq diff-hl-inline-popup--current-custom-keymap keymap)
+ (setq diff-hl-inline-popup--close-hook close-hook)
+ (setq diff-hl-inline-popup--height
(diff-hl-inline-popup--compute-content-height height))
+ (setq diff-hl-inline-popup--height (min diff-hl-inline-popup--height
(length diff-hl-inline-popup--current-lines)))
+ (diff-hl-inline-popup--ensure-enough-lines point
diff-hl-inline-popup--height)
+ (diff-hl-inline-popup-transient-mode 1)
+ (diff-hl-inline-popup-scroll-to 0)
+ overlay))
+
+(defun diff-hl-inline-popup--hide-all ()
+ "Testing purposes, use in case some inline popups get stuck in a buffer."
+ (interactive)
+ (when diff-hl-inline-popup-transient-mode
+ (diff-hl-inline-popup-transient-mode -1))
+ (setq diff-hl-inline-popup--current-popup nil)
+ (let* ((all-overlays (overlays-in (point-min) (point-max)))
+ (overlays (cl-remove-if-not (lambda (o)(overlay-get o
'diff-hl-inline-popup)) all-overlays)))
+ (dolist (o overlays)
+ (delete-overlay o))))
+
+(provide 'diff-hl-inline-popup)
+;;; diff-hl-inline-popup ends here
diff --git a/diff-hl-show-hunk-posframe.el b/diff-hl-show-hunk-posframe.el
new file mode 100644
index 0000000..522a6bc
--- /dev/null
+++ b/diff-hl-show-hunk-posframe.el
@@ -0,0 +1,238 @@
+;;; diff-hl-show-hunk-posframe.el --- posframe backend for diff-hl-show-hunk
-*- lexical-binding: t -*-
+
+;; Copyright (C) 2020-2021 Free Software Foundation, Inc.
+
+;; Author: Álvaro González <alvarogonzalezsotillo@gmail.com>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; This provides `diff-hl-show-hunk-posframe' than can be used as
+;; `diff-hl-show-hunk-function'. `posframe' is a runtime dependency,
+;; it is not required by this package, but it should be installed.
+;;
+;;; Code:
+
+(require 'diff-hl-show-hunk)
+
+;; This package uses some runtime dependencies, so we need to declare
+;; the external functions and variables
+(declare-function posframe-workable-p "posframe")
+(declare-function posframe-show "posframe")
+(defvar posframe-mouse-banish)
+
+(defgroup diff-hl-show-hunk-posframe nil
+ "Show vc diffs in a posframe."
+ :group 'diff-hl-show-hunk)
+
+(defcustom diff-hl-show-hunk-posframe-show-header-line t
+ "Show some useful buttons at the top of the diff-hl posframe."
+ :type 'boolean)
+
+(defcustom diff-hl-show-hunk-posframe-internal-border-width 2
+ "Internal border width of the posframe."
+ :type 'integer)
+
+(defcustom diff-hl-show-hunk-posframe-internal-border-color "#00ffff"
+ "Internal border color of the posframe."
+ :type 'color)
+
+(defcustom diff-hl-show-hunk-posframe-poshandler nil
+ "Poshandler of the posframe (see `posframe-show`)."
+ :type 'function)
+
+(defcustom diff-hl-show-hunk-posframe-parameters nil
+ "The frame parameters used by helm-posframe."
+ :type 'string)
+
+(defface diff-hl-show-hunk-posframe '((t nil))
+ "Face for the posframe buffer.
+Customize it to change the base properties of the text.")
+
+(defface diff-hl-show-hunk-posframe-button-face '((t . (:height 0.9)))
+ "Face for the posframe buttons" )
+
+(defvar diff-hl-show-hunk--frame nil "The postframe frame used in function
`diff-hl-show-hunk-posframe'.")
+(defvar diff-hl-show-hunk--original-frame nil "The frame from which the hunk
is shown.")
+
+(defun diff-hl-show-hunk--posframe-hide ()
+ "Hide the posframe and clean up buffer."
+ (interactive)
+ (diff-hl-show-hunk-posframe--transient-mode -1)
+ (when (frame-live-p diff-hl-show-hunk--frame)
+ (make-frame-invisible diff-hl-show-hunk--frame))
+ (when diff-hl-show-hunk--original-frame
+ (when (frame-live-p diff-hl-show-hunk--original-frame)
+ (let ((frame diff-hl-show-hunk--original-frame))
+ (select-frame-set-input-focus frame)
+ ;; In Gnome, sometimes the input focus is not restored to the
+ ;; original frame, so we try harder in a while
+ (run-with-timer 0.1 nil (lambda () (select-frame-set-input-focus
frame)))))
+ (setq diff-hl-show-hunk--original-frame nil)))
+
+(defvar diff-hl-show-hunk-posframe--transient-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map [escape] #'diff-hl-show-hunk-hide)
+ (define-key map (kbd "q") #'diff-hl-show-hunk-hide)
+ (define-key map (kbd "C-g") #'diff-hl-show-hunk-hide)
+ (define-key map (kbd "p") #'diff-hl-show-hunk-previous)
+ (define-key map (kbd "n") #'diff-hl-show-hunk-next)
+ (define-key map (kbd "c") #'diff-hl-show-hunk-copy-original-text)
+ (define-key map (kbd "r") #'diff-hl-show-hunk-revert-hunk)
+ (define-key map (kbd "[") #'diff-hl-show-hunk-previous)
+ (define-key map (kbd "]") #'diff-hl-show-hunk-next)
+ (define-key map (kbd "{") #'diff-hl-show-hunk-previous)
+ (define-key map (kbd "}") #'diff-hl-show-hunk-next)
+ map)
+ "Keymap for command `diff-hl-show-hunk-posframe--transient-mode'.
+Capture all the vertical movement of the point, and converts it
+to scroll in the posframe")
+
+(define-minor-mode diff-hl-show-hunk-posframe--transient-mode
+ "Temporal minor mode to control diff-hl posframe."
+ :lighter ""
+ :global t
+ (if diff-hl-show-hunk-posframe--transient-mode
+ (add-hook 'post-command-hook
#'diff-hl-show-hunk--posframe-post-command-hook nil)
+ (remove-hook 'post-command-hook
#'diff-hl-show-hunk--posframe-post-command-hook nil)))
+
+(defun diff-hl-show-hunk--posframe-post-command-hook ()
+ "Called for each command while in
`diff-hl-show-hunk-posframe--transient-mode."
+ (let* ((allowed-command (or
+ (diff-hl-show-hunk-ignorable-command-p this-command)
+ (and (symbolp this-command)
+ (string-match-p "diff-hl-" (symbol-name
this-command)))))
+ (event-in-frame (eq last-event-frame diff-hl-show-hunk--frame))
+ (has-focus (and (frame-live-p diff-hl-show-hunk--frame)
+ (functionp 'frame-focus-state)
+ (eq (frame-focus-state diff-hl-show-hunk--frame) t)))
+ (still-visible (or event-in-frame allowed-command has-focus)))
+ (unless still-visible
+ (diff-hl-show-hunk--posframe-hide))))
+
+(defun diff-hl-show-hunk--posframe-button (text help-echo action)
+ "Make a string implementing a button with TEXT and a HELP-ECHO.
+The button calls an ACTION."
+ (concat
+ (propertize (concat " " text " ")
+ 'help-echo (if action help-echo "Not available")
+ 'face 'diff-hl-show-hunk-posframe-button-face
+ 'mouse-face (when action '(:box (:style released-button)))
+ 'keymap (when action
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "<header-line> <mouse-1>")
action)
+ map)))
+ " "))
+
+(defun diff-hl-show-hunk-posframe--header-line ()
+ "Make the header line of the posframe."
+ (concat
+ (diff-hl-show-hunk--posframe-button
+ "⨯ Close"
+ "Close (\\[diff-hl-show-hunk-hide])"
+ #'diff-hl-show-hunk-hide)
+ (diff-hl-show-hunk--posframe-button
+ "⬆ Previous change"
+ "Previous change in hunk (\\[diff-hl-show-hunk-previous])"
+ #'diff-hl-show-hunk-previous)
+
+ (diff-hl-show-hunk--posframe-button
+ "⬇ Next change"
+ "Next change in hunk (\\[diff-hl-show-hunk-next])"
+ #'diff-hl-show-hunk-next)
+
+ (diff-hl-show-hunk--posframe-button
+ "⊚ Copy original"
+ "Copy original (\\[diff-hl-show-hunk-copy-original-text])"
+ #'diff-hl-show-hunk-copy-original-text)
+
+ (diff-hl-show-hunk--posframe-button
+ "♻ Revert hunk"
+ "Revert hunk (\\[diff-hl-show-hunk-revert-hunk])"
+ #'diff-hl-show-hunk-revert-hunk)))
+
+;;;###autoload
+(defun diff-hl-show-hunk-posframe (buffer &optional _line)
+ "Implementation to show the hunk in a posframe."
+
+ (unless (require 'posframe nil t)
+ (user-error
+ (concat
+ "`diff-hl-show-hunk-posframe' requires the `posframe' package."
+ " Please install it or customize `diff-hl-show-hunk-function'.")))
+
+ (unless (posframe-workable-p)
+ (user-error
+ "Package `posframe' is not workable. Please customize
diff-hl-show-hunk-function"))
+
+ (diff-hl-show-hunk--posframe-hide)
+ (setq diff-hl-show-hunk--hide-function #'diff-hl-show-hunk--posframe-hide)
+
+ ;; put an overlay to override read-only-mode keymap
+ (with-current-buffer buffer
+ ;; Change face size
+ (buffer-face-set 'diff-hl-show-hunk-posframe)
+
+ (let ((full-overlay (make-overlay 1 (1+ (buffer-size)))))
+ (overlay-put full-overlay
+ 'keymap diff-hl-show-hunk-posframe--transient-mode-map)))
+
+ (setq posframe-mouse-banish nil)
+ (setq diff-hl-show-hunk--original-frame last-event-frame)
+
+ (let* ((hunk-overlay diff-hl-show-hunk--original-overlay)
+ (position (overlay-end hunk-overlay)))
+ (setq
+ diff-hl-show-hunk--frame
+ (posframe-show buffer
+ :position position
+ :poshandler diff-hl-show-hunk-posframe-poshandler
+ :internal-border-width
diff-hl-show-hunk-posframe-internal-border-width
+ :accept-focus t
+ ;; internal-border-color Doesn't always work, if not
customize internal-border face
+ :internal-border-color
diff-hl-show-hunk-posframe-internal-border-color
+ :hidehandler nil
+ ;; Sometimes, header-line is not taken into account, so
put a min height and a min width
+ :min-height (when
diff-hl-show-hunk-posframe-show-header-line 10)
+ :min-width (when
diff-hl-show-hunk-posframe-show-header-line
+ (length
(diff-hl-show-hunk-posframe--header-line)))
+ :respect-header-line
diff-hl-show-hunk-posframe-show-header-line
+ :respect-tab-line nil
+ :respect-mode-line nil
+ :override-parameters
diff-hl-show-hunk-posframe-parameters)))
+
+ (set-frame-parameter diff-hl-show-hunk--frame 'drag-internal-border t)
+ (set-frame-parameter diff-hl-show-hunk--frame 'drag-with-header-line t)
+
+ (with-selected-frame diff-hl-show-hunk--frame
+ (with-current-buffer buffer
+ (diff-hl-show-hunk-posframe--transient-mode 1)
+ (when diff-hl-show-hunk-posframe-show-header-line
+ (setq header-line-format (diff-hl-show-hunk-posframe--header-line)))
+ (goto-char (point-min))
+ (setq buffer-quit-function #'diff-hl-show-hunk--posframe-hide)
+ (select-window (window-main-window diff-hl-show-hunk--frame))
+
+ ;; Make cursor visible (mainly for selecting text in posframe)
+ (setq cursor-type 'box)
+
+ ;; Recenter arround point
+ (recenter)))
+ (select-frame-set-input-focus diff-hl-show-hunk--frame))
+
+(provide 'diff-hl-show-hunk-posframe)
+;;; diff-hl-show-hunk-posframe.el ends here
diff --git a/diff-hl-show-hunk.el b/diff-hl-show-hunk.el
new file mode 100644
index 0000000..bb9bf8c
--- /dev/null
+++ b/diff-hl-show-hunk.el
@@ -0,0 +1,439 @@
+;;; diff-hl-show-hunk.el --- Integrate popup/posframe and
diff-hl-diff-goto-hunk -*- lexical-binding: t -*-
+
+;; Copyright (C) 2020-2021 Free Software Foundation, Inc.
+
+;; Author: Álvaro González <alvarogonzalezsotillo@gmail.com>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; `diff-hl-show-hunk' shows a popup with the modified hunk at point.
+;; `diff-hl-show-hunk-function' contains the backend used to show the
+;; hunk. Its default value is `diff-hl-show-hunk-inline-popup', that
+;; shows diffs inline using overlay. There is another built-in backend:
+;; `diff-hl-show-hunk-posframe' (based on posframe). Other backends (for
+;; example based on `pos-tip') could also be implemented.
+;;
+;; `diff-hl-show-hunk-mode' adds the following keybindings:
+;;
+;; - `diff-hl-show-hunk': C-x v *
+;; - `diff-hl-show-hunk-next': C-x v }
+;; - `diff-hl-show-hunk-previous': C-x v {
+;;
+;; `diff-hl-show-hunk-mouse-mode' includes all the keybindings of
+;; `diff-hl-show-hunk-mode', and adds `diff-hl-show-hunk' when
+;; clicking in the margin or the fringe.
+;;
+;; To use one or both in all buffers:
+;;
+;; (global-diff-hl-show-hunk-mode)
+;;
+;; and/or
+;;
+;; (global-diff-hl-show-hunk-mouse-mode)
+
+;;; Code:
+
+(require 'diff-hl-inline-popup)
+(require 'diff-hl)
+
+(defvar diff-hl-show-hunk-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (concat diff-hl-command-prefix "*") #'diff-hl-show-hunk)
+ (define-key map (concat diff-hl-command-prefix "{")
#'diff-hl-show-hunk-previous)
+ (define-key map (concat diff-hl-command-prefix "}")
#'diff-hl-show-hunk-next)
+ map)
+ "Keymap for command `diff-hl-show-hunk-mode'.")
+
+(defvar diff-hl-show-hunk-mouse-mode-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "<left-margin> <mouse-1>") 'diff-hl-show-hunk--click)
+ (define-key map (kbd "<right-margin> <mouse-1>") 'diff-hl-show-hunk--click)
+ (define-key map (kbd "<left-fringe> <mouse-1>") 'diff-hl-show-hunk--click)
+ (define-key map (kbd "<right-fringe> <mouse-1>") 'diff-hl-show-hunk--click)
+ (set-keymap-parent map diff-hl-show-hunk-mode-map)
+ map)
+ "Keymap for command `diff-hl-show-hunk-mouse-mode'.")
+
+(defvar diff-hl-show-hunk-buffer-name "*diff-hl-show-hunk-buffer*"
+ "Name of the posframe used by diff-hl-show-hunk.")
+
+(defvar diff-hl-show-hunk--original-window nil
+ "The vc window of which the hunk is shown.")
+
+(defvar diff-hl-show-hunk--original-buffer nil
+ "The vc buffer of which the hunk is shown.")
+
+(defvar diff-hl-show-hunk--original-content nil
+ "The original content of the hunk.")
+
+(defvar diff-hl-show-hunk--original-overlay nil
+ "Copy of the diff-hl hunk overlay.")
+
+(defgroup diff-hl-show-hunk nil
+ "Show vc diffs in a posframe or popup."
+ :group 'diff-hl)
+
+(defconst diff-hl-show-hunk-boundary "^@@.*@@")
+(defconst diff-hl-show-hunk--no-lines-removed-message (list "<<no lines
removed>>"))
+
+(defcustom diff-hl-show-hunk-inline-popup-hide-hunk nil
+ "If t, inline-popup is shown over the hunk, hiding it."
+ :type 'boolean)
+
+(defcustom diff-hl-show-hunk-inline-popup-smart-lines t
+ "If t, inline-popup tries to show only the deleted lines of the
+hunk. The added lines are shown when scrolling the popup. If
+the hunk consist only on added lines, then
+`diff-hl-show-hunk--no-lines-removed-message' it is shown."
+ :type 'boolean)
+
+(defcustom diff-hl-show-hunk-function 'diff-hl-show-hunk-inline-popup
+ "The function used to render the hunk.
+The function receives as first parameter a buffer with the
+contents of the hunk, and as second parameter the line number
+corresponding to the clicked line in the original buffer."
+ :type '(choice
+ (const :tag "Show inline" diff-hl-show-hunk-inline-popup)
+ (const :tag "Show using posframe" diff-hl-show-hunk-posframe)))
+
+(defvar diff-hl-show-hunk--hide-function nil
+ "Function to call to close the shown hunk.")
+
+(defun diff-hl-show-hunk-hide ()
+ "Hide the current shown hunk."
+ (interactive)
+ (if (and diff-hl-show-hunk--original-window (window-live-p
diff-hl-show-hunk--original-window))
+ (select-window diff-hl-show-hunk--original-window))
+ (setq diff-hl-show-hunk--original-window nil)
+ (if (buffer-live-p diff-hl-show-hunk--original-buffer)
+ (switch-to-buffer diff-hl-show-hunk--original-buffer))
+ (setq diff-hl-show-hunk--original-buffer nil)
+ (with-current-buffer (get-buffer-create diff-hl-show-hunk-buffer-name)
+ (read-only-mode -1)
+ (erase-buffer))
+ (when diff-hl-show-hunk--hide-function
+ (let ((hidefunc diff-hl-show-hunk--hide-function))
+ (setq diff-hl-show-hunk--hide-function nil)
+ (funcall hidefunc)))
+ (when diff-hl-show-hunk--original-overlay
+ (diff-hl-show-hunk--goto-hunk-overlay diff-hl-show-hunk--original-overlay))
+ (when diff-hl-show-hunk--original-overlay
+ (delete-overlay diff-hl-show-hunk--original-overlay))
+ (setq diff-hl-show-hunk--original-overlay nil))
+
+(defun diff-hl-show-hunk-ignorable-command-p (command)
+ "Decide if COMMAND is a command allowed while showing the current hunk."
+ (member command '(ignore diff-hl-show-hunk handle-switch-frame
diff-hl-show-hunk--click)))
+
+(defun diff-hl-show-hunk--compute-diffs ()
+ "Compute diffs using funcions of diff-hl.
+Then put the differences in *diff-hl-show-hunk-diff-buffer*
+buffer, and set the point in that buffer to the corresponding
+line of the original buffer."
+ (defvar vc-sentinel-movepoint)
+ (let* ((buffer (or (buffer-base-buffer) (current-buffer)))
+ (line (line-number-at-pos))
+ (dest-buffer "*diff-hl-show-hunk-diff-buffer*"))
+ (with-current-buffer buffer
+ (diff-hl-diff-buffer-with-head (buffer-file-name buffer) dest-buffer)
+ (switch-to-buffer dest-buffer)
+ (diff-hl-diff-skip-to line)
+ (setq vc-sentinel-movepoint (point)))
+ dest-buffer))
+
+(defun diff-hl-show-hunk--get-original-lines (content)
+ "Extracts the lines starting with '-' from CONTENT and save them."
+ (let* ((lines (split-string content "[\n\r]+" )))
+ (cl-remove-if-not (lambda (l) (string-match-p "^-.*" l)) lines)))
+
+(defun diff-hl-show-hunk--fill-original-content (content)
+ "Extracts the lines starting with '-' from CONTENT and save them."
+ (let* ((original-lines (diff-hl-show-hunk--get-original-lines content))
+ (original-lines (mapcar (lambda (l) (substring l 1)) original-lines))
+ (content (string-join original-lines "\n")))
+ (setq diff-hl-show-hunk--original-content content)))
+
+(defun diff-hl-show-hunk-buffer ()
+ "Create the buffer with the contents of the hunk at point.
+The buffer has the point in the corresponding line of the hunk.
+Returns a list with the buffer and the line number of the clicked line."
+ (let ((content)
+ (point-in-buffer)
+ (line)
+ (line-overlay)
+ ;;
https://emacs.stackexchange.com/questions/35680/stop-emacs-from-updating-display
+ (inhibit-redisplay t)
+ (buffer (get-buffer-create diff-hl-show-hunk-buffer-name)))
+
+ ;; Get differences
+ (save-window-excursion
+ (save-excursion
+ (with-current-buffer (diff-hl-show-hunk--compute-diffs)
+ (setq content (buffer-substring-no-properties (point-min)
(point-max)))
+ (setq point-in-buffer (point)))))
+
+ (with-current-buffer buffer
+ (read-only-mode -1)
+ (erase-buffer)
+ (insert content)
+
+ ;; Highlight the clicked line
+ (goto-char point-in-buffer)
+ (setq line-overlay (make-overlay (point-at-bol) (min (point-max) (1+
(point-at-eol)))))
+
+ ;; diff-mode
+ (diff-mode)
+ (read-only-mode 1)
+
+ ;; Find the hunk and narrow to it
+ (re-search-backward diff-hl-show-hunk-boundary nil 1)
+ (forward-line 1)
+ (let* ((start (point)))
+ (re-search-forward diff-hl-show-hunk-boundary nil 1)
+ (move-beginning-of-line nil)
+ (narrow-to-region start (point)))
+
+ ;; Store original content
+ (let ((content (buffer-string)))
+ (diff-hl-show-hunk--fill-original-content content))
+
+ ;; Come back to the clicked line
+ (goto-char (overlay-start line-overlay))
+ (setq line (line-number-at-pos)))
+
+ (list buffer line)))
+
+(defun diff-hl-show-hunk--click (event)
+ "Called when user clicks on margins. EVENT is click information."
+ (interactive "e")
+ ;; Go the click's position.
+ (posn-set-point (event-start event))
+ (diff-hl-show-hunk))
+
+(defvar diff-hl-show-hunk--inline-popup-map
+ (let ((map (make-sparse-keymap)))
+ (define-key map (kbd "p") #'diff-hl-show-hunk-previous)
+ (define-key map (kbd "n") #'diff-hl-show-hunk-next)
+ (define-key map (kbd "c") #'diff-hl-show-hunk-copy-original-text)
+ (define-key map (kbd "r") #'diff-hl-show-hunk-revert-hunk)
+ (define-key map (kbd "[") #'diff-hl-show-hunk-previous)
+ (define-key map (kbd "]") #'diff-hl-show-hunk-next)
+ (define-key map (kbd "{") #'diff-hl-show-hunk-previous)
+ (define-key map (kbd "}") #'diff-hl-show-hunk-next)
+ map))
+
+(defvar diff-hl-show-hunk--hide-function)
+
+;;;###autoload
+(defun diff-hl-show-hunk-inline-popup (buffer &optional _ignored-line)
+ "Implementation to show the hunk in a inline popup.
+BUFFER is a buffer with the hunk."
+ (diff-hl-inline-popup-hide)
+ (setq diff-hl-show-hunk--hide-function #'inline-popup-hide)
+ (let* ((lines (split-string (with-current-buffer buffer (buffer-string))
"[\n\r]+" ))
+ (smart-lines diff-hl-show-hunk-inline-popup-smart-lines)
+ (original-lines-number (cl-count-if (lambda (s) (string-prefix-p "-"
s)) lines))
+ (lines (if (string= (car (last lines)) "" ) (butlast lines) lines))
+ (lines (if (and (eq original-lines-number 0) smart-lines)
+ diff-hl-show-hunk--no-lines-removed-message
+ lines))
+ (overlay diff-hl-show-hunk--original-overlay)
+ (type (overlay-get overlay 'diff-hl-hunk-type))
+ (point (if (eq type 'delete) (overlay-start overlay) (overlay-end
overlay)))
+ (propertize-line (lambda (l)
+ (propertize l 'face
+ (cond ((string-prefix-p "+" l)
+ 'diff-added)
+ ((string-prefix-p "-" l)
+ 'diff-removed)))))
+ (propertized-lines (mapcar propertize-line lines)))
+
+ (save-excursion
+ ;; Save point in case the hunk is hidden, so next/previous works as
expected
+ ;; If the hunk is delete type, then don't hide the hunk
+ ;; (because the hunk is located in a non deleted line)
+ (when (and diff-hl-show-hunk-inline-popup-hide-hunk
+ (not (eq type 'delete)))
+ (let* ((invisible-overlay (make-overlay (overlay-start overlay)
+ (overlay-end overlay))))
+ ;; Make new overlay, since the diff-hl overlay can be changed by
diff-hl-flydiff
+ (overlay-put invisible-overlay 'invisible t)
+ ;; Change default hide popup function, to make the overlay visible
+ (setq diff-hl-show-hunk--hide-function
+ (lambda ()
+ (overlay-put invisible-overlay 'invisible nil)
+ (delete-overlay invisible-overlay)
+ (diff-hl-inline-popup-hide)))))
+ (let ((height
+ (when smart-lines
+ (when (not (eq 0 original-lines-number))
+ original-lines-number))))
+ (diff-hl-inline-popup-show
+ propertized-lines
+ (if (and (boundp 'diff-hl-reference-revision)
diff-hl-reference-revision)
+ (concat "Diff with " diff-hl-reference-revision)
+ "Diff with HEAD")
+ "(q)Quit (p)Previous (n)Next (r)Revert (c)Copy original"
+ diff-hl-show-hunk--inline-popup-map
+ #'diff-hl-show-hunk-hide
+ point
+ height))
+ (diff-hl-show-hunk--goto-hunk-overlay overlay))))
+
+(defun diff-hl-show-hunk-copy-original-text ()
+ "Extracts all the lines from BUFFER starting with '-' to the kill ring."
+ (interactive)
+ (kill-new diff-hl-show-hunk--original-content)
+ (message "Original hunk content added to kill-ring"))
+
+(defun diff-hl-show-hunk-revert-hunk ()
+ "Dismiss the popup and prompt to revert the current diff hunk."
+ (interactive)
+ (diff-hl-show-hunk-hide)
+ (diff-hl-revert-hunk))
+
+(defun diff-hl-show-hunk-ensure-hunk-visible (&optional goto-start)
+ "Ensure that the end of `diff-hl-show-hunk--original-overlay',
+and maybe the start (if GOTO-START), is visible."
+ (let* ((overlay diff-hl-show-hunk--original-overlay))
+ (when overlay
+ (goto-char (overlay-end overlay)))
+ ;; Window scrolls to position only on next redisplay
+ (redisplay t)
+ (when goto-start
+ (goto-char (overlay-start overlay)))))
+
+;;;###autoload
+(defun diff-hl-show-hunk-previous ()
+ "Go to previous hunk/change and show it."
+ (interactive)
+ (let* ((point (if diff-hl-show-hunk--original-overlay
+ (overlay-start diff-hl-show-hunk--original-overlay)
+ nil))
+ (previous-overlay (diff-hl-show-hunk--next-hunk t point)))
+ (if (not previous-overlay)
+ (message "There is no previous change")
+ (diff-hl-show-hunk-hide)
+ (diff-hl-show-hunk--goto-hunk-overlay previous-overlay)
+ (recenter)
+ (diff-hl-show-hunk))))
+
+(defun diff-hl-show-hunk--next-hunk (backward point)
+ "Same as `diff-hl-search-next-hunk', but in the current buffer
+of `diff-hl-show-hunk'."
+ (with-current-buffer (or diff-hl-show-hunk--original-buffer (current-buffer))
+ (diff-hl-search-next-hunk backward point)))
+
+(defun diff-hl-show-hunk--goto-hunk-overlay (overlay)
+ "Tries to display the whole overlay, and place the point at the
+end of the OVERLAY, so posframe/inline is placed below the hunk."
+ (goto-char (overlay-start overlay))
+ (redisplay)
+ (goto-char (1- (overlay-end overlay))))
+
+;;;###autoload
+(defun diff-hl-show-hunk-next ()
+ "Go to next hunk/change and show it."
+ (interactive)
+ (let* ((point (if diff-hl-show-hunk--original-overlay
+ (overlay-start diff-hl-show-hunk--original-overlay)
+ nil))
+ (next-overlay (diff-hl-show-hunk--next-hunk nil point)))
+ (if (not next-overlay)
+ (message "There is no next change")
+ (diff-hl-show-hunk-hide)
+ (diff-hl-show-hunk--goto-hunk-overlay next-overlay)
+ (recenter)
+ (diff-hl-show-hunk))))
+
+;;;###autoload
+(defun diff-hl-show-hunk ()
+ "Show the VC diff hunk at point.
+The backend is determined by `diff-hl-show-hunk-function'."
+ (interactive)
+
+ ;; Close any previous hunk
+ (save-excursion
+ (diff-hl-show-hunk-hide))
+
+ (cond
+ ((not (vc-backend buffer-file-name))
+ (user-error "The buffer is not under version control"))
+ ((not (diff-hl-hunk-overlay-at (point)))
+ (diff-hl-previous-hunk)))
+
+ (setq diff-hl-show-hunk--original-overlay nil)
+
+ ;; Store begining and end of hunk overlay
+ (let ((overlay (diff-hl-hunk-overlay-at (point))))
+ (when overlay
+ (let ((start (overlay-start overlay))
+ (end (overlay-end overlay))
+ (type (overlay-get overlay 'diff-hl-hunk-type)))
+ (setq diff-hl-show-hunk--original-overlay (make-overlay start end))
+ (overlay-put diff-hl-show-hunk--original-overlay 'diff-hl-hunk-type
type)))
+
+ (unless overlay
+ (user-error "Not in a hunk")))
+
+ (cond
+ ((not diff-hl-show-hunk-function)
+ (message "Please configure `diff-hl-show-hunk-function'")
+ (diff-hl-diff-goto-hunk))
+ ((let ((buffer-and-line (diff-hl-show-hunk-buffer)))
+ (setq diff-hl-show-hunk--original-buffer (current-buffer))
+ (setq diff-hl-show-hunk--original-window (selected-window))
+ (diff-hl-show-hunk-ensure-hunk-visible)
+ (apply diff-hl-show-hunk-function buffer-and-line))
+ ;; We could fall back to `diff-hl-diff-goto-hunk', but the
+ ;; current default should work in all environments (both GUI
+ ;; and terminal), and if something goes wrong we better show
+ ;; the error to the user.
+ )))
+
+;;;###autoload
+(define-minor-mode diff-hl-show-hunk-mouse-mode
+ "Enables the margin and fringe to show a posframe/popup with vc diffs when
clicked.
+By default, the posframe/popup shows only the current hunk, and
+the line of the hunk that matches the current position is
+highlighted. The face, border and other visual preferences are
+customizable. It can be also invoked with the command
+`diff-hl-show-hunk'
+\\{diff-hl-show-hunk-mouse-mode-map}"
+ :group 'diff-hl-show-hunk
+ :lighter "")
+
+;;;###autoload
+(define-globalized-minor-mode global-diff-hl-show-hunk-mouse-mode
+ diff-hl-show-hunk-mouse-mode
+ diff-hl-show-hunk-mouse-mode)
+
+;;;###autoload
+(define-minor-mode diff-hl-show-hunk-mode
+ "Enables a keymap with some commands of the `diff-hl-show-hunk' package
+\\{diff-hl-show-hunk-mode-map}"
+ :group 'diff-hl-show-hunk
+ :lighter "")
+
+;;;###autoload
+(define-globalized-minor-mode global-diff-hl-show-hunk-mode
+ diff-hl-show-hunk-mode
+ diff-hl-show-hunk-mode)
+
+(provide 'diff-hl-show-hunk)
+;;; diff-hl-show-hunk.el ends here
diff --git a/diff-hl.el b/diff-hl.el
index f12d4a7..7cb2f6d 100644
--- a/diff-hl.el
+++ b/diff-hl.el
@@ -358,6 +358,7 @@ performance when viewing such files in certain conditions."
(hook '(diff-hl-overlay-modified)))
(overlay-put h 'diff-hl t)
(overlay-put h 'diff-hl-hunk t)
+ (overlay-put h 'diff-hl-hunk-type type)
(overlay-put h 'modification-hooks hook)
(overlay-put h 'insert-in-front-hooks hook)
(overlay-put h 'insert-behind-hooks hook)))))))))
@@ -562,20 +563,26 @@ in the source file, or the last line of the hunk above
it."
when (overlay-get o 'diff-hl-hunk)
return o))
+(defun diff-hl-search-next-hunk (&optional backward point)
+ "Search the next hunk in the current buffer, or previous if BACKWARD."
+ (save-excursion
+ (when point
+ (goto-char point))
+ (catch 'found
+ (while (not (if backward (bobp) (eobp)))
+ (goto-char (if backward
+ (previous-overlay-change (point))
+ (next-overlay-change (point))))
+ (let ((o (diff-hl-hunk-overlay-at (point))))
+ (when (and o (= (overlay-start o) (point)))
+ (throw 'found o)))))))
+
(defun diff-hl-next-hunk (&optional backward)
"Go to the beginning of the next hunk in the current buffer."
(interactive)
- (let ((pos (save-excursion
- (catch 'found
- (while (not (if backward (bobp) (eobp)))
- (goto-char (if backward
- (previous-overlay-change (point))
- (next-overlay-change (point))))
- (let ((o (diff-hl-hunk-overlay-at (point))))
- (when (and o (= (overlay-start o) (point)))
- (throw 'found (overlay-start o)))))))))
- (if pos
- (goto-char pos)
+ (let ((overlay (diff-hl-search-next-hunk backward)))
+ (if overlay
+ (goto-char (overlay-start overlay))
(user-error "No further hunks found"))))
(defun diff-hl-previous-hunk ()
@@ -718,6 +725,72 @@ The value of this variable is a mode line template as in
(add-hook 'vc-checkin-hook 'diff-hl-dir-update t t)
(remove-hook 'vc-checkin-hook 'diff-hl-dir-update t)))
+(defun diff-hl-make-temp-file-name (buffer rev &optional manual)
+ "Return a backup file name for REV or the current version of BUFFER.
+If MANUAL is non-nil it means that a name for backups created by
+the user should be returned."
+ (let* ((auto-save-file-name-transforms
+ `((".*" ,temporary-file-directory t))))
+ (expand-file-name
+ (concat (make-auto-save-file-name)
+ ".~" (subst-char-in-string
+ ?/ ?_ rev)
+ (unless manual ".") "~")
+ temporary-file-directory)))
+
+(defun diff-hl-create-revision (file revision)
+ "Read REVISION of BUFFER into a buffer and return the buffer."
+ (let ((automatic-backup (diff-hl-make-temp-file-name file revision))
+ (filebuf (get-file-buffer file))
+ (filename (diff-hl-make-temp-file-name file revision 'manual)))
+ (unless (file-exists-p filename)
+ (if (file-exists-p automatic-backup)
+ (rename-file automatic-backup filename nil)
+ (with-current-buffer filebuf
+ (let ((coding-system-for-read 'no-conversion)
+ (coding-system-for-write 'no-conversion))
+ (condition-case nil
+ (with-temp-file filename
+ (let ((outbuf (current-buffer)))
+ ;; Change buffer to get local value of
+ ;; vc-checkout-switches.
+ (with-current-buffer filebuf
+ (vc-call find-revision file revision outbuf))))
+ (error
+ (when (file-exists-p filename)
+ (delete-file filename))))))))
+ filename))
+
+(defun diff-hl-working-revision (file)
+ "Like vc-working-revision, but always up-to-date"
+ (vc-file-setprop file 'vc-working-revision
+ (vc-call-backend (vc-backend file) 'working-revision file)))
+
+(defun diff-hl-diff-buffer-with-head (file &optional dest-buffer)
+ "Compute the differences between FILE and its associated file
+in head revision. The diffs are computed in the buffer
+DEST-BUFFER. This requires the external program `diff' to be in
+your `exec-path'."
+ (vc-ensure-vc-buffer)
+ (save-current-buffer
+ (let* ((dest-buffer (or dest-buffer "*diff-hl-diff-bufer-with-head*"))
+ (temporary-file-directory
+ (if (file-directory-p "/dev/shm/")
+ "/dev/shm/"
+ temporary-file-directory))
+ (rev (diff-hl-create-revision
+ file
+ (or diff-hl-reference-revision
+ (diff-hl-working-revision file)))))
+ ;; FIXME: When against staging, do it differently!
+ (diff-no-select rev (current-buffer) "-U 0 --strip-trailing-cr" 'noasync
+ (get-buffer-create dest-buffer))
+ (with-current-buffer dest-buffer
+ (let ((inhibit-read-only t))
+ ;; Function `diff-sentinel' adds a final line, so remove it
+ (delete-matching-lines "^Diff finished.*")))
+ (get-buffer-create dest-buffer))))
+
;;;###autoload
(defun turn-on-diff-hl-mode ()
"Turn on `diff-hl-mode' or `diff-hl-dir-mode' in a buffer if appropriate."
- [elpa] externals/diff-hl updated (4c46b3b -> ba1e50c), ELPA Syncer, 2021/02/22
- [elpa] externals/diff-hl 39b8455 06/10: Fold diff-hl-show-hunk-mode into the "main" mode, ELPA Syncer, 2021/02/22
- [elpa] externals/diff-hl 6c34a01 04/10: Minor touches, ELPA Syncer, 2021/02/22
- [elpa] externals/diff-hl 4460d1c 03/10: Use lexical-binding in two remaining filesg, ELPA Syncer, 2021/02/22
- [elpa] externals/diff-hl ba1e50c 10/10: Remove excessive redisplays, they caused blinking, ELPA Syncer, 2021/02/22
- [elpa] externals/diff-hl 414ccac 07/10: More reflowing of code, ELPA Syncer, 2021/02/22
- [elpa] externals/diff-hl 3aa5a7e 08/10: Make sure the "underlying face" does not affect the popup's display, ELPA Syncer, 2021/02/22
- [elpa] externals/diff-hl 679f557 01/10: New feature: diff-hl-show-hunk,
ELPA Syncer <=
- [elpa] externals/diff-hl ba905d8 02/10: Drop compatibility with Emacs < 25, ELPA Syncer, 2021/02/22
- [elpa] externals/diff-hl 033e880 05/10: Remove the gif, ELPA Syncer, 2021/02/22
- [elpa] externals/diff-hl 5c65ada 09/10: Fix one warning, ELPA Syncer, 2021/02/22