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