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