" Copyright (C) 2010-2015 Hong Xu

" This file is part of SingleCompile.

" SingleCompile 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.

" SingleCompile 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 SingleCompile.  If not, see <http://www.gnu.org/licenses/>.

" File: autoload/SingleCompile.vim
" Version: 2.11.0
" check doc/SingleCompile.txt for more information


let s:saved_cpo = &cpo
set cpo&vim


" varibles {{{1
" the dict to store the compiler template
let s:CompilerTemplate = {}

" is template initialize
let s:Initialized = 0

" Chars to escape for ':lcd' command
if has('win32')
    let s:CharsEscape = '" '
else
    let s:CharsEscape = '" \'
endif

" executable suffix
if has('win32')
    let s:ExecutableSuffix = '.exe'
else
    let s:ExecutableSuffix = ''
endif

function! SingleCompile#GetExecutableSuffix()
    return s:ExecutableSuffix
endfunction

" seperator in the environment varibles
if has('win32')
    let s:EnvSeperator = ';'
else
    let s:EnvSeperator = ':'
endif

if has('win32')
    let s:PathSeperator = '\'
else
    let s:PathSeperator = '/'
endif

" the path of file where the output running result is stored
let s:run_result_tempfile = ''



function! SingleCompile#GetVersion() " get the script version {{{1
    " Before 2.9.3, the return value is: major * 100 + minor * 10 + subminor
    " For example, 2.9.2 is corresponding to 292
    " From 2.10.0, the return value is: major * 1000 + minor * 10 + subminor
    " For example, 2.10.1 is corresponding to 2101
    return 2110
endfunction

" util {{{1
function! SingleCompile#GetDefaultOpenCommand() " {{{2
" Get the default open command. It is "open" on Windows and Mac OS X,
" "xdg-open" on Linux and other UNIX systems.
    if has('win32')
        return 'start'
    elseif has('macunix')
        return 'open'
    elseif has('unix')
        return 'xdg-open'
    else
        return 'open' " We guess 'open' for any other systems
    endif
endfunction

function! s:GetCurrentShell() " {{{2
" Get the name of the current shell according to &shell for UNIX. For example,
" if &shell is '/bin/sh', the return value would be 'sh'.
    if has('unix')
        return strpart(&shell, strridx(&shell, '/') + 1)
    endif
endfunction

function! s:IsShellSh(shell_name) " is the shell Bourne shell? {{2
    if a:shell_name =~ '^sh' ||
                \a:shell_name =~ '^bash' ||
                \a:shell_name =~ '^ksh' ||
                \a:shell_name =~ '^mksh' ||
                \a:shell_name =~ '^pdksh' ||
                \a:shell_name =~ '^zsh'
        return 1
    else
        return 0
    endif
endfunction

function! s:IsShellCsh(shell_name) " is the shell C Shell? {{2
    if a:shell_name =~ '^csh' || a:shell_name =~ '^tcsh'
        return 1
    else
        return 0
    endif
endfunction

function! s:IsShellFish(shell_name) " is the shell FISH shell {{2
    return a:shell_name =~ '^fish'
endfunction

function! s:GetShellLastExitCodeVariable() " {{{2
    " Get the variable that presents the exit code of last command.

    if has('unix')

        let l:cur_shell = s:GetCurrentShell()

        if s:IsShellCsh(l:cur_shell) || s:IsShellFish(l:cur_shell)
            return '$status'
        elseif s:IsShellSh(l:cur_shell)
            return '$?'
        else
            return ''
        endif

    elseif has('win32')
        return '$?'
    endif

endfunction

function! s:GetShellPipe(tee_used) " {{{2
    " get the shell pipe command according to it's platform. If a:tee_used is
    " set to nonzero, then the shell pipe contains "tee", otherwise "tee"
    " wouldn't be contained in the return value.

    if has('unix')

        let l:cur_shell = s:GetCurrentShell()

        if s:IsShellCsh(l:cur_shell)
            if a:tee_used
                return '|& tee'
            else
                return '>&'
            endif
        elseif s:IsShellSh(l:cur_shell) || s:IsShellFish(l:cur_shell)
            if a:tee_used
                return '2>&1| tee'
            else
                return '>%s 2>&1'
            endif
        else
            if a:tee_used
                return '| tee'
            else
                return '>'
            endif
        endif
    elseif has('win32')
        if executable('tee') && a:tee_used && g:SingleCompile_usetee
            return '2>&1 | tee'
        else
            return '>%s 2>&1'
        endif
    endif

