First attempt at an Emacs mode that syntax-highlights Halibut markup.
authorsimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 9 Aug 2011 18:34:25 +0000 (18:34 +0000)
committersimon <simon@cda61777-01e9-0310-a592-d414129be87e>
Tue, 9 Aug 2011 18:34:25 +0000 (18:34 +0000)
git-svn-id: svn://svn.tartarus.org/sgt/halibut@9259 cda61777-01e9-0310-a592-d414129be87e

misc/halibut.el [new file with mode: 0644]

diff --git a/misc/halibut.el b/misc/halibut.el
new file mode 100644 (file)
index 0000000..b5d6190
--- /dev/null
@@ -0,0 +1,113 @@
+;; Halibut mode for emacs.
+;;
+;; Tested on GNU emacs 23.2.1.
+
+(defun halibut-mode-font-lock-extend-region ()
+  (save-excursion
+    (let (new-beg new-end)
+      (goto-char font-lock-beg)
+      (if (re-search-backward "\n[ \t]*\n" nil 1)
+          (goto-char (match-end 0)))
+      (setq new-beg (point))
+      (goto-char font-lock-end)
+      (re-search-forward "\n[ \t]*\n" nil 1)
+      (setq new-end (point))
+      (if (and (= new-beg font-lock-beg) (= new-end font-lock-end))
+          nil ;; did nothing
+        (setq font-lock-beg new-beg)
+        (setq font-lock-end new-end)
+        t))))
+
+(defun halibut-mode-match-braced-comment (limit)
+  ;; Look for a braced Halibut comment, which starts with \#{ and ends
+  ;; with } but has to skip over matching unescaped braces in between.
+  (if (not (search-forward "\\#{" limit t))
+      nil ;; didn't find the introducer string
+    (let ((start (match-beginning 0))
+          (depth 1))
+      (goto-char (match-end 0))
+      ;; Repeatedly find the next unescaped brace and adjust depth.
+      (while (and (> depth 0)
+                  (looking-at "\\([^\\\\{}]\\|\\\\.\\)*\\([{}]\\)")
+                  (< (match-end 2) limit))
+        (setq depth (if (string= (match-string 2) "{") (1+ depth) (1- depth)))
+        (goto-char (match-end 2)))
+      ;; If depth hit zero, we've stopped just after the closing
+      ;; brace. If it didn't, we should stop at limit.
+      (if (> depth 0) (goto-char limit))
+      ;; Now the string between 'start' and point is our match.
+      (set-match-data (list start (point)))
+      t)))
+
+(defun halibut-mode-match-paragraph-comment (limit)
+  ;; Look for a whole-paragraph Halibut comment, which starts with \#
+  ;; and then something other than an open brace, and ends at the next
+  ;; paragraph break.
+  (catch 'found-one
+    (while (search-forward "\\#" limit t)
+      (let ((start (match-beginning 0)))
+        ;; For each \# we find, check to see if it's eligible.
+        (when (and
+               ;; It must not be followed by {.
+               (not (looking-at "\\\\#{"))
+               ;; It must be the first thing in its paragraph (either
+               ;; because the chunk of whitespace immediately preceding it
+               ;; contains more than one \n, or because that chunk of
+               ;; whitespace terminates at the beginning of the file).
+               (let ((this-line (line-number-at-pos)))
+                 (save-excursion
+                   (goto-char start)
+                   (skip-chars-backward "\n\t ")
+                   (or (= (point) (point-min))
+                       (< (line-number-at-pos) (1- this-line))))))
+          ;; If those conditions are satisfied, we've found an
+          ;; eligible \#. Search forward for the next paragraph end.
+          (if (re-search-forward "\n[ \t]*\n" nil 1)
+              (goto-char (match-beginning 0)))
+          ;; Now the string between 'start' and point is our match.
+          (set-match-data (list start (point)))
+          ;; Terminate the while loop.
+          (throw 'found-one t))))
+    ;; The loop terminated without finding anything.
+    nil))
+
+(defun halibut-mode-match-code-or-emphasis-line (char limit)
+  ;; Look for a Halibut code line (starting with "\c " or containing
+  ;; only "\c"). Either right here...
+  (if (and (= (current-column) 0) (looking-at (concat "\\\\" char "[ \n]")))
+      (let ((start (match-beginning 0)))
+        (end-of-line)
+        (set-match-data (list start (min limit (point))))
+        t)
+    ;; ... or further down...
+    (if (re-search-forward (concat "\n\\\\" char "[ \n]") limit t)
+        (let ((start (1+ (match-beginning 0))))
+          (goto-char start)
+          (end-of-line)
+          (set-match-data (list start (min limit (point))))
+          t)
+      ;; and if neither of those, we didn't find one.
+      nil)))
+
+(defun halibut-mode-match-code-line (limit)
+  (halibut-mode-match-code-or-emphasis-line "c" limit))
+(defun halibut-mode-match-emphasis-line (limit)
+  (halibut-mode-match-code-or-emphasis-line "e" limit))
+
+(defconst halibut-font-lock-keywords
+  '((halibut-mode-match-braced-comment . font-lock-comment-face)
+    (halibut-mode-match-paragraph-comment . font-lock-comment-face)
+    (halibut-mode-match-code-line . font-lock-string-face)
+    (halibut-mode-match-emphasis-line . font-lock-preprocessor-face)
+    ("\\\\\\([-{}_\\\\]\\|u[0-9a-fA-F]*\\|[A-Za-tv-z][0-9A-Za-z]*\\)" .
+     font-lock-keyword-face))
+  "Syntax highlighting for Halibut mode.")
+
+;;;###autoload
+(define-derived-mode halibut-mode fundamental-mode "Halibut"
+  "Major mode for editing Halibut documentation markup."
+  (setq font-lock-defaults '(halibut-font-lock-keywords t))
+  (add-hook 'font-lock-extend-region-functions 'halibut-mode-font-lock-extend-region))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.but\\'" . halibut-mode))