Implement live interactive mode (#15)

This commit adds LiveEasyAlign command. (The name was chosen not to introduce
ambiguity when typing in only the prefix of the command: e.g. `:EasyA*|`)
In live interactive mode, the selected text is aligned on-the-fly as the user
type in. In order to finalize the alignment, the user has to type in the
same delimiter key again. (Or CTRL-X on regular expressions)
This commit is contained in:
Junegunn Choi
2013-10-19 19:21:23 +09:00
parent bec3e94383
commit 6e2264672a
6 changed files with 318 additions and 146 deletions

View File

@@ -60,6 +60,7 @@ variant `:EasyAlign!`) for visual mode.
| Interactive mode | `:EasyAlign[!] [OPTIONS]` | | Interactive mode | `:EasyAlign[!] [OPTIONS]` |
| Using predefined rules | `:EasyAlign[!] [N-th] DELIMITER_KEY [OPTIONS]` | | Using predefined rules | `:EasyAlign[!] [N-th] DELIMITER_KEY [OPTIONS]` |
| Using regular expressions | `:EasyAlign[!] [N-th] /REGEXP/ [OPTIONS]` | | Using regular expressions | `:EasyAlign[!] [N-th] /REGEXP/ [OPTIONS]` |
| Live interactive mode | `:LiveEasyAlign[!] [...]` |
### Concept of _alignment rule_ ### Concept of _alignment rule_
@@ -168,17 +169,24 @@ repeatable, non-interactive command recorded in `g:easy_align_last_command`.
:<C-R>=g:easy_align_last_command<Enter><Enter> :<C-R>=g:easy_align_last_command<Enter><Enter>
``` ```
--- ### Live interactive mode
### *Intermission* If you're performing a complex alignment where multiple options should be
carefully adjusted, try "live interactive mode" which aligns the text on-the-fly
as you type in.
You can stop reading here. Trust me. All the fancy features described in the Live interactive mode can be started with `:LiveEasyAlign` command which takes
following sections are really powerful but you won't be needing them in most the same parameters as `:EasyAlign`. I suggest you define the following mapping
of the cases. in addition to the one for `:EasyAlign` command.
Go try out vim-easy-align right now, and come back later when you feel like it. ```vim
vnoremap <silent> <Leader><Enter> :LiveEasyAlign<Enter>
```
--- In live interactive mode, you have to type in the same delimiter (or `CTRL-X` on
regular expression) again to finalize the alignment. This allows you to preview
the result of the alignment and freely change the delimiter using backspace key
without leaving the interactive mode.
### Using `EasyAlign` in command line ### Using `EasyAlign` in command line

View File