endfunction
function! s:Expand(str, ...) " expand the string{{{2
    " the second argument is optional. If it is given and it is zero, then
    " we thought we don't need any quote; otherwise, we use double quotes on
    " Windows and single quotes on all other systems.

    let l:quote_needed = 1
    if a:0 > 1
        call s:ShowMessage('s:Expand argument error.')
        return ''
    elseif a:0 == 1
        if !a:1
            let l:quote_needed = 0
        endif
    endif

    let l:rep_dict = {
                \'\$(FILE_NAME)\$': '%',
                \'\$(FILE_PATH)\$': '%:p',
                \'\$(FILE_TITLE)\$': '%:r',
                \'\$(FILE_EXEC)\$': '%:p:r',
                \'\$(FILE_RUN)\$': '%:p:r'}
    let l:rep_dict_prefix = {
                \'\$(FILE_NAME)\$': '',
                \'\$(FILE_PATH)\$': '',
                \'\$(FILE_TITLE)\$': '',
                \'\$(FILE_EXEC)\$': '',
                \'\$(FILE_RUN)\$': ''}
    let l:rep_dict_suffix = {
                \'\$(FILE_NAME)\$': '',
                \'\$(FILE_PATH)\$': '',
                \'\$(FILE_TITLE)\$': '',
                \'\$(FILE_EXEC)\$': '',
                \'\$(FILE_RUN)\$': ''}

    if has('win32')
        let l:rep_dict_prefix['\$(FILE_RUN)\$'] = ''
        let l:rep_dict_suffix['\$(FILE_EXEC)\$'] = '.exe'
        let l:rep_dict_suffix['\$(FILE_RUN)\$'] = '.exe'
    elseif has('unix')
        let l:rep_dict_prefix['\$(FILE_RUN)\$'] = './'
        let l:rep_dict_suffix['\$(FILE_EXEC)\$'] = ''
        let l:rep_dict_suffix['\$(FILE_RUN)\$'] = ''
    endif


    let l:str = a:str
    for one_key in keys(l:rep_dict)
        let l:rep_string = l:rep_dict_prefix[one_key] .
                    \ expand(l:rep_dict[one_key]) .
                    \ l:rep_dict_suffix[one_key]

        " on win32, replace the backslash with '/'
        if has('win32')
            let l:rep_string = substitute(l:rep_string, '/', '\\', 'g')
        endif

        let l:rep_string = escape(l:rep_string, '\')
        if l:quote_needed && match(l:rep_string, ' ') != -1

            if has('win32')
                let l:rep_string = '"'.l:rep_string.'"'
            else
                let l:rep_string = "'".l:rep_string."'"
            endif

        endif
        let l:str = substitute(l:str, one_key, l:rep_string, 'g')
    endfor

    return l:str
endfunction

function! s:RunAsyncWithMessage(run_cmd) " {{{2
    " run a command and display messages that we need to

    let l:async_run_res = SingleCompileAsync#Run(a:run_cmd)

    if l:async_run_res != 0
        call s:ShowMessage(
                    \"Fail to run the command '".
                    \a:run_cmd."'. Error code: ".l:async_run_res)

        if l:async_run_res == 1
            call s:ShowMessage(
                        \'There is already an existing process '.
                        \'running in background.')
        endif

        return 1
    endif

    return 0
endfunction

function! s:PushEnvironmentVaribles() " {{{2
    " push environment varibles into stack. Win32 only.

    if !exists('s:environment_varibles_list') ||
                \ type(s:environment_varibles_list) != type([])
        unlet! s:environment_varibles_list
        let s:environment_varibles_list = []
    endif

    let l:environment_varibles_dic = {}

    let l:environment_varibles_string = system('set')

    for line in split(l:environment_varibles_string, '\n')

        " find the '=' first. The left part is environment varible's name, the
        " right part is the value
        let l:eq_pos = match(line, '=')
        if l:eq_pos == -1
            continue
        endif

        let l:key = strpart(line, 0, l:eq_pos)
        let l:val = strpart(line, l:eq_pos + 1)

        let l:environment_varibles_dic[l:key] = l:val
    endfor

    call add(s:environment_varibles_list, l:environment_varibles_dic)
endfunction

function! s:PopEnvironmentVaribles() " {{{2
    " pop environment varibles out of the stack. Win32 only.

    if !exists('s:environment_varibles_list') ||
                \ type(s:environment_varibles_list) != type([]) ||
                \ empty(s:environment_varibles_list)
        return 0
    endif

    let l:environment_varibles_dic = remove(
                \ s:environment_varibles_list,
                \ len(s:environment_varibles_list) - 1)

    for l:key in keys(l:environment_varibles_dic)
        silent! exec 'let $' . l:key . "='" .
                    \ substitute(l:environment_varibles_dic[l:key],
                    \ "'", "''", 'g') . "'"
    endfor
endfunction

" pre-do functions {{{1

function! s:AddLmIfMathH(compiling_info) " {{{2
    " add -lm flag if math.h is included

    " if we find '#include <math.h>' in the file, then add '-lm' flag
    if match(getline(1, '$'), '^[ \t]*#[ \t]*include[ \t]*["<]math.h[">][ \t]*$')
                \!= -1
        let l:new_comp_info = a:compiling_info
        let l:new_comp_info['args'] = l:new_comp_info['args'] . ' -lm'
        return l:new_comp_info
    endif

    return a:compiling_info
endfunction

function! SingleCompile#PredoWatcom(compiling_info) " watcom pre-do {{{2
    let s:old_path = $PATH
    let $PATH = $WATCOM.s:PathSeperator.'binnt'.s:EnvSeperator.
                \$WATCOM.s:PathSeperator.'binw'.s:EnvSeperator.
                \$PATH
    return a:compiling_info
endfunction

function! SingleCompile#PredoGcc(compiling_info) " gcc pre-do {{{2
    if has('unix')
        return s:AddLmIfMathH(a:compiling_info)
    else
        return a:compiling_info
    endif
endfunction

function! SingleCompile#PredoSolStudioC(compiling_info) " solaris studio C/C++ pre-do {{{2
    return s:AddLmIfMathH(a:compiling_info)
endfunction

function! SingleCompile#PredoClang(compiling_info) " clang Predo {{{2
    if has('unix')
        return s:AddLmIfMathH(a:compiling_info)
    else
        return a:compiling_info
    endif
endfunction

function! SingleCompile#PredoMicrosoftVC(compiling_info) " MSVC Predo {{{2

    call s:PushEnvironmentVaribles()

    " to get the result environment varibles, we need to write a temp batch
    " file and call it.
    let l:tmpbat = tempname() . '.bat'
    call writefile(['@echo off', 'call "%VS'.
                \str2nr(strpart(a:compiling_info['command'], 2)).
                \'COMNTOOLS%..\..\VC\vcvarsall.bat"', 'set'], l:tmpbat)
    let l:environment_varibles_string = system(l:tmpbat)

    " Set the environment varibles
    for l:line in split(l:environment_varibles_string, '\n')
        let l:eq_pos = match(l:line, '=')

        if l:eq_pos == -1
            continue
        endif

        silent! exec 'let $' . strpart(l:line, 0, l:eq_pos) . "='" .
                    \ substitute(strpart(l:line, l:eq_pos + 1),
                    \ "'", "''", 'g') . "'"
    endfor

    let l:new_compiling_info = a:compiling_info
    let l:new_compiling_info['command'] = 'cl'

    return l:new_compiling_info
endfunction

function! SingleCompile#PredoShell(compiling_info) " shell predo {{{2
    let l:new_compiling_info = a:compiling_info

    let l:shebang = getline(1)
    if l:shebang !~ '#!/.*' " don't change anything if no shebang is found
        return a:compiling_info
    endif

    " Use the shell indicated by the shebang
    let l:new_compiling_info['command'] = matchstr(l:shebang, '#!\zs/.*')

    return l:new_compiling_info
endfunction

" post-do functions {{{1
function! SingleCompile#PostdoWatcom(compiling_info) " watcom pre-do {{{2
    let $PATH = s:old_path
    return a:compiling_info
endfunction

function! SingleCompile#PostdoMicrosoftVC(not_used_arg) " MSVC post-do {{{2
    call s:PopEnvironmentVaribles()
endfunction

" compiler detect functions {{{1
function! s:DetectCompilerGenerally(compiling_command) " {{{2
    " the general function of compiler detection. The principle is to search
    " the environment varible PATH and some special directory

    if has('unix')
        let l:list_to_detect = [s:Expand(expand(a:compiling_command)),
                    \s:Expand(expand('~/bin/'.a:compiling_command)),
                    \s:Expand(expand('/usr/local/bin/'.a:compiling_command)),
                    \s:Expand(expand('/usr/bin/'.a:compiling_command)),
                    \s:Expand(expand('/bin/'.a:compiling_command))
                    \]
    else
        let l:list_to_detect = [s:Expand(expand(a:compiling_command))]
    endif

    for cmd in l:list_to_detect
        if executable(cmd) == 1
            return cmd
        endif
    endfor

    return 0
endfunction

function! SingleCompile#DetectCompilerGenerally(compiling_command)
    return s:DetectCompilerGenerally(a:compiling_command)
endfunction

function! SingleCompile#DetectWatcom(compiling_command) " {{{2
    let l:watcom_command =
                \s:DetectCompilerGenerally(a:compiling_command)
    if l:watcom_command != 0
        return l:watcom_command
    endif

    if $WATCOM != ''
        return $WATCOM.'\binnt\'.a:compiling_command
    endif
endfunction

function! SingleCompile#DetectMicrosoftVC(compiling_command) " {{{2
    " the compiling_command should be cl_vcversion, such as cl80, cl100, etc.

    " return 0 if not starting with 'cl'
    if strpart(a:compiling_command, 0, 2) != 'cl'
        return 0
    endif

    " get the version of vc
    let l:vc_version = strpart(a:compiling_command, 2)

    " if we have VSXXCOMNTOOLS environment variable, and
    " %VSXXCOMNTOOLS%\..\..\VC\BIN\cl.exe is executable, and
    " %VSXXCOMNTOOLS%\..\..\VC\vcvarsall.bat is executable, MSVC is detected
    exec 'let l:vs_common_tools = $VS'.l:vc_version.'COMNTOOLS'

    if !empty(l:vs_common_tools) &&
                \ executable(l:vs_common_tools.'..\..\VC\BIN\cl.exe') &&
                \ executable(l:vs_common_tools.'..\..\VC\vcvarsall.bat')
        return a:compiling_command
    else
        return 0
    endif
endfunction

function! SingleCompile#DetectIe(not_used_arg) " {{{2
    if executable('iexplore')
        return 'iexplore'
    endif

    if has('win32')
        let iepath = $PROGRAMFILES . '\Internet Explorer\iexplore.exe'
        if executable(iepath)
            return "\"".iepath."\""
        endif
    endif
endfunction

function! s:Initialize() "{{{1

    if s:Initialized == 0
        let s:Initialized = 1
    else
        return
    endif

    if !exists('g:SingleCompile_alwayscompile') ||
                \type(g:SingleCompile_alwayscompile) != type(0)
        unlet! g:SingleCompile_alwayscompile
        let g:SingleCompile_alwayscompile = 1
    endif

    if !exists('g:SingleCompile_asyncrunmode') ||
                \type(g:SingleCompile_asyncrunmode) != type('')
        unlet! g:SingleCompile_asyncrunmode
        let g:SingleCompile_asyncrunmode = 'auto'
    endif

    if !exists('g:SingleCompile_autowrite') ||
                \type(g:SingleCompile_autowrite) != type(0)
        unlet! g:SingleCompile_autowrite
        let g:SingleCompile_autowrite = 1
    endif

    if !exists('g:SingleCompile_quickfixwindowposition') ||
                \type(g:SingleCompile_quickfixwindowposition) != type('')
        unlet! g:SingleCompile_quickfixwindowposition
        let g:SingleCompile_quickfixwindowposition = 'botright'
    end

    if !exists('g:SingleCompile_resultsize') ||
                \type(g:SingleCompile_resultsize) != type(0) ||
                \g:SingleCompile_resultsize <= 0
        unlet! g:SingleCompile_resultsize
        let g:SingleCompile_resultsize = 5
    endif

    if !exists('g:SingleCompile_split')
        let g:SingleCompile_split = 'split'
    endif

    if !exists('g:SingleCompile_showquickfixiferror') ||
                \type(g:SingleCompile_showquickfixiferror) != type(0)
        unlet! g:SingleCompile_showquickfixiferror
        let g:SingleCompile_showquickfixiferror = 0
    endif

    if !exists('g:SingleCompile_showquickfixifwarning') ||
                \type(g:SingleCompile_showquickfixifwarning) != type(0)
        unlet! g:SingleCompile_showquickfixifwarning
        let g:SingleCompile_showquickfixifwarning =
                    \g:SingleCompile_showquickfixiferror
    endif

    if !exists('g:SingleCompile_showresultafterrun') ||
                \type(g:SingleCompile_showresultafterrun) != type(0)
        unlet! g:SingleCompile_showresultafterrun
        let g:SingleCompile_showresultafterrun = 0
    endif

    if !exists('g:SingleCompile_usedialog') ||
                \type(g:SingleCompile_usedialog) != type(0)
        unlet! g:SingleCompile_usedialog
        let g:SingleCompile_usedialog = 0
    endif

    if !exists('g:SingleCompile_usequickfix') ||
                \type(g:SingleCompile_usequickfix) != type(0)
        unlet! g:SingleCompile_usequickfix
        let g:SingleCompile_usequickfix = 1
    endif

    if !exists('g:SingleCompile_usetee') ||
                \type(g:SingleCompile_usetee) != type(0)
        unlet! g:SingleCompile_usetee
        let g:SingleCompile_usetee = 1
    endif

    if !exists('g:SingleCompile_silentcompileifshowquickfix') ||
                \type(g:SingleCompile_silentcompileifshowquickfix) != type(0)
        unlet! g:SingleCompile_silentcompileifshowquickfix
        let g:SingleCompile_silentcompileifshowquickfix = 0
    endif

    " Initialize async mode
    if g:SingleCompile_asyncrunmode !=? 'none'
        let l:async_init_res = SingleCompileAsync#Initialize(
                    \g:SingleCompile_asyncrunmode)

        if type(l:async_init_res) == type('')
            call s:ShowMessage(
                        \"Failed to initialize the async mode '".
                        \g:SingleCompile_asyncrunmode."'.\n".l:async_init_res)
        elseif l:async_init_res == 3
            call s:ShowMessage("The specified async mode '".
                        \g:SingleCompile_asyncrunmode."' doesn't exist.")
        elseif l:async_init_res != 0
            call s:ShowMessage(
                        \"Failed to initialize the async mode'".
                        \g:SingleCompile_asyncrunmode."'.")
        endif
    endif

    " terminate the background process before existing vim
    if has('autocmd')
        autocmd VimLeave * call SingleCompileAsync#Terminate()
    endif

    " initialize builtin templates
    if has('win32')
        let g:SingleCompile_common_run_command = '$(FILE_TITLE)$.exe'
        let g:SingleCompile_common_out_file = '$(FILE_TITLE)$.exe'
    else
        let g:SingleCompile_common_run_command = './$(FILE_TITLE)$'
        let g:SingleCompile_common_out_file = '$(FILE_TITLE)$'
    endif

    for builtin_filetype in [
                \ 'ada',
                \ 'bash',
                \ 'c',
                \ 'cmake',
                \ 'cpp',
                \ 'coffee',
                \ 'cs',
                \ 'csh',
                \ 'd',
                \ 'dosbatch',
                \ 'elixir',
                \ 'erlang',
                \ 'fortran',
                \ 'go',
                \ 'haskell',
                \ 'html',
                \ 'idlang',
                \ 'java',
                \ 'javascript',
                \ 'ksh',
                \ 'lisp',
                \ 'ls',
                \ 'lua',
                \ 'make',
                \ 'markdown',
                \ 'matlab',
                \ 'ocaml',
                \ 'objc',
                \ 'pascal',
                \ 'perl',
                \ 'php',
                \ 'python',
                \ 'qml',
                \ 'r',
                \ 'rst',
                \ 'ruby',
                \ 'rust',
                \ 'scala',
                \ 'sh',
                \ 'tcl',
                \ 'tcsh',
                \ 'tex',
                \ 'vb',
                \ 'vim',
                \ 'xhtml',
                \ 'zsh']
        exec 'call SingleCompile#templates#' .
                    \ builtin_filetype . '#Initialize()'
    endfor
