auto-update-daily-20260202
  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
 62Always keep the popup this many columns away from the right edge of
 63the window.
 64
 65Note: If the popup breaks or crosses the right edge of window, you may
 66set this variable to warkaround it.  But remember, that's a *bug*, so
 67if that ever happens to you please report the issue at
 68https://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
 94If `corfu-terminal-disable-on-gui' is non-nil and  `display-graphic-p'
 95returns non-nil then call FN instead, where FN should be the original
 96definition 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
110Show LINES, a list of lines.  Highlight CURRth line as current
111selection.  Show a vertical scroll bar of size BAR + 1 from LOth line.
112
113If `corfu-terminal-disable-on-gui' is non-nil and  `display-graphic-p'
114returns non-nil then call FN instead, where FN should be the original
115definition 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