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