function! s:RacerGetPrefixCol(base)
    let col = col('.') - 1
    let b:racer_col = col
    let b:tmpfname = tempname()
    call writefile(s:RacerGetBufferContents(a:base), b:tmpfname)
    let cmd = g:racer_cmd . ' prefix ' . line('.') . ' ' . col . ' ' . b:tmpfname
    let res = system(cmd)
    let prefixline = split(res, '\n')[0]
    let startbyte = split(prefixline[7:], ',')[0]
    return startbyte - line2byte(byte2line(startbyte)) + 1
endfunction

function! s:RacerGetExpCompletions(base)
    let col = col('.')-1
    let b:tmpfname = tempname()
    call writefile(s:RacerGetBufferContents(a:base), b:tmpfname)
    let fname = expand('%:p')
    let cmd = g:racer_cmd . ' complete ' . line('.') . ' ' . col . ' "' . fname . '" "' . b:tmpfname . '"'
    let res = system(cmd)

    let typeMap = {
        \ 'Struct' : 's', 'Module' : 'M', 'Function' : 'f',
        \ 'Crate' : 'C', 'Let' : 'v', 'StructField' : 'm',
        \ 'Impl' : 'i', 'Enum' : 'e', 'EnumVariant' : 'E',
        \ 'Type' : 't', 'FnArg' : 'v', 'Trait' : 'T'
        \ }

    let lines = split(res, '\n')
    let out = []

    for line in lines
        if line !~# '^MATCH'
            continue
        endif

        let completions = split(line[6:], ',')
        let kind = get(typeMap, completions[4])
        let completion = { 'kind' : kind, 'word' : completions[0], 'dup' : 1 }
        let info = join(completions[5:], ',')

        if kind ==# 'f'
            " function
            let completion['menu'] = substitute(
                \   substitute(
                \     substitute(info, '\(pub\|fn\) ', '', 'g'),
                \     '{*$', '', ''
                \   ),
                \   ' where\s\?.*$', '', ''
                \ )
            if g:racer_insert_paren == 1
                let completion['abbr'] = completions[0]
                let completion['word'] .= '('
            endif
            let completion['info'] = info
        elseif kind ==# 's' " struct
            let completion['menu'] = substitute(
                \   substitute(info, '\(pub\|struct\) ', '', 'g'),
                \   '{*$', '', ''
                \ )
        endif

        if stridx(tolower(completions[0]), tolower(a:base)) == 0
            let out = add(out, completion)
        endif
    endfor
    call delete(b:tmpfname)
    return out
endfunction

