Compare commits

...

81 Commits
v1.2.0 ... flow

Author SHA1 Message Date
c23bca69f8 (feat): Implement org-roam-flow 2020-08-04 15:23:00 +02:00
f206b5bbf9 (fix): skip unreadable files while building the cache (#995)
When using gpg encrypted files it might happen (intended) that on a
certain machine there is no key to decrypt that file.  Currently an
error of type 'file-error' will be raised and the cache building
process will be aborted.  So in a certain sense org-roam will not
be functional in that case.  Furthermore, when the the cache building
process is run from the emacs initialisation it will come to a halt as
well and the user will be left with a halfworking emacs instance.

This patch changes the behaviour to just skipping over offending files
while removing them from the db at the same time.  Here an offending
file is any file that cannot be read for indexing.  As it stands this
case only works reliably for the case of missing gpg keys.

An error buffer will still show up and be prominently displayed,
besides a log to the message buffer.

Implementation note: This io error will only be caught during the
first part of the cache rebuilding ("files and headlines").  If such
an error would occur during the second part ("rebuild the rest")
another (more severe) cause might be the problem and the user better
be informed the hard way (i.e. the same behaviour as before).
2020-08-03 16:58:22 +08:00
30fab7bcc4 (feat): Add toggle for custom-faces for Org-roam links (#997)
* org-roam.el (org-roam-link-use-custom-faces): New toggle
(org-roam--file-link-face): Refactor for new toggle
2020-08-02 18:27:38 +02:00
76d2e3f6b4 (feat): Simplify org-roam-store-link (#994)
* (feat): Simplify org-roam-store-link

Drop the wrapper, and refactor as an org-store-link function.
2020-08-01 08:02:57 +02:00
8881c9732b (fix) Change Emacs date to 1976 (#993)
cf. https://en.wikipedia.org/wiki/Emacs
2020-07-31 15:29:59 +02:00
0aa0a7c05a (doc): Add target audience section (#990) 2020-07-31 02:15:42 +08:00
ea4bfbb55d (fix): fix face-related functionality (#988)
- Ensure `org-roam-store-link-file` has no effect if org-roam-mode is
disabled
- Remove custom styling for ID links when org-roam-mode is disabled
2020-07-30 09:56:13 +08:00
0443351800 (internal): move faces into own file (#987) 2020-07-30 09:12:56 +08:00
6345d0c22e (feat): Protect region targeted by org-roam-insert (#974)
Co-authored-by: Jethro Kuan <jethrokuan95@gmail.com>
2020-07-27 20:16:39 +02:00
f2c1500beb (doc): Fix docstring (#979) 2020-07-27 11:43:23 +02:00
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
25 changed files with 2363 additions and 801 deletions

View File

@ -3,4 +3,4 @@
((emacs-lisp-mode ((emacs-lisp-mode
(eval . (require 'org-roam-dev)) (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! Many thanks to the following backers, your contributions are greatly appreciated!
- Nathan Tran - Nathan Tran
- Burke Libbey
- forkrul
- Andreas Stuhlmüller

View File

@ -1,5 +1,47 @@
# Changelog # Changelog
## 1.2.2 (TBD)
### Breaking Changes
### Features
- [#974](https://github.com/org-roam/org-roam/pull/974) Protect region targeted by `org-roam-insert`
- [#994](https://github.com/org-roam/org-roam/pull/994) Simplify org-roam-store-link
### Bugfixes
## 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) ## 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. 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 +74,7 @@ We also add `org-roam-unlinked-references`, which naively finds text that could
In this release, we added two new features: 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. 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. As usual, this release comes with a multitude of bug-fixes and refactorings.

View File

@ -4,6 +4,11 @@
## Synopsis ## Synopsis
> **NOTE:** Org-roam builds upon Emacs and Org-mode, both of which are intricate
> tools that require time investment for mastery. This makes Org-roam less
> friendly for beginners, but extremely powerful for those familiar with the
> ecosystem, or willing to invest effort in it.
Org-roam is a [Roam][roamresearch] replica built on top of the Org-roam is a [Roam][roamresearch] replica built on top of the
all-powerful [Org-mode][org]. all-powerful [Org-mode][org].
@ -50,6 +55,7 @@ Here's a sample configuration with using `use-package`:
```emacs-lisp ```emacs-lisp
(use-package org-roam (use-package org-roam
:ensure t
:hook :hook
(after-init . org-roam-mode) (after-init . org-roam-mode)
:custom :custom
@ -57,9 +63,10 @@ Here's a sample configuration with using `use-package`:
:bind (:map org-roam-mode-map :bind (:map org-roam-mode-map
(("C-c n l" . org-roam) (("C-c n l" . org-roam)
("C-c n f" . org-roam-find-file) ("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 :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 `org-roam-graph` by default expects to find the `dot` executable
@ -72,6 +79,25 @@ For more detailed installation and configuration instructions (including for
Doom and Spacemacs users), please see [the Doom and Spacemacs users), please see [the
documentation][docs]. 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 ## Knowledge Bases using Org-roam
- [Jethro Kuan](https://braindump.jethro.dev/) - [Jethro Kuan](https://braindump.jethro.dev/)
@ -95,6 +121,7 @@ General Public License, Version 3
[roamresearch]: https://www.roamresearch.com/ [roamresearch]: https://www.roamresearch.com/
[org]: https://orgmode.org/ [org]: https://orgmode.org/
[badge-license]: https://img.shields.io/badge/license-GPL_3-green.svg [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/ [discourse]: https://org-roam.discourse.group/
[slack]: https://join.slack.com/t/orgroam/shared_invite/zt-deoqamys-043YQ~s5Tay3iJ5QRI~Lxg [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. You can find us on Discourse and Slack.
<ul> <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>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> <li>Chat with us on <a href="https://join.slack.com/t/orgroam/shared_invite/zt-deoqamys-043YQ~s5Tay3iJ5QRI~Lxg">Slack</a></li>
</ul> </ul>

View File

@ -8,13 +8,13 @@
#+texinfo_dir_category: Emacs #+texinfo_dir_category: Emacs
#+texinfo_dir_title: Org-roam: (org-roam). #+texinfo_dir_title: Org-roam: (org-roam).
#+texinfo_dir_desc: Rudimentary Roam Replica for Emacs. #+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 #+options: H:4 num:3 toc:2 creator:t
#+property: header-args :eval never #+property: header-args :eval never
#+texinfo: @noindent #+texinfo: @noindent
This manual is for Org-roam version 1.2.0. This manual is for Org-roam version 1.2.1.
#+BEGIN_QUOTE #+BEGIN_QUOTE
Copyright (C) 2020-2020 Jethro Kuan <jethrokuan95@gmail.com> Copyright (C) 2020-2020 Jethro Kuan <jethrokuan95@gmail.com>
@ -56,6 +56,38 @@ Org-roam provides several benefits over other tooling:
- Leverages the Org-mode ecosystem :: Over the years, Emacs and Org-mode has developed into a mature system for plain-text organization. Building upon Org-mode already puts Org-roam light-years ahead of many other solutions. - Leverages the Org-mode ecosystem :: Over the years, Emacs and Org-mode has developed into a mature system for plain-text organization. Building upon Org-mode already puts Org-roam light-years ahead of many other solutions.
- Built on Emacs :: Emacs is also a fantastic interface for editing text, and we can inherit many of the powerful text-navigation and editing packages available to Emacs. - Built on Emacs :: Emacs is also a fantastic interface for editing text, and we can inherit many of the powerful text-navigation and editing packages available to Emacs.
* Target Audience
Org-roam is a tool that will appear unfriendly to anyone unfamiliar with Emacs
and Org-mode, but is also extremely powerful to those willing to put effort in
mastering the intricacies of the tools. Org-roam stands on the shoulders on
giants. Emacs was first created in 1976, and remains a top tier tool for editing
text and designing textual interfaces. The malleability of Emacs allowed the
creation of Org-mode, an all-purpose plain-text system for maintaining TODO
lists, planning projects, and authoring documents. Both of these tools are
incredibly vast and require significant time investment to master.
Org-roam assumes basic familiarity with these tools. It is not difficult to get
up and running with basic text-editing functionality, but one will only fully
appreciate the power of building Roam functionality into Emacs and Org-mode when
the usage of these tools become more advanced.
One key advantage to Org-roam is that building on top of Emacs gives it
malleability. This is especially important for note-taking workflows. It is our
belief that note-taking workflows are extremely personal, and there is no one
tool that's perfect for you. Org-mode and Org-roam allows you to discover what
works for you, and build that perfect tool for yourself.
If you are new to the software, and choose to take this leap of faith, I hope
you find yourself equally entranced as Neal Stephenson was.
#+BEGIN_QUOTE
Emacs outshines all other editing software in approximately the same way that
the noonday sun does the stars. It is not just bigger and brighter; it simply
makes everything else vanish. Neal Stephenson, In the Beginning was the
Command Line (1998)
#+END_QUOTE
* A Brief Introduction to the Zettelkasten Method * A Brief Introduction to the Zettelkasten Method
Org-roam provides utilities for maintaining a digital slip-box. This section Org-roam provides utilities for maintaining a digital slip-box. This section
@ -152,6 +184,17 @@ using:
Now see [[*Post-Installation Tasks][Post-Installation Tasks]]. 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 ** TODO Installing from the Git Repository
** Post-Installation Tasks ** Post-Installation Tasks
@ -185,9 +228,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 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]]. 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 To first start using Org-roam, one needs to pick a location to store the
containing your notes. For this tutorial, create an empty directory, and set the Org-roam files. The directory that will contain your notes, and database index
=org-roam-directory=: 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 #+BEGIN_SRC emacs-lisp
(make-directory "~/org-roam") (make-directory "~/org-roam")
@ -196,34 +241,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 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 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 Instead of relying on the file hierarchy for any form of categorization, we
solely rely on links between files to establish connections between notes. 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 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 #+BEGIN_SRC emacs-lisp
(add-hook 'after-init-hook 'org-roam-mode) (add-hook 'after-init-hook 'org-roam-mode)
#+END_SRC #+END_SRC
To build the cache manually, one can run =M-x org-roam-db-build-cache=. The cache To build the cache manually, one can run ~M-x org-roam-db-build-cache~. The
is a sqlite database named =org-roam.db=, which defaults to residing in the root cache is a sqlite database named ~org-roam.db~, which defaults to residing in
=org-roam-directory=. Cache builds may take a while the first time, but is often the root ~org-roam-directory~. Cache builds may take a while the first time, but
instantaneous in subsequent runs. is often instantaneous in subsequent runs.
Let us now create our first note. Call =M-x org-roam-find-file=. This shows a list 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 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 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 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 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 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 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 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. 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 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 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 a new note with that title. Good usage of Org-roam requires liberally linking
@ -232,7 +277,7 @@ notes.
Org-roam provides an interface to view backlinks. It shows backlinks for the 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 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 For a visual representation of the notes and their connections, Org-roam also
provides graphing capabilities, using Graphviz. It generates graphs with notes provides graphing capabilities, using Graphviz. It generates graphs with notes
@ -255,13 +300,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 like "World War 2", it may be desirable to also refer to it using the acronym
"WWII". "WWII".
Org-roam calls =org-roam--extract-titles= to extract titles. It uses 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 variable ~org-roam-title-sources~, to control how the titles are extracted. The
title extraction methods supported are: title extraction methods supported are:
1. ='title=: This extracts the title using the file =#+title= 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 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. 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 The aliases are space-delimited, and can be multi-worded using quotes
Take for example the following org file: Take for example the following org file:
@ -275,19 +320,19 @@ Take for example the following org file:
| Method | Titles | | Method | Titles |
|-------------+--------------------------| |-------------+--------------------------|
| ='title= | '("World War 2") | | ~'title~ | '("World War 2") |
| ='headline= | '("Headline") | | ~'headline~ | '("Headline") |
| ='alias= | '("WWII" "World War II") | | ~'alias~ | '("WWII" "World War II") |
One can freely control which extraction methods to use by customizing 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 information. If all methods of title extraction return no results, the file-name
is used in place of the titles for completions. is used in place of the titles for completions.
If you wish to add your own title extraction method, you may push a symbol If you wish to add your own title extraction method, you may push a symbol
='foo= into =org-roam-title-sources=, and define a ~'foo~ into ~org-roam-title-sources~, and define a
=org-roam--extract-titles-foo= which accepts no arguments. See ~org-roam--extract-titles-foo~ which accepts no arguments. See
=org-roam--extract-titles-title= for an example. ~org-roam--extract-titles-title~ for an example.
** Tags ** Tags
@ -295,29 +340,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 where titles are insufficient. For example, tags allow for categorization of
notes: differentiating between bibliographical and structure notes during interactive commands. 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 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 variable ~org-roam-tag-sources~, to control how tags are extracted. The tag
extraction methods supported are: 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. 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 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 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=. ~foo/bar/file.org~, the file will have tags ~foo~ and ~bar~.
3. ='last-directory=: Extracts the last directory relative to 3. ~'last-directory~: Extracts the last directory relative to
=org-roam-directory= as the tag. That is, if a file is located at relative ~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=. 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 By default, only the ~'prop~ extraction method is enabled. To enable the other
extraction methods, you may modify =org-roam-tag-sources=: extraction methods, you may modify ~org-roam-tag-sources~:
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(setq org-roam-tag-sources '(prop last-directory)) (setq org-roam-tag-sources '(prop last-directory))
#+END_SRC #+END_SRC
If you wish to add your own tag extraction method, you may push a symbol ='foo= 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 into ~org-roam-tag-sources~, and define a ~org-roam--extract-tags-foo~ which
accepts the absolute file path as its argument. See 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 ** File Refs
@ -330,7 +378,7 @@ For example, a note for a website may contain a ref:
#+END_SRC #+END_SRC
These keys come in useful for when taking website notes, using the 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 Alternatively, add a ref for notes for a specific paper, using its
[[https://github.com/jkitchin/org-ref][org-ref]] citation key: [[https://github.com/jkitchin/org-ref][org-ref]] citation key:
@ -346,8 +394,9 @@ The backlinks buffer will show any cites of this key: e.g.
[[file:images/org-ref-citelink.png]] [[file:images/org-ref-citelink.png]]
* The Templating System * The Templating System
Rather than creating blank files on =org-roam-insert= and =org-roam-find-file=, it Rather than creating blank files on ~org-roam-insert~ and ~org-roam-find-file~,
may be desirable to prefill the file with templated content. This may include: it may be desirable to prefill the file with templated content. This may
include:
- Time of creation - Time of creation
- File it was created from - File it was created from
@ -355,15 +404,17 @@ may be desirable to prefill the file with templated content. This may include:
- Any other data you may want to input manually - Any other data you may want to input manually
This requires a complex template insertion system. Fortunately, Org ships with a 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. powerful one: ~org-capture~ (see info:org#capture). However, org-capture was not
Org-roam abuses =org-capture=, extending its syntax. To first understand how designed for such use. Org-roam abuses ~org-capture~, extending its syntax and
org-roam's templating system works, it may be useful to look into basic usage of capabilities. To first understand how org-roam's templating system works, it may
=org-capture=. be useful to look into basic usage of ~org-capture~.
Org-roam's templates can be customized by modifying the variable For these reasons, Org-roam capture templates are not compatible with regular
=org-roam-capture-templates=. Just like the base =org-capture= this variable can ~org-capture~. Hence, Org-roam's templates can be customized by instead
contain multiple templates, in which case you will be prompted on which one to modifying the variable ~org-roam-capture-templates~. Just like
use when capturing a new note. ~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 ** Template Walkthrough
@ -378,28 +429,28 @@ the default template, reproduced below.
:unnarrowed t) :unnarrowed t)
#+END_SRC #+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. org-roam automatically chooses this template for you.
2. The template is given a description of ="default"=. 2. The template is given a description of ~"default"~.
3. =plain= text is inserted. Other options include Org headings via 3. ~plain~ text is inserted. Other options include Org headings via
=entry=. ~entry~.
4. =(function org-roam--capture-get-point)= should not be changed. 4. ~(function org-roam--capture-get-point)~ should not be changed.
5. ="%?"= is the template inserted on each call to =org-roam-capture--capture=. 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 This template means don't insert any content, but place the cursor
here. 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 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 allows you to specify if you want the note to go into a subdirectory. For
example, the template =private/${slug}= will create notes in example, the template ~private/${slug}~ will create notes in
=/path/to/org-roam-directory/private=. ~/path/to/org-roam-directory/private~.
7. =:head= contains the initial template to be inserted (once only), at 7. ~:head~ contains the initial template to be inserted (once only), at
the beginning of the file. Here, the title global attribute is the beginning of the file. Here, the title global attribute is
inserted. 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. 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 ** Org-roam Template Expansion
@ -407,26 +458,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 allow prefilling of strings. We have seen a glimpse of this in [[*Template Walkthrough][Template
Walkthrough]]. Walkthrough]].
In org-roam templates, the =${var}= syntax allows for the expansion of In org-roam templates, the ~${var}~ syntax allows for the expansion of
variables, stored in =org-roam-capture--info=. For example, during variables, stored in ~org-roam-capture--info~. For example, during
=org-roam-insert=, the user is prompted for a title. Upon entering a ~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 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 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 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 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 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]]. 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: 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 ~"%<%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>= 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=. with timestamp: e.g. ~20200213032037-Foo~.
All of the flexibility afforded by Emacs and Org-mode are available. For 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 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. directly to provide its third argument to specify UTC.
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
@ -443,7 +494,7 @@ the Org-roam codebase manageable. However, we attempt to accommodate as
many usage styles as possible. many usage styles as possible.
All of Org-roam's customization options can be viewed via 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 ** Directories and Files
@ -468,39 +519,39 @@ The Org-roam buffer displays backlinks for the currently active Org-roam note.
- User Option: org-roam-buffer - 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 - User Option: org-roam-buffer-position
The position of the Org-roam buffer side window. Valid values are ='left=, The position of the Org-roam buffer side window. Valid values are ~'left~,
='right=, ='top=, ='bottom=. ~'right~, ~'top~, ~'bottom~.
- User Option: org-roam-buffer-width - User Option: org-roam-buffer-width
Width of =org-roam-buffer=. Has an effect only if =org-roam-buffer-position= is Width of ~org-roam-buffer~. Has an effect only if ~org-roam-buffer-position~ is
='left= or ='right=. ~'left~ or ~'right~.
- User Option: org-roam-buffer-height - User Option: org-roam-buffer-height
Height of =org-roam-buffer=. Has an effect only if =org-roam-buffer-position= is Height of ~org-roam-buffer~. Has an effect only if ~org-roam-buffer-position~ is
='top= or ='bottom=. ~'top~ or ~'bottom~.
- User Option: org-roam-buffer-no-delete-other-windows - 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
Org-roam links are regular =file:= links in Org-mode. By default, links are 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=. inserted with the title as the link description with ~org-roam-insert~.
- User Option: org-roam-link-title-format - User Option: org-roam-link-title-format
To distinguish between org-roam links and regular links, one may choose to use 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, 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. by customizing the ~org-roam-link~, and ~org-roam-link-current~ faces.
** Org-roam Files ** Org-roam Files
@ -515,27 +566,27 @@ As your collection grows, you might want to create an index where you keep links
to your main files. to your main files.
In Org-roam, you can define the path to your index file by setting 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 - Variable: org-roam-index-file
Path to the 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 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. 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 Otherwise, the index is assumed to be a note in ~org-roam-index~ whose
title is ="Index"=. title is ~"Index"~.
- Function: org-roam-find-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 * Encryption
One may wish to keep private, encrypted files. Org-roam supports encryption (via 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= 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 to ~t~. When enabled, new files are created with the ~.org.gpg~ extension and
decryption are handled automatically by EasyPG. decryption are handled automatically by EasyPG.
Note that Emacs will prompt for a password for encrypted files during Note that Emacs will prompt for a password for encrypted files during
@ -551,7 +602,7 @@ Org-roam provides graphing capabilities to explore interconnections between
notes. This is done by performing SQL queries and generating images using 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]]. [[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 - Function: org-roam-graph & optional arg file node-query
@ -559,18 +610,18 @@ The entry point to graph creation is =org-roam-graph=.
If FILE is nil, default to current buffers file name. If FILE is nil, default to current buffers file name.
ARG may be any of the following values: ARG may be any of the following values:
- =nil= show the graph. - ~nil~ show the graph.
- =C-u= show the graph for FILE. - ~C-u~ show the graph for FILE.
- =C-u N= show the graph for FILE limiting nodes to N steps. - ~C-u N~ show the graph for FILE limiting nodes to N steps.
- =C-u C-u= build the graph. - ~C-u C-u~ build the graph.
- =C-u -= build the graph for FILE. - ~C-u -~ build the graph for FILE.
- =C-u -N= build the graph for FILE limiting nodes to N steps. - ~C-u -N~ build the graph for FILE limiting nodes to N steps.
- User Option: org-roam-graph-executable - 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. 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. compact graph layout.
- User Option: org-roam-graph-viewer - User Option: org-roam-graph-viewer
@ -580,7 +631,16 @@ The entry point to graph creation is =org-roam-graph=.
1. A string, which is a path to the program used 1. A string, which is a path to the program used
2. a function accepting a single argument: the graph file path. 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 ** Graph Options
@ -589,22 +649,22 @@ Graphviz provides many options for customizing the graph output, and Org-roam su
- User Option: org-roam-graph-extra-config - User Option: org-roam-graph-extra-config
Extra options passed to graphviz for the digraph (The "G" attributes). 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 - User Option: org-roam-graph-node-extra-config
Extra options for nodes in the graphviz output (The "N" attributes). 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 - User Option: org-roam-graph-edge-extra-config
Extra options for edges in the graphviz output (The "E" attributes). 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 - User Option: org-roam-graph-edge-cites-extra-config
Extra options for citation edges in the graphviz output. Extra options for citation edges in the graphviz output.
Example: ='(("color" . "red"))= Example: ~'(("color" . "red"))~
** Excluding Nodes and Edges ** Excluding Nodes and Edges
@ -628,26 +688,30 @@ This setting excludes all files whose path contain "private" or "dailies".
* Org-roam Completion System * Org-roam Completion System
Org-roam offers completion when choosing note titles etc. The completion Org-roam allows customization of which minibuffer completion system to use for
system is configurable. The default setting, its interactive commands. The default setting uses Emacs' standard
~completing-read~ mechanism.
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(setq org-roam-completion-system 'default) (setq org-roam-completion-system 'default)
#+END_SRC #+END_SRC
uses Emacs' standard =completing-read=. If you prefer If you have installed Helm or Ivy, and have their modes enabled, under the
[[https://emacs-helm.github.io/helm/][Helm]], use ~'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 #+BEGIN_SRC emacs-lisp
(setq org-roam-completion-system 'helm) (setq org-roam-completion-system 'helm)
#+END_SRC #+END_SRC
Other options include ='ido=, and ='ivy=. Other options include ~'ido~, and ~'ivy~.
* Roam Protocol * Roam Protocol
** _ :ignore: ** _ :ignore:
Org-roam extending =org-protocol= with 2 protocols: the =roam-file= Org-roam extending ~org-protocol~ with 2 protocols: the ~roam-file~
and =roam-ref= protocol. and ~roam-ref~ protocol.
** Installation ** Installation
@ -657,12 +721,12 @@ To enable Org-roam's protocol extensions, you have to add the following to your
(require 'org-roam-protocol) (require 'org-roam-protocol)
#+END_SRC #+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. 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 #+begin_example
[Desktop Entry] [Desktop Entry]
@ -674,7 +738,7 @@ Terminal=false
MimeType=x-scheme-handler/org-protocol MimeType=x-scheme-handler/org-protocol
#+end_example #+end_example
Associate =org-protocol://= links with the desktop application by Associate ~org-protocol://~ links with the desktop application by
running in your shell: running in your shell:
#+BEGIN_SRC bash #+BEGIN_SRC bash
@ -682,7 +746,7 @@ xdg-mime default org-protocol.desktop x-scheme-handler/org-protocol
#+END_SRC #+END_SRC
To disable the "confirm" prompt in Chrome, you can also make Chrome 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: without confirmation. To do this, run in a shell:
#+BEGIN_SRC bash #+BEGIN_SRC bash
@ -698,8 +762,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 and then restart Chrome (for example, by navigating to <chrome://restart>) to
make the new policy take effect. 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 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. [[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 For MacOS, one solution is to use [[https://github.com/sveinbjornt/Platypus][Platypus]]. Here are the instructions for
setting up with Platypus and Chrome: setting up with Platypus and Chrome:
@ -710,7 +774,7 @@ setting up with Platypus and Chrome:
brew cask install platypus brew cask install platypus
#+END_SRC #+END_SRC
2. Create a script =launch_emacs.sh=: 2. Create a script ~launch_emacs.sh~:
#+BEGIN_SRC bash #+BEGIN_SRC bash
#!/usr/bin/env bash #!/usr/bin/env bash
@ -731,7 +795,7 @@ brew cask install platypus
#+end_example #+end_example
Inside =Settings=: Inside ~Settings~:
#+begin_example #+begin_example
| Setting | Value | | Setting | Value |
@ -742,7 +806,7 @@ Inside =Settings=:
#+end_example #+end_example
To disable the "confirm" prompt in Chrome, you can also make Chrome 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: without confirmation. To do this, run in a shell:
#+BEGIN_SRC bash #+BEGIN_SRC bash
@ -751,7 +815,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 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: the default handler instead, run:
#+BEGIN_SRC bash #+BEGIN_SRC bash
@ -761,20 +825,41 @@ defaults write com.apple.LaunchServices/com.apple.launchservices.secure LSHandle
Then restart your computer. 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= [HKEY_CLASSES_ROOT\org-protocol]
key (e.g. =org-protocol://roam-file?file=/tmp/file.org=). This is used @="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. 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]]): 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]] [[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 #+BEGIN_SRC javascript
javascript:location.href = javascript:location.href =
@ -788,7 +873,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]]): [[https://github.com/qutebrowser/qutebrowser/blob/master/doc/help/configuring.asciidoc][Configuring qutebrowser]]):
#+BEGIN_SRC python #+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 #+END_SRC
where ~template~ is the template key for a template in where ~template~ is the template key for a template in
@ -801,15 +886,17 @@ should contain a ~#+roam_key: ${ref}~ in it.
Org-roam provides a utility for diagnosing and repairing problematic files via 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-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 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-doctor~, but note that this may take some time.
- Function: org-roam-doctor &optional this-buffer - Function: org-roam-doctor &optional this-buffer
Perform a check on Org-roam files to ensure cleanliness. If THIS-BUFFER, run Perform a check on Org-roam files to ensure cleanliness. If THIS-BUFFER, run
the check only for the current buffer. 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 #+BEGIN_SRC emacs-lisp
(make-org-roam-doctor-checker (make-org-roam-doctor-checker
@ -820,14 +907,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)))) ("R" . ("Replace link (keep label)" . org-roam-doctor--replace-link-keep-label))))
#+END_SRC #+END_SRC
The =:name= property is the name of the function run. The function takes in the 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 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 short description of what the checker does. ~:actions~ is an alist containing
elements of the form =(char . (prompt . function))=. These actions are defined per elements of the form ~(char . (prompt . function))~. These actions are defined per
checker, to perform autofixes for the errors. For each error detected, 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 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 * _ Copying
:PROPERTIES: :PROPERTIES:
:COPYING: t :COPYING: t
@ -862,15 +965,36 @@ General Public License for more details.
- Videos :: - Videos ::
- [[https://www.youtube.com/watch?v=RvWic15iXjk][How to Use Roam to Outline a New Article in Under 20 Minutes]] - [[https://www.youtube.com/watch?v=RvWic15iXjk][How to Use Roam to Outline a New Article in Under 20 Minutes]]
** Ecosystem ** 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: :PROPERTIES:
:CUSTOM_ID: deft :CUSTOM_ID: deft
:END: :END:
[[https://jblevins.org/projects/deft/][Deft]] provides a nice interface [[https://jblevins.org/projects/deft/][Deft]] provides a nice interface for browsing and filtering org-roam notes.
for browsing and filtering org-roam notes.
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(use-package deft (use-package deft
@ -885,7 +1009,7 @@ for browsing and filtering org-roam notes.
#+END_SRC #+END_SRC
If the title of the Org file is not the first line, you might not get 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 functionality. Here I'm using
[[https://github.com/raxod502/el-patch][el-patch]]: [[https://github.com/raxod502/el-patch][el-patch]]:
@ -925,7 +1049,7 @@ that uses an external search engine and indexer.
:END: :END:
[[https://github.com/bastibe/org-journal][Org-journal]] is a more [[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 provides better journaling capabilities, and a nice calendar interface
to see all dated entries. to see all dated entries.
@ -1004,8 +1128,8 @@ etc.) within Org-mode.
tight integration between tight integration between
[[https://github.com/jkitchin/org-ref][org-ref]], [[https://github.com/jkitchin/org-ref][org-ref]],
[[https://github.com/tmalsburg/helm-bibtex][helm-bibtex]] and [[https://github.com/tmalsburg/helm-bibtex][helm-bibtex]] and
=org-roam=. This helps you manage your bibliographic notes under ~org-roam~. This helps you manage your bibliographic notes under
=org-roam=. ~org-roam~.
**** Spaced Repetition **** Spaced Repetition
:PROPERTIES: :PROPERTIES:
@ -1019,11 +1143,11 @@ files. Other alternatives include [[https://orgmode.org/worg/org-contrib/org-dri
** How do I have more than one Org-roam directory? ** How do I have more than one Org-roam directory?
Emacs supports directory-local variables, allowing the value of Emacs supports directory-local variables, allowing the value of
=org-roam-directory= to be different in different directories. It does this by ~org-roam-directory~ to be different in different directories. It does this by
checking for a file named =.dir-locals.el=. checking for a file named ~.dir-locals.el~.
To add support for multiple directories, override the =org-roam-directory= To add support for multiple directories, override the ~org-roam-directory~
variable using directory-local variables. This is what =.dir-locals.el= may variable using directory-local variables. This is what ~.dir-locals.el~ may
contain: contain:
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
@ -1031,7 +1155,7 @@ contain:
#+END_SRC #+END_SRC
All files within that directory will be treated as their own separate 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. file within that directory, at least once.
** How do I migrate from Roam Research? ** How do I migrate from Roam Research?

View File

@ -31,7 +31,7 @@ General Public License for more details.
@finalout @finalout
@titlepage @titlepage
@title Org-roam User Manual @title Org-roam User Manual
@subtitle for version 1.2.0 @subtitle for version 1.2.1
@author Jethro Kuan @author Jethro Kuan
@page @page
@vskip 0pt plus 1filll @vskip 0pt plus 1filll
@ -46,7 +46,7 @@ General Public License for more details.
@noindent @noindent
This manual is for Org-roam version 1.2.0. This manual is for Org-roam version 1.2.1.
@quotation @quotation
Copyright (C) 2020-2020 Jethro Kuan <jethrokuan95@@gmail.com> Copyright (C) 2020-2020 Jethro Kuan <jethrokuan95@@gmail.com>
@ -65,6 +65,7 @@ General Public License for more details.
@menu @menu
* Introduction:: * Introduction::
* Target Audience::
* A Brief Introduction to the Zettelkasten Method:: * A Brief Introduction to the Zettelkasten Method::
* Installation:: * Installation::
* Getting Started:: * Getting Started::
@ -78,6 +79,7 @@ General Public License for more details.
* Roam Protocol:: * Roam Protocol::
* Daily Notes:: * Daily Notes::
* Diagnosing and Repairing Files:: * Diagnosing and Repairing Files::
* Performance Optimization::
* Appendix:: * Appendix::
* FAQ:: * FAQ::
@ -87,6 +89,7 @@ General Public License for more details.
Installation Installation
* Installing from MELPA:: * Installing from MELPA::
* Installing from Apt::
* Installing from the Git Repository:: * Installing from the Git Repository::
* Post-Installation Tasks:: * Post-Installation Tasks::
@ -121,8 +124,13 @@ Roam Protocol
* _:: * _::
* Installation: Installation (1). * Installation: Installation (1).
* The @samp{roam-file} protocol:: * The roam-file protocol::
* The @samp{roam-ref} Protocol:: * The roam-ref protocol::
Performance Optimization
* Profiling Key Operations::
* Garbage Collection::
Appendix Appendix
@ -131,7 +139,9 @@ Appendix
Ecosystem Ecosystem
* Deft:: * Browsing History with winner-mode::
* Versioning Notes::
* Full-text search interface with Deft::
* Org-journal:: * Org-journal::
* Note-taking Add-ons:: * Note-taking Add-ons::
@ -177,6 +187,40 @@ Over the years, Emacs and Org-mode has developed into a mature system for plain-
Emacs is also a fantastic interface for editing text, and we can inherit many of the powerful text-navigation and editing packages available to Emacs. Emacs is also a fantastic interface for editing text, and we can inherit many of the powerful text-navigation and editing packages available to Emacs.
@end table @end table
@node Target Audience
@chapter Target Audience
Org-roam is a tool that will appear unfriendly to anyone unfamiliar with Emacs
and Org-mode, but is also extremely powerful to those willing to put effort in
mastering the intricacies of the tools. Org-roam stands on the shoulders on
giants. Emacs was first created in 1976, and remains a top tier tool for editing
text and designing textual interfaces. The malleability of Emacs allowed the
creation of Org-mode, an all-purpose plain-text system for maintaining TODO
lists, planning projects, and authoring documents. Both of these tools are
incredibly vast and require significant time investment to master.
Org-roam assumes basic familiarity with these tools. It is not difficult to get
up and running with basic text-editing functionality, but one will only fully
appreciate the power of building Roam functionality into Emacs and Org-mode when
the usage of these tools become more advanced.
One key advantage to Org-roam is that building on top of Emacs gives it
malleability. This is especially important for note-taking workflows. It is our
belief that note-taking workflows are extremely personal, and there is no one
tool that's perfect for you. Org-mode and Org-roam allows you to discover what
works for you, and build that perfect tool for yourself.
If you are new to the software, and choose to take this leap of faith, I hope
you find yourself equally entranced as Neal Stephenson was.
@quotation
Emacs outshines all other editing software in approximately the same way that
the noonday sun does the stars. It is not just bigger and brighter; it simply
makes everything else vanish. Neal Stephenson, In the Beginning was the
Command Line (1998)
@end quotation
@node A Brief Introduction to the Zettelkasten Method @node A Brief Introduction to the Zettelkasten Method
@chapter A Brief Introduction to the Zettelkasten Method @chapter A Brief Introduction to the Zettelkasten Method
@ -231,6 +275,7 @@ development repository.
@menu @menu
* Installing from MELPA:: * Installing from MELPA::
* Installing from Apt::
* Installing from the Git Repository:: * Installing from the Git Repository::
* Post-Installation Tasks:: * Post-Installation Tasks::
@end menu @end menu
@ -288,6 +333,18 @@ M-x package-install RET org-roam RET
Now see @ref{Post-Installation Tasks}. 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 @node Installing from the Git Repository
@section @strong{TODO} Installing from the Git Repository @section @strong{TODO} Installing from the Git Repository
@ -324,9 +381,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 requires a radical change in your current note-taking workflow. To understand
more about the methods and madness, see @ref{Note-taking Workflows}. 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 To first start using Org-roam, one needs to pick a location to store the
containing your notes. For this tutorial, create an empty directory, and set the Org-roam files. The directory that will contain your notes, and database index
@samp{org-roam-directory}: 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 @lisp
(make-directory "~/org-roam") (make-directory "~/org-roam")
@ -335,34 +394,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 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 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 Instead of relying on the file hierarchy for any form of categorization, we
solely rely on links between files to establish connections between notes. 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 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 @lisp
(add-hook 'after-init-hook 'org-roam-mode) (add-hook 'after-init-hook 'org-roam-mode)
@end lisp @end lisp
To build the cache manually, one can run @samp{M-x org-roam-db-build-cache}. The cache To build the cache manually, one can run @code{M-x org-roam-db-build-cache}. The
is a sqlite database named @samp{org-roam.db}, which defaults to residing in the root cache is a sqlite database named @code{org-roam.db}, which defaults to residing in
@samp{org-roam-directory}. Cache builds may take a while the first time, but is often the root @code{org-roam-directory}. Cache builds may take a while the first time, but
instantaneous in subsequent runs. 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 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 @samp{org-roam-directory}. It should show nothing 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 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 note you wish to create, and pressing @code{RET} should begin the note creation
process. This process uses @samp{org-capture}'s templating system, and can be freely 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 @samp{C-c customized (see @ref{The Templating System}). Using the default template, pressing @code{C-c
C-c} finishes the note capture. Running @samp{M-x org-roam-find-file} again should show 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 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. 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 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 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 a new note with that title. Good usage of Org-roam requires liberally linking
@ -371,7 +430,7 @@ notes.
Org-roam provides an interface to view backlinks. It shows backlinks for the 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 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 For a visual representation of the notes and their connections, Org-roam also
provides graphing capabilities, using Graphviz. It generates graphs with notes provides graphing capabilities, using Graphviz. It generates graphs with notes
@ -402,17 +461,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 like ``World War 2'', it may be desirable to also refer to it using the acronym
``WWII''. ``WWII''.
Org-roam calls @samp{org-roam--extract-titles} to extract titles. It uses the Org-roam calls @code{org-roam--extract-titles} to extract titles. It uses the
variable @samp{org-roam-title-sources}, to control how the titles are extracted. The variable @code{org-roam-title-sources}, to control how the titles are extracted. The
title extraction methods supported are: title extraction methods supported are:
@enumerate @enumerate
@item @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 @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 @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 The aliases are space-delimited, and can be multi-worded using quotes
@end enumerate @end enumerate
@ -428,23 +487,23 @@ Take for example the following org file:
@multitable {aaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaa} @multitable {aaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaa}
@headitem Method @headitem Method
@tab Titles @tab Titles
@item @samp{'title} @item @code{'title}
@tab '(``World War 2'') @tab '(``World War 2'')
@item @samp{'headline} @item @code{'headline}
@tab '(``Headline'') @tab '(``Headline'')
@item @samp{'alias} @item @code{'alias}
@tab '(``WWII'' ``World War II'') @tab '(``WWII'' ``World War II'')
@end multitable @end multitable
One can freely control which extraction methods to use by customizing 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 information. If all methods of title extraction return no results, the file-name
is used in place of the titles for completions. is used in place of the titles for completions.
If you wish to add your own title extraction method, you may push a symbol 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 @code{'foo} into @code{org-roam-title-sources}, and define a
@samp{org-roam--extract-titles-foo} which accepts no arguments. See @code{org-roam--extract-titles-foo} which accepts no arguments. See
@samp{org-roam--extract-titles-title} for an example. @code{org-roam--extract-titles-title} for an example.
@node Tags @node Tags
@section Tags @section Tags
@ -453,34 +512,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 where titles are insufficient. For example, tags allow for categorization of
notes: differentiating between bibliographical and structure notes during interactive commands. 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 Org-roam calls @code{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 variable @code{org-roam-tag-sources}, to control how tags are extracted. The tag
extraction methods supported are: extraction methods supported are:
@enumerate @enumerate
@item @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 @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 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 @item
@samp{'last-directory}: Extracts the last directory relative to @code{'last-directory}: Extracts the last directory relative to
@samp{org-roam-directory} as the tag. That is, if a file is located at relative @code{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}. 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 @end enumerate
By default, only the @samp{'prop} extraction method is enabled. To enable the other By default, only the @code{'prop} extraction method is enabled. To enable the other
extraction methods, you may modify @samp{org-roam-tag-sources}: extraction methods, you may modify @code{org-roam-tag-sources}:
@lisp @lisp
(setq org-roam-tag-sources '(prop last-directory)) (setq org-roam-tag-sources '(prop last-directory))
@end lisp @end lisp
If you wish to add your own tag extraction method, you may push a symbol @samp{'foo} If you wish to add your own tag extraction method, you may push a symbol @code{'foo}
into @samp{org-roam-tag-sources}, and define a @samp{org-roam--extract-tags-foo} which 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 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 @node File Refs
@section File Refs @section File Refs
@ -494,7 +557,7 @@ For example, a note for a website may contain a ref:
@end example @end example
These keys come in useful for when taking website notes, using the 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 Alternatively, add a ref for notes for a specific paper, using its
@uref{https://github.com/jkitchin/org-ref, org-ref} citation key: @uref{https://github.com/jkitchin/org-ref, org-ref} citation key:
@ -514,8 +577,9 @@ The backlinks buffer will show any cites of this key: e.g.
@node The Templating System @node The Templating System
@chapter The Templating System @chapter The Templating System
Rather than creating blank files on @samp{org-roam-insert} and @samp{org-roam-find-file}, it Rather than creating blank files on @code{org-roam-insert} and @code{org-roam-find-file},
may be desirable to prefill the file with templated content. This may include: it may be desirable to prefill the file with templated content. This may
include:
@itemize @itemize
@item @item
@ -529,15 +593,17 @@ Any other data you may want to input manually
@end itemize @end itemize
This requires a complex template insertion system. Fortunately, Org ships with a 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. powerful one: @code{org-capture} (see @ref{capture,,,org,}). However, org-capture was not
Org-roam abuses @samp{org-capture}, extending its syntax. To first understand how designed for such use. Org-roam abuses @code{org-capture}, extending its syntax and
org-roam's templating system works, it may be useful to look into basic usage of capabilities. To first understand how org-roam's templating system works, it may
@samp{org-capture}. be useful to look into basic usage of @code{org-capture}.
Org-roam's templates can be customized by modifying the variable For these reasons, Org-roam capture templates are not compatible with regular
@samp{org-roam-capture-templates}. Just like the base @samp{org-capture} this variable can @code{org-capture}. Hence, Org-roam's templates can be customized by instead
contain multiple templates, in which case you will be prompted on which one to modifying the variable @code{org-roam-capture-templates}. Just like
use when capturing a new note. @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 @menu
* Template Walkthrough:: * Template Walkthrough::
@ -560,36 +626,36 @@ the default template, reproduced below.
@enumerate @enumerate
@item @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. org-roam automatically chooses this template for you.
@item @item
The template is given a description of @samp{"default"}. The template is given a description of @code{"default"}.
@item @item
@samp{plain} text is inserted. Other options include Org headings via @code{plain} text is inserted. Other options include Org headings via
@samp{entry}. @code{entry}.
@item @item
@samp{(function org-roam--capture-get-point)} should not be changed. @code{(function org-roam--capture-get-point)} should not be changed.
@item @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 This template means don't insert any content, but place the cursor
here. here.
@item @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 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 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 example, the template @code{private/$@{slug@}} will create notes in
@samp{/path/to/org-roam-directory/private}. @code{/path/to/org-roam-directory/private}.
@item @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 the beginning of the file. Here, the title global attribute is
inserted. inserted.
@item @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. file, rather than narrowing to just the entry.
@end enumerate @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 @node Org-roam Template Expansion
@section Org-roam Template Expansion @section Org-roam Template Expansion
@ -598,26 +664,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 allow prefilling of strings. We have seen a glimpse of this in @ref{Template Walkthrough, , Template
Walkthrough}. Walkthrough}.
In org-roam templates, the @samp{$@{var@}} syntax allows for the expansion of In org-roam templates, the @code{$@{var@}} syntax allows for the expansion of
variables, stored in @samp{org-roam-capture--info}. For example, during variables, stored in @code{org-roam-capture--info}. For example, during
@samp{org-roam-insert}, the user is prompted for a title. Upon entering a @code{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 non-existent title, the @code{title} key in @code{org-roam-capture--info} is set to the
provided title. @samp{$@{title@}} is then expanded into the provided title during 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 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 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 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}. 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: 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 @code{"%<%Y%m%d%H%M%S>-$@{title@}"}, with the title @code{"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>} 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. @samp{20200213032037-Foo}. with timestamp: e.g. @code{20200213032037-Foo}.
All of the flexibility afforded by Emacs and Org-mode are available. For 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 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@. directly to provide its third argument to specify UTC@.
@lisp @lisp
@ -636,7 +702,7 @@ the Org-roam codebase manageable. However, we attempt to accommodate as
many usage styles as possible. many usage styles as possible.
All of Org-roam's customization options can be viewed via 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 @menu
* Directories and Files:: * Directories and Files::
@ -676,47 +742,47 @@ The Org-roam buffer displays backlinks for the currently active Org-roam note.
@item @item
User Option: org-roam-buffer 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 @item
User Option: org-roam-buffer-position User Option: org-roam-buffer-position
The position of the Org-roam buffer side window. Valid values are @samp{'left}, The position of the Org-roam buffer side window. Valid values are @code{'left},
@samp{'right}, @samp{'top}, @samp{'bottom}. @code{'right}, @code{'top}, @code{'bottom}.
@item @item
User Option: org-roam-buffer-width User Option: org-roam-buffer-width
Width of @samp{org-roam-buffer}. Has an effect only if @samp{org-roam-buffer-position} is Width of @code{org-roam-buffer}. Has an effect only if @code{org-roam-buffer-position} is
@samp{'left} or @samp{'right}. @code{'left} or @code{'right}.
@item @item
User Option: org-roam-buffer-height User Option: org-roam-buffer-height
Height of @samp{org-roam-buffer}. Has an effect only if @samp{org-roam-buffer-position} is Height of @code{org-roam-buffer}. Has an effect only if @code{org-roam-buffer-position} is
@samp{'top} or @samp{'bottom}. @code{'top} or @code{'bottom}.
@item @item
User Option: org-roam-buffer-no-delete-other-windows 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 @end itemize
@node Org-roam Links @node Org-roam Links
@section Org-roam Links @section Org-roam Links
Org-roam links are regular @samp{file:} links in Org-mode. By default, links are Org-roam links are regular @code{file:} links in Org-mode. By default, links are
inserted with the title as the link description with @samp{org-roam-insert}. inserted with the title as the link description with @code{org-roam-insert}.
@itemize @itemize
@item @item
User Option: org-roam-link-title-format User Option: org-roam-link-title-format
To distinguish between org-roam links and regular links, one may choose to use 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, If your version of Org is at least @code{9.2}, consider styling the link differently,
by customizing the @samp{org-roam-link}, and @samp{org-roam-link-current} faces. by customizing the @code{org-roam-link}, and @code{org-roam-link-current} faces.
@end itemize @end itemize
@node Org-roam Files @node Org-roam Files
@ -739,7 +805,7 @@ As your collection grows, you might want to create an index where you keep links
to your main files. to your main files.
In Org-roam, you can define the path to your index file by setting 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 @itemize
@item @item
@ -748,23 +814,23 @@ Variable: org-roam-index-file
Path to the 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 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. 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 Otherwise, the index is assumed to be a note in @code{org-roam-index} whose
title is @samp{"Index"}. title is @code{"Index"}.
@item @item
Function: org-roam-find-index 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 @end itemize
@node Encryption @node Encryption
@chapter Encryption @chapter Encryption
One may wish to keep private, encrypted files. Org-roam supports encryption (via 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} GPG), which can be enabled for all new files by setting @code{org-roam-encrypt-files}
to @samp{t}. When enabled, new files are created with the @samp{.org.gpg} extension and to @code{t}. When enabled, new files are created with the @code{.org.gpg} extension and
decryption are handled automatically by EasyPG@. decryption are handled automatically by EasyPG@.
Note that Emacs will prompt for a password for encrypted files during Note that Emacs will prompt for a password for encrypted files during
@ -785,7 +851,7 @@ Org-roam provides graphing capabilities to explore interconnections between
notes. This is done by performing SQL queries and generating images using 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}. @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 @itemize
@item @item
@ -797,17 +863,17 @@ ARG may be any of the following values:
@itemize @itemize
@item @item
@samp{nil} show the graph. @code{nil} show the graph.
@item @item
@samp{C-u} show the graph for FILE@. @code{C-u} show the graph for FILE@.
@item @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 @item
@samp{C-u C-u} build the graph. @code{C-u C-u} build the graph.
@item @item
@samp{C-u -} build the graph for FILE@. @code{C-u -} build the graph for FILE@.
@item @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 @end itemize
@item @item
@ -815,7 +881,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. 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. compact graph layout.
@item @item
@ -830,7 +896,16 @@ A string, which is a path to the program used
a function accepting a single argument: the graph file path. a function accepting a single argument: the graph file path.
@end enumerate @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 @end itemize
@menu @menu
@ -848,25 +923,25 @@ Graphviz provides many options for customizing the graph output, and Org-roam su
User Option: org-roam-graph-extra-config User Option: org-roam-graph-extra-config
Extra options passed to graphviz for the digraph (The ``G'' attributes). Extra options passed to graphviz for the digraph (The ``G'' attributes).
Example: @samp{'=(("rankdir" . "LR"))} Example: @code{'~(("rankdir" . "LR"))}
@item @item
User Option: org-roam-graph-node-extra-config User Option: org-roam-graph-node-extra-config
Extra options for nodes in the graphviz output (The ``N'' attributes). Extra options for nodes in the graphviz output (The ``N'' attributes).
Example: @samp{'(("color" . "skyblue"))} Example: @code{'(("color" . "skyblue"))}
@item @item
User Option: org-roam-graph-edge-extra-config User Option: org-roam-graph-edge-extra-config
Extra options for edges in the graphviz output (The ``E'' attributes). Extra options for edges in the graphviz output (The ``E'' attributes).
Example: @samp{'(("dir" . "back"))} Example: @code{'(("dir" . "back"))}
@item @item
User Option: org-roam-graph-edge-cites-extra-config User Option: org-roam-graph-edge-cites-extra-config
Extra options for citation edges in the graphviz output. Extra options for citation edges in the graphviz output.
Example: @samp{'(("color" . "red"))} Example: @code{'(("color" . "red"))}
@end itemize @end itemize
@node Excluding Nodes and Edges @node Excluding Nodes and Edges
@ -896,21 +971,25 @@ This setting excludes all files whose path contain ``private'' or ``dailies''.
@node Org-roam Completion System @node Org-roam Completion System
@chapter Org-roam Completion System @chapter Org-roam Completion System
Org-roam offers completion when choosing note titles etc. The completion Org-roam allows customization of which minibuffer completion system to use for
system is configurable. The default setting, its interactive commands. The default setting uses Emacs' standard
@code{completing-read} mechanism.
@lisp @lisp
(setq org-roam-completion-system 'default) (setq org-roam-completion-system 'default)
@end lisp @end lisp
uses Emacs' standard @samp{completing-read}. If you prefer If you have installed Helm or Ivy, and have their modes enabled, under the
@uref{https://emacs-helm.github.io/helm/, Helm}, use @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 @lisp
(setq org-roam-completion-system 'helm) (setq org-roam-completion-system 'helm)
@end lisp @end lisp
Other options include @samp{'ido}, and @samp{'ivy}. Other options include @code{'ido}, and @code{'ivy}.
@node Roam Protocol @node Roam Protocol
@chapter Roam Protocol @chapter Roam Protocol
@ -918,15 +997,15 @@ Other options include @samp{'ido}, and @samp{'ivy}.
@menu @menu
* _:: * _::
* Installation: Installation (1). * Installation: Installation (1).
* The @samp{roam-file} protocol:: * The roam-file protocol::
* The @samp{roam-ref} Protocol:: * The roam-ref protocol::
@end menu @end menu
@node _ @node _
@section _ :ignore: @section _ :ignore:
Org-roam extending @samp{org-protocol} with 2 protocols: the @samp{roam-file} Org-roam extending @code{org-protocol} with 2 protocols: the @code{roam-file}
and @samp{roam-ref} protocol. and @code{roam-ref} protocol.
@node Installation (1) @node Installation (1)
@section Installation @section Installation
@ -937,12 +1016,12 @@ To enable Org-roam's protocol extensions, you have to add the following to your
(require 'org-roam-protocol) (require 'org-roam-protocol)
@end lisp @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. 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 @example
[Desktop Entry] [Desktop Entry]
@ -954,7 +1033,7 @@ Terminal=false
MimeType=x-scheme-handler/org-protocol MimeType=x-scheme-handler/org-protocol
@end example @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: running in your shell:
@example @example
@ -962,7 +1041,7 @@ xdg-mime default org-protocol.desktop x-scheme-handler/org-protocol
@end example @end example
To disable the ``confirm'' prompt in Chrome, you can also make Chrome 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: without confirmation. To do this, run in a shell:
@example @example
@ -978,8 +1057,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 and then restart Chrome (for example, by navigating to <chrome://restart>) to
make the new policy take effect. 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 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 @samp{ExternalProtocolDialogShowAlwaysOpenCheckbox} policy. @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 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: setting up with Platypus and Chrome:
@ -995,7 +1074,7 @@ brew cask install platypus
@enumerate @enumerate
@item @item
Create a script @samp{launch_emacs.sh}: Create a script @code{launch_emacs.sh}:
@end enumerate @end enumerate
@example @example
@ -1020,7 +1099,7 @@ Create a Platypus app with the following settings:
@end example @end example
Inside @samp{Settings}: Inside @code{Settings}:
@example @example
| Setting | Value | | Setting | Value |
@ -1031,7 +1110,7 @@ Inside @samp{Settings}:
@end example @end example
To disable the ``confirm'' prompt in Chrome, you can also make Chrome 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: without confirmation. To do this, run in a shell:
@example @example
@ -1040,7 +1119,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 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: the default handler instead, run:
@example @example
@ -1050,21 +1129,43 @@ defaults write com.apple.LaunchServices/com.apple.launchservices.secure LSHandle
Then restart your computer. Then restart your computer.
@node The @samp{roam-file} protocol For Windows, create a temporary @code{org-protocol.reg} file:
@section The @samp{roam-file} protocol
This is a simple protocol that opens the path specified by the @samp{file} @example
key (e.g. @samp{org-protocol://roam-file?file=/tmp/file.org}). This is used 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. in the generated graph.
@node The @samp{roam-ref} Protocol @node The roam-ref protocol
@section The @samp{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}): 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} @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 @example
javascript:location.href = javascript:location.href =
@ -1078,7 +1179,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}): @uref{https://github.com/qutebrowser/qutebrowser/blob/master/doc/help/configuring.asciidoc, Configuring qutebrowser}):
@example @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 @end example
where @code{template} is the template key for a template in where @code{template} is the template key for a template in
@ -1093,7 +1194,7 @@ should contain a @code{#+roam_key: $@{ref@}} in it.
Org-roam provides a utility for diagnosing and repairing problematic files via 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 @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. org-roam-doctor}, but note that this may take some time.
@itemize @itemize
@ -1104,7 +1205,9 @@ Perform a check on Org-roam files to ensure cleanliness. If THIS-BUFFER, run
the check only for the current buffer. the check only for the current buffer.
@end itemize @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 @lisp
(make-org-roam-doctor-checker (make-org-roam-doctor-checker
@ -1115,14 +1218,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)))) ("R" . ("Replace link (keep label)" . org-roam-doctor--replace-link-keep-label))))
@end lisp @end lisp
The @samp{:name} property is the name of the function run. The function takes in the The @code{: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 Org parse tree, and returns a list of @code{(point error-message)}. @code{:description} is a
short description of what the checker does. @samp{:actions} is an alist containing short description of what the checker does. @code{:actions} is an alist containing
elements of the form @samp{(char . (prompt . function))}. These actions are defined per elements of the form @code{(char . (prompt . function))}. These actions are defined per
checker, to perform autofixes for the errors. For each error detected, 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 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 @node Appendix
@chapter Appendix @chapter Appendix
@ -1169,19 +1298,44 @@ taken provided in @samp{:actions}.
@node Ecosystem @node Ecosystem
@section Ecosystem @section Ecosystem
A number of packages work well combined with Org-Roam:
@menu @menu
* Deft:: * Browsing History with winner-mode::
* Versioning Notes::
* Full-text search interface with Deft::
* Org-journal:: * Org-journal::
* Note-taking Add-ons:: * Note-taking Add-ons::
@end menu @end menu
@node Deft @node Browsing History with winner-mode
@subsection Deft @subsection Browsing History with winner-mode
@uref{https://jblevins.org/projects/deft/, Deft} provides a nice interface @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.
for browsing and filtering org-roam notes.
@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 @lisp
(use-package deft (use-package deft
@ -1196,7 +1350,7 @@ for browsing and filtering org-roam notes.
@end lisp @end lisp
If the title of the Org file is not the first line, you might not get 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 functionality. Here I'm using
@uref{https://github.com/raxod502/el-patch, el-patch}: @uref{https://github.com/raxod502/el-patch, el-patch}:
@ -1234,7 +1388,7 @@ that uses an external search engine and indexer.
@subsection Org-journal @subsection Org-journal
@uref{https://github.com/bastibe/org-journal, Org-journal} is a more @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 provides better journaling capabilities, and a nice calendar interface
to see all dated entries. to see all dated entries.
@ -1315,8 +1469,8 @@ etc.) within Org-mode.
tight integration between tight integration between
@uref{https://github.com/jkitchin/org-ref, org-ref}, @uref{https://github.com/jkitchin/org-ref, org-ref},
@uref{https://github.com/tmalsburg/helm-bibtex, helm-bibtex} and @uref{https://github.com/tmalsburg/helm-bibtex, helm-bibtex} and
@samp{org-roam}. This helps you manage your bibliographic notes under @code{org-roam}. This helps you manage your bibliographic notes under
@samp{org-roam}. @code{org-roam}.
@node Spaced Repetition @node Spaced Repetition
@unnumberedsubsubsec Spaced Repetition @unnumberedsubsubsec Spaced Repetition
@ -1336,11 +1490,11 @@ files. Other alternatives include @uref{https://orgmode.org/worg/org-contrib/org
@section How do I have more than one Org-roam directory? @section How do I have more than one Org-roam directory?
Emacs supports directory-local variables, allowing the value of Emacs supports directory-local variables, allowing the value of
@samp{org-roam-directory} to be different in different directories. It does this by @code{org-roam-directory} to be different in different directories. It does this by
checking for a file named @samp{.dir-locals.el}. checking for a file named @code{.dir-locals.el}.
To add support for multiple directories, override the @samp{org-roam-directory} To add support for multiple directories, override the @code{org-roam-directory}
variable using directory-local variables. This is what @samp{.dir-locals.el} may variable using directory-local variables. This is what @code{.dir-locals.el} may
contain: contain:
@lisp @lisp
@ -1348,7 +1502,7 @@ contain:
@end lisp @end lisp
All files within that directory will be treated as their own separate 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. file within that directory, at least once.
@node How do I migrate from Roam Research? @node How do I migrate from Roam Research?

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com> ;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam ;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience ;; 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")) ;; 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. ;; This file is NOT part of GNU Emacs.
@ -49,6 +49,7 @@
(declare-function org-roam--get-backlinks "org-roam") (declare-function org-roam--get-backlinks "org-roam")
(declare-function org-roam-backlinks-mode "org-roam") (declare-function org-roam-backlinks-mode "org-roam")
(declare-function org-roam-mode "org-roam") (declare-function org-roam-mode "org-roam")
(declare-function org-roam--find-file "org-roam")
(defcustom org-roam-buffer-position 'right (defcustom org-roam-buffer-position 'right
"Position of `org-roam' buffer. "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 (defcustom org-roam-buffer-prepare-hook '(org-roam-buffer--insert-title
org-roam-buffer--insert-backlinks 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." "Hook run in the `org-roam-buffer' before it is displayed."
:type 'hook :type 'hook
:group 'org-roam) :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 (defvar org-roam-buffer--current nil
"Currently displayed file in `org-roam' buffer.") "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 () (defun org-roam-buffer--insert-title ()
"Insert the org-roam-buffer title." "Insert the org-roam-buffer title."
(insert (propertize (org-roam--get-title-or-slug (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)))))) ,wrong-type))))))
(concat string (when (> l 1) "s")))) (concat string (when (> l 1) "s"))))
(defun org-roam-buffer--insert-citelinks () (defun org-roam-buffer--insert-ref-links ()
"Insert citation backlinks for the current buffer." "Insert ref backlinks for the current buffer."
(when-let ((org-ref-p (require 'org-ref nil t)) ;; Ensure that org-ref is present (when-let ((ref (cdr (with-temp-buffer
(ref (cdr (with-temp-buffer
(insert-buffer-substring org-roam-buffer--current) (insert-buffer-substring org-roam-buffer--current)
(org-roam--extract-ref))))) (org-roam--extract-ref)))))
(if-let* ((key-backlinks (org-roam--get-backlinks 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 (progn
(insert (let ((l (length key-backlinks))) (insert (let ((l (length key-backlinks)))
(format "\n\n* %d %s\n" (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) (dolist (group grouped-backlinks)
(let ((file-from (car group)) (let ((file-from (car group))
(bls (cdr 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))) (org-roam--get-title-or-slug file-from)))
(dolist (backlink bls) (dolist (backlink bls)
(pcase-let ((`(,file-from _ ,props) backlink)) (pcase-let ((`(,file-from _ ,props) backlink))
(insert (propertize (insert (propertize (plist-get props :content)
(s-trim (s-replace "\n" " "
(plist-get props :content)))
'help-echo "mouse-1: visit backlinked note" 'help-echo "mouse-1: visit backlinked note"
'file-from file-from 'file-from file-from
'file-from-point (plist-get props :point))) 'file-from-point (plist-get props :point)))
(insert "\n\n")))))) (insert "\n\n"))))))
(insert "\n\n* No cite backlinks!")))) (insert "\n\n* No ref backlinks!"))))
(defun org-roam-buffer--insert-backlinks () (defun org-roam-buffer--insert-backlinks ()
"Insert the org-roam-buffer backlinks string for the current buffer." "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))) (org-roam--get-title-or-slug file-from)))
(dolist (backlink bls) (dolist (backlink bls)
(pcase-let ((`(,file-from _ ,props) backlink)) (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" " " (s-trim (s-replace "\n" " "
(plist-get props :content))) (plist-get props :content)))
'help-echo "mouse-1: visit backlinked note" 'help-echo "mouse-1: visit backlinked note"
'file-from file-from 'file-from file-from
'file-from-point (plist-get props :point))) 'file-from-point (plist-get props :point))
(insert "\n\n")))))) "\n\n"))))))
(insert "\n\n* No backlinks!"))) (insert "\n\n* No backlinks!")))
(defun org-roam-buffer-update () (defun org-roam-buffer-update ()
@ -266,14 +277,25 @@ Valid states are 'visible, 'exists and 'none."
(org-roam-buffer--set-height (org-roam-buffer--set-height
(round (* (frame-height) org-roam-buffer-height)))))))) (round (* (frame-height) org-roam-buffer-height))))))))
(defun org-roam-buffer-toggle-display () (defun org-roam-buffer-activate ()
"Toggle display of the `org-roam-buffer'." "Activate display of the `org-roam-buffer'."
(interactive) (interactive)
(unless org-roam-mode (org-roam-mode)) (unless org-roam-mode (org-roam-mode))
(setq org-roam-last-window (get-buffer-window)) (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) (pcase (org-roam-buffer--visibility)
('visible (delete-window (get-buffer-window org-roam-buffer))) ('visible (org-roam-buffer-deactivate))
((or 'exists 'none) (org-roam-buffer--get-create)))) ((or 'exists 'none) (org-roam-buffer-activate))))
(provide 'org-roam-buffer) (provide 'org-roam-buffer)

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com> ;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam ;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience ;; 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")) ;; 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. ;; This file is NOT part of GNU Emacs.
@ -31,6 +31,7 @@
;;; Code: ;;; Code:
;;;; Library Requires ;;;; Library Requires
(require 'org-capture) (require 'org-capture)
(require 'org-roam-macs)
(require 'dash) (require 'dash)
(require 's) (require 's)
(require 'cl-lib) (require 'cl-lib)
@ -39,11 +40,12 @@
(defvar org-roam-encrypt-files) (defvar org-roam-encrypt-files)
(defvar org-roam-directory) (defvar org-roam-directory)
(defvar org-roam-mode) (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-title-path-completions "org-roam")
(declare-function org-roam--get-ref-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--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--format-link "org-roam")
(declare-function org-roam--title-to-slug "org-roam")
(declare-function org-roam-mode "org-roam") (declare-function org-roam-mode "org-roam")
(declare-function org-roam-completion--completing-read "org-roam-completion") (declare-function org-roam-completion--completing-read "org-roam-completion")
@ -81,68 +83,286 @@ note with the given `ref'.")
(defconst org-roam-capture--template-keywords '(:file-name :head) (defconst org-roam-capture--template-keywords '(:file-name :head)
"Keywords used in `org-roam-capture-templates' specific to Org-roam.") "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) '(("d" "default" plain (function org-roam-capture--get-point)
"%?" "%?"
:file-name "%<%Y%m%d%H%M%S>-${slug}" :file-name "%<%Y%m%d%H%M%S>-${slug}"
:head "#+title: ${title}\n" :head "#+title: ${title}\n"
:unnarrowed t)) :unnarrowed t))
"Capture templates for Org-roam. "Capture templates for Org-roam.
The capture templates are an extension of The Org-roam capture-templates builds on the default behaviours of
`org-capture-templates', and the documentation there also `org-capture-templates' by expanding them in 3 areas:
applies.
`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. 2. The `:file-name' key is added, which defines the naming format
See `org-roam-capture--fill-template' for more details. to use when creating new notes. This file-name is relative to
`org-roam-directory', and is without the file-extension.
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.
3. The `:head' key is added, which contains the template that is 3. The `:head' key is added, which contains the template that is
inserted on initial creation (added only once). This is where inserted upon the creation of a new file. This is where you
insertion of any note metadata should go.") 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) '(("r" "ref" plain (function org-roam-capture--get-point)
"" "%?"
:file-name "${slug}" :file-name "${slug}"
:head "#+title: ${title} :head "#+title: ${title}\n#+roam_key: ${ref}\n"
#+roam_key: ${ref}\n"
:unnarrowed t)) :unnarrowed t))
"The Org-roam templates used during a capture from the roam-ref protocol. "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) (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)) (plist-get (plist-get org-capture-plist :org-roam) keyword))
(defun org-roam-capture--put (&rest stuff) (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))) (let ((p (plist-get org-capture-plist :org-roam)))
(while stuff (while stuff
(setq p (plist-put p (setq p (plist-put p (pop stuff) (pop stuff))))
(pop stuff) (pop stuff))))
(setq org-capture-plist (setq org-capture-plist
(plist-put org-capture-plist :org-roam p)))) (plist-put org-capture-plist :org-roam p))))
(defun org-roam-capture--in-process-p () ;; FIXME: Pending upstream patch
"Return non-nil if a `org-roam-capture' buffer exists." ;; https://orgmode.org/list/87h7tv9pkm.fsf@hidden/T/#u
(cl-some (lambda (buffer) ;;
(and (eq (buffer-local-value 'major-mode buffer) ;; Org-capture's behaviour right now is that `org-capture-plist' is valid only
'org-mode) ;; during the initialization of the Org-capture buffer. The value of
(plist-get (buffer-local-value 'org-capture-current-plist buffer) ;; `org-capture-plist' is saved into buffer-local `org-capture-current-plist'.
:org-roam))) ;; However, the value for that particular capture is no longer accessible for
(buffer-list))) ;; 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) (advice-add 'org-capture-finalize :before #'org-roam-capture--update-plist)
"Expands the template STR, returning the string.
(defun org-roam-capture--finalize ()
"Finalize the `org-roam-capture' process."
(let* ((finalize (org-roam-capture--get :finalize))
;; In case any regions were shielded before, unshield them
(region (when-let ((region (org-roam-capture--get :region)))
(org-roam-unshield-region (car region) (cdr region))))
(beg (car region))
(end (cdr region)))
(unless org-note-abort
(pcase 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 region
(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))))))))))
(when region
(set-marker beg nil)
(set-marker end nil))
(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. 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 If there is a ${var} with no matching var in the alist, the value
of var is prompted for via `completing-read'. of var is prompted for via `completing-read'.
@ -150,23 +370,13 @@ Next, it expands the remaining template string using
`org-capture-fill-template'." `org-capture-fill-template'."
(-> str (-> str
(s-format (lambda (key) (s-format (lambda (key)
(or (s--aget info key) (or (s--aget org-roam-capture--info key)
(completing-read (format "%s: " key ) nil))) nil) (when-let ((val (completing-read (format "%s: " key) nil)))
(push (cons key val) org-roam-capture--info)
val))) nil)
(org-capture-fill-template))) (org-capture-fill-template)))
(defun org-roam-capture--insert-link-h () (defun org-roam-capture--save-file-maybe ()
"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 ()
"Save the file conditionally. "Save the file conditionally.
The file is saved if the original value of :no-save is not t and 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 `org-note-abort' is not t. It is added to
@ -180,8 +390,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)) ((and (not (org-roam-capture--get :orig-no-save))
(not org-note-abort)) (not org-note-abort))
(with-current-buffer (org-capture-get :buffer) (with-current-buffer (org-capture-get :buffer)
(save-buffer)))) (save-buffer)))))
(remove-hook 'org-capture-after-finalize-hook #'org-roam-capture--save-file-maybe-h))
(defun org-roam-capture--new-file () (defun org-roam-capture--new-file ()
"Return the path to the new file during an Org-roam capture. "Return the path to the new file during an Org-roam capture.
@ -201,14 +410,13 @@ aborted, we do the following:
2. Set the capture template's :no-save to t. 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 the file if the original value of :no-save is not t and
`org-note-abort' is not t." `org-note-abort' is not t."
(let* ((name-templ (or (org-roam-capture--get :file-name) (let* ((name-templ (or (org-roam-capture--get :file-name)
org-roam-capture--file-name-default)) org-roam-capture--file-name-default))
(new-id (s-trim (org-roam-capture--fill-template (new-id (s-trim (org-roam-capture--fill-template
name-templ name-templ)))
org-roam-capture--info)))
(file-path (org-roam--file-path-from-id new-id)) (file-path (org-roam--file-path-from-id new-id))
(roam-head (or (org-roam-capture--get :head) (roam-head (or (org-roam-capture--get :head)
org-roam-capture--header-default)) org-roam-capture--header-default))
@ -229,16 +437,6 @@ the file if the original value of :no-save is not t and
:no-save t)) :no-save t))
file-path)) 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 () (defun org-roam-capture--get-point ()
"Return exact point to file for org-capture-template. "Return exact point to file for org-capture-template.
The file to use is dependent on the context: The file to use is dependent on the context:
@ -271,7 +469,8 @@ This function is used solely in Org-roam's capture templates: see
(plist-get pl :path) (plist-get pl :path)
(org-roam-capture--new-file)))) (org-roam-capture--new-file))))
(_ (error "Invalid org-roam-capture-context"))))) (_ (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) (org-roam-capture--put :file-path file-path)
(while org-roam-capture-additional-template-props (while org-roam-capture-additional-template-props
(let ((prop (pop org-roam-capture-additional-template-props)) (let ((prop (pop org-roam-capture-additional-template-props))
@ -299,33 +498,31 @@ This function is used solely in Org-roam's capture templates: see
(append converted options `(:org-roam ,org-roam-plist)))) (append converted options `(:org-roam ,org-roam-plist))))
(_ (user-error "Invalid capture template format: %s" template)))) (_ (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 (defcustom org-roam-capture-after-find-file-hook nil
"Hook that is run right after an Org-roam capture process is finalized. "Hook that is run right after an Org-roam capture process is finalized.
Suitable for moving point." Suitable for moving point."
:group 'org-roam :group 'org-roam
:type 'hook) :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) (defun org-roam-capture--capture (&optional goto keys)
"Create a new file, and return the path to the edited file. "Create a new file, and return the path to the edited file.
The templates are defined at `org-roam-capture-templates'. The The templates are defined at `org-roam-capture-templates'. The
GOTO and KEYS argument have the same functionality as GOTO and KEYS argument have the same functionality as
`org-capture'." `org-capture'."
(let ((org-capture-templates (mapcar #'org-roam-capture--convert-template org-roam-capture-templates)) (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) org-capture-templates-contexts)
(when (= (length org-capture-templates) 1) (when one-template-p
(setq keys (caar org-capture-templates))) (setq keys (caar org-capture-templates)))
(add-hook 'org-capture-after-finalize-hook #'org-roam-capture--save-file-maybe-h) (if (or one-template-p
(org-capture goto keys))) (eq org-roam-capture-function 'org-capture))
(org-capture goto keys)
(funcall-interactively org-roam-capture-function))))
;;;###autoload ;;;###autoload
(defun org-roam-capture () (defun org-roam-capture ()
@ -333,8 +530,6 @@ GOTO and KEYS argument have the same functionality as
This uses the templates defined at `org-roam-capture-templates'." This uses the templates defined at `org-roam-capture-templates'."
(interactive) (interactive)
(unless org-roam-mode (org-roam-mode)) (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)) (let* ((completions (org-roam--get-title-path-completions))
(title-with-keys (org-roam-completion--completing-read "File: " (title-with-keys (org-roam-completion--completing-read "File: "
completions)) completions))
@ -342,10 +537,9 @@ This uses the templates defined at `org-roam-capture-templates'."
(title (or (plist-get res :title) title-with-keys)) (title (or (plist-get res :title) title-with-keys))
(file-path (plist-get res :path))) (file-path (plist-get res :path)))
(let ((org-roam-capture--info (list (cons 'title title) (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))) (cons 'file file-path)))
(org-roam-capture--context 'capture)) (org-roam-capture--context 'capture))
(setq org-roam-capture-additional-template-props (list :capture-fn 'org-roam-capture))
(condition-case err (condition-case err
(org-roam-capture--capture) (org-roam-capture--capture)
(error (user-error "%s. Please adjust `org-roam-capture-templates'" (error (user-error "%s. Please adjust `org-roam-capture-templates'"

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com> ;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam ;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience ;; 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")) ;; 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. ;; This file is NOT part of GNU Emacs.
@ -79,6 +79,8 @@
"org-roam 1.1.0") "org-roam 1.1.0")
(define-obsolete-function-alias 'org-roam-db--maybe-update 'org-roam-db--update-maybe (define-obsolete-function-alias 'org-roam-db--maybe-update 'org-roam-db--update-maybe
"org-roam 1.1.0") "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") (when (version< (org-version) "9.3")
(defalias 'org-link-make-string 'org-make-link-string)) (defalias 'org-link-make-string 'org-make-link-string))

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com> ;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam ;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience ;; 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")) ;; 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. ;; This file is NOT part of GNU Emacs.

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com> ;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam ;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience ;; 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")) ;; 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. ;; This file is NOT part of GNU Emacs.
@ -36,13 +36,62 @@
(require 'org-roam-capture) (require 'org-roam-capture)
(require 'org-roam-macs) (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) '(("d" "daily" plain (function org-roam-capture--get-point)
"" ""
:immediate-finish t :immediate-finish t
:file-name "%<%Y-%m-%d>" :file-name "%<%Y-%m-%d>"
:head "#+title: %<%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 ;; Declarations
(defvar org-roam-mode) (defvar org-roam-mode)
@ -54,7 +103,7 @@
(let ((org-roam-capture-templates org-roam-dailies-capture-templates) (let ((org-roam-capture-templates org-roam-dailies-capture-templates)
(org-roam-capture--info (list (cons 'time time))) (org-roam-capture--info (list (cons 'time time)))
(org-roam-capture--context 'dailies)) (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--with-template-error 'org-roam-dailies-capture-templates
(org-roam-capture--capture)))) (org-roam-capture--capture))))

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com> ;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam ;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience ;; 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")) ;; 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. ;; 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-headlines "org-roam")
(declare-function org-roam--extract-links "org-roam") (declare-function org-roam--extract-links "org-roam")
(declare-function org-roam--list-all-files "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") (declare-function org-roam-buffer--update-maybe "org-roam-buffer")
;;;; Options ;;;; Options
@ -60,7 +62,22 @@ when used with multiple Org-roam instances."
:type 'string :type 'string
:group 'org-roam) :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) (defvar org-roam-db--connection (make-hash-table :test #'equal)
"Database connection to Org-roam database.") "Database connection to Org-roam database.")
@ -137,7 +154,7 @@ SQL can be either the emacsql vector representation, or a string."
(titles (titles
[(file :not-null) [(file :not-null)
titles]) title])
(refs (refs
[(ref :unique :not-null) [(ref :unique :not-null)
@ -179,7 +196,7 @@ the current `org-roam-directory'."
;;;; Database API ;;;; Database API
;;;;; Initialization ;;;;; Initialization
(defun org-roam-db--initialized-p () (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)) (and (file-exists-p (org-roam-db--get))
(> (caar (org-roam-db-query [:select (funcall count) :from titles])) (> (caar (org-roam-db-query [:select (funcall count) :from titles]))
0))) 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"))) (error "[Org-roam] your cache isn't built yet! Please run org-roam-db-build-cache")))
;;;;; Clearing ;;;;; Clearing
(defun org-roam-db--clear () (defun org-roam-db-clear ()
"Clears all entries in the caches." "Clears all entries in the Org-roam cache."
(interactive) (interactive)
(when (file-exists-p (org-roam-db--get)) (when (file-exists-p (org-roam-db--get))
(dolist (table (mapcar #'car org-roam-db--table-schemata)) (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 (org-roam-db-query
[:insert :into titles [:insert :into titles
:values $v1] :values $v1]
(list (vector file titles)))) (mapcar (lambda (title)
(vector file title)) titles)))
(defun org-roam-db--insert-headlines (headlines) (defun org-roam-db--insert-headlines (headlines)
"Insert HEADLINES into the Org-roam cache." "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 (org-roam-db-query
[:insert :into headlines [:insert :into headlines
:values $v1] :values $v1]
headlines)) 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) (defun org-roam-db--insert-tags (file tags)
"Insert TAGS for a FILE into the Org-roam cache." "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)))) (list (vector file tags))))
(defun org-roam-db--insert-ref (file ref) (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)) (let ((key (cdr ref))
(type (car ref))) (type (car ref)))
(condition-case nil
(progn
(org-roam-db-query (org-roam-db-query
[:insert :into refs :values $v1] [:insert :into refs :values $v1]
(list (vector key file type))))) (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 ;;;;; Fetching
(defun org-roam-db--get-current-files () (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) (defun org-roam-db--get-titles (file)
"Return the titles of FILE from the cache." "Return the titles of FILE from the cache."
(caar (org-roam-db-query [:select [titles] :from titles (caar (org-roam-db-query [:select [title] :from titles
:where (= file $s1)] :where (= file $s1)
file :limit 1]
:limit 1))) file)))
(defun org-roam-db--connected-component (file) (defun org-roam-db--connected-component (file)
"Return all files reachable from/connected to FILE, including the file itself. "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 (mapcar 'car-safe (emacsql (org-roam-db) query file max-distance))))
files)) 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 ;;;;; Updating
(defun org-roam-db--update-meta () (defun org-roam-db--update-meta ()
"Update the metadata of the current buffer into the cache." "Update the metadata of the current buffer into the cache."
@ -328,7 +395,7 @@ connections, nil is returned."
(attr (file-attributes file)) (attr (file-attributes file))
(atime (file-attribute-access-time attr)) (atime (file-attribute-access-time attr))
(mtime (file-attribute-modification-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 (org-roam-db-query [:delete :from files
:where (= file $s1)] :where (= file $s1)]
file) file)
@ -337,11 +404,12 @@ connections, nil is returned."
(defun org-roam-db--update-titles () (defun org-roam-db--update-titles ()
"Update the title of the current buffer into the cache." "Update the title of the current buffer into the cache."
(let* ((file (file-truename (buffer-file-name))) (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 (org-roam-db-query [:delete :from titles
:where (= file $s1)] :where (= file $s1)]
file) file)
(org-roam-db--insert-titles file title))) (org-roam-db--insert-titles file titles)))
(defun org-roam-db--update-tags () (defun org-roam-db--update-tags ()
"Update the tags of the current buffer into the cache." "Update the tags of the current buffer into the cache."
@ -388,12 +456,13 @@ connections, nil is returned."
(current-buffer)))) (current-buffer))))
(with-current-buffer buf (with-current-buffer buf
(save-excursion (save-excursion
(emacsql-with-transaction (org-roam-db)
(org-roam-db--update-meta) (org-roam-db--update-meta)
(org-roam-db--update-tags) (org-roam-db--update-tags)
(org-roam-db--update-titles) (org-roam-db--update-titles)
(org-roam-db--update-refs) (org-roam-db--update-refs)
(org-roam-db--update-headlines) (org-roam-db--update-headlines)
(org-roam-db--update-links) (org-roam-db--update-links))
(org-roam-buffer--update-maybe :redisplay t)))))) (org-roam-buffer--update-maybe :redisplay t))))))
(defun org-roam-db-build-cache (&optional force) (defun org-roam-db-build-cache (&optional force)
@ -403,91 +472,80 @@ If FORCE, force a rebuild of the cache from scratch."
(when force (delete-file (org-roam-db--get))) (when force (delete-file (org-roam-db--get)))
(org-roam-db--close) ;; Force a reconnect (org-roam-db--close) ;; Force a reconnect
(org-roam-db) ;; To initialize the database, no-op if already initialized (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)) (current-files (org-roam-db--get-current-files))
all-files all-headlines all-links all-titles all-refs all-tags) (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 ;; Two-step building
;; First step: Rebuild files and headlines ;; First step: Rebuild files and headlines
(dolist (file org-roam-files) (dolist (file org-roam-files)
(let* ((attr (file-attributes file)) (let* ((attr (file-attributes file))
(atime (file-attribute-access-time attr)) (atime (file-attribute-access-time attr))
(mtime (file-attribute-modification-time attr))) (mtime (file-attribute-modification-time attr)))
(org-roam--with-temp-buffer file (let ((contents-hash (org-roam-db--file-hash file)))
(let ((contents-hash (secure-hash 'sha1 (current-buffer))))
(unless (string= (gethash file current-files) (unless (string= (gethash file current-files)
contents-hash) contents-hash)
(condition-case nil
(org-roam--with-temp-buffer file
(org-roam-db--clear-file file) (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 (org-roam-db-query
[:insert :into files [:insert :into files
:values $v1] :values $v1]
all-files)) (vector file contents-hash (list :atime atime :mtime mtime)))
(when all-headlines (setq file-count (1+ file-count))
(org-roam-db-query (when-let ((headlines (org-roam--extract-headlines file)))
[:insert :into headlines (when (org-roam-db--insert-headlines headlines)
:values $v1] (setq headline-count (1+ headline-count)))))
all-headlines)) (file-error
(setq org-roam-files (remove file org-roam-files))
(org-roam-db--clear-file file)
(lwarn '(org-roam) :warning
"Skipping unreadable file while building cache: %s" file)))))))
;; Second step: Rebuild the rest ;; Second step: Rebuild the rest
(dolist (file org-roam-files) (dolist (file org-roam-files)
(org-roam--with-temp-buffer file (let ((contents-hash (org-roam-db--file-hash file)))
(let ((contents-hash (secure-hash 'sha1 (current-buffer))))
(unless (string= (gethash file current-files) (unless (string= (gethash file current-files)
contents-hash) contents-hash)
(org-roam--with-temp-buffer file
(when-let (links (org-roam--extract-links file)) (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 (org-roam-db-query
[:insert :into links [:insert :into links
:values $v1] :values $v1]
all-links)) links)
(when all-titles (setq link-count (1+ link-count)))
(org-roam-db-query (when-let (tags (org-roam--extract-tags file))
[:insert :into titles
:values $v1]
all-titles))
(when all-tags
(org-roam-db-query (org-roam-db-query
[:insert :into tags [:insert :into tags
:values $v1] :values $v1]
all-tags)) (vector file tags))
(when all-refs (setq tag-count (1+ tag-count)))
(org-roam-db-query (let ((titles (or (org-roam--extract-titles)
[:insert :into refs (list (org-roam--path-to-slug file)))))
:values $v1] (org-roam-db--insert-titles file titles)
all-refs)) (setq title-count (+ title-count (length titles))))
(let ((stats (list :files (length all-files) (when-let* ((ref (org-roam--extract-ref)))
:headlines (length all-headlines) (when (org-roam-db--insert-ref file ref)
:links (length all-links) (setq ref-count (1+ ref-count))))))
:tags (length all-tags) (remhash file current-files)))
:titles (length all-titles) (dolist (file (hash-table-keys current-files))
:refs (length all-refs) ;; These files are no longer around, remove from cache...
:deleted (length (hash-table-keys current-files))))) (org-roam-db--clear-file file)
(org-roam-message "files: %s, headlines: %s, links: %s, tags: %s, titles: %s, refs: %s, deleted: %s" (setq deleted-count (1+ deleted-count))))
(plist-get stats :files) (org-roam-message "files: Δ%s, headlines: Δ%s, links: Δ%s, tags: Δ%s, titles: Δ%s, refs: Δ%s, deleted: Δ%s"
(plist-get stats :headlines) file-count
(plist-get stats :links) headline-count
(plist-get stats :tags) link-count
(plist-get stats :titles) tag-count
(plist-get stats :refs) title-count
(plist-get stats :deleted)) ref-count
stats))) deleted-count)))
(provide 'org-roam-db) (provide 'org-roam-db)

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com> ;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam ;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience ;; 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")) ;; 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. ;; This file is NOT part of GNU Emacs.
@ -33,7 +33,14 @@
;; ;;
;;; Code: ;;; Code:
(require 'emacsql) (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 ;;; org-roam-dev.el ends here

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com> ;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/jethrokuan/org-roam ;; URL: https://github.com/jethrokuan/org-roam
;; Keywords: org-mode, roam, convenience ;; 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")) ;; 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. ;; This file is NOT part of GNU Emacs.

67
org-roam-faces.el Normal file
View File

@ -0,0 +1,67 @@
;;; org-roam-faces.el --- Face definitions -*- coding: utf-8; lexical-binding: t; -*-
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 1.2.1
;; Package-Requires: ((emacs "26.1"))
;; This file is NOT part of GNU Emacs.
;; 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, 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 GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;; This file contains the face definitions for Org-roam.
;;; Code:
(defgroup org-roam-faces nil
"Faces used by Org-roam."
:group 'org-roam
:group 'faces)
;;; Definitions
(defface org-roam-link
'((t :inherit org-link))
"Face for Org-roam links."
:group 'org-roam-faces)
(defface org-roam-link-current
'((t :inherit org-link))
"Face for Org-roam links pointing to the current buffer."
:group 'org-roam-faces)
(defface org-roam-link-invalid
'((t :inherit (error org-link)))
"Face for Org-roam links that are not valid.
This face is used for links without a destination."
:group 'org-roam-faces)
(defface org-roam-link-shielded
'((t :inherit (warning org-link)))
"Face for Org-roam links that are shielded.
This face is used on the region target by `org-roam-insertion'
during an `org-roam-capture'."
:group 'org-roam-faces)
;;; _
(provide 'org-roam-faces)
;;; org-roam-faces.el ends here

0
org-roam-flow.el Normal file
View File

View File

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

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com> ;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam ;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience ;; 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")) ;; 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. ;; This file is NOT part of GNU Emacs.
@ -33,6 +33,7 @@
;; ;;
;;; Code: ;;; Code:
;;;; Library Requires ;;;; Library Requires
(require 'dash)
(defvar org-roam-verbose) (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 (make-symbol "current-org-roam-directory")))
`(let ((,current-org-roam-directory org-roam-directory)) `(let ((,current-org-roam-directory org-roam-directory))
(with-temp-buffer (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 (when ,file
(insert-file-contents ,file) (insert-file-contents ,file)
(setq-local org-roam-file-name ,file)) (setq-local org-roam-file-name ,file))
@ -68,6 +71,34 @@ to look.
(when org-roam-verbose (when org-roam-verbose
(apply #'message `(,(concat "(org-roam) " format-string) ,@args)))) (apply #'message `(,(concat "(org-roam) " format-string) ,@args))))
(defun org-roam-string-quote (str)
"Quote STR."
(->> str
(s-replace "\\" "\\\\")
(s-replace "\"" "\\\"")))
;;; Shielding regions
(defun org-roam-shield-region (beg end)
"Shield REGION against modifications.
REGION must be a cons-cell containing the marker to the region
beginning and maximum values."
(when (and beg end)
(add-text-properties beg end
'(font-lock-face org-roam-link-shielded
read-only t)
(marker-buffer beg))
(cons beg end)))
(defun org-roam-unshield-region (beg end)
"Unshield the shielded REGION."
(when (and beg end)
(let ((inhibit-read-only t))
(remove-text-properties beg end
'(font-lock-face org-roam-link-shielded
read-only t)
(marker-buffer beg)))
(cons beg end)))
(provide 'org-roam-macs) (provide 'org-roam-macs)
;;; org-roam-macs.el ends here ;;; org-roam-macs.el ends here

View File

@ -4,7 +4,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com> ;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam ;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience ;; Keywords: org-mode, roam, convenience
;; Version: 1.2.0 ;; Version: 1.2.1
;; Package-Requires: ((emacs "26.1") (org "9.3")) ;; Package-Requires: ((emacs "26.1") (org "9.3"))
;; This file is NOT part of GNU Emacs. ;; 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) (unless (assoc 'ref decoded-alist)
(error "No ref key provided")) (error "No ref key provided"))
(when-let ((title (cdr (assoc 'title decoded-alist)))) (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) (let* ((org-roam-capture-templates org-roam-capture-ref-templates)
(org-roam-capture--context 'ref) (org-roam-capture--context 'ref)
(org-roam-capture--info decoded-alist) (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" org-protocol://roam-file?file=/path/to/file.org"
(when-let ((file (plist-get info :file))) (when-let ((file (plist-get info :file)))
(raise-frame) (raise-frame)
(find-file file)) (org-roam--find-file file))
nil) nil)
(push '("org-roam-ref" :protocol "roam-ref" :function org-roam-protocol-open-ref) (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> ;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam ;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience ;; 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")) ;; 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. ;; This file is NOT part of GNU Emacs.
@ -54,6 +54,8 @@
;; @TODO: implement something akin to `org-modules' that allows ;; @TODO: implement something akin to `org-modules' that allows
;; selectively loading different sets of features. ;; selectively loading different sets of features.
;; ~NV [2020-05-22 Fri] ;; ~NV [2020-05-22 Fri]
(require 'org-roam-faces)
(require 'org-roam-buffer) (require 'org-roam-buffer)
(require 'org-roam-completion) (require 'org-roam-completion)
(require 'org-roam-capture) (require 'org-roam-capture)
@ -70,19 +72,13 @@
(defvar org-id-link-to-org-use-id) (defvar org-id-link-to-org-use-id)
(declare-function org-id-find-id-in-file "ext:org-id" (id file &optional markerp)) (declare-function org-id-find-id-in-file "ext:org-id" (id file &optional markerp))
;;;; Customizable variables ;;;; Customizable variables
(defgroup org-roam nil (defgroup org-roam nil
"Roam Research replica in Org-mode." "Roam Research replica in Org-mode."
:group 'org :group 'org
:prefix "org-roam-" :prefix "org-roam-"
:link '(url-link :tag "Github" "https://github.com/org-roam/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."
:group 'org-roam
:group 'faces)
(defcustom org-roam-directory (expand-file-name "~/org-roam/") (defcustom org-roam-directory (expand-file-name "~/org-roam/")
"Default path to Org-roam files. "Default path to Org-roam files.
@ -104,6 +100,12 @@ ensure that."
:type '(repeat string) :type '(repeat string)
:group 'org-roam) :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 (defcustom org-roam-include-type-in-ref-path-completions nil
"When t, include the type in ref-path completions. "When t, include the type in ref-path completions.
Note that this only affects interactive calls. Note that this only affects interactive calls.
@ -197,10 +199,22 @@ extraction methods:
`last-directory' `last-directory'
Extract the last directory relative to `org-roam-directory'. Extract the last directory relative to `org-roam-directory'.
That is, if a file is located at relative path foo/bar/file.org, That is, if a file is located at relative path foo/bar/file.org,
the file will have tag \"bar\"." the file will have tag \"bar\".
:type '(set (const :tag "#+roam_tags" PROP)
`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 "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) (defcustom org-roam-title-sources '((title headline) alias)
"The list of sources from which to retrieve a note title. "The list of sources from which to retrieve a note title.
@ -489,10 +503,46 @@ PATH should be the root from which to compute the relativity."
(when (f-relative-p link) (when (f-relative-p link)
(delete-region (match-beginning 1) (delete-region (match-beginning 1)
(match-end 1)) (match-end 1))
(insert (expand-file-name (insert (expand-file-name link dir))))
(concat dir link)))))
(buffer-string)))) (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) (defun org-roam--extract-links (&optional file-path)
"Extracts all link items within the current buffer. "Extracts all link items within the current buffer.
Link items are of the form: Link items are of the form:
@ -503,6 +553,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 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 in temp buffers. In cases where this occurs, we do know the file path, and pass
it as FILE-PATH." it as FILE-PATH."
(require 'org-ref nil t)
(let ((file-path (or file-path (let ((file-path (or file-path
(file-truename (buffer-file-name)))) (file-truename (buffer-file-name))))
links) links)
@ -510,20 +561,7 @@ it as FILE-PATH."
(lambda (link) (lambda (link)
(let* ((type (org-element-property :type link)) (let* ((type (org-element-property :type link))
(path (org-element-property :path link)) (path (org-element-property :path link))
(start (org-element-property :begin 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) (goto-char start)
(let* ((element (org-element-at-point)) (let* ((element (org-element-at-point))
(begin (or (org-element-property :content-begin element) (begin (or (org-element-property :content-begin element)
@ -536,21 +574,32 @@ it as FILE-PATH."
(content (string-trim content)) (content (string-trim content))
;; Expand all relative links to absolute links ;; Expand all relative links to absolute links
(content (org-roam--expand-links content file-path))) (content (org-roam--expand-links content file-path)))
(let ((context (list :content content :point begin)) (let ((properties (list :outline (mapcar (lambda (path)
(names (pcase link-type (org-roam--expand-links path file-path))
(org-roam--get-outline-path))
:content content
:point begin))
(names (pcase type
("file" ("file"
(list (file-truename (expand-file-name path (file-name-directory file-path))))) (if (file-remote-p path)
(list path)
(list (file-truename (expand-file-name path (file-name-directory file-path))))))
("id" ("id"
(list (car id-data))) (list (car (org-roam-id-find path))))
("cite" ((pred (lambda (typ)
(org-ref-split-and-strip-string path))))) (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) (seq-do (lambda (name)
(when name
(push (vector file-path (push (vector file-path
name name
link-type type
context) properties)
links)) links)))
names))))))) names))))))
links)) links))
(defun org-roam--extract-headlines (&optional file-path) (defun org-roam--extract-headlines (&optional file-path)
@ -621,16 +670,24 @@ If NESTED, return the first successful result from SOURCES."
"Extract tags from using the directory path FILE. "Extract tags from using the directory path FILE.
All sub-directories relative to `org-roam-directory' are used as tags." All sub-directories relative to `org-roam-directory' are used as tags."
(when-let ((dir-relative (file-name-directory (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))) (f-split dir-relative)))
(defun org-roam--extract-tags-last-directory (file) (defun org-roam--extract-tags-last-directory (file)
"Extract tags from using the directory path FILE. "Extract tags from using the directory path FILE.
The final directory component is used as a tag." The final directory component is used as a tag."
(when-let ((dir-relative (file-name-directory (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)))) (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) (defun org-roam--extract-tags-prop (_file)
"Extract tags from the current buffer's \"#roam_tags\" global property." "Extract tags from the current buffer's \"#roam_tags\" global property."
(let* ((prop (cdr (assoc "ROAM_TAGS" (org-roam--extract-global-props '("ROAM_TAGS")))))) (let* ((prop (cdr (assoc "ROAM_TAGS" (org-roam--extract-global-props '("ROAM_TAGS"))))))
@ -718,7 +775,7 @@ Examples:
(defun org-roam--get-title-or-slug (path) (defun org-roam--get-title-or-slug (path)
"Convert `PATH' to the file title, if it exists. Else, return the 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))) (org-roam--path-to-slug path)))
(defun org-roam--title-to-slug (title) (defun org-roam--title-to-slug (title)
@ -761,7 +818,7 @@ Examples:
"Return an alist for completion. "Return an alist for completion.
The car is the displayed title for completion, and the cdr is the The car is the displayed title for completion, and the cdr is the
to the file." 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 :left :join tags
:on (= titles:file tags:file) :on (= titles:file tags:file)
:left :join files :left :join files
@ -772,15 +829,13 @@ to the file."
#'time-less-p #'time-less-p
rows) rows)
(dolist (row rows completions) (dolist (row rows completions)
(pcase-let ((`(,file-path ,titles ,tags) row)) (pcase-let ((`(,file-path ,title ,tags) row))
(let ((titles (or titles (list (org-roam--path-to-slug file-path)))))
(dolist (title titles)
(let ((k (concat (let ((k (concat
(when tags (when tags
(format "(%s) " (s-join org-roam-tag-separator tags))) (format "(%s) " (s-join org-roam-tag-separator tags)))
title)) title))
(v (list :path file-path :title title))) (v (list :path file-path :title title)))
(push (cons k v) completions)))))))) (push (cons k v) completions))))))
(defun org-roam--get-index-path () (defun org-roam--get-index-path ()
"Return the path to the index in `org-roam-directory'. "Return the path to the index in `org-roam-directory'.
@ -800,30 +855,40 @@ whose title is 'Index'."
index))) index)))
;;;; org-roam-find-ref ;;;; 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. "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 When called interactively (i.e. when ARG is 1), formats the car
completion-candidates as 'type:ref'. 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: FILTER can either be a string or a function:
- If it is a string, it should be the type of refs to include as - 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 - 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 takes three arguments: the type, the ref, and the file of the
current candidate. It should return t if that candidate is to be current candidate. It should return t if that candidate is to
included as a candidate." be included as a candidate."
(let ((rows (org-roam-db-query [:select [refs:type refs:ref refs:file ] :from refs (let ((rows (org-roam-db-query
:left :join files [:select [refs:type refs:ref refs:file titles:title tags:tags]
:on (= refs:file files:file)])) :from titles
(include-type (and interactive :left :join tags
org-roam-include-type-in-ref-path-completions)) :on (= titles:file tags:file)
:left :join refs :on (= titles:file refs:file)
:where refs:file :is :not :null]))
completions) completions)
(seq-sort-by (lambda (x) (seq-sort-by (lambda (x)
(plist-get (nth 3 x) :mtime)) (plist-get (nth 3 x) :mtime))
#'time-less-p #'time-less-p
rows) rows)
(dolist (row rows completions) (dolist (row rows completions)
(pcase-let ((`(,type ,ref ,file-path) row)) (pcase-let ((`(,type ,ref ,file-path ,title ,tags) row))
(when (pcase filter (when (pcase filter
('nil t) ('nil t)
((pred stringp) (string= type filter)) ((pred stringp) (string= type filter))
@ -831,13 +896,21 @@ included as a candidate."
(wrong-type (signal 'wrong-type-argument (wrong-type (signal 'wrong-type-argument
`((stringp functionp) `((stringp functionp)
,wrong-type)))) ,wrong-type))))
(let ((k (concat (let ((k (if (eq arg 1)
(when include-type (concat
(format "(%s) " type)) (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)) ref))
(v (list :path file-path :type type :ref ref))) (v (list :path file-path :type type :ref ref)))
(push (cons k v) completions))))))) (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) (defun org-roam--find-ref (ref)
"Find and open and Org-roam file from REF if it exists. "Find and open and Org-roam file from REF if it exists.
REF should be the value of '#+roam_key:' without any REF should be the value of '#+roam_key:' without any
@ -845,7 +918,7 @@ type-information (e.g. 'cite:').
Return nil if the file does not exist." Return nil if the file does not exist."
(when-let* ((completions (org-roam--get-ref-path-completions)) (when-let* ((completions (org-roam--get-ref-path-completions))
(file (plist-get (cdr (assoc ref completions)) :path))) (file (plist-get (cdr (assoc ref completions)) :path)))
(find-file file))) (org-roam--find-file file)))
(defun org-roam--get-roam-buffers () (defun org-roam--get-roam-buffers ()
"Return a list of buffers that are Org-roam files." "Return a list of buffers that are Org-roam files."
@ -866,25 +939,7 @@ Return nil if the file does not exist."
file) file)
org-roam-directory)))) org-roam-directory))))
;;; The org-roam buffer ;;; org-roam-backlinks-mode
;;;; org-roam-link-face
(defface org-roam-link
'((t :inherit org-link))
"Face for Org-roam links."
:group 'org-roam-faces)
(defface org-roam-link-current
'((t :inherit org-link))
"Face for Org-roam links pointing to the current buffer."
:group 'org-roam-faces)
(defface org-roam-link-invalid
'((t :inherit (error org-link)))
"Face for Org-roam links that are not valid.
This face is used for links without a destination."
:group 'org-roam-faces)
;;;; org-roam-backlinks-mode
(define-minor-mode org-roam-backlinks-mode (define-minor-mode org-roam-backlinks-mode
"Minor mode for the `org-roam-buffer'. "Minor mode for the `org-roam-buffer'.
\\{org-roam-backlinks-mode-map}" \\{org-roam-backlinks-mode-map}"
@ -923,38 +978,6 @@ buffer or a marker."
(backlink-dest (org-roam--retrieve-link-destination))) (backlink-dest (org-roam--retrieve-link-destination)))
(string= current backlink-dest))) (string= current backlink-dest)))
(defun org-roam--roam-file-link-face (path)
"Conditional face for org file links.
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))
'org-roam-link-invalid)
((and (org-roam--in-buffer-p)
(org-roam--backlink-to-current-p))
'org-roam-link-current)
((org-roam--org-roam-file-p path)
'org-roam-link)
(t
'org-link)))
(defun org-roam--roam-id-link-face (id)
"Conditional face for org ID links.
Applies `org-roam-link-current' if ID corresponds to the
currently opened Org-roam file in the backlink buffer, or
`org-roam-link-face' if ID corresponds to any other Org-roam
file."
(cond ((not (org-roam-id-find id))
'org-roam-link-invalid)
((and (org-roam--in-buffer-p)
(org-roam--backlink-to-current-p))
'org-roam-link-current)
((org-roam-id-find id t)
'org-roam-link)
(t
'org-link)))
(defun org-roam-open-at-point () (defun org-roam-open-at-point ()
"Open an Org-roam link or visit the text previewed at point. "Open an Org-roam link or visit the text previewed at point.
When point is on an Org-roam link, open the link in the Org-roam window. When point is on an Org-roam link, open the link in the Org-roam window.
@ -969,27 +992,19 @@ This function hooks into `org-open-at-point' via `org-open-at-point-functions'."
(when (and (eq (org-element-type context) 'link) (when (and (eq (org-element-type context) 'link)
(string= "file" type) (string= "file" type)
(org-roam--org-roam-file-p (file-truename path))) (org-roam--org-roam-file-p (file-truename path)))
(org-roam--find-file path) (org-roam-buffer--find-file path)
(org-show-context) (org-show-context)
t))) t)))
;; Org-roam preview text ;; Org-roam preview text
((when-let ((file-from (get-text-property (point) 'file-from)) ((when-let ((file-from (get-text-property (point) 'file-from))
(p (get-text-property (point) 'file-from-point))) (p (get-text-property (point) 'file-from-point)))
(org-roam--find-file file-from) (org-roam-buffer--find-file file-from)
(goto-char p) (goto-char p)
(org-show-context) (org-show-context)
t)) t))
;; If called via `org-open-at-point', fall back to default behavior. ;; If called via `org-open-at-point', fall back to default behavior.
(t nil))) (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) (defun org-roam--get-backlinks (target)
"Return the backlinks for TARGET. "Return the backlinks for TARGET.
TARGET may be a file, for Org-roam file links, or a citation key, TARGET may be a file, for Org-roam file links, or a citation key,
@ -999,34 +1014,21 @@ for Org-ref cite links."
:order-by (asc from)] :order-by (asc from)]
target)) target))
(defun org-roam-store-link-file () (defun org-roam-store-link ()
"Store a link to an `org-roam' file." "Store a link to an Org-roam file or heading."
(when (org-before-first-heading-p) (when (and (bound-and-true-p org-roam-mode)
(when-let ((title (cdr (assoc "TITLE" (org-roam--extract-global-props '("TITLE")))))) (org-roam--org-roam-file-p))
(if (org-before-first-heading-p)
(when-let ((titles (org-roam--extract-titles)))
(org-link-store-props (org-link-store-props
:type "file" :type "file"
:link (format "file:%s" (abbreviate-file-name buffer-file-name)) :link (format "file:%s" (abbreviate-file-name buffer-file-name))
:description title)))) :description (car titles)))
(let ((id (org-id-get)))
(defun org-roam--store-link (arg &optional interactive?) (org-id-store-link)
"Store a link to the current location within Org-roam.
See `org-roam-store-link' for details on ARG and INTERACTIVE?."
(let ((org-id-link-to-org-use-id t)
(id (org-id-get)))
(org-store-link arg interactive?)
;; If :ID: was created, update the cache ;; If :ID: was created, update the cache
(unless id (unless id
(org-roam-db--update-headlines)))) (org-roam-db--update-headlines))))))
(defun org-roam-store-link (arg &optional interactive?)
"Store a link to the current location.
This commands is a wrapper for `org-store-link' which forces the
automatic creation of :ID: properties.
See `org-roam-store-link' for details on ARG and INTERACTIVE?."
(interactive "P\np")
(if (org-roam--org-roam-file-p)
(org-roam--store-link arg interactive?)
(org-store-link arg interactive?)))
(defun org-roam-id-find (id &optional markerp strict) (defun org-roam-id-find (id &optional markerp strict)
"Return the location of the entry with the id ID. "Return the location of the entry with the id ID.
@ -1080,15 +1082,73 @@ This function hooks into `org-open-at-point' via
(t (t
nil))))) nil)))))
;;; The global minor org-roam-mode ;;; Org-roam-mode
;;;; Function Faces
;; These faces are used by `org-link-set-parameters', which take one argument,
;; which is the path.
(defcustom org-roam-link-use-custom-faces 'everywhere
"Define where to apply custom faces to Org-roam links.
Valide values are:
t Use custom faces inside Org-roam notes (i.e. files in
`org-roam-directory'.)
everywhere Apply custom faces everywhere.
Otherwise, do not apply custom faces to Org-roam links."
:type '(choice
(const :tag "Use custom faces inside Org-roam notes" t)
(const :tag "Apply custom faces everywhere" everywhere)
(const :tag "Do not apply custom faces" nil))
:group 'org-roam)
(defun org-roam--file-link-face (path)
"Conditional face for file: links.
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."
(let* ((in-note (-> (buffer-file-name (buffer-base-buffer))
(org-roam--org-roam-file-p)))
(custom (or (and in-note org-roam-link-use-custom-faces)
(eq org-roam-link-use-custom-faces 'everywhere))))
(cond ((and custom
(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))
'org-roam-link-current)
((and custom
(org-roam--org-roam-file-p path))
'org-roam-link)
(t
'org-link))))
(defun org-roam--id-link-face (id)
"Conditional face for id links.
Applies `org-roam-link-current' if ID corresponds to the
currently opened Org-roam file in the backlink buffer, or
`org-roam-link-face' if ID corresponds to any other Org-roam
file."
(cond ((not (org-roam-id-find id))
'org-roam-link-invalid)
((and (org-roam--in-buffer-p)
(org-roam--backlink-to-current-p))
'org-roam-link-current)
((org-roam-id-find id t)
'org-roam-link)
(t
'org-link)))
;;;; Hooks and Advices
(defun org-roam--find-file-hook-function () (defun org-roam--find-file-hook-function ()
"Called by `find-file-hook' when mode symbol `org-roam-mode' is on." "Called by `find-file-hook' when mode symbol `org-roam-mode' is on."
(when (org-roam--org-roam-file-p) (when (org-roam--org-roam-file-p)
(setq org-roam-last-window (get-buffer-window)) (setq org-roam-last-window (get-buffer-window))
(add-hook 'post-command-hook #'org-roam-buffer--update-maybe nil t) (add-hook 'post-command-hook #'org-roam-buffer--update-maybe nil t)
(add-hook 'after-save-hook #'org-roam-db--update-file nil t) (add-hook 'after-save-hook #'org-roam-db--update-file nil t)
(org-link-set-parameters "file" :face 'org-roam--roam-file-link-face :store #'org-roam-store-link-file)
(org-link-set-parameters "id" :face 'org-roam--roam-id-link-face)
(org-roam-buffer--update-maybe :redisplay t))) (org-roam-buffer--update-maybe :redisplay t)))
(defun org-roam--delete-file-advice (file &optional _trash) (defun org-roam--delete-file-advice (file &optional _trash)
@ -1164,13 +1224,15 @@ replaced links are made relative to the current buffer."
new-file-or-dir))) new-file-or-dir)))
(when (and (not (auto-save-file-name-p old-file)) (when (and (not (auto-save-file-name-p old-file))
(not (auto-save-file-name-p new-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) (org-roam-db--ensure-built)
(let* ((old-path (file-truename old-file)) (let* ((old-path (file-truename old-file))
(new-path (file-truename new-file)) (new-path (file-truename new-file))
(old-slug (org-roam--get-title-or-slug old-file)) (old-slug (org-roam--get-title-or-slug old-file))
(old-desc (org-roam--format-link-title old-slug)) (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))) (org-roam--path-to-slug new-path)))
(new-desc (org-roam--format-link-title new-slug)) (new-desc (org-roam--format-link-title new-slug))
(new-buffer (or (find-buffer-visiting new-path) (new-buffer (or (find-buffer-visiting new-path)
@ -1201,7 +1263,8 @@ replaced links are made relative to the current buffer."
(with-current-buffer new-buffer (with-current-buffer new-buffer
(org-roam--fix-relative-links old-path) (org-roam--fix-relative-links old-path)
(save-buffer))) (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 ;;;###autoload
(define-minor-mode org-roam-mode (define-minor-mode org-roam-mode
@ -1220,14 +1283,15 @@ nil, or positive. If ARG is `toggle', toggle `org-roam-mode'.
Otherwise, behave as if called interactively." Otherwise, behave as if called interactively."
:lighter " Org-roam" :lighter " Org-roam"
:keymap (let ((map (make-sparse-keymap))) :keymap (let ((map (make-sparse-keymap)))
(define-key map [remap org-store-link] 'org-roam-store-link)
map) map)
:group 'org-roam :group 'org-roam
:require 'org-roam :require 'org-roam
:global t :global t
(cond (cond
(org-roam-mode (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'. \ (lwarn '(org-roam) :error "Cannot find executable 'sqlite3'. \
Ensure it is installed and can be found within `exec-path'. \ Ensure it is installed and can be found within `exec-path'. \
M-x info for more information at Org-roam > Installation > Post-Installation Tasks.")) M-x info for more information at Org-roam > Installation > Post-Installation Tasks."))
@ -1237,6 +1301,9 @@ M-x info for more information at Org-roam > Installation > Post-Installation Tas
(add-hook 'org-open-at-point-functions #'org-roam-open-id-at-point) (add-hook 'org-open-at-point-functions #'org-roam-open-id-at-point)
(advice-add 'rename-file :after #'org-roam--rename-file-advice) (advice-add 'rename-file :after #'org-roam--rename-file-advice)
(advice-add 'delete-file :before #'org-roam--delete-file-advice) (advice-add 'delete-file :before #'org-roam--delete-file-advice)
(when (fboundp 'org-link-set-parameters)
(org-link-set-parameters "file" :face 'org-roam--file-link-face :store #'org-roam-store-link)
(org-link-set-parameters "id" :face 'org-roam---id-link-face))
(org-roam-db-build-cache)) (org-roam-db-build-cache))
(t (t
(setq org-execute-file-search-functions (delete 'org-roam--execute-file-row-col org-execute-file-search-functions)) (setq org-execute-file-search-functions (delete 'org-roam--execute-file-row-col org-execute-file-search-functions))
@ -1245,11 +1312,13 @@ M-x info for more information at Org-roam > Installation > Post-Installation Tas
(remove-hook 'org-open-at-point-functions #'org-roam-open-id-at-point) (remove-hook 'org-open-at-point-functions #'org-roam-open-id-at-point)
(advice-remove 'rename-file #'org-roam--rename-file-advice) (advice-remove 'rename-file #'org-roam--rename-file-advice)
(advice-remove 'delete-file #'org-roam--delete-file-advice) (advice-remove 'delete-file #'org-roam--delete-file-advice)
(when (fboundp 'org-link-set-parameters)
(dolist (face '("file" "id"))
(org-link-set-parameters face :face 'org-link)))
(org-roam-db--close-all) (org-roam-db--close-all)
;; Disable local hooks for all org-roam buffers ;; Disable local hooks for all org-roam buffers
(dolist (buf (org-roam--get-roam-buffers)) (dolist (buf (org-roam--get-roam-buffers))
(with-current-buffer buf (with-current-buffer buf
(org-link-set-parameters "file" :face 'org-link)
(remove-hook 'post-command-hook #'org-roam-buffer--update-maybe t) (remove-hook 'post-command-hook #'org-roam-buffer--update-maybe t)
(remove-hook 'after-save-hook #'org-roam-db--update-file t)))))) (remove-hook 'after-save-hook #'org-roam-db--update-file t))))))
@ -1284,7 +1353,6 @@ which takes as its argument an alist of path-completions. See
`org-roam--get-title-path-completions' for details." `org-roam--get-title-path-completions' for details."
(interactive) (interactive)
(unless org-roam-mode (org-roam-mode)) (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) (let* ((completions (funcall (or filter-fn #'identity)
(or completions (org-roam--get-title-path-completions)))) (or completions (org-roam--get-title-path-completions))))
(title-with-tags (org-roam-completion--completing-read "File: " completions (title-with-tags (org-roam-completion--completing-read "File: " completions
@ -1292,11 +1360,11 @@ which takes as its argument an alist of path-completions. See
(res (cdr (assoc title-with-tags completions))) (res (cdr (assoc title-with-tags completions)))
(file-path (plist-get res :path))) (file-path (plist-get res :path)))
(if file-path (if file-path
(find-file file-path) (org-roam--find-file file-path)
(let ((org-roam-capture--info `((title . ,title-with-tags) (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)) (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--with-template-error 'org-roam-capture-templates
(org-roam-capture--capture)))))) (org-roam-capture--capture))))))
@ -1304,7 +1372,7 @@ which takes as its argument an alist of path-completions. See
(defun org-roam-find-directory () (defun org-roam-find-directory ()
"Find and open `org-roam-directory'." "Find and open `org-roam-directory'."
(interactive) (interactive)
(find-file org-roam-directory)) (org-roam--find-file org-roam-directory))
;;;###autoload ;;;###autoload
(defun org-roam-find-ref (arg &optional filter) (defun org-roam-find-ref (arg &optional filter)
@ -1326,11 +1394,18 @@ included as a candidate."
:require-match t)) :require-match t))
(file (-> (cdr (assoc ref completions)) (file (-> (cdr (assoc ref completions))
(plist-get :path)))) (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 ;;;###autoload
(defun org-roam-insert (&optional lowercase completions filter-fn description) (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. "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. If LOWERCASE, downcase the title before insertion.
COMPLETIONS is a list of completions to be used instead of COMPLETIONS is a list of completions to be used instead of
`org-roam--get-title-path-completions`. `org-roam--get-title-path-completions`.
@ -1340,11 +1415,16 @@ If DESCRIPTION is provided, use this as the link label. See
`org-roam--get-title-path-completions' for details." `org-roam--get-title-path-completions' for details."
(interactive "P") (interactive "P")
(unless org-roam-mode (org-roam-mode)) (unless org-roam-mode (org-roam-mode))
(let* ((region (and (region-active-p) ;; Deactivate the mark on quit since `atomic-change-group' prevents it
;; following may lose active region, so save it (unwind-protect
(cons (region-beginning) (region-end)))) ;; Group functions together to avoid inconsistent state on quit
(region-text (when region (atomic-change-group
(buffer-substring-no-properties (car region) (cdr region)))) (let* (region-text
beg end
(_ (when (region-active-p)
(setq beg (set-marker (make-marker) (region-beginning)))
(setq end (set-marker (make-marker) (region-end)))
(setq region-text (buffer-substring-no-properties beg end))))
(completions (--> (or completions (completions (--> (or completions
(org-roam--get-title-path-completions)) (org-roam--get-title-path-completions))
(if filter-fn (if filter-fn
@ -1360,23 +1440,49 @@ If DESCRIPTION is provided, use this as the link label. See
(link-description (org-roam--format-link-title (if lowercase (link-description (org-roam--format-link-title (if lowercase
(downcase description) (downcase description)
description)))) description))))
(if (and target-file-path (cond ((and target-file-path
(file-exists-p target-file-path)) (file-exists-p target-file-path))
(progn (when region-text
(when region ;; Remove previously selected text. (delete-region beg end)
(delete-region (car region) (cdr region))) (set-marker beg nil)
(set-marker end nil))
(insert (org-roam--format-link target-file-path link-description))) (insert (org-roam--format-link target-file-path link-description)))
(when (org-roam-capture--in-process-p) (t
(user-error "Nested Org-roam capture processes not supported"))
(let ((org-roam-capture--info `((title . ,title-with-tags) (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)) (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 (org-roam-shield-region beg end)
(setq org-roam-capture-additional-template-props (list :region region :insert-at (point-marker)
:link-description link-description :link-description link-description
:capture-fn 'org-roam-insert)) :finalize 'insert-link))
(org-roam--with-template-error 'org-roam-capture-templates (org-roam--with-template-error 'org-roam-capture-templates
(org-roam-capture--capture)))))) (org-roam-capture--capture)))))
res))
(deactivate-mark)))
;;;###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 ;;;###autoload
(defun org-roam-jump-to-index () (defun org-roam-jump-to-index ()
@ -1390,7 +1496,7 @@ command will offer you to create one."
(let ((index (org-roam--get-index-path))) (let ((index (org-roam--get-index-path)))
(if (and index (if (and index
(file-exists-p 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? ") (when (y-or-n-p "Index file does not exist. Would you like to create it? ")
(org-roam-find-file "Index"))))) (org-roam-find-file "Index")))))
@ -1438,7 +1544,7 @@ linked, lest the network graph get too crowded."
(unless (org-roam--org-roam-file-p) (unless (org-roam--org-roam-file-p)
(user-error "Not in org-roam file")) (user-error "Not in org-roam file"))
(if (not (executable-find "rg")) (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)) (let* ((titles (org-roam--extract-titles))
(rg-command (concat "rg -o --vimgrep -P -i " (rg-command (concat "rg -o --vimgrep -P -i "
(string-join (mapcar (lambda (glob) (concat "-g " glob)) (string-join (mapcar (lambda (glob) (concat "-g " glob))
@ -1451,7 +1557,7 @@ linked, lest the network graph get too crowded."
(file-loc (buffer-file-name)) (file-loc (buffer-file-name))
(buf (get-buffer-create "*org-roam unlinked references*")) (buf (get-buffer-create "*org-roam unlinked references*"))
(results (split-string (shell-command-to-string rg-command) "\n")) (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)) (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 ;; Copyright (C) 2020 Jethro Kuan
;; Author: Jethro Kuan <jethrokuan95@gmail.com> ;; 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 ;; 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 ;; it under the terms of the GNU General Public License as published by
@ -211,6 +211,20 @@
:to-equal :to-equal
'("deeply"))) '("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" (describe "uses org-roam-tag-sources correctly"
(it "'(prop)" (it "'(prop)"
(expect (let ((org-roam-tag-sources '(prop))) (expect (let ((org-roam-tag-sources '(prop)))
@ -225,6 +239,26 @@
:to-equal :to-equal
'("t1" "t2 with space" "t3" "tags")))))) '("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 ;;; Tests
(xdescribe "org-roam-db-build-cache" (xdescribe "org-roam-db-build-cache"
(before-each (before-each