At work, I mostly use Emacs for all my programming and text editing but I wouldn't really say I'm a pro. I'm more like beginner or intermediate so I'm not sure if this is really even a question.

I have been tasked with migrating our documentation from LaTeX to Markdown (Our own flavor of Markdown) and after converting to vanilla markdown with pandoc, I have found myself repeating the following pattern a lot.

Say I need to include an image in the .md file from the figures/ directory. My markdown will look something like this

![Fission gas release experimental comparison of IFA-535 rod 809 during
the ramp test.](rod809_fgr){width="4in"}

And I need to convert it to this

!media figures/rod809_fgr.png 
    caption=Fission gas release experimental comparison of IFA-535 rod 809 during the ramp test. 

Notice how the file name in the parenths (rod809_fgr) became figures/rod809_fgr.png and id=rod809_fgr and the description was put into the caption. Also the text was indented a specific way.

How can I write a Lisp function that highlights the original markdown and gives me the output I want? Once again, not too familiar with Lisp and still a beginner with Emacs.


Here's a regexp-based approach. Useful documentation links:

(defun markdown-to-dylanjm-image ()
  "Convert a Markdown image element to dylanjm's house markup format."
  (interactive "@*")
  ;; Look for a Markdown image element before the cursor or immediately
  ;; after the cursor. Don't look any further than the start of the current
  ;; paragraph.
  (let ((limit (save-excursion
        (regexp "\\s-*!\\[\\([^][]*\\)\\](\\([^()]*\\))\\({[^}{]*}\\)?"))
    (if (and (eq ?! (char-after (1- (point))))
             (eq ?\[ (char-after (point))))
    (while (not (looking-at regexp))
      (search-backward "![" limit))
    ;; If the search didn't error out, then we exited the loop with
    ;; `looking-at' matching `regexp'. The match data therefore contains
    ;; the parts of the image link.
    (skip-syntax-forward "-")
    (let ((caption (subst-char-in-string ?\n ?\  (match-string 1)))
          (id (match-string 2))
          (style (and (match-string 3)
                      (substring (match-string 3) 1 -1))))
      ;; Remove the Markdown syntax.
      (delete-region (- (match-beginning 1) 2) (match-end 0))
      ;; Add line breaks before and after if it looks like there aren't any.
      (unless (eolp)
        (open-line 1))
      (unless (<= (point) (save-excursion
        (insert "\n"))
      ;; Insert the new syntax.
      (insert "!media figures/" id ".png\n"
              "    id=" id "\n"
              "    caption=" caption)
      (when style
        (insert "\n    style=" style)))))

To bind this command to a C-c i after loading the markdown-mode package, put this in your init file:

(defun my-eval-after-load-markdown-mode ()
  (define-key markdown-mode-map "\C-ci" 'markdown-to-dylanjm-image))
(eval-after-load "markdown-mode" '(my-eval-after-load-markdown-mode))
| improve this answer | |
  • Where do I run the 2nd part (to establish the key-binding)? I tried putting it in my init.el file but it's giving me warnings. – dylanjm Feb 13 '19 at 22:56
  • @dylanjm I had a misplaced parenthesis. Fixed. – Gilles 'SO- stop being evil' Feb 13 '19 at 22:59
  • hmmm I'm now getting a strange error after putting the code in my init.el file. symbols value as variable is void: my-eval-after-load-markdown-mode – dylanjm Feb 13 '19 at 23:08
  • I'd removed too many parentheses... Sorry. – Gilles 'SO- stop being evil' Feb 13 '19 at 23:25
  • Sorry, it's not really working for me. It turns off my markdown-mode when I copy/paste it into my init.el file – dylanjm Feb 13 '19 at 23:54

Just put your cursor at "!" (or anywhere before "}") and run adl/md-converting, that should solve.

(defun adl/md-converting ()
    (adl/md-bas ?\{ "style=width:50%")
    (adl/md-bas ?\[ "caption=")
    (adl/md-bas ?\( "id=")))

(defun adl/md-remove-new-lines ()
  (let ((beg (point)))
    (search-forward "}")
    (narrow-to-region beg (point)))
  (goto-char (point-min))
  (while (search-forward "\n" nil t) (replace-match " " nil t))
  (search-backward "!" nil t))

(defun adl/md-bas (delim tag)
  (search-forward (string delim))
      ((beg (point))
    (cond ((eq delim ?\() ?\))
          ((eq delim ?\[) ?\])
          ((eq delim ?\{) ?\}))))
    (search-forward (string compl-delim))
    (delete-char -1)
    (kill-region beg (point)))
  (delete-char -1)
  (insert (concat "    " tag))
  (unless (eq delim ?{) (yank))
  (search-backward "!" nil t)
  (when (eq delim ?\()
    (insert "media figures/")
    (insert ".png")))

EDIT: You can map it to C-x C-m with (global-set-key (kbd "C-x C-m") 'adl/md-converting). You should put these codes in your init file.

It is worth noting that C-m is RET so C-x C-m is equal to C-x RET (C-x + ENTER).

| improve this answer | |
  • Wow! That's awesome! Is there a way to map it to keybindings like C-x C-m or something similar? – dylanjm Feb 13 '19 at 22:39
  • @dylanjm I updated the answer with the keybinding part. – adl Feb 14 '19 at 23:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.