Wikia

Vim Tips Wiki

Regex-based text alignment

Talk0
1,612pages on
this wiki
Tip 894 Printable Monobook Previous Next

created 2005 · complexity intermediate · author Nall-ohki · version 7.0


Some people like to align program statements, for example, so that the "=" in assignments are lined up. This tip provides a script that defaults to aligning "=", but which can align using a search pattern. The script may not work correctly when tab characters are present.

ExamplesEdit

For example, consider the lines:

let range = '%'
let foo = 'Vim'
let something = 42

With this tip, if you visually select the lines then press \a (assuming the default backslash leader key), the lines would be changed to:

let range     = '%'
let foo       = 'Vim'
let something = 42

Now consider:

int Add(int a, int b);
void Point(doublePt dp);
const *char GetString(unsigned int index);

Selecting these lines, then entering the command :Align \S\+( would align the function names. Repeating, with command :Align ( would then align the (. Doing both results in:

int         Add       (int a, int b);
void        Point     (doublePt dp);
const *char GetString (unsigned int index);

ScriptEdit

Save the following in a file, say, align.vim. Enter the command :so align.vim to source the script.

command! -nargs=? -range Align <line1>,<line2>call AlignSection('<args>')
vnoremap <silent> <Leader>a :Align<CR>
function! AlignSection(regex) range
  let extra = 1
  let sep = empty(a:regex) ? '=' : a:regex
  let maxpos = 0
  let section = getline(a:firstline, a:lastline)
  for line in section
    let pos = match(line, ' *'.sep)
    if maxpos < pos
      let maxpos = pos
    endif
  endfor
  call map(section, 'AlignLine(v:val, sep, maxpos, extra)')
  call setline(a:firstline, section)
endfunction

function! AlignLine(line, sep, maxpos, extra)
  let m = matchlist(a:line, '\(.\{-}\) \{-}\('.a:sep.'.*\)')
  if empty(m)
    return a:line
  endif
  let spaces = repeat(' ', a:maxpos - strlen(m[1]) + a:extra)
  return m[1] . spaces . m[2]
endfunction

Related tipsEdit

 TO DO 

  • We have a number of tips which use the terms "align", "justify", "format" in various, often inconsistent, ways. Need to check each tip and see they use the words correctly.
  • Need to clean up following tips. Some might be merged, but want to avoid undue complexity.

Align text using Vim

Align text using an external script

Align tables using Vim

Invoke an external text formatting tool

CommentsEdit

See Align text plugin which describes the Align plugin. Separators are regular expression patterns, and one may restrict Align to operate only on lines which satisfy (or don't satisfy) a regular expression pattern (:AlignCtrl g pattern or :AlignCtrl v pattern).

Alternative scriptEdit

 TO DO 

  • Examine following. Is it worth keeping?

I decided to extend the tip's functionality to be able to do left, right and shift alignments. Shift alignments are basically indentation alignments that are useful for aligning decimal points on numbers.

Align() also allows alignment of words before and after the regex pattern. For words before the pattern, enter a positive number when prompted. For words after the pattern, enter a negative number instead. For example, to choose the second word after the regex pattern, enter -2. After that, select the type of alignment to be done.

"place in vimrc - tested on gvim 6.3
set magic
"align code - select lines with Visual Block using <Ctrl-V><move down>
vmap <C-F4> :call Align(Prompt("0"),Prompt("1","0"),Prompt("2","l"))<CR>

"align code function
function Align(regex, wnum, align) range
  let range = a:firstline.",".a:lastline
  let curcol = 0
  let maxcol = 0
  "find maximum column
  let words = Words(a:wnum, a:align, "find")
  let i = a:firstline
  while i <= a:lastline
    let line = getline(i)
    if line =~ a:regex
      if a:wnum < 0
        let curcol = matchend(line, a:regex.words)
      else
        let curcol = match(line, words.a:regex)
      endif
      let maxcol = curcol > maxcol ? curcol : maxcol
    endif
    let i = i + 1
  endwhile
  "perform alignment
  let i = a:firstline
  while i <= a:lastline
    let line = getline(i)
    if line =~ a:regex
      if a:wnum < 0
        let curcol = matchend(line, a:regex.words)
      else
        let curcol = match(line, words.a:regex)
      endif
      let pad = ""
      while strlen(pad) < (maxcol - curcol)
        let pad = pad." "
      endwhile
      "determine padding location
      let words2 = Words(a:wnum, a:align, "pad")
      if a:wnum < 0
        let curcol = matchend(line, a:regex.words2)
      else
        let curcol = match(line, words2.a:regex)
      endif
      "left-word or shift aligned
      if a:align == "s"
        call setline(i, pad.strpart(line, 0, curcol).strpart(line, curcol))
      else
        call setline(i, strpart(line, 0, curcol).pad.strpart(line, curcol))
      endif
    endif
    let i = i + 1
  endwhile
  execute "normal gv"
endfunction

"set up words regular expression
function Words(wnum, align, action)
  if a:align == "r"
    if a:action == "find"
      if a:wnum > 0
        let words = "\\(\\S*\\s*\\)\\{".a:wnum."}"
      elseif a:wnum < -1
        let words = "\\(\\s*\\S*\\)\\{".(-a:wnum)."}"
      elseif a:wnum == -1
        let words = "\\s*\\S*"
      else
        let words = ""
      endif
    elseif a:action == "pad"
      if a:wnum >= 0
        let words = "\\(\\S*\\s*\\)\\{".(a:wnum + 1)."}"
      elseif a:wnum < -1
        let words = "\\s*\\(\\S*\\s*\\)\\{".(-a:wnum - 1)."}"
      elseif a:wnum == -1
        let words = "\\s*"
      else
        let words = ""
      endif
    endif
  else
    if a:wnum > 0
      let words = "\\(\\S*\\s*\\)\\{".a:wnum."}"
    elseif a:wnum < -1
      let words = "\\s*\\(\\S*\\s*\\)\\{".(-a:wnum - 1)."}"
    elseif a:wnum == -1
      let words = "\\s*"
    else
      let words = ""
    endif
  endif
  return words
endfunction

"prompt user for alignment settings
function Prompt(str, ...)
  if a:str
    if a:str == "1"
      let str = "How many words before pattern? "
    elseif a:str == "2"
      let str = "Alignment [(l)eft (r)ight (s)hift]? "
    endif
  else
    let str = "Pattern? "
  endif
  let default = a:0 ? a:1 : ""
  execute "let ret = input(\"".str."\", \"".default."\")"
  return ret
endfunction

Around Wikia's network

Random Wiki