Compare commits

...

13 Commits

Author SHA1 Message Date
57fadf54a6 test: add tests for link replacement optimization
Added integration tests for the roam link replacement functionality
to validate the performance optimization that limits the scope of
link replacement by searching specifically for "[[roam:" instead
of any org link bracket pattern. The tests ensure:

1. Special regex characters in buffer content don't break the
   search functionality when using search-forward
2. Only roam: links are processed, not other link types like
   file: or https:

These tests validate the optimization maintains correctness while
improving performance by avoiding unnecessary replacement attempts
on non-roam links.

Ref: fc8638759b
Ref: 89dfaef38b
2025-07-01 22:41:27 -07:00
54a6eeaf1d test: fix org-roam-demote-entire-buffer leaving git dirty
The test was modifying tests/roam-files/demoteable.org directly,
causing the file to be left in a modified state after test runs.
This left git in a dirty state and caused subsequent test failures
when the database expected the original file contents.

Changed the test to use a temporary copy of the file instead,
ensuring the original test data remains unmodified.
2025-07-01 21:35:16 -07:00
89dfaef38b Fix link replacement optimization
We are searching for a string literal for speed, so don’t treat it as a
regexp.

Fixes: fc8638759b ("feat: Limit link replacement scope")
Fixes: #2529
2025-07-01 00:28:11 -05:00
fc8638759b feat: Limit link replacement scope
Test for `roam:` link prefix to limit the number of times we need to
call `org-roam-link-replace-at-point`.
2025-06-29 19:44:00 -05:00
1958e035fc build: do not lock issues
Allow folks to revisit issues at any time if they want to restart the conversation.

See Discourse conversation: https://org-roam.discourse.group/t/org-roam-development-status-may-2025/3810/15

Amend: d099204129
2025-06-28 15:38:16 -07:00
1ea7e3077c fix: run org-roam-db-clear-file on file deletion via vc-delete-file 2025-06-26 14:54:27 -07:00
7ce95a286b release 2.3.1 2025-06-26 11:55:56 -07:00
c172951345 nit: fix indentation 2025-06-09 01:20:49 -07:00
0786f73669 fix: write unlinked references regex to a temp file
Node titles with special characters (single quotes, dollar signs, etc.)
break the ripgrep command because the regex pattern is passed through
the shell. This causes silent failures that show up as unlinked
references not being present for a given node.

This change writes the regex pattern to a temp file and uses ripgrep's
--file option instead of shell command line. `shell-quote-argument` is
replaced with `regexp-quote` since we're no longer passing through
shell. Wrapped in unwind-protect for cleanup.

Fix: #2407
Close: #2408
2025-06-09 00:39:50 -07:00
df4e903208 perf: suppress extra org features in the temp buffer
Figuring out how to indent things and folding and clock updates, etc.
consumes a great deal of time especially considering that files
are (currently) processed for /every single backlink/ even if multiple
backlinks come from the same file.

This dramatically improves the load/refresh time for the org-roam
buffer.

Fix: #2399
2025-06-07 19:58:20 -07:00
d099204129 build: tidy up old issues
To keep us from having to triage a ton of old issues/PRs that may not be
relevant anymore; this should give us a fresh(ish) start.
2025-06-07 10:27:45 -07:00
031ee63bee (fix): Use correct type specifications to suppress warnings 2025-05-27 08:58:53 -07:00
fed577f805 refactor: improve speed of retrieving and formatting nodes
The original code uses macros that when expanded use a lot memory
and are relatively slow. This removes some of that processing:

- Add a constructor to org-roam-node that does uses parameters
  by position instead or by name

- Add the option of using a function to format nodes. The current
  code uses a string template. But this template is processes
  for every node. Using a function will further improve
  performance significantly.

Simplified expected return value user's template function.

As suggested by akashpal-21, the return value of the user's template
function can be simpler.

I have refactored the code such that the function only returns
a string (potentially propertized) with the formatted node.

I have also modified the documentation of
org-roam-node-display-template with an example of the user defined
function.

Acknowledgement: akashpal-21 for the suggestion for improvement

