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)