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