PageRenderTime 30ms CodeModel.GetById 13ms app.highlight 13ms RepoModel.GetById 1ms app.codeStats 0ms

/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
  5;; ***** BEGIN LICENSE BLOCK *****
  6;; Version: MPL 1.1/GPL 2.0/LGPL 2.1
  7
  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/
 12
 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.
 17
 18;; The Original Code is ERB Tag Support for RHTML-MODE.
 19
 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.
 24
 25;; Contributor(s):
 26
 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.
 38
 39;; ***** END LICENSE BLOCK *****
 40
 41
 42;;; History
 43;; 2006SEP12 - Created
 44
 45;; Brief note on conventions:
 46;; DELIM - refers to the things like <% and %>
 47;; TAG - refers to entire <%ERB%> area, -including- the delims
 48
 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)
 54
 55
 56(defconst rhtml-erb-open-delim
 57  "<%"
 58  "ERB opening tag.
 59Due to implementation of `sgml-mode', this absolutely must begin with a
 60< and be at least two characters long to work correctly.")
 61
 62(defconst rhtml-erb-close-delim
 63  "%>"
 64  "ERB ending tag.
 65I don't think this has any restrictions.")
 66
 67(defconst rhtml-erb-open-delim-len
 68  (length rhtml-erb-open-delim))
 69
 70(defconst rhtml-erb-close-delim-len
 71  (length rhtml-erb-open-delim))
 72
 73(defconst rhtml-erb-delim-re
 74  (concat rhtml-erb-open-delim "\\|" rhtml-erb-close-delim))
 75
 76(defconst rhtml-erb-tag-open-re
 77  (concat rhtml-erb-open-delim "\\(?:-=\\|[-=#]?\\)?"))
 78
 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  "<%#")
 89
 90(defconst rhtml-erb-tag-body-re
 91  "\\(?:.\\|\n\\)*?")
 92
 93(defconst rhtml-erb-tag-close-re
 94  (concat "-?" rhtml-erb-close-delim))
 95
 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 "\\)"))
100
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))))
111
112(defun rhtml-erb-middle-offset (prev-line-start cur-line-start)
113  "Helper method for modified `sgml-calculate-indent'.
114Calculates adjustment of branches like \"else\".  PREV-LINE-START
115and CUR-LINE-START should be the first non-white space on each
116line, 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)))))
124
125(defconst rhtml-erb-block-open-re
126  (concat "[A-Za-z_)][ ]+do[ ]+\\(?:|[A-Za-z_, ]*|\\)?[ ]*" rhtml-erb-tag-close-re))
127
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\".")
131
132(defmacro rhtml-erb-block-open-p ()
133  "Guess if a Ruby fragment opens a block with do.
134Returns `block' or `brace-block' on success."
135  `(re-search-forward ,rhtml-erb-block-open-re nil t))
136
137(defmacro rhtml-erb-brace-block-open-p ()
138  "Guess if a Ruby fragment opens a brace block (with {)
139Returns `block' or `brace-block' on success."
140  `(re-search-forward ,rhtml-erb-brace-block-open-re nil t))
141
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))))
146
147(defun rhtml-skip-erb-tag ()
148  "Skips over an ERB tag starting at (POINT); returns non-nil if succesful.
149If the search is successful (POINT) will be advanced."
150  (let ((found (rhtml-at-erb-tag-p)))
151    (when found
152      (goto-char (cdr found)))))
153
154(defun rhtml-erb-tag-type-p (type)
155  (memq type '(erb-open erb-middle erb-close erb-data)))
156
157(defun rhtml-scan-for-erb-tags (tags)
158  "Like `rhtml-scan-erb-tag' but will only return (ERB-TYPE . NAME)
159if (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)))
167
168
169(defun rhtml-scan-erb-tag ()
170  "Scans an ERB tag moving (POINT) to the end and returning (ERB-TYPE . NAME) on success.
171ERB-TYPE is `erb-open', `erb-data', `erb-middle', or `erb-close'.
172NAME is something like \"erb-brace-block\" or \"erb-start-form-tag\" that is
173used 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"))
202                       (t
203                        (cons 'erb-data "erb-data"))))
204             (goto-char erb-tag-end)))
205        (t ;no match
206         (cons nil nil)))))
207
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.
211If 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))))))
228
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))))))
248
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
258
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))
265
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))
274
275          ;; erb tag
276          (push (list
277                 (case (char-after (+ tag-start 2))
278                   (?= 'out) (?# 'comment) (t 'exec))
279                 tag-start (point))
280                regions))))))
281
282(defun rhtml-union-region-containing-erb-tags (r-start r-end)
283  "Returns (START . END) for a region which is an aggregate of
284the region defined by R-START, R-END and any ERB tags which
285start, 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)))
291
292(defun rhtml-widen-to-erb-tag ()
293  "Widens the buffer to the ERB tag.
294If no ERB tag is found the buffer will be reset to pre-state.
295The 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)))))
305
306(defun rhtml-region-has-erb-tag-p (start end)
307  "Returns non-nil if the region bounded by START and END
308contains an ERB tag."
309  (save-excursion
310    (goto-char start)
311    (re-search-forward rhtml-erb-tag-re end t)))
312
313
314;; utility functions
315
316(defun skip-whitespace-forward ()
317  "Skip forward common ([ \t\r\n]) whitespace."
318  (skip-chars-forward " \t\r\n"))
319
320;;
321(provide 'rhtml-erb)