├── plugin └── easy_align.vim ├── doc └── easy_align.txt └── autoload └── easy_align.vim /plugin/easy_align.vim: -------------------------------------------------------------------------------- 1 | " Copyright (c) 2014 Junegunn Choi 2 | " 3 | " MIT License 4 | " 5 | " Permission is hereby granted, free of charge, to any person obtaining 6 | " a copy of this software and associated documentation files (the 7 | " "Software"), to deal in the Software without restriction, including 8 | " without limitation the rights to use, copy, modify, merge, publish, 9 | " distribute, sublicense, and/or sell copies of the Software, and to 10 | " permit persons to whom the Software is furnished to do so, subject to 11 | " the following conditions: 12 | " 13 | " The above copyright notice and this permission notice shall be 14 | " included in all copies or substantial portions of the Software. 15 | " 16 | " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | " EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | " NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | " LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | " OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | if exists("g:loaded_easy_align_plugin") 25 | finish 26 | endif 27 | let g:loaded_easy_align_plugin = 1 28 | 29 | command! -nargs=* -range -bang EasyAlign ,call easy_align#align(0, 0, 'command', ) 30 | command! -nargs=* -range -bang LiveEasyAlign ,call easy_align#align(0, 1, 'command', ) 31 | 32 | let s:last_command = 'EasyAlign' 33 | 34 | function! s:abs(v) 35 | return a:v >= 0 ? a:v : - a:v 36 | endfunction 37 | 38 | function! s:remember_visual(mode) 39 | let s:last_visual = [a:mode, s:abs(line("'>") - line("'<")), s:abs(col("'>") - col("'<"))] 40 | endfunction 41 | 42 | function! s:repeat_visual() 43 | let [mode, ldiff, cdiff] = s:last_visual 44 | let cmd = 'normal! '.mode 45 | if ldiff > 0 46 | let cmd .= ldiff . 'j' 47 | endif 48 | 49 | let ve_save = &virtualedit 50 | try 51 | if mode == "\" 52 | if cdiff > 0 53 | let cmd .= cdiff . 'l' 54 | endif 55 | set virtualedit+=block 56 | endif 57 | execute cmd.":\=g:easy_align_last_command\\" 58 | call s:set_repeat() 59 | finally 60 | if ve_save != &virtualedit 61 | let &virtualedit = ve_save 62 | endif 63 | endtry 64 | endfunction 65 | 66 | function! s:repeat_in_visual() 67 | if exists('g:easy_align_last_command') 68 | call s:remember_visual(visualmode()) 69 | call s:repeat_visual() 70 | endif 71 | endfunction 72 | 73 | function! s:set_repeat() 74 | silent! call repeat#set("\(EasyAlignRepeat)") 75 | endfunction 76 | 77 | function! s:generic_easy_align_op(type, vmode, live) 78 | if !&modifiable 79 | if a:vmode 80 | normal! gv 81 | endif 82 | return 83 | endif 84 | let sel_save = &selection 85 | let &selection = "inclusive" 86 | 87 | if a:vmode 88 | let vmode = a:type 89 | let [l1, l2] = ["'<", "'>"] 90 | call s:remember_visual(vmode) 91 | else 92 | let vmode = '' 93 | let [l1, l2] = [line("'["), line("']")] 94 | unlet! s:last_visual 95 | endif 96 | 97 | try 98 | let range = l1.','.l2 99 | if get(g:, 'easy_align_need_repeat', 0) 100 | execute range . g:easy_align_last_command 101 | else 102 | execute range . "call easy_align#align(0, a:live, vmode, '')" 103 | end 104 | call s:set_repeat() 105 | finally 106 | let &selection = sel_save 107 | endtry 108 | endfunction 109 | 110 | function! s:easy_align_op(type, ...) 111 | call s:generic_easy_align_op(a:type, a:0, 0) 112 | endfunction 113 | 114 | function! s:live_easy_align_op(type, ...) 115 | call s:generic_easy_align_op(a:type, a:0, 1) 116 | endfunction 117 | 118 | function! s:easy_align_repeat() 119 | if exists('s:last_visual') 120 | call s:repeat_visual() 121 | else 122 | try 123 | let g:easy_align_need_repeat = 1 124 | normal! . 125 | finally 126 | unlet! g:easy_align_need_repeat 127 | endtry 128 | endif 129 | endfunction 130 | 131 | nnoremap (EasyAlign) :set opfunc=easy_align_opg@ 132 | vnoremap (EasyAlign) :call easy_align_op(visualmode(), 1) 133 | nnoremap (LiveEasyAlign) :set opfunc=live_easy_align_opg@ 134 | vnoremap (LiveEasyAlign) :call live_easy_align_op(visualmode(), 1) 135 | 136 | " vim-repeat support 137 | nnoremap (EasyAlignRepeat) :call easy_align_repeat() 138 | vnoremap (EasyAlignRepeat) :call repeat_in_visual() 139 | 140 | " Backward-compatibility (deprecated) 141 | nnoremap (EasyAlignOperator) :set opfunc=easy_align_opg@ 142 | 143 | " vim: set et sw=2 : 144 | -------------------------------------------------------------------------------- /doc/easy_align.txt: -------------------------------------------------------------------------------- 1 | *easy-align.txt* easy-align Last change: December 14 2014 2 | EASY-ALIGN - TABLE OF CONTENTS *easyalign* *easy-align* *easy-align-toc* 3 | ============================================================================== 4 | 5 | vim-easy-align 6 | Demo |easy-align-1| 7 | Features |easy-align-2| 8 | Installation |easy-align-3| 9 | TLDR - One-minute guide |easy-align-4| 10 | Usage |easy-align-5| 11 | Concept of alignment rule |easy-align-5-1| 12 | Execution models |easy-align-5-2| 13 | 1. Using mappings |easy-align-5-2-1| 14 | 2. Using :EasyAlign command |easy-align-5-2-2| 15 | Interactive mode |easy-align-5-3| 16 | Predefined alignment rules |easy-align-5-3-1| 17 | Examples |easy-align-5-3-2| 18 | Using regular expressions |easy-align-5-3-3| 19 | Alignment options in interactive mode |easy-align-5-3-4| 20 | Live interactive mode |easy-align-5-4| 21 | Non-interactive mode |easy-align-5-5| 22 | Partial alignment in blockwise-visual mode |easy-align-5-6| 23 | Alignment options |easy-align-6| 24 | List of options |easy-align-6-1| 25 | Filtering lines |easy-align-6-2| 26 | Examples |easy-align-6-2-1| 27 | Ignoring delimiters in comments or strings |easy-align-6-3| 28 | Ignoring unmatched lines |easy-align-6-4| 29 | Aligning delimiters of different lengths |easy-align-6-5| 30 | Adjusting indentation |easy-align-6-6| 31 | Alignments over multiple occurrences of delimiters |easy-align-6-7| 32 | Extending alignment rules |easy-align-6-8| 33 | Examples |easy-align-6-8-1| 34 | Other options |easy-align-7| 35 | Disabling &foldmethod during alignment |easy-align-7-1| 36 | Left/right/center mode switch in interactive mode |easy-align-7-2| 37 | Advanced examples and use cases |easy-align-8| 38 | Related work |easy-align-9| 39 | Author |easy-align-10| 40 | License |easy-align-11| 41 | 42 | 43 | VIM-EASY-ALIGN *vim-easy-align* 44 | ============================================================================== 45 | 46 | A simple, easy-to-use Vim alignment plugin. 47 | 48 | 49 | *easy-align-1* 50 | DEMO *easy-align-demo* 51 | ============================================================================== 52 | 53 | Screencast: 54 | https://raw.githubusercontent.com/junegunn/i/master/vim-easy-align.gif 55 | 56 | (Too fast? Slower GIF is {here}{1}) 57 | 58 | {1} https://raw.githubusercontent.com/junegunn/i/master/vim-easy-align-slow.gif 59 | 60 | 61 | *easy-align-2* 62 | FEATURES *easy-align-features* 63 | ============================================================================== 64 | 65 | - Easy to use 66 | - Comes with a predefined set of alignment rules 67 | - Provides a fast and intuitive interface 68 | - Extensible 69 | - You can define your own rules 70 | - Supports arbitrary regular expressions 71 | - Optimized for code editing 72 | - Takes advantage of syntax highlighting feature to avoid unwanted 73 | alignments 74 | 75 | 76 | *easy-align-3* 77 | INSTALLATION *easy-align-installation* 78 | ============================================================================== 79 | 80 | Use your favorite plugin manager. 81 | 82 | Using {vim-plug}{2}: 83 | > 84 | Plug 'junegunn/vim-easy-align' 85 | < 86 | {2} https://github.com/junegunn/vim-plug 87 | 88 | 89 | *easy-align-4* 90 | TLDR - ONE-MINUTE GUIDE *easy-align-tldr-one-minute-guide* 91 | ============================================================================== 92 | 93 | Add the following mappings to your .vimrc. 94 | 95 | *(EasyAlign)* 96 | > 97 | " Start interactive EasyAlign in visual mode (e.g. vip) 98 | vmap (EasyAlign) 99 | 100 | " Start interactive EasyAlign for a motion/text object (e.g. gaip) 101 | nmap ga (EasyAlign) 102 | < 103 | And with the following lines of text, 104 | > 105 | apple =red 106 | grass+=green 107 | sky-= blue 108 | < 109 | try these commands: 110 | 111 | - vip= 112 | - `v`isual-select `i`nner `p`aragraph 113 | - Start EasyAlign command () 114 | - Align around `=` 115 | - `gaip=` 116 | - Start EasyAlign command (`ga`) for `i`nner `p`aragraph 117 | - Align around `=` 118 | 119 | Notice that the commands are repeatable with `.` key if you have installed 120 | {repeat.vim}{3}. Install {visualrepeat}{4} as well if you want to repeat in 121 | visual mode. 122 | 123 | {3} https://github.com/tpope/vim-repeat 124 | {4} https://github.com/vim-scripts/visualrepeat 125 | 126 | 127 | *easy-align-5* 128 | USAGE *easy-align-usage* 129 | ============================================================================== 130 | 131 | 132 | < Concept of alignment rule >_________________________________________________~ 133 | *easy-align-concept-of-alignment-rule* 134 | *easy-align-5-1* 135 | 136 | Though easy-align can align lines of text around any delimiter, it provides 137 | shortcuts for the most common use cases with the concept of "alignment rule". 138 | 139 | An alignment rule is a predefined set of options for common alignment tasks, 140 | which is identified by a single character, DELIMITER KEY, such as , 141 | `=`, `:`, `.`, `|`, `&`, `#`, and `,`. 142 | 143 | Think of it as a shortcut. Instead of writing regular expression and setting 144 | several options, you can just type in a single character. 145 | 146 | 147 | < Execution models >__________________________________________________________~ 148 | *easy-align-execution-models* 149 | *easy-align-5-2* 150 | 151 | There are two ways to use easy-align. 152 | 153 | 154 | 1. Using mappings~ 155 | *easy-align-1-using-plug-mappings* 156 | *easy-align-5-2-1* 157 | 158 | The recommended method is to use mappings as described earlier. 159 | 160 | *(LiveEasyAlign)* 161 | 162 | ----------------------+--------+----------------------------------------------------- 163 | Mapping | Mode | Description ~ 164 | ----------------------+--------+----------------------------------------------------- 165 | (EasyAlign) | normal | Start interactive mode for a motion/text object 166 | (EasyAlign) | visual | Start interactive mode for the selection 167 | (LiveEasyAlign) | normal | Start live-interactive mode for a motion/text object 168 | (LiveEasyAlign) | visual | Start live-interactive mode for the selection 169 | ----------------------+--------+----------------------------------------------------- 170 | 171 | 172 | 2. Using :EasyAlign command~ 173 | *easy-align-2-using-easyalign-command* 174 | *easy-align-5-2-2* 175 | 176 | *:EasyAlign* 177 | 178 | If you prefer command-line or do not want to start interactive mode, you can 179 | use `:EasyAlign` command instead. 180 | 181 | *:LiveEasyAlign* 182 | 183 | -------------------------------------------+----------------------------------------------- 184 | Mode | Command ~ 185 | -------------------------------------------+----------------------------------------------- 186 | Interactive mode | `:EasyAlign[!] [OPTIONS]` 187 | Live interactive mode | `:LiveEasyAlign[!] [...]` 188 | Non-interactive mode (predefined rules) | `:EasyAlign[!] [N-th] DELIMITER_KEY [OPTIONS]` 189 | Non-interactive mode (regular expressions) | `:EasyAlign[!] [N-th] /REGEXP/ [OPTIONS]` 190 | -------------------------------------------+----------------------------------------------- 191 | 192 | 193 | < Interactive mode >__________________________________________________________~ 194 | *easy-align-interactive-mode* 195 | *easy-align-5-3* 196 | 197 | The following sections will assume that you have (EasyAlign) mappings in 198 | your .vimrc as below: 199 | > 200 | " Start interactive EasyAlign in visual mode (e.g. vip) 201 | vmap (EasyAlign) 202 | 203 | " Start interactive EasyAlign for a motion/text object (e.g. gaip) 204 | nmap ga (EasyAlign) 205 | < 206 | With these mappings, you can align text with only a few keystrokes. 207 | 208 | 1. key in visual mode, or `ga` followed by a motion or a text object to 209 | start interactive mode 210 | 2. Optional: Enter keys to select alignment mode (left, right, or center) 211 | 3. Optional: N-th delimiter (default: 1) 212 | - `1` Around the 1st occurrences of delimiters 213 | - `2` Around the 2nd occurrences of delimiters 214 | - ... 215 | - `*` Around all occurrences of delimiters 216 | - `**` Left-right alternating alignment around all delimiters 217 | - `-` Around the last occurrences of delimiters (`-1`) 218 | - `-2` Around the second to last occurrences of delimiters 219 | - ... 220 | 4. Delimiter key (a single keystroke; , `=`, `:`, `.`, `|`, `&`, `#`, `,`) 221 | 222 | 223 | Predefined alignment rules~ 224 | *easy-align-predefined-alignment-rules* 225 | *easy-align-5-3-1* 226 | 227 | --------------+-------------------------------------------------------------------- 228 | Delimiter key | Description/Use cases ~ 229 | --------------+-------------------------------------------------------------------- 230 | | General alignment around whitespaces 231 | `=` | Operators containing equals sign ( `=` , `==,` `!=` , `+=` , `&&=` , ...) 232 | `:` | Suitable for formatting JSON or YAML 233 | `.` | Multi-line method chaining 234 | `,` | Multi-line method arguments 235 | `&` | LaTeX tables (matches `&` and `\\` ) 236 | `#` | Ruby/Python comments 237 | `"` | Vim comments 238 | | Table markdown 239 | --------------+-------------------------------------------------------------------- 240 | 241 | *g:easy_align_delimiters* 242 | 243 | You can override these default rules or define your own rules with 244 | `g:easy_align_delimiters`, which will be described in {the later section}{5}. 245 | 246 | {5} https://github.com/junegunn/vim-easy-align#extending-alignment-rules 247 | 248 | 249 | Examples~ 250 | *easy-align-examples* 251 | *easy-align-5-3-2* 252 | 253 | ------------------+------------------------------------+-------------------- 254 | With visual map | Description | Equivalent command ~ 255 | ------------------+------------------------------------+-------------------- 256 | | Around 1st whitespaces | :'<,'>EasyAlign\ 257 | 2 | Around 2nd whitespaces | :'<,'>EasyAlign2\ 258 | - | Around the last whitespaces | :'<,'>EasyAlign-\ 259 | -2 | Around the 2nd to last whitespaces | :'<,'>EasyAlign-2\ 260 | : | Around 1st colon ( `key: value` ) | :'<,'>EasyAlign: 261 | : | Around 1st colon ( `key : value` ) | :'<,'>EasyAlign:= | Around 1st operators with = | :'<,'>EasyAlign= 263 | 3= | Around 3rd operators with = | :'<,'>EasyAlign3= 264 | *= | Around all operators with = | :'<,'>EasyAlign*= 265 | **= | Left-right alternating around = | :'<,'>EasyAlign**= 266 | = | Right alignment around 1st = | :'<,'>EasyAlign!= 267 | **= | Right-left alternating around = | :'<,'>EasyAlign!**= 268 | ------------------+------------------------------------+-------------------- 269 | 270 | 271 | Using regular expressions~ 272 | *easy-align-using-regular-expressions* 273 | *easy-align-5-3-3* 274 | 275 | Instead of finishing the command with a predefined delimiter key, you can type 276 | in a regular expression after CTRL-/ or CTRL-X key. For example, if you want 277 | to align text around all occurrences of numbers: 278 | 279 | - 280 | - `*` 281 | - CTRL-X 282 | - `[0-9]\+` 283 | 284 | 285 | Alignment options in interactive mode~ 286 | *easy-align-alignment-options-in-interactive-mode* 287 | *easy-align-5-3-4* 288 | 289 | While in interactive mode, you can set alignment options using special 290 | shortcut keys listed below. The meaning of each option will be described in 291 | {the following sections}{6}. 292 | 293 | --------+--------------------+--------------------------------------------------- 294 | Key | Option | Values ~ 295 | --------+--------------------+--------------------------------------------------- 296 | CTRL-F | `filter` | Input string ( `[gv]/.*/?` ) 297 | CTRL-I | `indentation` | shallow, deep, none, keep 298 | CTRL-L | `left_margin` | Input number or string 299 | CTRL-R | `right_margin` | Input number or string 300 | CTRL-D | `delimiter_align` | left, center, right 301 | CTRL-U | `ignore_unmatched` | 0, 1 302 | CTRL-G | `ignore_groups` | [], ["String'], ["Comment'], ["String', "Comment'] 303 | CTRL-A | `align` | Input string ( `/[lrc]+\*{0,2}/` ) 304 | | `stick_to_left` | `{ 'stick_to_left': 1, 'left_margin': 0 }` 305 | | `stick_to_left` | `{ 'stick_to_left': 0, 'left_margin': 1 }` 306 | | `*_margin` | `{ 'left_margin': 0, 'right_margin': 0 }` 307 | --------+--------------------+--------------------------------------------------- 308 | 309 | {6} https://github.com/junegunn/vim-easy-align#alignment-options 310 | 311 | 312 | < Live interactive mode >_____________________________________________________~ 313 | *easy-align-live-interactive-mode* 314 | *easy-align-5-4* 315 | 316 | If you're performing a complex alignment where multiple options should be 317 | carefully adjusted, try "live interactive mode" where you can preview the 318 | result of the alignment on-the-fly as you type in. 319 | 320 | Live interactive mode can be started with either (LiveEasyAlign) map or 321 | `:LiveEasyAlign` command. Or you can switch to live interactive mode while in 322 | ordinary interactive mode by pressing CTRL-P. (P for Preview) 323 | 324 | In live interactive mode, you have to type in the same delimiter (or CTRL-X on 325 | regular expression) again to finalize the alignment. This allows you to 326 | preview the result of the alignment and freely change the delimiter using 327 | backspace key without leaving the interactive mode. 328 | 329 | 330 | < Non-interactive mode >______________________________________________________~ 331 | *easy-align-non-interactive-mode* 332 | *easy-align-5-5* 333 | 334 | Instead of starting interactive mode, you can use declarative, non-interactive 335 | `:EasyAlign` command. 336 | > 337 | " Using predefined alignment rules 338 | " :EasyAlign[!] [N-th] DELIMITER_KEY [OPTIONS] 339 | :EasyAlign : 340 | :EasyAlign = 341 | :EasyAlign *= 342 | :EasyAlign 3\ 343 | 344 | " Using arbitrary regular expressions 345 | " :EasyAlign[!] [N-th] /REGEXP/ [OPTIONS] 346 | :EasyAlign /[:;]\+/ 347 | :EasyAlign 2/[:;]\+/ 348 | :EasyAlign */[:;]\+/ 349 | :EasyAlign **/[:;]\+/ 350 | < 351 | A command can end with alignment options, {each of which will be discussed in 352 | detail later}{6}, in Vim dictionary format. 353 | 354 | - `:EasyAlign * /[:;]\+/ { 'stick_to_left': 1, 'left_margin': 0 }` 355 | 356 | `stick_to_left` of 1 means that the matched delimiter should be positioned 357 | right next to the preceding token, and `left_margin` of 0 removes the margin 358 | on the left. So we get: 359 | > 360 | apple;: banana:: cake 361 | data;; exchange:; format 362 | < 363 | Option names are fuzzy-matched, so you can write as follows: 364 | 365 | - `:EasyAlign * /[:;]\+/ { 'stl': 1, 'l': 0 }` 366 | 367 | You can even omit spaces between the arguments, so concisely (or cryptically): 368 | 369 | - `:EasyAlign*/[:;]\+/{'s':1,'l':0}` 370 | 371 | Nice. But let's make it even shorter. Option values can be written in 372 | shorthand notation. 373 | 374 | - `:EasyAlign*/[:;]\+/` 385 | `ignore_unmatched` | `iu[01]` 386 | `ignore_groups` | `ig\[.*\]` 387 | `align` | `a[lrc*]*` 388 | `delimiter_align` | `d[lrc]` 389 | `indentation` | `i[ksdn]` 390 | -------------------+----------- 391 | 392 | For your information, the same operation can be done in interactive mode as 393 | follows: 394 | 395 | - 396 | - `*` 397 | - 398 | - CTRL-X 399 | - `[:;]\+` 400 | 401 | {6} https://github.com/junegunn/vim-easy-align#alignment-options 402 | 403 | 404 | < Partial alignment in blockwise-visual mode >________________________________~ 405 | *easy-align-partial-alignment-in-blockwise-visual-mode* 406 | *easy-align-5-6* 407 | 408 | In blockwise-visual mode (CTRL-V), EasyAlign command aligns only the selected 409 | text in the block, instead of the whole lines in the range. 410 | 411 | Consider the following case where you want to align text around `=>` 412 | operators. 413 | > 414 | my_hash = { :a => 1, 415 | :aa => 2, 416 | :aaa => 3 } 417 | < 418 | In non-blockwise visual mode (`v` / `V`), = won't work since the 419 | assignment operator in the first line gets in the way. So we instead enter 420 | blockwise-visual mode (CTRL-V), and select the text around`=>` operators, then 421 | press =. 422 | > 423 | my_hash = { :a => 1, 424 | :aa => 2, 425 | :aaa => 3 } 426 | < 427 | However, in this case, we don't really need blockwise visual mode since the 428 | same can be easily done using the negative N-th parameter: -= 429 | 430 | 431 | *easy-align-6* 432 | ALIGNMENT OPTIONS *easy-align-alignment-options* 433 | ============================================================================== 434 | 435 | 436 | < List of options >___________________________________________________________~ 437 | *easy-align-list-of-options* 438 | *easy-align-6-1* 439 | 440 | -------------------+---------+-----------------------+-------------------------------------------------------- 441 | Option | Type | Default | Description ~ 442 | -------------------+---------+-----------------------+-------------------------------------------------------- 443 | `filter` | string | | Line filtering expression: `g/../` or `v/../` 444 | `left_margin` | number | 1 | Number of spaces to attach before delimiter 445 | `left_margin` | string | `' '` | String to attach before delimiter 446 | `right_margin` | number | 1 | Number of spaces to attach after delimiter 447 | `right_margin` | string | `' '` | String to attach after delimiter 448 | `stick_to_left` | boolean | 0 | Whether to position delimiter on the left-side 449 | `ignore_groups` | list | ["String', "Comment'] | Delimiters in these syntax highlight groups are ignored 450 | `ignore_unmatched` | boolean | 1 | Whether to ignore lines without matching delimiter 451 | `indentation` | string | `k` | Indentation method (keep, deep, shallow, none) 452 | `delimiter_align` | string | `r` | Determines how to align delimiters of different lengths 453 | `align` | string | `l` | Alignment modes for multiple occurrences of delimiters 454 | -------------------+---------+-----------------------+-------------------------------------------------------- 455 | 456 | There are 4 ways to set alignment options (from lowest precedence to highest): 457 | 458 | 1. Some option values can be set with corresponding global variables 459 | 2. Option values can be specified in the definition of each alignment rule 460 | 3. Option values can be given as arguments to `:EasyAlign` command 461 | 4. Option values can be set in interactive mode using special shortcut keys 462 | 463 | *g:easy_align_ignore_groups* *g:easy_align_ignore_unmatched* 464 | *g:easy_align_indentation* *g:easy_align_delimiter_align* 465 | 466 | -------------------+-----------------+-------------+-------------------------------- 467 | Option name | Shortcut key | Abbreviated | Global variable ~ 468 | -------------------+-----------------+-------------+-------------------------------- 469 | `filter` | CTRL-F | `[gv]/.*/` | 470 | `left_margin` | CTRL-L | `l[0-9]+` | 471 | `right_margin` | CTRL-R | `r[0-9]+` | 472 | `stick_to_left` | , | `<` or `>` | 473 | `ignore_groups` | CTRL-G | `ig\[.*\]` | `g:easy_align_ignore_groups` 474 | `ignore_unmatched` | CTRL-U | `iu[01]` | `g:easy_align_ignore_unmatched` 475 | `indentation` | CTRL-I | `i[ksdn]` | `g:easy_align_indentation` 476 | `delimiter_align` | CTRL-D | `d[lrc]` | `g:easy_align_delimiter_align` 477 | `align` | CTRL-A | `a[lrc*]*` | 478 | -------------------+-----------------+-------------+-------------------------------- 479 | 480 | 481 | < Filtering lines >___________________________________________________________~ 482 | *easy-align-filtering-lines* 483 | *easy-align-6-2* 484 | 485 | With `filter` option, you can align lines that only match or do not match a 486 | given pattern. There are several ways to set the pattern. 487 | 488 | 1. Press CTRL-F in interactive mode and type in `g/pat/` or `v/pat/` 489 | 2. In command-line, it can be written in dictionary format: `{'filter': 490 | 'g/pat/'}` 491 | 3. Or in shorthand notation: `g/pat/` or `v/pat/` 492 | 493 | (You don't need to escape "/'s in the regular expression) 494 | 495 | 496 | Examples~ 497 | 498 | *easy-align-6-2-1* 499 | > 500 | " Start interactive mode with filter option set to g/hello/ 501 | EasyAlign g/hello/ 502 | 503 | " Start live interactive mode with filter option set to v/goodbye/ 504 | LiveEasyAlign v/goodbye/ 505 | 506 | " Align the lines with 'hi' around the first colons 507 | EasyAlign:g/hi/ 508 | < 509 | 510 | < Ignoring delimiters in comments or strings >________________________________~ 511 | *easy-align-ignoring-delimiters-in-comments-or-strings* 512 | *easy-align-6-3* 513 | 514 | EasyAlign can be configured to ignore delimiters in certain syntax highlight 515 | groups, such as code comments or strings. By default, delimiters that are 516 | highlighted as code comments or strings are ignored. 517 | > 518 | " Default: 519 | " If a delimiter is in a highlight group whose name matches 520 | " any of the following regular expressions, it will be ignored. 521 | let g:easy_align_ignore_groups = ['Comment', 'String'] 522 | < 523 | For example, the following paragraph 524 | > 525 | { 526 | # Quantity of apples: 1 527 | apple: 1, 528 | # Quantity of bananas: 2 529 | bananas: 2, 530 | # Quantity of grape:fruits: 3 531 | 'grape:fruits': 3 532 | } 533 | < 534 | becomes as follows on : (or `:EasyAlign:`) 535 | > 536 | { 537 | # Quantity of apples: 1 538 | apple: 1, 539 | # Quantity of bananas: 2 540 | bananas: 2, 541 | # Quantity of grape:fruits: 3 542 | 'grape:fruits': 3 543 | } 544 | < 545 | Naturally, this feature only works when syntax highlighting is enabled. 546 | 547 | You can change the default rule by using one of these 4 methods. 548 | 549 | 1. Press CTRL-G in interactive mode to switch groups 550 | 2. Define global `g:easy_align_ignore_groups` list 551 | 3. Define a custom rule in `g:easy_align_delimiters` with `ignore_groups` option 552 | 4. Provide `ignore_groups` option to `:EasyAlign` command. e.g. `:EasyAlign:ig[]` 553 | 554 | For example if you set `ignore_groups` option to be an empty list, you get 555 | > 556 | { 557 | # Quantity of apples: 1 558 | apple: 1, 559 | # Quantity of bananas: 2 560 | bananas: 2, 561 | # Quantity of grape: fruits: 3 562 | 'grape: fruits': 3 563 | } 564 | < 565 | If a pattern in `ignore_groups` is prepended by a `!`, it will have the 566 | opposite meaning. For instance, if `ignore_groups` is given as `['!Comment']`, 567 | delimiters that are not highlighted as Comment will be ignored during the 568 | alignment. 569 | 570 | To make `ignore_groups` work, and to debug the related issues, it is useful to 571 | know which highlight group a certain location in a file belongs to. A special 572 | function exists for this purpose, returning exactly the name of the highlight 573 | group that is used by the easy align plugin. 574 | > 575 | " Highlight group name of the cursor position 576 | echo easy_align#get_highlight_group_name() 577 | 578 | " Highlight group name of the line 10, column 20 579 | echo easy_align#get_highlight_group_name(10, 20) 580 | < 581 | 582 | < Ignoring unmatched lines >__________________________________________________~ 583 | *easy-align-ignoring-unmatched-lines* 584 | *easy-align-6-4* 585 | 586 | `ignore_unmatched` option determines how EasyAlign command processes lines 587 | that do not have N-th delimiter. 588 | 589 | 1. In left-alignment mode, they are ignored 590 | 2. In right or center-alignment mode, they are not ignored, and the last tokens 591 | from those lines are aligned as well as if there is an invisible trailing 592 | delimiter at the end of each line 593 | 3. If `ignore_unmatched` is 1, they are ignored regardless of the alignment mode 594 | 4. If `ignore_unmatched` is 0, they are not ignored regardless of the mode 595 | 596 | Let's take an example. When we align the following code block around the (1st) 597 | colons, 598 | > 599 | { 600 | apple: proc { 601 | this_line_does_not_have_a_colon 602 | }, 603 | bananas: 2, 604 | grapefruits: 3 605 | } 606 | < 607 | this is usually what we want. 608 | > 609 | { 610 | apple: proc { 611 | this_line_does_not_have_a_colon 612 | }, 613 | bananas: 2, 614 | grapefruits: 3 615 | } 616 | < 617 | However, we can override this default behavior by setting `ignore_unmatched` 618 | option to zero using one of the following methods. 619 | 620 | 1. Press CTRL-U in interactive mode to toggle `ignore_unmatched` option 621 | 2. Set the global `g:easy_align_ignore_unmatched` variable to 0 622 | 3. Define a custom alignment rule with `ignore_unmatched` option set to 0 623 | 4. Provide `ignore_unmatched` option to `:EasyAlign` command. e.g. 624 | `:EasyAlign:iu0` 625 | 626 | Then we get, 627 | > 628 | { 629 | apple: proc { 630 | this_line_does_not_have_a_colon 631 | }, 632 | bananas: 2, 633 | grapefruits: 3 634 | } 635 | < 636 | 637 | < Aligning delimiters of different lengths >__________________________________~ 638 | *easy-align-aligning-delimiters-of-different-lengths* 639 | *easy-align-6-5* 640 | 641 | Global `g:easy_align_delimiter_align` option and rule-wise/command-wise 642 | `delimiter_align` option determines how matched delimiters of different 643 | lengths are aligned. 644 | > 645 | apple = 1 646 | banana += apple 647 | cake ||= banana 648 | < 649 | By default, delimiters are right-aligned as follows. 650 | > 651 | apple = 1 652 | banana += apple 653 | cake ||= banana 654 | < 655 | However, with `:EasyAlign=dl`, delimiters are left-aligned. 656 | > 657 | apple = 1 658 | banana += apple 659 | cake ||= banana 660 | < 661 | And on `:EasyAlign=dc`, center-aligned. 662 | > 663 | apple = 1 664 | banana += apple 665 | cake ||= banana 666 | < 667 | In interactive mode, you can change the option value with CTRL-D key. 668 | 669 | 670 | < Adjusting indentation >_____________________________________________________~ 671 | *easy-align-adjusting-indentation* 672 | *easy-align-6-6* 673 | 674 | By default :EasyAlign command keeps the original indentation of the lines. But 675 | then again we have `indentation` option. See the following example. 676 | > 677 | # Lines with different indentation 678 | apple = 1 679 | banana = 2 680 | cake = 3 681 | daisy = 4 682 | eggplant = 5 683 | 684 | # Default: _k_eep the original indentation 685 | # :EasyAlign= 686 | apple = 1 687 | banana = 2 688 | cake = 3 689 | daisy = 4 690 | eggplant = 5 691 | 692 | # Use the _s_hallowest indentation among the lines 693 | # :EasyAlign=is 694 | apple = 1 695 | banana = 2 696 | cake = 3 697 | daisy = 4 698 | eggplant = 5 699 | 700 | # Use the _d_eepest indentation among the lines 701 | # :EasyAlign=id 702 | apple = 1 703 | banana = 2 704 | cake = 3 705 | daisy = 4 706 | eggplant = 5 707 | 708 | # Indentation: _n_one 709 | # :EasyAlign=in 710 | apple = 1 711 | banana = 2 712 | cake = 3 713 | daisy = 4 714 | eggplant = 5 715 | < 716 | In interactive mode, you can change the option value with CTRL-I key. 717 | 718 | 719 | < Alignments over multiple occurrences of delimiters >________________________~ 720 | *easy-align-alignments-over-multiple-occurrences-of-delimiters* 721 | *easy-align-6-7* 722 | 723 | As stated above, "N-th" parameter is used to target specific occurrences of 724 | the delimiter when it appears multiple times in each line. 725 | 726 | To recap: 727 | > 728 | " Left-alignment around the FIRST occurrences of delimiters 729 | :EasyAlign = 730 | 731 | " Left-alignment around the SECOND occurrences of delimiters 732 | :EasyAlign 2= 733 | 734 | " Left-alignment around the LAST occurrences of delimiters 735 | :EasyAlign -= 736 | 737 | " Left-alignment around ALL occurrences of delimiters 738 | :EasyAlign *= 739 | 740 | " Left-right ALTERNATING alignment around all occurrences of delimiters 741 | :EasyAlign **= 742 | 743 | " Right-left ALTERNATING alignment around all occurrences of delimiters 744 | :EasyAlign! **= 745 | < 746 | In addition to these, you can fine-tune alignments over multiple occurrences 747 | of the delimiters with "align' option. (The option can also be set in 748 | interactive mode with the special key CTRL-A) 749 | > 750 | " Left alignment over the first two occurrences of delimiters 751 | :EasyAlign = { 'align': 'll' } 752 | 753 | " Right, left, center alignment over the 1st to 3rd occurrences of delimiters 754 | :EasyAlign = { 'a': 'rlc' } 755 | 756 | " Using shorthand notation 757 | :EasyAlign = arlc 758 | 759 | " Right, left, center alignment over the 2nd to 4th occurrences of delimiters 760 | :EasyAlign 2=arlc 761 | 762 | " (*) Repeating alignments (default: l, r, or c) 763 | " Right, left, center, center, center, center, ... 764 | :EasyAlign *=arlc 765 | 766 | " (**) Alternating alignments (default: lr or rl) 767 | " Right, left, center, right, left, center, ... 768 | :EasyAlign **=arlc 769 | 770 | " Right, left, center, center, center, ... repeating alignment 771 | " over the 3rd to the last occurrences of delimiters 772 | :EasyAlign 3=arlc* 773 | 774 | " Right, left, center, right, left, center, ... alternating alignment 775 | " over the 3rd to the last occurrences of delimiters 776 | :EasyAlign 3=arlc** 777 | < 778 | 779 | < Extending alignment rules >_________________________________________________~ 780 | *easy-align-extending-alignment-rules* 781 | *easy-align-6-8* 782 | 783 | Although the default rules should cover the most of the use cases, you can 784 | extend the rules by setting a dictionary named `g:easy_align_delimiters`. 785 | 786 | You may refer to the definitions of the default alignment rules {here}{7}. 787 | 788 | {7} https://github.com/junegunn/vim-easy-align/blob/2.9.6/autoload/easy_align.vim#L32-L46 789 | 790 | 791 | Examples~ 792 | 793 | *easy-align-6-8-1* 794 | > 795 | let g:easy_align_delimiters = { 796 | \ '>': { 'pattern': '>>\|=>\|>' }, 797 | \ '/': { 798 | \ 'pattern': '//\+\|/\*\|\*/', 799 | \ 'delimiter_align': 'l', 800 | \ 'ignore_groups': ['!Comment'] }, 801 | \ ']': { 802 | \ 'pattern': '[[\]]', 803 | \ 'left_margin': 0, 804 | \ 'right_margin': 0, 805 | \ 'stick_to_left': 0 806 | \ }, 807 | \ ')': { 808 | \ 'pattern': '[()]', 809 | \ 'left_margin': 0, 810 | \ 'right_margin': 0, 811 | \ 'stick_to_left': 0 812 | \ }, 813 | \ 'd': { 814 | \ 'pattern': ' \(\S\+\s*[;=]\)\@=', 815 | \ 'left_margin': 0, 816 | \ 'right_margin': 0 817 | \ } 818 | \ } 819 | < 820 | 821 | *easy-align-7* 822 | OTHER OPTIONS *easy-align-other-options* 823 | ============================================================================== 824 | 825 | 826 | < Disabling &foldmethod during alignment >____________________________________~ 827 | *easy-align-disabling-foldmethod-during-alignment* 828 | *easy-align-7-1* 829 | 830 | *g:easy_align_bypass_fold* 831 | 832 | {It is reported}{8} that 'foldmethod' value of `expr` or `syntax` can 833 | significantly slow down the alignment when editing a large, complex file with 834 | many folds. To alleviate this issue, EasyAlign provides an option to 835 | temporarily set 'foldmethod' to `manual` during the alignment task. In order 836 | to enable this feature, set `g:easy_align_bypass_fold` switch to 1. 837 | > 838 | let g:easy_align_bypass_fold = 1 839 | < 840 | {8} https://github.com/junegunn/vim-easy-align/issues/14 841 | 842 | 843 | < Left/right/center mode switch in interactive mode >_________________________~ 844 | *easy-align-left-right-center-mode-switch-in-interactive-mode* 845 | *easy-align-7-2* 846 | 847 | In interactive mode, you can choose the alignment mode you want by pressing 848 | enter keys. The non-bang command, `:EasyAlign` starts in left-alignment mode 849 | and changes to right and center mode as you press enter keys, while the bang 850 | version first starts in right-alignment mode. 851 | 852 | - `:EasyAlign` 853 | - Left, Right, Center 854 | - `:EasyAlign!` 855 | - Right, Left, Center 856 | 857 | If you do not prefer this default mode transition, you can define your own 858 | settings as follows. 859 | 860 | *g:easy_align_interactive_modes* *g:easy_align_bang_interactive_modes* 861 | > 862 | let g:easy_align_interactive_modes = ['l', 'r'] 863 | let g:easy_align_bang_interactive_modes = ['c', 'r'] 864 | < 865 | 866 | *easy-align-8* 867 | ADVANCED EXAMPLES AND USE CASES *easy-align-advanced-examples-and-use-cases* 868 | ============================================================================== 869 | 870 | See {EXAMPLES.md}{9} for more examples. 871 | 872 | {9} https://github.com/junegunn/vim-easy-align/blob/master/EXAMPLES.md 873 | 874 | 875 | *easy-align-9* 876 | RELATED WORK *easy-align-related-work* 877 | ============================================================================== 878 | 879 | - {DrChip's Alignment Tool for Vim}{10} 880 | - {Tabular}{11} 881 | 882 | {10} http://www.drchip.org/astronaut/vim/align.html 883 | {11} https://github.com/godlygeek/tabular 884 | 885 | 886 | *easy-align-10* 887 | AUTHOR *easy-align-author* 888 | ============================================================================== 889 | 890 | {Junegunn Choi}{12} 891 | 892 | {12} https://github.com/junegunn 893 | 894 | 895 | *easy-align-11* 896 | LICENSE *easy-align-license* 897 | ============================================================================== 898 | 899 | MIT 900 | 901 | ============================================================================== 902 | vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap: 903 | -------------------------------------------------------------------------------- /autoload/easy_align.vim: -------------------------------------------------------------------------------- 1 | " Copyright (c) 2014 Junegunn Choi 2 | " 3 | " MIT License 4 | " 5 | " Permission is hereby granted, free of charge, to any person obtaining 6 | " a copy of this software and associated documentation files (the 7 | " "Software"), to deal in the Software without restriction, including 8 | " without limitation the rights to use, copy, modify, merge, publish, 9 | " distribute, sublicense, and/or sell copies of the Software, and to 10 | " permit persons to whom the Software is furnished to do so, subject to 11 | " the following conditions: 12 | " 13 | " The above copyright notice and this permission notice shall be 14 | " included in all copies or substantial portions of the Software. 15 | " 16 | " THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | " EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | " MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | " NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | " LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | " OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | " WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | if exists("g:loaded_easy_align") 25 | finish 26 | endif 27 | let g:loaded_easy_align = 1 28 | 29 | let s:cpo_save = &cpo 30 | set cpo&vim 31 | 32 | let s:easy_align_delimiters_default = { 33 | \ ' ': { 'pattern': ' ', 'left_margin': 0, 'right_margin': 0, 'stick_to_left': 0 }, 34 | \ '=': { 'pattern': '===\|<=>\|\(&&\|||\|<<\|>>\)=\|=\~[#?]\?\|=>\|[:+/*!%^=><&|.?-]\?=[#?]\?', 35 | \ 'left_margin': 1, 'right_margin': 1, 'stick_to_left': 0 }, 36 | \ ':': { 'pattern': ':', 'left_margin': 0, 'right_margin': 1, 'stick_to_left': 1 }, 37 | \ ',': { 'pattern': ',', 'left_margin': 0, 'right_margin': 1, 'stick_to_left': 1 }, 38 | \ '|': { 'pattern': '|', 'left_margin': 1, 'right_margin': 1, 'stick_to_left': 0 }, 39 | \ '.': { 'pattern': '\.', 'left_margin': 0, 'right_margin': 0, 'stick_to_left': 0 }, 40 | \ '#': { 'pattern': '#\+', 'delimiter_align': 'l', 'ignore_groups': ['!Comment'] }, 41 | \ '"': { 'pattern': '"\+', 'delimiter_align': 'l', 'ignore_groups': ['!Comment'] }, 42 | \ '&': { 'pattern': '\\\@ winlen ? '..' : '' 149 | 150 | echon "\r" 151 | let yet = 0 152 | for [hl, msg] in a:tokens 153 | if empty(msg) | continue | endif 154 | execute "echohl ". hl 155 | let yet += len(msg) 156 | if yet > winlen - len(ellipsis) 157 | echon msg[ 0 : (winlen - len(ellipsis) - yet - 1) ] . ellipsis 158 | break 159 | else 160 | echon msg 161 | endif 162 | endfor 163 | finally 164 | echohl None 165 | let [&ruler, &showcmd] = xy 166 | endtry 167 | endfunction 168 | 169 | function! s:echon(l, n, r, d, o, warn) 170 | let tokens = [ 171 | \ ['Function', s:live ? ':LiveEasyAlign' : ':EasyAlign'], 172 | \ ['ModeMsg', get(s:mode_labels, a:l, a:l)], 173 | \ ['None', ' ']] 174 | 175 | if a:r == -1 | call add(tokens, ['Comment', '(']) | endif 176 | call add(tokens, [a:n =~ '*' ? 'Repeat' : 'Number', a:n]) 177 | call extend(tokens, a:r == 1 ? 178 | \ [['Delimiter', '/'], ['String', a:d], ['Delimiter', '/']] : 179 | \ [['Identifier', a:d == ' ' ? '\ ' : (a:d == '\' ? '\\' : a:d)]]) 180 | if a:r == -1 | call extend(tokens, [['Normal', '_'], ['Comment', ')']]) | endif 181 | call add(tokens, ['Statement', empty(a:o) ? '' : ' '.string(a:o)]) 182 | if !empty(a:warn) 183 | call add(tokens, ['WarningMsg', ' ('.a:warn.')']) 184 | endif 185 | 186 | call s:echon_(tokens) 187 | return join(map(tokens, 'v:val[1]'), '') 188 | endfunction 189 | 190 | function! s:exit(msg) 191 | call s:echon_([['ErrorMsg', a:msg]]) 192 | throw 'exit' 193 | endfunction 194 | 195 | function! s:ltrim(str) 196 | return substitute(a:str, '^\s\+', '', '') 197 | endfunction 198 | 199 | function! s:rtrim(str) 200 | return substitute(a:str, '\s\+$', '', '') 201 | endfunction 202 | 203 | function! s:trim(str) 204 | return substitute(a:str, '^\s*\(.\{-}\)\s*$', '\1', '') 205 | endfunction 206 | 207 | function! s:fuzzy_lu(key) 208 | if has_key(s:known_options, a:key) 209 | return a:key 210 | endif 211 | let key = tolower(a:key) 212 | 213 | " stl -> ^s.*_t.*_l.* 214 | let regexp1 = '^' .key[0]. '.*' .substitute(key[1 : -1], '\(.\)', '_\1.*', 'g') 215 | let matches = filter(keys(s:known_options), 'v:val =~ regexp1') 216 | if len(matches) == 1 217 | return matches[0] 218 | endif 219 | 220 | " stl -> ^s.*t.*l.* 221 | let regexp2 = '^' . substitute(substitute(key, '-', '_', 'g'), '\(.\)', '\1.*', 'g') 222 | let matches = filter(keys(s:known_options), 'v:val =~ regexp2') 223 | 224 | if empty(matches) 225 | call s:exit("Unknown option key: ". a:key) 226 | elseif len(matches) == 1 227 | return matches[0] 228 | else 229 | " Avoid ambiguity introduced by deprecated margin_left and margin_right 230 | if sort(matches) == ['margin_left', 'margin_right', 'mode_sequence'] 231 | return 'mode_sequence' 232 | endif 233 | if sort(matches) == ['ignore_groups', 'ignores'] 234 | return 'ignore_groups' 235 | endif 236 | call s:exit("Ambiguous option key: ". a:key ." (" .join(matches, ', '). ")") 237 | endif 238 | endfunction 239 | 240 | function! s:shift(modes, cycle) 241 | let item = remove(a:modes, 0) 242 | if a:cycle || empty(a:modes) 243 | call add(a:modes, item) 244 | endif 245 | return item 246 | endfunction 247 | 248 | function! s:normalize_options(opts) 249 | let ret = {} 250 | for k in keys(a:opts) 251 | let v = a:opts[k] 252 | let k = s:fuzzy_lu(k) 253 | " Backward-compatibility 254 | if k == 'margin_left' | let k = 'left_margin' | endif 255 | if k == 'margin_right' | let k = 'right_margin' | endif 256 | if k == 'mode_sequence' | let k = 'align' | endif 257 | let ret[k] = v 258 | unlet v 259 | endfor 260 | return s:validate_options(ret) 261 | endfunction 262 | 263 | function! s:compact_options(opts) 264 | let ret = {} 265 | for k in keys(a:opts) 266 | let ret[s:shorthand[k]] = a:opts[k] 267 | endfor 268 | return ret 269 | endfunction 270 | 271 | function! s:validate_options(opts) 272 | for k in keys(a:opts) 273 | let v = a:opts[k] 274 | if index(s:known_options[k], type(v)) == -1 275 | call s:exit("Invalid type for option: ". k) 276 | endif 277 | unlet v 278 | endfor 279 | return a:opts 280 | endfunction 281 | 282 | function! s:split_line(line, nth, modes, cycle, fc, lc, pattern, stick_to_left, ignore_unmatched, ignore_groups) 283 | let mode = '' 284 | 285 | let string = a:lc ? 286 | \ strpart(getline(a:line), a:fc - 1, a:lc - a:fc + 1) : 287 | \ strpart(getline(a:line), a:fc - 1) 288 | let idx = 0 289 | let nomagic = match(a:pattern, '\\v') > match(a:pattern, '\C\\[mMV]') 290 | let pattern = '^.\{-}\s*\zs\('.a:pattern.(nomagic ? ')' : '\)') 291 | let tokens = [] 292 | let delims = [] 293 | 294 | " Phase 1: split 295 | let ignorable = 0 296 | let token = '' 297 | let phantom = 0 298 | while 1 299 | let matchidx = match(string, pattern, idx) 300 | " No match 301 | if matchidx < 0 | break | endif 302 | let matchend = matchend(string, pattern, idx) 303 | let spaces = matchstr(string, '\s'.(a:stick_to_left ? '*' : '\{-}'), matchend + (matchidx == matchend)) 304 | 305 | " Match, but empty 306 | if len(spaces) + matchend - idx == 0 307 | let char = strpart(string, idx, 1) 308 | if empty(char) | break | endif 309 | let [match, part, delim] = [char, char, ''] 310 | " Match 311 | else 312 | let match = strpart(string, idx, matchend - idx + len(spaces)) 313 | let part = strpart(string, idx, matchidx - idx) 314 | let delim = strpart(string, matchidx, matchend - matchidx) 315 | endif 316 | 317 | let ignorable = s:highlighted_as(a:line, idx + len(part) + a:fc, a:ignore_groups) 318 | if ignorable 319 | let token .= match 320 | else 321 | let [pmode, mode] = [mode, s:shift(a:modes, a:cycle)] 322 | call add(tokens, token . match) 323 | call add(delims, delim) 324 | let token = '' 325 | endif 326 | 327 | let idx += len(match) 328 | 329 | " If the string is non-empty and ends with the delimiter, 330 | " append an empty token to the list 331 | if idx == len(string) 332 | let phantom = 1 333 | break 334 | endif 335 | endwhile 336 | 337 | let leftover = token . strpart(string, idx) 338 | if !empty(leftover) 339 | let ignorable = s:highlighted_as(a:line, len(string) + a:fc - 1, a:ignore_groups) 340 | call add(tokens, leftover) 341 | call add(delims, '') 342 | elseif phantom 343 | call add(tokens, '') 344 | call add(delims, '') 345 | endif 346 | let [pmode, mode] = [mode, s:shift(a:modes, a:cycle)] 347 | 348 | " Preserve indentation - merge first two tokens 349 | if len(tokens) > 1 && empty(s:rtrim(tokens[0])) 350 | let tokens[1] = tokens[0] . tokens[1] 351 | call remove(tokens, 0) 352 | call remove(delims, 0) 353 | let mode = pmode 354 | endif 355 | 356 | " Skip comment line 357 | if ignorable && len(tokens) == 1 && a:ignore_unmatched 358 | let tokens = [] 359 | let delims = [] 360 | " Append an empty item to enable right/center alignment of the last token 361 | " - if the last token is not ignorable or ignorable but not the only token 362 | elseif a:ignore_unmatched != 1 && 363 | \ (mode ==? 'r' || mode ==? 'c') && 364 | \ (!ignorable || len(tokens) > 1) && 365 | \ a:nth >= 0 " includes -0 366 | call add(tokens, '') 367 | call add(delims, '') 368 | endif 369 | 370 | return [tokens, delims] 371 | endfunction 372 | 373 | function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, nth, recur, dict) 374 | let mode = a:modes[0] 375 | let lines = {} 376 | let min_indent = -1 377 | let max = { 'pivot_len2': 0, 'token_len': 0, 'just_len': 0, 'delim_len': 0, 378 | \ 'indent': 0, 'tokens': 0, 'strip_len': 0 } 379 | let d = a:dict 380 | let [f, fx] = s:parse_filter(d.filter) 381 | 382 | " Phase 1 383 | for line in range(a:fl, a:ll) 384 | let snip = a:lc > 0 ? getline(line)[a:fc-1 : a:lc-1] : getline(line) 385 | if f == 1 && snip !~ fx 386 | continue 387 | elseif f == -1 && snip =~ fx 388 | continue 389 | endif 390 | 391 | if !has_key(a:all_tokens, line) 392 | " Split line into the tokens by the delimiters 393 | let [tokens, delims] = s:split_line( 394 | \ line, a:nth, copy(a:modes), a:recur == 2, 395 | \ a:fc, a:lc, d.pattern, 396 | \ d.stick_to_left, d.ignore_unmatched, d.ignore_groups) 397 | 398 | " Remember tokens for subsequent recursive calls 399 | let a:all_tokens[line] = tokens 400 | let a:all_delims[line] = delims 401 | else 402 | let tokens = a:all_tokens[line] 403 | let delims = a:all_delims[line] 404 | endif 405 | 406 | " Skip empty lines 407 | if empty(tokens) 408 | continue 409 | endif 410 | 411 | " Calculate the maximum number of tokens for a line within the range 412 | let max.tokens = max([max.tokens, len(tokens)]) 413 | 414 | if a:nth > 0 " Positive N-th 415 | if len(tokens) < a:nth 416 | continue 417 | endif 418 | let nth = a:nth - 1 " make it 0-based 419 | else " -0 or Negative N-th 420 | if a:nth == 0 && mode !=? 'l' 421 | let nth = len(tokens) - 1 422 | else 423 | let nth = len(tokens) + a:nth 424 | endif 425 | if empty(delims[len(delims) - 1]) 426 | let nth -= 1 427 | endif 428 | 429 | if nth < 0 || nth == len(tokens) 430 | continue 431 | endif 432 | endif 433 | 434 | let prefix = nth > 0 ? join(tokens[0 : nth - 1], '') : '' 435 | let delim = delims[nth] 436 | let token = s:rtrim( tokens[nth] ) 437 | let token = s:rtrim( strpart(token, 0, len(token) - len(s:rtrim(delim))) ) 438 | if empty(delim) && !exists('tokens[nth + 1]') && d.ignore_unmatched 439 | continue 440 | endif 441 | 442 | let indent = s:strwidth(matchstr(tokens[0], '^\s*')) 443 | if min_indent < 0 || indent < min_indent 444 | let min_indent = indent 445 | endif 446 | if mode ==? 'c' 447 | let token .= substitute(matchstr(token, '^\s*'), '\t', repeat(' ', &tabstop), 'g') 448 | endif 449 | let [pw, tw] = [s:strwidth(prefix), s:strwidth(token)] 450 | let max.indent = max([max.indent, indent]) 451 | let max.token_len = max([max.token_len, tw]) 452 | let max.just_len = max([max.just_len, pw + tw]) 453 | let max.delim_len = max([max.delim_len, s:strwidth(delim)]) 454 | 455 | if mode ==? 'c' 456 | let pivot_len2 = pw * 2 + tw 457 | if max.pivot_len2 < pivot_len2 458 | let max.pivot_len2 = pivot_len2 459 | endif 460 | let max.strip_len = max([max.strip_len, s:strwidth(s:trim(token))]) 461 | endif 462 | let lines[line] = [nth, prefix, token, delim] 463 | endfor 464 | 465 | " Phase 1-5: indentation handling (only on a:nth == 1) 466 | if a:nth == 1 467 | let idt = d.indentation 468 | if idt ==? 'd' 469 | let indent = max.indent 470 | elseif idt ==? 's' 471 | let indent = min_indent 472 | elseif idt ==? 'n' 473 | let indent = 0 474 | elseif idt !=? 'k' 475 | call s:exit('Invalid indentation: ' . idt) 476 | end 477 | 478 | if idt !=? 'k' 479 | let max.just_len = 0 480 | let max.token_len = 0 481 | let max.pivot_len2 = 0 482 | 483 | for [line, elems] in items(lines) 484 | let [nth, prefix, token, delim] = elems 485 | 486 | let tindent = matchstr(token, '^\s*') 487 | while 1 488 | let len = s:strwidth(tindent) 489 | if len < indent 490 | let tindent .= repeat(' ', indent - len) 491 | break 492 | elseif len > indent 493 | let tindent = tindent[0 : -2] 494 | else 495 | break 496 | endif 497 | endwhile 498 | 499 | let token = tindent . s:ltrim(token) 500 | if mode ==? 'c' 501 | let token = substitute(token, '\s*$', repeat(' ', indent), '') 502 | endif 503 | let [pw, tw] = [s:strwidth(prefix), s:strwidth(token)] 504 | let max.token_len = max([max.token_len, tw]) 505 | let max.just_len = max([max.just_len, pw + tw]) 506 | if mode ==? 'c' 507 | let pivot_len2 = pw * 2 + tw 508 | if max.pivot_len2 < pivot_len2 509 | let max.pivot_len2 = pivot_len2 510 | endif 511 | endif 512 | 513 | let lines[line][2] = token 514 | endfor 515 | endif 516 | endif 517 | 518 | " Phase 2 519 | for [line, elems] in items(lines) 520 | let tokens = a:all_tokens[line] 521 | let delims = a:all_delims[line] 522 | let [nth, prefix, token, delim] = elems 523 | 524 | " Remove the leading whitespaces of the next token 525 | if len(tokens) > nth + 1 526 | let tokens[nth + 1] = s:ltrim(tokens[nth + 1]) 527 | endif 528 | 529 | " Pad the token with spaces 530 | let [pw, tw] = [s:strwidth(prefix), s:strwidth(token)] 531 | let rpad = '' 532 | if mode ==? 'l' 533 | let pad = repeat(' ', max.just_len - pw - tw) 534 | if d.stick_to_left 535 | let rpad = pad 536 | else 537 | let token = token . pad 538 | endif 539 | elseif mode ==? 'r' 540 | let pad = repeat(' ', max.just_len - pw - tw) 541 | let indent = matchstr(token, '^\s*') 542 | let token = indent . pad . s:ltrim(token) 543 | elseif mode ==? 'c' 544 | let p1 = max.pivot_len2 - (pw * 2 + tw) 545 | let p2 = max.token_len - tw 546 | let pf1 = s:floor2(p1) 547 | if pf1 < p1 | let p2 = s:ceil2(p2) 548 | else | let p2 = s:floor2(p2) 549 | endif 550 | let strip = s:ceil2(max.token_len - max.strip_len) / 2 551 | let indent = matchstr(token, '^\s*') 552 | let token = indent. repeat(' ', pf1 / 2) .s:ltrim(token). repeat(' ', p2 / 2) 553 | let token = substitute(token, repeat(' ', strip) . '$', '', '') 554 | 555 | if d.stick_to_left 556 | if empty(s:rtrim(token)) 557 | let center = len(token) / 2 558 | let [token, rpad] = [strpart(token, 0, center), strpart(token, center)] 559 | else 560 | let [token, rpad] = [s:rtrim(token), matchstr(token, '\s*$')] 561 | endif 562 | endif 563 | endif 564 | let tokens[nth] = token 565 | 566 | " Pad the delimiter 567 | let dpadl = max.delim_len - s:strwidth(delim) 568 | let da = d.delimiter_align 569 | if da ==? 'l' 570 | let [dl, dr] = ['', repeat(' ', dpadl)] 571 | elseif da ==? 'c' 572 | let dl = repeat(' ', dpadl / 2) 573 | let dr = repeat(' ', dpadl - dpadl / 2) 574 | elseif da ==? 'r' 575 | let [dl, dr] = [repeat(' ', dpadl), ''] 576 | else 577 | call s:exit('Invalid delimiter_align: ' . da) 578 | endif 579 | 580 | " Before and after the range (for blockwise visual mode) 581 | let cline = getline(line) 582 | let before = strpart(cline, 0, a:fc - 1) 583 | let after = a:lc ? strpart(cline, a:lc) : '' 584 | 585 | " Determine the left and right margin around the delimiter 586 | let rest = join(tokens[nth + 1 : -1], '') 587 | let nomore = empty(rest.after) 588 | let ml = (empty(prefix . token) || empty(delim) && nomore) ? '' : d.ml 589 | let mr = nomore ? '' : d.mr 590 | 591 | " Adjust indentation of the lines starting with a delimiter 592 | let lpad = '' 593 | if nth == 0 594 | let ipad = repeat(' ', min_indent - s:strwidth(token.ml)) 595 | if mode ==? 'l' 596 | let token = ipad . token 597 | else 598 | let lpad = ipad 599 | endif 600 | endif 601 | 602 | " Align the token 603 | let aligned = join([lpad, token, ml, dl, delim, dr, mr, rpad], '') 604 | let tokens[nth] = aligned 605 | 606 | " Update the line 607 | let a:todo[line] = before.join(tokens, '').after 608 | endfor 609 | 610 | if a:nth < max.tokens && (a:recur || len(a:modes) > 1) 611 | call s:shift(a:modes, a:recur == 2) 612 | return [a:todo, a:modes, a:all_tokens, a:all_delims, 613 | \ a:fl, a:ll, a:fc, a:lc, a:nth + 1, a:recur, a:dict] 614 | endif 615 | return [a:todo] 616 | endfunction 617 | 618 | function! s:input(str, default, vis) 619 | if a:vis 620 | normal! gv 621 | redraw 622 | execute "normal! \" 623 | else 624 | " EasyAlign command can be called without visual selection 625 | redraw 626 | endif 627 | let got = input(a:str, a:default) 628 | return got 629 | endfunction 630 | 631 | function! s:atoi(str) 632 | return (a:str =~ '^[0-9]\+$') ? str2nr(a:str) : a:str 633 | endfunction 634 | 635 | function! s:shift_opts(opts, key, vals) 636 | let val = s:shift(a:vals, 1) 637 | if type(val) == 0 && val == -1 638 | call remove(a:opts, a:key) 639 | else 640 | let a:opts[a:key] = val 641 | endif 642 | endfunction 643 | 644 | function! s:interactive(range, modes, n, d, opts, rules, vis, bvis) 645 | let mode = s:shift(a:modes, 1) 646 | let n = a:n 647 | let d = a:d 648 | let ch = '' 649 | let opts = s:compact_options(a:opts) 650 | let vals = deepcopy(s:option_values) 651 | let regx = 0 652 | let warn = '' 653 | let undo = 0 654 | 655 | while 1 656 | " Live preview 657 | let rdrw = 0 658 | if undo 659 | silent! undo 660 | let undo = 0 661 | let rdrw = 1 662 | endif 663 | if s:live && !empty(d) 664 | let output = s:process(a:range, mode, n, d, s:normalize_options(opts), regx, a:rules, a:bvis) 665 | let &undolevels = &undolevels " Break undo block 666 | call s:update_lines(output.todo) 667 | let undo = !empty(output.todo) 668 | let rdrw = 1 669 | endif 670 | if rdrw 671 | if a:vis 672 | normal! gv 673 | endif 674 | redraw 675 | if a:vis | execute "normal! \" | endif 676 | endif 677 | call s:echon(mode, n, -1, regx ? '/'.d.'/' : d, opts, warn) 678 | 679 | let check = 0 680 | let warn = '' 681 | 682 | try 683 | let c = getchar() 684 | catch /^Vim:Interrupt$/ 685 | let c = 27 686 | endtry 687 | let ch = nr2char(c) 688 | if c == 3 || c == 27 " CTRL-C / ESC 689 | if undo 690 | silent! undo 691 | endif 692 | throw 'exit' 693 | elseif c == "\" 694 | if !empty(d) 695 | let d = '' 696 | let regx = 0 697 | elseif len(n) > 0 698 | let n = strpart(n, 0, len(n) - 1) 699 | endif 700 | elseif c == 13 " Enter key 701 | let mode = s:shift(a:modes, 1) 702 | if has_key(opts, 'a') 703 | let opts.a = mode . strpart(opts.a, 1) 704 | endif 705 | elseif ch == '-' 706 | if empty(n) | let n = '-' 707 | elseif n == '-' | let n = '' 708 | else | let check = 1 709 | endif 710 | elseif ch == '*' 711 | if empty(n) | let n = '*' 712 | elseif n == '*' | let n = '**' 713 | elseif n == '**' | let n = '' 714 | else | let check = 1 715 | endif 716 | elseif empty(d) && ((c == 48 && len(n) > 0) || c > 48 && c <= 57) " Numbers 717 | if n[0] == '*' | let check = 1 718 | else | let n = n . ch 719 | end 720 | elseif ch == "\" 721 | call s:shift_opts(opts, 'da', vals['delimiter_align']) 722 | elseif ch == "\" 723 | call s:shift_opts(opts, 'idt', vals['indentation']) 724 | elseif ch == "\" 725 | let lm = s:input("Left margin: ", get(opts, 'lm', ''), a:vis) 726 | if empty(lm) 727 | let warn = 'Set to default. Input 0 to remove it' 728 | silent! call remove(opts, 'lm') 729 | else 730 | let opts['lm'] = s:atoi(lm) 731 | endif 732 | elseif ch == "\" 733 | let rm = s:input("Right margin: ", get(opts, 'rm', ''), a:vis) 734 | if empty(rm) 735 | let warn = 'Set to default. Input 0 to remove it' 736 | silent! call remove(opts, 'rm') 737 | else 738 | let opts['rm'] = s:atoi(rm) 739 | endif 740 | elseif ch == "\" 741 | call s:shift_opts(opts, 'iu', vals['ignore_unmatched']) 742 | elseif ch == "\" 743 | call s:shift_opts(opts, 'ig', vals['ignore_groups']) 744 | elseif ch == "\" 745 | if s:live 746 | if !empty(d) 747 | let ch = d 748 | break 749 | else 750 | let s:live = 0 751 | endif 752 | else 753 | let s:live = 1 754 | endif 755 | elseif c == "\" 756 | let opts['stl'] = 1 757 | let opts['lm'] = 0 758 | elseif c == "\" 759 | let opts['stl'] = 0 760 | let opts['lm'] = 1 761 | elseif c == "\" 762 | let opts['lm'] = 0 763 | let opts['rm'] = 0 764 | elseif c == "\" 765 | silent! call remove(opts, 'stl') 766 | silent! call remove(opts, 'lm') 767 | silent! call remove(opts, 'rm') 768 | elseif ch == "\" || ch == "\" 769 | let modes = tolower(s:input("Alignment ([lrc...][[*]*]): ", get(opts, 'a', mode), a:vis)) 770 | if match(modes, '^[lrc]\+\*\{0,2}$') != -1 771 | let opts['a'] = modes 772 | let mode = modes[0] 773 | while mode != s:shift(a:modes, 1) 774 | endwhile 775 | else 776 | silent! call remove(opts, 'a') 777 | endif 778 | elseif ch == "\" || ch == "\" 779 | if s:live && regx && !empty(d) 780 | break 781 | endif 782 | 783 | let prompt = 'Regular expression: ' 784 | let ch = s:input(prompt, '', a:vis) 785 | if !empty(ch) && s:valid_regexp(ch) 786 | let regx = 1 787 | let d = ch 788 | if !s:live | break | endif 789 | else 790 | let warn = 'Invalid regular expression: '.ch 791 | endif 792 | elseif ch == "\" 793 | let f = s:input("Filter (g/../ or v/../): ", get(opts, 'f', ''), a:vis) 794 | let m = matchlist(f, '^[gv]/\(.\{-}\)/\?$') 795 | if empty(f) 796 | silent! call remove(opts, 'f') 797 | elseif !empty(m) && s:valid_regexp(m[1]) 798 | let opts['f'] = f 799 | else 800 | let warn = 'Invalid filter expression' 801 | endif 802 | elseif ch =~ '[[:print:]]' 803 | let check = 1 804 | else 805 | let warn = 'Invalid character' 806 | endif 807 | 808 | if check 809 | if empty(d) 810 | if has_key(a:rules, ch) 811 | let d = ch 812 | if !s:live 813 | if a:vis 814 | execute "normal! gv\" 815 | endif 816 | break 817 | endif 818 | else 819 | let warn = 'Unknown delimiter key: '.ch 820 | endif 821 | else 822 | if regx 823 | let warn = 'Press to finish' 824 | else 825 | if d == ch 826 | break 827 | else 828 | let warn = 'Press '''.d.''' again to finish' 829 | endif 830 | end 831 | endif 832 | endif 833 | endwhile 834 | if s:live 835 | let copts = call('s:summarize', output.summarize) 836 | let s:live = 0 837 | let g:easy_align_last_command = s:echon('', n, regx, d, copts, '') 838 | let s:live = 1 839 | end 840 | return [mode, n, ch, opts, regx] 841 | endfunction 842 | 843 | function! s:valid_regexp(regexp) 844 | try 845 | call matchlist('', a:regexp) 846 | catch 847 | return 0 848 | endtry 849 | return 1 850 | endfunction 851 | 852 | function! s:test_regexp(regexp) 853 | let regexp = empty(a:regexp) ? @/ : a:regexp 854 | if !s:valid_regexp(regexp) 855 | call s:exit('Invalid regular expression: '. regexp) 856 | endif 857 | return regexp 858 | endfunction 859 | 860 | let s:shorthand_regex = 861 | \ '\s*\%(' 862 | \ .'\(lm\?[0-9]\+\)\|\(rm\?[0-9]\+\)\|\(iu[01]\)\|\(\%(s\%(tl\)\?[01]\)\|[<>]\)\|' 863 | \ .'\(da\?[clr]\)\|\(\%(ms\?\|a\)[lrc*]\+\)\|\(i\%(dt\)\?[kdsn]\)\|\([gv]/.*/\)\|\(ig\[.*\]\)' 864 | \ .'\)\+\s*$' 865 | 866 | function! s:parse_shorthand_opts(expr) 867 | let opts = {} 868 | let expr = substitute(a:expr, '\s', '', 'g') 869 | let regex = '^'. s:shorthand_regex 870 | 871 | if empty(expr) 872 | return opts 873 | elseif expr !~ regex 874 | call s:exit("Invalid expression: ". a:expr) 875 | else 876 | let match = matchlist(expr, regex) 877 | for m in filter(match[ 1 : -1 ], '!empty(v:val)') 878 | for key in ['lm', 'rm', 'l', 'r', 'stl', 's', '<', '>', 'iu', 'da', 'd', 'ms', 'm', 'ig', 'i', 'g', 'v', 'a'] 879 | if stridx(tolower(m), key) == 0 880 | let rest = strpart(m, len(key)) 881 | if key == 'i' | let key = 'idt' | endif 882 | if key == 'g' || key == 'v' 883 | let rest = key.rest 884 | let key = 'f' 885 | endif 886 | 887 | if key == 'idt' || index(['d', 'f', 'm', 'a'], key[0]) >= 0 888 | let opts[key] = rest 889 | elseif key == 'ig' 890 | try 891 | let arr = eval(rest) 892 | if type(arr) == 3 893 | let opts[key] = arr 894 | else 895 | throw 'Not an array' 896 | endif 897 | catch 898 | call s:exit("Invalid ignore_groups: ". a:expr) 899 | endtry 900 | elseif key =~ '[<>]' 901 | let opts['stl'] = key == '<' 902 | else 903 | let opts[key] = str2nr(rest) 904 | endif 905 | break 906 | endif 907 | endfor 908 | endfor 909 | endif 910 | return s:normalize_options(opts) 911 | endfunction 912 | 913 | function! s:parse_args(args) 914 | if empty(a:args) 915 | return ['', '', {}, 0] 916 | endif 917 | let n = '' 918 | let ch = '' 919 | let args = a:args 920 | let cand = '' 921 | let opts = {} 922 | 923 | " Poor man's option parser 924 | let idx = 0 925 | while 1 926 | let midx = match(args, '\s*{.*}\s*$', idx) 927 | if midx == -1 | break | endif 928 | 929 | let cand = strpart(args, midx) 930 | try 931 | let [l, r, c, k, s, d, n] = ['l', 'r', 'c', 'k', 's', 'd', 'n'] 932 | let [L, R, C, K, S, D, N] = ['l', 'r', 'c', 'k', 's', 'd', 'n'] 933 | let o = eval(cand) 934 | if type(o) == 4 935 | let opts = o 936 | if args[midx - 1 : midx] == '\ ' 937 | let midx += 1 938 | endif 939 | let args = strpart(args, 0, midx) 940 | break 941 | endif 942 | catch 943 | " Ignore 944 | endtry 945 | let idx = midx + 1 946 | endwhile 947 | 948 | " Invalid option dictionary 949 | if len(substitute(cand, '\s', '', 'g')) > 2 && empty(opts) 950 | call s:exit("Invalid option: ". cand) 951 | else 952 | let opts = s:normalize_options(opts) 953 | endif 954 | 955 | " Shorthand option notation 956 | let sopts = matchstr(args, s:shorthand_regex) 957 | if !empty(sopts) 958 | let args = strpart(args, 0, len(args) - len(sopts)) 959 | let opts = extend(s:parse_shorthand_opts(sopts), opts) 960 | endif 961 | 962 | " Has /Regexp/? 963 | let matches = matchlist(args, '^\(.\{-}\)\s*/\(.*\)/\s*$') 964 | 965 | " Found regexp 966 | if !empty(matches) 967 | return [matches[1], s:test_regexp(matches[2]), opts, 1] 968 | else 969 | let tokens = matchlist(args, '^\([1-9][0-9]*\|-[0-9]*\|\*\*\?\)\?\s*\(.\{-}\)\?$') 970 | " Try swapping n and ch 971 | let [n, ch] = empty(tokens[2]) ? reverse(tokens[1:2]) : tokens[1:2] 972 | 973 | " Resolving command-line ambiguity 974 | " '\ ' => ' ' 975 | " '\' => ' ' 976 | if ch =~ '^\\\s*$' 977 | let ch = ' ' 978 | " '\\' => '\' 979 | elseif ch =~ '^\\\\\s*$' 980 | let ch = '\' 981 | endif 982 | 983 | return [n, ch, opts, 0] 984 | endif 985 | endfunction 986 | 987 | function! s:parse_filter(f) 988 | let m = matchlist(a:f, '^\([gv]\)/\(.\{-}\)/\?$') 989 | if empty(m) 990 | return [0, ''] 991 | else 992 | return [m[1] == 'g' ? 1 : -1, m[2]] 993 | endif 994 | endfunction 995 | 996 | function! s:interactive_modes(bang) 997 | return get(g:, 998 | \ (a:bang ? 'easy_align_bang_interactive_modes' : 'easy_align_interactive_modes'), 999 | \ (a:bang ? ['r', 'l', 'c'] : ['l', 'r', 'c'])) 1000 | endfunction 1001 | 1002 | function! s:alternating_modes(mode) 1003 | return a:mode ==? 'r' ? 'rl' : 'lr' 1004 | endfunction 1005 | 1006 | function! s:update_lines(todo) 1007 | for [line, content] in items(a:todo) 1008 | call setline(line, s:rtrim(content)) 1009 | endfor 1010 | endfunction 1011 | 1012 | function! s:parse_nth(n) 1013 | let n = a:n 1014 | let recur = 0 1015 | if n == '*' | let [nth, recur] = [1, 1] 1016 | elseif n == '**' | let [nth, recur] = [1, 2] 1017 | elseif n == '-' | let nth = -1 1018 | elseif empty(n) | let nth = 1 1019 | elseif n == '0' || ( n != '-0' && n != string(str2nr(n)) ) 1020 | call s:exit('Invalid N-th parameter: '. n) 1021 | else 1022 | let nth = n 1023 | endif 1024 | return [nth, recur] 1025 | endfunction 1026 | 1027 | function! s:build_dict(delimiters, ch, regexp, opts) 1028 | if a:regexp 1029 | let dict = { 'pattern': a:ch } 1030 | else 1031 | if !has_key(a:delimiters, a:ch) 1032 | call s:exit('Unknown delimiter key: '. a:ch) 1033 | endif 1034 | let dict = copy(a:delimiters[a:ch]) 1035 | endif 1036 | call extend(dict, a:opts) 1037 | 1038 | let ml = get(dict, 'left_margin', ' ') 1039 | let mr = get(dict, 'right_margin', ' ') 1040 | if type(ml) == 0 | let ml = repeat(' ', ml) | endif 1041 | if type(mr) == 0 | let mr = repeat(' ', mr) | endif 1042 | call extend(dict, { 'ml': ml, 'mr': mr }) 1043 | 1044 | let dict.pattern = get(dict, 'pattern', a:ch) 1045 | let dict.delimiter_align = 1046 | \ get(dict, 'delimiter_align', get(g:, 'easy_align_delimiter_align', 'r'))[0] 1047 | let dict.indentation = 1048 | \ get(dict, 'indentation', get(g:, 'easy_align_indentation', 'k'))[0] 1049 | let dict.stick_to_left = 1050 | \ get(dict, 'stick_to_left', 0) 1051 | let dict.ignore_unmatched = 1052 | \ get(dict, 'ignore_unmatched', get(g:, 'easy_align_ignore_unmatched', 2)) 1053 | let dict.ignore_groups = 1054 | \ get(dict, 'ignore_groups', get(dict, 'ignores', s:ignored_syntax())) 1055 | let dict.filter = 1056 | \ get(dict, 'filter', '') 1057 | return dict 1058 | endfunction 1059 | 1060 | function! s:build_mode_sequence(expr, recur) 1061 | let [expr, recur] = [a:expr, a:recur] 1062 | let suffix = matchstr(a:expr, '\*\+$') 1063 | if suffix == '*' 1064 | let expr = expr[0 : -2] 1065 | let recur = 1 1066 | elseif suffix == '**' 1067 | let expr = expr[0 : -3] 1068 | let recur = 2 1069 | endif 1070 | return [tolower(expr), recur] 1071 | endfunction 1072 | 1073 | function! s:process(range, mode, n, ch, opts, regexp, rules, bvis) 1074 | let [nth, recur] = s:parse_nth((empty(a:n) && exists('g:easy_align_nth')) ? g:easy_align_nth : a:n) 1075 | let dict = s:build_dict(a:rules, a:ch, a:regexp, a:opts) 1076 | let [mode_sequence, recur] = s:build_mode_sequence( 1077 | \ get(dict, 'align', recur == 2 ? s:alternating_modes(a:mode) : a:mode), 1078 | \ recur) 1079 | 1080 | let ve = &virtualedit 1081 | set ve=all 1082 | let args = [ 1083 | \ {}, split(mode_sequence, '\zs'), 1084 | \ {}, {}, a:range[0], a:range[1], 1085 | \ a:bvis ? min([virtcol("'<"), virtcol("'>")]) : 1, 1086 | \ (!recur && a:bvis) ? max([virtcol("'<"), virtcol("'>")]) : 0, 1087 | \ nth, recur, dict ] 1088 | let &ve = ve 1089 | while len(args) > 1 1090 | let args = call('s:do_align', args) 1091 | endwhile 1092 | 1093 | " todo: lines to update 1094 | " summarize: arguments to s:summarize 1095 | return { 'todo': args[0], 'summarize': [ a:opts, recur, mode_sequence ] } 1096 | endfunction 1097 | 1098 | function s:summarize(opts, recur, mode_sequence) 1099 | let copts = s:compact_options(a:opts) 1100 | let nbmode = s:interactive_modes(0)[0] 1101 | if !has_key(copts, 'a') && ( 1102 | \ (a:recur == 2 && s:alternating_modes(nbmode) != a:mode_sequence) || 1103 | \ (a:recur != 2 && (a:mode_sequence[0] != nbmode || len(a:mode_sequence) > 1)) 1104 | \ ) 1105 | call extend(copts, { 'a': a:mode_sequence }) 1106 | endif 1107 | return copts 1108 | endfunction 1109 | 1110 | function! s:align(bang, live, visualmode, first_line, last_line, expr) 1111 | " Heuristically determine if the user was in visual mode 1112 | if a:visualmode == 'command' 1113 | let vis = a:first_line == line("'<") && a:last_line == line("'>") 1114 | let bvis = vis && visualmode() == "\" 1115 | elseif empty(a:visualmode) 1116 | let vis = 0 1117 | let bvis = 0 1118 | else 1119 | let vis = 1 1120 | let bvis = a:visualmode == "\" 1121 | end 1122 | let range = [a:first_line, a:last_line] 1123 | let modes = s:interactive_modes(a:bang) 1124 | let mode = modes[0] 1125 | let s:live = a:live 1126 | 1127 | let rules = s:easy_align_delimiters_default 1128 | if exists('g:easy_align_delimiters') 1129 | let rules = extend(copy(rules), g:easy_align_delimiters) 1130 | endif 1131 | 1132 | let [n, ch, opts, regexp] = s:parse_args(a:expr) 1133 | 1134 | let bypass_fold = get(g:, 'easy_align_bypass_fold', 0) 1135 | let ofm = &l:foldmethod 1136 | try 1137 | if bypass_fold | let &l:foldmethod = 'manual' | endif 1138 | 1139 | if empty(n) && empty(ch) || s:live 1140 | let [mode, n, ch, opts, regexp] = s:interactive(range, copy(modes), n, ch, opts, rules, vis, bvis) 1141 | endif 1142 | 1143 | if !s:live 1144 | let output = s:process(range, mode, n, ch, s:normalize_options(opts), regexp, rules, bvis) 1145 | call s:update_lines(output.todo) 1146 | let copts = call('s:summarize', output.summarize) 1147 | let g:easy_align_last_command = s:echon('', n, regexp, ch, copts, '') 1148 | endif 1149 | finally 1150 | if bypass_fold | let &l:foldmethod = ofm | endif 1151 | endtry 1152 | endfunction 1153 | 1154 | function! easy_align#align(bang, live, visualmode, expr) range 1155 | try 1156 | call s:align(a:bang, a:live, a:visualmode, a:firstline, a:lastline, a:expr) 1157 | catch /^\%(Vim:Interrupt\|exit\)$/ 1158 | if empty(a:visualmode) 1159 | echon "\r" 1160 | echon "\r" 1161 | else 1162 | normal! gv 1163 | endif 1164 | endtry 1165 | endfunction 1166 | 1167 | let &cpo = s:cpo_save 1168 | unlet s:cpo_save 1169 | 1170 | " vim: set et sw=2 : 1171 | --------------------------------------------------------------------------------