xref: /vim-8.2.3635/runtime/ftplugin/ocaml.vim (revision 3577c6fa)
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