neovim_gdb.vim 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. sign define GdbBreakpoint text=●
  2. sign define GdbCurrentLine text=⇒
  3. let s:gdb_port = 7778
  4. let s:run_gdb = "gdb -q -f build/bin/nvim"
  5. let s:breakpoints = {}
  6. let s:max_breakpoint_sign_id = 0
  7. let s:GdbServer = {}
  8. function s:GdbServer.new(gdb)
  9. let this = copy(self)
  10. let this._gdb = a:gdb
  11. return this
  12. endfunction
  13. function s:GdbServer.on_exit()
  14. let self._gdb._server_exited = 1
  15. endfunction
  16. let s:GdbPaused = vimexpect#State([
  17. \ ['Continuing.', 'continue'],
  18. \ ['\v[\o32]{2}([^:]+):(\d+):\d+', 'jump'],
  19. \ ['Remote communication error. Target disconnected.:', 'retry'],
  20. \ ])
  21. function s:GdbPaused.continue(...)
  22. call self._parser.switch(s:GdbRunning)
  23. call self.update_current_line_sign(0)
  24. endfunction
  25. function s:GdbPaused.jump(file, line, ...)
  26. if tabpagenr() != self._tab
  27. " Don't jump if we are not in the debugger tab
  28. return
  29. endif
  30. let window = winnr()
  31. exe self._jump_window 'wincmd w'
  32. let self._current_buf = bufnr('%')
  33. let target_buf = bufnr(a:file, 1)
  34. if bufnr('%') != target_buf
  35. exe 'buffer ' target_buf
  36. let self._current_buf = target_buf
  37. endif
  38. exe ':' a:line
  39. let self._current_line = a:line
  40. exe window 'wincmd w'
  41. call self.update_current_line_sign(1)
  42. endfunction
  43. function s:GdbPaused.retry(...)
  44. if self._server_exited
  45. return
  46. endif
  47. sleep 1
  48. call self.attach()
  49. call self.send('continue')
  50. endfunction
  51. let s:GdbRunning = vimexpect#State([
  52. \ ['\v^Breakpoint \d+', 'pause'],
  53. \ ['\v\[Inferior\ +.{-}\ +exited\ +normally', 'disconnected'],
  54. \ ['(gdb)', 'pause'],
  55. \ ])
  56. function s:GdbRunning.pause(...)
  57. call self._parser.switch(s:GdbPaused)
  58. if !self._initialized
  59. call self.send('set confirm off')
  60. call self.send('set pagination off')
  61. if !empty(self._server_addr)
  62. call self.send('set remotetimeout 50')
  63. call self.attach()
  64. call s:RefreshBreakpoints()
  65. call self.send('c')
  66. endif
  67. let self._initialized = 1
  68. endif
  69. endfunction
  70. function s:GdbRunning.disconnected(...)
  71. if !self._server_exited && self._reconnect
  72. " Refresh to force a delete of all watchpoints
  73. call s:RefreshBreakpoints()
  74. sleep 1
  75. call self.attach()
  76. call self.send('continue')
  77. endif
  78. endfunction
  79. let s:Gdb = {}
  80. function s:Gdb.kill()
  81. tunmap <f8>
  82. tunmap <f10>
  83. tunmap <f11>
  84. tunmap <f12>
  85. call self.update_current_line_sign(0)
  86. exe 'bd! '.self._client_buf
  87. if self._server_buf != -1
  88. exe 'bd! '.self._server_buf
  89. endif
  90. exe 'tabnext '.self._tab
  91. tabclose
  92. unlet g:gdb
  93. endfunction
  94. function! s:Gdb.send(data)
  95. call jobsend(self._client_id, a:data."\<cr>")
  96. endfunction
  97. function! s:Gdb.attach()
  98. call self.send(printf('target remote %s', self._server_addr))
  99. endfunction
  100. function! s:Gdb.update_current_line_sign(add)
  101. " to avoid flicker when removing/adding the sign column(due to the change in
  102. " line width), we switch ids for the line sign and only remove the old line
  103. " sign after marking the new one
  104. let old_line_sign_id = get(self, '_line_sign_id', 4999)
  105. let self._line_sign_id = old_line_sign_id == 4999 ? 4998 : 4999
  106. if a:add && self._current_line != -1 && self._current_buf != -1
  107. exe 'sign place '.self._line_sign_id.' name=GdbCurrentLine line='
  108. \.self._current_line.' buffer='.self._current_buf
  109. endif
  110. exe 'sign unplace '.old_line_sign_id
  111. endfunction
  112. function! s:Spawn(server_cmd, client_cmd, server_addr, reconnect)
  113. if exists('g:gdb')
  114. throw 'Gdb already running'
  115. endif
  116. let gdb = vimexpect#Parser(s:GdbRunning, copy(s:Gdb))
  117. " gdbserver port
  118. let gdb._server_addr = a:server_addr
  119. let gdb._reconnect = a:reconnect
  120. let gdb._initialized = 0
  121. " window number that will be displaying the current file
  122. let gdb._jump_window = 1
  123. let gdb._current_buf = -1
  124. let gdb._current_line = -1
  125. let gdb._has_breakpoints = 0
  126. let gdb._server_exited = 0
  127. " Create new tab for the debugging view
  128. tabnew
  129. let gdb._tab = tabpagenr()
  130. " create horizontal split to display the current file and maybe gdbserver
  131. sp
  132. let gdb._server_buf = -1
  133. if type(a:server_cmd) == type('')
  134. " spawn gdbserver in a vertical split
  135. let server = s:GdbServer.new(gdb)
  136. server.term = v:true
  137. vsp | enew | let gdb._server_id = jobstart(a:server_cmd, server)
  138. let gdb._jump_window = 2
  139. let gdb._server_buf = bufnr('%')
  140. endif
  141. " go to the bottom window and spawn gdb client
  142. wincmd j
  143. gdb.term = v:true
  144. enew | let gdb._client_id = jobstart(a:client_cmd, gdb)
  145. let gdb._client_buf = bufnr('%')
  146. tnoremap <silent> <f8> <c-\><c-n>:GdbContinue<cr>i
  147. tnoremap <silent> <f10> <c-\><c-n>:GdbNext<cr>i
  148. tnoremap <silent> <f11> <c-\><c-n>:GdbStep<cr>i
  149. tnoremap <silent> <f12> <c-\><c-n>:GdbFinish<cr>i
  150. " go to the window that displays the current file
  151. exe gdb._jump_window 'wincmd w'
  152. let g:gdb = gdb
  153. endfunction
  154. function! s:Test(bang, filter)
  155. let cmd = "GDB=1 make test"
  156. if a:bang == '!'
  157. let server_addr = '| vgdb'
  158. let cmd = printf('VALGRIND=1 %s', cmd)
  159. else
  160. let server_addr = printf('localhost:%d', s:gdb_port)
  161. let cmd = printf('GDBSERVER_PORT=%d %s', s:gdb_port, cmd)
  162. endif
  163. if a:filter != ''
  164. let cmd = printf('TEST_SCREEN_TIMEOUT=1000000 TEST_FILTER="%s" %s', a:filter, cmd)
  165. endif
  166. call s:Spawn(cmd, s:run_gdb, server_addr, 1)
  167. endfunction
  168. function! s:ToggleBreak()
  169. let file_name = bufname('%')
  170. let file_breakpoints = get(s:breakpoints, file_name, {})
  171. let linenr = line('.')
  172. if has_key(file_breakpoints, linenr)
  173. call remove(file_breakpoints, linenr)
  174. else
  175. let file_breakpoints[linenr] = 1
  176. endif
  177. let s:breakpoints[file_name] = file_breakpoints
  178. call s:RefreshBreakpointSigns()
  179. call s:RefreshBreakpoints()
  180. endfunction
  181. function! s:ClearBreak()
  182. let s:breakpoints = {}
  183. call s:RefreshBreakpointSigns()
  184. call s:RefreshBreakpoints()
  185. endfunction
  186. function! s:RefreshBreakpointSigns()
  187. let buf = bufnr('%')
  188. let i = 5000
  189. while i <= s:max_breakpoint_sign_id
  190. exe 'sign unplace '.i
  191. let i += 1
  192. endwhile
  193. let s:max_breakpoint_sign_id = 0
  194. let id = 5000
  195. for linenr in keys(get(s:breakpoints, bufname('%'), {}))
  196. exe 'sign place '.id.' name=GdbBreakpoint line='.linenr.' buffer='.buf
  197. let s:max_breakpoint_sign_id = id
  198. let id += 1
  199. endfor
  200. endfunction
  201. function! s:RefreshBreakpoints()
  202. if !exists('g:gdb')
  203. return
  204. endif
  205. if g:gdb._parser.state() == s:GdbRunning
  206. " pause first
  207. call jobsend(g:gdb._client_id, "\<c-c>")
  208. endif
  209. if g:gdb._has_breakpoints
  210. call g:gdb.send('delete')
  211. endif
  212. let g:gdb._has_breakpoints = 0
  213. for [file, breakpoints] in items(s:breakpoints)
  214. for linenr in keys(breakpoints)
  215. let g:gdb._has_breakpoints = 1
  216. call g:gdb.send('break '.file.':'.linenr)
  217. endfor
  218. endfor
  219. endfunction
  220. function! s:GetExpression(...) range
  221. let [lnum1, col1] = getpos("'<")[1:2]
  222. let [lnum2, col2] = getpos("'>")[1:2]
  223. let lines = getline(lnum1, lnum2)
  224. let lines[-1] = lines[-1][:col2 - 1]
  225. let lines[0] = lines[0][col1 - 1:]
  226. return join(lines, "\n")
  227. endfunction
  228. function! s:Send(data)
  229. if !exists('g:gdb')
  230. throw 'Gdb is not running'
  231. endif
  232. call g:gdb.send(a:data)
  233. endfunction
  234. function! s:Eval(expr)
  235. call s:Send(printf('print %s', a:expr))
  236. endfunction
  237. function! s:Watch(expr)
  238. let expr = a:expr
  239. if expr[0] != '&'
  240. let expr = '&' . expr
  241. endif
  242. call s:Eval(expr)
  243. call s:Send('watch *$')
  244. endfunction
  245. function! s:Interrupt()
  246. if !exists('g:gdb')
  247. throw 'Gdb is not running'
  248. endif
  249. call jobsend(g:gdb._client_id, "\<c-c>info line\<cr>")
  250. endfunction
  251. function! s:Kill()
  252. if !exists('g:gdb')
  253. throw 'Gdb is not running'
  254. endif
  255. call g:gdb.kill()
  256. endfunction
  257. command! GdbDebugNvim call s:Spawn(printf('make && gdbserver localhost:%d build/bin/nvim', s:gdb_port), s:run_gdb, printf('localhost:%d', s:gdb_port), 0)
  258. command! -nargs=1 GdbDebugServer call s:Spawn(0, s:run_gdb, 'localhost:'.<q-args>, 0)
  259. command! -bang -nargs=? GdbDebugTest call s:Test(<q-bang>, <q-args>)
  260. command! -nargs=1 -complete=file GdbInspectCore call s:Spawn(0, printf('gdb -q -f -c %s build/bin/nvim', <q-args>), 0, 0)
  261. command! GdbDebugStop call s:Kill()
  262. command! GdbToggleBreakpoint call s:ToggleBreak()
  263. command! GdbClearBreakpoints call s:ClearBreak()
  264. command! GdbContinue call s:Send("c")
  265. command! GdbNext call s:Send("n")
  266. command! GdbStep call s:Send("s")
  267. command! GdbFinish call s:Send("finish")
  268. command! GdbFrameUp call s:Send("up")
  269. command! GdbFrameDown call s:Send("down")
  270. command! GdbInterrupt call s:Interrupt()
  271. command! GdbEvalWord call s:Eval(expand('<cword>'))
  272. command! -range GdbEvalRange call s:Eval(s:GetExpression(<f-args>))
  273. command! GdbWatchWord call s:Watch(expand('<cword>')
  274. command! -range GdbWatchRange call s:Watch(s:GetExpression(<f-args>))
  275. nnoremap <silent> <f8> :GdbContinue<cr>
  276. nnoremap <silent> <f10> :GdbNext<cr>
  277. nnoremap <silent> <f11> :GdbStep<cr>
  278. nnoremap <silent> <f12> :GdbFinish<cr>
  279. nnoremap <silent> <c-b> :GdbToggleBreakpoint<cr>
  280. nnoremap <silent> <m-pageup> :GdbFrameUp<cr>
  281. nnoremap <silent> <m-pagedown> :GdbFrameDown<cr>
  282. nnoremap <silent> <f9> :GdbEvalWord<cr>
  283. vnoremap <silent> <f9> :GdbEvalRange<cr>
  284. nnoremap <silent> <m-f9> :GdbWatchWord<cr>
  285. vnoremap <silent> <m-f9> :GdbWatchRange<cr>