neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

ocaml.vim (23890B)


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