diff --git a/org-roam-db.el b/org-roam-db.el index 117cde5..b20c696 100644 --- a/org-roam-db.el +++ b/org-roam-db.el @@ -263,6 +263,32 @@ If the file does not have any connections, nil is returned." (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 reachable from/connected to FILE in at most MAX-DISTANCE steps, +including the file itself. If the file does not have any connections, nil is returned." + (let* ((query "WITH RECURSIVE + links_of(file, link) AS + (SELECT \"from\", \"to\" FROM links UNION + SELECT \"to\", \"from\" FROM links), + -- 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)) + ;;;;; Updating (defun org-roam-db--update-titles () "Update the title of the current buffer into the cache." diff --git a/org-roam-graph.el b/org-roam-graph.el index 4a1d685..e24db98 100644 --- a/org-roam-graph.el +++ b/org-roam-graph.el @@ -130,7 +130,7 @@ into a digraph." (let* ((nodes (org-roam-db-query node-query)) (edges-query `[:with selected :as [:select [file] :from ,node-query] - :select [to from] :from links + :select :distinct [to from] :from links :where (and (in to selected) (in from selected))]) (edges (org-roam-db-query edges-query))) (insert "digraph \"org-roam\" {\n") @@ -186,18 +186,22 @@ If PREFIX, then the graph is generated but the viewer is not invoked." (call-process org-roam-graph-viewer nil 0 nil temp-graph) (view-file temp-graph))))) -(defun org-roam-graph-show-connected-component (&optional prefix) +(defun org-roam-graph-show-connected-component (&optional max-distance no-display) "Like `org-roam-graph-show', but only show nodes connected to the current entry. -If PREFIX is non-nil, the graph is generated but the viewer is not invoked." +If MAX-DISTANCE is non-nil, only nodes within the given number of steps are shown. +If NO-DISPLAY is non-nil, the graph is generated but the viewer is not invoked." (interactive "P") (unless (org-roam--org-roam-file-p) (user-error "Not in an Org-roam file")) (let* ((file (file-truename (buffer-file-name))) - (files (or (org-roam-db--connected-component file) (list file))) + (files (or (if (and max-distance (>= (prefix-numeric-value max-distance) 0)) + (org-roam-db--links-with-max-distance file max-distance) + (org-roam-db--connected-component file)) + (list file))) (query `[:select [file titles] :from titles :where (in file [,@files])])) - (org-roam-graph-show prefix query))) + (org-roam-graph-show no-display query))) (provide 'org-roam-graph)