/vendor/rhtml/rhtml-erb.el

http://github.com/rejeep/emacs · Lisp · 321 lines · 219 code · 48 blank · 54 comment · 11 complexity · 4b48f449246a80bfab384bb1a41cd54a MD5 · raw file

  1. ;;;
  2. ;;; rhtml-erb.el - ERB tag support for `rhtml-mode'
  3. ;;;
  4. ;; ***** BEGIN LICENSE BLOCK *****
  5. ;; Version: MPL 1.1/GPL 2.0/LGPL 2.1
  6. ;; The contents of this file are subject to the Mozilla Public License Version
  7. ;; 1.1 (the "License"); you may not use this file except in compliance with
  8. ;; the License. You may obtain a copy of the License at
  9. ;; http://www.mozilla.org/MPL/
  10. ;; Software distributed under the License is distributed on an "AS IS" basis,
  11. ;; WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. ;; for the specific language governing rights and limitations under the
  13. ;; License.
  14. ;; The Original Code is ERB Tag Support for RHTML-MODE.
  15. ;; The Initial Developer of the Original Code is
  16. ;; Paul Nathan Stickney <pstickne@gmail.com>.
  17. ;; Portions created by the Initial Developer are Copyright (C) 2006
  18. ;; the Initial Developer. All Rights Reserved.
  19. ;; Contributor(s):
  20. ;; Alternatively, the contents of this file may be used under the terms of
  21. ;; either the GNU General Public License Version 2 or later (the "GPL"), or
  22. ;; the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  23. ;; in which case the provisions of the GPL or the LGPL are applicable instead
  24. ;; of those above. If you wish to allow use of your version of this file only
  25. ;; under the terms of either the GPL or the LGPL, and not to allow others to
  26. ;; use your version of this file under the terms of the MPL, indicate your
  27. ;; decision by deleting the provisions above and replace them with the notice
  28. ;; and other provisions required by the GPL or the LGPL. If you do not delete
  29. ;; the provisions above, a recipient may use your version of this file under
  30. ;; the terms of any one of the MPL, the GPL or the LGPL.
  31. ;; ***** END LICENSE BLOCK *****
  32. ;;; History
  33. ;; 2006SEP12 - Created
  34. ;; Brief note on conventions:
  35. ;; DELIM - refers to the things like <% and %>
  36. ;; TAG - refers to entire <%ERB%> area, -including- the delims
  37. ;; (load-file "~/.emacs.d/macro-utils.el")
  38. ;; (defmacro symbol-name-or-nil (symbol)
  39. ;; (once-only (symbol)
  40. ;; `(if ,symbol (symbol-name ,symbol))))
  41. ;; (put 'symbol-name-or-nil 'lisp-indent-function 1)
  42. (defconst rhtml-erb-open-delim
  43. "<%"
  44. "ERB opening tag.
  45. Due to implementation of `sgml-mode', this absolutely must begin with a
  46. < and be at least two characters long to work correctly.")
  47. (defconst rhtml-erb-close-delim
  48. "%>"
  49. "ERB ending tag.
  50. I don't think this has any restrictions.")
  51. (defconst rhtml-erb-open-delim-len
  52. (length rhtml-erb-open-delim))
  53. (defconst rhtml-erb-close-delim-len
  54. (length rhtml-erb-open-delim))
  55. (defconst rhtml-erb-delim-re
  56. (concat rhtml-erb-open-delim "\\|" rhtml-erb-close-delim))
  57. (defconst rhtml-erb-tag-open-re
  58. (concat rhtml-erb-open-delim "\\(?:-=\\|[-=#]?\\)?"))
  59. ;; specific tags
  60. (defconst rhtml-erb-exec-tag-open-re
  61. (concat rhtml-erb-open-delim "\\(?:-\\(?:[^=#]\\|$\\)\\|[^-=#]\\|$\\)")
  62. "<%, and who would have thought it would be so complicated?")
  63. (defconst rhtml-erb-out-tag-open-re
  64. (concat rhtml-erb-open-delim "-?=")
  65. "<%=")
  66. (defconst rhtml-erb-comment-tag-open-re
  67. (concat rhtml-erb-open-delim "-?#")
  68. "<%#")
  69. (defconst rhtml-erb-tag-body-re
  70. "\\(?:.\\|\n\\)*?")
  71. (defconst rhtml-erb-tag-close-re
  72. (concat "-?" rhtml-erb-close-delim))
  73. (defconst rhtml-erb-tag-re
  74. (concat "\\(" rhtml-erb-tag-open-re "\\)"
  75. "\\(" rhtml-erb-tag-body-re "\\)"
  76. "\\(" rhtml-erb-tag-close-re "\\)"))
  77. (defun rhtml-erb-delim-type (start-delim)
  78. "Return `exec', `out', `comment' or nil dependin on the type of delimeter this is."
  79. (flet ((match? (regex)
  80. (eq (string-match regex start-delim) 0)))
  81. (cond ((match? rhtml-erb-exec-tag-open-re)
  82. 'exec)
  83. ((match? rhtml-erb-out-tag-open-re)
  84. 'out)
  85. ((match? rhtml-erb-comment-tag-open-re)
  86. 'comment))))
  87. (defun rhtml-erb-middle-offset (prev-line-start cur-line-start)
  88. "Helper method for modified `sgml-calculate-indent'.
  89. Calculates adjustment of branches like \"else\". PREV-LINE-START
  90. and CUR-LINE-START should be the first non-white space on each
  91. line, respectively."
  92. (save-excursion
  93. (+ (progn
  94. (goto-char cur-line-start)
  95. (if (rhtml-scan-for-erb-tags '(erb-middle)) sgml-basic-offset 0))
  96. (progn
  97. (goto-char prev-line-start)
  98. (if (rhtml-scan-for-erb-tags '(erb-middle)) (- sgml-basic-offset) 0)))))
  99. (defconst rhtml-erb-block-open-re
  100. (concat "[A-Za-z_)][ ]+do[ ]+\\(?:|[A-Za-z_, ]*|\\)?[ ]*" rhtml-erb-tag-close-re))
  101. (defconst rhtml-erb-brace-block-open-re
  102. (concat "[ ]+{[ ]+\\(?:|[A-Za-z_, ]*|\\)?[ ]*" rhtml-erb-tag-close-re)
  103. "Slightly less strictive to allow for \"hash = {\n\".")
  104. (defmacro rhtml-erb-block-open-p ()
  105. "Guess if a Ruby fragment opens a block with do.
  106. Returns `block' or `brace-block' on success."
  107. `(re-search-forward ,rhtml-erb-block-open-re nil t))
  108. (defmacro rhtml-erb-brace-block-open-p ()
  109. "Guess if a Ruby fragment opens a brace block (with {)
  110. Returns `block' or `brace-block' on success."
  111. `(re-search-forward ,rhtml-erb-brace-block-open-re nil t))
  112. (defun rhtml-at-erb-tag-p ()
  113. "Returns (TAG-START . TAG-END) if at beginning of ERB tag."
  114. (if (looking-at rhtml-erb-tag-re)
  115. (cons (match-beginning 0) (match-end 0))))
  116. (defun rhtml-skip-erb-tag ()
  117. "Skips over an ERB tag starting at (POINT); returns non-nil if succesful.
  118. If the search is successful (POINT) will be advanced."
  119. (let ((found (rhtml-at-erb-tag-p)))
  120. (when found
  121. (goto-char (cdr found)))))
  122. (defun rhtml-erb-tag-type-p (type)
  123. (memq type '(erb-open erb-middle erb-close erb-data)))
  124. (defun rhtml-scan-for-erb-tags (tags)
  125. "Like `rhtml-scan-erb-tag' but will only return (ERB-TYPE . NAME)
  126. if (memq ERB-TYPE tags)."
  127. (let ((start (point))
  128. (tag-info (rhtml-scan-erb-tag)))
  129. (if (memq (car tag-info) tags)
  130. tag-info
  131. ;; reset on failure
  132. (goto-char start)
  133. nil)))
  134. (defun rhtml-scan-erb-tag ()
  135. "Scans an ERB tag moving (POINT) to the end and returning (ERB-TYPE . NAME) on success.
  136. ERB-TYPE is `erb-open', `erb-data', `erb-middle', or `erb-close'.
  137. NAME is something like \"erb-brace-block\" or \"erb-start-form-tag\" that is
  138. used for level-matching."
  139. (let* ((erb-tag (rhtml-at-erb-tag-p))
  140. (erb-tag-end (cdr erb-tag)))
  141. (cond (erb-tag
  142. ;; Lead-in
  143. (looking-at rhtml-erb-tag-open-re)
  144. (goto-char (match-end 0))
  145. (skip-whitespace-forward)
  146. (prog1
  147. (save-restriction
  148. (narrow-to-region (point) erb-tag-end) ;(- end 2))
  149. (cond ((looking-at "if \\|unless ")
  150. (cons 'erb-open "erb-multi-block"))
  151. ((looking-at "for\\b\\|while ")
  152. (cons 'erb-open "erb-block"))
  153. ((rhtml-erb-block-open-p)
  154. (cons 'erb-open "erb-block"))
  155. ((rhtml-erb-brace-block-open-p)
  156. (cons 'erb-open "erb-brace-block"))
  157. ((looking-at "else \\|elsif")
  158. (cons 'erb-middle "erb-middle"))
  159. ((looking-at "end\\b")
  160. (cons 'erb-close "erb-block"))
  161. ((looking-at "}")
  162. (cons 'erb-close "erb-brace-block"))
  163. ((looking-at "start_form_tag\\b")
  164. (cons 'erb-open "erb-form-tag"))
  165. ((looking-at "end_form_tag\\b")
  166. (cons 'erb-close "erb-form-tag"))
  167. (t
  168. (cons 'erb-data "erb-data"))))
  169. (goto-char erb-tag-end)))
  170. (t ;no match
  171. (cons nil nil)))))
  172. ;; TODO - simply by removing point parameter
  173. (defun rhtml-erb-tag-region (&optional point)
  174. "If inside a ERB tag returns (START . END) of the tag, otherwise nil.
  175. If POINT is specified it will be used instead of (POINT)."
  176. (if point
  177. (save-excursion
  178. (goto-char point)
  179. (rhtml-erb-tag-region))
  180. (let ((prev (save-excursion ; -> (STR . START)
  181. (skip-chars-forward rhtml-erb-open-delim)
  182. (when (re-search-backward rhtml-erb-delim-re nil t)
  183. (cons (match-string 0) (match-beginning 0)))))
  184. (next (save-excursion ; -> (STR . END)
  185. (skip-chars-backward rhtml-erb-open-delim)
  186. (when (re-search-forward rhtml-erb-delim-re nil t)
  187. (cons (match-string 0) (match-end 0))))))
  188. ;; limit matches to valid regions
  189. (when (and (string= (car prev) rhtml-erb-open-delim)
  190. (string= (car next) rhtml-erb-close-delim))
  191. (cons (cdr prev) (cdr next))))))
  192. (defun rhtml-erb-regions (begin end)
  193. "Returns a list of elements in the form (TYPE START END) where type is
  194. `exec', `comment', `out'."
  195. (let* (tag-start regions last-tag-end)
  196. (catch 'done
  197. (save-excursion
  198. (goto-char begin)
  199. (while t
  200. (when (not (search-forward rhtml-erb-open-delim end t))
  201. (throw 'done regions))
  202. (setq tag-start (- (point) 2))
  203. (when (not (search-forward rhtml-erb-close-delim end t))
  204. (throw 'done regions))
  205. ;; erb tag
  206. (push (list
  207. (case (char-after (+ tag-start 2))
  208. (?= 'out) (?# 'comment) (t 'exec))
  209. tag-start (point))
  210. regions))))))
  211. ;; PST -- what is the point? At the very least it needs a better name.
  212. (defun rhtml-erb-regions2 (begin end)
  213. "Returns a list of elements in the form (TYPE START END) where type is
  214. `exec', `comment', `out' or, for non-ERb secions, `other'."
  215. (let* (tag-start regions last-tag-end)
  216. (catch 'done
  217. (save-excursion
  218. (goto-char begin)
  219. (while t
  220. (when (not (search-forward rhtml-erb-open-delim end t))
  221. ;; no more erb tags
  222. (push (list 'other (or last-tag-end begin) end)
  223. regions)
  224. (throw 'done regions))
  225. (setq tag-start (- (point) 2))
  226. (when (not (search-forward rhtml-erb-close-delim end t))
  227. (throw 'done regions))
  228. ;; other section
  229. ;; PST -- may catch partial start tag
  230. (when (> (point) (or last-tag-end begin))
  231. (push (list 'other begin (point))
  232. regions))
  233. (setq last-tag-end (point))
  234. ;; erb tag
  235. (push (list
  236. (case (char-after (+ tag-start 2))
  237. (?= 'out) (?# 'comment) (t 'exec))
  238. tag-start (point))
  239. regions))))))
  240. (defun rhtml-union-region-containing-erb-tags (r-start r-end)
  241. "Returns (START . END) for a region which is an aggregate of
  242. the region defined by R-START, R-END and any ERB tags which
  243. start, stop, or are contained in the region."
  244. (let* ((unopened-tag (rhtml-erb-tag-region r-start))
  245. (unclosed-tag (rhtml-erb-tag-region r-end))
  246. (new-start (or (and unopened-tag (car unopened-tag)) r-start))
  247. (new-end (or (and unclosed-tag (cdr unclosed-tag)) r-end)))
  248. (cons new-start new-end)))
  249. (defun rhtml-widen-to-erb-tag ()
  250. "Widens the buffer to the ERB tag.
  251. If no ERB tag is found the buffer will be reset to pre-state.
  252. The point is advanced to the beginning of the new region (even if no ERB found)."
  253. (let ((r-start (point-min))
  254. (r-end (point-max)))
  255. (widen)
  256. (let ((region (rhtml-erb-tag-region)))
  257. (when region
  258. (setq r-start (car region))
  259. (setq r-end (cdr region)))
  260. (narrow-to-region r-start r-end)
  261. (goto-char (point-min)))))
  262. (defun rhtml-region-has-erb-tag-p (start end)
  263. "Returns non-nil if the region bounded by START and END
  264. contains an ERB tag."
  265. (save-excursion
  266. (goto-char start)
  267. (re-search-forward rhtml-erb-tag-re end t)))
  268. ;; utility functions
  269. (defun skip-whitespace-forward ()
  270. "Skip forward common ([ \t\r\n]) whitespace."
  271. (skip-chars-forward " \t\r\n"))
  272. ;;
  273. (provide 'rhtml-erb)