├── .gitignore ├── README └── jinja2-mode.el /.gitignore: -------------------------------------------------------------------------------- 1 | *.elc 2 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Jinja2 mode for emacs 2 | Copyright (C) 2011 Florian Mounier aka paradoxxxzero 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | 9 | This program is distributed in the hope that it will be useful, 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | GNU General Public License for more details. 13 | 14 | You should have received a copy of the GNU General Public License 15 | along with this program. If not, see . 16 | 17 | Author: Florian Mounier aka paradoxxxzero 18 | Description : 19 | This is an emacs major mode for jinja2 with: 20 | syntax highlighting 21 | sgml/html integration 22 | indentation (working with sgml) 23 | more to come 24 | 25 | This file comes from http://github.com/paradoxxxzero/jinja2-mode 26 | -------------------------------------------------------------------------------- /jinja2-mode.el: -------------------------------------------------------------------------------- 1 | ;;; jinja2-mode.el --- A major mode for jinja2 2 | 3 | ;; Copyright (C) 2011-2022 Florian Mounier aka paradoxxxzero 4 | 5 | ;; Author: Florian Mounier aka paradoxxxzero 6 | ;; Version: 0.3 7 | 8 | ;; This program is free software: you can redistribute it and/or modify 9 | ;; it under the terms of the GNU General Public License as published by 10 | ;; the Free Software Foundation, either version 3 of the License, or 11 | ;; (at your option) any later version. 12 | 13 | ;; This program is distributed in the hope that it will be useful, 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | ;; GNU General Public License for more details. 17 | 18 | ;; You should have received a copy of the GNU General Public License 19 | ;; along with this program. If not, see . 20 | 21 | ;;; Commentary: 22 | 23 | ;; This is an emacs major mode for jinja2 with: 24 | ;; syntax highlighting 25 | ;; sgml/html integration 26 | ;; indentation (working with sgml) 27 | ;; more to come 28 | 29 | ;; This file comes from http://github.com/paradoxxxzero/jinja2-mode 30 | 31 | ;;; Code: 32 | 33 | (require 'sgml-mode) 34 | 35 | (defgroup jinja2 nil 36 | "Major mode for editing jinja2 code." 37 | :prefix "jinja2-" 38 | :group 'languages) 39 | 40 | (defcustom jinja2-user-keywords nil 41 | "Custom keyword names" 42 | :type '(repeat string) 43 | :group 'jinja2) 44 | 45 | (defcustom jinja2-user-functions nil 46 | "Custom function names" 47 | :type '(repeat string) 48 | :group 'jinja2) 49 | 50 | ;; (defcustom jinja2-debug nil 51 | ;; "Log indentation logic" 52 | ;; :type 'boolean 53 | ;; :group 'jinja2) 54 | 55 | (defun jinja2-closing-keywords () 56 | (append 57 | jinja2-user-keywords 58 | '("if" "for" "block" "filter" "with" 59 | "raw" "macro" "autoescape" "trans" "call"))) 60 | 61 | (defun jinja2-indenting-keywords () 62 | (append 63 | (jinja2-closing-keywords) 64 | '("else" "elif"))) 65 | 66 | (defun jinja2-builtin-keywords () 67 | '("as" "autoescape" "debug" "extends" 68 | "firstof" "in" "include" "load" 69 | "now" "regroup" "ssi" "templatetag" 70 | "url" "widthratio" "elif" "true" 71 | "false" "none" "False" "True" "None" 72 | "loop" "super" "caller" "varargs" 73 | "kwargs" "break" "continue" "is" 74 | "not" "or" "and" 75 | "do" "pluralize" "set" "from" "import" 76 | "context" "with" "without" "ignore" 77 | "missing" "scoped")) 78 | 79 | (defun jinja2-functions-keywords () 80 | (append 81 | jinja2-user-functions 82 | '("abs" "attr" "batch" "capitalize" 83 | "center" "default" "dictsort" 84 | "escape" "filesizeformat" "first" 85 | "float" "forceescape" "format" 86 | "groupby" "indent" "int" "join" 87 | "last" "length" "list" "lower" 88 | "pprint" "random" "replace" 89 | "reverse" "round" "safe" "slice" 90 | "sort" "string" "striptags" "sum" 91 | "title" "trim" "truncate" "upper" 92 | "urlize" "wordcount" "wordwrap" "xmlattr"))) 93 | 94 | (defun jinja2-find-open-tag () 95 | (if (search-backward-regexp 96 | (rx-to-string 97 | `(and "{%" 98 | (? "-") 99 | (* whitespace) 100 | (? (group 101 | "end")) 102 | (group 103 | ,(append '(or) 104 | (jinja2-closing-keywords) 105 | )) 106 | (group 107 | (*? anything)) 108 | (* whitespace) 109 | (? "-") 110 | "%}")) nil t) 111 | (if (match-string 1) ;; End tag, going on 112 | (let ((matches (jinja2-find-open-tag))) 113 | (if (string= (car matches) (match-string 2)) 114 | (jinja2-find-open-tag) 115 | (list (match-string 2) (match-string 3)))) 116 | (list (match-string 2) (match-string 3))) 117 | nil)) 118 | 119 | (defun jinja2-close-tag () 120 | "Close the previously opened template tag." 121 | (interactive) 122 | (let ((open-tag (save-excursion (jinja2-find-open-tag)))) 123 | (if open-tag 124 | (insert 125 | (if (string= (car open-tag) "block") 126 | (format "{%% end%s%s %%}" 127 | (car open-tag)(nth 1 open-tag)) 128 | (format "{%% end%s %%}" 129 | (match-string 2)))) 130 | (error "Nothing to close"))) 131 | (save-excursion (jinja2-indent-line))) 132 | 133 | (defun jinja2-insert-tag () 134 | "Insert an empty tag" 135 | (interactive) 136 | (insert "{% ") 137 | (save-excursion 138 | (insert " %}") 139 | (jinja2-indent-line))) 140 | 141 | (defun jinja2-insert-var () 142 | "Insert an empty tag" 143 | (interactive) 144 | (insert "{{ ") 145 | (save-excursion 146 | (insert " }}") 147 | (jinja2-indent-line))) 148 | 149 | (defun jinja2-insert-comment () 150 | "Insert an empty tag" 151 | (interactive) 152 | (insert "{# ") 153 | (save-excursion 154 | (insert " #}") 155 | (jinja2-indent-line))) 156 | 157 | (defconst jinja2-font-lock-comments 158 | `( 159 | (,(rx "{#" 160 | (* whitespace) 161 | (group 162 | (*? anything) 163 | ) 164 | (* whitespace) 165 | "#}") 166 | . (1 font-lock-comment-face t)))) 167 | 168 | (defconst jinja2-font-lock-keywords-1 169 | (append 170 | jinja2-font-lock-comments 171 | sgml-font-lock-keywords-1)) 172 | 173 | (defconst jinja2-font-lock-keywords-2 174 | (append 175 | jinja2-font-lock-keywords-1 176 | sgml-font-lock-keywords-2)) 177 | 178 | (defconst jinja2-font-lock-keywords-3 179 | (append 180 | jinja2-font-lock-keywords-1 181 | jinja2-font-lock-keywords-2 182 | `( 183 | (,(rx "{{" 184 | (* whitespace) 185 | (group 186 | (*? anything) 187 | ) 188 | (* 189 | "|" (* whitespace) (*? anything)) 190 | (* whitespace) 191 | "}}") (1 font-lock-variable-name-face t)) 192 | (,(rx (group "|" (* whitespace)) 193 | (group (+ word)) 194 | ) 195 | (1 font-lock-keyword-face t) 196 | (2 font-lock-warning-face t)) 197 | (,(rx-to-string `(and (group "|" (* whitespace)) 198 | (group 199 | ,(append '(or) 200 | (jinja2-functions-keywords) 201 | )))) 202 | (1 font-lock-keyword-face t) 203 | (2 font-lock-function-name-face t) 204 | ) 205 | (,(rx-to-string `(and word-start 206 | (? "end") 207 | ,(append '(or) 208 | (jinja2-indenting-keywords) 209 | ) 210 | word-end)) (0 font-lock-keyword-face)) 211 | (,(rx-to-string `(and word-start 212 | ,(append '(or) 213 | (jinja2-builtin-keywords) 214 | ) 215 | word-end)) (0 font-lock-builtin-face)) 216 | 217 | (,(rx (or "{%" "%}" "{%-" "-%}")) (0 font-lock-function-name-face t)) 218 | (,(rx (or "{{" "}}")) (0 font-lock-type-face t)) 219 | (,(rx "{#" 220 | (* whitespace) 221 | (group 222 | (*? anything) 223 | ) 224 | (* whitespace) 225 | "#}") 226 | (1 font-lock-comment-face t)) 227 | (,(rx (or "{#" "#}")) (0 font-lock-comment-delimiter-face t)) 228 | ))) 229 | 230 | (defvar jinja2-font-lock-keywords 231 | jinja2-font-lock-keywords-1) 232 | 233 | (defvar jinja2-enable-indent-on-save nil) 234 | 235 | (defun sgml-indent-line-num () 236 | "Indent the current line as SGML." 237 | (let* ((savep (point)) 238 | (indent-col 239 | (save-excursion 240 | (back-to-indentation) 241 | (if (>= (point) savep) (setq savep nil)) 242 | (sgml-calculate-indent)))) 243 | (if (null indent-col) 244 | 0 245 | (if savep 246 | (save-excursion indent-col) 247 | indent-col)))) 248 | 249 | (defun jinja2-calculate-indent-backward (default) 250 | "Return indent column based on previous lines" 251 | (let ((indent-width sgml-basic-offset) (default (sgml-indent-line-num))) 252 | (forward-line -1) 253 | (if (looking-at "^[ \t]*{%-? *end") ; Don't indent after end 254 | (current-indentation) 255 | (if (looking-at (concat "^[ \t]*{%-? *.*?{%-? *end" (regexp-opt (jinja2-indenting-keywords)))) 256 | (current-indentation) 257 | (if (looking-at (concat "^[ \t]*{%-? *" (regexp-opt (jinja2-indenting-keywords)))) ; Check start tag 258 | (+ (current-indentation) indent-width) 259 | (if (looking-at "^[ \t]*<") ; Assume sgml block trust sgml 260 | default 261 | (if (bobp) 262 | 0 263 | (jinja2-calculate-indent-backward default)))))))) 264 | 265 | 266 | (defun jinja2-calculate-indent () 267 | "Return indent column" 268 | (if (bobp) ; Check beginning of buffer 269 | 0 270 | (let ((indent-width sgml-basic-offset) (default (sgml-indent-line-num))) 271 | (if (looking-at "^[ \t]*{%-? *e\\(nd\\|lse\\|lif\\)") ; Check close tag 272 | (save-excursion 273 | (forward-line -1) 274 | (if 275 | (and 276 | (looking-at (concat "^[ \t]*{%-? *" (regexp-opt (jinja2-indenting-keywords)))) 277 | (not (looking-at (concat "^[ \t]*{%-? *.*?{% *end" (regexp-opt (jinja2-indenting-keywords)))))) 278 | (current-indentation) 279 | (- (current-indentation) indent-width))) 280 | (if (looking-at "^[ \t]*