1" Debugger plugin using gdb.
2"
3" Author: Bram Moolenaar
4" Copyright: Vim license applies, see ":help license"
5" Last Update: 2018 Jun 3
6"
7" WORK IN PROGRESS - Only the basics work
8" Note: On MS-Windows you need a recent version of gdb.  The one included with
9" MingW is too old (7.6.1).
10" I used version 7.12 from http://www.equation.com/servlet/equation.cmd?fa=gdb
11"
12" There are two ways to run gdb:
13" - In a terminal window; used if possible, does not work on MS-Windows
14"   Not used when g:termdebug_use_prompt is set to 1.
15" - Using a "prompt" buffer; may use a terminal window for the program
16"
17" For both the current window is used to view source code and shows the
18" current statement from gdb.
19"
20" USING A TERMINAL WINDOW
21"
22" Opens two visible terminal windows:
23" 1. runs a pty for the debugged program, as with ":term NONE"
24" 2. runs gdb, passing the pty of the debugged program
25" A third terminal window is hidden, it is used for communication with gdb.
26"
27" USING A PROMPT BUFFER
28"
29" Opens a window with a prompt buffer to communicate with gdb.
30" Gdb is run as a job with callbacks for I/O.
31" On Unix another terminal window is opened to run the debugged program
32" On MS-Windows a separate console is opened to run the debugged program
33"
34" The communication with gdb uses GDB/MI.  See:
35" https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html
36
37" In case this gets sourced twice.
38if exists(':Termdebug')
39  finish
40endif
41
42" Need either the +terminal feature or +channel and the prompt buffer.
43" The terminal feature does not work with gdb on win32.
44if has('terminal') && !has('win32')
45  let s:way = 'terminal'
46elseif has('channel') && exists('*prompt_setprompt')
47  let s:way = 'prompt'
48else
49  if has('terminal')
50    let s:err = 'Cannot debug, missing prompt buffer support'
51  else
52    let s:err = 'Cannot debug, +channel feature is not supported'
53  endif
54  command -nargs=* -complete=file -bang Termdebug echoerr s:err
55  command -nargs=+ -complete=file -bang TermdebugCommand echoerr s:err
56  finish
57endif
58
59let s:keepcpo = &cpo
60set cpo&vim
61
62" The command that starts debugging, e.g. ":Termdebug vim".
63" To end type "quit" in the gdb window.
64command -nargs=* -complete=file -bang Termdebug call s:StartDebug(<bang>0, <f-args>)
65command -nargs=+ -complete=file -bang TermdebugCommand call s:StartDebugCommand(<bang>0, <f-args>)
66
67" Name of the gdb command, defaults to "gdb".
68if !exists('termdebugger')
69  let termdebugger = 'gdb'
70endif
71
72let s:pc_id = 12
73let s:break_id = 13  " breakpoint number is added to this
74let s:stopped = 1
75
76" Take a breakpoint number as used by GDB and turn it into an integer.
77" The breakpoint may contain a dot: 123.4
78func s:Breakpoint2SignNumber(nr)
79  let t = split(a:nr, '\.')
80  return t[0] * 1000 + (len(t) == 2 ? t[1] : 0)
81endfunction
82
83func s:Highlight(init, old, new)
84  let default = a:init ? 'default ' : ''
85  if a:new ==# 'light' && a:old !=# 'light'
86    exe "hi " . default . "debugPC term=reverse ctermbg=lightblue guibg=lightblue"
87  elseif a:new ==# 'dark' && a:old !=# 'dark'
88    exe "hi " . default . "debugPC term=reverse ctermbg=darkblue guibg=darkblue"
89  endif
90endfunc
91
92call s:Highlight(1, '', &background)
93hi default debugBreakpoint term=reverse ctermbg=red guibg=red
94
95func s:StartDebug(bang, ...)
96  " First argument is the command to debug, second core file or process ID.
97  call s:StartDebug_internal({'gdb_args': a:000, 'bang': a:bang})
98endfunc
99
100func s:StartDebugCommand(bang, ...)
101  " First argument is the command to debug, rest are run arguments.
102  call s:StartDebug_internal({'gdb_args': [a:1], 'proc_args': a:000[1:], 'bang': a:bang})
103endfunc
104
105func s:StartDebug_internal(dict)
106  if exists('s:gdbwin')
107    echoerr 'Terminal debugger already running'
108    return
109  endif
110  let s:ptywin = 0
111  let s:pid = 0
112
113  " Uncomment this line to write logging in "debuglog".
114  " call ch_logfile('debuglog', 'w')
115
116  let s:sourcewin = win_getid(winnr())
117  let s:startsigncolumn = &signcolumn
118
119  let s:save_columns = 0
120  if exists('g:termdebug_wide')
121    if &columns < g:termdebug_wide
122      let s:save_columns = &columns
123      let &columns = g:termdebug_wide
124    endif
125    let s:vertical = 1
126  else
127    let s:vertical = 0
128  endif
129
130  " Override using a terminal window by setting g:termdebug_use_prompt to 1.
131  let use_prompt = exists('g:termdebug_use_prompt') && g:termdebug_use_prompt
132  if has('terminal') && !has('win32') && !use_prompt
133    let s:way = 'terminal'
134  else
135    let s:way = 'prompt'
136  endif
137
138  if s:way == 'prompt'
139    call s:StartDebug_prompt(a:dict)
140  else
141    call s:StartDebug_term(a:dict)
142  endif
143endfunc
144
145func s:StartDebug_term(dict)
146  " Open a terminal window without a job, to run the debugged program in.
147  let s:ptybuf = term_start('NONE', {
148        \ 'term_name': 'debugged program',
149        \ 'vertical': s:vertical,
150        \ })
151  if s:ptybuf == 0
152    echoerr 'Failed to open the program terminal window'
153    return
154  endif
155  let pty = job_info(term_getjob(s:ptybuf))['tty_out']
156  let s:ptywin = win_getid(winnr())
157  if s:vertical
158    " Assuming the source code window will get a signcolumn, use two more
159    " columns for that, thus one less for the terminal window.
160    exe (&columns / 2 - 1) . "wincmd |"
161  endif
162
163  " Create a hidden terminal window to communicate with gdb
164  let s:commbuf = term_start('NONE', {
165        \ 'term_name': 'gdb communication',
166        \ 'out_cb': function('s:CommOutput'),
167        \ 'hidden': 1,
168        \ })
169  if s:commbuf == 0
170    echoerr 'Failed to open the communication terminal window'
171    exe 'bwipe! ' . s:ptybuf
172    return
173  endif
174  let commpty = job_info(term_getjob(s:commbuf))['tty_out']
175
176  " Open a terminal window to run the debugger.
177  " Add -quiet to avoid the intro message causing a hit-enter prompt.
178  let gdb_args = get(a:dict, 'gdb_args', [])
179  let proc_args = get(a:dict, 'proc_args', [])
180
181  let cmd = [g:termdebugger, '-quiet', '-tty', pty] + gdb_args
182  call ch_log('executing "' . join(cmd) . '"')
183  let s:gdbbuf = term_start(cmd, {
184        \ 'exit_cb': function('s:EndTermDebug'),
185        \ 'term_finish': 'close',
186        \ })
187  if s:gdbbuf == 0
188    echoerr 'Failed to open the gdb terminal window'
189    exe 'bwipe! ' . s:ptybuf
190    exe 'bwipe! ' . s:commbuf
191    return
192  endif
193  let s:gdbwin = win_getid(winnr())
194
195  " Set arguments to be run
196  if len(proc_args)
197    call term_sendkeys(s:gdbbuf, 'set args ' . join(proc_args) . "\r")
198  endif
199
200  " Connect gdb to the communication pty, using the GDB/MI interface
201  call term_sendkeys(s:gdbbuf, 'new-ui mi ' . commpty . "\r")
202
203  " Wait for the response to show up, users may not notice the error and wonder
204  " why the debugger doesn't work.
205  let try_count = 0
206  while 1
207    let response = ''
208    for lnum in range(1,200)
209      if term_getline(s:gdbbuf, lnum) =~ 'new-ui mi '
210        " response can be in the same line or the next line
211        let response = term_getline(s:gdbbuf, lnum) . term_getline(s:gdbbuf, lnum + 1)
212        if response =~ 'Undefined command'
213          echoerr 'Sorry, your gdb is too old, gdb 7.12 is required'
214          exe 'bwipe! ' . s:ptybuf
215          exe 'bwipe! ' . s:commbuf
216          return
217        endif
218        if response =~ 'New UI allocated'
219          " Success!
220          break
221        endif
222      endif
223    endfor
224    if response =~ 'New UI allocated'
225      break
226    endif
227    let try_count += 1
228    if try_count > 100
229      echoerr 'Cannot check if your gdb works, continuing anyway'
230      break
231    endif
232    sleep 10m
233  endwhile
234
235  " Interpret commands while the target is running.  This should usualy only be
236  " exec-interrupt, since many commands don't work properly while the target is
237  " running.
238  call s:SendCommand('-gdb-set mi-async on')
239  " Older gdb uses a different command.
240  call s:SendCommand('-gdb-set target-async on')
241
242  " Disable pagination, it causes everything to stop at the gdb
243  " "Type <return> to continue" prompt.
244  call s:SendCommand('set pagination off')
245
246  call s:StartDebugCommon(a:dict)
247endfunc
248
249func s:StartDebug_prompt(dict)
250  " Open a window with a prompt buffer to run gdb in.
251  if s:vertical
252    vertical new
253  else
254    new
255  endif
256  let s:gdbwin = win_getid(winnr())
257  let s:promptbuf = bufnr('')
258  call prompt_setprompt(s:promptbuf, 'gdb> ')
259  set buftype=prompt
260  file gdb
261  call prompt_setcallback(s:promptbuf, function('s:PromptCallback'))
262  call prompt_setinterrupt(s:promptbuf, function('s:PromptInterrupt'))
263
264  if s:vertical
265    " Assuming the source code window will get a signcolumn, use two more
266    " columns for that, thus one less for the terminal window.
267    exe (&columns / 2 - 1) . "wincmd |"
268  endif
269
270  " Add -quiet to avoid the intro message causing a hit-enter prompt.
271  let gdb_args = get(a:dict, 'gdb_args', [])
272  let proc_args = get(a:dict, 'proc_args', [])
273
274  let cmd = [g:termdebugger, '-quiet', '--interpreter=mi2'] + gdb_args
275  call ch_log('executing "' . join(cmd) . '"')
276
277  let s:gdbjob = job_start(cmd, {
278        \ 'exit_cb': function('s:EndPromptDebug'),
279        \ 'out_cb': function('s:GdbOutCallback'),
280        \ })
281  if job_status(s:gdbjob) != "run"
282    echoerr 'Failed to start gdb'
283    exe 'bwipe! ' . s:promptbuf
284    return
285  endif
286  " Mark the buffer modified so that it's not easy to close.
287  set modified
288  let s:gdb_channel = job_getchannel(s:gdbjob)
289
290  " Interpret commands while the target is running.  This should usualy only
291  " be exec-interrupt, since many commands don't work properly while the
292  " target is running.
293  call s:SendCommand('-gdb-set mi-async on')
294  " Older gdb uses a different command.
295  call s:SendCommand('-gdb-set target-async on')
296
297  let s:ptybuf = 0
298  if has('win32')
299    " MS-Windows: run in a new console window for maximum compatibility
300    call s:SendCommand('set new-console on')
301  elseif has('terminal')
302    " Unix: Run the debugged program in a terminal window.  Open it below the
303    " gdb window.
304    belowright let s:ptybuf = term_start('NONE', {
305          \ 'term_name': 'debugged program',
306          \ })
307    if s:ptybuf == 0
308      echoerr 'Failed to open the program terminal window'
309      call job_stop(s:gdbjob)
310      return
311    endif
312    let s:ptywin = win_getid(winnr())
313    let pty = job_info(term_getjob(s:ptybuf))['tty_out']
314    call s:SendCommand('tty ' . pty)
315
316    " Since GDB runs in a prompt window, the environment has not been set to
317    " match a terminal window, need to do that now.
318    call s:SendCommand('set env TERM = xterm-color')
319    call s:SendCommand('set env ROWS = ' . winheight(s:ptywin))
320    call s:SendCommand('set env LINES = ' . winheight(s:ptywin))
321    call s:SendCommand('set env COLUMNS = ' . winwidth(s:ptywin))
322    call s:SendCommand('set env COLORS = ' . &t_Co)
323    call s:SendCommand('set env VIM_TERMINAL = ' . v:version)
324  else
325    " TODO: open a new terminal get get the tty name, pass on to gdb
326    call s:SendCommand('show inferior-tty')
327  endif
328  call s:SendCommand('set print pretty on')
329  call s:SendCommand('set breakpoint pending on')
330  " Disable pagination, it causes everything to stop at the gdb
331  call s:SendCommand('set pagination off')
332
333  " Set arguments to be run
334  if len(proc_args)
335    call s:SendCommand('set args ' . join(proc_args))
336  endif
337
338  call s:StartDebugCommon(a:dict)
339  startinsert
340endfunc
341
342func s:StartDebugCommon(dict)
343  " Sign used to highlight the line where the program has stopped.
344  " There can be only one.
345  sign define debugPC linehl=debugPC
346
347  " Install debugger commands in the text window.
348  call win_gotoid(s:sourcewin)
349  call s:InstallCommands()
350  call win_gotoid(s:gdbwin)
351
352  " Enable showing a balloon with eval info
353  if has("balloon_eval") || has("balloon_eval_term")
354    set balloonexpr=TermDebugBalloonExpr()
355    if has("balloon_eval")
356      set ballooneval
357    endif
358    if has("balloon_eval_term")
359      set balloonevalterm
360    endif
361  endif
362
363  " Contains breakpoints that have been placed, key is a string with the GDB
364  " breakpoint number.
365  let s:breakpoints = {}
366
367  augroup TermDebug
368    au BufRead * call s:BufRead()
369    au BufUnload * call s:BufUnloaded()
370    au OptionSet background call s:Highlight(0, v:option_old, v:option_new)
371  augroup END
372
373  " Run the command if the bang attribute was given and got to the debug
374  " window.
375  if get(a:dict, 'bang', 0)
376    call s:SendCommand('-exec-run')
377    call win_gotoid(s:ptywin)
378  endif
379endfunc
380
381" Send a command to gdb.  "cmd" is the string without line terminator.
382func s:SendCommand(cmd)
383  call ch_log('sending to gdb: ' . a:cmd)
384  if s:way == 'prompt'
385    call ch_sendraw(s:gdb_channel, a:cmd . "\n")
386  else
387    call term_sendkeys(s:commbuf, a:cmd . "\r")
388  endif
389endfunc
390
391" This is global so that a user can create their mappings with this.
392func TermDebugSendCommand(cmd)
393  if s:way == 'prompt'
394    call ch_sendraw(s:gdb_channel, a:cmd . "\n")
395  else
396    let do_continue = 0
397    if !s:stopped
398      let do_continue = 1
399      call s:SendCommand('-exec-interrupt')
400      sleep 10m
401    endif
402    call term_sendkeys(s:gdbbuf, a:cmd . "\r")
403    if do_continue
404      Continue
405    endif
406  endif
407endfunc
408
409" Function called when entering a line in the prompt buffer.
410func s:PromptCallback(text)
411  call s:SendCommand(a:text)
412endfunc
413
414" Function called when pressing CTRL-C in the prompt buffer and when placing a
415" breakpoint.
416func s:PromptInterrupt()
417  call ch_log('Interrupting gdb')
418  if has('win32')
419    " Using job_stop() does not work on MS-Windows, need to send SIGTRAP to
420    " the debugger program so that gdb responds again.
421    if s:pid == 0
422      echoerr 'Cannot interrupt gdb, did not find a process ID'
423    else
424      call debugbreak(s:pid)
425    endif
426  else
427    call job_stop(s:gdbjob, 'int')
428  endif
429endfunc
430
431" Function called when gdb outputs text.
432func s:GdbOutCallback(channel, text)
433  call ch_log('received from gdb: ' . a:text)
434
435  " Drop the gdb prompt, we have our own.
436  " Drop status and echo'd commands.
437  if a:text == '(gdb) ' || a:text == '^done' || a:text[0] == '&'
438    return
439  endif
440  if a:text =~ '^^error,msg='
441    let text = s:DecodeMessage(a:text[11:])
442    if exists('s:evalexpr') && text =~ 'A syntax error in expression, near\|No symbol .* in current context'
443      " Silently drop evaluation errors.
444      unlet s:evalexpr
445      return
446    endif
447  elseif a:text[0] == '~'
448    let text = s:DecodeMessage(a:text[1:])
449  else
450    call s:CommOutput(a:channel, a:text)
451    return
452  endif
453
454  let curwinid = win_getid(winnr())
455  call win_gotoid(s:gdbwin)
456
457  " Add the output above the current prompt.
458  call append(line('$') - 1, text)
459  set modified
460
461  call win_gotoid(curwinid)
462endfunc
463
464" Decode a message from gdb.  quotedText starts with a ", return the text up
465" to the next ", unescaping characters.
466func s:DecodeMessage(quotedText)
467  if a:quotedText[0] != '"'
468    echoerr 'DecodeMessage(): missing quote in ' . a:quotedText
469    return
470  endif
471  let result = ''
472  let i = 1
473  while a:quotedText[i] != '"' && i < len(a:quotedText)
474    if a:quotedText[i] == '\'
475      let i += 1
476      if a:quotedText[i] == 'n'
477        " drop \n
478        let i += 1
479        continue
480      endif
481    endif
482    let result .= a:quotedText[i]
483    let i += 1
484  endwhile
485  return result
486endfunc
487
488" Extract the "name" value from a gdb message with fullname="name".
489func s:GetFullname(msg)
490  if a:msg !~ 'fullname'
491    return ''
492  endif
493  let name = s:DecodeMessage(substitute(a:msg, '.*fullname=', '', ''))
494  if has('win32') && name =~ ':\\\\'
495    " sometimes the name arrives double-escaped
496    let name = substitute(name, '\\\\', '\\', 'g')
497  endif
498  return name
499endfunc
500
501func s:EndTermDebug(job, status)
502  exe 'bwipe! ' . s:commbuf
503  unlet s:gdbwin
504
505  call s:EndDebugCommon()
506endfunc
507
508func s:EndDebugCommon()
509  let curwinid = win_getid(winnr())
510
511  if exists('s:ptybuf') && s:ptybuf
512    exe 'bwipe! ' . s:ptybuf
513  endif
514
515  call win_gotoid(s:sourcewin)
516  let &signcolumn = s:startsigncolumn
517  call s:DeleteCommands()
518
519  call win_gotoid(curwinid)
520
521  if s:save_columns > 0
522    let &columns = s:save_columns
523  endif
524
525  if has("balloon_eval") || has("balloon_eval_term")
526    set balloonexpr=
527    if has("balloon_eval")
528      set noballooneval
529    endif
530    if has("balloon_eval_term")
531      set noballoonevalterm
532    endif
533  endif
534
535  au! TermDebug
536endfunc
537
538func s:EndPromptDebug(job, status)
539  let curwinid = win_getid(winnr())
540  call win_gotoid(s:gdbwin)
541  set nomodified
542  close
543  if curwinid != s:gdbwin
544    call win_gotoid(curwinid)
545  endif
546
547  call s:EndDebugCommon()
548  unlet s:gdbwin
549  call ch_log("Returning from EndPromptDebug()")
550endfunc
551
552" Handle a message received from gdb on the GDB/MI interface.
553func s:CommOutput(chan, msg)
554  let msgs = split(a:msg, "\r")
555
556  for msg in msgs
557    " remove prefixed NL
558    if msg[0] == "\n"
559      let msg = msg[1:]
560    endif
561    if msg != ''
562      if msg =~ '^\(\*stopped\|\*running\|=thread-selected\)'
563        call s:HandleCursor(msg)
564      elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,'
565        call s:HandleNewBreakpoint(msg)
566      elseif msg =~ '^=breakpoint-deleted,'
567        call s:HandleBreakpointDelete(msg)
568      elseif msg =~ '^=thread-group-started'
569        call s:HandleProgramRun(msg)
570      elseif msg =~ '^\^done,value='
571        call s:HandleEvaluate(msg)
572      elseif msg =~ '^\^error,msg='
573        call s:HandleError(msg)
574      endif
575    endif
576  endfor
577endfunc
578
579" Install commands in the current window to control the debugger.
580func s:InstallCommands()
581  let save_cpo = &cpo
582  set cpo&vim
583
584  command Break call s:SetBreakpoint()
585  command Clear call s:ClearBreakpoint()
586  command Step call s:SendCommand('-exec-step')
587  command Over call s:SendCommand('-exec-next')
588  command Finish call s:SendCommand('-exec-finish')
589  command -nargs=* Run call s:Run(<q-args>)
590  command -nargs=* Arguments call s:SendCommand('-exec-arguments ' . <q-args>)
591  command Stop call s:SendCommand('-exec-interrupt')
592
593  " using -exec-continue results in CTRL-C in gdb window not working
594  if s:way == 'prompt'
595    command Continue call s:SendCommand('continue')
596  else
597    command Continue call term_sendkeys(s:gdbbuf, "continue\r")
598  endif
599
600  command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>)
601  command Gdb call win_gotoid(s:gdbwin)
602  command Program call win_gotoid(s:ptywin)
603  command Source call s:GotoSourcewinOrCreateIt()
604  command Winbar call s:InstallWinbar()
605
606  " TODO: can the K mapping be restored?
607  nnoremap K :Evaluate<CR>
608
609  if has('menu') && &mouse != ''
610    call s:InstallWinbar()
611
612    if !exists('g:termdebug_popup') || g:termdebug_popup != 0
613      let s:saved_mousemodel = &mousemodel
614      let &mousemodel = 'popup_setpos'
615      an 1.200 PopUp.-SEP3-	<Nop>
616      an 1.210 PopUp.Set\ breakpoint	:Break<CR>
617      an 1.220 PopUp.Clear\ breakpoint	:Clear<CR>
618      an 1.230 PopUp.Evaluate		:Evaluate<CR>
619    endif
620  endif
621
622  let &cpo = save_cpo
623endfunc
624
625let s:winbar_winids = []
626
627" Install the window toolbar in the current window.
628func s:InstallWinbar()
629  if has('menu') && &mouse != ''
630    nnoremenu WinBar.Step   :Step<CR>
631    nnoremenu WinBar.Next   :Over<CR>
632    nnoremenu WinBar.Finish :Finish<CR>
633    nnoremenu WinBar.Cont   :Continue<CR>
634    nnoremenu WinBar.Stop   :Stop<CR>
635    nnoremenu WinBar.Eval   :Evaluate<CR>
636    call add(s:winbar_winids, win_getid(winnr()))
637  endif
638endfunc
639
640" Delete installed debugger commands in the current window.
641func s:DeleteCommands()
642  delcommand Break
643  delcommand Clear
644  delcommand Step
645  delcommand Over
646  delcommand Finish
647  delcommand Run
648  delcommand Arguments
649  delcommand Stop
650  delcommand Continue
651  delcommand Evaluate
652  delcommand Gdb
653  delcommand Program
654  delcommand Source
655  delcommand Winbar
656
657  nunmap K
658
659  if has('menu')
660    " Remove the WinBar entries from all windows where it was added.
661    let curwinid = win_getid(winnr())
662    for winid in s:winbar_winids
663      if win_gotoid(winid)
664        aunmenu WinBar.Step
665        aunmenu WinBar.Next
666        aunmenu WinBar.Finish
667        aunmenu WinBar.Cont
668        aunmenu WinBar.Stop
669        aunmenu WinBar.Eval
670      endif
671    endfor
672    call win_gotoid(curwinid)
673    let s:winbar_winids = []
674
675    if exists('s:saved_mousemodel')
676      let &mousemodel = s:saved_mousemodel
677      unlet s:saved_mousemodel
678      aunmenu PopUp.-SEP3-
679      aunmenu PopUp.Set\ breakpoint
680      aunmenu PopUp.Clear\ breakpoint
681      aunmenu PopUp.Evaluate
682    endif
683  endif
684
685  exe 'sign unplace ' . s:pc_id
686  for key in keys(s:breakpoints)
687    exe 'sign unplace ' . (s:break_id + s:Breakpoint2SignNumber(key))
688  endfor
689  unlet s:breakpoints
690
691  sign undefine debugPC
692  for val in s:BreakpointSigns
693    exe "sign undefine debugBreakpoint" . val
694  endfor
695  let s:BreakpointSigns = []
696endfunc
697
698" :Break - Set a breakpoint at the cursor position.
699func s:SetBreakpoint()
700  " Setting a breakpoint may not work while the program is running.
701  " Interrupt to make it work.
702  let do_continue = 0
703  if !s:stopped
704    let do_continue = 1
705    if s:way == 'prompt'
706      call s:PromptInterrupt()
707    else
708      call s:SendCommand('-exec-interrupt')
709    endif
710    sleep 10m
711  endif
712  " Use the fname:lnum format, older gdb can't handle --source.
713  call s:SendCommand('-break-insert '
714        \ . fnameescape(expand('%:p')) . ':' . line('.'))
715  if do_continue
716    call s:SendCommand('-exec-continue')
717  endif
718endfunc
719
720" :Clear - Delete a breakpoint at the cursor position.
721func s:ClearBreakpoint()
722  let fname = fnameescape(expand('%:p'))
723  let lnum = line('.')
724  for [key, val] in items(s:breakpoints)
725    if val['fname'] == fname && val['lnum'] == lnum
726      call s:SendCommand('-break-delete ' . key)
727      " Assume this always wors, the reply is simply "^done".
728      exe 'sign unplace ' . (s:break_id + s:Breakpoint2SignNumber(key))
729      unlet s:breakpoints[key]
730      break
731    endif
732  endfor
733endfunc
734
735func s:Run(args)
736  if a:args != ''
737    call s:SendCommand('-exec-arguments ' . a:args)
738  endif
739  call s:SendCommand('-exec-run')
740endfunc
741
742func s:SendEval(expr)
743  call s:SendCommand('-data-evaluate-expression "' . a:expr . '"')
744  let s:evalexpr = a:expr
745endfunc
746
747" :Evaluate - evaluate what is under the cursor
748func s:Evaluate(range, arg)
749  if a:arg != ''
750    let expr = a:arg
751  elseif a:range == 2
752    let pos = getcurpos()
753    let reg = getreg('v', 1, 1)
754    let regt = getregtype('v')
755    normal! gv"vy
756    let expr = @v
757    call setpos('.', pos)
758    call setreg('v', reg, regt)
759  else
760    let expr = expand('<cexpr>')
761  endif
762  let s:ignoreEvalError = 0
763  call s:SendEval(expr)
764endfunc
765
766let s:ignoreEvalError = 0
767let s:evalFromBalloonExpr = 0
768
769" Handle the result of data-evaluate-expression
770func s:HandleEvaluate(msg)
771  let value = substitute(a:msg, '.*value="\(.*\)"', '\1', '')
772  let value = substitute(value, '\\"', '"', 'g')
773  if s:evalFromBalloonExpr
774    if s:evalFromBalloonExprResult == ''
775      let s:evalFromBalloonExprResult = s:evalexpr . ': ' . value
776    else
777      let s:evalFromBalloonExprResult .= ' = ' . value
778    endif
779    call balloon_show(s:evalFromBalloonExprResult)
780  else
781    echomsg '"' . s:evalexpr . '": ' . value
782  endif
783
784  if s:evalexpr[0] != '*' && value =~ '^0x' && value != '0x0' && value !~ '"$'
785    " Looks like a pointer, also display what it points to.
786    let s:ignoreEvalError = 1
787    call s:SendEval('*' . s:evalexpr)
788  else
789    let s:evalFromBalloonExpr = 0
790  endif
791endfunc
792
793" Show a balloon with information of the variable under the mouse pointer,
794" if there is any.
795func TermDebugBalloonExpr()
796  if v:beval_winid != s:sourcewin
797    return
798  endif
799  if !s:stopped
800    " Only evaluate when stopped, otherwise setting a breakpoint using the
801    " mouse triggers a balloon.
802    return
803  endif
804  let s:evalFromBalloonExpr = 1
805  let s:evalFromBalloonExprResult = ''
806  let s:ignoreEvalError = 1
807  call s:SendEval(v:beval_text)
808  return ''
809endfunc
810
811" Handle an error.
812func s:HandleError(msg)
813  if s:ignoreEvalError
814    " Result of s:SendEval() failed, ignore.
815    let s:ignoreEvalError = 0
816    let s:evalFromBalloonExpr = 0
817    return
818  endif
819  echoerr substitute(a:msg, '.*msg="\(.*\)"', '\1', '')
820endfunc
821
822func s:GotoSourcewinOrCreateIt()
823  if !win_gotoid(s:sourcewin)
824    new
825    let s:sourcewin = win_getid(winnr())
826    call s:InstallWinbar()
827  endif
828endfunc
829
830" Handle stopping and running message from gdb.
831" Will update the sign that shows the current position.
832func s:HandleCursor(msg)
833  let wid = win_getid(winnr())
834
835  if a:msg =~ '^\*stopped'
836    call ch_log('program stopped')
837    let s:stopped = 1
838  elseif a:msg =~ '^\*running'
839    call ch_log('program running')
840    let s:stopped = 0
841  endif
842
843  if a:msg =~ 'fullname='
844    let fname = s:GetFullname(a:msg)
845  else
846    let fname = ''
847  endif
848  if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname)
849    let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '')
850    if lnum =~ '^[0-9]*$'
851    call s:GotoSourcewinOrCreateIt()
852      if expand('%:p') != fnamemodify(fname, ':p')
853        if &modified
854          " TODO: find existing window
855          exe 'split ' . fnameescape(fname)
856          let s:sourcewin = win_getid(winnr())
857          call s:InstallWinbar()
858        else
859          exe 'edit ' . fnameescape(fname)
860        endif
861      endif
862      exe lnum
863      exe 'sign unplace ' . s:pc_id
864      exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fname
865      setlocal signcolumn=yes
866    endif
867  elseif !s:stopped || fname != ''
868    exe 'sign unplace ' . s:pc_id
869  endif
870
871  call win_gotoid(wid)
872endfunc
873
874let s:BreakpointSigns = []
875
876func s:CreateBreakpoint(nr)
877  if index(s:BreakpointSigns, a:nr) == -1
878    call add(s:BreakpointSigns, a:nr)
879    exe "sign define debugBreakpoint" . a:nr . " text=" . substitute(a:nr, '\..*', '', '') . " texthl=debugBreakpoint"
880  endif
881endfunc
882
883func s:SplitMsg(s)
884  return split(a:s, '{\%([a-z-]\+=[^,]\+,*\)\+}\zs')
885endfunction
886
887" Handle setting a breakpoint
888" Will update the sign that shows the breakpoint
889func s:HandleNewBreakpoint(msg)
890  if a:msg !~ 'fullname='
891    " a watch does not have a file name
892    return
893  endif
894  for msg in s:SplitMsg(a:msg)
895    let fname = s:GetFullname(msg)
896    if empty(fname)
897      continue
898    endif
899    let nr = substitute(msg, '.*number="\([0-9.]*\)\".*', '\1', '')
900    if empty(nr)
901      return
902    endif
903    call s:CreateBreakpoint(nr)
904
905    if has_key(s:breakpoints, nr)
906      let entry = s:breakpoints[nr]
907    else
908      let entry = {}
909      let s:breakpoints[nr] = entry
910    endif
911
912    let lnum = substitute(msg, '.*line="\([^"]*\)".*', '\1', '')
913    let entry['fname'] = fname
914    let entry['lnum'] = lnum
915
916    if bufloaded(fname)
917      call s:PlaceSign(nr, entry)
918    endif
919  endfor
920endfunc
921
922func s:PlaceSign(nr, entry)
923  exe 'sign place ' . (s:break_id +  s:Breakpoint2SignNumber(a:nr)) . ' line=' . a:entry['lnum'] . ' name=debugBreakpoint' . a:nr . ' file=' . a:entry['fname']
924  let a:entry['placed'] = 1
925endfunc
926
927" Handle deleting a breakpoint
928" Will remove the sign that shows the breakpoint
929func s:HandleBreakpointDelete(msg)
930  let key = substitute(a:msg, '.*id="\([0-9.]*\)\".*', '\1', '')
931  if empty(key)
932    return
933  endif
934  for [nr, entry] in items(s:breakpoints)
935    if stridx(nr, key) != 0
936      continue
937    endif
938    let entry = s:breakpoints[nr]
939    if has_key(entry, 'placed')
940      exe 'sign unplace ' . (s:break_id + s:Breakpoint2SignNumber(nr))
941      unlet entry['placed']
942    endif
943    unlet s:breakpoints[nr]
944  endfor
945endfunc
946
947" Handle the debugged program starting to run.
948" Will store the process ID in s:pid
949func s:HandleProgramRun(msg)
950  let nr = substitute(a:msg, '.*pid="\([0-9]*\)\".*', '\1', '') + 0
951  if nr == 0
952    return
953  endif
954  let s:pid = nr
955  call ch_log('Detected process ID: ' . s:pid)
956endfunc
957
958" Handle a BufRead autocommand event: place any signs.
959func s:BufRead()
960  let fname = expand('<afile>:p')
961  for [nr, entry] in items(s:breakpoints)
962    if entry['fname'] == fname
963      call s:PlaceSign(nr, entry)
964    endif
965  endfor
966endfunc
967
968" Handle a BufUnloaded autocommand event: unplace any signs.
969func s:BufUnloaded()
970  let fname = expand('<afile>:p')
971  for [nr, entry] in items(s:breakpoints)
972    if entry['fname'] == fname
973      let entry['placed'] = 0
974    endif
975  endfor
976endfunc
977
978let &cpo = s:keepcpo
979unlet s:keepcpo
980