changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / emacs/lib/corfu-terminal.el

changeset 633: 88a3f078c185
author: Richard Westhaver <ellis@rwest.io>
date: Sun, 01 Sep 2024 21:00:12 -0400
permissions: -rw-r--r--
description: add corfu-terminal
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