emacs-corfu-terminal/corfu-terminal.el

225 lines
8.6 KiB
EmacsLisp

;;; corfu-terminal.el --- Corfu popup on terminal -*- lexical-binding: t; -*-
;; Copyright (C) 2022 Akib Azmain Turja.
;; Author: Akib Azmain Turja <[email protected]>
;; 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