Close: #2511
2025-05-25 00:15:21 -07:00
22 changed files with 332 additions and 101 deletions

93
.github/workflows/tidy-issues.yml vendored Normal file
View File

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

View File

@ -1,5 +1,10 @@
# Changelog
## 2.3.1 (2025-06-26)
* (fix): Use correct type specifications to suppress warnings by @okomestudio in https://github.com/org-roam/org-roam/pull/2522
* perf: suppress extra org features in the temp buffer by @dustinfarris in https://github.com/org-roam/org-roam/pull/2524
## 2.3.0 (2025-05-24)
* (perf)node-read: filter before map to candidate by @russmatney in https://github.com/org-roam/org-roam/pull/2168

View File

@ -8,13 +8,13 @@
#+texinfo_dir_category: Emacs
#+texinfo_dir_title: Org-roam: (org-roam).
#+texinfo_dir_desc: Roam Research for Emacs.
#+subtitle: for version 2.3.0
#+subtitle: for version 2.3.1
#+options: H:4 num:3 toc:nil creator:t ':t
#+property: header-args :eval never
#+texinfo: @noindent
This manual is for Org-roam version 2.3.0.
This manual is for Org-roam version 2.3.1.
#+BEGIN_QUOTE
Copyright (C) 2020-2025 Jethro Kuan <jethrokuan95@gmail.com>

View File

@ -31,7 +31,7 @@ General Public License for more details.
@finalout
@titlepage
@title Org-roam User Manual
@subtitle for version 2.3.0
@subtitle for version 2.3.1
@author Jethro Kuan
@page
@vskip 0pt plus 1filll
@ -44,7 +44,7 @@ General Public License for more details.
@noindent
This manual is for Org-roam version 2.3.0.
This manual is for Org-roam version 2.3.1.
@quotation
Copyright (C) 2020-2025 Jethro Kuan <jethrokuan95@@gmail.com>

View File

@ -7,7 +7,7 @@
;; Leo Vivier <leo.vivier+dev@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.0
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org-roam "2.1"))
;; This file is NOT part of GNU Emacs.

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.0
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (org "9.6") (org-roam "2.1"))
;; This file is NOT part of GNU Emacs.

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.0
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (org "9.6") (org-roam "2.1"))
;; This file is NOT part of GNU Emacs.

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.0
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (org "9.6") (org-roam "2.1"))
;; This file is NOT part of GNU Emacs.

View File

