5

I often find myself writing ReST files with embedded source code examples (e.g. in .. code:: blocks or GFM-style triple-backticks; usually the embedded source code is Python). And this is always an exercise in frustration, because ReST mode has opinions about things like how text should be indented and where my cursor should go when I hit enter, and these are totally wrong for source code. So I find myself not only having to indent my code by hand (like an animal), but actively fighting against emacs to do it.

I'm hoping someone out there has found something, anything, that makes writing these kinds of documents less painful. Exiling source code snippets to external files sometimes helps, but it's not well-supported by all ReST processors and for short snippets often isn't worth the hassle. Possibly the gold standard would be an equivalent to org-mode's excellent org-edit-special (which pops out a source code block into its own buffer to edit in the proper major mode, while synchronizing changes back to the original buffer), but I'm not picky...

(If there's a markdown mode that handles this better I'd also be interested in that -- I don't always get a choice between rst and md, but in cases when I do then this could be a compelling differentiator.)

What do you do?

  • As you mentioned Org-mode, you might consider writing in Org directly and then exporting to other formats. – blujay Oct 14 '16 at 10:15
4

I think polymode can provide some help for this. By itself, polymode is framework for combining multiple major modes in a systematic way. Among the the examples, there is a polymode available that creates a new major mode based on markdown-mode that can display any other major-mode recognized by Emacs. The example can be seen here and is installed with polymode by default from Melpa.

There are some caveats though, polymode needs to figure out which major-mode somehow and thus the name must match the major mode used by Emacs. E.g., instead of writing shell, you have to write sh-script. See the samples folder on polymode for an some examples of this.

It is probably possible to fix that however by using some kind of look-up function, but that would require non-trivial changes. Furthermore, I believe it would be possible to create a ReST polymode in a similar fashion, but that would probably require a significant amount of hacking.

The configuration I ended up using for getting all of this to work is this:

(use-package polymode
  :ensure t
  :defer t)

(use-package poly-markdown
  :ensure polymode
  :defer t
  :mode ("\\.md" . poly-markdown-mode))

A word of warning though: It is important to know that mixing major-modes can cause a wide variety of issues. Usually, this means that font-locking or indenting can start to behave strangely, or the performance might degrade significantly. Personally though, I haven't run into any issues yet but then again, I haven't worked with any really large markdown files either.

EDIT

After getting slightly bored at work, I actually implemented the ReST equivalent to the poly-markdown-mode:

;;; poly-rest-mode.el -- Polymode for ReST. -*- lexical-binding: t -*-
;;
;;; Commentary:
;; A mode for interacting with multiple kinds of code inside a ReST host-mode.
;;
;;; Code:

(require 'polymode)


(defcustom pm-host/ReST
  (pm-bchunkmode "ReST"
                 :mode 'rst-mode
                 :init-functions '(poly-rest-fixes))
  "Restructued Text host chunkmode."
  :group 'hostmodes
  :type 'object)

(defvar pm-ReST-code-regexp
  (regexp-opt '("code" "code-block" "sourcecode" "highlight"))
  "Regular expression to match all possible ReST code blocks.")

(defvar pm-ReST-head-regexp
  (concat "^[^ ]*\\( *\.\. +" pm-ReST-code-regexp "::.*\n"
          "\\(?: +:.+\n\\)*\\)")      ;; Match 0 or more option lines.
  "Regular expression to match the `head' of a ReST code block.")

(defvar pm-ReST-retriever-regexp
  (concat "^[^ ]*\\(?: *\.\. +" pm-ReST-code-regexp ":: \\(.+\\)\\)$")
  "Regular expression to retrieve the mode of the code block.")

(pm-create-indented-block-matchers "ReST" pm-ReST-head-regexp)

(defcustom pm-inner/ReST-code
  (pm-hbtchunkmode-auto "ReST"
                        :head-reg #'pm-ReST-head-matcher
                        :tail-reg #'pm-ReST-tail-matcher
                        :head-mode 'host
                        :tail-mode 'host
                        :retriever-regexp pm-ReST-retriever-regexp
                        :font-lock-narrow t)
  "Restructured Text inner code block mode."
  :group 'innermodes
  :type 'object)


(defcustom pm-poly/ReST
  (pm-polymode-multi-auto "ReST"
                          :hostmode 'pm-host/ReST
                          :auto-innermode 'pm-inner/ReST-code)
  "Restructured Text typical `polymode' configuration."
  :group 'polymodes
  :type 'object)


;;;###autoload (autoload #'poly-rest-mode "poly-rest-mode")
(define-polymode poly-rest-mode pm-poly/ReST)


(defun poly-rest-fixes ()
  "Fix various minor issues that can occur in the poly-ReST-mode."
  (remove-hook 'prog-mode-hook   #'whitespace-mode)
  (remove-hook 'python-mode-hook #'mmm-mode))

(provide 'poly-rest-mode)

;;; poly-rest-mode.el ends here

Now, it is likely that I've missed some detail where this doesn't work perfectly, I'm simply not familiar enough with ReST to tell.

It does seem to work fairly well on a rather complete example:

poly-rest-mode in action

Edit

The concerns mentioned in the comments regarding nested indentations have been fixed in the above code snippet.

  • Wow, this is super cool. I actually started talking to the polymode dev about this a bit too -- see this bug. The problem is ReST boundaries seem a bit too complicated to capture with a simple single-line regexp -- in particular you have to handle "options" on the code block, and to recognize the tail you need to take into account the indentation of the head. The github issue has more details -- maybe we should take the discussion over there? – Nathaniel J. Smith Oct 14 '16 at 18:38
  • Ah, suspected that might be an issue. You're right, let us see if we can solve this with the author of polymode instead. – Xaldew Oct 22 '16 at 8:43

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.