corfu-terminal.el (8856B)
1 ;;; corfu-terminal.el --- Corfu popup on terminal -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2022 Akib Azmain Turja. 4 5 ;; Author: Akib Azmain Turja <akib@disroot.org> 6 ;; Created: 2022-04-11 7 ;; Version: 0.7 8 ;; Package-Requires: ((emacs "26.1") (corfu "0.36") (popon "0.13")) 9 ;; Keywords: convenience 10 ;; Homepage: https://codeberg.org/akib/emacs-corfu-terminal 11 12 ;; This file is not part of GNU Emacs. 13 14 ;; This file is free software; you can redistribute it and/or modify 15 ;; it under the terms of the GNU General Public License as published by 16 ;; the Free Software Foundation; either version 3, or (at your option) 17 ;; any later version. 18 19 ;; This program is distributed in the hope that it will be useful, 20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 ;; GNU General Public License for more details. 23 24 ;; For a full copy of the GNU General Public License 25 ;; see <https://www.gnu.org/licenses/>. 26 27 ;;; Commentary: 28 29 ;; Corfu uses child frames to display candidates. This makes Corfu 30 ;; unusable on terminal. This package replaces that with popup/popon, 31 ;; which works everywhere. Use M-x corfu-terminal-mode to enable. 32 ;; You'll probably want to enable it only on terminal. In that case, 33 ;; put the following in your init file: 34 35 ;; (unless (display-graphic-p) 36 ;; (corfu-terminal-mode +1)) 37 38 ;;; Code: 39 40 (require 'subr-x) 41 (require 'corfu) 42 (require 'popon) 43 (require 'cl-lib) 44 45 (defgroup corfu-terminal nil 46 "Corfu popup on terminal." 47 :group 'convenience 48 :link '(url-link "https://codeberg.org/akib/emacs-corfu-terminal") 49 :prefix "corfu-terminal-") 50 51 (defcustom corfu-terminal-enable-on-minibuffer t 52 "Non-nil means enable corfu-terminal on minibuffer." 53 :type 'boolean) 54 55 (defcustom corfu-terminal-resize-minibuffer t 56 "Non-nil means resize minibuffer to show popup." 57 :type 'boolean) 58 59 (defcustom corfu-terminal-position-right-margin 0 60 "Number of columns of margin at the right of window. 61 62 Always keep the popup this many columns away from the right edge of 63 the window. 64 65 Note: If the popup breaks or crosses the right edge of window, you may 66 set this variable to warkaround it. But remember, that's a *bug*, so 67 if that ever happens to you please report the issue at 68 https://codeberg.org/akib/emacs-corfu-terminal/issues." 69 :type 'integer) 70 71 (defcustom corfu-terminal-disable-on-gui t 72 "Don't use popon UI on GUI." 73 :type '(choice (const :tag "Yes" t) 74 (const :tag "No" nil))) 75 76 (defvar corfu-terminal--popon nil 77 "Popon object.") 78 79 (defvar corfu-terminal--last-position nil 80 "Position of last popon, and some data to make sure that's valid.") 81 82 (cl-defmethod corfu--popup-support-p (&context (corfu-terminal-mode 83 (eql t))) 84 "Return whether corfu-terminal supports showing popon now." 85 (or (not (minibufferp)) 86 corfu-terminal-enable-on-minibuffer 87 (and corfu-terminal-disable-on-gui 88 (display-graphic-p)))) 89 90 (cl-defmethod corfu--popup-hide (&context (corfu-terminal-mode 91 (eql t))) 92 "Hide popup. 93 94 If `corfu-terminal-disable-on-gui' is non-nil and `display-graphic-p' 95 returns non-nil then call FN instead, where FN should be the original 96 definition in Corfu." 97 (if (and corfu-terminal-disable-on-gui 98 (display-graphic-p)) 99 (cl-call-next-method) 100 (when corfu-terminal--popon 101 (setq corfu-terminal--popon 102 (popon-kill corfu-terminal--popon))))) 103 104 (cl-defmethod corfu--popup-show ( pos off width lines 105 &context (corfu-terminal-mode 106 (eql t)) 107 &optional curr lo bar) 108 "Show popup at OFF columns before POS. 109 110 Show LINES, a list of lines. Highlight CURRth line as current 111 selection. Show a vertical scroll bar of size BAR + 1 from LOth line. 112 113 If `corfu-terminal-disable-on-gui' is non-nil and `display-graphic-p' 114 returns non-nil then call FN instead, where FN should be the original 115 definition in Corfu." 116 (if (and corfu-terminal-disable-on-gui 117 (display-graphic-p)) 118 (cl-call-next-method) 119 (corfu--popup-hide) ; Hide the popup first. 120 (when (and (window-minibuffer-p) 121 (< (/ (window-body-height nil 'pixelwise) 122 (default-font-height)) 123 (1+ (length lines))) 124 corfu-terminal-resize-minibuffer 125 (not (frame-root-window-p (selected-window)))) 126 (window-resize nil (- (1+ (length lines)) 127 (/ (window-body-height nil 'pixelwise) 128 (default-font-height))))) 129 (let* ((bar-width (ceiling (* (default-font-width) 130 corfu-bar-width))) 131 (margin-left-width (ceiling (* (default-font-width) 132 corfu-left-margin-width))) 133 (margin-right-width (max (ceiling 134 (* (default-font-width) 135 corfu-right-margin-width)) 136 bar-width)) 137 (scroll-bar 138 (when (< 0 bar-width) 139 (if (display-graphic-p) 140 (concat 141 (propertize 142 " " 'display 143 `(space 144 :width (,(- margin-right-width bar-width)))) 145 (propertize " " 'display 146 `(space :width (,bar-width)) 147 'face 'corfu-bar)) 148 (concat 149 (make-string (- margin-right-width bar-width) ?\ ) 150 (propertize (make-string bar-width ?\ ) 'face 151 'corfu-bar))))) 152 (margin-left 153 (when (> margin-left-width 0) 154 (if (display-graphic-p) 155 (propertize 156 " " 'display `(space :width (,margin-left-width))) 157 (make-string margin-left-width ?\ )))) 158 (margin-right 159 (when (> margin-right-width 0) 160 (if (display-graphic-p) 161 (propertize 162 " " 'display `(space :width (,margin-right-width))) 163 (make-string margin-right-width ?\ )))) 164 (popon-width 165 (if (display-graphic-p) 166 (+ width (round (/ (+ margin-left-width 167 margin-right-width) 168 (default-font-width)))) 169 (+ width margin-left-width margin-right-width))) 170 (popon-pos 171 (if (equal (cdr corfu-terminal--last-position) 172 (list (posn-point pos) popon-width 173 (window-start) (buffer-modified-tick))) 174 (car corfu-terminal--last-position) 175 (let ((x-y (popon-x-y-at-posn pos))) 176 (cons 177 (max 178 (min (- (car x-y) (+ off margin-left-width)) 179 (- (window-max-chars-per-line) 180 corfu-terminal-position-right-margin 181 popon-width)) 182 0) 183 (if (and (< (/ (window-body-height nil 'pixelwise) 184 (default-font-height)) 185 (+ (1+ (cdr x-y)) (length lines))) 186 (>= (cdr x-y) (length lines))) 187 (- (cdr x-y) (length lines)) 188 (1+ (cdr x-y)))))))) 189 (setq corfu-terminal--last-position 190 (list popon-pos (posn-point pos) popon-width 191 (window-start) (buffer-modified-tick))) 192 (setq corfu-terminal--popon 193 (popon-create 194 (cons 195 (string-join 196 (seq-map-indexed 197 (lambda (line line-number) 198 (let ((str 199 (concat 200 margin-left line 201 (make-string (- width (string-width line)) 202 ?\ ) 203 (if (and lo (<= lo line-number (+ lo bar))) 204 scroll-bar 205 margin-right)))) 206 (add-face-text-property 0 (length str) 207 (if (eq line-number curr) 208 'corfu-current 209 'corfu-default) 210 t str) 211 str)) 212 lines) 213 "\n") 214 popon-width) 215 popon-pos)) 216 nil))) 217 218 ;;;###autoload 219 (define-minor-mode corfu-terminal-mode 220 "Corfu popup on terminal." 221 :global t 222 :group 'corfu-terminal) 223 224 (provide 'corfu-terminal) 225 ;;; corfu-terminal.el ends here