Customizing Emacs Muse – Part 1
Emacs has some extremely nice extension mechanisms. The most important of these are emacs hooks, but there is also advice and you can even redefine any functions that aren’t behaving exactly as you want.
There comes a time in every emacs user’s life where they are using a great extension, but the hook they want to use isn’t there [where it should be]. The Emacs Muse HTML publisher has a function called
muse-html-src-tag that adds syntax highlighting to source code. The hook that I need was missing from the function.
The end of that function looks like this, only now I have added
muse-html-after-htmlize-hook. The reason the hook needs to be there, is that after the function returns, the narrowing has gone and we don’t know which area to operate on any more.
(defvar muse-html-after-htmlize-hook nil) (save-restriction (narrow-to-region (point) (point)) (insert htmltext) (goto-char (point-min)) (re-search-forward "<pre\\([^>]*\\)>\n?" nil t) (replace-match "<pre>") (goto-char (point-max)) (run-hooks 'muse-html-after-htmlize-hook) (muse-publish-mark-read-only (point-min) (point-max))))))
Okay, so back to Squidoo – why do I need to generate custom HTML? Well, pretty simple – they’ve disabled a bunch of the tags. <pre>, <div> and <span> have gone. Well, <div> and <span> I can pretty much replace with <p> and <b>, kinda, but I’m pretty sad to lose <pre>. More on that shortly.
First, some helper functions. I need to run multiple regex replacements on the region and also it will be useful to have a function that repeats strings. I couldn’t find a built-in function quickly and it took 20 seconds to write (the possibly inefficient)
(defun regex-replace (regex string) (goto-char (point-min)) (while (re-search-forward regex nil t) (replace-match string))) (defun string-repeat (str n) (let ((retval "")) (dotimes (i n) (setq retval (concat retval str))) retval))
sq-code-prefix is the prefix for all source code regions;
regex-multi-space matches multiple spaces at the beginning of a line.
(defconst sq-code-prefix (concat "<p style=\"font-family: monospace; font-size: 115%; " "border: 1px solid #bbb; " "background: #eee; overflow: auto; " "margin: 0; padding: 5px;\">")) (defconst cr (string ?\n)) (defconst regex-multi-space (concat cr " +"))
The hook function for source code regions needs to do a couple of things – it needs to replace the span and pre tags. It also needs to put non-breaking-spaces at the start of any lines with spaces as that is how a pre would behave. And finally, to reduce the amount of surrounding space it needs to remove the extra lines before and after the region.
(defun squidoo-htmlize () (regex-replace "<span style=\"" "<b style=\"font-weight: normal; ") (regex-replace "<pre>" sq-code-prefix) (regex-replace "</span>" "</b>") (regex-replace "</pre>" "</p>") (goto-char (point-min)) (while (re-search-forward regex-multi-space nil t) (let ((str (buffer-substring-no-properties (match-beginning 0) (match-end 0)))) (replace-match (concat cr (string-repeat " " (1- (length str))))))) (goto-char (point-min)) (beginning-of-line 2) (delete-indentation) (delete-char 1) (goto-char (point-min)) (insert "XXDELETE") (goto-char (point-max)) (beginning-of-line 1) (delete-indentation) (delete-char 1))
We also need another hook to clean up duplicate closing tags and style the <code> tag. Actually, I could just redefine one of the variables to fix the code tag, but where would the fun in that be?
(defun squidoo-fix-para () (regex-replace "<p[^>]*>XXDELETE" "") (regex-replace "<code>" "<b style=\"font-weight: normal; font-size:108%; background: #eee; padding: 3px;\">) (regex-replace "</code>" "</b>") (regex-replace "</p></p>" "</p>"))
Finally, add the two functions to the hooks.
(add-hook 'muse-html-after-htmlize-hook 'squidoo-htmlize) (add-hook 'muse-after-publish-hook 'squidoo-fix-para)
Whew! After all that the result is: my squidoo lens on how to create a new emacs command. And why do I want to make squidoo lenses? That will have to wait for another post.