eschulte / rhtml
- Source
- Commits
- Network (7)
- Issues (2)
- Downloads (0)
- Wiki (1)
- Graphs
-
Tree:
ccf0200
rhtml / rhtml-erb.el
| d6fb339d » | eschulte | 2008-07-08 | 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. | ||||
| 59 | Due 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. | ||||
| 65 | I 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 | ||||
| dc8d6c48 » | eschulte | 2008-08-04 | 77 | (concat rhtml-erb-open-delim "\\(?:-=\\|[-=#]?\\)?")) | |
| d6fb339d » | eschulte | 2008-07-08 | 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'. | ||||
| 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))))) | ||||
| 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. | ||||
| 134 | Returns `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 {) | ||||
| 139 | Returns `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. | ||||
| 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))))) | ||||
| 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) | ||||
| 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))) | ||||
| 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. | ||||
| 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")) | ||||
| 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. | ||||
| 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)))))) | ||||
| 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 | ||||
| 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))) | ||||
| 291 | |||||
| 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))))) | ||||
| 305 | |||||
| 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))) | ||||
| 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) | ||||
