Compare commits

...

71 Commits

Author SHA1 Message Date
89e9121f26 (release): Org-roam v1.2.1 (#975) 2020-07-27 01:03:35 +08:00
c536fd4f2e (fix): check if path is remote before using file-truename (#970)
this prevents cache builds from stalling, on paths that point to remote files
2020-07-27 00:30:54 +08:00
20f876aa6b (feat): enable nested captures (#966)
This PR enables the long-awaited nested-captures.

1. Adds a hook to org-capture-prepare-finalize-hook, which installs org-roam-capture--finalize into org-capture-after-finalize-hook if the capture is an Org-roam capture. This function contains key functionality for Org-roam to Do The Right Thing after specific interactive functions, such as finding the file, or inserting a link.

2. A patch for org-capture-finalize. Specifically, we make org-capture-plist valid during org-capture-finalize.

3. Many hacks that were originally in place are now replaced with nicer alternatives.

Co-authored-by: Leo Vivier <zaephon@gmail.com>
2020-07-27 00:21:41 +08:00
863ae2427e (feat): add customize settings for capture templates (#968)
adds ability to customize capture templates using the Customize interface for:

1. org-roam-capture-ref-templates
2. org-roam-dailies-capture-templates
3. org-roam-capture-immediate-template
2020-07-26 15:15:56 +08:00
379d5e4770 (doc): Add Andreas to backers (#969) 2020-07-25 16:33:36 +08:00
4d992ce9e3 (doc): add debian/ubuntu installation instructions (#965)
Addresses #964
2020-07-23 22:46:21 +08:00
1d9968bf69 Make the db caching more efficient for gpg encrypted files (#963)
Before this patch all hash-sums were computed over the files or
buffers content.  For encrypted files this means that we first have
decrypt the file before we can compute the hash-sum.  So when the
cache get's updated all gpg files need to be decrypted which is a very
expensive operation and nearly defeats the purpose of having a cache
in the first place (for gpg files).

This changes the computation of hash-sums for gpg encrypted files.
Instead of the content the raw files on disk will be read.

This shouldn't interfere with the current use of hashes.

There is one ugly (but otherwise inconsequential) ward, though.
For open buffers of to be gpg encrypted files we need to compute the
hash sum over the contents as well.  This is because there is
no (easy) way to get the encrypted version.  The consequence is that
that buffers file will be rehashed again (then using the bytes on
disk).  But all other non changed gpg files will only be hashed once,
as desired.

Co-authored-by: Jethro Kuan <jethrokuan95@gmail.com>
2020-07-23 22:36:57 +08:00
650744adc7 (doc): Create ‘Getting Help’ (#961)
* (doc): Create ‘Getting Help’

* README.md: Add ‘Getting Help’

Co-authored-by: Jethro Kuan <jethrokuan95@gmail.com>
2020-07-22 19:06:54 +02:00
d099f9bef9 (fix): enable org-mode in temp buffers (#957)
Fixes #956.

The extraction of links rely on appropriate regexp being set for
outline-mode, to determine the outline information. Because the
information were extracted in a temporary buffer, the outline regexp was
not set. This corrects for that, but probably adds significant
extraction time.
2020-07-22 20:59:35 +08:00
b5f3f04318 (doc): Add an FAQ section (#959)
* README.md: Create FAQ section and mention `C-M-j` for `ivy`
2020-07-22 09:07:49 +02:00
c20c8f9a0a (fix): Check sqlite3’s executability using boundp (#954)
Fixes #837. The existing merged fixes do not actually work, because fboundp tests for a function.
2020-07-21 15:51:43 +08:00
c24fb51b03 (feat): cache entered template variables (#952)
Fixes #951.
2020-07-20 22:14:21 +08:00
80390b5a84 (fix): fix display of preview content (#950)
Don't modify line-breaks in preview content. Closes #630.
2020-07-19 13:33:01 +08:00
eb7ee0ef6c (docs): add docs on notes versioning (#949) 2020-07-19 12:37:53 +08:00
fb5beeb14d (docs): clarify org-roam-completion-system (#948) 2020-07-19 12:26:00 +08:00
10e91a88c1 (docs): make explicit need to set org-roam-directory early (#946) 2020-07-18 18:04:52 +08:00
4cdab9103f (fix): fix possible nil dbs on update operations (#945)
Addresses #942
2020-07-18 13:09:40 +08:00
4ec4e60358 (doc): Add SVG source for logo (#943) 2020-07-16 10:20:42 +02:00
7a4b15fd36 (fix): Check if link-description is empty prior to inserting in db (#927)
* (fix): Check if link-description is empty prior to inserting in db

Caused problems with plain links, i.e., those not framed by
brackets (e.g. `file:foo.org` or `id:$uuid`).
2020-07-11 23:42:05 +02:00
f9fea29c44 (feat): Add customize settings to org-roam-capture-templates (#924)
* (feat): Add customize settings to `org-roam-capture-templates`

Declare `org-roam-capture-templates` with `defcustom` and update the
docstring.

Co-authored-by: Leo Vivier <leo.vivier+dev@gmail.com>
2020-07-11 20:48:10 +02:00
569aeb84ed (fix): Change symbol used for delta (#925)
7f56df7f4d introduced the delta symbol in the
db-rebuild message to make it clearer the the db was updated as opposed to
being completely rebuilt.

However, the unicode symbol used, U+1D6AB, which is the mathematical, bold
variant of the Greek letter is not present in many fonts.  Switching to
U+0394, the base Greek letter, is better for compatibility.
2020-07-11 20:19:55 +02:00
1574e0d351 Exclude backup files from renaming advice (#915)
Co-authored-by: Leo Vivier <leo.vivier+dev@gmail.com>
2020-07-10 23:18:33 +02:00
fffef6711f (fix): Update rename-file-advice to new schemata (#917)
Caused by #908.
2020-07-10 19:59:07 +02:00
ef23f507ec (fix): Adapt get-title-or-slug to new schemata (#916)
Follow-up to #908.
2020-07-10 19:34:12 +02:00
f1dbe3fdf9 (fix): fix org-roam graph building with new db schema (#914) 2020-07-10 19:23:38 +08:00
efba3c2bf0 (internal): normalize titles in database (#908)
Instead of storing titles as a list within in the Org-roam cache, e.g.
file, ("title1" "title2"). We normalize it and store it as:

file, "title1"
file, "title2"
2020-07-10 14:36:54 +08:00
6770c3eaf5 (feat): Implement basic output for find-ref with C-u arg (#907) 2020-07-10 08:24:04 +02:00
b8aa5c1f23 (feat): Improve interactive format for ref completions (#906) 2020-07-10 11:47:07 +08:00
ca4a7421bc (docs): Update org-roam-capture documentation (#904) 2020-07-09 12:56:17 +08:00
3348298527 (internal): org-roam-db--clear -> org-roam-db-clear (#902)
Since it is a called interactive, it should be given a public name.
2020-07-08 15:55:23 +08:00
d77f897400 (feat): support more ref links (#900)
This adds support for all sorts of ref links (http, https, and any
custom link types). If the ref is not a file or org-ref citation link,
the full link path is used.

Closes #744.
2020-07-08 12:29:06 +08:00
9f7ed4353c (feat): add org-roam-random-note (#898) 2020-07-07 14:45:28 +08:00
d19a711a44 (fix): fix escaping of backslashes in graph generation (#897)
Fixes #884
2020-07-07 13:07:10 +08:00
9766862e84 (docs): Add forkrul to BACKERS (#896) 2020-07-07 12:36:27 +08:00
aedfca8de2 (fix): rename links as long as old file is Org-roam file (#894)
Previously, if the new file is no longer an Org-roam file, links will
not be fixed. The current behaviour is now to perform the link fixes as
long as the old file as an Org-roam file.

Closes #893
2020-07-06 21:34:52 +08:00
21bc220ed3 (docs): fix qutebrowser roam-protocol binding (#891) 2020-07-06 11:39:11 +08:00
d58fc31dfb (docs): add docs for using winner-mode as history (#890) 2020-07-05 23:07:41 +08:00
64a0bfd168 (docs): add Burke to BACKERS (#889) 2020-07-05 22:43:13 +08:00
3aff6b2be7 (bugfix): prevent file-exists-p opening tramp links (#885)
Links like /ssh:me@host:/ cause emacs to lock up asking for the password repeatedly due to using file-exists-p in a function passed to font lock.

Co-authored-by: Herbert Jones <herbert@hj-desktop.home>
Co-authored-by: Jethro Kuan <jethrokuan95@gmail.com>
2020-07-05 22:36:35 +08:00
7f56df7f4d (internal): add delta symbol in cache build message (#886)
This makes it clearer that the message shows the changes in the database, and not the total number.
2020-07-05 17:48:17 +08:00
0830da4504 (tests): increase perf test threshold (#887)
Things take more time to run now that we are also caching headline hierarchies.
2020-07-05 17:36:44 +08:00
3a1c826aa0 (docs): add protocol instructions for Windows and WSL2 (#882)
See #870
2020-07-04 00:25:23 +08:00
1e11a3a16f (feat): add customizable org-capture function (#877)
New option: `org-roam-capture-function`

Specifies which command is used by org-roam to run the org-capture
process. The default behaviour maintains compatibility with earlier
versions of org-roam. I.E.:
- uses `org-capture`.
- immeadiately chooses the first capture template when
  `org-roam-capture-templates` has only one template.

When you've changed `org-roam-capture-function` AND
`org-roam-capture-templates` has multiple templates,
`org-roam-capture-function` is run interactively to
start the org capture process.

Eg. usage: `(setq org-roam-capture-function 'counsel-org-capture)`
2020-07-01 23:24:40 +08:00
e33c3bcb3f (internal): lower default value of org-roam-db-gc-threshold (#872)
The high value has been reported to cause significant slowdowns, so we
reset the default, and add documentation for its customization instead.
Ref #834
2020-06-28 15:16:14 +08:00
610d4ced85 (internal): save-match-data on outline extraction (#871) 2020-06-27 21:26:03 +08:00
2f13d1fe64 (internal): fix expand-file usage in org-roam--expand-links (#866) 2020-06-26 14:57:27 +08:00
ee28b5e6b1 (docs): improve roam-ref docs (#865)
The current documentation made me think that bookmarklet feature was limited to Firefox. This patch slightly improves it.
2020-06-26 14:52:08 +08:00
79c75ac174 (feat): Add header level to backlinks buffer (#863)
adds the outline hierarchy to the backlinks buffer
2020-06-25 12:40:22 +08:00
c59d6c4f7c (internal): wrap db updates in a sql transaction (#862)
This will ensure atomicity with updates and should stop any partial updates that may occur.
2020-06-24 13:30:44 +08:00
220f395c1f (feat): add org-roam-find-file-immediate (#852)
Addresses #852.
2020-06-22 16:19:27 +08:00
76b2ac3460 (bugfix): fix tag extraction for symlinked directories (#857)
* (bugfix): fix tag extraction for symlinked directories

Resolves symlinks before computing relative paths, fixes #855

* Update changelog
2020-06-21 19:34:04 +08:00
11e0aa4c55 (feat): warn on duplicate IDs and refs rather than fail (#854)
Instead of having db update operations fail, a useful error is shown, and the db updates complete. Closes #816.
2020-06-21 17:00:20 +08:00
408e38f8ba (perf): use sqlite transactions, and GC less (#847)
We use sqlite transactions to commit changes into the database, rather
than storing all the data in a list before running one big insert.
Hopefully this gives a noticeable perf boost.

We also add `org-roam-db-gc-threshold`, which shaves time by deferring the garbage collection to the end.
2020-06-19 18:27:14 +08:00
f16de357a6 (feat): add 'first-directory option for org-roam-tag-sources (#851)
Co-authored-by: Jethro Kuan <jethrokuan95@gmail.com>
2020-06-19 18:22:52 +08:00
abd81918e1 (docs): update docs link in README (#850)
Thanks for the catch!
2020-06-19 12:04:09 +08:00
6a37fff1e6 (fix): fix bad upcase for org-roam-tag-sources (#849) 2020-06-19 01:58:17 +08:00
527c693f65 (docs): Update links to online page (#848)
We now own and use the orgroam.com domain
2020-06-18 19:30:42 +08:00
76d419cc89 (fix): fix emacsql-sqlite3-executable possibly unbound (#846)
On old versions of emacsql-sqlite3, this will catastrophically fail.
2020-06-18 14:44:26 +08:00
3f2f7e3ff7 (docs): use org-roam-graph-show on README (#844) 2020-06-18 02:56:25 +08:00
fd73da9410 (feat): org-roam-insert: return selected file (#839)
This is useful in scenarios when you want to automatically do something with the
context where the link was inserted.
2020-06-17 19:07:35 +08:00
11902bc790 (tests): add performance tests to CI (#841) 2020-06-17 18:38:10 +08:00
185f9877ae (tests): add test for headline extraction (#840) 2020-06-17 15:32:12 +08:00
1276e801c0 (feat): add customizable org-roam-title-to-slug-function (#833)
Co-authored-by: Jethro Kuan <jethrokuan95@gmail.com>
2020-06-17 14:58:11 +08:00
04d335cc40 (fix): Check sqlite3’s executability before existence (#838) 2020-06-17 14:52:26 +08:00
N V
8b16e5d520 (feat): org-roam-find-file-function customization (#807)
Allow users to specify a function for finding files in various commands.
Can be let-bound to define new commands that suit user preferences.

See: #790
2020-06-16 15:37:03 +08:00
7ab67436a7 (feat): Implement ‘org-roam-insert-immediate’ (#814) 2020-06-15 18:34:27 +08:00
87403b330c (feat): Split org-roam-buffer-toggle into -activate and -deactivate (#819)
Co-authored-by: Leo Vivier <leo.vivier+dev@gmail.com>
2020-06-14 08:12:23 +02:00
N V
fae45434b5 (doc): Add :ensure to use-package declaration (#820)
Ensure use-package installs org-roam.

See: #803
2020-06-14 11:58:42 +08:00
9cf26494e8 (ux): update the error message for org-roam-unlinked-references (#818)
Co-authored-by: Leo Vivier <leo.vivier+dev@gmail.com>
Co-authored-by: Santiago Gepigon <santiago.gepigon@gmail.com>
2020-06-13 22:33:37 +08:00
0d5efe1c14 (fix): fix unlinked-references for older Emacs (#812)
Emacs' in-built rx.el was rewritten in Emacs 27, and the form `anychar`
only exists in later versions. We use the older form `anything` that has
support even in Emacs 26.

Emacs 26: https://github.com/emacs-mirror/emacs/blob/emacs-26/lisp/emacs-lisp/rx.el#L889
2020-06-13 11:14:57 +02:00
2eb0aac88a (feat): Switch to a minor-mode for the dev-suite (#805)
* (feat): Switch to a minor-mode for the dev-suite

Co-authored-by: N V <44036031+progfolio@users.noreply.github.com>

Co-authored-by: N V <44036031+progfolio@users.noreply.github.com>
2020-06-12 22:01:52 +02:00
23 changed files with 2053 additions and 682 deletions

View File

@ -3,4 +3,4 @@
((emacs-lisp-mode
(eval . (require 'org-roam-dev))
(sentence-end-double-space . nil)))
(eval . (org-roam-dev-mode))))

View File

@ -3,3 +3,6 @@
Many thanks to the following backers, your contributions are greatly appreciated!
- Nathan Tran
- Burke Libbey
- forkrul
- Andreas Stuhlmüller

View File

@ -1,5 +1,36 @@
# Changelog
## 1.2.1 (27-07-2020)
This release consisted of a big deal of refactoring and bug fixes. Notably, we fixed several catastrophic failures on db builds with bad setups (#854), and modularized tag and title extractions.
We also added some new features that had been a long time coming:
1. We made the backlinks more outline-friendly by also showing the outline hierarchy for a backlink (#863)
2. We now support nested captures, which is crucial for `org-roam-protocol` (#966)
### Breaking Changes
- [#908](https://github.com/org-roam/org-roam/pull/908) Normalized titles in database. May break external packages that rely on unnormalized titles.
### Features
- [#814](https://github.com/org-roam/org-roam/pull/814) Implement `org-roam-insert-immediate`
- [#833](https://github.com/org-roam/org-roam/pull/833) Add customization of file titles with `org-roam-title-to-slug-function`.
- [#839](https://github.com/org-roam/org-roam/pull/839) Return selected file from `org-roam-insert`
- [#847](https://github.com/org-roam/org-roam/pull/847) Add GC threshold `org-roam-db-gc-threshold` to temporarily change the threshold on expensive operations.
- [#847](https://github.com/org-roam/org-roam/pull/847) Use sqlite3 transactions instead of storing the values to be inserted.
- [#851](https://github.com/org-roam/org-roam/pull/851) Add `'first-directory'` option for `org-roam-tag-sources`
- [#863](https://github.com/org-roam/org-roam/pull/863) Display outline hierarchy in backlinks buffer
- [#898](https://github.com/org-roam/org-roam/pull/898) Add `org-roam-random-note` to browse a random note.
- [#900](https://github.com/org-roam/org-roam/pull/900) Support and index all valid org links
- [#966](https://github.com/org-roam/org-roam/pull/966) Enable nested captures
### Bugfixes
- [#854](https://github.com/org-roam/org-roam/pull/854) Warn instead of fail when duplicate refs and IDs exist.
- [#857](https://github.com/org-roam/org-roam/pull/857) Fix tag extraction for symlinked directories.
- [#894](https://github.com/org-roam/org-roam/pull/894) Perform link fixes on all Org-roam files
- [#952](https://github.com/org-roam/org-roam/pull/952) Cache `${foo}` template variables so they do not need to be re-entered twice
## 1.2.0 (12-06-2020)
In this release, we improved the linking process by achieving feature parity between links to files and links to headlines. Before, we used the `file:foo::*bar` format to link to the headline `bar` in file `foo`, but this was prone to breakage upon renaming the file or modifying the headline. This is not the case anymore. Now, we use `org-id` to create IDs for those headlines, which are then stored in our database to compute the relationships and jump around. Note that this will work even if youre not using `org-id` in your global configuration for Org-mode.
@ -32,7 +63,7 @@ We also add `org-roam-unlinked-references`, which naively finds text that could
In this release, we added two new features:
1. `org-roam-doctor`: a linting system that helps you discover possible problems with your Org-roam files. This is in the spirit of keeping notes in top quality.
2. A tagging system: one can now use sub-directories, and the `#+roam_tags` key add additional meta data to notes. For more information, see [here](https://org-roam.github.io/org-roam/manual/Tags.html#Tags).
2. A tagging system: one can now use sub-directories, and the `#+roam_tags` key add additional meta data to notes. For more information, see [here](https://www.orgroam.com/manual/Tags.html#Tags).
As usual, this release comes with a multitude of bug-fixes and refactorings.

View File

@ -50,6 +50,7 @@ Here's a sample configuration with using `use-package`:
```emacs-lisp
(use-package org-roam
:ensure t
:hook
(after-init . org-roam-mode)
:custom
@ -57,9 +58,10 @@ Here's a sample configuration with using `use-package`:
:bind (:map org-roam-mode-map
(("C-c n l" . org-roam)
("C-c n f" . org-roam-find-file)
("C-c n g" . org-roam-show-graph))
("C-c n g" . org-roam-graph-show))
:map org-mode-map
(("C-c n i" . org-roam-insert))))
(("C-c n i" . org-roam-insert))
(("C-c n I" . org-roam-insert-immediate))))
```
`org-roam-graph` by default expects to find the `dot` executable
@ -72,6 +74,25 @@ For more detailed installation and configuration instructions (including for
Doom and Spacemacs users), please see [the
documentation][docs].
## Frequently-asked Questions
Q: How do I create a note whose title already matches one of the candidates (e.g. creating `bar` when `barricade` already exists)?
A: With `ivy`, you need to press `C-M-j` to use the current input instead of the nearest candidate. (Source: [`ivy`s
FAQ](https://github.com/abo-abo/swiper#frequently-asked-questions))
## Getting Help
Before creating a new topic/issue, please be mindful of our time and ensure
that it has not already been addressed on
[GitHub][issues] or on
[Discourse][discourse].
- If you are new to Emacs and have problem setting up Org-roam, please ask your question on [Slack, channel #how-do-i][slack].
- For quick questions, please ask them on [Slack, channel #troubleshooting][slack].
- If something is not working as it should, or if you would like to suggest a new feature, please [create a new issue][issues].
- If you have questions about your workflow with the slip-box method, please find a relevant topic on [Discourse][discourse], or create a new one.
## Knowledge Bases using Org-roam
- [Jethro Kuan](https://braindump.jethro.dev/)
@ -95,6 +116,7 @@ General Public License, Version 3
[roamresearch]: https://www.roamresearch.com/
[org]: https://orgmode.org/
[badge-license]: https://img.shields.io/badge/license-GPL_3-green.svg
[docs]: https://org-roam.github.io/org-roam/manual/
[docs]: https://www.orgroam.com/manual/
[discourse]: https://org-roam.discourse.group/
[slack]: https://join.slack.com/t/orgroam/shared_invite/zt-deoqamys-043YQ~s5Tay3iJ5QRI~Lxg
[issues]: https://github.com/org-roam/org-roam/issues

572
doc/img/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -93,7 +93,7 @@
You can find us on Discourse and Slack.
<ul>
<li>Read our documentation within Emacs, or on the <a href="https://org-roam.github.io/org-roam/manual/">Online Manual</a></li>
<li>Read our documentation within Emacs, or on the <a href="https://www.orgroam.com/manual/">Online Manual</a></li>
<li>Participate in our forum discussions on <a href="https://org-roam.discourse.group">Discourse</a></li>
<li>Chat with us on <a href="https://join.slack.com/t/orgroam/shared_invite/zt-deoqamys-043YQ~s5Tay3iJ5QRI~Lxg">Slack</a></li>
</ul>

View File

@ -8,13 +8,13 @@
#+texinfo_dir_category: Emacs
#+texinfo_dir_title: Org-roam: (org-roam).
#+texinfo_dir_desc: Rudimentary Roam Replica for Emacs.
#+subtitle: for version 1.2.0
#+subtitle: for version 1.2.1
#+options: H:4 num:3 toc:2 creator:t
#+property: header-args :eval never
#+texinfo: @noindent
This manual is for Org-roam version 1.2.0.
This manual is for Org-roam version 1.2.1.
#+BEGIN_QUOTE
Copyright (C) 2020-2020 Jethro Kuan <jethrokuan95@gmail.com>
@ -152,6 +152,17 @@ using:
Now see [[*Post-Installation Tasks][Post-Installation Tasks]].
** Installing from Apt
Users of Debian 11 or later or Ubuntu 20.10 or later can simply install Org-roam
using Apt:
#+BEGIN_SRC bash
apt-get install elpa-org-roam
#+END_SRC
Org-roam will then be autoloaded into Emacs.
** TODO Installing from the Git Repository
** Post-Installation Tasks
@ -185,9 +196,11 @@ workflows. Org-roam does not magically make note-taking better -- this often
requires a radical change in your current note-taking workflow. To understand
more about the methods and madness, see [[*Note-taking Workflows][Note-taking Workflows]].
To begin using Org-roam, one should set the =org-roam-directory= to the directory
containing your notes. For this tutorial, create an empty directory, and set the
=org-roam-directory=:
To first start using Org-roam, one needs to pick a location to store the
Org-roam files. The directory that will contain your notes, and database index
is specified by the variable ~org-roam-directory~. This variable needs to be set
before any calls to Org-roam functions, including enabling ~org-roam-mode~. For
this tutorial, create an empty directory, and set ~org-roam-directory~:
#+BEGIN_SRC emacs-lisp
(make-directory "~/org-roam")
@ -196,34 +209,34 @@ containing your notes. For this tutorial, create an empty directory, and set the
We encourage using a flat hierarchy for storing notes, but some prefer using
folders for storing specific kinds of notes (e.g. websites, papers). This is
fine; Org-roam searches recursively within =org-roam-directory= for any notes.
fine; Org-roam searches recursively within ~org-roam-directory~ for any notes.
Instead of relying on the file hierarchy for any form of categorization, we
solely rely on links between files to establish connections between notes.
Next, we need to enable the global minor mode =org-roam-mode=. This sets up Emacs
Next, we need to enable the global minor mode ~org-roam-mode~. This sets up Emacs
with several hooks, builds a cache and keeps it consistent. We recommend
starting =org-roam-mode= on startup:
starting ~org-roam-mode~ on startup:
#+BEGIN_SRC emacs-lisp
(add-hook 'after-init-hook 'org-roam-mode)
#+END_SRC
To build the cache manually, one can run =M-x org-roam-db-build-cache=. The cache
is a sqlite database named =org-roam.db=, which defaults to residing in the root
=org-roam-directory=. Cache builds may take a while the first time, but is often
instantaneous in subsequent runs.
To build the cache manually, one can run ~M-x org-roam-db-build-cache~. The
cache is a sqlite database named ~org-roam.db~, which defaults to residing in
the root ~org-roam-directory~. Cache builds may take a while the first time, but
is often instantaneous in subsequent runs.
Let us now create our first note. Call =M-x org-roam-find-file=. This shows a list
of titles for notes that reside in =org-roam-directory=. It should show nothing
Let us now create our first note. Call ~M-x org-roam-find-file~. This shows a list
of titles for notes that reside in ~org-roam-directory~. It should show nothing
right now, since there are no notes in the directory. Entering the title of the
note you wish to create, and pressing =RET= should begin the note creation
process. This process uses =org-capture='s templating system, and can be freely
customized (see [[*The Templating System][The Templating System]]). Using the default template, pressing =C-c
C-c= finishes the note capture. Running =M-x org-roam-find-file= again should show
note you wish to create, and pressing ~RET~ should begin the note creation
process. This process uses ~org-capture~'s templating system, and can be freely
customized (see [[*The Templating System][The Templating System]]). Using the default template, pressing ~C-c
C-c~ finishes the note capture. Running ~M-x org-roam-find-file~ again should show
the note you have created, and selecting that entry will bring you to that note.
The crux of Org-roam is making it easy to create notes, and link them together.
To link notes together, we call =M-x org-roam-insert=. This brings up a prompt
To link notes together, we call ~M-x org-roam-insert~. This brings up a prompt
with a list of title for existing notes. Selecting an existing entry will create
and insert a link to the current file. Entering a non-existent title will create
a new note with that title. Good usage of Org-roam requires liberally linking
@ -232,7 +245,7 @@ notes.
Org-roam provides an interface to view backlinks. It shows backlinks for the
currently active Org-roam note, along with some surrounding context. To toggle
the visibility of this buffer, call =M-x org-roam=.
the visibility of this buffer, call ~M-x org-roam~.
For a visual representation of the notes and their connections, Org-roam also
provides graphing capabilities, using Graphviz. It generates graphs with notes
@ -255,13 +268,13 @@ especially useful for topics or concepts with acronyms. For example, for a note
like "World War 2", it may be desirable to also refer to it using the acronym
"WWII".
Org-roam calls =org-roam--extract-titles= to extract titles. It uses the
variable =org-roam-title-sources=, to control how the titles are extracted. The
Org-roam calls ~org-roam--extract-titles~ to extract titles. It uses the
variable ~org-roam-title-sources~, to control how the titles are extracted. The
title extraction methods supported are:
1. ='title=: This extracts the title using the file =#+title= property
2. ='headline=: This extracts the title from the first headline in the Org file
3. ='alias=: This extracts a list of titles using the =#+roam_alias= property.
1. ~'title~: This extracts the title using the file ~#+title~ property
2. ~'headline~: This extracts the title from the first headline in the Org file
3. ~'alias~: This extracts a list of titles using the ~#+roam_alias~ property.
The aliases are space-delimited, and can be multi-worded using quotes
Take for example the following org file:
@ -275,19 +288,19 @@ Take for example the following org file:
| Method | Titles |
|-------------+--------------------------|
| ='title= | '("World War 2") |
| ='headline= | '("Headline") |
| ='alias= | '("WWII" "World War II") |
| ~'title~ | '("World War 2") |
| ~'headline~ | '("Headline") |
| ~'alias~ | '("WWII" "World War II") |
One can freely control which extraction methods to use by customizing
=org-roam-title-sources=: see the doc-string for the variable for more
~org-roam-title-sources~: see the doc-string for the variable for more
information. If all methods of title extraction return no results, the file-name
is used in place of the titles for completions.
If you wish to add your own title extraction method, you may push a symbol
='foo= into =org-roam-title-sources=, and define a
=org-roam--extract-titles-foo= which accepts no arguments. See
=org-roam--extract-titles-title= for an example.
~'foo~ into ~org-roam-title-sources~, and define a
~org-roam--extract-titles-foo~ which accepts no arguments. See
~org-roam--extract-titles-title~ for an example.
** Tags
@ -295,29 +308,32 @@ Tags are used as meta-data for files: they facilitate interactions with notes
where titles are insufficient. For example, tags allow for categorization of
notes: differentiating between bibliographical and structure notes during interactive commands.
Org-roam calls =org-roam--extract-tags= to extract tags from files. It uses the
variable =org-roam-tag-sources=, to control how tags are extracted. The tag
Org-roam calls ~org-roam--extract-tags~ to extract tags from files. It uses the
variable ~org-roam-tag-sources~, to control how tags are extracted. The tag
extraction methods supported are:
1. ='prop=: This extracts tags from the =#+roam_tags= property. Tags are space delimited, and can be multi-word using double quotes.
2. ='all-directories=: All sub-directories relative to =org-roam-directory= are
1. ~'prop~: This extracts tags from the ~#+roam_tags~ property. Tags are space delimited, and can be multi-word using double quotes.
2. ~'all-directories~: All sub-directories relative to ~org-roam-directory~ are
extracted as tags. That is, if a file is located at relative path
=foo/bar/file.org=, the file will have tags =foo= and =bar=.
3. ='last-directory=: Extracts the last directory relative to
=org-roam-directory= as the tag. That is, if a file is located at relative
path =foo/bar/file.org=, the file will have tag =bar=.
~foo/bar/file.org~, the file will have tags ~foo~ and ~bar~.
3. ~'last-directory~: Extracts the last directory relative to
~org-roam-directory~ as the tag. That is, if a file is located at relative
path ~foo/bar/file.org~, the file will have tag ~bar~.
4. ~'first-directory~: Extracts the first directory relative to
~org-roam-directory~ as the tag. That is, if a file is located at relative
path ~foo/bar/file.org~, the file will have tag ~foo~.
By default, only the ='prop= extraction method is enabled. To enable the other
extraction methods, you may modify =org-roam-tag-sources=:
By default, only the ~'prop~ extraction method is enabled. To enable the other
extraction methods, you may modify ~org-roam-tag-sources~:
#+BEGIN_SRC emacs-lisp
(setq org-roam-tag-sources '(prop last-directory))
#+END_SRC
If you wish to add your own tag extraction method, you may push a symbol ='foo=
into =org-roam-tag-sources=, and define a =org-roam--extract-tags-foo= which
If you wish to add your own tag extraction method, you may push a symbol ~'foo~
into ~org-roam-tag-sources~, and define a ~org-roam--extract-tags-foo~ which
accepts the absolute file path as its argument. See
=org-roam--extract-tags-prop= for an example.
~org-roam--extract-tags-prop~ for an example.
** File Refs
@ -330,7 +346,7 @@ For example, a note for a website may contain a ref:
#+END_SRC
These keys come in useful for when taking website notes, using the
=roam-ref= protocol (see [[*Roam Protocol][Roam Protocol]]).
~roam-ref~ protocol (see [[*Roam Protocol][Roam Protocol]]).
Alternatively, add a ref for notes for a specific paper, using its
[[https://github.com/jkitchin/org-ref][org-ref]] citation key:
@ -346,8 +362,9 @@ The backlinks buffer will show any cites of this key: e.g.
[[file:images/org-ref-citelink.png]]
* The Templating System
Rather than creating blank files on =org-roam-insert= and =org-roam-find-file=, it
may be desirable to prefill the file with templated content. This may include:
Rather than creating blank files on ~org-roam-insert~ and ~org-roam-find-file~,
it may be desirable to prefill the file with templated content. This may
include:
- Time of creation
- File it was created from
@ -355,15 +372,17 @@ may be desirable to prefill the file with templated content. This may include:
- Any other data you may want to input manually
This requires a complex template insertion system. Fortunately, Org ships with a
powerful one: =org-capture=. However, org-capture was not designed for such use.
Org-roam abuses =org-capture=, extending its syntax. To first understand how
org-roam's templating system works, it may be useful to look into basic usage of
=org-capture=.
powerful one: ~org-capture~ (see info:org#capture). However, org-capture was not
designed for such use. Org-roam abuses ~org-capture~, extending its syntax and
capabilities. To first understand how org-roam's templating system works, it may
be useful to look into basic usage of ~org-capture~.
Org-roam's templates can be customized by modifying the variable
=org-roam-capture-templates=. Just like the base =org-capture= this variable can
contain multiple templates, in which case you will be prompted on which one to
use when capturing a new note.
For these reasons, Org-roam capture templates are not compatible with regular
~org-capture~. Hence, Org-roam's templates can be customized by instead
modifying the variable ~org-roam-capture-templates~. Just like
~org-capture-templates~, ~org-roam-capture-templates~ can contain multiple
templates. If ~org-roam-capture-templates~ only contains one template, there
will be no prompt for template selection.
** Template Walkthrough
@ -378,28 +397,28 @@ the default template, reproduced below.
:unnarrowed t)
#+END_SRC
1. The template has short key ="d"=. If you have only one template,
1. The template has short key ~"d"~. If you have only one template,
org-roam automatically chooses this template for you.
2. The template is given a description of ="default"=.
3. =plain= text is inserted. Other options include Org headings via
=entry=.
4. =(function org-roam--capture-get-point)= should not be changed.
5. ="%?"= is the template inserted on each call to =org-roam-capture--capture=.
2. The template is given a description of ~"default"~.
3. ~plain~ text is inserted. Other options include Org headings via
~entry~.
4. ~(function org-roam--capture-get-point)~ should not be changed.
5. ~"%?"~ is the template inserted on each call to ~org-roam-capture--capture~.
This template means don't insert any content, but place the cursor
here.
6. =:file-name= is the file-name template for a new note, if it doesn't yet
6. ~:file-name~ is the file-name template for a new note, if it doesn't yet
exist. This creates a file at path that looks like
=/path/to/org-roam-directory/20200213032037-foo.org=. This template also
~/path/to/org-roam-directory/20200213032037-foo.org~. This template also
allows you to specify if you want the note to go into a subdirectory. For
example, the template =private/${slug}= will create notes in
=/path/to/org-roam-directory/private=.
7. =:head= contains the initial template to be inserted (once only), at
example, the template ~private/${slug}~ will create notes in
~/path/to/org-roam-directory/private~.
7. ~:head~ contains the initial template to be inserted (once only), at
the beginning of the file. Here, the title global attribute is
inserted.
8. =:unnarrowed t= tells org-capture to show the contents for the whole
8. ~:unnarrowed t~ tells org-capture to show the contents for the whole
file, rather than narrowing to just the entry.
Other options you may want to learn about include =:immediate-finish=.
Other options you may want to learn about include ~:immediate-finish~.
** Org-roam Template Expansion
@ -407,26 +426,26 @@ Org-roam's template definitions also extend org-capture's template syntax, to
allow prefilling of strings. We have seen a glimpse of this in [[*Template Walkthrough][Template
Walkthrough]].
In org-roam templates, the =${var}= syntax allows for the expansion of
variables, stored in =org-roam-capture--info=. For example, during
=org-roam-insert=, the user is prompted for a title. Upon entering a
non-existent title, the =title= key in =org-roam-capture--info= is set to the
provided title. =${title}= is then expanded into the provided title during the
In org-roam templates, the ~${var}~ syntax allows for the expansion of
variables, stored in ~org-roam-capture--info~. For example, during
~org-roam-insert~, the user is prompted for a title. Upon entering a
non-existent title, the ~title~ key in ~org-roam-capture--info~ is set to the
provided title. ~${title}~ is then expanded into the provided title during the
org-capture process. Any variables that do not contain strings, are prompted for
values using =completing-read=.
values using ~completing-read~.
After doing this expansion, the org-capture's template expansion system
is used to fill up the rest of the template. You may read up more on
this on [[https://orgmode.org/manual/Template-expansion.html#Template-expansion][org-capture's documentation page]].
To illustrate this dual expansion process, take for example the template string:
="%<%Y%m%d%H%M%S>-${title}"=, with the title ="Foo"=. The template is first
expanded into =%<%Y%m%d%H%M%S>-Foo=. Then org-capture expands =%<%Y%m%d%H%M%S>=
with timestamp: e.g. =20200213032037-Foo=.
~"%<%Y%m%d%H%M%S>-${title}"~, with the title ~"Foo"~. The template is first
expanded into ~%<%Y%m%d%H%M%S>-Foo~. Then org-capture expands ~%<%Y%m%d%H%M%S>~
with timestamp: e.g. ~20200213032037-Foo~.
All of the flexibility afforded by Emacs and Org-mode are available. For
example, if you want to encode a UTC timestamp in the filename, you can take
advantage of org-mode's =%(EXP)= template expansion to call =format-time-string=
advantage of org-mode's ~%(EXP)~ template expansion to call ~format-time-string~
directly to provide its third argument to specify UTC.
#+BEGIN_SRC emacs-lisp
@ -443,7 +462,7 @@ the Org-roam codebase manageable. However, we attempt to accommodate as
many usage styles as possible.
All of Org-roam's customization options can be viewed via
=M-x customize-group org-roam=.
~M-x customize-group org-roam~.
** Directories and Files
@ -468,39 +487,39 @@ The Org-roam buffer displays backlinks for the currently active Org-roam note.
- User Option: org-roam-buffer
The name of the org-roam buffer. Defaults to =*org-roam*=.
The name of the org-roam buffer. Defaults to ~*org-roam*~.
- User Option: org-roam-buffer-position
The position of the Org-roam buffer side window. Valid values are ='left=,
='right=, ='top=, ='bottom=.
The position of the Org-roam buffer side window. Valid values are ~'left~,
~'right~, ~'top~, ~'bottom~.
- User Option: org-roam-buffer-width
Width of =org-roam-buffer=. Has an effect only if =org-roam-buffer-position= is
='left= or ='right=.
Width of ~org-roam-buffer~. Has an effect only if ~org-roam-buffer-position~ is
~'left~ or ~'right~.
- User Option: org-roam-buffer-height
Height of =org-roam-buffer=. Has an effect only if =org-roam-buffer-position= is
='top= or ='bottom=.
Height of ~org-roam-buffer~. Has an effect only if ~org-roam-buffer-position~ is
~'top~ or ~'bottom~.
- User Option: org-roam-buffer-no-delete-other-windows
The =no-delete-window= parameter for the org-roam buffer. Setting it to ='t= prevents the window from being deleted when calling =delete-other-windows=.
The ~no-delete-window~ parameter for the org-roam buffer. Setting it to ~'t~ prevents the window from being deleted when calling ~delete-other-windows~.
** Org-roam Links
Org-roam links are regular =file:= links in Org-mode. By default, links are
inserted with the title as the link description with =org-roam-insert=.
Org-roam links are regular ~file:~ links in Org-mode. By default, links are
inserted with the title as the link description with ~org-roam-insert~.
- User Option: org-roam-link-title-format
To distinguish between org-roam links and regular links, one may choose to use
special indicators for Org-roam links. Defaults to ="%s"=.
special indicators for Org-roam links. Defaults to ~"%s"~.
If your version of Org is at least =9.2=, consider styling the link differently,
by customizing the =org-roam-link=, and =org-roam-link-current= faces.
If your version of Org is at least ~9.2~, consider styling the link differently,
by customizing the ~org-roam-link~, and ~org-roam-link-current~ faces.
** Org-roam Files
@ -515,27 +534,27 @@ As your collection grows, you might want to create an index where you keep links
to your main files.
In Org-roam, you can define the path to your index file by setting
=org-roam-index-file=.
~org-roam-index-file~.
- Variable: org-roam-index-file
Path to the Org-roam index file.
The path can be a string or a function. If it is a string, it should be the
path (absolute or relative to =org-roam-directory=) to the index file. If it
path (absolute or relative to ~org-roam-directory~) to the index file. If it
is is a function, the function should return the path to the index file.
Otherwise, the index is assumed to be a note in =org-roam-index= whose
title is ="Index"=.
Otherwise, the index is assumed to be a note in ~org-roam-index~ whose
title is ~"Index"~.
- Function: org-roam-find-index
Opens the Index file in the current =org-roam-directory=.
Opens the Index file in the current ~org-roam-directory~.
* Encryption
One may wish to keep private, encrypted files. Org-roam supports encryption (via
GPG), which can be enabled for all new files by setting =org-roam-encrypt-files=
to =t=. When enabled, new files are created with the =.org.gpg= extension and
GPG), which can be enabled for all new files by setting ~org-roam-encrypt-files~
to ~t~. When enabled, new files are created with the ~.org.gpg~ extension and
decryption are handled automatically by EasyPG.
Note that Emacs will prompt for a password for encrypted files during
@ -551,7 +570,7 @@ Org-roam provides graphing capabilities to explore interconnections between
notes. This is done by performing SQL queries and generating images using
[[https://graphviz.org/][Graphviz]]. The graph can also be navigated: see [[*Roam Protocol][Roam Protocol]].
The entry point to graph creation is =org-roam-graph=.
The entry point to graph creation is ~org-roam-graph~.
- Function: org-roam-graph & optional arg file node-query
@ -559,18 +578,18 @@ The entry point to graph creation is =org-roam-graph=.
If FILE is nil, default to current buffers file name.
ARG may be any of the following values:
- =nil= show the graph.
- =C-u= show the graph for FILE.
- =C-u N= show the graph for FILE limiting nodes to N steps.
- =C-u C-u= build the graph.
- =C-u -= build the graph for FILE.
- =C-u -N= build the graph for FILE limiting nodes to N steps.
- ~nil~ show the graph.
- ~C-u~ show the graph for FILE.
- ~C-u N~ show the graph for FILE limiting nodes to N steps.
- ~C-u C-u~ build the graph.
- ~C-u -~ build the graph for FILE.
- ~C-u -N~ build the graph for FILE limiting nodes to N steps.
- User Option: org-roam-graph-executable
Path to the graphing executable (in this case, Graphviz). Set this if Org-roam is unable to find the Graphviz executable on your system.
You may also choose to use =neato= in place of =dot=, which generates a more
You may also choose to use ~neato~ in place of ~dot~, which generates a more
compact graph layout.
- User Option: org-roam-graph-viewer
@ -580,7 +599,16 @@ The entry point to graph creation is =org-roam-graph=.
1. A string, which is a path to the program used
2. a function accepting a single argument: the graph file path.
=nil= uses =view-file= to view the graph.
~nil~ uses ~view-file~ to view the graph.
If you are using WSL2 and would like to open the graph in Windows, you can use the second option to set the browser and network file path:
#+BEGIN_SRC emacs-lisp
(setq org-roam-graph-viewer
(lambda (file)
(let ((org-roam-graph-viewer "/mnt/c/Program Files/Mozilla Firefox/firefox.exe"))
(org-roam-graph--open (concat "file://///wsl$/Ubuntu" file)))))
#+END_SRC
** Graph Options
@ -589,22 +617,22 @@ Graphviz provides many options for customizing the graph output, and Org-roam su
- User Option: org-roam-graph-extra-config
Extra options passed to graphviz for the digraph (The "G" attributes).
Example: ='=(("rankdir" . "LR"))=
Example: ~'~(("rankdir" . "LR"))~
- User Option: org-roam-graph-node-extra-config
Extra options for nodes in the graphviz output (The "N" attributes).
Example: ='(("color" . "skyblue"))=
Example: ~'(("color" . "skyblue"))~
- User Option: org-roam-graph-edge-extra-config
Extra options for edges in the graphviz output (The "E" attributes).
Example: ='(("dir" . "back"))=
Example: ~'(("dir" . "back"))~
- User Option: org-roam-graph-edge-cites-extra-config
Extra options for citation edges in the graphviz output.
Example: ='(("color" . "red"))=
Example: ~'(("color" . "red"))~
** Excluding Nodes and Edges
@ -628,26 +656,30 @@ This setting excludes all files whose path contain "private" or "dailies".
* Org-roam Completion System
Org-roam offers completion when choosing note titles etc. The completion
system is configurable. The default setting,
Org-roam allows customization of which minibuffer completion system to use for
its interactive commands. The default setting uses Emacs' standard
~completing-read~ mechanism.
#+BEGIN_SRC emacs-lisp
(setq org-roam-completion-system 'default)
#+END_SRC
uses Emacs' standard =completing-read=. If you prefer
[[https://emacs-helm.github.io/helm/][Helm]], use
If you have installed Helm or Ivy, and have their modes enabled, under the
~'default~ setting they will be used.
In the rare scenario where you use Ivy globally, but prefer [[https://emacs-helm.github.io/helm/][Helm]] for org-roam
commands, set:
#+BEGIN_SRC emacs-lisp
(setq org-roam-completion-system 'helm)
#+END_SRC
Other options include ='ido=, and ='ivy=.
Other options include ~'ido~, and ~'ivy~.
* Roam Protocol
** _ :ignore:
Org-roam extending =org-protocol= with 2 protocols: the =roam-file=
and =roam-ref= protocol.
Org-roam extending ~org-protocol~ with 2 protocols: the ~roam-file~
and ~roam-ref~ protocol.
** Installation
@ -657,12 +689,12 @@ To enable Org-roam's protocol extensions, you have to add the following to your
(require 'org-roam-protocol)
#+END_SRC
The instructions for setting up =org-protocol== are reproduced below.
The instructions for setting up ~org-protocol~ are reproduced below.
We will also need to create a desktop application for =emacsclient=. The
We will also need to create a desktop application for ~emacsclient~. The
instructions for various platforms are shown below.
For Linux users, create a desktop application in =~/.local/share/applications/org-protocol.desktop=:
For Linux users, create a desktop application in ~~/.local/share/applications/org-protocol.desktop~:
#+begin_example
[Desktop Entry]
@ -674,7 +706,7 @@ Terminal=false
MimeType=x-scheme-handler/org-protocol
#+end_example
Associate =org-protocol://= links with the desktop application by
Associate ~org-protocol://~ links with the desktop application by
running in your shell:
#+BEGIN_SRC bash
@ -682,7 +714,7 @@ xdg-mime default org-protocol.desktop x-scheme-handler/org-protocol
#+END_SRC
To disable the "confirm" prompt in Chrome, you can also make Chrome
show a checkbox to tick, so that the =Org-Protocol Client= app will be used
show a checkbox to tick, so that the ~Org-Protocol Client~ app will be used
without confirmation. To do this, run in a shell:
#+BEGIN_SRC bash
@ -698,8 +730,8 @@ sudo chmod 644 /etc/opt/chrome/policies/managed/external_protocol_dialog.json
and then restart Chrome (for example, by navigating to <chrome://restart>) to
make the new policy take effect.
See [[https://www.chromium.org/administrators/linux-quick-start][here]] for more info on the =/etc/opt/chrome/policies/managed= directory and
[[https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ExternalProtocolDialogShowAlwaysOpenCheckbox][here]] for information on the =ExternalProtocolDialogShowAlwaysOpenCheckbox= policy.
See [[https://www.chromium.org/administrators/linux-quick-start][here]] for more info on the ~/etc/opt/chrome/policies/managed~ directory and
[[https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ExternalProtocolDialogShowAlwaysOpenCheckbox][here]] for information on the ~ExternalProtocolDialogShowAlwaysOpenCheckbox~ policy.
For MacOS, one solution is to use [[https://github.com/sveinbjornt/Platypus][Platypus]]. Here are the instructions for
setting up with Platypus and Chrome:
@ -710,7 +742,7 @@ setting up with Platypus and Chrome:
brew cask install platypus
#+END_SRC
2. Create a script =launch_emacs.sh=:
2. Create a script ~launch_emacs.sh~:
#+BEGIN_SRC bash
#!/usr/bin/env bash
@ -731,7 +763,7 @@ brew cask install platypus
#+end_example
Inside =Settings=:
Inside ~Settings~:
#+begin_example
| Setting | Value |
@ -742,7 +774,7 @@ Inside =Settings=:
#+end_example
To disable the "confirm" prompt in Chrome, you can also make Chrome
show a checkbox to tick, so that the =OrgProtocol= app will be used
show a checkbox to tick, so that the ~OrgProtocol~ app will be used
without confirmation. To do this, run in a shell:
#+BEGIN_SRC bash
@ -751,7 +783,7 @@ defaults write com.google.Chrome ExternalProtocolDialogShowAlwaysOpenCheckbox -b
If you're using [[https://github.com/railwaycat/homebrew-emacsmacport][Emacs Mac Port]], it registered its `Emacs.app` as the default
handler for the URL scheme `org-protocol`. To make =OrgProtocol.app=
handler for the URL scheme `org-protocol`. To make ~OrgProtocol.app~
the default handler instead, run:
#+BEGIN_SRC bash
@ -761,20 +793,41 @@ defaults write com.apple.LaunchServices/com.apple.launchservices.secure LSHandle
Then restart your computer.
For Windows, create a temporary ~org-protocol.reg~ file:
** The =roam-file= protocol
#+BEGIN_SRC text
REGEDIT4
This is a simple protocol that opens the path specified by the =file=
key (e.g. =org-protocol://roam-file?file=/tmp/file.org=). This is used
[HKEY_CLASSES_ROOT\org-protocol]
@="URL:Org Protocol"
"URL Protocol"=""
[HKEY_CLASSES_ROOT\org-protocol\shell]
[HKEY_CLASSES_ROOT\org-protocol\shell\open]
[HKEY_CLASSES_ROOT\org-protocol\shell\open\command]
@="\"C:\\Windows\\System32\\wsl.exe\" emacsclient \"%1\""
#+END_SRC
The above will forward the protocol to WSL. If you run Emacs natively on Windows, replace the last line with:
#+BEGIN_SRC text
@="\"c:\\path\\to\\emacs\\bin\\emacsclientw.exe\" \"%1\""
#+END_SRC
After executing the .reg file, the protocol is registered and you can delete the file.
** The roam-file protocol
This is a simple protocol that opens the path specified by the ~file~
key (e.g. ~org-protocol://roam-file?file=/tmp/file.org~). This is used
in the generated graph.
** The =roam-ref= Protocol
** The roam-ref protocol
This protocol finds or creates a new note with a given ~roam_key~ (see [[*Anatomy of an Org-roam File][Anatomy of an Org-roam File]]):
[[file:images/roam-ref.gif]]
To use this, create a Firefox bookmarklet as follows:
To use this, create the following [[https://en.wikipedia.org/wiki/Bookmarklet][bookmarklet]] in your browser:
#+BEGIN_SRC javascript
javascript:location.href =
@ -788,7 +841,7 @@ or as a keybinding in ~qutebrowser~ in , using the ~config.py~ file (see
[[https://github.com/qutebrowser/qutebrowser/blob/master/doc/help/configuring.asciidoc][Configuring qutebrowser]]):
#+BEGIN_SRC python
config.bind("<Ctrl-r>", "spawn bash -c 'emacsclient \"org-protocol://roam-ref?template=r&ref={url:pretty}&title={title}\" '")
config.bind("<Ctrl-r>", "open javascript:location.href='org-protocol://roam-ref?template=r&ref='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)")
#+END_SRC
where ~template~ is the template key for a template in
@ -801,15 +854,17 @@ should contain a ~#+roam_key: ${ref}~ in it.
Org-roam provides a utility for diagnosing and repairing problematic files via
~org-roam-doctor~. By default, ~org-roam-doctor~ runs the check on the current
Org-roam file. To run the check only for the current file, run =C-u M-x
org-roam-doctor=, but note that this may take some time.
Org-roam file. To run the check only for the current file, run ~C-u M-x
org-roam-doctor~, but note that this may take some time.
- Function: org-roam-doctor &optional this-buffer
Perform a check on Org-roam files to ensure cleanliness. If THIS-BUFFER, run
the check only for the current buffer.
The checks run are defined in =org-roam-doctor--checkers=. Each checker is an instance of =org-roam-doctor-checker=. To define a checker, use =make-org-roam-doctor-checker=. Here is a sample definition:
The checks run are defined in ~org-roam-doctor--checkers~. Each checker is an
instance of ~org-roam-doctor-checker~. To define a checker, use
~make-org-roam-doctor-checker~. Here is a sample definition:
#+BEGIN_SRC emacs-lisp
(make-org-roam-doctor-checker
@ -820,14 +875,30 @@ The checks run are defined in =org-roam-doctor--checkers=. Each checker is an in
("R" . ("Replace link (keep label)" . org-roam-doctor--replace-link-keep-label))))
#+END_SRC
The =:name= property is the name of the function run. The function takes in the
Org parse tree, and returns a list of =(point error-message)=. =:description= is a
short description of what the checker does. =:actions= is an alist containing
elements of the form =(char . (prompt . function))=. These actions are defined per
The ~:name~ property is the name of the function run. The function takes in the
Org parse tree, and returns a list of ~(point error-message)~. ~:description~ is a
short description of what the checker does. ~:actions~ is an alist containing
elements of the form ~(char . (prompt . function))~. These actions are defined per
checker, to perform autofixes for the errors. For each error detected,
=org-roam-doctor= will move the point to the current error, and pop-up a help
~org-roam-doctor~ will move the point to the current error, and pop-up a help
window displaying the error message, as well as the list of actions that can be
taken provided in =:actions=.
taken provided in ~:actions~.
* Performance Optimization
** TODO Profiling Key Operations
** Garbage Collection
During the cache-build process, Org-roam generates a lot of in-memory
data-structures (such as the Org file's AST), which are discarded after use. These structures are garbage collected at regular intervals (see [[info:elisp#Garbage Collection][info:elisp#Garbage Collection]]).
Org-roam provides the option ~org-roam-db-gc-threshold~ to temporarily change
the threshold value for GC to be triggered during these memory-intensive
operations. To reduce the number of garbage collection processes, one may set
~org-roam-db-gc-threshold~ to a high value (such as ~most-positive-fixnum~):
#+BEGIN_SRC emacs-lisp
(setq org-roam-db-gc-threshold most-positive-fixnum)
#+END_SRC
* _ Copying
:PROPERTIES:
:COPYING: t
@ -862,15 +933,36 @@ General Public License for more details.
- Videos ::
- [[https://www.youtube.com/watch?v=RvWic15iXjk][How to Use Roam to Outline a New Article in Under 20 Minutes]]
** Ecosystem
A number of packages work well combined with Org-Roam:
*** Deft
*** Browsing History with winner-mode
~winner-mode~ is a global minor mode that allows one to undo and redo changes in the window configuration. It is included with GNU Emacs since version 20.
~winner-mode~ can be used as a simple version of browser history for Org-roam. Each click through org-roam links (from both Org files and the backlinks buffer) causes changes in window configuration, which can be undone and redone using ~winner-mode~. To use ~winner-mode~, simply enable it, and bind the appropriate interactive functions:
#+BEGIN_SRC emacs-lisp
(winner-mode +1)
(define-key winner-mode-map (kbd "<M-left>") #'winner-undo)
(define-key winner-mode-map (kbd "<M-right>") #'winner-redo)
#+END_SRC
*** Versioning Notes
Since Org-roam notes are just plain text, it is trivial to track changes in your
notes database using version control systems such as [[https://git-scm.com/][Git]]. Simply initialize
~org-roam-directory~ as a Git repository, and commit your files at regular or
appropriate intervals. [[https://magit.vc/][Magit]] is a great interface to Git within Emacs.
In addition, it may be useful to observe how a particular note has evolved, by
looking at the file history. [[https://gitlab.com/pidu/git-timemachine][Git-timemachine]] allows you to visit historic
versions of a tracked Org-roam note.
*** Full-text search interface with Deft
:PROPERTIES:
:CUSTOM_ID: deft
:END:
[[https://jblevins.org/projects/deft/][Deft]] provides a nice interface
for browsing and filtering org-roam notes.
[[https://jblevins.org/projects/deft/][Deft]] provides a nice interface for browsing and filtering org-roam notes.
#+BEGIN_SRC emacs-lisp
(use-package deft
@ -885,7 +977,7 @@ for browsing and filtering org-roam notes.
#+END_SRC
If the title of the Org file is not the first line, you might not get
nice titles. You may choose to patch this to use =org-roam='s
nice titles. You may choose to patch this to use ~org-roam~'s
functionality. Here I'm using
[[https://github.com/raxod502/el-patch][el-patch]]:
@ -925,7 +1017,7 @@ that uses an external search engine and indexer.
:END:
[[https://github.com/bastibe/org-journal][Org-journal]] is a more
powerful alternative to the simple function =org-roam-dailies-today=. It
powerful alternative to the simple function ~org-roam-dailies-today~. It
provides better journaling capabilities, and a nice calendar interface
to see all dated entries.
@ -1004,8 +1096,8 @@ etc.) within Org-mode.
tight integration between
[[https://github.com/jkitchin/org-ref][org-ref]],
[[https://github.com/tmalsburg/helm-bibtex][helm-bibtex]] and
=org-roam=. This helps you manage your bibliographic notes under
=org-roam=.
~org-roam~. This helps you manage your bibliographic notes under
~org-roam~.
**** Spaced Repetition
:PROPERTIES:
@ -1019,11 +1111,11 @@ files. Other alternatives include [[https://orgmode.org/worg/org-contrib/org-dri
** How do I have more than one Org-roam directory?
Emacs supports directory-local variables, allowing the value of
=org-roam-directory= to be different in different directories. It does this by
checking for a file named =.dir-locals.el=.
~org-roam-directory~ to be different in different directories. It does this by
checking for a file named ~.dir-locals.el~.
To add support for multiple directories, override the =org-roam-directory=
variable using directory-local variables. This is what =.dir-locals.el= may
To add support for multiple directories, override the ~org-roam-directory~
variable using directory-local variables. This is what ~.dir-locals.el~ may
contain:
#+BEGIN_SRC emacs-lisp
@ -1031,7 +1123,7 @@ contain:
#+END_SRC
All files within that directory will be treated as their own separate
set of Org-roam files. Remember to run =org-roam-db-build-cache= from a
set of Org-roam files. Remember to run ~org-roam-db-build-cache~ from a
file within that directory, at least once.
** How do I migrate from Roam Research?

View File

@ -31,7 +31,7 @@ General Public License for more details.
@finalout
@titlepage
@title Org-roam User Manual
@subtitle for version 1.2.0
@subtitle for version 1.2.1
@author Jethro Kuan
@page
@vskip 0pt plus 1filll
@ -46,7 +46,7 @@ General Public License for more details.
@noindent
This manual is for Org-roam version 1.2.0.
This manual is for Org-roam version 1.2.1.
@quotation
Copyright (C) 2020-2020 Jethro Kuan <jethrokuan95@@gmail.com>
@ -78,6 +78,7 @@ General Public License for more details.
* Roam Protocol::
* Daily Notes::
* Diagnosing and Repairing Files::
* Performance Optimization::
* Appendix::
* FAQ::
@ -87,6 +88,7 @@ General Public License for more details.
Installation
* Installing from MELPA::
* Installing from Apt::
* Installing from the Git Repository::
* Post-Installation Tasks::
@ -121,8 +123,13 @@ Roam Protocol
* _::
* Installation: Installation (1).
* The @samp{roam-file} protocol::
* The @samp{roam-ref} Protocol::
* The roam-file protocol::
* The roam-ref protocol::
Performance Optimization
* Profiling Key Operations::
* Garbage Collection::
Appendix
@ -131,7 +138,9 @@ Appendix
Ecosystem
* Deft::
* Browsing History with winner-mode::
* Versioning Notes::
* Full-text search interface with Deft::
* Org-journal::
* Note-taking Add-ons::
@ -231,6 +240,7 @@ development repository.
@menu
* Installing from MELPA::
* Installing from Apt::
* Installing from the Git Repository::
* Post-Installation Tasks::
@end menu
@ -288,6 +298,18 @@ M-x package-install RET org-roam RET
Now see @ref{Post-Installation Tasks}.
@node Installing from Apt
@section Installing from Apt
Users of Debian 11 or later or Ubuntu 20.10 or later can simply install Org-roam
using Apt:
@example
apt-get install elpa-org-roam
@end example
Org-roam will then be autoloaded into Emacs.
@node Installing from the Git Repository
@section @strong{TODO} Installing from the Git Repository
@ -324,9 +346,11 @@ workflows. Org-roam does not magically make note-taking better -- this often
requires a radical change in your current note-taking workflow. To understand
more about the methods and madness, see @ref{Note-taking Workflows}.
To begin using Org-roam, one should set the @samp{org-roam-directory} to the directory
containing your notes. For this tutorial, create an empty directory, and set the
@samp{org-roam-directory}:
To first start using Org-roam, one needs to pick a location to store the
Org-roam files. The directory that will contain your notes, and database index
is specified by the variable @code{org-roam-directory}. This variable needs to be set
before any calls to Org-roam functions, including enabling @code{org-roam-mode}. For
this tutorial, create an empty directory, and set @code{org-roam-directory}:
@lisp
(make-directory "~/org-roam")
@ -335,34 +359,34 @@ containing your notes. For this tutorial, create an empty directory, and set the
We encourage using a flat hierarchy for storing notes, but some prefer using
folders for storing specific kinds of notes (e.g. websites, papers). This is
fine; Org-roam searches recursively within @samp{org-roam-directory} for any notes.
fine; Org-roam searches recursively within @code{org-roam-directory} for any notes.
Instead of relying on the file hierarchy for any form of categorization, we
solely rely on links between files to establish connections between notes.
Next, we need to enable the global minor mode @samp{org-roam-mode}. This sets up Emacs
Next, we need to enable the global minor mode @code{org-roam-mode}. This sets up Emacs
with several hooks, builds a cache and keeps it consistent. We recommend
starting @samp{org-roam-mode} on startup:
starting @code{org-roam-mode} on startup:
@lisp
(add-hook 'after-init-hook 'org-roam-mode)
@end lisp
To build the cache manually, one can run @samp{M-x org-roam-db-build-cache}. The cache
is a sqlite database named @samp{org-roam.db}, which defaults to residing in the root
@samp{org-roam-directory}. Cache builds may take a while the first time, but is often
instantaneous in subsequent runs.
To build the cache manually, one can run @code{M-x org-roam-db-build-cache}. The
cache is a sqlite database named @code{org-roam.db}, which defaults to residing in
the root @code{org-roam-directory}. Cache builds may take a while the first time, but
is often instantaneous in subsequent runs.
Let us now create our first note. Call @samp{M-x org-roam-find-file}. This shows a list
of titles for notes that reside in @samp{org-roam-directory}. It should show nothing
Let us now create our first note. Call @code{M-x org-roam-find-file}. This shows a list
of titles for notes that reside in @code{org-roam-directory}. It should show nothing
right now, since there are no notes in the directory. Entering the title of the
note you wish to create, and pressing @samp{RET} should begin the note creation
process. This process uses @samp{org-capture}'s templating system, and can be freely
customized (see @ref{The Templating System}). Using the default template, pressing @samp{C-c
C-c} finishes the note capture. Running @samp{M-x org-roam-find-file} again should show
note you wish to create, and pressing @code{RET} should begin the note creation
process. This process uses @code{org-capture}'s templating system, and can be freely
customized (see @ref{The Templating System}). Using the default template, pressing @code{C-c
C-c} finishes the note capture. Running @code{M-x org-roam-find-file} again should show
the note you have created, and selecting that entry will bring you to that note.
The crux of Org-roam is making it easy to create notes, and link them together.
To link notes together, we call @samp{M-x org-roam-insert}. This brings up a prompt
To link notes together, we call @code{M-x org-roam-insert}. This brings up a prompt
with a list of title for existing notes. Selecting an existing entry will create
and insert a link to the current file. Entering a non-existent title will create
a new note with that title. Good usage of Org-roam requires liberally linking
@ -371,7 +395,7 @@ notes.
Org-roam provides an interface to view backlinks. It shows backlinks for the
currently active Org-roam note, along with some surrounding context. To toggle
the visibility of this buffer, call @samp{M-x org-roam}.
the visibility of this buffer, call @code{M-x org-roam}.
For a visual representation of the notes and their connections, Org-roam also
provides graphing capabilities, using Graphviz. It generates graphs with notes
@ -402,17 +426,17 @@ especially useful for topics or concepts with acronyms. For example, for a note
like ``World War 2'', it may be desirable to also refer to it using the acronym
``WWII''.
Org-roam calls @samp{org-roam--extract-titles} to extract titles. It uses the
variable @samp{org-roam-title-sources}, to control how the titles are extracted. The
Org-roam calls @code{org-roam--extract-titles} to extract titles. It uses the
variable @code{org-roam-title-sources}, to control how the titles are extracted. The
title extraction methods supported are:
@enumerate
@item
@samp{'title}: This extracts the title using the file @samp{#+title} property
@code{'title}: This extracts the title using the file @code{#+title} property
@item
@samp{'headline}: This extracts the title from the first headline in the Org file
@code{'headline}: This extracts the title from the first headline in the Org file
@item
@samp{'alias}: This extracts a list of titles using the @samp{#+roam_alias} property.
@code{'alias}: This extracts a list of titles using the @code{#+roam_alias} property.
The aliases are space-delimited, and can be multi-worded using quotes
@end enumerate
@ -428,23 +452,23 @@ Take for example the following org file:
@multitable {aaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaa}
@headitem Method
@tab Titles
@item @samp{'title}
@item @code{'title}
@tab '(``World War 2'')
@item @samp{'headline}
@item @code{'headline}
@tab '(``Headline'')
@item @samp{'alias}
@item @code{'alias}
@tab '(``WWII'' ``World War II'')
@end multitable
One can freely control which extraction methods to use by customizing
@samp{org-roam-title-sources}: see the doc-string for the variable for more
@code{org-roam-title-sources}: see the doc-string for the variable for more
information. If all methods of title extraction return no results, the file-name
is used in place of the titles for completions.
If you wish to add your own title extraction method, you may push a symbol
@samp{'foo} into @samp{org-roam-title-sources}, and define a
@samp{org-roam--extract-titles-foo} which accepts no arguments. See
@samp{org-roam--extract-titles-title} for an example.
@code{'foo} into @code{org-roam-title-sources}, and define a
@code{org-roam--extract-titles-foo} which accepts no arguments. See
@code{org-roam--extract-titles-title} for an example.
@node Tags
@section Tags
@ -453,34 +477,38 @@ Tags are used as meta-data for files: they facilitate interactions with notes
where titles are insufficient. For example, tags allow for categorization of
notes: differentiating between bibliographical and structure notes during interactive commands.
Org-roam calls @samp{org-roam--extract-tags} to extract tags from files. It uses the
variable @samp{org-roam-tag-sources}, to control how tags are extracted. The tag
Org-roam calls @code{org-roam--extract-tags} to extract tags from files. It uses the
variable @code{org-roam-tag-sources}, to control how tags are extracted. The tag
extraction methods supported are:
@enumerate
@item
@samp{'prop}: This extracts tags from the @samp{#+roam_tags} property. Tags are space delimited, and can be multi-word using double quotes.
@code{'prop}: This extracts tags from the @code{#+roam_tags} property. Tags are space delimited, and can be multi-word using double quotes.
@item
@samp{'all-directories}: All sub-directories relative to @samp{org-roam-directory} are
@code{'all-directories}: All sub-directories relative to @code{org-roam-directory} are
extracted as tags. That is, if a file is located at relative path
@samp{foo/bar/file.org}, the file will have tags @samp{foo} and @samp{bar}.
@code{foo/bar/file.org}, the file will have tags @code{foo} and @code{bar}.
@item
@samp{'last-directory}: Extracts the last directory relative to
@samp{org-roam-directory} as the tag. That is, if a file is located at relative
path @samp{foo/bar/file.org}, the file will have tag @samp{bar}.
@code{'last-directory}: Extracts the last directory relative to
@code{org-roam-directory} as the tag. That is, if a file is located at relative
path @code{foo/bar/file.org}, the file will have tag @code{bar}.
@item
@code{'first-directory}: Extracts the first directory relative to
@code{org-roam-directory} as the tag. That is, if a file is located at relative
path @code{foo/bar/file.org}, the file will have tag @code{foo}.
@end enumerate
By default, only the @samp{'prop} extraction method is enabled. To enable the other
extraction methods, you may modify @samp{org-roam-tag-sources}:
By default, only the @code{'prop} extraction method is enabled. To enable the other
extraction methods, you may modify @code{org-roam-tag-sources}:
@lisp
(setq org-roam-tag-sources '(prop last-directory))
@end lisp
If you wish to add your own tag extraction method, you may push a symbol @samp{'foo}
into @samp{org-roam-tag-sources}, and define a @samp{org-roam--extract-tags-foo} which
If you wish to add your own tag extraction method, you may push a symbol @code{'foo}
into @code{org-roam-tag-sources}, and define a @code{org-roam--extract-tags-foo} which
accepts the absolute file path as its argument. See
@samp{org-roam--extract-tags-prop} for an example.
@code{org-roam--extract-tags-prop} for an example.
@node File Refs
@section File Refs
@ -494,7 +522,7 @@ For example, a note for a website may contain a ref:
@end example
These keys come in useful for when taking website notes, using the
@samp{roam-ref} protocol (see @ref{Roam Protocol}).
@code{roam-ref} protocol (see @ref{Roam Protocol}).
Alternatively, add a ref for notes for a specific paper, using its
@uref{https://github.com/jkitchin/org-ref, org-ref} citation key:
@ -514,8 +542,9 @@ The backlinks buffer will show any cites of this key: e.g.
@node The Templating System
@chapter The Templating System
Rather than creating blank files on @samp{org-roam-insert} and @samp{org-roam-find-file}, it
may be desirable to prefill the file with templated content. This may include:
Rather than creating blank files on @code{org-roam-insert} and @code{org-roam-find-file},
it may be desirable to prefill the file with templated content. This may
include:
@itemize
@item
@ -529,15 +558,17 @@ Any other data you may want to input manually
@end itemize
This requires a complex template insertion system. Fortunately, Org ships with a
powerful one: @samp{org-capture}. However, org-capture was not designed for such use.
Org-roam abuses @samp{org-capture}, extending its syntax. To first understand how
org-roam's templating system works, it may be useful to look into basic usage of
@samp{org-capture}.
powerful one: @code{org-capture} (see @ref{capture,,,org,}). However, org-capture was not
designed for such use. Org-roam abuses @code{org-capture}, extending its syntax and
capabilities. To first understand how org-roam's templating system works, it may
be useful to look into basic usage of @code{org-capture}.
Org-roam's templates can be customized by modifying the variable
@samp{org-roam-capture-templates}. Just like the base @samp{org-capture} this variable can
contain multiple templates, in which case you will be prompted on which one to
use when capturing a new note.
For these reasons, Org-roam capture templates are not compatible with regular
@code{org-capture}. Hence, Org-roam's templates can be customized by instead
modifying the variable @code{org-roam-capture-templates}. Just like
@code{org-capture-templates}, @code{org-roam-capture-templates} can contain multiple
templates. If @code{org-roam-capture-templates} only contains one template, there
will be no prompt for template selection.
@menu
* Template Walkthrough::
@ -560,36 +591,36 @@ the default template, reproduced below.
@enumerate
@item
The template has short key @samp{"d"}. If you have only one template,
The template has short key @code{"d"}. If you have only one template,
org-roam automatically chooses this template for you.
@item
The template is given a description of @samp{"default"}.
The template is given a description of @code{"default"}.
@item
@samp{plain} text is inserted. Other options include Org headings via
@samp{entry}.
@code{plain} text is inserted. Other options include Org headings via
@code{entry}.
@item
@samp{(function org-roam--capture-get-point)} should not be changed.
@code{(function org-roam--capture-get-point)} should not be changed.
@item
@samp{"%?"} is the template inserted on each call to @samp{org-roam-capture--capture}.
@code{"%?"} is the template inserted on each call to @code{org-roam-capture--capture}.
This template means don't insert any content, but place the cursor
here.
@item
@samp{:file-name} is the file-name template for a new note, if it doesn't yet
@code{:file-name} is the file-name template for a new note, if it doesn't yet
exist. This creates a file at path that looks like
@samp{/path/to/org-roam-directory/20200213032037-foo.org}. This template also
@code{/path/to/org-roam-directory/20200213032037-foo.org}. This template also
allows you to specify if you want the note to go into a subdirectory. For
example, the template @samp{private/$@{slug@}} will create notes in
@samp{/path/to/org-roam-directory/private}.
example, the template @code{private/$@{slug@}} will create notes in
@code{/path/to/org-roam-directory/private}.
@item
@samp{:head} contains the initial template to be inserted (once only), at
@code{:head} contains the initial template to be inserted (once only), at
the beginning of the file. Here, the title global attribute is
inserted.
@item
@samp{:unnarrowed t} tells org-capture to show the contents for the whole
@code{:unnarrowed t} tells org-capture to show the contents for the whole
file, rather than narrowing to just the entry.
@end enumerate
Other options you may want to learn about include @samp{:immediate-finish}.
Other options you may want to learn about include @code{:immediate-finish}.
@node Org-roam Template Expansion
@section Org-roam Template Expansion
@ -598,26 +629,26 @@ Org-roam's template definitions also extend org-capture's template syntax, to
allow prefilling of strings. We have seen a glimpse of this in @ref{Template Walkthrough, , Template
Walkthrough}.
In org-roam templates, the @samp{$@{var@}} syntax allows for the expansion of
variables, stored in @samp{org-roam-capture--info}. For example, during
@samp{org-roam-insert}, the user is prompted for a title. Upon entering a
non-existent title, the @samp{title} key in @samp{org-roam-capture--info} is set to the
provided title. @samp{$@{title@}} is then expanded into the provided title during the
In org-roam templates, the @code{$@{var@}} syntax allows for the expansion of
variables, stored in @code{org-roam-capture--info}. For example, during
@code{org-roam-insert}, the user is prompted for a title. Upon entering a
non-existent title, the @code{title} key in @code{org-roam-capture--info} is set to the
provided title. @code{$@{title@}} is then expanded into the provided title during the
org-capture process. Any variables that do not contain strings, are prompted for
values using @samp{completing-read}.
values using @code{completing-read}.
After doing this expansion, the org-capture's template expansion system
is used to fill up the rest of the template. You may read up more on
this on @uref{https://orgmode.org/manual/Template-expansion.html#Template-expansion, org-capture's documentation page}.
To illustrate this dual expansion process, take for example the template string:
@samp{"%<%Y%m%d%H%M%S>-$@{title@}"}, with the title @samp{"Foo"}. The template is first
expanded into @samp{%<%Y%m%d%H%M%S>-Foo}. Then org-capture expands @samp{%<%Y%m%d%H%M%S>}
with timestamp: e.g. @samp{20200213032037-Foo}.
@code{"%<%Y%m%d%H%M%S>-$@{title@}"}, with the title @code{"Foo"}. The template is first
expanded into @code{%<%Y%m%d%H%M%S>-Foo}. Then org-capture expands @code{%<%Y%m%d%H%M%S>}
with timestamp: e.g. @code{20200213032037-Foo}.
All of the flexibility afforded by Emacs and Org-mode are available. For
example, if you want to encode a UTC timestamp in the filename, you can take
advantage of org-mode's @samp{%(EXP)} template expansion to call @samp{format-time-string}
advantage of org-mode's @code{%(EXP)} template expansion to call @code{format-time-string}
directly to provide its third argument to specify UTC@.
@lisp
@ -636,7 +667,7 @@ the Org-roam codebase manageable. However, we attempt to accommodate as
many usage styles as possible.
All of Org-roam's customization options can be viewed via
@samp{M-x customize-group org-roam}.
@code{M-x customize-group org-roam}.
@menu
* Directories and Files::
@ -676,47 +707,47 @@ The Org-roam buffer displays backlinks for the currently active Org-roam note.
@item
User Option: org-roam-buffer
The name of the org-roam buffer. Defaults to @samp{*org-roam*}.
The name of the org-roam buffer. Defaults to @code{*org-roam*}.
@item
User Option: org-roam-buffer-position
The position of the Org-roam buffer side window. Valid values are @samp{'left},
@samp{'right}, @samp{'top}, @samp{'bottom}.
The position of the Org-roam buffer side window. Valid values are @code{'left},
@code{'right}, @code{'top}, @code{'bottom}.
@item
User Option: org-roam-buffer-width
Width of @samp{org-roam-buffer}. Has an effect only if @samp{org-roam-buffer-position} is
@samp{'left} or @samp{'right}.
Width of @code{org-roam-buffer}. Has an effect only if @code{org-roam-buffer-position} is
@code{'left} or @code{'right}.
@item
User Option: org-roam-buffer-height
Height of @samp{org-roam-buffer}. Has an effect only if @samp{org-roam-buffer-position} is
@samp{'top} or @samp{'bottom}.
Height of @code{org-roam-buffer}. Has an effect only if @code{org-roam-buffer-position} is
@code{'top} or @code{'bottom}.
@item
User Option: org-roam-buffer-no-delete-other-windows
The @samp{no-delete-window} parameter for the org-roam buffer. Setting it to @samp{'t} prevents the window from being deleted when calling @samp{delete-other-windows}.
The @code{no-delete-window} parameter for the org-roam buffer. Setting it to @code{'t} prevents the window from being deleted when calling @code{delete-other-windows}.
@end itemize
@node Org-roam Links
@section Org-roam Links
Org-roam links are regular @samp{file:} links in Org-mode. By default, links are
inserted with the title as the link description with @samp{org-roam-insert}.
Org-roam links are regular @code{file:} links in Org-mode. By default, links are
inserted with the title as the link description with @code{org-roam-insert}.
@itemize
@item
User Option: org-roam-link-title-format
To distinguish between org-roam links and regular links, one may choose to use
special indicators for Org-roam links. Defaults to @samp{"%s"}.
special indicators for Org-roam links. Defaults to @code{"%s"}.
If your version of Org is at least @samp{9.2}, consider styling the link differently,
by customizing the @samp{org-roam-link}, and @samp{org-roam-link-current} faces.
If your version of Org is at least @code{9.2}, consider styling the link differently,
by customizing the @code{org-roam-link}, and @code{org-roam-link-current} faces.
@end itemize
@node Org-roam Files
@ -739,7 +770,7 @@ As your collection grows, you might want to create an index where you keep links
to your main files.
In Org-roam, you can define the path to your index file by setting
@samp{org-roam-index-file}.
@code{org-roam-index-file}.
@itemize
@item
@ -748,23 +779,23 @@ Variable: org-roam-index-file
Path to the Org-roam index file.
The path can be a string or a function. If it is a string, it should be the
path (absolute or relative to @samp{org-roam-directory}) to the index file. If it
path (absolute or relative to @code{org-roam-directory}) to the index file. If it
is is a function, the function should return the path to the index file.
Otherwise, the index is assumed to be a note in @samp{org-roam-index} whose
title is @samp{"Index"}.
Otherwise, the index is assumed to be a note in @code{org-roam-index} whose
title is @code{"Index"}.
@item
Function: org-roam-find-index
Opens the Index file in the current @samp{org-roam-directory}.
Opens the Index file in the current @code{org-roam-directory}.
@end itemize
@node Encryption
@chapter Encryption
One may wish to keep private, encrypted files. Org-roam supports encryption (via
GPG), which can be enabled for all new files by setting @samp{org-roam-encrypt-files}
to @samp{t}. When enabled, new files are created with the @samp{.org.gpg} extension and
GPG), which can be enabled for all new files by setting @code{org-roam-encrypt-files}
to @code{t}. When enabled, new files are created with the @code{.org.gpg} extension and
decryption are handled automatically by EasyPG@.
Note that Emacs will prompt for a password for encrypted files during
@ -785,7 +816,7 @@ Org-roam provides graphing capabilities to explore interconnections between
notes. This is done by performing SQL queries and generating images using
@uref{https://graphviz.org/, Graphviz}. The graph can also be navigated: see @ref{Roam Protocol}.
The entry point to graph creation is @samp{org-roam-graph}.
The entry point to graph creation is @code{org-roam-graph}.
@itemize
@item
@ -797,17 +828,17 @@ ARG may be any of the following values:
@itemize
@item
@samp{nil} show the graph.
@code{nil} show the graph.
@item
@samp{C-u} show the graph for FILE@.
@code{C-u} show the graph for FILE@.
@item
@samp{C-u N} show the graph for FILE limiting nodes to N steps.
@code{C-u N} show the graph for FILE limiting nodes to N steps.
@item
@samp{C-u C-u} build the graph.
@code{C-u C-u} build the graph.
@item
@samp{C-u -} build the graph for FILE@.
@code{C-u -} build the graph for FILE@.
@item
@samp{C-u -N} build the graph for FILE limiting nodes to N steps.
@code{C-u -N} build the graph for FILE limiting nodes to N steps.
@end itemize
@item
@ -815,7 +846,7 @@ User Option: org-roam-graph-executable
Path to the graphing executable (in this case, Graphviz). Set this if Org-roam is unable to find the Graphviz executable on your system.
You may also choose to use @samp{neato} in place of @samp{dot}, which generates a more
You may also choose to use @code{neato} in place of @code{dot}, which generates a more
compact graph layout.
@item
@ -830,7 +861,16 @@ A string, which is a path to the program used
a function accepting a single argument: the graph file path.
@end enumerate
@samp{nil} uses @samp{view-file} to view the graph.
@code{nil} uses @code{view-file} to view the graph.
If you are using WSL2 and would like to open the graph in Windows, you can use the second option to set the browser and network file path:
@lisp
(setq org-roam-graph-viewer
(lambda (file)
(let ((org-roam-graph-viewer "/mnt/c/Program Files/Mozilla Firefox/firefox.exe"))
(org-roam-graph--open (concat "file://///wsl$/Ubuntu" file)))))
@end lisp
@end itemize
@menu
@ -848,25 +888,25 @@ Graphviz provides many options for customizing the graph output, and Org-roam su
User Option: org-roam-graph-extra-config
Extra options passed to graphviz for the digraph (The ``G'' attributes).
Example: @samp{'=(("rankdir" . "LR"))}
Example: @code{'~(("rankdir" . "LR"))}
@item
User Option: org-roam-graph-node-extra-config
Extra options for nodes in the graphviz output (The ``N'' attributes).
Example: @samp{'(("color" . "skyblue"))}
Example: @code{'(("color" . "skyblue"))}
@item
User Option: org-roam-graph-edge-extra-config
Extra options for edges in the graphviz output (The ``E'' attributes).
Example: @samp{'(("dir" . "back"))}
Example: @code{'(("dir" . "back"))}
@item
User Option: org-roam-graph-edge-cites-extra-config
Extra options for citation edges in the graphviz output.
Example: @samp{'(("color" . "red"))}
Example: @code{'(("color" . "red"))}
@end itemize
@node Excluding Nodes and Edges
@ -896,21 +936,25 @@ This setting excludes all files whose path contain ``private'' or ``dailies''.
@node Org-roam Completion System
@chapter Org-roam Completion System
Org-roam offers completion when choosing note titles etc. The completion
system is configurable. The default setting,
Org-roam allows customization of which minibuffer completion system to use for
its interactive commands. The default setting uses Emacs' standard
@code{completing-read} mechanism.
@lisp
(setq org-roam-completion-system 'default)
@end lisp
uses Emacs' standard @samp{completing-read}. If you prefer
@uref{https://emacs-helm.github.io/helm/, Helm}, use
If you have installed Helm or Ivy, and have their modes enabled, under the
@code{'default} setting they will be used.
In the rare scenario where you use Ivy globally, but prefer @uref{https://emacs-helm.github.io/helm/, Helm} for org-roam
commands, set:
@lisp
(setq org-roam-completion-system 'helm)
@end lisp
Other options include @samp{'ido}, and @samp{'ivy}.
Other options include @code{'ido}, and @code{'ivy}.
@node Roam Protocol
@chapter Roam Protocol
@ -918,15 +962,15 @@ Other options include @samp{'ido}, and @samp{'ivy}.
@menu
* _::
* Installation: Installation (1).
* The @samp{roam-file} protocol::
* The @samp{roam-ref} Protocol::
* The roam-file protocol::
* The roam-ref protocol::
@end menu
@node _
@section _ :ignore:
Org-roam extending @samp{org-protocol} with 2 protocols: the @samp{roam-file}
and @samp{roam-ref} protocol.
Org-roam extending @code{org-protocol} with 2 protocols: the @code{roam-file}
and @code{roam-ref} protocol.
@node Installation (1)
@section Installation
@ -937,12 +981,12 @@ To enable Org-roam's protocol extensions, you have to add the following to your
(require 'org-roam-protocol)
@end lisp
The instructions for setting up @samp{org-protocol=} are reproduced below.
The instructions for setting up @code{org-protocol} are reproduced below.
We will also need to create a desktop application for @samp{emacsclient}. The
We will also need to create a desktop application for @code{emacsclient}. The
instructions for various platforms are shown below.
For Linux users, create a desktop application in @samp{~/.local/share/applications/org-protocol.desktop}:
For Linux users, create a desktop application in @code{~/.local/share/applications/org-protocol.desktop}:
@example
[Desktop Entry]
@ -954,7 +998,7 @@ Terminal=false
MimeType=x-scheme-handler/org-protocol
@end example
Associate @samp{org-protocol://} links with the desktop application by
Associate @code{org-protocol://} links with the desktop application by
running in your shell:
@example
@ -962,7 +1006,7 @@ xdg-mime default org-protocol.desktop x-scheme-handler/org-protocol
@end example
To disable the ``confirm'' prompt in Chrome, you can also make Chrome
show a checkbox to tick, so that the @samp{Org-Protocol Client} app will be used
show a checkbox to tick, so that the @code{Org-Protocol Client} app will be used
without confirmation. To do this, run in a shell:
@example
@ -978,8 +1022,8 @@ sudo chmod 644 /etc/opt/chrome/policies/managed/external_protocol_dialog.json
and then restart Chrome (for example, by navigating to <chrome://restart>) to
make the new policy take effect.
See @uref{https://www.chromium.org/administrators/linux-quick-start, here} for more info on the @samp{/etc/opt/chrome/policies/managed} directory and
@uref{https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ExternalProtocolDialogShowAlwaysOpenCheckbox, here} for information on the @samp{ExternalProtocolDialogShowAlwaysOpenCheckbox} policy.
See @uref{https://www.chromium.org/administrators/linux-quick-start, here} for more info on the @code{/etc/opt/chrome/policies/managed} directory and
@uref{https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ExternalProtocolDialogShowAlwaysOpenCheckbox, here} for information on the @code{ExternalProtocolDialogShowAlwaysOpenCheckbox} policy.
For MacOS, one solution is to use @uref{https://github.com/sveinbjornt/Platypus, Platypus}. Here are the instructions for
setting up with Platypus and Chrome:
@ -995,7 +1039,7 @@ brew cask install platypus
@enumerate
@item
Create a script @samp{launch_emacs.sh}:
Create a script @code{launch_emacs.sh}:
@end enumerate
@example
@ -1020,7 +1064,7 @@ Create a Platypus app with the following settings:
@end example
Inside @samp{Settings}:
Inside @code{Settings}:
@example
| Setting | Value |
@ -1031,7 +1075,7 @@ Inside @samp{Settings}:
@end example
To disable the ``confirm'' prompt in Chrome, you can also make Chrome
show a checkbox to tick, so that the @samp{OrgProtocol} app will be used
show a checkbox to tick, so that the @code{OrgProtocol} app will be used
without confirmation. To do this, run in a shell:
@example
@ -1040,7 +1084,7 @@ defaults write com.google.Chrome ExternalProtocolDialogShowAlwaysOpenCheckbox -b
If you're using @uref{https://github.com/railwaycat/homebrew-emacsmacport, Emacs Mac Port}, it registered its `Emacs.app` as the default
handler for the URL scheme `org-protocol`. To make @samp{OrgProtocol.app}
handler for the URL scheme `org-protocol`. To make @code{OrgProtocol.app}
the default handler instead, run:
@example
@ -1050,21 +1094,43 @@ defaults write com.apple.LaunchServices/com.apple.launchservices.secure LSHandle
Then restart your computer.
@node The @samp{roam-file} protocol
@section The @samp{roam-file} protocol
For Windows, create a temporary @code{org-protocol.reg} file:
This is a simple protocol that opens the path specified by the @samp{file}
key (e.g. @samp{org-protocol://roam-file?file=/tmp/file.org}). This is used
@example
REGEDIT4
[HKEY_CLASSES_ROOT\org-protocol]
@@="URL:Org Protocol"
"URL Protocol"=""
[HKEY_CLASSES_ROOT\org-protocol\shell]
[HKEY_CLASSES_ROOT\org-protocol\shell\open]
[HKEY_CLASSES_ROOT\org-protocol\shell\open\command]
@@="\"C:\\Windows\\System32\\wsl.exe\" emacsclient \"%1\""
@end example
The above will forward the protocol to WSL@. If you run Emacs natively on Windows, replace the last line with:
@example
@@="\"c:\\path\\to\\emacs\\bin\\emacsclientw.exe\" \"%1\""
@end example
After executing the .reg file, the protocol is registered and you can delete the file.
@node The roam-file protocol
@section The roam-file protocol
This is a simple protocol that opens the path specified by the @code{file}
key (e.g. @code{org-protocol://roam-file?file=/tmp/file.org}). This is used
in the generated graph.
@node The @samp{roam-ref} Protocol
@section The @samp{roam-ref} Protocol
@node The roam-ref protocol
@section The roam-ref protocol
This protocol finds or creates a new note with a given @code{roam_key} (see @ref{Anatomy of an Org-roam File}):
@image{images/roam-ref,,,,gif}
To use this, create a Firefox bookmarklet as follows:
To use this, create the following @uref{https://en.wikipedia.org/wiki/Bookmarklet, bookmarklet} in your browser:
@example
javascript:location.href =
@ -1078,7 +1144,7 @@ or as a keybinding in @code{qutebrowser} in , using the @code{config.py} file (s
@uref{https://github.com/qutebrowser/qutebrowser/blob/master/doc/help/configuring.asciidoc, Configuring qutebrowser}):
@example
config.bind("<Ctrl-r>", "spawn bash -c 'emacsclient \"org-protocol://roam-ref?template=r&ref=@{url:pretty@}&title=@{title@}\" '")
config.bind("<Ctrl-r>", "open javascript:location.href='org-protocol://roam-ref?template=r&ref='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)")
@end example
where @code{template} is the template key for a template in
@ -1093,7 +1159,7 @@ should contain a @code{#+roam_key: $@{ref@}} in it.
Org-roam provides a utility for diagnosing and repairing problematic files via
@code{org-roam-doctor}. By default, @code{org-roam-doctor} runs the check on the current
Org-roam file. To run the check only for the current file, run @samp{C-u M-x
Org-roam file. To run the check only for the current file, run @code{C-u M-x
org-roam-doctor}, but note that this may take some time.
@itemize
@ -1104,7 +1170,9 @@ Perform a check on Org-roam files to ensure cleanliness. If THIS-BUFFER, run
the check only for the current buffer.
@end itemize
The checks run are defined in @samp{org-roam-doctor--checkers}. Each checker is an instance of @samp{org-roam-doctor-checker}. To define a checker, use @samp{make-org-roam-doctor-checker}. Here is a sample definition:
The checks run are defined in @code{org-roam-doctor--checkers}. Each checker is an
instance of @code{org-roam-doctor-checker}. To define a checker, use
@code{make-org-roam-doctor-checker}. Here is a sample definition:
@lisp
(make-org-roam-doctor-checker
@ -1115,14 +1183,40 @@ The checks run are defined in @samp{org-roam-doctor--checkers}. Each checker is
("R" . ("Replace link (keep label)" . org-roam-doctor--replace-link-keep-label))))
@end lisp
The @samp{:name} property is the name of the function run. The function takes in the
Org parse tree, and returns a list of @samp{(point error-message)}. @samp{:description} is a
short description of what the checker does. @samp{:actions} is an alist containing
elements of the form @samp{(char . (prompt . function))}. These actions are defined per
The @code{:name} property is the name of the function run. The function takes in the
Org parse tree, and returns a list of @code{(point error-message)}. @code{:description} is a
short description of what the checker does. @code{:actions} is an alist containing
elements of the form @code{(char . (prompt . function))}. These actions are defined per
checker, to perform autofixes for the errors. For each error detected,
@samp{org-roam-doctor} will move the point to the current error, and pop-up a help
@code{org-roam-doctor} will move the point to the current error, and pop-up a help
window displaying the error message, as well as the list of actions that can be
taken provided in @samp{:actions}.
taken provided in @code{:actions}.
@node Performance Optimization
@chapter Performance Optimization
@menu
* Profiling Key Operations::
* Garbage Collection::
@end menu
@node Profiling Key Operations
@section @strong{TODO} Profiling Key Operations
@node Garbage Collection
@section Garbage Collection
During the cache-build process, Org-roam generates a lot of in-memory
data-structures (such as the Org file's AST), which are discarded after use. These structures are garbage collected at regular intervals (see @ref{Garbage Collection,info:elisp#Garbage Collection,,elisp,}).
Org-roam provides the option @code{org-roam-db-gc-threshold} to temporarily change
the threshold value for GC to be triggered during these memory-intensive
operations. To reduce the number of garbage collection processes, one may set
@code{org-roam-db-gc-threshold} to a high value (such as @code{most-positive-fixnum}):
@lisp
(setq org-roam-db-gc-threshold most-positive-fixnum)
@end lisp
@node Appendix
@chapter Appendix
@ -1169,19 +1263,44 @@ taken provided in @samp{:actions}.
@node Ecosystem
@section Ecosystem
A number of packages work well combined with Org-Roam:
@menu
* Deft::
* Browsing History with winner-mode::
* Versioning Notes::
* Full-text search interface with Deft::
* Org-journal::
* Note-taking Add-ons::
@end menu
@node Deft
@subsection Deft
@node Browsing History with winner-mode
@subsection Browsing History with winner-mode
@uref{https://jblevins.org/projects/deft/, Deft} provides a nice interface
for browsing and filtering org-roam notes.
@code{winner-mode} is a global minor mode that allows one to undo and redo changes in the window configuration. It is included with GNU Emacs since version 20.
@code{winner-mode} can be used as a simple version of browser history for Org-roam. Each click through org-roam links (from both Org files and the backlinks buffer) causes changes in window configuration, which can be undone and redone using @code{winner-mode}. To use @code{winner-mode}, simply enable it, and bind the appropriate interactive functions:
@lisp
(winner-mode +1)
(define-key winner-mode-map (kbd "<M-left>") #'winner-undo)
(define-key winner-mode-map (kbd "<M-right>") #'winner-redo)
@end lisp
@node Versioning Notes
@subsection Versioning Notes
Since Org-roam notes are just plain text, it is trivial to track changes in your
notes database using version control systems such as @uref{https://git-scm.com/, Git}. Simply initialize
@code{org-roam-directory} as a Git repository, and commit your files at regular or
appropriate intervals. @uref{https://magit.vc/, Magit} is a great interface to Git within Emacs.
In addition, it may be useful to observe how a particular note has evolved, by
looking at the file history. @uref{https://gitlab.com/pidu/git-timemachine, Git-timemachine} allows you to visit historic
versions of a tracked Org-roam note.
@node Full-text search interface with Deft
@subsection Full-text search interface with Deft
@uref{https://jblevins.org/projects/deft/, Deft} provides a nice interface for browsing and filtering org-roam notes.
@lisp
(use-package deft
@ -1196,7 +1315,7 @@ for browsing and filtering org-roam notes.
@end lisp
If the title of the Org file is not the first line, you might not get
nice titles. You may choose to patch this to use @samp{org-roam}'s
nice titles. You may choose to patch this to use @code{org-roam}'s
functionality. Here I'm using
@uref{https://github.com/raxod502/el-patch, el-patch}:
@ -1234,7 +1353,7 @@ that uses an external search engine and indexer.
@subsection Org-journal
@uref{https://github.com/bastibe/org-journal, Org-journal} is a more
powerful alternative to the simple function @samp{org-roam-dailies-today}. It
powerful alternative to the simple function @code{org-roam-dailies-today}. It
provides better journaling capabilities, and a nice calendar interface
to see all dated entries.
@ -1315,8 +1434,8 @@ etc.) within Org-mode.
tight integration between
@uref{https://github.com/jkitchin/org-ref, org-ref},
@uref{https://github.com/tmalsburg/helm-bibtex, helm-bibtex} and
@samp{org-roam}. This helps you manage your bibliographic notes under
@samp{org-roam}.
@code{org-roam}. This helps you manage your bibliographic notes under
@code{org-roam}.
@node Spaced Repetition
@unnumberedsubsubsec Spaced Repetition
@ -1336,11 +1455,11 @@ files. Other alternatives include @uref{https://orgmode.org/worg/org-contrib/org
@section How do I have more than one Org-roam directory?
Emacs supports directory-local variables, allowing the value of
@samp{org-roam-directory} to be different in different directories. It does this by
checking for a file named @samp{.dir-locals.el}.
@code{org-roam-directory} to be different in different directories. It does this by
checking for a file named @code{.dir-locals.el}.
To add support for multiple directories, override the @samp{org-roam-directory}
variable using directory-local variables. This is what @samp{.dir-locals.el} may
To add support for multiple directories, override the @code{org-roam-directory}
variable using directory-local variables. This is what @code{.dir-locals.el} may
contain:
@lisp
@ -1348,7 +1467,7 @@ contain:
@end lisp
All files within that directory will be treated as their own separate
set of Org-roam files. Remember to run @samp{org-roam-db-build-cache} from a
set of Org-roam files. Remember to run @code{org-roam-db-build-cache} from a
file within that directory, at least once.
@node How do I migrate from Roam Research?

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.2.0
;; Version: 1.2.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.0"))
;; This file is NOT part of GNU Emacs.
@ -49,6 +49,7 @@
(declare-function org-roam--get-backlinks "org-roam")
(declare-function org-roam-backlinks-mode "org-roam")
(declare-function org-roam-mode "org-roam")
(declare-function org-roam--find-file "org-roam")
(defcustom org-roam-buffer-position 'right
"Position of `org-roam' buffer.
@ -83,7 +84,7 @@ Has an effect if and only if `org-roam-buffer-position' is `top' or `bottom'."
(defcustom org-roam-buffer-prepare-hook '(org-roam-buffer--insert-title
org-roam-buffer--insert-backlinks
org-roam-buffer--insert-citelinks)
org-roam-buffer--insert-ref-links)
"Hook run in the `org-roam-buffer' before it is displayed."
:type 'hook
:group 'org-roam)
@ -97,6 +98,14 @@ For example: (setq org-roam-buffer-window-parameters '((no-other-window . t)))"
(defvar org-roam-buffer--current nil
"Currently displayed file in `org-roam' buffer.")
(defun org-roam-buffer--find-file (file)
"Open FILE in the window `org-roam' was called from."
(if (and org-roam-last-window (window-valid-p org-roam-last-window))
(progn (with-selected-window org-roam-last-window
(org-roam--find-file file))
(select-window org-roam-last-window))
(org-roam--find-file file)))
(defun org-roam-buffer--insert-title ()
"Insert the org-roam-buffer title."
(insert (propertize (org-roam--get-title-or-slug
@ -114,10 +123,9 @@ For example: (setq org-roam-buffer-window-parameters '((no-other-window . t)))"
,wrong-type))))))
(concat string (when (> l 1) "s"))))
(defun org-roam-buffer--insert-citelinks ()
"Insert citation backlinks for the current buffer."
(when-let ((org-ref-p (require 'org-ref nil t)) ;; Ensure that org-ref is present
(ref (cdr (with-temp-buffer
(defun org-roam-buffer--insert-ref-links ()
"Insert ref backlinks for the current buffer."
(when-let ((ref (cdr (with-temp-buffer
(insert-buffer-substring org-roam-buffer--current)
(org-roam--extract-ref)))))
(if-let* ((key-backlinks (org-roam--get-backlinks ref))
@ -125,7 +133,7 @@ For example: (setq org-roam-buffer-window-parameters '((no-other-window . t)))"
(progn
(insert (let ((l (length key-backlinks)))
(format "\n\n* %d %s\n"
l (org-roam-buffer--pluralize "Cite backlink" l))))
l (org-roam-buffer--pluralize "Ref Backlink" l))))
(dolist (group grouped-backlinks)
(let ((file-from (car group))
(bls (cdr group)))
@ -134,14 +142,12 @@ For example: (setq org-roam-buffer-window-parameters '((no-other-window . t)))"
(org-roam--get-title-or-slug file-from)))
(dolist (backlink bls)
(pcase-let ((`(,file-from _ ,props) backlink))
(insert (propertize
(s-trim (s-replace "\n" " "
(plist-get props :content)))
(insert (propertize (plist-get props :content)
'help-echo "mouse-1: visit backlinked note"
'file-from file-from
'file-from-point (plist-get props :point)))
(insert "\n\n"))))))
(insert "\n\n* No cite backlinks!"))))
(insert "\n\n* No ref backlinks!"))))
(defun org-roam-buffer--insert-backlinks ()
"Insert the org-roam-buffer backlinks string for the current buffer."
@ -160,13 +166,18 @@ For example: (setq org-roam-buffer-window-parameters '((no-other-window . t)))"
(org-roam--get-title-or-slug file-from)))
(dolist (backlink bls)
(pcase-let ((`(,file-from _ ,props) backlink))
(insert (propertize
(insert "*** "
(if-let ((outline (plist-get props :outline)))
(string-join outline " > ")
"Top")
"\n"
(propertize
(s-trim (s-replace "\n" " "
(plist-get props :content)))
'help-echo "mouse-1: visit backlinked note"
'file-from file-from
'file-from-point (plist-get props :point)))
(insert "\n\n"))))))
'file-from-point (plist-get props :point))
"\n\n"))))))
(insert "\n\n* No backlinks!")))
(defun org-roam-buffer-update ()
@ -266,14 +277,25 @@ Valid states are 'visible, 'exists and 'none."
(org-roam-buffer--set-height
(round (* (frame-height) org-roam-buffer-height))))))))
(defun org-roam-buffer-toggle-display ()
"Toggle display of the `org-roam-buffer'."
(defun org-roam-buffer-activate ()
"Activate display of the `org-roam-buffer'."
(interactive)
(unless org-roam-mode (org-roam-mode))
(setq org-roam-last-window (get-buffer-window))
(org-roam-buffer--get-create))
(defun org-roam-buffer-deactivate ()
"Deactivate display of the `org-roam-buffer'."
(interactive)
(setq org-roam-last-window (get-buffer-window))
(delete-window (get-buffer-window org-roam-buffer)))
(defun org-roam-buffer-toggle-display ()
"Toggle display of the `org-roam-buffer'."
(interactive)
(pcase (org-roam-buffer--visibility)
('visible (delete-window (get-buffer-window org-roam-buffer)))
((or 'exists 'none) (org-roam-buffer--get-create))))
('visible (org-roam-buffer-deactivate))
((or 'exists 'none) (org-roam-buffer-activate))))
(provide 'org-roam-buffer)

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.2.0
;; Version: 1.2.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.0"))
;; This file is NOT part of GNU Emacs.
@ -39,11 +39,12 @@
(defvar org-roam-encrypt-files)
(defvar org-roam-directory)
(defvar org-roam-mode)
(defvar org-roam-title-to-slug-function)
(declare-function org-roam--get-title-path-completions "org-roam")
(declare-function org-roam--get-ref-path-completions "org-roam")
(declare-function org-roam--file-path-from-id "org-roam")
(declare-function org-roam--find-file "org-roam")
(declare-function org-roam--format-link "org-roam")
(declare-function org-roam--title-to-slug "org-roam")
(declare-function org-roam-mode "org-roam")
(declare-function org-roam-completion--completing-read "org-roam-completion")
@ -81,68 +82,277 @@ note with the given `ref'.")
(defconst org-roam-capture--template-keywords '(:file-name :head)
"Keywords used in `org-roam-capture-templates' specific to Org-roam.")
(defvar org-roam-capture-templates
(defcustom org-roam-capture-templates
'(("d" "default" plain (function org-roam-capture--get-point)
"%?"
:file-name "%<%Y%m%d%H%M%S>-${slug}"
:head "#+title: ${title}\n"
:unnarrowed t))
"Capture templates for Org-roam.
The capture templates are an extension of
`org-capture-templates', and the documentation there also
applies.
The Org-roam capture-templates builds on the default behaviours of
`org-capture-templates' by expanding them in 3 areas:
`org-capture-templates' are extended in 3 ways:
1. Template-expansion capabilities are extended with additional
custom syntax. See `org-roam-capture--fill-template' for more
details.
1. Template expansion capabilities are extended with additional custom syntax.
See `org-roam-capture--fill-template' for more details.
2. The `:file-name' key is added, which expands to the file-name
of the note if it creates a new file. This file-name is
relative to `org-roam-directory', and is without the
file-extension.
2. The `:file-name' key is added, which defines the naming format
to use when creating new notes. This file-name is relative to
`org-roam-directory', and is without the file-extension.
3. The `:head' key is added, which contains the template that is
inserted on initial creation (added only once). This is where
insertion of any note metadata should go.")
inserted upon the creation of a new file. This is where you
should add your note metadata should go.
(defvar org-roam-capture-ref-templates
Each template should have the following structure:
\(KEY DESCRIPTION `plain' `(function org-roam-capture--get-point)'
TEMPLATE
`:file-name' FILENAME-FORMAT
`:head' HEADER-FORMAT
`:unnarrowed t'
OPTIONS-PLIST)
The elements of a template-entry and their placement are the same
as in `org-capture-templates', except that the entry type must
always be the symbol `plain', and that the target must always be
the list `(function org-roam-capture--get-point)'.
Org-roam requires the plist elements `:file-name' and `:head' to
be present, and its recommended that `:unnarrowed' be set to t."
:group 'org-roam
;; Adapted from `org-capture-templates'
:type
'(repeat
(choice :value ("d" "default" plain (function org-roam-capture--get-point)
"%?"
:file-name "%<%Y%m%d%H%M%S>-${slug}"
:head "#+title: ${title}\n"
:unnarrowed t)
(list :tag "Multikey description"
(string :tag "Keys ")
(string :tag "Description"))
(list :tag "Template entry"
(string :tag "Keys ")
(string :tag "Description ")
(const :format "" plain)
(const :format "" (function org-roam-capture--get-point))
(choice :tag "Template "
(string :tag "String"
:format "String:\n \
Template string :\n%v")
(list :tag "File"
(const :format "" file)
(file :tag "Template file "))
(list :tag "Function"
(const :format "" function)
(function :tag "Template function ")))
(const :format "File name format :" :file-name)
(string :format " %v" :value "#+title: ${title}\n")
(const :format "Header format :" :head)
(string :format "\n%v" :value "%<%Y%m%d%H%M%S>-${slug}")
(const :format "" :unnarrowed) (const :format "" t)
(plist :inline t
:tag "Options"
;; Give the most common options as checkboxes
:options
(((const :format "%v " :prepend) (const t))
((const :format "%v " :immediate-finish) (const t))
((const :format "%v " :jump-to-captured) (const t))
((const :format "%v " :empty-lines) (const 1))
((const :format "%v " :empty-lines-before) (const 1))
((const :format "%v " :empty-lines-after) (const 1))
((const :format "%v " :clock-in) (const t))
((const :format "%v " :clock-keep) (const t))
((const :format "%v " :clock-resume) (const t))
((const :format "%v " :time-prompt) (const t))
((const :format "%v " :tree-type) (const week))
((const :format "%v " :table-line-pos) (string))
((const :format "%v " :kill-buffer) (const t))))))))
(defcustom org-roam-capture-immediate-template
(append (car org-roam-capture-templates) '(:immediate-finish t))
"Capture template to use for immediate captures in Org-roam.
This is a single template, so do not enclose it into a list.
See `org-roam-capture-templates' for details on templates."
:group 'org-roam
;; Adapted from `org-capture-templates'
:type
'(list :tag "Template entry"
:value ("d" "default" plain (function org-roam-capture--get-point)
"%?"
:file-name "%<%Y%m%d%H%M%S>-${slug}"
:head "#+title: ${title}\n"
:unnarrowed t
:immediate-finish t)
(string :tag "Keys ")
(string :tag "Description ")
(const :format "" plain)
(const :format "" (function org-roam-capture--get-point))
(choice :tag "Template "
(string :tag "String"
:format "String:\n \
Template string :\n%v")
(list :tag "File"
(const :format "" file)
(file :tag "Template file "))
(list :tag "Function"
(const :format "" function)
(function :tag "Template function ")))
(const :format "File name format :" :file-name)
(string :format " %v" :value "#+title: ${title}\n")
(const :format "Header format :" :head)
(string :format "\n%v" :value "%<%Y%m%d%H%M%S>-${slug}")
(const :format "" :unnarrowed) (const :format "" t)
(const :format "" :immediate-finish) (const :format "" t)
(plist :inline t
:tag "Options"
;; Give the most common options as checkboxes
:options
(((const :format "%v " :prepend) (const t))
((const :format "%v " :jump-to-captured) (const t))
((const :format "%v " :empty-lines) (const 1))
((const :format "%v " :empty-lines-before) (const 1))
((const :format "%v " :empty-lines-after) (const 1))
((const :format "%v " :clock-in) (const t))
((const :format "%v " :clock-keep) (const t))
((const :format "%v " :clock-resume) (const t))
((const :format "%v " :time-prompt) (const t))
((const :format "%v " :tree-type) (const week))
((const :format "%v " :table-line-pos) (string))
((const :format "%v " :kill-buffer) (const t))))))
(defcustom org-roam-capture-ref-templates
'(("r" "ref" plain (function org-roam-capture--get-point)
""
"%?"
:file-name "${slug}"
:head "#+title: ${title}
#+roam_key: ${ref}\n"
:head "#+title: ${title}\n#+roam_key: ${ref}\n"
:unnarrowed t))
"The Org-roam templates used during a capture from the roam-ref protocol.
Details on how to specify for the template is given in `org-roam-capture-templates'.")
Details on how to specify for the template is given in `org-roam-capture-templates'."
:group 'org-roam
;; Adapted from `org-capture-templates'
:type
'(repeat
(choice :value ("d" "default" plain (function org-roam-capture--get-point)
"%?"
:file-name "${slug}"
:head "#+title: ${title}\n#+roam_key: ${ref}\n"
:unnarrowed t)
(list :tag "Multikey description"
(string :tag "Keys ")
(string :tag "Description"))
(list :tag "Template entry"
(string :tag "Keys ")
(string :tag "Description ")
(const :format "" plain)
(const :format "" (function org-roam-capture--get-point))
(choice :tag "Template "
(string :tag "String"
:format "String:\n \
Template string :\n%v")
(list :tag "File"
(const :format "" file)
(file :tag "Template file "))
(list :tag "Function"
(const :format "" function)
(function :tag "Template function ")))
(const :format "File name format :" :file-name)
(string :format " %v" :value "#+title: ${title}\n")
(const :format "Header format :" :head)
(string :format "\n%v" :value "%<%Y%m%d%H%M%S>-${slug}")
(const :format "" :unnarrowed) (const :format "" t)
(plist :inline t
:tag "Options"
;; Give the most common options as checkboxes
:options
(((const :format "%v " :prepend) (const t))
((const :format "%v " :immediate-finish) (const t))
((const :format "%v " :jump-to-captured) (const t))
((const :format "%v " :empty-lines) (const 1))
((const :format "%v " :empty-lines-before) (const 1))
((const :format "%v " :empty-lines-after) (const 1))
((const :format "%v " :clock-in) (const t))
((const :format "%v " :clock-keep) (const t))
((const :format "%v " :clock-resume) (const t))
((const :format "%v " :time-prompt) (const t))
((const :format "%v " :tree-type) (const week))
((const :format "%v " :table-line-pos) (string))
((const :format "%v " :kill-buffer) (const t))))))))
(defun org-roam-capture-p ()
"Return t if the current capture process is an Org-roam capture.
This function is to only be called when org-capture-plist is
valid for the capture (i.e. initialization, and finalization of
the capture)."
(plist-get org-capture-plist :org-roam))
(defun org-roam-capture--get (keyword)
"Gets the value for KEYWORD from the `org-roam-capture-template'."
"Get the value for KEYWORD from the `org-roam-capture-template'."
(plist-get (plist-get org-capture-plist :org-roam) keyword))
(defun org-roam-capture--put (&rest stuff)
"Puts properties from STUFF into the `org-roam-capture-template'."
"Put properties from STUFF into the `org-roam-capture-template'."
(let ((p (plist-get org-capture-plist :org-roam)))
(while stuff
(setq p (plist-put p
(pop stuff) (pop stuff))))
(setq p (plist-put p (pop stuff) (pop stuff))))
(setq org-capture-plist
(plist-put org-capture-plist :org-roam p))))
(defun org-roam-capture--in-process-p ()
"Return non-nil if a `org-roam-capture' buffer exists."
(cl-some (lambda (buffer)
(and (eq (buffer-local-value 'major-mode buffer)
'org-mode)
(plist-get (buffer-local-value 'org-capture-current-plist buffer)
:org-roam)))
(buffer-list)))
;; FIXME: Pending upstream patch
;; https://orgmode.org/list/87h7tv9pkm.fsf@hidden/T/#u
;;
;; Org-capture's behaviour right now is that `org-capture-plist' is valid only
;; during the initialization of the Org-capture buffer. The value of
;; `org-capture-plist' is saved into buffer-local `org-capture-current-plist'.
;; However, the value for that particular capture is no longer accessible for
;; hooks in `org-capture-after-finalize-hook', since the capture buffer has been
;; cleaned up.
;;
;; This advice restores the global `org-capture-plist' during finalization, so
;; the plist is valid during both initialization and finalization of the
;; capture.
(defun org-roam-capture--update-plist (&optional _)
"Update global plist from local var."
(setq org-capture-plist org-capture-current-plist))
(defun org-roam-capture--fill-template (str &optional info)
"Expands the template STR, returning the string.
(advice-add 'org-capture-finalize :before #'org-roam-capture--update-plist)
(defun org-roam-capture--finalize ()
"Finalize the `org-roam-capture' process."
(unless org-note-abort
(pcase (org-roam-capture--get :finalize)
('find-file
(when-let ((file-path (org-roam-capture--get :file-path)))
(org-roam--find-file file-path)
(run-hooks 'org-roam-capture-after-find-file-hook)))
('insert-link
(when-let* ((mkr (org-roam-capture--get :insert-at))
(buf (marker-buffer mkr)))
(with-current-buffer buf
(when-let ((region (org-roam-capture--get :region))) ;; Remove previously selected text.
(delete-region (car region) (cdr region)))
(let ((path (org-roam-capture--get :file-path))
(desc (org-roam-capture--get :link-description)))
(if (eq (point) (marker-position mkr))
(insert (org-roam--format-link path desc))
(org-with-point-at mkr
(insert (org-roam--format-link path desc))))))))))
(org-roam-capture--save-file-maybe)
(remove-hook 'org-capture-after-finalize-hook #'org-roam-capture--finalize))
(defun org-roam-capture--install-finalize ()
"Install `org-roam-capture--finalize' if the capture is an Org-roam capture."
(when (org-roam-capture-p)
(add-hook 'org-capture-after-finalize-hook #'org-roam-capture--finalize)))
(add-hook 'org-capture-prepare-finalize-hook #'org-roam-capture--install-finalize)
(defun org-roam-capture--fill-template (str)
"Expand the template STR, returning the string.
This is an extension of org-capture's template expansion.
First, it expands ${var} occurrences in STR, using the INFO alist.
First, it expands ${var} occurrences in STR, using `org-roam-capture--info'.
If there is a ${var} with no matching var in the alist, the value
of var is prompted for via `completing-read'.
@ -150,23 +360,13 @@ Next, it expands the remaining template string using
`org-capture-fill-template'."
(-> str
(s-format (lambda (key)
(or (s--aget info key)
(completing-read (format "%s: " key ) nil))) nil)
(or (s--aget org-roam-capture--info key)
(when-let ((val (completing-read (format "%s: " key) nil)))
(push (cons key val) org-roam-capture--info)
val))) nil)
(org-capture-fill-template)))
(defun org-roam-capture--insert-link-h ()
"Insert the link into the original buffer, after the capture process is done.
This is added as a hook to `org-capture-after-finalize-hook'."
(when (and (not org-note-abort)
(eq (org-roam-capture--get :capture-fn)
'org-roam-insert))
(when-let ((region (org-roam-capture--get :region))) ;; Remove previously selected text.
(delete-region (car region) (cdr region)))
(insert (org-roam--format-link (org-roam-capture--get :file-path)
(org-roam-capture--get :link-description))))
(remove-hook 'org-capture-after-finalize-hook #'org-roam-capture--insert-link-h))
(defun org-roam-capture--save-file-maybe-h ()
(defun org-roam-capture--save-file-maybe ()
"Save the file conditionally.
The file is saved if the original value of :no-save is not t and
`org-note-abort' is not t. It is added to
@ -180,8 +380,7 @@ The file is saved if the original value of :no-save is not t and
((and (not (org-roam-capture--get :orig-no-save))
(not org-note-abort))
(with-current-buffer (org-capture-get :buffer)
(save-buffer))))
(remove-hook 'org-capture-after-finalize-hook #'org-roam-capture--save-file-maybe-h))
(save-buffer)))))
(defun org-roam-capture--new-file ()
"Return the path to the new file during an Org-roam capture.
@ -201,14 +400,13 @@ aborted, we do the following:
2. Set the capture template's :no-save to t.
3. Add a function on `org-capture-after-finalize-hook' that saves
3. Add a function on `org-capture-before-finalize-hook' that saves
the file if the original value of :no-save is not t and
`org-note-abort' is not t."
(let* ((name-templ (or (org-roam-capture--get :file-name)
org-roam-capture--file-name-default))
(new-id (s-trim (org-roam-capture--fill-template
name-templ
org-roam-capture--info)))
name-templ)))
(file-path (org-roam--file-path-from-id new-id))
(roam-head (or (org-roam-capture--get :head)
org-roam-capture--header-default))
@ -229,16 +427,6 @@ the file if the original value of :no-save is not t and
:no-save t))
file-path))
(defun org-roam-capture--expand-template ()
"Expand capture template with information from `org-roam-capture--info'."
(org-capture-put :template
(s-format (org-capture-get :template)
(lambda (key)
(or (s--aget org-roam-capture--info key)
(when-let ((v (completing-read (format "%s: " key ) nil)))
(push (cons key v) org-roam-capture--info)
v))) nil)))
(defun org-roam-capture--get-point ()
"Return exact point to file for org-capture-template.
The file to use is dependent on the context:
@ -271,7 +459,8 @@ This function is used solely in Org-roam's capture templates: see
(plist-get pl :path)
(org-roam-capture--new-file))))
(_ (error "Invalid org-roam-capture-context")))))
(org-roam-capture--expand-template)
(org-capture-put :template
(org-roam-capture--fill-template (org-capture-get :template)))
(org-roam-capture--put :file-path file-path)
(while org-roam-capture-additional-template-props
(let ((prop (pop org-roam-capture-additional-template-props))
@ -299,33 +488,31 @@ This function is used solely in Org-roam's capture templates: see
(append converted options `(:org-roam ,org-roam-plist))))
(_ (user-error "Invalid capture template format: %s" template))))
(defun org-roam-capture--find-file-h ()
"Opens the newly created template file.
This is added as a hook to `org-capture-after-finalize-hook'.
Run the hooks defined in `org-roam-capture-after-find-file-hook'."
(unless org-note-abort
(when-let ((file-path (org-roam-capture--get :file-path)))
(find-file file-path))
(run-hooks 'org-roam-capture-after-find-file-hook))
(remove-hook 'org-capture-after-finalize-hook #'org-roam-capture--find-file-h))
(defcustom org-roam-capture-after-find-file-hook nil
"Hook that is run right after an Org-roam capture process is finalized.
Suitable for moving point."
:group 'org-roam
:type 'hook)
(defcustom org-roam-capture-function #'org-capture
"Function that is invoked to start the `org-capture' process."
:group 'org-roam
:type 'function)
(defun org-roam-capture--capture (&optional goto keys)
"Create a new file, and return the path to the edited file.
The templates are defined at `org-roam-capture-templates'. The
GOTO and KEYS argument have the same functionality as
`org-capture'."
(let ((org-capture-templates (mapcar #'org-roam-capture--convert-template org-roam-capture-templates))
org-capture-templates-contexts)
(when (= (length org-capture-templates) 1)
(let* ((org-capture-templates (mapcar #'org-roam-capture--convert-template org-roam-capture-templates))
(one-template-p (= (length org-capture-templates) 1))
org-capture-templates-contexts)
(when one-template-p
(setq keys (caar org-capture-templates)))
(add-hook 'org-capture-after-finalize-hook #'org-roam-capture--save-file-maybe-h)
(org-capture goto keys)))
(if (or one-template-p
(eq org-roam-capture-function 'org-capture))
(org-capture goto keys)
(funcall-interactively org-roam-capture-function))))
;;;###autoload
(defun org-roam-capture ()
@ -333,8 +520,6 @@ GOTO and KEYS argument have the same functionality as
This uses the templates defined at `org-roam-capture-templates'."
(interactive)
(unless org-roam-mode (org-roam-mode))
(when (org-roam-capture--in-process-p)
(user-error "Nested Org-roam capture processes not supported"))
(let* ((completions (org-roam--get-title-path-completions))
(title-with-keys (org-roam-completion--completing-read "File: "
completions))
@ -342,10 +527,9 @@ This uses the templates defined at `org-roam-capture-templates'."
(title (or (plist-get res :title) title-with-keys))
(file-path (plist-get res :path)))
(let ((org-roam-capture--info (list (cons 'title title)
(cons 'slug (org-roam--title-to-slug title))
(cons 'slug (funcall org-roam-title-to-slug-function title))
(cons 'file file-path)))
(org-roam-capture--context 'capture))
(setq org-roam-capture-additional-template-props (list :capture-fn 'org-roam-capture))
(condition-case err
(org-roam-capture--capture)
(error (user-error "%s. Please adjust `org-roam-capture-templates'"

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.2.0
;; Version: 1.2.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.0"))
;; This file is NOT part of GNU Emacs.
@ -79,6 +79,8 @@
"org-roam 1.1.0")
(define-obsolete-function-alias 'org-roam-db--maybe-update 'org-roam-db--update-maybe
"org-roam 1.1.0")
(define-obsolete-function-alias 'org-roam-db--clear 'org-roam-db-clear
"org-roam 1.2.0")
(when (version< (org-version) "9.3")
(defalias 'org-link-make-string 'org-make-link-string))

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.2.0
;; Version: 1.2.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.0"))
;; This file is NOT part of GNU Emacs.

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.2.0
;; Version: 1.2.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.0"))
;; This file is NOT part of GNU Emacs.
@ -36,13 +36,62 @@
(require 'org-roam-capture)
(require 'org-roam-macs)
(defvar org-roam-dailies-capture-templates
(defcustom org-roam-dailies-capture-templates
'(("d" "daily" plain (function org-roam-capture--get-point)
""
:immediate-finish t
:file-name "%<%Y-%m-%d>"
:head "#+title: %<%Y-%m-%d>"))
"Capture templates for daily notes in Org-roam.")
"Capture templates for daily notes in Org-roam."
:group 'org-roam
;; Adapted from `org-capture-templates'
:type
'(repeat
(choice :value ("d" "daily" plain (function org-roam-capture--get-point)
""
:immediate-finish t
:file-name "%<%Y-%m-%d>"
:head "#+title: %<%Y-%m-%d>")
(list :tag "Multikey description"
(string :tag "Keys ")
(string :tag "Description"))
(list :tag "Template entry"
(string :tag "Keys ")
(string :tag "Description ")
(const :format "" plain)
(const :format "" (function org-roam-capture--get-point))
(choice :tag "Template "
(string :tag "String"
:format "String:\n \
Template string :\n%v")
(list :tag "File"
(const :format "" file)
(file :tag "Template file "))
(list :tag "Function"
(const :format "" function)
(function :tag "Template function ")))
(const :format "" :immediate-finish) (const :format "" t)
(const :format "File name format :" :file-name)
(string :format " %v" :value "#+title: ${title}\n")
(const :format "Header format :" :head)
(string :format "\n%v" :value "%<%Y%m%d%H%M%S>-${slug}")
(plist :inline t
:tag "Options"
;; Give the most common options as checkboxes
:options
(((const :format "%v " :prepend) (const t))
((const :format "%v " :jump-to-captured) (const t))
((const :format "%v " :empty-lines) (const 1))
((const :format "%v " :empty-lines-before) (const 1))
((const :format "%v " :empty-lines-after) (const 1))
((const :format "%v " :clock-in) (const t))
((const :format "%v " :clock-keep) (const t))
((const :format "%v " :clock-resume) (const t))
((const :format "%v " :time-prompt) (const t))
((const :format "%v " :tree-type) (const week))
((const :format "%v " :table-line-pos) (string))
((const :format "%v " :kill-buffer) (const t))
((const :format "%v " :unnarrowed) (const t))))))))
;; Declarations
(defvar org-roam-mode)
@ -54,7 +103,7 @@
(let ((org-roam-capture-templates org-roam-dailies-capture-templates)
(org-roam-capture--info (list (cons 'time time)))
(org-roam-capture--context 'dailies))
(add-hook 'org-capture-after-finalize-hook #'org-roam-capture--find-file-h)
(setq org-roam-capture-additional-template-props (list :finalize 'find-file))
(org-roam--with-template-error 'org-roam-dailies-capture-templates
(org-roam-capture--capture))))

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.2.0
;; Version: 1.2.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.0"))
;; This file is NOT part of GNU Emacs.
@ -48,6 +48,8 @@
(declare-function org-roam--extract-headlines "org-roam")
(declare-function org-roam--extract-links "org-roam")
(declare-function org-roam--list-all-files "org-roam")
(declare-function org-roam--path-to-slug "org-roam")
(declare-function org-roam--file-name-extension "org-roam")
(declare-function org-roam-buffer--update-maybe "org-roam-buffer")
;;;; Options
@ -60,7 +62,22 @@ when used with multiple Org-roam instances."
:type 'string
:group 'org-roam)
(defconst org-roam-db--version 6)
(defcustom org-roam-db-gc-threshold gc-cons-threshold
"The value to temporarily set the `gc-cons-threshold' threshold to.
During large, heavy operations like `org-roam-db-build-cache',
many GC operations happen because of the large number of
temporary structures generated (e.g. parsed ASTs). Temporarily
increasing `gc-cons-threshold' will help reduce the number of GC
operations, at the cost of temporary memory usage.
This defaults to the original value of `gc-cons-threshold', but
tweaking this number may lead to better overall performance. For
example, to reduce the number of GCs, one may set it to a large
value like `most-positive-fixnum'."
:type 'int
:group 'org-roam)
(defconst org-roam-db--version 7)
(defvar org-roam-db--connection (make-hash-table :test #'equal)
"Database connection to Org-roam database.")
@ -68,7 +85,7 @@ when used with multiple Org-roam instances."
;;;; Core Functions
(defun org-roam-db--get ()
"Return the sqlite db file."
(or org-roam-db-location
(or org-roam-db-location
(expand-file-name "org-roam.db" org-roam-directory)))
(defun org-roam-db--get-connection ()
@ -137,7 +154,7 @@ SQL can be either the emacsql vector representation, or a string."
(titles
[(file :not-null)
titles])
title])
(refs
[(ref :unique :not-null)
@ -179,7 +196,7 @@ the current `org-roam-directory'."
;;;; Database API
;;;;; Initialization
(defun org-roam-db--initialized-p ()
"Whether the cache has been initialized."
"Whether the Org-roam cache has been initialized."
(and (file-exists-p (org-roam-db--get))
(> (caar (org-roam-db-query [:select (funcall count) :from titles]))
0)))
@ -190,8 +207,8 @@ the current `org-roam-directory'."
(error "[Org-roam] your cache isn't built yet! Please run org-roam-db-build-cache")))
;;;;; Clearing
(defun org-roam-db--clear ()
"Clears all entries in the caches."
(defun org-roam-db-clear ()
"Clears all entries in the Org-roam cache."
(interactive)
(when (file-exists-p (org-roam-db--get))
(dolist (table (mapcar #'car org-roam-db--table-schemata))
@ -227,14 +244,29 @@ This is equivalent to removing the node from the graph."
(org-roam-db-query
[:insert :into titles
:values $v1]
(list (vector file titles))))
(mapcar (lambda (title)
(vector file title)) titles)))
(defun org-roam-db--insert-headlines (headlines)
"Insert HEADLINES into the Org-roam cache."
(org-roam-db-query
[:insert :into headlines
:values $v1]
headlines))
"Insert HEADLINES into the Org-roam cache.
Returns t if the insertion was successful, nil otherwise.
Insertions can fail when there is an ID conflict."
(condition-case nil
(progn
(org-roam-db-query
[:insert :into headlines
:values $v1]
headlines)
t)
(error
(unless (listp headlines)
(setq headlines (list headlines)))
(lwarn '(org-roam) :error
(format "Duplicate IDs in %s, one of:\n\n%s\n\nskipping..."
(aref (car headlines) 1)
(string-join (mapcar (lambda (hl)
(aref hl 0)) headlines) "\n")))
nil)))
(defun org-roam-db--insert-tags (file tags)
"Insert TAGS for a FILE into the Org-roam cache."
@ -244,12 +276,27 @@ This is equivalent to removing the node from the graph."
(list (vector file tags))))
(defun org-roam-db--insert-ref (file ref)
"Insert REF for FILE into the Org-roam cache."
"Insert REF for FILE into the Org-roam cache.
Returns t if successful, and nil otherwise.
Insertions can fail if the key is already in the database."
(let ((key (cdr ref))
(type (car ref)))
(org-roam-db-query
[:insert :into refs :values $v1]
(list (vector key file type)))))
(condition-case nil
(progn
(org-roam-db-query
[:insert :into refs :values $v1]
(list (vector key file type)))
t)
(error
(lwarn '(org-roam) :error
(format "Duplicate ref %s in:\n\nA: %s\nB: %s\n\nskipping..."
key
file
(caar (org-roam-db-query
[:select file :from refs
:where (= ref $v1)]
(vector key)))))
nil))))
;;;;; Fetching
(defun org-roam-db--get-current-files ()
@ -262,10 +309,10 @@ This is equivalent to removing the node from the graph."
(defun org-roam-db--get-titles (file)
"Return the titles of FILE from the cache."
(caar (org-roam-db-query [:select [titles] :from titles
:where (= file $s1)]
file
:limit 1)))
(caar (org-roam-db-query [:select [title] :from titles
:where (= file $s1)
:limit 1]
file)))
(defun org-roam-db--connected-component (file)
"Return all files reachable from/connected to FILE, including the file itself.
@ -321,6 +368,26 @@ connections, nil is returned."
(files (mapcar 'car-safe (emacsql (org-roam-db) query file max-distance))))
files))
(defun org-roam-db--file-hash (&optional file-path)
"Compute the hash of FILE-PATH, a file or current buffer."
(let* ((file-p (and file-path))
(file-path (or file-path
(buffer-file-name (current-buffer))))
(encrypted-p (and file-path
(string= (org-roam--file-name-extension file-path)
"gpg"))))
(cond ((and encrypted-p file-p)
(with-temp-buffer
(set-buffer-multibyte nil)
(insert-file-contents-literally file-path)
(secure-hash 'sha1 (current-buffer))))
(file-p
(with-temp-buffer
(insert-file-contents file-path)
(secure-hash 'sha1 (current-buffer))))
(t
(secure-hash 'sha1 (current-buffer))))))
;;;;; Updating
(defun org-roam-db--update-meta ()
"Update the metadata of the current buffer into the cache."
@ -328,7 +395,7 @@ connections, nil is returned."
(attr (file-attributes file))
(atime (file-attribute-access-time attr))
(mtime (file-attribute-modification-time attr))
(hash (secure-hash 'sha1 (current-buffer))))
(hash (org-roam-db--file-hash)))
(org-roam-db-query [:delete :from files
:where (= file $s1)]
file)
@ -337,11 +404,12 @@ connections, nil is returned."
(defun org-roam-db--update-titles ()
"Update the title of the current buffer into the cache."
(let* ((file (file-truename (buffer-file-name)))
(title (org-roam--extract-titles)))
(titles (or (org-roam--extract-titles)
(list (org-roam--path-to-slug file)))))
(org-roam-db-query [:delete :from titles
:where (= file $s1)]
file)
(org-roam-db--insert-titles file title)))
(org-roam-db--insert-titles file titles)))
(defun org-roam-db--update-tags ()
"Update the tags of the current buffer into the cache."
@ -388,12 +456,13 @@ connections, nil is returned."
(current-buffer))))
(with-current-buffer buf
(save-excursion
(org-roam-db--update-meta)
(org-roam-db--update-tags)
(org-roam-db--update-titles)
(org-roam-db--update-refs)
(org-roam-db--update-headlines)
(org-roam-db--update-links)
(emacsql-with-transaction (org-roam-db)
(org-roam-db--update-meta)
(org-roam-db--update-tags)
(org-roam-db--update-titles)
(org-roam-db--update-refs)
(org-roam-db--update-headlines)
(org-roam-db--update-links))
(org-roam-buffer--update-maybe :redisplay t))))))
(defun org-roam-db-build-cache (&optional force)
@ -403,91 +472,74 @@ If FORCE, force a rebuild of the cache from scratch."
(when force (delete-file (org-roam-db--get)))
(org-roam-db--close) ;; Force a reconnect
(org-roam-db) ;; To initialize the database, no-op if already initialized
(let* ((org-roam-files (org-roam--list-all-files))
(let* ((gc-cons-threshold org-roam-db-gc-threshold)
(org-roam-files (org-roam--list-all-files))
(current-files (org-roam-db--get-current-files))
all-files all-headlines all-links all-titles all-refs all-tags)
;; Two-step building
;; First step: Rebuild files and headlines
(dolist (file org-roam-files)
(let* ((attr (file-attributes file))
(atime (file-attribute-access-time attr))
(mtime (file-attribute-modification-time attr)))
(org-roam--with-temp-buffer file
(let ((contents-hash (secure-hash 'sha1 (current-buffer))))
(file-count 0)
(headline-count 0)
(link-count 0)
(tag-count 0)
(title-count 0)
(ref-count 0)
(deleted-count 0))
(emacsql-with-transaction (org-roam-db)
;; Two-step building
;; First step: Rebuild files and headlines
(dolist (file org-roam-files)
(let* ((attr (file-attributes file))
(atime (file-attribute-access-time attr))
(mtime (file-attribute-modification-time attr)))
(let ((contents-hash (org-roam-db--file-hash file)))
(unless (string= (gethash file current-files)
contents-hash)
(org-roam-db--clear-file file)
(push (vector file contents-hash (list :atime atime :mtime mtime))
all-files)
(when-let (headlines (org-roam--extract-headlines file))
(push headlines all-headlines)))))))
(when all-files
(org-roam-db-query
[:insert :into files
:values $v1]
all-files))
(when all-headlines
(org-roam-db-query
[:insert :into headlines
:values $v1]
all-headlines))
;; Second step: Rebuild the rest
(dolist (file org-roam-files)
(org-roam--with-temp-buffer file
(let ((contents-hash (secure-hash 'sha1 (current-buffer))))
(org-roam--with-temp-buffer file
(org-roam-db--clear-file file)
(org-roam-db-query
[:insert :into files
:values $v1]
(vector file contents-hash (list :atime atime :mtime mtime)))
(setq file-count (1+ file-count))
(when-let ((headlines (org-roam--extract-headlines file)))
(when (org-roam-db--insert-headlines headlines)
(setq headline-count (1+ headline-count)))))))))
;; Second step: Rebuild the rest
(dolist (file org-roam-files)
(let ((contents-hash (org-roam-db--file-hash file)))
(unless (string= (gethash file current-files)
contents-hash)
(when-let (links (org-roam--extract-links file))
(push links all-links))
(when-let (tags (org-roam--extract-tags file))
(push (vector file tags) all-tags))
(let ((titles (org-roam--extract-titles)))
(push (vector file titles)
all-titles))
(when-let* ((ref (org-roam--extract-ref))
(type (car ref))
(key (cdr ref)))
(setq all-refs (cons (vector key file type) all-refs))))
(remhash file current-files))))
(dolist (file (hash-table-keys current-files))
;; These files are no longer around, remove from cache...
(org-roam-db--clear-file file))
(when all-links
(org-roam-db-query
[:insert :into links
:values $v1]
all-links))
(when all-titles
(org-roam-db-query
[:insert :into titles
:values $v1]
all-titles))
(when all-tags
(org-roam-db-query
[:insert :into tags
:values $v1]
all-tags))
(when all-refs
(org-roam-db-query
[:insert :into refs
:values $v1]
all-refs))
(let ((stats (list :files (length all-files)
:headlines (length all-headlines)
:links (length all-links)
:tags (length all-tags)
:titles (length all-titles)
:refs (length all-refs)
:deleted (length (hash-table-keys current-files)))))
(org-roam-message "files: %s, headlines: %s, links: %s, tags: %s, titles: %s, refs: %s, deleted: %s"
(plist-get stats :files)
(plist-get stats :headlines)
(plist-get stats :links)
(plist-get stats :tags)
(plist-get stats :titles)
(plist-get stats :refs)
(plist-get stats :deleted))
stats)))
(org-roam--with-temp-buffer file
(when-let (links (org-roam--extract-links file))
(org-roam-db-query
[:insert :into links
:values $v1]
links)
(setq link-count (1+ link-count)))
(when-let (tags (org-roam--extract-tags file))
(org-roam-db-query
[:insert :into tags
:values $v1]
(vector file tags))
(setq tag-count (1+ tag-count)))
(let ((titles (or (org-roam--extract-titles)
(list (org-roam--path-to-slug file)))))
(org-roam-db--insert-titles file titles)
(setq title-count (+ title-count (length titles))))
(when-let* ((ref (org-roam--extract-ref)))
(when (org-roam-db--insert-ref file ref)
(setq ref-count (1+ ref-count))))))
(remhash file current-files)))
(dolist (file (hash-table-keys current-files))
;; These files are no longer around, remove from cache...
(org-roam-db--clear-file file)
(setq deleted-count (1+ deleted-count))))
(org-roam-message "files: Δ%s, headlines: Δ%s, links: Δ%s, tags: Δ%s, titles: Δ%s, refs: Δ%s, deleted: Δ%s"
file-count
headline-count
link-count
tag-count
title-count
ref-count
deleted-count)))
(provide 'org-roam-db)

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.2.0
;; Version: 1.2.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.0"))
;; This file is NOT part of GNU Emacs.
@ -33,7 +33,14 @@
;;
;;; Code:
(require 'emacsql)
(emacsql-fix-vector-indentation)
(provide 'org-roam-dev)
;;;###autoload
(define-minor-mode org-roam-dev-mode
"Minor mode for setting the dev environment of Org-roam."
:lighter " ORD"
(when org-roam-dev-mode
(emacsql-fix-vector-indentation)
(setq-local sentence-end-double-space nil)))
(provide 'org-roam-dev)
;;; org-roam-dev.el ends here

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/jethrokuan/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.2.0
;; Version: 1.2.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.0"))
;; This file is NOT part of GNU Emacs.

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.2.0
;; Version: 1.2.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.0"))
;; This file is NOT part of GNU Emacs.
@ -192,14 +192,16 @@ into a digraph."
","))))
(dolist (node nodes)
(let* ((file (xml-escape-string (car node)))
(title (or (caadr node)
(title (or (cadr node)
(org-roam--path-to-slug file)))
(shortened-title (pcase org-roam-graph-shorten-titles
(`truncate (s-truncate org-roam-graph-max-title-length title))
(`wrap (s-word-wrap org-roam-graph-max-title-length title))
(_ title)))
(shortened-title (org-roam-string-quote shortened-title))
(title (org-roam-string-quote title))
(node-properties
`(("label" . ,(s-replace "\"" "\\\"" shortened-title))
`(("label" . ,shortened-title)
("URL" . ,(concat "org-protocol://roam-file?file=" (url-hexify-string file)))
("tooltip" . ,(xml-escape-string title)))))
(insert
@ -230,8 +232,9 @@ CALLBACK is passed the graph file as its sole argument."
"Please adjust `org-roam-graph-executable'")
org-roam-graph-executable))
(let* ((node-query (or node-query
`[:select [file titles] :from titles
,@(org-roam-graph--expand-matcher 'file t)]))
`[:select [file title] :from titles
,@(org-roam-graph--expand-matcher 'file t)
:group :by file]))
(graph (org-roam-graph--dot node-query))
(temp-dot (make-temp-file "graph." nil ".dot" graph))
(temp-graph (make-temp-file "graph." nil ".svg")))
@ -267,7 +270,7 @@ CALLBACK is passed to `org-roam-graph--build'."
(org-roam-db--links-with-max-distance file max-distance)
(org-roam-db--connected-component file))
(list file)))
(query `[:select [file titles]
(query `[:select [file title]
:from titles
:where (in file [,@files])]))
(org-roam-graph--build query callback)))

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.2.0
;; Version: 1.2.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.0"))
;; This file is NOT part of GNU Emacs.
@ -33,6 +33,7 @@
;;
;;; Code:
;;;; Library Requires
(require 'dash)
(defvar org-roam-verbose)
@ -44,7 +45,9 @@ If FILE, set `org-roam-temp-file-name' to file and insert its contents."
(let ((current-org-roam-directory (make-symbol "current-org-roam-directory")))
`(let ((,current-org-roam-directory org-roam-directory))
(with-temp-buffer
(let ((org-roam-directory ,current-org-roam-directory))
(let ((org-roam-directory ,current-org-roam-directory)
(org-mode-hook nil))
(org-mode)
(when ,file
(insert-file-contents ,file)
(setq-local org-roam-file-name ,file))
@ -68,6 +71,12 @@ to look.
(when org-roam-verbose
(apply #'message `(,(concat "(org-roam) " format-string) ,@args))))
(defun org-roam-string-quote (str)
"Quote STR."
(->> str
(s-replace "\\" "\\\\")
(s-replace "\"" "\\\"")))
(provide 'org-roam-macs)
;;; org-roam-macs.el ends here

View File

@ -4,7 +4,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.2.0
;; Version: 1.2.1
;; Package-Requires: ((emacs "26.1") (org "9.3"))
;; This file is NOT part of GNU Emacs.
@ -56,7 +56,7 @@ It opens or creates a note with the given ref.
(unless (assoc 'ref decoded-alist)
(error "No ref key provided"))
(when-let ((title (cdr (assoc 'title decoded-alist))))
(push (cons 'slug (org-roam--title-to-slug title)) decoded-alist))
(push (cons 'slug (funcall org-roam-title-to-slug-function title)) decoded-alist))
(let* ((org-roam-capture-templates org-roam-capture-ref-templates)
(org-roam-capture--context 'ref)
(org-roam-capture--info decoded-alist)
@ -78,7 +78,7 @@ It should contain the FILE key, pointing to the path of the file to open.
org-protocol://roam-file?file=/path/to/file.org"
(when-let ((file (plist-get info :file)))
(raise-frame)
(find-file file))
(org-roam--find-file file))
nil)
(push '("org-roam-ref" :protocol "roam-ref" :function org-roam-protocol-open-ref)

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.2.0
;; Version: 1.2.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.0"))
;; This file is NOT part of GNU Emacs.
@ -70,14 +70,13 @@
(defvar org-id-link-to-org-use-id)
(declare-function org-id-find-id-in-file "ext:org-id" (id file &optional markerp))
;;;; Customizable variables
(defgroup org-roam nil
"Roam Research replica in Org-mode."
:group 'org
:prefix "org-roam-"
:link '(url-link :tag "Github" "https://github.com/org-roam/org-roam")
:link '(url-link :tag "Online Manual" "https://org-roam.github.io/org-roam/manual/"))
:link '(url-link :tag "Online Manual" "https://www.orgroam.com/manual/"))
(defgroup org-roam-faces nil
"Faces used by Org-roam."
@ -104,6 +103,12 @@ ensure that."
:type '(repeat string)
:group 'org-roam)
(defcustom org-roam-find-file-function nil
"Function called when visiting files in Org-roam commands.
If nil, `find-file' is used."
:type 'function
:group 'org-roam)
(defcustom org-roam-include-type-in-ref-path-completions nil
"When t, include the type in ref-path completions.
Note that this only affects interactive calls.
@ -197,10 +202,22 @@ extraction methods:
`last-directory'
Extract the last directory relative to `org-roam-directory'.
That is, if a file is located at relative path foo/bar/file.org,
the file will have tag \"bar\"."
:type '(set (const :tag "#+roam_tags" PROP)
the file will have tag \"bar\".
`first-directory'
Extract the first directory relative to `org-roam-directory'.
That is, if a file is located at relative path foo/bar/file.org,
the file will have tag \"foo\"."
:type '(set (const :tag "#+roam_tags" prop)
(const :tag "sub-directories" all-directories)
(const :tag "parent directory" last-directory)))
(const :tag "parent directory" last-directory)
(const :tag "first sub-directory" first-directory)))
(defcustom org-roam-title-to-slug-function #'org-roam--title-to-slug
"Function to be used in converting a title to the filename slug.
Function should return a filename string based on title."
:type 'function
:group 'org-roam)
(defcustom org-roam-title-sources '((title headline) alias)
"The list of sources from which to retrieve a note title.
@ -489,10 +506,46 @@ PATH should be the root from which to compute the relativity."
(when (f-relative-p link)
(delete-region (match-beginning 1)
(match-end 1))
(insert (expand-file-name
(concat dir link)))))
(insert (expand-file-name link dir))))
(buffer-string))))
(defun org-roam--get-outline-path ()
"Return the outline path to the current entry.
An outline path is a list of ancestors for current headline, as a
list of strings. Statistics cookies are removed and links are
kept.
When optional argument WITH-SELF is non-nil, the path also
includes the current headline."
(org-with-wide-buffer
(save-match-data
(and (or (condition-case nil
(org-back-to-heading t)
(error nil))
(org-up-heading-safe))
(reverse (org-roam--get-outline-path-1))))))
(defun org-roam--get-outline-path-1 ()
"Return outline path to current headline.
Outline path is a list of strings, in reverse order. See
`org-roam--get-outline-path' for details.
Assume buffer is widened and point is on a headline."
(when org-complex-heading-regexp
(let ((heading (let ((case-fold-search nil))
(looking-at org-complex-heading-regexp)
(if (not (match-end 4)) ""
;; Remove statistics cookies.
(org-trim
(replace-regexp-in-string
"\\[[0-9]+%\\]\\|\\[[0-9]+/[0-9]+\\]" ""
(match-string-no-properties 4)))))))
(if (org-up-heading-safe)
(cons heading (org-roam--get-outline-path-1))
(list heading)))))
(defun org-roam--extract-links (&optional file-path)
"Extracts all link items within the current buffer.
Link items are of the form:
@ -503,6 +556,7 @@ This is the format that emacsql expects when inserting into the database.
FILE-FROM is typically the buffer file path, but this may not exist, for example
in temp buffers. In cases where this occurs, we do know the file path, and pass
it as FILE-PATH."
(require 'org-ref nil t)
(let ((file-path (or file-path
(file-truename (buffer-file-name))))
links)
@ -510,47 +564,45 @@ it as FILE-PATH."
(lambda (link)
(let* ((type (org-element-property :type link))
(path (org-element-property :path link))
(start (org-element-property :begin link))
(id-data (org-roam-id-find path))
(link-type (cond ((and (string= type "file")
(org-roam--org-file-p path))
"file")
((and (string= type "id")
id-data)
"id")
((and
(require 'org-ref nil t)
(-contains? org-ref-cite-types type))
"cite")
(t nil))))
(when link-type
(goto-char start)
(let* ((element (org-element-at-point))
(begin (or (org-element-property :content-begin element)
(org-element-property :begin element)))
(content (or (org-element-property :raw-value element)
(buffer-substring-no-properties
begin
(or (org-element-property :content-end element)
(org-element-property :end element)))))
(content (string-trim content))
;; Expand all relative links to absolute links
(content (org-roam--expand-links content file-path)))
(let ((context (list :content content :point begin))
(names (pcase link-type
("file"
(list (file-truename (expand-file-name path (file-name-directory file-path)))))
("id"
(list (car id-data)))
("cite"
(org-ref-split-and-strip-string path)))))
(seq-do (lambda (name)
(start (org-element-property :begin link)))
(goto-char start)
(let* ((element (org-element-at-point))
(begin (or (org-element-property :content-begin element)
(org-element-property :begin element)))
(content (or (org-element-property :raw-value element)
(buffer-substring-no-properties
begin
(or (org-element-property :content-end element)
(org-element-property :end element)))))
(content (string-trim content))
;; Expand all relative links to absolute links
(content (org-roam--expand-links content file-path)))
(let ((properties (list :outline (mapcar (lambda (path)
(org-roam--expand-links path file-path))
(org-roam--get-outline-path))
:content content
:point begin))
(names (pcase type
("file"
(if (file-remote-p path)
(list path)
(list (file-truename (expand-file-name path (file-name-directory file-path))))))
("id"
(list (car (org-roam-id-find path))))
((pred (lambda (typ)
(and (boundp 'org-ref-cite-types)
(-contains? org-ref-cite-types typ))))
(setq type "cite")
(org-ref-split-and-strip-string path))
(_ (list (org-element-property :raw-link link))))))
(seq-do (lambda (name)
(when name
(push (vector file-path
name
link-type
context)
links))
names)))))))
type
properties)
links)))
names))))))
links))
(defun org-roam--extract-headlines (&optional file-path)
@ -621,16 +673,24 @@ If NESTED, return the first successful result from SOURCES."
"Extract tags from using the directory path FILE.
All sub-directories relative to `org-roam-directory' are used as tags."
(when-let ((dir-relative (file-name-directory
(file-relative-name file org-roam-directory))))
(file-relative-name file (file-truename org-roam-directory)))))
(f-split dir-relative)))
(defun org-roam--extract-tags-last-directory (file)
"Extract tags from using the directory path FILE.
The final directory component is used as a tag."
(when-let ((dir-relative (file-name-directory
(file-relative-name file org-roam-directory))))
(file-relative-name file (file-truename org-roam-directory)))))
(last (f-split dir-relative))))
(defun org-roam--extract-tags-first-directory (file)
"Extract tags from path FILE.
The first directory component after `org-roam-directory' is used as a
tag."
(when-let ((dir-relative (file-name-directory
(file-relative-name file (file-truename org-roam-directory)))))
(list (car (f-split dir-relative)))))
(defun org-roam--extract-tags-prop (_file)
"Extract tags from the current buffer's \"#roam_tags\" global property."
(let* ((prop (cdr (assoc "ROAM_TAGS" (org-roam--extract-global-props '("ROAM_TAGS"))))))
@ -718,7 +778,7 @@ Examples:
(defun org-roam--get-title-or-slug (path)
"Convert `PATH' to the file title, if it exists. Else, return the path."
(or (car (org-roam-db--get-titles path))
(or (org-roam-db--get-titles path)
(org-roam--path-to-slug path)))
(defun org-roam--title-to-slug (title)
@ -761,7 +821,7 @@ Examples:
"Return an alist for completion.
The car is the displayed title for completion, and the cdr is the
to the file."
(let* ((rows (org-roam-db-query [:select [titles:file titles:titles tags:tags files:meta] :from titles
(let* ((rows (org-roam-db-query [:select [files:file titles:title tags:tags files:meta] :from titles
:left :join tags
:on (= titles:file tags:file)
:left :join files
@ -772,15 +832,13 @@ to the file."
#'time-less-p
rows)
(dolist (row rows completions)
(pcase-let ((`(,file-path ,titles ,tags) row))
(let ((titles (or titles (list (org-roam--path-to-slug file-path)))))
(dolist (title titles)
(let ((k (concat
(pcase-let ((`(,file-path ,title ,tags) row))
(let ((k (concat
(when tags
(format "(%s) " (s-join org-roam-tag-separator tags)))
title))
(v (list :path file-path :title title)))
(push (cons k v) completions))))))))
(push (cons k v) completions))))))
(defun org-roam--get-index-path ()
"Return the path to the index in `org-roam-directory'.
@ -800,43 +858,61 @@ whose title is 'Index'."
index)))
;;;; org-roam-find-ref
(defun org-roam--get-ref-path-completions (&optional interactive filter)
(defun org-roam--get-ref-path-completions (&optional arg filter)
"Return an alist of refs to absolute path of Org-roam files.
When `org-roam-include-type-in-ref-path-completions' and
INTERACTIVE are non-nil, format the car of the
completion-candidates as 'type:ref'.
When called interactively (i.e. when ARG is 1), formats the car
of the completion-candidates with extra information: title, tags,
and type \(when `org-roam-include-type-in-ref-path-completions'
is non-nil).
When called with a `C-u' prefix (i.e. when ARG is 4), forces the
default format without the formatting.
FILTER can either be a string or a function:
- If it is a string, it should be the type of refs to include as
candidates (e.g. \"cite\" ,\"website\" ,etc.)
candidates \(e.g. \"cite\", \"website\", etc.)
- If it is a function, it should be the name of a function that
takes three arguments: the type, the ref, and the file of the
current candidate. It should return t if that candidate is to be
included as a candidate."
(let ((rows (org-roam-db-query [:select [refs:type refs:ref refs:file ] :from refs
:left :join files
:on (= refs:file files:file)]))
(include-type (and interactive
org-roam-include-type-in-ref-path-completions))
takes three arguments: the type, the ref, and the file of the
current candidate. It should return t if that candidate is to
be included as a candidate."
(let ((rows (org-roam-db-query
[:select [refs:type refs:ref refs:file titles:title tags:tags]
:from titles
:left :join tags
:on (= titles:file tags:file)
:left :join refs :on (= titles:file refs:file)
:where refs:file :is :not :null]))
completions)
(seq-sort-by (lambda (x)
(plist-get (nth 3 x) :mtime))
#'time-less-p
rows)
(dolist (row rows completions)
(pcase-let ((`(,type ,ref ,file-path) row))
(pcase-let ((`(,type ,ref ,file-path ,title ,tags) row))
(when (pcase filter
('nil t)
((pred stringp) (string= type filter))
((pred functionp) (funcall filter type ref file-path))
(wrong-type (signal 'wrong-type-argument
`((stringp functionp)
,wrong-type))))
(let ((k (concat
(when include-type
(format "(%s) " type))
ref))
(v (list :path file-path :type type :ref ref)))
(push (cons k v) completions)))))))
('nil t)
((pred stringp) (string= type filter))
((pred functionp) (funcall filter type ref file-path))
(wrong-type (signal 'wrong-type-argument
`((stringp functionp)
,wrong-type))))
(let ((k (if (eq arg 1)
(concat
(when org-roam-include-type-in-ref-path-completions
(format "{%s} " type))
(when tags
(format "(%s) " (s-join org-roam-tag-separator tags)))
(format "%s (%s)" title ref))
ref))
(v (list :path file-path :type type :ref ref)))
(push (cons k v) completions)))))))
(defun org-roam--find-file (file)
"Open FILE using `org-roam-find-file-function' or `find-file'."
(funcall (or org-roam-find-file-function #'find-file) file))
(defun org-roam--find-ref (ref)
"Find and open and Org-roam file from REF if it exists.
@ -845,7 +921,7 @@ type-information (e.g. 'cite:').
Return nil if the file does not exist."
(when-let* ((completions (org-roam--get-ref-path-completions))
(file (plist-get (cdr (assoc ref completions)) :path)))
(find-file file)))
(org-roam--find-file file)))
(defun org-roam--get-roam-buffers ()
"Return a list of buffers that are Org-roam files."
@ -929,7 +1005,8 @@ Applies `org-roam-link-current' if PATH corresponds to the
currently opened Org-roam file in the backlink buffer, or
`org-roam-link-face' if PATH corresponds to any other Org-roam
file."
(cond ((not (file-exists-p path))
(cond ((and (not (file-remote-p path)) ;; Prevent lockups opening Tramp links
(not (file-exists-p path)))
'org-roam-link-invalid)
((and (org-roam--in-buffer-p)
(org-roam--backlink-to-current-p))
@ -969,27 +1046,19 @@ This function hooks into `org-open-at-point' via `org-open-at-point-functions'."
(when (and (eq (org-element-type context) 'link)
(string= "file" type)
(org-roam--org-roam-file-p (file-truename path)))
(org-roam--find-file path)
(org-roam-buffer--find-file path)
(org-show-context)
t)))
;; Org-roam preview text
((when-let ((file-from (get-text-property (point) 'file-from))
(p (get-text-property (point) 'file-from-point)))
(org-roam--find-file file-from)
(org-roam-buffer--find-file file-from)
(goto-char p)
(org-show-context)
t))
;; If called via `org-open-at-point', fall back to default behavior.
(t nil)))
(defun org-roam--find-file (file)
"Open FILE in the window `org-roam' was called from."
(if (and org-roam-last-window (window-valid-p org-roam-last-window))
(progn (with-selected-window org-roam-last-window
(find-file file))
(select-window org-roam-last-window))
(find-file file)))
(defun org-roam--get-backlinks (target)
"Return the backlinks for TARGET.
TARGET may be a file, for Org-roam file links, or a citation key,
@ -1164,13 +1233,15 @@ replaced links are made relative to the current buffer."
new-file-or-dir)))
(when (and (not (auto-save-file-name-p old-file))
(not (auto-save-file-name-p new-file))
(org-roam--org-roam-file-p new-file))
(not (backup-file-name-p old-file))
(not (backup-file-name-p new-file))
(org-roam--org-roam-file-p old-file))
(org-roam-db--ensure-built)
(let* ((old-path (file-truename old-file))
(new-path (file-truename new-file))
(old-slug (org-roam--get-title-or-slug old-file))
(old-desc (org-roam--format-link-title old-slug))
(new-slug (or (car (org-roam-db--get-titles old-path))
(new-slug (or (org-roam-db--get-titles old-path)
(org-roam--path-to-slug new-path)))
(new-desc (org-roam--format-link-title new-slug))
(new-buffer (or (find-buffer-visiting new-path)
@ -1201,7 +1272,8 @@ replaced links are made relative to the current buffer."
(with-current-buffer new-buffer
(org-roam--fix-relative-links old-path)
(save-buffer)))
(org-roam-db--update-file new-path)))))
(when (org-roam--org-roam-file-p new-file)
(org-roam-db--update-file new-path))))))
;;;###autoload
(define-minor-mode org-roam-mode
@ -1227,7 +1299,9 @@ Otherwise, behave as if called interactively."
:global t
(cond
(org-roam-mode
(unless (executable-find "sqlite3")
(unless (or (and (boundp 'emacsql-sqlite3-executable)
(file-executable-p emacsql-sqlite3-executable))
(executable-find "sqlite3"))
(lwarn '(org-roam) :error "Cannot find executable 'sqlite3'. \
Ensure it is installed and can be found within `exec-path'. \
M-x info for more information at Org-roam > Installation > Post-Installation Tasks."))
@ -1284,7 +1358,6 @@ which takes as its argument an alist of path-completions. See
`org-roam--get-title-path-completions' for details."
(interactive)
(unless org-roam-mode (org-roam-mode))
(when (org-roam-capture--in-process-p) (user-error "Org-roam capture in process"))
(let* ((completions (funcall (or filter-fn #'identity)
(or completions (org-roam--get-title-path-completions))))
(title-with-tags (org-roam-completion--completing-read "File: " completions
@ -1292,11 +1365,11 @@ which takes as its argument an alist of path-completions. See
(res (cdr (assoc title-with-tags completions)))
(file-path (plist-get res :path)))
(if file-path
(find-file file-path)
(org-roam--find-file file-path)
(let ((org-roam-capture--info `((title . ,title-with-tags)
(slug . ,(org-roam--title-to-slug title-with-tags))))
(slug . ,(funcall org-roam-title-to-slug-function title-with-tags))))
(org-roam-capture--context 'title))
(add-hook 'org-capture-after-finalize-hook #'org-roam-capture--find-file-h)
(setq org-roam-capture-additional-template-props (list :finalize 'find-file))
(org-roam--with-template-error 'org-roam-capture-templates
(org-roam-capture--capture))))))
@ -1304,7 +1377,7 @@ which takes as its argument an alist of path-completions. See
(defun org-roam-find-directory ()
"Find and open `org-roam-directory'."
(interactive)
(find-file org-roam-directory))
(org-roam--find-file org-roam-directory))
;;;###autoload
(defun org-roam-find-ref (arg &optional filter)
@ -1326,11 +1399,18 @@ included as a candidate."
:require-match t))
(file (-> (cdr (assoc ref completions))
(plist-get :path))))
(find-file file)))
(org-roam--find-file file)))
;;;###autoload
(defun org-roam-random-note ()
"Find a random Org-roam file."
(interactive)
(find-file (seq-random-elt (org-roam--list-all-files))))
;;;###autoload
(defun org-roam-insert (&optional lowercase completions filter-fn description)
"Find an Org-roam file, and insert a relative org link to it at point.
Return selected file if it exists.
If LOWERCASE, downcase the title before insertion.
COMPLETIONS is a list of completions to be used instead of
`org-roam--get-title-path-completions`.
@ -1366,17 +1446,40 @@ If DESCRIPTION is provided, use this as the link label. See
(when region ;; Remove previously selected text.
(delete-region (car region) (cdr region)))
(insert (org-roam--format-link target-file-path link-description)))
(when (org-roam-capture--in-process-p)
(user-error "Nested Org-roam capture processes not supported"))
(let ((org-roam-capture--info `((title . ,title-with-tags)
(slug . ,(org-roam--title-to-slug title-with-tags))))
(slug . ,(funcall org-roam-title-to-slug-function title-with-tags))))
(org-roam-capture--context 'title))
(add-hook 'org-capture-after-finalize-hook #'org-roam-capture--insert-link-h)
(setq org-roam-capture-additional-template-props (list :region region
:insert-at (point-marker)
:link-description link-description
:capture-fn 'org-roam-insert))
:finalize 'insert-link))
(org-roam--with-template-error 'org-roam-capture-templates
(org-roam-capture--capture))))))
(org-roam-capture--capture))))
res))
;;;###autoload
(defun org-roam-insert-immediate (arg &rest args)
"Find an Org-roam file, and insert a relative org link to it at point.
This variant of `org-roam-insert' inserts the link immediately by
using the template in `org-roam-capture-immediate-template'. The
interactive ARG and ARGS are passed to `org-roam-insert'.
See `org-roam-insert' for details."
(interactive "P")
(let ((args (push arg args))
(org-roam-capture-templates (list org-roam-capture-immediate-template)))
(apply #'org-roam-insert args)))
;;;###autoload
(defun org-roam-find-file-immediate (arg &rest args)
"Find and open an Org-roam file.
This variant of `org-roam-find-file' uses the template in
`org-roam-capture-immediate-template', avoiding the capture
process. The interactive ARG and ARGS are passed to
`org-roam-find-file'. See `org-roam-find-file' for details."
(interactive "P")
(let ((args (push arg args))
(org-roam-capture-templates (list org-roam-capture-immediate-template)))
(apply #'org-roam-find-file args)))
;;;###autoload
(defun org-roam-jump-to-index ()
@ -1390,7 +1493,7 @@ command will offer you to create one."
(let ((index (org-roam--get-index-path)))
(if (and index
(file-exists-p index))
(find-file index)
(org-roam--find-file index)
(when (y-or-n-p "Index file does not exist. Would you like to create it? ")
(org-roam-find-file "Index")))))
@ -1438,7 +1541,7 @@ linked, lest the network graph get too crowded."
(unless (org-roam--org-roam-file-p)
(user-error "Not in org-roam file"))
(if (not (executable-find "rg"))
(user-error "Cannot find the \"rg\" executable, aborting")
(error "Cannot find the ripgrep executable \"rg\". Check that it is installed and available on `exec-path'")
(let* ((titles (org-roam--extract-titles))
(rg-command (concat "rg -o --vimgrep -P -i "
(string-join (mapcar (lambda (glob) (concat "-g " glob))
@ -1451,7 +1554,7 @@ linked, lest the network graph get too crowded."
(file-loc (buffer-file-name))
(buf (get-buffer-create "*org-roam unlinked references*"))
(results (split-string (shell-command-to-string rg-command) "\n"))
(result-regex (rx (group (one-or-more anychar))
(result-regex (rx (group (one-or-more anything))
":"
(group (one-or-more digit))
":"

View File

@ -0,0 +1,14 @@
#+TITLE: Headline
* Headline 1
:PROPERTIES:
:ID: e84d0630-efad-4017-9059-5ef917908823
:END:
* No headline here
Oops.
* Headline 2
:PROPERTIES:
:ID: 801b58eb-97e2-435f-a33e-ff59a2f0c213
:END:

View File

@ -0,0 +1,53 @@
;;; test-org-roam-perf.el --- Performance Tests for Org-roam -*- lexical-binding: t; -*-
;; Copyright (C) 2020 Jethro Kuan
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; Package-Requires: ((buttercup))
;; 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.
;; 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.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'buttercup)
(require 'org-roam)
(defconst test-org-roam-perf-zip-url "https://github.com/org-roam/test-org-files/archive/master.zip"
"Path to zip for test org-roam files.")
(defun test-org-roam-perf--abs-path (file-path)
"Get absolute FILE-PATH from `org-roam-directory'."
(file-truename (expand-file-name file-path org-roam-directory)))
(defun test-org-roam-perf--init ()
"."
(let* ((temp-loc (expand-file-name (make-temp-name "test-org-files-") temporary-file-directory))
(zip-file-loc (concat temp-loc ".zip"))
(_ (url-copy-file test-org-roam-perf-zip-url zip-file-loc))
(_ (shell-command (format "mkdir -p %s && unzip -j -qq %s -d %s" temp-loc zip-file-loc temp-loc))))
(setq org-roam-directory temp-loc)))
(describe "Cache Build"
(it "cache build from scratch time to be acceptable"
(test-org-roam-perf--init)
(pcase (benchmark-run 1 (org-roam-db-build-cache t))
(`(,time ,gcs ,time-in-gc)
(message "Elapsed time: %fs (%fs in %d GCs)" time time-in-gc gcs)
(expect time :to-be-less-than 100))))
(it "builds quickly without change"
(pcase (benchmark-run 1 (org-roam-db-build-cache))
(`(,time ,gcs ,time-in-gc)
(message "Elapsed time: %fs (%fs in %d GCs)" time time-in-gc gcs)
(expect time :to-be-less-than 5)))))

View File

@ -3,7 +3,7 @@
;; Copyright (C) 2020 Jethro Kuan
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; Package-Requires: ((buttercup) (with-simulated-input))
;; Package-Requires: ((buttercup))
;; 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
@ -211,6 +211,20 @@
:to-equal
'("deeply")))
(it "extracts from first directory"
(expect (test #'org-roam--extract-tags-first-directory
"base.org")
:to-equal
nil)
(expect (test #'org-roam--extract-tags-first-directory
"tags/tag.org")
:to-equal
'("tags"))
(expect (test #'org-roam--extract-tags-first-directory
"nested/deeply/deeply_nested_file.org")
:to-equal
'("nested")))
(describe "uses org-roam-tag-sources correctly"
(it "'(prop)"
(expect (let ((org-roam-tag-sources '(prop)))
@ -225,6 +239,26 @@
:to-equal
'("t1" "t2 with space" "t3" "tags"))))))
(describe "Headline extraction"
(before-all
(test-org-roam--init))
(after-all
(test-org-roam--teardown))
(cl-flet
((test (fn file)
(let* ((fname (test-org-roam--abs-path file))
(buf (find-file-noselect fname)))
(with-current-buffer buf
(funcall fn fname)))))
(it "extracts headlines"
(expect (test #'org-roam--extract-headlines
"headlines/headline.org")
:to-equal
`(["e84d0630-efad-4017-9059-5ef917908823" ,(test-org-roam--abs-path "headlines/headline.org")]
["801b58eb-97e2-435f-a33e-ff59a2f0c213" ,(test-org-roam--abs-path "headlines/headline.org")])))))
;;; Tests
(xdescribe "org-roam-db-build-cache"
(before-each