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: 2005 Oct 13 - removed GPL; better matchit support (MM, SZ) 8" 9" if exists("b:did_ftplugin") 10" finish 11" endif 12let b:did_ftplugin=1 13 14" Error handling -- helps moving where the compiler wants you to go 15let s:cposet=&cpoptions 16set cpo-=C 17setlocal efm= 18 \%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 30 31" Add mappings, unless the user didn't want this. 32if !exists("no_plugin_maps") && !exists("no_ocaml_maps") 33 " (un)commenting 34 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 42 nnoremap <buffer> <Plug>LUncomOff :s/^(\* \(.*\) \*)/\1/<CR>:noh<CR> 43 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') 47 iabbrev <buffer> ASS (assert false (* XXX *)) 48 endif 49endif 50 51" Let % jump between structure elements (due to Issac Trotts) 52let 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 61 62" switching between interfaces (.mli) and implementations (.ml) 63if !exists("g:did_ocaml_switch") 64 let g:did_ocaml_switch = 1 65 map <LocalLeader>s :call OCaml_switch(0)<CR> 66 map <LocalLeader>S :call OCaml_switch(1)<CR> 67 fun OCaml_switch(newwin) 68 if (match(bufname(""), "\\.mli$") >= 0) 69 let fname = substitute(bufname(""), "\\.mli$", ".ml", "") 70 if (a:newwin == 1) 71 exec "new " . fname 72 else 73 exec "arge " . fname 74 endif 75 elseif (match(bufname(""), "\\.ml$") >= 0) 76 let fname = bufname("") . "i" 77 if (a:newwin == 1) 78 exec "new " . fname 79 else 80 exec "arge " . fname 81 endif 82 endif 83 endfun 84endif 85 86" 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) 177" 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" 188" Typing ',3' will cause OCamlPrintType function to be invoked with 189" the right argument depending on the current mode (visual or not). 190" 191" Copyright (C) <2003-2004> Stefano Zacchiroli <[email protected]> 192" 193" Created: Wed, 01 Oct 2003 18:16:22 +0200 zack 194" LastModified: Wed, 25 Aug 2004 18:28:39 +0200 zack 195 196if !has("python") 197 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 240 end of the range described by the block. 241 - 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( 256 '^"[^"]*"\s+(\d+)\s+(\d+)\s+(\d+)\s+"[^"]*"\s+(\d+)\s+(\d+)\s+(\d+)$') 257 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") 284 key = ((line1, col1), (line2, col2)) 285 if not self.__annot.has_key(key): 286 self.__annot[key] = type 287 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 361fun! OCamlPrintType(current_mode) 362 if (a:current_mode == "visual") 363 python printOCamlType("visual") 364 else 365 python printOCamlType("normal") 366 endif 367endfun 368 369fun! OCamlParseAnnot() 370 python parseOCamlAnnot() 371endfun 372 373map <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 380 381