I've rewritten the indenter - this time I've added a parser for statements within curly brackets, so there is a lot less hackery. I've also added some more tests cases.
Evgeni
New css-mode.css
diff --git a/css-mode.css b/css-mode.css
new file mode 100644
index 0000000..f101971
--- /dev/null
+++ b/css-mode.css
@@ -0,0 +1,230 @@
+#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; }
+
+#glass {
+ 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,
+ /* com */
+ 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*/);
+ -webkit-mask-image:
+ /* -webkit- */gradient(
+ linear,
+ left /* top */,
+ /* left */bottom,
+ left bottom,
+ color-stop(
+ /* 1, */
+ /* rgba(0,0,0,0.2) */
+ ),
+ color-stop(1,
+ /* com */
+ rgba(0,0,0,0.2)
+
+ ),
+ /* comment */
+ color-stop(0.7/* , rgba(0,0,0,.0) */
+ , rgba(0,0,0,.0) ),
+ color-stop
+ (
+
+ 0.7, rgba(0,0,0,.0)
+ )
+ /* comment*/);
+ color: black,
+ /* red */,
+ blue;
+ /* -webkit-mask-image: */
+ background:
+ -webkit-gradient(
+ /* com */
+ );
+ -webkit-mask-image: -webkit-gradient(
+ /* Forgivable? Better? */
+ ),
+ -webkit-mask-image(
+ /* Back on track */
+ );
+ background-attachment: fixed
+ /* sdfsdf */
+}
+
+p:nth-child(2) {
+ margin:
+ 0
+ 0
+ 0
+ 0;
+ margin: 0 0
+ 0
+ 0;
+
+ /* comment */
+
+ text-shadow:1px 1px #FDE31D, /* no space */
+ -1px -1px #B36300;
+}
+
+p:nth-child
+(
+ 2
+)
+{
+ height: 2px
+}
+
+#field_message {
+ width: 100%;
+ display: block;
+ -webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
+ -moz-box-sizing: border-box; /* Firefox, other Gecko */
+ box-sizing: border-box; /* Opera/IE 8+ */
+ /* resize: vertical; */
+ resize: none;
+ height: 160px
+}
+
+
+some {
+
+}
+
+/* SASS */
+
+.some {
+ /* resize: vertical; */
+ resize: none;
+
+ height: 160px;
+ .other,
+ .another,
+ &:a,
+ #id {
+ /* resize: vertical; */
+ resize: none;
+
+ height: 160px;
+ }
+ /* Comment */
+ resize: none;
+ height: 160px;
+ /* Comment */
+}
+
+
+/* Local Variables: */
+/* css-debug: t */
+/* End: */
\ No newline at end of file
Modified css-mode.el
diff --git a/css-mode.el b/css-mode.el
index 1abe9a8..9f3a786 100644
--- a/css-mode.el
+++ b/css-mode.el
@@ -31,12 +31,95 @@
;; - completion
;; - fix font-lock errors with multi-line selectors
+;;; TODO:
+;; extra empty line indentation
+
;;; Code:
+(require 'cl-lib)
+
(defgroup css nil
"Cascading Style Sheets (CSS) editing mode."
:group 'languages)
+;; Debugging
+
+(defvar css-debug-overlays nil)
+
+(defun css-debug-overlay (beginning end color)
+ (let ((overlay (make-overlay beginning end)))
+ (overlay-put overlay 'face `(:background ,color))
+ (push overlay css-debug-overlays)))
+
+(defvar css-debug nil)
+
+(defun css-debug-msg (name)
+ (when css-debug
+ (message "%s" name)))
+
+(defun css-debug-goto-root-declaration ()
+ (let* (( ppss (syntax-ppss))
+ ( depth (nth 0 ppss)))
+ (when (nth 3 ppss)
+ (goto-char (nth 8 ppss)))
+ (while (and (cl-plusp depth)
+ (not (equal (char-after) ?\{ )))
+ (up-list -1)
+ (cl-decf depth))))
+
+(defun css-debug-parser-current ()
+ (interactive)
+ (mapc 'delete-overlay css-debug-overlays)
+ (let* (( point
+ (save-excursion
+ (back-to-indentation)
+ (point)))
+ ( parsed
+ (save-excursion
+ (css-debug-goto-root-declaration)
+ (css-parse-curly)))
+ ( relevant
+ (css-pc-get-relevant parsed point)))
+ (css-debug-overlay
+ (car relevant)
+ (cadr relevant)
+ "DarkRed")
+ nil))
+
+(defun css-debug-highight-parsed (parsed)
+ (mapc 'delete-overlay css-debug-overlays)
+ (cl-mapc (lambda (item)
+ (css-debug-overlay
+ (car item)
+ (cadr item)
+ (case (caddr item)
+ ('nested-selector "DarkGreen")
+ ('comment "SaddleBrown")
+ ( t "DarkRed"))))
+ parsed))
+
+(defun css-debug-parser-all ()
+ (interactive)
+ (let* (( parsed
+ (save-excursion
+ (css-debug-goto-root-declaration)
+ (css-parse-curly))))
+ (css-debug-highight-parsed
+ parsed)
+ nil))
+
+(defun css-debug-parser-inside ()
+ (interactive)
+ (let* (( parsed
+ (save-excursion
+ (css-debug-goto-root-declaration)
+ (css-parse-curly)))
+ ( inside
+ (css-pc-inside-statement
+ parsed (point))))
+ (message "P %s" inside)))
+
+;; EOF Debugging
(defun css-extract-keyword-list (res)
(with-temp-buffer
@@ -92,7 +175,6 @@
(t nil)))
elems))
-
(defun css-extract-props-and-vals ()
(with-temp-buffer
@@ -122,7 +204,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 +346,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 +358,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 +478,188 @@
;; 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)))
-
-(defcustom css-indent-offset 4
- "Basic size of one indentation step."
- :version "22.2"
- :type 'integer
- :group 'css)
+(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 (car (syntax-ppss)))
+ (css-beginning-of-defun))
+ (progn
+ (search-forward "{" nil t arg)
+ (backward-char)
+ (forward-sexp)
+ (ignore-errors
+ (forward-char)))
+ t))
+
+(defun css-go-up ()
+ (let* (( ppss (syntax-ppss)))
+ (when (or (nth 3 ppss) (nth 4 ppss))
+ (goto-char (nth 8 ppss)))
+ (when (cl-plusp (nth 0 ppss))
+ (up-list -1))))
+
+(defmacro css-while-point-moving (&rest rest)
+ (let ((old-point (cl-gensym)))
+ `(let (,old-point)
+ (while (not (equal (point) ,old-point))
+ (setq ,old-point (point))
+ ,@rest))))
+
+(defun css-parse-curly ()
+ (let (( start (point))
+ ( indentation (current-indentation))
+ ( end (save-excursion
+ (forward-sexp)
+ (point)))
+ point result)
+ (forward-char)
+ (cl-loop named main-loop
+ do
+ (skip-chars-forward "\n\t " end)
+ (when (>= (point) (1- end))
+ (cl-return-from main-loop))
+ (setq point (point))
+ (if (forward-comment 1)
+ (push (list point (point) 'comment) result)
+ (progn
+ (cl-loop (unless (re-search-forward ";\\|{\\|}" end t)
+ (cl-return-from main-loop))
+ (unless (nth 4 (syntax-ppss))
+ (cl-return)))
+ (cond ( (equal (char-before) ?\{ )
+ (backward-char)
+ (forward-sexp)
+ (push (list point (point) 'nested-selector) result))
+ ( (equal (char-before) ?\} )
+ (backward-char)
+ (css-while-point-moving
+ (skip-chars-backward "\n\t " start)
+ (forward-comment -1))
+ (push (list point (point) 'statement) result))
+ ( t (push (list point (point) 'statement) result))))))
+ (nreverse result)))
+
+(defun css-pc-get-relevant (parsed point)
+ (car (reverse (cl-remove-if (apply-partially '< point)
+ parsed :key 'car))))
+
+(defun css-pc-inside-statement (parsed point)
+ (cl-some (lambda (item)
+ (and (<= (car item) point)
+ (<= point (cadr item))))
+ parsed))
(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))))))))))
-
+ (save-match-data
+ (condition-case error
+ (with-syntax-table css-navigation-syntax-table
+ (back-to-indentation)
+ (let* ((point (point))
+ (ppss (syntax-ppss))
+ ( css-curly-parsed
+ (save-excursion
+ (css-go-up)
+ (when (equal (char-after) ?{ )
+ (css-parse-curly))))
+ css-parsed-relevant
+ (block-ending-line
+ (member (char-after
+ (save-excursion
+ (back-to-indentation)
+ (point)))
+ '( ?\} ?\) ) )))
+ (cond ( (nth 4 ppss)
+ ;; Inside a multiline comment
+ (css-debug-msg "MC")
+ (save-excursion
+ (forward-line -1)
+ (skip-chars-forward " \t")
+ (if (>= (nth 8 ppss) (point))
+ (progn
+ (goto-char (nth 8 ppss))
+ (if (eq (char-after point) ?*)
+ (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-debug-msg "ZERO")
+ 0)
+ ( ;; inside curly brackets
+ (and css-curly-parsed
+ (not block-ending-line)
+ (setq css-parsed-relevant
+ (css-pc-get-relevant
+ css-curly-parsed point))
+ (not (eq (nth 2 css-parsed-relevant)
+ 'nested-selector)))
+ (css-debug-msg "C")
+ (+ css-indent-offset
+ (save-excursion
+ (css-go-up)
+ (current-indentation))
+ (if (and (not (equal (line-number-at-pos
+ (car css-parsed-relevant))
+ (line-number-at-pos)))
+ (css-pc-inside-statement
+ css-curly-parsed point))
+ css-indent-offset 0)))
+ ( ;; Inside parentheses, closing brackets
+ t
+ (css-debug-msg "P")
+ (+ (save-excursion
+ (css-go-up)
+ (current-indentation))
+ (if block-ending-line
+ 0 css-indent-offset))))))
+ (error ;; My best error-less guess
+ (css-debug-msg "Err")
+ (* (car (syntax-ppss))
+ 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