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