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