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