1" Debugger plugin using gdb. 2" 3" WORK IN PROGRESS - much doesn't work yet 4" 5" Open two visible terminal windows: 6" 1. run a pty, as with ":term NONE" 7" 2. run gdb, passing the pty 8" The current window is used to view source code and follows gdb. 9" 10" A third terminal window is hidden, it is used for communication with gdb. 11" 12" The communication with gdb uses GDB/MI. See: 13" https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html 14" 15" Author: Bram Moolenaar 16" Copyright: Vim license applies, see ":help license" 17 18" The command that starts debugging, e.g. ":Termdebug vim". 19" To end type "quit" in the gdb window. 20command -nargs=* -complete=file Termdebug call s:StartDebug(<q-args>) 21 22" Name of the gdb command, defaults to "gdb". 23if !exists('debugger') 24 let debugger = 'gdb' 25endif 26 27" Sign used to highlight the line where the program has stopped. 28sign define debugPC linehl=debugPC 29if &background == 'light' 30 hi debugPC term=reverse ctermbg=lightblue guibg=lightblue 31else 32 hi debugPC term=reverse ctermbg=darkblue guibg=darkblue 33endif 34let s:pc_id = 12 35 36func s:StartDebug(cmd) 37 let s:startwin = win_getid(winnr()) 38 let s:startsigncolumn = &signcolumn 39 40 " Open a terminal window without a job, to run the debugged program 41 let s:ptybuf = term_start('NONE', { 42 \ 'term_name': 'gdb program', 43 \ }) 44 if s:ptybuf == 0 45 echoerr 'Failed to open the program terminal window' 46 return 47 endif 48 let pty = job_info(term_getjob(s:ptybuf))['tty_out'] 49 50 " Create a hidden terminal window to communicate with gdb 51 let s:commbuf = term_start('NONE', { 52 \ 'term_name': 'gdb communication', 53 \ 'out_cb': function('s:CommOutput'), 54 \ 'hidden': 1, 55 \ }) 56 if s:commbuf == 0 57 echoerr 'Failed to open the communication terminal window' 58 exe 'bwipe! ' . s:ptybuf 59 return 60 endif 61 let commpty = job_info(term_getjob(s:commbuf))['tty_out'] 62 63 " Open a terminal window to run the debugger. 64 let cmd = [g:debugger, '-tty', pty, a:cmd] 65 echomsg 'executing "' . join(cmd) . '"' 66 let gdbbuf = term_start(cmd, { 67 \ 'exit_cb': function('s:EndDebug'), 68 \ 'term_finish': 'close', 69 \ }) 70 if gdbbuf == 0 71 echoerr 'Failed to open the gdb terminal window' 72 exe 'bwipe! ' . s:ptybuf 73 exe 'bwipe! ' . s:commbuf 74 return 75 endif 76 77 " Connect gdb to the communication pty, using the GDB/MI interface 78 call term_sendkeys(gdbbuf, 'new-ui mi ' . commpty . "\r") 79endfunc 80 81func s:EndDebug(job, status) 82 exe 'bwipe! ' . s:ptybuf 83 exe 'bwipe! ' . s:commbuf 84 call setwinvar(s:startwin, '&signcolumn', s:startsigncolumn) 85endfunc 86 87" Handle a message received from gdb on the GDB/MI interface. 88func s:CommOutput(chan, msg) 89 let msgs = split(a:msg, "\r") 90 91 for msg in msgs 92 " remove prefixed NL 93 if msg[0] == "\n" 94 let msg = msg[1:] 95 endif 96 if msg != '' 97 if msg =~ '^\*\(stopped\|running\)' 98 let wid = win_getid(winnr()) 99 100 if win_gotoid(s:startwin) 101 if msg =~ '^\*stopped' 102 " TODO: proper parsing 103 let fname = substitute(msg, '.*fullname="\([^"]*\)".*', '\1', '') 104 let lnum = substitute(msg, '.*line="\([^"]*\)".*', '\1', '') 105 if lnum =~ '^[0-9]*$' 106 if expand('%:h') != fname 107 if &modified 108 " TODO: find existing window 109 exe 'split ' . fnameescape(fname) 110 let s:startwin = win_getid(winnr()) 111 else 112 exe 'edit ' . fnameescape(fname) 113 endif 114 endif 115 exe lnum 116 exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fnameescape(fname) 117 setlocal signcolumn=yes 118 endif 119 else 120 exe 'sign unplace ' . s:pc_id 121 endif 122 123 call win_gotoid(wid) 124 endif 125 endif 126 endif 127 endfor 128endfunc 129