xref: /vim-8.2.3635/runtime/ftplugin/ocaml.vim (revision 6c391a74)
1071d4279SBram Moolenaar" Language:    OCaml
2a5792f58SBram Moolenaar" Maintainer:  David Baelde        <[email protected]>
3a5792f58SBram Moolenaar"              Mike Leary          <[email protected]>
4a5792f58SBram Moolenaar"              Markus Mottl        <[email protected]>
5b8ff1fb5SBram Moolenaar"              Pierre Vittet       <[email protected]>
65eb86f91SBram Moolenaar"              Stefano Zacchiroli  <[email protected]>
73577c6faSBram Moolenaar"              Vincent Aravantinos <[email protected]>
87e6a515eSBram Moolenaar" URL:         https://github.com/ocaml/vim-ocaml
9b8ff1fb5SBram Moolenaar" Last Change:
10790c18bfSBram Moolenaar"              2013 Oct 27 - Added commentstring (MM)
1116ea3676SBram Moolenaar"              2013 Jul 26 - load default compiler settings (MM)
1216ea3676SBram Moolenaar"              2013 Jul 24 - removed superfluous efm-setting (MM)
1316ea3676SBram Moolenaar"              2013 Jul 22 - applied fixes supplied by Hirotaka Hamada (MM)
14b8ff1fb5SBram Moolenaar
154c3f536fSBram Moolenaarif exists("b:did_ftplugin")
164c3f536fSBram Moolenaar  finish
174c3f536fSBram Moolenaarendif
18071d4279SBram Moolenaarlet b:did_ftplugin=1
19071d4279SBram Moolenaar
2016ea3676SBram Moolenaar" Use standard compiler settings unless user wants otherwise
2116ea3676SBram Moolenaarif !exists("current_compiler")
2216ea3676SBram Moolenaar  :compiler ocaml
2316ea3676SBram Moolenaarendif
2416ea3676SBram Moolenaar
25e37d50a5SBram Moolenaar" some macro
26e37d50a5SBram Moolenaarif exists('*fnameescape')
27e37d50a5SBram Moolenaar  function! s:Fnameescape(s)
28e37d50a5SBram Moolenaar    return fnameescape(a:s)
29e37d50a5SBram Moolenaar  endfun
30e37d50a5SBram Moolenaarelse
31e37d50a5SBram Moolenaar  function! s:Fnameescape(s)
32e37d50a5SBram Moolenaar    return escape(a:s," \t\n*?[{`$\\%#'\"|!<")
33e37d50a5SBram Moolenaar  endfun
34e37d50a5SBram Moolenaarendif
35e37d50a5SBram Moolenaar
36a5792f58SBram Moolenaar" Error handling -- helps moving where the compiler wants you to go
37a5792f58SBram Moolenaarlet s:cposet=&cpoptions
38f1568ecaSBram Moolenaarset cpo&vim
39071d4279SBram Moolenaar
40790c18bfSBram Moolenaar" Comment string
417e6a515eSBram Moolenaarsetlocal comments=sr:(*\ ,mb:\ ,ex:*)
427e6a515eSBram Moolenaarsetlocal comments^=sr:(**,mb:\ \ ,ex:*)
43790c18bfSBram Moolenaarsetlocal commentstring=(*%s*)
44790c18bfSBram Moolenaar
45071d4279SBram Moolenaar" Add mappings, unless the user didn't want this.
46071d4279SBram Moolenaarif !exists("no_plugin_maps") && !exists("no_ocaml_maps")
47a5792f58SBram Moolenaar  " (un)commenting
48071d4279SBram Moolenaar  if !hasmapto('<Plug>Comment')
49071d4279SBram Moolenaar    nmap <buffer> <LocalLeader>c <Plug>LUncomOn
5016ea3676SBram Moolenaar    xmap <buffer> <LocalLeader>c <Plug>BUncomOn
51071d4279SBram Moolenaar    nmap <buffer> <LocalLeader>C <Plug>LUncomOff
5216ea3676SBram Moolenaar    xmap <buffer> <LocalLeader>C <Plug>BUncomOff
53071d4279SBram Moolenaar  endif
54071d4279SBram Moolenaar
5516ea3676SBram Moolenaar  nnoremap <buffer> <Plug>LUncomOn gI(* <End> *)<ESC>
56a5792f58SBram Moolenaar  nnoremap <buffer> <Plug>LUncomOff :s/^(\* \(.*\) \*)/\1/<CR>:noh<CR>
5716ea3676SBram Moolenaar  xnoremap <buffer> <Plug>BUncomOn <ESC>:'<,'><CR>`<O<ESC>0i(*<ESC>`>o<ESC>0i*)<ESC>`<
5816ea3676SBram Moolenaar  xnoremap <buffer> <Plug>BUncomOff <ESC>:'<,'><CR>`<dd`>dd`<
59071d4279SBram Moolenaar
6016ea3676SBram Moolenaar  nmap <buffer> <LocalLeader>s <Plug>OCamlSwitchEdit
6116ea3676SBram Moolenaar  nmap <buffer> <LocalLeader>S <Plug>OCamlSwitchNewWin
6216ea3676SBram Moolenaar
6316ea3676SBram Moolenaar  nmap <buffer> <LocalLeader>t <Plug>OCamlPrintType
6416ea3676SBram Moolenaar  xmap <buffer> <LocalLeader>t <Plug>OCamlPrintType
65071d4279SBram Moolenaarendif
665eb86f91SBram Moolenaar
675eb86f91SBram Moolenaar" Let % jump between structure elements (due to Issac Trotts)
68790c18bfSBram Moolenaarlet b:mw =         '\<let\>:\<and\>:\(\<in\>\|;;\)'
69a5792f58SBram Moolenaarlet b:mw = b:mw . ',\<if\>:\<then\>:\<else\>'
70790c18bfSBram Moolenaarlet b:mw = b:mw . ',\<\(for\|while\)\>:\<do\>:\<done\>'
71a5792f58SBram Moolenaarlet b:mw = b:mw . ',\<\(object\|sig\|struct\|begin\)\>:\<end\>'
72a5792f58SBram Moolenaarlet b:mw = b:mw . ',\<\(match\|try\)\>:\<with\>'
735eb86f91SBram Moolenaarlet b:match_words = b:mw
745eb86f91SBram Moolenaar
75a5792f58SBram Moolenaarlet b:match_ignorecase=0
76a5792f58SBram Moolenaar
77790c18bfSBram Moolenaarfunction! s:OcpGrep(bang,args) abort
78790c18bfSBram Moolenaar  let grepprg = &l:grepprg
79790c18bfSBram Moolenaar  let grepformat = &l:grepformat
80790c18bfSBram Moolenaar  let shellpipe = &shellpipe
81790c18bfSBram Moolenaar  try
82790c18bfSBram Moolenaar    let &l:grepprg = "ocp-grep -c never"
83790c18bfSBram Moolenaar    setlocal grepformat=%f:%l:%m
84790c18bfSBram Moolenaar    if &shellpipe ==# '2>&1| tee' || &shellpipe ==# '|& tee'
85790c18bfSBram Moolenaar      let &shellpipe = "| tee"
86790c18bfSBram Moolenaar    endif
87790c18bfSBram Moolenaar    execute 'grep! '.a:args
88790c18bfSBram Moolenaar    if empty(a:bang) && !empty(getqflist())
89790c18bfSBram Moolenaar      return 'cfirst'
90790c18bfSBram Moolenaar    else
91790c18bfSBram Moolenaar      return ''
92790c18bfSBram Moolenaar    endif
93790c18bfSBram Moolenaar  finally
94790c18bfSBram Moolenaar    let &l:grepprg = grepprg
95790c18bfSBram Moolenaar    let &l:grepformat = grepformat
96790c18bfSBram Moolenaar    let &shellpipe = shellpipe
97790c18bfSBram Moolenaar  endtry
98790c18bfSBram Moolenaarendfunction
99790c18bfSBram Moolenaarcommand! -bar -bang -complete=file -nargs=+ Ocpgrep exe s:OcpGrep(<q-bang>, <q-args>)
100790c18bfSBram Moolenaar
1015eb86f91SBram Moolenaar" switching between interfaces (.mli) and implementations (.ml)
1025eb86f91SBram Moolenaarif !exists("g:did_ocaml_switch")
1035eb86f91SBram Moolenaar  let g:did_ocaml_switch = 1
10416ea3676SBram Moolenaar  nnoremap <Plug>OCamlSwitchEdit :<C-u>call OCaml_switch(0)<CR>
10516ea3676SBram Moolenaar  nnoremap <Plug>OCamlSwitchNewWin :<C-u>call OCaml_switch(1)<CR>
1065eb86f91SBram Moolenaar  fun OCaml_switch(newwin)
1075eb86f91SBram Moolenaar    if (match(bufname(""), "\\.mli$") >= 0)
108e37d50a5SBram Moolenaar      let fname = s:Fnameescape(substitute(bufname(""), "\\.mli$", ".ml", ""))
1095eb86f91SBram Moolenaar      if (a:newwin == 1)
1105eb86f91SBram Moolenaar        exec "new " . fname
1115eb86f91SBram Moolenaar      else
1125eb86f91SBram Moolenaar        exec "arge " . fname
1135eb86f91SBram Moolenaar      endif
1145eb86f91SBram Moolenaar    elseif (match(bufname(""), "\\.ml$") >= 0)
115e37d50a5SBram Moolenaar      let fname = s:Fnameescape(bufname("")) . "i"
1165eb86f91SBram Moolenaar      if (a:newwin == 1)
1175eb86f91SBram Moolenaar        exec "new " . fname
1185eb86f91SBram Moolenaar      else
1195eb86f91SBram Moolenaar        exec "arge " . fname
1205eb86f91SBram Moolenaar      endif
1215eb86f91SBram Moolenaar    endif
1225eb86f91SBram Moolenaar  endfun
1235eb86f91SBram Moolenaarendif
1245eb86f91SBram Moolenaar
125a5792f58SBram Moolenaar" Folding support
126a5792f58SBram Moolenaar
127a5792f58SBram Moolenaar" Get the modeline because folding depends on indentation
128790c18bfSBram Moolenaarlet lnum = search('^\s*(\*:o\?caml:', 'n')
129790c18bfSBram Moolenaarlet s:modeline = lnum? getline(lnum): ""
130a5792f58SBram Moolenaar
131a5792f58SBram Moolenaar" Get the indentation params
132a5792f58SBram Moolenaarlet s:m = matchstr(s:modeline,'default\s*=\s*\d\+')
133a5792f58SBram Moolenaarif s:m != ""
134a5792f58SBram Moolenaar  let s:idef = matchstr(s:m,'\d\+')
135a5792f58SBram Moolenaarelseif exists("g:omlet_indent")
136a5792f58SBram Moolenaar  let s:idef = g:omlet_indent
137a5792f58SBram Moolenaarelse
138a5792f58SBram Moolenaar  let s:idef = 2
139a5792f58SBram Moolenaarendif
140a5792f58SBram Moolenaarlet s:m = matchstr(s:modeline,'struct\s*=\s*\d\+')
141a5792f58SBram Moolenaarif s:m != ""
142a5792f58SBram Moolenaar  let s:i = matchstr(s:m,'\d\+')
143a5792f58SBram Moolenaarelseif exists("g:omlet_indent_struct")
144a5792f58SBram Moolenaar  let s:i = g:omlet_indent_struct
145a5792f58SBram Moolenaarelse
146a5792f58SBram Moolenaar  let s:i = s:idef
147a5792f58SBram Moolenaarendif
148a5792f58SBram Moolenaar
149a5792f58SBram Moolenaar" Set the folding method
150a5792f58SBram Moolenaarif exists("g:ocaml_folding")
151a5792f58SBram Moolenaar  setlocal foldmethod=expr
152a5792f58SBram Moolenaar  setlocal foldexpr=OMLetFoldLevel(v:lnum)
153a5792f58SBram Moolenaarendif
154a5792f58SBram Moolenaar
15516ea3676SBram Moolenaarlet b:undo_ftplugin = "setlocal efm< foldmethod< foldexpr<"
15616ea3676SBram Moolenaar	\ . "| unlet! b:mw b:match_words b:match_ignorecase"
15716ea3676SBram Moolenaar
15816ea3676SBram Moolenaar
159a5792f58SBram Moolenaar" - Only definitions below, executed once -------------------------------------
160a5792f58SBram Moolenaar
161a5792f58SBram Moolenaarif exists("*OMLetFoldLevel")
162e0e39175SBram Moolenaar  let &cpoptions = s:cposet
163e0e39175SBram Moolenaar  unlet s:cposet
164a5792f58SBram Moolenaar  finish
165a5792f58SBram Moolenaarendif
166a5792f58SBram Moolenaar
167a5792f58SBram Moolenaarfunction s:topindent(lnum)
168a5792f58SBram Moolenaar  let l = a:lnum
169a5792f58SBram Moolenaar  while l > 0
170a5792f58SBram Moolenaar    if getline(l) =~ '\s*\%(\<struct\>\|\<sig\>\|\<object\>\)'
171a5792f58SBram Moolenaar      return indent(l)
172a5792f58SBram Moolenaar    endif
173a5792f58SBram Moolenaar    let l = l-1
174a5792f58SBram Moolenaar  endwhile
175a5792f58SBram Moolenaar  return -s:i
176a5792f58SBram Moolenaarendfunction
177a5792f58SBram Moolenaar
178a5792f58SBram Moolenaarfunction OMLetFoldLevel(l)
179a5792f58SBram Moolenaar
180a5792f58SBram Moolenaar  " This is for not merging blank lines around folds to them
181a5792f58SBram Moolenaar  if getline(a:l) !~ '\S'
182a5792f58SBram Moolenaar    return -1
183a5792f58SBram Moolenaar  endif
184a5792f58SBram Moolenaar
185a5792f58SBram Moolenaar  " We start folds for modules, classes, and every toplevel definition
186a5792f58SBram Moolenaar  if getline(a:l) =~ '^\s*\%(\<val\>\|\<module\>\|\<class\>\|\<type\>\|\<method\>\|\<initializer\>\|\<inherit\>\|\<exception\>\|\<external\>\)'
187a5792f58SBram Moolenaar    exe 'return ">' (indent(a:l)/s:i)+1 '"'
188a5792f58SBram Moolenaar  endif
189a5792f58SBram Moolenaar
190a5792f58SBram Moolenaar  " Toplevel let are detected thanks to the indentation
191a5792f58SBram Moolenaar  if getline(a:l) =~ '^\s*let\>' && indent(a:l) == s:i+s:topindent(a:l)
192a5792f58SBram Moolenaar    exe 'return ">' (indent(a:l)/s:i)+1 '"'
193a5792f58SBram Moolenaar  endif
194a5792f58SBram Moolenaar
195a5792f58SBram Moolenaar  " We close fold on end which are associated to struct, sig or object.
196a5792f58SBram Moolenaar  " We use syntax information to do that.
197a5792f58SBram Moolenaar  if getline(a:l) =~ '^\s*end\>' && synIDattr(synID(a:l, indent(a:l)+1, 0), "name") != "ocamlKeyword"
198a5792f58SBram Moolenaar    return (indent(a:l)/s:i)+1
199a5792f58SBram Moolenaar  endif
200a5792f58SBram Moolenaar
201a5792f58SBram Moolenaar  " Folds end on ;;
202a5792f58SBram Moolenaar  if getline(a:l) =~ '^\s*;;'
203a5792f58SBram Moolenaar    exe 'return "<' (indent(a:l)/s:i)+1 '"'
204a5792f58SBram Moolenaar  endif
205a5792f58SBram Moolenaar
206a5792f58SBram Moolenaar  " Comments around folds aren't merged to them.
207a5792f58SBram Moolenaar  if synIDattr(synID(a:l, indent(a:l)+1, 0), "name") == "ocamlComment"
208a5792f58SBram Moolenaar    return -1
209a5792f58SBram Moolenaar  endif
210a5792f58SBram Moolenaar
211a5792f58SBram Moolenaar  return '='
212a5792f58SBram Moolenaarendfunction
213a5792f58SBram Moolenaar
2143577c6faSBram Moolenaar" Vim support for OCaml .annot files
2155eb86f91SBram Moolenaar"
2163577c6faSBram Moolenaar" Last Change: 2007 Jul 17
2173577c6faSBram Moolenaar" Maintainer:  Vincent Aravantinos <[email protected]>
2183577c6faSBram Moolenaar" License:     public domain
2193577c6faSBram Moolenaar"
2203577c6faSBram Moolenaar" Originally inspired by 'ocaml-dtypes.vim' by Stefano Zacchiroli.
2213577c6faSBram Moolenaar" The source code is quite radically different for we not use python anymore.
2223577c6faSBram Moolenaar" However this plugin should have the exact same behaviour, that's why the
2233577c6faSBram Moolenaar" following lines are the quite exact copy of Stefano's original plugin :
2243577c6faSBram Moolenaar"
2253577c6faSBram Moolenaar" <<
2263577c6faSBram Moolenaar" Executing Ocaml_print_type(<mode>) function will display in the Vim bottom
2275eb86f91SBram Moolenaar" line(s) the type of an ocaml value getting it from the corresponding .annot
2285eb86f91SBram Moolenaar" file (if any).  If Vim is in visual mode, <mode> should be "visual" and the
2295eb86f91SBram Moolenaar" selected ocaml value correspond to the highlighted text, otherwise (<mode>
2305eb86f91SBram Moolenaar" can be anything else) it corresponds to the literal found at the current
2315eb86f91SBram Moolenaar" cursor position.
2325eb86f91SBram Moolenaar"
2333577c6faSBram Moolenaar" Typing '<LocalLeader>t' (LocalLeader defaults to '\', see :h LocalLeader)
2343577c6faSBram Moolenaar" will cause " Ocaml_print_type function to be invoked with the right
2353577c6faSBram Moolenaar" argument depending on the current mode (visual or not).
2363577c6faSBram Moolenaar" >>
2375eb86f91SBram Moolenaar"
2383577c6faSBram Moolenaar" If you find something not matching this behaviour, please signal it.
2395eb86f91SBram Moolenaar"
2403577c6faSBram Moolenaar" Differences are:
2413577c6faSBram Moolenaar"   - no need for python support
2423577c6faSBram Moolenaar"     + plus : more portable
2433577c6faSBram Moolenaar"     + minus: no more lazy parsing, it looks very fast however
2445eb86f91SBram Moolenaar"
2453577c6faSBram Moolenaar"   - ocamlbuild support, ie.
2463577c6faSBram Moolenaar"     + the plugin finds the _build directory and looks for the
2473577c6faSBram Moolenaar"       corresponding file inside;
2483577c6faSBram Moolenaar"     + if the user decides to change the name of the _build directory thanks
2493577c6faSBram Moolenaar"       to the '-build-dir' option of ocamlbuild, the plugin will manage in
2503577c6faSBram Moolenaar"       most cases to find it out (most cases = if the source file has a unique
2513577c6faSBram Moolenaar"       name among your whole project);
2523577c6faSBram Moolenaar"     + if ocamlbuild is not used, the usual behaviour holds; ie. the .annot
2533577c6faSBram Moolenaar"       file should be in the same directory as the source file;
2543577c6faSBram Moolenaar"     + for vim plugin programmers:
2553577c6faSBram Moolenaar"       the variable 'b:_build_dir' contains the inferred path to the build
2563577c6faSBram Moolenaar"       directory, even if this one is not named '_build'.
2573577c6faSBram Moolenaar"
2583577c6faSBram Moolenaar" Bonus :
2593577c6faSBram Moolenaar"   - latin1 accents are handled
2603577c6faSBram Moolenaar"   - lists are handled, even on multiple lines, you don't need the visual mode
2613577c6faSBram Moolenaar"     (the cursor must be on the first bracket)
2623577c6faSBram Moolenaar"   - parenthesized expressions, arrays, and structures (ie. '(...)', '[|...|]',
2633577c6faSBram Moolenaar"     and '{...}') are handled the same way
2645eb86f91SBram Moolenaar
2653577c6faSBram Moolenaar  " Copied from Stefano's original plugin :
2663577c6faSBram Moolenaar  " <<
2673577c6faSBram Moolenaar  "      .annot ocaml file representation
2683577c6faSBram Moolenaar  "
2693577c6faSBram Moolenaar  "      File format (copied verbatim from caml-types.el)
2703577c6faSBram Moolenaar  "
2713577c6faSBram Moolenaar  "      file ::= block *
2723577c6faSBram Moolenaar  "      block ::= position <SP> position <LF> annotation *
2733577c6faSBram Moolenaar  "      position ::= filename <SP> num <SP> num <SP> num
2743577c6faSBram Moolenaar  "      annotation ::= keyword open-paren <LF> <SP> <SP> data <LF> close-paren
2753577c6faSBram Moolenaar  "
2763577c6faSBram Moolenaar  "      <SP> is a space character (ASCII 0x20)
2773577c6faSBram Moolenaar  "      <LF> is a line-feed character (ASCII 0x0A)
2783577c6faSBram Moolenaar  "      num is a sequence of decimal digits
2793577c6faSBram Moolenaar  "      filename is a string with the lexical conventions of O'Caml
2803577c6faSBram Moolenaar  "      open-paren is an open parenthesis (ASCII 0x28)
2813577c6faSBram Moolenaar  "      close-paren is a closed parenthesis (ASCII 0x29)
2823577c6faSBram Moolenaar  "      data is any sequence of characters where <LF> is always followed by
2833577c6faSBram Moolenaar  "           at least two space characters.
2843577c6faSBram Moolenaar  "
2853577c6faSBram Moolenaar  "      - in each block, the two positions are respectively the start and the
2863577c6faSBram Moolenaar  "        end of the range described by the block.
2873577c6faSBram Moolenaar  "      - in a position, the filename is the name of the file, the first num
2883577c6faSBram Moolenaar  "        is the line number, the second num is the offset of the beginning
2893577c6faSBram Moolenaar  "        of the line, the third num is the offset of the position itself.
2903577c6faSBram Moolenaar  "      - the char number within the line is the difference between the third
2913577c6faSBram Moolenaar  "        and second nums.
2923577c6faSBram Moolenaar  "
2933577c6faSBram Moolenaar  "      For the moment, the only possible keyword is \"type\"."
2943577c6faSBram Moolenaar  " >>
2955eb86f91SBram Moolenaar
296e37d50a5SBram Moolenaar
2973577c6faSBram Moolenaar" 1. Finding the annotation file even if we use ocamlbuild
2985eb86f91SBram Moolenaar
2993577c6faSBram Moolenaar    " In:  two strings representing paths
3003577c6faSBram Moolenaar    " Out: one string representing the common prefix between the two paths
3013577c6faSBram Moolenaar  function! s:Find_common_path (p1,p2)
3023577c6faSBram Moolenaar    let temp = a:p2
3033577c6faSBram Moolenaar    while matchstr(a:p1,temp) == ''
3043577c6faSBram Moolenaar      let temp = substitute(temp,'/[^/]*$','','')
3053577c6faSBram Moolenaar    endwhile
3063577c6faSBram Moolenaar    return temp
3073577c6faSBram Moolenaar  endfun
3085eb86f91SBram Moolenaar
3093577c6faSBram Moolenaar    " After call:
310b8ff1fb5SBram Moolenaar    "
311b8ff1fb5SBram Moolenaar    "  Following information have been put in s:annot_file_list, using
312b8ff1fb5SBram Moolenaar    "  annot_file_name name as key:
313b8ff1fb5SBram Moolenaar    " - annot_file_path :
3143577c6faSBram Moolenaar    "                       path to the .annot file corresponding to the
3153577c6faSBram Moolenaar    "                       source file (dealing with ocamlbuild stuff)
316b8ff1fb5SBram Moolenaar    " - _build_path:
3173577c6faSBram Moolenaar    "                       path to the build directory even if this one is
3183577c6faSBram Moolenaar    "                       not named '_build'
319b8ff1fb5SBram Moolenaar    " - date_of_last annot:
320b8ff1fb5SBram Moolenaar    "                       Set to 0 until we load the file. It contains the
321b8ff1fb5SBram Moolenaar    "                       date at which the file has been loaded.
3223577c6faSBram Moolenaar  function! s:Locate_annotation()
323b8ff1fb5SBram Moolenaar    let annot_file_name = s:Fnameescape(expand('%:t:r')).'.annot'
324b8ff1fb5SBram Moolenaar    if !exists ("s:annot_file_list[annot_file_name]")
325e37d50a5SBram Moolenaar      silent exe 'cd' s:Fnameescape(expand('%:p:h'))
3263577c6faSBram Moolenaar      " 1st case : the annot file is in the same directory as the buffer (no ocamlbuild)
327b8ff1fb5SBram Moolenaar      let annot_file_path = findfile(annot_file_name,'.')
328b8ff1fb5SBram Moolenaar      if annot_file_path != ''
329b8ff1fb5SBram Moolenaar        let annot_file_path = getcwd().'/'.annot_file_path
330b8ff1fb5SBram Moolenaar        let _build_path = ''
3315eb86f91SBram Moolenaar      else
3323577c6faSBram Moolenaar        " 2nd case : the buffer and the _build directory are in the same directory
3333577c6faSBram Moolenaar        "      ..
3343577c6faSBram Moolenaar        "     /  \
3353577c6faSBram Moolenaar        "    /    \
3363577c6faSBram Moolenaar        " _build  .ml
3373577c6faSBram Moolenaar        "
338b8ff1fb5SBram Moolenaar        let _build_path = finddir('_build','.')
339b8ff1fb5SBram Moolenaar        if _build_path != ''
340b8ff1fb5SBram Moolenaar          let _build_path = getcwd().'/'._build_path
341b8ff1fb5SBram Moolenaar          let annot_file_path           = findfile(annot_file_name,'_build')
342b8ff1fb5SBram Moolenaar          if annot_file_path != ''
343b8ff1fb5SBram Moolenaar            let annot_file_path = getcwd().'/'.annot_file_path
3443577c6faSBram Moolenaar          endif
3453577c6faSBram Moolenaar        else
3463577c6faSBram Moolenaar          " 3rd case : the _build directory is in a directory higher in the file hierarchy
3473577c6faSBram Moolenaar          "            (it can't be deeper by ocamlbuild requirements)
3483577c6faSBram Moolenaar          "      ..
3493577c6faSBram Moolenaar          "     /  \
3503577c6faSBram Moolenaar          "    /    \
3513577c6faSBram Moolenaar          " _build  ...
3523577c6faSBram Moolenaar          "           \
3533577c6faSBram Moolenaar          "            \
3543577c6faSBram Moolenaar          "           .ml
3553577c6faSBram Moolenaar          "
356b8ff1fb5SBram Moolenaar          let _build_path = finddir('_build',';')
357b8ff1fb5SBram Moolenaar          if _build_path != ''
358b8ff1fb5SBram Moolenaar            let project_path                = substitute(_build_path,'/_build$','','')
359e37d50a5SBram Moolenaar            let path_relative_to_project    = s:Fnameescape(substitute(expand('%:p:h'),project_path.'/','',''))
360b8ff1fb5SBram Moolenaar            let annot_file_path           = findfile(annot_file_name,project_path.'/_build/'.path_relative_to_project)
3613577c6faSBram Moolenaar          else
362b8ff1fb5SBram Moolenaar            let annot_file_path = findfile(annot_file_name,'**')
3633577c6faSBram Moolenaar            "4th case : what if the user decided to change the name of the _build directory ?
3643577c6faSBram Moolenaar            "           -> we relax the constraints, it should work in most cases
365b8ff1fb5SBram Moolenaar            if annot_file_path != ''
3663577c6faSBram Moolenaar              " 4a. we suppose the renamed _build directory is in the current directory
367b8ff1fb5SBram Moolenaar              let _build_path = matchstr(annot_file_path,'^[^/]*')
368b8ff1fb5SBram Moolenaar              if annot_file_path != ''
369b8ff1fb5SBram Moolenaar                let annot_file_path = getcwd().'/'.annot_file_path
370b8ff1fb5SBram Moolenaar                let _build_path     = getcwd().'/'._build_path
3713577c6faSBram Moolenaar              endif
3723577c6faSBram Moolenaar            else
373b8ff1fb5SBram Moolenaar              let annot_file_name = ''
374*6c391a74SBram Moolenaar              "(Pierre Vittet: I have commented 4b because this was crashing
375b8ff1fb5SBram Moolenaar              "my vim (it produced infinite loop))
376b8ff1fb5SBram Moolenaar              "
3773577c6faSBram Moolenaar              " 4b. anarchy : the renamed _build directory may be higher in the hierarchy
3783577c6faSBram Moolenaar              " this will work if the file for which we are looking annotations has a unique name in the whole project
3793577c6faSBram Moolenaar              " if this is not the case, it may still work, but no warranty here
380b8ff1fb5SBram Moolenaar              "let annot_file_path = findfile(annot_file_name,'**;')
381b8ff1fb5SBram Moolenaar              "let project_path      = s:Find_common_path(annot_file_path,expand('%:p:h'))
382b8ff1fb5SBram Moolenaar              "let _build_path       = matchstr(annot_file_path,project_path.'/[^/]*')
3833577c6faSBram Moolenaar            endif
3843577c6faSBram Moolenaar          endif
3853577c6faSBram Moolenaar        endif
3863577c6faSBram Moolenaar      endif
3873577c6faSBram Moolenaar
388b8ff1fb5SBram Moolenaar      if annot_file_path == ''
3893577c6faSBram Moolenaar        throw 'E484: no annotation file found'
3903577c6faSBram Moolenaar      endif
3913577c6faSBram Moolenaar
3923577c6faSBram Moolenaar      silent exe 'cd' '-'
393b8ff1fb5SBram Moolenaar      let s:annot_file_list[annot_file_name]= [annot_file_path, _build_path, 0]
3945eb86f91SBram Moolenaar    endif
3955eb86f91SBram Moolenaar  endfun
3965eb86f91SBram Moolenaar
3977e6a515eSBram Moolenaar  " This variable contains a dictionary of lists. Each element of the dictionary
3987e6a515eSBram Moolenaar  " represents an annotation system. An annotation system is a list with:
3997e6a515eSBram Moolenaar  " - annotation file name as its key
400b8ff1fb5SBram Moolenaar  " - annotation file path as first element of the contained list
401b8ff1fb5SBram Moolenaar  " - build path as second element of the contained list
402b8ff1fb5SBram Moolenaar  " - annot_file_last_mod (contain the date of .annot file) as third element
403b8ff1fb5SBram Moolenaar  let s:annot_file_list = {}
4043577c6faSBram Moolenaar
4053577c6faSBram Moolenaar" 2. Finding the type information in the annotation file
4063577c6faSBram Moolenaar
4073577c6faSBram Moolenaar  " a. The annotation file is opened in vim as a buffer that
4083577c6faSBram Moolenaar  " should be (almost) invisible to the user.
4093577c6faSBram Moolenaar
4103577c6faSBram Moolenaar      " After call:
4113577c6faSBram Moolenaar      " The current buffer is now the one containing the .annot file.
4123577c6faSBram Moolenaar      " We manage to keep all this hidden to the user's eye.
413b8ff1fb5SBram Moolenaar    function! s:Enter_annotation_buffer(annot_file_path)
4143577c6faSBram Moolenaar      let s:current_pos = getpos('.')
4153577c6faSBram Moolenaar      let s:current_hidden = &l:hidden
4163577c6faSBram Moolenaar      set hidden
4173577c6faSBram Moolenaar      let s:current_buf = bufname('%')
418b8ff1fb5SBram Moolenaar      if bufloaded(a:annot_file_path)
419b8ff1fb5SBram Moolenaar        silent exe 'keepj keepalt' 'buffer' s:Fnameescape(a:annot_file_path)
4203577c6faSBram Moolenaar      else
421b8ff1fb5SBram Moolenaar        silent exe 'keepj keepalt' 'view' s:Fnameescape(a:annot_file_path)
4223577c6faSBram Moolenaar      endif
423b8ff1fb5SBram Moolenaar      call setpos(".", [0, 0 , 0 , 0])
4245eb86f91SBram Moolenaar    endfun
4255eb86f91SBram Moolenaar
4263577c6faSBram Moolenaar      " After call:
4273577c6faSBram Moolenaar      "   The original buffer has been restored in the exact same state as before.
4283577c6faSBram Moolenaar    function! s:Exit_annotation_buffer()
429e37d50a5SBram Moolenaar      silent exe 'keepj keepalt' 'buffer' s:Fnameescape(s:current_buf)
4303577c6faSBram Moolenaar      let &l:hidden = s:current_hidden
4313577c6faSBram Moolenaar      call setpos('.',s:current_pos)
4323577c6faSBram Moolenaar    endfun
4333577c6faSBram Moolenaar
4343577c6faSBram Moolenaar      " After call:
4353577c6faSBram Moolenaar      "   The annot file is loaded and assigned to a buffer.
4363577c6faSBram Moolenaar      "   This also handles the modification date of the .annot file, eg. after a
437b8ff1fb5SBram Moolenaar      "   compilation (return an updated annot_file_list).
438b8ff1fb5SBram Moolenaar    function! s:Load_annotation(annot_file_name)
439b8ff1fb5SBram Moolenaar      let annot = s:annot_file_list[a:annot_file_name]
440b8ff1fb5SBram Moolenaar      let annot_file_path = annot[0]
441b8ff1fb5SBram Moolenaar      let annot_file_last_mod = 0
442b8ff1fb5SBram Moolenaar      if exists("annot[2]")
443b8ff1fb5SBram Moolenaar        let annot_file_last_mod = annot[2]
4443577c6faSBram Moolenaar      endif
445b8ff1fb5SBram Moolenaar      if bufloaded(annot_file_path) && annot_file_last_mod < getftime(annot_file_path)
446b8ff1fb5SBram Moolenaar        " if there is a more recent file
447b8ff1fb5SBram Moolenaar        let nr = bufnr(annot_file_path)
448b8ff1fb5SBram Moolenaar        silent exe 'keepj keepalt' 'bunload' nr
449b8ff1fb5SBram Moolenaar      endif
450b8ff1fb5SBram Moolenaar      if !bufloaded(annot_file_path)
451b8ff1fb5SBram Moolenaar        call s:Enter_annotation_buffer(annot_file_path)
4523577c6faSBram Moolenaar        setlocal nobuflisted
4533577c6faSBram Moolenaar        setlocal bufhidden=hide
4543577c6faSBram Moolenaar        setlocal noswapfile
4553577c6faSBram Moolenaar        setlocal buftype=nowrite
4563577c6faSBram Moolenaar        call s:Exit_annotation_buffer()
457b8ff1fb5SBram Moolenaar        let annot[2] = getftime(annot_file_path)
458b8ff1fb5SBram Moolenaar        " List updated with the new date
459b8ff1fb5SBram Moolenaar        let s:annot_file_list[a:annot_file_name] = annot
4603577c6faSBram Moolenaar      endif
4613577c6faSBram Moolenaar    endfun
4623577c6faSBram Moolenaar
4633577c6faSBram Moolenaar  "b. 'search' and 'match' work to find the type information
4643577c6faSBram Moolenaar
465*6c391a74SBram Moolenaar      "In:  - lin1,col1: position of expression first char
466*6c391a74SBram Moolenaar      "     - lin2,col2: position of expression last char
4673577c6faSBram Moolenaar      "Out: - the pattern to be looked for to find the block
4683577c6faSBram Moolenaar      " Must be called in the source buffer (use of line2byte)
4693577c6faSBram Moolenaar    function! s:Block_pattern(lin1,lin2,col1,col2)
4703577c6faSBram Moolenaar      let start_num1 = a:lin1
4713577c6faSBram Moolenaar      let start_num2 = line2byte(a:lin1) - 1
4723577c6faSBram Moolenaar      let start_num3 = start_num2 + a:col1
4739c754c45SBram Moolenaar      let path       = '"\(\\"\|[^"]\)\+"'
4749c754c45SBram Moolenaar      let start_pos  = path.' '.start_num1.' '.start_num2.' '.start_num3
4753577c6faSBram Moolenaar      let end_num1   = a:lin2
4763577c6faSBram Moolenaar      let end_num2   = line2byte(a:lin2) - 1
4773577c6faSBram Moolenaar      let end_num3   = end_num2 + a:col2
4789c754c45SBram Moolenaar      let end_pos    = path.' '.end_num1.' '.end_num2.' '.end_num3
4793577c6faSBram Moolenaar      return '^'.start_pos.' '.end_pos."$"
4803577c6faSBram Moolenaar      " rq: the '^' here is not totally correct regarding the annot file "grammar"
4813577c6faSBram Moolenaar      " but currently the annotation file respects this, and it's a little bit faster with the '^';
4823577c6faSBram Moolenaar      " can be removed safely.
4833577c6faSBram Moolenaar    endfun
4843577c6faSBram Moolenaar
4853577c6faSBram Moolenaar      "In: (the cursor position should be at the start of an annotation)
4863577c6faSBram Moolenaar      "Out: the type information
4873577c6faSBram Moolenaar      " Must be called in the annotation buffer (use of search)
4883577c6faSBram Moolenaar    function! s:Match_data()
4893577c6faSBram Moolenaar      " rq: idem as previously, in the following, the '^' at start of patterns is not necessary
4903577c6faSBram Moolenaar      keepj while search('^type($','ce',line(".")) == 0
4913577c6faSBram Moolenaar        keepj if search('^.\{-}($','e') == 0
4923577c6faSBram Moolenaar          throw "no_annotation"
4933577c6faSBram Moolenaar        endif
4943577c6faSBram Moolenaar        keepj if searchpair('(','',')') == 0
4953577c6faSBram Moolenaar          throw "malformed_annot_file"
4963577c6faSBram Moolenaar        endif
4973577c6faSBram Moolenaar      endwhile
4983577c6faSBram Moolenaar      let begin = line(".") + 1
4993577c6faSBram Moolenaar      keepj if searchpair('(','',')') == 0
5003577c6faSBram Moolenaar        throw "malformed_annot_file"
5013577c6faSBram Moolenaar      endif
5023577c6faSBram Moolenaar      let end = line(".") - 1
5033577c6faSBram Moolenaar      return join(getline(begin,end),"\n")
5043577c6faSBram Moolenaar    endfun
5053577c6faSBram Moolenaar
5063577c6faSBram Moolenaar      "In:  the pattern to look for in order to match the block
5073577c6faSBram Moolenaar      "Out: the type information (calls s:Match_data)
5083577c6faSBram Moolenaar      " Should be called in the annotation buffer
509b8ff1fb5SBram Moolenaar    function! s:Extract_type_data(block_pattern, annot_file_name)
510b8ff1fb5SBram Moolenaar      let annot_file_path = s:annot_file_list[a:annot_file_name][0]
511b8ff1fb5SBram Moolenaar      call s:Enter_annotation_buffer(annot_file_path)
5123577c6faSBram Moolenaar      try
5133577c6faSBram Moolenaar        if search(a:block_pattern,'e') == 0
5143577c6faSBram Moolenaar          throw "no_annotation"
5153577c6faSBram Moolenaar        endif
5163577c6faSBram Moolenaar        call cursor(line(".") + 1,1)
5173577c6faSBram Moolenaar        let annotation = s:Match_data()
5183577c6faSBram Moolenaar      finally
5193577c6faSBram Moolenaar        call s:Exit_annotation_buffer()
5203577c6faSBram Moolenaar      endtry
5213577c6faSBram Moolenaar      return annotation
5223577c6faSBram Moolenaar    endfun
5233577c6faSBram Moolenaar
5243577c6faSBram Moolenaar  "c. link this stuff with what the user wants
5253577c6faSBram Moolenaar  " ie. get the expression selected/under the cursor
5263577c6faSBram Moolenaar
5277e6a515eSBram Moolenaar    let s:ocaml_word_char = '\w|[\xc0-\xff]|'''
5283577c6faSBram Moolenaar
5293577c6faSBram Moolenaar      "In:  the current mode (eg. "visual", "normal", etc.)
5303577c6faSBram Moolenaar      "Out: the borders of the expression we are looking for the type
5313577c6faSBram Moolenaar    function! s:Match_borders(mode)
5323577c6faSBram Moolenaar      if a:mode == "visual"
5333577c6faSBram Moolenaar        let cur = getpos(".")
5343577c6faSBram Moolenaar        normal `<
5353577c6faSBram Moolenaar        let col1 = col(".")
5363577c6faSBram Moolenaar        let lin1 = line(".")
5373577c6faSBram Moolenaar        normal `>
5383577c6faSBram Moolenaar        let col2 = col(".")
5393577c6faSBram Moolenaar        let lin2 = line(".")
5403577c6faSBram Moolenaar        call cursor(cur[1],cur[2])
5413577c6faSBram Moolenaar        return [lin1,lin2,col1-1,col2]
5423577c6faSBram Moolenaar      else
5433577c6faSBram Moolenaar        let cursor_line = line(".")
5443577c6faSBram Moolenaar        let cursor_col  = col(".")
5453577c6faSBram Moolenaar        let line = getline('.')
5463577c6faSBram Moolenaar        if line[cursor_col-1:cursor_col] == '[|'
5473577c6faSBram Moolenaar          let [lin2,col2] = searchpairpos('\[|','','|\]','n')
5483577c6faSBram Moolenaar          return [cursor_line,lin2,cursor_col-1,col2+1]
5493577c6faSBram Moolenaar        elseif     line[cursor_col-1] == '['
5503577c6faSBram Moolenaar          let [lin2,col2] = searchpairpos('\[','','\]','n')
5513577c6faSBram Moolenaar          return [cursor_line,lin2,cursor_col-1,col2]
5523577c6faSBram Moolenaar        elseif line[cursor_col-1] == '('
5533577c6faSBram Moolenaar          let [lin2,col2] = searchpairpos('(','',')','n')
5543577c6faSBram Moolenaar          return [cursor_line,lin2,cursor_col-1,col2]
5553577c6faSBram Moolenaar        elseif line[cursor_col-1] == '{'
5563577c6faSBram Moolenaar          let [lin2,col2] = searchpairpos('{','','}','n')
5573577c6faSBram Moolenaar          return [cursor_line,lin2,cursor_col-1,col2]
5583577c6faSBram Moolenaar        else
5593577c6faSBram Moolenaar          let [lin1,col1] = searchpos('\v%('.s:ocaml_word_char.'|\.)*','ncb')
5603577c6faSBram Moolenaar          let [lin2,col2] = searchpos('\v%('.s:ocaml_word_char.'|\.)*','nce')
5613577c6faSBram Moolenaar          if col1 == 0 || col2 == 0
5623577c6faSBram Moolenaar            throw "no_expression"
5633577c6faSBram Moolenaar          endif
5643577c6faSBram Moolenaar          return [cursor_line,cursor_line,col1-1,col2]
5653577c6faSBram Moolenaar        endif
5663577c6faSBram Moolenaar      endif
5673577c6faSBram Moolenaar    endfun
5683577c6faSBram Moolenaar
5693577c6faSBram Moolenaar      "In:  the current mode (eg. "visual", "normal", etc.)
5703577c6faSBram Moolenaar      "Out: the type information (calls s:Extract_type_data)
571b8ff1fb5SBram Moolenaar    function! s:Get_type(mode, annot_file_name)
5723577c6faSBram Moolenaar      let [lin1,lin2,col1,col2] = s:Match_borders(a:mode)
573b8ff1fb5SBram Moolenaar      return s:Extract_type_data(s:Block_pattern(lin1,lin2,col1,col2), a:annot_file_name)
5743577c6faSBram Moolenaar    endfun
5753577c6faSBram Moolenaar
57616ea3676SBram Moolenaar      "In: A string destined to be printed in the 'echo buffer'. It has line
57716ea3676SBram Moolenaar      "break and 2 space at each line beginning.
57816ea3676SBram Moolenaar      "Out: A string destined to be yanked, without space and double space.
57916ea3676SBram Moolenaar    function s:unformat_ocaml_type(res)
58016ea3676SBram Moolenaar      "Remove end of line.
58116ea3676SBram Moolenaar      let res = substitute (a:res, "\n", "", "g" )
58216ea3676SBram Moolenaar      "remove double space
58316ea3676SBram Moolenaar      let res =substitute(res , "  ", " ", "g")
584*6c391a74SBram Moolenaar      "remove space at beginning of string.
58516ea3676SBram Moolenaar      let res = substitute(res, "^ *", "", "g")
58616ea3676SBram Moolenaar      return res
58716ea3676SBram Moolenaar    endfunction
58816ea3676SBram Moolenaar
5893577c6faSBram Moolenaar  "d. main
5903577c6faSBram Moolenaar      "In:         the current mode (eg. "visual", "normal", etc.)
5913577c6faSBram Moolenaar      "After call: the type information is displayed
5923577c6faSBram Moolenaar    if !exists("*Ocaml_get_type")
5933577c6faSBram Moolenaar      function Ocaml_get_type(mode)
594b8ff1fb5SBram Moolenaar        let annot_file_name = s:Fnameescape(expand('%:t:r')).'.annot'
5953577c6faSBram Moolenaar        call s:Locate_annotation()
596b8ff1fb5SBram Moolenaar        call s:Load_annotation(annot_file_name)
59716ea3676SBram Moolenaar        let res = s:Get_type(a:mode, annot_file_name)
59816ea3676SBram Moolenaar        " Copy result in the unnamed buffer
59916ea3676SBram Moolenaar        let @" = s:unformat_ocaml_type(res)
60016ea3676SBram Moolenaar        return res
6013577c6faSBram Moolenaar      endfun
6023577c6faSBram Moolenaar    endif
6033577c6faSBram Moolenaar
6043577c6faSBram Moolenaar    if !exists("*Ocaml_get_type_or_not")
6053577c6faSBram Moolenaar      function Ocaml_get_type_or_not(mode)
6063577c6faSBram Moolenaar        let t=reltime()
6073577c6faSBram Moolenaar        try
60816ea3676SBram Moolenaar          let res = Ocaml_get_type(a:mode)
60916ea3676SBram Moolenaar          return res
6103577c6faSBram Moolenaar        catch
6113577c6faSBram Moolenaar          return ""
6123577c6faSBram Moolenaar        endtry
6133577c6faSBram Moolenaar      endfun
6143577c6faSBram Moolenaar    endif
6153577c6faSBram Moolenaar
6163577c6faSBram Moolenaar    if !exists("*Ocaml_print_type")
6173577c6faSBram Moolenaar      function Ocaml_print_type(mode)
6183577c6faSBram Moolenaar        if expand("%:e") == "mli"
6193577c6faSBram Moolenaar          echohl ErrorMsg | echo "No annotations for interface (.mli) files" | echohl None
6203577c6faSBram Moolenaar          return
6213577c6faSBram Moolenaar        endif
6223577c6faSBram Moolenaar        try
6233577c6faSBram Moolenaar          echo Ocaml_get_type(a:mode)
6243577c6faSBram Moolenaar        catch /E484:/
6253577c6faSBram Moolenaar          echohl ErrorMsg | echo "No type annotations (.annot) file found" | echohl None
6263577c6faSBram Moolenaar        catch /no_expression/
6273577c6faSBram Moolenaar          echohl ErrorMsg | echo "No expression found under the cursor" | echohl None
6283577c6faSBram Moolenaar        catch /no_annotation/
6293577c6faSBram Moolenaar          echohl ErrorMsg | echo "No type annotation found for the given text" | echohl None
6303577c6faSBram Moolenaar        catch /malformed_annot_file/
6313577c6faSBram Moolenaar          echohl ErrorMsg | echo "Malformed .annot file" | echohl None
6323577c6faSBram Moolenaar        endtry
6333577c6faSBram Moolenaar      endfun
6343577c6faSBram Moolenaar    endif
6353577c6faSBram Moolenaar
6363577c6faSBram Moolenaar" Maps
63716ea3676SBram Moolenaar  nnoremap <silent> <Plug>OCamlPrintType :<C-U>call Ocaml_print_type("normal")<CR>
63816ea3676SBram Moolenaar  xnoremap <silent> <Plug>OCamlPrintType :<C-U>call Ocaml_print_type("visual")<CR>`<
639a5792f58SBram Moolenaar
640a5792f58SBram Moolenaarlet &cpoptions = s:cposet
641a5792f58SBram Moolenaarunlet s:cposet
642a5792f58SBram Moolenaar
6433577c6faSBram Moolenaar" vim:sw=2 fdm=indent
644