debug.vim 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890
  1. scriptencoding utf-8
  2. if !exists('g:go_debug_windows')
  3. let g:go_debug_windows = {
  4. \ 'stack': 'leftabove 20vnew',
  5. \ 'out': 'botright 10new',
  6. \ 'vars': 'leftabove 30vnew',
  7. \ }
  8. endif
  9. if !exists('g:go_debug_address')
  10. let g:go_debug_address = '127.0.0.1:8181'
  11. endif
  12. if !exists('s:state')
  13. let s:state = {
  14. \ 'rpcid': 1,
  15. \ 'running': 0,
  16. \ 'breakpoint': {},
  17. \ 'currentThread': {},
  18. \ 'localVars': {},
  19. \ 'functionArgs': {},
  20. \ 'message': [],
  21. \}
  22. if go#util#HasDebug('debugger-state')
  23. let g:go_debug_diag = s:state
  24. endif
  25. endif
  26. if !exists('s:start_args')
  27. let s:start_args = []
  28. endif
  29. function! s:groutineID() abort
  30. return s:state['currentThread'].goroutineID
  31. endfunction
  32. function! s:exit(job, status) abort
  33. if has_key(s:state, 'job')
  34. call remove(s:state, 'job')
  35. endif
  36. call s:clearState()
  37. if a:status > 0
  38. call go#util#EchoError(s:state['message'])
  39. endif
  40. endfunction
  41. function! s:logger(prefix, ch, msg) abort
  42. let l:cur_win = bufwinnr('')
  43. let l:log_win = bufwinnr(bufnr('__GODEBUG_OUTPUT__'))
  44. if l:log_win == -1
  45. return
  46. endif
  47. exe l:log_win 'wincmd w'
  48. try
  49. setlocal modifiable
  50. if getline(1) == ''
  51. call setline('$', a:prefix . a:msg)
  52. else
  53. call append('$', a:prefix . a:msg)
  54. endif
  55. normal! G
  56. setlocal nomodifiable
  57. finally
  58. exe l:cur_win 'wincmd w'
  59. endtry
  60. endfunction
  61. function! s:call_jsonrpc(method, ...) abort
  62. if go#util#HasDebug('debugger-commands')
  63. if !exists('g:go_debug_commands')
  64. let g:go_debug_commands = []
  65. endif
  66. echom 'sending to dlv ' . a:method
  67. endif
  68. if len(a:000) > 0 && type(a:000[0]) == v:t_func
  69. let Cb = a:000[0]
  70. let args = a:000[1:]
  71. else
  72. let Cb = v:none
  73. let args = a:000
  74. endif
  75. let s:state['rpcid'] += 1
  76. let req_json = json_encode({
  77. \ 'id': s:state['rpcid'],
  78. \ 'method': a:method,
  79. \ 'params': args,
  80. \})
  81. try
  82. " Use callback
  83. if type(Cb) == v:t_func
  84. let s:ch = ch_open('127.0.0.1:8181', {'mode': 'nl', 'callback': Cb})
  85. call ch_sendraw(s:ch, req_json)
  86. if go#util#HasDebug('debugger-commands')
  87. let g:go_debug_commands = add(g:go_debug_commands, {
  88. \ 'request': req_json,
  89. \ 'response': Cb,
  90. \ })
  91. endif
  92. return
  93. endif
  94. let ch = ch_open('127.0.0.1:8181', {'mode': 'nl', 'timeout': 20000})
  95. call ch_sendraw(ch, req_json)
  96. let resp_json = ch_readraw(ch)
  97. if go#util#HasDebug('debugger-commands')
  98. let g:go_debug_commands = add(g:go_debug_commands, {
  99. \ 'request': req_json,
  100. \ 'response': resp_json,
  101. \ })
  102. endif
  103. let obj = json_decode(resp_json)
  104. if type(obj) == v:t_dict && has_key(obj, 'error') && !empty(obj.error)
  105. throw obj.error
  106. endif
  107. return obj
  108. catch
  109. throw substitute(v:exception, '^Vim', '', '')
  110. endtry
  111. endfunction
  112. " Update the location of the current breakpoint or line we're halted on based on
  113. " response from dlv.
  114. function! s:update_breakpoint(res) abort
  115. if type(a:res) ==# v:t_none
  116. return
  117. endif
  118. let state = a:res.result.State
  119. if !has_key(state, 'currentThread')
  120. return
  121. endif
  122. let s:state['currentThread'] = state.currentThread
  123. let bufs = filter(map(range(1, winnr('$')), '[v:val,bufname(winbufnr(v:val))]'), 'v:val[1]=~"\.go$"')
  124. if len(bufs) == 0
  125. return
  126. endif
  127. exe bufs[0][0] 'wincmd w'
  128. let filename = state.currentThread.file
  129. let linenr = state.currentThread.line
  130. let oldfile = fnamemodify(expand('%'), ':p:gs!\\!/!')
  131. if oldfile != filename
  132. silent! exe 'edit' filename
  133. endif
  134. silent! exe 'norm!' linenr.'G'
  135. silent! normal! zvzz
  136. silent! sign unplace 9999
  137. silent! exe 'sign place 9999 line=' . linenr . ' name=godebugcurline file=' . filename
  138. endfunction
  139. " Populate the stacktrace window.
  140. function! s:show_stacktrace(res) abort
  141. if !has_key(a:res, 'result')
  142. return
  143. endif
  144. let l:stack_win = bufwinnr(bufnr('__GODEBUG_STACKTRACE__'))
  145. if l:stack_win == -1
  146. return
  147. endif
  148. let l:cur_win = bufwinnr('')
  149. exe l:stack_win 'wincmd w'
  150. try
  151. setlocal modifiable
  152. silent %delete _
  153. for i in range(len(a:res.result.Locations))
  154. let loc = a:res.result.Locations[i]
  155. call setline(i+1, printf('%s - %s:%d', loc.function.name, fnamemodify(loc.file, ':p'), loc.line))
  156. endfor
  157. finally
  158. setlocal nomodifiable
  159. exe l:cur_win 'wincmd w'
  160. endtry
  161. endfunction
  162. " Populate the variable window.
  163. function! s:show_variables() abort
  164. let l:var_win = bufwinnr(bufnr('__GODEBUG_VARIABLES__'))
  165. if l:var_win == -1
  166. return
  167. endif
  168. let l:cur_win = bufwinnr('')
  169. exe l:var_win 'wincmd w'
  170. try
  171. setlocal modifiable
  172. silent %delete _
  173. let v = []
  174. let v += ['# Local Variables']
  175. if type(get(s:state, 'localVars', [])) is type([])
  176. for c in s:state['localVars']
  177. let v += split(s:eval_tree(c, 0), "\n")
  178. endfor
  179. endif
  180. let v += ['']
  181. let v += ['# Function Arguments']
  182. if type(get(s:state, 'functionArgs', [])) is type([])
  183. for c in s:state['functionArgs']
  184. let v += split(s:eval_tree(c, 0), "\n")
  185. endfor
  186. endif
  187. call setline(1, v)
  188. finally
  189. setlocal nomodifiable
  190. exe l:cur_win 'wincmd w'
  191. endtry
  192. endfunction
  193. function! s:clearState() abort
  194. let s:state['currentThread'] = {}
  195. let s:state['localVars'] = {}
  196. let s:state['functionArgs'] = {}
  197. let s:state['message'] = []
  198. silent! sign unplace 9999
  199. endfunction
  200. function! s:stop() abort
  201. call s:clearState()
  202. if has_key(s:state, 'job')
  203. call job_stop(s:state['job'])
  204. call remove(s:state, 'job')
  205. endif
  206. endfunction
  207. function! go#debug#Stop() abort
  208. " Remove signs.
  209. for k in keys(s:state['breakpoint'])
  210. let bt = s:state['breakpoint'][k]
  211. if bt.id >= 0
  212. silent exe 'sign unplace ' . bt.id
  213. endif
  214. endfor
  215. " Remove all commands and add back the default commands.
  216. for k in map(split(execute('command GoDebug'), "\n")[1:], 'matchstr(v:val, "^\\s*\\zs\\S\\+")')
  217. exe 'delcommand' k
  218. endfor
  219. command! -nargs=* -complete=customlist,go#package#Complete GoDebugStart call go#debug#Start(<f-args>)
  220. command! -nargs=? GoDebugBreakpoint call go#debug#Breakpoint(<f-args>)
  221. " Remove all mappings.
  222. for k in map(split(execute('map <Plug>(go-debug-'), "\n")[1:], 'matchstr(v:val, "^n\\s\\+\\zs\\S\\+")')
  223. exe 'unmap' k
  224. endfor
  225. call s:stop()
  226. let bufs = filter(map(range(1, winnr('$')), '[v:val,bufname(winbufnr(v:val))]'), 'v:val[1]=~"\.go$"')
  227. if len(bufs) > 0
  228. exe bufs[0][0] 'wincmd w'
  229. else
  230. wincmd p
  231. endif
  232. silent! exe bufwinnr(bufnr('__GODEBUG_STACKTRACE__')) 'wincmd c'
  233. silent! exe bufwinnr(bufnr('__GODEBUG_VARIABLES__')) 'wincmd c'
  234. silent! exe bufwinnr(bufnr('__GODEBUG_OUTPUT__')) 'wincmd c'
  235. set noballooneval
  236. set balloonexpr=
  237. endfunction
  238. function! s:goto_file() abort
  239. let m = matchlist(getline('.'), ' - \(.*\):\([0-9]\+\)$')
  240. if m[1] == ''
  241. return
  242. endif
  243. let bufs = filter(map(range(1, winnr('$')), '[v:val,bufname(winbufnr(v:val))]'), 'v:val[1]=~"\.go$"')
  244. if len(bufs) == 0
  245. return
  246. endif
  247. exe bufs[0][0] 'wincmd w'
  248. let filename = m[1]
  249. let linenr = m[2]
  250. let oldfile = fnamemodify(expand('%'), ':p:gs!\\!/!')
  251. if oldfile != filename
  252. silent! exe 'edit' filename
  253. endif
  254. silent! exe 'norm!' linenr.'G'
  255. silent! normal! zvzz
  256. endfunction
  257. function! s:delete_expands()
  258. let nr = line('.')
  259. while 1
  260. let l = getline(nr+1)
  261. if empty(l) || l =~ '^\S'
  262. return
  263. endif
  264. silent! exe (nr+1) . 'd _'
  265. endwhile
  266. silent! exe 'norm!' nr.'G'
  267. endfunction
  268. function! s:expand_var() abort
  269. " Get name from struct line.
  270. let name = matchstr(getline('.'), '^[^:]\+\ze: [a-zA-Z0-9\.·]\+{\.\.\.}$')
  271. " Anonymous struct
  272. if name == ''
  273. let name = matchstr(getline('.'), '^[^:]\+\ze: struct {.\{-}}$')
  274. endif
  275. if name != ''
  276. setlocal modifiable
  277. let not_open = getline(line('.')+1) !~ '^ '
  278. let l = line('.')
  279. call s:delete_expands()
  280. if not_open
  281. call append(l, split(s:eval(name), "\n")[1:])
  282. endif
  283. silent! exe 'norm!' l.'G'
  284. setlocal nomodifiable
  285. return
  286. endif
  287. " Expand maps
  288. let m = matchlist(getline('.'), '^[^:]\+\ze: map.\{-}\[\(\d\+\)\]$')
  289. if len(m) > 0 && m[1] != ''
  290. setlocal modifiable
  291. let not_open = getline(line('.')+1) !~ '^ '
  292. let l = line('.')
  293. call s:delete_expands()
  294. if not_open
  295. " TODO: Not sure how to do this yet... Need to get keys of the map.
  296. " let vs = ''
  297. " for i in range(0, min([10, m[1]-1]))
  298. " let vs .= ' ' . s:eval(printf("%s[%s]", m[0], ))
  299. " endfor
  300. " call append(l, split(vs, "\n"))
  301. endif
  302. silent! exe 'norm!' l.'G'
  303. setlocal nomodifiable
  304. return
  305. endif
  306. " Expand string.
  307. let m = matchlist(getline('.'), '^\([^:]\+\)\ze: \(string\)\[\([0-9]\+\)\]\(: .\{-}\)\?$')
  308. if len(m) > 0 && m[1] != ''
  309. setlocal modifiable
  310. let not_open = getline(line('.')+1) !~ '^ '
  311. let l = line('.')
  312. call s:delete_expands()
  313. if not_open
  314. let vs = ''
  315. for i in range(0, min([10, m[3]-1]))
  316. let vs .= ' ' . s:eval(m[1] . '[' . i . ']')
  317. endfor
  318. call append(l, split(vs, "\n"))
  319. endif
  320. silent! exe 'norm!' l.'G'
  321. setlocal nomodifiable
  322. return
  323. endif
  324. " Expand slice.
  325. let m = matchlist(getline('.'), '^\([^:]\+\)\ze: \(\[\]\w\{-}\)\[\([0-9]\+\)\]$')
  326. if len(m) > 0 && m[1] != ''
  327. setlocal modifiable
  328. let not_open = getline(line('.')+1) !~ '^ '
  329. let l = line('.')
  330. call s:delete_expands()
  331. if not_open
  332. let vs = ''
  333. for i in range(0, min([10, m[3]-1]))
  334. let vs .= ' ' . s:eval(m[1] . '[' . i . ']')
  335. endfor
  336. call append(l, split(vs, "\n"))
  337. endif
  338. silent! exe 'norm!' l.'G'
  339. setlocal nomodifiable
  340. return
  341. endif
  342. endfunction
  343. function! s:start_cb(ch, json) abort
  344. let res = json_decode(a:json)
  345. if type(res) == v:t_dict && has_key(res, 'error') && !empty(res.error)
  346. throw res.error
  347. endif
  348. if empty(res) || !has_key(res, 'result')
  349. return
  350. endif
  351. for bt in res.result.Breakpoints
  352. if bt.id >= 0
  353. let s:state['breakpoint'][bt.id] = bt
  354. exe 'sign place '. bt.id .' line=' . bt.line . ' name=godebugbreakpoint file=' . bt.file
  355. endif
  356. endfor
  357. let oldbuf = bufnr('%')
  358. silent! only!
  359. let winnum = bufwinnr(bufnr('__GODEBUG_STACKTRACE__'))
  360. if winnum != -1
  361. return
  362. endif
  363. if exists('g:go_debug_windows["stack"]') && g:go_debug_windows['stack'] != ''
  364. exe 'silent ' . g:go_debug_windows['stack']
  365. silent file `='__GODEBUG_STACKTRACE__'`
  366. setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
  367. setlocal filetype=godebugstacktrace
  368. nmap <buffer> <cr> :<c-u>call <SID>goto_file()<cr>
  369. nmap <buffer> q <Plug>(go-debug-stop)
  370. endif
  371. if exists('g:go_debug_windows["out"]') && g:go_debug_windows['out'] != ''
  372. exe 'silent ' . g:go_debug_windows['out']
  373. silent file `='__GODEBUG_OUTPUT__'`
  374. setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
  375. setlocal filetype=godebugoutput
  376. nmap <buffer> q <Plug>(go-debug-stop)
  377. endif
  378. if exists('g:go_debug_windows["vars"]') && g:go_debug_windows['vars'] != ''
  379. exe 'silent ' . g:go_debug_windows['vars']
  380. silent file `='__GODEBUG_VARIABLES__'`
  381. setlocal buftype=nofile bufhidden=wipe nomodified nobuflisted noswapfile nowrap nonumber nocursorline
  382. setlocal filetype=godebugvariables
  383. call append(0, ["# Local Variables", "", "# Function Arguments"])
  384. nmap <buffer> <silent> <cr> :<c-u>call <SID>expand_var()<cr>
  385. nmap <buffer> q <Plug>(go-debug-stop)
  386. endif
  387. silent! delcommand GoDebugStart
  388. command! -nargs=0 GoDebugContinue call go#debug#Stack('continue')
  389. command! -nargs=0 GoDebugNext call go#debug#Stack('next')
  390. command! -nargs=0 GoDebugStep call go#debug#Stack('step')
  391. command! -nargs=0 GoDebugStepOut call go#debug#Stack('stepOut')
  392. command! -nargs=0 GoDebugRestart call go#debug#Restart()
  393. command! -nargs=0 GoDebugStop call go#debug#Stop()
  394. command! -nargs=* GoDebugSet call go#debug#Set(<f-args>)
  395. command! -nargs=1 GoDebugPrint call go#debug#Print(<q-args>)
  396. nnoremap <silent> <Plug>(go-debug-breakpoint) :<C-u>call go#debug#Breakpoint()<CR>
  397. nnoremap <silent> <Plug>(go-debug-next) :<C-u>call go#debug#Stack('next')<CR>
  398. nnoremap <silent> <Plug>(go-debug-step) :<C-u>call go#debug#Stack('step')<CR>
  399. nnoremap <silent> <Plug>(go-debug-stepout) :<C-u>call go#debug#Stack('stepout')<CR>
  400. nnoremap <silent> <Plug>(go-debug-continue) :<C-u>call go#debug#Stack('continue')<CR>
  401. nnoremap <silent> <Plug>(go-debug-stop) :<C-u>call go#debug#Stop()<CR>
  402. nnoremap <silent> <Plug>(go-debug-print) :<C-u>call go#debug#Print(expand('<cword>'))<CR>
  403. nmap <F5> <Plug>(go-debug-continue)
  404. nmap <F6> <Plug>(go-debug-print)
  405. nmap <F9> <Plug>(go-debug-breakpoint)
  406. nmap <F10> <Plug>(go-debug-next)
  407. nmap <F11> <Plug>(go-debug-step)
  408. set balloonexpr=go#debug#BalloonExpr()
  409. set ballooneval
  410. exe bufwinnr(oldbuf) 'wincmd w'
  411. endfunction
  412. function! s:starting(ch, msg) abort
  413. call go#util#EchoProgress(a:msg)
  414. let s:state['message'] += [a:msg]
  415. if stridx(a:msg, g:go_debug_address) != -1
  416. call ch_setoptions(a:ch, {
  417. \ 'out_cb': function('s:logger', ['OUT: ']),
  418. \ 'err_cb': function('s:logger', ['ERR: ']),
  419. \})
  420. " Tell dlv about the breakpoints that the user added before delve started.
  421. let l:breaks = copy(s:state.breakpoint)
  422. let s:state['breakpoint'] = {}
  423. for l:bt in values(l:breaks)
  424. call go#debug#Breakpoint(bt.line)
  425. endfor
  426. call s:call_jsonrpc('RPCServer.ListBreakpoints', function('s:start_cb'))
  427. endif
  428. endfunction
  429. " Start the debug mode. The first argument is the package name to compile and
  430. " debug, anything else will be passed to the running program.
  431. function! go#debug#Start(...) abort
  432. if has('nvim')
  433. call go#util#EchoError('This feature only works in Vim for now; Neovim is not (yet) supported. Sorry :-(')
  434. return
  435. endif
  436. if !go#util#has_job()
  437. call go#util#EchoError('This feature requires Vim 8.0.0087 or newer with +job.')
  438. return
  439. endif
  440. " It's already running.
  441. if has_key(s:state, 'job') && job_status(s:state['job']) == 'run'
  442. return
  443. endif
  444. let s:start_args = a:000
  445. if go#util#HasDebug('debugger-state')
  446. let g:go_debug_diag = s:state
  447. endif
  448. let l:is_test = bufname('')[-8:] is# '_test.go'
  449. let dlv = go#path#CheckBinPath("dlv")
  450. if empty(dlv)
  451. return
  452. endif
  453. try
  454. if len(a:000) > 0
  455. let l:pkgname = a:1
  456. " Expand .; otherwise this won't work from a tmp dir.
  457. if l:pkgname[0] == '.'
  458. let l:pkgname = go#package#FromPath(getcwd()) . l:pkgname[1:]
  459. endif
  460. else
  461. let l:pkgname = go#package#FromPath(getcwd())
  462. endif
  463. let l:args = []
  464. if len(a:000) > 1
  465. let l:args = ['--'] + a:000[1:]
  466. endif
  467. let l:cmd = [
  468. \ dlv,
  469. \ (l:is_test ? 'test' : 'debug'),
  470. \ '--output', tempname(),
  471. \ '--headless',
  472. \ '--api-version', '2',
  473. \ '--log',
  474. \ '--listen', g:go_debug_address,
  475. \ '--accept-multiclient',
  476. \]
  477. if get(g:, 'go_build_tags', '') isnot ''
  478. let l:cmd += ['--build-flags', '--tags=' . g:go_build_tags]
  479. endif
  480. let l:cmd += l:args
  481. call go#util#EchoProgress('Starting GoDebug...')
  482. let s:state['message'] = []
  483. let s:state['job'] = job_start(l:cmd, {
  484. \ 'out_cb': function('s:starting'),
  485. \ 'err_cb': function('s:starting'),
  486. \ 'exit_cb': function('s:exit'),
  487. \ 'stoponexit': 'kill',
  488. \})
  489. catch
  490. call go#util#EchoError(v:exception)
  491. endtry
  492. endfunction
  493. " Translate a reflect kind constant to a human string.
  494. function! s:reflect_kind(k)
  495. " Kind constants from Go's reflect package.
  496. return [
  497. \ 'Invalid Kind',
  498. \ 'Bool',
  499. \ 'Int',
  500. \ 'Int8',
  501. \ 'Int16',
  502. \ 'Int32',
  503. \ 'Int64',
  504. \ 'Uint',
  505. \ 'Uint8',
  506. \ 'Uint16',
  507. \ 'Uint32',
  508. \ 'Uint64',
  509. \ 'Uintptr',
  510. \ 'Float32',
  511. \ 'Float64',
  512. \ 'Complex64',
  513. \ 'Complex128',
  514. \ 'Array',
  515. \ 'Chan',
  516. \ 'Func',
  517. \ 'Interface',
  518. \ 'Map',
  519. \ 'Ptr',
  520. \ 'Slice',
  521. \ 'String',
  522. \ 'Struct',
  523. \ 'UnsafePointer',
  524. \ ][a:k]
  525. endfunction
  526. function! s:eval_tree(var, nest) abort
  527. if a:var.name =~ '^\~'
  528. return ''
  529. endif
  530. let nest = a:nest
  531. let v = ''
  532. let kind = s:reflect_kind(a:var.kind)
  533. if !empty(a:var.name)
  534. let v .= repeat(' ', nest) . a:var.name . ': '
  535. if kind == 'Bool'
  536. let v .= printf("%s\n", a:var.value)
  537. elseif kind == 'Struct'
  538. " Anonymous struct
  539. if a:var.type[:8] == 'struct { '
  540. let v .= printf("%s\n", a:var.type)
  541. else
  542. let v .= printf("%s{...}\n", a:var.type)
  543. endif
  544. elseif kind == 'String'
  545. let v .= printf("%s[%d]%s\n", a:var.type, a:var.len,
  546. \ len(a:var.value) > 0 ? ': ' . a:var.value : '')
  547. elseif kind == 'Slice' || kind == 'String' || kind == 'Map' || kind == 'Array'
  548. let v .= printf("%s[%d]\n", a:var.type, a:var.len)
  549. elseif kind == 'Chan' || kind == 'Func' || kind == 'Interface'
  550. let v .= printf("%s\n", a:var.type)
  551. elseif kind == 'Ptr'
  552. " TODO: We can do something more useful here.
  553. let v .= printf("%s\n", a:var.type)
  554. elseif kind == 'Complex64' || kind == 'Complex128'
  555. let v .= printf("%s%s\n", a:var.type, a:var.value)
  556. " Int, Float
  557. else
  558. let v .= printf("%s(%s)\n", a:var.type, a:var.value)
  559. endif
  560. else
  561. let nest -= 1
  562. endif
  563. if index(['Chan', 'Complex64', 'Complex128'], kind) == -1 && a:var.type != 'error'
  564. for c in a:var.children
  565. let v .= s:eval_tree(c, nest+1)
  566. endfor
  567. endif
  568. return v
  569. endfunction
  570. function! s:eval(arg) abort
  571. try
  572. let res = s:call_jsonrpc('RPCServer.State')
  573. let goroutineID = res.result.State.currentThread.goroutineID
  574. let res = s:call_jsonrpc('RPCServer.Eval', {
  575. \ 'expr': a:arg,
  576. \ 'scope': {'GoroutineID': goroutineID}
  577. \ })
  578. return s:eval_tree(res.result.Variable, 0)
  579. catch
  580. call go#util#EchoError(v:exception)
  581. return ''
  582. endtry
  583. endfunction
  584. function! go#debug#BalloonExpr() abort
  585. silent! let l:v = s:eval(v:beval_text)
  586. return l:v
  587. endfunction
  588. function! go#debug#Print(arg) abort
  589. try
  590. echo substitute(s:eval(a:arg), "\n$", "", 0)
  591. catch
  592. call go#util#EchoError(v:exception)
  593. endtry
  594. endfunction
  595. function! s:update_variables() abort
  596. " FollowPointers requests pointers to be automatically dereferenced.
  597. " MaxVariableRecurse is how far to recurse when evaluating nested types.
  598. " MaxStringLen is the maximum number of bytes read from a string
  599. " MaxArrayValues is the maximum number of elements read from an array, a slice or a map.
  600. " MaxStructFields is the maximum number of fields read from a struct, -1 will read all fields.
  601. let l:cfg = {
  602. \ 'scope': {'GoroutineID': s:groutineID()},
  603. \ 'cfg': {'MaxStringLen': 20, 'MaxArrayValues': 20}
  604. \ }
  605. try
  606. let res = s:call_jsonrpc('RPCServer.ListLocalVars', l:cfg)
  607. let s:state['localVars'] = res.result['Variables']
  608. catch
  609. call go#util#EchoError(v:exception)
  610. endtry
  611. try
  612. let res = s:call_jsonrpc('RPCServer.ListFunctionArgs', l:cfg)
  613. let s:state['functionArgs'] = res.result['Args']
  614. catch
  615. call go#util#EchoError(v:exception)
  616. endtry
  617. call s:show_variables()
  618. endfunction
  619. function! go#debug#Set(symbol, value) abort
  620. try
  621. let res = s:call_jsonrpc('RPCServer.State')
  622. let goroutineID = res.result.State.currentThread.goroutineID
  623. call s:call_jsonrpc('RPCServer.Set', {
  624. \ 'symbol': a:symbol,
  625. \ 'value': a:value,
  626. \ 'scope': {'GoroutineID': goroutineID}
  627. \ })
  628. catch
  629. call go#util#EchoError(v:exception)
  630. endtry
  631. call s:update_variables()
  632. endfunction
  633. function! s:update_stacktrace() abort
  634. try
  635. let res = s:call_jsonrpc('RPCServer.Stacktrace', {'id': s:groutineID(), 'depth': 5})
  636. call s:show_stacktrace(res)
  637. catch
  638. call go#util#EchoError(v:exception)
  639. endtry
  640. endfunction
  641. function! s:stack_cb(ch, json) abort
  642. let s:stack_name = ''
  643. let res = json_decode(a:json)
  644. if type(res) == v:t_dict && has_key(res, 'error') && !empty(res.error)
  645. call go#util#EchoError(res.error)
  646. call s:clearState()
  647. call go#debug#Restart()
  648. return
  649. endif
  650. if empty(res) || !has_key(res, 'result')
  651. return
  652. endif
  653. call s:update_breakpoint(res)
  654. call s:update_stacktrace()
  655. call s:update_variables()
  656. endfunction
  657. " Send a command to change the cursor location to Delve.
  658. "
  659. " a:name must be one of continue, next, step, or stepOut.
  660. function! go#debug#Stack(name) abort
  661. let l:name = a:name
  662. " Run continue if the program hasn't started yet.
  663. if s:state.running is 0
  664. let s:state.running = 1
  665. let l:name = 'continue'
  666. endif
  667. " Add a breakpoint to the main.Main if the user didn't define any.
  668. if len(s:state['breakpoint']) is 0
  669. try
  670. let res = s:call_jsonrpc('RPCServer.FindLocation', {'loc': 'main.main'})
  671. let res = s:call_jsonrpc('RPCServer.CreateBreakpoint', {'Breakpoint': {'addr': res.result.Locations[0].pc}})
  672. let bt = res.result.Breakpoint
  673. let s:state['breakpoint'][bt.id] = bt
  674. catch
  675. call go#util#EchoError(v:exception)
  676. endtry
  677. endif
  678. try
  679. " TODO: document why this is needed.
  680. if l:name is# 'next' && get(s:, 'stack_name', '') is# 'next'
  681. call s:call_jsonrpc('RPCServer.CancelNext')
  682. endif
  683. let s:stack_name = l:name
  684. call s:call_jsonrpc('RPCServer.Command', function('s:stack_cb'), {'name': l:name})
  685. catch
  686. call go#util#EchoError(v:exception)
  687. endtry
  688. endfunction
  689. function! go#debug#Restart() abort
  690. try
  691. call job_stop(s:state['job'])
  692. while has_key(s:state, 'job') && job_status(s:state['job']) is# 'run'
  693. sleep 50m
  694. endwhile
  695. let l:breaks = s:state['breakpoint']
  696. let s:state = {
  697. \ 'rpcid': 1,
  698. \ 'running': 0,
  699. \ 'breakpoint': {},
  700. \ 'currentThread': {},
  701. \ 'localVars': {},
  702. \ 'functionArgs': {},
  703. \ 'message': [],
  704. \}
  705. " Preserve breakpoints.
  706. for bt in values(l:breaks)
  707. " TODO: should use correct filename
  708. exe 'sign unplace '. bt.id .' file=' . bt.file
  709. call go#debug#Breakpoint(bt.line)
  710. endfor
  711. call call('go#debug#Start', s:start_args)
  712. catch
  713. call go#util#EchoError(v:exception)
  714. endtry
  715. endfunction
  716. " Report if debugger mode is active.
  717. function! s:isActive()
  718. return len(s:state['message']) > 0
  719. endfunction
  720. " Toggle breakpoint.
  721. function! go#debug#Breakpoint(...) abort
  722. let l:filename = fnamemodify(expand('%'), ':p:gs!\\!/!')
  723. " Get line number from argument.
  724. if len(a:000) > 0
  725. let linenr = str2nr(a:1)
  726. if linenr is 0
  727. call go#util#EchoError('not a number: ' . a:1)
  728. return
  729. endif
  730. else
  731. let linenr = line('.')
  732. endif
  733. try
  734. " Check if we already have a breakpoint for this line.
  735. let found = v:none
  736. for k in keys(s:state.breakpoint)
  737. let bt = s:state.breakpoint[k]
  738. if bt.file == l:filename && bt.line == linenr
  739. let found = bt
  740. break
  741. endif
  742. endfor
  743. " Remove breakpoint.
  744. if type(found) == v:t_dict
  745. call remove(s:state['breakpoint'], bt.id)
  746. exe 'sign unplace '. found.id .' file=' . found.file
  747. if s:isActive()
  748. let res = s:call_jsonrpc('RPCServer.ClearBreakpoint', {'id': found.id})
  749. endif
  750. " Add breakpoint.
  751. else
  752. if s:isActive()
  753. let res = s:call_jsonrpc('RPCServer.CreateBreakpoint', {'Breakpoint': {'file': l:filename, 'line': linenr}})
  754. let bt = res.result.Breakpoint
  755. exe 'sign place '. bt.id .' line=' . bt.line . ' name=godebugbreakpoint file=' . bt.file
  756. let s:state['breakpoint'][bt.id] = bt
  757. else
  758. let id = len(s:state['breakpoint']) + 1
  759. let s:state['breakpoint'][id] = {'id': id, 'file': l:filename, 'line': linenr}
  760. exe 'sign place '. id .' line=' . linenr . ' name=godebugbreakpoint file=' . l:filename
  761. endif
  762. endif
  763. catch
  764. call go#util#EchoError(v:exception)
  765. endtry
  766. endfunction
  767. sign define godebugbreakpoint text=> texthl=GoDebugBreakpoint
  768. sign define godebugcurline text== linehl=GoDebugCurrent texthl=GoDebugCurrent
  769. fun! s:hi()
  770. hi GoDebugBreakpoint term=standout ctermbg=117 ctermfg=0 guibg=#BAD4F5 guifg=Black
  771. hi GoDebugCurrent term=reverse ctermbg=12 ctermfg=7 guibg=DarkBlue guifg=White
  772. endfun
  773. augroup vim-go-breakpoint
  774. autocmd!
  775. autocmd ColorScheme * call s:hi()
  776. augroup end
  777. call s:hi()
  778. " vim: sw=2 ts=2 et