First attempt at an Emacs mode that syntax-highlights Halibut markup.
[sgt/halibut] / misc / halibut.el
1 ;; Halibut mode for emacs.
2 ;;
3 ;; Tested on GNU emacs 23.2.1.
4
5 (defun halibut-mode-font-lock-extend-region ()
6 (save-excursion
7 (let (new-beg new-end)
8 (goto-char font-lock-beg)
9 (if (re-search-backward "\n[ \t]*\n" nil 1)
10 (goto-char (match-end 0)))
11 (setq new-beg (point))
12 (goto-char font-lock-end)
13 (re-search-forward "\n[ \t]*\n" nil 1)
14 (setq new-end (point))
15 (if (and (= new-beg font-lock-beg) (= new-end font-lock-end))
16 nil ;; did nothing
17 (setq font-lock-beg new-beg)
18 (setq font-lock-end new-end)
19 t))))
20
21 (defun halibut-mode-match-braced-comment (limit)
22 ;; Look for a braced Halibut comment, which starts with \#{ and ends
23 ;; with } but has to skip over matching unescaped braces in between.
24 (if (not (search-forward "\\#{" limit t))
25 nil ;; didn't find the introducer string
26 (let ((start (match-beginning 0))
27 (depth 1))
28 (goto-char (match-end 0))
29 ;; Repeatedly find the next unescaped brace and adjust depth.
30 (while (and (> depth 0)
31 (looking-at "\\([^\\\\{}]\\|\\\\.\\)*\\([{}]\\)")
32 (< (match-end 2) limit))
33 (setq depth (if (string= (match-string 2) "{") (1+ depth) (1- depth)))
34 (goto-char (match-end 2)))
35 ;; If depth hit zero, we've stopped just after the closing
36 ;; brace. If it didn't, we should stop at limit.
37 (if (> depth 0) (goto-char limit))
38 ;; Now the string between 'start' and point is our match.
39 (set-match-data (list start (point)))
40 t)))
41
42 (defun halibut-mode-match-paragraph-comment (limit)
43 ;; Look for a whole-paragraph Halibut comment, which starts with \#
44 ;; and then something other than an open brace, and ends at the next
45 ;; paragraph break.
46 (catch 'found-one
47 (while (search-forward "\\#" limit t)
48 (let ((start (match-beginning 0)))
49 ;; For each \# we find, check to see if it's eligible.
50 (when (and
51 ;; It must not be followed by {.
52 (not (looking-at "\\\\#{"))
53 ;; It must be the first thing in its paragraph (either
54 ;; because the chunk of whitespace immediately preceding it
55 ;; contains more than one \n, or because that chunk of
56 ;; whitespace terminates at the beginning of the file).
57 (let ((this-line (line-number-at-pos)))
58 (save-excursion
59 (goto-char start)
60 (skip-chars-backward "\n\t ")
61 (or (= (point) (point-min))
62 (< (line-number-at-pos) (1- this-line))))))
63 ;; If those conditions are satisfied, we've found an
64 ;; eligible \#. Search forward for the next paragraph end.
65 (if (re-search-forward "\n[ \t]*\n" nil 1)
66 (goto-char (match-beginning 0)))
67 ;; Now the string between 'start' and point is our match.
68 (set-match-data (list start (point)))
69 ;; Terminate the while loop.
70 (throw 'found-one t))))
71 ;; The loop terminated without finding anything.
72 nil))
73
74 (defun halibut-mode-match-code-or-emphasis-line (char limit)
75 ;; Look for a Halibut code line (starting with "\c " or containing
76 ;; only "\c"). Either right here...
77 (if (and (= (current-column) 0) (looking-at (concat "\\\\" char "[ \n]")))
78 (let ((start (match-beginning 0)))
79 (end-of-line)
80 (set-match-data (list start (min limit (point))))
81 t)
82 ;; ... or further down...
83 (if (re-search-forward (concat "\n\\\\" char "[ \n]") limit t)
84 (let ((start (1+ (match-beginning 0))))
85 (goto-char start)
86 (end-of-line)
87 (set-match-data (list start (min limit (point))))
88 t)
89 ;; and if neither of those, we didn't find one.
90 nil)))
91
92 (defun halibut-mode-match-code-line (limit)
93 (halibut-mode-match-code-or-emphasis-line "c" limit))
94 (defun halibut-mode-match-emphasis-line (limit)
95 (halibut-mode-match-code-or-emphasis-line "e" limit))
96
97 (defconst halibut-font-lock-keywords
98 '((halibut-mode-match-braced-comment . font-lock-comment-face)
99 (halibut-mode-match-paragraph-comment . font-lock-comment-face)
100 (halibut-mode-match-code-line . font-lock-string-face)
101 (halibut-mode-match-emphasis-line . font-lock-preprocessor-face)
102 ("\\\\\\([-{}_\\\\]\\|u[0-9a-fA-F]*\\|[A-Za-tv-z][0-9A-Za-z]*\\)" .
103 font-lock-keyword-face))
104 "Syntax highlighting for Halibut mode.")
105
106 ;;;###autoload
107 (define-derived-mode halibut-mode fundamental-mode "Halibut"
108 "Major mode for editing Halibut documentation markup."
109 (setq font-lock-defaults '(halibut-font-lock-keywords t))
110 (add-hook 'font-lock-extend-region-functions 'halibut-mode-font-lock-extend-region))
111
112 ;;;###autoload
113 (add-to-list 'auto-mode-alist '("\\.but\\'" . halibut-mode))