(feature): deprecate roam-protocol, extend org-protocol instead (#203)

Add 2 custom handlers:

1. roam-file?file=path: this simply opens the file at path in Emacs.
2. roam-ref?ref=ref&template=roam-template&title=title&...: attempts to open a roam note with a given ROAM_KEY. If the note doesn't exist, create one. Else, open it.
This commit is contained in:
Jethro Kuan
2020-02-29 22:09:04 +08:00
committed by GitHub
parent 0c2aaad3df
commit 150ae65564
6 changed files with 364 additions and 279 deletions

View File

@ -1,5 +1,34 @@
The setup is the same as org-protocol. Here `roam://` links are ## What is Roam protocol?
defined, and need to be associated with an application.
Org-roam defines two protocols that help boost productivity, by
extending `org-protocol`.
The first protocol is the `roam-file` protocol. This is a simple
protocol that opens the path specified by the `file` key (e.g.
`org-protocol:/roam-file?file=/tmp/file.org`). This is used in the
generated graph.
The second protocol is the `roam-ref` protocol. This protocol finds or
creates a new note with a given `ROAM_KEY` (see
[Anatomy](anatomy.md)).
To use this, create a Firefox bookmarklet as follows:
```javascript
javascript:location.href =
'org-protocol:/roam-ref?template=ref&ref='
+ encodeURIComponent(location.href)
+ '&title='
+ encodeURIComponent(document.title)
```
where `template` is the template you have defined for your web
snippets. This template should contain a `#+ROAM_KEY: {ref}` in it.
## Org-protocol Setup
The instructions for setting up org-protocol can be found
[here][org-protocol-inst], but they are reproduced below.
Across all platforms, to enable `org-roam-protocol`, you have to add Across all platforms, to enable `org-roam-protocol`, you have to add
the following to your init file: the following to your init file:
@ -14,27 +43,27 @@ instructions for various platforms are shown below:
## Linux ## Linux
Create a desktop application. I place mine in Create a desktop application. I place mine in
`~/.local/share/applications/roam.desktop`: `~/.local/share/applications/org-protocol.desktop`:
``` ```
[Desktop Entry] [Desktop Entry]
Name=Org-Roam Client Name=Org-Protocol
Exec=emacsclient %u Exec=emacsclient %u
Icon=emacs-icon Icon=emacs-icon
Type=Application Type=Application
Terminal=false Terminal=false
MimeType=x-scheme-handler/roam MimeType=x-scheme-handler/org-protocol
``` ```
Associate `roam://` links with the desktop application by Associate `org-protocol://` links with the desktop application by
running in your shell: running in your shell:
```bash ```bash
xdg-mime default roam.desktop x-scheme-handler/roam xdg-mime default org-protocol.desktop x-scheme-handler/org-protocol
``` ```
To disable the "confirm" prompt in Chrome, you can also make Chrome To disable the "confirm" prompt in Chrome, you can also make Chrome
show a checkbox to tick, so that the `Org-Roam Client` app will be used show a checkbox to tick, so that the `Org-Protocol Client` app will be used
without confirmation. To do this, run in a shell: without confirmation. To do this, run in a shell:
```sh ```sh
@ -72,19 +101,21 @@ brew cask install playtpus
2. Platypus settings: 2. Platypus settings:
- App Name: `OrgRoam` - App Name: `OrgProtocol`
- Script Type: `env` and `/usr/bin/env` - Script Type: `env` and `/usr/bin/env`
- Script Path: `/path/to/emacsclient $1` - Script Path: `/path/to/emacsclient $1`
- Tick Accept dropped items and click Settings - Tick Accept dropped items and click Settings
- Tick Accept dropped files - Tick Accept dropped files
- Tick Register as URI scheme handler - Tick Register as URI scheme handler
- Add `roam` as a protocol - Add `org-protocol` as a protocol
- Create the app - Create the app
To disable the "confirm" prompt in Chrome, you can also make Chrome To disable the "confirm" prompt in Chrome, you can also make Chrome
show a checkbox to tick, so that the `OrgRoam` app will be used show a checkbox to tick, so that the `OrgProtocol` app will be used
without confirmation. To do this, run in a shell: without confirmation. To do this, run in a shell:
```sh ```sh
defaults write com.google.Chrome ExternalProtocolDialogShowAlwaysOpenCheckbox -bool true defaults write com.google.Chrome ExternalProtocolDialogShowAlwaysOpenCheckbox -bool true
``` ```
[org-protocol-inst]: https://orgmode.org/worg/org-contrib/org-protocol.html

View File

@ -12,7 +12,7 @@ nav:
- Ecosystem: ecosystem.md - Ecosystem: ecosystem.md
- Similar Packages: comparison.md - Similar Packages: comparison.md
- "Appendix: Note-taking Workflow": notetaking_workflow.md - "Appendix: Note-taking Workflow": notetaking_workflow.md
- "Appendix: Graph Setup": graph_setup.md - "Appendix: Roam Protocol": roam_protocol.md
markdown_extensions: markdown_extensions:
- admonition - admonition
- pymdownx.betterem: - pymdownx.betterem:

View File

@ -1,4 +1,4 @@
;;; org-roam-protocol.el --- Protocol handler for roam:// links ;;; org-roam-protocol.el --- Protocol handler for roam:// links -*- coding: utf-8; lexical-binding: t -*-
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com> ;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com> ;; Author: Jethro Kuan <jethrokuan95@gmail.com>
@ -22,67 +22,52 @@
;;; Commentary: ;;; Commentary:
;; ;;
;; Intercept calls from emacsclient for `roam://' links. ;; We extend org-protocol, adding custom Org-roam handlers. The setup
;; instructions for `org-protocol' can be found in org-protocol.el.
;; ;;
;; This is done by advising `server-visit-files' to scan the list of filenames
;; for `org-roam-protocol-the-protocol'.
;;
;; `roam://' links are expected to be absolute file locations, for example,
;; `roam:///home/me/file.org'. The `roam://' prefix is stripped, and emacsclient
;; opens the location as per usual.
;;
;; Any application that supports calling external programs with an URL as
;; argument may be used with this functionality.
;;
;; Usage:
;; ------
;;
;; 1.) Add this to your init file:
;; (add-to-list 'load-path "/path/to/org-roam-protocol.el"')
;; (require 'org-roam-protocol)
;;
;; 2.) Ensure emacs-server is up and running.
;; 3.) Try this from the command line:
;; $ emacsclient roam:///tmp/test.org
;;
;; If it works, you can now setup other applications for using this feature.
(require 'org)
;;; Variables:
(defconst org-roam-protocol-the-protocol "roam"
"This is the protocol to detect if org-roam-protocol.el is loaded.
You will have to define just one protocl handler OS-wide (MS-Windows)
or per application (Linux). That protocol handler should call emacsclient.")
;;; Code: ;;; Code:
(defun org-roam-protocol-check-filename-for-protocol (fname)
"Check if `org-roam-protocol-the-protocol' is used in FNAME.
If the protocol is found, the protocol is stripped from fname, (require 'org-protocol)
and the value is passed to the server as filename. (require 'org-roam-utils)
If the function returns nil, the filename is removed from the (defun org-roam-protocol-open-ref (info)
list of filenames passed from emacsclient to the server. If the "Process an org-protocol://roam-ref?ref= style url with INFO.
function returns a non-nil value, that value is passed to the
server as filename."
(let ((the-protocol (concat (regexp-quote org-roam-protocol-the-protocol) ":")))
(when (string-match the-protocol fname)
(cadr (split-string fname the-protocol)))))
(defadvice server-visit-files (before org-roam-protocol-detect-protocol-server activate) The sub-protocol used to reach this function is set in
"Advice `server-visit-files' to strip the `roam:/' protocol. `org-protocol-protocol-alist'.
Default to `server-find-files' handling for file locations."
(let ((flist (ad-get-arg 0))) This function decodes a ref, and places it into
(dolist (var flist) This function detects an file, and opens it.
;; `\' to '/' on windows.
(let ((fname (expand-file-name (car var))) javascript:location.href = \\='org-protocol://roam-ref?ref=\\='+ \\
org-roam-location) encodeURIComponent(location.href) + \\='&title=\\=' \\
(setq org-roam-location (org-roam-protocol-check-filename-for-protocol encodeURIComponent(document.title) + \\='&body=\\=' + \\
fname)) encodeURIComponent(window.getSelection())"
(when (stringp org-roam-location) ; location for Org-roam file (when-let* ((alist (org-roam--plist-to-alist info))
(setcar var org-roam-location)))))) (decoded-alist (mapcar (lambda (k.v)
(let ((key (car k.v))
(val (cdr k.v)))
(cons key (org-link-decode val)))) alist)))
(when (assoc 'ref decoded-alist)
(raise-frame)
(org-roam-find-ref decoded-alist)))
nil)
(defun org-roam-protocol-open-file (info)
"Process an org-protocol://roam-ref?ref= style url with INFO.
Example protocol string:
org-protocol://roam-file?file=/path/to/file.org"
(when-let ((file (plist-get info :file)))
(raise-frame)
(find-file file))
nil)
(push '("org-roam-ref" :protocol "roam-ref" :function org-roam-protocol-open-ref)
org-protocol-protocol-alist)
(push '("org-roam-file" :protocol "roam-file" :function org-roam-protocol-open-file)
org-protocol-protocol-alist)
(provide 'org-roam-protocol) (provide 'org-roam-protocol)

98
org-roam-utils.el Normal file
View File

@ -0,0 +1,98 @@
;;; org-roam-utils.el --- Org-roam utility functions -*- coding: utf-8; lexical-binding: t -*-
;; Copyright © 2020 Jethro Kuan <jethrokuan95@gmail.com>
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;;
;; Provides several utility functions used throughout Org-roam.
;;
;;; Code:
(require 'f)
(require 'ob-core) ;for org-babel-parse-header-arguments
(defun org-roam--plist-to-alist (plist)
"Return an alist of the property-value pairs in PLIST."
(let (res)
(while plist
(let ((prop (intern (substring (symbol-name (pop plist)) 1 nil)))
(val (pop plist)))
(push (cons prop val) res)))
res))
(defun org-roam--touch-file (path)
"Touches an empty file at PATH."
(make-directory (file-name-directory path) t)
(f-touch path))
(defun org-roam--file-name-extension (filename)
"Return file name extension for FILENAME.
Like file-name-extension, but does not strip version number."
(save-match-data
(let ((file (file-name-nondirectory filename)))
(if (and (string-match "\\.[^.]*\\'" file)
(not (eq 0 (match-beginning 0))))
(substring file (+ (match-beginning 0) 1))))))
(defun org-roam--org-file-p (path)
"Check if PATH is pointing to an org file."
(let ((ext (org-roam--file-name-extension path)))
(or (string= ext "org")
(and
(string= ext "gpg")
(string= (org-roam--file-name-extension (file-name-sans-extension path)) "org")))))
(defun org-roam--org-roam-file-p (&optional file)
"Return t if FILE is part of org-roam system, return nil otherwise.
If FILE is not specified, use the current-buffer file path."
(let ((path (or file
(buffer-file-name (current-buffer)))))
(and path
(org-roam--org-file-p path)
(f-descendant-of-p (file-truename path)
(file-truename org-roam-directory)))))
(defun org-roam--aliases-str-to-list (str)
"Function to transform string STR into list of alias titles.
This snippet is obtained from ox-hugo:
https://github.com/kaushalmodi/ox-hugo/blob/a80b250987bc770600c424a10b3bca6ff7282e3c/ox-hugo.el#L3131"
(when (stringp str)
(let* ((str (org-trim str))
(str-list (split-string str "\n"))
ret)
(dolist (str-elem str-list)
(let* ((format-str ":dummy '(%s)") ;The :dummy key is discarded in the `lst' var below.
(alist (org-babel-parse-header-arguments (format format-str str-elem)))
(lst (cdr (car alist)))
(str-list2 (mapcar (lambda (elem)
(cond
((symbolp elem)
(symbol-name elem))
(t
elem)))
lst)))
(setq ret (append ret str-list2))))
ret)))
;;; -
(provide 'org-roam-utils)
;;; org-roam-utils.el ends here

View File

@ -34,16 +34,15 @@
;; ;;
;;; Code: ;;; Code:
(eval-when-compile (require 'cl-lib))
(require 'org) (require 'org)
(require 'org-element) (require 'org-element)
(require 'ob-core) ;for org-babel-parse-header-arguments
(require 'subr-x) (require 'subr-x)
(require 'dash) (require 'dash)
(require 's) (require 's)
(require 'f) (require 'f)
(require 'cl-lib) (require 'cl-lib)
(require 'org-roam-db) (require 'org-roam-db)
(require 'org-roam-utils)
;;; Customizations ;;; Customizations
(defgroup org-roam nil (defgroup org-roam nil
@ -60,14 +59,12 @@
(defcustom org-roam-new-file-directory nil (defcustom org-roam-new-file-directory nil
"Path to where new Org-roam files are created. "Path to where new Org-roam files are created.
If nil, default to the org-roam-directory (preferred)." If nil, default to the org-roam-directory (preferred)."
:type 'directory :type 'directory
:group 'org-roam) :group 'org-roam)
(defcustom org-roam-buffer-position 'right (defcustom org-roam-buffer-position 'right
"Position of `org-roam' buffer. "Position of `org-roam' buffer.
Valid values are Valid values are
* left, * left,
* right." * right."
@ -81,8 +78,7 @@ Valid values are
:group 'org-roam) :group 'org-roam)
(defcustom org-roam-filename-noconfirm t (defcustom org-roam-filename-noconfirm t
"Whether to prompt for confirmation of fil name for new files. "Whether to prompt for confirmation of filename for new files.
If nil, always ask for filename." If nil, always ask for filename."
:type 'boolean :type 'boolean
:group 'org-roam) :group 'org-roam)
@ -129,30 +125,9 @@ If nil, always ask for filename."
"Last window `org-roam' was called from.") "Last window `org-roam' was called from.")
;;; Utilities ;;; Utilities
(defun org-roam--touch-file (path) (defun org-roam--list-files (dir)
"Touches an empty file at PATH." "Return all Org-roam files located within DIR, at any nesting level.
(make-directory (file-name-directory path) t) Ignores hidden files and directories."
(f-touch path))
(defun org-roam--file-name-extension (filename)
"Return file name extension for FILENAME.
Like file-name-extension, but does not strip version number."
(save-match-data
(let ((file (file-name-nondirectory filename)))
(if (and (string-match "\\.[^.]*\\'" file)
(not (eq 0 (match-beginning 0))))
(substring file (+ (match-beginning 0) 1))))))
(defun org-roam--org-file-p (path)
"Check if PATH is pointing to an org file."
(let ((ext (org-roam--file-name-extension path)))
(or (string= ext "org")
(and
(string= ext "gpg")
(string= (org-roam--file-name-extension (file-name-sans-extension path)) "org")))))
(defun org-roam--find-files (dir)
"Return all `org-roam' files in `DIR'."
(if (file-exists-p dir) (if (file-exists-p dir)
(let ((files (directory-files dir t "." t)) (let ((files (directory-files dir t "." t))
(dir-ignore-regexp (concat "\\(?:" (dir-ignore-regexp (concat "\\(?:"
@ -164,15 +139,22 @@ Like file-name-extension, but does not strip version number."
(cond (cond
((file-directory-p file) ((file-directory-p file)
(when (not (string-match dir-ignore-regexp file)) (when (not (string-match dir-ignore-regexp file))
(setq result (append (org-roam--find-files file) result)))) (setq result (append (org-roam--list-files file) result))))
((and (file-readable-p file) ((and (file-readable-p file)
(org-roam--org-file-p file)) (org-roam--org-file-p file))
(setq result (cons (file-truename file) result))))) (setq result (cons (file-truename file) result)))))
result))) result)))
(defun org-roam--get-links (&optional file-path) (defun org-roam--extract-links (&optional file-path)
"Get the links in the buffer. "Extracts all link items within the current buffer.
If FILE-PATH is passed, use that as the source file." Link items are of the form:
[file-from file-to properties]
This is the format that emacsql expects when inserting into the database.
FILE-FROM is typically the buffer file path, but this may not exist, for example
in temp buffers. In cases where this occurs, we do know the file path, and pass
it as FILE-PATH."
(let ((file-path (or file-path (let ((file-path (or file-path
(file-truename (buffer-file-name (current-buffer)))))) (file-truename (buffer-file-name (current-buffer))))))
(org-element-map (org-element-parse-buffer) 'link (org-element-map (org-element-parse-buffer) 'link
@ -197,43 +179,19 @@ If FILE-PATH is passed, use that as the source file."
(list :content content :point begin))))))))) (list :content content :point begin)))))))))
(defun org-roam--extract-global-props (props) (defun org-roam--extract-global-props (props)
"Extract PROPS from the current buffer." "Extract PROPS from the current org buffer.
The search terminates when the first property is encountered."
(let ((buf (org-element-parse-buffer)) (let ((buf (org-element-parse-buffer))
(res '())) res)
(dolist (prop props) (dolist (prop props)
(let ((p (org-element-map (let ((p (org-element-map buf 'keyword
buf
'keyword
(lambda (kw) (lambda (kw)
(when (string= (org-element-property :key kw) prop) (when (string= (org-element-property :key kw) prop)
(org-element-property :value kw))) (org-element-property :value kw)))
:first-match t))) :first-match t)))
(setq res (cons (cons prop p) res)))) (push (cons prop p) res)))
res)) res))
(defun org-roam--aliases-str-to-list (str)
"Function to transform string STR into list of alias titles.
This snippet is obtained from ox-hugo:
https://github.com/kaushalmodi/ox-hugo/blob/a80b250987bc770600c424a10b3bca6ff7282e3c/ox-hugo.el#L3131"
(when (stringp str)
(let* ((str (org-trim str))
(str-list (split-string str "\n"))
ret)
(dolist (str-elem str-list)
(let* ((format-str ":dummy '(%s)") ;The :dummy key is discarded in the `lst' var below.
(alist (org-babel-parse-header-arguments (format format-str str-elem)))
(lst (cdr (car alist)))
(str-list2 (mapcar (lambda (elem)
(cond
((symbolp elem)
(symbol-name elem))
(t
elem)))
lst)))
(setq ret (append ret str-list2))))
ret)))
(defun org-roam--extract-titles () (defun org-roam--extract-titles ()
"Extract the titles from current buffer. "Extract the titles from current buffer.
Titles are obtained via the #+TITLE property, or aliases Titles are obtained via the #+TITLE property, or aliases
@ -250,28 +208,28 @@ specified via the #+ROAM_ALIAS property."
"Extract the ref from current buffer." "Extract the ref from current buffer."
(cdr (assoc "ROAM_KEY" (org-roam--extract-global-props '("ROAM_KEY"))))) (cdr (assoc "ROAM_KEY" (org-roam--extract-global-props '("ROAM_KEY")))))
(defun org-roam--insert-links (links) (defun org-roam--db-insert-links (links)
"Insert LINK into the org-roam cache." "Insert LINK into the org-roam cache."
(org-roam-sql (org-roam-sql
[:insert :into file-links [:insert :into file-links
:values $v1] :values $v1]
links)) links))
(defun org-roam--insert-titles (file titles) (defun org-roam--db-insert-titles (file titles)
"Insert TITLES into the org-roam-cache." "Insert TITLES into the org-roam-cache."
(org-roam-sql (org-roam-sql
[:insert :into titles [:insert :into titles
:values $v1] :values $v1]
(list (vector file titles)))) (list (vector file titles))))
(defun org-roam--insert-ref (file ref) (defun org-roam--db-insert-ref (file ref)
"Insert REF into the Org-roam cache." "Insert REF into the Org-roam cache."
(org-roam-sql (org-roam-sql
[:insert :into refs [:insert :into refs
:values $v1] :values $v1]
(list (vector ref file)))) (list (vector ref file))))
(defun org-roam--clear-cache () (defun org-roam--db-clear ()
"Clears all entries in the caches." "Clears all entries in the caches."
(interactive) (interactive)
(when (file-exists-p (org-roam--get-db)) (when (file-exists-p (org-roam--get-db))
@ -281,23 +239,23 @@ specified via the #+ROAM_ALIAS property."
(org-roam-sql [:delete :from files]) (org-roam-sql [:delete :from files])
(org-roam-sql [:delete :from refs]))) (org-roam-sql [:delete :from refs])))
(defun org-roam--clear-file-from-cache (&optional filepath) (defun org-roam--db-clear-file (&optional filepath)
"Remove any related links to the file at FILEPATH. "Remove any related links to the file at FILEPATH.
This is equivalent to removing the node from the graph." This is equivalent to removing the node from the graph."
(let* ((path (or filepath (let* ((path (or filepath
(buffer-file-name (current-buffer)))) (buffer-file-name (current-buffer))))
(file (file-truename path))) (file (file-truename path)))
(org-roam-sql [:delete :from files (org-roam-sql [:delete :from files
:where (= file $s1)] :where (= file $s1)]
file) file)
(org-roam-sql [:delete :from file-links (org-roam-sql [:delete :from file-links
:where (= file-from $s1)] :where (= file-from $s1)]
file) file)
(org-roam-sql [:delete :from titles (org-roam-sql [:delete :from titles
:where (= file $s1)] :where (= file $s1)]
file) file)
(org-roam-sql [:delete :from refs (org-roam-sql [:delete :from refs
:where (= file $s1)] :where (= file $s1)]
file))) file)))
(defun org-roam--get-current-files () (defun org-roam--get-current-files ()
@ -308,45 +266,31 @@ This is equivalent to removing the node from the graph."
(puthash (car row) (cadr row) ht)) (puthash (car row) (cadr row) ht))
ht)) ht))
(defun org-roam--cache-initialized-p () (defun org-roam--db-initialized-p ()
"Whether the cache has been initialized." "Whether the cache has been initialized."
(and (file-exists-p (org-roam--get-db)) (and (file-exists-p (org-roam--get-db))
(> (caar (org-roam-sql [:select (funcall count) :from titles])) (> (caar (org-roam-sql [:select (funcall count) :from titles]))
0))) 0)))
(defun org-roam--ensure-cache-built () (defun org-roam--db-ensure-built ()
"Ensures that org-roam cache is built." "Ensures that org-roam cache is built."
(unless (org-roam--cache-initialized-p) (unless (org-roam--db-initialized-p)
(error "[Org-roam] your cache isn't built yet! Please wait."))) (error "[Org-roam] your cache isn't built yet! Please run org-roam-build-cache.")))
(defun org-roam--org-roam-file-p (&optional file) (defun org-roam--db-get-titles (file)
"Return t if FILE is part of org-roam system, defaulting to the name of the current buffer. Else, return nil." "Return the titles of FILE from the cache."
(let ((path (or file
(buffer-file-name (current-buffer)))))
(and path
(org-roam--org-file-p path)
(f-descendant-of-p (file-truename path)
(file-truename org-roam-directory)))))
(defun org-roam--get-titles-from-cache (file)
"Return titles and aliases of `FILE' from the cache."
(caar (org-roam-sql [:select [titles] :from titles (caar (org-roam-sql [:select [titles] :from titles
:where (= file $s1)] :where (= file $s1)]
file file
:limit 1))) :limit 1)))
(defun org-roam--get-title-from-cache (file) (defun org-roam--list-all-files ()
"Return the title of `FILE' from the cache." "Return a list of all org-roam files within `org-roam-directory'."
(car (org-roam--get-titles-from-cache file))) (org-roam--list-files (file-truename org-roam-directory)))
(defun org-roam--find-all-files ()
"Return all org-roam files."
(org-roam--find-files (file-truename org-roam-directory)))
(defun org-roam--new-file-path (id &optional absolute) (defun org-roam--new-file-path (id &optional absolute)
"Make new file path from identifier `ID'. "The file path for a new Org-roam file, with identifier ID.
If ABSOLUTE, return an absolute file-path. Else, return a relative file-path."
If `ABSOLUTE', return an absolute file-path. Else, return a relative file-path."
(let ((absolute-file-path (file-truename (let ((absolute-file-path (file-truename
(expand-file-name (expand-file-name
(if org-roam-encrypt-files (if org-roam-encrypt-files
@ -367,9 +311,8 @@ If `ABSOLUTE', return an absolute file-path. Else, return a relative file-path."
(defun org-roam--get-title-or-slug (path) (defun org-roam--get-title-or-slug (path)
"Convert `PATH' to the file title, if it exists. Else, return the path." "Convert `PATH' to the file title, if it exists. Else, return the path."
(if-let (titles (org-roam--get-titles-from-cache path)) (or (car (org-roam--db-get-titles path))
(car titles) (org-roam--path-to-slug path)))
(org-roam--path-to-slug path)))
(defun org-roam--title-to-slug (title) (defun org-roam--title-to-slug (title)
"Convert TITLE to a filename-suitable slug." "Convert TITLE to a filename-suitable slug."
@ -396,35 +339,39 @@ It uses TITLE and the current timestamp to form a unique title."
:content "#+TITLE: ${title}"))) :content "#+TITLE: ${title}")))
"Templates to insert for new files in org-roam.") "Templates to insert for new files in org-roam.")
(defun org-roam--make-new-file (title &optional template-key) (defun org-roam--get-template (&optional template-key)
"Return an Org-roam template. TEMPLATE-KEY is used to get a template."
(unless org-roam-templates (unless org-roam-templates
(user-error "No templates defined")) (user-error "No templates defined"))
(let (template) (if template-key
(if template-key (cadr (assoc template-key org-roam-templates))
(setq template (cadr (assoc template-key org-roam-templates))) (if (= (length org-roam-templates) 1)
(if (= (length org-roam-templates) 1) (cadar org-roam-templates)
(setq template (cadar org-roam-templates)) (cadr (assoc (completing-read "Template: " org-roam-templates)
(setq template org-roam-templates)))))
(cadr (assoc (completing-read "Template: " org-roam-templates)
org-roam-templates)))))
(let (file-name-fn file-path) (defun org-roam--make-new-file (&optional info)
(fset 'file-name-fn (plist-get template :file)) (let ((template (org-roam--get-template (cdr (assoc 'template info))))
(setq file-path (org-roam--new-file-path (file-name-fn title) t)) (title (or (cdr (assoc 'title info))
(if (file-exists-p file-path) (completing-read "Title: " nil)))
file-path file-name-fn file-path)
(org-roam--touch-file file-path) (fset 'file-name-fn (plist-get template :file))
(write-region (setq file-path (org-roam--new-file-path (file-name-fn title) t))
(s-format (plist-get template :content) (setq info (cons (cons 'slug (org-roam--title-to-slug title)) info))
'aget (if (file-exists-p file-path)
(list (cons "title" title) file-path
(cons "slug" (org-roam--title-to-slug title)))) (org-roam--touch-file file-path)
nil file-path nil) (write-region
file-path)))) (s-format (plist-get template :content)
'aget
info)
nil file-path nil)
file-path)))
;;; Inserting org-roam links ;;; Inserting org-roam links
(defun org-roam-insert (prefix) (defun org-roam-insert (prefix)
"Find an org-roam file, and insert a relative org link to it at point. "Find an org-roam file, and insert a relative org link to it at point.
If PREFIX, downcase the title before insertion." If PREFIX, downcase the title before insertion."
(interactive "P") (interactive "P")
(let* ((region (and (region-active-p) (let* ((region (and (region-active-p)
@ -437,7 +384,7 @@ If PREFIX, downcase the title before insertion."
(title (completing-read "File: " completions nil nil region-text)) (title (completing-read "File: " completions nil nil region-text))
(region-or-title (or region-text title)) (region-or-title (or region-text title))
(absolute-file-path (or (cdr (assoc title completions)) (absolute-file-path (or (cdr (assoc title completions))
(org-roam--make-new-file title))) (org-roam--make-new-file (list (cons 'title title)))))
(current-file-path (-> (or (buffer-base-buffer) (current-file-path (-> (or (buffer-base-buffer)
(current-buffer)) (current-buffer))
(buffer-file-name) (buffer-file-name)
@ -463,18 +410,37 @@ If PREFIX, downcase the title before insertion."
(titles (cadr row))) (titles (cadr row)))
(if titles (if titles
(dolist (title titles) (dolist (title titles)
(setq res (cons (cons title file-path) res))) (push (cons title file-path) res))
(setq res (cons (cons (org-roam--path-to-slug file-path) (push (cons (org-roam--path-to-slug file-path)
file-path) res))))) file-path) res))))
res)) res))
(defun org-roam--get-ref-path-completions ()
"Return a list of cons pairs for titles to absolute path of Org-roam files."
(let ((rows (org-roam-sql [:select [ref file] :from refs])))
(mapcar (lambda (row)
(cons (car row)
(cadr row))) rows)))
(defun org-roam-find-ref (&optional info)
"Find and open an org-roam file from a ref.
INFO is an alist containing additional information."
(interactive)
(let* ((completions (org-roam--get-ref-path-completions))
(ref (or (cdr (assoc 'ref info))
(completing-read "Ref: " (org-roam--get-ref-path-completions))))
(file-path (cdr (assoc ref completions))))
(if file-path
(find-file file-path)
(find-file (org-roam--make-new-file info)))))
(defun org-roam-find-file () (defun org-roam-find-file ()
"Find and open an org-roam file." "Find and open an org-roam file."
(interactive) (interactive)
(let* ((completions (org-roam--get-title-path-completions)) (let* ((completions (org-roam--get-title-path-completions))
(title-or-slug (completing-read "File: " completions)) (title-or-slug (completing-read "File: " completions))
(absolute-file-path (or (cdr (assoc title-or-slug completions)) (absolute-file-path (or (cdr (assoc title-or-slug completions))
(org-roam--make-new-file title-or-slug)))) (org-roam--make-new-file (list (cons 'title title-or-slug))))))
(find-file absolute-file-path))) (find-file absolute-file-path)))
(defun org-roam--get-roam-buffers () (defun org-roam--get-roam-buffers ()
@ -485,7 +451,7 @@ If PREFIX, downcase the title before insertion."
(buffer-list))) (buffer-list)))
(defun org-roam-switch-to-buffer () (defun org-roam-switch-to-buffer ()
"Switch to an existing org-roam buffer using completing-read." "Switch to an existing org-roam buffer."
(interactive) (interactive)
(let* ((roam-buffers (org-roam--get-roam-buffers)) (let* ((roam-buffers (org-roam--get-roam-buffers))
(names-and-buffers (mapcar (lambda (buffer) (names-and-buffers (mapcar (lambda (buffer)
@ -495,7 +461,7 @@ If PREFIX, downcase the title before insertion."
buffer)) buffer))
roam-buffers))) roam-buffers)))
(unless roam-buffers (unless roam-buffers
(error "No roam buffers.")) (user-error "No roam buffers."))
(when-let ((name (completing-read "Choose a buffer: " names-and-buffers))) (when-let ((name (completing-read "Choose a buffer: " names-and-buffers)))
(switch-to-buffer (cdr (assoc name names-and-buffers)))))) (switch-to-buffer (cdr (assoc name names-and-buffers))))))
@ -504,7 +470,7 @@ If PREFIX, downcase the title before insertion."
"Build the cache for `org-roam-directory'." "Build the cache for `org-roam-directory'."
(interactive) (interactive)
(org-roam-db) ;; To initialize the database, no-op if already initialized (org-roam-db) ;; To initialize the database, no-op if already initialized
(let* ((org-roam-files (org-roam--find-files org-roam-directory)) (let* ((org-roam-files (org-roam--list-files org-roam-directory))
(current-files (org-roam--get-current-files)) (current-files (org-roam--get-current-files))
(time (current-time)) (time (current-time))
all-files all-links all-titles all-refs) all-files all-links all-titles all-refs)
@ -514,10 +480,10 @@ If PREFIX, downcase the title before insertion."
(let ((contents-hash (secure-hash 'sha1 (current-buffer)))) (let ((contents-hash (secure-hash 'sha1 (current-buffer))))
(unless (string= (gethash file current-files) (unless (string= (gethash file current-files)
contents-hash) contents-hash)
(org-roam--clear-file-from-cache file) (org-roam--db-clear-file file)
(setq all-files (setq all-files
(cons (vector file contents-hash time) all-files)) (cons (vector file contents-hash time) all-files))
(when-let (links (org-roam--get-links file)) (when-let (links (org-roam--extract-links file))
(setq all-links (append links all-links))) (setq all-links (append links all-links)))
(let ((titles (org-roam--extract-titles))) (let ((titles (org-roam--extract-titles)))
(setq all-titles (cons (vector file titles) all-titles))) (setq all-titles (cons (vector file titles) all-titles)))
@ -526,26 +492,26 @@ If PREFIX, downcase the title before insertion."
(remhash file current-files)))) (remhash file current-files))))
(dolist (file (hash-table-keys current-files)) (dolist (file (hash-table-keys current-files))
;; These files are no longer around, remove from cache... ;; These files are no longer around, remove from cache...
(org-roam--clear-file-from-cache file)) (org-roam--db-clear-file file))
(when all-files (when all-files
(org-roam-sql (org-roam-sql
[:insert :into files [:insert :into files
:values $v1] :values $v1]
all-files)) all-files))
(when all-links (when all-links
(org-roam-sql (org-roam-sql
[:insert :into file-links [:insert :into file-links
:values $v1] :values $v1]
all-links)) all-links))
(when all-titles (when all-titles
(org-roam-sql (org-roam-sql
[:insert :into titles [:insert :into titles
:values $v1] :values $v1]
all-titles)) all-titles))
(when all-refs (when all-refs
(org-roam-sql (org-roam-sql
[:insert :into refs [:insert :into refs
:values $v1] :values $v1]
all-refs)) all-refs))
(let ((stats (list :files (length all-files) (let ((stats (list :files (length all-files)
:links (length all-links) :links (length all-links)
@ -560,22 +526,22 @@ If PREFIX, downcase the title before insertion."
(plist-get stats :deleted))) (plist-get stats :deleted)))
stats))) stats)))
(defun org-roam--update-cache-titles () (defun org-roam--db-update-titles ()
"Update the title of the current buffer into the cache." "Update the title of the current buffer into the cache."
(let ((file (file-truename (buffer-file-name (current-buffer))))) (let ((file (file-truename (buffer-file-name (current-buffer)))))
(org-roam-sql [:delete :from titles (org-roam-sql [:delete :from titles
:where (= file $s1)] :where (= file $s1)]
file) file)
(org-roam--insert-titles file (org-roam--extract-titles)))) (org-roam--db-insert-titles file (org-roam--extract-titles))))
(defun org-roam--update-cache-refs () (defun org-roam--db-update-refs ()
"Update the ref of the current buffer into the cache." "Update the ref of the current buffer into the cache."
(let ((file (file-truename (buffer-file-name (current-buffer))))) (let ((file (file-truename (buffer-file-name (current-buffer)))))
(org-roam-sql [:delete :from refs (org-roam-sql [:delete :from refs
:where (= file $s1)] :where (= file $s1)]
file) file)
(when-let ((ref (org-roam--extract-ref))) (when-let ((ref (org-roam--extract-ref)))
(org-roam--insert-ref file ref)))) (org-roam--db-insert-ref file ref))))
(defun org-roam--update-cache-links () (defun org-roam--update-cache-links ()
"Update the file links of the current buffer in the cache." "Update the file links of the current buffer in the cache."
@ -583,14 +549,14 @@ If PREFIX, downcase the title before insertion."
(org-roam-sql [:delete :from file-links (org-roam-sql [:delete :from file-links
:where (= file-from $s1)] :where (= file-from $s1)]
file) file)
(when-let ((links (org-roam--get-links))) (when-let ((links (org-roam--extract-links)))
(org-roam--insert-links links)))) (org-roam--db-insert-links links))))
(defun org-roam--update-cache () (defun org-roam--db-update-buffer-file ()
"Update org-roam caches for the current buffer file." "Update org-roam caches for the current buffer file."
(save-excursion (save-excursion
(org-roam--update-cache-titles) (org-roam--db-update-titles)
(org-roam--update-cache-refs) (org-roam--db-update-refs)
(org-roam--update-cache-links) (org-roam--update-cache-links)
(org-roam--maybe-update-buffer :redisplay t))) (org-roam--maybe-update-buffer :redisplay t)))
@ -599,7 +565,8 @@ If PREFIX, downcase the title before insertion."
"Create and find file for TIME." "Create and find file for TIME."
(let* ((org-roam-templates (list (list "daily" (list :file (lambda (title) title) (let* ((org-roam-templates (list (list "daily" (list :file (lambda (title) title)
:content "#+TITLE: ${title}"))))) :content "#+TITLE: ${title}")))))
(org-roam--make-new-file (format-time-string "%Y-%m-%d" time) "daily"))) (org-roam--make-new-file (list (cons 'title (format-time-string "%Y-%m-%d" time))
(cons 'template "daily")))))
(defun org-roam-today () (defun org-roam-today ()
"Create and find file for today." "Create and find file for today."
@ -620,7 +587,8 @@ If PREFIX, downcase the title before insertion."
(let ((path (org-roam--file-for-time time))) (let ((path (org-roam--file-for-time time)))
(org-roam--find-file path)))) (org-roam--find-file path))))
;;; Org-roam buffer
;;; Org-roam Buffer
(define-derived-mode org-roam-backlinks-mode org-mode "Backlinks" (define-derived-mode org-roam-backlinks-mode org-mode "Backlinks"
"Major mode for the org-roam backlinks buffer "Major mode for the org-roam backlinks buffer
@ -674,7 +642,7 @@ If item at point is not org-roam specific, default to Org behaviour."
(defun org-roam-update (file-path) (defun org-roam-update (file-path)
"Show the backlinks for given org file for file at `FILE-PATH'." "Show the backlinks for given org file for file at `FILE-PATH'."
(org-roam--ensure-cache-built) (org-roam--db-ensure-built)
(let* ((source-org-roam-directory org-roam-directory)) (let* ((source-org-roam-directory org-roam-directory))
(let ((buffer-title (org-roam--get-title-or-slug file-path))) (let ((buffer-title (org-roam--get-title-or-slug file-path)))
(with-current-buffer org-roam-buffer (with-current-buffer org-roam-buffer
@ -718,10 +686,13 @@ If item at point is not org-roam specific, default to Org behaviour."
(insert "\n\n* No backlinks!"))) (insert "\n\n* No backlinks!")))
(read-only-mode 1)))))) (read-only-mode 1))))))
;;; Building the Graphviz graph ;;; Graph
(defun org-roam-build-graph () (defun org-roam--build-graph ()
"Build graphviz graph output." "Build the Graphviz string.
(org-roam--ensure-cache-built) The Org-roam database titles table is read, to obtain the list of titles.
The file-links table is then read to obtain all directed links, and formatted
into a digraph."
(org-roam--db-ensure-built)
(with-temp-buffer (with-temp-buffer
(insert "digraph {\n") (insert "digraph {\n")
(let ((rows (org-roam-sql [:select [file titles] :from titles]))) (let ((rows (org-roam-sql [:select [file titles] :from titles])))
@ -731,7 +702,7 @@ If item at point is not org-roam specific, default to Org behaviour."
(org-roam--path-to-slug file))) (org-roam--path-to-slug file)))
(shortened-title (s-truncate org-roam-graph-max-title-length title))) (shortened-title (s-truncate org-roam-graph-max-title-length title)))
(insert (insert
(format " \"%s\" [label=\"%s\", shape=%s, URL=\"roam://%s\", tooltip=\"%s\"];\n" (format " \"%s\" [label=\"%s\", shape=%s, URL=\"org-protocol://roam-file?file=%s\", tooltip=\"%s\"];\n"
file file
shortened-title shortened-title
org-roam-graph-node-shape org-roam-graph-node-shape
@ -746,16 +717,16 @@ If item at point is not org-roam specific, default to Org behaviour."
(buffer-string))) (buffer-string)))
(defun org-roam-show-graph () (defun org-roam-show-graph ()
"Generate the org-roam graph in SVG format, and display it using `org-roam-graph-viewer'." "Displays the generated Org-roam graph using `org-roam-graph-viewer'."
(interactive) (interactive)
(declare (indent 0))
(unless org-roam-graphviz-executable (unless org-roam-graphviz-executable
(setq org-roam-graphviz-executable (executable-find "dot"))) (setq org-roam-graphviz-executable (executable-find "dot")))
(unless org-roam-graphviz-executable (unless org-roam-graphviz-executable
(user-error "Can't find graphviz executable. Please check if it is in your path")) (user-error "Can't find graphviz executable. Please check if it is in your path"))
(declare (indent 0))
(let ((temp-dot (expand-file-name "graph.dot" temporary-file-directory)) (let ((temp-dot (expand-file-name "graph.dot" temporary-file-directory))
(temp-graph (expand-file-name "graph.svg" temporary-file-directory)) (temp-graph (expand-file-name "graph.svg" temporary-file-directory))
(graph (org-roam-build-graph))) (graph (org-roam--build-graph)))
(with-temp-file temp-dot (with-temp-file temp-dot
(insert graph)) (insert graph))
(call-process org-roam-graphviz-executable nil 0 nil temp-dot "-Tsvg" "-o" temp-graph) (call-process org-roam-graphviz-executable nil 0 nil temp-dot "-Tsvg" "-o" temp-graph)
@ -765,8 +736,8 @@ If item at point is not org-roam specific, default to Org behaviour."
;;; Org-roam minor mode ;;; Org-roam minor mode
(cl-defun org-roam--maybe-update-buffer (&key redisplay) (cl-defun org-roam--maybe-update-buffer (&key redisplay)
"Update `org-roam-buffer' with the necessary information. "Reconstructs `org-roam-buffer'.
This needs to be quick/infrequent, because this is run at This needs to be quick or infrequent, because this is run at
`post-command-hook'." `post-command-hook'."
(let ((buffer (window-buffer))) (let ((buffer (window-buffer)))
(when (and (or redisplay (when (and (or redisplay
@ -784,7 +755,6 @@ This needs to be quick/infrequent, because this is run at
(defun org-roam--roam-link-face (path) (defun org-roam--roam-link-face (path)
"Conditional face for org file links. "Conditional face for org file links.
Applies `org-roam-link-face' if PATH correponds to a Roam file." Applies `org-roam-link-face' if PATH correponds to a Roam file."
(if (org-roam--org-roam-file-p path) (if (org-roam--org-roam-file-p path)
'org-roam-link 'org-roam-link
@ -795,7 +765,7 @@ Applies `org-roam-link-face' if PATH correponds to a Roam file."
(when (org-roam--org-roam-file-p) (when (org-roam--org-roam-file-p)
(setq org-roam-last-window (get-buffer-window)) (setq org-roam-last-window (get-buffer-window))
(add-hook 'post-command-hook #'org-roam--maybe-update-buffer nil t) (add-hook 'post-command-hook #'org-roam--maybe-update-buffer nil t)
(add-hook 'after-save-hook #'org-roam--update-cache nil t) (add-hook 'after-save-hook #'org-roam--db-update-buffer-file nil t)
(org-roam--setup-file-links) (org-roam--setup-file-links)
(org-roam--maybe-update-buffer :redisplay nil))) (org-roam--maybe-update-buffer :redisplay nil)))
@ -816,14 +786,14 @@ This sets `file:' Org links to have the org-link face."
(defun org-roam--delete-file-advice (file &optional _trash) (defun org-roam--delete-file-advice (file &optional _trash)
"Advice for maintaining cache consistency during file deletes." "Advice for maintaining cache consistency during file deletes."
(org-roam--clear-file-from-cache (file-truename file))) (org-roam--db-clear-file (file-truename file)))
(defun org-roam--rename-file-advice (file new-file &rest args) (defun org-roam--rename-file-advice (file new-file &rest args)
"Rename backlinks of FILE to refer to NEW-FILE." "Rename backlinks of FILE to refer to NEW-FILE."
(when (and (not (auto-save-file-name-p file)) (when (and (not (auto-save-file-name-p file))
(not (auto-save-file-name-p new-file)) (not (auto-save-file-name-p new-file))
(org-roam--org-roam-file-p new-file)) (org-roam--org-roam-file-p new-file))
(org-roam--ensure-cache-built) (org-roam--db-ensure-built)
(let* ((files-to-rename (org-roam-sql [:select :distinct [file-from] (let* ((files-to-rename (org-roam-sql [:select :distinct [file-from]
:from file-links :from file-links
:where (= file-to $s1)] :where (= file-to $s1)]
@ -832,10 +802,10 @@ This sets `file:' Org links to have the org-link face."
(new-path (file-truename new-file)) (new-path (file-truename new-file))
(slug (org-roam--get-title-or-slug file)) (slug (org-roam--get-title-or-slug file))
(old-title (format org-roam-link-title-format slug)) (old-title (format org-roam-link-title-format slug))
(new-slug (or (org-roam--get-title-from-cache path) (new-slug (or (car (org-roam--db-get-titles path))
(org-roam--get-title-or-slug new-path))) (org-roam--path-to-slug new-path)))
(new-title (format org-roam-link-title-format new-slug))) (new-title (format org-roam-link-title-format new-slug)))
(org-roam--clear-file-from-cache file) (org-roam--db-clear-file file)
(dolist (file-from files-to-rename) (dolist (file-from files-to-rename)
(let* ((file-from (car file-from)) (let* ((file-from (car file-from))
(file-from (if (string-equal (file-truename file-from) (file-from (if (string-equal (file-truename file-from)
@ -859,10 +829,10 @@ This sets `file:' Org links to have the org-link face."
(replace-match (format "[[file:%s][\\1]]" relative-path)))) (replace-match (format "[[file:%s][\\1]]" relative-path))))
(save-window-excursion (save-window-excursion
(find-file file-from) (find-file file-from)
(org-roam--update-cache)))) (org-roam--db-update-buffer-file))))
(save-window-excursion (save-window-excursion
(find-file new-path) (find-file new-path)
(org-roam--update-cache))))) (org-roam--db-update-buffer-file)))))
;;;###autoload ;;;###autoload
(define-minor-mode org-roam-mode (define-minor-mode org-roam-mode
@ -895,7 +865,7 @@ If ARG is `toggle', toggle `org-roam-mode'. Otherwise, behave as if called inter
(with-current-buffer buf (with-current-buffer buf
(org-roam--teardown-file-links) (org-roam--teardown-file-links)
(remove-hook 'post-command-hook #'org-roam--maybe-update-buffer t) (remove-hook 'post-command-hook #'org-roam--maybe-update-buffer t)
(remove-hook 'after-save-hook #'org-roam--update-cache t)))))) (remove-hook 'after-save-hook #'org-roam--db-update-buffer-file t))))))
;;; Show/hide the org-roam buffer ;;; Show/hide the org-roam buffer
(define-inline org-roam--current-visibility () (define-inline org-roam--current-visibility ()

View File

@ -30,6 +30,7 @@
(require 'with-simulated-input) (require 'with-simulated-input)
(require 'org-roam) (require 'org-roam)
(require 'org-roam-db) (require 'org-roam-db)
(require 'org-roam-utils)
(require 'dash) (require 'dash)
(defun abs-path (file-path) (defun abs-path (file-path)
@ -115,7 +116,7 @@
(describe "org-roam-insert" (describe "org-roam-insert"
(before-each (before-each
(org-roam--test-init) (org-roam--test-init)
(org-roam--clear-cache) (org-roam--db-clear)
(org-roam-build-cache)) (org-roam-build-cache))
(it "temp1 -> foo" (it "temp1 -> foo"
@ -153,7 +154,7 @@
(describe "rename file updates cache" (describe "rename file updates cache"
(before-each (before-each
(org-roam--test-init) (org-roam--test-init)
(org-roam--clear-cache) (org-roam--db-clear)
(org-roam-build-cache)) (org-roam-build-cache))
(it "foo -> new_foo" (it "foo -> new_foo"
@ -161,28 +162,28 @@
(abs-path "new_foo.org")) (abs-path "new_foo.org"))
;; Cache should be cleared of old file ;; Cache should be cleared of old file
(expect (caar (org-roam-sql [:select (funcall count) (expect (caar (org-roam-sql [:select (funcall count)
:from titles :from titles
:where (= file $s1)] :where (= file $s1)]
(abs-path "foo.org"))) :to-be 0) (abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-sql [:select (funcall count) (expect (caar (org-roam-sql [:select (funcall count)
:from refs :from refs
:where (= file $s1)] :where (= file $s1)]
(abs-path "foo.org"))) :to-be 0) (abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-sql [:select (funcall count) (expect (caar (org-roam-sql [:select (funcall count)
:from file-links :from file-links
:where (= file-from $s1)] :where (= file-from $s1)]
(abs-path "foo.org"))) :to-be 0) (abs-path "foo.org"))) :to-be 0)
;; Cache should be updated ;; Cache should be updated
(expect (org-roam-sql [:select [file-to] (expect (org-roam-sql [:select [file-to]
:from file-links :from file-links
:where (= file-from $s1)] :where (= file-from $s1)]
(abs-path "new_foo.org")) (abs-path "new_foo.org"))
:to-have-same-items-as :to-have-same-items-as
(list (list (abs-path "bar.org")))) (list (list (abs-path "bar.org"))))
(expect (org-roam-sql [:select [file-from] (expect (org-roam-sql [:select [file-from]
:from file-links :from file-links
:where (= file-to $s1)] :where (= file-to $s1)]
(abs-path "new_foo.org")) (abs-path "new_foo.org"))
:to-have-same-items-as :to-have-same-items-as
(list (list (abs-path "nested/bar.org")))) (list (list (abs-path "nested/bar.org"))))
@ -199,28 +200,28 @@
(abs-path "foo with spaces.org")) (abs-path "foo with spaces.org"))
;; Cache should be cleared of old file ;; Cache should be cleared of old file
(expect (caar (org-roam-sql [:select (funcall count) (expect (caar (org-roam-sql [:select (funcall count)
:from titles :from titles
:where (= file $s1)] :where (= file $s1)]
(abs-path "foo.org"))) :to-be 0) (abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-sql [:select (funcall count) (expect (caar (org-roam-sql [:select (funcall count)
:from refs :from refs
:where (= file $s1)] :where (= file $s1)]
(abs-path "foo.org"))) :to-be 0) (abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-sql [:select (funcall count) (expect (caar (org-roam-sql [:select (funcall count)
:from file-links :from file-links
:where (= file-from $s1)] :where (= file-from $s1)]
(abs-path "foo.org"))) :to-be 0) (abs-path "foo.org"))) :to-be 0)
;; Cache should be updated ;; Cache should be updated
(expect (org-roam-sql [:select [file-to] (expect (org-roam-sql [:select [file-to]
:from file-links :from file-links
:where (= file-from $s1)] :where (= file-from $s1)]
(abs-path "foo with spaces.org")) (abs-path "foo with spaces.org"))
:to-have-same-items-as :to-have-same-items-as
(list (list (abs-path "bar.org")))) (list (list (abs-path "bar.org"))))
(expect (org-roam-sql [:select [file-from] (expect (org-roam-sql [:select [file-from]
:from file-links :from file-links
:where (= file-to $s1)] :where (= file-to $s1)]
(abs-path "foo with spaces.org")) (abs-path "foo with spaces.org"))
:to-have-same-items-as :to-have-same-items-as
(list (list (abs-path "nested/bar.org")))) (list (list (abs-path "nested/bar.org"))))
@ -237,12 +238,12 @@
(abs-path "meaningful-title.org")) (abs-path "meaningful-title.org"))
;; File has no forward links ;; File has no forward links
(expect (caar (org-roam-sql [:select (funcall count) (expect (caar (org-roam-sql [:select (funcall count)
:from file-links :from file-links
:where (= file-from $s1)] :where (= file-from $s1)]
(abs-path "no-title.org"))) :to-be 0) (abs-path "no-title.org"))) :to-be 0)
(expect (caar (org-roam-sql [:select (funcall count) (expect (caar (org-roam-sql [:select (funcall count)
:from file-links :from file-links
:where (= file-from $s1)] :where (= file-from $s1)]
(abs-path "meaningful-title.org"))) :to-be 1) (abs-path "meaningful-title.org"))) :to-be 1)
;; Links are updated with the appropriate name ;; Links are updated with the appropriate name
@ -255,7 +256,7 @@
(it "web_ref -> hello" (it "web_ref -> hello"
(expect (org-roam-sql (expect (org-roam-sql
[:select [file] :from refs [:select [file] :from refs
:where (= ref $s1)] :where (= ref $s1)]
"https://google.com/") "https://google.com/")
:to-equal :to-equal
(list (list (abs-path "web_ref.org")))) (list (list (abs-path "web_ref.org"))))
@ -263,35 +264,35 @@
(abs-path "hello.org")) (abs-path "hello.org"))
(expect (org-roam-sql (expect (org-roam-sql
[:select [file] :from refs [:select [file] :from refs
:where (= ref $s1)] :where (= ref $s1)]
"https://google.com/") "https://google.com/")
:to-equal (list (list (abs-path "hello.org")))) :to-equal (list (list (abs-path "hello.org"))))
(expect (caar (org-roam-sql (expect (caar (org-roam-sql
[:select [ref] :from refs [:select [ref] :from refs
:where (= file $s1)] :where (= file $s1)]
(abs-path "web_ref.org"))) (abs-path "web_ref.org")))
:to-equal nil))) :to-equal nil)))
(describe "delete file updates cache" (describe "delete file updates cache"
(before-each (before-each
(org-roam--test-init) (org-roam--test-init)
(org-roam--clear-cache) (org-roam--db-clear)
(org-roam-build-cache) (org-roam-build-cache)
(sleep-for 1)) (sleep-for 1))
(it "delete foo" (it "delete foo"
(delete-file (abs-path "foo.org")) (delete-file (abs-path "foo.org"))
(expect (caar (org-roam-sql [:select (funcall count) (expect (caar (org-roam-sql [:select (funcall count)
:from titles :from titles
:where (= file $s1)] :where (= file $s1)]
(abs-path "foo.org"))) :to-be 0) (abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-sql [:select (funcall count) (expect (caar (org-roam-sql [:select (funcall count)
:from refs :from refs
:where (= file $s1)] :where (= file $s1)]
(abs-path "foo.org"))) :to-be 0) (abs-path "foo.org"))) :to-be 0)
(expect (caar (org-roam-sql [:select (funcall count) (expect (caar (org-roam-sql [:select (funcall count)
:from file-links :from file-links
:where (= file-from $s1)] :where (= file-from $s1)]
(abs-path "foo.org"))) :to-be 0)) (abs-path "foo.org"))) :to-be 0))
(it "delete web_ref" (it "delete web_ref"