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