├── .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]*") ; Assume sgml end block trust sgml
281 | default
282 | (save-excursion
283 | (jinja2-calculate-indent-backward default)))))))
284 |
285 | (defun jinja2-indent-line ()
286 | "Indent current line as Jinja code"
287 | (interactive)
288 | (let ((old_indent (current-indentation)) (old_point (point)))
289 | (move-beginning-of-line nil)
290 | (let ((indent (max 0 (jinja2-calculate-indent))))
291 | (indent-line-to indent)
292 | (if (< old_indent (- old_point (line-beginning-position)))
293 | (goto-char (+ (- indent old_indent) old_point)))
294 | indent)))
295 |
296 | (defun jinja2-indent-buffer()
297 | (interactive)
298 | (save-excursion
299 | (indent-region (point-min) (point-max))))
300 |
301 | ;;;###autoload
302 | (define-derived-mode jinja2-mode html-mode "Jinja2"
303 | "Major mode for editing jinja2 files"
304 | :group 'jinja2
305 | ;; Disabling this because of this emacs bug:
306 | ;; http://lists.gnu.org/archive/html/bug-gnu-emacs/2002-09/msg00041.html
307 | ;; (modify-syntax-entry ?\' "\"" sgml-mode-syntax-table)
308 | (set (make-local-variable 'comment-start) "{#")
309 | (set (make-local-variable 'comment-start-skip) "{#")
310 | (set (make-local-variable 'comment-end) "#}")
311 | (set (make-local-variable 'comment-end-skip) "#}")
312 | ;; it mainly from sgml-mode font lock setting
313 | (set (make-local-variable 'font-lock-defaults)
314 | '((
315 | jinja2-font-lock-keywords
316 | jinja2-font-lock-keywords-1
317 | jinja2-font-lock-keywords-2
318 | jinja2-font-lock-keywords-3)
319 | nil t nil nil
320 | (font-lock-syntactic-keywords
321 | . sgml-font-lock-syntactic-keywords)))
322 | (set (make-local-variable 'indent-line-function) 'jinja2-indent-line))
323 |
324 | (define-key jinja2-mode-map (kbd "C-c c") 'jinja2-close-tag)
325 | (define-key jinja2-mode-map (kbd "C-c t") 'jinja2-insert-tag)
326 | (define-key jinja2-mode-map (kbd "C-c v") 'jinja2-insert-var)
327 | (define-key jinja2-mode-map (kbd "C-c #") 'jinja2-insert-comment)
328 |
329 | (when jinja2-enable-indent-on-save
330 | (add-hook 'jinja2-mode-hook
331 | (lambda ()
332 | (add-hook 'after-save-hook 'jinja2-indent-buffer nil 'make-it-local))))
333 |
334 | ;;;###autoload
335 | (add-to-list 'auto-mode-alist '("\\.jinja2\\'" . jinja2-mode))
336 | ;;;###autoload
337 | (add-to-list 'auto-mode-alist '("\\.j2\\'" . jinja2-mode))
338 |
339 | (provide 'jinja2-mode)
340 |
341 | ;;; jinja2-mode.el ends here
342 |
--------------------------------------------------------------------------------