diff --git a/diff-hl-inline-popup.el b/diff-hl-inline-popup.el deleted file mode 100644 index 48a6f991..00000000 --- a/diff-hl-inline-popup.el +++ /dev/null @@ -1,288 +0,0 @@ -;;; 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 - -;; 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 . - -;;; 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--ensure-enough-lines (pos content-height) - "Ensure there is enough lines below POS to show the inline popup. -CONTENT-HEIGHT specifies the height of the popup." - (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. -Compute it 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. -Compute it from 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. -Compute it from 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) - "" - (concat "\n" (propertize (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))) - ;; https://debbugs.gnu.org/38563, `company--replacement-string'. - (add-face-text-property 0 (length str) 'default t str) - (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 "") #'diff-hl-inline-popup--popup-pageup) - (define-key map (kbd "M-v") #'diff-hl-inline-popup--popup-pageup) - (define-key map (kbd "") #'diff-hl-inline-popup--popup-pagedown) - (define-key map (kbd "C-v") #'diff-hl-inline-popup--popup-pagedown) - (define-key map (kbd "") #'diff-hl-inline-popup--popup-up) - (define-key map (kbd "C-p") #'diff-hl-inline-popup--popup-up) - (define-key map (kbd "") #'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 "") #'diff-hl-inline-popup--popup-up) - (define-key map (kbd "") #'diff-hl-inline-popup--popup-up) - (define-key map (kbd "") #'diff-hl-inline-popup--popup-down) - (define-key map (kbd "") #'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 (line-end-position))) - (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-inline.el b/diff-hl-show-hunk-inline.el new file mode 100644 index 00000000..79b2d108 --- /dev/null +++ b/diff-hl-show-hunk-inline.el @@ -0,0 +1,368 @@ +;;; diff-hl-show-hunk-inline.el --- inline popup using phantom overlays -*- lexical-binding: t -*- + +;; Copyright (C) 2020-2021 Free Software Foundation, Inc. + +;; Author: Álvaro González + +;; 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 . + +;;; Commentary: +;; Shows inline popups using phantom overlays. The lines of the popup +;; can be scrolled. +;;; Code: + +(require 'diff-hl-show-hunk) +(eval-when-compile + (require 'subr-x)) + +(define-obsolete-variable-alias 'diff-hl-show-hunk-inline-popup-hide-hunk 'diff-hl-show-hunk-inline-hide-hunk "1.11.0") +(defcustom diff-hl-show-hunk-inline-hide-hunk nil + "If t, the inline popup is shown over the hunk, hiding it." + :type 'boolean + :group 'diff-hl-show-hunk) + +(define-obsolete-variable-alias 'diff-hl-show-hunk-inline-popup-smart-lines 'diff-hl-show-hunk-inline-smart-lines "1.11.0") +(defcustom diff-hl-show-hunk-inline-smart-lines t + "If t, the 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 + :group 'diff-hl-show-hunk) + +(defvar diff-hl-show-hunk-inline--current-popup nil "The overlay of the current inline popup.") +(defvar diff-hl-show-hunk-inline--current-lines nil "A list of the lines to show in the popup.") +(defvar diff-hl-show-hunk-inline--current-index nil "First line showed in popup.") +(defvar diff-hl-show-hunk-inline--invoking-command nil "Command that invoked the popup.") +(defvar diff-hl-show-hunk-inline--current-footer nil "String to be displayed in the footer.") +(defvar diff-hl-show-hunk-inline--current-header nil "String to be displayed in the header.") +(defvar diff-hl-show-hunk-inline--height nil "Height of the popup.") +(defvar diff-hl-show-hunk-inline--current-custom-keymap nil "Keymap to be added to the keymap of the inline popup.") +(defvar diff-hl-show-hunk-inline--close-hook nil "Function to be called when the popup closes.") + +(make-variable-buffer-local 'diff-hl-show-hunk-inline--current-popup) +(make-variable-buffer-local 'diff-hl-show-hunk-inline--current-lines) +(make-variable-buffer-local 'diff-hl-show-hunk-inline--current-index) +(make-variable-buffer-local 'diff-hl-show-hunk-inline--current-header) +(make-variable-buffer-local 'diff-hl-show-hunk-inline--current-footer) +(make-variable-buffer-local 'diff-hl-show-hunk-inline--invoking-command) +(make-variable-buffer-local 'diff-hl-show-hunk-inline--current-custom-keymap) +(make-variable-buffer-local 'diff-hl-show-hunk-inline--height) +(make-variable-buffer-local 'diff-hl-show-hunk-inline--close-hook) + +(defun diff-hl-show-hunk-inline--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-show-hunk-inline--ensure-enough-lines (pos content-height) + "Ensure there is enough lines below POS to show the inline popup. +CONTENT-HEIGHT specifies the height of the popup." + (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-show-hunk-inline--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-show-hunk-inline--current-lines))) + (max-size (- (/(window-height) 2) 3))) + (min content-size max-size))) + +(defun diff-hl-show-hunk-inline--compute-content-lines (lines index window-size) + "Compute the lines to show in the popup. +Compute it 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-show-hunk-inline--splice lines index window-size))) + +(defun diff-hl-show-hunk-inline--compute-header (width &optional header) + "Compute the header of the popup. +Compute it from some WIDTH, and some optional HEADER text." + (let* ((scroll-indicator (if (eq diff-hl-show-hunk-inline--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-show-hunk-inline--separator new-width) + header scroll-indicator ) + 'face '(:underline t)))) + (concat line "\n") )) + +(defun diff-hl-show-hunk-inline--compute-footer (width &optional footer) + "Compute the header of the popup. +Compute it from some WIDTH, and some optional FOOTER text." + (let* ((scroll-indicator (if (>= diff-hl-show-hunk-inline--current-index + (- (length diff-hl-show-hunk-inline--current-lines) + diff-hl-show-hunk-inline--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) + "" + (concat "\n" (propertize (diff-hl-show-hunk-inline--separator width) + 'face '(:underline t))))) + (line (propertize (concat (diff-hl-show-hunk-inline--separator new-width) + footer scroll-indicator) + 'face '(:overline t)))) + (concat blank-line "\n" line))) + +(defun diff-hl-show-hunk-inline--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-show-hunk-inline--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-show-hunk-inline--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-show-hunk-inline--available-width)) + (content-lines (diff-hl-show-hunk-inline--compute-content-lines lines index window-size)) + (header (diff-hl-show-hunk-inline--compute-header width header)) + (footer (diff-hl-show-hunk-inline--compute-footer width footer))) + (concat header (string-join content-lines "\n") footer "\n"))) + +(defun diff-hl-show-hunk-inline-scroll-to (index) + "Scroll the inline popup to make visible the line at position INDEX." + (when diff-hl-show-hunk-inline--current-popup + (setq diff-hl-show-hunk-inline--current-index (max 0 (min index (- (length diff-hl-show-hunk-inline--current-lines) diff-hl-show-hunk-inline--height)))) + (let* ((str (diff-hl-show-hunk-inline--compute-popup-str + diff-hl-show-hunk-inline--current-lines + diff-hl-show-hunk-inline--current-index + diff-hl-show-hunk-inline--height + diff-hl-show-hunk-inline--current-header + diff-hl-show-hunk-inline--current-footer))) + ;; https://debbugs.gnu.org/38563, `company--replacement-string'. + (add-face-text-property 0 (length str) 'default t str) + (overlay-put diff-hl-show-hunk-inline--current-popup 'after-string str)))) + +(defun diff-hl-show-hunk-inline--popup-down() + "Scrolls one line down." + (interactive) + (diff-hl-show-hunk-inline-scroll-to (1+ diff-hl-show-hunk-inline--current-index) )) + +(defun diff-hl-show-hunk-inline--popup-up() + "Scrolls one line up." + (interactive) + (diff-hl-show-hunk-inline-scroll-to (1- diff-hl-show-hunk-inline--current-index) )) + +(defun diff-hl-show-hunk-inline--popup-pagedown() + "Scrolls one page down." + (interactive) + (diff-hl-show-hunk-inline-scroll-to (+ diff-hl-show-hunk-inline--current-index diff-hl-show-hunk-inline--height) )) + +(defun diff-hl-show-hunk-inline--popup-pageup() + "Scrolls one page up." + (interactive) + (diff-hl-show-hunk-inline-scroll-to (- diff-hl-show-hunk-inline--current-index diff-hl-show-hunk-inline--height) )) + +(defvar diff-hl-show-hunk-inline-transient-mode-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "") #'diff-hl-show-hunk-inline--popup-pageup) + (define-key map (kbd "M-v") #'diff-hl-show-hunk-inline--popup-pageup) + (define-key map (kbd "") #'diff-hl-show-hunk-inline--popup-pagedown) + (define-key map (kbd "C-v") #'diff-hl-show-hunk-inline--popup-pagedown) + (define-key map (kbd "") #'diff-hl-show-hunk-inline--popup-up) + (define-key map (kbd "C-p") #'diff-hl-show-hunk-inline--popup-up) + (define-key map (kbd "") #'diff-hl-show-hunk-inline--popup-down) + (define-key map (kbd "C-n") #'diff-hl-show-hunk-inline--popup-down) + (define-key map (kbd "C-g") #'diff-hl-show-hunk-inline-hide) + (define-key map [escape] #'diff-hl-show-hunk-inline-hide) + (define-key map (kbd "q") #'diff-hl-show-hunk-inline-hide) + ;;http://ergoemacs.org/emacs/emacs_mouse_wheel_config.html + (define-key map (kbd "") #'diff-hl-show-hunk-inline--popup-up) + (define-key map (kbd "") #'diff-hl-show-hunk-inline--popup-up) + (define-key map (kbd "") #'diff-hl-show-hunk-inline--popup-down) + (define-key map (kbd "") #'diff-hl-show-hunk-inline--popup-down) + map) + "Keymap for command `diff-hl-show-hunk-inline-transient-mode'. +Capture all the vertical movement of the point, and converts it +to scroll in the popup") + +(defun diff-hl-show-hunk-inline--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-show-hunk-inline--current-custom-keymap + diff-hl-show-hunk-inline-transient-mode-map ) t)) + (invoking (eq command diff-hl-show-hunk-inline--invoking-command))) + (or keys invoking))) + +(defun diff-hl-show-hunk-inline--post-command-hook () + "Called each time a command is executed." + (let ((allowed-command (or + (diff-hl-show-hunk-ignorable-command-p this-command) + (string-match-p "diff-hl-show-hunk-inline-" (symbol-name this-command)) + (diff-hl-show-hunk-inline--ignorable-command-p this-command)))) + (unless allowed-command + (diff-hl-show-hunk-inline-hide)))) + +(define-minor-mode diff-hl-show-hunk-inline-transient-mode + "Temporal minor mode to control an inline popup" + :global nil + (remove-hook 'post-command-hook #'diff-hl-show-hunk-inline--post-command-hook t) + (set-keymap-parent diff-hl-show-hunk-inline-transient-mode-map nil) + + (when diff-hl-show-hunk-inline-transient-mode + (set-keymap-parent diff-hl-show-hunk-inline-transient-mode-map + diff-hl-show-hunk-inline--current-custom-keymap) + (add-hook 'post-command-hook #'diff-hl-show-hunk-inline--post-command-hook 0 t))) + +;;;###autoload +(defun diff-hl-show-hunk-inline-hide() + "Hide the current inline popup." + (interactive) + (when diff-hl-show-hunk-inline-transient-mode + (diff-hl-show-hunk-inline-transient-mode -1)) + (when diff-hl-show-hunk-inline--close-hook + (funcall diff-hl-show-hunk-inline--close-hook) + (setq diff-hl-show-hunk-inline--close-hook nil)) + (when diff-hl-show-hunk-inline--current-popup + (delete-overlay diff-hl-show-hunk-inline--current-popup) + (setq diff-hl-show-hunk-inline--current-popup nil))) + +;;;###autoload +(defun diff-hl-show-hunk-inline-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-show-hunk-inline--current-popup + (delete-overlay diff-hl-show-hunk-inline--current-popup) + (setq diff-hl-show-hunk-inline--current-popup nil)) + + (when (< (diff-hl-show-hunk-inline--compute-content-height 99) 2) + (user-error "There is no enough vertical space to show the inline popup")) + (let* ((the-point (or point (line-end-position))) + (the-buffer (current-buffer)) + (overlay (make-overlay the-point the-point the-buffer))) + (overlay-put overlay 'phantom t) + (overlay-put overlay 'diff-hl-show-hunk-inline t) + (setq diff-hl-show-hunk-inline--current-popup overlay) + + (setq diff-hl-show-hunk-inline--current-lines + (mapcar (lambda (s) (replace-regexp-in-string "\n" " " s)) lines)) + (setq diff-hl-show-hunk-inline--current-header header) + (setq diff-hl-show-hunk-inline--current-footer footer) + (setq diff-hl-show-hunk-inline--invoking-command this-command) + (setq diff-hl-show-hunk-inline--current-custom-keymap keymap) + (setq diff-hl-show-hunk-inline--close-hook close-hook) + (setq diff-hl-show-hunk-inline--height (diff-hl-show-hunk-inline--compute-content-height height)) + (setq diff-hl-show-hunk-inline--height (min diff-hl-show-hunk-inline--height + (length diff-hl-show-hunk-inline--current-lines))) + ;; (diff-hl-show-hunk-inline--ensure-enough-lines point diff-hl-show-hunk-inline--height) + (diff-hl-show-hunk-inline-transient-mode 1) + (diff-hl-show-hunk-inline-scroll-to 0) + overlay)) + +(defun diff-hl-show-hunk-inline--hide-all () + "Testing purposes, use in case some inline popups get stuck in a buffer." + (interactive) + (when diff-hl-show-hunk-inline-transient-mode + (diff-hl-show-hunk-inline-transient-mode -1)) + (setq diff-hl-show-hunk-inline--current-popup nil) + (let* ((all-overlays (overlays-in (point-min) (point-max))) + (overlays (cl-remove-if-not (lambda (o)(overlay-get o 'diff-hl-show-hunk-inline)) all-overlays))) + (dolist (o overlays) + (delete-overlay o)))) + +;;;###autoload +(defun diff-hl-show-hunk-inline (buffer &optional _ignored-line) + "Implementation to show the hunk in a inline popup. +BUFFER is a buffer with the hunk." + (diff-hl-show-hunk-inline-hide) + (setq diff-hl-show-hunk--hide-function #'diff-hl-show-hunk-inline-hide) + (let* ((lines (split-string (with-current-buffer buffer (buffer-string)) "[\n\r]+" )) + (smart-lines diff-hl-show-hunk-inline-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-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-show-hunk-inline-hide))))) + (diff-hl-show-hunk--goto-hunk-overlay overlay) + (let ((height + (when smart-lines + (when (not (eq 0 original-lines-number)) + original-lines-number))) + (footer "(q)Quit (p)Previous (n)Next (r)Revert (c)Copy original")) + (unless diff-hl-show-staged-changes + (setq footer (concat footer " (S)Stage"))) + (diff-hl-show-hunk-inline-show + propertized-lines + (if (and (boundp 'diff-hl-reference-revision) diff-hl-reference-revision) + (concat "Diff with " diff-hl-reference-revision) + "Diff with HEAD") + footer + diff-hl-show-hunk-map + #'diff-hl-show-hunk-hide + point + height))))) + +(define-obsolete-function-alias 'diff-hl-show-hunk-inline-popup 'diff-hl-show-hunk-inline "1.11.0") + +(provide 'diff-hl-show-hunk-inline) + +;;; diff-hl-show-hunk-inline ends here diff --git a/diff-hl-show-hunk.el b/diff-hl-show-hunk.el index 3e82e102..5085dad7 100644 --- a/diff-hl-show-hunk.el +++ b/diff-hl-show-hunk.el @@ -22,9 +22,9 @@ ;;; Commentary: ;; `diff-hl-show-hunk' shows a popup with the modification hunk at point. -;; `diff-hl-show-hunk-function' points to 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-function' points to the backend used to show the hunk. +;; Its default value is `diff-hl-show-hunk-inline', that shows diffs inline +;; using overlay. There is another built-in backend: ;; `diff-hl-show-hunk-posframe' (based on posframe). ;; ;; `diff-hl-show-hunk-mouse-mode' adds interaction on clicking in the @@ -36,9 +36,31 @@ ;;; Code: -(require 'diff-hl-inline-popup) (require 'diff-hl) +(defgroup diff-hl-show-hunk nil + "Show vc diffs in a posframe or popup." + :group 'diff-hl) + +(defcustom diff-hl-show-hunk-ignorable-commands + '(ignore + diff-hl-show-hunk + handle-switch-frame + diff-hl-show-hunk--click) + "Commands that will keep the hunk shown. +Any command not on this list will cause the hunk to be hidden." + :type '(repeat function) + :group 'diff-hl-show-hunk) + +(defcustom diff-hl-show-hunk-function 'diff-hl-show-hunk-inline + "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-mouse-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd " ") 'diff-hl-show-hunk--click) @@ -66,31 +88,16 @@ (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 "<>")) -(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 +(defcustom diff-hl-show-hunk-function 'diff-hl-show-hunk-inline "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 inline" diff-hl-show-hunk-inline) (const :tag "Show using posframe" diff-hl-show-hunk-posframe))) (defvar diff-hl-show-hunk--hide-function nil @@ -123,7 +130,7 @@ corresponding to the clicked line in the original buffer." (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))) + (member command diff-hl-show-hunk-ignorable-commands)) (defun diff-hl-show-hunk--compute-diffs () "Compute diffs using functions of diff-hl. @@ -225,68 +232,6 @@ Returns a list with the buffer and the line number of the clicked line." (define-key map (kbd "S") #'diff-hl-show-hunk-stage-hunk) 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 #'diff-hl-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))))) - (diff-hl-show-hunk--goto-hunk-overlay overlay) - (let ((height - (when smart-lines - (when (not (eq 0 original-lines-number)) - original-lines-number))) - (footer "(q)Quit (p)Previous (n)Next (r)Revert (c)Copy original")) - (unless diff-hl-show-staged-changes - (setq footer (concat footer " (S)Stage"))) - (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") - footer - diff-hl-show-hunk-map - #'diff-hl-show-hunk-hide - point - height)) - ))) - (defun diff-hl-show-hunk-copy-original-text () "Extracts all the lines from BUFFER starting with '-' to the kill ring." (interactive)