nftable-migration
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