endfunction

function! s:SetVimCompiler(lang_name, compiler) " {{{1
    " call the :compiler command

    let l:dict_compiler = s:CompilerTemplate[a:lang_name][a:compiler]
    if has_key(l:dict_compiler, 'vim-compiler')
        silent! exec 'compiler '.l:dict_compiler['vim-compiler']
    else
        silent! exec 'compiler '.a:compiler
    endif
endfunction

function! s:SetGlobalVimCompiler(lang_name, compiler) " {{{1
    "call the :compiler! command

    let l:dict_compiler = s:CompilerTemplate[a:lang_name][a:compiler]
    if has_key(l:dict_compiler, 'vim-compiler')
        silent! exec 'compiler! '.l:dict_compiler['vim-compiler']
    else
        silent! exec 'compiler! '.a:compiler
    endif
endfunction

" SingleCompile#SetCompilerTemplate {{{1
function! SingleCompile#SetCompilerTemplate(lang_name, compiler,
            \compiler_name, detect_func_arg, flags, run_command, ...)
    " set compiler's template, including compiler, compiler's name, the
    " detecting function argument, compilation flags, run command and
    " a compiler detecting function which is optional.

    call s:Initialize()

    call s:SetCompilerSingleTemplate(a:lang_name, a:compiler, 'name',
                \a:compiler_name)
    call s:SetCompilerSingleTemplate(a:lang_name, a:compiler,
                \'detect_func_arg', a:detect_func_arg)
    call s:SetCompilerSingleTemplate(a:lang_name, a:compiler, 'flags',
                \a:flags)
    call s:SetCompilerSingleTemplate(a:lang_name, a:compiler, 'run',
                \a:run_command)
    if a:0 == 0
        call SingleCompile#SetDetectFunc(a:lang_name, a:compiler,
                    \function("s:DetectCompilerGenerally"))
    else
        call SingleCompile#SetDetectFunc(a:lang_name, a:compiler, a:1)
    endif
