Using vi, how can I make CSS rules into one liners

2019-01-13 19:24发布

问题:

Example bad:

#main {
      padding:0;
      margin: 10px auto;
}

Example good:

#main {padding:0;margin:10px auto;}

I have a ton of CSS rules that are taking up too many lines. And I cannot figure out the :%s/ commands to use.

回答1:

Here's a one-liner:

:%s/{\_.\{-}}/\=substitute(submatch(0), '\n', '', 'g')/

\_. matches any character, including a newline, and \{-} is the non-greedy version of *, so {\_.\{-}} matches everything between a matching pair of curly braces, inclusive.

The \= allows you to substitute the result of a vim expression, which we here use to strip out all the newlines '\n' from the matched text (in submatch(0)) using the substitute() function.

The inverse (converting the one-line version to multi-line) can also be done as a one liner:

:%s/{\_.\{-}}/\=substitute(submatch(0), '[{;]', '\0\r', 'g')/


回答2:

If you are at the beginning or end of the rule, V%J will join it into a single line:

  • Go to the opening (or closing) brace
  • Hit V to enter visual mode
  • Hit % to match the other brace, selecting the whole rule
  • Hit J to join the lines


回答3:

Try something like this:

:%s/{\n/{/g
:%s/;\n/;/g
:%s/{\s+/{/g
:%s/;\s+/;/g

This removes the newlines after opening braces and semicolons ('{' and ';') and then removes the extra whitespace between the concatenated lines.



回答4:

If you want to change the file, go for rampion's solution.

If you don't want (or can't) change the file, you can play with a custom folding as it permits to choose what and how to display the folded text. For instance:

" {rtp}/fold/css-fold.vim
" [-- local settings --]               {{{1
setlocal foldexpr=CssFold(v:lnum)
setlocal foldtext=CssFoldText()

let b:width1 = 20
let b:width2 = 15

nnoremap <buffer> + :let b:width2+=1<cr><c-l>
nnoremap <buffer> - :let b:width2-=1<cr><c-l>

" [-- global definitions --]           {{{1
if exists('*CssFold')
  setlocal foldmethod=expr
  " finish
endif

function! CssFold(lnum)
  let cline = getline(a:lnum)
  if     cline =~ '{\s*$'
      return 'a1'
  elseif cline =~ '}\s*$'
      return 's1'
  else
      return '='
  endif
endfunction

function! s:Complete(txt, width)
  let length = strlen(a:txt)
  if length > a:width
      return a:txt
  endif
  return a:txt . repeat(' ', a:width - length)
endfunction

function! CssFoldText()
  let lnum = v:foldstart
  let txt = s:Complete(getline(lnum), b:width1)
  let lnum += 1
  while lnum < v:foldend
      let add = s:Complete(substitute(getline(lnum), '^\s*\(\S\+\)\s*:\s*\(.\{-}\)\s*;\s*$', '\1: \2;', ''), b:width2)
      if add !~ '^\s*$'
          let txt .= ' ' . add
      endif

      let lnum += 1
  endwhile
  return txt. '}'
endfunction

I leave the sorting of the fields as exercise. Hint: get all the lines between v:foldstart+1 and v:voldend in a List, sort the list, build the string, and that's all.



回答5:

I won't answer the question directly, but instead I suggest you to reconsider your needs. I think that your "bad" example is in fact the better one. It is more readable, easier to reason about and modify. Good indentation is very important not only when it comes to programming languages, but also in CSS and HTML.

You mention that CSS rules are "taking up too many lines". If you are worried about file size you should consider using CSS/JS minifier like YUI Compressor instead of making the code less readable.



回答6:

A convenient way of doing this transformation is to run the following short command.

:g/{/,/}/j


回答7:

Go to the first line of the file, and use the command gqG to run the whole file through the formatter. Assuming runs of nonempty lines should be collapsed in the whole file.