Initial Emacs mode from bpt
[hcoop/domtool2.git] / elisp / domtool-mode.el
1 ; Created by Brian Templeton (bpt@hcoop.net)
2 ; Extended by Adam Chlipala (adamc@hcoop.net)
3
4 (eval-when-compile (require 'cl))
5
6 (defvar domtool-indent 2)
7
8 (defvar domtool-mode-syntax-table
9 (let ((table (make-syntax-table)))
10 (loop for i from ?a to ?z
11 do (modify-syntax-entry i "w" table))
12 (loop for i from ?A to ?Z
13 do (modify-syntax-entry i "w" table))
14 (loop for i from ?0 to ?9
15 do (modify-syntax-entry i "w" table))
16 (mapc
17 (lambda (pair)
18 (loop for ch across (if (stringp (car pair))
19 (car pair)
20 (string (car pair)))
21 do (modify-syntax-entry ch (cadr pair) table)))
22 '((" \t\n\14" " ") ; \14 is ^L
23 (?_ "_")
24 (?* ". 23n")
25 (?\( "()1")
26 (?\) ")(4")
27 (?\" "\"")
28 (?\\ "\\")
29 (?\[ "(]")
30 (?\] ")[")
31 ;; We identify single-line comments using
32 ;; font-lock-syntactic-keywords, because it's easier to
33 ;; recognize documentation comments using the syntax table.
34 (?\{ "(}12b")
35 (?\} "){34b")
36 ("->=<,:;^!&" ".") ; -> => <- = , : ; ^ ! &
37 ))
38 table))
39
40 (defvar domtool-font-lock-keywords
41 `(,(concat
42 "\\_<"
43 (regexp-opt '("let" "in" "begin" "end" "with" "where" "extern" "type"
44 "val" "context" "Root"
45 ;; Actions
46 "vhost" "location" "directory" "domain" "dom"
47 "webAt" "web")
48 t)
49 "\\_>")
50 ("type[ \t]+\\(\\(\\sw\\|\\s_\\)+\\)" 1 font-lock-type-face)
51 ("val[ \t]+\\(\\(\\sw\\|\\s_\\)+\\)" 1 font-lock-variable-name-face)))
52
53 (defvar domtool-font-lock-syntactic-keywords
54 '(("\\(#\\).*\\(\n\\|\\'\\)"
55 (1 "!")
56 (2 "!"))))
57
58 (defun domtool-font-lock-syntactic-face-function (state)
59 "Major mode for editing Domtool files."
60 (cond ((nth 3 state) font-lock-string-face)
61 ((eq (nth 7 state) t) font-lock-doc-face)
62 (t font-lock-comment-face)))
63
64 (define-derived-mode domtool-mode fundamental-mode "Domtool"
65 ;; For some reason, whatever twiddling `define-derived-mode' does
66 ;; with the syntax table breaks recognition of (*...*) comments. So
67 ;; we need to tell d-d-m to leave our syntax table alone.
68 :syntax-table domtool-mode-syntax-table
69 (set (make-local-variable 'indent-line-function) 'domtool-indent-line)
70 (set (make-local-variable 'font-lock-defaults)
71 '(domtool-font-lock-keywords
72 nil nil nil nil
73 (font-lock-syntactic-keywords
74 . domtool-font-lock-syntactic-keywords)
75 (font-lock-syntactic-face-function
76 . domtool-font-lock-syntactic-face-function))))
77
78 (defun domtool-indent-line ()
79 (let ((savep (> (current-column) (current-indentation)))
80 (indent (domtool-calculate-indent)))
81 (cond
82 ((eq indent 'noindent) indent)
83 (savep (save-excursion (indent-line-to indent)))
84 (t (indent-line-to indent)))))
85
86 (defun domtool-calculate-indent ()
87 (save-excursion
88 (back-to-indentation)
89 (multiple-value-bind (previous-keyword base-indent)
90 (save-excursion
91 (if (re-search-backward "\\_<\\(with\\|where\\|end\\)\\_>"
92 nil t)
93 (values (match-string 0) (current-indentation))
94 (values nil 0)))
95 (let ((state (syntax-ppss)))
96 (cond
97 ((nth 3 state)
98 'noindent)
99 ((nth 4 state)
100 (domtool-calculate-comment-indent state))
101 ((and (looking-at "\\_<end\\_>")
102 (string= previous-keyword "end"))
103 (- base-indent domtool-indent))
104 ((looking-at "\\_<\\(with\\|end\\)\\_>")
105 base-indent)
106 ((not previous-keyword)
107 base-indent)
108 ((string= previous-keyword "end")
109 base-indent)
110 (t
111 (+ base-indent domtool-indent)))))))
112
113 (defun domtool-calculate-comment-indent (state)
114 (ecase (nth 7 state)
115 ((t) 'noindent)
116 ((syntax-table) 'noindent) ; can't happen
117 ((nil) (let ((start (nth 8 state))
118 (depth 0))
119 (while (> (point) start)
120 (re-search-backward "(\\*\\|\\*)" start t)
121 (if (looking-at "(\\*")
122 (incf depth)
123 (decf depth)))
124 (+ (current-indentation) depth)))))