Ignore delimiters in certain syntax highlighting groups

This commit is contained in:
Junegunn Choi
2013-07-30 01:54:32 +09:00
parent 5b48e997a1
commit 5f59570c9f
4 changed files with 323 additions and 129 deletions

136
EXAMPLES.md Normal file
View File

@@ -0,0 +1,136 @@
vim: set scrolloff=1 buftype=nofile colorcolumn=:
vim-easy-align examples
=======================
To enable syntax highlighting in the code blocks, define and call the following
function.
```vim
function! GFM()
let syntaxes = {
\ 'ruby': 'syntax/ruby.vim',
\ 'yaml': 'syntax/yaml.vim',
\ 'vim': 'syntax/vim.vim',
\ 'c': 'syntax/c.vim'
\ }
for [lang, syn] in items(syntaxes)
unlet b:current_syntax
silent! exec printf("syntax include @%s %s", lang, syn)
exec printf("syntax region %sSnip matchgroup=Snip start='```%s' end='```' contains=@%s",
\ lang, lang, lang)
endfor
let b:current_syntax='mkd'
endfunction
```
Alignment around whitespaces
----------------------------
```
Paul McCartney 1942
George Harrison 1943
Ringo Starr 1940
Pete Best 1941
```
Formatting table
----------------
| 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 |
Alignment around =
------------------
```ruby
a =
a = 1
bbbb = 2
ccccccc = 3
ccccccccccccccc
ddd = 4
eeee === eee = eee = eee=f
fff = ggg += gg &&= gg
g != hhhhhhhh == 888
i := 5
i %= 5
i *= 5
j =~ 5
j >= 5
aa => 123
aa <<= 123
aa >>= 123
bbb => 123
c => 1233123
d => 123
dddddd &&= 123
dddddd ||= 123
dddddd /= 123
gg <=> ee
```
Formatting YAML (or JSON)
-------------------------
```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:r00t
```
Partial alignment in block-visual mode / Negative field index
-------------------------------------------------------------
```ruby
options = { :caching => nil,
:versions => 3,
"cache=blocks" => false }.merge(options)
```
Commas
------
```
aaa, bb,c
d,eeeeeee
fffff, gggggggggg,
h, , ii
j,,k
```
Ignoring delimiters in comments and strings
-------------------------------------------
```c
/* a */ b = c
aa >= bb
// aaa = bbb = cccc
/* aaaa = */ bbbb === cccc " = dddd = " = eeee
aaaaa /* bbbbb */ == ccccc /* != eeeee = */ === fffff
```

View File

