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 Moolenaar" Sign used to highlight the line where the program has stopped.
28e09ba7baSBram Moolenaar" There can be only one.
29fe386641SBram Moolenaarsign define debugPC linehl=debugPC
30fe386641SBram Moolenaarlet s:pc_id = 12
31e09ba7baSBram Moolenaarlet s:break_id = 13
32e09ba7baSBram Moolenaar
33e09ba7baSBram Moolenaar" Sign used to indicate a breakpoint.
34e09ba7baSBram Moolenaar" Can be used multiple times.
35e09ba7baSBram Moolenaarsign define debugBreakpoint text=>> texthl=debugBreakpoint
36e09ba7baSBram Moolenaar
37e09ba7baSBram Moolenaarif &background == 'light'
38e09ba7baSBram Moolenaar  hi default debugPC term=reverse ctermbg=lightblue guibg=lightblue
39e09ba7baSBram Moolenaarelse
40e09ba7baSBram Moolenaar  hi default debugPC term=reverse ctermbg=darkblue guibg=darkblue
41e09ba7baSBram Moolenaarendif
42e09ba7baSBram Moolenaarhi default debugBreakpoint term=reverse ctermbg=red guibg=red
43fe386641SBram Moolenaar
44c572da5fSBram Moolenaarfunc s:StartDebug(cmd)
45fe386641SBram Moolenaar  let s:startwin = win_getid(winnr())
46fe386641SBram Moolenaar  let s:startsigncolumn = &signcolumn
47fe386641SBram Moolenaar
48c572da5fSBram Moolenaar  " Open a terminal window without a job, to run the debugged program
49fe386641SBram Moolenaar  let s:ptybuf = term_start('NONE', {
50fe386641SBram Moolenaar	\ 'term_name': 'gdb program',
51fe386641SBram Moolenaar	\ })
52fe386641SBram Moolenaar  if s:ptybuf == 0
53fe386641SBram Moolenaar    echoerr 'Failed to open the program terminal window'
54fe386641SBram Moolenaar    return
55fe386641SBram Moolenaar  endif
56fe386641SBram Moolenaar  let pty = job_info(term_getjob(s:ptybuf))['tty_out']
57*45d5f26dSBram Moolenaar  let s:ptywin = win_getid(winnr())
58fe386641SBram Moolenaar
59fe386641SBram Moolenaar  " Create a hidden terminal window to communicate with gdb
60fe386641SBram Moolenaar  let s:commbuf = term_start('NONE', {
61fe386641SBram Moolenaar	\ 'term_name': 'gdb communication',
62fe386641SBram Moolenaar	\ 'out_cb': function('s:CommOutput'),
63fe386641SBram Moolenaar	\ 'hidden': 1,
64fe386641SBram Moolenaar	\ })
65fe386641SBram Moolenaar  if s:commbuf == 0
66fe386641SBram Moolenaar    echoerr 'Failed to open the communication terminal window'
67fe386641SBram Moolenaar    exe 'bwipe! ' . s:ptybuf
68fe386641SBram Moolenaar    return
69fe386641SBram Moolenaar  endif
70fe386641SBram Moolenaar  let commpty = job_info(term_getjob(s:commbuf))['tty_out']
71c572da5fSBram Moolenaar
72c572da5fSBram Moolenaar  " Open a terminal window to run the debugger.
73e09ba7baSBram Moolenaar  let cmd = [g:termdebugger, '-tty', pty, a:cmd]
74c572da5fSBram Moolenaar  echomsg 'executing "' . join(cmd) . '"'
75c572da5fSBram Moolenaar  let gdbbuf = term_start(cmd, {
76c572da5fSBram Moolenaar	\ 'exit_cb': function('s:EndDebug'),
77fe386641SBram Moolenaar	\ 'term_finish': 'close',
78c572da5fSBram Moolenaar	\ })
79fe386641SBram Moolenaar  if gdbbuf == 0
80fe386641SBram Moolenaar    echoerr 'Failed to open the gdb terminal window'
81fe386641SBram Moolenaar    exe 'bwipe! ' . s:ptybuf
82fe386641SBram Moolenaar    exe 'bwipe! ' . s:commbuf
83fe386641SBram Moolenaar    return
84fe386641SBram Moolenaar  endif
85*45d5f26dSBram Moolenaar  let s:gdbwin = win_getid(winnr())
86fe386641SBram Moolenaar
87fe386641SBram Moolenaar  " Connect gdb to the communication pty, using the GDB/MI interface
88fe386641SBram Moolenaar  call term_sendkeys(gdbbuf, 'new-ui mi ' . commpty . "\r")
89e09ba7baSBram Moolenaar
90*45d5f26dSBram Moolenaar  " Install debugger commands in the text window.
91*45d5f26dSBram Moolenaar  call win_gotoid(s:startwin)
92e09ba7baSBram Moolenaar  call s:InstallCommands()
93*45d5f26dSBram Moolenaar  call win_gotoid(s:gdbwin)
94e09ba7baSBram Moolenaar
95e09ba7baSBram Moolenaar  let s:breakpoints = {}
96c572da5fSBram Moolenaarendfunc
97c572da5fSBram Moolenaar
98c572da5fSBram Moolenaarfunc s:EndDebug(job, status)
99c572da5fSBram Moolenaar  exe 'bwipe! ' . s:ptybuf
100fe386641SBram Moolenaar  exe 'bwipe! ' . s:commbuf
101e09ba7baSBram Moolenaar
102e09ba7baSBram Moolenaar  let curwinid = win_getid(winnr())
103e09ba7baSBram Moolenaar
104e09ba7baSBram Moolenaar  call win_gotoid(s:startwin)
105e09ba7baSBram Moolenaar  let &signcolumn = s:startsigncolumn
106e09ba7baSBram Moolenaar  call s:DeleteCommands()
107e09ba7baSBram Moolenaar
108e09ba7baSBram Moolenaar  call win_gotoid(curwinid)
109fe386641SBram Moolenaarendfunc
110fe386641SBram Moolenaar
111fe386641SBram Moolenaar" Handle a message received from gdb on the GDB/MI interface.
112fe386641SBram Moolenaarfunc s:CommOutput(chan, msg)
113fe386641SBram Moolenaar  let msgs = split(a:msg, "\r")
114fe386641SBram Moolenaar
115fe386641SBram Moolenaar  for msg in msgs
116fe386641SBram Moolenaar    " remove prefixed NL
117fe386641SBram Moolenaar    if msg[0] == "\n"
118fe386641SBram Moolenaar      let msg = msg[1:]
119fe386641SBram Moolenaar    endif
120fe386641SBram Moolenaar    if msg != ''
121fe386641SBram Moolenaar      if msg =~ '^\*\(stopped\|running\)'
122e09ba7baSBram Moolenaar	call s:HandleCursor(msg)
123*45d5f26dSBram Moolenaar      elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,'
124e09ba7baSBram Moolenaar	call s:HandleNewBreakpoint(msg)
125e09ba7baSBram Moolenaar      elseif msg =~ '^=breakpoint-deleted,'
126e09ba7baSBram Moolenaar	call s:HandleBreakpointDelete(msg)
127*45d5f26dSBram Moolenaar      elseif msg =~ '^\^done,value='
128*45d5f26dSBram Moolenaar	call s:HandleEvaluate(msg)
129*45d5f26dSBram Moolenaar      elseif msg =~ '^\^error,msg='
130*45d5f26dSBram Moolenaar	call s:HandleError(msg)
131e09ba7baSBram Moolenaar      endif
132e09ba7baSBram Moolenaar    endif
133e09ba7baSBram Moolenaar  endfor
134e09ba7baSBram Moolenaarendfunc
135e09ba7baSBram Moolenaar
136e09ba7baSBram Moolenaar" Install commands in the current window to control the debugger.
137e09ba7baSBram Moolenaarfunc s:InstallCommands()
138e09ba7baSBram Moolenaar  command Break call s:SetBreakpoint()
139e09ba7baSBram Moolenaar  command Delete call s:DeleteBreakpoint()
140e09ba7baSBram Moolenaar  command Step call s:SendCommand('-exec-step')
141*45d5f26dSBram Moolenaar  command Over call s:SendCommand('-exec-next')
142e09ba7baSBram Moolenaar  command Finish call s:SendCommand('-exec-finish')
143e09ba7baSBram Moolenaar  command Continue call s:SendCommand('-exec-continue')
144*45d5f26dSBram Moolenaar  command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>)
145*45d5f26dSBram Moolenaar  command Gdb call win_gotoid(s:gdbwin)
146*45d5f26dSBram Moolenaar  command Program call win_gotoid(s:ptywin)
147*45d5f26dSBram Moolenaar
148*45d5f26dSBram Moolenaar  " TODO: can the K mapping be restored?
149*45d5f26dSBram Moolenaar  nnoremap K :Evaluate<CR>
150e09ba7baSBram Moolenaarendfunc
151e09ba7baSBram Moolenaar
152e09ba7baSBram Moolenaar" Delete installed debugger commands in the current window.
153e09ba7baSBram Moolenaarfunc s:DeleteCommands()
154e09ba7baSBram Moolenaar  delcommand Break
155e09ba7baSBram Moolenaar  delcommand Delete
156e09ba7baSBram Moolenaar  delcommand Step
157*45d5f26dSBram Moolenaar  delcommand Over
158e09ba7baSBram Moolenaar  delcommand Finish
159e09ba7baSBram Moolenaar  delcommand Continue
160*45d5f26dSBram Moolenaar  delcommand Evaluate
161*45d5f26dSBram Moolenaar  delcommand Gdb
162*45d5f26dSBram Moolenaar  delcommand Program
163*45d5f26dSBram Moolenaar
164*45d5f26dSBram Moolenaar  nunmap K
165*45d5f26dSBram Moolenaar  sign undefine debugPC
166*45d5f26dSBram Moolenaar  sign undefine debugBreakpoint
167*45d5f26dSBram Moolenaar  exe 'sign unplace ' . s:pc_id
168*45d5f26dSBram Moolenaar  for key in keys(s:breakpoints)
169*45d5f26dSBram Moolenaar    exe 'sign unplace ' . (s:break_id + key)
170*45d5f26dSBram Moolenaar  endfor
171*45d5f26dSBram Moolenaar  unlet s:breakpoints
172e09ba7baSBram Moolenaarendfunc
173e09ba7baSBram Moolenaar
174e09ba7baSBram Moolenaar" :Break - Set a breakpoint at the cursor position.
175e09ba7baSBram Moolenaarfunc s:SetBreakpoint()
176e09ba7baSBram Moolenaar  call term_sendkeys(s:commbuf, '-break-insert --source '
177e09ba7baSBram Moolenaar	\ . fnameescape(expand('%:p')) . ' --line ' . line('.') . "\r")
178e09ba7baSBram Moolenaarendfunc
179e09ba7baSBram Moolenaar
180e09ba7baSBram Moolenaar" :Delete - Delete a breakpoint at the cursor position.
181e09ba7baSBram Moolenaarfunc s:DeleteBreakpoint()
182e09ba7baSBram Moolenaar  let fname = fnameescape(expand('%:p'))
183e09ba7baSBram Moolenaar  let lnum = line('.')
184e09ba7baSBram Moolenaar  for [key, val] in items(s:breakpoints)
185e09ba7baSBram Moolenaar    if val['fname'] == fname && val['lnum'] == lnum
186e09ba7baSBram Moolenaar      call term_sendkeys(s:commbuf, '-break-delete ' . key . "\r")
187e09ba7baSBram Moolenaar      " Assume this always wors, the reply is simply "^done".
188e09ba7baSBram Moolenaar      exe 'sign unplace ' . (s:break_id + key)
189e09ba7baSBram Moolenaar      unlet s:breakpoints[key]
190e09ba7baSBram Moolenaar      break
191e09ba7baSBram Moolenaar    endif
192e09ba7baSBram Moolenaar  endfor
193e09ba7baSBram Moolenaarendfunc
194e09ba7baSBram Moolenaar
195e09ba7baSBram Moolenaar" :Next, :Continue, etc - send a command to gdb
196e09ba7baSBram Moolenaarfunc s:SendCommand(cmd)
197e09ba7baSBram Moolenaar  call term_sendkeys(s:commbuf, a:cmd . "\r")
198e09ba7baSBram Moolenaarendfunc
199e09ba7baSBram Moolenaar
200*45d5f26dSBram Moolenaar" :Evaluate - evaluate what is under the cursor
201*45d5f26dSBram Moolenaarfunc s:Evaluate(range, arg)
202*45d5f26dSBram Moolenaar  if a:arg != ''
203*45d5f26dSBram Moolenaar    let expr = a:arg
204*45d5f26dSBram Moolenaar  elseif a:range == 2
205*45d5f26dSBram Moolenaar    let pos = getcurpos()
206*45d5f26dSBram Moolenaar    let reg = getreg('v', 1, 1)
207*45d5f26dSBram Moolenaar    let regt = getregtype('v')
208*45d5f26dSBram Moolenaar    normal! gv"vy
209*45d5f26dSBram Moolenaar    let expr = @v
210*45d5f26dSBram Moolenaar    call setpos('.', pos)
211*45d5f26dSBram Moolenaar    call setreg('v', reg, regt)
212*45d5f26dSBram Moolenaar  else
213*45d5f26dSBram Moolenaar    let expr = expand('<cexpr>')
214*45d5f26dSBram Moolenaar  endif
215*45d5f26dSBram Moolenaar  call term_sendkeys(s:commbuf, '-data-evaluate-expression "' . expr . "\"\r")
216*45d5f26dSBram Moolenaar  let s:evalexpr = expr
217*45d5f26dSBram Moolenaarendfunc
218*45d5f26dSBram Moolenaar
219*45d5f26dSBram Moolenaar" Handle the result of data-evaluate-expression
220*45d5f26dSBram Moolenaarfunc s:HandleEvaluate(msg)
221*45d5f26dSBram Moolenaar  echomsg '"' . s:evalexpr . '": ' . substitute(a:msg, '.*value="\(.*\)"', '\1', '')
222*45d5f26dSBram Moolenaarendfunc
223*45d5f26dSBram Moolenaar
224*45d5f26dSBram Moolenaar" Handle an error.
225*45d5f26dSBram Moolenaarfunc s:HandleError(msg)
226*45d5f26dSBram Moolenaar  echoerr substitute(a:msg, '.*msg="\(.*\)"', '\1', '')
227*45d5f26dSBram Moolenaarendfunc
228*45d5f26dSBram Moolenaar
229e09ba7baSBram Moolenaar" Handle stopping and running message from gdb.
230e09ba7baSBram Moolenaar" Will update the sign that shows the current position.
231e09ba7baSBram Moolenaarfunc s:HandleCursor(msg)
232fe386641SBram Moolenaar  let wid = win_getid(winnr())
233fe386641SBram Moolenaar
234fe386641SBram Moolenaar  if win_gotoid(s:startwin)
235e09ba7baSBram Moolenaar    if a:msg =~ '^\*stopped'
236e09ba7baSBram Moolenaar      let fname = substitute(a:msg, '.*fullname="\([^"]*\)".*', '\1', '')
237e09ba7baSBram Moolenaar      let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '')
238fe386641SBram Moolenaar      if lnum =~ '^[0-9]*$'
239fe386641SBram Moolenaar	if expand('%:h') != fname
240fe386641SBram Moolenaar	  if &modified
241fe386641SBram Moolenaar	    " TODO: find existing window
242fe386641SBram Moolenaar	    exe 'split ' . fnameescape(fname)
243fe386641SBram Moolenaar	    let s:startwin = win_getid(winnr())
244fe386641SBram Moolenaar	  else
245fe386641SBram Moolenaar	    exe 'edit ' . fnameescape(fname)
246fe386641SBram Moolenaar	  endif
247fe386641SBram Moolenaar	endif
248fe386641SBram Moolenaar	exe lnum
249fe386641SBram Moolenaar	exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fnameescape(fname)
250fe386641SBram Moolenaar	setlocal signcolumn=yes
251fe386641SBram Moolenaar      endif
252fe386641SBram Moolenaar    else
253fe386641SBram Moolenaar      exe 'sign unplace ' . s:pc_id
254fe386641SBram Moolenaar    endif
255fe386641SBram Moolenaar
256fe386641SBram Moolenaar    call win_gotoid(wid)
257fe386641SBram Moolenaar  endif
258e09ba7baSBram Moolenaarendfunc
259e09ba7baSBram Moolenaar
260e09ba7baSBram Moolenaar" Handle setting a breakpoint
261e09ba7baSBram Moolenaar" Will update the sign that shows the breakpoint
262e09ba7baSBram Moolenaarfunc s:HandleNewBreakpoint(msg)
263e09ba7baSBram Moolenaar  let nr = substitute(a:msg, '.*number="\([0-9]\)*\".*', '\1', '') + 0
264e09ba7baSBram Moolenaar  if nr == 0
265e09ba7baSBram Moolenaar    return
266fe386641SBram Moolenaar  endif
267e09ba7baSBram Moolenaar
268e09ba7baSBram Moolenaar  if has_key(s:breakpoints, nr)
269e09ba7baSBram Moolenaar    let entry = s:breakpoints[nr]
270e09ba7baSBram Moolenaar  else
271e09ba7baSBram Moolenaar    let entry = {}
272e09ba7baSBram Moolenaar    let s:breakpoints[nr] = entry
273fe386641SBram Moolenaar  endif
274e09ba7baSBram Moolenaar
275e09ba7baSBram Moolenaar  let fname = substitute(a:msg, '.*fullname="\([^"]*\)".*', '\1', '')
276e09ba7baSBram Moolenaar  let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '')
277e09ba7baSBram Moolenaar
278e09ba7baSBram Moolenaar  exe 'sign place ' . (s:break_id + nr) . ' line=' . lnum . ' name=debugBreakpoint file=' . fnameescape(fname)
279e09ba7baSBram Moolenaar
280e09ba7baSBram Moolenaar  let entry['fname'] = fname
281e09ba7baSBram Moolenaar  let entry['lnum'] = lnum
282e09ba7baSBram Moolenaarendfunc
283e09ba7baSBram Moolenaar
284e09ba7baSBram Moolenaar" Handle deleting a breakpoint
285e09ba7baSBram Moolenaar" Will remove the sign that shows the breakpoint
286e09ba7baSBram Moolenaarfunc s:HandleBreakpointDelete(msg)
287e09ba7baSBram Moolenaar  let nr = substitute(a:msg, '.*id="\([0-9]*\)\".*', '\1', '') + 0
288e09ba7baSBram Moolenaar  if nr == 0
289e09ba7baSBram Moolenaar    return
290e09ba7baSBram Moolenaar  endif
291e09ba7baSBram Moolenaar  exe 'sign unplace ' . (s:break_id + nr)
292e09ba7baSBram Moolenaar  unlet s:breakpoints[nr]
293c572da5fSBram Moolenaarendfunc
294