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]` |
|
| 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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
if has_key(a:rules, ch)
|
||||||
|
let d = ch
|
||||||
|
if !a:live
|
||||||
break
|
break
|
||||||
|
endif
|
||||||
else
|
else
|
||||||
let warn = 'Unknown delimiter key: '.ch
|
let warn = 'Unknown delimiter key: '.ch
|
||||||
endif
|
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
|
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
|
||||||
|
let dict = copy(a:delimiters[a:ch])
|
||||||
endif
|
endif
|
||||||
if !has_key(delimiters, ch)
|
call extend(dict, a:opts)
|
||||||
call s:exit('Unknown delimiter key: '. ch)
|
|
||||||
endif
|
|
||||||
let dict = copy(delimiters[ch])
|
|
||||||
endif
|
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -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*
|
||||||
|
|||||||
@@ -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>)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 = {}
|
||||||
|
let mapleader = ' '
|
||||||
vnoremap <silent> <Enter> :EasyAlign<Enter>
|
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|
|
|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
|
||||||
|
|||||||
Reference in New Issue
Block a user