blob: 8b88d8d00176758d52143b189f4a72b1052f7af3 [file] [log] [blame]
Bram Moolenaara5792f52005-11-23 21:25:05 +00001" Language: OCaml
2" Maintainer: David Baelde <firstname.name@ens-lyon.org>
3" Mike Leary <leary@nwlink.com>
4" Markus Mottl <markus.mottl@gmail.com>
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +01005" Pierre Vittet <pierre-vittet@pvittet.com>
Bram Moolenaara5792f52005-11-23 21:25:05 +00006" Stefano Zacchiroli <zack@bononia.it>
Bram Moolenaar3577c6f2008-06-24 21:16:56 +00007" Vincent Aravantinos <firstname.name@imag.fr>
Riley Bruins0a083062024-06-03 20:40:45 +02008" Riley Bruins <ribru17@gmail.com> ('commentstring')
Bram Moolenaar7e6a5152021-01-02 16:39:53 +01009" URL: https://github.com/ocaml/vim-ocaml
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +010010" Last Change:
Bram Moolenaar790c18b2019-07-04 17:22:06 +020011" 2013 Oct 27 - Added commentstring (MM)
Bram Moolenaar16ea3672013-07-28 16:02:18 +020012" 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)
Riley Bruins0a083062024-06-03 20:40:45 +020015" 2024 May 23 - added space in commentstring (RB)
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +010016
Bram Moolenaar4c3f5362006-04-11 21:38:50 +000017if exists("b:did_ftplugin")
18 finish
19endif
Bram Moolenaara5792f52005-11-23 21:25:05 +000020let b:did_ftplugin=1
Bram Moolenaar071d4272004-06-13 20:20:40 +000021
Bram Moolenaar16ea3672013-07-28 16:02:18 +020022" Use standard compiler settings unless user wants otherwise
23if !exists("current_compiler")
24 :compiler ocaml
25endif
26
Bram Moolenaare37d50a2008-08-06 17:06:04 +000027" some macro
28if exists('*fnameescape')
29 function! s:Fnameescape(s)
30 return fnameescape(a:s)
31 endfun
32else
33 function! s:Fnameescape(s)
34 return escape(a:s," \t\n*?[{`$\\%#'\"|!<")
35 endfun
36endif
37
Bram Moolenaara5792f52005-11-23 21:25:05 +000038" Error handling -- helps moving where the compiler wants you to go
39let s:cposet=&cpoptions
Bram Moolenaarf1568ec2011-12-14 21:17:39 +010040set cpo&vim
Bram Moolenaar071d4272004-06-13 20:20:40 +000041
Bram Moolenaar790c18b2019-07-04 17:22:06 +020042" Comment string
Bram Moolenaar7e6a5152021-01-02 16:39:53 +010043setlocal comments=sr:(*\ ,mb:\ ,ex:*)
44setlocal comments^=sr:(**,mb:\ \ ,ex:*)
Riley Bruins0a083062024-06-03 20:40:45 +020045setlocal commentstring=(*\ %s\ *)
Bram Moolenaar790c18b2019-07-04 17:22:06 +020046
Bram Moolenaar071d4272004-06-13 20:20:40 +000047" Add mappings, unless the user didn't want this.
48if !exists("no_plugin_maps") && !exists("no_ocaml_maps")
Bram Moolenaara5792f52005-11-23 21:25:05 +000049 " (un)commenting
Bram Moolenaar071d4272004-06-13 20:20:40 +000050 if !hasmapto('<Plug>Comment')
51 nmap <buffer> <LocalLeader>c <Plug>LUncomOn
Bram Moolenaar16ea3672013-07-28 16:02:18 +020052 xmap <buffer> <LocalLeader>c <Plug>BUncomOn
Bram Moolenaar071d4272004-06-13 20:20:40 +000053 nmap <buffer> <LocalLeader>C <Plug>LUncomOff
Bram Moolenaar16ea3672013-07-28 16:02:18 +020054 xmap <buffer> <LocalLeader>C <Plug>BUncomOff
Bram Moolenaar071d4272004-06-13 20:20:40 +000055 endif
56
Bram Moolenaar16ea3672013-07-28 16:02:18 +020057 nnoremap <buffer> <Plug>LUncomOn gI(* <End> *)<ESC>
Bram Moolenaara5792f52005-11-23 21:25:05 +000058 nnoremap <buffer> <Plug>LUncomOff :s/^(\* \(.*\) \*)/\1/<CR>:noh<CR>
Bram Moolenaar16ea3672013-07-28 16:02:18 +020059 xnoremap <buffer> <Plug>BUncomOn <ESC>:'<,'><CR>`<O<ESC>0i(*<ESC>`>o<ESC>0i*)<ESC>`<
60 xnoremap <buffer> <Plug>BUncomOff <ESC>:'<,'><CR>`<dd`>dd`<
Bram Moolenaar071d4272004-06-13 20:20:40 +000061
Bram Moolenaar16ea3672013-07-28 16:02:18 +020062 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
Bram Moolenaar071d4272004-06-13 20:20:40 +000067endif
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000068
69" Let % jump between structure elements (due to Issac Trotts)
Bram Moolenaar790c18b2019-07-04 17:22:06 +020070let b:mw = '\<let\>:\<and\>:\(\<in\>\|;;\)'
Bram Moolenaara5792f52005-11-23 21:25:05 +000071let b:mw = b:mw . ',\<if\>:\<then\>:\<else\>'
Bram Moolenaar790c18b2019-07-04 17:22:06 +020072let b:mw = b:mw . ',\<\(for\|while\)\>:\<do\>:\<done\>'
Bram Moolenaara5792f52005-11-23 21:25:05 +000073let b:mw = b:mw . ',\<\(object\|sig\|struct\|begin\)\>:\<end\>'
74let b:mw = b:mw . ',\<\(match\|try\)\>:\<with\>'
75let b:match_words = b:mw
76
77let b:match_ignorecase=0
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000078
Bram Moolenaar790c18b2019-07-04 17:22:06 +020079function! 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
100endfunction
101command! -bar -bang -complete=file -nargs=+ Ocpgrep exe s:OcpGrep(<q-bang>, <q-args>)
102
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000103" switching between interfaces (.mli) and implementations (.ml)
104if !exists("g:did_ocaml_switch")
105 let g:did_ocaml_switch = 1
Bram Moolenaar16ea3672013-07-28 16:02:18 +0200106 nnoremap <Plug>OCamlSwitchEdit :<C-u>call OCaml_switch(0)<CR>
107 nnoremap <Plug>OCamlSwitchNewWin :<C-u>call OCaml_switch(1)<CR>
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000108 fun OCaml_switch(newwin)
109 if (match(bufname(""), "\\.mli$") >= 0)
Bram Moolenaare37d50a2008-08-06 17:06:04 +0000110 let fname = s:Fnameescape(substitute(bufname(""), "\\.mli$", ".ml", ""))
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000111 if (a:newwin == 1)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000112 exec "new " . fname
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000113 else
Bram Moolenaara5792f52005-11-23 21:25:05 +0000114 exec "arge " . fname
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000115 endif
116 elseif (match(bufname(""), "\\.ml$") >= 0)
Bram Moolenaare37d50a2008-08-06 17:06:04 +0000117 let fname = s:Fnameescape(bufname("")) . "i"
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000118 if (a:newwin == 1)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000119 exec "new " . fname
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000120 else
Bram Moolenaara5792f52005-11-23 21:25:05 +0000121 exec "arge " . fname
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000122 endif
123 endif
124 endfun
125endif
126
Bram Moolenaara5792f52005-11-23 21:25:05 +0000127" Folding support
128
129" Get the modeline because folding depends on indentation
Bram Moolenaar790c18b2019-07-04 17:22:06 +0200130let lnum = search('^\s*(\*:o\?caml:', 'n')
131let s:modeline = lnum? getline(lnum): ""
Bram Moolenaara5792f52005-11-23 21:25:05 +0000132
133" Get the indentation params
134let s:m = matchstr(s:modeline,'default\s*=\s*\d\+')
135if s:m != ""
136 let s:idef = matchstr(s:m,'\d\+')
137elseif exists("g:omlet_indent")
138 let s:idef = g:omlet_indent
139else
140 let s:idef = 2
141endif
142let s:m = matchstr(s:modeline,'struct\s*=\s*\d\+')
143if s:m != ""
144 let s:i = matchstr(s:m,'\d\+')
145elseif exists("g:omlet_indent_struct")
146 let s:i = g:omlet_indent_struct
147else
148 let s:i = s:idef
149endif
150
151" Set the folding method
152if exists("g:ocaml_folding")
153 setlocal foldmethod=expr
154 setlocal foldexpr=OMLetFoldLevel(v:lnum)
155endif
156
Bram Moolenaar16ea3672013-07-28 16:02:18 +0200157let b:undo_ftplugin = "setlocal efm< foldmethod< foldexpr<"
158 \ . "| unlet! b:mw b:match_words b:match_ignorecase"
159
160
Bram Moolenaara5792f52005-11-23 21:25:05 +0000161" - Only definitions below, executed once -------------------------------------
162
163if exists("*OMLetFoldLevel")
Bram Moolenaare0e39172021-01-25 21:14:57 +0100164 let &cpoptions = s:cposet
165 unlet s:cposet
Bram Moolenaara5792f52005-11-23 21:25:05 +0000166 finish
167endif
168
169function 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
178endfunction
179
180function 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 '='
214endfunction
215
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000216" Vim support for OCaml .annot files
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000217"
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000218" 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
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000229" 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"
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000235" Typing '<LocalLeader>t' (LocalLeader defaults to '\', see :h LocalLeader)
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100236" will cause " Ocaml_print_type function to be invoked with the right
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000237" argument depending on the current mode (visual or not).
238" >>
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000239"
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000240" If you find something not matching this behaviour, please signal it.
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000241"
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000242" Differences are:
243" - no need for python support
244" + plus : more portable
245" + minus: no more lazy parsing, it looks very fast however
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100246"
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000247" - ocamlbuild support, ie.
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100248" + the plugin finds the _build directory and looks for the
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000249" 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:
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100257" the variable 'b:_build_dir' contains the inferred path to the build
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000258" directory, even if this one is not named '_build'.
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000259"
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000260" 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
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000266
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000267 " 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 " >>
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000297
Bram Moolenaare37d50a2008-08-06 17:06:04 +0000298
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000299" 1. Finding the annotation file even if we use ocamlbuild
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000300
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000301 " 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
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000310
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000311 " After call:
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100312 "
313 " Following information have been put in s:annot_file_list, using
314 " annot_file_name name as key:
315 " - annot_file_path :
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000316 " path to the .annot file corresponding to the
317 " source file (dealing with ocamlbuild stuff)
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100318 " - _build_path:
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000319 " path to the build directory even if this one is
320 " not named '_build'
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100321 " - 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.
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000324 function! s:Locate_annotation()
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100325 let annot_file_name = s:Fnameescape(expand('%:t:r')).'.annot'
326 if !exists ("s:annot_file_list[annot_file_name]")
Bram Moolenaare37d50a2008-08-06 17:06:04 +0000327 silent exe 'cd' s:Fnameescape(expand('%:p:h'))
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000328 " 1st case : the annot file is in the same directory as the buffer (no ocamlbuild)
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100329 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 = ''
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000333 else
334 " 2nd case : the buffer and the _build directory are in the same directory
335 " ..
336 " / \
337 " / \
338 " _build .ml
339 "
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100340 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
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000346 endif
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000347 else
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100348 " 3rd case : the _build directory is in a directory higher in the file hierarchy
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000349 " (it can't be deeper by ocamlbuild requirements)
350 " ..
351 " / \
352 " / \
353 " _build ...
354 " \
355 " \
356 " .ml
357 "
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100358 let _build_path = finddir('_build',';')
359 if _build_path != ''
360 let project_path = substitute(_build_path,'/_build$','','')
Bram Moolenaare37d50a2008-08-06 17:06:04 +0000361 let path_relative_to_project = s:Fnameescape(substitute(expand('%:p:h'),project_path.'/','',''))
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100362 let annot_file_path = findfile(annot_file_name,project_path.'/_build/'.path_relative_to_project)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000363 else
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100364 let annot_file_path = findfile(annot_file_name,'**')
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000365 "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
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100367 if annot_file_path != ''
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000368 " 4a. we suppose the renamed _build directory is in the current directory
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100369 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
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000373 endif
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000374 else
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100375 let annot_file_name = ''
Bram Moolenaar6c391a72021-09-09 21:55:11 +0200376 "(Pierre Vittet: I have commented 4b because this was crashing
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100377 "my vim (it produced infinite loop))
378 "
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000379 " 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
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100382 "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.'/[^/]*')
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000385 endif
386 endif
387 endif
388 endif
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000389
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100390 if annot_file_path == ''
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000391 throw 'E484: no annotation file found'
392 endif
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000393
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000394 silent exe 'cd' '-'
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100395 let s:annot_file_list[annot_file_name]= [annot_file_path, _build_path, 0]
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000396 endif
397 endfun
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000398
Bram Moolenaar7e6a5152021-01-02 16:39:53 +0100399 " 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
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100402 " - 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 = {}
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000406
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000407" 2. Finding the type information in the annotation file
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100408
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000409 " a. The annotation file is opened in vim as a buffer that
410 " should be (almost) invisible to the user.
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000411
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000412 " 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.
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100415 function! s:Enter_annotation_buffer(annot_file_path)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000416 let s:current_pos = getpos('.')
417 let s:current_hidden = &l:hidden
418 set hidden
419 let s:current_buf = bufname('%')
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100420 if bufloaded(a:annot_file_path)
421 silent exe 'keepj keepalt' 'buffer' s:Fnameescape(a:annot_file_path)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000422 else
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100423 silent exe 'keepj keepalt' 'view' s:Fnameescape(a:annot_file_path)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000424 endif
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100425 call setpos(".", [0, 0 , 0 , 0])
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000426 endfun
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000427
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000428 " After call:
429 " The original buffer has been restored in the exact same state as before.
430 function! s:Exit_annotation_buffer()
Bram Moolenaare37d50a2008-08-06 17:06:04 +0000431 silent exe 'keepj keepalt' 'buffer' s:Fnameescape(s:current_buf)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000432 let &l:hidden = s:current_hidden
433 call setpos('.',s:current_pos)
434 endfun
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000435
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000436 " After call:
437 " The annot file is loaded and assigned to a buffer.
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100438 " This also handles the modification date of the .annot file, eg. after a
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100439 " 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]
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000446 endif
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100447 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)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000454 setlocal nobuflisted
455 setlocal bufhidden=hide
456 setlocal noswapfile
457 setlocal buftype=nowrite
458 call s:Exit_annotation_buffer()
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100459 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
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000462 endif
463 endfun
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100464
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000465 "b. 'search' and 'match' work to find the type information
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100466
Bram Moolenaar6c391a72021-09-09 21:55:11 +0200467 "In: - lin1,col1: position of expression first char
468 " - lin2,col2: position of expression last char
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000469 "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
Bram Moolenaar9c754c42010-07-10 15:52:35 +0200475 let path = '"\(\\"\|[^"]\)\+"'
476 let start_pos = path.' '.start_num1.' '.start_num2.' '.start_num3
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000477 let end_num1 = a:lin2
478 let end_num2 = line2byte(a:lin2) - 1
479 let end_num3 = end_num2 + a:col2
Bram Moolenaar9c754c42010-07-10 15:52:35 +0200480 let end_pos = path.' '.end_num1.' '.end_num2.' '.end_num3
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000481 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
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000486
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000487 "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
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100507
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000508 "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
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100511 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)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000514 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
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100525
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000526 "c. link this stuff with what the user wants
527 " ie. get the expression selected/under the cursor
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100528
Bram Moolenaar7e6a5152021-01-02 16:39:53 +0100529 let s:ocaml_word_char = '\w|[\xc0-\xff]|'''
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000530
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000531 "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
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000570
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000571 "In: the current mode (eg. "visual", "normal", etc.)
572 "Out: the type information (calls s:Extract_type_data)
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100573 function! s:Get_type(mode, annot_file_name)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000574 let [lin1,lin2,col1,col2] = s:Match_borders(a:mode)
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100575 return s:Extract_type_data(s:Block_pattern(lin1,lin2,col1,col2), a:annot_file_name)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000576 endfun
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100577
Bram Moolenaar16ea3672013-07-28 16:02:18 +0200578 "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")
Bram Moolenaar6c391a72021-09-09 21:55:11 +0200586 "remove space at beginning of string.
Bram Moolenaar16ea3672013-07-28 16:02:18 +0200587 let res = substitute(res, "^ *", "", "g")
588 return res
589 endfunction
590
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000591 "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)
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100596 let annot_file_name = s:Fnameescape(expand('%:t:r')).'.annot'
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000597 call s:Locate_annotation()
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100598 call s:Load_annotation(annot_file_name)
Bram Moolenaar16ea3672013-07-28 16:02:18 +0200599 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
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000603 endfun
604 endif
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000605
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000606 if !exists("*Ocaml_get_type_or_not")
607 function Ocaml_get_type_or_not(mode)
608 let t=reltime()
609 try
Bram Moolenaar16ea3672013-07-28 16:02:18 +0200610 let res = Ocaml_get_type(a:mode)
611 return res
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000612 catch
613 return ""
614 endtry
615 endfun
616 endif
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000617
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000618 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
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000637
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000638" Maps
Bram Moolenaar16ea3672013-07-28 16:02:18 +0200639 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>`<
Bram Moolenaara5792f52005-11-23 21:25:05 +0000641
Bram Moolenaare0e39172021-01-25 21:14:57 +0100642let &cpoptions = s:cposet
Bram Moolenaara5792f52005-11-23 21:25:05 +0000643unlet s:cposet
644
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000645" vim:sw=2 fdm=indent