]> git.madduck.net Git - etc/vim.git/blob - .vim/bundle/vim-markdown/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:

Merge commit '294584081929424aec883f90c7d6515b3743358d' as '.vim/bundle/vim-lsp-ale'
[etc/vim.git] / .vim / bundle / vim-markdown / ftplugin / markdown.vim
1 "TODO print messages when on visual mode. I only see VISUAL, not the messages.
2
3 " Function interface philosophy:
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 " Matches 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 " Return list of headers and their levels.
158 "
159 function! s:GetHeaderList()
160     let l:bufnr = bufnr('%')
161     let l:fenced_block = 0
162     let l:front_matter = 0
163     let l:header_list = []
164     let l:vim_markdown_frontmatter = get(g:, 'vim_markdown_frontmatter', 0)
165     let l:fence_str = ''
166     for i in range(1, line('$'))
167         let l:lineraw = getline(i)
168         let l:l1 = getline(i+1)
169         let l:line = substitute(l:lineraw, '#', "\\\#", 'g')
170         " exclude lines in fenced code blocks
171         if l:line =~# '\v^[[:space:]>]*(`{3,}|\~{3,})\s*(\w+)?\s*$'
172             if l:fenced_block == 0
173                 let l:fenced_block = 1
174                 let l:fence_str = matchstr(l:line, '\v(`{3,}|\~{3,})')
175             elseif l:fenced_block == 1 && matchstr(l:line, '\v(`{3,}|\~{3,})') ==# l:fence_str
176                 let l:fenced_block = 0
177                 let l:fence_str = ''
178             endif
179         " exclude lines in frontmatters
180         elseif l:vim_markdown_frontmatter == 1
181             if l:front_matter == 1
182                 if l:line ==# '---'
183                     let l:front_matter = 0
184                 endif
185             elseif i == 1
186                 if l:line ==# '---'
187                     let l:front_matter = 1
188                 endif
189             endif
190         endif
191         " match line against header regex
192         if join(getline(i, i + 1), "\n") =~# s:headersRegexp && l:line =~# '^\S'
193             let l:is_header = 1
194         else
195             let l:is_header = 0
196         endif
197         if l:is_header ==# 1 && l:fenced_block ==# 0 && l:front_matter ==# 0
198             " remove hashes from atx headers
199             if match(l:line, '^#') > -1
200                 let l:line = substitute(l:line, '\v^#*[ ]*', '', '')
201                 let l:line = substitute(l:line, '\v[ ]*#*$', '', '')
202             endif
203             " append line to list
204             let l:level = s:GetHeaderLevel(i)
205             let l:item = {'level': l:level, 'text': l:line, 'lnum': i, 'bufnr': bufnr}
206             let l:header_list = l:header_list + [l:item]
207         endif
208     endfor
209     return l:header_list
210 endfunction
211
212 " Returns the level of the header at the given line.
213 "
214 " If there is no header at the given line, returns `0`.
215 "
216 function! s:GetLevelOfHeaderAtLine(linenum)
217     let l:lines = join(getline(a:linenum, a:linenum + 1), "\n")
218     for l:key in keys(s:levelRegexpDict)
219         if l:lines =~ get(s:levelRegexpDict, l:key)
220             return l:key
221         endif
222     endfor
223     return 0
224 endfunction
225
226 " Move cursor to parent header of the current header.
227 "
228 " If it does not exit, print a warning and do nothing.
229 "
230 function! s:MoveToParentHeader()
231     let l:linenum = s:GetParentHeaderLineNumber()
232     if l:linenum != 0
233         call setpos("''", getpos('.'))
234         call cursor(l:linenum, 1)
235     else
236         echo 'no parent header'
237     endif
238 endfunction
239
240 " Return the line number of the parent header of line `line`.
241 "
242 " If it has no parent, return `0`.
243 "
244 function! s:GetParentHeaderLineNumber(...)
245     if a:0 == 0
246         let l:line = line('.')
247     else
248         let l:line = a:1
249     endif
250     let l:level = s:GetHeaderLevel(l:line)
251     if l:level > 1
252         let l:linenum = s:GetPreviousHeaderLineNumberAtLevel(l:level - 1, l:line)
253         return l:linenum
254     endif
255     return 0
256 endfunction
257
258 " Return the line number of the previous header of given level.
259 " in relation to line `a:1`. If not given, `a:1 = getline()`
260 "
261 " `a:1` line is included, and this may return the current header.
262 "
263 " If none return 0.
264 "
265 function! s:GetNextHeaderLineNumberAtLevel(level, ...)
266     if a:0 < 1
267         let l:line = line('.')
268     else
269         let l:line = a:1
270     endif
271     let l:l = l:line
272     while(l:l <= line('$'))
273         if join(getline(l:l, l:l + 1), "\n") =~ get(s:levelRegexpDict, a:level)
274             return l:l
275         endif
276         let l:l += 1
277     endwhile
278     return 0
279 endfunction
280
281 " Return the line number of the previous header of given level.
282 " in relation to line `a:1`. If not given, `a:1 = getline()`
283 "
284 " `a:1` line is included, and this may return the current header.
285 "
286 " If none return 0.
287 "
288 function! s:GetPreviousHeaderLineNumberAtLevel(level, ...)
289     if a:0 == 0
290         let l:line = line('.')
291     else
292         let l:line = a:1
293     endif
294     let l:l = l:line
295     while(l:l > 0)
296         if join(getline(l:l, l:l + 1), "\n") =~ get(s:levelRegexpDict, a:level)
297             return l:l
298         endif
299         let l:l -= 1
300     endwhile
301     return 0
302 endfunction
303
304 " Move cursor to next sibling header.
305 "
306 " If there is no next siblings, print a warning and don't move.
307 "
308 function! s:MoveToNextSiblingHeader()
309     let l:curHeaderLineNumber = s:GetHeaderLineNum()
310     let l:curHeaderLevel = s:GetLevelOfHeaderAtLine(l:curHeaderLineNumber)
311     let l:curHeaderParentLineNumber = s:GetParentHeaderLineNumber()
312     let l:nextHeaderSameLevelLineNumber = s:GetNextHeaderLineNumberAtLevel(l:curHeaderLevel, l:curHeaderLineNumber + 1)
313     let l:noNextSibling = 0
314     if l:nextHeaderSameLevelLineNumber == 0
315         let l:noNextSibling = 1
316     else
317         let l:nextHeaderSameLevelParentLineNumber = s:GetParentHeaderLineNumber(l:nextHeaderSameLevelLineNumber)
318         if l:curHeaderParentLineNumber == l:nextHeaderSameLevelParentLineNumber
319             call cursor(l:nextHeaderSameLevelLineNumber, 1)
320         else
321             let l:noNextSibling = 1
322         endif
323     endif
324     if l:noNextSibling
325         echo 'no next sibling header'
326     endif
327 endfunction
328
329 " Move cursor to previous sibling header.
330 "
331 " If there is no previous siblings, print a warning and do nothing.
332 "
333 function! s:MoveToPreviousSiblingHeader()
334     let l:curHeaderLineNumber = s:GetHeaderLineNum()
335     let l:curHeaderLevel = s:GetLevelOfHeaderAtLine(l:curHeaderLineNumber)
336     let l:curHeaderParentLineNumber = s:GetParentHeaderLineNumber()
337     let l:previousHeaderSameLevelLineNumber = s:GetPreviousHeaderLineNumberAtLevel(l:curHeaderLevel, l:curHeaderLineNumber - 1)
338     let l:noPreviousSibling = 0
339     if l:previousHeaderSameLevelLineNumber == 0
340         let l:noPreviousSibling = 1
341     else
342         let l:previousHeaderSameLevelParentLineNumber = s:GetParentHeaderLineNumber(l:previousHeaderSameLevelLineNumber)
343         if l:curHeaderParentLineNumber == l:previousHeaderSameLevelParentLineNumber
344             call cursor(l:previousHeaderSameLevelLineNumber, 1)
345         else
346             let l:noPreviousSibling = 1
347         endif
348     endif
349     if l:noPreviousSibling
350         echo 'no previous sibling header'
351     endif
352 endfunction
353
354 function! s:Toc(...)
355     if a:0 > 0
356         let l:window_type = a:1
357     else
358         let l:window_type = 'vertical'
359     endif
360
361
362     let l:cursor_line = line('.')
363     let l:cursor_header = 0
364     let l:header_list = s:GetHeaderList()
365     let l:indented_header_list = []
366     if len(l:header_list) == 0
367         echom 'Toc: No headers.'
368         return
369     endif
370     let l:header_max_len = 0
371     let l:vim_markdown_toc_autofit = get(g:, 'vim_markdown_toc_autofit', 0)
372     for h in l:header_list
373         " set header number of the cursor position
374         if l:cursor_header == 0
375             let l:header_line = h.lnum
376             if l:header_line == l:cursor_line
377                 let l:cursor_header = index(l:header_list, h) + 1
378             elseif l:header_line > l:cursor_line
379                 let l:cursor_header = index(l:header_list, h)
380             endif
381         endif
382         " indent header based on level
383         let l:text = repeat('  ', h.level-1) . h.text
384         " keep track of the longest header size (heading level + title)
385         let l:total_len = strdisplaywidth(l:text)
386         if l:total_len > l:header_max_len
387             let l:header_max_len = l:total_len
388         endif
389         " append indented line to list
390         let l:item = {'lnum': h.lnum, 'text': l:text, 'valid': 1, 'bufnr': h.bufnr, 'col': 1}
391         let l:indented_header_list = l:indented_header_list + [l:item]
392     endfor
393     call setloclist(0, l:indented_header_list)
394
395     if l:window_type ==# 'horizontal'
396         lopen
397     elseif l:window_type ==# 'vertical'
398         vertical lopen
399         " auto-fit toc window when possible to shrink it
400         if (&columns/2) > l:header_max_len && l:vim_markdown_toc_autofit == 1
401             " header_max_len + 1 space for first header + 3 spaces for line numbers
402             execute 'vertical resize ' . (l:header_max_len + 1 + 3)
403         else
404             execute 'vertical resize ' . (&columns/2)
405         endif
406     elseif l:window_type ==# 'tab'
407         tab lopen
408     else
409         lopen
410     endif
411     setlocal modifiable
412     for i in range(1, line('$'))
413         " this is the location-list data for the current item
414         let d = getloclist(0)[i-1]
415         call setline(i, d.text)
416     endfor
417     setlocal nomodified
418     setlocal nomodifiable
419     execute 'normal! ' . l:cursor_header . 'G'
420 endfunction
421
422 function! s:InsertToc(format, ...)
423     if a:0 > 0
424         if type(a:1) != type(0)
425             echohl WarningMsg
426             echomsg '[vim-markdown] Invalid argument, must be an integer >= 2.'
427             echohl None
428             return
429         endif
430         let l:max_level = a:1
431         if l:max_level < 2
432             echohl WarningMsg
433             echomsg '[vim-markdown] Maximum level cannot be smaller than 2.'
434             echohl None
435             return
436         endif
437     else
438         let l:max_level = 0
439     endif
440
441     let l:toc = []
442     let l:header_list = s:GetHeaderList()
443     if len(l:header_list) == 0
444         echom 'InsertToc: No headers.'
445         return
446     endif
447
448     if a:format ==# 'numbers'
449         let l:h2_count = 0
450         for header in l:header_list
451             if header.level == 2
452                 let l:h2_count += 1
453             endif
454         endfor
455         let l:max_h2_number_len = strlen(string(l:h2_count))
456     else
457         let l:max_h2_number_len = 0
458     endif
459
460     let l:h2_count = 0
461     for header in l:header_list
462         let l:level = header.level
463         if l:level == 1
464             " skip level-1 headers
465             continue
466         elseif l:max_level != 0 && l:level > l:max_level
467             " skip unwanted levels
468             continue
469         elseif l:level == 2
470             " list of level-2 headers can be bullets or numbers
471             if a:format ==# 'bullets'
472                 let l:indent = ''
473                 let l:marker = '* '
474             else
475                 let l:h2_count += 1
476                 let l:number_len = strlen(string(l:h2_count))
477                 let l:indent = repeat(' ', l:max_h2_number_len - l:number_len)
478                 let l:marker = l:h2_count . '. '
479             endif
480         else
481             let l:indent = repeat(' ', l:max_h2_number_len + 2 * (l:level - 2))
482             let l:marker = '* '
483         endif
484         let l:text = '[' . header.text . ']'
485         let l:link = '(#' . substitute(tolower(header.text), '\v[ ]+', '-', 'g') . ')'
486         let l:line = l:indent . l:marker . l:text . l:link
487         let l:toc = l:toc + [l:line]
488     endfor
489
490     call append(line('.'), l:toc)
491 endfunction
492
493 " Convert Setex headers in range `line1 .. line2` to Atx.
494 "
495 " Return the number of conversions.
496 "
497 function! s:SetexToAtx(line1, line2)
498     let l:originalNumLines = line('$')
499     execute 'silent! ' . a:line1 . ',' . a:line2 . 'substitute/\v(.*\S.*)\n\=+$/# \1/'
500
501     let l:changed = l:originalNumLines - line('$')
502     execute 'silent! ' . a:line1 . ',' . (a:line2 - l:changed) . 'substitute/\v(.*\S.*)\n-+$/## \1'
503     return l:originalNumLines - line('$')
504 endfunction
505
506 " If `a:1` is 0, decrease the level of all headers in range `line1 .. line2`.
507 "
508 " Otherwise, increase the level. `a:1` defaults to `0`.
509 "
510 function! s:HeaderDecrease(line1, line2, ...)
511     if a:0 > 0
512         let l:increase = a:1
513     else
514         let l:increase = 0
515     endif
516     if l:increase
517         let l:forbiddenLevel = 6
518         let l:replaceLevels = [5, 1]
519         let l:levelDelta = 1
520     else
521         let l:forbiddenLevel = 1
522         let l:replaceLevels = [2, 6]
523         let l:levelDelta = -1
524     endif
525     for l:line in range(a:line1, a:line2)
526         if join(getline(l:line, l:line + 1), "\n") =~ s:levelRegexpDict[l:forbiddenLevel]
527             echomsg 'There is an h' . l:forbiddenLevel . ' at line ' . l:line . '. Aborting.'
528             return
529         endif
530     endfor
531     let l:numSubstitutions = s:SetexToAtx(a:line1, a:line2)
532     let l:flags = (&gdefault ? '' : 'g')
533     for l:level in range(replaceLevels[0], replaceLevels[1], -l:levelDelta)
534         execute 'silent! ' . a:line1 . ',' . (a:line2 - l:numSubstitutions) . 'substitute/' . s:levelRegexpDict[l:level] . '/' . repeat('#', l:level + l:levelDelta) . '/' . l:flags
535     endfor
536 endfunction
537
538 " Format table under cursor.
539 "
540 " Depends on Tabularize.
541 "
542 function! s:TableFormat()
543     let l:pos = getpos('.')
544
545     if get(g:, 'vim_markdown_borderless_table', 0)
546       " add `|` to the beginning of the line if it isn't present
547       normal! {
548       call search('|')
549       execute 'silent .,''}s/\v^(\s{0,})\|?([^\|])/\1|\2/e'
550
551       " add `|` to the end of the line if it isn't present
552       normal! {
553       call search('|')
554       execute 'silent .,''}s/\v([^\|])\|?(\s{0,})$/\1|\2/e'
555     endif
556
557     normal! {
558     " Search instead of `normal! j` because of the table at beginning of file edge case.
559     call search('|')
560     normal! j
561     " Remove everything that is not a pipe, colon or hyphen next to a colon othewise
562     " well formated tables would grow because of addition of 2 spaces on the separator
563     " line by Tabularize /|.
564     let l:flags = (&gdefault ? '' : 'g')
565     execute 's/\(:\@<!-:\@!\|[^|:-]\)//e' . l:flags
566     execute 's/--/-/e' . l:flags
567     Tabularize /\(\\\)\@<!|
568     " Move colons for alignment to left or right side of the cell.
569     execute 's/:\( \+\)|/\1:|/e' . l:flags
570     execute 's/|\( \+\):/|:\1/e' . l:flags
571     execute 's/|:\?\zs[ -]\+\ze:\?|/\=repeat("-", len(submatch(0)))/' . l:flags
572     call setpos('.', l:pos)
573 endfunction
574
575 " Wrapper to do move commands in visual mode.
576 "
577 function! s:VisMove(f)
578     norm! gv
579     call function(a:f)()
580 endfunction
581
582 " Map in both normal and visual modes.
583 "
584 function! s:MapNormVis(rhs,lhs)
585     execute 'nn <buffer><silent> ' . a:rhs . ' :call ' . a:lhs . '()<cr>'
586     execute 'vn <buffer><silent> ' . a:rhs . ' <esc>:call <sid>VisMove(''' . a:lhs . ''')<cr>'
587 endfunction
588
589 " Parameters:
590 "
591 " - step +1 for right, -1 for left
592 "
593 " TODO: multiple lines.
594 "
595 function! s:FindCornerOfSyntax(lnum, col, step)
596     let l:col = a:col
597     let l:syn = synIDattr(synID(a:lnum, l:col, 1), 'name')
598     while synIDattr(synID(a:lnum, l:col, 1), 'name') ==# l:syn
599         let l:col += a:step
600     endwhile
601     return l:col - a:step
602 endfunction
603
604 " Return the next position of the given syntax name,
605 " inclusive on the given position.
606 "
607 " TODO: multiple lines
608 "
609 function! s:FindNextSyntax(lnum, col, name)
610     let l:col = a:col
611     let l:step = 1
612     while synIDattr(synID(a:lnum, l:col, 1), 'name') !=# a:name
613         let l:col += l:step
614     endwhile
615     return [a:lnum, l:col]
616 endfunction
617
618 function! s:FindCornersOfSyntax(lnum, col)
619     return [<sid>FindLeftOfSyntax(a:lnum, a:col), <sid>FindRightOfSyntax(a:lnum, a:col)]
620 endfunction
621
622 function! s:FindRightOfSyntax(lnum, col)
623     return <sid>FindCornerOfSyntax(a:lnum, a:col, 1)
624 endfunction
625
626 function! s:FindLeftOfSyntax(lnum, col)
627     return <sid>FindCornerOfSyntax(a:lnum, a:col, -1)
628 endfunction
629
630 " Returns:
631 "
632 " - a string with the the URL for the link under the cursor
633 " - an empty string if the cursor is not on a link
634 "
635 " TODO
636 "
637 " - multiline support
638 " - give an error if the separator does is not on a link
639 "
640 function! s:Markdown_GetUrlForPosition(lnum, col)
641     let l:lnum = a:lnum
642     let l:col = a:col
643     let l:syn = synIDattr(synID(l:lnum, l:col, 1), 'name')
644
645     if l:syn ==# 'mkdInlineURL' || l:syn ==# 'mkdURL' || l:syn ==# 'mkdLinkDefTarget'
646         " Do nothing.
647     elseif l:syn ==# 'mkdLink'
648         let [l:lnum, l:col] = <sid>FindNextSyntax(l:lnum, l:col, 'mkdURL')
649         let l:syn = 'mkdURL'
650     elseif l:syn ==# 'mkdDelimiter'
651         let l:line = getline(l:lnum)
652         let l:char = l:line[col - 1]
653         if l:char ==# '<'
654             let l:col += 1
655         elseif l:char ==# '>' || l:char ==# ')'
656             let l:col -= 1
657         elseif l:char ==# '[' || l:char ==# ']' || l:char ==# '('
658             let [l:lnum, l:col] = <sid>FindNextSyntax(l:lnum, l:col, 'mkdURL')
659         else
660             return ''
661         endif
662     else
663         return ''
664     endif
665
666     let [l:left, l:right] = <sid>FindCornersOfSyntax(l:lnum, l:col)
667     return getline(l:lnum)[l:left - 1 : l:right - 1]
668 endfunction
669
670 " Front end for GetUrlForPosition.
671 "
672 function! s:OpenUrlUnderCursor()
673     let l:url = s:Markdown_GetUrlForPosition(line('.'), col('.'))
674     if l:url !=# ''
675       if l:url =~? 'http[s]\?:\/\/[[:alnum:]%\/_#.-]*'
676         "Do nothing
677       else
678         let l:url = expand(expand('%:h').'/'.l:url)
679       endif
680       call s:VersionAwareNetrwBrowseX(l:url)
681     else
682         echomsg 'The cursor is not on a link.'
683     endif
684 endfunction
685
686 " We need a definition guard because we invoke 'edit' which will reload this
687 " script while this function is running. We must not replace it.
688 if !exists('*s:EditUrlUnderCursor')
689     function s:EditUrlUnderCursor()
690         let l:editmethod = ''
691         " determine how to open the linked file (split, tab, etc)
692         if exists('g:vim_markdown_edit_url_in')
693           if g:vim_markdown_edit_url_in ==# 'tab'
694             let l:editmethod = 'tabnew'
695           elseif g:vim_markdown_edit_url_in ==# 'vsplit'
696             let l:editmethod = 'vsp'
697           elseif g:vim_markdown_edit_url_in ==# 'hsplit'
698             let l:editmethod = 'sp'
699           else
700             let l:editmethod = 'edit'
701           endif
702         else
703           " default to current buffer
704           let l:editmethod = 'edit'
705         endif
706         let l:url = s:Markdown_GetUrlForPosition(line('.'), col('.'))
707         if l:url !=# ''
708             if get(g:, 'vim_markdown_autowrite', 0)
709                 write
710             endif
711             let l:anchor = ''
712             if get(g:, 'vim_markdown_follow_anchor', 0)
713                 let l:parts = split(l:url, '#', 1)
714                 if len(l:parts) == 2
715                     let [l:url, l:anchor] = parts
716                     let l:anchorexpr = get(g:, 'vim_markdown_anchorexpr', '')
717                     if l:anchorexpr !=# ''
718                         let l:anchor = eval(substitute(
719                             \ l:anchorexpr, 'v:anchor',
720                             \ escape('"'.l:anchor.'"', '"'), ''))
721                     endif
722                 endif
723             endif
724             if l:url !=# ''
725                 let l:ext = ''
726                 if get(g:, 'vim_markdown_no_extensions_in_markdown', 0)
727                     " use another file extension if preferred
728                     if exists('g:vim_markdown_auto_extension_ext')
729                         let l:ext = '.'.g:vim_markdown_auto_extension_ext
730                     else
731                         let l:ext = '.md'
732                     endif
733                 endif
734                 let l:url = fnameescape(fnamemodify(expand('%:h').'/'.l:url.l:ext, ':.'))
735                 execute l:editmethod l:url
736             endif
737             if l:anchor !=# ''
738                 call search(l:anchor, 's')
739             endif
740         else
741             execute l:editmethod . ' <cfile>'
742         endif
743     endfunction
744 endif
745
746 function! s:VersionAwareNetrwBrowseX(url)
747     if has('patch-7.4.567')
748         call netrw#BrowseX(a:url, 0)
749     else
750         call netrw#NetrwBrowseX(a:url, 0)
751     endif
752 endf
753
754 function! s:MapNotHasmapto(lhs, rhs)
755     if !hasmapto('<Plug>' . a:rhs)
756         execute 'nmap <buffer>' . a:lhs . ' <Plug>' . a:rhs
757         execute 'vmap <buffer>' . a:lhs . ' <Plug>' . a:rhs
758     endif
759 endfunction
760
761 call <sid>MapNormVis('<Plug>Markdown_MoveToNextHeader', '<sid>MoveToNextHeader')
762 call <sid>MapNormVis('<Plug>Markdown_MoveToPreviousHeader', '<sid>MoveToPreviousHeader')
763 call <sid>MapNormVis('<Plug>Markdown_MoveToNextSiblingHeader', '<sid>MoveToNextSiblingHeader')
764 call <sid>MapNormVis('<Plug>Markdown_MoveToPreviousSiblingHeader', '<sid>MoveToPreviousSiblingHeader')
765 call <sid>MapNormVis('<Plug>Markdown_MoveToParentHeader', '<sid>MoveToParentHeader')
766 call <sid>MapNormVis('<Plug>Markdown_MoveToCurHeader', '<sid>MoveToCurHeader')
767 nnoremap <Plug>Markdown_OpenUrlUnderCursor :call <sid>OpenUrlUnderCursor()<cr>
768 nnoremap <Plug>Markdown_EditUrlUnderCursor :call <sid>EditUrlUnderCursor()<cr>
769
770 if !get(g:, 'vim_markdown_no_default_key_mappings', 0)
771     call <sid>MapNotHasmapto(']]', 'Markdown_MoveToNextHeader')
772     call <sid>MapNotHasmapto('[[', 'Markdown_MoveToPreviousHeader')
773     call <sid>MapNotHasmapto('][', 'Markdown_MoveToNextSiblingHeader')
774     call <sid>MapNotHasmapto('[]', 'Markdown_MoveToPreviousSiblingHeader')
775     call <sid>MapNotHasmapto(']u', 'Markdown_MoveToParentHeader')
776     call <sid>MapNotHasmapto(']h', 'Markdown_MoveToCurHeader')
777     call <sid>MapNotHasmapto('gx', 'Markdown_OpenUrlUnderCursor')
778     call <sid>MapNotHasmapto('ge', 'Markdown_EditUrlUnderCursor')
779 endif
780
781 command! -buffer -range=% HeaderDecrease call s:HeaderDecrease(<line1>, <line2>)
782 command! -buffer -range=% HeaderIncrease call s:HeaderDecrease(<line1>, <line2>, 1)
783 command! -buffer -range=% SetexToAtx call s:SetexToAtx(<line1>, <line2>)
784 command! -buffer -range TableFormat call s:TableFormat()
785 command! -buffer Toc call s:Toc()
786 command! -buffer Toch call s:Toc('horizontal')
787 command! -buffer Tocv call s:Toc('vertical')
788 command! -buffer Toct call s:Toc('tab')
789 command! -buffer -nargs=? InsertToc call s:InsertToc('bullets', <args>)
790 command! -buffer -nargs=? InsertNToc call s:InsertToc('numbers', <args>)
791
792 " Heavily based on vim-notes - http://peterodding.com/code/vim/notes/
793 if exists('g:vim_markdown_fenced_languages')
794     let s:filetype_dict = {}
795     for s:filetype in g:vim_markdown_fenced_languages
796         let key = matchstr(s:filetype, '[^=]*')
797         let val = matchstr(s:filetype, '[^=]*$')
798         let s:filetype_dict[key] = val
799     endfor
800 else
801     let s:filetype_dict = {
802         \ 'c++': 'cpp',
803         \ 'viml': 'vim',
804         \ 'bash': 'sh',
805         \ 'ini': 'dosini'
806     \ }
807 endif
808
809 function! s:MarkdownHighlightSources(force)
810     " Syntax highlight source code embedded in notes.
811     " Look for code blocks in the current file
812     let filetypes = {}
813     for line in getline(1, '$')
814         let ft = matchstr(line, '\(`\{3,}\|\~\{3,}\)\s*\zs[0-9A-Za-z_+-]*\ze.*')
815         if !empty(ft) && ft !~# '^\d*$' | let filetypes[ft] = 1 | endif
816     endfor
817     if !exists('b:mkd_known_filetypes')
818         let b:mkd_known_filetypes = {}
819     endif
820     if !exists('b:mkd_included_filetypes')
821         " set syntax file name included
822         let b:mkd_included_filetypes = {}
823     endif
824     if !a:force && (b:mkd_known_filetypes == filetypes || empty(filetypes))
825         return
826     endif
827
828     " Now we're ready to actually highlight the code blocks.
829     let startgroup = 'mkdCodeStart'
830     let endgroup = 'mkdCodeEnd'
831     for ft in keys(filetypes)
832         if a:force || !has_key(b:mkd_known_filetypes, ft)
833             if has_key(s:filetype_dict, ft)
834                 let filetype = s:filetype_dict[ft]
835             else
836                 let filetype = ft
837             endif
838             let group = 'mkdSnippet' . toupper(substitute(filetype, '[+-]', '_', 'g'))
839             if !has_key(b:mkd_included_filetypes, filetype)
840                 let include = s:SyntaxInclude(filetype)
841                 let b:mkd_included_filetypes[filetype] = 1
842             else
843                 let include = '@' . toupper(filetype)
844             endif
845             let command_backtick = 'syntax region %s matchgroup=%s start="^\s*`\{3,}\s*%s.*$" matchgroup=%s end="\s*`\{3,}$" keepend contains=%s%s'
846             let command_tilde    = 'syntax region %s matchgroup=%s start="^\s*\~\{3,}\s*%s.*$" matchgroup=%s end="\s*\~\{3,}$" keepend contains=%s%s'
847             execute printf(command_backtick, group, startgroup, ft, endgroup, include, has('conceal') && get(g:, 'vim_markdown_conceal', 1) && get(g:, 'vim_markdown_conceal_code_blocks', 1) ? ' concealends' : '')
848             execute printf(command_tilde,    group, startgroup, ft, endgroup, include, has('conceal') && get(g:, 'vim_markdown_conceal', 1) && get(g:, 'vim_markdown_conceal_code_blocks', 1) ? ' concealends' : '')
849             execute printf('syntax cluster mkdNonListItem add=%s', group)
850
851             let b:mkd_known_filetypes[ft] = 1
852         endif
853     endfor
854 endfunction
855
856 function! s:SyntaxInclude(filetype)
857     " Include the syntax highlighting of another {filetype}.
858     let grouplistname = '@' . toupper(a:filetype)
859     " Unset the name of the current syntax while including the other syntax
860     " because some syntax scripts do nothing when "b:current_syntax" is set
861     if exists('b:current_syntax')
862         let syntax_save = b:current_syntax
863         unlet b:current_syntax
864     endif
865     try
866         execute 'syntax include' grouplistname 'syntax/' . a:filetype . '.vim'
867         execute 'syntax include' grouplistname 'after/syntax/' . a:filetype . '.vim'
868     catch /E484/
869         " Ignore missing scripts
870     endtry
871     " Restore the name of the current syntax
872     if exists('syntax_save')
873         let b:current_syntax = syntax_save
874     elseif exists('b:current_syntax')
875         unlet b:current_syntax
876     endif
877     return grouplistname
878 endfunction
879
880 function! s:IsHighlightSourcesEnabledForBuffer()
881     " Enable for markdown buffers, and for liquid buffers with markdown format
882     return &filetype =~# 'markdown' || get(b:, 'liquid_subtype', '') =~# 'markdown'
883 endfunction
884
885 function! s:MarkdownRefreshSyntax(force)
886     " Use != to compare &syntax's value to use the same logic run on
887     " $VIMRUNTIME/syntax/synload.vim.
888     "
889     " vint: next-line -ProhibitEqualTildeOperator
890     if s:IsHighlightSourcesEnabledForBuffer() && line('$') > 1 && &syntax != 'OFF'
891         call s:MarkdownHighlightSources(a:force)
892     endif
893 endfunction
894
895 function! s:MarkdownClearSyntaxVariables()
896     if s:IsHighlightSourcesEnabledForBuffer()
897         unlet! b:mkd_included_filetypes
898     endif
899 endfunction
900
901 augroup Mkd
902     " These autocmd calling s:MarkdownRefreshSyntax need to be kept in sync with
903     " the autocmds calling s:MarkdownSetupFolding in after/ftplugin/markdown.vim.
904     autocmd! * <buffer>
905     autocmd BufWinEnter <buffer> call s:MarkdownRefreshSyntax(1)
906     autocmd BufUnload <buffer> call s:MarkdownClearSyntaxVariables()
907     autocmd BufWritePost <buffer> call s:MarkdownRefreshSyntax(0)
908     autocmd InsertEnter,InsertLeave <buffer> call s:MarkdownRefreshSyntax(0)
909     autocmd CursorHold,CursorHoldI <buffer> call s:MarkdownRefreshSyntax(0)
910 augroup END