]> git.madduck.net Git - etc/vim.git/blob - ftplugin/markdown.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

752aee40b558066e3d3d5febbeabf3742a4c1829
[etc/vim.git] / ftplugin / markdown.vim
1 "TODO print messages when on visual mode. I only see VISUAL, not the messages.
2
3 " Function interface phylosophy:
4 "
5 " - functions take arbitrary line numbers as parameters.
6 "    Current cursor line is only a suitable default parameter.
7 "
8 " - only functions that bind directly to user actions:
9 "
10 "    - print error messages.
11 "       All intermediate functions limit themselves return `0` to indicate an error.
12 "
13 "    - move the cursor. All other functions do not move the cursor.
14 "
15 " This is how you should view headers for the header mappings:
16 "
17 "   |BUFFER
18 "   |
19 "   |Outside any header
20 "   |
21 " a-+# a
22 "   |
23 "   |Inside a
24 "   |
25 " a-+
26 " b-+## b
27 "   |
28 "   |inside b
29 "   |
30 " b-+
31 " c-+### c
32 "   |
33 "   |Inside c
34 "   |
35 " c-+
36 " d-|# d
37 "   |
38 "   |Inside d
39 "   |
40 " d-+
41 " e-|e
42 "   |====
43 "   |
44 "   |Inside e
45 "   |
46 " e-+
47
48 " For each level, contains the regexp that matches at that level only.
49 "
50 let s:levelRegexpDict = {
51     \ 1: '\v^(#[^#]@=|.+\n\=+$)',
52     \ 2: '\v^(##[^#]@=|.+\n-+$)',
53     \ 3: '\v^###[^#]@=',
54     \ 4: '\v^####[^#]@=',
55     \ 5: '\v^#####[^#]@=',
56     \ 6: '\v^######[^#]@='
57 \ }
58
59 " Maches any header level of any type.
60 "
61 " This could be deduced from `s:levelRegexpDict`, but it is more
62 " efficient to have a single regexp for this.
63 "
64 let s:headersRegexp = '\v^(#|.+\n(\=+|-+)$)'
65
66 " Returns the line number of the first header before `line`, called the
67 " current header.
68 "
69 " If there is no current header, return `0`.
70 "
71 " @param a:1 The line to look the header of. Default value: `getpos('.')`.
72 "
73 function! s:GetHeaderLineNum(...)
74     if a:0 == 0
75         let l:l = line('.')
76     else
77         let l:l = a:1
78     endif
79     while(l:l > 0)
80         if join(getline(l:l, l:l + 1), "\n") =~ s:headersRegexp
81             return l:l
82         endif
83         let l:l -= 1
84     endwhile
85     return 0
86 endfunction
87
88 " -  if inside a header goes to it.
89 "    Return its line number.
90 "
91 " -  if on top level outside any headers,
92 "    print a warning
93 "    Return `0`.
94 "
95 function! s:MoveToCurHeader()
96     let l:lineNum = s:GetHeaderLineNum()
97     if l:lineNum != 0
98         call cursor(l:lineNum, 1)
99     else
100         echo 'outside any header'
101         "normal! gg
102     endif
103     return l:lineNum
104 endfunction
105
106 " Move cursor to next header of any level.
107 "
108 " If there are no more headers, print a warning.
109 "
110 function! s:MoveToNextHeader()
111     if search(s:headersRegexp, 'W') == 0
112         "normal! G
113         echo 'no next header'
114     endif
115 endfunction
116
117 " Move cursor to previous header (before current) of any level.
118 "
119 " If it does not exist, print a warning.
120 "
121 function! s:MoveToPreviousHeader()
122     let l:curHeaderLineNumber = s:GetHeaderLineNum()
123     let l:noPreviousHeader = 0
124     if l:curHeaderLineNumber <= 1
125         let l:noPreviousHeader = 1
126     else
127         let l:previousHeaderLineNumber = s:GetHeaderLineNum(l:curHeaderLineNumber - 1)
128         if l:previousHeaderLineNumber == 0
129             let l:noPreviousHeader = 1
130         else
131             call cursor(l:previousHeaderLineNumber, 1)
132         endif
133     endif
134     if l:noPreviousHeader
135         echo 'no previous header'
136     endif
137 endfunction
138
139 " - if line is inside a header, return the header level (h1 -> 1, h2 -> 2, etc.).
140 "
141 " - if line is at top level outside any headers, return `0`.
142 "
143 function! s:GetHeaderLevel(...)
144     if a:0 == 0
145         let l:line = line('.')
146     else
147         let l:line = a:1
148     endif
149     let l:linenum = s:GetHeaderLineNum(l:line)
150     if l:linenum != 0
151         return s:GetLevelOfHeaderAtLine(l:linenum)
152     else
153         return 0
154     endif
155 endfunction
156
157 " Returns the level of the header at the given line.
158 "
159 " If there is no header at the given line, returns `0`.
160 "
161 function! s:GetLevelOfHeaderAtLine(linenum)
162     let l:lines = join(getline(a:linenum, a:linenum + 1), "\n")
163     for l:key in keys(s:levelRegexpDict)
164         if l:lines =~ get(s:levelRegexpDict, l:key)
165             return l:key
166         endif
167     endfor
168     return 0
169 endfunction
170
171 " Move cursor to parent header of the current header.
172 "
173 " If it does not exit, print a warning and do nothing.
174 "
175 function! s:MoveToParentHeader()
176     let l:linenum = s:GetParentHeaderLineNumber()
177     if l:linenum != 0
178         call cursor(l:linenum, 1)
179     else
180         echo 'no parent header'
181     endif
182 endfunction
183
184 " Return the line number of the parent header of line `line`.
185 "
186 " If it has no parent, return `0`.
187 "
188 function! s:GetParentHeaderLineNumber(...)
189     if a:0 == 0
190         let l:line = line('.')
191     else
192         let l:line = a:1
193     endif
194     let l:level = s:GetHeaderLevel(l:line)
195     if l:level > 1
196         let l:linenum = s:GetPreviousHeaderLineNumberAtLevel(l:level - 1, l:line)
197         return l:linenum
198     endif
199     return 0
200 endfunction
201
202 " Return the line number of the previous header of given level.
203 " in relation to line `a:1`. If not given, `a:1 = getline()`
204 "
205 " `a:1` line is included, and this may return the current header.
206 "
207 " If none return 0.
208 "
209 function! s:GetNextHeaderLineNumberAtLevel(level, ...)
210     if a:0 < 1
211         let l:line = line('.')
212     else
213         let l:line = a:1
214     endif
215     let l:l = l:line
216     while(l:l <= line('$'))
217         if join(getline(l:l, l:l + 1), "\n") =~ get(s:levelRegexpDict, a:level)
218             return l:l
219         endif
220         let l:l += 1
221     endwhile
222     return 0
223 endfunction
224
225 " Return the line number of the previous header of given level.
226 " in relation to line `a:1`. If not given, `a:1 = getline()`
227 "
228 " `a:1` line is included, and this may return the current header.
229 "
230 " If none return 0.
231 "
232 function! s:GetPreviousHeaderLineNumberAtLevel(level, ...)
233     if a:0 == 0
234         let l:line = line('.')
235     else
236         let l:line = a:1
237     endif
238     let l:l = l:line
239     while(l:l > 0)
240         if join(getline(l:l, l:l + 1), "\n") =~ get(s:levelRegexpDict, a:level)
241             return l:l
242         endif
243         let l:l -= 1
244     endwhile
245     return 0
246 endfunction
247
248 " Move cursor to next sibling header.
249 "
250 " If there is no next siblings, print a warning and don't move.
251 "
252 function! s:MoveToNextSiblingHeader()
253     let l:curHeaderLineNumber = s:GetHeaderLineNum()
254     let l:curHeaderLevel = s:GetLevelOfHeaderAtLine(l:curHeaderLineNumber)
255     let l:curHeaderParentLineNumber = s:GetParentHeaderLineNumber()
256     let l:nextHeaderSameLevelLineNumber = s:GetNextHeaderLineNumberAtLevel(l:curHeaderLevel, l:curHeaderLineNumber + 1)
257     let l:noNextSibling = 0
258     if l:nextHeaderSameLevelLineNumber == 0
259         let l:noNextSibling = 1
260     else
261         let l:nextHeaderSameLevelParentLineNumber = s:GetParentHeaderLineNumber(l:nextHeaderSameLevelLineNumber)
262         if l:curHeaderParentLineNumber == l:nextHeaderSameLevelParentLineNumber
263             call cursor(l:nextHeaderSameLevelLineNumber, 1)
264         else
265             let l:noNextSibling = 1
266         endif
267     endif
268     if l:noNextSibling
269         echo 'no next sibling header'
270     endif
271 endfunction
272
273 " Move cursor to previous sibling header.
274 "
275 " If there is no previous siblings, print a warning and do nothing.
276 "
277 function! s:MoveToPreviousSiblingHeader()
278     let l:curHeaderLineNumber = s:GetHeaderLineNum()
279     let l:curHeaderLevel = s:GetLevelOfHeaderAtLine(l:curHeaderLineNumber)
280     let l:curHeaderParentLineNumber = s:GetParentHeaderLineNumber()
281     let l:previousHeaderSameLevelLineNumber = s:GetPreviousHeaderLineNumberAtLevel(l:curHeaderLevel, l:curHeaderLineNumber - 1)
282     let l:noPreviousSibling = 0
283     if l:previousHeaderSameLevelLineNumber == 0
284         let l:noPreviousSibling = 1
285     else
286         let l:previousHeaderSameLevelParentLineNumber = s:GetParentHeaderLineNumber(l:previousHeaderSameLevelLineNumber)
287         if l:curHeaderParentLineNumber == l:previousHeaderSameLevelParentLineNumber
288             call cursor(l:previousHeaderSameLevelLineNumber, 1)
289         else
290             let l:noPreviousSibling = 1
291         endif
292     endif
293     if l:noPreviousSibling
294         echo 'no previous sibling header'
295     endif
296 endfunction
297
298 function! s:Toc(...)
299     if a:0 > 0
300         let l:window_type = a:1
301     else
302         let l:window_type = 'vertical'
303     endif
304
305
306     let b:bufnr = bufnr('%')
307     let b:fenced_block = 0
308     let b:header_list = []
309     let l:header_max_len = 0
310     let g:vim_markdown_toc_autofit = get(g:, "vim_markdown_toc_autofit", 0)
311     for i in range(1, line('$'))
312         let l:lineraw = getline(i)
313         let l:l1 = getline(i+1)
314         let l:line = substitute(l:lineraw, "#", "\\\#", "g")
315         if l:line =~ '````*' || l:line =~ '\~\~\~\~*'
316             if b:fenced_block == 0
317                 let b:fenced_block = 1
318             elseif b:fenced_block == 1
319                 let b:fenced_block = 0
320             endif
321         endif
322         if l:line =~ '^#\+' || (l:l1 =~ '^=\+\s*$' || l:l1 =~ '^-\+\s*$') && l:line =~ '^\S'
323             let b:is_header = 1
324         else
325             let b:is_header = 0
326         endif
327         if b:is_header == 1 && b:fenced_block == 0
328             " append line to location list
329             let b:item = {'lnum': i, 'text': l:line, 'valid': 1, 'bufnr': b:bufnr, 'col': 1}
330             let b:header_list = b:header_list + [b:item]
331             " keep track of the longest header size (heading level + title)
332             let b:total_len = stridx(l:line, ' ') + len(l:line)
333             if b:total_len > l:header_max_len
334                 let l:header_max_len = b:total_len
335             endif
336         endif
337     endfor
338     if len(b:header_list) == 0
339         echom "Toc: No headers."
340         return
341     endif
342     call setloclist(0, b:header_list)
343
344     if l:window_type ==# 'horizontal'
345         lopen
346     elseif l:window_type ==# 'vertical'
347         vertical lopen
348         " auto-fit toc window when possible to shrink it
349         if (&columns/2) > l:header_max_len && g:vim_markdown_toc_autofit == 1
350             execute 'vertical resize ' . (l:header_max_len + 1)
351         else
352             execute 'vertical resize ' . (&columns/2)
353         endif
354     elseif l:window_type ==# 'tab'
355         tab lopen
356     else
357         lopen
358     endif
359     setlocal modifiable
360     for i in range(1, line('$'))
361         " this is the location-list data for the current item
362         let d = getloclist(0)[i-1]
363         " atx headers
364         if match(d.text, "^#") > -1
365             let l:level = len(matchstr(d.text, '#*', 'g'))-1
366             let d.text = substitute(d.text, '\v^#*[ ]*', '', '')
367             let d.text = substitute(d.text, '\v[ ]*#*$', '', '')
368         " setex headers
369         else
370             let l:next_line = getbufline(d.bufnr, d.lnum+1)
371             if match(l:next_line, "=") > -1
372                 let l:level = 0
373             elseif match(l:next_line, "-") > -1
374                 let l:level = 1
375             endif
376         endif
377         call setline(i, repeat('  ', l:level). d.text)
378     endfor
379     setlocal nomodified
380     setlocal nomodifiable
381     normal! gg
382 endfunction
383
384 " Convert Setex headers in range `line1 .. line2` to Atx.
385 "
386 " Return the number of conversions.
387 "
388 function! s:SetexToAtx(line1, line2)
389     let l:originalNumLines = line('$')
390     execute 'silent! ' . a:line1 . ',' . a:line2 . 'substitute/\v(.*\S.*)\n\=+$/# \1/'
391     execute 'silent! ' . a:line1 . ',' . a:line2 . 'substitute/\v(.*\S.*)\n-+$/## \1/'
392     return l:originalNumLines - line('$')
393 endfunction
394
395 " If `a:1` is 0, decrease the level of all headers in range `line1 .. line2`.
396 "
397 " Otherwise, increase the level. `a:1` defaults to `0`.
398 "
399 function! s:HeaderDecrease(line1, line2, ...)
400     if a:0 > 0
401         let l:increase = a:1
402     else
403         let l:increase = 0
404     endif
405     if l:increase
406         let l:forbiddenLevel = 6
407         let l:replaceLevels = [5, 1]
408         let l:levelDelta = 1
409     else
410         let l:forbiddenLevel = 1
411         let l:replaceLevels = [2, 6]
412         let l:levelDelta = -1
413     endif
414     for l:line in range(a:line1, a:line2)
415         if join(getline(l:line, l:line + 1), "\n") =~ s:levelRegexpDict[l:forbiddenLevel]
416             echomsg 'There is an h' . l:forbiddenLevel . ' at line ' . l:line . '. Aborting.'
417             return
418         endif
419     endfor
420     let l:numSubstitutions = s:SetexToAtx(a:line1, a:line2)
421     let l:flags = (&gdefault ? '' : 'g')
422     for l:level in range(replaceLevels[0], replaceLevels[1], -l:levelDelta)
423         execute 'silent! ' . a:line1 . ',' . (a:line2 - l:numSubstitutions) . 'substitute/' . s:levelRegexpDict[l:level] . '/' . repeat('#', l:level + l:levelDelta) . '/' . l:flags
424     endfor
425 endfunction
426
427 " Format table under cursor.
428 "
429 " Depends on Tabularize.
430 "
431 function! s:TableFormat()
432     let l:pos = getpos('.')
433     normal! {
434     " Search instead of `normal! j` because of the table at beginning of file edge case.
435     call search('|')
436     normal! j
437     " Remove everything that is not a pipe othewise well formated tables would grow
438     " because of addition of 2 spaces on the separator line by Tabularize /|.
439     let l:flags = (&gdefault ? '' : 'g')
440     execute 's/[^|]//' . l:flags
441     Tabularize /|
442     execute 's/ /-/' . l:flags
443     call setpos('.', l:pos)
444 endfunction
445
446 " Wrapper to do move commands in visual mode.
447 "
448 function! s:VisMove(f)
449     norm! gv
450     call function(a:f)()
451 endfunction
452
453 " Map in both normal and visual modes.
454 "
455 function! s:MapNormVis(rhs,lhs)
456     execute 'nn <buffer><silent> ' . a:rhs . ' :call ' . a:lhs . '()<cr>'
457     execute 'vn <buffer><silent> ' . a:rhs . ' <esc>:call <sid>VisMove(''' . a:lhs . ''')<cr>'
458 endfunction
459
460 " Parameters:
461 "
462 " - step +1 for right, -1 for left
463 "
464 " TODO: multiple lines.
465 "
466 function! s:FindCornerOfSyntax(lnum, col, step)
467     let l:col = a:col
468     let l:syn = synIDattr(synID(a:lnum, l:col, 1), 'name')
469     while synIDattr(synID(a:lnum, l:col, 1), 'name') ==# l:syn
470         let l:col += a:step
471     endwhile
472     return l:col - a:step
473 endfunction
474
475 " Return the next position of the given syntax name,
476 " inclusive on the given position.
477 "
478 " TODO: multiple lines
479 "
480 function! s:FindNextSyntax(lnum, col, name)
481     let l:col = a:col
482     let l:step = 1
483     while synIDattr(synID(a:lnum, l:col, 1), 'name') !=# a:name
484         let l:col += l:step
485     endwhile
486     return [a:lnum, l:col]
487 endfunction
488
489 function! s:FindCornersOfSyntax(lnum, col)
490     return [<sid>FindLeftOfSyntax(a:lnum, a:col), <sid>FindRightOfSyntax(a:lnum, a:col)]
491 endfunction
492
493 function! s:FindRightOfSyntax(lnum, col)
494     return <sid>FindCornerOfSyntax(a:lnum, a:col, 1)
495 endfunction
496
497 function! s:FindLeftOfSyntax(lnum, col)
498     return <sid>FindCornerOfSyntax(a:lnum, a:col, -1)
499 endfunction
500
501 " Returns:
502 "
503 " - a string with the the URL for the link under the cursor
504 " - an empty string if the cursor is not on a link
505 "
506 " TODO
507 "
508 " - multiline support
509 " - give an error if the separator does is not on a link
510 "
511 function! s:Markdown_GetUrlForPosition(lnum, col)
512     let l:lnum = a:lnum
513     let l:col = a:col
514     let l:syn = synIDattr(synID(l:lnum, l:col, 1), 'name')
515
516     if l:syn ==# 'mkdInlineURL' || l:syn ==# 'mkdURL' || l:syn ==# 'mkdLinkDefTarget'
517         " Do nothing.
518     elseif l:syn ==# 'mkdLink'
519         let [l:lnum, l:col] = <sid>FindNextSyntax(l:lnum, l:col, 'mkdURL')
520         let l:syn = 'mkdURL'
521     elseif l:syn ==# 'mkdDelimiter'
522         let l:line = getline(l:lnum)
523         let l:char = l:line[col - 1]
524         if l:char ==# '<'
525             let l:col += 1
526         elseif l:char ==# '>' || l:char ==# ')'
527             let l:col -= 1
528         elseif l:char ==# '[' || l:char ==# ']' || l:char ==# '('
529             let [l:lnum, l:col] = <sid>FindNextSyntax(l:lnum, l:col, 'mkdURL')
530         else
531             return ''
532         endif
533     else
534         return ''
535     endif
536
537     let [l:left, l:right] = <sid>FindCornersOfSyntax(l:lnum, l:col)
538     return getline(l:lnum)[l:left - 1 : l:right - 1]
539 endfunction
540
541 " Front end for GetUrlForPosition.
542 "
543 function! s:OpenUrlUnderCursor()
544     let l:url = s:Markdown_GetUrlForPosition(line('.'), col('.'))
545     if l:url != ''
546         call s:VersionAwareNetrwBrowseX(l:url)
547     else
548         echomsg 'The cursor is not on a link.'
549     endif
550 endfunction
551
552 function! s:VersionAwareNetrwBrowseX(url)
553     if has('patch-7.4.567')
554         call netrw#BrowseX(a:url, 0)
555     else
556         call netrw#NetrwBrowseX(a:url, 0)
557     endif
558 endf
559
560 function! s:MapNotHasmapto(lhs, rhs)
561     if !hasmapto('<Plug>' . a:rhs)
562         execute 'nmap <buffer>' . a:lhs . ' <Plug>' . a:rhs
563         execute 'vmap <buffer>' . a:lhs . ' <Plug>' . a:rhs
564     endif
565 endfunction
566
567 call <sid>MapNormVis('<Plug>Markdown_MoveToNextHeader', '<sid>MoveToNextHeader')
568 call <sid>MapNormVis('<Plug>Markdown_MoveToPreviousHeader', '<sid>MoveToPreviousHeader')
569 call <sid>MapNormVis('<Plug>Markdown_MoveToNextSiblingHeader', '<sid>MoveToNextSiblingHeader')
570 call <sid>MapNormVis('<Plug>Markdown_MoveToPreviousSiblingHeader', '<sid>MoveToPreviousSiblingHeader')
571 call <sid>MapNormVis('<Plug>Markdown_MoveToParentHeader', '<sid>MoveToParentHeader')
572 call <sid>MapNormVis('<Plug>Markdown_MoveToCurHeader', '<sid>MoveToCurHeader')
573 nnoremap <Plug>Markdown_OpenUrlUnderCursor :call <sid>OpenUrlUnderCursor()<cr>
574
575 if !get(g:, 'vim_markdown_no_default_key_mappings', 0)
576     call <sid>MapNotHasmapto(']]', 'Markdown_MoveToNextHeader')
577     call <sid>MapNotHasmapto('[[', 'Markdown_MoveToPreviousHeader')
578     call <sid>MapNotHasmapto('][', 'Markdown_MoveToNextSiblingHeader')
579     call <sid>MapNotHasmapto('[]', 'Markdown_MoveToPreviousSiblingHeader')
580     call <sid>MapNotHasmapto(']u', 'Markdown_MoveToParentHeader')
581     call <sid>MapNotHasmapto(']c', 'Markdown_MoveToCurHeader')
582     call <sid>MapNotHasmapto('gx', 'Markdown_OpenUrlUnderCursor')
583 endif
584
585 command! -buffer -range=% HeaderDecrease call s:HeaderDecrease(<line1>, <line2>)
586 command! -buffer -range=% HeaderIncrease call s:HeaderDecrease(<line1>, <line2>, 1)
587 command! -buffer -range=% SetexToAtx call s:SetexToAtx(<line1>, <line2>)
588 command! -buffer TableFormat call s:TableFormat()
589 command! -buffer Toc call s:Toc()
590 command! -buffer Toch call s:Toc('horizontal')
591 command! -buffer Tocv call s:Toc('vertical')
592 command! -buffer Toct call s:Toc('tab')
593
594 " Heavily based on vim-notes - http://peterodding.com/code/vim/notes/
595 let s:filetype_dict = {
596     \ 'c++': 'cpp',
597     \ 'viml': 'vim'
598 \ }
599
600 function! s:Markdown_highlight_sources(force)
601     " Syntax highlight source code embedded in notes.
602     " Look for code blocks in the current file
603     let filetypes = {}
604     for line in getline(1, '$')
605         let ft = matchstr(line, '```\zs[0-9A-Za-z_+-]*')
606         if !empty(ft) && ft !~ '^\d*$' | let filetypes[ft] = 1 | endif
607     endfor
608     if !exists('b:mkd_known_filetypes')
609         let b:mkd_known_filetypes = {}
610     endif
611     if !a:force && (b:mkd_known_filetypes == filetypes || empty(filetypes))
612         return
613     endif
614
615     " Now we're ready to actually highlight the code blocks.
616     let startgroup = 'mkdCodeStart'
617     let endgroup = 'mkdCodeEnd'
618     for ft in keys(filetypes)
619         if a:force || !has_key(b:mkd_known_filetypes, ft)
620             if has_key(s:filetype_dict, ft)
621                 let filetype = s:filetype_dict[ft]
622             else
623                 let filetype = ft
624             endif
625             let group = 'mkdSnippet' . toupper(substitute(filetype, "[+-]", "_", "g"))
626             let include = s:syntax_include(filetype)
627             let command = 'syntax region %s matchgroup=%s start="^\s*```%s$" matchgroup=%s end="\s*```$" keepend contains=%s%s'
628             execute printf(command, group, startgroup, ft, endgroup, include, has('conceal') ? ' concealends' : '')
629             execute printf('syntax cluster mkdNonListItem add=%s', group)
630
631             let b:mkd_known_filetypes[ft] = 1
632         endif
633     endfor
634 endfunction
635
636 function! s:syntax_include(filetype)
637     " Include the syntax highlighting of another {filetype}.
638     let grouplistname = '@' . toupper(a:filetype)
639     " Unset the name of the current syntax while including the other syntax
640     " because some syntax scripts do nothing when "b:current_syntax" is set
641     if exists('b:current_syntax')
642         let syntax_save = b:current_syntax
643         unlet b:current_syntax
644     endif
645     try
646         execute 'syntax include' grouplistname 'syntax/' . a:filetype . '.vim'
647         execute 'syntax include' grouplistname 'after/syntax/' . a:filetype . '.vim'
648     catch /E484/
649         " Ignore missing scripts
650     endtry
651     " Restore the name of the current syntax
652     if exists('syntax_save')
653         let b:current_syntax = syntax_save
654     elseif exists('b:current_syntax')
655         unlet b:current_syntax
656     endif
657     return grouplistname
658 endfunction
659
660
661 function! s:Markdown_refresh_syntax(force)
662     if &filetype == 'markdown' && line('$') > 1
663         call s:Markdown_highlight_sources(a:force)
664     endif
665 endfunction
666
667 augroup Mkd
668     autocmd!
669     au BufWinEnter * call s:Markdown_refresh_syntax(1)
670     au BufWritePost * call s:Markdown_refresh_syntax(0)
671     au InsertEnter,InsertLeave * call s:Markdown_refresh_syntax(0)
672     au CursorHold,CursorHoldI * call s:Markdown_refresh_syntax(0)
673 augroup END