28 Commits
1.2 ... 1.7

Author SHA1 Message Date
Junegunn Choi
5f59570c9f Ignore delimiters in certain syntax highlighting groups 2013-07-30 01:54:32 +09:00
Junegunn Choi
5b48e997a1 Update doc 2013-07-19 01:03:15 +09:00
Junegunn Choi
67ac2f74e6 Merge pull request #3 from junegunn/alternating
Left-right alternating alignment (**)
2013-07-15 04:56:26 -07:00
Junegunn Choi
94524ec3d3 Left-right alternating alignment (**) 2013-07-15 20:53:30 +09:00
Junegunn Choi
3e700c6ce4 Update an example rule 2013-07-15 10:02:33 +09:00
Junegunn Choi
3ee8cdfd9e Optimize recursive(*) alignment performance 2013-07-06 15:19:30 +09:00
Junegunn Choi
a6e707b1b3 Ignore comment lines 2013-06-19 14:20:37 +09:00
Junegunn Choi
34a555c220 More rules 2013-05-16 18:58:17 +09:00
Junegunn Choi
912bb949f7 Update custom alignment rule example 2013-05-16 18:45:49 +09:00
Junegunn Choi
81138d0e2c update README 2013-05-14 01:02:06 +09:00
Junegunn Choi
214efaad0c update help example 2013-05-14 01:00:42 +09:00
Junegunn Choi
29078629f5 Merge pull request #1 from junegunn/negative-modifier
Add support for negative field number (scan backwards)
2013-05-13 08:51:22 -07:00
Junegunn Choi
3ab67e2870 update doc 2013-05-14 00:48:21 +09:00
Junegunn Choi
8dd2daaa9e fix to preserve trailing whitespaces from prefix when delimiter sticks to left 2013-05-14 00:41:56 +09:00
Junegunn Choi
e3f7352ab8 remove unnecessary nr2char call 2013-05-13 15:02:44 +09:00
Junegunn Choi
0fe7d17908 add support for unicode characters 2013-05-13 12:44:42 +09:00
Junegunn Choi
27b95b4ce0 finish implementing negative field number 2013-05-13 12:38:58 +09:00
Junegunn Choi
5c870f60d9 fix non-interactive EasyAlign to take negative field # 2013-05-13 03:21:52 +09:00
Junegunn Choi
cb71847a11 support backspace in interactive mode 2013-05-13 03:10:33 +09:00
Junegunn Choi
811ea63b11 update help 2013-05-13 03:01:43 +09:00
Junegunn Choi
9c9a16c759 add use case of negative field number 2013-05-13 02:59:01 +09:00
Junegunn Choi
1e627366e7 support negative modifier (backward scan) 2013-05-13 02:43:29 +09:00
Junegunn Choi
32224d88f2 less silly example 2013-05-13 02:04:48 +09:00
Junegunn Choi
1d9c5ef9a4 fix helptag 2013-05-10 18:11:08 +09:00
Junegunn Choi
b35b11d884 add screencast gif 2013-05-07 23:07:00 +09:00
Junegunn Choi
0ce554c4a5 fix typo 2013-05-07 01:18:24 +09:00
Junegunn Choi
3e5a11c3e6 add installation section 2013-05-07 01:14:46 +09:00
Junegunn Choi
99bc81d492 add === to = group 2013-05-07 01:07:30 +09:00
5 changed files with 636 additions and 180 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
```

243
README.md
View File

@@ -3,10 +3,32 @@ vim-easy-align
A simple, easy-to-use Vim alignment plugin without too much ambition.
Features:
- Optimized for code editing
- Requires minimal keystrokes
- Extensible alignment rules
- Aligns text around either _all or n-th_ occurrence(s) of the delimiter
- Ignores delimiters in certain syntax highlighting groups (e.g. comments, strings)
- Ignores lines without a matching delimiter
Demo
----
[Screencast](https://vimeo.com/63506219)
![Screencast](https://raw.github.com/junegunn/vim-easy-align/gif/vim-easy-align.gif)
Installation
------------
Either [download zip file](http://www.vim.org/scripts/script.php?script_id=4520)
and extract in ~/.vim or use [Vundle](https://github.com/gmarik/vundle) (recommended)
or [Pathogen](https://github.com/tpope/vim-pathogen).
### With Vundle
```vim
Bundle 'junegunn/vim-easy-align'
```
Usage
-----
@@ -23,41 +45,48 @@ With the mapping, you can align selected lines with a few keystrokes.
1. `<Enter>` key to start interactive EasyAlign command
1. Optional Enter keys to toggle right-justification mode
1. Optional field number (default: 1)
- `1` Alignment around 1st delimiters
- `2` Alignment around 2nd delimiters
- `1` Around the 1st occurrences of delimiters
- `2` Around the 2nd occurrences of delimiters
- ...
- `*` Alignment around all delimiters (recursive)
1. Delimiter (`<space>`, `=`, `:`, `.`, `|`, `,`)
- `*` Around all occurrences of delimiters
- `**` Left-right alternating alignment around all delimiters
- `-` Around the last occurrences of delimiters (`-1`)
- `-2` Around the second to last occurrences of delimiters
- ...
1. Delimiter key (a single keystroke; `<space>`, `=`, `:`, `.`, `|`, `,`)
Alignment rules for the following delimiters have been crafted to meet the most needs.
Alignment rules for the following delimiters have been defined to meet the most needs.
| Delimiter | Description/Use cases |
| --------- | ---------------------------------------------------------- |
| `<space>` | General alignment around spaces |
| `=` | Operators containing equals sign (=, ==, !=, +=, &&=, ...) |
| `:` | Suitable for formatting JSON or YAML |
| `.` | Multi-line method chaining |
| `,` | Multi-line method arguments |
| &#124; | Table markdown |
| Delimiter key | Description/Use cases |
| ------------- | ---------------------------------------------------------- |
| `<space>` | General alignment around spaces |
| `=` | Operators containing equals sign (=, ==, !=, +=, &&=, ...) |
| `:` | Suitable for formatting JSON or YAML |
| `.` | Multi-line method chaining |
| `,` | Multi-line method arguments |
| &#124; | Table markdown |
### Example command sequences
| With visual map | Description | Equivalent command |
| ----------------- | ------------------------------------------------- | ----------------------- |
| `<Enter>=` | Alignment around 1st equals signs (and the likes) | `:'<,'>EasyAlign=` |
| `<Enter>2=` | Alignment around 2nd equals signs (and the likes) | `:'<,'>EasyAlign2=` |
| `<Enter>3=` | Alignment around 3rd equals signs (and the likes) | `:'<,'>EasyAlign3=` |
| `<Enter>*=` | Alignment around all equals signs (and the likes) | `:'<,'>EasyAlign*=` |
| `<Enter><Enter>=` | Right-justified alignment around 1st equals signs | `:'<,'>EasyAlignRight=` |
| `<Enter><space>` | Alignment around 1st space | `:'<,'>EasyAlign\ ` |
| `<Enter>2<space>` | Alignment around 2nd space | `:'<,'>EasyAlign2\ ` |
| `<Enter>:` | Alignment around 1st colon | `:'<,'>EasyAlign:` |
| ... | ... | |
| With visual map | Description | Equivalent command |
| ------------------- | -------------------------------------------------------- | ------------------------- |
| `<Enter><space>` | Alignment around 1st whitespaces | `:'<,'>EasyAlign\ ` |
| `<Enter>2<space>` | Alignment around 2nd whitespaces | `:'<,'>EasyAlign2\ ` |
| `<Enter>-<space>` | Alignment around the last whitespaces | `:'<,'>EasyAlign-\ ` |
| `<Enter>:` | Alignment around 1st colon | `:'<,'>EasyAlign:` |
| `<Enter>=` | Alignment around 1st equals signs (and the likes) | `:'<,'>EasyAlign=` |
| `<Enter>2=` | Alignment around 2nd equals signs (and the likes) | `:'<,'>EasyAlign2=` |
| `<Enter>3=` | Alignment around 3rd equals signs (and the likes) | `:'<,'>EasyAlign3=` |
| `<Enter>*=` | Alignment around all equals signs (and the likes) | `:'<,'>EasyAlign*=` |
| `<Enter>**=` | Left-right alternating alignment around all equals signs | `:'<,'>EasyAlign**=` |
| `<Enter><Enter>=` | Right-justified alignment around 1st equals signs | `:'<,'>EasyAlignRight=` |
| `<Enter><Enter>**=` | Right-left alternating alignment around all equals signs | `:'<,'>EasyAlignRight**=` |
| ... | ... | |
### Partial alignment in blockwise-visual mode
In blockwise-visual mode (`CTRL-V`), EasyAlign command aligns only the selected
parts, instead of the whole lines in the range.
text in the block, instead of the whole lines in the range.
Consider the following case where you want to align text around `=>` operators.
@@ -78,56 +107,111 @@ my_hash = { :a => 1,
:aaa => 3 }
```
Defining custom alignment rules
-------------------------------
However, in this case, we don't really need blockwise visual mode
since the same can be easily done using the negative field number: `<Enter>-=`
Options
-------
| 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 delimiters in comments or strings
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
let g:easy_align_delimiters = {
\ '>': { 'pattern': '>>\|=>\|>' },
\ '/': { 'pattern': '//*' },
\ 'x': {
\ 'pattern': '[xX]',
\ 'margin_left': ' <<<',
\ 'margin_right': '>>> ',
\ 'stick_to_left': 0
\ }
\ }
" 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']
```
Handling unmatched lines
------------------------
For example,
EasyAlign by default ignores lines without the matching delimiters (except in right-justification mode).
This is to ignore interleaved comments commonly found in code.
For example, when aligning the following code block,
```
```ruby
{
# Quantity of apples
# Quantity of apples: 1
apple: 1,
# Quantity of bananas
# Quantity of bananas: 2
bananas: 2,
# Quantity of grapefruits
grapefruits: 3
# Quantity of grape:fruits: 3
'grape:fruits': 3
}
```
we don't want to the comment lines to affect the alignment,
so this is usually what we want.
becomes
```
```ruby
{
# Quantity of apples
apple: 1,
# Quantity of bananas
bananas: 2,
# Quantity of grapefruits
# Quantity of apples: 1
apple: 1,
# Quantity of bananas: 2
bananas: 2,
# Quantity of grape:fruits: 3
'grape:fruits': 3
}
```
Naturally, this only works when syntax highlighting is enabled.
You can override `g:easy_align_ignores` to change the rule.
```vim
" 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 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).
For example, when aligning the following code block around the colons,
```ruby
{
apple: proc {
this_line_does_not_have_a_colon
},
bananas: 2,
grapefruits: 3
}
```
However, this default behavior is configurable.
this is usually what we want.
```ruby
{
apple: proc {
this_line_does_not_have_a_colon
},
bananas: 2,
grapefruits: 3
}
```
However, this default behavior is also configurable.
```vim
let g:easy_align_ignore_unmatched = 0
@@ -135,18 +219,45 @@ let g:easy_align_ignore_unmatched = 0
Then we get,
```
```ruby
{
# Quantity of apples
apple: 1,
# Quantity of bananas
bananas: 2,
# Quantity of grapefruits
grapefruits: 3
apple: proc {
this_line_does_not_have_a_colon
},
bananas: 2,
grapefruits: 3
}
```
### Extending alignment rules
```vim
" Examples
let g:easy_align_delimiters = {
\ '>': { 'pattern': '>>\|=>\|>' },
\ '/': { 'pattern': '//\+\|/\*\|\*/' },
\ '#': { 'pattern': '#\+' },
\ ']': {
\ 'pattern': '[\[\]]',
\ 'margin_left': '',
\ 'margin_right': '',
\ 'stick_to_left': 0
\ },
\ ')': {
\ 'pattern': '[()]',
\ 'margin_left': '',
\ 'margin_right': '',
\ 'stick_to_left': 0
\ }
\ }
```
Author
------
[Junegunn Choi](https://github.com/junegunn)
License
-------
MIT

View File

@@ -1,95 +1,212 @@
if exists("g:easy_align_loaded")
" Copyright (c) 2013 Junegunn Choi
"
" MIT License
"
" Permission is hereby granted, free of charge, to any person obtaining
" a copy of this software and associated documentation files (the
" "Software"), to deal in the Software without restriction, including
" without limitation the rights to use, copy, modify, merge, publish,
" distribute, sublicense, and/or sell copies of the Software, and to
" permit persons to whom the Software is furnished to do so, subject to
" the following conditions:
"
" The above copyright notice and this permission notice shall be
" included in all copies or substantial portions of the Software.
"
" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
if exists("g:loaded_easy_align")
finish
endif
let g:easy_align_loaded = 1
let g:loaded_easy_align = 1
let s:easy_align_delimiters_default = {
\ ' ': { 'pattern': ' ', 'margin_left': '', 'margin_right': '', 'stick_to_left': 0 },
\ '=': { 'pattern': '<=>\|\(&&\|||\|<<\|>>\)=\|=\~\|=>\|[:+/*!%^=><&|-]\?=',
\ '=': { 'pattern': '===\|<=>\|\(&&\|||\|<<\|>>\)=\|=\~\|=>\|[:+/*!%^=><&|-]\?=[#?]\?',
\ 'margin_left': ' ', 'margin_right': ' ', 'stick_to_left': 0 },
\ ':': { '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 }
\ }
let s:just = ['', '[R]']
function! s:do_align(just, 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*'
if exists("*strwidth")
function! s:strwidth(str)
return strwidth(a:str)
endfunction
else
function! s:strwidth(str)
return len(split(a:str, '\zs'))
endfunction
endif
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 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
" 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 len(tokens) < a:nth
continue
if a:nth > 0 " Positive field number
if len(tokens) < a:nth
continue
endif
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 -= 1
endif
if nth < 0 || nth == len(tokens)
continue
endif
endif
let nth = a:nth - 1 " 0-based
let last = tokens[nth]
let prefix = (nth > 0 ? join(tokens[0 : nth - 1], '') : '')
let token = substitute(last, pattern.'$', '', '')
let suffix = join(tokens[nth + 1: -1], '')
if match(last, pattern.'$') == -1
if a:just == 0 && (!exists("g:easy_align_ignore_unmatched") || g:easy_align_ignore_unmatched)
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([len(token.prefix), max_just_len])
let max_delim_len = max([len(delim), max_delim_len])
let lines[line] = [prefix, token, delim, suffix]
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] = [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
let pad = repeat(' ', max_just_len - len(prefix) - len(token))
" 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
let delim = repeat(' ', max_delim_len - len(delim)). delim
" 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
call s:do_align(a:just, a:fl, a:ll, a:fc, a:lc, a:pattern, a:nth + 1, a:ml, a:mr, a:stick_to_left, a:recursive)
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,
\ a:nth + 1, a:ml, a:mr, a:stick_to_left, a:recursive)
endif
endfunction
@@ -110,65 +227,74 @@ function! easy_align#align(just, ...) range
let c = getchar()
let ch = nr2char(c)
if c == 3 || c == 27
if c == 3 || c == 27 " CTRL-C / ESC
return
elseif c == 13
elseif c == '<27>kb' " Backspace
if len(n) > 0
let n = strpart(n, 0, len(n) - 1)
endif
elseif c == 13 " Enter key
let just = (just + 1) % len(s:just)
elseif c >= 48 && c <= 57
if n == '*'
break
else
let n = n . nr2char(c)
elseif ch == '-'
if empty(n) | let n = '-'
elseif n == '-' | let n = ''
else | break
endif
elseif ch == '*'
if !empty(n)
break
else
let n = '*'
if empty(n) | let n = '*'
elseif n == '*' | let n = '**'
elseif n == '**' | let n = ''
else | break
endif
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]*\|\*\)\?\(.\)$')
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 n = 1
let recursive = 1
elseif empty(n)
let n = 1
elseif 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),
\ exists("g:easy_align_delimiters") ? 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]
call s:do_align(just, a:firstline, a:lastline,
call s:do_align(just, {}, a:firstline, a:lastline,
\ visualmode() == '' ? min([col("'<"), col("'>")]) : 1,
\ visualmode() == '' ? max([col("'<"), col("'>")]) : 0,
\ get(dict, 'pattern', ch),
\ n,
\ nth,
\ get(dict, 'margin_left', ' '),
\ get(dict, 'margin_right', ' '),
\ get(dict, 'stick_to_left', 0), recursive)
call s:echon(just, (recursive ? '*' : n), ch)
call s:echon(just, n, ch)
else
echon "\rUnknown delimiter: ". ch
endif

View File

@@ -22,11 +22,14 @@ With this mapping, you can align selected lines with a few keystrokes.
1. <Enter> key to start interactive EasyAlign command
2. Optional Enter keys to switch justficiation mode (default: left)
3. Optional field number (default: 1)
1 Alignment around 1st delimiters
2 Alignment around 2nd delimiters
1 Around the 1st occurrences of delimiters
2 Around the 2nd occurrences of delimiters
* Around all occurrences of delimiters
** Left-right alternating alignment around all delimiters
- Around the last occurrences of delimiters (`-1`)
-2 Around the second to last occurrences of delimiters
...
* Alignment around all delimiters (recursive)
4. Delimiter
4. Delimiter key (a single keystroke)
<space> General alignment around whitespaces
= Operators containing equals sign (=, ==, !=, +=, &&=, ...)
: Suitable for formatting JSON or YAML
@@ -38,85 +41,142 @@ During the key sequence, <Enter> key will toggle right-justification mode.
Examples:
<Enter>= Alignment around 1st equals signs (and the likes)
<Enter>2= Alignment around 2nd equals signs (and the likes)
<Enter>3= Alignment around 3rd equals signs (and the likes)
<Enter>*= Alignment around all equals signs (and the likes)
<Enter><Enter>= Right-justified alignment around 1st equals signs
<Enter><space> Alignment around 1st whitespace
<Enter>2<space> Alignment around 2nd whitespace
<Enter>: Alignment around 1st colon
<Enter><space> Alignment around 1st whitespaces
<Enter>2<space> Alignment around 2nd whitespaces
<Enter>-<space> Alignment around the last whitespaces
<Enter>: Alignment around 1st colon
<Enter>= Alignment around 1st equals signs (and the likes)
<Enter>2= Alignment around 2nd equals signs (and the likes)
<Enter>3= Alignment around 3rd equals signs (and the likes)
<Enter>*= Alignment around all equals signs (and the likes)
<Enter>**= Left-right alternating alignment around all equals signs
<Enter><Enter>= Right-justified alignment around 1st equals signs
<Enter><Enter>**= Right-left alternating alignment around all equals signs
EasyAlignRight *EasyAlignRight*
-------------------------------------------------------------------------
*EasyAlignRight* is the right-justified version of EasyAlign command.
EasyAlignRight is the right-justified version of EasyAlign command.
Partial alignment in blockwise-visual mode
-------------------------------------------------------------------------
In blockwise-visual mode (`CTRL-V`), EasyAlign command aligns only
the selected parts, instead of the whole lines in the range.
the selected text in the block, instead of the whole lines in the range.
Defining custom alignment rules *g:easy_align_delimiters*
Ignoring delimiters in comments or strings *g:easy_align_ignores*
-------------------------------------------------------------------------
let g:easy_align_delimiters = {
\ '/': { 'pattern': '//*' },
\ 'x': {
\ 'pattern': '[xX]',
\ 'margin_left': ' <<<',
\ 'margin_right': '>>> ',
\ 'stick_to_left': 0
\ }
\ }
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,
{
# Quantity of apples: 1
apple: 1,
# Quantity of bananas: 2
bananas: 2,
# Quantity of grape:fruits: 3
'grape:fruits': 3
}
becomes
{
# Quantity of apples: 1
apple: 1,
# Quantity of bananas: 2
bananas: 2,
# Quantity of grape:fruits: 3
'grape:fruits': 3
}
You can override `g:easy_align_ignores` to change the rule.
let g:easy_align_ignores = []
Then you get,
{
# Quantity of apples: 1
apple: 1,
# Quantity of bananas: 2
bananas: 2,
# Quantity of grape: fruits: 3
'grape: fruits': 3
}
Handling unmatched lines *g:easy_align_ignore_unmatched*
-------------------------------------------------------------------------
EasyAlign by default ignores lines without the matching delimiters
(except in right-justification mode).
This is to ignore interleaved comments commonly found in code.
Lines without a matching delimiter are ignored as well (except in
right-justification mode).
For example, when aligning the following code,
For example, when aligning the following code block around the colons,
{
# Quantity of apples
apple: 1,
# Quantity of bananas
apple: proc {
this_line_does_not_have_a_colon
},
bananas: 2,
# Quantity of grapefruits
grapefruits: 3
}
this is usually what we want.
{
# Quantity of apples
apple: 1,
# Quantity of bananas
apple: proc {
this_line_does_not_have_a_colon
},
bananas: 2,
# Quantity of grapefruits
grapefruits: 3
}
However, this default behavior is configurable.
However, this default behavior is also configurable.
let g:easy_align_ignore_unmatched = 0
Then we get,
{
# Quantity of apples
apple: 1,
# Quantity of bananas
bananas: 2,
# Quantity of grapefruits
grapefruits: 3
apple: proc {
this_line_does_not_have_a_colon
},
bananas: 2,
grapefruits: 3
}
Extending alignment rules *g:easy_align_delimiters*
-------------------------------------------------------------------------
let g:easy_align_delimiters = {
\ '>': { 'pattern': '>>\|=>\|>' },
\ '/': { 'pattern': '//\+' },
\ '#': { 'pattern': '#\+' },
\ ']': {
\ 'pattern': '[\[\]]',
\ 'margin_left': '',
\ 'margin_right': '',
\ 'stick_to_left': 0
\ },
\ ')': {
\ 'pattern': '[()]',
\ 'margin_left': '',
\ 'margin_right': '',
\ 'stick_to_left': 0
\ }
\ }

View File

@@ -1,7 +1,30 @@
if exists("g:easy_align_plugin_loaded")
" Copyright (c) 2013 Junegunn Choi
"
" MIT License
"
" Permission is hereby granted, free of charge, to any person obtaining
" a copy of this software and associated documentation files (the
" "Software"), to deal in the Software without restriction, including
" without limitation the rights to use, copy, modify, merge, publish,
" distribute, sublicense, and/or sell copies of the Software, and to
" permit persons to whom the Software is furnished to do so, subject to
" the following conditions:
"
" The above copyright notice and this permission notice shall be
" included in all copies or substantial portions of the Software.
"
" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
if exists("g:loaded_easy_align_plugin")
finish
endif
let g:easy_align_plugin_loaded = 1
let g:loaded_easy_align_plugin = 1
command! -nargs=* -range EasyAlign <line1>,<line2>call easy_align#align(0, <f-args>)
command! -nargs=* -range EasyAlignRight <line1>,<line2>call easy_align#align(1, <f-args>)