endfunction

" function! SingleCompile#SetCompilerTemplateByDict {{{1
function! SingleCompile#SetCompilerTemplateByDict(
            \lang_name, compiler, template_dict)
    " set templates by using a dict(template_dict), thus calling the template
    " settings functions below one by one is not needed.

    let l:key_list = ['name', 'detect_func_arg', 'flags', 'run',
                \'detect_func', 'pre-do', 'priority', 'post-do', 'out-file',
                \'vim-compiler']

    for key in l:key_list
        if has_key(a:template_dict, key)
            call s:SetCompilerSingleTemplate(a:lang_name, a:compiler, key,
                        \get(a:template_dict, key))
        endif
    endfor
endfunction

" extra template settings functions {{{1
function! SingleCompile#SetDetectFunc(lang_name, compiler, detect_func) " {{{2
    " set the detect_func function

    call s:SetCompilerSingleTemplate(a:lang_name, a:compiler, 'detect_func',
                \a:detect_func)
endfunction

function! SingleCompile#SetPredo(lang_name, compiler, predo_func) " {{{2
    " set the pre-do function

    call s:SetCompilerSingleTemplate(a:lang_name, a:compiler, 'pre-do',
                \a:predo_func)
endfunction

function! SingleCompile#SetPostdo(lang_name, compiler, postdo_func) " {{{2
    " set the post-do function

    call s:SetCompilerSingleTemplate(a:lang_name, a:compiler,
                \'post-do', a:postdo_func)
endfunction

function! SingleCompile#SetOutfile(lang_name, compiler, outfile) " {{{2
    " set out-file

    call s:SetCompilerSingleTemplate(a:lang_name, a:compiler,
                \'out-file', a:outfile)
endfunction

fun! SingleCompile#SetVimCompiler(lang_name, compiler, vim_compiler) " {{{2
    " set vim-compiler

    call s:SetCompilerSingleTemplate(a:lang_name, a:compiler,
                \'vim-compiler', a:vim_compiler)
endfunction

function! SingleCompile#SetPriority(lang_name, compiler, priority) " {{{2
    " set priority

    call s:SetCompilerSingleTemplate(a:lang_name, a:compiler,
                \ 'priority', a:priority)
endfunction

function! s:GetCompilerSingleTemplate(lang_name, compiler_name, key) " {{{1
    return s:CompilerTemplate[a:lang_name][a:compiler_name][a:key]
endfunction

" SetCompilerSingleTemplate {{{1
fun! s:SetCompilerSingleTemplate(lang_name, compiler, key, value, ...)
    " set the template. if the '...' is nonzero, this function will not
    " override the corresponding template if there is an existing template

    " Set the default template first so that we could let this override the
    " default compile template.
    call s:Initialize()

    if a:0 > 1
        call s:ShowMessage(
                    \'Too many argument for'.
                    \' SingleCompile#SetCompilerSingleTemplate function!')
        return
    endif

    " if the key a:lang_name does not exist, create it
    if !has_key(s:CompilerTemplate,a:lang_name)
        let s:CompilerTemplate[a:lang_name] = {}
    elseif type(s:CompilerTemplate[a:lang_name]) != type({})
        unlet! s:CompilerTemplate[a:lang_name]
        let s:CompilerTemplate[a:lang_name] = {}
    endif

    " if a:compiler does not exist, create it
    if !has_key(s:CompilerTemplate[a:lang_name],a:compiler)
        let s:CompilerTemplate[a:lang_name][a:compiler] = {}
    elseif type(s:CompilerTemplate[a:lang_name][a:compiler]) != type({})
        unlet! s:CompilerTemplate[a:lang_name][a:compiler]
        let s:CompilerTemplate[a:lang_name][a:compiler] = {}
    endif

    " if a:key does not exist, create it
    " if the ... from the argument is 0 or the additional argument does
    " not exist, also override the original one
    if !has_key(s:CompilerTemplate[a:lang_name][a:compiler], a:key) ||
                \a:0 == 0 || a:1 == 0
        let s:CompilerTemplate[a:lang_name][a:compiler][a:key] = a:value
    endif
endfunction

