Compare commits

..

48 Commits

Author SHA1 Message Date
cc01cf346e (release): v1.2.3 (#1269) 2020-11-13 14:22:18 +08:00
eaf99cba03 (doc): minor changes (#1268) 2020-11-13 13:17:28 +08:00
65a2cb6efd (fix): readme: fix link to manual 2020-11-13 02:52:48 +08:00
bc12d1cf04 (feat): add org-roam-db-update-method (#1264)
* (feat): add `org-roam-db-update-method`

Add `org-roam-db-update-method`, which can be one of two choices:

1. `immediate`: the cache is updated upon file changes
2. `idle-timer`: Marks the org-roam database as dirty. If Emacs idles
for some seconds, the Org-roam cache is updated. This is the default,
and current behaviour.

The idle method makes for a smoother editing experience, but some
inconsistencies can be faced.

* org-roam-update-db-idle-seconds -> org-roam-db-update-idle-seconds
2020-11-12 19:48:40 +08:00
a86d82b20e (fix): fix backlink-to-current-p killing buffers (#1263)
Change `org-roam--backlink-to-current-p` to only perform a DB query.
2020-11-12 16:11:35 +08:00
d4c875b53b (doc): doc stylistic changes (#1262)
- style tables properly
- reduce pre font-size
2020-11-12 15:40:27 +08:00
2159b6a846 (web): Remove mention to company-org-roam on index.html (#1261) 2020-11-12 08:27:22 +01:00
1db4c22950 (doc): remove top-level TOC (#1260) 2020-11-12 15:13:59 +08:00
9c0f030ffd (doc): add indexes (#1259)
Add keystroke, command, function and variable indexes
2020-11-12 14:56:48 +08:00
983d7a8798 (doc): Link org-journal to section on daily-notes (#1258) 2020-11-12 07:47:44 +01:00
910b37268e (doc): make manual single-page (#1257)
This should make things easier to browse/search for. Also simplify styling.
2020-11-12 14:32:59 +08:00
d39556a78b (fix): unlinked-references: support filenames with spaces (#1256) 2020-11-12 12:05:14 +08:00
167553b8ee (doc): various improvements (#1253)
- options: explicitly enable smart quotes on export
- zettelkasten introduction: add sections for fleeting notes and
  permanent notes for easier access and reference
- anatomy: add a missing period
- encryption: clarify org-roam-encrypt-files is a user option
- roam protocol: remove extraneous heading
- roam protocol: small grammar fix
- doctor: explain what the doctor does by default
- org-roam-bibtex: explain part of what orb actually does
2020-11-11 18:04:09 +08:00
76affe177a (doc): reflect that we now allow multiple refs (#1251) 2020-11-11 15:42:45 +08:00
e96685b1a9 (fix): respect original link type during replacement (#1252)
During automatic link replacement, respect the original link type
i.e. absolute links remain absolute. Fixes #1228.
2020-11-11 14:25:50 +08:00
aef71f1623 (docs): use ox-texinfo+ (#1250)
This generates texi documents with understanding of variables and
functions.
2020-11-11 13:00:49 +08:00
d913447939 (fix): Fix command signature (#1247) 2020-11-10 18:04:59 +01:00
47e83f7d3f (fix): Deprecate old ord commands (#1246) 2020-11-10 17:57:26 +01:00
023bcce867 (internal): remove unnecessary variables (#1244)
For captures, enforce that `:file-name` needs to be specified, and
`:head` when not provided defaults to the empty string (i.e. no head)
2020-11-10 23:58:16 +08:00
4f6eb285bf (feat): Overhaul org-roam-dailies (#978)
Implement ideas from `org-journal` to `org-roam-dailies`.

Update the doc.
2020-11-10 14:31:38 +01:00
8c81104816 (feat): unlinked-references: add error message if rg has no PCRE2 support (#1243) 2020-11-10 13:48:50 +08:00
7602b8c48d (feat): Allow ORP to capture the webpage's selection (#1239)
* (feat): Temporarily store link when capturing with ORP

* org-roam-protocol.el (org-roam-protocol-open-ref): Replicate default
`org-protocol' behaviour temporarily for storing links
* org-roam-capture.el (org-roam-capture--capture): Prevent stored link from
being reset

When capturing a web-page with org-roam-protocol, a link is now temporarily
stored in `org-store-link-plist' via `org-link-store-props'.  This is to allow
the forwarding of properties to `org-capture', one of them being `:initial'
which contains the content of the selected text in the browser.

* (feat): Add toggle for storing link when capturing with ORP

* org-roam-protocol.el (org-roam-protocol-store-links): Add new toggle
(org-roam-protocol-open-ref): Conditionally store link for later used

Building up on b2ee5f2c68, the user can now
decide whether to store links when capturing with org-roam-protocol (default
nil).

* Update changelog
2020-11-08 13:30:16 +01:00
c6797cbd75 (feat): Allow one file to have multiple roam_key statements (#1215) 2020-11-07 15:33:31 +08:00
440461a90b (feat): add org-roam-prefer-id-links to select linking method (#1238) 2020-11-07 15:06:12 +08:00
4d423a916e (doc): add note on unlinked references and encrypted files (#1236) 2020-11-05 16:34:11 +08:00
b184cdaef0 (feat): use :preselect for ivy-read completions (#1234)
This way, in case of the initial-input being an exact match, ivy will suggest this one first.
2020-11-04 17:37:50 +08:00
b2cc997976 (fix): fix relative link replacement (#1233) 2020-11-04 15:25:08 +08:00
bc5c41d212 (fix): widen before title extraction (#1232)
Prevent renaming file and changing links incorrectly due to using (org-narrow-to-subtree) and saving.
2020-11-04 13:34:40 +08:00
7c83a84db3 (fix): only update relative path of file links (#1226) 2020-11-04 00:07:31 +08:00
56c47fbff8 (internal): make sure there are no byte-comp warnings... (#1220)
...about undefined functions or unused lexical variables.

Undefined functions: for example, s.el and org-element should be
`require`'d when their functions are used.

Unused lexical variables: if Org isn't loaded yet, dynamic variables
defined in org.el would be treated as lexical and byte-comp would emit
this warning. This is especially important in the future as
native-comp / gccemacs will optimize away unused lexical variables,
and we cannot rely on Org having been implicitly loaded before our
modules are compiled.

Explicitly stating in our modules that the variables are dynamic
prevents that.
2020-10-30 14:23:53 +08:00
0d235686f4 (fix): fix id-face killing buffers (#1218)
org-roam-id-get-file no longer falls back onto the current buffer: It queries the org-roam database, and optionally org-id-locations, and declares that it does not exist if it isn't in either.
2020-10-27 00:25:39 +08:00
ac2044b84b (fix): save current position in mark ring before id-open (#1217) 2020-10-26 13:36:35 +08:00
cffa0bd201 (fix): support multidir with dirty db flag (#1216) 2020-10-26 11:22:40 +08:00
bd8b5587f5 (internal): rename link columns (#1213)
from -> source
to -> dest

"from" is a reserved keyword in sqlite, so we avoid it.
2020-10-25 22:33:43 +08:00
b937bc9655 (internal): simplify db update operations (#1212)
Instead of maintaining a file queue to process for updating the Org-roam
database, we instead simply call `org-roam-db-build-cache` to rebuild
the db. `org-roam-db-build-cache` feels Fast Enough(TM), and basically
runs instantly if no files are modified. This greatly simplifies the
code, basically allowing to maintaining a single code path for db
operations.

This PR should also address the slowness wrt to ID links. Org-roam now
builds the headline information first, so there is no need to read the
file to check if the headline is there. The downside to this is that
this only works for IDs in Org-roam files.
2020-10-25 16:35:05 +08:00
a7cf48ea89 (fix): org-roam-db-build-cache: fix order of processing (#1201)
* (fix): org-roam-db-build-cache: fix order of processing

Org-roam used to perform the removal of deleted files towards the end.
This can cause some issues with db rebuilds. Consider this scenario:

1. Create a file `foo.org` with `id:abc`
2. Run `org-roam-db-build-cache`
3. Delete `foo.org`
4. Create a file `bar.org` with `id:abc`
5. Run `org-roam-db-build-cache`

Here Org-roam will complain that the id `abc` is a duplicate, and error
out, because the data for `foo.org` has not yet been cleared from the
database. This PR reorders the db creation steps the following way:

1. Figure out which files no longer exist, and which files are modified
2. Clear the database for these files, leaving only files that are
unmodified in the database
3. Insert new data from modified files into the database
2020-10-19 22:12:34 +08:00
46327991ef (fix): let-bind org-roam-last-window (#1198) (#1200)
* org-roam-buffer.el (org-roam-buffer--find-file): Make
org-roam-buffer--find-file insensitive to mutation of global
variables
2020-10-19 21:14:55 +08:00
a4da8f32bf (fix): use org-roam-link-title-format in link replacement (#1199)
* (fix): use org-roam-link-title-format in link replacement

Makes `org-roam-format-link` respect `org-roam-link-title-format`.

Also removes lowercasing via prefix argument in `org-roam-insert`, not
sure how many people use it.

* update changelog
2020-10-19 21:02:25 +08:00
5d483f2d4d (fix): fall back to org-id search ID face computation (#1195)
* (fix): fall back to org-id search ID face computation

Fixes the scenario where the face is reported as invalid although it is
part of Org's ID files (e.g. in the agenda).

Fixes #1191.

This can however slow face computation a lot (when there are many org
id/agenda files). Here, we choose to focus on correctness.

* Document faces, default to only applying to Org-roam notes
2020-10-19 13:47:07 +08:00
09fd41ce24 (fix): do not kill temp buffer when extracting links for file (#1193)
Otherwise it breaks `org-roam-db-build-cache` as it relies on temporary buffer.
Without keeping the buffer, `org-roam--extract-links` kills it and any further
functionality of file processing breaks.
2020-10-19 12:12:36 +08:00
a0c4abf579 (fix): allow title changes that don't modify filename (#1189)
Prevent error when trying to move file to same name

Prevent string matching in org from messing with the current re-search-forward
position which has caused infinite broken search
2020-10-16 02:19:15 +08:00
cbf1b585ac (doc): fix org-roam-buffer-window-parameters documentation (#1190)
Closes #1188
2020-10-16 00:23:47 +08:00
66cd5b6226 (internal): simplify internal db cache update api (#1186) 2020-10-12 21:55:32 +08:00
5348654a7e (feat): add interactive functions for managing aliases and tags (#1183) 2020-10-12 14:51:10 +08:00
87d7c07e87 (fix): correct usage of org-element api (#1181)
content-end -> contents-end
2020-10-11 14:52:27 +08:00
02fda3adb1 (docs): document org-roam-buffer-window-parameters (#1180)
also remove documentation on deprecated variable ~org-roam-buffer-no-delete-other-windows~
2020-10-10 22:28:29 +08:00
82bd6c6cda (fix): fix org-roam--extract-links (#1179)
Co-authored-by: Boris Buliga <boris@d12frosted.io>
Co-authored-by: Gustav <gustav@whil.se>
2020-10-10 21:34:16 +08:00
e8d3516fa8 (fix): fix org-roam--extract-ids at outline level 0 (#1174) 2020-10-09 22:00:58 +08:00
26 changed files with 2213 additions and 1505 deletions

2
.gitignore vendored
View File

@ -12,4 +12,4 @@
/doc/mimetype /doc/mimetype
/doc/stats/ /doc/stats/
/config.mk /config.mk
/doc/manual/ /doc/manual.html

View File

@ -1,5 +1,33 @@
# Changelog # Changelog
## 1.2.3 (13-11-2020)
Primarily a stabilization and bug-fix release.
Org-roam-dailies has also been revamped to include new features, see [this video](https://www.youtube.com/watch?v=1q9x2aZCJJ4) for a quick overview.
### Added
- [#978](https://github.com/org-roam/org-roam/pull/978) Revamp org-roam-dailies
- [#1183](https://github.com/org-roam/org-roam/pull/1183) Interactive functions for managing aliases and tags in Org-roam file, namely `org-roam-alias-add`, `org-roam-alias-delete`, `org-roam-tag-add`, and `org-roam-tag-delete`.
- [#1215](https://github.com/org-roam/org-roam/pull/1215) Multiple `ROAM_KEY` keywords can now be specified in one file. This allows bibliographical entries to share the same note file.
- [#1238](https://github.com/org-roam/org-roam/pull/1238) Add `org-roam-prefer-id-links` variable to select linking method
- [#1239](https://github.com/org-roam/org-roam/pull/1239) Allow `org-roam-protocol` to capture the webpage's selection, and add a toggle for storing the links to the pages
- [#1264](https://github.com/org-roam/org-roam/pull/1264) add `org-roam-db-update-method` to control when the cache is rebuilt.
### Changed
- [#1264](https://github.com/org-roam/org-roam/pull/1264) renamed `org-roam-update-db-idle-seconds` to `org-roam-db-idle-idle-seconds`
### Fixed
- [#1074](https://github.com/org-roam/org-roam/issues/1074) fix `org-roam--extract-links` to handle content boundaries.
- [#1193](https://github.com/org-roam/org-roam/issues/1193) fix `org-roam-db-build-cache` by not killing temporary buffer in `org-roam--extract-links`.
- [#1195](https://github.com/org-roam/org-roam/issues/1195) fix ID face showing as invalid if within Org ID files, but not Org-roam's.
- [#1199](https://github.com/org-roam/org-roam/issues/1199) make Org-roam link insertions respect `org-roam-link-title-format` everywhere.
- [#1201](https://github.com/org-roam/org-roam/issues/1201) fix `org-roam-db-build-cache` failing in scenarios involving duplicate IDs and deleted files.
- [#1226](https://github.com/org-roam/org-roam/issues/1226) only update relative path of file links
- [#1232](https://github.com/org-roam/org-roam/issues/1232) fix incorrect title extractions from narrowed buffers
- [#1233](https://github.com/org-roam/org-roam/issues/1233) fixes bug where descriptive file links become plain links during update for relative paths
- [#1252](https://github.com/org-roam/org-roam/issues/1252) respect original link type during automatic replacement
## 1.2.2 (06-10-2020) ## 1.2.2 (06-10-2020)
In this release we support fuzzy links of the form `[[roam:Title]]`, `[[roam:*Headline]]` and `[[roam:Title*Headline]]`. Completion for these fuzzy links is supported via `completion-at-point`. In this release we support fuzzy links of the form `[[roam:Title]]`, `[[roam:*Headline]]` and `[[roam:Title*Headline]]`. Completion for these fuzzy links is supported via `completion-at-point`.

View File

@ -121,7 +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://www.orgroam.com/manual/ [docs]: https://www.orgroam.com/manual.html
[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 [issues]: https://github.com/org-roam/org-roam/issues

View File

@ -28,11 +28,8 @@ dir: org-roam.info
@$(MAKEINFO) --html --no-split $(MANUAL_HTML_ARGS) $< @$(MAKEINFO) --html --no-split $(MANUAL_HTML_ARGS) $<
html-dir: html-dir:
@printf "Generating org-roam/*.html\n" @$(MAKEINFO) --html --no-split $(MANUAL_HTML_ARGS) org-roam.texi
@$(MAKEINFO) --html $(MANUAL_HTML_ARGS) org-roam.texi mv org-roam.html manual.html
mv org-roam manual
cp -r assets manual
cp -r images manual
%.pdf: %.texi %.pdf: %.texi
@printf "Generating $@\n" @printf "Generating $@\n"

View File

@ -1,442 +1,59 @@
/* Import Inter font */
/* More info at https://github.com/xz/fonts */
@import url("https://fonts.xz.style/serve/inter.css");
:root { :root {
--nc-font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, --border: #526980;
Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif, --code: #007;
"Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
--nc-font-mono: "Courier New", Courier, "Ubuntu Mono", "Liberation Mono",
monospace;
--nc-tx-1: #000000;
--nc-tx-2: #1a1a1a;
--nc-bg-1: #ffffff;
--nc-bg-2: #f6f8fa;
--nc-bg-3: #e5e7eb;
--nc-lk-1: #0070f3;
--nc-lk-2: #0366d6;
--nc-lk-tx: #ffffff;
--nc-ac-1: #79ffe1;
--nc-ac-tx: #0c4047;
}
@media (prefers-color-scheme: dark) {
:root {
--nc-tx-1: #ffffff;
--nc-tx-2: #eeeeee;
--nc-bg-1: #000000;
--nc-bg-2: #111111;
--nc-bg-3: #222222;
--nc-lk-1: #3291ff;
--nc-lk-2: #0070f3;
--nc-lk-tx: #ffffff;
--nc-ac-1: #7928ca;
--nc-ac-tx: #ffffff;
}
}
* {
/* Reset margins and padding */
margin: 0;
padding: 0;
}
address,
area,
article,
aside,
audio,
blockquote,
datalist,
details,
dl,
fieldset,
figure,
form,
input,
iframe,
img,
meter,
nav,
ol,
optgroup,
option,
output,
p,
pre,
progress,
ruby,
section,
table,
textarea,
ul,
video {
/* Margins for most elements */
margin-bottom: 1rem;
}
html,
input,
select,
button {
/* Set body font family and some finicky elements */
font-family: var(--nc-font-sans);
} }
body { body {
/* Center body in page */ margin: 5ex 10ex;
margin: 0 auto; max-width: 80ex;
max-width: 750px; line-height: 1.5;
padding: 2rem; font-family: sans-serif;
border-radius: 6px;
overflow-x: hidden;
background: var(--nc-bg-1);
/* Main body text */
color: var(--nc-tx-2);
font-size: 1.03rem;
line-height: 1.5;
} }
::selection { h1, h2, h3 {
/* Set background color for selected text */ font-weight: normal;
background: var(--nc-ac-1);
color: var(--nc-ac-tx);
} }
p { pre, code {
margin-bottom: 1rem; font-family: x, monospace;
}
h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1;
color: var(--nc-tx-1);
padding-top: 0.875rem;
}
h1,
h2,
h3 {
color: var(--nc-tx-1);
padding-bottom: 2px;
margin-bottom: 8px;
border-bottom: 1px solid var(--nc-bg-2);
}
h4,
h5,
h6 {
margin-bottom: 0.3rem;
}
h1 {
font-size: 2.25rem;
}
h2 {
font-size: 1.85rem;
}
h3 {
font-size: 1.55rem;
}
h4 {
font-size: 1.25rem;
}
h5 {
font-size: 1rem;
}
h6 {
font-size: 0.875rem;
}
a {
color: var(--nc-lk-1);
}
a:hover {
color: var(--nc-lk-2);
}
abbr:hover {
/* Set the '?' cursor while hovering an abbreviation */
cursor: help;
}
blockquote {
padding: 1.5rem;
background: var(--nc-bg-2);
border-left: 5px solid var(--nc-bg-3);
}
abbr {
cursor: help;
}
blockquote *:last-child {
padding-bottom: 0;
margin-bottom: 0;
}
header {
background: var(--nc-bg-2);
border-bottom: 1px solid var(--nc-bg-3);
padding: 2rem 1.5rem;
/* This sets the right and left margins to cancel out the body's margins. It's width is still the same, but the background stretches across the page's width. */
margin: -2rem calc(0px - (50vw - 50%)) 2rem;
/* Shorthand for:
margin-top: -2rem;
margin-bottom: 2rem;
margin-left: calc(0px - (50vw - 50%));
margin-right: calc(0px - (50vw - 50%)); */
padding-left: calc(50vw - 50%);
padding-right: calc(50vw - 50%);
}
header h1,
header h2,
header h3 {
padding-bottom: 0;
border-bottom: 0;
}
header > *:first-child {
margin-top: 0;
padding-top: 0;
}
header > *:last-child {
margin-bottom: 0;
}
.button,
button,
input[type="submit"],
input[type="reset"],
input[type="button"] {
font-size: 1rem;
display: inline-block;
padding: 6px 12px;
text-align: center;
text-decoration: none;
white-space: nowrap;
background: var(--nc-lk-1);
color: var(--nc-lk-tx);
border: 0;
border-radius: 4px;
box-sizing: border-box;
cursor: pointer;
color: var(--nc-lk-tx);
}
.button[disabled],
button[disabled],
input[type="submit"][disabled],
input[type="reset"][disabled],
input[type="button"][disabled] {
cursor: default;
opacity: 0.5;
/* Set the [X] cursor while hovering a disabled link */
cursor: not-allowed;
}
.button:focus,
.button:hover,
button:focus,
button:hover,
input[type="submit"]:focus,
input[type="submit"]:hover,
input[type="reset"]:focus,
input[type="reset"]:hover,
input[type="button"]:focus,
input[type="button"]:hover {
background: var(--nc-lk-2);
}
code,
pre,
kbd,
samp {
/* Set the font family for monospaced elements */
font-family: var(--nc-font-mono);
}
code,
samp,
kbd,
pre {
/* The main preformatted style. This is changed slightly across different cases. */
background: var(--nc-bg-2);
border: 1px solid var(--nc-bg-3);
border-radius: 4px;
padding: 3px 6px;
font-size: 0.9rem;
}
kbd {
/* Makes the kbd element look like a keyboard key */
border-bottom: 3px solid var(--nc-bg-3);
} }
pre { pre {
padding: 1rem 1.4rem; padding: 1ex;
max-width: 100%; background: #eee;
overflow: auto; border: solid 1px #ddd;
min-width: 0;
font-size: 80%;
overflow: auto;
} }
pre code { code {
/* When <code> is in a <pre>, reset it's formatting to blend in */ color: var(--code);
background: inherit;
font-size: inherit;
color: inherit;
border: 0;
padding: 0;
margin: 0;
}
code pre {
/* When <pre> is in a <code>, reset it's formatting to blend in */
display: inline;
background: inherit;
font-size: inherit;
color: inherit;
border: 0;
padding: 0;
margin: 0;
}
details {
/* Make the <details> look more "clickable" */
padding: 0.6rem 1rem;
background: var(--nc-bg-2);
border: 1px solid var(--nc-bg-3);
border-radius: 4px;
}
summary {
/* Makes the <summary> look more like a "clickable" link with the pointer cursor */
cursor: pointer;
font-weight: bold;
}
details[open] {
/* Adjust the <details> padding while open */
padding-bottom: 0.75rem;
}
details[open] summary {
/* Adjust the <details> padding while open */
margin-bottom: 6px;
}
details[open] > *:last-child {
/* Resets the bottom margin of the last element in the <details> while <details> is opened. This prevents double margins/paddings. */
margin-bottom: 0;
}
dt {
font-weight: bold;
}
dd::before {
/* Add an arrow to data table definitions */
content: "→ ";
}
hr {
/* Reset the border of the <hr> separator, then set a better line */
border: 0;
border-bottom: 1px solid var(--nc-bg-3);
margin: 1rem auto;
}
fieldset {
margin-top: 1rem;
padding: 2rem;
border: 1px solid var(--nc-bg-3);
border-radius: 4px;
}
legend {
padding: auto 0.5rem;
}
textarea {
/* Don't let the <textarea> extend off the screen naturally or when dragged by the user */
max-width: 100%;
}
ol,
ul {
/* Replace the browser default padding */
padding-left: 2rem;
}
li {
margin-top: 0.4rem;
}
ul ul,
ol ul,
ul ol,
ol ol {
margin-bottom: 0;
}
mark {
padding: 3px 6px;
background: var(--nc-ac-1);
color: var(--nc-ac-tx);
}
textarea,
select,
input {
padding: 6px 12px;
margin-bottom: 0.5rem;
background: var(--nc-bg-2);
color: var(--nc-tx-2);
/* Set a border of the same color as the main background. It isn't visible on idle, but prevents the cell from growing in size when a darker border is set on focus. */
border: 1px solid var(--nc-bg-2);
border-radius: 4px;
box-shadow: none;
box-sizing: border-box;
}
textarea:focus,
select:focus,
input[type]:focus {
border: 1px solid var(--nc-bg-3);
/* Reset any browser default outlines */
outline: 0;
} }
img { img {
max-width: 100%; max-width: 100%;
} }
/* Customizations */ table {
.menu-comment { border-collapse: collapse;
font-weight: bold; width: 100%;
border: 0; }
margin: 0;
padding: 0; thead {
font-size: 1.2rem; border-bottom: 1px solid var(--border);
}
tfoot {
border-top: 1px solid var(--border);
}
blockquote {
margin-left: 1rem;
font-style: italic;
font-family: serif;
border-left: 3px solid;
border-left-color: currentcolor;
border-color: var(--text-color);
padding-left: 1em;
} }

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://www.orgroam.com/manual/">Online Manual</a></li> <li>Read our documentation within Emacs, or on the <a href="https://www.orgroam.com/manual.html">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>
@ -120,13 +120,6 @@
>org-roam-server</a >org-roam-server</a
> >
</div> </div>
<div class="row">
<a
class="content footer-links"
href="https://github.com/org-roam/company-org-roam"
>company-org-roam</a
>
</div>
</div> </div>
<div class="col"> <div class="col">

View File

@ -1,20 +1,20 @@
#+title: Org-roam User Manual #+title: Org-roam User Manual
:PREAMBLE:
#+author: Jethro Kuan #+author: Jethro Kuan
#+email: jethrokuan95@gmail.com #+email: jethrokuan95@gmail.com
#+date: 2020-2020 #+date: 2020-2020
#+language: en #+language: en
#+texinfo_deffn: t
#+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.2 #+subtitle: for version 1.2.3
#+options: H:4 num:3 toc:2 creator:t #+options: H:4 num:3 toc:nil creator:t ':t
#+property: header-args :eval never #+property: header-args :eval never
#+texinfo: @noindent #+texinfo: @noindent
This manual is for Org-roam version 1.2.2. This manual is for Org-roam version 1.2.3.
#+BEGIN_QUOTE #+BEGIN_QUOTE
Copyright (C) 2020-2020 Jethro Kuan <jethrokuan95@gmail.com> Copyright (C) 2020-2020 Jethro Kuan <jethrokuan95@gmail.com>
@ -29,20 +29,19 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details. General Public License for more details.
#+END_QUOTE #+END_QUOTE
:END:
* Introduction * Introduction
Org-roam is a [[https://roamresearch.com/][Roam Research]] replica built around the Org-roam is a tool for network thought. It reproduces some of [[https://roamresearch.com/][Roam
all-powerful [[https://orgmode.org/][Org-mode]]. Research's]] [fn:roam] features within the all-powerful [[https://orgmode.org/][Org-mode]].
Org-roam is a solution for effortless non-hierarchical note-taking Org-roam is a solution for effortless non-hierarchical note-taking with
with Org-mode. With Org-roam, notes flow naturally, making note-taking Org-mode. With Org-roam, notes flow naturally, making note-taking fun and easy.
fun and easy. Org-roam should also work as a plug-and-play solution Org-roam keeps closely to Org syntax, and will work for anyone already using
for anyone already using Org-mode for their personal wiki. Org-mode for their personal wiki.
To understand more about Roam, a collection of links are available in Org-roam gains its superpowers by leveraging the mature ecosystem around
[[*Note-taking Workflows][Note-taking Workflows]]. Org-mode. For example, it has first-class support for [[https://github.com/jkitchin/org-ref][org-ref]] for citation
management.
Org-roam aims to implement the core features of Roam, leveraging the Org-roam aims to implement the core features of Roam, leveraging the
mature ecosystem around Org-mode where possible. Eventually, we hope mature ecosystem around Org-mode where possible. Eventually, we hope
@ -50,11 +49,23 @@ to further introduce features enabled by the Emacs ecosystem.
Org-roam provides several benefits over other tooling: Org-roam provides several benefits over other tooling:
- Privacy and Security :: Edit your personal wiki completely offline, entirely in your control. Encrypt your notes with GPG. - *Privacy and Security:* Keep your personal wiki entirely offline and in your
- Longevity of Plain Text :: Unlike web solutions like Roam research, the notes are first and foremost plain Org-mode files -- Org-roam simply builds up an auxilliary database to give the personal wiki superpowers. Having your notes in plain-text is crucial for the longevity of your wiki. Never have to worry about proprietary web solutions being taken down. Edit your plain-text notes in notepad if all other editors cease to exist control. Encrypt your notes with GPG.
- Free and Open Source :: Org-roam is free and open-source, which means that if you feel unhappy with any part of Org-roam, you may choose to extend Org-roam, or open a PR. - *Longevity of Plain Text:* Unlike web solutions like Roam Research, the notes
- 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. are first and foremost plain Org-mode files -- Org-roam simply builds an
- 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. auxiliary database to give the personal wiki superpowers. Having your notes
in plain-text is crucial for the longevity of your wiki. Never have to worry
about proprietary web solutions being taken down. The notes are still
functional even if Org-roam ceases to exist.
- *Free and Open Source:* Org-roam is free and open-source, which means that if
you feel unhappy with any part of Org-roam, you may choose to extend Org-roam,
or open a pull request.
- *Leverage 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.
* Target Audience * Target Audience
@ -94,38 +105,44 @@ Org-roam provides utilities for maintaining a digital slip-box. This section
aims to provide a brief introduction to the "slip-box", or "Zettelkasten" aims to provide a brief introduction to the "slip-box", or "Zettelkasten"
method. By providing some background on the method, we hope that the design method. By providing some background on the method, we hope that the design
decisions of Org-roam will become clear, and that will aid in using Org-roam decisions of Org-roam will become clear, and that will aid in using Org-roam
appropriately. In this section we will also introduce terms commonly used within appropriately. In this section we will introduce terms commonly used within the
the Zettelkasten community, which will also commonly appear in the Org-roam Zettelkasten community and the Org-roam forums.
forums and channels of discussion.
The Zettelkasten method of note-taking is designed to increase research The Zettelkasten is a personal tool for thinking and writing. It places heavy
productivity: in particular, it acts as a research partner, where conversations emphasis on connecting ideas, building up a web of thought. Hence, it is well
with it may produce new and surprising lines of thought. This method is suited for knowledge workers and intellectual tasks, such as conducting
attributed to German sociologist Niklas Luhmann, who using the method had research. The Zettelkasten can act as a research partner, where conversations
produced volumes of written works. with it may produce new and surprising lines of thought.
In its paper form, the slip-box is simply a box of cards. These cards are small This method is attributed to German sociologist Niklas Luhmann, who using the
-- often only large enough to fit a single concept. The size limitation method had produced volumes of written works. Luhmann's slip-box was simply a
encourages ideas to be broken down into individual concepts. These ideas are box of cards. These cards are small -- often only large enough to fit a single
explicitly linked together. The breakdown of ideas encourages tangential concept. The size limitation encourages ideas to be broken down into individual
exploration of ideas, increasing the surface for thought. Making linking concepts. These ideas are explicitly linked together. The breakdown of ideas
explicit between notes also encourages one to think about the connections encourages tangential exploration of ideas, increasing the surface for thought.
between concepts. Making linking explicit between notes also encourages one to think about the
connections between concepts.
At the corner of each note, Luhmann ascribed each note with an ordered ID,
allowing him to link and jump between notes. In Org-roam, we simply use
hyperlinks.
Org-roam is the slip-box, digitalized in Org-mode. Every zettel (card) is a Org-roam is the slip-box, digitalized in Org-mode. Every zettel (card) is a
plain-text, Org-mode file. These files are often placed in the same directory. plain-text, Org-mode file. In the same way one would maintain a paper slip-box,
In the same way one would maintain a paper slip-box, Org-roam makes it easy to Org-roam makes it easy to create new zettels, pre-filling boilerplate content
create new zettels, pre-filling boilerplate content using a powerful templating using a powerful templating system.
system. Org-roam also facilitates the linking of zettels using Org-mode ~file:~
links.
A slip-box requires a method of quickly capturing ideas. These are called ** Fleeting notes
A slip-box requires a method for quickly capturing ideas. These are called
*fleeting notes*: they are simple reminders of information or ideas that will *fleeting notes*: they are simple reminders of information or ideas that will
need to be processed later on, or trashed. This is typically accomplished using need to be processed later on, or trashed. This is typically accomplished using
~org-capture~ (see info:org#capture), or using Org-roam's daily notes ~org-capture~ (see info:org#capture), or using Org-roam's daily notes
functionality (see [[*Daily Notes][Daily Notes]]). This provides a central inbox for collecting functionality (see [[*Daily-notes][Daily-notes]]). This provides a central inbox for collecting
thoughts, to be processed later into permanent notes. thoughts, to be processed later into permanent notes.
** Permanent notes
Permanent notes are further split into two categories: *literature notes* and Permanent notes are further split into two categories: *literature notes* and
*concept notes*. Literature notes can be brief annotations on a particular *concept notes*. Literature notes can be brief annotations on a particular
source (e.g. book, website or paper), that you'd like to access later on. source (e.g. book, website or paper), that you'd like to access later on.
@ -197,9 +214,16 @@ Org-roam will then be autoloaded into Emacs.
** Installing from the Git Repository ** Installing from the Git Repository
You may install Org-roam directly from the repository on [[https://github.com/org-roam/org-roam][GitHub]] if you like. This will give you access to the latest version hours or days before it appears on MELPA, and months (or more) before it is added to the Debian or Ubuntu repositories. This will also give you access to various developmental branches that may be available. You may install Org-roam directly from the repository on [[https://github.com/org-roam/org-roam][GitHub]] if you like.
This will give you access to the latest version hours or days before it appears
on MELPA, and months (or more) before it is added to the Debian or Ubuntu
repositories. This will also give you access to various developmental branches
that may be available.
Note, however, that development version, and especially any feature branches, may not always be in working order. You'll need to be prepared to do some debugging, or to manually roll-back to working versions, if you install from GitHub. Note, however, that development version, and especially any feature branches,
may not always be in working order. You'll need to be prepared to do some
debugging, or to manually roll-back to working versions, if you install from
GitHub.
Installing from GitHub requires that you clone the repository: Installing from GitHub requires that you clone the repository:
@ -209,14 +233,16 @@ git clone https://github.com/org-roam/org-roam.git /path/to/org/roam
where ~./path/to/org/roam~ is the location you will store your copy of the code. where ~./path/to/org/roam~ is the location you will store your copy of the code.
Next, you need to add this location to your load path, and ~require~ the Org-roam library. Add the following code to your ~.emacs~: Next, you need to add this location to your load path, and ~require~ the
Org-roam library. Add the following code to your ~.emacs~:
#+begin_src elisp #+begin_src elisp
(add-to-list 'load-path "/path/to/org/roam") (add-to-list 'load-path "/path/to/org/roam")
(require 'org-roam) (require 'org-roam)
#+end_src #+end_src
You now have Org-roam installed. However, you don't necessarily have the dependencies that it requires. These include: You now have Org-roam installed. However, you don't necessarily have the
dependencies that it requires. These include:
- dash - dash
- f - f
@ -225,22 +251,29 @@ You now have Org-roam installed. However, you don't necessarily have the depende
- emacsql - emacsql
- emacsql-sqlite3 - emacsql-sqlite3
You can install this manually as well, or get the latest version from MELPA. You may wish to use [[https://github.com/jwiegley/use-package][use-package]], [[https://github.com/raxod502/straight.el][straight.el]], or some other tool or tools to help manage this. You can install this manually as well, or get the latest version from MELPA. You
may wish to use [[https://github.com/jwiegley/use-package][use-package]], [[https://github.com/raxod502/straight.el][straight.el]] to help manage this.
If you would like to install the manual for access from Emacs' built-in Info system, you'll need to compile the .texi source file, and install it in an appropriate location. If you would like to install the manual for access from Emacs' built-in Info
system, you'll need to compile the .texi source file, and install it in an
appropriate location.
To compile the .texi source file, from a terminal navigate to the ~/doc~ subdirectory of the Org-roam repository, and run the following: To compile the .texi source file, from a terminal navigate to the ~/doc~
subdirectory of the Org-roam repository, and run the following:
#+begin_src bash #+begin_src bash
make infodir=/path/to/my/info/files install-info make infodir=/path/to/my/info/files install-info
#+end_src #+end_src
Where ~/path/to/my/info/files~ is the location where you keep info files. This target directory needs to be stored in the variable `Info-default-directory-list`. If you aren't using one of the default info locations, you can configure this with the following in your ~.emacs~ file: Where ~/path/to/my/info/files~ is the location where you keep info files. This
target directory needs to be stored in the variable
`Info-default-directory-list`. If you aren't using one of the default info
locations, you can configure this with the following in your ~.emacs~ file:
#+begin_src elisp #+begin_src elisp
(require 'info) (require 'info)
(add-to-list 'Info-default-directory-list (add-to-list 'Info-default-directory-list
"/path/to/my/info/files") "/path/to/my/info/files")
#+end_src #+end_src
You can also use one of the default locations, such as: You can also use one of the default locations, such as:
@ -249,9 +282,11 @@ You can also use one of the default locations, such as:
- /usr/share/info/ - /usr/share/info/
- /usr/local/share/info/ - /usr/local/share/info/
If you do this, you'll need to make sure you have write-access to that location, or run the above ~make~ command as root. If you do this, you'll need to make sure you have write-access to that location,
or run the above ~make~ command as root.
Now that the info file is ready, you need to add it to the corresponding ~dir~ file: Now that the info file is ready, you need to add it to the corresponding ~dir~
file:
#+begin_src bash #+begin_src bash
install-info /path/to/my/info/files/org-roam.info /path/to/my/info/files/dir install-info /path/to/my/info/files/org-roam.info /path/to/my/info/files/dir
@ -289,10 +324,10 @@ 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 first start using Org-roam, one needs to pick a location to store the To first start using Org-roam, one needs to pick a location to store the
Org-roam files. The directory that will contain your notes, and database index Org-roam files. The directory that will contain your notes is specified by the
is specified by the variable ~org-roam-directory~. This variable needs to be set variable ~org-roam-directory~. This variable needs to be set before any calls to
before any calls to Org-roam functions, including enabling ~org-roam-mode~. For Org-roam functions, including enabling ~org-roam-mode~. For this tutorial,
this tutorial, create an empty directory, and set ~org-roam-directory~: 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")
@ -301,39 +336,38 @@ this tutorial, create an empty directory, and set ~org-roam-directory~:
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 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, one
solely rely on links between files to establish connections between notes. should use 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
with several hooks, builds a cache and keeps it consistent. We recommend Emacs with several hooks, building a cache that is kept consistent as your
starting ~org-roam-mode~ on startup: slip-box grows. We recommend 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 To build the cache manually, one can run ~M-x org-roam-db-build-cache~. Cache
cache is a sqlite database named ~org-roam.db~, which defaults to residing in builds may take a while the first time, but is often instantaneous in subsequent
the root ~org-roam-directory~. Cache builds may take a while the first time, but runs because it only reprocesses modified files.
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
of titles for notes that reside in ~org-roam-directory~. It should show nothing list of titles for notes that reside in ~org-roam-directory~. It should show
right now, since there are no notes in the directory. Entering the title of the nothing right now, since there are no notes in the directory. Entering the title
note you wish to create, and pressing ~RET~ should begin the note creation of the note you wish to create, and pressing ~RET~ should begin the note
process. This process uses ~org-capture~'s templating system, and can be freely creation process. This process uses ~org-capture~'s templating system, and can
customized (see [[*The Templating System][The Templating System]]). Using the default template, pressing ~C-c be customized (see [[*The Templating System][The Templating System]]). Using the default template, pressing
C-c~ finishes the note capture. Running ~M-x org-roam-find-file~ again should show ~C-c C-c~ finishes the note capture. Running ~M-x org-roam-find-file~ again
the note you have created, and selecting that entry will bring you to that note. should show the note you have created, and selecting that entry will bring you
to that note.
The crux of Org-roam is making it easy to create notes, and link them together. Org-roam makes it easy to create notes, and link them together. To link notes
To link notes together, we call ~M-x org-roam-insert~. This brings up a prompt together, we call ~M-x org-roam-insert~. This brings up a prompt with a list of
with a list of title for existing notes. Selecting an existing entry will create title for existing notes. Selecting an existing entry will create and insert a
and insert a link to the current file. Entering a non-existent title will create link to the current file. Entering a non-existent title will create a new note
a new note with that title. Good usage of Org-roam requires liberally linking with that title. Good usage of Org-roam requires liberally linking files: this
files: this facilitates building up a dense knowledge graph of inter-connected facilitates building up a dense graph of inter-connected notes.
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
@ -342,15 +376,14 @@ 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
as nodes, and links between them as edges. The generated graph can be used to as nodes, and links between them as edges. The generated graph can be used to
navigate to the files, but this requires some additional setup (see [[*Roam Protocol][Roam navigate to the files, but this requires some additional setup (see [[*Roam
Protocol]]). Protocol][Roam Protocol]]).
* Anatomy of an Org-roam File * Anatomy of an Org-roam File
The bulk of Org-roam's functionality is built on top of vanilla The bulk of Org-roam's functionality is built on top of vanilla Org-mode.
Org-mode. However, to support additional functionality, Org-roam adds However, to support additional functionality, Org-roam adds several
several Org-roam-specific keywords. These functionality are not crucial Org-roam-specific keywords.
to effective use of Org-roam.
** Titles ** Titles
@ -367,7 +400,7 @@ 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:
@ -398,13 +431,15 @@ If you wish to add your own title extraction method, you may push a symbol
Tags are used as meta-data for files: they facilitate interactions with notes 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~.
@ -429,29 +464,34 @@ accepts the absolute file path as its argument. See
** File Refs ** File Refs
Refs are unique identifiers for files. Each note can only have 1 ref. Refs are unique identifiers for files. For example, a note for a website may
For example, a note for a website may contain a ref: contain a ref:
#+BEGIN_SRC org #+BEGIN_SRC org
#+title: Google #+title: Google
#+roam_key: https://www.google.com/ #+roam_key: https://www.google.com/
#+END_SRC #+END_SRC
These keys come in useful for when taking website notes, using the These keys allow references to the key to show up in the backlinks buffer. For
~roam-ref~ protocol (see [[*Roam Protocol][Roam Protocol]]). instance, with the example above, if another file then links to
https://www.google.com, that will show up as a “Ref Backlink”.
Alternatively, add a ref for notes for a specific paper, using its These keys also come in useful for when taking website notes, using the
[[https://github.com/jkitchin/org-ref][org-ref]] citation key: ~roam-ref~ protocol (see [[*Roam Protocol][Roam Protocol]]).
[[https://github.com/jkitchin/org-ref][org-ref]] citation keys can also be used as refs:
#+BEGIN_SRC org #+BEGIN_SRC org
#+title: Neural Ordinary Differential Equations #+title: Neural Ordinary Differential Equations
#+roam_key: cite:chen18_neural_ordin_differ_equat #+roam_key: cite:chen18_neural_ordin_differ_equat
#+END_SRC #+END_SRC
The backlinks buffer will show any cites of this key: e.g.
#+CAPTION: org-ref-citelink #+CAPTION: org-ref-citelink
[[file:images/org-ref-citelink.png]] [[file:images/org-ref-citelink.png]]
You may assign multiple refs to a single file, for example when you want
multiple papers in a series to share the same note, or an article has a citation
key and a URL at the same time.
* The Templating System * The Templating System
Rather than creating blank files on ~org-roam-insert~ and ~org-roam-find-file~, Rather than creating blank files on ~org-roam-insert~ and ~org-roam-find-file~,
@ -489,15 +529,14 @@ 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
org-roam automatically chooses this template for you. 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
@ -526,9 +565,9 @@ 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
is used to fill up the rest of the template. You may read up more on to fill up the rest of the template. You may read up more on this on
this on [[https://orgmode.org/manual/Template-expansion.html#Template-expansion][org-capture's documentation page]]. [[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
@ -549,12 +588,12 @@ directly to provide its third argument to specify UTC.
#+END_SRC #+END_SRC
* Concepts and Configuration * Concepts and Configuration
The number of configuration options is deliberately kept small, to keep The number of configuration options is deliberately kept small, to keep the
the Org-roam codebase manageable. However, we attempt to accommodate as Org-roam codebase manageable. However, we attempt to accommodate as many usage
many usage styles as possible. 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
~M-x customize-group org-roam~. org-roam~.
** Directories and Files ** Directories and Files
@ -600,15 +639,56 @@ The Org-roam buffer displays backlinks for the currently active Org-roam note.
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-window-parameters
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~. Additional window parameters for the org-roam-buffer side window.
For example one can prevent the window from being deleted when calling
~delete-other-windows~, by setting it with the following:
~(setq org-roam-buffer-window-parameters '((no-delete-other-windows . t)))~
** Org-roam Files ** Org-roam Files
Org-roam files are created and prefilled using Org-roam's templating Org-roam files are created and prefilled using Org-roam's templating
system. The templating system is customizable (see [[*The Templating System][The Templating System]]). system. The templating system is customizable (see [[*The Templating System][The Templating System]]).
** Org-roam Faces
Org-roam introduces several faces to distinguish links within the same buffer.
These faces are enabled by default in Org-roam notes.
- User Option: org-roam-link-use-custom-faces
When ~t~, use custom faces only inside Org-roam notes.
When ~everywhere~, the custom face is applied additionally to non Org-roam notes.
When ~nil~, do not use Org-roam's custom faces.
The ~org-roam-link~ face is the face applied to links to other Org-roam files.
This distinguishes internal links from external links (e.g. external web links).
The ~org-roam-link-current~ face corresponds to links to the same file it is in.
The ~org-roam-link-invalid~ face is applied to links that are broken. These are
links to files or IDs that cannot be found.
** TODO The Database
Org-roam is backed by a Sqlite database.
- User Option: org-roam-db-update-method
Method to update the Org-roam database.
~'immediate~: Update the database immediately upon file changes.
~'idle-timer~: Updates the database if dirty, if Emacs idles for
~org-roam-db-update-idle-seconds~.
- User Option: org-roam-db-update-idle-seconds
Number of idle seconds before triggering an Org-roam database update. This is
only valid if ~org-roam-db-update-method~ is ~'idle-timer~.
* Inserting Links * Inserting Links
The preferred mode of linking is via ~file~ links to files, and ~id~ links for The preferred mode of linking is via ~file~ links to files, and ~id~ links for
@ -626,14 +706,17 @@ An alternative mode of insertion is using Org-roam's ~roam~ links. Org-roam
registers this link type, and interprets the path as follows: registers this link type, and interprets the path as follows:
- ~[[roam:title]]~ :: links to an Org-roam file with title or alias "title" - ~[[roam:title]]~ :: links to an Org-roam file with title or alias "title"
- ~[[roam:*headline]]~ :: links to the headline "headline" in the current Org-roam file - ~[[roam:*headline]]~ :: links to the headline "headline" in the current
- ~[[roam:title*headline]]~ :: links to the headline "headline" in the Org-roam file with title or alias "title" Org-roam file
- ~[[roam:title*headline]]~ :: links to the headline "headline" in the Org-roam
file with title or alias "title"
~roam~ links support auto-completion via ~completion-at-point~: simply call ~roam~ links support auto-completion via ~completion-at-point~: simply call
~completion-at-point~ within a roam link. Users of ~company-mode~ may want to ~completion-at-point~ within a roam link. Users of ~company-mode~ may want to
prepend ~company-capf~ to the beginning of variable ~company-backends~. prepend ~company-capf~ to the beginning of variable ~company-backends~.
To easily insert ~roam~ links, one may wish to use a package like [[https://github.com/emacsorphanage/key-chord/][key-chord]]. In the following example, typing "[[" will insert a stub ~roam~ link: To easily insert ~roam~ links, one may wish to use a package like [[https://github.com/emacsorphanage/key-chord/][key-chord]]. In
the following example, typing "[[" will insert a stub ~roam~ link:
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(key-chord-define org-mode-map "[[" #'my/insert-roam-link) (key-chord-define org-mode-map "[[" #'my/insert-roam-link)
@ -667,7 +750,6 @@ To easily insert ~roam~ links, one may wish to use a package like [[https://gith
harder to edit. Defaults to ~t~. harder to edit. Defaults to ~t~.
* Navigating Around * Navigating Around
** Index File ** Index File
As your collection grows, you might want to create an index where you keep links As your collection grows, you might want to create an index where you keep links
@ -697,13 +779,14 @@ 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 cache
cache updates if it requires reading the encrypted file. To reduce the updates if it requires reading the encrypted file. To reduce the number of
number of password prompts, you may wish to cache the password. password prompts, you may wish to cache the password.
- Variable: org-roam-encrypt-files - User Option: org-roam-encrypt-files
Whether to encrypt new files. If true, create files with .org.gpg extension.
Whether to encrypt new files. If true, create files with .org.gpg extension.
* Graphing * Graphing
Org-roam provides graphing capabilities to explore interconnections between Org-roam provides graphing capabilities to explore interconnections between
@ -727,21 +810,24 @@ The entry point to graph creation is ~org-roam-graph~.
- 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
Org-roam defaults to using Firefox (located on PATH) to view the SVG, but you may choose to set it to: Org-roam defaults to using Firefox (located on PATH) to view the SVG, but you
may choose to set it to:
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: 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 #+BEGIN_SRC emacs-lisp
(setq org-roam-graph-viewer (setq org-roam-graph-viewer
@ -752,7 +838,9 @@ The entry point to graph creation is ~org-roam-graph~.
** Graph Options ** Graph Options
Graphviz provides many options for customizing the graph output, and Org-roam supports some of them. See https://graphviz.gitlab.io/_pages/doc/info/attrs.html for customizable options. Graphviz provides many options for customizing the graph output, and Org-roam
supports some of them. See https://graphviz.gitlab.io/_pages/doc/info/attrs.html
for customizable options.
- User Option: org-roam-graph-extra-config - User Option: org-roam-graph-extra-config
@ -817,13 +905,14 @@ commands, set:
Other options include ~'ido~, and ~'ivy~. Other options include ~'ido~, and ~'ivy~.
* Roam Protocol * Roam Protocol
** _ :ignore:
Org-roam extending ~org-protocol~ with 2 protocols: the ~roam-file~ Org-roam extends ~org-protocol~ with 2 protocols: the ~roam-file~ and ~roam-ref~
and ~roam-ref~ protocol. protocols.
** Installation ** Installation
To enable Org-roam's protocol extensions, you have to add the following to your init file: To enable Org-roam's protocol extensions, you have to add the following to your
init file:
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(require 'org-roam-protocol) (require 'org-roam-protocol)
@ -834,7 +923,8 @@ 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]
@ -853,9 +943,9 @@ running in your shell:
xdg-mime default org-protocol.desktop x-scheme-handler/org-protocol 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
show a checkbox to tick, so that the ~Org-Protocol Client~ app will be used checkbox to tick, so that the ~Org-Protocol Client~ app will be used without
without confirmation. To do this, run in a shell: confirmation. To do this, run in a shell:
#+BEGIN_SRC bash #+BEGIN_SRC bash
sudo mkdir -p /etc/opt/chrome/policies/managed/ sudo mkdir -p /etc/opt/chrome/policies/managed/
@ -891,7 +981,6 @@ brew cask install platypus
3. Create a Platypus app with the following settings: 3. Create a Platypus app with the following settings:
#+begin_example
| Setting | Value | | Setting | Value |
|--------------------------------+---------------------------| |--------------------------------+---------------------------|
| App Name | "OrgProtocol" | | App Name | "OrgProtocol" |
@ -900,18 +989,15 @@ brew cask install platypus
| Interface | None | | Interface | None |
| Accept dropped items | true | | Accept dropped items | true |
| Remain running after execution | false | | Remain running after execution | false |
#+end_example
Inside ~Settings~: Inside ~Settings~:
#+begin_example
| Setting | Value | | Setting | Value |
|--------------------------------+----------------| |--------------------------------+----------------|
| Accept dropped files | true | | Accept dropped files | true |
| Register as URI scheme handler | true | | Register as URI scheme handler | true |
| Protocol | "org-protocol" | | Protocol | "org-protocol" |
#+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
@ -947,13 +1033,15 @@ REGEDIT4
@="\"C:\\Windows\\System32\\wsl.exe\" emacsclient \"%1\"" @="\"C:\\Windows\\System32\\wsl.exe\" emacsclient \"%1\""
#+END_SRC #+END_SRC
The above will forward the protocol to WSL. If you run Emacs natively on Windows, replace the last line with: The above will forward the protocol to WSL. If you run Emacs natively on
Windows, replace the last line with:
#+BEGIN_SRC text #+BEGIN_SRC text
@="\"c:\\path\\to\\emacs\\bin\\emacsclientw.exe\" \"%1\"" @="\"c:\\path\\to\\emacs\\bin\\emacsclientw.exe\" \"%1\""
#+END_SRC #+END_SRC
After executing the .reg file, the protocol is registered and you can delete the file. After executing the .reg file, the protocol is registered and you can delete the
file.
** The roam-file protocol ** The roam-file protocol
@ -970,11 +1058,13 @@ This protocol finds or creates a new note with a given ~roam_key~ (see [[*Anatom
To use this, create the following [[https://en.wikipedia.org/wiki/Bookmarklet][bookmarklet]] in your browser: 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 =
'org-protocol://roam-ref?template=r&ref=' 'org-protocol://roam-ref?template=r&ref='
+ encodeURIComponent(location.href) + encodeURIComponent(location.href)
+ '&title=' + '&title='
+ encodeURIComponent(document.title) + encodeURIComponent(document.title)
+ '&body='
+ encodeURIComponent(window.getSelection())
#+END_SRC #+END_SRC
or as a keybinding in ~qutebrowser~ in , using the ~config.py~ file (see or as a keybinding in ~qutebrowser~ in , using the ~config.py~ file (see
@ -988,7 +1078,122 @@ where ~template~ is the template key for a template in
~org-roam-capture-ref-templates~ (see [[*The Templating System][The Templating System]]). These templates ~org-roam-capture-ref-templates~ (see [[*The Templating System][The Templating System]]). These templates
should contain a ~#+roam_key: ${ref}~ in it. should contain a ~#+roam_key: ${ref}~ in it.
* TODO Daily Notes * Daily-notes
Org-roam provides journaling capabilities akin to
[[#org-journal][Org-journal]] with ~org-roam-dailies~.
** Configuration
For ~org-roam-dailies~ to work, you need to define two variables:
- Variable: ~org-roam-dailies-directory~
Path to daily-notes.
- Variable: ~org-roam-dailies-capture-templates~
Capture templates for daily-notes in Org-roam.
Here is a sane default configuration:
#+begin_src emacs-lisp
(setq org-roam-dailies-directory "daily/")
(setq org-roam-dailies-capture-templates
'(("d" "default" entry
#'org-roam-capture--get-point
"* %?"
:file-name "daily/%<%Y-%m-%d>"
:head "#+title: %<%Y-%m-%d>\n\n")))
#+end_src
Make sure that ~org-roam-dailies-directory~ appears in ~:file-name~ for your
notes to be recognized as daily-notes. You can have different templates placing
their notes in different directories, but the one in
~org-roam-dailies-directory~ will be considered as the main one in commands.
See [[*The Templating System][The Templating System]] for creating new
templates. ~org-roam-dailies~ provides an extra ~:olp~ option which allows
specifying the outline-path to a heading:
#+begin_src emacs-lisp
(setq org-roam-dailies-capture-templates
'(("l" "lab" entry
#'org-roam-capture--get-point
"* %?"
:file-name "daily/%<%Y-%m-%d>"
:head "#+title: %<%Y-%m-%d>\n\n* Lab notes\n* Journal"
:olp ("Journal"))
("j" "journal" entry
#'org-roam-capture--get-point
"* %?"
:file-name "daily/%<%Y-%m-%d>"
:head "#+title: %<%Y-%m-%d>\n\n* Lab notes\n* Journal"
:olp ("Lab notes"))))
#+end_src
The template ~l~ will put its notes under the heading Lab notes, and the
template ~j~ will put its notes under the heading Journal. When you use
~:olp~, make sure that the headings are present in ~:head~.
** Capturing and finding daily-notes
- Function: ~org-roam-dailies-capture-today~ &optional goto
Create an entry in the daily note for today.
When ~goto~ is non-nil, go the note without creating an entry.
- Function: ~org-roam-dailies-find-today~
Find the daily note for today, creating it if necessary.
There are variants of those commands for ~-yesterday~ and ~-tomorrow~:
- Function: ~org-roam-dailies-capture-yesterday~ n &optional goto
Create an entry in the daily note for yesteday.
With numeric argument ~n~, use the daily note ~n~ days in the past.
- Function: ~org-roam-dailies-find-yesterday~
With numeric argument N, use the daily-note N days in the future.
There are also commands which allow you to use Emacss ~calendar~ to find the date
- Function: ~org-roam-dailies-capture-date~
Create an entry in the daily note for a date using the calendar.
Prefer past dates, unless ~prefer-future~ is non-nil.
With a 'C-u' prefix or when ~goto~ is non-nil, go the note without
creating an entry.
- Function: ~org-roam-dailies-find-date~
Find the daily note for a date using the calendar, creating it if necessary.
Prefer past dates, unless ~prefer-future~ is non-nil.
** Navigation
You can navigate between daily-notes:
- Function: ~org-roam-dailies-find-directory~
Find and open ~org-roam-dailies-directory~.
- Function: ~org-roam-dailies-find-previous-note~
When in an daily-note, find the previous one.
- Function: ~org-roam-dailies-find-next-note~
When in an daily-note, find the next one.
* Diagnosing and Repairing Files * Diagnosing and Repairing Files
@ -1002,9 +1207,11 @@ org-roam-doctor~, but note that this may take some time.
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 The checks run are defined in ~org-roam-doctor--checkers~. By default, there are
instance of ~org-roam-doctor-checker~. To define a checker, use checkers for broken links and invalid =#+roam_*= properties.
~make-org-roam-doctor-checker~. Here is a sample definition:
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
@ -1016,10 +1223,10 @@ instance of ~org-roam-doctor-checker~. To define a checker, use
#+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
short description of what the checker does. ~:actions~ is an alist containing a short description of what the checker does. ~:actions~ is an alist containing
elements of the form ~(char . (prompt . function))~. These actions are defined per elements of the form ~(char . (prompt . function))~. These actions are defined
checker, to perform autofixes for the errors. For each error detected, per checker, to perform autofixes for the errors. For each error detected,
~org-roam-doctor~ will move the point to the current error, and pop-up a help ~org-roam-doctor~ will move the point to the current error, and pop-up a help
window displaying the error message, as well as the list of actions that can be window displaying the error message, as well as the list of actions that can be
taken provided in ~:actions~. taken provided in ~:actions~.
@ -1032,17 +1239,24 @@ facilities for discovering these unlinked references, so one may decide whether
to convert them into links. to convert them into links.
To use this feature, simply call ~M-x org-roam-unlinked-references~ from within To use this feature, simply call ~M-x org-roam-unlinked-references~ from within
an Org-roam note. Org-roam uses [[https://github.com/BurntSushi/ripgrep][ripgrep]], specifically a clever PCRE regex to an Org-roam note. Internally, Org-roam uses [[https://github.com/BurntSushi/ripgrep][ripgrep]] and a clever PCRE regex to
find occurrences of the title or aliases of the currently open note in all find occurrences of the title or aliases of the currently open note in all
Org-roam files. This thus requires a version of ripgrep that is compiled with Org-roam files. Hence, this requires a version of ripgrep that is compiled with
PCRE support. PCRE support.
#+begin_quote
NOTE: Since ripgrep cannot read encrypted files, this function cannot find
unlinked references within encrypted files.
#+end_quote
* Performance Optimization * Performance Optimization
** TODO Profiling Key Operations ** TODO Profiling Key Operations
** Garbage Collection ** Garbage Collection
During the cache-build process, Org-roam generates a lot of in-memory 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]]). 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 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 the threshold value for GC to be triggered during these memory-intensive
@ -1090,9 +1304,14 @@ General Public License for more details.
*** Browsing History with winner-mode *** 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~ 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: ~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 #+BEGIN_SRC emacs-lisp
(winner-mode +1) (winner-mode +1)
@ -1130,10 +1349,9 @@ versions of a tracked Org-roam note.
(deft-directory "/path/to/org-roam-files/")) (deft-directory "/path/to/org-roam-files/"))
#+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
nice titles. You may choose to patch this to use ~org-roam~'s titles. You may choose to patch this to use ~org-roam~'s functionality. Here I'm
functionality. Here I'm using using [[https://github.com/raxod502/el-patch][el-patch]]:
[[https://github.com/raxod502/el-patch][el-patch]]:
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(use-package el-patch (use-package el-patch
@ -1161,19 +1379,18 @@ functionality. Here I'm using
(org-roam--get-title-or-slug file)))) (org-roam--get-title-or-slug file))))
#+END_SRC #+END_SRC
The Deft interface can slow down quickly when the number of files get The Deft interface can slow down quickly when the number of files get huge.
huge. [[https://github.com/hasu/notdeft][Notdeft]] is a fork of Deft [[https://github.com/hasu/notdeft][Notdeft]] is a fork of Deft that uses an external search engine and indexer.
that uses an external search engine and indexer.
*** Org-journal *** Org-journal
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: org-journal :CUSTOM_ID: org-journal
:END: :END:
[[https://github.com/bastibe/org-journal][Org-journal]] is a more [[https://github.com/bastibe/org-journal][Org-journal]] provides journaling capabilities to Org-mode. A lot of its
powerful alternative to the simple function ~org-roam-dailies-today~. It functionalities have been incorporated into Org-roam under the name
provides better journaling capabilities, and a nice calendar interface [[*Daily-notes][~org-roam-dailies~]]. It remains a good tool if you want to isolate your verbose
to see all dated entries. journal entries from the ideas you would write on a scratchpad.
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(use-package org-journal (use-package org-journal
@ -1182,7 +1399,7 @@ to see all dated entries.
:custom :custom
(org-journal-date-prefix "#+title: ") (org-journal-date-prefix "#+title: ")
(org-journal-file-format "%Y-%m-%d.org") (org-journal-file-format "%Y-%m-%d.org")
(org-journal-dir "/path/to/org-roam-files/") (org-journal-dir "/path/to/journal/files/")
(org-journal-date-format "%A, %d %B %Y")) (org-journal-date-format "%A, %d %B %Y"))
#+END_SRC #+END_SRC
@ -1253,6 +1470,10 @@ tight integration between
~org-roam~. This helps you manage your bibliographic notes under ~org-roam~. This helps you manage your bibliographic notes under
~org-roam~. ~org-roam~.
For example, though helm-bibtex provides the ability to visit notes for
bibliographic entries, org-roam-bibtex extends it with the ability to visit the
file with the right =#+roam_key=.
**** Spaced Repetition **** Spaced Repetition
:PROPERTIES: :PROPERTIES:
:CUSTOM_ID: spaced-repetition :CUSTOM_ID: spaced-repetition
@ -1277,13 +1498,15 @@ contain:
(org-roam-db-location . "./org-roam.db")))) (org-roam-db-location . "./org-roam.db"))))
#+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
set of Org-roam files. Remember to run ~org-roam-db-build-cache~ from a Org-roam files. Remember to run ~org-roam-db-build-cache~ from a file within
file within that directory, at least once. that directory, at least once.
** How do I migrate from Roam Research? ** How do I migrate from Roam Research?
Fabio has produced a command-line tool that converts markdown files exported from Roam Research into Org-roam compatible markdown. More instructions are provided [[https://github.com/fabioberger/roam-migration][in the repository]]. Fabio has produced a command-line tool that converts markdown files exported
from Roam Research into Org-roam compatible markdown. More instructions are
provided [[https://github.com/fabioberger/roam-migration][in the repository]].
** How do I create a note whose title already matches one of the candidates? ** How do I create a note whose title already matches one of the candidates?
@ -1293,11 +1516,40 @@ This situation arises when, for example, one would like to create a note titled
The solution is dependent on the mini-buffer completion framework in use. Here The solution is dependent on the mini-buffer completion framework in use. Here
are the solutions: are the solutions:
- Ivy :: call ~ivy-immediate-done~, typically bound to ~C-M-j~. Alternatively, set ~ivy-use-selectable-prompt~ to ~t~, so that "bar" is now selectable. - Ivy :: call ~ivy-immediate-done~, typically bound to ~C-M-j~. Alternatively,
- Helm :: Org-roam should provide a selectable "[?] bar" candidate at the top of the candidate list. set ~ivy-use-selectable-prompt~ to ~t~, so that "bar" is now selectable.
- Helm :: Org-roam should provide a selectable "[?] bar" candidate at the top of
the candidate list.
* Keystroke Index
:PROPERTIES:
:APPENDIX: t
:INDEX: ky
:COOKIE_DATA: recursive
:END:
* Command Index
:PROPERTIES:
:APPENDIX: t
:INDEX: cp
:END:
* Function Index
:PROPERTIES:
:APPENDIX: t
:INDEX: fn
:END:
* Variable Index
:PROPERTIES:
:APPENDIX: t
:INDEX: vr
:END:
* Footnotes
[fn:roam] To understand more about Roam, a collection of links are available in [[*Note-taking Workflows][Note-taking Workflows]].
# Local Variables: # Local Variables:
# eval: (require 'ol-info) # eval: (require 'ol-info)
# eval: (require 'ox-texinfo+ nil t)
# eval: (auto-fill-mode +1)
# before-save-hook: org-make-toc # before-save-hook: org-make-toc
# after-save-hook: (lambda nil (progn (require 'ox-texinfo nil t) (org-texinfo-export-to-info))) # after-save-hook: (lambda nil (progn (require 'ox-texinfo nil t) (org-texinfo-export-to-info)))
# indent-tabs-mode: nil # indent-tabs-mode: nil

File diff suppressed because it is too large Load Diff

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.2 ;; Version: 1.2.3
;; 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.2")) ;; 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.2"))
;; This file is NOT part of GNU Emacs. ;; This file is NOT part of GNU Emacs.
@ -47,7 +47,7 @@
(defvar org-roam--org-link-bracket-typed-re) (defvar org-roam--org-link-bracket-typed-re)
(declare-function org-roam-db--ensure-built "org-roam-db") (declare-function org-roam-db--ensure-built "org-roam-db")
(declare-function org-roam--extract-ref "org-roam") (declare-function org-roam--extract-refs "org-roam")
(declare-function org-roam--extract-titles "org-roam") (declare-function org-roam--extract-titles "org-roam")
(declare-function org-roam--get-title-or-slug "org-roam") (declare-function org-roam--get-title-or-slug "org-roam")
(declare-function org-roam--get-backlinks "org-roam") (declare-function org-roam--get-backlinks "org-roam")
@ -107,11 +107,12 @@ For example: (setq org-roam-buffer-window-parameters '((no-other-window . t)))"
(defun org-roam-buffer--find-file (file) (defun org-roam-buffer--find-file (file)
"Open FILE in the window `org-roam' was called from." "Open FILE in the window `org-roam' was called from."
(setq file (expand-file-name file)) (setq file (expand-file-name file))
(if (and org-roam-last-window (window-valid-p org-roam-last-window)) (let ((last-window org-roam-last-window))
(progn (with-selected-window org-roam-last-window (if (window-valid-p last-window)
(org-roam--find-file file)) (progn (with-selected-window last-window
(select-window org-roam-last-window)) (org-roam--find-file file))
(org-roam--find-file file))) (select-window 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."
@ -148,10 +149,11 @@ ORIG-PATH is the path where the CONTENT originated."
(defun org-roam-buffer--insert-ref-links () (defun org-roam-buffer--insert-ref-links ()
"Insert ref backlinks for the current buffer." "Insert ref backlinks for the current buffer."
(when-let ((path (cdr (with-temp-buffer (when-let* ((refs (with-temp-buffer
(insert-buffer-substring org-roam-buffer--current) (insert-buffer-substring org-roam-buffer--current)
(org-roam--extract-ref))))) (org-roam--extract-refs)))
(if-let* ((key-backlinks (org-roam--get-backlinks path)) (paths (mapcar #'cdr refs)))
(if-let* ((key-backlinks (mapcan #'org-roam--get-backlinks paths))
(grouped-backlinks (--group-by (nth 0 it) key-backlinks))) (grouped-backlinks (--group-by (nth 0 it) key-backlinks)))
(progn (progn
(insert (let ((l (length key-backlinks))) (insert (let ((l (length key-backlinks)))
@ -162,57 +164,56 @@ ORIG-PATH is the path where the CONTENT originated."
(bls (cdr group))) (bls (cdr group)))
(insert (format "** %s\n" (insert (format "** %s\n"
(org-roam-format-link file-from (org-roam-format-link file-from
(org-roam--get-title-or-slug file-from) (org-roam--get-title-or-slug file-from)
"file"))) "file")))
(dolist (backlink bls) (dolist (backlink bls)
(pcase-let ((`(,file-from _ ,props) backlink)) (pcase-let ((`(,file-from _ ,props) backlink))
(insert (propertize (org-roam-buffer-expand-links (plist-get props :content) file-from) (insert (if-let ((content (plist-get props :content)))
'help-echo "mouse-1: visit backlinked note" (propertize (org-roam-buffer-expand-links content file-from)
'file-from file-from 'help-echo "mouse-1: visit backlinked note"
'file-from-point (plist-get props :point))) 'file-from file-from
(insert "\n\n")))))) 'file-from-point (plist-get props :point))
"")
"\n\n"))))))
(insert "\n\n* No ref 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."
(if-let* ((file-path (buffer-file-name org-roam-buffer--current)) (let (props file-from)
(titles (with-current-buffer org-roam-buffer--current (if-let* ((file-path (buffer-file-name org-roam-buffer--current))
(org-roam--extract-titles))) (titles (with-current-buffer org-roam-buffer--current
(backlinks (org-roam--get-backlinks (push file-path titles))) (org-roam--extract-titles)))
(grouped-backlinks (--group-by (nth 0 it) backlinks))) (backlinks (org-roam--get-backlinks (push file-path titles)))
(progn (grouped-backlinks (--group-by (nth 0 it) backlinks)))
(insert (let ((l (length backlinks))) (progn
(format "\n\n* %d %s\n" (insert (let ((l (length backlinks)))
l (org-roam-buffer--pluralize "Backlink" l)))) (format "\n\n* %d %s\n"
(dolist (group grouped-backlinks) l (org-roam-buffer--pluralize "Backlink" l))))
(let ((file-from (car group)) (dolist (group grouped-backlinks)
(bls (mapcar (lambda (row) (setq file-from (car group))
(nth 2 row)) (cdr group)))) (setq props (mapcar (lambda (row) (nth 2 row)) (cdr group)))
(setq props (seq-sort-by (lambda (p) (plist-get p :point)) #'< props))
(insert (format "** %s\n" (insert (format "** %s\n"
(org-roam-format-link file-from (org-roam-format-link file-from
(org-roam--get-title-or-slug file-from) (org-roam--get-title-or-slug file-from)
"file"))) "file")))
;; Sort backlinks according to time of occurrence in buffer (dolist (prop props)
(setq bls (seq-sort-by (lambda (bl)
(plist-get bl :point))
#'<
bls))
(dolist (props bls)
(insert "*** " (insert "*** "
(if-let ((outline (plist-get props :outline))) (if-let ((outline (plist-get prop :outline)))
(-> outline (-> outline
(string-join " > ") (string-join " > ")
(org-roam-buffer-expand-links file-from)) (org-roam-buffer-expand-links file-from))
"Top") "Top")
"\n" "\n"
(propertize (if-let ((content (plist-get prop :content)))
(s-trim (s-replace "\n" " " (propertize
(org-roam-buffer-expand-links (plist-get props :content) file-from))) (s-trim (s-replace "\n" " " (org-roam-buffer-expand-links content file-from)))
'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 prop :point))
"\n\n"))))) "")
(insert "\n\n* No backlinks!"))) "\n\n"))))
(insert "\n\n* No backlinks!"))))
(defun org-roam-buffer-update () (defun org-roam-buffer-update ()
"Update the `org-roam-buffer'." "Update the `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.2 ;; Version: 1.2.3
;; 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.2")) ;; 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.2"))
;; This file is NOT part of GNU Emacs. ;; This file is NOT part of GNU Emacs.
@ -41,6 +41,7 @@
(defvar org-roam-directory) (defvar org-roam-directory)
(defvar org-roam-mode) (defvar org-roam-mode)
(defvar org-roam-title-to-slug-function) (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")
@ -74,11 +75,11 @@ note with the given `ref'.")
(defvar org-roam-capture-additional-template-props nil (defvar org-roam-capture-additional-template-props nil
"Additional props to be added to the Org-roam template.") "Additional props to be added to the Org-roam template.")
(defconst org-roam-capture--template-keywords '(:file-name :head) (defconst org-roam-capture--template-keywords '(:file-name :head :olp)
"Keywords used in `org-roam-capture-templates' specific to Org-roam.") "Keywords used in `org-roam-capture-templates' specific to Org-roam.")
(defcustom 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"
@ -218,10 +219,10 @@ Template string :\n%v")
((const :format "%v " :kill-buffer) (const t)))))) ((const :format "%v " :kill-buffer) (const t))))))
(defcustom org-roam-capture-ref-templates (defcustom org-roam-capture-ref-templates
'(("r" "ref" plain (function org-roam-capture--get-point) '(("r" "ref" plain #'org-roam-capture--get-point
"%?" "%?"
:file-name "${slug}" :file-name "${slug}"
:head "#+title: ${title}\n#+roam_key: ${ref}\n" :head "#+title: ${title}\n#+roam_key: ${ref}"
: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'."
@ -408,26 +409,37 @@ aborted, we do the following:
3. Add a function on `org-capture-before-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 (org-roam-capture--get :file-name)) (let* ((name-templ (or (org-roam-capture--get :file-name)
(user-error "Template needs to specify `:file-name'")))
(new-id (s-trim (org-roam-capture--fill-template (new-id (s-trim (org-roam-capture--fill-template
name-templ))) name-templ)))
(file-path (org-roam--file-path-from-id new-id)) (file-path (org-roam--file-path-from-id new-id))
(roam-head (org-roam-capture--get :head)) (roam-head (or (org-roam-capture--get :head)
""))
(org-template (org-capture-get :template)) (org-template (org-capture-get :template))
(roam-template (concat roam-head org-template))) (roam-template (concat roam-head org-template)))
(unless (file-exists-p file-path) (unless (or (file-exists-p file-path)
(cl-some (lambda (buffer)
(string= (buffer-file-name buffer)
file-path))
(buffer-list)))
(make-directory (file-name-directory file-path) t) (make-directory (file-name-directory file-path) t)
(org-roam-capture--put :orig-no-save (org-capture-get :no-save) (org-roam-capture--put :orig-no-save (org-capture-get :no-save)
:new-file t) :new-file t)
(org-capture-put :template (pcase org-roam-capture--context
;; Fixes org-capture-place-plain-text throwing 'invalid search bound' ('dailies
;; when both :unnarowed t and "%?" is missing from the template string; ;; Populate the header of the daily file before capture to prevent it
;; may become unnecessary when the upstream bug is fixed ;; from appearing in the buffer-restriction
(if (s-contains-p "%?" roam-template) (save-window-excursion
roam-template (find-file file-path)
(concat roam-template "%?")) (insert (substring (org-capture-fill-template (concat roam-head "*"))
:type 'plain 0 -2))
:no-save t)) (set-buffer-modified-p nil))
(org-capture-put :template org-template))
(_
(org-capture-put :template roam-template
:type 'plain)))
(org-capture-put :no-save t))
file-path)) file-path))
(defun org-roam-capture--get-point () (defun org-roam-capture--get-point ()
@ -446,32 +458,53 @@ If there is no file with that ref, a file with that ref is created.
This function is used solely in Org-roam's capture templates: see This function is used solely in Org-roam's capture templates: see
`org-roam-capture-templates'." `org-roam-capture-templates'."
(let ((file-path (pcase org-roam-capture--context (let* ((file-path (pcase org-roam-capture--context
('capture ('capture
(or (cdr (assoc 'file org-roam-capture--info)) (or (cdr (assoc 'file org-roam-capture--info))
(org-roam-capture--new-file))) (org-roam-capture--new-file)))
('title ('title
(org-roam-capture--new-file)) (org-roam-capture--new-file))
('dailies ('dailies
(org-capture-put :default-time (cdr (assoc 'time org-roam-capture--info))) (org-capture-put :default-time (cdr (assoc 'time org-roam-capture--info)))
(org-roam-capture--new-file)) (org-roam-capture--new-file))
('ref ('ref
(let ((completions (org-roam--get-ref-path-completions)) (let ((completions (org-roam--get-ref-path-completions))
(ref (cdr (assoc 'ref org-roam-capture--info)))) (ref (cdr (assoc 'ref org-roam-capture--info))))
(if-let ((pl (cdr (assoc ref completions)))) (if-let ((pl (cdr (assoc ref completions))))
(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-capture-put :template (org-capture-put :template
(org-roam-capture--fill-template (org-capture-get :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
:finalize (or (org-capture-get :finalize)
(org-roam-capture--get :finalize)))
(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))
(val (pop org-roam-capture-additional-template-props))) (val (pop org-roam-capture-additional-template-props)))
(org-roam-capture--put prop val))) (org-roam-capture--put prop val)))
(set-buffer (org-capture-target-buffer file-path)) (set-buffer (org-capture-target-buffer file-path))
(widen) (widen)
(goto-char (point-max)))) (if-let* ((olp (when (eq org-roam-capture--context 'dailies)
(--> (org-roam-capture--get :olp)
(pcase it
((pred stringp)
(list it))
((pred listp)
it)
(wrong-type
(signal 'wrong-type-argument
`((stringp listp)
,wrong-type))))))))
(condition-case err
(when-let ((marker (org-find-olp `(,file-path ,@olp))))
(goto-char marker)
(set-marker marker nil))
(error
(when (org-roam-capture--get :new-file)
(kill-buffer))
(signal (car err) (cdr err))))
(goto-char (point-min)))))
(defun org-roam-capture--convert-template (template) (defun org-roam-capture--convert-template (template)
"Convert TEMPLATE from Org-roam syntax to `org-capture-templates' syntax." "Convert TEMPLATE from Org-roam syntax to `org-capture-templates' syntax."
@ -512,7 +545,8 @@ 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)) (one-template-p (= (length org-capture-templates) 1))
org-capture-templates-contexts) org-capture-templates-contexts
(org-capture-link-is-already-stored t))
(when one-template-p (when one-template-p
(setq keys (caar org-capture-templates))) (setq keys (caar org-capture-templates)))
(if (or one-template-p (if (or one-template-p

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.2 ;; Version: 1.2.3
;; 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.2")) ;; 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.2"))
;; This file is NOT part of GNU Emacs. ;; This file is NOT part of GNU Emacs.
@ -79,6 +79,14 @@
"org-roam 1.1.0") "org-roam 1.1.0")
(define-obsolete-function-alias 'org-roam-db--clear 'org-roam-db-clear (define-obsolete-function-alias 'org-roam-db--clear 'org-roam-db-clear
"org-roam 1.2.0") "org-roam 1.2.0")
(define-obsolete-function-alias 'org-roam-dailies-today 'org-roam-dailies-find-today
"org-roam 1.2.2")
(define-obsolete-function-alias 'org-roam-dailies-yesterday 'org-roam-dailies-find-yesterday
"org-roam 1.2.2")
(define-obsolete-function-alias 'org-roam-dailies-tomorrow 'org-roam-dailies-find-tomorrow
"org-roam 1.2.2")
(define-obsolete-function-alias 'org-roam-dailies-date 'org-roam-dailies-find-date
"org-roam 1.2.2")
;;;; Variables ;;;; Variables
(define-obsolete-variable-alias 'org-roam-graphviz-extra-options (define-obsolete-variable-alias 'org-roam-graphviz-extra-options
@ -95,6 +103,9 @@
'org-roam-dailies-capture-templates "org-roam 1.0.0") 'org-roam-dailies-capture-templates "org-roam 1.0.0")
(define-obsolete-variable-alias 'org-roam-date-filename-format (define-obsolete-variable-alias 'org-roam-date-filename-format
'org-roam-dailies-capture-templates "org-roam 1.0.0") 'org-roam-dailies-capture-templates "org-roam 1.0.0")
(define-obsolete-variable-alias 'org-roam-update-db-idle-seconds
'org-roam-db-update-idle-seconds "org-roam 1.2.2")
(make-obsolete-variable 'org-roam-buffer-no-delete-other-windows (make-obsolete-variable 'org-roam-buffer-no-delete-other-windows
'org-roam-buffer-window-parameters "org-roam 1.1.1") 'org-roam-buffer-window-parameters "org-roam 1.1.1")

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.2 ;; Version: 1.2.3
;; 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.2")) ;; 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.2"))
;; This file is NOT part of GNU Emacs. ;; This file is NOT part of GNU Emacs.
@ -79,6 +79,7 @@ Return user choice."
(if (fboundp 'ivy-read) (if (fboundp 'ivy-read)
(ivy-read prompt choices (ivy-read prompt choices
:initial-input initial-input :initial-input initial-input
:preselect initial-input
:require-match require-match :require-match require-match
:action (prog1 action :action (prog1 action
(setq action nil)) (setq action nil))

View File

@ -1,11 +1,13 @@
;;; org-roam-dailies.el --- Daily notes for Org-roam -*- coding: utf-8; lexical-binding: t; -*- ;;; org-roam-dailies.el --- Daily-notes for Org-roam -*- coding: utf-8; lexical-binding: t; -*-
;;; ;;;
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com> ;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
;; Copyright © 2020 Leo Vivier <leo.vivier+dev@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com> ;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; Leo Vivier <leo.vivier+dev@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.2 ;; Version: 1.2.3
;; 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.2")) ;; 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.2"))
;; This file is NOT part of GNU Emacs. ;; This file is NOT part of GNU Emacs.
@ -27,7 +29,7 @@
;;; Commentary: ;;; Commentary:
;; ;;
;; This library provides functionality for creating daily notes. This is a ;; This library provides functionality for creating daily-notes. This is a
;; concept borrowed from Roam Research. ;; concept borrowed from Roam Research.
;; ;;
;;; Code: ;;; Code:
@ -35,102 +37,330 @@
(require 'org-capture) (require 'org-capture)
(require 'org-roam-capture) (require 'org-roam-capture)
(require 'org-roam-macs) (require 'org-roam-macs)
(require 'f)
;;;; Declarations
(defvar org-roam-mode)
(defvar org-roam-directory)
(declare-function org-roam--org-file-p "org-roam")
(declare-function org-roam--file-path-from-id "org-roam")
(declare-function org-roam--find-file "org-roam")
(declare-function org-roam-mode "org-roam")
;;;; Customizable variables
(defcustom org-roam-dailies-directory "daily/"
"Path to daily-notes."
:group 'org-roam
:type 'string)
(defcustom org-roam-dailies-find-file-hook nil
"Hook that is run right after navigating to a daily-note."
:group 'org-roam
:type 'hook)
(defcustom org-roam-dailies-capture-templates (defcustom org-roam-dailies-capture-templates
'(("d" "daily" plain (function org-roam-capture--get-point) '(("d" "default" entry (function org-roam-capture--get-point)
"" "* %?"
:immediate-finish t :file-name "daily/%<%Y-%m-%d>"
:file-name "%<%Y-%m-%d>" :head "#+title: %<%Y-%m-%d>\n"))
:head "#+title: %<%Y-%m-%d>")) "Capture templates for daily-notes in Org-roam."
"Capture templates for daily notes in Org-roam."
:group 'org-roam :group 'org-roam
;; Adapted from `org-capture-templates' ;; Adapted from `org-capture-templates'
:type :type
'(repeat '(repeat
(choice :value ("d" "daily" plain (function org-roam-capture--get-point) (choice :value ("d" "default" plain (function org-roam-capture--get-point)
"" "%?"
:immediate-finish t :file-name "daily/%<%Y-%m-%d>"
:file-name "%<%Y-%m-%d>" :head "#+title: %<%Y-%m-%d>\n"
:head "#+title: %<%Y-%m-%d>") :unnarrowed t)
(list :tag "Multikey description" (list :tag "Multikey description"
(string :tag "Keys ") (string :tag "Keys ")
(string :tag "Description")) (string :tag "Description"))
(list :tag "Template entry" (list :tag "Template entry"
(string :tag "Keys ") (string :tag "Keys ")
(string :tag "Description ") (string :tag "Description ")
(const :format "" plain) (choice :tag "Type "
(const :format "" (function org-roam-capture--get-point)) (const :tag "Plain" plain)
(choice :tag "Template " (const :tag "Entry (for creating headlines)" entry))
(string :tag "String" (const :format "" #'org-roam-capture--get-point)
:format "String:\n \ (choice :tag "Template "
(string :tag "String"
:format "String:\n \
Template string :\n%v") Template string :\n%v")
(list :tag "File" (list :tag "File"
(const :format "" file) (const :format "" file)
(file :tag "Template file ")) (file :tag "Template file "))
(list :tag "Function" (list :tag "Function"
(const :format "" function) (const :format "" function)
(function :tag "Template function "))) (function :tag "Template function ")))
(const :format "" :immediate-finish) (const :format "" t) (const :format "File name format :" :file-name)
(const :format "File name format :" :file-name) (string :format " %v" :value "daily/%<%Y-%m-%d>")
(string :format " %v" :value "#+title: ${title}\n") (const :format "Header format :" :head)
(const :format "Header format :" :head) (string :format " %v" :value "#+title: ${title}\n")
(string :format "\n%v" :value "%<%Y%m%d%H%M%S>-${slug}") (plist :inline t
(plist :inline t :tag "Options"
:tag "Options" ;; Give the most common options as checkboxes
;; Give the most common options as checkboxes :options
:options (((const :tag "Outline path" :olp)
(((const :format "%v " :prepend) (const t)) (repeat :tag "Headings"
((const :format "%v " :jump-to-captured) (const t)) (string :tag "Heading")))
((const :format "%v " :empty-lines) (const 1)) ((const :format "%v " :unnarrowed) (const t))
((const :format "%v " :empty-lines-before) (const 1)) ((const :format "%v " :prepend) (const t))
((const :format "%v " :empty-lines-after) (const 1)) ((const :format "%v " :immediate-finish) (const t))
((const :format "%v " :clock-in) (const t)) ((const :format "%v " :jump-to-captured) (const t))
((const :format "%v " :clock-keep) (const t)) ((const :format "%v " :empty-lines) (const 1))
((const :format "%v " :clock-resume) (const t)) ((const :format "%v " :empty-lines-before) (const 1))
((const :format "%v " :time-prompt) (const t)) ((const :format "%v " :empty-lines-after) (const 1))
((const :format "%v " :tree-type) (const week)) ((const :format "%v " :clock-in) (const t))
((const :format "%v " :table-line-pos) (string)) ((const :format "%v " :clock-keep) (const t))
((const :format "%v " :kill-buffer) (const t)) ((const :format "%v " :clock-resume) (const t))
((const :format "%v " :unnarrowed) (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))))))))
;; Declarations ;;;; Utilities
(defvar org-roam-mode) (defun org-roam-dailies-directory--get-absolute-path ()
(declare-function org-roam--file-path-from-id "org-roam") "Get absolute path to `org-roam-dailies-directory'."
(declare-function org-roam-mode "org-roam") (-> (concat
(file-name-as-directory org-roam-directory)
org-roam-dailies-directory)
(file-truename)))
(defun org-roam-dailies--file-for-time (time) (defun org-roam-dailies-find-directory ()
"Create and find file for TIME." "Find and open `org-roam-dailies-directory'."
(let ((org-roam-capture-templates org-roam-dailies-capture-templates) (interactive)
(org-roam--find-file (org-roam-dailies-directory--get-absolute-path)))
(defun org-roam-dailies--daily-note-p (&optional file)
"Return t if FILE is an Org-roam daily-note, nil otherwise.
If FILE is not specified, use the current buffer's file-path."
(when-let ((path (or file
(-> (buffer-base-buffer)
(buffer-file-name))))
(directory (org-roam-dailies-directory--get-absolute-path)))
(setq path (file-truename path))
(save-match-data
(and
(org-roam--org-file-p path)
(f-descendant-of-p path directory)))))
(defun org-roam-dailies--capture (time &optional goto)
"Capture an entry in a daily-note for TIME, creating it if necessary.
When GOTO is non-nil, go the note without creating an entry."
(unless org-roam-mode (org-roam-mode))
(let ((org-roam-capture-templates (--> org-roam-dailies-capture-templates
(if goto (list (car it)) it)))
(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))
(setq org-roam-capture-additional-template-props (list :finalize 'find-file)) (setq org-roam-capture-additional-template-props (list :finalize 'find-file))
(org-roam-capture--capture))) (org-roam-capture--capture (when goto '(4)))))
(defun org-roam-dailies-today () ;;;; Commands
"Create and find the daily note for today." ;;; Today
(defun org-roam-dailies-capture-today (&optional goto)
"Create an entry in the daily-note for today.
When GOTO is non-nil, go the note without creating an entry."
(interactive "P")
(org-roam-dailies--capture (current-time) goto)
(when goto
(run-hooks 'org-roam-dailies-find-file-hook)
(message "Showing daily-note for today")))
(defun org-roam-dailies-find-today ()
"Find the daily-note for today, creating it if necessary."
(interactive) (interactive)
(unless org-roam-mode (org-roam-mode)) (org-roam-dailies-capture-today t))
(org-roam-dailies--file-for-time (current-time)))
(defun org-roam-dailies-tomorrow (n) ;;; Tomorrow
"Create and find the daily note for tomorrow. (defun org-roam-dailies-capture-tomorrow (n &optional goto)
With numeric argument N, use N days in the future." "Create an entry in the daily-note for tomorrow.
With numeric argument N, use the daily-note N days in the future.
With a `C-u' prefix or when GOTO is non-nil, go the note without
creating an entry."
(interactive "p") (interactive "p")
(unless org-roam-mode (org-roam-mode)) (org-roam-dailies--capture (time-add (* n 86400) (current-time)) goto))
(org-roam-dailies--file-for-time (time-add (* n 86400) (current-time))))
(defun org-roam-dailies-yesterday (n) (defun org-roam-dailies-find-tomorrow (n)
"Create and find the file for yesterday. "Find the daily-note for tomorrow, creating it if necessary.
With numeric argument N, use N days in the past."
With numeric argument N, use the daily-note N days in the
future."
(interactive "p") (interactive "p")
(unless org-roam-mode (org-roam-mode)) (org-roam-dailies-capture-tomorrow n t))
(org-roam-dailies-tomorrow (- n)))
(defun org-roam-dailies-date () ;;; Yesterday
"Create the file for any date using the calendar interface." (defun org-roam-dailies-capture-yesterday (n &optional goto)
"Create an entry in the daily-note for yesteday.
With numeric argument N, use the daily-note N days in the past.
When GOTO is non-nil, go the note without creating an entry."
(interactive "p")
(org-roam-dailies-capture-tomorrow (- n) goto))
(defun org-roam-dailies-find-yesterday (n)
"Find the daily-note for yesterday, creating it if necessary.
With numeric argument N, use the daily-note N days in the
future."
(interactive "p")
(org-roam-dailies-capture-tomorrow (- n) t))
;;; Calendar
(defvar org-roam-dailies-calendar-hook (list 'org-roam-dailies-calendar-mark-entries)
"Hooks to run when showing the `org-roam-dailies-calendar'.")
(defun org-roam-dailies-calendar--install-hook ()
"Install Org-roam-dailies hooks to calendar."
(add-hook 'calendar-today-visible-hook #'org-roam-dailies-calendar--run-hook)
(add-hook 'calendar-today-invisible-hook #'org-roam-dailies-calendar--run-hook))
(defun org-roam-dailies-calendar--run-hook ()
"Run Org-roam-dailies hooks to calendar."
(run-hooks 'org-roam-dailies-calendar-hook)
(remove-hook 'calendar-today-visible-hook #'org-roam-dailies-calendar--run-hook)
(remove-hook 'calendar-today-invisible-hook #'org-roam-dailies-calendar--run-hook))
(defun org-roam-dailies-calendar--file-to-date (&optional file)
"Convert FILE to date.
Return (MONTH DAY YEAR)."
(let ((file (or file
(-> (buffer-base-buffer)
(buffer-file-name)))))
(cl-destructuring-bind (_ _ _ d m y _ _ _)
(-> file
(file-name-nondirectory)
(file-name-sans-extension)
(org-parse-time-string))
(list m d y))))
(defun org-roam-dailies-calendar--date-to-time (date)
"Convert DATE as returned from the calendar (MONTH DAY YEAR) to a time."
(encode-time 0 0 0 (nth 1 date) (nth 0 date) (nth 2 date)))
(defun org-roam-dailies-calendar-mark-entries ()
"Mark days in the calendar for which a daily-note is present."
(when (file-exists-p (org-roam-dailies-directory--get-absolute-path))
(dolist (date (mapcar #'org-roam-dailies-calendar--file-to-date
(org-roam-dailies--list-files)))
(when (calendar-date-is-visible-p date)
(calendar-mark-visible-date date 'org-roam-dailies-calendar-note)))))
;;; Date
(defun org-roam-dailies-capture-date (&optional goto prefer-future)
"Create an entry in the daily-note for a date using the calendar.
Prefer past dates, unless PREFER-FUTURE is non-nil.
With a `C-u' prefix or when GOTO is non-nil, go the note without
creating an entry."
(interactive "P")
(org-roam-dailies-calendar--install-hook)
(let* ((time-str (let ((org-read-date-prefer-future prefer-future))
(org-read-date nil nil nil (if goto
"Find daily-note: "
"Capture to daily-note: "))))
(time (org-read-date nil t time-str)))
(org-roam-dailies--capture time goto)
(when goto
(run-hooks 'org-roam-dailies-find-file-hook)
(message "Showing note for %s" time-str))))
(defun org-roam-dailies-find-date (&optional prefer-future)
"Find the daily-note for a date using the calendar, creating it if necessary.
Prefer past dates, unless PREFER-FUTURE is non-nil."
(interactive) (interactive)
(let ((time (org-read-date nil 'to-time nil "Date: "))) (org-roam-dailies-capture-date t prefer-future))
(org-roam-dailies--file-for-time time)))
;;; Navigation
(defun org-roam-dailies--list-files (&rest extra-files)
"List all files in `org-roam-dailies-directory'.
EXTRA-FILES can be used to append extra files to the list."
(let ((dir (org-roam-dailies-directory--get-absolute-path)))
(append (--remove (let ((file (file-name-nondirectory it)))
(when (or (auto-save-file-name-p file)
(backup-file-name-p file)
(string-match "^\\." file))
it))
(directory-files-recursively dir ""))
extra-files)))
(defun org-roam-dailies--find-next-note-path (&optional n file)
"Find next daily-note from FILE.
With numeric argument N, find note N days in the future. If N is
negative, find note N days in the past.
If FILE is not provided, use the file visited by the current
buffer."
(unless (org-roam-dailies--daily-note-p file)
(user-error "Not in a daily-note"))
(let ((n (or n 1))
(file (or file
(-> (buffer-base-buffer)
(buffer-file-name)))))
;; Ensure that the buffer is saved before moving
(save-buffer file)
(let* ((list (org-roam-dailies--list-files))
(position
(cl-position-if (lambda (candidate)
(string= file candidate))
list)))
(pcase n
((pred (natnump))
(if (eq position (- (length list) 1))
(user-error "Already at newest note")
(message "Showing next daily-note")))
((pred (integerp))
(if (eq position 0)
(user-error "Already at oldest note")
(message "Showing previous daily-note"))))
(nth (+ position n) list))))
(defun org-roam-dailies-find-next-note (&optional n)
"Find next daily-note.
With numeric argument N, find note N days in the future. If N is
negative, find note N days in the past."
(interactive "p")
(let* ((n (or n 1))
(next (org-roam-dailies--find-next-note-path n)))
(find-file next)
(run-hooks 'org-roam-dailies-find-file-hook)))
(defun org-roam-dailies-find-previous-note (&optional n)
"Find previous daily-note.
With numeric argument N, find note N days in the past. If N is
negative, find note N days in the future."
(interactive "p")
(let ((n (if n (- n) -1)))
(org-roam-dailies-find-next-note n)))
;;;; Bindings
(defvar org-roam-dailies-map (make-sparse-keymap)
"Keymap for `org-roam-dailies'.")
(define-prefix-command 'org-roam-dailies-map)
(define-key org-roam-dailies-map (kbd "d") #'org-roam-dailies-find-today)
(define-key org-roam-dailies-map (kbd "y") #'org-roam-dailies-find-yesterday)
(define-key org-roam-dailies-map (kbd "t") #'org-roam-dailies-find-tomorrow)
(define-key org-roam-dailies-map (kbd "n") #'org-roam-dailies-capture-today)
(define-key org-roam-dailies-map (kbd "f") #'org-roam-dailies-find-next-note)
(define-key org-roam-dailies-map (kbd "b") #'org-roam-dailies-find-previous-note)
(define-key org-roam-dailies-map (kbd "c") #'org-roam-dailies-find-date)
(define-key org-roam-dailies-map (kbd "v") #'org-roam-dailies-capture-date)
(define-key org-roam-dailies-map (kbd ".") #'org-roam-dailies-find-directory)
(provide 'org-roam-dailies) (provide 'org-roam-dailies)

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.2 ;; Version: 1.2.3
;; 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.2")) ;; 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.2"))
;; This file is NOT part of GNU Emacs. ;; This file is NOT part of GNU Emacs.
@ -35,17 +35,22 @@
(require 'emacsql) (require 'emacsql)
(require 'emacsql-sqlite3) (require 'emacsql-sqlite3)
(require 'seq) (require 'seq)
(require 'org-macs)
(require 'org-roam-macs) (eval-and-compile
(require 'org-roam-macs)
;; For `org-with-wide-buffer'
(require 'org-macs))
(defvar org-roam-directory) (defvar org-roam-directory)
(defvar org-roam-enable-headline-linking) (defvar org-roam-enable-headline-linking)
(defvar org-roam-verbose) (defvar org-roam-verbose)
(defvar org-roam-file-name) (defvar org-roam-file-name)
(defvar org-agenda-files)
(declare-function org-roam--org-roam-file-p "org-roam") (declare-function org-roam--org-roam-file-p "org-roam")
(declare-function org-roam--extract-titles "org-roam") (declare-function org-roam--extract-titles "org-roam")
(declare-function org-roam--extract-ref "org-roam") (declare-function org-roam--extract-refs "org-roam")
(declare-function org-roam--extract-tags "org-roam") (declare-function org-roam--extract-tags "org-roam")
(declare-function org-roam--extract-ids "org-roam") (declare-function org-roam--extract-ids "org-roam")
(declare-function org-roam--extract-links "org-roam") (declare-function org-roam--extract-links "org-roam")
@ -79,11 +84,33 @@ value like `most-positive-fixnum'."
:type 'int :type 'int
:group 'org-roam) :group 'org-roam)
(defconst org-roam-db--version 9) (defconst org-roam-db--version 10)
(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.")
(defvar org-roam-db-dirty nil
"Whether the org-roam database is dirty and requires an update.
Contains pairs of `org-roam-directory' and `org-roam-db-location'
so that multi-directories are updated.")
(defcustom org-roam-db-update-method 'idle-timer
"Method to update the Org-roam database.
`immediate'
Update the database immediately upon file changes.
`idle-timer'
Updates the database if dirty, if Emacs idles for `org-roam-db-update-idle-seconds'."
:type '(set (const :tag "idle-timer" idle-timer)
(const :tag "immediate" immediate))
:group 'org-roam)
(defcustom org-roam-db-update-idle-seconds 2
"Number of idle seconds before triggering an Org-roam database update."
:type 'integer
:group 'org-roam)
;;;; Core Functions ;;;; Core Functions
(defun org-roam-db--get-connection () (defun org-roam-db--get-connection ()
@ -141,8 +168,8 @@ SQL can be either the emacsql vector representation, or a string."
(level :not-null)]) (level :not-null)])
(links (links
[(from :not-null) [(source :not-null)
(to :not-null) (dest :not-null)
(type :not-null) (type :not-null)
(properties :not-null)]) (properties :not-null)])
@ -191,6 +218,22 @@ the current `org-roam-directory'."
(dolist (conn (hash-table-values org-roam-db--connection)) (dolist (conn (hash-table-values org-roam-db--connection))
(org-roam-db--close conn))) (org-roam-db--close conn)))
;;;; Timer-based updating
(defvar org-roam-db-file-update-timer nil
"Timer for updating the database when dirty.")
(defun org-roam-db-mark-dirty ()
"Mark the Org-roam database as dirty."
(add-to-list 'org-roam-db-dirty (list org-roam-directory org-roam-db-location)
nil #'equal))
(defun org-roam-db-update-cache-on-timer ()
"Update the cache if the database is dirty.
This function is called on `org-roam-db-file-update-timer'."
(pcase-dolist (`(,org-roam-directory ,org-roam-db-location) org-roam-db-dirty)
(org-roam-db-build-cache))
(setq org-roam-db-dirty nil))
;;;; Database API ;;;; Database API
;;;;; Initialization ;;;;; Initialization
(defun org-roam-db--initialized-p () (defun org-roam-db--initialized-p ()
@ -219,82 +262,137 @@ This is equivalent to removing the node from the graph."
(buffer-file-name (buffer-base-buffer)))))) (buffer-file-name (buffer-base-buffer))))))
(dolist (table (mapcar #'car org-roam-db--table-schemata)) (dolist (table (mapcar #'car org-roam-db--table-schemata))
(org-roam-db-query `[:delete :from ,table (org-roam-db-query `[:delete :from ,table
:where (= ,(if (eq table 'links) 'from 'file) $s1)] :where (= ,(if (eq table 'links) 'source 'file) $s1)]
file)))) file))))
;;;;; Insertion ;;;;; Inserting
(defun org-roam-db--insert-meta (file hash meta) (defun org-roam-db--insert-meta (&optional update-p)
"Insert HASH and META for a FILE into the Org-roam cache." "Update the metadata of the current buffer into the cache.
(org-roam-db-query If UPDATE-P is non-nil, first remove the meta for the file in the database."
[:insert :into files (let* ((file (or org-roam-file-name (buffer-file-name)))
:values $v1] (attr (file-attributes file))
(list (vector file hash meta)))) (atime (file-attribute-access-time attr))
(mtime (file-attribute-modification-time attr))
(hash (org-roam-db--file-hash)))
(when update-p
(org-roam-db-query [:delete :from files
:where (= file $s1)]
file))
(org-roam-db-query
[:insert :into files
:values $v1]
(list (vector file hash (list :atime atime :mtime mtime))))))
(defun org-roam-db--insert-links (links) (defun org-roam-db--insert-titles (&optional update-p)
"Insert LINKS into the Org-roam cache." "Update the titles of the current buffer into the cache.
(org-roam-db-query If UPDATE-P is non-nil, first remove titles for the file in the database.
[:insert :into links Returns the number of rows inserted."
:values $v1] (let* ((file (or org-roam-file-name (buffer-file-name)))
links)) (titles (or (org-roam--extract-titles)
(list (org-roam--path-to-slug file))))
(rows (mapcar (lambda (title)
(vector file title)) titles)))
(when update-p
(org-roam-db-query [:delete :from titles
:where (= file $s1)]
file))
(org-roam-db-query
[:insert :into titles
:values $v1]
rows)
(length rows)))
(defun org-roam-db--insert-titles (file titles) (defun org-roam-db--insert-refs (&optional update-p)
"Insert TITLES for a FILE into the Org-roam cache." "Update the refs of the current buffer into the cache.
(org-roam-db-query If UPDATE-P is non-nil, first remove the ref for the file in the database."
[:insert :into titles (let ((file (or org-roam-file-name (buffer-file-name)))
:values $v1] (count 0))
(mapcar (lambda (title) (when update-p
(vector file title)) titles))) (org-roam-db-query [:delete :from refs
:where (= file $s1)]
file))
(when-let ((refs (org-roam--extract-refs)))
(dolist (ref refs)
(let ((key (cdr ref))
(type (car ref)))
(condition-case nil
(progn
(org-roam-db-query
[:insert :into refs :values $v1]
(list (vector key file type)))
(cl-incf count))
(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))))))))))
count))
(defun org-roam-db--insert-ids (ids) (defun org-roam-db--insert-links (&optional update-p)
"Insert IDS into the Org-roam cache. "Update the file links of the current buffer in the cache.
Returns t if the insertion was successful, nil otherwise. If UPDATE-P is non-nil, first remove the links for the file in the database.
Insertions can fail when there is an ID conflict." Return the number of rows inserted."
(condition-case nil (let ((file (or org-roam-file-name (buffer-file-name))))
(progn (when update-p
(org-roam-db-query (org-roam-db-query [:delete :from links
[:insert :into ids :where (= source $s1)]
:values $v1] file))
ids) (if-let ((links (org-roam--extract-links)))
t)
(error
(unless (listp ids)
(setq ids (list ids)))
(lwarn '(org-roam) :error
(format "Duplicate IDs in %s, one of:\n\n%s\n\nskipping..."
(aref (car ids) 1)
(string-join (mapcar (lambda (hl)
(aref hl 0)) ids) "\n")))
nil)))
(defun org-roam-db--insert-tags (file tags)
"Insert TAGS for a FILE into the Org-roam cache."
(org-roam-db-query
[:insert :into tags
:values $v1]
(list (vector file tags))))
(defun org-roam-db--insert-ref (file ref)
"Insert REF for FILE into the Org-roam cache.
Returns t if successful, and nil otherwise.
Insertions can fail if the key is already in the database."
(let ((key (cdr ref))
(type (car ref)))
(condition-case nil
(progn (progn
(org-roam-db-query (org-roam-db-query
[:insert :into refs :values $v1] [:insert :into links
(list (vector key file type))) :values $v1]
t) links)
(error (length links))
(lwarn '(org-roam) :error 0)))
(format "Duplicate ref %s in:\n\nA: %s\nB: %s\n\nskipping..."
key (defun org-roam-db--insert-ids (&optional update-p)
file "Update the ids of the current buffer into the cache.
(caar (org-roam-db-query If UPDATE-P is non-nil, first remove ids for the file in the database.
[:select file :from refs Returns the number of rows inserted."
:where (= ref $v1)] (let ((file (or org-roam-file-name (buffer-file-name))))
(vector key))))) (when update-p
nil)))) (org-roam-db-query [:delete :from ids
:where (= file $s1)]
file))
(if-let ((ids (org-roam--extract-ids file)))
(condition-case nil
(progn
(org-roam-db-query
[:insert :into ids
:values $v1]
ids)
(length ids))
(error
(lwarn '(org-roam) :error
(format "Duplicate IDs in %s, one of:\n\n%s\n\nskipping..."
(aref (car ids) 1)
(string-join (mapcar (lambda (hl)
(aref hl 0)) ids) "\n")))
0))
0)))
(defun org-roam-db--insert-tags (&optional update-p)
"Insert tags for the current buffer into the Org-roam cache.
If UPDATE-P is non-nil, first remove tags for the file in the database.
Return the number of rows inserted."
(let* ((file (or org-roam-file-name (buffer-file-name)))
(tags (org-roam--extract-tags file)))
(when update-p
(org-roam-db-query [:delete :from tags
:where (= file $s1)]
file))
(if tags
(progn (org-roam-db-query
[:insert :into tags
:values $v1]
(list (vector file tags)))
1)
0)))
;;;;; Fetching ;;;;; Fetching
(defun org-roam-db--get-current-files () (defun org-roam-db--get-current-files ()
@ -329,12 +427,12 @@ If the file does not have any connections, nil is returned."
links_of(file, link) AS links_of(file, link) AS
(WITH filelinks AS (SELECT * FROM links WHERE NOT \"type\" = '\"cite\"'), (WITH filelinks AS (SELECT * FROM links WHERE NOT \"type\" = '\"cite\"'),
citelinks AS (SELECT * FROM links citelinks AS (SELECT * FROM links
JOIN refs ON links.\"to\" = refs.\"ref\" JOIN refs ON links.\"dest\" = refs.\"ref\"
AND links.\"type\" = '\"cite\"') AND links.\"type\" = '\"cite\"')
SELECT \"from\", \"to\" FROM filelinks UNION SELECT \"source\", \"dest\" FROM filelinks UNION
SELECT \"to\", \"from\" FROM filelinks UNION SELECT \"dest\", \"source\" FROM filelinks UNION
SELECT \"file\", \"from\" FROM citelinks UNION SELECT \"file\", \"source\" FROM citelinks UNION
SELECT \"from\", \"file\" FROM citelinks), SELECT \"dest\", \"file\" FROM citelinks),
connected_component(file) AS connected_component(file) AS
(SELECT link FROM links_of WHERE file = $s1 (SELECT link FROM links_of WHERE file = $s1
UNION UNION
@ -351,12 +449,12 @@ connections, nil is returned."
links_of(file, link) AS links_of(file, link) AS
(WITH filelinks AS (SELECT * FROM links WHERE NOT \"type\" = '\"cite\"'), (WITH filelinks AS (SELECT * FROM links WHERE NOT \"type\" = '\"cite\"'),
citelinks AS (SELECT * FROM links citelinks AS (SELECT * FROM links
JOIN refs ON links.\"to\" = refs.\"ref\" JOIN refs ON links.\"dest\" = refs.\"ref\"
AND links.\"type\" = '\"cite\"') AND links.\"type\" = '\"cite\"')
SELECT \"from\", \"to\" FROM filelinks UNION SELECT \"source\", \"dest\" FROM filelinks UNION
SELECT \"to\", \"from\" FROM filelinks UNION SELECT \"dest\", \"source\" FROM filelinks UNION
SELECT \"file\", \"from\" FROM citelinks UNION SELECT \"file\", \"source\" FROM citelinks UNION
SELECT \"from\", \"file\" FROM citelinks), SELECT \"source\", \"file\" FROM citelinks),
-- Links are traversed in a breadth-first search. In order to calculate the -- Links are traversed in a breadth-first search. In order to calculate the
-- distance of nodes and to avoid following cyclic links, the visited nodes -- distance of nodes and to avoid following cyclic links, the visited nodes
-- are tracked in 'trace'. -- are tracked in 'trace'.
@ -387,65 +485,6 @@ connections, nil is returned."
(secure-hash 'sha1 (current-buffer))))) (secure-hash 'sha1 (current-buffer)))))
;;;;; Updating ;;;;; Updating
(defun org-roam-db--update-meta ()
"Update the metadata of the current buffer into the cache."
(let* ((file (or org-roam-file-name (buffer-file-name)))
(attr (file-attributes file))
(atime (file-attribute-access-time attr))
(mtime (file-attribute-modification-time attr))
(hash (org-roam-db--file-hash)))
(org-roam-db-query [:delete :from files
:where (= file $s1)]
file)
(org-roam-db--insert-meta file hash (list :atime atime :mtime mtime))))
(defun org-roam-db--update-titles ()
"Update the title of the current buffer into the cache."
(let* ((file (or org-roam-file-name (buffer-file-name)))
(titles (or (org-roam--extract-titles)
(list (org-roam--path-to-slug file)))))
(org-roam-db-query [:delete :from titles
:where (= file $s1)]
file)
(org-roam-db--insert-titles file titles)))
(defun org-roam-db--update-tags ()
"Update the tags of the current buffer into the cache."
(let* ((file (or org-roam-file-name (buffer-file-name)))
(tags (org-roam--extract-tags file)))
(org-roam-db-query [:delete :from tags
:where (= file $s1)]
file)
(when tags
(org-roam-db--insert-tags file tags))))
(defun org-roam-db--update-refs ()
"Update the ref of the current buffer into the cache."
(let ((file (or org-roam-file-name (buffer-file-name))))
(org-roam-db-query [:delete :from refs
:where (= file $s1)]
file)
(when-let ((ref (org-roam--extract-ref)))
(org-roam-db--insert-ref file ref))))
(defun org-roam-db--update-links ()
"Update the file links of the current buffer in the cache."
(let ((file (or org-roam-file-name (buffer-file-name))))
(org-roam-db-query [:delete :from links
:where (= from $s1)]
file)
(when-let ((links (org-roam--extract-links)))
(org-roam-db--insert-links links))))
(defun org-roam-db--update-ids ()
"Update the ids of the current buffer into the cache."
(let* ((file (or org-roam-file-name (buffer-file-name))))
(org-roam-db-query [:delete :from ids
:where (= file $s1)]
file)
(when-let ((ids (org-roam--extract-ids file)))
(org-roam-db--insert-ids ids))))
(defun org-roam-db--update-file (&optional file-path) (defun org-roam-db--update-file (&optional file-path)
"Update Org-roam cache for FILE-PATH. "Update Org-roam cache for FILE-PATH.
If the file does not exist anymore, remove it from the cache. If the file does not exist anymore, remove it from the cache.
@ -460,13 +499,13 @@ If the file exists, update the cache with information."
(save-buffer))) (save-buffer)))
(org-roam--with-temp-buffer file-path (org-roam--with-temp-buffer file-path
(emacsql-with-transaction (org-roam-db) (emacsql-with-transaction (org-roam-db)
(org-roam-db--update-meta) (org-roam-db--insert-meta 'update)
(org-roam-db--update-tags) (org-roam-db--insert-tags 'update)
(org-roam-db--update-titles) (org-roam-db--insert-titles 'update)
(org-roam-db--update-refs) (org-roam-db--insert-refs 'update)
(when org-roam-enable-headline-linking (when org-roam-enable-headline-linking
(org-roam-db--update-ids)) (org-roam-db--insert-ids 'update))
(org-roam-db--update-links))))) (org-roam-db--insert-links 'update)))))
(defun org-roam-db-build-cache (&optional force) (defun org-roam-db-build-cache (&optional force)
"Build the cache for `org-roam-directory'. "Build the cache for `org-roam-directory'.
@ -476,72 +515,67 @@ If FORCE, force a rebuild of the cache from scratch."
(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* ((gc-cons-threshold org-roam-db-gc-threshold) (let* ((gc-cons-threshold org-roam-db-gc-threshold)
(org-agenda-files nil) (org-agenda-files nil)
(org-roam-files (org-roam--list-all-files)) (org-roam-files (org-roam--list-all-files))
(current-files (org-roam-db--get-current-files)) (current-files (org-roam-db--get-current-files))
(file-count 0)
(id-count 0) (id-count 0)
(link-count 0) (link-count 0)
(tag-count 0) (tag-count 0)
(title-count 0) (title-count 0)
(ref-count 0) (ref-count 0)
(deleted-count 0) (deleted-count 0)
(processed-count 0)) (modified-count 0)
(emacsql-with-transaction (org-roam-db) (modified-files nil))
;; Two-step building (dolist (file org-roam-files)
;; First step: Rebuild files and ids (let ((contents-hash (org-roam-db--file-hash file)))
(dolist (file org-roam-files) (unless (string= (gethash file current-files)
(org-roam-message "Processed %s/%s files..." processed-count (length org-roam-files)) contents-hash)
(let* ((attr (file-attributes file)) (push (cons file contents-hash) modified-files)))
(atime (file-attribute-access-time attr)) (remhash file current-files))
(mtime (file-attribute-modification-time attr))) (dolist (file (hash-table-keys current-files))
(let ((contents-hash (org-roam-db--file-hash file)))
(unless (string= (gethash file current-files)
contents-hash)
(condition-case nil
(org-roam--with-temp-buffer file
(org-roam-db--clear-file file)
(org-roam-db-query
[:insert :into files
:values $v1]
(vector file contents-hash (list :atime atime :mtime mtime)))
(setq file-count (1+ file-count))
(when org-roam-enable-headline-linking
(when-let ((ids (org-roam--extract-ids file)))
(when (org-roam-db--insert-ids ids)
(setq id-count (+ id-count (length ids))))))
(when-let (links (org-roam--extract-links file))
(org-roam-db-query
[:insert :into links
:values $v1]
links)
(setq link-count (1+ link-count)))
(when-let (tags (org-roam--extract-tags file))
(org-roam-db-query
[:insert :into tags
:values $v1]
(vector file tags))
(setq tag-count (1+ tag-count)))
(let ((titles (or (org-roam--extract-titles)
(list (org-roam--path-to-slug file)))))
(org-roam-db--insert-titles file titles)
(setq title-count (+ title-count (length titles))))
(when-let* ((ref (org-roam--extract-ref)))
(when (org-roam-db--insert-ref file ref)
(setq ref-count (1+ ref-count)))))
(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))))
(remhash file current-files)
(setq processed-count (+ processed-count 1)))))
(dolist (file (hash-table-keys current-files))
;; These files are no longer around, remove from cache... ;; These files are no longer around, remove from cache...
(org-roam-db--clear-file file) (org-roam-db--clear-file file)
(setq deleted-count (1+ deleted-count)))) (setq deleted-count (1+ deleted-count)))
(org-roam-message "files: Δ%s, ids: Δ%s, links: Δ%s, tags: Δ%s, titles: Δ%s, refs: Δ%s, deleted: Δ%s" (pcase-dolist (`(,file . _) modified-files)
file-count (org-roam-db--clear-file file))
;; Process all the files for IDs first
;;
;; We do this so that link extraction is cheaper: this eliminates the need
;; to read the file to check if the ID really exists
(pcase-dolist (`(,file . ,contents-hash) modified-files)
(let* ((attr (file-attributes file))
(atime (file-attribute-access-time attr))
(mtime (file-attribute-modification-time attr)))
(condition-case nil
(org-roam--with-temp-buffer file
(org-roam-db-query
[:insert :into files
:values $v1]
(vector file contents-hash (list :atime atime :mtime mtime)))
(when org-roam-enable-headline-linking
(setq id-count (+ id-count (org-roam-db--insert-ids)))))
(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)))))
(pcase-dolist (`(,file . _) modified-files)
(org-roam-message "Processed %s/%s modified files..." modified-count (length modified-files))
(condition-case nil
(org-roam--with-temp-buffer file
(setq modified-count (1+ modified-count))
(setq link-count (+ link-count (org-roam-db--insert-links)))
(setq tag-count (+ tag-count (org-roam-db--insert-tags)))
(setq title-count (+ title-count (org-roam-db--insert-titles)))
(setq ref-count (+ ref-count (org-roam-db--insert-refs))))
(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))))
(org-roam-message "total: Δ%s, files-modified: Δ%s, ids: Δ%s, links: Δ%s, tags: Δ%s, titles: Δ%s, refs: Δ%s, deleted: Δ%s"
(length org-roam-files)
modified-count
id-count id-count
link-count link-count
tag-count tag-count
@ -549,6 +583,16 @@ If FORCE, force a rebuild of the cache from scratch."
ref-count ref-count
deleted-count))) deleted-count)))
(defun org-roam-db-update ()
"Update the database."
(pcase org-roam-db-update-method
('immediate
(org-roam-db-build-cache))
('idle-timer
(org-roam-db-mark-dirty))
(_
(user-error "Invalid `org-roam-db-update-method'"))))
(provide 'org-roam-db) (provide 'org-roam-db)
;;; org-roam-db.el ends here ;;; org-roam-db.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/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.2 ;; Version: 1.2.3
;; 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.2")) ;; 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.2"))
;; 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/jethrokuan/org-roam ;; URL: https://github.com/jethrokuan/org-roam
;; Keywords: org-mode, roam, convenience ;; Keywords: org-mode, roam, convenience
;; Version: 1.2.2 ;; Version: 1.2.3
;; 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.2")) ;; 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.2"))
;; 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.2 ;; Version: 1.2.3
;; Package-Requires: ((emacs "26.1")) ;; Package-Requires: ((emacs "26.1"))
;; This file is NOT part of GNU Emacs. ;; This file is NOT part of GNU Emacs.
@ -65,6 +65,11 @@ This face is used on the region target by `org-roam-insertion'
during an `org-roam-capture'." during an `org-roam-capture'."
:group 'org-roam-faces) :group 'org-roam-faces)
(defface org-roam-dailies-calendar-note
'((t :inherit (org-roam-link) :underline nil))
"Face for dates with a daily-note in the calendar"
:group 'org-roam-faces)
;;; _ ;;; _
(provide 'org-roam-faces) (provide 'org-roam-faces)

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.2 ;; Version: 1.2.3
;; 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.2")) ;; 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.2"))
;; This file is NOT part of GNU Emacs. ;; This file is NOT part of GNU Emacs.
@ -32,7 +32,8 @@
;;; Code: ;;; Code:
(require 'xml) ;xml-escape-string (require 'xml) ;xml-escape-string
(require 's) ;s-truncate, s-replace (require 's) ;s-truncate, s-replace
(require 'org-roam-macs) (eval-and-compile
(require 'org-roam-macs))
(require 'org-roam-db) (require 'org-roam-db)
;;;; Declarations ;;;; Declarations
@ -169,15 +170,15 @@ into a digraph."
(let* ((nodes (org-roam-db-query node-query)) (let* ((nodes (org-roam-db-query node-query))
(edges-query (edges-query
`[:with selected :as [:select [file] :from ,node-query] `[:with selected :as [:select [file] :from ,node-query]
:select :distinct [to from] :from links :select :distinct [dest source] :from links
:where (and (in to selected) (in from selected))]) :where (and (in dest selected) (in source selected))])
(edges-cites-query (edges-cites-query
`[:with selected :as [:select [file] :from ,node-query] `[:with selected :as [:select [file] :from ,node-query]
:select :distinct [file from] :select :distinct [file source]
:from links :inner :join refs :on (and (= links:to refs:ref) :from links :inner :join refs :on (and (= links:dest refs:ref)
(= links:type "cite") (= links:type "cite")
(= refs:type "cite")) (= refs:type "cite"))
:where (and (in file selected) (in from selected))]) :where (and (in file selected) (in source selected))])
(edges (org-roam-db-query edges-query)) (edges (org-roam-db-query edges-query))
(edges-cites (org-roam-db-query edges-cites-query))) (edges-cites (org-roam-db-query edges-cites-query)))
(insert "digraph \"org-roam\" {\n") (insert "digraph \"org-roam\" {\n")

View File

@ -6,7 +6,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.2 ;; Version: 1.2.3
;; 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.2")) ;; 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.2"))
;; This file is NOT part of GNU Emacs. ;; This file is NOT part of GNU Emacs.
@ -36,6 +36,10 @@
(require 'ol) (require 'ol)
(require 'org-roam-compat) (require 'org-roam-compat)
(require 'org-roam-macs)
(require 'org-roam-db)
(require 'org-element)
(defvar org-roam-completion-ignore-case) (defvar org-roam-completion-ignore-case)
(defvar org-roam-directory) (defvar org-roam-directory)
@ -155,11 +159,11 @@ If there is no corresponding headline, return nil."
(org-id-get-create)))))))) (org-id-get-create))))))))
;;; Path-related functions ;;; Path-related functions
(defun org-roam-link-get-path (path) (defun org-roam-link-get-path (path &optional type)
"Return the PATH of the link to use. "Return the PATH of the link to use.
Respect `org-link-file-path-type', see the variable documentation for details. If TYPE is non-nil, create a link of TYPE. Otherwise, respect
If DIR is passed, use DIR as the default directory." `org-link-file-path-type'."
(pcase org-roam-link-file-path-type (pcase (or type org-roam-link-file-path-type)
('absolute ('absolute
(abbreviate-file-name (expand-file-name path))) (abbreviate-file-name (expand-file-name path)))
('noabbrev ('noabbrev

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.2 ;; Version: 1.2.3
;; 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.2")) ;; 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.2"))
;; This file is NOT part of GNU Emacs. ;; This file is NOT part of GNU Emacs.
@ -34,9 +34,15 @@
;;; Code: ;;; Code:
;;;; Library Requires ;;;; Library Requires
(require 'dash) (require 'dash)
(require 's)
(defvar org-roam-verbose) (defvar org-roam-verbose)
;; This is necessary to ensure all dependents on this module see
;; `org-mode-hook' and `org-inhibit-startup' as dynamic variables,
;; regardless of whether Org is loaded before their compilation.
(require 'org)
;;;; Utility Functions ;;;; Utility Functions
(defun org-roam--list-interleave (lst separator) (defun org-roam--list-interleave (lst separator)
"Interleaves elements in LST with SEPARATOR." "Interleaves elements in LST with SEPARATOR."

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.2 ;; Version: 1.2.3
;; 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.
@ -39,6 +39,11 @@
(require 'org-roam) (require 'org-roam)
(require 'ol) ;; for org-link-decode (require 'ol) ;; for org-link-decode
(defcustom org-roam-protocol-store-links nil
"Whether to store links when capturing websites with `org-roam-protocol'."
:type 'boolean
:group 'org-roam)
;;;; Functions ;;;; Functions
(defun org-roam-protocol-open-ref (info) (defun org-roam-protocol-open-ref (info)
"Process an org-protocol://roam-ref?ref= style url with INFO. "Process an org-protocol://roam-ref?ref= style url with INFO.
@ -46,7 +51,7 @@
It opens or creates a note with the given ref. It opens or creates a note with the given ref.
javascript:location.href = \\='org-protocol://roam-ref?template=r&ref=\\='+ \\ javascript:location.href = \\='org-protocol://roam-ref?template=r&ref=\\='+ \\
encodeURIComponent(location.href) + \\='&title=\\=' \\ encodeURIComponent(location.href) + \\='&title=\\=' + \\
encodeURIComponent(document.title) + \\='&body=\\=' + \\ encodeURIComponent(document.title) + \\='&body=\\=' + \\
encodeURIComponent(window.getSelection())" encodeURIComponent(window.getSelection())"
(when-let* ((alist (org-roam--plist-to-alist info)) (when-let* ((alist (org-roam--plist-to-alist info))
@ -58,6 +63,20 @@ It opens or creates a note with the given ref.
(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 (funcall org-roam-title-to-slug-function title)) decoded-alist)) (push (cons 'slug (funcall org-roam-title-to-slug-function title)) decoded-alist))
(let-alist decoded-alist
(let* ((ref (org-protocol-sanitize-uri .ref))
(type (and (string-match "^\\([a-z]+\\):" ref)
(match-string 1 ref)))
(title (or .title ""))
(body (or .body ""))
(orglink
(org-link-make-string ref (or (org-string-nw-p title) ref))))
(when org-roam-protocol-store-links
(push (list ref title) org-stored-links))
(org-link-store-props :type type
:link ref
:annotation orglink
:initial body)))
(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)

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.2 ;; Version: 1.2.3
;; 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.2")) ;; 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.2"))
;; This file is NOT part of GNU Emacs. ;; This file is NOT part of GNU Emacs.
@ -114,11 +114,6 @@ If nil, `find-file' is used."
:type 'function :type 'function
:group 'org-roam) :group 'org-roam)
(defcustom org-roam-update-db-idle-seconds 2
"Number of idle seconds before triggering an Org-roam database update."
:type 'integer
: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.
@ -147,6 +142,11 @@ Formatter may be a function that takes title as its only argument."
(function :tag "Custom function")) (function :tag "Custom function"))
:group 'org-roam) :group 'org-roam)
(defcustom org-roam-prefer-id-links t
"If non-nil, use ID for linking instead where available."
:type 'boolean
:group 'org-roam)
(defcustom org-roam-list-files-commands (defcustom org-roam-list-files-commands
(if (member system-type '(windows-nt ms-dos cygwin)) (if (member system-type '(windows-nt ms-dos cygwin))
nil nil
@ -304,12 +304,6 @@ descriptive warnings when certain operations fail (e.g. parsing).")
"]")) "]"))
"Matches a typed link in double brackets.") "Matches a typed link in double brackets.")
(defvar org-roam--file-update-timer nil
"Timer for updating the database on file changes.")
(defvar org-roam--file-update-queue nil
"List of files that need to be processed for a database update. Processed within `org-roam--file-update-timer'.")
;;;; Utilities ;;;; Utilities
(defun org-roam--plist-to-alist (plist) (defun org-roam--plist-to-alist (plist)
"Return an alist of the property-value pairs in PLIST." "Return an alist of the property-value pairs in PLIST."
@ -372,13 +366,14 @@ Like `file-name-extension', but does not strip version number."
If FILE is not specified, use the current buffer's file-path." If FILE is not specified, use the current buffer's file-path."
(when-let ((path (or file (when-let ((path (or file
org-roam-file-name org-roam-file-name
(buffer-file-name)))) (-> (buffer-base-buffer)
(save-match-data (buffer-file-name)))))
(and (save-match-data
(org-roam--org-file-p path) (and
(not (and org-roam-file-exclude-regexp (org-roam--org-file-p path)
(string-match-p org-roam-file-exclude-regexp path))) (not (and org-roam-file-exclude-regexp
(f-descendant-of-p path (expand-file-name org-roam-directory)))))) (string-match-p org-roam-file-exclude-regexp path)))
(f-descendant-of-p path (expand-file-name org-roam-directory))))))
(defun org-roam--shell-command-files (cmd) (defun org-roam--shell-command-files (cmd)
"Run CMD in the shell and return a list of files. If no files are found, an empty list is returned." "Run CMD in the shell and return a list of files. If no files are found, an empty list is returned."
@ -510,22 +505,30 @@ Use external shell commands if defined in `org-roam-list-files-commands'."
;;;; Org extraction functions ;;;; Org extraction functions
(defun org-roam--extract-global-props (props) (defun org-roam--extract-global-props (props)
"Extract PROPS from the current org buffer. "Extract PROPS from the current org buffer."
The search terminates when the first property is encountered." (let ((collected
(if (functionp 'org-collect-keywords) ;; Collect the raw props first
(->> (org-collect-keywords props) ;; It'll be returned in the form of
;; convert (("TITLE" "my title")) to (("TITLE" . "my title")) ;; (("PROP" "value" ...) ("PROP2" "value" ...))
(mapcar (pcase-lambda (`(,k ,v)) (cons k v)))) (if (functionp 'org-collect-keywords)
(let ((buf (org-element-parse-buffer)) (org-collect-keywords props)
res) (let ((buf (org-element-parse-buffer))
(dolist (prop props) res)
(let ((p (org-element-map buf 'keyword (dolist (prop props)
(lambda (kw) (let ((p (org-element-map buf 'keyword
(when (string-equal (org-element-property :key kw) prop) (lambda (kw)
(org-element-property :value kw))) (when (string-equal (org-element-property :key kw) prop)
:first-match t))) (org-element-property :value kw)))
(push (cons prop p) res))) :first-match nil)))
res))) (push (cons prop p) res)))
res))))
;; convert (("TITLE" "a" "b") ("Another" "c"))
;; to (("TITLE" . "a") ("TITLE" . "b") ("Another" . "c"))
(let (ret)
(pcase-dolist (`(,key . ,values) collected)
(dolist (value values)
(push (cons key value) ret)))
ret)))
(defun org-roam--get-outline-path () (defun org-roam--get-outline-path ()
"Return the outline path to the current entry. "Return the outline path to the current entry.
@ -568,7 +571,7 @@ Assume buffer is widened and point is on a headline."
"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:
[from to type properties] [source dest type properties]
This is the format that emacsql expects when inserting into the database. 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
@ -586,20 +589,20 @@ it as FILE-PATH."
(let* ((type (org-roam--collate-types (org-element-property :type link))) (let* ((type (org-roam--collate-types (org-element-property :type link)))
(path (org-element-property :path link)) (path (org-element-property :path link))
(element (org-element-at-point)) (element (org-element-at-point))
(begin (or (org-element-property :content-begin element) (begin (or (org-element-property :contents-begin element)
(org-element-property :begin element))) (org-element-property :begin element)))
(end (or (org-element-property :contents-end element)
(org-element-property :end element)))
(content (or (org-element-property :raw-value element) (content (or (org-element-property :raw-value element)
(buffer-substring-no-properties (when (and begin end)
begin (string-trim (buffer-substring-no-properties begin end)))))
(or (org-element-property :content-end element)
(org-element-property :end element)))))
(content (string-trim content))
(properties (list :outline (org-roam--get-outline-path) (properties (list :outline (org-roam--get-outline-path)
:content content :content content
:point begin)) :point begin))
(names (pcase type (names (pcase type
("id" ("id"
(list (car (org-roam-id-find path)))) (when-let ((file-path (org-roam-id-get-file path)))
(list file-path)))
("cite" (list path)) ("cite" (list path))
("website" (list path)) ("website" (list path))
("fuzzy" (list path)) ("fuzzy" (list path))
@ -623,8 +626,9 @@ If FILE-PATH is nil, use the current file."
(let (result) (let (result)
;; We need to handle the special case of the file property drawer (at outline level 0) ;; We need to handle the special case of the file property drawer (at outline level 0)
(org-with-point-at (point-min) (org-with-point-at (point-min)
(when-let ((id (org-entry-get nil "ID"))) (when-let ((before-first-heading (= 0 (org-outline-level)))
(push (vector id file-path (org-outline-level)) result))) (id (org-entry-get nil "ID")))
(push (vector id file-path 0) result)))
(org-map-region (org-map-region
(lambda () (lambda ()
(when-let ((id (org-entry-get nil "ID"))) (when-let ((id (org-entry-get nil "ID")))
@ -671,18 +675,19 @@ Reads from the \"roam_alias\" property."
(defun org-roam--extract-titles (&optional sources nested) (defun org-roam--extract-titles (&optional sources nested)
"Extract the titles from current buffer using SOURCES. "Extract the titles from current buffer using SOURCES.
If NESTED, return the first successful result from SOURCES." If NESTED, return the first successful result from SOURCES."
(let (coll res) (org-with-wide-buffer
(cl-dolist (source (or sources (let (coll res)
org-roam-title-sources)) (cl-dolist (source (or sources
(setq res (if (symbolp source) org-roam-title-sources))
(funcall (intern (concat "org-roam--extract-titles-" (symbol-name source)))) (setq res (if (symbolp source)
(org-roam--extract-titles source t))) (funcall (intern (concat "org-roam--extract-titles-" (symbol-name source))))
(when res (org-roam--extract-titles source t)))
(if (not nested) (when res
(setq coll (nconc coll res)) (if (not nested)
(setq coll res) (setq coll (nconc coll res))
(cl-return)))) (setq coll res)
coll)) (cl-return))))
coll)))
(defun org-roam--extract-tags-all-directories (file) (defun org-roam--extract-tags-all-directories (file)
"Extract tags from using the directory path FILE. "Extract tags from using the directory path FILE.
@ -759,20 +764,30 @@ backlinks."
"website") "website")
(t type))) (t type)))
(defun org-roam--extract-refs ()
"Extract all refs (ROAM_KEY statements) from the current buffer.
Each ref is returned as a cons of its type and its key."
(let (refs)
(pcase-dolist
(`(,_ . ,roam-key)
(org-roam--extract-global-props '("ROAM_KEY")))
(let (type path)
(pcase roam-key
('nil nil)
((pred string-empty-p)
(user-error "Org property #+roam_key cannot be empty"))
(ref
(when (string-match org-link-plain-re ref)
(setq type (org-roam--collate-types (match-string 1 ref))
path (match-string 2 ref)))))
(when (and type path)
(push (cons type path) refs))))
refs))
(defun org-roam--extract-ref () (defun org-roam--extract-ref ()
"Extract the ref from current buffer and return the type and the key of the ref." "Extract the ref from current buffer and return the type and the key of the ref."
(let (type path) (car (org-roam--extract-refs)))
(pcase (cdr (assoc "ROAM_KEY"
(org-roam--extract-global-props '("ROAM_KEY"))))
('nil nil)
((pred string-empty-p)
(user-error "Org property #+roam_key cannot be empty"))
(ref
(when (string-match org-link-plain-re ref)
(setq type (org-roam--collate-types (match-string 1 ref))
path (match-string 2 ref)))))
(when (and type path)
(cons type path))))
;;;; Title/Path/Slug conversion ;;;; Title/Path/Slug conversion
(defun org-roam--path-to-slug (path) (defun org-roam--path-to-slug (path)
@ -802,19 +817,14 @@ backlinks."
(slug (-reduce-from #'cl-replace (strip-nonspacing-marks title) pairs))) (slug (-reduce-from #'cl-replace (strip-nonspacing-marks title) pairs)))
(downcase slug)))) (downcase slug))))
(defun org-roam--format-link-title (title &optional type) (defun org-roam-format-link (target &optional description type link-type)
"Return the link title, given the file TITLE.
If `org-roam-link-title-format title' is defined, use it with TYPE."
(if (functionp org-roam-link-title-format)
(funcall org-roam-link-title-format title type)
(format org-roam-link-title-format title)))
(defun org-roam-format-link (target &optional description type)
"Formats an org link for a given file TARGET, link DESCRIPTION and link TYPE. "Formats an org link for a given file TARGET, link DESCRIPTION and link TYPE.
TYPE defaults to \"file\". TYPE defaults to \"file\". LINK-TYPE is the type of file link to
Here, we also check if there is an ID for the file." be generated. Here, we also check if there is an ID for the
file."
(setq type (or type "file")) (setq type (or type "file"))
(when-let ((id (and (string-equal type "file") (when-let ((id (and org-roam-prefer-id-links
(string-equal type "file")
(caar (org-roam-db-query [:select [id] :from ids (caar (org-roam-db-query [:select [id] :from ids
:where (= file $s1) :where (= file $s1)
:and (= level 0) :and (= level 0)
@ -822,7 +832,11 @@ Here, we also check if there is an ID for the file."
target))))) target)))))
(setq type "id" target id)) (setq type "id" target id))
(when (string-equal type "file") (when (string-equal type "file")
(setq target (org-roam-link-get-path target))) (setq target (org-roam-link-get-path target link-type)))
(setq description
(if (functionp org-roam-link-title-format)
(funcall org-roam-link-title-format description type)
(format org-roam-link-title-format description)))
(org-link-make-string (concat type ":" target) description)) (org-link-make-string (concat type ":" target) description))
(defun org-roam--prepend-tag-string (str tags) (defun org-roam--prepend-tag-string (str tags)
@ -870,6 +884,26 @@ whose title is 'Index'."
(concat (expand-file-name org-roam-directory) path) (concat (expand-file-name org-roam-directory) path)
index))) index)))
;;;; dealing with file-wide properties
(defun org-roam--set-global-prop (name value)
"Set a file property called NAME to VALUE.
If the property is already set, it's value is replaced."
(save-excursion
(widen)
(goto-char (point-min))
(if (re-search-forward (concat "^#\\+" name ": \\(.*\\)") (point-max) t)
(replace-match (concat "#+" name ": " value) 'fixedcase)
(while (and (not (eobp))
(looking-at "^[#:]"))
(if (save-excursion (end-of-line) (eobp))
(progn
(end-of-line)
(insert "\n"))
(forward-line)
(beginning-of-line)))
(insert "#+" name ": " value "\n"))))
;;;; org-roam-find-ref ;;;; org-roam-find-ref
(defun org-roam--get-ref-path-completions (&optional arg 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.
@ -973,24 +1007,18 @@ Return nil if the file does not exist."
(and (boundp org-roam-backlinks-mode) (and (boundp org-roam-backlinks-mode)
org-roam-backlinks-mode)) org-roam-backlinks-mode))
(defun org-roam--retrieve-link-destination (&optional pom)
"Retrieve the destination of the link at POM.
The point-or-marker POM can either be a position in the current
buffer or a marker."
(let ((pom (or pom (point))))
(org-with-point-at pom
(let* ((context (org-element-context))
(type (org-element-property :type context))
(dest (org-element-property :path context)))
(pcase type
("id" (car (org-roam-id-find dest)))
(_ dest))))))
(defun org-roam--backlink-to-current-p () (defun org-roam--backlink-to-current-p ()
"Return t if backlink is to the current Org-roam file." "Return t if the link at point is to the current Org-roam file."
(let ((current (buffer-file-name org-roam-buffer--current)) (save-match-data
(backlink-dest (org-roam--retrieve-link-destination))) (let ((current-file (buffer-file-name org-roam-buffer--current))
(string= current backlink-dest))) (backlink-dest (save-excursion
(let* ((context (org-element-context))
(type (org-element-property :type context))
(dest (org-element-property :path context)))
(pcase type
("id" (org-roam-id-get-file dest))
(_ dest))))))
(string= current-file backlink-dest))))
(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.
@ -1025,20 +1053,27 @@ citation key, for Org-ref cite links."
(unless (listp targets) (unless (listp targets)
(setq targets (list targets))) (setq targets (list targets)))
(let ((conditions (--> targets (let ((conditions (--> targets
(mapcar (lambda (i) (list '= 'to i)) it) (mapcar (lambda (i) (list '= 'dest i)) it)
(org-roam--list-interleave it :or)))) (org-roam--list-interleave it :or))))
(org-roam-db-query `[:select [from to properties] :from links (org-roam-db-query `[:select [source dest properties] :from links
:where ,@conditions :where ,@conditions
:order-by (asc from)]))) :order-by (asc source)])))
(defun org-roam-id-get-file (id) (defun org-roam-id-get-file (id &optional strict)
"Return the file if ID exists in the Org-roam database. "Return the file if ID exists.
When STRICT is non-nil, only consider Org-roam's database.
Return nil otherwise." Return nil otherwise."
(caar (org-roam-db-query [:select [file] (or (caar (org-roam-db-query [:select [file]
:from ids :from ids
:where (= id $s1) :where (= id $s1)
:limit 1] :limit 1]
id))) id))
(and (not strict)
(progn
(unless org-id-locations (org-id-locations-load))
(or (and org-id-locations
(hash-table-p org-id-locations)
(gethash id org-id-locations)))))))
(defun org-roam-id-find (id &optional markerp strict keep-buffer-p) (defun org-roam-id-find (id &optional markerp strict keep-buffer-p)
"Return the location of the entry with the id ID. "Return the location of the entry with the id ID.
@ -1046,8 +1081,7 @@ When MARKERP is non-nil, return a marker pointing to the headline.
Otherwise, return a cons formatted as \(file . pos). Otherwise, return a cons formatted as \(file . pos).
When STRICT is non-nil, only consider Org-roams database. When STRICT is non-nil, only consider Org-roams database.
When KEEP-BUFFER-P is non-nil, keep the buffers navigated by Org-roam open." When KEEP-BUFFER-P is non-nil, keep the buffers navigated by Org-roam open."
(let ((file (or (org-roam-id-get-file id) (let ((file (org-roam-id-get-file id strict)))
(unless strict (org-id-find-id-file id)))))
(when file (when file
(let ((existing-buf (find-buffer-visiting file)) (let ((existing-buf (find-buffer-visiting file))
(res (org-id-find-id-in-file id file markerp))) (res (org-id-find-id-in-file id file markerp)))
@ -1068,6 +1102,7 @@ When STRICT is non-nil, only consider Org-roams database."
(when-let ((marker (if (markerp id-or-marker) (when-let ((marker (if (markerp id-or-marker)
id-or-marker id-or-marker
(org-roam-id-find id-or-marker t strict t)))) (org-roam-id-find id-or-marker t strict t))))
(org-mark-ring-push)
(org-goto-marker-or-bmk marker) (org-goto-marker-or-bmk marker)
(set-marker marker nil))) (set-marker marker nil)))
@ -1170,7 +1205,7 @@ This is active when `org-roam-completion-everywhere' is non-nil."
;;;; Function Faces ;;;; Function Faces
;; These faces are used by `org-link-set-parameters', which take one argument, ;; These faces are used by `org-link-set-parameters', which take one argument,
;; which is the path. ;; which is the path.
(defcustom org-roam-link-use-custom-faces 'everywhere (defcustom org-roam-link-use-custom-faces t
"Define where to apply custom faces to Org-roam links. "Define where to apply custom faces to Org-roam links.
Valide values are: Valide values are:
@ -1182,9 +1217,9 @@ everywhere Apply custom faces everywhere.
Otherwise, do not apply custom faces to Org-roam links." Otherwise, do not apply custom faces to Org-roam links."
:type '(choice :type '(choice
(const :tag "Use custom faces inside Org-roam notes" t) (const :tag "Use custom faces inside Org-roam notes" t)
(const :tag "Apply custom faces everywhere" everywhere) (const :tag "Apply custom faces everywhere" everywhere)
(const :tag "Do not apply custom faces" nil)) (const :tag "Do not apply custom faces" nil))
:group 'org-roam) :group 'org-roam)
(defun org-roam--file-link-face (path) (defun org-roam--file-link-face (path)
@ -1222,35 +1257,18 @@ file."
(org-roam--org-roam-file-p))) (org-roam--org-roam-file-p)))
(custom (or (and in-note org-roam-link-use-custom-faces) (custom (or (and in-note org-roam-link-use-custom-faces)
(eq org-roam-link-use-custom-faces 'everywhere)))) (eq org-roam-link-use-custom-faces 'everywhere))))
(cond ((and custom (cond ((and (org-roam--in-buffer-p)
(not (org-roam-id-get-file id)))
'org-roam-link-invalid)
((and (org-roam--in-buffer-p)
(org-roam--backlink-to-current-p)) (org-roam--backlink-to-current-p))
'org-roam-link-current) 'org-roam-link-current)
((and custom ((and custom
(org-roam-id-get-file id)) (org-roam-id-get-file id t))
'org-roam-link) 'org-roam-link)
((and custom
(not (org-roam-id-get-file id)))
'org-roam-link-invalid)
(t (t
'org-link))))) 'org-link)))))
(defun org-roam--queue-file-for-update (&optional file-path)
"Queue FILE-PATH for `org-roam' database update.
This is a lightweight function that is called during `after-save-hook'
and only schedules the current Org file to be `org-roam' updated
during the next idle slot."
(let ((fp (or file-path buffer-file-name)))
(when (org-roam--org-roam-file-p file-path)
(add-to-list 'org-roam--file-update-queue fp))))
(defun org-roam--process-update-queue ()
"Process files queued in `org-roam--file-update-queue'."
(when org-roam--file-update-queue
(mapc #'org-roam-db--update-file org-roam--file-update-queue)
(org-roam-message "database updated during idle: %s."
(mapconcat #'file-name-nondirectory org-roam--file-update-queue ", ") )
(setq org-roam--file-update-queue nil)))
;;;; Hooks and Advices ;;;; Hooks and Advices
(defcustom org-roam-file-setup-hook nil (defcustom org-roam-file-setup-hook nil
"Hook that is run on setting up an Org-roam file." "Hook that is run on setting up an Org-roam file."
@ -1265,7 +1283,7 @@ during the next idle slot."
(org-roam--setup-title-auto-update) (org-roam--setup-title-auto-update)
(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 'before-save-hook #'org-roam-link--replace-link-on-save nil t) (add-hook 'before-save-hook #'org-roam-link--replace-link-on-save nil t)
(add-hook 'after-save-hook #'org-roam--queue-file-for-update nil t) (add-hook 'after-save-hook #'org-roam-db-update nil t)
(dolist (fn org-roam-completion-functions) (dolist (fn org-roam-completion-functions)
(add-hook 'completion-at-point-functions fn nil t)) (add-hook 'completion-at-point-functions fn nil t))
(org-roam-buffer--update-maybe :redisplay t))) (org-roam-buffer--update-maybe :redisplay t)))
@ -1276,44 +1294,51 @@ during the next idle slot."
(org-roam--org-roam-file-p file)) (org-roam--org-roam-file-p file))
(org-roam-db--clear-file (expand-file-name file)))) (org-roam-db--clear-file (expand-file-name file))))
(defun org-roam--get-link-replacement (old-path new-path &optional old-desc new-desc)
"Create replacement text for link at point if OLD-PATH is a match.
Will update link to NEW-PATH. If OLD-DESC is set, and is not the
same as the link description, it is assumed that the user has
modified the description, and the description will not be
updated. Else, update with NEW-DESC."
(let (type path link-type label new-label)
(when-let ((link (org-element-lineage (org-element-context) '(link) t)))
(setq type (org-element-property :type link)
path (org-element-property :path link))
(when (and (string-equal (expand-file-name path) old-path)
(org-in-regexp org-link-bracket-re 1))
(setq link-type (when (file-name-absolute-p path) 'absolute)
label (if (match-end 2)
(match-string-no-properties 2)
(org-link-unescape (match-string-no-properties 1))))
(setq new-label (if (string-equal label old-desc) new-desc label))
(org-roam-format-link new-path new-label type link-type)))))
(defun org-roam--replace-link (old-path new-path &optional old-desc new-desc) (defun org-roam--replace-link (old-path new-path &optional old-desc new-desc)
"Replace Org-roam file links with path OLD-PATH to path NEW-PATH. "Replace Org-roam file links with path OLD-PATH to path NEW-PATH.
If OLD-DESC is passed, and is not the same as the link If OLD-DESC is passed, and is not the same as the link
description, it is assumed that the user has modified the description, it is assumed that the user has modified the
description, and the description will not be updated. Else, description, and the description will not be updated. Else,
update with NEW-DESC." update with NEW-DESC."
(save-excursion (org-with-point-at 1
(goto-char (point-min)) (while (re-search-forward org-link-bracket-re nil t)
(while (re-search-forward org-link-any-re nil t) (when-let ((link (save-match-data (org-roam--get-link-replacement old-path new-path old-desc new-desc))))
(when-let ((link (org-element-lineage (org-element-context) '(link) t))) (replace-match link)))))
(let ((type (org-element-property :type link))
(path (org-element-property :path link)))
(when (and (string-equal (expand-file-name path) old-path)
(org-in-regexp org-link-bracket-re 1))
(let* ((label (if (match-end 2)
(match-string-no-properties 2)
(org-link-unescape (match-string-no-properties 1))))
(new-label (if (string-equal label old-desc)
new-desc
label)))
(replace-match (org-roam-format-link new-path new-label type)))))))))
(defun org-roam--fix-relative-links (old-path) (defun org-roam--fix-relative-links (old-path)
"Fix file-relative links in current buffer. "Fix file-relative links in current buffer.
File relative links are assumed to originate from OLD-PATH. The File relative links are assumed to originate from OLD-PATH. The
replaced links are made relative to the current buffer." replaced links are made relative to the current buffer."
(save-excursion (org-with-point-at 1
(goto-char (point-min)) (let (link new-link type path)
(while (re-search-forward org-link-any-re nil t) (while (re-search-forward org-link-bracket-re nil t)
(when-let ((link (org-element-lineage (org-element-context) '(link) t))) (when (setq link (save-match-data (org-element-lineage (org-element-context) '(link) t)))
(let ((type (org-element-property :type link)) (setq type (org-element-property :type link))
(path (org-element-property :path link))) (setq path (org-element-property :path link))
(when (and (f-relative-p path) (when (and (string= type "file")
(org-in-regexp org-link-bracket-re 1)) (f-relative-p path))
(let* ((file-path (expand-file-name path (file-name-directory old-path))) (setq new-link
(new-path (org-roam-link-get-path file-path))) (concat type ":" (org-roam-link-get-path (expand-file-name path (file-name-directory old-path)))))
(replace-match (concat type ":" new-path) (replace-match new-link nil t nil 1)))))))
nil t nil 1))))))))
(defcustom org-roam-rename-file-on-title-change t (defcustom org-roam-rename-file-on-title-change t
"If non-nil, alter the filename on title change. "If non-nil, alter the filename on title change.
@ -1356,9 +1381,9 @@ if applicable.
To be added to `org-roam-title-change-hook'." To be added to `org-roam-title-change-hook'."
(let* ((current-path (buffer-file-name)) (let* ((current-path (buffer-file-name))
(files-affected (org-roam-db-query [:select :distinct [from] (files-affected (org-roam-db-query [:select :distinct [source]
:from links :from links
:where (= to $s1)] :where (= dest $s1)]
current-path))) current-path)))
(dolist (file files-affected) (dolist (file files-affected)
(with-current-buffer (or (find-buffer-visiting (car file)) (with-current-buffer (or (find-buffer-visiting (car file))
@ -1381,10 +1406,11 @@ To be added to `org-roam-title-change-hook'."
(when (string-match-p old-slug file-name) (when (string-match-p old-slug file-name)
(let* ((new-slug (funcall org-roam-title-to-slug-function new-title)) (let* ((new-slug (funcall org-roam-title-to-slug-function new-title))
(new-file-name (replace-regexp-in-string old-slug new-slug file-name))) (new-file-name (replace-regexp-in-string old-slug new-slug file-name)))
(rename-file file-name new-file-name) (unless (string-match-p file-name new-file-name)
(set-visited-file-name new-file-name t t) (rename-file file-name new-file-name)
(add-to-list 'org-roam--file-update-queue new-file-name) (set-visited-file-name new-file-name t t)
(org-roam-message "File moved to %S" (abbreviate-file-name new-file-name))))))) (org-roam-db-update)
(org-roam-message "File moved to %S" (abbreviate-file-name new-file-name))))))))
(defun org-roam--rename-file-advice (old-file new-file-or-dir &rest _args) (defun org-roam--rename-file-advice (old-file new-file-or-dir &rest _args)
"Rename backlinks of OLD-FILE to refer to NEW-FILE-OR-DIR. "Rename backlinks of OLD-FILE to refer to NEW-FILE-OR-DIR.
@ -1398,42 +1424,40 @@ When NEW-FILE-OR-DIR is a directory, we use it to compute the new file path."
(not (backup-file-name-p new-file)) (not (backup-file-name-p new-file))
(org-roam--org-roam-file-p old-file)) (org-roam--org-roam-file-p old-file))
(org-roam-db--ensure-built) (org-roam-db--ensure-built)
(let* ((old-path (expand-file-name old-file)) (let* ((new-buffer (or (find-buffer-visiting new-file)
(new-path (expand-file-name new-file)) (find-file-noselect new-file)))
(new-buffer (or (find-buffer-visiting new-path) (files-affected (org-roam-db-query [:select :distinct [source]
(find-file-noselect new-path)))
(files-affected (org-roam-db-query [:select :distinct [from]
:from links :from links
:where (= to $s1)] :where (= dest $s1)]
old-path))) old-file)))
;; Remove database entries for old-file.org ;; Remove database entries for old-file.org
(org-roam-db--clear-file old-file) (org-roam-db--clear-file old-file)
;; Replace links from old-file.org -> new-file.org in all Org-roam files with these links ;; Replace links from old-file.org -> new-file.org in all Org-roam files with these links
(mapc (lambda (file) (mapc (lambda (file)
(setq file (if (string-equal (expand-file-name (car file)) old-path) (setq file (if (string-equal (car file) old-file)
new-path new-file
(car file))) (car file)))
(with-current-buffer (or (find-buffer-visiting file) (with-current-buffer (or (find-buffer-visiting file)
(find-file-noselect file)) (find-file-noselect file))
(org-roam--replace-link old-path new-path) (org-roam--replace-link old-file new-file)
(save-buffer) (save-buffer)
(org-roam-db--update-file))) (org-roam-db--update-file)))
files-affected) files-affected)
;; If the new path is in a different directory, relative links ;; If the new path is in a different directory, relative links
;; will break. Fix all file-relative links: ;; will break. Fix all file-relative links:
(unless (string= (file-name-directory old-path) (unless (string= (file-name-directory old-file)
(file-name-directory new-path)) (file-name-directory new-file))
(with-current-buffer new-buffer (with-current-buffer new-buffer
(org-roam--fix-relative-links old-path) (org-roam--fix-relative-links old-file)
(save-buffer))) (save-buffer)))
(when (org-roam--org-roam-file-p new-file) (when (org-roam--org-roam-file-p new-file)
(org-roam-db--update-file new-path)))))) (org-roam-db--update-file new-file))))))
(defun org-roam--id-new-advice (&rest _args) (defun org-roam--id-new-advice (&rest _args)
"Update the database if a new Org ID is created." "Update the database if a new Org ID is created."
(when (and org-roam-enable-headline-linking (when (and org-roam-enable-headline-linking
(org-roam--org-roam-file-p)) (org-roam--org-roam-file-p))
(add-to-list 'org-roam--file-update-queue (buffer-file-name)))) (org-roam-db-update)))
;;;###autoload ;;;###autoload
(define-minor-mode org-roam-mode (define-minor-mode org-roam-mode
@ -1468,8 +1492,9 @@ M-x info for more information at Org-roam > Installation > Post-Installation Tas
(add-hook 'find-file-hook #'org-roam--find-file-hook-function) (add-hook 'find-file-hook #'org-roam--find-file-hook-function)
(add-hook 'kill-emacs-hook #'org-roam-db--close-all) (add-hook 'kill-emacs-hook #'org-roam-db--close-all)
(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)
(unless org-roam--file-update-timer (if (and org-roam-db-file-update-timer
(setq org-roam--file-update-timer (run-with-idle-timer org-roam-update-db-idle-seconds t #'org-roam--process-update-queue))) (eq org-roam-db-update-method 'idle-timer))
(setq org-roam-db-file-update-timer (run-with-idle-timer org-roam-db-update-idle-seconds t #'org-roam-db-update-cache-on-timer)))
(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)
(advice-add 'org-id-new :after #'org-roam--id-new-advice) (advice-add 'org-id-new :after #'org-roam--id-new-advice)
@ -1482,8 +1507,8 @@ M-x info for more information at Org-roam > Installation > Post-Installation Tas
(remove-hook 'find-file-hook #'org-roam--find-file-hook-function) (remove-hook 'find-file-hook #'org-roam--find-file-hook-function)
(remove-hook 'kill-emacs-hook #'org-roam-db--close-all) (remove-hook 'kill-emacs-hook #'org-roam-db--close-all)
(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)
(when org-roam--file-update-timer (when org-roam-db-file-update-timer
(cancel-timer org-roam--file-update-timer)) (cancel-timer org-roam-db-file-update-timer))
(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)
(advice-remove 'org-id-new #'org-roam--id-new-advice) (advice-remove 'org-id-new #'org-roam--id-new-advice)
@ -1496,7 +1521,7 @@ M-x info for more information at Org-roam > Installation > Post-Installation Tas
(with-current-buffer buf (with-current-buffer buf
(remove-hook 'post-command-hook #'org-roam-buffer--update-maybe t) (remove-hook 'post-command-hook #'org-roam-buffer--update-maybe t)
(remove-hook 'before-save-hook #'org-roam-link--replace-link-on-save t) (remove-hook 'before-save-hook #'org-roam-link--replace-link-on-save t)
(remove-hook 'after-save-hook #'org-roam--queue-file-for-update t)))))) (remove-hook 'after-save-hook #'org-roam-db-update t))))))
;;; Interactive Commands ;;; Interactive Commands
;;;###autoload ;;;###autoload
@ -1584,8 +1609,8 @@ included as a candidate."
(defun org-roam-insert (&optional lowercase completions filter-fn description link-type) (defun org-roam-insert (&optional lowercase completions filter-fn description link-type)
"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. Return selected file if it exists.
If LOWERCASE is non-nil, downcase the link description.
LINK-TYPE is the type of link to be created. It defaults to \"file\". LINK-TYPE is the type of link to be created. It defaults to \"file\".
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`.
FILTER-FN is the name of a function to apply on the candidates FILTER-FN is the name of a function to apply on the candidates
@ -1616,17 +1641,16 @@ If DESCRIPTION is provided, use this as the link label. See
title-with-tags)) title-with-tags))
(target-file-path (plist-get res :path)) (target-file-path (plist-get res :path))
(description (or description region-text title)) (description (or description region-text title))
(link-description (org-roam--format-link-title (if lowercase (description (if lowercase
(downcase description) (downcase description)
description) description)))
link-type)))
(cond ((and target-file-path (cond ((and target-file-path
(file-exists-p target-file-path)) (file-exists-p target-file-path))
(when region-text (when region-text
(delete-region beg end) (delete-region beg end)
(set-marker beg nil) (set-marker beg nil)
(set-marker end nil)) (set-marker end nil))
(insert (org-roam-format-link target-file-path link-description link-type))) (insert (org-roam-format-link target-file-path description link-type)))
(t (t
(let ((org-roam-capture--info `((title . ,title-with-tags) (let ((org-roam-capture--info `((title . ,title-with-tags)
(slug . ,(funcall org-roam-title-to-slug-function title-with-tags)))) (slug . ,(funcall org-roam-title-to-slug-function title-with-tags))))
@ -1634,7 +1658,7 @@ If DESCRIPTION is provided, use this as the link label. See
(setq org-roam-capture-additional-template-props (list :region (org-roam-shield-region beg end) (setq org-roam-capture-additional-template-props (list :region (org-roam-shield-region beg end)
:insert-at (point-marker) :insert-at (point-marker)
:link-type link-type :link-type link-type
:link-description link-description :link-description description
:finalize 'insert-link)) :finalize 'insert-link))
(org-roam-capture--capture)))) (org-roam-capture--capture))))
res)) res))
@ -1680,6 +1704,68 @@ command will offer you to create one."
(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")))))
;;;###autoload
(defun org-roam-alias-add ()
"Add an alias to Org-roam file.
Return added alias."
(interactive)
(unless org-roam-mode (org-roam-mode))
(let ((alias (read-string "Alias: " )))
(when (string-empty-p alias)
(user-error "Alias can't be empty"))
(org-roam--set-global-prop
"ROAM_ALIAS"
(combine-and-quote-strings
(seq-uniq (cons alias
(org-roam--extract-titles-alias)))))
(org-roam-db--update-file (buffer-file-name (buffer-base-buffer)))
alias))
;;;###autoload
(defun org-roam-alias-delete ()
"Delete an alias from Org-roam file."
(interactive)
(unless org-roam-mode (org-roam-mode))
(if-let ((aliases (org-roam--extract-titles-alias)))
(let ((alias (completing-read "Alias: " aliases nil 'require-match)))
(org-roam--set-global-prop
"ROAM_ALIAS"
(combine-and-quote-strings (delete alias aliases)))
(org-roam-db--update-file (buffer-file-name (buffer-base-buffer))))
(user-error "No aliases to delete")))
(defun org-roam-tag-add ()
"Add a tag to Org-roam file.
Return added tag."
(interactive)
(unless org-roam-mode (org-roam-mode))
(let* ((all-tags (org-roam-db--get-tags))
(tag (completing-read "Tag: " all-tags))
(file (buffer-file-name (buffer-base-buffer)))
(existing-tags (org-roam--extract-tags-prop file)))
(when (string-empty-p tag)
(user-error "Tag can't be empty"))
(org-roam--set-global-prop
"ROAM_TAGS"
(combine-and-quote-strings (seq-uniq (cons tag existing-tags))))
(org-roam-db--insert-tags 'update)
tag))
(defun org-roam-tag-delete ()
"Delete a tag from Org-roam file."
(interactive)
(unless org-roam-mode (org-roam-mode))
(if-let* ((file (buffer-file-name (buffer-base-buffer)))
(tags (org-roam--extract-tags-prop file)))
(let ((tag (completing-read "Tag: " tags nil 'require-match)))
(org-roam--set-global-prop
"ROAM_TAGS"
(combine-and-quote-strings (delete tag tags)))
(org-roam-db--insert-tags 'update))
(user-error "No tag to delete")))
;;;###autoload ;;;###autoload
(defun org-roam-switch-to-buffer () (defun org-roam-switch-to-buffer ()
"Switch to an existing Org-roam buffer." "Switch to an existing Org-roam buffer."
@ -1719,12 +1805,17 @@ means that the results can be noisy, and may not truly indicate
an unlinked reference. an unlinked reference.
Users are encouraged to think hard about whether items should be Users are encouraged to think hard about whether items should be
linked, lest the network graph get too crowded." linked, lest the network graph get too crowded.
Requires a version of Ripgrep with PCRE2 support installed, with
the executable 'rg' in variable `exec-path'."
(interactive) (interactive)
(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"))
(error "Cannot find the ripgrep executable \"rg\". Check that it is installed and available on `exec-path'") (error "Cannot find the ripgrep executable \"rg\". Check that it is installed and available on `exec-path'")
(when (string-match "PCRE2 is not available" (shell-command-to-string "rg --pcre2-version"))
(error "\"rg\" must be compiled with PCRE2 support"))
(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))
@ -1771,7 +1862,9 @@ linked, lest the network graph get too crowded."
(concat "sed -n " (concat "sed -n "
row row
"p " "p "
file)))) "\""
file
"\""))))
(insert "\n"))))))) (insert "\n")))))))
(read-only-mode +1) (read-only-mode +1)
(dolist (title titles) (dolist (title titles)

View File

@ -0,0 +1 @@
#+roam_key: cite:mitsuha2007

View File

@ -0,0 +1,2 @@
#+roam_key: https://www.orgroam.com/
#+roam_key: cite:orgroam2020

View File

@ -45,7 +45,7 @@
(pcase (benchmark-run 1 (org-roam-db-build-cache t)) (pcase (benchmark-run 1 (org-roam-db-build-cache t))
(`(,time ,gcs ,time-in-gc) (`(,time ,gcs ,time-in-gc)
(message "Elapsed time: %fs (%fs in %d GCs)" time time-in-gc gcs) (message "Elapsed time: %fs (%fs in %d GCs)" time time-in-gc gcs)
(expect time :to-be-less-than 90)))) (expect time :to-be-less-than 110))))
(it "builds quickly without change" (it "builds quickly without change"
(pcase (benchmark-run 1 (org-roam-db-build-cache)) (pcase (benchmark-run 1 (org-roam-db-build-cache))
(`(,time ,gcs ,time-in-gc) (`(,time ,gcs ,time-in-gc)

View File

@ -71,6 +71,40 @@
(expect (org-roam--str-to-list "\"hello") (expect (org-roam--str-to-list "\"hello")
:to-throw))) :to-throw)))
(describe "Ref 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
;; Unlike tag extraction, it doesn't make sense to
;; pass a filename.
(funcall fn)))))
;; Enable "cite:" link parsing
(org-link-set-parameters "cite")
(it "extracts web keys"
(expect (test #'org-roam--extract-ref
"web_ref.org")
:to-equal
'("website" . "//google.com/")))
(it "extracts cite keys"
(expect (test #'org-roam--extract-ref
"cite_ref.org")
:to-equal
'("cite" . "mitsuha2007")))
(it "extracts all keys"
(expect (test #'org-roam--extract-refs
"multiple-refs.org")
:to-have-same-items-as
'(("cite" . "orgroam2020")
("website" . "//www.orgroam.com/"))))))
(describe "Title extraction" (describe "Title extraction"
:var (org-roam-title-sources) :var (org-roam-title-sources)
(before-all (before-all
@ -300,21 +334,21 @@
;; Links ;; Links
(expect (caar (org-roam-db-query [:select (funcall count) :from links (expect (caar (org-roam-db-query [:select (funcall count) :from links
:where (= from $s1)] :where (= source $s1)]
(test-org-roam--abs-path "foo.org"))) :to-be 1) (test-org-roam--abs-path "foo.org"))) :to-be 1)
(expect (caar (org-roam-db-query [:select (funcall count) :from links (expect (caar (org-roam-db-query [:select (funcall count) :from links
:where (= from $s1)] :where (= source $s1)]
(test-org-roam--abs-path "nested/bar.org"))) :to-be 2) (test-org-roam--abs-path "nested/bar.org"))) :to-be 2)
;; Links -- File-to ;; Links -- File-to
(expect (caar (org-roam-db-query [:select (funcall count) :from links (expect (caar (org-roam-db-query [:select (funcall count) :from links
:where (= to $s1)] :where (= dest $s1)]
(test-org-roam--abs-path "nested/foo.org"))) :to-be 1) (test-org-roam--abs-path "nested/foo.org"))) :to-be 1)
(expect (caar (org-roam-db-query [:select (funcall count) :from links (expect (caar (org-roam-db-query [:select (funcall count) :from links
:where (= to $s1)] :where (= dest $s1)]
(test-org-roam--abs-path "nested/bar.org"))) :to-be 1) (test-org-roam--abs-path "nested/bar.org"))) :to-be 1)
(expect (caar (org-roam-db-query [:select (funcall count) :from links (expect (caar (org-roam-db-query [:select (funcall count) :from links
:where (= to $s1)] :where (= dest $s1)]
(test-org-roam--abs-path "unlinked.org"))) :to-be 0) (test-org-roam--abs-path "unlinked.org"))) :to-be 0)
;; TODO Test titles ;; TODO Test titles
(expect (org-roam-db-query [:select * :from titles]) (expect (org-roam-db-query [:select * :from titles])