]> git.madduck.net Git - etc/vim.git/blob - ftplugin/mkd.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 pull request #71 from cirosantilli/toc
[etc/vim.git] / ftplugin / mkd.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:
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 let s:levelRegexpDict = {
50     \ 1: '\v^(\s*#[^#]|.+\n\=+$)',
51     \ 2: '\v^(\s*##[^#]|.+\n-+$)',
52     \ 3: '\v^\s*###[^#]',
53     \ 4: '\v^\s*####[^#]',
54     \ 5: '\v^\s*#####[^#]',
55     \ 6: '\v^\s*######[^#]'
56 \ }
57
58 " Maches any header level of any type.
59 "
60 " This could be deduced from `s:levelRegexpDict`, but it is more
61 " efficient to have a single regexp for this.
62 "
63 let s:headersRegexp = '\v^(\s*#|.+\n(\=+|-+)$)'
64
65 " Returns the line number of the first header before `line`, called the
66 " current header.
67 "
68 " If there is no current header, return `0`.
69 "
70 " @param a:1 The line to look the header of. Default value: `getpos('.')`.
71 "
72 function! b:Markdown_GetHeaderLineNum(...)
73     if a:0 == 0
74         let l:l = line('.')
75     else
76         let l:l = a:1
77     endif
78     while(l:l > 0)
79         if join(getline(l:l, l:l + 1), "\n") =~ s:headersRegexp
80             return l:l
81         endif
82         let l:l -= 1
83     endwhile
84     return 0
85 endfunction
86
87 " - if inside a header goes to it.
88 "    Return its line number.
89 "
90 " - if on top level outside any headers,
91 "    print a warning
92 "    Return `0`.
93 "
94 function! b:Markdown_MoveToCurHeader()
95     let l:lineNum = b:Markdown_GetHeaderLineNum()
96     if l:lineNum != 0
97         call cursor(l:lineNum, 1)
98     else
99         echo 'outside any header'
100         "normal! gg
101     endif
102     return l:lineNum
103 endfunction
104
105 " Move cursor to next header of any level.
106 "
107 " If there are no more headers, print a warning.
108 "
109 function! b:Markdown_MoveToNextHeader()
110     if search(s:headersRegexp, 'W') == 0
111         "normal! G
112         echo 'no next header'
113     endif
114 endfunction
115
116 " Move cursor to previous header (before current) of any level.
117 "
118 " If it does not exist, print a warning.
119 "
120 function! b:Markdown_MoveToPreviousHeader()
121     let l:curHeaderLineNumber = b:Markdown_GetHeaderLineNum()
122     let l:noPreviousHeader = 0
123     if l:curHeaderLineNumber <= 1
124         let l:noPreviousHeader = 1
125     else
126         let l:previousHeaderLineNumber = b:Markdown_GetHeaderLineNum(l:curHeaderLineNumber - 1)
127         if l:previousHeaderLineNumber == 0
128             let l:noPreviousHeader = 1
129         else
130             call cursor(l:previousHeaderLineNumber, 1)
131         endif
132     endif
133     if l:noPreviousHeader
134         echo 'no previous header'
135     endif
136 endfunction
137
138 " - if line is inside a header, return the header level (h1 -> 1, h2 -> 2, etc.).
139 "
140 " - if line is at top level outside any headers, return `0`.
141 "
142 function! b:Markdown_GetHeaderLevel(...)
143     if a:0 == 0
144         let l:line = line('.')
145     else
146         let l:line = a:1
147     endif
148     let l:linenum = b:Markdown_GetHeaderLineNum(l:line)
149     if l:linenum != 0
150         return b:Markdown_GetLevelOfHeaderAtLine(l:linenum)
151     else
152         return 0
153     endif
154 endfunction
155
156 " Returns the level of the header at the given line.
157 "
158 " If there is no header at the given line, returns `0`.
159 "
160 function! b:Markdown_GetLevelOfHeaderAtLine(linenum)
161     let l:lines = join(getline(a:linenum, a:linenum + 1), "\n")
162     for l:key in keys(s:levelRegexpDict)
163         if l:lines =~ get(s:levelRegexpDict, l:key)
164             return l:key
165         endif
166     endfor
167     return 0
168 endfunction
169
170 " Move cursor to parent header of the current header.
171 "
172 " If it does not exit, print a warning and do nothing.
173 "
174 function! b:Markdown_MoveToParentHeader()
175     let l:linenum = b:Markdown_GetParentHeaderLineNumber()
176     if l:linenum != 0
177         call cursor(l:linenum, 1)
178     else
179         echo 'no parent header'
180     endif
181 endfunction
182
183 " Return the line number of the parent header of line `line`.
184 "
185 " If it has no parent, return `0`.
186 "
187 function! b:Markdown_GetParentHeaderLineNumber(...)
188     if a:0 == 0
189         let l:line = line('.')
190     else
191         let l:line = a:1
192     endif
193     let l:level = b:Markdown_GetHeaderLevel(l:line)
194     if l:level > 1
195         let l:linenum = b:Markdown_GetPreviousHeaderLineNumberAtLevel(l:level - 1, l:line)
196         return l:linenum
197     endif
198     return 0
199 endfunction
200
201 " Return the line number of the previous header of given level.
202 " in relation to line `a:1`. If not given, `a:1 = getline()`
203 "
204 " `a:1` line is included, and this may return the current header.
205 "
206 " If none return 0.
207 "
208 function! b:Markdown_GetNextHeaderLineNumberAtLevel(level, ...)
209     if a:0 < 1
210         let l:line = line('.')
211     else
212         let l:line = a:1
213     endif
214     let l:l = l:line
215     while(l:l <= line('$'))
216         if join(getline(l:l, l:l + 1), "\n") =~ get(s:levelRegexpDict, a:level)
217             return l:l
218         endif
219         let l:l += 1
220     endwhile
221     return 0
222 endfunction
223
224 " Return the line number of the previous header of given level.
225 " in relation to line `a:1`. If not given, `a:1 = getline()`
226 "
227 " `a:1` line is included, and this may return the current header.
228 "
229 " If none return 0.
230 "
231 function! b:Markdown_GetPreviousHeaderLineNumberAtLevel(level, ...)
232     if a:0 == 0
233         let l:line = line('.')
234     else
235         let l:line = a:1
236     endif
237     let l:l = l:line
238     while(l:l > 0)
239         if join(getline(l:l, l:l + 1), "\n") =~ get(s:levelRegexpDict, a:level)
240             return l:l
241         endif
242         let l:l -= 1
243     endwhile
244     return 0
245 endfunction
246
247 " Move cursor to next sibling header.
248 "
249 " If there is no next siblings, print a warning and don't move.
250 "
251 function! b:Markdown_MoveToNextSiblingHeader()
252     let l:curHeaderLineNumber = b:Markdown_GetHeaderLineNum()
253     let l:curHeaderLevel = b:Markdown_GetLevelOfHeaderAtLine(l:curHeaderLineNumber)
254     let l:curHeaderParentLineNumber = b:Markdown_GetParentHeaderLineNumber()
255     let l:nextHeaderSameLevelLineNumber = b:Markdown_GetNextHeaderLineNumberAtLevel(l:curHeaderLevel, l:curHeaderLineNumber + 1)
256     let l:noNextSibling = 0
257     if l:nextHeaderSameLevelLineNumber == 0
258         let l:noNextSibling = 1
259     else
260         let l:nextHeaderSameLevelParentLineNumber = b:Markdown_GetParentHeaderLineNumber(l:nextHeaderSameLevelLineNumber) 
261         if l:curHeaderParentLineNumber == l:nextHeaderSameLevelParentLineNumber
262             call cursor(l:nextHeaderSameLevelLineNumber, 1)
263         else
264             let l:noNextSibling = 1
265         endif
266     endif
267     if l:noNextSibling
268         echo 'no next sibling header'
269     endif
270 endfunction
271
272 " Move cursor to previous sibling header.
273 "
274 " If there is no previous siblings, print a warning and do nothing.
275 "
276 function! b:Markdown_MoveToPreviousSiblingHeader()
277     let l:curHeaderLineNumber = b:Markdown_GetHeaderLineNum()
278     let l:curHeaderLevel = b:Markdown_GetLevelOfHeaderAtLine(l:curHeaderLineNumber)
279     let l:curHeaderParentLineNumber = b:Markdown_GetParentHeaderLineNumber()
280     let l:previousHeaderSameLevelLineNumber = b:Markdown_GetPreviousHeaderLineNumberAtLevel(l:curHeaderLevel, l:curHeaderLineNumber - 1)
281     let l:noPreviousSibling = 0
282     if l:previousHeaderSameLevelLineNumber == 0
283         let l:noPreviousSibling = 1
284     else
285         let l:previousHeaderSameLevelParentLineNumber = b:Markdown_GetParentHeaderLineNumber(l:previousHeaderSameLevelLineNumber) 
286         if l:curHeaderParentLineNumber == l:previousHeaderSameLevelParentLineNumber
287             call cursor(l:previousHeaderSameLevelLineNumber, 1)
288         else
289             let l:noPreviousSibling = 1
290         endif
291     endif
292     if l:noPreviousSibling
293         echo 'no previous sibling header'
294     endif
295 endfunction
296
297 function! b:Markdown_Toc(...)
298     if a:0 > 0
299         let l:window_type = a:1
300     else
301         let l:window_type = 'vertical'
302     endif
303     silent vimgrep '^#' %
304     if l:window_type ==# 'horizontal'
305         copen
306     elseif l:window_type ==# 'vertical'
307         vertical copen
308         let &winwidth=(&columns/2)
309     elseif l:window_type ==# 'tab'
310         tab copen
311     else
312         copen
313     endif
314     set modifiable
315     %s/\v^([^|]*\|){2,2} #//
316     for i in range(1, line('$'))
317         let l:line = getline(i)
318         let l:header =  matchstr(l:line, '^#*')
319         let l:length = len(l:header)
320         let l:line = substitute(l:line, '\v^#*[ ]*', '', '')
321         let l:line = substitute(l:line, '\v[ ]*#*$', '', '')
322         let l:line = repeat(' ', (2 * l:length)) . l:line
323         call setline(i, l:line)
324     endfor
325     set nomodified
326     set nomodifiable
327     normal! gg
328 endfunction
329
330 " Wrapper to do move commands in visual mode.
331 "
332 function! s:VisMove(f)
333     norm! gv
334     call function(a:f)()
335 endfunction
336
337 " Map in both normal and visual modes.
338 "
339 function! s:MapNormVis(rhs,lhs)
340     execute 'nn <buffer><silent> ' . a:rhs . ' :call ' . a:lhs . '()<cr>'
341     execute 'vn <buffer><silent> ' . a:rhs . ' <esc>:call <sid>VisMove(''' . a:lhs . ''')<cr>'
342 endfunction
343
344 call <sid>MapNormVis(']]', 'b:Markdown_MoveToNextHeader')
345 call <sid>MapNormVis('[[', 'b:Markdown_MoveToPreviousHeader')
346 call <sid>MapNormVis('][', 'b:Markdown_MoveToNextSiblingHeader')
347 call <sid>MapNormVis('[]', 'b:Markdown_MoveToPreviousSiblingHeader')
348 " Menmonic: Up
349 call <sid>MapNormVis(']u', 'b:Markdown_MoveToParentHeader')
350 " Menmonic: Current
351 call <sid>MapNormVis(']c', 'b:Markdown_MoveToCurHeader')
352
353 command! -buffer Toc call b:Markdown_Toc()
354 command! -buffer Toch call b:Markdown_Toc('horizontal')
355 command! -buffer Tocv call b:Markdown_Toc('vertical')
356 command! -buffer Toct call b:Markdown_Toc('tab')