function! SingleCompile#SetTemplate(lang_name, stype, string,...) " {{{1
    " set the template. if the '...' is nonzero, this function will not
    " override the corresponding template if there is an existing template

    if a:0 > 1
        call s:ShowMessage('Too many argument for '.
                    \'SingleCompile#SetTemplate function')
        return
    endif

    if a:0 == 0 || a:1 == 0
        let l:override = 0
    else
        let l:override = 1
    endif

    if a:stype == 'command'
        let l:stype = 'detect_func_arg'
    else
        let l:stype = a:stype
    endif

    " Set the name any way
    call s:SetCompilerSingleTemplate(a:lang_name,
                \'user_defined_compiler_using_old_style_function',
                \'name', 'user_defined_compiler_using_old_style_function', 1)

    let l:compiler_dict = s:CompilerTemplate[a:lang_name][
                \ 'user_defined_compiler_using_old_style_function']

    " Use general detect_func
    if !has_key(l:compiler_dict, 'detect_func')
        call SingleCompile#SetDetectFunc(a:lang_name,
                    \'user_defined_compiler_using_old_style_function',
                    \function("s:DetectCompilerGenerally"))
    endif

    " Set the template we want to set
    call s:SetCompilerSingleTemplate(a:lang_name,
                \'user_defined_compiler_using_old_style_function',
                \l:stype, a:string, l:override)

    if has_key(l:compiler_dict, 'name') &&
                \ has_key(l:compiler_dict, 'detect_func') &&
                \ has_key(l:compiler_dict, 'detect_func_arg') &&
                \ has_key(l:compiler_dict, 'flags') &&
                \ has_key(l:compiler_dict, 'run')
        call SingleCompile#ChooseCompiler(a:lang_name,
                    \ 'user_defined_compiler_using_old_style_function')
    endif
endfunction


function! s:ShowMessage(message) "{{{1

    if g:SingleCompile_usedialog == 0 || !((has('gui_running') &&
                \has('dialog_gui')) || has('dialog_con'))
        echohl Error | echo 'SingleCompile: '.a:message | echohl None
    else
        call confirm('SingleCompile: '.a:message)
    endif

endfunction

function! s:IsLanguageInterpreting(filetype_name) "{{{1
    "tell if a language is an interpreting language, reutrn 1 if yes, 0 if no

    let l:chosen_compiler =
                \s:CompilerTemplate[a:filetype_name]['chosen_compiler']
    return (!has_key(
                \s:CompilerTemplate[a:filetype_name][l:chosen_compiler],
                \'run')
                \ || substitute(
                \s:CompilerTemplate[a:filetype_name][l:chosen_compiler]
                \['run'],
                \' ', '', "g") == '')
endfunction

function! s:ShouldQuickfixBeUsed() " tell whether quickfix sould be used{{{1
    if g:SingleCompile_usequickfix == 0
                \ || !has('quickfix')
                \ || ( s:IsLanguageInterpreting(&filetype) && !has('unix') )
        return 0
    else
        return 1
    endif
endfunction

function! SingleCompile#Compile(...) " compile synchronously {{{1
    return s:CompileInternal(a:000, 0)
endfunction

function! SingleCompile#CompileAsync(...) " compile asynchronously {{{1
    return s:CompileInternal(a:000, 1)
endfunction

