Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 373
- Log:
Initial import of Radiant 0.9.1, which is now packaged as a gem. This is an
import of the tagged 0.9.1 source checked out from GitHub, which isn't quite
the same as the gem distribution - but it doesn't seem to be available in an
archived form and the installed gem already has modifications, so this is
the closest I can get.
- Author:
- rool
- Date:
- Mon Mar 21 13:40:05 +0000 2011
- Size:
- 28082 Bytes
1 | ;;; haml-mode.el --- Major mode for editing Haml files |
2 | |
3 | ;; Copyright (c) 2007, 2008 Nathan Weizenbaum |
4 | |
5 | ;; Author: Nathan Weizenbaum |
6 | ;; URL: http://github.com/nex3/haml/tree/master |
7 | ;; Version: 2.2.21 |
8 | ;; Created: 2007-03-08 |
9 | ;; By: Nathan Weizenbaum |
10 | ;; Keywords: markup, language, html |
11 | |
12 | ;;; Commentary: |
13 | |
14 | ;; Because Haml's indentation schema is similar |
15 | ;; to that of YAML and Python, many indentation-related |
16 | ;; functions are similar to those in yaml-mode and python-mode. |
17 | |
18 | ;; To install, save this on your load path and add the following to |
19 | ;; your .emacs file: |
20 | ;; |
21 | ;; (require 'haml-mode) |
22 | |
23 | ;;; Code: |
24 | |
25 | (eval-when-compile (require 'cl)) |
26 | (require 'ruby-mode) |
27 | |
28 | ;; Additional (optional) libraries for fontification |
29 | (require 'css-mode nil t) |
30 | (require 'textile-mode nil t) |
31 | (require 'markdown-mode nil t) |
32 | (require 'javascript-mode "javascript" t) |
33 | (require 'js nil t) |
34 | |
35 | |
36 | ;; User definable variables |
37 | |
38 | (defgroup haml nil |
39 | "Support for the Haml template language." |
40 | :group 'languages |
41 | :prefix "haml-") |
42 | |
43 | (defcustom haml-mode-hook nil |
44 | "Hook run when entering Haml mode." |
45 | :type 'hook |
46 | :group 'haml) |
47 | |
48 | (defcustom haml-indent-offset 2 |
49 | "Amount of offset per level of indentation." |
50 | :type 'integer |
51 | :group 'haml) |
52 | |
53 | (defcustom haml-backspace-backdents-nesting t |
54 | "Non-nil to have `haml-electric-backspace' re-indent blocks of code. |
55 | This means that all code nested beneath the backspaced line is |
56 | re-indented along with the line itself." |
57 | :type 'boolean |
58 | :group 'haml) |
59 | |
60 | (defface haml-tab-face |
61 | '((((class color)) (:background "hotpink")) |
62 | (t (:reverse-video t))) |
63 | "Face to use for highlighting tabs in Haml files." |
64 | :group 'faces |
65 | :group 'haml) |
66 | |
67 | (defvar haml-indent-function 'haml-indent-p |
68 | "A function for checking if nesting is allowed. |
69 | This function should look at the current line and return t |
70 | if the next line could be nested within this line. |
71 | |
72 | The function can also return a positive integer to indicate |
73 | a specific level to which the current line could be indented.") |
74 | |
75 | (defconst haml-tag-beg-re |
76 | "^ *\\(?:[%\\.#][a-z0-9_:\\-]*\\)+\\(?:(.*)\\|{.*}\\|\\[.*\\]\\)*" |
77 | "A regexp matching the beginning of a Haml tag, through (), {}, and [].") |
78 | |
79 | (defvar haml-block-openers |
80 | `(,(concat haml-tag-beg-re "[><]*[ \t]*$") |
81 | "^ *[&!]?[-=~].*do[ \t]*\\(|.*|[ \t]*\\)?$" |
82 | ,(concat "^ *[&!]?[-=~][ \t]*\\(" |
83 | (regexp-opt '("if" "unless" "while" "until" "else" |
84 | "begin" "elsif" "rescue" "ensure" "when")) |
85 | "\\)") |
86 | "^ */\\(\\[.*\\]\\)?[ \t]*$" |
87 | "^ *-#" |
88 | "^ *:") |
89 | "A list of regexps that match lines of Haml that open blocks. |
90 | That is, a Haml line that can have text nested beneath it should |
91 | be matched by a regexp in this list.") |
92 | |
93 | ;; Font lock |
94 | |
95 | (defun haml-nested-regexp (re) |
96 | "Create a regexp to match a block starting with RE. |
97 | The line containing RE is matched, as well as all lines indented beneath it." |
98 | (concat "^\\( *\\)" re "\\(\n\\(?:\\(?:\\1 .*\\| *\\)\n\\)*\\(?:\\1 .*\\| *\\)?\\)?")) |
99 | |
100 | (defconst haml-font-lock-keywords |
101 | `((,(haml-nested-regexp "\\(?:-#\\|/\\).*") 0 font-lock-comment-face) |
102 | (,(haml-nested-regexp ":\\w+") 0 font-lock-string-face) |
103 | (haml-highlight-ruby-filter-block 1 font-lock-preprocessor-face) |
104 | (haml-highlight-css-filter-block 1 font-lock-preprocessor-face) |
105 | (haml-highlight-textile-filter-block 1 font-lock-preprocessor-face) |
106 | (haml-highlight-markdown-filter-block 1 font-lock-preprocessor-face) |
107 | (haml-highlight-js-filter-block 1 font-lock-preprocessor-face) |
108 | (haml-highlight-interpolation 1 font-lock-variable-name-face prepend) |
109 | (haml-highlight-ruby-tag 1 font-lock-preprocessor-face) |
110 | (haml-highlight-ruby-script 1 font-lock-preprocessor-face) |
111 | ("^ *\\(\t\\)" 1 'haml-tab-face) |
112 | ("^!!!.*" 0 font-lock-constant-face) |
113 | ("| *$" 0 font-lock-string-face))) |
114 | |
115 | (defconst haml-filter-re "^ *:\\w+") |
116 | (defconst haml-comment-re "^ *\\(?:-\\#\\|/\\)") |
117 | |
118 | (defun haml-fontify-region (beg end keywords syntax-table syntactic-keywords) |
119 | "Fontify a region between BEG and END using another mode's fontification. |
120 | |
121 | KEYWORDS, SYNTAX-TABLE, and SYNTACTIC-KEYWORDS are the values of that mode's |
122 | `font-lock-keywords', `font-lock-syntax-table', |
123 | and `font-lock-syntactic-keywords', respectively." |
124 | (save-excursion |
125 | (save-match-data |
126 | (let ((font-lock-keywords keywords) |
127 | (font-lock-syntax-table syntax-table) |
128 | (font-lock-syntactic-keywords syntactic-keywords) |
129 | (font-lock-multiline 'undecided) |
130 | font-lock-keywords-only |
131 | font-lock-extend-region-functions |
132 | font-lock-keywords-case-fold-search) |
133 | ;; font-lock-fontify-region apparently isn't inclusive, |
134 | ;; so we have to move the beginning back one char |
135 | (font-lock-fontify-region (- beg 1) end))))) |
136 | |
137 | (defun haml-fontify-region-as-ruby (beg end) |
138 | "Use Ruby's font-lock variables to fontify the region between BEG and END." |
139 | (haml-fontify-region beg end ruby-font-lock-keywords nil |
140 | ruby-font-lock-syntactic-keywords)) |
141 | |
142 | (defun haml-handle-filter (filter-name limit fn) |
143 | "If a FILTER-NAME filter is found within LIMIT, run FN on that filter. |
144 | |
145 | FN is passed a pair of points representing the beginning and end |
146 | of the filtered text." |
147 | (when (re-search-forward (haml-nested-regexp (concat ":" filter-name)) limit t) |
148 | (funcall fn (+ 2 (match-beginning 2)) (match-end 2)))) |
149 | |
150 | (defun haml-fontify-filter-region (filter-name limit &rest fontify-region-args) |
151 | "If a FILTER-NAME filter is found within LIMIT, fontify it. |
152 | |
153 | The fontification is done by passing FONTIFY-REGION-ARGS to |
154 | `haml-fontify-region'." |
155 | (haml-handle-filter filter-name limit |
156 | (lambda (beg end) |
157 | (apply 'haml-fontify-region |
158 | (append (list beg end) |
159 | fontify-region-args))))) |
160 | |
161 | (defun haml-highlight-ruby-filter-block (limit) |
162 | "If a :ruby filter is found within LIMIT, highlight it." |
163 | (haml-handle-filter "ruby" limit 'haml-fontify-region-as-ruby)) |
164 | |
165 | (defun haml-highlight-css-filter-block (limit) |
166 | "If a :css filter is found within LIMIT, highlight it. |
167 | |
168 | This requires that `css-mode' is available. |
169 | `css-mode' is included with Emacs 23." |
170 | (if (boundp 'css-font-lock-keywords) |
171 | (haml-fontify-filter-region "css" limit css-font-lock-keywords nil nil))) |
172 | |
173 | (defun haml-highlight-js-filter-block (limit) |
174 | "If a :javascript filter is found within LIMIT, highlight it. |
175 | |
176 | This requires that Karl Landström's javascript mode be available, either as the |
177 | \"js.el\" bundled with Emacs 23, or as \"javascript.el\" found in ELPA and |
178 | elsewhere." |
179 | (let ((keywords (or (and (featurep 'js) js--font-lock-keywords-3) |
180 | (and (featurep 'javascript-mode) js-font-lock-keywords-3))) |
181 | (syntax-table (or (and (featurep 'js) js-mode-syntax-table) |
182 | (and (featurep 'javascript-mode) javascript-mode-syntax-table)))) |
183 | (when keywords |
184 | (haml-fontify-filter-region "javascript" limit keywords syntax-table nil)))) |
185 | |
186 | (defun haml-highlight-textile-filter-block (limit) |
187 | "If a :textile filter is found within LIMIT, highlight it. |
188 | |
189 | This requires that `textile-mode' be available. |
190 | |
191 | Note that the results are not perfect, since `textile-mode' expects |
192 | certain constructs such as \"h1.\" to be at the beginning of a line, |
193 | and indented Haml filters always have leading whitespace." |
194 | (if (boundp 'textile-font-lock-keywords) |
195 | (haml-fontify-filter-region "textile" limit textile-font-lock-keywords nil nil))) |
196 | |
197 | (defun haml-highlight-markdown-filter-block (limit) |
198 | "If a :markdown filter is found within LIMIT, highlight it. |
199 | |
200 | This requires that `markdown-mode' be available." |
201 | (if (boundp 'markdown-mode-font-lock-keywords) |
202 | (haml-fontify-filter-region "markdown" limit |
203 | markdown-mode-font-lock-keywords |
204 | markdown-mode-syntax-table |
205 | nil))) |
206 | |
207 | (defun haml-highlight-ruby-script (limit) |
208 | "Highlight a Ruby script expression (-, =, or ~). |
209 | LIMIT works as it does in `re-search-forward'." |
210 | (when (re-search-forward "^ *\\(-\\|[&!]?[=~]\\) \\(.*\\)$" limit t) |
211 | (haml-fontify-region-as-ruby (match-beginning 2) (match-end 2)))) |
212 | |
213 | (defun haml-highlight-ruby-tag (limit) |
214 | "Highlight Ruby code within a Haml tag. |
215 | LIMIT works as it does in `re-search-forward'. |
216 | |
217 | This highlights the tag attributes and object refs of the tag, |
218 | as well as the script expression (-, =, or ~) following the tag. |
219 | |
220 | For example, this will highlight all of the following: |
221 | %p{:foo => 'bar'} |
222 | %p[@bar] |
223 | %p= 'baz' |
224 | %p{:foo => 'bar'}[@bar]= 'baz'" |
225 | (when (re-search-forward "^ *[%.#]" limit t) |
226 | (forward-char -1) |
227 | |
228 | ;; Highlight tag, classes, and ids |
229 | (while (haml-move "\\([.#%]\\)[a-z0-9_:\\-]*") |
230 | (put-text-property (match-beginning 0) (match-end 0) 'face |
231 | (case (char-after (match-beginning 1)) |
232 | (?% font-lock-function-name-face) |
233 | (?# font-lock-keyword-face) |
234 | (?. font-lock-type-face)))) |
235 | |
236 | (block loop |
237 | (while t |
238 | (let ((eol (save-excursion (end-of-line) (point)))) |
239 | (case (char-after) |
240 | ;; Highlight obj refs |
241 | (?\[ |
242 | (let ((beg (point))) |
243 | (haml-limited-forward-sexp eol) |
244 | (haml-fontify-region-as-ruby beg (point)))) |
245 | ;; Highlight new attr hashes |
246 | (?\( |
247 | (forward-char 1) |
248 | (while |
249 | (and (haml-parse-new-attr-hash |
250 | (lambda (type beg end) |
251 | (case type |
252 | (name (put-text-property beg end 'face font-lock-constant-face)) |
253 | (value (haml-fontify-region-as-ruby beg end))))) |
254 | (not (eobp))) |
255 | (forward-line 1) |
256 | (beginning-of-line))) |
257 | ;; Highlight old attr hashes |
258 | (?\{ |
259 | (let ((beg (point))) |
260 | (haml-limited-forward-sexp eol) |
261 | |
262 | ;; Check for multiline |
263 | (while (and (eolp) (eq (char-before) ?,) (not (eobp))) |
264 | (forward-line) |
265 | (let ((eol (save-excursion (end-of-line) (point)))) |
266 | ;; If no sexps are closed, |
267 | ;; we're still continuing a multiline hash |
268 | (if (>= (car (parse-partial-sexp (point) eol)) 0) |
269 | (end-of-line) |
270 | ;; If sexps have been closed, |
271 | ;; set the point at the end of the total sexp |
272 | (goto-char beg) |
273 | (haml-limited-forward-sexp eol)))) |
274 | |
275 | (haml-fontify-region-as-ruby (+ 1 beg) (point)))) |
276 | (t (return-from loop)))))) |
277 | |
278 | ;; Move past end chars |
279 | (when (looking-at "[<>&!]+") (goto-char (match-end 0))) |
280 | ;; Highlight script |
281 | (if (looking-at "\\([=~]\\) \\(.*\\)$") |
282 | (haml-fontify-region-as-ruby (match-beginning 2) (match-end 2)) |
283 | ;; Give font-lock something to highlight |
284 | (forward-char -1) |
285 | (looking-at "\\(\\)")) |
286 | t)) |
287 | |
288 | (defun haml-move (re) |
289 | "Try matching and moving to the end of regular expression RE. |
290 | Returns non-nil if the expression was sucessfully matched." |
291 | (when (looking-at re) |
292 | (goto-char (match-end 0)) |
293 | t)) |
294 | |
295 | (defun haml-highlight-interpolation (limit) |
296 | "Highlight Ruby interpolation (#{foo}). |
297 | LIMIT works as it does in `re-search-forward'." |
298 | (when (re-search-forward "\\(#{\\)" limit t) |
299 | (save-match-data |
300 | (forward-char -1) |
301 | (let ((beg (point))) |
302 | (haml-limited-forward-sexp limit) |
303 | (haml-fontify-region-as-ruby (+ 1 beg) (point))) |
304 | |
305 | (when (eq (char-before) ?}) |
306 | (put-text-property (- (point) 1) (point) |
307 | 'face font-lock-variable-name-face)) |
308 | t))) |
309 | |
310 | (defun haml-limited-forward-sexp (limit &optional arg) |
311 | "Move forward using `forward-sexp' or to LIMIT, whichever comes first. |
312 | With ARG, do it that many times." |
313 | (let (forward-sexp-function) |
314 | (condition-case err |
315 | (save-restriction |
316 | (narrow-to-region (point) limit) |
317 | (forward-sexp arg)) |
318 | (scan-error |
319 | (unless (equal (nth 1 err) "Unbalanced parentheses") |
320 | (signal 'scan-error (cdr err))) |
321 | (goto-char limit))))) |
322 | |
323 | (defun* haml-extend-region-filters-comments () |
324 | "Extend the font-lock region to encompass filters and comments." |
325 | (let ((old-beg font-lock-beg) |
326 | (old-end font-lock-end)) |
327 | (save-excursion |
328 | (goto-char font-lock-beg) |
329 | (beginning-of-line) |
330 | (unless (or (looking-at haml-filter-re) |
331 | (looking-at haml-comment-re)) |
332 | (return-from haml-extend-region-filters-comments)) |
333 | (setq font-lock-beg (point)) |
334 | (haml-forward-sexp) |
335 | (beginning-of-line) |
336 | (setq font-lock-end (max font-lock-end (point)))) |
337 | (or (/= old-beg font-lock-beg) |
338 | (/= old-end font-lock-end)))) |
339 | |
340 | (defun* haml-extend-region-multiline-hashes () |
341 | "Extend the font-lock region to encompass multiline attribute hashes." |
342 | (let ((old-beg font-lock-beg) |
343 | (old-end font-lock-end)) |
344 | (save-excursion |
345 | (goto-char font-lock-beg) |
346 | (let ((attr-props (haml-parse-multiline-attr-hash)) |
347 | multiline-end) |
348 | (when attr-props |
349 | (setq font-lock-beg (cdr (assq 'point attr-props))) |
350 | |
351 | (end-of-line) |
352 | ;; Move through multiline attrs |
353 | (when (eq (char-before) ?,) |
354 | (save-excursion |
355 | (while (progn (end-of-line) (and (eq (char-before) ?,) (not (eobp)))) |
356 | (forward-line)) |
357 | |
358 | (forward-line -1) |
359 | (end-of-line) |
360 | (setq multiline-end (point)))) |
361 | |
362 | (goto-char (+ (cdr (assq 'point attr-props)) |
363 | (cdr (assq 'hash-indent attr-props)) |
364 | -1)) |
365 | (haml-limited-forward-sexp |
366 | (or multiline-end |
367 | (save-excursion (end-of-line) (point)))) |
368 | (setq font-lock-end (max font-lock-end (point)))))) |
369 | (or (/= old-beg font-lock-beg) |
370 | (/= old-end font-lock-end)))) |
371 | |
372 | |
373 | ;; Mode setup |
374 | |
375 | (defvar haml-mode-syntax-table |
376 | (let ((table (make-syntax-table))) |
377 | (modify-syntax-entry ?: "." table) |
378 | (modify-syntax-entry ?_ "w" table) |
379 | table) |
380 | "Syntax table in use in `haml-mode' buffers.") |
381 | |
382 | (defvar haml-mode-map |
383 | (let ((map (make-sparse-keymap))) |
384 | (define-key map [backspace] 'haml-electric-backspace) |
385 | (define-key map "\C-?" 'haml-electric-backspace) |
386 | (define-key map "\C-c\C-f" 'haml-forward-sexp) |
387 | (define-key map "\C-c\C-b" 'haml-backward-sexp) |
388 | (define-key map "\C-c\C-u" 'haml-up-list) |
389 | (define-key map "\C-c\C-d" 'haml-down-list) |
390 | (define-key map "\C-c\C-k" 'haml-kill-line-and-indent) |
391 | (define-key map "\C-c\C-r" 'haml-output-region) |
392 | (define-key map "\C-c\C-l" 'haml-output-buffer) |
393 | map)) |
394 | |
395 | ;;;###autoload |
396 | (define-derived-mode haml-mode fundamental-mode "Haml" |
397 | "Major mode for editing Haml files. |
398 | |
399 | \\{haml-mode-map}" |
400 | (set-syntax-table haml-mode-syntax-table) |
401 | (add-to-list 'font-lock-extend-region-functions 'haml-extend-region-filters-comments) |
402 | (add-to-list 'font-lock-extend-region-functions 'haml-extend-region-multiline-hashes) |
403 | (set (make-local-variable 'font-lock-multiline) t) |
404 | (set (make-local-variable 'indent-line-function) 'haml-indent-line) |
405 | (set (make-local-variable 'indent-region-function) 'haml-indent-region) |
406 | (set (make-local-variable 'parse-sexp-lookup-properties) t) |
407 | (setq comment-start "-#") |
408 | (setq indent-tabs-mode nil) |
409 | (setq font-lock-defaults '((haml-font-lock-keywords) t t))) |
410 | |
411 | ;; Useful functions |
412 | |
413 | (defun haml-comment-block () |
414 | "Comment the current block of Haml code." |
415 | (interactive) |
416 | (save-excursion |
417 | (let ((indent (current-indentation))) |
418 | (back-to-indentation) |
419 | (insert "-#") |
420 | (newline) |
421 | (indent-to indent) |
422 | (beginning-of-line) |
423 | (haml-mark-sexp) |
424 | (haml-reindent-region-by haml-indent-offset)))) |
425 | |
426 | (defun haml-uncomment-block () |
427 | "Uncomment the current block of Haml code." |
428 | (interactive) |
429 | (save-excursion |
430 | (beginning-of-line) |
431 | (while (not (looking-at haml-comment-re)) |
432 | (haml-up-list) |
433 | (beginning-of-line)) |
434 | (haml-mark-sexp) |
435 | (kill-line 1) |
436 | (haml-reindent-region-by (- haml-indent-offset)))) |
437 | |
438 | (defun haml-replace-region (start end) |
439 | "Replace the current block of Haml code with the HTML equivalent. |
440 | Called from a program, START and END specify the region to indent." |
441 | (interactive "r") |
442 | (save-excursion |
443 | (goto-char end) |
444 | (setq end (point-marker)) |
445 | (goto-char start) |
446 | (let ((ci (current-indentation))) |
447 | (while (re-search-forward "^ +" end t) |
448 | (replace-match (make-string (- (current-indentation) ci) ? )))) |
449 | (shell-command-on-region start end "haml" "haml-output" t))) |
450 | |
451 | (defun haml-output-region (start end) |
452 | "Displays the HTML output for the current block of Haml code. |
453 | Called from a program, START and END specify the region to indent." |
454 | (interactive "r") |
455 | (kill-new (buffer-substring start end)) |
456 | (with-temp-buffer |
457 | (yank) |
458 | (haml-indent-region (point-min) (point-max)) |
459 | (shell-command-on-region (point-min) (point-max) "haml" "haml-output"))) |
460 | |
461 | (defun haml-output-buffer () |
462 | "Displays the HTML output for entire buffer." |
463 | (interactive) |
464 | (haml-output-region (point-min) (point-max))) |
465 | |
466 | ;; Navigation |
467 | |
468 | (defun haml-forward-through-whitespace (&optional backward) |
469 | "Move the point forward through any whitespace. |
470 | The point will move forward at least one line, until it reaches |
471 | either the end of the buffer or a line with no whitespace. |
472 | |
473 | If BACKWARD is non-nil, move the point backward instead." |
474 | (let ((arg (if backward -1 1)) |
475 | (endp (if backward 'bobp 'eobp))) |
476 | (loop do (forward-line arg) |
477 | while (and (not (funcall endp)) |
478 | (looking-at "^[ \t]*$"))))) |
479 | |
480 | (defun haml-at-indent-p () |
481 | "Return non-nil if the point is before any text on the line." |
482 | (let ((opoint (point))) |
483 | (save-excursion |
484 | (back-to-indentation) |
485 | (>= (point) opoint)))) |
486 | |
487 | (defun haml-forward-sexp (&optional arg) |
488 | "Move forward across one nested expression. |
489 | With ARG, do it that many times. Negative arg -N means move |
490 | backward across N balanced expressions. |
491 | |
492 | A sexp in Haml is defined as a line of Haml code as well as any |
493 | lines nested beneath it." |
494 | (interactive "p") |
495 | (or arg (setq arg 1)) |
496 | (if (and (< arg 0) (not (haml-at-indent-p))) |
497 | (back-to-indentation) |
498 | (while (/= arg 0) |
499 | (let ((indent (current-indentation))) |
500 | (loop do (haml-forward-through-whitespace (< arg 0)) |
501 | while (and (not (eobp)) |
502 | (not (bobp)) |
503 | (> (current-indentation) indent))) |
504 | (back-to-indentation) |
505 | (setq arg (+ arg (if (> arg 0) -1 1))))))) |
506 | |
507 | (defun haml-backward-sexp (&optional arg) |
508 | "Move backward across one nested expression. |
509 | With ARG, do it that many times. Negative arg -N means move |
510 | forward across N balanced expressions. |
511 | |
512 | A sexp in Haml is defined as a line of Haml code as well as any |
513 | lines nested beneath it." |
514 | (interactive "p") |
515 | (haml-forward-sexp (if arg (- arg) -1))) |
516 | |
517 | (defun haml-up-list (&optional arg) |
518 | "Move out of one level of nesting. |
519 | With ARG, do this that many times." |
520 | (interactive "p") |
521 | (or arg (setq arg 1)) |
522 | (while (> arg 0) |
523 | (let ((indent (current-indentation))) |
524 | (loop do (haml-forward-through-whitespace t) |
525 | while (and (not (bobp)) |
526 | (>= (current-indentation) indent))) |
527 | (setq arg (- arg 1)))) |
528 | (back-to-indentation)) |
529 | |
530 | (defun haml-down-list (&optional arg) |
531 | "Move down one level of nesting. |
532 | With ARG, do this that many times." |
533 | (interactive "p") |
534 | (or arg (setq arg 1)) |
535 | (while (> arg 0) |
536 | (let ((indent (current-indentation))) |
537 | (haml-forward-through-whitespace) |
538 | (when (<= (current-indentation) indent) |
539 | (haml-forward-through-whitespace t) |
540 | (back-to-indentation) |
541 | (error "Nothing is nested beneath this line")) |
542 | (setq arg (- arg 1)))) |
543 | (back-to-indentation)) |
544 | |
545 | (defun haml-mark-sexp () |
546 | "Mark the next Haml block." |
547 | (let ((forward-sexp-function 'haml-forward-sexp)) |
548 | (mark-sexp))) |
549 | |
550 | (defun haml-mark-sexp-but-not-next-line () |
551 | "Mark the next Haml block, but not the next line. |
552 | Put the mark at the end of the last line of the sexp rather than |
553 | the first non-whitespace character of the next line." |
554 | (haml-mark-sexp) |
555 | (set-mark |
556 | (save-excursion |
557 | (goto-char (mark)) |
558 | (forward-line -1) |
559 | (end-of-line) |
560 | (point)))) |
561 | |
562 | ;; Indentation and electric keys |
563 | |
564 | (defun* haml-indent-p () |
565 | "Returns t if the current line can have lines nested beneath it." |
566 | (let ((attr-props (haml-parse-multiline-attr-hash))) |
567 | (when attr-props |
568 | (return-from haml-indent-p |
569 | (if (haml-unclosed-attr-hash-p) (cdr (assq 'hash-indent attr-props)) |
570 | (list (+ (cdr (assq 'indent attr-props)) haml-indent-offset) nil))))) |
571 | (loop for opener in haml-block-openers |
572 | if (looking-at opener) return t |
573 | finally return nil)) |
574 | |
575 | (defun* haml-parse-multiline-attr-hash () |
576 | "Parses a multiline attribute hash, and returns |
577 | an alist with the following keys: |
578 | |
579 | INDENT is the indentation of the line beginning the hash. |
580 | |
581 | HASH-INDENT is the indentation of the first character |
582 | within the attribute hash. |
583 | |
584 | POINT is the character position at the beginning of the line |
585 | beginning the hash." |
586 | (save-excursion |
587 | (while t |
588 | (beginning-of-line) |
589 | (if (looking-at (concat haml-tag-beg-re "\\([{(]\\)")) |
590 | (progn |
591 | (goto-char (- (match-end 0) 1)) |
592 | (haml-limited-forward-sexp (save-excursion (end-of-line) (point))) |
593 | (return-from haml-parse-multiline-attr-hash |
594 | (when (or (string-equal (match-string 1) "(") (eq (char-before) ?,)) |
595 | `((indent . ,(current-indentation)) |
596 | (hash-indent . ,(- (match-end 0) (match-beginning 0))) |
597 | (point . ,(match-beginning 0)))))) |
598 | (when (bobp) (return-from haml-parse-multiline-attr-hash)) |
599 | (forward-line -1) |
600 | (unless (haml-unclosed-attr-hash-p) |
601 | (return-from haml-parse-multiline-attr-hash)))))) |
602 | |
603 | (defun* haml-unclosed-attr-hash-p () |
604 | "Return t if this line has an unclosed attribute hash, new or old." |
605 | (save-excursion |
606 | (end-of-line) |
607 | (when (eq (char-before) ?,) (return-from haml-unclosed-attr-hash-p t)) |
608 | (re-search-backward "(\\|^") |
609 | (haml-move "(") |
610 | (haml-parse-new-attr-hash))) |
611 | |
612 | (defun* haml-parse-new-attr-hash (&optional (fn (lambda (type beg end) ()))) |
613 | "Parse a new-style attribute hash on this line, and returns |
614 | t if it's not finished on the current line. |
615 | |
616 | FN should take three parameters: TYPE, BEG, and END. |
617 | TYPE is the type of text parsed ('name or 'value) |
618 | and BEG and END delimit that text in the buffer." |
619 | (let ((eol (save-excursion (end-of-line) (point)))) |
620 | (while (not (haml-move ")")) |
621 | (haml-move " *") |
622 | (unless (haml-move "[a-z0-9_:\\-]+") |
623 | (return-from haml-parse-new-attr-hash (haml-move " *$"))) |
624 | (funcall fn 'name (match-beginning 0) (match-end 0)) |
625 | (haml-move " *") |
626 | (when (haml-move "=") |
627 | (haml-move " *") |
628 | (unless (looking-at "[\"'@a-z]") (return-from haml-parse-new-attr-hash)) |
629 | (let ((beg (point))) |
630 | (haml-limited-forward-sexp eol) |
631 | (funcall fn 'value beg (point))) |
632 | (haml-move " *"))) |
633 | nil)) |
634 | |
635 | (defun haml-compute-indentation () |
636 | "Calculate the maximum sensible indentation for the current line." |
637 | (save-excursion |
638 | (beginning-of-line) |
639 | (if (bobp) (list 0 nil) |
640 | (haml-forward-through-whitespace t) |
641 | (let ((indent (funcall haml-indent-function))) |
642 | (cond |
643 | ((consp indent) indent) |
644 | ((integerp indent) (list indent t)) |
645 | (indent (list (+ (current-indentation) haml-indent-offset) nil)) |
646 | (t (list (current-indentation) nil))))))) |
647 | |
648 | (defun haml-indent-region (start end) |
649 | "Indent each nonblank line in the region. |
650 | This is done by indenting the first line based on |
651 | `haml-compute-indentation' and preserving the relative |
652 | indentation of the rest of the region. START and END specify the |
653 | region to indent. |
654 | |
655 | If this command is used multiple times in a row, it will cycle |
656 | between possible indentations." |
657 | (save-excursion |
658 | (goto-char end) |
659 | (setq end (point-marker)) |
660 | (goto-char start) |
661 | (let (this-line-column current-column |
662 | (next-line-column |
663 | (if (and (equal last-command this-command) (/= (current-indentation) 0)) |
664 | (* (/ (- (current-indentation) 1) haml-indent-offset) haml-indent-offset) |
665 | (car (haml-compute-indentation))))) |
666 | (while (< (point) end) |
667 | (setq this-line-column next-line-column |
668 | current-column (current-indentation)) |
669 | ;; Delete whitespace chars at beginning of line |
670 | (delete-horizontal-space) |
671 | (unless (eolp) |
672 | (setq next-line-column (save-excursion |
673 | (loop do (forward-line 1) |
674 | while (and (not (eobp)) (looking-at "^[ \t]*$"))) |
675 | (+ this-line-column |
676 | (- (current-indentation) current-column)))) |
677 | ;; Don't indent an empty line |
678 | (unless (eolp) (indent-to this-line-column))) |
679 | (forward-line 1))) |
680 | (move-marker end nil))) |
681 | |
682 | (defun haml-indent-line () |
683 | "Indent the current line. |
684 | The first time this command is used, the line will be indented to the |
685 | maximum sensible indentation. Each immediately subsequent usage will |
686 | back-dent the line by `haml-indent-offset' spaces. On reaching column |
687 | 0, it will cycle back to the maximum sensible indentation." |
688 | (interactive "*") |
689 | (let ((ci (current-indentation)) |
690 | (cc (current-column))) |
691 | (destructuring-bind (need strict) (haml-compute-indentation) |
692 | (save-excursion |
693 | (beginning-of-line) |
694 | (delete-horizontal-space) |
695 | (if (and (not strict) (equal last-command this-command) (/= ci 0)) |
696 | (indent-to (* (/ (- ci 1) haml-indent-offset) haml-indent-offset)) |
697 | (indent-to need)))) |
698 | (when (< (current-column) (current-indentation)) |
699 | (forward-to-indentation 0)))) |
700 | |
701 | (defun haml-reindent-region-by (n) |
702 | "Add N spaces to the beginning of each line in the region. |
703 | If N is negative, will remove the spaces instead. Assumes all |
704 | lines in the region have indentation >= that of the first line." |
705 | (let ((ci (current-indentation))) |
706 | (save-excursion |
707 | (while (re-search-forward (concat "^" (make-string ci ?\s)) (mark) t) |
708 | (replace-match (make-string (max 0 (+ ci n)) ?\s)))))) |
709 | |
710 | (defun haml-electric-backspace (arg) |
711 | "Delete characters or back-dent the current line. |
712 | If invoked following only whitespace on a line, will back-dent |
713 | the line and all nested lines to the immediately previous |
714 | multiple of `haml-indent-offset' spaces. With ARG, do it that |
715 | many times. |
716 | |
717 | Set `haml-backspace-backdents-nesting' to nil to just back-dent |
718 | the current line." |
719 | (interactive "*p") |
720 | (if (or (/= (current-indentation) (current-column)) |
721 | (bolp) |
722 | (looking-at "^[ \t]+$")) |
723 | (backward-delete-char arg) |
724 | (save-excursion |
725 | (let ((ci (current-column))) |
726 | (beginning-of-line) |
727 | (if haml-backspace-backdents-nesting |
728 | (haml-mark-sexp-but-not-next-line) |
729 | (set-mark (save-excursion (end-of-line) (point)))) |
730 | (haml-reindent-region-by (* (- arg) haml-indent-offset)) |
731 | (back-to-indentation) |
732 | (pop-mark))))) |
733 | |
734 | (defun haml-kill-line-and-indent () |
735 | "Kill the current line, and re-indent all lines nested beneath it." |
736 | (interactive) |
737 | (beginning-of-line) |
738 | (haml-mark-sexp-but-not-next-line) |
739 | (kill-line 1) |
740 | (haml-reindent-region-by (* -1 haml-indent-offset))) |
741 | |
742 | (defun haml-indent-string () |
743 | "Return the indentation string for `haml-indent-offset'." |
744 | (mapconcat 'identity (make-list haml-indent-offset " ") "")) |
745 | |
746 | ;;;###autoload |
747 | (add-to-list 'auto-mode-alist '("\\.haml$" . haml-mode)) |
748 | |
749 | ;; Setup/Activation |
750 | (provide 'haml-mode) |
751 | ;;; haml-mode.el ends here |