changeset 655: |
65102f74d1ae |
parent: |
328e1ff73938
|
child: |
b499d4bcfc39 |
author: |
Richard Westhaver <ellis@rwest.io> |
date: |
Mon, 16 Sep 2024 21:28:33 -0400 |
permissions: |
-rw-r--r-- |
description: |
some optimizations, may have muddied the waters with cli-opt a bit though.. tbd |
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 "EDGES" 115 "Controls how/where to insert edges. If nil edges will just be inserted 118 ;; TODO 2024-09-16: edge properties 119 (defvar org-graph-edge-prefix 'org-graph-edge-prefix-timestamp 120 "Prefix to insert before the edge. 121 This can be a string, nil, or a function that takes no arguments and 124 Default is the function `org-graph-edge-prefix-timestamp' 125 which returns an inactive timestamp formatted according to the variable 126 `org-time-stamp-formats' and a separator ' <- '.") 128 ;; TODO 2024-09-16: do we need this? what sort of information for a 129 ;; given edge would go in the postfix? this may be better suited as a 130 ;; per-edge value rather than global - maybe use for comments. 131 (defvar org-graph-edge-postfix nil 132 "Postfix to insert after the edge. 133 This can be a string, nil, or a function that takes no arguments and 136 (defvar org-graph-edge-related-into-drawer t 137 "Controls how/where to insert links. 138 If non-nil a drawer will be created and links inserted there. The 139 default is `org-graph-edge-related-drawer-default-name'. If this is set to a 140 string a drawer will be created using that string. For example LINKS. 141 If nil links will just be inserted at point.") 143 (defvar org-graph-edge-related-drawer-default-name "EDGES" 144 "Default name to use for link drawer. 145 If variable `org-graph-edge-related-into-drawer' is 't' use this 146 name for the drawer. See variable `org-graph-edge-related-into-drawer' for more info.") 148 (defvar org-graph-edge-link-prefix nil 149 "Prefix to insert before the link. 150 This can be a string, nil, or a function that takes no arguments and 153 (defvar org-graph-edge-link-postfix nil 154 "Postfix to insert after the link. 155 This can be a string, nil, or a function that takes no arguments and 158 (defvar org-graph-edge-default-description-formatter org-make-link-description-function 159 "What to use if no description is provided. 160 This can be a string, nil or a function that accepts two arguments 161 LINK and DESC and returns a string. 163 nil will return the default desciption or the link. 164 string will be used only as a default fall back if set. 165 function will be called for every link. 167 Default is the variable `org-make-link-desciption-function'.") 169 (defvar org-graph-edge-search-function 'org-graph-edge-get-location 170 "The interface to use for finding target links. If you provide a custom 171 function it will be called with the `point` at the location the link 172 should be inserted. The only other requirement is that it should call 173 the function `org-graph-edge--insert-link' with a marker to the target 174 link. AKA the place you want the edge. 176 `org-graph-edge-get-location' internally uses `org-refile-get-location'.") 178 (defvar org-graph-edge-pre-link-hook nil 179 "Hook called before storing the link on the link side. 180 This is called with point at the location where it was called.") 182 (defvar org-graph-edge-pre-backlink-hook nil 183 "Hook called before storing the link on the backlink side. 184 This is called with point in the heading of the backlink.") 186 (defvar org-graph-edge-indicator-alist 192 "An alist of (EDGE-TYPE . INDICATOR) pairs. Each INDICATOR is a string 193 which will be printed between the properties and backlink of the 194 associated EDGE-TYPE.") 196 (defun org-graph-edge-get-location () 197 "Default for function `org-graph-edge-search-function' that reuses the `org-refile' machinery." 198 (let ((target (org-refile-get-location "Node"))) 199 (org-graph-edge--insert-link (set-marker (make-marker) (car (cdddr target)) 200 (get-file-buffer (car (cdr target))))))) 202 (defun org-graph-edge-search-function () 203 "Call the search interface specified in variable `org-graph-edge-search-function'." 204 (funcall org-graph-edge-search-function)) 206 (defun org-graph-edge-prefix () 207 "Return an appropriate string based on variable `org-graph-edge-prefix'." 208 (cond ((equal org-graph-edge-prefix nil) "") 209 ((stringp org-graph-edge-prefix) org-graph-edge-prefix) 210 (t (funcall org-graph-edge-prefix)))) 212 (defun org-graph-edge-postfix () 213 "Return an appropriate string based on variable `org-graph-edge-postfix'." 214 (cond ((equal org-graph-edge-postfix nil) "\n") 215 ((stringp org-graph-edge-postfix) org-graph-edge-postfix) 216 (t (funcall org-graph-edge-postfix)))) 218 (defun org-graph-edge-link-prefix () 219 "Return an appropriate string based on variable `org-graph-edge-link-prefix'." 220 (cond ((equal org-graph-edge-link-prefix nil) "") 221 ((stringp org-graph-edge-link-prefix) org-graph-edge-link-prefix) 222 (t (funcall org-graph-edge-link-prefix)))) 224 (defun org-graph-edge-link-postfix () 225 "Return an appropriate string based on variable `org-graph-edge-link-postfix'." 226 (cond ((equal org-graph-edge-link-postfix nil) "") 227 ((stringp org-graph-edge-link-postfix) org-graph-edge-link-postfix) 228 (t (funcall org-graph-edge-link-postfix)))) 230 ;; TODO 2024-09-16: edge-properties 231 (defun org-graph-edge-prefix-timestamp () 232 "Return the default prefix string for an edge. 233 Inactive timestamp formatted according to `org-time-stamp-formats' and 235 (concat (format-time-string (org-time-stamp-format t t) (current-time)) 238 (defun org-graph-edge-default-description-formatter (link desc) 239 "Return a string to use as the link desciption. 240 LINK is the link target. DESC is the provided desc." 241 (let ((p org-graph-edge-default-description-formatter)) 242 (cond ((equal p nil) (or desc link)) 243 ((stringp p) (or desc p)) 244 ((fboundp p) (funcall p link desc)) 247 (defun org-graph-edge-drawer () 248 "Name of the edge drawer, as a string, or nil. 249 This is the value of variable 250 `org-graph-edge-drawer'. However, if the current 251 entry has or inherits a EDGE_DRAWER property, it will be 252 used instead of the default value." 253 (let ((p (org-entry-get nil "EDGE_DRAWER" 'inherit t))) 254 (cond ((equal p "nil") nil) 256 (t org-graph-edge-drawer)))) 258 ;; delete related functions 259 (defun org-graph-edge--find-link (id) 260 "Return link element for ID." 262 (org-graph-edge--org-narrow-to-here) 264 (org-element-map (org-element-parse-buffer) 'link 266 (when (string= (org-element-property :path link) id) 269 (if (> (length link) 1) 270 (error "Multiple links found. Canceling delete") 273 (defun org-graph-edge--org-narrow-to-here () 274 "Narrow to current heading, excluding subheadings." 275 (org-narrow-to-subtree) 277 (org-next-visible-heading 1) 278 (narrow-to-region (point-min) (point)))) 281 (defun org-graph-edge--in-drawer () 282 "Return nil if point is not in a drawer. 283 Return element at point is in a drawer." 284 (let ((element (org-element-at-point))) 286 (not (memq (org-element-type element) '(drawer property-drawer)))) 287 (setq element (org-element-property :parent element))) 290 (defun org-graph-edge--delete-link (link) 292 If point is in drawer, delete the entire line." 294 (goto-char (org-element-property :begin link)) 295 (if (org-graph-edge--in-drawer) 298 (org-remove-empty-drawer-at (point))) 299 (delete-region (org-element-property :begin link) (org-element-property :end link))))) 301 ;;; EXPERIMENTAL 'related into drawer' 302 (defun org-graph-edge-related-into-drawer () 303 "Name of the related drawer, as a string, or nil. 304 This is the value of variable 305 `org-graph-edge-related-into-drawer'. However, if the current 306 entry has or inherits a RELATED_INTO_DRAWER property, it will be 307 used instead of the default value." 308 (let ((p (org-entry-get nil "RELATED_INTO_DRAWER" 'inherit t))) 309 (cond ((equal p "nil") nil) 310 ((equal p "t") org-graph-edge-related-drawer-default-name) 312 (p org-graph-edge-related-drawer-default-name) 313 ((stringp org-graph-edge-related-into-drawer) org-graph-edge-related-into-drawer) 314 (org-graph-edge-related-into-drawer org-graph-edge-related-drawer-default-name)))) 316 (defun org-graph-edge-link-prefix-timestamp () 317 "Return the default prefix string for an edge. 318 Inactive timestamp formatted according to `org-time-stamp-formats' and 320 (concat (format-time-string (org-time-stamp-format t t) (current-time)) 321 (format " %s " (cdr (assoc 'link org-graph-edge-indicator-alist))))) 323 (defun org-graph-edge-insert-related-link (link desc) 324 "LINK DESC related experiment." 325 (if (org-graph-edge-related-into-drawer) 326 (let* ((org-log-into-drawer (org-graph-edge-related-into-drawer)) 327 (beg (org-log-beginning t))) 329 (insert (org-graph-edge-link-prefix)) 330 (insert (org-graph-edge-link-prefix-timestamp)) 331 (org-insert-link nil link desc) 332 (insert (org-graph-edge-link-postfix) "\n") 333 (org-indent-region beg (point))) 334 (insert (org-graph-edge-link-prefix)) 335 (org-insert-link nil link desc) 336 (insert (org-graph-edge-link-postfix)))) 338 (defun org-graph-edge-quick-insert-drawer-link () 339 "Insert link into drawer regardless of variable `org-graph-edge-related-into-drawer' value." 341 ;; how to handle prefix here? 342 (let ((org-graph-edge-related-into-drawer (or org-graph-edge-related-into-drawer t)) 343 (org-graph-edge-link-prefix 'org-graph-edge-link-prefix-timestamp)) 344 (org-graph-edge-link))) 346 (defun org-graph-edge-quick-insert-inline-link () 347 "Insert inline link regardless of variable `org-graph-edge-related-into-drawer' value." 349 ;; how to handle prefix here? 350 (let ((org-graph-edge-related-into-drawer nil) 351 (org-graph-edge-link-prefix nil)) 352 (org-graph-edge-link))) 356 (defun org-graph-edge-insert (link desc) 357 "Insert edge to LINK with DESC. 358 Where the edge is placed is determined by the variable `org-graph-edge-drawer'." 359 (let* ((org-log-into-drawer (org-graph-edge-drawer)) 360 (description (org-graph-edge-default-description-formatter link desc)) 361 (beg (org-log-beginning t))) 363 (insert (org-graph-edge-prefix)) 364 (insert (org-link-make-string link description)) 365 (insert (org-graph-edge-postfix)) 366 (org-indent-region beg (point)))) 368 (defun org-graph-edge-links-action (marker hooks) 369 "Go to MARKER, run HOOKS and store a link." 370 (with-current-buffer (marker-buffer marker) 373 (widen) ;; buffer could be narrowed 374 (goto-char (marker-position marker)) 376 (call-interactively #'org-store-link) 377 (pop org-stored-links))))) 379 (defun org-graph-edge-link-builder (link) 380 "Format link description for LINK." 381 (let* ((link-ref (car link)) 382 (pre-desc (cadr link)) 383 (description (org-graph-edge-default-description-formatter link-ref pre-desc))) 384 (cons link-ref description))) 386 (defun org-graph-edge--insert-link (target &optional no-forward) 387 "Insert link to marker TARGET at current `point`, and create edge to here. 388 Only create edges in files in `org-mode' or a derived mode, otherwise just 389 act like a normal link. 391 If NO-FORWARD is non-nil skip creating the forward link. Currently 392 only used when converting a link." 393 (let* ((source (point-marker)) 394 (source-link (org-graph-edge-links-action source 'org-graph-edge-pre-link-hook)) 395 (target-link (org-graph-edge-links-action target 'org-graph-edge-pre-backlink-hook)) 396 (source-formatted-link (org-graph-edge-link-builder source-link)) 397 (target-formatted-link (org-graph-edge-link-builder target-link))) 398 (with-current-buffer (marker-buffer target) 401 (widen) ;; buffer could be narrowed 402 (goto-char (marker-position target)) 403 (when (derived-mode-p 'org-mode) 404 (org-graph-edge-insert (car source-formatted-link) (cdr source-formatted-link)))))) 406 (with-current-buffer (marker-buffer source) 408 (goto-char (marker-position source)) 409 (org-graph-edge-insert-related-link (car target-formatted-link) (cdr target-formatted-link))))))) 412 (defun org-graph-edge-convert-link (arg) 413 "Convert a normal `org-mode' link at `point' to a graph link, ARG prefix. 414 If variable `org-graph-edge-related-into-drawer' is non-nil move 415 the link into drawer. 417 When called interactively with a `C-u' prefix argument ignore 418 variable `org-graph-edge-related-into-drawer' configuration and 419 do not modify existing link." 421 (let ((from-m (point-marker)) 422 (target (save-window-excursion 423 (with-current-buffer (current-buffer) 427 (org-graph-edge--insert-link target (or arg (not org-graph-edge-related-into-drawer))) 428 (goto-char (marker-position from-m))) 430 (when (and (not arg) (org-graph-edge-related-into-drawer)) 431 (let ((begin (org-element-property :begin (org-element-context))) 432 (end (org-element-property :end (org-element-context)))) 433 (delete-region begin end)))) 436 (defun org-graph-edge-delete () 437 "Delete the link at point, and the corresponding reverse link. 438 If no reverse link exists, just delete link at point. 439 This works from either side, and deletes both sides of a link." 441 (save-window-excursion 442 (with-current-buffer (current-buffer) 444 (let ((id (org-id-get (point)))) 446 (let ((link-element (org-graph-edge--find-link id))) 448 (org-graph-edge--delete-link link-element) 449 (message "No edge found. Deleting active only."))))))) 450 (org-graph-edge--delete-link (org-element-context))) 453 (defun org-graph-edge-store-link (&optional GOTO KEYS) 454 "Store a point to register for use in function `org-graph-edge-insert-link'. 455 This is primarily intended to be called before `org-capture', but 456 could possibly even be used to replace `org-store-link' IF 457 function `org-graph-edge-insert-link' is used to replace 458 `org-insert-link'. This has not been thoroughly tested outside 459 of links to/form org files. GOTO and KEYS are unused." 464 ;; this is a hack. if the point is at the first char of a heading 465 ;; the marker is not updated as expected when text is inserted 466 ;; above the heading. for example a capture template inserted 467 ;; above. that results in the link being to the heading above the 469 (goto-char (line-end-position)) 470 (let ((c1 (make-marker))) 471 (set-marker c1 (point) (current-buffer)) 473 (message "Link copied")))) 476 (defun org-graph-edge-insert-link () 477 "Insert an edge link from the register." 479 (let* ((target (get-register ?^))) 482 (org-graph-edge--insert-link target) 483 (set-register ?^ nil)) 484 (message "No link to insert!")))) 487 (defun org-graph-edge-link () 488 "Insert a link edge and add a backlink edge to the target heading." 490 (org-graph-edge-search-function)) 493 ;; graph.el ends here