vim-polyglot/ftplugin/ocaml.vim

1134 lines
40 KiB
VimL
Raw Normal View History

if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'vim') == -1
" Language: OCaml
" Maintainer: David Baelde <firstname.name@ens-lyon.org>
" Mike Leary <leary@nwlink.com>
" Markus Mottl <markus.mottl@gmail.com>
" Pierre Vittet <pierre-vittet@pvittet.com>
" Stefano Zacchiroli <zack@bononia.it>
" Vincent Aravantinos <firstname.name@imag.fr>
" URL: http://www.ocaml.info/vim/ftplugin/ocaml.vim
" Last Change:
" 2013 Jul 26 - load default compiler settings (MM)
" 2013 Jul 24 - removed superfluous efm-setting (MM)
" 2013 Jul 22 - applied fixes supplied by Hirotaka Hamada (MM)
" 2013 Mar 15 - Improved error format (MM)
if exists("b:did_ftplugin")
finish
endif
let b:did_ftplugin=1
" Use standard compiler settings unless user wants otherwise
if !exists("current_compiler")
:compiler ocaml
endif
" some macro
if exists('*fnameescape')
function! s:Fnameescape(s)
return fnameescape(a:s)
endfun
else
function! s:Fnameescape(s)
return escape(a:s," \t\n*?[{`$\\%#'\"|!<")
endfun
endif
" Error handling -- helps moving where the compiler wants you to go
let s:cposet=&cpoptions
set cpo&vim
" Add mappings, unless the user didn't want this.
if !exists("no_plugin_maps") && !exists("no_ocaml_maps")
" (un)commenting
if !hasmapto('<Plug>Comment')
nmap <buffer> <LocalLeader>c <Plug>LUncomOn
xmap <buffer> <LocalLeader>c <Plug>BUncomOn
nmap <buffer> <LocalLeader>C <Plug>LUncomOff
xmap <buffer> <LocalLeader>C <Plug>BUncomOff
endif
nnoremap <buffer> <Plug>LUncomOn gI(* <End> *)<ESC>
nnoremap <buffer> <Plug>LUncomOff :s/^(\* \(.*\) \*)/\1/<CR>:noh<CR>
xnoremap <buffer> <Plug>BUncomOn <ESC>:'<,'><CR>`<O<ESC>0i(*<ESC>`>o<ESC>0i*)<ESC>`<
xnoremap <buffer> <Plug>BUncomOff <ESC>:'<,'><CR>`<dd`>dd`<
nmap <buffer> <LocalLeader>s <Plug>OCamlSwitchEdit
nmap <buffer> <LocalLeader>S <Plug>OCamlSwitchNewWin
nmap <buffer> <LocalLeader>t <Plug>OCamlPrintType
xmap <buffer> <LocalLeader>t <Plug>OCamlPrintType
endif
" Let % jump between structure elements (due to Issac Trotts)
let b:mw = ''
let b:mw = b:mw . ',\<let\>:\<and\>:\(\<in\>\|;;\)'
let b:mw = b:mw . ',\<if\>:\<then\>:\<else\>'
let b:mw = b:mw . ',\<\(for\|while\)\>:\<do\>:\<done\>,'
let b:mw = b:mw . ',\<\(object\|sig\|struct\|begin\)\>:\<end\>'
let b:mw = b:mw . ',\<\(match\|try\)\>:\<with\>'
let b:match_words = b:mw
let b:match_ignorecase=0
" switching between interfaces (.mli) and implementations (.ml)
if !exists("g:did_ocaml_switch")
let g:did_ocaml_switch = 1
nnoremap <Plug>OCamlSwitchEdit :<C-u>call OCaml_switch(0)<CR>
nnoremap <Plug>OCamlSwitchNewWin :<C-u>call OCaml_switch(1)<CR>
fun OCaml_switch(newwin)
if (match(bufname(""), "\\.mli$") >= 0)
let fname = s:Fnameescape(substitute(bufname(""), "\\.mli$", ".ml", ""))
if (a:newwin == 1)
exec "new " . fname
else
exec "arge " . fname
endif
elseif (match(bufname(""), "\\.ml$") >= 0)
let fname = s:Fnameescape(bufname("")) . "i"
if (a:newwin == 1)
exec "new " . fname
else
exec "arge " . fname
endif
endif
endfun
endif
" Folding support
" Get the modeline because folding depends on indentation
let s:s = line2byte(line('.'))+col('.')-1
if search('^\s*(\*:o\?caml:')
let s:modeline = getline(".")
else
let s:modeline = ""
endif
if s:s > 0
exe 'goto' s:s
endif
" Get the indentation params
let s:m = matchstr(s:modeline,'default\s*=\s*\d\+')
if s:m != ""
let s:idef = matchstr(s:m,'\d\+')
elseif exists("g:omlet_indent")
let s:idef = g:omlet_indent
else
let s:idef = 2
endif
let s:m = matchstr(s:modeline,'struct\s*=\s*\d\+')
if s:m != ""
let s:i = matchstr(s:m,'\d\+')
elseif exists("g:omlet_indent_struct")
let s:i = g:omlet_indent_struct
else
let s:i = s:idef
endif
" Set the folding method
if exists("g:ocaml_folding")
setlocal foldmethod=expr
setlocal foldexpr=OMLetFoldLevel(v:lnum)
endif
let b:undo_ftplugin = "setlocal efm< foldmethod< foldexpr<"
\ . "| unlet! b:mw b:match_words b:match_ignorecase"
" - Only definitions below, executed once -------------------------------------
if exists("*OMLetFoldLevel")
finish
endif
function s:topindent(lnum)
let l = a:lnum
while l > 0
if getline(l) =~ '\s*\%(\<struct\>\|\<sig\>\|\<object\>\)'
return indent(l)
endif
let l = l-1
endwhile
return -s:i
endfunction
function OMLetFoldLevel(l)
" This is for not merging blank lines around folds to them
if getline(a:l) !~ '\S'
return -1
endif
" We start folds for modules, classes, and every toplevel definition
if getline(a:l) =~ '^\s*\%(\<val\>\|\<module\>\|\<class\>\|\<type\>\|\<method\>\|\<initializer\>\|\<inherit\>\|\<exception\>\|\<external\>\)'
exe 'return ">' (indent(a:l)/s:i)+1 '"'
endif
" Toplevel let are detected thanks to the indentation
if getline(a:l) =~ '^\s*let\>' && indent(a:l) == s:i+s:topindent(a:l)
exe 'return ">' (indent(a:l)/s:i)+1 '"'
endif
" We close fold on end which are associated to struct, sig or object.
" We use syntax information to do that.
if getline(a:l) =~ '^\s*end\>' && synIDattr(synID(a:l, indent(a:l)+1, 0), "name") != "ocamlKeyword"
return (indent(a:l)/s:i)+1
endif
" Folds end on ;;
if getline(a:l) =~ '^\s*;;'
exe 'return "<' (indent(a:l)/s:i)+1 '"'
endif
" Comments around folds aren't merged to them.
if synIDattr(synID(a:l, indent(a:l)+1, 0), "name") == "ocamlComment"
return -1
endif
return '='
endfunction
" Vim support for OCaml .annot files
"
" Last Change: 2007 Jul 17
" Maintainer: Vincent Aravantinos <vincent.aravantinos@gmail.com>
" License: public domain
"
" Originally inspired by 'ocaml-dtypes.vim' by Stefano Zacchiroli.
" The source code is quite radically different for we not use python anymore.
" However this plugin should have the exact same behaviour, that's why the
" following lines are the quite exact copy of Stefano's original plugin :
"
" <<
" Executing Ocaml_print_type(<mode>) function will display in the Vim bottom
" line(s) the type of an ocaml value getting it from the corresponding .annot
" file (if any). If Vim is in visual mode, <mode> should be "visual" and the
" selected ocaml value correspond to the highlighted text, otherwise (<mode>
" can be anything else) it corresponds to the literal found at the current
" cursor position.
"
" Typing '<LocalLeader>t' (LocalLeader defaults to '\', see :h LocalLeader)
" will cause " Ocaml_print_type function to be invoked with the right
" argument depending on the current mode (visual or not).
" >>
"
" If you find something not matching this behaviour, please signal it.
"
" Differences are:
" - no need for python support
" + plus : more portable
" + minus: no more lazy parsing, it looks very fast however
"
" - ocamlbuild support, ie.
" + the plugin finds the _build directory and looks for the
" corresponding file inside;
" + if the user decides to change the name of the _build directory thanks
" to the '-build-dir' option of ocamlbuild, the plugin will manage in
" most cases to find it out (most cases = if the source file has a unique
" name among your whole project);
" + if ocamlbuild is not used, the usual behaviour holds; ie. the .annot
" file should be in the same directory as the source file;
" + for vim plugin programmers:
" the variable 'b:_build_dir' contains the inferred path to the build
" directory, even if this one is not named '_build'.
"
" Bonus :
" - latin1 accents are handled
" - lists are handled, even on multiple lines, you don't need the visual mode
" (the cursor must be on the first bracket)
" - parenthesized expressions, arrays, and structures (ie. '(...)', '[|...|]',
" and '{...}') are handled the same way
" Copied from Stefano's original plugin :
" <<
" .annot ocaml file representation
"
" File format (copied verbatim from caml-types.el)
"
" file ::= block *
" block ::= position <SP> position <LF> annotation *
" position ::= filename <SP> num <SP> num <SP> num
" annotation ::= keyword open-paren <LF> <SP> <SP> data <LF> close-paren
"
" <SP> is a space character (ASCII 0x20)
" <LF> is a line-feed character (ASCII 0x0A)
" num is a sequence of decimal digits
" filename is a string with the lexical conventions of O'Caml
" open-paren is an open parenthesis (ASCII 0x28)
" close-paren is a closed parenthesis (ASCII 0x29)
" data is any sequence of characters where <LF> is always followed by
" at least two space characters.
"
" - in each block, the two positions are respectively the start and the
" end of the range described by the block.
" - in a position, the filename is the name of the file, the first num
" is the line number, the second num is the offset of the beginning
" of the line, the third num is the offset of the position itself.
" - the char number within the line is the difference between the third
" and second nums.
"
" For the moment, the only possible keyword is \"type\"."
" >>
" 1. Finding the annotation file even if we use ocamlbuild
" In: two strings representing paths
" Out: one string representing the common prefix between the two paths
function! s:Find_common_path (p1,p2)
let temp = a:p2
while matchstr(a:p1,temp) == ''
let temp = substitute(temp,'/[^/]*$','','')
endwhile
return temp
endfun
" After call:
"
" Following information have been put in s:annot_file_list, using
" annot_file_name name as key:
" - annot_file_path :
" path to the .annot file corresponding to the
" source file (dealing with ocamlbuild stuff)
" - _build_path:
" path to the build directory even if this one is
" not named '_build'
" - date_of_last annot:
" Set to 0 until we load the file. It contains the
" date at which the file has been loaded.
function! s:Locate_annotation()
let annot_file_name = s:Fnameescape(expand('%:t:r')).'.annot'
if !exists ("s:annot_file_list[annot_file_name]")
silent exe 'cd' s:Fnameescape(expand('%:p:h'))
" 1st case : the annot file is in the same directory as the buffer (no ocamlbuild)
let annot_file_path = findfile(annot_file_name,'.')
if annot_file_path != ''
let annot_file_path = getcwd().'/'.annot_file_path
let _build_path = ''
else
" 2nd case : the buffer and the _build directory are in the same directory
" ..
" / \
" / \
" _build .ml
"
let _build_path = finddir('_build','.')
if _build_path != ''
let _build_path = getcwd().'/'._build_path
let annot_file_path = findfile(annot_file_name,'_build')
if annot_file_path != ''
let annot_file_path = getcwd().'/'.annot_file_path
endif
else
" 3rd case : the _build directory is in a directory higher in the file hierarchy
" (it can't be deeper by ocamlbuild requirements)
" ..
" / \
" / \
" _build ...
" \
" \
" .ml
"
let _build_path = finddir('_build',';')
if _build_path != ''
let project_path = substitute(_build_path,'/_build$','','')
let path_relative_to_project = s:Fnameescape(substitute(expand('%:p:h'),project_path.'/','',''))
let annot_file_path = findfile(annot_file_name,project_path.'/_build/'.path_relative_to_project)
else
let annot_file_path = findfile(annot_file_name,'**')
"4th case : what if the user decided to change the name of the _build directory ?
" -> we relax the constraints, it should work in most cases
if annot_file_path != ''
" 4a. we suppose the renamed _build directory is in the current directory
let _build_path = matchstr(annot_file_path,'^[^/]*')
if annot_file_path != ''
let annot_file_path = getcwd().'/'.annot_file_path
let _build_path = getcwd().'/'._build_path
endif
else
let annot_file_name = ''
"(Pierre Vittet: I have commented 4b because this was chrashing
"my vim (it produced infinite loop))
"
" 4b. anarchy : the renamed _build directory may be higher in the hierarchy
" this will work if the file for which we are looking annotations has a unique name in the whole project
" if this is not the case, it may still work, but no warranty here
"let annot_file_path = findfile(annot_file_name,'**;')
"let project_path = s:Find_common_path(annot_file_path,expand('%:p:h'))
"let _build_path = matchstr(annot_file_path,project_path.'/[^/]*')
endif
endif
endif
endif
if annot_file_path == ''
throw 'E484: no annotation file found'
endif
silent exe 'cd' '-'
let s:annot_file_list[annot_file_name]= [annot_file_path, _build_path, 0]
endif
endfun
" This variable contain a dictionnary of list. Each element of the dictionnary
" represent an annotation system. An annotation system is a list with :
" - annotation file name as it's key
" - annotation file path as first element of the contained list
" - build path as second element of the contained list
" - annot_file_last_mod (contain the date of .annot file) as third element
let s:annot_file_list = {}
" 2. Finding the type information in the annotation file
" a. The annotation file is opened in vim as a buffer that
" should be (almost) invisible to the user.
" After call:
" The current buffer is now the one containing the .annot file.
" We manage to keep all this hidden to the user's eye.
function! s:Enter_annotation_buffer(annot_file_path)
let s:current_pos = getpos('.')
let s:current_hidden = &l:hidden
set hidden
let s:current_buf = bufname('%')
if bufloaded(a:annot_file_path)
silent exe 'keepj keepalt' 'buffer' s:Fnameescape(a:annot_file_path)
else
silent exe 'keepj keepalt' 'view' s:Fnameescape(a:annot_file_path)
endif
call setpos(".", [0, 0 , 0 , 0])
endfun
" After call:
" The original buffer has been restored in the exact same state as before.
function! s:Exit_annotation_buffer()
silent exe 'keepj keepalt' 'buffer' s:Fnameescape(s:current_buf)
let &l:hidden = s:current_hidden
call setpos('.',s:current_pos)
endfun
" After call:
" The annot file is loaded and assigned to a buffer.
" This also handles the modification date of the .annot file, eg. after a
" compilation (return an updated annot_file_list).
function! s:Load_annotation(annot_file_name)
let annot = s:annot_file_list[a:annot_file_name]
let annot_file_path = annot[0]
let annot_file_last_mod = 0
if exists("annot[2]")
let annot_file_last_mod = annot[2]
endif
if bufloaded(annot_file_path) && annot_file_last_mod < getftime(annot_file_path)
" if there is a more recent file
let nr = bufnr(annot_file_path)
silent exe 'keepj keepalt' 'bunload' nr
endif
if !bufloaded(annot_file_path)
call s:Enter_annotation_buffer(annot_file_path)
setlocal nobuflisted
setlocal bufhidden=hide
setlocal noswapfile
setlocal buftype=nowrite
call s:Exit_annotation_buffer()
let annot[2] = getftime(annot_file_path)
" List updated with the new date
let s:annot_file_list[a:annot_file_name] = annot
endif
endfun
"b. 'search' and 'match' work to find the type information
"In: - lin1,col1: postion of expression first char
" - lin2,col2: postion of expression last char
"Out: - the pattern to be looked for to find the block
" Must be called in the source buffer (use of line2byte)
function! s:Block_pattern(lin1,lin2,col1,col2)
let start_num1 = a:lin1
let start_num2 = line2byte(a:lin1) - 1
let start_num3 = start_num2 + a:col1
let path = '"\(\\"\|[^"]\)\+"'
let start_pos = path.' '.start_num1.' '.start_num2.' '.start_num3
let end_num1 = a:lin2
let end_num2 = line2byte(a:lin2) - 1
let end_num3 = end_num2 + a:col2
let end_pos = path.' '.end_num1.' '.end_num2.' '.end_num3
return '^'.start_pos.' '.end_pos."$"
" rq: the '^' here is not totally correct regarding the annot file "grammar"
" but currently the annotation file respects this, and it's a little bit faster with the '^';
" can be removed safely.
endfun
"In: (the cursor position should be at the start of an annotation)
"Out: the type information
" Must be called in the annotation buffer (use of search)
function! s:Match_data()
" rq: idem as previously, in the following, the '^' at start of patterns is not necessary
keepj while search('^type($','ce',line(".")) == 0
keepj if search('^.\{-}($','e') == 0
throw "no_annotation"
endif
keepj if searchpair('(','',')') == 0
throw "malformed_annot_file"
endif
endwhile
let begin = line(".") + 1
keepj if searchpair('(','',')') == 0
throw "malformed_annot_file"
endif
let end = line(".") - 1
return join(getline(begin,end),"\n")
endfun
"In: the pattern to look for in order to match the block
"Out: the type information (calls s:Match_data)
" Should be called in the annotation buffer
function! s:Extract_type_data(block_pattern, annot_file_name)
let annot_file_path = s:annot_file_list[a:annot_file_name][0]
call s:Enter_annotation_buffer(annot_file_path)
try
if search(a:block_pattern,'e') == 0
throw "no_annotation"
endif
call cursor(line(".") + 1,1)
let annotation = s:Match_data()
finally
call s:Exit_annotation_buffer()
endtry
return annotation
endfun
"c. link this stuff with what the user wants
" ie. get the expression selected/under the cursor
let s:ocaml_word_char = '\w|[<5B>-<2D>]|'''
"In: the current mode (eg. "visual", "normal", etc.)
"Out: the borders of the expression we are looking for the type
function! s:Match_borders(mode)
if a:mode == "visual"
let cur = getpos(".")
normal `<
let col1 = col(".")
let lin1 = line(".")
normal `>
let col2 = col(".")
let lin2 = line(".")
call cursor(cur[1],cur[2])
return [lin1,lin2,col1-1,col2]
else
let cursor_line = line(".")
let cursor_col = col(".")
let line = getline('.')
if line[cursor_col-1:cursor_col] == '[|'
let [lin2,col2] = searchpairpos('\[|','','|\]','n')
return [cursor_line,lin2,cursor_col-1,col2+1]
elseif line[cursor_col-1] == '['
let [lin2,col2] = searchpairpos('\[','','\]','n')
return [cursor_line,lin2,cursor_col-1,col2]
elseif line[cursor_col-1] == '('
let [lin2,col2] = searchpairpos('(','',')','n')
return [cursor_line,lin2,cursor_col-1,col2]
elseif line[cursor_col-1] == '{'
let [lin2,col2] = searchpairpos('{','','}','n')
return [cursor_line,lin2,cursor_col-1,col2]
else
let [lin1,col1] = searchpos('\v%('.s:ocaml_word_char.'|\.)*','ncb')
let [lin2,col2] = searchpos('\v%('.s:ocaml_word_char.'|\.)*','nce')
if col1 == 0 || col2 == 0
throw "no_expression"
endif
return [cursor_line,cursor_line,col1-1,col2]
endif
endif
endfun
"In: the current mode (eg. "visual", "normal", etc.)
"Out: the type information (calls s:Extract_type_data)
function! s:Get_type(mode, annot_file_name)
let [lin1,lin2,col1,col2] = s:Match_borders(a:mode)
return s:Extract_type_data(s:Block_pattern(lin1,lin2,col1,col2), a:annot_file_name)
endfun
"In: A string destined to be printed in the 'echo buffer'. It has line
"break and 2 space at each line beginning.
"Out: A string destined to be yanked, without space and double space.
function s:unformat_ocaml_type(res)
"Remove end of line.
let res = substitute (a:res, "\n", "", "g" )
"remove double space
let res =substitute(res , " ", " ", "g")
"remove space at begining of string.
let res = substitute(res, "^ *", "", "g")
return res
endfunction
"d. main
"In: the current mode (eg. "visual", "normal", etc.)
"After call: the type information is displayed
if !exists("*Ocaml_get_type")
function Ocaml_get_type(mode)
let annot_file_name = s:Fnameescape(expand('%:t:r')).'.annot'
call s:Locate_annotation()
call s:Load_annotation(annot_file_name)
let res = s:Get_type(a:mode, annot_file_name)
" Copy result in the unnamed buffer
let @" = s:unformat_ocaml_type(res)
return res
endfun
endif
if !exists("*Ocaml_get_type_or_not")
function Ocaml_get_type_or_not(mode)
let t=reltime()
try
let res = Ocaml_get_type(a:mode)
return res
catch
return ""
endtry
endfun
endif
if !exists("*Ocaml_print_type")
function Ocaml_print_type(mode)
if expand("%:e") == "mli"
echohl ErrorMsg | echo "No annotations for interface (.mli) files" | echohl None
return
endif
try
echo Ocaml_get_type(a:mode)
catch /E484:/
echohl ErrorMsg | echo "No type annotations (.annot) file found" | echohl None
catch /no_expression/
echohl ErrorMsg | echo "No expression found under the cursor" | echohl None
catch /no_annotation/
echohl ErrorMsg | echo "No type annotation found for the given text" | echohl None
catch /malformed_annot_file/
echohl ErrorMsg | echo "Malformed .annot file" | echohl None
endtry
endfun
endif
" Maps
nnoremap <silent> <Plug>OCamlPrintType :<C-U>call Ocaml_print_type("normal")<CR>
xnoremap <silent> <Plug>OCamlPrintType :<C-U>call Ocaml_print_type("visual")<CR>`<
let &cpoptions=s:cposet
unlet s:cposet
" vim:sw=2 fdm=indent
endif
if !exists('g:polyglot_disabled') || index(g:polyglot_disabled, 'ocaml') == -1
" Language: OCaml
" Maintainer: David Baelde <firstname.name@ens-lyon.org>
" Mike Leary <leary@nwlink.com>
" Markus Mottl <markus.mottl@gmail.com>
" Stefano Zacchiroli <zack@bononia.it>
" URL: http://www.ocaml.info/vim/ftplugin/ocaml.vim
" Last Change: 2009 Nov 10 - Improved .annot support
" (MM for <radugrigore@gmail.com>)
" 2009 Nov 10 - Added support for looking up definitions
" (MM for <ygrek@autistici.org>)
"
if exists("b:did_ftplugin")
finish
endif
let b:did_ftplugin=1
" Error handling -- helps moving where the compiler wants you to go
let s:cposet=&cpoptions
set cpo-=C
setlocal efm=
\%EFile\ \"%f\"\\,\ line\ %l\\,\ characters\ %c-%*\\d:,
\%EFile\ \"%f\"\\,\ line\ %l\\,\ character\ %c:%m,
\%+EReference\ to\ unbound\ regexp\ name\ %m,
\%Eocamlyacc:\ e\ -\ line\ %l\ of\ \"%f\"\\,\ %m,
\%Wocamlyacc:\ w\ -\ %m,
\%-Zmake%.%#,
\%C%m,
\%D%*\\a[%*\\d]:\ Entering\ directory\ `%f',
\%X%*\\a[%*\\d]:\ Leaving\ directory\ `%f',
\%D%*\\a:\ Entering\ directory\ `%f',
\%X%*\\a:\ Leaving\ directory\ `%f',
\%DMaking\ %*\\a\ in\ %f
" Add mappings, unless the user didn't want this.
if !exists("no_plugin_maps") && !exists("no_ocaml_maps")
" (un)commenting
if !hasmapto('<Plug>Comment')
nmap <buffer> <LocalLeader>c <Plug>LUncomOn
vmap <buffer> <LocalLeader>c <Plug>BUncomOn
nmap <buffer> <LocalLeader>C <Plug>LUncomOff
vmap <buffer> <LocalLeader>C <Plug>BUncomOff
endif
nnoremap <buffer> <Plug>LUncomOn mz0i(* <ESC>$A *)<ESC>`z
nnoremap <buffer> <Plug>LUncomOff :s/^(\* \(.*\) \*)/\1/<CR>:noh<CR>
vnoremap <buffer> <Plug>BUncomOn <ESC>:'<,'><CR>`<O<ESC>0i(*<ESC>`>o<ESC>0i*)<ESC>`<
vnoremap <buffer> <Plug>BUncomOff <ESC>:'<,'><CR>`<dd`>dd`<
if !hasmapto('<Plug>Abbrev')
iabbrev <buffer> ASF (assert false (* XXX *))
iabbrev <buffer> ASS (assert (0=1) (* XXX *))
endif
endif
" Let % jump between structure elements (due to Issac Trotts)
let b:mw = ''
let b:mw = b:mw . ',\<let\>:\<and\>:\(\<in\>\|;;\)'
let b:mw = b:mw . ',\<if\>:\<then\>:\<else\>'
let b:mw = b:mw . ',\<\(for\|while\)\>:\<do\>:\<done\>,'
let b:mw = b:mw . ',\<\(object\|sig\|struct\|begin\)\>:\<end\>'
let b:mw = b:mw . ',\<\(match\|try\)\>:\<with\>'
let b:match_words = b:mw
let b:match_ignorecase=0
" switching between interfaces (.mli) and implementations (.ml)
if !exists("g:did_ocaml_switch")
let g:did_ocaml_switch = 1
map <LocalLeader>s :call OCaml_switch(0)<CR>
map <LocalLeader>S :call OCaml_switch(1)<CR>
fun OCaml_switch(newwin)
if (match(bufname(""), "\\.mli$") >= 0)
let fname = substitute(bufname(""), "\\.mli$", ".ml", "")
if (a:newwin == 1)
exec "new " . fname
else
exec "arge " . fname
endif
elseif (match(bufname(""), "\\.ml$") >= 0)
let fname = bufname("") . "i"
if (a:newwin == 1)
exec "new " . fname
else
exec "arge " . fname
endif
endif
endfun
endif
" Folding support
" Get the modeline because folding depends on indentation
let s:s = line2byte(line('.'))+col('.')-1
if search('^\s*(\*:o\?caml:')
let s:modeline = getline(".")
else
let s:modeline = ""
endif
if s:s > 0
exe 'goto' s:s
endif
" Get the indentation params
let s:m = matchstr(s:modeline,'default\s*=\s*\d\+')
if s:m != ""
let s:idef = matchstr(s:m,'\d\+')
elseif exists("g:omlet_indent")
let s:idef = g:omlet_indent
else
let s:idef = 2
endif
let s:m = matchstr(s:modeline,'struct\s*=\s*\d\+')
if s:m != ""
let s:i = matchstr(s:m,'\d\+')
elseif exists("g:omlet_indent_struct")
let s:i = g:omlet_indent_struct
else
let s:i = s:idef
endif
" Set the folding method
if exists("g:ocaml_folding")
setlocal foldmethod=expr
setlocal foldexpr=OMLetFoldLevel(v:lnum)
endif
" - Only definitions below, executed once -------------------------------------
if exists("*OMLetFoldLevel")
finish
endif
function s:topindent(lnum)
let l = a:lnum
while l > 0
if getline(l) =~ '\s*\%(\<struct\>\|\<sig\>\|\<object\>\)'
return indent(l)
endif
let l = l-1
endwhile
return -s:i
endfunction
function OMLetFoldLevel(l)
" This is for not merging blank lines around folds to them
if getline(a:l) !~ '\S'
return -1
endif
" We start folds for modules, classes, and every toplevel definition
if getline(a:l) =~ '^\s*\%(\<val\>\|\<module\>\|\<class\>\|\<type\>\|\<method\>\|\<initializer\>\|\<inherit\>\|\<exception\>\|\<external\>\)'
exe 'return ">' (indent(a:l)/s:i)+1 '"'
endif
" Toplevel let are detected thanks to the indentation
if getline(a:l) =~ '^\s*let\>' && indent(a:l) == s:i+s:topindent(a:l)
exe 'return ">' (indent(a:l)/s:i)+1 '"'
endif
" We close fold on end which are associated to struct, sig or object.
" We use syntax information to do that.
if getline(a:l) =~ '^\s*end\>' && synIDattr(synID(a:l, indent(a:l)+1, 0), "name") != "ocamlKeyword"
return (indent(a:l)/s:i)+1
endif
" Folds end on ;;
if getline(a:l) =~ '^\s*;;'
exe 'return "<' (indent(a:l)/s:i)+1 '"'
endif
" Comments around folds aren't merged to them.
if synIDattr(synID(a:l, indent(a:l)+1, 0), "name") == "ocamlComment"
return -1
endif
return '='
endfunction
" Vim support for OCaml .annot files (requires Vim with python support)
"
" Executing OCamlPrintType(<mode>) function will display in the Vim bottom
" line(s) the type of an ocaml value getting it from the corresponding .annot
" file (if any). If Vim is in visual mode, <mode> should be "visual" and the
" selected ocaml value correspond to the highlighted text, otherwise (<mode>
" can be anything else) it corresponds to the literal found at the current
" cursor position.
"
" .annot files are parsed lazily the first time OCamlPrintType is invoked; is
" also possible to force the parsing using the OCamlParseAnnot() function.
"
" Typing '<LocalLeader>t' (usually ',t') will cause OCamlPrintType function
" to be invoked with the right argument depending on the current mode (visual
" or not).
"
" Copyright (C) <2003-2004> Stefano Zacchiroli <zack@bononia.it>
"
" Created: Wed, 01 Oct 2003 18:16:22 +0200 zack
" LastModified: Wed, 25 Aug 2004 18:28:39 +0200 zack
" '<LocalLeader>d' will find the definition of the name under the cursor
" and position cursor on it (only for current file) or print fully qualified name
" (for external definitions). (ocaml >= 3.11)
"
" Additionally '<LocalLeader>t' will show whether function call is tail call
" or not. Current implementation requires selecting the whole function call
" expression (in visual mode) to work. (ocaml >= 3.11)
"
" Copyright (C) 2009 <ygrek@autistici.org>
if !has("python")
finish
endif
python << EOF
import re
import os
import os.path
import string
import time
import vim
debug = False
class AnnExc(Exception):
def __init__(self, reason):
self.reason = reason
no_annotations = AnnExc("No annotations (.annot) file found")
annotation_not_found = AnnExc("No type annotation found for the given text")
definition_not_found = AnnExc("No definition found for the given text")
def malformed_annotations(lineno, reason):
return AnnExc("Malformed .annot file (line = %d, reason = %s)" % (lineno,reason))
class Annotations:
"""
.annot ocaml file representation
File format (copied verbatim from caml-types.el)
file ::= block *
block ::= position <SP> position <LF> annotation *
position ::= filename <SP> num <SP> num <SP> num
annotation ::= keyword open-paren <LF> <SP> <SP> data <LF> close-paren
<SP> is a space character (ASCII 0x20)
<LF> is a line-feed character (ASCII 0x0A)
num is a sequence of decimal digits
filename is a string with the lexical conventions of O'Caml
open-paren is an open parenthesis (ASCII 0x28)
close-paren is a closed parenthesis (ASCII 0x29)
data is any sequence of characters where <LF> is always followed by
at least two space characters.
- in each block, the two positions are respectively the start and the
end of the range described by the block.
- in a position, the filename is the name of the file, the first num
is the line number, the second num is the offset of the beginning
of the line, the third num is the offset of the position itself.
- the char number within the line is the difference between the third
and second nums.
Possible keywords are \"type\", \"ident\" and \"call\".
"""
def __init__(self):
self.__filename = None # last .annot parsed file
self.__ml_filename = None # as above but s/.annot/.ml/
self.__timestamp = None # last parse action timestamp
self.__annot = {}
self.__refs = {}
self.__calls = {}
self.__re = re.compile(
'^"[^"]*"\s+(\d+)\s+(\d+)\s+(\d+)\s+"[^"]*"\s+(\d+)\s+(\d+)\s+(\d+)$')
self.__re_int_ref = re.compile('^int_ref\s+(\w+)\s"[^"]*"\s+(\d+)\s+(\d+)\s+(\d+)')
self.__re_def_full = re.compile(
'^def\s+(\w+)\s+"[^"]*"\s+(\d+)\s+(\d+)\s+(\d+)\s+"[^"]*"\s+(\d+)\s+(\d+)\s+(\d+)$')
self.__re_def = re.compile('^def\s+(\w+)\s"[^"]*"\s+(\d+)\s+(\d+)\s+(\d+)\s+')
self.__re_ext_ref = re.compile('^ext_ref\s+(\S+)')
self.__re_kw = re.compile('^(\w+)\($')
def __parse(self, fname):
try:
f = open(fname)
self.__annot = {} # erase internal mappings when file is reparsed
self.__refs = {}
self.__calls = {}
line = f.readline() # position line
lineno = 1
while (line != ""):
m = self.__re.search(line)
if (not m):
raise malformed_annotations(lineno,"re doesn't match")
line1 = int(m.group(1))
col1 = int(m.group(3)) - int(m.group(2))
line2 = int(m.group(4))
col2 = int(m.group(6)) - int(m.group(5))
while 1:
line = f.readline() # keyword or position line
lineno += 1
m = self.__re_kw.search(line)
if (not m):
break
desc = []
line = f.readline() # description
lineno += 1
if (line == ""): raise malformed_annotations(lineno,"no content")
while line != ")\n":
desc.append(string.strip(line))
line = f.readline()
lineno += 1
if (line == ""): raise malformed_annotations(lineno,"bad content")
desc = string.join(desc, "\n")
key = ((line1, col1), (line2, col2))
if (m.group(1) == "type"):
if not self.__annot.has_key(key):
self.__annot[key] = desc
if (m.group(1) == "call"): # region, accessible only in visual mode
if not self.__calls.has_key(key):
self.__calls[key] = desc
if (m.group(1) == "ident"):
m = self.__re_int_ref.search(desc)
if m:
line = int(m.group(2))
col = int(m.group(4)) - int(m.group(3))
name = m.group(1)
else:
line = -1
col = -1
m = self.__re_ext_ref.search(desc)
if m:
name = m.group(1)
else:
line = -2
col = -2
name = desc
if not self.__refs.has_key(key):
self.__refs[key] = (line,col,name)
f.close()
self.__filename = fname
self.__ml_filename = vim.current.buffer.name
self.__timestamp = int(time.time())
except IOError:
raise no_annotations
def parse(self):
annot_file = os.path.splitext(vim.current.buffer.name)[0] + ".annot"
previous_head, head, tail = '***', annot_file, ''
while not os.path.isfile(annot_file) and head != previous_head:
previous_head = head
head, x = os.path.split(head)
if tail == '':
tail = x
else:
os.path.join(x, tail)
annot_file = os.path.join(head, '_build', tail)
self.__parse(annot_file)
def check_file(self):
if vim.current.buffer.name == None:
raise no_annotations
if vim.current.buffer.name != self.__ml_filename or \
os.stat(self.__filename).st_mtime > self.__timestamp:
self.parse()
def get_type(self, (line1, col1), (line2, col2)):
if debug:
print line1, col1, line2, col2
self.check_file()
try:
try:
extra = self.__calls[(line1, col1), (line2, col2)]
if extra == "tail":
extra = " (* tail call *)"
else:
extra = " (* function call *)"
except KeyError:
extra = ""
return self.__annot[(line1, col1), (line2, col2)] + extra
except KeyError:
raise annotation_not_found
def get_ident(self, (line1, col1), (line2, col2)):
if debug:
print line1, col1, line2, col2
self.check_file()
try:
(line,col,name) = self.__refs[(line1, col1), (line2, col2)]
if line >= 0 and col >= 0:
vim.command("normal "+str(line)+"gg"+str(col+1)+"|")
#current.window.cursor = (line,col)
if line == -2:
m = self.__re_def_full.search(name)
if m:
l2 = int(m.group(5))
c2 = int(m.group(7)) - int(m.group(6))
name = m.group(1)
else:
m = self.__re_def.search(name)
if m:
l2 = int(m.group(2))
c2 = int(m.group(4)) - int(m.group(3))
name = m.group(1)
else:
l2 = -1
if False and l2 >= 0:
# select region
if c2 == 0 and l2 > 0:
vim.command("normal v"+str(l2-1)+"gg"+"$")
else:
vim.command("normal v"+str(l2)+"gg"+str(c2)+"|")
return name
except KeyError:
raise definition_not_found
word_char_RE = re.compile("^[\w.]$")
# TODO this function should recognize ocaml literals, actually it's just an
# hack that recognize continuous sequences of word_char_RE above
def findBoundaries(line, col):
""" given a cursor position (as returned by vim.current.window.cursor)
return two integers identify the beggining and end column of the word at
cursor position, if any. If no word is at the cursor position return the
column cursor position twice """
left, right = col, col
line = line - 1 # mismatch vim/python line indexes
(begin_col, end_col) = (0, len(vim.current.buffer[line]) - 1)
try:
while word_char_RE.search(vim.current.buffer[line][left - 1]):
left = left - 1
except IndexError:
pass
try:
while word_char_RE.search(vim.current.buffer[line][right + 1]):
right = right + 1
except IndexError:
pass
return (left, right)
annot = Annotations() # global annotation object
def get_marks(mode):
if mode == "visual": # visual mode: lookup highlighted text
(line1, col1) = vim.current.buffer.mark("<")
(line2, col2) = vim.current.buffer.mark(">")
else: # any other mode: lookup word at cursor position
(line, col) = vim.current.window.cursor
(col1, col2) = findBoundaries(line, col)
(line1, line2) = (line, line)
begin_mark = (line1, col1)
end_mark = (line2, col2 + 1)
return (begin_mark,end_mark)
def printOCamlType(mode):
try:
(begin_mark,end_mark) = get_marks(mode)
print annot.get_type(begin_mark, end_mark)
except AnnExc, exc:
print exc.reason
def gotoOCamlDefinition(mode):
try:
(begin_mark,end_mark) = get_marks(mode)
print annot.get_ident(begin_mark, end_mark)
except AnnExc, exc:
print exc.reason
def parseOCamlAnnot():
try:
annot.parse()
except AnnExc, exc:
print exc.reason
EOF
fun! OCamlPrintType(current_mode)
if (a:current_mode == "visual")
python printOCamlType("visual")
else
python printOCamlType("normal")
endif
endfun
fun! OCamlGotoDefinition(current_mode)
if (a:current_mode == "visual")
python gotoOCamlDefinition("visual")
else
python gotoOCamlDefinition("normal")
endif
endfun
fun! OCamlParseAnnot()
python parseOCamlAnnot()
endfun
map <LocalLeader>t :call OCamlPrintType("normal")<RETURN>
vmap <LocalLeader>t :call OCamlPrintType("visual")<RETURN>
map <LocalLeader>d :call OCamlGotoDefinition("normal")<RETURN>
vmap <LocalLeader>d :call OCamlGotoDefinition("visual")<RETURN>
let &cpoptions=s:cposet
unlet s:cposet
" vim:sw=2
endif