1" Author: Kevin Ballard 2" Description: Helper functions for Rust commands/mappings 3" Last Modified: May 27, 2014 4" For bugs, patches and license go to https://github.com/rust-lang/rust.vim 5 6" Jump {{{1 7 8function! rust#Jump(mode, function) range 9 let cnt = v:count1 10 normal! m' 11 if a:mode ==# 'v' 12 norm! gv 13 endif 14 let foldenable = &foldenable 15 set nofoldenable 16 while cnt > 0 17 execute "call <SID>Jump_" . a:function . "()" 18 let cnt = cnt - 1 19 endwhile 20 let &foldenable = foldenable 21endfunction 22 23function! s:Jump_Back() 24 call search('{', 'b') 25 keepjumps normal! w99[{ 26endfunction 27 28function! s:Jump_Forward() 29 normal! j0 30 call search('{', 'b') 31 keepjumps normal! w99[{% 32 call search('{') 33endfunction 34 35" Run {{{1 36 37function! rust#Run(bang, args) 38 let args = s:ShellTokenize(a:args) 39 if a:bang 40 let idx = index(l:args, '--') 41 if idx != -1 42 let rustc_args = idx == 0 ? [] : l:args[:idx-1] 43 let args = l:args[idx+1:] 44 else 45 let rustc_args = l:args 46 let args = [] 47 endif 48 else 49 let rustc_args = [] 50 endif 51 52 let b:rust_last_rustc_args = l:rustc_args 53 let b:rust_last_args = l:args 54 55 call s:WithPath(function("s:Run"), rustc_args, args) 56endfunction 57 58function! s:Run(dict, rustc_args, args) 59 let exepath = a:dict.tmpdir.'/'.fnamemodify(a:dict.path, ':t:r') 60 if has('win32') 61 let exepath .= '.exe' 62 endif 63 64 let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path) 65 let rustc_args = [relpath, '-o', exepath] + a:rustc_args 66 67 let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc" 68 69 let pwd = a:dict.istemp ? a:dict.tmpdir : '' 70 let output = s:system(pwd, shellescape(rustc) . " " . join(map(rustc_args, 'shellescape(v:val)'))) 71 if output != '' 72 echohl WarningMsg 73 echo output 74 echohl None 75 endif 76 if !v:shell_error 77 exe '!' . shellescape(exepath) . " " . join(map(a:args, 'shellescape(v:val)')) 78 endif 79endfunction 80 81" Expand {{{1 82 83function! rust#Expand(bang, args) 84 let args = s:ShellTokenize(a:args) 85 if a:bang && !empty(l:args) 86 let pretty = remove(l:args, 0) 87 else 88 let pretty = "expanded" 89 endif 90 call s:WithPath(function("s:Expand"), pretty, args) 91endfunction 92 93function! s:Expand(dict, pretty, args) 94 try 95 let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc" 96 97 if a:pretty =~? '^\%(everybody_loops$\|flowgraph=\)' 98 let flag = '--xpretty' 99 else 100 let flag = '--pretty' 101 endif 102 let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path) 103 let args = [relpath, '-Z', 'unstable-options', l:flag, a:pretty] + a:args 104 let pwd = a:dict.istemp ? a:dict.tmpdir : '' 105 let output = s:system(pwd, shellescape(rustc) . " " . join(map(args, 'shellescape(v:val)'))) 106 if v:shell_error 107 echohl WarningMsg 108 echo output 109 echohl None 110 else 111 new 112 silent put =output 113 1 114 d 115 setl filetype=rust 116 setl buftype=nofile 117 setl bufhidden=hide 118 setl noswapfile 119 " give the buffer a nice name 120 let suffix = 1 121 let basename = fnamemodify(a:dict.path, ':t:r') 122 while 1 123 let bufname = basename 124 if suffix > 1 | let bufname .= ' ('.suffix.')' | endif 125 let bufname .= '.pretty.rs' 126 if bufexists(bufname) 127 let suffix += 1 128 continue 129 endif 130 exe 'silent noautocmd keepalt file' fnameescape(bufname) 131 break 132 endwhile 133 endif 134 endtry 135endfunction 136 137function! rust#CompleteExpand(lead, line, pos) 138 if a:line[: a:pos-1] =~ '^RustExpand!\s*\S*$' 139 " first argument and it has a ! 140 let list = ["normal", "expanded", "typed", "expanded,identified", "flowgraph=", "everybody_loops"] 141 if !empty(a:lead) 142 call filter(list, "v:val[:len(a:lead)-1] == a:lead") 143 endif 144 return list 145 endif 146 147 return glob(escape(a:lead, "*?[") . '*', 0, 1) 148endfunction 149 150" Emit {{{1 151 152function! rust#Emit(type, args) 153 let args = s:ShellTokenize(a:args) 154 call s:WithPath(function("s:Emit"), a:type, args) 155endfunction 156 157function! s:Emit(dict, type, args) 158 try 159 let output_path = a:dict.tmpdir.'/output' 160 161 let rustc = exists("g:rustc_path") ? g:rustc_path : "rustc" 162 163 let relpath = get(a:dict, 'tmpdir_relpath', a:dict.path) 164 let args = [relpath, '--emit', a:type, '-o', output_path] + a:args 165 let pwd = a:dict.istemp ? a:dict.tmpdir : '' 166 let output = s:system(pwd, shellescape(rustc) . " " . join(map(args, 'shellescape(v:val)'))) 167 if output != '' 168 echohl WarningMsg 169 echo output 170 echohl None 171 endif 172 if !v:shell_error 173 new 174 exe 'silent keepalt read' fnameescape(output_path) 175 1 176 d 177 if a:type == "llvm-ir" 178 setl filetype=llvm 179 let extension = 'll' 180 elseif a:type == "asm" 181 setl filetype=asm 182 let extension = 's' 183 endif 184 setl buftype=nofile 185 setl bufhidden=hide 186 setl noswapfile 187 if exists('l:extension') 188 " give the buffer a nice name 189 let suffix = 1 190 let basename = fnamemodify(a:dict.path, ':t:r') 191 while 1 192 let bufname = basename 193 if suffix > 1 | let bufname .= ' ('.suffix.')' | endif 194 let bufname .= '.'.extension 195 if bufexists(bufname) 196 let suffix += 1 197 continue 198 endif 199 exe 'silent noautocmd keepalt file' fnameescape(bufname) 200 break 201 endwhile 202 endif 203 endif 204 endtry 205endfunction 206 207" Utility functions {{{1 208 209" Invokes func(dict, ...) 210" Where {dict} is a dictionary with the following keys: 211" 'path' - The path to the file 212" 'tmpdir' - The path to a temporary directory that will be deleted when the 213" function returns. 214" 'istemp' - 1 if the path is a file inside of {dict.tmpdir} or 0 otherwise. 215" If {istemp} is 1 then an additional key is provided: 216" 'tmpdir_relpath' - The {path} relative to the {tmpdir}. 217" 218" {dict.path} may be a path to a file inside of {dict.tmpdir} or it may be the 219" existing path of the current buffer. If the path is inside of {dict.tmpdir} 220" then it is guaranteed to have a '.rs' extension. 221function! s:WithPath(func, ...) 222 let buf = bufnr('') 223 let saved = {} 224 let dict = {} 225 try 226 let saved.write = &write 227 set write 228 let dict.path = expand('%') 229 let pathisempty = empty(dict.path) 230 231 " Always create a tmpdir in case the wrapped command wants it 232 let dict.tmpdir = tempname() 233 call mkdir(dict.tmpdir) 234 235 if pathisempty || !saved.write 236 let dict.istemp = 1 237 " if we're doing this because of nowrite, preserve the filename 238 if !pathisempty 239 let filename = expand('%:t:r').".rs" 240 else 241 let filename = 'unnamed.rs' 242 endif 243 let dict.tmpdir_relpath = filename 244 let dict.path = dict.tmpdir.'/'.filename 245 246 let saved.mod = &mod 247 set nomod 248 249 silent exe 'keepalt write! ' . fnameescape(dict.path) 250 if pathisempty 251 silent keepalt 0file 252 endif 253 else 254 let dict.istemp = 0 255 update 256 endif 257 258 call call(a:func, [dict] + a:000) 259 finally 260 if bufexists(buf) 261 for [opt, value] in items(saved) 262 silent call setbufvar(buf, '&'.opt, value) 263 unlet value " avoid variable type mismatches 264 endfor 265 endif 266 if has_key(dict, 'tmpdir') | silent call s:RmDir(dict.tmpdir) | endif 267 endtry 268endfunction 269 270function! rust#AppendCmdLine(text) 271 call setcmdpos(getcmdpos()) 272 let cmd = getcmdline() . a:text 273 return cmd 274endfunction 275 276" Tokenize the string according to sh parsing rules 277function! s:ShellTokenize(text) 278 " states: 279 " 0: start of word 280 " 1: unquoted 281 " 2: unquoted backslash 282 " 3: double-quote 283 " 4: double-quoted backslash 284 " 5: single-quote 285 let l:state = 0 286 let l:current = '' 287 let l:args = [] 288 for c in split(a:text, '\zs') 289 if l:state == 0 || l:state == 1 " unquoted 290 if l:c ==# ' ' 291 if l:state == 0 | continue | endif 292 call add(l:args, l:current) 293 let l:current = '' 294 let l:state = 0 295 elseif l:c ==# '\' 296 let l:state = 2 297 elseif l:c ==# '"' 298 let l:state = 3 299 elseif l:c ==# "'" 300 let l:state = 5 301 else 302 let l:current .= l:c 303 let l:state = 1 304 endif 305 elseif l:state == 2 " unquoted backslash 306 if l:c !=# "\n" " can it even be \n? 307 let l:current .= l:c 308 endif 309 let l:state = 1 310 elseif l:state == 3 " double-quote 311 if l:c ==# '\' 312 let l:state = 4 313 elseif l:c ==# '"' 314 let l:state = 1 315 else 316 let l:current .= l:c 317 endif 318 elseif l:state == 4 " double-quoted backslash 319 if stridx('$`"\', l:c) >= 0 320 let l:current .= l:c 321 elseif l:c ==# "\n" " is this even possible? 322 " skip it 323 else 324 let l:current .= '\'.l:c 325 endif 326 let l:state = 3 327 elseif l:state == 5 " single-quoted 328 if l:c == "'" 329 let l:state = 1 330 else 331 let l:current .= l:c 332 endif 333 endif 334 endfor 335 if l:state != 0 336 call add(l:args, l:current) 337 endif 338 return l:args 339endfunction 340 341function! s:RmDir(path) 342 " sanity check; make sure it's not empty, /, or $HOME 343 if empty(a:path) 344 echoerr 'Attempted to delete empty path' 345 return 0 346 elseif a:path == '/' || a:path == $HOME 347 echoerr 'Attempted to delete protected path: ' . a:path 348 return 0 349 endif 350 return system("rm -rf " . shellescape(a:path)) 351endfunction 352 353" Executes {cmd} with the cwd set to {pwd}, without changing Vim's cwd. 354" If {pwd} is the empty string then it doesn't change the cwd. 355function! s:system(pwd, cmd) 356 let cmd = a:cmd 357 if !empty(a:pwd) 358 let cmd = 'cd ' . shellescape(a:pwd) . ' && ' . cmd 359 endif 360 return system(cmd) 361endfunction 362 363" Playpen Support {{{1 364" Parts of gist.vim by Yasuhiro Matsumoto <[email protected]> reused 365" gist.vim available under the BSD license, available at 366" http://github.com/mattn/gist-vim 367function! s:has_webapi() 368 if !exists("*webapi#http#post") 369 try 370 call webapi#http#post() 371 catch 372 endtry 373 endif 374 return exists("*webapi#http#post") 375endfunction 376 377function! rust#Play(count, line1, line2, ...) abort 378 redraw 379 380 let l:rust_playpen_url = get(g:, 'rust_playpen_url', 'https://play.rust-lang.org/') 381 let l:rust_shortener_url = get(g:, 'rust_shortener_url', 'https://is.gd/') 382 383 if !s:has_webapi() 384 echohl ErrorMsg | echomsg ':RustPlay depends on webapi.vim (https://github.com/mattn/webapi-vim)' | echohl None 385 return 386 endif 387 388 let bufname = bufname('%') 389 if a:count < 1 390 let content = join(getline(a:line1, a:line2), "\n") 391 else 392 let save_regcont = @" 393 let save_regtype = getregtype('"') 394 silent! normal! gvy 395 let content = @" 396 call setreg('"', save_regcont, save_regtype) 397 endif 398 399 let body = l:rust_playpen_url."?code=".webapi#http#encodeURI(content) 400 401 if strlen(body) > 5000 402 echohl ErrorMsg | echomsg 'Buffer too large, max 5000 encoded characters ('.strlen(body).')' | echohl None 403 return 404 endif 405 406 let payload = "format=simple&url=".webapi#http#encodeURI(body) 407 let res = webapi#http#post(l:rust_shortener_url.'create.php', payload, {}) 408 let url = res.content 409 410 redraw | echomsg 'Done: '.url 411endfunction 412 413" }}}1 414 415" vim: set noet sw=8 ts=8: 416