11 Commits
2.7.0 ... 2.8.1

Author SHA1 Message Date
Junegunn Choi
3921d0bab3 Fix #20 - Alignment with tabs inserting spaces 2013-11-20 07:27:39 +09:00
Junegunn Choi
ea1ea51ef1 Fix link to slow GIF 2013-11-15 02:27:14 +09:00
Junegunn Choi
2daae46a43 Fix indentation option to work with hard tabs (#19) 2013-10-31 12:32:07 +09:00
Junegunn Choi
2b119f9bb6 Fix issue #19 (hard tab indentation) 2013-10-31 11:08:02 +09:00
Junegunn Choi
15bcbc9499 Update readme 2013-10-29 23:24:14 +09:00
Junegunn Choi
54e6b5d866 Update screenshot 2013-10-29 22:17:23 +09:00
Junegunn Choi
1f28ce346e Update README 2013-10-27 03:14:40 +09:00
Junegunn Choi
2832a76cea Implement filter option (#16, #17)
This commit implements filter option which can be used to filter lines within
the range based on the given pattern. The value of filter option should be
either `g/pattern/` or `v/pattern/`. The former aligns lines that match the
pattern, the latter aligns lines that do not match the pattern.
2013-10-27 03:10:04 +09:00
Junegunn Choi
1a232ac19b Revert "Make user confirm regular expression on live interactive mode"
This reverts commit a76cfdb8ae.
2013-10-25 02:17:52 +09:00
Junegunn Choi
a76cfdb8ae Make user confirm regular expression on live interactive mode 2013-10-25 02:11:43 +09:00
Junegunn Choi
c3a7842b0d Remove a redundant Expect block 2013-10-22 11:24:46 +09:00
5 changed files with 372 additions and 33 deletions

View File

@@ -6,9 +6,9 @@ A simple, easy-to-use Vim alignment plugin.
Demo
----
![Screencast](https://raw.github.com/junegunn/vim-easy-align/gif/vim-easy-align.gif)
![Screencast](https://raw.github.com/junegunn/i/master/vim-easy-align.gif)
(Too fast? Slower GIF is [here](https://raw.github.com/junegunn/vim-easy-align/gif/vim-easy-align-slow.gif))
(Too fast? Slower GIF is [here](https://raw.github.com/junegunn/i/master/vim-easy-align-slow.gif))
Features
--------
@@ -151,13 +151,14 @@ keys listed below. The meaning of each option will be described in
| Key | Option | Values |
| -------- | ------------------ | -------------------------------------------------- |
| `CTRL-F` | `filter` | Input string (`[gv]/.*/?`) |
| `CTRL-I` | `indentation` | shallow, deep, none, keep |
| `CTRL-L` | `left_margin` | Input number or string |
| `CTRL-R` | `right_margin` | Input number or string |
| `CTRL-D` | `delimiter_align` | left, center, right |
| `CTRL-U` | `ignore_unmatched` | 0, 1 |
| `CTRL-G` | `ignore_groups` | [], ['String'], ['Comment'], ['String', 'Comment'] |
| `CTRL-O` | `mode_sequence` | Input string of `/[lrc]+\*{0,2}/` |
| `CTRL-O` | `mode_sequence` | Input string (`/[lrc]+\*{0,2}/`) |
| `<Left>` | `stick_to_left` | `{ 'stick_to_left': 1, 'left_margin': 0 }` |
| `<Right>` | `stick_to_left` | `{ 'stick_to_left': 0, 'left_margin': 1 }` |
| `<Down>` | `*_margin` | `{ 'left_margin': 0, 'right_margin': 0 }` |
@@ -172,12 +173,12 @@ repeatable, non-interactive command recorded in `g:easy_align_last_command`.
### Live interactive mode
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.
carefully adjusted, try "live interactive mode" where you can preview the result
of the alignment 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.
the same parameters as `:EasyAlign`. I suggest you define a mapping such as
follows in addition to the one for `:EasyAlign` command.
```vim
vnoremap <silent> <Leader><Enter> :LiveEasyAlign<Enter>
@@ -242,6 +243,7 @@ The following table summarizes the shorthand notation.
| Option | Expression |
| ---------------- | ---------- |
| filter | `[gv]/.*/` |
| left_margin | `l[0-9]+` |
| right_margin | `r[0-9]+` |
| stick_to_left | `s[01]` |
@@ -314,6 +316,7 @@ Alignment options
| Option | Type | Default | Description |
| ------------------ | ------- | --------------------- | ------------------------------------------------------- |
| `filter` | string | | Line filtering expression: `g/../` or `v/../` |
| `left_margin` | number | 1 | Number of spaces to attach before delimiter |
| `left_margin` | string | `' '` | String to attach before delimiter |
| `right_margin` | number | 1 | Number of spaces to attach after delimiter |
@@ -334,6 +337,7 @@ There are 4 ways to set alignment options (from lowest precedence to highest):
| Option name | Shortcut key | Abbreviated | Global variable |
| ------------------ | ------------------- | ----------- | ------------------------------- |
| `filter` | `CTRL-F` | `[gv]/.*/` | |
| `left_margin` | `CTRL-L` | `l[0-9]+` | |
| `right_margin` | `CTRL-R` | `r[0-9]+` | |
| `stick_to_left` | `<Left>`, `<Right>` | `s[01]` | |
@@ -343,6 +347,30 @@ There are 4 ways to set alignment options (from lowest precedence to highest):
| `delimiter_align` | `CTRL-D` | `d[lrc]` | `g:easy_align_delimiter_align` |
| `mode_sequence` | `CTRL-O` | `m[lrc*]*` | |
### Filtering lines
With `filter` option, you can align lines that only match or do not match a
given pattern. There are several ways to set the pattern.
1. Press `CTRL-F` in interactive mode and input `g/pat/` or `v/pat/`
2. In command-line, it can be written in dictionary format: `{'filter': 'g/pat/'}`
3. Or in shorthand notation: `g/pat/` or `v/pat/`
(You don't need to escape '/'s in the regular expression)
#### Examples
```vim
" Start interactive mode with filter option set to g/hello/
EasyAlign g/hello/
" Start live interactive mode with filter option set to v/goodbye/
LiveEasyAlign v/goodbye/
" Align the lines with 'hi' around the first colons
EasyAlign:g/hi/
```
### Ignoring delimiters in comments or strings
EasyAlign can be configured to ignore delimiters in certain syntax highlight

View File

@@ -47,7 +47,7 @@ let s:known_options = {
\ 'margin_left': [0, 1], 'margin_right': [0, 1], 'stick_to_left': [0],
\ 'left_margin': [0, 1], 'right_margin': [0, 1], 'indentation': [1],
\ 'ignore_groups': [3 ], 'ignore_unmatched': [0 ], 'delimiter_align': [1],
\ 'mode_sequence': [1 ], 'ignores': [3]
\ 'mode_sequence': [1 ], 'ignores': [3], 'filter': [1]
\ }
let s:option_values = {
@@ -61,14 +61,16 @@ 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'
\ 'mode_sequence': 'm', 'ignores': 'ig', 'filter': 'f'
\ }
if exists("*strwidth")
let s:strwidth = function('strwidth')
function! s:strwidth(str)
return strwidth(a:str) + len(matchstr(a:str, '^\t*')) * (&tabstop - 1)
endfunction
else
function! s:strwidth(str)
return len(split(a:str, '\zs'))
return len(split(a:str, '\zs')) + len(matchstr(a:str, '^\t*')) * (&tabstop - 1)
endfunction
endif
@@ -333,9 +335,16 @@ function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, nth, r
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
let [f, fx] = s:parse_filter(d.filter)
" Phase 1
for line in range(a:fl, a:ll)
if f == 1 && getline(line) !~ fx
continue
elseif f == -1 && getline(line) =~ fx
continue
endif
if !has_key(a:all_tokens, line)
" Split line into the tokens by the delimiters
let [tokens, delims] = s:split_line(
@@ -387,11 +396,13 @@ function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, nth, r
continue
endif
let indent = match(tokens[0], '^\s*\zs')
let indent = s:strwidth(matchstr(tokens[0], '^\s*'))
if min_indent < 0 || indent < min_indent
let min_indent = indent
endif
if mode ==? 'c' | let token .= matchstr(token, '^\s*') | endif
if mode ==? 'c'
let token .= substitute(matchstr(token, '^\s*'), '\t', repeat(' ', &tabstop), 'g')
endif
let [pw, tw] = [s:strwidth(prefix), s:strwidth(token)]
let max.indent = max([max.indent, indent])
let max.token_len = max([max.token_len, tw])
@@ -411,11 +422,11 @@ function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, nth, r
if a:nth == 1
let idt = d.indentation
if idt ==? 'd'
let indent = repeat(' ', max.indent)
let indent = max.indent
elseif idt ==? 's'
let indent = repeat(' ', min_indent)
let indent = min_indent
elseif idt ==? 'n'
let indent = ''
let indent = 0
elseif idt !=? 'k'
call s:exit('Invalid indentation: ' . idt)
end
@@ -428,9 +439,22 @@ function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, nth, r
for [line, elems] in items(lines)
let [nth, prefix, token, delim] = elems
let token = substitute(token, '^\s*', indent, '')
let tindent = matchstr(token, '^\s*')
while 1
let len = s:strwidth(tindent)
if len < indent
let tindent .= repeat(' ', indent - len)
break
elseif len > indent
let tindent = tindent[0 : -2]
else
break
endif
endwhile
let token = tindent . s:ltrim(token)
if mode ==? 'c'
let token = substitute(token, '\s*$', indent, '')
let token = substitute(token, '\s*$', repeat(' ', indent), '')
endif
let [pw, tw] = [s:strwidth(prefix), s:strwidth(token)]
let max.token_len = max([max.token_len, tw])
@@ -469,7 +493,8 @@ function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, nth, r
endif
elseif mode ==? 'r'
let pad = repeat(' ', max.just_len - pw - tw)
let token = pad . token
let indent = matchstr(token, '^\s*')
let token = indent . pad . s:ltrim(token)
elseif mode ==? 'c'
let p1 = max.pivot_len - (pw + tw / 2.0)
let p2 = (max.token_len - tw) / 2.0
@@ -478,7 +503,8 @@ function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, nth, r
else | let p2 = floor(p2)
endif
let strip = float2nr(ceil((max.token_len - max.strip_len) / 2.0))
let token = repeat(' ', float2nr(pf1)) .token. repeat(' ', float2nr(p2))
let indent = matchstr(token, '^\s*')
let token = indent. repeat(' ', float2nr(pf1)) .s:ltrim(token). repeat(' ', float2nr(p2))
let token = substitute(token, repeat(' ', strip) . '$', '', '')
if d.stick_to_left
@@ -520,7 +546,7 @@ function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, nth, r
" Adjust indentation of the lines starting with a delimiter
let lpad = ''
if nth == 0
let ipad = repeat(' ', min_indent - len(token.ml))
let ipad = repeat(' ', min_indent - s:strwidth(token.ml))
if mode ==? 'l'
let token = ipad . token
else
@@ -703,6 +729,16 @@ function! s:interactive(range, modes, n, d, opts, rules, vis, live)
else
let warn = 'Invalid regular expression: '.ch
endif
elseif ch == "\<C-F>"
let f = s:input("Filter (g/../ or v/../): ", get(opts, 'f', ''), a:vis)
let m = matchlist(f, '^[gv]/\(.\{-}\)/\?$')
if empty(f)
silent! call remove(opts, 'f')
elseif !empty(m) && s:valid_regexp(m[1])
let opts['f'] = f
else
let warn = 'Invalid filter expression'
endif
elseif ch =~ '[[:print:]]'
let check = 1
else
@@ -756,9 +792,9 @@ function! s:test_regexp(regexp)
endfunction
let s:shorthand_regex =
\ '\s*\('
\ '\s*\%('
\ .'\(lm\?[0-9]\+\)\|\(rm\?[0-9]\+\)\|\(iu[01]\)\|\(s\%(tl\)\?[01]\)\|'
\ .'\(da\?[clr]\)\|\(ms\?[lrc*]\+\)\|\(i\%(dt\)\?[kdsn]\)\|\(ig\[.*\]\)'
\ .'\(da\?[clr]\)\|\(ms\?[lrc*]\+\)\|\(i\%(dt\)\?[kdsn]\)\|\([gv]/.*/\)\|\(ig\[.*\]\)'
\ .'\)\+\s*$'
function! s:parse_shorthand_opts(expr)
@@ -773,13 +809,17 @@ function! s:parse_shorthand_opts(expr)
else
let match = matchlist(expr, regex)
if empty(match) | break | endif
for m in filter(match[ 2 : -1 ], '!empty(v:val)')
for key in ['lm', 'rm', 'l', 'r', 'stl', 's', 'iu', 'da', 'd', 'ms', 'm', 'ig', 'i']
for m in filter(match[ 1 : -1 ], '!empty(v:val)')
for key in ['lm', 'rm', 'l', 'r', 'stl', 's', 'iu', 'da', 'd', 'ms', 'm', 'ig', 'i', 'g', 'v']
if stridx(tolower(m), key) == 0
let rest = strpart(m, len(key))
if key == 'i' | let key = 'idt' | endif
if key == 'g' || key == 'v'
let rest = key.rest
let key = 'f'
endif
if key == 'idt' || index(['d', 'm'], key[0]) >= 0
if key == 'idt' || index(['d', 'f', 'm'], key[0]) >= 0
let opts[key] = rest
elseif key == 'ig'
try
@@ -877,6 +917,15 @@ function! s:parse_args(args)
endif
endfunction
function! s:parse_filter(f)
let m = matchlist(a:f, '^\([gv]\)/\(.\{-}\)/\?$')
if empty(m)
return [0, '']
else
return [m[1] == 'g' ? 1 : -1, m[2]]
endif
endfunction
function! s:interactive_modes(bang)
return get(g:,
\ (a:bang ? 'easy_align_bang_interactive_modes' : 'easy_align_interactive_modes'),
@@ -936,6 +985,8 @@ function! s:build_dict(delimiters, ch, regexp, opts)
\ 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()))
let dict.filter =
\ get(dict, 'filter', '')
return dict
endfunction

View File

@@ -201,6 +201,7 @@ The following table summarizes the shorthand notation.
| Option | Expression |
| -------------- | ---------- |
| filter | [gv]/.*/ |
| left_margin | l[0-9]+ |
| right_margin | r[0-9]+ |
| stick_to_left | s[01] |
@@ -234,6 +235,7 @@ Available options are as follows.
| Atrribute | Type | Default |
| ---------------- | ---------------- | ----------------------------- |
| filter | string | |
| left_margin | number or string | 1 |
| right_margin | number or string | 1 |
| stick_to_left | boolean | 0 |
@@ -255,6 +257,7 @@ There are 4 ways to set alignment options (from lowest precedence to highest):
| Option | Shortcut key | Abbreviated | Global variable |
| ---------------- | --------------- | ----------- | ----------------------------- |
| filter | CTRL-F | [gv]/.*/ | |
| left_margin | CTRL-L | l[0-9]+ | |
| right_margin | CTRL-R | r[0-9]+ | |
| stick_to_left | <Left>, <Right> | s[01] | |
@@ -265,6 +268,30 @@ There are 4 ways to set alignment options (from lowest precedence to highest):
| mode_sequence | CTRL-O | m[lrc*]+ | |
Filtering lines
-------------------------------------------------------------------------
With filter option, you can align lines that only match or do not match a
given pattern. There are several ways to set the pattern.
1. Press CTRL-F in interactive mode and input g/pat/ or v/pat/
2. In command-line, it can be written in dictionary format: {'filter': 'g/pat/'}
3. Or in shorthand notation: g/pat/ or v/pat/
Examples:
" Start interactive mode with filter option set to g/hello/
EasyAlign g/hello/
" Start live interactive mode with filter option set to v/goodbye/
LiveEasyAlign v/goodbye/
" Align the lines with 'hi' around the first colons
EasyAlign : g/hi/
(You don't need to escape '/'s in the regular expression)
Ignoring delimiters in comments or strings *g:easy_align_ignore_groups*
-------------------------------------------------------------------------

View File

@@ -1,3 +1,6 @@
Execute:
Save &tabstop
Given (Table):
|a|b|c|d|
| -|-|>-|-|
@@ -40,3 +43,80 @@ Execute:
Expect:
a <|>b <|>c <|
aa<|>bb<|>cc<|
Given (Tab-indented code (#20)):
class MyUnitTest(unittest.TestCase):
def test_base(self):
n2f = {}
n2v = {}
f2v = {}
n2gv = {}
n2vt = {}
Execute:
set tabstop=1
%EasyAlign=
Expect:
class MyUnitTest(unittest.TestCase):
def test_base(self):
n2f = {}
n2v = {}
f2v = {}
n2gv = {}
n2vt = {}
Execute:
set tabstop=2
%EasyAlign=
Expect:
class MyUnitTest(unittest.TestCase):
def test_base(self):
n2f = {}
n2v = {}
f2v = {}
n2gv = {}
n2vt = {}
Execute:
set tabstop=4
%EasyAlign=
Expect:
class MyUnitTest(unittest.TestCase):
def test_base(self):
n2f = {}
n2v = {}
f2v = {}
n2gv = {}
n2vt = {}
Execute:
set tabstop=8
%EasyAlign=
Expect:
class MyUnitTest(unittest.TestCase):
def test_base(self):
n2f = {}
n2v = {}
f2v = {}
n2gv = {}
n2vt = {}
Execute:
set tabstop=12
%EasyAlign=
Expect:
class MyUnitTest(unittest.TestCase):
def test_base(self):
n2f = {}
n2v = {}
f2v = {}
n2gv = {}
n2vt = {}
Execute:
Restore

View File

@@ -2,7 +2,7 @@ Execute (Clean up test environment):
Save g:easy_align_ignore_groups, g:easy_align_ignore_unmatched
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 g:easy_align_delimiters, &tabstop
Save mapleader
" TODO: revert after test
@@ -257,12 +257,6 @@ Expect:
Ri ng o St ar r 19 40 mm
Pe te Be st 19 41
Expect:
Paul McCartney 1942
George Harrison 1943mmdd
Ringo Starr 1940mm
Pete Best 1941
###########################################################
Given ruby (delimiters in comments and strings):
@@ -1349,5 +1343,164 @@ Expect:
a = b = c
aabba = bbbbb
###########################################################
Given (test filter option):
aaa=aaa=aaa
aaaaa=aaaaa=aaaaa
aaaaaaa=aaaaaaa=aaaaaaab
bbbbb=bbbbb=bbbbb
aaa=aaa=aaa
Do (g/a/):
vip\<Enter>
\<C-F>g/a/\<Enter>
*=
Expect:
aaa = aaa = aaa
aaaaa = aaaaa = aaaaa
aaaaaaa = aaaaaaa = aaaaaaab
bbbbb=bbbbb=bbbbb
aaa = aaa = aaa
Do (g/a - you can omit the trailing /):
vip\<Enter>
\<C-F>g/a\<Enter>
*=
Expect:
aaa = aaa = aaa
aaaaa = aaaaa = aaaaa
aaaaaaa = aaaaaaa = aaaaaaab
bbbbb=bbbbb=bbbbb
aaa = aaa = aaa
Do (v/b/):
vip\<Enter>
\<C-F>v/b/\<Enter>
*=
Expect:
aaa = aaa = aaa
aaaaa = aaaaa = aaaaa
aaaaaaa=aaaaaaa=aaaaaaab
bbbbb=bbbbb=bbbbb
aaa = aaa = aaa
Do (invalid filter expression):
vip\<Enter>
\<C-F>haha\<Enter>
*=
Expect:
aaa = aaa = aaa
aaaaa = aaaaa = aaaaa
aaaaaaa = aaaaaaa = aaaaaaab
bbbbb = bbbbb = bbbbb
aaa = aaa = aaa
Execute (g-filter in shorthand notation):
%EasyAlign*=g/a/
Expect:
aaa = aaa = aaa
aaaaa = aaaaa = aaaaa
aaaaaaa = aaaaaaa = aaaaaaab
bbbbb=bbbbb=bbbbb
aaa = aaa = aaa
Execute (v-filter in shorthand notation):
%EasyAlign*=v/b/
Expect:
aaa = aaa = aaa
aaaaa = aaaaa = aaaaa
aaaaaaa=aaaaaaa=aaaaaaab
bbbbb=bbbbb=bbbbb
aaa = aaa = aaa
Execute (filter in dictionary format):
%EasyAlign*={'filter': 'v/b/'}
Expect:
aaa = aaa = aaa
aaaaa = aaaaa = aaaaa
aaaaaaa=aaaaaaa=aaaaaaab
bbbbb=bbbbb=bbbbb
aaa = aaa = aaa
###########################################################
Given (hard-tab indentation (#19)):
a=1=3
bbb=2=4
ccccc=4=5
fff=4=6
Do (Left alignment):
vip\<Enter>=
Expect:
a = 1=3
bbb = 2=4
ccccc = 4=5
fff = 4=6
Do (Right alignment):
vip\<Enter>\<Enter>=
Expect:
a = 1=3
bbb = 2=4
ccccc = 4=5
fff = 4=6
Do (Center alignment):
vip\<Enter>\<Enter>\<Enter>=
Expect:
a = 1=3
bbb = 2=4
ccccc = 4=5
fff = 4=6
Do (Left alignment with shallow indentation):
vip\<Enter>\<C-I>=
Expect:
a = 1=3
bbb = 2=4
ccccc = 4=5
fff = 4=6
Do (Center alignment with deep indentation):
vip\<Enter>\<Enter>\<Enter>\<C-I>\<C-I>=
Expect:
a = 1=3
bbb = 2=4
ccccc = 4=5
fff = 4=6
Given (hard-tab indentation - dictionary (#19)):
ddict={'homePage':'360452',
'key':'TEST',
'name':'DocumentationAPITestingSpace',
'type':'global',
'url':'http://localhost:8090/display/TEST'}
Do (Right alignment):
vip\<Enter>\<Enter>:
Expect:
ddict={'homePage': '360452',
'key': 'TEST',
'name': 'DocumentationAPITestingSpace',
'type': 'global',
'url': 'http://localhost:8090/display/TEST'}
###########################################################
Execute:
Restore