1fe386641SBram Moolenaar" Debugger plugin using gdb.
2c572da5fSBram Moolenaar"
3c572da5fSBram Moolenaar" WORK IN PROGRESS - much doesn't work yet
4c572da5fSBram Moolenaar"
5fe386641SBram Moolenaar" Open two visible terminal windows:
6c572da5fSBram Moolenaar" 1. run a pty, as with ":term NONE"
7c572da5fSBram Moolenaar" 2. run gdb, passing the pty
8fe386641SBram Moolenaar" The current window is used to view source code and follows gdb.
9fe386641SBram Moolenaar"
10fe386641SBram Moolenaar" A third terminal window is hidden, it is used for communication with gdb.
11fe386641SBram Moolenaar"
12fe386641SBram Moolenaar" The communication with gdb uses GDB/MI.  See:
13fe386641SBram Moolenaar" https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html
14c572da5fSBram Moolenaar"
15c572da5fSBram Moolenaar" Author: Bram Moolenaar
16fe386641SBram Moolenaar" Copyright: Vim license applies, see ":help license"
17c572da5fSBram Moolenaar
18fe386641SBram Moolenaar" The command that starts debugging, e.g. ":Termdebug vim".
19fe386641SBram Moolenaar" To end type "quit" in the gdb window.
20c572da5fSBram Moolenaarcommand -nargs=* -complete=file Termdebug call s:StartDebug(<q-args>)
21c572da5fSBram Moolenaar
22fe386641SBram Moolenaar" Name of the gdb command, defaults to "gdb".
23e09ba7baSBram Moolenaarif !exists('termdebugger')
24e09ba7baSBram Moolenaar  let termdebugger = 'gdb'
25c572da5fSBram Moolenaarendif
26c572da5fSBram Moolenaar
27fe386641SBram Moolenaarlet s:pc_id = 12
28e09ba7baSBram Moolenaarlet s:break_id = 13
29e09ba7baSBram Moolenaar
30e09ba7baSBram Moolenaarif &background == 'light'
31e09ba7baSBram Moolenaar  hi default debugPC term=reverse ctermbg=lightblue guibg=lightblue
32e09ba7baSBram Moolenaarelse
33e09ba7baSBram Moolenaar  hi default debugPC term=reverse ctermbg=darkblue guibg=darkblue
34e09ba7baSBram Moolenaarendif
35e09ba7baSBram Moolenaarhi default debugBreakpoint term=reverse ctermbg=red guibg=red
36fe386641SBram Moolenaar
37c572da5fSBram Moolenaarfunc s:StartDebug(cmd)
38fe386641SBram Moolenaar  let s:startwin = win_getid(winnr())
39fe386641SBram Moolenaar  let s:startsigncolumn = &signcolumn
40fe386641SBram Moolenaar
41*38baa3e6SBram Moolenaar  if exists('g:termdebug_wide') && &columns < g:termdebug_wide
42*38baa3e6SBram Moolenaar    let s:save_columns = &columns
43*38baa3e6SBram Moolenaar    let &columns = g:termdebug_wide
44*38baa3e6SBram Moolenaar    let vertical = 1
45*38baa3e6SBram Moolenaar  else
46*38baa3e6SBram Moolenaar    let s:save_columns = 0
47*38baa3e6SBram Moolenaar    let vertical = 0
48*38baa3e6SBram Moolenaar  endif
49*38baa3e6SBram Moolenaar
50c572da5fSBram Moolenaar  " Open a terminal window without a job, to run the debugged program
51fe386641SBram Moolenaar  let s:ptybuf = term_start('NONE', {
52fe386641SBram Moolenaar	\ 'term_name': 'gdb program',
53*38baa3e6SBram Moolenaar	\ 'vertical': vertical,
54fe386641SBram Moolenaar	\ })
55fe386641SBram Moolenaar  if s:ptybuf == 0
56fe386641SBram Moolenaar    echoerr 'Failed to open the program terminal window'
57fe386641SBram Moolenaar    return
58fe386641SBram Moolenaar  endif
59fe386641SBram Moolenaar  let pty = job_info(term_getjob(s:ptybuf))['tty_out']
6045d5f26dSBram Moolenaar  let s:ptywin = win_getid(winnr())
61fe386641SBram Moolenaar
62fe386641SBram Moolenaar  " Create a hidden terminal window to communicate with gdb
63fe386641SBram Moolenaar  let s:commbuf = term_start('NONE', {
64fe386641SBram Moolenaar	\ 'term_name': 'gdb communication',
65fe386641SBram Moolenaar	\ 'out_cb': function('s:CommOutput'),
66fe386641SBram Moolenaar	\ 'hidden': 1,
67fe386641SBram Moolenaar	\ })
68fe386641SBram Moolenaar  if s:commbuf == 0
69fe386641SBram Moolenaar    echoerr 'Failed to open the communication terminal window'
70fe386641SBram Moolenaar    exe 'bwipe! ' . s:ptybuf
71fe386641SBram Moolenaar    return
72fe386641SBram Moolenaar  endif
73fe386641SBram Moolenaar  let commpty = job_info(term_getjob(s:commbuf))['tty_out']
74c572da5fSBram Moolenaar
75c572da5fSBram Moolenaar  " Open a terminal window to run the debugger.
76e09ba7baSBram Moolenaar  let cmd = [g:termdebugger, '-tty', pty, a:cmd]
77c572da5fSBram Moolenaar  echomsg 'executing "' . join(cmd) . '"'
78c572da5fSBram Moolenaar  let gdbbuf = term_start(cmd, {
79c572da5fSBram Moolenaar	\ 'exit_cb': function('s:EndDebug'),
80fe386641SBram Moolenaar	\ 'term_finish': 'close',
81c572da5fSBram Moolenaar	\ })
82fe386641SBram Moolenaar  if gdbbuf == 0
83fe386641SBram Moolenaar    echoerr 'Failed to open the gdb terminal window'
84fe386641SBram Moolenaar    exe 'bwipe! ' . s:ptybuf
85fe386641SBram Moolenaar    exe 'bwipe! ' . s:commbuf
86fe386641SBram Moolenaar    return
87fe386641SBram Moolenaar  endif
8845d5f26dSBram Moolenaar  let s:gdbwin = win_getid(winnr())
89fe386641SBram Moolenaar
90fe386641SBram Moolenaar  " Connect gdb to the communication pty, using the GDB/MI interface
91fe386641SBram Moolenaar  call term_sendkeys(gdbbuf, 'new-ui mi ' . commpty . "\r")
92e09ba7baSBram Moolenaar
93*38baa3e6SBram Moolenaar  " Sign used to highlight the line where the program has stopped.
94*38baa3e6SBram Moolenaar  " There can be only one.
95*38baa3e6SBram Moolenaar  sign define debugPC linehl=debugPC
96*38baa3e6SBram Moolenaar
97*38baa3e6SBram Moolenaar  " Sign used to indicate a breakpoint.
98*38baa3e6SBram Moolenaar  " Can be used multiple times.
99*38baa3e6SBram Moolenaar  sign define debugBreakpoint text=>> texthl=debugBreakpoint
100*38baa3e6SBram Moolenaar
10145d5f26dSBram Moolenaar  " Install debugger commands in the text window.
10245d5f26dSBram Moolenaar  call win_gotoid(s:startwin)
103e09ba7baSBram Moolenaar  call s:InstallCommands()
10445d5f26dSBram Moolenaar  call win_gotoid(s:gdbwin)
105e09ba7baSBram Moolenaar
106e09ba7baSBram Moolenaar  let s:breakpoints = {}
107c572da5fSBram Moolenaarendfunc
108c572da5fSBram Moolenaar
109c572da5fSBram Moolenaarfunc s:EndDebug(job, status)
110c572da5fSBram Moolenaar  exe 'bwipe! ' . s:ptybuf
111fe386641SBram Moolenaar  exe 'bwipe! ' . s:commbuf
112e09ba7baSBram Moolenaar
113e09ba7baSBram Moolenaar  let curwinid = win_getid(winnr())
114e09ba7baSBram Moolenaar
115e09ba7baSBram Moolenaar  call win_gotoid(s:startwin)
116e09ba7baSBram Moolenaar  let &signcolumn = s:startsigncolumn
117e09ba7baSBram Moolenaar  call s:DeleteCommands()
118e09ba7baSBram Moolenaar
119e09ba7baSBram Moolenaar  call win_gotoid(curwinid)
120*38baa3e6SBram Moolenaar  if s:save_columns > 0
121*38baa3e6SBram Moolenaar    let &columns = s:save_columns
122*38baa3e6SBram Moolenaar  endif
123fe386641SBram Moolenaarendfunc
124fe386641SBram Moolenaar
125fe386641SBram Moolenaar" Handle a message received from gdb on the GDB/MI interface.
126fe386641SBram Moolenaarfunc s:CommOutput(chan, msg)
127fe386641SBram Moolenaar  let msgs = split(a:msg, "\r")
128fe386641SBram Moolenaar
129fe386641SBram Moolenaar  for msg in msgs
130fe386641SBram Moolenaar    " remove prefixed NL
131fe386641SBram Moolenaar    if msg[0] == "\n"
132fe386641SBram Moolenaar      let msg = msg[1:]
133fe386641SBram Moolenaar    endif
134fe386641SBram Moolenaar    if msg != ''
135fe386641SBram Moolenaar      if msg =~ '^\*\(stopped\|running\)'
136e09ba7baSBram Moolenaar	call s:HandleCursor(msg)
13745d5f26dSBram Moolenaar      elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,'
138e09ba7baSBram Moolenaar	call s:HandleNewBreakpoint(msg)
139e09ba7baSBram Moolenaar      elseif msg =~ '^=breakpoint-deleted,'
140e09ba7baSBram Moolenaar	call s:HandleBreakpointDelete(msg)
14145d5f26dSBram Moolenaar      elseif msg =~ '^\^done,value='
14245d5f26dSBram Moolenaar	call s:HandleEvaluate(msg)
14345d5f26dSBram Moolenaar      elseif msg =~ '^\^error,msg='
14445d5f26dSBram Moolenaar	call s:HandleError(msg)
145e09ba7baSBram Moolenaar      endif
146e09ba7baSBram Moolenaar    endif
147e09ba7baSBram Moolenaar  endfor
148e09ba7baSBram Moolenaarendfunc
149e09ba7baSBram Moolenaar
150e09ba7baSBram Moolenaar" Install commands in the current window to control the debugger.
151e09ba7baSBram Moolenaarfunc s:InstallCommands()
152e09ba7baSBram Moolenaar  command Break call s:SetBreakpoint()
153e09ba7baSBram Moolenaar  command Delete call s:DeleteBreakpoint()
154e09ba7baSBram Moolenaar  command Step call s:SendCommand('-exec-step')
15545d5f26dSBram Moolenaar  command Over call s:SendCommand('-exec-next')
156e09ba7baSBram Moolenaar  command Finish call s:SendCommand('-exec-finish')
157e09ba7baSBram Moolenaar  command Continue call s:SendCommand('-exec-continue')
15845d5f26dSBram Moolenaar  command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>)
15945d5f26dSBram Moolenaar  command Gdb call win_gotoid(s:gdbwin)
16045d5f26dSBram Moolenaar  command Program call win_gotoid(s:ptywin)
16145d5f26dSBram Moolenaar
16245d5f26dSBram Moolenaar  " TODO: can the K mapping be restored?
16345d5f26dSBram Moolenaar  nnoremap K :Evaluate<CR>
164e09ba7baSBram Moolenaarendfunc
165e09ba7baSBram Moolenaar
166e09ba7baSBram Moolenaar" Delete installed debugger commands in the current window.
167e09ba7baSBram Moolenaarfunc s:DeleteCommands()
168e09ba7baSBram Moolenaar  delcommand Break
169e09ba7baSBram Moolenaar  delcommand Delete
170e09ba7baSBram Moolenaar  delcommand Step
17145d5f26dSBram Moolenaar  delcommand Over
172e09ba7baSBram Moolenaar  delcommand Finish
173e09ba7baSBram Moolenaar  delcommand Continue
17445d5f26dSBram Moolenaar  delcommand Evaluate
17545d5f26dSBram Moolenaar  delcommand Gdb
17645d5f26dSBram Moolenaar  delcommand Program
17745d5f26dSBram Moolenaar
17845d5f26dSBram Moolenaar  nunmap K
17945d5f26dSBram Moolenaar  exe 'sign unplace ' . s:pc_id
18045d5f26dSBram Moolenaar  for key in keys(s:breakpoints)
18145d5f26dSBram Moolenaar    exe 'sign unplace ' . (s:break_id + key)
18245d5f26dSBram Moolenaar  endfor
183*38baa3e6SBram Moolenaar  sign undefine debugPC
184*38baa3e6SBram Moolenaar  sign undefine debugBreakpoint
18545d5f26dSBram Moolenaar  unlet s:breakpoints
186e09ba7baSBram Moolenaarendfunc
187e09ba7baSBram Moolenaar
188e09ba7baSBram Moolenaar" :Break - Set a breakpoint at the cursor position.
189e09ba7baSBram Moolenaarfunc s:SetBreakpoint()
190e09ba7baSBram Moolenaar  call term_sendkeys(s:commbuf, '-break-insert --source '
191e09ba7baSBram Moolenaar	\ . fnameescape(expand('%:p')) . ' --line ' . line('.') . "\r")
192e09ba7baSBram Moolenaarendfunc
193e09ba7baSBram Moolenaar
194e09ba7baSBram Moolenaar" :Delete - Delete a breakpoint at the cursor position.
195e09ba7baSBram Moolenaarfunc s:DeleteBreakpoint()
196e09ba7baSBram Moolenaar  let fname = fnameescape(expand('%:p'))
197e09ba7baSBram Moolenaar  let lnum = line('.')
198e09ba7baSBram Moolenaar  for [key, val] in items(s:breakpoints)
199e09ba7baSBram Moolenaar    if val['fname'] == fname && val['lnum'] == lnum
200e09ba7baSBram Moolenaar      call term_sendkeys(s:commbuf, '-break-delete ' . key . "\r")
201e09ba7baSBram Moolenaar      " Assume this always wors, the reply is simply "^done".
202e09ba7baSBram Moolenaar      exe 'sign unplace ' . (s:break_id + key)
203e09ba7baSBram Moolenaar      unlet s:breakpoints[key]
204e09ba7baSBram Moolenaar      break
205e09ba7baSBram Moolenaar    endif
206e09ba7baSBram Moolenaar  endfor
207e09ba7baSBram Moolenaarendfunc
208e09ba7baSBram Moolenaar
209e09ba7baSBram Moolenaar" :Next, :Continue, etc - send a command to gdb
210e09ba7baSBram Moolenaarfunc s:SendCommand(cmd)
211e09ba7baSBram Moolenaar  call term_sendkeys(s:commbuf, a:cmd . "\r")
212e09ba7baSBram Moolenaarendfunc
213e09ba7baSBram Moolenaar
21445d5f26dSBram Moolenaar" :Evaluate - evaluate what is under the cursor
21545d5f26dSBram Moolenaarfunc s:Evaluate(range, arg)
21645d5f26dSBram Moolenaar  if a:arg != ''
21745d5f26dSBram Moolenaar    let expr = a:arg
21845d5f26dSBram Moolenaar  elseif a:range == 2
21945d5f26dSBram Moolenaar    let pos = getcurpos()
22045d5f26dSBram Moolenaar    let reg = getreg('v', 1, 1)
22145d5f26dSBram Moolenaar    let regt = getregtype('v')
22245d5f26dSBram Moolenaar    normal! gv"vy
22345d5f26dSBram Moolenaar    let expr = @v
22445d5f26dSBram Moolenaar    call setpos('.', pos)
22545d5f26dSBram Moolenaar    call setreg('v', reg, regt)
22645d5f26dSBram Moolenaar  else
22745d5f26dSBram Moolenaar    let expr = expand('<cexpr>')
22845d5f26dSBram Moolenaar  endif
22945d5f26dSBram Moolenaar  call term_sendkeys(s:commbuf, '-data-evaluate-expression "' . expr . "\"\r")
23045d5f26dSBram Moolenaar  let s:evalexpr = expr
23145d5f26dSBram Moolenaarendfunc
23245d5f26dSBram Moolenaar
23345d5f26dSBram Moolenaar" Handle the result of data-evaluate-expression
23445d5f26dSBram Moolenaarfunc s:HandleEvaluate(msg)
23545d5f26dSBram Moolenaar  echomsg '"' . s:evalexpr . '": ' . substitute(a:msg, '.*value="\(.*\)"', '\1', '')
23645d5f26dSBram Moolenaarendfunc
23745d5f26dSBram Moolenaar
23845d5f26dSBram Moolenaar" Handle an error.
23945d5f26dSBram Moolenaarfunc s:HandleError(msg)
24045d5f26dSBram Moolenaar  echoerr substitute(a:msg, '.*msg="\(.*\)"', '\1', '')
24145d5f26dSBram Moolenaarendfunc
24245d5f26dSBram Moolenaar
243e09ba7baSBram Moolenaar" Handle stopping and running message from gdb.
244e09ba7baSBram Moolenaar" Will update the sign that shows the current position.
245e09ba7baSBram Moolenaarfunc s:HandleCursor(msg)
246fe386641SBram Moolenaar  let wid = win_getid(winnr())
247fe386641SBram Moolenaar
248fe386641SBram Moolenaar  if win_gotoid(s:startwin)
249e09ba7baSBram Moolenaar    let fname = substitute(a:msg, '.*fullname="\([^"]*\)".*', '\1', '')
250*38baa3e6SBram Moolenaar    if a:msg =~ '^\*stopped' && filereadable(fname)
251e09ba7baSBram Moolenaar      let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '')
252fe386641SBram Moolenaar      if lnum =~ '^[0-9]*$'
253fe386641SBram Moolenaar	if expand('%:h') != fname
254fe386641SBram Moolenaar	  if &modified
255fe386641SBram Moolenaar	    " TODO: find existing window
256fe386641SBram Moolenaar	    exe 'split ' . fnameescape(fname)
257fe386641SBram Moolenaar	    let s:startwin = win_getid(winnr())
258fe386641SBram Moolenaar	  else
259fe386641SBram Moolenaar	    exe 'edit ' . fnameescape(fname)
260fe386641SBram Moolenaar	  endif
261fe386641SBram Moolenaar	endif
262fe386641SBram Moolenaar	exe lnum
263fe386641SBram Moolenaar	exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fnameescape(fname)
264fe386641SBram Moolenaar	setlocal signcolumn=yes
265fe386641SBram Moolenaar      endif
266fe386641SBram Moolenaar    else
267fe386641SBram Moolenaar      exe 'sign unplace ' . s:pc_id
268fe386641SBram Moolenaar    endif
269fe386641SBram Moolenaar
270fe386641SBram Moolenaar    call win_gotoid(wid)
271fe386641SBram Moolenaar  endif
272e09ba7baSBram Moolenaarendfunc
273e09ba7baSBram Moolenaar
274e09ba7baSBram Moolenaar" Handle setting a breakpoint
275e09ba7baSBram Moolenaar" Will update the sign that shows the breakpoint
276e09ba7baSBram Moolenaarfunc s:HandleNewBreakpoint(msg)
277e09ba7baSBram Moolenaar  let nr = substitute(a:msg, '.*number="\([0-9]\)*\".*', '\1', '') + 0
278e09ba7baSBram Moolenaar  if nr == 0
279e09ba7baSBram Moolenaar    return
280fe386641SBram Moolenaar  endif
281e09ba7baSBram Moolenaar
282e09ba7baSBram Moolenaar  if has_key(s:breakpoints, nr)
283e09ba7baSBram Moolenaar    let entry = s:breakpoints[nr]
284e09ba7baSBram Moolenaar  else
285e09ba7baSBram Moolenaar    let entry = {}
286e09ba7baSBram Moolenaar    let s:breakpoints[nr] = entry
287fe386641SBram Moolenaar  endif
288e09ba7baSBram Moolenaar
289e09ba7baSBram Moolenaar  let fname = substitute(a:msg, '.*fullname="\([^"]*\)".*', '\1', '')
290e09ba7baSBram Moolenaar  let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '')
291e09ba7baSBram Moolenaar
292e09ba7baSBram Moolenaar  exe 'sign place ' . (s:break_id + nr) . ' line=' . lnum . ' name=debugBreakpoint file=' . fnameescape(fname)
293e09ba7baSBram Moolenaar
294e09ba7baSBram Moolenaar  let entry['fname'] = fname
295e09ba7baSBram Moolenaar  let entry['lnum'] = lnum
296e09ba7baSBram Moolenaarendfunc
297e09ba7baSBram Moolenaar
298e09ba7baSBram Moolenaar" Handle deleting a breakpoint
299e09ba7baSBram Moolenaar" Will remove the sign that shows the breakpoint
300e09ba7baSBram Moolenaarfunc s:HandleBreakpointDelete(msg)
301e09ba7baSBram Moolenaar  let nr = substitute(a:msg, '.*id="\([0-9]*\)\".*', '\1', '') + 0
302e09ba7baSBram Moolenaar  if nr == 0
303e09ba7baSBram Moolenaar    return
304e09ba7baSBram Moolenaar  endif
305e09ba7baSBram Moolenaar  exe 'sign unplace ' . (s:break_id + nr)
306e09ba7baSBram Moolenaar  unlet s:breakpoints[nr]
307c572da5fSBram Moolenaarendfunc
308