├── README.md └── mustache-mode.el /README.md: -------------------------------------------------------------------------------- 1 | # Emacs Major Mode for Mustache 2 | 3 | Based on Google's tpl-mode for ctemplates, this major mode adds 4 | support for Mustache's more lenient tag values and includes a few 5 | commands for your editing pleasure. 6 | 7 | ## Installing 8 | 9 | In your shell: 10 | 11 | cd ~/.emacs.d/vendor 12 | curl --location -O https://raw.github.com/mustache/emacs/master/mustache-mode.el 13 | 14 | In your Emacs config: 15 | 16 | (add-to-list 'load-path "~/.emacs.d/vendor") 17 | (require 'mustache-mode) 18 | -------------------------------------------------------------------------------- /mustache-mode.el: -------------------------------------------------------------------------------- 1 | ;;; mustache-mode.el --- A major mode for editing Mustache files. 2 | 3 | ;; Author: Tony Gentilcore 4 | ;; Chris Wanstrath 5 | ;; Daniel Hackney 6 | 7 | ;; Version: 1.3 8 | 9 | ;; This file is not part of Emacs 10 | 11 | ;; This file is free software; you can redistribute it and/or modify 12 | ;; it under the terms of the GNU General Public License as published by 13 | ;; the Free Software Foundation; either version 2, or (at your option) 14 | ;; any later version. 15 | 16 | ;; This file is distributed in the hope that it will be useful, 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | ;; GNU General Public License for more details. 20 | 21 | ;; You should have received a copy of the GNU General Public License 22 | ;; along with GNU Emacs; see the file COPYING. If not, write to 23 | ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | ;; Boston, MA 02111-1307, USA. 25 | 26 | ;;; Commentary: 27 | 28 | ;; 1) Copy this file somewhere in your Emacs `load-path'. To see what 29 | ;; your `load-path' is, run inside emacs: C-h v load-path 30 | ;; 31 | ;; 2) Add the following to your .emacs file: 32 | ;; 33 | ;; (require 'mustache-mode) 34 | 35 | ;; While the Mustache language can be used for any types of text, 36 | ;; this mode is intended for using Mustache to write HTML. 37 | 38 | ;;; Known Bugs: 39 | 40 | ;; The indentation still has minor bugs due to the fact that 41 | ;; templates do not require valid HTML. 42 | 43 | ;; It would be nice to be able to highlight attributes of HTML tags, 44 | ;; however this is difficult due to the presence of CTemplate symbols 45 | ;; embedded within attributes. 46 | 47 | (eval-when-compile 48 | (require 'font-lock)) 49 | 50 | (defvar mustache-mode-version "1.3" 51 | "Version of `mustache-mode.el'.") 52 | 53 | (defvar mustache-mode-map 54 | (let ((map (make-sparse-keymap))) 55 | (define-key map "\C-m" 'reindent-then-newline-and-indent) 56 | (define-key map "\C-ct" 'mustache-insert-tag) 57 | (define-key map "\C-cv" 'mustache-insert-variable) 58 | (define-key map "\C-cs" 'mustache-insert-section) 59 | map) 60 | "Keymap for mustache-mode major mode") 61 | 62 | (defvar mustache-mode-syntax-table 63 | (let ((st (make-syntax-table))) 64 | (modify-syntax-entry ?< "(> " st) 65 | (modify-syntax-entry ?> ")< " st) 66 | (modify-syntax-entry ?\" ". " st) 67 | (modify-syntax-entry ?\\ ". " st) 68 | (modify-syntax-entry ?' "w " st) 69 | st) 70 | "Syntax table in use in mustache-mode buffers.") 71 | 72 | (defvar mustache-basic-offset 2 73 | "The basic indentation offset.") 74 | 75 | ;; Constant regular expressions to identify template elements. 76 | (defconst mustache-mode-mustache-token "[a-zA-Z_.][a-zA-Z0-9_:=\?!.-]*?") 77 | (defconst mustache-mode-section (concat "\\({{[#^/]\s*" 78 | mustache-mode-mustache-token 79 | "\s*}}\\)")) 80 | (defconst mustache-mode-open-section (concat "\\({{[#^]\s*" 81 | mustache-mode-mustache-token 82 | "\s*}}\\)")) 83 | (defconst mustache-mode-close-section (concat "{{/\\(\s*" 84 | mustache-mode-mustache-token 85 | "\s*\\)}}")) 86 | (defconst mustache-mode-comment "\\({{!\\(?:.\\|\n\\)*?}}\\)") 87 | (defconst mustache-mode-include (concat "\\({{[><]\s*" 88 | mustache-mode-mustache-token 89 | "\s*}}\\)")) 90 | (defconst mustache-mode-variable (concat "\\({{\s*" 91 | mustache-mode-mustache-token 92 | "\s*}}\\)")) 93 | (defconst mustache-mode-variable (concat "\\({{{?\s*" 94 | mustache-mode-mustache-token 95 | "\s*}}}?\\)")) 96 | (defconst mustache-mode-builtins 97 | (concat 98 | "\\({{\\<\s*" 99 | (regexp-opt 100 | '("BI_NEWLINE" "BI_SPACE") 101 | t) 102 | "\s*\\>}}\\)")) 103 | (defconst mustache-mode-close-section-at-start (concat "^[ \t]*?" 104 | mustache-mode-close-section)) 105 | 106 | ;; Constant regular expressions to identify html tags. 107 | ;; Taken from HTML 4.01 / XHTML 1.0 Reference found at: 108 | ;; http://www.w3schools.com/tags/default.asp. 109 | (defconst mustache-mode-html-constant "\\(&#?[a-z0-9]\\{2,5\\};\\)") 110 | (defconst mustache-mode-pair-tag 111 | (concat 112 | "\\<" 113 | (regexp-opt 114 | '("a" "abbr" "acronym" "address" "applet" "area" "b" "bdo" 115 | "big" "blockquote" "body" "button" "caption" "center" "cite" 116 | "code" "col" "colgroup" "dd" "del" "dfn" "dif" "div" "dl" 117 | "dt" "em" "fieldset" "font" "form" "frame" "frameset" "h1" 118 | "header" "nav" "footer" "section" 119 | "h2" "h3" "h4" "h5" "h6" "head" "html" "i" "iframe" "ins" 120 | "kbd" "label" "legend" "li" "link" "map" "menu" "noframes" 121 | "noscript" "object" "ol" "optgroup" "option" "p" "pre" "q" 122 | "s" "samp" "script" "select" "small" "span" "strike" 123 | "strong" "style" "sub" "sup" "table" "tbody" "td" "textarea" 124 | "tfoot" "th" "thead" "title" "tr" "tt" "u" "ul" "var") 125 | t) 126 | "\\>")) 127 | (defconst mustache-mode-standalone-tag 128 | (concat 129 | "\\<" 130 | (regexp-opt 131 | '("base" "br" "hr" "img" "input" "meta" "param") 132 | t) 133 | "\\>")) 134 | (defconst mustache-mode-open-tag (concat "<\\(" 135 | mustache-mode-pair-tag 136 | "\\)")) 137 | (defconst mustache-mode-close-tag (concat "")) 140 | (defconst mustache-mode-close-tag-at-start (concat "^[ \t]*?" 141 | mustache-mode-close-tag)) 142 | 143 | (defconst mustache-mode-blank-line "^[ \t]*?$") 144 | (defconst mustache-mode-dangling-open (concat "\\(" 145 | mustache-mode-open-section 146 | "\\)\\|\\(" 147 | mustache-mode-open-tag 148 | "\\)[^/]*$")) 149 | 150 | (defun mustache-insert-tag (tag) 151 | "Inserts an HTML tag." 152 | (interactive "sTag: ") 153 | (mustache-indent) 154 | (insert (concat "<" tag ">")) 155 | (insert "\n\n") 156 | (insert (concat "")) 157 | (mustache-indent) 158 | (forward-line -1) 159 | (mustache-indent)) 160 | 161 | (defun mustache-insert-variable (variable) 162 | "Inserts a tpl variable." 163 | (interactive "sVariable: ") 164 | (insert (concat "{{" variable "}}"))) 165 | 166 | (defun mustache-insert-section (section) 167 | "Inserts a tpl section." 168 | (interactive "sSection: ") 169 | (mustache-indent) 170 | (insert (concat "{{#" section "}}\n")) 171 | (insert "\n") 172 | (insert (concat "{{/" section "}}")) 173 | (mustache-indent) 174 | (forward-line -1) 175 | (mustache-indent)) 176 | 177 | (defun mustache-indent () 178 | "Indent current line" 179 | ;; Set the point to beginning of line. 180 | (beginning-of-line) 181 | ;; If we are at the beginning of the file, indent to 0. 182 | (if (bobp) 183 | (indent-line-to 0) 184 | (let ((tag-stack 1) (close-tag "") (cur-indent 0) (old-pnt (point-marker)) 185 | (close-at-start) (open-token) (dangling-open)) 186 | (progn 187 | ;; Determine if this is a template line or an html line. 188 | (if (looking-at "^[ \t]*?{{") 189 | (setq close-at-start mustache-mode-close-section-at-start 190 | open-token "{{#") 191 | (setq close-at-start mustache-mode-close-tag-at-start 192 | open-token "<")) 193 | ;; If there is a closing tag at the start of the line, search back 194 | ;; for its opener and indent to that level. 195 | (if (looking-at close-at-start) 196 | (progn 197 | (save-excursion 198 | (setq close-tag (match-string 1)) 199 | ;; Keep searching for a match for the close tag until 200 | ;; the tag-stack is 0. 201 | (while (and (not (bobp)) 202 | (> tag-stack 0) 203 | (re-search-backward (concat open-token 204 | "\\(/?\\)" 205 | close-tag) nil t)) 206 | (if (string-equal (match-string 1) "/") 207 | ;; We found another close tag, so increment tag-stack. 208 | (setq tag-stack (+ tag-stack 1)) 209 | ;; We found an open tag, so decrement tag-stack. 210 | (setq tag-stack (- tag-stack 1))) 211 | (setq cur-indent (current-indentation)))) 212 | (if (> tag-stack 0) 213 | (save-excursion 214 | (forward-line -1) 215 | (setq cur-indent (current-indentation))))) 216 | ;; This was not a closing tag, so we check if the previous line 217 | ;; was an opening tag. 218 | (save-excursion 219 | ;; Keep moving back until we find a line that is not blank 220 | (while (progn 221 | (forward-line -1) 222 | (and (not (bobp)) (looking-at mustache-mode-blank-line)))) 223 | (setq cur-indent (current-indentation)) 224 | (if (re-search-forward mustache-mode-dangling-open old-pnt t) 225 | (setq cur-indent (+ cur-indent mustache-basic-offset))))) 226 | ;; Finally, we execute the actual indentation. 227 | (if (> cur-indent 0) 228 | (indent-line-to cur-indent) 229 | (indent-line-to 0)))))) 230 | 231 | (defconst mustache-mode-font-lock-keywords 232 | `((,mustache-mode-section (1 font-lock-keyword-face)) 233 | (,mustache-mode-comment (1 font-lock-comment-face)) 234 | (,mustache-mode-include (1 font-lock-function-name-face)) 235 | (,mustache-mode-builtins (1 font-lock-variable-name-face)) 236 | (,mustache-mode-variable (1 font-lock-reference-face)) 237 | (,(concat "