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
- Extensible alignment rules
- 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
Demo
@@ -17,8 +17,6 @@ Demo
![Screencast](https://raw.github.com/junegunn/vim-easy-align/gif/vim-easy-align.gif)
[Screencast](https://vimeo.com/63506219)
Installation
------------
@@ -115,15 +113,24 @@ since the same can be easily done using the negative field number: `<Enter>-=`
Options
-------
| Option | Type | Default | Description |
| ----------------------------- | ---------- | ------- | --------------------------------------- |
| g:easy_align_ignore_comment | boolean | `1` | Ignore comment lines |
| g:easy_align_ignore_unmatched | boolean | `1` | Ignore lines without matching delimiter |
| g:easy_align_delimiters | dictionary | `{}` | Extend or override alignment rules |
| Option | Type | Default | Description |
| ----------------------------- | ---------- | --------------------- | ----------------------------------------------------- |
| 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_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,
@@ -133,8 +140,8 @@ For example,
apple: 1,
# Quantity of bananas: 2
bananas: 2,
# Quantity of grapefruits: 3
grapefruits: 3
# Quantity of grape:fruits: 3
'grape:fruits': 3
}
```
@@ -143,39 +150,42 @@ becomes
```ruby
{
# Quantity of apples: 1
apple: 1,
apple: 1,
# Quantity of bananas: 2
bananas: 2,
# Quantity of grapefruits: 3
grapefruits: 3
bananas: 2,
# Quantity of grape:fruits: 3
'grape:fruits': 3
}
```
Since finding comment lines is done heuristically using syntax highlighting feature,
this only works when syntax highlighting is enabled.
Naturally, 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
let g:easy_align_ignore_comment = 0
" Ignore nothing!
let g:easy_align_ignores = []
```
Then you get,
```ruby
{
# Quantity of apples: 1
apple: 1,
# Quantity of bananas: 2
bananas: 2,
# Quantity of grapefruits: 3
grapefruits: 3
# Quantity of apples: 1
apple: 1,
# Quantity of bananas: 2
bananas: 2,
# Quantity of grape: fruits: 3
'grape: fruits': 3
}
```
Satisfied? :satisfied:
### 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,
@@ -222,6 +232,7 @@ Then we get,
### Extending alignment rules
```vim
" Examples
let g:easy_align_delimiters = {
\ '>': { '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': 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
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 max_just_len = 0
let max_delim_len = 0
let max_tokens = 0
let pattern = '\s*\(' .a:pattern. '\)\s' . (a:stick_to_left ? '*' : '\{-}')
let ignore_comment = has('syntax') && exists('g:syntax_on') &&
\ get(g:, 'easy_align_ignore_comment', 1)
let ignored_syntax = s:ignored_syntax()
" Phase 1
for line in range(a:fl, a:ll)
let tokens = split(a:lc ?
\ strpart(getline(line), a:fc - 1, a:lc - a:fc + 1) :
\ strpart(getline(line), a:fc - 1),
\ pattern.'\zs')
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')
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)
continue
endif
if ignore_comment
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
" Calculate the maximum number of tokens for a line within the range
let max_tokens = max([len(tokens), max_tokens])
if a:nth > 0
if a:nth > 0 " Positive field number
if len(tokens) < a:nth
continue
endif
let nth = a:nth - 1 " 0-based
else
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
let nth = len(tokens) + a:nth - 1
else
let nth = len(tokens) + a:nth
let nth -= 1
endif
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 prefix = (nth > 0 ? join(tokens[0 : nth - 1], '') : '')
let token = substitute(last, pattern.'$', '', '')
let suffix = substitute(join(tokens[nth + 1: -1], ''), '^\s*', '', '')
if match(last, pattern.'$') == -1
if a:just == 0 && get(g:, 'easy_align_ignore_unmatched', 1)
continue
else
let delim = ''
endif
else
let delim = matchlist(last, pattern)[1]
let delim = get(matchlist(last, pattern.'$'), 1, '')
if empty(delim) && a:just == 0 && get(g:, 'easy_align_ignore_unmatched', 1)
continue
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 lines[line] = [prefix, token, delim, suffix]
let lines[line] = [nth, prefix, token, delim]
endfor
for [line, tokens] in items(lines)
let [prefix, token, delim, suffix] = tokens
" Phase 2
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 rpad = ''
if a:just == 0
if a:stick_to_left
let suffix = pad . suffix
let rpad = pad
else
let token = token . pad
endif
elseif a:just == 1
let token = pad . token
endif
let tokens[nth] = token
" Pad the delimiter
let delim = repeat(' ', max_delim_len - s:strwidth(delim)). delim
" Before and after the range (for blockwise visual mode)
let cline = getline(line)
let before = strpart(cline, 0, a:fc - 1)
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 mr = (empty(suffix . after) || (empty(suffix) && stridx(after, a:mr) == 0)) ? '' : a:mr
let aligned = join([prefix, token, ml, delim, mr, suffix], '')
let aligned = empty(after) ? substitute(aligned, '\s*$', '', '') : aligned
let mr = (empty(rest) ||
\ (empty(rest) && stridx(after, a:mr) == 0)) ? '' : a:mr
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
if a:recursive && a:nth < max_tokens
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
endfunction
@@ -178,67 +236,53 @@ function! easy_align#align(just, ...) range
elseif c == 13 " Enter key
let just = (just + 1) % len(s:just)
elseif ch == '-'
if empty(n)
let n = '-'
elseif n == '-'
let n = ''
else
break
if empty(n) | let n = '-'
elseif n == '-' | let n = ''
else | break
endif
elseif ch == '*'
if empty(n)
let n = '*'
elseif n == '*'
let n = '**'
elseif n == '**'
let n = ''
else
break
if empty(n) | let n = '*'
elseif n == '*' | let n = '**'
elseif n == '**' | let n = ''
else | break
endif
elseif c >= 48 && c <= 57
if n[0] == '*'
break
else
let n = n . ch
elseif c >= 48 && c <= 57 " Numbers
if n[0] == '*' | break
else | let n = n . ch
end
else
break
endif
endwhile
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)
echo "Invalid arguments: ". a:1
return
endif
let [n, ch] = tokens[1:2]
elseif a:0 == 2
let n = a:1
let ch = a:2
let [n, ch] = a:000
else
echo "Invalid number of arguments: ". a:0 ." (expected 0, 1, or 2)"
return
endif
if n == '*'
let nth = 1
let recursive = 1
elseif n == '**'
let nth = 1
let recursive = 2
elseif n == '-'
let nth = -1
elseif empty(n)
let nth = 1
elseif n != '-0' && n != string(str2nr(n))
if n == '*' | let [nth, recursive] = [1, 1]
elseif n == '**' | let [nth, recursive] = [1, 2]
elseif n == '-' | let nth = -1
elseif empty(n) | let nth = 1
elseif n == '0' || ( n != '-0' && n != string(str2nr(n)) )
echon "\rInvalid field number: ". n
return
else
let nth = n
endif
let delimiters = extend(copy(s:easy_align_delimiters_default),
\ get(g:, 'easy_align_delimiters', {}))
let delimiters = s:easy_align_delimiters_default
if exists('g:easy_align_delimiters')
let delimiters = extend(copy(delimiters), g:easy_align_delimiters)
endif
if has_key(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.
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,
@@ -79,38 +86,34 @@ For example,
apple: 1,
# Quantity of bananas: 2
bananas: 2,
# Quantity of grapefruits: 3
grapefruits: 3
# Quantity of grape:fruits: 3
'grape:fruits': 3
}
becomes
{
# Quantity of apples: 1
apple: 1,
apple: 1,
# Quantity of bananas: 2
bananas: 2,
# Quantity of grapefruits: 3
grapefruits: 3
bananas: 2,
# Quantity of grape:fruits: 3
'grape:fruits': 3
}
Since finding comment lines is done heuristically using syntax highlighting
feature, this only works when syntax highlighting is enabled.
You can override `g:easy_align_ignores` to change the rule.
If you do not want comment lines to be ignored, you can unset
`g:easy_align_ignore_comment` as follows.
let g:easy_align_ignore_comment = 0
let g:easy_align_ignores = []
Then you get,
{
# Quantity of apples: 1
apple: 1,
# Quantity of bananas: 2
bananas: 2,
# Quantity of grapefruits: 3
grapefruits: 3
# Quantity of apples: 1
apple: 1,
# Quantity of bananas: 2
bananas: 2,
# Quantity of grape: fruits: 3
'grape: fruits': 3
}