blob: e0d7efe9974173a23049151c6be5a9e126f75be3 [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>
Bram Moolenaar790c18b2019-07-04 17:22:06 +02008" URL: https://github.com/rgrinberg/vim-ocaml
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +01009" Last Change:
Bram Moolenaar790c18b2019-07-04 17:22:06 +020010" 2013 Oct 27 - Added commentstring (MM)
Bram Moolenaar16ea3672013-07-28 16:02:18 +020011" 2013 Jul 26 - load default compiler settings (MM)
12" 2013 Jul 24 - removed superfluous efm-setting (MM)
13" 2013 Jul 22 - applied fixes supplied by Hirotaka Hamada (MM)
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +010014
Bram Moolenaar4c3f5362006-04-11 21:38:50 +000015if exists("b:did_ftplugin")
16 finish
17endif
Bram Moolenaara5792f52005-11-23 21:25:05 +000018let b:did_ftplugin=1
Bram Moolenaar071d4272004-06-13 20:20:40 +000019
Bram Moolenaar16ea3672013-07-28 16:02:18 +020020" Use standard compiler settings unless user wants otherwise
21if !exists("current_compiler")
22 :compiler ocaml
23endif
24
Bram Moolenaare37d50a2008-08-06 17:06:04 +000025" some macro
26if exists('*fnameescape')
27 function! s:Fnameescape(s)
28 return fnameescape(a:s)
29 endfun
30else
31 function! s:Fnameescape(s)
32 return escape(a:s," \t\n*?[{`$\\%#'\"|!<")
33 endfun
34endif
35
Bram Moolenaara5792f52005-11-23 21:25:05 +000036" Error handling -- helps moving where the compiler wants you to go
37let s:cposet=&cpoptions
Bram Moolenaarf1568ec2011-12-14 21:17:39 +010038set cpo&vim
Bram Moolenaar071d4272004-06-13 20:20:40 +000039
Bram Moolenaar790c18b2019-07-04 17:22:06 +020040" Comment string
41setlocal comments=
42setlocal commentstring=(*%s*)
43
Bram Moolenaar071d4272004-06-13 20:20:40 +000044" Add mappings, unless the user didn't want this.
45if !exists("no_plugin_maps") && !exists("no_ocaml_maps")
Bram Moolenaara5792f52005-11-23 21:25:05 +000046 " (un)commenting
Bram Moolenaar071d4272004-06-13 20:20:40 +000047 if !hasmapto('<Plug>Comment')
48 nmap <buffer> <LocalLeader>c <Plug>LUncomOn
Bram Moolenaar16ea3672013-07-28 16:02:18 +020049 xmap <buffer> <LocalLeader>c <Plug>BUncomOn
Bram Moolenaar071d4272004-06-13 20:20:40 +000050 nmap <buffer> <LocalLeader>C <Plug>LUncomOff
Bram Moolenaar16ea3672013-07-28 16:02:18 +020051 xmap <buffer> <LocalLeader>C <Plug>BUncomOff
Bram Moolenaar071d4272004-06-13 20:20:40 +000052 endif
53
Bram Moolenaar16ea3672013-07-28 16:02:18 +020054 nnoremap <buffer> <Plug>LUncomOn gI(* <End> *)<ESC>
Bram Moolenaara5792f52005-11-23 21:25:05 +000055 nnoremap <buffer> <Plug>LUncomOff :s/^(\* \(.*\) \*)/\1/<CR>:noh<CR>
Bram Moolenaar16ea3672013-07-28 16:02:18 +020056 xnoremap <buffer> <Plug>BUncomOn <ESC>:'<,'><CR>`<O<ESC>0i(*<ESC>`>o<ESC>0i*)<ESC>`<
57 xnoremap <buffer> <Plug>BUncomOff <ESC>:'<,'><CR>`<dd`>dd`<
Bram Moolenaar071d4272004-06-13 20:20:40 +000058
Bram Moolenaar16ea3672013-07-28 16:02:18 +020059 nmap <buffer> <LocalLeader>s <Plug>OCamlSwitchEdit
60 nmap <buffer> <LocalLeader>S <Plug>OCamlSwitchNewWin
61
62 nmap <buffer> <LocalLeader>t <Plug>OCamlPrintType
63 xmap <buffer> <LocalLeader>t <Plug>OCamlPrintType
Bram Moolenaar071d4272004-06-13 20:20:40 +000064endif
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000065
66" Let % jump between structure elements (due to Issac Trotts)
Bram Moolenaar790c18b2019-07-04 17:22:06 +020067let b:mw = '\<let\>:\<and\>:\(\<in\>\|;;\)'
Bram Moolenaara5792f52005-11-23 21:25:05 +000068let b:mw = b:mw . ',\<if\>:\<then\>:\<else\>'
Bram Moolenaar790c18b2019-07-04 17:22:06 +020069let b:mw = b:mw . ',\<\(for\|while\)\>:\<do\>:\<done\>'
Bram Moolenaara5792f52005-11-23 21:25:05 +000070let b:mw = b:mw . ',\<\(object\|sig\|struct\|begin\)\>:\<end\>'
71let b:mw = b:mw . ',\<\(match\|try\)\>:\<with\>'
72let b:match_words = b:mw
73
74let b:match_ignorecase=0
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000075
Bram Moolenaar790c18b2019-07-04 17:22:06 +020076function! s:OcpGrep(bang,args) abort
77 let grepprg = &l:grepprg
78 let grepformat = &l:grepformat
79 let shellpipe = &shellpipe
80 try
81 let &l:grepprg = "ocp-grep -c never"
82 setlocal grepformat=%f:%l:%m
83 if &shellpipe ==# '2>&1| tee' || &shellpipe ==# '|& tee'
84 let &shellpipe = "| tee"
85 endif
86 execute 'grep! '.a:args
87 if empty(a:bang) && !empty(getqflist())
88 return 'cfirst'
89 else
90 return ''
91 endif
92 finally
93 let &l:grepprg = grepprg
94 let &l:grepformat = grepformat
95 let &shellpipe = shellpipe
96 endtry
97endfunction
98command! -bar -bang -complete=file -nargs=+ Ocpgrep exe s:OcpGrep(<q-bang>, <q-args>)
99
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000100" switching between interfaces (.mli) and implementations (.ml)
101if !exists("g:did_ocaml_switch")
102 let g:did_ocaml_switch = 1
Bram Moolenaar16ea3672013-07-28 16:02:18 +0200103 nnoremap <Plug>OCamlSwitchEdit :<C-u>call OCaml_switch(0)<CR>
104 nnoremap <Plug>OCamlSwitchNewWin :<C-u>call OCaml_switch(1)<CR>
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000105 fun OCaml_switch(newwin)
106 if (match(bufname(""), "\\.mli$") >= 0)
Bram Moolenaare37d50a2008-08-06 17:06:04 +0000107 let fname = s:Fnameescape(substitute(bufname(""), "\\.mli$", ".ml", ""))
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000108 if (a:newwin == 1)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000109 exec "new " . fname
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000110 else
Bram Moolenaara5792f52005-11-23 21:25:05 +0000111 exec "arge " . fname
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000112 endif
113 elseif (match(bufname(""), "\\.ml$") >= 0)
Bram Moolenaare37d50a2008-08-06 17:06:04 +0000114 let fname = s:Fnameescape(bufname("")) . "i"
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000115 if (a:newwin == 1)
Bram Moolenaara5792f52005-11-23 21:25:05 +0000116 exec "new " . fname
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000117 else
Bram Moolenaara5792f52005-11-23 21:25:05 +0000118 exec "arge " . fname
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000119 endif
120 endif
121 endfun
122endif
123
Bram Moolenaara5792f52005-11-23 21:25:05 +0000124" Folding support
125
126" Get the modeline because folding depends on indentation
Bram Moolenaar790c18b2019-07-04 17:22:06 +0200127let lnum = search('^\s*(\*:o\?caml:', 'n')
128let s:modeline = lnum? getline(lnum): ""
Bram Moolenaara5792f52005-11-23 21:25:05 +0000129
130" Get the indentation params
131let s:m = matchstr(s:modeline,'default\s*=\s*\d\+')
132if s:m != ""
133 let s:idef = matchstr(s:m,'\d\+')
134elseif exists("g:omlet_indent")
135 let s:idef = g:omlet_indent
136else
137 let s:idef = 2
138endif
139let s:m = matchstr(s:modeline,'struct\s*=\s*\d\+')
140if s:m != ""
141 let s:i = matchstr(s:m,'\d\+')
142elseif exists("g:omlet_indent_struct")
143 let s:i = g:omlet_indent_struct
144else
145 let s:i = s:idef
146endif
147
148" Set the folding method
149if exists("g:ocaml_folding")
150 setlocal foldmethod=expr
151 setlocal foldexpr=OMLetFoldLevel(v:lnum)
152endif
153
Bram Moolenaar16ea3672013-07-28 16:02:18 +0200154let b:undo_ftplugin = "setlocal efm< foldmethod< foldexpr<"
155 \ . "| unlet! b:mw b:match_words b:match_ignorecase"
156
157
Bram Moolenaara5792f52005-11-23 21:25:05 +0000158" - Only definitions below, executed once -------------------------------------
159
160if exists("*OMLetFoldLevel")
161 finish
162endif
163
164function s:topindent(lnum)
165 let l = a:lnum
166 while l > 0
167 if getline(l) =~ '\s*\%(\<struct\>\|\<sig\>\|\<object\>\)'
168 return indent(l)
169 endif
170 let l = l-1
171 endwhile
172 return -s:i
173endfunction
174
175function OMLetFoldLevel(l)
176
177 " This is for not merging blank lines around folds to them
178 if getline(a:l) !~ '\S'
179 return -1
180 endif
181
182 " We start folds for modules, classes, and every toplevel definition
183 if getline(a:l) =~ '^\s*\%(\<val\>\|\<module\>\|\<class\>\|\<type\>\|\<method\>\|\<initializer\>\|\<inherit\>\|\<exception\>\|\<external\>\)'
184 exe 'return ">' (indent(a:l)/s:i)+1 '"'
185 endif
186
187 " Toplevel let are detected thanks to the indentation
188 if getline(a:l) =~ '^\s*let\>' && indent(a:l) == s:i+s:topindent(a:l)
189 exe 'return ">' (indent(a:l)/s:i)+1 '"'
190 endif
191
192 " We close fold on end which are associated to struct, sig or object.
193 " We use syntax information to do that.
194 if getline(a:l) =~ '^\s*end\>' && synIDattr(synID(a:l, indent(a:l)+1, 0), "name") != "ocamlKeyword"
195 return (indent(a:l)/s:i)+1
196 endif
197
198 " Folds end on ;;
199 if getline(a:l) =~ '^\s*;;'
200 exe 'return "<' (indent(a:l)/s:i)+1 '"'
201 endif
202
203 " Comments around folds aren't merged to them.
204 if synIDattr(synID(a:l, indent(a:l)+1, 0), "name") == "ocamlComment"
205 return -1
206 endif
207
208 return '='
209endfunction
210
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000211" Vim support for OCaml .annot files
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000212"
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000213" Last Change: 2007 Jul 17
214" Maintainer: Vincent Aravantinos <vincent.aravantinos@gmail.com>
215" License: public domain
216"
217" Originally inspired by 'ocaml-dtypes.vim' by Stefano Zacchiroli.
218" The source code is quite radically different for we not use python anymore.
219" However this plugin should have the exact same behaviour, that's why the
220" following lines are the quite exact copy of Stefano's original plugin :
221"
222" <<
223" Executing Ocaml_print_type(<mode>) function will display in the Vim bottom
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000224" line(s) the type of an ocaml value getting it from the corresponding .annot
225" file (if any). If Vim is in visual mode, <mode> should be "visual" and the
226" selected ocaml value correspond to the highlighted text, otherwise (<mode>
227" can be anything else) it corresponds to the literal found at the current
228" cursor position.
229"
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000230" Typing '<LocalLeader>t' (LocalLeader defaults to '\', see :h LocalLeader)
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100231" will cause " Ocaml_print_type function to be invoked with the right
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000232" argument depending on the current mode (visual or not).
233" >>
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000234"
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000235" If you find something not matching this behaviour, please signal it.
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000236"
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000237" Differences are:
238" - no need for python support
239" + plus : more portable
240" + minus: no more lazy parsing, it looks very fast however
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100241"
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000242" - ocamlbuild support, ie.
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100243" + the plugin finds the _build directory and looks for the
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000244" corresponding file inside;
245" + if the user decides to change the name of the _build directory thanks
246" to the '-build-dir' option of ocamlbuild, the plugin will manage in
247" most cases to find it out (most cases = if the source file has a unique
248" name among your whole project);
249" + if ocamlbuild is not used, the usual behaviour holds; ie. the .annot
250" file should be in the same directory as the source file;
251" + for vim plugin programmers:
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100252" the variable 'b:_build_dir' contains the inferred path to the build
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000253" directory, even if this one is not named '_build'.
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000254"
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000255" Bonus :
256" - latin1 accents are handled
257" - lists are handled, even on multiple lines, you don't need the visual mode
258" (the cursor must be on the first bracket)
259" - parenthesized expressions, arrays, and structures (ie. '(...)', '[|...|]',
260" and '{...}') are handled the same way
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000261
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000262 " Copied from Stefano's original plugin :
263 " <<
264 " .annot ocaml file representation
265 "
266 " File format (copied verbatim from caml-types.el)
267 "
268 " file ::= block *
269 " block ::= position <SP> position <LF> annotation *
270 " position ::= filename <SP> num <SP> num <SP> num
271 " annotation ::= keyword open-paren <LF> <SP> <SP> data <LF> close-paren
272 "
273 " <SP> is a space character (ASCII 0x20)
274 " <LF> is a line-feed character (ASCII 0x0A)
275 " num is a sequence of decimal digits
276 " filename is a string with the lexical conventions of O'Caml
277 " open-paren is an open parenthesis (ASCII 0x28)
278 " close-paren is a closed parenthesis (ASCII 0x29)
279 " data is any sequence of characters where <LF> is always followed by
280 " at least two space characters.
281 "
282 " - in each block, the two positions are respectively the start and the
283 " end of the range described by the block.
284 " - in a position, the filename is the name of the file, the first num
285 " is the line number, the second num is the offset of the beginning
286 " of the line, the third num is the offset of the position itself.
287 " - the char number within the line is the difference between the third
288 " and second nums.
289 "
290 " For the moment, the only possible keyword is \"type\"."
291 " >>
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000292
Bram Moolenaare37d50a2008-08-06 17:06:04 +0000293
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000294" 1. Finding the annotation file even if we use ocamlbuild
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000295
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000296 " In: two strings representing paths
297 " Out: one string representing the common prefix between the two paths
298 function! s:Find_common_path (p1,p2)
299 let temp = a:p2
300 while matchstr(a:p1,temp) == ''
301 let temp = substitute(temp,'/[^/]*$','','')
302 endwhile
303 return temp
304 endfun
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000305
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000306 " After call:
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100307 "
308 " Following information have been put in s:annot_file_list, using
309 " annot_file_name name as key:
310 " - annot_file_path :
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000311 " path to the .annot file corresponding to the
312 " source file (dealing with ocamlbuild stuff)
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100313 " - _build_path:
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000314 " path to the build directory even if this one is
315 " not named '_build'
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100316 " - date_of_last annot:
317 " Set to 0 until we load the file. It contains the
318 " date at which the file has been loaded.
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000319 function! s:Locate_annotation()
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100320 let annot_file_name = s:Fnameescape(expand('%:t:r')).'.annot'
321 if !exists ("s:annot_file_list[annot_file_name]")
Bram Moolenaare37d50a2008-08-06 17:06:04 +0000322 silent exe 'cd' s:Fnameescape(expand('%:p:h'))
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000323 " 1st case : the annot file is in the same directory as the buffer (no ocamlbuild)
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100324 let annot_file_path = findfile(annot_file_name,'.')
325 if annot_file_path != ''
326 let annot_file_path = getcwd().'/'.annot_file_path
327 let _build_path = ''
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000328 else
329 " 2nd case : the buffer and the _build directory are in the same directory
330 " ..
331 " / \
332 " / \
333 " _build .ml
334 "
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100335 let _build_path = finddir('_build','.')
336 if _build_path != ''
337 let _build_path = getcwd().'/'._build_path
338 let annot_file_path = findfile(annot_file_name,'_build')
339 if annot_file_path != ''
340 let annot_file_path = getcwd().'/'.annot_file_path
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000341 endif
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000342 else
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100343 " 3rd case : the _build directory is in a directory higher in the file hierarchy
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000344 " (it can't be deeper by ocamlbuild requirements)
345 " ..
346 " / \
347 " / \
348 " _build ...
349 " \
350 " \
351 " .ml
352 "
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100353 let _build_path = finddir('_build',';')
354 if _build_path != ''
355 let project_path = substitute(_build_path,'/_build$','','')
Bram Moolenaare37d50a2008-08-06 17:06:04 +0000356 let path_relative_to_project = s:Fnameescape(substitute(expand('%:p:h'),project_path.'/','',''))
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100357 let annot_file_path = findfile(annot_file_name,project_path.'/_build/'.path_relative_to_project)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000358 else
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100359 let annot_file_path = findfile(annot_file_name,'**')
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000360 "4th case : what if the user decided to change the name of the _build directory ?
361 " -> we relax the constraints, it should work in most cases
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100362 if annot_file_path != ''
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000363 " 4a. we suppose the renamed _build directory is in the current directory
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100364 let _build_path = matchstr(annot_file_path,'^[^/]*')
365 if annot_file_path != ''
366 let annot_file_path = getcwd().'/'.annot_file_path
367 let _build_path = getcwd().'/'._build_path
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000368 endif
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000369 else
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100370 let annot_file_name = ''
371 "(Pierre Vittet: I have commented 4b because this was chrashing
372 "my vim (it produced infinite loop))
373 "
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000374 " 4b. anarchy : the renamed _build directory may be higher in the hierarchy
375 " this will work if the file for which we are looking annotations has a unique name in the whole project
376 " if this is not the case, it may still work, but no warranty here
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100377 "let annot_file_path = findfile(annot_file_name,'**;')
378 "let project_path = s:Find_common_path(annot_file_path,expand('%:p:h'))
379 "let _build_path = matchstr(annot_file_path,project_path.'/[^/]*')
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000380 endif
381 endif
382 endif
383 endif
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000384
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100385 if annot_file_path == ''
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000386 throw 'E484: no annotation file found'
387 endif
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000388
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000389 silent exe 'cd' '-'
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100390 let s:annot_file_list[annot_file_name]= [annot_file_path, _build_path, 0]
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000391 endif
392 endfun
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000393
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100394 " This variable contain a dictionnary of list. Each element of the dictionnary
Bram Moolenaar790c18b2019-07-04 17:22:06 +0200395 " represent an annotation system. An annotation system is a list with :
396 " - annotation file name as it's key
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100397 " - annotation file path as first element of the contained list
398 " - build path as second element of the contained list
399 " - annot_file_last_mod (contain the date of .annot file) as third element
400 let s:annot_file_list = {}
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000401
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000402" 2. Finding the type information in the annotation file
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100403
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000404 " a. The annotation file is opened in vim as a buffer that
405 " should be (almost) invisible to the user.
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000406
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000407 " After call:
408 " The current buffer is now the one containing the .annot file.
409 " We manage to keep all this hidden to the user's eye.
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100410 function! s:Enter_annotation_buffer(annot_file_path)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000411 let s:current_pos = getpos('.')
412 let s:current_hidden = &l:hidden
413 set hidden
414 let s:current_buf = bufname('%')
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100415 if bufloaded(a:annot_file_path)
416 silent exe 'keepj keepalt' 'buffer' s:Fnameescape(a:annot_file_path)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000417 else
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100418 silent exe 'keepj keepalt' 'view' s:Fnameescape(a:annot_file_path)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000419 endif
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100420 call setpos(".", [0, 0 , 0 , 0])
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000421 endfun
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000422
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000423 " After call:
424 " The original buffer has been restored in the exact same state as before.
425 function! s:Exit_annotation_buffer()
Bram Moolenaare37d50a2008-08-06 17:06:04 +0000426 silent exe 'keepj keepalt' 'buffer' s:Fnameescape(s:current_buf)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000427 let &l:hidden = s:current_hidden
428 call setpos('.',s:current_pos)
429 endfun
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000430
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000431 " After call:
432 " The annot file is loaded and assigned to a buffer.
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100433 " This also handles the modification date of the .annot file, eg. after a
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100434 " compilation (return an updated annot_file_list).
435 function! s:Load_annotation(annot_file_name)
436 let annot = s:annot_file_list[a:annot_file_name]
437 let annot_file_path = annot[0]
438 let annot_file_last_mod = 0
439 if exists("annot[2]")
440 let annot_file_last_mod = annot[2]
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000441 endif
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100442 if bufloaded(annot_file_path) && annot_file_last_mod < getftime(annot_file_path)
443 " if there is a more recent file
444 let nr = bufnr(annot_file_path)
445 silent exe 'keepj keepalt' 'bunload' nr
446 endif
447 if !bufloaded(annot_file_path)
448 call s:Enter_annotation_buffer(annot_file_path)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000449 setlocal nobuflisted
450 setlocal bufhidden=hide
451 setlocal noswapfile
452 setlocal buftype=nowrite
453 call s:Exit_annotation_buffer()
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100454 let annot[2] = getftime(annot_file_path)
455 " List updated with the new date
456 let s:annot_file_list[a:annot_file_name] = annot
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000457 endif
458 endfun
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100459
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000460 "b. 'search' and 'match' work to find the type information
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100461
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000462 "In: - lin1,col1: postion of expression first char
463 " - lin2,col2: postion of expression last char
464 "Out: - the pattern to be looked for to find the block
465 " Must be called in the source buffer (use of line2byte)
466 function! s:Block_pattern(lin1,lin2,col1,col2)
467 let start_num1 = a:lin1
468 let start_num2 = line2byte(a:lin1) - 1
469 let start_num3 = start_num2 + a:col1
Bram Moolenaar9c754c42010-07-10 15:52:35 +0200470 let path = '"\(\\"\|[^"]\)\+"'
471 let start_pos = path.' '.start_num1.' '.start_num2.' '.start_num3
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000472 let end_num1 = a:lin2
473 let end_num2 = line2byte(a:lin2) - 1
474 let end_num3 = end_num2 + a:col2
Bram Moolenaar9c754c42010-07-10 15:52:35 +0200475 let end_pos = path.' '.end_num1.' '.end_num2.' '.end_num3
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000476 return '^'.start_pos.' '.end_pos."$"
477 " rq: the '^' here is not totally correct regarding the annot file "grammar"
478 " but currently the annotation file respects this, and it's a little bit faster with the '^';
479 " can be removed safely.
480 endfun
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000481
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000482 "In: (the cursor position should be at the start of an annotation)
483 "Out: the type information
484 " Must be called in the annotation buffer (use of search)
485 function! s:Match_data()
486 " rq: idem as previously, in the following, the '^' at start of patterns is not necessary
487 keepj while search('^type($','ce',line(".")) == 0
488 keepj if search('^.\{-}($','e') == 0
489 throw "no_annotation"
490 endif
491 keepj if searchpair('(','',')') == 0
492 throw "malformed_annot_file"
493 endif
494 endwhile
495 let begin = line(".") + 1
496 keepj if searchpair('(','',')') == 0
497 throw "malformed_annot_file"
498 endif
499 let end = line(".") - 1
500 return join(getline(begin,end),"\n")
501 endfun
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100502
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000503 "In: the pattern to look for in order to match the block
504 "Out: the type information (calls s:Match_data)
505 " Should be called in the annotation buffer
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100506 function! s:Extract_type_data(block_pattern, annot_file_name)
507 let annot_file_path = s:annot_file_list[a:annot_file_name][0]
508 call s:Enter_annotation_buffer(annot_file_path)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000509 try
510 if search(a:block_pattern,'e') == 0
511 throw "no_annotation"
512 endif
513 call cursor(line(".") + 1,1)
514 let annotation = s:Match_data()
515 finally
516 call s:Exit_annotation_buffer()
517 endtry
518 return annotation
519 endfun
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100520
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000521 "c. link this stuff with what the user wants
522 " ie. get the expression selected/under the cursor
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100523
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000524 let s:ocaml_word_char = '\w|[À-ÿ]|'''
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000525
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000526 "In: the current mode (eg. "visual", "normal", etc.)
527 "Out: the borders of the expression we are looking for the type
528 function! s:Match_borders(mode)
529 if a:mode == "visual"
530 let cur = getpos(".")
531 normal `<
532 let col1 = col(".")
533 let lin1 = line(".")
534 normal `>
535 let col2 = col(".")
536 let lin2 = line(".")
537 call cursor(cur[1],cur[2])
538 return [lin1,lin2,col1-1,col2]
539 else
540 let cursor_line = line(".")
541 let cursor_col = col(".")
542 let line = getline('.')
543 if line[cursor_col-1:cursor_col] == '[|'
544 let [lin2,col2] = searchpairpos('\[|','','|\]','n')
545 return [cursor_line,lin2,cursor_col-1,col2+1]
546 elseif line[cursor_col-1] == '['
547 let [lin2,col2] = searchpairpos('\[','','\]','n')
548 return [cursor_line,lin2,cursor_col-1,col2]
549 elseif line[cursor_col-1] == '('
550 let [lin2,col2] = searchpairpos('(','',')','n')
551 return [cursor_line,lin2,cursor_col-1,col2]
552 elseif line[cursor_col-1] == '{'
553 let [lin2,col2] = searchpairpos('{','','}','n')
554 return [cursor_line,lin2,cursor_col-1,col2]
555 else
556 let [lin1,col1] = searchpos('\v%('.s:ocaml_word_char.'|\.)*','ncb')
557 let [lin2,col2] = searchpos('\v%('.s:ocaml_word_char.'|\.)*','nce')
558 if col1 == 0 || col2 == 0
559 throw "no_expression"
560 endif
561 return [cursor_line,cursor_line,col1-1,col2]
562 endif
563 endif
564 endfun
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000565
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000566 "In: the current mode (eg. "visual", "normal", etc.)
567 "Out: the type information (calls s:Extract_type_data)
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100568 function! s:Get_type(mode, annot_file_name)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000569 let [lin1,lin2,col1,col2] = s:Match_borders(a:mode)
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100570 return s:Extract_type_data(s:Block_pattern(lin1,lin2,col1,col2), a:annot_file_name)
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000571 endfun
Bram Moolenaarf1568ec2011-12-14 21:17:39 +0100572
Bram Moolenaar16ea3672013-07-28 16:02:18 +0200573 "In: A string destined to be printed in the 'echo buffer'. It has line
574 "break and 2 space at each line beginning.
575 "Out: A string destined to be yanked, without space and double space.
576 function s:unformat_ocaml_type(res)
577 "Remove end of line.
578 let res = substitute (a:res, "\n", "", "g" )
579 "remove double space
580 let res =substitute(res , " ", " ", "g")
581 "remove space at begining of string.
582 let res = substitute(res, "^ *", "", "g")
583 return res
584 endfunction
585
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000586 "d. main
587 "In: the current mode (eg. "visual", "normal", etc.)
588 "After call: the type information is displayed
589 if !exists("*Ocaml_get_type")
590 function Ocaml_get_type(mode)
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100591 let annot_file_name = s:Fnameescape(expand('%:t:r')).'.annot'
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000592 call s:Locate_annotation()
Bram Moolenaarb8ff1fb2012-02-04 21:59:01 +0100593 call s:Load_annotation(annot_file_name)
Bram Moolenaar16ea3672013-07-28 16:02:18 +0200594 let res = s:Get_type(a:mode, annot_file_name)
595 " Copy result in the unnamed buffer
596 let @" = s:unformat_ocaml_type(res)
597 return res
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000598 endfun
599 endif
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000600
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000601 if !exists("*Ocaml_get_type_or_not")
602 function Ocaml_get_type_or_not(mode)
603 let t=reltime()
604 try
Bram Moolenaar16ea3672013-07-28 16:02:18 +0200605 let res = Ocaml_get_type(a:mode)
606 return res
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000607 catch
608 return ""
609 endtry
610 endfun
611 endif
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000612
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000613 if !exists("*Ocaml_print_type")
614 function Ocaml_print_type(mode)
615 if expand("%:e") == "mli"
616 echohl ErrorMsg | echo "No annotations for interface (.mli) files" | echohl None
617 return
618 endif
619 try
620 echo Ocaml_get_type(a:mode)
621 catch /E484:/
622 echohl ErrorMsg | echo "No type annotations (.annot) file found" | echohl None
623 catch /no_expression/
624 echohl ErrorMsg | echo "No expression found under the cursor" | echohl None
625 catch /no_annotation/
626 echohl ErrorMsg | echo "No type annotation found for the given text" | echohl None
627 catch /malformed_annot_file/
628 echohl ErrorMsg | echo "Malformed .annot file" | echohl None
629 endtry
630 endfun
631 endif
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000632
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000633" Maps
Bram Moolenaar16ea3672013-07-28 16:02:18 +0200634 nnoremap <silent> <Plug>OCamlPrintType :<C-U>call Ocaml_print_type("normal")<CR>
635 xnoremap <silent> <Plug>OCamlPrintType :<C-U>call Ocaml_print_type("visual")<CR>`<
Bram Moolenaara5792f52005-11-23 21:25:05 +0000636
637let &cpoptions=s:cposet
638unlet s:cposet
639
Bram Moolenaar3577c6f2008-06-24 21:16:56 +0000640" vim:sw=2 fdm=indent