" Author: Eric Van Dewoestine " " License: {{{ " " Copyright (C) 2005 - 2014 Eric Van Dewoestine " " This program is free software: you can redistribute it and/or modify " it under the terms of the GNU General Public License as published by " the Free Software Foundation, either version 3 of the License, or " (at your option) any later version. " " This program is distributed in the hope that it will be useful, " but WITHOUT ANY WARRANTY; without even the implied warranty of " MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the " GNU General Public License for more details. " " You should have received a copy of the GNU General Public License " along with this program. If not, see . " " }}} " Script Variables {{{ let s:buffer_write_closing_commands = '^\s*\(' . \ 'wq\|xa\|' . \ '\d*w[nN]\|\d*wp\|' . \ 'ZZ' . \ '\)' let s:bourne_shells = ['sh', 'bash', 'dash', 'ksh', 'zsh'] let s:c_shells = ['csh', 'tcsh'] let s:show_current_error_displaying = 0 let s:command_setting = '-command setting -s ' let s:log_levels = { \ 'trace': 5, \ 'debug': 4, \ 'info': 3, \ 'warning': 2, \ 'error': 1, \ 'off': 0, \ } " }}} " Balloon(message) {{{ " Function for use as a vim balloonexpr expression. function! eclim#util#Balloon(message) let message = a:message if !has('balloon_multiline') " remove any new lines let message = substitute(message, '\n', ' ', 'g') endif return message endfunction " }}} " CompilerExists(compiler) {{{ " Check whether a particular vim compiler is available. function! eclim#util#CompilerExists(compiler) if !exists('s:compilers') redir => compilers silent compiler redir END let s:compilers = split(compilers, '\n') call map(s:compilers, 'fnamemodify(v:val, ":t:r")') endif return index(s:compilers, a:compiler) != -1 endfunction " }}} " DelayedCommand(command, [delay]) {{{ " Executes a delayed command. Useful in cases where one would expect an " autocommand event (WinEnter, etc) to fire, but doesn't, or you need a " command to execute after other autocommands have finished. " Note: Nesting is not supported. A delayed command cannot be invoke off " another delayed command. function! eclim#util#DelayedCommand(command, ...) let uid = fnamemodify(tempname(), ':t:r') if &updatetime > 1 exec 'let g:eclim_updatetime_save' . uid . ' = &updatetime' endif exec 'let g:eclim_delayed_command' . uid . ' = a:command' let &updatetime = len(a:000) ? a:000[0] : 1 exec 'augroup delayed_command' . uid exec 'autocmd CursorHold * ' . \ ' if exists("g:eclim_updatetime_save' . uid . '") | ' . \ ' let &updatetime = g:eclim_updatetime_save' . uid . ' | ' . \ ' unlet g:eclim_updatetime_save' . uid . ' | ' . \ ' endif | ' . \ ' exec g:eclim_delayed_command' . uid . ' | ' . \ ' unlet g:eclim_delayed_command' . uid . ' | ' . \ ' autocmd! delayed_command' . uid exec 'augroup END' endfunction " }}} function! eclim#util#EchoTrace(message, ...) " {{{ " Optional args: " time_elapsed let message = a:message if a:0 > 0 let message = '(' . a:1 . 's) ' . message endif call s:EchoLevel(message, 'trace', g:EclimHighlightTrace) endfunction " }}} function! eclim#util#EchoDebug(message) " {{{ call s:EchoLevel(a:message, 'debug', g:EclimHighlightDebug) endfunction " }}} function! eclim#util#EchoInfo(message) " {{{ call s:EchoLevel(a:message, 'info', g:EclimHighlightInfo) endfunction " }}} function! eclim#util#EchoWarning(message) " {{{ call s:EchoLevel(a:message, 'warning', g:EclimHighlightWarning) endfunction " }}} function! eclim#util#EchoError(message) " {{{ call s:EchoLevel(a:message, 'error', g:EclimHighlightError) endfunction " }}} function! s:EchoLevel(message, level, highlight) " {{{ " Echos the supplied message at the supplied level with the specified " highlight. " don't echo if the message is 0, which signals an eclim#Execute failure. if type(a:message) == g:NUMBER_TYPE && a:message == 0 return endif if s:log_levels[g:EclimLogLevel] < s:log_levels[a:level] return endif if type(a:message) == g:LIST_TYPE let messages = a:message else let messages = split(a:message, '\n') endif exec "echohl " . a:highlight redraw if mode() == 'n' || mode() == 'c' || s:log_levels[a:level] > s:log_levels['info'] " Note: in command mode, the message won't display, but the user can view " it using :messages for line in messages echom line endfor else " if we aren't in normal mode then use regular 'echo' since echom " messages won't be displayed while the current mode is displayed in " vim's command line (but still use echom above for debug/verbose messages " so the user can get at them with :messages). echo join(messages, "\n") . "\n" endif echohl None endfunction " }}} function! eclim#util#Echo(message) " {{{ " Echos a message using the info highlight regardless of what log level is set. if a:message != "0" exec "echohl " . g:EclimHighlightInfo redraw for line in split(a:message, '\n') echom line endfor echohl None endif endfunction " }}} " EscapeBufferName(name) {{{ " Escapes the supplied buffer name so that it can be safely used by buf* " functions. function! eclim#util#EscapeBufferName(name) let name = a:name " escaping the space in cygwin could lead to the dos path error message that " cygwin throws when a dos path is referenced. if !has('win32unix') let name = escape(a:name, ' ') endif return substitute(name, '\(.\{-}\)\[\(.\{-}\)\]\(.\{-}\)', '\1[[]\2[]]\3', 'g') endfunction " }}} " Exec(cmd [,output]) {{{ " Used when executing ! commands that may be disrupted by non default vim " options. function! eclim#util#Exec(cmd, ...) let exec_output = len(a:000) > 0 ? a:000[0] : 0 return eclim#util#System(a:cmd, 1, exec_output) endfunction " }}} " ExecWithoutAutocmds(cmd, [events]) {{{ " Execute a command after disabling all autocommands (borrowed from taglist.vim) function! eclim#util#ExecWithoutAutocmds(cmd, ...) let save_opt = &eventignore let events = len(a:000) == 0 ? 'all' : a:000[0] exec 'set eventignore=' . events try exec a:cmd finally let &eventignore = save_opt endtry endfunction " }}} " FindFileInPath(file, exclude_relative) {{{ " Searches for the supplied file in the &path. " If exclude_relative supplied is 1, then relative &path entries ('.' and '') " are not searched). function! eclim#util#FindFileInPath(file, exclude_relative) let path = &path if a:exclude_relative " remove '' path entry let path = substitute(path, '[,]\?[,]\?', '', 'g') " remove '.' path entry let path = substitute(path, '[,]\?\.[,]\?', '', 'g') endif return split(eclim#util#Globpath(path, "**/" . a:file), '\n') endfunction " }}} " Findfile(name, [path, count]) {{{ " Used to issue a findfile() handling any vim options that may otherwise " disrupt it. function! eclim#util#Findfile(name, ...) let savewig = &wildignore set wildignore="" if len(a:000) == 0 let result = findfile(a:name) elseif len(a:000) == 1 let result = findfile(a:name, expand(escape(a:000[0], '*'))) elseif len(a:000) == 2 let result = findfile(a:name, expand(escape(a:000[0], '*')), a:000[1]) endif let &wildignore = savewig return result endfunction " }}} " GetEncoding() {{{ " Gets the encoding of the current file. function! eclim#util#GetEncoding() let encoding = &fileencoding if encoding == '' let encoding = &encoding endif " handle vim's compiled without multi-byte support if encoding == '' let encoding = 'utf-8' endif return encoding endfunction " }}} " GetOffset([line, col]) {{{ " Gets the byte offset for the current cursor position or supplied line, col. function! eclim#util#GetOffset(...) let lnum = a:0 > 0 ? a:000[0] : line('.') let cnum = a:0 > 1 ? a:000[1] : col('.') let offset = 0 " handle case where display encoding differs from the underlying file " encoding if &fileencoding != '' && &encoding != '' && &fileencoding != &encoding let prev = lnum - 1 if prev > 0 let lineEnding = &ff == 'dos' ? "\r\n" : "\n" " convert each line to the file encoding and sum their lengths let offset = eval( \ join( \ map( \ range(1, prev), \ 'len(iconv(getline(v:val), &encoding, &fenc) . "' . lineEnding . '")'), \ '+')) endif " normal case else let offset = line2byte(lnum) - 1 endif let offset += cnum - 1 return offset endfunction " }}} " GetCurrentElementColumn() {{{ " Gets the column for the element under the cursor. function! eclim#util#GetCurrentElementColumn() let pos = getpos('.') let line = getline('.') " cursor not on the word if line[col('.') - 1] =~ '\W' silent normal! w " cursor not at the beginning of the word elseif line[col('.') - 2] =~ '\w' silent normal! b endif let col = col('.') " restore the cursor position. call setpos('.', pos) return col endfunction " }}} " GetCurrentElementPosition() {{{ " Gets the byte offset and length for the element under the cursor. function! eclim#util#GetCurrentElementPosition() let offset = eclim#util#GetCurrentElementOffset() let word = expand('') return offset . ";" . strlen(word) endfunction " }}} " GetCurrentElementOffset() {{{ " Gets the byte offset for the element under the cursor. function! eclim#util#GetCurrentElementOffset() let pos = getpos('.') let line = getline('.') " cursor not on the word if line[col('.') - 1] =~ '\W' silent normal! w " cursor not at the beginning of the word elseif line[col('.') - 2] =~ '\w' silent normal! b endif let offset = eclim#util#GetOffset() " restore the cursor position. call setpos('.', pos) return offset endfunction " }}} " GetIndent(level) {{{ " Gets an indentation string for the supplied indentation level. function! eclim#util#GetIndent(level) let result = '' if a:level if !exists('b:eclim_indent') if exists('g:EclimIndent') let b:eclim_indent = g:EclimIndent else if !&expandtab let b:eclim_indent = "\t" else let b:eclim_indent = '' let index = 0 while index < &shiftwidth let b:eclim_indent = b:eclim_indent . " " let index = index + 1 endwhile endif endif endif let num = a:level while num > 0 let result .= b:eclim_indent let num -= 1 endwhile endif return result endfunction " }}} " GetLineError(line) {{{ " Gets the error (or message) for the supplie line number if one. function! eclim#util#GetLineError(line) let line = line('.') let col = col('.') let errornum = 0 let errorcol = 0 let index = 0 let locerrors = getloclist(0) let qferrors = getqflist() let bufname = expand('%') let lastline = line('$') for error in qferrors + locerrors let index += 1 if bufname(error.bufnr) == bufname && \ (error.lnum == line || (error.lnum > lastline && line == lastline)) if errornum == 0 || (col >= error.col && error.col != errorcol) let errornum = index let errorcol = error.col endif endif endfor if errornum > 0 let src = 'qf' let cnt = len(qferrors) let errors = qferrors if errornum > cnt let errornum -= cnt let src = 'loc' let cnt = len(locerrors) let errors = locerrors endif let message = src . ' - (' . errornum . ' of ' . cnt . '): ' \ . substitute(errors[errornum - 1].text, '^\s\+', '', '') return message endif return '' endfunction " }}} " GetPathEntry(file) {{{ " Returns the path entry that contains the supplied file (excluding '.' and ''). " The argument must be an absolute path to the file. " &path is expected to be using commas for path delineation. " Returns 0 if no path found. function! eclim#util#GetPathEntry(file) let paths = split(&path, ',') for path in paths if path != "" && path != "." let path = substitute(expand(path), '\', '/', 'g') let file = substitute(expand(a:file), '\', '/', 'g') if file =~ '^' . path return path endif endif endfor return 0 endfunction " }}} " GetSetting(setting, [workspace]) {{{ " Gets a global setting from eclim. Returns '' if the setting does not " exist, 0 if an error occurs communicating with the server. function! eclim#util#GetSetting(setting, ...) let command = s:command_setting let command = substitute(command, '', a:setting, '') let workspace = a:0 > 0 ? a:1 : '' let result = eclim#Execute(command, {'workspace': workspace}) if result == '0' return result endif if result == '' call eclim#util#EchoWarning("Setting '" . a:setting . "' does not exist.") endif return result endfunction " }}} " GetVisualSelection(line1, line2, default) {{{ " Returns the contents of, and then clears, the last visual selection. " If default is set, the default range will be honor. function! eclim#util#GetVisualSelection(line1, line2, default) let lines = a:default ? getline(a:line1, a:line2) : [] let mode = visualmode(1) if mode != '' && line("'<") == a:line1 if len(lines) == 0 let lines = getline(a:line1, a:line2) endif if mode == "v" let start = col("'<") - 1 let end = col("'>") - 1 " slice in end before start in case the selection is only one line let lines[-1] = lines[-1][: end] let lines[0] = lines[0][start :] elseif mode == "\" let start = col("'<") if col("'>") < start let start = col("'>") endif let start = start - 1 call map(lines, 'v:val[start :]') endif endif return join(lines, "\n") endfunction " }}} " Glob(expr, [honor_wildignore]) {{{ " Used to issue a glob() handling any vim options that may otherwise disrupt " it. function! eclim#util#Glob(expr, ...) if len(a:000) == 0 let savewig = &wildignore set wildignore="" endif let paths = split(a:expr, '\n') if len(paths) == 1 let result = glob(paths[0]) else let result = join(paths, "\n") endif if len(a:000) == 0 let &wildignore = savewig endif return result endfunction " }}} " Globpath(path, expr, [honor_wildignore]) {{{ " Used to issue a globpath() handling any vim options that may otherwise disrupt " it. function! eclim#util#Globpath(path, expr, ...) if len(a:000) == 0 let savewig = &wildignore set wildignore="" endif let result = globpath(a:path, a:expr) if len(a:000) == 0 let &wildignore = savewig endif return result endfunction " }}} " GoToBufferWindow(buf) {{{ " Focuses the window containing the supplied buffer name or buffer number. " Returns 1 if the window was found, 0 otherwise. function! eclim#util#GoToBufferWindow(buf) if type(a:buf) == g:NUMBER_TYPE let winnr = bufwinnr(a:buf) else let name = eclim#util#EscapeBufferName(a:buf) let winnr = bufwinnr(bufnr('^' . name . '$')) endif if winnr != -1 exec winnr . "winc w" call eclim#util#DelayedCommand('doautocmd WinEnter') return 1 endif return 0 endfunction " }}} " GoToBufferWindowOrOpen(name, cmd) {{{ " Gives focus to the window containing the buffer for the supplied file, or if " none, opens the file using the supplied command. function! eclim#util#GoToBufferWindowOrOpen(name, cmd) let name = eclim#util#EscapeBufferName(a:name) let winnr = bufwinnr(bufnr('^' . name . '$')) if winnr != -1 exec winnr . "winc w" call eclim#util#DelayedCommand('doautocmd WinEnter') else let cmd = a:cmd " if splitting and the buffer is a unamed empty buffer, then switch to an " edit. if cmd == 'split' && expand('%') == '' && \ !&modified && line('$') == 1 && getline(1) == '' let cmd = 'edit' endif silent exec cmd . ' ' . escape(eclim#util#Simplify(a:name), ' ') endif endfunction " }}} " GoToBufferWindowRegister(buf) {{{ " Registers the autocmd for returning the user to the supplied buffer when the " current buffer is closed. function! eclim#util#GoToBufferWindowRegister(buf) exec 'autocmd BufWinLeave ' . \ 'call eclim#util#GoToBufferWindow("' . escape(a:buf, '\') . '") | ' . \ 'doautocmd BufEnter' endfunction " }}} " GrabUri([line, col]) {{{ " Grabs an uri from the file's current cursor position. function! eclim#util#GrabUri(...) if len(a:000) == 2 let lnum = a:000[0] let cnum = a:000[1] else let lnum = line('.') let cnum = col('.') endif let line = getline(lnum) let uri = substitute(line, \ "\\(.*[[:space:]\"',(\\[{><]\\|^\\)\\(.*\\%" . \ cnum . "c.\\{-}\\)\\([[:space:]\"',)\\]}<>].*\\|$\\)", \ '\2', '') return uri endfunction " }}} " ListContains(list, element) {{{ " Returns 1 if the supplied list contains the specified element, 0 otherwise. " To determine element equality both '==' and 'is' are tried as well as " ^element$ to support a regex supplied element string. function! eclim#util#ListContains(list, element) let string = type(a:element) == g:STRING_TYPE ? \ a:element : escape(string(a:element), '\') for element in a:list if element is a:element || \ (type(element) == type(a:element) && element == a:element) return 1 else let estring = type(element) == g:STRING_TYPE ? element : string(element) if estring =~ '^' . string . '$' return 1 endif endif endfor return 0 endfunction " }}} function! eclim#util#ListDedupe(list) " {{{ " assumes the list is presorted. if exists('*uniq') return uniq(copy(a:list)) endif return filter(copy(a:list), 'index(a:list, v:val, v:key + 1) == -1') endfunction " }}} function! eclim#util#Make(bang, args) " {{{ " Executes make using the supplied arguments. let makefile = findfile('makefile', '.;') let makefile2 = findfile('Makefile', '.;') if len(makefile2) > len(makefile) let makefile = makefile2 endif let cwd = getcwd() let save_mlcd = g:EclimMakeLCD exec 'lcd ' . fnamemodify(makefile, ':h') let g:EclimMakeLCD = 0 try call eclim#util#MakeWithCompiler('eclim_make', a:bang, a:args) finally exec 'lcd ' . escape(cwd, ' ') let g:EclimMakeLCD = save_mlcd endtry endfunction " }}} " MakeWithCompiler(compiler, bang, args) {{{ " Executes :make using the supplied compiler. " Note: on windows the make program will be executed manually if the 'tee' " progam is available (only the cygwin version is currenty supported) to allow " the display of the make program output while running. function! eclim#util#MakeWithCompiler(compiler, bang, args, ...) if exists('g:current_compiler') let saved_compiler = g:current_compiler endif if exists('b:current_compiler') let saved_compiler = b:current_compiler endif if !exists('saved_compiler') let saved_makeprg = &makeprg let saved_errorformat = &errorformat endif if has('win32') || has('win64') let saved_shellpipe = &shellpipe set shellpipe=>\ %s\ 2<&1 endif try unlet! g:current_compiler b:current_compiler exec 'compiler ' . a:compiler let make_cmd = substitute(&makeprg, '\$\*', a:args, '') if g:EclimMakeLCD && eclim#EclimAvailable(0) let w:quickfix_dir = getcwd() let dir = eclim#project#util#GetCurrentProjectRoot() if dir != '' exec 'lcd ' . escape(dir, ' ') endif endif " use dispatch if available and not disabled if exists(':Dispatch') == 2 && g:EclimMakeDispatchEnabled call eclim#util#EchoTrace('dispatch: ' . make_cmd) " since dispatch is intended to run the make cmd in the background, make " sure the errorformat doesn't suppress all the non-error output so the " user can see the full build output in the quickfix window. let &l:errorformat=substitute(&errorformat, '\M,%-G%.%#$', '', '') exec 'Dispatch' . a:bang . ' _ ' . a:args " windows machines where 'tee' is available elseif (has('win32') || has('win64')) && \ (executable('tee') || executable('wtee')) doautocmd QuickFixCmdPre make let resultfile = eclim#util#Exec(make_cmd, 2) if filereadable(resultfile) if a:bang == '' exec 'cfile ' . escape(resultfile, ' ') else exec 'cgetfile ' . escape(resultfile, ' ') endif call delete(resultfile) endif silent doautocmd QuickFixCmdPost make " all other platforms else call eclim#util#EchoTrace('make: ' . make_cmd) exec 'make' . a:bang . ' ' . a:args endif catch /E42\>/ " ignore 'E42: No Errors' which occurs when the make has qf results, but a " QuickFixCmdPost filters them all out. finally if exists('saved_compiler') unlet! g:current_compiler b:current_compiler exec 'compiler ' . saved_compiler unlet saved_compiler else let &makeprg = saved_makeprg let &errorformat = saved_errorformat endif if has('win32') || has('win64') let &shellpipe = saved_shellpipe endif if exists('w:quickfix_dir') exec 'lcd ' . escape(w:quickfix_dir, ' ') unlet w:quickfix_dir endif endtry endfunction " }}} " MarkRestore(markLine) {{{ " Restores the ' mark with the new line. function! eclim#util#MarkRestore(markLine) let pos = getpos('.') call cursor(a:markLine, s:markCol) mark ' call setpos('.', pos) endfunction " }}} " MarkSave() {{{ " Saves the ' mark and returns the line. function! eclim#util#MarkSave() let s:markCol = col("'`") return line("''") endfunction " }}} " Pad(string, length, [char]) {{{ " Pad the supplied string. function! eclim#util#Pad(string, length, ...) let char = a:0 > 0 ? a:1 : ' ' let string = a:string while len(string) < a:length let string .= char endwhile return string endfunction " }}} " ParseArgs(args) {{{ " Parses the supplied argument line into a list of args, handling quoted " strings, escaped spaces, etc. function! eclim#util#ParseArgs(args) let args = [] let arg = '' let quote = '' let escape = 0 let index = 0 while index < len(a:args) let char = a:args[index] let index += 1 if char == ' ' && quote == '' && !escape if arg != '' call add(args, arg) let arg = '' endif elseif char == '\' if escape let arg .= char endif let escape = !escape elseif char == '"' || char == "'" if !escape if quote != '' && char == quote let quote = '' elseif quote == '' let quote = char else let arg .= char endif else let arg .= char let escape = 0 endif else if escape && char != ' ' let arg .= '\' endif let arg .= char let escape = 0 endif endwhile if arg != '' call add(args, arg) endif return args endfunction " }}} " ParseLocationEntries(entries, [sort]) {{{ " Parses the supplied list of location entry lines (%f|%l col %c|%m) into a " vim compatable list of dictionaries that can be passed to setqflist() or " setloclist(). " In addition to the above line format, this function also supports " %f|%l col %c|%m|%s, where %s is the type of the entry. The value will " be placed in the dictionary under the 'type' key. " The optional 'sort' parameter currently only supports 'severity' as an " argument. function! eclim#util#ParseLocationEntries(entries, ...) if len(a:000) > 0 && a:1 == 'severity' let entries = {} else let entries = [] endif for entry in a:entries let dict = s:ParseLocationEntry(entry) " partition by severity if type(entries) == g:DICT_TYPE " empty key not allowed let type = dict.type == '' ? ' ' : tolower(dict.type) if !has_key(entries, type) let entries[type] = [] endif call add(entries[type], dict) " default sort else call add(entries, dict) endif endfor " re-assemble severity partitioned results if type(entries) == g:DICT_TYPE let results = [] if has_key(entries, 'e') let results += remove(entries, 'e') endif if has_key(entries, 'w') let results += remove(entries, 'w') endif if has_key(entries, 'i') let results += remove(entries, 'i') endif " should only be key '' (no type), but we don't want to accidentally " filter out other possible types. let keys = keys(entries) call reverse(sort(keys)) for key in keys let results += entries[key] endfor return results endif return entries endfunction " }}} " s:ParseLocationEntry(entry) {{{ function! s:ParseLocationEntry(entry) let entry = a:entry if type(entry) == g:DICT_TYPE let file = entry.filename let line = entry.line let col = entry.column let message = entry.message let type = '' if has_key(entry, 'type') let type = entry.type[0] elseif has_key(entry, 'warning') let type = entry.warning ? 'w' : 'e' endif " FIXME: should be safe to remove this block after all commands have gone " through the json conversion. else let file = substitute(entry, '\(.\{-}\)|.*', '\1', '') let line = substitute(entry, '.*|\([0-9]\+\) col.*', '\1', '') let col = substitute(entry, '.*col \([0-9]\+\)|.*', '\1', '') let message = substitute(entry, '.*col [0-9]\+|\(.\{-}\)\(|.*\|$\)', '\1', '') let type = substitute(entry, '.*|\(e\|w\)$', '\1', '') if type == entry let type = '' endif endif if has('win32unix') let file = eclim#cygwin#CygwinPath(file) endif let dict = { \ 'filename': eclim#util#Simplify(file), \ 'lnum': line, \ 'col': col, \ 'text': message, \ 'type': type \ } return dict endfunction " }}} " Prompt(prompt, [validator], [highlight]) {{{ " Creates a prompt for the user using the supplied prompt string, validator " and highlight. The prompt can be either a just a string to be displayed to " the user or a 2 item list where the first item is the prompt and the second " is the defaut value. The validator may return 0 to indicate an invalid input " or a message indicating why the input is invalid, which will be displayed to " the user. The validator should return 1 or the empty string to indicate " valid input. Returns an empty string if the user doesn't enter a value or " cancels the prompt. function! eclim#util#Prompt(prompt, ...) " for unit testing if exists('g:EclimTestPromptQueue') && len(g:EclimTestPromptQueue) return remove(g:EclimTestPromptQueue, 0) endif let highlight = g:EclimHighlightInfo if a:0 > 0 if type(a:1) == g:FUNCREF_TYPE let Validator = a:1 elseif type(a:1) == g:STRING_TYPE let highlight = a:1 endif endif if a:0 > 1 if type(a:2) == g:FUNCREF_TYPE let Validator = a:2 elseif type(a:2) == g:STRING_TYPE let highlight = a:2 endif endif if type(a:prompt) == g:LIST_TYPE let prompt = a:prompt[0] let default = a:prompt[1] else let prompt = a:prompt endif exec "echohl " . highlight try if exists('l:default') let result = input(prompt . ': ', default) else let result = input(prompt . ': ') endif while result != '' if exists('l:Validator') let valid = Validator(result) if type(valid) == g:STRING_TYPE && valid != '' let result = input(valid . " (Ctrl-C to cancel): ", result) elseif type(valid) == g:NUMBER_TYPE && !valid let result = input(prompt, result) else return result endif else return result endif endwhile finally echohl None endtry return result endfunction " }}} " PromptList(prompt, list, [highlight]) {{{ " Creates a prompt for the user using the supplied prompt string and list of " items to choose from. Returns -1 if the list is empty or if the user " canceled, and 0 if the list contains only one item. function! eclim#util#PromptList(prompt, list, ...) " for unit testing if exists('g:EclimTestPromptQueue') && len(g:EclimTestPromptQueue) return remove(g:EclimTestPromptQueue, 0) endif " no elements, no prompt if empty(a:list) return -1 endif " only one element, no need to choose. if len(a:list) == 1 return 0 endif let prompt = "" let index = g:EclimPromptListStartIndex for item in a:list let prompt = prompt . index . ") " . item . "\n" let index = index + 1 endfor let maxindex = index - 1 exec "echohl " . (a:0 ? a:1 : g:EclimHighlightInfo) try " clear any previous messages redraw try let response = input(prompt . "\n" . a:prompt . ": ") catch " echoing the list prompt vs. using it in the input() avoids apparent vim " bug that causes "Internal error: get_tv_string_buf()". echo prompt . "\n" let response = input(a:prompt . ": ") endtry while response !~ '\(^$\|^[0-9]\+$\)' || \ response < g:EclimPromptListStartIndex || \ response > maxindex let response = input("You must choose a value between " . \ g:EclimPromptListStartIndex . " and " . maxindex . \ ". (Ctrl-C to cancel): ") endwhile finally echohl None redraw! endtry if response == '' return -1 endif return response - g:EclimPromptListStartIndex endfunction " }}} " PromptConfirm(prompt, [highlight]) {{{ " Creates a yes/no prompt for the user using the supplied prompt string. " Returns -1 if the user canceled, otherwise 1 for yes, and 0 for no. function! eclim#util#PromptConfirm(prompt, ...) " for unit testing if exists('g:EclimTestPromptQueue') && len(g:EclimTestPromptQueue) let choice = remove(g:EclimTestPromptQueue, 0) return choice =~ '\c\s*\(y\(es\)\?\)\s*' endif exec "echohl " . (a:0 ? a:1 : g:EclimHighlightInfo) try " clear any previous messages redraw echo a:prompt . "\n" let response = input("(y/n): ") while response != '' && response !~ '^\c\s*\(y\(es\)\?\|no\?\|\)\s*$' let response = input("You must choose either y or n. (Ctrl-C to cancel): ") endwhile finally echohl None endtry if response == '' return -1 endif return response =~ '\c\s*\(y\(es\)\?\)\s*' endfunction " }}} " Complete(start, completions) {{{ " Returns 1 if completion has been setup/triggered, 0 if not (because it is " active already) and any input trigger would need to be inserted by the " caller. function! eclim#util#Complete(start, completions) " {{{ if !exists('##CompleteDone') call complete(a:start, a:completions) return 1 endif let b:eclim_complete_temp_start = a:start let b:eclim_complete_temp_completions = a:completions " If the temporary completion is active already, stop here and indicate " that the trigger needs to be inserted manually by returning 0. " (e.g. '{% ee' in a Django template) if &completefunc == 'eclim#util#CompleteTemp' return 0 endif let b:eclim_complete_temp_func = &completefunc let b:eclim_complete_temp_opt = &completeopt augroup eclim_complete_temp autocmd! autocmd CompleteDone call eclim#util#CompleteTempReset() augroup END setlocal completefunc=eclim#util#CompleteTemp setlocal completeopt=menuone,longest call feedkeys("\\", "n") return 1 endfunction " }}} function! eclim#util#CompleteTemp(findstart, base) " {{{ if a:findstart " complete() is 1 based, but omni completion functions are 0 based return b:eclim_complete_temp_start - 1 endif return b:eclim_complete_temp_completions endfunction " }}} function! eclim#util#CompleteTempReset() " {{{ silent! let &completefunc = b:eclim_complete_temp_func silent! let &completeopt = b:eclim_complete_temp_opt silent! unlet b:eclim_complete_temp_start silent! unlet b:eclim_complete_temp_completions silent! unlet b:eclim_complete_temp_func silent! unlet b:eclim_complete_temp_opt augroup eclim_complete_temp autocmd! augroup END endfunction " }}} function! eclim#util#Reload(options) " {{{ " Reload the current file using ':edit' and perform other operations based on " the options supplied. " Supported Options: " retab: Issue a retab of the file. " pos: A line/column pair indicating the new cursor position post edit. When " this pair is supplied, this function will attempt to preserve the " current window's viewport. let winview = winsaveview() " save expand tab in case an indent detection plugin changes it based on code " inserted by eclipse, which may not yet match the user's actual settings. let save_expandtab = &expandtab edit! let &expandtab = save_expandtab if has_key(a:options, 'pos') && len(a:options.pos) == 2 let lnum = a:options.pos[0] let cnum = a:options.pos[1] if winheight(0) < line('$') let winview.topline += lnum - winview.lnum let winview.lnum = lnum let winview.col = cnum - 1 call winrestview(winview) else call cursor(lnum, cnum) endif endif if has_key(a:options, 'retab') && a:options.retab && &expandtab " set tabstop to the same value as shiftwidth if we may be expanding tabs let save_tabstop = &tabstop let &tabstop = &shiftwidth try retab finally let &tabstop = save_tabstop endtry endif endfunction " }}} function! eclim#util#SetLocationList(list, ...) " {{{ " Sets the contents of the location list for the current window. " Optional args: " action: The action passed to the setloclist() function call. let loclist = a:list " filter the list if the current buffer defines a list of filters. if exists('b:EclimLocationListFilter') let newlist = [] for item in loclist let addit = 1 for filter in b:EclimLocationListFilter if item.text =~ filter let addit = 0 break endif endfor if addit call add(newlist, item) endif endfor let loclist = newlist endif if a:0 == 0 call setloclist(0, loclist) else call setloclist(0, loclist, a:1) endif silent let projectName = eclim#project#util#GetCurrentProjectName() if projectName != '' " setbufvar seems to have the side affect of changing to the buffer's dir " when autochdir is set. let save_autochdir = &autochdir set noautochdir for item in getloclist(0) call setbufvar(item.bufnr, 'eclim_project', projectName) endfor let &autochdir = save_autochdir endif if g:EclimShowCurrentError && len(loclist) > 0 call eclim#util#DelayedCommand('call eclim#util#ShowCurrentError()') endif let b:eclim_loclist = 1 call eclim#display#signs#Update() endfunction " }}} " ClearLocationList([namespace, namespace, ...]) {{{ " Clears the current location list. Optionally 'namespace' arguments can be " supplied which will only clear items with text prefixed with '[namespace]'. " Also the special namespace 'global' may be supplied which will only remove " items with no namepace prefix. function! eclim#util#ClearLocationList(...) if a:0 > 0 let loclist = getloclist(0) if len(loclist) > 0 let pattern = '' for ns in a:000 if pattern != '' let pattern .= '\|' endif if ns == 'global' let pattern .= '\(\[\w\+\]\)\@!' else let pattern .= '\[' . ns . '\]' endif endfor let pattern = '^\(' . pattern . '\)' call filter(loclist, 'v:val.text !~ pattern') call setloclist(0, loclist, 'r') endif else call setloclist(0, [], 'r') endif call eclim#display#signs#Update() unlet! b:eclim_loclist endfunction " }}} " SetQuickfixList(list, [action]) {{{ " Sets the contents of the quickfix list. function! eclim#util#SetQuickfixList(list, ...) let qflist = a:list if exists('b:EclimQuickfixFilter') let newlist = [] for item in qflist let addit = 1 for filter in b:EclimQuickfixFilter if item.text =~ filter let addit = 0 break endif endfor if addit call add(newlist, item) endif endfor let qflist = newlist endif if a:0 == 0 call setqflist(qflist) else call setqflist(qflist, a:1) endif if g:EclimShowCurrentError && len(qflist) > 0 call eclim#util#DelayedCommand('call eclim#util#ShowCurrentError()') endif call eclim#display#signs#Update() endfunction " }}} " ShowCurrentError() {{{ " Shows the error on the cursor line if one. function! eclim#util#ShowCurrentError() if mode() != 'n' || expand('%') == '' return endif let message = eclim#util#GetLineError(line('.')) if message != '' " remove any new lines let message = substitute(message, '\n', ' ', 'g') " convert tabs to spaces to ensure a consistent char to display length let message = substitute(message, '\t', ' ', 'g') call eclim#util#WideMessage('echo', message) let s:show_current_error_displaying = 1 else " clear the message if one of our error messages was displaying if s:show_current_error_displaying call eclim#util#WideMessage('echo', message) let s:show_current_error_displaying = 0 endif endif endfunction " }}} " Simplify(file) {{{ " Simply the supplied file to the shortest valid name. function! eclim#util#Simplify(file) let file = a:file " Don't run simplify on url files, it will screw them up. if file !~ '://' let file = simplify(file) endif " replace all '\' chars with '/' except those escaping spaces. let file = substitute(file, '\\\([^[:space:]]\)', '/\1', 'g') let cwd = substitute(getcwd(), '\', '/', 'g') if cwd !~ '/$' let cwd .= '/' endif if file =~ '^' . cwd let file = substitute(file, '^' . cwd, '', '') endif return file endfunction " }}} " System(cmd, [exec, exec_results]) {{{ " Executes system() accounting for possibly disruptive vim options. " exec (0 or 1): whether or not to use exec instead of system " exec_results (0, 1, or 2): 0 to not return the results of an exec, 1 to " return the results, or 2 to return the filename containing the results. function! eclim#util#System(cmd, ...) let saveshell = &shell let saveshellcmdflag = &shellcmdflag let saveshellpipe = &shellpipe let saveshellquote = &shellquote let saveshellredir = &shellredir let saveshellslash = &shellslash let saveshelltemp = &shelltemp let saveshellxquote = &shellxquote if has("win32") || has("win64") set shell=cmd.exe set shellcmdflag=/c set shellpipe=>%s\ 2>&1 set shellquote= set shellredir=>%s\ 2>&1 set noshellslash set shelltemp set shellxquote= else if executable('/bin/bash') set shell=/bin/bash else set shell=/bin/sh endif set shellcmdflag=-c set shellpipe=2>&1\|\ tee set shellquote= set shellredir=>%s\ 2>&1 set noshellslash set shelltemp set shellxquote= endif try " use exec if len(a:000) > 0 && a:000[0] let cmd = a:cmd let begin = localtime() let exec_output = len(a:000) > 1 ? a:000[1] : 0 if exec_output let outfile = g:EclimTempDir . '/eclim_exec_output.txt' if has('win32') || has('win64') || has('win32unix') let cmd = substitute(cmd, '^!', '', '') if has('win32unix') let cmd = '!cmd /c "' . cmd . ' 2>&1 " | tee "' . outfile . '"' elseif executable('tee') || executable('wtee') let tee = executable('wtee') ? 'wtee' : 'tee' let cmd = '!cmd /c "' . cmd . ' 2>&1 | ' . tee . ' "' . outfile . '" "' else let cmd = '!cmd /c "' . cmd . ' >"' . outfile . '" 2>&1 "' endif else let cmd .= ' 2>&1| tee "' . outfile . '"' endif endif try exec cmd finally call eclim#util#EchoTrace('exec: ' . cmd, localtime() - begin) endtry let result = '' if exec_output == 1 && filereadable(outfile) let result = join(readfile(outfile), "\n") call delete(outfile) elseif exec_output == 2 let result = outfile endif " use system else let begin = localtime() let cmd = a:cmd try " Dos is pretty bad at dealing with quoting of commands resulting in " eclim calls failing if the path to the eclim bat/cmd file is quoted " and there is a quoted arg in that command as well. We can fix this " by wrapping the whole command in quotes with a space between the " quotes and the actual command. if (has('win32') || has('win64')) && a:cmd =~ '^"' let cmd = '" ' . cmd . ' "' " same issue, but handle the fact that we prefix eclim calls with " 'cmd /c' for cygwin elseif has('win32unix') && a:cmd =~? '^cmd /c "[a-z]' let cmd = 'cmd /c " ' . substitute(cmd, '^cmd /c ', '', '') . ' "' endif let result = system(cmd) finally call eclim#util#EchoTrace('system: ' . cmd, localtime() - begin) endtry endif finally let &shell = saveshell let &shellcmdflag = saveshellcmdflag let &shellquote = saveshellquote let &shellslash = saveshellslash let &shelltemp = saveshelltemp let &shellxquote = saveshellxquote " If a System call is executed at startup, it appears to interfere with " vim's setting of 'shellpipe' and 'shellredir' to their shell specific " values. So, if we detect that the values we are restoring look like " uninitialized defaults, then attempt to mimic vim's documented " (:h 'shellpipe' :h 'shellredir') logic for setting the proper values based " on the shell. " Note: still doesn't handle more obscure shells if saveshellredir == '>' if index(s:bourne_shells, fnamemodify(&shell, ':t')) != -1 set shellpipe=2>&1\|\ tee set shellredir=>%s\ 2>&1 elseif index(s:c_shells, fnamemodify(&shell, ':t')) != -1 set shellpipe=\|&\ tee set shellredir=>& else let &shellpipe = saveshellpipe let &shellredir = saveshellredir endif else let &shellpipe = saveshellpipe let &shellredir = saveshellredir endif endtry return result endfunction " }}} function! eclim#util#TempWindow(name, lines, ...) " {{{ " Opens a temp window w/ the given name and contents which is readonly unless " specified otherwise. let options = a:0 > 0 ? a:1 : {} let filename = expand('%:p') let winnr = winnr() let bufname = eclim#util#EscapeBufferName(a:name) let name = escape(a:name, ' ') if has('unix') let name = escape(name, '[]') endif let line = 1 let col = 1 if bufwinnr(bufname) == -1 let height = get(options, 'height', 10) silent! noautocmd exec "keepalt botright " . height . "sview " . name setlocal nowrap setlocal winfixheight setlocal noswapfile setlocal nobuflisted setlocal buftype=nofile setlocal bufhidden=wipe silent doautocmd WinEnter else let temp_winnr = bufwinnr(bufname) if temp_winnr != winnr() exec temp_winnr . 'winc w' silent doautocmd WinEnter if get(options, 'preserveCursor', 0) let line = line('.') let col = col('.') endif endif endif call eclim#util#TempWindowClear(a:name) setlocal modifiable setlocal noreadonly call append(1, a:lines) retab let undolevels = &undolevels set undolevels=-1 silent 1,1delete _ let &undolevels = undolevels call cursor(line, col) if get(options, 'readonly', 1) setlocal nomodified setlocal nomodifiable setlocal readonly nmap q :q endif silent doautocmd BufEnter " Store filename and window number so that plugins can use it if necessary. if filename != expand('%:p') let b:filename = filename let b:winnr = winnr augroup eclim_temp_window autocmd! BufWinLeave call eclim#util#GoToBufferWindowRegister(b:filename) augroup END endif endfunction " }}} function! eclim#util#TempWindowClear(name) " {{{ " Clears the contents of the temp window with the given name. let name = eclim#util#EscapeBufferName(a:name) if bufwinnr(name) != -1 let curwinnr = winnr() exec bufwinnr(name) . "winc w" setlocal modifiable setlocal noreadonly silent 1,$delete _ exec curwinnr . "winc w" endif endfunction " }}} " WideMessage(command, message) {{{ " Executes the supplied echo command and forces vim to display as much as " possible without the "Press Enter" prompt. " Thanks to vimtip #1289 function! eclim#util#WideMessage(command, message) let saved_ruler = &ruler let saved_showcmd = &showcmd let message = substitute(a:message, '^\s\+', '', '') set noruler noshowcmd redraw let vimwidth = &columns * &cmdheight if len(message) > vimwidth - 1 let remove = len(message) - vimwidth let start = (len(message) / 2) - (remove / 2) - 4 let end = start + remove + 4 let message = substitute(message, '\%' . start . 'c.*\%' . end . 'c', '...', '') endif exec a:command . ' "' . escape(message, '"\') . '"' let &ruler = saved_ruler let &showcmd = saved_showcmd endfunction " }}} " WillWrittenBufferClose() {{{ " Returns 1 if the current buffer is to be hidden/closed/deleted after it is " written, or 0 otherwise. This function is useful during a post write auto " command for determining whether or not to perform some operation based on " whether the buffer will still be visible to the user once the current " command has finished. " Note: This function only detects command typed by the user at the " command (:) prompt, not any normal mappings which may hide/close/delete the " buffer. function! eclim#util#WillWrittenBufferClose() return histget("cmd") =~ s:buffer_write_closing_commands endfunction " }}} function! eclim#util#CommandCompleteFile(argLead, cmdLine, cursorPos) " {{{ let cmdTail = strpart(a:cmdLine, a:cursorPos) let argLead = substitute(a:argLead, cmdTail . '$', '', '') let results = split(eclim#util#Glob(argLead . '*', 1), '\n') call map(results, 'isdirectory(v:val) ? v:val . "/" : v:val') call map(results, "substitute(v:val, '\\', '/', 'g')") call map(results, "substitute(v:val, ' ', '\\\\ ', 'g')") return eclim#util#ParseCommandCompletionResults(argLead, results) endfunction " }}} function! eclim#util#CommandCompleteDir(argLead, cmdLine, cursorPos) " {{{ let cmdLine = strpart(a:cmdLine, 0, a:cursorPos) let args = eclim#util#ParseCmdLine(cmdLine) let argLead = cmdLine =~ '\s$' ? '' : args[len(args) - 1] let results = split(eclim#util#Glob(expand(argLead) . '*', 1), '\n') let index = 0 for result in results if !isdirectory(result) call remove(results, index) else let result = result . '/' let result = substitute(result, '\', '/', 'g') let result = substitute(result, ' ', '\\\\ ', 'g') exec "let results[" . index . "] = \"" . result . "\"" let index += 1 endif endfor return eclim#util#ParseCommandCompletionResults(argLead, results) endfunction " }}} function! eclim#util#CommandCompleteOptions(argLead, cmdLine, cursorPos, options_map) " {{{ let cmdLine = strpart(a:cmdLine, 0, a:cursorPos) let cmdTail = strpart(a:cmdLine, a:cursorPos) let argLead = substitute(a:argLead, cmdTail . '$', '', '') for [key, values] in items(a:options_map) if cmdLine =~ key . '\s\+[a-z]*$' return filter(copy(values), 'v:val =~ "^' . argLead . '"') endif endfor if cmdLine =~ '\s\+[-]\?$' let options = keys(a:options_map) let index = 0 for option in options if a:cmdLine =~ option call remove(options, index) else let index += 1 endif endfor return options endif return [] endfunction " }}} function! eclim#util#ParseCmdLine(args) " {{{ " Parses the supplied argument line into a list of args. let args = split(a:args, '[^\\]\s\zs') call map(args, 'substitute(v:val, "\\([^\\\\]\\)\\s\\+$", "\\1", "")') return args endfunction " }}} function! eclim#util#ParseCommandCompletionResults(argLead, results) " {{{ " Bit of a hack for vim's lack of support for escaped spaces in custom " completion. let results = a:results if stridx(a:argLead, ' ') != -1 let removePrefix = escape(substitute(a:argLead, '\(.*\s\).*', '\1', ''), '\') call map(results, "substitute(v:val, '^" . removePrefix . "', '', '')") endif return results endfunction " }}} function! eclim#util#ExtractCmdArgs(argline, extract) " {{{ " Extracts one or more args from the given argline. " The 'extract' arg here is a list of args in the form '-x' where the -x arg " would be extracted. You can also use the getopts like syntax of '-x:' " (trailing colon) to indicate that you want the arg to the -x option to be " extracted as well. " " Returns a tuple with a list of the extracted args and the updated argline. let extract = type(a:extract) == g:LIST_TYPE ? a:extract : [a:extract] let args = eclim#util#ParseCmdLine(a:argline) let extracted_args = [] let remaining_args = [] let extract_next = 0 for arg in args if extract_next call add(extracted_args, arg) let extract_next = 0 continue endif for e in extract let has_value = 0 if e =~ ':$' let e = e[:-2] let has_value = 1 endif if arg == e call add(extracted_args, arg) let extract_next = has_value else call add(remaining_args, arg) endif endfor endfor return [extracted_args, join(remaining_args)] endfunction "}}} " vim:ft=vim:fdm=marker