Commit d254cfe56759

Vincent Demeester <vincent@sbr.pm>
2025-05-20 22:29:10
tools/emacs/init: add corfu-terminal
Signed-off-by: Vincent Demeester <vincent@sbr.pm>
1 parent b70b96a
Changed files (1)
tools
emacs
mini
tools/emacs/mini/site-lisp/corfu-terminal.el
@@ -0,0 +1,225 @@
+;;; corfu-terminal.el --- Corfu popup on terminal -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022 Akib Azmain Turja.
+
+;; Author: Akib Azmain Turja <akib@disroot.org>
+;; Created: 2022-04-11
+;; Version: 0.7
+;; Package-Requires: ((emacs "26.1") (corfu "0.36") (popon "0.13"))
+;; Keywords: convenience
+;; Homepage: https://codeberg.org/akib/emacs-corfu-terminal
+
+;; This file is not part of GNU Emacs.
+
+;; This file is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation; either version 3, or (at your option)
+;; any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; For a full copy of the GNU General Public License
+;; see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Corfu uses child frames to display candidates.  This makes Corfu
+;; unusable on terminal.  This package replaces that with popup/popon,
+;; which works everywhere.  Use M-x corfu-terminal-mode to enable.
+;; You'll probably want to enable it only on terminal.  In that case,
+;; put the following in your init file:
+
+;;   (unless (display-graphic-p)
+;;     (corfu-terminal-mode +1))
+
+;;; Code:
+
+(require 'subr-x)
+(require 'corfu)
+(require 'popon)
+(require 'cl-lib)
+
+(defgroup corfu-terminal nil
+  "Corfu popup on terminal."
+  :group 'convenience
+  :link '(url-link "https://codeberg.org/akib/emacs-corfu-terminal")
+  :prefix "corfu-terminal-")
+
+(defcustom corfu-terminal-enable-on-minibuffer t
+  "Non-nil means enable corfu-terminal on minibuffer."
+  :type 'boolean)
+
+(defcustom corfu-terminal-resize-minibuffer t
+  "Non-nil means resize minibuffer to show popup."
+  :type 'boolean)
+
+(defcustom corfu-terminal-position-right-margin 0
+  "Number of columns of margin at the right of window.
+
+Always keep the popup this many columns away from the right edge of
+the window.
+
+Note: If the popup breaks or crosses the right edge of window, you may
+set this variable to warkaround it.  But remember, that's a *bug*, so
+if that ever happens to you please report the issue at
+https://codeberg.org/akib/emacs-corfu-terminal/issues."
+  :type 'integer)
+
+(defcustom corfu-terminal-disable-on-gui t
+  "Don't use popon UI on GUI."
+  :type '(choice (const :tag "Yes" t)
+                 (const :tag "No" nil)))
+
+(defvar corfu-terminal--popon nil
+  "Popon object.")
+
+(defvar corfu-terminal--last-position nil
+  "Position of last popon, and some data to make sure that's valid.")
+
+(cl-defmethod corfu--popup-support-p (&context (corfu-terminal-mode
+                                                (eql t)))
+  "Return whether corfu-terminal supports showing popon now."
+  (or (not (minibufferp))
+      corfu-terminal-enable-on-minibuffer
+      (and corfu-terminal-disable-on-gui
+           (display-graphic-p))))
+
+(cl-defmethod corfu--popup-hide (&context (corfu-terminal-mode
+                                           (eql t)))
+  "Hide popup.
+
+If `corfu-terminal-disable-on-gui' is non-nil and  `display-graphic-p'
+returns non-nil then call FN instead, where FN should be the original
+definition in Corfu."
+  (if (and corfu-terminal-disable-on-gui
+           (display-graphic-p))
+      (cl-call-next-method)
+    (when corfu-terminal--popon
+      (setq corfu-terminal--popon
+            (popon-kill corfu-terminal--popon)))))
+
+(cl-defmethod corfu--popup-show ( pos off width lines
+                                  &context (corfu-terminal-mode
+                                            (eql t))
+                                  &optional curr lo bar)
+  "Show popup at OFF columns before POS.
+
+Show LINES, a list of lines.  Highlight CURRth line as current
+selection.  Show a vertical scroll bar of size BAR + 1 from LOth line.
+
+If `corfu-terminal-disable-on-gui' is non-nil and  `display-graphic-p'
+returns non-nil then call FN instead, where FN should be the original
+definition in Corfu."
+  (if (and corfu-terminal-disable-on-gui
+           (display-graphic-p))
+      (cl-call-next-method)
+    (corfu--popup-hide) ; Hide the popup first.
+    (when (and (window-minibuffer-p)
+               (< (/ (window-body-height nil 'pixelwise)
+                     (default-font-height))
+                  (1+ (length lines)))
+               corfu-terminal-resize-minibuffer
+               (not (frame-root-window-p (selected-window))))
+      (window-resize nil (- (1+ (length lines))
+                            (/ (window-body-height nil 'pixelwise)
+                               (default-font-height)))))
+    (let* ((bar-width (ceiling (* (default-font-width)
+                                  corfu-bar-width)))
+           (margin-left-width (ceiling (* (default-font-width)
+                                          corfu-left-margin-width)))
+           (margin-right-width (max (ceiling
+                                     (* (default-font-width)
+                                        corfu-right-margin-width))
+                                    bar-width))
+           (scroll-bar
+            (when (< 0 bar-width)
+              (if (display-graphic-p)
+                  (concat
+                   (propertize
+                    " " 'display
+                    `(space
+                      :width (,(- margin-right-width bar-width))))
+                   (propertize " " 'display
+                               `(space :width (,bar-width))
+                               'face 'corfu-bar))
+                (concat
+                 (make-string (- margin-right-width bar-width) ?\ )
+                 (propertize (make-string bar-width ?\ ) 'face
+                             'corfu-bar)))))
+           (margin-left
+            (when (> margin-left-width 0)
+              (if (display-graphic-p)
+                  (propertize
+                   " " 'display `(space :width (,margin-left-width)))
+                (make-string margin-left-width ?\ ))))
+           (margin-right
+            (when (> margin-right-width 0)
+              (if (display-graphic-p)
+                  (propertize
+                   " " 'display `(space :width (,margin-right-width)))
+                (make-string margin-right-width ?\ ))))
+           (popon-width
+            (if (display-graphic-p)
+                (+ width (round (/ (+ margin-left-width
+                                      margin-right-width)
+                                   (default-font-width))))
+              (+ width margin-left-width margin-right-width)))
+           (popon-pos
+            (if (equal (cdr corfu-terminal--last-position)
+                       (list (posn-point pos) popon-width
+                             (window-start) (buffer-modified-tick)))
+                (car corfu-terminal--last-position)
+              (let ((x-y (popon-x-y-at-posn pos)))
+                (cons
+                 (max
+                  (min (- (car x-y) (+ off margin-left-width))
+                       (- (window-max-chars-per-line)
+                          corfu-terminal-position-right-margin
+                          popon-width))
+                  0)
+                 (if (and (< (/ (window-body-height nil 'pixelwise)
+                                (default-font-height))
+                             (+ (1+ (cdr x-y)) (length lines)))
+                          (>= (cdr x-y) (length lines)))
+                     (- (cdr x-y) (length lines))
+                   (1+ (cdr x-y))))))))
+      (setq corfu-terminal--last-position
+            (list popon-pos (posn-point pos) popon-width
+                  (window-start) (buffer-modified-tick)))
+      (setq corfu-terminal--popon
+            (popon-create
+             (cons
+              (string-join
+               (seq-map-indexed
+                (lambda (line line-number)
+                  (let ((str
+                         (concat
+                          margin-left line
+                          (make-string (- width (string-width line))
+                                       ?\ )
+                          (if (and lo (<= lo line-number (+ lo bar)))
+                              scroll-bar
+                            margin-right))))
+                    (add-face-text-property 0 (length str)
+                                            (if (eq line-number curr)
+                                                'corfu-current
+                                              'corfu-default)
+                                            t str)
+                    str))
+                lines)
+               "\n")
+              popon-width)
+             popon-pos))
+      nil)))
+
+;;;###autoload
+(define-minor-mode corfu-terminal-mode
+  "Corfu popup on terminal."
+  :global t
+  :group 'corfu-terminal)
+
+(provide 'corfu-terminal)
+;;; corfu-terminal.el ends here