123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890 |
- scriptencoding utf-8
- if !exists('g:go_debug_windows')
- let g:go_debug_windows = {
- \ 'stack': 'leftabove 20vnew',
- \ 'out': 'botright 10new',
- \ 'vars': 'leftabove 30vnew',
- \ }
- endif
- if !exists('g:go_debug_address')
- let g:go_debug_address = '127.0.0.1:8181'
- endif
- if !exists('s:state')
- let s:state = {
- \ 'rpcid': 1,
- \ 'running': 0,
- \ 'breakpoint': {},
- \ 'currentThread': {},
- \ 'localVars': {},
- \ 'functionArgs': {},
- \ 'message': [],
- \}
- if go#util#HasDebug('debugger-state')
- let g:go_debug_diag = s:state
- endif
- endif
- if !exists('s:start_args')
- let s:start_args = []
- endif
- function! s:groutineID() abort
- return s:state['currentThread'].goroutineID
- endfunction
- function! s:exit(job, status) abort
- if has_key(s:state, 'job')
- call remove(s:state, 'job')
- endif
- call s:clearState()
- if a:status > 0
- call go#util#EchoError(s:state['message'])
- endif
- endfunction
- function! s:logger(prefix, ch, msg) abort
- let l:cur_win = bufwinnr('')
- let l:log_win = bufwinnr(bufnr('__GODEBUG_OUTPUT__'))
- if l:log_win == -1
- return
- endif
- exe l:log_win 'wincmd w'
- try
- setlocal modifiable
- if getline(1) == ''
- call setline('$', a:prefix . a:msg)
- else
- call append('$', a:prefix . a:msg)
- endif
- normal! G
- setlocal nomodifiable
- finally
- exe l:cur_win 'wincmd w'
- endtry
- endfunction
- function! s:call_jsonrpc(method, ...) abort
- if go#util#HasDebug('debugger-commands')
- if !exists('g:go_debug_commands')
- let g:go_debug_commands = []
- endif
- echom 'sending to dlv ' . a:method
- endif
- if len(a:000) > 0 && type(a:000[0]) == v:t_func
- let Cb = a:000[0]
- let args = a:000[1:]
- else
- let Cb = v:none
- let args = a:000
- endif
- let s:state['rpcid'] += 1
- let req_json = json_encode({
- \ 'id': s:state['rpcid'],
- \ 'method': a:method,
- \ 'params': args,
- \})
- try
- " Use callback
- if type(Cb) == v:t_func
- let s:ch = ch_open('127.0.0.1:8181', {'mode': 'nl', 'callback': Cb})
- call ch_sendraw(s:ch, req_json)
- if go#util#HasDebug('debugger-commands')
- let g:go_debug_commands = add(g:go_debug_commands, {
- \ 'request': req_json,
- \ 'response': Cb,
- \ })
- endif
- return
- endif
- let ch = ch_open('127.0.0.1:8181', {'mode': 'nl', 'timeout': 20000})
- call ch_sendraw(ch, req_json)
- let resp_json = ch_readraw(ch)
- if go#util#HasDebug('debugger-commands')
- let g:go_debug_commands = add(g:go_debug_commands, {
- \ 'request': req_json,
- \ 'response': resp_json,
- \ })
- endif
- let obj = json_decode(resp_json)
- if type(obj) == v:t_dict && has_key(obj, 'error') && !empty(obj.error)
- throw obj.error
- endif
- return obj
- catch
- throw substitute(v:exception, '^Vim', '', '')
- endtry
- endfunction
- " Update the location of the current breakpoint or line we're halted on based on
- " response from dlv.
- function! s:update_breakpoint(res) abort
- if type(a:res) ==# v:t_none
- return
- endif
- let state = a:res.result.State
- if !has_key(state, 'currentThread')
- return
- endif
- let s:state['currentThread'] = state.currentThread
- let bufs = filter(map(range(1, winnr('$')), '[v:val,bufname(winbufnr(v:val))]'), 'v:val[1]=~"\.go$"')
- if len(bufs) == 0
- return
- endif
- exe bufs[0][0] 'wincmd w'
- let filename = state.currentThread.file
- let linenr = state.currentThread.line
- let oldfile = fnamemodify(expand('%'), ':p:gs!\\!/!')
- if oldfile != filename
- silent! exe 'edit' filename
- endif
- silent! exe 'norm!' linenr.'G'
- silent! normal! zvzz
- silent! sign unplace 9999
- silent! exe 'sign place 9999 line=' . linenr . ' name=godebugcurline file=' . filename
- endfunction
- " Populate the stacktrace window.
- function! s:show_stacktrace(res) abort
- if !has_key(a:res, 'result')
- return
- endif
- let l:stack_win = bufwinnr(bufnr('__GODEBUG_STACKTRACE__'))
- if l:stack_win == -1
- return
- endif
- let l:cur_win = bufwinnr('')
- exe l:stack_win 'wincmd w'
- try
- setlocal modifiable
- silent %delete _
- for i in range(len(a:res.result.Locations))
- let loc = a:res.result.Locations[i]
- call setline(i+1, printf('%s - %s:%d', loc.function.name, fnamemodify(loc.file, ':p'), loc.line))
- endfor
- finally
- setlocal nomodifiable
- exe l:cur_win 'wincmd w'
- endtry
- endfunction
- " Populate the variable window.
- function! s:show_variables() abort
- let l:var_win = bufwinnr(bufnr('__GODEBUG_VARIABLES__'))
- if l:var_win == -1
- return
- endif
- let l:cur_win = bufwinnr('')
- exe l:var_win 'wincmd w'
- try
- setlocal modifiable
- silent %delete _
- let v = []
- let v += ['# Local Variables']
- if type(get(s:state, 'localVars', [])) is type([])
- for c in s:state['localVars']
- let v += split(s:eval_tree(c, 0), "\n")
- endfor
- endif
- let v += ['']
- let v += ['# Function Arguments']
- if type(get(s:state, 'functionArgs', [])) is type([])
- for c in s:state['functionArgs']
- let v += split(s:eval_tree(c, 0), "\n")
- endfor
- endif
- call setline(1, v)
- finally
- setlocal nomodifiable
- exe l:cur_win 'wincmd w'
- endtry
- endfunction
- function! s:clearState() abort
- let s:state['currentThread'] = {}
- let s:state['localVars'] = {}
- let s:state['functionArgs'] = {}
- let s:state['message'] = []
- silent! sign unplace 9999
- endfunction
- function! s:stop() abort
- call s:clearState()
- if has_key(s:state, 'job')
- call job_stop(s:state['job'])
- call remove(s:state, 'job')
- endif
- endfunction
- function! go#debug#Stop() abort
- " Remove signs.
- for k in keys(s:state['breakpoint'])
- let bt = s:state['breakpoint'][k]
- if bt.id >= 0
- silent exe 'sign unplace ' . bt.id
- endif
- endfor
- " Remove all commands and add back the default commands.
- for k in map(split(execute('command GoDebug'), "\n")[1:], 'matchstr(v:val, "^\\s*\\zs\\S\\+")')
- exe 'delcommand' k
- endfor
- command! -nargs=* -complete=customlist,go#package#Complete GoDebugStart call go#debug#Start(<f-args>)
- command! -nargs=? GoDebugBreakpoint call go#debug#Breakpoint(<f-args>)
- " Remove all mappings.
- for k in map(split(execute('map <Plug>(go-debug-'), "\n")[1:], 'matchstr(v:val, "^n\\s\\+\\zs\\S\\+")')
- exe 'unmap' k
- endfor
- call s:stop()
- let bufs = filter(map(range(1, winnr('$')), '[v:val,bufname(winbufnr(v:val))]'), 'v:val[1]=~"\.go$"')
- if len(bufs) > 0
- exe bufs[0][0] 'wincmd w'
- else
- wincmd p
- endif
- silent! exe bufwinnr(bufnr('__GODEBUG_STACKTRACE__')) 'wincmd c'
- silent! exe bufwinnr(bufnr('__GODEBUG_VARIABLES__')) 'wincmd c'
- silent! exe bufwinnr(bufnr('__GODEBUG_OUTPUT__')) 'wincmd c'
- set noballooneval
- set balloonexpr=
- endfunction
- function! s:goto_file() abort
- let m = matchlist(getline('.'), ' - \(.*\):\([0-9]\+\)$')
- if m[1] == ''
- return
- endif
- let bufs = filter(map(range(1, winnr('$')), '[v:val,bufname(winbufnr(v:val))]'), 'v:val[1]=~"\.go$"')
- if len(bufs) == 0
- return
- endif
- exe bufs[0][0] 'wincmd w'
- let filename = m[1]
- let linenr = m[2]
- let oldfile = fnamemodify(expand('%'), ':p:gs!\\!/!')
- if oldfile != filename
- silent! exe 'edit' filename
- endif
- silent! exe 'norm!' linenr.'G'
- silent! normal! zvzz
- endfunction
- function! s:delete_expands()
- let nr = line('.')
- while 1
- let l = getline(nr+1)
- if empty(l) || l =~ '^\S'
- return
- endif
- silent! exe (nr+1) . 'd _'
- endwhile
- silent! exe 'norm!' nr.'G'
- endfunction
- function! s:expand_var() abort
- " Get name from struct line.
- let name = matchstr(getline('.'), '^[^:]\+\ze: [a-zA-Z0-9\.·]\+{\.\.\.}$')
- " Anonymous struct
- if name == ''
- let name = matchstr(getline('.'), '^[^:]\+\ze: struct {.\{-}}$')
- endif
- if name != ''
- setlocal modifiable
- let not_open = getline(line('.')+1) !~ '^ '
- let l = line('.')
- call s:delete_expands()
- if not_open
- call append(l, split(s:eval(name), "\n")[1:])
- endif
- silent! exe 'norm!' l.'G'
- setlocal nomodifiable
- return
- endif
- " Expand maps
- let m = matchlist(getline('.'), '^[^:]\+\ze: map.\{-}\[\(\d\+\)\]$')
- if len(m) > 0 && m[1] != ''
- setlocal modifiable
- let not_open = getline(line('.')+1) !~ '^ '
- let l = line('.')
- call s:delete_expands()
- if not_open
- " TODO: Not sure how to do this yet... Need to get keys of the map.
- " let vs = ''
- " for i in range(0, min([10, m[1]-1]))
- " let vs .= ' ' . s:eval(printf("%s[%s]", m[0], ))
- " endfor
- " call append(l, split(vs, "\n"))
- endif
- silent! exe 'norm!' l.'G'
- setlocal nomodifiable
- return
- endif
- " Expand string.
- let m = matchlist(getline('.'), '^\([^:]\+\)\ze: \(string\)\[\([0-9]\+\)\]\(: .\{-}\)\?$')
- if len(m) > 0 && m[1] != ''
- setlocal modifiable
- let not_open = getline(line('.')+1) !~ '^ '
- let l = line('.')
- call s:delete_expands()
- if not_open
- let vs = ''
- for i in range(0, min([10, m[3]-1]))
- let vs .= ' ' . s:eval(m[1] . '[' . i . ']')
- endfor
- call append(l, split(vs, "\n"))
- endif
- silent! exe 'norm!' l.'G'
- setlocal nomodifiable
- return
- endif
- " Expand slice.
- let m = matchlist(getline('.'), '^\([^:]\+\)\ze: \(\[\]\w\{-}\)\[\([0-9]\+\)\]$')
- if len(m) > 0 && m[1] != ''
- setlocal modifiable
- let not_open = getline(line('.')+1) !~ '^ '
- let l = line('.')
- call s:delete_expands()
- if not_open
- let vs = ''
- for i in range(0, min([10, m[3]-1]))
- let vs .= ' ' . s:eval(m[1] . '[' . i . ']')
- endfor
- call append(l, split(vs, "\n"))
- endif
- silent! exe 'norm!' l.'G'
- setlocal nomodifiable
- return
- endif
- endfunction
- function! s:start_cb(ch, json) abort
- let res = json_decode(a:json)
- if type(res) == v:t_dict && has_key(res, 'error') && !empty(res.error)
- throw res.error
- endif
- if empty(res) || !has_key(res, 'result')
- return
- endif
- for bt in res.result.Breakpoints
- if bt.id >= 0
- let s:state['breakpoint'][bt.id] = bt
- exe 'sign place '. bt.id .' line=' . bt.line . ' name=godebugbreakpoint file=' . bt.file
- endif
- endfor
- let oldbuf = bufnr('%')
- silent! only!
- let winnum = bufwinnr(bufnr('__GODEBUG_STACKTRACE__'))
- if winnum != -1
- return
- endif
- if exists('g:go_debug_windows["stack"]') && g:go_debug_windows['stack'] != ''
- exe 'silent ' . g:go_debug_windows['stack']
- silent file `='__GODEBUG_STACKTRACE__'`
- setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
- setlocal filetype=godebugstacktrace
- nmap <buffer> <cr> :<c-u>call <SID>goto_file()<cr>
- nmap <buffer> q <Plug>(go-debug-stop)
- endif
- if exists('g:go_debug_windows["out"]') && g:go_debug_windows['out'] != ''
- exe 'silent ' . g:go_debug_windows['out']
- silent file `='__GODEBUG_OUTPUT__'`
- setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
- setlocal filetype=godebugoutput
- nmap <buffer> q <Plug>(go-debug-stop)
- endif
- if exists('g:go_debug_windows["vars"]') && g:go_debug_windows['vars'] != ''
- exe 'silent ' . g:go_debug_windows['vars']
- silent file `='__GODEBUG_VARIABLES__'`
- setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
- setlocal filetype=godebugvariables
- call append(0, ["# Local Variables", "", "# Function Arguments"])
- nmap <buffer> <silent> <cr> :<c-u>call <SID>expand_var()<cr>
- nmap <buffer> q <Plug>(go-debug-stop)
- endif
- silent! delcommand GoDebugStart
- command! -nargs=0 GoDebugContinue call go#debug#Stack('continue')
- command! -nargs=0 GoDebugNext call go#debug#Stack('next')
- command! -nargs=0 GoDebugStep call go#debug#Stack('step')
- command! -nargs=0 GoDebugStepOut call go#debug#Stack('stepOut')
- command! -nargs=0 GoDebugRestart call go#debug#Restart()
- command! -nargs=0 GoDebugStop call go#debug#Stop()
- command! -nargs=* GoDebugSet call go#debug#Set(<f-args>)
- command! -nargs=1 GoDebugPrint call go#debug#Print(<q-args>)
- nnoremap <silent> <Plug>(go-debug-breakpoint) :<C-u>call go#debug#Breakpoint()<CR>
- nnoremap <silent> <Plug>(go-debug-next) :<C-u>call go#debug#Stack('next')<CR>
- nnoremap <silent> <Plug>(go-debug-step) :<C-u>call go#debug#Stack('step')<CR>
- nnoremap <silent> <Plug>(go-debug-stepout) :<C-u>call go#debug#Stack('stepout')<CR>
- nnoremap <silent> <Plug>(go-debug-continue) :<C-u>call go#debug#Stack('continue')<CR>
- nnoremap <silent> <Plug>(go-debug-stop) :<C-u>call go#debug#Stop()<CR>
- nnoremap <silent> <Plug>(go-debug-print) :<C-u>call go#debug#Print(expand('<cword>'))<CR>
- nmap <F5> <Plug>(go-debug-continue)
- nmap <F6> <Plug>(go-debug-print)
- nmap <F9> <Plug>(go-debug-breakpoint)
- nmap <F10> <Plug>(go-debug-next)
- nmap <F11> <Plug>(go-debug-step)
- set balloonexpr=go#debug#BalloonExpr()
- set ballooneval
- exe bufwinnr(oldbuf) 'wincmd w'
- endfunction
- function! s:starting(ch, msg) abort
- call go#util#EchoProgress(a:msg)
- let s:state['message'] += [a:msg]
- if stridx(a:msg, g:go_debug_address) != -1
- call ch_setoptions(a:ch, {
- \ 'out_cb': function('s:logger', ['OUT: ']),
- \ 'err_cb': function('s:logger', ['ERR: ']),
- \})
- " Tell dlv about the breakpoints that the user added before delve started.
- let l:breaks = copy(s:state.breakpoint)
- let s:state['breakpoint'] = {}
- for l:bt in values(l:breaks)
- call go#debug#Breakpoint(bt.line)
- endfor
- call s:call_jsonrpc('RPCServer.ListBreakpoints', function('s:start_cb'))
- endif
- endfunction
- " Start the debug mode. The first argument is the package name to compile and
- " debug, anything else will be passed to the running program.
- function! go#debug#Start(...) abort
- if has('nvim')
- call go#util#EchoError('This feature only works in Vim for now; Neovim is not (yet) supported. Sorry :-(')
- return
- endif
- if !go#util#has_job()
- call go#util#EchoError('This feature requires Vim 8.0.0087 or newer with +job.')
- return
- endif
- " It's already running.
- if has_key(s:state, 'job') && job_status(s:state['job']) == 'run'
- return
- endif
- let s:start_args = a:000
- if go#util#HasDebug('debugger-state')
- let g:go_debug_diag = s:state
- endif
- let l:is_test = bufname('')[-8:] is# '_test.go'
- let dlv = go#path#CheckBinPath("dlv")
- if empty(dlv)
- return
- endif
- try
- if len(a:000) > 0
- let l:pkgname = a:1
- " Expand .; otherwise this won't work from a tmp dir.
- if l:pkgname[0] == '.'
- let l:pkgname = go#package#FromPath(getcwd()) . l:pkgname[1:]
- endif
- else
- let l:pkgname = go#package#FromPath(getcwd())
- endif
- let l:args = []
- if len(a:000) > 1
- let l:args = ['--'] + a:000[1:]
- endif
- let l:cmd = [
- \ dlv,
- \ (l:is_test ? 'test' : 'debug'),
- \ '--output', tempname(),
- \ '--headless',
- \ '--api-version', '2',
- \ '--log',
- \ '--listen', g:go_debug_address,
- \ '--accept-multiclient',
- \]
- if get(g:, 'go_build_tags', '') isnot ''
- let l:cmd += ['--build-flags', '--tags=' . g:go_build_tags]
- endif
- let l:cmd += l:args
- call go#util#EchoProgress('Starting GoDebug...')
- let s:state['message'] = []
- let s:state['job'] = job_start(l:cmd, {
- \ 'out_cb': function('s:starting'),
- \ 'err_cb': function('s:starting'),
- \ 'exit_cb': function('s:exit'),
- \ 'stoponexit': 'kill',
- \})
- catch
- call go#util#EchoError(v:exception)
- endtry
- endfunction
- " Translate a reflect kind constant to a human string.
- function! s:reflect_kind(k)
- " Kind constants from Go's reflect package.
- return [
- \ 'Invalid Kind',
- \ 'Bool',
- \ 'Int',
- \ 'Int8',
- \ 'Int16',
- \ 'Int32',
- \ 'Int64',
- \ 'Uint',
- \ 'Uint8',
- \ 'Uint16',
- \ 'Uint32',
- \ 'Uint64',
- \ 'Uintptr',
- \ 'Float32',
- \ 'Float64',
- \ 'Complex64',
- \ 'Complex128',
- \ 'Array',
- \ 'Chan',
- \ 'Func',
- \ 'Interface',
- \ 'Map',
- \ 'Ptr',
- \ 'Slice',
- \ 'String',
- \ 'Struct',
- \ 'UnsafePointer',
- \ ][a:k]
- endfunction
- function! s:eval_tree(var, nest) abort
- if a:var.name =~ '^\~'
- return ''
- endif
- let nest = a:nest
- let v = ''
- let kind = s:reflect_kind(a:var.kind)
- if !empty(a:var.name)
- let v .= repeat(' ', nest) . a:var.name . ': '
- if kind == 'Bool'
- let v .= printf("%s\n", a:var.value)
- elseif kind == 'Struct'
- " Anonymous struct
- if a:var.type[:8] == 'struct { '
- let v .= printf("%s\n", a:var.type)
- else
- let v .= printf("%s{...}\n", a:var.type)
- endif
- elseif kind == 'String'
- let v .= printf("%s[%d]%s\n", a:var.type, a:var.len,
- \ len(a:var.value) > 0 ? ': ' . a:var.value : '')
- elseif kind == 'Slice' || kind == 'String' || kind == 'Map' || kind == 'Array'
- let v .= printf("%s[%d]\n", a:var.type, a:var.len)
- elseif kind == 'Chan' || kind == 'Func' || kind == 'Interface'
- let v .= printf("%s\n", a:var.type)
- elseif kind == 'Ptr'
- " TODO: We can do something more useful here.
- let v .= printf("%s\n", a:var.type)
- elseif kind == 'Complex64' || kind == 'Complex128'
- let v .= printf("%s%s\n", a:var.type, a:var.value)
- " Int, Float
- else
- let v .= printf("%s(%s)\n", a:var.type, a:var.value)
- endif
- else
- let nest -= 1
- endif
- if index(['Chan', 'Complex64', 'Complex128'], kind) == -1 && a:var.type != 'error'
- for c in a:var.children
- let v .= s:eval_tree(c, nest+1)
- endfor
- endif
- return v
- endfunction
- function! s:eval(arg) abort
- try
- let res = s:call_jsonrpc('RPCServer.State')
- let goroutineID = res.result.State.currentThread.goroutineID
- let res = s:call_jsonrpc('RPCServer.Eval', {
- \ 'expr': a:arg,
- \ 'scope': {'GoroutineID': goroutineID}
- \ })
- return s:eval_tree(res.result.Variable, 0)
- catch
- call go#util#EchoError(v:exception)
- return ''
- endtry
- endfunction
- function! go#debug#BalloonExpr() abort
- silent! let l:v = s:eval(v:beval_text)
- return l:v
- endfunction
- function! go#debug#Print(arg) abort
- try
- echo substitute(s:eval(a:arg), "\n$", "", 0)
- catch
- call go#util#EchoError(v:exception)
- endtry
- endfunction
- function! s:update_variables() abort
- " FollowPointers requests pointers to be automatically dereferenced.
- " MaxVariableRecurse is how far to recurse when evaluating nested types.
- " MaxStringLen is the maximum number of bytes read from a string
- " MaxArrayValues is the maximum number of elements read from an array, a slice or a map.
- " MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields.
- let l:cfg = {
- \ 'scope': {'GoroutineID': s:groutineID()},
- \ 'cfg': {'MaxStringLen': 20, 'MaxArrayValues': 20}
- \ }
- try
- let res = s:call_jsonrpc('RPCServer.ListLocalVars', l:cfg)
- let s:state['localVars'] = res.result['Variables']
- catch
- call go#util#EchoError(v:exception)
- endtry
- try
- let res = s:call_jsonrpc('RPCServer.ListFunctionArgs', l:cfg)
- let s:state['functionArgs'] = res.result['Args']
- catch
- call go#util#EchoError(v:exception)
- endtry
- call s:show_variables()
- endfunction
- function! go#debug#Set(symbol, value) abort
- try
- let res = s:call_jsonrpc('RPCServer.State')
- let goroutineID = res.result.State.currentThread.goroutineID
- call s:call_jsonrpc('RPCServer.Set', {
- \ 'symbol': a:symbol,
- \ 'value': a:value,
- \ 'scope': {'GoroutineID': goroutineID}
- \ })
- catch
- call go#util#EchoError(v:exception)
- endtry
- call s:update_variables()
- endfunction
- function! s:update_stacktrace() abort
- try
- let res = s:call_jsonrpc('RPCServer.Stacktrace', {'id': s:groutineID(), 'depth': 5})
- call s:show_stacktrace(res)
- catch
- call go#util#EchoError(v:exception)
- endtry
- endfunction
- function! s:stack_cb(ch, json) abort
- let s:stack_name = ''
- let res = json_decode(a:json)
- if type(res) == v:t_dict && has_key(res, 'error') && !empty(res.error)
- call go#util#EchoError(res.error)
- call s:clearState()
- call go#debug#Restart()
- return
- endif
- if empty(res) || !has_key(res, 'result')
- return
- endif
- call s:update_breakpoint(res)
- call s:update_stacktrace()
- call s:update_variables()
- endfunction
- " Send a command to change the cursor location to Delve.
- "
- " a:name must be one of continue, next, step, or stepOut.
- function! go#debug#Stack(name) abort
- let l:name = a:name
- " Run continue if the program hasn't started yet.
- if s:state.running is 0
- let s:state.running = 1
- let l:name = 'continue'
- endif
- " Add a breakpoint to the main.Main if the user didn't define any.
- if len(s:state['breakpoint']) is 0
- try
- let res = s:call_jsonrpc('RPCServer.FindLocation', {'loc': 'main.main'})
- let res = s:call_jsonrpc('RPCServer.CreateBreakpoint', {'Breakpoint': {'addr': res.result.Locations[0].pc}})
- let bt = res.result.Breakpoint
- let s:state['breakpoint'][bt.id] = bt
- catch
- call go#util#EchoError(v:exception)
- endtry
- endif
- try
- " TODO: document why this is needed.
- if l:name is# 'next' && get(s:, 'stack_name', '') is# 'next'
- call s:call_jsonrpc('RPCServer.CancelNext')
- endif
- let s:stack_name = l:name
- call s:call_jsonrpc('RPCServer.Command', function('s:stack_cb'), {'name': l:name})
- catch
- call go#util#EchoError(v:exception)
- endtry
- endfunction
- function! go#debug#Restart() abort
- try
- call job_stop(s:state['job'])
- while has_key(s:state, 'job') && job_status(s:state['job']) is# 'run'
- sleep 50m
- endwhile
- let l:breaks = s:state['breakpoint']
- let s:state = {
- \ 'rpcid': 1,
- \ 'running': 0,
- \ 'breakpoint': {},
- \ 'currentThread': {},
- \ 'localVars': {},
- \ 'functionArgs': {},
- \ 'message': [],
- \}
- " Preserve breakpoints.
- for bt in values(l:breaks)
- " TODO: should use correct filename
- exe 'sign unplace '. bt.id .' file=' . bt.file
- call go#debug#Breakpoint(bt.line)
- endfor
- call call('go#debug#Start', s:start_args)
- catch
- call go#util#EchoError(v:exception)
- endtry
- endfunction
- " Report if debugger mode is active.
- function! s:isActive()
- return len(s:state['message']) > 0
- endfunction
- " Toggle breakpoint.
- function! go#debug#Breakpoint(...) abort
- let l:filename = fnamemodify(expand('%'), ':p:gs!\\!/!')
- " Get line number from argument.
- if len(a:000) > 0
- let linenr = str2nr(a:1)
- if linenr is 0
- call go#util#EchoError('not a number: ' . a:1)
- return
- endif
- else
- let linenr = line('.')
- endif
- try
- " Check if we already have a breakpoint for this line.
- let found = v:none
- for k in keys(s:state.breakpoint)
- let bt = s:state.breakpoint[k]
- if bt.file == l:filename && bt.line == linenr
- let found = bt
- break
- endif
- endfor
- " Remove breakpoint.
- if type(found) == v:t_dict
- call remove(s:state['breakpoint'], bt.id)
- exe 'sign unplace '. found.id .' file=' . found.file
- if s:isActive()
- let res = s:call_jsonrpc('RPCServer.ClearBreakpoint', {'id': found.id})
- endif
- " Add breakpoint.
- else
- if s:isActive()
- let res = s:call_jsonrpc('RPCServer.CreateBreakpoint', {'Breakpoint': {'file': l:filename, 'line': linenr}})
- let bt = res.result.Breakpoint
- exe 'sign place '. bt.id .' line=' . bt.line . ' name=godebugbreakpoint file=' . bt.file
- let s:state['breakpoint'][bt.id] = bt
- else
- let id = len(s:state['breakpoint']) + 1
- let s:state['breakpoint'][id] = {'id': id, 'file': l:filename, 'line': linenr}
- exe 'sign place '. id .' line=' . linenr . ' name=godebugbreakpoint file=' . l:filename
- endif
- endif
- catch
- call go#util#EchoError(v:exception)
- endtry
- endfunction
- sign define godebugbreakpoint text=> texthl=GoDebugBreakpoint
- sign define godebugcurline text== linehl=GoDebugCurrent texthl=GoDebugCurrent
- fun! s:hi()
- hi GoDebugBreakpoint term=standout ctermbg=117 ctermfg=0 guibg=#BAD4F5 guifg=Black
- hi GoDebugCurrent term=reverse ctermbg=12 ctermfg=7 guibg=DarkBlue guifg=White
- endfun
- augroup vim-go-breakpoint
- autocmd!
- autocmd ColorScheme * call s:hi()
- augroup end
- call s:hi()
- " vim: sw=2 ts=2 et
|