ad34bdfe |
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)) |