Compare commits

..

1 Commits

Author SHA1 Message Date
Jethro Kuan
b5acacac20 (feat!)capture: change id creation to headline on entry-type capture-templates 2022-01-20 15:46:42 -08:00
40 changed files with 1430 additions and 2595 deletions

View File

@@ -4,9 +4,6 @@
(elisp-lint-ignored-validators . ("byte-compile" "package-lint"))
(elisp-lint-indent-specs . ((describe . 1)
(it . 1)
(thread-first . 0)
(cl-flet . 1)
(cl-flet* . 1)
(org-element-map . defun)
(org-roam-dolist-with-progress . 2)
(org-roam-with-temp-buffer . 1)

2
.github/FUNDING.yml vendored
View File

@@ -1,6 +1,6 @@
# These are supported funding model platforms
github: [jethrokuan]
github: [jethrokuan, zaeph]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username

View File

@@ -39,3 +39,5 @@ Example:
### Environment
<!-- Please M-x org-roam-diagnostics and paste results here -->
- Org-roam commit: https://github.com/jethrokuan/org-roam/commit/commithashhere

View File

@@ -4,7 +4,7 @@ name: "Docs"
on:
push:
branches:
- main
- master
jobs:
build:

View File

@@ -28,7 +28,7 @@ on:
pull_request:
push:
branches:
- main
- master
jobs:
build:
@@ -36,11 +36,7 @@ jobs:
strategy:
matrix:
emacs_version:
# REVIEW: we do not yet have the bootstrapping in place to test
# versions of emacs without built-in sqlite (pre 29.1)
# - 27.2
# - 28.2
- 29.4
- 27.1
- snapshot
steps:
- uses: purcell/setup-emacs@master
@@ -50,7 +46,7 @@ jobs:
- uses: actions/checkout@v2
- name: Install Eldev
run: curl -fsSL https://raw.github.com/org-roam/org-roam/master/github-eldev | sh
run: curl -fsSL https://raw.github.com/doublep/eldev/master/webinstall/github-eldev | sh
- name: Install dependencies
run: make prepare

View File

@@ -1,93 +0,0 @@
name: 'Manage stale/dormant issues and PRs'
on:
schedule:
- cron: '0 1 * * *' # Daily at 1 AM UTC
workflow_dispatch: # Allow this to be run manually
inputs:
dry_run:
description: 'Dry run: Show what would happen without making changes'
required: false
default: 'false'
type: boolean
jobs:
stale:
runs-on: ubuntu-latest
steps:
# Handle stale issues/PRs
- name: Mark/close stale issues and PRs
uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# Dry run setting
debug-only: ${{ github.event.inputs.dry_run == 'true' }}
# Messages
stale-issue-message: |
📅 **Stale Issue Notice**
This issue has been automatically marked as stale because it has not had recent activity for **6 months**.
**⏰ This issue will be closed in 2 weeks** if no further activity occurs.
**To keep this issue open:**
- Comment on this issue
- Reference it in a commit or PR
- Add new information or updates
Thank you for your contributions to org-roam! 🙏
close-issue-message: |
🔒 **Issue Automatically Closed**
This issue was automatically closed due to **6 months of inactivity** followed by 2 weeks notice.
**To reopen:**
- If still relevant, comment below and we'll reopen
- Or create a new issue with updated information
If this issue is not reopened in 2 weeks, it will be locked.
This helps keep our issue tracker focused and manageable.
stale-pr-message: |
📅 **Stale Pull Request Notice**
This pull request has been automatically marked as stale because it has not had recent activity for **6 months**.
**⏰ This PR will be closed in 2 weeks** if no further activity occurs.
**To keep this PR open:**
- Push new commits
- Comment with updates
- Rebase on latest main branch
Thank you for your contributions to org-roam! 🙏
close-pr-message: |
🔒 **Pull Request Automatically Closed**
This pull request was automatically closed due to **6 months of inactivity** followed by 2 weeks notice.
# Timing (6 months + 2 weeks)
days-before-stale: 182 # ~6 months
days-before-close: 14 # 2 weeks notice
# Performance
operations-per-run: 1000
# Show dry run summary
- name: Dry run summary
if: github.event.inputs.dry_run == 'true'
run: |
echo "🧪 DRY RUN COMPLETED"
echo "This was a dry run - no actual changes were made."
echo "Check the action logs above to see what would have happened."
echo ""
echo "To run for real:"
echo "1. Go to Actions tab"
echo "2. Click 'Run workflow'"
echo "3. Leave 'Dry run mode' unchecked"
echo "4. Click 'Run workflow'"

View File

@@ -1,112 +1,5 @@
# Changelog
## 2.3.1 (2025-06-26)
* (fix): Use correct type specifications to suppress warnings by @okomestudio in https://github.com/org-roam/org-roam/pull/2522
* perf: suppress extra org features in the temp buffer by @dustinfarris in https://github.com/org-roam/org-roam/pull/2524
## 2.3.0 (2025-05-24)
* (perf)node-read: filter before map to candidate by @russmatney in https://github.com/org-roam/org-roam/pull/2168
* (chore): remove extraneous changelog line by @jethrokuan in https://github.com/org-roam/org-roam/pull/2172
* (fix)org-roam-file-p: don't exclude org-roam-directory by @toregilhk in https://github.com/org-roam/org-roam/pull/2178
* (chore) fix version numbers by @jethrokuan in https://github.com/org-roam/org-roam/pull/2182
* (fix) org-roam-file-p handle opening a buffer with no path by @jethrokuan in https://github.com/org-roam/org-roam/pull/2185
* (fix)db: update atime on file access by @jethrokuan in https://github.com/org-roam/org-roam/pull/2174
* Fix org-roam-extract-subtree by @ralfdoering in https://github.com/org-roam/org-roam/pull/2191
* (node): fix org-roam-node-at-point check by @jethrokuan in https://github.com/org-roam/org-roam/pull/2195
* (chore): reapply #2178 by @jethrokuan in https://github.com/org-roam/org-roam/pull/2197
* Solved issue #2192 (Respect blank lines in capture templates) by @alopezrivera in https://github.com/org-roam/org-roam/pull/2203
* (chore)ci: /s/master/main by @jethrokuan in https://github.com/org-roam/org-roam/pull/2204
* support custom minibuffer matching function by @cuttlefisch in https://github.com/org-roam/org-roam/pull/2177
* (db)fix: org-roam-db-connector group by @jethrokuan in https://github.com/org-roam/org-roam/pull/2209
* ensure unique candidates in `org-roam-ref-find` by @bdarcus in https://github.com/org-roam/org-roam/pull/2208
* (db)fix: FOREIGN KEY error in narrowed buffer by @psii in https://github.com/org-roam/org-roam/pull/2215
* (feat): org-roam-property-* code duplication removed (#2217) by @clanghans in https://github.com/org-roam/org-roam/pull/2218
* (db)fix: file modification detection failing in some edge cases by @kisaragi-hiu in https://github.com/org-roam/org-roam/pull/2221
* (fix): autoload org-roam-list-files by @jethrokuan in https://github.com/org-roam/org-roam/pull/2226
* (fix): links not displayed properly in org-roam-buffer by @ntharim in https://github.com/org-roam/org-roam/pull/2236
* (fix): remove use of deprecated org-font-lock-ensure by @jethrokuan in https://github.com/org-roam/org-roam/pull/2238
* was missing the FULL parameter to `org-end-of-meta-data` - fixes #2242 by @jmay in https://github.com/org-roam/org-roam/pull/2246
* (Docs): Fixed typos in the docs by @hnvy in https://github.com/org-roam/org-roam/pull/2256
* Fix space chars in roam refs by @hwiorn in https://github.com/org-roam/org-roam/pull/2285
* org-roam-tag-add: use tags separator as crm-separator by @Hugo-Heagren in https://github.com/org-roam/org-roam/pull/2282
* Support multi-line org titles by @FelixBrendel in https://github.com/org-roam/org-roam/pull/2264
* (Docs): orgmode.org/elpa has been shut down by @aviad in https://github.com/org-roam/org-roam/pull/2258
* Minor typo in the commented text of the definition of `org-roam-refile` by @apc in https://github.com/org-roam/org-roam/pull/2263
* fix outline of link in properties by @bhuztez in https://github.com/org-roam/org-roam/pull/2230
* (fix): update `org-roam-unlinked-references-section` check by @Elilif in https://github.com/org-roam/org-roam/pull/2254
* Add support for filtering backlinks. by @swflint in https://github.com/org-roam/org-roam/pull/2247
* (docs): update documentation on database-connectors by @jethrokuan in https://github.com/org-roam/org-roam/pull/2298
* (db): default to sqlite-builtin when possible by @jethrokuan in https://github.com/org-roam/org-roam/pull/2299
* don't complete org-roam nodes in source blocks by @ParetoOptimalDev in https://github.com/org-roam/org-roam/pull/2292
* (core): add org-roam-node-category by @jethrokuan in https://github.com/org-roam/org-roam/pull/2300
* add discoverability support for age encrypted org files by @anticomputer in https://github.com/org-roam/org-roam/pull/2302
* (minor): fix lints on main by @jethrokuan in https://github.com/org-roam/org-roam/pull/2320
* utils: descendant-of-p: Defend against nils by @qzdl in https://github.com/org-roam/org-roam/pull/2319
* Add customisable function for prompting when adding refs by @Hugo-Heagren in https://github.com/org-roam/org-roam/pull/2317
* Fix org-fold-core-style in org-roam-buffer by @hwiorn in https://github.com/org-roam/org-roam/pull/2325
* depend on snapshot of emacsql by @jethrokuan in https://github.com/org-roam/org-roam/pull/2327
* (node): org-roam-node-at-point: don't error in non-org buffers by @Hugo-Heagren in https://github.com/org-roam/org-roam/pull/2329
* (node) add optional NOCASE parameter to org-roam-node-from-title-or-alias by @nuthub in https://github.com/org-roam/org-roam/pull/2403
* (docs): update org-protocol instructions by @benthamite in https://github.com/org-roam/org-roam/pull/2401
* Fix some typos by @feltcat in https://github.com/org-roam/org-roam/pull/2430
* (docs): fix name of my-org-roam-show-backlink-p by @Delapouite in https://github.com/org-roam/org-roam/pull/2447
* Link Martin Edström's knowledge base by @meedstrom in https://github.com/org-roam/org-roam/pull/2394
* Update org-roam.org by @marcosbodio in https://github.com/org-roam/org-roam/pull/2441
* (chore): fix indent blocking ci lint execution by @Delapouite in https://github.com/org-roam/org-roam/pull/2448
* Fix Issue #2410 - Unlinked References doesnt work if the org-roam-directory has a space in it. by @vikram-mandyam in https://github.com/org-roam/org-roam/pull/2411
* (test): add "org-roam--list-files-search-globs" by @Delapouite in https://github.com/org-roam/org-roam/pull/2449
* Backlink heading by @toregilhk in https://github.com/org-roam/org-roam/pull/2333
* (test): add org-roam-id-find by @Delapouite in https://github.com/org-roam/org-roam/pull/2450
* (test): add org-roam-id-at-point by @Delapouite in https://github.com/org-roam/org-roam/pull/2451
* (test): add org-roam--buffer-promoteable-p by @Delapouite in https://github.com/org-roam/org-roam/pull/2452
* (test): add org-roam--get-titles by @Delapouite in https://github.com/org-roam/org-roam/pull/2453
* (test): add org-roam-node-from-{id|title-or-alias} by @Delapouite in https://github.com/org-roam/org-roam/pull/2454
* (test): add org-roam-alias-{add|remove} by @Delapouite in https://github.com/org-roam/org-roam/pull/2455
* (docs): add missing versions dates and obsolete notice by @Delapouite in https://github.com/org-roam/org-roam/pull/2456
* (docs): explain org-roam-file-exclude-regexp by @adamoudad in https://github.com/org-roam/org-roam/pull/2458
* (test): add org-roam-demote-entire-buffer by @Delapouite in https://github.com/org-roam/org-roam/pull/2459
* (test): add org-roam-file-p by @Delapouite in https://github.com/org-roam/org-roam/pull/2460
* (test): add org-roam-buffer-p by @Delapouite in https://github.com/org-roam/org-roam/pull/2461
* (fix): remove dead-code about org-roam-shield feature by @Delapouite in https://github.com/org-roam/org-roam/pull/2462
* (test): add org-roam-db--file-hash by @Delapouite in https://github.com/org-roam/org-roam/pull/2464
* (test): add org-roam-db-get-{scheduled|deadline}-time by @Delapouite in https://github.com/org-roam/org-roam/pull/2465
* Doc formatting: 2 fixes by @kevinrineer in https://github.com/org-roam/org-roam/pull/2475
* Take org-roam-node as argument to #'org-roam-refile by @pestctrl in https://github.com/org-roam/org-roam/pull/2388
* Compatibility with latest org-id version: advise org-id-find rather than overwriting id link by @ricklupton in https://github.com/org-roam/org-roam/pull/2432
* Depend on emacsql 4.0.0 by @bcc32 in https://github.com/org-roam/org-roam/pull/2466
* Use regexp match to replace hard-coded path equal test by @manphiz in https://github.com/org-roam/org-roam/pull/2497
* Align sqlite integration with emacsql 4.0 by @dustinfarris in https://github.com/org-roam/org-roam/pull/2503
* fix #2425 Prevent data loss when user has called org-roam-extract-subtree on folded org headline by @akashpal-21 in https://github.com/org-roam/org-roam/pull/2444
* Rely on emacsql-sqlite-open to pick the best available back-end by @tarsius in https://github.com/org-roam/org-roam/pull/2486
* Set org-roam-directory to a non-existent path to ensure robust test by @manphiz in https://github.com/org-roam/org-roam/pull/2499
* (perf): Deprecate link :outline properties by @meedstrom in https://github.com/org-roam/org-roam/pull/2509
* Bump DB version to avoid error by @meedstrom in https://github.com/org-roam/org-roam/pull/2514
## 2.2.2 (2022-04-25)
### Breaking
### Added
- [#2138](https://github.com/org-roam/org-roam/pull/2138) export: add new module
- [#2170](https://github.com/org-roam/org-roam/pull/2170) log: add new module for working with org logs
- [#2158](https://github.com/org-roam/org-roam/pull/2158) db: support emacsql-sqlite-builtin and emacsql-sqlite-module
- [#2160](https://github.com/org-roam/org-roam/pull/2160) core: support a list of `org-roam-file-exclude-regexp`
### Removed
### Fixed
- [#2091](https://github.com/org-roam/org-roam/pull/2091) node: fix org-roam-promote-entire-buffer structural errors
- [#2130](https://github.com/org-roam/org-roam/pull/2130) buffer: unlinked-references section now also searches within symlinked directories
- [#2152](https://github.com/org-roam/org-roam/pull/2152) org-roam-preview-default-function: doesn't copy copy content of next heading node when current node's content is empty
- [#2159](https://github.com/org-roam/org-roam/pull/2159) db: fix db syncs on narrowed buffers
- [#2156](https://github.com/org-roam/org-roam/pull/2157) capture: templates with functions are handled correctly to avoid signaling `char-or-string-p`
### Changed
- [#2160](https://github.com/org-roam/org-roam/pull/2160) core: ignore files in `org-attach-id-dir` by default
## 2.2.1 (2022-03-15)
## TBD
### Breaking
- [#2054](https://github.com/org-roam/org-roam/pull/2054) node: simplify default `org-roam-node-display-template`.
This was done so completions work fine by default on all completion systems. To restore the tabular vertical completion interface, set this in your configuration:
@@ -121,27 +14,15 @@
- [#2042](https://github.com/org-roam/org-roam/pull/2042) db: add `org-roam-db-extra-links-elements` and `org-roam-db-extra-links-exclude-keys` for fine-grained control over additional link parsing
- [#2049](https://github.com/org-roam/org-roam/pull/2049) capture: allow ID to be used as part of `org-roam-capture-templates`
- [#2050](https://github.com/org-roam/org-roam/pull/2050) core: add `FILTER-FN` to `org-roam-node-random`
- [#2065](https://github.com/org-roam/org-roam/pull/2065) dailies: add `keys` argument to the remaining dailies functions `org-roam-dailies-goto-yesterday`/`-today`/`-tomorrow`/`-date` and `org-roam-dailies-capture-yesterday`/`-tomorrow`/`-date` to give the abilty to get into a capture buffer bypassing the selection screen in all dailies commands. Extension of #2055
- [#2079](https://github.com/org-roam/org-roam/pull/2079) capture: ensure that `:ref` info captured in all cases.
- [#2121](https://github.com/org-roam/org-roam/pull/2121) buffer: add unique option to `org-roam-backlinks-section`
### Removed
### Fixed
- [#2086](https://github.com/org-roam/org-roam/pull/2086) capture: correctly update org-id-locations on capture
- [#2082](https://github.com/org-roam/org-roam/pull/2082) buffer: don't destroy window if `org-roam-node-toggle` reuses window
- [#2080](https://github.com/org-roam/org-roam/pull/2080) dailies: prevent multiple "dailies/" subdir expansions
- [#2055](https://github.com/org-roam/org-roam/pull/2055) dailies: removed stray f require, which was causing require and compilation errors
- [#2117](https://github.com/org-roam/org-roam/pull/2117) capture: preserve trailing whitespace content in capture templates
- [#2056](https://github.com/org-roam/org-roam/pull/2056) capture: IDs are now created for entries in capture templates
### Changed
- [#2060](https://github.com/org-roam/org-roam/pull/2060) node: added double acute accent normalization for Unicode characters in titles
- [#2040](https://github.com/org-roam/org-roam/pull/2040) completions: fix completions display-width for Helm users
- [#2025](https://github.com/org-roam/org-roam/pull/2025) chore: removed the dependencies on f.el and s.el
- [#2109](https://github.com/org-roam/org-roam/pull/2109) capture: `org-roam-node-insert` places cursor after inserted link where appropriate
- [#2123](https://github.com/org-roam/org-roam/pull/2123), [#2124](https://github.com/org-roam/org-roam/pull/2124) buffer: `org-roam-mode-section-functions` renamed to `org-roam-mode-sections`, supports passing args into the section-rendering function
## 2.2.0 (2022-01-14)
## 2.2.0
### Added
- [#1806](https://github.com/org-roam/org-roam/pull/1806), [#2017](https://github.com/org-roam/org-roam/pull/2017) db: support caching and usage of Org 9.5's in-built citations
- [#1963](https://github.com/org-roam/org-roam/pull/1963) db: cache file title into files table
@@ -172,8 +53,7 @@
- [#2016](https://github.com/org-roam/org-roam/pull/2016) db: fix node caching being affected by agenda variables
- [#2033](https://github.com/org-roam/org-roam/pull/2023) db: respect local variables during db parsing
## 2.1.0 (2021-08-20)
## 2.1.0
### Added
- [#1693](https://github.com/org-roam/org-roam/pull/1693) added `filter-fn` and `templates` parameter to `org-roam-capture`, `org-roam-node-find`, and `org-roam-node-insert`
- [#1709](https://github.com/org-roam/org-roam/pull/1709) added ability to specify default value in org-roam capture templates
@@ -203,10 +83,7 @@
- [#1713](https://github.com/org-roam/org-roam/pull/1713) capture: check for file-existence before template insertion
- [#1725](https://github.com/org-roam/org-roam/pull/1725) db: always compute hash of encrypted file to prevent re-processing of encrypted files
## 2.0.0 (2021-07-17)
A few symbols have been marked as obsolete. Have a look at the content of [org-roam-compat.el](https://github.com/org-roam/org-roam/blob/f819720c510185af713522c592833ec9f2934251/org-roam-compat.el#L159)
## 2.0.0
### Added
- [#1396](https://github.com/org-roam/org-roam/pull/1396) add option to choose between prepending, appending, and omitting `roam_tags` in file completion
- [#1270](https://github.com/org-roam/org-roam/pull/1270) capture: create OLP if it does not exist. Removes need for OLP setup in `:head`.
@@ -233,7 +110,7 @@ A few symbols have been marked as obsolete. Have a look at the content of [org-r
- [#1409](https://github.com/org-roam/org-roam/issues/1398) prevent inclusion of non-org-roam files in `org-roam-dailies--list-files`
- [#1542](https://github.com/org-roam/org-roam/issues/1542) fix files not excluded when `org-roam-list-files-commands` is nil
## 1.2.3 (2020-11-13)
## 1.2.3 (13-11-2020)
Primarily a stabilization and bug-fix release.
@@ -264,7 +141,7 @@ Org-roam-dailies has also been revamped to include new features, see [this video
- [#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 (2020-10-06)
## 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`.
@@ -302,7 +179,7 @@ This change requires you to set `org-roam-directory` to the resolved path of a f
- [#1057](https://github.com/org-roam/org-roam/pull/1057) Improve performance of database builds by preventing generation of LaTeX and image previews.
- [#1103](https://github.com/org-roam/org-roam/pull/1103) Fix long build-times for Windows on files with URL links
## 1.2.1 (2020-07-27)
## 1.2.1 (27-07-2020)
This release consisted of a big deal of refactoring and bug fixes. Notably, we fixed several catastrophic failures on db builds with bad setups (#854), and modularized tag and title extractions.
@@ -335,7 +212,7 @@ We also added some new features that had been a long time coming:
- [#894](https://github.com/org-roam/org-roam/pull/894) Perform link fixes on all Org-roam files
- [#952](https://github.com/org-roam/org-roam/pull/952) Cache `${foo}` template variables so they do not need to be re-entered twice
## 1.2.0 (2020-06-12)
## 1.2.0 (12-06-2020)
In this release, we improved the linking process by achieving feature parity between links to files and links to headlines. Before, we used the `file:foo::*bar` format to link to the headline `bar` in file `foo`, but this was prone to breakage upon renaming the file or modifying the headline. This is not the case anymore. Now, we use `org-id` to create IDs for those headlines, which are then stored in our database to compute the relationships and jump around. Note that this will work even if youre not using `org-id` in your global configuration for Org-mode.
@@ -363,7 +240,7 @@ We also add `org-roam-unlinked-references`, which naively finds text that could
- [#759](https://github.com/org-roam/org-roam/pull/759), [#760](https://github.com/org-roam/org-roam/pull/760) Tags are only added to the tags table if there are actually tags present
- [#700](https://github.com/org-roam/org-roam/pull/700), [#733](https://github.com/org-roam/org-roam/pull/733) Allow symlinks within the `org-roam` directory
## 1.1.1 (2020-05-18)
## 1.1.1 (18-05-2020)
In this release, we added two new features:
@@ -403,7 +280,7 @@ As usual, this release comes with a multitude of bug-fixes and refactorings.
- [#606](https://github.com/org-roam/org-roam/pull/606) Added `org-roam-dev.el` for developer coding standards
- [#622](https://github.com/org-roam/org-roam/pull/622) Online documentation now points to the Org-based documentation
## 1.1.0 (2020-04-21)
## 1.1.0 (21-04-2020)
To the average user, this release is mainly a bugfix release with additional options to customize. However, the changes made to the source is significant. Most notably, in this release:
@@ -441,7 +318,7 @@ In the coming months, you can expect work on bigger projects (e.g. revamping the
- [#363](https://github.com/org-roam/org-roam/pull/363), [#473](https://github.com/org-roam/org-roam/pull/473) Modularize org-roam features.
- [#497](https://github.com/org-roam/org-roam/pull/497) Simplify `org-roam--list-files` implementation
## 1.0.0 (2020-03-23)
## 1.0.0 (23-03-2020)
Org-roam is now on MELPA! We have squashed most of the bugs, and Org-roam has
been stable for the most part.
@@ -460,7 +337,7 @@ been stable for the most part.
- [#293](https://github.com/org-roam/org-roam/pull/293) Fix capture templates not working as expected for `org-roam-find-file`
- [#275](https://github.com/org-roam/org-roam/pull/275) Fix database rebuild when `org-roam-directory` is set locally
## 1.0.0-rc1 (2020-03-06)
## 1.0.0-rc1 (06-03-2020)
This is a pre-release before the push to MELPA. It contains large
internal changes, with little user-facing changes. Most notably, the

View File

@@ -153,8 +153,9 @@ dependencies. These include:
- dash
- f
- s
- org (9.6 is the minimum required version!)
- org (9.4 is the minimal required version!)
- emacsql
- emacsql-sqlite
- magit-section
- filenotify-recursive
@@ -227,9 +228,8 @@ it has not already been addressed on [GitHub][issues] or on
- [Jethro Kuan](https://braindump.jethro.dev/)
([Source](https://github.com/jethrokuan/braindump/tree/master/org))
- [Alexey Shmalko](https://www.alexeyshmalko.com/)
- [Alexey Shmalko](https://braindump.rasen.dev/)
- [Sidharth Arya](https://sidhartharya.github.io/braindump/index.html)
- [Martin Edström](https://edstrom.dev/)
## Contributing

View File

@@ -1,23 +1,23 @@
#+title: Org-roam User Manual
#+author: Jethro Kuan
#+email: jethrokuan95@gmail.com
#+date: 2020-2025
#+date: 2020-2022
#+language: en
#+texinfo_deffn: t
#+texinfo_dir_category: Emacs
#+texinfo_dir_title: Org-roam: (org-roam).
#+texinfo_dir_desc: Roam Research for Emacs.
#+subtitle: for version 2.3.1
#+subtitle: for version 2.2.0
#+options: H:4 num:3 toc:nil creator:t ':t
#+property: header-args :eval never
#+texinfo: @noindent
This manual is for Org-roam version 2.3.1.
This manual is for Org-roam version 2.2.0.
#+BEGIN_QUOTE
Copyright (C) 2020-2025 Jethro Kuan <jethrokuan95@gmail.com>
Copyright (C) 2020-2021 Jethro Kuan <jethrokuan95@gmail.com>
You can redistribute this document and/or modify it under the terms of the GNU
General Public License as published by the Free Software Foundation, either
@@ -134,7 +134,7 @@ 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
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
functionality (see [[*org-roam-dailies][org-roam-dailies]]). This provides a central inbox for collecting
functionality (see [[*Org-roam Dailies][Org-roam Dailies]]). This provides a central inbox for collecting
thoughts, to be processed later into permanent notes.
*Permanent notes*
@@ -178,7 +178,18 @@ archives to =package-archives=:
#+END_SRC
Org-roam also depends on a recent version of Org, which can be obtained in Org's
package repository (see info:org#Installation).
package repository (see info:org#Installation). To use Org's ELPA archive:
#+BEGIN_SRC emacs-lisp
(add-to-list 'package-archives '("org" . "https://orgmode.org/elpa/") t)
#+END_SRC
Once you have added your preferred archive, you need to update the
local package list using:
#+BEGIN_EXAMPLE
M-x package-refresh-contents RET
#+END_EXAMPLE
Once you have done that, you can install Org-roam and its dependencies
using:
@@ -224,6 +235,7 @@ dependencies that it requires. These include:
- s
- org
- emacsql
- emacsql-sqlite
- magit-section
You can install this manually as well, or get the latest version from MELPA. You
@@ -267,6 +279,40 @@ file:
install-info /path/to/my/info/files/org-roam.info /path/to/my/info/files/dir
#+end_src
** Installation Troubleshooting
*** C Compiler
Org-roam relies on an Emacs package called ~emacsql~ and ~emacsql-sqlite~ to
work with the ~sqlite~ database. Both of them should be installed automatically
in your Emacs environment as a prerequisite for Org-roam when you install it.
~emacsql-sqlite~ requires a C compiler (e.g. ~gcc~ or ~clang~) to be present in
your computer. How to install a C compiler depends on the OS that you use.
**** C Compiler for Windows
One of the easiest ways to install a C compiler in Windows is to use [[https://www.msys2.org/][MSYS2]] as at the time of this writing:
1. Download and use the installer in the official MSYS2 website
2. Run MSYS2 and in its terminal, type the following and answer "Y" to
proceed -- this will install ~gcc~ in your PC:
#+BEGIN_SRC bash
pacman -S gcc
#+END_SRC
4. On Windows, add ~C:\msys64\usr\bin~ (command =where gcc= in MSYS2 terminal
can tell you the correct path) to ~PATH~ in your environmental variables
5. Launch Emacs and call ~M-x org-roam-db-autosync-mode~ (launch Emacs after
defining the path, so that Emacs can recognize it)
This will automatically start compiling ~emacsql-sqlite~; you should see a
message in minibuffer. It may take a while until compilation completes. Once
complete, you should see a new file ~emacsql-sqlite.exe~ created in a subfolder
named ~sqlite~ under ~emacsql-sqlite~ installation folder. It's typically in
your Emacs configuration folder like this:
~/.config/emacs/elpa/emacsql-sqlite-20190727.1710/sqlite~
* Getting Started
** The Org-roam Node
@@ -375,7 +421,7 @@ Org-roam provides (see [[*Completion][Completion]]).
** Customizing Node Completions
Node selection is achieved via the ~completing-read~ interface, typically
through ~org-roam-node-read~. The presentation of these nodes are governed by
through `org-roam-node-read`. The presentation of these nodes are governed by
~org-roam-node-display-template~.
- Variable: org-roam-node-display-template
@@ -422,8 +468,49 @@ can set ~org-roam-node-display-template~ as such:
* Customizing Node Caching
** How to cache
Org-roam uses a SQLite database to perform caching. This integration is
managed by the [[https://github.com/magit/emacsql][emacsql]] library. It should "just work".
Org-roam uses a sqlite database to perform caching, but there are multiple Emacs
libraries that can be used. The default used by Org-roam is ~emacs-sqlite~.
Below the pros and cons of each package is used:
[[https://github.com/skeeto/emacsql][**emacs-sqlite**]]
The default option used by Org-roam. This library is the most mature and
well-supported and is imported by default in Org-roam.
One downside of using ~emacs-sqlite~ is that using it requires compilation and
can cause issues in some environments (especially Windows). If you have issues
producing the customized binary required by ~emacs-sqlite~, consider using
~emacs-sqlite3~.
[[https://github.com/cireu/emacsql-sqlite3][**emacs-sqlite3**]]
~emacs-sqlite3~ uses the official sqlite3 binary that can be obtained from your
system's package manager. This is useful if you have issues producing the
~sqlite3~ binary required by the other packages. However, it is not recommended
because it has some compatibility issues with Emacs, but should work for most
regular cases. See [[https://nullprogram.com/blog/2014/02/06/][Chris Wellon's blog post]] for more information.
To use ~emacsql-sqlite3~, ensure that the package is installed, and set:
#+begin_src emacs-lisp
(setq org-roam-database-connector 'sqlite3)
#+end_src
[[https://github.com/emacscollective/emacsql-libsqlite3/][**emacsql-libsqlite3**]]
~emacs-libsqlite3~ is a relatively young package which uses an Emacs module that
exposes parts of the SQLite C API to Emacs Lisp, instead of using subprocess as
~emacsql-sqlite~ does. It is expected to be a more performant drop-in
replacement for ~emacs-sqlite~.
At the moment it is experimental and does not work well with the SQL query load
required by Org-roam, but you may still try it by ensuring the package is
installed and setting:
#+begin_src emacs-lisp
(setq org-roam-database-connector 'libsqlite3)
#+end_src
** What to cache
@@ -445,7 +532,7 @@ property to a non-nil value. For example:
One can also set ~org-roam-db-node-include-function~. For example, to exclude
all headlines with the ~ATTACH~ tag from the Org-roam database, one can set:
#+begin_src emacs-lisp
#+begin_src org
(setq org-roam-db-node-include-function
(lambda ()
(not (member "ATTACH" (org-get-tags)))))
@@ -546,10 +633,10 @@ There are currently 3 provided widget types:
- Unlinked references :: View nodes that contain text that match the nodes
title/alias but are not linked
To configure what sections are displayed in the buffer, set ~org-roam-mode-sections~.
To configure what sections are displayed in the buffer, set ~org-roam-mode-section-functions~.
#+begin_src emacs-lisp
(setq org-roam-mode-sections
(setq org-roam-mode-section-functions
(list #'org-roam-backlinks-section
#'org-roam-reflinks-section
;; #'org-roam-unlinked-references-section
@@ -558,29 +645,6 @@ To configure what sections are displayed in the buffer, set ~org-roam-mode-secti
Note that computing unlinked references may be slow, and has not been added in by default.
For each section function, you can pass args along to modify its behaviour. For
example, if you want to render unique sources for backlinks (and also keep
rendering reference links), set ~org-roam-mode-sections~ as follows:
#+begin_src emacs-lisp
(setq org-roam-mode-sections
'((org-roam-backlinks-section :unique t)
org-roam-reflinks-section))
#+end_src
The backlinks section ~org-roam-backlinks-section~ also supports a
predicate to filter backlinks, ~:show-backlink-p~. This can be used
as follows:
#+begin_src emacs-lisp
(defun my-org-roam-show-backlink-p (backlink)
(not (member "daily" (org-roam-node-tags (org-roam-backlink-source-node backlink)))))
(setq org-roam-mode-sections
'((org-roam-backlinks-section :unique t :show-backlink-p my-org-roam-show-backlink-p)
org-roam-reflinks-section))
#+end_src
** Configuring the Org-roam buffer display
Org-roam does not control how the pop-up buffer is displayed: this is left to
@@ -688,7 +752,7 @@ With the above example, if another node links to https://www.google.com/, it
will show up as a “reference backlink”.
These keys also come in useful for when taking website notes, using the
~roam-ref~ protocol (see [[*org-roam-protocol][org-roam-protocol]]).
~roam-ref~ protocol (see [[*Org-roam Protocol][Roam Protocol]]).
You may assign multiple refs to a single node, for example when you want
multiple papers in a series to share the same note, or an article has a citation
@@ -791,7 +855,7 @@ Similarly, the completion candidates are the titles and aliases for all Org-roam
nodes, and upon choosing a candidate a ~roam:Title~ link will be inserted
linking to the node of choice.
This is disabled by default. To enable it, set ~org-roam-completion-everywhere~
This is disable by default. To enable it, set ~org-roam-completion-everywhere~
to ~t~:
#+begin_src emacs-lisp
@@ -819,6 +883,202 @@ Note that the Org-roam database stores metadata information in plain-text
(headline text, for example), so if this information is private to you then you
should also ensure the database is encrypted.
* Org-roam Protocol
Org-roam provides extensions for capturing content from external applications
such as the browser, via ~org-protocol~. Org-roam extends ~org-protocol~ with 2
protocols: the ~roam-node~ and ~roam-ref~ protocols.
** Installation
To enable Org-roam's protocol extensions, simply add the following to your init
file:
#+BEGIN_SRC emacs-lisp
(require 'org-roam-protocol)
#+END_SRC
We also need to set up ~org-protocol~: the instructions for setting up
~org-protocol~ are reproduced below.
*** Linux
For Linux users, create a desktop application in
~~/.local/share/applications/org-protocol.desktop~:
#+begin_example
[Desktop Entry]
Name=Org-Protocol
Exec=emacsclient %u
Icon=emacs-icon
Type=Application
Terminal=false
MimeType=x-scheme-handler/org-protocol
#+end_example
Associate ~org-protocol://~ links with the desktop application by
running in your shell:
#+BEGIN_SRC bash
xdg-mime default org-protocol.desktop x-scheme-handler/org-protocol
#+END_SRC
To disable the "confirm" prompt in Chrome, you can also make Chrome show a
checkbox to tick, so that the ~Org-Protocol Client~ app will be used without
confirmation. To do this, run in a shell:
#+BEGIN_SRC bash
sudo mkdir -p /etc/opt/chrome/policies/managed/
sudo tee /etc/opt/chrome/policies/managed/external_protocol_dialog.json >/dev/null <<'EOF'
{
"ExternalProtocolDialogShowAlwaysOpenCheckbox": true
}
EOF
sudo chmod 644 /etc/opt/chrome/policies/managed/external_protocol_dialog.json
#+END_SRC
and then restart Chrome (for example, by navigating to <chrome://restart>) to
make the new policy take effect.
See [[https://www.chromium.org/administrators/linux-quick-start][here]] for more info on the ~/etc/opt/chrome/policies/managed~ directory and
[[https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ExternalProtocolDialogShowAlwaysOpenCheckbox][here]] for information on the ~ExternalProtocolDialogShowAlwaysOpenCheckbox~ policy.
*** Mac OS
For Mac OS, we need to create our own application.
1. Launch Script Editor
2. Use the following script, paying attention to the path to ~emacsclient~:
#+begin_src emacs-lisp
on open location this_URL
set EC to "/usr/local/bin/emacsclient --no-wait "
set filePath to quoted form of this_URL
do shell script EC & filePath
tell application "Emacs" to activate
end open location
#+end_src
3. Save the script in ~/Applications/OrgProtocolClient.app~, changing the script type to
"Application", rather than "Script".
4. Edit ~/Applications/OrgProtocolClient.app/Contents/Info.plist~, adding the
following before the last ~</dict>~ tag:
#+begin_src text
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>org-protocol handler</string>
<key>CFBundleURLSchemes</key>
<array>
<string>org-protocol</string>
</array>
</dict>
</array>
#+end_src
5. Save the file, and run the ~OrgProtocolClient.app~ to register the protocol.
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
without confirmation. To do this, run in a shell:
#+BEGIN_SRC bash
defaults write com.google.Chrome ExternalProtocolDialogShowAlwaysOpenCheckbox -bool true
#+END_SRC
If you're using [[https://github.com/railwaycat/homebrew-emacsmacport][Emacs Mac Port]], it registered its `Emacs.app` as the default
handler for the URL scheme `org-protocol`. To make ~OrgProtocol.app~
the default handler instead, run:
#+BEGIN_SRC bash
defaults write com.apple.LaunchServices/com.apple.launchservices.secure LSHandlers -array-add \
'{"LSHandlerPreferredVersions" = { "LSHandlerRoleAll" = "-"; }; LSHandlerRoleAll = "org.yourusername.OrgProtocol"; LSHandlerURLScheme = "org-protocol";}'
#+END_SRC
Then restart your computer.
**** Testing org-protocol
To test that you have the handler setup and registered properly from the command
line you can run:
#+begin_src bash
open org-protocol://roam-ref\?template=r\&ref=test\&title=this
#+end_src
If you get an error similar too this or the wrong handler is run:
#+begin_quote
No application knows how to open URL org-protocol://roam-ref?template=r&ref=test&title=this (Error Domain=NSOSStatusErrorDomain Code=-10814 "kLSApplicationNotFoundErr: E.g. no application claims the file" UserInfo={_LSLine=1489, _LSFunction=runEvaluator}).
#+end_quote
You may need to manually register your handler, like this:
#+begin_src bash
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -R -f /Applications/OrgProtocolClient.app
#+end_src
Here is a link to the lsregister command that is really useful: https://eclecticlight.co/2019/03/25/lsregister-a-valuable-undocumented-command-for-launchservices/
*** Windows
For Windows, create a temporary ~org-protocol.reg~ file:
#+BEGIN_SRC text
REGEDIT4
[HKEY_CLASSES_ROOT\org-protocol]
@="URL:Org Protocol"
"URL Protocol"=""
[HKEY_CLASSES_ROOT\org-protocol\shell]
[HKEY_CLASSES_ROOT\org-protocol\shell\open]
[HKEY_CLASSES_ROOT\org-protocol\shell\open\command]
@="\"C:\\Windows\\System32\\wsl.exe\" emacsclient \"%1\""
#+END_SRC
The above will forward the protocol to WSL. If you run Emacs natively on
Windows, replace the last line with:
#+BEGIN_SRC text
@="\"c:\\path\\to\\emacs\\bin\\emacsclientw.exe\" \"%1\""
#+END_SRC
After executing the .reg file, the protocol is registered and you can delete the
file.
** The roam-node protocol
The roam-node protocol opens the node with ID specified by the ~node~ key (e.g.
~org-protocol://roam-node?node=node-id~). ~org-roam-graph~ uses this to make the
graph navigable.
** The roam-ref protocol
This protocol finds or creates a new note with a given ~ROAM_REFS~:
[[file:images/roam-ref.gif]]
To use this, create the following [[https://en.wikipedia.org/wiki/Bookmarklet][bookmarklet]] in your browser:
#+BEGIN_SRC javascript
javascript:location.href =
'org-protocol://roam-ref?template=r&ref='
+ encodeURIComponent(location.href)
+ '&title='
+ encodeURIComponent(document.title)
+ '&body='
+ encodeURIComponent(window.getSelection())
#+END_SRC
or as a keybinding in ~qutebrowser~ in , using the ~config.py~ file (see
[[https://github.com/qutebrowser/qutebrowser/blob/master/doc/help/configuring.asciidoc][Configuring qutebrowser]]):
#+BEGIN_SRC python
config.bind("<Ctrl-r>", "open javascript:location.href='org-protocol://roam-ref?template=r&ref='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)")
#+END_SRC
where ~template~ is the template key for a template in
~org-roam-capture-ref-templates~ (see [[*The Templating System][The Templating System]]).
* The Templating System
Org-roam extends the ~org-capture~ system, providing a smoother note-taking
@@ -900,232 +1160,12 @@ One can check the list of available keys for nodes by inspecting the
This makes ~${file}~, ~${file-hash}~ etc. all valid substitutions.
* Extensions
** org-roam-protocol
Org-roam provides extensions for capturing content from external applications
such as the browser, via ~org-protocol~. Org-roam extends ~org-protocol~ with 2
protocols: the ~roam-node~ and ~roam-ref~ protocols.
*** Installation
To enable Org-roam's protocol extensions, simply add the following to your init
file:
#+BEGIN_SRC emacs-lisp
(require 'org-roam-protocol)
#+END_SRC
We also need to set up ~org-protocol~: the instructions for setting up
~org-protocol~ are reproduced here.
On a high-level, external calls are passed to Emacs via ~emacsclient~.
~org-protocol~ intercepts these and runs custom actions based on the protocols
registered. Hence, to use ~org-protocol~, once must:
1. launch the ~emacsclient~ process
2. Register ~org-protocol://~ as a valid scheme-handler
The instructions for the latter for each operating system is detailed below.
**** Linux
For Linux users, create a desktop application in
~~/.local/share/applications/org-protocol.desktop~:
#+begin_example
[Desktop Entry]
Name=Org-Protocol
Exec=emacsclient %u
Icon=emacs-icon
Type=Application
Terminal=false
MimeType=x-scheme-handler/org-protocol
#+end_example
Associate ~org-protocol://~ links with the desktop application by
running in your shell:
#+BEGIN_SRC bash
xdg-mime default org-protocol.desktop x-scheme-handler/org-protocol
#+END_SRC
To disable the "confirm" prompt in Chrome, you can also make Chrome show a
checkbox to tick, so that the ~Org-Protocol Client~ app will be used without
confirmation. To do this, run in a shell:
#+BEGIN_SRC bash
sudo mkdir -p /etc/opt/chrome/policies/managed/
sudo tee /etc/opt/chrome/policies/managed/external_protocol_dialog.json >/dev/null <<'EOF'
{
"ExternalProtocolDialogShowAlwaysOpenCheckbox": true
}
EOF
sudo chmod 644 /etc/opt/chrome/policies/managed/external_protocol_dialog.json
#+END_SRC
and then restart Chrome (for example, by navigating to <chrome://restart>) to
make the new policy take effect.
See [[https://www.chromium.org/administrators/linux-quick-start][here]] for more info on the ~/etc/opt/chrome/policies/managed~ directory and
[[https://cloud.google.com/docs/chrome-enterprise/policies/?policy=ExternalProtocolDialogShowAlwaysOpenCheckbox][here]] for information on the ~ExternalProtocolDialogShowAlwaysOpenCheckbox~ policy.
**** Mac OS
For Mac OS, we need to create our own application.
1. Launch Script Editor
2. Use the following script, paying attention to the path to ~emacsclient~:
#+begin_src emacs-lisp
on open location this_URL
set EC to "/usr/local/bin/emacsclient --no-wait "
set filePath to quoted form of this_URL
do shell script EC & filePath & " &> /dev/null &"
tell application "Emacs" to activate
end open location
#+end_src
3. Save the script in ~/Applications/OrgProtocolClient.app~, changing the script type to
"Application", rather than "Script".
4. Edit ~/Applications/OrgProtocolClient.app/Contents/Info.plist~, adding the
following before the last ~</dict>~ tag:
#+begin_src text
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>org-protocol handler</string>
<key>CFBundleURLSchemes</key>
<array>
<string>org-protocol</string>
</array>
</dict>
</array>
#+end_src
5. Save the file, and run the ~OrgProtocolClient.app~ to register the protocol.
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
without confirmation. To do this, run in a shell:
#+BEGIN_SRC bash
defaults write com.google.Chrome ExternalProtocolDialogShowAlwaysOpenCheckbox -bool true
#+END_SRC
If you're using [[https://github.com/railwaycat/homebrew-emacsmacport][Emacs Mac Port]], it registered its `Emacs.app` as the default
handler for the URL scheme `org-protocol`. To make ~OrgProtocol.app~
the default handler instead, run:
#+BEGIN_SRC bash
defaults write com.apple.LaunchServices/com.apple.launchservices.secure LSHandlers -array-add \
'{"LSHandlerPreferredVersions" = { "LSHandlerRoleAll" = "-"; }; LSHandlerRoleAll = "org.yourusername.OrgProtocol"; LSHandlerURLScheme = "org-protocol";}'
#+END_SRC
Then restart your computer.
If you're using the [[https://formulae.brew.sh/formula/emacs][Emacs Homebrew formula]], you may need one of the following additional configurations:
a) Add option `-c` to `emacsclient` in the script, and start emacs from command line with `emacs --daemon`
#+begin_src emacs-lisp
on open location this_URL
set EC to "/usr/local/bin/emacsclient -c --no-wait "
set filePath to quoted form of this_URL
do shell script EC & filePath & " &> /dev/null &"
tell application "Emacs" to activate
end open location
#+end_src
b) Add `(server-start)` in .emacs (in this case you do not need option `-c` for `emacsclient` in the script, and you do not need to start emacs with `emacs --daemon`
***** Testing org-protocol
To test that you have the handler setup and registered properly from the command
line you can run:
#+begin_src bash
open org-protocol://roam-ref\?template=r\&ref=test\&title=this
#+end_src
If you get an error similar too this or the wrong handler is run:
#+begin_quote
No application knows how to open URL org-protocol://roam-ref?template=r&ref=test&title=this (Error Domain=NSOSStatusErrorDomain Code=-10814 "kLSApplicationNotFoundErr: E.g. no application claims the file" UserInfo={_LSLine=1489, _LSFunction=runEvaluator}).
#+end_quote
You may need to manually register your handler, like this:
#+begin_src bash
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -R -f /Applications/OrgProtocolClient.app
#+end_src
Here is a link to the lsregister command that is really useful: https://eclecticlight.co/2019/03/25/lsregister-a-valuable-undocumented-command-for-launchservices/
**** Windows
For Windows, create a temporary ~org-protocol.reg~ file:
#+BEGIN_SRC text
REGEDIT4
[HKEY_CLASSES_ROOT\org-protocol]
@="URL:Org Protocol"
"URL Protocol"=""
[HKEY_CLASSES_ROOT\org-protocol\shell]
[HKEY_CLASSES_ROOT\org-protocol\shell\open]
[HKEY_CLASSES_ROOT\org-protocol\shell\open\command]
@="\"C:\\Windows\\System32\\wsl.exe\" emacsclient \"%1\""
#+END_SRC
The above will forward the protocol to WSL. If you run Emacs natively on
Windows, replace the last line with:
#+BEGIN_SRC text
@="\"c:\\path\\to\\emacs\\bin\\emacsclientw.exe\" \"%1\""
#+END_SRC
After executing the .reg file, the protocol is registered and you can delete the
file.
*** The roam-node protocol
The roam-node protocol opens the node with ID specified by the ~node~ key (e.g.
~org-protocol://roam-node?node=node-id~). ~org-roam-graph~ uses this to make the
graph navigable.
*** The roam-ref protocol
This protocol finds or creates a new note with a given ~ROAM_REFS~:
[[file:images/roam-ref.gif]]
To use this, create the following [[https://en.wikipedia.org/wiki/Bookmarklet][bookmarklet]] in your browser:
#+BEGIN_SRC javascript
javascript:location.href =
'org-protocol://roam-ref?template=r&ref='
+ encodeURIComponent(location.href)
+ '&title='
+ encodeURIComponent(document.title)
+ '&body='
+ encodeURIComponent(window.getSelection())
#+END_SRC
or as a keybinding in ~qutebrowser~ in , using the ~config.py~ file (see
[[https://github.com/qutebrowser/qutebrowser/blob/master/doc/help/configuring.asciidoc][Configuring qutebrowser]]):
#+BEGIN_SRC python
config.bind("<Ctrl-r>", "open javascript:location.href='org-protocol://roam-ref?template=r&ref='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)")
#+END_SRC
where ~template~ is the template key for a template in
~org-roam-capture-ref-templates~ (see [[*The Templating System][The Templating System]]).
** org-roam-graph
* Graphing
Org-roam provides basic graphing capabilities to explore interconnections
between notes, in ~org-roam-graph~. This is done by performing SQL queries and
generating images using [[https://graphviz.org/][Graphviz]]. The graph can also be navigated: see [[*org-roam-protocol][org-roam-protocol]].
generating images using [[https://graphviz.org/][Graphviz]]. The graph can also be navigated: see [[*Org-roam Protocol][Roam
Protocol]].
The entry point to graph creation is ~org-roam-graph~.
@@ -1166,7 +1206,7 @@ ARG may be any of the following values:
(org-roam-graph--open (concat "file://///wsl$/Ubuntu" file)))))
#+END_SRC
*** 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
@@ -1192,12 +1232,12 @@ for customizable options.
Extra options for edges in the graphviz output (The "E" attributes).
Example: ~'(("dir" . "back"))~
** org-roam-dailies
* Org-roam Dailies
Org-roam provides journaling capabilities akin to
Org-journal with ~org-roam-dailies~.
*** Configuration
** Configuration
For ~org-roam-dailies~ to work, you need to define two variables:
@@ -1223,7 +1263,7 @@ Here is a sane default configuration:
See [[*The Templating System][The Templating System]] for creating new templates.
*** Usage
** Usage
~org-roam-dailies~ provides these interactive functions:
@@ -1241,7 +1281,7 @@ 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 yesterday.
Create an entry in the daily note for yesteday.
With numeric argument ~n~, use the daily note ~n~ days in the past.
@@ -1277,18 +1317,6 @@ There are also commands which allow you to use Emacss ~calendar~ to find the
- Function: ~org-roam-dailies-goto-next-note~
When in an daily-note, find the next one.
** org-roam-export
Because Org-roam files are plain org files, they can be exported easily using
~org-export~ to a variety of formats, including ~html~ and ~pdf~. However,
Org-roam relies heavily on ID links, which Org's html export has poor support
of. To fix this, Org-roam provides a bunch of overrides to better support
export. To use them, simply run:
#+begin_src emacs-lisp
(require 'org-roam-export)
#+end_src
* Performance Optimization
** Garbage Collection
@@ -1362,7 +1390,7 @@ The Deft interface can slow down quickly when the number of files get huge.
[[https://github.com/bastibe/org-journal][Org-journal]] provides journaling capabilities to Org-mode. A lot of its
functionalities have been incorporated into Org-roam under the name
[[*org-roam-dailies][~org-roam-dailies~]]. It remains a good tool if you want to isolate your verbose
[[*Org-roam Dailies][~org-roam-dailies~]]. It remains a good tool if you want to isolate your verbose
journal entries from the ideas you would write on a scratchpad.
#+BEGIN_SRC emacs-lisp
@@ -1590,6 +1618,7 @@ to the publishing directory. Example code follows:
(kill-buffer (file-name-nondirectory svg))
(setq my-publish-time 0)))))
#+end_src
* Developer's Guide to Org-roam
** Org-roam's Design Principle
@@ -1628,7 +1657,7 @@ queries on their Org files.
** Building Extensions and Advanced Customization of Org-roam
Because Org-roam's core functionality is small, it is possible and sometimes
desirable to build extensions on top of it. These extensions may use one or more of
desirable to build extensions on top of it. These extensions may one or more of
the following functionalities:
- Access to Org-roam's database
@@ -1735,7 +1764,7 @@ When GOTO is non-nil, go the note without creating an entry."
:END:
#+BEGIN_QUOTE
Copyright (C) 2020-2025 Jethro Kuan <jethrokuan95@gmail.com>
Copyright (C) 2020-2021 Jethro Kuan <jethrokuan95@gmail.com>
You can redistribute this document and/or modify it under the terms
of the GNU General Public License as published by the Free Software

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
;;; org-roam-dailies.el --- Daily-notes for Org-roam -*- coding: utf-8; lexical-binding: t; -*-
;;;
;; Copyright © 2020-2025 Jethro Kuan <jethrokuan95@gmail.com>
;; Copyright © 2020-2021 Jethro Kuan <jethrokuan95@gmail.com>
;; Copyright © 2020 Leo Vivier <leo.vivier+dev@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; Leo Vivier <leo.vivier+dev@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.1
;; Version: 2.2.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org-roam "2.1"))
;; This file is NOT part of GNU Emacs.
@@ -139,93 +139,72 @@ In this case, interactive selection will be bypassed."
(org-roam-dailies--capture (current-time) goto keys))
;;;###autoload
(defun org-roam-dailies-goto-today (&optional keys)
"Find the daily-note for today, creating it if necessary.
ELisp programs can set KEYS to a string associated with a template.
In this case, interactive selection will be bypassed."
(defun org-roam-dailies-goto-today ()
"Find the daily-note for today, creating it if necessary."
(interactive)
(org-roam-dailies-capture-today t keys))
(org-roam-dailies-capture-today t))
;;;; Tomorrow
;;;###autoload
(defun org-roam-dailies-capture-tomorrow (n &optional goto keys)
(defun org-roam-dailies-capture-tomorrow (n &optional goto)
"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.
ELisp programs can set KEYS to a string associated with a template.
In this case, interactive selection will be bypassed."
creating an entry."
(interactive "p")
(org-roam-dailies--capture (time-add (* n 86400) (current-time)) goto keys))
(org-roam-dailies--capture (time-add (* n 86400) (current-time)) goto))
;;;###autoload
(defun org-roam-dailies-goto-tomorrow (n &optional keys)
(defun org-roam-dailies-goto-tomorrow (n)
"Find the daily-note for tomorrow, creating it if necessary.
With numeric argument N, use the daily-note N days in the
future.
ELisp programs can set KEYS to a string associated with a template.
In this case, interactive selection will be bypassed."
future."
(interactive "p")
(org-roam-dailies-capture-tomorrow n t keys))
(org-roam-dailies-capture-tomorrow n t))
;;;; Yesterday
;;;###autoload
(defun org-roam-dailies-capture-yesterday (n &optional goto keys)
(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.
ELisp programs can set KEYS to a string associated with a template.
In this case, interactive selection will be bypassed."
When GOTO is non-nil, go the note without creating an entry."
(interactive "p")
(org-roam-dailies-capture-tomorrow (- n) goto keys))
(org-roam-dailies-capture-tomorrow (- n) goto))
;;;###autoload
(defun org-roam-dailies-goto-yesterday (n &optional keys)
(defun org-roam-dailies-goto-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.
ELisp programs can set KEYS to a string associated with a template.
In this case, interactive selection will be bypassed."
future."
(interactive "p")
(org-roam-dailies-capture-tomorrow (- n) t keys))
(org-roam-dailies-capture-tomorrow (- n) t))
;;;; Date
;;;###autoload
(defun org-roam-dailies-capture-date (&optional goto prefer-future keys)
(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.
ELisp programs can set KEYS to a string associated with a template.
In this case, interactive selection will be bypassed."
creating an entry."
(interactive "P")
(let ((time (let ((org-read-date-prefer-future prefer-future))
(org-read-date nil t nil (if goto
"Find daily-note: "
"Capture to daily-note: ")))))
(org-roam-dailies--capture time goto keys)))
(org-roam-dailies--capture time goto)))
;;;###autoload
(defun org-roam-dailies-goto-date (&optional prefer-future keys)
(defun org-roam-dailies-goto-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.
ELisp programs can set KEYS to a string associated with a template.
In this case, interactive selection will be bypassed."
Prefer past dates, unless PREFER-FUTURE is non-nil."
(interactive)
(org-roam-dailies-capture-date t prefer-future keys))
(org-roam-dailies-capture-date t prefer-future))
;;;; Navigation
(defun org-roam-dailies-goto-next-note (&optional n)
@@ -329,8 +308,7 @@ When GOTO is non-nil, go the note without creating an entry.
ELisp programs can set KEYS to a string associated with a template.
In this case, interactive selection will be bypassed."
(let ((org-roam-directory (expand-file-name org-roam-dailies-directory org-roam-directory))
(org-roam-dailies-directory "./"))
(let ((org-roam-directory (expand-file-name org-roam-dailies-directory org-roam-directory)))
(org-roam-capture- :goto (when goto '(4))
:keys keys
:node (org-roam-node-create)
@@ -339,11 +317,12 @@ In this case, interactive selection will be bypassed."
(when goto (run-hooks 'org-roam-dailies-find-file-hook)))
(add-hook 'org-roam-capture-preface-hook #'org-roam-dailies--override-capture-time-h)
(defun org-roam-dailies--override-capture-time-h ()
"Override the `:default-time' with the time from `:override-default-time'."
(prog1 nil
(when (org-roam-capture--get :override-default-time)
(org-capture-put :default-time (org-roam-capture--get :override-default-time)))))
(org-capture-put :default-time (org-roam-capture--get :override-default-time)))
nil)
;;; Bindings
(defvar org-roam-dailies-map (make-sparse-keymap)

View File

@@ -1,75 +0,0 @@
;;; org-roam-export.el --- Org-roam org-export tweaks -*- coding: utf-8; lexical-binding: t; -*-
;; Copyright © 2020-2025 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (org "9.6") (org-roam "2.1"))
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;;
;; This package provides the necessary changes required to make org-export work out-of-the-box.
;;
;; To enable it, run:
;;
;; (require 'org-roam-export)
;;
;; The key issue Org's export-to-html functionality has is that it does not respect the ID property, which
;; Org-roam relies heavily on. This patches the necessary function in ox-html to export ID links correctly,
;; pointing to the correct place.
;;
;;; Code:
(require 'ox-html)
(defun org-roam-export--org-html--reference (datum info &optional named-only)
"Org-roam's patch for `org-html--reference' to support ID link export.
See `org-html--reference' for DATUM, INFO and NAMED-ONLY."
(let* ((type (org-element-type datum))
(user-label
(org-element-property
(pcase type
((or `headline `inlinetask) :CUSTOM_ID)
((or `radio-target `target) :value)
(_ :name))
datum))
(user-label
(or user-label
(when-let ((path (org-element-property :ID datum)))
;; see `org-html-link' for why we use "ID-"
;; (search for "ID-" in ox-html.el)
(concat "ID-" path)))))
(cond
((and user-label
(or (plist-get info :html-prefer-user-labels)
(memq type '(headline inlinetask))))
user-label)
((and named-only
(not (memq type '(headline inlinetask radio-target target)))
(not user-label))
nil)
(t
(org-export-get-reference datum info)))))
(advice-add 'org-html--reference :override #'org-roam-export--org-html--reference)
(provide 'org-roam-export)
;;; org-roam-export.el ends here

View File

@@ -1,12 +1,12 @@
;;; org-roam-graph.el --- Basic graphing functionality for Org-roam -*- coding: utf-8; lexical-binding: t; -*-
;; Copyright © 2020-2025 Jethro Kuan <jethrokuan95@gmail.com>
;; Copyright © 2020-2021 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (org "9.6") (org-roam "2.1"))
;; Version: 2.2.0
;; Package-Requires: ((emacs "26.1") (org "9.4") (org-roam "2.1"))
;; This file is NOT part of GNU Emacs.

View File

@@ -1,12 +1,12 @@
;;; org-roam-overlay.el --- Link overlay for [id:] links to Org-roam nodes -*- coding: utf-8; lexical-binding: t; -*-
;; Copyright © 2020-2025 Jethro Kuan <jethrokuan95@gmail.com>
;; Copyright © 2020-2021 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (org "9.6") (org-roam "2.1"))
;; Version: 2.2.0
;; Package-Requires: ((emacs "26.1") (org "9.4") (org-roam "2.1"))
;; This file is NOT part of GNU Emacs.

View File

@@ -1,11 +1,11 @@
;;; org-roam-protocol.el --- Protocol handler for roam:// links -*- coding: utf-8; lexical-binding: t; -*-
;; Copyright © 2020-2025 Jethro Kuan <jethrokuan95@gmail.com>
;; Copyright © 2020-2021 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (org "9.6") (org-roam "2.1"))
;; Version: 2.2.0
;; Package-Requires: ((emacs "26.1") (org "9.4") (org-roam "2.1"))
;; This file is NOT part of GNU Emacs.
@@ -169,6 +169,25 @@ org-protocol://roam-node?node=uuid"
(push '("org-roam-node" :protocol "roam-node" :function org-roam-protocol-open-node)
org-protocol-protocol-alist)
;;; Capture implementation
(add-hook 'org-roam-capture-preface-hook #'org-roam-protocol--try-capture-to-ref-h)
(defun org-roam-protocol--try-capture-to-ref-h ()
"Try to capture to an existing node that match the ref."
(when-let ((node (and (plist-get org-roam-capture--info :ref)
(org-roam-node-from-ref
(plist-get org-roam-capture--info :ref)))))
(set-buffer (org-capture-target-buffer (org-roam-node-file node)))
(goto-char (org-roam-node-point node))
(widen)
(org-roam-node-id node)))
(add-hook 'org-roam-capture-new-node-hook #'org-roam-protocol--insert-captured-ref-h)
(defun org-roam-protocol--insert-captured-ref-h ()
"Insert the ref if any."
(when-let ((ref (plist-get org-roam-capture--info :ref)))
(org-roam-ref-add ref)))
(provide 'org-roam-protocol)
;;; org-roam-protocol.el ends here

View File

@@ -1,11 +0,0 @@
#! /bin/sh
set -e
ELDEV_BIN_DIR=~/.local/bin
# `$GITHUB_PATH' is a magic file which contents is translated to environment variable `$PATH'.
echo "$ELDEV_BIN_DIR" >> $GITHUB_PATH
mkdir -p $ELDEV_BIN_DIR
curl -fsSL https://raw.githubusercontent.com/doublep/eldev/f111d19cda305e5e8fcb70a5675b87173041cb68/bin/eldev > $ELDEV_BIN_DIR/eldev
chmod a+x $ELDEV_BIN_DIR/eldev

View File

@@ -1,12 +1,12 @@
;;; org-roam-capture.el --- Capture functionality -*- coding: utf-8; lexical-binding: t; -*-
;; Copyright © 2020-2025 Jethro Kuan <jethrokuan95@gmail.com>
;; Copyright © 2020-2021 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6") (emacsql "4.1.0") (magit-section "3.0.0"))
;; Version: 2.2.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "3.0.0"))
;; This file is NOT part of GNU Emacs.
@@ -466,22 +466,21 @@ processing by `org-capture'.
Note: During the capture process this function is run by
`org-capture-set-target-location', as a (function ...) based
capture target."
(let ((id (cond ((run-hook-with-args-until-success 'org-roam-capture-preface-hook))
(t (org-roam-capture--setup-target-location)))))
(org-roam-capture--adjust-point-for-capture-type)
(if-let ((id (run-hook-with-args-until-success 'org-roam-capture-preface-hook)))
(org-roam-capture--put :id id)
(org-roam-capture--setup-target-location))
(let ((template (org-capture-get :template)))
(when (stringp template)
(org-capture-put
:template
(org-roam-capture--fill-template template))))
(org-roam-capture--put :id id)
(org-roam-capture--put :finalize (or (org-capture-get :finalize)
(org-roam-capture--get :finalize)))))
(org-roam-capture--get :finalize))))
(defun org-roam-capture--setup-target-location ()
"Initialize the buffer, and goto the location of the new capture.
Return the ID of the location."
(let (p new-file-p)
"Initialize the buffer, and goto the location of the new capture."
(let ((target-entry-p t)
p new-file-p id)
(pcase (org-roam-capture--get-target)
(`(file ,path)
(setq path (org-roam-capture--target-truepath path)
@@ -489,7 +488,8 @@ Return the ID of the location."
(when new-file-p (org-roam-capture--put :new-file path))
(set-buffer (org-capture-target-buffer path))
(widen)
(setq p (goto-char (point-min))))
(setq p (goto-char (point-min))
target-entry-p nil))
(`(file+olp ,path ,olp)
(setq path (org-roam-capture--target-truepath path)
new-file-p (org-roam-capture--new-file-p path))
@@ -505,9 +505,10 @@ Return the ID of the location."
(set-buffer (org-capture-target-buffer path))
(when new-file-p
(org-roam-capture--put :new-file path)
(insert (org-roam-capture--fill-template head 'ensure-newline)))
(insert (org-roam-capture--fill-template head t)))
(widen)
(setq p (goto-char (point-min))))
(setq p (goto-char (point-min))
target-entry-p nil))
(`(file+head+olp ,path ,head ,olp)
(setq path (org-roam-capture--target-truepath path)
new-file-p (org-roam-capture--new-file-p path))
@@ -515,7 +516,7 @@ Return the ID of the location."
(widen)
(when new-file-p
(org-roam-capture--put :new-file path)
(insert (org-roam-capture--fill-template head 'ensure-newline)))
(insert (org-roam-capture--fill-template head t)))
(setq p (point-min))
(let ((m (org-roam-capture-find-or-create-olp olp)))
(goto-char m)))
@@ -569,17 +570,45 @@ Return the ID of the location."
(user-error "No node with title or id \"%s\"" title-or-id))))
(set-buffer (org-capture-target-buffer (org-roam-node-file node)))
(goto-char (org-roam-node-point node))
(setq p (org-roam-node-point node)))))
(setq p (org-roam-node-point node)
target-entry-p (and (derived-mode-p 'org-mode) (org-at-heading-p))))))
;; Setup `org-id' for the current capture target and return it back to the
;; caller.
;; Unless it's an entry type, then we want to create an ID for the entry instead
(pcase (org-capture-get :type)
('entry
(advice-add #'org-capture-place-entry :after #'org-roam-capture--create-id-for-entry)
(org-roam-capture--put :new-node-p t)
(setq id (org-roam-node-id org-roam-capture--node)))
(_
(save-excursion
(goto-char p)
(if-let ((id (org-entry-get p "ID")))
(unless (org-entry-get p "ID")
(org-roam-capture--put :new-node-p t))
(setq id (or (org-entry-get p "ID")
(org-roam-node-id org-roam-capture--node)))
(setf (org-roam-node-id org-roam-capture--node) id)
(org-entry-put p "ID" (org-roam-node-id org-roam-capture--node)))
(prog1
(org-id-get)
(run-hooks 'org-roam-capture-new-node-hook)))))
(org-entry-put p "ID" id))))
(org-roam-capture--put :id id)
(org-roam-capture--put :target-entry-p target-entry-p)
(advice-add #'org-capture-place-template :before #'org-roam-capture--set-target-entry-p-a)
(advice-add #'org-capture-place-template :after #'org-roam-capture-run-new-node-hook-a)))
(defun org-roam-capture--set-target-entry-p-a (_)
"Correct `:target-entry-p' in Org-capture template based on `:target.'"
(org-capture-put :target-entry-p (org-roam-capture--get :target-entry-p))
(advice-remove #'org-capture-place-template #'org-roam-capture--set-target-entry-p-a))
(defun org-roam-capture-run-new-node-hook-a (_)
"Advice to run after the Org-capture template is placed."
(when (org-roam-capture--get :new-node-p)
(run-hooks 'org-roam-capture-new-node-hook))
(advice-remove #'org-capture-place-template #'org-roam-capture--place-template-a))
(defun org-roam-capture--create-id-for-entry ()
"Create the ID for the new entry."
(org-entry-put (point) "ID" (org-roam-capture--get :id))
(advice-remove #'org-capture-place-entry #'org-roam-capture--create-id-for-entry))
(defun org-roam-capture--get-target ()
"Get the current capture :target for the capture template in use."
@@ -591,9 +620,8 @@ Return the ID of the location."
PATH is a string that can optionally contain templated text in
it."
(or (org-roam-node-file org-roam-capture--node)
(thread-first
path
(org-roam-capture--fill-template)
(thread-first path
(org-roam-capture--fill-template t)
(string-trim)
(expand-file-name org-roam-directory))))
@@ -618,7 +646,7 @@ you can catch it with `condition-case'."
(org-with-wide-buffer
(goto-char start)
(dolist (heading olp)
(setq heading (org-roam-capture--fill-template heading))
(setq heading (org-roam-capture--fill-template heading t))
(let ((re (format org-complex-heading-regexp-format
(regexp-quote heading)))
(cnt 0))
@@ -649,55 +677,6 @@ you can catch it with `condition-case'."
end (save-excursion (org-end-of-subtree t t))))
(point-marker))))
(defun org-roam-capture--adjust-point-for-capture-type (&optional pos)
"Reposition the point for template insertion dependently on the capture type.
Return the newly adjusted position of `point'.
POS is the current position of point (an integer) inside the
currently active capture buffer, where the adjustment should
start to begin from. If it's nil, then it will default to
the current value of `point'."
(or pos (setq pos (point)))
(goto-char pos)
(let ((location-type (if (= pos 1) 'beginning-of-file 'heading-at-point)))
(and (eq location-type 'heading-at-point)
(cl-assert (org-at-heading-p)))
(pcase (org-capture-get :type)
(`plain
(cl-case location-type
(beginning-of-file
(if (org-capture-get :prepend)
(let ((el (org-element-at-point)))
(while (and (not (eobp))
(memq (org-element-type el)
'(drawer property-drawer keyword comment comment-block horizontal-rule)))
(goto-char (org-element-property :end el))
(setq el (org-element-at-point))))
(goto-char (org-entry-end-position))))
(heading-at-point
(if (org-capture-get :prepend)
(org-end-of-meta-data t)
(goto-char (org-entry-end-position))))))))
(point))
;;; Capture implementation
(add-hook 'org-roam-capture-preface-hook #'org-roam-capture--try-capture-to-ref-h)
(defun org-roam-capture--try-capture-to-ref-h ()
"Try to capture to an existing node that match the ref."
(when-let ((node (and (plist-get org-roam-capture--info :ref)
(org-roam-node-from-ref
(plist-get org-roam-capture--info :ref)))))
(set-buffer (org-capture-target-buffer (org-roam-node-file node)))
(goto-char (org-roam-node-point node))
(widen)
(org-roam-node-id node)))
(add-hook 'org-roam-capture-new-node-hook #'org-roam-capture--insert-captured-ref-h)
(defun org-roam-capture--insert-captured-ref-h ()
"Insert the ref if any."
(when-let ((ref (plist-get org-roam-capture--info :ref)))
(org-roam-ref-add ref)))
;;;; Finalizers
(add-hook 'org-capture-prepare-finalize-hook #'org-roam-capture--install-finalize-h)
(defun org-roam-capture--install-finalize-h ()
@@ -707,15 +686,14 @@ the current value of `point'."
(defun org-roam-capture--finalize ()
"Finalize the `org-roam-capture' process."
(when-let ((region (org-roam-capture--get :region)))
(org-roam-unshield-region (car region) (cdr region)))
(if org-note-abort
(when-let ((new-file (org-roam-capture--get :new-file))
(_ (yes-or-no-p "Delete file for aborted capture?")))
(when-let ((new-file (org-roam-capture--get :new-file)))
(org-roam-message "Deleting file for aborted capture %s" new-file)
(when (find-buffer-visiting new-file)
(kill-buffer (find-buffer-visiting new-file)))
(delete-file new-file))
(when-let* ((buffer (plist-get org-capture-plist :buffer))
(file (buffer-file-name buffer)))
(org-id-add-location (org-roam-capture--get :id) file))
(when-let* ((finalize (org-roam-capture--get :finalize))
(org-roam-finalize-fn (intern (concat "org-roam-capture--finalize-"
(symbol-name finalize)))))
@@ -738,33 +716,21 @@ This function is to be called in the Org-capture finalization process."
(buf (marker-buffer mkr)))
(with-current-buffer buf
(when-let ((region (org-roam-capture--get :region)))
(org-roam-unshield-region (car region) (cdr region))
(delete-region (car region) (cdr region))
(set-marker (car region) nil)
(set-marker (cdr region) nil))
(let* ((id (org-roam-capture--get :id))
(description (org-roam-capture--get :link-description))
(link (org-link-make-string (concat "id:" id)
description)))
(if (eq (point) (marker-position mkr))
(insert link)
(org-with-point-at mkr
(insert link)))
(run-hook-with-args 'org-roam-post-node-insert-hook
id
description)))))
(insert (org-link-make-string (concat "id:" (org-roam-capture--get :id))
(org-roam-capture--get :link-description)))))))
;;;; Processing of the capture templates
(defun org-roam-capture--fill-template (template &optional ensure-newline)
(defun org-roam-capture--fill-template (template &optional org-capture-p newline)
"Expand TEMPLATE and return it.
It expands ${var} occurrences in TEMPLATE, and then runs
org-capture's template expansion.
When ENSURE-NEWLINE, always ensure there's a newline behind."
(let* ((template (if (functionp template)
(funcall template)
template))
(template-whitespace-content (org-roam-whitespace-content template)))
(setq template
(org-roam-format-template
It expands ${var} occurrences in TEMPLATE. When ORG-CAPTURE-P,
also run Org-capture's template expansion.
If NEWLINE, ensure that the template returned ends with a newline."
(setq template (org-roam-format-template
template
(lambda (key default-val)
(let ((fn (intern key))
@@ -784,13 +750,18 @@ When ENSURE-NEWLINE, always ensure there's a newline behind."
;; `org-capture-fill-template' fills the template, but post-processes whitespace such that the resultant
;; template does not start with any whitespace, and only ends with a single newline
;;
;; Instead, we restore the whitespace in the original template.
(setq template (replace-regexp-in-string "[\n]*\\'" "" (org-capture-fill-template template)))
(when (and ensure-newline
(string-equal template-whitespace-content ""))
(setq template-whitespace-content "\n"))
(setq template (concat template template-whitespace-content))
template))
;; In most cases where we rely on `org-capture-fill-template' to populate non-org-capture-related templates,
;; (e.g. in OLPs), we strip the final newline, obtaining a template that seems to be string-trimmed.
;;
;; This means that if the original passed template has newlines, and ORG-CAPTURE-P is true, then the extra
;; whitespace specified in the template will be ignored.
(when org-capture-p
(setq template
(replace-regexp-in-string "\n$" "" (org-capture-fill-template template))))
(when (and newline
(not (string-suffix-p "\n" template)))
(setq template (concat template "\n")))
template)
(defun org-roam-capture--convert-template (template &optional props)
"Convert TEMPLATE from Org-roam syntax to `org-capture-templates' syntax.

View File

@@ -1,11 +1,11 @@
;;; org-roam-compat.el --- Backward compatibility code -*- coding: utf-8; lexical-binding: t; -*-
;; Copyright © 2020-2025 Jethro Kuan <jethrokuan95@gmail.com>
;; Copyright © 2020-2021 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.1
;; Version: 2.2.0
;; Package-Requires: ((emacs "26.1"))
;; This file is NOT part of GNU Emacs.
@@ -229,10 +229,6 @@ nodes." org-id-locations-file)
'org-roam-remove-property
'org-roam-property-remove "org-roam 2.1")
(define-obsolete-variable-alias
'org-roam-mode-section-functions
'org-roam-mode-sections "org-roam 2.2.0")
;;; Obsolete functions
(make-obsolete 'org-roam-get-keyword 'org-collect-keywords "org-roam 2.0")

View File

@@ -1,12 +1,12 @@
;;; org-roam-db.el --- Org-roam database API -*- coding: utf-8; lexical-binding: t; -*-
;; Copyright © 2020-2025 Jethro Kuan <jethrokuan95@gmail.com>
;; Copyright © 2020-2021 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6") (emacsql "4.1.0") (magit-section "3.0.0"))
;; Version: 2.2.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "3.0.0"))
;; This file is NOT part of GNU Emacs.
@@ -31,11 +31,29 @@
;;
;;; Code:
(require 'org-roam)
(require 'url-parse)
(require 'ol)
(defvar org-outline-path-cache)
;;; Options
(defcustom org-roam-database-connector 'sqlite
"The database connector used by Org-roam.
This must be set before `org-roam' is loaded. To use an
alternative connector you must install the respective package
explicitly. When `sqlite', then use the `emacsql-sqlite' library
that is being maintained in the same repository as `emacsql'
itself. When `libsqlite3', then use the `emacsql-libsqlite3'
library, which itself uses a module provided by the `sqlite3'
package. This is still experimental. When `sqlite3', then use the
`emacsql-sqlite3' library, which uses the official `sqlite3' cli
tool, which is not recommended because it is not suitable to be
used like this, but has the advantage that you likely don't need
a compiler. See https://nullprogram.com/blog/2014/02/06/."
:package-version '(org-roam . "2.2.0")
:group 'org-roam
:type '(choice (const sqlite)
(const libsqlite3)
(const sqlite3)
(symbol :tag "other")))
(defcustom org-roam-db-location (locate-user-emacs-file "org-roam.db")
"The path to file where the Org-roam database is stored.
@@ -89,8 +107,7 @@ is desirable to parse and cache these links (e.g. hiding links in
a property drawer)."
:package-version '(org-roam . "2.2.0")
:group 'org-roam
:type '(set
(const :tag "keywords" keyword)
:type '(set (const :tag "keywords" keyword)
(const :tag "property drawers" node-property)))
(defcustom org-roam-db-extra-links-exclude-keys '((node-property . ("ROAM_REFS"))
@@ -108,7 +125,7 @@ ROAM_REFS."
:type '(alist))
;;; Variables
(defconst org-roam-db-version 20)
(defconst org-roam-db-version 18)
(defvar org-roam-db--connection (make-hash-table :test #'equal)
"Database connection to Org-roam database.")
@@ -119,6 +136,26 @@ ROAM_REFS."
(gethash (expand-file-name (file-name-as-directory org-roam-directory))
org-roam-db--connection))
(declare-function emacsql-sqlite "ext:emacsql-sqlite")
(declare-function emacsql-libsqlite3 "ext:emacsql-libsqlite3")
(declare-function emacsql-sqlite3 "ext:emacsql-sqlite3")
(defun org-roam-db--conn-fn ()
"Return the function for creating the database connection."
(cl-case org-roam-database-connector
(sqlite
(progn
(require 'emacsql-sqlite)
#'emacsql-sqlite))
(libsqlite3
(progn
(require 'emacsql-libsqlite3)
#'emacsql-libsqlite3))
(sqlite3
(progn
(require 'emacsql-sqlite3)
#'emacsql-sqlite3))))
(defun org-roam-db ()
"Entrypoint to the Org-roam sqlite database.
Initializes and stores the database, and the database connection.
@@ -127,7 +164,10 @@ Performs a database upgrade when required."
(emacsql-live-p (org-roam-db--get-connection)))
(let ((init-db (not (file-exists-p org-roam-db-location))))
(make-directory (file-name-directory org-roam-db-location) t)
(let ((conn (emacsql-sqlite-open org-roam-db-location)))
(let ((conn (funcall (org-roam-db--conn-fn) org-roam-db-location)))
(emacsql conn [:pragma (= foreign_keys ON)])
(when-let ((process (emacsql-process conn)))
(set-process-query-on-exit-flag process nil))
(puthash (expand-file-name (file-name-as-directory org-roam-directory))
conn
org-roam-db--connection)
@@ -139,7 +179,7 @@ Performs a database upgrade when required."
((> version org-roam-db-version)
(emacsql-close conn)
(user-error
"The Org-roam database was created with a newer Org-roam version. %s"
"The Org-roam database was created with a newer Org-roam version. "
"You need to update the Org-roam package"))
((< version org-roam-db-version)
(emacsql-close conn)
@@ -281,21 +321,20 @@ If FILE is nil, clear the current buffer."
If there is no title, return the file name relative to
`org-roam-directory'."
(org-link-display-format
(or (string-join (cdr (assoc "TITLE" (org-collect-keywords '("title")))) " ")
(or (cadr (assoc "TITLE" (org-collect-keywords '("title"))))
(file-name-sans-extension (file-relative-name
(buffer-file-name (buffer-base-buffer))
org-roam-directory)))))
(defun org-roam-db-insert-file (&optional hash)
(defun org-roam-db-insert-file ()
"Update the files table for the current buffer.
If UPDATE-P is non-nil, first remove the file in the database.
If HASH is non-nil, use that as the file's hash without recalculating it."
If UPDATE-P is non-nil, first remove the file in the database."
(let* ((file (buffer-file-name))
(file-title (org-roam-db--file-title))
(attr (file-attributes file))
(atime (file-attribute-access-time attr))
(mtime (file-attribute-modification-time attr))
(hash (or hash (org-roam-db--file-hash file))))
(hash (org-roam-db--file-hash)))
(org-roam-db-query
[:insert :into files
:values $v1]
@@ -304,12 +343,12 @@ If HASH is non-nil, use that as the file's hash without recalculating it."
(defun org-roam-db-get-scheduled-time ()
"Return the scheduled time at point in ISO8601 format."
(when-let ((time (org-get-scheduled-time (point))))
(format-time-string "%FT%T" time)))
(org-format-time-string "%FT%T%z" time)))
(defun org-roam-db-get-deadline-time ()
"Return the deadline time at point in ISO8601 format."
(when-let ((time (org-get-deadline-time (point))))
(format-time-string "%FT%T" time)))
(org-format-time-string "%FT%T%z" time)))
(defun org-roam-db-node-p ()
"Return t if headline at point is an Org-roam node, else return nil."
@@ -319,13 +358,12 @@ If HASH is non-nil, use that as the file's hash without recalculating it."
(defun org-roam-db-map-nodes (fns)
"Run FNS over all nodes in the current buffer."
(org-with-wide-buffer
(org-map-region
(lambda ()
(when (org-roam-db-node-p)
(dolist (fn fns)
(funcall fn))))
(point-min) (point-max))))
(point-min) (point-max)))
(defun org-roam-db-map-links (fns)
"Run FNS over all links in the current buffer."
@@ -334,10 +372,9 @@ If HASH is non-nil, use that as the file's hash without recalculating it."
;; `re-search-forward' let the cursor one character after the link, we need to go backward one char to
;; make the point be on the link.
(backward-char)
(let* ((begin (match-beginning 0))
(element (org-element-context))
(let* ((element (org-element-context))
(type (org-element-type element))
link)
link bounds)
(cond
;; Links correctly recognized by Org Mode
((eq type 'link)
@@ -348,9 +385,14 @@ If HASH is non-nil, use that as the file's hash without recalculating it."
((and (member type org-roam-db-extra-links-elements)
(not (member-ignore-case (org-element-property :key element)
(cdr (assoc type org-roam-db-extra-links-exclude-keys))))
(setq link (save-excursion
(goto-char begin)
(save-match-data (org-element-link-parser)))))))
(setq bounds (org-in-regexp org-link-any-re))
(setq link (buffer-substring-no-properties
(car bounds)
(cdr bounds))))
(with-temp-buffer
(delay-mode-hooks (org-mode))
(insert link)
(setq link (org-element-context)))))
(when link
(dolist (fn fns)
(funcall fn link)))))))
@@ -474,14 +516,9 @@ INFO is the org-element parsed buffer."
(;; https://google.com, cite:citeKey
;; Note: we use string-match here because it matches any link: e.g. [[cite:abc][abc]]
;; But this form of matching is loose, and can accept invalid links e.g. [[cite:abc]
(string-match org-link-any-re (org-link-encode ref '(#x20)))
(setq ref (org-link-encode ref '(#x20)))
(let ((ref-url (url-generic-parse-url (or (match-string 2 ref) (match-string 0 ref))))
(link-type ()) ;; clear url-type for backward compatible.
(path ()))
(setq link-type (url-type ref-url))
(setf (url-type ref-url) nil)
(setq path (org-link-decode (url-recreate-url ref-url)))
(string-match org-link-plain-re ref)
(let ((link-type (match-string 1 ref))
(path (match-string 2 ref)))
(if (and (boundp 'org-ref-cite-types)
(or (assoc link-type org-ref-cite-types)
(member link-type org-ref-cite-types)))
@@ -500,18 +537,12 @@ INFO is the org-element parsed buffer."
"Insert link data for LINK at current point into the Org-roam cache."
(save-excursion
(goto-char (org-element-property :begin link))
(let* ((type (org-element-property :type link))
(let ((type (org-element-property :type link))
(path (org-element-property :path link))
(option (and (string-match "::\\(.*\\)\\'" path)
(match-string 1 path)))
(path (if (not option) path
(substring path 0 (match-beginning 0))))
(source (org-roam-id-at-point))
(properties (list :outline (ignore-errors
;; This can error if link is not under any headline
(org-get-outline-path 'with-self 'use-cache))))
(properties (if option (plist-put properties :search-option option)
properties)))
(org-get-outline-path 'with-self 'use-cache)))))
;; For Org-ref links, we need to split the path into the cite keys
(when (and source path)
(if (and (boundp 'org-ref-cite-types)
@@ -551,12 +582,19 @@ INFO is the org-element parsed buffer."
(puthash (car row) (cadr row) ht))
ht))
(defun org-roam-db--file-hash (file-path)
"Compute the hash of FILE-PATH."
(defun org-roam-db--file-hash (&optional file-path)
"Compute the hash of FILE-PATH, a file or current buffer."
;; If it is a GPG encrypted file, we always want to compute the hash
;; for the GPG encrypted file (undecrypted)
(when (and (not file-path) (equal "gpg" (file-name-extension (buffer-file-name))))
(setq file-path (buffer-file-name)))
(if file-path
(with-temp-buffer
(set-buffer-multibyte nil)
(insert-file-contents-literally file-path)
(secure-hash 'sha1 (current-buffer))))
(secure-hash 'sha1 (current-buffer)))
(org-with-wide-buffer
(secure-hash 'sha1 (current-buffer)))))
;;;; Synchronization
(defun org-roam-db-update-file (&optional file-path no-require)
@@ -579,12 +617,11 @@ in `org-roam-db-sync'."
(org-roam-require '(org-ref oc)))
(org-roam-with-file file-path nil
(emacsql-with-transaction (org-roam-db)
(org-with-wide-buffer
(save-excursion
(org-set-regexps-and-options 'tags-only)
;; Org doesn't use this anymore, so we probably should stop too.
;; (org-refresh-category-properties)
(org-refresh-category-properties)
(org-roam-db-clear-file)
(org-roam-db-insert-file content-hash)
(org-roam-db-insert-file)
(org-roam-db-insert-file-node)
(setq org-outline-path-cache nil)
(org-roam-db-map-nodes
@@ -655,14 +692,12 @@ database, see `org-roam-db-sync' command."
(add-hook 'kill-emacs-hook #'org-roam-db--close-all)
(advice-add #'rename-file :after #'org-roam-db-autosync--rename-file-a)
(advice-add #'delete-file :before #'org-roam-db-autosync--delete-file-a)
(advice-add #'vc-delete-file :around #'org-roam-db-autosync--vc-delete-file-a)
(org-roam-db-sync))
(t
(remove-hook 'find-file-hook #'org-roam-db-autosync--setup-file-h)
(remove-hook 'kill-emacs-hook #'org-roam-db--close-all)
(advice-remove #'rename-file #'org-roam-db-autosync--rename-file-a)
(advice-remove #'delete-file #'org-roam-db-autosync--delete-file-a)
(advice-remove #'vc-delete-file #'org-roam-db-autosync--vc-delete-file-a)
(org-roam-db--close-all)
;; Disable local hooks for all org-roam buffers
(dolist (buf (org-roam-buffer-list))
@@ -690,17 +725,6 @@ FILE is removed from the database."
(org-roam-file-p file))
(org-roam-db-clear-file (expand-file-name file))))
(defun org-roam-db-autosync--vc-delete-file-a (fun file)
"Maintain cache consistency on file deletion by FUN.
FILE is removed from the database."
(let ((org-roam-file-p (and (not (auto-save-file-name-p file))
(not (backup-file-name-p file))
(org-roam-file-p file))))
(apply fun `(,file))
(when (and org-roam-file-p
(not (file-exists-p file)))
(org-roam-db-clear-file (expand-file-name file)))))
(defun org-roam-db-autosync--rename-file-a (old-file new-file-or-dir &rest _args)
"Maintain cache consistency of file rename.
OLD-FILE is cleared from the database, and NEW-FILE-OR-DIR is added."

View File

@@ -1,94 +0,0 @@
;;; org-roam-id.el --- ID-related utilities for Org-roam -*- lexical-binding: t; -*-
;; Copyright © 2020-2025 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6") (magit-section "3.0.0"))
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;;
;; This module provides ID-related facilities using the Org-roam database.
;;
;;; Code:
(require 'org-id)
(defun org-roam-id-at-point ()
"Return the ID at point, if any.
Recursively traverses up the headline tree to find the
first encapsulating ID."
(org-with-wide-buffer
(org-back-to-heading-or-point-min t)
(while (and (not (org-roam-db-node-p))
(not (bobp)))
(org-roam-up-heading-or-point-min))
(when (org-roam-db-node-p)
(org-id-get))))
(defun org-roam-id-find (id &optional markerp)
"Return the location of the entry with the id ID using the Org-roam db.
The return value is a cons cell (file-name . position), or nil
if there is no entry with that ID.
With optional argument MARKERP, return the position as a new marker."
(cond
((symbolp id) (setq id (symbol-name id)))
((numberp id) (setq id (number-to-string id))))
(let ((node (org-roam-populate (org-roam-node-create :id id))))
(when-let ((file (org-roam-node-file node)))
(if markerp
(let ((buffer (or (find-buffer-visiting file)
(find-file-noselect file))))
(with-current-buffer buffer
(move-marker (make-marker) (org-roam-node-point node) buffer)))
(cons (org-roam-node-file node)
(org-roam-node-point node))))))
(defalias 'org-roam-id-open 'org-id-open
"Obsolete alias - use `org-id-open' directly.")
(advice-add 'org-id-find :before-until #'org-roam-id-find)
;;;###autoload
(defun org-roam-update-org-id-locations (&rest directories)
"Scan Org-roam files to update `org-id' related state.
This is like `org-id-update-id-locations', but will automatically
use the currently bound `org-directory' and `org-roam-directory'
along with DIRECTORIES (if any), where the lookup for files in
these directories will be always recursive.
Note: Org-roam doesn't have hard dependency on
`org-id-locations-file' to lookup IDs for nodes that are stored
in the database, but it still tries to properly integrates with
`org-id'. This allows the user to cross-reference IDs outside of
the current `org-roam-directory', and also link with \"id:\"
links to headings/files within the current `org-roam-directory'
that are excluded from identification in Org-roam as
`org-roam-node's, e.g. with \"ROAM_EXCLUDE\" property."
(interactive)
(cl-loop for dir in (cons org-roam-directory directories)
for org-roam-directory = dir
nconc (org-roam-list-files) into files
finally (org-id-update-id-locations files org-roam-verbose)))
(provide 'org-roam-id)
;;; org-roam-id.el ends here

View File

@@ -1,53 +0,0 @@
;;; org-roam-log.el --- Integrations with Org-log -*- coding: utf-8; lexical-binding: t; -*-
;; Copyright © 2022-2025 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6") (emacsql "4.1.0") (magit-section "3.0.0"))
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;;
;; This module provides integrations with Org-log.
;;
;;; Code:
(require 'org-roam)
(defcustom org-roam-log-setup-hook nil
"Hook run when a log for an Org-roam file is setup."
:group 'org-roam
:type 'hook)
(defun org-roam-log-p ()
"Return t if the log buffer is for an Org-roam file, nil otherwise."
(and org-log-note-marker
(org-roam-file-p (buffer-file-name (marker-buffer org-log-note-marker)))))
(defun org-roam-log--setup ()
"Run hooks in `org-roam-log-setup-hook'."
(run-hooks 'org-roam-log-setup-hook))
(add-hook 'org-roam-log-setup-hook #'org-roam--register-completion-functions-h)
(add-hook 'org-log-buffer-setup-hook #'org-roam-log--setup)
(provide 'org-roam-log)
;;; org-roam-log.el ends here

View File

@@ -1,12 +1,12 @@
;;; org-roam-migrate.el --- Migration utilities from v1 to v2 -*- coding: utf-8; lexical-binding: t; -*-
;; Copyright © 2020-2025 Jethro Kuan <jethrokuan95@gmail.com>
;; Copyright © 2020-2021 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6") (emacsql "4.1.0") (magit-section "3.0.0"))
;; Version: 2.2.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "3.0.0"))
;; This file is NOT part of GNU Emacs.

View File

@@ -1,12 +1,12 @@
;;; org-roam-mode.el --- Major mode for special Org-roam buffers -*- lexical-binding: t -*-
;; Copyright © 2020-2025 Jethro Kuan <jethrokuan95@gmail.com>
;; Copyright © 2020-2022 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6") (emacsql "4.1.0") (magit-section "3.0.0"))
;; Version: 2.2.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "3.0.0"))
;; This file is NOT part of GNU Emacs.
@@ -39,28 +39,14 @@
(defvar org-ref-buffer-hacked)
;;; Options
(defcustom org-roam-mode-sections (list #'org-roam-backlinks-section
(defcustom org-roam-mode-section-functions (list #'org-roam-backlinks-section
#'org-roam-reflinks-section)
"A list of sections for the `org-roam-mode' based buffers.
Each section is a function that is passed the `org-roam-node'
for which the section will be constructed as the first
argument. Normally this node is `org-roam-buffer-current-node'.
The function may also accept other optional arguments. Each item
in the list is either:
1. A function, which is called only with the `org-roam-node' as the argument
2. A list, containing the function and the optional arguments.
For example, one can add
(org-roam-backlinks-section :unique t)
to the list to pass :unique t to the section-rendering function."
"Functions that insert sections in the `org-roam-mode' based buffers.
Each function is called with one argument, which is an
`org-roam-node' for which the buffer will be constructed for.
Normally this node is `org-roam-buffer-current-node'."
:group 'org-roam
:type `(repeat (choice (symbol :tag "Function")
(list :tag "Function with arguments"
(symbol :tag "Function")
(repeat :tag "Arguments" :inline t (sexp :tag "Arg"))))))
:type 'hook)
(defcustom org-roam-buffer-postrender-functions (list)
"Functions to run after the Org-roam buffer is rendered.
@@ -182,7 +168,7 @@ This mode is used by special Org-roam buffers, such as persistent
`org-roam-buffer' and dedicated Org-roam buffers
\(`org-roam-buffer-display-dedicated'), which render the
information in a section-like manner (see
`org-roam-mode-sections'), with which the user can
`org-roam-mode-section-functions'), with which the user can
interact with."
:group 'org-roam
(face-remap-add-relative 'header-line 'org-roam-header-line))
@@ -242,14 +228,7 @@ buffer."
(org-roam-node-title org-roam-buffer-current-node))
(magit-insert-section (org-roam)
(magit-insert-heading)
(dolist (section org-roam-mode-sections)
(pcase section
((pred functionp)
(funcall section org-roam-buffer-current-node))
(`(,fn . ,args)
(apply fn (cons org-roam-buffer-current-node args)))
(_
(user-error "Invalid `org-roam-mode-sections' specification")))))
(run-hook-with-args 'org-roam-mode-section-functions org-roam-buffer-current-node))
(run-hooks 'org-roam-buffer-postrender-functions)
(goto-char 0)))
@@ -310,7 +289,7 @@ To toggle its display use `org-roam-buffer-toggle' command.")
(pcase (org-roam-buffer--visibility)
('visible
(progn
(quit-window nil (get-buffer-window org-roam-buffer))
(delete-window (get-buffer-window org-roam-buffer))
(remove-hook 'post-command-hook #'org-roam-buffer--redisplay-h)))
((or 'exists 'none)
(progn
@@ -319,7 +298,7 @@ To toggle its display use `org-roam-buffer-toggle' command.")
(define-inline org-roam-buffer--visibility ()
"Return the current visibility state of the persistent `org-roam-buffer'.
Valid states are `visible', `exists' and `none'."
Valid states are 'visible, 'exists and 'none."
(declare (side-effect-free t))
(inline-quote
(cond
@@ -339,7 +318,7 @@ Has no effect when there's no `org-roam-node-at-point'."
(add-hook 'kill-buffer-hook #'org-roam-buffer--persistent-cleanup-h nil t)))))
(defun org-roam-buffer--persistent-cleanup-h ()
"Clean-up global state that's dedicated for the persistent `org-roam-buffer'."
"Clean-up global state thats dedicated for the persistent `org-roam-buffer'."
(setq-default org-roam-buffer-current-node nil
org-roam-buffer-current-directory nil))
@@ -441,7 +420,7 @@ In interactive calls OTHER-WINDOW is set with
(with-current-buffer buf
(widen)
(goto-char point))
(when (org-invisible-p) (org-fold-show-context))
(when (org-invisible-p) (org-show-context))
buf))
(defun org-roam-preview-default-function ()
@@ -449,11 +428,9 @@ In interactive calls OTHER-WINDOW is set with
This function returns the all contents under the current
headline, up to the next headline."
(let ((beg (save-excursion
(org-roam-end-of-meta-data t)
(let ((beg (progn (org-roam-end-of-meta-data t)
(point)))
(end (save-excursion
(org-next-visible-heading 1)
(end (progn (org-next-visible-heading 1)
(point))))
(string-trim (buffer-substring-no-properties beg end))))
@@ -482,23 +459,14 @@ headline, up to the next headline."
(org-roam-populate (org-roam-backlink-target-node backlink)))
backlink)
(cl-defun org-roam-backlinks-get (node &key unique)
"Return the backlinks for NODE.
When UNIQUE is nil, show all positions where references are found.
When UNIQUE is t, limit to unique sources."
(let* ((sql (if unique
[:select :distinct [source dest pos properties]
:from links
:where (= dest $s1)
:and (= type "id")
:group :by source
:having (funcall min pos)]
(defun org-roam-backlinks-get (node)
"Return the backlinks for NODE."
(let ((backlinks (org-roam-db-query
[:select [source dest pos properties]
:from links
:where (= dest $s1)
:and (= type "id")]))
(backlinks (org-roam-db-query sql (org-roam-node-id node))))
:and (= type "id")]
(org-roam-node-id node))))
(cl-loop for backlink in backlinks
collect (pcase-let ((`(,source-id ,dest-id ,pos ,properties) backlink))
(org-roam-populate
@@ -514,28 +482,16 @@ Sorts by title."
(string< (org-roam-node-title (org-roam-backlink-source-node a))
(org-roam-node-title (org-roam-backlink-source-node b))))
(cl-defun org-roam-backlinks-section (node &key (unique nil) (show-backlink-p nil)
(section-heading "Backlinks:"))
"The backlinks section for NODE.
When UNIQUE is nil, show all positions where references are found.
When UNIQUE is t, limit to unique sources.
When SHOW-BACKLINK-P is not null, only show backlinks for which
this predicate is not nil.
SECTION-HEADING is the string used as a heading for the backlink section."
(when-let ((backlinks (seq-sort #'org-roam-backlinks-sort (org-roam-backlinks-get node :unique unique))))
(defun org-roam-backlinks-section (node)
"The backlinks section for NODE."
(when-let ((backlinks (seq-sort #'org-roam-backlinks-sort (org-roam-backlinks-get node))))
(magit-insert-section (org-roam-backlinks)
(magit-insert-heading section-heading)
(magit-insert-heading "Backlinks:")
(dolist (backlink backlinks)
(when (or (null show-backlink-p)
(and (not (null show-backlink-p))
(funcall show-backlink-p backlink)))
(org-roam-node-insert-section
:source-node (org-roam-backlink-source-node backlink)
:point (org-roam-backlink-point backlink)
:properties (org-roam-backlink-properties backlink))))
:properties (org-roam-backlink-properties backlink)))
(insert ?\n))))
;;;; Reflinks
@@ -630,7 +586,7 @@ instead."
(forward-line (1- row)))
(when col
(forward-char (1- col))))
(when (org-invisible-p) (org-fold-show-context))
(when (org-invisible-p) (org-show-context))
buf))
;;;; Unlinked references
@@ -658,42 +614,24 @@ This is the ROW within FILE."
(end-of-line)
(point)))))
(defun org-roam-unlinked-references--rg-command (titles temp-file)
"Return the ripgrep command searching for TITLES using TEMP-FILE for pattern.
This avoids shell escaping issues by writing the pattern to a file instead
of passing it directly through the shell command line."
;; Write pattern to temp file to avoid shell escaping issues with quotes,
;; spaces, and other special characters in titles
(with-temp-file temp-file
(insert "\\[([^[]]++|(?R))*\\]"
(mapconcat (lambda (title)
;; Use regexp-quote instead of shell-quote-argument
;; since we're writing a regex pattern, not a shell argument
(format "|(\\b%s\\b)" (regexp-quote title)))
titles "")))
(concat "rg --follow --only-matching --vimgrep --pcre2 --ignore-case "
(mapconcat (lambda (glob) (concat "--glob " glob))
(org-roam--list-files-search-globs org-roam-file-extensions)
" ")
" --file " (shell-quote-argument temp-file) " "
(shell-quote-argument (expand-file-name org-roam-directory))))
(defun org-roam-unlinked-references-section (node)
"The unlinked references section for NODE.
References from FILE are excluded."
(when (and (executable-find "rg")
(org-roam-node-title node)
(not (string-match "PCRE2 is not available"
(shell-command-to-string "rg --pcre2-version"))))
(let* ((titles (cons (org-roam-node-title node)
(org-roam-node-aliases node)))
;; Create temp file for the regex pattern
(temp-file (make-temp-file "org-roam-rg-pattern-"))
(rg-command (org-roam-unlinked-references--rg-command titles temp-file)))
;; Use unwind-protect to ensure temp file cleanup even if errors occur
(unwind-protect
(let* ((results (split-string (shell-command-to-string rg-command) "\n"))
(rg-command (concat "rg -o --vimgrep -P -i "
(mapconcat (lambda (glob) (concat "-g " glob))
(org-roam--list-files-search-globs org-roam-file-extensions)
" ")
(format " '\\[([^[]]++|(?R))*\\]%s' "
(mapconcat (lambda (title)
(format "|(\\b%s\\b)" (shell-quote-argument title)))
titles ""))
org-roam-directory))
(results (split-string (shell-command-to-string rg-command) "\n"))
f row col match)
(magit-insert-section (unlinked-references)
(magit-insert-heading "Unlinked References:")
@@ -718,9 +656,7 @@ References from FILE are excluded."
(org-roam-fontify-like-in-org-mode
(org-roam-unlinked-references-preview-line f row))
"\n"))))))
(insert ?\n)))
;; Clean up temp file - this runs even if an error occurs above
(delete-file temp-file)))))
(insert ?\n)))))
(provide 'org-roam-mode)
;;; org-roam-mode.el ends here

View File

@@ -1,12 +1,12 @@
;;; org-roam-node.el --- Interfacing and interacting with nodes -*- lexical-binding: t; -*-
;; Copyright © 2020-2025 Jethro Kuan <jethrokuan95@gmail.com>
;; Copyright © 2020-2021 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6") (magit-section "3.0.0"))
;; Version: 2.2.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.4") (magit-section "3.0.0"))
;; This file is NOT part of GNU Emacs.
@@ -32,23 +32,12 @@
;; interactively.
;;
;;; Code:
(require 'crm)
(require 'org-roam)
;;; Options
;;;; Completing-read
(defcustom org-roam-node-display-template "${title}"
"Configures display formatting for Org-roam node.
If it is a function, it will be called to format a node.
Its result is expected to be a string (potentially with
embedded properties).
If it is a string and it will be used as described in org-roam
(see org-roam-node-display-template)
When it is a string, the following processing is done:
Patterns of form \"${field-name:length}\" are interpolated based
on the current node.
@@ -74,23 +63,9 @@ as many characters as possible and will be aligned accordingly.
A closure can also be assigned to this variable in which case the
closure is evaluated and the return value is used as the
template. The closure must evaluate to a valid template string.
When org-roam-node-display-template is a function, the function is
expected to return a string, potentially propertized. For example, the
following function shows the title and base filename of the node:
\(defun my--org-roam-format (node)
\"formats the node\"
(format \"%-40s %s\"
(if (org-roam-node-title node)
(propertize (org-roam-node-title node) 'face 'org-todo)
\"\")
(file-name-nondirectory (org-roam-node-file node))))
\q(setq org-roam-node-display-template 'my--org-roam-format)"
template. The closure must evaluate to a valid template string."
:group 'org-roam
:type '(choice string function))
:type '(string function))
(defcustom org-roam-node-annotation-function #'org-roam-node-read--annotation
"This function used to attach annotations for `org-roam-node-read'.
@@ -114,7 +89,7 @@ argument, an `org-roam-node', and return a string.
If a string is provided, it is a template string expanded by
`org-roam-node--format-entry'."
:group 'org-roam
:type '(choice string function))
:type '(string function))
(defcustom org-roam-node-template-prefixes
'(("tags" . "#")
@@ -137,12 +112,6 @@ It takes a single argument REF, which is a propertized string."
:group 'org-roam
:type '(function))
(defcustom org-roam-ref-prompt-function nil
"Function to prompt for ref strings in `org-roam-ref-add'.
Should take no arguments, prompt the user, and return a string."
:group 'org-roam
:type 'function)
;;;; Completion-at-point
(defcustom org-roam-completion-everywhere nil
"When non-nil, provide link completion matching outside of Org links."
@@ -162,15 +131,10 @@ Should take no arguments, prompt the user, and return a string."
:type 'boolean)
(defcustom org-roam-extract-new-file-path "%<%Y%m%d%H%M%S>-${slug}.org"
"The file path template to use when a node is extracted to its own file.
This path is relative to `org-roam-directory'."
"The file path to use when a node is extracted to its own file."
:group 'org-roam
:type 'string)
(defvar org-roam-link-type "roam"
"Link type for org-roam nodes.
Replaced by `id' automatically when `org-roam-link-auto-replace' is non-nil.")
(defvar org-roam-node-history nil
"Minibuffer history of nodes.")
@@ -179,11 +143,6 @@ Replaced by `id' automatically when `org-roam-link-auto-replace' is non-nil.")
;;; Definition
(cl-defstruct (org-roam-node (:constructor org-roam-node-create)
(:constructor org-roam-node-create-from-db
(title aliases ; 2
id file file-title level todo ; 5
point priority scheduled deadline properties ;;5
olp file-atime file-mtime tags refs)) ;;5
(:copier nil))
"A heading or top level file with an assigned ID property."
file file-title file-hash file-atime file-mtime
@@ -215,7 +174,6 @@ Replaced by `id' automatically when `org-roam-link-auto-replace' is non-nil.")
776 ; U+0308 COMBINING DIAERESIS
777 ; U+0309 COMBINING HOOK ABOVE
778 ; U+030A COMBINING RING ABOVE
779 ; U+030B COMBINING DOUBLE ACUTE ACCENT
780 ; U+030C COMBINING CARON
795 ; U+031B COMBINING HORN
803 ; U+0323 COMBINING DOT BELOW
@@ -227,12 +185,14 @@ Replaced by `id' automatically when `org-roam-link-auto-replace' is non-nil.")
816 ; U+0330 COMBINING TILDE BELOW
817 ; U+0331 COMBINING MACRON BELOW
)))
(cl-flet* ((nonspacing-mark-p (char) (memq char slug-trim-chars))
(strip-nonspacing-marks (s) (string-glyph-compose
(apply #'string
(seq-remove #'nonspacing-mark-p
(cl-flet* ((nonspacing-mark-p (char)
(memq char slug-trim-chars))
(strip-nonspacing-marks (s)
(string-glyph-compose
(apply #'string (seq-remove #'nonspacing-mark-p
(string-glyph-decompose s)))))
(cl-replace (title pair) (replace-regexp-in-string (car pair) (cdr pair) title)))
(cl-replace (title pair)
(replace-regexp-in-string (car pair) (cdr pair) title)))
(let* ((pairs `(("[^[:alnum:][:digit:]]" . "_") ;; convert anything not alphanumeric
("__*" . "_") ;; remove sequential underscores
("^_" . "") ;; remove starting underscore
@@ -250,10 +210,6 @@ Replaced by `id' automatically when `org-roam-link-auto-replace' is non-nil.")
(_
(org-roam-node-title node))))
(cl-defmethod org-roam-node-category ((node org-roam-node))
"Return the category for NODE."
(cdr (assoc-string "CATEGORY" (org-roam-node-properties node))))
;;; Nodes
;;;; Getters
(defun org-roam-node-at-point (&optional assert)
@@ -268,12 +224,9 @@ populated."
(magit-section-up)
(org-roam-node-at-point)))
(t (org-with-wide-buffer
(while (not (or (org-roam-db-node-p)
(bobp)
(eq (funcall outline-level)
(save-excursion
(org-roam-up-heading-or-point-min)
(funcall outline-level)))))
(org-back-to-heading-or-point-min t)
(while (and (not (org-roam-db-node-p))
(not (bobp)))
(org-roam-up-heading-or-point-min))
(when-let ((id (org-id-get)))
(org-roam-populate
@@ -290,21 +243,17 @@ Return nil if a node with ID does not exist."
id)) 0)
(org-roam-populate (org-roam-node-create :id id))))
(defun org-roam-node-from-title-or-alias (s &optional nocase)
(defun org-roam-node-from-title-or-alias (s)
"Return an `org-roam-node' for the node with title or alias S.
Return nil if the node does not exist.
Throw an error if multiple choices exist.
If NOCASE is non-nil, the query is case insensitive. It is case sensitive otherwise."
Throw an error if multiple choices exist."
(let ((matches (seq-uniq
(append
(org-roam-db-query (vconcat [:select [id] :from nodes
(org-roam-db-query [:select [id] :from nodes
:where (= title $s1)]
(if nocase [ :collate NOCASE ]))
s)
(org-roam-db-query (vconcat [:select [node-id] :from aliases
(org-roam-db-query [:select [node-id] :from aliases
:where (= alias $s1)]
(if nocase [ :collate NOCASE ]))
s)))))
(cond
((seq-empty-p matches)
@@ -343,8 +292,7 @@ Return nil if there's no node with such REF."
Uses the ID, and fetches remaining details from the database.
This can be quite costly: avoid, unless dealing with very few
nodes."
(when-let ((node-info (car (org-roam-db-query [:select [
file level pos todo priority
(when-let ((node-info (car (org-roam-db-query [:select [file level pos todo priority
scheduled deadline title properties olp]
:from nodes
:where (= id $s1)
@@ -385,27 +333,23 @@ nodes."
(defun org-roam-node-list ()
"Return all nodes stored in the database as a list of `org-roam-node's."
(let ((rows (org-roam-db-query
"
SELECT
title,
aliases,
"SELECT
id,
file,
filetitle,
\"level\",
todo,
pos,
priority ,
scheduled ,
deadline ,
title,
properties ,
olp,
atime,
mtime,
'(' || group_concat(tags, ' ') || ')' as tags,
aliases,
refs
FROM
(
@@ -454,56 +398,46 @@ FROM
LEFT JOIN refs ON refs.node_id = nodes.id
GROUP BY nodes.id, tags.tag, aliases.alias )
GROUP BY id, tags )
GROUP BY id
")))
(mapcan
(lambda (row)
(let (
(all-titles (cons (car row) (nth 1 row)))
)
GROUP BY id")))
(cl-loop for row in rows
append (pcase-let* ((`(,id ,file ,file-title ,level ,todo ,pos ,priority ,scheduled ,deadline
,title ,properties ,olp ,atime ,mtime ,tags ,aliases ,refs)
row)
(all-titles (cons title aliases)))
(mapcar (lambda (temp-title)
(apply 'org-roam-node-create-from-db (cons temp-title (cdr row))))
all-titles)
))
rows)
)
)
(org-roam-node-create :id id
:file file
:file-title file-title
:file-atime atime
:file-mtime mtime
:level level
:point pos
:todo todo
:priority priority
:scheduled scheduled
:deadline deadline
:title temp-title
:aliases aliases
:properties properties
:olp olp
:tags tags
:refs refs))
all-titles)))))
;;;; Finders
(defun org-roam-node-marker (node)
"Get the marker for NODE."
(let* ((file (org-roam-node-file node))
(buffer (or (find-buffer-visiting file)
(find-file-noselect file))))
(with-current-buffer buffer
(move-marker (make-marker) (org-roam-node-point node) buffer))))
(defun org-roam-node-open (node &optional cmd force)
"Go to the node NODE.
CMD is the command used to display the buffer. If not provided,
`org-link-frame-setup' is respected. Assumes that the node is
fully populated, with file and point. If NODE is already visited,
this won't automatically move the point to the beginning of the
NODE, unless FORCE is non-nil."
(interactive (list (org-roam-node-at-point) current-prefix-arg))
(org-mark-ring-push)
(let ((m (org-roam-node-marker node))
(cmd (or cmd
(cdr
(assq
(cdr (assq 'file org-link-frame-setup))
'((find-file . switch-to-buffer)
(find-file-other-window . switch-to-buffer-other-window)
(find-file-other-frame . switch-to-buffer-other-frame))))
'switch-to-buffer-other-window)))
(if (not (equal (current-buffer) (marker-buffer m)))
(funcall cmd (marker-buffer m)))
(defun org-roam-node-find-noselect (node &optional force)
"Navigate to the point for NODE, and return the buffer.
If NODE is already visited, this won't automatically move the
point to the beginning of the NODE, unless FORCE is non-nil."
(unless (org-roam-node-file node)
(user-error "Node does not have corresponding file"))
(let ((buf (find-file-noselect (org-roam-node-file node))))
(with-current-buffer buf
(when (or force
(not (equal (org-roam-node-id node)
(org-roam-id-at-point))))
(goto-char m))
(move-marker m nil))
(org-fold-show-context))
(goto-char (org-roam-node-point node))))
buf))
(defun org-roam-node-visit (node &optional other-window force)
"From the current buffer, visit NODE. Return the visited buffer.
@@ -515,13 +449,16 @@ If NODE is already visited, this won't automatically move the
point to the beginning of the NODE, unless FORCE is non-nil. In
interactive calls FORCE always set to t."
(interactive (list (org-roam-node-at-point t) current-prefix-arg t))
(org-roam-node-open node (if other-window
(let ((buf (org-roam-node-find-noselect node force))
(display-buffer-fn (if other-window
#'switch-to-buffer-other-window
#'pop-to-buffer-same-window)
force))
#'pop-to-buffer-same-window)))
(funcall display-buffer-fn buf)
(when (org-invisible-p) (org-show-context))
buf))
;;;###autoload
(cl-defun org-roam-node-find (&optional other-window initial-input filter-fn pred &key templates)
(cl-defun org-roam-node-find (&optional other-window initial-input filter-fn &key templates)
"Find and open an Org-roam node by its title or alias.
INITIAL-INPUT is the initial input for the prompt.
FILTER-FN is a function to filter out nodes: it takes an `org-roam-node',
@@ -530,7 +467,7 @@ If OTHER-WINDOW, visit the NODE in another window.
The TEMPLATES, if provided, override the list of capture templates (see
`org-roam-capture-'.)"
(interactive current-prefix-arg)
(let ((node (org-roam-node-read initial-input filter-fn pred)))
(let ((node (org-roam-node-read initial-input filter-fn)))
(if (org-roam-node-file node)
(org-roam-node-visit node other-window)
(org-roam-capture-
@@ -539,7 +476,7 @@ The TEMPLATES, if provided, override the list of capture templates (see
:props '(:finalize find-file)))))
;;;###autoload
(defun org-roam-node-random (&optional other-window filter-fn)
(defun org-roam-node-random (filter-fn &optional other-window)
"Find and open a random Org-roam node.
With prefix argument OTHER-WINDOW, visit the node in another
window instead.
@@ -581,25 +518,6 @@ PROMPT is a string to show at the beginning of the mini-buffer, defaulting to \"
(or (cdr (assoc node nodes))
(org-roam-node-create :title node))))
(defun org-roam--format-nodes-using-template (nodes)
"Formats NODES using org-roam template features.
Uses org-roam--node-display-template."
(let (
(wTemplate (org-roam-node--process-display-format org-roam-node-display-template))
)
(mapcar (lambda (node)
(org-roam-node-read--to-candidate node wTemplate)) nodes))
)
(defun org-roam--format-nodes-using-function (nodes)
"Formats NODES using the function org-roam-node-display-template."
(mapcar (lambda (node)
(cons
(propertize (funcall org-roam-node-display-template node) 'node node)
node))
nodes)
)
(defun org-roam-node-read--completions (&optional filter-fn sort-fn)
"Return an alist for node completion.
The car is the displayed title or alias for the node, and the cdr
@@ -609,17 +527,15 @@ and when nil is returned the node will be filtered out.
SORT-FN is a function to sort nodes. See `org-roam-node-read-sort-by-file-mtime'
for an example sort function.
The displayed title is formatted according to `org-roam-node-display-template'."
(let* (
(let* ((template (org-roam-node--process-display-format org-roam-node-display-template))
(nodes (org-roam-node-list))
(nodes (mapcar (lambda (node)
(org-roam-node-read--to-candidate node template)) nodes))
(nodes (if filter-fn
(cl-remove-if-not
(lambda (n) (funcall filter-fn n))
(lambda (n) (funcall filter-fn (cdr n)))
nodes)
nodes))
(nodes (if (functionp org-roam-node-display-template)
(org-roam--format-nodes-using-function nodes)
(org-roam--format-nodes-using-template nodes)))
(sort-fn (or sort-fn
(when org-roam-node-default-sort
(intern (concat "org-roam-node-read-sort-by-"
@@ -753,12 +669,8 @@ The INFO, if provided, is passed to the underlying `org-roam-capture-'."
(delete-region beg end)
(set-marker beg nil)
(set-marker end nil))
(let ((id (org-roam-node-id node)))
(insert (org-link-make-string
(concat "id:" id)
description))
(run-hook-with-args 'org-roam-post-node-insert-hook
id
(concat "id:" (org-roam-node-id node))
description)))
(org-roam-capture-
:node node
@@ -767,12 +679,33 @@ The INFO, if provided, is passed to the underlying `org-roam-capture-'."
:props (append
(when (and beg end)
(list :region (cons beg end)))
(list :link-description description
(list :insert-at (point-marker)
:link-description description
:finalize 'insert-link))))))
(deactivate-mark)))
(add-hook 'org-roam-find-file-hook #'org-roam-open-id-with-org-roam-db-h)
(defun org-roam-open-id-with-org-roam-db-h ()
"Try to open \"id:\" links at point by querying them to the database."
(add-hook 'org-open-at-point-functions #'org-roam-open-id-at-point nil t))
(defun org-roam-open-id-at-point ()
"Navigate to \"id:\" link at point using the Org-roam database."
(when (org-in-regexp org-link-any-re)
(let ((link (match-string 2))
id)
(when (string-prefix-p "id:" link)
(setq id (substring-no-properties link 3))
(let ((node (org-roam-populate (org-roam-node-create :id id))))
(cond
((org-roam-node-file node)
(org-mark-ring-push)
(org-roam-node-visit node nil 'force)
t)
(t nil)))))))
;;;;; [roam:] link
(org-link-set-parameters org-roam-link-type :follow #'org-roam-link-follow-link)
(org-link-set-parameters "roam" :follow #'org-roam-link-follow-link)
(defun org-roam-link-follow-link (title-or-alias)
"Navigate \"roam:\" link to find and open the node with TITLE-OR-ALIAS.
Assumes that the cursor was put where the link is."
@@ -801,7 +734,7 @@ Assumes that the cursor was put where the link is."
node)
(goto-char (org-element-property :begin link))
(when (and (org-in-regexp org-link-any-re 1)
(string-equal type org-roam-link-type)
(string-equal type "roam")
(setq node (save-match-data (org-roam-node-from-title-or-alias path))))
(replace-match (org-link-make-string
(concat "id:" (org-roam-node-id node))
@@ -811,7 +744,7 @@ Assumes that the cursor was put where the link is."
"Replace all \"roam:\" links in buffer with \"id:\" links."
(interactive)
(org-with-point-at 1
(while (search-forward (concat "[[" org-roam-link-type ":") nil t)
(while (re-search-forward org-link-bracket-re nil t)
(org-roam-link-replace-at-point))))
(add-hook 'org-roam-find-file-hook #'org-roam--replace-roam-links-on-save-h)
@@ -831,8 +764,7 @@ We use this as a substitute for `org-link-bracket-re', because
"Complete \"roam:\" link at point to an existing Org-roam node."
(let (roam-p start end)
(when (org-in-regexp org-roam-bracket-completion-re 1)
(setq roam-p (not (or (org-in-src-block-p)
(string-blank-p (match-string 1))))
(setq roam-p (not (string-blank-p (match-string 1)))
start (match-beginning 2)
end (match-end 2))
(list start end
@@ -854,7 +786,6 @@ outside of the bracket syntax for links (i.e. \"[[roam:|]]\"),
hence \"everywhere\"."
(when (and org-roam-completion-everywhere
(thing-at-point 'word)
(not (org-in-src-block-p))
(not (save-match-data (org-in-regexp org-link-any-re))))
(let ((bounds (bounds-of-thing-at-point 'word)))
(list (car bounds) (cdr bounds)
@@ -869,8 +800,6 @@ hence \"everywhere\"."
:exclusive 'no))))
(add-hook 'org-roam-find-file-hook #'org-roam--register-completion-functions-h)
(add-hook 'org-roam-indirect-buffer-hook #'org-roam--register-completion-functions-h)
(defun org-roam--register-completion-functions-h ()
"Setup `org-roam-completion-functions' for `completion-at-point'."
(dolist (f org-roam-completion-functions)
@@ -888,8 +817,7 @@ Any tags declared on #+FILETAGS: are transferred to tags on the new top heading.
Any top level properties drawers are incorporated into the new heading."
(interactive)
(org-with-point-at 1
(org-map-region #'org-do-demote
(point-min) (point-max))
(org-map-entries 'org-do-demote)
(insert "* "
(org-roam--get-keyword "title")
"\n")
@@ -898,52 +826,31 @@ Any top level properties drawers are incorporated into the new heading."
(org-roam-erase-keyword "title")
(org-roam-erase-keyword "filetags")))
(defun org-roam--h1-count ()
"Count level-1 headings in the current file."
(let ((h1-count 0))
(org-with-wide-buffer
(org-map-region (lambda ()
(if (= (org-current-level) 1)
(cl-incf h1-count)))
(point-min) (point-max))
h1-count)))
(defun org-roam--buffer-promoteable-p ()
"Verify that this buffer is promoteable:
There is a single level-1 heading
and no extra content before the first heading."
(and
(= (org-roam--h1-count) 1)
(org-with-point-at 1 (org-at-heading-p))))
(defun org-roam-promote-entire-buffer ()
"Promote the current buffer.
Converts a file containing a single level-1 headline node to a file
Converts a file containing a headline node at the top to a file
node."
(interactive)
(unless (org-roam--buffer-promoteable-p)
(user-error "Cannot promote: multiple root headings or there is extra file-level text"))
(org-with-point-at 1
(org-map-entries (lambda ()
(when (> (org-outline-level) 1)
(org-do-promote))))
(let ((title (nth 4 (org-heading-components)))
(tags (org-get-tags)))
(org-fold-show-all)
(kill-whole-line)
(org-roam-end-of-meta-data t)
(insert "#+title: " title "\n")
(when tags (org-roam-tag-add tags))
(org-map-region #'org-promote (point-min) (point-max))
(org-roam-db-update-file))))
(tags (nth 5 (org-heading-components))))
(beginning-of-line)
(kill-line 1)
(org-roam-set-keyword "title" title)
(when tags (org-roam-set-keyword "filetags" tags)))))
;;;###autoload
(defun org-roam-refile (node)
"Refile node at point to an org-roam NODE.
(defun org-roam-refile ()
"Refile node at point to an Org-roam node.
If region is active, then use it instead of the node at point."
(interactive
(list (org-roam-node-read nil nil nil 'require-match)))
(interactive)
(let* ((regionp (org-region-active-p))
(region-start (and regionp (region-beginning)))
(region-end (and regionp (region-end)))
(node (org-roam-node-read nil nil nil 'require-match))
(file (org-roam-node-file node))
(nbuf (or (find-buffer-visiting file)
(find-file-noselect file)))
@@ -987,7 +894,7 @@ If region is active, then use it instead of the node at point."
(if (buffer-file-name)
(delete-file (buffer-file-name)))
(set-buffer-modified-p nil)
;; If this was done during capture, abort the capture process.
;; In this was done during capture, abort the capture process.
(when (and org-capture-mode
(buffer-base-buffer (current-buffer)))
(org-capture-kill))
@@ -1019,22 +926,53 @@ If region is active, then use it instead of the node at point."
(t (let ((r (read-from-minibuffer (format "%s: " key) default-val)))
(plist-put template-info ksym r)
r)))))))
(file-path
(expand-file-name
(read-file-name "Extract node to: "
(file-name-as-directory org-roam-directory) template nil template)
org-roam-directory)))
(file-path (read-file-name "Extract node to: "
(file-name-as-directory org-roam-directory) template nil template)))
(when (file-exists-p file-path)
(user-error "%s exists. Aborting" file-path))
(org-cut-subtree)
(save-buffer)
(with-current-buffer (find-file-noselect file-path)
(org-paste-subtree)
(while (> (org-current-level) 1) (org-promote-subtree))
(save-buffer)
(org-roam-promote-entire-buffer)
(save-buffer)))))
;;; IDs
;;;; Getters
(defun org-roam-id-at-point ()
"Return the ID at point, if any.
Recursively traverses up the headline tree to find the
first encapsulating ID."
(org-with-wide-buffer
(org-back-to-heading-or-point-min t)
(while (and (not (org-roam-db-node-p))
(not (bobp)))
(org-roam-up-heading-or-point-min))
(when (org-roam-db-node-p)
(org-id-get))))
;;;###autoload
(defun org-roam-update-org-id-locations (&rest directories)
"Scan Org-roam files to update `org-id' related state.
This is like `org-id-update-id-locations', but will automatically
use the currently bound `org-directory' and `org-roam-directory'
along with DIRECTORIES (if any), where the lookup for files in
these directories will be always recursive.
Note: Org-roam doesn't have hard dependency on
`org-id-locations-file' to lookup IDs for nodes that are stored
in the database, but it still tries to properly integrates with
`org-id'. This allows the user to cross-reference IDs outside of
the current `org-roam-directory', and also link with \"id:\"
links to headings/files within the current `org-roam-directory'
that are excluded from identification in Org-roam as
`org-roam-node's, e.g. with \"ROAM_EXCLUDE\" property."
(interactive)
(cl-loop for dir in (cons org-roam-directory directories)
for org-roam-directory = dir
nconc (org-roam-list-files) into files
finally (org-id-update-id-locations files org-roam-verbose)))
;;; Refs
;;;; Completing-read interface
(defun org-roam-ref-read (&optional initial-input filter-fn)
@@ -1071,9 +1009,7 @@ The car is the ref, and the cdr is the corresponding node for the ref."
:file file
:point pos
:title title)))
(cons
(concat (propertize ref 'node node 'type type)
(propertize id 'invisible t))
(cons (propertize ref 'node node 'type type)
node)))))
(defun org-roam-ref-read--annotation (ref)
@@ -1097,15 +1033,11 @@ and when nil is returned the node will be filtered out."
;;;; Editing
(defun org-roam-ref-add (ref)
"Add REF to the node at point."
(interactive `(,(if org-roam-ref-prompt-function
(funcall org-roam-ref-prompt-function)
(read-string "Ref: "))))
(interactive "sRef: ")
(let ((node (org-roam-node-at-point 'assert)))
(save-excursion
(goto-char (org-roam-node-point node))
(org-roam-property-add "ROAM_REFS" (if (member " " (string-to-list ref))
(concat "\"" ref "\"")
ref)))))
(org-roam-property-add "ROAM_REFS" ref))))
(defun org-roam-ref-remove (&optional ref)
"Remove a REF from the node at point."
@@ -1133,8 +1065,7 @@ and when nil is returned the node will be filtered out."
(defun org-roam-tag-add (tags)
"Add TAGS to the node at point."
(interactive
(list (let ((crm-separator "[ ]*:[ ]*"))
(completing-read-multiple "Tag: " (org-roam-tag-completions)))))
(list (completing-read-multiple "Tag: " (org-roam-tag-completions))))
(let ((node (org-roam-node-at-point 'assert)))
(save-excursion
(goto-char (org-roam-node-point node))

View File

@@ -1,12 +1,12 @@
;;; org-roam-utils.el --- Utilities for Org-roam -*- lexical-binding: t; -*-
;; Copyright © 2020-2025 Jethro Kuan <jethrokuan95@gmail.com>
;; Copyright © 2020-2022 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6"))
;; Version: 2.2.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.4"))
;; This file is NOT part of GNU Emacs.
@@ -69,14 +69,6 @@ Like `string-equal', but case-insensitive."
(or (string-equal s1 s2)
(string-equal (downcase s1) (downcase s2)))))
(defun org-roam-whitespace-content (s)
"Return the whitespace content at the end of S."
(with-temp-buffer
(insert s)
(skip-chars-backward " \t\n")
(buffer-substring-no-properties
(point) (point-max))))
(defun org-roam-strip-comments (s)
"Strip Org comments from string S."
(with-temp-buffer
@@ -84,8 +76,7 @@ Like `string-equal', but case-insensitive."
(goto-char (point-min))
(while (not (eobp))
(if (org-at-comment-p)
(delete-region (line-beginning-position)
(progn (forward-line) (point)))
(delete-region (point-at-bol) (progn (forward-line) (point)))
(forward-line)))
(buffer-string)))
@@ -120,9 +111,9 @@ SPEC is a list, as per `dolist'."
;;; File utilities
(defun org-roam-descendant-of-p (a b)
"Return t if A is descendant of B."
(unless (and a b (equal (file-truename a) (file-truename b)))
(string-prefix-p (replace-regexp-in-string "^\\([A-Za-z]\\):" 'downcase (expand-file-name b) t t)
(replace-regexp-in-string "^\\([A-Za-z]\\):" 'downcase (expand-file-name a) t t))))
(unless (equal (file-truename a) (file-truename b))
(string-prefix-p (expand-file-name b)
(expand-file-name a))))
(defmacro org-roam-with-file (file keep-buf-p &rest body)
"Execute BODY within FILE.
@@ -163,8 +154,7 @@ If FILE, set `default-directory' to FILE's directory and insert its contents."
(let ((current-org-roam-directory (make-symbol "current-org-roam-directory")))
`(let ((,current-org-roam-directory org-roam-directory))
(with-temp-buffer
(let ((org-roam-directory ,current-org-roam-directory)
(org-inhibit-startup t))
(let ((org-roam-directory ,current-org-roam-directory))
(delay-mode-hooks (org-mode))
(when ,file
(insert-file-contents ,file)
@@ -230,10 +220,36 @@ Like `org-fontify-like-in-org-mode', but supports `org-ref'."
(insert s)
(let ((org-ref-buffer-hacked t))
(org-mode)
(setq-local org-fold-core-style 'overlays)
(font-lock-ensure)
(org-font-lock-ensure)
(buffer-string))))
;;;; Shielding regions
(defface org-roam-shielded
'((t :inherit (warning)))
"Face for regions that are shielded (marked as read-only).
This face is used on the region target by org-roam-insertion
during an `org-roam-capture'."
:group 'org-roam-faces)
(defun org-roam-shield-region (beg end)
"Shield region against modifications.
BEG and END are markers for the beginning and end regions.
REGION must be a cons-cell containing the marker to the region
beginning and maximum values."
(add-text-properties beg end
'(font-lock-face org-roam-shielded
read-only t)
(marker-buffer beg)))
(defun org-roam-unshield-region (beg end)
"Unshield the shielded REGION.
BEG and END are markers for the beginning and end regions."
(let ((inhibit-read-only t))
(remove-text-properties beg end
'(font-lock-face org-roam-shielded
read-only t)
(marker-buffer beg))))
;;; Org-mode utilities
;;;; Motions
(defun org-roam-up-heading-or-point-min ()
@@ -339,14 +355,26 @@ If the property is already set, it's value is replaced."
(defun org-roam-add-property (val prop)
"Add VAL value to PROP property for the node at point.
Both, VAL and PROP are strings."
(org-roam-property-add prop val))
(let* ((p (org-entry-get (point) prop))
(lst (when p (split-string-and-unquote p)))
(lst (if (memq val lst) lst (cons val lst)))
(lst (seq-uniq lst)))
(org-set-property prop (combine-and-quote-strings lst))
val))
(defun org-roam-remove-property (prop &optional val)
"Remove VAL value from PROP property for the node at point.
Both VAL and PROP are strings.
If VAL is not specified, user is prompted to select a value."
(org-roam-property-remove prop val))
(let* ((p (org-entry-get (point) prop))
(lst (when p (split-string-and-unquote p)))
(prop-to-remove (or val (completing-read "Remove: " lst)))
(lst (delete prop-to-remove lst)))
(if lst
(org-set-property prop (combine-and-quote-strings lst))
(org-delete-property prop))
prop-to-remove))
(defun org-roam-property-add (prop val)
"Add VAL value to PROP property for the node at point.
@@ -449,11 +477,8 @@ See <https://github.com/raxod502/straight.el/issues/520>."
'("Doom" "Spacemacs" "N/A" "I don't know"))
(quit "N/A"))))
(insert (format "- Org: %s\n" (org-version nil 'full)))
(insert (format "- Org-roam: %s" (org-roam-version)))
(insert (format "- sqlite-connector: %s"
(if-let ((conn (org-roam-db--get-connection)))
(eieio-object-class conn)
"not connected")))))
(insert (format "- Org-roam: %s" (org-roam-version)))))
(provide 'org-roam-utils)
;;; org-roam-utils.el ends here

View File

@@ -1,12 +1,12 @@
;;; org-roam.el --- A database abstraction layer for Org-mode -*- coding: utf-8; lexical-binding: t; -*-
;; Copyright © 2020-2025 Jethro Kuan <jethrokuan95@gmail.com>
;; Copyright © 2020-2021 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6") (emacsql "4.1.0") (magit-section "3.0.0"))
;; Version: 2.2.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "3.0.0"))
;; This file is NOT part of GNU Emacs.
@@ -80,13 +80,9 @@
(require 'magit-section)
(require 'emacsql)
;; REVIEW: is this require needed?
;; emacsql-sqlite provides a common interface to an emacsql SQLite backend (e.g. emacs-sqlite-builtin)
;; not to be confused with a backend itself named emacsql-sqlite that existed in emacsql < 4.0.
(require 'emacsql-sqlite)
(require 'org)
(require 'org-attach) ; To set `org-attach-id-dir'
(require 'org-id)
(require 'ol)
(require 'org-element)
@@ -126,12 +122,6 @@ All Org files, at any level of nesting, are considered part of the Org-roam."
:group 'org-roam
:type 'hook)
(defcustom org-roam-post-node-insert-hook nil
"Hook run when an Org-roam node is inserted as an Org link.
Each function takes two arguments: the id of the node, and the link description."
:group 'org-roam
:type 'hook)
(defcustom org-roam-file-extensions '("org")
"List of file extensions to be included by Org-Roam.
While a file extension different from \".org\" may be used, the
@@ -140,11 +130,9 @@ responsibility to ensure that."
:type '(repeat string)
:group 'org-roam)
(defcustom org-roam-file-exclude-regexp (list org-attach-id-dir)
"Files matching this regular expression or list of regular expressions are excluded from the Org-roam."
(defcustom org-roam-file-exclude-regexp nil
"Files matching this regular expression are excluded from the Org-roam."
:type '(choice
(repeat
(string :tag "Regular expression matching files to ignore"))
(string :tag "Regular expression matching files to ignore")
(const :tag "Include everything" nil))
:group 'org-roam)
@@ -155,42 +143,32 @@ responsibility to ensure that."
'(find fd fdfind rg))
"Commands that will be used to find Org-roam files.
It should be a list of symbols or cons cells representing any of
the following supported file search methods.
It should be a list of symbols or cons cells representing any of the following
supported file search methods.
The commands will be tried in order until an executable for a
command is found. The Elisp implementation is used if no command
in the list is found.
The commands will be tried in order until an executable for a command is found.
The Elisp implementation is used if no command in the list is found.
`find'
Use find as the file search method.
Example command:
find /path/to/dir -type f \
\( -name \"*.org\" -o -name \"*.org.gpg\" -name \"*.org.age\" \)
find /path/to/dir -type f \( -name \"*.org\" -o -name \"*.org.gpg\" \)
`fd'
Use fd as the file search method.
Example command:
fd /path/to/dir/ --type file -e \".org\" -e \".org.gpg\" -e \".org.age\"
Example command: fd /path/to/dir/ --type file -e \".org\" -e \".org.gpg\"
`fdfind'
Same as `fd'. It's an alias that used in some OSes (e.g. Debian, Ubuntu)
`rg'
Use ripgrep as the file search method.
Example command:
rg /path/to/dir/ --files -g \"*.org\" -g \"*.org.gpg\" -g \"*.org.age\"
Example command: rg /path/to/dir/ --files -g \"*.org\" -g \"*.org.gpg\"
By default, `executable-find' will be used to look up the path to
the executable. If a custom path is required, it can be specified
together with the method symbol as a cons cell. For example:
\\='(find (rg . \"/path/to/rg\"))."
:type '(set
(const :tag "find" find)
By default, `executable-find' will be used to look up the path to the
executable. If a custom path is required, it can be specified together with the
method symbol as a cons cell. For example: '(find (rg . \"/path/to/rg\"))."
:type '(set (const :tag "find" find)
(const :tag "fd" fd)
(const :tag "fdfind" fdfind)
(const :tag "rg" rg)
@@ -205,34 +183,19 @@ FILE is an Org-roam file if:
- It's located somewhere under `org-roam-directory'
- It has a matching file extension (`org-roam-file-extensions')
- It doesn't match excluded regexp (`org-roam-file-exclude-regexp')"
(when (or file (buffer-file-name (buffer-base-buffer)))
(let* ((path (or file (buffer-file-name (buffer-base-buffer))))
(relative-path (file-relative-name path org-roam-directory))
(ext (org-roam--file-name-extension path))
(ext (if (or (string= ext "gpg")
(string= ext "age"))
(ext (when path (org-roam--file-name-extension path)))
(ext (if (string= ext "gpg")
(org-roam--file-name-extension (file-name-sans-extension path))
ext))
(org-roam-dir-p (org-roam-descendant-of-p path org-roam-directory))
(valid-file-ext-p (member ext org-roam-file-extensions))
(match-exclude-regexp-p
(cond
((not org-roam-file-exclude-regexp) nil)
((stringp org-roam-file-exclude-regexp)
(string-match-p org-roam-file-exclude-regexp relative-path))
((listp org-roam-file-exclude-regexp)
(let (is-match)
(dolist (exclude-re org-roam-file-exclude-regexp)
(setq is-match (or is-match (string-match-p exclude-re relative-path))))
is-match)))))
ext)))
(save-match-data
(and
path
org-roam-dir-p
valid-file-ext-p
(not match-exclude-regexp-p))))))
(member ext org-roam-file-extensions)
(not (and org-roam-file-exclude-regexp
(string-match-p org-roam-file-exclude-regexp path)))
(org-roam-descendant-of-p path (expand-file-name org-roam-directory))))))
;;;###autoload
(defun org-roam-list-files ()
"Return a list of all Org-roam files under `org-roam-directory'.
See `org-roam-file-p' for how each file is determined to be as
@@ -297,15 +260,14 @@ If no files are found, an empty list is returned."
(ansi-color-filter-apply it)
(split-string it "\n")
(seq-filter (lambda (s)
(not (or (null s) (string= "" s)))) it)))
(or (null s) (string= "" s))) it)))
(defun org-roam--list-files-search-globs (exts)
"Given EXTS, return a list of search globs.
E.g. (\".org\") => (\"*.org\" \"*.org.gpg\")"
(cl-loop for e in exts
append (list (format "\"*.%s\"" e)
(format "\"*.%s.gpg\"" e)
(format "\"*.%s.age\"" e))))
(format "\"*.%s.gpg\"" e))))
(defun org-roam--list-files-find (executable dir)
"Return all Org-roam files under DIR, using \"find\", provided as EXECUTABLE."
@@ -326,8 +288,7 @@ E.g. (\".org\") => (\"*.org\" \"*.org.gpg\")"
(defun org-roam--list-files-rg (executable dir)
"Return all Org-roam files under DIR, using \"rg\", provided as EXECUTABLE."
(let* ((globs (org-roam--list-files-search-globs org-roam-file-extensions))
(command (string-join `(
,executable "-L" ,dir "--files"
(command (string-join `(,executable "-L" ,dir "--files"
,@(mapcar (lambda (glob) (concat "-g " glob)) globs)) " ")))
(org-roam--shell-command-files command)))
@@ -337,7 +298,7 @@ E.g. (\".org\") => (\"*.org\" \"*.org.gpg\")"
"Return all Org-roam files under DIR, using Elisp based implementation."
(let ((regex (concat "\\.\\(?:"(mapconcat
#'regexp-quote org-roam-file-extensions
"\\|" )"\\)\\(?:\\.gpg\\|\\.age\\)?\\'"))
"\\|" )"\\)\\(?:\\.gpg\\)?\\'"))
result)
(dolist (file (org-roam--directory-files-recursively dir regex nil nil t) result)
(when (and (file-readable-p file)
@@ -352,10 +313,8 @@ E.g. (\".org\") => (\"*.org\" \"*.org.gpg\")"
(require 'org-roam-utils)
(require 'org-roam-db)
(require 'org-roam-node)
(require 'org-roam-id)
(require 'org-roam-capture)
(require 'org-roam-mode)
(require 'org-roam-log)
(require 'org-roam-migrate))
;;; org-roam.el ends here

View File

@@ -1,8 +0,0 @@
:PROPERTIES:
:ID: 97bf31cf-dfee-45d8-87a5-2ae0dabc4734
:END:
#+title: Demoteable
* Demoteable h1
** Demoteable child

View File

@@ -1,19 +0,0 @@
:PROPERTIES:
:ID: 998b2341-b7fe-434d-848c-5282c0727870
:END:
#+title: Family
* Grand-Parent
:PROPERTIES:
:ID: 77a90980-1994-464e-901f-7e3d3df07fd3
:END:
** Parent
:PROPERTIES:
:ID: 0fa5bb3e-3d8c-4966-8bc9-78d32e505d69
:END:
*** Child
:PROPERTIES:
:ID: 5fb4fdc5-b6d2-4f75-8d54-e60053e467ec
:END:

View File

@@ -1,3 +0,0 @@
* Promoteable h1
** Promoteable child

View File

@@ -1,7 +0,0 @@
:PROPERTIES:
:ROAM_REFS: "http://site.net/docs/01. introduction - hello world.html"
:ID: 5b9a7400-f59c-4ef9-acbb-045b69af98f1
:END:
#+title: ref with space
* Note
hello

View File

@@ -1,5 +0,0 @@
:PROPERTIES:
:ID: 57ff3ce7-5bda-4825-8fca-c09f523e87ba
:ROAM_ALIASES: Batman
:END:
#+title: Bruce Wayne

View File

@@ -1,16 +0,0 @@
:PROPERTIES:
:ID: 9a20ca6c-5555-41c9-a039-ac38bf59c7a9
:END:
#+title: With Times
* Scheduled heading
SCHEDULED: <2024-07-16 Tue>
:PROPERTIES:
:ID: a523c198-4cb4-44d2-909c-a0e3258089cd
:END:
* Deadline heading
DEADLINE: <2024-07-17 Tue>
:PROPERTIES:
:ID: 3ab84701-d1c1-463f-b5c6-715e6ff5a0bf
:END:

View File

@@ -1,63 +0,0 @@
;;; test-org-roam-capture.el --- Tests for Org-roam -*- lexical-binding: t; -*-
;; Copyright (C) 2020 Jethro Kuan
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; Package-Requires: ((buttercup))
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'buttercup)
(require 'org-roam)
(describe "org-roam-capture--fill-template"
(it "fills template without newline"
(expect
(org-roam-capture--fill-template "foo")
:to-equal "foo"))
(it "fills template ensuring newline"
(expect
(org-roam-capture--fill-template "foo" 'ensure-newline)
:to-equal "foo\n"))
(it "fills template with newline"
(expect
(org-roam-capture--fill-template "foo\n")
:to-equal "foo\n"))
(it "fills template with two newlines"
(expect
(org-roam-capture--fill-template "foo\n\n")
:to-equal "foo\n\n")
(expect
(org-roam-capture--fill-template "foo\n\t\n")
:to-equal "foo\n\t\n"))
(it "fills template without deleting newlines in its body"
(expect
(org-roam-capture--fill-template "foo\n\n\nbar\n\n")
:to-equal "foo\n\n\nbar\n\n"))
(it "expands templates when it's a function"
(expect
(org-roam-capture--fill-template (lambda () "foo"))
:to-equal "foo")))
(provide 'test-org-roam-capture)
;;; test-org-roam-capture.el ends here

View File

@@ -1,120 +0,0 @@
;;; test-org-roam-db.el --- Tests for Org-roam -*- lexical-binding: t; -*-
;; Copyright (C) 2020 Jethro Kuan
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; Package-Requires: ((buttercup))
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'buttercup)
(require 'org-roam)
(defvar root-directory default-directory)
(describe "org-roam-db-get-scheduled-time"
(before-all
(setq org-roam-directory (expand-file-name "tests/roam-files")
org-roam-db-location (expand-file-name "org-roam.db" temporary-file-directory)
org-roam-file-extensions '("org")
org-roam-file-exclude-regexp nil)
(org-roam-db-sync))
(after-all
(org-roam-db--close)
(delete-file org-roam-db-location)
(cd root-directory))
(it "should get scheduled time for current heading node"
(org-roam-id-open "a523c198-4cb4-44d2-909c-a0e3258089cd" nil)
(expect (org-roam-db-get-scheduled-time) :to-equal "2024-07-16T00:00:00")))
(describe "org-roam-db-get-deadline-time"
(before-all
(setq org-roam-directory (expand-file-name "tests/roam-files")
org-roam-db-location (expand-file-name "org-roam.db" temporary-file-directory)
org-roam-file-extensions '("org")
org-roam-file-exclude-regexp nil)
(org-roam-db-sync))
(after-all
(org-roam-db--close)
(delete-file org-roam-db-location)
(cd root-directory))
(it "should get deadline time for current heading node"
(org-roam-id-open "3ab84701-d1c1-463f-b5c6-715e6ff5a0bf" nil)
(expect (org-roam-db-get-deadline-time) :to-equal "2024-07-17T00:00:00")))
(describe "org-roam-db--file-hash"
(it "computes the SHA1 of file"
(expect (org-roam-db--file-hash "tests/roam-files/family.org")
:to-equal
"c4ebf8918c1533df72e4d182cbf1bbd90f776b3b")))
(describe "org-roam-db-sync"
(before-all
(setq org-roam-directory (expand-file-name "tests/roam-files")
org-roam-db-location (expand-file-name "org-roam.db" temporary-file-directory)
org-roam-file-extensions '("org")
org-roam-file-exclude-regexp nil)
(org-roam-db-sync))
(after-all
(org-roam-db--close)
(delete-file org-roam-db-location))
(it "has the correct number of files"
(expect (caar (org-roam-db-query [:select (funcall count) :from files]))
:to-equal
9))
(it "has the correct number of nodes"
(expect (caar (org-roam-db-query [:select (funcall count) :from nodes]))
:to-equal
12))
(it "has the correct number of links"
(expect (caar (org-roam-db-query [:select (funcall count) :from links]))
:to-equal
1))
(it "respects ROAM_EXCLUDE"
;; The excluded node has ID "53fadc75-f48e-461e-be06-44a1e88b2abe"
(expect (mapcar #'car (org-roam-db-query [:select id :from nodes]))
:to-have-same-items-as
'("884b2341-b7fe-434d-848c-5282c0727861"
"440795d0-70c1-4165-993d-aebd5eef7a24"
"5b9a7400-f59c-4ef9-acbb-045b69af98f1"
"0fa5bb3e-3d8c-4966-8bc9-78d32e505d69"
"5fb4fdc5-b6d2-4f75-8d54-e60053e467ec"
"77a90980-1994-464e-901f-7e3d3df07fd3"
"57ff3ce7-5bda-4825-8fca-c09f523e87ba"
"998b2341-b7fe-434d-848c-5282c0727870"
"a523c198-4cb4-44d2-909c-a0e3258089cd"
"3ab84701-d1c1-463f-b5c6-715e6ff5a0bf"
"9a20ca6c-5555-41c9-a039-ac38bf59c7a9"
"97bf31cf-dfee-45d8-87a5-2ae0dabc4734")))
(it "reads ref in quotes correctly"
(expect (mapcar #'car (org-roam-db-query [:select [ref] :from refs]))
:to-have-same-items-as
'("//site.net/docs/01. introduction - hello world.html"))))
(provide 'test-org-roam-db)
;;; test-org-roam-db.el ends here

View File

@@ -1,79 +0,0 @@
;;; test-org-roam-id.el --- Tests for Org-roam -*- lexical-binding: t; -*-
;; Copyright (C) 2020 Jethro Kuan
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; Package-Requires: ((buttercup))
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'buttercup)
(require 'org-roam)
(defvar root-directory default-directory)
(describe "org-roam-id-at-point"
(before-all
(setq org-roam-directory (expand-file-name "tests/roam-files")
org-roam-db-location (expand-file-name "org-roam.db" temporary-file-directory)
org-roam-file-extensions '("org")
org-roam-file-exclude-regexp nil)
(org-roam-db-sync))
(after-all
(org-roam-db--close)
(delete-file org-roam-db-location)
(cd root-directory))
(it "returns the correct node ids"
(find-file "tests/roam-files/family.org" nil)
(expect (org-roam-id-at-point) :to-equal "998b2341-b7fe-434d-848c-5282c0727870")
(search-forward "Grand")
(expect (org-roam-id-at-point) :to-equal "77a90980-1994-464e-901f-7e3d3df07fd3")
(search-forward "Child")
(expect (org-roam-id-at-point) :to-equal "5fb4fdc5-b6d2-4f75-8d54-e60053e467ec")))
(describe "org-roam-id-find"
(before-all
(setq org-roam-directory (expand-file-name "tests/roam-files")
org-roam-db-location (expand-file-name "org-roam.db" temporary-file-directory)
org-roam-file-extensions '("org")
org-roam-file-exclude-regexp nil)
(org-roam-db-sync))
(after-all
(org-roam-db--close)
(delete-file org-roam-db-location))
(it "finds nothing for non-existing node"
(expect (org-roam-id-find "non-existing") :to-equal nil))
(it "finds the correct file node"
(let ((location (org-roam-id-find "884b2341-b7fe-434d-848c-5282c0727861")))
(expect (car location) :to-match ".*/tests/roam-files/foo.org")
(expect (cdr location) :to-equal 1)))
(it "finds the correct heading node"
(let ((location (org-roam-id-find "0fa5bb3e-3d8c-4966-8bc9-78d32e505d69")))
(expect (car location) :to-match ".*/tests/roam-files/family.org")
(expect (cdr location) :to-equal 156))))
(provide 'test-org-roam-id)
;;; test-org-roam-id.el ends here

View File

@@ -1,56 +0,0 @@
;;; test-org-roam-link-replace.el --- Tests for Org-roam link replacement -*- lexical-binding: t; -*-
;; Copyright (C) 2025
;; Package-Requires: ((buttercup))
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; Tests for commits fc86387 and 89dfaef - link replacement optimization
;;; Code:
(require 'buttercup)
(require 'org-roam)
(require 'org-roam-node)
(describe "org-roam-link-replace-all optimization"
(before-all
(setq org-roam-directory (expand-file-name "tests/roam-files")
org-roam-db-location (expand-file-name "org-roam.db" temporary-file-directory))
(org-roam-db-sync))
(after-all
(org-roam-db--close)
(delete-file org-roam-db-location))
(it "only processes roam: links, not other bracket links"
(with-temp-buffer
(org-mode)
(insert "[[file:test.org][File]]\n[[roam:Foo]]\n[[https://example.com][Web]]")
(let ((replace-count 0)
(original-fn (symbol-function 'org-roam-link-replace-at-point)))
;; Wrap the original function to count calls
(cl-letf (((symbol-function 'org-roam-link-replace-at-point)
(lambda ()
(cl-incf replace-count)
(funcall original-fn))))
(org-roam-link-replace-all)
;; Should only be called once, for the roam: link
(expect replace-count :to-equal 1)
(expect (buffer-string) :to-match "\\[\\[id:.*\\]\\[Foo\\]\\]"))))))
(provide 'test-org-roam-link-replace)
;;; test-org-roam-link-replace.el ends here

View File

@@ -1,39 +0,0 @@
;;; test-org-roam-mode.el --- Tests for Org-roam -*- lexical-binding: t; -*-
;; Copyright (C) 2020 Jethro Kuan
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; Package-Requires: ((buttercup))
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'buttercup)
(require 'org-roam)
(describe "org-roam-unlinked-references--rg-command"
(before-each
;; the space in the directory is on purpose
(setq org-roam-directory "/tmp/org roam"))
(it "returns the correct rg command for unlinked references"
(expect (org-roam-unlinked-references--rg-command '("foo" "bar") "/tmp/regex")
:to-equal
"rg --follow --only-matching --vimgrep --pcre2 --ignore-case --glob \"*.org\" --glob \"*.org.gpg\" --glob \"*.org.age\" --file /tmp/regex /tmp/org\\ roam")))
(provide 'test-org-roam-mode)
;;; test-org-roam-mode.el ends here

View File

@@ -1,166 +0,0 @@
;;; test-org-roam-node.el --- Tests for Org-roam -*- lexical-binding: t; -*-
;; Copyright (C) 2020 Jethro Kuan
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; Package-Requires: ((buttercup))
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'buttercup)
(require 'org-roam)
(defvar root-directory default-directory)
(describe "org-roam-node-from-id"
(before-all
(setq org-roam-directory (expand-file-name "tests/roam-files")
org-roam-db-location (expand-file-name "org-roam.db" temporary-file-directory)
org-roam-file-extensions '("org")
org-roam-file-exclude-regexp nil)
(org-roam-db-sync))
(after-all
(org-roam-db--close)
(delete-file org-roam-db-location))
(it "returns nil for unknown id"
(expect (org-roam-node-from-id "non-existing") :to-equal nil))
(it "returns correct node from id"
(let ((node (org-roam-node-from-id "884b2341-b7fe-434d-848c-5282c0727861")))
(expect (org-roam-node-title node) :to-equal "Foo"))))
(describe "org-roam-node-from-title-or-alias"
(before-all
(setq org-roam-directory (expand-file-name "tests/roam-files")
org-roam-db-location (expand-file-name "org-roam.db" temporary-file-directory)
org-roam-file-extensions '("org")
org-roam-file-exclude-regexp nil)
(org-roam-db-sync))
(after-all
(org-roam-db--close)
(delete-file org-roam-db-location))
(it "returns nil for unknown title"
(expect (org-roam-node-from-title-or-alias "non-existing") :to-equal nil))
(it "returns correct node from title"
(let ((node (org-roam-node-from-title-or-alias "Foo")))
(expect (org-roam-node-title node) :to-equal "Foo")))
(it "returns correct node from alias"
(let ((node (org-roam-node-from-title-or-alias "Batman")))
(expect (org-roam-node-title node) :to-equal "Bruce Wayne")))
(it "returns correct node from alias with nocase"
(let ((node (org-roam-node-from-title-or-alias "batman" t)))
(expect (org-roam-node-title node) :to-equal "Bruce Wayne"))))
(describe "org-roam-demote-entire-buffer"
(it "demotes an entire org buffer"
;; Use a temporary copy to avoid interfering with other tests
(let ((temp-file (make-temp-file "test-demoteable-" nil ".org")))
(copy-file "tests/roam-files/demoteable.org" temp-file t)
(find-file temp-file)
(org-roam-demote-entire-buffer)
(expect (buffer-substring-no-properties (point) (point-max))
:to-equal "* Demoteable\n:PROPERTIES:\n:ID: 97bf31cf-dfee-45d8-87a5-2ae0dabc4734\n:END:\n\n** Demoteable h1\n\n*** Demoteable child\n")
(kill-buffer)
(delete-file temp-file))))
(describe "org-roam--h1-count"
(after-each
(cd root-directory))
(it "returns the correct number of level-1 headings"
(find-file "tests/roam-files/foo.org" nil)
(expect (org-roam--h1-count) :to-equal 0)
(cd root-directory)
(find-file "tests/roam-files/family.org" nil)
(expect (org-roam--h1-count) :to-equal 1)))
(describe "org-roam--buffer-promoteable-p"
(after-each
(cd root-directory))
(it "should check if buffer is promoteable"
(find-file "tests/roam-files/foo.org" nil)
(expect (org-roam--buffer-promoteable-p) :to-equal nil)
(cd root-directory)
(find-file "tests/roam-files/family.org" nil)
(expect (org-roam--buffer-promoteable-p) :to-equal nil)
(cd root-directory)
(find-file "tests/roam-files/promoteable.org" nil)
(expect (org-roam--buffer-promoteable-p) :to-equal t)))
(describe "org-roam--get-titles"
(before-all
(setq org-roam-directory (expand-file-name "tests/roam-files")
org-roam-db-location (expand-file-name "org-roam.db" temporary-file-directory)
org-roam-file-extensions '("org")
org-roam-file-exclude-regexp nil)
(org-roam-db-sync))
(after-all
(org-roam-db--close)
(delete-file org-roam-db-location))
(it "returns the list of titles and aliases"
(expect (org-roam--get-titles)
:to-have-same-items-as
`("Bar" "Batman" "Bruce Wayne" "Child" "Deadline heading" "Demoteable" "Family"
"Foo" "Grand-Parent" "Parent" "ref with space" "Scheduled heading" "With Times"))))
(describe "org-roam-alias"
(before-all
(setq org-roam-directory (expand-file-name "tests/roam-files")
org-roam-db-location (expand-file-name "org-roam.db" temporary-file-directory)
org-roam-file-extensions '("org")
org-roam-file-exclude-regexp nil)
(org-roam-db-sync))
(after-all
(org-roam-db--close)
(delete-file org-roam-db-location)
(cd root-directory))
(it "adds an alias to a node"
(cd root-directory)
(find-file "tests/roam-files/foo.org" nil)
(org-roam-alias-add "qux")
(expect (buffer-substring-no-properties (point) (point-max))
:to-equal ":PROPERTIES:\n:ID: 884b2341-b7fe-434d-848c-5282c0727861\n:ROAM_ALIASES: qux\n:END:\n#+title: Foo\n"))
(it "removes an alias from a node"
(cd root-directory)
(find-file "tests/roam-files/with-alias.org" nil)
(org-roam-alias-remove "Batman")
(expect (buffer-substring-no-properties (point) (point-max))
:to-equal ":PROPERTIES:\n:ID: 57ff3ce7-5bda-4825-8fca-c09f523e87ba\n:END:\n#+title: Bruce Wayne\n")))
(provide 'test-org-roam-node)
;;; test-org-roam-node.el ends here

View File

@@ -1,64 +0,0 @@
;;; test-org-roam-utils.el --- Tests for Org-roam -*- lexical-binding: t; -*-
;; Copyright (C) 2020 Jethro Kuan
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; Package-Requires: ((buttercup))
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'buttercup)
(require 'org-roam)
(describe "org-roam-whitespace-content"
(it "extracts whitespace correctly"
(expect
(org-roam-whitespace-content "foo")
:to-equal "")
(expect
(org-roam-whitespace-content "foo\n")
:to-equal "\n")
(expect
(org-roam-whitespace-content "foo\n\t\n")
:to-equal "\n\t\n")))
(describe "org-roam-db--file-title"
(it "supports normal titles"
(expect
(with-temp-buffer
(insert "#+title:normal title")
(org-roam-db--file-title))
:to-equal "normal title"))
(it "supports multi-line titles"
(expect
(with-temp-buffer
(insert "#+title: title:\n#+title: separated by newline")
(org-roam-db--file-title))
:to-equal "title: separated by newline"))
(it "supports file-name based titles"
(progn
(setq org-roam-directory temporary-file-directory
org-roam-db-location (expand-file-name "org-roam.db" temporary-file-directory)
org-roam-file-extensions '("org"))
(with-temp-buffer
(write-file (expand-file-name "test file.org" org-roam-directory))
(org-roam-db--file-title)))
:to-equal "test file"))
(provide 'test-org-roam-utils)
;;; test-org-roam-utils.el ends here

View File

@@ -24,28 +24,6 @@
(require 'buttercup)
(require 'org-roam)
(defvar root-directory default-directory)
(describe "org-roam-file-p"
(it "checks if given file respects criteria"
(setq org-roam-directory "/non-existent")
(expect (org-roam-file-p "tests/roam-files/family.org") :to-equal nil)
(setq org-roam-directory (expand-file-name "tests/roam-files"))
(expect (org-roam-file-p "tests/roam-files/family.org") :to-equal t)
(expect (org-roam-file-p "tests/roam-files/markdown.md") :to-equal nil)
(setq org-roam-file-exclude-regexp (regexp-quote "family.org"))
(expect (org-roam-file-p "tests/roam-files/family.org") :to-equal nil)))
(describe "org-roam-buffer-p"
(it "checks if current buffer respects criteria"
(setq org-roam-directory (expand-file-name "tests/roam-files")
org-roam-file-exclude-regexp nil)
(find-file "tests/roam-files/family.org" nil)
(expect (org-roam-buffer-p) :to-equal t)
(cd root-directory)))
(describe "org-roam-list-files"
(before-each
(setq org-roam-directory (expand-file-name "tests/roam-files")
@@ -53,29 +31,52 @@
org-roam-file-extensions '("org")
org-roam-file-exclude-regexp nil))
(after-all
(org-roam-db--close)
(delete-file org-roam-db-location))
(it "gets files correctly"
(expect (length (org-roam-list-files)) :to-equal 9))
(expect (length (org-roam-list-files))
:to-equal 3))
(it "respects org-roam-file-extensions"
(setq org-roam-file-extensions '("md"))
(expect (length (org-roam-list-files)) :to-equal 1)
(setq org-roam-file-extensions '("org" "md"))
(expect (length (org-roam-list-files)) :to-equal 10))
(expect (length (org-roam-list-files)) :to-equal 4))
(it "respects org-roam-file-exclude-regexp"
(setq org-roam-file-exclude-regexp (regexp-quote "foo.org"))
(expect (length (org-roam-list-files)) :to-equal 8)))
(expect (length (org-roam-list-files)) :to-equal 2)))
(describe "org-roam--list-files-search-globs"
(describe "org-roam-db-sync"
(before-all
(setq org-roam-directory (expand-file-name "tests/roam-files")
org-roam-db-location (expand-file-name "org-roam.db" temporary-file-directory)
org-roam-file-extensions '("org")
org-roam-file-exclude-regexp nil)
(org-roam-db-sync))
(it "returns the correct list of globs"
(expect (org-roam--list-files-search-globs org-roam-file-extensions)
(after-all
(org-roam-db--close)
(delete-file org-roam-db-location))
(it "has the correct number of files"
(expect (caar (org-roam-db-query [:select (funcall count) :from files]))
:to-equal
3))
(it "has the correct number of nodes"
(expect (caar (org-roam-db-query [:select (funcall count) :from nodes]))
:to-equal
2))
(it "has the correct number of links"
(expect (caar (org-roam-db-query [:select (funcall count) :from links]))
:to-equal
1))
(it "respects ROAM_EXCLUDE"
;; The excluded node has ID "53fadc75-f48e-461e-be06-44a1e88b2abe"
(expect (mapcar #'car (org-roam-db-query [:select id :from nodes]))
:to-have-same-items-as
'("\"*.org\"" "\"*.org.gpg\"" "\"*.org.age\""))))
'("884b2341-b7fe-434d-848c-5282c0727861" "440795d0-70c1-4165-993d-aebd5eef7a24"))))
(provide 'test-org-roam)