/ diff-hl-show-hunk-posframe.el
diff-hl-show-hunk-posframe.el
1 ;;; diff-hl-show-hunk-posframe.el --- posframe backend for diff-hl-show-hunk -*- lexical-binding: t -*- 2 3 ;; Copyright (C) 2020-2021 Free Software Foundation, Inc. 4 5 ;; Author: Álvaro González <alvarogonzalezsotillo@gmail.com> 6 7 ;; This file is part of GNU Emacs. 8 9 ;; GNU Emacs is free software: you can redistribute it and/or modify 10 ;; it under the terms of the GNU General Public License as published by 11 ;; the Free Software Foundation, either version 3 of the License, or 12 ;; (at your option) any later version. 13 14 ;; GNU Emacs is distributed in the hope that it will be useful, 15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 ;; GNU General Public License for more details. 18 19 ;; You should have received a copy of the GNU General Public License 20 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. 21 22 ;;; Commentary: 23 ;; 24 ;; This provides `diff-hl-show-hunk-posframe' than can be used as 25 ;; `diff-hl-show-hunk-function'. `posframe' is a runtime dependency, 26 ;; it is not required by this package, but it should be installed. 27 ;; 28 ;;; Code: 29 30 (require 'diff-hl-show-hunk) 31 32 ;; This package uses some runtime dependencies, so we need to declare 33 ;; the external functions and variables 34 (declare-function posframe-workable-p "posframe") 35 (declare-function posframe-show "posframe") 36 (defvar posframe-mouse-banish) 37 38 (defgroup diff-hl-show-hunk-posframe nil 39 "Show vc diffs in a posframe." 40 :group 'diff-hl-show-hunk) 41 42 (defcustom diff-hl-show-hunk-posframe-show-header-line t 43 "Show some useful buttons at the top of the diff-hl posframe." 44 :type 'boolean) 45 46 (defcustom diff-hl-show-hunk-posframe-internal-border-width 2 47 "Internal border width of the posframe." 48 :type 'integer) 49 50 (defcustom diff-hl-show-hunk-posframe-internal-border-color "#00ffff" 51 "Internal border color of the posframe." 52 :type 'color) 53 54 (defcustom diff-hl-show-hunk-posframe-poshandler nil 55 "Poshandler of the posframe (see `posframe-show`)." 56 :type '(choice function 57 (const :tag "None" nil))) 58 59 (defcustom diff-hl-show-hunk-posframe-parameters nil 60 "The frame parameters used by helm-posframe." 61 :type '(choice string 62 (const :tag "None" nil))) 63 64 (defface diff-hl-show-hunk-posframe '((t nil)) 65 "Face for the posframe buffer. 66 Customize it to change the base properties of the text.") 67 68 (defface diff-hl-show-hunk-posframe-button-face '((t . (:height 0.9))) 69 "Face for the posframe buttons" ) 70 71 (defvar diff-hl-show-hunk--frame nil "The postframe frame used in function `diff-hl-show-hunk-posframe'.") 72 (defvar diff-hl-show-hunk--original-frame nil "The frame from which the hunk is shown.") 73 74 (defun diff-hl-show-hunk--posframe-hide () 75 "Hide the posframe and clean up buffer." 76 (interactive) 77 (diff-hl-show-hunk-posframe--transient-mode -1) 78 (when (frame-live-p diff-hl-show-hunk--frame) 79 (make-frame-invisible diff-hl-show-hunk--frame))) 80 81 (defvar diff-hl-show-hunk-posframe--transient-mode-map 82 (let ((map (make-sparse-keymap))) 83 (define-key map [escape] #'diff-hl-show-hunk-hide) 84 (define-key map (kbd "q") #'diff-hl-show-hunk-hide) 85 (define-key map (kbd "C-g") #'diff-hl-show-hunk-hide) 86 (set-keymap-parent map diff-hl-show-hunk-map) 87 map) 88 "Keymap for command `diff-hl-show-hunk-posframe--transient-mode'.") 89 90 (define-minor-mode diff-hl-show-hunk-posframe--transient-mode 91 "Temporal minor mode to control diff-hl posframe." 92 :lighter "" 93 :global t 94 (if diff-hl-show-hunk-posframe--transient-mode 95 (add-hook 'post-command-hook #'diff-hl-show-hunk--posframe-post-command-hook nil) 96 (remove-hook 'post-command-hook #'diff-hl-show-hunk--posframe-post-command-hook nil))) 97 98 (defun diff-hl-show-hunk--posframe-post-command-hook () 99 "Called for each command while in `diff-hl-show-hunk-posframe--transient-mode." 100 (let* ((allowed-command (or 101 (diff-hl-show-hunk-ignorable-command-p this-command) 102 (and (symbolp this-command) 103 (string-match-p "diff-hl-" (symbol-name this-command))))) 104 (event-in-frame (eq last-event-frame diff-hl-show-hunk--frame)) 105 (has-focus (and (frame-live-p diff-hl-show-hunk--frame) 106 (functionp 'frame-focus-state) 107 (eq (frame-focus-state diff-hl-show-hunk--frame) t))) 108 (still-visible (or event-in-frame allowed-command has-focus))) 109 (unless still-visible 110 (diff-hl-show-hunk--posframe-hide)))) 111 112 (defun diff-hl-show-hunk--posframe-button (text help-echo action) 113 "Make a string implementing a button with TEXT and a HELP-ECHO. 114 The button calls an ACTION." 115 (concat 116 (propertize (concat " " text " ") 117 'help-echo (if action help-echo "Not available") 118 'face 'diff-hl-show-hunk-posframe-button-face 119 'mouse-face (when action '(:box (:style released-button))) 120 'keymap (when action 121 (let ((map (make-sparse-keymap))) 122 (define-key map (kbd "<header-line> <mouse-1>") action) 123 map))) 124 " ")) 125 126 (defun diff-hl-show-hunk-posframe--header-line () 127 "Make the header line of the posframe." 128 (concat 129 (diff-hl-show-hunk--posframe-button 130 "⨯ Close" 131 "Close (\\[diff-hl-show-hunk-hide])" 132 #'diff-hl-show-hunk-hide) 133 (diff-hl-show-hunk--posframe-button 134 "⬆ Previous change" 135 "Previous change in hunk (\\[diff-hl-show-hunk-previous])" 136 #'diff-hl-show-hunk-previous) 137 138 (diff-hl-show-hunk--posframe-button 139 "⬇ Next change" 140 "Next change in hunk (\\[diff-hl-show-hunk-next])" 141 #'diff-hl-show-hunk-next) 142 143 (diff-hl-show-hunk--posframe-button 144 "⊚ Copy original" 145 "Copy original (\\[diff-hl-show-hunk-copy-original-text])" 146 #'diff-hl-show-hunk-copy-original-text) 147 148 (diff-hl-show-hunk--posframe-button 149 "♻ Revert hunk" 150 "Revert hunk (\\[diff-hl-show-hunk-revert-hunk])" 151 #'diff-hl-show-hunk-revert-hunk) 152 153 (unless diff-hl-show-staged-changes 154 (diff-hl-show-hunk--posframe-button 155 "⊕ Stage hunk" 156 "Stage hunk (\\[diff-hl-show-hunk-stage-hunk])" 157 #'diff-hl-show-hunk-stage-hunk)) 158 )) 159 160 ;;;###autoload 161 (defun diff-hl-show-hunk-posframe (buffer &optional _line) 162 "Implementation to show the hunk in a posframe." 163 164 (unless (require 'posframe nil t) 165 (user-error 166 (concat 167 "`diff-hl-show-hunk-posframe' requires the `posframe' package." 168 " Please install it or customize `diff-hl-show-hunk-function'."))) 169 170 (unless (posframe-workable-p) 171 (user-error 172 "Package `posframe' is not workable. Please customize diff-hl-show-hunk-function")) 173 174 (setq diff-hl-show-hunk--hide-function #'diff-hl-show-hunk--posframe-hide) 175 176 ;; put an overlay to override read-only-mode keymap 177 (with-current-buffer buffer 178 ;; Change face size 179 (buffer-face-set 'diff-hl-show-hunk-posframe) 180 181 (let ((full-overlay (make-overlay 1 (1+ (buffer-size))))) 182 (overlay-put full-overlay 183 'keymap diff-hl-show-hunk-posframe--transient-mode-map))) 184 185 (setq posframe-mouse-banish nil) 186 (setq diff-hl-show-hunk--original-frame (selected-frame)) 187 188 (let* ((overlay diff-hl-show-hunk--original-overlay) 189 (type (overlay-get overlay 'diff-hl-hunk-type)) 190 (position (save-excursion 191 (goto-char (overlay-end overlay)) 192 (forward-line -1) 193 (point)))) 194 (setq 195 diff-hl-show-hunk--frame 196 (posframe-show buffer 197 :position position 198 :poshandler diff-hl-show-hunk-posframe-poshandler 199 :internal-border-width diff-hl-show-hunk-posframe-internal-border-width 200 :accept-focus t 201 ;; internal-border-color Doesn't always work, if not customize internal-border face 202 :internal-border-color diff-hl-show-hunk-posframe-internal-border-color 203 :hidehandler nil 204 ;; Sometimes, header-line is not taken into account, so put a min height and a min width 205 :min-width (when diff-hl-show-hunk-posframe-show-header-line 206 (length (diff-hl-show-hunk-posframe--header-line))) 207 :respect-header-line diff-hl-show-hunk-posframe-show-header-line 208 :respect-tab-line nil 209 :respect-mode-line nil 210 :y-pixel-offset (if (eq type 'delete) (- (default-line-height))) 211 :override-parameters diff-hl-show-hunk-posframe-parameters))) 212 213 (set-frame-parameter diff-hl-show-hunk--frame 'drag-internal-border t) 214 (set-frame-parameter diff-hl-show-hunk--frame 'drag-with-header-line t) 215 216 (with-selected-frame diff-hl-show-hunk--frame 217 (with-current-buffer buffer 218 (diff-hl-show-hunk-posframe--transient-mode 1) 219 (when diff-hl-show-hunk-posframe-show-header-line 220 (setq header-line-format (diff-hl-show-hunk-posframe--header-line))) 221 (goto-char (point-min)) 222 (setq buffer-quit-function #'diff-hl-show-hunk--posframe-hide) 223 (select-window (window-main-window diff-hl-show-hunk--frame)) 224 225 ;; Make cursor visible (mainly for selecting text in posframe) 226 (setq cursor-type 'box) 227 228 ;; Recenter around point 229 (recenter)))) 230 231 (provide 'diff-hl-show-hunk-posframe) 232 ;;; diff-hl-show-hunk-posframe.el ends here