Here is a patch, including a more advanced test case. In the end there is a "failing" section - although I haven't seen anyone breaking at spaces.
There is a varaiable called css-colon - the default is ":". If set to ": ", it will also indent statements like this in SASS (a CSS pre-processor)
a {
&:hover,
&:active {
background: green;
}
}
However, it will no longer correctly indent statements like this:
background:#000, /* no space */
url('image.png');
If I manage to fix the "breaking at spaces" case, I'll send another patch.
Evgeni
New css-mode.css
diff --git a/css-mode.css b/css-mode.css
new file mode 100644
index 0000000..40a732f
--- /dev/null
+++ b/css-mode.css
@@ -0,0 +1,86 @@
+#top_menu .button.active,
+#top_menu .button.active:active {
+ background-image: url('images/header_button_active.png');
+ cursor: default;
+ color: #999;
+ text-shadow: 1px 1px 2px #111;
+ /* Comment */
+}
+#top_menu .button.active,
+#top_menu .button.active:active
+{
+ /* Comment */
+ background-image: url('images/header_button_active.png');
+ cursor: default;
+ color: #999;
+ text-shadow: 1px 1px 2px #111;
+ /* Multiline
+ comment1 */
+ /* Multiline
+ * comment2 */
+}
+/* Multiline
+ comment1 */
+/* Multiline
+ * comment2 */
+#glass {
+ z-index: 2;
+ position: absolute;
+ top: -112px;
+ left: 0;
+ right: 0;
+ margin: 0 0 0 0;
+ margin:
+ 0 0 0 0;
+ text-shadow:
+ 1px 1px #FDE31D,
+ -1px -1px #B36300;
+ height: 140px;
+ background-image: url('images/honey_blurred2.png');
+ background-image: url(
+ 'images/honey_blurred2.png');
+ background-image:
+ #fff,
+ url('images/honey_blurred2.png'),
+ url('images/honey_blurred2.png');
+ background-image:
+ #fff,
+ /* #fff, */
+ url('images/honey_blurred2.png');
+ background-image: #fff,
+ url('images/honey_blurred2.png');
+ -webkit-mask-image:
+ -webkit-gradient(
+ linear,
+ left top,
+ /* left bottom, */
+ left bottom,
+ color-stop(
+ 1,
+ rgba(0,0,0,0.2)
+ ),
+ color-stop(1,
+ rgba(0,0,0,0.2)
+ ),
+ /* comment */
+ color-stop(0.7, rgba(0,0,0,.0)),
+ color-stop
+ (
+ 0.7, rgba(0,0,0,.0)
+ )
+ /* comment*/ );
+ background-repeat: repeat-x;
+ background-position: center top;
+ background-attachment: fixed; }
+
+#failing {
+ margin: 0
+ 0
+ 0
+ 0;
+ margin:
+ 0
+ 0
+ 0
+ 0;
+}
\ No newline at end of file
Modified css-mode.el
diff --git a/css-mode.el b/css-mode.el
index 1abe9a8..44682e1 100644
--- a/css-mode.el
+++ b/css-mode.el
@@ -33,6 +33,8 @@
;;; Code:
+(require 'cl-lib)
+
(defgroup css nil
"Cascading Style Sheets (CSS) editing mode."
:group 'languages)
@@ -92,7 +94,6 @@
(t nil)))
elems))
-
(defun css-extract-props-and-vals ()
(with-temp-buffer
(url-insert-file-contents "http://www.w3.org/TR/CSS21/propidx.html")
@@ -122,7 +123,7 @@
(defconst css-pseudo-ids
'("active" "after" "before" "first" "first-child" "first-letter" "first-line"
- "focus" "hover" "lang" "left" "link" "right" "visited")
+ "focus" "hover" "lang" "last-child" "left" "link" "right" "visited")
"Identifiers for pseudo-elements and pseudo-classes.")
(defconst css-at-ids
@@ -264,8 +265,8 @@
'(css-font-lock-keywords nil t))
;;;###autoload
-(define-derived-mode css-mode fundamental-mode "CSS"
- "Major mode to edit Cascading Style Sheets."
+(define-derived-mode css-mode fundamental-mode
+ "CSS" "Major mode to edit Cascading Style Sheets."
(setq-local font-lock-defaults css-font-lock-defaults)
(setq-local comment-start "/*")
(setq-local comment-start-skip "/\\*+[ \t]*")
@@ -276,6 +277,8 @@
(setq-local indent-line-function 'css-indent-line)
(setq-local fill-paragraph-function 'css-fill-paragraph)
(setq-local add-log-current-defun-function #'css-current-defun-name)
+ (setq-local beginning-of-defun-function 'css-beginning-of-defun)
+ (setq-local end-of-defun-function 'css-end-of-defun)
(when css-electric-keys
(let ((fc (make-char-table 'auto-fill-chars)))
(set-char-table-parent fc auto-fill-chars)
@@ -394,102 +397,178 @@
;; FIXME: We should also skip punctuation.
(not (memq (char-after) '(?\; ?\})))))))))))
-(defun css-indent-calculate-virtual ()
- (if (or (save-excursion (skip-chars-backward " \t") (bolp))
- (if (looking-at "\\s(")
- (save-excursion
- (forward-char 1) (skip-chars-forward " \t")
- (not (or (eolp) (looking-at comment-start-skip))))))
- (current-column)
- (css-indent-calculate)))
+(defun css-comment-line-p ()
+ (interactive)
+ (cond ( (save-excursion
+ (back-to-indentation)
+ (looking-at "/\\*"))
+ 1 )
+ ( (nth 4 (syntax-ppss))
+ t)))
+
+(defun css-beginning-of-defun (&optional arg)
+ (unless arg (setq arg 1))
+ (when (progn
+ ;; What for?
+ (unless (zerop (current-column))
+ (end-of-line))
+ (re-search-backward "^[^\n ].+{[ ]?$" nil t arg))
+ (while (save-excursion
+ (and (zerop (forward-line -1))
+ (string-match-p
+ "^[^}[:space:]/]"
+ (buffer-substring
+ (line-beginning-position)
+ (line-end-position)))))
+ (forward-line -1))))
+
+(defun css-end-of-defun (&optional arg)
+ (interactive)
+ (unless arg (setq arg 1))
+ (ignore-errors
+ (when (cl-plusp (first (syntax-ppss)))
+ (css-beginning-of-defun))
+ (progn
+ (search-forward "{" nil t arg)
+ (backward-char)
+ (forward-sexp)
+ (ignore-errors
+ (forward-char)))
+ t))
+
+;; To make writing derived modes easier. Ex. SASS also supports // type comments
+(defvar css-comment-line-p-function 'css-comment-line-p
+ "Should return 1 if at the beginning of a comment, t if inside.")
+
+(defun css--goto-prev-struct-line ()
+ (while (and (zerop (forward-line -1))
+ (funcall css-comment-line-p-function))))
+
+(defvar css-debug nil)
+
+(defun css-indent-debug-msg (name)
+ (when css-debug
+ (message "%s" name)))
+
+(defun css-visible-end-of-line ()
+ (save-excursion
+ (end-of-line)
+ (skip-syntax-backward
+ " " (line-beginning-position))
+ (point)))
-(defcustom css-indent-offset 4
- "Basic size of one indentation step."
- :version "22.2"
- :type 'integer
- :group 'css)
+(defun css-indentation-end-pos ()
+ (save-excursion
+ (back-to-indentation)
+ (point)))
-(defun css-indent-calculate ()
- (let ((ppss (syntax-ppss))
- pos)
- (with-syntax-table css-navigation-syntax-table
- (save-excursion
- (cond
- ;; Inside a string.
- ((nth 3 ppss) 'noindent)
- ;; Inside a comment.
- ((nth 4 ppss)
- (setq pos (point))
- (forward-line -1)
- (skip-chars-forward " \t")
- (if (>= (nth 8 ppss) (point))
- (progn
- (goto-char (nth 8 ppss))
- (if (eq (char-after pos) ?*)
- (forward-char 1)
- (if (not (looking-at comment-start-skip))
- (error "Internal css-mode error")
- (goto-char (match-end 0))))
- (current-column))
- (if (and (eq (char-after pos) ?*) (eq (char-after) ?*))
- (current-column)
- ;; 'noindent
- (current-column)
- )))
- ;; In normal code.
- (t
- (or
- (when (looking-at "\\s)")
- (forward-char 1)
- (backward-sexp 1)
- (css-indent-calculate-virtual))
- (when (looking-at comment-start-skip)
- (forward-comment (point-max))
- (css-indent-calculate))
- (when (save-excursion (forward-comment (- (point-max)))
- (setq pos (point))
- (eq (char-syntax (preceding-char)) ?\())
- (goto-char (1- pos))
- (if (not (looking-at "\\s([ \t]*"))
- (error "Internal css-mode error")
- (if (or (memq (char-after (match-end 0)) '(?\n nil))
- (save-excursion (goto-char (match-end 0))
- (looking-at comment-start-skip)))
- (+ (css-indent-calculate-virtual) css-indent-offset)
- (progn (goto-char (match-end 0)) (current-column)))))
- (progn
- (css-backward-sexp 1)
- (if (looking-at "\\s(")
- (css-indent-calculate)
- (css-indent-calculate-virtual))))))))))
+(defun css-current-character-indentation ()
+ "Like (current-indentation), but counts tabs as single characters."
+ (save-excursion
+ (back-to-indentation)
+ (- (point) (line-beginning-position))))
+
+(defvar css-colon ":"
+ "A dervied mode for SASS or LESS might want to set this to
+\": \", to make nested pseudo-classes work.")
+(defun css-indent-calculate ()
+ ;; If I go to the beginning of line, MC stops working
+ (back-to-indentation)
+ (with-syntax-table css-navigation-syntax-table
+ (let* (psl-indent
+ psl-last-char
+ psl-first-char
+ ( psl
+ (save-excursion
+ (css--goto-prev-struct-line)
+ (setq psl-indent (current-indentation))
+ (setq psl-last-char (char-before (css-visible-end-of-line)))
+ (setq psl-first-char (char-after (css-indentation-end-pos)))
+ (buffer-substring
+ (line-beginning-position)
+ (line-end-position))))
+ ( psl-closing-brackets
+ (+ (cl-count ?} psl)
+ (cl-count ?\) psl)))
+ ( psl-open-brackets (+ (cl-count ?{ psl) (cl-count ?\( psl)))
+ ( psl-has-colon (cl-plusp (cl-count ?: psl)))
+ (ppss (syntax-ppss))
+ previous-comment-indent
+ previous-line-was-comment
+ pos)
+ (cond ( ;; Inside a multiline comment
+ ( eq (funcall css-comment-line-p-function) t)
+ (css-indent-debug-msg "MC")
+ (save-excursion
+ (nth 4 ppss)
+ (setq pos (point))
+ (forward-line -1)
+ (skip-chars-forward " \t")
+ (if (>= (nth 8 ppss) (point))
+ (progn
+ (goto-char (nth 8 ppss))
+ (if (eq (char-after pos) ?*)
+ (forward-char 1)
+ (if (not (looking-at comment-start-skip))
+ (error "Internal css-mode error")
+ (goto-char (match-end 0))))
+ (current-column))
+ (current-column))))
+ ( ;; If "outside" indent to 0
+ (zerop (nth 0 ppss))
+ (css-indent-debug-msg "ZERO")
+ 0)
+ ( ;; Not-first member of comma ending lines
+ (and (not (cl-search css-colon psl))
+ (equal psl-last-char ?\, )
+ (= psl-open-brackets psl-closing-brackets))
+ (css-indent-debug-msg "MCB")
+ psl-indent)
+ ( ;; Line after beginning of comma block
+ (and (member psl-last-char '( ?: ?\, ) )
+ (= psl-open-brackets psl-closing-brackets))
+ (css-indent-debug-msg "LABOC")
+ (+ psl-indent css-indent-offset))
+ ( ;; Default, based on nesting level
+ t
+ (css-indent-debug-msg "LAST")
+ (let (( parent-indent
+ (save-excursion
+ (backward-up-list)
+ (css-current-character-indentation)))
+ ( block-ending-line
+ (member (char-after (css-indentation-end-pos))
+ '( ?\} ?\) ) )))
+ (+ parent-indent
+ (* (+ (if block-ending-line -1 0)
+ 1)
+ css-indent-offset))))
+ ))))
(defun css-indent-line ()
"Indent current line according to CSS indentation rules."
(interactive)
- (let* ((savep (point))
- (forward-sexp-function nil)
- (indent (condition-case nil
- (save-excursion
- (forward-line 0)
- (skip-chars-forward " \t")
- (if (>= (point) savep) (setq savep nil))
- (css-indent-calculate))
- (error nil))))
- (if (not (numberp indent)) 'noindent
- (if savep
- (save-excursion (indent-line-to indent))
- (indent-line-to indent)))))
+ (save-excursion
+ (indent-line-to (css-indent-calculate)))
+ (when (< (current-column) (current-indentation))
+ (back-to-indentation)))
+
+(defcustom css-indent-offset 4
+ "Basic size of one indentation step."
+ :version "22.2"
+ :type 'integer
+ :group 'css)
(defun css-current-defun-name ()
"Return the name of the CSS section at point, or nil."
(save-excursion
(let ((max (max (point-min) (- (point) 1600)))) ; approx 20 lines back
(when (search-backward "{" max t)
- (skip-chars-backward " \t\r\n")
- (beginning-of-line)
- (if (looking-at "^[ \t]*\\([^{\r\n]*[^ {\t\r\n]\\)")
- (match-string-no-properties 1))))))
+ (skip-chars-backward " \t\r\n")
+ (beginning-of-line)
+ (if (looking-at "^[ \t]*\\([^{\r\n]*[^ {\t\r\n]\\)")
+ (match-string-no-properties 1))))))
(provide 'css-mode)
;;; css-mode.el ends here
\ No newline at end of file