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