neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

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>