├── images ├── nova-corfu-example.png ├── nova-eldoc-example.png └── nova-vertico-example.png ├── nova-common-vars.el ├── nova-utils.el ├── nova-eldoc.el ├── nova-corfu-popupinfo.el ├── nova-corfu.el ├── nova-side-left.el ├── nova-top-center.el ├── nova.el ├── nova-vertico.el └── README.md /images/nova-corfu-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thisisran/nova/HEAD/images/nova-corfu-example.png -------------------------------------------------------------------------------- /images/nova-eldoc-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thisisran/nova/HEAD/images/nova-eldoc-example.png -------------------------------------------------------------------------------- /images/nova-vertico-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thisisran/nova/HEAD/images/nova-vertico-example.png -------------------------------------------------------------------------------- /nova-common-vars.el: -------------------------------------------------------------------------------- 1 | ;;; nova-common-vars.el --- common variables shared between each nova module -*- lexical-binding: t; -*- 2 | ;; Copyright (C) 2025 Free Software Foundation, Inc. 3 | ;; Author: blueranger1981 4 | ;; Maintainer: blueranger1981 5 | ;; URL: https://github.com/thisisran/nova-emacs 6 | ;; Version: 0.1.0 7 | ;; Package-Requires: ((emacs "27.1") (posframe "1.4.0")) 8 | ;; Keywords: convenience, posframe, nova 9 | 10 | ;; This file is not part of GNU Emacs. 11 | 12 | ;; GNU Emacs is free software: you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation, either version 3 of the License, or 15 | ;; (at your option) any later version. 16 | 17 | ;; GNU Emacs is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with GNU Emacs. If not, see . 24 | 25 | ;;; Commentary: 26 | 27 | ;;; Code: 28 | 29 | (defvar nova--frame-list nil "Keep track of all the nova (and associated posframes) frames.") 30 | 31 | (defvar-local nova--frame nil "The frame representing nova") 32 | (defvar-local nova--wrapped-posframe nil "The frame of the wrapped posframe") 33 | (defvar-local nova--name nil "Name that represents the nova frame.") 34 | (defvar-local nova--title nil "Title for the nova frame.") 35 | (defvar-local nova--style nil "The frame drawing style.") 36 | (defvar-local nova--last-size-position nil "Save last size and position of the nova frame.") 37 | 38 | (defvar-local nova-background-color (face-attribute 'default :background) "Background color for the nova frame.") 39 | (defvar-local nova-border-color "#3d5a80" "Border color for the nova frame.") 40 | (defvar-local nova-border-size 1 "Border size for the frame.") 41 | (defvar-local nova-title-color "#161c28" "Color of the title in the frame.") ; #e3b7bb 42 | (defvar-local nova-title-background-color "#6DB9EF" "Background color of sidebar titles.") 43 | (defvar-local nova-radius-x 18 "X radius for the rounds corners of the frame.") 44 | (defvar-local nova-radius-y 18 "Y radius for the rounds corners of the frame.") 45 | (defvar-local nova-min-height 10 "Minimum height (in lines)") 46 | (defvar-local nova-left-padding 15 "Left padding to use when wrapping a posframe.") 47 | (defvar-local nova-top-padding 0 "Top padding to use when wrapping a posframe.") 48 | (defvar-local nova-extra-height 2 "Extra height to add to the frame.") 49 | 50 | (provide 'nova-common-vars) 51 | ;;; nova-common-vars.el ends here 52 | -------------------------------------------------------------------------------- /nova-utils.el: -------------------------------------------------------------------------------- 1 | ;;; nova-utils.el --- Utility functions for the nova library -*- lexical-binding: t; -*- 2 | ;; Copyright (C) 2025 Free Software Foundation, Inc. 3 | ;; Author: blueranger1981 4 | ;; Maintainer: blueranger1981 5 | ;; URL: https://github.com/thisisran/nova-emacs 6 | ;; Version: 0.1.0 7 | ;; Package-Requires: ((emacs "27.1")) 8 | ;; Keywords: convenience, posframe, nova 9 | 10 | ;; This file is part of GNU Emacs. 11 | 12 | ;; GNU Emacs is free software: you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation, either version 3 of the License, or 15 | ;; (at your option) any later version. 16 | 17 | ;; GNU Emacs is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with GNU Emacs. If not, see . 24 | 25 | ;;; Commentary: 26 | ;; utility functions for the nova package 27 | 28 | ;;; Code: 29 | 30 | (defmacro nova--get-local (var name) 31 | "Get a buffer-local value for VAR in the NAME nova frame." 32 | `(buffer-local-value (quote ,var) (get-buffer ,name))) 33 | 34 | (defmacro nova--set-local (var value name) 35 | "Set a buffer-local VALUE for VAR in the NAME nova frame." 36 | `(with-current-buffer (get-buffer ,name) 37 | (setq-local ,var ,value))) 38 | 39 | (defun nova--pixels-to-char-width (pixels) 40 | "Convert PIXELS width to a char width." 41 | (nova--div-and-round pixels (frame-char-width))) 42 | 43 | (defun nova--frame-params-relative-update (frame args) 44 | "Update a frame's parameters by a relative amount specificed in ARGS" 45 | (let ((parameters (frame-parameters frame))) 46 | (while args 47 | (let* ((curr-arg (pop args)) 48 | (param-name (car curr-arg)) 49 | (param-new-value (cdr curr-arg)) 50 | (param-curr-value (cdr (assoc param-name parameters))) 51 | (param-combined (+ param-curr-value param-new-value))) 52 | (modify-frame-parameters frame `((,param-name . ,param-combined))))))) 53 | 54 | (defun nova--div-and-round (num denom) 55 | "Round and divide NUM by DENOM." 56 | (round (/ (float num) denom))) 57 | 58 | (cl-defun nova--get-size (name &key horizontal) 59 | "Get the width (HORIZONTAL is t) or height (HORIZONTAL is nil) of the frame associated with NAME." 60 | (let ((posframe-window (get-buffer-window name t))) 61 | (when (window-live-p posframe-window) 62 | (with-selected-window posframe-window 63 | (window-size posframe-window horizontal))))) 64 | 65 | (defun nova--update-params (plist &rest args) 66 | "Update PLIST with multiple key-value pairs from ARGS and return the new plist." 67 | (while args 68 | (setq plist (plist-put plist (pop args) (pop args)))) 69 | plist) 70 | 71 | (provide 'nova-utils) 72 | ;;; nova-utils.el ends here 73 | -------------------------------------------------------------------------------- /nova-eldoc.el: -------------------------------------------------------------------------------- 1 | ;;; nova-eldoc.el --- Display eldoc in a nova frame -*- lexical-binding: t; -*- 2 | ;; Copyright (C) 2025 Free Software Foundation, Inc. 3 | ;; Author: blueranger1981 4 | ;; Maintainer: blueranger1981 5 | ;; URL: https://github.com/thisisran/nova-emacs 6 | ;; Version: 0.1.0 7 | ;; Package-Requires: ((emacs "27.1") (posframe "1.4.0")) 8 | ;; Keywords: convenience, posframe, nova 9 | 10 | ;; This file is part of GNU Emacs. 11 | 12 | ;; GNU Emacs is free software: you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation, either version 3 of the License, or 15 | ;; (at your option) any later version. 16 | 17 | ;; GNU Emacs is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with GNU Emacs. If not, see . 24 | 25 | ;;; Commentary: 26 | ;; Show a eldoc-box-hover-mode in a nova frame 27 | 28 | ;; How to use 29 | ;; ---------- 30 | ;; (nova-eldoc 1) 31 | 32 | ;;; Code: 33 | 34 | (unless (require 'eldoc-box nil t) 35 | (message "eldoc-box must be installed in order to use nova-eldoc")) 36 | 37 | (defvar nova-eldoc--buffer " *nova-eldoc--buffer*") 38 | (defvar nova-eldoc--original-markdown-code (face-attribute 'markdown-code-face :background nil t)) 39 | 40 | (defun nova-eldoc--enable-theme () 41 | "Set the colors for the buffer on (re)open." 42 | (with-current-buffer (get-buffer-create nova-eldoc--buffer) 43 | (setq-local nova-title-color "white") 44 | (setq-local nova-top-padding 10) 45 | (setq-local nova-left-padding 8) 46 | (set-face-attribute 'markdown-code-face nil :background (nova--get-local nova-background-color nova-eldoc--buffer)))) 47 | 48 | (defun nova-eldoc--disable-theme () 49 | "Unset the colors for the buffer before close." 50 | (set-face-attribute 'markdown-code-face nil :background nova-eldoc--original-markdown-code)) 51 | 52 | ;;;###autoload 53 | (define-minor-mode nova-eldoc-mode 54 | "A minor mode to show eldoc-box in a flickers popup." 55 | :global t 56 | (if nova-eldoc-mode 57 | (progn 58 | (advice-add 'eldoc-box--display :around #'nova-eldoc-show) 59 | (advice-add 'eldoc-box-quit-frame :around #'nova-eldoc-hide) 60 | (nova-eldoc--enable-theme)) 61 | (advice-remove 'eldoc-box--display #'nova-eldoc-show) 62 | (advice-remove 'eldoc-box-quit-frame #'nova-eldoc-hide) 63 | (nova-eldoc--disable-theme))) 64 | 65 | (defun nova-eldoc-show (orig-fun &rest args) 66 | 67 | (nova-eldoc--enable-theme) 68 | 69 | (apply orig-fun args) 70 | 71 | (modify-frame-parameters eldoc-box--frame `((border-width . 0) (internal-border-width . 0))) 72 | (nova--frame-params-relative-update eldoc-box--frame '((left . -100) (top . 50))) 73 | (nova-show-with-posframe nova-eldoc--buffer 74 | "Doc" 75 | 'top-center 76 | eldoc-box--frame)) 77 | 78 | (defun nova-eldoc-hide (orig-fun &rest args) 79 | (posframe-delete nova-eldoc--buffer) 80 | (nova-eldoc--disable-theme) 81 | (apply orig-fun args)) 82 | 83 | 84 | (provide 'nova-eldoc) 85 | ;;; nova-eldoc.el ends here 86 | -------------------------------------------------------------------------------- /nova-corfu-popupinfo.el: -------------------------------------------------------------------------------- 1 | ;;; nova-corfu-popupinfo.el --- Display corfu-popupinfo in a nova frame -*- lexical-binding: t; -*- 2 | ;; Copyright (C) 2025 Free Software Foundation, Inc. 3 | ;; Author: blueranger1981 4 | ;; Maintainer: blueranger1981 5 | ;; URL: https://github.com/thisisran/nova-emacs 6 | ;; Version: 0.1.0 7 | ;; Package-Requires: ((emacs "27.1") (posframe "1.4.0")) 8 | ;; Keywords: convenience, posframe, nova 9 | 10 | ;; This file is part of GNU Emacs. 11 | 12 | ;; GNU Emacs is free software: you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation, either version 3 of the License, or 15 | ;; (at your option) any later version. 16 | 17 | ;; GNU Emacs is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with GNU Emacs. If not, see . 24 | 25 | ;;; Commentary: 26 | ;; Show a corfu-popupinfo in a nova frame 27 | 28 | ;; How to use 29 | ;; ---------- 30 | ;; (nova-corfu-popupinfo-mode 1) 31 | 32 | ;;; Code: 33 | 34 | (require 'nova) 35 | 36 | (unless (require 'corfu-popupinfo nil t) 37 | (message "corfu must be installed in order to use nova-vertico")) 38 | 39 | 40 | (defconst nova-corfu-popupinfo--buffer " *corfu-popupinfo-nova--buffer*") 41 | (defvar nova-corfu-popupinfo--save-x-pos nil) 42 | 43 | (defun nova-corfu-popupinfo--enable-theme () 44 | "Set the colors for the buffer on (re)open." 45 | (with-current-buffer (get-buffer-create nova-corfu-popupinfo--buffer) 46 | (setq nova-background-color (face-attribute 'corfu-popupinfo :background nil t)) 47 | (setq nova-border-color (face-attribute 'corfu-popupinfo :background nil t)))) 48 | 49 | ;;;###autoload 50 | (define-minor-mode nova-corfu-popupinfo-mode 51 | "A minor mode to show eldoc-box in a nova popup." 52 | :global t 53 | (if nova-corfu-popupinfo-mode 54 | (progn 55 | (corfu-popupinfo-mode 1) 56 | (advice-add 'corfu-popupinfo--show :around #'nova-corfu-popupinfo--show) 57 | (advice-add 'corfu-popupinfo--hide :around #'nova-corfu-popupinfo--hide) 58 | (nova-corfu-popupinfo--enable-theme)) 59 | (advice-remove 'corfu-popupinfo--show #'nova-corfu-popupinfo--show) 60 | (advice-remove 'corfu-popupinfo--hide #'nova-corfu-popupinfo--hide))) 61 | 62 | (defun nova-corfu-popupinfo--show (orig-fun &rest args) 63 | "Main function to show a nova frame when corfu-popupinfo is shown." 64 | 65 | (when (and (corfu-popupinfo--visible-p corfu--frame)) 66 | (nova-corfu-popupinfo--enable-theme) 67 | (apply orig-fun args) 68 | (let* ((parameters (frame-parameters corfu-popupinfo--frame)) 69 | (x (cdr (assoc 'left parameters)))) 70 | (setq nova-corfu-popupinfo--save-x-pos x) 71 | 72 | ;; top-center 73 | (modify-frame-parameters corfu-popupinfo--frame `((left . ,(+ nova-corfu-popupinfo--save-x-pos 15))))) 74 | 75 | ;; side-left 76 | ;; (modify-frame-parameters corfu-popupinfo--frame `((left . ,(+ nova-corfu-popupinfo--save-x-pos 20))))) 77 | 78 | (nova-show-with-posframe nova-corfu-popupinfo--buffer 79 | "" 80 | 'top-center 81 | corfu-popupinfo--frame))) 82 | 83 | (defun nova-corfu-popupinfo--hide (orig-fun &rest args) 84 | (setq nova-corfu-popupinfo--save-x-pos nil) 85 | (posframe-delete nova-corfu-popupinfo--buffer) 86 | (apply orig-fun args)) 87 | 88 | (provide 'nova-corfu-popupinfo) 89 | ;;; nova-corfu-popupinfo.el ends here 90 | -------------------------------------------------------------------------------- /nova-corfu.el: -------------------------------------------------------------------------------- 1 | ;;; nova-corfu.el --- Wrap the corfu frame inside of nova -*- lexical-binding: t -*- 2 | ;; Copyright (C) 2025 Free Software Foundation, Inc. 3 | ;; Author: blueranger1981 4 | ;; Maintainer: blueranger1981 5 | ;; URL: https://github.com/thisisran/nova-emacs 6 | ;; Version: 0.1.0 7 | ;; Package-Requires: ((emacs "27.1") (posframe "1.4.0") (corfu)) 8 | ;; Keywords: convenience, corfu, posframe, nova 9 | 10 | ;; This file is part of GNU Emacs. 11 | 12 | ;; GNU Emacs is free software: you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation, either version 3 of the License, or 15 | ;; (at your option) any later version. 16 | 17 | ;; GNU Emacs is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with GNU Emacs. If not, see . 24 | 25 | ;;; Commentary: 26 | ;; nova-vertico uses the nova and vertico-posframe libraries to show 27 | ;; vertico in a nova posframe. 28 | 29 | ;; NOTE: nova-vertico requires Emacs 27.1 30 | 31 | ;; How to enable nova-vertico 32 | ;; -------------------------- 33 | ;; (require 'nova-corfu) 34 | ;; (nova-corfu-mode 1) 35 | 36 | ;;; Code: 37 | 38 | (require 'nova) 39 | (require 'nova-utils) 40 | (require 'posframe) 41 | (unless (require 'corfu nil t) 42 | (message "corfu must be installed in order to use nova-corfu")) 43 | 44 | (defvar nova-corfu--original-corfu-border (face-attribute 'corfu-border :background nil t)) 45 | (defvar nova-corfu--original-corfu-default (face-attribute 'corfu-default :background nil t)) 46 | (defvar nova-corfu--original-corfu-current (face-attribute 'corfu-current :background nil t)) 47 | (defvar nova-corfu--original-orderless-match-background (face-attribute 'orderless-match-face-0 :background nil t)) 48 | (defvar nova-corfu--original-orderless-match-foreground (face-attribute 'orderless-match-face-0 :foreground nil t)) 49 | 50 | (defvar nova-corfu-top 10 "Adjust the top of the corfu frame by this amount") 51 | (defvar nova-corfu-border-color "#292E41" "Color for the corfu border") 52 | (defconst nova-corfu--buffer " *nova-corfu--buffer*") 53 | 54 | (defun nova-corfu-mode-workable-p () 55 | "Indicate whether the mode is on or off." 56 | nova-corfu-mode) 57 | 58 | (defun nova-corfu--enable-theme () 59 | "Set the colors for the buffer on (re)open." 60 | 61 | (with-current-buffer (get-buffer-create nova-corfu--buffer) 62 | 63 | (setq-local nova-left-padding 8) 64 | (setq-local nova-background-color "#292E41") ; (face-attrkbute 'corfu-default :background nkl t)) 65 | (setq-local nova-border-size 1) 66 | (setq-local nova-border-color "#292E41") 67 | (setq-local nova-title-color "#A999DB") 68 | (setq-local nova-title-background-color "#383852") 69 | 70 | (set-face-attribute 'corfu-border nil :background "#292E41") ; (face-attribute 'default :background nil t)) 71 | (set-face-attribute 'corfu-current nil :background "#212534" :weight 'bold) ; (face-attribute 'default :background nil t) :weight 'bold) 72 | (set-face-attribute 'corfu-default nil :background "#292E41") ; (face-attribute 'default :background nil t)) 73 | (set-face-attribute 'orderless-match-face-0 nil :background (face-attribute 'default :background nil t) :foreground "#B29CE9"))) 74 | 75 | (defun nova-corfu--disable-theme () 76 | "Unset the colors for the buffer before close." 77 | 78 | (set-face-attribute 'corfu-border nil :background nova-corfu--original-corfu-border) 79 | (set-face-attribute 'corfu-default nil :background nova-corfu--original-corfu-default) 80 | (set-face-attribute 'corfu-current nil :background nova-corfu--original-corfu-current) 81 | (set-face-attribute 'orderless-match-face-0 nil 82 | :background nova-corfu--original-orderless-match-background 83 | :foreground nova-corfu--original-orderless-match-foreground)) 84 | 85 | ;;;###autoload 86 | (define-minor-mode nova-corfu-mode 87 | "Display corfu completions in a nova frame." 88 | :global t 89 | (if nova-corfu-mode 90 | (progn 91 | (corfu-mode 1) 92 | (nova-corfu--enable-theme)) 93 | (nova-corfu--disable-theme))) 94 | 95 | (cl-defmethod corfu--popup-show :after (_pos _off _width _lines &context ((nova-corfu-mode-workable-p) (eql t)) &optional _curr _lo _bar) 96 | "Called after corfu pops up a menu to display it with a nova frame." 97 | 98 | (nova-corfu--enable-theme) 99 | 100 | (nova--frame-params-relative-update corfu--frame `((top . ,nova-corfu-top))) 101 | (nova-show-with-posframe nova-corfu--buffer 102 | "" 103 | 'top-center 104 | corfu--frame)) 105 | 106 | (cl-defmethod corfu--popup-hide :before (&context ((nova-corfu-mode-workable-p) (eql t))) 107 | "Called after corfu hides its popup to hide the nova frame as well." 108 | 109 | (when (get-buffer nova-corfu--buffer) 110 | (posframe-hide nova-corfu--buffer) 111 | (kill-buffer nova-corfu--buffer))) 112 | 113 | (provide 'nova-corfu) 114 | ;;; nova-corfu.el ends here 115 | -------------------------------------------------------------------------------- /nova-side-left.el: -------------------------------------------------------------------------------- 1 | ;;; nova-side-left.el --- A nova style with round borders and a left rotated title -*- lexical-binding: t; -*- 2 | ;; Copyright (C) 2025 Free Software Foundation, Inc. 3 | ;; Author: blueranger1981 4 | ;; Maintainer: blueranger1981 5 | ;; URL: https://github.com/thisisran/nova-emacs 6 | ;; Version: 0.1.0 7 | ;; Package-Requires: ((emacs "27.1") (posframe "1.4.0")) 8 | ;; Keywords: convenience, posframe, nova 9 | 10 | ;; This file is not part of GNU Emacs. 11 | 12 | ;; GNU Emacs is free software: you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation, either version 3 of the License, or 15 | ;; (at your option) any later version. 16 | 17 | ;; GNU Emacs is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with GNU Emacs. If not, see . 24 | 25 | ;;; Commentary: 26 | 27 | ;; A nova style that has a round border, with a title 28 | ;; showing on the left side of the frame 29 | 30 | ;;; Code: 31 | 32 | (require 'nova-common-vars) 33 | (require 'nova-utils) 34 | 35 | (defconst nova-sidebar-shape-width 15 "Give the sidebar some width") ; The 15 here is just to give the sidebar shape a little width 36 | (defconst nova-sidebar-width-factor 1.333 "a factor to calculate the sidebar width based on the radius") 37 | 38 | (defun nova--render-side-left (name svg-object) 39 | "Draw the nova title on the left of the frame, as a sidebar. 40 | SVG-OBJECT: main SVG object 41 | WIDTH: width of the frame 42 | HEIGHT: height of the frame" 43 | (let* ((width (aref (nova--get-local nova--last-size-position name) 0)) 44 | (height (aref (nova--get-local nova--last-size-position name) 1)) 45 | (width (* width (frame-char-width))) 46 | (height (* height (frame-char-height))) 47 | 48 | (char-width-in-pixels (frame-char-width)) 49 | (radius-x (nova--get-local nova-radius-x name)) 50 | (radius-y (nova--get-local nova-radius-y name)) 51 | (background-color (nova--get-local nova-background-color name)) 52 | (border-size (nova--get-local nova-border-size name)) 53 | (double-border-size (* border-size 2)) 54 | (quad-border-size (* double-border-size 2)) 55 | (outer-height (- height quad-border-size)) 56 | (sidebar-top (* border-size 3)) 57 | (sidebar-width (* char-width-in-pixels nova-sidebar-shape-width)) 58 | (sidebar-height (- outer-height double-border-size)) 59 | (title (nova--get-local nova--title name)) 60 | (title-length (length title)) 61 | (title-width-in-pixels (* char-width-in-pixels title-length)) 62 | (title-y-pos (nova--div-and-round (+ outer-height title-width-in-pixels) 2)) 63 | (background-x (round (* radius-y nova-sidebar-width-factor))) ; give the sidebar a width of 33% more than the radius to give it some width 64 | ) 65 | 66 | ;; Main border 67 | (svg-rectangle svg-object 68 | border-size 69 | double-border-size 70 | (- width double-border-size) 71 | outer-height 72 | :rx radius-x 73 | :ry radius-y 74 | :stroke (nova--get-local nova-border-color name) 75 | :stroke-width border-size 76 | :fill-color background-color) 77 | 78 | ;; Sidebar 79 | (svg-rectangle svg-object 80 | double-border-size 81 | sidebar-top 82 | sidebar-width 83 | sidebar-height 84 | :rx radius-x 85 | :ry radius-y 86 | :fill-color (nova--get-local nova-title-background-color name)) 87 | 88 | ;; Sidebar title 89 | (svg-text svg-object 90 | title 91 | :x 0 92 | :y 0 93 | :font-weight "bold" 94 | :transform (format "translate (%s, %s) rotate(270)" 95 | radius-y 96 | title-y-pos) 97 | :fill-color (nova--get-local nova-title-color name)) 98 | 99 | ;; Sidebar background 100 | (svg-rectangle svg-object 101 | background-x 102 | sidebar-top 103 | sidebar-width 104 | sidebar-height 105 | :fill-color background-color))) 106 | 107 | 108 | (defun nova--show-side-left (name) 109 | "Set up a side-left frame for NAME." 110 | 111 | (let* ((pos-frame (nova--get-local nova--wrapped-posframe name)) 112 | (width (frame-width pos-frame)) 113 | (height (max (frame-height pos-frame) (nova--get-local nova-min-height name))) 114 | 115 | (parameters (frame-parameters pos-frame)) 116 | (x (cdr (assoc 'left parameters))) 117 | (y (cdr (assoc 'top parameters))) 118 | (left-padding (nova--get-local nova-left-padding name)) 119 | (x-radius (nova--get-local nova-radius-x name)) 120 | (sidebar-width (+ (round (* x-radius nova-sidebar-width-factor)) left-padding)) 121 | (nova-height (+ height (nova--get-local nova-extra-height name))) 122 | (half-radius (nova--div-and-round x-radius 2)) 123 | (nova-position (cons 124 | (- x sidebar-width) 125 | (- y half-radius (nova--get-local nova-border-size name)))) 126 | (extra-width (nova--pixels-to-char-width sidebar-width)) 127 | (nova-width (+ width extra-width (nova--pixels-to-char-width left-padding))) 128 | ) 129 | 130 | (nova--set-local nova--last-size-position `[,nova-width ,nova-height ,(car nova-position) ,(cdr nova-position)] name) 131 | 132 | (posframe-show (get-buffer-create name) 133 | :width nova-width 134 | :height nova-height 135 | :position nova-position))) 136 | 137 | (provide 'nova-side-left) 138 | ;;; nova-side-left.el ends here 139 | -------------------------------------------------------------------------------- /nova-top-center.el: -------------------------------------------------------------------------------- 1 | ;;; nova-top-center.el --- A nova style with round borders and top center title -*- lexical-binding: t; -*- 2 | ;; Copyright (C) 2025 Free Software Foundation, Inc. 3 | ;; Author: blueranger1981 4 | ;; Maintainer: blueranger1981 5 | ;; URL: https://github.com/thisisran/nova-emacs 6 | ;; Version: 0.1.0 7 | ;; Package-Requires: ((emacs "27.1") (posframe "1.4.0")) 8 | ;; Keywords: convenience, posframe, nova 9 | 10 | ;; This file is not part of GNU Emacs. 11 | 12 | ;; GNU Emacs is free software: you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation, either version 3 of the License, or 15 | ;; (at your option) any later version. 16 | 17 | ;; GNU Emacs is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with GNU Emacs. If not, see . 24 | 25 | ;;; Commentary: 26 | 27 | ;; A nova style that has a round border, with the (optional) 28 | ;; title at the top center of the frame 29 | 30 | ;;; Code: 31 | 32 | (require 'nova-common-vars) 33 | (require 'nova-utils) 34 | 35 | (defvar-local nova-top-center-side-padding 20 "Padding around top center title") 36 | 37 | 38 | (defun nova--render-top-center (name svg-object) 39 | "Draw the nova title at the top center of the frame. 40 | SVG-OBJECT: main SVG object 41 | NAME: name representing the frame 42 | WIDTH: width of the frame 43 | HEIGHT: height of the frame" 44 | 45 | (let* ((size-position (nova--get-local nova--last-size-position name)) 46 | (width (aref size-position 0)) 47 | (height (aref size-position 1)) 48 | (width (* width (frame-char-width))) 49 | (height (* height (frame-char-height))) 50 | 51 | (title-length (length (nova--get-local nova--title name))) 52 | (border-size (nova--get-local nova-border-size name)) 53 | (half-border-size (nova--div-and-round border-size 2)) 54 | (double-border-size (* border-size 2)) 55 | (default-font (face-attribute 'default :font)) 56 | (font-height (font-get default-font :size)) 57 | (title-top-padding (nova--div-and-round font-height 2)) 58 | (top-padding (if (> title-length 0) title-top-padding half-border-size)) 59 | (char-width-in-pixels (frame-char-width)) 60 | (title-width-in-pixels (* char-width-in-pixels title-length)) 61 | (x (nova--div-and-round width 2)) 62 | (y font-height) 63 | (top-center-side-padding (nova--get-local nova-top-center-side-padding name)) 64 | (title-container-x (- x (nova--div-and-round title-width-in-pixels 2) (nova--div-and-round top-center-side-padding 2))) ; 65 | (title-container-width (+ title-width-in-pixels top-center-side-padding)) 66 | ) 67 | 68 | ;; Main border 69 | (svg-rectangle svg-object 70 | half-border-size 71 | top-padding 72 | (- width border-size) 73 | (- height top-padding half-border-size) 74 | :rx (nova--get-local nova-radius-x name) 75 | :ry (nova--get-local nova-radius-y name) 76 | :stroke (nova--get-local nova-border-color name) 77 | :stroke-width border-size 78 | :fill-color (nova--get-local nova-background-color name)) 79 | 80 | ;; Title background 81 | (when (> title-length 0) 82 | (svg-rectangle svg-object 83 | title-container-x 84 | 0 85 | title-container-width 86 | (+ title-top-padding double-border-size) 87 | :fill-color (nova--get-local nova-background-color name) 88 | ) 89 | 90 | ;; Title text 91 | (svg-text svg-object (nova--get-local nova--title name) 92 | :x x 93 | :y y 94 | :fill (nova--get-local nova-title-color name) 95 | :text-anchor "middle" 96 | :dominant-baseline "central")))) 97 | 98 | (defun nova--show-top-center (name) 99 | "Set up a top-center frame for NAME." 100 | 101 | (let* ((pos-frame (nova--get-local nova--wrapped-posframe name)) 102 | (parameters (frame-parameters pos-frame)) 103 | (x (cdr (assoc 'left parameters))) 104 | (y (cdr (assoc 'top parameters))) 105 | (width (frame-width pos-frame)) 106 | (height (max (frame-height pos-frame) (nova--get-local nova-min-height name))) 107 | (default-font (face-attribute 'default :font)) 108 | (font-height (font-get default-font :size)) 109 | (title-length (length (nova--get-local nova--title name))) 110 | (title-exists (> title-length 0)) 111 | (border-size (nova--get-local nova-border-size name)) 112 | (half-border-size (nova--div-and-round border-size 2)) 113 | (title-top-padding (nova--div-and-round font-height 2)) 114 | (top-padding (if title-exists title-top-padding half-border-size)) 115 | (extra-y (if title-exists border-size 0)) 116 | (radius-x (nova--get-local nova-radius-x name)) 117 | (radius-y (nova--get-local nova-radius-y name)) 118 | (nova-position (cons 119 | (- x (nova--div-and-round radius-x 2)) 120 | (- y top-padding (nova--div-and-round radius-y 2) extra-y (nova--get-local nova-top-padding name)))) ; The 2 at the end is just a constant space from the title 121 | (extra-width (nova--pixels-to-char-width radius-x)) 122 | (extra-height (nova--div-and-round (if title-exists radius-y radius-y) font-height)) 123 | (nova-width (+ width extra-width)) 124 | (nova-height (+ height extra-height (if title-exists 1 0))) 125 | ) 126 | 127 | (nova--set-local nova--last-size-position `[,nova-width ,nova-height ,(car nova-position) ,(cdr nova-position)] name) 128 | 129 | (posframe-show (get-buffer-create name) 130 | :width nova-width 131 | :height nova-height 132 | :position nova-position 133 | :nova-frame t))) 134 | 135 | (provide 'nova-top-center) 136 | ;;; nova-top-center.el ends here 137 | -------------------------------------------------------------------------------- /nova.el: -------------------------------------------------------------------------------- 1 | ;;; nova.el --- A library to show SVG frames with posframe as a backend -*- lexical-binding: t; -*- 2 | ;; Copyright (C) 2025 Free Software Foundation, Inc. 3 | ;; Author: blueranger1981 4 | ;; Maintainer: blueranger1981 5 | ;; URL: https://github.com/thisisran/nova-emacs 6 | ;; Version: 0.1.0 7 | ;; Package-Requires: ((emacs "27.1") (posframe "1.4.0")) 8 | ;; Keywords: convenience, posframe, nova 9 | 10 | ;; This file is not part of GNU Emacs. 11 | 12 | ;; GNU Emacs is free software: you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation, either version 3 of the License, or 15 | ;; (at your option) any later version. 16 | 17 | ;; GNU Emacs is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with GNU Emacs. If not, see . 24 | 25 | ;;; Commentary: 26 | 27 | ;; nova is a wrapper around `posframe` that makes 28 | ;; creating and customizing posframes easy and visually appealing. 29 | 30 | ;; The package provides two preconfigured styles out of the box for 31 | ;; immediate use. These styles are designed to look modern and polished, 32 | ;; suitable for various contexts such as completion UIs or transient 33 | ;; menus. 34 | ;; 35 | ;; Developers can also use nova to build their own 36 | ;; custom styles. To create a new design, you implement 2 functions: 37 | ;; nova--render-style-name, and nova--show-style-name, 38 | ;; where render paints the frame, and show calculates the 39 | ;; correct size and position of the frame. 40 | ;; Look at the current implementations for an example. 41 | 42 | ;; NOTE: nova requires Emacs 27.1 43 | 44 | ;; Getting started 45 | ;; --------------- 46 | 47 | ;; Call nova-show-with-posframe (if you already have a posframe 48 | ;; you want to wrap), or nova-show to create a posframe 49 | ;; wrapped in a nova frame 50 | 51 | ;; nova-update: recreate the SVG frame (in case the underlying 52 | ;; posframe has changed) 53 | 54 | ;; Customization 55 | ;; ------------- 56 | 57 | ;; Set nova-background-color, nova-border-color, 58 | ;; nova-border-size (recommended to use 1 as a starting point), 59 | ;; nova-title-color, and nova-title-background-color to your 60 | ;; preferred colors. 61 | 62 | ;; If something doesn't look right, consider tweaking 63 | ;; (all located at nova-common-vars.el) nova-radius-x, 64 | ;; nova-radius-y, nova-min-height, nova-left-padding, 65 | ;; nova-top-padding, nova-extra-height, 66 | ;; and nova-top-center-side-padding to make it look 67 | ;; right on your screen. 68 | 69 | ;;; Code: 70 | 71 | (require 'svg) 72 | (require 'posframe) 73 | (require 'nova-utils) 74 | (require 'nova-common-vars) 75 | 76 | ;; Supported Styles 77 | (require 'nova-side-left) 78 | (require 'nova-top-center) 79 | 80 | (defconst nova--show-func-prefix "nova--show") 81 | (defconst nova--render-func-prefix "nova--render") 82 | 83 | 84 | (defun nova--call-func-by-style (name func-prefix &rest args) 85 | (let* ((style-func (intern (format "%s-%s" func-prefix (nova--get-local nova--style name)))) 86 | (func-args (if args (cons name args) (list name)))) 87 | (apply style-func func-args))) 88 | 89 | 90 | (defun nova--posframe-to-svg (name) 91 | "When called from a posframe, turn it into an svg frame. 92 | NAME: see nova-show-with-posframe" 93 | 94 | (let ((posframe-window (get-buffer-window name t)) 95 | w 96 | h) 97 | 98 | (when (window-live-p posframe-window) 99 | (with-selected-window posframe-window 100 | (setq w (* (frame-char-width) (nova--get-size name :horizontal t))) 101 | (setq h (* (frame-char-height) (nova--get-size name)))) 102 | 103 | (let ((main-svg (svg-create w h))) 104 | 105 | (nova--call-func-by-style name nova--render-func-prefix main-svg) 106 | 107 | ;; Finally, draw the svg to the screen 108 | (with-current-buffer (nova--get-local nova--name name) 109 | (svg-insert-image main-svg) 110 | (goto-char (point-min))))))) 111 | 112 | 113 | (defun nova-show (name title style pos-name &rest posframe-args) 114 | "Creates a posframe with POS-NAME buffer name and POSFRAME-ARGS, 115 | and passes that to nova-show-with-posframe with NAME TITLE and STYLE." 116 | 117 | (nova-show-with-posframe name 118 | title 119 | style 120 | (apply #'posframe-show pos-name posframe-args))) 121 | 122 | 123 | (defun nova-show-with-posframe (name title style pos-frame) 124 | "Open a nova frame, and returns it. 125 | 126 | NAME: A unique name for the nova buffer. 127 | 128 | buffer name can prefix with space, for example \" *mybuffer*\", so 129 | the buffer name will hide for ibuffer and `list-buffers' 130 | 131 | TITLE: title of the frame 132 | STYLE: which style to use (side-left or top-center) to draw the title 133 | POS-FRAME: a posframe to build the nova UI around." 134 | 135 | (when (display-graphic-p) 136 | 137 | (with-current-buffer (get-buffer-create name) 138 | (setq-local nova--name name) 139 | (setq-local nova--title (or title "")) 140 | (setq-local nova--style style) 141 | (setq-local nova--wrapped-posframe pos-frame) 142 | (erase-buffer)) 143 | 144 | (modify-frame-parameters pos-frame '((z-group . above))) 145 | 146 | (let* ((save-nova-frame (nova--call-func-by-style name nova--show-func-prefix))) 147 | 148 | (push `(,save-nova-frame . ,pos-frame) nova--frame-list) 149 | 150 | (with-current-buffer name 151 | (setq-local nova--frame save-nova-frame)) 152 | 153 | (nova--posframe-to-svg name) 154 | save-nova-frame))) 155 | 156 | 157 | (defun nova-update (name) 158 | "Regenerates NAME's frame's SVG." 159 | (with-current-buffer (get-buffer name) 160 | (erase-buffer)) 161 | 162 | (nova--call-func-by-style name nova--show-func-prefix) 163 | (nova--posframe-to-svg name)) 164 | 165 | 166 | (defun nova--is-frame-visible (name) 167 | "Check if a nova frame (associated with NAME) exists and is visible." 168 | (let ((buffer (get-buffer name))) 169 | (and buffer 170 | (buffer-live-p buffer) 171 | (posframe-workable-p) 172 | (frame-live-p (buffer-local-value 'posframe--frame buffer)) 173 | (frame-visible-p (buffer-local-value 'posframe--frame buffer))))) 174 | 175 | 176 | (defun nova-delete-frame (name) 177 | "Delete the nova frame associated with NAME." 178 | (interactive) 179 | (when (nova--is-frame-visible name) 180 | (posframe-delete name))) 181 | 182 | 183 | ;; TODO: must be a better way to do it 184 | ;; probably using some frame parameter 185 | (defun nova-delete-all () 186 | "Delete all nova frames (and the their contained posframes)." 187 | (interactive) 188 | (dolist (nova-frame-value nova--frame-list) 189 | (let ((nova-frame (car nova-frame-value)) 190 | (nova-posframe (cdr nova-frame-value))) 191 | (delete-frame nova-frame) 192 | (delete-frame nova-posframe))) 193 | (setq nova--frame-list nil)) 194 | 195 | (provide 'nova) 196 | ;;; nova.el ends here 197 | -------------------------------------------------------------------------------- /nova-vertico.el: -------------------------------------------------------------------------------- 1 | ;;; nova-vertico.el --- Using nova and posframe to show Vertico -*- lexical-binding: t; -*- 2 | ;; Copyright (C) 2025 Free Software Foundation, Inc. 3 | ;; Author: blueranger1981 4 | ;; Maintainer: blueranger1981 5 | ;; URL: https://github.com/thisisran/nova-emacs 6 | ;; Version: 0.1.0 7 | ;; Package-Requires: ((emacs "27.1") (posframe "1.4.0") (vertico "1.1")) 8 | ;; Keywords: convenience, vertico, posframe, nova 9 | 10 | ;; This file is not part of GNU Emacs. 11 | 12 | ;; GNU Emacs is free software: you can redistribute it and/or modify 13 | ;; it under the terms of the GNU General Public License as published by 14 | ;; the Free Software Foundation, either version 3 of the License, or 15 | ;; (at your option) any later version. 16 | 17 | ;; GNU Emacs is distributed in the hope that it will be useful, 18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | ;; GNU General Public License for more details. 21 | 22 | ;; You should have received a copy of the GNU General Public License 23 | ;; along with GNU Emacs. If not, see . 24 | 25 | ;;; Commentary: 26 | ;; nova-vertico uses the nova and vertico-posframe libraries to show 27 | ;; vertico in a nova posframe. 28 | 29 | ;; NOTE: nova-vertico requires Emacs 27.1 30 | 31 | ;; How to enable nova-vertico 32 | ;; -------------------------- 33 | ;; (require 'nova-vertico) 34 | ;; (nova-vertico-mode 1) 35 | 36 | ;;; Code: 37 | 38 | (require 'nova) 39 | 40 | (unless (require 'vertico-posframe nil t) 41 | (message "vertico-posframe must be installed in order to use nova-vertico")) 42 | 43 | (defconst nova-vertico--dedicated-buffer " *nova-vertico--dedicated-buffer*") 44 | (defconst nova-vertico--save-vertico-posframe vertico-posframe-poshandler "Save the original poshandler") 45 | 46 | (defvar nova-vertico--main-posframe nil "nova-vertico's posframe") 47 | 48 | (defvar nova-vertico-depth-2-max-width 100 "frame's max width when depth is <= 2") 49 | (defvar nova-vertico-deep-depth-max-width 150 "frame's max width when depth is > 2") 50 | 51 | 52 | ;;;###autoload 53 | (define-minor-mode nova-vertico-mode 54 | "Display Vertico in nova instead of the minibuffer." 55 | 56 | :global t 57 | (when (display-graphic-p) 58 | (if nova-vertico-mode 59 | (progn 60 | (nova-vertico--enable-theme) 61 | (setq vertico-posframe-poshandler #'posframe-poshandler-frame-center) 62 | (vertico-posframe-mode 1) 63 | (advice-add 'vertico-posframe--show :around #'nova-vertico--enable) 64 | (advice-add 'vertico-posframe--multiform-function :around #'nova-vertico--hide) 65 | (advice-add 'vertico-posframe--minibuffer-exit-hook :around #'nova-vertico--hide) 66 | (advice-add 'vertico-posframe-cleanup :around #'nova-vertico--delete) 67 | (advice-add 'vertico-posframe-hidehandler :around #'nova-vertico--invisible)) 68 | 69 | (setq vertico-posframe-poshandler nova-vertico--save-vertico-posframe) 70 | (advice-remove 'vertico-posframe-hidehandler #'nova-vertico--invisible) 71 | (advice-remove 'vertico-posframe--show #'nova-vertico--enable) 72 | (advice-remove 'vertico-posframe--multiform-function #'nova-vertico--hide) 73 | (advice-remove 'vertico-posframe--minibuffer-exit-hook #'nova-vertico--hide) 74 | (advice-remove 'vertico-posframe-cleanup #'nova-vertico--delete) 75 | (vertico-posframe-mode 0)))) 76 | 77 | (defun nova-vertico--enable-theme () 78 | "Set the colors for the buffer on (re)open." 79 | (with-current-buffer (get-buffer-create nova-vertico--dedicated-buffer) 80 | (setq-local nova-background-color (face-attribute 'default :background)))) 81 | 82 | (defun nova-vertico--invisible (orig-fun &rest args) 83 | (advice-add 'posframe--make-frame-invisible :around #'nova-vertico--invisible-advice) 84 | (apply orig-fun args)) 85 | 86 | (defun nova-vertico--enable (orig-fun &rest args) 87 | "Capture posframe-show before calling ORIG-FUN using ARGS." 88 | (advice-add 'posframe-show :around #'nova-vertico--show-advice) 89 | (apply orig-fun args)) 90 | 91 | (defun nova-vertico--hide (orig-fun &rest args) 92 | "Capture posframe-hide before calling ORIG-FUN using ARGS." 93 | (advice-add 'posframe-hide :around #'nova-vertico--hide-advice) 94 | (apply orig-fun args)) 95 | 96 | (defun nova-vertico--delete (orig-fun &rest args) 97 | "Capture posframe-delete-frame before calling ORIG-FUN using ARGS." 98 | (advice-add 'posframe-delete-frame :around #'nova-vertico--delete-advice) 99 | (apply orig-fun args)) 100 | 101 | (defun nova-vertico--format-title (title) 102 | "Formats the title, truncating if necessary." 103 | 104 | (let* ((no-colon (if (string-suffix-p ":" title) (substring title 0 -1) title)) 105 | (no-paren (replace-regexp-in-string "(\\([^()]*\\))" "" no-colon)) 106 | (trimmed (string-trim no-paren))) 107 | trimmed)) 108 | 109 | (defun nova-vertico--show-advice (orig-fun &rest args) 110 | "Advice to create a background posframe before the original `posframe-show`." 111 | 112 | (advice-remove 'posframe-show #'nova-vertico--show-advice) 113 | (nova-vertico--enable-theme) 114 | 115 | ;; if minibuffer-depth < 2, then save the position and width of the original posframe 116 | ;; else, set the apply below to set the left and width according to the original posframe 117 | (let* ((title-raw (or (and (minibuffer-prompt) (string-trim (minibuffer-prompt))) "")) 118 | (title-text (nova-vertico--format-title title-raw))) 119 | (if (< (minibuffer-depth) 2) 120 | (progn 121 | (setq nova-vertico--main-posframe 122 | (apply orig-fun (cons (car args) (nova--update-params (cdr args) :border-width 0 :max-width nova-vertico-depth-2-max-width)))) 123 | (nova-show-with-posframe nova-vertico--dedicated-buffer 124 | title-text 125 | 'side-left 126 | nova-vertico--main-posframe)) 127 | (let* ((main-posframe nova-vertico--main-posframe) 128 | (parameters (frame-parameters main-posframe)) 129 | (x (cdr (assoc 'left parameters))) 130 | (new-width (cdr (assoc 'width parameters))) 131 | (recursive-minibuffer-frame (apply orig-fun (cons (car args) (nova--update-params (cdr args) 132 | :border-width 0 133 | :max-width nova-vertico-depth-2-max-width))))) 134 | (nova-show-with-posframe nova-vertico--dedicated-buffer 135 | (concat title-text (format " (depth %s)" (minibuffer-depth))) 136 | 'side-left 137 | nova-vertico--main-posframe) 138 | (modify-frame-parameters recursive-minibuffer-frame `((z-group . above) (left . ,x) (width . ,new-width))))))) 139 | 140 | (defun nova-vertico--hide-advice (orig-fun &rest args) 141 | (advice-remove 'posframe-hide #'nova-vertico--hide-advice) 142 | (when (= (minibuffer-depth) 1) 143 | (posframe-hide nova-vertico--dedicated-buffer)) 144 | (apply orig-fun args)) 145 | 146 | (defun nova-vertico--invisible-advice (orig-fun &rest args) 147 | (advice-remove 'posframe--make-frame-invisible #'nova-vertico--invisible-advice) 148 | (apply orig-fun args)) 149 | 150 | (defun nova-vertico--delete-advice (orig-fun &rest args) 151 | (advice-remove 'posframe-delete-frame #'nova-vertico--delete-advice) 152 | (nova-delete-frame nova-vertico--dedicated-buffer) 153 | (apply orig-fun args)) 154 | 155 | (provide 'nova-vertico) 156 | ;;; nova-vertico.el ends here -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nova: Emacs SVG Child Frames 2 | 3 | This is very preliminary work, nova may not work for you. 4 | You might need to tweak the code to make it work on your screen 5 | and font configuration. 6 | 7 | ![nova-vertico in action](images/nova-vertico-example.png "nova-vertico in action") 8 | 9 | ![nova-corfu in action](images/nova-corfu-example.png "nova-corfu in action") 10 | 11 | ![nova-eldoc in action](images/nova-eldoc-example.png "nova-eldoc in action") 12 | 13 | ## Overview 14 | 15 | Nova provides a visually enhanced way to display 16 | child frames in Emacs by leveraging an SVG-based posframe 17 | wrapped around ![posframe](https://github.com/tumashu/posframe) (or a regular child frame). 18 | Instead of modifying an existing child frame, this 19 | package creates a secondary frame that draws a 20 | customizable SVG background—complete with rounded 21 | corners, shadows, or potentially any other decorative elements, 22 | before placing the actual frame content on top of it. 23 | The result is a seamless, modern-looking child frame that 24 | integrates smoothly with your Emacs setup. 25 | 26 | You can customize the SVG to your liking, 27 | tailoring the shape, size, and aesthetics of your child frames without 28 | changing how you normally interact with them. 29 | This makes it a straightforward way to beautify tooltips, 30 | pop-up windows, or any other child frames without 31 | sacrificing ease of use. 32 | 33 | Doing it this way of course means the nova frame doesn't 34 | really have a rounded corner, but in vast majority of cases 35 | it isn't noticeable (as it is configured to have the same 36 | background as the main frame). To make it truly rounded, Emacs' 37 | c code will have to be modified, maybe in the future. 38 | 39 | ## Installation 40 | 41 | ### Using Straight/Elpaca 42 | 43 | As Nova is not on Elpa/Melpa (yet), the easiest way to install Nova 44 | is by using straight/elpaca: 45 | 46 | Straight: 47 | 48 | ```elisp 49 | 50 | (straight-use-package 51 | '(nova :type git :host github :repo "thisisran/nova")) 52 | 53 | ``` 54 | 55 | Elpaca (with use-package integration): 56 | 57 | ```elisp 58 | 59 | (use-package nova 60 | :ensure (:host github :repo "thisisran/nova")) 61 | 62 | ``` 63 | 64 | ### Manual install 65 | 66 | To install Nova manually, clone ![this](https://github.com/thisisran/nova-emacs) repo and then add the necessary path: 67 | 68 | ```elisp 69 | 70 | (add-to-list 'load-path ) 71 | 72 | ``` 73 | 74 | For the Nova library only: 75 | 76 | ```elisp 77 | 78 | (require 'nova) 79 | 80 | ``` 81 | 82 | For nova-vertico (showing vertico in a floating nova frame): 83 | 84 | ```elisp 85 | 86 | (require 'nova-vertico) 87 | 88 | ;; Then load using (nova-vertico-mode 1) 89 | 90 | ``` 91 | 92 | For nova-corfu/nova-corfu-popupup-info (showing corfu and corfu-popupinfo in a nova frame): 93 | 94 | ```elisp 95 | 96 | (require 'nova-corfu) 97 | (require 'nova-corfu-popupinfo) 98 | 99 | ;; Then load using: 100 | ;; (nova-corfu-mode 1) 101 | ;; (nova-corfu-popupinfo-mode 1) 102 | 103 | ``` 104 | 105 | For nova-eldoc (showing eldoc-box-hover in a nova frame): 106 | 107 | ```elisp 108 | 109 | (require 'nova-eldoc) 110 | 111 | ;; Then load using (nova-eldoc-mode 1) 112 | 113 | ``` 114 | 115 | 116 | ## Configuration 117 | 118 | You can adjust the way the frames look by setting the following: 119 | 120 | nova-background-color, nova-border-color, nova-border-size (1 is recommended), 121 | nova-title-color, and nova-title-background-color. 122 | 123 | In addition, nova-radius-x and nova-radius-y allow you to change how round the 124 | corners of the frame will be. Keep in mind that this might cause the frame to 125 | be rendered incorrectly for some values. 126 | 127 | nova-min-height is used to give the nova frame a minimal height value. In some 128 | cases, when wrapping a frame, because of the added decorations nova adds to a 129 | frame, it doesn't look right when the height is too small. 130 | 131 | nova-top-padding and nova-left-padding are used to add some padding for the top 132 | and left side of an existing frame we are wrapping the nova frame around. This 133 | is useful when it seems the existing (non-nova) frame is "stepping" on top of the 134 | nova frame, and we want to give nova more room. 135 | 136 | nova-extra-height allows you to give extra height to the nova frame. 137 | 138 | Each of these variables are buffer local. There is a a convenience macro, 139 | **nova--set-local**, to help set the variables and make sure they are set in 140 | a buffer local context. It accepts 3 arguments: 141 | 142 | var - the variable name you want to set. 143 | value - the new value you want to give the variable. 144 | name - the unique name representing the nova frame you want to set the new value for. 145 | 146 | ## Usage (users) 147 | 148 | Once the package is loaded correctly (whether by using straight/elpaca, 149 | or the manual install), you can use the following to enable nova with 150 | vertico, corfu, or eldoc (more modes to come): 151 | 152 | ### nova library 153 | 154 | nova currently supports 2 styles: **side-left** and **top-center** 155 | 156 | side-left shows the title on the left side of the frame (as can be seen 157 | in the nova-vertico image above). It adds some width to the frame, and 158 | might not fit all scenarios (for example, when no title is required). 159 | 160 | top-center shows the title at the top center of the frame. It is more 161 | minimalistic than side-left, and also looks nice when no title is needed. 162 | 163 | There are currently 2 functions that allow you to create a nova frame: 164 | nova-show, and nova-show-with-posframe. nova frames are wrappers around 165 | existing child frames (posframe or other). Sometimes you have an existing 166 | frame you want to wrap around (such as the case with nova-vertico, nova-corfu, 167 | and nova-eldoc), but in other cases you might just want to pop a nova frame 168 | to display some information. 169 | 170 | **nova-show** is used in the latter case, where you just want to popup nova 171 | to display some inforamtion. it accepts 4 arguments (and another optional one): 172 | 173 | name - a unique name to identify the nova frame (used for the buffer of the frame). 174 | title - self explanatory. 175 | style - currently nova supports either 'side-left or 'top-center. 176 | pos-name - a unique name given to the internal posframe that will be wrapped around 177 | by nova. 178 | posframe-args - additional arguments to pass to the posframe function for the internal 179 | posframe we create. 180 | 181 | 182 | **nova-show-with-posframe** is used in cases where we have an existing posframe we want 183 | to wrap around with nova (i.e. nova-vertico). It accepts 4 arguments: 184 | 185 | name, title, style - same as nova-show above. 186 | 187 | pos-frame - the existing posframe you want to wrap nova around. 188 | 189 | 190 | In addition to nova-show and nova-show-with-posframe, the nova library 191 | provides the following functions: 192 | 193 | **nova-update**: recreates the nova frame's SVG to adjust the look of the frame in 194 | case the frame was updated (i.e. its size has changed) 195 | 196 | **nova-delete-frame**: delete a nova frame by its name 197 | 198 | **nova-delete-all**: deletes all nova frames 199 | 200 | ### nova-vertico 201 | 202 | nova-vertico is similar to ![vertico-posframe](https://github.com/tumashu/vertico-posframe) (make sure to have it installed), 203 | and relies on it, but also wraps it in a (side-left) nova frame: 204 | 205 | ```elisp 206 | 207 | (nova-vertico-mode 1) 208 | 209 | ``` 210 | 211 | ### nova-corfu/nova-corfu-popupinfo 212 | 213 | nova-corfu (nova-corfu-popupinfo) shows corfu in-buffer completions 214 | (or corfu-popupinfo) in a (side-left) nova frame: 215 | 216 | ```elisp 217 | 218 | (nova-corfu-mode 1) 219 | (nova-corfu-popupinfo-mode 1) 220 | 221 | ``` 222 | 223 | ### nova-eldoc 224 | 225 | nova-eldoc shows eldoc-box-hover (make sure to have eldoc installed), 226 | in a (top-center) nova frame: 227 | 228 | ```elisp 229 | 230 | (nova-eldoc-mode 1) 231 | 232 | ``` 233 | 234 | ## Creating new styles 235 | 236 | To develop a new nova style, you will need to implement 2 functions: 237 | 238 | ### nova--render-- 239 | 240 | nova--render functions' job is to draw the actual SVG, 241 | representing the frame's aesthetics. 242 | 243 | Please look at nova--render-side-left or nova--render-top-center 244 | (in nova-side-left.el or nova-top-center.el, accordingly) for 245 | examples on how to accomplish that. 246 | 247 | **Note**: make sure the coordinates you use to calculate where to 248 | place shapes/etc. are screen agnostic, so that the frame will 249 | be shown correctly on any screen's resolution. 250 | 251 | nova--render functions must accept 2 arguments: 252 | 253 | - name 254 | 255 | `name' is the name given to the nova frame itself, so that 256 | you can get the local values of the nova library's variables, 257 | like nova-title-color, nova-radius-x, etc. using the library's 258 | internal function called nova--get-local. 259 | 260 | - svg-object 261 | 262 | `svg-object' is the svg object on which you do the actual 263 | SVG drawings on to (it is passed to nova--render by the 264 | main nova library functions. 265 | 266 | 267 | ### nova--show-- 268 | 269 | nova--show functions' job is to set the posframe representing 270 | the nova frame in correct coordinates/size. 271 | 272 | For example, the side-left style adds a bar to the left of the 273 | frame to show the title on the left side, and so requires a 274 | different configuration than the top-center style, which shows 275 | the title at the top center, without requiring any extra width 276 | to the frame (but does require an extra height to show the title). 277 | 278 | Please take a look at nova--show-side-left or nova--show-top-center 279 | (in nova-side-left.el or nova-top-center.el, accordingly) for 280 | examples on how to accomplish that. 281 | 282 | ## Known issues 283 | 284 | - Under Mac Sequoia, with the window placements enabled, sometimes 285 | just moving the window causes the nova frame to stay in-spot, while 286 | the underlying frame moves with the window (as it should). Maximizing 287 | the window seems to work 288 | --------------------------------------------------------------------------------