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