├── ftplugin ├── go.vim └── c.vim ├── autoload ├── debugger_term.vim ├── window.vim └── debugger_util.vim ├── README.md └── plugin └── neovim_gdb.vim /ftplugin/go.vim: -------------------------------------------------------------------------------- 1 | command! -buffer -bang -nargs=? -complete=file GoDebug call GoDlvDebug() 2 | -------------------------------------------------------------------------------- /autoload/debugger_term.vim: -------------------------------------------------------------------------------- 1 | function! debugger_term#Send(data) 2 | if !exists('g:gdb') 3 | throw 'Gdb is not running' 4 | endif 5 | call g:gdb.send(a:data) 6 | endfunction 7 | 8 | function! debugger_term#SendRaw(data) 9 | if !exists('g:gdb') 10 | throw 'Gdb is not running' 11 | endif 12 | call g:gdb.sendRaw(a:data) 13 | endfunction 14 | 15 | -------------------------------------------------------------------------------- /autoload/window.vim: -------------------------------------------------------------------------------- 1 | function! window#CreateGdbWin() 2 | botright new | res 12 | setl winfixheight 3 | endfunction 4 | 5 | function! window#GetGdbWin() 6 | if !exists('g:gdb') | return | endif 7 | if g:gdb._client_buf >= 0 8 | let buflist = tabpagebuflist() 9 | if index(buflist, g:gdb._client_buf) >= 0 | return | endif 10 | call window#CreateGdbWin() 11 | execute('silent buffer +set\ nornu ' . g:gdb._client_buf) 12 | execute('wincmd w | stopinsert') 13 | endif 14 | endfunction 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Demo 2 | 3 | [![screencast](https://asciinema.org/a/dT2652AAwegDo0o0gWKsGOo1W.png)](https://asciinema.org/a/dT2652AAwegDo0o0gWKsGOo1W) 4 | 5 | # What is NeovimGdb 6 | Integrate vim and gdb, it could help you: 7 | 8 | * View code in vim and run gdb command in a separated vim window 9 | * Login to remote manchine and run gdb 10 | 11 | Now, also suppport dlv to debug go files 12 | 13 | # Usage 14 | ## Debug in remote server 15 | * Use `GdbConnect host` to connect to remote server, this will `ssh host` or `docker exec -it host` (if the first item in g:nvimgdb_host_cmd is `Docker`) first, then run the following command in `g:nvimgdb_host_cmd `. 16 | 17 | ```vim 18 | let g:nvimgdb_host_cmd = { 19 | \ 'dr01' : ['cads', 'gdb -f ./ads'], 20 | \ 'ts' : ['Docker', 'gdb -q -f --pid `pgrep transcoding`'], 21 | \ } 22 | ``` 23 | 24 | -------------------------------------------------------------------------------- /autoload/debugger_util.vim: -------------------------------------------------------------------------------- 1 | function! debugger_util#Eval(expr) 2 | call debugger_term#Send(printf('print %s', a:expr)) 3 | endfunction 4 | 5 | function! debugger_util#GetLocalFilePath(file) 6 | let paths = split(split(a:file, ':')[0], '/') 7 | 8 | let file_path = "" 9 | for i in range(-1, -len(paths), -1) 10 | let search_pattern = "**/" . join(paths[i:], '/') 11 | let res = split(globpath(getcwd(), search_pattern)) 12 | if len(res) == 0 13 | return file_path 14 | endif 15 | 16 | if len(res) == 1 17 | let file_path = res[0] 18 | break 19 | endif 20 | endfor 21 | 22 | if !empty(file_path) 23 | let file_path = fnamemodify(file_path, ":~:.") 24 | endif 25 | 26 | return file_path 27 | endfunction 28 | 29 | function! debugger_util#GetCppCword() 30 | let save_keyword = &iskeyword 31 | set iskeyword+=.,-,>,: 32 | let cword = expand('') 33 | let &iskeyword = save_keyword 34 | return cword 35 | endfunction 36 | 37 | function! debugger_util#GoCurrentLine() 38 | if !exists('g:gdb') 39 | return 40 | endif 41 | execute(":buffer " . g:gdb._current_buf) 42 | execute(":" . g:gdb._current_line) 43 | endfunction 44 | 45 | function! debugger_util#DebuggerMapping(load) 46 | if ! exists('g:vim_debugger_mapping') 47 | return 48 | endif 49 | 50 | for k in keys(g:vim_debugger_mapping) 51 | if a:load 52 | execute(printf(':nnoremap %s :call debugger_term#Send("%s")', k, g:vim_debugger_mapping[k])) 53 | else 54 | execute(printf(':unmap %s', k)) 55 | endif 56 | endfor 57 | endfunction 58 | -------------------------------------------------------------------------------- /ftplugin/c.vim: -------------------------------------------------------------------------------- 1 | if exists('b:loaded_onekeycompile') || exists('g:loaded_onekeycompile') 2 | finish 3 | endif 4 | 5 | let b:loaded_onekeycompile = 1 6 | 7 | let b:binary_filename = expand('%:p:r') . '.bin' 8 | let b:makeprg = {} 9 | let b:makeprg.c = "clang -std=c99 -Wall % -o " . b:binary_filename 10 | let b:makeprg.cpp = "g++ -Wall -std=c++20 % -o " . b:binary_filename 11 | let b:makeprg.gdb = "g++ -Wall -g -O0 -std=c++20 % -o " . b:binary_filename 12 | let b:makeprg.gtest = "g++ -Wall -lgmock_main -lgmock -g -O0 -std=c++20 % -o " . b:binary_filename 13 | 14 | function! s:ErrReturn(msg) 15 | echohl ErrorMsg | echom a:msg | echohl None 16 | return -1 17 | endfunction 18 | 19 | function! s:NormalReturn(msg) 20 | echohl WarningMsg | echo a:msg | echohl None 21 | return 0 22 | endfunction 23 | 24 | function! CompileCCpp(gdb_flag, msg) "{{{ 25 | if &modified | w | endif 26 | let b:syntastic_mode = 'active' 27 | 28 | if filereadable(b:binary_filename) && delete(b:binary_filename) 29 | set makeprg=make 30 | return s:ErrReturn("Delete " . b:binary_filename . " failed") 31 | endif 32 | 33 | if a:gdb_flag == 1 34 | let &makeprg = b:makeprg['gdb'] 35 | elseif a:gdb_flag == 2 36 | let &makeprg = b:makeprg['gtest'] 37 | else 38 | let &makeprg = b:makeprg[&filetype] 39 | endif 40 | execute "silent make!" 41 | 42 | if !filereadable(b:binary_filename) 43 | execute "copen" 44 | return -3 45 | endif 46 | 47 | if !empty(a:msg) 48 | return s:NormalReturn(a:msg) 49 | else 50 | return 0 51 | endif 52 | endfunction "}}} 53 | 54 | function! NormalRun(flag) "{{{ 55 | if CompileCCpp(a:flag, "") < 0 | return | endif 56 | let binary = b:binary_filename 57 | botright new | res 12 58 | let cmd = binary ." ; errcode=$?; if [[ $errcode -gt 128 ]]; then echo \"System Error! Errno: $errcode\"; fi" 59 | call termopen(cmd) 60 | startinsert 61 | endfunction "}}} 62 | 63 | function! DebugRun() "{{{ 64 | if CompileCCpp(1, "") < 0 | return | endif 65 | 66 | if has('nvim') 67 | silent execute "GdbStart " . b:binary_filename 68 | endif 69 | endfunction "}}} 70 | 71 | nmap ,rr :call NormalRun(0) 72 | map ,rd :call DebugRun() 73 | command! -nargs=0 -buffer GdbLocal call DebugRun() 74 | map ,rt :call NormalRun(2) 75 | 76 | command! -buffer M call CompileCCpp(0, "Build succesfully!") 77 | command! -buffer MD call CompileCCpp(1, "Build in Debug mode succesfully!") 78 | nmap ,mk :M 79 | 80 | " For TopCoder plugin VimCoder 81 | nmap ,op ,cd:!xdg-open Problem.html:redraw! 82 | 83 | " vim:fdm=marker: 84 | -------------------------------------------------------------------------------- /plugin/neovim_gdb.vim: -------------------------------------------------------------------------------- 1 | if !exists('g:nvimgdb_host_cmd') 2 | let g:nvimgdb_host_cmd = {} 3 | endif 4 | 5 | if !has('nvim') | finish | endif 6 | 7 | sign define GdbBreakpoint text=● 8 | sign define GdbCurrentLine text=⇒ 9 | 10 | let s:breakpoints = {} 11 | let s:max_breakpoint_sign_id = 5000 12 | 13 | let s:GdbServer = {} 14 | 15 | function s:GdbServer.new(gdb) 16 | let this = copy(self) 17 | let this._gdb = a:gdb 18 | return this 19 | endfunction 20 | 21 | function s:GdbServer.on_exit() 22 | let self._gdb._server_exited = 1 23 | endfunction 24 | 25 | let s:GdbPaused = vimexpect#State([ 26 | \ ['\v[\o32]{2}(\f+):(\d+):\d+', 'jump'], 27 | \ ['\v^(\f+):(\d+):\d+', 'jump'], 28 | \ ['\v^\> \S+ (\f+):(\d+)', 'jump'], 29 | \ ['Continuing.', 'continue'], 30 | \ ['Starting program', 'continue'], 31 | \ ['\v^Breakpoint (\d+) at 0[xX]\x+: file ([^,]+), line (\d+)', 'mybreak'], 32 | \ ['\v^Breakpoint (\d+) at 0[xX]\x+: ([^:]+):(\d+)', 'mybreak'], 33 | \ ['\v^Breakpoint (\d+) set at 0[xX]\x+ for \S+ ([^:]+):(\d+)', 'mybreak'], 34 | \ ]) 35 | 36 | let s:GdbRunning = vimexpect#State([ 37 | \ ['\v^Breakpoint \d+,', 'pause'], 38 | \ ['\vhit Breakpoint \d+, ', 'pause'], 39 | \ ['\v^Temporary breakpoint \d+,', 'pause'], 40 | \ ['\vhit Hardware ', 'pause'], 41 | \ ['(gdb)', 'pause'], 42 | \ ['gdb\$', 'pause'], 43 | \ ['(dlv)', 'pause'], 44 | \ ['\v\[Inferior\ +.{-}\ +exited\ +normally', 'disconnected'], 45 | \ ]) 46 | 47 | function s:GdbPaused.continue(...) 48 | call self._parser.switch(s:GdbRunning) 49 | call self.update_current_line_sign(0) 50 | endfunction 51 | 52 | function s:GdbPaused.jump(file, line, ...) 53 | let file = a:file 54 | if !empty(g:gdb._server_addr) 55 | let file = debugger_util#GetLocalFilePath(file) 56 | endif 57 | 58 | if empty(file) | return -1 | endif 59 | 60 | let window = winnr() 61 | exe self._jump_window 'wincmd w' 62 | let self._current_buf = bufnr('%') 63 | let target_buf = bufnr(file, 1) 64 | if bufnr('%') != target_buf 65 | exe 'buffer ' target_buf 66 | let self._current_buf = target_buf 67 | endif 68 | exe ':' a:line 69 | let self._current_line = a:line 70 | exe window 'wincmd w' 71 | call self.update_current_line_sign(1) 72 | endfunction 73 | 74 | function! ToggleBreakpoint() 75 | if !exists('g:gdb') | return | endif 76 | 77 | let file_breakpoints = get(s:breakpoints, bufname('%'), {}) 78 | 79 | let linenr = line('.') 80 | if has_key(file_breakpoints, linenr) 81 | call debugger_term#Send("delete " . file_breakpoints[linenr]['brknum']) 82 | call remove(file_breakpoints, linenr) 83 | exe "sign unplace" 84 | return 85 | endif 86 | 87 | if g:gdb._parser.state() == s:GdbRunning 88 | call jobsend(g:gdb._client_id, "\") 89 | sleep 200m 90 | endif 91 | 92 | let trimed_filename = expand('%:p:h:t') .'/'. expand('%:t') 93 | call debugger_term#Send(printf('break %s:%d ', trimed_filename, line('.'))) 94 | endfunction 95 | 96 | function! s:GdbPaused.mybreak(brknum, filename, linenr, ...) 97 | execute("1wincmd w") 98 | let file_name = bufname('%') 99 | 100 | let file_breakpoints = get(s:breakpoints, file_name, {}) 101 | let linenr = line('.') 102 | 103 | if has_key(file_breakpoints, linenr) 104 | return 105 | endif 106 | 107 | let file_breakpoints[linenr] = {} 108 | let file_breakpoints[linenr]['content'] = getline('.') 109 | let file_breakpoints[linenr]['brknum'] = a:brknum 110 | let s:breakpoints[file_name] = file_breakpoints 111 | 112 | exe 'sign place '. s:max_breakpoint_sign_id .' name=GdbBreakpoint line='.line('.').' buffer=' . bufnr('%') 113 | let s:max_breakpoint_sign_id += 1 114 | endfunction 115 | 116 | function s:GdbRunning.pause(...) 117 | call self._parser.switch(s:GdbPaused) 118 | if !self._initialized 119 | call self.send('set confirm off') 120 | call self.send('shell clear') 121 | let self._initialized = 1 122 | endif 123 | endfunction 124 | 125 | function s:GdbRunning.disconnected(...) 126 | if !self._server_exited && self._reconnect 127 | " Refresh to force a delete of all watchpoints 128 | call s:RefreshBreakpoints() 129 | sleep 1 130 | " call self.attach() 131 | call self.send('continue') 132 | endif 133 | endfunction 134 | 135 | let s:Gdb = {} 136 | 137 | function s:Gdb.kill() 138 | if !exists('g:gdb') | return | endif 139 | call debugger_util#DebuggerMapping(0) 140 | call self.update_current_line_sign(0) 141 | let s:breakpoints = {} 142 | call s:RefreshBreakpointSigns() 143 | exe 'bd! '.self._client_buf 144 | if self._server_buf != -1 145 | exe 'bd! '.self._server_buf 146 | endif 147 | exe 'tabnext '.self._tab 148 | unlet g:gdb 149 | endfunction 150 | 151 | function! s:Gdb.send(data) 152 | call window#GetGdbWin() 153 | call jobsend(self._client_id, "\") 154 | call jobsend(self._client_id, a:data."\") 155 | endfunction 156 | 157 | function! s:Gdb.sendRaw(data) 158 | call window#GetGdbWin() 159 | call jobsend(self._client_id, "\") 160 | call jobsend(self._client_id, a:data) 161 | endfunction 162 | 163 | function! s:Gdb.update_current_line_sign(add) 164 | " to avoid flicker when removing/adding the sign column(due to the change in 165 | " line width), we switch ids for the line sign and only remove the old line 166 | " sign after marking the new one 167 | let old_line_sign_id = get(self, '_line_sign_id', 4999) 168 | let self._line_sign_id = old_line_sign_id == 4999 ? 4998 : 4999 169 | if a:add && self._current_line != -1 && self._current_buf != -1 170 | exe 'sign place '.self._line_sign_id.' name=GdbCurrentLine line=' 171 | \.self._current_line.' buffer='.self._current_buf 172 | endif 173 | exe 'sign unplace '.old_line_sign_id 174 | endfunction 175 | 176 | function! s:Spawn(server_host, client_cmd) 177 | if exists('g:gdb') 178 | throw 'Gdb already running' 179 | endif 180 | 181 | call debugger_util#DebuggerMapping(1) 182 | 183 | let gdb = vimexpect#Parser(s:GdbRunning, copy(s:Gdb)) 184 | let gdb._server_addr = a:server_host 185 | let gdb._reconnect = 0 186 | 187 | let gdb._initialized = 0 188 | if &filetype == "go" 189 | let gdb._initialized = 1 190 | endif 191 | 192 | let gdb._jump_window = 1 193 | let gdb._current_buf = -1 194 | let gdb._current_line = -1 195 | let gdb._has_breakpoints = 0 196 | let gdb._server_exited = 0 197 | let gdb._server_buf = -1 198 | let gdb._client_buf = -1 199 | 200 | let gdb._tab = tabpagenr() 201 | 202 | call window#CreateGdbWin() 203 | 204 | if empty(a:server_host) 205 | let gdb._client_id = termopen(a:client_cmd, gdb) 206 | else 207 | let gdb._client_id = termopen('zsh', gdb) 208 | 209 | let items = split(a:server_host, ':') 210 | let ssh_host = items[0] 211 | let ssh_cmd = printf('ssh %s', ssh_host) 212 | if len(items) > 1 213 | let ssh_port = items[1] 214 | let ssh_cmd = printf('ssh %s -p %s', ssh_host, ssh_port) 215 | endif 216 | 217 | if has_key(g:nvimgdb_host_cmd, ssh_host) 218 | let commands = g:nvimgdb_host_cmd[ssh_host] 219 | 220 | if commands[0] == 'Docker' 221 | call jobsend(gdb._client_id, "docker exec -it " .a:server_host. " bash\") 222 | let commands = commands[1:] 223 | else 224 | call jobsend(gdb._client_id, ssh_cmd ." \") 225 | endif 226 | 227 | for cmd in commands 228 | call jobsend(gdb._client_id, cmd . "\") 229 | endfor 230 | else 231 | call jobsend(gdb._client_id, ssh_cmd ." \") 232 | endif 233 | endif 234 | 235 | let gdb._client_buf = bufnr('%') 236 | 237 | exe gdb._jump_window 'wincmd w' 238 | let g:gdb = gdb 239 | endfunction 240 | 241 | function! s:ToggleBreak() 242 | let file_name = bufname('%') 243 | let file_breakpoints = get(s:breakpoints, file_name, {}) 244 | let linenr = line('.') 245 | if has_key(file_breakpoints, linenr) 246 | call remove(file_breakpoints, linenr) 247 | else 248 | let file_breakpoints[linenr] = getline('.') 249 | endif 250 | let s:breakpoints[file_name] = file_breakpoints 251 | call s:RefreshBreakpointSigns() 252 | call s:RefreshBreakpoints() 253 | endfunction 254 | 255 | function! s:ClearBreak() 256 | let s:breakpoints = {} 257 | call s:RefreshBreakpointSigns() 258 | call s:RefreshBreakpoints() 259 | endfunction 260 | 261 | function! s:SetBreakpoints() 262 | call s:RefreshBreakpointSigns() 263 | call s:RefreshBreakpoints() 264 | endfunction 265 | 266 | function! s:RefreshBreakpointSigns() 267 | let buf = bufnr('%') 268 | let i = 5000 269 | while i < s:max_breakpoint_sign_id 270 | exe 'sign unplace '.i 271 | let i += 1 272 | endwhile 273 | let id = 5000 274 | for linenr in keys(get(s:breakpoints, bufname('%'), {})) 275 | exe 'sign place '.id.' name=GdbBreakpoint line='.linenr.' buffer='.buf 276 | let id += 1 277 | let s:max_breakpoint_sign_id = id 278 | endfor 279 | endfunction 280 | 281 | function! s:SetLocationList() 282 | if !exists('g:gdb') && !g:gdb._has_breakpoints | return | endif 283 | let expr_list = [] 284 | for [file, breakpoints] in items(s:breakpoints) 285 | for [linenr,line] in items(breakpoints) 286 | call add(expr_list, file . ':' . linenr . ': ' . line['content']) 287 | endfor 288 | endfor 289 | 290 | if !empty(expr_list) 291 | lgetexpr expr_list 292 | botright lopen 293 | endif 294 | endfunction 295 | 296 | command! GdbList call s:SetLocationList() 297 | nmap ;gl :GdbList 298 | 299 | function! s:RefreshBreakpoints() 300 | if !exists('g:gdb') | return | endif 301 | if g:gdb._parser.state() == s:GdbRunning 302 | " pause first 303 | call jobsend(g:gdb._client_id, "\") 304 | endif 305 | if g:gdb._has_breakpoints 306 | call g:gdb.send('delete') 307 | endif 308 | let g:gdb._has_breakpoints = 0 309 | for [file, breakpoints] in items(s:breakpoints) 310 | for linenr in keys(breakpoints) 311 | let g:gdb._has_breakpoints = 1 312 | call g:gdb.send('break '.file.':'.linenr) 313 | endfor 314 | endfor 315 | endfunction 316 | 317 | function! s:GetExpression(...) range 318 | let [lnum1, col1] = getpos("'<")[1:2] 319 | let [lnum2, col2] = getpos("'>")[1:2] 320 | let lines = getline(lnum1, lnum2) 321 | let lines[-1] = lines[-1][:col2 - 1] 322 | let lines[0] = lines[0][col1 - 1:] 323 | return join(lines, "\n") 324 | endfunction 325 | 326 | function! s:Watch(expr) 327 | let expr = a:expr 328 | if expr[0] != '&' 329 | let expr = '&' . expr 330 | endif 331 | 332 | call debugger_util#Eval(expr) 333 | call debugger_term#Send('watch *$') 334 | endfunction 335 | 336 | function! s:Interrupt() 337 | if !exists('g:gdb') 338 | throw 'Gdb is not running' 339 | endif 340 | call jobsend(g:gdb._client_id, "\info line\") 341 | endfunction 342 | 343 | function! s:Kill() 344 | if !exists('g:gdb') | return | endif 345 | call g:gdb.kill() 346 | endfunction 347 | 348 | function! s:CreateToggleBreak() 349 | if !exists('g:gdb') 350 | call s:Spawn(0, printf("gdb -q -f %s.bin", expand('%:r'))) 351 | sleep 100m 352 | endif 353 | call s:ToggleBreak() 354 | endfunction 355 | 356 | let g:local_gdb_cmd = "gdb -q -f" 357 | 358 | if has('mac') 359 | let g:local_gdb_cmd = "sudo " .g:local_gdb_cmd 360 | endif 361 | 362 | function! GoDlvDebug() 363 | if empty("") 364 | call s:Spawn(0, "dlv debug ") 365 | else 366 | call s:Spawn(0, "dlv debug " .expand('%')) 367 | endif 368 | endfunction 369 | 370 | command! -nargs=1 -complete=file GdbStart call s:Spawn(0, printf(g:local_gdb_cmd . " %s", )) 371 | command! -nargs=1 GdbConnect call s:Spawn(, 0) 372 | command! GdbZsh call s:Spawn(0, "zsh") 373 | command! GdbWin call window#GetGdbWin() 374 | command! GdbSetBreaks call s:SetBreakpoints() 375 | 376 | command! GdbStop call s:Kill() 377 | command! GdbToggleBreakpoint call s:CreateToggleBreak() 378 | command! GdbClearBreakpoints call s:ClearBreak() 379 | command! GdbInterrupt call s:Interrupt() 380 | command! GdbEvalWord call debugger_util#Eval(debugger_util#GetCppCword()) 381 | command! -range GdbEvalRange call debugger_util#Eval(s:GetExpression()) 382 | command! GdbWatchWord call s:Watch(expand('')) 383 | command! -range GdbWatchRange call s:Watch(s:GetExpression()) 384 | 385 | let g:vim_debugger_mapping = { 386 | \ ';r' : "run", 387 | \ ';c' : "c", 388 | \ ';n' : "n", 389 | \ ';s' : "s", 390 | \ ';f' : "finish", 391 | \ } 392 | 393 | nnoremap ;b :call ToggleBreakpoint() 394 | nnoremap ;p :GdbEvalWord 395 | vnoremap ;p "vy:call debugger_util#Eval(@v) 396 | nnoremap ;gc :call debugger_util#GoCurrentLine() 397 | nnoremap ;gk :GdbStop 398 | 399 | nnoremap ;gb :call debugger_term#SendRaw(printf("break %s:%d ", expand('%'), line('.'))) 400 | nnoremap ;tb :call debugger_term#Send(printf("tbreak %s:%d", expand('%'), line('.'))) 401 | nnoremap ;u :call debugger_term#Send(printf("until %s:%d", expand('%'), line('.'))) 402 | --------------------------------------------------------------------------------