codekingpro/portable-devtools
114k
1" Language: OCaml2" Maintainer: David Baelde <firstname.name@ens-lyon.org>3" Mike Leary <leary@nwlink.com>4" Markus Mottl <markus.mottl@gmail.com>5" Pierre Vittet <pierre-vittet@pvittet.com>6" Stefano Zacchiroli <zack@bononia.it>7" Vincent Aravantinos <firstname.name@imag.fr>8" Riley Bruins <ribru17@gmail.com> ('commentstring')9" URL: https://github.com/ocaml/vim-ocaml10" Last Change:11" 2013 Oct 27 - Added commentstring (MM)12" 2013 Jul 26 - load default compiler settings (MM)13" 2013 Jul 24 - removed superfluous efm-setting (MM)14" 2013 Jul 22 - applied fixes supplied by Hirotaka Hamada (MM)15" 2024 May 23 - added space in commentstring (RB)16 17if exists("b:did_ftplugin")18 finish19endif20let b:did_ftplugin=121 22" Use standard compiler settings unless user wants otherwise23if !exists("current_compiler")24 :compiler ocaml25endif26 27" some macro28if exists('*fnameescape')29 function! s:Fnameescape(s)30 return fnameescape(a:s)31 endfun32else33 function! s:Fnameescape(s)34 return escape(a:s," \t\n*?[{`$\\%#'\"|!<")35 endfun36endif37 38" Error handling -- helps moving where the compiler wants you to go39let s:cposet=&cpoptions40set cpo&vim41 42" Comment string43setlocal comments=sr:(*\ ,mb:\ ,ex:*)44setlocal comments^=sr:(**,mb:\ \ ,ex:*)45setlocal commentstring=(*\ %s\ *)46 47" Add mappings, unless the user didn't want this.48if !exists("no_plugin_maps") && !exists("no_ocaml_maps")49 " (un)commenting50 if !hasmapto('<Plug>Comment')51 nmap <buffer> <LocalLeader>c <Plug>LUncomOn52 xmap <buffer> <LocalLeader>c <Plug>BUncomOn53 nmap <buffer> <LocalLeader>C <Plug>LUncomOff54 xmap <buffer> <LocalLeader>C <Plug>BUncomOff55 endif56 57 nnoremap <buffer> <Plug>LUncomOn gI(* <End> *)<ESC>58 nnoremap <buffer> <Plug>LUncomOff :s/^(\* \(.*\) \*)/\1/<CR>:noh<CR>59 xnoremap <buffer> <Plug>BUncomOn <ESC>:'<,'><CR>`<O<ESC>0i(*<ESC>`>o<ESC>0i*)<ESC>`<60 xnoremap <buffer> <Plug>BUncomOff <ESC>:'<,'><CR>`<dd`>dd`<61 62 nmap <buffer> <LocalLeader>s <Plug>OCamlSwitchEdit63 nmap <buffer> <LocalLeader>S <Plug>OCamlSwitchNewWin64 65 nmap <buffer> <LocalLeader>t <Plug>OCamlPrintType66 xmap <buffer> <LocalLeader>t <Plug>OCamlPrintType67endif68 69" Let % jump between structure elements (due to Issac Trotts)70let 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:mw76 77let b:match_ignorecase=078 79function! s:OcpGrep(bang,args) abort80 let grepprg = &l:grepprg81 let grepformat = &l:grepformat82 let shellpipe = &shellpipe83 try84 let &l:grepprg = "ocp-grep -c never"85 setlocal grepformat=%f:%l:%m86 if &shellpipe ==# '2>&1| tee' || &shellpipe ==# '|& tee'87 let &shellpipe = "| tee"88 endif89 execute 'grep! '.a:args90 if empty(a:bang) && !empty(getqflist())91 return 'cfirst'92 else93 return ''94 endif95 finally96 let &l:grepprg = grepprg97 let &l:grepformat = grepformat98 let &shellpipe = shellpipe99 endtry100endfunction101command! -bar -bang -complete=file -nargs=+ Ocpgrep exe s:OcpGrep(<q-bang>, <q-args>)102 103" switching between interfaces (.mli) and implementations (.ml)104if !exists("g:did_ocaml_switch")105 let g:did_ocaml_switch = 1106 nnoremap <Plug>OCamlSwitchEdit :<C-u>call OCaml_switch(0)<CR>107 nnoremap <Plug>OCamlSwitchNewWin :<C-u>call OCaml_switch(1)<CR>108 fun OCaml_switch(newwin)109 if (match(bufname(""), "\\.mli$") >= 0)110 let fname = s:Fnameescape(substitute(bufname(""), "\\.mli$", ".ml", ""))111 if (a:newwin == 1)112 exec "new " . fname113 else114 exec "arge " . fname115 endif116 elseif (match(bufname(""), "\\.ml$") >= 0)117 let fname = s:Fnameescape(bufname("")) . "i"118 if (a:newwin == 1)119 exec "new " . fname120 else121 exec "arge " . fname122 endif123 endif124 endfun125endif126 127" Folding support128 129" Get the modeline because folding depends on indentation130let lnum = search('^\s*(\*:o\?caml:', 'n')131let s:modeline = lnum? getline(lnum): ""132 133" Get the indentation params134let s:m = matchstr(s:modeline,'default\s*=\s*\d\+')135if s:m != ""136 let s:idef = matchstr(s:m,'\d\+')137elseif exists("g:omlet_indent")138 let s:idef = g:omlet_indent139else140 let s:idef = 2141endif142let s:m = matchstr(s:modeline,'struct\s*=\s*\d\+')143if s:m != ""144 let s:i = matchstr(s:m,'\d\+')145elseif exists("g:omlet_indent_struct")146 let s:i = g:omlet_indent_struct147else148 let s:i = s:idef149endif150 151" Set the folding method152if exists("g:ocaml_folding")153 setlocal foldmethod=expr154 setlocal foldexpr=OMLetFoldLevel(v:lnum)155endif156 157let b:undo_ftplugin = "setlocal efm< foldmethod< foldexpr<"158 \ . "| unlet! b:mw b:match_words b:match_ignorecase"159 160 161" - Only definitions below, executed once -------------------------------------162 163if exists("*OMLetFoldLevel")164 let &cpoptions = s:cposet165 unlet s:cposet166 finish167endif168 169function s:topindent(lnum)170 let l = a:lnum171 while l > 0172 if getline(l) =~ '\s*\%(\<struct\>\|\<sig\>\|\<object\>\)'173 return indent(l)174 endif175 let l = l-1176 endwhile177 return -s:i178endfunction179 180function OMLetFoldLevel(l)181 182 " This is for not merging blank lines around folds to them183 if getline(a:l) !~ '\S'184 return -1185 endif186 187 " We start folds for modules, classes, and every toplevel definition188 if getline(a:l) =~ '^\s*\%(\<val\>\|\<module\>\|\<class\>\|\<type\>\|\<method\>\|\<initializer\>\|\<inherit\>\|\<exception\>\|\<external\>\)'189 exe 'return ">' (indent(a:l)/s:i)+1 '"'190 endif191 192 " Toplevel let are detected thanks to the indentation193 if getline(a:l) =~ '^\s*let\>' && indent(a:l) == s:i+s:topindent(a:l)194 exe 'return ">' (indent(a:l)/s:i)+1 '"'195 endif196 197 " We close fold on end which are associated to struct, sig or object.198 " We use syntax information to do that.199 if getline(a:l) =~ '^\s*end\>' && synIDattr(synID(a:l, indent(a:l)+1, 0), "name") != "ocamlKeyword"200 return (indent(a:l)/s:i)+1201 endif202 203 " Folds end on ;;204 if getline(a:l) =~ '^\s*;;'205 exe 'return "<' (indent(a:l)/s:i)+1 '"'206 endif207 208 " Comments around folds aren't merged to them.209 if synIDattr(synID(a:l, indent(a:l)+1, 0), "name") == "ocamlComment"210 return -1211 endif212 213 return '='214endfunction215 216" Vim support for OCaml .annot files217"218" Last Change: 2007 Jul 17219" Maintainer: Vincent Aravantinos <vincent.aravantinos@gmail.com>220" License: public domain221"222" Originally inspired by 'ocaml-dtypes.vim' by Stefano Zacchiroli.223" The source code is quite radically different for we not use python anymore.224" However this plugin should have the exact same behaviour, that's why the225" following lines are the quite exact copy of Stefano's original plugin :226"227" <<228" Executing Ocaml_print_type(<mode>) function will display in the Vim bottom229" line(s) the type of an ocaml value getting it from the corresponding .annot230" file (if any). If Vim is in visual mode, <mode> should be "visual" and the231" selected ocaml value correspond to the highlighted text, otherwise (<mode>232" can be anything else) it corresponds to the literal found at the current233" cursor position.234"235" Typing '<LocalLeader>t' (LocalLeader defaults to '\', see :h LocalLeader)236" will cause " Ocaml_print_type function to be invoked with the right237" argument depending on the current mode (visual or not).238" >>239"240" If you find something not matching this behaviour, please signal it.241"242" Differences are:243" - no need for python support244" + plus : more portable245" + minus: no more lazy parsing, it looks very fast however246"247" - ocamlbuild support, ie.248" + the plugin finds the _build directory and looks for the249" corresponding file inside;250" + if the user decides to change the name of the _build directory thanks251" to the '-build-dir' option of ocamlbuild, the plugin will manage in252" most cases to find it out (most cases = if the source file has a unique253" name among your whole project);254" + if ocamlbuild is not used, the usual behaviour holds; ie. the .annot255" file should be in the same directory as the source file;256" + for vim plugin programmers:257" the variable 'b:_build_dir' contains the inferred path to the build258" directory, even if this one is not named '_build'.259"260" Bonus :261" - latin1 accents are handled262" - lists are handled, even on multiple lines, you don't need the visual mode263" (the cursor must be on the first bracket)264" - parenthesized expressions, arrays, and structures (ie. '(...)', '[|...|]',265" and '{...}') are handled the same way266 267 " Copied from Stefano's original plugin :268 " <<269 " .annot ocaml file representation270 "271 " File format (copied verbatim from caml-types.el)272 "273 " file ::= block *274 " block ::= position <SP> position <LF> annotation *275 " position ::= filename <SP> num <SP> num <SP> num276 " annotation ::= keyword open-paren <LF> <SP> <SP> data <LF> close-paren277 "278 " <SP> is a space character (ASCII 0x20)279 " <LF> is a line-feed character (ASCII 0x0A)280 " num is a sequence of decimal digits281 " filename is a string with the lexical conventions of O'Caml282 " open-paren is an open parenthesis (ASCII 0x28)283 " close-paren is a closed parenthesis (ASCII 0x29)284 " data is any sequence of characters where <LF> is always followed by285 " at least two space characters.286 "287 " - in each block, the two positions are respectively the start and the288 " end of the range described by the block.289 " - in a position, the filename is the name of the file, the first num290 " is the line number, the second num is the offset of the beginning291 " of the line, the third num is the offset of the position itself.292 " - the char number within the line is the difference between the third293 " and second nums.294 "295 " For the moment, the only possible keyword is \"type\"."296 " >>297 298 299" 1. Finding the annotation file even if we use ocamlbuild300 301 " In: two strings representing paths302 " Out: one string representing the common prefix between the two paths303 function! s:Find_common_path (p1,p2)304 let temp = a:p2305 while matchstr(a:p1,temp) == ''306 let temp = substitute(temp,'/[^/]*$','','')307 endwhile308 return temp309 endfun310 311 " After call:312 "313 " Following information have been put in s:annot_file_list, using314 " annot_file_name name as key:315 " - annot_file_path :316 " path to the .annot file corresponding to the317 " source file (dealing with ocamlbuild stuff)318 " - _build_path:319 " path to the build directory even if this one is320 " not named '_build'321 " - date_of_last annot:322 " Set to 0 until we load the file. It contains the323 " date at which the file has been loaded.324 function! s:Locate_annotation()325 let annot_file_name = s:Fnameescape(expand('%:t:r')).'.annot'326 if !exists ("s:annot_file_list[annot_file_name]")327 silent exe 'cd' s:Fnameescape(expand('%:p:h'))328 " 1st case : the annot file is in the same directory as the buffer (no ocamlbuild)329 let annot_file_path = findfile(annot_file_name,'.')330 if annot_file_path != ''331 let annot_file_path = getcwd().'/'.annot_file_path332 let _build_path = ''333 else334 " 2nd case : the buffer and the _build directory are in the same directory335 " ..336 " / \337 " / \338 " _build .ml339 "340 let _build_path = finddir('_build','.')341 if _build_path != ''342 let _build_path = getcwd().'/'._build_path343 let annot_file_path = findfile(annot_file_name,'_build')344 if annot_file_path != ''345 let annot_file_path = getcwd().'/'.annot_file_path346 endif347 else348 " 3rd case : the _build directory is in a directory higher in the file hierarchy349 " (it can't be deeper by ocamlbuild requirements)350 " ..351 " / \352 " / \353 " _build ...354 " \355 " \356 " .ml357 "358 let _build_path = finddir('_build',';')359 if _build_path != ''360 let project_path = substitute(_build_path,'/_build$','','')361 let path_relative_to_project = s:Fnameescape(substitute(expand('%:p:h'),project_path.'/','',''))362 let annot_file_path = findfile(annot_file_name,project_path.'/_build/'.path_relative_to_project)363 else364 let annot_file_path = findfile(annot_file_name,'**')365 "4th case : what if the user decided to change the name of the _build directory ?366 " -> we relax the constraints, it should work in most cases367 if annot_file_path != ''368 " 4a. we suppose the renamed _build directory is in the current directory369 let _build_path = matchstr(annot_file_path,'^[^/]*')370 if annot_file_path != ''371 let annot_file_path = getcwd().'/'.annot_file_path372 let _build_path = getcwd().'/'._build_path373 endif374 else375 let annot_file_name = ''376 "(Pierre Vittet: I have commented 4b because this was crashing377 "my vim (it produced infinite loop))378 "379 " 4b. anarchy : the renamed _build directory may be higher in the hierarchy380 " this will work if the file for which we are looking annotations has a unique name in the whole project381 " if this is not the case, it may still work, but no warranty here382 "let annot_file_path = findfile(annot_file_name,'**;')383 "let project_path = s:Find_common_path(annot_file_path,expand('%:p:h'))384 "let _build_path = matchstr(annot_file_path,project_path.'/[^/]*')385 endif386 endif387 endif388 endif389 390 if annot_file_path == ''391 throw 'E484: no annotation file found'392 endif393 394 silent exe 'cd' '-'395 let s:annot_file_list[annot_file_name]= [annot_file_path, _build_path, 0]396 endif397 endfun398 399 " This variable contains a dictionary of lists. Each element of the dictionary400 " represents an annotation system. An annotation system is a list with:401 " - annotation file name as its key402 " - annotation file path as first element of the contained list403 " - build path as second element of the contained list404 " - annot_file_last_mod (contain the date of .annot file) as third element405 let s:annot_file_list = {}406 407" 2. Finding the type information in the annotation file408 409 " a. The annotation file is opened in vim as a buffer that410 " should be (almost) invisible to the user.411 412 " After call:413 " The current buffer is now the one containing the .annot file.414 " We manage to keep all this hidden to the user's eye.415 function! s:Enter_annotation_buffer(annot_file_path)416 let s:current_pos = getpos('.')417 let s:current_hidden = &l:hidden418 set hidden419 let s:current_buf = bufname('%')420 if bufloaded(a:annot_file_path)421 silent exe 'keepj keepalt' 'buffer' s:Fnameescape(a:annot_file_path)422 else423 silent exe 'keepj keepalt' 'view' s:Fnameescape(a:annot_file_path)424 endif425 call setpos(".", [0, 0 , 0 , 0])426 endfun427 428 " After call:429 " The original buffer has been restored in the exact same state as before.430 function! s:Exit_annotation_buffer()431 silent exe 'keepj keepalt' 'buffer' s:Fnameescape(s:current_buf)432 let &l:hidden = s:current_hidden433 call setpos('.',s:current_pos)434 endfun435 436 " After call:437 " The annot file is loaded and assigned to a buffer.438 " This also handles the modification date of the .annot file, eg. after a439 " compilation (return an updated annot_file_list).440 function! s:Load_annotation(annot_file_name)441 let annot = s:annot_file_list[a:annot_file_name]442 let annot_file_path = annot[0]443 let annot_file_last_mod = 0444 if exists("annot[2]")445 let annot_file_last_mod = annot[2]446 endif447 if bufloaded(annot_file_path) && annot_file_last_mod < getftime(annot_file_path)448 " if there is a more recent file449 let nr = bufnr(annot_file_path)450 silent exe 'keepj keepalt' 'bunload' nr451 endif452 if !bufloaded(annot_file_path)453 call s:Enter_annotation_buffer(annot_file_path)454 setlocal nobuflisted455 setlocal bufhidden=hide456 setlocal noswapfile457 setlocal buftype=nowrite458 call s:Exit_annotation_buffer()459 let annot[2] = getftime(annot_file_path)460 " List updated with the new date461 let s:annot_file_list[a:annot_file_name] = annot462 endif463 endfun464 465 "b. 'search' and 'match' work to find the type information466 467 "In: - lin1,col1: position of expression first char468 " - lin2,col2: position of expression last char469 "Out: - the pattern to be looked for to find the block470 " Must be called in the source buffer (use of line2byte)471 function! s:Block_pattern(lin1,lin2,col1,col2)472 let start_num1 = a:lin1473 let start_num2 = line2byte(a:lin1) - 1474 let start_num3 = start_num2 + a:col1475 let path = '"\(\\"\|[^"]\)\+"'476 let start_pos = path.' '.start_num1.' '.start_num2.' '.start_num3477 let end_num1 = a:lin2478 let end_num2 = line2byte(a:lin2) - 1479 let end_num3 = end_num2 + a:col2480 let end_pos = path.' '.end_num1.' '.end_num2.' '.end_num3481 return '^'.start_pos.' '.end_pos."$"482 " rq: the '^' here is not totally correct regarding the annot file "grammar"483 " but currently the annotation file respects this, and it's a little bit faster with the '^';484 " can be removed safely.485 endfun486 487 "In: (the cursor position should be at the start of an annotation)488 "Out: the type information489 " Must be called in the annotation buffer (use of search)490 function! s:Match_data()491 " rq: idem as previously, in the following, the '^' at start of patterns is not necessary492 keepj while search('^type($','ce',line(".")) == 0493 keepj if search('^.\{-}($','e') == 0494 throw "no_annotation"495 endif496 keepj if searchpair('(','',')') == 0497 throw "malformed_annot_file"498 endif499 endwhile500 let begin = line(".") + 1501 keepj if searchpair('(','',')') == 0502 throw "malformed_annot_file"503 endif504 let end = line(".") - 1505 return join(getline(begin,end),"\n")506 endfun507 508 "In: the pattern to look for in order to match the block509 "Out: the type information (calls s:Match_data)510 " Should be called in the annotation buffer511 function! s:Extract_type_data(block_pattern, annot_file_name)512 let annot_file_path = s:annot_file_list[a:annot_file_name][0]513 call s:Enter_annotation_buffer(annot_file_path)514 try515 if search(a:block_pattern,'e') == 0516 throw "no_annotation"517 endif518 call cursor(line(".") + 1,1)519 let annotation = s:Match_data()520 finally521 call s:Exit_annotation_buffer()522 endtry523 return annotation524 endfun525 526 "c. link this stuff with what the user wants527 " ie. get the expression selected/under the cursor528 529 let s:ocaml_word_char = '\w|[\xc0-\xff]|'''530 531 "In: the current mode (eg. "visual", "normal", etc.)532 "Out: the borders of the expression we are looking for the type533 function! s:Match_borders(mode)534 if a:mode == "visual"535 let cur = getpos(".")536 normal `<537 let col1 = col(".")538 let lin1 = line(".")539 normal `>540 let col2 = col(".")541 let lin2 = line(".")542 call cursor(cur[1],cur[2])543 return [lin1,lin2,col1-1,col2]544 else545 let cursor_line = line(".")546 let cursor_col = col(".")547 let line = getline('.')548 if line[cursor_col-1:cursor_col] == '[|'549 let [lin2,col2] = searchpairpos('\[|','','|\]','n')550 return [cursor_line,lin2,cursor_col-1,col2+1]551 elseif line[cursor_col-1] == '['552 let [lin2,col2] = searchpairpos('\[','','\]','n')553 return [cursor_line,lin2,cursor_col-1,col2]554 elseif line[cursor_col-1] == '('555 let [lin2,col2] = searchpairpos('(','',')','n')556 return [cursor_line,lin2,cursor_col-1,col2]557 elseif line[cursor_col-1] == '{'558 let [lin2,col2] = searchpairpos('{','','}','n')559 return [cursor_line,lin2,cursor_col-1,col2]560 else561 let [lin1,col1] = searchpos('\v%('.s:ocaml_word_char.'|\.)*','ncb')562 let [lin2,col2] = searchpos('\v%('.s:ocaml_word_char.'|\.)*','nce')563 if col1 == 0 || col2 == 0564 throw "no_expression"565 endif566 return [cursor_line,cursor_line,col1-1,col2]567 endif568 endif569 endfun570 571 "In: the current mode (eg. "visual", "normal", etc.)572 "Out: the type information (calls s:Extract_type_data)573 function! s:Get_type(mode, annot_file_name)574 let [lin1,lin2,col1,col2] = s:Match_borders(a:mode)575 return s:Extract_type_data(s:Block_pattern(lin1,lin2,col1,col2), a:annot_file_name)576 endfun577 578 "In: A string destined to be printed in the 'echo buffer'. It has line579 "break and 2 space at each line beginning.580 "Out: A string destined to be yanked, without space and double space.581 function s:unformat_ocaml_type(res)582 "Remove end of line.583 let res = substitute (a:res, "\n", "", "g" )584 "remove double space585 let res =substitute(res , " ", " ", "g")586 "remove space at beginning of string.587 let res = substitute(res, "^ *", "", "g")588 return res589 endfunction590 591 "d. main592 "In: the current mode (eg. "visual", "normal", etc.)593 "After call: the type information is displayed594 if !exists("*Ocaml_get_type")595 function Ocaml_get_type(mode)596 let annot_file_name = s:Fnameescape(expand('%:t:r')).'.annot'597 call s:Locate_annotation()598 call s:Load_annotation(annot_file_name)599 let res = s:Get_type(a:mode, annot_file_name)600 " Copy result in the unnamed buffer601 let @" = s:unformat_ocaml_type(res)602 return res603 endfun604 endif605 606 if !exists("*Ocaml_get_type_or_not")607 function Ocaml_get_type_or_not(mode)608 let t=reltime()609 try610 let res = Ocaml_get_type(a:mode)611 return res612 catch613 return ""614 endtry615 endfun616 endif617 618 if !exists("*Ocaml_print_type")619 function Ocaml_print_type(mode)620 if expand("%:e") == "mli"621 echohl ErrorMsg | echo "No annotations for interface (.mli) files" | echohl None622 return623 endif624 try625 echo Ocaml_get_type(a:mode)626 catch /E484:/627 echohl ErrorMsg | echo "No type annotations (.annot) file found" | echohl None628 catch /no_expression/629 echohl ErrorMsg | echo "No expression found under the cursor" | echohl None630 catch /no_annotation/631 echohl ErrorMsg | echo "No type annotation found for the given text" | echohl None632 catch /malformed_annot_file/633 echohl ErrorMsg | echo "Malformed .annot file" | echohl None634 endtry635 endfun636 endif637 638" Maps639 nnoremap <silent> <Plug>OCamlPrintType :<C-U>call Ocaml_print_type("normal")<CR>640 xnoremap <silent> <Plug>OCamlPrintType :<C-U>call Ocaml_print_type("visual")<CR>`<641 642let &cpoptions = s:cposet643unlet s:cposet644 645" vim:sw=2 fdm=indent646 