snipMate.vim 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. fun! Filename(...)
  2. let filename = expand('%:t:r')
  3. if filename == '' | return a:0 == 2 ? a:2 : '' | endif
  4. return !a:0 || a:1 == '' ? filename : substitute(a:1, '$1', filename, 'g')
  5. endf
  6. fun s:RemoveSnippet()
  7. unl! g:snipPos s:curPos s:snipLen s:endCol s:endLine s:prevLen
  8. \ s:lastBuf s:oldWord
  9. if exists('s:update')
  10. unl s:startCol s:origWordLen s:update
  11. if exists('s:oldVars') | unl s:oldVars s:oldEndCol | endif
  12. endif
  13. aug! snipMateAutocmds
  14. endf
  15. fun snipMate#expandSnip(snip, col)
  16. let lnum = line('.') | let col = a:col
  17. let snippet = s:ProcessSnippet(a:snip)
  18. " Avoid error if eval evaluates to nothing
  19. if snippet == '' | return '' | endif
  20. " Expand snippet onto current position with the tab stops removed
  21. let snipLines = split(substitute(snippet, '$\d\+\|${\d\+.\{-}}', '', 'g'), "\n", 1)
  22. let line = getline(lnum)
  23. let afterCursor = strpart(line, col - 1)
  24. " Keep text after the cursor
  25. if afterCursor != "\t" && afterCursor != ' '
  26. let line = strpart(line, 0, col - 1)
  27. let snipLines[-1] .= afterCursor
  28. else
  29. let afterCursor = ''
  30. " For some reason the cursor needs to move one right after this
  31. if line != '' && col == 1 && &ve != 'all' && &ve != 'onemore'
  32. let col += 1
  33. endif
  34. endif
  35. call setline(lnum, line.snipLines[0])
  36. " Autoindent snippet according to previous indentation
  37. let indent = matchend(line, '^.\{-}\ze\(\S\|$\)') + 1
  38. call append(lnum, map(snipLines[1:], "'".strpart(line, 0, indent - 1)."'.v:val"))
  39. " Open any folds snippet expands into
  40. if &fen | sil! exe lnum.','.(lnum + len(snipLines) - 1).'foldopen' | endif
  41. let [g:snipPos, s:snipLen] = s:BuildTabStops(snippet, lnum, col - indent, indent)
  42. if s:snipLen
  43. aug snipMateAutocmds
  44. au CursorMovedI * call s:UpdateChangedSnip(0)
  45. au InsertEnter * call s:UpdateChangedSnip(1)
  46. aug END
  47. let s:lastBuf = bufnr(0) " Only expand snippet while in current buffer
  48. let s:curPos = 0
  49. let s:endCol = g:snipPos[s:curPos][1]
  50. let s:endLine = g:snipPos[s:curPos][0]
  51. call cursor(g:snipPos[s:curPos][0], g:snipPos[s:curPos][1])
  52. let s:prevLen = [line('$'), col('$')]
  53. if g:snipPos[s:curPos][2] != -1 | return s:SelectWord() | endif
  54. else
  55. unl g:snipPos s:snipLen
  56. " Place cursor at end of snippet if no tab stop is given
  57. let newlines = len(snipLines) - 1
  58. call cursor(lnum + newlines, indent + len(snipLines[-1]) - len(afterCursor)
  59. \ + (newlines ? 0: col - 1))
  60. endif
  61. return ''
  62. endf
  63. " Prepare snippet to be processed by s:BuildTabStops
  64. fun s:ProcessSnippet(snip)
  65. let snippet = a:snip
  66. " Evaluate eval (`...`) expressions.
  67. " Backquotes prefixed with a backslash "\" are ignored.
  68. " Using a loop here instead of a regex fixes a bug with nested "\=".
  69. if stridx(snippet, '`') != -1
  70. while match(snippet, '\(^\|[^\\]\)`.\{-}[^\\]`') != -1
  71. let snippet = substitute(snippet, '\(^\|[^\\]\)\zs`.\{-}[^\\]`\ze',
  72. \ substitute(eval(matchstr(snippet, '\(^\|[^\\]\)`\zs.\{-}[^\\]\ze`')),
  73. \ "\n\\%$", '', ''), '')
  74. endw
  75. let snippet = substitute(snippet, "\r", "\n", 'g')
  76. let snippet = substitute(snippet, '\\`', '`', 'g')
  77. endif
  78. " Place all text after a colon in a tab stop after the tab stop
  79. " (e.g. "${#:foo}" becomes "${:foo}foo").
  80. " This helps tell the position of the tab stops later.
  81. let snippet = substitute(snippet, '${\d\+:\(.\{-}\)}', '&\1', 'g')
  82. " Update the a:snip so that all the $# become the text after
  83. " the colon in their associated ${#}.
  84. " (e.g. "${1:foo}" turns all "$1"'s into "foo")
  85. let i = 1
  86. while stridx(snippet, '${'.i) != -1
  87. let s = matchstr(snippet, '${'.i.':\zs.\{-}\ze}')
  88. if s != ''
  89. let snippet = substitute(snippet, '$'.i, s.'&', 'g')
  90. endif
  91. let i += 1
  92. endw
  93. if &et " Expand tabs to spaces if 'expandtab' is set.
  94. return substitute(snippet, '\t', repeat(' ', &sts ? &sts : &sw), 'g')
  95. endif
  96. return snippet
  97. endf
  98. " Counts occurences of haystack in needle
  99. fun s:Count(haystack, needle)
  100. let counter = 0
  101. let index = stridx(a:haystack, a:needle)
  102. while index != -1
  103. let index = stridx(a:haystack, a:needle, index+1)
  104. let counter += 1
  105. endw
  106. return counter
  107. endf
  108. " Builds a list of a list of each tab stop in the snippet containing:
  109. " 1.) The tab stop's line number.
  110. " 2.) The tab stop's column number
  111. " (by getting the length of the string between the last "\n" and the
  112. " tab stop).
  113. " 3.) The length of the text after the colon for the current tab stop
  114. " (e.g. "${1:foo}" would return 3). If there is no text, -1 is returned.
  115. " 4.) If the "${#:}" construct is given, another list containing all
  116. " the matches of "$#", to be replaced with the placeholder. This list is
  117. " composed the same way as the parent; the first item is the line number,
  118. " and the second is the column.
  119. fun s:BuildTabStops(snip, lnum, col, indent)
  120. let snipPos = []
  121. let i = 1
  122. let withoutVars = substitute(a:snip, '$\d\+', '', 'g')
  123. while stridx(a:snip, '${'.i) != -1
  124. let beforeTabStop = matchstr(withoutVars, '^.*\ze${'.i.'\D')
  125. let withoutOthers = substitute(withoutVars, '${\('.i.'\D\)\@!\d\+.\{-}}', '', 'g')
  126. let j = i - 1
  127. call add(snipPos, [0, 0, -1])
  128. let snipPos[j][0] = a:lnum + s:Count(beforeTabStop, "\n")
  129. let snipPos[j][1] = a:indent + len(matchstr(withoutOthers, '.*\(\n\|^\)\zs.*\ze${'.i.'\D'))
  130. if snipPos[j][0] == a:lnum | let snipPos[j][1] += a:col | endif
  131. " Get all $# matches in another list, if ${#:name} is given
  132. if stridx(withoutVars, '${'.i.':') != -1
  133. let snipPos[j][2] = len(matchstr(withoutVars, '${'.i.':\zs.\{-}\ze}'))
  134. let dots = repeat('.', snipPos[j][2])
  135. call add(snipPos[j], [])
  136. let withoutOthers = substitute(a:snip, '${\d\+.\{-}}\|$'.i.'\@!\d\+', '', 'g')
  137. while match(withoutOthers, '$'.i.'\(\D\|$\)') != -1
  138. let beforeMark = matchstr(withoutOthers, '^.\{-}\ze'.dots.'$'.i.'\(\D\|$\)')
  139. call add(snipPos[j][3], [0, 0])
  140. let snipPos[j][3][-1][0] = a:lnum + s:Count(beforeMark, "\n")
  141. let snipPos[j][3][-1][1] = a:indent + (snipPos[j][3][-1][0] > a:lnum
  142. \ ? len(matchstr(beforeMark, '.*\n\zs.*'))
  143. \ : a:col + len(beforeMark))
  144. let withoutOthers = substitute(withoutOthers, '$'.i.'\ze\(\D\|$\)', '', '')
  145. endw
  146. endif
  147. let i += 1
  148. endw
  149. return [snipPos, i - 1]
  150. endf
  151. fun snipMate#jumpTabStop(backwards)
  152. let leftPlaceholder = exists('s:origWordLen')
  153. \ && s:origWordLen != g:snipPos[s:curPos][2]
  154. if leftPlaceholder && exists('s:oldEndCol')
  155. let startPlaceholder = s:oldEndCol + 1
  156. endif
  157. if exists('s:update')
  158. call s:UpdatePlaceholderTabStops()
  159. else
  160. call s:UpdateTabStops()
  161. endif
  162. " Don't reselect placeholder if it has been modified
  163. if leftPlaceholder && g:snipPos[s:curPos][2] != -1
  164. if exists('startPlaceholder')
  165. let g:snipPos[s:curPos][1] = startPlaceholder
  166. else
  167. let g:snipPos[s:curPos][1] = col('.')
  168. let g:snipPos[s:curPos][2] = 0
  169. endif
  170. endif
  171. let s:curPos += a:backwards ? -1 : 1
  172. " Loop over the snippet when going backwards from the beginning
  173. if s:curPos < 0 | let s:curPos = s:snipLen - 1 | endif
  174. if s:curPos == s:snipLen
  175. let sMode = s:endCol == g:snipPos[s:curPos-1][1]+g:snipPos[s:curPos-1][2]
  176. call s:RemoveSnippet()
  177. return sMode ? "\<tab>" : TriggerSnippet()
  178. endif
  179. call cursor(g:snipPos[s:curPos][0], g:snipPos[s:curPos][1])
  180. let s:endLine = g:snipPos[s:curPos][0]
  181. let s:endCol = g:snipPos[s:curPos][1]
  182. let s:prevLen = [line('$'), col('$')]
  183. return g:snipPos[s:curPos][2] == -1 ? '' : s:SelectWord()
  184. endf
  185. fun s:UpdatePlaceholderTabStops()
  186. let changeLen = s:origWordLen - g:snipPos[s:curPos][2]
  187. unl s:startCol s:origWordLen s:update
  188. if !exists('s:oldVars') | return | endif
  189. " Update tab stops in snippet if text has been added via "$#"
  190. " (e.g., in "${1:foo}bar$1${2}").
  191. if changeLen != 0
  192. let curLine = line('.')
  193. for pos in g:snipPos
  194. if pos == g:snipPos[s:curPos] | continue | endif
  195. let changed = pos[0] == curLine && pos[1] > s:oldEndCol
  196. let changedVars = 0
  197. let endPlaceholder = pos[2] - 1 + pos[1]
  198. " Subtract changeLen from each tab stop that was after any of
  199. " the current tab stop's placeholders.
  200. for [lnum, col] in s:oldVars
  201. if lnum > pos[0] | break | endif
  202. if pos[0] == lnum
  203. if pos[1] > col || (pos[2] == -1 && pos[1] == col)
  204. let changed += 1
  205. elseif col < endPlaceholder
  206. let changedVars += 1
  207. endif
  208. endif
  209. endfor
  210. let pos[1] -= changeLen * changed
  211. let pos[2] -= changeLen * changedVars " Parse variables within placeholders
  212. " e.g., "${1:foo} ${2:$1bar}"
  213. if pos[2] == -1 | continue | endif
  214. " Do the same to any placeholders in the other tab stops.
  215. for nPos in pos[3]
  216. let changed = nPos[0] == curLine && nPos[1] > s:oldEndCol
  217. for [lnum, col] in s:oldVars
  218. if lnum > nPos[0] | break | endif
  219. if nPos[0] == lnum && nPos[1] > col
  220. let changed += 1
  221. endif
  222. endfor
  223. let nPos[1] -= changeLen * changed
  224. endfor
  225. endfor
  226. endif
  227. unl s:endCol s:oldVars s:oldEndCol
  228. endf
  229. fun s:UpdateTabStops()
  230. let changeLine = s:endLine - g:snipPos[s:curPos][0]
  231. let changeCol = s:endCol - g:snipPos[s:curPos][1]
  232. if exists('s:origWordLen')
  233. let changeCol -= s:origWordLen
  234. unl s:origWordLen
  235. endif
  236. let lnum = g:snipPos[s:curPos][0]
  237. let col = g:snipPos[s:curPos][1]
  238. " Update the line number of all proceeding tab stops if <cr> has
  239. " been inserted.
  240. if changeLine != 0
  241. let changeLine -= 1
  242. for pos in g:snipPos
  243. if pos[0] >= lnum
  244. if pos[0] == lnum | let pos[1] += changeCol | endif
  245. let pos[0] += changeLine
  246. endif
  247. if pos[2] == -1 | continue | endif
  248. for nPos in pos[3]
  249. if nPos[0] >= lnum
  250. if nPos[0] == lnum | let nPos[1] += changeCol | endif
  251. let nPos[0] += changeLine
  252. endif
  253. endfor
  254. endfor
  255. elseif changeCol != 0
  256. " Update the column of all proceeding tab stops if text has
  257. " been inserted/deleted in the current line.
  258. for pos in g:snipPos
  259. if pos[1] >= col && pos[0] == lnum
  260. let pos[1] += changeCol
  261. endif
  262. if pos[2] == -1 | continue | endif
  263. for nPos in pos[3]
  264. if nPos[0] > lnum | break | endif
  265. if nPos[0] == lnum && nPos[1] >= col
  266. let nPos[1] += changeCol
  267. endif
  268. endfor
  269. endfor
  270. endif
  271. endf
  272. fun s:SelectWord()
  273. let s:origWordLen = g:snipPos[s:curPos][2]
  274. let s:oldWord = strpart(getline('.'), g:snipPos[s:curPos][1] - 1,
  275. \ s:origWordLen)
  276. let s:prevLen[1] -= s:origWordLen
  277. if !empty(g:snipPos[s:curPos][3])
  278. let s:update = 1
  279. let s:endCol = -1
  280. let s:startCol = g:snipPos[s:curPos][1] - 1
  281. endif
  282. if !s:origWordLen | return '' | endif
  283. let l = col('.') != 1 ? 'l' : ''
  284. if &sel == 'exclusive'
  285. return "\<esc>".l.'v'.s:origWordLen."l\<c-g>"
  286. endif
  287. return s:origWordLen == 1 ? "\<esc>".l.'gh'
  288. \ : "\<esc>".l.'v'.(s:origWordLen - 1)."l\<c-g>"
  289. endf
  290. " This updates the snippet as you type when text needs to be inserted
  291. " into multiple places (e.g. in "${1:default text}foo$1bar$1",
  292. " "default text" would be highlighted, and if the user types something,
  293. " UpdateChangedSnip() would be called so that the text after "foo" & "bar"
  294. " are updated accordingly)
  295. "
  296. " It also automatically quits the snippet if the cursor is moved out of it
  297. " while in insert mode.
  298. fun s:UpdateChangedSnip(entering)
  299. if exists('g:snipPos') && bufnr(0) != s:lastBuf
  300. call s:RemoveSnippet()
  301. elseif exists('s:update') " If modifying a placeholder
  302. if !exists('s:oldVars') && s:curPos + 1 < s:snipLen
  303. " Save the old snippet & word length before it's updated
  304. " s:startCol must be saved too, in case text is added
  305. " before the snippet (e.g. in "foo$1${2}bar${1:foo}").
  306. let s:oldEndCol = s:startCol
  307. let s:oldVars = deepcopy(g:snipPos[s:curPos][3])
  308. endif
  309. let col = col('.') - 1
  310. if s:endCol != -1
  311. let changeLen = col('$') - s:prevLen[1]
  312. let s:endCol += changeLen
  313. else " When being updated the first time, after leaving select mode
  314. if a:entering | return | endif
  315. let s:endCol = col - 1
  316. endif
  317. " If the cursor moves outside the snippet, quit it
  318. if line('.') != g:snipPos[s:curPos][0] || col < s:startCol ||
  319. \ col - 1 > s:endCol
  320. unl! s:startCol s:origWordLen s:oldVars s:update
  321. return s:RemoveSnippet()
  322. endif
  323. call s:UpdateVars()
  324. let s:prevLen[1] = col('$')
  325. elseif exists('g:snipPos')
  326. if !a:entering && g:snipPos[s:curPos][2] != -1
  327. let g:snipPos[s:curPos][2] = -2
  328. endif
  329. let col = col('.')
  330. let lnum = line('.')
  331. let changeLine = line('$') - s:prevLen[0]
  332. if lnum == s:endLine
  333. let s:endCol += col('$') - s:prevLen[1]
  334. let s:prevLen = [line('$'), col('$')]
  335. endif
  336. if changeLine != 0
  337. let s:endLine += changeLine
  338. let s:endCol = col
  339. endif
  340. " Delete snippet if cursor moves out of it in insert mode
  341. if (lnum == s:endLine && (col > s:endCol || col < g:snipPos[s:curPos][1]))
  342. \ || lnum > s:endLine || lnum < g:snipPos[s:curPos][0]
  343. call s:RemoveSnippet()
  344. endif
  345. endif
  346. endf
  347. " This updates the variables in a snippet when a placeholder has been edited.
  348. " (e.g., each "$1" in "${1:foo} $1bar $1bar")
  349. fun s:UpdateVars()
  350. let newWordLen = s:endCol - s:startCol + 1
  351. let newWord = strpart(getline('.'), s:startCol, newWordLen)
  352. if newWord == s:oldWord || empty(g:snipPos[s:curPos][3])
  353. return
  354. endif
  355. let changeLen = g:snipPos[s:curPos][2] - newWordLen
  356. let curLine = line('.')
  357. let startCol = col('.')
  358. let oldStartSnip = s:startCol
  359. let updateTabStops = changeLen != 0
  360. let i = 0
  361. for [lnum, col] in g:snipPos[s:curPos][3]
  362. if updateTabStops
  363. let start = s:startCol
  364. if lnum == curLine && col <= start
  365. let s:startCol -= changeLen
  366. let s:endCol -= changeLen
  367. endif
  368. for nPos in g:snipPos[s:curPos][3][(i):]
  369. " This list is in ascending order, so quit if we've gone too far.
  370. if nPos[0] > lnum | break | endif
  371. if nPos[0] == lnum && nPos[1] > col
  372. let nPos[1] -= changeLen
  373. endif
  374. endfor
  375. if lnum == curLine && col > start
  376. let col -= changeLen
  377. let g:snipPos[s:curPos][3][i][1] = col
  378. endif
  379. let i += 1
  380. endif
  381. " "Very nomagic" is used here to allow special characters.
  382. call setline(lnum, substitute(getline(lnum), '\%'.col.'c\V'.
  383. \ escape(s:oldWord, '\'), escape(newWord, '\&'), ''))
  384. endfor
  385. if oldStartSnip != s:startCol
  386. call cursor(0, startCol + s:startCol - oldStartSnip)
  387. endif
  388. let s:oldWord = newWord
  389. let g:snipPos[s:curPos][2] = newWordLen
  390. endf
  391. " vim:noet:sw=4:ts=4:ft=vim