Package: emacs;
Reported by: E Sabof <esabof <at> gmail.com>
Date: Sun, 13 Jan 2013 07:45:01 UTC
Severity: wishlist
Done: Simen Heggestøyl <simenheg <at> gmail.com>
Bug is archived. No further changes may be made.
To add a comment to this bug, you must first unarchive it, by sending
a message to control AT debbugs.gnu.org, with unarchive 13425 in the body.
You can then email your comments to 13425 AT debbugs.gnu.org in the normal way.
Toggle the display of automated, internal messages from the tracker.
View this report as an mbox folder, status mbox, maintainer mbox
bug-gnu-emacs <at> gnu.org
:bug#13425
; Package emacs
.
(Sun, 13 Jan 2013 07:45:02 GMT) Full text and rfc822 format available.E Sabof <esabof <at> gmail.com>
:bug-gnu-emacs <at> gnu.org
.
(Sun, 13 Jan 2013 07:45:02 GMT) Full text and rfc822 format available.Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):
From: E Sabof <esabof <at> gmail.com> To: bug-gnu-emacs <at> gnu.org Subject: new css-mode indenter Date: Sun, 13 Jan 2013 07:43:31 +0000
[Message part 1 (text/plain, inline)]
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
[Message part 2 (text/html, inline)]
bug-gnu-emacs <at> gnu.org
:bug#13425
; Package emacs
.
(Tue, 15 Jan 2013 05:01:01 GMT) Full text and rfc822 format available.Message #8 received at 13425 <at> debbugs.gnu.org (full text, mbox):
From: Stefan Monnier <monnier <at> iro.umontreal.ca> To: E Sabof <esabof <at> gmail.com> Cc: 13425 <at> debbugs.gnu.org Subject: Re: bug#13425: new css-mode indenter Date: Tue, 15 Jan 2013 00:00:19 -0500
> 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."
bug-gnu-emacs <at> gnu.org
:bug#13425
; Package emacs
.
(Tue, 15 Jan 2013 19:34:02 GMT) Full text and rfc822 format available.Message #11 received at 13425 <at> debbugs.gnu.org (full text, mbox):
From: E Sabof <esabof <at> gmail.com> To: Stefan Monnier <monnier <at> iro.umontreal.ca> Cc: 13425 <at> debbugs.gnu.org Subject: Re: bug#13425: new css-mode indenter Date: Tue, 15 Jan 2013 19:32:43 +0000
[Message part 1 (text/plain, inline)]
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 <at> 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." > >
[Message part 2 (text/html, inline)]
bug-gnu-emacs <at> gnu.org
:bug#13425
; Package emacs
.
(Thu, 17 Jan 2013 00:19:02 GMT) Full text and rfc822 format available.Message #14 received at 13425 <at> debbugs.gnu.org (full text, mbox):
From: E Sabof <esabof <at> gmail.com> To: Stefan Monnier <monnier <at> iro.umontreal.ca> Cc: 13425 <at> debbugs.gnu.org Subject: Re: bug#13425: new css-mode indenter Date: Thu, 17 Jan 2013 00:17:34 +0000
[Message part 1 (text/plain, inline)]
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 <at> 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 <at> 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." >> >> >
[Message part 2 (text/html, inline)]
bug-gnu-emacs <at> gnu.org
:bug#13425
; Package emacs
.
(Wed, 25 Jan 2017 23:07:02 GMT) Full text and rfc822 format available.Message #17 received at 13425 <at> debbugs.gnu.org (full text, mbox):
From: Tom Tromey <tom <at> tromey.com> To: 13425 <at> debbugs.gnu.org Subject: close this bug? Date: Wed, 25 Jan 2017 16:06:16 -0700
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
bug-gnu-emacs <at> gnu.org
:bug#13425
; Package emacs
.
(Sun, 29 Jan 2017 14:03:02 GMT) Full text and rfc822 format available.Message #20 received at 13425 <at> debbugs.gnu.org (full text, mbox):
From: Simen Heggestøyl <simenheg <at> gmail.com> To: Tom Tromey <tom <at> tromey.com>, Stefan Monnier <monnier <at> iro.umontreal.ca> Cc: 13425 <at> debbugs.gnu.org Subject: Re: bug#13425: close this bug? Date: Sun, 29 Jan 2017 15:01:55 +0100
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 <at> 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
bug-gnu-emacs <at> gnu.org
:bug#13425
; Package emacs
.
(Sun, 29 Jan 2017 17:17:01 GMT) Full text and rfc822 format available.Message #23 received at 13425 <at> debbugs.gnu.org (full text, mbox):
From: Stefan Monnier <monnier <at> IRO.UMontreal.CA> To: Tom Tromey <tom <at> tromey.com> Cc: 13425 <at> debbugs.gnu.org Subject: Re: bug#13425: close this bug? Date: Sun, 29 Jan 2017 12:16:20 -0500
> 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
bug-gnu-emacs <at> gnu.org
:bug#13425
; Package emacs
.
(Sun, 29 Jan 2017 17:39:02 GMT) Full text and rfc822 format available.Message #26 received at 13425 <at> debbugs.gnu.org (full text, mbox):
From: Simen Heggestøyl <simenheg <at> gmail.com> To: Stefan Monnier <monnier <at> IRO.UMontreal.CA> Cc: 13425 <at> debbugs.gnu.org, Tom Tromey <tom <at> tromey.com> Subject: Re: bug#13425: close this bug? Date: Sun, 29 Jan 2017 18:38:25 +0100
On Sun, Jan 29, 2017 at 6:16 PM, Stefan Monnier <monnier <at> 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
bug-gnu-emacs <at> gnu.org
:bug#13425
; Package emacs
.
(Sun, 29 Jan 2017 18:16:02 GMT) Full text and rfc822 format available.Message #29 received at 13425 <at> debbugs.gnu.org (full text, mbox):
From: Stefan Monnier <monnier <at> IRO.UMontreal.CA> To: Simen Heggestøyl <simenheg <at> gmail.com> Cc: 13425 <at> debbugs.gnu.org, Tom Tromey <tom <at> tromey.com> Subject: Re: bug#13425: close this bug? Date: Sun, 29 Jan 2017 13:15:42 -0500
>> 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
bug-gnu-emacs <at> gnu.org
:bug#13425
; Package emacs
.
(Mon, 30 Jan 2017 22:29:01 GMT) Full text and rfc822 format available.Message #32 received at 13425 <at> debbugs.gnu.org (full text, mbox):
From: Stefan Monnier <monnier <at> IRO.UMontreal.CA> To: Tom Tromey <tom <at> tromey.com> Cc: 13425 <at> debbugs.gnu.org Subject: Re: bug#13425: close this bug? Date: Mon, 30 Jan 2017 17:28:40 -0500
> In my view the simplest is treating ":" different inside and outside of > braces. That should work for CSS, but not for SCSS. Stefan
bug-gnu-emacs <at> gnu.org
:bug#13425
; Package emacs
.
(Tue, 31 Jan 2017 00:47:02 GMT) Full text and rfc822 format available.Message #35 received at 13425 <at> debbugs.gnu.org (full text, mbox):
From: Tom Tromey <tom <at> tromey.com> To: Stefan Monnier <monnier <at> IRO.UMontreal.CA> Cc: 13425 <at> debbugs.gnu.org, Tom Tromey <tom <at> tromey.com> Subject: Re: bug#13425: close this bug? Date: Mon, 30 Jan 2017 15:17:48 -0700
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; +}
bug-gnu-emacs <at> gnu.org
:bug#13425
; Package emacs
.
(Thu, 02 Feb 2017 19:13:02 GMT) Full text and rfc822 format available.Message #38 received at 13425 <at> debbugs.gnu.org (full text, mbox):
From: Simen Heggestøyl <simenheg <at> gmail.com> To: Stefan Monnier <monnier <at> IRO.UMontreal.CA> Cc: 13425 <at> debbugs.gnu.org, Tom Tromey <tom <at> tromey.com> Subject: Re: bug#13425: close this bug? Date: Thu, 02 Feb 2017 20:12:14 +0100
[Message part 1 (text/plain, inline)]
Thanks for the hints, Stefan. The attached patch seems sufficient in my tests. Do you see any problems with it? -- Simen
[0001-WIP.patch (text/x-patch, attachment)]
bug-gnu-emacs <at> gnu.org
:bug#13425
; Package emacs
.
(Fri, 03 Feb 2017 00:04:01 GMT) Full text and rfc822 format available.Message #41 received at 13425 <at> debbugs.gnu.org (full text, mbox):
From: Stefan Monnier <monnier <at> iro.umontreal.ca> To: Simen Heggestøyl <simenheg <at> gmail.com> Cc: 13425 <at> debbugs.gnu.org, Tom Tromey <tom <at> tromey.com> Subject: Re: bug#13425: close this bug? Date: Thu, 02 Feb 2017 19:02:57 -0500
> + (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
Simen Heggestøyl <simenheg <at> gmail.com>
:E Sabof <esabof <at> gmail.com>
:Message #46 received at 13425-done <at> debbugs.gnu.org (full text, mbox):
From: Simen Heggestøyl <simenheg <at> gmail.com> To: Stefan Monnier <monnier <at> iro.umontreal.ca>, esabof <at> gmail.com Cc: 13425-done <at> debbugs.gnu.org, Tom Tromey <tom <at> tromey.com> Subject: Re: bug#13425: close this bug? Date: Sat, 04 Feb 2017 20:31:11 +0100
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
Debbugs Internal Request <help-debbugs <at> gnu.org>
to internal_control <at> debbugs.gnu.org
.
(Sun, 05 Mar 2017 12:24:03 GMT) Full text and rfc822 format available.
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.