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