blob: 10ead8a1eb9aae5803a0ccacec0e99b7477d6791 [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 Moolenaar9964e462007-05-05 17:54:07 +00007" Last Change: 2006 May 01 - Added .annot support for file.whateverext (SZ)
8" 2006 Apr 11 - Fixed an initialization bug; fixed ASS abbrev (MM)
Bram Moolenaar4c3f5362006-04-11 21:38:50 +00009" 2005 Oct 13 - removed GPL; better matchit support (MM, SZ)
Bram Moolenaara5792f52005-11-23 21:25:05 +000010"
Bram Moolenaar4c3f5362006-04-11 21:38:50 +000011if exists("b:did_ftplugin")
12 finish
13endif
Bram Moolenaara5792f52005-11-23 21:25:05 +000014let b:did_ftplugin=1
Bram Moolenaar071d4272004-06-13 20:20:40 +000015
Bram Moolenaara5792f52005-11-23 21:25:05 +000016" Error handling -- helps moving where the compiler wants you to go
17let s:cposet=&cpoptions
Bram Moolenaar071d4272004-06-13 20:20:40 +000018set cpo-=C
Bram Moolenaar071d4272004-06-13 20:20:40 +000019setlocal efm=
Bram Moolenaara5792f52005-11-23 21:25:05 +000020 \%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
Bram Moolenaar071d4272004-06-13 20:20:40 +000032
33" Add mappings, unless the user didn't want this.
34if !exists("no_plugin_maps") && !exists("no_ocaml_maps")
Bram Moolenaara5792f52005-11-23 21:25:05 +000035 " (un)commenting
Bram Moolenaar071d4272004-06-13 20:20:40 +000036 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
Bram Moolenaara5792f52005-11-23 21:25:05 +000044 nnoremap <buffer> <Plug>LUncomOff :s/^(\* \(.*\) \*)/\1/<CR>:noh<CR>
Bram Moolenaar071d4272004-06-13 20:20:40 +000045 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')
Bram Moolenaar4c3f5362006-04-11 21:38:50 +000049 iabbrev <buffer> ASS (assert (0=1) (* XXX *))
Bram Moolenaar071d4272004-06-13 20:20:40 +000050 endif
51endif
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000052
53" Let % jump between structure elements (due to Issac Trotts)
Bram Moolenaara5792f52005-11-23 21:25:05 +000054let 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
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000063
64" switching between interfaces (.mli) and implementations (.ml)
65if !exists("g:did_ocaml_switch")
66 let g:did_ocaml_switch = 1
Bram Moolenaara5792f52005-11-23 21:25:05 +000067 map <LocalLeader>s :call OCaml_switch(0)<CR>
68 map <LocalLeader>S :call OCaml_switch(1)<CR>
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000069 fun OCaml_switch(newwin)
70 if (match(bufname(""), "\\.mli$") >= 0)
71 let fname = substitute(bufname(""), "\\.mli$", ".ml", "")
72 if (a:newwin == 1)
Bram Moolenaara5792f52005-11-23 21:25:05 +000073 exec "new " . fname
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000074 else
Bram Moolenaara5792f52005-11-23 21:25:05 +000075 exec "arge " . fname
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000076 endif
77 elseif (match(bufname(""), "\\.ml$") >= 0)
78 let fname = bufname("") . "i"
79 if (a:newwin == 1)
Bram Moolenaara5792f52005-11-23 21:25:05 +000080 exec "new " . fname
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000081 else
Bram Moolenaara5792f52005-11-23 21:25:05 +000082 exec "arge " . fname
Bram Moolenaar5eb86f92004-07-26 12:53:41 +000083 endif
84 endif
85 endfun
86endif
87
Bram Moolenaara5792f52005-11-23 21:25:05 +000088" 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)
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000179"
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"
Bram Moolenaara5792f52005-11-23 21:25:05 +0000190" Typing ',3' will cause OCamlPrintType function to be invoked with
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000191" the right argument depending on the current mode (visual or not).
192"
Bram Moolenaara5792f52005-11-23 21:25:05 +0000193" Copyright (C) <2003-2004> Stefano Zacchiroli <zack@bononia.it>
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000194"
195" Created: Wed, 01 Oct 2003 18:16:22 +0200 zack
Bram Moolenaara5792f52005-11-23 21:25:05 +0000196" LastModified: Wed, 25 Aug 2004 18:28:39 +0200 zack
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000197
198if !has("python")
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000199 finish
200endif
201
202python << EOF
203
204import re
205import os
Bram Moolenaar9964e462007-05-05 17:54:07 +0000206import os.path
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000207import 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
Bram Moolenaara5792f52005-11-23 21:25:05 +0000243 end of the range described by the block.
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000244 - 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(
Bram Moolenaara5792f52005-11-23 21:25:05 +0000259 '^"[^"]*"\s+(\d+)\s+(\d+)\s+(\d+)\s+"[^"]*"\s+(\d+)\s+(\d+)\s+(\d+)$')
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000260
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")
Bram Moolenaara5792f52005-11-23 21:25:05 +0000287 key = ((line1, col1), (line2, col2))
288 if not self.__annot.has_key(key):
289 self.__annot[key] = type
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000290 line = f.readline() # position line
291 f.close()
292 self.__filename = fname
Bram Moolenaar9964e462007-05-05 17:54:07 +0000293 self.__ml_filename = vim.current.buffer.name
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000294 self.__timestamp = int(time.time())
295 except IOError:
296 raise no_annotations
297
298 def parse(self):
Bram Moolenaar9964e462007-05-05 17:54:07 +0000299 annot_file = os.path.splitext(vim.current.buffer.name)[0] + ".annot"
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000300 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
Bram Moolenaara5792f52005-11-23 21:25:05 +0000364fun! OCamlPrintType(current_mode)
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000365 if (a:current_mode == "visual")
366 python printOCamlType("visual")
367 else
368 python printOCamlType("normal")
369 endif
370endfun
371
Bram Moolenaara5792f52005-11-23 21:25:05 +0000372fun! OCamlParseAnnot()
Bram Moolenaar5eb86f92004-07-26 12:53:41 +0000373 python parseOCamlAnnot()
374endfun
375
Bram Moolenaara5792f52005-11-23 21:25:05 +0000376map <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