easy_align.vim 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170
  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. if exists("g:loaded_easy_align")
  24. finish
  25. endif
  26. let g:loaded_easy_align = 1
  27. let s:cpo_save = &cpo
  28. set cpo&vim
  29. let s:easy_align_delimiters_default = {
  30. \ ' ': { 'pattern': ' ', 'left_margin': 0, 'right_margin': 0, 'stick_to_left': 0 },
  31. \ '=': { 'pattern': '===\|<=>\|\(&&\|||\|<<\|>>\)=\|=\~[#?]\?\|=>\|[:+/*!%^=><&|.?-]\?=[#?]\?',
  32. \ 'left_margin': 1, 'right_margin': 1, 'stick_to_left': 0 },
  33. \ ':': { 'pattern': ':', 'left_margin': 0, 'right_margin': 1, 'stick_to_left': 1 },
  34. \ ',': { 'pattern': ',', 'left_margin': 0, 'right_margin': 1, 'stick_to_left': 1 },
  35. \ '|': { 'pattern': '|', 'left_margin': 1, 'right_margin': 1, 'stick_to_left': 0 },
  36. \ '.': { 'pattern': '\.', 'left_margin': 0, 'right_margin': 0, 'stick_to_left': 0 },
  37. \ '#': { 'pattern': '#\+', 'delimiter_align': 'l', 'ignore_groups': ['!Comment'] },
  38. \ '"': { 'pattern': '"\+', 'delimiter_align': 'l', 'ignore_groups': ['!Comment'] },
  39. \ '&': { 'pattern': '\\\@<!&\|\\\\',
  40. \ 'left_margin': 1, 'right_margin': 1, 'stick_to_left': 0 },
  41. \ '{': { 'pattern': '(\@<!{',
  42. \ 'left_margin': 1, 'right_margin': 1, 'stick_to_left': 0 },
  43. \ '}': { 'pattern': '}', 'left_margin': 1, 'right_margin': 0, 'stick_to_left': 0 }
  44. \ }
  45. let s:mode_labels = { 'l': '', 'r': '[R]', 'c': '[C]' }
  46. let s:known_options = {
  47. \ 'margin_left': [0, 1], 'margin_right': [0, 1], 'stick_to_left': [0],
  48. \ 'left_margin': [0, 1], 'right_margin': [0, 1], 'indentation': [1],
  49. \ 'ignore_groups': [3 ], 'ignore_unmatched': [0 ], 'delimiter_align': [1],
  50. \ 'mode_sequence': [1 ], 'ignores': [3], 'filter': [1],
  51. \ 'align': [1 ]
  52. \ }
  53. let s:option_values = {
  54. \ 'indentation': ['shallow', 'deep', 'none', 'keep', -1],
  55. \ 'delimiter_align': ['left', 'center', 'right', -1],
  56. \ 'ignore_unmatched': [0, 1, -1],
  57. \ 'ignore_groups': [[], ['String'], ['Comment'], ['String', 'Comment'], -1]
  58. \ }
  59. let s:shorthand = {
  60. \ 'margin_left': 'lm', 'margin_right': 'rm', 'stick_to_left': 'stl',
  61. \ 'left_margin': 'lm', 'right_margin': 'rm', 'indentation': 'idt',
  62. \ 'ignore_groups': 'ig', 'ignore_unmatched': 'iu', 'delimiter_align': 'da',
  63. \ 'mode_sequence': 'a', 'ignores': 'ig', 'filter': 'f',
  64. \ 'align': 'a'
  65. \ }
  66. if exists("*strdisplaywidth")
  67. function! s:strwidth(str)
  68. return strdisplaywidth(a:str)
  69. endfunction
  70. else
  71. function! s:strwidth(str)
  72. return len(split(a:str, '\zs')) + len(matchstr(a:str, '^\t*')) * (&tabstop - 1)
  73. endfunction
  74. endif
  75. function! s:ceil2(v)
  76. return a:v % 2 == 0 ? a:v : a:v + 1
  77. endfunction
  78. function! s:floor2(v)
  79. return a:v % 2 == 0 ? a:v : a:v - 1
  80. endfunction
  81. function! s:get_highlight_group_name(line, col)
  82. let hl = synIDattr(synID(a:line, a:col, 0), 'name')
  83. if hl == '' && has('nvim-0.9.0')
  84. let insp = luaeval('vim.inspect_pos and vim.inspect_pos( nil, ' .. (a:line-1) .. ', ' .. (a:col-1) .. ' ) or { treesitter = {} }')
  85. if !empty(insp.treesitter)
  86. let hl = insp.treesitter[0].hl_group_link
  87. endif
  88. endif
  89. " and, finally
  90. return hl
  91. endfunction
  92. function! easy_align#get_highlight_group_name(...)
  93. let l = get(a:, 1, line('.'))
  94. let c = get(a:, 2, col('.'))
  95. let hl = s:get_highlight_group_name(l, c)
  96. return { 'line': l, 'column': c, 'group': hl }
  97. endfunction
  98. function! s:highlighted_as(line, col, groups)
  99. if empty(a:groups) | return 0 | endif
  100. let hl = s:get_highlight_group_name(a:line, a:col)
  101. for grp in a:groups
  102. if grp[0] == '!'
  103. if hl !~# grp[1:-1]
  104. return 1
  105. endif
  106. elseif hl =~# grp
  107. return 1
  108. endif
  109. endfor
  110. return 0
  111. endfunction
  112. function! s:ignored_syntax()
  113. if has('syntax') && exists('g:syntax_on')
  114. " Backward-compatibility
  115. return get(g:, 'easy_align_ignore_groups',
  116. \ get(g:, 'easy_align_ignores',
  117. \ (get(g:, 'easy_align_ignore_comment', 1) == 0) ?
  118. \ ['String'] : ['String', 'Comment']))
  119. else
  120. return []
  121. endif
  122. endfunction
  123. function! s:echon_(tokens)
  124. " http://vim.wikia.com/wiki/How_to_print_full_screen_width_messages
  125. let xy = [&ruler, &showcmd]
  126. try
  127. set noruler noshowcmd
  128. let winlen = winwidth(winnr()) - 2
  129. let len = len(join(map(copy(a:tokens), 'v:val[1]'), ''))
  130. let ellipsis = len > winlen ? '..' : ''
  131. echon "\r"
  132. let yet = 0
  133. for [hl, msg] in a:tokens
  134. if empty(msg) | continue | endif
  135. execute "echohl ". hl
  136. let yet += len(msg)
  137. if yet > winlen - len(ellipsis)
  138. echon msg[ 0 : (winlen - len(ellipsis) - yet - 1) ] . ellipsis
  139. break
  140. else
  141. echon msg
  142. endif
  143. endfor
  144. finally
  145. echohl None
  146. let [&ruler, &showcmd] = xy
  147. endtry
  148. endfunction
  149. function! s:echon(l, n, r, d, o, warn)
  150. let tokens = [
  151. \ ['Function', s:live ? ':LiveEasyAlign' : ':EasyAlign'],
  152. \ ['ModeMsg', get(s:mode_labels, a:l, a:l)],
  153. \ ['None', ' ']]
  154. if a:r == -1 | call add(tokens, ['Comment', '(']) | endif
  155. call add(tokens, [a:n =~ '*' ? 'Repeat' : 'Number', a:n])
  156. call extend(tokens, a:r == 1 ?
  157. \ [['Delimiter', '/'], ['String', a:d], ['Delimiter', '/']] :
  158. \ [['Identifier', a:d == ' ' ? '\ ' : (a:d == '\' ? '\\' : a:d)]])
  159. if a:r == -1 | call extend(tokens, [['Normal', '_'], ['Comment', ')']]) | endif
  160. call add(tokens, ['Statement', empty(a:o) ? '' : ' '.string(a:o)])
  161. if !empty(a:warn)
  162. call add(tokens, ['WarningMsg', ' ('.a:warn.')'])
  163. endif
  164. call s:echon_(tokens)
  165. return join(map(tokens, 'v:val[1]'), '')
  166. endfunction
  167. function! s:exit(msg)
  168. call s:echon_([['ErrorMsg', a:msg]])
  169. throw 'exit'
  170. endfunction
  171. function! s:ltrim(str)
  172. return substitute(a:str, '^\s\+', '', '')
  173. endfunction
  174. function! s:rtrim(str)
  175. return substitute(a:str, '\s\+$', '', '')
  176. endfunction
  177. function! s:trim(str)
  178. return substitute(a:str, '^\s*\(.\{-}\)\s*$', '\1', '')
  179. endfunction
  180. function! s:fuzzy_lu(key)
  181. if has_key(s:known_options, a:key)
  182. return a:key
  183. endif
  184. let key = tolower(a:key)
  185. " stl -> ^s.*_t.*_l.*
  186. let regexp1 = '^' .key[0]. '.*' .substitute(key[1 : -1], '\(.\)', '_\1.*', 'g')
  187. let matches = filter(keys(s:known_options), 'v:val =~ regexp1')
  188. if len(matches) == 1
  189. return matches[0]
  190. endif
  191. " stl -> ^s.*t.*l.*
  192. let regexp2 = '^' . substitute(substitute(key, '-', '_', 'g'), '\(.\)', '\1.*', 'g')
  193. let matches = filter(keys(s:known_options), 'v:val =~ regexp2')
  194. if empty(matches)
  195. call s:exit("Unknown option key: ". a:key)
  196. elseif len(matches) == 1
  197. return matches[0]
  198. else
  199. " Avoid ambiguity introduced by deprecated margin_left and margin_right
  200. if sort(matches) == ['margin_left', 'margin_right', 'mode_sequence']
  201. return 'mode_sequence'
  202. endif
  203. if sort(matches) == ['ignore_groups', 'ignores']
  204. return 'ignore_groups'
  205. endif
  206. call s:exit("Ambiguous option key: ". a:key ." (" .join(matches, ', '). ")")
  207. endif
  208. endfunction
  209. function! s:shift(modes, cycle)
  210. let item = remove(a:modes, 0)
  211. if a:cycle || empty(a:modes)
  212. call add(a:modes, item)
  213. endif
  214. return item
  215. endfunction
  216. function! s:normalize_options(opts)
  217. let ret = {}
  218. for k in keys(a:opts)
  219. let v = a:opts[k]
  220. let k = s:fuzzy_lu(k)
  221. " Backward-compatibility
  222. if k == 'margin_left' | let k = 'left_margin' | endif
  223. if k == 'margin_right' | let k = 'right_margin' | endif
  224. if k == 'mode_sequence' | let k = 'align' | endif
  225. let ret[k] = v
  226. unlet v
  227. endfor
  228. return s:validate_options(ret)
  229. endfunction
  230. function! s:compact_options(opts)
  231. let ret = {}
  232. for k in keys(a:opts)
  233. let ret[s:shorthand[k]] = a:opts[k]
  234. endfor
  235. return ret
  236. endfunction
  237. function! s:validate_options(opts)
  238. for k in keys(a:opts)
  239. let v = a:opts[k]
  240. if index(s:known_options[k], type(v)) == -1
  241. call s:exit("Invalid type for option: ". k)
  242. endif
  243. unlet v
  244. endfor
  245. return a:opts
  246. endfunction
  247. function! s:split_line(line, nth, modes, cycle, fc, lc, pattern, stick_to_left, ignore_unmatched, ignore_groups)
  248. let mode = ''
  249. let string = a:lc ?
  250. \ strpart(getline(a:line), a:fc - 1, a:lc - a:fc + 1) :
  251. \ strpart(getline(a:line), a:fc - 1)
  252. let idx = 0
  253. let nomagic = match(a:pattern, '\\v') > match(a:pattern, '\C\\[mMV]')
  254. let pattern = '^.\{-}\s*\zs\('.a:pattern.(nomagic ? ')' : '\)')
  255. let tokens = []
  256. let delims = []
  257. " Phase 1: split
  258. let ignorable = 0
  259. let token = ''
  260. let phantom = 0
  261. while 1
  262. let matchidx = match(string, pattern, idx)
  263. " No match
  264. if matchidx < 0 | break | endif
  265. let matchend = matchend(string, pattern, idx)
  266. let spaces = matchstr(string, '\s'.(a:stick_to_left ? '*' : '\{-}'), matchend + (matchidx == matchend))
  267. " Match, but empty
  268. if len(spaces) + matchend - idx == 0
  269. let char = strpart(string, idx, 1)
  270. if empty(char) | break | endif
  271. let [match, part, delim] = [char, char, '']
  272. " Match
  273. else
  274. let match = strpart(string, idx, matchend - idx + len(spaces))
  275. let part = strpart(string, idx, matchidx - idx)
  276. let delim = strpart(string, matchidx, matchend - matchidx)
  277. endif
  278. let ignorable = s:highlighted_as(a:line, idx + len(part) + a:fc, a:ignore_groups)
  279. if ignorable
  280. let token .= match
  281. else
  282. let [pmode, mode] = [mode, s:shift(a:modes, a:cycle)]
  283. call add(tokens, token . match)
  284. call add(delims, delim)
  285. let token = ''
  286. endif
  287. let idx += len(match)
  288. " If the string is non-empty and ends with the delimiter,
  289. " append an empty token to the list
  290. if idx == len(string)
  291. let phantom = 1
  292. break
  293. endif
  294. endwhile
  295. let leftover = token . strpart(string, idx)
  296. if !empty(leftover)
  297. let ignorable = s:highlighted_as(a:line, len(string) + a:fc - 1, a:ignore_groups)
  298. call add(tokens, leftover)
  299. call add(delims, '')
  300. elseif phantom
  301. call add(tokens, '')
  302. call add(delims, '')
  303. endif
  304. let [pmode, mode] = [mode, s:shift(a:modes, a:cycle)]
  305. " Preserve indentation - merge first two tokens
  306. if len(tokens) > 1 && empty(s:rtrim(tokens[0]))
  307. let tokens[1] = tokens[0] . tokens[1]
  308. call remove(tokens, 0)
  309. call remove(delims, 0)
  310. let mode = pmode
  311. endif
  312. " Skip comment line
  313. if ignorable && len(tokens) == 1 && a:ignore_unmatched
  314. let tokens = []
  315. let delims = []
  316. " Append an empty item to enable right/center alignment of the last token
  317. " - if the last token is not ignorable or ignorable but not the only token
  318. elseif a:ignore_unmatched != 1 &&
  319. \ (mode ==? 'r' || mode ==? 'c') &&
  320. \ (!ignorable || len(tokens) > 1) &&
  321. \ a:nth >= 0 " includes -0
  322. call add(tokens, '')
  323. call add(delims, '')
  324. endif
  325. return [tokens, delims]
  326. endfunction
  327. function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, nth, recur, dict)
  328. let mode = a:modes[0]
  329. let lines = {}
  330. let min_indent = -1
  331. let max = { 'pivot_len2': 0, 'token_len': 0, 'just_len': 0, 'delim_len': 0,
  332. \ 'indent': 0, 'tokens': 0, 'strip_len': 0 }
  333. let d = a:dict
  334. let [f, fx] = s:parse_filter(d.filter)
  335. " Phase 1
  336. for line in range(a:fl, a:ll)
  337. let snip = a:lc > 0 ? getline(line)[a:fc-1 : a:lc-1] : getline(line)
  338. if f == 1 && snip !~ fx
  339. continue
  340. elseif f == -1 && snip =~ fx
  341. continue
  342. endif
  343. if !has_key(a:all_tokens, line)
  344. " Split line into the tokens by the delimiters
  345. let [tokens, delims] = s:split_line(
  346. \ line, a:nth, copy(a:modes), a:recur == 2,
  347. \ a:fc, a:lc, d.pattern,
  348. \ d.stick_to_left, d.ignore_unmatched, d.ignore_groups)
  349. " Remember tokens for subsequent recursive calls
  350. let a:all_tokens[line] = tokens
  351. let a:all_delims[line] = delims
  352. else
  353. let tokens = a:all_tokens[line]
  354. let delims = a:all_delims[line]
  355. endif
  356. " Skip empty lines
  357. if empty(tokens)
  358. continue
  359. endif
  360. " Calculate the maximum number of tokens for a line within the range
  361. let max.tokens = max([max.tokens, len(tokens)])
  362. if a:nth > 0 " Positive N-th
  363. if len(tokens) < a:nth
  364. continue
  365. endif
  366. let nth = a:nth - 1 " make it 0-based
  367. else " -0 or Negative N-th
  368. if a:nth == 0 && mode !=? 'l'
  369. let nth = len(tokens) - 1
  370. else
  371. let nth = len(tokens) + a:nth
  372. endif
  373. if empty(delims[len(delims) - 1])
  374. let nth -= 1
  375. endif
  376. if nth < 0 || nth == len(tokens)
  377. continue
  378. endif
  379. endif
  380. let prefix = nth > 0 ? join(tokens[0 : nth - 1], '') : ''
  381. let delim = delims[nth]
  382. let token = s:rtrim( tokens[nth] )
  383. let token = s:rtrim( strpart(token, 0, len(token) - len(s:rtrim(delim))) )
  384. if empty(delim) && !exists('tokens[nth + 1]') && d.ignore_unmatched
  385. continue
  386. endif
  387. let indent = s:strwidth(matchstr(tokens[0], '^\s*'))
  388. if min_indent < 0 || indent < min_indent
  389. let min_indent = indent
  390. endif
  391. if mode ==? 'c'
  392. let token .= substitute(matchstr(token, '^\s*'), '\t', repeat(' ', &tabstop), 'g')
  393. endif
  394. let [pw, tw] = [s:strwidth(prefix), s:strwidth(token)]
  395. let max.indent = max([max.indent, indent])
  396. let max.token_len = max([max.token_len, tw])
  397. let max.just_len = max([max.just_len, pw + tw])
  398. let max.delim_len = max([max.delim_len, s:strwidth(delim)])
  399. if mode ==? 'c'
  400. let pivot_len2 = pw * 2 + tw
  401. if max.pivot_len2 < pivot_len2
  402. let max.pivot_len2 = pivot_len2
  403. endif
  404. let max.strip_len = max([max.strip_len, s:strwidth(s:trim(token))])
  405. endif
  406. let lines[line] = [nth, prefix, token, delim]
  407. endfor
  408. " Phase 1-5: indentation handling (only on a:nth == 1)
  409. if a:nth == 1
  410. let idt = d.indentation
  411. if idt ==? 'd'
  412. let indent = max.indent
  413. elseif idt ==? 's'
  414. let indent = min_indent
  415. elseif idt ==? 'n'
  416. let indent = 0
  417. elseif idt !=? 'k'
  418. call s:exit('Invalid indentation: ' . idt)
  419. end
  420. if idt !=? 'k'
  421. let max.just_len = 0
  422. let max.token_len = 0
  423. let max.pivot_len2 = 0
  424. for [line, elems] in items(lines)
  425. let [nth, prefix, token, delim] = elems
  426. let tindent = matchstr(token, '^\s*')
  427. while 1
  428. let len = s:strwidth(tindent)
  429. if len < indent
  430. let tindent .= repeat(' ', indent - len)
  431. break
  432. elseif len > indent
  433. let tindent = tindent[0 : -2]
  434. else
  435. break
  436. endif
  437. endwhile
  438. let token = tindent . s:ltrim(token)
  439. if mode ==? 'c'
  440. let token = substitute(token, '\s*$', repeat(' ', indent), '')
  441. endif
  442. let [pw, tw] = [s:strwidth(prefix), s:strwidth(token)]
  443. let max.token_len = max([max.token_len, tw])
  444. let max.just_len = max([max.just_len, pw + tw])
  445. if mode ==? 'c'
  446. let pivot_len2 = pw * 2 + tw
  447. if max.pivot_len2 < pivot_len2
  448. let max.pivot_len2 = pivot_len2
  449. endif
  450. endif
  451. let lines[line][2] = token
  452. endfor
  453. endif
  454. endif
  455. " Phase 2
  456. for [line, elems] in items(lines)
  457. let tokens = a:all_tokens[line]
  458. let delims = a:all_delims[line]
  459. let [nth, prefix, token, delim] = elems
  460. " Remove the leading whitespaces of the next token
  461. if len(tokens) > nth + 1
  462. let tokens[nth + 1] = s:ltrim(tokens[nth + 1])
  463. endif
  464. " Pad the token with spaces
  465. let [pw, tw] = [s:strwidth(prefix), s:strwidth(token)]
  466. let rpad = ''
  467. if mode ==? 'l'
  468. let pad = repeat(' ', max.just_len - pw - tw)
  469. if d.stick_to_left
  470. let rpad = pad
  471. else
  472. let token = token . pad
  473. endif
  474. elseif mode ==? 'r'
  475. let pad = repeat(' ', max.just_len - pw - tw)
  476. let indent = matchstr(token, '^\s*')
  477. let token = indent . pad . s:ltrim(token)
  478. elseif mode ==? 'c'
  479. let p1 = max.pivot_len2 - (pw * 2 + tw)
  480. let p2 = max.token_len - tw
  481. let pf1 = s:floor2(p1)
  482. if pf1 < p1 | let p2 = s:ceil2(p2)
  483. else | let p2 = s:floor2(p2)
  484. endif
  485. let strip = s:ceil2(max.token_len - max.strip_len) / 2
  486. let indent = matchstr(token, '^\s*')
  487. let token = indent. repeat(' ', pf1 / 2) .s:ltrim(token). repeat(' ', p2 / 2)
  488. let token = substitute(token, repeat(' ', strip) . '$', '', '')
  489. if d.stick_to_left
  490. if empty(s:rtrim(token))
  491. let center = len(token) / 2
  492. let [token, rpad] = [strpart(token, 0, center), strpart(token, center)]
  493. else
  494. let [token, rpad] = [s:rtrim(token), matchstr(token, '\s*$')]
  495. endif
  496. endif
  497. endif
  498. let tokens[nth] = token
  499. " Pad the delimiter
  500. let dpadl = max.delim_len - s:strwidth(delim)
  501. let da = d.delimiter_align
  502. if da ==? 'l'
  503. let [dl, dr] = ['', repeat(' ', dpadl)]
  504. elseif da ==? 'c'
  505. let dl = repeat(' ', dpadl / 2)
  506. let dr = repeat(' ', dpadl - dpadl / 2)
  507. elseif da ==? 'r'
  508. let [dl, dr] = [repeat(' ', dpadl), '']
  509. else
  510. call s:exit('Invalid delimiter_align: ' . da)
  511. endif
  512. " Before and after the range (for blockwise visual mode)
  513. let cline = getline(line)
  514. let before = strpart(cline, 0, a:fc - 1)
  515. let after = a:lc ? strpart(cline, a:lc) : ''
  516. " Determine the left and right margin around the delimiter
  517. let rest = join(tokens[nth + 1 : -1], '')
  518. let nomore = empty(rest.after)
  519. let ml = (empty(prefix . token) || empty(delim) && nomore) ? '' : d.ml
  520. let mr = nomore ? '' : d.mr
  521. " Adjust indentation of the lines starting with a delimiter
  522. let lpad = ''
  523. if nth == 0
  524. let ipad = repeat(' ', min_indent - s:strwidth(token.ml))
  525. if mode ==? 'l'
  526. let token = ipad . token
  527. else
  528. let lpad = ipad
  529. endif
  530. endif
  531. " Align the token
  532. let aligned = join([lpad, token, ml, dl, delim, dr, mr, rpad], '')
  533. let tokens[nth] = aligned
  534. " Update the line
  535. let a:todo[line] = before.join(tokens, '').after
  536. endfor
  537. if a:nth < max.tokens && (a:recur || len(a:modes) > 1)
  538. call s:shift(a:modes, a:recur == 2)
  539. return [a:todo, a:modes, a:all_tokens, a:all_delims,
  540. \ a:fl, a:ll, a:fc, a:lc, a:nth + 1, a:recur, a:dict]
  541. endif
  542. return [a:todo]
  543. endfunction
  544. function! s:input(str, default, vis)
  545. if a:vis
  546. normal! gv
  547. redraw
  548. execute "normal! \<esc>"
  549. else
  550. " EasyAlign command can be called without visual selection
  551. redraw
  552. endif
  553. let got = input(a:str, a:default)
  554. return got
  555. endfunction
  556. function! s:atoi(str)
  557. return (a:str =~ '^[0-9]\+$') ? str2nr(a:str) : a:str
  558. endfunction
  559. function! s:shift_opts(opts, key, vals)
  560. let val = s:shift(a:vals, 1)
  561. if type(val) == 0 && val == -1
  562. call remove(a:opts, a:key)
  563. else
  564. let a:opts[a:key] = val
  565. endif
  566. endfunction
  567. function! s:interactive(range, modes, n, d, opts, rules, vis, bvis)
  568. let mode = s:shift(a:modes, 1)
  569. let n = a:n
  570. let d = a:d
  571. let ch = ''
  572. let opts = s:compact_options(a:opts)
  573. let vals = deepcopy(s:option_values)
  574. let regx = 0
  575. let warn = ''
  576. let undo = 0
  577. while 1
  578. " Live preview
  579. let rdrw = 0
  580. if undo
  581. silent! undo
  582. let undo = 0
  583. let rdrw = 1
  584. endif
  585. if s:live && !empty(d)
  586. let output = s:process(a:range, mode, n, d, s:normalize_options(opts), regx, a:rules, a:bvis)
  587. let &undolevels = &undolevels " Break undo block
  588. call s:update_lines(output.todo)
  589. let undo = !empty(output.todo)
  590. let rdrw = 1
  591. endif
  592. if rdrw
  593. if a:vis
  594. normal! gv
  595. endif
  596. redraw
  597. if a:vis | execute "normal! \<esc>" | endif
  598. endif
  599. call s:echon(mode, n, -1, regx ? '/'.d.'/' : d, opts, warn)
  600. let check = 0
  601. let warn = ''
  602. try
  603. let c = getchar()
  604. catch /^Vim:Interrupt$/
  605. let c = 27
  606. endtry
  607. let ch = nr2char(c)
  608. if c == 3 || c == 27 " CTRL-C / ESC
  609. if undo
  610. silent! undo
  611. endif
  612. throw 'exit'
  613. elseif c == "\<bs>"
  614. if !empty(d)
  615. let d = ''
  616. let regx = 0
  617. elseif len(n) > 0
  618. let n = strpart(n, 0, len(n) - 1)
  619. endif
  620. elseif c == 13 " Enter key
  621. let mode = s:shift(a:modes, 1)
  622. if has_key(opts, 'a')
  623. let opts.a = mode . strpart(opts.a, 1)
  624. endif
  625. elseif ch == '-'
  626. if empty(n) | let n = '-'
  627. elseif n == '-' | let n = ''
  628. else | let check = 1
  629. endif
  630. elseif ch == '*'
  631. if empty(n) | let n = '*'
  632. elseif n == '*' | let n = '**'
  633. elseif n == '**' | let n = ''
  634. else | let check = 1
  635. endif
  636. elseif empty(d) && ((c == 48 && len(n) > 0) || c > 48 && c <= 57) " Numbers
  637. if n[0] == '*' | let check = 1
  638. else | let n = n . ch
  639. end
  640. elseif ch == "\<C-D>"
  641. call s:shift_opts(opts, 'da', vals['delimiter_align'])
  642. elseif ch == "\<C-I>"
  643. call s:shift_opts(opts, 'idt', vals['indentation'])
  644. elseif ch == "\<C-L>"
  645. let lm = s:input("Left margin: ", get(opts, 'lm', ''), a:vis)
  646. if empty(lm)
  647. let warn = 'Set to default. Input 0 to remove it'
  648. silent! call remove(opts, 'lm')
  649. else
  650. let opts['lm'] = s:atoi(lm)
  651. endif
  652. elseif ch == "\<C-R>"
  653. let rm = s:input("Right margin: ", get(opts, 'rm', ''), a:vis)
  654. if empty(rm)
  655. let warn = 'Set to default. Input 0 to remove it'
  656. silent! call remove(opts, 'rm')
  657. else
  658. let opts['rm'] = s:atoi(rm)
  659. endif
  660. elseif ch == "\<C-U>"
  661. call s:shift_opts(opts, 'iu', vals['ignore_unmatched'])
  662. elseif ch == "\<C-G>"
  663. call s:shift_opts(opts, 'ig', vals['ignore_groups'])
  664. elseif ch == "\<C-P>"
  665. if s:live
  666. if !empty(d)
  667. let ch = d
  668. break
  669. else
  670. let s:live = 0
  671. endif
  672. else
  673. let s:live = 1
  674. endif
  675. elseif c == "\<Left>"
  676. let opts['stl'] = 1
  677. let opts['lm'] = 0
  678. elseif c == "\<Right>"
  679. let opts['stl'] = 0
  680. let opts['lm'] = 1
  681. elseif c == "\<Down>"
  682. let opts['lm'] = 0
  683. let opts['rm'] = 0
  684. elseif c == "\<Up>"
  685. silent! call remove(opts, 'stl')
  686. silent! call remove(opts, 'lm')
  687. silent! call remove(opts, 'rm')
  688. elseif ch == "\<C-A>" || ch == "\<C-O>"
  689. let modes = tolower(s:input("Alignment ([lrc...][[*]*]): ", get(opts, 'a', mode), a:vis))
  690. if match(modes, '^[lrc]\+\*\{0,2}$') != -1
  691. let opts['a'] = modes
  692. let mode = modes[0]
  693. while mode != s:shift(a:modes, 1)
  694. endwhile
  695. else
  696. silent! call remove(opts, 'a')
  697. endif
  698. elseif ch == "\<C-_>" || ch == "\<C-X>"
  699. if s:live && regx && !empty(d)
  700. break
  701. endif
  702. let prompt = 'Regular expression: '
  703. let ch = s:input(prompt, '', a:vis)
  704. if !empty(ch) && s:valid_regexp(ch)
  705. let regx = 1
  706. let d = ch
  707. if !s:live | break | endif
  708. else
  709. let warn = 'Invalid regular expression: '.ch
  710. endif
  711. elseif ch == "\<C-F>"
  712. let f = s:input("Filter (g/../ or v/../): ", get(opts, 'f', ''), a:vis)
  713. let m = matchlist(f, '^[gv]/\(.\{-}\)/\?$')
  714. if empty(f)
  715. silent! call remove(opts, 'f')
  716. elseif !empty(m) && s:valid_regexp(m[1])
  717. let opts['f'] = f
  718. else
  719. let warn = 'Invalid filter expression'
  720. endif
  721. elseif ch =~ '[[:print:]]'
  722. let check = 1
  723. else
  724. let warn = 'Invalid character'
  725. endif
  726. if check
  727. if empty(d)
  728. if has_key(a:rules, ch)
  729. let d = ch
  730. if !s:live
  731. if a:vis
  732. execute "normal! gv\<esc>"
  733. endif
  734. break
  735. endif
  736. else
  737. let warn = 'Unknown delimiter key: '.ch
  738. endif
  739. else
  740. if regx
  741. let warn = 'Press <CTRL-X> to finish'
  742. else
  743. if d == ch
  744. break
  745. else
  746. let warn = 'Press '''.d.''' again to finish'
  747. endif
  748. end
  749. endif
  750. endif
  751. endwhile
  752. if s:live
  753. let copts = call('s:summarize', output.summarize)
  754. let s:live = 0
  755. let g:easy_align_last_command = s:echon('', n, regx, d, copts, '')
  756. let s:live = 1
  757. end
  758. return [mode, n, ch, opts, regx]
  759. endfunction
  760. function! s:valid_regexp(regexp)
  761. try
  762. call matchlist('', a:regexp)
  763. catch
  764. return 0
  765. endtry
  766. return 1
  767. endfunction
  768. function! s:test_regexp(regexp)
  769. let regexp = empty(a:regexp) ? @/ : a:regexp
  770. if !s:valid_regexp(regexp)
  771. call s:exit('Invalid regular expression: '. regexp)
  772. endif
  773. return regexp
  774. endfunction
  775. let s:shorthand_regex =
  776. \ '\s*\%('
  777. \ .'\(lm\?[0-9]\+\)\|\(rm\?[0-9]\+\)\|\(iu[01]\)\|\(\%(s\%(tl\)\?[01]\)\|[<>]\)\|'
  778. \ .'\(da\?[clr]\)\|\(\%(ms\?\|a\)[lrc*]\+\)\|\(i\%(dt\)\?[kdsn]\)\|\([gv]/.*/\)\|\(ig\[.*\]\)'
  779. \ .'\)\+\s*$'
  780. function! s:parse_shorthand_opts(expr)
  781. let opts = {}
  782. let expr = substitute(a:expr, '\s', '', 'g')
  783. let regex = '^'. s:shorthand_regex
  784. if empty(expr)
  785. return opts
  786. elseif expr !~ regex
  787. call s:exit("Invalid expression: ". a:expr)
  788. else
  789. let match = matchlist(expr, regex)
  790. for m in filter(match[ 1 : -1 ], '!empty(v:val)')
  791. for key in ['lm', 'rm', 'l', 'r', 'stl', 's', '<', '>', 'iu', 'da', 'd', 'ms', 'm', 'ig', 'i', 'g', 'v', 'a']
  792. if stridx(tolower(m), key) == 0
  793. let rest = strpart(m, len(key))
  794. if key == 'i' | let key = 'idt' | endif
  795. if key == 'g' || key == 'v'
  796. let rest = key.rest
  797. let key = 'f'
  798. endif
  799. if key == 'idt' || index(['d', 'f', 'm', 'a'], key[0]) >= 0
  800. let opts[key] = rest
  801. elseif key == 'ig'
  802. try
  803. let arr = eval(rest)
  804. if type(arr) == 3
  805. let opts[key] = arr
  806. else
  807. throw 'Not an array'
  808. endif
  809. catch
  810. call s:exit("Invalid ignore_groups: ". a:expr)
  811. endtry
  812. elseif key =~ '[<>]'
  813. let opts['stl'] = key == '<'
  814. else
  815. let opts[key] = str2nr(rest)
  816. endif
  817. break
  818. endif
  819. endfor
  820. endfor
  821. endif
  822. return s:normalize_options(opts)
  823. endfunction
  824. function! s:parse_args(args)
  825. if empty(a:args)
  826. return ['', '', {}, 0]
  827. endif
  828. let n = ''
  829. let ch = ''
  830. let args = a:args
  831. let cand = ''
  832. let opts = {}
  833. " Poor man's option parser
  834. let idx = 0
  835. while 1
  836. let midx = match(args, '\s*{.*}\s*$', idx)
  837. if midx == -1 | break | endif
  838. let cand = strpart(args, midx)
  839. try
  840. let [l, r, c, k, s, d, n] = ['l', 'r', 'c', 'k', 's', 'd', 'n']
  841. let [L, R, C, K, S, D, N] = ['l', 'r', 'c', 'k', 's', 'd', 'n']
  842. let o = eval(cand)
  843. if type(o) == 4
  844. let opts = o
  845. if args[midx - 1 : midx] == '\ '
  846. let midx += 1
  847. endif
  848. let args = strpart(args, 0, midx)
  849. break
  850. endif
  851. catch
  852. " Ignore
  853. endtry
  854. let idx = midx + 1
  855. endwhile
  856. " Invalid option dictionary
  857. if len(substitute(cand, '\s', '', 'g')) > 2 && empty(opts)
  858. call s:exit("Invalid option: ". cand)
  859. else
  860. let opts = s:normalize_options(opts)
  861. endif
  862. " Shorthand option notation
  863. let sopts = matchstr(args, s:shorthand_regex)
  864. if !empty(sopts)
  865. let args = strpart(args, 0, len(args) - len(sopts))
  866. let opts = extend(s:parse_shorthand_opts(sopts), opts)
  867. endif
  868. " Has /Regexp/?
  869. let matches = matchlist(args, '^\(.\{-}\)\s*/\(.*\)/\s*$')
  870. " Found regexp
  871. if !empty(matches)
  872. return [matches[1], s:test_regexp(matches[2]), opts, 1]
  873. else
  874. let tokens = matchlist(args, '^\([1-9][0-9]*\|-[0-9]*\|\*\*\?\)\?\s*\(.\{-}\)\?$')
  875. " Try swapping n and ch
  876. let [n, ch] = empty(tokens[2]) ? reverse(tokens[1:2]) : tokens[1:2]
  877. " Resolving command-line ambiguity
  878. " '\ ' => ' '
  879. " '\' => ' '
  880. if ch =~ '^\\\s*$'
  881. let ch = ' '
  882. " '\\' => '\'
  883. elseif ch =~ '^\\\\\s*$'
  884. let ch = '\'
  885. endif
  886. return [n, ch, opts, 0]
  887. endif
  888. endfunction
  889. function! s:parse_filter(f)
  890. let m = matchlist(a:f, '^\([gv]\)/\(.\{-}\)/\?$')
  891. if empty(m)
  892. return [0, '']
  893. else
  894. return [m[1] == 'g' ? 1 : -1, m[2]]
  895. endif
  896. endfunction
  897. function! s:interactive_modes(bang)
  898. return get(g:,
  899. \ (a:bang ? 'easy_align_bang_interactive_modes' : 'easy_align_interactive_modes'),
  900. \ (a:bang ? ['r', 'l', 'c'] : ['l', 'r', 'c']))
  901. endfunction
  902. function! s:alternating_modes(mode)
  903. return a:mode ==? 'r' ? 'rl' : 'lr'
  904. endfunction
  905. function! s:update_lines(todo)
  906. for [line, content] in items(a:todo)
  907. call setline(line, s:rtrim(content))
  908. endfor
  909. endfunction
  910. function! s:parse_nth(n)
  911. let n = a:n
  912. let recur = 0
  913. if n == '*' | let [nth, recur] = [1, 1]
  914. elseif n == '**' | let [nth, recur] = [1, 2]
  915. elseif n == '-' | let nth = -1
  916. elseif empty(n) | let nth = 1
  917. elseif n == '0' || ( n != '-0' && n != string(str2nr(n)) )
  918. call s:exit('Invalid N-th parameter: '. n)
  919. else
  920. let nth = n
  921. endif
  922. return [nth, recur]
  923. endfunction
  924. function! s:build_dict(delimiters, ch, regexp, opts)
  925. if a:regexp
  926. let dict = { 'pattern': a:ch }
  927. else
  928. if !has_key(a:delimiters, a:ch)
  929. call s:exit('Unknown delimiter key: '. a:ch)
  930. endif
  931. let dict = copy(a:delimiters[a:ch])
  932. endif
  933. call extend(dict, a:opts)
  934. let ml = get(dict, 'left_margin', ' ')
  935. let mr = get(dict, 'right_margin', ' ')
  936. if type(ml) == 0 | let ml = repeat(' ', ml) | endif
  937. if type(mr) == 0 | let mr = repeat(' ', mr) | endif
  938. call extend(dict, { 'ml': ml, 'mr': mr })
  939. let dict.pattern = get(dict, 'pattern', a:ch)
  940. let dict.delimiter_align =
  941. \ get(dict, 'delimiter_align', get(g:, 'easy_align_delimiter_align', 'r'))[0]
  942. let dict.indentation =
  943. \ get(dict, 'indentation', get(g:, 'easy_align_indentation', 'k'))[0]
  944. let dict.stick_to_left =
  945. \ get(dict, 'stick_to_left', 0)
  946. let dict.ignore_unmatched =
  947. \ get(dict, 'ignore_unmatched', get(g:, 'easy_align_ignore_unmatched', 2))
  948. let dict.ignore_groups =
  949. \ get(dict, 'ignore_groups', get(dict, 'ignores', s:ignored_syntax()))
  950. let dict.filter =
  951. \ get(dict, 'filter', '')
  952. return dict
  953. endfunction
  954. function! s:build_mode_sequence(expr, recur)
  955. let [expr, recur] = [a:expr, a:recur]
  956. let suffix = matchstr(a:expr, '\*\+$')
  957. if suffix == '*'
  958. let expr = expr[0 : -2]
  959. let recur = 1
  960. elseif suffix == '**'
  961. let expr = expr[0 : -3]
  962. let recur = 2
  963. endif
  964. return [tolower(expr), recur]
  965. endfunction
  966. function! s:process(range, mode, n, ch, opts, regexp, rules, bvis)
  967. let [nth, recur] = s:parse_nth((empty(a:n) && exists('g:easy_align_nth')) ? g:easy_align_nth : a:n)
  968. let dict = s:build_dict(a:rules, a:ch, a:regexp, a:opts)
  969. let [mode_sequence, recur] = s:build_mode_sequence(
  970. \ get(dict, 'align', recur == 2 ? s:alternating_modes(a:mode) : a:mode),
  971. \ recur)
  972. let ve = &virtualedit
  973. set ve=all
  974. let args = [
  975. \ {}, split(mode_sequence, '\zs'),
  976. \ {}, {}, a:range[0], a:range[1],
  977. \ a:bvis ? min([virtcol("'<"), virtcol("'>")]) : 1,
  978. \ (!recur && a:bvis) ? max([virtcol("'<"), virtcol("'>")]) : 0,
  979. \ nth, recur, dict ]
  980. let &ve = ve
  981. while len(args) > 1
  982. let args = call('s:do_align', args)
  983. endwhile
  984. " todo: lines to update
  985. " summarize: arguments to s:summarize
  986. return { 'todo': args[0], 'summarize': [ a:opts, recur, mode_sequence ] }
  987. endfunction
  988. function s:summarize(opts, recur, mode_sequence)
  989. let copts = s:compact_options(a:opts)
  990. let nbmode = s:interactive_modes(0)[0]
  991. if !has_key(copts, 'a') && (
  992. \ (a:recur == 2 && s:alternating_modes(nbmode) != a:mode_sequence) ||
  993. \ (a:recur != 2 && (a:mode_sequence[0] != nbmode || len(a:mode_sequence) > 1))
  994. \ )
  995. call extend(copts, { 'a': a:mode_sequence })
  996. endif
  997. return copts
  998. endfunction
  999. function! s:align(bang, live, visualmode, first_line, last_line, expr)
  1000. " Heuristically determine if the user was in visual mode
  1001. if a:visualmode == 'command'
  1002. let vis = a:first_line == line("'<") && a:last_line == line("'>")
  1003. let bvis = vis && visualmode() == "\<C-V>"
  1004. elseif empty(a:visualmode)
  1005. let vis = 0
  1006. let bvis = 0
  1007. else
  1008. let vis = 1
  1009. let bvis = a:visualmode == "\<C-V>"
  1010. end
  1011. let range = [a:first_line, a:last_line]
  1012. let modes = s:interactive_modes(a:bang)
  1013. let mode = modes[0]
  1014. let s:live = a:live
  1015. let rules = s:easy_align_delimiters_default
  1016. if exists('g:easy_align_delimiters')
  1017. let rules = extend(copy(rules), g:easy_align_delimiters)
  1018. endif
  1019. let [n, ch, opts, regexp] = s:parse_args(a:expr)
  1020. let bypass_fold = get(g:, 'easy_align_bypass_fold', 0)
  1021. let ofm = &l:foldmethod
  1022. try
  1023. if bypass_fold | let &l:foldmethod = 'manual' | endif
  1024. if empty(n) && empty(ch) || s:live
  1025. let [mode, n, ch, opts, regexp] = s:interactive(range, copy(modes), n, ch, opts, rules, vis, bvis)
  1026. endif
  1027. if !s:live
  1028. let output = s:process(range, mode, n, ch, s:normalize_options(opts), regexp, rules, bvis)
  1029. call s:update_lines(output.todo)
  1030. let copts = call('s:summarize', output.summarize)
  1031. let g:easy_align_last_command = s:echon('', n, regexp, ch, copts, '')
  1032. endif
  1033. finally
  1034. if bypass_fold | let &l:foldmethod = ofm | endif
  1035. endtry
  1036. endfunction
  1037. function! easy_align#align(bang, live, visualmode, expr) range
  1038. try
  1039. call s:align(a:bang, a:live, a:visualmode, a:firstline, a:lastline, a:expr)
  1040. catch /^\%(Vim:Interrupt\|exit\)$/
  1041. if empty(a:visualmode)
  1042. echon "\r"
  1043. echon "\r"
  1044. else
  1045. normal! gv
  1046. endif
  1047. endtry
  1048. endfunction
  1049. let &cpo = s:cpo_save
  1050. unlet s:cpo_save
  1051. " vim: set et sw=2 :