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] == '&'
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 in ' . a:quotedText
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
462" Extract the "name" value from a gdb message with fullname="name".
463func s:GetFullname(msg)
464  let name = s:DecodeMessage(substitute(a:msg, '.*fullname=', '', ''))
465  if has('win32') && name =~ ':\\\\'
466    " sometimes the name arrives double-escaped
467    let name = substitute(name, '\\\\', '\\', 'g')
468  endif
469  return name
470endfunc
471
472func s:EndTermDebug(job, status)
473  exe 'bwipe! ' . s:commbuf
474  unlet s:gdbwin
475
476  call s:EndDebugCommon()
477endfunc
478
479func s:EndDebugCommon()
480  let curwinid = win_getid(winnr())
481
482  if exists('s:ptybuf') && s:ptybuf
483    exe 'bwipe! ' . s:ptybuf
484  endif
485
486  call win_gotoid(s:sourcewin)
487  let &signcolumn = s:startsigncolumn
488  call s:DeleteCommands()
489
490  call win_gotoid(curwinid)
491
492  if s:save_columns > 0
493    let &columns = s:save_columns
494  endif
495
496  if has("balloon_eval") || has("balloon_eval_term")
497    set balloonexpr=
498    if has("balloon_eval")
499      set noballooneval
500    endif
501    if has("balloon_eval_term")
502      set noballoonevalterm
503    endif
504  endif
505
506  au! TermDebug
507endfunc
508
509func s:EndPromptDebug(job, status)
510  let curwinid = win_getid(winnr())
511  call win_gotoid(s:gdbwin)
512  close
513  if curwinid != s:gdbwin
514    call win_gotoid(curwinid)
515  endif
516
517  call s:EndDebugCommon()
518  unlet s:gdbwin
519  call ch_log("Returning from EndPromptDebug()")
520endfunc
521
522" Handle a message received from gdb on the GDB/MI interface.
523func s:CommOutput(chan, msg)
524  let msgs = split(a:msg, "\r")
525
526  for msg in msgs
527    " remove prefixed NL
528    if msg[0] == "\n"
529      let msg = msg[1:]
530    endif
531    if msg != ''
532      if msg =~ '^\(\*stopped\|\*running\|=thread-selected\)'
533	call s:HandleCursor(msg)
534      elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,'
535	call s:HandleNewBreakpoint(msg)
536      elseif msg =~ '^=breakpoint-deleted,'
537	call s:HandleBreakpointDelete(msg)
538      elseif msg =~ '^\^done,value='
539	call s:HandleEvaluate(msg)
540      elseif msg =~ '^\^error,msg='
541	call s:HandleError(msg)
542      endif
543    endif
544  endfor
545endfunc
546
547" Install commands in the current window to control the debugger.
548func s:InstallCommands()
549  command Break call s:SetBreakpoint()
550  command Clear call s:ClearBreakpoint()
551  command Step call s:SendCommand('-exec-step')
552  command Over call s:SendCommand('-exec-next')
553  command Finish call s:SendCommand('-exec-finish')
554  command -nargs=* Run call s:Run(<q-args>)
555  command -nargs=* Arguments call s:SendCommand('-exec-arguments ' . <q-args>)
556  command Stop call s:SendCommand('-exec-interrupt')
557
558  " using -exec-continue results in CTRL-C in gdb window not working
559  if s:way == 'prompt'
560    command Continue call s:SendCommand('continue')
561  else
562    command Continue call term_sendkeys(s:gdbbuf, "continue\r")
563  endif
564
565  command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>)
566  command Gdb call win_gotoid(s:gdbwin)
567  command Program call win_gotoid(s:ptywin)
568  command Source call s:GotoSourcewinOrCreateIt()
569  command Winbar call s:InstallWinbar()
570
571  " TODO: can the K mapping be restored?
572  nnoremap K :Evaluate<CR>
573
574  if has('menu') && &mouse != ''
575    call s:InstallWinbar()
576
577    if !exists('g:termdebug_popup') || g:termdebug_popup != 0
578      let s:saved_mousemodel = &mousemodel
579      let &mousemodel = 'popup_setpos'
580      an 1.200 PopUp.-SEP3-	<Nop>
581      an 1.210 PopUp.Set\ breakpoint	:Break<CR>
582      an 1.220 PopUp.Clear\ breakpoint	:Clear<CR>
583      an 1.230 PopUp.Evaluate		:Evaluate<CR>
584    endif
585  endif
586endfunc
587
588let s:winbar_winids = []
589
590" Install the window toolbar in the current window.
591func s:InstallWinbar()
592  if has('menu') && &mouse != ''
593    nnoremenu WinBar.Step   :Step<CR>
594    nnoremenu WinBar.Next   :Over<CR>
595    nnoremenu WinBar.Finish :Finish<CR>
596    nnoremenu WinBar.Cont   :Continue<CR>
597    nnoremenu WinBar.Stop   :Stop<CR>
598    nnoremenu WinBar.Eval   :Evaluate<CR>
599    call add(s:winbar_winids, win_getid(winnr()))
600  endif
601endfunc
602
603" Delete installed debugger commands in the current window.
604func s:DeleteCommands()
605  delcommand Break
606  delcommand Clear
607  delcommand Step
608  delcommand Over
609  delcommand Finish
610  delcommand Run
611  delcommand Arguments
612  delcommand Stop
613  delcommand Continue
614  delcommand Evaluate
615  delcommand Gdb
616  delcommand Program
617  delcommand Source
618  delcommand Winbar
619
620  nunmap K
621
622  if has('menu')
623    " Remove the WinBar entries from all windows where it was added.
624    let curwinid = win_getid(winnr())
625    for winid in s:winbar_winids
626      if win_gotoid(winid)
627	aunmenu WinBar.Step
628	aunmenu WinBar.Next
629	aunmenu WinBar.Finish
630	aunmenu WinBar.Cont
631	aunmenu WinBar.Stop
632	aunmenu WinBar.Eval
633      endif
634    endfor
635    call win_gotoid(curwinid)
636    let s:winbar_winids = []
637
638    if exists('s:saved_mousemodel')
639      let &mousemodel = s:saved_mousemodel
640      unlet s:saved_mousemodel
641      aunmenu PopUp.-SEP3-
642      aunmenu PopUp.Set\ breakpoint
643      aunmenu PopUp.Clear\ breakpoint
644      aunmenu PopUp.Evaluate
645    endif
646  endif
647
648  exe 'sign unplace ' . s:pc_id
649  for key in keys(s:breakpoints)
650    exe 'sign unplace ' . (s:break_id + key)
651  endfor
652  unlet s:breakpoints
653
654  sign undefine debugPC
655  for val in s:BreakpointSigns
656    exe "sign undefine debugBreakpoint" . val
657  endfor
658  unlet s:BreakpointSigns
659endfunc
660
661" :Break - Set a breakpoint at the cursor position.
662func s:SetBreakpoint()
663  " Setting a breakpoint may not work while the program is running.
664  " Interrupt to make it work.
665  let do_continue = 0
666  if !s:stopped
667    let do_continue = 1
668    if s:way == 'prompt'
669      " Need to send a signal to get the UI to listen.  Strangely this is only
670      " needed once.
671      call job_stop(s:gdbjob, 'int')
672    else
673      call s:SendCommand('-exec-interrupt')
674    endif
675    sleep 10m
676  endif
677  " Use the fname:lnum format, older gdb can't handle --source.
678  call s:SendCommand('-break-insert '
679	\ . fnameescape(expand('%:p')) . ':' . line('.'))
680  if do_continue
681    call s:SendCommand('-exec-continue')
682  endif
683endfunc
684
685" :Clear - Delete a breakpoint at the cursor position.
686func s:ClearBreakpoint()
687  let fname = fnameescape(expand('%:p'))
688  let lnum = line('.')
689  for [key, val] in items(s:breakpoints)
690    if val['fname'] == fname && val['lnum'] == lnum
691      call s:SendCommand('-break-delete ' . key)
692      " Assume this always wors, the reply is simply "^done".
693      exe 'sign unplace ' . (s:break_id + key)
694      unlet s:breakpoints[key]
695      break
696    endif
697  endfor
698endfunc
699
700func s:Run(args)
701  if a:args != ''
702    call s:SendCommand('-exec-arguments ' . a:args)
703  endif
704  call s:SendCommand('-exec-run')
705endfunc
706
707func s:SendEval(expr)
708  call s:SendCommand('-data-evaluate-expression "' . a:expr . '"')
709  let s:evalexpr = a:expr
710endfunc
711
712" :Evaluate - evaluate what is under the cursor
713func s:Evaluate(range, arg)
714  if a:arg != ''
715    let expr = a:arg
716  elseif a:range == 2
717    let pos = getcurpos()
718    let reg = getreg('v', 1, 1)
719    let regt = getregtype('v')
720    normal! gv"vy
721    let expr = @v
722    call setpos('.', pos)
723    call setreg('v', reg, regt)
724  else
725    let expr = expand('<cexpr>')
726  endif
727  let s:ignoreEvalError = 0
728  call s:SendEval(expr)
729endfunc
730
731let s:ignoreEvalError = 0
732let s:evalFromBalloonExpr = 0
733
734" Handle the result of data-evaluate-expression
735func s:HandleEvaluate(msg)
736  let value = substitute(a:msg, '.*value="\(.*\)"', '\1', '')
737  let value = substitute(value, '\\"', '"', 'g')
738  if s:evalFromBalloonExpr
739    if s:evalFromBalloonExprResult == ''
740      let s:evalFromBalloonExprResult = s:evalexpr . ': ' . value
741    else
742      let s:evalFromBalloonExprResult .= ' = ' . value
743    endif
744    call balloon_show(s:evalFromBalloonExprResult)
745  else
746    echomsg '"' . s:evalexpr . '": ' . value
747  endif
748
749  if s:evalexpr[0] != '*' && value =~ '^0x' && value != '0x0' && value !~ '"$'
750    " Looks like a pointer, also display what it points to.
751    let s:ignoreEvalError = 1
752    call s:SendEval('*' . s:evalexpr)
753  else
754    let s:evalFromBalloonExpr = 0
755  endif
756endfunc
757
758" Show a balloon with information of the variable under the mouse pointer,
759" if there is any.
760func TermDebugBalloonExpr()
761  if v:beval_winid != s:sourcewin
762    return
763  endif
764  if !s:stopped
765    " Only evaluate when stopped, otherwise setting a breakpoint using the
766    " mouse triggers a balloon.
767    return
768  endif
769  let s:evalFromBalloonExpr = 1
770  let s:evalFromBalloonExprResult = ''
771  let s:ignoreEvalError = 1
772  call s:SendEval(v:beval_text)
773  return ''
774endfunc
775
776" Handle an error.
777func s:HandleError(msg)
778  if s:ignoreEvalError
779    " Result of s:SendEval() failed, ignore.
780    let s:ignoreEvalError = 0
781    let s:evalFromBalloonExpr = 0
782    return
783  endif
784  echoerr substitute(a:msg, '.*msg="\(.*\)"', '\1', '')
785endfunc
786
787func s:GotoSourcewinOrCreateIt()
788  if !win_gotoid(s:sourcewin)
789    new
790    let s:sourcewin = win_getid(winnr())
791    call s:InstallWinbar()
792  endif
793endfunc
794
795" Handle stopping and running message from gdb.
796" Will update the sign that shows the current position.
797func s:HandleCursor(msg)
798  let wid = win_getid(winnr())
799
800  if a:msg =~ '^\*stopped'
801    let s:stopped = 1
802  elseif a:msg =~ '^\*running'
803    let s:stopped = 0
804  endif
805
806  call s:GotoSourcewinOrCreateIt()
807
808  if a:msg =~ 'fullname='
809    let fname = s:GetFullname(a:msg)
810  else
811    let fname = ''
812  endif
813  if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname)
814    let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '')
815    if lnum =~ '^[0-9]*$'
816      if expand('%:p') != fnamemodify(fname, ':p')
817	if &modified
818	  " TODO: find existing window
819	  exe 'split ' . fnameescape(fname)
820	  let s:sourcewin = win_getid(winnr())
821	  call s:InstallWinbar()
822	else
823	  exe 'edit ' . fnameescape(fname)
824	endif
825      endif
826      exe lnum
827      exe 'sign unplace ' . s:pc_id
828      exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fname
829      setlocal signcolumn=yes
830    endif
831  else
832    exe 'sign unplace ' . s:pc_id
833  endif
834
835  call win_gotoid(wid)
836endfunc
837
838let s:BreakpointSigns = []
839
840func s:CreateBreakpoint(nr)
841  if index(s:BreakpointSigns, a:nr) == -1
842    call add(s:BreakpointSigns, a:nr)
843    exe "sign define debugBreakpoint" . a:nr . " text=" . a:nr . " texthl=debugBreakpoint"
844  endif
845endfunc
846
847" Handle setting a breakpoint
848" Will update the sign that shows the breakpoint
849func s:HandleNewBreakpoint(msg)
850  let nr = substitute(a:msg, '.*number="\([0-9]\)*\".*', '\1', '') + 0
851  if nr == 0
852    return
853  endif
854  call s:CreateBreakpoint(nr)
855
856  if has_key(s:breakpoints, nr)
857    let entry = s:breakpoints[nr]
858  else
859    let entry = {}
860    let s:breakpoints[nr] = entry
861  endif
862
863  let fname = s:GetFullname(a:msg)
864  let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '')
865  let entry['fname'] = fname
866  let entry['lnum'] = lnum
867
868  if bufloaded(fname)
869    call s:PlaceSign(nr, entry)
870  endif
871endfunc
872
873func s:PlaceSign(nr, entry)
874  exe 'sign place ' . (s:break_id + a:nr) . ' line=' . a:entry['lnum'] . ' name=debugBreakpoint' . a:nr . ' file=' . a:entry['fname']
875  let a:entry['placed'] = 1
876endfunc
877
878" Handle deleting a breakpoint
879" Will remove the sign that shows the breakpoint
880func s:HandleBreakpointDelete(msg)
881  let nr = substitute(a:msg, '.*id="\([0-9]*\)\".*', '\1', '') + 0
882  if nr == 0
883    return
884  endif
885  if has_key(s:breakpoints, nr)
886    let entry = s:breakpoints[nr]
887    if has_key(entry, 'placed')
888      exe 'sign unplace ' . (s:break_id + nr)
889      unlet entry['placed']
890    endif
891    unlet s:breakpoints[nr]
892  endif
893endfunc
894
895" Handle a BufRead autocommand event: place any signs.
896func s:BufRead()
897  let fname = expand('<afile>:p')
898  for [nr, entry] in items(s:breakpoints)
899    if entry['fname'] == fname
900      call s:PlaceSign(nr, entry)
901    endif
902  endfor
903endfunc
904
905" Handle a BufUnloaded autocommand event: unplace any signs.
906func s:BufUnloaded()
907  let fname = expand('<afile>:p')
908  for [nr, entry] in items(s:breakpoints)
909    if entry['fname'] == fname
910      let entry['placed'] = 0
911    endif
912  endfor
913endfunc
914