function! s:CompileInternal(arg_list, async) " compile only {{{1
    " Return 0 for compiling language successfully compiled;
    " Return 1 for compiling language failed to be compiled;
    " Return 2 for interpreting language successfully run;
    " Return 3 for interpreting language failed to be run.

    call s:Initialize()
    let l:toret = 0

    " whether we should run asynchronously if we are working with an
    " interpreting language
    let l:async = a:async && !empty(SingleCompileAsync#GetMode())
    if !l:async && executable('tee') && g:SingleCompile_usetee
                \ && g:SingleCompile_showresultafterrun == 1
        let l:show_result_after_run = 1
    else
        let l:show_result_after_run = 0
    endif

    " save current file type. Don't use &filetype directly because after
    " 'make' and quickfix is working and the error is in another file,
    " sometimes the value of &filetype may be incorrect.
    let l:cur_filetype = &filetype

    " Save the filetype to a script variable so it can be used by s:Run()
    let s:cur_filetype = l:cur_filetype

    " if current filetype is an empty string, show an error message and
    " return.
    if l:cur_filetype == ''
        call s:ShowMessage(
                    \"Current buffer's filetype is not specified. ".
                    \"Use \" :help 'filetype' \" command to see more details".
                    \" if you don't know what filetype is.")
        return -1
    endif

    " Check whether the language template is available
    if !(has_key(s:CompilerTemplate, l:cur_filetype) &&
                \type(s:CompilerTemplate[l:cur_filetype]) == type({}))
        call s:ShowMessage('Language template for "'.
                    \l:cur_filetype.'" is not defined on your system.')
        return -1
    endif

    " if current buffer has no name (for example the buffer has never been
    " saved), don't compile
    if bufname('%') == ''
        call s:ShowMessage(
                    \'Current buffer does not have a file name. '.
                    \'Please save current buffer first. '.
                    \'Compilation canceled.')
        return -1
    endif


    " if autowrite is set and the buffer has been modified, then save
    if g:SingleCompile_autowrite != 0 && &modified != 0
        write
    endif

    " detect compilers
    if !has_key(s:CompilerTemplate[l:cur_filetype], 'chosen_compiler')
        let l:detected_compilers = s:DetectCompiler(l:cur_filetype)
        " if l:detected_compilers is empty, then no compiler is detected
        if empty(l:detected_compilers)
            call s:ShowMessage(
                        \'No compiler is detected on your system!')
            return -1
        endif

        let s:CompilerTemplate[l:cur_filetype]['chosen_compiler'] =
                    \get(l:detected_compilers, 0)
    endif
    let l:chosen_compiler =
                \s:CompilerTemplate[l:cur_filetype]['chosen_compiler']
    let l:compile_cmd = s:GetCompilerSingleTemplate(
                \l:cur_filetype, l:chosen_compiler, 'command')

    " save current working directory
    let l:cwd = getcwd()
    " switch current work directory to the file's directory
    silent lcd %:p:h

    " If current language is not interpreting language, check the last
    " modification time of the file, whose name is the value of the 'out-file'
    " key. If the last modification time of that file is earlier than the last
    " modification time of current buffer's file, don't compile.
    if !g:SingleCompile_alwayscompile
                \&& !s:IsLanguageInterpreting(l:cur_filetype)
                \&& has_key(
                \s:CompilerTemplate[l:cur_filetype][l:chosen_compiler],
                \'out-file')
                \&& getftime(s:Expand(
                \s:CompilerTemplate[l:cur_filetype][l:chosen_compiler]
                \['out-file'], 0))
                \> getftime(expand('%:p'))
        " switch back to the original directory
        exec 'lcd '.escape(l:cwd, s:CharsEscape)
        echo 'SingleCompile: '.
                    \'No need to compile. '.
                    \'No modification is detected.'
        return 0
    endif

    if len(a:arg_list) == 1
        " if there is only one argument, it means use this argument as the
        " compilation flag

        let l:compile_flags = a:arg_list[0]
    elseif len(a:arg_list) == 2 && has_key(
                \s:CompilerTemplate[l:cur_filetype][
                \s:CompilerTemplate[l:cur_filetype]['chosen_compiler']],
                \'flags')
        " if there are two arguments, it means append the provided argument to
        " the flag defined in the template

        let l:compile_flags = s:GetCompilerSingleTemplate(l:cur_filetype,
                    \s:CompilerTemplate[l:cur_filetype]['chosen_compiler'],
                    \'flags').' '.a:arg_list[1]
    elseif len(a:arg_list) == 0 && has_key(
                \s:CompilerTemplate[l:cur_filetype][
                \s:CompilerTemplate[l:cur_filetype]['chosen_compiler']],
                \'flags')
        let l:compile_flags = s:GetCompilerSingleTemplate(l:cur_filetype,
                    \s:CompilerTemplate[l:cur_filetype]['chosen_compiler'],
                    \'flags')
    else
        " if len(a:arg_list) is zero and 'flags' is not defined, assign '' to
        " let l:compile_flags

        let l:compile_flags = ''
    endif


    if match(l:compile_flags, '\$(FILE_PATH)\$') == -1 &&
                \match(l:compile_flags, '\$(FILE_NAME)\$') == -1
        let l:compile_flags = l:compile_flags.' $(FILE_PATH)$'
    endif
    let l:compile_args = s:Expand(l:compile_flags)

    " call the pre-do function if set
    if has_key(s:CompilerTemplate[l:cur_filetype][l:chosen_compiler],
                \ 'pre-do')
        let l:command_dic =
                    \s:CompilerTemplate[l:cur_filetype][l:chosen_compiler][
                    \'pre-do'](
                    \{ 'command': l:compile_cmd,
                    \'args': l:compile_args})
        let l:compile_cmd = l:command_dic['command']
        let l:compile_args = l:command_dic['args']
    endif



    if s:ShouldQuickfixBeUsed() == 0
        " if quickfix is not enabled for this plugin or the language is an
        " interpreting language not in unix, then don't use quickfix

        if l:async && s:IsLanguageInterpreting(l:cur_filetype)

            call s:RunAsyncWithMessage(l:compile_cmd.' '.l:compile_args)
        else
            exec '!'.l:compile_cmd.' '.l:compile_args

            " check whether compiling is successful, if not, show the return value
            " with error message highlighting and set the return value to 1
            if v:shell_error != 0
                echo ' '
                call s:ShowMessage('Error! Return value is '.v:shell_error)
                if s:IsLanguageInterpreting(l:cur_filetype)
                    let l:toret = 3
                else
                    let l:toret = 1
                endif
            endif
        endif

    elseif has('unix') && s:IsLanguageInterpreting(l:cur_filetype)
        " use quickfix for interpreting language in unix

        if l:async
            " run the interpreter asynchronously

            call s:RunAsyncWithMessage(l:compile_cmd.' '.l:compile_args)
        else
            " save old values of makeprg and errorformat which :compiler!
            " command may change
            let l:old_makeprg = &g:makeprg
            let l:old_errorformat = &g:errorformat

            " call :compiler! command to set vim compiler. We use :compiler!
            " but not :compiler because cgetexpr command uses global option
            " value of errorformat
            call s:SetGlobalVimCompiler(l:cur_filetype, l:chosen_compiler)

            let l:exit_code_tempfile = tempname()
            let s:run_result_tempfile = tempname()
            " The output is put into s:run_result_tempfile, and exit code is
            " written into l:exit_code_tempfile. The command should be
            " something like this on bash:
            "   !(compiler_cmd compile_args; echo $? >tmp1) | tee tmp2
            exec (l:show_result_after_run ? 'silent ' : '').
                        \ '!('.l:compile_cmd.' '.l:compile_args.'; '.
                        \ 'echo '.s:GetShellLastExitCodeVariable().' >'.
                        \ l:exit_code_tempfile.') '.s:GetShellPipe(1).' '.
                        \ s:run_result_tempfile

            " if the exit code couldn't be obtained or the exit code is not 0,
            " l:toret is set to 3 (this return value is for interpreting
            " language only, means interpreting failed)
            let l:exit_code_str = readfile(l:exit_code_tempfile)
            if (len(l:exit_code_str) < 1) ||
                        \ (len(l:exit_code_str) >= 1 &&
                        \ str2nr(l:exit_code_str[0]))
                echo ' '
                call s:ShowMessage(
                            \ 'Interpreter exit code is '.l:exit_code_str[0])

                let l:toret = 3
            endif

            cgetexpr readfile(s:run_result_tempfile)

            " recover the old makeprg and errorformat value
            let &g:makeprg = l:old_makeprg
            let &g:errorformat = l:old_errorformat
        endif

    else " use quickfix for compiling language

        " change the makeprg and shellpipe temporarily
        let l:old_makeprg = &l:makeprg
        let l:old_shellpipe = &l:shellpipe
        let l:old_errorformat = &l:errorformat

        " call :compiler command to set vim compiler
        call s:SetVimCompiler(l:cur_filetype, l:chosen_compiler)

        let &l:makeprg = l:compile_cmd
        let &l:shellpipe = s:GetShellPipe(0)
        let l:prefix_args = ''
        let l:silentcompile = g:SingleCompile_silentcompileifshowquickfix &&
                    \ g:SingleCompile_showquickfixiferror &&
                    \ has("gui_running")

        if l:silentcompile
            let l:prefix_args = 'silent '
        endif
        exec l:prefix_args.'make'.' '.l:compile_args

        " check whether compiling is successful, if not, show the return value
        " with error message highlighting and set the return value to 1
        if v:shell_error != 0
            if !l:silentcompile
                echo ' '
                call s:ShowMessage(
                            \ 'Compiler exit code is '.v:shell_error)
            endif
            let l:toret = 1
        endif

        " set back makeprg and shellpipe
        let &l:makeprg = l:old_makeprg
        let &l:shellpipe = l:old_shellpipe
        let &l:errorformat = l:old_errorformat
    endif

    " if it's interpreting language, and l:toret has not been set, then return
    " 2 (means do not call run if user uses SCCompileRun command
    if l:toret == 0 && s:IsLanguageInterpreting(l:cur_filetype)
        let l:toret = 2
    endif

    " call the post-do function if set
    if has_key(s:CompilerTemplate[l:cur_filetype][l:chosen_compiler],
                \'post-do')
        let l:TmpFunc = s:CompilerTemplate[l:cur_filetype][l:chosen_compiler][
                    \'post-do']
        call l:TmpFunc({'command': l:compile_cmd, 'args': l:compile_args})
    endif


    " switch back to the original directory
    exec 'lcd '.escape(l:cwd, s:CharsEscape)

    " show the quickfix window if error occurs, quickfix is used and
    " g:SingleCompile_showquickfixiferror is set to nonzero
    if g:SingleCompile_showquickfixiferror && s:ShouldQuickfixBeUsed()
        " We have error
        if l:toret == 1 || l:toret == 3
            " workaround when the compiler file is broken
            exec g:SingleCompile_quickfixwindowposition . ' cope'
        " We may have warning
        elseif g:SingleCompile_showquickfixifwarning
            exec g:SingleCompile_quickfixwindowposition . ' cw'
        endif
    endif

    " if we are running an interpreting language source file, and we want to
    " show the result window right after the run, and we are not running it
    " asynchronously, then we show or hide the result
    if l:show_result_after_run
        if l:toret == 2
            call SingleCompile#ViewResult(0)
            redraw!
        elseif l:toret == 1 || l:toret == 3
            call SingleCompile#CloseViewResult()
        endif
    endif

    return l:toret
endfunction

function! s:CompareCompilerPriority(compiler1, compiler2) " {{{1
    " Compare the priorities of two compiler. The lang_name is determinted by
    " s:lang_name_compare_compiler_priority (if it is presented) or &filetype

    if exists('s:lang_name_compare_compiler_priority')
        let l:lang_name = s:lang_name_compare_compiler_priority
    else
        let l:lang_name = &filetype
    endif

    if has_key(s:CompilerTemplate[l:lang_name][a:compiler1], 'priority')
        let l:compiler1_priority =
                    \ s:CompilerTemplate[l:lang_name][a:compiler1]['priority']
    else
        let l:compiler1_priority = 100
    endif

    if has_key(s:CompilerTemplate[l:lang_name][a:compiler2], 'priority')
        let l:compiler2_priority =
                    \ s:CompilerTemplate[l:lang_name][a:compiler2]['priority']
    else
        let l:compiler2_priority = 100
    endif

    return l:compiler1_priority == l:compiler2_priority ? 0 : (
                \ l:compiler1_priority > l:compiler2_priority ? 1 : -1)
endfunction

function! s:DetectCompiler(lang_name) " {{{1
    " to detect compilers for one language. Return available compilers sorted
    " by priority

    let l:toret = []

    " call the compiler detection function to get the compilation command
    for some_compiler in keys(s:CompilerTemplate[a:lang_name])
        if some_compiler == 'chosen_compiler'
            continue
        endif

        " ignore this compiler if things are not all available (name,
        " detect_func, detect_func_arg, flags, run)
        let l:compiler_dict = s:CompilerTemplate[a:lang_name][some_compiler]
        if !(has_key(l:compiler_dict, 'name') &&
                    \ has_key(l:compiler_dict, 'detect_func') &&
                    \ has_key(l:compiler_dict, 'detect_func_arg') &&
                    \ has_key(l:compiler_dict, 'flags') &&
                    \ has_key(l:compiler_dict, 'run'))
            continue
        endif

        let l:DetectFunc = s:CompilerTemplate[a:lang_name][some_compiler][
                    \'detect_func']

        call s:SetCompilerSingleTemplate(
                    \a:lang_name,
                    \some_compiler,
                    \'command',
                    \l:DetectFunc(
                    \s:CompilerTemplate[a:lang_name][some_compiler][
                    \'detect_func_arg']))

        " if the type of s:CompilerTemplate[&filetype]['command'] returned
        " by the detection function is not a string, then we may think
        " that this compiler cannot be detected
        if type(s:CompilerTemplate[a:lang_name][some_compiler]['command']) ==
                    \type('')
            call add(l:toret, some_compiler)
        endif
    endfor

    " sort detected compilers by priority
    let s:lang_name_compare_compiler_priority = a:lang_name
    call sort(l:toret, 's:CompareCompilerPriority')

    return l:toret
endfunction

function! s:Run(async) " {{{1
    " if async is non-zero, then run asynchronously

    " Get the current filetype. We do not use &filetype because quickfix may
    " open another file automatically, which may cause the filetype changed to
    " an incorrect value.
    let l:cur_filetype = s:cur_filetype

    let l:ret_val = 0

    call s:Initialize()

    " whether we should use async mode
    let l:async = a:async && !empty(SingleCompileAsync#GetMode())
    if !l:async && executable('tee') && g:SingleCompile_usetee
                \ && g:SingleCompile_showresultafterrun == 1
        let l:show_result_after_run = 1
    else
        let l:show_result_after_run = 0
    endif

    if !(has_key(s:CompilerTemplate[l:cur_filetype][
                \s:CompilerTemplate[l:cur_filetype]['chosen_compiler']],
                \'run'))
        call s:ShowMessage('Fail to run!')
        return 1
    endif

    " save current working directory
    let l:cwd = getcwd()
    silent lcd %:p:h

    let l:run_cmd = s:Expand(s:GetCompilerSingleTemplate(l:cur_filetype,
                \ s:CompilerTemplate[l:cur_filetype]['chosen_compiler'],
                \ 'run'), 1)

    if l:async
        let l:ret_val = s:RunAsyncWithMessage(l:run_cmd)
    else
        if executable('tee') && g:SingleCompile_usetee
            " if tee is available, and we enabled the use of "tee", then
            " redirect the result to a temp file

            let s:run_result_tempfile = tempname()
            let l:run_cmd = l:run_cmd.
                        \' '.s:GetShellPipe(1).' '.s:run_result_tempfile
        endif

        try
            exec (l:show_result_after_run ? 'silent ' : '').'!'.l:run_cmd
        catch
            call s:ShowMessage('Failed to execute "'.l:run_cmd.'"')
        endtry
    endif

    " switch back to the original directory
    exec 'lcd '.escape(l:cwd, s:CharsEscape)

    " if tee is available, and we are running synchronously, and we want to
    " show the result window right after the run, then we call
    " SingleCompile#ViewResult
    if l:show_result_after_run
        call SingleCompile#ViewResult(0)
        redraw!
    endif

    return l:ret_val
endfunction

function! s:CompileRunInternal(comp_param, async) " {{{1

    " save the current path, thus if quickfix switches to another file, we can
    " still switch back to execute future code correctly
    let l:cur_filepath = expand('%:p')

    " if async run is not available, give an error message and stop here.
    if a:async && empty(SingleCompileAsync#GetMode())
        call s:ShowMessage('Async mode is not available for your vim.')
        return
    endif

    " call different functions according to a:async
    if a:async
        let l:CompileFunc = function('SingleCompile#CompileAsync')
    else
        let l:CompileFunc = function('SingleCompile#Compile')
    endif

    if len(a:comp_param) == 1
        let l:compile_result = l:CompileFunc(a:comp_param[0])
    elseif len(a:comp_param) == 2
        let l:compile_result = l:CompileFunc(
                    \a:comp_param[0], a:comp_param[1])
    else
        let l:compile_result = l:CompileFunc()
    endif

    if l:compile_result != 0
        return
    endif



    let l:cur_filepath2 = expand('%:p')

    if l:cur_filepath != l:cur_filepath2
        exec 'edit ' . l:cur_filepath
    endif

    " run the command and display the following messages only when the process
    " is successfully run.
    if !s:Run(a:async) && a:async
        echo 'SingleCompile: Now the program is running in background.'
        echo 'SingleCompile: you could use :SCViewResultAsync to see the '
                    \.'output if the program has terminated.'
    endif

    if l:cur_filepath != l:cur_filepath2
        exec 'edit ' . l:cur_filepath2
    endif
endfunction

function! SingleCompile#CompileRun(...) " compile and run {{{1
    call s:CompileRunInternal(a:000, 0)
endfunction

function! SingleCompile#CompileRunAsync(...) " {{{1
    " compile and run asynchronously

    call s:CompileRunInternal(a:000, 1)
endfunction

fun! SingleCompile#ChooseCompiler(lang_name, ...) " choose a compiler {{{1

    call s:Initialize()

    if a:0 > 1
        call s:ShowMessage(
                    \'Too many argument for SingleCompile#ChooseCompiler!')
        return
    endif

    if a:0 == 1 " a:0 == 1 means the user has specified a compiler to choose
        if type(a:1) != type('')
            call s:ShowMessage(
                        \'SingleCompile#ChooseCompiler argument error')
            return
        endif

        " If the current language template is set, then we check whether it is
        " available on the system; If not, then an error message is given.  If
        " the current language template is not set, then we give an error
        " message directly.
        if has_key(s:CompilerTemplate, a:lang_name) &&
                    \has_key(s:CompilerTemplate[a:lang_name], a:1)
            " current language template is set

            let l:detected_compilers = s:DetectCompiler(a:lang_name)

            if count(l:detected_compilers, a:1) == 0
                " if a:1 is not a detected compiler

                call s:ShowMessage('"'.
                            \a:1.'" is not available on your system.')
                return
            endif

            let s:CompilerTemplate[a:lang_name]['chosen_compiler'] = a:1
        else
            " current language template is not set
            call s:ShowMessage(
                        \'The template of the compiler/interpreter "'.
                        \a:1.'" is not set.')
        endif

        return
    endif

    " when no argument is provided, list all available compilers and
    " interpreters for user to choose
    if a:0 == 0

        " if the language template is not defined for this language, show an
        " error message then return
        if !has_key(s:CompilerTemplate, a:lang_name)
            call s:ShowMessage('Language template for "'.
                        \a:lang_name.'" is not defined on your system.')
            return
        endif

        let l:detected_compilers = s:DetectCompiler(a:lang_name)
        " used to store the detected compilers
        let l:choose_list = []
        " used to filled with compiler names to be displayed in front of user
        let l:choose_list_display = []

        let l:count = 1

        if !has_key(s:CompilerTemplate[a:lang_name], 'chosen_compiler')
            let s:CompilerTemplate[a:lang_name]['chosen_compiler'] =
                        \ get(l:detected_compilers, 0)
        endif

        for some_compiler in sort(keys(s:CompilerTemplate[a:lang_name]))
            if some_compiler == 'chosen_compiler'
                continue
            endif

            if count(l:detected_compilers, some_compiler) > 0
                " if the compiler is detected, then add it to the choose_list,
                " which would be displayed then

                call add(l:choose_list, some_compiler)
                call add(l:choose_list_display,
                            \l:count.'. '.some_compiler.'('.
                            \s:CompilerTemplate[a:lang_name][some_compiler][
                            \'name'].
                            \')')
                let l:count += 1
            endif
        endfor

        " if l:choose_list is empty, it means no compiler is available for
        " this language
        if empty(l:choose_list)
            call s:ShowMessage(
                        \'No compiler is available for this language!')
            return
        endif

        " display current compiler/interpreter
        echo "Current Compiler/Interpreter: " .
                    \ s:CompilerTemplate[a:lang_name]['chosen_compiler']

        let l:user_choose = inputlist( extend(['Detected compilers: '],
                    \l:choose_list_display) )

        " If user cancels the choosing, then return directly; if user chooses
        " a number which is too big, then echo an empty line first and then
        " show an error message, then return
        if l:user_choose <= 0
            return
        elseif l:user_choose > len(l:choose_list_display)
            echo ' '
            call s:ShowMessage(
                        \'The number you have chosen is invalid.')
            return
        endif

        let s:CompilerTemplate[a:lang_name]['chosen_compiler'] =
                    \get(l:choose_list, l:user_choose-1)

        return
    endif
endfunction

function! SingleCompile#ViewResult(async) " view the running result {{{1
    " split a window below and put the result there

    " don't show result if s:run_result_tempfile is empty for synchronous run
    " result and the mode hasn't been set or the process is still running in
    " background
    if (!a:async && empty(s:run_result_tempfile))
                \|| (a:async
                \&& (empty(SingleCompileAsync#GetMode())
                \|| SingleCompileAsync#IsRunning()))
        return
    endif

    " if the async output cannot be obtained, give an error message and return
    " directly
    if a:async
        let l:async_out = SingleCompileAsync#GetOutput()
        if type(l:async_out) == type(0)
            call s:ShowMessage(
                        \'Failed to get the output of the '.
                        \'process running asynchronously.')
            return
        endif
    endif

    call s:Initialize()

    let l:result_bufnr = bufnr('__SINGLE_COMPILE_RUN_RESULT__')

    " if the __SINGLE_COMPILE_RUN_RESULT__ buffer doesn't exist, make one
    " else clear it, but leave it there to be refilled
    if l:result_bufnr == -1
        exec 'keepalt rightbelow '.g:SingleCompile_resultsize.
                    \g:SingleCompile_split.' __SINGLE_COMPILE_RUN_RESULT__'
        setl noswapfile buftype=nofile bufhidden=wipe foldcolumn=0 nobuflisted
    else
        let l:result_bufwinnr = bufwinnr(l:result_bufnr)
        exec l:result_bufwinnr.'wincmd w'
        let l:save_cursor = getpos(".")
        setl modifiable
        normal! ggdG
        setl nomodifiable
    endif


    setl modifiable
    if a:async
        call append(0, l:async_out)
    else
        call append(0, readfile(s:run_result_tempfile))
    endif
    nnoremap <buffer> q :q<CR>
    setl nomodifiable

    if l:result_bufnr != -1
        call setpos('.', l:save_cursor)
    endif

    exec 'wincmd p'
endfunction

function! SingleCompile#CloseViewResult() " close the last result {{{1
    let l:result_bufnr = bufnr('__SINGLE_COMPILE_RUN_RESULT__')
    if l:result_bufnr != -1
        exec bufwinnr(l:result_bufnr).'wincmd w'
        if l:result_bufnr == bufnr('%') " if we got there
            quit
        endif
    endif
endfunction

call s:Initialize() " {{{1 call the initialize function



" }}}


let &cpo = s:saved_cpo
unlet! s:saved_cpo

" vim703: cc=78
" vim: fdm=marker et ts=4 tw=78 sw=4 fdc=3
