diff --git a/org-brain.el b/org-brain.el index b4aeaee..2e0cb09 100644 --- a/org-brain.el +++ b/org-brain.el @@ -42,29 +42,33 @@ :prefix "org-brain-" :group 'org) -;;;; Custom vars +;;; Custom vars -(defcustom org-brain-path (file-truename (expand-file-name "brain" org-directory)) +(defcustom org-brain-path (file-truename (expand-file-name "brain" org-directory)) "The root directory of your org-brain. `org-mode' files placed in this directory, or its subdirectories, will be considered org-brain entries." :group 'org-brain + :local t :type '(directory)) (defcustom org-brain-scan-directories-recursively t "If subdirectories inside `org-brain-path' are considered part of the brain or not." :group 'org-brain + :local t :type '(boolean)) (defcustom org-brain-files-extension "org" "The extension for entry files in `org-brain-path'." :group 'org-brain + :local t :type '(string)) (defcustom org-brain-ignored-resource-links '("fuzzy" "radio" "brain-child" "brain-parent" "brain-friend") "`org-link-types' which shouldn't be shown as resources in `org-brain-visualize'." :group 'org-brain + :local t :type '(repeat string)) (defcustom org-brain-backlink nil @@ -76,8 +80,9 @@ variable to non-nil would also create A as a resource in B. If this variable is a string it will be added as a prefix in the backlink. Example: \"<--\" would add \"<--A\" in the example above." :group 'org-brain + :local t :type '(restricted-sexp :match-alternatives - (stringp 't 'nil))) + (stringp 't 'nil))) (defcustom org-brain-backlink-heading t "If the org heading should be used when creating a backlink. @@ -90,15 +95,17 @@ Example: Creating a brain-link in A to B and A is an org file with the headings: Setting this variable to t will create the following backlink in B: [[file:A.org::*Child][Parent header > Child]]." :group 'org-brain + :local t :type '(boolean)) (make-obsolete-variable 'org-brain-suggest-stored-link-as-resource "org-brain-suggest-stored-link-as-resource isn't needed because of `org-insert-link-global'." "0.6") -(defcustom org-brain-data-file (file-truename (expand-file-name ".org-brain-data.el" org-brain-path)) +(defcustom org-brain-data-file (file-truename (expand-file-name ".org-brain-data.el" org-brain-path)) "Where org-brain data is saved." :group 'org-brain + :local t :type '(directory)) (load org-brain-data-file t t) @@ -109,6 +116,7 @@ If 'all, choose from all file and headline entries. If 'files, only choose from file entries. If 'root, only choose from file entries in `org-brain-path' (non-recursive)." :group 'org-brain + :local t :type '(choice (const :tag "All entries" all) (const :tag "Only file entries" files) @@ -118,6 +126,7 @@ If 'root, only choose from file entries in `org-brain-path' (non-recursive)." "If set to nil `org-brain' is optimized for headline entries. Only headlines will be considered as entries when visualizing." :group 'org-brain + :local t :type '(boolean)) (make-obsolete-variable @@ -132,41 +141,55 @@ If nil, create the new entry as a file entry relative to `org-brain-path'. If set to a string it should be a file entry. That entry will be used as the local parent and the new entry will be a headline." :group 'org-brain + :local t :type '(choice string (const nil))) (defcustom org-brain-show-full-entry nil "Always show entire entry contents?" :group 'org-brain + :local t :type '(boolean)) (defcustom org-brain-show-resources t "Should entry resources be shown in `org-brain-visualize'?" :group 'org-brain + :local t :type '(boolean)) (defcustom org-brain-show-text t "Should the entry text be shown in `org-brain-visualize'?" :group 'org-brain + :local t :type '(boolean)) (defcustom org-brain-show-history t "Should the navigation history be shown in `org-brain-visualize'?" :group 'org-brain + :local t :type '(boolean)) (defcustom org-brain-show-icons t "Should icons from `org-agenda-category-icon-alist' be shown when visualizing?" :group 'org-brain + :local t :type '(boolean)) (defcustom org-brain-category-icon-width 2 "The character width of icons." :group 'org-brain + :local t :type '(integer)) (defcustom org-brain-quit-after-goto nil "Should the *org-brain* buffer window close itself after executing a goto command?" :group 'org-brain + :local t + :type '(boolean)) + +(defcustom org-brain-goto-evil-normal t + "While in evil mode visit org-mode file node in normal rather than insert mode" + :group 'org-brain + :local t :type '(boolean)) (defcustom org-brain-headline-links-only-show-visible t @@ -175,6 +198,7 @@ local parent and the new entry will be a headline." See the docstring for `org-brain-headline-at' for more info on how this is implemented." :group 'org-brain + :local t :type '(boolean)) (defcustom org-brain-file-entries-use-title t @@ -182,6 +206,7 @@ on how this is implemented." This can potentially be slow. If set to nil, the relative filenames will be shown instead, which is faster." :group 'org-brain + :local t :type '(boolean)) (defcustom org-brain-scan-for-header-entries t @@ -190,6 +215,7 @@ Useful if you don't tend to use header entries in your workflow, since scanning can be slow in long file entries. This only affects selection prompts and not functions like `org-brain-headline-to-file'." :group 'org-brain + :local t :type '(boolean)) (defcustom org-brain-headline-entry-name-format-string "%s::%s" @@ -197,29 +223,35 @@ This only affects selection prompts and not functions like `org-brain-headline-t This `format' string is used in `org-brain-entry-name' for headline entries. `format' gets two objects: the file and the headline." :group 'org-brain + :local t :type '(string)) + (defcustom org-brain-visualize-text-hook nil "Hook runs after inserting `org-brain-text' in `org-brain-visualize'. Can be used to prettify the entry text, e.g. `org-display-inline-images'." :group 'org-brain + :local t :type 'hook) (defcustom org-brain-after-visualize-hook nil "Hook run after `org-brain-visualize', but before `org-brain-text'. Can be used to prettify the buffer output, e.g. `ascii-art-to-unicode'." :group 'org-brain + :local t :type 'hook) (defcustom org-brain-new-entry-hook nil "Hook run after a new headline entry has been created." :group 'org-brain + :local t :type 'hook) (defcustom org-brain-visualize-follow-hook nil "Hook run after viewing an entry by means of `org-brain-visualize-follow'." :group 'org-brain + :local t :type 'hook) (defcustom org-brain-after-resource-button-functions nil @@ -228,6 +260,7 @@ Insert a bullet, then run hook functions, then insert the actual button. Each function must take a single argument: the org link to the resource. Can for instance be used in combination with `all-the-icons'." :group 'org-brain + :local t :type 'hook) (defcustom org-brain-vis-title-prepend-functions '(org-brain-entry-icon) @@ -235,6 +268,7 @@ Can for instance be used in combination with `all-the-icons'." Each function should take the entry as the only argument, and should return a string. The strings are prepended to the entry title." :group 'org-brain + :local t :type 'hook :options '(org-brain-entry-icon org-brain-entry-todo-state @@ -245,6 +279,7 @@ should return a string. The strings are prepended to the entry title." Each function should take the entry as the only argument, and should return a string. The strings are appended to the entry title." :group 'org-brain + :local t :type 'hook :options '(org-brain-entry-icon org-brain-entry-todo-state @@ -254,6 +289,7 @@ should return a string. The strings are appended to the entry title." "Like `org-brain-vis-title-prepend-functions' for the current visualized entry. First `org-brain-vis-title-prepend-functions' are ran, and then these." :group 'org-brain + :local t :type 'hook :options '(org-brain-entry-icon org-brain-entry-todo-state @@ -263,6 +299,7 @@ First `org-brain-vis-title-prepend-functions' are ran, and then these." "Like `org-brain-vis-title-append-functions' for the current visualized entry. First `org-brain-vis-title-append-functions' are ran, and then these." :group 'org-brain + :local t :type 'hook :options '(org-brain-entry-icon org-brain-entry-todo-state @@ -272,69 +309,82 @@ First `org-brain-vis-title-append-functions' are ran, and then these." "`org-mode' tag stopping `org-brain-visualize' from fetching entry text. Only applies to headline entries." :group 'org-brain + :local t :type '(string)) (defcustom org-brain-exclude-resouces-tag "resourceless" "`org-mode' tag stopping `org-brain-visualize' from fetching entry resources. Only applies to headline entries." :group 'org-brain + :local t :type '(string)) (defcustom org-brain-exclude-children-tag "childless" "`org-mode' tag which exclude the headline's children from org-brain's entries." :group 'org-brain + :local t :type '(string)) (defcustom org-brain-show-children-tag "showchildren" "`org-mode' tag which get entire subtree from headline entry during `org-brain-text'." :group 'org-brain + :local t :type '(string)) (defcustom org-brain-exclude-tree-tag "nobrain" "`org-mode' tag which exclude the headline and its children from org-brain's entries." :group 'org-brain + :local t :type '(string)) (defcustom org-brain-exclude-siblings-tag "nosiblings" "`org-mode' tag which prevents the siblings of children of this node from being displayed." :group 'org-brain + :local t :type '(string)) (defcustom org-brain-exclude-local-parent-tag "nolocalparent" "`org-mode' tag which prevents this node to be displayed as a local parent." :group 'org-brain + :local t :type '(string)) (defcustom org-brain-each-child-on-own-line-tag "ownline" "`org-mode' tag which makes each child of the headline entry be listed on its own line." :group 'org-brain + :local t :type '(string)) (defcustom org-brain-no-sort-children-tag "nosort" "`org-mode' tag which makes the children of the headline entry appear in file order rather than sorted." :group 'org-brain + :local t :type '(string)) (defcustom org-brain-wander-interval 3 "Seconds between randomized entries, when using `org-brain-visualize-wander'." :group 'org-brain + :local t :type 'integer) (defcustom org-brain-title-max-length 0 "If a title is longer than this, it'll be capped during `org-brain-visualize'. If 0 or a negative value, the title won't be capped." :group 'org-brain + :local t :type 'integer) (defcustom org-brain-cap-mind-map-titles nil "Whether to cap entries longer than org-brain-title-max-length in mind map visualization mode." :group 'org-brain + :local t :type '(boolean)) (defcustom org-brain-entry-separator ";" "Can be used as a separator when adding children, parents, or friends. Doing so allows for adding multiple entries at once." :group 'org-brain + :local t :type '(string)) (make-obsolete-variable @@ -351,11 +401,13 @@ Reasonable values include: '(window-width): lines will break at the width of the window 'most-positive-fixnum: All children will be on one line" :group 'org-brain + :local t :type '(sexp)) (defcustom org-brain-refile-max-level 1 "The default max-level used by `org-brain-refile'." :group 'org-brain + :local t :type 'integer) (defcustom org-brain-child-link-name "brain-child" @@ -363,6 +415,7 @@ Reasonable values include: Must be set before `org-brain' is loaded. Insert links using `org-insert-link'." :group 'org-brain + :local t :type '(string)) (defcustom org-brain-parent-link-name "brain-parent" @@ -370,6 +423,7 @@ Insert links using `org-insert-link'." Must be set before `org-brain' is loaded. Insert links using `org-insert-link'." :group 'org-brain + :local t :type '(string)) (defcustom org-brain-friend-link-name "brain-friend" @@ -377,46 +431,55 @@ Insert links using `org-insert-link'." Must be set before `org-brain' is loaded. Insert links using `org-insert-link'." :group 'org-brain + :local t :type '(string)) (defcustom org-brain-children-property-name "BRAIN_CHILDREN" "The name for the org-mode property in which child relationships are stored. Must be set before `org-brain' is loaded." :group 'org-brain + :local t + :local t :type '(string)) (defcustom org-brain-parents-property-name "BRAIN_PARENTS" "The name for the org-mode property in which brain relationships are stored. Must be set before `org-brain' is loaded." :group 'org-brain + :local t :type '(string)) (defcustom org-brain-friends-property-name "BRAIN_FRIENDS" "The name for the org-mode property in which friend relationships are stored. Must be set before `org-brain' is loaded." :group 'org-brain + :local t :type '(string)) (defcustom org-brain-edge-property-prefix-name "BRAIN_EDGE" "The prefix for the org-mode property in which edge annotations are stored. Must be set before `org-brain' is loaded." :group 'org-brain + :local t :type '(string)) (defcustom org-brain-resources-drawer-name "RESOURCES" "The org-mode drawer name in which resources of an entry are stored. Must be set before `org-brain' is loaded." :group 'org-brain + :local t :type '(string)) (defcustom org-brain-open-same-window nil "Should `org-brain-visualize' open up in the same window it was launched in?" :group 'org-brain + :local t :type '(boolean)) (defcustom org-brain-completion-system 'default "The completion system to be used by `org-brain'." :group 'org-brain + :local t :type '(radio (const :tag "Ido" ido) (const :tag "Helm" helm) @@ -424,7 +487,7 @@ Must be set before `org-brain' is loaded." (const :tag "Default" default) (function :tag "Custom function"))) -;;;;; Faces and face helper functions +;;; Faces and face helper functions (defface org-brain-title '((t . (:inherit 'org-level-1))) @@ -501,6 +564,7 @@ File entries also use this, but also applies `org-brain-file-face-template'.") to the visualized entry.") ;; This needs to be here or defface complains that it is undefined. + (defun org-brain-specified-face-attrs (face &optional frame) "Return a plist of all face attributes of FACE that are not `unspecified'. If FRAME is not specified, `selected-frame' is used." @@ -535,7 +599,7 @@ EDGE determines if `org-brain-edge-annotation-face-template' should be used." `((t . ,(org-brain-specified-face-attrs 'highlight))) "Attributes of this face are added to the faces of selected entries.") -;;;; API +;;; API ;; An entry is either a string or a list of three strings. ;; If a string, then the entry is a file. @@ -545,26 +609,26 @@ EDGE determines if `org-brain-edge-annotation-face-template' should be used." ;; In the case of headline nicknames the car of the list is a symbol (instead of a string) ;; ('alias "headline title" "ID") -(defvar org-brain--vis-entry nil +(defvar-local org-brain--vis-entry nil "The last entry argument to `org-brain-visualize'.") -(defvar org-brain--vis-entry-keywords nil +(defvar-local org-brain--vis-entry-keywords nil "The `org-brain-keywords' of `org-brain--vis-entry'.") -(defvar org-brain--vis-history nil +(defvar-local org-brain--vis-history nil "History previously visualized entries. Newest first.") -(defvar org-brain-resources-start-re (concat "^[ \t]*:" org-brain-resources-drawer-name ":[ \t]*$") +(defvar-local org-brain-resources-start-re (concat "^[ \t]*:" org-brain-resources-drawer-name ":[ \t]*$") "Regular expression matching the first line of a resources drawer.") -(defvar org-brain-keyword-regex "^#\\+[a-zA-Z_]+:" +(defvar-local org-brain-keyword-regex "^#\\+[a-zA-Z_]+:" "Regular expression matching org keywords.") -(defvar org-brain-pins nil "List of pinned org-brain entries.") +(defvar-local org-brain-pins nil "List of pinned org-brain entries.") -(defvar org-brain-selected nil "List of selected org-brain entries.") +(defvar-local org-brain-selected nil "List of selected org-brain entries.") -(defvar org-brain-headline-cache (make-hash-table :test 'equal) +(defvar-local org-brain-headline-cache (make-hash-table :test 'equal) "Cache for headline entries. Updates when files have been saved.") ;;;###autoload @@ -633,7 +697,7 @@ Run `org-brain-new-entry-hook' if a new ID is created." (defun org-brain-entry-path (entry &optional check-title) "Get path of org-brain ENTRY. -If CHECK-TITLE is non-nil, consider that ENTRY might be a file entry title." + If CHECK-TITLE is non-nil, consider that ENTRY might be a file entry title." (let ((name (if (org-brain-filep entry) (or (and check-title org-brain-file-entries-use-title @@ -647,7 +711,7 @@ If CHECK-TITLE is non-nil, consider that ENTRY might be a file entry title." entry) (car entry)))) (file-truename (expand-file-name (org-link-unescape (format "%s.%s" name org-brain-files-extension)) - org-brain-path)))) + org-brain-path)))) (defun org-brain-files (&optional relative) "Get all org files (recursively) in `org-brain-path'. @@ -662,7 +726,7 @@ Ignores \"dotfiles\"." (directory-files org-brain-path t (format "^[^.].*\\.%s$" org-brain-files-extension))))) -(defvar org-brain-link-re +(defvar-local org-brain-link-re "\\[\\[\\(\\(?:[^][\\]\\|\\\\\\(?:\\\\\\\\\\)*[][]\\|\\\\+[^][]\\)+\\)]\\(?:\\[\\(\\(?:.\\|\\)+?\\)]\\)?]" "Regex matching an `org-mode' link. The first match is the URI, the second is the (optional) desciption. @@ -1396,9 +1460,10 @@ Optionally only delete if matching MATCH-REGEX." (org-brain-entry-identifier parent))) (org-save-all-org-buffers)) -;;;; Buffer commands +;;; Buffer commands ;;;###autoload + (defun org-brain-add-child (entry children &optional verbose) "Add external CHILDREN (a list of entries) to ENTRY. If called interactively use `org-brain-entry-at-pt' and let user choose entry. @@ -1644,6 +1709,8 @@ Unless GOTO-FILE-FUNC is nil, use `pop-to-buffer-same-window' for opening the en (when (org-at-heading-p) (org-show-entry) (org-show-subtree))) + (when (and org-brain-goto-evil-normal (memq 'evil-mode minor-mode-list)) + (evil-normal-state)) entry) (define-obsolete-function-alias 'org-brain-open 'org-brain-goto "0.4") @@ -2158,6 +2225,7 @@ If run interactively, get ENTRY from context and prompt for TITLE." (org-brain--revert-if-visualizing)) ;;;###autoload + (defun org-brain-set-tags (entry) "Modify the ENTRY tags. Use `org-set-tags-command' on headline ENTRY. @@ -2305,23 +2373,23 @@ function." (org-brain-path-entry-name file) (car (split-string (org-element-property :path link) "::")))))))))) -;;;; Sorting +;;; Sorting (defun org-brain-title< (entry1 entry2) "Return non-nil if title of ENTRY1 is less than ENTRY2 in lexicographic order. Case is significant." (string< (org-brain-title entry1) (org-brain-title entry2))) -(defvar org-brain-visualize-sort-function 'org-brain-title< +(defvar-local org-brain-visualize-sort-function 'org-brain-title< "How to sort lists of relationships when visualizing. Should be a function which accepts two entries as arguments. The function returns t if the first entry is smaller than the second. If you don't want to sort the relationships, set this to `ignore'.") -;;;; Visualize +;;; Visualize -(defvar org-brain--visualize-follow nil "Used by `org-brain-visualize-follow'.") +(defvar-local org-brain--visualize-follow nil "Used by `org-brain-visualize-follow'.") ;;;###autoload (defun org-brain-visualize-follow (should-follow) @@ -2470,7 +2538,7 @@ restrict to descendants of the visualized entry." (org-brain-headline-entries))))) (org-brain-visualize (nth (random (length entries)) entries) nil nil t))) -(defvar org-brain-wander-timer nil +(defvar-local org-brain-wander-timer nil "A timer running `org-brain-visualize-random' at a set interval. Can be (de)activated by `org-brain-visualize-wander'.") @@ -2765,10 +2833,11 @@ TWO-WAY will be t unless called with `\\[universal-argument\\]'." (org-set-property (org-brain-edge-prop-name target) annotation) (org-delete-property (org-brain-edge-prop-name target))) (save-buffer))) - (when two-way - (run-with-idle-timer 0.2 nil 'org-brain-annotate-edge - target entry annotation nil)) - (org-brain--revert-if-visualizing)) + +(when two-way + (run-with-idle-timer 0.2 nil 'org-brain-annotate-edge + target entry annotation nil)) +(org-brain--revert-if-visualizing)) (defun org-brain-visualize-back () "Go back to the previously visualized entry." @@ -2815,7 +2884,7 @@ Used as `bookmark-make-record-function' in `org-brain-visualize-mode'." (setq-local revert-buffer-function #'org-brain-visualize-revert) (setq-local bookmark-make-record-function #'org-brain-make-bookmark-record)) -;;;;; Keybindings +;;; Keybindings (define-key org-brain-visualize-mode-map "p" 'org-brain-add-parent) (define-key org-brain-visualize-mode-map "P" 'org-brain-remove-parent) @@ -2887,7 +2956,7 @@ Used as `bookmark-make-record-function' in `org-brain-visualize-mode'." "" " " "<" ">" "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" [33554464]))) -;;;;; Drawing helpers +;;; Drawing helpers (defun org-brain--visually-sort (lst) "Sort LST destructively according to org-brain-visualize-sort-function." @@ -2919,10 +2988,10 @@ Used as `bookmark-make-record-function' in `org-brain-visualize-mode'." "List of siblings, sorted unless ENTRY has a :nosort: tag." (let ((siblings (org-brain-siblings entry))) (if (member org-brain-no-sort-children-tag (org-brain-get-tags entry)) - siblings + siblings (sort siblings (lambda (x y) - (funcall org-brain-visualize-sort-function - (car x) (car y))))))) + (funcall org-brain-visualize-sort-function + (car x) (car y))))))) (defun org-brain--visually-sorted-siblings-from (pair) "List of siblings for a parent, sorted unless the parent in PAIR has a :nosort: tag, or empty list if the parent has a :nosiblings: tag." @@ -3011,7 +3080,7 @@ Helper function for `org-brain-visualize'." 'org-brain-sibling) 'sibling) (setq max-width (max max-width (current-column))) (newline (forward-line 1))) - children-links) + children-links) (org-goto-line base-line) (forward-line (1- sibling-middle)) (picture-forward-column col-start) @@ -3054,7 +3123,7 @@ Helper function for `org-brain-visualize'." (move-to-column (/ (+ (cdar (last parent-positions)) (cdar parent-positions)) 2) - t) + t) (delete-char 1) (when (> (length parent-positions) 1) (org-brain--insert-wire "+") @@ -3105,7 +3174,7 @@ Helper function for `org-brain-visualize'." (insert "\n\n--- Resources ---------------------------------\n") (mapc #'org-brain-insert-resource-button resources))) -(defvar org-brain--vis-entry-text-marker 0 +(defvar-local org-brain--vis-entry-text-marker 0 "Marker to where `org-brain-text' begins in `org-brain-visualize-mode'.") (defun org-brain--vis-text (entry) @@ -3133,7 +3202,7 @@ Helper function for `org-brain-visualize'." (run-hooks 'org-brain-after-visualize-hook))) (run-hooks 'org-brain-after-visualize-hook))) -;;;;; Mind-map +;;; Mind-map (defun org-brain-map-create-indentation (level) "Return a string of spaces, length determined by indentation LEVEL." @@ -3147,7 +3216,7 @@ Each button is indented, starting at level determined by INDENT." (org-brain-insert-visualize-button entry 'org-brain-child (if (> max-level 0) 'grandchild 'child)) (insert "\n") (dolist (child (and (> max-level 0) - (org-brain--visually-sorted-children entry))) + (org-brain--visually-sorted-children entry))) (org-brain-insert-recursive-child-buttons child (1- max-level) (1+ indent)))) (defun org-brain-tree-depth (tree) @@ -3216,8 +3285,10 @@ Return the position of ENTRY in the buffer." (org-brain-insert-recursive-child-buttons child (1- children-max-level) (1+ indent))) entry-pos)) -(defvar org-brain-visualizing-mind-map nil) +(defvar-local org-brain-visualizing-mind-map nil) + (defvar-local org-brain-mind-map-child-level 1) + (defvar-local org-brain-mind-map-parent-level 1) (defun org-brain-visualize-mind-map () @@ -3227,7 +3298,7 @@ Return the position of ENTRY in the buffer." (setq org-brain-visualizing-mind-map (not org-brain-visualizing-mind-map)) (org-brain-visualize org-brain--vis-entry))) -;;;;; Show/hide nested levels +;;; Show/hide nested levels (defun org-brain-show-descendant-level () "Show one more level of descendant entries to the right in the mind-map visualization buffer." (interactive) @@ -3267,7 +3338,7 @@ Return the position of ENTRY in the buffer." (define-obsolete-function-alias 'org-brain-visualize-remove-grandparent 'org-brain-hide-ancestor-level "0.5") -;;;;; Polymode +;;; Polymode ;; This code has been adapted from Dustin Lacewell's project polybrain ;; Have a look at: https://github.com/dustinlacewell/polybrain.el/ @@ -3276,41 +3347,41 @@ Return the position of ENTRY in the buffer." (define-hostmode org-brain-poly-hostmode :mode 'org-brain-visualize-mode) - (define-innermode org-brain-poly-innermode - :mode 'org-mode - :head-matcher "^[─-]\\{3\\} Entry [─-]+\n" - :tail-matcher "\\'" - :head-mode 'host - :tail-mode 'host) - - (define-polymode org-brain-polymode - :hostmode 'org-brain-poly-hostmode - :innermodes '(org-brain-poly-innermode) - (setq-local polymode-move-these-vars-from-old-buffer - (delq 'buffer-read-only polymode-move-these-vars-from-old-buffer))) - - (defun org-brain-polymode-save () - "Save entry text to the entry's file." - (interactive) - (when (buffer-modified-p) - (let ((text (save-excursion - (goto-char org-brain--vis-entry-text-marker) - (end-of-line) - (buffer-substring (point) (point-max))))) - (find-file (org-brain-entry-path org-brain--vis-entry)) - (seq-let (entry-min entry-max) (org-brain-text-positions org-brain--vis-entry) - (goto-char entry-min) - (delete-region entry-min entry-max) - (insert text) - (unless (looking-at-p "\n") - (insert "\n\n")) - (save-buffer) - (switch-to-buffer (other-buffer (current-buffer) 1)) - (set-buffer-modified-p nil))))) - - (define-key org-brain-polymode-map "\C-x\C-s" 'org-brain-polymode-save)) - -;;;; Brain link +(define-innermode org-brain-poly-innermode + :mode 'org-mode + :head-matcher "^[─-]\\{3\\} Entry [─-]+\n" + :tail-matcher "\\'" + :head-mode 'host + :tail-mode 'host) + +(define-polymode org-brain-polymode + :hostmode 'org-brain-poly-hostmode + :innermodes '(org-brain-poly-innermode) + (setq-local polymode-move-these-vars-from-old-buffer + (delq 'buffer-read-only polymode-move-these-vars-from-old-buffer))) + +(defun org-brain-polymode-save () + "Save entry text to the entry's file." + (interactive) + (when (buffer-modified-p) + (let ((text (save-excursion + (goto-char org-brain--vis-entry-text-marker) + (end-of-line) + (buffer-substring (point) (point-max))))) + (find-file (org-brain-entry-path org-brain--vis-entry)) + (seq-let (entry-min entry-max) (org-brain-text-positions org-brain--vis-entry) + (goto-char entry-min) + (delete-region entry-min entry-max) + (insert text) + (unless (looking-at-p "\n") + (insert "\n\n")) + (save-buffer) + (switch-to-buffer (other-buffer (current-buffer) 1)) + (set-buffer-modified-p nil))))) + +(define-key org-brain-polymode-map "\C-x\C-s" 'org-brain-polymode-save)) + +;;; Brain link (defun org-brain-link-complete (&optional link-type) "Create an org-link target string to a file in `org-brain-path'. @@ -3348,7 +3419,7 @@ LINK-TYPE will be \"brain\" by default." (ignore-errors (org-get-outline-path t))))) (concat "::* " (nth 0 (last outline-path))))) (concat (and (stringp org-brain-backlink) org-brain-backlink) - (if (and org-brain-backlink-heading + (if (and org-brain-backlink-heading (ignore-errors (org-get-outline-path t))) (string-join (org-get-outline-path t) " > ") (file-name-base))) @@ -3386,7 +3457,7 @@ LINK-TYPE will be \"brain\" by default." :complete (lambda () (org-brain-link-complete org-brain-friend-link-name)) :follow 'org-brain-goto) -;;;; Brain switch link +;;; Brain switch link (defun org-brain--switch-link-complete () "Create an org-link target string to an org-brain and one of its entries." @@ -3416,136 +3487,141 @@ ENTRY should be a string; an id in the case of an headline entry." :complete 'org-brain--switch-link-complete :follow 'org-brain--switch-link-follow) -;;;; Helm integration +;;; Helm integration (with-eval-after-load "helm" - (defun helm-brain--add-children (_c) - (dolist (candidate (helm-marked-candidates)) - (org-brain-add-relationship - (org-brain-entry-at-pt) (or (org-brain-entry-from-id candidate) candidate))) - (org-brain--revert-if-visualizing)) - - (defun helm-brain--add-parents (_c) - (dolist (candidate (helm-marked-candidates)) - (org-brain-add-relationship - (or (org-brain-entry-from-id candidate) candidate) (org-brain-entry-at-pt))) - (org-brain--revert-if-visualizing)) - - (defun helm-brain--add-friends (_c) - (dolist (candidate (helm-marked-candidates)) - (org-brain--internal-add-friendship - (org-brain-entry-at-pt) (or (org-brain-entry-from-id candidate) candidate))) - (org-brain--revert-if-visualizing)) - - (defun helm-brain--delete-entries (_c) - (dolist (candidate (helm-marked-candidates)) - (org-brain-delete-entry (or (org-brain-entry-from-id candidate) candidate)))) - - (defun helm-brain--archive (_c) - (dolist (candidate (helm-marked-candidates)) - (org-brain-archive (or (org-brain-entry-from-id candidate) candidate)))) - - (defun helm-brain--select (_c) - (dolist (candidate (helm-marked-candidates)) - (org-brain-select (or (org-brain-entry-from-id candidate) candidate) 1))) - - (defun helm-brain--unselect (_c) - (dolist (candidate (helm-marked-candidates)) - (org-brain-select (or (org-brain-entry-from-id candidate) candidate) -1))) - - (defvar helm-brain--actions - (helm-make-actions - "Visualize" (lambda (x) - (org-brain-visualize (or (org-brain-entry-from-id x) x))) - "Add children" 'helm-brain--add-children - "Add parents" 'helm-brain--add-parents - "Add friends" 'helm-brain--add-friends - "Delete" 'helm-brain--delete-entries - "Archive" 'helm-brain--archive - "Select" 'helm-brain--select - "Unselect" 'helm-brain--unselect)) - - (defvar helm-brain--source - (helm-make-source "Brain" 'helm-source-sync - :candidates #'org-brain--all-targets - :action 'helm-brain--actions)) - - (defvar helm-brain--fallback-source - (helm-make-source "New entry" 'helm-source-dummy - :action (helm-make-actions - "Visualize" (lambda (x) - (org-brain-visualize (org-brain-get-entry-from-title x))) - "Add children" 'helm-brain--add-children - "Add parents" 'helm-brain--add-parents - "Add friends" 'helm-brain--add-friends))) - - (defun helm-brain () - "Use `helm' to choose among your org-brain entries. + +(defun helm-brain--add-children (_c) + (dolist (candidate (helm-marked-candidates)) + (org-brain-add-relationship + (org-brain-entry-at-pt) (or (org-brain-entry-from-id candidate) candidate))) + (org-brain--revert-if-visualizing)) + +(defun helm-brain--add-parents (_c) + (dolist (candidate (helm-marked-candidates)) + (org-brain-add-relationship + (or (org-brain-entry-from-id candidate) candidate) (org-brain-entry-at-pt))) + (org-brain--revert-if-visualizing)) + +(defun helm-brain--add-friends (_c) + (dolist (candidate (helm-marked-candidates)) + (org-brain--internal-add-friendship + (org-brain-entry-at-pt) (or (org-brain-entry-from-id candidate) candidate))) + (org-brain--revert-if-visualizing)) + +(defun helm-brain--delete-entries (_c) + (dolist (candidate (helm-marked-candidates)) + (org-brain-delete-entry (or (org-brain-entry-from-id candidate) candidate)))) + +(defun helm-brain--archive (_c) + (dolist (candidate (helm-marked-candidates)) + (org-brain-archive (or (org-brain-entry-from-id candidate) candidate)))) + +(defun helm-brain--select (_c) + (dolist (candidate (helm-marked-candidates)) + (org-brain-select (or (org-brain-entry-from-id candidate) candidate) 1))) + +(defun helm-brain--unselect (_c) + (dolist (candidate (helm-marked-candidates)) + (org-brain-select (or (org-brain-entry-from-id candidate) candidate) -1))) + +(defvar-local elm-brain--actions + (helm-make-actions + "Visualize" (lambda (x) + (org-brain-visualize (or (org-brain-entry-from-id x) x))) + "Add children" 'helm-brain--add-children + "Add parents" 'helm-brain--add-parents + "Add friends" 'helm-brain--add-friends + "Delete" 'helm-brain--delete-entries + "Archive" 'helm-brain--archive + "Select" 'helm-brain--select + "Unselect" 'helm-brain--unselect)) + +(defvar-local elm-brain--source + (helm-make-source "Brain" 'helm-source-sync + :candidates #'org-brain--all-targets + :action 'helm-brain--actions)) + +(defvar-local elm-brain--fallback-source + (helm-make-source "New entry" 'helm-source-dummy + :action (helm-make-actions + "Visualize" (lambda (x) + (org-brain-visualize (org-brain-get-entry-from-title x))) + "Add children" 'helm-brain--add-children + "Add parents" 'helm-brain--add-parents + "Add friends" 'helm-brain--add-friends))) + +(defun helm-brain () + "Use `helm' to choose among your org-brain entries. Provides actions for visualizing, adding/removing relations, etc. Supports selecting multiple entries at once." - (interactive) - (helm :sources '(helm-brain--source helm-brain--fallback-source)))) + (interactive) + (helm :sources '(helm-brain--source helm-brain--fallback-source)))) -;;;; Ivy integration +;;; Ivy integration (with-eval-after-load "ivy" - (defun counsel-brain () - "Use Ivy to choose among your org-brain entries. + +(defun counsel-brain () + "Use Ivy to choose among your org-brain entries. Provides actions for visualizing, adding/removing relations, etc." - (interactive) - (let ((targets (org-brain--all-targets))) - (ivy-read "Org-brain: " - targets - :action (lambda (x) - (org-brain-visualize - (if (stringp x) - (org-brain-get-entry-from-title x) - (or (org-brain-entry-from-id (cdr x)) - (cdr x))))) - :preselect (ignore-errors - (org-brain-entry-name - (org-brain-entry-at-pt))) - :caller 'counsel-brain))) - - (defun counsel-brain--add-child (child) - (org-brain-add-relationship (org-brain-entry-at-pt) - (or (org-brain-entry-from-id (cdr child)) - (cdr child))) - (org-brain--revert-if-visualizing)) - - (defun counsel-brain--add-parent (parent) - (org-brain-add-relationship (or (org-brain-entry-from-id (cdr parent)) - (cdr parent)) - (org-brain-entry-at-pt)) - (org-brain--revert-if-visualizing)) - - (defun counsel-brain--add-friend (friend) - (org-brain--internal-add-friendship (org-brain-entry-at-pt) - (or (org-brain-entry-from-id (cdr friend)) - (cdr friend))) - (org-brain--revert-if-visualizing)) - - (defun counsel-brain--delete (x) - (org-brain-delete-entry (or (org-brain-entry-from-id (cdr x)) (cdr x)))) - - (defun counsel-brain--archive (x) - (org-brain-archive (or (org-brain-entry-from-id (cdr x)) (cdr x)))) - - (defun counsel-brain--select (x) - (org-brain-select (or (org-brain-entry-from-id (cdr x)) (cdr x)) 1)) - - (defun counsel-brain--unselect (x) - (org-brain-select (or (org-brain-entry-from-id (cdr x)) (cdr x)) -1)) - - (ivy-set-actions - 'counsel-brain - '(("c" counsel-brain--add-child "add as child") - ("p" counsel-brain--add-parent "add as parent") - ("f" counsel-brain--add-friend "add as friend") - ("d" counsel-brain--delete "delete") - ("a" counsel-brain--archive "archive") - ("s" counsel-brain--select "select") - ("S" counsel-brain--unselect "unselect")))) + (interactive) + (let ((targets (org-brain--all-targets))) + (ivy-read "Org-brain: " + targets + :action (lambda (x) + (org-brain-visualize + (if (stringp x) + (org-brain-get-entry-from-title x) + (or (org-brain-entry-from-id (cdr x)) + (cdr x))))) + :preselect (ignore-errors + (org-brain-entry-name + (org-brain-entry-at-pt))) + :caller 'counsel-brain))) + +(defun counsel-brain--add-child (child) + (org-brain-add-relationship (org-brain-entry-at-pt) + (or (org-brain-entry-from-id (cdr child)) + (cdr child))) + (org-brain--revert-if-visualizing)) + +(defun counsel-brain--add-parent (parent) + (org-brain-add-relationship (or (org-brain-entry-from-id (cdr parent)) + (cdr parent)) + (org-brain-entry-at-pt)) + (org-brain--revert-if-visualizing)) + +(defun counsel-brain--add-friend (friend) + (org-brain--internal-add-friendship (org-brain-entry-at-pt) + (or (org-brain-entry-from-id (cdr friend)) + (cdr friend))) + (org-brain--revert-if-visualizing)) + +(defun counsel-brain--delete (x) + (org-brain-delete-entry (or (org-brain-entry-from-id (cdr x)) (cdr x)))) + +(defun counsel-brain--archive (x) + (org-brain-archive (or (org-brain-entry-from-id (cdr x)) (cdr x)))) + +(defun counsel-brain--select (x) + (org-brain-select (or (org-brain-entry-from-id (cdr x)) (cdr x)) 1)) + +(defun counsel-brain--unselect (x) + (org-brain-select (or (org-brain-entry-from-id (cdr x)) (cdr x)) -1)) + +(ivy-set-actions + 'counsel-brain + '(("c" counsel-brain--add-child "add as child") + ("p" counsel-brain--add-parent "add as parent") + ("f" counsel-brain--add-friend "add as friend") + ("d" counsel-brain--delete "delete") + ("a" counsel-brain--archive "archive") + ("s" counsel-brain--select "select") + ("S" counsel-brain--unselect "unselect")))) + +;;; winding up (provide 'org-brain) + ;;; org-brain.el ends here diff --git a/org-brain.org b/org-brain.org new file mode 100644 index 0000000..e396197 --- /dev/null +++ b/org-brain.org @@ -0,0 +1,5206 @@ +#+title: Literate org-brain.el +#+PROPERTY: header-args:emacs-lisp :tangle ./org-brain.el :mkdirp yes + +* top matter + + #+begin_src emacs-lisp + + ;;; org-brain.el --- Org-mode concept mapping -*- lexical-binding: t; -*- + + ;; Copyright (C) 2017--2020 Erik Sjöstrand + ;; MIT License + + ;; Author: Erik Sjöstrand + ;; URL: http://github.com/Kungsgeten/org-brain + ;; Keywords: outlines hypermedia + ;; Package-Requires: ((emacs "25.1") (org "9.2")) + ;; Version: 0.94 + + #+end_src + + #+RESULTS: + +* Commentary: + + #+begin_src emacs-lisp + + ;;; Commentary: + + ;; org-brain implements a variant of concept mapping with org-mode, it is + ;; inspired by The Brain software (http://thebrain.com). An org-brain is a + ;; network of org-mode entries, where each entry is a file or a headline, and + ;; you can get a visual overview of the relationships between the entries: + ;; parents, children, siblings and friends. This visual overview can also be + ;; used to browse your entries. You can think of entries as nodes in a mind map, + ;; or pages in a wiki. + + ;; All org files put into your `org-brain-path' directory will be considered + ;; entries in your org-brain. Headlines with an ID property in your entry file(s) + ;; are also considered as entries. + + ;; Use `org-brain-visualize' to see the relationships between entries, quickly + ;; add parents/children/friends/pins to an entry, and open them for editing. + + #+end_src + + #+RESULTS: + +* Code: +** requires + #+begin_src emacs-lisp + + ;;; Code: + + (require 'org-element) + (require 'org-attach) + (require 'org-agenda) + (require 'org-macs) + (require 'org-id) + (require 'picture) + (require 'subr-x) + (require 'seq) + + #+end_src + +** define group + #+begin_src emacs-lisp + + (defgroup org-brain nil + "Org-mode concept mapping" + :prefix "org-brain-" + :group 'org) + + #+end_src + + #+RESULTS: + : org-brain + +** Custom vars + #+begin_src emacs-lisp + + ;;; Custom vars + + #+end_src + + #+RESULTS: + +*** org-brain-path + #+begin_src emacs-lisp + + (defcustom org-brain-path (file-truename (expand-file-name "brain" org-directory)) + "The root directory of your org-brain. + + `org-mode' files placed in this directory, or its subdirectories, + will be considered org-brain entries." + :group 'org-brain + :local t + :type '(directory)) + #+end_src + + #+RESULTS: + : org-brain-path + +*** org-brain-scan-directories-recursively + #+begin_src emacs-lisp + + (defcustom org-brain-scan-directories-recursively t + "If subdirectories inside `org-brain-path' are considered part of the brain or not." + :group 'org-brain + :local t + :type '(boolean)) + + #+end_src + + #+RESULTS: + : org-brain-scan-directories-recursively + +*** org-brain-files-extension + #+begin_src emacs-lisp + + (defcustom org-brain-files-extension "org" + "The extension for entry files in `org-brain-path'." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + + #+RESULTS: + : org-brain-files-extension + +*** org-brain-ignored-resource-links + #+begin_src emacs-lisp + + (defcustom org-brain-ignored-resource-links '("fuzzy" "radio" "brain-child" "brain-parent" "brain-friend") + "`org-link-types' which shouldn't be shown as resources in `org-brain-visualize'." + :group 'org-brain + :local t + :type '(repeat string)) + + #+end_src + + #+RESULTS: + : org-brain-ignored-resource-links + +*** org-brain-backlink + #+begin_src emacs-lisp + + (defcustom org-brain-backlink nil + "If backlink resource should be added when creating a brain org-link. + This only works when completing the link via `org-insert-link'. + Example: If you create a brain-link in A to B, setting this + variable to non-nil would also create A as a resource in B. + + If this variable is a string it will be added as a prefix in the backlink. + Example: \"<--\" would add \"<--A\" in the example above." + :group 'org-brain + :local t + :type '(restricted-sexp :match-alternatives + (stringp 't 'nil))) + #+end_src + + #+RESULTS: + : org-brain-backlink + +*** org-brain-backlink-heading + #+begin_src emacs-lisp + + (defcustom org-brain-backlink-heading t + "If the org heading should be used when creating a backlink. + + Example: Creating a brain-link in A to B and A is an org file with the headings: + ,* Parent header + ,** Child + [brain:linkToB] + + Setting this variable to t will create the following backlink in B: + [[file:A.org::*Child][Parent header > Child]]." + :group 'org-brain + :local t + :type '(boolean)) + + (make-obsolete-variable 'org-brain-suggest-stored-link-as-resource + "org-brain-suggest-stored-link-as-resource isn't needed because of `org-insert-link-global'." + "0.6") + + #+end_src + + #+RESULTS: + : org-brain-suggest-stored-link-as-resource + +*** org-brain-data-file + #+begin_src emacs-lisp + + (defcustom org-brain-data-file (file-truename (expand-file-name ".org-brain-data.el" org-brain-path)) + "Where org-brain data is saved." + :group 'org-brain + :local t + :type '(directory)) + + (load org-brain-data-file t t) + + #+end_src + + #+RESULTS: + : org-brain-data-file + +*** org-brain-visualize-default-choices + #+begin_src emacs-lisp + + (defcustom org-brain-visualize-default-choices 'all + "Which entries to choose from when using `org-brain-visualize'. + If 'all, choose from all file and headline entries. + If 'files, only choose from file entries. + If 'root, only choose from file entries in `org-brain-path' (non-recursive)." + :group 'org-brain + :local t + :type '(choice + (const :tag "All entries" all) + (const :tag "Only file entries" files) + (const :tag "Only root file entries" root))) + + #+end_src + +*** org-brain-include-file-entries + #+begin_src emacs-lisp + + (defcustom org-brain-include-file-entries t + "If set to nil `org-brain' is optimized for headline entries. + Only headlines will be considered as entries when visualizing." + :group 'org-brain + :local t + :type '(boolean)) + + (make-obsolete-variable + 'org-brain-file-from-input-function + "`org-brain-default-file-parent' can be used as a better alternative." + "0.92") + + #+end_src + +*** org-brain-default-file-parent + #+begin_src emacs-lisp + + (defcustom org-brain-default-file-parent nil + "Where to store new entries with unspecified local parent. + For instance if creating a new entry with `org-brain-visualize'. + If nil, create the new entry as a file entry relative to `org-brain-path'. + If set to a string it should be a file entry. That entry will be used as the + local parent and the new entry will be a headline." + :group 'org-brain + :local t + :type '(choice string (const nil))) + + #+end_src + +*** org-brain-show-full-entry + #+begin_src emacs-lisp + + (defcustom org-brain-show-full-entry nil + "Always show entire entry contents?" + :group 'org-brain + :local t + :type '(boolean)) + + #+end_src + +*** org-brain-show-resources + #+begin_src emacs-lisp + + (defcustom org-brain-show-resources t + "Should entry resources be shown in `org-brain-visualize'?" + :group 'org-brain + :local t + :type '(boolean)) + + #+end_src + +*** org-brain-show-text + #+begin_src emacs-lisp + + (defcustom org-brain-show-text t + "Should the entry text be shown in `org-brain-visualize'?" + :group 'org-brain + :local t + :type '(boolean)) + + #+end_src + +*** org-brain-show-history + #+begin_src emacs-lisp + + (defcustom org-brain-show-history t + "Should the navigation history be shown in `org-brain-visualize'?" + :group 'org-brain + :local t + :type '(boolean)) + + #+end_src + +*** org-brain-show-icons + #+begin_src emacs-lisp + + (defcustom org-brain-show-icons t + "Should icons from `org-agenda-category-icon-alist' be shown when visualizing?" + :group 'org-brain + :local t + :type '(boolean)) + + #+end_src + +*** org-brain-category-icon-width + #+begin_src emacs-lisp + + (defcustom org-brain-category-icon-width 2 + "The character width of icons." + :group 'org-brain + :local t + :type '(integer)) + + #+end_src + +*** org-brain-quit-after-goto + #+begin_src emacs-lisp + + (defcustom org-brain-quit-after-goto nil + "Should the *org-brain* buffer window close itself after executing a goto command?" + :group 'org-brain + :local t + :type '(boolean)) + + #+end_src + + #+RESULTS: + : org-brain-quit-after-goto + +*** org-brain-goto-evil-normal + + #+begin_src emacs-lisp + + (defcustom org-brain-goto-evil-normal t + "While in evil mode visit org-mode file node in normal rather than insert mode" + :group 'org-brain + :local t + :type '(boolean)) + + #+end_src + + #+RESULTS: + : org-brain-goto-evil-normal + +*** org-brain-headline-links-only-show-visible + #+begin_src emacs-lisp + + (defcustom org-brain-headline-links-only-show-visible t + "Only show visible parts (descriptions) of headline links. + + See the docstring for `org-brain-headline-at' for more info + on how this is implemented." + :group 'org-brain + :local t + :type '(boolean)) + + #+end_src + +*** org-brain-file-entries-use-title + #+begin_src emacs-lisp + + (defcustom org-brain-file-entries-use-title t + "If file entries should show their title, when choosing entries from a list. + This can potentially be slow. If set to nil, the relative + filenames will be shown instead, which is faster." + :group 'org-brain + :local t + :type '(boolean)) + + #+end_src + +*** org-brain-scan-for-header-entries + #+begin_src emacs-lisp + + (defcustom org-brain-scan-for-header-entries t + "If org-brain should scan for header entries inside files. + Useful if you don't tend to use header entries in your workflow, + since scanning can be slow in long file entries. + This only affects selection prompts and not functions like `org-brain-headline-to-file'." + :group 'org-brain + :local t + :type '(boolean)) + + #+end_src + +*** org-brain-headline-entry-name-format-string + #+begin_src emacs-lisp + + (defcustom org-brain-headline-entry-name-format-string "%s::%s" + "How headline entries are represented when choosing entries. + This `format' string is used in `org-brain-entry-name' for headline entries. + `format' gets two objects: the file and the headline." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + +*** org-brain-visualize-text-hook + #+begin_src emacs-lisp + + (defcustom org-brain-visualize-text-hook nil + "Hook runs after inserting `org-brain-text' in `org-brain-visualize'. + + Can be used to prettify the entry text, e.g. + `org-display-inline-images'." + :group 'org-brain + :local t + :type 'hook) + + #+end_src + +*** org-brain-after-visualize-hook + #+begin_src emacs-lisp + + (defcustom org-brain-after-visualize-hook nil + "Hook run after `org-brain-visualize', but before `org-brain-text'. + Can be used to prettify the buffer output, e.g. `ascii-art-to-unicode'." + :group 'org-brain + :local t + :type 'hook) + + #+end_src + +*** org-brain-new-entry-hook + #+begin_src emacs-lisp + + (defcustom org-brain-new-entry-hook nil + "Hook run after a new headline entry has been created." + :group 'org-brain + :local t + :type 'hook) + + #+end_src + +*** org-brain-visualize-follow-hook + #+begin_src emacs-lisp + + (defcustom org-brain-visualize-follow-hook nil + "Hook run after viewing an entry by means of `org-brain-visualize-follow'." + :group 'org-brain + :local t + :type 'hook) + + #+end_src + +*** org-brain-after-resource-button-functions + #+begin_src emacs-lisp + + (defcustom org-brain-after-resource-button-functions nil + "Hook run during `org-brain-insert-resource-button'. + Insert a bullet, then run hook functions, then insert the actual button. + Each function must take a single argument: the org link to the resource. + Can for instance be used in combination with `all-the-icons'." + :group 'org-brain + :local t + :type 'hook) + + #+end_src + +*** org-brain-vis-title-prepend-functions + #+begin_src emacs-lisp + + (defcustom org-brain-vis-title-prepend-functions '(org-brain-entry-icon) + "Functions which `org-brain-vis-title' use before inserting the entry title. + Each function should take the entry as the only argument, and + should return a string. The strings are prepended to the entry title." + :group 'org-brain + :local t + :type 'hook + :options '(org-brain-entry-icon + org-brain-entry-todo-state + org-brain-entry-tags-string)) + #+end_src + +*** org-brain-vis-title-append-functions + #+begin_src emacs-lisp + + (defcustom org-brain-vis-title-append-functions '() + "Functions which `org-brain-vis-title' use after inserting the entry title. + Each function should take the entry as the only argument, and + should return a string. The strings are appended to the entry title." + :group 'org-brain + :local t + :type 'hook + :options '(org-brain-entry-icon + org-brain-entry-todo-state + org-brain-entry-tags-string)) + + #+end_src + +*** org-brain-vis-current-title-prepend-functions + #+begin_src emacs-lisp + + (defcustom org-brain-vis-current-title-prepend-functions '() + "Like `org-brain-vis-title-prepend-functions' for the current visualized entry. + First `org-brain-vis-title-prepend-functions' are ran, and then these." + :group 'org-brain + :local t + :type 'hook + :options '(org-brain-entry-icon + org-brain-entry-todo-state + org-brain-entry-tags-string)) + + #+end_src + +*** org-brain-vis-current-title-append-functions + #+begin_src emacs-lisp + + (defcustom org-brain-vis-current-title-append-functions '() + "Like `org-brain-vis-title-append-functions' for the current visualized entry. + First `org-brain-vis-title-append-functions' are ran, and then these." + :group 'org-brain + :local t + :type 'hook + :options '(org-brain-entry-icon + org-brain-entry-todo-state + org-brain-entry-tags-string)) + + #+end_src + +*** org-brain-exclude-text-tag + #+begin_src emacs-lisp + + (defcustom org-brain-exclude-text-tag "notext" + "`org-mode' tag stopping `org-brain-visualize' from fetching entry text. + Only applies to headline entries." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + +*** org-brain-exclude-resouces-tag + #+begin_src emacs-lisp + + (defcustom org-brain-exclude-resouces-tag "resourceless" + "`org-mode' tag stopping `org-brain-visualize' from fetching entry resources. + Only applies to headline entries." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + +*** org-brain-exclude-children-tag + #+begin_src emacs-lisp + + (defcustom org-brain-exclude-children-tag "childless" + "`org-mode' tag which exclude the headline's children from org-brain's entries." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + +*** org-brain-show-children-tag + #+begin_src emacs-lisp + + (defcustom org-brain-show-children-tag "showchildren" + "`org-mode' tag which get entire subtree from headline entry during `org-brain-text'." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + +*** org-brain-exclude-tree-tag + #+begin_src emacs-lisp + + (defcustom org-brain-exclude-tree-tag "nobrain" + "`org-mode' tag which exclude the headline and its children from org-brain's entries." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + +*** org-brain-exclude-siblings-tag + #+begin_src emacs-lisp + + (defcustom org-brain-exclude-siblings-tag "nosiblings" + "`org-mode' tag which prevents the siblings of children of this node from being displayed." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + +*** org-brain-exclude-local-parent-tag + #+begin_src emacs-lisp + + (defcustom org-brain-exclude-local-parent-tag "nolocalparent" + "`org-mode' tag which prevents this node to be displayed as a local parent." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + +*** org-brain-each-child-on-own-line-tag + #+begin_src emacs-lisp + + (defcustom org-brain-each-child-on-own-line-tag "ownline" + "`org-mode' tag which makes each child of the headline entry be listed on its own line." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + +*** org-brain-no-sort-children-tag + #+begin_src emacs-lisp + + (defcustom org-brain-no-sort-children-tag "nosort" + "`org-mode' tag which makes the children of the headline entry appear in file order rather than sorted." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + +*** org-brain-wander-interval + #+begin_src emacs-lisp + + (defcustom org-brain-wander-interval 3 + "Seconds between randomized entries, when using `org-brain-visualize-wander'." + :group 'org-brain + :local t + :type 'integer) + + #+end_src + +*** org-brain-title-max-length + #+begin_src emacs-lisp + + (defcustom org-brain-title-max-length 0 + "If a title is longer than this, it'll be capped during `org-brain-visualize'. + If 0 or a negative value, the title won't be capped." + :group 'org-brain + :local t + :type 'integer) + + #+end_src + +*** org-brain-cap-mind-map-titles + #+begin_src emacs-lisp + + (defcustom org-brain-cap-mind-map-titles nil + "Whether to cap entries longer than org-brain-title-max-length in mind map visualization mode." + :group 'org-brain + :local t + :type '(boolean)) + + #+end_src + +*** org-brain-entry-separator + #+begin_src emacs-lisp + + (defcustom org-brain-entry-separator ";" + "Can be used as a separator when adding children, parents, or friends. + Doing so allows for adding multiple entries at once." + :group 'org-brain + :local t + :type '(string)) + + (make-obsolete-variable + 'org-brain-visualize-one-child-per-line + "Setting `org-brain-child-linebreak-sexp' to 0 visualizes one child per line." + "0.7") + + #+end_src + +*** org-brain-child-linebreak-sexp + #+begin_src emacs-lisp + + (defcustom org-brain-child-linebreak-sexp 'fill-column + "Where to break lines when visualizing children? + Reasonable values include: + + '0: every child will be on its own line + 'fill-column: lines will break at `fill-column' + '(window-width): lines will break at the width of the window + 'most-positive-fixnum: All children will be on one line" + :group 'org-brain + :local t + :type '(sexp)) + + #+end_src + +*** org-brain-refile-max-level + #+begin_src emacs-lisp + + (defcustom org-brain-refile-max-level 1 + "The default max-level used by `org-brain-refile'." + :group 'org-brain + :local t + :type 'integer) + + #+end_src + +*** org-brain-child-link-name + #+begin_src emacs-lisp + + (defcustom org-brain-child-link-name "brain-child" + "The name for `org-mode' links, creating child relationships. + Must be set before `org-brain' is loaded. + Insert links using `org-insert-link'." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + +*** org-brain-parent-link-name + #+begin_src emacs-lisp + + (defcustom org-brain-parent-link-name "brain-parent" + "The name for `org-mode' links, creating parent relationships. + Must be set before `org-brain' is loaded. + Insert links using `org-insert-link'." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + +*** org-brain-friend-link-name + #+begin_src emacs-lisp + + (defcustom org-brain-friend-link-name "brain-friend" + "The name for `org-mode' links, creating friend relationships. + Must be set before `org-brain' is loaded. + Insert links using `org-insert-link'." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + +*** org-brain-children-property-name + #+begin_src emacs-lisp + + (defcustom org-brain-children-property-name "BRAIN_CHILDREN" + "The name for the org-mode property in which child relationships are stored. + Must be set before `org-brain' is loaded." + :group 'org-brain + :local t + :local t + :type '(string)) + + #+end_src + +*** org-brain-parents-property-name + #+begin_src emacs-lisp + + (defcustom org-brain-parents-property-name "BRAIN_PARENTS" + "The name for the org-mode property in which brain relationships are stored. + Must be set before `org-brain' is loaded." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + +*** org-brain-friends-property-name + #+begin_src emacs-lisp + + (defcustom org-brain-friends-property-name "BRAIN_FRIENDS" + "The name for the org-mode property in which friend relationships are stored. + Must be set before `org-brain' is loaded." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + +*** org-brain-edge-property-prefix-name + #+begin_src emacs-lisp + + (defcustom org-brain-edge-property-prefix-name "BRAIN_EDGE" + "The prefix for the org-mode property in which edge annotations are stored. + Must be set before `org-brain' is loaded." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + +*** org-brain-resources-drawer-name + #+begin_src emacs-lisp + + (defcustom org-brain-resources-drawer-name "RESOURCES" + "The org-mode drawer name in which resources of an entry are stored. + Must be set before `org-brain' is loaded." + :group 'org-brain + :local t + :type '(string)) + + #+end_src + +*** org-brain-open-same-window + #+begin_src emacs-lisp + + (defcustom org-brain-open-same-window nil + "Should `org-brain-visualize' open up in the same window it was launched in?" + :group 'org-brain + :local t + :type '(boolean)) + + #+end_src + +*** org-brain-completion-system + #+begin_src emacs-lisp + + (defcustom org-brain-completion-system 'default + "The completion system to be used by `org-brain'." + :group 'org-brain + :local t + :type '(radio + (const :tag "Ido" ido) + (const :tag "Helm" helm) + (const :tag "Ivy" ivy) + (const :tag "Default" default) + (function :tag "Custom function"))) + #+end_src + +** Faces and face helper functions + #+begin_src emacs-lisp + + ;;; Faces and face helper functions + #+end_src + +*** org-brain-title + + #+begin_src emacs-lisp + + (defface org-brain-title + '((t . (:inherit 'org-level-1))) + "Face for the currently selected entry.") + + #+end_src + +*** org-brain-wires + + #+begin_src emacs-lisp + + (defface org-brain-wires + `((t . (:inherit 'font-lock-comment-face :italic nil))) + "Face for the wires connecting entries.") + + #+end_src + +*** org-brain-button + + #+begin_src emacs-lisp + + (defface org-brain-button + '((t . (:inherit button))) + "Face for header-entry buttons in the org-brain visualize buffer. + File entries also use this, but also applies `org-brain-file-face-template'.") + + #+end_src + +*** org-brain-parent + + #+begin_src emacs-lisp + + (defface org-brain-parent + '((t . (:inherit (font-lock-builtin-face org-brain-button)))) + "Face for the entries' linked header-entry parent nodes. + File entries also use this, but also applies `org-brain-file-face-template'.") + + #+end_src + +*** org-brain-local-parent + + #+begin_src emacs-lisp + + (defface org-brain-local-parent + '((t . (:inherit org-brain-parent :weight bold))) + "Face for the entries' local header-entry parent nodes. + File entries also use this, but also applies `org-brain-file-face-template'.") + + #+end_src + +*** org-brain-child + + #+begin_src emacs-lisp + + (defface org-brain-child + '((t . (:inherit org-brain-button))) + "Face for the entries' linked header-entry child nodes. + File entries also use this, but also applies `org-brain-file-face-template'.") + + #+end_src + +*** org-brain-local-child + + #+begin_src emacs-lisp + + (defface org-brain-local-child + '((t . (:inherit org-brain-child :weight bold))) + "Face for the entries' local header-entry child nodes. + File entries also use this, but also applies `org-brain-file-face-template'.") + + #+end_src + +*** org-brain-sibling + + #+begin_src emacs-lisp + + (defface org-brain-sibling + '((t . (:inherit org-brain-child))) + "Face for the entries' header-entry sibling nodes. + File entries also use this, but also applies `org-brain-file-face-template'.") + + #+end_src + +*** org-brain-local-sibling + + #+begin_src emacs-lisp + + (defface org-brain-local-sibling + '((t . (:inherit org-brain-sibling :weight bold))) + "Face for the entries' local header-entry sibling nodes. + An entry is a local sibling of another entry if they share a local parent. + File entries also use this, but also applies `org-brain-file-face-template'.") + + #+end_src + +*** org-brain-friend + + #+begin_src emacs-lisp + + (defface org-brain-friend + '((t . (:inherit org-brain-button))) + "Face for the entries' header-entry friend nodes. + File entries also use this, but also applies `org-brain-file-face-template'.") + + #+end_src + +*** org-brain-pinned + + #+begin_src emacs-lisp + + (defface org-brain-pinned + '((t . (:inherit org-brain-button))) + + "Face for pinned header entries. + File entries also use this, but also applies `org-brain-file-face-template'.") + + #+end_src + +*** org-brain-selected-list + + #+begin_src emacs-lisp + + (defface org-brain-selected-list + '((t . (:inherit org-brain-pinned))) + "Face for header entries in the selection list. + File entries also use this, but also applies `org-brain-file-face-template'.") + + #+end_src + +*** org-brain-history-list + + #+begin_src emacs-lisp + + (defface org-brain-history-list + '((t . (:inherit org-brain-pinned))) + "Face for header entries in the history list. + File entries also use this, but also applies `org-brain-file-face-template'.") + + #+end_src + +*** org-brain-file-face-template + + #+begin_src emacs-lisp + + (defface org-brain-file-face-template + '((t . (:slant italic))) + "Attributes of this face are added to file-entry faces.") + + #+end_src + +*** org-brain-edge-annotation-face-template + + #+begin_src emacs-lisp + + (defface org-brain-edge-annotation-face-template + '((t . (:box t))) + "Attributes of this face are added to links which have an edge annotation + to the visualized entry.") + + ;; This needs to be here or defface complains that it is undefined. + #+end_src + +*** org-brain-specified-face-attrs + #+begin_src emacs-lisp + + (defun org-brain-specified-face-attrs (face &optional frame) + "Return a plist of all face attributes of FACE that are not `unspecified'. + If FRAME is not specified, `selected-frame' is used." + (cl-labels ((alist->plist (alist) + (pcase alist + ('nil nil) + (`((,h1 . ,h2) . ,tail) `(,h1 . (,h2 . ,(alist->plist tail))))))) + (alist->plist (seq-filter + (lambda (f) (not (equal (cdr f) 'unspecified))) + (face-all-attributes face (or frame (selected-frame))))))) + + #+end_src + +*** org-brain-display-face + #+begin_src emacs-lisp + + (defun org-brain-display-face (entry &optional face edge) + "Return the final display face for ENTRY. + Takes FACE as a starting face, or `org-brain-button' if FACE is not specified. + Applies the attributes in `org-brain-edge-annotation-face-template', + `org-brain-selected-face-template', and `org-brain-file-face-template' + as appropriate. + EDGE determines if `org-brain-edge-annotation-face-template' should be used." + (let ((selected-face-attrs + (when (member entry org-brain-selected) + (org-brain-specified-face-attrs 'org-brain-selected-face-template))) + (file-face-attrs + (when (org-brain-filep entry) + (org-brain-specified-face-attrs 'org-brain-file-face-template)))) + (append (list :inherit (or face 'org-brain-button)) + selected-face-attrs + file-face-attrs + (when edge + (org-brain-specified-face-attrs 'org-brain-edge-annotation-face-template))))) + + #+end_src + +*** org-brain-selected-face-template + + #+begin_src emacs-lisp + + (defface org-brain-selected-face-template + `((t . ,(org-brain-specified-face-attrs 'highlight))) + "Attributes of this face are added to the faces of selected entries.") + #+end_src + +** API + #+begin_src emacs-lisp + ;;; API + + ;; An entry is either a string or a list of three strings. + ;; If a string, then the entry is a file. + ;; If a list, then the entry is a headline: + ;; ("file entry" "headline title" "ID") + ;; There's also a special entry type: Nicknames + ;; In the case of headline nicknames the car of the list is a symbol (instead of a string) + ;; ('alias "headline title" "ID") + #+end_src + +*** org-brain--vis-entry + #+begin_src emacs-lisp + + (defvar-local org-brain--vis-entry nil + "The last entry argument to `org-brain-visualize'.") + + #+end_src + +*** org-brain--vis-entry-keywords + #+begin_src emacs-lisp + + (defvar-local org-brain--vis-entry-keywords nil + "The `org-brain-keywords' of `org-brain--vis-entry'.") + + #+end_src + +*** org-brain--vis-history + #+begin_src emacs-lisp + + (defvar-local org-brain--vis-history nil + "History previously visualized entries. Newest first.") + + #+end_src + +*** org-brain-resources-start-re + #+begin_src emacs-lisp + + (defvar-local org-brain-resources-start-re (concat "^[ \t]*:" org-brain-resources-drawer-name ":[ \t]*$") + "Regular expression matching the first line of a resources drawer.") + + #+end_src + +*** org-brain-keyword-regex + #+begin_src emacs-lisp + + (defvar-local org-brain-keyword-regex "^#\\+[a-zA-Z_]+:" + "Regular expression matching org keywords.") + + #+end_src + +*** org-brain-pins + #+begin_src emacs-lisp + + (defvar-local org-brain-pins nil "List of pinned org-brain entries.") + + #+end_src + +*** org-brain-selected + #+begin_src emacs-lisp + + (defvar-local org-brain-selected nil "List of selected org-brain entries.") + + #+end_src + +*** org-brain-headline-cache + #+begin_src emacs-lisp + + (defvar-local org-brain-headline-cache (make-hash-table :test 'equal) + "Cache for headline entries. Updates when files have been saved.") + + #+end_src + +*** org-brain-update-id-locations + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-update-id-locations () + "Scan `org-brain-files' using `org-id-update-id-locations'." + (interactive) + (org-id-update-id-locations (org-brain-files))) + + #+end_src + +*** org-brain-get-id + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-get-id () + "Get ID of headline at point, creating one if it doesn't exist. + Run `org-brain-new-entry-hook' if a new ID is created." + (interactive) + (or (org-id-get) + (progn + (run-hooks 'org-brain-new-entry-hook) + (org-id-get nil t)))) + + #+end_src + +*** org-brain-switch-brain + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-switch-brain (directory) + "Choose another DIRECTORY to be your `org-brain-path'." + (interactive "D") + (if (file-equal-p directory org-brain-path) + (message "Current brain already is %s, no switch" directory) + (setq org-brain-path directory) + (setq org-brain-data-file (file-truename (expand-file-name ".org-brain-data.el" org-brain-path))) + (unless (file-exists-p org-brain-data-file) + (org-brain-save-data)) + (setq org-brain-pins nil) + (setq org-brain--vis-history nil) + (load org-brain-data-file t) + (org-brain-update-id-locations) + (message "Switched org-brain to %s" directory))) + + #+end_src + +*** org-brain-maybe-switch-brain + #+begin_src emacs-lisp + + (defun org-brain-maybe-switch-brain () + "Switch brain to `default-directory' if a file named \".org-brain-data.el\" exists there." + (when (and (not (file-equal-p default-directory org-brain-path)) + (file-exists-p (file-truename (expand-file-name ".org-brain-data.el" default-directory)))) + (org-brain-switch-brain default-directory))) + + #+end_src + +*** org-brain-filep + #+begin_src emacs-lisp + + (defun org-brain-filep (entry) + "Return t if the ENTRY is a (potential) brain file." + (stringp entry)) + + #+end_src + +*** org-brain-save-data + #+begin_src emacs-lisp + + (defun org-brain-save-data () + "Save data to `org-brain-data-file'." + ;; Code adapted from Magnar Sveen's multiple-cursors + (with-temp-file org-brain-data-file + (emacs-lisp-mode) + (dolist (data '(org-brain-pins)) + (insert "(setq " (symbol-name data) "\n" + " '(") + (newline-and-indent) + (mapc #'(lambda (value) + (insert (format "%S" value)) + (newline-and-indent)) + (symbol-value data)) + (insert "))") + (newline)))) + + #+end_src + +*** org-brain-path-entry-name + #+begin_src emacs-lisp + + (defun org-brain-path-entry-name (path) + "Get PATH as an org-brain entry name." + (string-remove-suffix (concat "." org-brain-files-extension) + (file-relative-name (file-truename path) + (file-truename org-brain-path)))) + #+end_src + +*** org-brain-entry-path + #+begin_src emacs-lisp + + (defun org-brain-entry-path (entry &optional check-title) + "Get path of org-brain ENTRY. + If CHECK-TITLE is non-nil, consider that ENTRY might be a file entry title." + (let ((name (if (org-brain-filep entry) + (or (and check-title + org-brain-file-entries-use-title + (cdr + (assoc entry + (mapcar (lambda (x) + (cons (concat (file-name-directory x) + (org-brain-title x)) + x)) + (org-brain-files t))))) + entry) + (car entry)))) + (file-truename (expand-file-name (org-link-unescape (format "%s.%s" name org-brain-files-extension)) + org-brain-path)))) + + #+end_src + +*** org-brain-files + #+begin_src emacs-lisp + + (defun org-brain-files (&optional relative) + "Get all org files (recursively) in `org-brain-path'. + If RELATIVE is t, then return relative paths and remove file extension. + Ignores \"dotfiles\"." + (make-directory org-brain-path t) + (if relative + (mapcar #'org-brain-path-entry-name (org-brain-files)) + (if org-brain-scan-directories-recursively + (directory-files-recursively + org-brain-path (format "^[^.].*\\.%s$" org-brain-files-extension)) + (directory-files + org-brain-path t (format "^[^.].*\\.%s$" org-brain-files-extension))))) + + #+end_src + +*** org-brain-link-re + + #+begin_src emacs-lisp + + (defvar-local org-brain-link-re + "\\[\\[\\(\\(?:[^][\\]\\|\\\\\\(?:\\\\\\\\\\)*[][]\\|\\\\+[^][]\\)+\\)]\\(?:\\[\\(\\(?:.\\|\\)+?\\)]\\)?]" + "Regex matching an `org-mode' link. + The first match is the URI, the second is the (optional) desciption. + + This variable should be the same as `org-link-bracket-re'. + However the implementation changed in `org-mode' 9.3 and + the old `org-bracket-link-regexp' had different match groups. + The purpose of `org-brain-link-re' is protection against future changes.") + + #+end_src + +*** org-brain-replace-links-with-visible-parts + #+begin_src emacs-lisp + + (defun org-brain-replace-links-with-visible-parts (raw-str) + "Get RAW-STR with its links replaced by their descriptions." + (let ((ret-str "") + (start 0) + match-start) + (while (setq match-start (string-match org-brain-link-re raw-str start)) + (setq ret-str + (concat ret-str + ;; Include everything not part of the string. + (substring-no-properties raw-str start match-start) + ;; Include either the link description, or the link + ;; destination. + (or (match-string-no-properties 2 raw-str) + (match-string-no-properties 1 raw-str)))) + (setq start (match-end 0))) + (concat ret-str (substring-no-properties raw-str start nil)))) + #+end_src + +*** org-brain-headline-at + #+begin_src emacs-lisp + + (defun org-brain-headline-at (&optional pom) + "Return the full headline of the entry at POM. + + If `org-brain-headline-links-only-show-visible' is nil, the links + will be returned raw (all of the bracket syntax visible.) + + If `org-brain-headline-links-only-show-visible' is non-nil, + returns only the visible parts of links in the heading. (For any + links that have descriptions, only the descriptions will be + returned.) + + This is done via regex, and does not depend on org-mode's + visibility rendering/formatting in-buffer." + (let ((pom (or pom (point)))) + (if org-brain-headline-links-only-show-visible + (org-brain-replace-links-with-visible-parts (org-entry-get pom "ITEM")) + (org-entry-get pom "ITEM")))) + + #+end_src + +*** org-brain--headline-entry-at-point + #+begin_src emacs-lisp + + (defun org-brain--headline-entry-at-point (&optional create-id) + "Get headline entry at point. + If CREATE-ID is non-nil, call `org-brain-get-id' first." + (if create-id (org-brain-get-id)) + (when-let ((id (org-entry-get (point) "ID"))) + (list (org-brain-path-entry-name buffer-file-name) + (org-brain-headline-at (point)) id))) + + #+end_src + +*** org-brain-entry-at-point-excludedp + #+begin_src emacs-lisp + + (defun org-brain-entry-at-point-excludedp () + "Return t if the entry at point is tagged as being excluded from org-brain." + (let ((tags (org-get-tags))) + (or (member org-brain-exclude-tree-tag tags) + (and (member org-brain-exclude-children-tag tags) + (not (member org-brain-exclude-children-tag + (org-get-tags nil t))))))) + + #+end_src + +*** org-brain-id-exclude-taggedp + #+begin_src emacs-lisp + + (defun org-brain-id-exclude-taggedp (id) + "Return t if ID is tagged as being excluded from org-brain." + (org-with-point-at (org-id-find id t) + (org-brain-entry-at-point-excludedp))) + + #+end_src + +*** org-brain--name-and-id-at-point + #+begin_src emacs-lisp + + (defun org-brain--name-and-id-at-point () + "Get name and id of headline entry at point. + Respect excluded entries." + (unless (org-brain-entry-at-point-excludedp) + (when-let ((id (org-entry-get (point) "ID"))) + (list (org-brain-headline-at (point)) id)))) + + #+end_src + +*** org-brain--nicknames-at-point + #+begin_src emacs-lisp + + (defun org-brain--nicknames-at-point () + "Get nicknames of the headline entry at point." + (when-let ((id (org-entry-get (point) "ID"))) + (mapcar (lambda (nickname) + (list 'nickname nickname id)) + (org-entry-get-multivalued-property (point) "NICKNAMES")))) + + #+end_src + +*** org-brain-headline-entries-in-file + #+begin_src emacs-lisp + + (defun org-brain-headline-entries-in-file (file &optional no-temp-buffer) + "Get a list of all headline (and nicknames) entries in FILE. + If the entries are cached in `org-brain-headline-cache', get them from there. + Else the FILE is inserted in a temp buffer and get scanned for entries. + If NO-TEMP-BUFFER is non-nil, run the scanning in the current buffer instead." + (if no-temp-buffer + (let ((cached (gethash file org-brain-headline-cache nil))) + (if (or (not cached) + (not (equal (car cached) + (file-attribute-modification-time + (file-attributes file))))) + (let ((file-entry (org-brain-path-entry-name file))) + (insert-file-contents file nil nil nil 'replace) + (cdr (puthash file (cons (file-attribute-modification-time + (file-attributes file)) + (apply #'append + (mapcar (lambda (entry) (cons file-entry entry)) + (remove nil (org-map-entries + #'org-brain--name-and-id-at-point))) + (remove nil (org-map-entries #'org-brain--nicknames-at-point)))) + org-brain-headline-cache))) + (cdr cached))) + (with-temp-buffer + (delay-mode-hooks + (org-mode) + (org-brain-headline-entries-in-file file t))))) + + #+end_src + +*** org-brain-headline-entries + #+begin_src emacs-lisp + + (defun org-brain-headline-entries (&optional include-nicknames) + "Get all org-brain headline entries. + INCLUDE-NICKNAMES also return duplicates for headlines with NICKNAMES property." + (with-temp-buffer + (delay-mode-hooks + (org-mode) + (apply #'append + (mapcar + (lambda (file) + (seq-filter + (if include-nicknames + #'identity + (lambda (x) (stringp (car x)))) + (org-brain-headline-entries-in-file file t))) + (org-brain-files)))))) + + #+end_src + +*** org-brain-entry-from-id + #+begin_src emacs-lisp + + (defun org-brain-entry-from-id (id) + "Get entry from ID." + (unless org-id-locations (org-id-locations-load)) + (when-let ((path (gethash id org-id-locations))) + (list (org-brain-path-entry-name path) + (org-brain-headline-at (org-id-find id t)) + id))) + + #+end_src + +*** org-brain-entry-identifier + #+begin_src emacs-lisp + + (defun org-brain-entry-identifier (entry) + "Get identifier of ENTRY. + The identifier is an id if ENTRY is a headline. + If ENTRY is file, then the identifier is the relative file name." + (if (org-brain-filep entry) + (org-entry-protect-space entry) + (nth 2 entry))) + + #+end_src + +*** org-brain-entry-at-pt + #+begin_src emacs-lisp + + (defun org-brain-entry-at-pt (&optional create-id) + "Get current org-brain entry. + CREATE-ID asks to create an ID öif there isn't one already." + (cond ((eq major-mode 'org-mode) + (unless (string-prefix-p (file-truename org-brain-path) + (file-truename (buffer-file-name))) + (error "Not in a brain file")) + (if org-brain-scan-for-header-entries + (if (ignore-errors (org-get-heading)) + (or (org-brain--headline-entry-at-point) + (when create-id + (let ((closest-parent + (save-excursion + (let ((e)) + (while (and (not e) (org-up-heading-safe)) + (setq e (org-brain--headline-entry-at-point))) + (or e + (when org-brain-include-file-entries + (org-brain-path-entry-name (buffer-file-name)))))))) + (if (y-or-n-p + (format "'%s' has no ID, create one%s? " + (org-brain-headline-at) + (if closest-parent + (format " [else use local parent '%s']" + (org-brain-title closest-parent)) + ""))) + (org-brain--headline-entry-at-point t) + (or (org-brain-entry-at-pt) (error "No entry at pt")))))) + (if org-brain-include-file-entries + (org-brain-path-entry-name (buffer-file-name)) + (error "Not under an org headline, and org-brain-include-file-entries is nil"))) + (org-brain-path-entry-name (buffer-file-name)))) + ((eq major-mode 'org-brain-visualize-mode) + org-brain--vis-entry) + (t + (error "Not in org-mode or org-brain-visualize")))) + + #+end_src + +*** org-brain-entry-name + #+begin_src emacs-lisp + + (defun org-brain-entry-name (entry) + "Get name string of ENTRY." + (if (org-brain-filep entry) + (if org-brain-file-entries-use-title + (concat (file-name-directory entry) (org-brain-title entry)) + entry) + (format org-brain-headline-entry-name-format-string + (org-brain-entry-name (car entry)) (cadr entry)))) + + #+end_src + +*** org-brain-entry-data + #+begin_src emacs-lisp + + (defun org-brain-entry-data (entry) + "Run `org-element-parse-buffer' on ENTRY text." + (with-temp-buffer + (insert (org-brain-text entry t)) + (org-element-parse-buffer))) + + #+end_src + +*** org-brain--file-targets + #+begin_src emacs-lisp + + (defun org-brain--file-targets (file) + "Return alist of (name . entry-id) for all entries in FILE. + The list also includes nicknames from the NICKNAMES keyword/properties. + Should only be used in a temp-buffer." + (let* ((file-relative (org-brain-path-entry-name file)) + (file-entry-name (org-brain-entry-name file-relative))) + (remove + nil + (append + (when org-brain-include-file-entries + (apply + #'append + (list (cons file-entry-name file-relative)) + (mapcar (lambda (x) + (list (cons (org-entry-restore-space x) file-relative))) + (when-let ((nicknames (assoc "NICKNAMES" (org-brain-keywords file-relative)))) + (split-string (cdr nicknames) " " t))))) + (mapcar + (lambda (x) + (cons (format org-brain-headline-entry-name-format-string + file-entry-name + (nth 1 x)) + (nth 2 x))) + (org-brain-headline-entries-in-file file t)))))) + + #+end_src + +*** org-brain--all-targets + #+begin_src emacs-lisp + + (defun org-brain--all-targets () + "Get an alist with (name . entry-id) of all targets in org-brain. + `org-brain-include-file-entries' and `org-brain-scan-for-header-entries' + affect the fetched targets." + (if org-brain-scan-for-header-entries + (with-temp-buffer + (delay-mode-hooks + (org-mode) + (mapcan #'org-brain--file-targets + (org-brain-files)))) + (mapcar (lambda (x) (cons (org-brain-entry-name x) x)) + (org-brain-files t)))) + + #+end_src + +*** org-brain-completing-read + #+begin_src emacs-lisp + + (defun org-brain-completing-read (prompt choices &optional predicate require-match initial-input hist def inherit-input) + "A version of `completing-read' which is tailored to `org-brain-completion-system'." + (let ((args (list prompt choices predicate require-match initial-input hist def inherit-input))) + (or (pcase org-brain-completion-system + ('default (apply #'completing-read args)) + ('ido (apply #'ido-completing-read args)) + ('ivy (apply #'ivy-completing-read args)) + ('helm (apply #'helm-completing-read-default-1 + (append args '("org-brain" "*org-brain-helm*"))))) + (funcall org-brain-completion-system prompt choices)))) + + #+end_src + +*** org-brain-get-entry-from-title + #+begin_src emacs-lisp + + (defun org-brain-get-entry-from-title (title &optional targets) + "Search for TITLE in TARGETS and return an entry. Create it if non-existing. + TARGETS is an alist of (title . entry-id). + If TARGETS is nil then use `org-brain--all-targets'." + (unless org-id-locations (org-id-locations-load)) + (let* ((targets (or targets (org-brain--all-targets))) + (id (or (cdr (assoc title targets)) title))) + (or + ;; Headline entry exists, return it + (org-brain-entry-from-id id) + ;; File entry + (progn + (setq id (split-string id "::" t)) + (let* ((entry-path (org-brain-entry-path (car id) t)) + (entry-file (org-brain-path-entry-name entry-path))) + (unless (file-exists-p entry-path) + (if (and org-brain-default-file-parent (equal (length id) 1)) + (setq entry-file org-brain-default-file-parent + id `(,org-brain-default-file-parent ,(car id))) + (make-directory (file-name-directory entry-path) t) + (write-region "" nil entry-path))) + (if (or (not org-brain-include-file-entries) + (equal (length id) 2) + (not (equal (car id) entry-file))) + ;; Create new headline entry in file + (org-with-point-at (org-brain-entry-marker entry-file) + (if (and (not org-brain-include-file-entries) + (or + ;; Search heading without tags + (save-excursion + (re-search-forward (concat "\n\\* +" (regexp-quote (car id)) "[ \t]*$") nil t)) + ;; Search heading with tags + (save-excursion + (re-search-forward (concat "\n\\* +" (regexp-quote (car id)) "[ \t]+:.*:$") nil t)))) + (org-brain-entry-at-pt) + (goto-char (point-max)) + (insert (concat "\n* " (or (cadr id) (car id)))) + (let ((new-id (org-brain-get-id))) + (save-buffer) + (list entry-file (or (cadr id) (car id)) new-id)))) + entry-file)))))) + + #+end_src + +*** org-brain-add-entry + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-add-entry (title) + "Add a new entry named TITLE." + (interactive "sNew entry: ") + (message "Added new entry: '%s'" + (org-brain-entry-name (org-brain-get-entry-from-title title)))) + + #+end_src + +*** org-brain-choose-entries + #+begin_src emacs-lisp + + (defun org-brain-choose-entries (prompt entries &optional predicate require-match initial-input hist def inherit-input-method) + "PROMPT for one or more ENTRIES, separated by `org-brain-entry-separator'. + ENTRIES can be a list, or 'all which lists all headline and file entries. + Return the prompted entries in a list. + Very similar to `org-brain-choose-entry', but can return several entries. + + For PREDICATE, REQUIRE-MATCH, INITIAL-INPUT, HIST, DEF and + INHERIT-INPUT-METHOD see `completing-read'." + (let* ((targets (if (eq entries 'all) + (org-brain--all-targets) + (mapcar (lambda (x) + (cons (org-brain-entry-name x) + (if (org-brain-filep x) + x + (nth 2 x)))) + entries))) + (choices (org-brain-completing-read prompt targets + predicate require-match initial-input hist def inherit-input-method))) + (mapcar (lambda (title) (org-brain-get-entry-from-title title targets)) + (if org-brain-entry-separator + (split-string choices org-brain-entry-separator) + (list choices))))) + + #+end_src + +*** org-brain-choose-entry + #+begin_src emacs-lisp + + (defun org-brain-choose-entry (prompt entries &optional predicate require-match initial-input hist def inherit-input-method) + "PROMPT for an entry from ENTRIES and return it. + ENTRIES can be 'all, which lists all headline and file entries. + For PREDICATE, REQUIRE-MATCH, INITIAL-INPUT, HIST, DEF and INHERIT-INPUT-METHOD see `completing-read'." + (let ((org-brain-entry-separator nil)) + (car (org-brain-choose-entries prompt entries predicate require-match initial-input hist def inherit-input-method)))) + + #+end_src + +*** org-brain-first-headline-position + #+begin_src emacs-lisp + + (defun org-brain-first-headline-position () + "Get position of first headline in buffer. `point-max' if no headline exists." + (save-excursion + (goto-char (point-min)) + (or (looking-at-p org-heading-regexp) + (outline-next-heading) + (goto-char (point-max))) + (point))) + + #+end_src + +*** org-brain-keywords + #+begin_src emacs-lisp + + (defun org-brain-keywords (entry) + "Get alist of `org-mode' keywords and their values in file ENTRY." + (if (org-brain-filep entry) + (with-temp-buffer + (insert + (with-temp-buffer + (ignore-errors (insert-file-contents (org-brain-entry-path entry))) + (buffer-substring-no-properties (point-min) (org-brain-first-headline-position)))) + (org-element-map (org-element-parse-buffer) 'keyword + (lambda (kw) + (cons (org-element-property :key kw) + (org-element-property :value kw))))) + (error "Only file entries have keywords"))) + + #+end_src + +*** org-brain-get-tags + #+begin_src emacs-lisp + + (defun org-brain-get-tags (entry &optional inherit) + "Return the tags at ENTRY. Only use local tags unless INHERIT is non-nil. + Works for both file and headline entries." + (if (org-brain-filep entry) + (ignore-errors + (split-string + (cdr (assoc "FILETAGS" (org-brain-keywords entry))) ":" t)) + (org-with-point-at + (org-brain-entry-marker entry) + (org-get-tags nil (not inherit))))) + + #+end_src + +*** org-brain-entry-tags-string + #+begin_src emacs-lisp + + (defun org-brain-entry-tags-string (entry) + "Get a string of ENTRY's local tags." + (let ((tags (string-join (org-brain-get-tags entry) ":"))) + (if (string-empty-p tags) + "" + (concat ":" tags ":")))) + + #+end_src + +*** org-brain-entry-todo-state + #+begin_src emacs-lisp + + (defun org-brain-entry-todo-state (entry) + "Get the todo-state of ENTRY. + Only works on headline entries." + (if (org-brain-filep entry) + "" + (org-with-point-at (org-brain-entry-marker entry) + (or (org-get-todo-state) "")))) + + #+end_src + +*** org-brain--missing-id-error + #+begin_src emacs-lisp + + (defun org-brain--missing-id-error (entry) + "Error message to be shown if id of ENTRY isn't found by `org-id-find'." + (error "Couldn't find entry %s, try running org-brain-update-id-locations. " + (org-brain-entry-name entry))) + + #+end_src + +*** org-brain-entry-marker + #+begin_src emacs-lisp + + (defun org-brain-entry-marker (entry) + "Get marker to ENTRY." + (if (org-brain-filep entry) + (let ((path (org-brain-entry-path entry))) + (if (file-exists-p path) + (set-marker (make-marker) 0 + (or (org-find-base-buffer-visiting path) + (find-file-noselect path))) + ;; If file doesn't exists, it is probably an id + (or (org-id-find entry t) + (org-brain--missing-id-error entry)))) + (or (org-id-find (nth 2 entry) t) + (org-brain--missing-id-error entry)))) + + #+end_src + +*** org-brain-title + #+begin_src emacs-lisp + + (defun org-brain-title (entry &optional capped) + "Get title of ENTRY. If CAPPED is t, max length is `org-brain-title-max-length'." + (let ((title + (if (org-brain-filep entry) + (or (cdr (assoc "TITLE" (org-brain-keywords entry))) + (car (last (split-string entry "/" t)))) + (nth 1 entry)))) + (if (and capped (> org-brain-title-max-length 0) (> (length title) org-brain-title-max-length)) + (concat (substring title 0 (1- org-brain-title-max-length)) "…") + title))) + + #+end_src + +*** org-brain-text-positions + #+begin_src emacs-lisp + + (defun org-brain-text-positions (entry &optional all-data) + "Get the beginning and end position of the ENTRY text. + Only get the body text, unless ALL-DATA is t." + (if (org-brain-filep entry) + ;; File entry + (with-temp-buffer + (ignore-errors (insert-file-contents (org-brain-entry-path entry))) + (goto-char (org-brain-first-headline-position)) + (list + (if all-data + (point-min) + (or (save-excursion + (when (re-search-backward org-brain-keyword-regex nil t) + (end-of-line) + (point))) + (point-min))) + (if (let ((filetags (org-brain-get-tags entry))) + (or org-brain-show-full-entry + (member org-brain-show-children-tag filetags) + (member org-brain-exclude-children-tag filetags))) + (point-max) + (point)))) + ;; Headline entry + (org-with-point-at (org-brain-entry-marker entry) + (let ((tags (org-get-tags nil t))) + (unless (and (member org-brain-exclude-text-tag tags) + (not all-data)) + (unless all-data + (goto-char (cdr (org-get-property-block))) + (end-of-line)) + (let (end) + (save-excursion + (or (and (not org-brain-show-full-entry) + (not (member org-brain-exclude-children-tag tags)) + (not (member org-brain-show-children-tag tags)) + (org-goto-first-child)) + (org-end-of-subtree t)) + (setq end (point))) + (list (point) end))))))) + + #+end_src + +*** org-brain-text + #+begin_src emacs-lisp + + (defun org-brain-text (entry &optional all-data) + "Get the text of ENTRY as string. + Only get the body text, unless ALL-DATA is t." + (when-let ((entry-text + (if (org-brain-filep entry) + ;; File entry + (with-temp-buffer + (ignore-errors (insert-file-contents (org-brain-entry-path entry))) + (apply #'buffer-substring-no-properties + (org-brain-text-positions entry all-data))) + ;; Headline entry + (org-with-point-at (org-brain-entry-marker entry) + (apply #'buffer-substring-no-properties + (org-brain-text-positions entry all-data)))))) + (if all-data + (org-remove-indentation entry-text) + (with-temp-buffer + (insert (org-remove-indentation entry-text)) + (goto-char (org-brain-first-headline-position)) + (if (re-search-backward org-brain-resources-start-re nil t) + (progn + (end-of-line) + (re-search-forward org-drawer-regexp nil t)) + (goto-char (point-min))) + (buffer-substring (point) (point-max)))))) + + #+end_src + +*** org-brain-parents + #+begin_src emacs-lisp + + (defun org-brain-parents (entry) + "Get parents of ENTRY. + Often you want the siblings too, then use `org-brain-siblings' instead." + (delete-dups + (append (org-brain--linked-property-entries entry org-brain-parents-property-name) + (org-brain-local-parent entry)))) + + #+end_src + +*** org-brain-local-parent + #+begin_src emacs-lisp + + (defun org-brain-local-parent (entry) + "Get file local parent of ENTRY, as a list." + (if-let ((parent + (unless (org-brain-filep entry) + (org-with-point-at (org-brain-entry-marker entry) + (if (and (org-up-heading-safe) + (org-entry-get nil "ID")) + (org-brain-entry-from-id (org-entry-get nil "ID")) + (when (and org-brain-include-file-entries + (not (member org-brain-exclude-local-parent-tag + (org-brain-get-tags (car entry))))) + (car entry))))))) + (list parent))) + + #+end_src + +*** org-brain-children + #+begin_src emacs-lisp + + (defun org-brain-children (entry) + "Get children of ENTRY." + (delete-dups + (append (org-brain--linked-property-entries entry org-brain-children-property-name) + (org-brain-local-children entry)))) + + #+end_src + +*** org-brain-local-children + #+begin_src emacs-lisp + + (defun org-brain-local-children (entry) + "Get file local children of ENTRY." + (remove + entry + (if (org-brain-filep entry) + ;; File entry + (with-temp-buffer + (ignore-errors (insert-file-contents (org-brain-entry-path entry))) + (org-element-map (org-element-parse-buffer 'headline) 'headline + (lambda (headline) + (when-let ((id (org-element-property :ID headline))) + (unless (org-brain-id-exclude-taggedp id) + (org-brain-entry-from-id id)))) + nil nil 'headline)) + ;; Headline entry + (org-with-point-at (org-brain-entry-marker entry) + (let (children) + (deactivate-mark) + (org-mark-subtree) + (org-goto-first-child) + (setq children + (org-map-entries + (lambda () (org-brain-entry-from-id (org-entry-get nil "ID"))) + t 'region-start-level + (lambda () + (let ((id (org-entry-get nil "ID"))) + (when (or (not id) + (org-brain-id-exclude-taggedp id)) + (save-excursion + (outline-next-heading) + (point))))))) + (deactivate-mark) + children))))) + + #+end_src + +*** org-brain-descendants + #+begin_src emacs-lisp + + (defun org-brain-descendants (entry) + "Get all entries which descend from ENTRY. + In other words get all the children, grand children, grand-grand children, etc. + The ENTRY itself is also included in the returned list." + (let ((checked nil)) + (cl-labels ((collect-descendants + (e) + (unless (member e checked) + (push e checked) + (mapc #'collect-descendants (org-brain-children e))))) + (collect-descendants entry) + checked))) + + #+end_src + +*** org-brain-local-descendants + #+begin_src emacs-lisp + + (defun org-brain-local-descendants (entry) + "Return the local descendants of ENTRY (excluding ENTRY itself). + Similar to `org-brain-descendants' but only for local children." + (remove + entry + (if (org-brain-filep entry) + ;; File entry + (with-temp-buffer + (ignore-errors (insert-file-contents (org-brain-entry-path entry))) + (org-element-map (org-element-parse-buffer 'headline) 'headline + (lambda (headline) + (when-let ((id (org-element-property :ID headline))) + (unless (org-brain-id-exclude-taggedp id) + (org-brain-entry-from-id id)))))) + ;; Headline entry + (org-with-point-at (org-brain-entry-marker entry) + (org-map-entries + (lambda () (org-brain-entry-from-id (org-entry-get nil "ID"))) + t 'tree + (lambda () + (let ((id (org-entry-get nil "ID"))) + (when (or (not id) + (org-brain-id-exclude-taggedp id)) + (or (outline-next-heading) + (point)))))))))) + + #+end_src + +*** org-brain-siblings + #+begin_src emacs-lisp + + (defun org-brain-siblings (entry) + "Get siblings of ENTRY. + Return an alist where key = parent, value = siblings from that parent." + (delete-dups + (mapcar + (lambda (parent) + (cons parent (remove entry (org-brain-children parent)))) + (org-brain-parents entry)))) + + #+end_src + +*** org-brain-friends + #+begin_src emacs-lisp + + (defun org-brain-friends (entry) + "Get friends of ENTRY." + (delete-dups (org-brain--linked-property-entries entry org-brain-friends-property-name))) + + #+end_src + +*** org-brain-resources + #+begin_src emacs-lisp + + (defun org-brain-resources (entry) + "Get alist of links in ENTRY, excluding `org-brain-ignored-resource-links'. + A link can be either an org link or an org attachment. + The car is the raw-link and the cdr is the description." + (let ((links + (delete-dups + (with-temp-buffer + (insert (org-brain-text entry t)) + (org-element-map (org-brain-entry-data entry) 'link + (lambda (link) + (unless (member (org-element-property :type link) + org-brain-ignored-resource-links) + (cons (org-element-property :raw-link link) + (when-let ((beg (org-element-property :contents-begin link)) + (end (org-element-property :contents-end link))) + (replace-regexp-in-string + "[ \t\n\r]+" " " (buffer-substring beg end)))))) + nil nil t))))) + (if (org-brain-filep entry) + links + ;; Headline entry + (org-with-point-at (org-brain-entry-marker entry) + (unless (member org-brain-exclude-resouces-tag (org-get-tags nil t)) + (append links + ;; Attachments + (when-let ((attach-dir (org-attach-dir))) + (mapcar (lambda (attachment) + (cons (format "file:%s" + (org-link-escape + (file-truename (expand-file-name attachment attach-dir)))) + attachment)) + (org-attach-file-list attach-dir))))))))) + + #+end_src + +*** org-brain--choose-resource + #+begin_src emacs-lisp + + (defun org-brain--choose-resource (entries) + "Use `completing-read' to get link to a resource from ENTRIES." + (let ((resources (mapcan + (lambda (entry) + (mapcar (lambda (x) + (cons (or (cdr x) (car x)) (car x))) + (org-brain-resources entry))) + entries))) + (if (equal (length resources) 1) + (cdar resources) + (cdr (assoc (org-brain-completing-read "Resource: " resources nil t) resources))))) + + #+end_src + +*** org-brain-open-resource + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-open-resource (entry) + "Choose and open a resource from ENTRY. + If run with `\\[universal-argument]' then also choose from descendants of ENTRY. + Uses `org-brain-entry-at-pt' for ENTRY, or asks for it if none at point." + (interactive (list (or (ignore-errors (org-brain-entry-at-pt t)) + (org-brain-choose-entry "Resource from: " 'all)))) + (org-open-link-from-string + (format "[[%s]]" (org-brain--choose-resource + (if current-prefix-arg + (org-brain-descendants entry) + (list entry)))))) + + #+end_src + +*** org-brain--linked-property-entries + #+begin_src emacs-lisp + + (defun org-brain--linked-property-entries (entry property) + "Get list of entries linked to in ENTRY by PROPERTY. + PROPERTY could for instance be `org-brain-children-property-name'." + (let ((propertylist + (if (org-brain-filep entry) + ;; File entry + (mapcar + (lambda (x) (or (org-brain-entry-from-id x) x)) + (mapcar #'org-entry-restore-space + (when-let ((kw-values (cdr (assoc property + (org-brain-keywords entry))))) + (org-split-string kw-values "[ \t]+")))) + ;; Headline entry + (mapcar + (lambda (x) (or (org-brain-entry-from-id x) x)) + (org-entry-get-multivalued-property (org-brain-entry-marker entry) property))))) + (if (equal propertylist '("")) nil propertylist))) + + #+end_src + +*** org-brain-add-relationship + #+begin_src emacs-lisp + + (defun org-brain-add-relationship (parent child) + "Add external relationship between PARENT and CHILD." + (when (equal parent child) + (error "An entry can't be a parent/child to itself")) + (unless (member child (org-brain-children parent)) + (org-save-all-org-buffers) + (if (org-brain-filep parent) + ;; Parent = File + (org-with-point-at (org-brain-entry-marker parent) + (goto-char (point-min)) + (if (re-search-forward (concat "^#\\+" org-brain-children-property-name ":.*$") nil t) + (insert (concat " " (org-brain-entry-identifier child))) + (insert (concat "#+" org-brain-children-property-name ": " + (org-brain-entry-identifier child) + "\n\n")))) + ;; Parent = Headline + (org-entry-add-to-multivalued-property (org-brain-entry-marker parent) + org-brain-children-property-name + (org-brain-entry-identifier child))) + (if (org-brain-filep child) + ;; Child = File + (org-with-point-at (org-brain-entry-marker child) + (goto-char (point-min)) + (if (re-search-forward (concat "^#\\+" org-brain-parents-property-name ":.*$") nil t) + (insert (concat " " (org-brain-entry-identifier parent))) + (insert (concat "#+" org-brain-parents-property-name ": " + (org-brain-entry-identifier parent) + "\n\n")))) + ;; Child = Headline + (org-entry-add-to-multivalued-property (org-brain-entry-marker child) + org-brain-parents-property-name + (org-brain-entry-identifier parent))) + (org-save-all-org-buffers))) + + #+end_src + +*** org-brain-delete-current-line + #+begin_src emacs-lisp + + (defun org-brain-delete-current-line (&optional match-regex) + "Delete whole line at `point', and the newline. + Optionally only delete if matching MATCH-REGEX." + (when (or (not match-regex) + (string-match match-regex (buffer-substring + (line-beginning-position) + (line-end-position)))) + (delete-region (line-beginning-position) + (progn (forward-line 1) (point))))) + + #+end_src + +*** org-brain-remove-relationship + #+begin_src emacs-lisp + + (defun org-brain-remove-relationship (parent child) + "Remove external relationship between PARENT and CHILD." + (unless (member child (org-brain-children parent)) + (error "Relationship doesn't exist")) + (org-save-all-org-buffers) + (if (org-brain-filep parent) + ;; Parent = File + (org-with-point-at (org-brain-entry-marker parent) + (goto-char (point-min)) + (re-search-forward (concat "^#\\+" org-brain-children-property-name ":.*$")) + (beginning-of-line) + (re-search-forward (concat " " (regexp-quote (org-brain-entry-identifier child)))) + (replace-match "") + (org-brain-delete-current-line (concat "^#\\+" org-brain-children-property-name ":[[:space:]]*$")) + (org-brain-delete-current-line "^[[:space:]]*$") + (save-buffer)) + ;; Parent = Headline + (org-entry-remove-from-multivalued-property (org-brain-entry-marker parent) + org-brain-children-property-name + (org-brain-entry-identifier child))) + (if (org-brain-filep child) + ;; Child = File + (org-with-point-at (org-brain-entry-marker child) + (goto-char (point-min)) + (re-search-forward (concat "^#\\+" org-brain-parents-property-name ":.*$")) + (beginning-of-line) + (re-search-forward (concat " " (regexp-quote (org-brain-entry-identifier parent)))) + (replace-match "") + (org-brain-delete-current-line (concat "^#\\+" org-brain-parents-property-name ":[[:space:]]*$")) + (org-brain-delete-current-line "^[[:space:]]*$") + (save-buffer)) + ;; Child = Headline + (org-entry-remove-from-multivalued-property (org-brain-entry-marker child) + org-brain-parents-property-name + (org-brain-entry-identifier parent))) + (org-save-all-org-buffers)) + + #+end_src + +** Buffer commands + #+begin_src emacs-lisp + + ;;; Buffer commands + + #+end_src + +*** org-brain-add-child + #+begin_src emacs-lisp + ;;;###autoload + + (defun org-brain-add-child (entry children &optional verbose) + "Add external CHILDREN (a list of entries) to ENTRY. + If called interactively use `org-brain-entry-at-pt' and let user choose entry. + Using `\\[universal-argument]' will use `org-brain-button-at-point' as ENTRY. + If chosen CHILD entry doesn't exist, create it as a new file. + Several children can be added, by using `org-brain-entry-separator'. + If VERBOSE is non-nil then display a message." + (interactive (list (if current-prefix-arg + (car (org-brain-button-at-point)) + (org-brain-entry-at-pt t)) + (org-brain-choose-entries "Add child: " 'all) + t)) + (dolist (child-entry children) + (org-brain-add-relationship entry child-entry) + (if verbose (message "Added '%s' as a child of '%s'." + (org-brain-entry-name child-entry) + (org-brain-entry-name entry)))) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** org-brain-add-child-headline + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-add-child-headline (entry child-names &optional verbose) + "Create new internal child headline(s) to ENTRY named CHILD-NAMES. + Several children can be created, by using `org-brain-entry-separator'. + If called interactively use `org-brain-entry-at-pt' and prompt for children. + Using `\\[universal-argument]' will use `org-brain-button-at-point' as ENTRY. + If VERBOSE is non-nil then display a message." + (interactive (list (if current-prefix-arg + (car (org-brain-button-at-point)) + (org-brain-entry-at-pt t)) + (read-string "Add child headline: ") + t)) + (dolist (child-name (split-string child-names org-brain-entry-separator)) + (when (equal (length child-name) 0) + (error "Child name must be at least 1 character")) + (if (org-brain-filep entry) + ;; File entry + (org-with-point-at (org-brain-entry-marker entry) + (goto-char (org-brain-first-headline-position)) + (open-line 1) + (insert (concat "* " child-name)) + (org-brain-get-id) + (save-buffer)) + ;; Headline entry + (org-with-point-at (org-brain-entry-marker entry) + (if (org-goto-first-child) + (open-line 1) + (org-end-of-subtree t)) + (org-insert-heading nil t) + (org-do-demote) + (insert child-name) + (org-brain-get-id) + (save-buffer))) + (if verbose (message "Added '%s' as a child of '%s'." + child-name + (org-brain-entry-name entry)))) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** obsolete function alias org-brain-new-child + #+begin_src emacs-lisp + + (define-obsolete-function-alias 'org-brain-new-child 'org-brain-add-child-headline "0.5") + + #+end_src + +*** org-brain-remove-child + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-remove-child (entry child &optional verbose) + "Remove CHILD from ENTRY. + If called interactively use `org-brain-entry-at-point' and prompt for CHILD. + Using `\\[universal-argument]' will use `org-brain-button-at-point' as ENTRY. + If VERBOSE is non-nil then display a message." + (interactive (let ((e (if current-prefix-arg + (car (org-brain-button-at-point)) + (org-brain-entry-at-pt)))) + (list e (org-brain-choose-entry "Remove child: " + (org-brain-children e) + nil t) + t))) + (if (member child (org-brain-local-children entry)) + (if (and (> (length (org-brain-parents child)) 1) + (y-or-n-p + (format "%s is %s's local parent. Would you like to change the local parent of %s? " + (org-brain-title entry) (org-brain-title child) (org-brain-title child)))) + (let* ((linked-parents (org-brain--linked-property-entries child org-brain-parents-property-name)) + (new-parent (if (equal 1 (length linked-parents)) + (car-safe linked-parents) + (org-brain-choose-entry "Refile to parent: " linked-parents)))) + (org-brain-remove-relationship entry (org-brain-change-local-parent child new-parent))) + (org-brain-delete-entry child)) + (org-brain-remove-relationship entry child)) + (if verbose (message "'%s' is no longer a child of '%s'." + (org-brain-entry-name child) + (org-brain-entry-name entry))) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** org-brain-add-parent + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-add-parent (entry parents &optional verbose) + "Add external PARENTS (a list of entries) to ENTRY. + If called interactively use `org-brain-entry-at-pt' and prompt for PARENT. + Using `\\[universal-argument]' will use `org-brain-button-at-point' as ENTRY. + + If chosen parent entry doesn't exist, create it as a new file. + Several parents can be added, by using `org-brain-entry-separator'. + If VERBOSE is non-nil then display a message." + (interactive (list (if current-prefix-arg + (car (org-brain-button-at-point)) + (org-brain-entry-at-pt t)) + (org-brain-choose-entries "Add parent: " 'all) + t)) + (dolist (parent parents) + (org-brain-add-relationship parent entry) + (if verbose (message "Added '%s' as a parent of '%s'." + (org-brain-entry-name parent) + (org-brain-entry-name entry)))) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** org-brain-remove-parent + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-remove-parent (entry parent &optional verbose) + "Remove PARENT from ENTRY. + If called interactively use `org-brain-entry-at-pt' and prompt for PARENT. + Using `\\[universal-argument]' will use `org-brain-button-at-point' as ENTRY." + (interactive (let ((e (if current-prefix-arg + (car (org-brain-button-at-point)) + (org-brain-entry-at-pt)))) + (list e (org-brain-choose-entry "Remove parent: " + (org-brain-parents e) + nil t) + t))) + (if (member entry (org-brain-local-children parent)) + (if-let* ((linked-parents (org-brain--linked-property-entries entry org-brain-parents-property-name)) + (new-parent (if (equal 1 (length linked-parents)) + (car-safe linked-parents) + (org-brain-choose-entry (format "Removing %s's local parent. Refile to: " + (org-brain-title entry)) + linked-parents)))) + (org-brain-remove-relationship parent (org-brain-change-local-parent entry new-parent)) + (if (and org-brain-default-file-parent + (y-or-n-p (format "%s has no more parents, move it to %s? " + (org-brain-title entry) org-brain-default-file-parent))) + (org-brain-remove-relationship + parent (org-brain-change-local-parent entry org-brain-default-file-parent)) + (error "%s is %s's only parent, it can't be removed" + (org-brain-title parent) (org-brain-title entry)))) + (org-brain-remove-relationship parent entry)) + (if verbose (message "'%s' is no longer a parent of '%s'." + (org-brain-entry-name parent) + (org-brain-entry-name entry))) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** org-brain--internal-add-friendship + #+begin_src emacs-lisp + + (defun org-brain--internal-add-friendship (entry1 entry2 &optional oneway) + "Add friendship between ENTRY1 and ENTRY2. + If ONEWAY is t, add ENTRY2 as friend of ENTRY1, but not the other way around." + (when (equal entry1 entry2) + (error "Can't have an entry as a friend to itself")) + (unless (member entry2 (org-brain-friends entry1)) + (if (org-brain-filep entry1) + ;; Entry1 = File + (org-with-point-at (org-brain-entry-marker entry1) + (goto-char (point-min)) + (if (re-search-forward (concat "^#\\+" org-brain-friends-property-name ":.*$") nil t) + (insert (concat " " (org-brain-entry-identifier entry2))) + (insert (concat "#+" org-brain-friends-property-name ": " + (org-brain-entry-identifier entry2) + "\n\n"))) + (save-buffer)) + ;; Entry1 = Headline + (org-entry-add-to-multivalued-property (org-brain-entry-marker entry1) + org-brain-friends-property-name + (org-brain-entry-identifier entry2)))) + (unless oneway (org-brain--internal-add-friendship entry2 entry1 t)) + (org-save-all-org-buffers)) + + #+end_src + +*** org-brain-add-friendship + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-add-friendship (entry friends &optional verbose) + "Add a new FRIENDS (a list of entries) to ENTRY. + If called interactively use `org-brain-entry-at-pt' and prompt for FRIENDS. + Using `\\[universal-argument]' will use `org-brain-button-at-point' as ENTRY. + + If chosen friend entry doesn't exist, create it as a new file. + Several friends can be added, by using `org-brain-entry-separator'. + If VERBOSE is non-nil then display a message." + (interactive (list (if current-prefix-arg + (car (org-brain-button-at-point)) + (org-brain-entry-at-pt t)) + (org-brain-choose-entries "Add friend: " 'all) + t)) + (dolist (friend-entry friends) + (org-brain--internal-add-friendship entry friend-entry) + (if verbose (message "'%s' and '%s' are now friends." + (org-brain-entry-name entry) + (org-brain-entry-name friend-entry)))) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** org-brain-remove-friendship + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-remove-friendship (entry1 entry2 &optional oneway verbose) + "Remove friendship between ENTRY1 and ENTRY2. + If ONEWAY is t, then remove ENTRY2 as a friend of ENTRY1, but not vice versa. + + If run interactively, use `org-brain-entry-at-pt' as ENTRY1 and prompt for ENTRY2. + Using `\\[universal-argument]' will use `org-brain-button-at-point' as ENTRY1. + If VERBOSE is non-nil then display a message." + (interactive + (let ((entry-at-pt (if current-prefix-arg + (car (org-brain-button-at-point)) + (org-brain-entry-at-pt)))) + (list entry-at-pt + (org-brain-choose-entry "Remove friend: " (org-brain-friends entry-at-pt) nil t) + nil t))) + (when (member entry2 (org-brain-friends entry1)) + (if (org-brain-filep entry1) + ;; Entry1 = File + (org-with-point-at (org-brain-entry-marker entry1) + (goto-char (point-min)) + (re-search-forward (concat "^#\\+" org-brain-friends-property-name ":.*$")) + (beginning-of-line) + (re-search-forward (concat " " (regexp-quote (org-brain-entry-identifier entry2)))) + (replace-match "") + (org-brain-delete-current-line (concat "^#\\+" org-brain-friends-property-name ":[[:space:]]*$")) + (org-brain-delete-current-line "^[[:space:]]*$") + (save-buffer)) + ;; Entry2 = Headline + (org-entry-remove-from-multivalued-property (org-brain-entry-marker entry1) + org-brain-friends-property-name + (org-brain-entry-identifier entry2)))) + (if oneway + (org-brain--revert-if-visualizing) + (org-brain-remove-friendship entry2 entry1 t verbose)) + (org-save-all-org-buffers) + (if (and (not oneway) verbose) + (message "'%s' and '%s' are no longer friends." + (org-brain-entry-name entry1) + (org-brain-entry-name entry2)))) + + #+end_src + +*** org-brain-goto + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-goto (&optional entry goto-file-func) + "Goto buffer and position of org-brain ENTRY. + If ENTRY isn't specified, ask for the ENTRY. + Unless GOTO-FILE-FUNC is nil, use `pop-to-buffer-same-window' for opening the entry." + (interactive) + (org-brain-stop-wandering) + (unless entry (setq entry (org-brain-choose-entry "Goto entry: " 'all))) + (when (and org-brain-quit-after-goto (eq 'major-mode 'org-brain-visualize-mode)) + (org-brain-visualize-quit)) + (let ((marker (org-brain-entry-marker entry))) + (apply (or goto-file-func #'pop-to-buffer-same-window) + (list (marker-buffer marker))) + (widen) + (goto-char (marker-position marker)) + (when (org-at-heading-p) + (org-show-entry) + (org-show-subtree))) + (when (and org-brain-goto-evil-normal (memq 'evil-mode minor-mode-list)) + (evil-normal-state)) + entry) + + #+end_src + + #+RESULTS: + : org-brain-goto + +*** 'org-brain-open + #+begin_src emacs-lisp + + (define-obsolete-function-alias 'org-brain-open 'org-brain-goto "0.4") + + #+end_src + +*** org-brain-goto-other-window + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-goto-other-window (&optional entry) + "Goto buffer and position of org-brain ENTRY in other window. + If ENTRY isn't specified, ask for the ENTRY." + (interactive) + (org-brain-goto entry #'pop-to-buffer)) + + #+end_src + +*** org-brain-goto-end + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-goto-end (&optional entry same-window) + "Like `org-brain-goto', but visits the end of ENTRY. + If SAME-WINDOW is t, use the current window. + If ENTRY isn't specified, ask for the ENTRY." + (interactive) + (if (org-brain-filep (org-brain-goto entry (if same-window nil #'pop-to-buffer))) + (or (outline-next-heading) + (goto-char (point-max))) + (let ((tags (org-get-tags nil t))) + (or (and (not (member org-brain-exclude-children-tag tags)) + (not (member org-brain-show-children-tag tags)) + (org-goto-first-child)) + (org-end-of-subtree t))))) + + #+end_src + +*** org-brain-goto-current + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-goto-current (&optional same-window) + "Use `org-brain-goto' on `org-brain-entry-at-pt', in other window.. + If run with `\\[universal-argument]', or SAME-WINDOW as t, use current window." + (interactive "P") + (if same-window + (org-brain-goto (org-brain-entry-at-pt)) + (org-brain-goto (org-brain-entry-at-pt) #'pop-to-buffer))) + + #+end_src + +*** org-brain-goto-child + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-goto-child (entry &optional all) + "Goto a child of ENTRY. + If run interactively, get ENTRY from context. + If ALL is nil, choose only between externally linked children." + (interactive (list (org-brain-entry-at-pt))) + (let* ((entries (if all (org-brain-children entry) + (org-brain--linked-property-entries + entry org-brain-children-property-name))) + (child (cond + ((equal 1 (length entries)) (car-safe entries)) + ((not entries) (error (concat entry " has no children"))) + (t (org-brain-choose-entry "Goto child: " entries nil t))))) + (org-brain-goto child))) + + #+end_src + +*** org-brain-goto-parent + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-goto-parent (entry &optional all) + "Goto a parent of ENTRY. + If run interactively, get ENTRY from context. + If ALL is nil, choose only between externally linked parents." + (interactive (list (org-brain-entry-at-pt))) + (let* ((entries (if all (org-brain-parents entry) + (org-brain--linked-property-entries + entry org-brain-parents-property-name))) + (parent (cond + ((equal 1 (length entries)) (car-safe entries)) + ((not entries) (error (concat entry " has no parent"))) + (t (org-brain-choose-entry "Goto parent: " entries nil t))))) + (org-brain-goto parent))) + + #+end_src + +*** org-brain-visualize-parent + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-visualize-parent (entry) + "Visualize a parent of ENTRY, preferring local parents. + This allows the user to quickly jump up the hierarchy." + (interactive (list (org-brain-entry-at-pt))) + (if-let ((parent (car (or (org-brain-local-parent entry) + (org-brain-parents entry))))) + (org-brain-visualize parent) + (error "This entry has no parent"))) + + #+end_src + +*** org-brain-goto-friend + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-goto-friend (entry) + "Goto a friend of ENTRY. + If run interactively, get ENTRY from context." + (interactive (list (org-brain-entry-at-pt))) + (let* ((entries (org-brain--linked-property-entries + entry org-brain-friends-property-name)) + (friend (cond + ((equal 1 (length entries)) (car-safe entries)) + ((not entries) (error (concat entry " has no friends"))) + (t (org-brain-choose-entry "Goto friend: " entries nil t))))) + (org-brain-goto friend))) + + #+end_src + +*** org-brain-refile + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-refile (max-level) + "Run `org-refile' to a heading in `org-brain-files', with set MAX-LEVEL. + When in `org-brain-visualize-mode' the current entry will be refiled. + If MAX-LEVEL isn't given, use `org-brain-refile-max-level'. + After refiling, all headlines will be given an id." + (interactive "p") + (unless current-prefix-arg + (setq max-level org-brain-refile-max-level)) + (let ((org-refile-targets `((org-brain-files . (:maxlevel . ,max-level)))) + (org-after-refile-insert-hook org-after-refile-insert-hook)) + (add-hook 'org-after-refile-insert-hook + (lambda () (org-map-tree 'org-brain-get-id))) + (if (eq major-mode 'org-brain-visualize-mode) + (if (org-brain-filep org-brain--vis-entry) + (user-error "Only headline entries can be refiled") + (org-with-point-at (org-brain-entry-marker org-brain--vis-entry) + (org-refile)) + (org-brain--revert-if-visualizing)) + (org-refile)))) + + #+end_src + +*** org-brain-refile-to + #+begin_src emacs-lisp + + (defun org-brain-refile-to (entry parent) + "Refile ENTRY to be a local child of PARENT, returning the new refiled entry. + + If ENTRY is linked to PARENT before the refile, this relationship is removed. + Pins, history, and selected lists are updated + to account for the change in ENTRY's local parent." + (when (member parent (org-brain-local-descendants entry)) + (error "Cannot refile. New parent %s is a local descendant of %s" + (org-brain-title parent) (org-brain-title entry))) + (when (org-brain-filep entry) + (error "Cannot refile a file entry")) + (let ((entry-marker (org-brain-entry-marker entry)) + (parent-title (org-brain-title parent))) + (if (org-brain-filep parent) + ;; Parent is a file entry + (let ((parent-path (org-brain-entry-path parent))) + (with-current-buffer (find-file-noselect parent-path) + (goto-char (point-max)) + (insert "\n* temp headline") + (let ((newpoint (point))) + (org-with-point-at entry-marker + (org-refile nil nil (list parent-title parent-path "" newpoint)))) + (outline-next-heading) + (org-promote-subtree) + (outline-previous-heading) + (org-cut-subtree) + (pop kill-ring) + (forward-line -1) + (org-brain-delete-current-line "^[[:space:]]*$"))) + ;; Parent is a headline entry + (let ((id (org-brain-entry-identifier parent))) + (pcase (org-id-find id) + (`(,file-name . ,pos) + (org-with-point-at entry-marker + (org-refile nil nil (list parent-title file-name "" pos)))) + (_ (error "Parent headline with ID %s not found" id))))) + (let ((new-entry (org-brain-entry-from-id (org-brain-entry-identifier entry)))) + (cl-flet ((replace-entry (e) (if (equal e entry) new-entry e))) + (setq org-brain-pins (mapcar #'replace-entry org-brain-pins)) + (setq org-brain--vis-history (mapcar #'replace-entry org-brain--vis-history)) + (setq org-brain-selected (mapcar #'replace-entry org-brain-selected))) + (when (member parent + (org-brain--linked-property-entries new-entry org-brain-parents-property-name)) + (org-brain-remove-relationship parent new-entry)) + (org-save-all-org-buffers) + (when (eq entry org-brain--vis-entry) + (setq org-brain--vis-entry new-entry)) + new-entry))) + + #+end_src + +*** org-brain-change-local-parent + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-change-local-parent (&optional entry parent) + "Refile ENTRY to be a local child of PARENT. + Entries are relinked so existing parent-child relationships are unaffected. + + If ENTRY is not supplied, the entry at point is used. + If PARENT is not supplied, it is prompted for + among the list of ENTRY's linked parents. + Returns the new refiled entry." + (interactive) + (unless entry (setq entry (org-brain-entry-at-pt t))) + (unless parent (let ((linked-parents (org-brain--linked-property-entries entry org-brain-parents-property-name))) + (cl-case (length linked-parents) + (0 (error "Entry \"%s\" has only one parent" (org-brain-title entry))) + (1 (setq parent (car linked-parents))) + (otherwise (setq parent (org-brain-choose-entry + (format "Refile \"%s\" to parent: " (org-brain-title entry)) linked-parents)))))) + (let ((old-parent (car (org-brain-local-parent entry))) + (new-entry (org-brain-refile-to entry parent))) + (org-brain-add-relationship old-parent new-entry) + (org-brain--revert-if-visualizing) + new-entry)) + + #+end_src + +*** org-brain--remove-relationships + #+begin_src emacs-lisp + + (defun org-brain--remove-relationships (entry &optional recursive) + "Remove all external relationships from ENTRY. + Also unpin and unselect the entry. + + If RECURSIVE is t, remove local children's relationships." + (dolist (child (org-brain--linked-property-entries + entry org-brain-children-property-name)) + (org-brain-remove-relationship entry child)) + (dolist (parent (org-brain--linked-property-entries + entry org-brain-parents-property-name)) + (org-brain-remove-relationship parent entry)) + (dolist (friend (org-brain-friends entry)) + (org-brain-remove-friendship entry friend)) + (ignore-errors (org-brain-pin entry -1) + (org-brain-select entry -1)) + (when recursive + (dolist (child (org-brain-local-children entry)) + (org-brain--remove-relationships child t)))) + + #+end_src + +*** org-brain-rename-file + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-rename-file (file-entry new-name) + "Rename FILE-ENTRY to NEW-NAME. + Both arguments should be relative to `org-brain-path' and should + not contain `org-brain-files-extension'." + (interactive (let ((entry (org-brain-choose-entry + "Rename file: " (org-brain-files t) nil t))) + (list entry (read-string "New filename: " entry)))) + (let ((newpath (org-brain-entry-path new-name)) + (oldpath (org-brain-entry-path file-entry))) + (when (file-exists-p newpath) + (error "There's already a file %s" newpath)) + (when (member newpath (mapcar #'buffer-file-name (buffer-list))) + (error "There's an active buffer associated with file %s" newpath)) + (let ((children (org-brain--linked-property-entries file-entry org-brain-children-property-name)) + (parents (org-brain--linked-property-entries file-entry org-brain-parents-property-name)) + (friends (org-brain-friends file-entry)) + (is-pinned (member file-entry org-brain-pins)) + (is-selected (member file-entry org-brain-selected))) + (org-brain--remove-relationships file-entry) + (org-save-all-org-buffers) + (make-directory (file-name-directory newpath) t) + (if (vc-backend oldpath) + (vc-rename-file oldpath newpath) + (rename-file oldpath newpath)) + (org-brain-update-id-locations) + (when is-pinned (org-brain-pin new-name 1)) + (when is-selected (org-brain-select new-name 1)) + (cl-flet ((replace-entry (e) (if (org-brain-filep e) + (if (equal e file-entry) new-name e) + (when (equal (car e) file-entry) + (cons new-name (cdr e)) e)))) + (setq org-brain-pins (mapcar #'replace-entry org-brain-pins)) + (setq org-brain-selected (mapcar #'replace-entry org-brain-selected)) + (setq org-brain--vis-history (mapcar #'replace-entry org-brain--vis-history)) + (setq org-brain--vis-entry (replace-entry org-brain--vis-entry))) + (dolist (child children) + (org-brain-add-relationship new-name child)) + (dolist (parent parents) + (org-brain-add-relationship parent new-name)) + (dolist (friend friends) + (org-brain--internal-add-friendship new-name friend)) + (when (equal file-entry org-brain--vis-entry) + (setq org-brain--vis-entry new-name)) + ;; Change edges + (let ((edge-property (org-brain-edge-prop-name file-entry))) + (dolist (file (org-brain-files)) + (with-temp-file file + (insert-file-contents file) + (goto-char (point-min)) + (replace-regexp (concat edge-property ":") + (concat org-brain-edge-property-prefix-name "_" (org-brain-entry-identifier new-name) ":"))))) + (org-brain--revert-if-visualizing) + (message "Renamed %s to %s" file-entry new-name)))) + + #+end_src + +*** org-brain-delete-entry + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-delete-entry (entry &optional noconfirm) + "Delete ENTRY and all of its local children. + If run interactively, ask for the ENTRY. + If NOCONFIRM is nil, ask if we really want to delete." + (interactive + (list (org-brain-choose-entry "Delete entry: " 'all nil t) + nil)) + (let ((local-children (org-brain-local-children entry))) + (when (or noconfirm + (yes-or-no-p + (format "%s and its %d local children will be deleted. Are you sure? " + (org-brain-entry-name entry) + (length local-children)))) + (ignore-errors (org-brain-select entry -1)) + (dolist (child local-children) + (org-brain-delete-entry child t)) + (org-brain--remove-relationships entry) + (if (org-brain-filep entry) + (let ((filename (org-brain-entry-path entry))) + (if (vc-backend filename) + (vc-delete-file filename) + (delete-file filename delete-by-moving-to-trash) + (kill-buffer (get-file-buffer filename)))) + (org-with-point-at (org-brain-entry-marker entry) + (org-mark-subtree) + (delete-region (region-beginning) (region-end)))))) + (setq org-brain--vis-history (delete entry org-brain--vis-history)) + (org-save-all-org-buffers) + (if (equal entry org-brain--vis-entry) + (when-let ((brain-buffer (get-buffer "*org-brain*"))) + (if (ignore-errors (org-brain-visualize-back)) + (message "Deleted visualized entry, going back in history.") + (kill-buffer brain-buffer) + (message "Deleted visualized entry. No history, hence killing org-brain buffer."))) + (org-brain--revert-if-visualizing t))) + + #+end_src + +*** org-brain-insert-relationships + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-insert-relationships (entry &optional recursive) + "Insert an `org-mode' list of relationships to ENTRY. + Local children are not included in the list. + If run interactively, get ENTRY from context. + + Normally the list is inserted at point, but if RECURSIVE is t + insert at end of ENTRY. Then recurse in the local (grand)children + of ENTRY and insert there too." + (interactive (list (org-brain-entry-at-pt t))) + (cl-flet ((list-to-items + (list) + (when list + `(unordered + ,@(mapcar (lambda (x) + (list (org-make-link-string + (format "brain:%s" (org-brain-entry-identifier x)) + (org-brain-title x)))) + list))))) + (save-excursion + (when recursive + (org-brain-goto-end entry) + (newline 2)) + (insert + ":RELATIONSHIPS:\n" + (org-list-to-org `(unordered + ,(remq nil `("Parents" + ,(list-to-items (org-brain-parents entry)))) + ,(remq nil `("Children" + ,(list-to-items (org-brain--linked-property-entries + entry org-brain-children-property-name)))) + ,(remq nil `("Friends" + ,(list-to-items (org-brain-friends entry)))))) + "\n:END:\n"))) + (when recursive + (dolist (child (org-brain-local-children entry)) + (org-brain-insert-relationships child t)))) + + #+end_src + +*** org-brain-archive + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-archive (entry) + "Use `org-archive-subtree-default' on ENTRY. + If run interactively, get ENTRY from context. + Before archiving, recursively run `org-brain-insert-relationships' on ENTRY. + Remove external relationships from ENTRY, in order to clean up the brain." + (interactive (list (org-brain-entry-at-pt t))) + (when (org-brain-filep entry) + (user-error "Only headline entries can be archived")) + (org-brain-insert-relationships entry t) + (org-brain--remove-relationships entry t) + (org-with-point-at (org-brain-entry-marker entry) + (org-archive-subtree-default)) + (setq org-brain--vis-history (delete entry org-brain--vis-history)) + (org-save-all-org-buffers) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** org-brain-pin + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-pin (entry &optional status) + "Change if ENTRY is pinned or not. + If run interactively, get ENTRY from context. + Using `\\[universal-argument]' will use `org-brain-button-at-point' as ENTRY. + + If STATUS is positive, pin the entry. If negative, remove the pin. + If STATUS is omitted, toggle between pinned / not pinned." + (interactive (list (if current-prefix-arg + (car (org-brain-button-at-point)) + (org-brain-entry-at-pt t)))) + (cond ((eq status nil) + (if (member entry org-brain-pins) + (org-brain-pin entry -1) + (org-brain-pin entry 1))) + ((>= status 1) + (if (member entry org-brain-pins) + (error "Entry is already pinned") + (push entry org-brain-pins) + (org-brain-save-data) + (message "Pinned '%s'." (org-brain-entry-name entry)))) + ((< status 1) + (if (member entry org-brain-pins) + (progn + (setq org-brain-pins (delete entry org-brain-pins)) + (org-brain-save-data) + (message "Unpinned '%s'." (org-brain-entry-name entry))) + (error "Entry isn't pinned")))) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** org-brain-select + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-select (entry &optional status) + "Toggle selection of ENTRY. + If run interactively, get ENTRY from context. + + If STATUS is positive, select ENTRY. If negative, unselect it. + If STATUS is omitted, toggle between selected / not selected." + (interactive (list (org-brain-entry-at-pt))) + (when (null entry) (error "Cannot select null entry")) + (cond ((eq status nil) + (if (member entry org-brain-selected) + (org-brain-select entry -1) + (org-brain-select entry 1))) + ((>= status 1) + (if (member entry org-brain-selected) + (error "Entry is already selected") + (push entry org-brain-selected) + (org-brain-save-data) + (message "Entry selected."))) + ((< status 1) + (if (member entry org-brain-selected) + (progn + (setq org-brain-selected (delete entry org-brain-selected)) + (org-brain-save-data) + (message "Entry unselected.")) + (error "Entry isn't selected")))) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** org-brain-clear-selected + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-clear-selected () + "Clear the selected list." + (interactive) + (setq org-brain-selected nil) + (org-brain--revert-if-visualizing)) + #+end_src + +*** org-brain-add-selected-children + #+begin_src emacs-lisp + + (defun org-brain-add-selected-children (entry) + "Add selected entries as children of ENTRY. + If run interactively, get ENTRY from context. + + When ENTRY is in the selected list, it is ignored." + (interactive (list (org-brain-entry-at-pt))) + ;; org-brain-add-child takes a list of children, + ;; but we call it one at a time + ;; so that errors don't interrupt the bulk operation. + (dolist (child org-brain-selected) + (ignore-errors (org-brain-add-child entry (list child))))) + + #+end_src + +*** org-brain-remove-selected-children + #+begin_src emacs-lisp + + (defun org-brain-remove-selected-children (entry) + "Remove selected entries from the list of ENTRY's children. + If run interactively, get ENTRY from context. + + Ignores selected entries that are not children of ENTRY." + (interactive (list (org-brain-entry-at-pt))) + (dolist (child org-brain-selected) + (ignore-errors (org-brain-remove-child entry child)))) + + #+end_src + +*** org-brain-add-selected-parents + #+begin_src emacs-lisp + + (defun org-brain-add-selected-parents (entry) + "Add selected entries as parents of ENTRY. + If run interactively, get ENTRY from context. + + When ENTRY is in the selected list, it is ignored." + (interactive (list (org-brain-entry-at-pt))) + ;; org-brain-add-parent takes a list of parents, + ;; but we call it one at a time + ;; so that errors don't interrupt the bulk operation. + (dolist (parent org-brain-selected) + (ignore-errors (org-brain-add-parent entry (list parent))))) + + #+end_src + +*** org-brain-remove-selected-parents + #+begin_src emacs-lisp + + (defun org-brain-remove-selected-parents (entry) + "Remove selected entries from the list of ENTRY's parents. + If run interactively, get ENTRY from context. + + Ignores selected entries that are not parents of ENTRY." + (interactive (list (org-brain-entry-at-pt))) + (dolist (parent org-brain-selected) + (ignore-errors (org-brain-remove-parent entry parent)))) + + #+end_src + +*** org-brain-add-selected-friendships + #+begin_src emacs-lisp + + (defun org-brain-add-selected-friendships (entry) + "Add selected entries as friends of ENTRY. + If run interactively, get ENTRY from context. + + When ENTRY is in the selected list, it is ignored." + (interactive (list (org-brain-entry-at-pt))) + ;; org-brain-add-friendship takes a list of friends, + ;; but we call it one at a time + ;; so that errors don't interrupt the bulk operation. + (dolist (friend org-brain-selected) + (ignore-errors (org-brain-add-friendship entry (list friend))))) + + #+end_src + +*** org-brain-remove-selected-friendships + #+begin_src emacs-lisp + + (defun org-brain-remove-selected-friendships (entry) + "Remove selected entries from the list of ENTRY's friends. + If run interactively, get ENTRY from context. + + Ignores selected entries that are not friends of ENTRY." + (interactive (list (org-brain-entry-at-pt))) + (dolist (selected org-brain-selected) + (ignore-errors (org-brain-remove-friendship entry selected)))) + + #+end_src + +*** org-brain-delete-selected-entries + #+begin_src emacs-lisp + + (defun org-brain-delete-selected-entries () + "Delete all of the selected entries." + (interactive) + (dolist (selected org-brain-selected) + (org-brain-delete-entry selected))) + + #+end_src + +*** org-brain-change-selected-local-parents + #+begin_src emacs-lisp + + (defun org-brain-change-selected-local-parents () + "Change the local parent of all the selected entries." + (interactive) + (dolist (selected org-brain-selected) + (org-brain-change-local-parent selected))) + + #+end_src + +*** org-brain-set-title + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-set-title (entry title) + "Set the name of ENTRY to TITLE. + If run interactively, get ENTRY from context and prompt for TITLE." + (interactive + (let* ((entry-at-pt (org-brain-entry-at-pt t)) + (new-title (org-brain-title entry-at-pt))) + (when (equal (length new-title) 0) + (error "Title must be at least 1 character")) + (list entry-at-pt (read-string "Title: " new-title)))) + (if (org-brain-filep entry) + ;; File entry + (org-with-point-at (org-brain-entry-marker entry) + (goto-char (point-min)) + (when (assoc "TITLE" (org-brain-keywords entry)) + (re-search-forward "^#\\+TITLE:") + (org-brain-delete-current-line)) + (insert (format "#+TITLE: %s\n" title)) + (save-buffer)) + ;; Headline entry + (org-with-point-at (org-brain-entry-marker entry) + (org-edit-headline title) + (save-buffer) + (setf (nth 1 org-brain--vis-entry) title))) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** org-brain-set-tags + #+begin_src emacs-lisp + ;;;###autoload + + (defun org-brain-set-tags (entry) + "Modify the ENTRY tags. + Use `org-set-tags-command' on headline ENTRY. + Instead sets #+FILETAGS on file ENTRY. + If run interactively, get ENTRY from context." + (interactive (list (org-brain-entry-at-pt t))) + (if (org-brain-filep entry) + (org-with-point-at (org-brain-entry-marker entry) + (let ((tag-str (read-string "FILETAGS: " + (mapconcat #'identity org-file-tags ":")))) + (goto-char (point-min)) + (when (assoc "FILETAGS" (org-brain-keywords entry)) + (re-search-forward "^#\\+FILETAGS:") + (org-brain-delete-current-line)) + (insert (format "#+FILETAGS: %s\n" tag-str))) + ;; From org.el + (let ((org-inhibit-startup-visibility-stuff t) + (org-startup-align-all-tables nil)) + (when (boundp 'org-table-coordinate-overlays) + (mapc #'delete-overlay org-table-coordinate-overlays) + (setq org-table-coordinate-overlays nil)) + (org-save-outline-visibility 'use-markers (org-mode-restart))) + (save-buffer)) + (org-with-point-at (org-brain-entry-marker entry) + (org-set-tags-command) + (save-buffer))) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** org-brain-add-nickname + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-add-nickname (entry nickname) + "ENTRY gets a new NICKNAME. + If run interactively use `org-brain-entry-at-pt' and prompt for NICKNAME." + (interactive (list (org-brain-entry-at-pt) + (read-string "Nickname: "))) + (if (org-brain-filep entry) + (let ((nickname (org-entry-protect-space nickname))) + (org-with-point-at (org-brain-entry-marker entry) + (goto-char (point-min)) + (if (re-search-forward "^#\\+NICKNAMES:.*$" nil t) + (insert (concat " " nickname)) + (insert (format "#+NICKNAMES: %s\n" nickname))) + (save-buffer))) + (org-entry-add-to-multivalued-property + (org-brain-entry-marker entry) "NICKNAMES" nickname) + (org-save-all-org-buffers))) + + #+end_src + +*** org-brain-headline-to-file + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-headline-to-file (entry) + "Convert headline ENTRY to a file entry. + Prompt for name of the new file. + If interactive, also prompt for ENTRY." + (interactive (list (org-brain-choose-entry "Convert entry: " + (org-brain-headline-entries) + nil t))) + (let* (level + (title (org-brain-title entry)) + (new-entry (read-string "New file entry: " title)) + (path (org-brain-entry-path new-entry))) + (when (file-exists-p path) + (error "That file already exists")) + (let ((parents (org-brain-parents entry)) + (external-parents (org-brain--linked-property-entries entry org-brain-parents-property-name)) + (children (org-brain--linked-property-entries entry org-brain-children-property-name)) + (friends (org-brain-friends entry)) + (hl-text (org-with-point-at (org-brain-entry-marker entry) + (setq level (org-outline-level)) + (org-get-entry)))) + (dolist (parent external-parents) + (org-brain-remove-relationship parent entry)) + (dolist (child children) + (org-brain-remove-relationship entry child)) + (dolist (friend friends) + (org-brain-remove-friendship entry friend)) + (org-with-point-at (org-brain-entry-marker entry) + (org-cut-subtree) + (pop kill-ring) + (save-buffer)) + (make-directory (file-name-directory path) t) + (with-temp-file path + (insert (format "#+TITLE:%s\n\n%s" title hl-text)) + (delay-mode-hooks + (org-mode) + (goto-char (point-min)) + (re-search-forward org-property-drawer-re) + (replace-match "") + (goto-char (point-max)) + (let ((level-regex "^")) + (dotimes (_i (1+ level)) + (setq level-regex (concat level-regex "\\*"))) + (setq level-regex (concat level-regex " ")) + (while (re-search-backward level-regex nil t) + (dotimes (_i level) (org-promote-subtree)))))) + (dolist (parent parents) + (org-brain-add-relationship parent new-entry)) + (dolist (child children) + (org-brain-add-relationship new-entry child)) + (dolist (friend friends) + (org-brain--internal-add-friendship new-entry friend)) + (when (equal entry org-brain--vis-entry) + (setq org-brain--vis-entry new-entry)) + (when (member entry org-brain-pins) + (org-brain-pin entry -1) + (org-brain-pin new-entry 1))))) + + #+end_src + +*** org-brain-ensure-ids-in-buffer + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-ensure-ids-in-buffer () + "Run `org-brain-get-id' on all headlines in current buffer + taking into account the ignore tags such as :childess: + Only works if in an `org-mode' buffer inside `org-brain-path'. + Suitable for use with `before-save-hook'." + (interactive) + (and (eq major-mode 'org-mode) + (string-prefix-p (file-truename org-brain-path) + (file-truename (buffer-file-name))) + (let ((match (format "-%s-%s|-%s+TAGS={%s}" ; "-nobrain-childless|-nobrain+TAGS={childless}" + org-brain-exclude-tree-tag org-brain-exclude-children-tag + org-brain-exclude-tree-tag org-brain-exclude-children-tag))) + (org-map-entries #'org-brain-get-id match 'file)))) + + #+end_src + +*** org-brain-agenda + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-agenda () + "Like `org-agenda', but only for `org-brain-files'." + (interactive) + (let ((org-agenda-files (org-brain-files))) + (org-agenda))) + + #+end_src + +*** org-brain-create-relationships-from-links + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-create-relationships-from-links () + "Add relationships for brain: links in `org-brain-path'. + Only create relationships to other files, not to headline entries. + + This function is meant to be used in order to convert old + org-brain setups to the system introduced in version 0.4. Please + make a backup of your `org-brain-path' before running this + function." + (interactive) + (when (y-or-n-p "This function is meant for old configurations. Are you sure you want to scan for links? ") + (dolist (file (org-brain-files)) + (with-temp-buffer + (insert-file-contents file) + (org-element-map (org-element-parse-buffer) 'link + (lambda (link) + (when (string-equal (org-element-property :type link) "brain") + (org-brain-add-relationship + (org-brain-path-entry-name file) + (car (split-string (org-element-property :path link) "::")))))))))) + + #+end_src + +** Sorting + #+begin_src emacs-lisp + + ;;; Sorting + + #+end_src + +*** org-brain-title< + #+begin_src emacs-lisp + + (defun org-brain-title< (entry1 entry2) + "Return non-nil if title of ENTRY1 is less than ENTRY2 in lexicographic order. + Case is significant." + (string< (org-brain-title entry1) (org-brain-title entry2))) + + #+end_src + +*** org-brain-visualize-sort-function + #+begin_src emacs-lisp + + (defvar-local org-brain-visualize-sort-function 'org-brain-title< + "How to sort lists of relationships when visualizing. + Should be a function which accepts two entries as arguments. + The function returns t if the first entry is smaller than the second. + + If you don't want to sort the relationships, set this to `ignore'.") + + #+end_src + +** Visualize + #+begin_src emacs-lisp + ;;; Visualize + #+end_src + +*** org-brain--visualize-follow + #+begin_src emacs-lisp + + (defvar-local org-brain--visualize-follow nil "Used by `org-brain-visualize-follow'.") + + #+end_src + +*** org-brain-visualize-follow + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-visualize-follow (should-follow) + "Set if `org-brain-visualize' SHOULD-FOLLOW the current entry or not. + When following, the visualized entry will be shown in a separate + buffer when changing the visualized entry. + If run interactively, toggle following on/off." + (interactive (list (not org-brain--visualize-follow))) + (setq org-brain--visualize-follow should-follow) + (message (if should-follow + "Enabled following visualized entry." + "Disabled following visualized entry."))) + + #+end_src + +*** org-brain--visualize-header-end-pos + #+begin_src emacs-lisp + + (defvar-local org-brain--visualize-header-end-pos 0 + "Buffer position at end of headers (history etc) in `org-brain-visualize'.") + + #+end_src + +*** org-brain-visualize + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-visualize (entry &optional nofocus nohistory wander) + "View a concept map with ENTRY at the center. + + When run interactively, prompt for ENTRY and suggest + `org-brain-entry-at-pt'. By default, the choices presented is + determined by `org-brain-visualize-default-choices': 'all will + show all entries, 'files will only show file entries and 'root + will only show files in the root of `org-brain-path'. + + You can override `org-brain-visualize-default-choices': + `\\[universal-argument]' will use 'all. + `\\[universal-argument] \\[universal-argument]' will use 'files. + `\\[universal-argument] \\[universal-argument] \\[universal-argument]' will use 'root. + + Unless NOFOCUS is non-nil, the `org-brain-visualize' buffer will gain focus. + Unless NOHISTORY is non-nil, add the entry to `org-brain--vis-history'. + Setting NOFOCUS to t implies also having NOHISTORY as t. + Unless WANDER is t, `org-brain-stop-wandering' will be run." + (interactive + (progn + (org-brain-maybe-switch-brain) + (let ((choices (cond ((equal current-prefix-arg '(4)) 'all) + ((equal current-prefix-arg '(16)) 'files) + ((equal current-prefix-arg '(64)) 'root) + (t org-brain-visualize-default-choices))) + (def-choice (unless (eq major-mode 'org-brain-visualize-mode) + (ignore-errors (org-brain-entry-name (org-brain-entry-at-pt)))))) + (org-brain-stop-wandering) + (list + (org-brain-choose-entry + "Entry: " + (cond ((equal choices 'all) + 'all) + ((equal choices 'files) + (org-brain-files t)) + ((equal choices 'root) + (make-directory org-brain-path t) + (mapcar #'org-brain-path-entry-name + (directory-files org-brain-path t (format "\\.%s$" org-brain-files-extension))))) + nil nil def-choice))))) + (unless wander (org-brain-stop-wandering)) + (with-current-buffer (get-buffer-create "*org-brain*") + (setq-local indent-tabs-mode nil) + (read-only-mode 1) + (setq-local default-directory (file-name-directory (org-brain-entry-path entry))) + (org-brain-maybe-switch-brain) + (unless (eq org-brain--vis-entry entry) + (setq org-brain--vis-entry entry) + (setq org-brain-mind-map-parent-level (default-value 'org-brain-mind-map-parent-level)) + (setq org-brain-mind-map-child-level (default-value 'org-brain-mind-map-child-level))) + (setq org-brain--vis-entry-keywords (when (org-brain-filep entry) + (org-brain-keywords entry))) + (let ((inhibit-read-only t) + (entry-pos)) + (delete-region (point-min) (point-max)) + (org-brain--vis-pinned) + (org-brain--vis-selected) + (when (not nohistory) + (setq org-brain--vis-history + (seq-filter (lambda (elt) (not (equal elt entry))) org-brain--vis-history)) + (setq org-brain--vis-history (seq-take org-brain--vis-history 15)) + (push entry org-brain--vis-history)) + (when org-brain-show-history (org-brain--vis-history)) + (if org-brain-visualizing-mind-map + (setq entry-pos (org-brain-mind-map org-brain--vis-entry org-brain-mind-map-parent-level org-brain-mind-map-child-level)) + (setq-local org-brain--visualize-header-end-pos (point)) + (insert "\n\n") + (org-brain--vis-parents-siblings entry) + ;; Insert entry title + (let ((title (org-brain-vis-title entry))) + (let ((half-title-length (/ (string-width title) 2))) + (if (>= half-title-length (current-column)) + (delete-char (- (current-column))) + (ignore-errors (delete-char (- half-title-length))))) + (setq entry-pos (point)) + (insert (propertize title + 'face (org-brain-display-face entry 'org-brain-title) + 'aa2u-text t)) + (org-brain--vis-friends entry) + (org-brain--vis-children entry))) + (when (and org-brain-show-resources) + (org-brain--vis-resources (org-brain-resources entry))) + (if org-brain-show-text + (org-brain--vis-text entry) + (run-hooks 'org-brain-after-visualize-hook)) + (unless (eq major-mode 'org-brain-visualize-mode) + (org-brain-visualize-mode)) + (goto-char entry-pos) + (set-buffer-modified-p nil)) + (unless nofocus + (when org-brain--visualize-follow + (org-brain-goto-current) + (run-hooks 'org-brain-visualize-follow-hook)) + (if (or org-brain--visualize-follow org-brain-open-same-window) + (pop-to-buffer "*org-brain*") + (pop-to-buffer-same-window "*org-brain*"))))) + + #+end_src + +*** org-brain-visualize-dwim + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-visualize-dwim () + "Switch to the *org-brain* buffer. + If there's no such buffer, or if already there, run `org-brain-visualize'." + (interactive) + (if (and (not (org-brain-maybe-switch-brain)) + (not (eq major-mode 'org-brain-visualize-mode)) + (get-buffer "*org-brain*")) + (if org-brain-open-same-window + (pop-to-buffer "*org-brain*") + (pop-to-buffer-same-window "*org-brain*")) + (call-interactively #'org-brain-visualize))) + + #+end_src + +*** org-brain-visualize-entry-at-pt + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-visualize-entry-at-pt () + "Use `org-brain-visualize' on the `org-brain-entry-at-pt'. + Useful if wanting to visualize the current `org-mode' entry." + (interactive) + (org-brain-visualize (org-brain-entry-at-pt))) + + #+end_src + +*** org-brain-visualize-random + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-visualize-random (&optional restrict-to) + "Run `org-brain-visualize' on a random org-brain entry. + If RESTRICT-TO is given, then only choose among those entries. + + If called interactively with `\\[universal-argument]' then + restrict to descendants of the visualized entry." + (interactive (when (equal current-prefix-arg '(4)) + (list (org-brain-descendants org-brain--vis-entry)))) + (let ((entries (or restrict-to + (append (org-brain-files t) + (org-brain-headline-entries))))) + (org-brain-visualize (nth (random (length entries)) entries) nil nil t))) + + #+end_src + +*** org-brain-wander-timer + #+begin_src emacs-lisp + + (defvar-local org-brain-wander-timer nil + "A timer running `org-brain-visualize-random' at a set interval. + + Can be (de)activated by `org-brain-visualize-wander'.") + + #+end_src + +*** org-brain-stop-wandering + #+begin_src emacs-lisp + + (defun org-brain-stop-wandering () + "Cancels `org-brain-wander-timer', if it is active." + (when (member org-brain-wander-timer timer-list) + (cancel-timer org-brain-wander-timer) + t)) + + #+end_src + +*** org-brain-visualize-wander + #+begin_src emacs-lisp + + (defun org-brain-visualize-wander (&optional restrict-to) + "Run `org-brain-visualize-random' every `org-brain-wander-interval'. + If RESTRICT-TO is given, then only wander among those entries. + + If called interactively with `\\[universal-argument]' then + restrict to descendants of the visualized entry starting the wandering session. + + Wandering is cancelled by many org-brain commands, but can also be + cancelled manually with `org-brain-stop-wandering'." + (interactive (when (equal current-prefix-arg '(4)) + (list (org-brain-descendants org-brain--vis-entry)))) + (if (org-brain-stop-wandering) + (message "Wandering stopped.") + (setq org-brain-wander-timer (run-at-time nil org-brain-wander-interval #'org-brain-visualize-random restrict-to)) + (message "Wandering started."))) + + #+end_src + +*** org-brain-visualize-quit + #+begin_src emacs-lisp + + (defun org-brain-visualize-quit () + "Like `quit-window', but also stops `org-brain-visualize-wander'." + (interactive) + (org-brain-stop-wandering) + (quit-window)) + + #+end_src + +*** org-brain-entry-icon + #+begin_src emacs-lisp + + (defun org-brain-entry-icon (entry) + "Get a string representing the icon of ENTRY. + Checks for the org mode category of ENTRY, then search for the + category icon in `org-agenda-category-icon-alist'." + (when (and org-brain-show-icons + org-agenda-category-icon-alist) + (org-with-point-at (org-brain-entry-marker entry) + (when-let* ((category (org-get-category)) + (icon (org-agenda-get-category-icon category))) + (propertize (make-string org-brain-category-icon-width ? ) 'display icon))))) + + #+end_src + +*** org-brain-vis-title + #+begin_src emacs-lisp + + (defun org-brain-vis-title (entry) + "The title of ENTRY when shown in `org-brain-visualize-mode'." + (string-join (remove + "" + (list + ;; Prepend stuff to the title + (mapconcat (lambda (func) (funcall func entry)) + org-brain-vis-title-prepend-functions + " ") + (if (eq org-brain--vis-entry entry) + (mapconcat (lambda (func) (funcall func entry)) + org-brain-vis-current-title-prepend-functions + " ") + "") + ;; The title itself + (org-brain-title entry (or (not org-brain-visualizing-mind-map) + org-brain-cap-mind-map-titles)) + ;; Append stuff to the title + (mapconcat (lambda (func) (funcall func entry)) + org-brain-vis-title-append-functions + " ") + (if (eq org-brain--vis-entry entry) + (mapconcat (lambda (func) (funcall func entry)) + org-brain-vis-current-title-append-functions + " ") + ""))) + " ")) + + #+end_src + +*** org-brain-insert-visualize-button + #+begin_src emacs-lisp + + (defun org-brain-insert-visualize-button (entry &optional face category) + "Insert a button, running `org-brain-visualize' on ENTRY when clicked. + FACE is sent to `org-brain-display-face' and sets the face of the button. + CATEGORY is used to set the `brain-category` text property." + (let ((annotation (org-brain-get-edge-annotation org-brain--vis-entry + entry + org-brain--vis-entry-keywords))) + (insert-text-button + (org-brain-vis-title entry) + 'action (lambda (_x) (org-brain-visualize entry)) + 'id (org-brain-entry-identifier entry) + 'follow-link t + 'brain-category (or category 'default) + 'help-echo annotation + 'aa2u-text t + 'face (org-brain-display-face entry face annotation)))) + + #+end_src + +*** org-brain-jump-to-visualize-button + #+begin_src emacs-lisp + + (defun org-brain-jump-to-visualize-button (entry) + "If ENTRY has a visualize button in the current buffer, jump to its position." + (when (eq major-mode 'org-brain-visualize-mode) + (let ((start-pos (point)) + (entry-id (org-brain-entry-identifier entry))) + (goto-char org-brain--visualize-header-end-pos) + (while (and (or (ignore-errors (forward-button 1)) + (and (goto-char start-pos) nil)) + (not (equal (button-get (button-at (point)) 'id) + entry-id))))))) + + #+end_src + +*** org-brain-insert-resource-button + #+begin_src emacs-lisp + + (defun org-brain-insert-resource-button (resource &optional indent) + "Insert a new line with a RESOURCE button, indented by INDENT spaces." + (insert (make-string (or indent 0) ?\ ) "\n- ") + (run-hook-with-args 'org-brain-after-resource-button-functions (car resource)) + (insert-text-button + (or (cdr resource) (car resource)) + 'action (lambda (_x) + (org-open-link-from-string (format "[[%s]]" (car resource)))) + 'follow-link t + 'aa2u-text t)) + + #+end_src + +*** org-brain-button-at-point + #+begin_src emacs-lisp + + (defun org-brain-button-at-point () + "If there's an entry link button at `point' return (entry . button)." + (if-let* ((button (button-at (point))) + (id (button-get button 'id)) + (entry (or (org-brain-entry-from-id id) + (org-entry-restore-space id)))) + (cons entry button) + (user-error "No entry button at point"))) + + #+end_src + +*** org-brain-add-resource + #+begin_src emacs-lisp + + (defun org-brain-add-resource (&optional link description prompt entry) + "Insert LINK with DESCRIPTION in ENTRY. + If ENTRY is nil, try to get it from context or prompt for it. + If LINK is nil then use `org-insert-link-global'. Otherwise: + If PROMPT is non nil, let user edit the resource even if run non-interactively." + (interactive) + (unless entry + (setq entry (or (ignore-errors (org-brain-entry-at-pt)) + (org-brain-choose-entry "Insert link in entry: " 'all)))) + (let ((link-text + (if link + (progn + (when prompt + (setq link (read-string "Insert link: " link)) + (when (string-match org-bracket-link-regexp link) + (let ((linkdesc (match-string 3 link))) + (when (and (not description) linkdesc) + (setq description linkdesc)) + (setq link (match-string 1 link)))) + (setq description (read-string "Link description: " description))) + (concat "- " (org-make-link-string link description))) + (let ((bfn (buffer-file-name))) + (when-let ((l (with-temp-buffer + (let ((buffer-file-name bfn)) + (org-insert-link-global) + (buffer-string))))) + (concat "- " l)))))) + (if (org-brain-filep entry) + ;; File entry + (org-with-point-at (org-brain-entry-marker entry) + (goto-char (org-brain-first-headline-position)) + (if (re-search-backward org-brain-resources-start-re nil t) + (end-of-line) + (if (re-search-backward org-brain-keyword-regex nil t) + (progn + (end-of-line) + (newline-and-indent)) + (goto-char (point-min))) + (insert (concat ":" org-brain-resources-drawer-name ":\n:END:\n")) + (re-search-backward org-brain-resources-start-re nil t) + (end-of-line)) + (newline-and-indent) + (insert link-text) + (save-buffer)) + ;; Headline entry + (org-with-point-at (org-brain-entry-marker entry) + (goto-char (cdr (org-get-property-block))) + (forward-line 1) + (if (looking-at org-brain-resources-start-re) + (end-of-line) + (open-line 1) + (indent-for-tab-command) + (insert (concat ":" org-brain-resources-drawer-name ":")) + (save-excursion + (insert "\n") + (indent-for-tab-command) + (insert ":END:"))) + (newline-and-indent) + (insert link-text) + (save-buffer)))) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** 'org-brain-visualize-add-resource + #+begin_src emacs-lisp + + (defalias 'org-brain-visualize-add-resource #'org-brain-add-resource) + + #+end_src + +*** org-brain-add-file-line-as-resource + #+begin_src emacs-lisp + + (defun org-brain-add-file-line-as-resource (file line &optional entry) + "Add a link to a FILE LINE as a resource in ENTRY. + If called interactively use current FILE and LINE + and prompt for ENTRY, unless called with `\\[universal-argument]' + in which case use the current/last visualized entry." + (interactive (list (buffer-file-name) + (number-to-string (line-number-at-pos)))) + (org-brain-add-resource (concat "file:" file "::" line) + nil nil + (or entry (when current-prefix-arg + org-brain--vis-entry))) + (ignore-errors + (with-current-buffer "*org-brain*" + (org-brain--revert-if-visualizing))) + (message "A new resource has been added.")) + + #+end_src + +*** org-brain-add-file-as-resource + #+begin_src emacs-lisp + + (defun org-brain-add-file-as-resource (file &optional entry) + "Add a link to a FILE as a resource in ENTRY. + If called interactively use current FILE + and prompt for ENTRY, unless called with `\\[universal-argument]' + in which case use the current/last visualized entry." + (interactive (list (buffer-file-name))) + (org-brain-add-resource (concat "file:" file) + nil nil + (or entry (when current-prefix-arg + org-brain--vis-entry))) + (ignore-errors + (with-current-buffer "*org-brain*" + (org-brain--revert-if-visualizing))) + (message "A new resource has been added.")) + + #+end_src + +*** org-brain-visualize-attach + #+begin_src emacs-lisp + + (defun org-brain-visualize-attach () + "Use `org-attach' on `org-brain--vis-entry'." + (interactive) + (unless (eq major-mode 'org-brain-visualize-mode) + (error "Not in org-brain-visualize-mode")) + (when (org-brain-filep org-brain--vis-entry) + (error "Can only attach to headline entries")) + (org-with-point-at (org-brain-entry-marker org-brain--vis-entry) + (goto-char (cdr (org-id-find (nth 2 org-brain--vis-entry)))) + (call-interactively #'org-attach) + (save-buffer)) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** org-brain-paste-resource + #+begin_src emacs-lisp + + (defun org-brain-paste-resource () + "Add `current-kill' as a resource link. + See `org-brain-add-resource'." + (interactive) + (org-brain-add-resource (current-kill 0) nil t)) + + #+end_src + +*** 'org-brain-visualize-paste-resource + #+begin_src emacs-lisp + + (defalias 'org-brain-visualize-paste-resource #'org-brain-paste-resource) + + #+end_src + +*** org-brain-select-button + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-select-button () + "Toggle selection of the entry linked to by the button at point." + (interactive) + (org-brain-select (car (org-brain-button-at-point))) + t) + + #+end_src + +*** org-brain-select-dwim + #+begin_src emacs-lisp + + ;;;###autoload + (defun org-brain-select-dwim (arg) + "Use `org-brain-select-button' or `org-brain-select' depending on context. + If run with `\\[universal-argument\\]' (ARG is non nil) + then always use `org-brain-select'." + (interactive "P") + (when (or arg (not (ignore-errors (org-brain-select-button)))) + (org-brain-select (org-brain-entry-at-pt)))) + + #+end_src + +*** org-brain-edge-prop-name + #+begin_src emacs-lisp + + (defun org-brain-edge-prop-name (entry) + "Retrun edge annotation property name of ENTRY." + (concat org-brain-edge-property-prefix-name "_" (org-brain-entry-identifier entry))) + + #+end_src + +*** org-brain-get-edge-annotation + #+begin_src emacs-lisp + + (defun org-brain-get-edge-annotation (from to &optional keywords) + "Get edge annotation FROM an entry TO another entry. + If KEYWORDS is given, use it instead of `org-brain-keywords' (optimization)." + (if (org-brain-filep from) + (cdr (assoc (upcase (org-brain-edge-prop-name to)) + (or keywords (org-brain-keywords from)))) + (org-entry-get (org-brain-entry-marker from) (org-brain-edge-prop-name to)))) + + #+end_src + +*** org-brain-annotate-edge + #+begin_src emacs-lisp + + (defun org-brain-annotate-edge (entry target annotation two-way) + "When visualizing ENTRY, links to TARGET will have an ANNOTATION. + You can think of it as edges with comments in a graph. + If TWO-WAY is non-nil, then also add the ANNOTATION from TARGET to ENTRY. + + When called interactively use the visualized ENTRY, + `org-brain-button-at-point' as TARGET, and prompt for ANNOTATION. + TWO-WAY will be t unless called with `\\[universal-argument\\]'." + (interactive + (let ((target (car (org-brain-button-at-point)))) + (list org-brain--vis-entry + target + (read-string (concat (org-brain-title target) " edge: ")) + (not current-prefix-arg)))) + (if (org-brain-filep entry) + ;; File entry + (let ((edge-regex (format "^#\\+%s:" + (org-brain-edge-prop-name target)))) + (org-with-point-at (org-brain-entry-marker entry) + (if (re-search-forward edge-regex nil t) + (org-brain-delete-current-line edge-regex) + (goto-char (point-min))) + (when (> (length annotation) 0) + (insert "#+" (org-brain-edge-prop-name target) ": " annotation "\n")) + (save-buffer))) + ;; Headline entry + (org-with-point-at (org-brain-entry-marker entry) + (if (> (length annotation) 0) + (org-set-property (org-brain-edge-prop-name target) annotation) + (org-delete-property (org-brain-edge-prop-name target))) + (save-buffer))) + + #+end_src + +*** edge timer + #+begin_src emacs-lisp + + (when two-way + (run-with-idle-timer 0.2 nil 'org-brain-annotate-edge + target entry annotation nil)) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** org-brain-visualize-back + #+begin_src emacs-lisp + + (defun org-brain-visualize-back () + "Go back to the previously visualized entry." + (interactive) + (if (cadr org-brain--vis-history) + (progn (pop org-brain--vis-history) + (org-brain-visualize (car org-brain--vis-history) nil t)) + (error "No further history"))) + + #+end_src + +*** org-brain-visualize-revert + #+begin_src emacs-lisp + + (defun org-brain-visualize-revert (_ignore-auto _noconfirm) + "Revert function for `org-brain-visualize-mode'." + (org-brain-visualize org-brain--vis-entry t)) + + #+end_src + +*** org-brain--revert-if-visualizing + #+begin_src emacs-lisp + + (defun org-brain--revert-if-visualizing (&optional ignore-button-at-pt) + "Revert buffer if in `org-brain-visualize-mode'. + Unless IGNORE-BUTTON-AT-PT is non nil, jump to the button at + point before the buffer was reverted." + (when (eq major-mode 'org-brain-visualize-mode) + (let ((button-entry + (unless ignore-button-at-pt + (car (ignore-errors (org-brain-button-at-point)))))) + (org-brain-stop-wandering) + (revert-buffer) + (when button-entry (org-brain-jump-to-visualize-button button-entry))))) + + #+end_src + +*** org-brain--bookmark-handler + #+begin_src emacs-lisp + + (defun org-brain--bookmark-handler (bookmark) + "Visualize the entry stored in BOOKMARK." + (org-brain-visualize (cdr (assoc 'brain-entry bookmark)) nil) + (switch-to-buffer "*org-brain*")) + + #+end_src + +*** org-brain-make-bookmark-record + #+begin_src emacs-lisp + + (defun org-brain-make-bookmark-record () + "Make a bookmark out of `org-brain--vis-entry'. + Used as `bookmark-make-record-function' in `org-brain-visualize-mode'." + (if-let ((entry org-brain--vis-entry)) + (cons (org-brain-title org-brain--vis-entry) + `((handler . org-brain--bookmark-handler) + (brain-entry . ,org-brain--vis-entry))) + (user-error "For some reason `org-brain--vis-entry' is nil"))) + + #+end_src + +*** org-brain-visualize-mode + + #+begin_src emacs-lisp + + (define-derived-mode org-brain-visualize-mode + special-mode "Org-brain Visualize" + "Major mode for `org-brain-visualize'. + \\{org-brain-visualize-mode-map}" + (setq-local revert-buffer-function #'org-brain-visualize-revert) + (setq-local bookmark-make-record-function #'org-brain-make-bookmark-record)) + + #+end_src + +** Keybindings + #+begin_src emacs-lisp + + ;;; Keybindings + + (define-key org-brain-visualize-mode-map "p" 'org-brain-add-parent) + (define-key org-brain-visualize-mode-map "P" 'org-brain-remove-parent) + (define-key org-brain-visualize-mode-map "c" 'org-brain-add-child) + (define-key org-brain-visualize-mode-map "C" 'org-brain-remove-child) + (define-key org-brain-visualize-mode-map "*" 'org-brain-add-child-headline) + (define-key org-brain-visualize-mode-map "h" 'org-brain-add-child-headline) + (define-key org-brain-visualize-mode-map "n" 'org-brain-pin) + (define-key org-brain-visualize-mode-map "N" 'org-brain-add-nickname) + (define-key org-brain-visualize-mode-map "t" 'org-brain-set-title) + (define-key org-brain-visualize-mode-map "j" 'forward-button) + (define-key org-brain-visualize-mode-map "k" 'backward-button) + (define-key org-brain-visualize-mode-map "u" 'org-brain-visualize-parent) + (define-key org-brain-visualize-mode-map [?\t] 'forward-button) + (define-key org-brain-visualize-mode-map [backtab] 'backward-button) + (define-key org-brain-visualize-mode-map "o" 'org-brain-goto-current) + (define-key org-brain-visualize-mode-map "O" 'org-brain-goto) + (define-key org-brain-visualize-mode-map "v" 'org-brain-visualize) + (define-key org-brain-visualize-mode-map "V" 'org-brain-visualize-follow) + (define-key org-brain-visualize-mode-map "f" 'org-brain-add-friendship) + (define-key org-brain-visualize-mode-map "F" 'org-brain-remove-friendship) + (define-key org-brain-visualize-mode-map "d" 'org-brain-delete-entry) + (define-key org-brain-visualize-mode-map "l" 'org-brain-add-resource) + (define-key org-brain-visualize-mode-map "r" 'org-brain-open-resource) + (define-key org-brain-visualize-mode-map "a" 'org-brain-visualize-attach) + (define-key org-brain-visualize-mode-map "A" 'org-brain-archive) + (define-key org-brain-visualize-mode-map "b" 'org-brain-visualize-back) + (define-key org-brain-visualize-mode-map "\C-y" 'org-brain-visualize-paste-resource) + (define-key org-brain-visualize-mode-map "T" 'org-brain-set-tags) + (define-key org-brain-visualize-mode-map "q" 'org-brain-visualize-quit) + (define-key org-brain-visualize-mode-map "w" 'org-brain-visualize-random) + (define-key org-brain-visualize-mode-map "W" 'org-brain-visualize-wander) + (define-key org-brain-visualize-mode-map "m" 'org-brain-visualize-mind-map) + (define-key org-brain-visualize-mode-map "+" 'org-brain-show-descendant-level) + (define-key org-brain-visualize-mode-map "-" 'org-brain-hide-descendant-level) + (define-key org-brain-visualize-mode-map "z" 'org-brain-show-ancestor-level) + (define-key org-brain-visualize-mode-map "Z" 'org-brain-hide-ancestor-level) + (define-key org-brain-visualize-mode-map "e" 'org-brain-annotate-edge) + (define-key org-brain-visualize-mode-map "\C-c\C-w" 'org-brain-refile) + (define-key org-brain-visualize-mode-map "\C-c\C-x\C-v" 'org-toggle-inline-images) + + (define-prefix-command 'org-brain-select-map) + (define-key org-brain-select-map "s" 'org-brain-clear-selected) + (define-key org-brain-select-map "c" 'org-brain-add-selected-children) + (define-key org-brain-select-map "C" 'org-brain-remove-selected-children) + (define-key org-brain-select-map "p" 'org-brain-add-selected-parents) + (define-key org-brain-select-map "P" 'org-brain-remove-selected-parents) + (define-key org-brain-select-map "f" 'org-brain-add-selected-friendships) + (define-key org-brain-select-map "F" 'org-brain-remove-selected-friendships) + (define-key org-brain-select-map "s" 'org-brain-clear-selected) + (define-key org-brain-select-map "S" 'org-brain-clear-selected) + (define-key org-brain-select-map "d" 'org-brain-delete-selected-entries) + (define-key org-brain-select-map "l" 'org-brain-change-selected-local-parents) + + (define-key org-brain-visualize-mode-map "s" 'org-brain-select-dwim) + (define-key org-brain-visualize-mode-map "S" 'org-brain-select-map) + + (define-prefix-command 'org-brain-move-map) + (define-key org-brain-move-map "r" 'org-brain-refile) + (define-key org-brain-move-map "p" 'org-brain-change-local-parent) + + (define-key org-brain-visualize-mode-map "M" 'org-brain-move-map) + + (let ((map (define-prefix-command 'org-brain-prefix-map))) + (set-keymap-parent map org-brain-visualize-mode-map) + (mapc (lambda (x) (define-key map x nil)) + '("j" "k" "g" [?\t] [backtab] "o" "b" "u" "V" "T" "q" + "m" "+" "-" "z" "Z" "e" "?" "\C-c\C-w" "\C-c\C-x\C-v" + "" " " "<" ">" "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" + [33554464]))) + + #+end_src + +** Drawing helpers + #+begin_src emacs-lisp + + ;;; Drawing helpers + + #+end_src + +*** org-brain--visually-sort + #+begin_src emacs-lisp + + (defun org-brain--visually-sort (lst) + "Sort LST destructively according to org-brain-visualize-sort-function." + (sort lst org-brain-visualize-sort-function)) + + #+end_src + +*** org-brain--visually-sorted + #+begin_src emacs-lisp + + (defun org-brain--visually-sorted (lst) + "Sorted LST according to org-brain-visualize-sort-function." + (org-brain--visually-sort (copy-sequence lst))) + + #+end_src + +*** org-brain--maybe-visually-sort + #+begin_src emacs-lisp + + (defun org-brain--maybe-visually-sort (entry lst) + "Sorted LST unless ENTRY has a :nosort: tag." + (if (member org-brain-no-sort-children-tag (org-brain-get-tags entry)) + lst + (org-brain--visually-sort lst))) + + #+end_src + +*** org-brain--visually-sorted-parents + #+begin_src emacs-lisp + + (defun org-brain--visually-sorted-parents (entry) + "List of parents, sorted unless ENTRY has a :nosort: tag." + (org-brain--maybe-visually-sort entry (org-brain-parents entry))) + + #+end_src + +*** org-brain--visually-sorted-children + #+begin_src emacs-lisp + + (defun org-brain--visually-sorted-children (entry) + "List of children, sorted unless ENTRY has a :nosort: tag." + (org-brain--maybe-visually-sort entry (org-brain-children entry))) + + #+end_src + +*** org-brain--visually-sorted-friends + #+begin_src emacs-lisp + + (defun org-brain--visually-sorted-friends (entry) + "List of friends, sorted unless ENTRY has a :nosort: tag." + (org-brain--maybe-visually-sort entry (org-brain-friends entry))) + + #+end_src + +*** org-brain--visually-sorted-siblings + #+begin_src emacs-lisp + + (defun org-brain--visually-sorted-siblings (entry) + "List of siblings, sorted unless ENTRY has a :nosort: tag." + (let ((siblings (org-brain-siblings entry))) + (if (member org-brain-no-sort-children-tag (org-brain-get-tags entry)) + siblings + (sort siblings (lambda (x y) + (funcall org-brain-visualize-sort-function + (car x) (car y))))))) + + #+end_src + +*** org-brain--visually-sorted-siblings-from + #+begin_src emacs-lisp + + (defun org-brain--visually-sorted-siblings-from (pair) + "List of siblings for a parent, sorted unless the parent in PAIR has a :nosort: tag, or empty list if the parent has a :nosiblings: tag." + (let ((parent (car pair))) + (unless (member org-brain-exclude-siblings-tag (org-brain-get-tags parent)) + (org-brain--maybe-visually-sort parent (cdr pair))))) + + #+end_src + +*** org-brain--visually-sorted-pins + #+begin_src emacs-lisp + + (defun org-brain--visually-sorted-pins () + "List of pins visually sorted." + (org-brain--visually-sorted org-brain-pins)) + + #+end_src + +*** org-brain--visually-sorted-selected + #+begin_src emacs-lisp + + (defun org-brain--visually-sorted-selected () + "Visually sorted selection list." + (org-brain--visually-sorted org-brain-selected)) + + #+end_src + +*** org-brain--vis-pinned + #+begin_src emacs-lisp + + (defun org-brain--vis-pinned () + "Insert pinned entries. + Helper function for `org-brain-visualize'." + (insert "PINNED:") + (dolist (pin (org-brain--visually-sorted-pins)) + (insert " ") + (org-brain-insert-visualize-button pin 'org-brain-pinned 'pinned)) + (insert "\n")) + + #+end_src + +*** org-brain--vis-selected + #+begin_src emacs-lisp + + (defun org-brain--vis-selected () + "Insert selected entries. + Helper function for `org-brain-visualize'." + (unless (null org-brain-selected) + (insert "SELECTED:") + (dolist (selection (org-brain--visually-sorted-selected)) + (insert " ") + (org-brain-insert-visualize-button selection 'org-brain-selected-list)) + (insert "\n"))) + + #+end_src + +*** org-brain--hist-entries-to-draw + #+begin_src emacs-lisp + + (defun org-brain--hist-entries-to-draw (max-width hist width to-draw) + "Determines the entries in HIST that can fit on a line of MAX-WIDTH. + Returns those entries in reversed order. + WIDTH and TO-DRAW are state parameters. + WIDTH represents the width of the line comprising the elements in TO-DRAW. + Assumes elements will be drawn with a two-character padding between them. + Helper function for `org-brain--vis-history'." + (if (null hist) + to-draw + (let* ((entry-title-width (string-width (org-brain-vis-title (car hist)))) + (new-line-width (+ width 2 entry-title-width))) + (if (and (<= max-width new-line-width) + (not (null to-draw))) ; Always display at least one entry + to-draw + (org-brain--hist-entries-to-draw max-width (cdr hist) new-line-width (cons (car hist) to-draw)))))) + + #+end_src + +*** org-brain--vis-history + #+begin_src emacs-lisp + + (defun org-brain--vis-history () + "Show as many of the most recently visited entries as fit on one line. + Helper function for `org-brain-visualize'." + (insert "HISTORY:") + (dolist (entry (org-brain--hist-entries-to-draw (window-width) org-brain--vis-history (string-width "HISTORY:") nil)) + (insert " ") + (org-brain-insert-visualize-button entry 'org-brain-history-list 'history)) + (insert "\n")) + + #+end_src + +*** org-brain--insert-wire + #+begin_src emacs-lisp + + (defun org-brain--insert-wire (&rest strings) + "Helper function for drawing fontified wires in the org-brain visualization buffer." + (insert (propertize (apply 'concat strings) 'face 'org-brain-wires))) + + #+end_src + +*** org-brain--vis-parents-siblings + #+begin_src emacs-lisp + + (defun org-brain--vis-parents-siblings (entry) + "Insert parents and siblings of ENTRY. + Helper function for `org-brain-visualize'." + (when-let ((siblings (org-brain--visually-sorted-siblings entry))) + (let ((parent-positions nil) + (max-width 0)) + (dolist (parent siblings) + (let* ((children-links (org-brain--visually-sorted-siblings-from parent)) + (sibling-middle (ceiling (/ (length children-links) 2.0))) + (base-line (if org-brain-show-history 5 4)) + (col-start (+ 3 max-width)) + (parent-width (string-width (org-brain-vis-title (car parent))))) + (org-goto-line base-line) + (mapc + (lambda (child) + (picture-forward-column col-start) + (org-brain--insert-wire (make-string (1+ parent-width) ?\ ) "+-") + (org-brain-insert-visualize-button + child + (if (and (member (car parent) (org-brain-local-parent child)) + (member (car parent) (org-brain-local-parent entry))) + 'org-brain-local-sibling + 'org-brain-sibling) 'sibling) + (setq max-width (max max-width (current-column))) + (newline (forward-line 1))) + children-links) + (org-goto-line base-line) + (forward-line (1- sibling-middle)) + (picture-forward-column col-start) + (push (cons (picture-current-line) + (+ (current-column) (/ parent-width 2))) + parent-positions) + (org-brain-insert-visualize-button + (car parent) + (if (member (car parent) (org-brain-local-parent entry)) + 'org-brain-local-parent + 'org-brain-parent) 'parent) + (setq max-width (max max-width (current-column))) + (when children-links + (org-brain--insert-wire "-") + (delete-char (+ 1 parent-width))))) + ;; Draw lines + (when parent-positions + (let ((maxline (line-number-at-pos (point-max)))) + ;; Bottom line + (org-goto-line maxline) + (picture-forward-column (cdar (last parent-positions))) + (picture-move-down 1) + (org-brain--insert-wire (make-string (1+ (- (cdar parent-positions) + (cdar (last parent-positions)))) + ?-)) + ;; Lines from parents to bottom + (dolist (pos parent-positions) + (org-goto-line (car pos)) + (picture-forward-column (cdr pos)) + (while (< (line-number-at-pos (point)) + maxline) + (picture-move-down 1) + (org-brain--insert-wire "|") + (unless (looking-at-p "\n") (delete-char 1))) + (picture-move-down 1) + (ignore-errors + (delete-char 1)) + (org-brain--insert-wire "+")) + ;; Line to main entry + (move-to-column (/ (+ (cdar (last parent-positions)) + (cdar parent-positions)) + 2) + t) + (delete-char 1) + (when (> (length parent-positions) 1) + (org-brain--insert-wire "+") + (backward-char 1) + (picture-move-down 1) + (org-brain--insert-wire "|") + (picture-move-down 1)) + (org-brain--insert-wire "V")))) + (picture-move-down 1))) + + #+end_src + +*** org-brain--vis-children + #+begin_src emacs-lisp + + (defun org-brain--vis-children (entry) + "Insert children of ENTRY. + Helper function for `org-brain-visualize'." + (let ((tags (org-brain-get-tags entry t))) + (when-let ((children (org-brain--visually-sorted-children entry)) + (fill-col (if (member org-brain-each-child-on-own-line-tag + (org-brain-get-tags entry)) + 0 + (eval org-brain-child-linebreak-sexp)))) + (insert "\n\n") + (dolist (child children) + (let ((child-title (org-brain-title child)) + (face (if (member entry (org-brain-local-parent child)) + 'org-brain-local-child + 'org-brain-child))) + (when (> (+ (current-column) (length child-title)) fill-col) + (insert "\n")) + (org-brain-insert-visualize-button child face 'child) + (insert " ")))))) + + #+end_src + +*** org-brain--vis-friends + #+begin_src emacs-lisp + + (defun org-brain--vis-friends (entry) + "Insert friends of ENTRY. + Helper function for `org-brain-visualize'." + (when-let ((friends (org-brain--visually-sorted-friends entry))) + (org-brain--insert-wire " <-> ") + (dolist (friend friends) + (let ((column (current-column))) + (org-brain-insert-visualize-button friend 'org-brain-friend 'friend) + (picture-move-down 1) + (move-to-column column t))) + (org-brain-delete-current-line) + (backward-char 1))) + + #+end_src + +*** org-brain--vis-resources + #+begin_src emacs-lisp + + (defun org-brain--vis-resources (resources) + "Insert links to RESOURCES. + Helper function for `org-brain-visualize'." + (when resources + (insert "\n\n--- Resources ---------------------------------\n") + (mapc #'org-brain-insert-resource-button resources))) + + #+end_src + +*** org-brain--vis-entry-text-marker + #+begin_src emacs-lisp + + (defvar-local org-brain--vis-entry-text-marker 0 + "Marker to where `org-brain-text' begins in `org-brain-visualize-mode'.") + + #+end_src + +*** org-brain--vis-text + #+begin_src emacs-lisp + + (defun org-brain--vis-text (entry) + "Insert text of ENTRY. + Helper function for `org-brain-visualize'." + (if-let ((text (org-brain-text entry))) + (progn + (setq text (string-trim text)) + (if (or (boundp 'org-brain-polymode) + org-brain-show-full-entry + (> (length text) 0)) + (progn + (insert "\n\n") + (setq org-brain--vis-entry-text-marker (point-marker)) + (insert "--- Entry -------------------------------------\n\n") + (run-hooks 'org-brain-after-visualize-hook) + (insert (with-temp-buffer + (insert text) + (delay-mode-hooks + (org-mode) + (setq-local org-pretty-entities t) + (font-lock-ensure (point-min) (point-max)) + (buffer-string)))) + (run-hooks 'org-brain-visualize-text-hook)) + (run-hooks 'org-brain-after-visualize-hook))) + (run-hooks 'org-brain-after-visualize-hook))) + + #+end_src + +** Mind-map + #+begin_src emacs-lisp + + ;;; Mind-map + + #+end_src + +*** org-brain-map-create-indentation + #+begin_src emacs-lisp + + (defun org-brain-map-create-indentation (level) + "Return a string of spaces, length determined by indentation LEVEL." + (make-string (* level 2) ? )) + + #+end_src + +*** org-brain-insert-recursive-child-buttons + #+begin_src emacs-lisp + + (defun org-brain-insert-recursive-child-buttons (entry max-level indent) + "Use `org-brain-insert-visualize-button' on ENTRY and its children. + Also insert buttons for grand-children, up to MAX-LEVEL. + Each button is indented, starting at level determined by INDENT." + (insert (org-brain-map-create-indentation indent)) + (org-brain-insert-visualize-button entry 'org-brain-child (if (> max-level 0) 'grandchild 'child)) + (insert "\n") + (dolist (child (and (> max-level 0) + (org-brain--visually-sorted-children entry))) + (org-brain-insert-recursive-child-buttons child (1- max-level) (1+ indent)))) + + #+end_src + +*** org-brain-tree-depth + #+begin_src emacs-lisp + + (defun org-brain-tree-depth (tree) + "Return depth of nested TREE." + (if (atom tree) + 0 + (1+ (cl-reduce #'max (mapcar #'org-brain-tree-depth tree))))) + + #+end_src + +*** org-brain-recursive-parents + #+begin_src emacs-lisp + + (defun org-brain-recursive-parents (entry max-level &optional func) + "Return a tree of ENTRY and its (grand)parents, up to MAX-LEVEL. + Apply FUNC to each tree member. FUNC is a function which takes an + entry as the only argument. If FUNC is nil or omitted, get the + raw entry data." + (cons (funcall (or func #'identity) entry) + (when (> max-level 0) + (mapcar (lambda (x) (org-brain-recursive-parents x (1- max-level) func)) + (org-brain-parents entry))))) + + #+end_src + +*** org-brain-recursive-children + #+begin_src emacs-lisp + + (defun org-brain-recursive-children (entry max-level &optional func) + "Return a tree of ENTRY and its (grand)children up to MAX-LEVEL. + Apply FUNC to each tree member. FUNC is a function which takes an + entry as the only argument. If FUNC is nil or omitted, get the + raw entry data." + (cons (funcall (or func #'identity) entry) + (when (> max-level 0) + (mapcar (lambda (x) (org-brain-recursive-children x (1- max-level) func)) + (org-brain-children entry))))) + + #+end_src + +*** org-brain-insert-recursive-parent-buttons + #+begin_src emacs-lisp + + (defun org-brain-insert-recursive-parent-buttons (entry max-level indent) + "Use `org-brain-insert-visualize-button' on ENTRY and its parents. + Also insert buttons for grand-parents, up to MAX-LEVEL. + Each button is indented, starting at level determined by INDENT." + (dolist (parent (and (> max-level 0) + (org-brain--visually-sorted-parents entry))) + (org-brain-insert-recursive-parent-buttons parent (1- max-level) (1- indent))) + (insert (org-brain-map-create-indentation indent)) + (org-brain-insert-visualize-button entry 'org-brain-parent (if (> max-level 0) 'grandparent 'parent)) + (insert "\n")) + + #+end_src + +*** org-brain-mind-map + #+begin_src emacs-lisp + + (defun org-brain-mind-map (entry parent-max-level children-max-level) + "Insert a tree of buttons for the parents and children of ENTRY. + Insert friends to ENTRY in a row above the tree. + Will also insert grand-parents up to PARENT-MAX-LEVEL, and + children up to CHILDREN-MAX-LEVEL. + Return the position of ENTRY in the buffer." + (insert "FRIENDS:") + (dolist (friend (org-brain--visually-sorted-friends entry)) + (insert " ") + (org-brain-insert-visualize-button friend 'org-brain-friend 'friend)) + (setq-local org-brain--visualize-header-end-pos (point)) + (insert "\n\n") + (let ((indent (1- (org-brain-tree-depth (org-brain-recursive-parents entry parent-max-level)))) + (entry-pos)) + (dolist (parent (org-brain--visually-sorted-siblings entry)) + (org-brain-insert-recursive-parent-buttons (car parent) (1- parent-max-level) (1- indent)) + (dolist (sibling (org-brain--visually-sorted-siblings-from parent)) + (insert (org-brain-map-create-indentation indent)) + (org-brain-insert-visualize-button sibling 'org-brain-sibling 'sibling) + (insert "\n"))) + (insert (org-brain-map-create-indentation indent)) + (setq entry-pos (point)) + (insert (propertize (org-brain-title entry) + 'face 'org-brain-title + 'aa2u-text t) "\n") + (dolist (child (org-brain--visually-sorted-children entry)) + (org-brain-insert-recursive-child-buttons child (1- children-max-level) (1+ indent))) + entry-pos)) + + #+end_src + +*** org-brain-visualizing-mind-map + #+begin_src emacs-lisp + + (defvar-local org-brain-visualizing-mind-map nil) + + #+end_src + +*** org-brain-mind-map-child-level + #+begin_src emacs-lisp + + (defvar-local org-brain-mind-map-child-level 1) + + #+end_src + +*** org-brain-mind-map-parent-level + #+begin_src emacs-lisp + + (defvar-local org-brain-mind-map-parent-level 1) + + #+end_src + +*** org-brain-visualize-mind-map + #+begin_src emacs-lisp + + (defun org-brain-visualize-mind-map () + "Toggle mind-map view of `org-brain-visualize'." + (interactive) + (when (eq major-mode 'org-brain-visualize-mode) + (setq org-brain-visualizing-mind-map (not org-brain-visualizing-mind-map)) + (org-brain-visualize org-brain--vis-entry))) + + #+end_src + +** Show/hide nested levels +*** org-brain-show-descendant-level + #+begin_src emacs-lisp + + ;;; Show/hide nested levels + (defun org-brain-show-descendant-level () + "Show one more level of descendant entries to the right in the mind-map visualization buffer." + (interactive) + (setq org-brain-visualizing-mind-map t) + (cl-incf org-brain-mind-map-child-level) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** org-brain-hide-descendant-level + #+begin_src emacs-lisp + + (defun org-brain-hide-descendant-level () + "Hide the rightmost level of descendant entries in the mind-map visualization buffer." + (interactive) + (setq org-brain-visualizing-mind-map t) + (when (> org-brain-mind-map-child-level 1) + (cl-decf org-brain-mind-map-child-level)) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** org-brain-show-ancestor-level + #+begin_src emacs-lisp + + (defun org-brain-show-ancestor-level () + "Show one more level of ancestor entries to the left in the mind-map visualization buffer." + (interactive) + (setq org-brain-visualizing-mind-map t) + (cl-incf org-brain-mind-map-parent-level) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** org-brain-hide-ancestor-level + #+begin_src emacs-lisp + + (defun org-brain-hide-ancestor-level () + "Hide the leftmost level of ancestor entries in the mind-map visualization buffer." + (interactive) + (setq org-brain-visualizing-mind-map t) + (when (> org-brain-mind-map-parent-level 1) + (cl-decf org-brain-mind-map-parent-level)) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** obsolete function aliases + #+begin_src emacs-lisp + + (define-obsolete-function-alias + 'org-brain-visualize-add-grandchild 'org-brain-show-descendant-level "0.5") + (define-obsolete-function-alias + 'org-brain-visualize-remove-grandchild 'org-brain-hide-descendant-level "0.5") + (define-obsolete-function-alias + 'org-brain-visualize-add-grandparent 'org-brain-show-ancestor-level "0.5") + (define-obsolete-function-alias + 'org-brain-visualize-remove-grandparent 'org-brain-hide-ancestor-level "0.5") + + #+end_src + +** Polymode + #+begin_src emacs-lisp + + ;;; Polymode + + ;; This code has been adapted from Dustin Lacewell's project polybrain + ;; Have a look at: https://github.com/dustinlacewell/polybrain.el/ + + (with-eval-after-load "polymode" + (define-hostmode org-brain-poly-hostmode + :mode 'org-brain-visualize-mode) + + #+end_src + +** org-brain-poly-innermode + + #+begin_src emacs-lisp + + (define-innermode org-brain-poly-innermode + :mode 'org-mode + :head-matcher "^[─-]\\{3\\} Entry [─-]+\n" + :tail-matcher "\\'" + :head-mode 'host + :tail-mode 'host) + + #+end_src + +** org-brain-polymode + + #+begin_src emacs-lisp + + (define-polymode org-brain-polymode + :hostmode 'org-brain-poly-hostmode + :innermodes '(org-brain-poly-innermode) + (setq-local polymode-move-these-vars-from-old-buffer + (delq 'buffer-read-only polymode-move-these-vars-from-old-buffer))) + + #+end_src + +*** org-brain-polymode-save + #+begin_src emacs-lisp + + (defun org-brain-polymode-save () + "Save entry text to the entry's file." + (interactive) + (when (buffer-modified-p) + (let ((text (save-excursion + (goto-char org-brain--vis-entry-text-marker) + (end-of-line) + (buffer-substring (point) (point-max))))) + (find-file (org-brain-entry-path org-brain--vis-entry)) + (seq-let (entry-min entry-max) (org-brain-text-positions org-brain--vis-entry) + (goto-char entry-min) + (delete-region entry-min entry-max) + (insert text) + (unless (looking-at-p "\n") + (insert "\n\n")) + (save-buffer) + (switch-to-buffer (other-buffer (current-buffer) 1)) + (set-buffer-modified-p nil))))) + + (define-key org-brain-polymode-map "\C-x\C-s" 'org-brain-polymode-save)) + + #+end_src + +** Brain link + #+begin_src emacs-lisp + + ;;; Brain link + + #+end_src + +*** org-brain-link-complete + #+begin_src emacs-lisp + + (defun org-brain-link-complete (&optional link-type) + "Create an org-link target string to a file in `org-brain-path'. + LINK-TYPE will be \"brain\" by default." + (setq link-type (or link-type "brain")) + (let* ((entry (ignore-errors (org-brain-entry-at-pt t))) + (choice (if (and (not entry) + (member link-type + (list org-brain-child-link-name + org-brain-parent-link-name + org-brain-friend-link-name))) + (error "No entry at point") + (org-brain-choose-entry "Entry: " 'all)))) + (cond ((string-equal link-type org-brain-child-link-name) + (org-brain-add-relationship entry choice)) + ((string-equal link-type org-brain-parent-link-name) + (org-brain-add-relationship choice entry)) + ((string-equal link-type org-brain-friend-link-name) + (org-brain--internal-add-friendship entry choice)) + ((and org-brain-backlink (string-equal link-type "brain")) + (if entry + (org-brain-add-resource + (concat "brain:" (org-brain-entry-identifier entry)) + (concat (and (stringp org-brain-backlink) org-brain-backlink) + (org-brain-title entry)) + nil choice) + (org-brain-add-resource + (cl-concatenate 'string + "file:" + (file-relative-name + (buffer-file-name) + (file-name-directory (org-brain-entry-path choice))) + (if-let ((outline-path + (and org-brain-backlink-heading + (ignore-errors (org-get-outline-path t))))) + (concat "::* " (nth 0 (last outline-path))))) + (concat (and (stringp org-brain-backlink) org-brain-backlink) + (if (and org-brain-backlink-heading + (ignore-errors (org-get-outline-path t))) + (string-join (org-get-outline-path t) " > ") + (file-name-base))) + nil choice)))) + (let ((link (concat link-type ":" + (if (org-brain-filep choice) choice (nth 2 choice))))) + (if (version< (org-release) "9.3") + (push link org-insert-link-history) + (push link org-link--insert-history)) + (push `(,link ,(org-brain-title choice)) org-stored-links) + link))) + + #+end_src + +*** org-brain-link-store + #+begin_src emacs-lisp + + (defun org-brain-link-store () + "Store a brain: type link from an `org-brain-visualize-mode' buffer." + (when (eq major-mode 'org-brain-visualize-mode) + (org-store-link-props + :type "brain" + :link (concat "brain:" (org-brain-entry-identifier org-brain--vis-entry)) + :description (org-brain-title org-brain--vis-entry)))) + + #+end_src + +*** setting org-link parameters + #+begin_src emacs-lisp + + (org-link-set-parameters "brain" + :complete 'org-brain-link-complete + :follow 'org-brain-goto + :store 'org-brain-link-store) + + (org-link-set-parameters org-brain-child-link-name + :complete (lambda () (org-brain-link-complete org-brain-child-link-name)) + :follow 'org-brain-goto) + + (org-link-set-parameters org-brain-parent-link-name + :complete (lambda () (org-brain-link-complete org-brain-parent-link-name)) + :follow 'org-brain-goto) + + (org-link-set-parameters org-brain-friend-link-name + :complete (lambda () (org-brain-link-complete org-brain-friend-link-name)) + :follow 'org-brain-goto) + + #+end_src + +** brain switch link + #+begin_src emacs-lisp + + ;;; Brain switch link + + #+end_src + +*** org-brain--switch-link-complete + #+begin_src emacs-lisp + + (defun org-brain--switch-link-complete () + "Create an org-link target string to an org-brain and one of its entries." + (let* ((org-brain-path (read-directory-name "Brain dir: " org-brain-path)) + (entry (org-brain-choose-entry + "Entry: " (append (when org-brain-include-file-entries (org-brain-files t)) + (org-brain-headline-entries))))) + (concat "brainswitch:" org-brain-path + "::" + (if (org-brain-filep entry) + entry + (nth 2 entry))))) + + #+end_src + +*** org-brain--switch-and-visualize + #+begin_src emacs-lisp + + (defun org-brain--switch-and-visualize (directory entry) + "Switch brain to DIRECTORY and visualize ENTRY. + ENTRY should be a string; an id in the case of an headline entry." + (org-brain-switch-brain directory) + (org-brain-visualize (or (org-brain-entry-from-id entry) entry))) + + #+end_src + +*** org-brain--switch-link-follow + #+begin_src emacs-lisp + + (defun org-brain--switch-link-follow (link) + "Follow function for brainswitch links." + (let ((link-parts (split-string link "::"))) + (org-brain--switch-and-visualize (car link-parts) + (cadr link-parts)))) + + #+end_src + +*** org-link-set-parameters brainswitch + #+begin_src emacs-lisp + + (org-link-set-parameters "brainswitch" + :complete 'org-brain--switch-link-complete + :follow 'org-brain--switch-link-follow) + + #+end_src + +** Helm integration + #+begin_src emacs-lisp + + ;;; Helm integration + + (with-eval-after-load "helm" + + #+end_src + +*** helm-brain--add-children + #+begin_src emacs-lisp + + (defun helm-brain--add-children (_c) + (dolist (candidate (helm-marked-candidates)) + (org-brain-add-relationship + (org-brain-entry-at-pt) (or (org-brain-entry-from-id candidate) candidate))) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** helm-brain--add-parents + #+begin_src emacs-lisp + + (defun helm-brain--add-parents (_c) + (dolist (candidate (helm-marked-candidates)) + (org-brain-add-relationship + (or (org-brain-entry-from-id candidate) candidate) (org-brain-entry-at-pt))) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** helm-brain--add-friends + #+begin_src emacs-lisp + + (defun helm-brain--add-friends (_c) + (dolist (candidate (helm-marked-candidates)) + (org-brain--internal-add-friendship + (org-brain-entry-at-pt) (or (org-brain-entry-from-id candidate) candidate))) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** helm-brain--delete-entries + #+begin_src emacs-lisp + + (defun helm-brain--delete-entries (_c) + (dolist (candidate (helm-marked-candidates)) + (org-brain-delete-entry (or (org-brain-entry-from-id candidate) candidate)))) + + #+end_src + +*** helm-brain--archive + #+begin_src emacs-lisp + + (defun helm-brain--archive (_c) + (dolist (candidate (helm-marked-candidates)) + (org-brain-archive (or (org-brain-entry-from-id candidate) candidate)))) + + #+end_src + +*** helm-brain--select + #+begin_src emacs-lisp + + (defun helm-brain--select (_c) + (dolist (candidate (helm-marked-candidates)) + (org-brain-select (or (org-brain-entry-from-id candidate) candidate) 1))) + + #+end_src + +*** helm-brain--unselect + #+begin_src emacs-lisp + + (defun helm-brain--unselect (_c) + (dolist (candidate (helm-marked-candidates)) + (org-brain-select (or (org-brain-entry-from-id candidate) candidate) -1))) + + #+end_src + +*** helm-brain--actions + #+begin_src emacs-lisp + + (defvar-local elm-brain--actions + (helm-make-actions + "Visualize" (lambda (x) + (org-brain-visualize (or (org-brain-entry-from-id x) x))) + "Add children" 'helm-brain--add-children + "Add parents" 'helm-brain--add-parents + "Add friends" 'helm-brain--add-friends + "Delete" 'helm-brain--delete-entries + "Archive" 'helm-brain--archive + "Select" 'helm-brain--select + "Unselect" 'helm-brain--unselect)) + + #+end_src + +*** helm-brain--source + + #+begin_src emacs-lisp + + (defvar-local elm-brain--source + (helm-make-source "Brain" 'helm-source-sync + :candidates #'org-brain--all-targets + :action 'helm-brain--actions)) + + #+end_src + +*** helm-brain--fallback-source + + #+begin_src emacs-lisp + + (defvar-local elm-brain--fallback-source + (helm-make-source "New entry" 'helm-source-dummy + :action (helm-make-actions + "Visualize" (lambda (x) + (org-brain-visualize (org-brain-get-entry-from-title x))) + "Add children" 'helm-brain--add-children + "Add parents" 'helm-brain--add-parents + "Add friends" 'helm-brain--add-friends))) + + #+end_src + +*** helm-brain + #+begin_src emacs-lisp + + (defun helm-brain () + "Use `helm' to choose among your org-brain entries. + Provides actions for visualizing, adding/removing relations, etc. + Supports selecting multiple entries at once." + (interactive) + (helm :sources '(helm-brain--source helm-brain--fallback-source)))) + + #+end_src + +** Ivy integration + #+begin_src emacs-lisp + ;;; Ivy integration + + (with-eval-after-load "ivy" + + #+end_src +** counsel-brain + #+begin_src emacs-lisp + + (defun counsel-brain () + "Use Ivy to choose among your org-brain entries. + Provides actions for visualizing, adding/removing relations, etc." + (interactive) + (let ((targets (org-brain--all-targets))) + (ivy-read "Org-brain: " + targets + :action (lambda (x) + (org-brain-visualize + (if (stringp x) + (org-brain-get-entry-from-title x) + (or (org-brain-entry-from-id (cdr x)) + (cdr x))))) + :preselect (ignore-errors + (org-brain-entry-name + (org-brain-entry-at-pt))) + :caller 'counsel-brain))) + + #+end_src +*** counsel-brain--add-child + #+begin_src emacs-lisp + + (defun counsel-brain--add-child (child) + (org-brain-add-relationship (org-brain-entry-at-pt) + (or (org-brain-entry-from-id (cdr child)) + (cdr child))) + (org-brain--revert-if-visualizing)) + + #+end_src +*** counsel-brain--add-parent + #+begin_src emacs-lisp + + (defun counsel-brain--add-parent (parent) + (org-brain-add-relationship (or (org-brain-entry-from-id (cdr parent)) + (cdr parent)) + (org-brain-entry-at-pt)) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** counsel-brain--add-friend + #+begin_src emacs-lisp + + (defun counsel-brain--add-friend (friend) + (org-brain--internal-add-friendship (org-brain-entry-at-pt) + (or (org-brain-entry-from-id (cdr friend)) + (cdr friend))) + (org-brain--revert-if-visualizing)) + + #+end_src + +*** counsel-brain--delete + #+begin_src emacs-lisp + + (defun counsel-brain--delete (x) + (org-brain-delete-entry (or (org-brain-entry-from-id (cdr x)) (cdr x)))) + + #+end_src + +*** counsel-brain--archive + #+begin_src emacs-lisp + + (defun counsel-brain--archive (x) + (org-brain-archive (or (org-brain-entry-from-id (cdr x)) (cdr x)))) + + #+end_src + +*** counsel-brain--select + #+begin_src emacs-lisp + + (defun counsel-brain--select (x) + (org-brain-select (or (org-brain-entry-from-id (cdr x)) (cdr x)) 1)) + + #+end_src + +*** counsel-brain--unselect + #+begin_src emacs-lisp + + (defun counsel-brain--unselect (x) + (org-brain-select (or (org-brain-entry-from-id (cdr x)) (cdr x)) -1)) + + #+end_src + +*** ivy set actions + #+begin_src emacs-lisp + + (ivy-set-actions + 'counsel-brain + '(("c" counsel-brain--add-child "add as child") + ("p" counsel-brain--add-parent "add as parent") + ("f" counsel-brain--add-friend "add as friend") + ("d" counsel-brain--delete "delete") + ("a" counsel-brain--archive "archive") + ("s" counsel-brain--select "select") + ("S" counsel-brain--unselect "unselect")))) + + #+end_src + +* Winding up + #+begin_src emacs-lisp + + ;;; winding up + + (provide 'org-brain) + + ;;; org-brain.el ends here + + #+end_src