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.
Examples[]
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);
Script[]
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 tips[]
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
- 139 Align text plugin describes the Align plugin (script 294)
- 253 Specify a column with bar
- 319 Simple text alignment
- 893 Align numbers at decimal point
- 894 Regex-based text alignment this tip
Align text using an external script
- 570 Align text into a table Perl script intend merging this to 139
Align tables using Vim
Invoke an external text formatting tool
Comments[]
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 script[]
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