function! s:RacerSplitLine(line)
    let separator = ';'
    let placeholder = '{PLACEHOLDER}'
    let line = substitute(a:line, '\\;', placeholder, 'g')
    let parts = split(line, separator)
    let docs = substitute(
        \   substitute(
        \     substitute(
        \       substitute(get(parts, 7, ''), '^\"\(.*\)\"$', '\1', ''),
        \       '\\\"', '\"', 'g'
        \     ),
        \     '\\''', '''', 'g'
        \   ),
        \   '\\n', '\n', 'g'
        \ )
    let parts = add(parts[:6], docs)
    let parts = map(copy(parts), 'substitute(v:val, ''{PLACEHOLDER}'', '';'', ''g'')')

    return parts
endfunction

function! racer#ShowDocumentation()
    let winview = winsaveview()  " Save the current cursor position
    " Move to the end of the word for the entire token to search.
    " Move one char back to avoid moving to the end of the *next* word.
    execute 'normal he'
    let col = col('.')
    let b:tmpfname = tempname()
    " Create temporary file with the buffer's current state
    call writefile(getline(1, '$'), b:tmpfname)
    let fname = expand('%:p')
    let cmd = g:racer_cmd . ' complete-with-snippet ' . line('.') . ' ' . col . ' ' . fname . ' ' . b:tmpfname
    let res = system(cmd)
    " Restore de cursor position
    call winrestview(winview)
    " Delete the temporary file
    call delete(b:tmpfname)
    let lines = split(res, '\n')
    for line in lines
        if line !~# '^MATCH'
            continue
        endif

        let docs = s:RacerSplitLine(line[6:])[7]
        if len(docs) == 0
            break
        endif

        " Only open doc buffer if there're docs to show
        let bn = bufnr('__doc__')
        if bn > 0
            let wi = index(tabpagebuflist(tabpagenr()), bn)
            if wi >= 0
                " If the __doc__ buffer is open in the current tab, jump to it
                silent execute (wi+1) . 'wincmd w'
            else
                silent execute 'sbuffer ' . bn
            endif
        else
            split '__doc__'
        endif

        setlocal nobuflisted
        setlocal modifiable
        setlocal noswapfile
        setlocal buftype=nofile
        silent normal! ggdG
        silent $put=docs
        silent normal! 1Gdd
        setlocal nomodifiable
        setlocal nomodified
        setlocal filetype=rustdoc
        break
    endfor
endfunction

function! s:RacerGetCompletions(base)
    let col = col('.') - 1
    let b:tmpfname = tempname()
    " HACK: Special case to offer autocompletion on a string literal
    if getline('.')[:col-1] =~# '".*"\.$'
        call writefile(['fn main() {', '    let x: &str = "";', '    x.', '}'], b:tmpfname)
        let fname = expand('%:p')
        let cmd = g:racer_cmd . ' complete 3 6 "' . fname . '" "' . b:tmpfname . '"'
    else
        call writefile(s:RacerGetBufferContents(a:base), b:tmpfname)
        let fname = expand('%:p')
        let cmd = g:racer_cmd . ' complete ' . line('.') . ' ' . col . ' "' . fname . '" "' . b:tmpfname . '"'
    endif
    let res = system(cmd)
    let lines = split(res, '\n')
    let out = []
    for line in lines
        if line !~# '^MATCH'
            continue
        endif
        let completion = split(line[6:], ',')[0]
        if stridx(tolower(completion), tolower(a:base)) == 0
            let out = add(out, completion)
        endif
    endfor
    call delete(b:tmpfname)

    return out
endfunction

function! racer#GoToDefinition()
    if s:ErrorCheck()
        return
    endif

    let col = col('.') - 1
    let b:racer_col = col
    let fname = expand('%:p')
    let tmpfname = tempname()
    call writefile(getline(1, '$'), tmpfname)
    let cmd = g:racer_cmd . ' find-definition ' . line('.') . ' ' . col . ' ' . fname . ' ' . tmpfname
    let res = system(cmd)
    let lines = split(res, '\n')
    for line in lines
        if res =~# ' error: ' && line !=# 'END'
            call s:Warn(line)
        elseif line =~# '^MATCH'
            let linenum = split(line[6:], ',')[1]
            let colnum = split(line[6:], ',')[2]
            let fname = split(line[6:], ',')[3]
            call s:RacerJumpToLocation(fname, linenum, colnum)
            break
        endif
    endfor
    call delete(tmpfname)
endfunction

function! s:RacerGetBufferContents(base)
    " Re-combine the completion base word from omnicomplete with the current
    " line contents. Since the base word gets remove from the buffer before
    " this function is invoked we have to put it back in to out tmpfile.
    let col = col('.') - 1
    let buf_lines = getline(1, '$')
    let line_contents = getline('.')
    let buf_lines[line('.') - 1] =
        \ strpart(line_contents, 0, col) .
        \ a:base .
        \ strpart(line_contents, col, len(line_contents))
    return buf_lines
endfunction

function! s:RacerJumpToLocation(filename, linenum, colnum)
    if a:filename == ''
        return
    endif

    " Record jump mark
    normal! m`
    if a:filename != bufname('%')
        try
            exec 'keepjumps e ' . fnameescape(a:filename)
        catch /^Vim\%((\a\+)\)\=:E37/
            " When the buffer is not saved, E37 is thrown.  We can ignore it.
        endtry
    endif
    call cursor(a:linenum, a:colnum + 1)
    " Center definition on screen
    normal! zz
endfunction

function! racer#RacerComplete(findstart, base)
    if a:findstart
        if s:ErrorCheck()
            return -1
        endif

        return s:RacerGetPrefixCol(a:base)
    else
        if s:ErrorCheck()
            return []
        endif

        if g:racer_experimental_completer == 1
            return s:RacerGetExpCompletions(a:base)
        else
            return s:RacerGetCompletions(a:base)
        endif
    endif
endfunction

function! s:Warn(msg)
    echohl WarningMsg | echomsg a:msg | echohl NONE
endfunction

function! s:ErrorCheck()
    if !executable(g:racer_cmd)
        call s:Warn('No racer executable found in $PATH (' . $PATH . ')')
        return 1
    endif
endfunction
