3

I am trying to select the content of @media in CSS with regexp in Emacs (elisp) to make a slight change in the color of the background or font, so I can easily see that I am editing inside of them.

I already came up with this regexp:

@media.*\{\\(.*[^]]*?\}\\)[ \t\n]*\}

And this is selecting the content most of the times, but I'm having a problem when I have comments or leading spaces after the last bracket before the closing bracket of the @media statement.

I want to match just the content inside every @media query brackets.

/*... css above*/
/*tablet*/
@media (max-width: 800px){
    ._desktop-only {display:none !important;}
    ._tablet-only {display:initial;}
    ._mobile-only {display:none;}
}
/*mobile*/
@media (max-width: 480px){
     ._desktop-only {display:none;}
     ._tablet-only {display:none !important;}
     ._mobile-only {display:initial;} /* comment */
}
/*more css below...*/

Some people might suggest that I should create separated files. I usually do that, but sometimes I have to edit the files as they are. And don't mind much the sample, because my goal is to edit large CSS files with a lot of statements inside of them, and I want to be able to see if I am editing the main statements or the variation of them.

  • Please do not post the same question to this site and to StackOverflow. Choose one site only. – Drew Feb 4 '17 at 1:22
  • I just found out about this site. I meant to delete from the other site, but I've got busy. – morishi Feb 4 '17 at 3:42
  • No problem. And thanks for considering it. – Drew Feb 4 '17 at 5:34
  • Maybe show some examples of the CSS that you want to match, to give a better idea? – Drew Feb 4 '17 at 5:35
  • Related: stackoverflow.com/questions/2704728/… – Tobias Jun 22 '18 at 11:46
2

Since regular expressions can't match balanced expressions, your regex needs some additional hints to find the final }. E.g. if the first } at the start of a line necessarily must be the one belonging to the initial { (and all inner ones are indented), then

@media.*\{\\(\\(\n\\|.\\)*?\n\\)}

seems to do the job (for your example at least).


That said, I would just use narrowing – first I ensure point is in the @media's curly braces, then I hit shift-F11 which I have bound to this "narrow-DWIM" thing which I have no idea where I got from

(global-set-key
 (kbd "<S-f11>")
 (defun narrow-or-widen-dwim (p)
   "If the buffer is narrowed, it widens. Otherwise, it narrows intelligently.
Intelligently means: region, org-src-block, org-subtree, or defun,
whichever applies first.
Narrowing to org-src-block actually calls `org-edit-src-code'.

With prefix P, don't widen, just narrow even if buffer is already
narrowed."
   (interactive "P")
   (declare (interactive-only))
   (cond ((and (buffer-narrowed-p) (not p)) (widen))
         ((region-active-p)
          (narrow-to-region (region-beginning) (region-end)))
         ((derived-mode-p 'org-mode)
          ;; `org-edit-src-code' is not a real narrowing command.
          ;; Remove this first conditional if you don't want it.
          (cond ((org-in-src-block-p)
                 (org-edit-src-code)
                 (delete-other-windows))
                ((org-at-block-p)
                 (org-narrow-to-block))
                (t (org-narrow-to-subtree))))
         (t (narrow-to-defun)))))
2

There is the cexp extension of Emacs' regular expressions that also matches balanced expressions.

With that library you can use the following elisp expression:

(cexp-search-forward "@media *\\!(.*\\!) *\\!(.*\\!)")

which works just like re-search-forward with additional sexps.

If you run that elisp expression match data is set as follows:

  • (match-string 0): the overall match

    "@media (max-width: 480px){
      ._desktop-only {display:none;}
      ._tablet-only {display:none !important;}
      ._mobile-only {display:initial;} /* comment */
    }"
    
  • (match-string 1): the stuff before the first sexp "@media "

  • (match-string 2): the first sexp "(max-width: 800px)"

  • (match-string 3): the regular expression match within the first balanced expression, i.e. the match for .* within the match for the first \\!(.*\\!): "(max-width: 800px)"

  • (match-string 4): the stuff between the first and the second sexp ""

  • (match-string 5): the second balanced expression

    "{
     ._desktop-only {display:none !important;}
     ._tablet-only {display:initial;}
     ._mobile-only {display:none;}
    }"
    
  • (match-string 6): the (first) match for .* within the second balanced expression, i.e., "{"

Match string 6 is right and perhaps interesting. It shows the difference between the sub-match that matches a balanced expression (i.e. match string 5) and the match within the balanced expression (i.e. match string 6). The dot . only matches characters that are not new-lines and on the first line of the second balanced expression there is only the opening curly brace.

You can even use

(cexp-search-forward "@media *\\!(\\!) *\\!(\\!)")

In that case you get empty strings as (match-string 3) and (match-string 6). Even so you find the balanced expressions as (match-string 2) and (match-string 5).

For a better understanding I cite here the doc-string of cexp-search-forward:

Search for combined regular and balanced expressions (cexp). The syntax of cexp is almost that of a regular expression with the exception that the string !( introduces a balanced expression and !) closes a balanced expression. The matched balanced expressions and the matches for the regular expressions before, in between, and after the sexps appear in the match data. Regular expression braces ( and ) may not include balanced expressions (sexps). On the other hand balanced expressions may include regular expressions with groups.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.