Initial Commit
[temp.git] / site-lisp / rhtml / rhtml-erb.el
blobed50fc2137eff287c541369dba741468aa17a613
1 ;;;
2 ;;; rhtml-erb.el - ERB tag support for `rhtml-mode'
3 ;;;
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
16 ;; License.
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.
25 ;; Contributor(s):
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 *****
42 ;;; History
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
57 "<%"
58 "ERB opening tag.
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
63 "%>"
64 "ERB ending tag.
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 "\\(?:-=\\|[=#]?\\)?"))
79 ;; specific tags
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 "-?=")
85 "<%=")
86 (defconst rhtml-erb-comment-tag-open-re
87 (concat rhtml-erb-open-delim "-?#")
88 "<%#")
90 (defconst rhtml-erb-tag-body-re
91 "\\(?:.\\|\n\\)*?")
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)
106 'exec)
107 ((match? rhtml-erb-out-tag-open-re)
108 'out)
109 ((match? rhtml-erb-comment-tag-open-re)
110 'comment))))
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
116 line, respectively."
117 (save-excursion
118 (+ (progn
119 (goto-char cur-line-start)
120 (if (rhtml-scan-for-erb-tags '(erb-middle)) sgml-basic-offset 0))
121 (progn
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)))
151 (when found
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)
163 tag-info
164 ;; reset on failure
165 (goto-char start)
166 nil)))
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)))
176 (cond (erb-tag
177 ;; Lead-in
178 (looking-at rhtml-erb-tag-open-re)
179 (goto-char (match-end 0))
180 (skip-whitespace-forward)
181 (prog1
182 (save-restriction
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"))
196 ((looking-at "}")
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)))
205 (t ;no match
206 (cons nil nil)))))
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)."
212 (if point
213 (save-excursion
214 (goto-char 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)
233 (catch 'done
234 (save-excursion
235 (goto-char begin)
236 (while t
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))
242 ;; erb tag
243 (push (list
244 (case (char-after (+ tag-start 2))
245 (?= 'out) (?# 'comment) (t 'exec))
246 tag-start (point))
247 regions))))))
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)
254 (catch 'done
255 (save-excursion
256 (goto-char begin)
257 (while t
259 (when (not (search-forward rhtml-erb-open-delim end t))
260 ;; no more erb tags
261 (push (list 'other (or last-tag-end begin) end)
262 regions)
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))
268 ;; other section
269 ;; PST -- may catch partial start tag
270 (when (> (point) (or last-tag-end begin))
271 (push (list 'other begin (point))
272 regions))
273 (setq last-tag-end (point))
275 ;; erb tag
276 (push (list
277 (case (char-after (+ tag-start 2))
278 (?= 'out) (?# 'comment) (t 'exec))
279 tag-start (point))
280 regions))))))
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))
297 (r-end (point-max)))
298 (widen)
299 (let ((region (rhtml-erb-tag-region)))
300 (when 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."
309 (save-excursion
310 (goto-char start)
311 (re-search-forward rhtml-erb-tag-re end t)))
314 ;; utility functions
316 (defun skip-whitespace-forward ()
317 "Skip forward common ([ \t\r\n]) whitespace."
318 (skip-chars-forward " \t\r\n"))
321 (provide 'rhtml-erb)