changeset 652: |
328e1ff73938 |
parent: |
af486e0a40c9
|
child: |
65102f74d1ae |
author: |
Richard Westhaver <ellis@rwest.io> |
date: |
Sat, 14 Sep 2024 23:55:38 -0400 |
permissions: |
-rw-r--r-- |
description: |
graph and cli updates |
1 ;;; graph.el --- Graph-oriented Extensions -*- lexical-binding: t; -*- 3 ;; Copyright (C) 2024 The Compiler Company 5 ;; Author: Richard Westhaver <richard.westhaver@gmail.com> 6 ;; Keywords: docs, maint, outlines, extensions 8 ;; This program is free software; you can redistribute it and/or modify 9 ;; it under the terms of the GNU General Public License as published by 10 ;; the Free Software Foundation, either version 3 of the License, or 11 ;; (at your option) any later version. 13 ;; This program is distributed in the hope that it will be useful, 14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 ;; GNU General Public License for more details. 18 ;; You should have received a copy of the GNU General Public License 19 ;; along with this program. If not, see <https://www.gnu.org/licenses/>. 34 (defcustom org-graph-db-directory (join-paths user-org-stash-directory "graph") 35 "graph database storage directory." 39 (defcustom org-graph-locations (list (join-paths company-org-directory "notes")) 40 "List of directories to check for nodes." 41 :type '(list directory) 44 (defcustom org-graph-include-agenda-files nil 45 "When non-nil, include `org-agenda-files' in the graph." 49 (defcustom org-graph-include-archive nil 50 "When non-nil, include `org-arhive-location' in the graph." 54 (defcustom org-graph-include-org-directory nil 55 "When non-nil, include `org-directory' files in the graph." 59 (defcustom org-graph-compaction-hook nil 60 "Hook run when a graph is compacted to `org-graph-db'." 64 (defcustom org-graph-capture-hook nil 65 "Hook run when a node is added to the graph." 69 (defvar-local org-graph nil 70 "The currently active graph of org nodes.") 72 (defcustom org-graph-db-init-script (join-paths company-source-directory "infra/scripts/org-db-init.lisp") 73 "Path to a lisp script responsible for initializing the `org-graph-db-directory'.") 75 (cl-defstruct org-graph-db-handle 85 (defcustom org-graph-db (make-org-graph-db-handle) 86 "A handle to the database backend which stores nodes and edges." 87 :type 'org-graph-db-handle 90 (defun org-graph-from-id-locations () 91 "Populate the `org-graph' from `org-id-locations', filtering out any 92 entries not under a member of `org-graph-locations'." 94 (setq-local org-graph (copy-hash-table (org-id-locations-load))) 99 (unless (string-prefix-p x (file-truename v)) 100 (remhash k org-graph))) 101 org-graph-locations)) 104 (defun org-dblock-write:links () 105 "Generate a 'links' block for the designated node.") 107 (defun org-dblock-write:graph () 108 "Generate a 'graph' block for the designated set of nodes.") 111 ;; See https://github.com/toshism/org-super-links/blob/develop/org-super-links.el 112 (declare-function org-make-link-description-function "ext:org-mode") 114 (defvar org-graph-edge-drawer "LINKS" 115 "Controls how/where to insert edges. If nil edges will just be inserted 118 (defvar org-graph-edge-prefix 'org-graph-edge-prefix-timestamp 119 "Prefix to insert before the edge. 120 This can be a string, nil, or a function that takes no arguments and 123 Default is the function `org-graph-edge-prefix-timestamp' 124 which returns an inactive timestamp formatted according to the variable 125 `org-time-stamp-formats' and a separator ' <- '.") 127 (defvar org-graph-edge-postfix nil 128 "Postfix to insert after the edge. 129 This can be a string, nil, or a function that takes no arguments and 132 (defvar org-graph-edge-related-into-drawer nil 133 "Controls how/where to insert links. 134 If non-nil a drawer will be created and links inserted there. The 135 default is `org-graph-edge-related-drawer-default-name'. If this is set to a 136 string a drawer will be created using that string. For example LINKS. 137 If nil links will just be inserted at point.") 139 (defvar org-graph-edge-related-drawer-default-name "RELATED" 140 "Default name to use for link drawer. 141 If variable `org-graph-edge-related-into-drawer' is 't' use this 142 name for the drawer. See variable `org-graph-edge-related-into-drawer' for more info.") 144 (defvar org-graph-edge-link-prefix nil 145 "Prefix to insert before the link. 146 This can be a string, nil, or a function that takes no arguments and 149 (defvar org-graph-edge-link-postfix nil 150 "Postfix to insert after the link. 151 This can be a string, nil, or a function that takes no arguments and 154 (defvar org-graph-edge-default-description-formatter org-make-link-description-function 155 "What to use if no description is provided. 156 This can be a string, nil or a function that accepts two arguments 157 LINK and DESC and returns a string. 159 nil will return the default desciption or the link. 160 string will be used only as a default fall back if set. 161 function will be called for every link. 163 Default is the variable `org-make-link-desciption-function'.") 165 (defvar org-graph-edge-search-function 'org-graph-edge-get-location 166 "The interface to use for finding target links. If you provide a custom 167 function it will be called with the `point` at the location the link 168 should be inserted. The only other requirement is that it should call 169 the function `org-graph-edge--insert-link' with a marker to the target 170 link. AKA the place you want the edge. 172 `org-graph-edge-get-location' internally uses `org-refile-get-location'.") 174 (defvar org-graph-edge-pre-link-hook nil 175 "Hook called before storing the link on the link side. 176 This is called with point at the location where it was called.") 178 (defvar org-graph-edge-pre-backlink-hook nil 179 "Hook called before storing the link on the backlink side. 180 This is called with point in the heading of the backlink.") 182 (defun org-graph-edge-get-location () 183 "Default for function `org-graph-edge-search-function' that reuses the `org-refile' machinery." 184 (let ((target (org-refile-get-location "Node"))) 185 (org-graph-edge--insert-link (set-marker (make-marker) (car (cdddr target)) 186 (get-file-buffer (car (cdr target))))))) 188 (defun org-graph-edge-search-function () 189 "Call the search interface specified in variable `org-graph-edge-search-function'." 190 (funcall org-graph-edge-search-function)) 192 (defun org-graph-edge-prefix () 193 "Return an appropriate string based on variable `org-graph-edge-prefix'." 194 (cond ((equal org-graph-edge-prefix nil) "") 195 ((stringp org-graph-edge-prefix) org-graph-edge-prefix) 196 (t (funcall org-graph-edge-prefix)))) 198 (defun org-graph-edge-postfix () 199 "Return an appropriate string based on variable `org-graph-edge-postfix'." 200 (cond ((equal org-graph-edge-postfix nil) "\n") 201 ((stringp org-graph-edge-postfix) org-graph-edge-postfix) 202 (t (funcall org-graph-edge-postfix)))) 204 (defun org-graph-edge-link-prefix () 205 "Return an appropriate string based on variable `org-graph-edge-link-prefix'." 206 (cond ((equal org-graph-edge-link-prefix nil) "") 207 ((stringp org-graph-edge-link-prefix) org-graph-edge-link-prefix) 208 (t (funcall org-graph-edge-link-prefix)))) 210 (defun org-graph-edge-link-postfix () 211 "Return an appropriate string based on variable `org-graph-edge-link-postfix'." 212 (cond ((equal org-graph-edge-link-postfix nil) "") 213 ((stringp org-graph-edge-link-postfix) org-graph-edge-link-postfix) 214 (t (funcall org-graph-edge-link-postfix)))) 216 (defun org-graph-edge-prefix-timestamp () 217 "Return the default prefix string for an edge. 218 Inactive timestamp formatted according to `org-time-stamp-formats' and 220 (concat (format-time-string (org-time-stamp-format t t) (current-time)) 223 (defun org-graph-edge-default-description-formatter (link desc) 224 "Return a string to use as the link desciption. 225 LINK is the link target. DESC is the provided desc." 226 (let ((p org-graph-edge-default-description-formatter)) 227 (cond ((equal p nil) (or desc link)) 228 ((stringp p) (or desc p)) 229 ((fboundp p) (funcall p link desc)) 232 (defun org-graph-edge-drawer () 233 "Name of the edge drawer, as a string, or nil. 234 This is the value of variable 235 `org-graph-edge-drawer'. However, if the current 236 entry has or inherits a EDGE_DRAWER property, it will be 237 used instead of the default value." 238 (let ((p (org-entry-get nil "EDGE_DRAWER" 'inherit t))) 239 (cond ((equal p "nil") nil) 240 ((equal p "t") "LINKS") 243 ((stringp org-graph-edge-drawer) org-graph-edge-drawer) 244 (org-graph-edge-drawer "LINKS")))) 246 ;; delete related functions 247 (defun org-graph-edge--find-link (id) 248 "Return link element for ID." 250 (org-graph-edge--org-narrow-to-here) 252 (org-element-map (org-element-parse-buffer) 'link 254 (when (string= (org-element-property :path link) id) 257 (if (> (length link) 1) 258 (error "Multiple links found. Canceling delete") 261 (defun org-graph-edge--org-narrow-to-here () 262 "Narrow to current heading, excluding subheadings." 263 (org-narrow-to-subtree) 265 (org-next-visible-heading 1) 266 (narrow-to-region (point-min) (point)))) 269 (defun org-graph-edge--in-drawer () 270 "Return nil if point is not in a drawer. 271 Return element at point is in a drawer." 272 (let ((element (org-element-at-point))) 274 (not (memq (org-element-type element) '(drawer property-drawer)))) 275 (setq element (org-element-property :parent element))) 279 (defun org-graph-edge--delete-link (link) 281 If point is in drawer, delete the entire line." 283 (goto-char (org-element-property :begin link)) 284 (if (org-graph-edge--in-drawer) 287 (org-remove-empty-drawer-at (point))) 288 (delete-region (org-element-property :begin link) (org-element-property :end link))))) 291 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 292 ;; EXPERIMENTAL related into drawer 293 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 295 (defun org-graph-edge-related-into-drawer () 296 "Name of the related drawer, as a string, or nil. 297 This is the value of variable 298 `org-graph-edge-related-into-drawer'. However, if the current 299 entry has or inherits a RELATED_INTO_DRAWER property, it will be 300 used instead of the default value." 301 (let ((p (org-entry-get nil "RELATED_INTO_DRAWER" 'inherit t))) 302 (cond ((equal p "nil") nil) 303 ((equal p "t") org-graph-edge-related-drawer-default-name) 305 (p org-graph-edge-related-drawer-default-name) 306 ((stringp org-graph-edge-related-into-drawer) org-graph-edge-related-into-drawer) 307 (org-graph-edge-related-into-drawer org-graph-edge-related-drawer-default-name)))) 309 (defun org-graph-edge-insert-relatedlink (link desc) 310 "LINK DESC related experiment." 311 (if (org-graph-edge-related-into-drawer) 312 (let* ((org-log-into-drawer (org-graph-edge-related-into-drawer)) 313 (beg (org-log-beginning t))) 315 (insert (org-graph-edge-link-prefix)) 316 (org-insert-link nil link desc) 317 (insert (org-graph-edge-link-postfix) "\n") 318 (org-indent-region beg (point))) 319 (insert (org-graph-edge-link-prefix)) 320 (org-insert-link nil link desc) 321 (insert (org-graph-edge-link-postfix)))) 323 (defun org-graph-edge-link-prefix-timestamp () 324 "Return the default prefix string for an edge. 325 Inactive timestamp formatted according to `org-time-stamp-formats' and 327 (concat (format-time-string (org-time-stamp-format t t) (current-time)) 330 (defun org-graph-edge-quick-insert-drawer-link () 331 "Insert link into drawer regardless of variable `org-graph-edge-related-into-drawer' value." 333 ;; how to handle prefix here? 334 (let ((org-graph-edge-related-into-drawer (or org-graph-edge-related-into-drawer t)) 335 (org-graph-edge-link-prefix 'org-graph-edge-link-prefix-timestamp)) 336 (org-graph-edge-link))) 338 (defun org-graph-edge-quick-insert-inline-link () 339 "Insert inline link regardless of variable `org-graph-edge-related-into-drawer' value." 341 ;; how to handle prefix here? 342 (let ((org-graph-edge-related-into-drawer nil) 343 (org-graph-edge-link-prefix nil)) 344 (org-graph-edge-link))) 346 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 347 ;; /EXPERIMENTAL related into drawer 348 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 350 (defun org-graph-edge-insert (link desc) 351 "Insert edge to LINK with DESC. 352 Where the edge is placed is determined by the variable `org-graph-edge-drawer'." 353 (let* ((org-log-into-drawer (org-graph-edge-drawer)) 354 (description (org-graph-edge-default-description-formatter link desc)) 355 (beg (org-log-beginning t))) 357 (insert (org-graph-edge-prefix)) 358 (insert (org-link-make-string link description)) 359 (insert (org-graph-edge-postfix)) 360 (org-indent-region beg (point)))) 362 (defun org-graph-edge-links-action (marker hooks) 363 "Go to MARKER, run HOOKS and store a link." 364 (with-current-buffer (marker-buffer marker) 367 (widen) ;; buffer could be narrowed 368 (goto-char (marker-position marker)) 370 (call-interactively #'org-store-link) 371 (pop org-stored-links))))) 373 (defun org-graph-edge-link-builder (link) 374 "Format link description for LINK." 375 (let* ((link-ref (car link)) 376 (pre-desc (cadr link)) 377 (description (org-graph-edge-default-description-formatter link-ref pre-desc))) 378 (cons link-ref description))) 380 (defun org-graph-edge--insert-link (target &optional no-forward) 381 "Insert link to marker TARGET at current `point`, and create edge to here. 382 Only create edges in files in `org-mode' or a derived mode, otherwise just 383 act like a normal link. 385 If NO-FORWARD is non-nil skip creating the forward link. Currently 386 only used when converting a link." 387 (let* ((source (point-marker)) 388 (source-link (org-graph-edge-links-action source 'org-graph-edge-pre-link-hook)) 389 (target-link (org-graph-edge-links-action target 'org-graph-edge-pre-backlink-hook)) 390 (source-formatted-link (org-graph-edge-link-builder source-link)) 391 (target-formatted-link (org-graph-edge-link-builder target-link))) 392 (with-current-buffer (marker-buffer target) 395 (widen) ;; buffer could be narrowed 396 (goto-char (marker-position target)) 397 (when (derived-mode-p 'org-mode) 398 (org-graph-edge-insert (car source-formatted-link) (cdr source-formatted-link)))))) 400 (with-current-buffer (marker-buffer source) 402 (goto-char (marker-position source)) 403 (org-graph-edge-insert-relatedlink (car target-formatted-link) (cdr target-formatted-link))))))) 407 (defun org-graph-edge-convert-link-to-edge (arg) 408 "Convert a normal `org-mode' link at `point' to a graph link, ARG prefix. 409 If variable `org-graph-edge-related-into-drawer' is non-nil move 410 the link into drawer. 412 When called interactively with a `C-u' prefix argument ignore 413 variable `org-graph-edge-related-into-drawer' configuration and 414 do not modify existing link." 416 (let ((from-m (point-marker)) 417 (target (save-window-excursion 418 (with-current-buffer (current-buffer) 422 (org-graph-edge--insert-link target (or arg (not org-graph-edge-related-into-drawer))) 423 (goto-char (marker-position from-m))) 425 (when (and (not arg) (org-graph-edge-related-into-drawer)) 426 (let ((begin (org-element-property :begin (org-element-context))) 427 (end (org-element-property :end (org-element-context)))) 428 (delete-region begin end)))) 431 (defun org-graph-edge-delete-link () 432 "Delete the link at point, and the corresponding reverse link. 433 If no reverse link exists, just delete link at point. 434 This works from either side, and deletes both sides of a link." 436 (save-window-excursion 437 (with-current-buffer (current-buffer) 439 (let ((id (org-id-get (point)))) 441 (let ((link-element (org-graph-edge--find-link id))) 443 (org-graph-edge--delete-link link-element) 444 (message "No edge found. Deleting active only."))))))) 445 (org-graph-edge--delete-link (org-element-context))) 448 (defun org-graph-edge-store-link (&optional GOTO KEYS) 449 "Store a point to register for use in function `org-graph-edge-insert-link'. 450 This is primarily intended to be called before `org-capture', but 451 could possibly even be used to replace `org-store-link' IF 452 function `org-graph-edge-insert-link' is used to replace 453 `org-insert-link'. This has not been thoroughly tested outside 454 of links to/form org files. GOTO and KEYS are unused." 459 ;; this is a hack. if the point is at the first char of a heading 460 ;; the marker is not updated as expected when text is inserted 461 ;; above the heading. for example a capture template inserted 462 ;; above. that results in the link being to the heading above the 464 (goto-char (line-end-position)) 465 (let ((c1 (make-marker))) 466 (set-marker c1 (point) (current-buffer)) 468 (message "Link copied")))) 470 ;; not sure if this should be autoloaded or left to config? 472 (advice-add 'org-capture :before #'org-graph-edge-store-link) 475 (defun org-graph-edge-insert-link () 476 "Insert an edge link from the register." 478 (let* ((target (get-register ?^))) 481 (org-graph-edge--insert-link target) 482 (set-register ?^ nil)) 483 (message "No link to insert!")))) 486 (defun org-graph-edge-link () 487 "Insert a link and add a backlink to the target heading." 489 (org-graph-edge-search-function)) 492 ;; graph.el ends here