Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

330 lines
10 KiB

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