unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#13425: new css-mode indenter
@ 2013-01-13  7:43 E Sabof
  2013-01-15  5:00 ` Stefan Monnier
  2017-01-25 23:06 ` bug#13425: close this bug? Tom Tromey
  0 siblings, 2 replies; 14+ messages in thread
From: E Sabof @ 2013-01-13  7:43 UTC (permalink / raw)
  To: 13425

[-- Attachment #1: Type: text/plain, Size: 727 bytes --]

I've written a new indenter for CSS, which fixes issues with mulitline
statements following a colon, and comments:

body {
    color: #333;
    font: 15px "Helvetica Neue",
        Helvetica,
        Arial,
        "Nimbus Sans L",
        sans-serif;
    font-weight: 300;
    line-height: 1.625;
    a { /* It also handles SCSS */
        font: 15px "Helvetica Neue",
            Helvetica,
            Arial,
            "Nimbus Sans L",
            sans-serif;
        /* Some comment */
    }
    /* Some comment at the end of a block */
}

I wondered whether this functionality could be integrated into Emacs.
https://github.com/sabof/es-css-mode
https://github.com/sabof/es-lib (it takes some functions from it)

Evgeni

[-- Attachment #2: Type: text/html, Size: 1243 bytes --]

^ permalink raw reply	[flat|nested] 14+ messages in thread

* bug#13425: new css-mode indenter
  2013-01-13  7:43 bug#13425: new css-mode indenter E Sabof
@ 2013-01-15  5:00 ` Stefan Monnier
  2013-01-15 19:32   ` E Sabof
  2017-01-25 23:06 ` bug#13425: close this bug? Tom Tromey
  1 sibling, 1 reply; 14+ messages in thread
From: Stefan Monnier @ 2013-01-15  5:00 UTC (permalink / raw)
  To: E Sabof; +Cc: 13425

> I've written a new indenter for CSS, which fixes issues with mulitline
> statements following a colon, and comments:

Interesting, I don't use CSS myself very much, but I've toyed with an
SMIE indenter for css-mode (see below).

> body {
>     color: #333;
>     font: 15px "Helvetica Neue",
>         Helvetica,
>         Arial,
>         "Nimbus Sans L",
>         sans-serif;
>     font-weight: 300;
>     line-height: 1.625;
>     a { /* It also handles SCSS */
>         font: 15px "Helvetica Neue",
>             Helvetica,
>             Arial,
>             "Nimbus Sans L",
>             sans-serif;
>         /* Some comment */
>     }
>     /* Some comment at the end of a block */
> }

I've tried it on the above test case and the SMIE code seems to handle
it OK (tho differently), with the main exception being the
comment-at-end-of-block, which is probably a general problem which
should be fixed in smie.el (probably in smie-indent-comment).

But I have basically never used/tested my code, so...

> I wondered whether this functionality could be integrated into Emacs.
> https://github.com/sabof/es-css-mode
> https://github.com/sabof/es-lib (it takes some functions from it)

If you could provide a patch against css-mode.el, that would be more
convenient.  Also it would be good to add a good set of test cases (in
the form of a new file test/indent/css-mode.css).


        Stefan


=== modified file 'lisp/textmodes/css-mode.el'
--- lisp/textmodes/css-mode.el	2013-01-02 16:13:04 +0000
+++ lisp/textmodes/css-mode.el	2013-01-15 04:53:03 +0000
@@ -263,6 +263,22 @@
 (defvar css-font-lock-defaults
   '(css-font-lock-keywords nil t))
 
+(defcustom css-indent-offset 4
+  "Basic size of one indentation step."
+  :version "22.2"
+  :type 'integer)
+
+(defconst css-smie-grammar
+  (smie-prec2->grammar
+   (smie-precs->prec2 '((assoc ";") (left ":") (assoc ",")))))
+
+(defun css-smie-rules (kind token)
+  (pcase (cons kind token)
+    (`(:elem . basic) css-indent-offset)
+    (`(:elem . arg) 0)
+    (`(:before . "{") (if (smie-rule-hanging-p)
+                         (smie-rule-parent 0)))))
+
 ;;;###autoload
 (define-derived-mode css-mode fundamental-mode "CSS"
   "Major mode to edit Cascading Style Sheets."
@@ -271,11 +287,11 @@
   (setq-local comment-start-skip "/\\*+[ \t]*")
   (setq-local comment-end "*/")
   (setq-local comment-end-skip "[ \t]*\\*+/")
-  (setq-local forward-sexp-function 'css-forward-sexp)
   (setq-local parse-sexp-ignore-comments t)
   (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)
+  (smie-setup css-smie-grammar #'css-smie-rules)
   (when css-electric-keys
     (let ((fc (make-char-table 'auto-fill-chars)))
       (set-char-table-parent fc auto-fill-chars)
@@ -355,131 +371,6 @@
             ;; Don't use the default filling code.
             t)))))))
 
-;;; Navigation and indentation.
-
-(defconst css-navigation-syntax-table
-  (let ((st (make-syntax-table css-mode-syntax-table)))
-    (map-char-table (lambda (c v)
-                      ;; Turn punctuation (code = 1) into symbol (code = 1).
-                      (if (eq (car-safe v) 1)
-                          (set-char-table-range st c (cons 3 (cdr v)))))
-                    st)
-    st))
-
-(defun css-backward-sexp (n)
-  (let ((forward-sexp-function nil))
-    (if (< n 0) (css-forward-sexp (- n))
-      (while (> n 0)
-        (setq n (1- n))
-        (forward-comment (- (point-max)))
-        (if (not (eq (char-before) ?\;))
-            (backward-sexp 1)
-          (while (progn (backward-sexp 1)
-                        (save-excursion
-                          (forward-comment (- (point-max)))
-                          ;; FIXME: We should also skip punctuation.
-                          (not (or (bobp) (memq (char-before) '(?\; ?\{))))))))))))
-
-(defun css-forward-sexp (n)
-  (let ((forward-sexp-function nil))
-    (if (< n 0) (css-backward-sexp (- n))
-      (while (> n 0)
-        (setq n (1- n))
-        (forward-comment (point-max))
-        (if (not (eq (char-after) ?\;))
-            (forward-sexp 1)
-          (while (progn (forward-sexp 1)
-                        (save-excursion
-                          (forward-comment (point-max))
-                          ;; 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-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-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)))))
 
 (defun css-current-defun-name ()
   "Return the name of the CSS section at point, or nil."






^ permalink raw reply	[flat|nested] 14+ messages in thread

* bug#13425: new css-mode indenter
  2013-01-15  5:00 ` Stefan Monnier
@ 2013-01-15 19:32   ` E Sabof
  2013-01-17  0:17     ` E Sabof
  0 siblings, 1 reply; 14+ messages in thread
From: E Sabof @ 2013-01-15 19:32 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 13425

[-- Attachment #1: Type: text/plain, Size: 19517 bytes --]

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


On Tue, Jan 15, 2013 at 5:00 AM, Stefan Monnier <monnier@iro.umontreal.ca>wrote:

> > I've written a new indenter for CSS, which fixes issues with mulitline
> > statements following a colon, and comments:
>
> Interesting, I don't use CSS myself very much, but I've toyed with an
> SMIE indenter for css-mode (see below).
>
> > body {
> >     color: #333;
> >     font: 15px "Helvetica Neue",
> >         Helvetica,
> >         Arial,
> >         "Nimbus Sans L",
> >         sans-serif;
> >     font-weight: 300;
> >     line-height: 1.625;
> >     a { /* It also handles SCSS */
> >         font: 15px "Helvetica Neue",
> >             Helvetica,
> >             Arial,
> >             "Nimbus Sans L",
> >             sans-serif;
> >         /* Some comment */
> >     }
> >     /* Some comment at the end of a block */
> > }
>
> I've tried it on the above test case and the SMIE code seems to handle
> it OK (tho differently), with the main exception being the
> comment-at-end-of-block, which is probably a general problem which
> should be fixed in smie.el (probably in smie-indent-comment).
>
> But I have basically never used/tested my code, so...
>
> > I wondered whether this functionality could be integrated into Emacs.
> > https://github.com/sabof/es-css-mode
> > https://github.com/sabof/es-lib (it takes some functions from it)
>
> If you could provide a patch against css-mode.el, that would be more
> convenient.  Also it would be good to add a good set of test cases (in
> the form of a new file test/indent/css-mode.css).
>
>
>         Stefan
>
>
> === modified file 'lisp/textmodes/css-mode.el'
> --- lisp/textmodes/css-mode.el  2013-01-02 16:13:04 +0000
> +++ lisp/textmodes/css-mode.el  2013-01-15 04:53:03 +0000
> @@ -263,6 +263,22 @@
>  (defvar css-font-lock-defaults
>    '(css-font-lock-keywords nil t))
>
> +(defcustom css-indent-offset 4
> +  "Basic size of one indentation step."
> +  :version "22.2"
> +  :type 'integer)
> +
> +(defconst css-smie-grammar
> +  (smie-prec2->grammar
> +   (smie-precs->prec2 '((assoc ";") (left ":") (assoc ",")))))
> +
> +(defun css-smie-rules (kind token)
> +  (pcase (cons kind token)
> +    (`(:elem . basic) css-indent-offset)
> +    (`(:elem . arg) 0)
> +    (`(:before . "{") (if (smie-rule-hanging-p)
> +                         (smie-rule-parent 0)))))
> +
>  ;;;###autoload
>  (define-derived-mode css-mode fundamental-mode "CSS"
>    "Major mode to edit Cascading Style Sheets."
> @@ -271,11 +287,11 @@
>    (setq-local comment-start-skip "/\\*+[ \t]*")
>    (setq-local comment-end "*/")
>    (setq-local comment-end-skip "[ \t]*\\*+/")
> -  (setq-local forward-sexp-function 'css-forward-sexp)
>    (setq-local parse-sexp-ignore-comments t)
>    (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)
> +  (smie-setup css-smie-grammar #'css-smie-rules)
>    (when css-electric-keys
>      (let ((fc (make-char-table 'auto-fill-chars)))
>        (set-char-table-parent fc auto-fill-chars)
> @@ -355,131 +371,6 @@
>              ;; Don't use the default filling code.
>              t)))))))
>
> -;;; Navigation and indentation.
> -
> -(defconst css-navigation-syntax-table
> -  (let ((st (make-syntax-table css-mode-syntax-table)))
> -    (map-char-table (lambda (c v)
> -                      ;; Turn punctuation (code = 1) into symbol (code =
> 1).
> -                      (if (eq (car-safe v) 1)
> -                          (set-char-table-range st c (cons 3 (cdr v)))))
> -                    st)
> -    st))
> -
> -(defun css-backward-sexp (n)
> -  (let ((forward-sexp-function nil))
> -    (if (< n 0) (css-forward-sexp (- n))
> -      (while (> n 0)
> -        (setq n (1- n))
> -        (forward-comment (- (point-max)))
> -        (if (not (eq (char-before) ?\;))
> -            (backward-sexp 1)
> -          (while (progn (backward-sexp 1)
> -                        (save-excursion
> -                          (forward-comment (- (point-max)))
> -                          ;; FIXME: We should also skip punctuation.
> -                          (not (or (bobp) (memq (char-before) '(?\;
> ?\{))))))))))))
> -
> -(defun css-forward-sexp (n)
> -  (let ((forward-sexp-function nil))
> -    (if (< n 0) (css-backward-sexp (- n))
> -      (while (> n 0)
> -        (setq n (1- n))
> -        (forward-comment (point-max))
> -        (if (not (eq (char-after) ?\;))
> -            (forward-sexp 1)
> -          (while (progn (forward-sexp 1)
> -                        (save-excursion
> -                          (forward-comment (point-max))
> -                          ;; 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-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-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)))))
>
>  (defun css-current-defun-name ()
>    "Return the name of the CSS section at point, or nil."
>
>

[-- Attachment #2: Type: text/html, Size: 26299 bytes --]

^ permalink raw reply	[flat|nested] 14+ messages in thread

* bug#13425: new css-mode indenter
  2013-01-15 19:32   ` E Sabof
@ 2013-01-17  0:17     ` E Sabof
  0 siblings, 0 replies; 14+ messages in thread
From: E Sabof @ 2013-01-17  0:17 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 13425

[-- Attachment #1: Type: text/plain, Size: 39975 bytes --]

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
     (url-insert-file-contents "http://www.w3.org/TR/CSS21/propidx.html")
@@ -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



On Tue, Jan 15, 2013 at 7:32 PM, E Sabof <esabof@gmail.com> wrote:

> 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
>
>
> On Tue, Jan 15, 2013 at 5:00 AM, Stefan Monnier <monnier@iro.umontreal.ca>wrote:
>
>> > I've written a new indenter for CSS, which fixes issues with mulitline
>> > statements following a colon, and comments:
>>
>> Interesting, I don't use CSS myself very much, but I've toyed with an
>> SMIE indenter for css-mode (see below).
>>
>> > body {
>> >     color: #333;
>> >     font: 15px "Helvetica Neue",
>> >         Helvetica,
>> >         Arial,
>> >         "Nimbus Sans L",
>> >         sans-serif;
>> >     font-weight: 300;
>> >     line-height: 1.625;
>> >     a { /* It also handles SCSS */
>> >         font: 15px "Helvetica Neue",
>> >             Helvetica,
>> >             Arial,
>> >             "Nimbus Sans L",
>> >             sans-serif;
>> >         /* Some comment */
>> >     }
>> >     /* Some comment at the end of a block */
>> > }
>>
>> I've tried it on the above test case and the SMIE code seems to handle
>> it OK (tho differently), with the main exception being the
>> comment-at-end-of-block, which is probably a general problem which
>> should be fixed in smie.el (probably in smie-indent-comment).
>>
>> But I have basically never used/tested my code, so...
>>
>> > I wondered whether this functionality could be integrated into Emacs.
>> > https://github.com/sabof/es-css-mode
>> > https://github.com/sabof/es-lib (it takes some functions from it)
>>
>> If you could provide a patch against css-mode.el, that would be more
>> convenient.  Also it would be good to add a good set of test cases (in
>> the form of a new file test/indent/css-mode.css).
>>
>>
>>         Stefan
>>
>>
>> === modified file 'lisp/textmodes/css-mode.el'
>> --- lisp/textmodes/css-mode.el  2013-01-02 16:13:04 +0000
>> +++ lisp/textmodes/css-mode.el  2013-01-15 04:53:03 +0000
>> @@ -263,6 +263,22 @@
>>  (defvar css-font-lock-defaults
>>    '(css-font-lock-keywords nil t))
>>
>> +(defcustom css-indent-offset 4
>> +  "Basic size of one indentation step."
>> +  :version "22.2"
>> +  :type 'integer)
>> +
>> +(defconst css-smie-grammar
>> +  (smie-prec2->grammar
>> +   (smie-precs->prec2 '((assoc ";") (left ":") (assoc ",")))))
>> +
>> +(defun css-smie-rules (kind token)
>> +  (pcase (cons kind token)
>> +    (`(:elem . basic) css-indent-offset)
>> +    (`(:elem . arg) 0)
>> +    (`(:before . "{") (if (smie-rule-hanging-p)
>> +                         (smie-rule-parent 0)))))
>> +
>>  ;;;###autoload
>>  (define-derived-mode css-mode fundamental-mode "CSS"
>>    "Major mode to edit Cascading Style Sheets."
>> @@ -271,11 +287,11 @@
>>    (setq-local comment-start-skip "/\\*+[ \t]*")
>>    (setq-local comment-end "*/")
>>    (setq-local comment-end-skip "[ \t]*\\*+/")
>> -  (setq-local forward-sexp-function 'css-forward-sexp)
>>    (setq-local parse-sexp-ignore-comments t)
>>    (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)
>> +  (smie-setup css-smie-grammar #'css-smie-rules)
>>    (when css-electric-keys
>>      (let ((fc (make-char-table 'auto-fill-chars)))
>>        (set-char-table-parent fc auto-fill-chars)
>> @@ -355,131 +371,6 @@
>>              ;; Don't use the default filling code.
>>              t)))))))
>>
>> -;;; Navigation and indentation.
>> -
>> -(defconst css-navigation-syntax-table
>> -  (let ((st (make-syntax-table css-mode-syntax-table)))
>> -    (map-char-table (lambda (c v)
>> -                      ;; Turn punctuation (code = 1) into symbol (code =
>> 1).
>> -                      (if (eq (car-safe v) 1)
>> -                          (set-char-table-range st c (cons 3 (cdr v)))))
>> -                    st)
>> -    st))
>> -
>> -(defun css-backward-sexp (n)
>> -  (let ((forward-sexp-function nil))
>> -    (if (< n 0) (css-forward-sexp (- n))
>> -      (while (> n 0)
>> -        (setq n (1- n))
>> -        (forward-comment (- (point-max)))
>> -        (if (not (eq (char-before) ?\;))
>> -            (backward-sexp 1)
>> -          (while (progn (backward-sexp 1)
>> -                        (save-excursion
>> -                          (forward-comment (- (point-max)))
>> -                          ;; FIXME: We should also skip punctuation.
>> -                          (not (or (bobp) (memq (char-before) '(?\;
>> ?\{))))))))))))
>> -
>> -(defun css-forward-sexp (n)
>> -  (let ((forward-sexp-function nil))
>> -    (if (< n 0) (css-backward-sexp (- n))
>> -      (while (> n 0)
>> -        (setq n (1- n))
>> -        (forward-comment (point-max))
>> -        (if (not (eq (char-after) ?\;))
>> -            (forward-sexp 1)
>> -          (while (progn (forward-sexp 1)
>> -                        (save-excursion
>> -                          (forward-comment (point-max))
>> -                          ;; 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-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-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)))))
>>
>>  (defun css-current-defun-name ()
>>    "Return the name of the CSS section at point, or nil."
>>
>>
>

[-- Attachment #2: Type: text/html, Size: 54999 bytes --]

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* bug#13425: close this bug?
  2013-01-13  7:43 bug#13425: new css-mode indenter E Sabof
  2013-01-15  5:00 ` Stefan Monnier
@ 2017-01-25 23:06 ` Tom Tromey
  2017-01-29 14:01   ` Simen Heggestøyl
  2017-01-29 17:16   ` Stefan Monnier
  1 sibling, 2 replies; 14+ messages in thread
From: Tom Tromey @ 2017-01-25 23:06 UTC (permalink / raw)
  To: 13425

Stefan's new SMIE-based indenter has been in css-mode for a while.
It seems pretty good.

I did spend a bit of time trying to get it to line up continued
properties under the ":", like:

    font: 15px "Helvetica Neue",
	  Helvetica,
	  Arial,
	  "Nimbus Sans L",
	  sans-serif;

... but all I really accomplished was realizing that I don't understand
SMIE.

Anyway I think the issues in this bug are fixed.  I recommend closing
it.

Tom





^ permalink raw reply	[flat|nested] 14+ messages in thread

* bug#13425: close this bug?
  2017-01-25 23:06 ` bug#13425: close this bug? Tom Tromey
@ 2017-01-29 14:01   ` Simen Heggestøyl
  2017-01-29 17:16   ` Stefan Monnier
  1 sibling, 0 replies; 14+ messages in thread
From: Simen Heggestøyl @ 2017-01-29 14:01 UTC (permalink / raw)
  To: Tom Tromey, Stefan Monnier; +Cc: 13425

I agree that the SMIE-based indenter is good, and I'd like to keep it. 
The
mentioned problem of aligning lines after a comma is indeed the only 
problem
I've had with it.

On Thu, Jan 26, 2017 at 12:06 AM, Tom Tromey <tom@tromey.com> wrote:
> I did spend a bit of time trying to get it to line up continued
> properties under the ":", like:
> 
>     font: 15px "Helvetica Neue",
> 	  Helvetica,
> 	  Arial,
> 	  "Nimbus Sans L",
> 	  sans-serif;
> 
> ... but all I really accomplished was realizing that I don't 
> understand
> SMIE.

I did that too, but I had to reach the same conclusion...

If we aren't able to fix this with problem the SMIE-based indenter, and
Evgeni's indenter handles it better, I think we should consider it.

But as said, I think it would be ideal to fix and keep the indenter we 
have.

Stefan, do you have time to look into it, or do you have some pointers 
to how
we could fix this problem with the SMIE-based indenter?

-- Simen






^ permalink raw reply	[flat|nested] 14+ messages in thread

* bug#13425: close this bug?
  2017-01-25 23:06 ` bug#13425: close this bug? Tom Tromey
  2017-01-29 14:01   ` Simen Heggestøyl
@ 2017-01-29 17:16   ` Stefan Monnier
  2017-01-29 17:38     ` Simen Heggestøyl
  2017-01-30 22:17     ` Tom Tromey
  1 sibling, 2 replies; 14+ messages in thread
From: Stefan Monnier @ 2017-01-29 17:16 UTC (permalink / raw)
  To: Tom Tromey; +Cc: 13425

> I did spend a bit of time trying to get it to line up continued
> properties under the ":", like:

>     font: 15px "Helvetica Neue",
> 	  Helvetica,
> 	  Arial,
> 	  "Nimbus Sans L",
> 	  sans-serif;

> ... but all I really accomplished was realizing that I don't understand
> SMIE.

What have you tried?  I don't know the CSS grammar well enough to know
what we should do.

The above problem is trivially fixed with the patch below, but it seems
too easy, so it probably introduces problems elsewhere.


        Stefan


diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el
index c81c3f62e1..499de8db2e 100644
--- a/lisp/textmodes/css-mode.el
+++ b/lisp/textmodes/css-mode.el
@@ -734,7 +734,7 @@ css-indent-offset
 
 (defconst css-smie-grammar
   (smie-prec2->grammar
-   (smie-precs->prec2 '((assoc ";") (assoc ",") (left ":")))))
+   (smie-precs->prec2 '((assoc ";") (left ":") (assoc ",")))))
 
 (defun css-smie--forward-token ()
   (cond





^ permalink raw reply related	[flat|nested] 14+ messages in thread

* bug#13425: close this bug?
  2017-01-29 17:16   ` Stefan Monnier
@ 2017-01-29 17:38     ` Simen Heggestøyl
  2017-01-29 18:15       ` Stefan Monnier
  2017-01-30 22:17     ` Tom Tromey
  1 sibling, 1 reply; 14+ messages in thread
From: Simen Heggestøyl @ 2017-01-29 17:38 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 13425, Tom Tromey

On Sun, Jan 29, 2017 at 6:16 PM, Stefan Monnier 
<monnier@IRO.UMontreal.CA> wrote:
> The above problem is trivially fixed with the patch below, but it 
> seems
> too easy, so it probably introduces problems elsewhere.

Indeed, that causes problems with selectors that contain colons, like:

div:first-child,
div:last-child {
    top: 1.5rem;
}

Which gets indented like:

div:first-child,
    div:last-child {
    top: 1.5rem;
}

And SCSS mixins, like:

@include foo-mixin(
    $foo: 'foo',
    $bar: 'bar,
);

Which gets indented like:

@include foo-mixin(
    $foo: 'foo',
	  $bar: 'bar,
);

-- Simen








^ permalink raw reply	[flat|nested] 14+ messages in thread

* bug#13425: close this bug?
  2017-01-29 17:38     ` Simen Heggestøyl
@ 2017-01-29 18:15       ` Stefan Monnier
  2017-02-02 19:12         ` Simen Heggestøyl
  0 siblings, 1 reply; 14+ messages in thread
From: Stefan Monnier @ 2017-01-29 18:15 UTC (permalink / raw)
  To: Simen Heggestøyl; +Cc: 13425, Tom Tromey

>> The above problem is trivially fixed with the patch below, but it seems
>> too easy, so it probably introduces problems elsewhere.
> Indeed, that causes problems with selectors that contain colons, like:

Then we need to change css-smie--forward-token and
css-smie--backward-token.  Two ways to fix it:
- either make them distinguish between ", between selector" and ",
  between values".
- or make them distinguish between ": for selectors" and ": for values".

Then we can change the grammar to give different precedences for the two
different cases.

E.g.

    (defun css-smie--backward-token ()
      [...]
      (if (css--colon-inside-selector-p)
          ":-selector" ":"))

and then

 (defconst css-smie-grammar
   (smie-prec2->grammar
    (smie-precs->prec2 '((assoc ";") (left ":") (assoc ",")
                         (left ":-selector")))))

So the question is mostly: whether it's easier to distinguish the two
different kinds of commas, or whether it's easier to distinguish the two
different kinds of colons.


        Stefan





^ permalink raw reply	[flat|nested] 14+ messages in thread

* bug#13425: close this bug?
  2017-01-29 17:16   ` Stefan Monnier
  2017-01-29 17:38     ` Simen Heggestøyl
@ 2017-01-30 22:17     ` Tom Tromey
  2017-01-30 22:28       ` Stefan Monnier
  1 sibling, 1 reply; 14+ messages in thread
From: Tom Tromey @ 2017-01-30 22:17 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 13425, Tom Tromey

Stefan> What have you tried?  I don't know the CSS grammar well enough to know
Stefan> what we should do.

I've appended my first try, which also includes a test case.
(Though the last stanza of the test case is actually not indented
properly as-is, oops.)  Like yours mine failed on ":"s in selectors.

I had a second try but it worked even less well I think.

Stefan> So the question is mostly: whether it's easier to distinguish
Stefan> the two different kinds of commas, or whether it's easier to
Stefan> distinguish the two different kinds of colons.

In my view the simplest is treating ":" different inside and outside of
braces.

Outside of braces it acts as a separator in a selector, with the suffix
being a pseudo-class or pseudo-element:

    p::after {
      blah
    }

Inside braces it separates a property name from the value:

   p {
     property-name: value;
   }

Tom

diff --git a/lisp/emacs-lisp/smie.el b/lisp/emacs-lisp/smie.el
index 4d02b75..5cb9027 100644
--- a/lisp/emacs-lisp/smie.el
+++ b/lisp/emacs-lisp/smie.el
@@ -1140,7 +1140,7 @@ smie-rules-function
 - :before, in which case ARG is a token and the function should return the
   OFFSET to use to indent ARG itself.
 - :elem, in which case the function should return either:
-  - the offset to use to indent function arguments (ARG = `arg')
+  - the offset to use to indent function arguments (ARG = `args')
   - the basic indentation step (ARG = `basic').
   - the token to use (when ARG = `empty-line-token') when we don't know how
     to indent an empty line.
diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el
index c81c3f6..7f1b5ca 100644
--- a/lisp/textmodes/css-mode.el
+++ b/lisp/textmodes/css-mode.el
@@ -734,7 +734,7 @@ css-indent-offset
 
 (defconst css-smie-grammar
   (smie-prec2->grammar
-   (smie-precs->prec2 '((assoc ";") (assoc ",") (left ":")))))
+   (smie-precs->prec2 '((assoc ";") (left ":")))))
 
 (defun css-smie--forward-token ()
   (cond
@@ -764,6 +764,7 @@ css-smie--backward-token
 
 (defun css-smie-rules (kind token)
   (pcase (cons kind token)
+    (`(:list-intro . ":") t)
     (`(:elem . basic) css-indent-offset)
     (`(:elem . arg) 0)
     (`(:list-intro . ,(or `";" `"")) t) ;"" stands for BOB (bug#15467).
diff --git a/test/manual/indent/css-mode.css b/test/manual/indent/css-mode.css
index 3a00739..a7ee536 100644
--- a/test/manual/indent/css-mode.css
+++ b/test/manual/indent/css-mode.css
@@ -43,3 +43,44 @@ article:hover
 {
     color: black;
 }
+
+/* Example from bug#13425, with some changes.  */
+body {
+    color: #333;
+    font: 15px "Helvetica Neue",
+	  Helvetica,
+	  Arial,
+	  "Nimbus Sans L",
+	  sans-serif;
+    font-x-commas: 15px "Helvetica Neue"
+		   Helvetica
+		   Arial
+		   "Nimbus Sans L"
+		   sans-serif;
+    font-weight: 300;
+    line-height: 1.625;
+    a { /* It also handles SCSS */
+        font: 15px "Helvetica Neue",
+	      Helvetica,
+	      Arial,
+	      "Nimbus Sans L",
+	      sans-serif;
+        /* Some comment */
+	font-x-commas: 15px "Helvetica Neue"
+		       Helvetica
+		       Arial
+		       "Nimbus Sans L"
+		       sans-serif;
+    }
+    /* Some comment at the end of a block */
+}
+
+/* Ensure bug#13425 doesn't regress this.  */
+#navtable .current:link,
+#navtable .current:visited,
+#navtable .current:hover,
+#navtable .current:active {
+  background-color: grey;
+  color: white;
+  border: thin solid black;
+}





^ permalink raw reply related	[flat|nested] 14+ messages in thread

* bug#13425: close this bug?
  2017-01-30 22:17     ` Tom Tromey
@ 2017-01-30 22:28       ` Stefan Monnier
  0 siblings, 0 replies; 14+ messages in thread
From: Stefan Monnier @ 2017-01-30 22:28 UTC (permalink / raw)
  To: Tom Tromey; +Cc: 13425

> In my view the simplest is treating ":" different inside and outside of
> braces.

That should work for CSS, but not for SCSS.


        Stefan





^ permalink raw reply	[flat|nested] 14+ messages in thread

* bug#13425: close this bug?
  2017-01-29 18:15       ` Stefan Monnier
@ 2017-02-02 19:12         ` Simen Heggestøyl
  2017-02-03  0:02           ` Stefan Monnier
  0 siblings, 1 reply; 14+ messages in thread
From: Simen Heggestøyl @ 2017-02-02 19:12 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: 13425, Tom Tromey

[-- Attachment #1: Type: text/plain, Size: 123 bytes --]

Thanks for the hints, Stefan.

The attached patch seems sufficient in my tests. Do you see any
problems with it?

-- Simen

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-WIP.patch --]
[-- Type: text/x-patch, Size: 2854 bytes --]

From b2772f486ab833fc5221577811a5d517dfc77742 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Simen=20Heggest=C3=B8yl?= <simenheg@gmail.com>
Date: Thu, 2 Feb 2017 20:05:32 +0100
Subject: [PATCH] WIP

---
 lisp/textmodes/css-mode.el | 44 ++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 40 insertions(+), 4 deletions(-)

diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el
index 19f74daec6..65a599d6d4 100644
--- a/lisp/textmodes/css-mode.el
+++ b/lisp/textmodes/css-mode.el
@@ -32,10 +32,11 @@
 
 ;;; Code:
 
+(require 'eww)
 (require 'seq)
 (require 'sgml-mode)
 (require 'smie)
-(require 'eww)
+(require 'subr-x)
 
 (defgroup css nil
   "Cascading Style Sheets (CSS) editing mode."
@@ -741,7 +742,30 @@ css-indent-offset
 
 (defconst css-smie-grammar
   (smie-prec2->grammar
-   (smie-precs->prec2 '((assoc ";") (assoc ",") (left ":")))))
+   (smie-precs->prec2
+    '((assoc ";")
+      ;; Colons that belong to a CSS property.  These get a higher
+      ;; precedence than other colons, such as colons in selectors,
+      ;; which are represented by a plain ":" token.
+      (left ":-property")
+      (assoc ",")
+      (assoc ":")))))
+
+(defun css--colon-inside-selector-p ()
+  "Return t if point looks to be inside a CSS selector.
+This function is intended to be good enough to help SMIE during
+tokenization, but should not be regarded as a reliable function
+for determining wheter point is within a selector."
+  (save-excursion
+    (re-search-forward "[{};)]" nil t)
+    (eq (char-before) ?\{)))
+
+(defun css--colon-inside-funcall ()
+  "Return t if point is inside a function call."
+  (when-let (opening-paren-pos (nth 1 (syntax-ppss)))
+    (save-excursion
+      (goto-char opening-paren-pos)
+      (eq (char-after) ?\())))
 
 (defun css-smie--forward-token ()
   (cond
@@ -755,7 +779,13 @@ css-smie--forward-token
     ";")
    ((progn (forward-comment (point-max))
            (looking-at "[;,:]"))
-    (forward-char 1) (match-string 0))
+    (forward-char 1)
+    (if (equal (match-string 0) ":")
+        (if (or (css--colon-inside-selector-p)
+                (css--colon-inside-funcall))
+            ":"
+          ":-property")
+      (match-string 0)))
    (t (smie-default-forward-token))))
 
 (defun css-smie--backward-token ()
@@ -766,7 +796,13 @@ css-smie--backward-token
      ((and (eq (char-before) ?\}) (scss-smie--not-interpolation-p)
            (> pos (point))) ";")
      ((memq (char-before) '(?\; ?\, ?\:))
-      (forward-char -1) (string (char-after)))
+      (forward-char -1)
+      (if (eq (char-after) ?\:)
+          (if (or (css--colon-inside-selector-p)
+                  (css--colon-inside-funcall))
+              ":"
+            ":-property")
+        (string (char-after))))
      (t (smie-default-backward-token)))))
 
 (defun css-smie-rules (kind token)
-- 
2.11.0


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* bug#13425: close this bug?
  2017-02-02 19:12         ` Simen Heggestøyl
@ 2017-02-03  0:02           ` Stefan Monnier
  2017-02-04 19:31             ` Simen Heggestøyl
  0 siblings, 1 reply; 14+ messages in thread
From: Stefan Monnier @ 2017-02-03  0:02 UTC (permalink / raw)
  To: Simen Heggestøyl; +Cc: 13425, Tom Tromey

> +  (save-excursion
> +    (re-search-forward "[{};)]" nil t)
> +    (eq (char-before) ?\{)))

Looks OK.  It will get confused if there's a comment jut at the wrong
place with one of ";{})" inside, which is not unheard of.  It's good
enough for now, but a solution I've used frequently with SMIE is to call
a "tokenize-backward" (usually a less discriminating one than the actual
backward tokenizer I give to SMIE, to avoid inf-recursion) repeatedly
rather than using a regexp-search.


        Stefan





^ permalink raw reply	[flat|nested] 14+ messages in thread

* bug#13425: close this bug?
  2017-02-03  0:02           ` Stefan Monnier
@ 2017-02-04 19:31             ` Simen Heggestøyl
  0 siblings, 0 replies; 14+ messages in thread
From: Simen Heggestøyl @ 2017-02-04 19:31 UTC (permalink / raw)
  To: Stefan Monnier, esabof; +Cc: 13425-done, Tom Tromey

Good, I've installed the patch as f6ff7bb1fcd062fe. It should cover
the mentioned test cases.

I'm closing this bug since the current CSS indenter now handles
multiline property values better.

Evgeni, I'm sorry that your report had to sit around for so
long. Thank you for prodding us to improve the indentation engine in
this regard!

-- Simen






^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2017-02-04 19:31 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-01-13  7:43 bug#13425: new css-mode indenter E Sabof
2013-01-15  5:00 ` Stefan Monnier
2013-01-15 19:32   ` E Sabof
2013-01-17  0:17     ` E Sabof
2017-01-25 23:06 ` bug#13425: close this bug? Tom Tromey
2017-01-29 14:01   ` Simen Heggestøyl
2017-01-29 17:16   ` Stefan Monnier
2017-01-29 17:38     ` Simen Heggestøyl
2017-01-29 18:15       ` Stefan Monnier
2017-02-02 19:12         ` Simen Heggestøyl
2017-02-03  0:02           ` Stefan Monnier
2017-02-04 19:31             ` Simen Heggestøyl
2017-01-30 22:17     ` Tom Tromey
2017-01-30 22:28       ` Stefan Monnier

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).