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