dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

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