diff --git a/EXAMPLES.md b/EXAMPLES.md index 11f09fb..602378e 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -127,7 +127,6 @@ Try `:` here, to align text around only the first occurrences of colons. In this case, you don't want to align around all the colons: `*:`. ```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 `-`. 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 -`` 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 +`` 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\)\@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 `=` to finalize our alignment. +Okay, the names are now aligned. We select the lines again with `gv`, and then +press `=` 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* scores = pointer; +static std::map* 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* scores = pointer; +static std::map* 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 `d` diff --git a/autoload/easy_align.vim b/autoload/easy_align.vim index 5903078..d7884f7 100644 --- a/autoload/easy_align.vim +++ b/autoload/easy_align.vim @@ -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 diff --git a/test/basic.expected b/test/basic.expected index 0c9e388..a1cd24d 100644 --- a/test/basic.expected +++ b/test/basic.expected @@ -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* scores = pointer; - -``` - |Option |Type |Default |Description | |-- |-- |-- |-- | |threads |Fixnum |1 |number of threads in the thread pool | @@ -366,3 +365,12 @@ static std::map* 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* scores = pointer; + +``` + diff --git a/test/basic.md b/test/basic.md index 25f23ba..53a3999 100644 --- a/test/basic.md +++ b/test/basic.md @@ -113,7 +113,7 @@ my_object. const char* str = "Hello"; int64_t count = 1 + 2; static double pi = 3.14; -static std::map* scores = pointer; +static std::map* scores = pointer; ``` diff --git a/test/basic.script b/test/basic.script index 2a4dcf8..3b7b8f9 100644 --- a/test/basic.script +++ b/test/basic.script @@ -1 +1 @@ -4Gvipjyvip Pvip 2 Pvip * Pvip Pvip 2 Pvip * Pvip ** Pvip - Pvip -2 Pvip -1 Pvip ** 60zzvipjyvip *|Pvip *|Pvip **|80zzvip **|gv 3|vip *|90zzvip *,100zzvipjyvip =Pvip *=Pvip **=Pvip =vip 2=198Gvipjyvip =Pvip -=Pf:jj3E =209Gvip - 214zzvipjyvip #P:let g:easy_align_delimiters = { '#': {'pattern': '#\+', 'ignores': ['String'] } } vip #227zzvip :239zzvip *=vipjyP:let g:easy_align_ignores = [] vip *=:unlet g:easy_align_delimiters 4Gvipy4GP7Gojkkvip:EasyAlign /1/{'ml':'{{', 'mr-r':'}}'} vipjyPvip:EasyAlign */../ 263zzvipjygv .Pvip *.Pvip * .Pvip .vip 2.Pvip **.Pvip **.Pvip -.G303zzvip .310zzvipjygv *|Pvip *|Pvip |gv -|gv **|gv *|gv **|jji jjjhi vip ** |Gpvip:EasyAlign*|{'ml': 5, 'mrr': 0 } Gpvip:EasyAlign*/|/{'ml':'<', 'mrr': 4} 342G:let g:easy_align_delimiters = { 'd': { 'pattern': '\s\+\(\S\+\s*[;=]\)\@=', 'left_margin': 0, 'right_margin': 0 } } vip dgv = +4Gvipjyvip Pvip 2 Pvip * Pvip Pvip 2 Pvip * Pvip ** Pvip - Pvip -2 Pvip -1 Pvip ** 60zzvipjyvip *|Pvip *|Pvip **|80zzvip **|gv 3|vip *|90zzvip *,100zzvipjyvip =Pvip *=Pvip **=Pvip =vip 2=198Gvipjyvip =Pvip -=Pf:jj3E =209Gvip - 214zzvipjyvip #P:let g:easy_align_delimiters = { '#': {'pattern': '#\+', 'ignores': ['String'] } } vip #227zzvip :239zzvip *=vipjyP:let g:easy_align_ignores = [] vip *=:unlet g:easy_align_delimiters :unlet g:easy_align_ignores 4Gvipy4GP7Gojkkvip:EasyAlign /1/{'ml':'{{', 'mr-r':'}}'} vipjyPvip:EasyAlign */../ 263zzvipjygv .Pvip *.Pvip * .Pvip .vip 2.Pvip **.Pvip **.Pvip -.G303zzvip .310zzvipjygv *|Pvip *|Pvip |gv -|gv **|gv *|gv **|jji jjjhi vip ** |339Gpvip:EasyAlign*|{'ml': 5, 'mrr': 0 } 349Gpvip:EasyAlign*/|/{'ml':'<', 'mrr': 4} 362G:let g:easy_align_delimiters = { 'd': { 'pattern': '\s\+\(\S\+\s*[;=]\)\@=', 'left_margin': 0, 'right_margin': 0 } } vip dgv =236GvipjyPvip : diff --git a/test/include.vim b/test/include.vim index 1262de3..b972fda 100644 --- a/test/include.vim +++ b/test/include.vim @@ -39,4 +39,5 @@ set buftype=nofile silent! ScrollPositionHide call GFM() +syntax sync fromstart diff --git a/test/run.vim b/test/run.vim index fb33a15..e746629 100644 --- a/test/run.vim +++ b/test/run.vim @@ -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 -echo "Press @a" +" Syntax highlighting doesn't work if we do @a here