diff --git a/org-roam-completion.el b/org-roam-completion.el index 2c810f6..aafe088 100644 --- a/org-roam-completion.el +++ b/org-roam-completion.el @@ -27,31 +27,82 @@ ;;; Commentary: ;; -;; This library provides completion for org-roam. +;; This library provides completion-at-point for org-roam. ;;; Code: -;;;; Library Requires (require 'cl-lib) -(require 's) - -(defvar helm-pattern) -(declare-function helm "ext:helm") -(declare-function helm-make-source "ext:helm-source" (name class &rest args) t) - -(defcustom org-roam-completion-system 'default - "The completion system to be used by `org-roam'." - :type '(radio - (const :tag "Default" default) - (const :tag "Ido" ido) - (const :tag "Ivy" ivy) - (const :tag "Helm" helm) - (function :tag "Custom function")) - :group 'org-roam) (defcustom org-roam-completion-ignore-case t "Whether to ignore case in Org-roam `completion-at-point' completions." :group 'org-roam :type 'boolean) +(defvar org-roam-completion-functions (list #'org-roam-complete-link-at-point + #'org-roam-complete-everywhere) + "List of functions to be used with `completion-at-point' for Org-roam.") + +(defun org-roam-complete-everywhere () + "`completion-at-point' function for word at point. +This is active when `org-roam-completion-everywhere' is non-nil." + (let ((end (point)) + (start (point)) + (exit-fn (lambda (&rest _) nil)) + collection) + (when (thing-at-point 'word) + (let ((bounds (bounds-of-thing-at-point 'word))) + (setq start (car bounds) + end (cdr bounds) + collection #'org-roam--get-titles + exit-fn (lambda (str _status) + (delete-char (- (length str))) + (insert "[[roam:" str "]]"))))) + (when collection + (let ((prefix (buffer-substring-no-properties start end))) + (list start end + (if (functionp collection) + (completion-table-case-fold + (completion-table-dynamic + (lambda (_) + (cl-remove-if (apply-partially #'string= prefix) + (funcall collection)))) + (not org-roam-completion-ignore-case)) + collection) + :exit-function exit-fn))))) + +(defun org-roam-complete-link-at-point () + "Do appropriate completion for the link at point." + (let ((end (point)) + (start (point)) + collection path) + (when (org-in-regexp org-link-bracket-re 1) + (setq start (match-beginning 1) + end (match-end 1)) + (let ((context (org-element-context))) + (pcase (org-element-lineage context '(link) t) + (`nil nil) + (link + (setq link-type (org-element-property :type link) + path (org-element-property :path link)) + (when (member link-type '("roam" "fuzzy")) + (when (string= link-type "roam") (setq start (+ start (length "roam:")))) + (setq collection #'org-roam-link--get-nodes)))))) + (when collection + (let ((prefix (buffer-substring-no-properties start end))) + (list start end + (if (functionp collection) + (completion-table-case-fold + (completion-table-dynamic + (lambda (_) + (cl-remove-if (apply-partially #'string= prefix) + (funcall collection)))) + (not org-roam-completion-ignore-case)) + collection) + :exit-function + (lambda (str &rest _) + (delete-char (- 0 (length str))) + (insert (concat (unless (string= link-type "roam") "roam:") + str)) + (forward-char 2))))))) + (provide 'org-roam-completion) ;;; org-roam-completion.el ends here diff --git a/org-roam-db.el b/org-roam-db.el index c7d5dc7..5144826 100644 --- a/org-roam-db.el +++ b/org-roam-db.el @@ -401,62 +401,6 @@ If UPDATE-P is non-nil, first remove the file in the database." (puthash (car row) (cadr row) ht)) ht)) -(defun org-roam-db--connected-component (file) - "Return all files reachable from/connected to FILE, including the file itself. -If the file does not have any connections, nil is returned." - (let* ((query "WITH RECURSIVE - links_of(file, link) AS - (WITH filelinks AS (SELECT * FROM links WHERE NOT \"type\" = '\"cite\"'), - citelinks AS (SELECT * FROM links - JOIN refs ON links.\"dest\" = refs.\"ref\" - AND links.\"type\" = '\"cite\"') - SELECT \"source\", \"dest\" FROM filelinks UNION - SELECT \"dest\", \"source\" FROM filelinks UNION - SELECT \"file\", \"source\" FROM citelinks UNION - SELECT \"dest\", \"file\" FROM citelinks), - connected_component(file) AS - (SELECT link FROM links_of WHERE file = $s1 - UNION - SELECT link FROM links_of JOIN connected_component USING(file)) - SELECT * FROM connected_component;") - (files (mapcar 'car-safe (emacsql (org-roam-db) query file)))) - files)) - -(defun org-roam-db--links-with-max-distance (file max-distance) - "Return all files connected to FILE in at most MAX-DISTANCE steps. -This includes the file itself. If the file does not have any -connections, nil is returned." - (let* ((query "WITH RECURSIVE - links_of(file, link) AS - (WITH filelinks AS (SELECT * FROM links WHERE NOT \"type\" = '\"cite\"'), - citelinks AS (SELECT * FROM links - JOIN refs ON links.\"dest\" = refs.\"ref\" - AND links.\"type\" = '\"cite\"') - SELECT \"source\", \"dest\" FROM filelinks UNION - SELECT \"dest\", \"source\" FROM filelinks UNION - SELECT \"file\", \"source\" FROM citelinks UNION - SELECT \"source\", \"file\" FROM citelinks), - -- Links are traversed in a breadth-first search. In order to calculate the - -- distance of nodes and to avoid following cyclic links, the visited nodes - -- are tracked in 'trace'. - connected_component(file, trace) AS - (VALUES($s1, json_array($s1)) - UNION - SELECT lo.link, - json_insert(cc.trace, '$[' || json_array_length(cc.trace) || ']', lo.link) - FROM - connected_component AS cc JOIN links_of AS lo USING(file) - WHERE ( - -- Avoid cycles by only visiting each file once. - (SELECT count(*) FROM json_each(cc.trace) WHERE json_each.value == lo.link) == 0 - -- Note: BFS is cut off early here. - AND json_array_length(cc.trace) < ($s2 + 1))) - SELECT DISTINCT file, min(json_array_length(trace)) AS distance - FROM connected_component GROUP BY file ORDER BY distance;") - ;; In principle the distance would be available in the second column. - (files (mapcar 'car-safe (emacsql (org-roam-db) query file max-distance)))) - files)) - (defun org-roam-db--file-hash (&optional file-path) "Compute the hash of FILE-PATH, a file or current buffer." (if file-path diff --git a/org-roam.el b/org-roam.el index ecd010e..e24a44c 100644 --- a/org-roam.el +++ b/org-roam.el @@ -148,21 +148,11 @@ Function should return a filename string based on title." :type 'function :group 'org-roam) -(defcustom org-roam-file-completion-tag-position 'prepend - "Prepend, append, or omit tags from the file titles during completion." - :type '(choice (const :tag "Prepend" prepend) - (const :tag "Append" append) - (const :tag "Omit" omit)) - :group 'org-roam) - (defcustom org-roam-verbose t "Echo messages that are not errors." :type 'boolean :group 'org-roam) -(defvar org-roam-completion-functions nil - "List of functions to be used with `completion-at-point' for Org-roam.") - ;;;; Faces (defface org-roam-shielded '((t :inherit (warning org-link))) @@ -363,7 +353,6 @@ Use external shell commands if defined in `org-roam-list-files-commands'." (puthash node-id (cons tag (gethash node-id ht)) ht)) ht)) -;;;; org-roam-find-ref (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)) @@ -371,13 +360,6 @@ Use external shell commands if defined in `org-roam-list-files-commands'." (org-roam--org-roam-file-p (buffer-file-name it))) (buffer-list))) -;;; Completion at point -(defcustom org-roam-completion-everywhere nil - "If non-nil, provide completions from the current word at point." - :group 'org-roam - :type 'boolean) - -;;;; Tags completion (defun org-roam--get-titles () "Return all titles and aliases in the Org-roam database." (let* ((titles (mapcar #'car (org-roam-db-query [:select title :from nodes]))) @@ -385,40 +367,7 @@ Use external shell commands if defined in `org-roam-list-files-commands'." (completions (append titles aliases))) completions)) -(defun org-roam-complete-everywhere () - "`completion-at-point' function for word at point. -This is active when `org-roam-completion-everywhere' is non-nil." - (let ((end (point)) - (start (point)) - (exit-fn (lambda (&rest _) nil)) - collection) - (when (and org-roam-completion-everywhere - (thing-at-point 'word)) - (let ((bounds (bounds-of-thing-at-point 'word))) - (setq start (car bounds) - end (cdr bounds) - collection #'org-roam--get-titles - exit-fn (lambda (str _status) - (delete-char (- (length str))) - (insert "[[roam:" str "]]"))))) - (when collection - (let ((prefix (buffer-substring-no-properties start end))) - (list start end - (if (functionp collection) - (completion-table-case-fold - (completion-table-dynamic - (lambda (_) - (cl-remove-if (apply-partially #'string= prefix) - (funcall collection)))) - (not org-roam-completion-ignore-case)) - collection) - :exit-function exit-fn))))) - -(add-to-list 'org-roam-completion-functions #'org-roam-complete-everywhere) -(add-to-list 'org-roam-completion-functions #'org-roam-link-complete-at-point) - -;;; Org-roam-mode -;;; Org-roam entry point +;;; Org-roam setup and teardown (defun org-roam-setup () "Setup Org-roam." (interactive) @@ -482,12 +431,5 @@ OLD-FILE is cleared from the database, and NEW-FILE-OR-DIR is added." (when (org-roam--org-roam-file-p new-file) (org-roam-db-update-file new-file)))) -;;; Interactive Commands -;;;###autoload -(defun org-roam-find-directory () - "Find and open `org-roam-directory'." - (interactive) - (find-file org-roam-directory)) - (provide 'org-roam) ;;; org-roam.el ends here diff --git a/tests/roam-files/alias.org b/tests/roam-files/alias.org deleted file mode 100644 index 3e21811..0000000 --- a/tests/roam-files/alias.org +++ /dev/null @@ -1,2 +0,0 @@ -#+roam_alias: "a1" "a 2" -#+title: t1 diff --git a/tests/roam-files/bar.org b/tests/roam-files/bar.org index 3dd4911..0deb4e6 100644 --- a/tests/roam-files/bar.org +++ b/tests/roam-files/bar.org @@ -1,3 +1,6 @@ +:PROPERTIES: +:ID: 440795d0-70c1-4165-993d-aebd5eef7a24 +:END: #+title: Bar -This is file bar. Bar links to [[file:nested/bar.org][Nested Bar]]. +[[id:884b2341-b7fe-434d-848c-5282c0727861][Foo]] diff --git a/tests/roam-files/base.org b/tests/roam-files/base.org deleted file mode 100644 index 876953a..0000000 --- a/tests/roam-files/base.org +++ /dev/null @@ -1 +0,0 @@ -#+title: Base diff --git a/tests/roam-files/cite_ref.org b/tests/roam-files/cite_ref.org deleted file mode 100644 index ea18813..0000000 --- a/tests/roam-files/cite_ref.org +++ /dev/null @@ -1 +0,0 @@ -#+roam_key: cite:mitsuha2007 diff --git a/tests/roam-files/foo.org b/tests/roam-files/foo.org index 5252e95..8ce5d67 100644 --- a/tests/roam-files/foo.org +++ b/tests/roam-files/foo.org @@ -1,8 +1,4 @@ +:PROPERTIES: +:ID: 884b2341-b7fe-434d-848c-5282c0727861 +:END: #+title: Foo - -This is the foo file. It contains a link to [[file:bar.org][Bar]]. - -To make the tests more robust, here are some arbitrary links: - -- [[https:google.com][Google]] -- [[mailto:foo@john.com][mail to foo]] diff --git a/tests/roam-files/headlines/headline.org b/tests/roam-files/headlines/headline.org deleted file mode 100644 index 563b57e..0000000 --- a/tests/roam-files/headlines/headline.org +++ /dev/null @@ -1,14 +0,0 @@ -#+TITLE: Headline - -* Headline 1 -:PROPERTIES: -:ID: e84d0630-efad-4017-9059-5ef917908823 -:END: - -* No headline here -Oops. - -* Headline 2 -:PROPERTIES: -:ID: 801b58eb-97e2-435f-a33e-ff59a2f0c213 -:END: diff --git a/tests/roam-files/multiple-refs.org b/tests/roam-files/multiple-refs.org deleted file mode 100644 index dea8828..0000000 --- a/tests/roam-files/multiple-refs.org +++ /dev/null @@ -1,2 +0,0 @@ -#+roam_key: https://www.orgroam.com/ -#+roam_key: cite:orgroam2020 diff --git a/tests/roam-files/nested/bar.org b/tests/roam-files/nested/bar.org deleted file mode 100644 index b1c7400..0000000 --- a/tests/roam-files/nested/bar.org +++ /dev/null @@ -1,3 +0,0 @@ -#+title: Nested Bar - -This file is nested, 1 level deeper. It links to both [[file:../foo.org][Foo]] and [[file:foo.org][Nested Foo]]. diff --git a/tests/roam-files/nested/deeply/deeply_nested_file.org b/tests/roam-files/nested/deeply/deeply_nested_file.org deleted file mode 100644 index e64d8d3..0000000 --- a/tests/roam-files/nested/deeply/deeply_nested_file.org +++ /dev/null @@ -1 +0,0 @@ -#+title: Deeply Nested File diff --git a/tests/roam-files/nested/foo.org b/tests/roam-files/nested/foo.org deleted file mode 100644 index b40c6e5..0000000 --- a/tests/roam-files/nested/foo.org +++ /dev/null @@ -1,3 +0,0 @@ -#+title: Nested Foo - -This file has no links. diff --git a/tests/roam-files/no-title.org b/tests/roam-files/no-title.org deleted file mode 100644 index 6af889d..0000000 --- a/tests/roam-files/no-title.org +++ /dev/null @@ -1,5 +0,0 @@ -no title in this file :O - -links to itself, with no title: [[file:no-title.org][no-title]] - -* Headline title diff --git a/tests/roam-files/tags/no_tag.org b/tests/roam-files/tags/no_tag.org deleted file mode 100644 index ad8984f..0000000 --- a/tests/roam-files/tags/no_tag.org +++ /dev/null @@ -1,3 +0,0 @@ -#+title: Tagless File - -This file has no tags, and should not yield any tags on extracting via ~#+roam_tags~. diff --git a/tests/roam-files/tags/tag.org b/tests/roam-files/tags/tag.org deleted file mode 100644 index 206b026..0000000 --- a/tests/roam-files/tags/tag.org +++ /dev/null @@ -1,4 +0,0 @@ -#+roam_tags: "t1" "t2 with space" t3 -#+title: Tags - -This file is used to test functionality for =(org-roam--extract-tags)= diff --git a/tests/roam-files/titles/aliases.org b/tests/roam-files/titles/aliases.org deleted file mode 100644 index 440dd78..0000000 --- a/tests/roam-files/titles/aliases.org +++ /dev/null @@ -1 +0,0 @@ -#+roam_alias: "roam" "alias" diff --git a/tests/roam-files/titles/combination.org b/tests/roam-files/titles/combination.org deleted file mode 100644 index 34ec92f..0000000 --- a/tests/roam-files/titles/combination.org +++ /dev/null @@ -1,4 +0,0 @@ -#+title: TITLE PROP -#+roam_alias: "roam" "alias" - -* Headline diff --git a/tests/roam-files/titles/headline.org b/tests/roam-files/titles/headline.org deleted file mode 100644 index 4d4f027..0000000 --- a/tests/roam-files/titles/headline.org +++ /dev/null @@ -1 +0,0 @@ -* Headline diff --git a/tests/roam-files/titles/title.org b/tests/roam-files/titles/title.org deleted file mode 100644 index a241bc4..0000000 --- a/tests/roam-files/titles/title.org +++ /dev/null @@ -1 +0,0 @@ -#+title: Title diff --git a/tests/roam-files/unlinked.org b/tests/roam-files/unlinked.org deleted file mode 100644 index 0059965..0000000 --- a/tests/roam-files/unlinked.org +++ /dev/null @@ -1,3 +0,0 @@ -#+title: Unlinked - -Nothing links here :( diff --git a/tests/roam-files/web_ref.org b/tests/roam-files/web_ref.org deleted file mode 100644 index c5acfd4..0000000 --- a/tests/roam-files/web_ref.org +++ /dev/null @@ -1 +0,0 @@ -#+roam_key: https://google.com/ diff --git a/tests/test-org-roam-perf.el b/tests/test-org-roam-perf.el deleted file mode 100644 index 8182013..0000000 --- a/tests/test-org-roam-perf.el +++ /dev/null @@ -1,53 +0,0 @@ -;;; test-org-roam-perf.el --- Performance Tests for Org-roam -*- lexical-binding: t; -*- - -;; Copyright (C) 2020 Jethro Kuan - -;; Author: Jethro Kuan -;; 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 . - -;;; Commentary: -;;; Code: - -(require 'buttercup) -(require 'org-roam) - -(defconst test-org-roam-perf-zip-url "https://github.com/org-roam/test-org-files/archive/master.zip" - "Path to zip for test org-roam files.") - -(defun test-org-roam-perf--abs-path (file-path) - "Get absolute FILE-PATH from `org-roam-directory'." - (expand-file-name file-path org-roam-directory)) - -(defun test-org-roam-perf--init () - "." - (let* ((temp-loc (expand-file-name (make-temp-name "test-org-files-") temporary-file-directory)) - (zip-file-loc (concat temp-loc ".zip")) - (_ (url-copy-file test-org-roam-perf-zip-url zip-file-loc)) - (_ (shell-command (format "mkdir -p %s && unzip -j -qq %s -d %s" temp-loc zip-file-loc temp-loc)))) - (setq org-roam-directory temp-loc))) - -(describe "Cache Build" - (it "cache build from scratch time to be acceptable" - (test-org-roam-perf--init) - (pcase (benchmark-run 1 (org-roam-db-sync t)) - (`(,time ,gcs ,time-in-gc) - (message "Elapsed time: %fs (%fs in %d GCs)" time time-in-gc gcs) - (expect time :to-be-less-than 110)))) - (it "builds quickly without change" - (pcase (benchmark-run 1 (org-roam-db-sync)) - (`(,time ,gcs ,time-in-gc) - (message "Elapsed time: %fs (%fs in %d GCs)" time time-in-gc gcs) - (expect time :to-be-less-than 5))))) diff --git a/tests/test-org-roam.el b/tests/test-org-roam.el index e545285..9b05386 100644 --- a/tests/test-org-roam.el +++ b/tests/test-org-roam.el @@ -25,16 +25,6 @@ (require 'org-roam) (require 'dash) -(defun test-org-roam--abs-path (file-path) - "Get absolute FILE-PATH from `org-roam-directory'." - (expand-file-name file-path org-roam-directory)) - -(defun test-org-roam--find-file (path) - "PATH." - (let ((path (test-org-roam--abs-path path))) - (make-directory (file-name-directory path) t) - (find-file path))) - (defvar test-org-roam-directory (expand-file-name "tests/roam-files") "Directory containing org-roam test org files.") @@ -44,52 +34,35 @@ (new-dir (expand-file-name (make-temp-name "org-roam") temporary-file-directory))) (copy-directory original-dir new-dir) (setq org-roam-directory new-dir) - (org-roam-setup) - (sleep-for 2))) + (org-roam-setup))) (defun test-org-roam--teardown () + "." (org-roam-teardown) (delete-file org-roam-db-location) (org-roam-db--close)) -(describe "Ref extraction" +(describe "test files for org-roam-db-sync" (before-all (test-org-roam--init)) (after-all (test-org-roam--teardown)) - (cl-flet - ((test (fn file) - (let* ((fname (test-org-roam--abs-path file)) - (buf (find-file-noselect fname))) - (with-current-buffer buf - ;; Unlike tag extraction, it doesn't make sense to - ;; pass a filename. - (funcall fn))))) - ;; Enable "cite:" link parsing - (org-link-set-parameters "cite"))) - -;;; Tests -(xdescribe "org-roam-db-sync" - (before-each - (test-org-roam--init)) - - (after-each - (test-org-roam--teardown)) - - (it "initializes correctly" - ;; Cache - ;; TODO: Write tests - - (expect (org-roam-db-query [:select * :from refs]) - :to-have-same-items-as - (list (list "https://google.com/" (test-org-roam--abs-path "web_ref.org") "website"))) - - ;; Expect rebuilds to be really quick (nothing changed) - (expect (org-roam-db-sync) + (it "has the correct number of files" + (expect (caar (org-roam-db-query [:select (funcall count) :from files])) :to-equal - (list :files 0 :links 0 :tags 0 :titles 0 :refs 0 :deleted 0)))) + 2)) + + (it "has the correct number of nodes" + (expect (caar (org-roam-db-query [:select (funcall count) :from nodes])) + :to-equal + 2)) + + (it "has the correct number of links" + (expect (caar (org-roam-db-query [:select (funcall count) :from links])) + :to-equal + 1))) (provide 'test-org-roam)