├── LICENSE.org ├── README.org └── org-brain.el /LICENSE.org: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Erik Sjöstrand 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+TITLE:org-brain [[http://melpa.org/#/org-brain][file:http://melpa.org/packages/org-brain-badge.svg]] 2 | 3 | =org-brain= implements a variant of [[https://en.wikipedia.org/wiki/Concept_map][concept mapping]] in Emacs, using [[http://orgmode.org/][org-mode]]. It 4 | is heavily inspired by a piece of software called [[http://thebrain.com/][The Brain]], and you can view an 5 | introduction to that program [[https://www.youtube.com/watch?v=GFqLUBKCFdA][here]]. They also provide [[https://www.thebrain.com/blog/][a blog]] with great ideas of 6 | how you can think when organizing your Brain. 7 | 8 | You can think of =org-brain= as a combination of a wiki and a mind map, where 9 | each wiki page / mind map node is an =org-mode= file which resides in your 10 | =org-brain-path=, or a headline with an ID property in one of those files. These 11 | are called /entries/. Entries can be linked together, and you can then 12 | view the network of links as a mind map, using =M-x org-brain-visualize=. Here's [[https://www.youtube.com/watch?v=3EGOwfWok5s&t=][a video introducing =org-brain=]]. 13 | 14 | #+BEGIN_EXAMPLE 15 | PINNED: Index 16 | 17 | 18 | +-Python Game development-+-Game design 19 | +-Programming books | 20 | Programming-+-Emacs | 21 | | | 22 | +-----------------+-----------------+ 23 | | 24 | V 25 | Game programming <-> Computer games 26 | 27 | Game Maker Unity 28 | 29 | --- Resources --------------------------------- 30 | 31 | - https://en.wikipedia.org/wiki/Game_programming 32 | - Passing Through Ghosts in Pac-Man 33 | - In-House Engine Development: Technical Tips 34 | 35 | --- Text -------------------------------------- 36 | 37 | Game programming is the art of programming computer games... 38 | #+END_EXAMPLE 39 | 40 | When visualizing an entry, you will see the entry's relationship to other 41 | entries. There are four different types of relationships in =org-brain=: 42 | 43 | - Parents :: Entries above the visualized entry. If the visualized entry is a 44 | headline, then the parent headline in the =org-mode= file will be 45 | one of the parents. In the case of top level headlines, the file 46 | itself will be considered a parent. Additional parents can be added 47 | manually. In the example above, /Programming/ and /Game 48 | development/ are parents of the visualized /Game programming/ 49 | entry. 50 | - Children :: Entries below the visualized entry. This will by default be 51 | subheadings of the visualized entry (or level one headlines, if 52 | the visualized entry is a file). You can add other children, 53 | residing elsewhere, manually. In the example above, /Game Maker/ 54 | and /Unity/ are the children of /Game programming/. 55 | - Siblings :: These appear to the right of the parent entries. Siblings are the 56 | other children of the visualized entry's parents. 57 | - Friends :: These appear to the right of the visualized entry. Friends provide 58 | a way of adding a hierarchy independent two-way relationship 59 | between two entries. Friends must be added manually. In the example 60 | above, /Computer games/ and /Game programming/ are friends. 61 | 62 | [[http://blogarchive.thebrain.com/thought-relationships/][Here's an article]] describing how you can use the different relationships (The 63 | Brain's /jump thoughts/ are the equivalent of /friends/ in =org-brain=). 64 | 65 | Apart from the visualized entry's relationships, =org-brain-visualize= also show 66 | pinned entries, which are shown independently of the visualized entry; /Index/ 67 | is a pinned entry in the example above. =org-brain-visualize= also show a list 68 | of the entry's resources (links and attachments), and the text in the entry. The 69 | example above have three resources, and a short text. The resources and text is 70 | gathered from =org-mode= automatically. 71 | 72 | There's also the option to visualize the entry as a tree, or similar to a 73 | mind map, where you can zoom in order to show grandparents and grandchildren. 74 | 75 | The relationship entries, pinned entries and resources are all links; they can 76 | be pressed/clicked to visualize other entries, visit resources etc. 77 | 78 | You can also annotate the connection between the visualized entry and one of the 79 | other entries. You can think of it as annotating the edge between two nodes in a 80 | graph. Annotations will show up in the mini-buffer when hovering over an 81 | annotated connection. 82 | 83 | * Setup and requirements 84 | 85 | The easiest way is to get =org-brain= from MELPA. If you do not want to do that, 86 | clone this git repository or download =org-brain.el= and add it to your load-path. 87 | The example below is using [[https://github.com/jwiegley/use-package][use-package]] and assumes that you're using MELPA, but 88 | you could use =(require 'org-brain)= or add a =:load-path= to =use-package= instead. 89 | Most of the configuration below isn't necessary, but showcases some options. 90 | 91 | #+BEGIN_SRC emacs-lisp 92 | (use-package org-brain :ensure t 93 | :init 94 | (setq org-brain-path "directory/path/where-i-want-org-brain") 95 | ;; For Evil users 96 | (with-eval-after-load 'evil 97 | (evil-set-initial-state 'org-brain-visualize-mode 'emacs)) 98 | :config 99 | (bind-key "C-c b" 'org-brain-prefix-map org-mode-map) 100 | (setq org-id-track-globally t) 101 | (setq org-id-locations-file "~/.emacs.d/.org-id-locations") 102 | (add-hook 'before-save-hook #'org-brain-ensure-ids-in-buffer) 103 | (push '("b" "Brain" plain (function org-brain-goto-end) 104 | "* %i%?" :empty-lines 1) 105 | org-capture-templates) 106 | (setq org-brain-visualize-default-choices 'all) 107 | (setq org-brain-title-max-length 12) 108 | (setq org-brain-include-file-entries nil 109 | org-brain-file-entries-use-title nil)) 110 | 111 | ;; Allows you to edit entries directly from org-brain-visualize 112 | (use-package polymode 113 | :config 114 | (add-hook 'org-brain-visualize-mode-hook #'org-brain-polymode)) 115 | #+END_SRC 116 | 117 | 1. =org-brain= requires Emacs 25 and org-mode 9. These need to be part of your 118 | Emacs. 119 | 2. Configure =org-brain-path= (defaults to =/brain= in your =org-directory=) to 120 | a directory where you want to put your =org-brain= files (which could be the 121 | location where you already keep your org files if you wish to transform your 122 | existing org files into =org-brain= files). You can set this with the example 123 | config presented above or through =M-x customize-group RET org-brain=. 124 | 3. If you're an [[https://github.com/emacs-evil/evil][evil]] user, you'll want to add =(evil-set-initial-state 125 | 'org-brain-visualize-mode 'emacs)= to your =org-brain= configuration. 126 | 4. =org-brain= use =org-id= in order to speed things up. Because of this, the 127 | variable =org-id-track-globally= should be =t= (which it already is by default). 128 | You may want to modify =org-id-locations-file= too. If you add entries to 129 | =org-brain= directly from =org-mode= you must assign headlines an ID. A 130 | comfortable way to do this is with the command 131 | =org-brain-ensure-ids-in-buffer=. Even more comfortable is to add that to 132 | =before-save-hook=, so that it runs when saving. 133 | 5. =org-brain-prefix-map= can be bound to a key to make =org-brain= commands more 134 | accessable if you edit entries from =org-mode=. See /Editing from org-mode/ under 135 | /Usage/ below. 136 | 6. You might want to add information at the end of an entry, without visiting 137 | the file. A way to do this is to use a [[http://orgmode.org/manual/Capture.html][capture]] template, such as the one 138 | presented above. 139 | 7. If you have a lot of entries, it might take some time to gather information 140 | about all entries when using =org-brain-visualize=. You could change the 141 | value of =org-brain-visualize-default-choices= (which is ='all= by default) 142 | to only include files, or even just files in the direct root of 143 | =org-brain-path=. 144 | 8. If you feel that =org-brain-visualize= is too cluttered, you may want to set 145 | =org-brain-show-resources= and/or =org-brain-show-text= to =nil=. 146 | 9. If you have very long entry names, =org-brain-visualize= may take a lot of 147 | horizontal space. You can cap the shown length of entry titles, by setting 148 | =org-brain-title-max-length=. 149 | 10. Some users find it confusing having both /headline/ entries and /file/ entries 150 | (see below). It may be preferable to only use headline entries, by setting 151 | =org-brain-include-file-entries= to =nil=. If doing this, you should probably 152 | also set =org-brain-file-entries-use-title= to =nil=. Another possibility is if 153 | you're only using file entries, in which case you can set 154 | =org-brain-scan-for-header-entries= to =nil=. 155 | 11. =polymode= is a package (available on MELPA) which allows for several 156 | major-modes in the same buffer. If you have required the package you can use 157 | =M-x org-brain-polymode= inside =org-brain-visualize=, or (as in the example 158 | above) add =org-brain-polymode= to =org-brain-visualize-mode-hook=. 159 | 160 | ** Category icons 161 | 162 | =org-brain= supports showing icons for your entries, depending on their [[https://orgmode.org/manual/Categories.html][category]]. Use the variable =org-agenda-category-icon-alist= to specify icons for categories. 163 | 164 | See example of using /all-the-icons/ for this below under /Other useful packages/. 165 | 166 | ** Customizing the look of entry titles 167 | 168 | When visualizing you might want to see additional information about the entries. This can be done by customizing the following variables: 169 | 170 | - =org-brain-vis-title-prepend-functions= 171 | - =org-brain-vis-title-append-functions= 172 | - =org-brain-vis-current-title-prepend-functions= 173 | - =org-brain-vis-current-title-append-functions= 174 | 175 | Each of these variables should be a list of functions, which all takes an entry as the single parameter and returns a string. This string is the prepended or appended to the entry's title, according to the name of the function. The variables with the name =current= in them only applies the functions on the currently visualized entry (the focused one). 176 | 177 | Suitable functions to add to these lists might be: 178 | 179 | - =org-brain-entry-icon= (included in =org-brain-vis-title-prepend-functions= by default) 180 | - =org-brain-entry-todo-state= 181 | - =org-brain-entry-tags-string= 182 | 183 | * Headline and file entries 184 | 185 | There are two types of entries in =org-brain=: /headline/ entries and /file/ 186 | entries. For the most part these are used the same way, and the main difference 187 | between them is how their content is stored inside your =org-brain= directory. 188 | All .org-files inside the =org-brain-path= are considered as /file/ entries (the 189 | content typically being the text before the first headline in the file) and all 190 | headlines /with an ID property/ inside these files are considered as /headline/ 191 | entries. 192 | 193 | By default subdirectories inside =org-brain-path= are scanned recursively for 194 | files, so all subdirectories and their files are considered part of the brain. 195 | You can choose to only have the root of =org-brain-path= be scanned for files, by 196 | setting =org-brain-scan-directories-recursively= to =nil=. 197 | 198 | If you have a headline entry, which you want to convert to a file entry, use =M-x 199 | org-brain-headline-to-file=. Unfortunately there is currently no function to 200 | convert a file entry into a headline entry. 201 | 202 | ** Limiting =org-brain= to headline entries 203 | 204 | Most of =org-mode= is tailored towards working with headlines, and because of this 205 | =org-brain= has some limitations regarding what's possible with /file entries/. The 206 | concept of both headline and file entries is confusing to some users. 207 | 208 | If you prefer to only use headline entries, you can set the variable 209 | =org-brain-include-file-entries= to =nil=. It then also makes sense to set 210 | =org-brain-file-entries-use-title= to =nil=. 211 | 212 | You may choose to exclude the file part of entry names when choosing among 213 | entries. =org-brain= passes two objects, the file and the headline, to the emacs 214 | [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Formatting-Strings.html][format]] function. By setting =org-brain-headline-entry-name-format-string= to 215 | ="%2$s"=, =org-brain= will only show the headline title. 216 | 217 | Here's an example which may be suitable for a setup without file entries: 218 | 219 | #+BEGIN_SRC emacs-lisp 220 | (setq org-brain-include-file-entries nil) 221 | (setq org-brain-file-entries-use-title nil) 222 | (setq org-brain-headline-entry-name-format-string "%2$s") 223 | 224 | (setq my/default-org-brain-file "brain") 225 | (setq org-brain-default-file-parent my/default-org-brain-file) 226 | #+END_SRC 227 | 228 | ** Limiting =org-brain= to file entries 229 | 230 | If you instead prefer to work with file entries, you can set 231 | =org-brain-scan-for-header-entries= to =nil=. It will still be possible to add 232 | headline entries, but they will be excluded from most searches when choosing 233 | among entries. 234 | 235 | * Usage 236 | 237 | You can create new entries by =M-x org-brain-visualize= and then 238 | typing the name of a non-existing entry. You can go back to the 239 | =*org-brain*= buffer by =M-x org-brain-visualize-dwim=. It will switch 240 | to the =*org-brain*= buffer. If there's no such buffer, or if already 241 | there, run =org-brain-visualize=. You could also use =M-x 242 | org-brain-add-entry= if you do not want to visualize the new entry. 243 | Also commands which add children, parents and friends, or links to 244 | entries, can create new entries in the same way. Se /General 245 | information/ below. 246 | 247 | If you find that =org-brain= is missing entries, or list entries which doesn't 248 | exist, try using =M-x org-brain-update-id-locations=, which syncs the 249 | =org-brain= entries with the =org-id= caching system. 250 | 251 | ** =org-brain-visualize= 252 | 253 | The primary usage of =org-brain= is through =M-x org-brain-visualize= (which you 254 | might want to bind to a key). From there you can browse entries, add/remove 255 | relationships, open entries for editing etc. The following keybindings are 256 | available in =org-brain-visualize=: 257 | 258 | | Key | Command | Description | 259 | |-------------+------------------------------------+-----------------------------------------------------------------------------------| 260 | | m | =org-brain-visualize-mind-map= | Toggle between normal and mind-map visualization. | 261 | | j or TAB | =forward-button= | Goto next link | 262 | | k or S-TAB | =backward-button= | Goto previous link | 263 | | b | =org-brain-visualize-back= | Like the back button in a web browser. | 264 | | h or * | =org-brain-add-child-headline= * | Add a new child /headline/ to entry | 265 | | c | =org-brain-add-child= * | Add an existing entry, or a new /file/, as a child | 266 | | C | =org-brain-remove-child= * | Remove one the entry's child relations | 267 | | e | =org-brain-annotate-edge= | Annotate the connection between the visualized entry and the entry link at point. | 268 | | p | =org-brain-add-parent= * | Add an existing entry, or a new /file/, as a parent | 269 | | P | =org-brain-remove-parent= * | Remove one of the entry's parent relations | 270 | | f | =org-brain-add-friendship= * | Add an existing entry, or a new /file/, as a friend | 271 | | F | =org-brain-remove-friendship= * | Remove one of the entry's friend relations | 272 | | n | =org-brain-pin= * | Toggle if the entry is pinned or not | 273 | | N | =org-brain-add-nickname= | Add a nickname for the entry (you can have several names for the same entry) | 274 | | s | =org-brain-select-dwim= | Select an entry for batch processing. | 275 | | S | =org-brain-select-map= | Prefix key to do batch processing with selected entries. | 276 | | t | =org-brain-set-title= | Change the title of the entry. | 277 | | T | =org-brain-set-tags= | Change the tags of the entry. | 278 | | d | =org-brain-delete-entry= | Choose an entry to delete. | 279 | | l | =org-brain-visualize-add-resource= | Add a new resource link in entry | 280 | | r | =org-brain-open-resource= | Choose and open a resource from the entry. | 281 | | C-y | =org-brain-visualize-paste-resource= | Add a new resource link from clipboard | 282 | | a | =org-brain-visualize-attach= | Run =org-attach= on entry (headline entries only) | 283 | | A | =org-brain-archive= | Archive the entry (headline entries only) | 284 | | o | =org-brain-goto-current= | Open current entry for editing | 285 | | O | =org-brain-goto= | Choose and edit one of your =org-brain= entries | 286 | | v | =org-brain-visualize= | Choose and visualize a different entry | 287 | | V | =org-brain-visualize-follow= | Similar to =org-agenda-follow-mode=; view visualized entry in another window. | 288 | | w | =org-brain-visualize-random= | Visualize one of your entries at random. | 289 | | W | =org-brain-visualize-wander= | Visualize at random, in a set interval. =W= again to cancel. | 290 | | C-c C-x C-v | =org-toggle-inline-images= | Display org-mode images in the entry text. | 291 | | M | Move prefix | Move (refile) the current entry. | 292 | | M r | =org-brain-refile= | Move current entry to another entry (change local parent). | 293 | | M p | =org-brain-change-local-parent= | Choose among the entry's parents and make another of them the local parent. | 294 | 295 | The commands above marked with =*= can be run with the universal argument =C-u= in 296 | order to operate on the entry link at point instead of the visualized entry. 297 | 298 | You may use =org-store-link= inside of =org-brain-visualize= in order to store a 299 | link to the currently visualized =org-brain= entry. 300 | 301 | If the /universal argument/ =C-u= is used when running =org-brain-visualize-random= or 302 | =org-brain-visualize-wander=, the randomized targets are restricted to descendants 303 | (children, grandchildren, grand-grandchildren etc) of the currently visualized 304 | entry. Use for instance =C-u W= to wander among the descendants. 305 | 306 | The /universal argument/ =C-u= can also be used with =org-brain-open-resource=. This 307 | lets you choose not only resource from the visualized entry, but also from 308 | descendants (children, grand-children, etc) of that entry. 309 | 310 | If the /universal argument/ =C-u= is used when calling =org-brain-annotate-edge= then 311 | the annotation will be "one-way". The default behaviour is otherwise to annotate 312 | the connection in both directions. 313 | 314 | When using the mind map visualization (toggle by pressing =m=), you can use the 315 | following keybindings in order to show or hide successive levels of hierarchy 316 | relative to the current entry. 317 | 318 | | Key | Command | Description | 319 | |-----+---------------------------------+--------------------------------------------------------------------------| 320 | | + | =org-brain-show-descendant-level= | Show one more level of entries to the right (children of children, etc.) | 321 | | - | =org-brain-hide-descendant-level= | Hide rightmost level of descendant entries | 322 | | z | =org-brain-show-ancestor-level= | Show one more level of entries to the left (parents of parents, etc.) | 323 | | Z | =org-brain-hide-ancestor-level= | Hide leftmost level of ancestor entries | 324 | 325 | If you want to select several entries and then remove/add them as 326 | children/parents/friends you can use the =s= key (=org-brain-select-dwim=) to select 327 | an entry. If the point is over a button link to an entry, that entry will be 328 | selected, otherwise it will select the currently visualized entry. If the entry 329 | is already selected, it will be unselected instead. 330 | 331 | Once you have selected all the entries you wish to use, you can use the =S= prefix 332 | key to do batch processing on the selected entries. The keybindings in this 333 | prefix keymap is identical to those in =org-brain-visualize=. You could for 334 | instance use =S c= to add all selected entries as children to the visualized 335 | entry, or =S P= to remove the parent relationship of the selected entries. When 336 | you're done and wish to clear the selection use =org-brain-clear-selected=, which 337 | is bound to =S s=. 338 | 339 | ** Editing text from =org-brain-visualize-mode= 340 | 341 | If you have the =polymode= package installed you can edit your entries directly from =org-brain-visualize-mode=. Run =M-x org-brain-polymode= or add =org-brain-polymode= to =org-brain-visualize-mode-hook=. After editing you can use =C-x C-s= (bound to =org-brain-polymode-save=) to save your changes. 342 | 343 | ** Editing from =org-mode= 344 | 345 | You can edit =org-brain= entries directly from =org-mode=. You can use the default 346 | =org-mode= outline structure to define parent/children relationships, but keep in 347 | mind that only entries with an =ID= property will be considered as entries to 348 | =org-brain=; use =M-x org-brain-get-id= to create an =ID= property to the current 349 | =org-mode= headline. Another alternative is to use =M-x org-brain-refile= which will 350 | create the ids for you. You can also create IDs for all headlines in the buffer 351 | with =M-x org-brain-ensure-ids-in-buffer=, and you might find it useful to add 352 | this to =before-save-hook=. 353 | 354 | Most of the commands available in =org-brain-visualize= can also be used in 355 | =org-mode= directly, in which case they will operate on the "entry at point". In 356 | other words you can use =M-x org-brain-add-child= directly from =org-mode= in 357 | order to add a child to the =org-brain= entry at point. You may also want to use 358 | the commands =org-brain-goto-= to navigate between entries. 359 | 360 | Most of the commands available in =org-brain-visualize-mode= is also bound to the 361 | prefix keymap =org-brain-prefix-map=. You can bind this to a key in =org-mode=, for 362 | instance =C-c b=, and you could then type =C-c b p= to add a parent to the current 363 | entry. Example config: =(define-key org-mode-map (kbd "C-c b") 364 | 'org-brain-prefix-map)=. 365 | 366 | You may want to create a link to an =org-brain= entry in an =org-mode= file (not 367 | necessarily an =org-brain= file itself). =org-brain= provides several link types 368 | for this purpose. You can use =org-insert-link= (bound to =C-c C-l= in 369 | =org-mode= by default) to insert one of these links. They all have in common 370 | that they, when clicked, will open the =org-brain= entry for editing. When 371 | inserting a link like this, =org-brain= will run completion upon all your 372 | entries. 373 | 374 | - =brain:= :: The default kind of link. Just let's you visit another =org-brain= 375 | entry when clicked. If the variable =org-brain-backlink= is =t= a brain-link will 376 | also be created as a resource in the link target, linking back to where the 377 | link was created. If =org-brain-backlink= is set to a string, that string will 378 | be added as a prefix to the title of the backlink. *Example:* You set 379 | =org-brain-backlink= to ="<-- "= and create a =brain:= link in =Rabbits= linking to 380 | =Carrots=. Now a resource with the description =<-- Rabbits= will be created in 381 | =Carrots=. 382 | - =brain-child:= :: When inserted using =org-insert-link= this will make 383 | the linked entry a child to the current =org-brain= entry, 384 | upon completion. Keep in mind that this doesn't work if you 385 | type the link manually; only by completion through 386 | =org-insert-link=. 387 | - =brain-parent:= :: Like =brain-child:= but makes the linked entry a parent of 388 | the current entry. 389 | - =brain-friend:= :: Like =brain-child:= but adds the linked entry as a friend. 390 | - =brainswitch= :: If you have multiple brains you may want a link which switches to a specific brain and one of its entries. The =brainswitch= link allows for this. 391 | 392 | The names of the relationship inserting links (=brain-child=, =brain-parent= and =brain-friend=) can be customized with the variables =org-brain-child-link-name=, =org-brain-parent-link-name=, and =org-brain-friend-link-name=. This customization should be done before loading =org-brain=. If you're using =use-package=, put the customization in the =:init= block. 393 | 394 | ** Other commands 395 | 396 | If you're browsing a file and want to add the file -- or the current line in the file -- as a resource to an entry, you can use =M-x org-brain-add-file-as-resource= or =M-x org-brain-add-file-line-as-resource=. If any of these are run with /universal argument/ =C-u= then add the resources to current/last visualized entry. 397 | 398 | ** General information 399 | 400 | If you try to add a child/parent/friend to an entry which doesn't exist, that 401 | entry will be created. The same is true for many other commands prompting for an 402 | entry, like =org-brain-visualize=. The name of a new entry can be written like 403 | this: =file::headline=. The =headline= will be created as a level one headline in 404 | =file=. If you create a new entry without the headline part, it will by default be 405 | created as a file entry. It is possible to change that though by setting the 406 | variable =org-brain-default-file-parent= to a default file. Let's say you set the 407 | variable to ="brain"= and then add the entry =Bananas= (a non-existent entry). That 408 | would be the same thing as writing =brain::Bananas=. 409 | 410 | When adding children, parents, or friends, multiple entries can be added at once 411 | by separating their titles with =org-brain-entry-separator= (which is =;= by 412 | default). For instance =M-x org-brain-add-parent RET music;artists= would add 413 | both =music= and =artists= as parents. 414 | 415 | Another available command is =M-x org-brain-agenda=, which can be used to run 416 | =org-agenda= on your =org-brain= files. 417 | 418 | ** Slashes in file entry titles 419 | 420 | When giving a file entry a title, the title can not contain slashes (=/=) if 421 | =org-brain-file-entries-use-title= is =t=. 422 | 423 | ** Renaming files in =org-brain= 424 | 425 | Headline entries use =org-id= to identify themselves, so the headlines can be 426 | manually renamed without worries. File entries, on the other hand, uses the 427 | filename as the identifier. This will cause problems if you try to manually 428 | rename files inside of =org-brain=. 429 | 430 | In order to rename a file, use =M-x org-brain-rename-file=. 431 | 432 | ** Archiving entries 433 | 434 | =org-archive= has a problem in =org-brain=: relationships are maintained, even 435 | though the entry should really be removed from the brain. Because of this, 436 | please use =org-brain-archive= instead. This command removes relationships to 437 | the entry in the brain, before archiving it. The command also inserts handy 438 | links to the archived entry's relationships. 439 | 440 | ** Special tags 441 | 442 | You might have a headline which you do not really want as an entry in 443 | =org-brain=. The most basic way to exclude such a headline is simply to not add 444 | an =ID= property to it. However, =org-brain= also provide two tags, which you 445 | can use to tag the headline: 446 | 447 | - =:nobrain:= :: This tag excludes the headline, and its subheadings, from your 448 | =org-brain= entries. You can change the tag name by modifying 449 | =org-brain-exclude-tree-tag=. 450 | - =:childless:= :: This tag does not exclude the headline, but it excludes the 451 | subheadings. You can change the tag name by modifying 452 | =org-brain-exclude-children-tag=. Works on file entries. 453 | 454 | The following tags modifies the kind of information that is shown when an entry 455 | is visualized: 456 | 457 | - =:notext:= :: Do not show the entry's text in =org-brain-visualize=. You can 458 | change the tag name by modifying =org-brain-exclude-text-tag=. 459 | - =:resourceless:= :: Do not show the entry's resources in 460 | =org-brain-visualize=. You can change the tag name by modifying 461 | =org-brain-exclude-resources-tag=. 462 | - =:showchildren:= :: By default local child entries aren't shown as text. By 463 | setting this tag the entry get the entire subtree as text. You can change 464 | the tag name by modifying =org-brain-show-children-tag=. Works on file 465 | entries. 466 | - =:nosiblings:= :: You may have an entry with lots of children, and when you visualize one of these children you might not want to see the siblings from this parent. A good example would be if you have an =index= entry or similar. By tagging the parent with =nosiblings= the parent's children will not show siblings from that parent. You can change the tag name by modifying =org-brain-exclude-siblings-tag=. 467 | - :nolocalparent: :: This is similar to =:nosiblings:= but the tagged parent will 468 | not be shown at all when one of its local children are visualized. 469 | 470 | The following tags modify the way how information is shown when an 471 | entry is visualized. 472 | 473 | - =:ownline:= :: Make each child of the tagged entry appear on its own 474 | line when the tagged entry is visualized. This 475 | only affects the tagged entry. It works akin to 476 | temporarily setting =org-brain-child-fill-column-sexp= 477 | to =0=. 478 | 479 | - =:nosort:= :: Display each child of the tagged node in the order the 480 | children are listed in the file, rather than in the 481 | sorted order determined by 482 | =org-brain-visualize-sort-function=. This affects the 483 | order of the node’s children in both the child list 484 | (when the tagged node is being visited) and in the 485 | sibling list (when one of the tagged node’s children 486 | is being visited). 487 | 488 | ** Having multiple brains 489 | 490 | You can have multiple brains simply by having more than one brain folder. In this way, each folder becomes a separate brain. You can switch between these using =M-x org-brain-switch-brain=. You can also use =brainswitch:= links in =org-mode= to switch brains. 491 | 492 | If you run =org-brain-visualize= from inside an org-file in /the root/ of an org-brain directory, =org-brain= will automatically switch to this brain. 493 | 494 | ** Take note! 495 | 496 | =org-brain= creates and uses several headline properties in the =PROPERTIES= 497 | drawer of =org-mode= headlines: 498 | 499 | - =BRAIN_PARENTS= 500 | - =BRAIN_CHILDREN= 501 | - =BRAIN_FRIENDS= 502 | - =BRAIN_EDGE_$IDENTIFIER= 503 | - =ID= 504 | - =NICKNAMES= 505 | 506 | These properties are also mirrored as file keywords at the top of file entries, 507 | for instance =#+BRAIN_CHILDREN: 00c0f06c-9bd4-4c31-aed0-15bb3361d9a2=. 508 | 509 | These properties/keywords are /not meant to be manipulated directly/! If you want 510 | to remove these properties, use the corresponding command instead 511 | (=org-brain-remove-child= or similar). There's currently command to remove 512 | =NICKNAMES= though, so at the moment that has to be done manually. 513 | 514 | You might also see that =org-brain= inserts a =RESOURCES= drawer. It is okay to 515 | modify this drawer manually. 516 | 517 | The names of the parents/children/friends properties, the prefix for edge 518 | properties and the =RESOURCES= drawer can customized by setting the variables 519 | =org-brain-parents-property-name=, =org-brain-children-property-name=, 520 | =org-brain-friends-property-name=, =org-brain-edge-property-prefix-name= and 521 | =org-brain-resources-drawer-name=, respectively. Of course, after doing any 522 | customization, the property/drawer names of existing brain files have to be 523 | adjusted manually. 524 | 525 | ** =org-brain= is slow! 526 | 527 | If you feel that =org-brain= is slow while indexing your entries (for instance when running =M-x org-brain-visualize=) you can customize =org-brain-file-entries-use-title=, and set it to =nil=. This will display file names when indexing, instead of the file entry's title, which is faster. 528 | 529 | Should only the first call of =org-brain-visualize= be slow, an option may be to try persistent caching of the variable =org-brain-headline-cache=. Choose a [[https://www.emacswiki.org/emacs/SessionManagement][session management solution]] that works for you [[https://github.com/thierryvolpiatto/psession][(an option for helm users)]]. A simple and built-in method is to use the savehist package. To do so, you may add the following configuration: 530 | 531 | #+begin_src emacs-lisp 532 | (savehist-mode 1) 533 | (setq savehist-additional-variables '(org-brain-headline-cache)) 534 | #+end_src 535 | 536 | * FAQ 537 | ** Wrong number of arguments: (0 . 0), 2 538 | 539 | You are probably using the version of =org-mode= that came with your Emacs install, which has a lower version than 9.2. Check the version of org you have installed with =M-x org-version=. See [[https://github.com/Kungsgeten/org-brain/issues/278][Github issue #278]]. 540 | * Export to other formats 541 | 542 | =org-brain= has no built-in functionality for exporting to other formats. I've 543 | started experimenting with another package named [[https://github.com/Kungsgeten/org-brain-export][org-brain-export]] which might be 544 | merged into =org-brain= in the future. =org-brain-export= is in VERY early stages of 545 | development. 546 | * Helm and Ivy 547 | 548 | If you use [[https://github.com/emacs-helm/helm][Helm]] or [[https://oremacs.com/swiper/][Ivy]] you can use the commands =helm-brain= or =counsel-brain= respectively. These allow for visualizing entries, and adding parents/children/friends to the entry at point. They also allow selecting multiple entries. 549 | 550 | These commands do not have any keybindings by default. 551 | 552 | * Backwards compatibility breaking changes 553 | ** 0.7 554 | 555 | As of version 0.7 /entry descriptions/ are deprecated. They made visualization slow, and it was quite a hassle to actually write them. The "help echo" text is now used for edge annotations instead. 556 | 557 | ** 0.4 558 | 559 | /This is only relevant if you've been using org-brain before version 0.4/ 560 | 561 | As of version 0.4 (June 2017) =org-brain= has been rewritten, in order to 562 | increase performance and add more options. Because of this, older setups are 563 | considered obsolete. Prior to 0.4 only files were considered entries, but now 564 | also headlines with an =ID= property are included as entries. Prior to 0.4 565 | =org-brain= was using the =brain:= link and =#+BRAIN_PINNED:= file keyword to 566 | connect files, which was slow due to the need of searching all files for links. 567 | In version 0.4 =org-brain= uses a combination of headline properties, file 568 | keywords, =org-id=, and a data file (=org-brain-data-file=). 569 | 570 | No data in old configurations should be lost, but you'll have to update the 571 | connections between entries. This can be done by using =M-x 572 | org-brain-create-relationships-from-links=, but please backup your =org-brain= 573 | directory first. 574 | 575 | It is still possible to add children to an entry by using the =brain-child:= link, but 576 | only if the link is inserted with =org-insert-link= (bound to =C-c C-l= in 577 | =org-mode= by default). Linking to specific headlines in a file, via 578 | =brain:filename::*Headline= is *deprecated* and will no longer work, instead you 579 | can convert the headline to an entry and link directly to that. 580 | 581 | * Other useful packages 582 | 583 | There's some missing functionality in =org-brain=, which you may find useful. 584 | However there are other packages which might improve your =org-brain= 585 | experience. Below are some suggestions (feel free to create an issue or send a 586 | pull request if you have more examples), all of them should be available on 587 | MELPA. 588 | 589 | ** Chronological entries with =org-expiry= 590 | 591 | =org-brain= doesn't add any information on when entries are created, so it is hard 592 | get a list of your entries in chronological order. I've managed to use 593 | =org-expiry= (part of =org-plus-contrib=) to add a =CREATED= property to all =org-brain= 594 | headline entries. Then we can use =org-agenda= to show the entries in 595 | chronological order. 596 | 597 | #+BEGIN_SRC emacs-lisp 598 | ;; Setup org-expiry and define a org-agenda function to compare timestamps 599 | (require 'org-expiry) 600 | (setq org-expiry-inactive-timestamps t) 601 | (defun org-expiry-created-comp (a b) 602 | "Compare `org-expiry-created-property-name' properties of A and B." 603 | (let ((ta (ignore-errors 604 | (org-time-string-to-seconds 605 | (org-entry-get (get-text-property 0 'org-marker a) 606 | org-expiry-created-property-name)))) 607 | (tb (ignore-errors 608 | (org-time-string-to-seconds 609 | (org-entry-get (get-text-property 0 'org-marker b) 610 | org-expiry-created-property-name))))) 611 | (cond ((if ta (and tb (< ta tb)) tb) -1) 612 | ((if tb (and ta (< tb ta)) ta) +1)))) 613 | 614 | ;; Add CREATED property when adding a new org-brain headline entry 615 | (add-hook 'org-brain-new-entry-hook #'org-expiry-insert-created) 616 | 617 | ;; Finally add a function which lets us watch the entries chronologically 618 | (defun org-brain-timeline () 619 | "List all org-brain headlines in chronological order." 620 | (interactive) 621 | (let ((org-agenda-files (org-brain-files)) 622 | (org-agenda-cmp-user-defined #'org-expiry-created-comp) 623 | (org-agenda-sorting-strategy '(user-defined-down))) 624 | (org-tags-view nil (format "+%s>\"\"" org-expiry-created-property-name)))) 625 | #+END_SRC 626 | 627 | Now we can use =org-brain-timeline= to view entries in chronological order (newest 628 | first). 629 | 630 | ** [[https://github.com/rexim/org-cliplink][org-cliplink]] 631 | 632 | #+BEGIN_QUOTE 633 | A simple command that takes a URL from the clipboard and inserts an org-mode link with a title of a page found by the URL into the current buffer. 634 | #+END_QUOTE 635 | 636 | Here's a command which uses =org-cliplink= to add a link from the clipboard as an =org-brain= resource. It guesses the description from the URL title. Here I've bound it to =L= in =org-brain-visualize=. 637 | 638 | #+BEGIN_SRC emacs-lisp 639 | (defun org-brain-cliplink-resource () 640 | "Add a URL from the clipboard as an org-brain resource. 641 | Suggest the URL title as a description for resource." 642 | (interactive) 643 | (let ((url (org-cliplink-clipboard-content))) 644 | (org-brain-add-resource 645 | url 646 | (org-cliplink-retrieve-title-synchronously url) 647 | t))) 648 | 649 | (define-key org-brain-visualize-mode-map (kbd "L") #'org-brain-cliplink-resource) 650 | #+END_SRC 651 | 652 | ** [[https://github.com/noctuid/link-hint.el][link-hint]] 653 | 654 | #+BEGIN_QUOTE 655 | link-hint.el is inspired by the link hinting functionality in vim-like browsers 656 | and browser plugins such as pentadactyl. It provides commands for using avy to 657 | open or copy "links." 658 | #+END_QUOTE 659 | 660 | After installing =link-hint= you could bind =link-hint-open-link= to a key, and 661 | use it in =org-brain-visualize-mode=. If you only want to use =link-hint= in 662 | =org-brain-visualize-mode=, you could add the following to your init-file: 663 | 664 | #+BEGIN_SRC emacs-lisp 665 | (define-key org-brain-visualize-mode-map (kbd "C-l") #'link-hint-open-link) 666 | #+END_SRC 667 | 668 | ** [[http://www.gnuvola.org/software/aa2u/][ascii-art-to-unicode]] 669 | 670 | #+BEGIN_QUOTE 671 | Converts simple ASCII art line drawings in the region of the current buffer to 672 | Unicode. 673 | #+END_QUOTE 674 | 675 | =ascii-art-to-unicode= is useful if you want =org-brain-visualize-mode= to look 676 | a bit nicer. After installing, add the following to your init-file: 677 | 678 | #+BEGIN_SRC emacs-lisp 679 | (defface aa2u-face '((t . nil)) 680 | "Face for aa2u box drawing characters") 681 | (advice-add #'aa2u-1c :filter-return 682 | (lambda (str) (propertize str 'face 'aa2u-face))) 683 | (defun aa2u-org-brain-buffer () 684 | (let ((inhibit-read-only t)) 685 | (make-local-variable 'face-remapping-alist) 686 | (add-to-list 'face-remapping-alist 687 | '(aa2u-face . org-brain-wires)) 688 | (ignore-errors (aa2u (point-min) (point-max))))) 689 | (with-eval-after-load 'org-brain 690 | (add-hook 'org-brain-after-visualize-hook #'aa2u-org-brain-buffer)) 691 | #+END_SRC 692 | 693 | ** [[https://github.com/domtronn/all-the-icons.el][all-the-icons]] 694 | 695 | #+BEGIN_QUOTE 696 | A utility package to collect various Icon Fonts and propertize them within Emacs. 697 | #+END_QUOTE 698 | 699 | After installing =all-the-icons= you could decorate the resources in =org-brain=, by using 700 | =org-brain-after-resource-button-functions=. Here's a small example: 701 | 702 | #+BEGIN_SRC emacs-lisp 703 | (defun org-brain-insert-resource-icon (link) 704 | "Insert an icon, based on content of org-mode LINK." 705 | (insert (format "%s " 706 | (cond ((string-prefix-p "brain:" link) 707 | (all-the-icons-fileicon "brain")) 708 | ((string-prefix-p "info:" link) 709 | (all-the-icons-octicon "info")) 710 | ((string-prefix-p "help:" link) 711 | (all-the-icons-material "help")) 712 | ((string-prefix-p "http" link) 713 | (all-the-icons-icon-for-url link)) 714 | (t 715 | (all-the-icons-icon-for-file link)))))) 716 | 717 | (add-hook 'org-brain-after-resource-button-functions #'org-brain-insert-resource-icon) 718 | #+END_SRC 719 | 720 | You could also use =all-the-icons= to add icons to entry [[https://orgmode.org/manual/Categories.html][categories]]. For instance if you have two categories named /computers/ and /books/ which you want icons for: 721 | 722 | #+BEGIN_SRC emacs-lisp 723 | (setq org-agenda-category-icon-alist 724 | `(("computers" ,(list (all-the-icons-material "computer")) nil nil :ascent center) 725 | ("books" ,(list (all-the-icons-faicon "book")) nil nil :ascent center))) 726 | #+END_SRC 727 | 728 | ** [[http://jblevins.org/projects/deft/][deft]] 729 | 730 | #+BEGIN_QUOTE 731 | An Emacs mode for quickly browsing, filtering, and editing directories of plain 732 | text notes, inspired by Notational Velocity. 733 | #+END_QUOTE 734 | 735 | After installing =deft=, you can add the function below to your init-file. 736 | 737 | #+BEGIN_SRC emacs-lisp 738 | (defun org-brain-deft () 739 | "Use `deft' for files in `org-brain-path'." 740 | (interactive) 741 | (let ((deft-directory org-brain-path) 742 | (deft-recursive t) 743 | (deft-extensions '("org"))) 744 | (deft))) 745 | #+END_SRC 746 | 747 | ** [[https://github.com/alphapapa/helm-org-rifle][helm-org-rifle]] 748 | 749 | #+BEGIN_QUOTE 750 | It searches both headings and contents of entries in Org buffers, and it 751 | displays entries that match all search terms, whether the terms appear in the 752 | heading, the contents, or both. 753 | #+END_QUOTE 754 | 755 | After installing =helm-org-rifle=, you can add the function below to your 756 | init-file. 757 | 758 | #+BEGIN_SRC emacs-lisp 759 | (defun helm-org-rifle-brain () 760 | "Rifle files in `org-brain-path'." 761 | (interactive) 762 | (let ((helm-org-rifle-close-unopened-file-buffers nil)) 763 | (helm-org-rifle-directories (list org-brain-path)))) 764 | 765 | (defun helm-org-rifle-open-in-brain (candidate) 766 | (-let (((buffer . pos) candidate)) 767 | (with-current-buffer buffer 768 | (goto-char pos) 769 | (org-brain-visualize-entry-at-pt)))) 770 | 771 | (add-to-list 'helm-org-rifle-actions 772 | (cons "Show entry in org-brain" 'helm-org-rifle-open-in-brain) 773 | t) 774 | #+END_SRC 775 | 776 | ** [[https://github.com/weirdNox/org-noter][org-noter]] 777 | 778 | #+BEGIN_QUOTE 779 | Org-noter's purpose is to let you create notes that are kept in sync when you scroll through the [PDF etc] document 780 | #+END_QUOTE 781 | 782 | Thanks to [[https://github.com/rosetree][rosetree]] for providing this tip! After installing =org-noter=, add the following to your init-file: 783 | 784 | #+BEGIN_SRC emacs-lisp 785 | (add-hook 'org-noter-insert-heading-hook #'org-id-get-create) 786 | (defun org-brain-open-org-noter (entry) 787 | "Open `org-noter' on the ENTRY. 788 | If run interactively, get ENTRY from context." 789 | (interactive (list (org-brain-entry-at-pt))) 790 | (org-with-point-at (org-brain-entry-marker entry) 791 | (org-noter))) 792 | #+END_SRC 793 | 794 | =org-brain-open-org-noter= will run =org-noter= on the current entry. This lets you save your PDF notes in =org-brain=, so you can link to them from other entries etc. It can be a good idea to add this command to =org-brain-visualize=, like this: 795 | 796 | #+BEGIN_SRC emacs-lisp 797 | (define-key org-brain-visualize-mode-map (kbd "\C-c n") 'org-brain-open-org-noter) 798 | #+END_SRC 799 | 800 | ** [[https://github.com/scallywag/org-board][org-board]] 801 | #+BEGIN_QUOTE 802 | org-board is a bookmarking and web archival system for Emacs Org mode, building 803 | on ideas from Pinboard. It archives your bookmarks so that you can access them 804 | even when you're not online, or when the site hosting them goes down. 805 | #+END_QUOTE 806 | 807 | * Similar packages 808 | 809 | The Emacs Wiki has an article about [[https://www.emacswiki.org/emacs/WikiModes][wiki modes in Emacs]]. 810 | 811 | ** [[https://github.com/jethrokuan/org-roam][org-roam]] 812 | 813 | #+BEGIN_QUOTE 814 | Org-roam is a Roam replica built on top of the all-powerful Org-mode. 815 | 816 | Org-roam is a solution for effortless non-hierarchical note-taking with 817 | Org-mode. With Org-roam, notes flow naturally, making note-taking fun and easy. 818 | Org-roam should also work as a plug-and-play solution for anyone already using 819 | Org-mode for their personal wiki. 820 | 821 | Org-roam aims to implement the core features of Roam, leveraging the mature 822 | ecosystem around Org-mode where possible. Eventually, we hope to further 823 | introduce features enabled by the Emacs ecosystem. 824 | #+END_QUOTE 825 | 826 | ** [[https://github.com/l3kn/org-zettelkasten][Org Zettelkasten]] 827 | 828 | #+begin_quote 829 | An opinionated setup for managing large collections of interlinked org files. 830 | #+end_quote 831 | 832 | ** [[https://github.com/caiorss/org-wiki][org-wiki]] 833 | 834 | #+BEGIN_QUOTE 835 | Org-wiki is a org-mode extension that provides tools to manage and build 836 | personal wiki or desktop wiki where each wiki page is a org-mode file. 837 | #+END_QUOTE 838 | -------------------------------------------------------------------------------- /org-brain.el: -------------------------------------------------------------------------------- 1 | ;;; org-brain.el --- Org-mode concept mapping -*- lexical-binding: t; -*- 2 | 3 | ;; Copyright (C) 2017--2020 Erik Sjöstrand 4 | ;; MIT License 5 | 6 | ;; Author: Erik Sjöstrand 7 | ;; URL: http://github.com/Kungsgeten/org-brain 8 | ;; Keywords: outlines hypermedia 9 | ;; Package-Requires: ((emacs "25.1") (org "9.2")) 10 | ;; Version: 0.94 11 | 12 | ;;; Commentary: 13 | 14 | ;; org-brain implements a variant of concept mapping with org-mode, it is 15 | ;; inspired by The Brain software (http://thebrain.com). An org-brain is a 16 | ;; network of org-mode entries, where each entry is a file or a headline, and 17 | ;; you can get a visual overview of the relationships between the entries: 18 | ;; parents, children, siblings and friends. This visual overview can also be 19 | ;; used to browse your entries. You can think of entries as nodes in a mind map, 20 | ;; or pages in a wiki. 21 | 22 | ;; All org files put into your `org-brain-path' directory will be considered 23 | ;; entries in your org-brain. Headlines with an ID property in your entry file(s) 24 | ;; are also considered as entries. 25 | 26 | ;; Use `org-brain-visualize' to see the relationships between entries, quickly 27 | ;; add parents/children/friends/pins to an entry, and open them for editing. 28 | 29 | ;;; Code: 30 | 31 | (require 'org-element) 32 | (require 'org-attach) 33 | (require 'org-agenda) 34 | (require 'org-macs) 35 | (require 'org-id) 36 | (require 'picture) 37 | (require 'subr-x) 38 | (require 'seq) 39 | 40 | (defgroup org-brain nil 41 | "Org-mode concept mapping" 42 | :prefix "org-brain-" 43 | :group 'org) 44 | 45 | ;;;; Custom vars 46 | 47 | (defcustom org-brain-path (file-truename (expand-file-name "brain" org-directory)) 48 | "The root directory of your org-brain. 49 | 50 | `org-mode' files placed in this directory, or its subdirectories, 51 | will be considered org-brain entries." 52 | :group 'org-brain 53 | :type '(directory)) 54 | 55 | (defcustom org-brain-scan-directories-recursively t 56 | "If subdirectories inside `org-brain-path' are considered part of the brain or not." 57 | :group 'org-brain 58 | :type '(boolean)) 59 | 60 | (defcustom org-brain-files-extension "org" 61 | "The extension for entry files in `org-brain-path'." 62 | :group 'org-brain 63 | :type '(string)) 64 | 65 | (defcustom org-brain-ignored-resource-links '("fuzzy" "radio" "brain-child" "brain-parent" "brain-friend") 66 | "`org-link-types' which shouldn't be shown as resources in `org-brain-visualize'." 67 | :group 'org-brain 68 | :type '(repeat string)) 69 | 70 | (defcustom org-brain-backlink nil 71 | "If backlink resource should be added when creating a brain org-link. 72 | This only works when completing the link via `org-insert-link'. 73 | Example: If you create a brain-link in A to B, setting this 74 | variable to non-nil would also create A as a resource in B. 75 | 76 | If this variable is a string it will be added as a prefix in the backlink. 77 | Example: \"<--\" would add \"<--A\" in the example above." 78 | :group 'org-brain 79 | :type '(restricted-sexp :match-alternatives 80 | (stringp 't 'nil))) 81 | 82 | (defcustom org-brain-backlink-heading t 83 | "If the org heading should be used when creating a backlink. 84 | 85 | Example: Creating a brain-link in A to B and A is an org file with the headings: 86 | * Parent header 87 | ** Child 88 | [brain:linkToB] 89 | 90 | Setting this variable to t will create the following backlink in B: 91 | [[file:A.org::*Child][Parent header > Child]]." 92 | :group 'org-brain 93 | :type '(boolean)) 94 | 95 | (make-obsolete-variable 'org-brain-suggest-stored-link-as-resource 96 | "org-brain-suggest-stored-link-as-resource isn't needed because of `org-insert-link-global'." 97 | "0.6") 98 | 99 | (defcustom org-brain-data-file (file-truename (expand-file-name ".org-brain-data.el" org-brain-path)) 100 | "Where org-brain data is saved." 101 | :group 'org-brain 102 | :type '(directory)) 103 | 104 | (load org-brain-data-file t t) 105 | 106 | (defcustom org-brain-visualize-default-choices 'all 107 | "Which entries to choose from when using `org-brain-visualize'. 108 | If 'all, choose from all file and headline entries. 109 | If 'files, only choose from file entries. 110 | If 'root, only choose from file entries in `org-brain-path' (non-recursive)." 111 | :group 'org-brain 112 | :type '(choice 113 | (const :tag "All entries" all) 114 | (const :tag "Only file entries" files) 115 | (const :tag "Only root file entries" root))) 116 | 117 | (defcustom org-brain-include-file-entries t 118 | "If set to nil `org-brain' is optimized for headline entries. 119 | Only headlines will be considered as entries when visualizing." 120 | :group 'org-brain 121 | :type '(boolean)) 122 | 123 | (make-obsolete-variable 124 | 'org-brain-file-from-input-function 125 | "`org-brain-default-file-parent' can be used as a better alternative." 126 | "0.92") 127 | 128 | (defcustom org-brain-default-file-parent nil 129 | "Where to store new entries with unspecified local parent. 130 | For instance if creating a new entry with `org-brain-visualize'. 131 | If nil, create the new entry as a file entry relative to `org-brain-path'. 132 | If set to a string it should be a file entry. That entry will be used as the 133 | local parent and the new entry will be a headline." 134 | :group 'org-brain 135 | :type '(choice string (const nil))) 136 | 137 | (defcustom org-brain-show-full-entry nil 138 | "Always show entire entry contents?" 139 | :group 'org-brain 140 | :type '(boolean)) 141 | 142 | (defcustom org-brain-show-resources t 143 | "Should entry resources be shown in `org-brain-visualize'?" 144 | :group 'org-brain 145 | :type '(boolean)) 146 | 147 | (defcustom org-brain-show-text t 148 | "Should the entry text be shown in `org-brain-visualize'?" 149 | :group 'org-brain 150 | :type '(boolean)) 151 | 152 | (defcustom org-brain-show-history t 153 | "Should the navigation history be shown in `org-brain-visualize'?" 154 | :group 'org-brain 155 | :type '(boolean)) 156 | 157 | (defcustom org-brain-show-icons t 158 | "Should icons from `org-agenda-category-icon-alist' be shown when visualizing?" 159 | :group 'org-brain 160 | :type '(boolean)) 161 | 162 | (defcustom org-brain-category-icon-width 2 163 | "The character width of icons." 164 | :group 'org-brain 165 | :type '(integer)) 166 | 167 | (defcustom org-brain-quit-after-goto nil 168 | "Should the *org-brain* buffer window close itself after executing a goto command?" 169 | :group 'org-brain 170 | :type '(boolean)) 171 | 172 | (defcustom org-brain-headline-links-only-show-visible t 173 | "Only show visible parts (descriptions) of headline links. 174 | 175 | See the docstring for `org-brain-headline-at' for more info 176 | on how this is implemented." 177 | :group 'org-brain 178 | :type '(boolean)) 179 | 180 | (defcustom org-brain-file-entries-use-title t 181 | "If file entries should show their title, when choosing entries from a list. 182 | This can potentially be slow. If set to nil, the relative 183 | filenames will be shown instead, which is faster." 184 | :group 'org-brain 185 | :type '(boolean)) 186 | 187 | (defcustom org-brain-scan-for-header-entries t 188 | "If org-brain should scan for header entries inside files. 189 | Useful if you don't tend to use header entries in your workflow, 190 | since scanning can be slow in long file entries. 191 | This only affects selection prompts and not functions like `org-brain-headline-to-file'." 192 | :group 'org-brain 193 | :type '(boolean)) 194 | 195 | (defcustom org-brain-headline-entry-name-format-string "%s::%s" 196 | "How headline entries are represented when choosing entries. 197 | This `format' string is used in `org-brain-entry-name' for headline entries. 198 | `format' gets two objects: the file and the headline." 199 | :group 'org-brain 200 | :type '(string)) 201 | (defcustom org-brain-visualize-text-hook nil 202 | "Hook runs after inserting `org-brain-text' in `org-brain-visualize'. 203 | 204 | Can be used to prettify the entry text, e.g. 205 | `org-display-inline-images'." 206 | :group 'org-brain 207 | :type 'hook) 208 | 209 | (defcustom org-brain-after-visualize-hook nil 210 | "Hook run after `org-brain-visualize', but before `org-brain-text'. 211 | Can be used to prettify the buffer output, e.g. `ascii-art-to-unicode'." 212 | :group 'org-brain 213 | :type 'hook) 214 | 215 | (defcustom org-brain-new-entry-hook nil 216 | "Hook run after a new headline entry has been created." 217 | :group 'org-brain 218 | :type 'hook) 219 | 220 | (defcustom org-brain-visualize-follow-hook nil 221 | "Hook run after viewing an entry by means of `org-brain-visualize-follow'." 222 | :group 'org-brain 223 | :type 'hook) 224 | 225 | (defcustom org-brain-after-resource-button-functions nil 226 | "Hook run during `org-brain-insert-resource-button'. 227 | Insert a bullet, then run hook functions, then insert the actual button. 228 | Each function must take a single argument: the org link to the resource. 229 | Can for instance be used in combination with `all-the-icons'." 230 | :group 'org-brain 231 | :type 'hook) 232 | 233 | (defcustom org-brain-vis-title-prepend-functions '(org-brain-entry-icon) 234 | "Functions which `org-brain-vis-title' use before inserting the entry title. 235 | Each function should take the entry as the only argument, and 236 | should return a string. The strings are prepended to the entry title." 237 | :group 'org-brain 238 | :type 'hook 239 | :options '(org-brain-entry-icon 240 | org-brain-entry-todo-state 241 | org-brain-entry-tags-string)) 242 | 243 | (defcustom org-brain-vis-title-append-functions '() 244 | "Functions which `org-brain-vis-title' use after inserting the entry title. 245 | Each function should take the entry as the only argument, and 246 | should return a string. The strings are appended to the entry title." 247 | :group 'org-brain 248 | :type 'hook 249 | :options '(org-brain-entry-icon 250 | org-brain-entry-todo-state 251 | org-brain-entry-tags-string)) 252 | 253 | (defcustom org-brain-vis-current-title-prepend-functions '() 254 | "Like `org-brain-vis-title-prepend-functions' for the current visualized entry. 255 | First `org-brain-vis-title-prepend-functions' are ran, and then these." 256 | :group 'org-brain 257 | :type 'hook 258 | :options '(org-brain-entry-icon 259 | org-brain-entry-todo-state 260 | org-brain-entry-tags-string)) 261 | 262 | (defcustom org-brain-vis-current-title-append-functions '() 263 | "Like `org-brain-vis-title-append-functions' for the current visualized entry. 264 | First `org-brain-vis-title-append-functions' are ran, and then these." 265 | :group 'org-brain 266 | :type 'hook 267 | :options '(org-brain-entry-icon 268 | org-brain-entry-todo-state 269 | org-brain-entry-tags-string)) 270 | 271 | (defcustom org-brain-exclude-text-tag "notext" 272 | "`org-mode' tag stopping `org-brain-visualize' from fetching entry text. 273 | Only applies to headline entries." 274 | :group 'org-brain 275 | :type '(string)) 276 | 277 | (defcustom org-brain-exclude-resouces-tag "resourceless" 278 | "`org-mode' tag stopping `org-brain-visualize' from fetching entry resources. 279 | Only applies to headline entries." 280 | :group 'org-brain 281 | :type '(string)) 282 | 283 | (defcustom org-brain-exclude-children-tag "childless" 284 | "`org-mode' tag which exclude the headline's children from org-brain's entries." 285 | :group 'org-brain 286 | :type '(string)) 287 | 288 | (defcustom org-brain-show-children-tag "showchildren" 289 | "`org-mode' tag which get entire subtree from headline entry during `org-brain-text'." 290 | :group 'org-brain 291 | :type '(string)) 292 | 293 | (defcustom org-brain-exclude-tree-tag "nobrain" 294 | "`org-mode' tag which exclude the headline and its children from org-brain's entries." 295 | :group 'org-brain 296 | :type '(string)) 297 | 298 | (defcustom org-brain-exclude-siblings-tag "nosiblings" 299 | "`org-mode' tag which prevents the siblings of children of this node from being displayed." 300 | :group 'org-brain 301 | :type '(string)) 302 | 303 | (defcustom org-brain-exclude-local-parent-tag "nolocalparent" 304 | "`org-mode' tag which prevents this node to be displayed as a local parent." 305 | :group 'org-brain 306 | :type '(string)) 307 | 308 | (defcustom org-brain-each-child-on-own-line-tag "ownline" 309 | "`org-mode' tag which makes each child of the headline entry be listed on its own line." 310 | :group 'org-brain 311 | :type '(string)) 312 | 313 | (defcustom org-brain-no-sort-children-tag "nosort" 314 | "`org-mode' tag which makes the children of the headline entry appear in file order rather than sorted." 315 | :group 'org-brain 316 | :type '(string)) 317 | 318 | (defcustom org-brain-wander-interval 3 319 | "Seconds between randomized entries, when using `org-brain-visualize-wander'." 320 | :group 'org-brain 321 | :type 'integer) 322 | 323 | (defcustom org-brain-title-max-length 0 324 | "If a title is longer than this, it'll be capped during `org-brain-visualize'. 325 | If 0 or a negative value, the title won't be capped." 326 | :group 'org-brain 327 | :type 'integer) 328 | 329 | (defcustom org-brain-cap-mind-map-titles nil 330 | "Whether to cap entries longer than org-brain-title-max-length in mind map visualization mode." 331 | :group 'org-brain 332 | :type '(boolean)) 333 | 334 | (defcustom org-brain-entry-separator ";" 335 | "Can be used as a separator when adding children, parents, or friends. 336 | Doing so allows for adding multiple entries at once." 337 | :group 'org-brain 338 | :type '(string)) 339 | 340 | (make-obsolete-variable 341 | 'org-brain-visualize-one-child-per-line 342 | "Setting `org-brain-child-linebreak-sexp' to 0 visualizes one child per line." 343 | "0.7") 344 | 345 | (defcustom org-brain-child-linebreak-sexp 'fill-column 346 | "Where to break lines when visualizing children? 347 | Reasonable values include: 348 | 349 | '0: every child will be on its own line 350 | 'fill-column: lines will break at `fill-column' 351 | '(window-width): lines will break at the width of the window 352 | 'most-positive-fixnum: All children will be on one line" 353 | :group 'org-brain 354 | :type '(sexp)) 355 | 356 | (defcustom org-brain-refile-max-level 1 357 | "The default max-level used by `org-brain-refile'." 358 | :group 'org-brain 359 | :type 'integer) 360 | 361 | (defcustom org-brain-child-link-name "brain-child" 362 | "The name for `org-mode' links, creating child relationships. 363 | Must be set before `org-brain' is loaded. 364 | Insert links using `org-insert-link'." 365 | :group 'org-brain 366 | :type '(string)) 367 | 368 | (defcustom org-brain-parent-link-name "brain-parent" 369 | "The name for `org-mode' links, creating parent relationships. 370 | Must be set before `org-brain' is loaded. 371 | Insert links using `org-insert-link'." 372 | :group 'org-brain 373 | :type '(string)) 374 | 375 | (defcustom org-brain-friend-link-name "brain-friend" 376 | "The name for `org-mode' links, creating friend relationships. 377 | Must be set before `org-brain' is loaded. 378 | Insert links using `org-insert-link'." 379 | :group 'org-brain 380 | :type '(string)) 381 | 382 | (defcustom org-brain-children-property-name "BRAIN_CHILDREN" 383 | "The name for the org-mode property in which child relationships are stored. 384 | Must be set before `org-brain' is loaded." 385 | :group 'org-brain 386 | :type '(string)) 387 | 388 | (defcustom org-brain-parents-property-name "BRAIN_PARENTS" 389 | "The name for the org-mode property in which brain relationships are stored. 390 | Must be set before `org-brain' is loaded." 391 | :group 'org-brain 392 | :type '(string)) 393 | 394 | (defcustom org-brain-friends-property-name "BRAIN_FRIENDS" 395 | "The name for the org-mode property in which friend relationships are stored. 396 | Must be set before `org-brain' is loaded." 397 | :group 'org-brain 398 | :type '(string)) 399 | 400 | (defcustom org-brain-edge-property-prefix-name "BRAIN_EDGE" 401 | "The prefix for the org-mode property in which edge annotations are stored. 402 | Must be set before `org-brain' is loaded." 403 | :group 'org-brain 404 | :type '(string)) 405 | 406 | (defcustom org-brain-resources-drawer-name "RESOURCES" 407 | "The org-mode drawer name in which resources of an entry are stored. 408 | Must be set before `org-brain' is loaded." 409 | :group 'org-brain 410 | :type '(string)) 411 | 412 | (defcustom org-brain-open-same-window nil 413 | "Should `org-brain-visualize' open up in the same window it was launched in?" 414 | :group 'org-brain 415 | :type '(boolean)) 416 | 417 | (defcustom org-brain-completion-system 'default 418 | "The completion system to be used by `org-brain'." 419 | :group 'org-brain 420 | :type '(radio 421 | (const :tag "Ido" ido) 422 | (const :tag "Helm" helm) 423 | (const :tag "Ivy" ivy) 424 | (const :tag "Default" default) 425 | (function :tag "Custom function"))) 426 | 427 | ;;;;; Faces and face helper functions 428 | 429 | (defface org-brain-title 430 | '((t . (:inherit 'org-level-1))) 431 | "Face for the currently selected entry.") 432 | 433 | (defface org-brain-wires 434 | `((t . (:inherit 'font-lock-comment-face :italic nil))) 435 | "Face for the wires connecting entries.") 436 | 437 | (defface org-brain-button 438 | '((t . (:inherit button))) 439 | "Face for header-entry buttons in the org-brain visualize buffer. 440 | File entries also use this, but also applies `org-brain-file-face-template'.") 441 | 442 | (defface org-brain-parent 443 | '((t . (:inherit (font-lock-builtin-face org-brain-button)))) 444 | "Face for the entries' linked header-entry parent nodes. 445 | File entries also use this, but also applies `org-brain-file-face-template'.") 446 | 447 | (defface org-brain-local-parent 448 | '((t . (:inherit org-brain-parent :weight bold))) 449 | "Face for the entries' local header-entry parent nodes. 450 | File entries also use this, but also applies `org-brain-file-face-template'.") 451 | 452 | (defface org-brain-child 453 | '((t . (:inherit org-brain-button))) 454 | "Face for the entries' linked header-entry child nodes. 455 | File entries also use this, but also applies `org-brain-file-face-template'.") 456 | 457 | (defface org-brain-local-child 458 | '((t . (:inherit org-brain-child :weight bold))) 459 | "Face for the entries' local header-entry child nodes. 460 | File entries also use this, but also applies `org-brain-file-face-template'.") 461 | 462 | (defface org-brain-sibling 463 | '((t . (:inherit org-brain-child))) 464 | "Face for the entries' header-entry sibling nodes. 465 | File entries also use this, but also applies `org-brain-file-face-template'.") 466 | 467 | (defface org-brain-local-sibling 468 | '((t . (:inherit org-brain-sibling :weight bold))) 469 | "Face for the entries' local header-entry sibling nodes. 470 | An entry is a local sibling of another entry if they share a local parent. 471 | File entries also use this, but also applies `org-brain-file-face-template'.") 472 | 473 | (defface org-brain-friend 474 | '((t . (:inherit org-brain-button))) 475 | "Face for the entries' header-entry friend nodes. 476 | File entries also use this, but also applies `org-brain-file-face-template'.") 477 | 478 | (defface org-brain-pinned 479 | '((t . (:inherit org-brain-button))) 480 | 481 | "Face for pinned header entries. 482 | File entries also use this, but also applies `org-brain-file-face-template'.") 483 | 484 | (defface org-brain-selected-list 485 | '((t . (:inherit org-brain-pinned))) 486 | "Face for header entries in the selection list. 487 | File entries also use this, but also applies `org-brain-file-face-template'.") 488 | 489 | (defface org-brain-history-list 490 | '((t . (:inherit org-brain-pinned))) 491 | "Face for header entries in the history list. 492 | File entries also use this, but also applies `org-brain-file-face-template'.") 493 | 494 | (defface org-brain-file-face-template 495 | '((t . (:slant italic))) 496 | "Attributes of this face are added to file-entry faces.") 497 | 498 | (defface org-brain-edge-annotation-face-template 499 | '((t . (:box t))) 500 | "Attributes of this face are added to links which have an edge annotation 501 | to the visualized entry.") 502 | 503 | ;; This needs to be here or defface complains that it is undefined. 504 | (defun org-brain-specified-face-attrs (face &optional frame) 505 | "Return a plist of all face attributes of FACE that are not `unspecified'. 506 | If FRAME is not specified, `selected-frame' is used." 507 | (cl-labels ((alist->plist (alist) 508 | (pcase alist 509 | ('nil nil) 510 | (`((,h1 . ,h2) . ,tail) `(,h1 . (,h2 . ,(alist->plist tail))))))) 511 | (alist->plist (seq-filter 512 | (lambda (f) (not (equal (cdr f) 'unspecified))) 513 | (face-all-attributes face (or frame (selected-frame))))))) 514 | 515 | (defun org-brain-display-face (entry &optional face edge) 516 | "Return the final display face for ENTRY. 517 | Takes FACE as a starting face, or `org-brain-button' if FACE is not specified. 518 | Applies the attributes in `org-brain-edge-annotation-face-template', 519 | `org-brain-selected-face-template', and `org-brain-file-face-template' 520 | as appropriate. 521 | EDGE determines if `org-brain-edge-annotation-face-template' should be used." 522 | (let ((selected-face-attrs 523 | (when (member entry org-brain-selected) 524 | (org-brain-specified-face-attrs 'org-brain-selected-face-template))) 525 | (file-face-attrs 526 | (when (org-brain-filep entry) 527 | (org-brain-specified-face-attrs 'org-brain-file-face-template)))) 528 | (append (list :inherit (or face 'org-brain-button)) 529 | selected-face-attrs 530 | file-face-attrs 531 | (when edge 532 | (org-brain-specified-face-attrs 'org-brain-edge-annotation-face-template))))) 533 | 534 | (defface org-brain-selected-face-template 535 | `((t . ,(org-brain-specified-face-attrs 'highlight))) 536 | "Attributes of this face are added to the faces of selected entries.") 537 | 538 | ;;;; API 539 | 540 | ;; An entry is either a string or a list of three strings. 541 | ;; If a string, then the entry is a file. 542 | ;; If a list, then the entry is a headline: 543 | ;; ("file entry" "headline title" "ID") 544 | ;; There's also a special entry type: Nicknames 545 | ;; In the case of headline nicknames the car of the list is a symbol (instead of a string) 546 | ;; ('alias "headline title" "ID") 547 | 548 | (defvar org-brain--vis-entry nil 549 | "The last entry argument to `org-brain-visualize'.") 550 | 551 | (defvar org-brain--vis-entry-keywords nil 552 | "The `org-brain-keywords' of `org-brain--vis-entry'.") 553 | 554 | (defvar org-brain--vis-history nil 555 | "History previously visualized entries. Newest first.") 556 | 557 | (defvar org-brain-resources-start-re (concat "^[ \t]*:" org-brain-resources-drawer-name ":[ \t]*$") 558 | "Regular expression matching the first line of a resources drawer.") 559 | 560 | (defvar org-brain-keyword-regex "^#\\+[a-zA-Z_]+:" 561 | "Regular expression matching org keywords.") 562 | 563 | (defvar org-brain-pins nil "List of pinned org-brain entries.") 564 | 565 | (defvar org-brain-selected nil "List of selected org-brain entries.") 566 | 567 | (defvar org-brain-headline-cache (make-hash-table :test 'equal) 568 | "Cache for headline entries. Updates when files have been saved.") 569 | 570 | ;;;###autoload 571 | (defun org-brain-update-id-locations () 572 | "Scan `org-brain-files' using `org-id-update-id-locations'." 573 | (interactive) 574 | (org-id-update-id-locations (org-brain-files))) 575 | 576 | ;;;###autoload 577 | (defun org-brain-get-id () 578 | "Get ID of headline at point, creating one if it doesn't exist. 579 | Run `org-brain-new-entry-hook' if a new ID is created." 580 | (interactive) 581 | (or (org-id-get) 582 | (progn 583 | (run-hooks 'org-brain-new-entry-hook) 584 | (org-id-get nil t)))) 585 | 586 | ;;;###autoload 587 | (defun org-brain-switch-brain (directory) 588 | "Choose another DIRECTORY to be your `org-brain-path'." 589 | (interactive "D") 590 | (if (file-equal-p directory org-brain-path) 591 | (message "Current brain already is %s, no switch" directory) 592 | (setq org-brain-path directory) 593 | (setq org-brain-data-file (file-truename (expand-file-name ".org-brain-data.el" org-brain-path))) 594 | (unless (file-exists-p org-brain-data-file) 595 | (org-brain-save-data)) 596 | (setq org-brain-pins nil) 597 | (setq org-brain--vis-history nil) 598 | (load org-brain-data-file t) 599 | (org-brain-update-id-locations) 600 | (message "Switched org-brain to %s" directory))) 601 | 602 | (defun org-brain-maybe-switch-brain () 603 | "Switch brain to `default-directory' if a file named \".org-brain-data.el\" exists there." 604 | (when (and (not (file-equal-p default-directory org-brain-path)) 605 | (file-exists-p (file-truename (expand-file-name ".org-brain-data.el" default-directory)))) 606 | (org-brain-switch-brain default-directory))) 607 | 608 | (defun org-brain-filep (entry) 609 | "Return t if the ENTRY is a (potential) brain file." 610 | (stringp entry)) 611 | 612 | (defun org-brain-save-data () 613 | "Save data to `org-brain-data-file'." 614 | ;; Code adapted from Magnar Sveen's multiple-cursors 615 | (with-temp-file org-brain-data-file 616 | (emacs-lisp-mode) 617 | (dolist (data '(org-brain-pins)) 618 | (insert "(setq " (symbol-name data) "\n" 619 | " '(") 620 | (newline-and-indent) 621 | (mapc #'(lambda (value) 622 | (insert (format "%S" value)) 623 | (newline-and-indent)) 624 | (symbol-value data)) 625 | (insert "))") 626 | (newline)))) 627 | 628 | (defun org-brain-path-entry-name (path) 629 | "Get PATH as an org-brain entry name." 630 | (string-remove-suffix (concat "." org-brain-files-extension) 631 | (file-relative-name (file-truename path) 632 | (file-truename org-brain-path)))) 633 | 634 | (defun org-brain-entry-path (entry &optional check-title) 635 | "Get path of org-brain ENTRY. 636 | If CHECK-TITLE is non-nil, consider that ENTRY might be a file entry title." 637 | (let ((name (if (org-brain-filep entry) 638 | (or (and check-title 639 | org-brain-file-entries-use-title 640 | (cdr 641 | (assoc entry 642 | (mapcar (lambda (x) 643 | (cons (concat (file-name-directory x) 644 | (org-brain-title x)) 645 | x)) 646 | (org-brain-files t))))) 647 | entry) 648 | (car entry)))) 649 | (file-truename (expand-file-name (org-link-unescape (format "%s.%s" name org-brain-files-extension)) 650 | org-brain-path)))) 651 | 652 | (defun org-brain-files (&optional relative) 653 | "Get all org files (recursively) in `org-brain-path'. 654 | If RELATIVE is t, then return relative paths and remove file extension. 655 | Ignores \"dotfiles\"." 656 | (make-directory org-brain-path t) 657 | (if relative 658 | (mapcar #'org-brain-path-entry-name (org-brain-files)) 659 | (if org-brain-scan-directories-recursively 660 | (directory-files-recursively 661 | org-brain-path (format "^[^.].*\\.%s$" org-brain-files-extension)) 662 | (directory-files 663 | org-brain-path t (format "^[^.].*\\.%s$" org-brain-files-extension))))) 664 | 665 | (defvar org-brain-link-re 666 | "\\[\\[\\(\\(?:[^][\\]\\|\\\\\\(?:\\\\\\\\\\)*[][]\\|\\\\+[^][]\\)+\\)]\\(?:\\[\\(\\(?:.\\|\\)+?\\)]\\)?]" 667 | "Regex matching an `org-mode' link. 668 | The first match is the URI, the second is the (optional) desciption. 669 | 670 | This variable should be the same as `org-link-bracket-re'. 671 | However the implementation changed in `org-mode' 9.3 and 672 | the old `org-bracket-link-regexp' had different match groups. 673 | The purpose of `org-brain-link-re' is protection against future changes.") 674 | 675 | (defun org-brain-replace-links-with-visible-parts (raw-str) 676 | "Get RAW-STR with its links replaced by their descriptions." 677 | (let ((ret-str "") 678 | (start 0) 679 | match-start) 680 | (while (setq match-start (string-match org-brain-link-re raw-str start)) 681 | (setq ret-str 682 | (concat ret-str 683 | ;; Include everything not part of the string. 684 | (substring-no-properties raw-str start match-start) 685 | ;; Include either the link description, or the link 686 | ;; destination. 687 | (or (match-string-no-properties 2 raw-str) 688 | (match-string-no-properties 1 raw-str)))) 689 | (setq start (match-end 0))) 690 | (concat ret-str (substring-no-properties raw-str start nil)))) 691 | 692 | (defun org-brain-headline-at (&optional pom) 693 | "Return the full headline of the entry at POM. 694 | 695 | If `org-brain-headline-links-only-show-visible' is nil, the links 696 | will be returned raw (all of the bracket syntax visible.) 697 | 698 | If `org-brain-headline-links-only-show-visible' is non-nil, 699 | returns only the visible parts of links in the heading. (For any 700 | links that have descriptions, only the descriptions will be 701 | returned.) 702 | 703 | This is done via regex, and does not depend on org-mode's 704 | visibility rendering/formatting in-buffer." 705 | (let ((pom (or pom (point)))) 706 | (if org-brain-headline-links-only-show-visible 707 | (org-brain-replace-links-with-visible-parts (org-entry-get pom "ITEM")) 708 | (org-entry-get pom "ITEM")))) 709 | 710 | (defun org-brain--headline-entry-at-point (&optional create-id) 711 | "Get headline entry at point. 712 | If CREATE-ID is non-nil, call `org-brain-get-id' first." 713 | (if create-id (org-brain-get-id)) 714 | (when-let ((id (org-entry-get (point) "ID"))) 715 | (list (org-brain-path-entry-name buffer-file-name) 716 | (org-brain-headline-at (point)) id))) 717 | 718 | (defun org-brain-entry-at-point-excludedp () 719 | "Return t if the entry at point is tagged as being excluded from org-brain." 720 | (let ((tags (org-get-tags))) 721 | (or (member org-brain-exclude-tree-tag tags) 722 | (and (member org-brain-exclude-children-tag tags) 723 | (not (member org-brain-exclude-children-tag 724 | (org-get-tags nil t))))))) 725 | 726 | (defun org-brain-id-exclude-taggedp (id) 727 | "Return t if ID is tagged as being excluded from org-brain." 728 | (org-with-point-at (org-id-find id t) 729 | (org-brain-entry-at-point-excludedp))) 730 | 731 | (defun org-brain--name-and-id-at-point () 732 | "Get name and id of headline entry at point. 733 | Respect excluded entries." 734 | (unless (org-brain-entry-at-point-excludedp) 735 | (when-let ((id (org-entry-get (point) "ID"))) 736 | (list (org-brain-headline-at (point)) id)))) 737 | 738 | (defun org-brain--nicknames-at-point () 739 | "Get nicknames of the headline entry at point." 740 | (when-let ((id (org-entry-get (point) "ID"))) 741 | (mapcar (lambda (nickname) 742 | (list 'nickname nickname id)) 743 | (org-entry-get-multivalued-property (point) "NICKNAMES")))) 744 | 745 | (defun org-brain-headline-entries-in-file (file &optional no-temp-buffer) 746 | "Get a list of all headline (and nicknames) entries in FILE. 747 | If the entries are cached in `org-brain-headline-cache', get them from there. 748 | Else the FILE is inserted in a temp buffer and get scanned for entries. 749 | If NO-TEMP-BUFFER is non-nil, run the scanning in the current buffer instead." 750 | (if no-temp-buffer 751 | (let ((cached (gethash file org-brain-headline-cache nil))) 752 | (if (or (not cached) 753 | (not (equal (car cached) 754 | (file-attribute-modification-time 755 | (file-attributes file))))) 756 | (let ((file-entry (org-brain-path-entry-name file))) 757 | (insert-file-contents file nil nil nil 'replace) 758 | (cdr (puthash file (cons (file-attribute-modification-time 759 | (file-attributes file)) 760 | (apply #'append 761 | (mapcar (lambda (entry) (cons file-entry entry)) 762 | (remove nil (org-map-entries 763 | #'org-brain--name-and-id-at-point))) 764 | (remove nil (org-map-entries #'org-brain--nicknames-at-point)))) 765 | org-brain-headline-cache))) 766 | (cdr cached))) 767 | (with-temp-buffer 768 | (delay-mode-hooks 769 | (org-mode) 770 | (org-brain-headline-entries-in-file file t))))) 771 | 772 | (defun org-brain-headline-entries (&optional include-nicknames) 773 | "Get all org-brain headline entries. 774 | INCLUDE-NICKNAMES also return duplicates for headlines with NICKNAMES property." 775 | (with-temp-buffer 776 | (delay-mode-hooks 777 | (org-mode) 778 | (apply #'append 779 | (mapcar 780 | (lambda (file) 781 | (seq-filter 782 | (if include-nicknames 783 | #'identity 784 | (lambda (x) (stringp (car x)))) 785 | (org-brain-headline-entries-in-file file t))) 786 | (org-brain-files)))))) 787 | 788 | (defun org-brain-entry-from-id (id) 789 | "Get entry from ID." 790 | (unless org-id-locations (org-id-locations-load)) 791 | (when-let ((path (gethash id org-id-locations))) 792 | (list (org-brain-path-entry-name path) 793 | (org-brain-headline-at (org-id-find id t)) 794 | id))) 795 | 796 | (defun org-brain-entry-identifier (entry) 797 | "Get identifier of ENTRY. 798 | The identifier is an id if ENTRY is a headline. 799 | If ENTRY is file, then the identifier is the relative file name." 800 | (if (org-brain-filep entry) 801 | (org-entry-protect-space entry) 802 | (nth 2 entry))) 803 | 804 | (defun org-brain-entry-at-pt (&optional create-id) 805 | "Get current org-brain entry. 806 | CREATE-ID asks to create an ID öif there isn't one already." 807 | (cond ((eq major-mode 'org-mode) 808 | (unless (string-prefix-p (file-truename org-brain-path) 809 | (file-truename (buffer-file-name))) 810 | (error "Not in a brain file")) 811 | (if org-brain-scan-for-header-entries 812 | (if (ignore-errors (org-get-heading)) 813 | (or (org-brain--headline-entry-at-point) 814 | (when create-id 815 | (let ((closest-parent 816 | (save-excursion 817 | (let ((e)) 818 | (while (and (not e) (org-up-heading-safe)) 819 | (setq e (org-brain--headline-entry-at-point))) 820 | (or e 821 | (when org-brain-include-file-entries 822 | (org-brain-path-entry-name (buffer-file-name)))))))) 823 | (if (y-or-n-p 824 | (format "'%s' has no ID, create one%s? " 825 | (org-brain-headline-at) 826 | (if closest-parent 827 | (format " [else use local parent '%s']" 828 | (org-brain-title closest-parent)) 829 | ""))) 830 | (org-brain--headline-entry-at-point t) 831 | (or (org-brain-entry-at-pt) (error "No entry at pt")))))) 832 | (if org-brain-include-file-entries 833 | (org-brain-path-entry-name (buffer-file-name)) 834 | (error "Not under an org headline, and org-brain-include-file-entries is nil"))) 835 | (org-brain-path-entry-name (buffer-file-name)))) 836 | ((eq major-mode 'org-brain-visualize-mode) 837 | org-brain--vis-entry) 838 | (t 839 | (error "Not in org-mode or org-brain-visualize")))) 840 | 841 | (defun org-brain-entry-name (entry) 842 | "Get name string of ENTRY." 843 | (if (org-brain-filep entry) 844 | (if org-brain-file-entries-use-title 845 | (concat (file-name-directory entry) (org-brain-title entry)) 846 | entry) 847 | (format org-brain-headline-entry-name-format-string 848 | (org-brain-entry-name (car entry)) (cadr entry)))) 849 | 850 | (defun org-brain-entry-data (entry) 851 | "Run `org-element-parse-buffer' on ENTRY text." 852 | (with-temp-buffer 853 | (insert (org-brain-text entry t)) 854 | (org-element-parse-buffer))) 855 | 856 | (defun org-brain--file-targets (file) 857 | "Return alist of (name . entry-id) for all entries in FILE. 858 | The list also includes nicknames from the NICKNAMES keyword/properties. 859 | Should only be used in a temp-buffer." 860 | (let* ((file-relative (org-brain-path-entry-name file)) 861 | (file-entry-name (org-brain-entry-name file-relative))) 862 | (remove 863 | nil 864 | (append 865 | (when org-brain-include-file-entries 866 | (apply 867 | #'append 868 | (list (cons file-entry-name file-relative)) 869 | (mapcar (lambda (x) 870 | (list (cons (org-entry-restore-space x) file-relative))) 871 | (when-let ((nicknames (assoc "NICKNAMES" (org-brain-keywords file-relative)))) 872 | (split-string (cdr nicknames) " " t))))) 873 | (mapcar 874 | (lambda (x) 875 | (cons (format org-brain-headline-entry-name-format-string 876 | file-entry-name 877 | (nth 1 x)) 878 | (nth 2 x))) 879 | (org-brain-headline-entries-in-file file t)))))) 880 | 881 | (defun org-brain--all-targets () 882 | "Get an alist with (name . entry-id) of all targets in org-brain. 883 | `org-brain-include-file-entries' and `org-brain-scan-for-header-entries' 884 | affect the fetched targets." 885 | (if org-brain-scan-for-header-entries 886 | (with-temp-buffer 887 | (delay-mode-hooks 888 | (org-mode) 889 | (mapcan #'org-brain--file-targets 890 | (org-brain-files)))) 891 | (mapcar (lambda (x) (cons (org-brain-entry-name x) x)) 892 | (org-brain-files t)))) 893 | 894 | (defun org-brain-completing-read (prompt choices &optional predicate require-match initial-input hist def inherit-input) 895 | "A version of `completing-read' which is tailored to `org-brain-completion-system'." 896 | (let ((args (list prompt choices predicate require-match initial-input hist def inherit-input))) 897 | (or (pcase org-brain-completion-system 898 | ('default (apply #'completing-read args)) 899 | ('ido (apply #'ido-completing-read args)) 900 | ('ivy (apply #'ivy-completing-read args)) 901 | ('helm (apply #'helm-completing-read-default-1 902 | (append args '("org-brain" "*org-brain-helm*"))))) 903 | (funcall org-brain-completion-system prompt choices)))) 904 | 905 | (defun org-brain-get-entry-from-title (title &optional targets) 906 | "Search for TITLE in TARGETS and return an entry. Create it if non-existing. 907 | TARGETS is an alist of (title . entry-id). 908 | If TARGETS is nil then use `org-brain--all-targets'." 909 | (unless org-id-locations (org-id-locations-load)) 910 | (let* ((targets (or targets (org-brain--all-targets))) 911 | (id (or (cdr (assoc title targets)) title))) 912 | (or 913 | ;; Headline entry exists, return it 914 | (org-brain-entry-from-id id) 915 | ;; File entry 916 | (progn 917 | (setq id (split-string id "::" t)) 918 | (let* ((entry-path (org-brain-entry-path (car id) t)) 919 | (entry-file (org-brain-path-entry-name entry-path))) 920 | (unless (file-exists-p entry-path) 921 | (if (and org-brain-default-file-parent (equal (length id) 1)) 922 | (setq entry-file org-brain-default-file-parent 923 | id `(,org-brain-default-file-parent ,(car id))) 924 | (make-directory (file-name-directory entry-path) t) 925 | (write-region "" nil entry-path))) 926 | (if (or (not org-brain-include-file-entries) 927 | (equal (length id) 2) 928 | (not (equal (car id) entry-file))) 929 | ;; Create new headline entry in file 930 | (org-with-point-at (org-brain-entry-marker entry-file) 931 | (if (and (not org-brain-include-file-entries) 932 | (or 933 | ;; Search heading without tags 934 | (save-excursion 935 | (re-search-forward (concat "\n\\* +" (regexp-quote (car id)) "[ \t]*$") nil t)) 936 | ;; Search heading with tags 937 | (save-excursion 938 | (re-search-forward (concat "\n\\* +" (regexp-quote (car id)) "[ \t]+:.*:$") nil t)))) 939 | (org-brain-entry-at-pt) 940 | (goto-char (point-max)) 941 | (insert (concat "\n* " (or (cadr id) (car id)))) 942 | (let ((new-id (org-brain-get-id))) 943 | (save-buffer) 944 | (list entry-file (or (cadr id) (car id)) new-id)))) 945 | entry-file)))))) 946 | 947 | ;;;###autoload 948 | (defun org-brain-add-entry (title) 949 | "Add a new entry named TITLE." 950 | (interactive "sNew entry: ") 951 | (message "Added new entry: '%s'" 952 | (org-brain-entry-name (org-brain-get-entry-from-title title)))) 953 | 954 | (defun org-brain-choose-entries (prompt entries &optional predicate require-match initial-input hist def inherit-input-method) 955 | "PROMPT for one or more ENTRIES, separated by `org-brain-entry-separator'. 956 | ENTRIES can be a list, or 'all which lists all headline and file entries. 957 | Return the prompted entries in a list. 958 | Very similar to `org-brain-choose-entry', but can return several entries. 959 | 960 | For PREDICATE, REQUIRE-MATCH, INITIAL-INPUT, HIST, DEF and 961 | INHERIT-INPUT-METHOD see `completing-read'." 962 | (let* ((targets (if (eq entries 'all) 963 | (org-brain--all-targets) 964 | (mapcar (lambda (x) 965 | (cons (org-brain-entry-name x) 966 | (if (org-brain-filep x) 967 | x 968 | (nth 2 x)))) 969 | entries))) 970 | (choices (org-brain-completing-read prompt targets 971 | predicate require-match initial-input hist def inherit-input-method))) 972 | (mapcar (lambda (title) (org-brain-get-entry-from-title title targets)) 973 | (if org-brain-entry-separator 974 | (split-string choices org-brain-entry-separator) 975 | (list choices))))) 976 | 977 | (defun org-brain-choose-entry (prompt entries &optional predicate require-match initial-input hist def inherit-input-method) 978 | "PROMPT for an entry from ENTRIES and return it. 979 | ENTRIES can be 'all, which lists all headline and file entries. 980 | For PREDICATE, REQUIRE-MATCH, INITIAL-INPUT, HIST, DEF and INHERIT-INPUT-METHOD see `completing-read'." 981 | (let ((org-brain-entry-separator nil)) 982 | (car (org-brain-choose-entries prompt entries predicate require-match initial-input hist def inherit-input-method)))) 983 | 984 | (defun org-brain-first-headline-position () 985 | "Get position of first headline in buffer. `point-max' if no headline exists." 986 | (save-excursion 987 | (goto-char (point-min)) 988 | (or (looking-at-p org-heading-regexp) 989 | (outline-next-heading) 990 | (goto-char (point-max))) 991 | (point))) 992 | 993 | (defun org-brain-keywords (entry) 994 | "Get alist of `org-mode' keywords and their values in file ENTRY." 995 | (if (org-brain-filep entry) 996 | (with-temp-buffer 997 | (insert 998 | (with-temp-buffer 999 | (ignore-errors (insert-file-contents (org-brain-entry-path entry))) 1000 | (buffer-substring-no-properties (point-min) (org-brain-first-headline-position)))) 1001 | (org-element-map (org-element-parse-buffer) 'keyword 1002 | (lambda (kw) 1003 | (cons (org-element-property :key kw) 1004 | (org-element-property :value kw))))) 1005 | (error "Only file entries have keywords"))) 1006 | 1007 | (defun org-brain-get-tags (entry &optional inherit) 1008 | "Return the tags at ENTRY. Only use local tags unless INHERIT is non-nil. 1009 | Works for both file and headline entries." 1010 | (if (org-brain-filep entry) 1011 | (ignore-errors 1012 | (split-string 1013 | (cdr (assoc "FILETAGS" (org-brain-keywords entry))) ":" t)) 1014 | (org-with-point-at 1015 | (org-brain-entry-marker entry) 1016 | (org-get-tags nil (not inherit))))) 1017 | 1018 | (defun org-brain-entry-tags-string (entry) 1019 | "Get a string of ENTRY's local tags." 1020 | (let ((tags (string-join (org-brain-get-tags entry) ":"))) 1021 | (if (string-empty-p tags) 1022 | "" 1023 | (concat ":" tags ":")))) 1024 | 1025 | (defun org-brain-entry-todo-state (entry) 1026 | "Get the todo-state of ENTRY. 1027 | Only works on headline entries." 1028 | (if (org-brain-filep entry) 1029 | "" 1030 | (org-with-point-at (org-brain-entry-marker entry) 1031 | (or (org-get-todo-state) "")))) 1032 | 1033 | (defun org-brain--missing-id-error (entry) 1034 | "Error message to be shown if id of ENTRY isn't found by `org-id-find'." 1035 | (error "Couldn't find entry %s, try running org-brain-update-id-locations. " 1036 | (org-brain-entry-name entry))) 1037 | 1038 | (defun org-brain-entry-marker (entry) 1039 | "Get marker to ENTRY." 1040 | (if (org-brain-filep entry) 1041 | (let ((path (org-brain-entry-path entry))) 1042 | (if (file-exists-p path) 1043 | (set-marker (make-marker) 0 1044 | (or (org-find-base-buffer-visiting path) 1045 | (find-file-noselect path))) 1046 | ;; If file doesn't exists, it is probably an id 1047 | (or (org-id-find entry t) 1048 | (org-brain--missing-id-error entry)))) 1049 | (or (org-id-find (nth 2 entry) t) 1050 | (org-brain--missing-id-error entry)))) 1051 | 1052 | (defun org-brain-title (entry &optional capped) 1053 | "Get title of ENTRY. If CAPPED is t, max length is `org-brain-title-max-length'." 1054 | (let ((title 1055 | (if (org-brain-filep entry) 1056 | (or (cdr (assoc "TITLE" (org-brain-keywords entry))) 1057 | (car (last (split-string entry "/" t)))) 1058 | (nth 1 entry)))) 1059 | (if (and capped (> org-brain-title-max-length 0) (> (length title) org-brain-title-max-length)) 1060 | (concat (substring title 0 (1- org-brain-title-max-length)) "…") 1061 | title))) 1062 | 1063 | (defun org-brain-text-positions (entry &optional all-data) 1064 | "Get the beginning and end position of the ENTRY text. 1065 | Only get the body text, unless ALL-DATA is t." 1066 | (if (org-brain-filep entry) 1067 | ;; File entry 1068 | (with-temp-buffer 1069 | (ignore-errors (insert-file-contents (org-brain-entry-path entry))) 1070 | (goto-char (org-brain-first-headline-position)) 1071 | (list 1072 | (if all-data 1073 | (point-min) 1074 | (or (save-excursion 1075 | (when (re-search-backward org-brain-keyword-regex nil t) 1076 | (end-of-line) 1077 | (point))) 1078 | (point-min))) 1079 | (if (let ((filetags (org-brain-get-tags entry))) 1080 | (or org-brain-show-full-entry 1081 | (member org-brain-show-children-tag filetags) 1082 | (member org-brain-exclude-children-tag filetags))) 1083 | (point-max) 1084 | (point)))) 1085 | ;; Headline entry 1086 | (org-with-point-at (org-brain-entry-marker entry) 1087 | (let ((tags (org-get-tags nil t))) 1088 | (unless (and (member org-brain-exclude-text-tag tags) 1089 | (not all-data)) 1090 | (unless all-data 1091 | (goto-char (cdr (org-get-property-block))) 1092 | (end-of-line)) 1093 | (let (end) 1094 | (save-excursion 1095 | (or (and (not org-brain-show-full-entry) 1096 | (not (member org-brain-exclude-children-tag tags)) 1097 | (not (member org-brain-show-children-tag tags)) 1098 | (org-goto-first-child)) 1099 | (org-end-of-subtree t)) 1100 | (setq end (point))) 1101 | (list (point) end))))))) 1102 | 1103 | (defun org-brain-text (entry &optional all-data) 1104 | "Get the text of ENTRY as string. 1105 | Only get the body text, unless ALL-DATA is t." 1106 | (when-let ((entry-text 1107 | (if (org-brain-filep entry) 1108 | ;; File entry 1109 | (with-temp-buffer 1110 | (ignore-errors (insert-file-contents (org-brain-entry-path entry))) 1111 | (apply #'buffer-substring-no-properties 1112 | (org-brain-text-positions entry all-data))) 1113 | ;; Headline entry 1114 | (org-with-point-at (org-brain-entry-marker entry) 1115 | (apply #'buffer-substring-no-properties 1116 | (org-brain-text-positions entry all-data)))))) 1117 | (if all-data 1118 | (org-remove-indentation entry-text) 1119 | (with-temp-buffer 1120 | (insert (org-remove-indentation entry-text)) 1121 | (goto-char (org-brain-first-headline-position)) 1122 | (if (re-search-backward org-brain-resources-start-re nil t) 1123 | (progn 1124 | (end-of-line) 1125 | (re-search-forward org-drawer-regexp nil t)) 1126 | (goto-char (point-min))) 1127 | (buffer-substring (point) (point-max)))))) 1128 | 1129 | (defun org-brain-parents (entry) 1130 | "Get parents of ENTRY. 1131 | Often you want the siblings too, then use `org-brain-siblings' instead." 1132 | (delete-dups 1133 | (append (org-brain--linked-property-entries entry org-brain-parents-property-name) 1134 | (org-brain-local-parent entry)))) 1135 | 1136 | (defun org-brain-local-parent (entry) 1137 | "Get file local parent of ENTRY, as a list." 1138 | (if-let ((parent 1139 | (unless (org-brain-filep entry) 1140 | (org-with-point-at (org-brain-entry-marker entry) 1141 | (if (and (org-up-heading-safe) 1142 | (org-entry-get nil "ID")) 1143 | (org-brain-entry-from-id (org-entry-get nil "ID")) 1144 | (when (and org-brain-include-file-entries 1145 | (not (member org-brain-exclude-local-parent-tag 1146 | (org-brain-get-tags (car entry))))) 1147 | (car entry))))))) 1148 | (list parent))) 1149 | 1150 | (defun org-brain-children (entry) 1151 | "Get children of ENTRY." 1152 | (delete-dups 1153 | (append (org-brain--linked-property-entries entry org-brain-children-property-name) 1154 | (org-brain-local-children entry)))) 1155 | 1156 | (defun org-brain-local-children (entry) 1157 | "Get file local children of ENTRY." 1158 | (remove 1159 | entry 1160 | (if (org-brain-filep entry) 1161 | ;; File entry 1162 | (with-temp-buffer 1163 | (ignore-errors (insert-file-contents (org-brain-entry-path entry))) 1164 | (org-element-map (org-element-parse-buffer 'headline) 'headline 1165 | (lambda (headline) 1166 | (when-let ((id (org-element-property :ID headline))) 1167 | (unless (org-brain-id-exclude-taggedp id) 1168 | (org-brain-entry-from-id id)))) 1169 | nil nil 'headline)) 1170 | ;; Headline entry 1171 | (org-with-point-at (org-brain-entry-marker entry) 1172 | (let (children) 1173 | (deactivate-mark) 1174 | (org-mark-subtree) 1175 | (org-goto-first-child) 1176 | (setq children 1177 | (org-map-entries 1178 | (lambda () (org-brain-entry-from-id (org-entry-get nil "ID"))) 1179 | t 'region-start-level 1180 | (lambda () 1181 | (let ((id (org-entry-get nil "ID"))) 1182 | (when (or (not id) 1183 | (org-brain-id-exclude-taggedp id)) 1184 | (save-excursion 1185 | (outline-next-heading) 1186 | (point))))))) 1187 | (deactivate-mark) 1188 | children))))) 1189 | 1190 | (defun org-brain-descendants (entry) 1191 | "Get all entries which descend from ENTRY. 1192 | In other words get all the children, grand children, grand-grand children, etc. 1193 | The ENTRY itself is also included in the returned list." 1194 | (let ((checked nil)) 1195 | (cl-labels ((collect-descendants 1196 | (e) 1197 | (unless (member e checked) 1198 | (push e checked) 1199 | (mapc #'collect-descendants (org-brain-children e))))) 1200 | (collect-descendants entry) 1201 | checked))) 1202 | 1203 | (defun org-brain-local-descendants (entry) 1204 | "Return the local descendants of ENTRY (excluding ENTRY itself). 1205 | Similar to `org-brain-descendants' but only for local children." 1206 | (remove 1207 | entry 1208 | (if (org-brain-filep entry) 1209 | ;; File entry 1210 | (with-temp-buffer 1211 | (ignore-errors (insert-file-contents (org-brain-entry-path entry))) 1212 | (org-element-map (org-element-parse-buffer 'headline) 'headline 1213 | (lambda (headline) 1214 | (when-let ((id (org-element-property :ID headline))) 1215 | (unless (org-brain-id-exclude-taggedp id) 1216 | (org-brain-entry-from-id id)))))) 1217 | ;; Headline entry 1218 | (org-with-point-at (org-brain-entry-marker entry) 1219 | (org-map-entries 1220 | (lambda () (org-brain-entry-from-id (org-entry-get nil "ID"))) 1221 | t 'tree 1222 | (lambda () 1223 | (let ((id (org-entry-get nil "ID"))) 1224 | (when (or (not id) 1225 | (org-brain-id-exclude-taggedp id)) 1226 | (or (outline-next-heading) 1227 | (point)))))))))) 1228 | 1229 | (defun org-brain-siblings (entry) 1230 | "Get siblings of ENTRY. 1231 | Return an alist where key = parent, value = siblings from that parent." 1232 | (delete-dups 1233 | (mapcar 1234 | (lambda (parent) 1235 | (cons parent (remove entry (org-brain-children parent)))) 1236 | (org-brain-parents entry)))) 1237 | 1238 | (defun org-brain-friends (entry) 1239 | "Get friends of ENTRY." 1240 | (delete-dups (org-brain--linked-property-entries entry org-brain-friends-property-name))) 1241 | 1242 | (defun org-brain-resources (entry) 1243 | "Get alist of links in ENTRY, excluding `org-brain-ignored-resource-links'. 1244 | A link can be either an org link or an org attachment. 1245 | The car is the raw-link and the cdr is the description." 1246 | (let ((links 1247 | (delete-dups 1248 | (with-temp-buffer 1249 | (insert (org-brain-text entry t)) 1250 | (org-element-map (org-brain-entry-data entry) 'link 1251 | (lambda (link) 1252 | (unless (member (org-element-property :type link) 1253 | org-brain-ignored-resource-links) 1254 | (cons (org-element-property :raw-link link) 1255 | (when-let ((beg (org-element-property :contents-begin link)) 1256 | (end (org-element-property :contents-end link))) 1257 | (replace-regexp-in-string 1258 | "[ \t\n\r]+" " " (buffer-substring beg end)))))) 1259 | nil nil t))))) 1260 | (if (org-brain-filep entry) 1261 | links 1262 | ;; Headline entry 1263 | (org-with-point-at (org-brain-entry-marker entry) 1264 | (unless (member org-brain-exclude-resouces-tag (org-get-tags nil t)) 1265 | (append links 1266 | ;; Attachments 1267 | (when-let ((attach-dir (org-attach-dir))) 1268 | (mapcar (lambda (attachment) 1269 | (cons (format "file:%s" 1270 | (org-link-escape 1271 | (file-truename (expand-file-name attachment attach-dir)))) 1272 | attachment)) 1273 | (org-attach-file-list attach-dir))))))))) 1274 | 1275 | (defun org-brain--choose-resource (entries) 1276 | "Use `completing-read' to get link to a resource from ENTRIES." 1277 | (let ((resources (mapcan 1278 | (lambda (entry) 1279 | (mapcar (lambda (x) 1280 | (cons (or (cdr x) (car x)) (car x))) 1281 | (org-brain-resources entry))) 1282 | entries))) 1283 | (if (equal (length resources) 1) 1284 | (cdar resources) 1285 | (cdr (assoc (org-brain-completing-read "Resource: " resources nil t) resources))))) 1286 | 1287 | ;;;###autoload 1288 | (defun org-brain-open-resource (entry) 1289 | "Choose and open a resource from ENTRY. 1290 | If run with `\\[universal-argument]' then also choose from descendants of ENTRY. 1291 | Uses `org-brain-entry-at-pt' for ENTRY, or asks for it if none at point." 1292 | (interactive (list (or (ignore-errors (org-brain-entry-at-pt t)) 1293 | (org-brain-choose-entry "Resource from: " 'all)))) 1294 | (org-open-link-from-string 1295 | (format "[[%s]]" (org-brain--choose-resource 1296 | (if current-prefix-arg 1297 | (org-brain-descendants entry) 1298 | (list entry)))))) 1299 | 1300 | (defun org-brain--linked-property-entries (entry property) 1301 | "Get list of entries linked to in ENTRY by PROPERTY. 1302 | PROPERTY could for instance be `org-brain-children-property-name'." 1303 | (let ((propertylist 1304 | (if (org-brain-filep entry) 1305 | ;; File entry 1306 | (mapcar 1307 | (lambda (x) (or (org-brain-entry-from-id x) x)) 1308 | (mapcar #'org-entry-restore-space 1309 | (when-let ((kw-values (cdr (assoc property 1310 | (org-brain-keywords entry))))) 1311 | (org-split-string kw-values "[ \t]+")))) 1312 | ;; Headline entry 1313 | (mapcar 1314 | (lambda (x) (or (org-brain-entry-from-id x) x)) 1315 | (org-entry-get-multivalued-property (org-brain-entry-marker entry) property))))) 1316 | (if (equal propertylist '("")) nil propertylist))) 1317 | 1318 | (defun org-brain-add-relationship (parent child) 1319 | "Add external relationship between PARENT and CHILD." 1320 | (when (equal parent child) 1321 | (error "An entry can't be a parent/child to itself")) 1322 | (unless (member child (org-brain-children parent)) 1323 | (org-save-all-org-buffers) 1324 | (if (org-brain-filep parent) 1325 | ;; Parent = File 1326 | (org-with-point-at (org-brain-entry-marker parent) 1327 | (goto-char (point-min)) 1328 | (if (re-search-forward (concat "^#\\+" org-brain-children-property-name ":.*$") nil t) 1329 | (insert (concat " " (org-brain-entry-identifier child))) 1330 | (insert (concat "#+" org-brain-children-property-name ": " 1331 | (org-brain-entry-identifier child) 1332 | "\n\n")))) 1333 | ;; Parent = Headline 1334 | (org-entry-add-to-multivalued-property (org-brain-entry-marker parent) 1335 | org-brain-children-property-name 1336 | (org-brain-entry-identifier child))) 1337 | (if (org-brain-filep child) 1338 | ;; Child = File 1339 | (org-with-point-at (org-brain-entry-marker child) 1340 | (goto-char (point-min)) 1341 | (if (re-search-forward (concat "^#\\+" org-brain-parents-property-name ":.*$") nil t) 1342 | (insert (concat " " (org-brain-entry-identifier parent))) 1343 | (insert (concat "#+" org-brain-parents-property-name ": " 1344 | (org-brain-entry-identifier parent) 1345 | "\n\n")))) 1346 | ;; Child = Headline 1347 | (org-entry-add-to-multivalued-property (org-brain-entry-marker child) 1348 | org-brain-parents-property-name 1349 | (org-brain-entry-identifier parent))) 1350 | (org-save-all-org-buffers))) 1351 | 1352 | (defun org-brain-delete-current-line (&optional match-regex) 1353 | "Delete whole line at `point', and the newline. 1354 | Optionally only delete if matching MATCH-REGEX." 1355 | (when (or (not match-regex) 1356 | (string-match match-regex (buffer-substring 1357 | (line-beginning-position) 1358 | (line-end-position)))) 1359 | (delete-region (line-beginning-position) 1360 | (progn (forward-line 1) (point))))) 1361 | 1362 | (defun org-brain-remove-relationship (parent child) 1363 | "Remove external relationship between PARENT and CHILD." 1364 | (unless (member child (org-brain-children parent)) 1365 | (error "Relationship doesn't exist")) 1366 | (org-save-all-org-buffers) 1367 | (if (org-brain-filep parent) 1368 | ;; Parent = File 1369 | (org-with-point-at (org-brain-entry-marker parent) 1370 | (goto-char (point-min)) 1371 | (re-search-forward (concat "^#\\+" org-brain-children-property-name ":.*$")) 1372 | (beginning-of-line) 1373 | (re-search-forward (concat " " (regexp-quote (org-brain-entry-identifier child)))) 1374 | (replace-match "") 1375 | (org-brain-delete-current-line (concat "^#\\+" org-brain-children-property-name ":[[:space:]]*$")) 1376 | (org-brain-delete-current-line "^[[:space:]]*$") 1377 | (save-buffer)) 1378 | ;; Parent = Headline 1379 | (org-entry-remove-from-multivalued-property (org-brain-entry-marker parent) 1380 | org-brain-children-property-name 1381 | (org-brain-entry-identifier child))) 1382 | (if (org-brain-filep child) 1383 | ;; Child = File 1384 | (org-with-point-at (org-brain-entry-marker child) 1385 | (goto-char (point-min)) 1386 | (re-search-forward (concat "^#\\+" org-brain-parents-property-name ":.*$")) 1387 | (beginning-of-line) 1388 | (re-search-forward (concat " " (regexp-quote (org-brain-entry-identifier parent)))) 1389 | (replace-match "") 1390 | (org-brain-delete-current-line (concat "^#\\+" org-brain-parents-property-name ":[[:space:]]*$")) 1391 | (org-brain-delete-current-line "^[[:space:]]*$") 1392 | (save-buffer)) 1393 | ;; Child = Headline 1394 | (org-entry-remove-from-multivalued-property (org-brain-entry-marker child) 1395 | org-brain-parents-property-name 1396 | (org-brain-entry-identifier parent))) 1397 | (org-save-all-org-buffers)) 1398 | 1399 | ;;;; Buffer commands 1400 | 1401 | ;;;###autoload 1402 | (defun org-brain-add-child (entry children &optional verbose) 1403 | "Add external CHILDREN (a list of entries) to ENTRY. 1404 | If called interactively use `org-brain-entry-at-pt' and let user choose entry. 1405 | Using `\\[universal-argument]' will use `org-brain-button-at-point' as ENTRY. 1406 | If chosen CHILD entry doesn't exist, create it as a new file. 1407 | Several children can be added, by using `org-brain-entry-separator'. 1408 | If VERBOSE is non-nil then display a message." 1409 | (interactive (list (if current-prefix-arg 1410 | (car (org-brain-button-at-point)) 1411 | (org-brain-entry-at-pt t)) 1412 | (org-brain-choose-entries "Add child: " 'all) 1413 | t)) 1414 | (dolist (child-entry children) 1415 | (org-brain-add-relationship entry child-entry) 1416 | (if verbose (message "Added '%s' as a child of '%s'." 1417 | (org-brain-entry-name child-entry) 1418 | (org-brain-entry-name entry)))) 1419 | (org-brain--revert-if-visualizing)) 1420 | 1421 | ;;;###autoload 1422 | (defun org-brain-add-child-headline (entry child-names &optional verbose) 1423 | "Create new internal child headline(s) to ENTRY named CHILD-NAMES. 1424 | Several children can be created, by using `org-brain-entry-separator'. 1425 | If called interactively use `org-brain-entry-at-pt' and prompt for children. 1426 | Using `\\[universal-argument]' will use `org-brain-button-at-point' as ENTRY. 1427 | If VERBOSE is non-nil then display a message." 1428 | (interactive (list (if current-prefix-arg 1429 | (car (org-brain-button-at-point)) 1430 | (org-brain-entry-at-pt t)) 1431 | (read-string "Add child headline: ") 1432 | t)) 1433 | (dolist (child-name (split-string child-names org-brain-entry-separator)) 1434 | (when (equal (length child-name) 0) 1435 | (error "Child name must be at least 1 character")) 1436 | (if (org-brain-filep entry) 1437 | ;; File entry 1438 | (org-with-point-at (org-brain-entry-marker entry) 1439 | (goto-char (org-brain-first-headline-position)) 1440 | (open-line 1) 1441 | (insert (concat "* " child-name)) 1442 | (org-brain-get-id) 1443 | (save-buffer)) 1444 | ;; Headline entry 1445 | (org-with-point-at (org-brain-entry-marker entry) 1446 | (if (org-goto-first-child) 1447 | (open-line 1) 1448 | (org-end-of-subtree t)) 1449 | (org-insert-heading nil t) 1450 | (org-do-demote) 1451 | (insert child-name) 1452 | (org-brain-get-id) 1453 | (save-buffer))) 1454 | (if verbose (message "Added '%s' as a child of '%s'." 1455 | child-name 1456 | (org-brain-entry-name entry)))) 1457 | (org-brain--revert-if-visualizing)) 1458 | 1459 | (define-obsolete-function-alias 'org-brain-new-child 'org-brain-add-child-headline "0.5") 1460 | 1461 | ;;;###autoload 1462 | (defun org-brain-remove-child (entry child &optional verbose) 1463 | "Remove CHILD from ENTRY. 1464 | If called interactively use `org-brain-entry-at-point' and prompt for CHILD. 1465 | Using `\\[universal-argument]' will use `org-brain-button-at-point' as ENTRY. 1466 | If VERBOSE is non-nil then display a message." 1467 | (interactive (let ((e (if current-prefix-arg 1468 | (car (org-brain-button-at-point)) 1469 | (org-brain-entry-at-pt)))) 1470 | (list e (org-brain-choose-entry "Remove child: " 1471 | (org-brain-children e) 1472 | nil t) 1473 | t))) 1474 | (if (member child (org-brain-local-children entry)) 1475 | (if (and (> (length (org-brain-parents child)) 1) 1476 | (y-or-n-p 1477 | (format "%s is %s's local parent. Would you like to change the local parent of %s? " 1478 | (org-brain-title entry) (org-brain-title child) (org-brain-title child)))) 1479 | (let* ((linked-parents (org-brain--linked-property-entries child org-brain-parents-property-name)) 1480 | (new-parent (if (equal 1 (length linked-parents)) 1481 | (car-safe linked-parents) 1482 | (org-brain-choose-entry "Refile to parent: " linked-parents)))) 1483 | (org-brain-remove-relationship entry (org-brain-change-local-parent child new-parent))) 1484 | (org-brain-delete-entry child)) 1485 | (org-brain-remove-relationship entry child)) 1486 | (if verbose (message "'%s' is no longer a child of '%s'." 1487 | (org-brain-entry-name child) 1488 | (org-brain-entry-name entry))) 1489 | (org-brain--revert-if-visualizing)) 1490 | 1491 | ;;;###autoload 1492 | (defun org-brain-add-parent (entry parents &optional verbose) 1493 | "Add external PARENTS (a list of entries) to ENTRY. 1494 | If called interactively use `org-brain-entry-at-pt' and prompt for PARENT. 1495 | Using `\\[universal-argument]' will use `org-brain-button-at-point' as ENTRY. 1496 | 1497 | If chosen parent entry doesn't exist, create it as a new file. 1498 | Several parents can be added, by using `org-brain-entry-separator'. 1499 | If VERBOSE is non-nil then display a message." 1500 | (interactive (list (if current-prefix-arg 1501 | (car (org-brain-button-at-point)) 1502 | (org-brain-entry-at-pt t)) 1503 | (org-brain-choose-entries "Add parent: " 'all) 1504 | t)) 1505 | (dolist (parent parents) 1506 | (org-brain-add-relationship parent entry) 1507 | (if verbose (message "Added '%s' as a parent of '%s'." 1508 | (org-brain-entry-name parent) 1509 | (org-brain-entry-name entry)))) 1510 | (org-brain--revert-if-visualizing)) 1511 | 1512 | ;;;###autoload 1513 | (defun org-brain-remove-parent (entry parent &optional verbose) 1514 | "Remove PARENT from ENTRY. 1515 | If called interactively use `org-brain-entry-at-pt' and prompt for PARENT. 1516 | Using `\\[universal-argument]' will use `org-brain-button-at-point' as ENTRY." 1517 | (interactive (let ((e (if current-prefix-arg 1518 | (car (org-brain-button-at-point)) 1519 | (org-brain-entry-at-pt)))) 1520 | (list e (org-brain-choose-entry "Remove parent: " 1521 | (org-brain-parents e) 1522 | nil t) 1523 | t))) 1524 | (if (member entry (org-brain-local-children parent)) 1525 | (if-let* ((linked-parents (org-brain--linked-property-entries entry org-brain-parents-property-name)) 1526 | (new-parent (if (equal 1 (length linked-parents)) 1527 | (car-safe linked-parents) 1528 | (org-brain-choose-entry (format "Removing %s's local parent. Refile to: " 1529 | (org-brain-title entry)) 1530 | linked-parents)))) 1531 | (org-brain-remove-relationship parent (org-brain-change-local-parent entry new-parent)) 1532 | (if (and org-brain-default-file-parent 1533 | (y-or-n-p (format "%s has no more parents, move it to %s? " 1534 | (org-brain-title entry) org-brain-default-file-parent))) 1535 | (org-brain-remove-relationship 1536 | parent (org-brain-change-local-parent entry org-brain-default-file-parent)) 1537 | (error "%s is %s's only parent, it can't be removed" 1538 | (org-brain-title parent) (org-brain-title entry)))) 1539 | (org-brain-remove-relationship parent entry)) 1540 | (if verbose (message "'%s' is no longer a parent of '%s'." 1541 | (org-brain-entry-name parent) 1542 | (org-brain-entry-name entry))) 1543 | (org-brain--revert-if-visualizing)) 1544 | 1545 | (defun org-brain--internal-add-friendship (entry1 entry2 &optional oneway) 1546 | "Add friendship between ENTRY1 and ENTRY2. 1547 | If ONEWAY is t, add ENTRY2 as friend of ENTRY1, but not the other way around." 1548 | (when (equal entry1 entry2) 1549 | (error "Can't have an entry as a friend to itself")) 1550 | (unless (member entry2 (org-brain-friends entry1)) 1551 | (if (org-brain-filep entry1) 1552 | ;; Entry1 = File 1553 | (org-with-point-at (org-brain-entry-marker entry1) 1554 | (goto-char (point-min)) 1555 | (if (re-search-forward (concat "^#\\+" org-brain-friends-property-name ":.*$") nil t) 1556 | (insert (concat " " (org-brain-entry-identifier entry2))) 1557 | (insert (concat "#+" org-brain-friends-property-name ": " 1558 | (org-brain-entry-identifier entry2) 1559 | "\n\n"))) 1560 | (save-buffer)) 1561 | ;; Entry1 = Headline 1562 | (org-entry-add-to-multivalued-property (org-brain-entry-marker entry1) 1563 | org-brain-friends-property-name 1564 | (org-brain-entry-identifier entry2)))) 1565 | (unless oneway (org-brain--internal-add-friendship entry2 entry1 t)) 1566 | (org-save-all-org-buffers)) 1567 | 1568 | ;;;###autoload 1569 | (defun org-brain-add-friendship (entry friends &optional verbose) 1570 | "Add a new FRIENDS (a list of entries) to ENTRY. 1571 | If called interactively use `org-brain-entry-at-pt' and prompt for FRIENDS. 1572 | Using `\\[universal-argument]' will use `org-brain-button-at-point' as ENTRY. 1573 | 1574 | If chosen friend entry doesn't exist, create it as a new file. 1575 | Several friends can be added, by using `org-brain-entry-separator'. 1576 | If VERBOSE is non-nil then display a message." 1577 | (interactive (list (if current-prefix-arg 1578 | (car (org-brain-button-at-point)) 1579 | (org-brain-entry-at-pt t)) 1580 | (org-brain-choose-entries "Add friend: " 'all) 1581 | t)) 1582 | (dolist (friend-entry friends) 1583 | (org-brain--internal-add-friendship entry friend-entry) 1584 | (if verbose (message "'%s' and '%s' are now friends." 1585 | (org-brain-entry-name entry) 1586 | (org-brain-entry-name friend-entry)))) 1587 | (org-brain--revert-if-visualizing)) 1588 | 1589 | ;;;###autoload 1590 | (defun org-brain-remove-friendship (entry1 entry2 &optional oneway verbose) 1591 | "Remove friendship between ENTRY1 and ENTRY2. 1592 | If ONEWAY is t, then remove ENTRY2 as a friend of ENTRY1, but not vice versa. 1593 | 1594 | If run interactively, use `org-brain-entry-at-pt' as ENTRY1 and prompt for ENTRY2. 1595 | Using `\\[universal-argument]' will use `org-brain-button-at-point' as ENTRY1. 1596 | If VERBOSE is non-nil then display a message." 1597 | (interactive 1598 | (let ((entry-at-pt (if current-prefix-arg 1599 | (car (org-brain-button-at-point)) 1600 | (org-brain-entry-at-pt)))) 1601 | (list entry-at-pt 1602 | (org-brain-choose-entry "Remove friend: " (org-brain-friends entry-at-pt) nil t) 1603 | nil t))) 1604 | (when (member entry2 (org-brain-friends entry1)) 1605 | (if (org-brain-filep entry1) 1606 | ;; Entry1 = File 1607 | (org-with-point-at (org-brain-entry-marker entry1) 1608 | (goto-char (point-min)) 1609 | (re-search-forward (concat "^#\\+" org-brain-friends-property-name ":.*$")) 1610 | (beginning-of-line) 1611 | (re-search-forward (concat " " (regexp-quote (org-brain-entry-identifier entry2)))) 1612 | (replace-match "") 1613 | (org-brain-delete-current-line (concat "^#\\+" org-brain-friends-property-name ":[[:space:]]*$")) 1614 | (org-brain-delete-current-line "^[[:space:]]*$") 1615 | (save-buffer)) 1616 | ;; Entry2 = Headline 1617 | (org-entry-remove-from-multivalued-property (org-brain-entry-marker entry1) 1618 | org-brain-friends-property-name 1619 | (org-brain-entry-identifier entry2)))) 1620 | (if oneway 1621 | (org-brain--revert-if-visualizing) 1622 | (org-brain-remove-friendship entry2 entry1 t verbose)) 1623 | (org-save-all-org-buffers) 1624 | (if (and (not oneway) verbose) 1625 | (message "'%s' and '%s' are no longer friends." 1626 | (org-brain-entry-name entry1) 1627 | (org-brain-entry-name entry2)))) 1628 | 1629 | ;;;###autoload 1630 | (defun org-brain-goto (&optional entry goto-file-func) 1631 | "Goto buffer and position of org-brain ENTRY. 1632 | If ENTRY isn't specified, ask for the ENTRY. 1633 | Unless GOTO-FILE-FUNC is nil, use `pop-to-buffer-same-window' for opening the entry." 1634 | (interactive) 1635 | (org-brain-stop-wandering) 1636 | (unless entry (setq entry (org-brain-choose-entry "Goto entry: " 'all))) 1637 | (when (and org-brain-quit-after-goto (eq 'major-mode 'org-brain-visualize-mode)) 1638 | (org-brain-visualize-quit)) 1639 | (let ((marker (org-brain-entry-marker entry))) 1640 | (apply (or goto-file-func #'pop-to-buffer-same-window) 1641 | (list (marker-buffer marker))) 1642 | (widen) 1643 | (goto-char (marker-position marker)) 1644 | (when (org-at-heading-p) 1645 | (org-show-entry) 1646 | (org-show-subtree))) 1647 | entry) 1648 | 1649 | (define-obsolete-function-alias 'org-brain-open 'org-brain-goto "0.4") 1650 | 1651 | ;;;###autoload 1652 | (defun org-brain-goto-other-window (&optional entry) 1653 | "Goto buffer and position of org-brain ENTRY in other window. 1654 | If ENTRY isn't specified, ask for the ENTRY." 1655 | (interactive) 1656 | (org-brain-goto entry #'pop-to-buffer)) 1657 | 1658 | ;;;###autoload 1659 | (defun org-brain-goto-end (&optional entry same-window) 1660 | "Like `org-brain-goto', but visits the end of ENTRY. 1661 | If SAME-WINDOW is t, use the current window. 1662 | If ENTRY isn't specified, ask for the ENTRY." 1663 | (interactive) 1664 | (if (org-brain-filep (org-brain-goto entry (if same-window nil #'pop-to-buffer))) 1665 | (or (outline-next-heading) 1666 | (goto-char (point-max))) 1667 | (let ((tags (org-get-tags nil t))) 1668 | (or (and (not (member org-brain-exclude-children-tag tags)) 1669 | (not (member org-brain-show-children-tag tags)) 1670 | (org-goto-first-child)) 1671 | (org-end-of-subtree t))))) 1672 | 1673 | ;;;###autoload 1674 | (defun org-brain-goto-current (&optional same-window) 1675 | "Use `org-brain-goto' on `org-brain-entry-at-pt', in other window.. 1676 | If run with `\\[universal-argument]', or SAME-WINDOW as t, use current window." 1677 | (interactive "P") 1678 | (if same-window 1679 | (org-brain-goto (org-brain-entry-at-pt)) 1680 | (org-brain-goto (org-brain-entry-at-pt) #'pop-to-buffer))) 1681 | 1682 | ;;;###autoload 1683 | (defun org-brain-goto-child (entry &optional all) 1684 | "Goto a child of ENTRY. 1685 | If run interactively, get ENTRY from context. 1686 | If ALL is nil, choose only between externally linked children." 1687 | (interactive (list (org-brain-entry-at-pt))) 1688 | (let* ((entries (if all (org-brain-children entry) 1689 | (org-brain--linked-property-entries 1690 | entry org-brain-children-property-name))) 1691 | (child (cond 1692 | ((equal 1 (length entries)) (car-safe entries)) 1693 | ((not entries) (error (concat entry " has no children"))) 1694 | (t (org-brain-choose-entry "Goto child: " entries nil t))))) 1695 | (org-brain-goto child))) 1696 | 1697 | ;;;###autoload 1698 | (defun org-brain-goto-parent (entry &optional all) 1699 | "Goto a parent of ENTRY. 1700 | If run interactively, get ENTRY from context. 1701 | If ALL is nil, choose only between externally linked parents." 1702 | (interactive (list (org-brain-entry-at-pt))) 1703 | (let* ((entries (if all (org-brain-parents entry) 1704 | (org-brain--linked-property-entries 1705 | entry org-brain-parents-property-name))) 1706 | (parent (cond 1707 | ((equal 1 (length entries)) (car-safe entries)) 1708 | ((not entries) (error (concat entry " has no parent"))) 1709 | (t (org-brain-choose-entry "Goto parent: " entries nil t))))) 1710 | (org-brain-goto parent))) 1711 | 1712 | ;;;###autoload 1713 | (defun org-brain-visualize-parent (entry) 1714 | "Visualize a parent of ENTRY, preferring local parents. 1715 | This allows the user to quickly jump up the hierarchy." 1716 | (interactive (list (org-brain-entry-at-pt))) 1717 | (if-let ((parent (car (or (org-brain-local-parent entry) 1718 | (org-brain-parents entry))))) 1719 | (org-brain-visualize parent) 1720 | (error "This entry has no parent"))) 1721 | 1722 | ;;;###autoload 1723 | (defun org-brain-goto-friend (entry) 1724 | "Goto a friend of ENTRY. 1725 | If run interactively, get ENTRY from context." 1726 | (interactive (list (org-brain-entry-at-pt))) 1727 | (let* ((entries (org-brain--linked-property-entries 1728 | entry org-brain-friends-property-name)) 1729 | (friend (cond 1730 | ((equal 1 (length entries)) (car-safe entries)) 1731 | ((not entries) (error (concat entry " has no friends"))) 1732 | (t (org-brain-choose-entry "Goto friend: " entries nil t))))) 1733 | (org-brain-goto friend))) 1734 | 1735 | ;;;###autoload 1736 | (defun org-brain-refile (max-level) 1737 | "Run `org-refile' to a heading in `org-brain-files', with set MAX-LEVEL. 1738 | When in `org-brain-visualize-mode' the current entry will be refiled. 1739 | If MAX-LEVEL isn't given, use `org-brain-refile-max-level'. 1740 | After refiling, all headlines will be given an id." 1741 | (interactive "p") 1742 | (unless current-prefix-arg 1743 | (setq max-level org-brain-refile-max-level)) 1744 | (let ((org-refile-targets `((org-brain-files . (:maxlevel . ,max-level)))) 1745 | (org-after-refile-insert-hook org-after-refile-insert-hook)) 1746 | (add-hook 'org-after-refile-insert-hook 1747 | (lambda () (org-map-tree 'org-brain-get-id))) 1748 | (if (eq major-mode 'org-brain-visualize-mode) 1749 | (if (org-brain-filep org-brain--vis-entry) 1750 | (user-error "Only headline entries can be refiled") 1751 | (org-with-point-at (org-brain-entry-marker org-brain--vis-entry) 1752 | (org-refile)) 1753 | (org-brain--revert-if-visualizing)) 1754 | (org-refile)))) 1755 | 1756 | (defun org-brain-refile-to (entry parent) 1757 | "Refile ENTRY to be a local child of PARENT, returning the new refiled entry. 1758 | 1759 | If ENTRY is linked to PARENT before the refile, this relationship is removed. 1760 | Pins, history, and selected lists are updated 1761 | to account for the change in ENTRY's local parent." 1762 | (when (member parent (org-brain-local-descendants entry)) 1763 | (error "Cannot refile. New parent %s is a local descendant of %s" 1764 | (org-brain-title parent) (org-brain-title entry))) 1765 | (when (org-brain-filep entry) 1766 | (error "Cannot refile a file entry")) 1767 | (let ((entry-marker (org-brain-entry-marker entry)) 1768 | (parent-title (org-brain-title parent))) 1769 | (if (org-brain-filep parent) 1770 | ;; Parent is a file entry 1771 | (let ((parent-path (org-brain-entry-path parent))) 1772 | (with-current-buffer (find-file-noselect parent-path) 1773 | (goto-char (point-max)) 1774 | (insert "\n* temp headline") 1775 | (let ((newpoint (point))) 1776 | (org-with-point-at entry-marker 1777 | (org-refile nil nil (list parent-title parent-path "" newpoint)))) 1778 | (outline-next-heading) 1779 | (org-promote-subtree) 1780 | (outline-previous-heading) 1781 | (org-cut-subtree) 1782 | (pop kill-ring) 1783 | (forward-line -1) 1784 | (org-brain-delete-current-line "^[[:space:]]*$"))) 1785 | ;; Parent is a headline entry 1786 | (let ((id (org-brain-entry-identifier parent))) 1787 | (pcase (org-id-find id) 1788 | (`(,file-name . ,pos) 1789 | (org-with-point-at entry-marker 1790 | (org-refile nil nil (list parent-title file-name "" pos)))) 1791 | (_ (error "Parent headline with ID %s not found" id))))) 1792 | (let ((new-entry (org-brain-entry-from-id (org-brain-entry-identifier entry)))) 1793 | (cl-flet ((replace-entry (e) (if (equal e entry) new-entry e))) 1794 | (setq org-brain-pins (mapcar #'replace-entry org-brain-pins)) 1795 | (setq org-brain--vis-history (mapcar #'replace-entry org-brain--vis-history)) 1796 | (setq org-brain-selected (mapcar #'replace-entry org-brain-selected))) 1797 | (when (member parent 1798 | (org-brain--linked-property-entries new-entry org-brain-parents-property-name)) 1799 | (org-brain-remove-relationship parent new-entry)) 1800 | (org-save-all-org-buffers) 1801 | (when (eq entry org-brain--vis-entry) 1802 | (setq org-brain--vis-entry new-entry)) 1803 | new-entry))) 1804 | 1805 | ;;;###autoload 1806 | (defun org-brain-change-local-parent (&optional entry parent) 1807 | "Refile ENTRY to be a local child of PARENT. 1808 | Entries are relinked so existing parent-child relationships are unaffected. 1809 | 1810 | If ENTRY is not supplied, the entry at point is used. 1811 | If PARENT is not supplied, it is prompted for 1812 | among the list of ENTRY's linked parents. 1813 | Returns the new refiled entry." 1814 | (interactive) 1815 | (unless entry (setq entry (org-brain-entry-at-pt t))) 1816 | (unless parent (let ((linked-parents (org-brain--linked-property-entries entry org-brain-parents-property-name))) 1817 | (cl-case (length linked-parents) 1818 | (0 (error "Entry \"%s\" has only one parent" (org-brain-title entry))) 1819 | (1 (setq parent (car linked-parents))) 1820 | (otherwise (setq parent (org-brain-choose-entry 1821 | (format "Refile \"%s\" to parent: " (org-brain-title entry)) linked-parents)))))) 1822 | (let ((old-parent (car (org-brain-local-parent entry))) 1823 | (new-entry (org-brain-refile-to entry parent))) 1824 | (org-brain-add-relationship old-parent new-entry) 1825 | (org-brain--revert-if-visualizing) 1826 | new-entry)) 1827 | 1828 | (defun org-brain--remove-relationships (entry &optional recursive) 1829 | "Remove all external relationships from ENTRY. 1830 | Also unpin and unselect the entry. 1831 | 1832 | If RECURSIVE is t, remove local children's relationships." 1833 | (dolist (child (org-brain--linked-property-entries 1834 | entry org-brain-children-property-name)) 1835 | (org-brain-remove-relationship entry child)) 1836 | (dolist (parent (org-brain--linked-property-entries 1837 | entry org-brain-parents-property-name)) 1838 | (org-brain-remove-relationship parent entry)) 1839 | (dolist (friend (org-brain-friends entry)) 1840 | (org-brain-remove-friendship entry friend)) 1841 | (ignore-errors (org-brain-pin entry -1) 1842 | (org-brain-select entry -1)) 1843 | (when recursive 1844 | (dolist (child (org-brain-local-children entry)) 1845 | (org-brain--remove-relationships child t)))) 1846 | 1847 | ;;;###autoload 1848 | (defun org-brain-rename-file (file-entry new-name) 1849 | "Rename FILE-ENTRY to NEW-NAME. 1850 | Both arguments should be relative to `org-brain-path' and should 1851 | not contain `org-brain-files-extension'." 1852 | (interactive (let ((entry (org-brain-choose-entry 1853 | "Rename file: " (org-brain-files t) nil t))) 1854 | (list entry (read-string "New filename: " entry)))) 1855 | (let ((newpath (org-brain-entry-path new-name)) 1856 | (oldpath (org-brain-entry-path file-entry))) 1857 | (when (file-exists-p newpath) 1858 | (error "There's already a file %s" newpath)) 1859 | (when (member newpath (mapcar #'buffer-file-name (buffer-list))) 1860 | (error "There's an active buffer associated with file %s" newpath)) 1861 | (let ((children (org-brain--linked-property-entries file-entry org-brain-children-property-name)) 1862 | (parents (org-brain--linked-property-entries file-entry org-brain-parents-property-name)) 1863 | (friends (org-brain-friends file-entry)) 1864 | (is-pinned (member file-entry org-brain-pins)) 1865 | (is-selected (member file-entry org-brain-selected))) 1866 | (org-brain--remove-relationships file-entry) 1867 | (org-save-all-org-buffers) 1868 | (make-directory (file-name-directory newpath) t) 1869 | (if (vc-backend oldpath) 1870 | (vc-rename-file oldpath newpath) 1871 | (rename-file oldpath newpath)) 1872 | (org-brain-update-id-locations) 1873 | (when is-pinned (org-brain-pin new-name 1)) 1874 | (when is-selected (org-brain-select new-name 1)) 1875 | (cl-flet ((replace-entry (e) (if (org-brain-filep e) 1876 | (if (equal e file-entry) new-name e) 1877 | (when (equal (car e) file-entry) 1878 | (cons new-name (cdr e)) e)))) 1879 | (setq org-brain-pins (mapcar #'replace-entry org-brain-pins)) 1880 | (setq org-brain-selected (mapcar #'replace-entry org-brain-selected)) 1881 | (setq org-brain--vis-history (mapcar #'replace-entry org-brain--vis-history)) 1882 | (setq org-brain--vis-entry (replace-entry org-brain--vis-entry))) 1883 | (dolist (child children) 1884 | (org-brain-add-relationship new-name child)) 1885 | (dolist (parent parents) 1886 | (org-brain-add-relationship parent new-name)) 1887 | (dolist (friend friends) 1888 | (org-brain--internal-add-friendship new-name friend)) 1889 | (when (equal file-entry org-brain--vis-entry) 1890 | (setq org-brain--vis-entry new-name)) 1891 | ;; Change edges 1892 | (let ((edge-property (org-brain-edge-prop-name file-entry))) 1893 | (dolist (file (org-brain-files)) 1894 | (with-temp-file file 1895 | (insert-file-contents file) 1896 | (goto-char (point-min)) 1897 | (replace-regexp (concat edge-property ":") 1898 | (concat org-brain-edge-property-prefix-name "_" (org-brain-entry-identifier new-name) ":"))))) 1899 | (org-brain--revert-if-visualizing) 1900 | (message "Renamed %s to %s" file-entry new-name)))) 1901 | 1902 | ;;;###autoload 1903 | (defun org-brain-delete-entry (entry &optional noconfirm) 1904 | "Delete ENTRY and all of its local children. 1905 | If run interactively, ask for the ENTRY. 1906 | If NOCONFIRM is nil, ask if we really want to delete." 1907 | (interactive 1908 | (list (org-brain-choose-entry "Delete entry: " 'all nil t) 1909 | nil)) 1910 | (let ((local-children (org-brain-local-children entry))) 1911 | (when (or noconfirm 1912 | (yes-or-no-p 1913 | (format "%s and its %d local children will be deleted. Are you sure? " 1914 | (org-brain-entry-name entry) 1915 | (length local-children)))) 1916 | (ignore-errors (org-brain-select entry -1)) 1917 | (dolist (child local-children) 1918 | (org-brain-delete-entry child t)) 1919 | (org-brain--remove-relationships entry) 1920 | (if (org-brain-filep entry) 1921 | (let ((filename (org-brain-entry-path entry))) 1922 | (if (vc-backend filename) 1923 | (vc-delete-file filename) 1924 | (delete-file filename delete-by-moving-to-trash) 1925 | (kill-buffer (get-file-buffer filename)))) 1926 | (org-with-point-at (org-brain-entry-marker entry) 1927 | (org-mark-subtree) 1928 | (delete-region (region-beginning) (region-end)))))) 1929 | (setq org-brain--vis-history (delete entry org-brain--vis-history)) 1930 | (org-save-all-org-buffers) 1931 | (if (equal entry org-brain--vis-entry) 1932 | (when-let ((brain-buffer (get-buffer "*org-brain*"))) 1933 | (if (ignore-errors (org-brain-visualize-back)) 1934 | (message "Deleted visualized entry, going back in history.") 1935 | (kill-buffer brain-buffer) 1936 | (message "Deleted visualized entry. No history, hence killing org-brain buffer."))) 1937 | (org-brain--revert-if-visualizing t))) 1938 | 1939 | ;;;###autoload 1940 | (defun org-brain-insert-relationships (entry &optional recursive) 1941 | "Insert an `org-mode' list of relationships to ENTRY. 1942 | Local children are not included in the list. 1943 | If run interactively, get ENTRY from context. 1944 | 1945 | Normally the list is inserted at point, but if RECURSIVE is t 1946 | insert at end of ENTRY. Then recurse in the local (grand)children 1947 | of ENTRY and insert there too." 1948 | (interactive (list (org-brain-entry-at-pt t))) 1949 | (cl-flet ((list-to-items 1950 | (list) 1951 | (when list 1952 | `(unordered 1953 | ,@(mapcar (lambda (x) 1954 | (list (org-make-link-string 1955 | (format "brain:%s" (org-brain-entry-identifier x)) 1956 | (org-brain-title x)))) 1957 | list))))) 1958 | (save-excursion 1959 | (when recursive 1960 | (org-brain-goto-end entry) 1961 | (newline 2)) 1962 | (insert 1963 | ":RELATIONSHIPS:\n" 1964 | (org-list-to-org `(unordered 1965 | ,(remq nil `("Parents" 1966 | ,(list-to-items (org-brain-parents entry)))) 1967 | ,(remq nil `("Children" 1968 | ,(list-to-items (org-brain--linked-property-entries 1969 | entry org-brain-children-property-name)))) 1970 | ,(remq nil `("Friends" 1971 | ,(list-to-items (org-brain-friends entry)))))) 1972 | "\n:END:\n"))) 1973 | (when recursive 1974 | (dolist (child (org-brain-local-children entry)) 1975 | (org-brain-insert-relationships child t)))) 1976 | 1977 | ;;;###autoload 1978 | (defun org-brain-archive (entry) 1979 | "Use `org-archive-subtree-default' on ENTRY. 1980 | If run interactively, get ENTRY from context. 1981 | Before archiving, recursively run `org-brain-insert-relationships' on ENTRY. 1982 | Remove external relationships from ENTRY, in order to clean up the brain." 1983 | (interactive (list (org-brain-entry-at-pt t))) 1984 | (when (org-brain-filep entry) 1985 | (user-error "Only headline entries can be archived")) 1986 | (org-brain-insert-relationships entry t) 1987 | (org-brain--remove-relationships entry t) 1988 | (org-with-point-at (org-brain-entry-marker entry) 1989 | (org-archive-subtree-default)) 1990 | (setq org-brain--vis-history (delete entry org-brain--vis-history)) 1991 | (org-save-all-org-buffers) 1992 | (org-brain--revert-if-visualizing)) 1993 | 1994 | ;;;###autoload 1995 | (defun org-brain-pin (entry &optional status) 1996 | "Change if ENTRY is pinned or not. 1997 | If run interactively, get ENTRY from context. 1998 | Using `\\[universal-argument]' will use `org-brain-button-at-point' as ENTRY. 1999 | 2000 | If STATUS is positive, pin the entry. If negative, remove the pin. 2001 | If STATUS is omitted, toggle between pinned / not pinned." 2002 | (interactive (list (if current-prefix-arg 2003 | (car (org-brain-button-at-point)) 2004 | (org-brain-entry-at-pt t)))) 2005 | (cond ((eq status nil) 2006 | (if (member entry org-brain-pins) 2007 | (org-brain-pin entry -1) 2008 | (org-brain-pin entry 1))) 2009 | ((>= status 1) 2010 | (if (member entry org-brain-pins) 2011 | (error "Entry is already pinned") 2012 | (push entry org-brain-pins) 2013 | (org-brain-save-data) 2014 | (message "Pinned '%s'." (org-brain-entry-name entry)))) 2015 | ((< status 1) 2016 | (if (member entry org-brain-pins) 2017 | (progn 2018 | (setq org-brain-pins (delete entry org-brain-pins)) 2019 | (org-brain-save-data) 2020 | (message "Unpinned '%s'." (org-brain-entry-name entry))) 2021 | (error "Entry isn't pinned")))) 2022 | (org-brain--revert-if-visualizing)) 2023 | 2024 | ;;;###autoload 2025 | (defun org-brain-select (entry &optional status) 2026 | "Toggle selection of ENTRY. 2027 | If run interactively, get ENTRY from context. 2028 | 2029 | If STATUS is positive, select ENTRY. If negative, unselect it. 2030 | If STATUS is omitted, toggle between selected / not selected." 2031 | (interactive (list (org-brain-entry-at-pt))) 2032 | (when (null entry) (error "Cannot select null entry")) 2033 | (cond ((eq status nil) 2034 | (if (member entry org-brain-selected) 2035 | (org-brain-select entry -1) 2036 | (org-brain-select entry 1))) 2037 | ((>= status 1) 2038 | (if (member entry org-brain-selected) 2039 | (error "Entry is already selected") 2040 | (push entry org-brain-selected) 2041 | (org-brain-save-data) 2042 | (message "Entry selected."))) 2043 | ((< status 1) 2044 | (if (member entry org-brain-selected) 2045 | (progn 2046 | (setq org-brain-selected (delete entry org-brain-selected)) 2047 | (org-brain-save-data) 2048 | (message "Entry unselected.")) 2049 | (error "Entry isn't selected")))) 2050 | (org-brain--revert-if-visualizing)) 2051 | 2052 | ;;;###autoload 2053 | (defun org-brain-clear-selected () 2054 | "Clear the selected list." 2055 | (interactive) 2056 | (setq org-brain-selected nil) 2057 | (org-brain--revert-if-visualizing)) 2058 | 2059 | (defun org-brain-add-selected-children (entry) 2060 | "Add selected entries as children of ENTRY. 2061 | If run interactively, get ENTRY from context. 2062 | 2063 | When ENTRY is in the selected list, it is ignored." 2064 | (interactive (list (org-brain-entry-at-pt))) 2065 | ;; org-brain-add-child takes a list of children, 2066 | ;; but we call it one at a time 2067 | ;; so that errors don't interrupt the bulk operation. 2068 | (dolist (child org-brain-selected) 2069 | (ignore-errors (org-brain-add-child entry (list child))))) 2070 | 2071 | (defun org-brain-remove-selected-children (entry) 2072 | "Remove selected entries from the list of ENTRY's children. 2073 | If run interactively, get ENTRY from context. 2074 | 2075 | Ignores selected entries that are not children of ENTRY." 2076 | (interactive (list (org-brain-entry-at-pt))) 2077 | (dolist (child org-brain-selected) 2078 | (ignore-errors (org-brain-remove-child entry child)))) 2079 | 2080 | (defun org-brain-add-selected-parents (entry) 2081 | "Add selected entries as parents of ENTRY. 2082 | If run interactively, get ENTRY from context. 2083 | 2084 | When ENTRY is in the selected list, it is ignored." 2085 | (interactive (list (org-brain-entry-at-pt))) 2086 | ;; org-brain-add-parent takes a list of parents, 2087 | ;; but we call it one at a time 2088 | ;; so that errors don't interrupt the bulk operation. 2089 | (dolist (parent org-brain-selected) 2090 | (ignore-errors (org-brain-add-parent entry (list parent))))) 2091 | 2092 | (defun org-brain-remove-selected-parents (entry) 2093 | "Remove selected entries from the list of ENTRY's parents. 2094 | If run interactively, get ENTRY from context. 2095 | 2096 | Ignores selected entries that are not parents of ENTRY." 2097 | (interactive (list (org-brain-entry-at-pt))) 2098 | (dolist (parent org-brain-selected) 2099 | (ignore-errors (org-brain-remove-parent entry parent)))) 2100 | 2101 | (defun org-brain-add-selected-friendships (entry) 2102 | "Add selected entries as friends of ENTRY. 2103 | If run interactively, get ENTRY from context. 2104 | 2105 | When ENTRY is in the selected list, it is ignored." 2106 | (interactive (list (org-brain-entry-at-pt))) 2107 | ;; org-brain-add-friendship takes a list of friends, 2108 | ;; but we call it one at a time 2109 | ;; so that errors don't interrupt the bulk operation. 2110 | (dolist (friend org-brain-selected) 2111 | (ignore-errors (org-brain-add-friendship entry (list friend))))) 2112 | 2113 | (defun org-brain-remove-selected-friendships (entry) 2114 | "Remove selected entries from the list of ENTRY's friends. 2115 | If run interactively, get ENTRY from context. 2116 | 2117 | Ignores selected entries that are not friends of ENTRY." 2118 | (interactive (list (org-brain-entry-at-pt))) 2119 | (dolist (selected org-brain-selected) 2120 | (ignore-errors (org-brain-remove-friendship entry selected)))) 2121 | 2122 | (defun org-brain-delete-selected-entries () 2123 | "Delete all of the selected entries." 2124 | (interactive) 2125 | (dolist (selected org-brain-selected) 2126 | (org-brain-delete-entry selected))) 2127 | 2128 | (defun org-brain-change-selected-local-parents () 2129 | "Change the local parent of all the selected entries." 2130 | (interactive) 2131 | (dolist (selected org-brain-selected) 2132 | (org-brain-change-local-parent selected))) 2133 | 2134 | ;;;###autoload 2135 | (defun org-brain-set-title (entry title) 2136 | "Set the name of ENTRY to TITLE. 2137 | If run interactively, get ENTRY from context and prompt for TITLE." 2138 | (interactive 2139 | (let* ((entry-at-pt (org-brain-entry-at-pt t)) 2140 | (new-title (org-brain-title entry-at-pt))) 2141 | (when (equal (length new-title) 0) 2142 | (error "Title must be at least 1 character")) 2143 | (list entry-at-pt (read-string "Title: " new-title)))) 2144 | (if (org-brain-filep entry) 2145 | ;; File entry 2146 | (org-with-point-at (org-brain-entry-marker entry) 2147 | (goto-char (point-min)) 2148 | (when (assoc "TITLE" (org-brain-keywords entry)) 2149 | (re-search-forward "^#\\+TITLE:") 2150 | (org-brain-delete-current-line)) 2151 | (insert (format "#+TITLE: %s\n" title)) 2152 | (save-buffer)) 2153 | ;; Headline entry 2154 | (org-with-point-at (org-brain-entry-marker entry) 2155 | (org-edit-headline title) 2156 | (save-buffer) 2157 | (setf (nth 1 org-brain--vis-entry) title))) 2158 | (org-brain--revert-if-visualizing)) 2159 | 2160 | ;;;###autoload 2161 | (defun org-brain-set-tags (entry) 2162 | "Modify the ENTRY tags. 2163 | Use `org-set-tags-command' on headline ENTRY. 2164 | Instead sets #+FILETAGS on file ENTRY. 2165 | If run interactively, get ENTRY from context." 2166 | (interactive (list (org-brain-entry-at-pt t))) 2167 | (if (org-brain-filep entry) 2168 | (org-with-point-at (org-brain-entry-marker entry) 2169 | (let ((tag-str (read-string "FILETAGS: " 2170 | (mapconcat #'identity org-file-tags ":")))) 2171 | (goto-char (point-min)) 2172 | (when (assoc "FILETAGS" (org-brain-keywords entry)) 2173 | (re-search-forward "^#\\+FILETAGS:") 2174 | (org-brain-delete-current-line)) 2175 | (insert (format "#+FILETAGS: %s\n" tag-str))) 2176 | ;; From org.el 2177 | (let ((org-inhibit-startup-visibility-stuff t) 2178 | (org-startup-align-all-tables nil)) 2179 | (when (boundp 'org-table-coordinate-overlays) 2180 | (mapc #'delete-overlay org-table-coordinate-overlays) 2181 | (setq org-table-coordinate-overlays nil)) 2182 | (org-save-outline-visibility 'use-markers (org-mode-restart))) 2183 | (save-buffer)) 2184 | (org-with-point-at (org-brain-entry-marker entry) 2185 | (org-set-tags-command) 2186 | (save-buffer))) 2187 | (org-brain--revert-if-visualizing)) 2188 | 2189 | ;;;###autoload 2190 | (defun org-brain-add-nickname (entry nickname) 2191 | "ENTRY gets a new NICKNAME. 2192 | If run interactively use `org-brain-entry-at-pt' and prompt for NICKNAME." 2193 | (interactive (list (org-brain-entry-at-pt) 2194 | (read-string "Nickname: "))) 2195 | (if (org-brain-filep entry) 2196 | (let ((nickname (org-entry-protect-space nickname))) 2197 | (org-with-point-at (org-brain-entry-marker entry) 2198 | (goto-char (point-min)) 2199 | (if (re-search-forward "^#\\+NICKNAMES:.*$" nil t) 2200 | (insert (concat " " nickname)) 2201 | (insert (format "#+NICKNAMES: %s\n" nickname))) 2202 | (save-buffer))) 2203 | (org-entry-add-to-multivalued-property 2204 | (org-brain-entry-marker entry) "NICKNAMES" nickname) 2205 | (org-save-all-org-buffers))) 2206 | 2207 | ;;;###autoload 2208 | (defun org-brain-headline-to-file (entry) 2209 | "Convert headline ENTRY to a file entry. 2210 | Prompt for name of the new file. 2211 | If interactive, also prompt for ENTRY." 2212 | (interactive (list (org-brain-choose-entry "Convert entry: " 2213 | (org-brain-headline-entries) 2214 | nil t))) 2215 | (let* (level 2216 | (title (org-brain-title entry)) 2217 | (new-entry (read-string "New file entry: " title)) 2218 | (path (org-brain-entry-path new-entry))) 2219 | (when (file-exists-p path) 2220 | (error "That file already exists")) 2221 | (let ((parents (org-brain-parents entry)) 2222 | (external-parents (org-brain--linked-property-entries entry org-brain-parents-property-name)) 2223 | (children (org-brain--linked-property-entries entry org-brain-children-property-name)) 2224 | (friends (org-brain-friends entry)) 2225 | (hl-text (org-with-point-at (org-brain-entry-marker entry) 2226 | (setq level (org-outline-level)) 2227 | (org-get-entry)))) 2228 | (dolist (parent external-parents) 2229 | (org-brain-remove-relationship parent entry)) 2230 | (dolist (child children) 2231 | (org-brain-remove-relationship entry child)) 2232 | (dolist (friend friends) 2233 | (org-brain-remove-friendship entry friend)) 2234 | (org-with-point-at (org-brain-entry-marker entry) 2235 | (org-cut-subtree) 2236 | (pop kill-ring) 2237 | (save-buffer)) 2238 | (make-directory (file-name-directory path) t) 2239 | (with-temp-file path 2240 | (insert (format "#+TITLE:%s\n\n%s" title hl-text)) 2241 | (delay-mode-hooks 2242 | (org-mode) 2243 | (goto-char (point-min)) 2244 | (re-search-forward org-property-drawer-re) 2245 | (replace-match "") 2246 | (goto-char (point-max)) 2247 | (let ((level-regex "^")) 2248 | (dotimes (_i (1+ level)) 2249 | (setq level-regex (concat level-regex "\\*"))) 2250 | (setq level-regex (concat level-regex " ")) 2251 | (while (re-search-backward level-regex nil t) 2252 | (dotimes (_i level) (org-promote-subtree)))))) 2253 | (dolist (parent parents) 2254 | (org-brain-add-relationship parent new-entry)) 2255 | (dolist (child children) 2256 | (org-brain-add-relationship new-entry child)) 2257 | (dolist (friend friends) 2258 | (org-brain--internal-add-friendship new-entry friend)) 2259 | (when (equal entry org-brain--vis-entry) 2260 | (setq org-brain--vis-entry new-entry)) 2261 | (when (member entry org-brain-pins) 2262 | (org-brain-pin entry -1) 2263 | (org-brain-pin new-entry 1))))) 2264 | 2265 | ;;;###autoload 2266 | (defun org-brain-ensure-ids-in-buffer () 2267 | "Run `org-brain-get-id' on all headlines in current buffer 2268 | taking into account the ignore tags such as :childess: 2269 | Only works if in an `org-mode' buffer inside `org-brain-path'. 2270 | Suitable for use with `before-save-hook'." 2271 | (interactive) 2272 | (and (eq major-mode 'org-mode) 2273 | (string-prefix-p (file-truename org-brain-path) 2274 | (file-truename (buffer-file-name))) 2275 | (let ((match (format "-%s-%s|-%s+TAGS={%s}" ; "-nobrain-childless|-nobrain+TAGS={childless}" 2276 | org-brain-exclude-tree-tag org-brain-exclude-children-tag 2277 | org-brain-exclude-tree-tag org-brain-exclude-children-tag))) 2278 | (org-map-entries #'org-brain-get-id match 'file)))) 2279 | 2280 | ;;;###autoload 2281 | (defun org-brain-agenda () 2282 | "Like `org-agenda', but only for `org-brain-files'." 2283 | (interactive) 2284 | (let ((org-agenda-files (org-brain-files))) 2285 | (org-agenda))) 2286 | 2287 | ;;;###autoload 2288 | (defun org-brain-create-relationships-from-links () 2289 | "Add relationships for brain: links in `org-brain-path'. 2290 | Only create relationships to other files, not to headline entries. 2291 | 2292 | This function is meant to be used in order to convert old 2293 | org-brain setups to the system introduced in version 0.4. Please 2294 | make a backup of your `org-brain-path' before running this 2295 | function." 2296 | (interactive) 2297 | (when (y-or-n-p "This function is meant for old configurations. Are you sure you want to scan for links? ") 2298 | (dolist (file (org-brain-files)) 2299 | (with-temp-buffer 2300 | (insert-file-contents file) 2301 | (org-element-map (org-element-parse-buffer) 'link 2302 | (lambda (link) 2303 | (when (string-equal (org-element-property :type link) "brain") 2304 | (org-brain-add-relationship 2305 | (org-brain-path-entry-name file) 2306 | (car (split-string (org-element-property :path link) "::")))))))))) 2307 | 2308 | ;;;; Sorting 2309 | 2310 | (defun org-brain-title< (entry1 entry2) 2311 | "Return non-nil if title of ENTRY1 is less than ENTRY2 in lexicographic order. 2312 | Case is significant." 2313 | (string< (org-brain-title entry1) (org-brain-title entry2))) 2314 | 2315 | (defvar org-brain-visualize-sort-function 'org-brain-title< 2316 | "How to sort lists of relationships when visualizing. 2317 | Should be a function which accepts two entries as arguments. 2318 | The function returns t if the first entry is smaller than the second. 2319 | 2320 | If you don't want to sort the relationships, set this to `ignore'.") 2321 | 2322 | ;;;; Visualize 2323 | 2324 | (defvar org-brain--visualize-follow nil "Used by `org-brain-visualize-follow'.") 2325 | 2326 | ;;;###autoload 2327 | (defun org-brain-visualize-follow (should-follow) 2328 | "Set if `org-brain-visualize' SHOULD-FOLLOW the current entry or not. 2329 | When following, the visualized entry will be shown in a separate 2330 | buffer when changing the visualized entry. 2331 | If run interactively, toggle following on/off." 2332 | (interactive (list (not org-brain--visualize-follow))) 2333 | (setq org-brain--visualize-follow should-follow) 2334 | (message (if should-follow 2335 | "Enabled following visualized entry." 2336 | "Disabled following visualized entry."))) 2337 | 2338 | (defvar-local org-brain--visualize-header-end-pos 0 2339 | "Buffer position at end of headers (history etc) in `org-brain-visualize'.") 2340 | 2341 | ;;;###autoload 2342 | (defun org-brain-visualize (entry &optional nofocus nohistory wander) 2343 | "View a concept map with ENTRY at the center. 2344 | 2345 | When run interactively, prompt for ENTRY and suggest 2346 | `org-brain-entry-at-pt'. By default, the choices presented is 2347 | determined by `org-brain-visualize-default-choices': 'all will 2348 | show all entries, 'files will only show file entries and 'root 2349 | will only show files in the root of `org-brain-path'. 2350 | 2351 | You can override `org-brain-visualize-default-choices': 2352 | `\\[universal-argument]' will use 'all. 2353 | `\\[universal-argument] \\[universal-argument]' will use 'files. 2354 | `\\[universal-argument] \\[universal-argument] \\[universal-argument]' will use 'root. 2355 | 2356 | Unless NOFOCUS is non-nil, the `org-brain-visualize' buffer will gain focus. 2357 | Unless NOHISTORY is non-nil, add the entry to `org-brain--vis-history'. 2358 | Setting NOFOCUS to t implies also having NOHISTORY as t. 2359 | Unless WANDER is t, `org-brain-stop-wandering' will be run." 2360 | (interactive 2361 | (progn 2362 | (org-brain-maybe-switch-brain) 2363 | (let ((choices (cond ((equal current-prefix-arg '(4)) 'all) 2364 | ((equal current-prefix-arg '(16)) 'files) 2365 | ((equal current-prefix-arg '(64)) 'root) 2366 | (t org-brain-visualize-default-choices))) 2367 | (def-choice (unless (eq major-mode 'org-brain-visualize-mode) 2368 | (ignore-errors (org-brain-entry-name (org-brain-entry-at-pt)))))) 2369 | (org-brain-stop-wandering) 2370 | (list 2371 | (org-brain-choose-entry 2372 | "Entry: " 2373 | (cond ((equal choices 'all) 2374 | 'all) 2375 | ((equal choices 'files) 2376 | (org-brain-files t)) 2377 | ((equal choices 'root) 2378 | (make-directory org-brain-path t) 2379 | (mapcar #'org-brain-path-entry-name 2380 | (directory-files org-brain-path t (format "\\.%s$" org-brain-files-extension))))) 2381 | nil nil def-choice))))) 2382 | (unless wander (org-brain-stop-wandering)) 2383 | (with-current-buffer (get-buffer-create "*org-brain*") 2384 | (setq-local indent-tabs-mode nil) 2385 | (read-only-mode 1) 2386 | (setq-local default-directory (file-name-directory (org-brain-entry-path entry))) 2387 | (setq list-buffers-directory (org-brain-vis-title entry)) 2388 | (org-brain-maybe-switch-brain) 2389 | (unless (eq org-brain--vis-entry entry) 2390 | (setq org-brain--vis-entry entry) 2391 | (setq org-brain-mind-map-parent-level (default-value 'org-brain-mind-map-parent-level)) 2392 | (setq org-brain-mind-map-child-level (default-value 'org-brain-mind-map-child-level))) 2393 | (setq org-brain--vis-entry-keywords (when (org-brain-filep entry) 2394 | (org-brain-keywords entry))) 2395 | (let ((inhibit-read-only t) 2396 | (entry-pos)) 2397 | (delete-region (point-min) (point-max)) 2398 | (org-brain--vis-pinned) 2399 | (org-brain--vis-selected) 2400 | (when (not nohistory) 2401 | (setq org-brain--vis-history 2402 | (seq-filter (lambda (elt) (not (equal elt entry))) org-brain--vis-history)) 2403 | (setq org-brain--vis-history (seq-take org-brain--vis-history 15)) 2404 | (push entry org-brain--vis-history)) 2405 | (when org-brain-show-history (org-brain--vis-history)) 2406 | (if org-brain-visualizing-mind-map 2407 | (setq entry-pos (org-brain-mind-map org-brain--vis-entry org-brain-mind-map-parent-level org-brain-mind-map-child-level)) 2408 | (setq-local org-brain--visualize-header-end-pos (point)) 2409 | (insert "\n\n") 2410 | (org-brain--vis-parents-siblings entry) 2411 | ;; Insert entry title 2412 | (let ((title (org-brain-vis-title entry))) 2413 | (let ((half-title-length (/ (string-width title) 2))) 2414 | (if (>= half-title-length (current-column)) 2415 | (delete-char (- (current-column))) 2416 | (ignore-errors (delete-char (- half-title-length))))) 2417 | (setq entry-pos (point)) 2418 | (insert (propertize title 2419 | 'face (org-brain-display-face entry 'org-brain-title) 2420 | 'aa2u-text t)) 2421 | (org-brain--vis-friends entry) 2422 | (org-brain--vis-children entry))) 2423 | (when (and org-brain-show-resources) 2424 | (org-brain--vis-resources (org-brain-resources entry))) 2425 | (if org-brain-show-text 2426 | (org-brain--vis-text entry) 2427 | (run-hooks 'org-brain-after-visualize-hook)) 2428 | (unless (eq major-mode 'org-brain-visualize-mode) 2429 | (org-brain-visualize-mode)) 2430 | (goto-char entry-pos) 2431 | (set-buffer-modified-p nil)) 2432 | (unless nofocus 2433 | (when org-brain--visualize-follow 2434 | (org-brain-goto-current) 2435 | (run-hooks 'org-brain-visualize-follow-hook)) 2436 | (if (or org-brain--visualize-follow org-brain-open-same-window) 2437 | (pop-to-buffer "*org-brain*") 2438 | (pop-to-buffer-same-window "*org-brain*"))))) 2439 | 2440 | ;;;###autoload 2441 | (defun org-brain-visualize-dwim () 2442 | "Switch to the *org-brain* buffer. 2443 | If there's no such buffer, or if already there, run `org-brain-visualize'." 2444 | (interactive) 2445 | (if (and (not (org-brain-maybe-switch-brain)) 2446 | (not (eq major-mode 'org-brain-visualize-mode)) 2447 | (get-buffer "*org-brain*")) 2448 | (if org-brain-open-same-window 2449 | (pop-to-buffer "*org-brain*") 2450 | (pop-to-buffer-same-window "*org-brain*")) 2451 | (call-interactively #'org-brain-visualize))) 2452 | 2453 | ;;;###autoload 2454 | (defun org-brain-visualize-entry-at-pt () 2455 | "Use `org-brain-visualize' on the `org-brain-entry-at-pt'. 2456 | Useful if wanting to visualize the current `org-mode' entry." 2457 | (interactive) 2458 | (org-brain-visualize (org-brain-entry-at-pt))) 2459 | 2460 | ;;;###autoload 2461 | (defun org-brain-visualize-random (&optional restrict-to) 2462 | "Run `org-brain-visualize' on a random org-brain entry. 2463 | If RESTRICT-TO is given, then only choose among those entries. 2464 | 2465 | If called interactively with `\\[universal-argument]' then 2466 | restrict to descendants of the visualized entry." 2467 | (interactive (when (equal current-prefix-arg '(4)) 2468 | (list (org-brain-descendants org-brain--vis-entry)))) 2469 | (let ((entries (or restrict-to 2470 | (append (org-brain-files t) 2471 | (org-brain-headline-entries))))) 2472 | (org-brain-visualize (nth (random (length entries)) entries) nil nil t))) 2473 | 2474 | (defvar org-brain-wander-timer nil 2475 | "A timer running `org-brain-visualize-random' at a set interval. 2476 | 2477 | Can be (de)activated by `org-brain-visualize-wander'.") 2478 | 2479 | (defun org-brain-stop-wandering () 2480 | "Cancels `org-brain-wander-timer', if it is active." 2481 | (when (member org-brain-wander-timer timer-list) 2482 | (cancel-timer org-brain-wander-timer) 2483 | t)) 2484 | 2485 | (defun org-brain-visualize-wander (&optional restrict-to) 2486 | "Run `org-brain-visualize-random' every `org-brain-wander-interval'. 2487 | If RESTRICT-TO is given, then only wander among those entries. 2488 | 2489 | If called interactively with `\\[universal-argument]' then 2490 | restrict to descendants of the visualized entry starting the wandering session. 2491 | 2492 | Wandering is cancelled by many org-brain commands, but can also be 2493 | cancelled manually with `org-brain-stop-wandering'." 2494 | (interactive (when (equal current-prefix-arg '(4)) 2495 | (list (org-brain-descendants org-brain--vis-entry)))) 2496 | (if (org-brain-stop-wandering) 2497 | (message "Wandering stopped.") 2498 | (setq org-brain-wander-timer (run-at-time nil org-brain-wander-interval #'org-brain-visualize-random restrict-to)) 2499 | (message "Wandering started."))) 2500 | 2501 | (defun org-brain-visualize-quit () 2502 | "Like `quit-window', but also stops `org-brain-visualize-wander'." 2503 | (interactive) 2504 | (org-brain-stop-wandering) 2505 | (quit-window)) 2506 | 2507 | (defun org-brain-entry-icon (entry) 2508 | "Get a string representing the icon of ENTRY. 2509 | Checks for the org mode category of ENTRY, then search for the 2510 | category icon in `org-agenda-category-icon-alist'." 2511 | (when (and org-brain-show-icons 2512 | org-agenda-category-icon-alist) 2513 | (org-with-point-at (org-brain-entry-marker entry) 2514 | (when-let* ((category (org-get-category)) 2515 | (icon (org-agenda-get-category-icon category))) 2516 | (propertize (make-string org-brain-category-icon-width ? ) 'display icon))))) 2517 | 2518 | (defun org-brain-vis-title (entry) 2519 | "The title of ENTRY when shown in `org-brain-visualize-mode'." 2520 | (string-join (remove 2521 | "" 2522 | (list 2523 | ;; Prepend stuff to the title 2524 | (mapconcat (lambda (func) (funcall func entry)) 2525 | org-brain-vis-title-prepend-functions 2526 | " ") 2527 | (if (eq org-brain--vis-entry entry) 2528 | (mapconcat (lambda (func) (funcall func entry)) 2529 | org-brain-vis-current-title-prepend-functions 2530 | " ") 2531 | "") 2532 | ;; The title itself 2533 | (org-brain-title entry (or (not org-brain-visualizing-mind-map) 2534 | org-brain-cap-mind-map-titles)) 2535 | ;; Append stuff to the title 2536 | (mapconcat (lambda (func) (funcall func entry)) 2537 | org-brain-vis-title-append-functions 2538 | " ") 2539 | (if (eq org-brain--vis-entry entry) 2540 | (mapconcat (lambda (func) (funcall func entry)) 2541 | org-brain-vis-current-title-append-functions 2542 | " ") 2543 | ""))) 2544 | " ")) 2545 | 2546 | (defun org-brain-insert-visualize-button (entry &optional face category) 2547 | "Insert a button, running `org-brain-visualize' on ENTRY when clicked. 2548 | FACE is sent to `org-brain-display-face' and sets the face of the button. 2549 | CATEGORY is used to set the `brain-category` text property." 2550 | (let ((annotation (org-brain-get-edge-annotation org-brain--vis-entry 2551 | entry 2552 | org-brain--vis-entry-keywords))) 2553 | (insert-text-button 2554 | (org-brain-vis-title entry) 2555 | 'action (lambda (_x) (org-brain-visualize entry)) 2556 | 'id (org-brain-entry-identifier entry) 2557 | 'follow-link t 2558 | 'brain-category (or category 'default) 2559 | 'help-echo annotation 2560 | 'aa2u-text t 2561 | 'face (org-brain-display-face entry face annotation)))) 2562 | 2563 | (defun org-brain-jump-to-visualize-button (entry) 2564 | "If ENTRY has a visualize button in the current buffer, jump to its position." 2565 | (when (eq major-mode 'org-brain-visualize-mode) 2566 | (let ((start-pos (point)) 2567 | (entry-id (org-brain-entry-identifier entry))) 2568 | (goto-char org-brain--visualize-header-end-pos) 2569 | (while (and (or (ignore-errors (forward-button 1)) 2570 | (and (goto-char start-pos) nil)) 2571 | (not (equal (button-get (button-at (point)) 'id) 2572 | entry-id))))))) 2573 | 2574 | (defun org-brain-insert-resource-button (resource &optional indent) 2575 | "Insert a new line with a RESOURCE button, indented by INDENT spaces." 2576 | (insert (make-string (or indent 0) ?\ ) "\n- ") 2577 | (run-hook-with-args 'org-brain-after-resource-button-functions (car resource)) 2578 | (insert-text-button 2579 | (or (cdr resource) (car resource)) 2580 | 'action (lambda (_x) 2581 | (org-open-link-from-string (format "[[%s]]" (car resource)))) 2582 | 'follow-link t 2583 | 'aa2u-text t)) 2584 | 2585 | (defun org-brain-button-at-point () 2586 | "If there's an entry link button at `point' return (entry . button)." 2587 | (if-let* ((button (button-at (point))) 2588 | (id (button-get button 'id)) 2589 | (entry (or (org-brain-entry-from-id id) 2590 | (org-entry-restore-space id)))) 2591 | (cons entry button) 2592 | (user-error "No entry button at point"))) 2593 | 2594 | (defun org-brain-add-resource (&optional link description prompt entry) 2595 | "Insert LINK with DESCRIPTION in ENTRY. 2596 | If ENTRY is nil, try to get it from context or prompt for it. 2597 | If LINK is nil then use `org-insert-link-global'. Otherwise: 2598 | If PROMPT is non nil, let user edit the resource even if run non-interactively." 2599 | (interactive) 2600 | (unless entry 2601 | (setq entry (or (ignore-errors (org-brain-entry-at-pt)) 2602 | (org-brain-choose-entry "Insert link in entry: " 'all)))) 2603 | (let ((link-text 2604 | (if link 2605 | (progn 2606 | (when prompt 2607 | (setq link (read-string "Insert link: " link)) 2608 | (when (string-match org-bracket-link-regexp link) 2609 | (let ((linkdesc (match-string 3 link))) 2610 | (when (and (not description) linkdesc) 2611 | (setq description linkdesc)) 2612 | (setq link (match-string 1 link)))) 2613 | (setq description (read-string "Link description: " description))) 2614 | (concat "- " (org-make-link-string link description))) 2615 | (let ((bfn (buffer-file-name))) 2616 | (when-let ((l (with-temp-buffer 2617 | (let ((buffer-file-name bfn)) 2618 | (org-insert-link-global) 2619 | (buffer-string))))) 2620 | (concat "- " l)))))) 2621 | (if (org-brain-filep entry) 2622 | ;; File entry 2623 | (org-with-point-at (org-brain-entry-marker entry) 2624 | (goto-char (org-brain-first-headline-position)) 2625 | (if (re-search-backward org-brain-resources-start-re nil t) 2626 | (end-of-line) 2627 | (if (re-search-backward org-brain-keyword-regex nil t) 2628 | (progn 2629 | (end-of-line) 2630 | (newline-and-indent)) 2631 | (goto-char (point-min))) 2632 | (insert (concat ":" org-brain-resources-drawer-name ":\n:END:\n")) 2633 | (re-search-backward org-brain-resources-start-re nil t) 2634 | (end-of-line)) 2635 | (newline-and-indent) 2636 | (insert link-text) 2637 | (save-buffer)) 2638 | ;; Headline entry 2639 | (org-with-point-at (org-brain-entry-marker entry) 2640 | (goto-char (cdr (org-get-property-block))) 2641 | (forward-line 1) 2642 | (if (looking-at org-brain-resources-start-re) 2643 | (end-of-line) 2644 | (open-line 1) 2645 | (indent-for-tab-command) 2646 | (insert (concat ":" org-brain-resources-drawer-name ":")) 2647 | (save-excursion 2648 | (insert "\n") 2649 | (indent-for-tab-command) 2650 | (insert ":END:"))) 2651 | (newline-and-indent) 2652 | (insert link-text) 2653 | (save-buffer)))) 2654 | (org-brain--revert-if-visualizing)) 2655 | 2656 | (defalias 'org-brain-visualize-add-resource #'org-brain-add-resource) 2657 | 2658 | (defun org-brain-add-file-line-as-resource (file line &optional entry) 2659 | "Add a link to a FILE LINE as a resource in ENTRY. 2660 | If called interactively use current FILE and LINE 2661 | and prompt for ENTRY, unless called with `\\[universal-argument]' 2662 | in which case use the current/last visualized entry." 2663 | (interactive (list (buffer-file-name) 2664 | (number-to-string (line-number-at-pos)))) 2665 | (org-brain-add-resource (concat "file:" file "::" line) 2666 | nil nil 2667 | (or entry (when current-prefix-arg 2668 | org-brain--vis-entry))) 2669 | (ignore-errors 2670 | (with-current-buffer "*org-brain*" 2671 | (org-brain--revert-if-visualizing))) 2672 | (message "A new resource has been added.")) 2673 | 2674 | (defun org-brain-add-file-as-resource (file &optional entry) 2675 | "Add a link to a FILE as a resource in ENTRY. 2676 | If called interactively use current FILE 2677 | and prompt for ENTRY, unless called with `\\[universal-argument]' 2678 | in which case use the current/last visualized entry." 2679 | (interactive (list (buffer-file-name))) 2680 | (org-brain-add-resource (concat "file:" file) 2681 | nil nil 2682 | (or entry (when current-prefix-arg 2683 | org-brain--vis-entry))) 2684 | (ignore-errors 2685 | (with-current-buffer "*org-brain*" 2686 | (org-brain--revert-if-visualizing))) 2687 | (message "A new resource has been added.")) 2688 | 2689 | (defun org-brain-visualize-attach () 2690 | "Use `org-attach' on `org-brain--vis-entry'." 2691 | (interactive) 2692 | (unless (eq major-mode 'org-brain-visualize-mode) 2693 | (error "Not in org-brain-visualize-mode")) 2694 | (when (org-brain-filep org-brain--vis-entry) 2695 | (error "Can only attach to headline entries")) 2696 | (org-with-point-at (org-brain-entry-marker org-brain--vis-entry) 2697 | (goto-char (cdr (org-id-find (nth 2 org-brain--vis-entry)))) 2698 | (call-interactively #'org-attach) 2699 | (save-buffer)) 2700 | (org-brain--revert-if-visualizing)) 2701 | 2702 | (defun org-brain-paste-resource () 2703 | "Add `current-kill' as a resource link. 2704 | See `org-brain-add-resource'." 2705 | (interactive) 2706 | (org-brain-add-resource (current-kill 0) nil t)) 2707 | 2708 | (defalias 'org-brain-visualize-paste-resource #'org-brain-paste-resource) 2709 | 2710 | ;;;###autoload 2711 | (defun org-brain-select-button () 2712 | "Toggle selection of the entry linked to by the button at point." 2713 | (interactive) 2714 | (org-brain-select (car (org-brain-button-at-point))) 2715 | t) 2716 | 2717 | ;;;###autoload 2718 | (defun org-brain-select-dwim (arg) 2719 | "Use `org-brain-select-button' or `org-brain-select' depending on context. 2720 | If run with `\\[universal-argument\\]' (ARG is non nil) 2721 | then always use `org-brain-select'." 2722 | (interactive "P") 2723 | (when (or arg (not (ignore-errors (org-brain-select-button)))) 2724 | (org-brain-select (org-brain-entry-at-pt)))) 2725 | 2726 | (defun org-brain-edge-prop-name (entry) 2727 | "Retrun edge annotation property name of ENTRY." 2728 | (concat org-brain-edge-property-prefix-name "_" (org-brain-entry-identifier entry))) 2729 | 2730 | (defun org-brain-get-edge-annotation (from to &optional keywords) 2731 | "Get edge annotation FROM an entry TO another entry. 2732 | If KEYWORDS is given, use it instead of `org-brain-keywords' (optimization)." 2733 | (if (org-brain-filep from) 2734 | (cdr (assoc (upcase (org-brain-edge-prop-name to)) 2735 | (or keywords (org-brain-keywords from)))) 2736 | (org-entry-get (org-brain-entry-marker from) (org-brain-edge-prop-name to)))) 2737 | 2738 | (defun org-brain-annotate-edge (entry target annotation two-way) 2739 | "When visualizing ENTRY, links to TARGET will have an ANNOTATION. 2740 | You can think of it as edges with comments in a graph. 2741 | If TWO-WAY is non-nil, then also add the ANNOTATION from TARGET to ENTRY. 2742 | 2743 | When called interactively use the visualized ENTRY, 2744 | `org-brain-button-at-point' as TARGET, and prompt for ANNOTATION. 2745 | TWO-WAY will be t unless called with `\\[universal-argument\\]'." 2746 | (interactive 2747 | (let ((target (car (org-brain-button-at-point)))) 2748 | (list org-brain--vis-entry 2749 | target 2750 | (read-string (concat (org-brain-title target) " edge: ")) 2751 | (not current-prefix-arg)))) 2752 | (if (org-brain-filep entry) 2753 | ;; File entry 2754 | (let ((edge-regex (format "^#\\+%s:" 2755 | (org-brain-edge-prop-name target)))) 2756 | (org-with-point-at (org-brain-entry-marker entry) 2757 | (if (re-search-forward edge-regex nil t) 2758 | (org-brain-delete-current-line edge-regex) 2759 | (goto-char (point-min))) 2760 | (when (> (length annotation) 0) 2761 | (insert "#+" (org-brain-edge-prop-name target) ": " annotation "\n")) 2762 | (save-buffer))) 2763 | ;; Headline entry 2764 | (org-with-point-at (org-brain-entry-marker entry) 2765 | (if (> (length annotation) 0) 2766 | (org-set-property (org-brain-edge-prop-name target) annotation) 2767 | (org-delete-property (org-brain-edge-prop-name target))) 2768 | (save-buffer))) 2769 | (when two-way 2770 | (run-with-idle-timer 0.2 nil 'org-brain-annotate-edge 2771 | target entry annotation nil)) 2772 | (org-brain--revert-if-visualizing)) 2773 | 2774 | (defun org-brain-visualize-back () 2775 | "Go back to the previously visualized entry." 2776 | (interactive) 2777 | (if (cadr org-brain--vis-history) 2778 | (progn (pop org-brain--vis-history) 2779 | (org-brain-visualize (car org-brain--vis-history) nil t)) 2780 | (error "No further history"))) 2781 | 2782 | (defun org-brain-visualize-revert (_ignore-auto _noconfirm) 2783 | "Revert function for `org-brain-visualize-mode'." 2784 | (org-brain-visualize org-brain--vis-entry t)) 2785 | 2786 | (defun org-brain--revert-if-visualizing (&optional ignore-button-at-pt) 2787 | "Revert buffer if in `org-brain-visualize-mode'. 2788 | Unless IGNORE-BUTTON-AT-PT is non nil, jump to the button at 2789 | point before the buffer was reverted." 2790 | (when (eq major-mode 'org-brain-visualize-mode) 2791 | (let ((button-entry 2792 | (unless ignore-button-at-pt 2793 | (car (ignore-errors (org-brain-button-at-point)))))) 2794 | (org-brain-stop-wandering) 2795 | (revert-buffer) 2796 | (when button-entry (org-brain-jump-to-visualize-button button-entry))))) 2797 | 2798 | (defun org-brain--bookmark-handler (bookmark) 2799 | "Visualize the entry stored in BOOKMARK." 2800 | (org-brain-visualize (cdr (assoc 'brain-entry bookmark)) nil) 2801 | (switch-to-buffer "*org-brain*")) 2802 | 2803 | (defun org-brain-make-bookmark-record () 2804 | "Make a bookmark out of `org-brain--vis-entry'. 2805 | Used as `bookmark-make-record-function' in `org-brain-visualize-mode'." 2806 | (if-let ((entry org-brain--vis-entry)) 2807 | (cons (org-brain-title org-brain--vis-entry) 2808 | `((handler . org-brain--bookmark-handler) 2809 | (brain-entry . ,org-brain--vis-entry))) 2810 | (user-error "For some reason `org-brain--vis-entry' is nil"))) 2811 | 2812 | (define-derived-mode org-brain-visualize-mode 2813 | special-mode "Org-brain Visualize" 2814 | "Major mode for `org-brain-visualize'. 2815 | \\{org-brain-visualize-mode-map}" 2816 | (setq-local revert-buffer-function #'org-brain-visualize-revert) 2817 | (setq-local bookmark-make-record-function #'org-brain-make-bookmark-record)) 2818 | 2819 | ;;;;; Keybindings 2820 | 2821 | (define-key org-brain-visualize-mode-map "p" 'org-brain-add-parent) 2822 | (define-key org-brain-visualize-mode-map "P" 'org-brain-remove-parent) 2823 | (define-key org-brain-visualize-mode-map "c" 'org-brain-add-child) 2824 | (define-key org-brain-visualize-mode-map "C" 'org-brain-remove-child) 2825 | (define-key org-brain-visualize-mode-map "*" 'org-brain-add-child-headline) 2826 | (define-key org-brain-visualize-mode-map "h" 'org-brain-add-child-headline) 2827 | (define-key org-brain-visualize-mode-map "n" 'org-brain-pin) 2828 | (define-key org-brain-visualize-mode-map "N" 'org-brain-add-nickname) 2829 | (define-key org-brain-visualize-mode-map "t" 'org-brain-set-title) 2830 | (define-key org-brain-visualize-mode-map "j" 'forward-button) 2831 | (define-key org-brain-visualize-mode-map "k" 'backward-button) 2832 | (define-key org-brain-visualize-mode-map "u" 'org-brain-visualize-parent) 2833 | (define-key org-brain-visualize-mode-map [?\t] 'forward-button) 2834 | (define-key org-brain-visualize-mode-map [backtab] 'backward-button) 2835 | (define-key org-brain-visualize-mode-map "o" 'org-brain-goto-current) 2836 | (define-key org-brain-visualize-mode-map "O" 'org-brain-goto) 2837 | (define-key org-brain-visualize-mode-map "v" 'org-brain-visualize) 2838 | (define-key org-brain-visualize-mode-map "V" 'org-brain-visualize-follow) 2839 | (define-key org-brain-visualize-mode-map "f" 'org-brain-add-friendship) 2840 | (define-key org-brain-visualize-mode-map "F" 'org-brain-remove-friendship) 2841 | (define-key org-brain-visualize-mode-map "d" 'org-brain-delete-entry) 2842 | (define-key org-brain-visualize-mode-map "l" 'org-brain-add-resource) 2843 | (define-key org-brain-visualize-mode-map "r" 'org-brain-open-resource) 2844 | (define-key org-brain-visualize-mode-map "a" 'org-brain-visualize-attach) 2845 | (define-key org-brain-visualize-mode-map "A" 'org-brain-archive) 2846 | (define-key org-brain-visualize-mode-map "b" 'org-brain-visualize-back) 2847 | (define-key org-brain-visualize-mode-map "\C-y" 'org-brain-visualize-paste-resource) 2848 | (define-key org-brain-visualize-mode-map "T" 'org-brain-set-tags) 2849 | (define-key org-brain-visualize-mode-map "q" 'org-brain-visualize-quit) 2850 | (define-key org-brain-visualize-mode-map "w" 'org-brain-visualize-random) 2851 | (define-key org-brain-visualize-mode-map "W" 'org-brain-visualize-wander) 2852 | (define-key org-brain-visualize-mode-map "m" 'org-brain-visualize-mind-map) 2853 | (define-key org-brain-visualize-mode-map "+" 'org-brain-show-descendant-level) 2854 | (define-key org-brain-visualize-mode-map "-" 'org-brain-hide-descendant-level) 2855 | (define-key org-brain-visualize-mode-map "z" 'org-brain-show-ancestor-level) 2856 | (define-key org-brain-visualize-mode-map "Z" 'org-brain-hide-ancestor-level) 2857 | (define-key org-brain-visualize-mode-map "e" 'org-brain-annotate-edge) 2858 | (define-key org-brain-visualize-mode-map "\C-c\C-w" 'org-brain-refile) 2859 | (define-key org-brain-visualize-mode-map "\C-c\C-x\C-v" 'org-toggle-inline-images) 2860 | 2861 | (define-prefix-command 'org-brain-select-map) 2862 | (define-key org-brain-select-map "s" 'org-brain-clear-selected) 2863 | (define-key org-brain-select-map "c" 'org-brain-add-selected-children) 2864 | (define-key org-brain-select-map "C" 'org-brain-remove-selected-children) 2865 | (define-key org-brain-select-map "p" 'org-brain-add-selected-parents) 2866 | (define-key org-brain-select-map "P" 'org-brain-remove-selected-parents) 2867 | (define-key org-brain-select-map "f" 'org-brain-add-selected-friendships) 2868 | (define-key org-brain-select-map "F" 'org-brain-remove-selected-friendships) 2869 | (define-key org-brain-select-map "s" 'org-brain-clear-selected) 2870 | (define-key org-brain-select-map "S" 'org-brain-clear-selected) 2871 | (define-key org-brain-select-map "d" 'org-brain-delete-selected-entries) 2872 | (define-key org-brain-select-map "l" 'org-brain-change-selected-local-parents) 2873 | 2874 | (define-key org-brain-visualize-mode-map "s" 'org-brain-select-dwim) 2875 | (define-key org-brain-visualize-mode-map "S" 'org-brain-select-map) 2876 | 2877 | (define-prefix-command 'org-brain-move-map) 2878 | (define-key org-brain-move-map "r" 'org-brain-refile) 2879 | (define-key org-brain-move-map "p" 'org-brain-change-local-parent) 2880 | 2881 | (define-key org-brain-visualize-mode-map "M" 'org-brain-move-map) 2882 | 2883 | (let ((map (define-prefix-command 'org-brain-prefix-map))) 2884 | (set-keymap-parent map org-brain-visualize-mode-map) 2885 | (mapc (lambda (x) (define-key map x nil)) 2886 | '("j" "k" "g" [?\t] [backtab] "o" "b" "u" "V" "T" "q" 2887 | "m" "+" "-" "z" "Z" "e" "?" "\C-c\C-w" "\C-c\C-x\C-v" 2888 | "" " " "<" ">" "0" "1" "2" "3" "4" "5" "6" "7" "8" "9" 2889 | [33554464]))) 2890 | 2891 | ;;;;; Drawing helpers 2892 | 2893 | (defun org-brain--visually-sort (lst) 2894 | "Sort LST destructively according to org-brain-visualize-sort-function." 2895 | (sort lst org-brain-visualize-sort-function)) 2896 | 2897 | (defun org-brain--visually-sorted (lst) 2898 | "Sorted LST according to org-brain-visualize-sort-function." 2899 | (org-brain--visually-sort (copy-sequence lst))) 2900 | 2901 | (defun org-brain--maybe-visually-sort (entry lst) 2902 | "Sorted LST unless ENTRY has a :nosort: tag." 2903 | (if (member org-brain-no-sort-children-tag (org-brain-get-tags entry)) 2904 | lst 2905 | (org-brain--visually-sort lst))) 2906 | 2907 | (defun org-brain--visually-sorted-parents (entry) 2908 | "List of parents, sorted unless ENTRY has a :nosort: tag." 2909 | (org-brain--maybe-visually-sort entry (org-brain-parents entry))) 2910 | 2911 | (defun org-brain--visually-sorted-children (entry) 2912 | "List of children, sorted unless ENTRY has a :nosort: tag." 2913 | (org-brain--maybe-visually-sort entry (org-brain-children entry))) 2914 | 2915 | (defun org-brain--visually-sorted-friends (entry) 2916 | "List of friends, sorted unless ENTRY has a :nosort: tag." 2917 | (org-brain--maybe-visually-sort entry (org-brain-friends entry))) 2918 | 2919 | (defun org-brain--visually-sorted-siblings (entry) 2920 | "List of siblings, sorted unless ENTRY has a :nosort: tag." 2921 | (let ((siblings (org-brain-siblings entry))) 2922 | (if (member org-brain-no-sort-children-tag (org-brain-get-tags entry)) 2923 | siblings 2924 | (sort siblings (lambda (x y) 2925 | (funcall org-brain-visualize-sort-function 2926 | (car x) (car y))))))) 2927 | 2928 | (defun org-brain--visually-sorted-siblings-from (pair) 2929 | "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." 2930 | (let ((parent (car pair))) 2931 | (unless (member org-brain-exclude-siblings-tag (org-brain-get-tags parent)) 2932 | (org-brain--maybe-visually-sort parent (cdr pair))))) 2933 | 2934 | (defun org-brain--visually-sorted-pins () 2935 | "List of pins visually sorted." 2936 | (org-brain--visually-sorted org-brain-pins)) 2937 | 2938 | (defun org-brain--visually-sorted-selected () 2939 | "Visually sorted selection list." 2940 | (org-brain--visually-sorted org-brain-selected)) 2941 | 2942 | (defun org-brain--vis-pinned () 2943 | "Insert pinned entries. 2944 | Helper function for `org-brain-visualize'." 2945 | (insert "PINNED:") 2946 | (dolist (pin (org-brain--visually-sorted-pins)) 2947 | (insert " ") 2948 | (org-brain-insert-visualize-button pin 'org-brain-pinned 'pinned)) 2949 | (insert "\n")) 2950 | 2951 | (defun org-brain--vis-selected () 2952 | "Insert selected entries. 2953 | Helper function for `org-brain-visualize'." 2954 | (unless (null org-brain-selected) 2955 | (insert "SELECTED:") 2956 | (dolist (selection (org-brain--visually-sorted-selected)) 2957 | (insert " ") 2958 | (org-brain-insert-visualize-button selection 'org-brain-selected-list)) 2959 | (insert "\n"))) 2960 | 2961 | (defun org-brain--hist-entries-to-draw (max-width hist width to-draw) 2962 | "Determines the entries in HIST that can fit on a line of MAX-WIDTH. 2963 | Returns those entries in reversed order. 2964 | WIDTH and TO-DRAW are state parameters. 2965 | WIDTH represents the width of the line comprising the elements in TO-DRAW. 2966 | Assumes elements will be drawn with a two-character padding between them. 2967 | Helper function for `org-brain--vis-history'." 2968 | (if (null hist) 2969 | to-draw 2970 | (let* ((entry-title-width (string-width (org-brain-vis-title (car hist)))) 2971 | (new-line-width (+ width 2 entry-title-width))) 2972 | (if (and (<= max-width new-line-width) 2973 | (not (null to-draw))) ; Always display at least one entry 2974 | to-draw 2975 | (org-brain--hist-entries-to-draw max-width (cdr hist) new-line-width (cons (car hist) to-draw)))))) 2976 | 2977 | (defun org-brain--vis-history () 2978 | "Show as many of the most recently visited entries as fit on one line. 2979 | Helper function for `org-brain-visualize'." 2980 | (insert "HISTORY:") 2981 | (dolist (entry (org-brain--hist-entries-to-draw (window-width) org-brain--vis-history (string-width "HISTORY:") nil)) 2982 | (insert " ") 2983 | (org-brain-insert-visualize-button entry 'org-brain-history-list 'history)) 2984 | (insert "\n")) 2985 | 2986 | (defun org-brain--insert-wire (&rest strings) 2987 | "Helper function for drawing fontified wires in the org-brain visualization buffer." 2988 | (insert (propertize (apply 'concat strings) 'face 'org-brain-wires))) 2989 | 2990 | (defun org-brain--vis-parents-siblings (entry) 2991 | "Insert parents and siblings of ENTRY. 2992 | Helper function for `org-brain-visualize'." 2993 | (when-let ((siblings (org-brain--visually-sorted-siblings entry))) 2994 | (let ((parent-positions nil) 2995 | (max-width 0)) 2996 | (dolist (parent siblings) 2997 | (let* ((children-links (org-brain--visually-sorted-siblings-from parent)) 2998 | (sibling-middle (ceiling (/ (length children-links) 2.0))) 2999 | (base-line (if org-brain-show-history 5 4)) 3000 | (col-start (+ 3 max-width)) 3001 | (parent-width (string-width (org-brain-vis-title (car parent))))) 3002 | (org-goto-line base-line) 3003 | (mapc 3004 | (lambda (child) 3005 | (picture-forward-column col-start) 3006 | (org-brain--insert-wire (make-string (1+ parent-width) ?\ ) "+-") 3007 | (org-brain-insert-visualize-button 3008 | child 3009 | (if (and (member (car parent) (org-brain-local-parent child)) 3010 | (member (car parent) (org-brain-local-parent entry))) 3011 | 'org-brain-local-sibling 3012 | 'org-brain-sibling) 'sibling) 3013 | (setq max-width (max max-width (current-column))) 3014 | (newline (forward-line 1))) 3015 | children-links) 3016 | (org-goto-line base-line) 3017 | (forward-line (1- sibling-middle)) 3018 | (picture-forward-column col-start) 3019 | (push (cons (picture-current-line) 3020 | (+ (current-column) (/ parent-width 2))) 3021 | parent-positions) 3022 | (org-brain-insert-visualize-button 3023 | (car parent) 3024 | (if (member (car parent) (org-brain-local-parent entry)) 3025 | 'org-brain-local-parent 3026 | 'org-brain-parent) 'parent) 3027 | (setq max-width (max max-width (current-column))) 3028 | (when children-links 3029 | (org-brain--insert-wire "-") 3030 | (delete-char (+ 1 parent-width))))) 3031 | ;; Draw lines 3032 | (when parent-positions 3033 | (let ((maxline (line-number-at-pos (point-max)))) 3034 | ;; Bottom line 3035 | (org-goto-line maxline) 3036 | (picture-forward-column (cdar (last parent-positions))) 3037 | (picture-move-down 1) 3038 | (org-brain--insert-wire (make-string (1+ (- (cdar parent-positions) 3039 | (cdar (last parent-positions)))) 3040 | ?-)) 3041 | ;; Lines from parents to bottom 3042 | (dolist (pos parent-positions) 3043 | (org-goto-line (car pos)) 3044 | (picture-forward-column (cdr pos)) 3045 | (while (< (line-number-at-pos (point)) 3046 | maxline) 3047 | (picture-move-down 1) 3048 | (org-brain--insert-wire "|") 3049 | (unless (looking-at-p "\n") (delete-char 1))) 3050 | (picture-move-down 1) 3051 | (ignore-errors 3052 | (delete-char 1)) 3053 | (org-brain--insert-wire "+")) 3054 | ;; Line to main entry 3055 | (move-to-column (/ (+ (cdar (last parent-positions)) 3056 | (cdar parent-positions)) 3057 | 2) 3058 | t) 3059 | (delete-char 1) 3060 | (when (> (length parent-positions) 1) 3061 | (org-brain--insert-wire "+") 3062 | (backward-char 1) 3063 | (picture-move-down 1) 3064 | (org-brain--insert-wire "|") 3065 | (picture-move-down 1)) 3066 | (org-brain--insert-wire "V")))) 3067 | (picture-move-down 1))) 3068 | 3069 | (defun org-brain--vis-children (entry) 3070 | "Insert children of ENTRY. 3071 | Helper function for `org-brain-visualize'." 3072 | (let ((tags (org-brain-get-tags entry t))) 3073 | (when-let ((children (org-brain--visually-sorted-children entry)) 3074 | (fill-col (if (member org-brain-each-child-on-own-line-tag 3075 | (org-brain-get-tags entry)) 3076 | 0 3077 | (eval org-brain-child-linebreak-sexp)))) 3078 | (insert "\n\n") 3079 | (dolist (child children) 3080 | (let ((child-title (org-brain-title child)) 3081 | (face (if (member entry (org-brain-local-parent child)) 3082 | 'org-brain-local-child 3083 | 'org-brain-child))) 3084 | (when (> (+ (current-column) (length child-title)) fill-col) 3085 | (insert "\n")) 3086 | (org-brain-insert-visualize-button child face 'child) 3087 | (insert " ")))))) 3088 | 3089 | (defun org-brain--vis-friends (entry) 3090 | "Insert friends of ENTRY. 3091 | Helper function for `org-brain-visualize'." 3092 | (when-let ((friends (org-brain--visually-sorted-friends entry))) 3093 | (org-brain--insert-wire " <-> ") 3094 | (dolist (friend friends) 3095 | (let ((column (current-column))) 3096 | (org-brain-insert-visualize-button friend 'org-brain-friend 'friend) 3097 | (picture-move-down 1) 3098 | (move-to-column column t))) 3099 | (org-brain-delete-current-line) 3100 | (backward-char 1))) 3101 | 3102 | (defun org-brain--vis-resources (resources) 3103 | "Insert links to RESOURCES. 3104 | Helper function for `org-brain-visualize'." 3105 | (when resources 3106 | (insert "\n\n--- Resources ---------------------------------\n") 3107 | (mapc #'org-brain-insert-resource-button resources))) 3108 | 3109 | (defvar org-brain--vis-entry-text-marker 0 3110 | "Marker to where `org-brain-text' begins in `org-brain-visualize-mode'.") 3111 | 3112 | (defun org-brain--vis-text (entry) 3113 | "Insert text of ENTRY. 3114 | Helper function for `org-brain-visualize'." 3115 | (if-let ((text (org-brain-text entry))) 3116 | (progn 3117 | (setq text (string-trim text)) 3118 | (if (or (boundp 'org-brain-polymode) 3119 | org-brain-show-full-entry 3120 | (> (length text) 0)) 3121 | (progn 3122 | (insert "\n\n") 3123 | (setq org-brain--vis-entry-text-marker (point-marker)) 3124 | (insert "--- Entry -------------------------------------\n\n") 3125 | (run-hooks 'org-brain-after-visualize-hook) 3126 | (insert (with-temp-buffer 3127 | (insert text) 3128 | (delay-mode-hooks 3129 | (org-mode) 3130 | (setq-local org-pretty-entities t) 3131 | (font-lock-ensure (point-min) (point-max)) 3132 | (buffer-string)))) 3133 | (run-hooks 'org-brain-visualize-text-hook)) 3134 | (run-hooks 'org-brain-after-visualize-hook))) 3135 | (run-hooks 'org-brain-after-visualize-hook))) 3136 | 3137 | ;;;;; Mind-map 3138 | 3139 | (defun org-brain-map-create-indentation (level) 3140 | "Return a string of spaces, length determined by indentation LEVEL." 3141 | (make-string (* level 2) ? )) 3142 | 3143 | (defun org-brain-insert-recursive-child-buttons (entry max-level indent) 3144 | "Use `org-brain-insert-visualize-button' on ENTRY and its children. 3145 | Also insert buttons for grand-children, up to MAX-LEVEL. 3146 | Each button is indented, starting at level determined by INDENT." 3147 | (insert (org-brain-map-create-indentation indent)) 3148 | (org-brain-insert-visualize-button entry 'org-brain-child (if (> max-level 0) 'grandchild 'child)) 3149 | (insert "\n") 3150 | (dolist (child (and (> max-level 0) 3151 | (org-brain--visually-sorted-children entry))) 3152 | (org-brain-insert-recursive-child-buttons child (1- max-level) (1+ indent)))) 3153 | 3154 | (defun org-brain-tree-depth (tree) 3155 | "Return depth of nested TREE." 3156 | (if (atom tree) 3157 | 0 3158 | (1+ (cl-reduce #'max (mapcar #'org-brain-tree-depth tree))))) 3159 | 3160 | (defun org-brain-recursive-parents (entry max-level &optional func) 3161 | "Return a tree of ENTRY and its (grand)parents, up to MAX-LEVEL. 3162 | Apply FUNC to each tree member. FUNC is a function which takes an 3163 | entry as the only argument. If FUNC is nil or omitted, get the 3164 | raw entry data." 3165 | (cons (funcall (or func #'identity) entry) 3166 | (when (> max-level 0) 3167 | (mapcar (lambda (x) (org-brain-recursive-parents x (1- max-level) func)) 3168 | (org-brain-parents entry))))) 3169 | 3170 | (defun org-brain-recursive-children (entry max-level &optional func) 3171 | "Return a tree of ENTRY and its (grand)children up to MAX-LEVEL. 3172 | Apply FUNC to each tree member. FUNC is a function which takes an 3173 | entry as the only argument. If FUNC is nil or omitted, get the 3174 | raw entry data." 3175 | (cons (funcall (or func #'identity) entry) 3176 | (when (> max-level 0) 3177 | (mapcar (lambda (x) (org-brain-recursive-children x (1- max-level) func)) 3178 | (org-brain-children entry))))) 3179 | 3180 | (defun org-brain-insert-recursive-parent-buttons (entry max-level indent) 3181 | "Use `org-brain-insert-visualize-button' on ENTRY and its parents. 3182 | Also insert buttons for grand-parents, up to MAX-LEVEL. 3183 | Each button is indented, starting at level determined by INDENT." 3184 | (dolist (parent (and (> max-level 0) 3185 | (org-brain--visually-sorted-parents entry))) 3186 | (org-brain-insert-recursive-parent-buttons parent (1- max-level) (1- indent))) 3187 | (insert (org-brain-map-create-indentation indent)) 3188 | (org-brain-insert-visualize-button entry 'org-brain-parent (if (> max-level 0) 'grandparent 'parent)) 3189 | (insert "\n")) 3190 | 3191 | (defun org-brain-mind-map (entry parent-max-level children-max-level) 3192 | "Insert a tree of buttons for the parents and children of ENTRY. 3193 | Insert friends to ENTRY in a row above the tree. 3194 | Will also insert grand-parents up to PARENT-MAX-LEVEL, and 3195 | children up to CHILDREN-MAX-LEVEL. 3196 | Return the position of ENTRY in the buffer." 3197 | (insert "FRIENDS:") 3198 | (dolist (friend (org-brain--visually-sorted-friends entry)) 3199 | (insert " ") 3200 | (org-brain-insert-visualize-button friend 'org-brain-friend 'friend)) 3201 | (setq-local org-brain--visualize-header-end-pos (point)) 3202 | (insert "\n\n") 3203 | (let ((indent (1- (org-brain-tree-depth (org-brain-recursive-parents entry parent-max-level)))) 3204 | (entry-pos)) 3205 | (dolist (parent (org-brain--visually-sorted-siblings entry)) 3206 | (org-brain-insert-recursive-parent-buttons (car parent) (1- parent-max-level) (1- indent)) 3207 | (dolist (sibling (org-brain--visually-sorted-siblings-from parent)) 3208 | (insert (org-brain-map-create-indentation indent)) 3209 | (org-brain-insert-visualize-button sibling 'org-brain-sibling 'sibling) 3210 | (insert "\n"))) 3211 | (insert (org-brain-map-create-indentation indent)) 3212 | (setq entry-pos (point)) 3213 | (insert (propertize (org-brain-title entry) 3214 | 'face 'org-brain-title 3215 | 'aa2u-text t) "\n") 3216 | (dolist (child (org-brain--visually-sorted-children entry)) 3217 | (org-brain-insert-recursive-child-buttons child (1- children-max-level) (1+ indent))) 3218 | entry-pos)) 3219 | 3220 | (defvar org-brain-visualizing-mind-map nil) 3221 | (defvar-local org-brain-mind-map-child-level 1) 3222 | (defvar-local org-brain-mind-map-parent-level 1) 3223 | 3224 | (defun org-brain-visualize-mind-map () 3225 | "Toggle mind-map view of `org-brain-visualize'." 3226 | (interactive) 3227 | (when (eq major-mode 'org-brain-visualize-mode) 3228 | (setq org-brain-visualizing-mind-map (not org-brain-visualizing-mind-map)) 3229 | (org-brain-visualize org-brain--vis-entry))) 3230 | 3231 | ;;;;; Show/hide nested levels 3232 | (defun org-brain-show-descendant-level () 3233 | "Show one more level of descendant entries to the right in the mind-map visualization buffer." 3234 | (interactive) 3235 | (setq org-brain-visualizing-mind-map t) 3236 | (cl-incf org-brain-mind-map-child-level) 3237 | (org-brain--revert-if-visualizing)) 3238 | 3239 | (defun org-brain-hide-descendant-level () 3240 | "Hide the rightmost level of descendant entries in the mind-map visualization buffer." 3241 | (interactive) 3242 | (setq org-brain-visualizing-mind-map t) 3243 | (when (> org-brain-mind-map-child-level 1) 3244 | (cl-decf org-brain-mind-map-child-level)) 3245 | (org-brain--revert-if-visualizing)) 3246 | 3247 | (defun org-brain-show-ancestor-level () 3248 | "Show one more level of ancestor entries to the left in the mind-map visualization buffer." 3249 | (interactive) 3250 | (setq org-brain-visualizing-mind-map t) 3251 | (cl-incf org-brain-mind-map-parent-level) 3252 | (org-brain--revert-if-visualizing)) 3253 | 3254 | (defun org-brain-hide-ancestor-level () 3255 | "Hide the leftmost level of ancestor entries in the mind-map visualization buffer." 3256 | (interactive) 3257 | (setq org-brain-visualizing-mind-map t) 3258 | (when (> org-brain-mind-map-parent-level 1) 3259 | (cl-decf org-brain-mind-map-parent-level)) 3260 | (org-brain--revert-if-visualizing)) 3261 | 3262 | (define-obsolete-function-alias 3263 | 'org-brain-visualize-add-grandchild 'org-brain-show-descendant-level "0.5") 3264 | (define-obsolete-function-alias 3265 | 'org-brain-visualize-remove-grandchild 'org-brain-hide-descendant-level "0.5") 3266 | (define-obsolete-function-alias 3267 | 'org-brain-visualize-add-grandparent 'org-brain-show-ancestor-level "0.5") 3268 | (define-obsolete-function-alias 3269 | 'org-brain-visualize-remove-grandparent 'org-brain-hide-ancestor-level "0.5") 3270 | 3271 | ;;;;; Polymode 3272 | 3273 | ;; This code has been adapted from Dustin Lacewell's project polybrain 3274 | ;; Have a look at: https://github.com/dustinlacewell/polybrain.el/ 3275 | 3276 | (with-eval-after-load 'polymode 3277 | (define-hostmode org-brain-poly-hostmode 3278 | :mode 'org-brain-visualize-mode) 3279 | 3280 | (define-innermode org-brain-poly-innermode 3281 | :mode 'org-mode 3282 | :head-matcher "^[─-]\\{3\\} Entry [─-]+\n" 3283 | :tail-matcher "\\'" 3284 | :head-mode 'host 3285 | :tail-mode 'host) 3286 | 3287 | (define-polymode org-brain-polymode 3288 | :hostmode 'org-brain-poly-hostmode 3289 | :innermodes '(org-brain-poly-innermode) 3290 | (setq-local polymode-move-these-vars-from-old-buffer 3291 | (delq 'buffer-read-only polymode-move-these-vars-from-old-buffer))) 3292 | 3293 | (defun org-brain-polymode-save () 3294 | "Save entry text to the entry's file." 3295 | (interactive) 3296 | (when (buffer-modified-p) 3297 | (let ((text (save-excursion 3298 | (goto-char org-brain--vis-entry-text-marker) 3299 | (end-of-line) 3300 | (buffer-substring (point) (point-max))))) 3301 | (find-file (org-brain-entry-path org-brain--vis-entry)) 3302 | (seq-let (entry-min entry-max) (org-brain-text-positions org-brain--vis-entry) 3303 | (goto-char entry-min) 3304 | (delete-region entry-min entry-max) 3305 | (insert text) 3306 | (unless (looking-at-p "\n") 3307 | (insert "\n\n")) 3308 | (save-buffer) 3309 | (switch-to-buffer (other-buffer (current-buffer) 1)) 3310 | (set-buffer-modified-p nil))))) 3311 | 3312 | (define-key org-brain-polymode-map "\C-x\C-s" 'org-brain-polymode-save)) 3313 | 3314 | ;;;; Brain link 3315 | 3316 | (defun org-brain-link-complete (&optional link-type) 3317 | "Create an org-link target string to a file in `org-brain-path'. 3318 | LINK-TYPE will be \"brain\" by default." 3319 | (setq link-type (or link-type "brain")) 3320 | (let* ((entry (ignore-errors (org-brain-entry-at-pt t))) 3321 | (choice (if (and (not entry) 3322 | (member link-type 3323 | (list org-brain-child-link-name 3324 | org-brain-parent-link-name 3325 | org-brain-friend-link-name))) 3326 | (error "No entry at point") 3327 | (org-brain-choose-entry "Entry: " 'all)))) 3328 | (cond ((string-equal link-type org-brain-child-link-name) 3329 | (org-brain-add-relationship entry choice)) 3330 | ((string-equal link-type org-brain-parent-link-name) 3331 | (org-brain-add-relationship choice entry)) 3332 | ((string-equal link-type org-brain-friend-link-name) 3333 | (org-brain--internal-add-friendship entry choice)) 3334 | ((and org-brain-backlink (string-equal link-type "brain")) 3335 | (if entry 3336 | (org-brain-add-resource 3337 | (concat "brain:" (org-brain-entry-identifier entry)) 3338 | (concat (and (stringp org-brain-backlink) org-brain-backlink) 3339 | (org-brain-title entry)) 3340 | nil choice) 3341 | (org-brain-add-resource 3342 | (cl-concatenate 'string 3343 | "file:" 3344 | (file-relative-name 3345 | (buffer-file-name) 3346 | (file-name-directory (org-brain-entry-path choice))) 3347 | (if-let ((outline-path 3348 | (and org-brain-backlink-heading 3349 | (ignore-errors (org-get-outline-path t))))) 3350 | (concat "::* " (nth 0 (last outline-path))))) 3351 | (concat (and (stringp org-brain-backlink) org-brain-backlink) 3352 | (if (and org-brain-backlink-heading 3353 | (ignore-errors (org-get-outline-path t))) 3354 | (string-join (org-get-outline-path t) " > ") 3355 | (file-name-base))) 3356 | nil choice)))) 3357 | (let ((link (concat link-type ":" 3358 | (if (org-brain-filep choice) choice (nth 2 choice))))) 3359 | (if (version< (org-release) "9.3") 3360 | (push link org-insert-link-history) 3361 | (push link org-link--insert-history)) 3362 | (push `(,link ,(org-brain-title choice)) org-stored-links) 3363 | link))) 3364 | 3365 | (defun org-brain-link-store () 3366 | "Store a brain: type link from an `org-brain-visualize-mode' buffer." 3367 | (when (eq major-mode 'org-brain-visualize-mode) 3368 | (org-store-link-props 3369 | :type "brain" 3370 | :link (concat "brain:" (org-brain-entry-identifier org-brain--vis-entry)) 3371 | :description (org-brain-title org-brain--vis-entry)))) 3372 | 3373 | (org-link-set-parameters "brain" 3374 | :complete 'org-brain-link-complete 3375 | :follow 'org-brain-goto 3376 | :store 'org-brain-link-store) 3377 | 3378 | (org-link-set-parameters org-brain-child-link-name 3379 | :complete (lambda () (org-brain-link-complete org-brain-child-link-name)) 3380 | :follow 'org-brain-goto) 3381 | 3382 | (org-link-set-parameters org-brain-parent-link-name 3383 | :complete (lambda () (org-brain-link-complete org-brain-parent-link-name)) 3384 | :follow 'org-brain-goto) 3385 | 3386 | (org-link-set-parameters org-brain-friend-link-name 3387 | :complete (lambda () (org-brain-link-complete org-brain-friend-link-name)) 3388 | :follow 'org-brain-goto) 3389 | 3390 | ;;;; Brain switch link 3391 | 3392 | (defun org-brain--switch-link-complete () 3393 | "Create an org-link target string to an org-brain and one of its entries." 3394 | (let* ((org-brain-path (read-directory-name "Brain dir: " org-brain-path)) 3395 | (entry (org-brain-choose-entry 3396 | "Entry: " (append (when org-brain-include-file-entries (org-brain-files t)) 3397 | (org-brain-headline-entries))))) 3398 | (concat "brainswitch:" org-brain-path 3399 | "::" 3400 | (if (org-brain-filep entry) 3401 | entry 3402 | (nth 2 entry))))) 3403 | 3404 | (defun org-brain--switch-and-visualize (directory entry) 3405 | "Switch brain to DIRECTORY and visualize ENTRY. 3406 | ENTRY should be a string; an id in the case of an headline entry." 3407 | (org-brain-switch-brain directory) 3408 | (org-brain-visualize (or (org-brain-entry-from-id entry) entry))) 3409 | 3410 | (defun org-brain--switch-link-follow (link) 3411 | "Follow function for brainswitch links." 3412 | (let ((link-parts (split-string link "::"))) 3413 | (org-brain--switch-and-visualize (car link-parts) 3414 | (cadr link-parts)))) 3415 | 3416 | (org-link-set-parameters "brainswitch" 3417 | :complete 'org-brain--switch-link-complete 3418 | :follow 'org-brain--switch-link-follow) 3419 | 3420 | ;;;; Helm integration 3421 | 3422 | (with-eval-after-load 'helm 3423 | (defun helm-brain--add-children (_c) 3424 | (dolist (candidate (helm-marked-candidates)) 3425 | (org-brain-add-relationship 3426 | (org-brain-entry-at-pt) (or (org-brain-entry-from-id candidate) candidate))) 3427 | (org-brain--revert-if-visualizing)) 3428 | 3429 | (defun helm-brain--add-parents (_c) 3430 | (dolist (candidate (helm-marked-candidates)) 3431 | (org-brain-add-relationship 3432 | (or (org-brain-entry-from-id candidate) candidate) (org-brain-entry-at-pt))) 3433 | (org-brain--revert-if-visualizing)) 3434 | 3435 | (defun helm-brain--add-friends (_c) 3436 | (dolist (candidate (helm-marked-candidates)) 3437 | (org-brain--internal-add-friendship 3438 | (org-brain-entry-at-pt) (or (org-brain-entry-from-id candidate) candidate))) 3439 | (org-brain--revert-if-visualizing)) 3440 | 3441 | (defun helm-brain--delete-entries (_c) 3442 | (dolist (candidate (helm-marked-candidates)) 3443 | (org-brain-delete-entry (or (org-brain-entry-from-id candidate) candidate)))) 3444 | 3445 | (defun helm-brain--archive (_c) 3446 | (dolist (candidate (helm-marked-candidates)) 3447 | (org-brain-archive (or (org-brain-entry-from-id candidate) candidate)))) 3448 | 3449 | (defun helm-brain--select (_c) 3450 | (dolist (candidate (helm-marked-candidates)) 3451 | (org-brain-select (or (org-brain-entry-from-id candidate) candidate) 1))) 3452 | 3453 | (defun helm-brain--unselect (_c) 3454 | (dolist (candidate (helm-marked-candidates)) 3455 | (org-brain-select (or (org-brain-entry-from-id candidate) candidate) -1))) 3456 | 3457 | (defvar helm-brain--actions 3458 | (helm-make-actions 3459 | "Visualize" (lambda (x) 3460 | (org-brain-visualize (or (org-brain-entry-from-id x) x))) 3461 | "Add children" 'helm-brain--add-children 3462 | "Add parents" 'helm-brain--add-parents 3463 | "Add friends" 'helm-brain--add-friends 3464 | "Delete" 'helm-brain--delete-entries 3465 | "Archive" 'helm-brain--archive 3466 | "Select" 'helm-brain--select 3467 | "Unselect" 'helm-brain--unselect)) 3468 | 3469 | (defvar helm-brain--source 3470 | (helm-make-source "Brain" 'helm-source-sync 3471 | :candidates #'org-brain--all-targets 3472 | :action 'helm-brain--actions)) 3473 | 3474 | (defvar helm-brain--fallback-source 3475 | (helm-make-source "New entry" 'helm-source-dummy 3476 | :action (helm-make-actions 3477 | "Visualize" (lambda (x) 3478 | (org-brain-visualize (org-brain-get-entry-from-title x))) 3479 | "Add children" 'helm-brain--add-children 3480 | "Add parents" 'helm-brain--add-parents 3481 | "Add friends" 'helm-brain--add-friends))) 3482 | 3483 | (defun helm-brain () 3484 | "Use `helm' to choose among your org-brain entries. 3485 | Provides actions for visualizing, adding/removing relations, etc. 3486 | Supports selecting multiple entries at once." 3487 | (interactive) 3488 | (helm :sources '(helm-brain--source helm-brain--fallback-source)))) 3489 | 3490 | ;;;; Ivy integration 3491 | 3492 | (with-eval-after-load 'ivy 3493 | (defun counsel-brain () 3494 | "Use Ivy to choose among your org-brain entries. 3495 | Provides actions for visualizing, adding/removing relations, etc." 3496 | (interactive) 3497 | (let ((targets (org-brain--all-targets))) 3498 | (ivy-read "Org-brain: " 3499 | targets 3500 | :action (lambda (x) 3501 | (org-brain-visualize 3502 | (if (stringp x) 3503 | (org-brain-get-entry-from-title x) 3504 | (or (org-brain-entry-from-id (cdr x)) 3505 | (cdr x))))) 3506 | :preselect (ignore-errors 3507 | (org-brain-entry-name 3508 | (org-brain-entry-at-pt))) 3509 | :caller 'counsel-brain))) 3510 | 3511 | (defun counsel-brain--add-child (child) 3512 | (org-brain-add-relationship (org-brain-entry-at-pt) 3513 | (or (org-brain-entry-from-id (cdr child)) 3514 | (cdr child))) 3515 | (org-brain--revert-if-visualizing)) 3516 | 3517 | (defun counsel-brain--add-parent (parent) 3518 | (org-brain-add-relationship (or (org-brain-entry-from-id (cdr parent)) 3519 | (cdr parent)) 3520 | (org-brain-entry-at-pt)) 3521 | (org-brain--revert-if-visualizing)) 3522 | 3523 | (defun counsel-brain--add-friend (friend) 3524 | (org-brain--internal-add-friendship (org-brain-entry-at-pt) 3525 | (or (org-brain-entry-from-id (cdr friend)) 3526 | (cdr friend))) 3527 | (org-brain--revert-if-visualizing)) 3528 | 3529 | (defun counsel-brain--delete (x) 3530 | (org-brain-delete-entry (or (org-brain-entry-from-id (cdr x)) (cdr x)))) 3531 | 3532 | (defun counsel-brain--archive (x) 3533 | (org-brain-archive (or (org-brain-entry-from-id (cdr x)) (cdr x)))) 3534 | 3535 | (defun counsel-brain--select (x) 3536 | (org-brain-select (or (org-brain-entry-from-id (cdr x)) (cdr x)) 1)) 3537 | 3538 | (defun counsel-brain--unselect (x) 3539 | (org-brain-select (or (org-brain-entry-from-id (cdr x)) (cdr x)) -1)) 3540 | 3541 | (ivy-set-actions 3542 | 'counsel-brain 3543 | '(("c" counsel-brain--add-child "add as child") 3544 | ("p" counsel-brain--add-parent "add as parent") 3545 | ("f" counsel-brain--add-friend "add as friend") 3546 | ("d" counsel-brain--delete "delete") 3547 | ("a" counsel-brain--archive "archive") 3548 | ("s" counsel-brain--select "select") 3549 | ("S" counsel-brain--unselect "unselect")))) 3550 | 3551 | (provide 'org-brain) 3552 | ;;; org-brain.el ends here 3553 | --------------------------------------------------------------------------------