1fe386641SBram Moolenaar" Debugger plugin using gdb.
2c572da5fSBram Moolenaar"
3b3307b5eSBram Moolenaar" Author: Bram Moolenaar
4b3307b5eSBram Moolenaar" Copyright: Vim license applies, see ":help license"
5b3307b5eSBram Moolenaar" Last Update: 2018 Jun 3
6c572da5fSBram Moolenaar"
7b3307b5eSBram Moolenaar" WORK IN PROGRESS - Only the basics work
8b3307b5eSBram Moolenaar" Note: On MS-Windows you need a recent version of gdb.  The one included with
9b3307b5eSBram Moolenaar" MingW is too old (7.6.1).
10b3307b5eSBram Moolenaar" I used version 7.12 from http://www.equation.com/servlet/equation.cmd?fa=gdb
11fe386641SBram Moolenaar"
12b3307b5eSBram Moolenaar" There are two ways to run gdb:
13b3307b5eSBram Moolenaar" - In a terminal window; used if possible, does not work on MS-Windows
14b3307b5eSBram Moolenaar"   Not used when g:termdebug_use_prompt is set to 1.
15b3307b5eSBram Moolenaar" - Using a "prompt" buffer; may use a terminal window for the program
16b3307b5eSBram Moolenaar"
17b3307b5eSBram Moolenaar" For both the current window is used to view source code and shows the
18b3307b5eSBram Moolenaar" current statement from gdb.
19b3307b5eSBram Moolenaar"
20b3307b5eSBram Moolenaar" USING A TERMINAL WINDOW
21b3307b5eSBram Moolenaar"
22b3307b5eSBram Moolenaar" Opens two visible terminal windows:
23b3307b5eSBram Moolenaar" 1. runs a pty for the debugged program, as with ":term NONE"
24b3307b5eSBram Moolenaar" 2. runs gdb, passing the pty of the debugged program
25fe386641SBram Moolenaar" A third terminal window is hidden, it is used for communication with gdb.
26fe386641SBram Moolenaar"
27b3307b5eSBram Moolenaar" USING A PROMPT BUFFER
28b3307b5eSBram Moolenaar"
29b3307b5eSBram Moolenaar" Opens a window with a prompt buffer to communicate with gdb.
30b3307b5eSBram Moolenaar" Gdb is run as a job with callbacks for I/O.
31b3307b5eSBram Moolenaar" On Unix another terminal window is opened to run the debugged program
32b3307b5eSBram Moolenaar" On MS-Windows a separate console is opened to run the debugged program
33b3307b5eSBram Moolenaar"
34fe386641SBram Moolenaar" The communication with gdb uses GDB/MI.  See:
35fe386641SBram Moolenaar" https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html
36c572da5fSBram Moolenaar
37b3307b5eSBram Moolenaar" In case this gets sourced twice.
3837c64c78SBram Moolenaarif exists(':Termdebug')
3937c64c78SBram Moolenaar  finish
4037c64c78SBram Moolenaarendif
4137c64c78SBram Moolenaar
42b3307b5eSBram Moolenaar" Need either the +terminal feature or +channel and the prompt buffer.
43b3307b5eSBram Moolenaar" The terminal feature does not work with gdb on win32.
44b3307b5eSBram Moolenaarif has('terminal') && !has('win32')
45b3307b5eSBram Moolenaar  let s:way = 'terminal'
46b3307b5eSBram Moolenaarelseif has('channel') && exists('*prompt_setprompt')
47b3307b5eSBram Moolenaar  let s:way = 'prompt'
48b3307b5eSBram Moolenaarelse
49b3307b5eSBram Moolenaar  if has('terminal')
50b3307b5eSBram Moolenaar    let s:err = 'Cannot debug, missing prompt buffer support'
51b3307b5eSBram Moolenaar  else
52b3307b5eSBram Moolenaar    let s:err = 'Cannot debug, +channel feature is not supported'
53b3307b5eSBram Moolenaar  endif
54b3307b5eSBram Moolenaar  command -nargs=* -complete=file -bang Termdebug echoerr s:err
55b3307b5eSBram Moolenaar  command -nargs=+ -complete=file -bang TermdebugCommand echoerr s:err
56b3307b5eSBram Moolenaar  finish
57b3307b5eSBram Moolenaarendif
5860e73f2aSBram Moolenaar
59ca4cc018SBram Moolenaarlet s:keepcpo = &cpo
60ca4cc018SBram Moolenaarset cpo&vim
61ca4cc018SBram Moolenaar
62fe386641SBram Moolenaar" The command that starts debugging, e.g. ":Termdebug vim".
63fe386641SBram Moolenaar" To end type "quit" in the gdb window.
6432c67ba7SBram Moolenaarcommand -nargs=* -complete=file -bang Termdebug call s:StartDebug(<bang>0, <f-args>)
6532c67ba7SBram Moolenaarcommand -nargs=+ -complete=file -bang TermdebugCommand call s:StartDebugCommand(<bang>0, <f-args>)
66c572da5fSBram Moolenaar
67fe386641SBram Moolenaar" Name of the gdb command, defaults to "gdb".
68e09ba7baSBram Moolenaarif !exists('termdebugger')
69e09ba7baSBram Moolenaar  let termdebugger = 'gdb'
70c572da5fSBram Moolenaarendif
71c572da5fSBram Moolenaar
72fe386641SBram Moolenaarlet s:pc_id = 12
73de1a8314SBram Moolenaarlet s:break_id = 13  " breakpoint number is added to this
7460e73f2aSBram Moolenaarlet s:stopped = 1
75e09ba7baSBram Moolenaar
765378e1cfSBram Moolenaar" Take a breakpoint number as used by GDB and turn it into an integer.
7737402ed5SBram Moolenaar" The breakpoint may contain a dot: 123.4 -> 123004
7837402ed5SBram Moolenaar" The main breakpoint has a zero subid.
7937402ed5SBram Moolenaarfunc s:Breakpoint2SignNumber(id, subid)
8037402ed5SBram Moolenaar  return s:break_id + a:id * 1000 + a:subid
815378e1cfSBram Moolenaarendfunction
825378e1cfSBram Moolenaar
83f07f9e73SBram Moolenaarfunc s:Highlight(init, old, new)
84f07f9e73SBram Moolenaar  let default = a:init ? 'default ' : ''
85f07f9e73SBram Moolenaar  if a:new ==# 'light' && a:old !=# 'light'
86f07f9e73SBram Moolenaar    exe "hi " . default . "debugPC term=reverse ctermbg=lightblue guibg=lightblue"
87f07f9e73SBram Moolenaar  elseif a:new ==# 'dark' && a:old !=# 'dark'
88f07f9e73SBram Moolenaar    exe "hi " . default . "debugPC term=reverse ctermbg=darkblue guibg=darkblue"
89e09ba7baSBram Moolenaar  endif
90f07f9e73SBram Moolenaarendfunc
91f07f9e73SBram Moolenaar
92f07f9e73SBram Moolenaarcall s:Highlight(1, '', &background)
93e09ba7baSBram Moolenaarhi default debugBreakpoint term=reverse ctermbg=red guibg=red
94fe386641SBram Moolenaar
9532c67ba7SBram Moolenaarfunc s:StartDebug(bang, ...)
9632c67ba7SBram Moolenaar  " First argument is the command to debug, second core file or process ID.
9732c67ba7SBram Moolenaar  call s:StartDebug_internal({'gdb_args': a:000, 'bang': a:bang})
9832c67ba7SBram Moolenaarendfunc
9932c67ba7SBram Moolenaar
10032c67ba7SBram Moolenaarfunc s:StartDebugCommand(bang, ...)
10132c67ba7SBram Moolenaar  " First argument is the command to debug, rest are run arguments.
10232c67ba7SBram Moolenaar  call s:StartDebug_internal({'gdb_args': [a:1], 'proc_args': a:000[1:], 'bang': a:bang})
10332c67ba7SBram Moolenaarendfunc
10432c67ba7SBram Moolenaar
10532c67ba7SBram Moolenaarfunc s:StartDebug_internal(dict)
106b3623a38SBram Moolenaar  if exists('s:gdbwin')
107b3623a38SBram Moolenaar    echoerr 'Terminal debugger already running'
108b3623a38SBram Moolenaar    return
109b3623a38SBram Moolenaar  endif
110b3307b5eSBram Moolenaar  let s:ptywin = 0
1114551c0a9SBram Moolenaar  let s:pid = 0
112b3623a38SBram Moolenaar
113b3307b5eSBram Moolenaar  " Uncomment this line to write logging in "debuglog".
114b3307b5eSBram Moolenaar  " call ch_logfile('debuglog', 'w')
115b3307b5eSBram Moolenaar
116b3307b5eSBram Moolenaar  let s:sourcewin = win_getid(winnr())
117fe386641SBram Moolenaar  let s:startsigncolumn = &signcolumn
118fe386641SBram Moolenaar
11924a98a0eSBram Moolenaar  let s:save_columns = 0
12068e6560bSBram Moolenaar  let s:allleft = 0
12124a98a0eSBram Moolenaar  if exists('g:termdebug_wide')
12224a98a0eSBram Moolenaar    if &columns < g:termdebug_wide
12338baa3e6SBram Moolenaar      let s:save_columns = &columns
12438baa3e6SBram Moolenaar      let &columns = g:termdebug_wide
12568e6560bSBram Moolenaar      " If we make the Vim window wider, use the whole left halve for the debug
12668e6560bSBram Moolenaar      " windows.
12768e6560bSBram Moolenaar      let s:allleft = 1
12824a98a0eSBram Moolenaar    endif
129b3307b5eSBram Moolenaar    let s:vertical = 1
13038baa3e6SBram Moolenaar  else
131b3307b5eSBram Moolenaar    let s:vertical = 0
13238baa3e6SBram Moolenaar  endif
13338baa3e6SBram Moolenaar
134b3307b5eSBram Moolenaar  " Override using a terminal window by setting g:termdebug_use_prompt to 1.
135b3307b5eSBram Moolenaar  let use_prompt = exists('g:termdebug_use_prompt') && g:termdebug_use_prompt
136b3307b5eSBram Moolenaar  if has('terminal') && !has('win32') && !use_prompt
137b3307b5eSBram Moolenaar    let s:way = 'terminal'
138b3307b5eSBram Moolenaar  else
139b3307b5eSBram Moolenaar    let s:way = 'prompt'
140b3307b5eSBram Moolenaar  endif
141b3307b5eSBram Moolenaar
142b3307b5eSBram Moolenaar  if s:way == 'prompt'
143b3307b5eSBram Moolenaar    call s:StartDebug_prompt(a:dict)
144b3307b5eSBram Moolenaar  else
145b3307b5eSBram Moolenaar    call s:StartDebug_term(a:dict)
146b3307b5eSBram Moolenaar  endif
147b3307b5eSBram Moolenaarendfunc
148b3307b5eSBram Moolenaar
149ef3c6a5bSBram Moolenaar" Use when debugger didn't start or ended.
150ef3c6a5bSBram Moolenaarfunc s:CloseBuffers()
151ef3c6a5bSBram Moolenaar  exe 'bwipe! ' . s:ptybuf
152ef3c6a5bSBram Moolenaar  exe 'bwipe! ' . s:commbuf
153ef3c6a5bSBram Moolenaar  unlet! s:gdbwin
154ef3c6a5bSBram Moolenaarendfunc
155ef3c6a5bSBram Moolenaar
156b3307b5eSBram Moolenaarfunc s:StartDebug_term(dict)
157b3307b5eSBram Moolenaar  " Open a terminal window without a job, to run the debugged program in.
158fe386641SBram Moolenaar  let s:ptybuf = term_start('NONE', {
159b3307b5eSBram Moolenaar        \ 'term_name': 'debugged program',
160b3307b5eSBram Moolenaar        \ 'vertical': s:vertical,
161fe386641SBram Moolenaar        \ })
162fe386641SBram Moolenaar  if s:ptybuf == 0
163fe386641SBram Moolenaar    echoerr 'Failed to open the program terminal window'
164fe386641SBram Moolenaar    return
165fe386641SBram Moolenaar  endif
166fe386641SBram Moolenaar  let pty = job_info(term_getjob(s:ptybuf))['tty_out']
16745d5f26dSBram Moolenaar  let s:ptywin = win_getid(winnr())
168b3307b5eSBram Moolenaar  if s:vertical
16951b0f370SBram Moolenaar    " Assuming the source code window will get a signcolumn, use two more
17051b0f370SBram Moolenaar    " columns for that, thus one less for the terminal window.
17151b0f370SBram Moolenaar    exe (&columns / 2 - 1) . "wincmd |"
17268e6560bSBram Moolenaar    if s:allleft
17368e6560bSBram Moolenaar      " use the whole left column
17468e6560bSBram Moolenaar      wincmd H
17568e6560bSBram Moolenaar    endif
17651b0f370SBram Moolenaar  endif
177fe386641SBram Moolenaar
178fe386641SBram Moolenaar  " Create a hidden terminal window to communicate with gdb
179fe386641SBram Moolenaar  let s:commbuf = term_start('NONE', {
180fe386641SBram Moolenaar        \ 'term_name': 'gdb communication',
181fe386641SBram Moolenaar        \ 'out_cb': function('s:CommOutput'),
182fe386641SBram Moolenaar        \ 'hidden': 1,
183fe386641SBram Moolenaar        \ })
184fe386641SBram Moolenaar  if s:commbuf == 0
185fe386641SBram Moolenaar    echoerr 'Failed to open the communication terminal window'
186fe386641SBram Moolenaar    exe 'bwipe! ' . s:ptybuf
187fe386641SBram Moolenaar    return
188fe386641SBram Moolenaar  endif
189fe386641SBram Moolenaar  let commpty = job_info(term_getjob(s:commbuf))['tty_out']
190c572da5fSBram Moolenaar
191c572da5fSBram Moolenaar  " Open a terminal window to run the debugger.
192c3632516SBram Moolenaar  " Add -quiet to avoid the intro message causing a hit-enter prompt.
19332c67ba7SBram Moolenaar  let gdb_args = get(a:dict, 'gdb_args', [])
19432c67ba7SBram Moolenaar  let proc_args = get(a:dict, 'proc_args', [])
19532c67ba7SBram Moolenaar
19632c67ba7SBram Moolenaar  let cmd = [g:termdebugger, '-quiet', '-tty', pty] + gdb_args
197b3307b5eSBram Moolenaar  call ch_log('executing "' . join(cmd) . '"')
19860e73f2aSBram Moolenaar  let s:gdbbuf = term_start(cmd, {
199fe386641SBram Moolenaar        \ 'term_finish': 'close',
200c572da5fSBram Moolenaar        \ })
20160e73f2aSBram Moolenaar  if s:gdbbuf == 0
202fe386641SBram Moolenaar    echoerr 'Failed to open the gdb terminal window'
203ef3c6a5bSBram Moolenaar    call s:CloseBuffers()
204fe386641SBram Moolenaar    return
205fe386641SBram Moolenaar  endif
20645d5f26dSBram Moolenaar  let s:gdbwin = win_getid(winnr())
207fe386641SBram Moolenaar
20832c67ba7SBram Moolenaar  " Set arguments to be run
20932c67ba7SBram Moolenaar  if len(proc_args)
21032c67ba7SBram Moolenaar    call term_sendkeys(s:gdbbuf, 'set args ' . join(proc_args) . "\r")
21132c67ba7SBram Moolenaar  endif
21232c67ba7SBram Moolenaar
213fe386641SBram Moolenaar  " Connect gdb to the communication pty, using the GDB/MI interface
21460e73f2aSBram Moolenaar  call term_sendkeys(s:gdbbuf, 'new-ui mi ' . commpty . "\r")
21560e73f2aSBram Moolenaar
2163e4b84d0SBram Moolenaar  " Wait for the response to show up, users may not notice the error and wonder
2173e4b84d0SBram Moolenaar  " why the debugger doesn't work.
2183e4b84d0SBram Moolenaar  let try_count = 0
2193e4b84d0SBram Moolenaar  while 1
220ef3c6a5bSBram Moolenaar    let gdbproc = term_getjob(s:gdbbuf)
221ef3c6a5bSBram Moolenaar    if gdbproc == v:null || job_status(gdbproc) !=# 'run'
222ef3c6a5bSBram Moolenaar      echoerr string(g:termdebugger) . ' exited unexpectedly'
223ef3c6a5bSBram Moolenaar      call s:CloseBuffers()
224ef3c6a5bSBram Moolenaar      return
225ef3c6a5bSBram Moolenaar    endif
226ef3c6a5bSBram Moolenaar
2273e4b84d0SBram Moolenaar    let response = ''
228b3623a38SBram Moolenaar    for lnum in range(1,200)
2293e4b84d0SBram Moolenaar      if term_getline(s:gdbbuf, lnum) =~ 'new-ui mi '
230f63db65bSBram Moolenaar        " response can be in the same line or the next line
231f63db65bSBram Moolenaar        let response = term_getline(s:gdbbuf, lnum) . term_getline(s:gdbbuf, lnum + 1)
2323e4b84d0SBram Moolenaar        if response =~ 'Undefined command'
233f3ba14ffSBram Moolenaar          echoerr 'Sorry, your gdb is too old, gdb 7.12 is required'
234ef3c6a5bSBram Moolenaar	  call s:CloseBuffers()
2353e4b84d0SBram Moolenaar          return
2363e4b84d0SBram Moolenaar        endif
2373e4b84d0SBram Moolenaar        if response =~ 'New UI allocated'
2383e4b84d0SBram Moolenaar          " Success!
2393e4b84d0SBram Moolenaar          break
2403e4b84d0SBram Moolenaar        endif
241*06fe74aeSBram Moolenaar        if response =~ 'Reading symbols from' && response !~ 'new-ui'
242*06fe74aeSBram Moolenaar          " Reading symbols might take a while
243*06fe74aeSBram Moolenaar	  let try_count -= 1
244*06fe74aeSBram Moolenaar        endif
2453e4b84d0SBram Moolenaar      endif
2463e4b84d0SBram Moolenaar    endfor
2473e4b84d0SBram Moolenaar    if response =~ 'New UI allocated'
2483e4b84d0SBram Moolenaar      break
2493e4b84d0SBram Moolenaar    endif
2503e4b84d0SBram Moolenaar    let try_count += 1
2513e4b84d0SBram Moolenaar    if try_count > 100
2523e4b84d0SBram Moolenaar      echoerr 'Cannot check if your gdb works, continuing anyway'
2533e4b84d0SBram Moolenaar      break
2543e4b84d0SBram Moolenaar    endif
2553e4b84d0SBram Moolenaar    sleep 10m
2563e4b84d0SBram Moolenaar  endwhile
2573e4b84d0SBram Moolenaar
25860e73f2aSBram Moolenaar  " Interpret commands while the target is running.  This should usualy only be
25960e73f2aSBram Moolenaar  " exec-interrupt, since many commands don't work properly while the target is
26060e73f2aSBram Moolenaar  " running.
26160e73f2aSBram Moolenaar  call s:SendCommand('-gdb-set mi-async on')
262b3307b5eSBram Moolenaar  " Older gdb uses a different command.
263b3307b5eSBram Moolenaar  call s:SendCommand('-gdb-set target-async on')
264e09ba7baSBram Moolenaar
265f3ba14ffSBram Moolenaar  " Disable pagination, it causes everything to stop at the gdb
266f3ba14ffSBram Moolenaar  " "Type <return> to continue" prompt.
267b3307b5eSBram Moolenaar  call s:SendCommand('set pagination off')
268f3ba14ffSBram Moolenaar
269ef3c6a5bSBram Moolenaar  call job_setoptions(gdbproc, {'exit_cb': function('s:EndTermDebug')})
270b3307b5eSBram Moolenaar  call s:StartDebugCommon(a:dict)
271b3307b5eSBram Moolenaarendfunc
272b3307b5eSBram Moolenaar
273b3307b5eSBram Moolenaarfunc s:StartDebug_prompt(dict)
274b3307b5eSBram Moolenaar  " Open a window with a prompt buffer to run gdb in.
275b3307b5eSBram Moolenaar  if s:vertical
276b3307b5eSBram Moolenaar    vertical new
277b3307b5eSBram Moolenaar  else
278b3307b5eSBram Moolenaar    new
279b3307b5eSBram Moolenaar  endif
280b3307b5eSBram Moolenaar  let s:gdbwin = win_getid(winnr())
281b3307b5eSBram Moolenaar  let s:promptbuf = bufnr('')
282b3307b5eSBram Moolenaar  call prompt_setprompt(s:promptbuf, 'gdb> ')
283b3307b5eSBram Moolenaar  set buftype=prompt
284b3307b5eSBram Moolenaar  file gdb
285b3307b5eSBram Moolenaar  call prompt_setcallback(s:promptbuf, function('s:PromptCallback'))
286b3307b5eSBram Moolenaar  call prompt_setinterrupt(s:promptbuf, function('s:PromptInterrupt'))
287b3307b5eSBram Moolenaar
288b3307b5eSBram Moolenaar  if s:vertical
289b3307b5eSBram Moolenaar    " Assuming the source code window will get a signcolumn, use two more
290b3307b5eSBram Moolenaar    " columns for that, thus one less for the terminal window.
291b3307b5eSBram Moolenaar    exe (&columns / 2 - 1) . "wincmd |"
292b3307b5eSBram Moolenaar  endif
293b3307b5eSBram Moolenaar
294b3307b5eSBram Moolenaar  " Add -quiet to avoid the intro message causing a hit-enter prompt.
295b3307b5eSBram Moolenaar  let gdb_args = get(a:dict, 'gdb_args', [])
296b3307b5eSBram Moolenaar  let proc_args = get(a:dict, 'proc_args', [])
297b3307b5eSBram Moolenaar
298b3307b5eSBram Moolenaar  let cmd = [g:termdebugger, '-quiet', '--interpreter=mi2'] + gdb_args
299b3307b5eSBram Moolenaar  call ch_log('executing "' . join(cmd) . '"')
300b3307b5eSBram Moolenaar
301b3307b5eSBram Moolenaar  let s:gdbjob = job_start(cmd, {
302b3307b5eSBram Moolenaar        \ 'exit_cb': function('s:EndPromptDebug'),
303b3307b5eSBram Moolenaar        \ 'out_cb': function('s:GdbOutCallback'),
304b3307b5eSBram Moolenaar        \ })
305b3307b5eSBram Moolenaar  if job_status(s:gdbjob) != "run"
306b3307b5eSBram Moolenaar    echoerr 'Failed to start gdb'
307b3307b5eSBram Moolenaar    exe 'bwipe! ' . s:promptbuf
308b3307b5eSBram Moolenaar    return
309b3307b5eSBram Moolenaar  endif
3104551c0a9SBram Moolenaar  " Mark the buffer modified so that it's not easy to close.
3114551c0a9SBram Moolenaar  set modified
312b3307b5eSBram Moolenaar  let s:gdb_channel = job_getchannel(s:gdbjob)
313b3307b5eSBram Moolenaar
314b3307b5eSBram Moolenaar  " Interpret commands while the target is running.  This should usualy only
315b3307b5eSBram Moolenaar  " be exec-interrupt, since many commands don't work properly while the
316b3307b5eSBram Moolenaar  " target is running.
317b3307b5eSBram Moolenaar  call s:SendCommand('-gdb-set mi-async on')
318b3307b5eSBram Moolenaar  " Older gdb uses a different command.
319b3307b5eSBram Moolenaar  call s:SendCommand('-gdb-set target-async on')
320b3307b5eSBram Moolenaar
321b3307b5eSBram Moolenaar  let s:ptybuf = 0
322b3307b5eSBram Moolenaar  if has('win32')
323b3307b5eSBram Moolenaar    " MS-Windows: run in a new console window for maximum compatibility
324b3307b5eSBram Moolenaar    call s:SendCommand('set new-console on')
325b3307b5eSBram Moolenaar  elseif has('terminal')
326b3307b5eSBram Moolenaar    " Unix: Run the debugged program in a terminal window.  Open it below the
327b3307b5eSBram Moolenaar    " gdb window.
328b3307b5eSBram Moolenaar    belowright let s:ptybuf = term_start('NONE', {
329b3307b5eSBram Moolenaar          \ 'term_name': 'debugged program',
330b3307b5eSBram Moolenaar          \ })
331b3307b5eSBram Moolenaar    if s:ptybuf == 0
332b3307b5eSBram Moolenaar      echoerr 'Failed to open the program terminal window'
333b3307b5eSBram Moolenaar      call job_stop(s:gdbjob)
334b3307b5eSBram Moolenaar      return
335b3307b5eSBram Moolenaar    endif
336b3307b5eSBram Moolenaar    let s:ptywin = win_getid(winnr())
337b3307b5eSBram Moolenaar    let pty = job_info(term_getjob(s:ptybuf))['tty_out']
338b3307b5eSBram Moolenaar    call s:SendCommand('tty ' . pty)
339b3307b5eSBram Moolenaar
340b3307b5eSBram Moolenaar    " Since GDB runs in a prompt window, the environment has not been set to
341b3307b5eSBram Moolenaar    " match a terminal window, need to do that now.
342b3307b5eSBram Moolenaar    call s:SendCommand('set env TERM = xterm-color')
343b3307b5eSBram Moolenaar    call s:SendCommand('set env ROWS = ' . winheight(s:ptywin))
344b3307b5eSBram Moolenaar    call s:SendCommand('set env LINES = ' . winheight(s:ptywin))
345b3307b5eSBram Moolenaar    call s:SendCommand('set env COLUMNS = ' . winwidth(s:ptywin))
346b3307b5eSBram Moolenaar    call s:SendCommand('set env COLORS = ' . &t_Co)
347b3307b5eSBram Moolenaar    call s:SendCommand('set env VIM_TERMINAL = ' . v:version)
348b3307b5eSBram Moolenaar  else
349b3307b5eSBram Moolenaar    " TODO: open a new terminal get get the tty name, pass on to gdb
350b3307b5eSBram Moolenaar    call s:SendCommand('show inferior-tty')
351b3307b5eSBram Moolenaar  endif
352b3307b5eSBram Moolenaar  call s:SendCommand('set print pretty on')
353b3307b5eSBram Moolenaar  call s:SendCommand('set breakpoint pending on')
354b3307b5eSBram Moolenaar  " Disable pagination, it causes everything to stop at the gdb
355b3307b5eSBram Moolenaar  call s:SendCommand('set pagination off')
356b3307b5eSBram Moolenaar
357b3307b5eSBram Moolenaar  " Set arguments to be run
358b3307b5eSBram Moolenaar  if len(proc_args)
359b3307b5eSBram Moolenaar    call s:SendCommand('set args ' . join(proc_args))
360b3307b5eSBram Moolenaar  endif
361b3307b5eSBram Moolenaar
362b3307b5eSBram Moolenaar  call s:StartDebugCommon(a:dict)
363b3307b5eSBram Moolenaar  startinsert
364b3307b5eSBram Moolenaarendfunc
365b3307b5eSBram Moolenaar
366b3307b5eSBram Moolenaarfunc s:StartDebugCommon(dict)
36738baa3e6SBram Moolenaar  " Sign used to highlight the line where the program has stopped.
36838baa3e6SBram Moolenaar  " There can be only one.
36938baa3e6SBram Moolenaar  sign define debugPC linehl=debugPC
37038baa3e6SBram Moolenaar
37145d5f26dSBram Moolenaar  " Install debugger commands in the text window.
372b3307b5eSBram Moolenaar  call win_gotoid(s:sourcewin)
373e09ba7baSBram Moolenaar  call s:InstallCommands()
37445d5f26dSBram Moolenaar  call win_gotoid(s:gdbwin)
375e09ba7baSBram Moolenaar
37651b0f370SBram Moolenaar  " Enable showing a balloon with eval info
377246fe03dSBram Moolenaar  if has("balloon_eval") || has("balloon_eval_term")
378246fe03dSBram Moolenaar    set balloonexpr=TermDebugBalloonExpr()
37951b0f370SBram Moolenaar    if has("balloon_eval")
38051b0f370SBram Moolenaar      set ballooneval
381246fe03dSBram Moolenaar    endif
38251b0f370SBram Moolenaar    if has("balloon_eval_term")
38351b0f370SBram Moolenaar      set balloonevalterm
38451b0f370SBram Moolenaar    endif
38551b0f370SBram Moolenaar  endif
38651b0f370SBram Moolenaar
3875378e1cfSBram Moolenaar  " Contains breakpoints that have been placed, key is a string with the GDB
3885378e1cfSBram Moolenaar  " breakpoint number.
38937402ed5SBram Moolenaar  " Each entry is a dict, containing the sub-breakpoints.  Key is the subid.
39037402ed5SBram Moolenaar  " For a breakpoint that is just a number the subid is zero.
39137402ed5SBram Moolenaar  " For a breakpoint "123.4" the id is "123" and subid is "4".
39237402ed5SBram Moolenaar  " Example, when breakpoint "44", "123", "123.1" and "123.2" exist:
39337402ed5SBram Moolenaar  " {'44': {'0': entry}, '123': {'0': entry, '1': entry, '2': entry}}
394e09ba7baSBram Moolenaar  let s:breakpoints = {}
3951b9645deSBram Moolenaar
39637402ed5SBram Moolenaar  " Contains breakpoints by file/lnum.  The key is "fname:lnum".
39737402ed5SBram Moolenaar  " Each entry is a list of breakpoint IDs at that position.
39837402ed5SBram Moolenaar  let s:breakpoint_locations = {}
39937402ed5SBram Moolenaar
4001b9645deSBram Moolenaar  augroup TermDebug
4011b9645deSBram Moolenaar    au BufRead * call s:BufRead()
4021b9645deSBram Moolenaar    au BufUnload * call s:BufUnloaded()
403f07f9e73SBram Moolenaar    au OptionSet background call s:Highlight(0, v:option_old, v:option_new)
4041b9645deSBram Moolenaar  augroup END
40532c67ba7SBram Moolenaar
406b3307b5eSBram Moolenaar  " Run the command if the bang attribute was given and got to the debug
407b3307b5eSBram Moolenaar  " window.
40832c67ba7SBram Moolenaar  if get(a:dict, 'bang', 0)
40932c67ba7SBram Moolenaar    call s:SendCommand('-exec-run')
41032c67ba7SBram Moolenaar    call win_gotoid(s:ptywin)
41132c67ba7SBram Moolenaar  endif
412c572da5fSBram Moolenaarendfunc
413c572da5fSBram Moolenaar
414b3307b5eSBram Moolenaar" Send a command to gdb.  "cmd" is the string without line terminator.
415b3307b5eSBram Moolenaarfunc s:SendCommand(cmd)
416b3307b5eSBram Moolenaar  call ch_log('sending to gdb: ' . a:cmd)
417b3307b5eSBram Moolenaar  if s:way == 'prompt'
418b3307b5eSBram Moolenaar    call ch_sendraw(s:gdb_channel, a:cmd . "\n")
419b3307b5eSBram Moolenaar  else
420b3307b5eSBram Moolenaar    call term_sendkeys(s:commbuf, a:cmd . "\r")
421b3307b5eSBram Moolenaar  endif
422b3307b5eSBram Moolenaarendfunc
423b3307b5eSBram Moolenaar
424b3307b5eSBram Moolenaar" This is global so that a user can create their mappings with this.
425b3307b5eSBram Moolenaarfunc TermDebugSendCommand(cmd)
426b3307b5eSBram Moolenaar  if s:way == 'prompt'
427b3307b5eSBram Moolenaar    call ch_sendraw(s:gdb_channel, a:cmd . "\n")
428b3307b5eSBram Moolenaar  else
429b3307b5eSBram Moolenaar    let do_continue = 0
430b3307b5eSBram Moolenaar    if !s:stopped
431b3307b5eSBram Moolenaar      let do_continue = 1
432b3307b5eSBram Moolenaar      call s:SendCommand('-exec-interrupt')
433b3307b5eSBram Moolenaar      sleep 10m
434b3307b5eSBram Moolenaar    endif
435b3307b5eSBram Moolenaar    call term_sendkeys(s:gdbbuf, a:cmd . "\r")
436b3307b5eSBram Moolenaar    if do_continue
437b3307b5eSBram Moolenaar      Continue
438b3307b5eSBram Moolenaar    endif
439b3307b5eSBram Moolenaar  endif
440b3307b5eSBram Moolenaarendfunc
441b3307b5eSBram Moolenaar
442b3307b5eSBram Moolenaar" Function called when entering a line in the prompt buffer.
443b3307b5eSBram Moolenaarfunc s:PromptCallback(text)
444b3307b5eSBram Moolenaar  call s:SendCommand(a:text)
445b3307b5eSBram Moolenaarendfunc
446b3307b5eSBram Moolenaar
4474551c0a9SBram Moolenaar" Function called when pressing CTRL-C in the prompt buffer and when placing a
4484551c0a9SBram Moolenaar" breakpoint.
449b3307b5eSBram Moolenaarfunc s:PromptInterrupt()
4502ed890f1SBram Moolenaar  call ch_log('Interrupting gdb')
4512ed890f1SBram Moolenaar  if has('win32')
4522ed890f1SBram Moolenaar    " Using job_stop() does not work on MS-Windows, need to send SIGTRAP to
4532ed890f1SBram Moolenaar    " the debugger program so that gdb responds again.
4544551c0a9SBram Moolenaar    if s:pid == 0
4554551c0a9SBram Moolenaar      echoerr 'Cannot interrupt gdb, did not find a process ID'
4564551c0a9SBram Moolenaar    else
4574551c0a9SBram Moolenaar      call debugbreak(s:pid)
4584551c0a9SBram Moolenaar    endif
4592ed890f1SBram Moolenaar  else
4602ed890f1SBram Moolenaar    call job_stop(s:gdbjob, 'int')
4612ed890f1SBram Moolenaar  endif
462b3307b5eSBram Moolenaarendfunc
463b3307b5eSBram Moolenaar
464b3307b5eSBram Moolenaar" Function called when gdb outputs text.
465b3307b5eSBram Moolenaarfunc s:GdbOutCallback(channel, text)
466b3307b5eSBram Moolenaar  call ch_log('received from gdb: ' . a:text)
467b3307b5eSBram Moolenaar
468b3307b5eSBram Moolenaar  " Drop the gdb prompt, we have our own.
469b3307b5eSBram Moolenaar  " Drop status and echo'd commands.
470a15b0a93SBram Moolenaar  if a:text == '(gdb) ' || a:text == '^done' || a:text[0] == '&'
471b3307b5eSBram Moolenaar    return
472b3307b5eSBram Moolenaar  endif
473b3307b5eSBram Moolenaar  if a:text =~ '^^error,msg='
474b3307b5eSBram Moolenaar    let text = s:DecodeMessage(a:text[11:])
475b3307b5eSBram Moolenaar    if exists('s:evalexpr') && text =~ 'A syntax error in expression, near\|No symbol .* in current context'
476b3307b5eSBram Moolenaar      " Silently drop evaluation errors.
477b3307b5eSBram Moolenaar      unlet s:evalexpr
478b3307b5eSBram Moolenaar      return
479b3307b5eSBram Moolenaar    endif
480b3307b5eSBram Moolenaar  elseif a:text[0] == '~'
481b3307b5eSBram Moolenaar    let text = s:DecodeMessage(a:text[1:])
482b3307b5eSBram Moolenaar  else
483b3307b5eSBram Moolenaar    call s:CommOutput(a:channel, a:text)
484b3307b5eSBram Moolenaar    return
485b3307b5eSBram Moolenaar  endif
486b3307b5eSBram Moolenaar
487b3307b5eSBram Moolenaar  let curwinid = win_getid(winnr())
488b3307b5eSBram Moolenaar  call win_gotoid(s:gdbwin)
489b3307b5eSBram Moolenaar
490b3307b5eSBram Moolenaar  " Add the output above the current prompt.
491b3307b5eSBram Moolenaar  call append(line('$') - 1, text)
4924551c0a9SBram Moolenaar  set modified
493b3307b5eSBram Moolenaar
494b3307b5eSBram Moolenaar  call win_gotoid(curwinid)
495b3307b5eSBram Moolenaarendfunc
496b3307b5eSBram Moolenaar
497b3307b5eSBram Moolenaar" Decode a message from gdb.  quotedText starts with a ", return the text up
498b3307b5eSBram Moolenaar" to the next ", unescaping characters.
499b3307b5eSBram Moolenaarfunc s:DecodeMessage(quotedText)
500b3307b5eSBram Moolenaar  if a:quotedText[0] != '"'
501a15b0a93SBram Moolenaar    echoerr 'DecodeMessage(): missing quote in ' . a:quotedText
502b3307b5eSBram Moolenaar    return
503b3307b5eSBram Moolenaar  endif
504b3307b5eSBram Moolenaar  let result = ''
505b3307b5eSBram Moolenaar  let i = 1
506b3307b5eSBram Moolenaar  while a:quotedText[i] != '"' && i < len(a:quotedText)
507b3307b5eSBram Moolenaar    if a:quotedText[i] == '\'
508b3307b5eSBram Moolenaar      let i += 1
509b3307b5eSBram Moolenaar      if a:quotedText[i] == 'n'
510b3307b5eSBram Moolenaar        " drop \n
511b3307b5eSBram Moolenaar        let i += 1
512b3307b5eSBram Moolenaar        continue
513b3307b5eSBram Moolenaar      endif
514b3307b5eSBram Moolenaar    endif
515b3307b5eSBram Moolenaar    let result .= a:quotedText[i]
516b3307b5eSBram Moolenaar    let i += 1
517b3307b5eSBram Moolenaar  endwhile
518b3307b5eSBram Moolenaar  return result
519b3307b5eSBram Moolenaarendfunc
520b3307b5eSBram Moolenaar
521a15b0a93SBram Moolenaar" Extract the "name" value from a gdb message with fullname="name".
522a15b0a93SBram Moolenaarfunc s:GetFullname(msg)
5235378e1cfSBram Moolenaar  if a:msg !~ 'fullname'
5245378e1cfSBram Moolenaar    return ''
5255378e1cfSBram Moolenaar  endif
526a15b0a93SBram Moolenaar  let name = s:DecodeMessage(substitute(a:msg, '.*fullname=', '', ''))
527a15b0a93SBram Moolenaar  if has('win32') && name =~ ':\\\\'
528a15b0a93SBram Moolenaar    " sometimes the name arrives double-escaped
529a15b0a93SBram Moolenaar    let name = substitute(name, '\\\\', '\\', 'g')
530a15b0a93SBram Moolenaar  endif
531a15b0a93SBram Moolenaar  return name
532a15b0a93SBram Moolenaarendfunc
533a15b0a93SBram Moolenaar
534b3307b5eSBram Moolenaarfunc s:EndTermDebug(job, status)
535fe386641SBram Moolenaar  exe 'bwipe! ' . s:commbuf
536b3623a38SBram Moolenaar  unlet s:gdbwin
537e09ba7baSBram Moolenaar
538b3307b5eSBram Moolenaar  call s:EndDebugCommon()
539b3307b5eSBram Moolenaarendfunc
540b3307b5eSBram Moolenaar
541b3307b5eSBram Moolenaarfunc s:EndDebugCommon()
542e09ba7baSBram Moolenaar  let curwinid = win_getid(winnr())
543e09ba7baSBram Moolenaar
544b3307b5eSBram Moolenaar  if exists('s:ptybuf') && s:ptybuf
545b3307b5eSBram Moolenaar    exe 'bwipe! ' . s:ptybuf
546b3307b5eSBram Moolenaar  endif
547b3307b5eSBram Moolenaar
548b3307b5eSBram Moolenaar  call win_gotoid(s:sourcewin)
549e09ba7baSBram Moolenaar  let &signcolumn = s:startsigncolumn
550e09ba7baSBram Moolenaar  call s:DeleteCommands()
551e09ba7baSBram Moolenaar
552e09ba7baSBram Moolenaar  call win_gotoid(curwinid)
553b3307b5eSBram Moolenaar
55438baa3e6SBram Moolenaar  if s:save_columns > 0
55538baa3e6SBram Moolenaar    let &columns = s:save_columns
55638baa3e6SBram Moolenaar  endif
5571b9645deSBram Moolenaar
558246fe03dSBram Moolenaar  if has("balloon_eval") || has("balloon_eval_term")
559246fe03dSBram Moolenaar    set balloonexpr=
56051b0f370SBram Moolenaar    if has("balloon_eval")
56151b0f370SBram Moolenaar      set noballooneval
562246fe03dSBram Moolenaar    endif
56351b0f370SBram Moolenaar    if has("balloon_eval_term")
56451b0f370SBram Moolenaar      set noballoonevalterm
56551b0f370SBram Moolenaar    endif
56651b0f370SBram Moolenaar  endif
56751b0f370SBram Moolenaar
5681b9645deSBram Moolenaar  au! TermDebug
569fe386641SBram Moolenaarendfunc
570fe386641SBram Moolenaar
571b3307b5eSBram Moolenaarfunc s:EndPromptDebug(job, status)
572b3307b5eSBram Moolenaar  let curwinid = win_getid(winnr())
573b3307b5eSBram Moolenaar  call win_gotoid(s:gdbwin)
5744551c0a9SBram Moolenaar  set nomodified
575b3307b5eSBram Moolenaar  close
576b3307b5eSBram Moolenaar  if curwinid != s:gdbwin
577b3307b5eSBram Moolenaar    call win_gotoid(curwinid)
578b3307b5eSBram Moolenaar  endif
579b3307b5eSBram Moolenaar
580b3307b5eSBram Moolenaar  call s:EndDebugCommon()
581b3307b5eSBram Moolenaar  unlet s:gdbwin
582b3307b5eSBram Moolenaar  call ch_log("Returning from EndPromptDebug()")
583b3307b5eSBram Moolenaarendfunc
584b3307b5eSBram Moolenaar
585fe386641SBram Moolenaar" Handle a message received from gdb on the GDB/MI interface.
586fe386641SBram Moolenaarfunc s:CommOutput(chan, msg)
587fe386641SBram Moolenaar  let msgs = split(a:msg, "\r")
588fe386641SBram Moolenaar
589fe386641SBram Moolenaar  for msg in msgs
590fe386641SBram Moolenaar    " remove prefixed NL
591fe386641SBram Moolenaar    if msg[0] == "\n"
592fe386641SBram Moolenaar      let msg = msg[1:]
593fe386641SBram Moolenaar    endif
594fe386641SBram Moolenaar    if msg != ''
5951b9645deSBram Moolenaar      if msg =~ '^\(\*stopped\|\*running\|=thread-selected\)'
596e09ba7baSBram Moolenaar        call s:HandleCursor(msg)
59745d5f26dSBram Moolenaar      elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,'
598e09ba7baSBram Moolenaar        call s:HandleNewBreakpoint(msg)
599e09ba7baSBram Moolenaar      elseif msg =~ '^=breakpoint-deleted,'
600e09ba7baSBram Moolenaar        call s:HandleBreakpointDelete(msg)
6014551c0a9SBram Moolenaar      elseif msg =~ '^=thread-group-started'
6024551c0a9SBram Moolenaar        call s:HandleProgramRun(msg)
60345d5f26dSBram Moolenaar      elseif msg =~ '^\^done,value='
60445d5f26dSBram Moolenaar        call s:HandleEvaluate(msg)
60545d5f26dSBram Moolenaar      elseif msg =~ '^\^error,msg='
60645d5f26dSBram Moolenaar        call s:HandleError(msg)
607e09ba7baSBram Moolenaar      endif
608e09ba7baSBram Moolenaar    endif
609e09ba7baSBram Moolenaar  endfor
610e09ba7baSBram Moolenaarendfunc
611e09ba7baSBram Moolenaar
612e09ba7baSBram Moolenaar" Install commands in the current window to control the debugger.
613e09ba7baSBram Moolenaarfunc s:InstallCommands()
614963c1ad5SBram Moolenaar  let save_cpo = &cpo
615963c1ad5SBram Moolenaar  set cpo&vim
616963c1ad5SBram Moolenaar
617e09ba7baSBram Moolenaar  command Break call s:SetBreakpoint()
61871137fedSBram Moolenaar  command Clear call s:ClearBreakpoint()
619e09ba7baSBram Moolenaar  command Step call s:SendCommand('-exec-step')
62045d5f26dSBram Moolenaar  command Over call s:SendCommand('-exec-next')
621e09ba7baSBram Moolenaar  command Finish call s:SendCommand('-exec-finish')
62260e73f2aSBram Moolenaar  command -nargs=* Run call s:Run(<q-args>)
62360e73f2aSBram Moolenaar  command -nargs=* Arguments call s:SendCommand('-exec-arguments ' . <q-args>)
62460e73f2aSBram Moolenaar  command Stop call s:SendCommand('-exec-interrupt')
625b3307b5eSBram Moolenaar
626b3307b5eSBram Moolenaar  " using -exec-continue results in CTRL-C in gdb window not working
627b3307b5eSBram Moolenaar  if s:way == 'prompt'
628b3307b5eSBram Moolenaar    command Continue call s:SendCommand('continue')
629b3307b5eSBram Moolenaar  else
630b3307b5eSBram Moolenaar    command Continue call term_sendkeys(s:gdbbuf, "continue\r")
631b3307b5eSBram Moolenaar  endif
632b3307b5eSBram Moolenaar
63345d5f26dSBram Moolenaar  command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>)
63445d5f26dSBram Moolenaar  command Gdb call win_gotoid(s:gdbwin)
63545d5f26dSBram Moolenaar  command Program call win_gotoid(s:ptywin)
636b3307b5eSBram Moolenaar  command Source call s:GotoSourcewinOrCreateIt()
63771137fedSBram Moolenaar  command Winbar call s:InstallWinbar()
63845d5f26dSBram Moolenaar
63945d5f26dSBram Moolenaar  " TODO: can the K mapping be restored?
64045d5f26dSBram Moolenaar  nnoremap K :Evaluate<CR>
6411b9645deSBram Moolenaar
642f0b03c4eSBram Moolenaar  if has('menu') && &mouse != ''
64371137fedSBram Moolenaar    call s:InstallWinbar()
64471137fedSBram Moolenaar
64571137fedSBram Moolenaar    if !exists('g:termdebug_popup') || g:termdebug_popup != 0
64671137fedSBram Moolenaar      let s:saved_mousemodel = &mousemodel
64771137fedSBram Moolenaar      let &mousemodel = 'popup_setpos'
64871137fedSBram Moolenaar      an 1.200 PopUp.-SEP3-	<Nop>
64971137fedSBram Moolenaar      an 1.210 PopUp.Set\ breakpoint	:Break<CR>
65071137fedSBram Moolenaar      an 1.220 PopUp.Clear\ breakpoint	:Clear<CR>
65171137fedSBram Moolenaar      an 1.230 PopUp.Evaluate		:Evaluate<CR>
65271137fedSBram Moolenaar    endif
65371137fedSBram Moolenaar  endif
654963c1ad5SBram Moolenaar
655963c1ad5SBram Moolenaar  let &cpo = save_cpo
65671137fedSBram Moolenaarendfunc
65771137fedSBram Moolenaar
65871137fedSBram Moolenaarlet s:winbar_winids = []
65971137fedSBram Moolenaar
66071137fedSBram Moolenaar" Install the window toolbar in the current window.
66171137fedSBram Moolenaarfunc s:InstallWinbar()
662c4b533e1SBram Moolenaar  if has('menu') && &mouse != ''
66324a98a0eSBram Moolenaar    nnoremenu WinBar.Step   :Step<CR>
66424a98a0eSBram Moolenaar    nnoremenu WinBar.Next   :Over<CR>
66524a98a0eSBram Moolenaar    nnoremenu WinBar.Finish :Finish<CR>
66624a98a0eSBram Moolenaar    nnoremenu WinBar.Cont   :Continue<CR>
66760e73f2aSBram Moolenaar    nnoremenu WinBar.Stop   :Stop<CR>
66824a98a0eSBram Moolenaar    nnoremenu WinBar.Eval   :Evaluate<CR>
66971137fedSBram Moolenaar    call add(s:winbar_winids, win_getid(winnr()))
670c4b533e1SBram Moolenaar  endif
671e09ba7baSBram Moolenaarendfunc
672e09ba7baSBram Moolenaar
673e09ba7baSBram Moolenaar" Delete installed debugger commands in the current window.
674e09ba7baSBram Moolenaarfunc s:DeleteCommands()
675e09ba7baSBram Moolenaar  delcommand Break
67671137fedSBram Moolenaar  delcommand Clear
677e09ba7baSBram Moolenaar  delcommand Step
67845d5f26dSBram Moolenaar  delcommand Over
679e09ba7baSBram Moolenaar  delcommand Finish
68060e73f2aSBram Moolenaar  delcommand Run
68160e73f2aSBram Moolenaar  delcommand Arguments
68260e73f2aSBram Moolenaar  delcommand Stop
683e09ba7baSBram Moolenaar  delcommand Continue
68445d5f26dSBram Moolenaar  delcommand Evaluate
68545d5f26dSBram Moolenaar  delcommand Gdb
68645d5f26dSBram Moolenaar  delcommand Program
687b3623a38SBram Moolenaar  delcommand Source
68871137fedSBram Moolenaar  delcommand Winbar
68945d5f26dSBram Moolenaar
69045d5f26dSBram Moolenaar  nunmap K
6911b9645deSBram Moolenaar
6921b9645deSBram Moolenaar  if has('menu')
69371137fedSBram Moolenaar    " Remove the WinBar entries from all windows where it was added.
69471137fedSBram Moolenaar    let curwinid = win_getid(winnr())
69571137fedSBram Moolenaar    for winid in s:winbar_winids
69671137fedSBram Moolenaar      if win_gotoid(winid)
6971b9645deSBram Moolenaar        aunmenu WinBar.Step
6981b9645deSBram Moolenaar        aunmenu WinBar.Next
6991b9645deSBram Moolenaar        aunmenu WinBar.Finish
7001b9645deSBram Moolenaar        aunmenu WinBar.Cont
70160e73f2aSBram Moolenaar        aunmenu WinBar.Stop
7021b9645deSBram Moolenaar        aunmenu WinBar.Eval
7031b9645deSBram Moolenaar      endif
70471137fedSBram Moolenaar    endfor
70571137fedSBram Moolenaar    call win_gotoid(curwinid)
70671137fedSBram Moolenaar    let s:winbar_winids = []
70771137fedSBram Moolenaar
70871137fedSBram Moolenaar    if exists('s:saved_mousemodel')
70971137fedSBram Moolenaar      let &mousemodel = s:saved_mousemodel
71071137fedSBram Moolenaar      unlet s:saved_mousemodel
71171137fedSBram Moolenaar      aunmenu PopUp.-SEP3-
71271137fedSBram Moolenaar      aunmenu PopUp.Set\ breakpoint
71371137fedSBram Moolenaar      aunmenu PopUp.Clear\ breakpoint
71471137fedSBram Moolenaar      aunmenu PopUp.Evaluate
71571137fedSBram Moolenaar    endif
71671137fedSBram Moolenaar  endif
7171b9645deSBram Moolenaar
71845d5f26dSBram Moolenaar  exe 'sign unplace ' . s:pc_id
71937402ed5SBram Moolenaar  for [id, entries] in items(s:breakpoints)
72037402ed5SBram Moolenaar    for subid in keys(entries)
72137402ed5SBram Moolenaar      exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid)
72237402ed5SBram Moolenaar    endfor
72345d5f26dSBram Moolenaar  endfor
72445d5f26dSBram Moolenaar  unlet s:breakpoints
72537402ed5SBram Moolenaar  unlet s:breakpoint_locations
726a15b0a93SBram Moolenaar
727a15b0a93SBram Moolenaar  sign undefine debugPC
728a15b0a93SBram Moolenaar  for val in s:BreakpointSigns
729a15b0a93SBram Moolenaar    exe "sign undefine debugBreakpoint" . val
730a15b0a93SBram Moolenaar  endfor
7314551c0a9SBram Moolenaar  let s:BreakpointSigns = []
732e09ba7baSBram Moolenaarendfunc
733e09ba7baSBram Moolenaar
734e09ba7baSBram Moolenaar" :Break - Set a breakpoint at the cursor position.
735e09ba7baSBram Moolenaarfunc s:SetBreakpoint()
73660e73f2aSBram Moolenaar  " Setting a breakpoint may not work while the program is running.
73760e73f2aSBram Moolenaar  " Interrupt to make it work.
73860e73f2aSBram Moolenaar  let do_continue = 0
73960e73f2aSBram Moolenaar  if !s:stopped
74060e73f2aSBram Moolenaar    let do_continue = 1
741b3307b5eSBram Moolenaar    if s:way == 'prompt'
7424551c0a9SBram Moolenaar      call s:PromptInterrupt()
743b3307b5eSBram Moolenaar    else
74460e73f2aSBram Moolenaar      call s:SendCommand('-exec-interrupt')
745b3307b5eSBram Moolenaar    endif
74660e73f2aSBram Moolenaar    sleep 10m
74760e73f2aSBram Moolenaar  endif
748a15b0a93SBram Moolenaar  " Use the fname:lnum format, older gdb can't handle --source.
749a15b0a93SBram Moolenaar  call s:SendCommand('-break-insert '
750a15b0a93SBram Moolenaar        \ . fnameescape(expand('%:p')) . ':' . line('.'))
75160e73f2aSBram Moolenaar  if do_continue
75260e73f2aSBram Moolenaar    call s:SendCommand('-exec-continue')
75360e73f2aSBram Moolenaar  endif
754e09ba7baSBram Moolenaarendfunc
755e09ba7baSBram Moolenaar
75671137fedSBram Moolenaar" :Clear - Delete a breakpoint at the cursor position.
75771137fedSBram Moolenaarfunc s:ClearBreakpoint()
758e09ba7baSBram Moolenaar  let fname = fnameescape(expand('%:p'))
759e09ba7baSBram Moolenaar  let lnum = line('.')
76037402ed5SBram Moolenaar  let bploc = printf('%s:%d', fname, lnum)
76137402ed5SBram Moolenaar  if has_key(s:breakpoint_locations, bploc)
76237402ed5SBram Moolenaar    let idx = 0
76337402ed5SBram Moolenaar    for id in s:breakpoint_locations[bploc]
76437402ed5SBram Moolenaar      if has_key(s:breakpoints, id)
76537402ed5SBram Moolenaar        " Assume this always works, the reply is simply "^done".
76637402ed5SBram Moolenaar        call s:SendCommand('-break-delete ' . id)
76737402ed5SBram Moolenaar        for subid in keys(s:breakpoints[id])
76837402ed5SBram Moolenaar          exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid)
76937402ed5SBram Moolenaar        endfor
77037402ed5SBram Moolenaar        unlet s:breakpoints[id]
77137402ed5SBram Moolenaar        unlet s:breakpoint_locations[bploc][idx]
772e09ba7baSBram Moolenaar        break
77337402ed5SBram Moolenaar      else
77437402ed5SBram Moolenaar	let idx += 1
775e09ba7baSBram Moolenaar      endif
776e09ba7baSBram Moolenaar    endfor
77737402ed5SBram Moolenaar    if empty(s:breakpoint_locations[bploc])
77837402ed5SBram Moolenaar      unlet s:breakpoint_locations[bploc]
77937402ed5SBram Moolenaar    endif
78037402ed5SBram Moolenaar  endif
781e09ba7baSBram Moolenaarendfunc
782e09ba7baSBram Moolenaar
78360e73f2aSBram Moolenaarfunc s:Run(args)
78460e73f2aSBram Moolenaar  if a:args != ''
78560e73f2aSBram Moolenaar    call s:SendCommand('-exec-arguments ' . a:args)
78660e73f2aSBram Moolenaar  endif
78760e73f2aSBram Moolenaar  call s:SendCommand('-exec-run')
78860e73f2aSBram Moolenaarendfunc
78960e73f2aSBram Moolenaar
79051b0f370SBram Moolenaarfunc s:SendEval(expr)
79151b0f370SBram Moolenaar  call s:SendCommand('-data-evaluate-expression "' . a:expr . '"')
79251b0f370SBram Moolenaar  let s:evalexpr = a:expr
79351b0f370SBram Moolenaarendfunc
79451b0f370SBram Moolenaar
79545d5f26dSBram Moolenaar" :Evaluate - evaluate what is under the cursor
79645d5f26dSBram Moolenaarfunc s:Evaluate(range, arg)
79745d5f26dSBram Moolenaar  if a:arg != ''
79845d5f26dSBram Moolenaar    let expr = a:arg
79945d5f26dSBram Moolenaar  elseif a:range == 2
80045d5f26dSBram Moolenaar    let pos = getcurpos()
80145d5f26dSBram Moolenaar    let reg = getreg('v', 1, 1)
80245d5f26dSBram Moolenaar    let regt = getregtype('v')
80345d5f26dSBram Moolenaar    normal! gv"vy
80445d5f26dSBram Moolenaar    let expr = @v
80545d5f26dSBram Moolenaar    call setpos('.', pos)
80645d5f26dSBram Moolenaar    call setreg('v', reg, regt)
80745d5f26dSBram Moolenaar  else
80845d5f26dSBram Moolenaar    let expr = expand('<cexpr>')
80945d5f26dSBram Moolenaar  endif
81022f1d0e3SBram Moolenaar  let s:ignoreEvalError = 0
81151b0f370SBram Moolenaar  call s:SendEval(expr)
81245d5f26dSBram Moolenaarendfunc
81345d5f26dSBram Moolenaar
81422f1d0e3SBram Moolenaarlet s:ignoreEvalError = 0
81551b0f370SBram Moolenaarlet s:evalFromBalloonExpr = 0
81651b0f370SBram Moolenaar
81745d5f26dSBram Moolenaar" Handle the result of data-evaluate-expression
81845d5f26dSBram Moolenaarfunc s:HandleEvaluate(msg)
8191b9645deSBram Moolenaar  let value = substitute(a:msg, '.*value="\(.*\)"', '\1', '')
8201b9645deSBram Moolenaar  let value = substitute(value, '\\"', '"', 'g')
82151b0f370SBram Moolenaar  if s:evalFromBalloonExpr
82251b0f370SBram Moolenaar    if s:evalFromBalloonExprResult == ''
82351b0f370SBram Moolenaar      let s:evalFromBalloonExprResult = s:evalexpr . ': ' . value
82451b0f370SBram Moolenaar    else
82551b0f370SBram Moolenaar      let s:evalFromBalloonExprResult .= ' = ' . value
82651b0f370SBram Moolenaar    endif
82751b0f370SBram Moolenaar    call balloon_show(s:evalFromBalloonExprResult)
82851b0f370SBram Moolenaar  else
8291b9645deSBram Moolenaar    echomsg '"' . s:evalexpr . '": ' . value
83051b0f370SBram Moolenaar  endif
8311b9645deSBram Moolenaar
8327f2e9d7cSBram Moolenaar  if s:evalexpr[0] != '*' && value =~ '^0x' && value != '0x0' && value !~ '"$'
8331b9645deSBram Moolenaar    " Looks like a pointer, also display what it points to.
83422f1d0e3SBram Moolenaar    let s:ignoreEvalError = 1
83551b0f370SBram Moolenaar    call s:SendEval('*' . s:evalexpr)
83651b0f370SBram Moolenaar  else
83751b0f370SBram Moolenaar    let s:evalFromBalloonExpr = 0
8381b9645deSBram Moolenaar  endif
83945d5f26dSBram Moolenaarendfunc
84045d5f26dSBram Moolenaar
84151b0f370SBram Moolenaar" Show a balloon with information of the variable under the mouse pointer,
84251b0f370SBram Moolenaar" if there is any.
84351b0f370SBram Moolenaarfunc TermDebugBalloonExpr()
844b3307b5eSBram Moolenaar  if v:beval_winid != s:sourcewin
845396e829fSBram Moolenaar    return ''
846b3307b5eSBram Moolenaar  endif
847b3307b5eSBram Moolenaar  if !s:stopped
848b3307b5eSBram Moolenaar    " Only evaluate when stopped, otherwise setting a breakpoint using the
849b3307b5eSBram Moolenaar    " mouse triggers a balloon.
850396e829fSBram Moolenaar    return ''
85151b0f370SBram Moolenaar  endif
85251b0f370SBram Moolenaar  let s:evalFromBalloonExpr = 1
85351b0f370SBram Moolenaar  let s:evalFromBalloonExprResult = ''
85422f1d0e3SBram Moolenaar  let s:ignoreEvalError = 1
85522f1d0e3SBram Moolenaar  call s:SendEval(v:beval_text)
85651b0f370SBram Moolenaar  return ''
85751b0f370SBram Moolenaarendfunc
85851b0f370SBram Moolenaar
85945d5f26dSBram Moolenaar" Handle an error.
86045d5f26dSBram Moolenaarfunc s:HandleError(msg)
86122f1d0e3SBram Moolenaar  if s:ignoreEvalError
86251b0f370SBram Moolenaar    " Result of s:SendEval() failed, ignore.
86322f1d0e3SBram Moolenaar    let s:ignoreEvalError = 0
86422f1d0e3SBram Moolenaar    let s:evalFromBalloonExpr = 0
86551b0f370SBram Moolenaar    return
86651b0f370SBram Moolenaar  endif
86745d5f26dSBram Moolenaar  echoerr substitute(a:msg, '.*msg="\(.*\)"', '\1', '')
86845d5f26dSBram Moolenaarendfunc
86945d5f26dSBram Moolenaar
870b3307b5eSBram Moolenaarfunc s:GotoSourcewinOrCreateIt()
871b3307b5eSBram Moolenaar  if !win_gotoid(s:sourcewin)
872c4b533e1SBram Moolenaar    new
873b3307b5eSBram Moolenaar    let s:sourcewin = win_getid(winnr())
874c4b533e1SBram Moolenaar    call s:InstallWinbar()
875c4b533e1SBram Moolenaar  endif
876c4b533e1SBram Moolenaarendfunc
877c4b533e1SBram Moolenaar
878e09ba7baSBram Moolenaar" Handle stopping and running message from gdb.
879e09ba7baSBram Moolenaar" Will update the sign that shows the current position.
880e09ba7baSBram Moolenaarfunc s:HandleCursor(msg)
881fe386641SBram Moolenaar  let wid = win_getid(winnr())
882fe386641SBram Moolenaar
88360e73f2aSBram Moolenaar  if a:msg =~ '^\*stopped'
8844551c0a9SBram Moolenaar    call ch_log('program stopped')
88560e73f2aSBram Moolenaar    let s:stopped = 1
88660e73f2aSBram Moolenaar  elseif a:msg =~ '^\*running'
8874551c0a9SBram Moolenaar    call ch_log('program running')
88860e73f2aSBram Moolenaar    let s:stopped = 0
88960e73f2aSBram Moolenaar  endif
89060e73f2aSBram Moolenaar
891a15b0a93SBram Moolenaar  if a:msg =~ 'fullname='
892a15b0a93SBram Moolenaar    let fname = s:GetFullname(a:msg)
893a15b0a93SBram Moolenaar  else
894a15b0a93SBram Moolenaar    let fname = ''
895a15b0a93SBram Moolenaar  endif
8961b9645deSBram Moolenaar  if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname)
897e09ba7baSBram Moolenaar    let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '')
898fe386641SBram Moolenaar    if lnum =~ '^[0-9]*$'
8994551c0a9SBram Moolenaar    call s:GotoSourcewinOrCreateIt()
9001b9645deSBram Moolenaar      if expand('%:p') != fnamemodify(fname, ':p')
901fe386641SBram Moolenaar        if &modified
902fe386641SBram Moolenaar          " TODO: find existing window
903fe386641SBram Moolenaar          exe 'split ' . fnameescape(fname)
904b3307b5eSBram Moolenaar          let s:sourcewin = win_getid(winnr())
905c4b533e1SBram Moolenaar          call s:InstallWinbar()
906fe386641SBram Moolenaar        else
907fe386641SBram Moolenaar          exe 'edit ' . fnameescape(fname)
908fe386641SBram Moolenaar        endif
909fe386641SBram Moolenaar      endif
910fe386641SBram Moolenaar      exe lnum
91101164a65SBram Moolenaar      exe 'sign unplace ' . s:pc_id
9121b9645deSBram Moolenaar      exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fname
913fe386641SBram Moolenaar      setlocal signcolumn=yes
914fe386641SBram Moolenaar    endif
9154551c0a9SBram Moolenaar  elseif !s:stopped || fname != ''
916fe386641SBram Moolenaar    exe 'sign unplace ' . s:pc_id
917fe386641SBram Moolenaar  endif
918fe386641SBram Moolenaar
919fe386641SBram Moolenaar  call win_gotoid(wid)
920e09ba7baSBram Moolenaarendfunc
921e09ba7baSBram Moolenaar
922de1a8314SBram Moolenaarlet s:BreakpointSigns = []
923a15b0a93SBram Moolenaar
92437402ed5SBram Moolenaarfunc s:CreateBreakpoint(id, subid)
92537402ed5SBram Moolenaar  let nr = printf('%d.%d', a:id, a:subid)
92637402ed5SBram Moolenaar  if index(s:BreakpointSigns, nr) == -1
92737402ed5SBram Moolenaar    call add(s:BreakpointSigns, nr)
92837402ed5SBram Moolenaar    exe "sign define debugBreakpoint" . nr . " text=" . substitute(nr, '\..*', '', '') . " texthl=debugBreakpoint"
929de1a8314SBram Moolenaar  endif
930de1a8314SBram Moolenaarendfunc
931de1a8314SBram Moolenaar
93237402ed5SBram Moolenaarfunc! s:SplitMsg(s)
93337402ed5SBram Moolenaar  return split(a:s, '{.\{-}}\zs')
9345378e1cfSBram Moolenaarendfunction
9355378e1cfSBram Moolenaar
936e09ba7baSBram Moolenaar" Handle setting a breakpoint
937e09ba7baSBram Moolenaar" Will update the sign that shows the breakpoint
938e09ba7baSBram Moolenaarfunc s:HandleNewBreakpoint(msg)
9396dccc962SBram Moolenaar  if a:msg !~ 'fullname='
9406dccc962SBram Moolenaar    " a watch does not have a file name
9416dccc962SBram Moolenaar    return
9426dccc962SBram Moolenaar  endif
9435378e1cfSBram Moolenaar  for msg in s:SplitMsg(a:msg)
9445378e1cfSBram Moolenaar    let fname = s:GetFullname(msg)
9455378e1cfSBram Moolenaar    if empty(fname)
9465378e1cfSBram Moolenaar      continue
9475378e1cfSBram Moolenaar    endif
9485378e1cfSBram Moolenaar    let nr = substitute(msg, '.*number="\([0-9.]*\)\".*', '\1', '')
9495378e1cfSBram Moolenaar    if empty(nr)
950e09ba7baSBram Moolenaar      return
951fe386641SBram Moolenaar    endif
952e09ba7baSBram Moolenaar
95337402ed5SBram Moolenaar    " If "nr" is 123 it becomes "123.0" and subid is "0".
95437402ed5SBram Moolenaar    " If "nr" is 123.4 it becomes "123.4.0" and subid is "4"; "0" is discarded.
95537402ed5SBram Moolenaar    let [id, subid; _] = map(split(nr . '.0', '\.'), 'v:val + 0')
95637402ed5SBram Moolenaar    call s:CreateBreakpoint(id, subid)
95737402ed5SBram Moolenaar
95837402ed5SBram Moolenaar    if has_key(s:breakpoints, id)
95937402ed5SBram Moolenaar      let entries = s:breakpoints[id]
96037402ed5SBram Moolenaar    else
96137402ed5SBram Moolenaar      let entries = {}
96237402ed5SBram Moolenaar      let s:breakpoints[id] = entries
96337402ed5SBram Moolenaar    endif
96437402ed5SBram Moolenaar    if has_key(entries, subid)
96537402ed5SBram Moolenaar      let entry = entries[subid]
966e09ba7baSBram Moolenaar    else
967e09ba7baSBram Moolenaar      let entry = {}
96837402ed5SBram Moolenaar      let entries[subid] = entry
969fe386641SBram Moolenaar    endif
970e09ba7baSBram Moolenaar
9715378e1cfSBram Moolenaar    let lnum = substitute(msg, '.*line="\([^"]*\)".*', '\1', '')
972e09ba7baSBram Moolenaar    let entry['fname'] = fname
973e09ba7baSBram Moolenaar    let entry['lnum'] = lnum
9741b9645deSBram Moolenaar
97537402ed5SBram Moolenaar    let bploc = printf('%s:%d', fname, lnum)
97637402ed5SBram Moolenaar    if !has_key(s:breakpoint_locations, bploc)
97737402ed5SBram Moolenaar      let s:breakpoint_locations[bploc] = []
97837402ed5SBram Moolenaar    endif
97937402ed5SBram Moolenaar    let s:breakpoint_locations[bploc] += [id]
98037402ed5SBram Moolenaar
9811b9645deSBram Moolenaar    if bufloaded(fname)
98237402ed5SBram Moolenaar      call s:PlaceSign(id, subid, entry)
9831b9645deSBram Moolenaar    endif
9845378e1cfSBram Moolenaar  endfor
9851b9645deSBram Moolenaarendfunc
9861b9645deSBram Moolenaar
98737402ed5SBram Moolenaarfunc s:PlaceSign(id, subid, entry)
98837402ed5SBram Moolenaar  let nr = printf('%d.%d', a:id, a:subid)
98937402ed5SBram Moolenaar  exe 'sign place ' . s:Breakpoint2SignNumber(a:id, a:subid) . ' line=' . a:entry['lnum'] . ' name=debugBreakpoint' . nr . ' file=' . a:entry['fname']
9901b9645deSBram Moolenaar  let a:entry['placed'] = 1
991e09ba7baSBram Moolenaarendfunc
992e09ba7baSBram Moolenaar
993e09ba7baSBram Moolenaar" Handle deleting a breakpoint
994e09ba7baSBram Moolenaar" Will remove the sign that shows the breakpoint
995e09ba7baSBram Moolenaarfunc s:HandleBreakpointDelete(msg)
99637402ed5SBram Moolenaar  let id = substitute(a:msg, '.*id="\([0-9]*\)\".*', '\1', '') + 0
99737402ed5SBram Moolenaar  if empty(id)
998e09ba7baSBram Moolenaar    return
999e09ba7baSBram Moolenaar  endif
100037402ed5SBram Moolenaar  if has_key(s:breakpoints, id)
100137402ed5SBram Moolenaar    for [subid, entry] in items(s:breakpoints[id])
10021b9645deSBram Moolenaar      if has_key(entry, 'placed')
100337402ed5SBram Moolenaar        exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid)
10041b9645deSBram Moolenaar        unlet entry['placed']
10051b9645deSBram Moolenaar      endif
10065378e1cfSBram Moolenaar    endfor
100737402ed5SBram Moolenaar    unlet s:breakpoints[id]
100837402ed5SBram Moolenaar  endif
1009c572da5fSBram Moolenaarendfunc
10101b9645deSBram Moolenaar
10114551c0a9SBram Moolenaar" Handle the debugged program starting to run.
10124551c0a9SBram Moolenaar" Will store the process ID in s:pid
10134551c0a9SBram Moolenaarfunc s:HandleProgramRun(msg)
10144551c0a9SBram Moolenaar  let nr = substitute(a:msg, '.*pid="\([0-9]*\)\".*', '\1', '') + 0
10154551c0a9SBram Moolenaar  if nr == 0
10164551c0a9SBram Moolenaar    return
10174551c0a9SBram Moolenaar  endif
10184551c0a9SBram Moolenaar  let s:pid = nr
10194551c0a9SBram Moolenaar  call ch_log('Detected process ID: ' . s:pid)
10204551c0a9SBram Moolenaarendfunc
10214551c0a9SBram Moolenaar
10221b9645deSBram Moolenaar" Handle a BufRead autocommand event: place any signs.
10231b9645deSBram Moolenaarfunc s:BufRead()
10241b9645deSBram Moolenaar  let fname = expand('<afile>:p')
102537402ed5SBram Moolenaar  for [id, entries] in items(s:breakpoints)
102637402ed5SBram Moolenaar    for [subid, entry] in items(entries)
10271b9645deSBram Moolenaar      if entry['fname'] == fname
102837402ed5SBram Moolenaar        call s:PlaceSign(id, subid, entry)
10291b9645deSBram Moolenaar      endif
10301b9645deSBram Moolenaar    endfor
103137402ed5SBram Moolenaar  endfor
10321b9645deSBram Moolenaarendfunc
10331b9645deSBram Moolenaar
10341b9645deSBram Moolenaar" Handle a BufUnloaded autocommand event: unplace any signs.
10351b9645deSBram Moolenaarfunc s:BufUnloaded()
10361b9645deSBram Moolenaar  let fname = expand('<afile>:p')
103737402ed5SBram Moolenaar  for [id, entries] in items(s:breakpoints)
103837402ed5SBram Moolenaar    for [subid, entry] in items(entries)
10391b9645deSBram Moolenaar      if entry['fname'] == fname
10401b9645deSBram Moolenaar        let entry['placed'] = 0
10411b9645deSBram Moolenaar      endif
10421b9645deSBram Moolenaar    endfor
104337402ed5SBram Moolenaar  endfor
10441b9645deSBram Moolenaarendfunc
1045ca4cc018SBram Moolenaar
1046ca4cc018SBram Moolenaarlet &cpo = s:keepcpo
1047ca4cc018SBram Moolenaarunlet s:keepcpo
1048