@ -4,7 +4,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.0
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (org "9.6") (org-roam "2.1"))
;; This file is NOT part of GNU Emacs.

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.0
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6") (emacsql "4.1.0") (magit-section "3.0.0"))
;; This file is NOT part of GNU Emacs.

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.0
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1"))
;; This file is NOT part of GNU Emacs.

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.0
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6") (emacsql "4.1.0") (magit-section "3.0.0"))
;; This file is NOT part of GNU Emacs.
@ -655,12 +655,14 @@ database, see `org-roam-db-sync' command."
(add-hook 'kill-emacs-hook #'org-roam-db--close-all)
(advice-add #'rename-file :after #'org-roam-db-autosync--rename-file-a)
(advice-add #'delete-file :before #'org-roam-db-autosync--delete-file-a)
(advice-add #'vc-delete-file :around #'org-roam-db-autosync--vc-delete-file-a)
(org-roam-db-sync))
(t
(remove-hook 'find-file-hook #'org-roam-db-autosync--setup-file-h)
(remove-hook 'kill-emacs-hook #'org-roam-db--close-all)
(advice-remove #'rename-file #'org-roam-db-autosync--rename-file-a)
(advice-remove #'delete-file #'org-roam-db-autosync--delete-file-a)
(advice-remove #'vc-delete-file #'org-roam-db-autosync--vc-delete-file-a)
(org-roam-db--close-all)
;; Disable local hooks for all org-roam buffers
(dolist (buf (org-roam-buffer-list))
@ -688,6 +690,17 @@ FILE is removed from the database."
(org-roam-file-p file))
(org-roam-db-clear-file (expand-file-name file))))
(defun org-roam-db-autosync--vc-delete-file-a (fun file)
"Maintain cache consistency on file deletion by FUN.
FILE is removed from the database."
(let ((org-roam-file-p (and (not (auto-save-file-name-p file))
(not (backup-file-name-p file))
(org-roam-file-p file))))
(apply fun `(,file))
(when (and org-roam-file-p
(not (file-exists-p file)))
(org-roam-db-clear-file (expand-file-name file)))))
(defun org-roam-db-autosync--rename-file-a (old-file new-file-or-dir &rest _args)
"Maintain cache consistency of file rename.
OLD-FILE is cleared from the database, and NEW-FILE-OR-DIR is added."

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.0
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6") (magit-section "3.0.0"))
;; This file is NOT part of GNU Emacs.

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.0
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6") (emacsql "4.1.0") (magit-section "3.0.0"))
;; This file is NOT part of GNU Emacs.

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.0
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6") (emacsql "4.1.0") (magit-section "3.0.0"))
;; This file is NOT part of GNU Emacs.

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.0
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6") (emacsql "4.1.0") (magit-section "3.0.0"))
;; This file is NOT part of GNU Emacs.
@ -658,17 +658,26 @@ This is the ROW within FILE."
(end-of-line)
(point)))))
(defun org-roam-unlinked-references--rg-command (titles)
"Return the ripgrep command searching for TITLES."
(defun org-roam-unlinked-references--rg-command (titles temp-file)
"Return the ripgrep command searching for TITLES using TEMP-FILE for pattern.
This avoids shell escaping issues by writing the pattern to a file instead
of passing it directly through the shell command line."
;; Write pattern to temp file to avoid shell escaping issues with quotes,
;; spaces, and other special characters in titles
(with-temp-file temp-file
(insert "\\[([^[]]++|(?R))*\\]"
(mapconcat (lambda (title)
;; Use regexp-quote instead of shell-quote-argument
;; since we're writing a regex pattern, not a shell argument
(format "|(\\b%s\\b)" (regexp-quote title)))
titles "")))
(concat "rg --follow --only-matching --vimgrep --pcre2 --ignore-case "
(mapconcat (lambda (glob) (concat "--glob " glob))
(org-roam--list-files-search-globs org-roam-file-extensions)
" ")
(format " '\\[([^[]]++|(?R))*\\]%s' "
(mapconcat (lambda (title)
(format "|(\\b%s\\b)" (shell-quote-argument title)))
titles ""))
(shell-quote-argument org-roam-directory)))
" --file " (shell-quote-argument temp-file) " "
(shell-quote-argument (expand-file-name org-roam-directory))))
(defun org-roam-unlinked-references-section (node)
"The unlinked references section for NODE.
@ -679,33 +688,39 @@ References from FILE are excluded."
(shell-command-to-string "rg --pcre2-version"))))
(let* ((titles (cons (org-roam-node-title node)
(org-roam-node-aliases node)))
(rg-command (org-roam-unlinked-references--rg-command titles))
(results (split-string (shell-command-to-string rg-command) "\n"))
f row col match)
(magit-insert-section (unlinked-references)
(magit-insert-heading "Unlinked References:")
(dolist (line results)
(save-match-data
(when (string-match org-roam-unlinked-references-result-re line)
(setq f (match-string 1 line)
row (string-to-number (match-string 2 line))
col (string-to-number (match-string 3 line))
match (match-string 4 line))
(when (and match
(not (file-equal-p (org-roam-node-file node) f))
(member (downcase match) (mapcar #'downcase titles)))
(magit-insert-section section (org-roam-grep-section)
(oset section file f)
(oset section row row)
(oset section col col)
(insert (propertize (format "%s:%s:%s"
(truncate-string-to-width (file-name-base f) 15 nil nil t)
row col) 'font-lock-face 'org-roam-dim)
" "
(org-roam-fontify-like-in-org-mode
(org-roam-unlinked-references-preview-line f row))
"\n"))))))
(insert ?\n)))))
;; Create temp file for the regex pattern
(temp-file (make-temp-file "org-roam-rg-pattern-"))
(rg-command (org-roam-unlinked-references--rg-command titles temp-file)))
;; Use unwind-protect to ensure temp file cleanup even if errors occur
(unwind-protect
(let* ((results (split-string (shell-command-to-string rg-command) "\n"))
f row col match)
(magit-insert-section (unlinked-references)
(magit-insert-heading "Unlinked References:")
(dolist (line results)
(save-match-data
(when (string-match org-roam-unlinked-references-result-re line)
(setq f (match-string 1 line)
row (string-to-number (match-string 2 line))
col (string-to-number (match-string 3 line))
match (match-string 4 line))
(when (and match
(not (file-equal-p (org-roam-node-file node) f))
(member (downcase match) (mapcar #'downcase titles)))
(magit-insert-section section (org-roam-grep-section)
(oset section file f)
(oset section row row)
(oset section col col)
(insert (propertize (format "%s:%s:%s"
(truncate-string-to-width (file-name-base f) 15 nil nil t)
row col) 'font-lock-face 'org-roam-dim)
" "
(org-roam-fontify-like-in-org-mode
(org-roam-unlinked-references-preview-line f row))
"\n"))))))
(insert ?\n)))
;; Clean up temp file - this runs even if an error occurs above
(delete-file temp-file)))))
(provide 'org-roam-mode)
;;; org-roam-mode.el ends here

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.0
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6") (magit-section "3.0.0"))
;; This file is NOT part of GNU Emacs.
@ -39,6 +39,16 @@
;;;; Completing-read
(defcustom org-roam-node-display-template "${title}"
"Configures display formatting for Org-roam node.
If it is a function, it will be called to format a node.
Its result is expected to be a string (potentially with
embedded properties).
If it is a string and it will be used as described in org-roam
(see org-roam-node-display-template)
When it is a string, the following processing is done:
Patterns of form \"${field-name:length}\" are interpolated based
on the current node.
@ -64,9 +74,23 @@ as many characters as possible and will be aligned accordingly.
A closure can also be assigned to this variable in which case the
closure is evaluated and the return value is used as the
template. The closure must evaluate to a valid template string."
template. The closure must evaluate to a valid template string.
When org-roam-node-display-template is a function, the function is
expected to return a string, potentially propertized. For example, the
following function shows the title and base filename of the node:
\(defun my--org-roam-format (node)
\"formats the node\"
(format \"%-40s %s\"
(if (org-roam-node-title node)
(propertize (org-roam-node-title node) 'face 'org-todo)
\"\")
(file-name-nondirectory (org-roam-node-file node))))
\q(setq org-roam-node-display-template 'my--org-roam-format)"
:group 'org-roam
:type '(string function))
:type '(choice string function))
(defcustom org-roam-node-annotation-function #'org-roam-node-read--annotation
"This function used to attach annotations for `org-roam-node-read'.
@ -90,7 +114,7 @@ argument, an `org-roam-node', and return a string.
If a string is provided, it is a template string expanded by
`org-roam-node--format-entry'."
:group 'org-roam
:type '(string function))
:type '(choice string function))
(defcustom org-roam-node-template-prefixes
'(("tags" . "#")
@ -143,6 +167,10 @@ This path is relative to `org-roam-directory'."
:group 'org-roam
:type 'string)
(defvar org-roam-link-type "roam"
"Link type for org-roam nodes.
Replaced by `id' automatically when `org-roam-link-auto-replace' is non-nil.")
(defvar org-roam-node-history nil
"Minibuffer history of nodes.")
@ -151,6 +179,11 @@ This path is relative to `org-roam-directory'."
;;; Definition
(cl-defstruct (org-roam-node (:constructor org-roam-node-create)
(:constructor org-roam-node-create-from-db
(title aliases ; 2
id file file-title level todo ; 5
point priority scheduled deadline properties ;;5
olp file-atime file-mtime tags refs)) ;;5
(:copier nil))
"A heading or top level file with an assigned ID property."
file file-title file-hash file-atime file-mtime
@ -352,23 +385,27 @@ nodes."
(defun org-roam-node-list ()
"Return all nodes stored in the database as a list of `org-roam-node's."
(let ((rows (org-roam-db-query
"SELECT
"
SELECT
title,
aliases,
id,
file,
filetitle,
\"level\",
todo,
pos,
priority ,
scheduled ,
deadline ,
title,
properties ,
olp,
atime,
mtime,
'(' || group_concat(tags, ' ') || ')' as tags,
aliases,
refs
FROM
(
@ -417,32 +454,20 @@ FROM
LEFT JOIN refs ON refs.node_id = nodes.id
GROUP BY nodes.id, tags.tag, aliases.alias )
GROUP BY id, tags )
GROUP BY id")))
(cl-loop for row in rows
append (pcase-let* ((`(
,id ,file ,file-title ,level ,todo ,pos ,priority ,scheduled ,deadline
,title ,properties ,olp ,atime ,mtime ,tags ,aliases ,refs)
row)
(all-titles (cons title aliases)))
(mapcar (lambda (temp-title)
(org-roam-node-create :id id
:file file
:file-title file-title
:file-atime atime
:file-mtime mtime
:level level
:point pos
:todo todo
:priority priority
:scheduled scheduled
:deadline deadline
:title temp-title
:aliases aliases
:properties properties
:olp olp
:tags tags
:refs refs))
all-titles)))))
GROUP BY id
")))
(mapcan
(lambda (row)
(let (
(all-titles (cons (car row) (nth 1 row)))
)
(mapcar (lambda (temp-title)
(apply 'org-roam-node-create-from-db (cons temp-title (cdr row))))
all-titles)
))
rows)
)
)
;;;; Finders
(defun org-roam-node-marker (node)
@ -556,6 +581,25 @@ PROMPT is a string to show at the beginning of the mini-buffer, defaulting to \"
(or (cdr (assoc node nodes))
(org-roam-node-create :title node))))
(defun org-roam--format-nodes-using-template (nodes)
"Formats NODES using org-roam template features.
Uses org-roam--node-display-template."
(let (
(wTemplate (org-roam-node--process-display-format org-roam-node-display-template))
)
(mapcar (lambda (node)
(org-roam-node-read--to-candidate node wTemplate)) nodes))
)
(defun org-roam--format-nodes-using-function (nodes)
"Formats NODES using the function org-roam-node-display-template."
(mapcar (lambda (node)
(cons
(propertize (funcall org-roam-node-display-template node) 'node node)
node))
nodes)
)
(defun org-roam-node-read--completions (&optional filter-fn sort-fn)
"Return an alist for node completion.
The car is the displayed title or alias for the node, and the cdr
@ -565,15 +609,17 @@ and when nil is returned the node will be filtered out.
SORT-FN is a function to sort nodes. See `org-roam-node-read-sort-by-file-mtime'
for an example sort function.
The displayed title is formatted according to `org-roam-node-display-template'."
(let* ((template (org-roam-node--process-display-format org-roam-node-display-template))
(let* (
(nodes (org-roam-node-list))
(nodes (if filter-fn
(cl-remove-if-not
(lambda (n) (funcall filter-fn n))
nodes)
nodes))
(nodes (mapcar (lambda (node)
(org-roam-node-read--to-candidate node template)) nodes))
(nodes (if (functionp org-roam-node-display-template)
(org-roam--format-nodes-using-function nodes)
(org-roam--format-nodes-using-template nodes)))
(sort-fn (or sort-fn
(when org-roam-node-default-sort
(intern (concat "org-roam-node-read-sort-by-"
@ -726,7 +772,7 @@ The INFO, if provided, is passed to the underlying `org-roam-capture-'."
(deactivate-mark)))
;;;;; [roam:] link
(org-link-set-parameters "roam" :follow #'org-roam-link-follow-link)
(org-link-set-parameters org-roam-link-type :follow #'org-roam-link-follow-link)
(defun org-roam-link-follow-link (title-or-alias)
"Navigate \"roam:\" link to find and open the node with TITLE-OR-ALIAS.
Assumes that the cursor was put where the link is."
@ -755,7 +801,7 @@ Assumes that the cursor was put where the link is."
node)
(goto-char (org-element-property :begin link))
(when (and (org-in-regexp org-link-any-re 1)
(string-equal type "roam")
(string-equal type org-roam-link-type)
(setq node (save-match-data (org-roam-node-from-title-or-alias path))))
(replace-match (org-link-make-string
(concat "id:" (org-roam-node-id node))
@ -765,7 +811,7 @@ Assumes that the cursor was put where the link is."
"Replace all \"roam:\" links in buffer with \"id:\" links."
(interactive)
(org-with-point-at 1
(while (re-search-forward org-link-bracket-re nil t)
(while (search-forward (concat "[[" org-roam-link-type ":") nil t)
(org-roam-link-replace-at-point))))
(add-hook 'org-roam-find-file-hook #'org-roam--replace-roam-links-on-save-h)

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.0
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6"))
;; This file is NOT part of GNU Emacs.
@ -163,7 +163,8 @@ If FILE, set `default-directory' to FILE's directory and insert its contents."
(let ((current-org-roam-directory (make-symbol "current-org-roam-directory")))
`(let ((,current-org-roam-directory org-roam-directory))
(with-temp-buffer
(let ((org-roam-directory ,current-org-roam-directory))
(let ((org-roam-directory ,current-org-roam-directory)
(org-inhibit-startup t))
(delay-mode-hooks (org-mode))
(when ,file
(insert-file-contents ,file)

View File

@ -5,7 +5,7 @@
;; Author: Jethro Kuan <jethrokuan95@gmail.com>
;; URL: https://github.com/org-roam/org-roam
;; Keywords: org-mode, roam, convenience
;; Version: 2.3.0
;; Version: 2.3.1
;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.6") (emacsql "4.1.0") (magit-section "3.0.0"))
;; This file is NOT part of GNU Emacs.

View File

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

View File

@ -30,9 +30,9 @@
(setq org-roam-directory "/tmp/org roam"))
(it "returns the correct rg command for unlinked references"
(expect (org-roam-unlinked-references--rg-command '("foo" "bar"))
(expect (org-roam-unlinked-references--rg-command '("foo" "bar") "/tmp/regex")
:to-equal
"rg --follow --only-matching --vimgrep --pcre2 --ignore-case --glob \"*.org\" --glob \"*.org.gpg\" --glob \"*.org.age\" '\\[([^[]]++|(?R))*\\]|(\\bfoo\\b)|(\\bbar\\b)' /tmp/org\\ roam")))
"rg --follow --only-matching --vimgrep --pcre2 --ignore-case --glob \"*.org\" --glob \"*.org.gpg\" --glob \"*.org.age\" --file /tmp/regex /tmp/org\\ roam")))
(provide 'test-org-roam-mode)

View File

@ -73,14 +73,16 @@
(expect (org-roam-node-title node) :to-equal "Bruce Wayne"))))
(describe "org-roam-demote-entire-buffer"
(after-each
(cd root-directory))
(it "demotes an entire org buffer"
(find-file "tests/roam-files/demoteable.org" nil)
(org-roam-demote-entire-buffer)
(expect (buffer-substring-no-properties (point) (point-max))
:to-equal "* Demoteable\n:PROPERTIES:\n:ID: 97bf31cf-dfee-45d8-87a5-2ae0dabc4734\n:END:\n\n** Demoteable h1\n\n*** Demoteable child\n")))
;; Use a temporary copy to avoid interfering with other tests
(let ((temp-file (make-temp-file "test-demoteable-" nil ".org")))
(copy-file "tests/roam-files/demoteable.org" temp-file t)
(find-file temp-file)
(org-roam-demote-entire-buffer)
(expect (buffer-substring-no-properties (point) (point-max))
:to-equal "* Demoteable\n:PROPERTIES:\n:ID: 97bf31cf-dfee-45d8-87a5-2ae0dabc4734\n:END:\n\n** Demoteable h1\n\n*** Demoteable child\n")
(kill-buffer)
(delete-file temp-file))))
(describe "org-roam--h1-count"
(after-each