-
Notifications
You must be signed in to change notification settings - Fork 1
/
sass-mode.el
207 lines (173 loc) · 7.16 KB
/
sass-mode.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
;;; sass-mode.el --- Major mode for editing Sass files
;; Copyright (c) 2007, 2008 Nathan Weizenbaum
;; Author: Nathan Weizenbaum
;; URL: http://github.com/nex3/haml/tree/master
;; Version: 3.0.13
;; Created: 2007-03-15
;; By: Nathan Weizenbaum
;; Keywords: markup, language, css
;; Package-Requires: ((haml-mode "3.0.13"))
;;; Commentary:
;; Because Sass's indentation schema is similar
;; to that of YAML and Python, many indentation-related
;; functions are similar to those in yaml-mode and python-mode.
;; To install, save this on your load path and add the following to
;; your .emacs file:
;;
;; (require 'sass-mode)
;;; Code:
(require 'haml-mode)
;; User definable variables
(defgroup sass nil
"Support for the Sass template language."
:group 'languages
:prefix "sass-")
(defcustom sass-mode-hook nil
"Hook run when entering Sass mode."
:type 'hook
:group 'sass)
(defcustom sass-indent-offset 2
"Amount of offset per level of indentation."
:type 'integer
:group 'sass)
(defvar sass-non-block-openers
'("^.*,$" ;; Continued selectors
"^ *@\\(extend\\|debug\\|warn\\|include\\|import\\)" ;; Single-line mixins
"^ *[$!]" ;; Variables
)
"A list of regexps that match lines of Sass that couldn't have
text nested beneath them.")
;; Font lock
(defconst sass-selector-font-lock-keywords
'(;; Attribute selectors (e.g. p[foo=bar])
("\\[\\([^]=]+\\)" (1 font-lock-variable-name-face)
("[~|$^*]?=\\([^]=]+\\)" nil nil (1 font-lock-string-face)))
("&" 0 font-lock-constant-face)
("\\.\\w+" 0 font-lock-type-face)
("#\\w+" 0 font-lock-keyword-face)
;; Pseudo-selectors, optionally with arguments (e.g. :first, :nth-child(12))
("\\(::?\\w+\\)" (1 font-lock-function-name-face)
("(\\([^)]+\\))" nil nil (1 font-lock-string-face)))))
(defconst sass-script-font-lock-keywords
`(("\"\\([^\"\\\\]\\|\\\\.\\)*\"" 0 font-lock-string-face)
("!\\(\\w\\|_\\)+" 0 font-lock-variable-name-face)
("#[0-9a-fA-F]\\{0,6\\}" 0 font-lock-preprocessor-face)
(,(regexp-opt
'("true" "false" "black" "silver" "gray" "white" "maroon" "red"
"purple" "fuchsia" "green" "lime" "olive" "yellow" "navy"
"blue" "teal" "aqua"))
0 font-lock-constant-face)
(,(regexp-opt '("and" "or" "not")) 0 font-lock-keyword-face)))
(defconst sass-syntax-table
(let ((st (make-syntax-table)))
(modify-syntax-entry ?- "w" st)
(modify-syntax-entry ?_ "w" st)
st))
(defconst sass-script-syntax-table
(let ((st (make-syntax-table sass-syntax-table)))
(modify-syntax-entry ?- "." st)
st))
(defconst sass-font-lock-keywords
'((sass-highlight-line 1 nil nil t)))
(defconst sass-line-keywords
'(("@\\(\\w+\\)" 0 font-lock-keyword-face sass-highlight-directive)
("/[/*].*" 0 font-lock-comment-face)
("[=+]\\w+" 0 font-lock-function-name-face sass-highlight-script-after-match)
("!\\w+" 0 font-lock-variable-name-face sass-highlight-script-after-match)
(":\\w+" 0 font-lock-variable-name-face)
("\\w+\s*:" 0 font-lock-variable-name-face)
("\\(\\w+\\)\s*=" 1 font-lock-variable-name-face sass-highlight-script-after-match)
("\\(:\\w+\\)\s*=" 1 font-lock-variable-name-face sass-highlight-script-after-match)
(".*" sass-highlight-selector))
"A list of full-line Sass syntax to highlight, used by `sass-highlight-line'.
Each item is either of the form (REGEXP SUBEXP FACE), (REGEXP FN),
or (REGEXP SUBEXP FACE FN). Each REGEXP is run successively on the
beginning of non-whitespace on the current line until one matches.
If it has SUBEXP and FACE, then SUBEXP is highlighted using FACE.
If it has FN, FN is run.")
(defun sass-highlight-line (limit)
"Highlight a single line using some Sass single-line syntax.
This syntax is taken from `sass-line-keywords'.
LIMIT is the limit of the search."
(save-match-data
(when (re-search-forward "^ *\\(.+\\)$" limit t)
(goto-char (match-beginning 1))
(dolist (keyword sass-line-keywords)
(destructuring-bind (keyword subexp-or-fn &optional face fn) keyword
(when (looking-at keyword)
(if (integerp subexp-or-fn)
(put-text-property (match-beginning subexp-or-fn)
(match-end subexp-or-fn)
'face face)
(setq fn subexp-or-fn))
(when fn (funcall fn))
(end-of-line)
(return t)))))))
(defun sass-highlight-selector ()
"Highlight a CSS selector starting at `point' and ending at `end-of-line'."
(let ((font-lock-keywords sass-selector-font-lock-keywords)
font-lock-multiline)
(font-lock-fontify-region
(point) (progn (end-of-line) (point))))
t)
(defun sass-highlight-script (beg end)
"Highlight a section of SassScript between BEG and END."
(save-match-data
(with-syntax-table sass-script-syntax-table
(let ((font-lock-keywords sass-script-font-lock-keywords)
font-lock-syntax-table
font-lock-extend-region-functions)
(font-lock-fontify-region beg end)))))
(defun sass-highlight-script-after-match ()
"Highlight a section of SassScript after the last match."
(end-of-line)
(sass-highlight-script (match-end 0) (point)))
(defun sass-highlight-directive ()
"Highlight a Sass directive."
(goto-char (match-end 0))
(block nil
(case (intern (match-string 1))
(for
(unless (looking-at " +!\\w+") (return))
(put-text-property (match-beginning 0) (match-end 0)
'face font-lock-variable-name-face)
(goto-char (match-end 0))
(unless (looking-at " +from") (return))
(put-text-property (match-beginning 0) (match-end 0)
'face font-lock-keyword-face)
(goto-char (match-end 0))
(when (looking-at " +\\(.+?\\) +\\(to\\|through\\)")
(sass-highlight-script (match-beginning 1) (match-end 1))
(put-text-property (match-beginning 2) (match-end 2)
'face font-lock-keyword-face))
(sass-highlight-script-after-match))
(else
(unless (looking-at " +if") (return))
(put-text-property (match-beginning 0) (match-end 0)
'face font-lock-keyword-face)
(sass-highlight-script-after-match))
((if while debug) (sass-highlight-script-after-match)))))
;; Constants
;; Mode setup
;;;###autoload
(define-derived-mode sass-mode haml-mode "Sass"
"Major mode for editing Sass files."
(set-syntax-table sass-syntax-table)
(setq font-lock-extend-region-functions
'(font-lock-extend-region-wholelines font-lock-extend-region-multiline))
(setq font-lock-multiline nil)
(setq comment-start "/*")
(set (make-local-variable 'haml-indent-function) 'sass-indent-p)
(set (make-local-variable 'haml-indent-offset) sass-indent-offset)
(setq font-lock-defaults '(sass-font-lock-keywords t t)))
;; Indentation
(defun sass-indent-p ()
"Return non-nil if the current line can have lines nested beneath it."
(loop for opener in sass-non-block-openers
if (looking-at opener) return nil
finally return t))
;;;###autoload
(add-to-list 'auto-mode-alist '("\\.sass$" . sass-mode))
;; Setup/Activation
(provide 'sass-mode)
;;; sass-mode.el ends here