xref: /vim-8.2.3635/runtime/ftplugin/ocaml.vim (revision f4cd3e80)
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" URL:         http://www.ocaml.info/vim/ftplugin/ocaml.vim
7" Last Change: 2005 Oct 13 - removed GPL; better matchit support (MM, SZ)
8"
9" if exists("b:did_ftplugin")
10"   finish
11" endif
12let b:did_ftplugin=1
13
14" Error handling -- helps moving where the compiler wants you to go
15let s:cposet=&cpoptions
16set cpo-=C
17setlocal efm=
18      \%EFile\ \"%f\"\\,\ line\ %l\\,\ characters\ %c-%*\\d:,
19      \%EFile\ \"%f\"\\,\ line\ %l\\,\ character\ %c:%m,
20      \%+EReference\ to\ unbound\ regexp\ name\ %m,
21      \%Eocamlyacc:\ e\ -\ line\ %l\ of\ \"%f\"\\,\ %m,
22      \%Wocamlyacc:\ w\ -\ %m,
23      \%-Zmake%.%#,
24      \%C%m,
25      \%D%*\\a[%*\\d]:\ Entering\ directory\ `%f',
26      \%X%*\\a[%*\\d]:\ Leaving\ directory\ `%f',
27      \%D%*\\a:\ Entering\ directory\ `%f',
28      \%X%*\\a:\ Leaving\ directory\ `%f',
29      \%DMaking\ %*\\a\ in\ %f
30
31" Add mappings, unless the user didn't want this.
32if !exists("no_plugin_maps") && !exists("no_ocaml_maps")
33  " (un)commenting
34  if !hasmapto('<Plug>Comment')
35    nmap <buffer> <LocalLeader>c <Plug>LUncomOn
36    vmap <buffer> <LocalLeader>c <Plug>BUncomOn
37    nmap <buffer> <LocalLeader>C <Plug>LUncomOff
38    vmap <buffer> <LocalLeader>C <Plug>BUncomOff
39  endif
40
41  nnoremap <buffer> <Plug>LUncomOn mz0i(* <ESC>$A *)<ESC>`z
42  nnoremap <buffer> <Plug>LUncomOff :s/^(\* \(.*\) \*)/\1/<CR>:noh<CR>
43  vnoremap <buffer> <Plug>BUncomOn <ESC>:'<,'><CR>`<O<ESC>0i(*<ESC>`>o<ESC>0i*)<ESC>`<
44  vnoremap <buffer> <Plug>BUncomOff <ESC>:'<,'><CR>`<dd`>dd`<
45
46  if !hasmapto('<Plug>Abbrev')
47    iabbrev <buffer> ASS (assert false (* XXX *))
48  endif
49endif
50
51" Let % jump between structure elements (due to Issac Trotts)
52let b:mw = ''
53let b:mw = b:mw . ',\<let\>:\<and\>:\(\<in\>\|;;\)'
54let b:mw = b:mw . ',\<if\>:\<then\>:\<else\>'
55let b:mw = b:mw . ',\<\(for\|while\)\>:\<do\>:\<done\>,'
56let b:mw = b:mw . ',\<\(object\|sig\|struct\|begin\)\>:\<end\>'
57let b:mw = b:mw . ',\<\(match\|try\)\>:\<with\>'
58let b:match_words = b:mw
59
60let b:match_ignorecase=0
61
62" switching between interfaces (.mli) and implementations (.ml)
63if !exists("g:did_ocaml_switch")
64  let g:did_ocaml_switch = 1
65  map <LocalLeader>s :call OCaml_switch(0)<CR>
66  map <LocalLeader>S :call OCaml_switch(1)<CR>
67  fun OCaml_switch(newwin)
68    if (match(bufname(""), "\\.mli$") >= 0)
69      let fname = substitute(bufname(""), "\\.mli$", ".ml", "")
70      if (a:newwin == 1)
71        exec "new " . fname
72      else
73        exec "arge " . fname
74      endif
75    elseif (match(bufname(""), "\\.ml$") >= 0)
76      let fname = bufname("") . "i"
77      if (a:newwin == 1)
78        exec "new " . fname
79      else
80        exec "arge " . fname
81      endif
82    endif
83  endfun
84endif
85
86" Folding support
87
88" Get the modeline because folding depends on indentation
89let s:s = line2byte(line('.'))+col('.')-1
90if search('^\s*(\*:o\?caml:')
91  let s:modeline = getline(".")
92else
93  let s:modeline = ""
94endif
95if s:s > 0
96  exe 'goto' s:s
97endif
98
99" Get the indentation params
100let s:m = matchstr(s:modeline,'default\s*=\s*\d\+')
101if s:m != ""
102  let s:idef = matchstr(s:m,'\d\+')
103elseif exists("g:omlet_indent")
104  let s:idef = g:omlet_indent
105else
106  let s:idef = 2
107endif
108let s:m = matchstr(s:modeline,'struct\s*=\s*\d\+')
109if s:m != ""
110  let s:i = matchstr(s:m,'\d\+')
111elseif exists("g:omlet_indent_struct")
112  let s:i = g:omlet_indent_struct
113else
114  let s:i = s:idef
115endif
116
117" Set the folding method
118if exists("g:ocaml_folding")
119  setlocal foldmethod=expr
120  setlocal foldexpr=OMLetFoldLevel(v:lnum)
121endif
122
123" - Only definitions below, executed once -------------------------------------
124
125if exists("*OMLetFoldLevel")
126  finish
127endif
128
129function s:topindent(lnum)
130  let l = a:lnum
131  while l > 0
132    if getline(l) =~ '\s*\%(\<struct\>\|\<sig\>\|\<object\>\)'
133      return indent(l)
134    endif
135    let l = l-1
136  endwhile
137  return -s:i
138endfunction
139
140function OMLetFoldLevel(l)
141
142  " This is for not merging blank lines around folds to them
143  if getline(a:l) !~ '\S'
144    return -1
145  endif
146
147  " We start folds for modules, classes, and every toplevel definition
148  if getline(a:l) =~ '^\s*\%(\<val\>\|\<module\>\|\<class\>\|\<type\>\|\<method\>\|\<initializer\>\|\<inherit\>\|\<exception\>\|\<external\>\)'
149    exe 'return ">' (indent(a:l)/s:i)+1 '"'
150  endif
151
152  " Toplevel let are detected thanks to the indentation
153  if getline(a:l) =~ '^\s*let\>' && indent(a:l) == s:i+s:topindent(a:l)
154    exe 'return ">' (indent(a:l)/s:i)+1 '"'
155  endif
156
157  " We close fold on end which are associated to struct, sig or object.
158  " We use syntax information to do that.
159  if getline(a:l) =~ '^\s*end\>' && synIDattr(synID(a:l, indent(a:l)+1, 0), "name") != "ocamlKeyword"
160    return (indent(a:l)/s:i)+1
161  endif
162
163  " Folds end on ;;
164  if getline(a:l) =~ '^\s*;;'
165    exe 'return "<' (indent(a:l)/s:i)+1 '"'
166  endif
167
168  " Comments around folds aren't merged to them.
169  if synIDattr(synID(a:l, indent(a:l)+1, 0), "name") == "ocamlComment"
170    return -1
171  endif
172
173  return '='
174endfunction
175
176" Vim support for OCaml .annot files (requires Vim with python support)
177"
178" Executing OCamlPrintType(<mode>) function will display in the Vim bottom
179" line(s) the type of an ocaml value getting it from the corresponding .annot
180" file (if any).  If Vim is in visual mode, <mode> should be "visual" and the
181" selected ocaml value correspond to the highlighted text, otherwise (<mode>
182" can be anything else) it corresponds to the literal found at the current
183" cursor position.
184"
185" .annot files are parsed lazily the first time OCamlPrintType is invoked; is
186" also possible to force the parsing using the OCamlParseAnnot() function.
187"
188" Typing ',3' will cause OCamlPrintType function to be invoked with
189" the right argument depending on the current mode (visual or not).
190"
191" Copyright (C) <2003-2004> Stefano Zacchiroli <[email protected]>
192"
193" Created:        Wed, 01 Oct 2003 18:16:22 +0200 zack
194" LastModified:   Wed, 25 Aug 2004 18:28:39 +0200 zack
195
196if !has("python")
197  finish
198endif
199
200python << EOF
201
202import re
203import os
204import string
205import time
206import vim
207
208debug = False
209
210class AnnExc(Exception):
211    def __init__(self, reason):
212        self.reason = reason
213
214no_annotations = AnnExc("No type annotations (.annot) file found")
215annotation_not_found = AnnExc("No type annotation found for the given text")
216def malformed_annotations(lineno):
217    return AnnExc("Malformed .annot file (line = %d)" % lineno)
218
219class Annotations:
220    """
221      .annot ocaml file representation
222
223      File format (copied verbatim from caml-types.el)
224
225      file ::= block *
226      block ::= position <SP> position <LF> annotation *
227      position ::= filename <SP> num <SP> num <SP> num
228      annotation ::= keyword open-paren <LF> <SP> <SP> data <LF> close-paren
229
230      <SP> is a space character (ASCII 0x20)
231      <LF> is a line-feed character (ASCII 0x0A)
232      num is a sequence of decimal digits
233      filename is a string with the lexical conventions of O'Caml
234      open-paren is an open parenthesis (ASCII 0x28)
235      close-paren is a closed parenthesis (ASCII 0x29)
236      data is any sequence of characters where <LF> is always followed by
237           at least two space characters.
238
239      - in each block, the two positions are respectively the start and the
240        end of the range described by the block.
241      - in a position, the filename is the name of the file, the first num
242        is the line number, the second num is the offset of the beginning
243        of the line, the third num is the offset of the position itself.
244      - the char number within the line is the difference between the third
245        and second nums.
246
247      For the moment, the only possible keyword is \"type\"."
248    """
249
250    def __init__(self):
251        self.__filename = None  # last .annot parsed file
252        self.__ml_filename = None # as above but s/.annot/.ml/
253        self.__timestamp = None # last parse action timestamp
254        self.__annot = {}
255        self.__re = re.compile(
256          '^"[^"]*"\s+(\d+)\s+(\d+)\s+(\d+)\s+"[^"]*"\s+(\d+)\s+(\d+)\s+(\d+)$')
257
258    def __parse(self, fname):
259        try:
260            f = open(fname)
261            line = f.readline() # position line
262            lineno = 1
263            while (line != ""):
264                m = self.__re.search(line)
265                if (not m):
266                    raise malformed_annotations(lineno)
267                line1 = int(m.group(1))
268                col1 = int(m.group(3)) - int(m.group(2))
269                line2 = int(m.group(4))
270                col2 = int(m.group(6)) - int(m.group(5))
271                line = f.readline() # "type(" string
272                lineno += 1
273                if (line == ""): raise malformed_annotations(lineno)
274                type = []
275                line = f.readline() # type description
276                lineno += 1
277                if (line == ""): raise malformed_annotations(lineno)
278                while line != ")\n":
279                    type.append(string.strip(line))
280                    line = f.readline()
281                    lineno += 1
282                    if (line == ""): raise malformed_annotations(lineno)
283                type = string.join(type, "\n")
284                key = ((line1, col1), (line2, col2))
285                if not self.__annot.has_key(key):
286                    self.__annot[key] = type
287                line = f.readline() # position line
288            f.close()
289            self.__filename = fname
290            self.__ml_filename = re.sub("\.annot$", ".ml", fname)
291            self.__timestamp = int(time.time())
292        except IOError:
293            raise no_annotations
294
295    def parse(self):
296        annot_file = re.sub("\.ml$", ".annot", vim.current.buffer.name)
297        self.__parse(annot_file)
298
299    def get_type(self, (line1, col1), (line2, col2)):
300        if debug:
301            print line1, col1, line2, col2
302        if vim.current.buffer.name == None:
303            raise no_annotations
304        if vim.current.buffer.name != self.__ml_filename or  \
305          os.stat(self.__filename).st_mtime > self.__timestamp:
306            self.parse()
307        try:
308            return self.__annot[(line1, col1), (line2, col2)]
309        except KeyError:
310            raise annotation_not_found
311
312word_char_RE = re.compile("^[\w.]$")
313
314  # TODO this function should recognize ocaml literals, actually it's just an
315  # hack that recognize continuous sequences of word_char_RE above
316def findBoundaries(line, col):
317    """ given a cursor position (as returned by vim.current.window.cursor)
318    return two integers identify the beggining and end column of the word at
319    cursor position, if any. If no word is at the cursor position return the
320    column cursor position twice """
321    left, right = col, col
322    line = line - 1 # mismatch vim/python line indexes
323    (begin_col, end_col) = (0, len(vim.current.buffer[line]) - 1)
324    try:
325        while word_char_RE.search(vim.current.buffer[line][left - 1]):
326            left = left - 1
327    except IndexError:
328        pass
329    try:
330        while word_char_RE.search(vim.current.buffer[line][right + 1]):
331            right = right + 1
332    except IndexError:
333        pass
334    return (left, right)
335
336annot = Annotations() # global annotation object
337
338def printOCamlType(mode):
339    try:
340        if mode == "visual":  # visual mode: lookup highlighted text
341            (line1, col1) = vim.current.buffer.mark("<")
342            (line2, col2) = vim.current.buffer.mark(">")
343        else: # any other mode: lookup word at cursor position
344            (line, col) = vim.current.window.cursor
345            (col1, col2) = findBoundaries(line, col)
346            (line1, line2) = (line, line)
347        begin_mark = (line1, col1)
348        end_mark = (line2, col2 + 1)
349        print annot.get_type(begin_mark, end_mark)
350    except AnnExc, exc:
351        print exc.reason
352
353def parseOCamlAnnot():
354    try:
355        annot.parse()
356    except AnnExc, exc:
357        print exc.reason
358
359EOF
360
361fun! OCamlPrintType(current_mode)
362  if (a:current_mode == "visual")
363    python printOCamlType("visual")
364  else
365    python printOCamlType("normal")
366  endif
367endfun
368
369fun! OCamlParseAnnot()
370  python parseOCamlAnnot()
371endfun
372
373map <LocalLeader>t :call OCamlPrintType("normal")<RETURN>
374vmap <LocalLeader>t :call OCamlPrintType("visual")<RETURN>
375
376let &cpoptions=s:cposet
377unlet s:cposet
378
379" vim:sw=2
380
381