+title: grove.el
+author: Jonathan Chu
+html:

An Obsidian-like note-taking mode for Emacs. One keybinding opens a full workspace with a file tree sidebar and your org notes.
[[file:grove-screenshot.png]]
[[https://melpa.org/#/grove][https://melpa.org/packages/grove-badge.svg]] [[https://img.shields.io/badge/license-GPL--3.0-blue.svg]]
- Features
- File tree sidebar with expand/collapse, indent guides, current file tracking, item counts, optional nerd font icons, and note preview on navigation
- Quick capture — just type, first line becomes the title, saved to your inbox
- Wikilinks — =[[note title]]= syntax with font-lock, click to follow, create on missing
- Backlinks — ripgrep-powered, computed on demand, displayed in a side panel
- Daily notes — one keybinding for today, yesterday, or tomorrow
- Search — full-text ripgrep search with optional Consult integration
- Tag search — find notes by =#hashtag= or org =:tag:= syntax
- Inbox review — triage untagged notes
- Graph view — visualize note connections via Graphviz
No database. No external Emacs dependencies. Just org files, a directory, and ripgrep.
- Requirements
- Emacs 29.1+
- [[https://github.com/BurntSushi/ripgrep][ripgrep]] (=rg=) on your PATH
Optional:
- [[https://github.com/minad/consult][Consult]] for live search with preview
- [[https://graphviz.org][Graphviz]] (=dot=) for graph view
- [[https://www.nerdfonts.com][Nerd Fonts]] for file/folder icons in the tree sidebar
- Installation
** MELPA
Grove is available on [[https://melpa.org/#/grove][MELPA]]. After [[https://melpa.org/#/getting-started][adding MELPA]] to your package archives:
+begin_src emacs-lisp
(use-package grove :ensure t :bind-keymap ("C-c v" . grove-command-map) :custom (grove-directory "~/notes/") :config (global-grove-mode 1))
+end_src
=global-grove-mode= activates =grove-mode= in any org buffer that lives inside =grove-directory=, which enables wikilink font-locking and the =C-c C-l= keybinding for inserting links. Omit it if you'd rather enable =grove-mode= manually per buffer.
** Manual
Clone the repository and add it to your load path:
+begin_src emacs-lisp
(use-package grove :load-path "~/path/to/grove" :bind-keymap ("C-c v" . grove-command-map) :custom (grove-directory "~/notes/") :config (global-grove-mode 1))
+end_src
- Usage
Set your vault directory and press =C-c v v= to open the workspace.
| Key | Command | Description | |-----------+----------------------+--------------------------| | =C-c v v= | =grove-open= | Open the grove workspace | | =C-c v q= | =grove-close= | Close and restore layout | | =C-c v n= | =grove-capture= | Quick capture a new note | | =C-c v f= | =grove-find= | Find note by title | | =C-c v s= | =grove-search= | Full-text ripgrep search | | =C-c v d= | =grove-daily= | Open today's daily note | | =C-c v b= | =grove-backlinks= | Show backlinks for note | | =C-c v t= | =grove-search-tag= | Search by tag | | =C-c v i= | =grove-inbox-review= | Triage untagged notes | | =C-c v l= | =grove-link-insert= | Insert a wikilink | | =C-c v g= | =grove-graph= | Show vault graph |
** Tree sidebar
| Key | Action | |-------------+-------------------------------| | =RET= | Open file / toggle dir | | =TAB= | Toggle directory expand | | =n= / =C-n= | Next entry (with preview) | | =p= / =C-p= | Previous entry (with preview) | | =g= | Refresh tree | | =q= | Close sidebar |
** Capture
=C-c v n= opens =grove-capture=, a blank org buffer. Type freely — no prompts, no template. The first line becomes the title; everything below becomes the body.
- =C-c C-c= saves to the inbox
- =C-c C-k= discards
For example, typing:
+begin_src org
Project ideas for next quarter
- migrate to ripgrep
- add a graph view
+end_src
and pressing =C-c C-c= writes =inbox/project-ideas-for-next-quarter.org=:
+begin_src org
,#+title: Project ideas for next quarter
- migrate to ripgrep
- add a graph view
+end_src
Filenames are derived from the title (lowercased, spaces to dashes). A numeric suffix is appended on collision.
** Inbox review
=C-c v i= opens =grove-inbox=, a triage buffer that lists notes needing attention. The buffer groups notes by the reason they showed up — currently, Untagged: notes with no =#+filetags:= line and no inline =#hashtags=.
| Key | Action | |-------------+------------------------------| | =RET= | Visit the note at point | | =n= / =p= | Move to next / previous line | | =g= | Refresh the buffer | | =q= | Close the buffer |
The intended workflow is short loops: open the inbox, jump into a note with =RET=, add tags or wikilinks, save, and either come back with =C-c v i= or refresh in place with =g=. Notes drop off the list as you tag them, so the buffer doubles as a progress indicator.
The list is built from the vault cache, so it covers your whole vault — not just files under =grove-inbox-directory=. Tag a note anywhere and it disappears from the next refresh.
** Graph
=C-c v g= renders a graph of all notes and their =[[wikilinks]]= using Graphviz. Requires =dot= on your PATH.
The graph display adapts to your frame width — on wide frames (160+ columns) it opens as a right side panel, otherwise it uses a full buffer. You can override this with =grove-graph-display=: ='side=, ='buffer=, or ='auto= (default).
Use =+= / =-= to zoom in/out and =0= to fit to window.
- Configuration
+begin_src emacs-lisp
;; Required: set your vault directory (setq grove-directory "~/notes/")
;; Optional: customize subdirectories (defaults shown) (setq grove-inbox-directory "inbox") (setq grove-daily-directory "daily")
;; Optional: daily note filename format (default shown) (setq grove-daily-format "%Y-%m-%d")
;; Optional: tree sidebar width (default shown) (setq grove-tree-width 30)
;; Optional: show nerd font icons in the tree sidebar (setq grove-tree-icons t)
;; Optional: graph view settings (defaults shown) (setq grove-graph-layout "neato") ; or "dot", "fdp", "sfdp" (setq grove-graph-display 'auto) ; or 'side, 'buffer (setq grove-graph-min-width 160) ; frame width threshold for auto
+end_src
- License
GPL-3.0. See [[file:LICENSE][LICENSE]].