ocaml.vim 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. " Language: OCaml
  2. " Maintainer: David Baelde <firstname.name@ens-lyon.org>
  3. " Mike Leary <leary@nwlink.com>
  4. " Markus Mottl <markus.mottl@gmail.com>
  5. " Pierre Vittet <pierre-vittet@pvittet.com>
  6. " Stefano Zacchiroli <zack@bononia.it>
  7. " Vincent Aravantinos <firstname.name@imag.fr>
  8. " Riley Bruins <ribru17@gmail.com> ('commentstring')
  9. " URL: https://github.com/ocaml/vim-ocaml
  10. " Last Change:
  11. " 2013 Oct 27 - Added commentstring (MM)
  12. " 2013 Jul 26 - load default compiler settings (MM)
  13. " 2013 Jul 24 - removed superfluous efm-setting (MM)
  14. " 2013 Jul 22 - applied fixes supplied by Hirotaka Hamada (MM)
  15. " 2024 May 23 - added space in commentstring (RB)
  16. if exists("b:did_ftplugin")
  17. finish
  18. endif
  19. let b:did_ftplugin=1
  20. " Use standard compiler settings unless user wants otherwise
  21. if !exists("current_compiler")
  22. :compiler ocaml
  23. endif
  24. " some macro
  25. if exists('*fnameescape')
  26. function! s:Fnameescape(s)
  27. return fnameescape(a:s)
  28. endfun
  29. else
  30. function! s:Fnameescape(s)
  31. return escape(a:s," \t\n*?[{`$\\%#'\"|!<")
  32. endfun
  33. endif
  34. " Error handling -- helps moving where the compiler wants you to go
  35. let s:cposet=&cpoptions
  36. set cpo&vim
  37. " Comment string
  38. setlocal comments=sr:(*\ ,mb:\ ,ex:*)
  39. setlocal comments^=sr:(**,mb:\ \ ,ex:*)
  40. setlocal commentstring=(*\ %s\ *)
  41. " Add mappings, unless the user didn't want this.
  42. if !exists("no_plugin_maps") && !exists("no_ocaml_maps")
  43. " (un)commenting
  44. if !hasmapto('<Plug>Comment')
  45. nmap <buffer> <LocalLeader>c <Plug>LUncomOn
  46. xmap <buffer> <LocalLeader>c <Plug>BUncomOn
  47. nmap <buffer> <LocalLeader>C <Plug>LUncomOff
  48. xmap <buffer> <LocalLeader>C <Plug>BUncomOff
  49. endif
  50. nnoremap <buffer> <Plug>LUncomOn gI(* <End> *)<ESC>
  51. nnoremap <buffer> <Plug>LUncomOff :s/^(\* \(.*\) \*)/\1/<CR>:noh<CR>
  52. xnoremap <buffer> <Plug>BUncomOn <ESC>:'<,'><CR>`<O<ESC>0i(*<ESC>`>o<ESC>0i*)<ESC>`<
  53. xnoremap <buffer> <Plug>BUncomOff <ESC>:'<,'><CR>`<dd`>dd`<
  54. nmap <buffer> <LocalLeader>s <Plug>OCamlSwitchEdit
  55. nmap <buffer> <LocalLeader>S <Plug>OCamlSwitchNewWin
  56. nmap <buffer> <LocalLeader>t <Plug>OCamlPrintType
  57. xmap <buffer> <LocalLeader>t <Plug>OCamlPrintType
  58. endif
  59. " Let % jump between structure elements (due to Issac Trotts)
  60. let b:mw = '\<let\>:\<and\>:\(\<in\>\|;;\)'
  61. let b:mw = b:mw . ',\<if\>:\<then\>:\<else\>'
  62. let b:mw = b:mw . ',\<\(for\|while\)\>:\<do\>:\<done\>'
  63. let b:mw = b:mw . ',\<\(object\|sig\|struct\|begin\)\>:\<end\>'
  64. let b:mw = b:mw . ',\<\(match\|try\)\>:\<with\>'
  65. let b:match_words = b:mw
  66. let b:match_ignorecase=0
  67. function! s:OcpGrep(bang,args) abort
  68. let grepprg = &l:grepprg
  69. let grepformat = &l:grepformat
  70. let shellpipe = &shellpipe
  71. try
  72. let &l:grepprg = "ocp-grep -c never"
  73. setlocal grepformat=%f:%l:%m
  74. if &shellpipe ==# '2>&1| tee' || &shellpipe ==# '|& tee'
  75. let &shellpipe = "| tee"
  76. endif
  77. execute 'grep! '.a:args
  78. if empty(a:bang) && !empty(getqflist())
  79. return 'cfirst'
  80. else
  81. return ''
  82. endif
  83. finally
  84. let &l:grepprg = grepprg
  85. let &l:grepformat = grepformat
  86. let &shellpipe = shellpipe
  87. endtry
  88. endfunction
  89. command! -bar -bang -complete=file -nargs=+ Ocpgrep exe s:OcpGrep(<q-bang>, <q-args>)
  90. " switching between interfaces (.mli) and implementations (.ml)
  91. if !exists("g:did_ocaml_switch")
  92. let g:did_ocaml_switch = 1
  93. nnoremap <Plug>OCamlSwitchEdit :<C-u>call OCaml_switch(0)<CR>
  94. nnoremap <Plug>OCamlSwitchNewWin :<C-u>call OCaml_switch(1)<CR>
  95. fun OCaml_switch(newwin)
  96. if (match(bufname(""), "\\.mli$") >= 0)
  97. let fname = s:Fnameescape(substitute(bufname(""), "\\.mli$", ".ml", ""))
  98. if (a:newwin == 1)
  99. exec "new " . fname
  100. else
  101. exec "arge " . fname
  102. endif
  103. elseif (match(bufname(""), "\\.ml$") >= 0)
  104. let fname = s:Fnameescape(bufname("")) . "i"
  105. if (a:newwin == 1)
  106. exec "new " . fname
  107. else
  108. exec "arge " . fname
  109. endif
  110. endif
  111. endfun
  112. endif
  113. " Folding support
  114. " Get the modeline because folding depends on indentation
  115. let lnum = search('^\s*(\*:o\?caml:', 'n')
  116. let s:modeline = lnum? getline(lnum): ""
  117. " Get the indentation params
  118. let s:m = matchstr(s:modeline,'default\s*=\s*\d\+')
  119. if s:m != ""
  120. let s:idef = matchstr(s:m,'\d\+')
  121. elseif exists("g:omlet_indent")
  122. let s:idef = g:omlet_indent
  123. else
  124. let s:idef = 2
  125. endif
  126. let s:m = matchstr(s:modeline,'struct\s*=\s*\d\+')
  127. if s:m != ""
  128. let s:i = matchstr(s:m,'\d\+')
  129. elseif exists("g:omlet_indent_struct")
  130. let s:i = g:omlet_indent_struct
  131. else
  132. let s:i = s:idef
  133. endif
  134. " Set the folding method
  135. if exists("g:ocaml_folding")
  136. setlocal foldmethod=expr
  137. setlocal foldexpr=OMLetFoldLevel(v:lnum)
  138. endif
  139. let b:undo_ftplugin = "setlocal efm< foldmethod< foldexpr<"
  140. \ . "| unlet! b:mw b:match_words b:match_ignorecase"
  141. " - Only definitions below, executed once -------------------------------------
  142. if exists("*OMLetFoldLevel")
  143. let &cpoptions = s:cposet
  144. unlet s:cposet
  145. finish
  146. endif
  147. function s:topindent(lnum)
  148. let l = a:lnum
  149. while l > 0
  150. if getline(l) =~ '\s*\%(\<struct\>\|\<sig\>\|\<object\>\)'
  151. return indent(l)
  152. endif
  153. let l = l-1
  154. endwhile
  155. return -s:i
  156. endfunction
  157. function OMLetFoldLevel(l)
  158. " This is for not merging blank lines around folds to them
  159. if getline(a:l) !~ '\S'
  160. return -1
  161. endif
  162. " We start folds for modules, classes, and every toplevel definition
  163. if getline(a:l) =~ '^\s*\%(\<val\>\|\<module\>\|\<class\>\|\<type\>\|\<method\>\|\<initializer\>\|\<inherit\>\|\<exception\>\|\<external\>\)'
  164. exe 'return ">' (indent(a:l)/s:i)+1 '"'
  165. endif
  166. " Toplevel let are detected thanks to the indentation
  167. if getline(a:l) =~ '^\s*let\>' && indent(a:l) == s:i+s:topindent(a:l)
  168. exe 'return ">' (indent(a:l)/s:i)+1 '"'
  169. endif
  170. " We close fold on end which are associated to struct, sig or object.
  171. " We use syntax information to do that.
  172. if getline(a:l) =~ '^\s*end\>' && synIDattr(synID(a:l, indent(a:l)+1, 0), "name") != "ocamlKeyword"
  173. return (indent(a:l)/s:i)+1
  174. endif
  175. " Folds end on ;;
  176. if getline(a:l) =~ '^\s*;;'
  177. exe 'return "<' (indent(a:l)/s:i)+1 '"'
  178. endif
  179. " Comments around folds aren't merged to them.
  180. if synIDattr(synID(a:l, indent(a:l)+1, 0), "name") == "ocamlComment"
  181. return -1
  182. endif
  183. return '='
  184. endfunction
  185. " Vim support for OCaml .annot files
  186. "
  187. " Last Change: 2007 Jul 17
  188. " Maintainer: Vincent Aravantinos <vincent.aravantinos@gmail.com>
  189. " License: public domain
  190. "
  191. " Originally inspired by 'ocaml-dtypes.vim' by Stefano Zacchiroli.
  192. " The source code is quite radically different for we not use python anymore.
  193. " However this plugin should have the exact same behaviour, that's why the
  194. " following lines are the quite exact copy of Stefano's original plugin :
  195. "
  196. " <<
  197. " Executing Ocaml_print_type(<mode>) function will display in the Vim bottom
  198. " line(s) the type of an ocaml value getting it from the corresponding .annot
  199. " file (if any). If Vim is in visual mode, <mode> should be "visual" and the
  200. " selected ocaml value correspond to the highlighted text, otherwise (<mode>
  201. " can be anything else) it corresponds to the literal found at the current
  202. " cursor position.
  203. "
  204. " Typing '<LocalLeader>t' (LocalLeader defaults to '\', see :h LocalLeader)
  205. " will cause " Ocaml_print_type function to be invoked with the right
  206. " argument depending on the current mode (visual or not).
  207. " >>
  208. "
  209. " If you find something not matching this behaviour, please signal it.
  210. "
  211. " Differences are:
  212. " - no need for python support
  213. " + plus : more portable
  214. " + minus: no more lazy parsing, it looks very fast however
  215. "
  216. " - ocamlbuild support, ie.
  217. " + the plugin finds the _build directory and looks for the
  218. " corresponding file inside;
  219. " + if the user decides to change the name of the _build directory thanks
  220. " to the '-build-dir' option of ocamlbuild, the plugin will manage in
  221. " most cases to find it out (most cases = if the source file has a unique
  222. " name among your whole project);
  223. " + if ocamlbuild is not used, the usual behaviour holds; ie. the .annot
  224. " file should be in the same directory as the source file;
  225. " + for vim plugin programmers:
  226. " the variable 'b:_build_dir' contains the inferred path to the build
  227. " directory, even if this one is not named '_build'.
  228. "
  229. " Bonus :
  230. " - latin1 accents are handled
  231. " - lists are handled, even on multiple lines, you don't need the visual mode
  232. " (the cursor must be on the first bracket)
  233. " - parenthesized expressions, arrays, and structures (ie. '(...)', '[|...|]',
  234. " and '{...}') are handled the same way
  235. " Copied from Stefano's original plugin :
  236. " <<
  237. " .annot ocaml file representation
  238. "
  239. " File format (copied verbatim from caml-types.el)
  240. "
  241. " file ::= block *
  242. " block ::= position <SP> position <LF> annotation *
  243. " position ::= filename <SP> num <SP> num <SP> num
  244. " annotation ::= keyword open-paren <LF> <SP> <SP> data <LF> close-paren
  245. "
  246. " <SP> is a space character (ASCII 0x20)
  247. " <LF> is a line-feed character (ASCII 0x0A)
  248. " num is a sequence of decimal digits
  249. " filename is a string with the lexical conventions of O'Caml
  250. " open-paren is an open parenthesis (ASCII 0x28)
  251. " close-paren is a closed parenthesis (ASCII 0x29)
  252. " data is any sequence of characters where <LF> is always followed by
  253. " at least two space characters.
  254. "
  255. " - in each block, the two positions are respectively the start and the
  256. " end of the range described by the block.
  257. " - in a position, the filename is the name of the file, the first num
  258. " is the line number, the second num is the offset of the beginning
  259. " of the line, the third num is the offset of the position itself.
  260. " - the char number within the line is the difference between the third
  261. " and second nums.
  262. "
  263. " For the moment, the only possible keyword is \"type\"."
  264. " >>
  265. " 1. Finding the annotation file even if we use ocamlbuild
  266. " In: two strings representing paths
  267. " Out: one string representing the common prefix between the two paths
  268. function! s:Find_common_path (p1,p2)
  269. let temp = a:p2
  270. while matchstr(a:p1,temp) == ''
  271. let temp = substitute(temp,'/[^/]*$','','')
  272. endwhile
  273. return temp
  274. endfun
  275. " After call:
  276. "
  277. " Following information have been put in s:annot_file_list, using
  278. " annot_file_name name as key:
  279. " - annot_file_path :
  280. " path to the .annot file corresponding to the
  281. " source file (dealing with ocamlbuild stuff)
  282. " - _build_path:
  283. " path to the build directory even if this one is
  284. " not named '_build'
  285. " - date_of_last annot:
  286. " Set to 0 until we load the file. It contains the
  287. " date at which the file has been loaded.
  288. function! s:Locate_annotation()
  289. let annot_file_name = s:Fnameescape(expand('%:t:r')).'.annot'
  290. if !exists ("s:annot_file_list[annot_file_name]")
  291. silent exe 'cd' s:Fnameescape(expand('%:p:h'))
  292. " 1st case : the annot file is in the same directory as the buffer (no ocamlbuild)
  293. let annot_file_path = findfile(annot_file_name,'.')
  294. if annot_file_path != ''
  295. let annot_file_path = getcwd().'/'.annot_file_path
  296. let _build_path = ''
  297. else
  298. " 2nd case : the buffer and the _build directory are in the same directory
  299. " ..
  300. " / \
  301. " / \
  302. " _build .ml
  303. "
  304. let _build_path = finddir('_build','.')
  305. if _build_path != ''
  306. let _build_path = getcwd().'/'._build_path
  307. let annot_file_path = findfile(annot_file_name,'_build')
  308. if annot_file_path != ''
  309. let annot_file_path = getcwd().'/'.annot_file_path
  310. endif
  311. else
  312. " 3rd case : the _build directory is in a directory higher in the file hierarchy
  313. " (it can't be deeper by ocamlbuild requirements)
  314. " ..
  315. " / \
  316. " / \
  317. " _build ...
  318. " \
  319. " \
  320. " .ml
  321. "
  322. let _build_path = finddir('_build',';')
  323. if _build_path != ''
  324. let project_path = substitute(_build_path,'/_build$','','')
  325. let path_relative_to_project = s:Fnameescape(substitute(expand('%:p:h'),project_path.'/','',''))
  326. let annot_file_path = findfile(annot_file_name,project_path.'/_build/'.path_relative_to_project)
  327. else
  328. let annot_file_path = findfile(annot_file_name,'**')
  329. "4th case : what if the user decided to change the name of the _build directory ?
  330. " -> we relax the constraints, it should work in most cases
  331. if annot_file_path != ''
  332. " 4a. we suppose the renamed _build directory is in the current directory
  333. let _build_path = matchstr(annot_file_path,'^[^/]*')
  334. if annot_file_path != ''
  335. let annot_file_path = getcwd().'/'.annot_file_path
  336. let _build_path = getcwd().'/'._build_path
  337. endif
  338. else
  339. let annot_file_name = ''
  340. "(Pierre Vittet: I have commented 4b because this was crashing
  341. "my vim (it produced infinite loop))
  342. "
  343. " 4b. anarchy : the renamed _build directory may be higher in the hierarchy
  344. " this will work if the file for which we are looking annotations has a unique name in the whole project
  345. " if this is not the case, it may still work, but no warranty here
  346. "let annot_file_path = findfile(annot_file_name,'**;')
  347. "let project_path = s:Find_common_path(annot_file_path,expand('%:p:h'))
  348. "let _build_path = matchstr(annot_file_path,project_path.'/[^/]*')
  349. endif
  350. endif
  351. endif
  352. endif
  353. if annot_file_path == ''
  354. throw 'E484: no annotation file found'
  355. endif
  356. silent exe 'cd' '-'
  357. let s:annot_file_list[annot_file_name]= [annot_file_path, _build_path, 0]
  358. endif
  359. endfun
  360. " This variable contains a dictionary of lists. Each element of the dictionary
  361. " represents an annotation system. An annotation system is a list with:
  362. " - annotation file name as its key
  363. " - annotation file path as first element of the contained list
  364. " - build path as second element of the contained list
  365. " - annot_file_last_mod (contain the date of .annot file) as third element
  366. let s:annot_file_list = {}
  367. " 2. Finding the type information in the annotation file
  368. " a. The annotation file is opened in vim as a buffer that
  369. " should be (almost) invisible to the user.
  370. " After call:
  371. " The current buffer is now the one containing the .annot file.
  372. " We manage to keep all this hidden to the user's eye.
  373. function! s:Enter_annotation_buffer(annot_file_path)
  374. let s:current_pos = getpos('.')
  375. let s:current_hidden = &l:hidden
  376. set hidden
  377. let s:current_buf = bufname('%')
  378. if bufloaded(a:annot_file_path)
  379. silent exe 'keepj keepalt' 'buffer' s:Fnameescape(a:annot_file_path)
  380. else
  381. silent exe 'keepj keepalt' 'view' s:Fnameescape(a:annot_file_path)
  382. endif
  383. call setpos(".", [0, 0 , 0 , 0])
  384. endfun
  385. " After call:
  386. " The original buffer has been restored in the exact same state as before.
  387. function! s:Exit_annotation_buffer()
  388. silent exe 'keepj keepalt' 'buffer' s:Fnameescape(s:current_buf)
  389. let &l:hidden = s:current_hidden
  390. call setpos('.',s:current_pos)
  391. endfun
  392. " After call:
  393. " The annot file is loaded and assigned to a buffer.
  394. " This also handles the modification date of the .annot file, eg. after a
  395. " compilation (return an updated annot_file_list).
  396. function! s:Load_annotation(annot_file_name)
  397. let annot = s:annot_file_list[a:annot_file_name]
  398. let annot_file_path = annot[0]
  399. let annot_file_last_mod = 0
  400. if exists("annot[2]")
  401. let annot_file_last_mod = annot[2]
  402. endif
  403. if bufloaded(annot_file_path) && annot_file_last_mod < getftime(annot_file_path)
  404. " if there is a more recent file
  405. let nr = bufnr(annot_file_path)
  406. silent exe 'keepj keepalt' 'bunload' nr
  407. endif
  408. if !bufloaded(annot_file_path)
  409. call s:Enter_annotation_buffer(annot_file_path)
  410. setlocal nobuflisted
  411. setlocal bufhidden=hide
  412. setlocal noswapfile
  413. setlocal buftype=nowrite
  414. call s:Exit_annotation_buffer()
  415. let annot[2] = getftime(annot_file_path)
  416. " List updated with the new date
  417. let s:annot_file_list[a:annot_file_name] = annot
  418. endif
  419. endfun
  420. "b. 'search' and 'match' work to find the type information
  421. "In: - lin1,col1: position of expression first char
  422. " - lin2,col2: position of expression last char
  423. "Out: - the pattern to be looked for to find the block
  424. " Must be called in the source buffer (use of line2byte)
  425. function! s:Block_pattern(lin1,lin2,col1,col2)
  426. let start_num1 = a:lin1
  427. let start_num2 = line2byte(a:lin1) - 1
  428. let start_num3 = start_num2 + a:col1
  429. let path = '"\(\\"\|[^"]\)\+"'
  430. let start_pos = path.' '.start_num1.' '.start_num2.' '.start_num3
  431. let end_num1 = a:lin2
  432. let end_num2 = line2byte(a:lin2) - 1
  433. let end_num3 = end_num2 + a:col2
  434. let end_pos = path.' '.end_num1.' '.end_num2.' '.end_num3
  435. return '^'.start_pos.' '.end_pos."$"
  436. " rq: the '^' here is not totally correct regarding the annot file "grammar"
  437. " but currently the annotation file respects this, and it's a little bit faster with the '^';
  438. " can be removed safely.
  439. endfun
  440. "In: (the cursor position should be at the start of an annotation)
  441. "Out: the type information
  442. " Must be called in the annotation buffer (use of search)
  443. function! s:Match_data()
  444. " rq: idem as previously, in the following, the '^' at start of patterns is not necessary
  445. keepj while search('^type($','ce',line(".")) == 0
  446. keepj if search('^.\{-}($','e') == 0
  447. throw "no_annotation"
  448. endif
  449. keepj if searchpair('(','',')') == 0
  450. throw "malformed_annot_file"
  451. endif
  452. endwhile
  453. let begin = line(".") + 1
  454. keepj if searchpair('(','',')') == 0
  455. throw "malformed_annot_file"
  456. endif
  457. let end = line(".") - 1
  458. return join(getline(begin,end),"\n")
  459. endfun
  460. "In: the pattern to look for in order to match the block
  461. "Out: the type information (calls s:Match_data)
  462. " Should be called in the annotation buffer
  463. function! s:Extract_type_data(block_pattern, annot_file_name)
  464. let annot_file_path = s:annot_file_list[a:annot_file_name][0]
  465. call s:Enter_annotation_buffer(annot_file_path)
  466. try
  467. if search(a:block_pattern,'e') == 0
  468. throw "no_annotation"
  469. endif
  470. call cursor(line(".") + 1,1)
  471. let annotation = s:Match_data()
  472. finally
  473. call s:Exit_annotation_buffer()
  474. endtry
  475. return annotation
  476. endfun
  477. "c. link this stuff with what the user wants
  478. " ie. get the expression selected/under the cursor
  479. let s:ocaml_word_char = '\w|[\xc0-\xff]|'''
  480. "In: the current mode (eg. "visual", "normal", etc.)
  481. "Out: the borders of the expression we are looking for the type
  482. function! s:Match_borders(mode)
  483. if a:mode == "visual"
  484. let cur = getpos(".")
  485. normal `<
  486. let col1 = col(".")
  487. let lin1 = line(".")
  488. normal `>
  489. let col2 = col(".")
  490. let lin2 = line(".")
  491. call cursor(cur[1],cur[2])
  492. return [lin1,lin2,col1-1,col2]
  493. else
  494. let cursor_line = line(".")
  495. let cursor_col = col(".")
  496. let line = getline('.')
  497. if line[cursor_col-1:cursor_col] == '[|'
  498. let [lin2,col2] = searchpairpos('\[|','','|\]','n')
  499. return [cursor_line,lin2,cursor_col-1,col2+1]
  500. elseif line[cursor_col-1] == '['
  501. let [lin2,col2] = searchpairpos('\[','','\]','n')
  502. return [cursor_line,lin2,cursor_col-1,col2]
  503. elseif line[cursor_col-1] == '('
  504. let [lin2,col2] = searchpairpos('(','',')','n')
  505. return [cursor_line,lin2,cursor_col-1,col2]
  506. elseif line[cursor_col-1] == '{'
  507. let [lin2,col2] = searchpairpos('{','','}','n')
  508. return [cursor_line,lin2,cursor_col-1,col2]
  509. else
  510. let [lin1,col1] = searchpos('\v%('.s:ocaml_word_char.'|\.)*','ncb')
  511. let [lin2,col2] = searchpos('\v%('.s:ocaml_word_char.'|\.)*','nce')
  512. if col1 == 0 || col2 == 0
  513. throw "no_expression"
  514. endif
  515. return [cursor_line,cursor_line,col1-1,col2]
  516. endif
  517. endif
  518. endfun
  519. "In: the current mode (eg. "visual", "normal", etc.)
  520. "Out: the type information (calls s:Extract_type_data)
  521. function! s:Get_type(mode, annot_file_name)
  522. let [lin1,lin2,col1,col2] = s:Match_borders(a:mode)
  523. return s:Extract_type_data(s:Block_pattern(lin1,lin2,col1,col2), a:annot_file_name)
  524. endfun
  525. "In: A string destined to be printed in the 'echo buffer'. It has line
  526. "break and 2 space at each line beginning.
  527. "Out: A string destined to be yanked, without space and double space.
  528. function s:unformat_ocaml_type(res)
  529. "Remove end of line.
  530. let res = substitute (a:res, "\n", "", "g" )
  531. "remove double space
  532. let res =substitute(res , " ", " ", "g")
  533. "remove space at beginning of string.
  534. let res = substitute(res, "^ *", "", "g")
  535. return res
  536. endfunction
  537. "d. main
  538. "In: the current mode (eg. "visual", "normal", etc.)
  539. "After call: the type information is displayed
  540. if !exists("*Ocaml_get_type")
  541. function Ocaml_get_type(mode)
  542. let annot_file_name = s:Fnameescape(expand('%:t:r')).'.annot'
  543. call s:Locate_annotation()
  544. call s:Load_annotation(annot_file_name)
  545. let res = s:Get_type(a:mode, annot_file_name)
  546. " Copy result in the unnamed buffer
  547. let @" = s:unformat_ocaml_type(res)
  548. return res
  549. endfun
  550. endif
  551. if !exists("*Ocaml_get_type_or_not")
  552. function Ocaml_get_type_or_not(mode)
  553. let t=reltime()
  554. try
  555. let res = Ocaml_get_type(a:mode)
  556. return res
  557. catch
  558. return ""
  559. endtry
  560. endfun
  561. endif
  562. if !exists("*Ocaml_print_type")
  563. function Ocaml_print_type(mode)
  564. if expand("%:e") == "mli"
  565. echohl ErrorMsg | echo "No annotations for interface (.mli) files" | echohl None
  566. return
  567. endif
  568. try
  569. echo Ocaml_get_type(a:mode)
  570. catch /E484:/
  571. echohl ErrorMsg | echo "No type annotations (.annot) file found" | echohl None
  572. catch /no_expression/
  573. echohl ErrorMsg | echo "No expression found under the cursor" | echohl None
  574. catch /no_annotation/
  575. echohl ErrorMsg | echo "No type annotation found for the given text" | echohl None
  576. catch /malformed_annot_file/
  577. echohl ErrorMsg | echo "Malformed .annot file" | echohl None
  578. endtry
  579. endfun
  580. endif
  581. " Maps
  582. nnoremap <silent> <Plug>OCamlPrintType :<C-U>call Ocaml_print_type("normal")<CR>
  583. xnoremap <silent> <Plug>OCamlPrintType :<C-U>call Ocaml_print_type("visual")<CR>`<
  584. let &cpoptions = s:cposet
  585. unlet s:cposet
  586. " vim:sw=2 fdm=indent