blob: 6f2b17cb7e03f8c93374085154716fa94829afc7 [file] [log] [blame]
Bram Moolenaara5792f52005-11-23 21:25:05 +00001" Language: OCaml
2" Maintainer: David Baelde <firstname.name@ens-lyon.org>
3" Mike Leary <leary@nwlink.com>
4" Markus Mottl <markus.mottl@gmail.com>
5" Stefano Zacchiroli <zack@bononia.it>
6" URL: http://www.ocaml.info/vim/ftplugin/ocaml.vim
Bram Moolenaar8b6144b2006-02-08 09:20:24 +00007" Last Change: 2006 Feb 05
Bram Moolenaara5792f52005-11-23 21:25:05 +00008"
9" if exists("b:did_ftplugin")
10" finish
11" endif
12let b:did_ftplugin=1
Bram Moolenaar071d4272004-06-13 20:20:40 +000013
Bram Moolenaara5792f52005-11-23 21:25:05 +000014" Error handling -- helps moving where the compiler wants you to go
15let s:cposet=&cpoptions
Bram Moolenaar071d4272004-06-13 20:20:40 +000016set cpo-=C
Bram Moolenaar071d4272004-06-13 20:20:40 +000017setlocal efm=
Bram Moolenaara5792f52005-11-23 21:25:05 +000018 \%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
Bram Moolenaar071d4272004-06-13 20:20:40 +000030
31" Add mappings, unless the user didn't want this.
32if !exists("no_plugin_maps") && !exists("no_ocaml_maps")
Bram Moolenaara5792f52005-11-23 21:25:05 +000033 " (un)commenting
Bram Moolenaar071d4272004-06-13 20:20:40 +000034 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
Bram Moolenaara5792f52005-11-23 21:25:05 +000042 nnoremap <buffer> <Plug>LUncomOff :s/^(\* \(.*\) \*)/\1/<CR>:noh<CR>
Bram Moolenaar071d4272004-06-13 20:20:40 +000043 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')
Bram Moolenaara5792f52005-11-23 21:25:05 +000047 iabbrev <buffer> ASS (assert false (* XXX *))
Bram Moolenaar071d4272004-06-13 20:20:40 +000048 endif
49endif
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000050
51" Let % jump between structure elements (due to Issac Trotts)
Bram Moolenaara5792f52005-11-23 21:25:05 +000052let 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
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000061
62" switching between interfaces (.mli) and implementations (.ml)
63if !exists("g:did_ocaml_switch")
64 let g:did_ocaml_switch = 1
Bram Moolenaara5792f52005-11-23 21:25:05 +000065 map <LocalLeader>s :call OCaml_switch(0)<CR>
66 map <LocalLeader>S :call OCaml_switch(1)<CR>
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000067 fun OCaml_switch(newwin)
68 if (match(bufname(""), "\\.mli$") >= 0)
69 let fname = substitute(bufname(""), "\\.mli$", ".ml", "")
70 if (a:newwin == 1)
Bram Moolenaara5792f52005-11-23 21:25:05 +000071 exec "new " . fname
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000072 else
Bram Moolenaara5792f52005-11-23 21:25:05 +000073 exec "arge " . fname
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000074 endif
75 elseif (match(bufname(""), "\\.ml$") >= 0)
76 let fname = bufname("") . "i"
77 if (a:newwin == 1)
Bram Moolenaara5792f52005-11-23 21:25:05 +000078 exec "new " . fname
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000079 else
Bram Moolenaara5792f52005-11-23 21:25:05 +000080 exec "arge " . fname
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000081 endif
82 endif
83 endfun
84endif
85
Bram Moolenaara5792f52005-11-23 21:25:05 +000086" 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)
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000177"
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"
Bram Moolenaara5792f52005-11-23 21:25:05 +0000188" Typing ',3' will cause OCamlPrintType function to be invoked with
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000189" the right argument depending on the current mode (visual or not).
190"
Bram Moolenaara5792f52005-11-23 21:25:05 +0000191" Copyright (C) <2003-2004> Stefano Zacchiroli <zack@bononia.it>
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000192"
193" Created: Wed, 01 Oct 2003 18:16:22 +0200 zack
Bram Moolenaara5792f52005-11-23 21:25:05 +0000194" LastModified: Wed, 25 Aug 2004 18:28:39 +0200 zack
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000195
196if !has("python")
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000197 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
Bram Moolenaara5792f52005-11-23 21:25:05 +0000240 end of the range described by the block.
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000241 - 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(
Bram Moolenaara5792f52005-11-23 21:25:05 +0000256 '^"[^"]*"\s+(\d+)\s+(\d+)\s+(\d+)\s+"[^"]*"\s+(\d+)\s+(\d+)\s+(\d+)$')
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000257
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")
Bram Moolenaara5792f52005-11-23 21:25:05 +0000284 key = ((line1, col1), (line2, col2))
285 if not self.__annot.has_key(key):
286 self.__annot[key] = type
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000287 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
Bram Moolenaara5792f52005-11-23 21:25:05 +0000361fun! OCamlPrintType(current_mode)
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000362 if (a:current_mode == "visual")
363 python printOCamlType("visual")
364 else
365 python printOCamlType("normal")
366 endif
367endfun
368
Bram Moolenaara5792f52005-11-23 21:25:05 +0000369fun! OCamlParseAnnot()
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000370 python parseOCamlAnnot()
371endfun
372
Bram Moolenaara5792f52005-11-23 21:25:05 +0000373map <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