dotemacs

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

org-roam-capture.el (36884B)


      1 ;;; org-roam-capture.el --- Capture functionality -*- coding: utf-8; lexical-binding: t; -*-
      2 
      3 ;; Copyright © 2020-2021 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.1.0
      9 ;; Package-Requires: ((emacs "26.1") (dash "2.13") (f "0.17.2") (org "9.4") (emacsql "3.0.0") (emacsql-sqlite "1.0.0") (magit-section "2.90.1"))
     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      :if-new (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 :if-new property. This determines the
     96 location of the new node. The :if-new property contains a list, supporting
     97 the following options:
     98 
     99    (file \"path/to/file\")
    100        The file will be created, and prescribed an ID.
    101 
    102    (file+head \"path/to/file\" \"head content\")
    103        The file will be created, prescribed an ID, and head content will be
    104        inserted into the file.
    105 
    106    (file+olp \"path/to/file\" (\"h1\" \"h2\"))
    107        The file will be created, prescribed an ID. The OLP (h1, h2) will be
    108        created, and the point placed after.
    109 
    110    (file+head+olp \"path/to/file\" \"head content\" (\"h1\" \"h2\"))
    111        The file will be created, prescribed an ID. Head content will be
    112        inserted at the start of the file. The OLP (h1, h2) will be created,
    113        and the point placed after.
    114 
    115    (file+datetree \"path/to/file\" day)
    116        The file will be created, prescribed an ID. Head content will be
    117        inserted at the start of the file. The datetree will be created,
    118        available options are day, week, month.
    119 
    120    (node \"title or alias or ID of an existing node\")
    121        The point will be placed for an existing node, based on either, its
    122        title, alias or ID.
    123 
    124 The rest of the entry is a property list of additional options.  Recognized
    125 properties are:
    126 
    127  :prepend            Normally newly captured information will be appended at
    128                      the target location (last child, last table line,
    129                      last list item...).  Setting this property will
    130                      change that.
    131 
    132  :immediate-finish   When set, do not offer to edit the information, just
    133                      file it away immediately.  This makes sense if the
    134                      template only needs information that can be added
    135                      automatically.
    136 
    137  :jump-to-captured   When set, jump to the captured entry when finished.
    138 
    139  :empty-lines        Set this to the number of lines that should be inserted
    140                      before and after the new item.  Default 0, only common
    141                      other value is 1.
    142 
    143  :empty-lines-before Set this to the number of lines that should be inserted
    144                      before the new item.  Overrides :empty-lines for the
    145                      number lines inserted before.
    146 
    147  :empty-lines-after  Set this to the number of lines that should be inserted
    148                      after the new item.  Overrides :empty-lines for the
    149                      number of lines inserted after.
    150 
    151  :clock-in           Start the clock in this item.
    152 
    153  :clock-keep         Keep the clock running when filing the captured entry.
    154 
    155  :clock-resume       Start the interrupted clock when finishing the capture.
    156                      Note that :clock-keep has precedence over :clock-resume.
    157                      When setting both to t, the current clock will run and
    158                      the previous one will not be resumed.
    159 
    160  :time-prompt        Prompt for a date/time to be used for date/week trees
    161                      and when filling the template.
    162 
    163  :tree-type          When `week', make a week tree instead of the month-day
    164                      tree.  When `month', make a month tree instead of the
    165                      month-day tree.
    166 
    167  :unnarrowed         Do not narrow the target buffer, simply show the
    168                      full buffer.  Default is to narrow it so that you
    169                      only see the new stuff.
    170 
    171  :table-line-pos     Specification of the location in the table where the
    172                      new line should be inserted.  It should be a string like
    173                      \"II-3\", meaning that the new line should become the
    174                      third line before the second horizontal separator line.
    175 
    176  :kill-buffer        If the target file was not yet visited by a buffer when
    177                      capture was invoked, kill the buffer again after capture
    178                      is finalized.
    179 
    180  :no-save            Do not save the target file after finishing the capture.
    181 
    182 The template defines the text to be inserted.  Often this is an
    183 Org mode entry (so the first line should start with a star) that
    184 will be filed as a child of the target headline.  It can also be
    185 freely formatted text.  Furthermore, the following %-escapes will
    186 be replaced with content and expanded:
    187 
    188   %[pathname] Insert the contents of the file given by
    189               `pathname'.  These placeholders are expanded at the very
    190               beginning of the process so they can be used to extend the
    191               current template.
    192   %(sexp)     Evaluate elisp `(sexp)' and replace it with the results.
    193               Only placeholders pre-existing within the template, or
    194               introduced with %[pathname] are expanded this way.  Since this
    195               happens after expanding non-interactive %-escapes, those can
    196               be used to fill the expression.
    197   %<...>      The result of `format-time-string' on the ... format specification.
    198   %t          Time stamp, date only.  The time stamp is the current time,
    199               except when called from agendas with `\\[org-agenda-capture]' or
    200               with `org-capture-use-agenda-date' set.
    201   %T          Time stamp as above, with date and time.
    202   %u, %U      Like the above, but inactive time stamps.
    203   %i          Initial content, copied from the active region.  If
    204               there is text before %i on the same line, such as
    205               indentation, and %i is not inside a %(sexp), that prefix
    206               will be added before every line in the inserted text.
    207   %a          Annotation, normally the link created with `org-store-link'.
    208   %A          Like %a, but prompt for the description part.
    209   %l          Like %a, but only insert the literal link.
    210   %L          Like %l, but without brackets (the link content itself).
    211   %c          Current kill ring head.
    212   %x          Content of the X clipboard.
    213   %k          Title of currently clocked task.
    214   %K          Link to currently clocked task.
    215   %n          User name (taken from the variable `user-full-name').
    216   %f          File visited by current buffer when `org-capture' was called.
    217   %F          Full path of the file or directory visited by current buffer.
    218   %:keyword   Specific information for certain link types, see below.
    219   %^g         Prompt for tags, with completion on tags in target file.
    220   %^G         Prompt for tags, with completion on all tags in all agenda files.
    221   %^t         Like %t, but prompt for date.  Similarly %^T, %^u, %^U.
    222               You may define a prompt like: %^{Please specify birthday}t.
    223               The default date is that of %t, see above.
    224   %^C         Interactive selection of which kill or clip to use.
    225   %^L         Like %^C, but insert as link.
    226   %^{prop}p   Prompt the user for a value for property `prop'.
    227               A default value can be specified like this:
    228               %^{prop|default}p.
    229   %^{prompt}  Prompt the user for a string and replace this sequence with it.
    230               A default value and a completion table can be specified like this:
    231               %^{prompt|default|completion2|completion3|...}.
    232   %?          After completing the template, position cursor here.
    233   %\\1 ... %\\N Insert the text entered at the nth %^{prompt}, where N
    234               is a number, starting from 1.
    235 
    236 Apart from these general escapes, you can access information specific to
    237 the link type that is created.  For example, calling `org-capture' in emails
    238 or in Gnus will record the author and the subject of the message, which you
    239 can access with \"%:from\" and \"%:subject\", respectively.  Here is a
    240 complete list of what is recorded for each link type.
    241 
    242 Link type               |  Available information
    243 ------------------------+------------------------------------------------------
    244 bbdb                    |  %:type %:name %:company
    245 vm, wl, mh, mew, rmail, |  %:type %:subject %:message-id
    246 gnus                    |  %:from %:fromname %:fromaddress
    247                         |  %:to   %:toname   %:toaddress
    248                         |  %:fromto (either \"to NAME\" or \"from NAME\")
    249                         |  %:date %:date-timestamp (as active timestamp)
    250                         |  %:date-timestamp-inactive (as inactive timestamp)
    251 gnus                    |  %:group, for messages also all email fields
    252 eww, w3, w3m            |  %:type %:url
    253 info                    |  %:type %:file %:node
    254 calendar                |  %:type %:date
    255 
    256 When you need to insert a literal percent sign in the template,
    257 you can escape ambiguous cases with a backward slash, e.g., \\%i.
    258 
    259 In addition to all of the above, Org-roam supports additional
    260 substitutions within its templates. \"${foo}\" will look for the
    261 foo property in the Org-roam node (see the `org-roam-node'). If
    262 the property does not exist, the user will be prompted to fill in
    263 the string value.
    264 
    265 Org-roam templates are NOT compatible with regular Org capture:
    266 they rely on additional hacks and hooks to achieve the
    267 streamlined user experience in Org-roam."
    268   :group 'org-roam
    269   :type '(repeat
    270           (choice (list :tag "Multikey description"
    271                         (string :tag "Keys       ")
    272                         (string :tag "Description"))
    273                   (list :tag "Template entry"
    274                         (string :tag "Keys           ")
    275                         (string :tag "Description    ")
    276                         (choice :tag "Capture Type   " :value entry
    277                                 (const :tag "Org entry" entry)
    278                                 (const :tag "Plain list item" item)
    279                                 (const :tag "Checkbox item" checkitem)
    280                                 (const :tag "Plain text" plain)
    281                                 (const :tag "Table line" table-line))
    282                         (choice :tag "Template       "
    283                                 (string)
    284                                 (list :tag "File"
    285                                       (const :format "" file)
    286                                       (file :tag "Template file"))
    287                                 (list :tag "Function"
    288                                       (const :format "" function)
    289                                       (function :tag "Template function")))
    290                         (plist :inline t
    291                                ;; Give the most common options as checkboxes
    292                                :options (((const :format "%v " :if-new)
    293                                           (choice :tag "Node location"
    294                                                   (list :tag "File"
    295                                                         (const :format "" file)
    296                                                         (string :tag "  File"))
    297                                                   (list :tag "File & Head Content"
    298                                                         (const :format "" file+head)
    299                                                         (string :tag "  File")
    300                                                         (string :tag "  Head Content"))
    301                                                   (list :tag "File & Outline path"
    302                                                         (const :format "" file+olp)
    303                                                         (string :tag "  File")
    304                                                         (list :tag "Outline path"
    305                                                               (repeat (string :tag "Headline"))))
    306                                                   (list :tag "File & Head Content & Outline path"
    307                                                         (const :format "" file+head+olp)
    308                                                         (string :tag "  File")
    309                                                         (string :tag "  Head Content")
    310                                                         (list :tag "Outline path"
    311                                                               (repeat (string :tag "Headline"))))))
    312                                          ((const :format "%v " :prepend) (const t))
    313                                          ((const :format "%v " :immediate-finish) (const t))
    314                                          ((const :format "%v " :jump-to-captured) (const t))
    315                                          ((const :format "%v " :empty-lines) (const 1))
    316                                          ((const :format "%v " :empty-lines-before) (const 1))
    317                                          ((const :format "%v " :empty-lines-after) (const 1))
    318                                          ((const :format "%v " :clock-in) (const t))
    319                                          ((const :format "%v " :clock-keep) (const t))
    320                                          ((const :format "%v " :clock-resume) (const t))
    321                                          ((const :format "%v " :time-prompt) (const t))
    322                                          ((const :format "%v " :tree-type) (const week))
    323                                          ((const :format "%v " :unnarrowed) (const t))
    324                                          ((const :format "%v " :table-line-pos) (string))
    325                                          ((const :format "%v " :kill-buffer) (const t))))))))
    326 
    327 (defcustom org-roam-capture-new-node-hook nil
    328   "Normal-mode hooks run when a new Org-roam node is created.
    329 The current point is the point of the new node.
    330 The hooks must not move the point."
    331   :group 'org-roam
    332   :type 'hook)
    333 
    334 (defvar org-roam-capture-preface-hook nil
    335   "Hook run when Org-roam tries to determine capture location of the node.
    336 If any hook returns a value (which should be an ID), all hooks
    337 after it are ignored.
    338 
    339 With this hook you can hijack controls over the location of the
    340 node for which the capture process is currently running for, or
    341 use to just perform an arbitrary side effect, e.g. modify the
    342 state related to the capture process. See `org-roam-protocol' and
    343 `org-roam-dailies' as examples for what and how this hook is used
    344 for.
    345 
    346 If you're trying to perform the hijack, it's mandatory for you to:
    347   1. Set the currently active buffer for editing operations using
    348      `org-capture-target-buffer'.
    349   2. Place the point in this buffer from where the location starts
    350      from (e.g. if it's a file based node it should be the BOB,
    351      otherwise it should be the position from where the heading
    352      based node starts from).
    353   3. Return the ID (as a string) of the capturing node.
    354 
    355 If you use this hook for any other purpose, but not the hijack,
    356 it's mandatory that you should return nil as the return value; so
    357 the capture process would be able to setup the capture buffer.
    358 
    359 If you need to do something when you capture new nodes, use
    360 `org-roam-capture-new-node-hook' instead of this hook.
    361 
    362 WARNING: This hook is primarily designed for the usage by the
    363 extensions and packages, and requires understanding of the
    364 internal capture process. If you don't understand it, you should
    365 learn these internals before using this or use it at your own
    366 risk breaking things.")
    367 
    368 ;;; Variables
    369 
    370 (defvar org-roam-capture--node nil
    371   "The node passed during an Org-roam capture.
    372 This variable is populated dynamically, and is only non-nil
    373 during the Org-roam capture process.")
    374 
    375 (defvar org-roam-capture--info nil
    376   "A property-list of additional information passed to the Org-roam template.
    377 This variable is populated dynamically, and is only non-nil
    378 during the Org-roam capture process.")
    379 
    380 (defconst org-roam-capture--template-keywords (list :if-new :id :link-description :call-location
    381                                                     :region)
    382   "Keywords used in `org-roam-capture-templates' specific to Org-roam.")
    383 
    384 ;;; Main entry point
    385 ;;;###autoload
    386 (cl-defun org-roam-capture- (&key goto keys node info props templates)
    387   "Main entry point of `org-roam-capture' module.
    388 GOTO and KEYS correspond to `org-capture' arguments.
    389 INFO is a plist for filling up Org-roam's capture templates.
    390 NODE is an `org-roam-node' construct containing information about the node.
    391 PROPS is a plist containing additional Org-roam properties for each template.
    392 TEMPLATES is a list of org-roam templates."
    393   (let* ((props (plist-put props :call-location (point-marker)))
    394          (org-capture-templates
    395           (mapcar (lambda (template)
    396                     (org-roam-capture--convert-template template props))
    397                   (or templates org-roam-capture-templates)))
    398          (org-roam-capture--node node)
    399          (org-roam-capture--info info))
    400     (when (and (not keys)
    401                (= (length org-capture-templates) 1))
    402       (setq keys (caar org-capture-templates)))
    403     (org-capture goto keys)))
    404 
    405 ;;;###autoload
    406 (cl-defun org-roam-capture (&optional goto keys &key filter-fn templates info)
    407   "Launches an `org-capture' process for a new or existing node.
    408 This uses the templates defined at `org-roam-capture-templates'.
    409 Arguments GOTO and KEYS see `org-capture'.
    410 FILTER-FN is a function to filter out nodes: it takes an `org-roam-node',
    411 and when nil is returned the node will be filtered out.
    412 The TEMPLATES, if provided, override the list of capture templates (see
    413 `org-roam-capture-'.)
    414 The INFO, if provided, is passed along to the underlying `org-roam-capture-'."
    415   (interactive "P")
    416   (let ((node (org-roam-node-read nil filter-fn)))
    417     (org-roam-capture- :goto goto
    418                        :info info
    419                        :keys keys
    420                        :templates templates
    421                        :node node
    422                        :props '(:immediate-finish nil))))
    423 
    424 ;;; Capture process
    425 (defun org-roam-capture-p ()
    426   "Return t if the current capture process is an Org-roam capture.
    427 This function is to only be called when `org-capture-plist' is
    428 valid for the capture (i.e. initialization, and finalization of
    429 the capture)."
    430   (plist-get org-capture-plist :org-roam))
    431 
    432 (defun org-roam-capture--get (keyword)
    433   "Get the value for KEYWORD from the `org-roam-capture-template'."
    434   (plist-get (plist-get org-capture-plist :org-roam) keyword))
    435 
    436 (defun org-roam-capture--put (&rest stuff)
    437   "Put properties from STUFF into the `org-roam-capture-template'."
    438   (let ((p (plist-get org-capture-plist :org-roam)))
    439     (while stuff
    440       (setq p (plist-put p (pop stuff) (pop stuff))))
    441     (setq org-capture-plist
    442           (plist-put org-capture-plist :org-roam p))))
    443 
    444 ;;;; Capture target
    445 (defun org-roam-capture--prepare-buffer ()
    446   "Prepare the capture buffer for the current Org-roam based capture template.
    447 This function will initialize and setup the capture buffer,
    448 create the target node (`:if-new') if it doesn't exist, and place
    449 the point for further processing by `org-capture'.
    450 
    451 Note: During the capture process this function is run by
    452 `org-capture-set-target-location', as a (function ...) based
    453 capture target."
    454   (let ((id (cond ((run-hook-with-args-until-success 'org-roam-capture-preface-hook))
    455                   ((and (org-roam-node-file org-roam-capture--node)
    456                         (org-roam-node-point org-roam-capture--node))
    457                    (set-buffer (org-capture-target-buffer (org-roam-node-file org-roam-capture--node)))
    458                    (goto-char (org-roam-node-point org-roam-capture--node))
    459                    (widen)
    460                    (org-roam-node-id org-roam-capture--node))
    461                   (t
    462                    (org-roam-capture--setup-target-location)))))
    463     (org-roam-capture--adjust-point-for-capture-type)
    464     (org-capture-put :template
    465                      (org-roam-capture--fill-template (org-capture-get :template)))
    466     (org-roam-capture--put :id id)
    467     (org-roam-capture--put :finalize (or (org-capture-get :finalize)
    468                                          (org-roam-capture--get :finalize)))))
    469 
    470 (defun org-roam-capture--setup-target-location ()
    471   "Initialize the buffer, and goto the location of the new capture.
    472 Return the ID of the location."
    473   (let (p new-file-p)
    474     (pcase (or (org-roam-capture--get :if-new)
    475                (user-error "Template needs to specify `:if-new'"))
    476       (`(file ,path)
    477        (setq path (expand-file-name
    478                    (string-trim (org-roam-capture--fill-template path t))
    479                    org-roam-directory))
    480        (setq new-file-p (org-roam-capture--new-file-p path))
    481        (when new-file-p (org-roam-capture--put :new-file path))
    482        (set-buffer (org-capture-target-buffer path))
    483        (widen)
    484        (setq p (goto-char (point-min))))
    485       (`(file+olp ,path ,olp)
    486        (setq path (expand-file-name
    487                    (string-trim (org-roam-capture--fill-template path t))
    488                    org-roam-directory))
    489        (setq new-file-p (org-roam-capture--new-file-p path))
    490        (when new-file-p (org-roam-capture--put :new-file path))
    491        (set-buffer (org-capture-target-buffer path))
    492        (setq p (point-min))
    493        (let ((m (org-roam-capture-find-or-create-olp olp)))
    494          (goto-char m))
    495        (widen))
    496       (`(file+head ,path ,head)
    497        (setq path (expand-file-name
    498                    (string-trim (org-roam-capture--fill-template path t))
    499                    org-roam-directory))
    500        (setq new-file-p (org-roam-capture--new-file-p path))
    501        (set-buffer (org-capture-target-buffer path))
    502        (when new-file-p
    503          (org-roam-capture--put :new-file path)
    504          (insert (org-roam-capture--fill-template head t)))
    505        (widen)
    506        (setq p (goto-char (point-min))))
    507       (`(file+head+olp ,path ,head ,olp)
    508        (setq path (expand-file-name
    509                    (string-trim (org-roam-capture--fill-template path t))
    510                    org-roam-directory))
    511        (setq new-file-p (org-roam-capture--new-file-p path))
    512        (set-buffer (org-capture-target-buffer path))
    513        (widen)
    514        (when new-file-p
    515          (org-roam-capture--put :new-file path)
    516          (insert (org-roam-capture--fill-template head t)))
    517        (setq p (point-min))
    518        (let ((m (org-roam-capture-find-or-create-olp olp)))
    519          (goto-char m)))
    520       (`(file+datetree ,path ,tree-type)
    521        (setq path (expand-file-name
    522                    (string-trim (org-roam-capture--fill-template path t))
    523                    org-roam-directory))
    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       (when-let* ((node org-roam-capture--node)
    578                   (id (org-roam-node-id node)))
    579         (org-entry-put p "ID" id))
    580       (prog1
    581           (org-id-get-create)
    582         (run-hooks 'org-roam-capture-new-node-hook)))))
    583 
    584 (defun org-roam-capture--new-file-p (path)
    585   "Return t if PATH is for a new file with no visiting buffer."
    586   (not (or (file-exists-p path)
    587            (org-find-base-buffer-visiting path))))
    588 
    589 (defun org-roam-capture-find-or-create-olp (olp)
    590   "Return a marker pointing to the entry at OLP in the current buffer.
    591 If OLP does not exist, create it. If anything goes wrong, throw
    592 an error, and if you need to do something based on this error,
    593 you can catch it with `condition-case'."
    594   (let* ((level 1)
    595          (lmin 1)
    596          (lmax 1)
    597          (start (point-min))
    598          (end (point-max))
    599          found flevel)
    600     (unless (derived-mode-p 'org-mode)
    601       (error "Buffer %s needs to be in Org mode" (current-buffer)))
    602     (org-with-wide-buffer
    603      (goto-char start)
    604      (dolist (heading olp)
    605        (let ((re (format org-complex-heading-regexp-format
    606                          (regexp-quote heading)))
    607              (cnt 0))
    608          (while (re-search-forward re end t)
    609            (setq level (- (match-end 1) (match-beginning 1)))
    610            (when (and (>= level lmin) (<= level lmax))
    611              (setq found (match-beginning 0) flevel level cnt (1+ cnt))))
    612          (when (> cnt 1)
    613            (error "Heading not unique on level %d: %s" lmax heading))
    614          (when (= cnt 0)
    615            ;; Create heading if it doesn't exist
    616            (goto-char end)
    617            (unless (bolp) (newline))
    618            (let (org-insert-heading-respect-content)
    619              (org-insert-heading nil nil t))
    620            (unless (= lmax 1)
    621              (dotimes (_ level) (org-do-demote)))
    622            (insert heading)
    623            (setq end (point))
    624            (goto-char start)
    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        (goto-char found)
    630        (setq lmin (1+ flevel) lmax (+ lmin (if org-odd-levels-only 1 0)))
    631        (setq start found
    632              end (save-excursion (org-end-of-subtree t t))))
    633      (point-marker))))
    634 
    635 (defun org-roam-capture--adjust-point-for-capture-type (&optional pos)
    636   "Reposition the point for template insertion dependently on the capture type.
    637 Return the newly adjusted position of `point'.
    638 
    639 POS is the current position of point (an integer) inside the
    640 currently active capture buffer, where the adjustment should
    641 start to begin from. If it's nil, then it will default to
    642 the current value of `point'."
    643   (or pos (setq pos (point)))
    644   (goto-char pos)
    645   (let ((location-type (if (= pos 1) 'beginning-of-file 'heading-at-point)))
    646     (and (eq location-type 'heading-at-point)
    647          (cl-assert (org-at-heading-p)))
    648     (pcase (org-capture-get :type)
    649       (`plain
    650        (cl-case location-type
    651          (beginning-of-file
    652           (if (org-capture-get :prepend)
    653               (let ((el (org-element-at-point)))
    654                 (while (and (not (eobp))
    655                             (memq (org-element-type el)
    656                                   '(drawer property-drawer keyword comment comment-block horizontal-rule)))
    657                   (goto-char (org-element-property :end el))
    658                   (setq el (org-element-at-point))))
    659             (goto-char (org-entry-end-position))))
    660          (heading-at-point
    661           (if (org-capture-get :prepend)
    662               (org-end-of-meta-data t)
    663             (goto-char (org-entry-end-position))))))))
    664   (point))
    665 
    666 ;;;; Finalizers
    667 (add-hook 'org-capture-prepare-finalize-hook #'org-roam-capture--install-finalize-h)
    668 (defun org-roam-capture--install-finalize-h ()
    669   "Install `org-roam-capture--finalize' if the capture is an Org-roam capture."
    670   (when (org-roam-capture-p)
    671     (add-hook 'org-capture-after-finalize-hook #'org-roam-capture--finalize)))
    672 
    673 (defun org-roam-capture--finalize ()
    674   "Finalize the `org-roam-capture' process."
    675   (when-let ((region (org-roam-capture--get :region)))
    676     (org-roam-unshield-region (car region) (cdr region)))
    677   (if org-note-abort
    678       (when-let ((new-file (org-roam-capture--get :new-file)))
    679         (org-roam-message "Deleting file for aborted capture %s" new-file)
    680         (when (find-buffer-visiting new-file)
    681           (kill-buffer (find-buffer-visiting new-file)))
    682         (delete-file new-file))
    683     (when-let* ((finalize (org-roam-capture--get :finalize))
    684                 (org-roam-finalize-fn (intern (concat "org-roam-capture--finalize-"
    685                                                       (symbol-name finalize)))))
    686       (if (functionp org-roam-finalize-fn)
    687           (funcall org-roam-finalize-fn)
    688         (funcall finalize))))
    689   (remove-hook 'org-capture-after-finalize-hook #'org-roam-capture--finalize))
    690 
    691 (defun org-roam-capture--finalize-find-file ()
    692   "Visit the buffer after Org-capture is done.
    693 This function is to be called in the Org-capture finalization process.
    694 ID is unused."
    695   (switch-to-buffer (org-capture-get :buffer)))
    696 
    697 (defun org-roam-capture--finalize-insert-link ()
    698   "Insert a link to ID into the buffer where Org-capture was called.
    699 ID is the Org id of the newly captured content.
    700 This function is to be called in the Org-capture finalization process."
    701   (when-let* ((mkr (org-roam-capture--get :call-location))
    702               (buf (marker-buffer mkr)))
    703     (with-current-buffer buf
    704       (when-let ((region (org-roam-capture--get :region)))
    705         (org-roam-unshield-region (car region) (cdr region))
    706         (delete-region (car region) (cdr region))
    707         (set-marker (car region) nil)
    708         (set-marker (cdr region) nil))
    709       (org-with-point-at mkr
    710         (insert (org-link-make-string (concat "id:" (org-roam-capture--get :id))
    711                                       (org-roam-capture--get :link-description)))))))
    712 
    713 ;;;; Processing of the capture templates
    714 (defun org-roam-capture--fill-template (template &optional org-capture-p)
    715   "Expand TEMPLATE and return it.
    716 It expands ${var} occurrences in TEMPLATE. When ORG-CAPTURE-P,
    717 also run Org-capture's template expansion."
    718   (funcall (if org-capture-p #'org-capture-fill-template #'identity)
    719            (org-roam-format-template
    720             template
    721             (lambda (key default-val)
    722               (let ((fn (intern key))
    723                     (node-fn (intern (concat "org-roam-node-" key)))
    724                     (ksym (intern (concat ":" key))))
    725                 (cond
    726                  ((fboundp fn)
    727                   (funcall fn org-roam-capture--node))
    728                  ((fboundp node-fn)
    729                   (funcall node-fn org-roam-capture--node))
    730                  ((plist-get org-roam-capture--info ksym)
    731                   (plist-get org-roam-capture--info ksym))
    732                  (t (let ((r (completing-read (format "%s: " key) nil nil nil default-val)))
    733                       (plist-put org-roam-capture--info ksym r)
    734                       r))))))))
    735 
    736 (defun org-roam-capture--convert-template (template &optional props)
    737   "Convert TEMPLATE from Org-roam syntax to `org-capture-templates' syntax.
    738 PROPS is a plist containing additional Org-roam specific
    739 properties to be added to the template."
    740   (pcase template
    741     (`(,_key ,_desc)
    742      template)
    743     ((or `(,key ,desc ,type ignore ,body . ,rest)
    744          `(,key ,desc ,type (function ignore) ,body . ,rest)
    745          `(,key ,desc ,type ,body . ,rest))
    746      (setq rest (append rest props))
    747      (let (org-roam-plist options)
    748        (while rest
    749          (let* ((key (pop rest))
    750                 (val (pop rest))
    751                 (custom (member key org-roam-capture--template-keywords)))
    752            (when (and custom
    753                       (not val))
    754              (user-error "Invalid capture template format: %s\nkey %s cannot be nil" template key))
    755            (if custom
    756                (setq org-roam-plist (plist-put org-roam-plist key val))
    757              (setq options (plist-put options key val)))))
    758        (append `(,key ,desc ,type #'org-roam-capture--prepare-buffer ,body)
    759                options
    760                (list :org-roam org-roam-plist))))
    761     (_
    762      (signal 'invalid-template template))))
    763 
    764 
    765 (provide 'org-roam-capture)
    766 
    767 ;;; org-roam-capture.el ends here