hypertext.vim 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. " vim: set fdm=marker et ts=4 sw=4 sts=4:
  2. function! pandoc#hypertext#Init() abort
  3. if !exists('g:pandoc#hypertext#open_editable_alternates')
  4. let g:pandoc#hypertext#open_editable_alternates = 1
  5. endif
  6. if !exists('g:pandoc#hypertext#editable_alternates_extensions')
  7. let g:pandoc#hypertext#editable_alternates_extensions ='\(pdf\|htm\|odt\|doc\)'
  8. endif
  9. if !exists('g:pandoc#hypertext#create_if_no_alternates_exists')
  10. let g:pandoc#hypertext#create_if_no_alternates_exists = 0
  11. endif
  12. if !exists('g:pandoc#hypertext#split_open_cmd')
  13. let g:pandoc#hypertext#split_open_cmd = 'botright vsplit'
  14. endif
  15. if !exists('g:pandoc#hypertext#edit_open_cmd')
  16. let g:pandoc#hypertext#edit_open_cmd = 'edit'
  17. endif
  18. if !exists('g:pandoc#hypertext#preferred_alternate')
  19. let g:pandoc#hypertext#preferred_alternate = 'md'
  20. endif
  21. if !exists('g:pandoc#hypertext#use_default_mappings')
  22. let g:pandoc#hypertext#use_default_mappings = 1
  23. endif
  24. if !exists('g:pandoc#hypertext#autosave_on_edit_open_link')
  25. let g:pandoc#hypertext#autosave_on_edit_open_link = 0
  26. endif
  27. if exists('g:pandoc#hypertext#automatic_link_regex')
  28. let s:automatic_link_regex = g:pandoc#hypertext#automatic_link_regex
  29. else
  30. let s:automatic_link_regex = '\v\<([^>]+)\>'
  31. endif
  32. if exists('g:pandoc#hypertext#inline_link_regex')
  33. let s:inline_link_regex = g:pandoc#hypertext#inline_link_regex
  34. else
  35. let s:inline_link_regex = '\v!?\[[^]]+\]\(([^) \t]+).*\)'
  36. endif
  37. if exists('g:pandoc#hypertext#reference_link_regex')
  38. let s:reference_link_regex = g:pandoc#hypertext#reference_link_regex
  39. else
  40. let s:reference_link_regex = '\v!?\[[^]]+\]%(\[([^]]*)\])?'
  41. endif
  42. if exists('g:pandoc#hypertext#referenced_link_regex')
  43. let s:referenced_link_regex = g:pandoc#hypertext#referenced_link_regex
  44. else
  45. let s:referenced_link_regex = '\v\[[^]]+\]:\s*([^\t ]+)'
  46. endif
  47. nnoremap <silent> <buffer> <Plug>(pandoc-hypertext-open-local) :<C-u>call pandoc#hypertext#OpenLocal()<cr>
  48. nnoremap <silent> <buffer> <Plug>(pandoc-hypertext-open-system) :<C-u>call pandoc#hypertext#OpenSystem()<cr>
  49. nnoremap <silent> <buffer> <Plug>(pandoc-hypertext-goto-id) :<C-u>call pandoc#hypertext#GotoID()<cr>
  50. if g:pandoc#hypertext#use_default_mappings == 1
  51. nmap <buffer> gf <Plug>(pandoc-hypertext-open-local)
  52. nmap <buffer> gx <Plug>(pandoc-hypertext-open-system)
  53. nmap <buffer> <localleader>xi <Plug>(pandoc-hypertext-goto-id)
  54. endif
  55. endfunction
  56. function! s:IsEditable(path) abort
  57. let exts = []
  58. for type_exts in values(g:pandoc_extensions_table)
  59. for ext in type_exts
  60. call add(exts, fnamemodify('*.'.ext, ':e'))
  61. endfor
  62. endfor
  63. if index(exts, fnamemodify(a:path, ':e')) > -1
  64. return 1
  65. endif
  66. return 0
  67. endfunction
  68. function! s:SortAlternates(a, b) abort
  69. let ext = fnamemodify(a:a, ':e')
  70. if ext ==? g:pandoc#hypertext#preferred_alternate
  71. " return 1 will cass the preferred on at the last
  72. return -1
  73. endif
  74. " return 0 will cause others before the preferred
  75. return 1
  76. endfunction
  77. function! s:FindAlternates(path) abort
  78. let candidates = glob(fnamemodify(a:path, ':r').'.*', 0, 1)
  79. if candidates !=# []
  80. return filter(candidates, 's:IsEditable(v:val) && v:val !=# glob(a:path)')
  81. endif
  82. return []
  83. endfunction
  84. function! s:ExtendedCFILE() abort
  85. let orig_isfname = &isfname
  86. let &isfname = orig_isfname . ',?,&,:'
  87. let addr = expand('<cfile>')
  88. let &isfname = orig_isfname
  89. return addr
  90. endfunction
  91. function! pandoc#hypertext#PushLink(link) abort
  92. if !exists('w:link_stack')
  93. let w:link_stack = []
  94. endif
  95. let w:link_stack = add(w:link_stack, a:link)
  96. endfunction
  97. function! pandoc#hypertext#PopLink() abort
  98. if exists('w:link_stack')
  99. try
  100. let link = remove(w:link_stack,-1)
  101. catch
  102. return ['','']
  103. endtry
  104. else
  105. return ['','']
  106. endif
  107. return link
  108. endfunction
  109. " MatchstrAtCursor(pattern)
  110. " Returns part of the line that matches pattern at cursor
  111. " Copied from vimwiki plugin: autoload/base.vim
  112. function! s:MatchstrAtCursor(pattern) abort "{{{1
  113. let col = col('.') - 1
  114. let line = getline('.')
  115. let ebeg = -1
  116. let cont = match(line, a:pattern, 0)
  117. while (ebeg >= 0 || (0 <= cont) && (cont <= col))
  118. let contn = matchend(line, a:pattern, cont)
  119. if (cont <= col) && (col < contn)
  120. let ebeg = match(line, a:pattern, cont)
  121. let elen = contn - ebeg
  122. break
  123. else
  124. let cont = match(line, a:pattern, contn)
  125. endif
  126. endwh
  127. if ebeg >= 0
  128. return strpart(line, ebeg, elen)
  129. else
  130. return ''
  131. endif
  132. endfunc " }}}1
  133. " GetLinkAtCursor(pat)
  134. " get the specified type of link at cursor
  135. function! s:GetLinkAtCursor(pat) abort "{{{1
  136. let matched_str = s:MatchstrAtCursor(a:pat)
  137. let url = substitute(matched_str, a:pat, '\1', 'g')
  138. let indices = [1, 2]
  139. if '!' ==# matched_str[:0]
  140. let indices = [2, 3]
  141. endif
  142. if '][]' ==# matched_str[-3:]
  143. let indices[1] += 2
  144. endif
  145. " for [hypertext][]
  146. if url ==# ''
  147. let url = strpart(matched_str, indices[0], strlen(matched_str) - indices[1])
  148. endif
  149. return url
  150. endfunc " }}}1
  151. function! s:GetReferenceUrl(ref) abort
  152. let pattern = '\v\c\s*\['.a:ref.'\]:\s*([^ ]+)'
  153. let linenum = search(pattern, 'nw')
  154. let url = substitute(getline(linenum), pattern, '\1', 'g')
  155. " if no reference URL exists, fall back to an implicit header reference
  156. if url ==# ''
  157. let url = '#' . markdown#headers#GetAutomaticID(a:ref)
  158. endif
  159. return url
  160. endfunction
  161. " GetLinkAddress
  162. " get the link at cursor
  163. function! pandoc#hypertext#GetLinkAddress() abort
  164. let link = s:GetLinkAtCursor(s:automatic_link_regex)
  165. " get link at cursor
  166. if link ==# ''
  167. let link = s:GetLinkAtCursor(s:inline_link_regex)
  168. if link ==# ''
  169. let link = s:GetLinkAtCursor(s:reference_link_regex)
  170. if link ==# ''
  171. let link = s:GetLinkAtCursor(s:referenced_link_regex)
  172. else
  173. " find the reference link
  174. let link = s:GetReferenceUrl(link)
  175. endif
  176. endif
  177. endif
  178. return link
  179. endfunction
  180. function! pandoc#hypertext#OpenFileWithCmd(file, cmd, push) abort
  181. if a:cmd ==# g:pandoc#hypertext#edit_open_cmd
  182. if &modified ==# 1 &&
  183. \ g:pandoc#hypertext#autosave_on_edit_open_link == 1
  184. exe 'write'
  185. endif
  186. if a:push == 1
  187. call pandoc#hypertext#PushLink( ['file', expand('%:p')] )
  188. endif
  189. endif
  190. exe a:cmd . ' ' . a:file
  191. endfunction
  192. function! pandoc#hypertext#OpenLocal(...) abort
  193. if exists('a:1')
  194. let addr = a:1
  195. else
  196. let addr = s:ExtendedCFILE()
  197. endif
  198. if exists('a:2')
  199. let cmd = a:2
  200. else
  201. let cmd = g:pandoc#hypertext#split_open_cmd
  202. endif
  203. if g:pandoc#hypertext#open_editable_alternates == 1
  204. let ext = fnamemodify(addr, ':e')
  205. if ext =~ g:pandoc#hypertext#editable_alternates_extensions
  206. let alt_addrs = s:FindAlternates(addr)
  207. if alt_addrs !=# []
  208. let pos = 0
  209. if len(alt_addrs) > 1
  210. let alt_file = fnamemodify(addr, ':r') . '.' . g:pandoc#hypertext#preferred_alternate
  211. let pos = index(alt_addrs, glob(alt_file))
  212. if pos == -1
  213. let pos = 0
  214. endif
  215. endif
  216. let addr = alt_addrs[pos]
  217. else
  218. " check weather to create the alternate file
  219. if g:pandoc#hypertext#create_if_no_alternates_exists == 1
  220. let dir = fnamemodify(addr, ':p:h')
  221. if !isdirectory(dir)
  222. call system('mkdir -p '.dir)
  223. endif
  224. let addr = fnamemodify(addr, ':r') . '.' . g:pandoc#hypertext#preferred_alternate
  225. call pandoc#hypertext#OpenFileWithCmd(addr, cmd, 1)
  226. else
  227. call pandoc#hypertext#OpenSystem(addr)
  228. endif
  229. return
  230. endif
  231. endif
  232. endif
  233. if glob(addr) !=# '' || g:pandoc#hypertext#create_if_no_alternates_exists == 1
  234. call pandoc#hypertext#OpenFileWithCmd(addr, cmd, 1)
  235. endif
  236. endfunction
  237. function! pandoc#hypertext#OpenSystem(...) abort
  238. if exists('a:1')
  239. let addr = a:1
  240. else
  241. let addr = s:ExtendedCFILE()
  242. endif
  243. if has('unix') && executable('xdg-open')
  244. call system('xdg-open '. shellescape(expand(addr,':p')))
  245. elseif has('win32') || has('win64')
  246. call system('cmd /c "start '. addr .'"')
  247. elseif has('macunix')
  248. call system('open '. addr)
  249. endif
  250. endfunction
  251. function! pandoc#hypertext#OpenLink(cmd) abort
  252. let url = pandoc#hypertext#GetLinkAddress()
  253. let ext = fnamemodify(url, ':e')
  254. let hashnum = match(url, '#')
  255. let sname = synIDattr(synID(line('.'), col('.'), 0), 'name')
  256. let curpos = getcurpos()
  257. let pos = getpos('.')
  258. let curfile = expand('%:p')
  259. if hashnum != -1 && hashnum != 0
  260. let id = url[hashnum + 1:]
  261. let url = url[:hashnum - 1]
  262. let ext = fnamemodify(url, ':e')
  263. endif
  264. if '#' ==# url[:0]
  265. call pandoc#hypertext#GotoID(url[1:], sname, curpos, pos)
  266. elseif ext =~ g:pandoc#hypertext#editable_alternates_extensions || s:IsEditable(url)
  267. call pandoc#hypertext#OpenLocal(url, a:cmd)
  268. if hashnum != -1
  269. call pandoc#hypertext#GotoID(id, sname, curpos, pos)
  270. call pandoc#hypertext#PushLink( ['file', curfile] )
  271. endif
  272. else
  273. call pandoc#hypertext#OpenSystem(url)
  274. endif
  275. endfunction
  276. function! pandoc#hypertext#BackFromLink() abort
  277. let [type, link] = pandoc#hypertext#PopLink()
  278. if type ==# 'position'
  279. call setpos('.', link)
  280. elseif type ==# 'file'
  281. call pandoc#hypertext#OpenFileWithCmd(link, g:pandoc#hypertext#edit_open_cmd, 0)
  282. endif
  283. endfunction
  284. function! pandoc#hypertext#BackFromFile() abort
  285. if exists('w:link_stack')
  286. let types = map(reverse(copy(w:link_stack)), 'v:val[0]')
  287. let i = index(types, 'file')
  288. if i != -1
  289. let w:link_stack = w:link_stack[0:-i-1]
  290. call pandoc#hypertext#BackFromLink()
  291. endif
  292. endif
  293. endfunction
  294. function! pandoc#hypertext#GotoID(...) abort
  295. if a:2 !=# 'pandocHeaderID'
  296. if exists('a:1')
  297. let id = a:1
  298. else
  299. let id = expand('<cfile>')
  300. endif
  301. try
  302. " call pandoc#hypertext#PushLink( ['position',getcurpos()] )
  303. call pandoc#hypertext#PushLink( ['position',a:3] )
  304. catch /E117/
  305. " call pandoc#hypertext#PushLink( ['position',getpos('.')] )
  306. call pandoc#hypertext#PushLink( ['position', a:4] )
  307. endtry
  308. " header indentifier
  309. let header_pos = markdown#headers#GetAllIDs()
  310. let line = get(header_pos, id)
  311. if line > 0
  312. call cursor(line, 1)
  313. else
  314. call pandoc#hypertext#BackFromLink()
  315. endif
  316. endif
  317. endfunction