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