2

Suppose I want to add a small sketch to a org-mode document (and possibly a LaTeX one). I'd like to type [[file:my_sketch.png]], and when the file my_sketch doesn't exist, offer to open an application (some paint equivalent) to create it. Do you know of an org-mode package providing this?

It would even be better if it were to work also in a LaTeX file with a \includegraphics{my_sketch.png} line.

Drew
  • 75,699
  • 9
  • 109
  • 225
wilk
  • 519
  • 2
  • 11

2 Answers2

1

You could simply electrify the closing bracket. I.e., rebind the org-key ] to a command org+-electric-closing-bracket that checks whether there is an file url before point and open the application if the file is missing.

(defcustom org+-missing-link-target-program "gimp"
  "Program for creating files for link targets."
  :group 'org-link
  :type 'string)

(defcustom org+-link-target-re "^file:\\(.*\\)$"
  "Regexp identifying file link targets."
  :group 'org-link
  :type 'regexp)

(defun org+-electric-closing-bracket (n)
  "Insert current character and send prefix-arg greater element, check whether we are at a bracketed link
   Should be bound to ?.
In that case start org+-missing-link-target-program "
  (interactive "p")
  (org-self-insert-command n)
  (when (looking-back org-bracket-link-regexp (line-beginning-position))
    (let* ((url (match-string 1))
       (descr (match-string 2))
       (fname (or (and (string-match org+-link-target-re url) (match-string 1 url))
              (and (string-match "`file:\\(.*\\)$" descr) (match-string 1 descr)))))
      (when (and (> (length fname) 0)
         (null (file-exists-p fname))
         (y-or-n-p (format "File \"%s\" missing.  Create with \"%s\"? " fname org+-missing-link-target-program)))
    (let ((fdir (file-name-directory fname)))
      (when (or (null fdir) ;; no directory component
            (file-exists-p fdir)
            (when (y-or-n-p (format "Directory \"%s\" missing. Create? " fdir))
              (mkdir fdir t)
              t))
        (let ((buf (get-buffer-create "*org process*")))
          (start-process "*org process*" buf org+-missing-link-target-program fname))))))))

(defun org+-eletrify-closing-bracket ()
  "Setup closing bracket for creating missing files."
  (local-set-key (kbd "]") #'org+-electric-closing-bracket))

(add-hook 'org-mode-hook #'org+-eletrify-closing-bracket)
Tobias
  • 32,569
  • 1
  • 34
  • 75
1

An alternative approach is to modify what clicking on the file link does. That way you choose when to open it in the editing program. Below I add a menu to the file link with completion that gives you an option to open it in inkscape. Apparently you cannot open a nonexistent file in inkscape, so there is some code to create one from a template first.

(defvar template-svg nil
  "Blank document for inkscape. You cannot create a file at the
  command line, so we put this template in and open it.")

(setq template-svg "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   xmlns:dc=\"http://purl.org/dc/elements/1.1/\"
   xmlns:cc=\"http://creativecommons.org/ns#\"
   xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"
   xmlns:svg=\"http://www.w3.org/2000/svg\"
   xmlns=\"http://www.w3.org/2000/svg\"
   xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"
   xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\"
   width=\"744.09448819\"
   height=\"1052.3622047\"
   id=\"svg2\"
   version=\"1.1\"
   inkscape:version=\"0.92.2 (5c3e80d, 2017-08-06)\"
   sodipodi:docname=\"some-sketch.svg\">
  <defs
     id=\"defs4\" />
  <sodipodi:namedview
     id=\"base\"
     pagecolor=\"#ffffff\"
     bordercolor=\"#666666\"
     borderopacity=\"1.0\"
     inkscape:pageopacity=\"0.0\"
     inkscape:pageshadow=\"2\"
     inkscape:zoom=\"0.35\"
     inkscape:cx=\"375\"
     inkscape:cy=\"520\"
     inkscape:document-units=\"px\"
     inkscape:current-layer=\"layer1\"
     showgrid=\"false\"
     inkscape:window-width=\"460\"
     inkscape:window-height=\"438\"
     inkscape:window-x=\"871\"
     inkscape:window-y=\"33\"
     inkscape:window-maximized=\"0\" />
  <metadata
     id=\"metadata7\">
    <rdf:RDF>
      <cc:Work
         rdf:about=\"\">
        <dc:format>image/svg+xml</dc:format>
        <dc:type
           rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\" />
        <dc:title></dc:title>
      </cc:Work>
    </rdf:RDF>
  </metadata>
  <g
     inkscape:label=\"Layer 1\"
     inkscape:groupmode=\"layer\"
     id=\"layer1\" />
</svg>")


(defun inkscape-open (path)
  "Open the path in inkscape"
  (interactive)
  (unless (f-ext-p path "svg") (error "Must be an svg file."))
  (unless (file-exists-p path)
    (with-temp-file path
      (insert template-svg)))
  (shell-command (format "inkscape %s &" path)))


(org-link-set-parameters
 "file"
 :follow (lambda (path)
       (let ((actions '(("find-file" . find-file)
                ("edit in inkscape" . inkscape-open))))
         (funcall (cdr (assoc (completing-read "Action: " actions) actions)) path))))
John Kitchin
  • 11,555
  • 1
  • 19
  • 41
  • Uups. I would **never** modify the `:follow` parameter for such a general url protocol like `file:`. If you need special handling define your own protocol. I considered that myself but it requires a quite complicated new definition for inline-display of images (see https://emacs.stackexchange.com/a/38109/2370). Furthermore `ivy-read` is not available in `emacs -Q`. You need to add a `require` or to specify installation instructions. – Tobias Jan 26 '18 at 16:50
  • I changed it to completing-read. Otherwise, to each their own. It is customizable so that you can customize it. I also considered a special link for it and ran into the same inline-image display complexity. – John Kitchin Jan 26 '18 at 18:32
  • It's exactly what I needed, I'm using it for png files with kolourpaint. It can create an empty file from the command line, so no need for a png template. – wilk Jan 31 '18 at 09:18