# HG changeset patch # User Richard Westhaver # Date 1725238812 14400 # Node ID 88a3f078c185aa94c1573826b0de2979a3702616 # Parent bbd9024f2fe26acb034c63a51d9e700ed89772d0 add corfu-terminal diff -r bbd9024f2fe2 -r 88a3f078c185 emacs/lib/corfu-terminal.el --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/emacs/lib/corfu-terminal.el Sun Sep 01 21:00:12 2024 -0400 @@ -0,0 +1,225 @@ +;;; corfu-terminal.el --- Corfu popup on terminal -*- lexical-binding: t; -*- + +;; Copyright (C) 2022 Akib Azmain Turja. + +;; Author: Akib Azmain Turja +;; 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 . + +;;; 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