Add a proper support for \@=

This commit is contained in:
Junegunn Choi
2013-08-12 00:54:34 +09:00
parent 5e4ec85956
commit a2811dc253
7 changed files with 113 additions and 83 deletions

View File

@@ -127,7 +127,6 @@ Try `<Enter>:` here, to align text around only the first occurrences of colons.
In this case, you don't want to align around all the colons: `<Enter>*:`.
```yaml
mysql:
# JDBC driver for MySQL database:
driver: com.mysql.jdbc.Driver
@@ -135,7 +134,6 @@ mysql:
url: jdbc:mysql://localhost/test
database: test
"user:pass":r00t:pa55
```
Formatting multi-line method chaining
@@ -220,7 +218,7 @@ So, how do we align the trailing comments in the above lines?
Simply try `<Enter>-<space>`. The spaces in the comments are ignored, so the
trailing comment in each line is considered to be a single chunk.
But this doesn't work in the following case.
But that doesn't work in the following case.
```ruby
apple = 1 # comment not aligned
@@ -262,17 +260,26 @@ command:
In this case, the second line is ignored as it doesn't contain a `#`. (The one
highlighted as String is ignored.) If you don't want the second line to be
ignored, set `g:easy_align_ignore_unmatched` to 0, or use the following
commands:
ignored, there are three options:
1. Set `g:easy_align_ignore_unmatched` to 0
2. Use the following commands:
```vim
" Using predefined rule with delimiter key #
" - "iu" is fuzzy-matched to "*i*gnore_*u*nmatched"
:EasyAlign#{'iu':0}`
" Using regular expression /#/
:EasyAlign/#/{'is':['String'],'iu':0}`
```
3. Update the rule with `ignore_unmatched`
```vim
let g:easy_align_delimiters['#'] = { 'pattern': '#', 'ignores': ['String'], 'ignore_unmatched': 0 } }
```
Then we get,
```ruby
@@ -302,18 +309,18 @@ static double pi = 3.14;
```
Not bad. However, the names of the variables, `str`, `count`, and `pi` are not
aligned with each other. Can we do better? You can clearly see that simple
`<Enter><space>` won't properly align those names. So we define a rule for such
cases.
aligned with each other. Can we do better? We can clearly see that simple
`<Enter><space>` won't properly align those names.
So let's define an alignment rule than can handle this case.
```vim
let g:easy_align_delimiters['d'] = {
\ 'pattern': '\(const\|static\)\@<!\s\+',
\ 'pattern': '\(const\|static\)\@<! ',
\ 'left_margin': 0, 'right_margin': 0
\ }
```
This new rule aligns text around whitespaces that are not preceded by
This new rule aligns text around spaces that are *not* preceded by
`const` or `static`. Let's try it with `<Enter>d`.
```c
@@ -322,8 +329,8 @@ int64_t count = 1 + 2;
static double pi = 3.14;
```
Okay, now the names are aligned. We select the lines again with `gv`, and then
press `<Enter>=` to finalize our alignment.
Okay, the names are now aligned. We select the lines again with `gv`, and then
press `<Enter>=` to finish our alignment.
```c
const char* str = "Hello";
@@ -331,35 +338,35 @@ int64_t count = 1 + 2;
static double pi = 3.14;
```
Looks good. However, this rule is not sufficient to handle more complex cases
such as C++ templates or Java generics. Take the following examples.
So far, so good. However, this rule is not sufficient to handle more complex
cases involving C++ templates or Java generics. Take the following examples:
```c
const char* str = "Hello";
int64_t count = 1 + 2;
static double pi = 3.14;
static std::map<std::string, float>* scores = pointer;
static std::map<std::string, float>* scores = pointer;
```
We see that our rule fails with the new fourth line.
We see that our rule above doesn't work anymore.
```c
const char* str = "Hello";
int64_t count = 1 + 2;
static double pi = 3.14;
static std::map<std::string, float>* scores = pointer;
static std::map<std::string, float>* scores = pointer;
```
So what do we do? Let's try to improve our alignment rule.
```vim
let g:easy_align_delimiters['d'] = {
\ 'pattern': '\s\+\(\S\+\s*[;=]\)\@=',
\ 'pattern': ' \(\S\+\s*[;=]\)\@=',
\ 'left_margin': 0, 'right_margin': 0
\ }
```
Now the new rule has changed to align text around whitespaces that are followed
Now the new rule has changed to align text around spaces that are followed
by some non-whitespace characters and then an equals sign or a semi-colon.
Try `<Enter>d`

View File

@@ -139,54 +139,75 @@ function! s:validate_options(opts)
return a:opts
endfunction
function! s:do_align(just, all_tokens, fl, ll, fc, lc, pattern, nth, ml, mr, stick_to_left, ignore_unmatched, ignores, recursive)
function! s:split_line(line, fc, lc, pattern, stick_to_left, ignore_unmatched, ignores)
let left = a:lc ?
\ strpart(getline(a:line), a:fc - 1, a:lc - a:fc + 1) :
\ strpart(getline(a:line), a:fc - 1)
let idx = 0
" Do not allow \zs
" 1: whole match
" 2: token
" 3: delimiter
let pattern = '^\(\(.\{-}\s*\)\(' .a:pattern. '\)\s' . (a:stick_to_left ? '*' : '\{-}') . '\)'
let tokens = []
let delims = []
" Phase 1: split
let ignorable = 0
let token = ''
while 1
let matches = matchlist(left, pattern, idx)
if empty(matches) | break | endif
let ignorable = s:highlighted_as(a:line, idx + len(matches[2]) + a:fc, a:ignores)
if ignorable
let token .= matches[1]
else
call add(tokens, token . matches[1])
call add(delims, matches[3])
let token = ''
endif
if empty(matches[1])
call s:exit("Invalid regular expression")
endif
let idx += len(matches[1])
endwhile
let leftover = token . strpart(left, idx)
if !empty(leftover)
call add(tokens, leftover)
call add(delims, '')
endif
" Skip comment line
if ignorable && len(tokens) == 1 && a:ignore_unmatched
let tokens = []
let delims = []
endif
return [tokens, delims]
endfunction
function! s:do_align(just, all_tokens, all_delims, fl, ll, fc, lc, pattern, nth, ml, mr, stick_to_left, ignore_unmatched, ignores, recursive)
let lines = {}
let max_just_len = 0
let max_delim_len = 0
let max_tokens = 0
let min_indent = -1
let pattern = '\s*\(' .a:pattern. '\)\s' . (a:stick_to_left ? '*' : '\{-}')
" Phase 1
for line in range(a:fl, a:ll)
if !has_key(a:all_tokens, line)
" Split line into the tokens by the delimiters
let raw_tokens = split(a:lc ?
\ strpart(getline(line), a:fc - 1, a:lc - a:fc + 1) :
\ strpart(getline(line), a:fc - 1),
\ pattern.'\zs')
let concat = 0
if empty(a:ignores)
let tokens = raw_tokens
else
" Concat adjacent tokens that are split by ignorable delimiters
let tokens = []
let idx = 0
for token in raw_tokens
let idx += len(token)
if concat
let tokens[len(tokens) - 1] .= token
else
call add(tokens, token)
endif
let concat = s:highlighted_as(line, idx + a:fc - 1, a:ignores)
endfor
endif
" Preserve indentation - merge first two tokens
if !empty(tokens) && match(tokens[0], '^\s*$') != -1
let tokens = extend([join(tokens[0:1], '')], tokens[2:-1])
endif
" Skip comment line
if concat && len(tokens) == 1 && a:ignore_unmatched
let tokens = []
endif
let [tokens, delims] = s:split_line(line, a:fc, a:lc, a:pattern, a:stick_to_left, a:ignore_unmatched, a:ignores)
" Remember tokens for subsequent recursive calls
let a:all_tokens[line] = tokens
let a:all_delims[line] = delims
else
let tokens = a:all_tokens[line]
let delims = a:all_delims[line]
endif
" Skip empty lines
@@ -204,7 +225,7 @@ function! s:do_align(just, all_tokens, fl, ll, fc, lc, pattern, nth, ml, mr, sti
let nth = a:nth - 1 " make it 0-based
else " Negative field number
let nth = len(tokens) + a:nth
if match(tokens[len(tokens) - 1], pattern.'$') == -1
if empty(delims[len(delims) - 1])
let nth -= 1
endif
@@ -213,11 +234,10 @@ function! s:do_align(just, all_tokens, fl, ll, fc, lc, pattern, nth, ml, mr, sti
endif
endif
let last = tokens[nth]
let prefix = (nth > 0 ? join(tokens[0 : nth - 1], '') : '')
let token = substitute(last, pattern.'$', '', '')
let delim = get(matchlist(last, pattern.'$'), 1, '')
let prefix = nth > 0 ? join(tokens[0 : nth - 1], '') : ''
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:just == 0 && a:ignore_unmatched
continue
endif
@@ -234,6 +254,7 @@ function! s:do_align(just, all_tokens, fl, ll, fc, lc, pattern, nth, ml, mr, sti
" Phase 2
for [line, elems] in items(lines)
let tokens = a:all_tokens[line]
let delims = a:all_delims[line]
let [nth, prefix, token, delim] = elems
" Remove the leading whitespaces of the next token
@@ -296,7 +317,7 @@ function! s:do_align(just, all_tokens, fl, ll, fc, lc, pattern, nth, ml, mr, sti
if a:recursive && a:nth < max_tokens
let just = a:recursive == 2 ? !a:just : a:just
call s:do_align(just, a:all_tokens, a:fl, a:ll, a:fc, a:lc, a:pattern,
call s:do_align(just, a:all_tokens, a:all_delims, a:fl, a:ll, a:fc, a:lc, a:pattern,
\ a:nth + 1, a:ml, a:mr, a:stick_to_left, a:ignore_unmatched,
\ a:ignores, a:recursive)
endif
@@ -384,10 +405,6 @@ function! s:parse_args(args)
try | call matchlist('', regexp)
catch | call s:exit("Invalid regular expression: ". regexp)
endtry
" Unsupported regular expression
if match(regexp, '\\zs') != -1
call s:exit("Using \\zs is not allowed. Use stick_to_left option instead.")
endif
return [matches[1], regexp, option, 1]
else
let tokens = matchlist(args, '^\([1-9][0-9]*\|-[0-9]*\|\*\*\?\)\?\s*\(.\{-}\)\?$')
@@ -461,7 +478,8 @@ function! easy_align#align(just, expr) range
if type(ml) == 0 | let ml = repeat(' ', ml) | endif
if type(mr) == 0 | let mr = repeat(' ', mr) | endif
call s:do_align(just, {}, a:firstline, a:lastline,
try
call s:do_align(just, {}, {}, a:firstline, a:lastline,
\ visualmode() == '' ? min([col("'<"), col("'>")]) : 1,
\ visualmode() == '' ? max([col("'<"), col("'>")]) : 0,
\ get(dict, 'pattern', ch),
@@ -472,6 +490,8 @@ function! easy_align#align(just, expr) range
\ get(dict, 'ignore_unmatched', get(g:, 'easy_align_ignore_unmatched', 1)),
\ get(dict, 'ignores', s:ignored_syntax()),
\ recur)
call s:echon(just, n, regexp ? '/'.ch.'/' : ch)
call s:echon(just, n, regexp ? '/'.ch.'/' : ch)
catch 'exit'
endtry
endfunction

View File

@@ -233,6 +233,14 @@ banana = 'Gros Michel' # comment 2
```yaml
mysql:
# JDBC driver for MySQL database:
driver: com.mysql.jdbc.Driver
# JDBC URL for the connection (jdbc:mysql://HOSTNAME/DATABASE)
url: jdbc:mysql://localhost/test
database: test
"user:pass": r00t:pa55
mysql:
# JDBC driver for MySQL database:
driver: com.mysql.jdbc.Driver
@@ -337,15 +345,6 @@ my_object .
| logger | Logger | nil | logger instance for debug logs |
```c
const char* str = "Hello";
int64_t count = 1 + 2;
static double pi = 3.14;
static std::map<std::string, float>* scores = pointer;
```
|Option |Type |Default |Description |
|-- |-- |-- |-- |
|threads |Fixnum |1 |number of threads in the thread pool |
@@ -366,3 +365,12 @@ static std::map<std::string, float>* scores = pointer;
| batch_size<| Fixnum <| nil <| number of maximum items to be assigned at once<|
| logger <| Logger <| nil <| logger instance for debug logs <|
```c
const char* str = "Hello";
int64_t count = 1 + 2;
static double pi = 3.14;
static std::map<std::string, float>* scores = pointer;
```

View File

@@ -113,7 +113,7 @@ my_object.
const char* str = "Hello";
int64_t count = 1 + 2;
static double pi = 3.14;
static std::map<std::string, float>* scores = pointer;
static std::map<std::string, float>* scores = pointer;
```

View File

@@ -1 +1 @@
4Gvipjyvip
4Gvipjyvip

View File

@@ -39,4 +39,5 @@ set buftype=nofile
silent! ScrollPositionHide
call GFM()
syntax sync fromstart

View File

@@ -1,13 +1,7 @@
e!
execute 'source '. expand('%:p:h') . '/include.vim'
while line('.') < line('$')
normal 30j
redraw
endwhile
normal gg
let @b=system('cat '. expand('%:p:r') . '.script')
let @a='@b:vert diffsplit ' . expand('%:p:r') . '.expected
'
" Syntax highlighting doesn't work
'