xref: /vim-8.2.3635/runtime/ftplugin/ocaml.vim (revision dfccaf0f)
1" Vim settings file
2" Language:     OCaml
3" Maintainers:  Mike Leary          <[email protected]>
4"               Markus Mottl        <[email protected]>
5"               Stefano Zacchiroli  <[email protected]>
6" URL:          http://www.oefai.at/~markus/vim/ftplugin/ocaml.vim
7" Last Change:  2004 Apr 12 - better .ml/.mli-switching without Python (SZ)
8"               2003 Nov 21 - match_words-patterns and .ml/.mli-switching (MM)
9"               2003 Oct 16 - re-entered variable 'did_ocaml_dtypes' (MM)
10"               2003 Oct 15 - added Stefano Zacchirolis (SZ) Python-code for
11"                             displaying type annotations (MM)
12
13" Only do these settings when not done yet for this buffer
14if exists("b:did_ftplugin")
15  finish
16endif
17
18" Don't do other file type settings for this buffer
19let b:did_ftplugin = 1
20
21set cpo-=C
22
23" Error formats
24setlocal efm=
25  \%EFile\ \"%f\"\\,\ line\ %l\\,\ characters\ %c-%*\\d:,
26  \%EFile\ \"%f\"\\,\ line\ %l\\,\ character\ %c:%m,
27  \%+EReference\ to\ unbound\ regexp\ name\ %m,
28  \%Eocamlyacc:\ e\ -\ line\ %l\ of\ \"%f\"\\,\ %m,
29  \%Wocamlyacc:\ w\ -\ %m,
30  \%-Zmake%.%#,
31  \%C%m
32
33" Add mappings, unless the user didn't want this.
34if !exists("no_plugin_maps") && !exists("no_ocaml_maps")
35  " Uncommenting
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 <ESC>:s/^(\* \(.*\) \*)/\1/<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 false)
50  endif
51endif
52
53" Let % jump between structure elements (due to Issac Trotts)
54let b:mw='\<let\>:\<and\>:\(\<in\>\|;;\),'
55let b:mw=b:mw . '\<if\>:\<then\>:\<else\>,\<do\>:\<done\>,'
56let b:mw=b:mw . '\<\(object\|sig\|struct\|begin\)\>:\<end\>'
57let b:match_words=b:mw
58
59" switching between interfaces (.mli) and implementations (.ml)
60if !exists("g:did_ocaml_switch")
61  let g:did_ocaml_switch = 1
62  map ,s :call OCaml_switch(0)<CR>
63  map ,S :call OCaml_switch(1)<CR>
64  fun OCaml_switch(newwin)
65    if (match(bufname(""), "\\.mli$") >= 0)
66      let fname = substitute(bufname(""), "\\.mli$", ".ml", "")
67      if (a:newwin == 1)
68	exec "new " . fname
69      else
70	exec "arge " . fname
71      endif
72    elseif (match(bufname(""), "\\.ml$") >= 0)
73      let fname = bufname("") . "i"
74      if (a:newwin == 1)
75	exec "new " . fname
76      else
77	exec "arge " . fname
78      endif
79    endif
80  endfun
81endif
82
83" Vim support for OCaml 3.07 .annot files (requires Vim with python support)
84"
85" Executing OCamlPrintType(<mode>) function will display in the Vim bottom
86" line(s) the type of an ocaml value getting it from the corresponding .annot
87" file (if any).  If Vim is in visual mode, <mode> should be "visual" and the
88" selected ocaml value correspond to the highlighted text, otherwise (<mode>
89" can be anything else) it corresponds to the literal found at the current
90" cursor position.
91"
92" .annot files are parsed lazily the first time OCamlPrintType is invoked; is
93" also possible to force the parsing using the OCamlParseAnnot() function.
94"
95" Hitting the <F3> key will cause OCamlPrintType function to be invoked with
96" the right argument depending on the current mode (visual or not).
97"
98" Copyright (C) <2003> Stefano Zacchiroli <[email protected]>
99"
100" Created:        Wed, 01 Oct 2003 18:16:22 +0200 zack
101" LastModified:   Mon, 06 Oct 2003 11:05:39 +0200 zack
102"
103" This program is free software; you can redistribute it and/or modify
104" it under the terms of the GNU General Public License as published by
105" the Free Software Foundation; either version 2 of the License, or
106" (at your option) any later version.
107"
108" This program is distributed in the hope that it will be useful,
109" but WITHOUT ANY WARRANTY; without even the implied warranty of
110" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
111" GNU General Public License for more details.
112"
113" You should have received a copy of the GNU General Public License
114" along with this program; if not, write to the Free Software
115" Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
116"
117
118if !has("python")
119  echo "Python support not found: OCaml .annot support disabled"
120  finish
121endif
122
123if !exists("g:did_ocaml_dtypes")
124  let g:did_ocaml_dtypes = 1
125else
126  finish
127endif
128
129python << EOF
130
131import re
132import os
133import string
134import time
135import vim
136
137debug = False
138
139class AnnExc(Exception):
140    def __init__(self, reason):
141        self.reason = reason
142
143no_annotations = AnnExc("No type annotations (.annot) file found")
144annotation_not_found = AnnExc("No type annotation found for the given text")
145def malformed_annotations(lineno):
146    return AnnExc("Malformed .annot file (line = %d)" % lineno)
147
148class Annotations:
149    """
150      .annot ocaml file representation
151
152      File format (copied verbatim from caml-types.el)
153
154      file ::= block *
155      block ::= position <SP> position <LF> annotation *
156      position ::= filename <SP> num <SP> num <SP> num
157      annotation ::= keyword open-paren <LF> <SP> <SP> data <LF> close-paren
158
159      <SP> is a space character (ASCII 0x20)
160      <LF> is a line-feed character (ASCII 0x0A)
161      num is a sequence of decimal digits
162      filename is a string with the lexical conventions of O'Caml
163      open-paren is an open parenthesis (ASCII 0x28)
164      close-paren is a closed parenthesis (ASCII 0x29)
165      data is any sequence of characters where <LF> is always followed by
166           at least two space characters.
167
168      - in each block, the two positions are respectively the start and the
169      - end of the range described by the block.
170      - in a position, the filename is the name of the file, the first num
171        is the line number, the second num is the offset of the beginning
172        of the line, the third num is the offset of the position itself.
173      - the char number within the line is the difference between the third
174        and second nums.
175
176      For the moment, the only possible keyword is \"type\"."
177    """
178
179    def __init__(self):
180        self.__filename = None  # last .annot parsed file
181        self.__ml_filename = None # as above but s/.annot/.ml/
182        self.__timestamp = None # last parse action timestamp
183        self.__annot = {}
184        self.__re = re.compile(
185          '^"[^"]+"\s+(\d+)\s+(\d+)\s+(\d+)\s+"[^"]+"\s+(\d+)\s+(\d+)\s+(\d+)$')
186
187    def __parse(self, fname):
188        try:
189            f = open(fname)
190            line = f.readline() # position line
191            lineno = 1
192            while (line != ""):
193                m = self.__re.search(line)
194                if (not m):
195                    raise malformed_annotations(lineno)
196                line1 = int(m.group(1))
197                col1 = int(m.group(3)) - int(m.group(2))
198                line2 = int(m.group(4))
199                col2 = int(m.group(6)) - int(m.group(5))
200                line = f.readline() # "type(" string
201                lineno += 1
202                if (line == ""): raise malformed_annotations(lineno)
203                type = []
204                line = f.readline() # type description
205                lineno += 1
206                if (line == ""): raise malformed_annotations(lineno)
207                while line != ")\n":
208                    type.append(string.strip(line))
209                    line = f.readline()
210                    lineno += 1
211                    if (line == ""): raise malformed_annotations(lineno)
212                type = string.join(type, "\n")
213                self.__annot[(line1, col1), (line2, col2)] = type
214                line = f.readline() # position line
215            f.close()
216            self.__filename = fname
217            self.__ml_filename = re.sub("\.annot$", ".ml", fname)
218            self.__timestamp = int(time.time())
219        except IOError:
220            raise no_annotations
221
222    def parse(self):
223        annot_file = re.sub("\.ml$", ".annot", vim.current.buffer.name)
224        self.__parse(annot_file)
225
226    def get_type(self, (line1, col1), (line2, col2)):
227        if debug:
228            print line1, col1, line2, col2
229        if vim.current.buffer.name == None:
230            raise no_annotations
231        if vim.current.buffer.name != self.__ml_filename or  \
232          os.stat(self.__filename).st_mtime > self.__timestamp:
233            self.parse()
234        try:
235            return self.__annot[(line1, col1), (line2, col2)]
236        except KeyError:
237            raise annotation_not_found
238
239word_char_RE = re.compile("^[\w.]$")
240
241  # TODO this function should recognize ocaml literals, actually it's just an
242  # hack that recognize continuous sequences of word_char_RE above
243def findBoundaries(line, col):
244    """ given a cursor position (as returned by vim.current.window.cursor)
245    return two integers identify the beggining and end column of the word at
246    cursor position, if any. If no word is at the cursor position return the
247    column cursor position twice """
248    left, right = col, col
249    line = line - 1 # mismatch vim/python line indexes
250    (begin_col, end_col) = (0, len(vim.current.buffer[line]) - 1)
251    try:
252        while word_char_RE.search(vim.current.buffer[line][left - 1]):
253            left = left - 1
254    except IndexError:
255        pass
256    try:
257        while word_char_RE.search(vim.current.buffer[line][right + 1]):
258            right = right + 1
259    except IndexError:
260        pass
261    return (left, right)
262
263annot = Annotations() # global annotation object
264
265def printOCamlType(mode):
266    try:
267        if mode == "visual":  # visual mode: lookup highlighted text
268            (line1, col1) = vim.current.buffer.mark("<")
269            (line2, col2) = vim.current.buffer.mark(">")
270        else: # any other mode: lookup word at cursor position
271            (line, col) = vim.current.window.cursor
272            (col1, col2) = findBoundaries(line, col)
273            (line1, line2) = (line, line)
274        begin_mark = (line1, col1)
275        end_mark = (line2, col2 + 1)
276        print annot.get_type(begin_mark, end_mark)
277    except AnnExc, exc:
278        print exc.reason
279
280def parseOCamlAnnot():
281    try:
282        annot.parse()
283    except AnnExc, exc:
284        print exc.reason
285
286EOF
287
288fun OCamlPrintType(current_mode)
289  if (a:current_mode == "visual")
290    python printOCamlType("visual")
291  else
292    python printOCamlType("normal")
293  endif
294endfun
295
296fun OCamlParseAnnot()
297  python parseOCamlAnnot()
298endfun
299
300map <F3> :call OCamlPrintType("normal")<RETURN>
301vmap <F3> :call OCamlPrintType("visual")<RETURN>
302