org-roam-capture.el (39655B)
1 ;;; org-roam-capture.el --- Capture functionality -*- coding: utf-8; lexical-binding: t; -*- 2 3 ;; Copyright © 2020-2022 Jethro Kuan <jethrokuan95@gmail.com> 4 5 ;; Author: Jethro Kuan <jethrokuan95@gmail.com> 6 ;; URL: https://github.com/org-roam/org-roam 7 ;; Keywords: org-mode, roam, convenience 8 ;; Version: 2.2.2 9 ;; Package-Requires: ((emacs "26.1") (dash "2.13") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "3.0.0")) 10 11 ;; This file is NOT part of GNU Emacs. 12 13 ;; This program is free software; you can redistribute it and/or modify 14 ;; it under the terms of the GNU General Public License as published by 15 ;; the Free Software Foundation; either version 3, or (at your option) 16 ;; any later version. 17 ;; 18 ;; This program is distributed in the hope that it will be useful, 19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 ;; GNU General Public License for more details. 22 ;; 23 ;; You should have received a copy of the GNU General Public License 24 ;; along with GNU Emacs; see the file COPYING. If not, write to the 25 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 26 ;; Boston, MA 02110-1301, USA. 27 28 ;;; Commentary: 29 ;; 30 ;; This module provides `org-capture' functionality for Org-roam. With this 31 ;; module the user can capture new nodes or capture new content to existing 32 ;; nodes. 33 ;; 34 ;;; Code: 35 (require 'org-roam) 36 37 ;;;; Declarations 38 (defvar org-end-time-was-given) 39 40 ;;; Options 41 (defcustom org-roam-capture-templates 42 '(("d" "default" plain "%?" 43 :target (file+head "%<%Y%m%d%H%M%S>-${slug}.org" 44 "#+title: ${title}\n") 45 :unnarrowed t)) 46 "Templates for the creation of new entries within Org-roam. 47 48 Each entry is a list with the following items: 49 50 keys The keys that will select the template, as a string, characters only, for 51 example \"a\" for a template to be selected with a single key, or 52 \"bt\" for selection with two keys. When using several keys, keys 53 using the same prefix must be together in the list and preceded by a 54 2-element entry explaining the prefix key, for example: 55 56 (\"b\" \"Templates for marking stuff to buy\") 57 58 The \"C\" key is used by default for quick access to the customization of 59 the template variable. But if you want to use that key for a template, 60 you can. 61 62 description A short string describing the template, which will be shown 63 during selection. 64 65 type The type of entry. Valid types are: 66 entry an Org node, with a headline. Will be filed 67 as the child of the target entry or as a 68 top level entry. Its default template is: 69 \"* %?\n %a\" 70 item a plain list item, will be placed in the 71 first plain list at the target location. 72 Its default template is: 73 \"- %?\" 74 checkitem a checkbox item. This differs from the 75 plain list item only in so far as it uses a 76 different default template. Its default 77 template is: 78 \"- [ ] %?\" 79 table-line a new line in the first table at target location. 80 Its default template is: 81 \"| %? |\" 82 plain text to be inserted as it is. 83 84 template The template for creating the capture item. 85 If it is an empty string or nil, a default template based on 86 the entry type will be used (see the \"type\" section above). 87 Instead of a string, this may also be one of: 88 89 (file \"/path/to/template-file\") 90 (function function-returning-the-template) 91 92 in order to get a template from a file, or dynamically 93 from a function. 94 95 The template contains a compulsory :target property. The :target property 96 contains a list, where: 97 - The first element indicates the type of the target. 98 - The second element indicates the location of the captured node. 99 - And the rest of the list indicate the prefilled template, that will be 100 inserted and the position of the point will be adjusted for. 101 This behavior varies from type to type. 102 103 The following options are supported for the :target property: 104 105 (file \"path/to/file\") 106 The file will be created, and prescribed an ID. 107 108 (file+head \"path/to/file\" \"head content\") 109 The file will be created, prescribed an ID, and head content will be 110 inserted if the node is a newly captured one. 111 112 (file+olp \"path/to/file\" (\"h1\" \"h2\")) 113 The file will be created, prescribed an ID. If the file doesn't contain 114 the outline path (h1, h2), it will be automatically created. The point 115 will be adjusted to the last element in the OLP. 116 117 (file+head+olp \"path/to/file\" \"head content\" (\"h1\" \"h2\")) 118 The file will be created, prescribed an ID. Head content will be 119 inserted at the start of the file if the node is a newly captured one. 120 If the file doesn't contain the outline path (h1, h2), it will be 121 automatically created. The point will be adjusted to the last element in 122 the OLP. 123 124 (file+datetree \"path/to/file\" tree-type) 125 The file will be created, prescribed an ID. A date based outline path 126 will be created for today's date. The tree-type can be one of the 127 following symbols: day, week or month. The point will adjusted to the 128 last element in the tree. To prompt for date instead of using today's, 129 use the :time-prompt property. 130 131 (node \"title or alias or ID of an existing node\") 132 The point will be placed for an existing node, based on either, its 133 title, alias or ID. 134 135 The rest of the entry is a property list of additional options. Recognized 136 properties are: 137 138 :prepend Normally newly captured information will be appended at 139 the target location (last child, last table line, 140 last list item...). Setting this property will 141 change that. 142 143 :immediate-finish When set, do not offer to edit the information, just 144 file it away immediately. This makes sense if the 145 template only needs information that can be added 146 automatically. 147 148 :jump-to-captured When set, jump to the captured entry when finished. 149 150 :empty-lines Set this to the number of lines that should be inserted 151 before and after the new item. Default 0, only common 152 other value is 1. 153 154 :empty-lines-before Set this to the number of lines that should be inserted 155 before the new item. Overrides :empty-lines for the 156 number lines inserted before. 157 158 :empty-lines-after Set this to the number of lines that should be inserted 159 after the new item. Overrides :empty-lines for the 160 number of lines inserted after. 161 162 :clock-in Start the clock in this item. 163 164 :clock-keep Keep the clock running when filing the captured entry. 165 166 :clock-resume Start the interrupted clock when finishing the capture. 167 Note that :clock-keep has precedence over :clock-resume. 168 When setting both to t, the current clock will run and 169 the previous one will not be resumed. 170 171 :time-prompt Prompt for a date/time to be used for date/week trees 172 and when filling the template. 173 174 :tree-type When `week', make a week tree instead of the month-day 175 tree. When `month', make a month tree instead of the 176 month-day tree. 177 178 :unnarrowed Do not narrow the target buffer, simply show the 179 full buffer. Default is to narrow it so that you 180 only see the new stuff. 181 182 :table-line-pos Specification of the location in the table where the 183 new line should be inserted. It should be a string like 184 \"II-3\", meaning that the new line should become the 185 third line before the second horizontal separator line. 186 187 :kill-buffer If the target file was not yet visited by a buffer when 188 capture was invoked, kill the buffer again after capture 189 is finalized. 190 191 :no-save Do not save the target file after finishing the capture. 192 193 The template defines the text to be inserted. Often this is an 194 Org mode entry (so the first line should start with a star) that 195 will be filed as a child of the target headline. It can also be 196 freely formatted text. Furthermore, the following %-escapes will 197 be replaced with content and expanded: 198 199 %[pathname] Insert the contents of the file given by 200 `pathname'. These placeholders are expanded at the very 201 beginning of the process so they can be used to extend the 202 current template. 203 %(sexp) Evaluate elisp `(sexp)' and replace it with the results. 204 Only placeholders pre-existing within the template, or 205 introduced with %[pathname] are expanded this way. Since this 206 happens after expanding non-interactive %-escapes, those can 207 be used to fill the expression. 208 %<...> The result of `format-time-string' on the ... format 209 specification. 210 %t Time stamp, date only. The time stamp is the current time, 211 except when called from agendas with `\\[org-agenda-capture]' or 212 with `org-capture-use-agenda-date' set. 213 %T Time stamp as above, with date and time. 214 %u, %U Like the above, but inactive time stamps. 215 %i Initial content, copied from the active region. If 216 there is text before %i on the same line, such as 217 indentation, and %i is not inside a %(sexp), that prefix 218 will be added before every line in the inserted text. 219 %a Annotation, normally the link created with `org-store-link'. 220 %A Like %a, but prompt for the description part. 221 %l Like %a, but only insert the literal link. 222 %L Like %l, but without brackets (the link content itself). 223 %c Current kill ring head. 224 %x Content of the X clipboard. 225 %k Title of currently clocked task. 226 %K Link to currently clocked task. 227 %n User name (taken from the variable `user-full-name'). 228 %f File visited by current buffer when `org-capture' was called. 229 %F Full path of the file or directory visited by current buffer. 230 %:keyword Specific information for certain link types, see below. 231 %^g Prompt for tags, with completion on tags in target file. 232 %^G Prompt for tags, with completion on all tags in all agenda files. 233 %^t Like %t, but prompt for date. Similarly %^T, %^u, %^U. 234 You may define a prompt like: %^{Please specify birthday}t. 235 The default date is that of %t, see above. 236 %^C Interactive selection of which kill or clip to use. 237 %^L Like %^C, but insert as link. 238 %^{prop}p Prompt the user for a value for property `prop'. 239 A default value can be specified like this: 240 %^{prop|default}p. 241 %^{prompt} Prompt the user for a string and replace this sequence with it. 242 A default value and a completion table can be specified like this: 243 %^{prompt|default|completion2|completion3|...}. 244 %? After completing the template, position cursor here. 245 %\\1 ... %\\N Insert the text entered at the nth %^{prompt}, where N 246 is a number, starting from 1. 247 248 Apart from these general escapes, you can access information specific to 249 the link type that is created. For example, calling `org-capture' in emails 250 or in Gnus will record the author and the subject of the message, which you 251 can access with \"%:from\" and \"%:subject\", respectively. Here is a 252 complete list of what is recorded for each link type. 253 254 Link type | Available information 255 ------------------------+------------------------------------------------------ 256 bbdb | %:type %:name %:company 257 vm, wl, mh, mew, rmail, | %:type %:subject %:message-id 258 gnus | %:from %:fromname %:fromaddress 259 | %:to %:toname %:toaddress 260 | %:fromto (either \"to NAME\" or \"from NAME\") 261 | %:date %:date-timestamp (as active timestamp) 262 | %:date-timestamp-inactive (as inactive timestamp) 263 gnus | %:group, for messages also all email fields 264 eww, w3, w3m | %:type %:url 265 info | %:type %:file %:node 266 calendar | %:type %:date 267 268 When you need to insert a literal percent sign in the template, 269 you can escape ambiguous cases with a backward slash, e.g., \\%i. 270 271 In addition to all of the above, Org-roam supports additional 272 substitutions within its templates. \"${foo}\" will look for the 273 foo property in the Org-roam node (see the `org-roam-node'). If 274 the property does not exist, the user will be prompted to fill in 275 the string value. 276 277 Org-roam templates are NOT compatible with regular Org capture: 278 they rely on additional hacks and hooks to achieve the 279 streamlined user experience in Org-roam." 280 :group 'org-roam 281 :type '(repeat 282 (choice (list :tag "Multikey description" 283 (string :tag "Keys ") 284 (string :tag "Description")) 285 (list :tag "Template entry" 286 (string :tag "Keys ") 287 (string :tag "Description ") 288 (choice :tag "Capture Type " :value entry 289 (const :tag "Org entry" entry) 290 (const :tag "Plain list item" item) 291 (const :tag "Checkbox item" checkitem) 292 (const :tag "Plain text" plain) 293 (const :tag "Table line" table-line)) 294 (choice :tag "Template " 295 (string) 296 (list :tag "File" 297 (const :format "" file) 298 (file :tag "Template file")) 299 (list :tag "Function" 300 (const :format "" function) 301 (function :tag "Template function"))) 302 (plist :inline t 303 ;; Give the most common options as checkboxes 304 :options (((const :format "%v " :target) 305 (choice :tag "Node location" 306 (list :tag "File" 307 (const :format "" file) 308 (string :tag " File")) 309 (list :tag "File & Head Content" 310 (const :format "" file+head) 311 (string :tag " File") 312 (string :tag " Head Content")) 313 (list :tag "File & Outline path" 314 (const :format "" file+olp) 315 (string :tag " File") 316 (list :tag "Outline path" 317 (repeat (string :tag "Headline")))) 318 (list :tag "File & Head Content & Outline path" 319 (const :format "" file+head+olp) 320 (string :tag " File") 321 (string :tag " Head Content") 322 (list :tag "Outline path" 323 (repeat (string :tag "Headline")))))) 324 ((const :format "%v " :prepend) (const t)) 325 ((const :format "%v " :immediate-finish) (const t)) 326 ((const :format "%v " :jump-to-captured) (const t)) 327 ((const :format "%v " :empty-lines) (const 1)) 328 ((const :format "%v " :empty-lines-before) (const 1)) 329 ((const :format "%v " :empty-lines-after) (const 1)) 330 ((const :format "%v " :clock-in) (const t)) 331 ((const :format "%v " :clock-keep) (const t)) 332 ((const :format "%v " :clock-resume) (const t)) 333 ((const :format "%v " :time-prompt) (const t)) 334 ((const :format "%v " :tree-type) (const week)) 335 ((const :format "%v " :unnarrowed) (const t)) 336 ((const :format "%v " :table-line-pos) (string)) 337 ((const :format "%v " :kill-buffer) (const t)))))))) 338 339 (defcustom org-roam-capture-new-node-hook nil 340 "Normal-mode hooks run when a new Org-roam node is created. 341 The current point is the point of the new node. 342 The hooks must not move the point." 343 :group 'org-roam 344 :type 'hook) 345 346 (defvar org-roam-capture-preface-hook nil 347 "Hook run when Org-roam tries to determine capture location of the node. 348 If any hook returns a value (which should be an ID), all hooks 349 after it are ignored. 350 351 With this hook you can hijack controls over the location of the 352 node for which the capture process is currently running for, or 353 use to just perform an arbitrary side effect, e.g. modify the 354 state related to the capture process. See `org-roam-protocol' and 355 `org-roam-dailies' as examples for what and how this hook is used 356 for. 357 358 If you're trying to perform the hijack, it's mandatory for you to: 359 1. Set the currently active buffer for editing operations using 360 `org-capture-target-buffer'. 361 2. Place the point in this buffer from where the location starts 362 from (e.g. if it's a file based node it should be the BOB, 363 otherwise it should be the position from where the heading 364 based node starts from). 365 3. Return the ID (as a string) of the capturing node. 366 367 If you use this hook for any other purpose, but not the hijack, 368 it's mandatory that you should return nil as the return value; so 369 the capture process would be able to setup the capture buffer. 370 371 If you need to do something when you capture new nodes, use 372 `org-roam-capture-new-node-hook' instead of this hook. 373 374 WARNING: This hook is primarily designed for the usage by the 375 extensions and packages, and requires understanding of the 376 internal capture process. If you don't understand it, you should 377 learn these internals before using this or use it at your own 378 risk breaking things.") 379 380 ;;; Variables 381 382 (defvar org-roam-capture--node nil 383 "The node passed during an Org-roam capture. 384 This variable is populated dynamically, and is only non-nil 385 during the Org-roam capture process.") 386 387 (defvar org-roam-capture--info nil 388 "A property-list of additional information passed to the Org-roam template. 389 This variable is populated dynamically, and is only non-nil 390 during the Org-roam capture process.") 391 392 (defconst org-roam-capture--template-keywords (list :target :id :link-description :call-location 393 :region) 394 "Keywords used in `org-roam-capture-templates' specific to Org-roam.") 395 396 ;;; Main entry point 397 ;;;###autoload 398 (cl-defun org-roam-capture- (&key goto keys node info props templates) 399 "Main entry point of `org-roam-capture' module. 400 GOTO and KEYS correspond to `org-capture' arguments. 401 INFO is a plist for filling up Org-roam's capture templates. 402 NODE is an `org-roam-node' construct containing information about the node. 403 PROPS is a plist containing additional Org-roam properties for each template. 404 TEMPLATES is a list of org-roam templates." 405 (let* ((props (plist-put props :call-location (point-marker))) 406 (org-capture-templates 407 (mapcar (lambda (template) 408 (org-roam-capture--convert-template template props)) 409 (or templates org-roam-capture-templates))) 410 (_ (setf (org-roam-node-id node) (or (org-roam-node-id node) 411 (org-id-new)))) 412 (org-roam-capture--node node) 413 (org-roam-capture--info info)) 414 (when (and (not keys) 415 (= (length org-capture-templates) 1)) 416 (setq keys (caar org-capture-templates))) 417 (org-capture goto keys))) 418 419 ;;;###autoload 420 (cl-defun org-roam-capture (&optional goto keys &key filter-fn templates info) 421 "Launches an `org-capture' process for a new or existing node. 422 This uses the templates defined at `org-roam-capture-templates'. 423 Arguments GOTO and KEYS see `org-capture'. 424 FILTER-FN is a function to filter out nodes: it takes an `org-roam-node', 425 and when nil is returned the node will be filtered out. 426 The TEMPLATES, if provided, override the list of capture templates (see 427 `org-roam-capture-'.) 428 The INFO, if provided, is passed along to the underlying `org-roam-capture-'." 429 (interactive "P") 430 (let ((node (org-roam-node-read nil filter-fn))) 431 (org-roam-capture- :goto goto 432 :info info 433 :keys keys 434 :templates templates 435 :node node 436 :props '(:immediate-finish nil)))) 437 438 ;;; Capture process 439 (defun org-roam-capture-p () 440 "Return t if the current capture process is an Org-roam capture. 441 This function is to only be called when `org-capture-plist' is 442 valid for the capture (i.e. initialization, and finalization of 443 the capture)." 444 (plist-get org-capture-plist :org-roam)) 445 446 (defun org-roam-capture--get (keyword) 447 "Get the value for KEYWORD from the `org-roam-capture-template'." 448 (plist-get (plist-get org-capture-plist :org-roam) keyword)) 449 450 (defun org-roam-capture--put (prop value) 451 "Set property PROP to VALUE in the `org-roam-capture-template'." 452 (let ((p (plist-get org-capture-plist :org-roam))) 453 (setq org-capture-plist 454 (plist-put org-capture-plist 455 :org-roam 456 (plist-put p prop value))))) 457 458 ;;;; Capture target 459 (defun org-roam-capture--prepare-buffer () 460 "Prepare the capture buffer for the current Org-roam based capture template. 461 This function will initialize and setup the capture buffer, 462 position the point to the current :target (and if necessary, 463 create it if it doesn't exist), and place the point for further 464 processing by `org-capture'. 465 466 Note: During the capture process this function is run by 467 `org-capture-set-target-location', as a (function ...) based 468 capture target." 469 (let ((id (cond ((run-hook-with-args-until-success 'org-roam-capture-preface-hook)) 470 (t (org-roam-capture--setup-target-location))))) 471 (org-roam-capture--adjust-point-for-capture-type) 472 (let ((template (org-capture-get :template))) 473 (when (stringp template) 474 (org-capture-put 475 :template 476 (org-roam-capture--fill-template template)))) 477 (org-roam-capture--put :id id) 478 (org-roam-capture--put :finalize (or (org-capture-get :finalize) 479 (org-roam-capture--get :finalize))))) 480 481 (defun org-roam-capture--setup-target-location () 482 "Initialize the buffer, and goto the location of the new capture. 483 Return the ID of the location." 484 (let (p new-file-p) 485 (pcase (org-roam-capture--get-target) 486 (`(file ,path) 487 (setq path (org-roam-capture--target-truepath path) 488 new-file-p (org-roam-capture--new-file-p path)) 489 (when new-file-p (org-roam-capture--put :new-file path)) 490 (set-buffer (org-capture-target-buffer path)) 491 (widen) 492 (setq p (goto-char (point-min)))) 493 (`(file+olp ,path ,olp) 494 (setq path (org-roam-capture--target-truepath path) 495 new-file-p (org-roam-capture--new-file-p path)) 496 (when new-file-p (org-roam-capture--put :new-file path)) 497 (set-buffer (org-capture-target-buffer path)) 498 (setq p (point-min)) 499 (let ((m (org-roam-capture-find-or-create-olp olp))) 500 (goto-char m)) 501 (widen)) 502 (`(file+head ,path ,head) 503 (setq path (org-roam-capture--target-truepath path) 504 new-file-p (org-roam-capture--new-file-p path)) 505 (set-buffer (org-capture-target-buffer path)) 506 (when new-file-p 507 (org-roam-capture--put :new-file path) 508 (insert (org-roam-capture--fill-template head 'ensure-newline))) 509 (widen) 510 (setq p (goto-char (point-min)))) 511 (`(file+head+olp ,path ,head ,olp) 512 (setq path (org-roam-capture--target-truepath path) 513 new-file-p (org-roam-capture--new-file-p path)) 514 (set-buffer (org-capture-target-buffer path)) 515 (widen) 516 (when new-file-p 517 (org-roam-capture--put :new-file path) 518 (insert (org-roam-capture--fill-template head 'ensure-newline))) 519 (setq p (point-min)) 520 (let ((m (org-roam-capture-find-or-create-olp olp))) 521 (goto-char m))) 522 (`(file+datetree ,path ,tree-type) 523 (setq path (org-roam-capture--target-truepath path)) 524 (require 'org-datetree) 525 (widen) 526 (set-buffer (org-capture-target-buffer path)) 527 (unless (file-exists-p path) 528 (org-roam-capture--put :new-file path)) 529 (funcall 530 (pcase tree-type 531 (`week #'org-datetree-find-iso-week-create) 532 (`month #'org-datetree-find-month-create) 533 (_ #'org-datetree-find-date-create)) 534 (calendar-gregorian-from-absolute 535 (cond 536 (org-overriding-default-time 537 ;; Use the overriding default time. 538 (time-to-days org-overriding-default-time)) 539 ((org-capture-get :default-time) 540 (time-to-days (org-capture-get :default-time))) 541 ((org-capture-get :time-prompt) 542 ;; Prompt for date. Bind `org-end-time-was-given' so 543 ;; that `org-read-date-analyze' handles the time range 544 ;; case and returns `prompt-time' with the start value. 545 (let* ((org-time-was-given nil) 546 (org-end-time-was-given nil) 547 (prompt-time (org-read-date 548 nil t nil "Date for tree entry:"))) 549 (org-capture-put 550 :default-time 551 (if (or org-time-was-given 552 (= (time-to-days prompt-time) (org-today))) 553 prompt-time 554 ;; Use 00:00 when no time is given for another 555 ;; date than today? 556 (apply #'encode-time 0 0 557 org-extend-today-until 558 (cl-cdddr (decode-time prompt-time))))) 559 (time-to-days prompt-time))) 560 (t 561 ;; Current date, possibly corrected for late night 562 ;; workers. 563 (org-today))))) 564 (setq p (point))) 565 (`(node ,title-or-id) 566 ;; first try to get ID, then try to get title/alias 567 (let ((node (or (org-roam-node-from-id title-or-id) 568 (org-roam-node-from-title-or-alias title-or-id) 569 (user-error "No node with title or id \"%s\"" title-or-id)))) 570 (set-buffer (org-capture-target-buffer (org-roam-node-file node))) 571 (goto-char (org-roam-node-point node)) 572 (setq p (org-roam-node-point node))))) 573 ;; Setup `org-id' for the current capture target and return it back to the 574 ;; caller. 575 (save-excursion 576 (goto-char p) 577 (if-let ((id (org-entry-get p "ID"))) 578 (setf (org-roam-node-id org-roam-capture--node) id) 579 (org-entry-put p "ID" (org-roam-node-id org-roam-capture--node))) 580 (prog1 581 (org-id-get) 582 (run-hooks 'org-roam-capture-new-node-hook))))) 583 584 (defun org-roam-capture--get-target () 585 "Get the current capture :target for the capture template in use." 586 (or (org-roam-capture--get :target) 587 (user-error "Template needs to specify `:target'"))) 588 589 (defun org-roam-capture--target-truepath (path) 590 "From PATH get the correct path to the current capture target and return it. 591 PATH is a string that can optionally contain templated text in 592 it." 593 (or (org-roam-node-file org-roam-capture--node) 594 (thread-first 595 path 596 (org-roam-capture--fill-template) 597 (string-trim) 598 (expand-file-name org-roam-directory)))) 599 600 (defun org-roam-capture--new-file-p (path) 601 "Return t if PATH is for a new file with no visiting buffer." 602 (not (or (file-exists-p path) 603 (org-find-base-buffer-visiting path)))) 604 605 (defun org-roam-capture-find-or-create-olp (olp) 606 "Return a marker pointing to the entry at OLP in the current buffer. 607 If OLP does not exist, create it. If anything goes wrong, throw 608 an error, and if you need to do something based on this error, 609 you can catch it with `condition-case'." 610 (let* ((level 1) 611 (lmin 1) 612 (lmax 1) 613 (start (point-min)) 614 (end (point-max)) 615 found flevel) 616 (unless (derived-mode-p 'org-mode) 617 (error "Buffer %s needs to be in Org mode" (current-buffer))) 618 (org-with-wide-buffer 619 (goto-char start) 620 (dolist (heading olp) 621 (setq heading (org-roam-capture--fill-template heading)) 622 (let ((re (format org-complex-heading-regexp-format 623 (regexp-quote heading))) 624 (cnt 0)) 625 (while (re-search-forward re end t) 626 (setq level (- (match-end 1) (match-beginning 1))) 627 (when (and (>= level lmin) (<= level lmax)) 628 (setq found (match-beginning 0) flevel level cnt (1+ cnt)))) 629 (when (> cnt 1) 630 (error "Heading not unique on level %d: %s" lmax heading)) 631 (when (= cnt 0) 632 ;; Create heading if it doesn't exist 633 (goto-char end) 634 (unless (bolp) (newline)) 635 (let (org-insert-heading-respect-content) 636 (org-insert-heading nil nil t)) 637 (unless (= lmax 1) 638 (dotimes (_ level) (org-do-demote))) 639 (insert heading) 640 (setq end (point)) 641 (goto-char start) 642 (while (re-search-forward re end t) 643 (setq level (- (match-end 1) (match-beginning 1))) 644 (when (and (>= level lmin) (<= level lmax)) 645 (setq found (match-beginning 0) flevel level cnt (1+ cnt)))))) 646 (goto-char found) 647 (setq lmin (1+ flevel) lmax (+ lmin (if org-odd-levels-only 1 0))) 648 (setq start found 649 end (save-excursion (org-end-of-subtree t t)))) 650 (point-marker)))) 651 652 (defun org-roam-capture--adjust-point-for-capture-type (&optional pos) 653 "Reposition the point for template insertion dependently on the capture type. 654 Return the newly adjusted position of `point'. 655 656 POS is the current position of point (an integer) inside the 657 currently active capture buffer, where the adjustment should 658 start to begin from. If it's nil, then it will default to 659 the current value of `point'." 660 (or pos (setq pos (point))) 661 (goto-char pos) 662 (let ((location-type (if (= pos 1) 'beginning-of-file 'heading-at-point))) 663 (and (eq location-type 'heading-at-point) 664 (cl-assert (org-at-heading-p))) 665 (pcase (org-capture-get :type) 666 (`plain 667 (cl-case location-type 668 (beginning-of-file 669 (if (org-capture-get :prepend) 670 (let ((el (org-element-at-point))) 671 (while (and (not (eobp)) 672 (memq (org-element-type el) 673 '(drawer property-drawer keyword comment comment-block horizontal-rule))) 674 (goto-char (org-element-property :end el)) 675 (setq el (org-element-at-point)))) 676 (goto-char (org-entry-end-position)))) 677 (heading-at-point 678 (if (org-capture-get :prepend) 679 (org-end-of-meta-data t) 680 (goto-char (org-entry-end-position)))))))) 681 (point)) 682 683 ;;; Capture implementation 684 (add-hook 'org-roam-capture-preface-hook #'org-roam-capture--try-capture-to-ref-h) 685 (defun org-roam-capture--try-capture-to-ref-h () 686 "Try to capture to an existing node that match the ref." 687 (when-let ((node (and (plist-get org-roam-capture--info :ref) 688 (org-roam-node-from-ref 689 (plist-get org-roam-capture--info :ref))))) 690 (set-buffer (org-capture-target-buffer (org-roam-node-file node))) 691 (goto-char (org-roam-node-point node)) 692 (widen) 693 (org-roam-node-id node))) 694 695 (add-hook 'org-roam-capture-new-node-hook #'org-roam-capture--insert-captured-ref-h) 696 (defun org-roam-capture--insert-captured-ref-h () 697 "Insert the ref if any." 698 (when-let ((ref (plist-get org-roam-capture--info :ref))) 699 (org-roam-ref-add ref))) 700 701 ;;;; Finalizers 702 (add-hook 'org-capture-prepare-finalize-hook #'org-roam-capture--install-finalize-h) 703 (defun org-roam-capture--install-finalize-h () 704 "Install `org-roam-capture--finalize' if the capture is an Org-roam capture." 705 (when (org-roam-capture-p) 706 (add-hook 'org-capture-after-finalize-hook #'org-roam-capture--finalize))) 707 708 (defun org-roam-capture--finalize () 709 "Finalize the `org-roam-capture' process." 710 (when-let ((region (org-roam-capture--get :region))) 711 (org-roam-unshield-region (car region) (cdr region))) 712 (if org-note-abort 713 (when-let ((new-file (org-roam-capture--get :new-file)) 714 (_ (yes-or-no-p "Delete file for aborted capture?"))) 715 (when (find-buffer-visiting new-file) 716 (kill-buffer (find-buffer-visiting new-file))) 717 (delete-file new-file)) 718 (when-let* ((buffer (plist-get org-capture-plist :buffer)) 719 (file (buffer-file-name buffer))) 720 (org-id-add-location (org-roam-capture--get :id) file)) 721 (when-let* ((finalize (org-roam-capture--get :finalize)) 722 (org-roam-finalize-fn (intern (concat "org-roam-capture--finalize-" 723 (symbol-name finalize))))) 724 (if (functionp org-roam-finalize-fn) 725 (funcall org-roam-finalize-fn) 726 (funcall finalize)))) 727 (remove-hook 'org-capture-after-finalize-hook #'org-roam-capture--finalize)) 728 729 (defun org-roam-capture--finalize-find-file () 730 "Visit the buffer after Org-capture is done. 731 This function is to be called in the Org-capture finalization process. 732 ID is unused." 733 (switch-to-buffer (org-capture-get :buffer))) 734 735 (defun org-roam-capture--finalize-insert-link () 736 "Insert a link to ID into the buffer where Org-capture was called. 737 ID is the Org id of the newly captured content. 738 This function is to be called in the Org-capture finalization process." 739 (when-let* ((mkr (org-roam-capture--get :call-location)) 740 (buf (marker-buffer mkr))) 741 (with-current-buffer buf 742 (when-let ((region (org-roam-capture--get :region))) 743 (org-roam-unshield-region (car region) (cdr region)) 744 (delete-region (car region) (cdr region)) 745 (set-marker (car region) nil) 746 (set-marker (cdr region) nil)) 747 (let* ((id (org-roam-capture--get :id)) 748 (description (org-roam-capture--get :link-description)) 749 (link (org-link-make-string (concat "id:" id) 750 description))) 751 (if (eq (point) (marker-position mkr)) 752 (insert link) 753 (org-with-point-at mkr 754 (insert link))) 755 (run-hook-with-args 'org-roam-post-node-insert-hook 756 id 757 description))))) 758 759 ;;;; Processing of the capture templates 760 (defun org-roam-capture--fill-template (template &optional ensure-newline) 761 "Expand TEMPLATE and return it. 762 It expands ${var} occurrences in TEMPLATE, and then runs 763 org-capture's template expansion. 764 When ENSURE-NEWLINE, always ensure there's a newline behind." 765 (let* ((template (if (functionp template) 766 (funcall template) 767 template)) 768 (template-whitespace-content (org-roam-whitespace-content template))) 769 (setq template 770 (org-roam-format-template 771 template 772 (lambda (key default-val) 773 (let ((fn (intern key)) 774 (node-fn (intern (concat "org-roam-node-" key))) 775 (ksym (intern (concat ":" key)))) 776 (cond 777 ((fboundp fn) 778 (funcall fn org-roam-capture--node)) 779 ((fboundp node-fn) 780 (funcall node-fn org-roam-capture--node)) 781 ((plist-get org-roam-capture--info ksym) 782 (plist-get org-roam-capture--info ksym)) 783 (t (let ((r (read-from-minibuffer (format "%s: " key) default-val))) 784 (plist-put org-roam-capture--info ksym r) 785 r))))))) 786 ;; WARNING: 787 ;; `org-capture-fill-template' fills the template, but post-processes whitespace such that the resultant 788 ;; template does not start with any whitespace, and only ends with a single newline 789 ;; 790 ;; Instead, we restore the whitespace in the original template. 791 (setq template (replace-regexp-in-string "[\n]*\\'" "" (org-capture-fill-template template))) 792 (when (and ensure-newline 793 (string-equal template-whitespace-content "")) 794 (setq template-whitespace-content "\n")) 795 (setq template (concat template template-whitespace-content)) 796 template)) 797 798 (defun org-roam-capture--convert-template (template &optional props) 799 "Convert TEMPLATE from Org-roam syntax to `org-capture-templates' syntax. 800 PROPS is a plist containing additional Org-roam specific 801 properties to be added to the template." 802 (pcase template 803 (`(,_key ,_desc) 804 template) 805 ((or `(,key ,desc ,type ignore ,body . ,rest) 806 `(,key ,desc ,type (function ignore) ,body . ,rest) 807 `(,key ,desc ,type ,body . ,rest)) 808 (setq rest (append rest props)) 809 (let (org-roam-plist options) 810 (while rest 811 (let* ((key (pop rest)) 812 (val (pop rest)) 813 (custom (member key org-roam-capture--template-keywords))) 814 (when (and custom 815 (not val)) 816 (user-error "Invalid capture template format: %s\nkey %s cannot be nil" template key)) 817 (if custom 818 (setq org-roam-plist (plist-put org-roam-plist key val)) 819 (setq options (plist-put options key val))))) 820 (append `(,key ,desc ,type #'org-roam-capture--prepare-buffer ,body) 821 options 822 (list :org-roam org-roam-plist)))) 823 (_ 824 (signal 'invalid-template template)))) 825 826 827 (provide 'org-roam-capture) 828 829 ;;; org-roam-capture.el ends here