initial commit

This commit is contained in:
2026-02-18 10:26:44 +01:00
commit a8bccc3dea
8 changed files with 1597 additions and 0 deletions

218
README.org Normal file
View File

@@ -0,0 +1,218 @@
#+title: org-roam-project
#+author: Luis
#+language: en
Per-project [[https://www.orgroam.com/][org-roam]] databases via [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Projects.html][project.el]].
* Overview
=org-roam-project= extends org-roam so that each =project.el=-recognised project
(a Git repository, or any directory with a VCS root) can have its own isolated
notes directory and SQLite database.
When you work inside a project, the =org-roam-project-*= commands scope
automatically to that project's notes and database. Your personal zettelkasten
— the global =org-roam-directory= — is completely unaffected.
#+begin_example
Personal zettelkasten Project notes
┌──────────────────┐ ┌──────────────────────────┐
│ ~/org-roam/ │ │ ~/code/my-project/ │
│ │ │ notes/ ← notes │
│ ~/.emacs.d/ │ │ notes/.org-roam.db ← DB │
│ org-roam.db │ └──────────────────────────┘
└──────────────────┘
▲ ▲
(not in a project) (inside a project)
#+end_example
* Requirements
- Emacs 28.1 or later (=project.el= is built in)
- [[https://github.com/org-roam/org-roam][org-roam]] 2.2.0 or later
- A working org-roam setup (=org-roam-directory= already configured)
* Installation
** Manual
Clone or copy =org-roam-project.el= somewhere on your =load-path=, then:
#+begin_src emacs-lisp
(require 'org-roam-project)
(org-roam-project-mode)
#+end_src
** use-package
#+begin_src emacs-lisp
(use-package org-roam-project
:load-path "path/to/org-roam-project"
:after org-roam
:config
(org-roam-project-mode))
#+end_src
* Quick start
1. Open any file inside a =project.el= project (e.g. a Git repository).
2. Initialise org-roam for that project:
#+begin_example
M-x org-roam-project-init
#+end_example
This creates the notes subdirectory (=notes/= by default), adds the database
file to =.gitignore= if one is present, and runs an initial database sync.
3. Start taking notes:
#+begin_example
M-x org-roam-project-node-find ; find or create a note
M-x org-roam-project-capture ; capture a new note
#+end_example
4. These commands are also available from the project dispatch menu:
#+begin_example
C-x p p ; project-switch-project
n ; Roam find (org-roam-project-node-find)
N ; Roam capture (org-roam-project-capture)
#+end_example
* Commands
| Command | Description |
|-----------------------------------+------------------------------------------------------------------|
| =org-roam-project-init= | Initialise the current project (create dir, update .gitignore, sync DB) |
| =org-roam-project-node-find= | Find or create a node in the project (like =org-roam-node-find=) |
| =org-roam-project-node-insert= | Insert a link to a project node (like =org-roam-node-insert=) |
| =org-roam-project-capture= | Capture a new project note (like =org-roam-capture=) |
| =org-roam-project-buffer-toggle= | Toggle the backlinks buffer scoped to the project |
| =org-roam-project-db-sync= | Manually re-sync the project's org-roam database |
* Configuration
** Customisation variables
| Variable | Default | Description |
|-------------------------------------+------------------+------------------------------------------------------|
| =org-roam-project-notes-subdir= | ="notes"= | Subdirectory within the project root for notes |
| =org-roam-project-db-filename= | =".org-roam.db"= | Filename of the per-project SQLite database |
| =org-roam-project-auto-create= | =nil= | Automatically create the notes dir if it is missing |
These can be changed globally:
#+begin_src emacs-lisp
(setq org-roam-project-notes-subdir "docs/notes"
org-roam-project-db-filename ".roam.db")
#+end_src
** Per-project configuration via =.dir-locals.el=
All three variables are declared =safe-local-variable=, so they can be
overridden in a project's =.dir-locals.el= without Emacs prompting you:
#+begin_src emacs-lisp
;; ~/code/my-project/.dir-locals.el
((nil . ((org-roam-project-notes-subdir . "docs/notes")
(org-roam-project-db-filename . ".org-roam.db"))))
#+end_src
This lets different projects store their notes in different places without
changing the global defaults.
** Automatic notes directory creation
By default, commands signal an error when the project's notes directory does
not exist, reminding you to run =org-roam-project-init= first. If you prefer
the directory to be created automatically on first use, set:
#+begin_src emacs-lisp
(setq org-roam-project-auto-create t)
#+end_src
* How it works
** Context detection
Every command calls =org-roam-project--context= which:
1. Calls =(project-current)= to detect the project from the current
=default-directory=.
2. Reads =org-roam-project-notes-subdir= and =org-roam-project-db-filename=,
respecting any =.dir-locals.el= values for the project root.
3. Returns the absolute paths to the notes directory and database file, or
=nil= if the notes directory does not exist.
** Scoping via =let=-binding
Rather than patching org-roam internals, the package simply =let=-binds
=org-roam-directory= and =org-roam-db-location= around every call:
#+begin_src emacs-lisp
(let ((org-roam-directory "/path/to/project/notes")
(org-roam-db-location "/path/to/project/notes/.org-roam.db"))
(org-roam-node-find))
#+end_src
Because org-roam's internal connection cache is keyed by =org-roam-directory=,
multiple project databases can be open simultaneously with no conflicts.
** Autosync on save
When =org-roam-project-mode= is active, an =:around= advice on
=org-roam-db-update-file= intercepts every save. If the file being saved lives
inside a project's initialised notes directory, the advice temporarily binds
the project context so the update goes to the right database. Files saved
outside any project's notes directory continue to update the global database as
normal.
** project-switch-commands integration
With =org-roam-project-mode= enabled, two entries are added to
=project-switch-commands=:
#+begin_src emacs-lisp
(org-roam-project-node-find "Roam find" ?n)
(org-roam-project-capture "Roam capture" ?N)
#+end_src
These appear in the =C-x p p= dispatch menu and work correctly with
=project-current-directory-override=, so switching to a project with
=C-x p p= and then pressing =n= opens that project's notes, not the global
zettelkasten.
* File layout
After running =org-roam-project-init= in a project, the layout looks like:
#+begin_example
~/code/my-project/
├── .git/
├── .gitignore ← .org-roam.db appended automatically
├── src/
│ └── ...
└── notes/ ← org-roam-project-notes-subdir
├── .org-roam.db ← org-roam-project-db-filename (not committed)
├── 20260101T120000--my-first-note.org
└── 20260215T090000--another-note.org
#+end_example
* Limitations
- *No cross-project linking.* Each project database is isolated. Links between
a project note and your personal zettelkasten (or another project) will not
appear as backlinks.
- *Global capture templates.* Per-project capture templates are not yet
supported. The global =org-roam-capture-templates= are used in all projects.
As a workaround, you can set =org-roam-capture-templates= via =.dir-locals.el=.
- *No graph visualisation scoping.* =org-roam-ui= (if you use it) is not
automatically scoped to the project database.
* License
GPLv3 or later. See the header of =org-roam-project.el= for details.