mirror of
https://github.com/junegunn/vim-easy-align.git
synced 2025-11-15 13:23:48 -05:00
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:
22
README.md
22
README.md
@@ -60,6 +60,7 @@ variant `:EasyAlign!`) for visual mode.
|
||||
| Interactive mode | `:EasyAlign[!] [OPTIONS]` |
|
||||
| Using predefined rules | `:EasyAlign[!] [N-th] DELIMITER_KEY [OPTIONS]` |
|
||||
| Using regular expressions | `:EasyAlign[!] [N-th] /REGEXP/ [OPTIONS]` |
|
||||
| Live interactive mode | `:LiveEasyAlign[!] [...]` |
|
||||
|
||||
### 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>
|
||||
```
|
||||
|
||||
---
|
||||
### 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
|
||||
following sections are really powerful but you won't be needing them in most
|
||||
of the cases.
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -326,13 +326,13 @@ function! s:split_line(line, nth, modes, cycle, fc, lc, pattern, stick_to_left,
|
||||
return [tokens, delims]
|
||||
endfunction
|
||||
|
||||
function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, pattern, nth,
|
||||
\ ml, mr, da, indentation, stick_to_left, ignore_unmatched, ignore_groups, recur)
|
||||
function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, nth, recur, dict)
|
||||
let mode = a:modes[0]
|
||||
let lines = {}
|
||||
let min_indent = -1
|
||||
let max = { 'pivot_len': 0.0, 'token_len': 0, 'just_len': 0, 'delim_len': 0,
|
||||
\ 'indent': 0, 'tokens': 0, 'strip_len': 0 }
|
||||
let d = a:dict
|
||||
|
||||
" Phase 1
|
||||
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
|
||||
let [tokens, delims] = s:split_line(
|
||||
\ line, a:nth, copy(a:modes), a:recur == 2,
|
||||
\ a:fc, a:lc, a:pattern,
|
||||
\ a:stick_to_left, a:ignore_unmatched, a:ignore_groups)
|
||||
\ a:fc, a:lc, d.pattern,
|
||||
\ d.stick_to_left, d.ignore_unmatched, d.ignore_groups)
|
||||
|
||||
" Remember tokens for subsequent recursive calls
|
||||
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 token = s:rtrim( tokens[nth] )
|
||||
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
|
||||
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)
|
||||
if a:nth == 1
|
||||
if a:indentation ==? 'd'
|
||||
let idt = d.indentation
|
||||
if idt ==? 'd'
|
||||
let indent = repeat(' ', max.indent)
|
||||
elseif a:indentation ==? 's'
|
||||
elseif idt ==? 's'
|
||||
let indent = repeat(' ', min_indent)
|
||||
elseif a:indentation ==? 'n'
|
||||
elseif idt ==? 'n'
|
||||
let indent = ''
|
||||
elseif a:indentation !=? 'k'
|
||||
call s:exit('Invalid indentation: ' . a:indentation)
|
||||
elseif idt !=? 'k'
|
||||
call s:exit('Invalid indentation: ' . idt)
|
||||
end
|
||||
|
||||
if a:indentation !=? 'k'
|
||||
if idt !=? 'k'
|
||||
let max.just_len = 0
|
||||
let max.token_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 = ''
|
||||
if mode ==? 'l'
|
||||
let pad = repeat(' ', max.just_len - pw - tw)
|
||||
if a:stick_to_left
|
||||
if d.stick_to_left
|
||||
let rpad = pad
|
||||
else
|
||||
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 = substitute(token, repeat(' ', strip) . '$', '', '')
|
||||
|
||||
if a:stick_to_left
|
||||
if d.stick_to_left
|
||||
if empty(s:rtrim(token))
|
||||
let center = len(token) / 2
|
||||
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
|
||||
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)]
|
||||
elseif a:da ==? 'c'
|
||||
elseif da ==? 'c'
|
||||
let dl = repeat(' ', dpadl / 2)
|
||||
let dr = repeat(' ', dpadl - dpadl / 2)
|
||||
elseif a:da ==? 'r'
|
||||
elseif da ==? 'r'
|
||||
let [dl, dr] = [repeat(' ', dpadl), '']
|
||||
else
|
||||
call s:exit('Invalid delimiter_align: ' . a:da)
|
||||
call s:exit('Invalid delimiter_align: ' . da)
|
||||
endif
|
||||
|
||||
" 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
|
||||
let rest = join(tokens[nth + 1 : -1], '')
|
||||
let ml = empty(prefix . token) ? '' : a:ml
|
||||
let mr = empty(rest.after) ? '' : a:mr
|
||||
let ml = empty(prefix . token) ? '' : d.ml
|
||||
let mr = empty(rest.after) ? '' : d.mr
|
||||
|
||||
" Adjust indentation of the lines starting with a delimiter
|
||||
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)
|
||||
call s:shift(a:modes, a:recur == 2)
|
||||
return [
|
||||
\ a:todo,
|
||||
\ 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]
|
||||
return [a:todo, a:modes, a:all_tokens, a:all_delims,
|
||||
\ a:fl, a:ll, a:fc, a:lc, a:nth + 1, a:recur, a:dict]
|
||||
endif
|
||||
return [a:todo]
|
||||
endfunction
|
||||
@@ -571,26 +569,56 @@ function! s:shift_opts(opts, key, vals)
|
||||
endif
|
||||
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 n = ''
|
||||
let n = a:n
|
||||
let d = a:d
|
||||
let ch = ''
|
||||
let opts = s:compact_options(a:opts)
|
||||
let vals = deepcopy(s:option_values)
|
||||
let regx = 0
|
||||
let warn = ''
|
||||
let undo = 0
|
||||
|
||||
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 warn = ''
|
||||
|
||||
let c = getchar()
|
||||
let ch = nr2char(c)
|
||||
if c == 3 || c == 27 " CTRL-C / ESC
|
||||
if undo
|
||||
silent! undo
|
||||
endif
|
||||
throw 'exit'
|
||||
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)
|
||||
endif
|
||||
elseif c == 13 " Enter key
|
||||
@@ -609,7 +637,7 @@ function! s:interactive(modes, vis, opts, delims)
|
||||
elseif n == '**' | let n = ''
|
||||
else | let check = 1
|
||||
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
|
||||
else | let n = n . ch
|
||||
end
|
||||
@@ -661,11 +689,16 @@ function! s:interactive(modes, vis, opts, delims)
|
||||
silent! call remove(opts, 'm')
|
||||
endif
|
||||
elseif ch == "\<C-_>" || ch == "\<C-X>"
|
||||
if a:live && regx && !empty(d)
|
||||
break
|
||||
endif
|
||||
|
||||
let prompt = 'Regular expression: '
|
||||
let ch = s:input(prompt, '', a:vis)
|
||||
if !empty(ch) && s:valid_regexp(ch)
|
||||
let regx = 1
|
||||
break
|
||||
let d = ch
|
||||
if !a:live | break | endif
|
||||
else
|
||||
let warn = 'Invalid regular expression: '.ch
|
||||
endif
|
||||
@@ -676,14 +709,33 @@ function! s:interactive(modes, vis, opts, delims)
|
||||
endif
|
||||
|
||||
if check
|
||||
if has_key(a:delims, ch)
|
||||
if empty(d)
|
||||
if has_key(a:rules, ch)
|
||||
let d = ch
|
||||
if !a:live
|
||||
break
|
||||
endif
|
||||
else
|
||||
let warn = 'Unknown delimiter key: '.ch
|
||||
endif
|
||||
else
|
||||
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
|
||||
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
|
||||
|
||||
function! s:valid_regexp(regexp)
|
||||
@@ -751,6 +803,9 @@ function! s:parse_shorthand_opts(expr)
|
||||
endfunction
|
||||
|
||||
function! s:parse_args(args)
|
||||
if empty(a:args)
|
||||
return ['', '', {}, 0]
|
||||
endif
|
||||
let n = ''
|
||||
let ch = ''
|
||||
let args = a:args
|
||||
@@ -804,55 +859,42 @@ function! s:parse_args(args)
|
||||
return [matches[1], s:test_regexp(matches[2]), opts, 1]
|
||||
else
|
||||
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
|
||||
endfunction
|
||||
|
||||
function! s:modes(bang)
|
||||
function! s:interactive_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']
|
||||
return a:mode ==? 'r' ? 'rl' : 'lr'
|
||||
endfunction
|
||||
|
||||
function! easy_align#align(bang, expr) range
|
||||
try
|
||||
call s:align(a:bang, a:firstline, a:lastline, a:expr)
|
||||
catch 'exit'
|
||||
endtry
|
||||
function! s:update_lines(todo)
|
||||
for [line, content] in items(a:todo)
|
||||
call setline(line, s:rtrim(content))
|
||||
endfor
|
||||
endfunction
|
||||
|
||||
function! s:align(bang, first_line, last_line, expr)
|
||||
let modes = s:modes(a:bang)
|
||||
let mode = modes[0]
|
||||
function! s:parse_nth(n)
|
||||
let n = a:n
|
||||
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]
|
||||
elseif n == '**' | let [nth, recur] = [1, 2]
|
||||
elseif n == '-' | let nth = -1
|
||||
@@ -862,93 +904,134 @@ function! s:align(bang, first_line, last_line, expr)
|
||||
else
|
||||
let nth = n
|
||||
endif
|
||||
return [nth, recur]
|
||||
endfunction
|
||||
|
||||
if regexp
|
||||
let dict = { 'pattern': ch }
|
||||
function! s:build_dict(delimiters, ch, regexp, opts)
|
||||
if a:regexp
|
||||
let dict = { 'pattern': a:ch }
|
||||
else
|
||||
" Resolving command-line ambiguity
|
||||
if !empty(a:expr)
|
||||
" '\ ' => ' '
|
||||
" '\' => ' '
|
||||
if ch =~ '^\\\s*$'
|
||||
let ch = ' '
|
||||
" '\\' => '\'
|
||||
elseif ch =~ '^\\\\\s*$'
|
||||
let ch = '\'
|
||||
if !has_key(a:delimiters, a:ch)
|
||||
call s:exit('Unknown delimiter key: '. a:ch)
|
||||
endif
|
||||
let dict = copy(a:delimiters[a:ch])
|
||||
endif
|
||||
if !has_key(delimiters, ch)
|
||||
call s:exit('Unknown delimiter key: '. ch)
|
||||
endif
|
||||
let dict = copy(delimiters[ch])
|
||||
endif
|
||||
|
||||
call extend(dict, opts)
|
||||
call extend(dict, a:opts)
|
||||
|
||||
let ml = get(dict, 'left_margin', ' ')
|
||||
let mr = get(dict, 'right_margin', ' ')
|
||||
if type(ml) == 0 | let ml = repeat(' ', ml) | 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')
|
||||
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 = [
|
||||
\ todo,
|
||||
\ aseq_list,
|
||||
\ {}, {}, a:first_line, a:last_line,
|
||||
\ bvisual ? min([col("'<"), col("'>")]) : 1,
|
||||
\ bvisual ? max([col("'<"), col("'>")]) : 0,
|
||||
\ 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 ]
|
||||
\ {}, split(mode_sequence, '\zs'),
|
||||
\ {}, {}, a:range[0], a:range[1],
|
||||
\ a:bvis ? min([col("'<"), col("'>")]) : 1,
|
||||
\ a:bvis ? max([col("'<"), col("'>")]) : 0,
|
||||
\ nth, recur, dict ]
|
||||
while len(args) > 1
|
||||
let args = call('s:do_align', args)
|
||||
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 ofm = &l:foldmethod
|
||||
try
|
||||
if bypass_fold | let &l:foldmethod = 'manual' | endif
|
||||
for [line, content] in items(todo)
|
||||
call setline(line, s:rtrim(content))
|
||||
endfor
|
||||
|
||||
if empty(n) && empty(ch) || a:live
|
||||
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
|
||||
if bypass_fold | let &l:foldmethod = ofm | endif
|
||||
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
|
||||
let g:easy_align_last_command = s:echon('', n, regexp, ch, copts, '')
|
||||
endfunction
|
||||
|
||||
function! easy_align#align(bang, live, expr) range
|
||||
try
|
||||
call s:align(a:bang, a:live, a:firstline, a:lastline, a:expr)
|
||||
catch 'exit'
|
||||
endtry
|
||||
endfunction
|
||||
|
||||
|
||||
@@ -108,6 +108,25 @@ repeatable, non-interactive command recorded in `g:easy_align_last_command`.
|
||||
:<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
|
||||
-------------------------------------------------------------------------
|
||||
*g:easy_align_interactive_modes*
|
||||
|
||||
@@ -26,4 +26,5 @@ if exists("g:loaded_easy_align_plugin")
|
||||
endif
|
||||
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>)
|
||||
|
||||
@@ -16,6 +16,7 @@ Given (fruits):
|
||||
|
||||
Execute (regular expression):
|
||||
%EasyAlign/[:;]\+/
|
||||
AssertEqual ':EasyAlign /[:;]\+/', g:easy_align_last_command
|
||||
|
||||
Expect:
|
||||
apple ;:;; banana::cake
|
||||
|
||||
@@ -3,6 +3,7 @@ Execute (Clean up test environment):
|
||||
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_delimiters
|
||||
Save mapleader
|
||||
|
||||
" TODO: revert after test
|
||||
silent! unlet g:easy_align_ignore_groups
|
||||
@@ -13,8 +14,11 @@ Execute (Clean up test environment):
|
||||
silent! unlet g:easy_align_bang_interactive_modes
|
||||
|
||||
let g:easy_align_delimiters = {}
|
||||
let mapleader = ' '
|
||||
vnoremap <silent> <Enter> :EasyAlign<Enter>
|
||||
vnoremap <silent> <Space><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|
|
||||
|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):
|
||||
@@ -1214,7 +1274,7 @@ Expect:
|
||||
ccc = 3
|
||||
|
||||
Do:
|
||||
vip\<Space>\<Enter>=
|
||||
vipr\<Enter>=
|
||||
|
||||
Expect:
|
||||
a = 1
|
||||
@@ -1222,7 +1282,7 @@ Expect:
|
||||
ccc = 3
|
||||
|
||||
Do:
|
||||
vip\<Space>\<Enter>\<Enter>=
|
||||
vipr\<Enter>\<Enter>=
|
||||
|
||||
Expect:
|
||||
a = 1
|
||||
@@ -1230,7 +1290,7 @@ Expect:
|
||||
ccc = 3
|
||||
|
||||
Do:
|
||||
vip\<Space>\<Enter>\<Enter>\<Enter>=
|
||||
vipr\<Enter>\<Enter>\<Enter>=
|
||||
|
||||
Expect:
|
||||
a = 1
|
||||
|
||||
Reference in New Issue
Block a user