Skip to content

Commit

Permalink
Merge pull request #2463 from xbsura/feat/multi-goroutine-debug
Browse files Browse the repository at this point in the history
feat/#2338: support debug multi goroutine
  • Loading branch information
bhcleek authored Sep 17, 2019
2 parents 9382419 + f5c4760 commit 75fa6fe
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 29 deletions.
5 changes: 3 additions & 2 deletions autoload/go/config.vim
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,10 @@ endfunction

function! go#config#DebugWindows() abort
return get(g:, 'go_debug_windows', {
\ 'stack': 'leftabove 20vnew',
\ 'out': 'botright 10new',
\ 'vars': 'leftabove 30vnew',
\ 'stack': 'leftabove 20new',
\ 'goroutines': 'botright 10new',
\ 'out': 'botright 5new',
\ }
\ )

Expand Down
161 changes: 142 additions & 19 deletions autoload/go/debug.vim
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ if !exists('s:start_args')
let s:start_args = []
endif

function! s:groutineID() abort
function! s:goroutineID() abort
return s:state['currentThread'].goroutineID
endfunction

Expand Down Expand Up @@ -418,6 +418,16 @@ function! s:start_cb() abort
endif

let debugwindows = go#config#DebugWindows()
if has_key(debugwindows, "vars") && debugwindows['vars'] != ''
exe 'silent ' . debugwindows['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

if has_key(debugwindows, "stack") && debugwindows['stack'] != ''
exe 'silent ' . debugwindows['stack']
silent file `='__GODEBUG_STACKTRACE__'`
Expand All @@ -427,23 +437,23 @@ function! s:start_cb() abort
nmap <buffer> q <Plug>(go-debug-stop)
endif

if has_key(debugwindows, "goroutines") && debugwindows['goroutines'] != ''
exe 'silent ' . debugwindows['goroutines']
silent file `='__GODEBUG_GOROUTINES__'`
setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
setlocal filetype=godebugvariables
call append(0, ["# Goroutines"])
nmap <buffer> <silent> <cr> :<c-u>call go#debug#Goroutine()<cr>
endif

if has_key(debugwindows, "out") && debugwindows['out'] != ''
exe 'silent ' . debugwindows['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 has_key(debugwindows, "vars") && debugwindows['vars'] != ''
exe 'silent ' . debugwindows['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
call win_gotoid(l:winid)

silent! delcommand GoDebugStart
silent! delcommand GoDebugTest
Expand Down Expand Up @@ -472,8 +482,6 @@ function! s:start_cb() abort
set ballooneval
endif

call win_gotoid(l:winid)

augroup vim-go-debug
autocmd! * <buffer>
autocmd FileType go nmap <buffer> <F5> <Plug>(go-debug-continue)
Expand All @@ -487,7 +495,7 @@ endfunction

function! s:err_cb(ch, msg) abort
if get(s:state, 'ready', 0) != 0
call call('s:logger', ['ERR: ', a:ch, a:msg])
call s:logger('ERR: ', a:ch, a:msg)
return
endif

Expand All @@ -496,7 +504,7 @@ endfunction

function! s:out_cb(ch, msg) abort
if get(s:state, 'ready', 0) != 0
call call('s:logger', ['OUT: ', a:ch, a:msg])
call s:logger('OUT: ', a:ch, a:msg)
return
endif

Expand Down Expand Up @@ -771,14 +779,98 @@ function! go#debug#Print(arg) abort
endtry
endfunction

function! s:update_goroutines() abort
try
let l:res = s:call_jsonrpc('RPCServer.State')
let l:currentGoroutineID = 0
try
let l:currentGoroutineID = l:res["result"]["State"]["currentGoroutine"]["id"]
catch
call go#util#EchoWarning("current goroutine not found...")
endtry

let l:res = s:call_jsonrpc('RPCServer.ListGoroutines')
call s:show_goroutines(l:currentGoroutineID, l:res)
catch
call go#util#EchoError(v:exception)
endtry
endfunction

function! s:show_goroutines(currentGoroutineID, res) abort
let l:goroutines_winid = bufwinid('__GODEBUG_GOROUTINES__')
if l:goroutines_winid == -1
return
endif

let l:winid = win_getid()
call win_gotoid(l:goroutines_winid)

try
setlocal modifiable
silent %delete _

let v = ['# Goroutines']

if !has_key(a:res, 'result')
call setline(1, v)
return
endif

let l:goroutines = a:res["result"]["Goroutines"]
if len(l:goroutines) == 0
call go#util#EchoWarning("No Goroutines Running Now...")
call setline(1, v)
return
endif

for l:idx in range(len(l:goroutines))
let l:goroutine = l:goroutines[l:idx]
let l:goroutineType = ""
let l:loc = 0
if l:goroutine.startLoc.file != ""
let l:loc = l:goroutine.startLoc
let l:goroutineType = "Start"
endif
if l:goroutine.goStatementLoc.file != ""
let l:loc = l:goroutine.goStatementLoc
let l:goroutineType = "Go"
endif
if l:goroutine.currentLoc.file != ""
let l:loc = l:goroutine.currentLoc
let l:goroutineType = "Runtime"
endif
if l:goroutine.userCurrentLoc.file != ""
let l:loc=l:goroutine.userCurrentLoc
let l:goroutineType = "User"
endif

" The current goroutine can be changed by pressing enter on one of the
" lines listing a non-active goroutine. If the format of either of these
" lines is modified, then make sure that go#debug#Goroutine is also
" changed if needed.
if l:goroutine.id == a:currentGoroutineID
let l:g = printf("* Goroutine %s - %s: %s:%s %s (thread: %s)", l:goroutine.id, l:goroutineType, l:loc.file, l:loc.line, l:loc.function.name, l:goroutine.threadID)
else
let l:g = printf(" Goroutine %s - %s: %s:%s %s (thread: %s)", l:goroutine.id, l:goroutineType, l:loc.file, l:loc.line, l:loc.function.name, l:goroutine.threadID)
endif
let v += [l:g]
endfor

call setline(1, v)
finally
setlocal nomodifiable
call win_gotoid(l:winid)
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()},
\ 'scope': {'GoroutineID': s:goroutineID()},
\ 'cfg': {'MaxStringLen': 20, 'MaxArrayValues': 20}
\ }

Expand Down Expand Up @@ -816,7 +908,7 @@ endfunction

function! s:update_stacktrace() abort
try
let l:res = s:call_jsonrpc('RPCServer.Stacktrace', {'id': s:groutineID(), 'depth': 5})
let l:res = s:call_jsonrpc('RPCServer.Stacktrace', {'id': s:goroutineID(), 'depth': 5})
call s:show_stacktrace(l:res)
catch
call go#util#EchoError(v:exception)
Expand All @@ -829,7 +921,9 @@ function! s:stack_cb(res) abort
if empty(a:res) || !has_key(a:res, 'result')
return
endif

call s:update_breakpoint(a:res)
call s:update_goroutines()
call s:update_stacktrace()
call s:update_variables()
endfunction
Expand Down Expand Up @@ -861,8 +955,20 @@ function! go#debug#Stack(name) abort
endif
let s:stack_name = l:name
try
let res = s:call_jsonrpc('RPCServer.Command', {'name': l:name})
call s:stack_cb(res)
let l:res = s:call_jsonrpc('RPCServer.Command', {'name': l:name})

if l:name is# 'next'
let l:res2 = l:res
let l:w = 0
while l:w < 1
if l:res2.result.State.NextInProgress == v:true
let l:res2 = s:call_jsonrpc('RPCServer.Command', {'name': 'continue'})
else
break
endif
endwhile
endif
call s:stack_cb(l:res)
catch
call go#util#EchoError(v:exception)
call s:clearState()
Expand Down Expand Up @@ -899,6 +1005,23 @@ function! s:isActive()
return len(s:state['message']) > 0
endfunction

" Change Goroutine
function! go#debug#Goroutine() abort
let l:goroutineID = substitute(getline('.'), '^ Goroutine \(.\{-1,\}\) - .*', '\1', 'g')

if l:goroutineID <= 0
return
endif

try
let l:res = s:call_jsonrpc('RPCServer.Command', {'Name': 'switchGoroutine', 'GoroutineID': str2nr(l:goroutineID)})
call s:stack_cb(l:res)
call go#util#EchoInfo("Switched goroutine to: " . l:goroutineID)
catch
call go#util#EchoError(v:exception)
endtry
endfunction

" Toggle breakpoint. Returns 0 on success and 1 on failure.
function! go#debug#Breakpoint(...) abort
let l:filename = fnamemodify(expand('%'), ':p:gs!\\!/!')
Expand Down
5 changes: 3 additions & 2 deletions autoload/go/debug_test.vim
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ function! Test_GoDebugStart_Errors() abort
endif

try
let l:tmp = gotest#load_fixture('debug/compilerror/main.go')

let l:expected = [
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 0, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': '# debug/compilerror'},
\ {'lnum': 6, 'bufnr': 7, 'col': 22, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': ' syntax error: unexpected newline, expecting comma or )'},
\ {'lnum': 6, 'bufnr': bufnr('%'), 'col': 22, 'valid': 1, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': ' syntax error: unexpected newline, expecting comma or )'},
\ {'lnum': 0, 'bufnr': 0, 'col': 0, 'valid': 0, 'vcol': 0, 'nr': -1, 'type': '', 'pattern': '', 'text': 'exit status 2'}
\]
call setqflist([], 'r')

let l:tmp = gotest#load_fixture('debug/compilerror/main.go')
call assert_false(exists(':GoDebugStop'))

let l:cd = exists('*haslocaldir') && haslocaldir() ? 'lcd' : 'cd'
Expand Down
15 changes: 9 additions & 6 deletions doc/vim-go.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2167,17 +2167,20 @@ DEBUGGER SETTINGS~

*'g:go_debug_windows'*

Controls the window layout for debugging mode. This is a |dict| with three
possible keys: "stack", "out", and "vars"; the windows will created in that
order with the commands in the value.
Controls the window layout for debugging mode. This is a |dict| with four
possible keys: "vars", "stack", "goroutines", and "out"; each of the new
windows will be created in that that order with the commands in the value. The
current window is made the only window before creating the debug windows.

A window will not be created if a key is missing or empty.

Defaults:
>
let g:go_debug_windows = {
\ 'stack': 'leftabove 20vnew',
\ 'out': 'botright 10new',
\ 'vars': 'leftabove 30vnew',
\ 'vars': 'leftabove 30vnew',
\ 'stack': 'leftabove 20new',
\ 'goroutines': 'botright 10new',
\ 'out': 'botright 5new',
\ }
<
Show only variables on the right-hand side: >
Expand Down

0 comments on commit 75fa6fe

Please sign in to comment.