/ emacs.d / mustache-mode.el
mustache-mode.el
  1  ;;; tpl-mode.el -- a major mode for editing Google CTemplate files.
  2  ;;; By Tony Gentilcore, July 2006
  3  ;;;
  4  ;;; Very minor, backwards compatible changes added for Mustache compatibility
  5  ;;; by Chris Wanstrath, October 2009
  6  ;;;
  7  ;;; TO USE:
  8  ;;; 1) Copy this file somewhere you in emacs load-path.  To see what
  9  ;;;    your load-path is, run inside emacs: C-h v load-path<RET>
 10  ;;; 2) Add the following two lines to your .emacs file:
 11  ;;;    (setq auto-mode-alist (cons '("\\.tpl$" . tpl-mode) auto-mode-alist))
 12  ;;;    (autoload 'tpl-mode "tpl-mode" "Major mode for editing CTemplate files." t)
 13  ;;; 3) Optionally (but recommended), add this third line as well:
 14  ;;;    (add-hook 'tpl-mode-hook '(lambda () (font-lock-mode 1)))
 15  ;;; ---
 16  ;;;
 17  ;;; While the Mustache language can be used for any types of text,
 18  ;;; this mode is intended for using Mustache to write HTML.
 19  ;;;
 20  ;;; The indentation still has minor bugs due to the fact that
 21  ;;; templates do not require valid HTML.
 22  ;;;
 23  ;;; It would be nice to be able to highlight attributes of HTML tags,
 24  ;;; however this is difficult due to the presence of CTemplate symbols
 25  ;;; embedded within attributes.
 26  
 27  (eval-when-compile
 28    (require 'font-lock))
 29  
 30  (defgroup tpl-mode nil
 31    "Major mode for editing Google CTemplate and Mustache files"
 32    :group 'languages)
 33  
 34  (defvar tpl-mode-version "1.1"
 35    "Version of `tpl-mode.el'.")
 36  
 37  (defvar tpl-mode-abbrev-table nil
 38    "Abbrev table for use in tpl-mode buffers.")
 39  
 40  (define-abbrev-table 'tpl-mode-abbrev-table ())
 41  
 42  (defcustom tpl-mode-hook nil
 43    "*Hook that runs upon entering tpl-mode."
 44    :type 'hook)
 45  
 46  (defvar tpl-mode-map nil
 47    "Keymap for tpl-mode major mode")
 48  
 49  (if tpl-mode-map
 50      nil
 51    (setq tpl-mode-map (make-sparse-keymap)))
 52  
 53  (define-key tpl-mode-map "\t" 'tpl-indent-command)
 54  (define-key tpl-mode-map "\C-m" 'reindent-then-newline-and-indent)
 55  (define-key tpl-mode-map "\C-ct" 'tpl-insert-tag)
 56  (define-key tpl-mode-map "\C-cv" 'tpl-insert-variable)
 57  (define-key tpl-mode-map "\C-cs" 'tpl-insert-section)
 58  
 59  
 60  (defvar tpl-mode-syntax-table nil
 61    "Syntax table in use in tpl-mode buffers.")
 62  
 63  ;; Syntax table.
 64  (if tpl-mode-syntax-table
 65      nil
 66    (setq tpl-mode-syntax-table (make-syntax-table text-mode-syntax-table))
 67    (modify-syntax-entry ?<  "(>  " tpl-mode-syntax-table)
 68    (modify-syntax-entry ?>  ")<  " tpl-mode-syntax-table)
 69    (modify-syntax-entry ?\" ".   " tpl-mode-syntax-table)
 70    (modify-syntax-entry ?\\ ".   " tpl-mode-syntax-table)
 71    (modify-syntax-entry ?'  "w   " tpl-mode-syntax-table))
 72  
 73  (defvar tpl-basic-offset 2
 74    "The basic indentation offset.")
 75  
 76  ;; Constant regular expressions to identify template elements.
 77  (defconst tpl-mode-tpl-token "[a-zA-Z_][a-zA-Z0-9_:=\?!-]*?")
 78  (defconst tpl-mode-section (concat "\\({{[#^/]\s*"
 79                                     tpl-mode-tpl-token
 80                                     "\s*}}\\)"))
 81  (defconst tpl-mode-open-section (concat "\\({{#\s*"
 82                                          tpl-mode-tpl-token
 83                                          "\s*}}\\)"))
 84  (defconst tpl-mode-close-section (concat "{{/\\(\s*"
 85                                           tpl-mode-tpl-token
 86                                           "\s*\\)}}"))
 87  ;; TODO(tonyg) Figure out a way to support multiline comments.
 88  (defconst tpl-mode-comment "\\({{!.*?}}\\)")
 89  (defconst tpl-mode-include (concat "\\({{[><]\s*"
 90                                     tpl-mode-tpl-token
 91                                     "\s*}}\\)"))
 92  (defconst tpl-mode-variable (concat "\\({{\s*"
 93                                      tpl-mode-tpl-token
 94                                      "\s*}}\\)"))
 95  (defconst tpl-mode-builtins
 96    (concat
 97     "\\({{\\<\s*"
 98     (regexp-opt
 99      '("BI_NEWLINE" "BI_SPACE")
100      t)
101     "\s*\\>}}\\)"))
102  (defconst tpl-mode-close-section-at-start (concat "^[ \t]*?"
103                                                    tpl-mode-close-section))
104  
105  ;; Constant regular expressions to identify html tags.
106  ;; Taken from HTML 4.01 / XHTML 1.0 Reference found at:
107  ;; http://www.w3schools.com/tags/default.asp.
108  (defconst tpl-mode-html-constant "\\(&#?[a-z0-9]\\{2,5\\};\\)")
109  (defconst tpl-mode-pair-tag
110    (concat
111     "\\<"
112     (regexp-opt
113      '("a" "abbr" "acronym" "address" "applet" "area" "b" "bdo"
114        "big" "blockquote" "body" "button" "caption" "center" "cite"
115        "code" "col" "colgroup" "dd" "del" "dfn" "dif" "div" "dl"
116        "dt" "em" "fieldset" "font" "form" "frame" "frameset" "h1"
117        "h2" "h3" "h4" "h5" "h6" "head" "html" "i" "iframe" "ins"
118        "kbd" "label" "legend" "li" "link" "map" "menu" "noframes"
119        "noscript" "object" "ol" "optgroup" "option" "p" "pre" "q"
120        "s" "samp" "script" "select" "small" "span" "strike"
121        "strong" "style" "sub" "sup" "table" "tbody" "td" "textarea"
122        "tfoot" "th" "thead" "title" "tr" "tt" "u" "ul" "var")
123      t)
124     "\\>"))
125  (defconst tpl-mode-standalone-tag
126    (concat
127     "\\<"
128     (regexp-opt
129      '("base" "br" "hr" "img" "input" "meta" "param")
130      t)
131     "\\>"))
132  (defconst tpl-mode-open-tag (concat "<\\("
133                                      tpl-mode-pair-tag
134                                      "\\)"))
135  (defconst tpl-mode-close-tag (concat "</\\("
136                                       tpl-mode-pair-tag
137                                       "\\)>"))
138  (defconst tpl-mode-close-tag-at-start (concat "^[ \t]*?"
139                                                tpl-mode-close-tag))
140  
141  (defconst tpl-mode-blank-line "^[ \t]*?$")
142  (defconst tpl-mode-dangling-open (concat "\\("
143                                           tpl-mode-open-section
144                                           "\\)\\|\\("
145                                           tpl-mode-open-tag
146                                           "\\)[^/]*$"))
147  
148  (defun tpl-indent-command ()
149    "Command for indenting text. Just calls tpl-indent."
150    (interactive)
151    (tpl-indent))
152  
153  (defun tpl-insert-tag (tag)
154    "Inserts an HTML tag."
155    (interactive "sTag: ")
156    (tpl-indent)
157    (insert (concat "<" tag ">"))
158    (insert "\n\n")
159    (insert (concat "</" tag ">"))
160    (tpl-indent)
161    (forward-line -1)
162    (tpl-indent))
163  
164  (defun tpl-insert-variable (variable)
165    "Inserts a tpl variable."
166    (interactive "sVariable: ")
167    (insert (concat "{{" variable "}}")))
168  
169  (defun tpl-insert-section (section)
170    "Inserts a tpl section."
171    (interactive "sSection: ")
172    (tpl-indent)
173    (insert (concat "{{#" section "}}\n"))
174    (insert "\n")
175    (insert (concat "{{/" section "}}"))
176    (tpl-indent)
177    (forward-line -1)
178    (tpl-indent))
179  
180  ;; Function to control indenting.
181  (defun tpl-indent ()
182    "Indent current line"
183    ;; Set the point to beginning of line.
184    (beginning-of-line)
185    ;; If we are at the beginning of the file, indent to 0.
186    (if (bobp)
187        (indent-line-to 0)
188      (let ((tag-stack 1) (close-tag "") (cur-indent 0) (old-pnt (point-marker))
189            (close-at-start) (open-token) (dangling-open))
190        (progn
191          ;; Determine if this is a template line or an html line.
192          (if (looking-at "^[ \t]*?{{")
193              (setq close-at-start tpl-mode-close-section-at-start
194                    open-token "{{#")
195            (setq close-at-start tpl-mode-close-tag-at-start
196                  open-token "<"))
197          ;; If there is a closing tag at the start of the line, search back
198          ;; for its opener and indent to that level.
199          (if (looking-at close-at-start)
200              (progn
201                (save-excursion
202                  (setq close-tag (match-string 1))
203                  ;; Keep searching for a match for the close tag until
204                  ;; the tag-stack is 0.
205                  (while (and (not (bobp))
206                              (> tag-stack 0)
207                              (re-search-backward (concat open-token
208                                                          "\\(/?\\)"
209                                                          close-tag) nil t))
210                    (if (string-equal (match-string 1) "/")
211                        ;; We found another close tag, so increment tag-stack.
212                        (setq tag-stack (+ tag-stack 1))
213                      ;; We found an open tag, so decrement tag-stack.
214                      (setq tag-stack (- tag-stack 1)))
215                    (setq cur-indent (current-indentation))))
216                (if (> tag-stack 0)
217                    (save-excursion
218                      (forward-line -1)
219                      (setq cur-indent (current-indentation)))))
220            ;; This was not a closing tag, so we check if the previous line
221            ;; was an opening tag.
222            (save-excursion
223              ;; Keep moving back until we find a line that is not blank
224              (while (progn
225                       (forward-line -1)
226                       (and (not (bobp)) (looking-at tpl-mode-blank-line))))
227              (setq cur-indent (current-indentation))
228              (if (re-search-forward tpl-mode-dangling-open old-pnt t)
229                  (setq cur-indent (+ cur-indent tpl-basic-offset)))))
230          ;; Finally, we execute the actual indentation.
231          (if (> cur-indent 0)
232              (indent-line-to cur-indent)
233            (indent-line-to 0))))))
234  
235  ;; controls highlighting
236  (defconst tpl-mode-font-lock-keywords
237    (list
238     (list tpl-mode-section
239           '(1 font-lock-keyword-face))
240     (list tpl-mode-comment
241           '(1 font-lock-comment-face))
242     (list tpl-mode-include
243           '(1 font-lock-function-name-face))
244     (list tpl-mode-builtins
245           '(1 font-lock-variable-name-face))
246     (list tpl-mode-variable
247           '(1 font-lock-reference-face))
248     (list (concat "</?\\(" tpl-mode-pair-tag "\\)")
249           '(1 font-lock-function-name-face))
250     (list (concat "<\\(" tpl-mode-standalone-tag "\\)")
251           '(1 font-lock-function-name-face))
252     (list tpl-mode-html-constant
253           '(1 font-lock-variable-name-face))))
254  
255  (put 'tpl-mode 'font-lock-defaults '(tpl-font-lock-keywords nil t))
256  
257  (defun tpl-mode ()
258    "Major mode for editing Google CTemplate file."
259    (interactive)
260    (kill-all-local-variables)
261    (use-local-map tpl-mode-map)
262    (setq major-mode 'tpl-mode)
263    (setq mode-name "tpl-mode")
264    (setq local-abbrev-table tpl-mode-abbrev-table)
265    (setq indent-tabs-mode nil)
266    (set-syntax-table tpl-mode-syntax-table)
267    ;; show trailing whitespace, but only when the user can fix it
268    (setq show-trailing-whitespace (not buffer-read-only))
269    (make-local-variable 'indent-line-function)
270    (setq indent-line-function 'tpl-indent)
271    (setq font-lock-defaults '(tpl-mode-font-lock-keywords))
272    (run-hooks 'tpl-mode-hook))
273  
274  ;; Automatically load tpl-mode for .mustache files.
275  (add-to-list 'auto-mode-alist '("\\.mustache$" . tpl-mode))
276  (add-hook 'tpl-mode-hook '(lambda () (font-lock-mode 1)))
277  
278  (provide 'mustache-mode)