changeset 656: |
b499d4bcfc39 |
parent: |
65102f74d1ae
|
child: |
c60decbaae3d |
author: |
Richard Westhaver <ellis@rwest.io> |
date: |
Tue, 17 Sep 2024 22:19:19 -0400 |
permissions: |
-rw-r--r-- |
description: |
removed x.lisp |
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 (defcustom org-graph-db-init-script (join-paths company-source-directory "infra/scripts/org-db-init.lisp") 70 "Path to a lisp script responsible for initializing the `org-graph-db-directory'.") 72 (cl-defstruct org-graph-db-handle 83 (defcustom org-graph-db (make-org-graph-db-handle) 84 "A handle to the database backend which stores nodes and edges." 85 :type 'org-graph-db-handle 88 (defun org-graph-from-id-locations () 89 "Populate the `org-graph' from `org-id-locations', filtering out any 90 entries not under a member of `org-graph-locations'." 92 (setq-local org-graph (copy-hash-table (org-id-locations-load))) 97 (unless (string-prefix-p x (file-truename v)) 98 (remhash k org-graph))) 102 (defun org-graph-files () 103 (org-list-files org-graph-locations org-agenda-extensions)) 105 (cl-defstruct org-graph 106 ;; TODO 2024-09-17: use integers instead of string 107 (nodes (make-hash-table :test 'equal)) 108 (edges (make-hash-table :test 'equal))) 110 (defvar org-graph (make-org-graph) 111 "The Emacs-native org-graph. Should be assigned to an `org-graph' instance.") 113 (cl-defstruct org-graph-node id name file point) 114 (cl-defstruct org-graph-edge (type 'link) in properties timestamp out) 116 (defun org-graph--file-hash (file) 117 "Compute the hash of FILE." 119 (set-buffer-multibyte nil) 120 (insert-file-contents-literally file) 121 (secure-hash 'md5 (current-buffer)))) 123 (defun org-graph-node-at-point (&optional update) 124 "Return the `org-graph-node' at point. When UPDATE is non-nil insert or 125 update the node into the currently active org-graph." 126 (let* ((file (buffer-file-name)) 127 (node (make-org-graph-node :point (point) :file file))) 128 (if (derived-mode-p 'org-mode) 130 (if (org-before-first-heading-p) 131 (setf (org-graph-node-name node) (org-get-title) 132 ;; use the filename, create a hash as id 133 (org-graph-node-id node) (org-graph--file-hash file)) 134 (setf (org-graph-node-id node) (org-id-get) 135 (org-graph-node-name node) (cadddr (org-heading-components))))) 136 (setf (org-graph-node-id node) (org-graph--file-hash file) 137 (org-graph-node-name node) (file-name-nondirectory file))) 139 (puthash (org-graph-node-id node) node (org-graph-nodes org-graph))) 140 (message "%s" node))) 143 (defun org-graph-edges-at-point (&optional update) 144 "Return a list of `org-graph-edge' instances associated with the node at 145 point. When UPDATE is non-nil insert or update the edges into the 146 currently active org-graph." 149 (if (derived-mode-p 'org-mode)) 152 (puthash (org-graph-edge-in edge) edge (org-graph-edges org-graph)))) 153 (message "%s" edge))) 155 (defun org-graph-buffer-update (&optional buffer) 156 "Map over an org buffer adding all nodes to the active org-graph." 159 (with-current-buffer (or buffer (current-buffer)) 161 (goto-char (point-min)) 162 (org-graph-node-at-point t) 163 (when (derived-mode-p 'org-mode) 164 (org-map-entries (lambda () (org-graph-node-at-point t))))))) 167 ;; See https://github.com/toshism/org-super-links/blob/develop/org-super-links.el 168 (declare-function org-make-link-description-function "ext:org-mode") 170 (defvar org-graph-edge-drawer "EDGES" 171 "Controls how/where to insert edges. If nil edges will just be inserted 174 ;; TODO 2024-09-16: edge properties 175 (defvar org-graph-edge-prefix 'org-graph-edge-prefix-timestamp 176 "Prefix to insert before the edge. 177 This can be a string, nil, or a function that takes no arguments and 180 Default is the function `org-graph-edge-prefix-timestamp' 181 which returns an inactive timestamp formatted according to the variable 182 `org-time-stamp-formats' and a separator ' <- '.") 184 ;; TODO 2024-09-16: do we need this? what sort of information for a 185 ;; given edge would go in the postfix? this may be better suited as a 186 ;; per-edge value rather than global - maybe use for comments. 187 (defvar org-graph-edge-postfix nil 188 "Postfix to insert after the edge. 189 This can be a string, nil, or a function that takes no arguments and 192 (defvar org-graph-edge-related-into-drawer t 193 "Controls how/where to insert links. 194 If non-nil a drawer will be created and links inserted there. The 195 default is `org-graph-edge-related-drawer-default-name'. If this is set to a 196 string a drawer will be created using that string. For example LINKS. 197 If nil links will just be inserted at point.") 199 (defvar org-graph-edge-related-drawer-default-name "EDGES" 200 "Default name to use for link drawer. 201 If variable `org-graph-edge-related-into-drawer' is 't' use this 202 name for the drawer. See variable `org-graph-edge-related-into-drawer' for more info.") 204 (defvar org-graph-edge-link-prefix nil 205 "Prefix to insert before the link. 206 This can be a string, nil, or a function that takes no arguments and 209 (defvar org-graph-edge-link-postfix nil 210 "Postfix to insert after the link. 211 This can be a string, nil, or a function that takes no arguments and 214 (defvar org-graph-edge-default-description-formatter org-make-link-description-function 215 "What to use if no description is provided. 216 This can be a string, nil or a function that accepts two arguments 217 LINK and DESC and returns a string. 219 nil will return the default desciption or the link. 220 string will be used only as a default fall back if set. 221 function will be called for every link. 223 Default is the variable `org-make-link-desciption-function'.") 225 (defvar org-graph-edge-search-function 'org-graph-edge-get-location 226 "The interface to use for finding target links. If you provide a custom 227 function it will be called with the `point` at the location the link 228 should be inserted. The only other requirement is that it should call 229 the function `org-graph-edge--insert-link' with a marker to the target 230 link. AKA the place you want the edge. 232 `org-graph-edge-get-location' internally uses `org-refile-get-location'.") 234 (defvar org-graph-edge-pre-link-hook nil 235 "Hook called before storing the link on the link side. 236 This is called with point at the location where it was called.") 238 (defvar org-graph-edge-pre-backlink-hook nil 239 "Hook called before storing the link on the backlink side. 240 This is called with point in the heading of the backlink.") 242 (defvar org-graph-edge-indicator-alist 248 "An alist of (EDGE-TYPE . INDICATOR) pairs. Each INDICATOR is a string 249 which will be printed between the properties and backlink of the 250 associated EDGE-TYPE.") 252 (defun org-graph-edge-get-location () 253 "Default for function `org-graph-edge-search-function' that reuses the `org-refile' machinery." 254 (let ((target (org-refile-get-location "Node"))) 255 (org-graph-edge--insert-link (set-marker (make-marker) (car (cdddr target)) 256 (get-file-buffer (car (cdr target))))))) 258 (defun org-graph-edge-search-function () 259 "Call the search interface specified in variable `org-graph-edge-search-function'." 260 (funcall org-graph-edge-search-function)) 262 (defun org-graph-edge-prefix () 263 "Return an appropriate string based on variable `org-graph-edge-prefix'." 264 (cond ((equal org-graph-edge-prefix nil) "") 265 ((stringp org-graph-edge-prefix) org-graph-edge-prefix) 266 (t (funcall org-graph-edge-prefix)))) 268 (defun org-graph-edge-postfix () 269 "Return an appropriate string based on variable `org-graph-edge-postfix'." 270 (cond ((equal org-graph-edge-postfix nil) "\n") 271 ((stringp org-graph-edge-postfix) org-graph-edge-postfix) 272 (t (funcall org-graph-edge-postfix)))) 274 (defun org-graph-edge-link-prefix () 275 "Return an appropriate string based on variable `org-graph-edge-link-prefix'." 276 (cond ((equal org-graph-edge-link-prefix nil) "") 277 ((stringp org-graph-edge-link-prefix) org-graph-edge-link-prefix) 278 (t (funcall org-graph-edge-link-prefix)))) 280 (defun org-graph-edge-link-postfix () 281 "Return an appropriate string based on variable `org-graph-edge-link-postfix'." 282 (cond ((equal org-graph-edge-link-postfix nil) "") 283 ((stringp org-graph-edge-link-postfix) org-graph-edge-link-postfix) 284 (t (funcall org-graph-edge-link-postfix)))) 286 ;; TODO 2024-09-16: edge-properties 287 (defun org-graph-edge-prefix-timestamp () 288 "Return the default prefix string for an edge. 289 Inactive timestamp formatted according to `org-time-stamp-formats' and 291 (concat (format-time-string (org-time-stamp-format t t) (current-time)) 294 (defun org-graph-edge-default-description-formatter (link desc) 295 "Return a string to use as the link desciption. 296 LINK is the link target. DESC is the provided desc." 297 (let ((p org-graph-edge-default-description-formatter)) 298 (cond ((equal p nil) (or desc link)) 299 ((stringp p) (or desc p)) 300 ((fboundp p) (funcall p link desc)) 303 (defun org-graph-edge-drawer () 304 "Name of the edge drawer, as a string, or nil. 305 This is the value of variable 306 `org-graph-edge-drawer'. However, if the current 307 entry has or inherits a EDGE_DRAWER property, it will be 308 used instead of the default value." 309 (let ((p (org-entry-get nil "EDGE_DRAWER" 'inherit t))) 310 (cond ((equal p "nil") nil) 312 (t org-graph-edge-drawer)))) 314 ;; delete related functions 315 (defun org-graph-edge--find-link (id) 316 "Return link element for ID." 318 (org-graph-edge--org-narrow-to-here) 320 (org-element-map (org-element-parse-buffer) 'link 322 (when (string= (org-element-property :path link) id) 325 (if (> (length link) 1) 326 (error "Multiple links found. Canceling delete") 329 (defun org-graph-edge--org-narrow-to-here () 330 "Narrow to current heading, excluding subheadings." 331 (org-narrow-to-subtree) 333 (org-next-visible-heading 1) 334 (narrow-to-region (point-min) (point)))) 337 (defun org-graph-edge--in-drawer () 338 "Return nil if point is not in a drawer. 339 Return element at point is in a drawer." 340 (let ((element (org-element-at-point))) 342 (not (memq (org-element-type element) '(drawer property-drawer)))) 343 (setq element (org-element-property :parent element))) 346 (defun org-graph-edge--delete-link (link) 348 If point is in drawer, delete the entire line." 350 (goto-char (org-element-property :begin link)) 351 (if (org-graph-edge--in-drawer) 354 (org-remove-empty-drawer-at (point))) 355 (delete-region (org-element-property :begin link) (org-element-property :end link))))) 357 ;;; EXPERIMENTAL 'related into drawer' 358 (defun org-graph-edge-related-into-drawer () 359 "Name of the related drawer, as a string, or nil. 360 This is the value of variable 361 `org-graph-edge-related-into-drawer'. However, if the current 362 entry has or inherits a RELATED_INTO_DRAWER property, it will be 363 used instead of the default value." 364 (let ((p (org-entry-get nil "RELATED_INTO_DRAWER" 'inherit t))) 365 (cond ((equal p "nil") nil) 366 ((equal p "t") org-graph-edge-related-drawer-default-name) 368 (p org-graph-edge-related-drawer-default-name) 369 ((stringp org-graph-edge-related-into-drawer) org-graph-edge-related-into-drawer) 370 (org-graph-edge-related-into-drawer org-graph-edge-related-drawer-default-name)))) 372 (defun org-graph-edge-link-prefix-timestamp () 373 "Return the default prefix string for an edge. 374 Inactive timestamp formatted according to `org-time-stamp-formats' and 376 (concat (format-time-string (org-time-stamp-format t t) (current-time)) 377 (format " %s " (cdr (assoc 'link org-graph-edge-indicator-alist))))) 379 (defun org-graph-edge-insert-related-link (link desc) 380 "LINK DESC related experiment." 381 (if (org-graph-edge-related-into-drawer) 382 (let* ((org-log-into-drawer (org-graph-edge-related-into-drawer)) 383 (beg (org-log-beginning t))) 385 (insert (org-graph-edge-link-prefix)) 386 (insert (org-graph-edge-link-prefix-timestamp)) 387 (org-insert-link nil link desc) 388 (insert (org-graph-edge-link-postfix) "\n") 389 (org-indent-region beg (point))) 390 (insert (org-graph-edge-link-prefix)) 391 (org-insert-link nil link desc) 392 (insert (org-graph-edge-link-postfix)))) 394 (defun org-graph-edge-quick-insert-drawer-link () 395 "Insert link into drawer regardless of variable `org-graph-edge-related-into-drawer' value." 397 ;; how to handle prefix here? 398 (let ((org-graph-edge-related-into-drawer (or org-graph-edge-related-into-drawer t)) 399 (org-graph-edge-link-prefix 'org-graph-edge-link-prefix-timestamp)) 400 (org-graph-edge-link))) 402 (defun org-graph-edge-quick-insert-inline-link () 403 "Insert inline link regardless of variable `org-graph-edge-related-into-drawer' value." 405 ;; how to handle prefix here? 406 (let ((org-graph-edge-related-into-drawer nil) 407 (org-graph-edge-link-prefix nil)) 408 (org-graph-edge-link))) 412 (defun org-graph-edge-insert (link desc) 413 "Insert edge to LINK with DESC. 414 Where the edge is placed is determined by the variable `org-graph-edge-drawer'." 415 (let* ((org-log-into-drawer (org-graph-edge-drawer)) 416 (description (org-graph-edge-default-description-formatter link desc)) 417 (beg (org-log-beginning t))) 419 (insert (org-graph-edge-prefix)) 420 (insert (org-link-make-string link description)) 421 (insert (org-graph-edge-postfix)) 422 (org-indent-region beg (point)))) 424 (defun org-graph-edge-links-action (marker hooks) 425 "Go to MARKER, run HOOKS and store a link." 426 (with-current-buffer (marker-buffer marker) 429 (widen) ;; buffer could be narrowed 430 (goto-char (marker-position marker)) 432 (call-interactively #'org-store-link) 433 (pop org-stored-links))))) 435 (defun org-graph-edge-link-builder (link) 436 "Format link description for LINK." 437 (let* ((link-ref (car link)) 438 (pre-desc (cadr link)) 439 (description (org-graph-edge-default-description-formatter link-ref pre-desc))) 440 (cons link-ref description))) 442 (defun org-graph-edge--insert-link (target &optional no-forward) 443 "Insert link to marker TARGET at current `point`, and create edge to here. 444 Only create edges in files in `org-mode' or a derived mode, otherwise just 445 act like a normal link. 447 If NO-FORWARD is non-nil skip creating the forward link. Currently 448 only used when converting a link." 449 (let* ((source (point-marker)) 450 (source-link (org-graph-edge-links-action source 'org-graph-edge-pre-link-hook)) 451 (target-link (org-graph-edge-links-action target 'org-graph-edge-pre-backlink-hook)) 452 (source-formatted-link (org-graph-edge-link-builder source-link)) 453 (target-formatted-link (org-graph-edge-link-builder target-link))) 454 (with-current-buffer (marker-buffer target) 457 (widen) ;; buffer could be narrowed 458 (goto-char (marker-position target)) 459 (when (derived-mode-p 'org-mode) 460 (org-graph-edge-insert (car source-formatted-link) (cdr source-formatted-link)))))) 462 (with-current-buffer (marker-buffer source) 464 (goto-char (marker-position source)) 465 (org-graph-edge-insert-related-link (car target-formatted-link) (cdr target-formatted-link))))))) 468 (defun org-graph-edge-convert-link (arg) 469 "Convert a normal `org-mode' link at `point' to a graph link, ARG prefix. 470 If variable `org-graph-edge-related-into-drawer' is non-nil move 471 the link into drawer. 473 When called interactively with a `C-u' prefix argument ignore 474 variable `org-graph-edge-related-into-drawer' configuration and 475 do not modify existing link." 477 (let ((from-m (point-marker)) 478 (target (save-window-excursion 479 (with-current-buffer (current-buffer) 483 (org-graph-edge--insert-link target (or arg (not org-graph-edge-related-into-drawer))) 484 (goto-char (marker-position from-m))) 486 (when (and (not arg) (org-graph-edge-related-into-drawer)) 487 (let ((begin (org-element-property :begin (org-element-context))) 488 (end (org-element-property :end (org-element-context)))) 489 (delete-region begin end)))) 492 (defun org-graph-edge-delete () 493 "Delete the link at point, and the corresponding reverse link. 494 If no reverse link exists, just delete link at point. 495 This works from either side, and deletes both sides of a link." 497 (save-window-excursion 498 (with-current-buffer (current-buffer) 500 (let ((id (org-id-get (point)))) 502 (let ((link-element (org-graph-edge--find-link id))) 504 (org-graph-edge--delete-link link-element) 505 (message "No edge found. Deleting active only."))))))) 506 (org-graph-edge--delete-link (org-element-context))) 508 (defvar org-graph-stored-mark nil 509 "mark stored with `org-graph-edge-store'.") 511 (defun org-graph-edge-store () 512 "Store a point to register for use in function `org-graph-edge-insert-link'. 513 This is primarily intended to be called before `org-capture', but 514 could possibly even be used to replace `org-store-link' IF 515 function `org-graph-edge-insert-link' is used to replace 516 `org-insert-link'. This has not been thoroughly tested outside 517 of links to/form org files." 519 (let ((c1 (make-marker))) 520 (set-marker c1 (point) (current-buffer)) 521 (setq org-graph-stored-mark c1) 522 (message "Mark stored."))) 525 (defun org-graph-edge-insert-link () 526 "Insert an edge from the list `org-graph-stored-marks'." 528 (if org-graph-stored-mark 530 (org-graph-edge--insert-link org-graph-stored-mark) 531 (setq org-graph-stored-mark nil)) 532 (org-graph-edge-link))) 535 (defun org-graph-edge-link () 536 "Insert a link edge and add a backlink edge to the target heading." 538 (org-graph-edge-search-function)) 540 (defun org-dblock-write:links () 541 "Generate a 'links' block for the designated node.") 543 (defun org-dblock-write:graph () 544 "Generate a 'graph' block for the designated set of nodes.") 547 ;; graph.el ends here