drnic-haml 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (190) hide show
  1. data/.yardopts +5 -0
  2. data/CONTRIBUTING +4 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +347 -0
  5. data/REVISION +1 -0
  6. data/Rakefile +371 -0
  7. data/VERSION +1 -0
  8. data/VERSION_NAME +1 -0
  9. data/bin/css2sass +7 -0
  10. data/bin/haml +9 -0
  11. data/bin/html2haml +7 -0
  12. data/bin/sass +8 -0
  13. data/extra/haml-mode.el +663 -0
  14. data/extra/sass-mode.el +205 -0
  15. data/extra/update_watch.rb +13 -0
  16. data/init.rb +8 -0
  17. data/lib/haml.rb +40 -0
  18. data/lib/haml/buffer.rb +307 -0
  19. data/lib/haml/engine.rb +301 -0
  20. data/lib/haml/error.rb +22 -0
  21. data/lib/haml/exec.rb +470 -0
  22. data/lib/haml/filters.rb +341 -0
  23. data/lib/haml/helpers.rb +560 -0
  24. data/lib/haml/helpers/action_view_extensions.rb +40 -0
  25. data/lib/haml/helpers/action_view_mods.rb +176 -0
  26. data/lib/haml/herb.rb +96 -0
  27. data/lib/haml/html.rb +308 -0
  28. data/lib/haml/precompiler.rb +997 -0
  29. data/lib/haml/shared.rb +78 -0
  30. data/lib/haml/template.rb +51 -0
  31. data/lib/haml/template/patch.rb +58 -0
  32. data/lib/haml/template/plugin.rb +71 -0
  33. data/lib/haml/util.rb +244 -0
  34. data/lib/haml/version.rb +64 -0
  35. data/lib/sass.rb +24 -0
  36. data/lib/sass/css.rb +423 -0
  37. data/lib/sass/engine.rb +491 -0
  38. data/lib/sass/environment.rb +79 -0
  39. data/lib/sass/error.rb +162 -0
  40. data/lib/sass/files.rb +133 -0
  41. data/lib/sass/plugin.rb +170 -0
  42. data/lib/sass/plugin/merb.rb +57 -0
  43. data/lib/sass/plugin/rails.rb +23 -0
  44. data/lib/sass/repl.rb +58 -0
  45. data/lib/sass/script.rb +55 -0
  46. data/lib/sass/script/bool.rb +17 -0
  47. data/lib/sass/script/color.rb +183 -0
  48. data/lib/sass/script/funcall.rb +50 -0
  49. data/lib/sass/script/functions.rb +199 -0
  50. data/lib/sass/script/lexer.rb +191 -0
  51. data/lib/sass/script/literal.rb +177 -0
  52. data/lib/sass/script/node.rb +14 -0
  53. data/lib/sass/script/number.rb +381 -0
  54. data/lib/sass/script/operation.rb +45 -0
  55. data/lib/sass/script/parser.rb +222 -0
  56. data/lib/sass/script/string.rb +12 -0
  57. data/lib/sass/script/unary_operation.rb +34 -0
  58. data/lib/sass/script/variable.rb +31 -0
  59. data/lib/sass/tree/comment_node.rb +84 -0
  60. data/lib/sass/tree/debug_node.rb +30 -0
  61. data/lib/sass/tree/directive_node.rb +70 -0
  62. data/lib/sass/tree/for_node.rb +48 -0
  63. data/lib/sass/tree/if_node.rb +54 -0
  64. data/lib/sass/tree/import_node.rb +69 -0
  65. data/lib/sass/tree/mixin_def_node.rb +29 -0
  66. data/lib/sass/tree/mixin_node.rb +48 -0
  67. data/lib/sass/tree/node.rb +252 -0
  68. data/lib/sass/tree/prop_node.rb +106 -0
  69. data/lib/sass/tree/root_node.rb +56 -0
  70. data/lib/sass/tree/rule_node.rb +220 -0
  71. data/lib/sass/tree/variable_node.rb +34 -0
  72. data/lib/sass/tree/while_node.rb +31 -0
  73. data/rails/init.rb +1 -0
  74. data/test/benchmark.rb +99 -0
  75. data/test/haml/engine_test.rb +1129 -0
  76. data/test/haml/helper_test.rb +282 -0
  77. data/test/haml/html2haml_test.rb +258 -0
  78. data/test/haml/markaby/standard.mab +52 -0
  79. data/test/haml/mocks/article.rb +6 -0
  80. data/test/haml/results/content_for_layout.xhtml +12 -0
  81. data/test/haml/results/eval_suppressed.xhtml +9 -0
  82. data/test/haml/results/filters.xhtml +62 -0
  83. data/test/haml/results/helpers.xhtml +93 -0
  84. data/test/haml/results/helpful.xhtml +10 -0
  85. data/test/haml/results/just_stuff.xhtml +68 -0
  86. data/test/haml/results/list.xhtml +12 -0
  87. data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
  88. data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
  89. data/test/haml/results/original_engine.xhtml +20 -0
  90. data/test/haml/results/partial_layout.xhtml +5 -0
  91. data/test/haml/results/partials.xhtml +21 -0
  92. data/test/haml/results/render_layout.xhtml +3 -0
  93. data/test/haml/results/silent_script.xhtml +74 -0
  94. data/test/haml/results/standard.xhtml +162 -0
  95. data/test/haml/results/tag_parsing.xhtml +23 -0
  96. data/test/haml/results/very_basic.xhtml +5 -0
  97. data/test/haml/results/whitespace_handling.xhtml +89 -0
  98. data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
  99. data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
  100. data/test/haml/rhtml/action_view.rhtml +62 -0
  101. data/test/haml/rhtml/standard.rhtml +54 -0
  102. data/test/haml/spec_test.rb +44 -0
  103. data/test/haml/template_test.rb +217 -0
  104. data/test/haml/templates/_av_partial_1.haml +9 -0
  105. data/test/haml/templates/_av_partial_1_ugly.haml +9 -0
  106. data/test/haml/templates/_av_partial_2.haml +5 -0
  107. data/test/haml/templates/_av_partial_2_ugly.haml +5 -0
  108. data/test/haml/templates/_layout.erb +3 -0
  109. data/test/haml/templates/_layout_for_partial.haml +3 -0
  110. data/test/haml/templates/_partial.haml +8 -0
  111. data/test/haml/templates/_text_area.haml +3 -0
  112. data/test/haml/templates/action_view.haml +47 -0
  113. data/test/haml/templates/action_view_ugly.haml +47 -0
  114. data/test/haml/templates/breakage.haml +8 -0
  115. data/test/haml/templates/content_for_layout.haml +8 -0
  116. data/test/haml/templates/eval_suppressed.haml +11 -0
  117. data/test/haml/templates/filters.haml +66 -0
  118. data/test/haml/templates/helpers.haml +95 -0
  119. data/test/haml/templates/helpful.haml +11 -0
  120. data/test/haml/templates/just_stuff.haml +83 -0
  121. data/test/haml/templates/list.haml +12 -0
  122. data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
  123. data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
  124. data/test/haml/templates/original_engine.haml +17 -0
  125. data/test/haml/templates/partial_layout.haml +3 -0
  126. data/test/haml/templates/partialize.haml +1 -0
  127. data/test/haml/templates/partials.haml +12 -0
  128. data/test/haml/templates/render_layout.haml +2 -0
  129. data/test/haml/templates/silent_script.haml +40 -0
  130. data/test/haml/templates/standard.haml +42 -0
  131. data/test/haml/templates/standard_ugly.haml +42 -0
  132. data/test/haml/templates/tag_parsing.haml +21 -0
  133. data/test/haml/templates/very_basic.haml +4 -0
  134. data/test/haml/templates/whitespace_handling.haml +87 -0
  135. data/test/haml/util_test.rb +92 -0
  136. data/test/linked_rails.rb +12 -0
  137. data/test/sass/css2sass_test.rb +294 -0
  138. data/test/sass/engine_test.rb +956 -0
  139. data/test/sass/functions_test.rb +126 -0
  140. data/test/sass/more_results/more1.css +9 -0
  141. data/test/sass/more_results/more1_with_line_comments.css +26 -0
  142. data/test/sass/more_results/more_import.css +29 -0
  143. data/test/sass/more_templates/_more_partial.sass +2 -0
  144. data/test/sass/more_templates/more1.sass +23 -0
  145. data/test/sass/more_templates/more_import.sass +11 -0
  146. data/test/sass/plugin_test.rb +229 -0
  147. data/test/sass/results/alt.css +4 -0
  148. data/test/sass/results/basic.css +9 -0
  149. data/test/sass/results/compact.css +5 -0
  150. data/test/sass/results/complex.css +87 -0
  151. data/test/sass/results/compressed.css +1 -0
  152. data/test/sass/results/expanded.css +19 -0
  153. data/test/sass/results/import.css +29 -0
  154. data/test/sass/results/line_numbers.css +49 -0
  155. data/test/sass/results/mixins.css +95 -0
  156. data/test/sass/results/multiline.css +24 -0
  157. data/test/sass/results/nested.css +22 -0
  158. data/test/sass/results/parent_ref.css +13 -0
  159. data/test/sass/results/script.css +16 -0
  160. data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
  161. data/test/sass/results/subdir/subdir.css +3 -0
  162. data/test/sass/results/units.css +11 -0
  163. data/test/sass/script_test.rb +261 -0
  164. data/test/sass/templates/_partial.sass +2 -0
  165. data/test/sass/templates/alt.sass +16 -0
  166. data/test/sass/templates/basic.sass +23 -0
  167. data/test/sass/templates/bork1.sass +2 -0
  168. data/test/sass/templates/bork2.sass +2 -0
  169. data/test/sass/templates/bork3.sass +2 -0
  170. data/test/sass/templates/compact.sass +17 -0
  171. data/test/sass/templates/complex.sass +307 -0
  172. data/test/sass/templates/compressed.sass +15 -0
  173. data/test/sass/templates/expanded.sass +17 -0
  174. data/test/sass/templates/import.sass +11 -0
  175. data/test/sass/templates/importee.sass +19 -0
  176. data/test/sass/templates/line_numbers.sass +13 -0
  177. data/test/sass/templates/mixins.sass +76 -0
  178. data/test/sass/templates/multiline.sass +20 -0
  179. data/test/sass/templates/nested.sass +25 -0
  180. data/test/sass/templates/nested_bork1.sass +2 -0
  181. data/test/sass/templates/nested_bork2.sass +2 -0
  182. data/test/sass/templates/nested_bork3.sass +2 -0
  183. data/test/sass/templates/parent_ref.sass +25 -0
  184. data/test/sass/templates/script.sass +101 -0
  185. data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
  186. data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
  187. data/test/sass/templates/subdir/subdir.sass +6 -0
  188. data/test/sass/templates/units.sass +11 -0
  189. data/test/test_helper.rb +44 -0
  190. metadata +298 -0
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 2.3.0
@@ -0,0 +1 @@
1
+ Bleeding Edge
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/haml'
4
+ require 'haml/exec'
5
+
6
+ opts = Haml::Exec::CSS2Sass.new(ARGV)
7
+ opts.parse!
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # The command line Haml parser.
3
+
4
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
5
+ require 'haml'
6
+ require 'haml/exec'
7
+
8
+ opts = Haml::Exec::Haml.new(ARGV)
9
+ opts.parse!
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/haml'
4
+ require 'haml/exec'
5
+
6
+ opts = Haml::Exec::HTML2Haml.new(ARGV)
7
+ opts.parse!
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # The command line Sass parser.
3
+
4
+ require File.dirname(__FILE__) + '/../lib/haml'
5
+ require 'haml/exec'
6
+
7
+ opts = Haml::Exec::Sass.new(ARGV)
8
+ opts.parse!
@@ -0,0 +1,663 @@
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.6
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
+ ;; User definable variables
29
+
30
+ (defgroup haml nil
31
+ "Support for the Haml template language."
32
+ :group 'languages
33
+ :prefix "haml-")
34
+
35
+ (defcustom haml-mode-hook nil
36
+ "Hook run when entering Haml mode."
37
+ :type 'hook
38
+ :group 'haml)
39
+
40
+ (defcustom haml-indent-offset 2
41
+ "Amount of offset per level of indentation."
42
+ :type 'integer
43
+ :group 'haml)
44
+
45
+ (defcustom haml-backspace-backdents-nesting t
46
+ "Non-nil to have `haml-electric-backspace' re-indent blocks of code.
47
+ This means that all code nested beneath the backspaced line is
48
+ re-indented along with the line itself."
49
+ :type 'boolean
50
+ :group 'haml)
51
+
52
+ (defface haml-tab-face
53
+ '((((class color)) (:background "hotpink"))
54
+ (t (:reverse-video t)))
55
+ "Face to use for highlighting tabs in Haml files."
56
+ :group 'faces
57
+ :group 'haml)
58
+
59
+ (defvar haml-indent-function 'haml-indent-p
60
+ "A function for checking if nesting is allowed.
61
+ This function should look at the current line and return t
62
+ if the next line could be nested within this line.
63
+
64
+ The function can also return a positive integer to indicate
65
+ a specific level to which the current line could be indented.")
66
+
67
+ (defconst haml-tag-beg-re
68
+ "^ *\\(?:[%\\.#][a-z0-9_:\\-]*\\)+\\(?:(.*)\\|{.*}\\|\\[.*\\]\\)*"
69
+ "A regexp matching the beginning of a Haml tag, through (), {}, and [].")
70
+
71
+ (defvar haml-block-openers
72
+ `(,(concat haml-tag-beg-re "[><]*[ \t]*$")
73
+ "^ *[&!]?[-=~].*do[ \t]*\\(|.*|[ \t]*\\)?$"
74
+ ,(concat "^ *[&!]?[-=~][ \t]*\\("
75
+ (regexp-opt '("if" "unless" "while" "until" "else"
76
+ "begin" "elsif" "rescue" "ensure" "when"))
77
+ "\\)")
78
+ "^ */\\(\\[.*\\]\\)?[ \t]*$"
79
+ "^ *-#"
80
+ "^ *:")
81
+ "A list of regexps that match lines of Haml that open blocks.
82
+ That is, a Haml line that can have text nested beneath it should
83
+ be matched by a regexp in this list.")
84
+
85
+ ;; Font lock
86
+
87
+ (defun haml-nested-regexp (re)
88
+ "Create a regexp to match a block starting with RE.
89
+ The line containing RE is matched, as well as all lines indented beneath it."
90
+ (concat "^\\( *\\)" re "\\(\n\\(?:\\(?:\\1 .*\\| *\\)\n\\)*\\(?:\\1 .*\\| *\\)?\\)?"))
91
+
92
+ (defconst haml-font-lock-keywords
93
+ `((,(haml-nested-regexp "\\(?:-#\\|/\\).*") 0 font-lock-comment-face)
94
+ (,(haml-nested-regexp ":\\w+") 0 font-lock-string-face)
95
+ (haml-highlight-interpolation 1 font-lock-variable-name-face prepend)
96
+ (haml-highlight-ruby-tag 1 font-lock-preprocessor-face)
97
+ (haml-highlight-ruby-script 1 font-lock-preprocessor-face)
98
+ ("^ *\\(\t\\)" 1 'haml-tab-face)
99
+ ("^!!!.*" 0 font-lock-constant-face)
100
+ ("| *$" 0 font-lock-string-face)))
101
+
102
+ (defconst haml-filter-re "^ *:\\w+")
103
+ (defconst haml-comment-re "^ *\\(?:-\\#\\|/\\)")
104
+
105
+ (defun haml-fontify-region-as-ruby (beg end)
106
+ "Use Ruby's font-lock variables to fontify the region between BEG and END."
107
+ (save-excursion
108
+ (save-match-data
109
+ (let ((font-lock-keywords ruby-font-lock-keywords)
110
+ (font-lock-syntactic-keywords ruby-font-lock-syntactic-keywords)
111
+ font-lock-keywords-only
112
+ font-lock-extend-region-functions
113
+ font-lock-keywords-case-fold-search)
114
+ ;; font-lock-fontify-region apparently isn't inclusive,
115
+ ;; so we have to move the beginning back one char
116
+ (font-lock-fontify-region (- beg 1) end)))))
117
+
118
+ (defun haml-highlight-ruby-script (limit)
119
+ "Highlight a Ruby script expression (-, =, or ~).
120
+ LIMIT works as it does in `re-search-forward'."
121
+ (when (re-search-forward "^ *\\(-\\|[&!]?[=~]\\) \\(.*\\)$" limit t)
122
+ (haml-fontify-region-as-ruby (match-beginning 2) (match-end 2))))
123
+
124
+ (defun haml-highlight-ruby-tag (limit)
125
+ "Highlight Ruby code within a Haml tag.
126
+ LIMIT works as it does in `re-search-forward'.
127
+
128
+ This highlights the tag attributes and object refs of the tag,
129
+ as well as the script expression (-, =, or ~) following the tag.
130
+
131
+ For example, this will highlight all of the following:
132
+ %p{:foo => 'bar'}
133
+ %p[@bar]
134
+ %p= 'baz'
135
+ %p{:foo => 'bar'}[@bar]= 'baz'"
136
+ (when (re-search-forward "^ *[%.#]" limit t)
137
+ (forward-char -1)
138
+
139
+ ;; Highlight tag, classes, and ids
140
+ (while (haml-move "\\([.#%]\\)[a-z0-9_:\\-]*")
141
+ (put-text-property (match-beginning 0) (match-end 0) 'face
142
+ (case (char-after (match-beginning 1))
143
+ (?% font-lock-function-name-face)
144
+ (?# font-lock-keyword-face)
145
+ (?. font-lock-type-face))))
146
+
147
+ (block loop
148
+ (while t
149
+ (let ((eol (save-excursion (end-of-line) (point))))
150
+ (case (char-after)
151
+ ;; Highlight obj refs
152
+ (?\[
153
+ (let ((beg (point)))
154
+ (haml-limited-forward-sexp eol)
155
+ (haml-fontify-region-as-ruby beg (point))))
156
+ ;; Highlight new attr hashes
157
+ (?\(
158
+ (forward-char 1)
159
+ (while
160
+ (and (haml-parse-new-attr-hash
161
+ (lambda (type beg end)
162
+ (case type
163
+ (name (put-text-property beg end 'face font-lock-constant-face))
164
+ (value (haml-fontify-region-as-ruby beg end)))))
165
+ (not (eobp)))
166
+ (forward-line 1)
167
+ (beginning-of-line)))
168
+ ;; Highlight old attr hashes
169
+ (?\{
170
+ (let ((beg (point)))
171
+ (haml-limited-forward-sexp eol)
172
+
173
+ ;; Check for multiline
174
+ (while (and (eolp) (eq (char-before) ?,) (not (eobp)))
175
+ (forward-line)
176
+ (let ((eol (save-excursion (end-of-line) (point))))
177
+ ;; If no sexps are closed,
178
+ ;; we're still continuing a multiline hash
179
+ (if (>= (car (parse-partial-sexp (point) eol)) 0)
180
+ (end-of-line)
181
+ ;; If sexps have been closed,
182
+ ;; set the point at the end of the total sexp
183
+ (goto-char beg)
184
+ (haml-limited-forward-sexp eol))))
185
+
186
+ (haml-fontify-region-as-ruby (+ 1 beg) (point))))
187
+ (t (return-from loop))))))
188
+
189
+ ;; Move past end chars
190
+ (when (looking-at "[<>&!]+") (goto-char (match-end 0)))
191
+ ;; Highlight script
192
+ (if (looking-at "\\([=~]\\) \\(.*\\)$")
193
+ (haml-fontify-region-as-ruby (match-beginning 2) (match-end 2))
194
+ ;; Give font-lock something to highlight
195
+ (forward-char -1)
196
+ (looking-at "\\(\\)"))
197
+ t))
198
+
199
+ (defun haml-move (re)
200
+ "Try matching and moving to the end of regular expression RE.
201
+ Returns non-nil if the expression was sucessfully matched."
202
+ (when (looking-at re)
203
+ (goto-char (match-end 0))
204
+ t))
205
+
206
+ (defun haml-highlight-interpolation (limit)
207
+ "Highlight Ruby interpolation (#{foo}).
208
+ LIMIT works as it does in `re-search-forward'."
209
+ (when (re-search-forward "\\(#{\\)" limit t)
210
+ (save-match-data
211
+ (forward-char -1)
212
+ (let ((beg (point)))
213
+ (haml-limited-forward-sexp limit)
214
+ (haml-fontify-region-as-ruby (+ 1 beg) (point)))
215
+
216
+ (when (eq (char-before) ?})
217
+ (put-text-property (- (point) 1) (point)
218
+ 'face font-lock-variable-name-face))
219
+ t)))
220
+
221
+ (defun haml-limited-forward-sexp (limit &optional arg)
222
+ "Move forward using `forward-sexp' or to LIMIT, whichever comes first.
223
+ With ARG, do it that many times."
224
+ (let (forward-sexp-function)
225
+ (condition-case err
226
+ (save-restriction
227
+ (narrow-to-region (point) limit)
228
+ (forward-sexp arg))
229
+ (scan-error
230
+ (unless (equal (nth 1 err) "Unbalanced parentheses")
231
+ (signal 'scan-error (cdr err)))
232
+ (goto-char limit)))))
233
+
234
+ (defun* haml-extend-region-filters-comments ()
235
+ "Extend the font-lock region to encompass filters and comments."
236
+ (let ((old-beg font-lock-beg)
237
+ (old-end font-lock-end))
238
+ (save-excursion
239
+ (goto-char font-lock-beg)
240
+ (beginning-of-line)
241
+ (unless (or (looking-at haml-filter-re)
242
+ (looking-at haml-comment-re))
243
+ (return-from haml-extend-region-filters-comments))
244
+ (setq font-lock-beg (point))
245
+ (haml-forward-sexp)
246
+ (beginning-of-line)
247
+ (setq font-lock-end (max font-lock-end (point))))
248
+ (or (/= old-beg font-lock-beg)
249
+ (/= old-end font-lock-end))))
250
+
251
+ (defun* haml-extend-region-multiline-hashes ()
252
+ "Extend the font-lock region to encompass multiline attribute hashes."
253
+ (let ((old-beg font-lock-beg)
254
+ (old-end font-lock-end))
255
+ (save-excursion
256
+ (goto-char font-lock-beg)
257
+ (let ((attr-props (haml-parse-multiline-attr-hash))
258
+ multiline-end)
259
+ (when attr-props
260
+ (setq font-lock-beg (cdr (assq 'point attr-props)))
261
+
262
+ (end-of-line)
263
+ ;; Move through multiline attrs
264
+ (when (eq (char-before) ?,)
265
+ (save-excursion
266
+ (while (progn (end-of-line)
267
+ (and (eq (char-before) ?,) (not (eobp))))
268
+ (forward-line))
269
+
270
+ (forward-line -1)
271
+ (end-of-line)
272
+ (setq multiline-end (point))))
273
+
274
+ (goto-char (+ (cdr (assq 'point attr-props))
275
+ (cdr (assq 'hash-indent attr-props))
276
+ -1))
277
+ (haml-limited-forward-sexp
278
+ (or multiline-end
279
+ (save-excursion (end-of-line) (point))))
280
+ (setq font-lock-end (max font-lock-end (point))))))
281
+ (or (/= old-beg font-lock-beg)
282
+ (/= old-end font-lock-end))))
283
+
284
+
285
+ ;; Mode setup
286
+
287
+ (defvar haml-mode-syntax-table
288
+ (let ((table (make-syntax-table)))
289
+ (modify-syntax-entry ?: "." table)
290
+ (modify-syntax-entry ?_ "w" table)
291
+ table)
292
+ "Syntax table in use in `haml-mode' buffers.")
293
+
294
+ (defvar haml-mode-map
295
+ (let ((map (make-sparse-keymap)))
296
+ (define-key map [backspace] 'haml-electric-backspace)
297
+ (define-key map "\C-?" 'haml-electric-backspace)
298
+ (define-key map "\C-c\C-f" 'haml-forward-sexp)
299
+ (define-key map "\C-c\C-b" 'haml-backward-sexp)
300
+ (define-key map "\C-c\C-u" 'haml-up-list)
301
+ (define-key map "\C-c\C-d" 'haml-down-list)
302
+ (define-key map "\C-c\C-k" 'haml-kill-line-and-indent)
303
+ (define-key map "\C-c\C-r" 'haml-output-region)
304
+ (define-key map "\C-c\C-l" 'haml-output-buffer)
305
+ map))
306
+
307
+ ;;;###autoload
308
+ (define-derived-mode haml-mode fundamental-mode "Haml"
309
+ "Major mode for editing Haml files.
310
+
311
+ \\{haml-mode-map}"
312
+ (set-syntax-table haml-mode-syntax-table)
313
+ (add-to-list 'font-lock-extend-region-functions 'haml-extend-region-filters-comments)
314
+ (add-to-list 'font-lock-extend-region-functions 'haml-extend-region-multiline-hashes)
315
+ (set (make-local-variable 'font-lock-multiline) t)
316
+ (set (make-local-variable 'indent-line-function) 'haml-indent-line)
317
+ (set (make-local-variable 'indent-region-function) 'haml-indent-region)
318
+ (set (make-local-variable 'parse-sexp-lookup-properties) t)
319
+ (setq comment-start "-#")
320
+ (setq indent-tabs-mode nil)
321
+ (setq font-lock-defaults '((haml-font-lock-keywords) t t)))
322
+
323
+ ;; Useful functions
324
+
325
+ (defun haml-comment-block ()
326
+ "Comment the current block of Haml code."
327
+ (interactive)
328
+ (save-excursion
329
+ (let ((indent (current-indentation)))
330
+ (back-to-indentation)
331
+ (insert "-#")
332
+ (newline)
333
+ (indent-to indent)
334
+ (beginning-of-line)
335
+ (haml-mark-sexp)
336
+ (haml-reindent-region-by haml-indent-offset))))
337
+
338
+ (defun haml-uncomment-block ()
339
+ "Uncomment the current block of Haml code."
340
+ (interactive)
341
+ (save-excursion
342
+ (beginning-of-line)
343
+ (while (not (looking-at haml-comment-re))
344
+ (haml-up-list)
345
+ (beginning-of-line))
346
+ (haml-mark-sexp)
347
+ (kill-line 1)
348
+ (haml-reindent-region-by (- haml-indent-offset))))
349
+
350
+ (defun haml-replace-region (start end)
351
+ "Replace the current block of Haml code with the HTML equivalent.
352
+ Called from a program, START and END specify the region to indent."
353
+ (interactive "r")
354
+ (save-excursion
355
+ (goto-char end)
356
+ (setq end (point-marker))
357
+ (goto-char start)
358
+ (let ((ci (current-indentation)))
359
+ (while (re-search-forward "^ +" end t)
360
+ (replace-match (make-string (- (current-indentation) ci) ? ))))
361
+ (shell-command-on-region start end "haml" "haml-output" t)))
362
+
363
+ (defun haml-output-region (start end)
364
+ "Displays the HTML output for the current block of Haml code.
365
+ Called from a program, START and END specify the region to indent."
366
+ (interactive "r")
367
+ (kill-new (buffer-substring start end))
368
+ (with-temp-buffer
369
+ (yank)
370
+ (haml-indent-region (point-min) (point-max))
371
+ (shell-command-on-region (point-min) (point-max) "haml" "haml-output")))
372
+
373
+ (defun haml-output-buffer ()
374
+ "Displays the HTML output for entire buffer."
375
+ (interactive)
376
+ (haml-output-region (point-min) (point-max)))
377
+
378
+ ;; Navigation
379
+
380
+ (defun haml-forward-through-whitespace (&optional backward)
381
+ "Move the point forward through any whitespace.
382
+ The point will move forward at least one line, until it reaches
383
+ either the end of the buffer or a line with no whitespace.
384
+
385
+ If BACKWARD is non-nil, move the point backward instead."
386
+ (let ((arg (if backward -1 1))
387
+ (endp (if backward 'bobp 'eobp)))
388
+ (loop do (forward-line arg)
389
+ while (and (not (funcall endp))
390
+ (looking-at "^[ \t]*$")))))
391
+
392
+ (defun haml-at-indent-p ()
393
+ "Return non-nil if the point is before any text on the line."
394
+ (let ((opoint (point)))
395
+ (save-excursion
396
+ (back-to-indentation)
397
+ (>= (point) opoint))))
398
+
399
+ (defun haml-forward-sexp (&optional arg)
400
+ "Move forward across one nested expression.
401
+ With ARG, do it that many times. Negative arg -N means move
402
+ backward across N balanced expressions.
403
+
404
+ A sexp in Haml is defined as a line of Haml code as well as any
405
+ lines nested beneath it."
406
+ (interactive "p")
407
+ (or arg (setq arg 1))
408
+ (if (and (< arg 0) (not (haml-at-indent-p)))
409
+ (back-to-indentation)
410
+ (while (/= arg 0)
411
+ (let ((indent (current-indentation)))
412
+ (loop do (haml-forward-through-whitespace (< arg 0))
413
+ while (and (not (eobp))
414
+ (not (bobp))
415
+ (> (current-indentation) indent)))
416
+ (back-to-indentation)
417
+ (setq arg (+ arg (if (> arg 0) -1 1)))))))
418
+
419
+ (defun haml-backward-sexp (&optional arg)
420
+ "Move backward across one nested expression.
421
+ With ARG, do it that many times. Negative arg -N means move
422
+ forward across N balanced expressions.
423
+
424
+ A sexp in Haml is defined as a line of Haml code as well as any
425
+ lines nested beneath it."
426
+ (interactive "p")
427
+ (haml-forward-sexp (if arg (- arg) -1)))
428
+
429
+ (defun haml-up-list (&optional arg)
430
+ "Move out of one level of nesting.
431
+ With ARG, do this that many times."
432
+ (interactive "p")
433
+ (or arg (setq arg 1))
434
+ (while (> arg 0)
435
+ (let ((indent (current-indentation)))
436
+ (loop do (haml-forward-through-whitespace t)
437
+ while (and (not (bobp))
438
+ (>= (current-indentation) indent)))
439
+ (setq arg (- arg 1))))
440
+ (back-to-indentation))
441
+
442
+ (defun haml-down-list (&optional arg)
443
+ "Move down one level of nesting.
444
+ With ARG, do this that many times."
445
+ (interactive "p")
446
+ (or arg (setq arg 1))
447
+ (while (> arg 0)
448
+ (let ((indent (current-indentation)))
449
+ (haml-forward-through-whitespace)
450
+ (when (<= (current-indentation) indent)
451
+ (haml-forward-through-whitespace t)
452
+ (back-to-indentation)
453
+ (error "Nothing is nested beneath this line"))
454
+ (setq arg (- arg 1))))
455
+ (back-to-indentation))
456
+
457
+ (defun haml-mark-sexp ()
458
+ "Mark the next Haml block."
459
+ (let ((forward-sexp-function 'haml-forward-sexp))
460
+ (mark-sexp)))
461
+
462
+ (defun haml-mark-sexp-but-not-next-line ()
463
+ "Mark the next Haml block, but not the next line.
464
+ Put the mark at the end of the last line of the sexp rather than
465
+ the first non-whitespace character of the next line."
466
+ (haml-mark-sexp)
467
+ (set-mark
468
+ (save-excursion
469
+ (goto-char (mark))
470
+ (forward-line -1)
471
+ (end-of-line)
472
+ (point))))
473
+
474
+ ;; Indentation and electric keys
475
+
476
+ (defun* haml-indent-p ()
477
+ "Returns t if the current line can have lines nested beneath it."
478
+ (let ((attr-props (haml-parse-multiline-attr-hash)))
479
+ (when attr-props
480
+ (return-from haml-indent-p
481
+ (if (haml-unclosed-attr-hash-p) (cdr (assq 'hash-indent attr-props))
482
+ (list (+ (cdr (assq 'indent attr-props)) haml-indent-offset) nil)))))
483
+ (loop for opener in haml-block-openers
484
+ if (looking-at opener) return t
485
+ finally return nil))
486
+
487
+ (defun* haml-parse-multiline-attr-hash ()
488
+ "Parses a multiline attribute hash, and returns
489
+ an alist with the following keys:
490
+
491
+ INDENT is the indentation of the line beginning the hash.
492
+
493
+ HASH-INDENT is the indentation of the first character
494
+ within the attribute hash.
495
+
496
+ POINT is the character position at the beginning of the line
497
+ beginning the hash."
498
+ (save-excursion
499
+ (while t
500
+ (beginning-of-line)
501
+ (if (looking-at (concat haml-tag-beg-re "\\([{(]\\)"))
502
+ (progn
503
+ (goto-char (- (match-end 0) 1))
504
+ (haml-limited-forward-sexp (save-excursion (end-of-line) (point)))
505
+ (return-from haml-parse-multiline-attr-hash
506
+ (when (or (string-equal (match-string 1) "(") (eq (char-before) ?,))
507
+ `((indent . ,(current-indentation))
508
+ (hash-indent . ,(- (match-end 0) (match-beginning 0)))
509
+ (point . ,(match-beginning 0))))))
510
+ (when (bobp) (return-from haml-parse-multiline-attr-hash))
511
+ (forward-line -1)
512
+ (unless (haml-unclosed-attr-hash-p)
513
+ (return-from haml-parse-multiline-attr-hash))))))
514
+
515
+ (defun* haml-unclosed-attr-hash-p ()
516
+ "Return t if this line has an unclosed attribute hash, new or old."
517
+ (save-excursion
518
+ (end-of-line)
519
+ (when (eq (char-before) ?,) (return-from haml-unclosed-attr-hash-p t))
520
+ (re-search-backward "(\\|^")
521
+ (haml-move "(")
522
+ (haml-parse-new-attr-hash)))
523
+
524
+ (defun* haml-parse-new-attr-hash (&optional (fn (lambda (type beg end) ())))
525
+ "Parse a new-style attribute hash on this line, and returns
526
+ t if it's not finished on the current line.
527
+
528
+ FN should take three parameters: TYPE, BEG, and END.
529
+ TYPE is the type of text parsed ('name or 'value)
530
+ and BEG and END delimit that text in the buffer."
531
+ (let ((eol (save-excursion (end-of-line) (point))))
532
+ (while (not (haml-move ")"))
533
+ (haml-move " *")
534
+ (unless (haml-move "[a-z0-9_:\\-]+")
535
+ (return-from haml-parse-new-attr-hash (haml-move " *$")))
536
+ (funcall fn 'name (match-beginning 0) (match-end 0))
537
+ (haml-move " *")
538
+ (when (haml-move "=")
539
+ (haml-move " *")
540
+ (unless (looking-at "[\"'@a-z]") (return-from haml-parse-new-attr-hash))
541
+ (let ((beg (point)))
542
+ (haml-limited-forward-sexp eol)
543
+ (funcall fn 'value beg (point)))
544
+ (haml-move " *")))
545
+ nil))
546
+
547
+ (defun haml-compute-indentation ()
548
+ "Calculate the maximum sensible indentation for the current line."
549
+ (save-excursion
550
+ (beginning-of-line)
551
+ (if (bobp) (list 0 nil)
552
+ (haml-forward-through-whitespace t)
553
+ (let ((indent (funcall haml-indent-function)))
554
+ (cond
555
+ ((consp indent) indent)
556
+ ((integerp indent) (list indent t))
557
+ (indent (list (+ (current-indentation) haml-indent-offset) nil))
558
+ (t (list (current-indentation) nil)))))))
559
+
560
+ (defun haml-indent-region (start end)
561
+ "Indent each nonblank line in the region.
562
+ This is done by indenting the first line based on
563
+ `haml-compute-indentation' and preserving the relative
564
+ indentation of the rest of the region. START and END specify the
565
+ region to indent.
566
+
567
+ If this command is used multiple times in a row, it will cycle
568
+ between possible indentations."
569
+ (save-excursion
570
+ (goto-char end)
571
+ (setq end (point-marker))
572
+ (goto-char start)
573
+ (let (this-line-column current-column
574
+ (next-line-column
575
+ (if (and (equal last-command this-command) (/= (current-indentation) 0))
576
+ (* (/ (- (current-indentation) 1) haml-indent-offset) haml-indent-offset)
577
+ (car (haml-compute-indentation)))))
578
+ (while (< (point) end)
579
+ (setq this-line-column next-line-column
580
+ current-column (current-indentation))
581
+ ;; Delete whitespace chars at beginning of line
582
+ (delete-horizontal-space)
583
+ (unless (eolp)
584
+ (setq next-line-column (save-excursion
585
+ (loop do (forward-line 1)
586
+ while (and (not (eobp)) (looking-at "^[ \t]*$")))
587
+ (+ this-line-column
588
+ (- (current-indentation) current-column))))
589
+ ;; Don't indent an empty line
590
+ (unless (eolp) (indent-to this-line-column)))
591
+ (forward-line 1)))
592
+ (move-marker end nil)))
593
+
594
+ (defun haml-indent-line ()
595
+ "Indent the current line.
596
+ The first time this command is used, the line will be indented to the
597
+ maximum sensible indentation. Each immediately subsequent usage will
598
+ back-dent the line by `haml-indent-offset' spaces. On reaching column
599
+ 0, it will cycle back to the maximum sensible indentation."
600
+ (interactive "*")
601
+ (let ((ci (current-indentation))
602
+ (cc (current-column)))
603
+ (destructuring-bind (need strict) (haml-compute-indentation)
604
+ (save-excursion
605
+ (beginning-of-line)
606
+ (delete-horizontal-space)
607
+ (if (and (not strict) (equal last-command this-command) (/= ci 0))
608
+ (indent-to (* (/ (- ci 1) haml-indent-offset) haml-indent-offset))
609
+ (indent-to need))))
610
+ (when (< (current-column) (current-indentation))
611
+ (forward-to-indentation 0))))
612
+
613
+ (defun haml-reindent-region-by (n)
614
+ "Add N spaces to the beginning of each line in the region.
615
+ If N is negative, will remove the spaces instead. Assumes all
616
+ lines in the region have indentation >= that of the first line."
617
+ (let ((ci (current-indentation)))
618
+ (save-excursion
619
+ (while (re-search-forward (concat "^" (make-string ci ?\s)) (mark) t)
620
+ (replace-match (make-string (max 0 (+ ci n)) ?\s))))))
621
+
622
+ (defun haml-electric-backspace (arg)
623
+ "Delete characters or back-dent the current line.
624
+ If invoked following only whitespace on a line, will back-dent
625
+ the line and all nested lines to the immediately previous
626
+ multiple of `haml-indent-offset' spaces. With ARG, do it that
627
+ many times.
628
+
629
+ Set `haml-backspace-backdents-nesting' to nil to just back-dent
630
+ the current line."
631
+ (interactive "*p")
632
+ (if (or (/= (current-indentation) (current-column))
633
+ (bolp)
634
+ (looking-at "^[ \t]+$"))
635
+ (backward-delete-char arg)
636
+ (save-excursion
637
+ (let ((ci (current-column)))
638
+ (beginning-of-line)
639
+ (if haml-backspace-backdents-nesting
640
+ (haml-mark-sexp-but-not-next-line)
641
+ (set-mark (save-excursion (end-of-line) (point))))
642
+ (haml-reindent-region-by (* (- arg) haml-indent-offset))
643
+ (back-to-indentation)
644
+ (pop-mark)))))
645
+
646
+ (defun haml-kill-line-and-indent ()
647
+ "Kill the current line, and re-indent all lines nested beneath it."
648
+ (interactive)
649
+ (beginning-of-line)
650
+ (haml-mark-sexp-but-not-next-line)
651
+ (kill-line 1)
652
+ (haml-reindent-region-by (* -1 haml-indent-offset)))
653
+
654
+ (defun haml-indent-string ()
655
+ "Return the indentation string for `haml-indent-offset'."
656
+ (mapconcat 'identity (make-list haml-indent-offset " ") ""))
657
+
658
+ ;;;###autoload
659
+ (add-to-list 'auto-mode-alist '("\\.haml$" . haml-mode))
660
+
661
+ ;; Setup/Activation
662
+ (provide 'haml-mode)
663
+ ;;; haml-mode.el ends here