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