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