mirror of
https://github.com/org-roam/org-roam
synced 2025-08-03 12:27:23 -05:00
Compare commits
148 Commits
feat/unify
...
v2-next
Author | SHA1 | Date | |
---|---|---|---|
18b75bd03e | |||
ecc3611bec | |||
3a3c4d97d5 | |||
07ec4342aa | |||
096b0c5e30 | |||
32c0f3d2ec | |||
377d39bfff | |||
3d4c93a2a0 | |||
683605c813 | |||
eae1cdc00c | |||
1bbc20cfa8 | |||
de94212cdd | |||
78144d434b | |||
55526a4283 | |||
9405d014af | |||
98434919e0 | |||
7ed513294d | |||
6fe38b959b | |||
44960f189f | |||
fbe3f2909f | |||
f8f3870cf0 | |||
1bdfc25f80 | |||
109917c589 | |||
bc4b7fa409 | |||
f8dd345bd1 | |||
0a7d365e22 | |||
389bf3c7e2 | |||
ca1b02829b | |||
dd7c3fab23 | |||
22c8d1032b | |||
4e0b3fb564 | |||
697c686390 | |||
68dab59a69 | |||
2dc5d6281a | |||
eb9a9e487c | |||
fd23360100 | |||
57f63461cd | |||
d9ba961f96 | |||
169ebe6367 | |||
38b5375354 | |||
aa755a8c77 | |||
49c048a177 | |||
741677869c | |||
9da45b54f3 | |||
1440802b8b | |||
0163461f87 | |||
c866275086 | |||
a37dd6afea | |||
3f31ff2cd9 | |||
20e021fdde | |||
5db30d8fdc | |||
b12bd4ea40 | |||
e6acc40e79 | |||
d53f0e60bf | |||
45aa3e6973 | |||
3c6e1b5194 | |||
be81a3a738 | |||
67b1562582 | |||
05d95abc2e | |||
d465ea0d72 | |||
6b98f4b836 | |||
3664d22fde | |||
f62c00ea73 | |||
597d3c6241 | |||
f43b24a5bf | |||
aa2b670cd7 | |||
6c34528837 | |||
bc7a4986d8 | |||
624ac77fe2 | |||
0697f376eb | |||
3615969b35 | |||
f52c3a47d4 | |||
517f18afb4 | |||
b1ebba4b9a | |||
bad71fb928 | |||
bbefa7c174 | |||
1773978dc3 | |||
4cddc21c02 | |||
121d08562d | |||
fbf15fe5f2 | |||
4d697dea8f | |||
e612b3f068 | |||
c9636d6551 | |||
eef1ed2873 | |||
a1710b41b3 | |||
a527560cf4 | |||
628d5dcfbe | |||
516d8dfbf8 | |||
866368bfee | |||
1c831dcd73 | |||
3a2df140cb | |||
de6477be17 | |||
6f233363c8 | |||
09b0d74359 | |||
1b1d2b2b76 | |||
9e4a943d03 | |||
9a6cfe56c4 | |||
45546ff6fa | |||
7a6b885b99 | |||
39bb939dc2 | |||
467b165678 | |||
d641ad4792 | |||
18083cc1ed | |||
5b0078e79d | |||
5a3363ac22 | |||
18aa3ac787 | |||
7a493d8549 | |||
50436a9b1c | |||
73a04a611a | |||
ca1080b349 | |||
4e5a21d7e3 | |||
fb27fc78bc | |||
a35b0e7c6d | |||
f883c3270f | |||
eec1edf8f5 | |||
3d61a76ecb | |||
49541298ba | |||
490901957e | |||
f681c51630 | |||
ae5a0c15ba | |||
732efc6262 | |||
537f167503 | |||
1ea5b029a5 | |||
486f3cac3d | |||
e62cf7f8aa | |||
a4ab83ea89 | |||
7bb61806ef | |||
6ac3a84057 | |||
47c3b024d7 | |||
e19c3d6273 | |||
6bcf164036 | |||
315e2dc447 | |||
2f77ae709d | |||
e238c6d766 | |||
9b0a45f105 | |||
843eacb80b | |||
6fae3aa74c | |||
d1dd75e3b2 | |||
be828e2c1d | |||
5c7027aece | |||
fb76f81423 | |||
121e969553 | |||
0c0d677631 | |||
dc0357b12e | |||
cbeaa4d92e | |||
302b2e3bc3 | |||
25c41a079a | |||
53dcf687ef |
@ -1,6 +1,12 @@
|
|||||||
;;; Directory Local Variables
|
|
||||||
;;; For more information see (info "(emacs) Directory Variables")
|
|
||||||
|
|
||||||
((emacs-lisp-mode
|
((emacs-lisp-mode
|
||||||
(eval . (require 'org-roam-dev))
|
(fill-column . 110)
|
||||||
(eval . (org-roam-dev-mode))))
|
(indent-tabs-mode . nil)
|
||||||
|
(elisp-lint-ignored-validators . ("byte-compile" "package-lint"))
|
||||||
|
(elisp-lint-indent-specs . ((describe . 1)
|
||||||
|
(it . 1)
|
||||||
|
(org-element-map . defun)
|
||||||
|
(org-roam-with-temp-buffer . 1)
|
||||||
|
(org-with-point-at . 1)
|
||||||
|
(magit-insert-section . defun)
|
||||||
|
(magit-section-case . 0)
|
||||||
|
(org-roam-with-file . 2)))))
|
||||||
|
25
.github/workflows/test.yml
vendored
25
.github/workflows/test.yml
vendored
@ -34,9 +34,9 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
matrix:
|
||||||
emacs_version:
|
emacs_version:
|
||||||
|
- 27.1
|
||||||
- snapshot
|
- snapshot
|
||||||
steps:
|
steps:
|
||||||
- uses: purcell/setup-emacs@master
|
- uses: purcell/setup-emacs@master
|
||||||
@ -45,28 +45,17 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Create Sandbox Directory
|
- name: Install Eldev
|
||||||
run: |
|
run: curl -fsSL https://raw.github.com/doublep/eldev/master/webinstall/github-eldev | sh
|
||||||
SANDBOX_DIR=$(mktemp -d) || exit 1
|
|
||||||
echo "SANDBOX_DIR=$SANDBOX_DIR" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Initialize Sandbox
|
- name: Install dependencies
|
||||||
run: |
|
run: make prepare
|
||||||
./makem.sh -vv --sandbox $SANDBOX_DIR --install-deps --install-linters
|
|
||||||
|
|
||||||
# The "all" rule is not used, because it treats compilation warnings
|
|
||||||
# as failures, so linting and testing are run as separate steps.
|
|
||||||
|
|
||||||
# org-roam-compat is excluded from linting because it contains
|
|
||||||
# symbols/aliases from other packages
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
continue-on-error: false
|
run: make lint
|
||||||
run: ./makem.sh -vv --sandbox $SANDBOX_DIR --exclude org-roam-compat.el lint
|
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
if: always() # Run test even if linting fails.
|
run: make test
|
||||||
run: ./makem.sh -vv --sandbox $SANDBOX_DIR test
|
|
||||||
|
|
||||||
# Local Variables:
|
# Local Variables:
|
||||||
# eval: (outline-minor-mode)
|
# eval: (outline-minor-mode)
|
||||||
# End:
|
# End:
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -13,3 +13,4 @@
|
|||||||
/doc/stats/
|
/doc/stats/
|
||||||
/config.mk
|
/config.mk
|
||||||
/doc/manual.html
|
/doc/manual.html
|
||||||
|
/.eldev/
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 1.2.4 (TBD)
|
## 1.2.4 (TBD)
|
||||||
|
|
||||||
### Added
|
### 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
|
- [#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`.
|
- [#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`.
|
||||||
- [#1353](https://github.com/org-roam/org-roam/pull/1353) support file-level property drawers
|
- [#1353](https://github.com/org-roam/org-roam/pull/1353) support file-level property drawers
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- [#1352](https://github.com/org-roam/org-roam/pull/1352) prefer lower-case for roam_tag and roam_alias in interactive commands
|
- [#1352](https://github.com/org-roam/org-roam/pull/1352) prefer lower-case for roam_tag and roam_alias in interactive commands
|
||||||
- [#1513](https://github.com/org-roam/org-roam/pull/1513) replaced hardcoded "svg" with defcustom org-roam-graph-filetype
|
- [#1513](https://github.com/org-roam/org-roam/pull/1513) replaced hardcoded "svg" with defcustom org-roam-graph-filetype
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- [#1281](https://github.com/org-roam/org-roam/pull/1281) fixed idle-timer not instantiated on `org-roam-mode`
|
- [#1281](https://github.com/org-roam/org-roam/pull/1281) fixed idle-timer not instantiated on `org-roam-mode`
|
||||||
- [#1308](https://github.com/org-roam/org-roam/pull/1308) fixed file renames corrupting database
|
- [#1308](https://github.com/org-roam/org-roam/pull/1308) fixed file renames corrupting database
|
||||||
- [#1325](https://github.com/org-roam/org-roam/pull/1325) make titles and tags extracted unique per note
|
- [#1325](https://github.com/org-roam/org-roam/pull/1325) make titles and tags extracted unique per note
|
||||||
@ -30,6 +34,7 @@ Primarily a stabilization and bug-fix release.
|
|||||||
Org-roam-dailies has also been revamped to include new features, see [this video](https://www.youtube.com/watch?v=1q9x2aZCJJ4) for a quick overview.
|
Org-roam-dailies has also been revamped to include new features, see [this video](https://www.youtube.com/watch?v=1q9x2aZCJJ4) for a quick overview.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- [#978](https://github.com/org-roam/org-roam/pull/978) Revamp org-roam-dailies
|
- [#978](https://github.com/org-roam/org-roam/pull/978) Revamp org-roam-dailies
|
||||||
- [#1183](https://github.com/org-roam/org-roam/pull/1183) Interactive functions for managing aliases and tags in Org-roam file, namely `org-roam-alias-add`, `org-roam-alias-delete`, `org-roam-tag-add`, and `org-roam-tag-delete`.
|
- [#1183](https://github.com/org-roam/org-roam/pull/1183) Interactive functions for managing aliases and tags in Org-roam file, namely `org-roam-alias-add`, `org-roam-alias-delete`, `org-roam-tag-add`, and `org-roam-tag-delete`.
|
||||||
- [#1215](https://github.com/org-roam/org-roam/pull/1215) Multiple `ROAM_KEY` keywords can now be specified in one file. This allows bibliographical entries to share the same note file.
|
- [#1215](https://github.com/org-roam/org-roam/pull/1215) Multiple `ROAM_KEY` keywords can now be specified in one file. This allows bibliographical entries to share the same note file.
|
||||||
@ -38,9 +43,11 @@ Org-roam-dailies has also been revamped to include new features, see [this video
|
|||||||
- [#1264](https://github.com/org-roam/org-roam/pull/1264) add `org-roam-db-update-method` to control when the cache is rebuilt.
|
- [#1264](https://github.com/org-roam/org-roam/pull/1264) add `org-roam-db-update-method` to control when the cache is rebuilt.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- [#1264](https://github.com/org-roam/org-roam/pull/1264) renamed `org-roam-update-db-idle-seconds` to `org-roam-db-idle-idle-seconds`
|
- [#1264](https://github.com/org-roam/org-roam/pull/1264) renamed `org-roam-update-db-idle-seconds` to `org-roam-db-idle-idle-seconds`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- [#1074](https://github.com/org-roam/org-roam/issues/1074) fix `org-roam--extract-links` to handle content boundaries.
|
- [#1074](https://github.com/org-roam/org-roam/issues/1074) fix `org-roam--extract-links` to handle content boundaries.
|
||||||
- [#1193](https://github.com/org-roam/org-roam/issues/1193) fix `org-roam-db-build-cache` by not killing temporary buffer in `org-roam--extract-links`.
|
- [#1193](https://github.com/org-roam/org-roam/issues/1193) fix `org-roam-db-build-cache` by not killing temporary buffer in `org-roam--extract-links`.
|
||||||
- [#1195](https://github.com/org-roam/org-roam/issues/1195) fix ID face showing as invalid if within Org ID files, but not Org-roam's.
|
- [#1195](https://github.com/org-roam/org-roam/issues/1195) fix ID face showing as invalid if within Org ID files, but not Org-roam's.
|
||||||
|
29
Eldev
Normal file
29
Eldev
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
; -*- mode: emacs-lisp; lexical-binding: t; no-byte-compile: t -*-
|
||||||
|
|
||||||
|
;; explicitly set main file
|
||||||
|
(setf eldev-project-main-file "org-roam.el")
|
||||||
|
|
||||||
|
(eldev-use-package-archive 'gnu)
|
||||||
|
(eldev-use-package-archive 'melpa-unstable)
|
||||||
|
|
||||||
|
;; allow to load test helpers
|
||||||
|
(eldev-add-loading-roots 'test "test/utils")
|
||||||
|
|
||||||
|
;; Tell checkdoc not to demand two spaces after a period.
|
||||||
|
(setq sentence-end-double-space nil)
|
||||||
|
|
||||||
|
(setf eldev-lint-default '(elisp))
|
||||||
|
(setf eldev-standard-excludes `(:or ,eldev-standard-excludes "org-roam-macs.el"))
|
||||||
|
|
||||||
|
(with-eval-after-load 'elisp-lint
|
||||||
|
;; We will byte-compile with Eldev.
|
||||||
|
(setf elisp-lint-ignored-validators '("package-lint" "fill-column")
|
||||||
|
enable-local-variables :all))
|
||||||
|
|
||||||
|
;; Teach linter how to properly indent emacsql vectors
|
||||||
|
(eldev-add-extra-dependencies 'lint 'emacsql)
|
||||||
|
(add-hook 'eldev-lint-hook
|
||||||
|
(lambda ()
|
||||||
|
(eldev-load-project-dependencies 'lint nil t)
|
||||||
|
(require 'emacsql)
|
||||||
|
(call-interactively #'emacsql-fix-vector-indentation)))
|
73
Makefile
73
Makefile
@ -1,70 +1,29 @@
|
|||||||
# * makem.sh/Makefile --- Script to aid building and testing Emacs Lisp packages
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
eldev clean all
|
||||||
|
|
||||||
# This Makefile is from the makem.sh repo: <https://github.com/alphapapa/makem.sh>.
|
.PHONY: prepare
|
||||||
|
prepare:
|
||||||
|
eldev -C --unstable -p -dtT prepare
|
||||||
|
|
||||||
# * Arguments
|
.PHONY: lint
|
||||||
|
lint:
|
||||||
|
eldev -C --unstable -T lint
|
||||||
|
|
||||||
# For consistency, we use only var=val options, not hyphen-prefixed options.
|
.PHONY: test
|
||||||
|
test:
|
||||||
# NOTE: I don't like duplicating the arguments here and in makem.sh,
|
eldev -C --unstable -T test
|
||||||
# but I haven't been able to find a way to pass arguments which
|
|
||||||
# conflict with Make's own arguments through Make to the script.
|
|
||||||
# Using -- doesn't seem to do it.
|
|
||||||
|
|
||||||
ifdef install-deps
|
|
||||||
INSTALL_DEPS = "--install-deps"
|
|
||||||
endif
|
|
||||||
ifdef install-linters
|
|
||||||
INSTALL_LINTERS = "--install-linters"
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifdef sandbox
|
|
||||||
ifeq ($(sandbox), t)
|
|
||||||
SANDBOX = --sandbox
|
|
||||||
else
|
|
||||||
SANDBOX = --sandbox $(sandbox)
|
|
||||||
endif
|
|
||||||
endif
|
|
||||||
|
|
||||||
ifdef debug
|
|
||||||
DEBUG = "--debug"
|
|
||||||
endif
|
|
||||||
|
|
||||||
# ** Verbosity
|
|
||||||
|
|
||||||
# Since the "-v" in "make -v" gets intercepted by Make itself, we have
|
|
||||||
# to use a variable.
|
|
||||||
|
|
||||||
verbose = $(v)
|
|
||||||
|
|
||||||
ifneq (,$(findstring vv,$(verbose)))
|
|
||||||
VERBOSE = "-vv"
|
|
||||||
else ifneq (,$(findstring v,$(verbose)))
|
|
||||||
VERBOSE = "-v"
|
|
||||||
endif
|
|
||||||
|
|
||||||
# * Rules
|
|
||||||
|
|
||||||
# TODO: Handle cases in which "test" or "tests" are called and a
|
|
||||||
# directory by that name exists, which can confuse Make.
|
|
||||||
|
|
||||||
%:
|
|
||||||
@./makem.sh $(DEBUG) $(VERBOSE) $(SANDBOX) $(INSTALL_DEPS) $(INSTALL_LINTERS) $(@)
|
|
||||||
|
|
||||||
.DEFAULT: init
|
|
||||||
init:
|
|
||||||
@./makem.sh $(DEBUG) $(VERBOSE) $(SANDBOX) $(INSTALL_DEPS) $(INSTALL_LINTERS)
|
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
@$(MAKE) -C doc all
|
make -C doc all
|
||||||
|
|
||||||
html:
|
html:
|
||||||
@$(MAKE) -C doc html-dir
|
make -C doc html-dir
|
||||||
|
|
||||||
install: install-docs
|
install: install-docs
|
||||||
|
|
||||||
install-docs: docs
|
install-docs: docs
|
||||||
@$(MAKE) -C doc install-docs
|
make -C doc install-docs
|
||||||
|
|
||||||
install-info: info
|
install-info: info
|
||||||
@$(MAKE) -C doc install-info
|
make -C doc install-info
|
||||||
|
@ -53,8 +53,7 @@ Here's a sample configuration with `use-package`:
|
|||||||
("C-c n f" . org-roam-find-file)
|
("C-c n f" . org-roam-find-file)
|
||||||
("C-c n g" . org-roam-graph))
|
("C-c n g" . org-roam-graph))
|
||||||
:map org-mode-map
|
:map org-mode-map
|
||||||
(("C-c n i" . org-roam-insert))
|
(("C-c n i" . org-roam-insert))))
|
||||||
(("C-c n I" . org-roam-insert-immediate))))
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The `file-truename` function is only necessary when you use symbolic links
|
The `file-truename` function is only necessary when you use symbolic links
|
||||||
|
660
doc/org-roam.org
660
doc/org-roam.org
@ -1,23 +1,23 @@
|
|||||||
#+title: Org-roam User Manual
|
#+title: Org-roam User Manual
|
||||||
#+author: Jethro Kuan
|
#+author: Jethro Kuan
|
||||||
#+email: jethrokuan95@gmail.com
|
#+email: jethrokuan95@gmail.com
|
||||||
#+date: 2020-2020
|
#+date: 2020-2021
|
||||||
#+language: en
|
#+language: en
|
||||||
|
|
||||||
#+texinfo_deffn: t
|
#+texinfo_deffn: t
|
||||||
#+texinfo_dir_category: Emacs
|
#+texinfo_dir_category: Emacs
|
||||||
#+texinfo_dir_title: Org-roam: (org-roam).
|
#+texinfo_dir_title: Org-roam: (org-roam).
|
||||||
#+texinfo_dir_desc: Rudimentary Roam Replica for Emacs.
|
#+texinfo_dir_desc: Roam Research for Emacs.
|
||||||
#+subtitle: for version 1.2.3
|
#+subtitle: for version 2.0.0
|
||||||
|
|
||||||
#+options: H:4 num:3 toc:nil creator:t ':t
|
#+options: H:4 num:3 toc:nil creator:t ':t
|
||||||
#+property: header-args :eval never
|
#+property: header-args :eval never
|
||||||
#+texinfo: @noindent
|
#+texinfo: @noindent
|
||||||
|
|
||||||
This manual is for Org-roam version 1.2.3.
|
This manual is for Org-roam version 2.0.0.
|
||||||
|
|
||||||
#+BEGIN_QUOTE
|
#+BEGIN_QUOTE
|
||||||
Copyright (C) 2020-2020 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
|
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
|
General Public License as published by the Free Software Foundation, either
|
||||||
@ -31,26 +31,22 @@ General Public License for more details.
|
|||||||
|
|
||||||
* Introduction
|
* Introduction
|
||||||
|
|
||||||
Org-roam is a tool for network thought. It reproduces some of [[https://roamresearch.com/][Roam
|
Org-roam is a tool for networked thought. It reproduces some of [[https://roamresearch.com/][Roam
|
||||||
Research's]] [fn:roam] features within the all-powerful [[https://orgmode.org/][Org-mode]].
|
Research's]] [fn:roam] key features within [[https://orgmode.org/][Org-mode]].
|
||||||
|
|
||||||
Org-roam is a solution for effortless non-hierarchical note-taking with
|
Org-roam allows for effortless non-hierarchical note-taking: with Org-roam,
|
||||||
Org-mode. With Org-roam, notes flow naturally, making note-taking fun and easy.
|
notes flow naturally, making note-taking fun and easy. Org-roam augments the
|
||||||
Org-roam keeps closely to Org syntax, and will work for anyone already using
|
Org-mode syntax, and will work for anyone already using Org-mode for their
|
||||||
Org-mode for their personal wiki.
|
personal wiki.
|
||||||
|
|
||||||
Org-roam gains its superpowers by leveraging the mature ecosystem around
|
Org-roam leverages the mature ecosystem around Org-mode. For example, it has
|
||||||
Org-mode. For example, it has first-class support for [[https://github.com/jkitchin/org-ref][org-ref]] for citation
|
first-class support for [[https://github.com/jkitchin/org-ref][org-ref]] for citation management, and is able to
|
||||||
management.
|
piggyback off Org's excellent LaTeX and source-block evaluation capabilities.
|
||||||
|
|
||||||
Org-roam aims to implement the core features of Roam, leveraging the
|
Org-roam provides these benefits over other tooling:
|
||||||
mature ecosystem around Org-mode where possible. Eventually, we hope
|
|
||||||
to further introduce features enabled by the Emacs ecosystem.
|
|
||||||
|
|
||||||
Org-roam provides several benefits over other tooling:
|
- *Privacy and Security:* Your personal wiki belongs only to you, entirely
|
||||||
|
offline and in your control. Encrypt your notes with GPG.
|
||||||
- *Privacy and Security:* Keep your personal wiki entirely offline and in your
|
|
||||||
control. Encrypt your notes with GPG.
|
|
||||||
- *Longevity of Plain Text:* Unlike web solutions like Roam Research, the notes
|
- *Longevity of Plain Text:* Unlike web solutions like Roam Research, the notes
|
||||||
are first and foremost plain Org-mode files -- Org-roam simply builds an
|
are first and foremost plain Org-mode files -- Org-roam simply builds an
|
||||||
auxiliary database to give the personal wiki superpowers. Having your notes
|
auxiliary database to give the personal wiki superpowers. Having your notes
|
||||||
@ -60,28 +56,28 @@ Org-roam provides several benefits over other tooling:
|
|||||||
- *Free and Open Source:* Org-roam is free and open-source, which means that if
|
- *Free and Open Source:* Org-roam is free and open-source, which means that if
|
||||||
you feel unhappy with any part of Org-roam, you may choose to extend Org-roam,
|
you feel unhappy with any part of Org-roam, you may choose to extend Org-roam,
|
||||||
or open a pull request.
|
or open a pull request.
|
||||||
- *Leverage the Org-mode ecosystem:* Over the years, Emacs and Org-mode has
|
- *Leverage the Org-mode ecosystem:* Over the decades, Emacs and Org-mode has
|
||||||
developed into a mature system for plain-text organization. Building upon
|
developed into a mature system for plain-text organization. Building upon
|
||||||
Org-mode already puts Org-roam light-years ahead of many other solutions.
|
Org-mode already puts Org-roam light-years ahead of many other solutions.
|
||||||
- *Built on Emacs:* Emacs is also a fantastic interface for editing text, and we
|
- *Built on Emacs:* Emacs is also a fantastic interface for editing text, and
|
||||||
can inherit many of the powerful text-navigation and editing packages
|
Org-roam inherits many of the powerful text-navigation and editing packages
|
||||||
available to Emacs.
|
available to Emacs.
|
||||||
|
|
||||||
* Target Audience
|
* Target Audience
|
||||||
|
|
||||||
Org-roam is a tool that will appear unfriendly to anyone unfamiliar with Emacs
|
Org-roam is a tool that will appear unfriendly to anyone unfamiliar with Emacs
|
||||||
and Org-mode, but is also extremely powerful to those willing to put effort in
|
and Org-mode, but it is also extremely powerful to those willing to put effort
|
||||||
mastering the intricacies of the tools. Org-roam stands on the shoulders on
|
inn mastering the intricacies. Org-roam stands on the shoulders of giants. Emacs
|
||||||
giants. Emacs was first created in 1976, and remains a top tier tool for editing
|
was first created in 1976, and remains the tool of choice for many for editing
|
||||||
text and designing textual interfaces. The malleability of Emacs allowed the
|
text and designing textual interfaces. The malleability of Emacs allowed the
|
||||||
creation of Org-mode, an all-purpose plain-text system for maintaining TODO
|
creation of Org-mode, an all-purpose plain-text system for maintaining TODO
|
||||||
lists, planning projects, and authoring documents. Both of these tools are
|
lists, planning projects, and authoring documents. Both of these tools are
|
||||||
incredibly vast and require significant time investment to master.
|
incredibly vast and require significant time investment to master.
|
||||||
|
|
||||||
Org-roam assumes basic familiarity with these tools. It is not difficult to get
|
Org-roam assumes only basic familiarity with these tools. It is not difficult to
|
||||||
up and running with basic text-editing functionality, but one will only fully
|
get up and running with basic text-editing functionality, but one will only
|
||||||
appreciate the power of building Roam functionality into Emacs and Org-mode when
|
fully appreciate the power of building Roam functionality into Emacs and
|
||||||
the usage of these tools become more advanced.
|
Org-mode when the usage of these tools become more advanced.
|
||||||
|
|
||||||
One key advantage to Org-roam is that building on top of Emacs gives it
|
One key advantage to Org-roam is that building on top of Emacs gives it
|
||||||
malleability. This is especially important for note-taking workflows. It is our
|
malleability. This is especially important for note-taking workflows. It is our
|
||||||
@ -132,7 +128,7 @@ plain-text, Org-mode file. In the same way one would maintain a paper slip-box,
|
|||||||
Org-roam makes it easy to create new zettels, pre-filling boilerplate content
|
Org-roam makes it easy to create new zettels, pre-filling boilerplate content
|
||||||
using a powerful templating system.
|
using a powerful templating system.
|
||||||
|
|
||||||
** Fleeting notes
|
*Fleeting notes*
|
||||||
|
|
||||||
A slip-box requires a method for quickly capturing ideas. These are called
|
A slip-box requires a method for quickly capturing ideas. These are called
|
||||||
*fleeting notes*: they are simple reminders of information or ideas that will
|
*fleeting notes*: they are simple reminders of information or ideas that will
|
||||||
@ -141,7 +137,7 @@ need to be processed later on, or trashed. This is typically accomplished using
|
|||||||
functionality (see [[*Daily-notes][Daily-notes]]). This provides a central inbox for collecting
|
functionality (see [[*Daily-notes][Daily-notes]]). This provides a central inbox for collecting
|
||||||
thoughts, to be processed later into permanent notes.
|
thoughts, to be processed later into permanent notes.
|
||||||
|
|
||||||
** Permanent notes
|
*Permanent notes*
|
||||||
|
|
||||||
Permanent notes are further split into two categories: *literature notes* and
|
Permanent notes are further split into two categories: *literature notes* and
|
||||||
*concept notes*. Literature notes can be brief annotations on a particular
|
*concept notes*. Literature notes can be brief annotations on a particular
|
||||||
@ -150,6 +146,9 @@ Concept notes require much more care in authoring: they need to be
|
|||||||
self-explanatory and detailed. Org-roam's templating system supports the
|
self-explanatory and detailed. Org-roam's templating system supports the
|
||||||
addition of different templates to facilitate the creation of these notes.
|
addition of different templates to facilitate the creation of these notes.
|
||||||
|
|
||||||
|
For further reading on the Zettelkasten method, "How to Take Smart Notes" by
|
||||||
|
Sonke Ahrens is a decent guide.
|
||||||
|
|
||||||
* Installation
|
* Installation
|
||||||
|
|
||||||
Org-roam can be installed using Emacs' package manager or manually from its
|
Org-roam can be installed using Emacs' package manager or manually from its
|
||||||
@ -212,7 +211,7 @@ using Apt:
|
|||||||
|
|
||||||
Org-roam will then be autoloaded into Emacs.
|
Org-roam will then be autoloaded into Emacs.
|
||||||
|
|
||||||
** Installing from the Git Repository
|
** Installing from Source
|
||||||
|
|
||||||
You may install Org-roam directly from the repository on [[https://github.com/org-roam/org-roam][GitHub]] if you like.
|
You may install Org-roam directly from the repository on [[https://github.com/org-roam/org-roam][GitHub]] if you like.
|
||||||
This will give you access to the latest version hours or days before it appears
|
This will give you access to the latest version hours or days before it appears
|
||||||
@ -294,40 +293,71 @@ install-info /path/to/my/info/files/org-roam.info /path/to/my/info/files/dir
|
|||||||
|
|
||||||
** Post-Installation Tasks
|
** Post-Installation Tasks
|
||||||
|
|
||||||
Org-roam uses ~emacsql-sqlite3~, which requires ~sqlite3~ to be located on
|
Org-roam requires ~sqlite3~ to be locatable by Emacs (i.e. on ~exec-path~).
|
||||||
~exec-path~. Please ensure that ~sqlite3~ is installed appropriately on your
|
Please ensure that ~sqlite3~ is installed appropriately on your operating
|
||||||
operating system. You can verify that this is the case by executing:
|
system. You can verify that this is the case by executing [fn:2]:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(executable-find "sqlite3")
|
(executable-find "sqlite3")
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
If you have ~sqlite3~ installed, and ~executable-find~ still reports ~nil~, then
|
If you have ~sqlite3~ installed, and ~executable-find~ still reports ~nil~, then
|
||||||
it is likely that the path to the executable is not a member of the Emacs
|
the path to the executable is not a member of the Emacs variable ~exec-path~.
|
||||||
variable ~exec-path~. You may rectify this by manually adding the path within
|
Rectify this by manually adding the path within your Emacs configuration:
|
||||||
your Emacs configuration:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(add-to-list 'exec-path "path/to/sqlite3")
|
(add-to-list 'exec-path "path/to/sqlite3")
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
* Getting Started
|
* Getting Started
|
||||||
|
** The Org-roam Node
|
||||||
|
|
||||||
This short tutorial describes the essential commands used in Org-roam, to help
|
We first begin with some terminology we'll use throughout the manual. We term
|
||||||
you get started.
|
the basic denomination in Org-roam a node. We define a node as follows:
|
||||||
|
|
||||||
First, it is important to understand how Org-roam was designed. Org-roam was
|
#+begin_quote
|
||||||
built to support a workflow that was not possible with vanilla Org-mode. This
|
A node is any headline or top level file with an ID.
|
||||||
flow is modelled after the [[https://zettelkasten.de/][Zettelkasten Method]], and many of [[https://roamresearch.com][Roam Research's]]
|
#+end_quote
|
||||||
workflows. Org-roam does not magically make note-taking better -- this often
|
|
||||||
requires a radical change in your current note-taking workflow. To understand
|
|
||||||
more about the methods and madness, see [[*Note-taking Workflows][Note-taking Workflows]].
|
|
||||||
|
|
||||||
To first start using Org-roam, one needs to pick a location to store the
|
For example, with this example file content:
|
||||||
Org-roam files. The directory that will contain your notes is specified by the
|
|
||||||
variable ~org-roam-directory~. This variable needs to be set before any calls to
|
#+begin_src org
|
||||||
Org-roam functions, including enabling ~org-roam-mode~. For this tutorial,
|
:PROPERTIES:
|
||||||
create an empty directory, and set ~org-roam-directory~:
|
:ID: foo
|
||||||
|
:END:
|
||||||
|
,#+title: Foo
|
||||||
|
|
||||||
|
,* Bar
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: bar
|
||||||
|
:END:
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
We create two nodes:
|
||||||
|
|
||||||
|
1. A file node "Foo" with id ~foo~.
|
||||||
|
2. A headline node "Bar" with id ~bar~.
|
||||||
|
|
||||||
|
Headlines without IDs will not be considered Org-roam nodes. Org IDs can be
|
||||||
|
added to files or headlines via the interactive command ~M-x org-id-get-create~.
|
||||||
|
|
||||||
|
** Links between Nodes
|
||||||
|
|
||||||
|
We link between nodes using Org's standard ID link (e.g. ~id:foo~). While only
|
||||||
|
ID links will be considered during the computation of links between nodes,
|
||||||
|
Org-roam caches all other links in the documents for external use.
|
||||||
|
|
||||||
|
** Setting up Org-roam
|
||||||
|
|
||||||
|
Org-roam's capabilities stem from its aggressive caching: it crawls all files
|
||||||
|
within ~org-roam-directory~, and maintains a cache of all links and nodes.
|
||||||
|
|
||||||
|
To start using Org-roam, pick a location to store the Org-roam files. The
|
||||||
|
directory that will contain your notes is specified by the variable
|
||||||
|
~org-roam-directory~. Org-roam searches recursively within ~org-roam-directory~
|
||||||
|
for notes. This variable needs to be set before any calls to Org-roam functions.
|
||||||
|
|
||||||
|
For this tutorial, create an empty directory, and set ~org-roam-directory~:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(make-directory "~/org-roam")
|
(make-directory "~/org-roam")
|
||||||
@ -337,253 +367,305 @@ create an empty directory, and set ~org-roam-directory~:
|
|||||||
The ~file-truename~ function is only necessary when you use symbolic links
|
The ~file-truename~ function is only necessary when you use symbolic links
|
||||||
inside ~org-roam-directory~: Org-roam does not resolve symbolic links.
|
inside ~org-roam-directory~: Org-roam does not resolve symbolic links.
|
||||||
|
|
||||||
We encourage using a flat hierarchy for storing notes, but some prefer using
|
Next, we setup Org-roam to run functions on file changes to maintain cache
|
||||||
folders for storing specific kinds of notes (e.g. websites, papers). This is
|
consistency. This is achieved by running ~M-x org-roam-setup~. To ensure that
|
||||||
fine; Org-roam searches recursively within ~org-roam-directory~ for notes.
|
Org-roam is available on startup, place this in your Emacs configuration:
|
||||||
Instead of relying on the file hierarchy for any form of categorization, one
|
|
||||||
should use links between files to establish connections between notes.
|
|
||||||
|
|
||||||
Next, we need to enable the global minor mode ~org-roam-mode~. This sets up
|
#+begin_src emacs-lisp
|
||||||
Emacs with several hooks, building a cache that is kept consistent as your
|
(require 'org-roam)
|
||||||
slip-box grows. We recommend starting ~org-roam-mode~ on startup:
|
(org-roam-setup)
|
||||||
|
#+end_src
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
To build the cache manually, run ~M-x org-roam-db-build-cache~. Cache builds may
|
||||||
(add-hook 'after-init-hook 'org-roam-mode)
|
take a while the first time, but subsequent builds are often instantaneous
|
||||||
#+END_SRC
|
because they only reprocess modified files.
|
||||||
|
|
||||||
To build the cache manually, one can run ~M-x org-roam-db-build-cache~. Cache
|
** TODO Creating and Linking Nodes
|
||||||
builds may take a while the first time, but is often instantaneous in subsequent
|
|
||||||
runs because it only reprocesses modified files.
|
|
||||||
|
|
||||||
Let us now create our first note. Call ~M-x org-roam-find-file~. This shows a
|
Org-roam makes it easy to create notes and link them together. There are 2 main
|
||||||
list of titles for notes that reside in ~org-roam-directory~. It should show
|
functions for creating nodes:
|
||||||
nothing right now, since there are no notes in the directory. Entering the title
|
|
||||||
of the note you wish to create, and pressing ~RET~ should begin the note
|
- ~org-roam-node-insert~: creates a node if it does not exist, and inserts a
|
||||||
|
link to the node at point.
|
||||||
|
- ~org-roam-node-find~: creates a node if it does not exist, and visits the
|
||||||
|
node.
|
||||||
|
|
||||||
|
Let's first try ~org-roam-node-find~. Calling ~M-x org-roam-node-find~ will
|
||||||
|
show a list of titles for nodes that reside in ~org-roam-directory~. It should
|
||||||
|
show nothing right now, since there are no notes in the directory. Enter the
|
||||||
|
title of the note you wish to create, and press ~RET~. This begins the note
|
||||||
creation process. This process uses ~org-capture~'s templating system, and can
|
creation process. This process uses ~org-capture~'s templating system, and can
|
||||||
be customized (see [[*The Templating System][The Templating System]]). Using the default template, pressing
|
be customized (see [[*The Templating System][The Templating System]]). Using the default template, pressing
|
||||||
~C-c C-c~ finishes the note capture.
|
~C-c C-c~ finishes the note capture.
|
||||||
|
|
||||||
By default, Org-roam updates the cache asynchronously in the background to
|
Now that we have a node, we can try inserting a link to the node using ~M-x
|
||||||
avoid getting in the way of writing. Org-roam queues updates to the files,
|
org-roam-node-insert~. This brings up the list of nodes, which should contain
|
||||||
waits for you to be idle for 2 seconds, and then automatically triggers
|
the node you just created. Selecting the node will insert an ~id:~ link to the
|
||||||
updating the cache. After the cache has been updated, running ~M-x
|
node. If you instead entered a title that does not exist, you will once again be
|
||||||
org-roam-find-file~ again should show the note you have created, and selecting
|
brought through the node creation process.
|
||||||
that entry will bring you to that note [fn:1]. One can customize the waiting
|
|
||||||
time by setting ~org-roam-db-update-idle-seconds~; or change the cache update
|
|
||||||
to be triggered immediately after buffer save by setting
|
|
||||||
~org-roam-db-update-method~ to ~'immediate~.
|
|
||||||
|
|
||||||
For experienced ~org-capture~ users, the behavior of ~M-x org-roam-find-file~
|
One can also conveniently insert links via the completion-at-point functions
|
||||||
may seem unfamiliar: after finishing a capture with ~C-c C-c~, you are returned
|
Org-roam provides (see [[id:70083bfd-d1e3-42b9-bf83-5b05708791c0][Completion]]).
|
||||||
not to the original buffer from which you called ~M-x org-roam-find-file~, but
|
|
||||||
to a buffer pointing to the note you just created. For the usual ~org-capture~
|
|
||||||
behavior you can call ~M-x org-roam-capture~ instead of ~M-x org-roam-find-file~.
|
|
||||||
|
|
||||||
Org-roam makes it easy to create notes, and link them together. To link notes
|
# For experienced ~org-capture~ users, the behavior of ~M-x org-roam-find-file~
|
||||||
together, we call ~M-x org-roam-insert~. This brings up a prompt with a list of
|
# may seem unfamiliar: after finishing a capture with ~C-c C-c~, you are returned
|
||||||
title for existing notes. Selecting an existing entry will create and insert a
|
# not to the original buffer from which you called ~M-x org-roam-find-file~, but
|
||||||
link to the current file. Entering a non-existent title will create a new note
|
# to a buffer pointing to the note you just created. For the usual ~org-capture~
|
||||||
with that title. Good usage of Org-roam requires liberally linking files: this
|
# behavior you can call ~M-x org-roam-capture~ instead of ~M-x org-roam-find-file~.
|
||||||
facilitates building up a dense graph of inter-connected notes.
|
|
||||||
|
|
||||||
Org-roam provides an interface to view backlinks. It shows backlinks for the
|
* TODO Viewing the links
|
||||||
currently active Org-roam note, along with some surrounding context. To toggle
|
|
||||||
the visibility of this buffer, call ~M-x org-roam~.
|
|
||||||
|
|
||||||
For a visual representation of the notes and their connections, Org-roam also
|
Org-roam provides an interface to view relationships with other notes
|
||||||
provides graphing capabilities, using Graphviz. It generates graphs with notes
|
(backlinks, reference links, unlinked references etc.). There are two main
|
||||||
as nodes, and links between them as edges. The generated graph can be used to
|
functions to use here:
|
||||||
navigate to the files, but this requires some additional setup (see [[*Roam
|
|
||||||
Protocol][Roam Protocol]]).
|
|
||||||
|
|
||||||
* Files
|
- ~org-roam-buffer~: Launch an Org-roam buffer for the current node at point.
|
||||||
|
- ~org-roam-buffer-toggle~: Launch an Org-roam buffer that tracks the node
|
||||||
|
currently at point. This means that the content of the buffer changes as the
|
||||||
|
point is moved, if necessary.
|
||||||
|
|
||||||
|
Use ~org-roam-buffer-toggle~ when you want wish for the Org-roam buffer to
|
||||||
|
buffer, call ~M-x org-roam-buffer~.
|
||||||
|
|
||||||
|
** Configuring what is displayed in the buffer
|
||||||
|
|
||||||
|
There are currently 3 provided widget types:
|
||||||
|
|
||||||
|
- Backlinks :: View (preview of) nodes that link to this node
|
||||||
|
- Reference Links :: Nodes that reference this node (see [[id:57c1f991-be38-4fab-b27d-60227047f3b7][Refs]])
|
||||||
|
- 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~.
|
||||||
|
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(setq org-roam-mode-sections
|
||||||
|
(list #'org-roam-backlinks-insert-section
|
||||||
|
#'org-roam-reflinks-insert-section
|
||||||
|
;; #'org-roam-unlinked-references-insert-section
|
||||||
|
))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Note that computing unlinked references may be slow, and you might want to turn
|
||||||
|
that off by default.
|
||||||
|
|
||||||
|
** Configuring the Org-roam buffer display
|
||||||
|
|
||||||
|
Org-roam does not control how the pop-up buffer is displayed: this is left to
|
||||||
|
the user. The author's recommended configuration is as follows:
|
||||||
|
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(add-to-list 'display-buffer-alist
|
||||||
|
'(("\\*org-roam\\*"
|
||||||
|
(display-buffer-in-direction)
|
||||||
|
(direction . right)
|
||||||
|
(window-width . 0.33)
|
||||||
|
(window-height . fit-window-to-buffer))))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Crucially, the window is a regular window (not a side-window), and this allows
|
||||||
|
for predictable navigation:
|
||||||
|
|
||||||
|
- ~RET~ navigates to thing-at-point in the current window, replacing the
|
||||||
|
Org-roam buffer.
|
||||||
|
- ~C-u RET~ navigates to thing-at-point in the other window.
|
||||||
|
|
||||||
|
* Node Properties
|
||||||
|
** Standard Org properties
|
||||||
|
|
||||||
|
Org-roam caches most of the standard Org properties. The full list now includes:
|
||||||
|
|
||||||
|
- outline level
|
||||||
|
- todo state
|
||||||
|
- priority
|
||||||
|
- scheduled
|
||||||
|
- deadline
|
||||||
|
- tags
|
||||||
|
|
||||||
|
** Titles and Aliases
|
||||||
|
|
||||||
|
Each node has a single title. For file nodes, this is specified with the
|
||||||
|
`#+title` property for the file. For headline nodes, this is the main text.
|
||||||
|
|
||||||
|
Nodes can also have multiple aliases. Aliases allow searching for nodes via an
|
||||||
|
alternative name. For example, one may want to assign a well-known acronym (AI)
|
||||||
|
to a node titled "Artificial Intelligence".
|
||||||
|
|
||||||
|
To assign an alias to a node, add the "ROAM_ALIASES" property to the node:
|
||||||
|
|
||||||
|
#+begin_src org
|
||||||
|
,* Artificial Intelligence
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ID: 3edec3e6-8e26-4a43-8a0a-bf204268bbb3
|
:ROAM_ALIASES: AI
|
||||||
:END:
|
:END:
|
||||||
|
#+end_src
|
||||||
|
|
||||||
In Org-roam, notes typically consist of multiple files, where each file is a
|
Alternatively, Org-roam provides some functions to add or remove aliases.
|
||||||
zettel.
|
|
||||||
|
|
||||||
While the bulk of Org-roam's functionality is built on top of vanilla Org-mode,
|
- Function: org-roam-alias-add alias
|
||||||
Org-roam adds several Org-roam-specific keywords to support additional
|
|
||||||
functionality.
|
|
||||||
|
|
||||||
This section explains the important components of a file, and the extensions to
|
Add ALIAS to the node at point. When called interactively, prompt for the
|
||||||
Org-mode.
|
alias to add.
|
||||||
|
|
||||||
** File Titles
|
- Function: org-roam-alias-remove
|
||||||
|
|
||||||
To easily find a note, a title needs to be prescribed to a note.
|
Remove an alias from the node at point.
|
||||||
|
|
||||||
A note can have many titles: this allows a note to be referred to by different
|
** Tags
|
||||||
names, which is especially useful for topics or concepts with acronyms. For
|
|
||||||
example, for a note like "World War 2", it may be desirable to also refer to it
|
|
||||||
using the acronym "WWII".
|
|
||||||
|
|
||||||
Org-roam calls ~org-roam--extract-titles~ to extract titles. It uses the
|
Tags for top-level (file) nodes are pulled from the variable ~org-file-tags~,
|
||||||
variable ~org-roam-title-sources~, to control how the titles are extracted. The
|
which is set by the ~#+filetags~ keyword, as well as other tags the file may
|
||||||
title extraction methods supported are:
|
have inherited. Tags for headline level nodes are regular Org tags.
|
||||||
|
|
||||||
1. ~'title~: This extracts the title using the file ~#+title~ property
|
Note that the ~#+filetags~ keyword results in tags being inherited by headers
|
||||||
2. ~'headline~: This extracts the title from the first headline in the Org file
|
within the file. This makes it impossible for selective tag inheritance: i.e.
|
||||||
3. ~'alias~: This extracts a list of titles using the ~#+roam_alias~ property.
|
either tag inheritance is turned off, or all headline nodes will inherit the
|
||||||
The aliases are space-delimited, and can be multi-worded using quotes.
|
tags from the file node. This is a design compromise of Org-roam.
|
||||||
|
|
||||||
Take for example the following org file:
|
** Refs
|
||||||
|
|
||||||
#+BEGIN_SRC org
|
Refs are unique identifiers for nodes. These keys allow references to the key to
|
||||||
#+title: World War 2
|
show up in the Org-roam buffer. For example, a node for a website may use the URL
|
||||||
#+roam_alias: "WWII" "World War II"
|
as the ref, and a node for a paper may use an Org-ref citation key.
|
||||||
|
|
||||||
* Headline
|
To add a ref, add to the "ROAM_REFS" property as follows:
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
| Method | Titles |
|
#+begin_src org
|
||||||
|-------------+--------------------------|
|
,* Google
|
||||||
| ~'title~ | '("World War 2") |
|
|
||||||
| ~'headline~ | '("Headline") |
|
|
||||||
| ~'alias~ | '("WWII" "World War II") |
|
|
||||||
|
|
||||||
If no title is provided, Org-roam defaults to using the file-path.
|
|
||||||
|
|
||||||
*** Customizing Title Extraction
|
|
||||||
|
|
||||||
To control how Org-roam extracts titles, customize ~org-roam-title-sources~. If
|
|
||||||
all methods of title extraction return no results, the file-name is used as the
|
|
||||||
note's title.
|
|
||||||
|
|
||||||
- User Option: org-roam-title-sources
|
|
||||||
|
|
||||||
The list of sources from which to retrieve a note title.
|
|
||||||
Each element in the list is either:
|
|
||||||
|
|
||||||
1. a symbol -- this symbol corresponds to a title retrieval function, which
|
|
||||||
returns the list of titles for the current buffer
|
|
||||||
2. a list of symbols -- symbols in the list are treated as with (1). The
|
|
||||||
return value of this list is the first symbol in the list returning a
|
|
||||||
non-nil value.
|
|
||||||
|
|
||||||
The return results of the root list are concatenated.
|
|
||||||
|
|
||||||
For example the setting: '((title headline) alias) means the following:
|
|
||||||
|
|
||||||
1. Return the 'title + 'alias, if the title of current buffer is non-empty;
|
|
||||||
2. Or return 'headline + 'alias otherwise.
|
|
||||||
|
|
||||||
The currently supported symbols are:
|
|
||||||
|
|
||||||
~'title~
|
|
||||||
The ~#+title~ property of org file.
|
|
||||||
|
|
||||||
~'alias~
|
|
||||||
The ~#+roam_alias~ property of the org file, using
|
|
||||||
space-delimited strings.
|
|
||||||
|
|
||||||
~'headline~
|
|
||||||
The first headline in the org file.
|
|
||||||
|
|
||||||
Adding your own title extraction method requires two steps. First, define a
|
|
||||||
method ~(defun org-roam--extract-titles-foo () ...)~, where ~foo~ a
|
|
||||||
self-prescribed name for the title extraction method. This method takes no
|
|
||||||
arguments, and returns a list of strings (titles). Finally, push the symbol
|
|
||||||
~foo~ into ~org-roam-title-sources~. You may need to rebuild the cache from
|
|
||||||
scratch to re-process all files to pick up the new titles.
|
|
||||||
|
|
||||||
** File Tags
|
|
||||||
|
|
||||||
Tags are used as meta-data for files: they facilitate interactions with notes
|
|
||||||
where titles are insufficient. For example, tags allow for categorization of
|
|
||||||
notes: differentiating between bibliographical and structure notes during
|
|
||||||
interactive commands.
|
|
||||||
|
|
||||||
By default, tags are extracted from the ~#+roam_tags~ property. To add
|
|
||||||
additional extraction methods, see [[id:c986edba-9498-4af1-b033-c94b733d42c8][Customizing Tag Extraction]].
|
|
||||||
|
|
||||||
*** Customizing Tag Extraction
|
|
||||||
:PROPERTIES:
|
:PROPERTIES:
|
||||||
:ID: c986edba-9498-4af1-b033-c94b733d42c8
|
:ROAM_REFS: https://www.google.com/
|
||||||
:END:
|
:END:
|
||||||
|
#+end_src
|
||||||
|
|
||||||
Org-roam calls ~org-roam--extract-tags~ to extract tags from files. The variable
|
With the above example, if another node links to https://www.google.com/, it
|
||||||
~org-roam-tag-sources~, to control how tags are extracted.
|
will show up as a “reference backlink”.
|
||||||
|
|
||||||
- User Option: org-roam-tag-sources
|
|
||||||
|
|
||||||
Sources to obtain tags from.
|
|
||||||
|
|
||||||
It should be a list of symbols representing any of the following extraction
|
|
||||||
methods:
|
|
||||||
|
|
||||||
~'prop~
|
|
||||||
Extract tags from the ~#+roam_tags~ property.
|
|
||||||
Tags are space delimited.
|
|
||||||
Tags may contain spaces if they are double-quoted.
|
|
||||||
e.g. ~#+roam_tags: TAG "tag with spaces"~
|
|
||||||
|
|
||||||
~'vanilla~
|
|
||||||
Extract vanilla org-mode tags, including ~#+FILETAGS~ and
|
|
||||||
inherited tags.
|
|
||||||
|
|
||||||
~'all-directories~
|
|
||||||
Extract sub-directories relative to ~org-roam-directory~.
|
|
||||||
That is, if a file is located at relative path foo/bar/file.org,
|
|
||||||
the file will have tags "foo" and "bar".
|
|
||||||
|
|
||||||
~'last-directory~
|
|
||||||
Extract the last directory relative to `org-roam-directory'.
|
|
||||||
That is, if a file is located at relative path foo/bar/file.org,
|
|
||||||
the file will have tag \"bar\".
|
|
||||||
|
|
||||||
~'first-directory~
|
|
||||||
Extract the first directory relative to ~org-roam-directory~.
|
|
||||||
That is, if a file is located at relative path foo/bar/file.org,
|
|
||||||
the file will have tag "foo"
|
|
||||||
|
|
||||||
By default, only the ~'prop~ extraction method is enabled. To enable the other
|
|
||||||
extraction methods, you may modify ~org-roam-tag-sources~, for example:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(setq org-roam-tag-sources '(prop last-directory))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Adding your own tag extraction method requires two steps. First, define a method
|
|
||||||
~(defun org-roam--extract-tags-foo (file) ...)~, where ~foo~ a self-prescribed
|
|
||||||
name for the tag extraction method. This method takes the file path as an
|
|
||||||
argument, and returns a list of strings (titles). Finally, push the symbol ~foo~
|
|
||||||
into ~org-roam-tag-sources~. You may need to rebuild the cache from scratch to
|
|
||||||
re-process all files to pick up the new tags.
|
|
||||||
|
|
||||||
** File Refs
|
|
||||||
|
|
||||||
Refs are unique identifiers for files. For example, a note for a website may
|
|
||||||
contain a ref:
|
|
||||||
|
|
||||||
#+BEGIN_SRC org
|
|
||||||
#+title: Google
|
|
||||||
#+roam_key: https://www.google.com/
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
These keys allow references to the key to show up in the backlinks buffer. For
|
|
||||||
instance, with the example above, if another file then links to
|
|
||||||
https://www.google.com, that will show up as a “Ref Backlink”.
|
|
||||||
|
|
||||||
These keys also come in useful for when taking website notes, using the
|
These keys also come in useful for when taking website notes, using the
|
||||||
~roam-ref~ protocol (see [[*Roam Protocol][Roam Protocol]]).
|
~roam-ref~ protocol (see [[*Roam Protocol][Roam Protocol]]).
|
||||||
|
|
||||||
[[https://github.com/jkitchin/org-ref][org-ref]] citation keys can also be used as refs:
|
You may assign multiple refs to a single node, for example when you want
|
||||||
|
|
||||||
#+BEGIN_SRC org
|
|
||||||
#+title: Neural Ordinary Differential Equations
|
|
||||||
#+roam_key: cite:chen18_neural_ordin_differ_equat
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
#+CAPTION: org-ref-citelink
|
|
||||||
[[file:images/org-ref-citelink.png]]
|
|
||||||
|
|
||||||
You may assign multiple refs to a single file, for example when you want
|
|
||||||
multiple papers in a series to share the same note, or an article has a citation
|
multiple papers in a series to share the same note, or an article has a citation
|
||||||
key and a URL at the same time.
|
key and a URL at the same time.
|
||||||
|
|
||||||
|
Org-roam also provides some functions to add or remove refs.
|
||||||
|
|
||||||
|
- Function: org-roam-ref-add ref
|
||||||
|
|
||||||
|
Add REF to the node at point. When called interactively, prompt for the
|
||||||
|
ref to add.
|
||||||
|
|
||||||
|
- Function: org-roam-ref-remove
|
||||||
|
|
||||||
|
Remove a ref from the node at point.
|
||||||
|
|
||||||
|
* TODO The Org-roam Buffer
|
||||||
|
|
||||||
|
The Org-roam buffer displays relationships between nodes. To show the
|
||||||
|
relationship between the current node at point with other nodes, call ~M-x
|
||||||
|
org-roam-buffer~.
|
||||||
|
|
||||||
|
- Function: org-roam-buffer
|
||||||
|
|
||||||
|
Launch an Org-roam buffer for the current node at point.
|
||||||
|
|
||||||
|
To bring up a buffer that tracks the current node at point, call ~M-x
|
||||||
|
org-roam-buffer-toggle~.
|
||||||
|
|
||||||
|
- Function: org-roam-buffer-toggle
|
||||||
|
|
||||||
|
Toggle display of the ~org-roam-buffer~.
|
||||||
|
|
||||||
|
** TODO Navigating the Org-roam Buffer
|
||||||
|
|
||||||
|
The Org-roam buffer uses ~magit-section~, making the typical ~magit-section~
|
||||||
|
keybindings available. Here are several of the more useful ones:
|
||||||
|
|
||||||
|
- ~M-{N}~: ~magit-section-show-level-{N}-all~
|
||||||
|
- ~n~: ~magit-section-forward~
|
||||||
|
-~<TAB>~: ~magit-section-toggle~
|
||||||
|
- ~<RET>~: ~org-roam-visit-thing~
|
||||||
|
|
||||||
|
~org-roam-visit-thing~ visits the item at point.
|
||||||
|
|
||||||
|
** TODO Customizing the Org-roam Buffer
|
||||||
|
|
||||||
|
* TODO Styling Org-roam
|
||||||
|
* TODO Completion
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: 70083bfd-d1e3-42b9-bf83-5b05708791c0
|
||||||
|
:END:
|
||||||
|
* TODO Encryption
|
||||||
|
* TODO Org-roam protocol
|
||||||
|
* TODO Diagnosing and Repair
|
||||||
|
* TODO Building Extensions
|
||||||
|
** TODO Public Interface
|
||||||
|
|
||||||
|
Database (for developers)
|
||||||
|
- querying: (org-roam-db-query)
|
||||||
|
- updating full database: (org-roam-db-sync)
|
||||||
|
- update current file: (org-roam-db-update-file)
|
||||||
|
- remove file from database: (org-roam-db-clear-file)
|
||||||
|
|
||||||
|
Nodes:
|
||||||
|
- List all nodes
|
||||||
|
- Read a node in from completion
|
||||||
|
- Get node at point
|
||||||
|
|
||||||
|
Links:
|
||||||
|
- Get backlinks for node
|
||||||
|
|
||||||
|
Tags:
|
||||||
|
- Add tag to node
|
||||||
|
- delete tag for node
|
||||||
|
- Get tags for node
|
||||||
|
|
||||||
|
Aliases:
|
||||||
|
- Add alias to node
|
||||||
|
- Delete alias for node
|
||||||
|
- Get aliases for node
|
||||||
|
|
||||||
|
Ref:
|
||||||
|
- Add ref to node
|
||||||
|
|
||||||
|
Capture:
|
||||||
|
|
||||||
|
Navigation:
|
||||||
|
- Find or create a node
|
||||||
|
|
||||||
|
* TODO The Org-mode Ecosystem
|
||||||
|
* TODO Frequently Asked Questions
|
||||||
|
* TODO Developer's Guide to Org-roam
|
||||||
|
** Org-roam's Design Principles
|
||||||
|
|
||||||
|
Org-roam is primarily motivated by the need for a dual representation. We
|
||||||
|
(humans) love operating in a plain-text environment. The syntax rules of
|
||||||
|
Org-mode are simple and fit snugly within our brain. This also allows us to use
|
||||||
|
the tools and packages we love to explore and edit our notes. Org-mode is simply
|
||||||
|
the most powerful plain-text format available, with support for images, LaTeX,
|
||||||
|
TODO planning and much more.
|
||||||
|
|
||||||
|
But this plain-text format is simply ill-suited for exploration of these notes:
|
||||||
|
plain-text is simply not amenable for answering large-scale, complex queries
|
||||||
|
(e.g. how many tasks do I have that are due by next week?). Interfaces such as
|
||||||
|
Org-agenda slow to a crawl when the number of files becomes unwieldy, which
|
||||||
|
can quickly become the case.
|
||||||
|
|
||||||
|
At its core, Org-roam provides a database abstraction layer, providing a dual
|
||||||
|
representation of what's already available in plain-text. This allows us
|
||||||
|
(humans) to continue working with plain-text, while programs can utilize the
|
||||||
|
database layer to perform complex queries. These capabilities include, but are
|
||||||
|
not limited to:
|
||||||
|
|
||||||
|
- link graph traversal and visualization
|
||||||
|
- Instantaneous SQL-like queries on headlines
|
||||||
|
- What are my TODOs, scheduled for X, or due by Y?
|
||||||
|
|
||||||
|
All of these functionality is powered by this database abstraction layer. Hence,
|
||||||
|
at its core Org-roam's primary goal is to provide a resilient dual
|
||||||
|
representation that is cheap to maintain, easy to understand, and is as
|
||||||
|
up-to-date as it possibly can. Org-roam also then exposes an API to this
|
||||||
|
database abstraction layer for users who would like to perform programmatic
|
||||||
|
queries on their Org files.
|
||||||
* The Templating System
|
* The Templating System
|
||||||
|
|
||||||
Rather than creating blank files on ~org-roam-insert~ and ~org-roam-find-file~,
|
Rather than creating blank files on ~org-roam-insert~ and ~org-roam-find-file~,
|
||||||
@ -627,7 +709,7 @@ the default template, reproduced below.
|
|||||||
3. ~plain~ text is inserted. Other options include Org headings via
|
3. ~plain~ text is inserted. Other options include Org headings via
|
||||||
~entry~.
|
~entry~.
|
||||||
4. ~(function org-roam--capture-get-point)~ should not be changed.
|
4. ~(function org-roam--capture-get-point)~ should not be changed.
|
||||||
5. ~"%?"~ is the template inserted on each call to ~org-roam-capture--capture~.
|
5. ~"%?"~ is the template inserted on each call to ~org-roam-capture-~.
|
||||||
This template means don't insert any content, but place the cursor here.
|
This template means don't insert any content, but place the cursor here.
|
||||||
6. ~:file-name~ is the file-name template for a new note, if it doesn't yet
|
6. ~:file-name~ is the file-name template for a new note, if it doesn't yet
|
||||||
exist. This creates a file at path that looks like
|
exist. This creates a file at path that looks like
|
||||||
@ -763,23 +845,6 @@ The ~org-roam-link-current~ face corresponds to links to the same file it is in.
|
|||||||
|
|
||||||
The ~org-roam-link-invalid~ face is applied to links that are broken. These are
|
The ~org-roam-link-invalid~ face is applied to links that are broken. These are
|
||||||
links to files or IDs that cannot be found.
|
links to files or IDs that cannot be found.
|
||||||
** TODO The Database
|
|
||||||
|
|
||||||
Org-roam is backed by a Sqlite database.
|
|
||||||
|
|
||||||
- User Option: org-roam-db-update-method
|
|
||||||
|
|
||||||
Method to update the Org-roam database.
|
|
||||||
|
|
||||||
~'immediate~: Update the database immediately upon file changes.
|
|
||||||
|
|
||||||
~'idle-timer~: Updates the database if dirty, if Emacs idles for
|
|
||||||
~org-roam-db-update-idle-seconds~.
|
|
||||||
|
|
||||||
- User Option: org-roam-db-update-idle-seconds
|
|
||||||
|
|
||||||
Number of idle seconds before triggering an Org-roam database update. This is
|
|
||||||
only valid if ~org-roam-db-update-method~ is ~'idle-timer~.
|
|
||||||
|
|
||||||
* Inserting Links
|
* Inserting Links
|
||||||
|
|
||||||
@ -844,11 +909,14 @@ to variable ~company-backends~.
|
|||||||
represents the cursor:
|
represents the cursor:
|
||||||
|
|
||||||
- ~[[|]]~: completes for a file title
|
- ~[[|]]~: completes for a file title
|
||||||
- ~[[roam:]]~: completes for a file title
|
- ~[[roam:|]]~: completes for a file title
|
||||||
- ~[[*|]]~: completes for a headline within this file
|
- ~[[*|]]~: completes for a headline within this file
|
||||||
- ~[[foo*|]]~: completes a headline within the file with title "foo"
|
- ~[[foo*|]]~: completes a headline within the file with title "foo"
|
||||||
- ~[[roam:foo*|]]~ completes a headline within the file with title "foo"
|
- ~[[roam:foo*|]]~ completes a headline within the file with title "foo"
|
||||||
|
|
||||||
|
If you don't see the literal display of your links like the above examples,
|
||||||
|
call ~M-x org-toggle-link-display~
|
||||||
|
|
||||||
Completions account for the current input. For example, for ~[[f|]]~, the
|
Completions account for the current input. For example, for ~[[f|]]~, the
|
||||||
completions (by default) only show for files with titles that start with "f".
|
completions (by default) only show for files with titles that start with "f".
|
||||||
|
|
||||||
@ -1183,7 +1251,7 @@ in the generated graph.
|
|||||||
|
|
||||||
** The roam-ref protocol
|
** The roam-ref protocol
|
||||||
|
|
||||||
This protocol finds or creates a new note with a given ~roam_key~ (see [[id:3edec3e6-8e26-4a43-8a0a-bf204268bbb3][Files]]):
|
This protocol finds or creates a new note with a given ~roam_key~:
|
||||||
|
|
||||||
[[file:images/roam-ref.gif]]
|
[[file:images/roam-ref.gif]]
|
||||||
|
|
||||||
@ -1275,7 +1343,7 @@ template ~j~ will put its notes under the heading ‘Journal’.
|
|||||||
|
|
||||||
Create an entry in the daily note for today.
|
Create an entry in the daily note for today.
|
||||||
|
|
||||||
When ~goto~ is non-nil, go the note without creating an entry.
|
When ~goto~ is non-nil, go to the note without creating an entry.
|
||||||
|
|
||||||
- Function: ~org-roam-dailies-find-today~
|
- Function: ~org-roam-dailies-find-today~
|
||||||
|
|
||||||
@ -1492,22 +1560,6 @@ using [[https://github.com/raxod502/el-patch][el-patch]]:
|
|||||||
|
|
||||||
(eval-when-compile
|
(eval-when-compile
|
||||||
(require 'el-patch))
|
(require 'el-patch))
|
||||||
|
|
||||||
(use-package deft
|
|
||||||
;; same as above...
|
|
||||||
:config/el-patch
|
|
||||||
(defun deft-parse-title (file contents)
|
|
||||||
"Parse the given FILE and CONTENTS and determine the title.
|
|
||||||
If `deft-use-filename-as-title' is nil, the title is taken to
|
|
||||||
be the first non-empty line of the FILE. Else the base name of the FILE is
|
|
||||||
used as title."
|
|
||||||
(el-patch-swap (if deft-use-filename-as-title
|
|
||||||
(deft-base-filename file)
|
|
||||||
(let ((begin (string-match "^.+$" contents)))
|
|
||||||
(if begin
|
|
||||||
(funcall deft-parse-title-function
|
|
||||||
(substring contents begin (match-end 0))))))
|
|
||||||
(org-roam-db--get-title file))))
|
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
The Deft interface can slow down quickly when the number of files get huge.
|
The Deft interface can slow down quickly when the number of files get huge.
|
||||||
@ -1613,6 +1665,9 @@ file with the right =#+roam_key=.
|
|||||||
[[https://www.leonrische.me/fc/index.html][Org-fc]] is a spaced repetition system that scales well with a large number of
|
[[https://www.leonrische.me/fc/index.html][Org-fc]] is a spaced repetition system that scales well with a large number of
|
||||||
files. Other alternatives include [[https://orgmode.org/worg/org-contrib/org-drill.html][org-drill]], and [[https://github.com/abo-abo/pamparam][pamparam]].
|
files. Other alternatives include [[https://orgmode.org/worg/org-contrib/org-drill.html][org-drill]], and [[https://github.com/abo-abo/pamparam][pamparam]].
|
||||||
|
|
||||||
|
To use Anki for spaced repetition, [[https://github.com/louietan/anki-editor][anki-editor]] allows you to write your cards in
|
||||||
|
Org-mode, and sync your cards to Anki via [[https://github.com/FooSoft/anki-connect#installation][anki-connect]].
|
||||||
|
|
||||||
* FAQ
|
* FAQ
|
||||||
** How do I have more than one Org-roam directory?
|
** How do I have more than one Org-roam directory?
|
||||||
|
|
||||||
@ -1675,16 +1730,17 @@ are the solutions:
|
|||||||
:END:
|
:END:
|
||||||
|
|
||||||
* Footnotes
|
* Footnotes
|
||||||
|
|
||||||
[fn:1] Depending on your completion framework, you may need to press TAB to
|
[fn:1] Depending on your completion framework, you may need to press TAB to
|
||||||
see the list.
|
see the list.
|
||||||
|
[fn:2] Two easy ways to evaluate elisp: 1) Place the cursor after the closing
|
||||||
|
paren and run =M-x eval-last-sexp RET= or 2) Press =C-c C-c= with your cursor in
|
||||||
|
an Org file code block (like =#+BEGIN_SRC emacs-lisp=).
|
||||||
[fn:roam] To understand more about Roam, a collection of links are available in [[*Note-taking Workflows][Note-taking Workflows]].
|
[fn:roam] To understand more about Roam, a collection of links are available in [[*Note-taking Workflows][Note-taking Workflows]].
|
||||||
|
|
||||||
# Local Variables:
|
# Local Variables:
|
||||||
# eval: (require 'ol-info)
|
# eval: (require 'ol-info)
|
||||||
# eval: (require 'ox-texinfo+ nil t)
|
# eval: (require 'ox-texinfo+ nil t)
|
||||||
# eval: (auto-fill-mode +1)
|
# eval: (auto-fill-mode +1)
|
||||||
# before-save-hook: org-make-toc
|
|
||||||
# after-save-hook: (lambda nil (progn (require 'ox-texinfo nil t) (org-texinfo-export-to-info)))
|
# after-save-hook: (lambda nil (progn (require 'ox-texinfo nil t) (org-texinfo-export-to-info)))
|
||||||
# indent-tabs-mode: nil
|
# indent-tabs-mode: nil
|
||||||
# org-src-preserve-indentation: nil
|
# org-src-preserve-indentation: nil
|
||||||
|
@ -25,13 +25,13 @@ General Public License for more details.
|
|||||||
|
|
||||||
@dircategory Emacs
|
@dircategory Emacs
|
||||||
@direntry
|
@direntry
|
||||||
* Org-roam: (org-roam). Rudimentary Roam Replica for Emacs.
|
* Org-roam: (org-roam). Roam Research for Emacs.
|
||||||
@end direntry
|
@end direntry
|
||||||
|
|
||||||
@finalout
|
@finalout
|
||||||
@titlepage
|
@titlepage
|
||||||
@title Org-roam User Manual
|
@title Org-roam User Manual
|
||||||
@subtitle for version 1.2.3
|
@subtitle for version 2.0.0
|
||||||
@author Jethro Kuan
|
@author Jethro Kuan
|
||||||
@page
|
@page
|
||||||
@vskip 0pt plus 1filll
|
@vskip 0pt plus 1filll
|
||||||
@ -44,10 +44,10 @@ General Public License for more details.
|
|||||||
|
|
||||||
@noindent
|
@noindent
|
||||||
|
|
||||||
This manual is for Org-roam version 1.2.3.
|
This manual is for Org-roam version 2.0.0.
|
||||||
|
|
||||||
@quotation
|
@quotation
|
||||||
Copyright (C) 2020-2020 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
|
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
|
General Public License as published by the Free Software Foundation, either
|
||||||
@ -67,13 +67,24 @@ General Public License for more details.
|
|||||||
* A Brief Introduction to the Zettelkasten Method::
|
* A Brief Introduction to the Zettelkasten Method::
|
||||||
* Installation::
|
* Installation::
|
||||||
* Getting Started::
|
* Getting Started::
|
||||||
* Files::
|
* Viewing the links::
|
||||||
|
* Node Properties::
|
||||||
|
* The Org-roam Buffer::
|
||||||
|
* Styling Org-roam::
|
||||||
|
* Completion::
|
||||||
|
* Encryption::
|
||||||
|
* Org-roam protocol::
|
||||||
|
* Diagnosing and Repair::
|
||||||
|
* Building Extensions::
|
||||||
|
* The Org-mode Ecosystem::
|
||||||
|
* Frequently Asked Questions::
|
||||||
|
* Developer's Guide to Org-roam::
|
||||||
* The Templating System::
|
* The Templating System::
|
||||||
* Concepts and Configuration::
|
* Concepts and Configuration::
|
||||||
* Inserting Links::
|
* Inserting Links::
|
||||||
* Completions::
|
* Completions::
|
||||||
* Navigating Around::
|
* Navigating Around::
|
||||||
* Encryption::
|
* Encryption: Encryption (1).
|
||||||
* Graphing::
|
* Graphing::
|
||||||
* Minibuffer Completion::
|
* Minibuffer Completion::
|
||||||
* Roam Protocol::
|
* Roam Protocol::
|
||||||
@ -91,31 +102,33 @@ General Public License for more details.
|
|||||||
@detailmenu
|
@detailmenu
|
||||||
--- The Detailed Node Listing ---
|
--- The Detailed Node Listing ---
|
||||||
|
|
||||||
A Brief Introduction to the Zettelkasten Method
|
|
||||||
|
|
||||||
* Fleeting notes::
|
|
||||||
* Permanent notes::
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
|
|
||||||
* Installing from MELPA::
|
* Installing from MELPA::
|
||||||
* Installing from Apt::
|
* Installing from Apt::
|
||||||
* Installing from the Git Repository::
|
* Installing from Source::
|
||||||
* Post-Installation Tasks::
|
* Post-Installation Tasks::
|
||||||
|
|
||||||
Files
|
Getting Started
|
||||||
|
|
||||||
* File Titles::
|
* The Org-roam Node::
|
||||||
* File Tags::
|
* Links between Nodes::
|
||||||
* File Refs::
|
* Setting up Org-roam::
|
||||||
|
* Creating and Linking Nodes::
|
||||||
|
|
||||||
File Titles
|
Node Properties
|
||||||
|
|
||||||
* Customizing Title Extraction::
|
* Standard Org properties::
|
||||||
|
* Aliases::
|
||||||
|
* Refs::
|
||||||
|
|
||||||
File Tags
|
Building Extensions
|
||||||
|
|
||||||
* Customizing Tag Extraction::
|
* Public Interface::
|
||||||
|
|
||||||
|
Developer's Guide to Org-roam
|
||||||
|
|
||||||
|
* Org-roam's Design Principles::
|
||||||
|
|
||||||
The Templating System
|
The Templating System
|
||||||
|
|
||||||
@ -125,10 +138,9 @@ The Templating System
|
|||||||
Concepts and Configuration
|
Concepts and Configuration
|
||||||
|
|
||||||
* Directories and Files::
|
* Directories and Files::
|
||||||
* The Org-roam Buffer::
|
* The Org-roam Buffer: The Org-roam Buffer (1).
|
||||||
* Org-roam Files::
|
* Org-roam Files::
|
||||||
* Org-roam Faces::
|
* Org-roam Faces::
|
||||||
* The Database::
|
|
||||||
|
|
||||||
Completions
|
Completions
|
||||||
|
|
||||||
@ -198,28 +210,24 @@ FAQ
|
|||||||
@node Introduction
|
@node Introduction
|
||||||
@chapter Introduction
|
@chapter Introduction
|
||||||
|
|
||||||
Org-roam is a tool for network thought. It reproduces some of @uref{https://roamresearch.com/, Roam
|
Org-roam is a tool for networked thought. It reproduces some of @uref{https://roamresearch.com/, Roam
|
||||||
Research's} @footnote{To understand more about Roam, a collection of links are available in @ref{Note-taking Workflows}.} features within the all-powerful @uref{https://orgmode.org/, Org-mode}.
|
Research's} @footnote{To understand more about Roam, a collection of links are available in @ref{Note-taking Workflows}.} key features within @uref{https://orgmode.org/, Org-mode}.
|
||||||
|
|
||||||
Org-roam is a solution for effortless non-hierarchical note-taking with
|
Org-roam allows for effortless non-hierarchical note-taking: with Org-roam,
|
||||||
Org-mode. With Org-roam, notes flow naturally, making note-taking fun and easy.
|
notes flow naturally, making note-taking fun and easy. Org-roam augments the
|
||||||
Org-roam keeps closely to Org syntax, and will work for anyone already using
|
Org-mode syntax, and will work for anyone already using Org-mode for their
|
||||||
Org-mode for their personal wiki.
|
personal wiki.
|
||||||
|
|
||||||
Org-roam gains its superpowers by leveraging the mature ecosystem around
|
Org-roam leverages the mature ecosystem around Org-mode. For example, it has
|
||||||
Org-mode. For example, it has first-class support for @uref{https://github.com/jkitchin/org-ref, org-ref} for citation
|
first-class support for @uref{https://github.com/jkitchin/org-ref, org-ref} for citation management, and is able to
|
||||||
management.
|
piggyback off Org's excellent @LaTeX{} and source-block evaluation capabilities.
|
||||||
|
|
||||||
Org-roam aims to implement the core features of Roam, leveraging the
|
Org-roam provides these benefits over other tooling:
|
||||||
mature ecosystem around Org-mode where possible. Eventually, we hope
|
|
||||||
to further introduce features enabled by the Emacs ecosystem.
|
|
||||||
|
|
||||||
Org-roam provides several benefits over other tooling:
|
|
||||||
|
|
||||||
@itemize
|
@itemize
|
||||||
@item
|
@item
|
||||||
@strong{Privacy and Security:} Keep your personal wiki entirely offline and in your
|
@strong{Privacy and Security:} Your personal wiki belongs only to you, entirely
|
||||||
control. Encrypt your notes with GPG@.
|
offline and in your control. Encrypt your notes with GPG@.
|
||||||
|
|
||||||
@item
|
@item
|
||||||
@strong{Longevity of Plain Text:} Unlike web solutions like Roam Research, the notes
|
@strong{Longevity of Plain Text:} Unlike web solutions like Roam Research, the notes
|
||||||
@ -235,13 +243,13 @@ you feel unhappy with any part of Org-roam, you may choose to extend Org-roam,
|
|||||||
or open a pull request.
|
or open a pull request.
|
||||||
|
|
||||||
@item
|
@item
|
||||||
@strong{Leverage the Org-mode ecosystem:} Over the years, Emacs and Org-mode has
|
@strong{Leverage the Org-mode ecosystem:} Over the decades, Emacs and Org-mode has
|
||||||
developed into a mature system for plain-text organization. Building upon
|
developed into a mature system for plain-text organization. Building upon
|
||||||
Org-mode already puts Org-roam light-years ahead of many other solutions.
|
Org-mode already puts Org-roam light-years ahead of many other solutions.
|
||||||
|
|
||||||
@item
|
@item
|
||||||
@strong{Built on Emacs:} Emacs is also a fantastic interface for editing text, and we
|
@strong{Built on Emacs:} Emacs is also a fantastic interface for editing text, and
|
||||||
can inherit many of the powerful text-navigation and editing packages
|
Org-roam inherits many of the powerful text-navigation and editing packages
|
||||||
available to Emacs.
|
available to Emacs.
|
||||||
@end itemize
|
@end itemize
|
||||||
|
|
||||||
@ -249,18 +257,18 @@ available to Emacs.
|
|||||||
@chapter Target Audience
|
@chapter Target Audience
|
||||||
|
|
||||||
Org-roam is a tool that will appear unfriendly to anyone unfamiliar with Emacs
|
Org-roam is a tool that will appear unfriendly to anyone unfamiliar with Emacs
|
||||||
and Org-mode, but is also extremely powerful to those willing to put effort in
|
and Org-mode, but it is also extremely powerful to those willing to put effort
|
||||||
mastering the intricacies of the tools. Org-roam stands on the shoulders on
|
inn mastering the intricacies. Org-roam stands on the shoulders of giants. Emacs
|
||||||
giants. Emacs was first created in 1976, and remains a top tier tool for editing
|
was first created in 1976, and remains the tool of choice for many for editing
|
||||||
text and designing textual interfaces. The malleability of Emacs allowed the
|
text and designing textual interfaces. The malleability of Emacs allowed the
|
||||||
creation of Org-mode, an all-purpose plain-text system for maintaining TODO
|
creation of Org-mode, an all-purpose plain-text system for maintaining TODO
|
||||||
lists, planning projects, and authoring documents. Both of these tools are
|
lists, planning projects, and authoring documents. Both of these tools are
|
||||||
incredibly vast and require significant time investment to master.
|
incredibly vast and require significant time investment to master.
|
||||||
|
|
||||||
Org-roam assumes basic familiarity with these tools. It is not difficult to get
|
Org-roam assumes only basic familiarity with these tools. It is not difficult to
|
||||||
up and running with basic text-editing functionality, but one will only fully
|
get up and running with basic text-editing functionality, but one will only
|
||||||
appreciate the power of building Roam functionality into Emacs and Org-mode when
|
fully appreciate the power of building Roam functionality into Emacs and
|
||||||
the usage of these tools become more advanced.
|
Org-mode when the usage of these tools become more advanced.
|
||||||
|
|
||||||
One key advantage to Org-roam is that building on top of Emacs gives it
|
One key advantage to Org-roam is that building on top of Emacs gives it
|
||||||
malleability. This is especially important for note-taking workflows. It is our
|
malleability. This is especially important for note-taking workflows. It is our
|
||||||
@ -313,13 +321,7 @@ plain-text, Org-mode file. In the same way one would maintain a paper slip-box,
|
|||||||
Org-roam makes it easy to create new zettels, pre-filling boilerplate content
|
Org-roam makes it easy to create new zettels, pre-filling boilerplate content
|
||||||
using a powerful templating system.
|
using a powerful templating system.
|
||||||
|
|
||||||
@menu
|
@strong{Fleeting notes}
|
||||||
* Fleeting notes::
|
|
||||||
* Permanent notes::
|
|
||||||
@end menu
|
|
||||||
|
|
||||||
@node Fleeting notes
|
|
||||||
@section Fleeting notes
|
|
||||||
|
|
||||||
A slip-box requires a method for quickly capturing ideas. These are called
|
A slip-box requires a method for quickly capturing ideas. These are called
|
||||||
@strong{fleeting notes}: they are simple reminders of information or ideas that will
|
@strong{fleeting notes}: they are simple reminders of information or ideas that will
|
||||||
@ -328,8 +330,7 @@ need to be processed later on, or trashed. This is typically accomplished using
|
|||||||
functionality (see @ref{Daily-notes}). This provides a central inbox for collecting
|
functionality (see @ref{Daily-notes}). This provides a central inbox for collecting
|
||||||
thoughts, to be processed later into permanent notes.
|
thoughts, to be processed later into permanent notes.
|
||||||
|
|
||||||
@node Permanent notes
|
@strong{Permanent notes}
|
||||||
@section Permanent notes
|
|
||||||
|
|
||||||
Permanent notes are further split into two categories: @strong{literature notes} and
|
Permanent notes are further split into two categories: @strong{literature notes} and
|
||||||
@strong{concept notes}. Literature notes can be brief annotations on a particular
|
@strong{concept notes}. Literature notes can be brief annotations on a particular
|
||||||
@ -338,6 +339,9 @@ Concept notes require much more care in authoring: they need to be
|
|||||||
self-explanatory and detailed. Org-roam's templating system supports the
|
self-explanatory and detailed. Org-roam's templating system supports the
|
||||||
addition of different templates to facilitate the creation of these notes.
|
addition of different templates to facilitate the creation of these notes.
|
||||||
|
|
||||||
|
For further reading on the Zettelkasten method, ``How to Take Smart Notes'' by
|
||||||
|
Sonke Ahrens is a decent guide.
|
||||||
|
|
||||||
@node Installation
|
@node Installation
|
||||||
@chapter Installation
|
@chapter Installation
|
||||||
|
|
||||||
@ -347,7 +351,7 @@ development repository.
|
|||||||
@menu
|
@menu
|
||||||
* Installing from MELPA::
|
* Installing from MELPA::
|
||||||
* Installing from Apt::
|
* Installing from Apt::
|
||||||
* Installing from the Git Repository::
|
* Installing from Source::
|
||||||
* Post-Installation Tasks::
|
* Post-Installation Tasks::
|
||||||
@end menu
|
@end menu
|
||||||
|
|
||||||
@ -416,8 +420,8 @@ apt-get install elpa-org-roam
|
|||||||
|
|
||||||
Org-roam will then be autoloaded into Emacs.
|
Org-roam will then be autoloaded into Emacs.
|
||||||
|
|
||||||
@node Installing from the Git Repository
|
@node Installing from Source
|
||||||
@section Installing from the Git Repository
|
@section Installing from Source
|
||||||
|
|
||||||
You may install Org-roam directly from the repository on @uref{https://github.com/org-roam/org-roam, GitHub} if you like.
|
You may install Org-roam directly from the repository on @uref{https://github.com/org-roam/org-roam, GitHub} if you like.
|
||||||
This will give you access to the latest version hours or days before it appears
|
This will give you access to the latest version hours or days before it appears
|
||||||
@ -520,18 +524,19 @@ install-info /path/to/my/info/files/org-roam.info /path/to/my/info/files/dir
|
|||||||
@node Post-Installation Tasks
|
@node Post-Installation Tasks
|
||||||
@section Post-Installation Tasks
|
@section Post-Installation Tasks
|
||||||
|
|
||||||
Org-roam uses @code{emacsql-sqlite3}, which requires @code{sqlite3} to be located on
|
Org-roam requires @code{sqlite3} to be locatable by Emacs (i.e. on @code{exec-path}).
|
||||||
@code{exec-path}. Please ensure that @code{sqlite3} is installed appropriately on your
|
Please ensure that @code{sqlite3} is installed appropriately on your operating
|
||||||
operating system. You can verify that this is the case by executing:
|
system. You can verify that this is the case by executing @footnote{Two easy ways to evaluate elisp: 1) Place the cursor after the closing
|
||||||
|
paren and run @samp{M-x eval-last-sexp RET} or 2) Press @samp{C-c C-c} with your cursor in
|
||||||
|
an Org file code block (like @samp{#+BEGIN_SRC emacs-lisp}).}:
|
||||||
|
|
||||||
@lisp
|
@lisp
|
||||||
(executable-find "sqlite3")
|
(executable-find "sqlite3")
|
||||||
@end lisp
|
@end lisp
|
||||||
|
|
||||||
If you have @code{sqlite3} installed, and @code{executable-find} still reports @code{nil}, then
|
If you have @code{sqlite3} installed, and @code{executable-find} still reports @code{nil}, then
|
||||||
it is likely that the path to the executable is not a member of the Emacs
|
the path to the executable is not a member of the Emacs variable @code{exec-path}.
|
||||||
variable @code{exec-path}. You may rectify this by manually adding the path within
|
Rectify this by manually adding the path within your Emacs configuration:
|
||||||
your Emacs configuration:
|
|
||||||
|
|
||||||
@lisp
|
@lisp
|
||||||
(add-to-list 'exec-path "path/to/sqlite3")
|
(add-to-list 'exec-path "path/to/sqlite3")
|
||||||
@ -540,283 +545,145 @@ your Emacs configuration:
|
|||||||
@node Getting Started
|
@node Getting Started
|
||||||
@chapter Getting Started
|
@chapter Getting Started
|
||||||
|
|
||||||
This short tutorial describes the essential commands used in Org-roam, to help
|
@menu
|
||||||
you get started.
|
* The Org-roam Node::
|
||||||
|
* Links between Nodes::
|
||||||
|
* Setting up Org-roam::
|
||||||
|
* Creating and Linking Nodes::
|
||||||
|
@end menu
|
||||||
|
|
||||||
First, it is important to understand how Org-roam was designed. Org-roam was
|
@node The Org-roam Node
|
||||||
built to support a workflow that was not possible with vanilla Org-mode. This
|
@section The Org-roam Node
|
||||||
flow is modelled after the @uref{https://zettelkasten.de/, Zettelkasten Method}, and many of @uref{https://roamresearch.com, Roam Research's}
|
|
||||||
workflows. Org-roam does not magically make note-taking better -- this often
|
We first begin with some terminology we'll use throughout the manual. We term
|
||||||
requires a radical change in your current note-taking workflow. To understand
|
the basic denomination in Org-roam a node. We define a node as follows:
|
||||||
more about the methods and madness, see @ref{Note-taking Workflows}.
|
|
||||||
|
@quotation
|
||||||
|
A node is any headline or top level file with an ID@.
|
||||||
|
|
||||||
|
@end quotation
|
||||||
|
|
||||||
|
For example, with this example file content:
|
||||||
|
|
||||||
|
@example
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: foo
|
||||||
|
:END:
|
||||||
|
#+title: Foo
|
||||||
|
|
||||||
|
* Bar
|
||||||
|
:PROPERTIES:
|
||||||
|
:ID: bar
|
||||||
|
:END:
|
||||||
|
@end example
|
||||||
|
|
||||||
|
We create two nodes:
|
||||||
|
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
A file node ``Foo'' with id @code{foo}.
|
||||||
|
|
||||||
|
@item
|
||||||
|
A headline node ``Bar'' with id @code{bar}.
|
||||||
|
@end itemize
|
||||||
|
|
||||||
|
Headlines without IDs will not be considered Org-roam nodes. Org IDs can be
|
||||||
|
added to files or headlines via the interactive command @code{M-x org-id-get-create}.
|
||||||
|
|
||||||
|
@node Links between Nodes
|
||||||
|
@section Links between Nodes
|
||||||
|
|
||||||
|
We link between nodes using Org's standard ID link (e.g. @code{id:foo}). While only
|
||||||
|
ID links will be considered during the computation of links between nodes,
|
||||||
|
Org-roam caches all other links in the documents for external use.
|
||||||
|
|
||||||
|
@node Setting up Org-roam
|
||||||
|
@section Setting up Org-roam
|
||||||
|
|
||||||
|
Org-roam's capabilities stem from its aggressive caching: it crawls all files
|
||||||
|
within @code{org-roam-directory}, keeping a cache of all its links and nodes, while
|
||||||
|
making sure that the cache is consistent.
|
||||||
|
|
||||||
To first start using Org-roam, one needs to pick a location to store the
|
To first start using Org-roam, one needs to pick a location to store the
|
||||||
Org-roam files. The directory that will contain your notes is specified by the
|
Org-roam files. The directory that will contain your notes is specified by the
|
||||||
variable @code{org-roam-directory}. This variable needs to be set before any calls to
|
variable @code{org-roam-directory}. Org-roam searches recursively within
|
||||||
Org-roam functions, including enabling @code{org-roam-mode}. For this tutorial,
|
@code{org-roam-directory} for notes. This variable needs to be set before any calls
|
||||||
create an empty directory, and set @code{org-roam-directory}:
|
to Org-roam functions. For this tutorial, create an empty directory, and set
|
||||||
|
@code{org-roam-directory}:
|
||||||
|
|
||||||
@lisp
|
@lisp
|
||||||
(make-directory "~/org-roam")
|
(make-directory "~/org-roam")
|
||||||
(setq org-roam-directory "~/org-roam")
|
(setq org-roam-directory "~/org-roam")
|
||||||
@end lisp
|
@end lisp
|
||||||
|
|
||||||
We encourage using a flat hierarchy for storing notes, but some prefer using
|
Next, we need to setup Org-roam to maintain cache consistency. This is achieved
|
||||||
folders for storing specific kinds of notes (e.g. websites, papers). This is
|
by running @code{M-x org-roam-setup}. To ensure that Org-roam is available on
|
||||||
fine; Org-roam searches recursively within @code{org-roam-directory} for notes.
|
startup, one can place this in their Emacs configuration:
|
||||||
Instead of relying on the file hierarchy for any form of categorization, one
|
|
||||||
should use links between files to establish connections between notes.
|
|
||||||
|
|
||||||
Next, we need to enable the global minor mode @code{org-roam-mode}. This sets up
|
|
||||||
Emacs with several hooks, building a cache that is kept consistent as your
|
|
||||||
slip-box grows. We recommend starting @code{org-roam-mode} on startup:
|
|
||||||
|
|
||||||
@lisp
|
@lisp
|
||||||
(add-hook 'after-init-hook 'org-roam-mode)
|
(require 'org-roam)
|
||||||
|
(org-roam-setup)
|
||||||
@end lisp
|
@end lisp
|
||||||
|
|
||||||
To build the cache manually, one can run @code{M-x org-roam-db-build-cache}. Cache
|
To build the cache manually, one can run @code{M-x org-roam-db-build-cache}. Cache
|
||||||
builds may take a while the first time, but is often instantaneous in subsequent
|
builds may take a while the first time, but is often instantaneous in subsequent
|
||||||
runs because it only reprocesses modified files.
|
runs because it only reprocesses modified files.
|
||||||
|
|
||||||
Let us now create our first note. Call @code{M-x org-roam-find-file}. This shows a
|
@node Creating and Linking Nodes
|
||||||
list of titles for notes that reside in @code{org-roam-directory}. It should show
|
@section @strong{TODO} Creating and Linking Nodes
|
||||||
nothing right now, since there are no notes in the directory. Entering the title
|
|
||||||
of the note you wish to create, and pressing @code{RET} should begin the note
|
Org-roam makes it easy to create notes and link them together. There are 2 main
|
||||||
|
functions for creating nodes:
|
||||||
|
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
@code{org-roam-node-insert}: creates a node if it does not exist, and inserts a
|
||||||
|
link to the node at point.
|
||||||
|
|
||||||
|
@item
|
||||||
|
@code{org-roam-node-find}: creates a node if it does not exist, and visits the
|
||||||
|
node.
|
||||||
|
@end itemize
|
||||||
|
|
||||||
|
Let's first try @code{org-roam-node-find}. Calling @code{M-x org-roam-node-find} will
|
||||||
|
show a list of titles for nodes that reside in @code{org-roam-directory}. It should
|
||||||
|
show nothing right now, since there are no notes in the directory. Enter the
|
||||||
|
title of the note you wish to create, and press @code{RET}. This begins the note
|
||||||
creation process. This process uses @code{org-capture}'s templating system, and can
|
creation process. This process uses @code{org-capture}'s templating system, and can
|
||||||
be customized (see @ref{The Templating System}). Using the default template, pressing
|
be customized (see @ref{The Templating System}). Using the default template, pressing
|
||||||
@code{C-c C-c} finishes the note capture.
|
@code{C-c C-c} finishes the note capture.
|
||||||
|
|
||||||
By default, Org-roam updates the cache asynchronously in the background to
|
Now that we have a node, we can try inserting a link to the node using @code{M-x
|
||||||
avoid getting in the way of writing. Org-roam queues updates to the files,
|
org-roam-node-insert}. This brings up the list of nodes, which should contain
|
||||||
waits for you to be idle for 2 seconds, and then automatically triggers
|
the node you just created. Selecting the node will insert an @code{id:} link to the
|
||||||
updating the cache. After the cache has been updated, running @code{M-x
|
node. If you instead entered a title that does not exist, you will once again be
|
||||||
org-roam-find-file} again should show the note you have created, and selecting
|
brought through the node creation process. To enable link auto-completion,
|
||||||
that entry will bring you to that note @footnote{Depending on your completion framework, you may need to press TAB to
|
see @ref{Completion}.
|
||||||
see the list.}. One can customize the waiting
|
|
||||||
time by setting @code{org-roam-db-update-idle-seconds}; or change the cache update
|
|
||||||
to be triggered immediately after buffer save by setting
|
|
||||||
@code{org-roam-db-update-method} to @code{'immediate}.
|
|
||||||
|
|
||||||
For experienced @code{org-capture} users, the behavior of @code{M-x org-roam-find-file}
|
@node Viewing the links
|
||||||
may seem unfamiliar: after finishing a capture with @code{C-c C-c}, you are returned
|
@chapter @strong{TODO} Viewing the links
|
||||||
not to the original buffer from which you called @code{M-x org-roam-find-file}, but
|
|
||||||
to a buffer pointing to the note you just created. For the usual @code{org-capture}
|
|
||||||
behavior you can call @code{M-x org-roam-capture} instead of @code{M-x org-roam-find-file}.
|
|
||||||
|
|
||||||
Org-roam makes it easy to create notes, and link them together. To link notes
|
Org-roam provides an interface to view relationships with other notes
|
||||||
together, we call @code{M-x org-roam-insert}. This brings up a prompt with a list of
|
(backlinks, reference links, unlinked references etc.). To pop up this info
|
||||||
title for existing notes. Selecting an existing entry will create and insert a
|
buffer, call @code{M-x org-roam-buffer}.
|
||||||
link to the current file. Entering a non-existent title will create a new note
|
|
||||||
with that title. Good usage of Org-roam requires liberally linking files: this
|
|
||||||
facilitates building up a dense graph of inter-connected notes.
|
|
||||||
|
|
||||||
Org-roam provides an interface to view backlinks. It shows backlinks for the
|
@node Node Properties
|
||||||
currently active Org-roam note, along with some surrounding context. To toggle
|
@chapter @strong{TODO} Node Properties
|
||||||
the visibility of this buffer, call @code{M-x org-roam}.
|
|
||||||
|
|
||||||
For a visual representation of the notes and their connections, Org-roam also
|
|
||||||
provides graphing capabilities, using Graphviz. It generates graphs with notes
|
|
||||||
as nodes, and links between them as edges. The generated graph can be used to
|
|
||||||
navigate to the files, but this requires some additional setup (see @ref{Roam Protocol}).
|
|
||||||
|
|
||||||
@node Files
|
|
||||||
@chapter Files
|
|
||||||
|
|
||||||
In Org-roam, notes typically consist of multiple files, where each file is a
|
|
||||||
zettel.
|
|
||||||
|
|
||||||
While the bulk of Org-roam's functionality is built on top of vanilla Org-mode,
|
|
||||||
Org-roam adds several Org-roam-specific keywords to support additional
|
|
||||||
functionality.
|
|
||||||
|
|
||||||
This section explains the important components of a file, and the extensions to
|
|
||||||
Org-mode.
|
|
||||||
|
|
||||||
@menu
|
@menu
|
||||||
* File Titles::
|
* Standard Org properties::
|
||||||
* File Tags::
|
* Aliases::
|
||||||
* File Refs::
|
* Refs::
|
||||||
@end menu
|
@end menu
|
||||||
|
|
||||||
@node File Titles
|
@node Standard Org properties
|
||||||
@section File Titles
|
@section @strong{TODO} Standard Org properties
|
||||||
|
|
||||||
To easily find a note, a title needs to be prescribed to a note.
|
@node Aliases
|
||||||
|
@section @strong{TODO} Aliases
|
||||||
|
|
||||||
A note can have many titles: this allows a note to be referred to by different
|
@node Refs
|
||||||
names, which is especially useful for topics or concepts with acronyms. For
|
@section @strong{TODO} Refs
|
||||||
example, for a note like ``World War 2'', it may be desirable to also refer to it
|
|
||||||
using the acronym ``WWII''.
|
|
||||||
|
|
||||||
Org-roam calls @code{org-roam--extract-titles} to extract titles. It uses the
|
|
||||||
variable @code{org-roam-title-sources}, to control how the titles are extracted. The
|
|
||||||
title extraction methods supported are:
|
|
||||||
|
|
||||||
@itemize
|
|
||||||
@item
|
|
||||||
@code{'title}: This extracts the title using the file @code{#+title} property
|
|
||||||
|
|
||||||
@item
|
|
||||||
@code{'headline}: This extracts the title from the first headline in the Org file
|
|
||||||
|
|
||||||
@item
|
|
||||||
@code{'alias}: This extracts a list of titles using the @code{#+roam_alias} property.
|
|
||||||
The aliases are space-delimited, and can be multi-worded using quotes.
|
|
||||||
@end itemize
|
|
||||||
|
|
||||||
Take for example the following org file:
|
|
||||||
|
|
||||||
@example
|
|
||||||
#+title: World War 2
|
|
||||||
#+roam_alias: "WWII" "World War II"
|
|
||||||
|
|
||||||
* Headline
|
|
||||||
@end example
|
|
||||||
|
|
||||||
@multitable {aaaaaaaaaaa} {aaaaaaaaaaaaaaaaaaaaaaaa}
|
|
||||||
@headitem Method
|
|
||||||
@tab Titles
|
|
||||||
@item @code{'title}
|
|
||||||
@tab '(``World War 2'')
|
|
||||||
@item @code{'headline}
|
|
||||||
@tab '(``Headline'')
|
|
||||||
@item @code{'alias}
|
|
||||||
@tab '(``WWII'' ``World War II'')
|
|
||||||
@end multitable
|
|
||||||
|
|
||||||
If no title is provided, Org-roam defaults to using the file-path.
|
|
||||||
|
|
||||||
@menu
|
|
||||||
* Customizing Title Extraction::
|
|
||||||
@end menu
|
|
||||||
|
|
||||||
@node Customizing Title Extraction
|
|
||||||
@subsection Customizing Title Extraction
|
|
||||||
|
|
||||||
To control how Org-roam extracts titles, customize @code{org-roam-title-sources}. If
|
|
||||||
all methods of title extraction return no results, the file-name is used as the
|
|
||||||
note's title.
|
|
||||||
|
|
||||||
@defopt org-roam-title-sources
|
|
||||||
|
|
||||||
The list of sources from which to retrieve a note title.
|
|
||||||
Each element in the list is either:
|
|
||||||
@end defopt
|
|
||||||
|
|
||||||
@itemize
|
|
||||||
@item
|
|
||||||
a symbol -- this symbol corresponds to a title retrieval function, which
|
|
||||||
returns the list of titles for the current buffer
|
|
||||||
@itemize
|
|
||||||
@item
|
|
||||||
a list of symbols -- symbols in the list are treated as with (1). The
|
|
||||||
return value of this list is the first symbol in the list returning a
|
|
||||||
non-nil value.
|
|
||||||
@end itemize
|
|
||||||
|
|
||||||
The return results of the root list are concatenated.
|
|
||||||
|
|
||||||
For example the setting: '((title headline) alias) means the following:
|
|
||||||
|
|
||||||
@itemize
|
|
||||||
@item
|
|
||||||
Return the 'title + 'alias, if the title of current buffer is non-empty;
|
|
||||||
|
|
||||||
@item
|
|
||||||
Or return 'headline + 'alias otherwise.
|
|
||||||
@end itemize
|
|
||||||
|
|
||||||
The currently supported symbols are:
|
|
||||||
|
|
||||||
@code{'title}
|
|
||||||
The @code{#+title} property of org file.
|
|
||||||
|
|
||||||
@code{'alias}
|
|
||||||
The @code{#+roam_alias} property of the org file, using
|
|
||||||
space-delimited strings.
|
|
||||||
|
|
||||||
@code{'headline}
|
|
||||||
The first headline in the org file.
|
|
||||||
@end itemize
|
|
||||||
|
|
||||||
Adding your own title extraction method requires two steps. First, define a
|
|
||||||
method @code{(defun org-roam--extract-titles-foo () ...)}, where @code{foo} a
|
|
||||||
self-prescribed name for the title extraction method. This method takes no
|
|
||||||
arguments, and returns a list of strings (titles). Finally, push the symbol
|
|
||||||
@code{foo} into @code{org-roam-title-sources}. You may need to rebuild the cache from
|
|
||||||
scratch to re-process all files to pick up the new titles.
|
|
||||||
|
|
||||||
@node File Tags
|
|
||||||
@section File Tags
|
|
||||||
|
|
||||||
Tags are used as meta-data for files: they facilitate interactions with notes
|
|
||||||
where titles are insufficient. For example, tags allow for categorization of
|
|
||||||
notes: differentiating between bibliographical and structure notes during
|
|
||||||
interactive commands.
|
|
||||||
|
|
||||||
By default, tags are extracted from the @code{#+roam_tags} property. To add
|
|
||||||
additional extraction methods, see @ref{Customizing Tag Extraction}.
|
|
||||||
|
|
||||||
@menu
|
|
||||||
* Customizing Tag Extraction::
|
|
||||||
@end menu
|
|
||||||
|
|
||||||
@node Customizing Tag Extraction
|
|
||||||
@subsection Customizing Tag Extraction
|
|
||||||
|
|
||||||
Org-roam calls @code{org-roam--extract-tags} to extract tags from files. The variable
|
|
||||||
@code{org-roam-tag-sources}, to control how tags are extracted.
|
|
||||||
|
|
||||||
@defopt org-roam-tag-sources
|
|
||||||
@end defopt
|
|
||||||
|
|
||||||
Sources to obtain tags from.
|
|
||||||
|
|
||||||
It should be a list of symbols representing any of the following extraction
|
|
||||||
methods:
|
|
||||||
|
|
||||||
@code{'prop}
|
|
||||||
Extract tags from the @code{#+roam_tags} property.
|
|
||||||
Tags are space delimited.
|
|
||||||
Tags may contain spaces if they are double-quoted.
|
|
||||||
e.g. @code{#+roam_tags: TAG "tag with spaces"}
|
|
||||||
|
|
||||||
@code{'vanilla}
|
|
||||||
Extract vanilla org-mode tags, including @code{#+FILETAGS} and
|
|
||||||
inherited tags.
|
|
||||||
|
|
||||||
@code{'all-directories}
|
|
||||||
Extract sub-directories relative to @code{org-roam-directory}.
|
|
||||||
That is, if a file is located at relative path foo/bar/file.org,
|
|
||||||
the file will have tags ``foo'' and ``bar''.
|
|
||||||
|
|
||||||
@code{'last-directory}
|
|
||||||
Extract the last directory relative to `org-roam-directory'.
|
|
||||||
That is, if a file is located at relative path foo/bar/file.org,
|
|
||||||
the file will have tag \``bar\''.
|
|
||||||
|
|
||||||
@code{'first-directory}
|
|
||||||
Extract the first directory relative to @code{org-roam-directory}.
|
|
||||||
That is, if a file is located at relative path foo/bar/file.org,
|
|
||||||
the file will have tag ``foo''
|
|
||||||
|
|
||||||
By default, only the @code{'prop} extraction method is enabled. To enable the other
|
|
||||||
extraction methods, you may modify @code{org-roam-tag-sources}, for example:
|
|
||||||
|
|
||||||
@lisp
|
|
||||||
(setq org-roam-tag-sources '(prop last-directory))
|
|
||||||
@end lisp
|
|
||||||
|
|
||||||
Adding your own tag extraction method requires two steps. First, define a method
|
|
||||||
@code{(defun org-roam--extract-tags-foo (file) ...)}, where @code{foo} a self-prescribed
|
|
||||||
name for the tag extraction method. This method takes the file path as an
|
|
||||||
argument, and returns a list of strings (titles). Finally, push the symbol @code{foo}
|
|
||||||
into @code{org-roam-tag-sources}. You may need to rebuild the cache from scratch to
|
|
||||||
re-process all files to pick up the new tags.
|
|
||||||
|
|
||||||
@node File Refs
|
|
||||||
@section File Refs
|
|
||||||
|
|
||||||
Refs are unique identifiers for files. For example, a note for a website may
|
Refs are unique identifiers for files. For example, a note for a website may
|
||||||
contain a ref:
|
contain a ref:
|
||||||
@ -849,6 +716,159 @@ You may assign multiple refs to a single file, for example when you want
|
|||||||
multiple papers in a series to share the same note, or an article has a citation
|
multiple papers in a series to share the same note, or an article has a citation
|
||||||
key and a URL at the same time.
|
key and a URL at the same time.
|
||||||
|
|
||||||
|
@node The Org-roam Buffer
|
||||||
|
@chapter @strong{TODO} The Org-roam Buffer
|
||||||
|
|
||||||
|
@node Styling Org-roam
|
||||||
|
@chapter @strong{TODO} Styling Org-roam
|
||||||
|
|
||||||
|
@node Completion
|
||||||
|
@chapter @strong{TODO} Completion
|
||||||
|
|
||||||
|
@node Encryption
|
||||||
|
@chapter @strong{TODO} Encryption
|
||||||
|
|
||||||
|
@node Org-roam protocol
|
||||||
|
@chapter @strong{TODO} Org-roam protocol
|
||||||
|
|
||||||
|
@node Diagnosing and Repair
|
||||||
|
@chapter @strong{TODO} Diagnosing and Repair
|
||||||
|
|
||||||
|
@node Building Extensions
|
||||||
|
@chapter @strong{TODO} Building Extensions
|
||||||
|
|
||||||
|
@menu
|
||||||
|
* Public Interface::
|
||||||
|
@end menu
|
||||||
|
|
||||||
|
@node Public Interface
|
||||||
|
@section @strong{TODO} Public Interface
|
||||||
|
|
||||||
|
Database (for developers)
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
querying: (org-roam-db-query)
|
||||||
|
|
||||||
|
@item
|
||||||
|
updating full database: (org-roam-db-sync)
|
||||||
|
|
||||||
|
@item
|
||||||
|
update current file: (org-roam-db-update-file)
|
||||||
|
|
||||||
|
@item
|
||||||
|
remove file from database: (org-roam-db-clear-file)
|
||||||
|
@end itemize
|
||||||
|
|
||||||
|
Nodes:
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
List all nodes
|
||||||
|
|
||||||
|
@item
|
||||||
|
Read a node in from completion
|
||||||
|
|
||||||
|
@item
|
||||||
|
Get node at point
|
||||||
|
@end itemize
|
||||||
|
|
||||||
|
Links:
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
Get backlinks for node
|
||||||
|
@end itemize
|
||||||
|
|
||||||
|
Tags:
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
Add tag to node
|
||||||
|
|
||||||
|
@item
|
||||||
|
delete tag for node
|
||||||
|
|
||||||
|
@item
|
||||||
|
Get tags for node
|
||||||
|
@end itemize
|
||||||
|
|
||||||
|
Aliases:
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
Add alias to node
|
||||||
|
|
||||||
|
@item
|
||||||
|
Delete alias for node
|
||||||
|
|
||||||
|
@item
|
||||||
|
Get aliases for node
|
||||||
|
@end itemize
|
||||||
|
|
||||||
|
Ref:
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
Add ref to node
|
||||||
|
@end itemize
|
||||||
|
|
||||||
|
Capture:
|
||||||
|
|
||||||
|
Navigation:
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
Find or create a node
|
||||||
|
@end itemize
|
||||||
|
|
||||||
|
@node The Org-mode Ecosystem
|
||||||
|
@chapter @strong{TODO} The Org-mode Ecosystem
|
||||||
|
|
||||||
|
@node Frequently Asked Questions
|
||||||
|
@chapter @strong{TODO} Frequently Asked Questions
|
||||||
|
|
||||||
|
@node Developer's Guide to Org-roam
|
||||||
|
@chapter @strong{TODO} Developer's Guide to Org-roam
|
||||||
|
|
||||||
|
@menu
|
||||||
|
* Org-roam's Design Principles::
|
||||||
|
@end menu
|
||||||
|
|
||||||
|
@node Org-roam's Design Principles
|
||||||
|
@section Org-roam's Design Principles
|
||||||
|
|
||||||
|
Org-roam is primarily motivated by the need for a dual representation. We
|
||||||
|
(humans) love operating in a plain-text environment. The syntax rules of
|
||||||
|
Org-mode are simple and fit snugly within our brain. This also allows us to use
|
||||||
|
the tools and packages we love to explore and edit our notes. Org-mode is simply
|
||||||
|
the most powerful plain-text format available, with support for images, @LaTeX{},
|
||||||
|
TODO planning and much more.
|
||||||
|
|
||||||
|
But this plain-text format is simply ill-suited for exploration of these notes:
|
||||||
|
plain-text is simply not amenable for answering large-scale, complex queries
|
||||||
|
(e.g. how many tasks do I have that are due by next week?). Interfaces such as
|
||||||
|
Org-agenda slow to a crawl when the number of files becomes unwieldy, which
|
||||||
|
can quickly become the case.
|
||||||
|
|
||||||
|
At its core, Org-roam provides a database abstraction layer, providing a dual
|
||||||
|
representation of what's already available in plain-text. This allows us
|
||||||
|
(humans) to continue working with plain-text, while programs can utilize the
|
||||||
|
database layer to perform complex queries. These capabilities include, but are
|
||||||
|
not limited to:
|
||||||
|
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
link graph traversal and visualization
|
||||||
|
|
||||||
|
@item
|
||||||
|
Instantaneous SQL-like queries on headlines
|
||||||
|
@itemize
|
||||||
|
@item
|
||||||
|
What are my TODOs, scheduled for X, or due by Y@?
|
||||||
|
@end itemize
|
||||||
|
@end itemize
|
||||||
|
|
||||||
|
All of these functionality is powered by this database abstraction layer. Hence,
|
||||||
|
at its core Org-roam's primary goal is to provide a resilient dual
|
||||||
|
representation that is cheap to maintain, easy to understand, and is as
|
||||||
|
up-to-date as it possibly can. Org-roam also then exposes an API to this
|
||||||
|
database abstraction layer for users who would like to perform programmatic
|
||||||
|
queries on their Org files.
|
||||||
|
|
||||||
@node The Templating System
|
@node The Templating System
|
||||||
@chapter The Templating System
|
@chapter The Templating System
|
||||||
|
|
||||||
@ -918,7 +938,7 @@ The template is given a description of @code{"default"}.
|
|||||||
@code{(function org-roam--capture-get-point)} should not be changed.
|
@code{(function org-roam--capture-get-point)} should not be changed.
|
||||||
|
|
||||||
@item
|
@item
|
||||||
@code{"%?"} is the template inserted on each call to @code{org-roam-capture--capture}.
|
@code{"%?"} is the template inserted on each call to @code{org-roam-capture-}.
|
||||||
This template means don't insert any content, but place the cursor here.
|
This template means don't insert any content, but place the cursor here.
|
||||||
|
|
||||||
@item
|
@item
|
||||||
@ -990,10 +1010,9 @@ org-roam}.
|
|||||||
|
|
||||||
@menu
|
@menu
|
||||||
* Directories and Files::
|
* Directories and Files::
|
||||||
* The Org-roam Buffer::
|
* The Org-roam Buffer: The Org-roam Buffer (1).
|
||||||
* Org-roam Files::
|
* Org-roam Files::
|
||||||
* Org-roam Faces::
|
* Org-roam Faces::
|
||||||
* The Database::
|
|
||||||
@end menu
|
@end menu
|
||||||
|
|
||||||
@node Directories and Files
|
@node Directories and Files
|
||||||
@ -1021,7 +1040,7 @@ with multiple Org-roam instances.
|
|||||||
Files matching this regular expression are excluded from the Org-roam.
|
Files matching this regular expression are excluded from the Org-roam.
|
||||||
@end defvar
|
@end defvar
|
||||||
|
|
||||||
@node The Org-roam Buffer
|
@node The Org-roam Buffer (1)
|
||||||
@section The Org-roam Buffer
|
@section The Org-roam Buffer
|
||||||
|
|
||||||
The Org-roam buffer displays backlinks for the currently active Org-roam note.
|
The Org-roam buffer displays backlinks for the currently active Org-roam note.
|
||||||
@ -1086,27 +1105,6 @@ The @code{org-roam-link-current} face corresponds to links to the same file it i
|
|||||||
The @code{org-roam-link-invalid} face is applied to links that are broken. These are
|
The @code{org-roam-link-invalid} face is applied to links that are broken. These are
|
||||||
links to files or IDs that cannot be found.
|
links to files or IDs that cannot be found.
|
||||||
|
|
||||||
@node The Database
|
|
||||||
@section @strong{TODO} The Database
|
|
||||||
|
|
||||||
Org-roam is backed by a Sqlite database.
|
|
||||||
|
|
||||||
@defopt org-roam-db-update-method
|
|
||||||
|
|
||||||
Method to update the Org-roam database.
|
|
||||||
|
|
||||||
@code{'immediate}: Update the database immediately upon file changes.
|
|
||||||
|
|
||||||
@code{'idle-timer}: Updates the database if dirty, if Emacs idles for
|
|
||||||
@code{org-roam-db-update-idle-seconds}.
|
|
||||||
@end defopt
|
|
||||||
|
|
||||||
@defopt org-roam-db-update-idle-seconds
|
|
||||||
|
|
||||||
Number of idle seconds before triggering an Org-roam database update. This is
|
|
||||||
only valid if @code{org-roam-db-update-method} is @code{'idle-timer}.
|
|
||||||
@end defopt
|
|
||||||
|
|
||||||
@node Inserting Links
|
@node Inserting Links
|
||||||
@chapter Inserting Links
|
@chapter Inserting Links
|
||||||
|
|
||||||
@ -1194,7 +1192,7 @@ represents the cursor:
|
|||||||
@code{[[|]]}: completes for a file title
|
@code{[[|]]}: completes for a file title
|
||||||
|
|
||||||
@item
|
@item
|
||||||
@code{[[roam:]]}: completes for a file title
|
@code{[[roam:|]]}: completes for a file title
|
||||||
|
|
||||||
@item
|
@item
|
||||||
@code{[[*|]]}: completes for a headline within this file
|
@code{[[*|]]}: completes for a headline within this file
|
||||||
@ -1206,6 +1204,9 @@ represents the cursor:
|
|||||||
@code{[[roam:foo*|]]} completes a headline within the file with title ``foo''
|
@code{[[roam:foo*|]]} completes a headline within the file with title ``foo''
|
||||||
@end itemize
|
@end itemize
|
||||||
|
|
||||||
|
If you don't see the literal display of your links like the above examples,
|
||||||
|
call @code{M-x org-toggle-link-display}
|
||||||
|
|
||||||
Completions account for the current input. For example, for @code{[[f|]]}, the
|
Completions account for the current input. For example, for @code{[[f|]]}, the
|
||||||
completions (by default) only show for files with titles that start with ``f''.
|
completions (by default) only show for files with titles that start with ``f''.
|
||||||
|
|
||||||
@ -1279,7 +1280,7 @@ title is @code{"Index"}.
|
|||||||
Opens the Index file in the current @code{org-roam-directory}.
|
Opens the Index file in the current @code{org-roam-directory}.
|
||||||
@end defun
|
@end defun
|
||||||
|
|
||||||
@node Encryption
|
@node Encryption (1)
|
||||||
@chapter Encryption
|
@chapter Encryption
|
||||||
|
|
||||||
One may wish to keep private, encrypted files. Org-roam supports encryption (via
|
One may wish to keep private, encrypted files. Org-roam supports encryption (via
|
||||||
@ -1617,7 +1618,7 @@ in the generated graph.
|
|||||||
@node The roam-ref protocol
|
@node The roam-ref protocol
|
||||||
@section The roam-ref protocol
|
@section The roam-ref protocol
|
||||||
|
|
||||||
This protocol finds or creates a new note with a given @code{roam_key} (see @ref{Files}):
|
This protocol finds or creates a new note with a given @code{roam_key}:
|
||||||
|
|
||||||
@image{images/roam-ref,,,,gif}
|
@image{images/roam-ref,,,,gif}
|
||||||
|
|
||||||
@ -1720,7 +1721,7 @@ template @code{j} will put its notes under the heading ‘Journal’.
|
|||||||
|
|
||||||
Create an entry in the daily note for today.
|
Create an entry in the daily note for today.
|
||||||
|
|
||||||
When @code{goto} is non-nil, go the note without creating an entry.
|
When @code{goto} is non-nil, go to the note without creating an entry.
|
||||||
@end defun
|
@end defun
|
||||||
|
|
||||||
@defun @code{org-roam-dailies-find-today}
|
@defun @code{org-roam-dailies-find-today}
|
||||||
@ -1986,22 +1987,6 @@ using @uref{https://github.com/raxod502/el-patch, el-patch}:
|
|||||||
|
|
||||||
(eval-when-compile
|
(eval-when-compile
|
||||||
(require 'el-patch))
|
(require 'el-patch))
|
||||||
|
|
||||||
(use-package deft
|
|
||||||
;; same as above...
|
|
||||||
:config/el-patch
|
|
||||||
(defun deft-parse-title (file contents)
|
|
||||||
"Parse the given FILE and CONTENTS and determine the title.
|
|
||||||
If `deft-use-filename-as-title' is nil, the title is taken to
|
|
||||||
be the first non-empty line of the FILE. Else the base name of the FILE is
|
|
||||||
used as title."
|
|
||||||
(el-patch-swap (if deft-use-filename-as-title
|
|
||||||
(deft-base-filename file)
|
|
||||||
(let ((begin (string-match "^.+$" contents)))
|
|
||||||
(if begin
|
|
||||||
(funcall deft-parse-title-function
|
|
||||||
(substring contents begin (match-end 0))))))
|
|
||||||
(org-roam-db--get-title file))))
|
|
||||||
@end lisp
|
@end lisp
|
||||||
|
|
||||||
The Deft interface can slow down quickly when the number of files get huge.
|
The Deft interface can slow down quickly when the number of files get huge.
|
||||||
@ -2105,6 +2090,9 @@ file with the right @samp{#+roam_key}.
|
|||||||
@uref{https://www.leonrische.me/fc/index.html, Org-fc} is a spaced repetition system that scales well with a large number of
|
@uref{https://www.leonrische.me/fc/index.html, Org-fc} is a spaced repetition system that scales well with a large number of
|
||||||
files. Other alternatives include @uref{https://orgmode.org/worg/org-contrib/org-drill.html, org-drill}, and @uref{https://github.com/abo-abo/pamparam, pamparam}.
|
files. Other alternatives include @uref{https://orgmode.org/worg/org-contrib/org-drill.html, org-drill}, and @uref{https://github.com/abo-abo/pamparam, pamparam}.
|
||||||
|
|
||||||
|
To use Anki for spaced repetition, @uref{https://github.com/louietan/anki-editor, anki-editor} allows you to write your cards in
|
||||||
|
Org-mode, and sync your cards to Anki via @uref{https://github.com/FooSoft/anki-connect#installation, anki-connect}.
|
||||||
|
|
||||||
@node FAQ
|
@node FAQ
|
||||||
@chapter FAQ
|
@chapter FAQ
|
||||||
|
|
||||||
|
@ -1,357 +0,0 @@
|
|||||||
;;; org-roam-buffer.el --- Metadata buffer -*- coding: utf-8; lexical-binding: t; -*-
|
|
||||||
|
|
||||||
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
|
|
||||||
|
|
||||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
|
||||||
;; URL: https://github.com/org-roam/org-roam
|
|
||||||
;; Keywords: org-mode, roam, convenience
|
|
||||||
;; Version: 1.2.3
|
|
||||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
|
||||||
|
|
||||||
;; 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 library provides the org-roam-buffer functionality for org-roam
|
|
||||||
;;; Code:
|
|
||||||
;;;; Library Requires
|
|
||||||
(eval-when-compile (require 'subr-x))
|
|
||||||
(require 'cl-lib)
|
|
||||||
(require 'dash)
|
|
||||||
(require 's)
|
|
||||||
(require 'f)
|
|
||||||
(require 'ol)
|
|
||||||
(require 'org-element)
|
|
||||||
(require 'org-roam-macs)
|
|
||||||
|
|
||||||
(defvar org-roam-directory)
|
|
||||||
(defvar org-link-frame-setup)
|
|
||||||
(defvar org-return-follows-link)
|
|
||||||
(defvar org-roam-backlinks-mode)
|
|
||||||
(defvar org-roam-last-window)
|
|
||||||
(defvar org-ref-cite-types) ;; in org-ref-core.el
|
|
||||||
(defvar org-roam-mode)
|
|
||||||
(defvar org-roam--org-link-bracket-typed-re)
|
|
||||||
|
|
||||||
(declare-function org-roam-db--ensure-built "org-roam-db")
|
|
||||||
(declare-function org-roam-db--get-title "org-roam-db")
|
|
||||||
(declare-function org-roam-db-has-file-p "org-roam-db")
|
|
||||||
(declare-function org-roam--extract-refs "org-roam")
|
|
||||||
(declare-function org-roam--extract-titles "org-roam")
|
|
||||||
(declare-function org-roam--get-backlinks "org-roam")
|
|
||||||
(declare-function org-roam-backlinks-mode "org-roam")
|
|
||||||
(declare-function org-roam-mode "org-roam")
|
|
||||||
(declare-function org-roam--find-file "org-roam")
|
|
||||||
(declare-function org-roam-format-link "org-roam")
|
|
||||||
(declare-function org-roam-link-get-path "org-roam-link")
|
|
||||||
|
|
||||||
(defcustom org-roam-buffer-position 'right
|
|
||||||
"Position of `org-roam' buffer.
|
|
||||||
Valid values are
|
|
||||||
* left,
|
|
||||||
* right,
|
|
||||||
* top,
|
|
||||||
* bottom,
|
|
||||||
* a function returning one of the above."
|
|
||||||
:type '(choice (const left)
|
|
||||||
(const right)
|
|
||||||
(const top)
|
|
||||||
(const bottom)
|
|
||||||
function)
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-buffer-width 0.33
|
|
||||||
"Width of `org-roam' buffer.
|
|
||||||
Has an effect if and only if `org-roam-buffer-position' is `left' or `right'."
|
|
||||||
:type 'number
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-buffer-height 0.27
|
|
||||||
"Height of `org-roam' buffer.
|
|
||||||
Has an effect if and only if `org-roam-buffer-position' is `top' or `bottom'."
|
|
||||||
:type 'number
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
|
|
||||||
(defcustom org-roam-buffer "*org-roam*"
|
|
||||||
"Org-roam buffer name."
|
|
||||||
:type 'string
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-buffer-prepare-hook '(org-roam-buffer--insert-title
|
|
||||||
org-roam-buffer--insert-backlinks
|
|
||||||
org-roam-buffer--insert-ref-links)
|
|
||||||
"Hook run in the `org-roam-buffer' before it is displayed."
|
|
||||||
:type 'hook
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-buffer-preview-function #'org-roam-buffer--preview
|
|
||||||
"Function to obtain preview contents for a given link.
|
|
||||||
The function takes in two arguments, the FILE containing the
|
|
||||||
link, and the POINT of the link."
|
|
||||||
:type 'function
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-buffer-window-parameters nil
|
|
||||||
"Additional window parameters for the `org-roam-buffer' side window.
|
|
||||||
For example: (setq org-roam-buffer-window-parameters '((no-other-window . t)))"
|
|
||||||
:type '(alist)
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defvar org-roam-buffer--current nil
|
|
||||||
"Currently displayed file in `org-roam' buffer.")
|
|
||||||
|
|
||||||
(defun org-roam-buffer--find-file (file)
|
|
||||||
"Open FILE in the window `org-roam' was called from."
|
|
||||||
(setq file (expand-file-name file))
|
|
||||||
(let ((last-window org-roam-last-window))
|
|
||||||
(if (window-valid-p last-window)
|
|
||||||
(progn (with-selected-window last-window
|
|
||||||
(org-roam--find-file file))
|
|
||||||
(select-window last-window))
|
|
||||||
(org-roam--find-file file))))
|
|
||||||
|
|
||||||
(defun org-roam-buffer--insert-title ()
|
|
||||||
"Insert the org-roam-buffer title."
|
|
||||||
(insert (propertize (org-roam-db--get-title
|
|
||||||
(buffer-file-name org-roam-buffer--current))
|
|
||||||
'font-lock-face
|
|
||||||
'org-document-title)))
|
|
||||||
|
|
||||||
(defun org-roam-buffer--preview (file point)
|
|
||||||
"Get preview content for FILE at POINT."
|
|
||||||
(save-excursion
|
|
||||||
(org-roam--with-temp-buffer file
|
|
||||||
(goto-char point)
|
|
||||||
(let ((elem (org-element-at-point)))
|
|
||||||
(or (org-element-property :raw-value elem)
|
|
||||||
(when-let ((begin (org-element-property :begin elem))
|
|
||||||
(end (org-element-property :end elem)))
|
|
||||||
(string-trim (buffer-substring-no-properties begin end))))))))
|
|
||||||
|
|
||||||
(defun org-roam-buffer--pluralize (string number)
|
|
||||||
"Conditionally pluralize STRING if NUMBER is above 1."
|
|
||||||
(let ((l (pcase number
|
|
||||||
((pred (listp)) (length number))
|
|
||||||
((pred (integerp)) number)
|
|
||||||
(wrong-type (signal 'wrong-type-argument
|
|
||||||
`((listp integerp)
|
|
||||||
,wrong-type))))))
|
|
||||||
(concat string (when (> l 1) "s"))))
|
|
||||||
|
|
||||||
(defun org-roam-buffer-expand-links (content orig-path)
|
|
||||||
"Crawl CONTENT for relative links and corrects them to be correctly displayed.
|
|
||||||
ORIG-PATH is the path where the CONTENT originated."
|
|
||||||
(with-temp-buffer
|
|
||||||
(insert content)
|
|
||||||
(goto-char (point-min))
|
|
||||||
(let (link link-type)
|
|
||||||
(while (re-search-forward org-roam--org-link-bracket-typed-re (point-max) t)
|
|
||||||
(setq link-type (match-string 1)
|
|
||||||
link (match-string 2))
|
|
||||||
(when (and (string-equal link-type "file")
|
|
||||||
(f-relative-p link))
|
|
||||||
(replace-match (org-roam-link-get-path (expand-file-name link (file-name-directory orig-path)))
|
|
||||||
nil t nil 2))))
|
|
||||||
(buffer-string)))
|
|
||||||
|
|
||||||
(defun org-roam-buffer--insert-ref-links ()
|
|
||||||
"Insert ref backlinks for the current buffer."
|
|
||||||
(when-let* ((refs (with-temp-buffer
|
|
||||||
(insert-buffer-substring org-roam-buffer--current)
|
|
||||||
(org-roam--extract-refs)))
|
|
||||||
(paths (mapcar #'cdr refs)))
|
|
||||||
(if-let* ((key-backlinks (mapcan #'org-roam--get-backlinks paths))
|
|
||||||
(grouped-backlinks (--group-by (nth 0 it) key-backlinks)))
|
|
||||||
(progn
|
|
||||||
(insert (let ((l (length key-backlinks)))
|
|
||||||
(format "\n\n* %d %s\n"
|
|
||||||
l (org-roam-buffer--pluralize "Ref Backlink" l))))
|
|
||||||
(dolist (group grouped-backlinks)
|
|
||||||
(let ((file-from (car group))
|
|
||||||
(bls (cdr group)))
|
|
||||||
(insert (format "** %s\n"
|
|
||||||
(org-roam-format-link file-from
|
|
||||||
(org-roam-db--get-title file-from)
|
|
||||||
"file")))
|
|
||||||
(dolist (backlink bls)
|
|
||||||
(pcase-let ((`(,file-from _ ,props) backlink))
|
|
||||||
(insert (if-let ((content (funcall org-roam-buffer-preview-function file-from (plist-get props :point))))
|
|
||||||
(propertize (org-roam-buffer-expand-links content file-from)
|
|
||||||
'help-echo "mouse-1: visit backlinked note"
|
|
||||||
'file-from file-from
|
|
||||||
'file-from-point (plist-get props :point))
|
|
||||||
"")
|
|
||||||
"\n\n"))))))
|
|
||||||
(insert "\n\n* No ref backlinks!"))))
|
|
||||||
|
|
||||||
(defun org-roam-buffer--insert-backlinks ()
|
|
||||||
"Insert the org-roam-buffer backlinks string for the current buffer."
|
|
||||||
(let (props file-from)
|
|
||||||
(if-let* ((file-path (buffer-file-name org-roam-buffer--current))
|
|
||||||
(titles (with-current-buffer org-roam-buffer--current
|
|
||||||
(org-roam--extract-titles)))
|
|
||||||
(backlinks (org-roam--get-backlinks (push file-path titles)))
|
|
||||||
(grouped-backlinks (--group-by (nth 0 it) backlinks)))
|
|
||||||
(progn
|
|
||||||
(insert (let ((l (length backlinks)))
|
|
||||||
(format "\n\n* %d %s\n"
|
|
||||||
l (org-roam-buffer--pluralize "Backlink" l))))
|
|
||||||
(dolist (group grouped-backlinks)
|
|
||||||
(setq file-from (car group))
|
|
||||||
(setq props (mapcar (lambda (row) (nth 2 row)) (cdr group)))
|
|
||||||
(setq props (seq-sort-by (lambda (p) (plist-get p :point)) #'< props))
|
|
||||||
(insert (format "** %s\n"
|
|
||||||
(org-roam-format-link file-from
|
|
||||||
(org-roam-db--get-title file-from)
|
|
||||||
"file")))
|
|
||||||
(dolist (prop props)
|
|
||||||
(insert "*** "
|
|
||||||
(if-let ((outline (plist-get prop :outline)))
|
|
||||||
(-> outline
|
|
||||||
(string-join " > ")
|
|
||||||
(org-roam-buffer-expand-links file-from))
|
|
||||||
"Top")
|
|
||||||
"\n"
|
|
||||||
(if-let ((content (funcall org-roam-buffer-preview-function file-from (plist-get prop :point))))
|
|
||||||
(propertize
|
|
||||||
(s-trim (s-replace "\n" " " (org-roam-buffer-expand-links content file-from)))
|
|
||||||
'help-echo "mouse-1: visit backlinked note"
|
|
||||||
'file-from file-from
|
|
||||||
'file-from-point (plist-get prop :point))
|
|
||||||
"")
|
|
||||||
"\n\n"))))
|
|
||||||
(insert "\n\n* No backlinks!"))))
|
|
||||||
|
|
||||||
(defun org-roam-buffer-update ()
|
|
||||||
"Update the `org-roam-buffer'."
|
|
||||||
(interactive)
|
|
||||||
(org-roam-db--ensure-built)
|
|
||||||
(let* ((source-org-roam-directory org-roam-directory))
|
|
||||||
(with-current-buffer org-roam-buffer
|
|
||||||
;; When dir-locals.el is used to override org-roam-directory,
|
|
||||||
;; org-roam-buffer should have a different local org-roam-directory and
|
|
||||||
;; default-directory, as relative links are relative from the overridden
|
|
||||||
;; org-roam-directory.
|
|
||||||
(setq-local org-roam-directory source-org-roam-directory)
|
|
||||||
(setq-local default-directory source-org-roam-directory)
|
|
||||||
;; Locally overwrite the file opening function to re-use the
|
|
||||||
;; last window org-roam was called from
|
|
||||||
(setq-local org-link-frame-setup
|
|
||||||
(cons '(file . org-roam--find-file) org-link-frame-setup))
|
|
||||||
(let ((inhibit-read-only t))
|
|
||||||
(erase-buffer)
|
|
||||||
(unless (eq major-mode 'org-mode)
|
|
||||||
(org-mode))
|
|
||||||
(unless org-roam-backlinks-mode
|
|
||||||
(org-roam-backlinks-mode))
|
|
||||||
(make-local-variable 'org-return-follows-link)
|
|
||||||
(setq org-return-follows-link t)
|
|
||||||
(run-hooks 'org-roam-buffer-prepare-hook)
|
|
||||||
(read-only-mode 1)))))
|
|
||||||
|
|
||||||
|
|
||||||
(cl-defun org-roam-buffer--update-maybe (&key redisplay)
|
|
||||||
"Reconstructs `org-roam-buffer'.
|
|
||||||
This needs to be quick or infrequent, because this is run at
|
|
||||||
`post-command-hook'. If REDISPLAY, force an update of
|
|
||||||
`org-roam-buffer'."
|
|
||||||
(let ((buffer (window-buffer)))
|
|
||||||
(when (and (or redisplay
|
|
||||||
(not (eq org-roam-buffer--current buffer)))
|
|
||||||
(eq 'visible (org-roam-buffer--visibility))
|
|
||||||
(buffer-file-name buffer)
|
|
||||||
(org-roam-db-has-file-p (buffer-file-name buffer)))
|
|
||||||
(setq org-roam-buffer--current buffer)
|
|
||||||
(org-roam-buffer-update))))
|
|
||||||
|
|
||||||
;;;; Toggling the org-roam buffer
|
|
||||||
(define-inline org-roam-buffer--visibility ()
|
|
||||||
"Return whether the current visibility state of the org-roam buffer.
|
|
||||||
Valid states are 'visible, 'exists and 'none."
|
|
||||||
(declare (side-effect-free t))
|
|
||||||
(inline-quote
|
|
||||||
(cond
|
|
||||||
((get-buffer-window org-roam-buffer) 'visible)
|
|
||||||
((get-buffer org-roam-buffer) 'exists)
|
|
||||||
(t 'none))))
|
|
||||||
|
|
||||||
(defun org-roam-buffer--set-width (width)
|
|
||||||
"Set the width of `org-roam-buffer' to `WIDTH'."
|
|
||||||
(unless (one-window-p)
|
|
||||||
(let ((window-size-fixed)
|
|
||||||
(w (max width window-min-width)))
|
|
||||||
(cond
|
|
||||||
((> (window-width) w)
|
|
||||||
(shrink-window-horizontally (- (window-width) w)))
|
|
||||||
((< (window-width) w)
|
|
||||||
(enlarge-window-horizontally (- w (window-width))))))))
|
|
||||||
|
|
||||||
(defun org-roam-buffer--set-height (height)
|
|
||||||
"Set the height of `org-roam-buffer' to `HEIGHT'."
|
|
||||||
(unless (one-window-p)
|
|
||||||
(let ((window-size-fixed)
|
|
||||||
(h (max height window-min-height)))
|
|
||||||
(cond
|
|
||||||
((> (window-height) h)
|
|
||||||
(shrink-window (- (window-height) h)))
|
|
||||||
((< (window-height) h)
|
|
||||||
(enlarge-window (- h (window-height))))))))
|
|
||||||
|
|
||||||
(defun org-roam-buffer--get-create ()
|
|
||||||
"Set up the `org-roam' buffer at `org-roam-buffer-position'."
|
|
||||||
(let ((position (if (functionp org-roam-buffer-position)
|
|
||||||
(funcall org-roam-buffer-position)
|
|
||||||
org-roam-buffer-position)))
|
|
||||||
(save-selected-window
|
|
||||||
(-> (get-buffer-create org-roam-buffer)
|
|
||||||
(display-buffer-in-side-window
|
|
||||||
`((side . ,position)
|
|
||||||
(window-parameters . ,org-roam-buffer-window-parameters)))
|
|
||||||
(select-window))
|
|
||||||
(pcase position
|
|
||||||
((or 'right 'left)
|
|
||||||
(org-roam-buffer--set-width
|
|
||||||
(round (* (frame-width) org-roam-buffer-width))))
|
|
||||||
((or 'top 'bottom)
|
|
||||||
(org-roam-buffer--set-height
|
|
||||||
(round (* (frame-height) org-roam-buffer-height))))))))
|
|
||||||
|
|
||||||
(defun org-roam-buffer-activate ()
|
|
||||||
"Activate display of the `org-roam-buffer'."
|
|
||||||
(interactive)
|
|
||||||
(unless org-roam-mode (org-roam-mode))
|
|
||||||
(setq org-roam-last-window (get-buffer-window))
|
|
||||||
(org-roam-buffer--get-create))
|
|
||||||
|
|
||||||
(defun org-roam-buffer-deactivate ()
|
|
||||||
"Deactivate display of the `org-roam-buffer'."
|
|
||||||
(interactive)
|
|
||||||
(setq org-roam-last-window (get-buffer-window))
|
|
||||||
(delete-window (get-buffer-window org-roam-buffer)))
|
|
||||||
|
|
||||||
(defun org-roam-buffer-toggle-display ()
|
|
||||||
"Toggle display of the `org-roam-buffer'."
|
|
||||||
(interactive)
|
|
||||||
(pcase (org-roam-buffer--visibility)
|
|
||||||
('visible (org-roam-buffer-deactivate))
|
|
||||||
((or 'exists 'none) (org-roam-buffer-activate))))
|
|
||||||
|
|
||||||
(provide 'org-roam-buffer)
|
|
||||||
|
|
||||||
;;; org-roam-buffer.el ends here
|
|
File diff suppressed because it is too large
Load Diff
@ -5,8 +5,8 @@
|
|||||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||||
;; URL: https://github.com/org-roam/org-roam
|
;; URL: https://github.com/org-roam/org-roam
|
||||||
;; Keywords: org-mode, roam, convenience
|
;; Keywords: org-mode, roam, convenience
|
||||||
;; Version: 1.2.3
|
;; Version: 2.0.0
|
||||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
|
||||||
|
|
||||||
;; This file is NOT part of GNU Emacs.
|
;; This file is NOT part of GNU Emacs.
|
||||||
|
|
||||||
@ -34,78 +34,7 @@
|
|||||||
;;;; Library Requires
|
;;;; Library Requires
|
||||||
|
|
||||||
;;; Obsolete aliases (remove after next major release)
|
;;; Obsolete aliases (remove after next major release)
|
||||||
;;;; Functions
|
;;; Obsolete functions
|
||||||
(define-obsolete-function-alias 'org-roam--capture-get-point 'org-roam-capture--get-point
|
|
||||||
"org-roam 1.0.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam-build-cache 'org-roam-db-build-cache
|
|
||||||
"org-roam 1.0.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam-sql 'org-roam-db-query
|
|
||||||
"org-roam 1.0.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam--db-clear 'org-roam-db--clear
|
|
||||||
"org-roam 1.0.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam-show-graph 'org-roam-graph-show
|
|
||||||
"org-roam 1.0.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam--maybe-update-buffer
|
|
||||||
'org-roam-buffer--update-maybe "org-roam 1.0.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam--current-visibility
|
|
||||||
'org-roam-buffer--visibility "org-roam 1.0.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam-update 'org-roam-buffer-update
|
|
||||||
"org-roam 1.0.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam--set-width 'org-roam-buffer--set-width
|
|
||||||
"org-roam 1.0.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam--set-height 'org-roam-buffer--set-height
|
|
||||||
"org-roam 1.0.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam--set-up-buffer 'org-roam-buffer--get-create
|
|
||||||
"org-roam 1.0.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam-today 'org-roam-dailies-today
|
|
||||||
"org-roam 1.0.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam-tomorrow 'org-roam-dailies-tomorrow
|
|
||||||
"org-roam 1.0.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam-yesterday 'org-roam-dailies-yesterday
|
|
||||||
"org-roam 1.0.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam-date 'org-roam-dailies-date
|
|
||||||
"org-roam 1.0.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam-graph-show 'org-roam-graph
|
|
||||||
"org-roam 1.0.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam-graph-build 'org-roam-graph
|
|
||||||
"org-roam 1.0.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam-find-index 'org-roam-jump-to-index
|
|
||||||
"org-roam 1.1.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam--pluralize 'org-roam-buffer--pluralize
|
|
||||||
"org-roam 1.1.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam--capture 'org-roam-capture--capture
|
|
||||||
"org-roam 1.1.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam-db--clear 'org-roam-db-clear
|
|
||||||
"org-roam 1.2.0")
|
|
||||||
(define-obsolete-function-alias 'org-roam-dailies-today 'org-roam-dailies-find-today
|
|
||||||
"org-roam 1.2.2")
|
|
||||||
(define-obsolete-function-alias 'org-roam-dailies-yesterday 'org-roam-dailies-find-yesterday
|
|
||||||
"org-roam 1.2.2")
|
|
||||||
(define-obsolete-function-alias 'org-roam-dailies-tomorrow 'org-roam-dailies-find-tomorrow
|
|
||||||
"org-roam 1.2.2")
|
|
||||||
(define-obsolete-function-alias 'org-roam-dailies-date 'org-roam-dailies-find-date
|
|
||||||
"org-roam 1.2.2")
|
|
||||||
|
|
||||||
;;;; Variables
|
|
||||||
(define-obsolete-variable-alias 'org-roam-graphviz-extra-options
|
|
||||||
'org-roam-graph-extra-config "org-roam 1.0.0")
|
|
||||||
(define-obsolete-variable-alias 'org-roam-grapher-extra-options
|
|
||||||
'org-roam-graph-extra-config "org-roam 1.0.0")
|
|
||||||
(define-obsolete-variable-alias 'org-roam-graph-node-shape
|
|
||||||
'org-roam-graph-node-extra-config "org-roam 1.0.0")
|
|
||||||
(define-obsolete-variable-alias 'org-roam--db-connection
|
|
||||||
'org-roam-db--connection "org-roam 1.0.0")
|
|
||||||
(define-obsolete-variable-alias 'org-roam--current-buffer
|
|
||||||
'org-roam-buffer--current "org-roam 1.0.0")
|
|
||||||
(define-obsolete-variable-alias 'org-roam-date-title-format
|
|
||||||
'org-roam-dailies-capture-templates "org-roam 1.0.0")
|
|
||||||
(define-obsolete-variable-alias 'org-roam-date-filename-format
|
|
||||||
'org-roam-dailies-capture-templates "org-roam 1.0.0")
|
|
||||||
(define-obsolete-variable-alias 'org-roam-update-db-idle-seconds
|
|
||||||
'org-roam-db-update-idle-seconds "org-roam 1.2.2")
|
|
||||||
|
|
||||||
(make-obsolete-variable 'org-roam-buffer-no-delete-other-windows
|
|
||||||
'org-roam-buffer-window-parameters "org-roam 1.1.1")
|
|
||||||
|
|
||||||
(provide 'org-roam-compat)
|
(provide 'org-roam-compat)
|
||||||
|
|
||||||
|
@ -5,9 +5,8 @@
|
|||||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||||
;; URL: https://github.com/org-roam/org-roam
|
;; URL: https://github.com/org-roam/org-roam
|
||||||
;; Keywords: org-mode, roam, convenience
|
;; Keywords: org-mode, roam, convenience
|
||||||
;; Version: 1.2.3
|
;; Version: 2.0.0
|
||||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
|
||||||
|
|
||||||
;; This file is NOT part of GNU Emacs.
|
;; This file is NOT part of GNU Emacs.
|
||||||
|
|
||||||
;; This program is free software; you can redistribute it and/or modify
|
;; This program is free software; you can redistribute it and/or modify
|
||||||
@ -27,90 +26,107 @@
|
|||||||
|
|
||||||
;;; Commentary:
|
;;; Commentary:
|
||||||
;;
|
;;
|
||||||
;; This library provides completion for org-roam.
|
;; This library provides completion-at-point functions for Org-roam.
|
||||||
|
;;
|
||||||
|
;; The two main functions provided to capf are:
|
||||||
|
;;
|
||||||
|
;; `org-roam-complete-link-at-point' provides completions to nodes
|
||||||
|
;; within link brackets
|
||||||
|
;;
|
||||||
|
;; `org-roam-complete-everywhere' provides completions for nodes everywhere,
|
||||||
|
;; matching the symbol at point
|
||||||
|
;;
|
||||||
;;; Code:
|
;;; Code:
|
||||||
;;;; Library Requires
|
|
||||||
(require 'cl-lib)
|
(require 'cl-lib)
|
||||||
(require 's)
|
(require 'org-element)
|
||||||
|
|
||||||
(defvar helm-pattern)
|
(declare-function org-roam--get-titles "org-roam")
|
||||||
(declare-function helm "ext:helm")
|
|
||||||
(declare-function helm-make-source "ext:helm-source" (name class &rest args) t)
|
|
||||||
|
|
||||||
(defcustom org-roam-completion-system 'default
|
|
||||||
"The completion system to be used by `org-roam'."
|
|
||||||
:type '(radio
|
|
||||||
(const :tag "Default" default)
|
|
||||||
(const :tag "Ido" ido)
|
|
||||||
(const :tag "Ivy" ivy)
|
|
||||||
(const :tag "Helm" helm)
|
|
||||||
(function :tag "Custom function"))
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-completion-ignore-case t
|
(defcustom org-roam-completion-ignore-case t
|
||||||
"Whether to ignore case in Org-roam `completion-at-point' completions."
|
"Whether to ignore case in Org-roam `completion-at-point' completions."
|
||||||
:group 'org-roam
|
:group 'org-roam
|
||||||
:type 'boolean)
|
:type 'boolean)
|
||||||
|
|
||||||
(defun org-roam-completion--helm-candidate-transformer (candidates _source)
|
(defcustom org-roam-completion-everywhere nil
|
||||||
"Transforms CANDIDATES for Helm-based completing read.
|
"When non-nil, provide link completion matching outside of Org links."
|
||||||
SOURCE is not used."
|
:group 'org-roam
|
||||||
(let ((prefix (propertize "[?] "
|
:type 'boolean)
|
||||||
'face 'helm-ff-prefix)))
|
|
||||||
(cons (propertize helm-pattern
|
|
||||||
'display (concat prefix helm-pattern))
|
|
||||||
candidates)))
|
|
||||||
|
|
||||||
(cl-defun org-roam-completion--completing-read (prompt choices &key
|
(defvar org-roam-completion-functions (list #'org-roam-complete-link-at-point
|
||||||
require-match initial-input
|
#'org-roam-complete-everywhere)
|
||||||
action)
|
"List of functions to be used with `completion-at-point' for Org-roam.")
|
||||||
"Present a PROMPT with CHOICES and optional INITIAL-INPUT.
|
|
||||||
If REQUIRE-MATCH is t, the user must select one of the CHOICES.
|
(defun org-roam-complete-everywhere ()
|
||||||
Return user choice."
|
"Provides completions for links for any word at point.
|
||||||
(let (res)
|
This is a `completion-at-point' function, and is active when
|
||||||
(setq res
|
`org-roam-completion-everywhere' is non-nil."
|
||||||
(cond
|
(let ((end (point))
|
||||||
((eq org-roam-completion-system 'ido)
|
(start (point))
|
||||||
(let ((candidates (mapcar #'car choices)))
|
(exit-fn (lambda (&rest _) nil))
|
||||||
(ido-completing-read prompt candidates nil require-match initial-input)))
|
collection)
|
||||||
((eq org-roam-completion-system 'default)
|
(when (and org-roam-completion-everywhere
|
||||||
(completing-read prompt choices nil require-match initial-input))
|
(thing-at-point 'word)
|
||||||
((eq org-roam-completion-system 'ivy)
|
(not (save-match-data (org-in-regexp org-link-any-re))))
|
||||||
(if (fboundp 'ivy-read)
|
(let ((bounds (bounds-of-thing-at-point 'word)))
|
||||||
(ivy-read prompt choices
|
(setq start (car bounds)
|
||||||
:initial-input initial-input
|
end (cdr bounds)
|
||||||
:preselect initial-input
|
collection #'org-roam--get-titles
|
||||||
:require-match require-match
|
exit-fn (lambda (str _status)
|
||||||
:action (prog1 action
|
(delete-char (- (length str)))
|
||||||
(setq action nil))
|
(insert "[[roam:" str "]]")))))
|
||||||
:caller 'org-roam--completing-read)
|
(when collection
|
||||||
(user-error "Please install ivy from \
|
(let ((prefix (buffer-substring-no-properties start end)))
|
||||||
https://github.com/abo-abo/swiper")))
|
(list start end
|
||||||
((eq org-roam-completion-system 'helm)
|
(if (functionp collection)
|
||||||
(unless (and (fboundp 'helm)
|
(completion-table-case-fold
|
||||||
(fboundp 'helm-make-source))
|
(completion-table-dynamic
|
||||||
(user-error "Please install helm from \
|
(lambda (_)
|
||||||
https://github.com/emacs-helm/helm"))
|
(cl-remove-if (apply-partially #'string= prefix)
|
||||||
(let ((source (helm-make-source prompt 'helm-source-sync
|
(funcall collection))))
|
||||||
:candidates (mapcar #'car choices)
|
(not org-roam-completion-ignore-case))
|
||||||
:filtered-candidate-transformer
|
collection)
|
||||||
(and (not require-match)
|
:exit-function exit-fn)))))
|
||||||
#'org-roam-completion--helm-candidate-transformer)))
|
|
||||||
(buf (concat "*org-roam "
|
(defun org-roam-complete-link-at-point ()
|
||||||
(s-downcase (s-chop-suffix ":" (s-trim prompt)))
|
"Do appropriate completion for the link at point."
|
||||||
"*")))
|
(let ((end (point))
|
||||||
(or (helm :sources source
|
(start (point))
|
||||||
:action (if action
|
collection link-type)
|
||||||
(prog1 action
|
(when (org-in-regexp org-link-bracket-re 1)
|
||||||
(setq action nil))
|
(setq start (match-beginning 1)
|
||||||
#'identity)
|
end (match-end 1))
|
||||||
:prompt prompt
|
(let ((context (org-element-context)))
|
||||||
:input initial-input
|
(pcase (org-element-lineage context '(link) t)
|
||||||
:buffer buf)
|
(`nil nil)
|
||||||
(keyboard-quit))))))
|
(link
|
||||||
(if action
|
(setq link-type (org-element-property :type link))
|
||||||
(funcall action res)
|
(when (member link-type '("roam" "fuzzy"))
|
||||||
res)))
|
(when (string= link-type "roam") (setq start (+ start (length "roam:"))))
|
||||||
|
(setq collection #'org-roam--get-titles))))))
|
||||||
|
(when collection
|
||||||
|
(let ((prefix (buffer-substring-no-properties start end)))
|
||||||
|
(list start end
|
||||||
|
(if (functionp collection)
|
||||||
|
(completion-table-case-fold
|
||||||
|
(completion-table-dynamic
|
||||||
|
(lambda (_)
|
||||||
|
(cl-remove-if (apply-partially #'string= prefix)
|
||||||
|
(funcall collection))))
|
||||||
|
(not org-roam-completion-ignore-case))
|
||||||
|
collection)
|
||||||
|
:exit-function
|
||||||
|
(lambda (str &rest _)
|
||||||
|
(delete-char (- 0 (length str)))
|
||||||
|
(insert (concat (unless (string= link-type "roam") "roam:")
|
||||||
|
str))
|
||||||
|
(forward-char 2)))))))
|
||||||
|
|
||||||
|
(defun org-roam--register-completion-functions ()
|
||||||
|
"."
|
||||||
|
(dolist (fn org-roam-completion-functions)
|
||||||
|
(add-hook 'completion-at-point-functions fn nil t)))
|
||||||
|
|
||||||
|
(add-hook 'org-roam-find-file-hook #'org-roam--register-completion-functions)
|
||||||
|
|
||||||
(provide 'org-roam-completion)
|
(provide 'org-roam-completion)
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@
|
|||||||
;; Leo Vivier <leo.vivier+dev@gmail.com>
|
;; Leo Vivier <leo.vivier+dev@gmail.com>
|
||||||
;; URL: https://github.com/org-roam/org-roam
|
;; URL: https://github.com/org-roam/org-roam
|
||||||
;; Keywords: org-mode, roam, convenience
|
;; Keywords: org-mode, roam, convenience
|
||||||
;; Version: 1.2.3
|
;; Version: 2.0.0
|
||||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
|
||||||
|
|
||||||
;; This file is NOT part of GNU Emacs.
|
;; This file is NOT part of GNU Emacs.
|
||||||
|
|
||||||
@ -36,16 +36,18 @@
|
|||||||
;;; Library Requires
|
;;; Library Requires
|
||||||
(require 'org-capture)
|
(require 'org-capture)
|
||||||
(require 'org-roam-capture)
|
(require 'org-roam-capture)
|
||||||
(require 'org-roam-macs)
|
|
||||||
(require 'f)
|
(require 'f)
|
||||||
|
|
||||||
;;;; Declarations
|
;;;; Declarations
|
||||||
(defvar org-roam-mode)
|
|
||||||
(defvar org-roam-directory)
|
(defvar org-roam-directory)
|
||||||
(defvar org-roam-file-extensions)
|
(defvar org-roam-file-extensions)
|
||||||
(declare-function org-roam--org-file-p "org-roam")
|
(declare-function org-roam--org-file-p "org-roam")
|
||||||
(declare-function org-roam--find-file "org-roam")
|
|
||||||
(declare-function org-roam-mode "org-roam")
|
;;;; Faces
|
||||||
|
(defface org-roam-dailies-calendar-note
|
||||||
|
'((t :inherit (org-link) :underline nil))
|
||||||
|
"Face for dates with a daily-note in the calendar."
|
||||||
|
:group 'org-roam-faces)
|
||||||
|
|
||||||
;;;; Customizable variables
|
;;;; Customizable variables
|
||||||
(defcustom org-roam-dailies-directory "daily/"
|
(defcustom org-roam-dailies-directory "daily/"
|
||||||
@ -59,54 +61,56 @@
|
|||||||
:type 'hook)
|
:type 'hook)
|
||||||
|
|
||||||
(defcustom org-roam-dailies-capture-templates
|
(defcustom org-roam-dailies-capture-templates
|
||||||
`(("d" "default" entry (function org-roam-capture--get-point)
|
`(("d" "default" entry
|
||||||
"* %?"
|
"* %?"
|
||||||
:file-name ,(concat org-roam-dailies-directory "%<%Y-%m-%d>")
|
:if-new (file+head ,(expand-file-name "%<%Y-%m-%d>.org" org-roam-dailies-directory)
|
||||||
:head "#+title: %<%Y-%m-%d>\n"))
|
"#+title: %<%Y-%m-%d>\n")))
|
||||||
"Capture templates for daily-notes in Org-roam."
|
"Capture templates for daily-notes in Org-roam.
|
||||||
|
See `org-roam-capture-templates' for the template documentation."
|
||||||
:group 'org-roam
|
:group 'org-roam
|
||||||
;; Adapted from `org-capture-templates'
|
:type '(repeat
|
||||||
:type
|
(choice (list :tag "Multikey description"
|
||||||
`(repeat
|
|
||||||
(choice :value ("d" "default" plain (function org-roam-capture--get-point)
|
|
||||||
"%?"
|
|
||||||
:file-name ,(concat org-roam-dailies-directory
|
|
||||||
"%<%Y-%m-%d>")
|
|
||||||
:head "#+title: %<%Y-%m-%d>\n"
|
|
||||||
:unnarrowed t)
|
|
||||||
(list :tag "Multikey description"
|
|
||||||
(string :tag "Keys ")
|
(string :tag "Keys ")
|
||||||
(string :tag "Description"))
|
(string :tag "Description"))
|
||||||
(list :tag "Template entry"
|
(list :tag "Template entry"
|
||||||
(string :tag "Keys ")
|
(string :tag "Keys ")
|
||||||
(string :tag "Description ")
|
(string :tag "Description ")
|
||||||
(choice :tag "Type "
|
(choice :tag "Capture Type " :value entry
|
||||||
(const :tag "Plain" plain)
|
(const :tag "Org entry" entry)
|
||||||
(const :tag "Entry (for creating headlines)" entry))
|
(const :tag "Plain list item" item)
|
||||||
(const :format "" #'org-roam-capture--get-point)
|
(const :tag "Checkbox item" checkitem)
|
||||||
|
(const :tag "Plain text" plain)
|
||||||
|
(const :tag "Table line" table-line))
|
||||||
(choice :tag "Template "
|
(choice :tag "Template "
|
||||||
(string :tag "String"
|
(string)
|
||||||
:format "String:\n \
|
|
||||||
Template string :\n%v")
|
|
||||||
(list :tag "File"
|
(list :tag "File"
|
||||||
(const :format "" file)
|
(const :format "" file)
|
||||||
(file :tag "Template file"))
|
(file :tag "Template file"))
|
||||||
(list :tag "Function"
|
(list :tag "Function"
|
||||||
(const :format "" function)
|
(const :format "" function)
|
||||||
(function :tag "Template function")))
|
(function :tag "Template function")))
|
||||||
(const :format "File name format :" :file-name)
|
|
||||||
(string :format " %v" :value ,(concat org-roam-dailies-directory
|
|
||||||
"%<%Y-%m-%d>"))
|
|
||||||
(const :format "Header format :" :head)
|
|
||||||
(string :format " %v" :value "#+title: ${title}\n")
|
|
||||||
(plist :inline t
|
(plist :inline t
|
||||||
:tag "Options"
|
|
||||||
;; Give the most common options as checkboxes
|
;; Give the most common options as checkboxes
|
||||||
:options
|
:options (((const :format "%v " :if-new)
|
||||||
(((const :tag "Outline path" :olp)
|
(choice :tag "Node location"
|
||||||
(repeat :tag "Headings"
|
(list :tag "File"
|
||||||
(string :tag "Heading")))
|
(const :format "" file)
|
||||||
((const :format "%v " :unnarrowed) (const t))
|
(string :tag " File"))
|
||||||
|
(list :tag "File & Head Content"
|
||||||
|
(const :format "" file+head)
|
||||||
|
(string :tag " File")
|
||||||
|
(string :tag " Head Content"))
|
||||||
|
(list :tag "File & Outline path"
|
||||||
|
(const :format "" file+olp)
|
||||||
|
(string :tag " File")
|
||||||
|
(list :tag "Outline path"
|
||||||
|
(repeat string :tag "Headline")))
|
||||||
|
(list :tag "File & Head Content & Outline path"
|
||||||
|
(const :format "" file+head+olp)
|
||||||
|
(string :tag " File")
|
||||||
|
(string :tag " Head Content")
|
||||||
|
(list :tag "Outline path"
|
||||||
|
(repeat string :tag "Headline")))))
|
||||||
((const :format "%v " :prepend) (const t))
|
((const :format "%v " :prepend) (const t))
|
||||||
((const :format "%v " :immediate-finish) (const t))
|
((const :format "%v " :immediate-finish) (const t))
|
||||||
((const :format "%v " :jump-to-captured) (const t))
|
((const :format "%v " :jump-to-captured) (const t))
|
||||||
@ -118,6 +122,7 @@ Template string :\n%v")
|
|||||||
((const :format "%v " :clock-resume) (const t))
|
((const :format "%v " :clock-resume) (const t))
|
||||||
((const :format "%v " :time-prompt) (const t))
|
((const :format "%v " :time-prompt) (const t))
|
||||||
((const :format "%v " :tree-type) (const week))
|
((const :format "%v " :tree-type) (const week))
|
||||||
|
((const :format "%v " :unnarrowed) (const t))
|
||||||
((const :format "%v " :table-line-pos) (string))
|
((const :format "%v " :table-line-pos) (string))
|
||||||
((const :format "%v " :kill-buffer) (const t))))))))
|
((const :format "%v " :kill-buffer) (const t))))))))
|
||||||
|
|
||||||
@ -129,15 +134,14 @@ Template string :\n%v")
|
|||||||
(defun org-roam-dailies-find-directory ()
|
(defun org-roam-dailies-find-directory ()
|
||||||
"Find and open `org-roam-dailies-directory'."
|
"Find and open `org-roam-dailies-directory'."
|
||||||
(interactive)
|
(interactive)
|
||||||
(org-roam--find-file (org-roam-dailies-directory--get-absolute-path)))
|
(find-file (org-roam-dailies-directory--get-absolute-path)))
|
||||||
|
|
||||||
(defun org-roam-dailies--daily-note-p (&optional file)
|
(defun org-roam-dailies--daily-note-p (&optional file)
|
||||||
"Return t if FILE is an Org-roam daily-note, nil otherwise.
|
"Return t if FILE is an Org-roam daily-note, nil otherwise.
|
||||||
|
|
||||||
If FILE is not specified, use the current buffer's file-path."
|
If FILE is not specified, use the current buffer's file-path."
|
||||||
(when-let ((path (or file
|
(when-let ((path (or file
|
||||||
(-> (buffer-base-buffer)
|
(buffer-base-buffer (buffer-file-name))))
|
||||||
(buffer-file-name))))
|
|
||||||
(directory (org-roam-dailies-directory--get-absolute-path)))
|
(directory (org-roam-dailies-directory--get-absolute-path)))
|
||||||
(setq path (expand-file-name path))
|
(setq path (expand-file-name path))
|
||||||
(save-match-data
|
(save-match-data
|
||||||
@ -149,12 +153,10 @@ If FILE is not specified, use the current buffer's file-path."
|
|||||||
"Capture an entry in a daily-note for TIME, creating it if necessary.
|
"Capture an entry in a daily-note for TIME, creating it if necessary.
|
||||||
|
|
||||||
When GOTO is non-nil, go the note without creating an entry."
|
When GOTO is non-nil, go the note without creating an entry."
|
||||||
(unless org-roam-mode (org-roam-mode))
|
(org-roam-capture- :goto (when goto '(4))
|
||||||
(let ((org-roam-capture-templates (--> org-roam-dailies-capture-templates
|
:node (org-roam-node-create)
|
||||||
(if goto (list (car it)) it)))
|
:templates org-roam-dailies-capture-templates
|
||||||
(org-roam-capture--info (list (cons 'time time)))
|
:props (list :default-time time)))
|
||||||
(org-roam-capture--context 'dailies))
|
|
||||||
(org-roam-capture--capture (when goto '(4)))))
|
|
||||||
|
|
||||||
;;;; Commands
|
;;;; Commands
|
||||||
;;; Today
|
;;; Today
|
||||||
@ -229,13 +231,11 @@ future."
|
|||||||
|
|
||||||
Return (MONTH DAY YEAR)."
|
Return (MONTH DAY YEAR)."
|
||||||
(let ((file (or file
|
(let ((file (or file
|
||||||
(-> (buffer-base-buffer)
|
(buffer-base-buffer (buffer-file-name)))))
|
||||||
(buffer-file-name)))))
|
|
||||||
(cl-destructuring-bind (_ _ _ d m y _ _ _)
|
(cl-destructuring-bind (_ _ _ d m y _ _ _)
|
||||||
(-> file
|
(org-parse-time-string
|
||||||
(file-name-nondirectory)
|
(file-name-sans-extension
|
||||||
(file-name-sans-extension)
|
(file-name-nondirectory file)))
|
||||||
(org-parse-time-string))
|
|
||||||
(list m d y))))
|
(list m d y))))
|
||||||
|
|
||||||
(defun org-roam-dailies-calendar--date-to-time (date)
|
(defun org-roam-dailies-calendar--date-to-time (date)
|
||||||
@ -271,7 +271,6 @@ creating an entry."
|
|||||||
|
|
||||||
(defun org-roam-dailies-find-date (&optional prefer-future)
|
(defun org-roam-dailies-find-date (&optional prefer-future)
|
||||||
"Find the daily-note for a date using the calendar, creating it if necessary.
|
"Find the daily-note for a date using the calendar, creating it if necessary.
|
||||||
|
|
||||||
Prefer past dates, unless PREFER-FUTURE is non-nil."
|
Prefer past dates, unless PREFER-FUTURE is non-nil."
|
||||||
(interactive)
|
(interactive)
|
||||||
(org-roam-dailies-capture-date t prefer-future))
|
(org-roam-dailies-capture-date t prefer-future))
|
||||||
|
665
org-roam-db.el
665
org-roam-db.el
@ -5,8 +5,8 @@
|
|||||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||||
;; URL: https://github.com/org-roam/org-roam
|
;; URL: https://github.com/org-roam/org-roam
|
||||||
;; Keywords: org-mode, roam, convenience
|
;; Keywords: org-mode, roam, convenience
|
||||||
;; Version: 1.2.3
|
;; Version: 2.0.0
|
||||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
|
||||||
|
|
||||||
;; This file is NOT part of GNU Emacs.
|
;; This file is NOT part of GNU Emacs.
|
||||||
|
|
||||||
@ -27,37 +27,32 @@
|
|||||||
|
|
||||||
;;; Commentary:
|
;;; Commentary:
|
||||||
;;
|
;;
|
||||||
;; This library is provides the underlying database api to org-roam
|
;; This library provides the underlying database api to org-roam.
|
||||||
;;
|
;;
|
||||||
;;; Code:
|
;;; Code:
|
||||||
;;;; Library Requires
|
;;;; Library Requires
|
||||||
(eval-when-compile (require 'subr-x))
|
(eval-when-compile (require 'subr-x))
|
||||||
(require 'emacsql)
|
(require 'emacsql)
|
||||||
(require 'emacsql-sqlite3)
|
(require 'emacsql-sqlite)
|
||||||
(require 'seq)
|
(require 'seq)
|
||||||
|
|
||||||
(eval-and-compile
|
(eval-and-compile
|
||||||
(require 'org-roam-macs)
|
(require 'org-roam-macs)
|
||||||
;; For `org-with-wide-buffer'
|
;; For `org-with-wide-buffer'
|
||||||
(require 'org-macs))
|
(require 'org-macs))
|
||||||
|
(require 'org)
|
||||||
|
(require 'ol)
|
||||||
|
(require 'org-roam-utils)
|
||||||
|
|
||||||
|
(defvar org-roam-find-file-hook)
|
||||||
(defvar org-roam-directory)
|
(defvar org-roam-directory)
|
||||||
(defvar org-roam-enable-headline-linking)
|
|
||||||
(defvar org-roam-verbose)
|
(defvar org-roam-verbose)
|
||||||
(defvar org-roam-file-name)
|
|
||||||
|
|
||||||
(defvar org-agenda-files)
|
(defvar org-agenda-files)
|
||||||
|
|
||||||
|
(declare-function org-roam-id-at-point "org-roam")
|
||||||
(declare-function org-roam--org-roam-file-p "org-roam")
|
(declare-function org-roam--org-roam-file-p "org-roam")
|
||||||
(declare-function org-roam--extract-titles "org-roam")
|
|
||||||
(declare-function org-roam--extract-refs "org-roam")
|
|
||||||
(declare-function org-roam--extract-tags "org-roam")
|
|
||||||
(declare-function org-roam--extract-ids "org-roam")
|
|
||||||
(declare-function org-roam--extract-links "org-roam")
|
|
||||||
(declare-function org-roam--list-all-files "org-roam")
|
(declare-function org-roam--list-all-files "org-roam")
|
||||||
(declare-function org-roam--path-to-slug "org-roam")
|
(declare-function org-roam-node-at-point "org-roam")
|
||||||
(declare-function org-roam--file-name-extension "org-roam")
|
|
||||||
(declare-function org-roam-buffer--update-maybe "org-roam-buffer")
|
|
||||||
|
|
||||||
;;;; Options
|
;;;; Options
|
||||||
(defcustom org-roam-db-location (expand-file-name "org-roam.db" user-emacs-directory)
|
(defcustom org-roam-db-location (expand-file-name "org-roam.db" user-emacs-directory)
|
||||||
@ -71,7 +66,7 @@ when used with multiple Org-roam instances."
|
|||||||
|
|
||||||
(defcustom org-roam-db-gc-threshold gc-cons-threshold
|
(defcustom org-roam-db-gc-threshold gc-cons-threshold
|
||||||
"The value to temporarily set the `gc-cons-threshold' threshold to.
|
"The value to temporarily set the `gc-cons-threshold' threshold to.
|
||||||
During large, heavy operations like `org-roam-db-build-cache',
|
During large, heavy operations like `org-roam-db-sync',
|
||||||
many GC operations happen because of the large number of
|
many GC operations happen because of the large number of
|
||||||
temporary structures generated (e.g. parsed ASTs). Temporarily
|
temporary structures generated (e.g. parsed ASTs). Temporarily
|
||||||
increasing `gc-cons-threshold' will help reduce the number of GC
|
increasing `gc-cons-threshold' will help reduce the number of GC
|
||||||
@ -84,34 +79,15 @@ value like `most-positive-fixnum'."
|
|||||||
:type 'int
|
:type 'int
|
||||||
:group 'org-roam)
|
:group 'org-roam)
|
||||||
|
|
||||||
(defconst org-roam-db--version 10)
|
(defconst org-roam-db--version 15)
|
||||||
|
(defconst org-roam--sqlite-available-p
|
||||||
|
(with-demoted-errors "Org-roam initialization: %S"
|
||||||
|
(emacsql-sqlite-ensure-binary)
|
||||||
|
t))
|
||||||
|
|
||||||
(defvar org-roam-db--connection (make-hash-table :test #'equal)
|
(defvar org-roam-db--connection (make-hash-table :test #'equal)
|
||||||
"Database connection to Org-roam database.")
|
"Database connection to Org-roam database.")
|
||||||
|
|
||||||
(defvar org-roam-db-dirty nil
|
|
||||||
"Whether the org-roam database is dirty and requires an update.
|
|
||||||
Contains pairs of `org-roam-directory' and `org-roam-db-location'
|
|
||||||
so that multi-directories are updated.")
|
|
||||||
|
|
||||||
(defcustom org-roam-db-update-method 'idle-timer
|
|
||||||
"Method to update the Org-roam database.
|
|
||||||
|
|
||||||
`immediate'
|
|
||||||
Update the database immediately upon file changes.
|
|
||||||
|
|
||||||
`idle-timer'
|
|
||||||
Updates the database if dirty, if Emacs idles for
|
|
||||||
`org-roam-db-update-idle-seconds'."
|
|
||||||
:type '(choice (const :tag "idle-timer" idle-timer)
|
|
||||||
(const :tag "immediate" immediate))
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-db-update-idle-seconds 2
|
|
||||||
"Number of idle seconds before triggering an Org-roam database update."
|
|
||||||
:type 'integer
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
;;;; Core Functions
|
;;;; Core Functions
|
||||||
|
|
||||||
(defun org-roam-db--get-connection ()
|
(defun org-roam-db--get-connection ()
|
||||||
@ -127,7 +103,7 @@ Performs a database upgrade when required."
|
|||||||
(emacsql-live-p (org-roam-db--get-connection)))
|
(emacsql-live-p (org-roam-db--get-connection)))
|
||||||
(let ((init-db (not (file-exists-p org-roam-db-location))))
|
(let ((init-db (not (file-exists-p org-roam-db-location))))
|
||||||
(make-directory (file-name-directory org-roam-db-location) t)
|
(make-directory (file-name-directory org-roam-db-location) t)
|
||||||
(let ((conn (emacsql-sqlite3 org-roam-db-location)))
|
(let ((conn (emacsql-sqlite org-roam-db-location)))
|
||||||
(set-process-query-on-exit-flag (emacsql-process conn) nil)
|
(set-process-query-on-exit-flag (emacsql-process conn) nil)
|
||||||
(puthash (expand-file-name org-roam-directory)
|
(puthash (expand-file-name org-roam-directory)
|
||||||
conn
|
conn
|
||||||
@ -160,38 +136,59 @@ SQL can be either the emacsql vector representation, or a string."
|
|||||||
(defconst org-roam-db--table-schemata
|
(defconst org-roam-db--table-schemata
|
||||||
'((files
|
'((files
|
||||||
[(file :unique :primary-key)
|
[(file :unique :primary-key)
|
||||||
(hash :not-null)
|
(hash :not-null)])
|
||||||
(meta :not-null)])
|
|
||||||
|
|
||||||
(ids
|
(nodes
|
||||||
[(id :unique :primary-key)
|
([(id :not-null :primary-key)
|
||||||
(file :not-null)
|
(file :not-null)
|
||||||
(level :not-null)])
|
(level :not-null)
|
||||||
|
(pos :not-null)
|
||||||
|
todo
|
||||||
|
priority
|
||||||
|
(scheduled text)
|
||||||
|
(deadline text)
|
||||||
|
title
|
||||||
|
properties
|
||||||
|
olp]
|
||||||
|
(:foreign-key [file] :references files [file] :on-delete :cascade)))
|
||||||
|
|
||||||
(links
|
(aliases
|
||||||
[(source :not-null)
|
([(node-id :not-null)
|
||||||
(dest :not-null)
|
alias]
|
||||||
(type :not-null)
|
(:foreign-key [node-id] :references nodes [id] :on-delete :cascade)))
|
||||||
(properties :not-null)])
|
|
||||||
|
|
||||||
(tags
|
|
||||||
[(file :unique :primary-key)
|
|
||||||
(tags)])
|
|
||||||
|
|
||||||
(titles
|
|
||||||
[(file :not-null)
|
|
||||||
title])
|
|
||||||
|
|
||||||
(refs
|
(refs
|
||||||
[(ref :unique :not-null)
|
([(node-id :not-null)
|
||||||
(file :not-null)
|
(ref :not-null)
|
||||||
(type :not-null)])))
|
(type :not-null)]
|
||||||
|
(:foreign-key [node-id] :references nodes [id] :on-delete :cascade)))
|
||||||
|
|
||||||
|
(tags
|
||||||
|
([(node-id :not-null)
|
||||||
|
tag]
|
||||||
|
(:foreign-key [node-id] :references nodes [id] :on-delete :cascade)))
|
||||||
|
|
||||||
|
(links
|
||||||
|
([(pos :not-null)
|
||||||
|
(source :not-null)
|
||||||
|
(dest :not-null)
|
||||||
|
(type :not-null)
|
||||||
|
(properties :not-null)]
|
||||||
|
(:foreign-key [source] :references nodes [id] :on-delete :cascade)))))
|
||||||
|
|
||||||
|
(defconst org-roam-db--table-indices
|
||||||
|
'((alias-node-id aliases [node-id])
|
||||||
|
(refs-node-id refs [node-id])
|
||||||
|
(tags-node-id tags [node-id])))
|
||||||
|
|
||||||
(defun org-roam-db--init (db)
|
(defun org-roam-db--init (db)
|
||||||
"Initialize database DB with the correct schema and user version."
|
"Initialize database DB with the correct schema and user version."
|
||||||
(emacsql-with-transaction db
|
(emacsql-with-transaction db
|
||||||
(pcase-dolist (`(,table . ,schema) org-roam-db--table-schemata)
|
(emacsql db "PRAGMA foreign_keys = ON")
|
||||||
|
(pcase-dolist (`(,table ,schema) org-roam-db--table-schemata)
|
||||||
(emacsql db [:create-table $i1 $S2] table schema))
|
(emacsql db [:create-table $i1 $S2] table schema))
|
||||||
|
(pcase-dolist (`(,index-name ,table ,columns) org-roam-db--table-indices)
|
||||||
|
(emacsql db [:create-index $i1 :on $i2 $S3] index-name table columns))
|
||||||
(emacsql db (format "PRAGMA user_version = %s" org-roam-db--version))))
|
(emacsql db (format "PRAGMA user_version = %s" org-roam-db--version))))
|
||||||
|
|
||||||
(defun org-roam-db--upgrade-maybe (db version)
|
(defun org-roam-db--upgrade-maybe (db version)
|
||||||
@ -202,7 +199,7 @@ SQL can be either the emacsql vector representation, or a string."
|
|||||||
(progn
|
(progn
|
||||||
(org-roam-message (format "Upgrading the Org-roam database from version %d to version %d"
|
(org-roam-message (format "Upgrading the Org-roam database from version %d to version %d"
|
||||||
version org-roam-db--version))
|
version org-roam-db--version))
|
||||||
(org-roam-db-build-cache t))))
|
(org-roam-db-sync t))))
|
||||||
version)
|
version)
|
||||||
|
|
||||||
(defun org-roam-db--close (&optional db)
|
(defun org-roam-db--close (&optional db)
|
||||||
@ -219,268 +216,198 @@ the current `org-roam-directory'."
|
|||||||
(dolist (conn (hash-table-values org-roam-db--connection))
|
(dolist (conn (hash-table-values org-roam-db--connection))
|
||||||
(org-roam-db--close conn)))
|
(org-roam-db--close conn)))
|
||||||
|
|
||||||
;;;; Timer-based updating
|
|
||||||
(defvar org-roam-db-file-update-timer nil
|
|
||||||
"Timer for updating the database when dirty.")
|
|
||||||
|
|
||||||
(defun org-roam-db-mark-dirty ()
|
|
||||||
"Mark the Org-roam database as dirty."
|
|
||||||
(add-to-list 'org-roam-db-dirty (list org-roam-directory org-roam-db-location)
|
|
||||||
nil #'equal))
|
|
||||||
|
|
||||||
(defun org-roam-db-update-cache-on-timer ()
|
|
||||||
"Update the cache if the database is dirty.
|
|
||||||
This function is called on `org-roam-db-file-update-timer'."
|
|
||||||
(pcase-dolist (`(,org-roam-directory ,org-roam-db-location) org-roam-db-dirty)
|
|
||||||
(org-roam-db-build-cache))
|
|
||||||
(setq org-roam-db-dirty nil))
|
|
||||||
|
|
||||||
;;;; Database API
|
;;;; Database API
|
||||||
;;;;; Initialization
|
|
||||||
(defun org-roam-db--initialized-p ()
|
|
||||||
"Whether the Org-roam cache has been initialized."
|
|
||||||
(and (file-exists-p org-roam-db-location)
|
|
||||||
(> (caar (org-roam-db-query [:select (funcall count) :from titles]))
|
|
||||||
0)))
|
|
||||||
|
|
||||||
(defun org-roam-db--ensure-built ()
|
|
||||||
"Ensures that Org-roam cache is built."
|
|
||||||
(unless (org-roam-db--initialized-p)
|
|
||||||
(error "[Org-roam] your cache isn't built yet! Please run org-roam-db-build-cache")))
|
|
||||||
|
|
||||||
;;;;; Clearing
|
;;;;; Clearing
|
||||||
(defun org-roam-db-clear ()
|
(defun org-roam-db-clear-all ()
|
||||||
"Clears all entries in the Org-roam cache."
|
"Clears all entries in the Org-roam cache."
|
||||||
(interactive)
|
(interactive)
|
||||||
(when (file-exists-p org-roam-db-location)
|
(when (file-exists-p org-roam-db-location)
|
||||||
(dolist (table (mapcar #'car org-roam-db--table-schemata))
|
(dolist (table (mapcar #'car org-roam-db--table-schemata))
|
||||||
(org-roam-db-query `[:delete :from ,table]))))
|
(org-roam-db-query `[:delete :from ,table]))))
|
||||||
|
|
||||||
(defun org-roam-db--clear-file (&optional file)
|
(defun org-roam-db-clear-file (&optional file)
|
||||||
"Remove any related links to the FILE.
|
"Remove any related links to the FILE.
|
||||||
This is equivalent to removing the node from the graph."
|
This is equivalent to removing the node from the graph.
|
||||||
|
If FILE is nil, clear the current buffer."
|
||||||
(setq file (or file (buffer-file-name (buffer-base-buffer))))
|
(setq file (or file (buffer-file-name (buffer-base-buffer))))
|
||||||
(dolist (table (mapcar #'car org-roam-db--table-schemata))
|
|
||||||
(org-roam-db-query `[:delete :from ,table
|
|
||||||
:where (= ,(if (eq table 'links) 'source 'file) $s1)]
|
|
||||||
file)))
|
|
||||||
|
|
||||||
;;;;; Inserting
|
|
||||||
(defun org-roam-db--insert-meta (&optional update-p)
|
|
||||||
"Update the metadata of the current buffer into the cache.
|
|
||||||
If UPDATE-P is non-nil, first remove the meta for the file in the database."
|
|
||||||
(let* ((file (or org-roam-file-name (buffer-file-name)))
|
|
||||||
(attr (file-attributes file))
|
|
||||||
(atime (file-attribute-access-time attr))
|
|
||||||
(mtime (file-attribute-modification-time attr))
|
|
||||||
(hash (org-roam-db--file-hash)))
|
|
||||||
(when update-p
|
|
||||||
(org-roam-db-query [:delete :from files
|
(org-roam-db-query [:delete :from files
|
||||||
:where (= file $s1)]
|
:where (= file $s1)]
|
||||||
file))
|
file))
|
||||||
|
|
||||||
|
;;;;; Updating tables
|
||||||
|
(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."
|
||||||
|
(let* ((file (buffer-file-name))
|
||||||
|
(hash (org-roam-db--file-hash)))
|
||||||
(org-roam-db-query
|
(org-roam-db-query
|
||||||
[:insert :into files
|
[:insert :into files
|
||||||
:values $v1]
|
:values $v1]
|
||||||
(list (vector file hash (list :atime atime :mtime mtime))))))
|
(list (vector file hash)))))
|
||||||
|
|
||||||
(defun org-roam-db--insert-titles (&optional update-p)
|
(defun org-roam-db-get-scheduled-time ()
|
||||||
"Update the titles of the current buffer into the cache.
|
"Return the scheduled time at point in ISO8601 format."
|
||||||
If UPDATE-P is non-nil, first remove titles for the file in the database.
|
(when-let ((time (org-get-scheduled-time (point))))
|
||||||
Returns the number of rows inserted."
|
(org-format-time-string "%FT%T%z" time)))
|
||||||
(let* ((file (or org-roam-file-name (buffer-file-name)))
|
|
||||||
(titles (or (org-roam--extract-titles)
|
(defun org-roam-db-get-deadline-time ()
|
||||||
(list (org-roam--path-to-slug file))))
|
"Return the deadline time at point in ISO8601 format."
|
||||||
(rows (mapcar (lambda (title)
|
(when-let ((time (org-get-deadline-time (point))))
|
||||||
(vector file title)) titles)))
|
(org-format-time-string "%FT%T%z" time)))
|
||||||
(when update-p
|
|
||||||
(org-roam-db-query [:delete :from titles
|
(defun org-roam-db-map-headlines (fns)
|
||||||
:where (= file $s1)]
|
"Run FNS over all headlines in the current buffer."
|
||||||
file))
|
(org-with-point-at 1
|
||||||
|
(org-map-entries
|
||||||
|
(lambda ()
|
||||||
|
(dolist (fn fns)
|
||||||
|
(funcall fn))))))
|
||||||
|
|
||||||
|
(defun org-roam-db-map-links (fns)
|
||||||
|
"Run FNS over all links in the current buffer."
|
||||||
|
(org-with-point-at 1
|
||||||
|
(org-element-map (org-element-parse-buffer) 'link
|
||||||
|
(lambda (link)
|
||||||
|
(dolist (fn fns)
|
||||||
|
(funcall fn link))))))
|
||||||
|
|
||||||
|
(defun org-roam-db-insert-file-node ()
|
||||||
|
"Insert the file-level node into the Org-roam cache."
|
||||||
|
(org-with-point-at 1
|
||||||
|
(when (= (org-outline-level) 0)
|
||||||
|
(when-let ((id (org-id-get)))
|
||||||
|
(let* ((file (buffer-file-name (buffer-base-buffer)))
|
||||||
|
(title (org-link-display-format
|
||||||
|
(or (cadr (assoc "TITLE" (org-collect-keywords '("title"))
|
||||||
|
#'string-equal))
|
||||||
|
(file-relative-name file org-roam-directory))))
|
||||||
|
(pos (point))
|
||||||
|
(todo nil)
|
||||||
|
(priority nil)
|
||||||
|
(scheduled nil)
|
||||||
|
(deadline nil)
|
||||||
|
(level 0)
|
||||||
|
(aliases (org-entry-get (point) "ROAM_ALIASES"))
|
||||||
|
(tags org-file-tags)
|
||||||
|
(refs (org-entry-get (point) "ROAM_REFS"))
|
||||||
|
(properties (org-entry-properties))
|
||||||
|
(olp (org-get-outline-path)))
|
||||||
(org-roam-db-query
|
(org-roam-db-query
|
||||||
[:insert :into titles
|
[:insert :into nodes
|
||||||
:values $v1]
|
:values $v1]
|
||||||
rows)
|
(vector id file level pos todo priority
|
||||||
(length rows)))
|
scheduled deadline title properties olp))
|
||||||
|
(when tags
|
||||||
(defun org-roam-db--insert-refs (&optional update-p)
|
|
||||||
"Update the refs of the current buffer into the cache.
|
|
||||||
If UPDATE-P is non-nil, first remove the ref for the file in the database."
|
|
||||||
(let ((file (or org-roam-file-name (buffer-file-name)))
|
|
||||||
(count 0))
|
|
||||||
(when update-p
|
|
||||||
(org-roam-db-query [:delete :from refs
|
|
||||||
:where (= file $s1)]
|
|
||||||
file))
|
|
||||||
(when-let ((refs (org-roam--extract-refs)))
|
|
||||||
(dolist (ref refs)
|
|
||||||
(let ((key (cdr ref))
|
|
||||||
(type (car ref)))
|
|
||||||
(condition-case nil
|
|
||||||
(progn
|
|
||||||
(org-roam-db-query
|
(org-roam-db-query
|
||||||
[:insert :into refs :values $v1]
|
[:insert :into tags
|
||||||
(list (vector key file type)))
|
:values $v1]
|
||||||
(cl-incf count))
|
(mapcar (lambda (tag)
|
||||||
(error
|
(vector id (substring-no-properties tag)))
|
||||||
(lwarn '(org-roam) :error
|
tags)))
|
||||||
(format "Duplicate ref %s in:\n\nA: %s\nB: %s\n\nskipping..."
|
(when aliases
|
||||||
key
|
(org-roam-db-query
|
||||||
file
|
[:insert :into aliases
|
||||||
(caar (org-roam-db-query
|
:values $v1]
|
||||||
[:select file :from refs
|
(mapcar (lambda (alias)
|
||||||
:where (= ref $v1)]
|
(vector id alias))
|
||||||
(vector key))))))))))
|
(split-string-and-unquote aliases))))
|
||||||
count))
|
(when refs
|
||||||
|
(setq refs (split-string-and-unquote refs))
|
||||||
(defun org-roam-db--insert-links (&optional update-p)
|
(let (rows)
|
||||||
"Update the file links of the current buffer in the cache.
|
(dolist (ref refs)
|
||||||
If UPDATE-P is non-nil, first remove the links for the file in the database.
|
(if (string-match org-link-plain-re ref)
|
||||||
Return the number of rows inserted."
|
|
||||||
(let ((file (or org-roam-file-name (buffer-file-name))))
|
|
||||||
(when update-p
|
|
||||||
(org-roam-db-query [:delete :from links
|
|
||||||
:where (= source $s1)]
|
|
||||||
file))
|
|
||||||
(if-let ((links (org-roam--extract-links)))
|
|
||||||
(progn
|
(progn
|
||||||
|
(push (vector id (match-string 2 ref)
|
||||||
|
(match-string 1 ref)) rows))
|
||||||
|
(lwarn '(org-roam) :warning
|
||||||
|
"%s:%s\tInvalid ref %s, skipping..."
|
||||||
|
(buffer-file-name) (point) ref)))
|
||||||
|
(when rows
|
||||||
|
(org-roam-db-query
|
||||||
|
[:insert :into refs
|
||||||
|
:values $v1]
|
||||||
|
rows)))))))))
|
||||||
|
|
||||||
|
(defun org-roam-db-insert-node-data ()
|
||||||
|
"Insert node data for headline at point into the Org-roam cache."
|
||||||
|
(when-let ((id (org-id-get)))
|
||||||
|
(let* ((file (buffer-file-name (buffer-base-buffer)))
|
||||||
|
(heading-components (org-heading-components))
|
||||||
|
(pos (point))
|
||||||
|
(todo (nth 2 heading-components))
|
||||||
|
(priority (nth 3 heading-components))
|
||||||
|
(level (nth 1 heading-components))
|
||||||
|
(scheduled (org-roam-db-get-scheduled-time))
|
||||||
|
(deadline (org-roam-db-get-deadline-time))
|
||||||
|
(title (org-link-display-format (nth 4 heading-components)))
|
||||||
|
(properties (org-entry-properties))
|
||||||
|
(olp (org-get-outline-path)))
|
||||||
|
(org-roam-db-query
|
||||||
|
[:insert :into nodes
|
||||||
|
:values $v1]
|
||||||
|
(vector id file level pos todo priority
|
||||||
|
scheduled deadline title properties olp)))))
|
||||||
|
|
||||||
|
(defun org-roam-db-insert-aliases ()
|
||||||
|
"Insert aliases for node at point into Org-roam cache."
|
||||||
|
(when-let ((file (buffer-file-name (buffer-base-buffer)))
|
||||||
|
(node-id (org-id-get))
|
||||||
|
(aliases (org-entry-get (point) "ROAM_ALIASES")))
|
||||||
|
(org-roam-db-query [:insert :into aliases
|
||||||
|
:values $v1]
|
||||||
|
(mapcar (lambda (alias)
|
||||||
|
(vector file node-id alias))
|
||||||
|
(split-string-and-unquote aliases)))))
|
||||||
|
|
||||||
|
(defun org-roam-db-insert-tags ()
|
||||||
|
"Insert tags for node at point into Org-roam cache."
|
||||||
|
(when-let ((node-id (org-id-get))
|
||||||
|
(tags (org-get-tags)))
|
||||||
|
(org-roam-db-query [:insert :into tags
|
||||||
|
:values $v1]
|
||||||
|
(mapcar (lambda (tag)
|
||||||
|
(vector node-id (substring-no-properties tag))) tags))))
|
||||||
|
|
||||||
|
(defun org-roam-db-insert-refs ()
|
||||||
|
"Insert refs for node at point into Org-roam cache."
|
||||||
|
(when-let* ((node-id (org-id-get))
|
||||||
|
(refs (org-entry-get (point) "ROAM_REFS"))
|
||||||
|
(refs (split-string-and-unquote refs)))
|
||||||
|
(let (rows)
|
||||||
|
(dolist (ref refs)
|
||||||
|
(save-match-data
|
||||||
|
(if (string-match org-link-plain-re ref)
|
||||||
|
(progn
|
||||||
|
(push (vector node-id (match-string 2 ref) (match-string 1 ref)) rows))
|
||||||
|
(lwarn '(org-roam) :warning
|
||||||
|
"%s:%s\tInvalid ref %s, skipping..." (buffer-file-name) (point) ref))))
|
||||||
|
(org-roam-db-query [:insert :into refs
|
||||||
|
:values $v1]
|
||||||
|
rows))))
|
||||||
|
|
||||||
|
(defun org-roam-db-insert-link (link)
|
||||||
|
"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))
|
||||||
|
(dest (org-element-property :path link))
|
||||||
|
(properties (list :outline (org-get-outline-path)))
|
||||||
|
(source (org-roam-id-at-point)))
|
||||||
|
(when source
|
||||||
(org-roam-db-query
|
(org-roam-db-query
|
||||||
[:insert :into links
|
[:insert :into links
|
||||||
:values $v1]
|
:values $v1]
|
||||||
links)
|
(vector (point) source dest type properties))))))
|
||||||
(length links))
|
|
||||||
0)))
|
|
||||||
|
|
||||||
(defun org-roam-db--insert-ids (&optional update-p)
|
|
||||||
"Update the ids of the current buffer into the cache.
|
|
||||||
If UPDATE-P is non-nil, first remove ids for the file in the database.
|
|
||||||
Returns the number of rows inserted."
|
|
||||||
(let ((file (or org-roam-file-name (buffer-file-name))))
|
|
||||||
(when update-p
|
|
||||||
(org-roam-db-query [:delete :from ids
|
|
||||||
:where (= file $s1)]
|
|
||||||
file))
|
|
||||||
(if-let ((ids (org-roam--extract-ids file)))
|
|
||||||
(condition-case nil
|
|
||||||
(progn
|
|
||||||
(org-roam-db-query
|
|
||||||
[:insert :into ids
|
|
||||||
:values $v1]
|
|
||||||
ids)
|
|
||||||
(length ids))
|
|
||||||
(error
|
|
||||||
(lwarn '(org-roam) :error
|
|
||||||
(format "Duplicate IDs in %s, one of:\n\n%s\n\nskipping..."
|
|
||||||
(aref (car ids) 1)
|
|
||||||
(string-join (mapcar (lambda (hl)
|
|
||||||
(aref hl 0)) ids) "\n")))
|
|
||||||
0))
|
|
||||||
0)))
|
|
||||||
|
|
||||||
(defun org-roam-db--insert-tags (&optional update-p)
|
|
||||||
"Insert tags for the current buffer into the Org-roam cache.
|
|
||||||
If UPDATE-P is non-nil, first remove tags for the file in the database.
|
|
||||||
Return the number of rows inserted."
|
|
||||||
(let* ((file (or org-roam-file-name (buffer-file-name)))
|
|
||||||
(tags (org-roam--extract-tags file)))
|
|
||||||
(when update-p
|
|
||||||
(org-roam-db-query [:delete :from tags
|
|
||||||
:where (= file $s1)]
|
|
||||||
file))
|
|
||||||
(if tags
|
|
||||||
(progn (org-roam-db-query
|
|
||||||
[:insert :into tags
|
|
||||||
:values $v1]
|
|
||||||
(list (vector file tags)))
|
|
||||||
1)
|
|
||||||
0)))
|
|
||||||
|
|
||||||
;;;;; Fetching
|
;;;;; Fetching
|
||||||
(defun org-roam-db-has-file-p (file)
|
|
||||||
"Return t if FILE is in the database, nil otherwise."
|
|
||||||
(> (caar (org-roam-db-query [:select (funcall count) :from files
|
|
||||||
:where (= file $s1)]
|
|
||||||
file))
|
|
||||||
0))
|
|
||||||
|
|
||||||
(defun org-roam-db--get-current-files ()
|
(defun org-roam-db--get-current-files ()
|
||||||
"Return a hash-table of file to the hash of its file contents."
|
"Return a hash-table of file to the hash of its file contents."
|
||||||
(let* ((current-files (org-roam-db-query [:select * :from files]))
|
(let ((current-files (org-roam-db-query [:select [file hash] :from files]))
|
||||||
(ht (make-hash-table :test #'equal)))
|
(ht (make-hash-table :test #'equal)))
|
||||||
(dolist (row current-files)
|
(dolist (row current-files)
|
||||||
(puthash (car row) (cadr row) ht))
|
(puthash (car row) (cadr row) ht))
|
||||||
ht))
|
ht))
|
||||||
|
|
||||||
(defun org-roam-db--get-title (file)
|
|
||||||
"Return the main title of FILE from the cache."
|
|
||||||
(caar (org-roam-db-query [:select [title] :from titles
|
|
||||||
:where (= file $s1)
|
|
||||||
:limit 1]
|
|
||||||
file)))
|
|
||||||
|
|
||||||
(defun org-roam-db--get-tags ()
|
|
||||||
"Return all distinct tags from the cache."
|
|
||||||
(let ((rows (org-roam-db-query [:select :distinct [tags] :from tags]))
|
|
||||||
acc)
|
|
||||||
(dolist (row rows)
|
|
||||||
(dolist (tag (car row))
|
|
||||||
(unless (member tag acc)
|
|
||||||
(push tag acc))))
|
|
||||||
acc))
|
|
||||||
|
|
||||||
(defun org-roam-db--connected-component (file)
|
|
||||||
"Return all files reachable from/connected to FILE, including the file itself.
|
|
||||||
If the file does not have any connections, nil is returned."
|
|
||||||
(let* ((query "WITH RECURSIVE
|
|
||||||
links_of(file, link) AS
|
|
||||||
(WITH filelinks AS (SELECT * FROM links WHERE NOT \"type\" = '\"cite\"'),
|
|
||||||
citelinks AS (SELECT * FROM links
|
|
||||||
JOIN refs ON links.\"dest\" = refs.\"ref\"
|
|
||||||
AND links.\"type\" = '\"cite\"')
|
|
||||||
SELECT \"source\", \"dest\" FROM filelinks UNION
|
|
||||||
SELECT \"dest\", \"source\" FROM filelinks UNION
|
|
||||||
SELECT \"file\", \"source\" FROM citelinks UNION
|
|
||||||
SELECT \"dest\", \"file\" FROM citelinks),
|
|
||||||
connected_component(file) AS
|
|
||||||
(SELECT link FROM links_of WHERE file = $s1
|
|
||||||
UNION
|
|
||||||
SELECT link FROM links_of JOIN connected_component USING(file))
|
|
||||||
SELECT * FROM connected_component;")
|
|
||||||
(files (mapcar 'car-safe (emacsql (org-roam-db) query file))))
|
|
||||||
files))
|
|
||||||
|
|
||||||
(defun org-roam-db--links-with-max-distance (file max-distance)
|
|
||||||
"Return all files connected to FILE in at most MAX-DISTANCE steps.
|
|
||||||
This includes the file itself. If the file does not have any
|
|
||||||
connections, nil is returned."
|
|
||||||
(let* ((query "WITH RECURSIVE
|
|
||||||
links_of(file, link) AS
|
|
||||||
(WITH filelinks AS (SELECT * FROM links WHERE NOT \"type\" = '\"cite\"'),
|
|
||||||
citelinks AS (SELECT * FROM links
|
|
||||||
JOIN refs ON links.\"dest\" = refs.\"ref\"
|
|
||||||
AND links.\"type\" = '\"cite\"')
|
|
||||||
SELECT \"source\", \"dest\" FROM filelinks UNION
|
|
||||||
SELECT \"dest\", \"source\" FROM filelinks UNION
|
|
||||||
SELECT \"file\", \"source\" FROM citelinks UNION
|
|
||||||
SELECT \"source\", \"file\" FROM citelinks),
|
|
||||||
-- Links are traversed in a breadth-first search. In order to calculate the
|
|
||||||
-- distance of nodes and to avoid following cyclic links, the visited nodes
|
|
||||||
-- are tracked in 'trace'.
|
|
||||||
connected_component(file, trace) AS
|
|
||||||
(VALUES($s1, json_array($s1))
|
|
||||||
UNION
|
|
||||||
SELECT lo.link, json_insert(cc.trace, '$[' || json_array_length(cc.trace) || ']', lo.link) FROM
|
|
||||||
connected_component AS cc JOIN links_of AS lo USING(file)
|
|
||||||
WHERE (
|
|
||||||
-- Avoid cycles by only visiting each file once.
|
|
||||||
(SELECT count(*) FROM json_each(cc.trace) WHERE json_each.value == lo.link) == 0
|
|
||||||
-- Note: BFS is cut off early here.
|
|
||||||
AND json_array_length(cc.trace) < ($s2 + 1)))
|
|
||||||
SELECT DISTINCT file, min(json_array_length(trace)) AS distance
|
|
||||||
FROM connected_component GROUP BY file ORDER BY distance;")
|
|
||||||
;; In principle the distance would be available in the second column.
|
|
||||||
(files (mapcar 'car-safe (emacsql (org-roam-db) query file max-distance))))
|
|
||||||
files))
|
|
||||||
|
|
||||||
(defun org-roam-db--file-hash (&optional file-path)
|
(defun org-roam-db--file-hash (&optional file-path)
|
||||||
"Compute the hash of FILE-PATH, a file or current buffer."
|
"Compute the hash of FILE-PATH, a file or current buffer."
|
||||||
(if file-path
|
(if file-path
|
||||||
@ -492,30 +419,8 @@ connections, nil is returned."
|
|||||||
(secure-hash 'sha1 (current-buffer)))))
|
(secure-hash 'sha1 (current-buffer)))))
|
||||||
|
|
||||||
;;;;; Updating
|
;;;;; Updating
|
||||||
(defun org-roam-db--update-file (&optional file-path)
|
(defun org-roam-db-sync (&optional force)
|
||||||
"Update Org-roam cache for FILE-PATH.
|
"Synchronize the cache state with the current Org files on-disk.
|
||||||
If the file does not exist anymore, remove it from the cache.
|
|
||||||
If the file exists, update the cache with information."
|
|
||||||
(setq file-path (or file-path
|
|
||||||
(buffer-file-name (buffer-base-buffer))))
|
|
||||||
(if (not (file-exists-p file-path))
|
|
||||||
(org-roam-db--clear-file file-path)
|
|
||||||
;; save the file before performing a database update
|
|
||||||
(when-let ((buf (find-buffer-visiting file-path)))
|
|
||||||
(with-current-buffer buf
|
|
||||||
(save-buffer)))
|
|
||||||
(org-roam--with-temp-buffer file-path
|
|
||||||
(emacsql-with-transaction (org-roam-db)
|
|
||||||
(org-roam-db--insert-meta 'update)
|
|
||||||
(org-roam-db--insert-tags 'update)
|
|
||||||
(org-roam-db--insert-titles 'update)
|
|
||||||
(org-roam-db--insert-refs 'update)
|
|
||||||
(when org-roam-enable-headline-linking
|
|
||||||
(org-roam-db--insert-ids 'update))
|
|
||||||
(org-roam-db--insert-links 'update)))))
|
|
||||||
|
|
||||||
(defun org-roam-db-build-cache (&optional force)
|
|
||||||
"Build the cache for `org-roam-directory'.
|
|
||||||
If FORCE, force a rebuild of the cache from scratch."
|
If FORCE, force a rebuild of the cache from scratch."
|
||||||
(interactive "P")
|
(interactive "P")
|
||||||
(when force (delete-file org-roam-db-location))
|
(when force (delete-file org-roam-db-location))
|
||||||
@ -525,109 +430,61 @@ If FORCE, force a rebuild of the cache from scratch."
|
|||||||
(org-agenda-files nil)
|
(org-agenda-files nil)
|
||||||
(org-roam-files (org-roam--list-all-files))
|
(org-roam-files (org-roam--list-all-files))
|
||||||
(current-files (org-roam-db--get-current-files))
|
(current-files (org-roam-db--get-current-files))
|
||||||
(count-plist nil)
|
|
||||||
(deleted-count 0)
|
|
||||||
(modified-files nil))
|
(modified-files nil))
|
||||||
(dolist (file org-roam-files)
|
(dolist (file org-roam-files)
|
||||||
(let ((contents-hash (org-roam-db--file-hash file)))
|
(let ((contents-hash (org-roam-db--file-hash file)))
|
||||||
(unless (string= (gethash file current-files)
|
(unless (string= (gethash file current-files)
|
||||||
contents-hash)
|
contents-hash)
|
||||||
(push (cons file contents-hash) modified-files)))
|
(push file modified-files)))
|
||||||
(remhash file current-files))
|
(remhash file current-files))
|
||||||
|
(emacsql-with-transaction (org-roam-db)
|
||||||
|
(if (fboundp 'dolist-with-progress-reporter)
|
||||||
|
(dolist-with-progress-reporter (file (hash-table-keys current-files))
|
||||||
|
"Clearing removed files..."
|
||||||
|
(org-roam-db-clear-file file))
|
||||||
(dolist (file (hash-table-keys current-files))
|
(dolist (file (hash-table-keys current-files))
|
||||||
;; These files are no longer around, remove from cache...
|
(org-roam-db-clear-file file)))
|
||||||
(org-roam-db--clear-file file)
|
(if (fboundp 'dolist-with-progress-reporter)
|
||||||
(setq deleted-count (1+ deleted-count)))
|
(dolist-with-progress-reporter (file modified-files)
|
||||||
(setq count-plist (org-roam-db--update-files modified-files))
|
"Processing modified files..."
|
||||||
(org-roam-message "total: Δ%s, files-modified: Δ%s, ids: Δ%s, links: Δ%s, tags: Δ%s, titles: Δ%s, refs: Δ%s, deleted: Δ%s"
|
(org-roam-db-update-file file))
|
||||||
(- (length org-roam-files) (plist-get count-plist :error-count))
|
(dolist (file modified-files)
|
||||||
(plist-get count-plist :modified-count)
|
(org-roam-db-update-file file))))))
|
||||||
(plist-get count-plist :id-count)
|
|
||||||
(plist-get count-plist :link-count)
|
|
||||||
(plist-get count-plist :tag-count)
|
|
||||||
(plist-get count-plist :title-count)
|
|
||||||
(plist-get count-plist :ref-count)
|
|
||||||
deleted-count)))
|
|
||||||
|
|
||||||
(defun org-roam-db--get-file-hash-from-db (&optional file-path)
|
(defun org-roam-db-update-file (&optional file-path)
|
||||||
"Get hash from Org-roam database for FILE-PATH."
|
|
||||||
(setq file-path (or file-path
|
|
||||||
(buffer-file-name (buffer-base-buffer))))
|
|
||||||
(caar (org-roam-db-query [:select hash :from files
|
|
||||||
:where (= file $s1)] file-path)))
|
|
||||||
|
|
||||||
(defun org-roam-db-update-file (file-path)
|
|
||||||
"Update Org-roam cache for FILE-PATH.
|
"Update Org-roam cache for FILE-PATH.
|
||||||
If the file does not exist anymore, remove it from the cache.
|
If the file does not exist anymore, remove it from the cache.
|
||||||
If the file exists, update the cache with information."
|
If the file exists, update the cache with information."
|
||||||
|
(setq file-path (or file-path (buffer-file-name (buffer-base-buffer))))
|
||||||
(let ((content-hash (org-roam-db--file-hash file-path))
|
(let ((content-hash (org-roam-db--file-hash file-path))
|
||||||
(db-hash (org-roam-db--get-file-hash-from-db file-path)))
|
(db-hash (caar (org-roam-db-query [:select hash :from files
|
||||||
|
:where (= file $s1)] file-path))))
|
||||||
(unless (string= content-hash db-hash)
|
(unless (string= content-hash db-hash)
|
||||||
(org-roam-db--update-files (list (cons file-path content-hash)))
|
(org-roam-with-file file-path nil
|
||||||
(org-roam-message "Updated: %s" file-path))))
|
(save-excursion
|
||||||
|
(org-set-regexps-and-options 'tags-only)
|
||||||
|
(org-roam-db-clear-file)
|
||||||
|
(org-roam-db-insert-file)
|
||||||
|
(org-roam-db-insert-file-node)
|
||||||
|
(org-roam-db-map-headlines
|
||||||
|
(list #'org-roam-db-insert-node-data
|
||||||
|
#'org-roam-db-insert-aliases
|
||||||
|
#'org-roam-db-insert-tags
|
||||||
|
#'org-roam-db-insert-refs))
|
||||||
|
(org-roam-db-map-links
|
||||||
|
(list #'org-roam-db-insert-link)))))))
|
||||||
|
|
||||||
(defun org-roam-db--update-files (modified-files)
|
(defun org-roam-db--update-on-save-h ()
|
||||||
"Update Org-roam cache for a list of MODIFIED-FILES.
|
"."
|
||||||
FILES is a list of (file . hash) pairs."
|
(add-hook 'after-save-hook #'org-roam-db-update-file nil t))
|
||||||
(let* ((gc-cons-threshold org-roam-db-gc-threshold)
|
|
||||||
(org-agenda-files nil)
|
|
||||||
(error-count 0)
|
|
||||||
(id-count 0)
|
|
||||||
(link-count 0)
|
|
||||||
(tag-count 0)
|
|
||||||
(title-count 0)
|
|
||||||
(ref-count 0)
|
|
||||||
(modified-count 0))
|
|
||||||
(pcase-dolist (`(,file . _) modified-files)
|
|
||||||
(org-roam-db--clear-file file))
|
|
||||||
;; Process all the files for IDs first
|
|
||||||
;;
|
|
||||||
;; We do this so that link extraction is cheaper: this eliminates the need
|
|
||||||
;; to read the file to check if the ID really exists
|
|
||||||
(pcase-dolist (`(,file . ,contents-hash) modified-files)
|
|
||||||
(let* ((attr (file-attributes file))
|
|
||||||
(atime (file-attribute-access-time attr))
|
|
||||||
(mtime (file-attribute-modification-time attr)))
|
|
||||||
(condition-case nil
|
|
||||||
(org-roam--with-temp-buffer file
|
|
||||||
(org-roam-db-query
|
|
||||||
[:insert :into files
|
|
||||||
:values $v1]
|
|
||||||
(vector file contents-hash (list :atime atime :mtime mtime)))
|
|
||||||
(when org-roam-enable-headline-linking
|
|
||||||
(setq id-count (+ id-count (org-roam-db--insert-ids)))))
|
|
||||||
(file-error
|
|
||||||
(setq error-count (1+ error-count))
|
|
||||||
(org-roam-db--clear-file file)
|
|
||||||
(lwarn '(org-roam) :warning
|
|
||||||
"Skipping unreadable file while building cache: %s" file)))))
|
|
||||||
|
|
||||||
;; Process titles, tags, links and ref links of file
|
(add-to-list 'org-roam-find-file-hook #'org-roam-db--update-on-save-h)
|
||||||
(pcase-dolist (`(,file . _) modified-files)
|
|
||||||
(org-roam-message "Processed %s/%s modified files..." modified-count (length modified-files))
|
|
||||||
(condition-case nil
|
|
||||||
(org-roam--with-temp-buffer file
|
|
||||||
(setq modified-count (1+ modified-count))
|
|
||||||
(setq link-count (+ link-count (org-roam-db--insert-links)))
|
|
||||||
(setq tag-count (+ tag-count (org-roam-db--insert-tags)))
|
|
||||||
(setq title-count (+ title-count (org-roam-db--insert-titles)))
|
|
||||||
(setq ref-count (+ ref-count (org-roam-db--insert-refs))))
|
|
||||||
(file-error
|
|
||||||
(setq error-count (1+ error-count))
|
|
||||||
(org-roam-db--clear-file file)
|
|
||||||
(lwarn '(org-roam) :warning
|
|
||||||
"Skipping unreadable file while building cache: %s" file))))
|
|
||||||
(list :error-count error-count :modified-count modified-count :id-count id-count :title-count title-count :tag-count tag-count :link-count link-count :ref-count ref-count)))
|
|
||||||
|
|
||||||
(defun org-roam-db-update ()
|
;; Diagnostic Interactives
|
||||||
"Update the database."
|
(defun org-roam-db-diagnose-node ()
|
||||||
(pcase org-roam-db-update-method
|
"Print information about node at point."
|
||||||
('immediate
|
(interactive)
|
||||||
(org-roam-db-update-file (buffer-file-name (buffer-base-buffer))))
|
(prin1 (org-roam-node-at-point)))
|
||||||
('idle-timer
|
|
||||||
(org-roam-db-mark-dirty))
|
|
||||||
(_
|
|
||||||
(user-error "Invalid `org-roam-db-update-method'"))))
|
|
||||||
|
|
||||||
(provide 'org-roam-db)
|
(provide 'org-roam-db)
|
||||||
|
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
;;; org-roam-dev.el --- Org-roam development code -mode -*- coding: utf-8; lexical-binding: t; -*-
|
|
||||||
|
|
||||||
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
|
|
||||||
|
|
||||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
|
||||||
;; URL: https://github.com/org-roam/org-roam
|
|
||||||
;; Keywords: org-mode, roam, convenience
|
|
||||||
;; Version: 1.2.3
|
|
||||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
|
||||||
|
|
||||||
;; 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 library provides code for org-roam developers.
|
|
||||||
;; It is intended to be loaded before editing org-roam source files.
|
|
||||||
;; It ensures consistent application of various developer settings.
|
|
||||||
;;
|
|
||||||
;;; Code:
|
|
||||||
(require 'emacsql)
|
|
||||||
|
|
||||||
;;;###autoload
|
|
||||||
(define-minor-mode org-roam-dev-mode
|
|
||||||
"Minor mode for setting the dev environment of Org-roam."
|
|
||||||
:lighter " ORD"
|
|
||||||
(when org-roam-dev-mode
|
|
||||||
(emacsql-fix-vector-indentation)
|
|
||||||
(setq-local sentence-end-double-space nil)))
|
|
||||||
|
|
||||||
(provide 'org-roam-dev)
|
|
||||||
;;; org-roam-dev.el ends here
|
|
@ -5,8 +5,8 @@
|
|||||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||||
;; URL: https://github.com/jethrokuan/org-roam
|
;; URL: https://github.com/jethrokuan/org-roam
|
||||||
;; Keywords: org-mode, roam, convenience
|
;; Keywords: org-mode, roam, convenience
|
||||||
;; Version: 1.2.3
|
;; Version: 2.0.0
|
||||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
|
||||||
|
|
||||||
;; This file is NOT part of GNU Emacs.
|
;; This file is NOT part of GNU Emacs.
|
||||||
|
|
||||||
@ -45,18 +45,18 @@
|
|||||||
(require 'cl-lib)
|
(require 'cl-lib)
|
||||||
(require 'org)
|
(require 'org)
|
||||||
(require 'org-element)
|
(require 'org-element)
|
||||||
(require 's)
|
|
||||||
(require 'dash)
|
(require 'dash)
|
||||||
(require 'org-roam-macs)
|
(eval-when-compile
|
||||||
|
(require 'org-roam-macs))
|
||||||
|
(require 'org-roam)
|
||||||
|
|
||||||
|
(defvar org-roam-mode-map)
|
||||||
|
|
||||||
(declare-function org-roam-insert "org-roam")
|
|
||||||
(declare-function org-roam--get-roam-buffers "org-roam")
|
(declare-function org-roam--get-roam-buffers "org-roam")
|
||||||
(declare-function org-roam--list-all-files "org-roam")
|
(declare-function org-roam--list-all-files "org-roam")
|
||||||
(declare-function org-roam--org-roam-file-p "org-roam")
|
(declare-function org-roam--org-roam-file-p "org-roam")
|
||||||
(declare-function org-roam-mode "org-roam")
|
|
||||||
|
|
||||||
(defvar org-roam-verbose)
|
(defvar org-roam-verbose)
|
||||||
(defvar org-roam-mode)
|
|
||||||
|
|
||||||
(defcustom org-roam-doctor-inhibit-startup t
|
(defcustom org-roam-doctor-inhibit-startup t
|
||||||
"Inhibit `org-mode' startup when processing files with `org-doctor'.
|
"Inhibit `org-mode' startup when processing files with `org-doctor'.
|
||||||
@ -78,79 +78,7 @@ processing multiple files"
|
|||||||
:name 'org-roam-doctor-broken-links
|
:name 'org-roam-doctor-broken-links
|
||||||
:description "Fix broken links."
|
:description "Fix broken links."
|
||||||
:actions '(("d" . ("Unlink" . org-roam-doctor--remove-link))
|
:actions '(("d" . ("Unlink" . org-roam-doctor--remove-link))
|
||||||
("r" . ("Replace link" . org-roam-doctor--replace-link))
|
("r" . ("Replace link" . org-roam-doctor--replace-link))))))
|
||||||
("R" . ("Replace link (keep label)" . org-roam-doctor--replace-link-keep-label))))
|
|
||||||
(make-org-roam-doctor-checker
|
|
||||||
:name 'org-roam-doctor-check-roam-props
|
|
||||||
:description "Check #+roam_* properties.")
|
|
||||||
(make-org-roam-doctor-checker
|
|
||||||
:name 'org-roam-doctor-check-tags
|
|
||||||
:description "Check #+roam_tags.")
|
|
||||||
(make-org-roam-doctor-checker
|
|
||||||
:name 'org-roam-doctor-check-alias
|
|
||||||
:description "Check #+roam_alias.")))
|
|
||||||
|
|
||||||
(defconst org-roam-doctor--supported-roam-properties
|
|
||||||
'("roam_tags" "roam_alias" "roam_key")
|
|
||||||
"List of supported Org-roam properties.")
|
|
||||||
|
|
||||||
(defun org-roam-doctor-check-roam-props (ast)
|
|
||||||
"Checker for detecting invalid #+roam_* properties.
|
|
||||||
AST is the org-element parse tree."
|
|
||||||
(let (reports)
|
|
||||||
(org-element-map ast 'keyword
|
|
||||||
(lambda (kw)
|
|
||||||
(let ((key (org-element-property :key kw)))
|
|
||||||
(when (and (string-prefix-p "ROAM_" key t)
|
|
||||||
(not (member (downcase key) org-roam-doctor--supported-roam-properties)))
|
|
||||||
(push
|
|
||||||
`(,(org-element-property :begin kw)
|
|
||||||
,(concat "Possible mispelled key: "
|
|
||||||
(prin1-to-string key)
|
|
||||||
"\nOrg-roam supports the following keys: "
|
|
||||||
(s-join ", " org-roam-doctor--supported-roam-properties)))
|
|
||||||
reports)))))
|
|
||||||
reports))
|
|
||||||
|
|
||||||
(defun org-roam-doctor-check-tags (ast)
|
|
||||||
"Checker for detecting invalid #+roam_tags.
|
|
||||||
AST is the org-element parse tree."
|
|
||||||
(let (reports)
|
|
||||||
(org-element-map ast 'keyword
|
|
||||||
(lambda (kw)
|
|
||||||
(when (string-collate-equalp (org-element-property :key kw) "roam_tags" nil t)
|
|
||||||
(let ((tags (org-element-property :value kw)))
|
|
||||||
(condition-case nil
|
|
||||||
(split-string-and-unquote tags)
|
|
||||||
(error
|
|
||||||
(push
|
|
||||||
`(,(org-element-property :begin kw)
|
|
||||||
,(concat "Unable to parse tags: "
|
|
||||||
tags
|
|
||||||
(when (s-contains? "," tags)
|
|
||||||
"\nCheck that your tags are not comma-separated.")))
|
|
||||||
reports)))))))
|
|
||||||
reports))
|
|
||||||
|
|
||||||
(defun org-roam-doctor-check-alias (ast)
|
|
||||||
"Checker for detecting invalid #+roam_alias.
|
|
||||||
AST is the org-element parse tree."
|
|
||||||
(let (reports)
|
|
||||||
(org-element-map ast 'keyword
|
|
||||||
(lambda (kw)
|
|
||||||
(when (string-collate-equalp (org-element-property :key kw) "roam_alias" nil t)
|
|
||||||
(let ((aliases (org-element-property :value kw)))
|
|
||||||
(condition-case nil
|
|
||||||
(split-string-and-unquote aliases)
|
|
||||||
(error
|
|
||||||
(push
|
|
||||||
`(,(org-element-property :begin kw)
|
|
||||||
,(concat "Unable to parse aliases: "
|
|
||||||
aliases
|
|
||||||
(when (s-contains? "," aliases)
|
|
||||||
"\nCheck that your aliases are not comma-separated.")))
|
|
||||||
reports)))))))
|
|
||||||
reports))
|
|
||||||
|
|
||||||
(defun org-roam-doctor-broken-links (ast)
|
(defun org-roam-doctor-broken-links (ast)
|
||||||
"Checker for detecting broken links.
|
"Checker for detecting broken links.
|
||||||
@ -158,17 +86,11 @@ AST is the org-element parse tree."
|
|||||||
(let (reports)
|
(let (reports)
|
||||||
(org-element-map ast 'link
|
(org-element-map ast 'link
|
||||||
(lambda (l)
|
(lambda (l)
|
||||||
(when (equal "file" (org-element-property :type l))
|
(when (equal "id" (org-element-property :type l))
|
||||||
(let ((file (org-element-property :path l)))
|
(let ((id (org-element-property :path l)))
|
||||||
(or (file-exists-p file)
|
(unless (org-id-find id)
|
||||||
(file-remote-p file)
|
(push `(,(org-element-property :begin l)
|
||||||
(push
|
,(format "Broken id link \"%s\"" id))
|
||||||
`(,(org-element-property :begin l)
|
|
||||||
,(format (if (org-element-lineage l '(link))
|
|
||||||
"Link to non-existent image file \"%s\"\
|
|
||||||
in link description"
|
|
||||||
"Link to non-existent local file \"%s\"")
|
|
||||||
file))
|
|
||||||
reports))))))
|
reports))))))
|
||||||
reports))
|
reports))
|
||||||
|
|
||||||
@ -221,25 +143,7 @@ CHECKERS is the list of checkers used."
|
|||||||
(condition-case nil
|
(condition-case nil
|
||||||
(save-excursion
|
(save-excursion
|
||||||
(replace-match "")
|
(replace-match "")
|
||||||
(org-roam-insert))
|
(org-roam-node-insert))
|
||||||
(quit (progn
|
|
||||||
(replace-buffer-contents orig)
|
|
||||||
(goto-char p)))))))
|
|
||||||
|
|
||||||
(defun org-roam-doctor--replace-link-keep-label ()
|
|
||||||
"Replace the current link with a new link, keeping the current link's label."
|
|
||||||
(save-match-data
|
|
||||||
(unless (org-in-regexp org-link-bracket-re 1)
|
|
||||||
(user-error "No link at point"))
|
|
||||||
(let ((orig (buffer-string))
|
|
||||||
(p (point)))
|
|
||||||
(condition-case nil
|
|
||||||
(save-excursion
|
|
||||||
(let ((label (if (match-end 2)
|
|
||||||
(match-string-no-properties 2)
|
|
||||||
(org-link-unescape (match-string-no-properties 1)))))
|
|
||||||
(replace-match "")
|
|
||||||
(org-roam-insert nil nil label)))
|
|
||||||
(quit (progn
|
(quit (progn
|
||||||
(replace-buffer-contents orig)
|
(replace-buffer-contents orig)
|
||||||
(goto-char p)))))))
|
(goto-char p)))))))
|
||||||
@ -258,7 +162,7 @@ CHECKERS is the list of checkers used."
|
|||||||
(defun org-roam-doctor--resolve (msg checker)
|
(defun org-roam-doctor--resolve (msg checker)
|
||||||
"Resolve an error.
|
"Resolve an error.
|
||||||
MSG is the error that was found, which is displayed in a help buffer.
|
MSG is the error that was found, which is displayed in a help buffer.
|
||||||
CHECKER is a org-roam-doctor checker instance."
|
CHECKER is a `org-roam-doctor' checker instance."
|
||||||
(let ((actions (org-roam-doctor-checker-actions checker))
|
(let ((actions (org-roam-doctor-checker-actions checker))
|
||||||
c)
|
c)
|
||||||
(push '("e" . ("Edit" . org-roam-doctor--recursive-edit)) actions)
|
(push '("e" . ("Edit" . org-roam-doctor--recursive-edit)) actions)
|
||||||
@ -291,7 +195,6 @@ CHECKER is a org-roam-doctor checker instance."
|
|||||||
"Perform a check on the current buffer to ensure cleanliness.
|
"Perform a check on the current buffer to ensure cleanliness.
|
||||||
If CHECKALL, run the check for all Org-roam files."
|
If CHECKALL, run the check for all Org-roam files."
|
||||||
(interactive "P")
|
(interactive "P")
|
||||||
(unless org-roam-mode (org-roam-mode))
|
|
||||||
(let ((files (if checkall
|
(let ((files (if checkall
|
||||||
(org-roam--list-all-files)
|
(org-roam--list-all-files)
|
||||||
(unless (org-roam--org-roam-file-p)
|
(unless (org-roam--org-roam-file-p)
|
||||||
@ -304,6 +207,7 @@ If CHECKALL, run the check for all Org-roam files."
|
|||||||
(save-window-excursion
|
(save-window-excursion
|
||||||
(let ((existing-buffers (org-roam--get-roam-buffers))
|
(let ((existing-buffers (org-roam--get-roam-buffers))
|
||||||
(org-inhibit-startup org-roam-doctor-inhibit-startup))
|
(org-inhibit-startup org-roam-doctor-inhibit-startup))
|
||||||
|
(org-id-update-id-locations)
|
||||||
(dolist (f files)
|
(dolist (f files)
|
||||||
(let ((buf (find-file-noselect f)))
|
(let ((buf (find-file-noselect f)))
|
||||||
(org-roam-doctor--check buf checkers)
|
(org-roam-doctor--check buf checkers)
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
;;; org-roam-faces.el --- Face definitions -*- coding: utf-8; lexical-binding: t; -*-
|
|
||||||
|
|
||||||
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
|
|
||||||
|
|
||||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
|
||||||
;; URL: https://github.com/org-roam/org-roam
|
|
||||||
;; Keywords: org-mode, roam, convenience
|
|
||||||
;; Version: 1.2.3
|
|
||||||
;; Package-Requires: ((emacs "26.1"))
|
|
||||||
|
|
||||||
;; This file is NOT part of GNU Emacs.
|
|
||||||
|
|
||||||
;; This program is free software; you can redistribute it and/or modify
|
|
||||||
;; it under the terms of the GNU General Public License as published by
|
|
||||||
;; the Free Software Foundation; either version 3, or (at your option)
|
|
||||||
;; any later version.
|
|
||||||
;;
|
|
||||||
;; This program is distributed in the hope that it will be useful,
|
|
||||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
;; GNU General Public License for more details.
|
|
||||||
;;
|
|
||||||
;; You should have received a copy of the GNU General Public License
|
|
||||||
;; along with GNU Emacs; see the file COPYING. If not, write to the
|
|
||||||
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
||||||
;; Boston, MA 02110-1301, USA.
|
|
||||||
|
|
||||||
;;; Commentary:
|
|
||||||
|
|
||||||
;; This file contains the face definitions for Org-roam.
|
|
||||||
|
|
||||||
;;; Code:
|
|
||||||
|
|
||||||
(defgroup org-roam-faces nil
|
|
||||||
"Faces used by Org-roam."
|
|
||||||
:group 'org-roam
|
|
||||||
:group 'faces)
|
|
||||||
|
|
||||||
;;; Definitions
|
|
||||||
(defface org-roam-link
|
|
||||||
'((t :inherit org-link))
|
|
||||||
"Face for Org-roam links."
|
|
||||||
:group 'org-roam-faces)
|
|
||||||
|
|
||||||
(defface org-roam-tag
|
|
||||||
'((t :weight bold))
|
|
||||||
"Face for Org-roam tags in minibuffer commands."
|
|
||||||
:group 'org-roam-faces)
|
|
||||||
|
|
||||||
(defface org-roam-link-current
|
|
||||||
'((t :inherit org-link))
|
|
||||||
"Face for Org-roam links pointing to the current buffer."
|
|
||||||
:group 'org-roam-faces)
|
|
||||||
|
|
||||||
(defface org-roam-link-invalid
|
|
||||||
'((t :inherit (error org-link)))
|
|
||||||
"Face for Org-roam links that are not valid.
|
|
||||||
This face is used for links without a destination."
|
|
||||||
:group 'org-roam-faces)
|
|
||||||
|
|
||||||
(defface org-roam-link-shielded
|
|
||||||
'((t :inherit (warning org-link)))
|
|
||||||
"Face for Org-roam links that are shielded.
|
|
||||||
This face is used on the region target by `org-roam-insertion'
|
|
||||||
during an `org-roam-capture'."
|
|
||||||
:group 'org-roam-faces)
|
|
||||||
|
|
||||||
(defface org-roam-dailies-calendar-note
|
|
||||||
'((t :inherit (org-roam-link) :underline nil))
|
|
||||||
"Face for dates with a daily-note in the calendar."
|
|
||||||
:group 'org-roam-faces)
|
|
||||||
|
|
||||||
;;; _
|
|
||||||
|
|
||||||
(provide 'org-roam-faces)
|
|
||||||
|
|
||||||
;;; org-roam-faces.el ends here
|
|
@ -1,315 +0,0 @@
|
|||||||
;;; org-roam-graph.el --- Graphing API -*- coding: utf-8; lexical-binding: t; -*-
|
|
||||||
|
|
||||||
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
|
|
||||||
|
|
||||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
|
||||||
;; URL: https://github.com/org-roam/org-roam
|
|
||||||
;; Keywords: org-mode, roam, convenience
|
|
||||||
;; Version: 1.2.3
|
|
||||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
|
||||||
|
|
||||||
;; 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 library provides graphing functionality for org-roam.
|
|
||||||
;;
|
|
||||||
;;; Code:
|
|
||||||
(require 'xml) ;xml-escape-string
|
|
||||||
(require 's) ;s-truncate, s-replace
|
|
||||||
(eval-and-compile
|
|
||||||
(require 'org-roam-macs))
|
|
||||||
(require 'org-roam-db)
|
|
||||||
|
|
||||||
;;;; Declarations
|
|
||||||
(defvar org-roam-directory)
|
|
||||||
(defvar org-roam-mode)
|
|
||||||
(declare-function org-roam--org-roam-file-p "org-roam")
|
|
||||||
(declare-function org-roam--path-to-slug "org-roam")
|
|
||||||
(declare-function org-roam-mode "org-roam")
|
|
||||||
|
|
||||||
;;;; Options
|
|
||||||
(defcustom org-roam-graph-viewer (executable-find "firefox")
|
|
||||||
"Method to view the org-roam graph.
|
|
||||||
It may be one of the following:
|
|
||||||
- a string representing the path to the executable for viewing the graph.
|
|
||||||
- a function accepting a single argument: the graph file path.
|
|
||||||
- nil uses `view-file' to view the graph."
|
|
||||||
:type '(choice
|
|
||||||
(string :tag "Path to executable")
|
|
||||||
(function :tag "Function to display graph" eww-open-file)
|
|
||||||
(const :tag "view-file"))
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-graph-executable "dot"
|
|
||||||
"Path to graphing executable, or its name."
|
|
||||||
:type 'string
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-graph-filetype "svg"
|
|
||||||
"File type to generate when producing graphs."
|
|
||||||
:type 'string
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
|
|
||||||
(defcustom org-roam-graph-extra-config nil
|
|
||||||
"Extra options passed to graphviz.
|
|
||||||
Example:
|
|
||||||
'((\"rankdir\" . \"LR\"))"
|
|
||||||
:type '(alist)
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-graph-node-extra-config
|
|
||||||
'(("shape" . "underline")
|
|
||||||
("style" . "rounded,filled")
|
|
||||||
("fillcolor" . "#EEEEEE")
|
|
||||||
("color" . "#C9C9C9")
|
|
||||||
("fontcolor" . "#111111"))
|
|
||||||
"Extra options for graphviz nodes.
|
|
||||||
Example:
|
|
||||||
'((\"color\" . \"skyblue\"))"
|
|
||||||
:type '(alist)
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-graph-edge-extra-config
|
|
||||||
'(("color" . "#333333"))
|
|
||||||
"Extra options for graphviz edges.
|
|
||||||
Example:
|
|
||||||
'((\"dir\" . \"back\"))"
|
|
||||||
:type '(alist)
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-graph-edge-cites-extra-config '(("color" . "red"))
|
|
||||||
"Extra options for graphviz edges for citation links.
|
|
||||||
Example:
|
|
||||||
'((\"dir\" . \"back\"))"
|
|
||||||
:type '(alist)
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-graph-max-title-length 100
|
|
||||||
"Maximum length of titles in graph nodes."
|
|
||||||
:type 'number
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-graph-shorten-titles 'truncate
|
|
||||||
"Determines how long titles appear in graph nodes.
|
|
||||||
Recognized values are the symbols `truncate' and `wrap', in which
|
|
||||||
cases the title will be truncated or wrapped, respectively, if it
|
|
||||||
is longer than `org-roam-graph-max-title-length'.
|
|
||||||
|
|
||||||
All other values including nil will have no effect."
|
|
||||||
:type '(choice
|
|
||||||
(const :tag "truncate" truncate)
|
|
||||||
(const :tag "wrap" wrap)
|
|
||||||
(const :tag "no" nil))
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
(defcustom org-roam-graph-exclude-matcher nil
|
|
||||||
"Matcher for excluding nodes from the generated graph.
|
|
||||||
Any nodes and links for file paths matching this string is
|
|
||||||
excluded from the graph.
|
|
||||||
|
|
||||||
If value is a string, the string is the only matcher.
|
|
||||||
|
|
||||||
If value is a list, all file paths matching any of the strings
|
|
||||||
are excluded."
|
|
||||||
:type '(choice
|
|
||||||
(string :tag "Matcher")
|
|
||||||
(list :tag "Matchers"))
|
|
||||||
:group 'org-roam)
|
|
||||||
|
|
||||||
;;;; Functions
|
|
||||||
(defun org-roam-graph--expand-matcher (col &optional negate where)
|
|
||||||
"Return the exclusion regexp from `org-roam-graph-exclude-matcher'.
|
|
||||||
COL is the symbol to be matched against. if NEGATE, add :not to sql query.
|
|
||||||
set WHERE to true if WHERE query already exists."
|
|
||||||
(let ((matchers (pcase org-roam-graph-exclude-matcher
|
|
||||||
('nil nil)
|
|
||||||
((pred stringp) `(,(concat "%" org-roam-graph-exclude-matcher "%")))
|
|
||||||
((pred listp) (mapcar (lambda (m)
|
|
||||||
(concat "%" m "%"))
|
|
||||||
org-roam-graph-exclude-matcher))
|
|
||||||
(_ (error "Invalid org-roam-graph-exclude-matcher"))))
|
|
||||||
res)
|
|
||||||
(dolist (match matchers)
|
|
||||||
(if where
|
|
||||||
(push :and res)
|
|
||||||
(push :where res)
|
|
||||||
(setq where t))
|
|
||||||
(push col res)
|
|
||||||
(when negate
|
|
||||||
(push :not res))
|
|
||||||
(push :like res)
|
|
||||||
(push match res))
|
|
||||||
(nreverse res)))
|
|
||||||
|
|
||||||
(defun org-roam-graph--dot-option (option &optional wrap-key wrap-val)
|
|
||||||
"Return dot string of form KEY=VAL for OPTION cons.
|
|
||||||
If WRAP-KEY is non-nil it wraps the KEY.
|
|
||||||
If WRAP-VAL is non-nil it wraps the VAL."
|
|
||||||
(concat wrap-key (car option) wrap-key
|
|
||||||
"="
|
|
||||||
wrap-val (cdr option) wrap-val))
|
|
||||||
|
|
||||||
(defun org-roam-graph--dot (node-query)
|
|
||||||
"Build the graphviz dot string for NODE-QUERY.
|
|
||||||
The Org-roam database titles table is read, to obtain the list of titles.
|
|
||||||
The links table is then read to obtain all directed links, and formatted
|
|
||||||
into a digraph."
|
|
||||||
(org-roam-db--ensure-built)
|
|
||||||
(org-roam--with-temp-buffer nil
|
|
||||||
(let* ((nodes (org-roam-db-query node-query))
|
|
||||||
(edges-query
|
|
||||||
`[:with selected :as [:select [file] :from ,node-query]
|
|
||||||
:select :distinct [dest source] :from links
|
|
||||||
:where (and (in dest selected) (in source selected))])
|
|
||||||
(edges-cites-query
|
|
||||||
`[:with selected :as [:select [file] :from ,node-query]
|
|
||||||
:select :distinct [file source]
|
|
||||||
:from links :inner :join refs :on (and (= links:dest refs:ref)
|
|
||||||
(= links:type "cite")
|
|
||||||
(= refs:type "cite"))
|
|
||||||
:where (and (in file selected) (in source selected))])
|
|
||||||
(edges (org-roam-db-query edges-query))
|
|
||||||
(edges-cites (org-roam-db-query edges-cites-query)))
|
|
||||||
(insert "digraph \"org-roam\" {\n")
|
|
||||||
(dolist (option org-roam-graph-extra-config)
|
|
||||||
(insert (org-roam-graph--dot-option option) ";\n"))
|
|
||||||
(dolist (attribute '("node" "edge"))
|
|
||||||
(insert (format " %s [%s];\n" attribute
|
|
||||||
(mapconcat (lambda (var)
|
|
||||||
(org-roam-graph--dot-option var nil "\""))
|
|
||||||
(symbol-value
|
|
||||||
(intern (concat "org-roam-graph-" attribute "-extra-config")))
|
|
||||||
","))))
|
|
||||||
(dolist (node nodes)
|
|
||||||
(let* ((file (xml-escape-string (car node)))
|
|
||||||
(title (or (cadr node)
|
|
||||||
(org-roam--path-to-slug file)))
|
|
||||||
(shortened-title (pcase org-roam-graph-shorten-titles
|
|
||||||
(`truncate (s-truncate org-roam-graph-max-title-length title))
|
|
||||||
(`wrap (s-word-wrap org-roam-graph-max-title-length title))
|
|
||||||
(_ title)))
|
|
||||||
(shortened-title (org-roam-string-quote shortened-title))
|
|
||||||
(title (org-roam-string-quote title))
|
|
||||||
(node-properties
|
|
||||||
`(("label" . ,shortened-title)
|
|
||||||
("URL" . ,(concat "org-protocol://roam-file?file=" (url-hexify-string file)))
|
|
||||||
("tooltip" . ,(xml-escape-string title)))))
|
|
||||||
(insert
|
|
||||||
(format " \"%s\" [%s];\n" file
|
|
||||||
(mapconcat (lambda (n)
|
|
||||||
(org-roam-graph--dot-option n nil "\""))
|
|
||||||
node-properties ",")))))
|
|
||||||
(dolist (edge edges)
|
|
||||||
(insert (apply #'format `(" \"%s\" -> \"%s\";\n"
|
|
||||||
,@(mapcar #'xml-escape-string edge)))))
|
|
||||||
(insert (format " edge [%s];\n"
|
|
||||||
(mapconcat #'org-roam-graph--dot-option
|
|
||||||
org-roam-graph-edge-cites-extra-config ",")))
|
|
||||||
(dolist (edge edges-cites)
|
|
||||||
(insert (apply #'format `(" \"%s\" -> \"%s\";\n"
|
|
||||||
,@(mapcar #'xml-escape-string edge)))))
|
|
||||||
(insert "}")
|
|
||||||
(buffer-string))))
|
|
||||||
|
|
||||||
(defun org-roam-graph--build (&optional node-query callback)
|
|
||||||
"Generate a graph showing the relations between nodes in NODE-QUERY.
|
|
||||||
Execute CALLBACK when process exits successfully.
|
|
||||||
CALLBACK is passed the graph file as its sole argument."
|
|
||||||
(unless (stringp org-roam-graph-executable)
|
|
||||||
(user-error "`org-roam-graph-executable' is not a string"))
|
|
||||||
(unless (executable-find org-roam-graph-executable)
|
|
||||||
(user-error (concat "Cannot find executable \"%s\" to generate the graph. "
|
|
||||||
"Please adjust `org-roam-graph-executable'")
|
|
||||||
org-roam-graph-executable))
|
|
||||||
(let* ((node-query (or node-query
|
|
||||||
`[:select [file title] :from titles
|
|
||||||
,@(org-roam-graph--expand-matcher 'file t)
|
|
||||||
:group :by file]))
|
|
||||||
(graph (org-roam-graph--dot node-query))
|
|
||||||
(temp-dot (make-temp-file "graph." nil ".dot" graph))
|
|
||||||
(temp-graph (make-temp-file "graph." nil (concat "." org-roam-graph-filetype))))
|
|
||||||
(org-roam-message "building graph")
|
|
||||||
(make-process
|
|
||||||
:name "*org-roam-graph--build-process*"
|
|
||||||
:buffer "*org-roam-graph--build-process*"
|
|
||||||
:command `(,org-roam-graph-executable ,temp-dot "-T" ,org-roam-graph-filetype "-o" ,temp-graph)
|
|
||||||
:sentinel (when callback
|
|
||||||
(lambda (process _event)
|
|
||||||
(when (= 0 (process-exit-status process))
|
|
||||||
(funcall callback temp-graph)))))))
|
|
||||||
|
|
||||||
(defun org-roam-graph--open (file)
|
|
||||||
"Open FILE using `org-roam-graph-viewer' with `view-file' as a fallback."
|
|
||||||
(pcase org-roam-graph-viewer
|
|
||||||
((pred stringp)
|
|
||||||
(if (executable-find org-roam-graph-viewer)
|
|
||||||
(condition-case err
|
|
||||||
(call-process org-roam-graph-viewer nil 0 nil file)
|
|
||||||
(error (user-error "Failed to open org-roam graph: %s" err)))
|
|
||||||
(user-error "Executable not found: \"%s\"" org-roam-graph-viewer)))
|
|
||||||
((pred functionp) (funcall org-roam-graph-viewer file))
|
|
||||||
('nil (view-file file))
|
|
||||||
(_ (signal 'wrong-type-argument `((functionp stringp null) ,org-roam-graph-viewer)))))
|
|
||||||
|
|
||||||
(defun org-roam-graph--build-connected-component (file &optional max-distance callback)
|
|
||||||
"Build a graph of nodes connected to FILE.
|
|
||||||
If MAX-DISTANCE is non-nil, limit nodes to MAX-DISTANCE steps.
|
|
||||||
CALLBACK is passed to `org-roam-graph--build'."
|
|
||||||
(let* ((file (expand-file-name file))
|
|
||||||
(files (or (if (and max-distance (>= max-distance 0))
|
|
||||||
(org-roam-db--links-with-max-distance file max-distance)
|
|
||||||
(org-roam-db--connected-component file))
|
|
||||||
(list file)))
|
|
||||||
(query `[:select [file title]
|
|
||||||
:from titles
|
|
||||||
:where (in file [,@files])]))
|
|
||||||
(org-roam-graph--build query callback)))
|
|
||||||
|
|
||||||
;;;; Commands
|
|
||||||
;;;###autoload
|
|
||||||
(defun org-roam-graph (&optional arg file node-query)
|
|
||||||
"Build and possibly display a graph for FILE from NODE-QUERY.
|
|
||||||
If FILE is nil, default to current buffer's file name.
|
|
||||||
ARG may be any of the following values:
|
|
||||||
- nil show the graph.
|
|
||||||
- `\\[universal-argument]' show the graph for FILE.
|
|
||||||
- `\\[universal-argument]' N show the graph for FILE limiting nodes to N steps.
|
|
||||||
- `\\[universal-argument] \\[universal-argument]' build the graph.
|
|
||||||
- `\\[universal-argument]' - build the graph for FILE.
|
|
||||||
- `\\[universal-argument]' -N build the graph for FILE limiting nodes to N steps."
|
|
||||||
(interactive "P")
|
|
||||||
(unless org-roam-mode (org-roam-mode))
|
|
||||||
(let ((file (or file (buffer-file-name (buffer-base-buffer)))))
|
|
||||||
(unless (or (not arg) (equal arg '(16)))
|
|
||||||
(unless file
|
|
||||||
(user-error "Cannot build graph for nil file. Is current buffer visiting a file?"))
|
|
||||||
(unless (org-roam--org-roam-file-p file)
|
|
||||||
(user-error "\"%s\" is not an org-roam file" file)))
|
|
||||||
(pcase arg
|
|
||||||
('nil (org-roam-graph--build node-query #'org-roam-graph--open))
|
|
||||||
('(4) (org-roam-graph--build-connected-component file nil #'org-roam-graph--open))
|
|
||||||
((pred integerp) (org-roam-graph--build-connected-component file (abs arg) (when (>= arg 0) #'org-roam-graph--open)))
|
|
||||||
('(16) (org-roam-graph--build node-query))
|
|
||||||
('- (org-roam-graph--build-connected-component file))
|
|
||||||
(_ (user-error "Unrecognized ARG: %s" arg)))))
|
|
||||||
|
|
||||||
(provide 'org-roam-graph)
|
|
||||||
|
|
||||||
;;; org-roam-graph.el ends here
|
|
324
org-roam-link.el
324
org-roam-link.el
@ -1,324 +0,0 @@
|
|||||||
;;; org-roam-link.el --- Custom links for Org-roam -*- coding: utf-8; lexical-binding: t; -*-
|
|
||||||
|
|
||||||
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
|
|
||||||
;; Alan Carroll
|
|
||||||
|
|
||||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
|
||||||
;; URL: https://github.com/org-roam/org-roam
|
|
||||||
;; Keywords: org-mode, roam, convenience
|
|
||||||
;; Version: 1.2.3
|
|
||||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
|
||||||
|
|
||||||
;; 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 adds the custom `roam:' link to Org-roam. `roam:' links allow linking to
|
|
||||||
;; Org-roam files via their titles and headlines.
|
|
||||||
;;
|
|
||||||
;;; Code:
|
|
||||||
;;;; Dependencies
|
|
||||||
|
|
||||||
(require 'ol)
|
|
||||||
(require 'org-roam-compat)
|
|
||||||
(require 'org-roam-macs)
|
|
||||||
(require 'org-roam-db)
|
|
||||||
|
|
||||||
(require 'org-element)
|
|
||||||
|
|
||||||
(defvar org-roam-completion-ignore-case)
|
|
||||||
(defvar org-roam-directory)
|
|
||||||
(declare-function org-roam--find-file "org-roam")
|
|
||||||
(declare-function org-roam-find-file "org-roam")
|
|
||||||
(declare-function org-roam-format-link "org-roam")
|
|
||||||
|
|
||||||
(defcustom org-roam-link-auto-replace t
|
|
||||||
"When non-nil, replace Org-roam's roam links with file/id equivalents."
|
|
||||||
:group 'org-roam
|
|
||||||
:type 'boolean)
|
|
||||||
|
|
||||||
(defcustom org-roam-link-file-path-type 'relative
|
|
||||||
"How the path name in file links should be stored.
|
|
||||||
Valid values are:
|
|
||||||
|
|
||||||
relative Relative to the current directory, i.e. the directory of the file
|
|
||||||
into which the link is being inserted.
|
|
||||||
absolute Absolute path, if possible with ~ for home directory.
|
|
||||||
noabbrev Absolute path, no abbreviation of home directory."
|
|
||||||
:group 'org-roam
|
|
||||||
:type '(choice
|
|
||||||
(const relative)
|
|
||||||
(const absolute)
|
|
||||||
(const noabbrev))
|
|
||||||
:safe #'symbolp)
|
|
||||||
|
|
||||||
;;; the roam: link
|
|
||||||
(org-link-set-parameters "roam"
|
|
||||||
:follow #'org-roam-link-follow-link)
|
|
||||||
|
|
||||||
(defun org-roam-link-follow-link (_path)
|
|
||||||
"Navigates to location in Org-roam link.
|
|
||||||
This function is called by Org when following links of the type
|
|
||||||
`roam'. While the path is passed, assume that the cursor is on
|
|
||||||
the link."
|
|
||||||
(pcase-let ((`(,link-type ,loc ,desc ,mkr) (org-roam-link--get-location)))
|
|
||||||
(when (and org-roam-link-auto-replace loc desc)
|
|
||||||
(org-roam-link--replace-link link-type loc desc))
|
|
||||||
(pcase link-type
|
|
||||||
("file"
|
|
||||||
(if loc
|
|
||||||
(org-roam--find-file loc)
|
|
||||||
(org-roam-find-file desc nil nil t)))
|
|
||||||
("id"
|
|
||||||
(org-goto-marker-or-bmk mkr)))))
|
|
||||||
|
|
||||||
;;; Retrieval Functions
|
|
||||||
(defun org-roam-link--get-titles ()
|
|
||||||
"Return all titles within Org-roam."
|
|
||||||
(mapcar #'car (org-roam-db-query [:select [titles:title] :from titles])))
|
|
||||||
|
|
||||||
(defun org-roam-link--get-headlines (&optional file with-marker use-stack)
|
|
||||||
"Return all outline headings for the current buffer.
|
|
||||||
If FILE, return outline headings for passed FILE instead.
|
|
||||||
If WITH-MARKER, return a cons cell of (headline . marker).
|
|
||||||
If USE-STACK, include the parent paths as well."
|
|
||||||
(org-roam-with-file file (when with-marker 'keep)
|
|
||||||
(let* ((outline-level-fn outline-level)
|
|
||||||
(path-separator "/")
|
|
||||||
(stack-level 0)
|
|
||||||
stack cands name level marker)
|
|
||||||
(save-excursion
|
|
||||||
(goto-char (point-min))
|
|
||||||
(while (re-search-forward org-complex-heading-regexp nil t)
|
|
||||||
(save-excursion
|
|
||||||
(setq name (substring-no-properties (or (match-string 4) "")))
|
|
||||||
(setq marker (point-marker))
|
|
||||||
(when use-stack
|
|
||||||
(goto-char (match-beginning 0))
|
|
||||||
(setq level (funcall outline-level-fn))
|
|
||||||
;; Update stack. The empty entry guards against incorrect
|
|
||||||
;; headline hierarchies, e.g. a level 3 headline
|
|
||||||
;; immediately following a level 1 entry.
|
|
||||||
(while (<= level stack-level)
|
|
||||||
(pop stack)
|
|
||||||
(cl-decf stack-level))
|
|
||||||
(while (> level stack-level)
|
|
||||||
(push name stack)
|
|
||||||
(cl-incf stack-level))
|
|
||||||
(setq name (mapconcat #'identity
|
|
||||||
(reverse stack)
|
|
||||||
path-separator)))
|
|
||||||
(push (if with-marker
|
|
||||||
(cons name marker)
|
|
||||||
name) cands))))
|
|
||||||
(nreverse cands))))
|
|
||||||
|
|
||||||
|
|
||||||
(defun org-roam-link--get-file-from-title (title &optional no-interactive)
|
|
||||||
"Return the file path corresponding to TITLE.
|
|
||||||
When NO-INTERACTIVE, return nil if there are multiple options."
|
|
||||||
(let ((files (mapcar #'car (org-roam-db-query [:select [titles:file] :from titles
|
|
||||||
:where (= titles:title $v1)]
|
|
||||||
(vector title)))))
|
|
||||||
(pcase files
|
|
||||||
('nil nil)
|
|
||||||
(`(,file) file)
|
|
||||||
(_
|
|
||||||
(unless no-interactive
|
|
||||||
(completing-read "Select file: " files))))))
|
|
||||||
|
|
||||||
(defun org-roam-link--get-id-from-headline (headline &optional file)
|
|
||||||
"Return (marker . id) correspondng to HEADLINE in FILE.
|
|
||||||
If FILE is nil, get ID from current buffer.
|
|
||||||
If there is no corresponding headline, return nil."
|
|
||||||
(save-excursion
|
|
||||||
(org-roam-with-file file 'keep
|
|
||||||
(let ((headlines (org-roam-link--get-headlines file 'with-markers)))
|
|
||||||
(when-let ((marker (cdr (assoc-string headline headlines))))
|
|
||||||
(goto-char marker)
|
|
||||||
(cons marker
|
|
||||||
(when org-roam-link-auto-replace
|
|
||||||
(org-id-get-create))))))))
|
|
||||||
|
|
||||||
;;; Path-related functions
|
|
||||||
(defun org-roam-link-get-path (path &optional type)
|
|
||||||
"Return the PATH of the link to use.
|
|
||||||
If TYPE is non-nil, create a link of TYPE. Otherwise, respect
|
|
||||||
`org-link-file-path-type'."
|
|
||||||
(pcase (or type org-roam-link-file-path-type)
|
|
||||||
('absolute
|
|
||||||
(abbreviate-file-name (expand-file-name path)))
|
|
||||||
('noabbrev
|
|
||||||
(expand-file-name path))
|
|
||||||
('relative
|
|
||||||
(file-relative-name path))))
|
|
||||||
|
|
||||||
(defun org-roam-link--split-path (path)
|
|
||||||
"Splits PATH into title and headline.
|
|
||||||
Return a list of the form (type title has-headline-p headline star-idx).
|
|
||||||
type is one of `title', `headline', `title+headline'.
|
|
||||||
title is the title component of the path.
|
|
||||||
headline is the headline component of the path.
|
|
||||||
star-idx is the index of the asterisk, if any."
|
|
||||||
(save-match-data
|
|
||||||
(let* ((star-index (string-match-p "\\*" path))
|
|
||||||
(title (substring-no-properties path 0 star-index))
|
|
||||||
(headline (if star-index
|
|
||||||
(substring-no-properties path (+ 1 star-index))
|
|
||||||
""))
|
|
||||||
(type (cond ((not star-index)
|
|
||||||
'title)
|
|
||||||
((= 0 star-index)
|
|
||||||
'headline)
|
|
||||||
(t 'title+headline))))
|
|
||||||
(list type title headline star-index))))
|
|
||||||
|
|
||||||
(defun org-roam-link--get-location ()
|
|
||||||
"Return the location of the Org-roam fuzzy link at point.
|
|
||||||
The location is returned as a list containing (link-type loc desc marker).
|
|
||||||
nil is returned if there is no matching location.
|
|
||||||
|
|
||||||
link-type is either \"file\" or \"id\".
|
|
||||||
loc is the target location: e.g. a file path, or an id.
|
|
||||||
marker is a marker to the headline, if applicable.
|
|
||||||
|
|
||||||
desc is either the the description of the link under point, or
|
|
||||||
the target of LINK (title or heading content)."
|
|
||||||
(let ((context (org-element-context))
|
|
||||||
mkr link-type desc loc)
|
|
||||||
(pcase (org-element-lineage context '(link) t)
|
|
||||||
(`nil (error "Not at an Org link"))
|
|
||||||
(link
|
|
||||||
(if (not (string-equal "roam" (org-element-property :type link)))
|
|
||||||
(error "Not at Org-roam link")
|
|
||||||
(setq desc (and (org-element-property :contents-begin link)
|
|
||||||
(org-element-property :contents-end link)
|
|
||||||
(buffer-substring-no-properties
|
|
||||||
(org-element-property :contents-begin link)
|
|
||||||
(org-element-property :contents-end link))))
|
|
||||||
(pcase-let ((`(,type ,title ,headline _) (org-roam-link--split-path
|
|
||||||
(org-element-property :path link))))
|
|
||||||
(pcase type
|
|
||||||
('title+headline
|
|
||||||
(let ((file (org-roam-link--get-file-from-title title)))
|
|
||||||
(if (not file)
|
|
||||||
(org-roam-message "Cannot find matching file")
|
|
||||||
(setq mkr (org-roam-link--get-id-from-headline headline file))
|
|
||||||
(pcase mkr
|
|
||||||
(`(,marker . ,target-id)
|
|
||||||
(progn
|
|
||||||
(setq mkr marker
|
|
||||||
loc target-id
|
|
||||||
desc (or desc headline)
|
|
||||||
link-type "id")))
|
|
||||||
(_ (org-roam-message "Cannot find matching id"))))))
|
|
||||||
('title
|
|
||||||
(setq loc (org-roam-link--get-file-from-title title)
|
|
||||||
link-type "file"
|
|
||||||
desc (or desc title)))
|
|
||||||
('headline
|
|
||||||
(setq mkr (org-roam-link--get-id-from-headline headline))
|
|
||||||
(pcase mkr
|
|
||||||
(`(,marker . ,target-id)
|
|
||||||
(setq mkr marker
|
|
||||||
loc target-id
|
|
||||||
link-type "id"
|
|
||||||
desc (or desc headline)))
|
|
||||||
(_ (org-roam-message "Cannot find matching headline")))))))))
|
|
||||||
(list link-type loc desc mkr)))
|
|
||||||
|
|
||||||
;;; Conversion Functions
|
|
||||||
(defun org-roam-link--replace-link (link-type loc &optional desc)
|
|
||||||
"Replace link at point with a vanilla Org link.
|
|
||||||
LINK-TYPE is the Org link type, typically \"file\" or \"id\".
|
|
||||||
LOC is path for the Org link.
|
|
||||||
DESC is the link description."
|
|
||||||
(save-excursion
|
|
||||||
(save-match-data
|
|
||||||
(unless (org-in-regexp org-link-bracket-re 1)
|
|
||||||
(user-error "No link at point"))
|
|
||||||
(replace-match "")
|
|
||||||
(insert (org-roam-format-link loc desc link-type)))))
|
|
||||||
|
|
||||||
(defun org-roam-link-replace-all ()
|
|
||||||
"Replace all roam links in the current buffer."
|
|
||||||
(interactive)
|
|
||||||
(save-excursion
|
|
||||||
(goto-char (point-min))
|
|
||||||
(while (re-search-forward org-link-bracket-re nil t)
|
|
||||||
(condition-case nil
|
|
||||||
(pcase-let ((`(,link-type ,loc ,desc _) (org-roam-link--get-location)))
|
|
||||||
(when (and link-type loc)
|
|
||||||
(org-roam-link--replace-link link-type loc desc)))
|
|
||||||
(error nil)))))
|
|
||||||
|
|
||||||
(defun org-roam-link--replace-link-on-save ()
|
|
||||||
"Hook to replace all roam links on save."
|
|
||||||
(when org-roam-link-auto-replace
|
|
||||||
(org-roam-link-replace-all)))
|
|
||||||
|
|
||||||
;;; Completion
|
|
||||||
(defun org-roam-link-complete-at-point ()
|
|
||||||
"Do appropriate completion for the link at point."
|
|
||||||
(let ((end (point))
|
|
||||||
(start (point))
|
|
||||||
collection link-type headline-only-p)
|
|
||||||
(when (org-in-regexp org-link-bracket-re 1)
|
|
||||||
(setq start (match-beginning 1)
|
|
||||||
end (match-end 1))
|
|
||||||
(let ((context (org-element-context)))
|
|
||||||
(pcase (org-element-lineage context '(link) t)
|
|
||||||
(`nil nil)
|
|
||||||
(link
|
|
||||||
(setq link-type (org-element-property :type link))
|
|
||||||
(when (member link-type '("roam" "fuzzy"))
|
|
||||||
(when (string= link-type "roam") (setq start (+ start (length "roam:"))))
|
|
||||||
(pcase-let ((`(,type ,title _ ,star-idx)
|
|
||||||
(org-roam-link--split-path (org-element-property :path link))))
|
|
||||||
(pcase type
|
|
||||||
('title+headline
|
|
||||||
(when-let ((file (org-roam-link--get-file-from-title title t)))
|
|
||||||
(setq collection (apply-partially #'org-roam-link--get-headlines file))
|
|
||||||
(setq start (+ start star-idx 1))))
|
|
||||||
('title
|
|
||||||
(setq collection #'org-roam-link--get-titles))
|
|
||||||
('headline
|
|
||||||
(setq collection #'org-roam-link--get-headlines)
|
|
||||||
(setq start (+ start star-idx 1))
|
|
||||||
(setq headline-only-p t)))))))))
|
|
||||||
(when collection
|
|
||||||
(let ((prefix (buffer-substring-no-properties start end)))
|
|
||||||
(list start end
|
|
||||||
(if (functionp collection)
|
|
||||||
(completion-table-case-fold
|
|
||||||
(completion-table-dynamic
|
|
||||||
(lambda (_)
|
|
||||||
(cl-remove-if (apply-partially #'string= prefix)
|
|
||||||
(funcall collection))))
|
|
||||||
(not org-roam-completion-ignore-case))
|
|
||||||
collection)
|
|
||||||
:exit-function
|
|
||||||
(lambda (str &rest _)
|
|
||||||
(delete-char (- 0 (length str)
|
|
||||||
(if headline-only-p 1 0)))
|
|
||||||
(insert (concat (unless (string= link-type "roam") "roam:")
|
|
||||||
(when headline-only-p "*")
|
|
||||||
(org-link-escape str)))))))))
|
|
||||||
|
|
||||||
(provide 'org-roam-link)
|
|
||||||
;;; org-roam-link.el ends here
|
|
@ -5,8 +5,8 @@
|
|||||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||||
;; URL: https://github.com/org-roam/org-roam
|
;; URL: https://github.com/org-roam/org-roam
|
||||||
;; Keywords: org-mode, roam, convenience
|
;; Keywords: org-mode, roam, convenience
|
||||||
;; Version: 1.2.3
|
;; Version: 2.0.0
|
||||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.3") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2"))
|
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
|
||||||
|
|
||||||
;; This file is NOT part of GNU Emacs.
|
;; This file is NOT part of GNU Emacs.
|
||||||
|
|
||||||
@ -27,30 +27,20 @@
|
|||||||
|
|
||||||
;;; Commentary:
|
;;; Commentary:
|
||||||
;;
|
;;
|
||||||
;; This library implements macros and utility functions used throughout
|
;; This library implements macros used throughout org-roam.
|
||||||
;; org-roam.
|
|
||||||
;;
|
|
||||||
;;
|
;;
|
||||||
;;; Code:
|
;;; Code:
|
||||||
;;;; Library Requires
|
(defmacro org-roam-plist-map! (fn plist)
|
||||||
(require 'dash)
|
"Map FN over PLIST, modifying it in-place."
|
||||||
(require 's)
|
(declare (indent 1))
|
||||||
|
(let ((plist-var (make-symbol "plist"))
|
||||||
(defvar org-roam-verbose)
|
(k (make-symbol "k"))
|
||||||
|
(v (make-symbol "v")))
|
||||||
;; This is necessary to ensure all dependents on this module see
|
`(let ((,plist-var (copy-sequence ,plist)))
|
||||||
;; `org-mode-hook' and `org-inhibit-startup' as dynamic variables,
|
(while ,plist-var
|
||||||
;; regardless of whether Org is loaded before their compilation.
|
(setq ,k (pop ,plist-var))
|
||||||
(require 'org)
|
(setq ,v (pop ,plist-var))
|
||||||
|
(setq ,plist (plist-put ,plist ,k (funcall ,fn ,k ,v)))))))
|
||||||
;;;; Utility Functions
|
|
||||||
(defun org-roam--list-interleave (lst separator)
|
|
||||||
"Interleaves elements in LST with SEPARATOR."
|
|
||||||
(when lst
|
|
||||||
(let ((new-lst (list (pop lst))))
|
|
||||||
(dolist (it lst)
|
|
||||||
(nconc new-lst (list separator it)))
|
|
||||||
new-lst)))
|
|
||||||
|
|
||||||
(defmacro org-roam-with-file (file keep-buf-p &rest body)
|
(defmacro org-roam-with-file (file keep-buf-p &rest body)
|
||||||
"Execute BODY within FILE.
|
"Execute BODY within FILE.
|
||||||
@ -58,6 +48,7 @@ If FILE is nil, execute BODY in the current buffer.
|
|||||||
Kills the buffer if KEEP-BUF-P is nil, and FILE is not yet visited."
|
Kills the buffer if KEEP-BUF-P is nil, and FILE is not yet visited."
|
||||||
(declare (indent 2) (debug t))
|
(declare (indent 2) (debug t))
|
||||||
`(let* (new-buf
|
`(let* (new-buf
|
||||||
|
(auto-mode-alist nil)
|
||||||
(buf (or (and (not ,file)
|
(buf (or (and (not ,file)
|
||||||
(current-buffer)) ;If FILE is nil, use current buffer
|
(current-buffer)) ;If FILE is nil, use current buffer
|
||||||
(find-buffer-visiting ,file) ; If FILE is already visited, find buffer
|
(find-buffer-visiting ,file) ; If FILE is already visited, find buffer
|
||||||
@ -66,6 +57,11 @@ Kills the buffer if KEEP-BUF-P is nil, and FILE is not yet visited."
|
|||||||
(find-file-noselect ,file)))) ; Else, visit FILE and return buffer
|
(find-file-noselect ,file)))) ; Else, visit FILE and return buffer
|
||||||
res)
|
res)
|
||||||
(with-current-buffer buf
|
(with-current-buffer buf
|
||||||
|
(unless (equal major-mode 'org-mode)
|
||||||
|
(delay-mode-hooks
|
||||||
|
(let ((org-inhibit-startup t)
|
||||||
|
(org-agenda-files nil))
|
||||||
|
(org-mode))))
|
||||||
(setq res (progn ,@body))
|
(setq res (progn ,@body))
|
||||||
(unless (and new-buf (not ,keep-buf-p))
|
(unless (and new-buf (not ,keep-buf-p))
|
||||||
(save-buffer)))
|
(save-buffer)))
|
||||||
@ -74,57 +70,21 @@ Kills the buffer if KEEP-BUF-P is nil, and FILE is not yet visited."
|
|||||||
(kill-buffer (find-buffer-visiting ,file))))
|
(kill-buffer (find-buffer-visiting ,file))))
|
||||||
res))
|
res))
|
||||||
|
|
||||||
(defmacro org-roam--with-temp-buffer (file &rest body)
|
(defmacro org-roam-with-temp-buffer (file &rest body)
|
||||||
"Execute BODY within a temp buffer.
|
"Execute BODY within a temp buffer.
|
||||||
Like `with-temp-buffer', but propagates `org-roam-directory'.
|
Like `with-temp-buffer', but propagates `org-roam-directory'.
|
||||||
If FILE, set `org-roam-temp-file-name' to file and insert its contents."
|
If FILE, set `default-directory' to FILE's directory and insert its contents."
|
||||||
(declare (indent 1) (debug t))
|
(declare (indent 1) (debug t))
|
||||||
(let ((current-org-roam-directory (make-symbol "current-org-roam-directory")))
|
(let ((current-org-roam-directory (make-symbol "current-org-roam-directory")))
|
||||||
`(let ((,current-org-roam-directory org-roam-directory))
|
`(let ((,current-org-roam-directory org-roam-directory))
|
||||||
(with-temp-buffer
|
(with-temp-buffer
|
||||||
(let ((org-roam-directory ,current-org-roam-directory)
|
(let ((org-roam-directory ,current-org-roam-directory))
|
||||||
(org-mode-hook nil)
|
(delay-mode-hooks (org-mode))
|
||||||
(org-inhibit-startup t))
|
|
||||||
(org-mode)
|
|
||||||
(when ,file
|
(when ,file
|
||||||
(insert-file-contents ,file)
|
(insert-file-contents ,file)
|
||||||
(setq-local org-roam-file-name ,file)
|
|
||||||
(setq-local default-directory (file-name-directory ,file)))
|
(setq-local default-directory (file-name-directory ,file)))
|
||||||
,@body)))))
|
,@body)))))
|
||||||
|
|
||||||
(defun org-roam-message (format-string &rest args)
|
|
||||||
"Pass FORMAT-STRING and ARGS to `message' when `org-roam-verbose' is t."
|
|
||||||
(when org-roam-verbose
|
|
||||||
(apply #'message `(,(concat "(org-roam) " format-string) ,@args))))
|
|
||||||
|
|
||||||
(defun org-roam-string-quote (str)
|
|
||||||
"Quote STR."
|
|
||||||
(->> str
|
|
||||||
(s-replace "\\" "\\\\")
|
|
||||||
(s-replace "\"" "\\\"")))
|
|
||||||
|
|
||||||
;;; Shielding regions
|
|
||||||
(defun org-roam-shield-region (beg end)
|
|
||||||
"Shield REGION against modifications.
|
|
||||||
REGION must be a cons-cell containing the marker to the region
|
|
||||||
beginning and maximum values."
|
|
||||||
(when (and beg end)
|
|
||||||
(add-text-properties beg end
|
|
||||||
'(font-lock-face org-roam-link-shielded
|
|
||||||
read-only t)
|
|
||||||
(marker-buffer beg))
|
|
||||||
(cons beg end)))
|
|
||||||
|
|
||||||
(defun org-roam-unshield-region (beg end)
|
|
||||||
"Unshield the shielded REGION."
|
|
||||||
(when (and beg end)
|
|
||||||
(let ((inhibit-read-only t))
|
|
||||||
(remove-text-properties beg end
|
|
||||||
'(font-lock-face org-roam-link-shielded
|
|
||||||
read-only t)
|
|
||||||
(marker-buffer beg)))
|
|
||||||
(cons beg end)))
|
|
||||||
|
|
||||||
(provide 'org-roam-macs)
|
(provide 'org-roam-macs)
|
||||||
|
|
||||||
;;; org-roam-macs.el ends here
|
;;; org-roam-macs.el ends here
|
||||||
|
458
org-roam-mode.el
Normal file
458
org-roam-mode.el
Normal file
@ -0,0 +1,458 @@
|
|||||||
|
;;; org-roam-mode.el --- create and refresh Org-roam buffers -*- lexical-binding: t -*-
|
||||||
|
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
|
||||||
|
|
||||||
|
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||||
|
;; URL: https://github.com/org-roam/org-roam
|
||||||
|
;; Keywords: org-mode, roam, convenience
|
||||||
|
;; Version: 2.0.0
|
||||||
|
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.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 library implements the abstract major-mode `org-roam-mode', from which
|
||||||
|
;; almost all other Org-roam major-modes derive.
|
||||||
|
;;
|
||||||
|
;;; Code:
|
||||||
|
(require 'magit-section)
|
||||||
|
|
||||||
|
(require 'org-roam-utils)
|
||||||
|
|
||||||
|
(defvar org-roam-directory)
|
||||||
|
(defvar org-roam-find-file-hook)
|
||||||
|
|
||||||
|
(declare-function org-roam--org-file-p "org-roam")
|
||||||
|
(declare-function org-roam-node-at-point "org-roam")
|
||||||
|
|
||||||
|
;;; Faces
|
||||||
|
(defface org-roam-header-line
|
||||||
|
`((((class color) (background light))
|
||||||
|
,@(and (>= emacs-major-version 27) '(:extend t))
|
||||||
|
:foreground "DarkGoldenrod4"
|
||||||
|
:weight bold)
|
||||||
|
(((class color) (background dark))
|
||||||
|
,@(and (>= emacs-major-version 27) '(:extend t))
|
||||||
|
:foreground "LightGoldenrod2"
|
||||||
|
:weight bold))
|
||||||
|
"Face for the `header-line' in some Org-roam modes."
|
||||||
|
:group 'org-roam-faces)
|
||||||
|
|
||||||
|
(defface org-roam-title
|
||||||
|
'((t :weight bold))
|
||||||
|
"Face for Org-roam titles."
|
||||||
|
:group 'org-roam-faces)
|
||||||
|
|
||||||
|
(defface org-roam-olp
|
||||||
|
'((((class color) (background light)) :foreground "grey60")
|
||||||
|
(((class color) (background dark)) :foreground "grey40"))
|
||||||
|
"Face for the OLP of the node."
|
||||||
|
:group 'org-roam-faces)
|
||||||
|
|
||||||
|
(defface org-roam-preview-heading
|
||||||
|
`((((class color) (background light))
|
||||||
|
,@(and (>= emacs-major-version 27) '(:extend t))
|
||||||
|
:background "grey80"
|
||||||
|
:foreground "grey30")
|
||||||
|
(((class color) (background dark))
|
||||||
|
,@(and (>= emacs-major-version 27) '(:extend t))
|
||||||
|
:background "grey25"
|
||||||
|
:foreground "grey70"))
|
||||||
|
"Face for preview headings."
|
||||||
|
:group 'org-roam-faces)
|
||||||
|
|
||||||
|
(defface org-roam-preview-heading-highlight
|
||||||
|
`((((class color) (background light))
|
||||||
|
,@(and (>= emacs-major-version 27) '(:extend t))
|
||||||
|
:background "grey75"
|
||||||
|
:foreground "grey30")
|
||||||
|
(((class color) (background dark))
|
||||||
|
,@(and (>= emacs-major-version 27) '(:extend t))
|
||||||
|
:background "grey35"
|
||||||
|
:foreground "grey70"))
|
||||||
|
"Face for current preview headings."
|
||||||
|
:group 'org-roam-faces)
|
||||||
|
|
||||||
|
(defface org-roam-preview-heading-selection
|
||||||
|
`((((class color) (background light))
|
||||||
|
,@(and (>= emacs-major-version 27) '(:extend t))
|
||||||
|
:inherit org-roam-preview-heading-highlight
|
||||||
|
:foreground "salmon4")
|
||||||
|
(((class color) (background dark))
|
||||||
|
,@(and (>= emacs-major-version 27) '(:extend t))
|
||||||
|
:inherit org-roam-preview-heading-highlight
|
||||||
|
:foreground "LightSalmon3"))
|
||||||
|
"Face for selected preview headings."
|
||||||
|
:group 'org-roam-faces)
|
||||||
|
|
||||||
|
(defface org-roam-preview-region
|
||||||
|
`((t :inherit bold
|
||||||
|
,@(and (>= emacs-major-version 27)
|
||||||
|
(list :extend (ignore-errors (face-attribute 'region :extend))))))
|
||||||
|
"Face used by `org-roam-highlight-preview-region-using-face'.
|
||||||
|
|
||||||
|
This face is overlaid over text that uses other hunk faces,
|
||||||
|
and those normally set the foreground and background colors.
|
||||||
|
The `:foreground' and especially the `:background' properties
|
||||||
|
should be avoided here. Setting the latter would cause the
|
||||||
|
loss of information. Good properties to set here are `:weight'
|
||||||
|
and `:slant'."
|
||||||
|
:group 'org-roam-faces)
|
||||||
|
|
||||||
|
(defface org-roam-dim
|
||||||
|
'((((class color) (background light)) :foreground "grey60")
|
||||||
|
(((class color) (background dark)) :foreground "grey40"))
|
||||||
|
"Face for the dimmer part of the widgets."
|
||||||
|
:group 'org-roam-faces)
|
||||||
|
|
||||||
|
;;; Variables
|
||||||
|
(defvar org-roam-current-node nil
|
||||||
|
"The current node at point.")
|
||||||
|
|
||||||
|
(defcustom org-roam-mode-section-functions (list #'org-roam-backlinks-section
|
||||||
|
#'org-roam-reflinks-section)
|
||||||
|
"Functions which insert sections of the `org-roam-buffer'.
|
||||||
|
Each function is called with one argument, which is the current org-roam node at point."
|
||||||
|
:group 'org-roam
|
||||||
|
:type 'hook)
|
||||||
|
|
||||||
|
;;; The mode
|
||||||
|
(defvar org-roam-mode-map
|
||||||
|
(let ((map (make-sparse-keymap)))
|
||||||
|
(set-keymap-parent map magit-section-mode-map)
|
||||||
|
(define-key map [C-return] 'org-roam-visit-thing)
|
||||||
|
(define-key map (kbd "C-m") 'org-roam-visit-thing)
|
||||||
|
(define-key map [remap revert-buffer] 'org-roam-buffer-render)
|
||||||
|
map)
|
||||||
|
"Parent keymap for all keymaps of modes derived from `org-roam-mode'.")
|
||||||
|
|
||||||
|
(define-derived-mode org-roam-mode magit-section-mode "Org-roam"
|
||||||
|
"Major mode for Org-roam's buffer."
|
||||||
|
:group 'org-roam
|
||||||
|
(face-remap-add-relative 'header-line 'org-roam-header-line))
|
||||||
|
|
||||||
|
;;; Key functions
|
||||||
|
(defun org-roam-visit-thing ()
|
||||||
|
"This is a placeholder command.
|
||||||
|
Where applicable, section-specific keymaps bind another command
|
||||||
|
which visits the thing at point."
|
||||||
|
(interactive)
|
||||||
|
(user-error "There is no thing at point that could be visited"))
|
||||||
|
|
||||||
|
(defun org-roam-buffer-render ()
|
||||||
|
"Render the current node at point."
|
||||||
|
(interactive)
|
||||||
|
(when (derived-mode-p 'org-roam-mode)
|
||||||
|
(let ((inhibit-read-only t))
|
||||||
|
(erase-buffer)
|
||||||
|
(org-roam-set-header-line-format (org-roam-node-title org-roam-current-node))
|
||||||
|
(magit-insert-section (org-roam)
|
||||||
|
(magit-insert-heading)
|
||||||
|
(run-hook-with-args 'org-roam-mode-section-functions org-roam-current-node)))))
|
||||||
|
|
||||||
|
(defun org-roam-buffer ()
|
||||||
|
"Launch an Org-roam buffer for the current node at point."
|
||||||
|
(interactive)
|
||||||
|
(if-let ((node (org-roam-node-at-point)))
|
||||||
|
(progn
|
||||||
|
(let ((buffer (get-buffer-create
|
||||||
|
(concat "org-roam: "
|
||||||
|
(file-relative-name (buffer-file-name) org-roam-directory)))))
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(org-roam-mode)
|
||||||
|
(setq-local org-roam-current-node node)
|
||||||
|
(org-roam-buffer-render))
|
||||||
|
(switch-to-buffer-other-window buffer)))
|
||||||
|
(user-error "No node at point")))
|
||||||
|
|
||||||
|
;;; Persistent buffer
|
||||||
|
(defvar org-roam-buffer "*org-roam*"
|
||||||
|
"The persistent Org-roam buffer name.")
|
||||||
|
|
||||||
|
(defun org-roam-buffer--post-command-h ()
|
||||||
|
"Reconstructs the Org-roam buffer.
|
||||||
|
This needs to be quick or infrequent, because this is run at
|
||||||
|
`post-command-hook'. If REDISPLAY, force an update of
|
||||||
|
the Org-roam buffer."
|
||||||
|
(when (get-buffer-window org-roam-buffer)
|
||||||
|
(when-let ((node (org-roam-node-at-point)))
|
||||||
|
(unless (equal node org-roam-current-node)
|
||||||
|
(setq org-roam-current-node node)
|
||||||
|
(org-roam-buffer-persistent-redisplay)))))
|
||||||
|
|
||||||
|
(define-inline org-roam-buffer--visibility ()
|
||||||
|
"Return whether the current visibility state of the org-roam buffer.
|
||||||
|
Valid states are 'visible, 'exists and 'none."
|
||||||
|
(declare (side-effect-free t))
|
||||||
|
(inline-quote
|
||||||
|
(cond
|
||||||
|
((get-buffer-window org-roam-buffer) 'visible)
|
||||||
|
((get-buffer org-roam-buffer) 'exists)
|
||||||
|
(t 'none))))
|
||||||
|
|
||||||
|
(defun org-roam-buffer-toggle ()
|
||||||
|
"Toggle display of the Org-roam buffer."
|
||||||
|
(interactive)
|
||||||
|
(pcase (org-roam-buffer--visibility)
|
||||||
|
('visible
|
||||||
|
(progn
|
||||||
|
(delete-window (get-buffer-window org-roam-buffer))
|
||||||
|
(remove-hook 'post-command-hook #'org-roam-buffer--post-command-h)))
|
||||||
|
((or 'exists 'none)
|
||||||
|
(progn
|
||||||
|
(display-buffer (get-buffer-create org-roam-buffer))
|
||||||
|
(setq org-roam-current-node (org-roam-node-at-point))
|
||||||
|
(org-roam-buffer-persistent-redisplay)))))
|
||||||
|
|
||||||
|
(defun org-roam-buffer-persistent-redisplay ()
|
||||||
|
"Recompute contents of the persistent Org-roam buffer.
|
||||||
|
Has no effect when `org-roam-current-node' is nil."
|
||||||
|
(when org-roam-current-node
|
||||||
|
(with-current-buffer (get-buffer-create org-roam-buffer)
|
||||||
|
(let ((inhibit-read-only t))
|
||||||
|
(erase-buffer)
|
||||||
|
(org-roam-mode)
|
||||||
|
(org-roam-set-header-line-format (org-roam-node-title org-roam-current-node))
|
||||||
|
(magit-insert-section (org-roam)
|
||||||
|
(magit-insert-heading)
|
||||||
|
(dolist (fn org-roam-mode-section-functions)
|
||||||
|
(funcall fn org-roam-current-node)))))))
|
||||||
|
|
||||||
|
(defun org-roam-buffer--redisplay ()
|
||||||
|
"."
|
||||||
|
(add-hook 'post-command-hook #'org-roam-buffer--post-command-h nil t))
|
||||||
|
|
||||||
|
(add-hook 'org-roam-find-file-hook #'org-roam-buffer--redisplay)
|
||||||
|
|
||||||
|
;;; Sections
|
||||||
|
;;;; Backlinks
|
||||||
|
(cl-defstruct (org-roam-backlink (:constructor org-roam-backlink-create)
|
||||||
|
(:copier nil))
|
||||||
|
source-node target-node
|
||||||
|
point properties)
|
||||||
|
|
||||||
|
(cl-defmethod org-roam-populate ((backlink org-roam-backlink))
|
||||||
|
"Populate BACKLINK from database."
|
||||||
|
(setf (org-roam-backlink-source-node backlink)
|
||||||
|
(org-roam-populate (org-roam-backlink-source-node backlink))
|
||||||
|
(org-roam-backlink-target-node backlink)
|
||||||
|
(org-roam-populate (org-roam-backlink-target-node backlink)))
|
||||||
|
backlink)
|
||||||
|
|
||||||
|
(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")]
|
||||||
|
(org-roam-node-id node))))
|
||||||
|
(cl-loop for backlink in backlinks
|
||||||
|
collect (pcase-let ((`(,source-id ,dest-id ,pos ,properties) backlink))
|
||||||
|
(org-roam-populate
|
||||||
|
(org-roam-backlink-create
|
||||||
|
:source-node (org-roam-node-create :id source-id)
|
||||||
|
:target-node (org-roam-node-create :id dest-id)
|
||||||
|
:point pos
|
||||||
|
:properties properties))))))
|
||||||
|
|
||||||
|
(defun org-roam-backlinks-sort (a b)
|
||||||
|
"Default sorting function for backlinks A and B.
|
||||||
|
Sorts by title."
|
||||||
|
(string< (org-roam-node-title (org-roam-backlink-source-node a))
|
||||||
|
(org-roam-node-title (org-roam-backlink-source-node b))))
|
||||||
|
|
||||||
|
(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 "Backlinks:")
|
||||||
|
(dolist (backlink backlinks)
|
||||||
|
(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)))
|
||||||
|
(insert ?\n))))
|
||||||
|
|
||||||
|
;;;; Reflinks
|
||||||
|
(cl-defstruct (org-roam-reflink (:constructor org-roam-reflink-create)
|
||||||
|
(:copier nil))
|
||||||
|
source-node ref
|
||||||
|
point properties)
|
||||||
|
|
||||||
|
(cl-defmethod org-roam-populate ((reflink org-roam-reflink))
|
||||||
|
"Populate REFLINK from database."
|
||||||
|
(setf (org-roam-reflink-source-node reflink)
|
||||||
|
(org-roam-populate (org-roam-reflink-source-node reflink)))
|
||||||
|
reflink)
|
||||||
|
|
||||||
|
(defun org-roam-reflinks-get (node)
|
||||||
|
"Return the reflinks for NODE."
|
||||||
|
(let ((refs (org-roam-db-query [:select [ref] :from refs
|
||||||
|
:where (= node-id $s1)]
|
||||||
|
(org-roam-node-id node)))
|
||||||
|
links)
|
||||||
|
(pcase-dolist (`(,ref) refs)
|
||||||
|
(pcase-dolist (`(,source-id ,pos ,properties) (org-roam-db-query
|
||||||
|
[:select [source pos properties]
|
||||||
|
:from links
|
||||||
|
:where (= dest $s1)]
|
||||||
|
ref))
|
||||||
|
(push (org-roam-populate
|
||||||
|
(org-roam-reflink-create
|
||||||
|
:source-node (org-roam-node-create :id source-id)
|
||||||
|
:ref ref
|
||||||
|
:point pos
|
||||||
|
:properties properties)) links)))
|
||||||
|
links))
|
||||||
|
|
||||||
|
(defun org-roam-reflinks-sort (a b)
|
||||||
|
"Default sorting function for reflinks A and B.
|
||||||
|
Sorts by title."
|
||||||
|
(string< (org-roam-node-title (org-roam-reflink-source-node a))
|
||||||
|
(org-roam-node-title (org-roam-reflink-source-node b))))
|
||||||
|
|
||||||
|
(defun org-roam-reflinks-section (node)
|
||||||
|
"The reflinks section for NODE."
|
||||||
|
(when (org-roam-node-refs node)
|
||||||
|
(let* ((reflinks (seq-sort #'org-roam-reflinks-sort (org-roam-reflinks-get node))))
|
||||||
|
(magit-insert-section (org-roam-reflinks)
|
||||||
|
(magit-insert-heading "Reflinks:")
|
||||||
|
(dolist (reflink reflinks)
|
||||||
|
(org-roam-node-insert-section
|
||||||
|
:source-node (org-roam-reflink-source-node reflink)
|
||||||
|
:point (org-roam-reflink-point reflink)
|
||||||
|
:properties (org-roam-reflink-properties reflink)))
|
||||||
|
(insert ?\n)))))
|
||||||
|
|
||||||
|
;;;; Unlinked references
|
||||||
|
(defvar org-roam-grep-map
|
||||||
|
(let ((map (make-sparse-keymap)))
|
||||||
|
(set-keymap-parent map org-roam-mode-map)
|
||||||
|
(define-key map [remap org-roam-visit-thing] 'org-roam-file-visit)
|
||||||
|
map)
|
||||||
|
"Keymap for Org-roam grep result sections.")
|
||||||
|
|
||||||
|
(defclass org-roam-grep-section (magit-section)
|
||||||
|
((keymap :initform org-roam-grep-map)
|
||||||
|
(file :initform nil)
|
||||||
|
(row :initform nil)
|
||||||
|
(col :initform nil)))
|
||||||
|
|
||||||
|
(defun org-roam-file-at-point (&optional assert)
|
||||||
|
"Return the file at point.
|
||||||
|
If ASSERT, throw an error."
|
||||||
|
(if-let ((file (magit-section-case
|
||||||
|
(org-roam-node-section (org-roam-node-file (oref it node)))
|
||||||
|
(org-roam-grep-section (oref it file))
|
||||||
|
(org-roam-preview-section (oref it file)))))
|
||||||
|
file
|
||||||
|
(when assert
|
||||||
|
(user-error "No file at point"))))
|
||||||
|
|
||||||
|
(defun org-roam-file-visit (file &optional other-window row col)
|
||||||
|
"Visits FILE.
|
||||||
|
With a prefix argument OTHER-WINDOW, display the buffer in
|
||||||
|
another window instead.
|
||||||
|
If ROW, move to the row, and if COL move to the COL."
|
||||||
|
(interactive (list (org-roam-file-at-point t)
|
||||||
|
current-prefix-arg
|
||||||
|
(oref (magit-current-section) row)
|
||||||
|
(oref (magit-current-section) col)))
|
||||||
|
(let ((buf (find-file-noselect file)))
|
||||||
|
(with-current-buffer buf
|
||||||
|
(widen)
|
||||||
|
(goto-char (point-min))
|
||||||
|
(when row
|
||||||
|
(forward-line (1- row)))
|
||||||
|
(when col
|
||||||
|
(forward-char (1- col))))
|
||||||
|
(funcall (if other-window
|
||||||
|
#'switch-to-buffer-other-window
|
||||||
|
#'pop-to-buffer-same-window) buf)))
|
||||||
|
|
||||||
|
(defvar org-roam-unlinked-references-result-re
|
||||||
|
(rx (group (one-or-more anything))
|
||||||
|
":"
|
||||||
|
(group (one-or-more digit))
|
||||||
|
":"
|
||||||
|
(group (one-or-more digit))
|
||||||
|
":"
|
||||||
|
(group (zero-or-more anything)))
|
||||||
|
"Regex for the return result of a ripgrep query.")
|
||||||
|
|
||||||
|
(defun org-roam-unlinked-references-preview-line (file row)
|
||||||
|
"Return the preview line from FILE.
|
||||||
|
This is the ROW within FILE."
|
||||||
|
(with-temp-buffer
|
||||||
|
(insert-file-contents-literally file)
|
||||||
|
(forward-line (1- row))
|
||||||
|
(buffer-substring-no-properties
|
||||||
|
(save-excursion
|
||||||
|
(beginning-of-line)
|
||||||
|
(point))
|
||||||
|
(save-excursion
|
||||||
|
(end-of-line)
|
||||||
|
(point)))))
|
||||||
|
|
||||||
|
(defun org-roam-unlinked-references-section (node)
|
||||||
|
"The unlinked references section for NODE.
|
||||||
|
References from FILE are excluded."
|
||||||
|
(when (and (executable-find "rg")
|
||||||
|
(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)))
|
||||||
|
(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:")
|
||||||
|
(dolist (line results)
|
||||||
|
(save-match-data
|
||||||
|
(when (string-match org-roam-unlinked-references-result-re line)
|
||||||
|
(setq f (match-string 1 line)
|
||||||
|
row (string-to-number (match-string 2 line))
|
||||||
|
col (string-to-number (match-string 3 line))
|
||||||
|
match (match-string 4 line))
|
||||||
|
(when (and match
|
||||||
|
(not (f-equal-p (org-roam-node-file node) f))
|
||||||
|
(member (downcase match) (mapcar #'downcase titles)))
|
||||||
|
(magit-insert-section section (org-roam-grep-section)
|
||||||
|
(oset section file f)
|
||||||
|
(oset section row row)
|
||||||
|
(oset section col col)
|
||||||
|
(insert (propertize (format "%s:%s:%s"
|
||||||
|
(truncate-string-to-width (file-name-base f) 15 nil nil "...")
|
||||||
|
row col) 'font-lock-face 'org-roam-dim)
|
||||||
|
" "
|
||||||
|
(org-roam-fontify-like-in-org-mode
|
||||||
|
(org-roam-unlinked-references-preview-line f row))
|
||||||
|
"\n"))))))
|
||||||
|
(insert ?\n)))))
|
||||||
|
|
||||||
|
(provide 'org-roam-mode)
|
||||||
|
;;; org-roam-mode.el ends here
|
@ -4,8 +4,8 @@
|
|||||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||||
;; URL: https://github.com/org-roam/org-roam
|
;; URL: https://github.com/org-roam/org-roam
|
||||||
;; Keywords: org-mode, roam, convenience
|
;; Keywords: org-mode, roam, convenience
|
||||||
;; Version: 1.2.3
|
;; Version: 2.0.0
|
||||||
;; Package-Requires: ((emacs "26.1") (org "9.3"))
|
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
|
||||||
|
|
||||||
;; This file is NOT part of GNU Emacs.
|
;; This file is NOT part of GNU Emacs.
|
||||||
|
|
||||||
@ -37,6 +37,8 @@
|
|||||||
;;; Code:
|
;;; Code:
|
||||||
(require 'org-protocol)
|
(require 'org-protocol)
|
||||||
(require 'org-roam)
|
(require 'org-roam)
|
||||||
|
(eval-when-compile
|
||||||
|
(require 'org-roam-macs))
|
||||||
(require 'ol) ;; for org-link-decode
|
(require 'ol) ;; for org-link-decode
|
||||||
|
|
||||||
(defcustom org-roam-protocol-store-links nil
|
(defcustom org-roam-protocol-store-links nil
|
||||||
@ -54,37 +56,31 @@ It opens or creates a note with the given ref.
|
|||||||
encodeURIComponent(location.href) + \\='&title=\\=' + \\
|
encodeURIComponent(location.href) + \\='&title=\\=' + \\
|
||||||
encodeURIComponent(document.title) + \\='&body=\\=' + \\
|
encodeURIComponent(document.title) + \\='&body=\\=' + \\
|
||||||
encodeURIComponent(window.getSelection())"
|
encodeURIComponent(window.getSelection())"
|
||||||
(when-let* ((alist (org-roam--plist-to-alist info))
|
(unless (plist-get info :ref)
|
||||||
(decoded-alist (mapcar (lambda (k.v)
|
(user-error "No ref key provided"))
|
||||||
(let ((key (car k.v))
|
(org-roam-plist-map! (lambda (k v)
|
||||||
(val (cdr k.v)))
|
(org-link-decode
|
||||||
(cons key (org-link-decode val)))) alist)))
|
(if (equal k :ref)
|
||||||
(unless (assoc 'ref decoded-alist)
|
(org-protocol-sanitize-uri v)
|
||||||
(error "No ref key provided"))
|
v))) info)
|
||||||
(when-let ((title (cdr (assoc 'title decoded-alist))))
|
|
||||||
(push (cons 'slug (funcall org-roam-title-to-slug-function title)) decoded-alist))
|
|
||||||
(let-alist decoded-alist
|
|
||||||
(let* ((ref (org-protocol-sanitize-uri .ref))
|
|
||||||
(type (and (string-match org-link-plain-re ref)
|
|
||||||
(match-string 1 ref)))
|
|
||||||
(title (or .title ""))
|
|
||||||
(body (or .body ""))
|
|
||||||
(orglink
|
|
||||||
(org-link-make-string ref (or (org-string-nw-p title) ref))))
|
|
||||||
(when org-roam-protocol-store-links
|
(when org-roam-protocol-store-links
|
||||||
(push (list ref title) org-stored-links))
|
(push (list (plist-get info :ref)
|
||||||
(org-link-store-props :type type
|
(plist-get info :title)) org-stored-links))
|
||||||
:link ref
|
(org-link-store-props :type (and (string-match org-link-plain-re
|
||||||
:annotation orglink
|
(plist-get info :ref))
|
||||||
:initial body)))
|
(match-string 1 (plist-get info :ref)))
|
||||||
(let* ((org-roam-capture-templates org-roam-capture-ref-templates)
|
:link (plist-get info :ref)
|
||||||
(org-roam-capture--context 'ref)
|
:annotation (org-link-make-string (plist-get info :ref)
|
||||||
(org-roam-capture--info decoded-alist)
|
(or (plist-get info :title)
|
||||||
(org-capture-link-is-already-stored t)
|
(plist-get info :ref)))
|
||||||
(template (cdr (assoc 'template decoded-alist))))
|
:initial (or (plist-get info :body) ""))
|
||||||
(raise-frame)
|
(raise-frame)
|
||||||
(org-roam-capture--capture nil template)
|
(org-roam-capture-
|
||||||
(org-roam-message "Item captured.")))
|
:keys (plist-get info :template)
|
||||||
|
:node (org-roam-node-create :title (plist-get info :title))
|
||||||
|
:info (list :ref (plist-get info :ref)
|
||||||
|
:body (plist-get info :body))
|
||||||
|
:templates org-roam-capture-ref-templates)
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
(defun org-roam-protocol-open-file (info)
|
(defun org-roam-protocol-open-file (info)
|
||||||
@ -98,7 +94,7 @@ It should contain the FILE key, pointing to the path of the file to open.
|
|||||||
org-protocol://roam-file?file=/path/to/file.org"
|
org-protocol://roam-file?file=/path/to/file.org"
|
||||||
(when-let ((file (plist-get info :file)))
|
(when-let ((file (plist-get info :file)))
|
||||||
(raise-frame)
|
(raise-frame)
|
||||||
(org-roam--find-file file))
|
(find-file file))
|
||||||
nil)
|
nil)
|
||||||
|
|
||||||
(push '("org-roam-ref" :protocol "roam-ref" :function org-roam-protocol-open-ref)
|
(push '("org-roam-ref" :protocol "roam-ref" :function org-roam-protocol-open-ref)
|
||||||
|
77
org-roam-refile.el
Normal file
77
org-roam-refile.el
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
;;; org-roam-refile.el --- Refile Org-roam Notes -*- lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
|
||||||
|
|
||||||
|
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||||
|
;; URL: https://github.com/org-roam/org-roam
|
||||||
|
;; Keywords: org-mode, roam, convenience
|
||||||
|
;; Version: 2.0.0
|
||||||
|
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.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:
|
||||||
|
;;
|
||||||
|
;; Org-roam refile allows you to refile notes to your nodes.
|
||||||
|
;;
|
||||||
|
;;; Code:
|
||||||
|
(defvar org-auto-align-tags)
|
||||||
|
(defvar org-loop-over-headlines-in-active-region)
|
||||||
|
|
||||||
|
(defun org-roam-refile ()
|
||||||
|
"Refile to node."
|
||||||
|
(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 'require-match))
|
||||||
|
(file (org-roam-node-file node))
|
||||||
|
(nbuf (or (find-buffer-visiting file)
|
||||||
|
(find-file-noselect file)))
|
||||||
|
level reversed)
|
||||||
|
(if regionp
|
||||||
|
(progn
|
||||||
|
(org-kill-new (buffer-substring region-start region-end))
|
||||||
|
(org-save-markers-in-region region-start region-end))
|
||||||
|
(org-copy-subtree 1 nil t))
|
||||||
|
(with-current-buffer nbuf
|
||||||
|
(org-with-wide-buffer
|
||||||
|
(goto-char (org-roam-node-point node))
|
||||||
|
(setq level (org-get-valid-level (funcall outline-level) 1)
|
||||||
|
reversed (org-notes-order-reversed-p))
|
||||||
|
(goto-char
|
||||||
|
(if reversed
|
||||||
|
(or (outline-next-heading) (point-max))
|
||||||
|
(or (save-excursion (org-get-next-sibling))
|
||||||
|
(org-end-of-subtree t t)
|
||||||
|
(point-max))))
|
||||||
|
(unless (bolp) (newline))
|
||||||
|
(org-paste-subtree level nil nil t)
|
||||||
|
(and org-auto-align-tags
|
||||||
|
(let ((org-loop-over-headlines-in-active-region nil))
|
||||||
|
(org-align-tags)))
|
||||||
|
(when (fboundp 'deactivate-mark) (deactivate-mark))))
|
||||||
|
(if regionp
|
||||||
|
(delete-region (point) (+ (point) (- region-end region-start)))
|
||||||
|
(org-preserve-local-variables
|
||||||
|
(delete-region
|
||||||
|
(and (org-back-to-heading t) (point))
|
||||||
|
(min (1+ (buffer-size)) (org-end-of-subtree t t) (point)))))))
|
||||||
|
|
||||||
|
(provide 'org-roam-refile)
|
||||||
|
;;; org-roam-refile.el ends here
|
213
org-roam-utils.el
Normal file
213
org-roam-utils.el
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
;;; org-roam-utils.el --- Utilities for Org-roam -*- coding: utf-8; lexical-binding: t; -*-
|
||||||
|
|
||||||
|
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
|
||||||
|
|
||||||
|
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||||
|
;; URL: https://github.com/org-roam/org-roam
|
||||||
|
;; Keywords: org-mode, roam, convenience
|
||||||
|
;; Version: 2.0.0
|
||||||
|
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.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 library implements utility functions used throughout
|
||||||
|
;; Org-roam.
|
||||||
|
;;
|
||||||
|
;;
|
||||||
|
;;; Code:
|
||||||
|
;;;; Library Requires
|
||||||
|
(require 'dash)
|
||||||
|
(require 's)
|
||||||
|
|
||||||
|
(defvar org-roam-verbose)
|
||||||
|
|
||||||
|
;; This is necessary to ensure all dependents on this module see
|
||||||
|
;; `org-mode-hook' and `org-inhibit-startup' as dynamic variables,
|
||||||
|
;; regardless of whether Org is loaded before their compilation.
|
||||||
|
(require 'org)
|
||||||
|
|
||||||
|
;;;; Utility Functions
|
||||||
|
(defun org-roam--list-interleave (lst separator)
|
||||||
|
"Interleaves elements in LST with SEPARATOR."
|
||||||
|
(when lst
|
||||||
|
(let ((new-lst (list (pop lst))))
|
||||||
|
(dolist (it lst)
|
||||||
|
(nconc new-lst (list separator it)))
|
||||||
|
new-lst)))
|
||||||
|
|
||||||
|
(defun org-roam-up-heading-or-point-min ()
|
||||||
|
"Fixed version of Org's `org-up-heading-or-point-min'."
|
||||||
|
(ignore-errors (org-back-to-heading t))
|
||||||
|
(let ((p (point)))
|
||||||
|
(if (< 1 (funcall outline-level))
|
||||||
|
(progn
|
||||||
|
(org-up-heading-safe)
|
||||||
|
(when (= (point) p)
|
||||||
|
(goto-char (point-min))))
|
||||||
|
(unless (bobp) (goto-char (point-min))))))
|
||||||
|
|
||||||
|
(defun org-roam-message (format-string &rest args)
|
||||||
|
"Pass FORMAT-STRING and ARGS to `message' when `org-roam-verbose' is t."
|
||||||
|
(when org-roam-verbose
|
||||||
|
(apply #'message `(,(concat "(org-roam) " format-string) ,@args))))
|
||||||
|
|
||||||
|
(defvar org-ref-buffer-hacked)
|
||||||
|
|
||||||
|
(defun org-roam-fontify-like-in-org-mode (s)
|
||||||
|
"Fontify string S like in Org mode.
|
||||||
|
Like `org-fontify-like-in-org-mode', but supports `org-ref'."
|
||||||
|
;; NOTE: pretend that the temporary buffer created by `org-fontify-like-in-org-mode' to
|
||||||
|
;; fontify a `cite:' reference has been hacked by org-ref, whatever that means;
|
||||||
|
;;
|
||||||
|
;; `org-ref-cite-link-face-fn', which is used to supply a face for `cite:' links, calls
|
||||||
|
;; `hack-dir-local-variables' rationalizing that `bibtex-completion' would throw some warnings
|
||||||
|
;; otherwise. This doesn't seem to be the case and calling this function just before
|
||||||
|
;; `org-font-lock-ensure' (alias of `font-lock-ensure') actually instead of fixing the alleged
|
||||||
|
;; warnings messes the things so badly that `font-lock-ensure' crashes with error and doesn't let
|
||||||
|
;; org-roam to proceed further. I don't know what's happening there exactly but disabling this hackery
|
||||||
|
;; fixes the crashing. Fortunately, org-ref provides the `org-ref-buffer-hacked' switch, which we use
|
||||||
|
;; here to make it believe that the buffer was hacked.
|
||||||
|
;;
|
||||||
|
;; This is a workaround for `cite:' links and does not have any effect on other ref types.
|
||||||
|
;;
|
||||||
|
;; `org-ref-buffer-hacked' is a buffer-local variable, therefore we inline
|
||||||
|
;; `org-fontify-like-in-org-mode' here
|
||||||
|
(with-temp-buffer
|
||||||
|
(insert s)
|
||||||
|
(let ((org-ref-buffer-hacked t))
|
||||||
|
(org-mode)
|
||||||
|
(org-font-lock-ensure)
|
||||||
|
(buffer-string))))
|
||||||
|
|
||||||
|
(defun org-roam-set-header-line-format (string)
|
||||||
|
"Set the header-line using STRING.
|
||||||
|
If the `face' property of any part of STRING is already set, then
|
||||||
|
that takes precedence. Also pad the left side of STRING so that
|
||||||
|
it aligns with the text area."
|
||||||
|
(setq-local header-line-format
|
||||||
|
(concat (propertize " " 'display '(space :align-to 0))
|
||||||
|
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))))
|
||||||
|
|
||||||
|
;;; Formatting
|
||||||
|
(defun org-roam-format (template replacer)
|
||||||
|
"Format TEMPLATE with the function REPLACER.
|
||||||
|
REPLACER takes an argument of the format variable and optionally
|
||||||
|
an extra argument which is the EXTRA value from the call to
|
||||||
|
`org-roam-format'.
|
||||||
|
Adapted from `s-format'."
|
||||||
|
(let ((saved-match-data (match-data)))
|
||||||
|
(unwind-protect
|
||||||
|
(replace-regexp-in-string
|
||||||
|
"\\${\\([^}]+\\)}"
|
||||||
|
(lambda (md)
|
||||||
|
(let ((var (match-string 1 md))
|
||||||
|
(replacer-match-data (match-data)))
|
||||||
|
(unwind-protect
|
||||||
|
(let ((v (progn
|
||||||
|
(set-match-data saved-match-data)
|
||||||
|
(funcall replacer var))))
|
||||||
|
(if v (format "%s" v) (signal 'org-roam-format-resolve md)))
|
||||||
|
(set-match-data replacer-match-data)))) template
|
||||||
|
;; Need literal to make sure it works
|
||||||
|
t t)
|
||||||
|
(set-match-data saved-match-data))))
|
||||||
|
|
||||||
|
(defvar org-roam--cached-display-format nil)
|
||||||
|
|
||||||
|
(defun org-roam--process-display-format (format)
|
||||||
|
"Pre-calculate minimal widths needed by the FORMAT string."
|
||||||
|
(or org-roam--cached-display-format
|
||||||
|
(setq org-roam--cached-display-format
|
||||||
|
(let* ((fields-width 0)
|
||||||
|
(string-width
|
||||||
|
(string-width
|
||||||
|
(org-roam-format
|
||||||
|
format
|
||||||
|
(lambda (field)
|
||||||
|
(setq fields-width
|
||||||
|
(+ fields-width
|
||||||
|
(string-to-number
|
||||||
|
(or (cadr (split-string field ":"))
|
||||||
|
"")))))))))
|
||||||
|
(cons format (+ fields-width string-width))))))
|
||||||
|
|
||||||
|
;;; Diagnostics
|
||||||
|
;;;###autoload
|
||||||
|
(defun org-roam-version (&optional message)
|
||||||
|
"Return `org-roam' version.
|
||||||
|
Interactively, or when MESSAGE is non-nil, show in the echo area."
|
||||||
|
(interactive)
|
||||||
|
(let* ((version
|
||||||
|
(with-temp-buffer
|
||||||
|
(insert-file-contents-literally (locate-library "org-roam.el"))
|
||||||
|
(goto-char (point-min))
|
||||||
|
(save-match-data
|
||||||
|
(if (re-search-forward "\\(?:;; Version: \\([^z-a]*?$\\)\\)" nil nil)
|
||||||
|
(substring-no-properties (match-string 1))
|
||||||
|
"N/A")))))
|
||||||
|
(if (or message (called-interactively-p 'interactive))
|
||||||
|
(message "%s" version)
|
||||||
|
version)))
|
||||||
|
|
||||||
|
;;;###autoload
|
||||||
|
(defun org-roam-diagnostics ()
|
||||||
|
"Collect and print info for `org-roam' issues."
|
||||||
|
(interactive)
|
||||||
|
(with-current-buffer (switch-to-buffer-other-window (get-buffer-create "*org-roam diagnostics*"))
|
||||||
|
(erase-buffer)
|
||||||
|
(insert (propertize "Copy info below this line into issue:\n" 'face '(:weight bold)))
|
||||||
|
(insert (format "- Emacs: %s\n" (emacs-version)))
|
||||||
|
(insert (format "- Framework: %s\n"
|
||||||
|
(condition-case _
|
||||||
|
(completing-read "I'm using the following Emacs framework:"
|
||||||
|
'("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)))))
|
||||||
|
|
||||||
|
(provide 'org-roam-utils)
|
||||||
|
;;; org-roam-utils.el ends here
|
2162
org-roam.el
2162
org-roam.el
File diff suppressed because it is too large
Load Diff
@ -1,2 +0,0 @@
|
|||||||
#+roam_alias: "a1" "a 2"
|
|
||||||
#+title: t1
|
|
@ -1,3 +1,6 @@
|
|||||||
|
:PROPERTIES:
|
||||||
|
:ID: 440795d0-70c1-4165-993d-aebd5eef7a24
|
||||||
|
:END:
|
||||||
#+title: Bar
|
#+title: Bar
|
||||||
|
|
||||||
This is file bar. Bar links to [[file:nested/bar.org][Nested Bar]].
|
[[id:884b2341-b7fe-434d-848c-5282c0727861][Foo]]
|
||||||
|
@ -1 +0,0 @@
|
|||||||
#+title: Base
|
|
@ -1 +0,0 @@
|
|||||||
#+roam_key: cite:mitsuha2007
|
|
@ -1,8 +1,4 @@
|
|||||||
|
:PROPERTIES:
|
||||||
|
:ID: 884b2341-b7fe-434d-848c-5282c0727861
|
||||||
|
:END:
|
||||||
#+title: Foo
|
#+title: Foo
|
||||||
|
|
||||||
This is the foo file. It contains a link to [[file:bar.org][Bar]].
|
|
||||||
|
|
||||||
To make the tests more robust, here are some arbitrary links:
|
|
||||||
|
|
||||||
- [[https:google.com][Google]]
|
|
||||||
- [[mailto:foo@john.com][mail to foo]]
|
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
#+TITLE: Headline
|
|
||||||
|
|
||||||
* Headline 1
|
|
||||||
:PROPERTIES:
|
|
||||||
:ID: e84d0630-efad-4017-9059-5ef917908823
|
|
||||||
:END:
|
|
||||||
|
|
||||||
* No headline here
|
|
||||||
Oops.
|
|
||||||
|
|
||||||
* Headline 2
|
|
||||||
:PROPERTIES:
|
|
||||||
:ID: 801b58eb-97e2-435f-a33e-ff59a2f0c213
|
|
||||||
:END:
|
|
@ -1,2 +0,0 @@
|
|||||||
#+roam_key: https://www.orgroam.com/
|
|
||||||
#+roam_key: cite:orgroam2020
|
|
@ -1,3 +0,0 @@
|
|||||||
#+title: Nested Bar
|
|
||||||
|
|
||||||
This file is nested, 1 level deeper. It links to both [[file:../foo.org][Foo]] and [[file:foo.org][Nested Foo]].
|
|
@ -1 +0,0 @@
|
|||||||
#+title: Deeply Nested File
|
|
@ -1,3 +0,0 @@
|
|||||||
#+title: Nested Foo
|
|
||||||
|
|
||||||
This file has no links.
|
|
@ -1,5 +0,0 @@
|
|||||||
no title in this file :O
|
|
||||||
|
|
||||||
links to itself, with no title: [[file:no-title.org][no-title]]
|
|
||||||
|
|
||||||
* Headline title
|
|
@ -1,3 +0,0 @@
|
|||||||
#+title: Tagless File
|
|
||||||
|
|
||||||
This file has no tags, and should not yield any tags on extracting via ~#+roam_tags~.
|
|
@ -1,4 +0,0 @@
|
|||||||
#+roam_tags: "t1" "t2 with space" t3
|
|
||||||
#+title: Tags
|
|
||||||
|
|
||||||
This file is used to test functionality for =(org-roam--extract-tags)=
|
|
@ -1 +0,0 @@
|
|||||||
#+roam_alias: "roam" "alias"
|
|
@ -1,4 +0,0 @@
|
|||||||
#+title: TITLE PROP
|
|
||||||
#+roam_alias: "roam" "alias"
|
|
||||||
|
|
||||||
* Headline
|
|
@ -1 +0,0 @@
|
|||||||
* Headline
|
|
@ -1 +0,0 @@
|
|||||||
#+title: Title
|
|
@ -1,3 +0,0 @@
|
|||||||
#+title: Unlinked
|
|
||||||
|
|
||||||
Nothing links here :(
|
|
@ -1 +0,0 @@
|
|||||||
#+roam_key: https://google.com/
|
|
@ -1,53 +0,0 @@
|
|||||||
;;; test-org-roam-perf.el --- Performance Tests for Org-roam -*- lexical-binding: t; -*-
|
|
||||||
|
|
||||||
;; Copyright (C) 2020 Jethro Kuan
|
|
||||||
|
|
||||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
|
||||||
;; Package-Requires: ((buttercup))
|
|
||||||
|
|
||||||
;; This program is free software; you can redistribute it and/or modify
|
|
||||||
;; it under the terms of the GNU General Public License as published by
|
|
||||||
;; the Free Software Foundation, either version 3 of the License, or
|
|
||||||
;; (at your option) any later version.
|
|
||||||
|
|
||||||
;; This program is distributed in the hope that it will be useful,
|
|
||||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
;; GNU General Public License for more details.
|
|
||||||
|
|
||||||
;; You should have received a copy of the GNU General Public License
|
|
||||||
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
;;; Commentary:
|
|
||||||
;;; Code:
|
|
||||||
|
|
||||||
(require 'buttercup)
|
|
||||||
(require 'org-roam)
|
|
||||||
|
|
||||||
(defconst test-org-roam-perf-zip-url "https://github.com/org-roam/test-org-files/archive/master.zip"
|
|
||||||
"Path to zip for test org-roam files.")
|
|
||||||
|
|
||||||
(defun test-org-roam-perf--abs-path (file-path)
|
|
||||||
"Get absolute FILE-PATH from `org-roam-directory'."
|
|
||||||
(expand-file-name file-path org-roam-directory))
|
|
||||||
|
|
||||||
(defun test-org-roam-perf--init ()
|
|
||||||
"."
|
|
||||||
(let* ((temp-loc (expand-file-name (make-temp-name "test-org-files-") temporary-file-directory))
|
|
||||||
(zip-file-loc (concat temp-loc ".zip"))
|
|
||||||
(_ (url-copy-file test-org-roam-perf-zip-url zip-file-loc))
|
|
||||||
(_ (shell-command (format "mkdir -p %s && unzip -j -qq %s -d %s" temp-loc zip-file-loc temp-loc))))
|
|
||||||
(setq org-roam-directory temp-loc)))
|
|
||||||
|
|
||||||
(describe "Cache Build"
|
|
||||||
(it "cache build from scratch time to be acceptable"
|
|
||||||
(test-org-roam-perf--init)
|
|
||||||
(pcase (benchmark-run 1 (org-roam-db-build-cache t))
|
|
||||||
(`(,time ,gcs ,time-in-gc)
|
|
||||||
(message "Elapsed time: %fs (%fs in %d GCs)" time time-in-gc gcs)
|
|
||||||
(expect time :to-be-less-than 110))))
|
|
||||||
(it "builds quickly without change"
|
|
||||||
(pcase (benchmark-run 1 (org-roam-db-build-cache))
|
|
||||||
(`(,time ,gcs ,time-in-gc)
|
|
||||||
(message "Elapsed time: %fs (%fs in %d GCs)" time time-in-gc gcs)
|
|
||||||
(expect time :to-be-less-than 5)))))
|
|
@ -23,341 +23,31 @@
|
|||||||
|
|
||||||
(require 'buttercup)
|
(require 'buttercup)
|
||||||
(require 'org-roam)
|
(require 'org-roam)
|
||||||
(require 'dash)
|
|
||||||
|
|
||||||
(defun test-org-roam--abs-path (file-path)
|
(describe "org-roam-db-sync"
|
||||||
"Get absolute FILE-PATH from `org-roam-directory'."
|
|
||||||
(expand-file-name file-path org-roam-directory))
|
|
||||||
|
|
||||||
(defun test-org-roam--find-file (path)
|
|
||||||
"PATH."
|
|
||||||
(let ((path (test-org-roam--abs-path path)))
|
|
||||||
(make-directory (file-name-directory path) t)
|
|
||||||
(find-file path)))
|
|
||||||
|
|
||||||
(defvar test-org-roam-directory (expand-file-name "tests/roam-files")
|
|
||||||
"Directory containing org-roam test org files.")
|
|
||||||
|
|
||||||
(defun test-org-roam--init ()
|
|
||||||
"."
|
|
||||||
(let ((original-dir test-org-roam-directory)
|
|
||||||
(new-dir (expand-file-name (make-temp-name "org-roam") temporary-file-directory)))
|
|
||||||
(copy-directory original-dir new-dir)
|
|
||||||
(setq org-roam-directory new-dir)
|
|
||||||
(org-roam-mode +1)
|
|
||||||
(sleep-for 2)))
|
|
||||||
|
|
||||||
(defun test-org-roam--teardown ()
|
|
||||||
(org-roam-mode -1)
|
|
||||||
(delete-file org-roam-db-location)
|
|
||||||
(org-roam-db--close))
|
|
||||||
|
|
||||||
(describe "Ref extraction"
|
|
||||||
(before-all
|
(before-all
|
||||||
(test-org-roam--init))
|
(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-setup))
|
||||||
|
|
||||||
(after-all
|
(after-all
|
||||||
(test-org-roam--teardown))
|
(org-roam-teardown)
|
||||||
|
(delete-file org-roam-db-location))
|
||||||
|
|
||||||
(cl-flet
|
(it "has the correct number of files"
|
||||||
((test (fn file)
|
(expect (caar (org-roam-db-query [:select (funcall count) :from files]))
|
||||||
(let* ((fname (test-org-roam--abs-path file))
|
|
||||||
(buf (find-file-noselect fname)))
|
|
||||||
(with-current-buffer buf
|
|
||||||
;; Unlike tag extraction, it doesn't make sense to
|
|
||||||
;; pass a filename.
|
|
||||||
(funcall fn)))))
|
|
||||||
;; Enable "cite:" link parsing
|
|
||||||
(org-link-set-parameters "cite")
|
|
||||||
(it "extracts web keys"
|
|
||||||
(expect (test #'org-roam--extract-ref
|
|
||||||
"web_ref.org")
|
|
||||||
:to-equal
|
:to-equal
|
||||||
'("website" . "//google.com/")))
|
2))
|
||||||
(it "extracts cite keys"
|
|
||||||
(expect (test #'org-roam--extract-ref
|
|
||||||
"cite_ref.org")
|
|
||||||
:to-equal
|
|
||||||
'("cite" . "mitsuha2007")))
|
|
||||||
(it "extracts all keys"
|
|
||||||
(expect (test #'org-roam--extract-refs
|
|
||||||
"multiple-refs.org")
|
|
||||||
:to-have-same-items-as
|
|
||||||
'(("cite" . "orgroam2020")
|
|
||||||
("website" . "//www.orgroam.com/"))))))
|
|
||||||
|
|
||||||
(describe "Title extraction"
|
(it "has the correct number of nodes"
|
||||||
:var (org-roam-title-sources)
|
(expect (caar (org-roam-db-query [:select (funcall count) :from nodes]))
|
||||||
(before-all
|
:to-equal
|
||||||
(test-org-roam--init))
|
2))
|
||||||
|
|
||||||
(after-all
|
(it "has the correct number of links"
|
||||||
(test-org-roam--teardown))
|
(expect (caar (org-roam-db-query [:select (funcall count) :from links]))
|
||||||
|
|
||||||
(cl-flet
|
|
||||||
((test (fn file)
|
|
||||||
(let ((buf (find-file-noselect
|
|
||||||
(test-org-roam--abs-path file))))
|
|
||||||
(with-current-buffer buf
|
|
||||||
(funcall fn)))))
|
|
||||||
(it "extracts title from title property"
|
|
||||||
(expect (test #'org-roam--extract-titles-title
|
|
||||||
"titles/title.org")
|
|
||||||
:to-equal
|
:to-equal
|
||||||
'("Title"))
|
1)))
|
||||||
(expect (test #'org-roam--extract-titles-title
|
|
||||||
"titles/aliases.org")
|
|
||||||
:to-equal
|
|
||||||
nil)
|
|
||||||
(expect (test #'org-roam--extract-titles-title
|
|
||||||
"titles/headline.org")
|
|
||||||
:to-equal
|
|
||||||
nil)
|
|
||||||
(expect (test #'org-roam--extract-titles-title
|
|
||||||
"titles/combination.org")
|
|
||||||
:to-equal
|
|
||||||
'("TITLE PROP")))
|
|
||||||
|
|
||||||
(it "extracts alias"
|
|
||||||
(expect (test #'org-roam--extract-titles-alias
|
|
||||||
"titles/title.org")
|
|
||||||
:to-equal
|
|
||||||
nil)
|
|
||||||
(expect (test #'org-roam--extract-titles-alias
|
|
||||||
"titles/aliases.org")
|
|
||||||
:to-equal
|
|
||||||
'("roam" "alias"))
|
|
||||||
(expect (test #'org-roam--extract-titles-alias
|
|
||||||
"titles/headline.org")
|
|
||||||
:to-equal
|
|
||||||
nil)
|
|
||||||
(expect (test #'org-roam--extract-titles-alias
|
|
||||||
"titles/combination.org")
|
|
||||||
:to-equal
|
|
||||||
'("roam" "alias")))
|
|
||||||
|
|
||||||
(it "extracts headlines"
|
|
||||||
(expect (test #'org-roam--extract-titles-alias
|
|
||||||
"titles/title.org")
|
|
||||||
:to-equal
|
|
||||||
nil)
|
|
||||||
(expect (test #'org-roam--extract-titles-headline
|
|
||||||
"titles/aliases.org")
|
|
||||||
:to-equal
|
|
||||||
nil)
|
|
||||||
(expect (test #'org-roam--extract-titles-headline
|
|
||||||
"titles/headline.org")
|
|
||||||
:to-equal
|
|
||||||
'("Headline"))
|
|
||||||
(expect (test #'org-roam--extract-titles-headline
|
|
||||||
"titles/combination.org")
|
|
||||||
:to-equal
|
|
||||||
'("Headline")))
|
|
||||||
|
|
||||||
(describe "uses org-roam-title-sources correctly"
|
|
||||||
(it "'((title headline) alias)"
|
|
||||||
(expect (let ((org-roam-title-sources '((title headline) alias)))
|
|
||||||
(test #'org-roam--extract-titles
|
|
||||||
"titles/combination.org"))
|
|
||||||
:to-equal
|
|
||||||
'("TITLE PROP" "roam" "alias")))
|
|
||||||
(it "'((headline title) alias)"
|
|
||||||
(expect (let ((org-roam-title-sources '((headline title) alias)))
|
|
||||||
(test #'org-roam--extract-titles
|
|
||||||
"titles/combination.org"))
|
|
||||||
:to-equal
|
|
||||||
'("Headline" "roam" "alias")))
|
|
||||||
(it "'(headline alias title)"
|
|
||||||
(expect (let ((org-roam-title-sources '(headline alias title)))
|
|
||||||
(test #'org-roam--extract-titles
|
|
||||||
"titles/combination.org"))
|
|
||||||
:to-equal
|
|
||||||
'("Headline" "roam" "alias" "TITLE PROP"))))))
|
|
||||||
|
|
||||||
(describe "Tag extraction"
|
|
||||||
:var (org-roam-tag-sources)
|
|
||||||
(before-all
|
|
||||||
(test-org-roam--init))
|
|
||||||
|
|
||||||
(after-all
|
|
||||||
(test-org-roam--teardown))
|
|
||||||
|
|
||||||
(cl-flet
|
|
||||||
((test (fn file)
|
|
||||||
(let* ((fname (test-org-roam--abs-path file))
|
|
||||||
(buf (find-file-noselect fname)))
|
|
||||||
(with-current-buffer buf
|
|
||||||
(funcall fn fname)))))
|
|
||||||
(it "extracts from prop"
|
|
||||||
(expect (test #'org-roam--extract-tags-prop
|
|
||||||
"tags/tag.org")
|
|
||||||
:to-equal
|
|
||||||
'("t1" "t2 with space" "t3"))
|
|
||||||
(expect (test #'org-roam--extract-tags-prop
|
|
||||||
"tags/no_tag.org")
|
|
||||||
:to-equal
|
|
||||||
nil))
|
|
||||||
|
|
||||||
(it "extracts from all directories"
|
|
||||||
(expect (test #'org-roam--extract-tags-all-directories
|
|
||||||
"base.org")
|
|
||||||
:to-equal
|
|
||||||
nil)
|
|
||||||
(expect (test #'org-roam--extract-tags-all-directories
|
|
||||||
"tags/tag.org")
|
|
||||||
:to-equal
|
|
||||||
'("tags"))
|
|
||||||
(expect (test #'org-roam--extract-tags-all-directories
|
|
||||||
"nested/deeply/deeply_nested_file.org")
|
|
||||||
:to-equal
|
|
||||||
'("nested" "deeply")))
|
|
||||||
|
|
||||||
(it "extracts from last directory"
|
|
||||||
(expect (test #'org-roam--extract-tags-last-directory
|
|
||||||
"base.org")
|
|
||||||
:to-equal
|
|
||||||
nil)
|
|
||||||
(expect (test #'org-roam--extract-tags-last-directory
|
|
||||||
"tags/tag.org")
|
|
||||||
:to-equal
|
|
||||||
'("tags"))
|
|
||||||
(expect (test #'org-roam--extract-tags-last-directory
|
|
||||||
"nested/deeply/deeply_nested_file.org")
|
|
||||||
:to-equal
|
|
||||||
'("deeply")))
|
|
||||||
|
|
||||||
(it "extracts from first directory"
|
|
||||||
(expect (test #'org-roam--extract-tags-first-directory
|
|
||||||
"base.org")
|
|
||||||
:to-equal
|
|
||||||
nil)
|
|
||||||
(expect (test #'org-roam--extract-tags-first-directory
|
|
||||||
"tags/tag.org")
|
|
||||||
:to-equal
|
|
||||||
'("tags"))
|
|
||||||
(expect (test #'org-roam--extract-tags-first-directory
|
|
||||||
"nested/deeply/deeply_nested_file.org")
|
|
||||||
:to-equal
|
|
||||||
'("nested")))
|
|
||||||
|
|
||||||
(describe "uses org-roam-tag-sources correctly"
|
|
||||||
(it "'(prop)"
|
|
||||||
(expect (let ((org-roam-tag-sources '(prop)))
|
|
||||||
(test #'org-roam--extract-tags
|
|
||||||
"tags/tag.org"))
|
|
||||||
:to-equal
|
|
||||||
'("t1" "t2 with space" "t3")))
|
|
||||||
(it "'(prop all-directories)"
|
|
||||||
(expect (let ((org-roam-tag-sources '(prop all-directories)))
|
|
||||||
(test #'org-roam--extract-tags
|
|
||||||
"tags/tag.org"))
|
|
||||||
:to-equal
|
|
||||||
'("t1" "t2 with space" "t3" "tags"))))))
|
|
||||||
|
|
||||||
(describe "ID extraction"
|
|
||||||
(before-all
|
|
||||||
(test-org-roam--init))
|
|
||||||
|
|
||||||
(after-all
|
|
||||||
(test-org-roam--teardown))
|
|
||||||
|
|
||||||
(cl-flet
|
|
||||||
((test (fn file)
|
|
||||||
(let* ((fname (test-org-roam--abs-path file))
|
|
||||||
(buf (find-file-noselect fname)))
|
|
||||||
(with-current-buffer buf
|
|
||||||
(funcall fn fname)))))
|
|
||||||
(it "extracts ids"
|
|
||||||
(expect (test #'org-roam--extract-ids
|
|
||||||
"headlines/headline.org")
|
|
||||||
:to-have-same-items-as
|
|
||||||
`(["e84d0630-efad-4017-9059-5ef917908823" ,(test-org-roam--abs-path "headlines/headline.org") 1]
|
|
||||||
["801b58eb-97e2-435f-a33e-ff59a2f0c213" ,(test-org-roam--abs-path "headlines/headline.org") 1])))))
|
|
||||||
|
|
||||||
(describe "Test roam links"
|
|
||||||
(it ""
|
|
||||||
(expect (org-roam-link--split-path "")
|
|
||||||
:to-equal
|
|
||||||
'(title "" "" nil)))
|
|
||||||
(it "title"
|
|
||||||
(expect (org-roam-link--split-path "title")
|
|
||||||
:to-equal
|
|
||||||
'(title "title" "" nil)))
|
|
||||||
(it "title*"
|
|
||||||
(expect (org-roam-link--split-path "title*")
|
|
||||||
:to-equal
|
|
||||||
'(title+headline "title" "" 5)))
|
|
||||||
(it "title*headline"
|
|
||||||
(expect (org-roam-link--split-path "title*headline")
|
|
||||||
:to-equal
|
|
||||||
'(title+headline "title" "headline" 5)))
|
|
||||||
(it "*headline"
|
|
||||||
(expect (org-roam-link--split-path "*headline")
|
|
||||||
:to-equal
|
|
||||||
'(headline "" "headline" 0))))
|
|
||||||
|
|
||||||
;;; Tests
|
|
||||||
(xdescribe "org-roam-db-build-cache"
|
|
||||||
(before-each
|
|
||||||
(test-org-roam--init))
|
|
||||||
|
|
||||||
(after-each
|
|
||||||
(test-org-roam--teardown))
|
|
||||||
|
|
||||||
(it "initializes correctly"
|
|
||||||
;; Cache
|
|
||||||
(expect (caar (org-roam-db-query [:select (funcall count) :from files])) :to-be 8)
|
|
||||||
(expect (caar (org-roam-db-query [:select (funcall count) :from links])) :to-be 5)
|
|
||||||
(expect (caar (org-roam-db-query [:select (funcall count) :from titles])) :to-be 8)
|
|
||||||
(expect (caar (org-roam-db-query [:select (funcall count) :from titles
|
|
||||||
:where titles :is-null])) :to-be 1)
|
|
||||||
(expect (caar (org-roam-db-query [:select (funcall count) :from refs])) :to-be 1)
|
|
||||||
|
|
||||||
;; Links
|
|
||||||
(expect (caar (org-roam-db-query [:select (funcall count) :from links
|
|
||||||
:where (= source $s1)]
|
|
||||||
(test-org-roam--abs-path "foo.org"))) :to-be 1)
|
|
||||||
(expect (caar (org-roam-db-query [:select (funcall count) :from links
|
|
||||||
:where (= source $s1)]
|
|
||||||
(test-org-roam--abs-path "nested/bar.org"))) :to-be 2)
|
|
||||||
|
|
||||||
;; Links -- File-to
|
|
||||||
(expect (caar (org-roam-db-query [:select (funcall count) :from links
|
|
||||||
:where (= dest $s1)]
|
|
||||||
(test-org-roam--abs-path "nested/foo.org"))) :to-be 1)
|
|
||||||
(expect (caar (org-roam-db-query [:select (funcall count) :from links
|
|
||||||
:where (= dest $s1)]
|
|
||||||
(test-org-roam--abs-path "nested/bar.org"))) :to-be 1)
|
|
||||||
(expect (caar (org-roam-db-query [:select (funcall count) :from links
|
|
||||||
:where (= dest $s1)]
|
|
||||||
(test-org-roam--abs-path "unlinked.org"))) :to-be 0)
|
|
||||||
;; TODO Test titles
|
|
||||||
(expect (org-roam-db-query [:select * :from titles])
|
|
||||||
:to-have-same-items-as
|
|
||||||
(list (list (test-org-roam--abs-path "alias.org")
|
|
||||||
(list "t1" "a1" "a 2"))
|
|
||||||
(list (test-org-roam--abs-path "bar.org")
|
|
||||||
(list "Bar"))
|
|
||||||
(list (test-org-roam--abs-path "foo.org")
|
|
||||||
(list "Foo"))
|
|
||||||
(list (test-org-roam--abs-path "nested/bar.org")
|
|
||||||
(list "Nested Bar"))
|
|
||||||
(list (test-org-roam--abs-path "nested/foo.org")
|
|
||||||
(list "Nested Foo"))
|
|
||||||
(list (test-org-roam--abs-path "no-title.org")
|
|
||||||
(list "Headline title"))
|
|
||||||
(list (test-org-roam--abs-path "web_ref.org") nil)
|
|
||||||
(list (test-org-roam--abs-path "unlinked.org")
|
|
||||||
(list "Unlinked"))))
|
|
||||||
|
|
||||||
(expect (org-roam-db-query [:select * :from refs])
|
|
||||||
:to-have-same-items-as
|
|
||||||
(list (list "https://google.com/" (test-org-roam--abs-path "web_ref.org") "website")))
|
|
||||||
|
|
||||||
;; Expect rebuilds to be really quick (nothing changed)
|
|
||||||
(expect (org-roam-db-build-cache)
|
|
||||||
:to-equal
|
|
||||||
(list :files 0 :links 0 :tags 0 :titles 0 :refs 0 :deleted 0))))
|
|
||||||
|
|
||||||
(provide 'test-org-roam)
|
(provide 'test-org-roam)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user