/vendor/rhtml/rhtml-erb.el
Lisp | 321 lines | 219 code | 48 blank | 54 comment | 11 complexity | 4b48f449246a80bfab384bb1a41cd54a MD5 | raw file
Possible License(s): GPL-2.0
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)