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