mirror of
https://github.com/junegunn/vim-easy-align.git
synced 2025-11-18 06:43:40 -05:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
663264ed39 | ||
|
|
df75520f72 | ||
|
|
a2d52611ee | ||
|
|
fce0a103ff | ||
|
|
1752bf567b | ||
|
|
003194a47f | ||
|
|
c1c6f32e8c |
29
README.md
29
README.md
@@ -112,20 +112,21 @@ You can override these default rules or define your own rules with
|
||||
|
||||
#### Example command sequences
|
||||
|
||||
| With visual map | Description | Equivalent command |
|
||||
| ------------------- | -------------------------------------------------------- | --------------------- |
|
||||
| `<Enter><space>` | Alignment around 1st whitespaces | `:'<,'>EasyAlign\ ` |
|
||||
| `<Enter>2<space>` | Alignment around 2nd whitespaces | `:'<,'>EasyAlign2\ ` |
|
||||
| `<Enter>-<space>` | Alignment around the last whitespaces | `:'<,'>EasyAlign-\ ` |
|
||||
| `<Enter>:` | Alignment around 1st colon | `:'<,'>EasyAlign:` |
|
||||
| `<Enter>=` | Alignment around 1st equals signs (and the likes) | `:'<,'>EasyAlign=` |
|
||||
| `<Enter>2=` | Alignment around 2nd equals signs (and the likes) | `:'<,'>EasyAlign2=` |
|
||||
| `<Enter>3=` | Alignment around 3rd equals signs (and the likes) | `:'<,'>EasyAlign3=` |
|
||||
| `<Enter>*=` | Alignment around all equals signs (and the likes) | `:'<,'>EasyAlign*=` |
|
||||
| `<Enter>**=` | Left-right alternating alignment around all equals signs | `:'<,'>EasyAlign**=` |
|
||||
| `<Enter><Enter>=` | Right alignment around 1st equals signs | `:'<,'>EasyAlign!=` |
|
||||
| `<Enter><Enter>**=` | Right-left alternating alignment around all equals signs | `:'<,'>EasyAlign!**=` |
|
||||
| ... | ... | |
|
||||
| With visual map | Description | Equivalent command |
|
||||
| ------------------- | ------------------------------------------------------- | ------------------------------- |
|
||||
| `<Enter><space>` | Alignment around 1st whitespaces | `:'<,'>EasyAlign\ ` |
|
||||
| `<Enter>2<space>` | Alignment around 2nd whitespaces | `:'<,'>EasyAlign2\ ` |
|
||||
| `<Enter>-<space>` | Alignment around the last whitespaces | `:'<,'>EasyAlign-\ ` |
|
||||
| `<Enter>-2<space>` | Alignment around the 2nd to last whitespaces | `:'<,'>EasyAlign-2\ ` |
|
||||
| `<Enter>:` | Alignment around 1st colon (`key: value`) | `:'<,'>EasyAlign:` |
|
||||
| `<Enter><Right>:` | Alignment around 1st colon (`key : value`) | `:'<,'>EasyAlign:{'s':0,'l':1}` |
|
||||
| `<Enter>=` | Alignment around 1st operators with = | `:'<,'>EasyAlign=` |
|
||||
| `<Enter>2=` | Alignment around 2nd operators with = | `:'<,'>EasyAlign2=` |
|
||||
| `<Enter>3=` | Alignment around 3rd operators with = | `:'<,'>EasyAlign3=` |
|
||||
| `<Enter>*=` | Alignment around all operators with = | `:'<,'>EasyAlign*=` |
|
||||
| `<Enter>**=` | Left-right alternating alignment around all = operators | `:'<,'>EasyAlign**=` |
|
||||
| `<Enter><Enter>=` | Right alignment around 1st equals signs | `:'<,'>EasyAlign!=` |
|
||||
| `<Enter><Enter>**=` | Right-left alternating alignment around all = operators | `:'<,'>EasyAlign!**=` |
|
||||
|
||||
#### Using regular expressions
|
||||
|
||||
|
||||
@@ -55,6 +55,13 @@ let s:option_values = {
|
||||
\ 'ignore_groups': [[], ['String'], ['Comment'], ['String', 'Comment']]
|
||||
\ }
|
||||
|
||||
let s:shorthand = {
|
||||
\ 'margin_left': 'lm', 'margin_right': 'rm', 'stick_to_left': 'stl',
|
||||
\ 'left_margin': 'lm', 'right_margin': 'rm', 'indentation': 'idt',
|
||||
\ 'ignore_groups': 'ig', 'ignore_unmatched': 'iu', 'delimiter_align': 'da',
|
||||
\ 'mode_sequence': 'm', 'ignores': 'ig'
|
||||
\ }
|
||||
|
||||
if exists("*strwidth")
|
||||
function! s:strwidth(str)
|
||||
return strwidth(a:str)
|
||||
@@ -88,14 +95,60 @@ function! s:ignored_syntax()
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:echon(l, n, d, o)
|
||||
echon "\r"
|
||||
echon "\rEasyAlign". s:mode_labels[a:l] ." (" .a:n.a:d. ")"
|
||||
\ . (empty(a:o) ? '' : ' '.string(a:o))
|
||||
let s:prev_echon_len = 0
|
||||
function! s:echon_(tokens)
|
||||
" http://vim.wikia.com/wiki/How_to_print_full_screen_width_messages
|
||||
let xy = [&ruler, &showcmd]
|
||||
try
|
||||
set noruler noshowcmd
|
||||
|
||||
let winlen = winwidth(winnr()) - 2
|
||||
let len = len(join(map(copy(a:tokens), 'v:val[1]'), ''))
|
||||
if len < s:prev_echon_len
|
||||
echohl None
|
||||
echon "\r". repeat(' ', min([winlen, s:prev_echon_len]))
|
||||
endif
|
||||
let s:prev_echon_len = len
|
||||
let ellipsis = len > winlen ? '..' : ''
|
||||
|
||||
echon "\r"
|
||||
let yet = 0
|
||||
for [hl, msg] in a:tokens
|
||||
if empty(msg) | continue | endif
|
||||
execute "echohl ". hl
|
||||
let yet += len(msg)
|
||||
if yet > winlen - len(ellipsis)
|
||||
echon msg[ 0 : (winlen - len(ellipsis) - yet - 1) ] . ellipsis
|
||||
break
|
||||
else
|
||||
echon msg
|
||||
endif
|
||||
endfor
|
||||
finally
|
||||
echohl None
|
||||
let [&ruler, &showcmd] = xy
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:echon(l, n, r, d, o)
|
||||
let tokens = [
|
||||
\ ['Function', ':EasyAlign'],
|
||||
\ ['ModeMsg', get(s:mode_labels, a:l, a:l)],
|
||||
\ ['None', ' ']]
|
||||
|
||||
if a:r == -1 | call add(tokens, ['Comment', '(']) | endif
|
||||
call add(tokens, [a:n =~ '*' ? 'Repeat' : 'Number', a:n])
|
||||
call extend(tokens, a:r == 1 ?
|
||||
\ [['Delimiter', '/'], ['String', a:d], ['Delimiter', '/']] :
|
||||
\ [['Identifier', a:d == ' ' ? '\ ' : (a:d == '\' ? '\\' : a:d)]])
|
||||
if a:r == -1 | call add(tokens, ['Comment', ')']) | endif
|
||||
call add(tokens, ['Statement', empty(a:o) ? '' : ' '.string(a:o)])
|
||||
|
||||
call s:echon_(tokens)
|
||||
endfunction
|
||||
|
||||
function! s:exit(msg)
|
||||
echon "\r". a:msg
|
||||
call s:echon_([['ErrorMsg', a:msg]])
|
||||
throw 'exit'
|
||||
endfunction
|
||||
|
||||
@@ -108,7 +161,7 @@ function! s:rtrim(str)
|
||||
endfunction
|
||||
|
||||
function! s:trim(str)
|
||||
return substitute(a:str, '^\s*\(\S*\)\s*$', '\1', '')
|
||||
return substitute(a:str, '^\s*\(.\{-}\)\s*$', '\1', '')
|
||||
endfunction
|
||||
|
||||
function! s:fuzzy_lu(key)
|
||||
@@ -166,6 +219,14 @@ function! s:normalize_options(opts)
|
||||
return s:validate_options(ret)
|
||||
endfunction
|
||||
|
||||
function! s:compact_options(opts)
|
||||
let ret = {}
|
||||
for k in keys(a:opts)
|
||||
let ret[s:shorthand[k]] = a:opts[k]
|
||||
endfor
|
||||
return ret
|
||||
endfunction
|
||||
|
||||
function! s:validate_options(opts)
|
||||
for k in keys(a:opts)
|
||||
let v = a:opts[k]
|
||||
@@ -492,23 +553,23 @@ function! s:input(str, default, vis)
|
||||
call inputsave()
|
||||
let got = input(a:str, a:default)
|
||||
call inputrestore()
|
||||
try
|
||||
return eval(got)
|
||||
catch
|
||||
return got
|
||||
endtry
|
||||
return got
|
||||
endfunction
|
||||
|
||||
function! s:interactive(modes, vis)
|
||||
function! s:atoi(str)
|
||||
return (a:str =~ '^[0-9]\+$') ? str2nr(a:str) : a:str
|
||||
endfunction
|
||||
|
||||
function! s:interactive(modes, vis, opts)
|
||||
let mode = s:shift(a:modes, 1)
|
||||
let n = ''
|
||||
let ch = ''
|
||||
let opts = {}
|
||||
let opts = s:compact_options(a:opts)
|
||||
let vals = deepcopy(s:option_values)
|
||||
let regx = 0
|
||||
|
||||
while 1
|
||||
call s:echon(mode, n, '', opts)
|
||||
call s:echon(mode, n, -1, '', opts)
|
||||
|
||||
let c = getchar()
|
||||
let ch = nr2char(c)
|
||||
@@ -543,9 +604,9 @@ function! s:interactive(modes, vis)
|
||||
elseif ch == "\<C-I>"
|
||||
let opts['idt'] = s:shift(vals['indentation'], 1)
|
||||
elseif ch == "\<C-L>"
|
||||
let opts['lm'] = s:input("Left margin: ", get(opts, 'lm', ''), a:vis)
|
||||
let opts['lm'] = s:atoi(s:input("Left margin: ", get(opts, 'lm', ''), a:vis))
|
||||
elseif ch == "\<C-R>"
|
||||
let opts['rm'] = s:input("Right margin: ", get(opts, 'rm', ''), a:vis)
|
||||
let opts['rm'] = s:atoi(s:input("Right margin: ", get(opts, 'rm', ''), a:vis))
|
||||
elseif ch == "\<C-U>"
|
||||
let opts['iu'] = s:shift(vals['ignore_unmatched'], 1)
|
||||
elseif ch == "\<C-G>"
|
||||
@@ -570,16 +631,24 @@ function! s:interactive(modes, vis)
|
||||
silent! call remove(opts, 'm')
|
||||
endif
|
||||
elseif ch == "\<C-_>" || ch == "\<C-X>"
|
||||
let ch = s:input('Regular expression: ', '', a:vis)
|
||||
let prompt = 'Regular expression: '
|
||||
let ch = s:input(prompt, '', a:vis)
|
||||
if !empty(ch)
|
||||
let regx = 1
|
||||
let s:prev_echon_len = len(prompt . ch)
|
||||
break
|
||||
endif
|
||||
else
|
||||
break
|
||||
endif
|
||||
endwhile
|
||||
return [mode, n, ch, opts, s:normalize_options(opts), regx]
|
||||
return [mode, n, ch, s:normalize_options(opts), regx]
|
||||
endfunction
|
||||
|
||||
function! s:test_regexp(regexp)
|
||||
try | call matchlist('', a:regexp)
|
||||
catch | call s:exit("Invalid regular expression: ". a:regexp)
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:parse_args(args)
|
||||
@@ -626,56 +695,59 @@ function! s:parse_args(args)
|
||||
|
||||
" Found regexp
|
||||
if !empty(matches)
|
||||
let regexp = matches[2]
|
||||
" Test regexp
|
||||
try | call matchlist('', regexp)
|
||||
catch | call s:exit("Invalid regular expression: ". regexp)
|
||||
endtry
|
||||
return [matches[1], regexp, opts, 1]
|
||||
return [matches[1], matches[2], opts, 1]
|
||||
else
|
||||
let tokens = matchlist(args, '^\([1-9][0-9]*\|-[0-9]*\|\*\*\?\)\?\s*\(.\{-}\)\?$')
|
||||
return [tokens[1], tokens[2], opts, 0]
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:modes(bang)
|
||||
return get(g:,
|
||||
\ (a:bang ? 'easy_align_bang_interactive_modes' : 'easy_align_interactive_modes'),
|
||||
\ (a:bang ? ['r', 'l', 'c'] : ['l', 'r', 'c']))
|
||||
endfunction
|
||||
|
||||
function! s:alternating_modes(mode)
|
||||
return a:mode ==? 'r' ? ['r', 'l'] : ['l', 'r']
|
||||
endfunction
|
||||
|
||||
function! easy_align#align(bang, expr) range
|
||||
let modes = get(g:,
|
||||
\ (a:bang ? 'easy_align_bang_interactive_modes' : 'easy_align_interactive_modes'),
|
||||
\ (a:bang ? ['r', 'l', 'c'] : ['l', 'r', 'c']))
|
||||
try
|
||||
call s:align(a:bang, a:firstline, a:lastline, a:expr)
|
||||
catch 'exit'
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
function! s:align(bang, first_line, last_line, expr)
|
||||
let modes = s:modes(a:bang)
|
||||
let mode = modes[0]
|
||||
let recur = 0
|
||||
let n = ''
|
||||
let ch = ''
|
||||
let opts = {}
|
||||
let ioptsr = {}
|
||||
let iopts = {}
|
||||
let regexp = 0
|
||||
" Heuristically determine if the user was in visual mode
|
||||
let vis = a:firstline == line("'<") && a:lastline == line("'>")
|
||||
let vis = a:first_line == line("'<") && a:last_line == line("'>")
|
||||
|
||||
try
|
||||
if empty(a:expr)
|
||||
let [mode, n, ch, ioptsr, iopts, regexp] = s:interactive(copy(modes), vis)
|
||||
else
|
||||
let [n, ch, opts, regexp] = s:parse_args(a:expr)
|
||||
if empty(n) && empty(ch)
|
||||
let [mode, n, ch, ioptsr, iopts, regexp] = s:interactive(copy(modes), vis)
|
||||
elseif empty(ch)
|
||||
" Try swapping n and ch
|
||||
let [n, ch] = ['', n]
|
||||
endif
|
||||
if empty(a:expr)
|
||||
let [mode, n, ch, opts, regexp] = s:interactive(copy(modes), vis, opts)
|
||||
else
|
||||
let [n, ch, opts, regexp] = s:parse_args(a:expr)
|
||||
if empty(n) && empty(ch)
|
||||
let [mode, n, ch, opts, regexp] = s:interactive(copy(modes), vis, opts)
|
||||
elseif empty(ch)
|
||||
" Try swapping n and ch
|
||||
let [n, ch] = ['', n]
|
||||
endif
|
||||
catch 'exit'
|
||||
return
|
||||
endtry
|
||||
endif
|
||||
|
||||
if n == '*' | let [nth, recur] = [1, 1]
|
||||
elseif n == '**' | let [nth, recur] = [1, 2]
|
||||
elseif n == '-' | let nth = -1
|
||||
elseif empty(n) | let nth = 1
|
||||
elseif n == '0' || ( n != '-0' && n != string(str2nr(n)) )
|
||||
echon "\rInvalid field number: ". n
|
||||
return
|
||||
call s:exit('Invalid field number: '. n)
|
||||
else
|
||||
let nth = n
|
||||
endif
|
||||
@@ -686,12 +758,14 @@ function! easy_align#align(bang, expr) range
|
||||
endif
|
||||
|
||||
if regexp
|
||||
call s:test_regexp(ch)
|
||||
let dict = { 'pattern': ch }
|
||||
else
|
||||
" Resolving command-line ambiguity
|
||||
if !empty(a:expr)
|
||||
" '\ ' => ' '
|
||||
if ch =~ '^\\\s\+$'
|
||||
" '\' => ' '
|
||||
if ch =~ '^\\\s*$'
|
||||
let ch = ' '
|
||||
" '\\' => '\'
|
||||
elseif ch =~ '^\\\\\s*$'
|
||||
@@ -699,17 +773,12 @@ function! easy_align#align(bang, expr) range
|
||||
endif
|
||||
endif
|
||||
if !has_key(delimiters, ch)
|
||||
echon "\rUnknown delimiter key: ". ch
|
||||
return
|
||||
call s:exit('Unknown delimiter key: '. ch)
|
||||
endif
|
||||
let dict = delimiters[ch]
|
||||
let dict = copy(delimiters[ch])
|
||||
endif
|
||||
|
||||
for opt in [opts, iopts]
|
||||
if !empty(opt)
|
||||
let dict = extend(copy(dict), opt)
|
||||
endif
|
||||
endfor
|
||||
call extend(dict, opts)
|
||||
|
||||
let ml = get(dict, 'left_margin', ' ')
|
||||
let mr = get(dict, 'right_margin', ' ')
|
||||
@@ -719,12 +788,11 @@ function! easy_align#align(bang, expr) range
|
||||
let bvisual = vis && char2nr(visualmode()) == 22 " ^V
|
||||
|
||||
if recur && bvisual
|
||||
echon "\rRecursive alignment is currently not supported in blockwise-visual mode"
|
||||
return
|
||||
call s:exit('Recursive alignment is not supported in blockwise-visual mode')
|
||||
endif
|
||||
|
||||
let aseq = get(dict, 'mode_sequence',
|
||||
\ recur == 2 ? (mode ==? 'r' ? ['r', 'l'] : ['l', 'r']) : [mode])
|
||||
\ recur == 2 ? s:alternating_modes(mode) : [mode])
|
||||
let mode_expansion = matchstr(aseq, '\*\+$')
|
||||
if mode_expansion == '*'
|
||||
let aseq = aseq[0 : -2]
|
||||
@@ -734,11 +802,11 @@ function! easy_align#align(bang, expr) range
|
||||
let recur = 2
|
||||
endif
|
||||
let aseq_list = type(aseq) == 1 ? split(tolower(aseq), '\s*') : map(copy(aseq), 'tolower(v:val)')
|
||||
let aseq_str = join(aseq_list, '')
|
||||
|
||||
try
|
||||
call s:do_align(
|
||||
call s:do_align(
|
||||
\ aseq_list,
|
||||
\ {}, {}, a:firstline, a:lastline,
|
||||
\ {}, {}, a:first_line, a:last_line,
|
||||
\ bvisual ? min([col("'<"), col("'>")]) : 1,
|
||||
\ bvisual ? max([col("'<"), col("'>")]) : 0,
|
||||
\ get(dict, 'pattern', ch),
|
||||
@@ -751,8 +819,15 @@ function! easy_align#align(bang, expr) range
|
||||
\ get(dict, 'ignore_unmatched', get(g:, 'easy_align_ignore_unmatched', 1)),
|
||||
\ get(dict, 'ignore_groups', get(dict, 'ignores', s:ignored_syntax())),
|
||||
\ recur)
|
||||
call s:echon(mode, n, regexp ? '/'.ch.'/' : ch, ioptsr)
|
||||
catch 'exit'
|
||||
endtry
|
||||
|
||||
let copts = s:compact_options(opts)
|
||||
let nbmode = s:modes(0)[0]
|
||||
if !has_key(copts, 'm') && (
|
||||
\ (recur == 2 && join(s:alternating_modes(nbmode), '') != aseq_str) ||
|
||||
\ (recur != 2 && (aseq_str[0] != nbmode || len(aseq_str) > 1))
|
||||
\ )
|
||||
call extend(copts, { 'm': aseq_str })
|
||||
endif
|
||||
call s:echon('', n, regexp, ch, copts)
|
||||
endfunction
|
||||
|
||||
|
||||
Reference in New Issue
Block a user