diff --git a/autoload/go/config.vim b/autoload/go/config.vim index 5c03569357..096469cb73 100644 --- a/autoload/go/config.vim +++ b/autoload/go/config.vim @@ -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', \ } \ ) diff --git a/autoload/go/debug.vim b/autoload/go/debug.vim index bb14cf0af4..721117d42b 100644 --- a/autoload/go/debug.vim +++ b/autoload/go/debug.vim @@ -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 @@ -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 :call expand_var() + nmap q (go-debug-stop) + endif + if has_key(debugwindows, "stack") && debugwindows['stack'] != '' exe 'silent ' . debugwindows['stack'] silent file `='__GODEBUG_STACKTRACE__'` @@ -427,6 +437,15 @@ function! s:start_cb() abort nmap q (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 :call go#debug#Goroutine() + endif + if has_key(debugwindows, "out") && debugwindows['out'] != '' exe 'silent ' . debugwindows['out'] silent file `='__GODEBUG_OUTPUT__'` @@ -434,16 +453,7 @@ function! s:start_cb() abort setlocal filetype=godebugoutput nmap q (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 :call expand_var() - nmap q (go-debug-stop) - endif + call win_gotoid(l:winid) silent! delcommand GoDebugStart silent! delcommand GoDebugTest @@ -472,8 +482,6 @@ function! s:start_cb() abort set ballooneval endif - call win_gotoid(l:winid) - augroup vim-go-debug autocmd! * autocmd FileType go nmap (go-debug-continue) @@ -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 @@ -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 @@ -771,6 +779,90 @@ 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. @@ -778,7 +870,7 @@ function! s:update_variables() abort " 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} \ } @@ -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) @@ -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 @@ -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() @@ -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!\\!/!') diff --git a/autoload/go/debug_test.vim b/autoload/go/debug_test.vim index b9aeb2a86f..36ba6b4297 100644 --- a/autoload/go/debug_test.vim +++ b/autoload/go/debug_test.vim @@ -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' diff --git a/doc/vim-go.txt b/doc/vim-go.txt index 4100b17658..4d903a6979 100644 --- a/doc/vim-go.txt +++ b/doc/vim-go.txt @@ -2170,17 +2170,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: >