Compare commits

...

43 Commits

Author SHA1 Message Date
18b75bd03e (feat): Hide drawer content when capturing 2021-05-12 09:16:05 +02:00
ecc3611bec Merge branch 'master' into v2 2021-05-12 09:13:11 +02:00
f754160402 (fix): Fix chronology issue between renaming notes and updating links (#1517) 2021-05-12 09:06:11 +02:00
3a3c4d97d5 (fix) setting non-blank keyword value (#1516)
`string-blank-p` returns a non-nil value if the string is blank, and
nil (which you can't compare to 0) otherwise.
2021-05-10 14:19:02 +08:00
07ec4342aa (feat) allow to specify props to remove and return them (#1515)
This is a follow up of #1420/ for v2.

Basically:

1. Allow to pass a tag/alias/ref to remove.
2. Return removed tag/alias/ref.
3. Return added tag/alias/ref to mirror (2).
2021-05-10 14:18:48 +08:00
096b0c5e30 allow capture to datetree 2021-05-08 18:16:42 +08:00
32c0f3d2ec fix org-roam-capture--goto-location (#1514)
Fix regression introduced in 7ed51329.

The function should return an ID.
2021-05-08 15:14:00 +08:00
02e35e3b01 (feat): add org-roam-graph-filetype (#1513)
Co-authored-by: Greg Coladonato <gcoladonato3@gatech.edu>
2021-05-06 19:23:46 +08:00
377d39bfff update org-roam-capture-tempates docstring (#1509)
Remove quotes before heading lists in olp examples, they do not belong
there. Quoting these lists will lead to an error.
2021-05-04 12:28:49 +08:00
3d4c93a2a0 macro: find-file non-literally, but start in fundamental-mode?
Lexically bind auto-mode-alist to nil, so emacs opens the file initially
in the global major-mode, which is traditionally fundamental-mode. cc @nobiot
2021-05-03 19:03:05 +08:00
683605c813 (fix) fix aliases lookup in org-roam-node-from-title-or-alias (#1508)
It should match by title instead of id and the left join is not needed
here as data is not used in any case.
2021-05-03 14:18:04 +08:00
eae1cdc00c try other buffer-file-coding-system fix
Previous fix resulted in slow DB caching, suspect that it doesn't apply
the perf enhancements. Maybe this works? cc @nobiot
2021-05-03 11:41:20 +08:00
1bbc20cfa8 remove primary key constraint on aliases table
this allows a node to have multiple aliases cc @myshevchuk
2021-05-03 11:37:00 +08:00
de94212cdd ensure buffer-encoding is appropriate
don't do a literal find for files, but do all the
org-inhibit-startup/delay-mode-hooks setting in the lexical context.

Addresses #1497?
2021-05-03 04:16:13 +08:00
78144d434b cache completions display format (#1505)
Co-authored-by: Jethro Kuan <jethrokuan95@gmail.com>
2021-05-03 03:52:17 +08:00
55526a4283 Fix auto save buffer in org-roam-doctor (#1493) 2021-05-03 03:36:50 +08:00
d2e933cc3e Fix auto save buffer in org-roam-doctor (#1493) 2021-05-03 03:36:09 +08:00
9405d014af fix: permanent-redisplay (#1504) (#1506)
Related to #1504, commit 109917c
It needs to change dolist for `sections` to `section-functions`
2021-05-03 02:59:41 +08:00
98434919e0 discard inherited property on headline node tags
Addresses #1494
2021-05-03 02:51:51 +08:00
7ed513294d org-roam-node-at-point: include point info
org-roam-node-at-point now returns a node with id and point if the noden
is not yet saved in the database. Fixes #1500.
2021-05-02 17:33:00 +08:00
6fe38b959b fix 2021-05-02 17:23:38 +08:00
44960f189f fix lints 2021-05-02 17:08:02 +08:00
fbe3f2909f Add org-macs where org-with-wide-buffer is called
Addresses https://github.com/org-roam/org-roam/issues/1026#issuecomment-830742020
2021-05-02 17:04:15 +08:00
f8f3870cf0 ensure binary when loading org-roam-db 2021-05-02 16:59:33 +08:00
1bdfc25f80 remove NOTE on foreign key 2021-05-02 16:43:50 +08:00
109917c589 org-roam-mode-sections: Convert to abnormal hook (#1504)
See: #1503
2021-05-02 16:35:36 +08:00
bc4b7fa409 prefer mapconcat over string-join (#1502) 2021-05-02 16:34:43 +08:00
f8dd345bd1 emacsql-sqlite3 -> emacsql-sqlite
Revert from emacsql-sqlite3 to emacsql-sqlite for foreign key support.
This makes it slightly harder to get Org-roam up and running for
Windows, but not impossible, but foreign keys makes database operations
faster and keeps the code clean.

Also, fix schemata for foreign keys on various tables, and add indices
where joins are common (org-roam-db--table-indices).
2021-05-02 16:30:43 +08:00
0a7d365e22 speed up org-roam-db-sync by wrapping db activity in a transaction (#1495)
In my case of testing with 8253 zettels (available on request if
anyone wants to test with them), this improves cleanup speed by 6x
and update speed by 2x.

Improvements will be smaller for smaller batches as this really just
removes the transaction overhead.
2021-04-30 12:51:29 +08:00
389bf3c7e2 Add properties and olp to db 2021-04-29 14:02:14 +08:00
ca1b02829b title: replace link with description on db-build 2021-04-28 13:54:12 +08:00
dd7c3fab23 Merge branch 'master' into v2
* master:
  (doc): Add file-truename to set org-roam-directory (README and documentation) (#1487)
2021-04-28 13:45:23 +08:00
15c1a46e41 (doc): Add file-truename to set org-roam-directory (README and documentation) (#1487)
* (doc): Add file-truename to set org-roam-directory

Refer this debuging Slack exchange:
https://orgroam.slack.com/archives/CV160S8EL/p1619089118195300

Not using `file-truename` to set
`org-roam-directory` can lead to an issue that is
hard to identify.

It appears that cache database may updates with
files but `org-roam-buffer` fails to find the file
as symbolic links do not resolve. This is
confusing as the table query to the table seems to
return what appears to be correct entries, but the
backlink fails to insert context around the
link (as Org-roam fails to find the file).

There have been similiar issues -- by making sure
`file-truename` is added in the documentation and
README, it is hoped to eliminate such issues to
recur.

This is probably relevant for V2.

* undo changes to texi

Co-authored-by: Jethro Kuan <jethrokuan95@gmail.com>
2021-04-28 13:43:42 +08:00
22c8d1032b fix indent 2021-04-28 13:16:08 +08:00
4e0b3fb564 update docs 2021-04-27 23:16:27 +08:00
697c686390 remove roam_tags
See #1492. Primary reason really is that I do want tags to be exactly
like Org tags, so we can reuse some of Org's cool features related to
tags.
2021-04-27 22:51:21 +08:00
68dab59a69 delete new file if org-note-abort
Fixes #1490
2021-04-27 20:11:55 +08:00
2dc5d6281a make tag completions unique 2021-04-27 19:18:42 +08:00
eb9a9e487c add org-roam-tag-{add,remove}
also fix org-roam interactive functions to operate on node
2021-04-27 18:04:53 +08:00
fd23360100 Add back ROAM_TAGS to top-level nodes 2021-04-27 13:04:24 +08:00
57f63461cd (perf): make Org load faster on literal find 2021-04-26 23:20:06 +08:00
d9ba961f96 fix indent 2021-04-26 00:47:40 +08:00
169ebe6367 fix org-roam-dailies-capture-templates
Fixes #1489
2021-04-26 00:28:55 +08:00
15 changed files with 418 additions and 220 deletions

View File

@ -11,6 +11,7 @@
### Changed
- [#1352](https://github.com/org-roam/org-roam/pull/1352) prefer lower-case for roam_tag and roam_alias in interactive commands
- [#1513](https://github.com/org-roam/org-roam/pull/1513) replaced hardcoded "svg" with defcustom org-roam-graph-filetype
### Fixed

View File

@ -47,7 +47,7 @@ Here's a sample configuration with `use-package`:
:hook
(after-init . org-roam-mode)
:custom
(org-roam-directory "/path/to/org-files/")
(org-roam-directory (file-truename "/path/to/org-files/"))
:bind (:map org-roam-mode-map
(("C-c n l" . org-roam)
("C-c n f" . org-roam-find-file)
@ -56,6 +56,9 @@ Here's a sample configuration with `use-package`:
(("C-c n i" . org-roam-insert))))
```
The `file-truename` function is only necessary when you use symbolic links
inside `org-roam-directory`: Org-roam does not resolve symbolic links.
Org-roam requires sqlite to function. Org-roam optionally uses Graphviz for
graph-related functionality. It is recommended to install PCRE-enabled ripgrep
for better performance and extended functionality.

View File

@ -361,9 +361,12 @@ For this tutorial, create an empty directory, and set ~org-roam-directory~:
#+BEGIN_SRC emacs-lisp
(make-directory "~/org-roam")
(setq org-roam-directory "~/org-roam")
(setq org-roam-directory (file-truename "~/org-roam"))
#+END_SRC
The ~file-truename~ function is only necessary when you use symbolic links
inside ~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 ~M-x org-roam-setup~. To ensure that
Org-roam is available on startup, place this in your Emacs configuration:
@ -508,6 +511,17 @@ Alternatively, Org-roam provides some functions to add or remove aliases.
Remove an alias from the node at point.
** Tags
Tags for top-level (file) nodes are pulled from the variable ~org-file-tags~,
which is set by the ~#+filetags~ keyword, as well as other tags the file may
have inherited. Tags for headline level nodes are regular Org tags.
Note that the ~#+filetags~ keyword results in tags being inherited by headers
within the file. This makes it impossible for selective tag inheritance: i.e.
either tag inheritance is turned off, or all headline nodes will inherit the
tags from the file node. This is a design compromise of Org-roam.
** Refs
Refs are unique identifiers for nodes. These keys allow references to the key to

View File

@ -6,7 +6,7 @@
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.0.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (s "1.12.0") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2") (magit-section "2.90.1"))
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
;; This file is NOT part of GNU Emacs.
@ -33,13 +33,16 @@
;;;; Library Requires
(require 'org-capture)
(eval-when-compile
(require 'org-roam-macs))
(require 'org-roam-macs)
(require 'org-macs))
(require 'org-roam-db)
(require 'dash)
(require 'cl-lib)
;; Declarations
(declare-function org-roam-ref-add "org-roam" (ref))
(declare-function org-datetree-find-date-create "org-datetree" (date &optional keep-restriction))
(declare-function org-datetree-find-month-create (d &optional keep-restriction))
(defvar org-roam-directory)
@ -121,15 +124,20 @@ the following options:
The file will be created, prescribed an ID, and head content will be
inserted into the file.
(file+olp \"path/to/file\" '(\"h1\" \"h2\"))
(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.
(file+head+olp \"path/to/file\" \"head content\" '(\"h1\" \"h2\"))
(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.
(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.
The rest of the entry is a property list of additional options. Recognized
properties are:
@ -468,7 +476,12 @@ This function is to be called in the Org-capture finalization process."
"Finalize the `org-roam-capture' process."
(when-let ((region (org-roam-capture--get :region)))
(org-roam-unshield-region (car region) (cdr region)))
(unless org-note-abort
(if org-note-abort
(when-let ((new-file (org-roam-capture--get :new-file)))
(org-roam-message "Deleting file for aborted capture %s" new-file)
(when (find-buffer-visiting new-file)
(kill-buffer (find-buffer-visiting new-file)))
(delete-file new-file))
(when-let ((finalize (org-roam-capture--get :finalize)))
(funcall (intern (concat "org-roam-capture--finalize-"
(symbol-name (org-roam-capture--get :finalize)))))))
@ -515,6 +528,8 @@ Return the ID of the location."
(setq path (expand-file-name
(string-trim (org-roam-capture--fill-template path t))
org-roam-directory))
(unless (file-exists-p path)
(org-roam-capture--put :new-file path))
(set-buffer (org-capture-target-buffer path))
(widen)
(setq p (point)))
@ -523,6 +538,8 @@ Return the ID of the location."
(string-trim (org-roam-capture--fill-template path t))
org-roam-directory))
(set-buffer (org-capture-target-buffer path))
(unless (file-exists-p path)
(org-roam-capture--put :new-file path))
(setq p (point-min))
(let ((m (org-roam-capture-find-or-create-olp olp)))
(goto-char m))
@ -531,10 +548,10 @@ Return the ID of the location."
(setq path (expand-file-name
(string-trim (org-roam-capture--fill-template path t))
org-roam-directory))
(let ((exists-p (file-exists-p path)))
(set-buffer (org-capture-target-buffer path))
(unless exists-p
(insert (org-roam-capture--fill-template head t))))
(set-buffer (org-capture-target-buffer path))
(unless (file-exists-p path)
(org-roam-capture--put :new-file path)
(insert (org-roam-capture--fill-template head t)))
(widen)
(setq p (point-min)))
(`(file+head+olp ,path ,head ,olp)
@ -542,13 +559,59 @@ Return the ID of the location."
(string-trim (org-roam-capture--fill-template path t))
org-roam-directory))
(widen)
(let ((exists-p (file-exists-p path)))
(set-buffer (org-capture-target-buffer path))
(unless exists-p
(insert (org-roam-capture--fill-template head t))))
(set-buffer (org-capture-target-buffer path))
(unless (file-exists-p path)
(org-roam-capture--put :new-file path)
(insert (org-roam-capture--fill-template head t)))
(setq p (point-min))
(let ((m (org-roam-capture-find-or-create-olp olp)))
(goto-char m)))
(`(file+datetree ,path ,tree-type)
(setq path (expand-file-name
(string-trim (org-roam-capture--fill-template path t))
org-roam-directory))
(require 'org-datetree)
(widen)
(set-buffer (org-capture-target-buffer path))
(unless (file-exists-p path)
(org-roam-capture--put :new-file path))
(funcall
(pcase tree-type
(`week #'org-datetree-find-iso-week-create)
(`month #'org-datetree-find-month-create)
(_ #'org-datetree-find-date-create))
(calendar-gregorian-from-absolute
(cond
(org-overriding-default-time
;; Use the overriding default time.
(time-to-days org-overriding-default-time))
((org-capture-get :default-time)
(time-to-days (org-capture-get :default-time)))
((org-capture-get :time-prompt)
;; Prompt for date. Bind `org-end-time-was-given' so
;; that `org-read-date-analyze' handles the time range
;; case and returns `prompt-time' with the start value.
(let* ((org-time-was-given nil)
(org-end-time-was-given nil)
(prompt-time (org-read-date
nil t nil "Date for tree entry:")))
(org-capture-put
:default-time
(if (or org-time-was-given
(= (time-to-days prompt-time) (org-today)))
prompt-time
;; Use 00:00 when no time is given for another
;; date than today?
(apply #'encode-time 0 0
org-extend-today-until
(cl-cdddr (decode-time prompt-time)))))
(time-to-days prompt-time)))
(t
;; Current date, possibly corrected for late night
;; workers.
(org-today)))))
(org-end-of-subtree t t)
(setq p (point)))
(`(node ,title-or-id)
;; first try to get ID, then try to get title/alias
(let ((node (or (org-roam-node-from-id title-or-id)
@ -560,8 +623,9 @@ Return the ID of the location."
(org-end-of-subtree t t))))
(save-excursion
(goto-char p)
(run-hooks 'org-roam-capture-new-node-hook)
(org-id-get-create))))
(prog1
(org-id-get-create)
(run-hooks 'org-roam-capture-new-node-hook)))))
(defun org-roam-capture-find-or-create-olp (olp)
"Return a marker pointing to the entry at OLP in the current buffer.
@ -696,7 +760,9 @@ TEMPLATES is a list of org-roam templates."
(when (and (not keys)
(= (length org-capture-templates) 1))
(setq keys (caar org-capture-templates)))
(org-capture goto keys)))
(org-capture goto keys)
(unless goto
(org-cycle-hide-drawers 'all))))
;;;###autoload
(defun org-roam-capture (&optional goto keys)

View File

@ -6,7 +6,7 @@
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.0.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2") (magit-section "2.90.1"))
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
;; This file is NOT part of GNU Emacs.

View File

@ -6,7 +6,7 @@
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.0.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2") (magit-section "2.90.1"))
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
;; This file is NOT part of GNU Emacs.
;; This program is free software; you can redistribute it and/or modify

View File

@ -8,7 +8,7 @@
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.0.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2") (magit-section "2.90.1"))
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
;; This file is NOT part of GNU Emacs.
@ -61,10 +61,10 @@
:type 'hook)
(defcustom org-roam-dailies-capture-templates
'(("d" "default" entry
`(("d" "default" entry
"* %?"
:if-new `(file+head ,(concat org-roam-dailies-directory "%<%Y-%m-%d>.org")
"#+title: %<%Y-%m-%d>\n")))
:if-new (file+head ,(expand-file-name "%<%Y-%m-%d>.org" org-roam-dailies-directory)
"#+title: %<%Y-%m-%d>\n")))
"Capture templates for daily-notes in Org-roam.
See `org-roam-capture-templates' for the template documentation."
:group 'org-roam

View File

@ -6,7 +6,7 @@
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.0.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2") (magit-section "2.90.1"))
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
;; This file is NOT part of GNU Emacs.
@ -33,7 +33,7 @@
;;;; Library Requires
(eval-when-compile (require 'subr-x))
(require 'emacsql)
(require 'emacsql-sqlite3)
(require 'emacsql-sqlite)
(require 'seq)
(eval-and-compile
@ -79,7 +79,11 @@ value like `most-positive-fixnum'."
:type 'int
:group 'org-roam)
(defconst org-roam-db--version 12)
(defconst org-roam-db--version 15)
(defconst org-roam--sqlite-available-p
(with-demoted-errors "Org-roam initialization: %S"
(emacsql-sqlite-ensure-binary)
t))
(defvar org-roam-db--connection (make-hash-table :test #'equal)
"Database connection to Org-roam database.")
@ -99,7 +103,7 @@ 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-sqlite3 org-roam-db-location)))
(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)
conn
@ -129,51 +133,53 @@ SQL can be either the emacsql vector representation, or a string."
(apply #'emacsql (org-roam-db) sql args)))
;;;; Schemata
;; NOTE: Foreign key somehow doesn't work! Adding a file column to every table as a workaround.
(defconst org-roam-db--table-schemata
'((files
[(file :unique :primary-key)
(hash :not-null)])
(nodes
[(id :primary-key :not-null)
(file :not-null)
(level :not-null)
(pos :not-null)
todo
priority
(scheduled text)
(deadline text)
title]
(:foreign-key [file] :references files [file] :on-delete :cascade))
([(id :not-null :primary-key)
(file :not-null)
(level :not-null)
(pos :not-null)
todo
priority
(scheduled text)
(deadline text)
title
properties
olp]
(:foreign-key [file] :references files [file] :on-delete :cascade)))
(aliases
[(file :not-null)
(node-id :not-null)
alias]
(:foreign-key [node-id] :references nodes [id] :on-delete :cascade))
([(node-id :not-null)
alias]
(:foreign-key [node-id] :references nodes [id] :on-delete :cascade)))
(refs
([(file :not-null)
(node-id :not-null)
([(node-id :not-null)
(ref :not-null)
(type :not-null)]
(:foreign-key [node-id] :references nodes [id] :on-delete :cascade)))
(tags
[(file :not-null)
(node-id :not-null)
tag]
(:foreign-key [node-id] :references nodes [id] :on-delete :cascade))
([(node-id :not-null)
tag]
(:foreign-key [node-id] :references nodes [id] :on-delete :cascade)))
(links
[(file :not-null)
(pos :not-null)
(source :not-null)
(dest :not-null)
(type :not-null)
(properties :not-null)]
(:foreign-key [file] :references files [file] :on-delete :cascade))))
([(pos :not-null)
(source :not-null)
(dest :not-null)
(type :not-null)
(properties :not-null)]
(:foreign-key [source] :references nodes [id] :on-delete :cascade)))))
(defconst org-roam-db--table-indices
'((alias-node-id aliases [node-id])
(refs-node-id refs [node-id])
(tags-node-id tags [node-id])))
(defun org-roam-db--init (db)
"Initialize database DB with the correct schema and user version."
@ -181,6 +187,8 @@ SQL can be either the emacsql vector representation, or a string."
(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)
(emacsql db [:create-index $i1 :on $i2 $S3] index-name table columns))
(emacsql db (format "PRAGMA user_version = %s" org-roam-db--version))))
(defun org-roam-db--upgrade-maybe (db version)
@ -222,10 +230,9 @@ the current `org-roam-directory'."
This is equivalent to removing the node from the graph.
If FILE is nil, clear the current buffer."
(setq file (or file (buffer-file-name (buffer-base-buffer))))
(dolist (table (mapcar #'car org-roam-db--table-schemata))
(org-roam-db-query `[:delete :from ,table
:where (= file $s1)]
file)))
(org-roam-db-query [:delete :from files
:where (= file $s1)]
file))
;;;;; Updating tables
(defun org-roam-db-insert-file ()
@ -270,9 +277,10 @@ If UPDATE-P is non-nil, first remove the file in the database."
(when (= (org-outline-level) 0)
(when-let ((id (org-id-get)))
(let* ((file (buffer-file-name (buffer-base-buffer)))
(title (or (cadr (assoc "TITLE" (org-collect-keywords '("title"))
#'string-equal))
(file-relative-name file org-roam-directory)))
(title (org-link-display-format
(or (cadr (assoc "TITLE" (org-collect-keywords '("title"))
#'string-equal))
(file-relative-name file org-roam-directory))))
(pos (point))
(todo nil)
(priority nil)
@ -281,46 +289,44 @@ If UPDATE-P is non-nil, first remove the file in the database."
(level 0)
(aliases (org-entry-get (point) "ROAM_ALIASES"))
(tags org-file-tags)
(refs (org-entry-get (point) "ROAM_REFS")))
(condition-case nil
(progn
(refs (org-entry-get (point) "ROAM_REFS"))
(properties (org-entry-properties))
(olp (org-get-outline-path)))
(org-roam-db-query
[:insert :into nodes
:values $v1]
(vector id file level pos todo priority
scheduled deadline title properties olp))
(when tags
(org-roam-db-query
[:insert :into tags
:values $v1]
(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 nodes
[:insert :into refs
:values $v1]
(vector id file level pos todo priority
scheduled deadline title))
(when tags
(org-roam-db-query
[:insert :into tags
:values $v1]
(mapcar (lambda (tag)
(vector file id (substring-no-properties tag)))
tags)))
(when aliases
(org-roam-db-query
[:insert :into aliases
:values $v1]
(mapcar (lambda (alias)
(vector file 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 file 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)))))
(t
(lwarn '(org-roam) :error "Duplicate ID %s, skipping..." id))))))))
rows)))))))))
(defun org-roam-db-insert-node-data ()
"Insert node data for headline at point into the Org-roam cache."
@ -333,16 +339,14 @@ If UPDATE-P is non-nil, first remove the file in the database."
(level (nth 1 heading-components))
(scheduled (org-roam-db-get-scheduled-time))
(deadline (org-roam-db-get-deadline-time))
(title (nth 4 heading-components)))
(condition-case nil
(org-roam-db-query
[:insert :into nodes
:values $v1]
(vector id file level pos todo priority
scheduled deadline title))
(t
(lwarn '(org-roam) :error
"Duplicate ID %s, skipping..." id))))))
(title (org-link-display-format (nth 4 heading-components)))
(properties (org-entry-properties))
(olp (org-get-outline-path)))
(org-roam-db-query
[:insert :into nodes
:values $v1]
(vector id file level pos todo priority
scheduled deadline title properties olp)))))
(defun org-roam-db-insert-aliases ()
"Insert aliases for node at point into Org-roam cache."
@ -357,18 +361,16 @@ If UPDATE-P is non-nil, first remove the file in the database."
(defun org-roam-db-insert-tags ()
"Insert tags for node at point into Org-roam cache."
(when-let ((file (buffer-file-name (buffer-base-buffer)))
(node-id (org-id-get))
(when-let ((node-id (org-id-get))
(tags (org-get-tags)))
(org-roam-db-query [:insert :into tags
:values $v1]
(mapcar (lambda (tag)
(vector file node-id tag)) tags))))
(vector node-id (substring-no-properties tag))) tags))))
(defun org-roam-db-insert-refs ()
"Insert refs for node at point into Org-roam cache."
(when-let* ((file (buffer-file-name (buffer-base-buffer)))
(node-id (org-id-get))
(when-let* ((node-id (org-id-get))
(refs (org-entry-get (point) "ROAM_REFS"))
(refs (split-string-and-unquote refs)))
(let (rows)
@ -376,7 +378,7 @@ If UPDATE-P is non-nil, first remove the file in the database."
(save-match-data
(if (string-match org-link-plain-re ref)
(progn
(push (vector file node-id (match-string 2 ref) (match-string 1 ref)) rows))
(push (vector node-id (match-string 2 ref) (match-string 1 ref)) rows))
(lwarn '(org-roam) :warning
"%s:%s\tInvalid ref %s, skipping..." (buffer-file-name) (point) ref))))
(org-roam-db-query [:insert :into refs
@ -387,8 +389,7 @@ If UPDATE-P is non-nil, first remove the file in the database."
"Insert link data for LINK at current point into the Org-roam cache."
(save-excursion
(goto-char (org-element-property :begin link))
(let ((file (buffer-file-name (buffer-base-buffer)))
(type (org-element-property :type link))
(let ((type (org-element-property :type link))
(dest (org-element-property :path link))
(properties (list :outline (org-get-outline-path)))
(source (org-roam-id-at-point)))
@ -396,7 +397,7 @@ If UPDATE-P is non-nil, first remove the file in the database."
(org-roam-db-query
[:insert :into links
:values $v1]
(vector file (point) source dest type properties))))))
(vector (point) source dest type properties))))))
;;;;; Fetching
(defun org-roam-db--get-current-files ()
@ -436,18 +437,19 @@ If FORCE, force a rebuild of the cache from scratch."
contents-hash)
(push file modified-files)))
(remhash file current-files))
(if (fboundp 'dolist-with-progress-reporter)
(dolist-with-progress-reporter (file (hash-table-keys current-files))
"Clearing removed files..."
(org-roam-db-clear-file file))
(dolist (file (hash-table-keys current-files))
(org-roam-db-clear-file file)))
(if (fboundp 'dolist-with-progress-reporter)
(dolist-with-progress-reporter (file modified-files)
"Processing modified files..."
(org-roam-db-update-file file))
(dolist (file modified-files)
(org-roam-db-update-file file)))))
(emacsql-with-transaction (org-roam-db)
(if (fboundp 'dolist-with-progress-reporter)
(dolist-with-progress-reporter (file (hash-table-keys current-files))
"Clearing removed files..."
(org-roam-db-clear-file file))
(dolist (file (hash-table-keys current-files))
(org-roam-db-clear-file file)))
(if (fboundp 'dolist-with-progress-reporter)
(dolist-with-progress-reporter (file modified-files)
"Processing modified files..."
(org-roam-db-update-file file))
(dolist (file modified-files)
(org-roam-db-update-file file))))))
(defun org-roam-db-update-file (&optional file-path)
"Update Org-roam cache for FILE-PATH.

View File

@ -6,7 +6,7 @@
;; URL: https://github.com/jethrokuan/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.0.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2") (magit-section "2.90.1"))
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
;; This file is NOT part of GNU Emacs.
@ -212,7 +212,8 @@ If CHECKALL, run the check for all Org-roam files."
(let ((buf (find-file-noselect f)))
(org-roam-doctor--check buf checkers)
(unless (memq buf existing-buffers)
(save-buffer buf)
(with-current-buffer buf
(save-buffer))
(kill-buffer buf))))))
(org-roam-message "Linting completed."))

View File

@ -6,7 +6,7 @@
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.0.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2") (magit-section "2.90.1"))
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
;; This file is NOT part of GNU Emacs.
@ -48,6 +48,7 @@ If FILE is nil, execute BODY in the current buffer.
Kills the buffer if KEEP-BUF-P is nil, and FILE is not yet visited."
(declare (indent 2) (debug t))
`(let* (new-buf
(auto-mode-alist 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
@ -57,7 +58,10 @@ Kills the buffer if KEEP-BUF-P is nil, and FILE is not yet visited."
res)
(with-current-buffer buf
(unless (equal major-mode 'org-mode)
(delay-mode-hooks (org-mode)))
(delay-mode-hooks
(let ((org-inhibit-startup t)
(org-agenda-files nil))
(org-mode))))
(setq res (progn ,@body))
(unless (and new-buf (not ,keep-buf-p))
(save-buffer)))

View File

@ -5,7 +5,7 @@
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.0.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2") (magit-section "2.90.1"))
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
;; This file is NOT part of GNU Emacs.
@ -124,11 +124,12 @@ and `:slant'."
(defvar org-roam-current-node nil
"The current node at point.")
(defcustom org-roam-mode-sections (list #'org-roam-backlinks-section
#'org-roam-reflinks-section)
"List of functions that insert sections for Org-roam."
(defcustom org-roam-mode-section-functions (list #'org-roam-backlinks-section
#'org-roam-reflinks-section)
"Functions which insert sections of the `org-roam-buffer'.
Each function is called with one argument, which is the current org-roam node at point."
:group 'org-roam
:type '(repeat function))
:type 'hook)
;;; The mode
(defvar org-roam-mode-map
@ -162,8 +163,7 @@ which visits the thing at point."
(org-roam-set-header-line-format (org-roam-node-title org-roam-current-node))
(magit-insert-section (org-roam)
(magit-insert-heading)
(dolist (fn org-roam-mode-sections)
(funcall fn org-roam-current-node))))))
(run-hook-with-args 'org-roam-mode-section-functions org-roam-current-node)))))
(defun org-roam-buffer ()
"Launch an Org-roam buffer for the current node at point."
@ -230,7 +230,7 @@ Has no effect when `org-roam-current-node' is nil."
(org-roam-set-header-line-format (org-roam-node-title org-roam-current-node))
(magit-insert-section (org-roam)
(magit-insert-heading)
(dolist (fn org-roam-mode-sections)
(dolist (fn org-roam-mode-section-functions)
(funcall fn org-roam-current-node)))))))
(defun org-roam-buffer--redisplay ()
@ -419,9 +419,9 @@ References from FILE are excluded."
(let* ((titles (cons (org-roam-node-title node)
(org-roam-node-aliases node)))
(rg-command (concat "rg -o --vimgrep -P -i "
(string-join (mapcar (lambda (glob) (concat "-g " glob))
(org-roam--list-files-search-globs
org-roam-file-extensions)) " ")
(mapconcat (lambda (glob) (concat "-g " glob))
(org-roam--list-files-search-globs org-roam-file-extensions)
" ")
(format " '\\[([^[]]++|(?R))*\\]%s' "
(mapconcat (lambda (title)
(format "|(\\b%s\\b)" (shell-quote-argument title)))

View File

@ -5,7 +5,7 @@
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.0.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2") (magit-section "2.90.1"))
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
;; This file is NOT part of GNU Emacs.

View File

@ -6,7 +6,7 @@
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.0.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2") (magit-section "2.90.1"))
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
;; This file is NOT part of GNU Emacs.

View File

@ -6,7 +6,7 @@
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.0.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2") (magit-section "2.90.1"))
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
;; This file is NOT part of GNU Emacs.
@ -156,20 +156,24 @@ Adapted from `s-format'."
t t)
(set-match-data saved-match-data))))
(defvar org-roam--cached-display-format nil)
(defun org-roam--process-display-format (format)
"Pre-calculate minimal widths needed by the FORMAT string."
(let* ((fields-width 0)
(string-width
(string-width
(org-roam-format
format
(lambda (field)
(setq fields-width
(+ fields-width
(string-to-number
(or (cadr (split-string field ":"))
"")))))))))
(cons format (+ fields-width string-width))))
(or org-roam--cached-display-format
(setq org-roam--cached-display-format
(let* ((fields-width 0)
(string-width
(string-width
(org-roam-format
format
(lambda (field)
(setq fields-width
(+ fields-width
(string-to-number
(or (cadr (split-string field ":"))
"")))))))))
(cons format (+ fields-width string-width))))))
;;; Diagnostics
;;;###autoload

View File

@ -6,7 +6,7 @@
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.0.0
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite3 "1.0.2") (magit-section "2.90.1"))
;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
;; This file is NOT part of GNU Emacs.
@ -50,7 +50,8 @@
;;;; Features
(require 'org-roam-compat)
(eval-when-compile
(require 'org-roam-macs))
(require 'org-roam-macs)
(require 'org-macs))
(require 'org-roam-utils)
(require 'org-roam-mode)
(require 'org-roam-completion)
@ -152,7 +153,8 @@ method symbol as a cons cell. For example: '(find (rg . \"/path/to/rg\"))."
)
"Characters to trim from Unicode normalization for slug.
By default, the characters are specified to remove Diacritical Marks from the Latin alphabet."
By default, the characters are specified to remove Diacritical
Marks from the Latin alphabet."
:type '(repeat character)
:group 'org-roam)
@ -341,11 +343,19 @@ Use external shell commands if defined in `org-roam-list-files-commands'."
(puthash node-id (cons tag (gethash node-id ht)) ht))
ht))
(defun org-roam--org-roam-buffer-p (&optional buffer)
"Return t if BUFFER is accessing a part of Org-roam system.
If BUFFER is not specified, use the current buffer."
(let ((buffer (or buffer (current-buffer)))
path)
(with-current-buffer buffer
(and (derived-mode-p 'org-mode)
(setq path (buffer-file-name (buffer-base-buffer)))
(org-roam--org-roam-file-p path)))))
(defun org-roam--get-roam-buffers ()
"Return a list of buffers that are Org-roam files."
(--filter (and (with-current-buffer it (derived-mode-p 'org-mode))
(buffer-file-name it)
(org-roam--org-roam-file-p (buffer-file-name it)))
(--filter (org-roam--org-roam-buffer-p it)
(buffer-list)))
(defun org-roam--get-titles ()
@ -421,7 +431,7 @@ OLD-FILE is cleared from the database, and NEW-FILE-OR-DIR is added."
;;;; Nodes
(cl-defstruct (org-roam-node (:constructor org-roam-node-create)
(:copier nil))
id file level point todo priority scheduled deadline title
id file level point todo priority scheduled deadline title properties olp
tags aliases refs)
(cl-defmethod org-roam-node-slug ((node org-roam-node))
@ -471,45 +481,47 @@ OLD-FILE is cleared from the database, and NEW-FILE-OR-DIR is added."
Uses the ID, and fetches remaining details from the database.
This can be quite costly: avoid, unless dealing with very few
nodes."
(let ((node-info (car (org-roam-db-query [:select [file level pos todo priority scheduled deadline title]
:from nodes
:where (= id $s1)
:limit 1]
(org-roam-node-id node))))
(tag-info (mapcar #'car (org-roam-db-query [:select [tag] :from tags
:where (= node-id $s1)]
(org-roam-node-id node))))
(alias-info (mapcar #'car (org-roam-db-query [:select [alias] :from aliases
(when-let ((node-info (car (org-roam-db-query [:select [file level pos todo priority
scheduled deadline title properties olp]
:from nodes
:where (= id $s1)
:limit 1]
(org-roam-node-id node)))))
(let ((tag-info (mapcar #'car (org-roam-db-query [:select [tag] :from tags
:where (= node-id $s1)]
(org-roam-node-id node))))
(refs-info (mapcar #'car (org-roam-db-query [:select [ref] :from refs
:where (= node-id $s1)]
(org-roam-node-id node)))))
(pcase-let ((`(,file ,level ,pos ,todo ,priority ,scheduled ,deadline ,title) node-info))
(setf (org-roam-node-file node) file
(org-roam-node-level node) level
(org-roam-node-point node) pos
(org-roam-node-todo node) todo
(org-roam-node-priority node) priority
(org-roam-node-scheduled node) scheduled
(org-roam-node-deadline node) deadline
(org-roam-node-title node) title
(org-roam-node-tags node) tag-info
(org-roam-node-refs node) refs-info
(org-roam-node-aliases node) alias-info))
node))
(alias-info (mapcar #'car (org-roam-db-query [:select [alias] :from aliases
:where (= node-id $s1)]
(org-roam-node-id node))))
(refs-info (mapcar #'car (org-roam-db-query [:select [ref] :from refs
:where (= node-id $s1)]
(org-roam-node-id node)))))
(pcase-let ((`(,file ,level ,pos ,todo ,priority ,scheduled
,deadline ,title ,properties ,olp) node-info))
(setf (org-roam-node-file node) file
(org-roam-node-level node) level
(org-roam-node-point node) pos
(org-roam-node-todo node) todo
(org-roam-node-priority node) priority
(org-roam-node-scheduled node) scheduled
(org-roam-node-deadline node) deadline
(org-roam-node-title node) title
(org-roam-node-properties node) properties
(org-roam-node-olp node) olp
(org-roam-node-tags node) tag-info
(org-roam-node-refs node) refs-info
(org-roam-node-aliases node) alias-info))))
node)
(defcustom org-roam-node-display-template
"${title:*} ${tags:10}"
"${title:*} ${tags:10}"
"Configures display formatting for Org-roam node."
:group 'org-roam
:type 'string)
(defun org-roam--tags-to-str (tags)
"Convert list of TAGS into a string."
(string-join
(mapcar (lambda (s) (concat "#" s)) tags)
" "))
(mapconcat (lambda (s) (concat "#" s)) tags " "))
(defun org-roam-node--format-entry (node width)
"Formats NODE for display in the results list.
@ -529,6 +541,9 @@ WIDTH is the width of the results list."
(when (and (equal field-name "file")
field-value)
(setq field-value (file-relative-name field-value org-roam-directory)))
(when (and (equal field-name "olp")
field-value)
(setq field-value (string-join field-value " > ")))
(if (not field-width)
field-value
(setq field-width (string-to-number field-width))
@ -556,8 +571,15 @@ WIDTH is the width of the results list."
If ASSERT, throw an error."
(if-let ((node (magit-section-case
(org-roam-node-section (oref it node))
(t (when-let ((id (org-roam-id-at-point)))
(org-roam-populate (org-roam-node-create :id id)))))))
(t (let (id)
(org-with-wide-buffer
(while (and (not (setq id (org-id-get)))
(not (bobp)))
(org-roam-up-heading-or-point-min))
(when id
(org-roam-populate
(org-roam-node-create :id id
:point (point))))))))))
node
(when assert
(user-error "No node at point"))))
@ -601,8 +623,7 @@ Throw an error if multiple choices exist."
:where (= title $s1)]
s)
(org-roam-db-query [:select [node-id] :from aliases
:left :join nodes :on (= nodes:id aliases:node-id)
:where (= aliases:node-id $s1)]
:where (= alias $s1)]
s)))))
(cond
((seq-empty-p matches)
@ -616,19 +637,22 @@ Throw an error if multiple choices exist."
"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'."
(setq org-roam--cached-display-format nil)
(let ((tags-table (org-roam--tags-table)))
(cl-loop for row in (append
(org-roam-db-query [:select [file pos title title id]
(org-roam-db-query [:select [file pos title title id properties olp]
:from nodes])
(org-roam-db-query [:select [nodes:file pos alias title node-id]
:from aliases
:left-join nodes
:on (= aliases:node-id nodes:id)]))
collect (pcase-let* ((`(,file ,pos ,alias ,title ,id) row)
collect (pcase-let* ((`(,file ,pos ,alias ,title ,id ,properties ,olp) row)
(node (org-roam-node-create :id id
:file file
:title alias
:point pos
:properties properties
:olp olp
:tags (gethash id tags-table)))
(candidate-main (org-roam-node--format-entry node (1- (frame-width))))
(tag-str (org-roam--tags-to-str (org-roam-node-tags node))))
@ -691,8 +715,7 @@ POINT is the point in buffer for the link.
PROPERTIES contains properties about the link."
(magit-insert-section section (org-roam-node-section)
(let ((outline (if-let ((outline (plist-get properties :outline)))
(string-join (mapcar #'org-link-display-format outline)
" > ")
(mapconcat #'org-link-display-format outline " > ")
"Top")))
(insert (concat (propertize (org-roam-node-title source-node)
'font-lock-face 'org-roam-title)
@ -779,41 +802,121 @@ window instead."
(let* ((p (org-entry-get (point) prop))
(lst (when p (split-string-and-unquote p)))
(lst (if (memq s lst) lst (cons s lst))))
(org-set-property prop (combine-and-quote-strings lst))))
(org-set-property prop (combine-and-quote-strings lst))
s))
(defun org-roam-remove-property (prop)
"Prompt to remove an item from PROP."
(defun org-roam-remove-property (prop &optional s)
"Remove S from property PROP.
If S 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 (completing-read "Remove: " lst))
(prop-to-remove (or s (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))))
(org-delete-property prop))
prop-to-remove))
(defun org-roam-set-keyword (key value)
"Set keyword KEY to VALUE.
If the property is already set, it's value is replaced."
(org-with-point-at 1
(let ((case-fold-search t))
(if (re-search-forward (concat "^#\\+" key ":\\(.*\\)") (point-max) t)
(if (string-blank-p value)
(kill-whole-line)
(replace-match (concat " " value) 'fixedcase nil nil 1))
(while (and (not (eobp))
(looking-at "^[#:]"))
(if (save-excursion (end-of-line) (eobp))
(progn
(end-of-line)
(insert "\n"))
(forward-line)
(beginning-of-line)))
(insert "#+" key ": " value "\n")))))
;;;; Tags
(defun org-roam-tag-completions ()
"Return list of tags for completions within Org-roam."
(let ((roam-tags (mapcar #'car (org-roam-db-query [:select :distinct [tag] :from tags])))
(org-tags (cl-loop for tagg in org-tag-alist
nconc (pcase tagg
('(:newline)
nil)
(`(,tag . ,_)
(list tag))
(_ nil)))))
(seq-uniq (append roam-tags org-tags))))
(defun org-roam-tag-add (tag)
"Add TAG to the node at point."
(interactive
(list (completing-read "Tag: " (org-roam-tag-completions))))
(let ((node (org-roam-node-at-point 'assert)))
(save-excursion
(goto-char (org-roam-node-point node))
(if (= (org-outline-level) 0)
(let ((current-tags (split-string (or (cadr (assoc "FILETAGS"
(org-collect-keywords '("filetags"))))
""))))
(org-roam-set-keyword "filetags" (string-join (seq-uniq (cons tag current-tags)) " ")))
(org-set-tags (seq-uniq (cons tag (org-get-tags)))))
tag)))
(defun org-roam-tag-remove (&optional tag)
"Remove a TAG to the node at point."
(interactive)
(let ((node (org-roam-node-at-point 'assert)))
(save-excursion
(goto-char (org-roam-node-point node))
(if (= (org-outline-level) 0)
(let* ((current-tags (split-string (or (cadr (assoc "FILETAGS"
(org-collect-keywords '("filetags"))))
(user-error "No tag to remove"))))
(tag (or tag (completing-read "Tag: " current-tags))))
(org-roam-set-keyword "filetags" (string-join (delete tag current-tags) " ")))
(let* ((current-tags (or (org-get-tags)
(user-error "No tag to remove")))
(tag (completing-read "Tag: " current-tags)))
(org-set-tags (delete tag current-tags))))
tag)))
;;;; Aliases
(defun org-roam-alias-add (alias)
"Add ALIAS to the node at point."
(interactive "sAlias: ")
(org-roam-add-property alias "ROAM_ALIASES"))
(let ((node (org-roam-node-at-point 'assert)))
(save-excursion
(goto-char (org-roam-node-point node))
(org-roam-add-property alias "ROAM_ALIASES"))))
(defun org-roam-alias-remove ()
"Remove an alias from the node at point."
(defun org-roam-alias-remove (&optional alias)
"Remove an ALIAS from the node at point."
(interactive)
(org-roam-remove-property "ROAM_ALIASES"))
(let ((node (org-roam-node-at-point 'assert)))
(save-excursion
(goto-char (org-roam-node-point node))
(org-roam-remove-property "ROAM_ALIASES" alias))))
;;;; Refs
(defun org-roam-ref-add (ref)
"Add REF to the node at point."
(interactive "sRef: ")
(org-roam-add-property ref "ROAM_REFS"))
(let ((node (org-roam-node-at-point 'assert)))
(save-excursion
(goto-char (org-roam-node-point node))
(org-roam-add-property ref "ROAM_REFS"))))
(defun org-roam-ref-remove ()
"Remove a ref from the node at point."
(defun org-roam-ref-remove (&optional ref)
"Remove a REF from the node at point."
(interactive)
(org-roam-remove-property "ROAM_REFS"))
(let ((node (org-roam-node-at-point 'assert)))
(save-excursion
(goto-char (org-roam-node-point node))
(org-roam-remove-property "ROAM_REFS" ref))))
;;;; Refs
(defun org-roam-ref--completions ()
"Return an alist for ref completion.
The car is the ref, and the cdr is the corresponding node for the ref."