Home
Softono
vulpea-ui

vulpea-ui

Open source Emacs Lisp
22
Stars
2
Forks
6
Issues
1
Watchers
1 week
Last Commit

About vulpea-ui

A widget-based sidebar for Emacs that displays contextual information (stats, outline, backlinks, links) for vulpea notes

Platforms

Web Self-hosted

Languages

Emacs Lisp

Links

+title: vulpea-ui

+author: Boris Buliga

Sidebar infrastructure and widget framework for [[https://github.com/d12frosted/vulpea][vulpea]] notes.

+begin_html

MELPA CI version

Sponsor

Screenshot

#+end_html
  • Features
  • Per-frame sidebar with configurable position and size
  • Widget system built on [[https://github.com/d12frosted/vui.el][vui]] components
  • Default widgets: outline, backlinks, forward links, stats
  • Easy API for creating custom widgets
  • Auto-hide when switching to non-vulpea buffers
  • Installation

** Dependencies

** Using package.el (MELPA)

+begin_src emacs-lisp

(package-install 'vulpea-ui)

+end_src

** Using straight.el

+begin_src emacs-lisp

(straight-use-package 'vulpea-ui)

+end_src

** Using elpaca

+begin_src emacs-lisp

(elpaca vulpea-ui)

+end_src

  • Usage

+begin_src emacs-lisp

(require 'vulpea-ui)

;; Open sidebar (vulpea-ui-sidebar-open)

;; Or toggle with a keybinding (global-set-key (kbd "C-c v s") #'vulpea-ui-sidebar-toggle)

+end_src

** Automatic sidebar

To automatically open the sidebar when entering org-mode buffers:

+begin_src emacs-lisp

(add-hook 'org-mode-hook #'vulpea-ui-sidebar-open)

+end_src

  • Configuration

** Sidebar position and size

+begin_src emacs-lisp

;; Position: 'right (default), 'left, 'top, 'bottom (setq vulpea-ui-sidebar-position 'right)

;; Size as fraction of frame (0.0-1.0) (setq vulpea-ui-sidebar-size 0.33)

+end_src

** Outline widget

+begin_src emacs-lisp

;; Maximum heading depth (nil = unlimited) (setq vulpea-ui-outline-max-depth 3)

+end_src

** Backlinks widget

+begin_src emacs-lisp

;; Show/hide content previews (setq vulpea-ui-backlinks-show-preview t)

;; Characters before/after link in prose previews (setq vulpea-ui-backlinks-prose-chars-before 30) (setq vulpea-ui-backlinks-prose-chars-after 50)

;; Filter which notes appear in backlinks ;; (function receiving vulpea-note, return non-nil to include) (setq vulpea-ui-backlinks-note-filter (lambda (note) (not (member "archive" (vulpea-note-tags note)))))

;; Filter by context type ;; t = all types, or a list of: meta, header, table, list, quote, code, footnote, prose (setq vulpea-ui-backlinks-context-types t)

;; Sorting: nil (no sorting), 'title-asc, 'title-desc, or custom function (setq vulpea-ui-backlinks-sort 'title-asc)

+end_src

** Behaviour options

+begin_src emacs-lisp

;; Auto-hide sidebar when switching to non-vulpea buffers ;; When nil, sidebar remains visible with stale content (setq vulpea-ui-sidebar-auto-hide t)

;; Start widgets collapsed (setq vulpea-ui-default-widget-collapsed nil)

;; Auto-refresh sidebar on save and idle (enabled by default) (setq vulpea-ui-auto-refresh t)

;; Idle delay before auto-refresh (in seconds) (setq vulpea-ui-auto-refresh-delay 1.5)

+end_src

** Performance

For large org files or many backlinks, enable fast parsing:

+begin_src emacs-lisp

;; Skip org-mode hooks during parsing (faster but may cause issues ;; if your setup depends on mode hooks for org-element parsing) (setq vulpea-ui-fast-parse t)

+end_src

  • Commands

| Command | Description | |-----------------------------+---------------------------| | =vulpea-ui-sidebar-open= | Open the sidebar | | =vulpea-ui-sidebar-close= | Close the sidebar | | =vulpea-ui-sidebar-toggle= | Toggle sidebar visibility | | =vulpea-ui-sidebar-refresh= | Force refresh content |

  • Sidebar keybindings

| Key | Action | |---------+---------------------------------| | =q= | Close sidebar | | =g= | Refresh content | | =TAB= | Navigate to next widget | | =S-TAB= | Navigate to previous widget | | =RET= | Activate widget at point |

=TAB=, =S-TAB=, and =RET= are inherited from =vui-mode=. =q= and =g= are sidebar-specific.

For quick navigation to any widget, consider [[https://github.com/d12frosted/ace-link-vui][ace-link-vui]].

  • Default widgets

** Stats widget

+begin_html

Screenshot

#+end_html

Shows character count, word count, and link count for the current note.

** Outline widget

+begin_html

Screenshot

#+end_html

Displays the heading structure of the note. Click headings to navigate.

** Backlinks widget

+begin_html

Screenshot

#+end_html

Shows notes that link to the current note, grouped by file with:

  • Heading path context (where in the document the link appears)
  • Content preview with context type indicators:
    • =⊢= meta property
    • =§= header
    • =▤= table
    • =·= list item
    • =>= quote
    • =λ= code
    • =†= footnote
    • (no indicator) prose

** Links widget

+begin_html

Screenshot

#+end_html

Shows notes that the current note links to.

  • Widget Registration

Widgets are registered with =vulpea-ui-register-widget=, which allows filtering by predicate and ordering.

** Built-in widgets

vulpea-ui registers these widgets by default:

| Widget | Order | Component | |-------------+-------+-----------------------------| | =stats= | 100 | =vulpea-ui-widget-stats= | | =outline= | 200 | =vulpea-ui-widget-outline= | | =backlinks= | 300 | =vulpea-ui-widget-backlinks= | | =links= | 400 | =vulpea-ui-widget-links= |

** Registering widgets

+begin_src emacs-lisp

(vulpea-ui-register-widget 'my-widget :component 'my-custom-widget-component :predicate #'my-note-predicate ; optional: only show when this returns non-nil :order 150) ; optional: default 100

+end_src

** Modifying widget properties

=vulpea-ui-widget-set= updates a single property on an already-registered widget. This works for any widget, including the built-in ones, so you can customise them without redefining anything.

+begin_src emacs-lisp

;; Change a widget's order (vulpea-ui-widget-set 'stats :order 50)

;; Attach a predicate to a built-in widget (vulpea-ui-widget-set 'outline :predicate #'my/show-outline-p)

;; Remove a widget (vulpea-ui-unregister-widget 'links)

+end_src

** Toggling a widget per note

Built-in widgets have no predicate by default, so they are shown for every note. You can install one with =vulpea-ui-widget-set= to decide per note whether the widget shows up. A common recipe is a global default variable combined with an org property that overrides it on individual notes.

The example below hides the =outline= widget by default and shows it only on notes that set =:OUTLINE: t=. Flip =my/vulpea-ui-always-show-outline= to =t= to invert the default, in which case =:OUTLINE: nil= hides the widget on specific notes.

+begin_src emacs-lisp

(defvar my/vulpea-ui-always-show-outline nil "When non-nil, show the outline widget unless a note opts out.")

(defun my/vulpea-ui-show-outline-p (note) "Return non-nil if the outline widget should be shown for NOTE. An `OUTLINE' property on the note overrides the default variable." (if-let* ((props (vulpea-note-properties note)) (entry (assoc "OUTLINE" props))) (not (equal (cdr entry) "nil")) my/vulpea-ui-always-show-outline))

(vulpea-ui-widget-set 'outline :predicate #'my/vulpea-ui-show-outline-p)

+end_src

The same pattern works for any widget (=stats=, =backlinks=, =links=, or your own).

** How it works

  1. Widgets are filtered by their =:predicate= (if any) against the current note
  2. Remaining widgets are sorted by =:order= (ascending)
  3. Each widget's =:component= is rendered

This allows packages like =vulpea-journal= to register context-specific widgets that only appear when viewing certain notes.

  • Custom widgets

Create custom widgets using vui's =vui-defcomponent= macro:

+begin_src emacs-lisp

(vui-defcomponent my-custom-widget () "My custom widget." :render (let ((note (use-vulpea-ui-note))) (vui-component 'vulpea-ui-widget :title "My Widget" :count 42 :children (lambda () (vui-text "Custom content here")))))

+end_src

Register the widget:

+begin_src emacs-lisp

(vulpea-ui-register-widget 'my-widget :component 'my-custom-widget :order 250) ; after outline, before backlinks

+end_src

For context-specific widgets (e.g., only for notes with a certain tag):

+begin_src emacs-lisp

(defun my-project-note-p (note) "Return non-nil if NOTE is a project note." (member "project" (vulpea-note-tags note)))

(vulpea-ui-register-widget 'project-tasks :component 'my-project-tasks-widget :predicate #'my-project-note-p :order 150)

+end_src

  • Utility Functions

** vulpea-ui-clean-org-markup

Cleans org-mode markup from text for display purposes:

+begin_src emacs-lisp

(vulpea-ui-clean-org-markup text)

+end_src

Transformations:

  • Links: =[[url][title]]= → =title=, =[[url]]= → =url= (bare =[[id:...]]= links are removed)
  • Drawers: =:PROPERTIES:...:END:= blocks are removed
  • Metadata: =#+TITLE:=, =#+FILETAGS:=, etc. lines are removed
  • Whitespace: multiple spaces/tabs collapsed to single space

Useful for rendering previews in custom widgets.

  • Faces

| Face | Description | |--------------------------------------+-------------------------| | =vulpea-ui-widget-header-face= | Widget header text | | =vulpea-ui-widget-count-face= | Widget count numbers | | =vulpea-ui-outline-heading-face= | Outline headings | | =vulpea-ui-stats-face= | Statistics text | | =vulpea-ui-backlink-preview-face= | Backlink preview text | | =vulpea-ui-backlink-heading-face= | Backlink heading path | | =vulpea-ui-backlink-meta-key-face= | Meta block keys | | =vulpea-ui-backlink-meta-value-face= | Meta block values | | =vulpea-ui-backlink-context-face= | Context type indicators |

  • Related Projects
  • License

Copyright (C) 2024-2026 Boris Buliga

This program 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 of the License, or (at your option) any later version.

  • Support

If you enjoy this project, you can support its development via [[https://github.com/sponsors/d12frosted][GitHub Sponsors]] or [[https://www.patreon.com/d12frosted][Patreon]].