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