mirror of
https://github.com/org-roam/org-roam
synced 2025-08-03 12:27:23 -05:00
Compare commits
92 Commits
Author | SHA1 | Date | |
---|---|---|---|
6170cc9928 | |||
cc95540135 | |||
24a683d58c | |||
86c9085363 | |||
b67bccd6c2 | |||
8b43093d1a | |||
679ef6ef00 | |||
ee9a8d423e | |||
d3c7d74329 | |||
3bf0a0a35d | |||
d0f17c6477 | |||
c90b2d68df | |||
91fd1083fe | |||
7ad5572741 | |||
b6d59e2238 | |||
2c5f429b24 | |||
7068d63e96 | |||
898295f4a0 | |||
abe63b4360 | |||
e992fc27e2 | |||
c3889b3b17 | |||
8c3c216191 | |||
023af3ec32 | |||
67f10864df | |||
c9efbe1dda | |||
ae533fa309 | |||
e4188179f6 | |||
91aae05810 | |||
a2e46db808 | |||
d93423d4e1 | |||
1af1639ee0 | |||
db573bbc4e | |||
50cc4d6990 | |||
b96efbb444 | |||
721f167563 | |||
25bc3acce3 | |||
d8c7f7d4e7 | |||
01c45f9dca | |||
239929045f | |||
d26047e6f7 | |||
36928d655a | |||
4a2b44252e | |||
3177b900e7 | |||
4d71fbdfe1 | |||
84f58cbc12 | |||
3e47f198c7 | |||
c789531e36 | |||
1b221a1d4a | |||
d0fd2c6959 | |||
2c75b194d8 | |||
c8f8c3e876 | |||
852042436e | |||
dafcf0dcf8 | |||
2b6f8ce615 | |||
5651f4598c | |||
e9299297f9 | |||
617e0021f5 | |||
f80515ab5f | |||
4af5ff662e | |||
73dfeb60cf | |||
5b933bd8ec | |||
3a54182bb1 | |||
34243a0a90 | |||
54d17cc50f | |||
54b63db350 | |||
8f2c51ad21 | |||
3b93c83b23 | |||
a101c548c1 | |||
1b8ece0219 | |||
0ecbd3a104 | |||
66e10df943 | |||
d2fc4be73b | |||
17d8e84ea5 | |||
777f969b50 | |||
bc833a9ff4 | |||
ec8d250f6c | |||
1795039ab9 | |||
a8a36a420b | |||
340215a16a | |||
941bd1f6b4 | |||
48a5d01726 | |||
0e6b93a253 | |||
cfabe0ec38 | |||
ab87a08e17 | |||
714ea7a3fc | |||
4c4b024b49 | |||
5dde894a0c | |||
74a6fd598a | |||
e9ae19c01c | |||
d25d477b4f | |||
04b7780ff9 | |||
858f531d96 |
@ -5,9 +5,9 @@
|
||||
(elisp-lint-indent-specs . ((describe . 1)
|
||||
(it . 1)
|
||||
(org-element-map . defun)
|
||||
(org-roam-dolist-with-progress . 2)
|
||||
(org-roam-with-temp-buffer . 1)
|
||||
(org-with-point-at . 1)
|
||||
(magit-insert-section . defun)
|
||||
(magit-section-case . 0)
|
||||
(->> . 1)
|
||||
(org-roam-with-file . 2)))))
|
||||
|
32
CHANGELOG.md
32
CHANGELOG.md
@ -1,7 +1,39 @@
|
||||
# Changelog
|
||||
|
||||
## 2.2.0
|
||||
### Added
|
||||
- [#1806](https://github.com/org-roam/org-roam/pull/1806), [#2017](https://github.com/org-roam/org-roam/pull/2017) db: support caching and usage of Org 9.5's in-built citations
|
||||
- [#1963](https://github.com/org-roam/org-roam/pull/1963) db: cache file title into files table
|
||||
- [#1977](https://github.com/org-roam/org-roam/pull/1977) db: support Org-ref v3 citations
|
||||
- [#1907](https://github.com/org-roam/org-roam/pull/1907), [#2009](https://github.com/org-roam/org-roam/pull/2009), [#2018](https://github.com/org-roam/org-roam/pull/2018) db: support sqlite3
|
||||
- [#2028](https://github.com/org-roam/org-roam/pull/2028) dailies: add `keys` argument to `org-roam-dailies-capture-today` and `org-roam-dailies--capture` functions to give the abilty to get into a capture buffer bypassing the selection screen
|
||||
|
||||
### Removed
|
||||
### Changed
|
||||
- [#1795](https://github.com/org-roam/org-roam/pull/1795) buffer: optimized reflinks fetch
|
||||
- [#1809](https://github.com/org-roam/org-roam/pull/1809) capture: the mandatory `:if-new` property of each capture template is now renamed to `:target`
|
||||
- [#1829](https://github.com/org-roam/org-roam/pull/1829) perf: file sql updates are now wrapped in a transaction
|
||||
- [#1877](https://github.com/org-roam/org-roam/pull/1877) dailies: stop asking for time, only date
|
||||
- [#1949](https://github.com/org-roam/org-roam/pull/1949) db: check for property drawers are now case-insensitive
|
||||
|
||||
### Fixed
|
||||
- [#1798](https://github.com/org-roam/org-roam/pull/1798) org-roam-node-at-point: do not skip invisible headings
|
||||
- [#1807](https://github.com/org-roam/org-roam/pull/1807) capture: always trigger `:if-new` template for existing nodes
|
||||
- [#1813](https://github.com/org-roam/org-roam/pull/1813) db: prevent empty ROAM_ALIASES from crashing db updates
|
||||
- [#1816](https://github.com/org-roam/org-roam/pull/1816) db: prevent invalid ROAM_REFS from crashing db updates
|
||||
- [#1893](https://github.com/org-roam/org-roam/pull/1893), [#1896](https://github.com/org-roam/org-roam/pull/1896), [#1901](https://github.com/org-roam/org-roam/pull/1901), [#1895](https://github.com/org-roam/org-roam/pull/1895), [#1904](https://github.com/org-roam/org-roam/pull/1904) completions: various mini-buffer related completion fixes
|
||||
- [#1931](https://github.com/org-roam/org-roam/pull/1931) utils: org-roam-set-keyword now skips over all drawers
|
||||
- [#1947](https://github.com/org-roam/org-roam/pull/1947) db: links in ROAM_REFS are no longer considered as links
|
||||
- [#1948](https://github.com/org-roam/org-roam/pull/1948) completions: fix same-line completions
|
||||
- [#1953](https://github.com/org-roam/org-roam/pull/1953) db: refresh CATEGORY before writing to db
|
||||
- [#1946](https://github.com/org-roam/org-roam/pull/1946), [#1946](https://github.com/org-roam/org-roam/pull/1946), [#1958](https://github.com/org-roam/org-roam/pull/1958) various performance improvements
|
||||
- [#1980](https://github.com/org-roam/org-roam/pull/1980) utils: fix org-roam-with-file changing the minor-mode
|
||||
- [#2016](https://github.com/org-roam/org-roam/pull/2016) db: fix node caching being affected by agenda variables
|
||||
- [#2033](https://github.com/org-roam/org-roam/pull/2023) db: respect local variables during db parsing
|
||||
|
||||
## 2.1.0
|
||||
### Added
|
||||
- [#1693](https://github.com/org-roam/org-roam/pull/1693) added `filter-fn` and `templates` parameter to `org-roam-capture`, `org-roam-node-find`, and `org-roam-node-insert`
|
||||
- [#1709](https://github.com/org-roam/org-roam/pull/1709) added ability to specify default value in org-roam capture templates
|
||||
- [#1710](https://github.com/org-roam/org-roam/pull/1710) added `org-roam-extract-subtree`
|
||||
- [#1720](https://github.com/org-roam/org-roam/pull/1720) added `org-roam-db-update-on-save`
|
||||
|
@ -39,7 +39,7 @@ detailed information, please read the [manual][docs].
|
||||
|
||||
### Using `package.el`
|
||||
<details>
|
||||
<summary>Toggle instuctions</summary>
|
||||
<summary>Toggle instructions</summary>
|
||||
|
||||
You can install `org-roam` from [MELPA](https://melpa.org/) or [MELPA
|
||||
Stable](https://stable.melpa.org/) using `package.el`:
|
||||
@ -75,7 +75,7 @@ to the directory.
|
||||
|
||||
### Using `straight.el`
|
||||
<details>
|
||||
<summary>Toggle instuctions</summary>
|
||||
<summary>Toggle instructions</summary>
|
||||
|
||||
Installation from MELPA or MELPA Stable using `straight.el`:
|
||||
|
||||
@ -115,7 +115,7 @@ next sample will get you there:
|
||||
|
||||
### Using Doom Emacs
|
||||
<details>
|
||||
<summary>Toggle instuctions</summary>
|
||||
<summary>Toggle instructions</summary>
|
||||
|
||||
Doom's `:lang org` module comes with support for `org-roam`, but it's not
|
||||
enabled by default. To activate it pass `+roam2` flag to `org` module in your
|
||||
@ -249,6 +249,6 @@ General Public License, Version 3.
|
||||
[release]: https://github.com/org-roam/org-roam/releases
|
||||
[docs]: https://www.orgroam.com/manual.html
|
||||
[discourse]: https://org-roam.discourse.group/
|
||||
[slack]: https://join.slack.com/t/orgroam/shared_invite/zt-deoqamys-043YQ~s5Tay3iJ5QRI~Lxg
|
||||
[slack]: https://join.slack.com/t/orgroam/shared_invite/zt-wuoize1z-x3UyQnQ0WHF0RhuEQ2NLnQ
|
||||
[issues]: https://github.com/org-roam/org-roam/issues
|
||||
[faq]: https://www.orgroam.com/manual.html#FAQ
|
||||
|
@ -95,7 +95,7 @@
|
||||
<ul>
|
||||
<li>Read our documentation within Emacs, or on the <a href="https://www.orgroam.com/manual.html">Online Manual</a></li>
|
||||
<li>Participate in our forum discussions on <a href="https://org-roam.discourse.group">Discourse</a></li>
|
||||
<li>Chat with us on <a href="https://join.slack.com/t/orgroam/shared_invite/zt-deoqamys-043YQ~s5Tay3iJ5QRI~Lxg">Slack</a></li>
|
||||
<li>Chat with us on <a href="https://join.slack.com/t/orgroam/shared_invite/zt-wuoize1z-x3UyQnQ0WHF0RhuEQ2NLnQ">Slack</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
@ -151,7 +151,7 @@
|
||||
<div class="row">
|
||||
<a
|
||||
class="content footer-links"
|
||||
href="https://join.slack.com/t/orgroam/shared_invite/zt-deoqamys-043YQ~s5Tay3iJ5QRI~Lxg"
|
||||
href="https://join.slack.com/t/orgroam/shared_invite/zt-wuoize1z-x3UyQnQ0WHF0RhuEQ2NLnQ"
|
||||
>Slack</a
|
||||
>
|
||||
</div>
|
||||
|
298
doc/org-roam.org
298
doc/org-roam.org
@ -1,20 +1,20 @@
|
||||
#+title: Org-roam User Manual
|
||||
#+author: Jethro Kuan
|
||||
#+email: jethrokuan95@gmail.com
|
||||
#+date: 2020-2021
|
||||
#+date: 2020-2022
|
||||
#+language: en
|
||||
|
||||
#+texinfo_deffn: t
|
||||
#+texinfo_dir_category: Emacs
|
||||
#+texinfo_dir_title: Org-roam: (org-roam).
|
||||
#+texinfo_dir_desc: Roam Research for Emacs.
|
||||
#+subtitle: for version 2.1.0
|
||||
#+subtitle: for version 2.2.0
|
||||
|
||||
#+options: H:4 num:3 toc:nil creator:t ':t
|
||||
#+property: header-args :eval never
|
||||
#+texinfo: @noindent
|
||||
|
||||
This manual is for Org-roam version 2.1.0.
|
||||
This manual is for Org-roam version 2.2.0.
|
||||
|
||||
#+BEGIN_QUOTE
|
||||
Copyright (C) 2020-2021 Jethro Kuan <jethrokuan95@gmail.com>
|
||||
@ -288,29 +288,25 @@ in your Emacs environment as a prerequisite for Org-roam when you install it.
|
||||
~emacsql-sqlite~ requires a C compiler (e.g. ~gcc~ or ~clang~) to be present in
|
||||
your computer. How to install a C compiler depends on the OS that you use.
|
||||
|
||||
- For Windows:
|
||||
**** C Compiler for Windows
|
||||
|
||||
There are various ways to install one, depending on how you have installed
|
||||
Emacs. If you use Emacs within a Cygwin or MinGW environment, then you should
|
||||
install a compiler using their respective package manager.
|
||||
One of the easiest ways to install a C compiler in Windows is to use [[https://www.msys2.org/][MSYS2]] as at the time of this writing:
|
||||
|
||||
If you have installed your Emacs from the [[https://www.gnu.org/software/emacs/][GNU Emacs website]], then the easiest way
|
||||
is to use [[https://www.msys2.org/][MSYS2]] as at the time of this writing:
|
||||
|
||||
1. Use the installer in the official website and install MSYS2
|
||||
2. Run MSYS2
|
||||
3. In the command-line tool, type the following and answer "Y" to proceed:
|
||||
1. Download and use the installer in the official MSYS2 website
|
||||
2. Run MSYS2 and in its terminal, type the following and answer "Y" to
|
||||
proceed -- this will install ~gcc~ in your PC:
|
||||
|
||||
#+BEGIN_SRC bash
|
||||
pacman -S gcc
|
||||
#+END_SRC
|
||||
|
||||
Note that you do not need to manually set the PATH for MSYS2; the
|
||||
installer automatically takes care of it for you.
|
||||
4. On Windows, add ~C:\msys64\usr\bin~ (command =where gcc= in MSYS2 terminal
|
||||
can tell you the correct path) to ~PATH~ in your environmental variables
|
||||
|
||||
4. Open Emacs and call ~M-x org-roam-db-autosync-mode~
|
||||
5. Launch Emacs and call ~M-x org-roam-db-autosync-mode~ (launch Emacs after
|
||||
defining the path, so that Emacs can recognize it)
|
||||
|
||||
This will automatically start compiling ~emacsql-sqlite~; you should see a
|
||||
This will automatically start compiling ~emacsql-sqlite~; you should see a
|
||||
message in minibuffer. It may take a while until compilation completes. Once
|
||||
complete, you should see a new file ~emacsql-sqlite.exe~ created in a subfolder
|
||||
named ~sqlite~ under ~emacsql-sqlite~ installation folder. It's typically in
|
||||
@ -418,6 +414,51 @@ One can also conveniently insert links via the completion-at-point functions
|
||||
Org-roam provides (see [[*Completion][Completion]]).
|
||||
|
||||
* Customizing Node Caching
|
||||
** How to cache
|
||||
|
||||
Org-roam uses a sqlite database to perform caching, but there are multiple Emacs
|
||||
libraries that can be used. The default used by Org-roam is ~emacs-sqlite~.
|
||||
Below the pros and cons of each package is used:
|
||||
|
||||
[[https://github.com/skeeto/emacsql][**emacs-sqlite**]]
|
||||
|
||||
The default option used by Org-roam. This library is the most mature and
|
||||
well-supported and is imported by default in Org-roam.
|
||||
|
||||
One downside of using ~emacs-sqlite~ is that using it requires compilation and
|
||||
can cause issues in some environments (especially Windows). If you have issues
|
||||
producing the customized binary required by ~emacs-sqlite~, consider using
|
||||
~emacs-sqlite3~.
|
||||
|
||||
[[https://github.com/cireu/emacsql-sqlite3][**emacs-sqlite3**]]
|
||||
|
||||
~emacs-sqlite3~ uses the official sqlite3 binary that can be obtained from your
|
||||
system's package manager. This is useful if you have issues producing the
|
||||
~sqlite3~ binary required by the other packages. However, it is not recommended
|
||||
because it has some compatibility issues with Emacs, but should work for most
|
||||
regular cases. See [[https://nullprogram.com/blog/2014/02/06/][Chris Wellon's blog post]] for more information.
|
||||
|
||||
To use ~emacsql-sqlite3~, ensure that the package is installed, and set:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq org-roam-database-connector 'sqlite3)
|
||||
#+end_src
|
||||
|
||||
[[https://github.com/emacscollective/emacsql-libsqlite3/][**emacsql-libsqlite3**]]
|
||||
|
||||
~emacs-libsqlite3~ is a relatively young package which uses an Emacs module that
|
||||
exposes parts of the SQLite C API to Emacs Lisp, instead of using subprocess as
|
||||
~emacsql-sqlite~ does. It is expected to be a more performant drop-in
|
||||
replacement for ~emacs-sqlite~.
|
||||
|
||||
At the moment it is experimental and does not work well with the SQL query load
|
||||
required by Org-roam, but you may still try it by ensuring the package is
|
||||
installed and setting:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq org-roam-database-connector 'libsqlite3)
|
||||
#+end_src
|
||||
|
||||
** What to cache
|
||||
|
||||
By default, all nodes (any headline or file with an ID) are cached by Org-roam.
|
||||
@ -495,7 +536,7 @@ 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~
|
||||
- ~<TAB>~: ~magit-section-toggle~
|
||||
- ~<RET>~: ~org-roam-buffer-visit-thing~
|
||||
|
||||
~org-roam-buffer-visit-thing~ is a placeholder command, that is replaced by
|
||||
@ -646,6 +687,59 @@ Org-roam also provides some functions to add or remove refs.
|
||||
|
||||
Remove a ref from the node at point.
|
||||
|
||||
* Citations
|
||||
|
||||
Since version 9.5, Org has first-class support for citations. Org-roam supports
|
||||
the caching of both these in-built citations (of form ~[cite:@key]~) and [[https://github.com/jkitchin/org-ref][org-ref]]
|
||||
citations (of form cite:key).
|
||||
|
||||
Org-roam attempts to load both the ~org-ref~ and ~org-cite~ package when
|
||||
indexing files, so no further setup from the user is required for citation
|
||||
support.
|
||||
|
||||
** Using the Cached Information
|
||||
|
||||
It is common to use take reference notes for academic papers. To designate the
|
||||
node to be the canonical node for the academic paper, we can use its unique
|
||||
citation key:
|
||||
|
||||
#+begin_src org
|
||||
,* Probabilistic Robotics
|
||||
:PROPERTIES:
|
||||
:ID: 51b7b82c-bbb4-4822-875a-ed548cffda10
|
||||
:ROAM_REFS: @thrun2005probabilistic
|
||||
:END:
|
||||
#+end_src
|
||||
|
||||
or
|
||||
|
||||
#+begin_src org
|
||||
,* Probabilistic Robotics
|
||||
:PROPERTIES:
|
||||
:ID: 51b7b82c-bbb4-4822-875a-ed548cffda10
|
||||
:ROAM_REFS: [cite:@thrun2005probabilistic]
|
||||
:END:
|
||||
#+end_src
|
||||
|
||||
for ~org-cite~, or:
|
||||
|
||||
#+begin_src org
|
||||
,* Probabilistic Robotics
|
||||
:PROPERTIES:
|
||||
:ID: 51b7b82c-bbb4-4822-875a-ed548cffda10
|
||||
:ROAM_REFS: cite:thrun2005probabilistic
|
||||
:END:
|
||||
#+end_src
|
||||
|
||||
for ~org-ref~.
|
||||
|
||||
When another node has a citation for that key, we can see it using the
|
||||
~Reflinks~ section of the Org-roam buffer.
|
||||
|
||||
Extension developers may be interested in retrieving the citations within their
|
||||
notes. This information can be found within the ~citation~ table of the Org-roam
|
||||
database.
|
||||
|
||||
* Completion
|
||||
|
||||
Completions for Org-roam are provided via ~completion-at-point~. Org-roam
|
||||
@ -698,7 +792,7 @@ extension in your Org-roam capture templates. For example:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq org-roam-capture-templates '(("d" "default" plain "%?"
|
||||
:if-new (file+head "${slug}.org.gpg"
|
||||
:target (file+head "${slug}.org.gpg"
|
||||
"#+title: ${title}\n")
|
||||
:unnarrowed t)))
|
||||
#+end_src
|
||||
@ -821,6 +915,29 @@ defaults write com.apple.LaunchServices/com.apple.launchservices.secure LSHandle
|
||||
|
||||
Then restart your computer.
|
||||
|
||||
**** Testing org-protocol
|
||||
|
||||
To test that you have the handler setup and registered properly from the command
|
||||
line you can run:
|
||||
|
||||
#+begin_src bash
|
||||
open org-protocol://roam-ref\?template=r\&ref=test\&title=this
|
||||
#+end_src
|
||||
|
||||
If you get an error similar too this or the wrong handler is run:
|
||||
|
||||
#+begin_quote
|
||||
No application knows how to open URL org-protocol://roam-ref?template=r&ref=test&title=this (Error Domain=NSOSStatusErrorDomain Code=-10814 "kLSApplicationNotFoundErr: E.g. no application claims the file" UserInfo={_LSLine=1489, _LSFunction=runEvaluator}).
|
||||
|
||||
#+end_quote
|
||||
|
||||
You may need to manually register your handler, like this:
|
||||
|
||||
#+begin_src bash
|
||||
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -R -f /Applications/OrgProtocolClient.app
|
||||
#+end_src
|
||||
|
||||
Here is a link to the lsregister command that is really useful: https://eclecticlight.co/2019/03/25/lsregister-a-valuable-undocumented-command-for-launchservices/
|
||||
*** Windows
|
||||
For Windows, create a temporary ~org-protocol.reg~ file:
|
||||
|
||||
@ -894,12 +1011,12 @@ will be no prompt for template selection.
|
||||
** Template Walkthrough
|
||||
|
||||
To demonstrate the additions made to org-capture templates. Here, we explain
|
||||
the default template, reproduced below. You will find some most of the elements
|
||||
the default template, reproduced below. You will find most of the elements
|
||||
of the template are similar to ~org-capture~ templates.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(("d" "default" plain "%?"
|
||||
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
|
||||
:target (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
|
||||
"#+title: ${title}\n")
|
||||
:unnarrowed t))
|
||||
#+END_SRC
|
||||
@ -913,8 +1030,12 @@ of the template are similar to ~org-capture~ templates.
|
||||
here.
|
||||
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.
|
||||
6. ~:if-new~ is a compulsory specification in the Org-roam capture template.
|
||||
This indicates the location for the new node.
|
||||
6. ~:target~ is a compulsory specification in the Org-roam capture template. The
|
||||
first element of the list indicates the type of the target, the second
|
||||
element indicates the location of the captured node, and the rest of the
|
||||
elements indicate prefilled template that will be inserted and the position
|
||||
of the point will be adjusted for. The latter behavior varies from type to
|
||||
type of the capture target.
|
||||
7. ~:unnarrowed t~ tells org-capture to show the contents for the whole file,
|
||||
rather than narrowing to just the entry. This is part of the Org-capture
|
||||
templates.
|
||||
@ -1054,7 +1175,7 @@ Here is a sane default configuration:
|
||||
(setq org-roam-dailies-capture-templates
|
||||
'(("d" "default" entry
|
||||
"* %?"
|
||||
:if-new (file+head "%<%Y-%m-%d>.org"
|
||||
:target (file+head "%<%Y-%m-%d>.org"
|
||||
"#+title: %<%Y-%m-%d>\n"))))
|
||||
#+end_src
|
||||
|
||||
@ -1240,6 +1361,9 @@ documents (PDF, EPUB etc.) within Org-mode.
|
||||
|
||||
** Bibliography
|
||||
|
||||
Org 9.5 added native citation and bibliography functionality, called "org-cite",
|
||||
which org-roam supports.
|
||||
|
||||
[[https://github.com/org-roam/org-roam-bibtex][org-roam-bibtex]] offers tight integration between [[https://github.com/jkitchin/org-ref][org-ref]], [[https://github.com/tmalsburg/helm-bibtex][helm-bibtex]] and
|
||||
~org-roam~. This helps you manage your bibliographic notes under ~org-roam~.
|
||||
|
||||
@ -1267,20 +1391,27 @@ variable using directory-local variables. This is what ~.dir-locals.el~ may
|
||||
contain:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
((nil . ((org-roam-directory . (expand-file-name "."))
|
||||
(org-roam-db-location . (expand-file-name "./org-roam.db")))))
|
||||
((nil . ((org-roam-directory . "/path/to/alt/org-roam-dir")
|
||||
(org-roam-db-location . "/path/to/alt/org-roam-dir/org-roam.db"))))
|
||||
#+END_SRC
|
||||
|
||||
Note ~org-roam-directory~ and ~org-roam-db-location~ should be an absolute path, not relative.
|
||||
|
||||
Alternatively, use ~eval~ if you wish to call functions:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
((nil . ((eval . (setq-local
|
||||
org-roam-directory (expand-file-name (locate-dominating-file
|
||||
default-directory ".dir-locals.el"))))
|
||||
(eval . (setq-local
|
||||
org-roam-db-location (expand-file-name "org-roam.db"
|
||||
org-roam-directory))))))
|
||||
#+END_SRC
|
||||
|
||||
All files within that directory will be treated as their own separate set of
|
||||
Org-roam files. Remember to run ~org-roam-db-sync~ from a file within
|
||||
that directory, at least once.
|
||||
|
||||
** How do I migrate from Roam Research?
|
||||
|
||||
Fabio has produced a command-line tool that converts markdown files exported
|
||||
from Roam Research into Org-roam compatible markdown. More instructions are
|
||||
provided [[https://github.com/fabioberger/roam-migration][in the repository]].
|
||||
|
||||
** How do I create a note whose title already matches one of the candidates?
|
||||
|
||||
This situation arises when, for example, one would like to create a note titled
|
||||
@ -1301,28 +1432,111 @@ you don't want them to be (e.g. when tangling an Org file), check the value you
|
||||
have set for ~org-id-link-to-org-use-id~: setting it to ~'create-if-interactive~
|
||||
is a popular option.
|
||||
|
||||
* Migrating from Org-roam v1
|
||||
** How do I migrate from Roam Research?
|
||||
|
||||
Fabio has produced a command-line tool that converts markdown files exported
|
||||
from Roam Research into Org-roam compatible markdown. More instructions are
|
||||
provided [[https://github.com/fabioberger/roam-migration][in the repository]].
|
||||
|
||||
** How to migrate from Org-roam v1?
|
||||
|
||||
Those coming from Org-roam v1 will do well treating v2 as entirely new software.
|
||||
V2 has a smaller core and fewer moving parts, while retaining the bulk of its
|
||||
functionality. It is recommended to read the documentation above about nodes.
|
||||
|
||||
It is still desirable to migrate notes collected in v1 to v2. To migrate your v1
|
||||
notes to v2, you may use the migration script provided in [[https://gist.github.com/jethrokuan/02f41028fb4a6f81787dc420fb99b6e4][this gist]], or [[https://gist.github.com/jethrokuan/02f41028fb4a6f81787dc420fb99b6e4#gistcomment-3737019][this
|
||||
gist]], the latter being better tested. [[https://d12frosted.io/posts/2021-06-11-path-to-org-roam-v2.html][This blog post]] provides a good overview of
|
||||
what's new in v2 and how to migrate.
|
||||
It is still desirable to migrate notes collected in v1 to v2.
|
||||
To migrate your v1 notes to v2, use =M-x org-roam-migrate-wizard=.
|
||||
[[https://d12frosted.io/posts/2021-06-11-path-to-org-roam-v2.html][This blog post]]
|
||||
provides a good overview of what's new in v2 and how to migrate.
|
||||
|
||||
Simply put, to migrate notes from v1 to v2, one must:
|
||||
Essentially, to migrate notes from v1 to v2, one must:
|
||||
|
||||
1. Add IDs to all existing notes. These are located in top-level property
|
||||
drawers (Although note that in v2, not all files need to have IDs)
|
||||
1. Add IDs to all existing notes.
|
||||
These are located in top-level property drawers
|
||||
(Although note that in v2, not all files need to have IDs).
|
||||
2. Update the Org-roam database to conform to the new schema.
|
||||
3. Replace ~#+ROAM_KEY~ into the ~ROAM_REFS~ property
|
||||
4. Replace ~#+ROAM_ALIAS~ into the ~ROAM_ALIASES~ property
|
||||
5. Move ~#+ROAM_TAGS~ into the ~#+FILETAGS~ property for file-level nodes, and
|
||||
the ~ROAM_TAGS~ property for headline nodes
|
||||
5. Move ~#+ROAM_TAGS~ into the ~#+FILETAGS~ property for file-level nodes,
|
||||
and the ~ROAM_TAGS~ property for headline nodes
|
||||
6. Replace existing file links with ID links.
|
||||
|
||||
** How do I publish my notes with an Internet-friendly graph?
|
||||
|
||||
The default graph builder creates a graph with an [[https://orgmode.org/worg/org-contrib/org-protocol.html][org-protocol]]
|
||||
handler which is convenient when you're working locally but
|
||||
inconvenient when you want to publish your notes for remote access.
|
||||
Likewise, it defaults to displaying the graph in Emacs which has the
|
||||
exact same caveats. This problem is solvable in the following way
|
||||
using org-mode's native [[https://orgmode.org/manual/Publishing.html][publishing]] capability:
|
||||
|
||||
1. configure org-mode to publish your org-roam notes as a project.
|
||||
2. create a function that overrides the default org-protocol link
|
||||
creation function(=org-roam-default-link-builder=).
|
||||
3. create a hook that's called at the end of graph creation to copy
|
||||
the generated graph to the appropriate place.
|
||||
|
||||
The example code below is used to publish to a local directory where a
|
||||
separate shell script copies the files to the remote site.
|
||||
|
||||
*** Configure org-mode for publishing
|
||||
This has two steps:
|
||||
1. Setting of a /roam/ project that publishes your notes.
|
||||
2. Configuring the /sitemap.html/ generation.
|
||||
3. Setting up =org-publish= to generate the graph.
|
||||
|
||||
This will require code like the following:
|
||||
#+begin_src emacs-lisp
|
||||
(defun roam-sitemap (title list)
|
||||
(concat "#+OPTIONS: ^:nil author:nil html-postamble:nil\n"
|
||||
"#+SETUPFILE: ./simple_inline.theme\n"
|
||||
"#+TITLE: " title "\n\n"
|
||||
(org-list-to-org list) "\nfile:sitemap.svg"))
|
||||
|
||||
(setq my-publish-time 0) ; see the next section for context
|
||||
(defun roam-publication-wrapper (plist filename pubdir)
|
||||
(org-roam-graph)
|
||||
(org-html-publish-to-html plist filename pubdir)
|
||||
(setq my-publish-time (cadr (current-time))))
|
||||
|
||||
(setq org-publish-project-alist
|
||||
'(("roam"
|
||||
:base-directory "~/roam"
|
||||
:auto-sitemap t
|
||||
:sitemap-function roam-sitemap
|
||||
:sitemap-title "Roam notes"
|
||||
:publishing-function roam-publication-wrapper
|
||||
:publishing-directory "~/roam-export"
|
||||
:section-number nil
|
||||
:table-of-contents nil
|
||||
:style "<link rel=\"stylesheet\" href=\"../other/mystyle.cs\" type=\"text/css\">")))
|
||||
#+end_src
|
||||
|
||||
*** Overriding the default link creation function
|
||||
The code below will generate a link to the generated html file instead
|
||||
of the default org-protocol link.
|
||||
#+begin_src emacs-lisp
|
||||
(defun org-roam-custom-link-builder (node)
|
||||
(let ((file (org-roam-node-file node)))
|
||||
(concat (file-name-base file) ".html")))
|
||||
|
||||
(setq org-roam-graph-link-builder 'org-roam-custom-link-builder)
|
||||
#+end_src
|
||||
|
||||
*** Copying the generated file to the export directory
|
||||
The default behavior of =org-roam-graph= is to generate the graph and
|
||||
display it in Emacs. There is an =org-roam-graph-generation-hook=
|
||||
available that provides access to the file names so they can be copied
|
||||
to the publishing directory. Example code follows:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(add-hook 'org-roam-graph-generation-hook
|
||||
(lambda (dot svg) (if (< (- (cadr (current-time)) my-publish-time) 5)
|
||||
(progn (copy-file svg "~/roam-export/sitemap.svg" 't)
|
||||
(kill-buffer (file-name-nondirectory svg))
|
||||
(setq my-publish-time 0)))))
|
||||
#+end_src
|
||||
|
||||
* Developer's Guide to Org-roam
|
||||
** Org-roam's Design Principle
|
||||
|
||||
|
@ -31,7 +31,7 @@ General Public License for more details.
|
||||
@finalout
|
||||
@titlepage
|
||||
@title Org-roam User Manual
|
||||
@subtitle for version 2.1.0
|
||||
@subtitle for version 2.2.0
|
||||
@author Jethro Kuan
|
||||
@page
|
||||
@vskip 0pt plus 1filll
|
||||
@ -44,7 +44,7 @@ General Public License for more details.
|
||||
|
||||
@noindent
|
||||
|
||||
This manual is for Org-roam version 2.1.0.
|
||||
This manual is for Org-roam version 2.2.0.
|
||||
|
||||
@quotation
|
||||
Copyright (C) 2020-2021 Jethro Kuan <jethrokuan95@@gmail.com>
|
||||
@ -70,6 +70,7 @@ General Public License for more details.
|
||||
* Customizing Node Caching::
|
||||
* The Org-roam Buffer::
|
||||
* Node Properties::
|
||||
* Citations::
|
||||
* Completion::
|
||||
* Encryption::
|
||||
* Org-roam Protocol::
|
||||
@ -79,7 +80,6 @@ General Public License for more details.
|
||||
* Performance Optimization::
|
||||
* The Org-mode Ecosystem::
|
||||
* FAQ::
|
||||
* Migrating from Org-roam v1::
|
||||
* Developer's Guide to Org-roam::
|
||||
* Appendix::
|
||||
* Keystroke Index::
|
||||
@ -100,6 +100,10 @@ Installation Troubleshooting
|
||||
|
||||
* C Compiler::
|
||||
|
||||
C Compiler
|
||||
|
||||
* C Compiler for Windows::
|
||||
|
||||
Getting Started
|
||||
|
||||
* The Org-roam Node::
|
||||
@ -109,6 +113,7 @@ Getting Started
|
||||
|
||||
Customizing Node Caching
|
||||
|
||||
* How to cache::
|
||||
* What to cache::
|
||||
* When to cache::
|
||||
|
||||
@ -126,6 +131,10 @@ Node Properties
|
||||
* Tags::
|
||||
* Refs::
|
||||
|
||||
Citations
|
||||
|
||||
* Using the Cached Information::
|
||||
|
||||
Completion
|
||||
|
||||
* Completing within Link Brackets::
|
||||
@ -143,6 +152,10 @@ Installation
|
||||
* Mac OS::
|
||||
* Windows::
|
||||
|
||||
Mac OS
|
||||
|
||||
* Testing org-protocol::
|
||||
|
||||
The Templating System
|
||||
|
||||
* Template Walkthrough::
|
||||
@ -176,9 +189,17 @@ The Org-mode Ecosystem
|
||||
FAQ
|
||||
|
||||
* How do I have more than one Org-roam directory?::
|
||||
* How do I migrate from Roam Research?::
|
||||
* How do I create a note whose title already matches one of the candidates?::
|
||||
* How can I stop Org-roam from creating IDs everywhere?::
|
||||
* How do I migrate from Roam Research?::
|
||||
* How to migrate from Org-roam v1?::
|
||||
* How do I publish my notes with an Internet-friendly graph?::
|
||||
|
||||
How do I publish my notes with an Internet-friendly graph?
|
||||
|
||||
* Configure org-mode for publishing::
|
||||
* Overriding the default link creation function::
|
||||
* Copying the generated file to the export directory::
|
||||
|
||||
Developer's Guide to Org-roam
|
||||
|
||||
@ -250,7 +271,7 @@ available to Emacs.
|
||||
|
||||
Org-roam is a tool that will appear unfriendly to anyone unfamiliar with Emacs
|
||||
and Org-mode, but it is also extremely powerful to those willing to put effort
|
||||
inn mastering the intricacies. Org-roam stands on the shoulders of giants. Emacs
|
||||
in mastering the intricacies. Org-roam stands on the shoulders of giants. Emacs
|
||||
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
|
||||
creation of Org-mode, an all-purpose plain-text system for maintaining TODO
|
||||
@ -518,42 +539,39 @@ in your Emacs environment as a prerequisite for Org-roam when you install it.
|
||||
@code{emacsql-sqlite} requires a C compiler (e.g. @code{gcc} or @code{clang}) to be present in
|
||||
your computer. How to install a C compiler depends on the OS that you use.
|
||||
|
||||
@itemize
|
||||
@item
|
||||
For Windows:
|
||||
@end itemize
|
||||
@menu
|
||||
* C Compiler for Windows::
|
||||
@end menu
|
||||
|
||||
There are various ways to install one, depending on how you have installed
|
||||
Emacs. If you use Emacs within a Cygwin or MinGW environment, then you should
|
||||
install a compiler using their respective package manager.
|
||||
@node C Compiler for Windows
|
||||
@unnumberedsubsubsec C Compiler for Windows
|
||||
|
||||
If you have installed your Emacs from the @uref{https://www.gnu.org/software/emacs/, GNU Emacs website}, then the easiest way
|
||||
is to use @uref{https://www.msys2.org/, MSYS2} as at the time of this writing:
|
||||
One of the easiest ways to install a C compiler in Windows is to use @uref{https://www.msys2.org/, MSYS2} as at the time of this writing:
|
||||
|
||||
@itemize
|
||||
@item
|
||||
Use the installer in the official website and install MSYS2
|
||||
Download and use the installer in the official MSYS2 website
|
||||
|
||||
@item
|
||||
Run MSYS2
|
||||
|
||||
@item
|
||||
In the command-line tool, type the following and answer ``Y'' to proceed:
|
||||
Run MSYS2 and in its terminal, type the following and answer ``Y'' to
|
||||
proceed -- this will install @code{gcc} in your PC:
|
||||
|
||||
@example
|
||||
pacman -S gcc
|
||||
@end example
|
||||
|
||||
Note that you do not need to manually set the PATH for MSYS2; the
|
||||
@end itemize
|
||||
installer automatically takes care of it for you.
|
||||
|
||||
@itemize
|
||||
@item
|
||||
Open Emacs and call @code{M-x org-roam-db-autosync-mode}
|
||||
On Windows, add @code{C:\msys64\usr\bin} (command @samp{where gcc} in MSYS2 terminal
|
||||
can tell you the correct path) to @code{PATH} in your environmental variables
|
||||
|
||||
|
||||
@item
|
||||
Launch Emacs and call @code{M-x org-roam-db-autosync-mode} (launch Emacs after
|
||||
defining the path, so that Emacs can recognize it)
|
||||
@end itemize
|
||||
|
||||
This will automatically start compiling @code{emacsql-sqlite}; you should see a
|
||||
@end itemize
|
||||
message in minibuffer. It may take a while until compilation completes. Once
|
||||
complete, you should see a new file @code{emacsql-sqlite.exe} created in a subfolder
|
||||
named @code{sqlite} under @code{emacsql-sqlite} installation folder. It's typically in
|
||||
@ -637,8 +655,8 @@ The @code{file-truename} function is only necessary when you use symbolic links
|
||||
inside @code{org-roam-directory}: Org-roam does not resolve symbolic links.
|
||||
|
||||
Next, we setup Org-roam to run functions on file changes to maintain cache
|
||||
consistency. This is achieved by running @code{M-x org-roam-db-autosync-mode~}.
|
||||
To ensure that Org-roam is available on startup, place this in your Emacs
|
||||
consistency. This is achieved by running @code{M-x org-roam-db-autosync-mode}. To
|
||||
ensure that Org-roam is available on startup, place this in your Emacs
|
||||
configuration:
|
||||
|
||||
@lisp
|
||||
@ -690,10 +708,57 @@ Org-roam provides (see @ref{Completion}).
|
||||
@chapter Customizing Node Caching
|
||||
|
||||
@menu
|
||||
* How to cache::
|
||||
* What to cache::
|
||||
* When to cache::
|
||||
@end menu
|
||||
|
||||
@node How to cache
|
||||
@section How to cache
|
||||
|
||||
Org-roam uses a sqlite database to perform caching, but there are multiple Emacs
|
||||
libraries that can be used. The default used by Org-roam is @code{emacs-sqlite}.
|
||||
Below the pros and cons of each package is used:
|
||||
|
||||
@uref{https://github.com/skeeto/emacsql, @strong{@strong{emacs-sqlite}}}
|
||||
|
||||
The default option used by Org-roam. This library is the most mature and
|
||||
well-supported and is imported by default in Org-roam.
|
||||
|
||||
One downside of using @code{emacs-sqlite} is that using it requires compilation and
|
||||
can cause issues in some environments (especially Windows). If you have issues
|
||||
producing the customized binary required by @code{emacs-sqlite}, consider using
|
||||
@code{emacs-sqlite3}.
|
||||
|
||||
@uref{https://github.com/cireu/emacsql-sqlite3, @strong{@strong{emacs-sqlite3}}}
|
||||
|
||||
@code{emacs-sqlite3} uses the official sqlite3 binary that can be obtained from your
|
||||
system's package manager. This is useful if you have issues producing the
|
||||
@code{sqlite3} binary required by the other packages. However, it is not recommended
|
||||
because it has some compatibility issues with Emacs, but should work for most
|
||||
regular cases. See @uref{https://nullprogram.com/blog/2014/02/06/, Chris Wellon's blog post} for more information.
|
||||
|
||||
To use @code{emacsql-sqlite3}, ensure that the package is installed, and set:
|
||||
|
||||
@lisp
|
||||
(setq org-roam-database-connector 'sqlite3)
|
||||
@end lisp
|
||||
|
||||
@uref{https://github.com/emacscollective/emacsql-libsqlite3/, @strong{@strong{emacsql-libsqlite3}}}
|
||||
|
||||
@code{emacs-libsqlite3} is a relatively young package which uses an Emacs module that
|
||||
exposes parts of the SQLite C API to Emacs Lisp, instead of using subprocess as
|
||||
@code{emacsql-sqlite} does. It is expected to be a more performant drop-in
|
||||
replacement for @code{emacs-sqlite}.
|
||||
|
||||
At the moment it is experimental and does not work well with the SQL query load
|
||||
required by Org-roam, but you may still try it by ensuring the package is
|
||||
installed and setting:
|
||||
|
||||
@lisp
|
||||
(setq org-roam-database-connector 'libsqlite3)
|
||||
@end lisp
|
||||
|
||||
@node What to cache
|
||||
@section What to cache
|
||||
|
||||
@ -794,9 +859,10 @@ keybindings available. Here are several of the more useful ones:
|
||||
|
||||
@item
|
||||
@code{n}: @code{magit-section-forward}
|
||||
@end itemize
|
||||
-@code{<TAB>}: @code{magit-section-toggle}
|
||||
@itemize
|
||||
|
||||
@item
|
||||
@code{<TAB>}: @code{magit-section-toggle}
|
||||
|
||||
@item
|
||||
@code{<RET>}: @code{org-roam-buffer-visit-thing}
|
||||
@end itemize
|
||||
@ -995,6 +1061,65 @@ ref to add.
|
||||
Remove a ref from the node at point.
|
||||
@end defun
|
||||
|
||||
@node Citations
|
||||
@chapter Citations
|
||||
|
||||
Since version 9.5, Org has first-class support for citations. Org-roam supports
|
||||
the caching of both these in-built citations (of form @code{[cite:@@key]}) and @uref{https://github.com/jkitchin/org-ref, org-ref}
|
||||
citations (of form cite:key).
|
||||
|
||||
Org-roam attempts to load both the @code{org-ref} and @code{org-cite} package when
|
||||
indexing files, so no further setup from the user is required for citation
|
||||
support.
|
||||
|
||||
@menu
|
||||
* Using the Cached Information::
|
||||
@end menu
|
||||
|
||||
@node Using the Cached Information
|
||||
@section Using the Cached Information
|
||||
|
||||
It is common to use take reference notes for academic papers. To designate the
|
||||
node to be the canonical node for the academic paper, we can use its unique
|
||||
citation key:
|
||||
|
||||
@example
|
||||
* Probabilistic Robotics
|
||||
:PROPERTIES:
|
||||
:ID: 51b7b82c-bbb4-4822-875a-ed548cffda10
|
||||
:ROAM_REFS: @@thrun2005probabilistic
|
||||
:END:
|
||||
@end example
|
||||
|
||||
or
|
||||
|
||||
@example
|
||||
* Probabilistic Robotics
|
||||
:PROPERTIES:
|
||||
:ID: 51b7b82c-bbb4-4822-875a-ed548cffda10
|
||||
:ROAM_REFS: [cite:@@thrun2005probabilistic]
|
||||
:END:
|
||||
@end example
|
||||
|
||||
for @code{org-cite}, or:
|
||||
|
||||
@example
|
||||
* Probabilistic Robotics
|
||||
:PROPERTIES:
|
||||
:ID: 51b7b82c-bbb4-4822-875a-ed548cffda10
|
||||
:ROAM_REFS: cite:thrun2005probabilistic
|
||||
:END:
|
||||
@end example
|
||||
|
||||
for @code{org-ref}.
|
||||
|
||||
When another node has a citation for that key, we can see it using the
|
||||
@code{Reflinks} section of the Org-roam buffer.
|
||||
|
||||
Extension developers may be interested in retrieving the citations within their
|
||||
notes. This information can be found within the @code{citation} table of the Org-roam
|
||||
database.
|
||||
|
||||
@node Completion
|
||||
@chapter Completion
|
||||
|
||||
@ -1062,7 +1187,7 @@ extension in your Org-roam capture templates. For example:
|
||||
|
||||
@lisp
|
||||
(setq org-roam-capture-templates '(("d" "default" plain "%?"
|
||||
:if-new (file+head "$@{slug@}.org.gpg"
|
||||
:target (file+head "$@{slug@}.org.gpg"
|
||||
"#+title: $@{title@}\n")
|
||||
:unnarrowed t)))
|
||||
@end lisp
|
||||
@ -1216,6 +1341,35 @@ defaults write com.apple.LaunchServices/com.apple.launchservices.secure LSHandle
|
||||
|
||||
Then restart your computer.
|
||||
|
||||
@menu
|
||||
* Testing org-protocol::
|
||||
@end menu
|
||||
|
||||
@node Testing org-protocol
|
||||
@unnumberedsubsubsec Testing org-protocol
|
||||
|
||||
To test that you have the handler setup and registered properly from the command
|
||||
line you can run:
|
||||
|
||||
@example
|
||||
open org-protocol://roam-ref\?template=r\&ref=test\&title=this
|
||||
@end example
|
||||
|
||||
If you get an error similar too this or the wrong handler is run:
|
||||
|
||||
@quotation
|
||||
No application knows how to open URL org-protocol://roam-ref?template=r&ref=test&title=this (Error Domain=NSOSStatusErrorDomain Code=-10814 ``kLSApplicationNotFoundErr: E.g. no application claims the file'' UserInfo=@{@math{_LSLine}=1489, _LSFunction=runEvaluator@}).
|
||||
|
||||
@end quotation
|
||||
|
||||
You may need to manually register your handler, like this:
|
||||
|
||||
@example
|
||||
/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister -R -f /Applications/OrgProtocolClient.app
|
||||
@end example
|
||||
|
||||
Here is a link to the lsregister command that is really useful: @uref{https://eclecticlight.co/2019/03/25/lsregister-a-valuable-undocumented-command-for-launchservices/}
|
||||
|
||||
@node Windows
|
||||
@subsection Windows
|
||||
|
||||
@ -1300,12 +1454,12 @@ will be no prompt for template selection.
|
||||
@section Template Walkthrough
|
||||
|
||||
To demonstrate the additions made to org-capture templates. Here, we explain
|
||||
the default template, reproduced below. You will find some most of the elements
|
||||
the default template, reproduced below. You will find most of the elements
|
||||
of the template are similar to @code{org-capture} templates.
|
||||
|
||||
@lisp
|
||||
(("d" "default" plain "%?"
|
||||
:if-new (file+head "%<%Y%m%d%H%M%S>-$@{slug@}.org"
|
||||
:target (file+head "%<%Y%m%d%H%M%S>-$@{slug@}.org"
|
||||
"#+title: $@{title@}\n")
|
||||
:unnarrowed t))
|
||||
@end lisp
|
||||
@ -1331,8 +1485,12 @@ here.
|
||||
This template means don't insert any content, but place the cursor here.
|
||||
|
||||
@item
|
||||
@code{:if-new} is a compulsory specification in the Org-roam capture template.
|
||||
This indicates the location for the new node.
|
||||
@code{:target} is a compulsory specification in the Org-roam capture template. The
|
||||
first element of the list indicates the type of the target, the second
|
||||
element indicates the location of the captured node, and the rest of the
|
||||
elements indicate prefilled template that will be inserted and the position
|
||||
of the point will be adjusted for. The latter behavior varies from type to
|
||||
type of the capture target.
|
||||
|
||||
@item
|
||||
@code{:unnarrowed t} tells org-capture to show the contents for the whole file,
|
||||
@ -1519,7 +1677,7 @@ Here is a sane default configuration:
|
||||
(setq org-roam-dailies-capture-templates
|
||||
'(("d" "default" entry
|
||||
"* %?"
|
||||
:if-new (file+head "%<%Y-%m-%d>.org"
|
||||
:target (file+head "%<%Y-%m-%d>.org"
|
||||
"#+title: %<%Y-%m-%d>\n"))))
|
||||
@end lisp
|
||||
|
||||
@ -1748,6 +1906,9 @@ documents (PDF, EPUB etc.) within Org-mode.
|
||||
@node Bibliography
|
||||
@section Bibliography
|
||||
|
||||
Org 9.5 added native citation and bibliography functionality, called ``org-cite'',
|
||||
which org-roam supports.
|
||||
|
||||
@uref{https://github.com/org-roam/org-roam-bibtex, org-roam-bibtex} offers tight integration between @uref{https://github.com/jkitchin/org-ref, org-ref}, @uref{https://github.com/tmalsburg/helm-bibtex, helm-bibtex} and
|
||||
@code{org-roam}. This helps you manage your bibliographic notes under @code{org-roam}.
|
||||
|
||||
@ -1769,9 +1930,11 @@ Org-mode, and sync your cards to Anki via @uref{https://github.com/FooSoft/anki-
|
||||
|
||||
@menu
|
||||
* How do I have more than one Org-roam directory?::
|
||||
* How do I migrate from Roam Research?::
|
||||
* How do I create a note whose title already matches one of the candidates?::
|
||||
* How can I stop Org-roam from creating IDs everywhere?::
|
||||
* How do I migrate from Roam Research?::
|
||||
* How to migrate from Org-roam v1?::
|
||||
* How do I publish my notes with an Internet-friendly graph?::
|
||||
@end menu
|
||||
|
||||
@node How do I have more than one Org-roam directory?
|
||||
@ -1786,21 +1949,27 @@ variable using directory-local variables. This is what @code{.dir-locals.el} may
|
||||
contain:
|
||||
|
||||
@lisp
|
||||
((nil . ((org-roam-directory . (expand-file-name "."))
|
||||
(org-roam-db-location . (expand-file-name "./org-roam.db")))))
|
||||
((nil . ((org-roam-directory . "/path/to/alt/org-roam-dir")
|
||||
(org-roam-db-location . "/path/to/alt/org-roam-dir/org-roam.db"))))
|
||||
@end lisp
|
||||
|
||||
Note @code{org-roam-directory} and @code{org-roam-db-location} should be an absolute path, not relative.
|
||||
|
||||
Alternatively, use @code{eval} if you wish to call functions:
|
||||
|
||||
@lisp
|
||||
((nil . ((eval . (setq-local
|
||||
org-roam-directory (expand-file-name (locate-dominating-file
|
||||
default-directory ".dir-locals.el"))))
|
||||
(eval . (setq-local
|
||||
org-roam-db-location (expand-file-name "org-roam.db"
|
||||
org-roam-directory))))))
|
||||
@end lisp
|
||||
|
||||
All files within that directory will be treated as their own separate set of
|
||||
Org-roam files. Remember to run @code{org-roam-db-sync} from a file within
|
||||
that directory, at least once.
|
||||
|
||||
@node How do I migrate from Roam Research?
|
||||
@section How do I migrate from Roam Research?
|
||||
|
||||
Fabio has produced a command-line tool that converts markdown files exported
|
||||
from Roam Research into Org-roam compatible markdown. More instructions are
|
||||
provided @uref{https://github.com/fabioberger/roam-migration, in the repository}.
|
||||
|
||||
@node How do I create a note whose title already matches one of the candidates?
|
||||
@section How do I create a note whose title already matches one of the candidates?
|
||||
|
||||
@ -1829,24 +1998,32 @@ you don't want them to be (e.g. when tangling an Org file), check the value you
|
||||
have set for @code{org-id-link-to-org-use-id}: setting it to @code{'create-if-interactive}
|
||||
is a popular option.
|
||||
|
||||
@node Migrating from Org-roam v1
|
||||
@chapter Migrating from Org-roam v1
|
||||
@node How do I migrate from Roam Research?
|
||||
@section How do I migrate from Roam Research?
|
||||
|
||||
Fabio has produced a command-line tool that converts markdown files exported
|
||||
from Roam Research into Org-roam compatible markdown. More instructions are
|
||||
provided @uref{https://github.com/fabioberger/roam-migration, in the repository}.
|
||||
|
||||
@node How to migrate from Org-roam v1?
|
||||
@section How to migrate from Org-roam v1?
|
||||
|
||||
Those coming from Org-roam v1 will do well treating v2 as entirely new software.
|
||||
V2 has a smaller core and fewer moving parts, while retaining the bulk of its
|
||||
functionality. It is recommended to read the documentation above about nodes.
|
||||
|
||||
It is still desirable to migrate notes collected in v1 to v2. To migrate your v1
|
||||
notes to v2, you may use the migration script provided in @uref{https://gist.github.com/jethrokuan/02f41028fb4a6f81787dc420fb99b6e4, this gist}, or @uref{https://gist.github.com/jethrokuan/02f41028fb4a6f81787dc420fb99b6e4#gistcomment-3737019, this
|
||||
gist}, the latter being better tested. @uref{https://d12frosted.io/posts/2021-06-11-path-to-org-roam-v2.html, This blog post} provides a good overview of
|
||||
what's new in v2 and how to migrate.
|
||||
It is still desirable to migrate notes collected in v1 to v2.
|
||||
To migrate your v1 notes to v2, use @samp{M-x org-roam-migrate-wizard}.
|
||||
@uref{https://d12frosted.io/posts/2021-06-11-path-to-org-roam-v2.html, This blog post}
|
||||
provides a good overview of what's new in v2 and how to migrate.
|
||||
|
||||
Simply put, to migrate notes from v1 to v2, one must:
|
||||
Essentially, to migrate notes from v1 to v2, one must:
|
||||
|
||||
@itemize
|
||||
@item
|
||||
Add IDs to all existing notes. These are located in top-level property
|
||||
drawers (Although note that in v2, not all files need to have IDs)
|
||||
Add IDs to all existing notes.
|
||||
These are located in top-level property drawers
|
||||
(Although note that in v2, not all files need to have IDs).
|
||||
|
||||
@item
|
||||
Update the Org-roam database to conform to the new schema.
|
||||
@ -1858,13 +2035,116 @@ Replace @code{#+ROAM_KEY} into the @code{ROAM_REFS} property
|
||||
Replace @code{#+ROAM_ALIAS} into the @code{ROAM_ALIASES} property
|
||||
|
||||
@item
|
||||
Move @code{#+ROAM_TAGS} into the @code{#+FILETAGS} property for file-level nodes, and
|
||||
the @code{ROAM_TAGS} property for headline nodes
|
||||
Move @code{#+ROAM_TAGS} into the @code{#+FILETAGS} property for file-level nodes,
|
||||
and the @code{ROAM_TAGS} property for headline nodes
|
||||
|
||||
@item
|
||||
Replace existing file links with ID links.
|
||||
@end itemize
|
||||
|
||||
@node How do I publish my notes with an Internet-friendly graph?
|
||||
@section How do I publish my notes with an Internet-friendly graph?
|
||||
|
||||
The default graph builder creates a graph with an @uref{https://orgmode.org/worg/org-contrib/org-protocol.html, org-protocol}
|
||||
handler which is convenient when you're working locally but
|
||||
inconvenient when you want to publish your notes for remote access.
|
||||
Likewise, it defaults to displaying the graph in Emacs which has the
|
||||
exact same caveats. This problem is solvable in the following way
|
||||
using org-mode's native @uref{https://orgmode.org/manual/Publishing.html, publishing} capability:
|
||||
|
||||
@itemize
|
||||
@item
|
||||
configure org-mode to publish your org-roam notes as a project.
|
||||
|
||||
@item
|
||||
create a function that overrides the default org-protocol link
|
||||
creation function(@samp{org-roam-default-link-builder}).
|
||||
|
||||
@item
|
||||
create a hook that's called at the end of graph creation to copy
|
||||
the generated graph to the appropriate place.
|
||||
@end itemize
|
||||
|
||||
The example code below is used to publish to a local directory where a
|
||||
separate shell script copies the files to the remote site.
|
||||
|
||||
@menu
|
||||
* Configure org-mode for publishing::
|
||||
* Overriding the default link creation function::
|
||||
* Copying the generated file to the export directory::
|
||||
@end menu
|
||||
|
||||
@node Configure org-mode for publishing
|
||||
@subsection Configure org-mode for publishing
|
||||
|
||||
This has two steps:
|
||||
@itemize
|
||||
@item
|
||||
Setting of a @emph{roam} project that publishes your notes.
|
||||
|
||||
@item
|
||||
Configuring the @emph{sitemap.html} generation.
|
||||
|
||||
@item
|
||||
Setting up @samp{org-publish} to generate the graph.
|
||||
@end itemize
|
||||
|
||||
This will require code like the following:
|
||||
@lisp
|
||||
(defun roam-sitemap (title list)
|
||||
(concat "#+OPTIONS: ^:nil author:nil html-postamble:nil\n"
|
||||
"#+SETUPFILE: ./simple_inline.theme\n"
|
||||
"#+TITLE: " title "\n\n"
|
||||
(org-list-to-org list) "\nfile:sitemap.svg"))
|
||||
|
||||
(setq my-publish-time 0) ; see the next section for context
|
||||
(defun roam-publication-wrapper (plist filename pubdir)
|
||||
(org-roam-graph)
|
||||
(org-html-publish-to-html plist filename pubdir)
|
||||
(setq my-publish-time (cadr (current-time))))
|
||||
|
||||
(setq org-publish-project-alist
|
||||
'(("roam"
|
||||
:base-directory "~/roam"
|
||||
:auto-sitemap t
|
||||
:sitemap-function roam-sitemap
|
||||
:sitemap-title "Roam notes"
|
||||
:publishing-function roam-publication-wrapper
|
||||
:publishing-directory "~/roam-export"
|
||||
:section-number nil
|
||||
:table-of-contents nil
|
||||
:style "<link rel=\"stylesheet\" href=\"../other/mystyle.cs\" type=\"text/css\">")))
|
||||
@end lisp
|
||||
|
||||
@node Overriding the default link creation function
|
||||
@subsection Overriding the default link creation function
|
||||
|
||||
The code below will generate a link to the generated html file instead
|
||||
of the default org-protocol link.
|
||||
@lisp
|
||||
(defun org-roam-custom-link-builder (node)
|
||||
(let ((file (org-roam-node-file node)))
|
||||
(concat (file-name-base file) ".html")))
|
||||
|
||||
(setq org-roam-graph-link-builder 'org-roam-custom-link-builder)
|
||||
@end lisp
|
||||
|
||||
@node Copying the generated file to the export directory
|
||||
@subsection Copying the generated file to the export directory
|
||||
|
||||
The default behavior of @samp{org-roam-graph} is to generate the graph and
|
||||
display it in Emacs. There is an @samp{org-roam-graph-generation-hook}
|
||||
available that provides access to the file names so they can be copied
|
||||
to the publishing directory. Example code follows:
|
||||
|
||||
@lisp
|
||||
(add-hook 'org-roam-graph-generation-hook
|
||||
(lambda (dot svg) (if (< (- (cadr (current-time)) my-publish-time) 5)
|
||||
(progn (copy-file svg "~/roam-export/sitemap.svg" 't)
|
||||
(kill-buffer (file-name-nondirectory svg))
|
||||
(setq my-publish-time 0)))))
|
||||
@end lisp
|
||||
|
||||
@node Developer's Guide to Org-roam
|
||||
@chapter Developer's Guide to Org-roam
|
||||
|
||||
@ -2117,5 +2397,5 @@ When GOTO is non-nil, go the note without creating an entry."
|
||||
|
||||
@printindex vr
|
||||
|
||||
Emacs 28.0.50 (Org mode 9.5)
|
||||
Emacs 29.0.50 (Org mode 9.6)
|
||||
@bye
|
||||
|
@ -7,7 +7,7 @@
|
||||
;; Leo Vivier <leo.vivier+dev@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 2.1.0
|
||||
;; Version: 2.2.0
|
||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org-roam "2.1"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -62,7 +62,7 @@ This path is relative to `org-roam-directory'."
|
||||
(defcustom org-roam-dailies-capture-templates
|
||||
`(("d" "default" entry
|
||||
"* %?"
|
||||
:if-new (file+head "%<%Y-%m-%d>.org"
|
||||
:target (file+head "%<%Y-%m-%d>.org"
|
||||
"#+title: %<%Y-%m-%d>\n")))
|
||||
"Capture templates for daily-notes in Org-roam.
|
||||
Note that for daily files to show up in the calendar, they have to be of format
|
||||
@ -92,7 +92,7 @@ See `org-roam-capture-templates' for the template documentation."
|
||||
(function :tag "Template function")))
|
||||
(plist :inline t
|
||||
;; Give the most common options as checkboxes
|
||||
:options (((const :format "%v " :if-new)
|
||||
:options (((const :format "%v " :target)
|
||||
(choice :tag "Node location"
|
||||
(list :tag "File"
|
||||
(const :format "" file)
|
||||
@ -130,11 +130,14 @@ See `org-roam-capture-templates' for the template documentation."
|
||||
;;; Commands
|
||||
;;;; Today
|
||||
;;;###autoload
|
||||
(defun org-roam-dailies-capture-today (&optional goto)
|
||||
(defun org-roam-dailies-capture-today (&optional goto keys)
|
||||
"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 the note without creating an entry.
|
||||
|
||||
ELisp programs can set KEYS to a string associated with a template.
|
||||
In this case, interactive selection will be bypassed."
|
||||
(interactive "P")
|
||||
(org-roam-dailies--capture (current-time) goto))
|
||||
(org-roam-dailies--capture (current-time) goto keys))
|
||||
|
||||
;;;###autoload
|
||||
(defun org-roam-dailies-goto-today ()
|
||||
@ -192,7 +195,7 @@ With a `C-u' prefix or when GOTO is non-nil, go the note without
|
||||
creating an entry."
|
||||
(interactive "P")
|
||||
(let ((time (let ((org-read-date-prefer-future prefer-future))
|
||||
(org-read-date t t nil (if goto
|
||||
(org-read-date nil t nil (if goto
|
||||
"Find daily-note: "
|
||||
"Capture to daily-note: ")))))
|
||||
(org-roam-dailies--capture time goto)))
|
||||
@ -300,11 +303,15 @@ Return (MONTH DAY YEAR) or nil if not an Org time-string."
|
||||
;;; Capture implementation
|
||||
(add-to-list 'org-roam-capture--template-keywords :override-default-time)
|
||||
|
||||
(defun org-roam-dailies--capture (time &optional goto)
|
||||
(defun org-roam-dailies--capture (time &optional goto keys)
|
||||
"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.
|
||||
|
||||
ELisp programs can set KEYS to a string associated with a template.
|
||||
In this case, interactive selection will be bypassed."
|
||||
(let ((org-roam-directory (expand-file-name org-roam-dailies-directory org-roam-directory)))
|
||||
(org-roam-capture- :goto (when goto '(4))
|
||||
:keys keys
|
||||
:node (org-roam-node-create)
|
||||
:templates org-roam-dailies-capture-templates
|
||||
:props (list :override-default-time time)))
|
||||
|
@ -5,7 +5,7 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 2.1.0
|
||||
;; Version: 2.2.0
|
||||
;; Package-Requires: ((emacs "26.1") (org "9.4") (org-roam "2.1"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -81,7 +81,7 @@ Example:
|
||||
("fillcolor" . "#EEEEEE")
|
||||
("color" . "#C9C9C9")
|
||||
("fontcolor" . "#0A97A6")))
|
||||
("https" . (("shape" . "rounded,filled")
|
||||
("https" . (("style" . "rounded,filled")
|
||||
("fillcolor" . "#EEEEEE")
|
||||
("color" . "#C9C9C9")
|
||||
("fontcolor" . "#0A97A6"))))
|
||||
@ -113,6 +113,25 @@ All other values including nil will have no effect."
|
||||
(const :tag "no" nil))
|
||||
:group 'org-roam)
|
||||
|
||||
(defcustom org-roam-graph-link-builder 'org-roam-org-protocol-link-builder
|
||||
"Function used to build the Org-roam graph links.
|
||||
Given a node name, return a string to be used for the link fed to
|
||||
the graph generation utility."
|
||||
:type 'function
|
||||
:group 'org-roam)
|
||||
|
||||
(defcustom org-roam-graph-generation-hook nil
|
||||
"Functions to run after the graph has been generated.
|
||||
Each function is called with two arguments: the filename
|
||||
containing the graph generation tool, and the generated graph."
|
||||
:type 'hook
|
||||
:group 'org-roam)
|
||||
|
||||
(defun org-roam-org-protocol-link-builder (node)
|
||||
"Default org-roam link builder. Generate an org-protocol link using NODE."
|
||||
(concat "org-protocol://roam-node?node="
|
||||
(url-hexify-string (org-roam-node-id node))))
|
||||
|
||||
;;; Interactive command
|
||||
;;;###autoload
|
||||
(defun org-roam-graph (&optional arg node)
|
||||
@ -147,13 +166,14 @@ CALLBACK is passed the graph file as its sole argument."
|
||||
(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*"
|
||||
:name "*org-roam-graph*"
|
||||
:buffer " *org-roam-graph*"
|
||||
: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)))))))
|
||||
(progn (funcall callback temp-graph)
|
||||
(run-hook-with-args 'org-roam-graph-generation-hook temp-dot temp-graph))))))))
|
||||
|
||||
(defun org-roam-graph--dot (&optional edges all-nodes)
|
||||
"Build the graphviz given the EDGES of the graph.
|
||||
@ -250,8 +270,7 @@ Handles both Org-roam nodes, and string nodes (e.g. urls)."
|
||||
(_ title)))))
|
||||
(setq node-id (org-roam-node-id node)
|
||||
node-properties `(("label" . ,shortened-title)
|
||||
("URL" . ,(concat "org-protocol://roam-node?node="
|
||||
(url-hexify-string (org-roam-node-id node))))
|
||||
("URL" . ,(funcall org-roam-graph-link-builder node))
|
||||
("tooltip" . ,(xml-escape-string title)))))
|
||||
(setq node-id node
|
||||
node-properties (append `(("label" . ,(concat type ":" node)))
|
||||
|
@ -5,7 +5,7 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 2.1.0
|
||||
;; Version: 2.2.0
|
||||
;; Package-Requires: ((emacs "26.1") (org "9.4") (org-roam "2.1"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -27,8 +27,8 @@
|
||||
|
||||
;;; Commentary:
|
||||
;;
|
||||
;; This extension provides allows to render [[id:]] links that don't have an
|
||||
;; asscoiated descriptor with an overlay that displays the node's current title.
|
||||
;; This extension allows to render [[id:]] links that don't have an associated
|
||||
;; descriptor with an overlay that displays the node's current title.
|
||||
;;
|
||||
;;; Code:
|
||||
(require 'org-roam)
|
||||
|
@ -4,7 +4,7 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 2.1.0
|
||||
;; Version: 2.2.0
|
||||
;; Package-Requires: ((emacs "26.1") (org "9.4") (org-roam "2.1"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -48,7 +48,7 @@
|
||||
|
||||
(defcustom org-roam-capture-ref-templates
|
||||
'(("r" "ref" plain "%?"
|
||||
:if-new (file+head "${slug}.org"
|
||||
:target (file+head "${slug}.org"
|
||||
"#+title: ${title}")
|
||||
:unnarrowed t))
|
||||
"The Org-roam templates used during a capture from the roam-ref protocol.
|
||||
@ -77,7 +77,7 @@ See `org-roam-capture-templates' for the template documentation."
|
||||
(function :tag "Template function")))
|
||||
(plist :inline t
|
||||
;; Give the most common options as checkboxes
|
||||
:options (((const :format "%v " :if-new)
|
||||
:options (((const :format "%v " :target)
|
||||
(choice :tag "Node location"
|
||||
(list :tag "File"
|
||||
(const :format "" file)
|
||||
@ -141,12 +141,13 @@ It opens or creates a note with the given ref.
|
||||
(plist-get info :ref)))
|
||||
:initial (or (plist-get info :body) ""))
|
||||
(raise-frame)
|
||||
(let ((org-capture-link-is-already-stored t))
|
||||
(org-roam-capture-
|
||||
: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)
|
||||
:templates org-roam-capture-ref-templates))
|
||||
nil)
|
||||
|
||||
(defun org-roam-protocol-open-node (info)
|
||||
|
@ -5,8 +5,8 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 2.1.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"))
|
||||
;; Version: 2.2.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 "3.0.0"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
|
||||
@ -40,7 +40,7 @@
|
||||
;;; Options
|
||||
(defcustom org-roam-capture-templates
|
||||
'(("d" "default" plain "%?"
|
||||
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
|
||||
:target (file+head "%<%Y%m%d%H%M%S>-${slug}.org"
|
||||
"#+title: ${title}\n")
|
||||
:unnarrowed t))
|
||||
"Templates for the creation of new entries within Org-roam.
|
||||
@ -92,30 +92,41 @@ template The template for creating the capture item.
|
||||
in order to get a template from a file, or dynamically
|
||||
from a function.
|
||||
|
||||
The template contains a compulsory :if-new property. This determines the
|
||||
location of the new node. The :if-new property contains a list, supporting
|
||||
the following options:
|
||||
The template contains a compulsory :target property. The :target property
|
||||
contains a list, where:
|
||||
- The first element indicates the type of the target.
|
||||
- The second element indicates the location of the captured node.
|
||||
- And the rest of the list indicate the prefilled template, that will be
|
||||
inserted and the position of the point will be adjusted for.
|
||||
This behavior varies from type to type.
|
||||
|
||||
The following options are supported for the :target property:
|
||||
|
||||
(file \"path/to/file\")
|
||||
The file will be created, and prescribed an ID.
|
||||
|
||||
(file+head \"path/to/file\" \"head content\")
|
||||
The file will be created, prescribed an ID, and head content will be
|
||||
inserted into the file.
|
||||
inserted if the node is a newly captured one.
|
||||
|
||||
(file+olp \"path/to/file\" (\"h1\" \"h2\"))
|
||||
The file will be created, prescribed an ID. The OLP (h1, h2) will be
|
||||
created, and the point placed after.
|
||||
The file will be created, prescribed an ID. If the file doesn't contain
|
||||
the outline path (h1, h2), it will be automatically created. The point
|
||||
will be adjusted to the last element in the OLP.
|
||||
|
||||
(file+head+olp \"path/to/file\" \"head content\" (\"h1\" \"h2\"))
|
||||
The file will be created, prescribed an ID. Head content will be
|
||||
inserted at the start of the file. The OLP (h1, h2) will be created,
|
||||
and the point placed after.
|
||||
inserted at the start of the file if the node is a newly captured one.
|
||||
If the file doesn't contain the outline path (h1, h2), it will be
|
||||
automatically created. The point will be adjusted to the last element in
|
||||
the OLP.
|
||||
|
||||
(file+datetree \"path/to/file\" day)
|
||||
The file will be created, prescribed an ID. Head content will be
|
||||
inserted at the start of the file. The datetree will be created,
|
||||
available options are day, week, month.
|
||||
(file+datetree \"path/to/file\" tree-type)
|
||||
The file will be created, prescribed an ID. A date based outline path
|
||||
will be created for today's date. The tree-type can be one of the
|
||||
following symbols: day, week or month. The point will adjusted to the
|
||||
last element in the tree. To prompt for date instead of using today's,
|
||||
use the :time-prompt property.
|
||||
|
||||
(node \"title or alias or ID of an existing node\")
|
||||
The point will be placed for an existing node, based on either, its
|
||||
@ -194,7 +205,8 @@ be replaced with content and expanded:
|
||||
introduced with %[pathname] are expanded this way. Since this
|
||||
happens after expanding non-interactive %-escapes, those can
|
||||
be used to fill the expression.
|
||||
%<...> The result of `format-time-string' on the ... format specification.
|
||||
%<...> The result of `format-time-string' on the ... format
|
||||
specification.
|
||||
%t Time stamp, date only. The time stamp is the current time,
|
||||
except when called from agendas with `\\[org-agenda-capture]' or
|
||||
with `org-capture-use-agenda-date' set.
|
||||
@ -289,7 +301,7 @@ streamlined user experience in Org-roam."
|
||||
(function :tag "Template function")))
|
||||
(plist :inline t
|
||||
;; Give the most common options as checkboxes
|
||||
:options (((const :format "%v " :if-new)
|
||||
:options (((const :format "%v " :target)
|
||||
(choice :tag "Node location"
|
||||
(list :tag "File"
|
||||
(const :format "" file)
|
||||
@ -377,7 +389,7 @@ during the Org-roam capture process.")
|
||||
This variable is populated dynamically, and is only non-nil
|
||||
during the Org-roam capture process.")
|
||||
|
||||
(defconst org-roam-capture--template-keywords (list :if-new :id :link-description :call-location
|
||||
(defconst org-roam-capture--template-keywords (list :target :id :link-description :call-location
|
||||
:region)
|
||||
"Keywords used in `org-roam-capture-templates' specific to Org-roam.")
|
||||
|
||||
@ -433,36 +445,33 @@ the capture)."
|
||||
"Get the value for KEYWORD from the `org-roam-capture-template'."
|
||||
(plist-get (plist-get org-capture-plist :org-roam) keyword))
|
||||
|
||||
(defun org-roam-capture--put (&rest stuff)
|
||||
"Put properties from STUFF into the `org-roam-capture-template'."
|
||||
(defun org-roam-capture--put (prop value)
|
||||
"Set property PROP to VALUE in the `org-roam-capture-template'."
|
||||
(let ((p (plist-get org-capture-plist :org-roam)))
|
||||
(while stuff
|
||||
(setq p (plist-put p (pop stuff) (pop stuff))))
|
||||
(setq org-capture-plist
|
||||
(plist-put org-capture-plist :org-roam p))))
|
||||
(plist-put org-capture-plist
|
||||
:org-roam
|
||||
(plist-put p prop value)))))
|
||||
|
||||
;;;; Capture target
|
||||
(defun org-roam-capture--prepare-buffer ()
|
||||
"Prepare the capture buffer for the current Org-roam based capture template.
|
||||
This function will initialize and setup the capture buffer,
|
||||
create the target node (`:if-new') if it doesn't exist, and place
|
||||
the point for further processing by `org-capture'.
|
||||
position the point to the current :target (and if necessary,
|
||||
create it if it doesn't exist), and place the point for further
|
||||
processing by `org-capture'.
|
||||
|
||||
Note: During the capture process this function is run by
|
||||
`org-capture-set-target-location', as a (function ...) based
|
||||
capture target."
|
||||
(let ((id (cond ((run-hook-with-args-until-success 'org-roam-capture-preface-hook))
|
||||
((and (org-roam-node-file org-roam-capture--node)
|
||||
(org-roam-node-point org-roam-capture--node))
|
||||
(set-buffer (org-capture-target-buffer (org-roam-node-file org-roam-capture--node)))
|
||||
(goto-char (org-roam-node-point org-roam-capture--node))
|
||||
(widen)
|
||||
(org-roam-node-id org-roam-capture--node))
|
||||
(t
|
||||
(org-roam-capture--setup-target-location)))))
|
||||
(t (org-roam-capture--setup-target-location)))))
|
||||
(org-roam-capture--adjust-point-for-capture-type)
|
||||
(org-capture-put :template
|
||||
(org-roam-capture--fill-template (org-capture-get :template)))
|
||||
(let ((template (org-capture-get :template)))
|
||||
(when (stringp template)
|
||||
(org-capture-put
|
||||
:template
|
||||
(org-roam-capture--fill-template template))))
|
||||
(org-roam-capture--put :id id)
|
||||
(org-roam-capture--put :finalize (or (org-capture-get :finalize)
|
||||
(org-roam-capture--get :finalize)))))
|
||||
@ -471,22 +480,17 @@ capture target."
|
||||
"Initialize the buffer, and goto the location of the new capture.
|
||||
Return the ID of the location."
|
||||
(let (p new-file-p)
|
||||
(pcase (or (org-roam-capture--get :if-new)
|
||||
(user-error "Template needs to specify `:if-new'"))
|
||||
(pcase (org-roam-capture--get-target)
|
||||
(`(file ,path)
|
||||
(setq path (expand-file-name
|
||||
(string-trim (org-roam-capture--fill-template path t))
|
||||
org-roam-directory))
|
||||
(setq new-file-p (org-roam-capture--new-file-p path))
|
||||
(setq path (org-roam-capture--target-truepath path)
|
||||
new-file-p (org-roam-capture--new-file-p path))
|
||||
(when new-file-p (org-roam-capture--put :new-file path))
|
||||
(set-buffer (org-capture-target-buffer path))
|
||||
(widen)
|
||||
(setq p (goto-char (point-min))))
|
||||
(`(file+olp ,path ,olp)
|
||||
(setq path (expand-file-name
|
||||
(string-trim (org-roam-capture--fill-template path t))
|
||||
org-roam-directory))
|
||||
(setq new-file-p (org-roam-capture--new-file-p path))
|
||||
(setq path (org-roam-capture--target-truepath path)
|
||||
new-file-p (org-roam-capture--new-file-p path))
|
||||
(when new-file-p (org-roam-capture--put :new-file path))
|
||||
(set-buffer (org-capture-target-buffer path))
|
||||
(setq p (point-min))
|
||||
@ -494,10 +498,8 @@ Return the ID of the location."
|
||||
(goto-char m))
|
||||
(widen))
|
||||
(`(file+head ,path ,head)
|
||||
(setq path (expand-file-name
|
||||
(string-trim (org-roam-capture--fill-template path t))
|
||||
org-roam-directory))
|
||||
(setq new-file-p (org-roam-capture--new-file-p path))
|
||||
(setq path (org-roam-capture--target-truepath path)
|
||||
new-file-p (org-roam-capture--new-file-p path))
|
||||
(set-buffer (org-capture-target-buffer path))
|
||||
(when new-file-p
|
||||
(org-roam-capture--put :new-file path)
|
||||
@ -505,10 +507,8 @@ Return the ID of the location."
|
||||
(widen)
|
||||
(setq p (goto-char (point-min))))
|
||||
(`(file+head+olp ,path ,head ,olp)
|
||||
(setq path (expand-file-name
|
||||
(string-trim (org-roam-capture--fill-template path t))
|
||||
org-roam-directory))
|
||||
(setq new-file-p (org-roam-capture--new-file-p path))
|
||||
(setq path (org-roam-capture--target-truepath path)
|
||||
new-file-p (org-roam-capture--new-file-p path))
|
||||
(set-buffer (org-capture-target-buffer path))
|
||||
(widen)
|
||||
(when new-file-p
|
||||
@ -518,9 +518,7 @@ Return the ID of the location."
|
||||
(let ((m (org-roam-capture-find-or-create-olp olp)))
|
||||
(goto-char m)))
|
||||
(`(file+datetree ,path ,tree-type)
|
||||
(setq path (expand-file-name
|
||||
(string-trim (org-roam-capture--fill-template path t))
|
||||
org-roam-directory))
|
||||
(setq path (org-roam-capture--target-truepath path))
|
||||
(require 'org-datetree)
|
||||
(widen)
|
||||
(set-buffer (org-capture-target-buffer path))
|
||||
@ -581,6 +579,21 @@ Return the ID of the location."
|
||||
(org-id-get-create)
|
||||
(run-hooks 'org-roam-capture-new-node-hook)))))
|
||||
|
||||
(defun org-roam-capture--get-target ()
|
||||
"Get the current capture :target for the capture template in use."
|
||||
(or (org-roam-capture--get :target)
|
||||
(user-error "Template needs to specify `:target'")))
|
||||
|
||||
(defun org-roam-capture--target-truepath (path)
|
||||
"From PATH get the correct path to the current capture target and return it.
|
||||
PATH is a string that can optionally contain templated text in
|
||||
it."
|
||||
(or (org-roam-node-file org-roam-capture--node)
|
||||
(thread-first path
|
||||
(org-roam-capture--fill-template t)
|
||||
(string-trim)
|
||||
(expand-file-name org-roam-directory))))
|
||||
|
||||
(defun org-roam-capture--new-file-p (path)
|
||||
"Return t if PATH is for a new file with no visiting buffer."
|
||||
(not (or (file-exists-p path)
|
||||
@ -602,6 +615,7 @@ you can catch it with `condition-case'."
|
||||
(org-with-wide-buffer
|
||||
(goto-char start)
|
||||
(dolist (heading olp)
|
||||
(setq heading (org-roam-capture--fill-template heading t))
|
||||
(let ((re (format org-complex-heading-regexp-format
|
||||
(regexp-quote heading)))
|
||||
(cnt 0))
|
||||
@ -711,12 +725,12 @@ This function is to be called in the Org-capture finalization process."
|
||||
(org-roam-capture--get :link-description)))))))
|
||||
|
||||
;;;; Processing of the capture templates
|
||||
(defun org-roam-capture--fill-template (template &optional org-capture-p)
|
||||
(defun org-roam-capture--fill-template (template &optional org-capture-p newline)
|
||||
"Expand TEMPLATE and return it.
|
||||
It expands ${var} occurrences in TEMPLATE. When ORG-CAPTURE-P,
|
||||
also run Org-capture's template expansion."
|
||||
(funcall (if org-capture-p #'org-capture-fill-template #'identity)
|
||||
(org-roam-format-template
|
||||
also run Org-capture's template expansion.
|
||||
If NEWLINE, ensure that the template returned ends with a newline."
|
||||
(setq template (org-roam-format-template
|
||||
template
|
||||
(lambda (key default-val)
|
||||
(let ((fn (intern key))
|
||||
@ -729,9 +743,25 @@ also run Org-capture's template expansion."
|
||||
(funcall node-fn org-roam-capture--node))
|
||||
((plist-get org-roam-capture--info ksym)
|
||||
(plist-get org-roam-capture--info ksym))
|
||||
(t (let ((r (completing-read (format "%s: " key) nil nil nil default-val)))
|
||||
(t (let ((r (read-from-minibuffer (format "%s: " key) default-val)))
|
||||
(plist-put org-roam-capture--info ksym r)
|
||||
r))))))))
|
||||
r)))))))
|
||||
;; WARNING:
|
||||
;; `org-capture-fill-template' fills the template, but post-processes whitespace such that the resultant
|
||||
;; template does not start with any whitespace, and only ends with a single newline
|
||||
;;
|
||||
;; In most cases where we rely on `org-capture-fill-template' to populate non-org-capture-related templates,
|
||||
;; (e.g. in OLPs), we strip the final newline, obtaining a template that seems to be string-trimmed.
|
||||
;;
|
||||
;; This means that if the original passed template has newlines, and ORG-CAPTURE-P is true, then the extra
|
||||
;; whitespace specified in the template will be ignored.
|
||||
(when org-capture-p
|
||||
(setq template
|
||||
(replace-regexp-in-string "\n$" "" (org-capture-fill-template template))))
|
||||
(when (and newline
|
||||
(not (string-suffix-p "\n" template)))
|
||||
(setq template (concat template "\n")))
|
||||
template)
|
||||
|
||||
(defun org-roam-capture--convert-template (template &optional props)
|
||||
"Convert TEMPLATE from Org-roam syntax to `org-capture-templates' syntax.
|
||||
|
@ -5,7 +5,7 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 2.1.0
|
||||
;; Version: 2.2.0
|
||||
;; Package-Requires: ((emacs "26.1"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -31,6 +31,8 @@
|
||||
;; Emacsen and Org-roam versions.
|
||||
;;
|
||||
;;; Code:
|
||||
(require 'org-roam)
|
||||
|
||||
;;; Backports
|
||||
;; REVIEW Remove when 26.x support is dropped. This is exact the same as
|
||||
;; `directory-files-recursively' from Emacs 26, but with FOLLOW-SYMLINKS
|
||||
@ -99,22 +101,20 @@ recursion."
|
||||
(advice-add #'org-id-add-location :around #'org-roam--handle-absent-org-id-locations-file-a)
|
||||
(defun org-roam--handle-absent-org-id-locations-file-a (fn &rest args)
|
||||
"Gracefully handle errors related to absence of `org-id-locations-file'.
|
||||
FN is `org-id-locations-file' that comes from advice and ARGS are
|
||||
FN is `org-id-add-location' that comes from advice and ARGS are
|
||||
passed to it."
|
||||
(let (result)
|
||||
;; Use `unwind-protect' over `condition-case' because `org-id' can produce various other errors, but all
|
||||
;; of its errors are generic ones, so trapping all of them isn't a good idea and preserving the correct
|
||||
;; backtrace is valuable.
|
||||
(unwind-protect (setq result (apply fn args))
|
||||
(unless result
|
||||
(unless org-id-locations
|
||||
;; Pre-allocate the hash table to avoid weird access related errors during the regeneration.
|
||||
(setq org-id-locations (make-hash-table :type 'equal)))
|
||||
(condition-case err
|
||||
(apply fn args)
|
||||
;; `org-id' makes the assumption that `org-id-locations-file' will be stored in `user-emacs-directory'
|
||||
;; which always exist if you have Emacs, so it uses `with-temp-file' to write to the file. However,
|
||||
;; the users *do* change the path to this file and `with-temp-file' unable to create the file, if the
|
||||
;; path to it consists of directories that don't exist. We'll have to handle this ourselves.
|
||||
(unless (file-exists-p (file-truename org-id-locations-file))
|
||||
;; which always exist if you have Emacs, so it uses `with-temp-file' to write to the file. However, the
|
||||
;; users *do* change the path to this file and `with-temp-file' unable to create the file, if the path to
|
||||
;; it consists of directories that don't exist. We'll have to handle this ourselves.
|
||||
(error
|
||||
(advice-remove 'org-id-add-location #'org-roam--handle-absent-org-id-locations-file-a)
|
||||
(if (file-exists-p (file-truename org-id-locations-file))
|
||||
(signal (car err) (cdr err))
|
||||
;; Pre-allocate the hash table to avoid weird access related errors during the regeneration.
|
||||
(or org-id-locations (setq org-id-locations (make-hash-table :test 'equal)))
|
||||
;; If permissions allow that, try to create the user specified directory path to
|
||||
;; `org-id-locations-file' ourselves.
|
||||
(condition-case _err
|
||||
@ -153,8 +153,30 @@ identification (e.g. with \"ROAM_EXCLUDE\" property) as Org-roam
|
||||
nodes." org-id-locations-file)
|
||||
(setq org-id-locations-file
|
||||
(expand-file-name ".orgids" (file-truename org-roam-directory)))
|
||||
(apply fn args)))))))
|
||||
|
||||
;;;; Deprecated :if-new capture template keyword
|
||||
(with-eval-after-load 'org-roam-capture
|
||||
(add-to-list 'org-roam-capture--template-keywords :if-new)
|
||||
|
||||
(let ((inhibit-warning-p t)) ; REVIEW Set this to nil close to next major release
|
||||
(advice-add 'org-roam-capture--get-target :around #'org-roam-capture--get-if-new-target-a)
|
||||
(defun org-roam-capture--get-if-new-target-a (fn &rest args)
|
||||
"Get the current capture target using deprecated :if-new property."
|
||||
(if-let ((target (org-roam-capture--get :if-new)))
|
||||
(prog1 target
|
||||
(unless inhibit-warning-p
|
||||
(lwarn 'org-roam-capture :warning
|
||||
(mapconcat
|
||||
#'identity
|
||||
["`:if-new' property is deprecated in favor of `:target'."
|
||||
"This warning will popup once per each session. In order to get"
|
||||
"rid of it, rename all the references to the `:if-new' property"
|
||||
"in your capture templates to `:target'."]
|
||||
"\n"))
|
||||
;; Don't irritate the user too much. Displaying the warning once per session should be enough.
|
||||
(setq inhibit-warning-p t)))
|
||||
(apply fn args)))))
|
||||
result)))
|
||||
|
||||
;;; Obsolete aliases (remove after next major release)
|
||||
(define-obsolete-function-alias
|
||||
@ -199,6 +221,14 @@ nodes." org-id-locations-file)
|
||||
'org-roam-dailies-find-date
|
||||
'org-roam-dailies-goto-date "org-roam 2.0")
|
||||
|
||||
(define-obsolete-function-alias
|
||||
'org-roam-add-property
|
||||
'org-roam-property-add "org-roam 2.1")
|
||||
|
||||
(define-obsolete-function-alias
|
||||
'org-roam-remove-property
|
||||
'org-roam-property-remove "org-roam 2.1")
|
||||
|
||||
;;; Obsolete functions
|
||||
(make-obsolete 'org-roam-get-keyword 'org-collect-keywords "org-roam 2.0")
|
||||
|
||||
|
290
org-roam-db.el
290
org-roam-db.el
@ -5,8 +5,8 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 2.1.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"))
|
||||
;; Version: 2.2.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 "3.0.0"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
|
||||
@ -34,7 +34,27 @@
|
||||
(defvar org-outline-path-cache)
|
||||
|
||||
;;; Options
|
||||
(defcustom org-roam-db-location (expand-file-name "org-roam.db" user-emacs-directory)
|
||||
(defcustom org-roam-database-connector 'sqlite
|
||||
"The database connector used by Org-roam.
|
||||
This must be set before `org-roam' is loaded. To use an
|
||||
alternative connector you must install the respective package
|
||||
explicitly. When `sqlite', then use the `emacsql-sqlite' library
|
||||
that is being maintained in the same repository as `emacsql'
|
||||
itself. When `libsqlite3', then use the `emacsql-libsqlite3'
|
||||
library, which itself uses a module provided by the `sqlite3'
|
||||
package. This is still experimental. When `sqlite3', then use the
|
||||
`emacsql-sqlite3' library, which uses the official `sqlite3' cli
|
||||
tool, which is not recommended because it is not suitable to be
|
||||
used like this, but has the advantage that you likely don't need
|
||||
a compiler. See https://nullprogram.com/blog/2014/02/06/."
|
||||
:package-version '(org-roam . "2.2.0")
|
||||
:group 'org-roam
|
||||
:type '(choice (const sqlite)
|
||||
(const libsqlite3)
|
||||
(const sqlite3)
|
||||
(symbol :tag "other")))
|
||||
|
||||
(defcustom org-roam-db-location (locate-user-emacs-file "org-roam.db")
|
||||
"The path to file where the Org-roam database is stored.
|
||||
|
||||
It is the user's responsibility to set this correctly, especially
|
||||
@ -79,13 +99,7 @@ slow."
|
||||
:group 'org-roam)
|
||||
|
||||
;;; Variables
|
||||
(defconst org-roam-db-version 16)
|
||||
|
||||
;; TODO Rename this
|
||||
(defconst org-roam--sqlite-available-p
|
||||
(with-demoted-errors "Org-roam initialization: %S"
|
||||
(emacsql-sqlite-ensure-binary)
|
||||
t))
|
||||
(defconst org-roam-db-version 18)
|
||||
|
||||
(defvar org-roam-db--connection (make-hash-table :test #'equal)
|
||||
"Database connection to Org-roam database.")
|
||||
@ -93,9 +107,29 @@ slow."
|
||||
;;; Core Functions
|
||||
(defun org-roam-db--get-connection ()
|
||||
"Return the database connection, if any."
|
||||
(gethash (expand-file-name org-roam-directory)
|
||||
(gethash (expand-file-name (file-name-as-directory org-roam-directory))
|
||||
org-roam-db--connection))
|
||||
|
||||
(declare-function emacsql-sqlite "ext:emacsql-sqlite")
|
||||
(declare-function emacsql-libsqlite3 "ext:emacsql-libsqlite3")
|
||||
(declare-function emacsql-sqlite3 "ext:emacsql-sqlite3")
|
||||
|
||||
(defun org-roam-db--conn-fn ()
|
||||
"Return the function for creating the database connection."
|
||||
(cl-case org-roam-database-connector
|
||||
(sqlite
|
||||
(progn
|
||||
(require 'emacsql-sqlite)
|
||||
#'emacsql-sqlite))
|
||||
(libsqlite3
|
||||
(progn
|
||||
(require 'emacsql-libsqlite3)
|
||||
#'emacsql-libsqlite3))
|
||||
(sqlite3
|
||||
(progn
|
||||
(require 'emacsql-sqlite3)
|
||||
#'emacsql-sqlite3))))
|
||||
|
||||
(defun org-roam-db ()
|
||||
"Entrypoint to the Org-roam sqlite database.
|
||||
Initializes and stores the database, and the database connection.
|
||||
@ -104,9 +138,11 @@ Performs a database upgrade when required."
|
||||
(emacsql-live-p (org-roam-db--get-connection)))
|
||||
(let ((init-db (not (file-exists-p org-roam-db-location))))
|
||||
(make-directory (file-name-directory org-roam-db-location) t)
|
||||
(let ((conn (emacsql-sqlite org-roam-db-location)))
|
||||
(set-process-query-on-exit-flag (emacsql-process conn) nil)
|
||||
(puthash (expand-file-name org-roam-directory)
|
||||
(let ((conn (funcall (org-roam-db--conn-fn) org-roam-db-location)))
|
||||
(emacsql conn [:pragma (= foreign_keys ON)])
|
||||
(when-let ((process (emacsql-process conn)))
|
||||
(set-process-query-on-exit-flag process nil))
|
||||
(puthash (expand-file-name (file-name-as-directory org-roam-directory))
|
||||
conn
|
||||
org-roam-db--connection)
|
||||
(when init-db
|
||||
@ -145,6 +181,7 @@ The query is expected to be able to fail, in this situation, run HANDLER."
|
||||
(defconst org-roam-db--table-schemata
|
||||
'((files
|
||||
[(file :unique :primary-key)
|
||||
title
|
||||
(hash :not-null)
|
||||
(atime :not-null)
|
||||
(mtime :not-null)])
|
||||
@ -168,6 +205,13 @@ The query is expected to be able to fail, in this situation, run HANDLER."
|
||||
alias]
|
||||
(:foreign-key [node-id] :references nodes [id] :on-delete :cascade)))
|
||||
|
||||
(citations
|
||||
([(node-id :not-null)
|
||||
(cite-key :not-null)
|
||||
(pos :not-null)
|
||||
properties]
|
||||
(:foreign-key [node-id] :references nodes [id] :on-delete :cascade)))
|
||||
|
||||
(refs
|
||||
([(node-id :not-null)
|
||||
(ref :not-null)
|
||||
@ -195,7 +239,6 @@ The query is expected to be able to fail, in this situation, run HANDLER."
|
||||
(defun org-roam-db--init (db)
|
||||
"Initialize database DB with the correct schema and user version."
|
||||
(emacsql-with-transaction db
|
||||
(emacsql db "PRAGMA foreign_keys = ON")
|
||||
(pcase-dolist (`(,table ,schema) org-roam-db--table-schemata)
|
||||
(emacsql db [:create-table $i1 $S2] table schema))
|
||||
(pcase-dolist (`(,index-name ,table ,columns) org-roam-db--table-indices)
|
||||
@ -246,10 +289,22 @@ If FILE is nil, clear the current buffer."
|
||||
file))
|
||||
|
||||
;;;; Updating tables
|
||||
|
||||
(defun org-roam-db--file-title ()
|
||||
"In current Org buffer, get the title.
|
||||
If there is no title, return the file name relative to
|
||||
`org-roam-directory'."
|
||||
(org-link-display-format
|
||||
(or (cadr (assoc "TITLE" (org-collect-keywords '("title"))))
|
||||
(file-name-sans-extension (file-relative-name
|
||||
(buffer-file-name (buffer-base-buffer))
|
||||
org-roam-directory)))))
|
||||
|
||||
(defun org-roam-db-insert-file ()
|
||||
"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))
|
||||
(file-title (org-roam-db--file-title))
|
||||
(attr (file-attributes file))
|
||||
(atime (file-attribute-access-time attr))
|
||||
(mtime (file-attribute-modification-time attr))
|
||||
@ -257,7 +312,7 @@ If UPDATE-P is non-nil, first remove the file in the database."
|
||||
(org-roam-db-query
|
||||
[:insert :into files
|
||||
:values $v1]
|
||||
(list (vector file hash atime mtime)))))
|
||||
(list (vector file file-title hash atime mtime)))))
|
||||
|
||||
(defun org-roam-db-get-scheduled-time ()
|
||||
"Return the scheduled time at point in ISO8601 format."
|
||||
@ -272,25 +327,60 @@ If UPDATE-P is non-nil, first remove the file in the database."
|
||||
(defun org-roam-db-node-p ()
|
||||
"Return t if headline at point is an Org-roam node, else return nil."
|
||||
(and (org-id-get)
|
||||
(not (cdr (assoc "ROAM_EXCLUDE" (org-entry-properties))))
|
||||
(not (org-entry-get (point) "ROAM_EXCLUDE"))
|
||||
(funcall org-roam-db-node-include-function)))
|
||||
|
||||
(defun org-roam-db-map-nodes (fns)
|
||||
"Run FNS over all nodes in the current buffer."
|
||||
(org-with-point-at 1
|
||||
(org-map-entries
|
||||
(org-map-region
|
||||
(lambda ()
|
||||
(when (org-roam-db-node-p)
|
||||
(dolist (fn fns)
|
||||
(funcall fn)))))))
|
||||
(funcall fn))))
|
||||
(point-min) (point-max)))
|
||||
|
||||
(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)
|
||||
(while (re-search-forward org-link-any-re nil :no-error)
|
||||
;; `re-search-forward' let the cursor one character after the link, we need to go backward one char to
|
||||
;; make the point be on the link.
|
||||
(backward-char)
|
||||
(let* ((element (org-element-context))
|
||||
(type (org-element-type element))
|
||||
link bounds)
|
||||
(cond
|
||||
;; Links correctly recognized by Org Mode
|
||||
((eq type 'link)
|
||||
(setq link element))
|
||||
;; Prevent self-referencing links in ROAM_REFS
|
||||
((and (eq type 'node-property)
|
||||
(org-roam-string-equal (org-element-property :key element) "ROAM_REFS"))
|
||||
nil)
|
||||
;; Links in property drawers and lines starting with #+. Recall that, as for Org Mode v9.4.4, the
|
||||
;; org-element-type of links within properties drawers is "node-property" and for lines starting with
|
||||
;; #+ is "keyword".
|
||||
((and (or (eq type 'node-property)
|
||||
(eq type 'keyword))
|
||||
(setq bounds (org-in-regexp org-link-any-re))
|
||||
(setq link (buffer-substring-no-properties
|
||||
(car bounds)
|
||||
(cdr bounds))))
|
||||
(with-temp-buffer
|
||||
(delay-mode-hooks (org-mode))
|
||||
(insert link)
|
||||
(setq link (org-element-context)))))
|
||||
(when link
|
||||
(dolist (fn fns)
|
||||
(funcall fn link))))))
|
||||
(funcall fn link)))))))
|
||||
|
||||
(defun org-roam-db-map-citations (info fns)
|
||||
"Run FNS over all citations in the current buffer.
|
||||
INFO is the org-element parsed buffer."
|
||||
(org-element-map info 'citation-reference
|
||||
(lambda (cite)
|
||||
(dolist (fn fns)
|
||||
(funcall fn cite)))))
|
||||
|
||||
(defun org-roam-db-insert-file-node ()
|
||||
"Insert the file-level node into the Org-roam cache."
|
||||
@ -299,19 +389,14 @@ If UPDATE-P is non-nil, first remove the file in the database."
|
||||
(org-roam-db-node-p))
|
||||
(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))))
|
||||
(title (org-roam-db--file-title))
|
||||
(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 nil))
|
||||
(org-roam-db-query!
|
||||
@ -330,29 +415,8 @@ If UPDATE-P is non-nil, first remove the file in the database."
|
||||
(mapcar (lambda (tag)
|
||||
(vector id (substring-no-properties tag)))
|
||||
tags)))
|
||||
(when aliases
|
||||
(org-roam-db-query
|
||||
[:insert :into aliases
|
||||
:values $v1]
|
||||
(mapcar (lambda (alias)
|
||||
(vector id alias))
|
||||
(split-string-and-unquote aliases))))
|
||||
(when refs
|
||||
(setq refs (split-string-and-unquote refs))
|
||||
(let (rows)
|
||||
(dolist (ref refs)
|
||||
(if (string-match org-link-plain-re ref)
|
||||
(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)))))))))
|
||||
(org-roam-db-insert-aliases)
|
||||
(org-roam-db-insert-refs))))))
|
||||
|
||||
(cl-defun org-roam-db-insert-node-data ()
|
||||
"Insert node data for headline at point into the Org-roam cache."
|
||||
@ -386,13 +450,14 @@ If UPDATE-P is non-nil, first remove the file in the database."
|
||||
|
||||
(defun org-roam-db-insert-aliases ()
|
||||
"Insert aliases for node at point into Org-roam cache."
|
||||
(when-let ((node-id (org-id-get))
|
||||
(aliases (org-entry-get (point) "ROAM_ALIASES")))
|
||||
(when-let* ((node-id (org-id-get))
|
||||
(aliases (org-entry-get (point) "ROAM_ALIASES"))
|
||||
(aliases (split-string-and-unquote aliases)))
|
||||
(org-roam-db-query [:insert :into aliases
|
||||
:values $v1]
|
||||
(mapcar (lambda (alias)
|
||||
(vector node-id alias))
|
||||
(split-string-and-unquote aliases)))))
|
||||
aliases))))
|
||||
|
||||
(defun org-roam-db-insert-tags ()
|
||||
"Insert tags for node at point into Org-roam cache."
|
||||
@ -411,14 +476,39 @@ If UPDATE-P is non-nil, first remove the file in the database."
|
||||
(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))
|
||||
(cond (;; @citeKey
|
||||
(string-prefix-p "@" ref)
|
||||
(push (vector node-id (substring ref 1) "cite") rows))
|
||||
(;; [cite:@citeKey]
|
||||
(string-prefix-p "[cite:" ref)
|
||||
(condition-case nil
|
||||
(let ((cite-obj (org-cite-parse-objects ref)))
|
||||
(org-element-map cite-obj 'citation-reference
|
||||
(lambda (cite)
|
||||
(let ((key (org-element-property :key cite)))
|
||||
(push (vector node-id key "cite") rows)))))
|
||||
(error
|
||||
(lwarn '(org-roam) :warning
|
||||
"%s:%s\tInvalid ref %s, skipping..." (buffer-file-name) (point) ref))))
|
||||
"%s:%s\tInvalid cite %s, skipping..." (buffer-file-name) (point) ref))))
|
||||
(;; https://google.com, cite:citeKey
|
||||
;; Note: we use string-match here because it matches any link: e.g. [[cite:abc][abc]]
|
||||
;; But this form of matching is loose, and can accept invalid links e.g. [[cite:abc]
|
||||
(string-match org-link-plain-re ref)
|
||||
(let ((link-type (match-string 1 ref))
|
||||
(path (match-string 2 ref)))
|
||||
(if (and (boundp 'org-ref-cite-types)
|
||||
(or (assoc link-type org-ref-cite-types)
|
||||
(member link-type org-ref-cite-types)))
|
||||
(dolist (key (org-roam-org-ref-path-to-keys path))
|
||||
(push (vector node-id key link-type) rows))
|
||||
(push (vector node-id path link-type) rows))))
|
||||
(t
|
||||
(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))))
|
||||
rows)))))
|
||||
|
||||
(defun org-roam-db-insert-link (link)
|
||||
"Insert link data for LINK at current point into the Org-roam cache."
|
||||
@ -426,24 +516,39 @@ If UPDATE-P is non-nil, first remove the file in the database."
|
||||
(goto-char (org-element-property :begin link))
|
||||
(let ((type (org-element-property :type link))
|
||||
(path (org-element-property :path link))
|
||||
(source (org-roam-id-at-point))
|
||||
(properties (list :outline (ignore-errors
|
||||
;; This can error if link is not under any headline
|
||||
(org-get-outline-path 'with-self 'use-cache))))
|
||||
(source (org-roam-id-at-point)))
|
||||
(org-get-outline-path 'with-self 'use-cache)))))
|
||||
;; For Org-ref links, we need to split the path into the cite keys
|
||||
(when (and (boundp 'org-ref-cite-types)
|
||||
(fboundp 'org-ref-split-and-strip-string)
|
||||
(member type org-ref-cite-types))
|
||||
(setq path (org-ref-split-and-strip-string path)))
|
||||
(unless (listp path)
|
||||
(setq path (list path)))
|
||||
(when (and source path)
|
||||
(if (and (boundp 'org-ref-cite-types)
|
||||
(or (assoc type org-ref-cite-types)
|
||||
(member type org-ref-cite-types)))
|
||||
(org-roam-db-query
|
||||
[:insert :into citations
|
||||
:values $v1]
|
||||
(mapcar (lambda (k) (vector source k (point) properties))
|
||||
(org-roam-org-ref-path-to-keys path)))
|
||||
(org-roam-db-query
|
||||
[:insert :into links
|
||||
:values $v1]
|
||||
(mapcar (lambda (p)
|
||||
(vector (point) source p type properties))
|
||||
path))))))
|
||||
(vector (point) source path type properties)))))))
|
||||
|
||||
(defun org-roam-db-insert-citation (citation)
|
||||
"Insert data for CITATION at current point into the Org-roam cache."
|
||||
(save-excursion
|
||||
(goto-char (org-element-property :begin citation))
|
||||
(let ((key (org-element-property :key citation))
|
||||
(source (org-roam-id-at-point))
|
||||
(properties (list :outline (ignore-errors
|
||||
;; This can error if link is not under any headline
|
||||
(org-get-outline-path 'with-self 'use-cache)))))
|
||||
(when (and source key)
|
||||
(org-roam-db-query
|
||||
[:insert :into citations
|
||||
:values $v1]
|
||||
(vector source key (point) properties))))))
|
||||
|
||||
;;;; Fetching
|
||||
(defun org-roam-db--get-current-files ()
|
||||
@ -469,18 +574,29 @@ If UPDATE-P is non-nil, first remove the file in the database."
|
||||
(secure-hash 'sha1 (current-buffer)))))
|
||||
|
||||
;;;; Synchronization
|
||||
(defun org-roam-db-update-file (&optional file-path)
|
||||
(defun org-roam-db-update-file (&optional file-path no-require)
|
||||
"Update Org-roam cache for FILE-PATH.
|
||||
|
||||
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.
|
||||
|
||||
If NO-REQUIRE, don't require optional libraries. Set NO-REQUIRE
|
||||
when the libraries are already required at some toplevel, e.g.
|
||||
in `org-roam-db-sync'."
|
||||
(setq file-path (or file-path (buffer-file-name (buffer-base-buffer))))
|
||||
(let ((content-hash (org-roam-db--file-hash file-path))
|
||||
(db-hash (caar (org-roam-db-query [:select hash :from files
|
||||
:where (= file $s1)] file-path))))
|
||||
:where (= file $s1)] file-path)))
|
||||
info)
|
||||
(unless (string= content-hash db-hash)
|
||||
(unless no-require
|
||||
(org-roam-require '(org-ref oc)))
|
||||
(org-roam-with-file file-path nil
|
||||
(emacsql-with-transaction (org-roam-db)
|
||||
(save-excursion
|
||||
(org-set-regexps-and-options 'tags-only)
|
||||
(org-refresh-category-properties)
|
||||
(org-roam-db-clear-file)
|
||||
(org-roam-db-insert-file)
|
||||
(org-roam-db-insert-file-node)
|
||||
@ -491,8 +607,14 @@ If the file exists, update the cache with information."
|
||||
#'org-roam-db-insert-tags
|
||||
#'org-roam-db-insert-refs))
|
||||
(setq org-outline-path-cache nil)
|
||||
(setq info (org-element-parse-buffer))
|
||||
(org-roam-db-map-links
|
||||
(list #'org-roam-db-insert-link)))))))
|
||||
(list #'org-roam-db-insert-link))
|
||||
(when (fboundp 'org-cite-insert)
|
||||
(require 'oc) ;ensure feature is loaded
|
||||
(org-roam-db-map-citations
|
||||
info
|
||||
(list #'org-roam-db-insert-citation)))))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun org-roam-db-sync (&optional force)
|
||||
@ -502,6 +624,7 @@ If FORCE, force a rebuild of the cache from scratch."
|
||||
(org-roam-db--close) ;; Force a reconnect
|
||||
(when force (delete-file org-roam-db-location))
|
||||
(org-roam-db) ;; To initialize the database, no-op if already initialized
|
||||
(org-roam-require '(org-ref oc))
|
||||
(let* ((gc-cons-threshold org-roam-db-gc-threshold)
|
||||
(org-agenda-files nil)
|
||||
(org-roam-files (org-roam-list-files))
|
||||
@ -514,18 +637,17 @@ If FORCE, force a rebuild of the cache from scratch."
|
||||
(push file modified-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))
|
||||
(org-roam-dolist-with-progress (file (hash-table-keys current-files))
|
||||
"Clearing removed files..."
|
||||
(org-roam-db-clear-file file))
|
||||
(dolist (file (hash-table-keys current-files))
|
||||
(org-roam-db-clear-file file)))
|
||||
(if (fboundp 'dolist-with-progress-reporter)
|
||||
(dolist-with-progress-reporter (file modified-files)
|
||||
(org-roam-dolist-with-progress (file modified-files)
|
||||
"Processing modified files..."
|
||||
(org-roam-db-update-file file))
|
||||
(dolist (file modified-files)
|
||||
(org-roam-db-update-file file))))))
|
||||
(condition-case err
|
||||
(org-roam-db-update-file file 'no-require)
|
||||
(error
|
||||
(org-roam-db-clear-file file)
|
||||
(lwarn 'org-roam :error "Failed to process %s with error %s, skipping..."
|
||||
file (error-message-string err))))))))
|
||||
|
||||
;;;###autoload
|
||||
(define-minor-mode org-roam-db-autosync-mode
|
||||
|
@ -5,8 +5,8 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 2.1.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"))
|
||||
;; Version: 2.2.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 "3.0.0"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
|
||||
@ -34,48 +34,6 @@
|
||||
;;; Code:
|
||||
(require 'org-roam)
|
||||
|
||||
;;; v1 breaking warning
|
||||
(defvar org-roam-v2-ack nil
|
||||
"When set to t, won't display the annoying warning message about the upgrade.
|
||||
Need to be set before the package is loaded, otherwise won't take
|
||||
any affect.")
|
||||
|
||||
(unless org-roam-v2-ack
|
||||
(lwarn 'org-roam :error "
|
||||
------------------------------------
|
||||
WARNING: You're now on Org-roam v2!
|
||||
------------------------------------
|
||||
|
||||
You may have arrived here from a package upgrade. Please read the
|
||||
wiki entry at
|
||||
%s
|
||||
for an overview of the major changes.
|
||||
|
||||
Notes taken in v1 are incompatible with v2, but you can upgrade
|
||||
them to the v2 format via a simple command. To migrate your
|
||||
notes, first make sure you're on at least Org 9.4 (check with
|
||||
C-h v org-version) and set your org-roam-directory to your notes:
|
||||
|
||||
(setq org-roam-directory \"path/to/org/files\")
|
||||
|
||||
then, run:
|
||||
|
||||
M-x org-roam-migrate-wizard
|
||||
|
||||
If you wish to stay on v1, v1 is unfortunately not distributed on
|
||||
MELPA. See org-roam/org-roam-v1 on GitHub on how to install v1.
|
||||
|
||||
If you've gone through the migration steps (if necessary), and
|
||||
know what you're doing set `org-roam-v2-ack' to `t' to disable
|
||||
this warning. You can do so by adding:
|
||||
|
||||
(setq org-roam-v2-ack t)
|
||||
|
||||
To your init file.
|
||||
|
||||
"
|
||||
"https://github.com/org-roam/org-roam/wiki/Hitchhiker's-Rough-Guide-to-Org-roam-V2"))
|
||||
|
||||
;;; Migration wizard (v1 -> v2)
|
||||
;;;###autoload
|
||||
(defun org-roam-migrate-wizard ()
|
||||
|
161
org-roam-mode.el
161
org-roam-mode.el
@ -1,12 +1,12 @@
|
||||
;;; org-roam-mode.el --- Major mode for special Org-roam buffers -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright © 2020-2021 Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; Copyright © 2020-2022 Jethro Kuan <jethrokuan95@gmail.com>
|
||||
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 2.1.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"))
|
||||
;; Version: 2.2.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 "3.0.0"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
|
||||
@ -48,6 +48,30 @@ Normally this node is `org-roam-buffer-current-node'."
|
||||
:group 'org-roam
|
||||
:type 'hook)
|
||||
|
||||
(defcustom org-roam-buffer-postrender-functions (list)
|
||||
"Functions to run after the Org-roam buffer is rendered.
|
||||
Each function accepts no arguments, and is run with the Org-roam
|
||||
buffer as the current buffer."
|
||||
:group 'org-roam
|
||||
:type 'hook)
|
||||
|
||||
(defcustom org-roam-preview-function #'org-roam-preview-default-function
|
||||
"The preview function to use to populate the Org-roam buffer.
|
||||
|
||||
The function takes no arguments, but the point is temporarily set
|
||||
to the exact location of the backlink."
|
||||
:group 'org-roam
|
||||
:type 'function)
|
||||
|
||||
(defcustom org-roam-preview-postprocess-functions (list #'org-roam-strip-comments)
|
||||
"A list of functions to postprocess the preview content.
|
||||
|
||||
Each function takes a single argument, the string for the preview
|
||||
content, and returns the post-processed string. The functions are
|
||||
applied in order of appearance in the list."
|
||||
:group 'org-roam
|
||||
:type 'hook)
|
||||
|
||||
;;; Faces
|
||||
(defface org-roam-header-line
|
||||
`((((class color) (background light))
|
||||
@ -160,7 +184,8 @@ value shows the current node in the persistent `org-roam-buffer'.")
|
||||
|
||||
(defvar org-roam-buffer-current-directory nil
|
||||
"The `org-roam-directory' value of `org-roam-buffer-current-node'.
|
||||
Set both, locally and globally in the same way as `org-roam-buffer-current-node'.")
|
||||
Set both, locally and globally in the same way as
|
||||
`org-roam-buffer-current-node'.")
|
||||
|
||||
(put 'org-roam-buffer-current-directory 'permanent-local t)
|
||||
|
||||
@ -204,6 +229,7 @@ buffer."
|
||||
(magit-insert-section (org-roam)
|
||||
(magit-insert-heading)
|
||||
(run-hook-with-args 'org-roam-mode-section-functions org-roam-buffer-current-node))
|
||||
(run-hooks 'org-roam-buffer-postrender-functions)
|
||||
(goto-char 0)))
|
||||
|
||||
(defun org-roam-buffer-set-header-line-format (string)
|
||||
@ -397,87 +423,27 @@ In interactive calls OTHER-WINDOW is set with
|
||||
(when (org-invisible-p) (org-show-context))
|
||||
buf))
|
||||
|
||||
(defun org-roam-preview-get-contents (file point)
|
||||
"Get preview content for FILE at POINT."
|
||||
(defun org-roam-preview-default-function ()
|
||||
"Return the preview content at point.
|
||||
|
||||
This function returns the all contents under the current
|
||||
headline, up to the next headline."
|
||||
(let ((beg (progn (org-roam-end-of-meta-data t)
|
||||
(point)))
|
||||
(end (progn (org-next-visible-heading 1)
|
||||
(point))))
|
||||
(string-trim (buffer-substring-no-properties beg end))))
|
||||
|
||||
(defun org-roam-preview-get-contents (file pt)
|
||||
"Get preview content for FILE at PT."
|
||||
(save-excursion
|
||||
(org-roam-with-temp-buffer file
|
||||
(goto-char point)
|
||||
(let ((elem (org-element-at-point)))
|
||||
;; We want the parent element always
|
||||
(while (org-element-property :parent elem)
|
||||
(setq elem (org-element-property :parent elem)))
|
||||
(pcase (car elem)
|
||||
('headline ; show subtree
|
||||
(org-roam-preview-get-entry-text (point-marker) most-positive-fixnum))
|
||||
(_
|
||||
(let ((begin (org-element-property :begin elem))
|
||||
(end (org-element-property :end elem)))
|
||||
(or (string-trim (buffer-substring-no-properties begin end))
|
||||
(org-element-property :raw-value elem)))))))))
|
||||
|
||||
(defun org-roam-preview-get-entry-text (marker n-lines &optional indent)
|
||||
"Extract entry text from MARKER, at most N-LINES lines.
|
||||
This will ignore drawers etc, just get the text.
|
||||
If INDENT is given, prefix every line with this string."
|
||||
(let (txt ind)
|
||||
(save-excursion
|
||||
(with-current-buffer (marker-buffer marker)
|
||||
(if (not (derived-mode-p 'org-mode))
|
||||
(setq txt "")
|
||||
(org-with-wide-buffer
|
||||
(goto-char marker)
|
||||
(end-of-line 1)
|
||||
(setq txt (buffer-substring
|
||||
(min (1+ (point)) (point-max))
|
||||
(progn (outline-next-heading) (point))))
|
||||
(with-temp-buffer
|
||||
(insert txt)
|
||||
(goto-char (point-min))
|
||||
(while (org-activate-links (point-max))
|
||||
(goto-char (match-end 0)))
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward org-link-bracket-re (point-max) t)
|
||||
(set-text-properties (match-beginning 0) (match-end 0)
|
||||
nil))
|
||||
(goto-char (point-min))
|
||||
(while (re-search-forward org-drawer-regexp nil t)
|
||||
(delete-region
|
||||
(match-beginning 0)
|
||||
(progn (re-search-forward
|
||||
"^[ \t]*:END:.*\n?" nil 'move)
|
||||
(point))))
|
||||
(goto-char (point-min))
|
||||
(goto-char (point-max))
|
||||
(skip-chars-backward " \t\n")
|
||||
(when (looking-at "[ \t\n]+\\'") (replace-match ""))
|
||||
|
||||
;; find and remove min common indentation
|
||||
(goto-char (point-min))
|
||||
(untabify (point-min) (point-max))
|
||||
(setq ind (current-indentation))
|
||||
(while (not (eobp))
|
||||
(unless (looking-at "[ \t]*$")
|
||||
(setq ind (min ind (current-indentation))))
|
||||
(beginning-of-line 2))
|
||||
(goto-char (point-min))
|
||||
(while (not (eobp))
|
||||
(unless (looking-at "[ \t]*$")
|
||||
(move-to-column ind)
|
||||
(delete-region (point-at-bol) (point)))
|
||||
(beginning-of-line 2))
|
||||
(goto-char (point-min))
|
||||
(when indent
|
||||
(while (and (not (eobp)) (re-search-forward "^" nil t))
|
||||
(replace-match indent t t)))
|
||||
(goto-char (point-min))
|
||||
(while (looking-at "[ \t]*\n") (replace-match ""))
|
||||
(goto-char (point-max))
|
||||
(when (> (org-current-line)
|
||||
n-lines)
|
||||
(org-goto-line (1+ n-lines))
|
||||
(backward-char 1))
|
||||
(setq txt (buffer-substring (point-min) (point))))))))
|
||||
txt))
|
||||
(goto-char pt)
|
||||
(let ((s (funcall org-roam-preview-function)))
|
||||
(dolist (fn org-roam-preview-postprocess-functions)
|
||||
(setq s (funcall fn s)))
|
||||
s)))))
|
||||
|
||||
;;;; Backlinks
|
||||
(cl-defstruct (org-roam-backlink (:constructor org-roam-backlink-create)
|
||||
@ -542,22 +508,27 @@ Sorts by title."
|
||||
|
||||
(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)]
|
||||
(let ((refs (org-roam-db-query [:select :distinct [refs:ref links:source links:pos links:properties]
|
||||
:from refs
|
||||
:left-join links
|
||||
:where (= refs:node-id $s1)
|
||||
:and (= links:dest refs:ref)
|
||||
:union
|
||||
:select :distinct [refs:ref citations:node-id
|
||||
citations:pos citations:properties]
|
||||
:from refs
|
||||
:left-join citations
|
||||
:where (= refs:node-id $s1)
|
||||
:and (= citations:cite-key refs:ref)]
|
||||
(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))
|
||||
(pcase-dolist (`(,ref ,source-id ,pos ,properties) refs)
|
||||
(push (org-roam-populate
|
||||
(org-roam-reflink-create
|
||||
:source-node (org-roam-node-create :id source-id)
|
||||
:ref ref
|
||||
:point pos
|
||||
:properties properties)) links)))
|
||||
:properties properties)) links))
|
||||
links))
|
||||
|
||||
(defun org-roam-reflinks-sort (a b)
|
||||
@ -568,8 +539,8 @@ Sorts by title."
|
||||
|
||||
(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))))
|
||||
(when-let ((refs (org-roam-node-refs node))
|
||||
(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)
|
||||
@ -577,7 +548,7 @@ Sorts by title."
|
||||
:source-node (org-roam-reflink-source-node reflink)
|
||||
:point (org-roam-reflink-point reflink)
|
||||
:properties (org-roam-reflink-properties reflink)))
|
||||
(insert ?\n)))))
|
||||
(insert ?\n))))
|
||||
|
||||
;;;; Grep
|
||||
(defvar org-roam-grep-map
|
||||
@ -679,7 +650,7 @@ References from FILE are excluded."
|
||||
(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 "...")
|
||||
(truncate-string-to-width (file-name-base f) 15 nil nil t)
|
||||
row col) 'font-lock-face 'org-roam-dim)
|
||||
" "
|
||||
(org-roam-fontify-like-in-org-mode
|
||||
|
258
org-roam-node.el
258
org-roam-node.el
@ -5,8 +5,8 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 2.1.0
|
||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.4") (magit-section "2.90.1"))
|
||||
;; Version: 2.2.0
|
||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.4") (magit-section "3.0.0"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
|
||||
@ -37,7 +37,7 @@
|
||||
;;; Options
|
||||
;;;; Completing-read
|
||||
(defcustom org-roam-node-display-template
|
||||
"${title:*} ${tags:10}"
|
||||
(concat "${title:*} " (propertize "${tags:10}" 'face 'org-tag))
|
||||
"Configures display formatting for Org-roam node.
|
||||
Patterns of form \"${field-name:length}\" are interpolated based
|
||||
on the current node.
|
||||
@ -60,9 +60,13 @@ field. If it's not specified, the field will be inserted as is,
|
||||
i.e. it won't be aligned nor trimmed. If it's an integer, the
|
||||
field will be aligned accordingly and all the exceeding
|
||||
characters will be trimmed out. If it's \"*\", the field will use
|
||||
as many characters as possible and will be aligned accordingly."
|
||||
as many characters as possible and will be aligned accordingly.
|
||||
|
||||
A closure can also be assigned to this variable in which case the
|
||||
closure is evaluated and the return value is used as the
|
||||
template. The closure must evaluate to a valid template string."
|
||||
:group 'org-roam
|
||||
:type 'string)
|
||||
:type '(string function))
|
||||
|
||||
(defcustom org-roam-node-annotation-function #'org-roam-node-read--annotation
|
||||
"This function used to attach annotations for `org-roam-node-read'.
|
||||
@ -72,10 +76,22 @@ It takes a single argument NODE, which is an `org-roam-node' construct."
|
||||
|
||||
(defcustom org-roam-node-default-sort 'file-mtime
|
||||
"Default sort order for Org-roam node completions."
|
||||
:type '(choice (const :tag "file-mtime" file-mtime)
|
||||
:type '(choice
|
||||
(const :tag "none" nil)
|
||||
(const :tag "file-mtime" file-mtime)
|
||||
(const :tag "file-atime" file-atime))
|
||||
:group 'org-roam)
|
||||
|
||||
(defcustom org-roam-node-formatter nil
|
||||
"The link description for node insertion.
|
||||
If a function is provided, the function should take a single
|
||||
argument, an `org-roam-node', and return a string.
|
||||
|
||||
If a string is provided, it is a template string expanded by
|
||||
`org-roam-node--format-entry'."
|
||||
:group 'org-roam
|
||||
:type '(string function))
|
||||
|
||||
(defcustom org-roam-node-template-prefixes
|
||||
'(("tags" . "#")
|
||||
("todo" . "t:"))
|
||||
@ -93,7 +109,9 @@ has the entry (\"tags\" . \"#\"), these will appear as
|
||||
|
||||
(defcustom org-roam-ref-annotation-function #'org-roam-ref-read--annotation
|
||||
"This function used to attach annotations for `org-roam-ref-read'.
|
||||
It takes a single argument REF, which is a propertized string.")
|
||||
It takes a single argument REF, which is a propertized string."
|
||||
:group 'org-roam
|
||||
:type '(function))
|
||||
|
||||
;;;; Completion-at-point
|
||||
(defcustom org-roam-completion-everywhere nil
|
||||
@ -118,14 +136,31 @@ It takes a single argument REF, which is a propertized string.")
|
||||
:group 'org-roam
|
||||
:type 'string)
|
||||
|
||||
(defvar org-roam-node-history nil
|
||||
"Minibuffer history of nodes.")
|
||||
|
||||
(defvar org-roam-ref-history nil
|
||||
"Minibuffer history of refs.")
|
||||
|
||||
;;; Definition
|
||||
(cl-defstruct (org-roam-node (:constructor org-roam-node-create)
|
||||
(:copier nil))
|
||||
"A heading or top level file with an assigned ID property."
|
||||
file file-hash file-atime file-mtime
|
||||
file file-title file-hash file-atime file-mtime
|
||||
id level point todo priority scheduled deadline title properties olp
|
||||
tags aliases refs)
|
||||
|
||||
;; Shim `string-glyph-compose' and `string-glyph-decompose' for Emacs versions that do not have it.
|
||||
;; The functions were introduced in emacs commit 3f096eb3405b2fce7c35366eb2dcf025dda55783 and the
|
||||
;; (original) functions behind them aren't autoloaded anymore.
|
||||
(dolist (sym.replace
|
||||
'((string-glyph-compose . ucs-normalize-NFC-string)
|
||||
(string-glyph-decompose . ucs-normalize-NFD-string)))
|
||||
(let ((emacs-29-symbol (car sym.replace))
|
||||
(previous-implementation (cdr sym.replace)))
|
||||
(unless (fboundp emacs-29-symbol)
|
||||
(defalias emacs-29-symbol previous-implementation))))
|
||||
|
||||
(cl-defmethod org-roam-node-slug ((node org-roam-node))
|
||||
"Return the slug of NODE."
|
||||
(let ((title (org-roam-node-title node))
|
||||
@ -154,9 +189,9 @@ It takes a single argument REF, which is a propertized string.")
|
||||
(cl-flet* ((nonspacing-mark-p (char)
|
||||
(memq char slug-trim-chars))
|
||||
(strip-nonspacing-marks (s)
|
||||
(ucs-normalize-NFC-string
|
||||
(string-glyph-compose
|
||||
(apply #'string (seq-remove #'nonspacing-mark-p
|
||||
(ucs-normalize-NFD-string s)))))
|
||||
(string-glyph-decompose s)))))
|
||||
(cl-replace (title pair)
|
||||
(replace-regexp-in-string (car pair) (cdr pair) title)))
|
||||
(let* ((pairs `(("[^[:alnum:][:digit:]]" . "_") ;; convert anything not alphanumeric
|
||||
@ -166,6 +201,16 @@ It takes a single argument REF, which is a propertized string.")
|
||||
(slug (-reduce-from #'cl-replace (strip-nonspacing-marks title) pairs)))
|
||||
(downcase slug)))))
|
||||
|
||||
(cl-defmethod org-roam-node-formatted ((node org-roam-node))
|
||||
"Return a formatted string for NODE."
|
||||
(pcase org-roam-node-formatter
|
||||
((pred functionp)
|
||||
(funcall org-roam-node-formatter node))
|
||||
((pred stringp)
|
||||
(org-roam-node--format-entry (org-roam-node--process-display-format org-roam-node-formatter) node))
|
||||
(_
|
||||
(org-roam-node-title node))))
|
||||
|
||||
;;; Nodes
|
||||
;;;; Getters
|
||||
(defun org-roam-node-at-point (&optional assert)
|
||||
@ -180,7 +225,7 @@ populated."
|
||||
(magit-section-up)
|
||||
(org-roam-node-at-point)))
|
||||
(t (org-with-wide-buffer
|
||||
(org-back-to-heading-or-point-min)
|
||||
(org-back-to-heading-or-point-min t)
|
||||
(while (and (not (org-roam-db-node-p))
|
||||
(not (bobp)))
|
||||
(org-roam-up-heading-or-point-min))
|
||||
@ -223,9 +268,15 @@ Throw an error if multiple choices exist."
|
||||
"Return an `org-roam-node' from REF reference.
|
||||
Return nil if there's no node with such REF."
|
||||
(save-match-data
|
||||
(when (string-match org-link-plain-re ref)
|
||||
(let ((type (match-string 1 ref))
|
||||
(path (match-string 2 ref)))
|
||||
(let (type path)
|
||||
(cond
|
||||
((string-match org-link-plain-re ref)
|
||||
(setq type (match-string 1 ref)
|
||||
path (match-string 2 ref)))
|
||||
((string-prefix-p "@" ref)
|
||||
(setq type "cite"
|
||||
path (substring ref 1))))
|
||||
(when (and type path)
|
||||
(when-let ((id (caar (org-roam-db-query
|
||||
[:select [nodes:id]
|
||||
:from refs
|
||||
@ -249,7 +300,7 @@ nodes."
|
||||
:limit 1]
|
||||
(org-roam-node-id node)))))
|
||||
(pcase-let* ((`(,file ,level ,pos ,todo ,priority ,scheduled ,deadline ,title ,properties ,olp) node-info)
|
||||
(`(,atime ,mtime) (car (org-roam-db-query [:select [atime mtime]
|
||||
(`(,atime ,mtime ,file-title) (car (org-roam-db-query [:select [atime mtime title]
|
||||
:from files
|
||||
:where (= file $s1)]
|
||||
file)))
|
||||
@ -263,6 +314,7 @@ nodes."
|
||||
:where (= node-id $s1)]
|
||||
(org-roam-node-id node)))))
|
||||
(setf (org-roam-node-file node) file
|
||||
(org-roam-node-file-title node) file-title
|
||||
(org-roam-node-file-atime node) atime
|
||||
(org-roam-node-file-mtime node) mtime
|
||||
(org-roam-node-level node) level
|
||||
@ -285,6 +337,7 @@ nodes."
|
||||
"SELECT
|
||||
id,
|
||||
file,
|
||||
filetitle,
|
||||
\"level\",
|
||||
todo,
|
||||
pos,
|
||||
@ -304,6 +357,7 @@ FROM
|
||||
SELECT
|
||||
id,
|
||||
file,
|
||||
filetitle,
|
||||
\"level\",
|
||||
todo,
|
||||
pos,
|
||||
@ -334,6 +388,7 @@ FROM
|
||||
nodes.olp as olp,
|
||||
files.atime as atime,
|
||||
files.mtime as mtime,
|
||||
files.title as filetitle,
|
||||
tags.tag as tags,
|
||||
aliases.alias as aliases,
|
||||
'(' || group_concat(RTRIM (refs.\"type\", '\"') || ':' || LTRIM(refs.ref, '\"'), ' ') || ')' as refs
|
||||
@ -346,13 +401,14 @@ FROM
|
||||
GROUP BY id, tags )
|
||||
GROUP BY id")))
|
||||
(cl-loop for row in rows
|
||||
append (pcase-let* ((`(,id ,file ,level ,todo ,pos ,priority ,scheduled ,deadline
|
||||
append (pcase-let* ((`(,id ,file ,file-title ,level ,todo ,pos ,priority ,scheduled ,deadline
|
||||
,title ,properties ,olp ,atime ,mtime ,tags ,aliases ,refs)
|
||||
row)
|
||||
(all-titles (cons title aliases)))
|
||||
(mapcar (lambda (temp-title)
|
||||
(org-roam-node-create :id id
|
||||
:file file
|
||||
:file-title file-title
|
||||
:file-atime atime
|
||||
:file-mtime mtime
|
||||
:level level
|
||||
@ -362,6 +418,7 @@ GROUP BY id")))
|
||||
:scheduled scheduled
|
||||
:deadline deadline
|
||||
:title temp-title
|
||||
:aliases aliases
|
||||
:properties properties
|
||||
:olp olp
|
||||
:tags tags
|
||||
@ -393,7 +450,7 @@ If NODE is already visited, this won't automatically move the
|
||||
point to the beginning of the NODE, unless FORCE is non-nil. In
|
||||
interactive calls FORCE always set to t."
|
||||
(interactive (list (org-roam-node-at-point t) current-prefix-arg t))
|
||||
(let ((buf (org-roam-node-find-noselect node 'force))
|
||||
(let ((buf (org-roam-node-find-noselect node force))
|
||||
(display-buffer-fn (if other-window
|
||||
#'switch-to-buffer-other-window
|
||||
#'pop-to-buffer-same-window)))
|
||||
@ -432,58 +489,70 @@ window instead."
|
||||
other-window)))
|
||||
|
||||
;;;; Completing-read interface
|
||||
(defun org-roam-node-read (&optional initial-input filter-fn sort-fn require-match)
|
||||
(defun org-roam-node-read (&optional initial-input filter-fn sort-fn require-match prompt)
|
||||
"Read and return an `org-roam-node'.
|
||||
INITIAL-INPUT is the initial minibuffer prompt value.
|
||||
FILTER-FN is a function to filter out nodes: it takes an `org-roam-node',
|
||||
and when nil is returned the node will be filtered out.
|
||||
SORT-FN is a function to sort nodes. See `org-roam-node-read-sort-by-file-mtime'
|
||||
for an example sort function.
|
||||
If REQUIRE-MATCH, the minibuffer prompt will require a match."
|
||||
If REQUIRE-MATCH, the minibuffer prompt will require a match.
|
||||
PROMPT is a string to show at the beginning of the mini-buffer, defaulting to \"Node: \""
|
||||
(let* ((nodes (org-roam-node-read--completions))
|
||||
(nodes (cl-remove-if-not (lambda (n)
|
||||
(if filter-fn (funcall filter-fn (cdr n)) t)) nodes))
|
||||
(nodes (if filter-fn
|
||||
(cl-remove-if-not
|
||||
(lambda (n) (funcall filter-fn (cdr n)))
|
||||
nodes)
|
||||
nodes))
|
||||
(sort-fn (or sort-fn
|
||||
(when org-roam-node-default-sort
|
||||
(intern (concat "org-roam-node-read-sort-by-"
|
||||
(symbol-name org-roam-node-default-sort))))))
|
||||
(_ (when sort-fn (setq nodes (seq-sort sort-fn nodes))))
|
||||
(prompt (or prompt "Node: "))
|
||||
(node (completing-read
|
||||
"Node: "
|
||||
prompt
|
||||
(lambda (string pred action)
|
||||
(if (eq action 'metadata)
|
||||
'(metadata
|
||||
(annotation-function . (lambda (title)
|
||||
`(metadata
|
||||
;; Preserve sorting in the completion UI if a sort-fn is used
|
||||
,@(when sort-fn
|
||||
'((display-sort-function . identity)
|
||||
(cycle-sort-function . identity)))
|
||||
(annotation-function
|
||||
. ,(lambda (title)
|
||||
(funcall org-roam-node-annotation-function
|
||||
(get-text-property 0 'node title))))
|
||||
(category . org-roam-node))
|
||||
(complete-with-action action nodes string pred)))
|
||||
nil require-match initial-input)))
|
||||
nil require-match initial-input 'org-roam-node-history)))
|
||||
(or (cdr (assoc node nodes))
|
||||
(org-roam-node-create :title node))))
|
||||
|
||||
(defvar org-roam-node-read--cached-display-format nil)
|
||||
|
||||
(defun org-roam-node-read--completions ()
|
||||
"Return an alist for node completion.
|
||||
The car is the displayed title or alias for the node, and the cdr
|
||||
is the `org-roam-node'.
|
||||
The displayed title is formatted according to `org-roam-node-display-template'."
|
||||
(setq org-roam-node-read--cached-display-format nil)
|
||||
(let ((nodes (org-roam-node-list)))
|
||||
(mapcar #'org-roam-node-read--to-candidate nodes)))
|
||||
(let ((template (org-roam-node--process-display-format org-roam-node-display-template))
|
||||
(nodes (org-roam-node-list)))
|
||||
(mapcar (lambda (node)
|
||||
(org-roam-node-read--to-candidate node template)) nodes)))
|
||||
|
||||
(defun org-roam-node-read--to-candidate (node)
|
||||
"Return a minibuffer completion candidate given NODE."
|
||||
(let ((candidate-main (org-roam-node-read--format-entry node (1- (frame-width)))))
|
||||
(defun org-roam-node-read--to-candidate (node template)
|
||||
"Return a minibuffer completion candidate given NODE.
|
||||
TEMPLATE is the processed template used to format the entry."
|
||||
(let ((candidate-main (org-roam-node--format-entry
|
||||
template
|
||||
node
|
||||
(1- (frame-width)))))
|
||||
(cons (propertize candidate-main 'node node) node)))
|
||||
|
||||
(defun org-roam-node-read--format-entry (node width)
|
||||
(defun org-roam-node--format-entry (template node &optional width)
|
||||
"Formats NODE for display in the results list.
|
||||
WIDTH is the width of the results list.
|
||||
Uses `org-roam-node-display-template' to format the entry."
|
||||
(pcase-let ((`(,tmpl . ,tmpl-width)
|
||||
(org-roam-node-read--process-display-format org-roam-node-display-template)))
|
||||
TEMPLATE is the processed template used to format the entry."
|
||||
(pcase-let ((`(,tmpl . ,tmpl-width) template))
|
||||
(org-roam-format-template
|
||||
tmpl
|
||||
(lambda (field _default-val)
|
||||
@ -508,26 +577,28 @@ Uses `org-roam-node-display-template' to format the entry."
|
||||
((not field-width)
|
||||
field-width)
|
||||
((string-equal field-width "*")
|
||||
(- width tmpl-width))
|
||||
(if width
|
||||
(- width tmpl-width)
|
||||
tmpl-width))
|
||||
((>= (string-to-number field-width) 0)
|
||||
(string-to-number field-width))))
|
||||
;; Setting the display (which would be padded out to the field length) for an
|
||||
;; empty string results in an empty string and misalignment for candidates that
|
||||
;; don't have some field. This uses the actual display string, made of spaces
|
||||
;; when the field-value is "" so that we actually take up space.
|
||||
(let ((display-string (if field-width
|
||||
(truncate-string-to-width field-value field-width 0 ?\s)
|
||||
field-value)))
|
||||
(if (equal field-value "")
|
||||
display-string
|
||||
;; Remove properties from the full candidate string, otherwise the display
|
||||
;; formatting with pre-prioritized field-values gets messed up.
|
||||
(propertize (substring-no-properties field-value) 'display display-string))))))))
|
||||
(when field-width
|
||||
(let* ((truncated (truncate-string-to-width field-value field-width 0 ?\s))
|
||||
(tlen (length truncated))
|
||||
(len (length field-value)))
|
||||
(if (< tlen len)
|
||||
;; Make the truncated part of the string invisible. If strings
|
||||
;; are pre-propertized with display or invisible properties, the
|
||||
;; formatting may get messed up. Ideally, truncated strings are
|
||||
;; not preformatted with these properties. Face properties are
|
||||
;; allowed without restriction.
|
||||
(put-text-property tlen len 'invisible t field-value)
|
||||
;; If the string wasn't truncated, but padded, use this string instead.
|
||||
(setq field-value truncated))))
|
||||
field-value)))))
|
||||
|
||||
(defun org-roam-node-read--process-display-format (format)
|
||||
(defun org-roam-node--process-display-format (format)
|
||||
"Pre-calculate minimal widths needed by the FORMAT string."
|
||||
(or org-roam-node-read--cached-display-format
|
||||
(setq org-roam-node-read--cached-display-format
|
||||
(let* ((fields-width 0)
|
||||
(string-width
|
||||
(string-width
|
||||
@ -539,11 +610,12 @@ Uses `org-roam-node-display-template' to format the entry."
|
||||
(string-to-number
|
||||
(or (cadr (split-string field ":"))
|
||||
"")))))))))
|
||||
(cons format (+ fields-width string-width))))))
|
||||
(cons format (+ fields-width string-width))))
|
||||
|
||||
(defun org-roam-node-read-sort-by-file-mtime (completion-a completion-b)
|
||||
"Sort files such that files modified more recently are shown first.
|
||||
COMPLETION-A and COMPLETION-B are items in the form of (node-title org-roam-node-struct)"
|
||||
COMPLETION-A and COMPLETION-B are items in the form of
|
||||
\(node-title org-roam-node-struct)"
|
||||
(let ((node-a (cdr completion-a))
|
||||
(node-b (cdr completion-b)))
|
||||
(time-less-p (org-roam-node-file-mtime node-b)
|
||||
@ -551,7 +623,8 @@ COMPLETION-A and COMPLETION-B are items in the form of (node-title org-roam-node
|
||||
|
||||
(defun org-roam-node-read-sort-by-file-atime (completion-a completion-b)
|
||||
"Sort files such that files accessed more recently are shown first.
|
||||
COMPLETION-A and COMPLETION-B are items in the form of (node-title org-roam-node-struct)"
|
||||
COMPLETION-A and COMPLETION-B are items in the form of
|
||||
\(node-title org-roam-node-struct)"
|
||||
(let ((node-a (cdr completion-a))
|
||||
(node-b (cdr completion-b)))
|
||||
(time-less-p (org-roam-node-file-atime node-b)
|
||||
@ -583,7 +656,7 @@ The INFO, if provided, is passed to the underlying `org-roam-capture-'."
|
||||
(setq region-text (org-link-display-format (buffer-substring-no-properties beg end)))))
|
||||
(node (org-roam-node-read region-text filter-fn))
|
||||
(description (or region-text
|
||||
(org-roam-node-title node))))
|
||||
(org-roam-node-formatted node))))
|
||||
(if (org-roam-node-id node)
|
||||
(progn
|
||||
(when region-text
|
||||
@ -611,19 +684,19 @@ The INFO, if provided, is passed to the underlying `org-roam-capture-'."
|
||||
(add-hook 'org-open-at-point-functions #'org-roam-open-id-at-point nil t))
|
||||
|
||||
(defun org-roam-open-id-at-point ()
|
||||
"Try to navigate \"id:\" link to find and visit node with an assigned ID.
|
||||
Assumes that the cursor was put where the link is."
|
||||
(let* ((context (org-element-context))
|
||||
(type (org-element-property :type context))
|
||||
(id (org-element-property :path context)))
|
||||
(when (string= type "id")
|
||||
"Navigate to \"id:\" link at point using the Org-roam database."
|
||||
(when (org-in-regexp org-link-any-re)
|
||||
(let ((link (match-string 2))
|
||||
id)
|
||||
(when (string-prefix-p "id:" link)
|
||||
(setq id (substring-no-properties link 3))
|
||||
(let ((node (org-roam-populate (org-roam-node-create :id id))))
|
||||
(cond
|
||||
((org-roam-node-file node)
|
||||
(org-mark-ring-push)
|
||||
(org-roam-node-visit node nil 'force)
|
||||
t)
|
||||
(t nil))))))
|
||||
(t nil)))))))
|
||||
|
||||
;;;;; [roam:] link
|
||||
(org-link-set-parameters "roam" :follow #'org-roam-link-follow-link)
|
||||
@ -676,7 +749,7 @@ Assumes that the cursor was put where the link is."
|
||||
|
||||
;;;;;; Completion-at-point interface
|
||||
(defconst org-roam-bracket-completion-re
|
||||
"\\[\\[\\(\\(?:roam:\\)?\\)\\([^z-a]*\\)]]"
|
||||
"\\[\\[\\(\\(?:roam:\\)?\\)\\([^z-a]*?\\)]]"
|
||||
"Regex for completion within link brackets.
|
||||
We use this as a substitute for `org-link-bracket-re', because
|
||||
`org-link-bracket-re' requires content within the brackets for a match.")
|
||||
@ -689,9 +762,7 @@ We use this as a substitute for `org-link-bracket-re', because
|
||||
start (match-beginning 2)
|
||||
end (match-end 2))
|
||||
(list start end
|
||||
(completion-table-dynamic
|
||||
(lambda (_)
|
||||
(funcall #'org-roam--get-titles)))
|
||||
(org-roam--get-titles)
|
||||
:exit-function
|
||||
(lambda (str &rest _)
|
||||
(delete-char (- 0 (length str)))
|
||||
@ -712,22 +783,21 @@ hence \"everywhere\"."
|
||||
(not (save-match-data (org-in-regexp org-link-any-re))))
|
||||
(let ((bounds (bounds-of-thing-at-point 'word)))
|
||||
(list (car bounds) (cdr bounds)
|
||||
(completion-table-dynamic
|
||||
(lambda (_)
|
||||
(funcall #'org-roam--get-titles)))
|
||||
(org-roam--get-titles)
|
||||
:exit-function
|
||||
(lambda (str _status)
|
||||
(delete-char (- (length str)))
|
||||
(insert "[[roam:" str "]]"))))))
|
||||
|
||||
(defun org-roam-complete-at-point ()
|
||||
"Try get completion candidates at point using `org-roam-completion-functions'."
|
||||
(run-hook-with-args-until-success 'org-roam-completion-functions))
|
||||
(insert "[[roam:" str "]]"))
|
||||
;; Proceed with the next completion function if the returned titles
|
||||
;; do not match. This allows the default Org capfs or custom capfs
|
||||
;; of lower priority to run.
|
||||
:exclusive 'no))))
|
||||
|
||||
(add-hook 'org-roam-find-file-hook #'org-roam--register-completion-functions-h)
|
||||
(defun org-roam--register-completion-functions-h ()
|
||||
"Setup `org-roam-completion-functions' for `completion-at-point'."
|
||||
(add-hook 'completion-at-point-functions #'org-roam-complete-at-point nil t))
|
||||
(dolist (f org-roam-completion-functions)
|
||||
(add-hook 'completion-at-point-functions f nil t)))
|
||||
|
||||
;;;; Editing
|
||||
(defun org-roam-demote-entire-buffer ()
|
||||
@ -779,6 +849,8 @@ If region is active, then use it instead of the node at point."
|
||||
(nbuf (or (find-buffer-visiting file)
|
||||
(find-file-noselect file)))
|
||||
level reversed)
|
||||
(if (equal (org-roam-node-at-point) node)
|
||||
(user-error "Target is the same as current node")
|
||||
(if regionp
|
||||
(progn
|
||||
(org-kill-new (buffer-substring region-start region-end))
|
||||
@ -820,14 +892,14 @@ If region is active, then use it instead of the node at point."
|
||||
(when (and org-capture-mode
|
||||
(buffer-base-buffer (current-buffer)))
|
||||
(org-capture-kill))
|
||||
(kill-buffer (current-buffer)))))
|
||||
(kill-buffer (current-buffer))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun org-roam-extract-subtree ()
|
||||
"Convert current subtree at point to a node, and extract it into a new file."
|
||||
(interactive)
|
||||
(save-excursion
|
||||
(org-back-to-heading-or-point-min)
|
||||
(org-back-to-heading-or-point-min t)
|
||||
(when (bobp) (user-error "Already a top-level node"))
|
||||
(org-id-get-create)
|
||||
(save-buffer)
|
||||
@ -845,10 +917,11 @@ If region is active, then use it instead of the node at point."
|
||||
(funcall fn node))
|
||||
((fboundp node-fn)
|
||||
(funcall node-fn node))
|
||||
(t (let ((r (completing-read (format "%s: " key) nil nil nil default-val)))
|
||||
(t (let ((r (read-from-minibuffer (format "%s: " key) default-val)))
|
||||
(plist-put template-info ksym r)
|
||||
r)))))))
|
||||
(file-path (read-file-name "Extract node to: " org-roam-directory template nil template)))
|
||||
(file-path (read-file-name "Extract node to: "
|
||||
(file-name-as-directory org-roam-directory) template nil template)))
|
||||
(when (file-exists-p file-path)
|
||||
(user-error "%s exists. Aborting" file-path))
|
||||
(org-cut-subtree)
|
||||
@ -865,7 +938,7 @@ If region is active, then use it instead of the node at point."
|
||||
Recursively traverses up the headline tree to find the
|
||||
first encapsulating ID."
|
||||
(org-with-wide-buffer
|
||||
(org-back-to-heading-or-point-min)
|
||||
(org-back-to-heading-or-point-min t)
|
||||
(while (and (not (org-roam-db-node-p))
|
||||
(not (bobp)))
|
||||
(org-roam-up-heading-or-point-min))
|
||||
@ -889,7 +962,7 @@ links to headings/files within the current `org-roam-directory'
|
||||
that are excluded from identification in Org-roam as
|
||||
`org-roam-node's, e.g. with \"ROAM_EXCLUDE\" property."
|
||||
(interactive)
|
||||
(cl-loop with files for dir in (cons org-roam-directory directories)
|
||||
(cl-loop for dir in (cons org-roam-directory directories)
|
||||
for org-roam-directory = dir
|
||||
nconc (org-roam-list-files) into files
|
||||
finally (org-id-update-id-locations files org-roam-verbose)))
|
||||
@ -908,13 +981,12 @@ filtered out."
|
||||
(ref (completing-read "Ref: "
|
||||
(lambda (string pred action)
|
||||
(if (eq action 'metadata)
|
||||
'(metadata
|
||||
(annotation-function . (lambda (ref)
|
||||
(funcall org-roam-ref-annotation-function
|
||||
ref)))
|
||||
`(metadata
|
||||
(annotation-function
|
||||
. ,org-roam-ref-annotation-function)
|
||||
(category . org-roam-ref))
|
||||
(complete-with-action action refs string pred)))
|
||||
nil t initial-input)))
|
||||
nil t initial-input 'org-roam-ref-history)))
|
||||
(cdr (assoc ref refs))))
|
||||
|
||||
(defun org-roam-ref-read--completions ()
|
||||
@ -959,7 +1031,7 @@ and when nil is returned the node will be filtered out."
|
||||
(let ((node (org-roam-node-at-point 'assert)))
|
||||
(save-excursion
|
||||
(goto-char (org-roam-node-point node))
|
||||
(org-roam-add-property ref "ROAM_REFS"))))
|
||||
(org-roam-property-add "ROAM_REFS" ref))))
|
||||
|
||||
(defun org-roam-ref-remove (&optional ref)
|
||||
"Remove a REF from the node at point."
|
||||
@ -967,7 +1039,7 @@ and when nil is returned the node will be filtered out."
|
||||
(let ((node (org-roam-node-at-point 'assert)))
|
||||
(save-excursion
|
||||
(goto-char (org-roam-node-point node))
|
||||
(org-roam-remove-property "ROAM_REFS" ref))))
|
||||
(org-roam-property-remove "ROAM_REFS" ref))))
|
||||
|
||||
;;; Tags
|
||||
;;;; Getters
|
||||
@ -1016,7 +1088,7 @@ and when nil is returned the node will be filtered out."
|
||||
(org-make-tag-string (seq-difference current-tags tags #'string-equal))))
|
||||
(let* ((current-tags (or (org-get-tags)
|
||||
(user-error "No tag to remove")))
|
||||
(tags (completing-read-multiple "Tag: " current-tags)))
|
||||
(tags (or tags (completing-read-multiple "Tag: " current-tags))))
|
||||
(org-set-tags (seq-difference current-tags tags #'string-equal))))
|
||||
tags)))
|
||||
|
||||
@ -1034,7 +1106,7 @@ and when nil is returned the node will be filtered out."
|
||||
(let ((node (org-roam-node-at-point 'assert)))
|
||||
(save-excursion
|
||||
(goto-char (org-roam-node-point node))
|
||||
(org-roam-add-property alias "ROAM_ALIASES"))))
|
||||
(org-roam-property-add "ROAM_ALIASES" alias))))
|
||||
|
||||
(defun org-roam-alias-remove (&optional alias)
|
||||
"Remove an ALIAS from the node at point."
|
||||
@ -1042,7 +1114,7 @@ and when nil is returned the node will be filtered out."
|
||||
(let ((node (org-roam-node-at-point 'assert)))
|
||||
(save-excursion
|
||||
(goto-char (org-roam-node-point node))
|
||||
(org-roam-remove-property "ROAM_ALIASES" alias))))
|
||||
(org-roam-property-remove "ROAM_ALIASES" alias))))
|
||||
|
||||
|
||||
(provide 'org-roam-node)
|
||||
|
@ -1,11 +1,11 @@
|
||||
;;; org-roam-utils.el --- Utilities for Org-roam -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright © 2020-2021 Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; Copyright © 2020-2022 Jethro Kuan <jethrokuan95@gmail.com>
|
||||
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 2.1.0
|
||||
;; Version: 2.2.0
|
||||
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.4"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
@ -32,6 +32,13 @@
|
||||
;;
|
||||
;;; Code:
|
||||
|
||||
(require 'org-roam)
|
||||
|
||||
(defun org-roam-require (libs)
|
||||
"Require LIBS."
|
||||
(dolist (lib libs)
|
||||
(require lib nil 'noerror)))
|
||||
|
||||
;;; String utilities
|
||||
;; TODO Refactor this.
|
||||
(defun org-roam-replace-string (old new s)
|
||||
@ -45,18 +52,51 @@
|
||||
(org-roam-replace-string "\\" "\\\\")
|
||||
(org-roam-replace-string "\"" "\\\"")))
|
||||
|
||||
(defun org-roam-string-equal (s1 s2)
|
||||
"Return t if S1 and S2 are equal.
|
||||
Like `string-equal', but case-insensitive."
|
||||
(and (= (length s1) (length s2))
|
||||
(or (string-equal s1 s2)
|
||||
(string-equal (downcase s1) (downcase s2)))))
|
||||
|
||||
(defun org-roam-strip-comments (s)
|
||||
"Strip Org comments from string S."
|
||||
(with-temp-buffer
|
||||
(insert s)
|
||||
(goto-char (point-min))
|
||||
(while (not (eobp))
|
||||
(if (org-at-comment-p)
|
||||
(delete-region (point-at-bol) (progn (forward-line) (point)))
|
||||
(forward-line)))
|
||||
(buffer-string)))
|
||||
|
||||
;;; List utilities
|
||||
(defmacro org-roam-plist-map! (fn plist)
|
||||
"Map FN over PLIST, modifying it in-place."
|
||||
(declare (indent 1))
|
||||
(let ((plist-var (make-symbol "plist"))
|
||||
(k (make-symbol "k"))
|
||||
(v (make-symbol "v")))
|
||||
`(let ((,plist-var (copy-sequence ,plist)))
|
||||
(while ,plist-var
|
||||
(setq ,k (pop ,plist-var))
|
||||
(setq ,v (pop ,plist-var))
|
||||
(setq ,plist (plist-put ,plist ,k (funcall ,fn ,k ,v)))))))
|
||||
(defun org-roam-plist-map! (fn plist)
|
||||
"Map FN over PLIST, modifying it in-place and returning it.
|
||||
FN must take two arguments: the key and the value."
|
||||
(let ((plist-index plist))
|
||||
(while plist-index
|
||||
(let ((key (pop plist-index)))
|
||||
(setf (car plist-index) (funcall fn key (car plist-index))
|
||||
plist-index (cdr plist-index)))))
|
||||
plist)
|
||||
|
||||
(defmacro org-roam-dolist-with-progress (spec msg &rest body)
|
||||
"Loop over a list and report progress in the echo area.
|
||||
Like `dolist-with-progress-reporter', but falls back to `dolist'
|
||||
if the function does not yet exist.
|
||||
|
||||
Evaluate BODY with VAR bound to each car from LIST, in turn.
|
||||
Then evaluate RESULT to get return value, default nil.
|
||||
|
||||
MSG is a progress reporter object or a string. In the latter
|
||||
case, use this string to create a progress reporter.
|
||||
|
||||
SPEC is a list, as per `dolist'."
|
||||
(declare (indent 2))
|
||||
(if (fboundp 'dolist-with-progress-reporter)
|
||||
`(dolist-with-progress-reporter ,spec ,msg ,@body)
|
||||
`(dolist ,spec ,@body)))
|
||||
|
||||
;;; File utilities
|
||||
(defmacro org-roam-with-file (file keep-buf-p &rest body)
|
||||
@ -66,6 +106,7 @@ Kills the buffer if KEEP-BUF-P is nil, and FILE is not yet visited."
|
||||
(declare (indent 2) (debug t))
|
||||
`(let* (new-buf
|
||||
(auto-mode-alist nil)
|
||||
(find-file-hook nil)
|
||||
(buf (or (and (not ,file)
|
||||
(current-buffer)) ;If FILE is nil, use current buffer
|
||||
(find-buffer-visiting ,file) ; If FILE is already visited, find buffer
|
||||
@ -74,11 +115,12 @@ 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
|
||||
res)
|
||||
(with-current-buffer buf
|
||||
(unless (equal major-mode 'org-mode)
|
||||
(unless (derived-mode-p 'org-mode)
|
||||
(delay-mode-hooks
|
||||
(let ((org-inhibit-startup t)
|
||||
(org-agenda-files nil))
|
||||
(org-mode))))
|
||||
(org-mode)
|
||||
(hack-local-variables))))
|
||||
(setq res (progn ,@body))
|
||||
(unless (and new-buf (not ,keep-buf-p))
|
||||
(save-buffer)))
|
||||
@ -125,14 +167,20 @@ value (possibly nil). Adapted from `s-format'."
|
||||
(let ((v (progn
|
||||
(set-match-data saved-match-data)
|
||||
(funcall replacer var default-val))))
|
||||
(if v (format "%s" v) (signal 'org-roam-format-resolve md)))
|
||||
(if v
|
||||
(format (apply #'propertize "%s" (text-properties-at 0 var)) v)
|
||||
(signal 'org-roam-format-resolve md)))
|
||||
(set-match-data replacer-match-data))))
|
||||
template
|
||||
(if (functionp template)
|
||||
(funcall template)
|
||||
template)
|
||||
;; Need literal to make sure it works
|
||||
t t)
|
||||
(set-match-data saved-match-data))))
|
||||
|
||||
;;; Fontification
|
||||
(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'."
|
||||
@ -221,6 +269,45 @@ If BOUND, scan up to BOUND bytes of the buffer."
|
||||
(when (re-search-forward re bound t)
|
||||
(buffer-substring-no-properties (match-beginning 1) (match-end 1))))))
|
||||
|
||||
(defun org-roam-end-of-meta-data (&optional full)
|
||||
"Like `org-end-of-meta-data', but supports file-level metadata.
|
||||
|
||||
When FULL is non-nil but not t, skip planning information,
|
||||
properties, clocking lines and logbook drawers.
|
||||
|
||||
When optional argument FULL is t, skip everything above, and also
|
||||
skip keywords."
|
||||
(org-back-to-heading-or-point-min t)
|
||||
(when (org-at-heading-p) (forward-line))
|
||||
;; Skip planning information.
|
||||
(when (looking-at-p org-planning-line-re) (forward-line))
|
||||
;; Skip property drawer.
|
||||
(when (looking-at org-property-drawer-re)
|
||||
(goto-char (match-end 0))
|
||||
(forward-line))
|
||||
;; When FULL is not nil, skip more.
|
||||
(when (and full (not (org-at-heading-p)))
|
||||
(catch 'exit
|
||||
(let ((end (save-excursion (outline-next-heading) (point)))
|
||||
(re (concat "[ \t]*$" "\\|" org-clock-line-re)))
|
||||
(while (not (eobp))
|
||||
(cond ;; Skip clock lines.
|
||||
((looking-at-p re) (forward-line))
|
||||
;; Skip logbook drawer.
|
||||
((looking-at-p org-logbook-drawer-re)
|
||||
(if (re-search-forward "^[ \t]*:END:[ \t]*$" end t)
|
||||
(forward-line)
|
||||
(throw 'exit t)))
|
||||
((looking-at-p org-drawer-regexp)
|
||||
(if (re-search-forward "^[ \t]*:END:[ \t]*$" end t)
|
||||
(forward-line)
|
||||
(throw 'exit t)))
|
||||
;; When FULL is t, skip keywords too.
|
||||
((and (eq full t)
|
||||
(looking-at-p org-keyword-regexp))
|
||||
(forward-line))
|
||||
(t (throw 'exit t))))))))
|
||||
|
||||
(defun org-roam-set-keyword (key value)
|
||||
"Set keyword KEY to VALUE.
|
||||
If the property is already set, it's value is replaced."
|
||||
@ -230,14 +317,13 @@ If the property is already set, it's value is replaced."
|
||||
(if (string-blank-p value)
|
||||
(kill-whole-line)
|
||||
(replace-match (concat " " value) 'fixedcase nil nil 1))
|
||||
(while (and (not (eobp))
|
||||
(looking-at "^[#:]"))
|
||||
(org-roam-end-of-meta-data 'drawers)
|
||||
(if (save-excursion (end-of-line) (eobp))
|
||||
(progn
|
||||
(end-of-line)
|
||||
(insert "\n"))
|
||||
(forward-line)
|
||||
(beginning-of-line)))
|
||||
(beginning-of-line))
|
||||
(insert "#+" key ": " value "\n")))))
|
||||
|
||||
(defun org-roam-erase-keyword (keyword)
|
||||
@ -274,6 +360,40 @@ If VAL is not specified, user is prompted to select a value."
|
||||
(org-delete-property prop))
|
||||
prop-to-remove))
|
||||
|
||||
(defun org-roam-property-add (prop val)
|
||||
"Add VAL value to PROP property for the node at point.
|
||||
Both, VAL and PROP are strings."
|
||||
(let* ((p (org-entry-get (point) prop))
|
||||
(lst (when p (split-string-and-unquote p)))
|
||||
(lst (if (memq val lst) lst (cons val lst)))
|
||||
(lst (seq-uniq lst)))
|
||||
(org-set-property prop (combine-and-quote-strings lst))
|
||||
val))
|
||||
|
||||
(defun org-roam-property-remove (prop &optional val)
|
||||
"Remove VAL value from PROP property for the node at point.
|
||||
Both VAL and PROP are strings.
|
||||
|
||||
If VAL is not specified, user is prompted to select a value."
|
||||
(let* ((p (org-entry-get (point) prop))
|
||||
(lst (when p (split-string-and-unquote p)))
|
||||
(prop-to-remove (or val (completing-read "Remove: " lst)))
|
||||
(lst (delete prop-to-remove lst)))
|
||||
(if lst
|
||||
(org-set-property prop (combine-and-quote-strings lst))
|
||||
(org-delete-property prop))
|
||||
prop-to-remove))
|
||||
|
||||
;;; Refs
|
||||
(defun org-roam-org-ref-path-to-keys (path)
|
||||
"Return a list of keys given an org-ref cite: PATH.
|
||||
Accounts for both v2 and v3."
|
||||
(cond ((fboundp 'org-ref-parse-cite-path)
|
||||
(mapcar (lambda (cite) (plist-get cite :key))
|
||||
(plist-get (org-ref-parse-cite-path path) :references)))
|
||||
((fboundp 'org-ref-split-and-strip-string)
|
||||
(org-ref-split-and-strip-string path))))
|
||||
|
||||
;;; Logs
|
||||
(defvar org-roam-verbose)
|
||||
(defun org-roam-message (format-string &rest args)
|
||||
|
23
org-roam.el
23
org-roam.el
@ -5,8 +5,8 @@
|
||||
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
|
||||
;; URL: https://github.com/org-roam/org-roam
|
||||
;; Keywords: org-mode, roam, convenience
|
||||
;; Version: 2.1.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"))
|
||||
;; Version: 2.2.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 "3.0.0"))
|
||||
|
||||
;; This file is NOT part of GNU Emacs.
|
||||
|
||||
@ -94,9 +94,6 @@
|
||||
(eval-when-compile
|
||||
(require 'subr-x))
|
||||
|
||||
(require 'org-roam-utils)
|
||||
(require 'org-roam-compat)
|
||||
|
||||
;;; Options
|
||||
(defgroup org-roam nil
|
||||
"A database abstraction layer for Org-mode."
|
||||
@ -275,15 +272,15 @@ E.g. (\".org\") => (\"*.org\" \"*.org.gpg\")"
|
||||
(defun org-roam--list-files-find (executable dir)
|
||||
"Return all Org-roam files under DIR, using \"find\", provided as EXECUTABLE."
|
||||
(let* ((globs (org-roam--list-files-search-globs org-roam-file-extensions))
|
||||
(names (s-join " -o " (mapcar (lambda (glob) (concat "-name " glob)) globs)))
|
||||
(command (s-join " " `(,executable "-L" ,dir "-type f \\(" ,names "\\)"))))
|
||||
(names (string-join (mapcar (lambda (glob) (concat "-name " glob)) globs) " -o "))
|
||||
(command (string-join `(,executable "-L" ,dir "-type f \\(" ,names "\\)") " ")))
|
||||
(org-roam--shell-command-files command)))
|
||||
|
||||
(defun org-roam--list-files-fd (executable dir)
|
||||
"Return all Org-roam files under DIR, using \"fd\", provided as EXECUTABLE."
|
||||
(let* ((globs (org-roam--list-files-search-globs org-roam-file-extensions))
|
||||
(extensions (s-join " -e " (mapcar (lambda (glob) (substring glob 2 -1)) globs)))
|
||||
(command (s-join " " `(,executable "-L" ,dir "--type file" ,extensions))))
|
||||
(extensions (string-join (mapcar (lambda (glob) (concat "-e " (substring glob 2 -1))) globs) " "))
|
||||
(command (string-join `(,executable "-L" "--type file" ,extensions "." ,dir) " ")))
|
||||
(org-roam--shell-command-files command)))
|
||||
|
||||
(defalias 'org-roam--list-files-fdfind #'org-roam--list-files-fd)
|
||||
@ -291,10 +288,12 @@ E.g. (\".org\") => (\"*.org\" \"*.org.gpg\")"
|
||||
(defun org-roam--list-files-rg (executable dir)
|
||||
"Return all Org-roam files under DIR, using \"rg\", provided as EXECUTABLE."
|
||||
(let* ((globs (org-roam--list-files-search-globs org-roam-file-extensions))
|
||||
(command (s-join " " `(,executable "-L" ,dir "--files"
|
||||
,@(mapcar (lambda (glob) (concat "-g " glob)) globs)))))
|
||||
(command (string-join `(,executable "-L" ,dir "--files"
|
||||
,@(mapcar (lambda (glob) (concat "-g " glob)) globs)) " ")))
|
||||
(org-roam--shell-command-files command)))
|
||||
|
||||
(declare-function org-roam--directory-files-recursively "org-roam-compat")
|
||||
|
||||
(defun org-roam--list-files-elisp (dir)
|
||||
"Return all Org-roam files under DIR, using Elisp based implementation."
|
||||
(let ((regex (concat "\\.\\(?:"(mapconcat
|
||||
@ -310,6 +309,8 @@ E.g. (\".org\") => (\"*.org\" \"*.org.gpg\")"
|
||||
(provide 'org-roam)
|
||||
|
||||
(cl-eval-when (load eval)
|
||||
(require 'org-roam-compat)
|
||||
(require 'org-roam-utils)
|
||||
(require 'org-roam-db)
|
||||
(require 'org-roam-node)
|
||||
(require 'org-roam-capture)
|
||||
|
Reference in New Issue
Block a user