diff --git a/CHANGELOG.md b/CHANGELOG.md index 25d7df7..51369f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ In this release, we improved the linking process by achieving feature parity bet This is a major step forward. Supporting the in-file structure of Org-mode files means that we can interface with many of its core-features like TODOs, properties, priorities, etc. UX will have to be figured out, but this release ushers in a new age in terms of functionalities. +We also add `org-roam-unlinked-references`, which naively finds text that could be references to the current Org-roam file. + ### Breaking Changes - [#701](https://github.com/org-roam/org-roam/pull/701) Use `emacsql-sqlite3` instead of `emacsql-sqlite` for better Windows compatibility. This requires the presence of the standard `sqlite3` binary on your machine. @@ -13,6 +15,7 @@ This is a major step forward. Supporting the in-file structure of Org-mode files ### Features +- [#787](https://github.com/org-roam/org-roam/pull/787) Add `org-roam-unlinked-references` - [#783](https://github.com/org-roam/org-roam/pull/783) Add support for headlines - [#757](https://github.com/org-roam/org-roam/pull/757) Roam global properties are now case-insensitive - [#680](https://github.com/org-roam/org-roam/pull/680) , [#703](https://github.com/org-roam/org-roam/pull/703), [#708](https://github.com/org-roam/org-roam/pull/708) Add `org-roam-doctor` checkers for `ROAM_*` properties diff --git a/org-roam.el b/org-roam.el index 23ea111..14db6b4 100644 --- a/org-roam.el +++ b/org-roam.el @@ -42,6 +42,7 @@ (require 'cl-lib) (require 'dash) (require 'f) +(require 'rx) (require 's) (require 'seq) (eval-when-compile (require 'subr-x)) @@ -1230,6 +1231,7 @@ Otherwise, behave as if called interactively." (lwarn '(org-roam) :error "Cannot find executable 'sqlite3'. \ Ensure it is installed and can be found within `exec-path'. \ M-x info for more information at Org-roam > Installation > Post-Installation Tasks.")) + (add-to-list 'org-execute-file-search-functions 'org-roam--execute-file-row-col) (add-hook 'find-file-hook #'org-roam--find-file-hook-function) (add-hook 'kill-emacs-hook #'org-roam-db--close-all) (add-hook 'org-open-at-point-functions #'org-roam-open-id-at-point) @@ -1237,6 +1239,7 @@ M-x info for more information at Org-roam > Installation > Post-Installation Tas (advice-add 'delete-file :before #'org-roam--delete-file-advice) (org-roam-db-build-cache)) (t + (setq org-execute-file-search-functions (delete 'org-roam--execute-file-row-col org-execute-file-search-functions)) (remove-hook 'find-file-hook #'org-roam--find-file-hook-function) (remove-hook 'kill-emacs-hook #'org-roam-db--close-all) (remove-hook 'org-open-at-point-functions #'org-roam-open-id-at-point) @@ -1408,6 +1411,88 @@ command will offer you to create one." :require-match t))) (switch-to-buffer (cdr (assoc name names-and-buffers)))))) +(defun org-roam--execute-file-row-col (s) + "Move to row:col if S match the row:col syntax. To be used with `org-execute-file-search-functions'." + (when (string-match (rx (group (1+ digit)) + ":" + (group (1+ digit))) s) + (let ((row (string-to-number (match-string 1 s))) + (col (string-to-number (match-string 2 s)))) + (org-goto-line row) + (move-to-column (- col 1)) + t))) + +;;###autoload +(defun org-roam-unlinked-references () + "Check for unlinked references in the current buffer. + +The check here is naive: it uses a regex that detects for +strict (case-insensitive) occurrences of possible titles (see +`org-roam--extract-titles'), and shows them in a buffer. This +means that the results can be noisy, and may not truly indicate +an unlinked reference. + +Users are encouraged to think hard about whether items should be +linked, lest the network graph get too crowded." + (interactive) + (unless (org-roam--org-roam-file-p) + (user-error "Not in org-roam file")) + (if (not (executable-find "rg")) + (user-error "Cannot find the \"rg\" executable, aborting") + (let* ((titles (org-roam--extract-titles)) + (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)) " ") + (format " '\\[([^[]]++|(?R))*\\]%s' " + (mapconcat (lambda (title) + (format "|(\\b%s\\b)" (shell-quote-argument title))) + titles "")) + org-roam-directory)) + (file-loc (buffer-file-name)) + (buf (get-buffer-create "*org-roam unlinked references*")) + (results (split-string (shell-command-to-string rg-command) "\n")) + (result-regex (rx (group (one-or-more anychar)) + ":" + (group (one-or-more digit)) + ":" + (group (one-or-more digit)) + ":" + (group (zero-or-more anything))))) + (pop-to-buffer buf) + (let ((inhibit-read-only t)) + (erase-buffer) + (org-mode) + (insert (propertize (car titles) 'font-lock-face 'org-document-title) "\n\n" + "* Unlinked References\n") + (dolist (line results) + (save-match-data + (when (string-match result-regex line) + (let ((file (match-string 1 line)) + (row (match-string 2 line)) + (col (match-string 3 line)) + (match (match-string 4 line))) + (when (and match + (member (downcase match) (mapcar #'downcase titles)) + (not (f-equal-p (expand-file-name file org-roam-directory) + file-loc))) + (let ((rowcol (concat row ":" col))) + (insert "- " + (org-link-make-string (concat "file:" file "::" rowcol) + (format "[%s] %s" rowcol (org-roam--get-title-or-slug file)))) + (when (executable-find "sed") ; insert line contents when sed is available + (insert " :: " + (shell-command-to-string + (concat "sed -n " + row + "p " + file)))) + (insert "\n"))))))) + (read-only-mode +1) + (dolist (title titles) + (highlight-phrase (downcase title) 'bold-italic)) + (goto-char (point-min)))))) + + ;;;###autoload (defun org-roam-version (&optional message) "Return `org-roam' version.