@@ -9,7 +9,7 @@ Features:
- Requires minimal keystrokes - Requires minimal keystrokes
- Extensible alignment rules - Extensible alignment rules
- Aligns text around either _all or n-th_ occurrence(s) of the delimiter - Aligns text around either _all or n-th_ occurrence(s) of the delimiter
- Ignores comment lines - Ignores delimiters in certain syntax highlighting groups (e.g. comments, strings)
- Ignores lines without a matching delimiter - Ignores lines without a matching delimiter
Demo Demo
@@ -17,8 +17,6 @@ Demo
![Screencast](https://raw.github.com/junegunn/vim-easy-align/gif/vim-easy-align.gif) ![Screencast](https://raw.github.com/junegunn/vim-easy-align/gif/vim-easy-align.gif)
[Screencast](https://vimeo.com/63506219)
Installation Installation
------------ ------------
@@ -115,15 +113,24 @@ since the same can be easily done using the negative field number: `<Enter>-=`
Options Options
------- -------
| Option | Type | Default | Description | | Option | Type | Default | Description |
| ----------------------------- | ---------- | ------- | --------------------------------------- | | ----------------------------- | ---------- | --------------------- | ----------------------------------------------------- |
| g:easy_align_ignore_comment | boolean | `1` | Ignore comment lines | | g:easy_align_ignores | list | ['String', 'Comment'] | Ignore delimiters in these syntax highlighting groups |
| g:easy_align_ignore_unmatched | boolean | `1` | Ignore lines without matching delimiter | | g:easy_align_ignore_unmatched | boolean | `1` | Ignore lines without matching delimiter |
| g:easy_align_delimiters | dictionary | `{}` | Extend or override alignment rules | | g:easy_align_delimiters | dictionary | `{}` | Extend or override alignment rules |
### Ignoring comment lines ### Ignoring delimiters in comments or strings
EasyAlign by default ignores comment lines. EasyAlign can be configured to ignore delimiters in certain highlight groups,
such as code comments or strings. By default, delimiters that are highlighted as
code comments or strings are ignored.
```vim
" Default:
" If a delimiter is in a highlight group whose name matches
" any of the followings, it will be ignored.
let g:easy_align_ignores = ['Comment', 'String']
```
For example, For example,
@@ -133,8 +140,8 @@ For example,
apple: 1, apple: 1,
# Quantity of bananas: 2 # Quantity of bananas: 2
bananas: 2, bananas: 2,
# Quantity of grapefruits: 3 # Quantity of grape:fruits: 3
grapefruits: 3 'grape:fruits': 3
} }
``` ```
@@ -143,39 +150,42 @@ becomes
```ruby ```ruby
{ {
# Quantity of apples: 1 # Quantity of apples: 1
apple: 1, apple: 1,
# Quantity of bananas: 2 # Quantity of bananas: 2
bananas: 2, bananas: 2,
# Quantity of grapefruits: 3 # Quantity of grape:fruits: 3
grapefruits: 3 'grape:fruits': 3
} }
``` ```
Since finding comment lines is done heuristically using syntax highlighting feature, Naturally, this only works when syntax highlighting is enabled.
this only works when syntax highlighting is enabled.
If you do not want comment lines to be ignored, you can unset `g:easy_align_ignore_comment` as follows. You can override `g:easy_align_ignores` to change the rule.
```vim ```vim
let g:easy_align_ignore_comment = 0 " Ignore nothing!
let g:easy_align_ignores = []
``` ```
Then you get, Then you get,
```ruby ```ruby
{ {
# Quantity of apples: 1 # Quantity of apples: 1
apple: 1, apple: 1,
# Quantity of bananas: 2 # Quantity of bananas: 2
bananas: 2, bananas: 2,
# Quantity of grapefruits: 3 # Quantity of grape: fruits: 3
grapefruits: 3 'grape: fruits': 3
} }
``` ```
Satisfied? :satisfied:
### Ignoring unmatched lines ### Ignoring unmatched lines
Lines without a matching delimiter are ignored as well (except in right-justification mode). Lines without a matching delimiter are ignored as well (except in
right-justification mode).
For example, when aligning the following code block around the colons, For example, when aligning the following code block around the colons,
@@ -222,6 +232,7 @@ Then we get,
### Extending alignment rules ### Extending alignment rules
```vim ```vim
" Examples
let g:easy_align_delimiters = { let g:easy_align_delimiters = {
\ '>': { 'pattern': '>>\|=>\|>' }, \ '>': { 'pattern': '>>\|=>\|>' },
\ '/': { 'pattern': '//\+\|/\*\|\*/' }, \ '/': { 'pattern': '//\+\|/\*\|\*/' },

View File

@@ -34,6 +34,8 @@ let s:easy_align_delimiters_default = {
\ ',': { 'pattern': ',', 'margin_left': '', 'margin_right': ' ', 'stick_to_left': 1 }, \ ',': { 'pattern': ',', 'margin_left': '', 'margin_right': ' ', 'stick_to_left': 1 },
\ '|': { 'pattern': '|', 'margin_left': ' ', 'margin_right': ' ', 'stick_to_left': 0 }, \ '|': { 'pattern': '|', 'margin_left': ' ', 'margin_right': ' ', 'stick_to_left': 0 },
\ '.': { 'pattern': '\.', 'margin_left': '', 'margin_right': '', 'stick_to_left': 0 }, \ '.': { 'pattern': '\.', 'margin_left': '', 'margin_right': '', 'stick_to_left': 0 },
\ '{': { 'pattern': '(\@<!{',
\ 'margin_left': ' ', 'margin_right': ' ', 'stick_to_left': 0 },
\ '}': { 'pattern': '}', 'margin_left': ' ', 'margin_right': '', 'stick_to_left': 0 } \ '}': { 'pattern': '}', 'margin_left': ' ', 'margin_right': '', 'stick_to_left': 0 }
\ } \ }
@@ -49,49 +51,90 @@ else
endfunction endfunction
endif endif
function! s:do_align(just, cl, fl, ll, fc, lc, pattern, nth, ml, mr, stick_to_left, recursive) function! s:highlighted_as(line, col, groups)
if empty(a:groups) | return 0 | endif
let hl = synIDattr(synID(a:line, a:col, 0), 'name')
for grp in a:groups
if hl =~# grp
return 1
endif
endfor
return 0
endfunction
function! s:ignored_syntax()
if has('syntax') && exists('g:syntax_on')
" Backward-compatibility
return get(g:, 'easy_align_ignores',
\ (get(g:, 'easy_align_ignore_comment', 1) == 0) ?
\ ['String'] : ['String', 'Comment'])
else
return []
endif
endfunction
function! s:do_align(just, all_tokens, fl, ll, fc, lc, pattern, nth, ml, mr, stick_to_left, recursive)
let lines = {} let lines = {}
let max_just_len = 0 let max_just_len = 0
let max_delim_len = 0 let max_delim_len = 0
let max_tokens = 0 let max_tokens = 0
let pattern = '\s*\(' .a:pattern. '\)\s' . (a:stick_to_left ? '*' : '\{-}') let pattern = '\s*\(' .a:pattern. '\)\s' . (a:stick_to_left ? '*' : '\{-}')
let ignore_comment = has('syntax') && exists('g:syntax_on') && let ignored_syntax = s:ignored_syntax()
\ get(g:, 'easy_align_ignore_comment', 1)
" Phase 1
for line in range(a:fl, a:ll) for line in range(a:fl, a:ll)
let tokens = split(a:lc ? if !has_key(a:all_tokens, line)
\ strpart(getline(line), a:fc - 1, a:lc - a:fc + 1) : " Split line into the tokens by the delimiters
\ strpart(getline(line), a:fc - 1), let raw_tokens = split(a:lc ?
\ pattern.'\zs') \ strpart(getline(line), a:fc - 1, a:lc - a:fc + 1) :
\ strpart(getline(line), a:fc - 1),
\ pattern.'\zs')
if empty(ignored_syntax)
let tokens = raw_tokens
else
" Concat adjacent tokens that are split by ignorable delimiters
let tokens = []
let idx = 0
let concat = 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, ignored_syntax)
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
" Remember tokens for subsequent recursive calls
let a:all_tokens[line] = tokens
else
let tokens = a:all_tokens[line]
endif
" Skip empty lines
if empty(tokens) if empty(tokens)
continue continue
endif endif
if ignore_comment " Calculate the maximum number of tokens for a line within the range
if !has_key(a:cl, line)
execute "normal! ". line ."G^"
let a:cl[line] =
\ synIDattr(synID(line, a:fc == 1 ? col('.') : a:fc, 0), 'name') =~? 'comment' &&
\ synIDattr(synID(line, a:lc ? min([a:lc, col('$') - 1]) : (col('$') - 1), 0), 'name') =~? 'comment'
endif
if a:cl[line] | continue | endif
endif
" Preserve indentation
if match(tokens[0], '^\s*$') != -1
let tokens = extend([join(tokens[0:1], '')], tokens[2:-1])
endif
let max_tokens = max([len(tokens), max_tokens]) let max_tokens = max([len(tokens), max_tokens])
if a:nth > 0
if a:nth > 0 " Positive field number
if len(tokens) < a:nth if len(tokens) < a:nth
continue continue
endif endif
let nth = a:nth - 1 " 0-based let nth = a:nth - 1 " make it 0-based
else else " Negative field number
let nth = len(tokens) + a:nth
if match(tokens[len(tokens) - 1], pattern.'$') == -1 if match(tokens[len(tokens) - 1], pattern.'$') == -1
let nth = len(tokens) + a:nth - 1 let nth -= 1
else
let nth = len(tokens) + a:nth
endif endif
if nth < 0 || nth == len(tokens) if nth < 0 || nth == len(tokens)
@@ -102,53 +145,68 @@ function! s:do_align(just, cl, fl, ll, fc, lc, pattern, nth, ml, mr, stick_to_le
let last = tokens[nth] let last = tokens[nth]
let prefix = (nth > 0 ? join(tokens[0 : nth - 1], '') : '') let prefix = (nth > 0 ? join(tokens[0 : nth - 1], '') : '')
let token = substitute(last, pattern.'$', '', '') let token = substitute(last, pattern.'$', '', '')
let suffix = substitute(join(tokens[nth + 1: -1], ''), '^\s*', '', '')
if match(last, pattern.'$') == -1 let delim = get(matchlist(last, pattern.'$'), 1, '')
if a:just == 0 && get(g:, 'easy_align_ignore_unmatched', 1) if empty(delim) && a:just == 0 && get(g:, 'easy_align_ignore_unmatched', 1)
continue continue
else
let delim = ''
endif
else
let delim = matchlist(last, pattern)[1]
endif endif
let max_just_len = max([s:strwidth(token.prefix), max_just_len]) let max_just_len = max([s:strwidth(prefix.token), max_just_len])
let max_delim_len = max([s:strwidth(delim), max_delim_len]) let max_delim_len = max([s:strwidth(delim), max_delim_len])
let lines[line] = [prefix, token, delim, suffix] let lines[line] = [nth, prefix, token, delim]
endfor endfor
for [line, tokens] in items(lines) " Phase 2
let [prefix, token, delim, suffix] = tokens for [line, elems] in items(lines)
let tokens = a:all_tokens[line]
let [nth, prefix, token, delim] = elems
" Remove the leading whitespaces of the next token
if len(tokens) > nth + 1
let tokens[nth + 1] = substitute(tokens[nth + 1], '^\s*', '', '')
endif
" Pad the token with spaces
let pad = repeat(' ', max_just_len - s:strwidth(prefix) - s:strwidth(token)) let pad = repeat(' ', max_just_len - s:strwidth(prefix) - s:strwidth(token))
let rpad = ''
if a:just == 0 if a:just == 0
if a:stick_to_left if a:stick_to_left
let suffix = pad . suffix let rpad = pad
else else
let token = token . pad let token = token . pad
endif endif
elseif a:just == 1 elseif a:just == 1
let token = pad . token let token = pad . token
endif endif
let tokens[nth] = token
" Pad the delimiter
let delim = repeat(' ', max_delim_len - s:strwidth(delim)). delim let delim = repeat(' ', max_delim_len - s:strwidth(delim)). delim
" Before and after the range (for blockwise visual mode)
let cline = getline(line) let cline = getline(line)
let before = strpart(cline, 0, a:fc - 1) let before = strpart(cline, 0, a:fc - 1)
let after = a:lc ? strpart(cline, a:lc) : '' let after = a:lc ? strpart(cline, a:lc) : ''
" Determine the left and right margin around the delimiter
let rest = join(tokens[nth + 1 : -1], '')
let ml = empty(prefix . token) ? '' : a:ml let ml = empty(prefix . token) ? '' : a:ml
let mr = (empty(suffix . after) || (empty(suffix) && stridx(after, a:mr) == 0)) ? '' : a:mr let mr = (empty(rest) ||
let aligned = join([prefix, token, ml, delim, mr, suffix], '') \ (empty(rest) && stridx(after, a:mr) == 0)) ? '' : a:mr
let aligned = empty(after) ? substitute(aligned, '\s*$', '', '') : aligned
call setline(line, before.aligned.after) " Align the token
let aligned = join([token, ml, delim, mr, rpad], '')
let tokens[nth] = aligned
" Update the line
let newline = substitute(before.join(tokens, '').after, '\s*$', '', '')
call setline(line, newline)
endfor endfor
if a:recursive && a:nth < max_tokens if a:recursive && a:nth < max_tokens
let just = a:recursive == 2 ? !a:just : a:just let just = a:recursive == 2 ? !a:just : a:just
call s:do_align(just, a:cl, a:fl, a:ll, a:fc, a:lc, a:pattern, a:nth + 1, a:ml, a:mr, a:stick_to_left, a:recursive) call s:do_align(just, a:all_tokens, a:fl, a:ll, a:fc, a:lc, a:pattern,
\ a:nth + 1, a:ml, a:mr, a:stick_to_left, a:recursive)
endif endif
endfunction endfunction
@@ -178,67 +236,53 @@ function! easy_align#align(just, ...) range
elseif c == 13 " Enter key elseif c == 13 " Enter key
let just = (just + 1) % len(s:just) let just = (just + 1) % len(s:just)
elseif ch == '-' elseif ch == '-'
if empty(n) if empty(n) | let n = '-'
let n = '-' elseif n == '-' | let n = ''
elseif n == '-' else | break
let n = ''
else
break
endif endif
elseif ch == '*' elseif ch == '*'
if empty(n) if empty(n) | let n = '*'
let n = '*' elseif n == '*' | let n = '**'
elseif n == '*' elseif n == '**' | let n = ''
let n = '**' else | break
elseif n == '**'
let n = ''
else
break
endif endif
elseif c >= 48 && c <= 57 elseif c >= 48 && c <= 57 " Numbers
if n[0] == '*' if n[0] == '*' | break
break else | let n = n . ch
else
let n = n . ch
end end
else else
break break
endif endif
endwhile endwhile
elseif a:0 == 1 elseif a:0 == 1
let tokens = matchlist(a:1, '^\([1-9][0-9]*\|-[0-9]*\|\*\)\?\(.\)$') let tokens = matchlist(a:1, '^\([1-9][0-9]*\|-[0-9]*\|\*\*\?\)\?\(.\)$')
if empty(tokens) if empty(tokens)
echo "Invalid arguments: ". a:1 echo "Invalid arguments: ". a:1
return return
endif endif
let [n, ch] = tokens[1:2] let [n, ch] = tokens[1:2]
elseif a:0 == 2 elseif a:0 == 2
let n = a:1 let [n, ch] = a:000
let ch = a:2
else else
echo "Invalid number of arguments: ". a:0 ." (expected 0, 1, or 2)" echo "Invalid number of arguments: ". a:0 ." (expected 0, 1, or 2)"
return return
endif endif
if n == '*' if n == '*' | let [nth, recursive] = [1, 1]
let nth = 1 elseif n == '**' | let [nth, recursive] = [1, 2]
let recursive = 1 elseif n == '-' | let nth = -1
elseif n == '**' elseif empty(n) | let nth = 1
let nth = 1 elseif n == '0' || ( n != '-0' && n != string(str2nr(n)) )
let recursive = 2
elseif n == '-'
let nth = -1
elseif empty(n)
let nth = 1
elseif n != '-0' && n != string(str2nr(n))
echon "\rInvalid field number: ". n echon "\rInvalid field number: ". n
return return
else else
let nth = n let nth = n
endif endif
let delimiters = extend(copy(s:easy_align_delimiters_default), let delimiters = s:easy_align_delimiters_default
\ get(g:, 'easy_align_delimiters', {})) if exists('g:easy_align_delimiters')
let delimiters = extend(copy(delimiters), g:easy_align_delimiters)
endif
if has_key(delimiters, ch) if has_key(delimiters, ch)
let dict = delimiters[ch] let dict = delimiters[ch]

View File

@@ -67,10 +67,17 @@ In blockwise-visual mode (`CTRL-V`), EasyAlign command aligns only
the selected text in the block, instead of the whole lines in the range. the selected text in the block, instead of the whole lines in the range.
Ignoring comment lines *g:easy_align_ignore_comment* Ignoring delimiters in comments or strings *g:easy_align_ignores*
------------------------------------------------------------------------- -------------------------------------------------------------------------
EasyAlign by default ignores comment lines. EasyAlign can be configured to ignore delimiters in certain highlight
groups, such as code comments or strings. By default, delimiters that are
highlighted as code comments or strings are ignored.
" Default:
" If a delimiter is in a highlight group whose name matches
" any of the followings, it will be ignored.
let g:easy_align_ignores = ['Comment', 'String']
For example, For example,
@@ -79,38 +86,34 @@ For example,
apple: 1, apple: 1,
# Quantity of bananas: 2 # Quantity of bananas: 2
bananas: 2, bananas: 2,
# Quantity of grapefruits: 3 # Quantity of grape:fruits: 3
grapefruits: 3 'grape:fruits': 3
} }
becomes becomes
{ {
# Quantity of apples: 1 # Quantity of apples: 1
apple: 1, apple: 1,
# Quantity of bananas: 2 # Quantity of bananas: 2
bananas: 2, bananas: 2,
# Quantity of grapefruits: 3 # Quantity of grape:fruits: 3
grapefruits: 3 'grape:fruits': 3
} }
Since finding comment lines is done heuristically using syntax highlighting You can override `g:easy_align_ignores` to change the rule.
feature, this only works when syntax highlighting is enabled.
If you do not want comment lines to be ignored, you can unset let g:easy_align_ignores = []
`g:easy_align_ignore_comment` as follows.
let g:easy_align_ignore_comment = 0
Then you get, Then you get,
{ {
# Quantity of apples: 1 # Quantity of apples: 1
apple: 1, apple: 1,
# Quantity of bananas: 2 # Quantity of bananas: 2
bananas: 2, bananas: 2,
# Quantity of grapefruits: 3 # Quantity of grape: fruits: 3
grapefruits: 3 'grape: fruits': 3
} }