@@ -326,13 +326,13 @@ function! s:split_line(line, nth, modes, cycle, fc, lc, pattern, stick_to_left,
return [tokens, delims] return [tokens, delims]
endfunction endfunction
function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, pattern, nth, function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, nth, recur, dict)
\ ml, mr, da, indentation, stick_to_left, ignore_unmatched, ignore_groups, recur)
let mode = a:modes[0] let mode = a:modes[0]
let lines = {} let lines = {}
let min_indent = -1 let min_indent = -1
let max = { 'pivot_len': 0.0, 'token_len': 0, 'just_len': 0, 'delim_len': 0, let max = { 'pivot_len': 0.0, 'token_len': 0, 'just_len': 0, 'delim_len': 0,
\ 'indent': 0, 'tokens': 0, 'strip_len': 0 } \ 'indent': 0, 'tokens': 0, 'strip_len': 0 }
let d = a:dict
" Phase 1 " Phase 1
for line in range(a:fl, a:ll) for line in range(a:fl, a:ll)
@@ -340,8 +340,8 @@ function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, patter
" Split line into the tokens by the delimiters " Split line into the tokens by the delimiters
let [tokens, delims] = s:split_line( let [tokens, delims] = s:split_line(
\ line, a:nth, copy(a:modes), a:recur == 2, \ line, a:nth, copy(a:modes), a:recur == 2,
\ a:fc, a:lc, a:pattern, \ a:fc, a:lc, d.pattern,
\ a:stick_to_left, a:ignore_unmatched, a:ignore_groups) \ d.stick_to_left, d.ignore_unmatched, d.ignore_groups)
" Remember tokens for subsequent recursive calls " Remember tokens for subsequent recursive calls
let a:all_tokens[line] = tokens let a:all_tokens[line] = tokens
@@ -383,7 +383,7 @@ function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, patter
let delim = delims[nth] let delim = delims[nth]
let token = s:rtrim( tokens[nth] ) let token = s:rtrim( tokens[nth] )
let token = s:rtrim( strpart(token, 0, len(token) - len(s:rtrim(delim))) ) let token = s:rtrim( strpart(token, 0, len(token) - len(s:rtrim(delim))) )
if empty(delim) && !exists('tokens[nth + 1]') && a:ignore_unmatched if empty(delim) && !exists('tokens[nth + 1]') && d.ignore_unmatched
continue continue
endif endif
@@ -409,17 +409,18 @@ function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, patter
" Phase 1-5: indentation handling (only on a:nth == 1) " Phase 1-5: indentation handling (only on a:nth == 1)
if a:nth == 1 if a:nth == 1
if a:indentation ==? 'd' let idt = d.indentation
if idt ==? 'd'
let indent = repeat(' ', max.indent) let indent = repeat(' ', max.indent)
elseif a:indentation ==? 's' elseif idt ==? 's'
let indent = repeat(' ', min_indent) let indent = repeat(' ', min_indent)
elseif a:indentation ==? 'n' elseif idt ==? 'n'
let indent = '' let indent = ''
elseif a:indentation !=? 'k' elseif idt !=? 'k'
call s:exit('Invalid indentation: ' . a:indentation) call s:exit('Invalid indentation: ' . idt)
end end
if a:indentation !=? 'k' if idt !=? 'k'
let max.just_len = 0 let max.just_len = 0
let max.token_len = 0 let max.token_len = 0
let max.pivot_len = 0 let max.pivot_len = 0
@@ -461,7 +462,7 @@ function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, patter
let rpad = '' let rpad = ''
if mode ==? 'l' if mode ==? 'l'
let pad = repeat(' ', max.just_len - pw - tw) let pad = repeat(' ', max.just_len - pw - tw)
if a:stick_to_left if d.stick_to_left
let rpad = pad let rpad = pad
else else
let token = token . pad let token = token . pad
@@ -480,7 +481,7 @@ function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, patter
let token = repeat(' ', float2nr(pf1)) .token. repeat(' ', float2nr(p2)) let token = repeat(' ', float2nr(pf1)) .token. repeat(' ', float2nr(p2))
let token = substitute(token, repeat(' ', strip) . '$', '', '') let token = substitute(token, repeat(' ', strip) . '$', '', '')
if a:stick_to_left if d.stick_to_left
if empty(s:rtrim(token)) if empty(s:rtrim(token))
let center = len(token) / 2 let center = len(token) / 2
let [token, rpad] = [strpart(token, 0, center), strpart(token, center)] let [token, rpad] = [strpart(token, 0, center), strpart(token, center)]
@@ -493,15 +494,16 @@ function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, patter
" Pad the delimiter " Pad the delimiter
let dpadl = max.delim_len - s:strwidth(delim) let dpadl = max.delim_len - s:strwidth(delim)
if a:da ==? 'l' let da = d.delimiter_align
if da ==? 'l'
let [dl, dr] = ['', repeat(' ', dpadl)] let [dl, dr] = ['', repeat(' ', dpadl)]
elseif a:da ==? 'c' elseif da ==? 'c'
let dl = repeat(' ', dpadl / 2) let dl = repeat(' ', dpadl / 2)
let dr = repeat(' ', dpadl - dpadl / 2) let dr = repeat(' ', dpadl - dpadl / 2)
elseif a:da ==? 'r' elseif da ==? 'r'
let [dl, dr] = [repeat(' ', dpadl), ''] let [dl, dr] = [repeat(' ', dpadl), '']
else else
call s:exit('Invalid delimiter_align: ' . a:da) call s:exit('Invalid delimiter_align: ' . da)
endif endif
" Before and after the range (for blockwise visual mode) " Before and after the range (for blockwise visual mode)
@@ -511,8 +513,8 @@ function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, patter
" Determine the left and right margin around the delimiter " Determine the left and right margin around the delimiter
let rest = join(tokens[nth + 1 : -1], '') let rest = join(tokens[nth + 1 : -1], '')
let ml = empty(prefix . token) ? '' : a:ml let ml = empty(prefix . token) ? '' : d.ml
let mr = empty(rest.after) ? '' : a:mr let mr = empty(rest.after) ? '' : d.mr
" Adjust indentation of the lines starting with a delimiter " Adjust indentation of the lines starting with a delimiter
let lpad = '' let lpad = ''
@@ -535,12 +537,8 @@ function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, patter
if a:nth < max.tokens && (a:recur || len(a:modes) > 1) if a:nth < max.tokens && (a:recur || len(a:modes) > 1)
call s:shift(a:modes, a:recur == 2) call s:shift(a:modes, a:recur == 2)
return [ return [a:todo, a:modes, a:all_tokens, a:all_delims,
\ a:todo, \ a:fl, a:ll, a:fc, a:lc, a:nth + 1, a:recur, a:dict]
\ a:modes, a:all_tokens, a:all_delims,
\ a:fl, a:ll, a:fc, a:lc, a:pattern,
\ a:nth + 1, a:ml, a:mr, a:da, a:indentation, a:stick_to_left,
\ a:ignore_unmatched, a:ignore_groups, a:recur]
endif endif
return [a:todo] return [a:todo]
endfunction endfunction
@@ -571,26 +569,56 @@ function! s:shift_opts(opts, key, vals)
endif endif
endfunction endfunction
function! s:interactive(modes, vis, opts, delims) function! s:interactive(range, modes, n, d, opts, rules, vis, live)
let mode = s:shift(a:modes, 1) let mode = s:shift(a:modes, 1)
let n = '' let n = a:n
let d = a:d
let ch = '' let ch = ''
let opts = s:compact_options(a:opts) let opts = s:compact_options(a:opts)
let vals = deepcopy(s:option_values) let vals = deepcopy(s:option_values)
let regx = 0 let regx = 0
let warn = '' let warn = ''
let undo = 0
while 1 while 1
call s:echon(mode, n, -1, '', opts, warn) " Live preview
let rdrw = 0
if undo
silent! undo
let undo = 0
let rdrw = 1
endif
if a:live && !empty(d)
let output = s:process(a:range, mode, n, d, s:normalize_options(opts), regx, a:rules, 0)
let &undolevels = &undolevels " Break undo block
call s:update_lines(output.todo)
let undo = 1
let rdrw = 1
endif
if rdrw
if a:vis
normal! gv
endif
redraw
if a:vis | execute "normal! \<esc>" | endif
endif
call s:echon(mode, n, -1, regx ? '/'.d.'/' : d, opts, warn)
let check = 0 let check = 0
let warn = '' let warn = ''
let c = getchar() let c = getchar()
let ch = nr2char(c) let ch = nr2char(c)
if c == 3 || c == 27 " CTRL-C / ESC if c == 3 || c == 27 " CTRL-C / ESC
if undo
silent! undo
endif
throw 'exit' throw 'exit'
elseif c == "\<bs>" elseif c == "\<bs>"
if len(n) > 0 if !empty(d)
let d = ''
let regx = 0
elseif len(n) > 0
let n = strpart(n, 0, len(n) - 1) let n = strpart(n, 0, len(n) - 1)
endif endif
elseif c == 13 " Enter key elseif c == 13 " Enter key
@@ -609,7 +637,7 @@ function! s:interactive(modes, vis, opts, delims)
elseif n == '**' | let n = '' elseif n == '**' | let n = ''
else | let check = 1 else | let check = 1
endif endif
elseif (c == 48 && len(n) > 0) || c > 48 && c <= 57 " Numbers elseif empty(d) && ((c == 48 && len(n) > 0) || c > 48 && c <= 57) " Numbers
if n[0] == '*' | let check = 1 if n[0] == '*' | let check = 1
else | let n = n . ch else | let n = n . ch
end end
@@ -661,11 +689,16 @@ function! s:interactive(modes, vis, opts, delims)
silent! call remove(opts, 'm') silent! call remove(opts, 'm')
endif endif
elseif ch == "\<C-_>" || ch == "\<C-X>" elseif ch == "\<C-_>" || ch == "\<C-X>"
if a:live && regx && !empty(d)
break
endif
let prompt = 'Regular expression: ' let prompt = 'Regular expression: '
let ch = s:input(prompt, '', a:vis) let ch = s:input(prompt, '', a:vis)
if !empty(ch) && s:valid_regexp(ch) if !empty(ch) && s:valid_regexp(ch)
let regx = 1 let regx = 1
break let d = ch
if !a:live | break | endif
else else
let warn = 'Invalid regular expression: '.ch let warn = 'Invalid regular expression: '.ch
endif endif
@@ -676,14 +709,33 @@ function! s:interactive(modes, vis, opts, delims)
endif endif
if check if check
if has_key(a:delims, ch) if empty(d)
break if has_key(a:rules, ch)
let d = ch
if !a:live
break
endif
else
let warn = 'Unknown delimiter key: '.ch
endif
else else
let warn = 'Unknown delimiter key: '.ch if regx
let warn = 'Press <CTRL-X> to finish'
else
if d == ch
break
else
let warn = 'Press '''.d.''' again to finish'
endif
end
endif endif
endif endif
endwhile endwhile
return [mode, n, ch, s:normalize_options(opts), regx] if a:live
let copts = call('s:summarize', output.summarize)
let g:easy_align_last_command = s:echon('', n, regx, d, copts, '')
end
return [mode, n, ch, opts, regx]
endfunction endfunction
function! s:valid_regexp(regexp) function! s:valid_regexp(regexp)
@@ -751,6 +803,9 @@ function! s:parse_shorthand_opts(expr)
endfunction endfunction
function! s:parse_args(args) function! s:parse_args(args)
if empty(a:args)
return ['', '', {}, 0]
endif
let n = '' let n = ''
let ch = '' let ch = ''
let args = a:args let args = a:args
@@ -804,55 +859,42 @@ function! s:parse_args(args)
return [matches[1], s:test_regexp(matches[2]), opts, 1] return [matches[1], s:test_regexp(matches[2]), opts, 1]
else else
let tokens = matchlist(args, '^\([1-9][0-9]*\|-[0-9]*\|\*\*\?\)\?\s*\(.\{-}\)\?$') let tokens = matchlist(args, '^\([1-9][0-9]*\|-[0-9]*\|\*\*\?\)\?\s*\(.\{-}\)\?$')
return [tokens[1], tokens[2], opts, 0] " Try swapping n and ch
let [n, ch] = empty(tokens[2]) ? reverse(tokens[1:2]) : tokens[1:2]
" Resolving command-line ambiguity
" '\ ' => ' '
" '\' => ' '
if ch =~ '^\\\s*$'
let ch = ' '
" '\\' => '\'
elseif ch =~ '^\\\\\s*$'
let ch = '\'
endif
return [n, ch, opts, 0]
endif endif
endfunction endfunction
function! s:modes(bang) function! s:interactive_modes(bang)
return get(g:, return get(g:,
\ (a:bang ? 'easy_align_bang_interactive_modes' : 'easy_align_interactive_modes'), \ (a:bang ? 'easy_align_bang_interactive_modes' : 'easy_align_interactive_modes'),
\ (a:bang ? ['r', 'l', 'c'] : ['l', 'r', 'c'])) \ (a:bang ? ['r', 'l', 'c'] : ['l', 'r', 'c']))
endfunction endfunction
function! s:alternating_modes(mode) function! s:alternating_modes(mode)
return a:mode ==? 'r' ? ['r', 'l'] : ['l', 'r'] return a:mode ==? 'r' ? 'rl' : 'lr'
endfunction endfunction
function! easy_align#align(bang, expr) range function! s:update_lines(todo)
try for [line, content] in items(a:todo)
call s:align(a:bang, a:firstline, a:lastline, a:expr) call setline(line, s:rtrim(content))
catch 'exit' endfor
endtry
endfunction endfunction
function! s:align(bang, first_line, last_line, expr) function! s:parse_nth(n)
let modes = s:modes(a:bang) let n = a:n
let mode = modes[0] let recur = 0
let recur = 0
let n = ''
let ch = ''
let opts = {}
let regexp = 0
" Heuristically determine if the user was in visual mode
let vis = a:first_line == line("'<") && a:last_line == line("'>")
let delimiters = s:easy_align_delimiters_default
if exists('g:easy_align_delimiters')
let delimiters = extend(copy(delimiters), g:easy_align_delimiters)
endif
if empty(a:expr)
let [mode, n, ch, opts, regexp] = s:interactive(copy(modes), vis, opts, delimiters)
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, delimiters)
elseif empty(ch)
" Try swapping n and ch
let [n, ch] = ['', n]
endif
endif
if n == '*' | let [nth, recur] = [1, 1] if n == '*' | let [nth, recur] = [1, 1]
elseif n == '**' | let [nth, recur] = [1, 2] elseif n == '**' | let [nth, recur] = [1, 2]
elseif n == '-' | let nth = -1 elseif n == '-' | let nth = -1
@@ -862,93 +904,134 @@ function! s:align(bang, first_line, last_line, expr)
else else
let nth = n let nth = n
endif endif
return [nth, recur]
endfunction
if regexp function! s:build_dict(delimiters, ch, regexp, opts)
let dict = { 'pattern': ch } if a:regexp
let dict = { 'pattern': a:ch }
else else
" Resolving command-line ambiguity if !has_key(a:delimiters, a:ch)
if !empty(a:expr) call s:exit('Unknown delimiter key: '. a:ch)
" '\ ' => ' '
" '\' => ' '
if ch =~ '^\\\s*$'
let ch = ' '
" '\\' => '\'
elseif ch =~ '^\\\\\s*$'
let ch = '\'
endif
endif endif
if !has_key(delimiters, ch) let dict = copy(a:delimiters[a:ch])
call s:exit('Unknown delimiter key: '. ch)
endif
let dict = copy(delimiters[ch])
endif endif
call extend(dict, a:opts)
call extend(dict, opts)
let ml = get(dict, 'left_margin', ' ') let ml = get(dict, 'left_margin', ' ')
let mr = get(dict, 'right_margin', ' ') let mr = get(dict, 'right_margin', ' ')
if type(ml) == 0 | let ml = repeat(' ', ml) | endif if type(ml) == 0 | let ml = repeat(' ', ml) | endif
if type(mr) == 0 | let mr = repeat(' ', mr) | endif if type(mr) == 0 | let mr = repeat(' ', mr) | endif
call extend(dict, { 'ml': ml, 'mr': mr })
let bvisual = vis && char2nr(visualmode()) == 22 " ^V let dict.pattern = get(dict, 'pattern', a:ch)
let dict.delimiter_align =
\ get(dict, 'delimiter_align', get(g:, 'easy_align_delimiter_align', 'r'))[0]
let dict.indentation =
\ get(dict, 'indentation', get(g:, 'easy_align_indentation', 'k'))[0]
let dict.stick_to_left =
\ get(dict, 'stick_to_left', 0)
let dict.ignore_unmatched =
\ get(dict, 'ignore_unmatched', get(g:, 'easy_align_ignore_unmatched', 2))
let dict.ignore_groups =
\ get(dict, 'ignore_groups', get(dict, 'ignores', s:ignored_syntax()))
return dict
endfunction
if recur && bvisual function! s:build_mode_sequence(expr, recur)
let [expr, recur] = [a:expr, a:recur]
let suffix = matchstr(a:expr, '\*\+$')
if suffix == '*'
let expr = expr[0 : -2]
let recur = 1
elseif suffix == '**'
let expr = expr[0 : -3]
let recur = 2
endif
return [tolower(expr), recur]
endfunction
function! s:process(range, mode, n, ch, opts, regexp, rules, bvis)
let [nth, recur] = s:parse_nth(a:n)
let dict = s:build_dict(a:rules, a:ch, a:regexp, a:opts)
let [mode_sequence, recur] = s:build_mode_sequence(
\ get(dict, 'mode_sequence', recur == 2 ? s:alternating_modes(a:mode) : a:mode),
\ recur)
if recur && a:bvis
call s:exit('Recursive alignment is not supported in blockwise-visual mode') call s:exit('Recursive alignment is not supported in blockwise-visual mode')
endif endif
let aseq = get(dict, 'mode_sequence',
\ recur == 2 ? s:alternating_modes(mode) : [mode])
let mode_expansion = matchstr(aseq, '\*\+$')
if mode_expansion == '*'
let aseq = aseq[0 : -2]
let recur = 1
elseif mode_expansion == '**'
let aseq = aseq[0 : -3]
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, '')
let todo = {}
let args = [ let args = [
\ todo, \ {}, split(mode_sequence, '\zs'),
\ aseq_list, \ {}, {}, a:range[0], a:range[1],
\ {}, {}, a:first_line, a:last_line, \ a:bvis ? min([col("'<"), col("'>")]) : 1,
\ bvisual ? min([col("'<"), col("'>")]) : 1, \ a:bvis ? max([col("'<"), col("'>")]) : 0,
\ bvisual ? max([col("'<"), col("'>")]) : 0, \ nth, recur, dict ]
\ get(dict, 'pattern', ch),
\ nth,
\ ml,
\ mr,
\ get(dict, 'delimiter_align', get(g:, 'easy_align_delimiter_align', 'r'))[0],
\ get(dict, 'indentation', get(g:, 'easy_align_indentation', 'k'))[0],
\ get(dict, 'stick_to_left', 0),
\ get(dict, 'ignore_unmatched', get(g:, 'easy_align_ignore_unmatched', 2)),
\ get(dict, 'ignore_groups', get(dict, 'ignores', s:ignored_syntax())),
\ recur ]
while len(args) > 1 while len(args) > 1
let args = call('s:do_align', args) let args = call('s:do_align', args)
endwhile endwhile
" todo: lines to update
" summarize: arguments to s:summarize
return { 'todo': args[0], 'summarize': [ a:opts, recur, mode_sequence ] }
endfunction
function s:summarize(opts, recur, mode_sequence)
let copts = s:compact_options(a:opts)
let nbmode = s:interactive_modes(0)[0]
if !has_key(copts, 'm') && (
\ (a:recur == 2 && s:alternating_modes(nbmode) != a:mode_sequence) ||
\ (a:recur != 2 && (a:mode_sequence[0] != nbmode || len(a:mode_sequence) > 1))
\ )
call extend(copts, { 'm': a:mode_sequence })
endif
return copts
endfunction
function! s:align(bang, live, first_line, last_line, expr)
" Heuristically determine if the user was in visual mode
let vis = a:first_line == line("'<") && a:last_line == line("'>")
let bvis = vis && char2nr(visualmode()) == 22 " ^V
let range = [a:first_line, a:last_line]
let modes = s:interactive_modes(a:bang)
let mode = modes[0]
if bvis && a:live
call s:exit('Live mode is not supported in blockwise-visual mode')
endif
let rules = s:easy_align_delimiters_default
if exists('g:easy_align_delimiters')
let rules = extend(copy(rules), g:easy_align_delimiters)
endif
let [n, ch, opts, regexp] = s:parse_args(a:expr)
let bypass_fold = get(g:, 'easy_align_bypass_fold', 0) let bypass_fold = get(g:, 'easy_align_bypass_fold', 0)
let ofm = &l:foldmethod let ofm = &l:foldmethod
try try
if bypass_fold | let &l:foldmethod = 'manual' | endif if bypass_fold | let &l:foldmethod = 'manual' | endif
for [line, content] in items(todo)
call setline(line, s:rtrim(content)) if empty(n) && empty(ch) || a:live
endfor let [mode, n, ch, opts, regexp] = s:interactive(range, copy(modes), n, ch, opts, rules, vis, a:live)
endif
if !a:live
let output = s:process(range, mode, n, ch, s:normalize_options(opts), regexp, rules, bvis)
call s:update_lines(output.todo)
let copts = call('s:summarize', output.summarize)
let g:easy_align_last_command = s:echon('', n, regexp, ch, copts, '')
endif
finally finally
if bypass_fold | let &l:foldmethod = ofm | endif if bypass_fold | let &l:foldmethod = ofm | endif
endtry endtry
endfunction
let copts = s:compact_options(opts)
let nbmode = s:modes(0)[0] function! easy_align#align(bang, live, expr) range
if !has_key(copts, 'm') && ( try
\ (recur == 2 && join(s:alternating_modes(nbmode), '') != aseq_str) || call s:align(a:bang, a:live, a:firstline, a:lastline, a:expr)
\ (recur != 2 && (aseq_str[0] != nbmode || len(aseq_str) > 1)) catch 'exit'
\ ) endtry
call extend(copts, { 'm': aseq_str })
endif
let g:easy_align_last_command = s:echon('', n, regexp, ch, copts, '')
endfunction endfunction

View File

@@ -108,6 +108,25 @@ repeatable, non-interactive command recorded in `g:easy_align_last_command`.
:<C-R>=g:easy_align_last_command<Enter><Enter> :<C-R>=g:easy_align_last_command<Enter><Enter>
Live interactive mode *:LiveEasyAlign* *:LiveEasyAlign!*
-------------------------------------------------------------------------
If you're performing a complex alignment where multiple options should be
carefully adjusted, try "live interactive mode" which aligns the text
on-the-fly as you type in.
Live interactive mode can be started with `:LiveEasyAlign` command which
takes the same parameters as `:EasyAlign`. I suggest you define the
following mapping in addition to the one for `:EasyAlign` command.
vnoremap <silent> <Leader><Enter> :LiveEasyAlign<Enter>
In live interactive mode, you have to type in the same delimiter (or
`CTRL-X` on regular expression) again to finalize the alignment. This
allows you to preview the result of the alignment and freely change the
delimiter using backspace key without leaving the interactive mode.
Left/right/center mode switch in interactive mode Left/right/center mode switch in interactive mode
------------------------------------------------------------------------- -------------------------------------------------------------------------
*g:easy_align_interactive_modes* *g:easy_align_interactive_modes*

View File

@@ -26,4 +26,5 @@ if exists("g:loaded_easy_align_plugin")
endif endif
let g:loaded_easy_align_plugin = 1 let g:loaded_easy_align_plugin = 1
command! -nargs=* -range -bang EasyAlign <line1>,<line2>call easy_align#align('<bang>' == '!', <q-args>) command! -nargs=* -range -bang EasyAlign <line1>,<line2>call easy_align#align('<bang>' == '!', 0, <q-args>)
command! -nargs=* -range -bang LiveEasyAlign <line1>,<line2>call easy_align#align('<bang>' == '!', 1, <q-args>)

View File

@@ -16,6 +16,7 @@ Given (fruits):
Execute (regular expression): Execute (regular expression):
%EasyAlign/[:;]\+/ %EasyAlign/[:;]\+/
AssertEqual ':EasyAlign /[:;]\+/', g:easy_align_last_command
Expect: Expect:
apple ;:;; banana::cake apple ;:;; banana::cake

View File

@@ -3,6 +3,7 @@ Execute (Clean up test environment):
Save g:easy_align_indentation, g:easy_align_delimiter_align Save g:easy_align_indentation, g:easy_align_delimiter_align
Save g:easy_align_interactive_modes, g:easy_align_bang_interactive_modes Save g:easy_align_interactive_modes, g:easy_align_bang_interactive_modes
Save g:easy_align_delimiters Save g:easy_align_delimiters
Save mapleader
" TODO: revert after test " TODO: revert after test
silent! unlet g:easy_align_ignore_groups silent! unlet g:easy_align_ignore_groups
@@ -13,8 +14,11 @@ Execute (Clean up test environment):
silent! unlet g:easy_align_bang_interactive_modes silent! unlet g:easy_align_bang_interactive_modes
let g:easy_align_delimiters = {} let g:easy_align_delimiters = {}
vnoremap <silent> <Enter> :EasyAlign<Enter> let mapleader = ' '
vnoremap <silent> <Space><Enter> :EasyAlign!<Enter> vnoremap <silent> <Enter> :EasyAlign<Enter>
vnoremap <silent> r<Enter> :EasyAlign!<Enter>
vnoremap <silent> <Leader><Enter> :LiveEasyAlign<Enter>
vnoremap <silent> <Leader>r<Enter> :LiveEasyAlign!<Enter>
########################################################### ###########################################################
@@ -477,6 +481,62 @@ Expect:
|batch_size|Fixnum | nil|number of maximum items to be assigned at once| |batch_size|Fixnum | nil|number of maximum items to be assigned at once|
|logger |Logger | nil|logger instance for debug logs | |logger |Logger | nil|logger instance for debug logs |
Do (live interactive mode):
vip\<Space>\<Enter>
|
\<C-L>(\<Enter>
\<C-R>)\<Enter>
***
**
\<BS>\<BS>\<BS>
**|\<Enter>|
Expect:
|)Option (|) Type(|)Default(|) Description(|
|)-- (|) --(|)-- (|) --(|
|)threads (|) Fixnum(|)1 (|) number of threads in the thread pool(|
|)queues (|) Fixnum(|)1 (|) number of concurrent queues(|
|)queue_size(|) Fixnum(|)1000 (|) size of each queue(|
|)interval (|)Numeric(|)0 (|) dispatcher interval for batch processing(|
|)batch (|)Boolean(|)false (|) enables batch processing mode(|
|)batch_size(|) Fixnum(|)nil (|)number of maximum items to be assigned at once(|
|)logger (|) Logger(|)nil (|) logger instance for debug logs(|
Do (live interactive mode!):
vip\<Space>r\<Enter>
|
\<C-L>[\<Enter>
\<C-R>]\<Enter>
***
**
\<BS>\<BS>\<BS>
**
\<C-I>\<C-I>
\<C-X>|\<Enter>
\<Enter>
\<C-X>
Expect:
[|] Option[|]Type [|]Default[|]Description [|[
[|] --[|]-- [|] --[|]-- [|[
[|] threads[|]Fixnum [|] 1[|]number of threads in the thread pool [|[
[|] queues[|]Fixnum [|] 1[|]number of concurrent queues [|[
[|]queue_size[|]Fixnum [|] 1000[|]size of each queue [|[
[|] interval[|]Numeric[|] 0[|]dispatcher interval for batch processing [|[
[|] batch[|]Boolean[|] false[|]enables batch processing mode [|[
[|]batch_size[|]Fixnum [|] nil[|]number of maximum items to be assigned at once[|[
[|] logger[|]Logger [|] nil[|]logger instance for debug logs [|[
Execute (g:easy_align_last_command should be set):
Assert exists('g:easy_align_last_command')
unlet g:easy_align_last_command
Do:
vip\<Space>\<Enter>**|\<C-C>
Execute (g:easy_align_last_command should not be set if interrupted):
Assert !exists('g:easy_align_last_command')
########################################################### ###########################################################
Given (comma-separated items): Given (comma-separated items):
@@ -1214,7 +1274,7 @@ Expect:
ccc = 3 ccc = 3
Do: Do:
vip\<Space>\<Enter>= vipr\<Enter>=
Expect: Expect:
a = 1 a = 1
@@ -1222,7 +1282,7 @@ Expect:
ccc = 3 ccc = 3
Do: Do:
vip\<Space>\<Enter>\<Enter>= vipr\<Enter>\<Enter>=
Expect: Expect:
a = 1 a = 1
@@ -1230,7 +1290,7 @@ Expect:
ccc = 3 ccc = 3
Do: Do:
vip\<Space>\<Enter>\<Enter>\<Enter>= vipr\<Enter>\<Enter>\<Enter>=
Expect: Expect:
a = 1 a = 1