Compare commits

..

15 Commits

Author SHA1 Message Date
Dustin Farris
8a61cf35f8 fix!(capture): move plain capture point past properties drawer
- Fix org-roam-capture--adjust-point-for-capture-type to properly handle
  cases where point is positioned after head content but not at a heading
- Remove redundant location-type logic that incorrectly assumed point > 1
  means we're at a heading (file+head can position point after keywords)
- Restore call to adjust-point-for-capture-type that was removed in ed94524
- Add comprehensive tests for plain capture ordering and assertion error fix
- Refactor adjust-point-for-capture-type for better readability

Amend: ed94524964
2025-09-23 13:45:18 -07:00
Dustin Farris
b7483a1df5 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-09-23 07:13:35 -07:00
Dustin Farris
2ac1760620 test: clean up demotable.org
This test had been leaving demotable.org in a modified state which was
getting picked up by git requiring devs to manually reset it before
committing other changes.
2025-09-22 18:24:35 -07:00
Jethro Kuan
ed94524964 (feat!)capture: change id creation to headline on entry-type capture-templates 2025-09-22 18:24:35 -07:00
Liam Hupfer
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
Cash Prokop-Weaver
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
Dustin Farris
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
Taro Sato
1ea7e3077c fix: run org-roam-db-clear-file on file deletion via vc-delete-file 2025-06-26 14:54:27 -07:00
Dustin Farris
7ce95a286b release 2.3.1 2025-06-26 11:55:56 -07:00
Dustin Farris
c172951345 nit: fix indentation 2025-06-09 01:20:49 -07:00
Taro Sato
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
Dustin Farris
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
Dustin Farris
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
Taro Sato
031ee63bee (fix): Use correct type specifications to suppress warnings 2025-05-27 08:58:53 -07:00
Daniel M German
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
23 changed files with 740 additions and 148 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,13 @@
# Changelog
## 2.3.2
- [#2056](https://github.com/org-roam/org-roam/pull/2056) capture: IDs are now created for entries in capture templates
## 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.
@@ -339,11 +339,12 @@ In this case, interactive selection will be bypassed."
(when goto (run-hooks 'org-roam-dailies-find-file-hook)))
(add-hook 'org-roam-capture-preface-hook #'org-roam-dailies--override-capture-time-h)
(defun org-roam-dailies--override-capture-time-h ()
"Override the `:default-time' with the time from `:override-default-time'."
(prog1 nil
(when (org-roam-capture--get :override-default-time)
(org-capture-put :default-time (org-roam-capture--get :override-default-time)))))
(when (org-roam-capture--get :override-default-time)
(org-capture-put :default-time (org-roam-capture--get :override-default-time)))
nil)
;;; Bindings
(defvar org-roam-dailies-map (make-sparse-keymap)

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.
@@ -466,22 +466,23 @@ processing by `org-capture'.
Note: During the capture process this function is run by
`org-capture-set-target-location', as a (function ...) based
capture target."
(let ((id (cond ((run-hook-with-args-until-success 'org-roam-capture-preface-hook))
(t (org-roam-capture--setup-target-location)))))
(org-roam-capture--adjust-point-for-capture-type)
(let ((template (org-capture-get :template)))
(when (stringp template)
(org-capture-put
:template
(org-roam-capture--fill-template template))))
(org-roam-capture--put :id id)
(org-roam-capture--put :finalize (or (org-capture-get :finalize)
(org-roam-capture--get :finalize)))))
(if-let ((id (run-hook-with-args-until-success 'org-roam-capture-preface-hook)))
(org-roam-capture--put :id id)
(org-roam-capture--setup-target-location)
;; Adjust point for plain captures to skip past metadata (e.g. properties drawer)
(org-roam-capture--adjust-point-for-capture-type))
(let ((template (org-capture-get :template)))
(when (stringp template)
(org-capture-put
:template
(org-roam-capture--fill-template template))))
(org-roam-capture--put :finalize (or (org-capture-get :finalize)
(org-roam-capture--get :finalize))))
(defun org-roam-capture--setup-target-location ()
"Initialize the buffer, and goto the location of the new capture.
Return the ID of the location."
(let (p new-file-p)
"Initialize the buffer, and goto the location of the new capture."
(let ((target-entry-p t)
p new-file-p id)
(pcase (org-roam-capture--get-target)
(`(file ,path)
(setq path (org-roam-capture--target-truepath path)
@@ -489,7 +490,8 @@ Return the ID of the location."
(when new-file-p (org-roam-capture--put :new-file path))
(set-buffer (org-capture-target-buffer path))
(widen)
(setq p (goto-char (point-min))))
(setq p (goto-char (point-min))
target-entry-p nil))
(`(file+olp ,path ,olp)
(setq path (org-roam-capture--target-truepath path)
new-file-p (org-roam-capture--new-file-p path))
@@ -505,9 +507,12 @@ Return the ID of the location."
(set-buffer (org-capture-target-buffer path))
(when new-file-p
(org-roam-capture--put :new-file path)
(insert (org-roam-capture--fill-template head 'ensure-newline)))
(insert (org-roam-capture--fill-template head 'ensure-newline))
(setq p (point-max)))
(widen)
(setq p (goto-char (point-min))))
(unless new-file-p
(setq p (goto-char (point-min))))
(setq target-entry-p nil))
(`(file+head+olp ,path ,head ,olp)
(setq path (org-roam-capture--target-truepath path)
new-file-p (org-roam-capture--new-file-p path))
@@ -569,17 +574,45 @@ Return the ID of the location."
(user-error "No node with title or id \"%s\"" title-or-id))))
(set-buffer (org-capture-target-buffer (org-roam-node-file node)))
(goto-char (org-roam-node-point node))
(setq p (org-roam-node-point node)))))
(setq p (org-roam-node-point node)
target-entry-p (and (derived-mode-p 'org-mode) (org-at-heading-p))))))
;; Setup `org-id' for the current capture target and return it back to the
;; caller.
(save-excursion
(goto-char p)
(if-let ((id (org-entry-get p "ID")))
(setf (org-roam-node-id org-roam-capture--node) id)
(org-entry-put p "ID" (org-roam-node-id org-roam-capture--node)))
(prog1
(org-id-get)
(run-hooks 'org-roam-capture-new-node-hook)))))
;; Unless it's an entry type, then we want to create an ID for the entry instead
(pcase (org-capture-get :type)
('entry
(advice-add #'org-capture-place-entry :after #'org-roam-capture--create-id-for-entry)
(org-roam-capture--put :new-node-p t)
(setq id (org-roam-node-id org-roam-capture--node)))
(_
(save-excursion
(goto-char p)
(unless (org-entry-get p "ID")
(org-roam-capture--put :new-node-p t))
(setq id (or (org-entry-get p "ID")
(org-roam-node-id org-roam-capture--node)))
(setf (org-roam-node-id org-roam-capture--node) id)
(org-entry-put p "ID" id))))
(org-roam-capture--put :id id)
(org-roam-capture--put :target-entry-p target-entry-p)
(advice-add #'org-capture-place-template :before #'org-roam-capture--set-target-entry-p-a)
(advice-add #'org-capture-place-template :after #'org-roam-capture-run-new-node-hook-a)))
(defun org-roam-capture--set-target-entry-p-a (_)
"Correct `:target-entry-p' in Org-capture template based on `:target.'."
(org-capture-put :target-entry-p (org-roam-capture--get :target-entry-p))
(advice-remove #'org-capture-place-template #'org-roam-capture--set-target-entry-p-a))
(defun org-roam-capture-run-new-node-hook-a (_)
"Advice to run after the Org-capture template is placed."
(when (org-roam-capture--get :new-node-p)
(run-hooks 'org-roam-capture-new-node-hook))
(advice-remove #'org-capture-place-template #'org-roam-capture--place-template-a))
(defun org-roam-capture--create-id-for-entry ()
"Create the ID for the new entry."
(org-entry-put (point) "ID" (org-roam-capture--get :id))
(advice-remove #'org-capture-place-entry #'org-roam-capture--create-id-for-entry))
(defun org-roam-capture--get-target ()
"Get the current capture :target for the capture template in use."
@@ -657,27 +690,17 @@ POS is the current position of point (an integer) inside the
currently active capture buffer, where the adjustment should
start to begin from. If it's nil, then it will default to
the current value of `point'."
(or pos (setq pos (point)))
(goto-char pos)
(let ((location-type (if (= pos 1) 'beginning-of-file 'heading-at-point)))
(and (eq location-type 'heading-at-point)
(cl-assert (org-at-heading-p)))
(pcase (org-capture-get :type)
(`plain
(cl-case location-type
(beginning-of-file
(if (org-capture-get :prepend)
(let ((el (org-element-at-point)))
(while (and (not (eobp))
(memq (org-element-type el)
'(drawer property-drawer keyword comment comment-block horizontal-rule)))
(goto-char (org-element-property :end el))
(setq el (org-element-at-point))))
(goto-char (org-entry-end-position))))
(heading-at-point
(if (org-capture-get :prepend)
(org-end-of-meta-data t)
(goto-char (org-entry-end-position))))))))
(goto-char (or pos (point)))
(pcase (org-capture-get :type)
(`plain
(if (org-capture-get :prepend)
(let ((el (org-element-at-point)))
(while (and (not (eobp))
(memq (org-element-type el)
'(drawer property-drawer keyword comment comment-block horizontal-rule)))
(goto-char (org-element-property :end el))
(setq el (org-element-at-point))))
(goto-char (org-entry-end-position)))))
(point))
;;; Capture implementation

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

@@ -58,6 +58,330 @@
(org-roam-capture--fill-template (lambda () "foo"))
:to-equal "foo")))
(describe "org-roam-capture entry-type ID creation"
(it "creates ID for entry-type captures"
(let* ((temp-dir (make-temp-file "org-roam-test" t))
(test-file (expand-file-name "test.org" temp-dir))
(org-roam-directory temp-dir)
(org-roam-capture--node (org-roam-node-create :id (org-id-new)))
(org-roam-capture--info (make-hash-table :test 'equal))
capture-id)
(unwind-protect
(progn
;; Create initial file content
(with-temp-file test-file
(insert "#+TITLE: Test File\n\n* Parent Heading\n:PROPERTIES:\n:ID: parent-id\n:END:\n"))
;; Mock org-capture context and get-target
(cl-letf* (((symbol-function 'org-capture-get)
(lambda (prop)
(pcase prop
(:type 'entry)
(_ nil))))
((symbol-function 'org-roam-capture--get-target)
(lambda () `(file ,test-file))))
;; Call the setup function
(with-current-buffer (find-file-noselect test-file)
(org-roam-capture--setup-target-location)
(setq capture-id (org-roam-capture--get :id)))
;; Verify ID was created and stored for entry type
(expect capture-id :not :to-be nil)
(expect (org-roam-capture--get :new-node-p) :to-be t)))
(delete-directory temp-dir t))))
(it "creates ID at target for non-entry-type captures"
(let* ((temp-dir (make-temp-file "org-roam-test" t))
(test-file (expand-file-name "test-plain.org" temp-dir))
(org-roam-directory temp-dir)
(org-roam-capture--node (org-roam-node-create :id (org-id-new)))
(org-roam-capture--info (make-hash-table :test 'equal))
capture-id)
(unwind-protect
(progn
;; Create initial empty file
(with-temp-file test-file
(insert "#+TITLE: Test\n"))
;; Mock org-capture context for plain type
(cl-letf* (((symbol-function 'org-capture-get)
(lambda (prop)
(pcase prop
(:type 'plain)
(_ nil))))
((symbol-function 'org-roam-capture--get-target)
(lambda () `(file ,test-file))))
;; Call the setup function
(with-current-buffer (find-file-noselect test-file)
(org-roam-capture--setup-target-location)
(setq capture-id (org-roam-capture--get :id)))
;; For non-entry types, ID should be created at the target location
(expect capture-id :not :to-be nil)
(expect (org-roam-capture--get :new-node-p) :to-be t)))
(delete-directory temp-dir t)))))
(describe "org-roam-capture advice functions"
:var ((org-roam-capture--info))
(before-each
(setq org-roam-capture--info (make-hash-table :test 'equal)))
(it "org-roam-capture--create-id-for-entry creates and removes advice"
(cl-letf* ((entry-id nil)
((symbol-function 'org-entry-put)
(lambda (pom prop val)
(when (string= prop "ID")
(setq entry-id val))))
((symbol-function 'org-roam-capture--get)
(lambda (prop)
(if (eq prop :id)
"test-id-123"
nil))))
;; Add the advice
(advice-add #'org-capture-place-entry :after #'org-roam-capture--create-id-for-entry)
;; Call the function
(org-roam-capture--create-id-for-entry)
;; Verify ID was set and advice removed
(expect entry-id :to-equal "test-id-123")
(expect (advice-member-p #'org-roam-capture--create-id-for-entry
#'org-capture-place-entry)
:to-be nil)))
(it "org-roam-capture--set-target-entry-p-a sets and removes advice"
(cl-letf* ((captured-value nil)
((symbol-function 'org-capture-put)
(lambda (prop val)
(when (eq prop :target-entry-p)
(setq captured-value val))))
((symbol-function 'org-roam-capture--get)
(lambda (prop)
(if (eq prop :target-entry-p)
t
nil))))
;; Add the advice
(advice-add #'org-capture-place-template :before #'org-roam-capture--set-target-entry-p-a)
;; Call the function
(org-roam-capture--set-target-entry-p-a nil)
;; Verify value was set and advice removed
(expect captured-value :to-be t)
(expect (advice-member-p #'org-roam-capture--set-target-entry-p-a
#'org-capture-place-template)
:to-be nil)))
(it "org-roam-capture-run-new-node-hook-a runs hook when new node"
(let ((hook-ran nil))
(cl-letf* (((symbol-function 'org-roam-capture--get)
(lambda (prop)
(if (eq prop :new-node-p)
t
nil))))
;; Add test hook
(add-hook 'org-roam-capture-new-node-hook
(lambda () (setq hook-ran t)))
;; Add the advice
(advice-add #'org-capture-place-template :after #'org-roam-capture-run-new-node-hook-a)
;; Call the function
(org-roam-capture-run-new-node-hook-a nil)
;; Verify hook ran
(expect hook-ran :to-be t)
;; Clean up
(remove-hook 'org-roam-capture-new-node-hook
(lambda () (setq hook-ran t)))))))
(describe "org-roam-capture target-entry-p detection"
(it "detects entry target for file+olp"
(let* ((temp-dir (make-temp-file "org-roam-test" t))
(test-file (expand-file-name "test-olp.org" temp-dir))
(org-roam-directory temp-dir)
(org-roam-capture--node (org-roam-node-create :id (org-id-new)))
(org-roam-capture--info (make-hash-table :test 'equal)))
(unwind-protect
(progn
(with-temp-file test-file
(insert "* Level 1\n** Level 2\n"))
(cl-letf* (((symbol-function 'org-roam-capture--get-target)
(lambda () `(file+olp ,test-file ("Level 1" "Level 2"))))
((symbol-function 'org-capture-get)
(lambda (prop) nil)))
(with-current-buffer (find-file-noselect test-file)
(org-roam-capture--setup-target-location)
(expect (org-roam-capture--get :target-entry-p) :to-be t))))
(delete-directory temp-dir t))))
(it "detects non-entry target for file"
(let* ((temp-dir (make-temp-file "org-roam-test" t))
(test-file (expand-file-name "test-file.org" temp-dir))
(org-roam-directory temp-dir)
(org-roam-capture--node (org-roam-node-create :id (org-id-new)))
(org-roam-capture--info (make-hash-table :test 'equal)))
(unwind-protect
(progn
(with-temp-file test-file
(insert "#+TITLE: Test\n"))
(cl-letf* (((symbol-function 'org-roam-capture--get-target)
(lambda () `(file ,test-file)))
((symbol-function 'org-capture-get)
(lambda (prop) nil)))
(with-current-buffer (find-file-noselect test-file)
(org-roam-capture--setup-target-location)
(expect (org-roam-capture--get :target-entry-p) :to-be nil))))
(delete-directory temp-dir t)))))
(describe "org-roam-capture plain type ordering"
:var ((temp-dir) (org-roam-directory) (org-roam-db-location))
(before-each
(setq temp-dir (make-temp-file "org-roam-test" t))
(setq org-roam-directory temp-dir)
(setq org-roam-db-location (expand-file-name "org-roam.db" temp-dir))
(org-roam-db-sync))
(after-each
(delete-directory temp-dir t))
(it "places properties drawer before captured content for plain type with file target"
(let* ((test-file (expand-file-name "test-plain-file.org" temp-dir))
(test-content "Test plain :target file")
(node (org-roam-node-create :title "Test Plain"))
(org-roam-capture--node node)
(org-roam-capture--info (make-hash-table :test 'equal)))
;; Call the setup directly to simulate capture without user interaction
(cl-letf* (((symbol-function 'org-capture-get)
(lambda (prop)
(pcase prop
(:type 'plain)
(:target-file test-file)
(_ nil))))
((symbol-function 'org-roam-capture--get-target)
(lambda () `(file ,test-file))))
;; Run the setup and insert content
(with-current-buffer (find-file-noselect test-file)
(org-roam-capture--setup-target-location)
(org-roam-capture--adjust-point-for-capture-type)
(insert test-content)
(save-buffer)))
;; Read the created file and check its structure
(with-temp-buffer
(insert-file-contents test-file)
(let ((buffer-content (buffer-string)))
;; The expected format is:
;; :PROPERTIES:
;; :ID: some-id
;; :END:
;; Test plain :target file
;; Check that properties come first
(expect buffer-content
:to-match
(rx bol ":PROPERTIES:"))
;; Verify ordering: properties, then content
(let ((props-pos (string-match ":PROPERTIES:" buffer-content))
(end-pos (string-match ":END:" buffer-content))
(content-pos (string-match (regexp-quote test-content) buffer-content)))
(expect props-pos :to-be 0)
(expect end-pos :not :to-be nil)
(expect end-pos :to-be-greater-than props-pos)
(expect content-pos :not :to-be nil)
(expect content-pos :to-be-greater-than end-pos))))))
(it "correctly orders buffer elements for plain type with file+head target"
(let* ((test-file (expand-file-name "test-plain-file-head.org" temp-dir))
(test-content "Test plain :target file+head")
(node (org-roam-node-create :title "plain file+head"))
(org-roam-capture--node node)
(org-roam-capture--info (make-hash-table :test 'equal)))
;; Populate capture info with title for template expansion
(puthash :title "plain file+head" org-roam-capture--info)
;; Call the setup directly to simulate capture without user interaction
(cl-letf* (((symbol-function 'org-capture-get)
(lambda (prop)
(pcase prop
(:type 'plain)
(:target-file test-file)
(_ nil))))
((symbol-function 'org-roam-capture--get-target)
(lambda () `(file+head ,test-file "#+title: ${title}\n"))))
;; Run the setup and insert content
(with-current-buffer (find-file-noselect test-file)
(org-roam-capture--setup-target-location)
(org-roam-capture--adjust-point-for-capture-type)
(insert test-content)
(save-buffer)))
;; Read the created file and check its structure
(with-temp-buffer
(insert-file-contents test-file)
(let ((buffer-content (buffer-string)))
;; The actual format according to org-mode property syntax is:
;; :PROPERTIES:
;; :ID: some-id
;; :END:
;; #+title: plain file+head
;; Test plain :target file+head
;;
;; This is correct - buffer-level properties must be at the top
;; Check that properties come first
(expect buffer-content
:to-match
(rx bol ":PROPERTIES:"))
;; Verify ordering: properties are at the top
(let ((props-pos (string-match ":PROPERTIES:" buffer-content))
(end-pos (string-match ":END:" buffer-content)))
;; Properties drawer should be first
(expect props-pos :to-be 0)
(expect end-pos :not :to-be nil)
(expect end-pos :to-be-greater-than props-pos))))))
(it "tests org-roam-capture--adjust-point-for-capture-type behavior"
;; Simple test to verify the fix for assertion error
(with-temp-buffer
(org-mode)
(insert "#+title: Test\n")
(goto-char (point-max))
;; Mock org-capture-get to return plain type
(cl-letf (((symbol-function 'org-capture-get)
(lambda (prop) (when (eq prop :type) 'plain))))
;; Document current state that previously caused assertion error
(let ((point-before (point))
(at-heading-before (org-at-heading-p)))
;; This should no longer trigger assertion error with our fix
(org-roam-capture--adjust-point-for-capture-type)
(expect point-before :to-be-greater-than 1)
(expect at-heading-before :to-be nil))))))
(provide 'test-org-roam-capture)
;;; test-org-roam-capture.el ends here

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

@@ -77,10 +77,22 @@
(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")))
(let* ((test-file "tests/roam-files/demoteable.org")
(buf (find-file-noselect test-file))
;; Store the original content before any modifications
(original-content (with-current-buffer buf
(buffer-substring-no-properties (point-min) (point-max)))))
(unwind-protect
(with-current-buffer buf
(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"))
;; Always restore the original content
(with-current-buffer buf
(erase-buffer)
(insert original-content)
(save-buffer)
(kill-buffer buf))))))
(describe "org-roam--h1-count"
(after-each