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
12024a98a0eSBram Moolenaar  if exists('g:termdebug_wide')
12124a98a0eSBram Moolenaar    if &columns < g:termdebug_wide
12238baa3e6SBram Moolenaar      let s:save_columns = &columns
12338baa3e6SBram Moolenaar      let &columns = g:termdebug_wide
12424a98a0eSBram Moolenaar    endif
125b3307b5eSBram Moolenaar    let s:vertical = 1
12638baa3e6SBram Moolenaar  else
127b3307b5eSBram Moolenaar    let s:vertical = 0
12838baa3e6SBram Moolenaar  endif
12938baa3e6SBram Moolenaar
130b3307b5eSBram Moolenaar  " Override using a terminal window by setting g:termdebug_use_prompt to 1.
131b3307b5eSBram Moolenaar  let use_prompt = exists('g:termdebug_use_prompt') && g:termdebug_use_prompt
132b3307b5eSBram Moolenaar  if has('terminal') && !has('win32') && !use_prompt
133b3307b5eSBram Moolenaar    let s:way = 'terminal'
134b3307b5eSBram Moolenaar  else
135b3307b5eSBram Moolenaar    let s:way = 'prompt'
136b3307b5eSBram Moolenaar  endif
137b3307b5eSBram Moolenaar
138b3307b5eSBram Moolenaar  if s:way == 'prompt'
139b3307b5eSBram Moolenaar    call s:StartDebug_prompt(a:dict)
140b3307b5eSBram Moolenaar  else
141b3307b5eSBram Moolenaar    call s:StartDebug_term(a:dict)
142b3307b5eSBram Moolenaar  endif
143b3307b5eSBram Moolenaarendfunc
144b3307b5eSBram Moolenaar
145*ef3c6a5bSBram Moolenaar" Use when debugger didn't start or ended.
146*ef3c6a5bSBram Moolenaarfunc s:CloseBuffers()
147*ef3c6a5bSBram Moolenaar  exe 'bwipe! ' . s:ptybuf
148*ef3c6a5bSBram Moolenaar  exe 'bwipe! ' . s:commbuf
149*ef3c6a5bSBram Moolenaar  unlet! s:gdbwin
150*ef3c6a5bSBram Moolenaarendfunc
151*ef3c6a5bSBram Moolenaar
152b3307b5eSBram Moolenaarfunc s:StartDebug_term(dict)
153b3307b5eSBram Moolenaar  " Open a terminal window without a job, to run the debugged program in.
154fe386641SBram Moolenaar  let s:ptybuf = term_start('NONE', {
155b3307b5eSBram Moolenaar        \ 'term_name': 'debugged program',
156b3307b5eSBram Moolenaar        \ 'vertical': s:vertical,
157fe386641SBram Moolenaar        \ })
158fe386641SBram Moolenaar  if s:ptybuf == 0
159fe386641SBram Moolenaar    echoerr 'Failed to open the program terminal window'
160fe386641SBram Moolenaar    return
161fe386641SBram Moolenaar  endif
162fe386641SBram Moolenaar  let pty = job_info(term_getjob(s:ptybuf))['tty_out']
16345d5f26dSBram Moolenaar  let s:ptywin = win_getid(winnr())
164b3307b5eSBram Moolenaar  if s:vertical
16551b0f370SBram Moolenaar    " Assuming the source code window will get a signcolumn, use two more
16651b0f370SBram Moolenaar    " columns for that, thus one less for the terminal window.
16751b0f370SBram Moolenaar    exe (&columns / 2 - 1) . "wincmd |"
16851b0f370SBram Moolenaar  endif
169fe386641SBram Moolenaar
170fe386641SBram Moolenaar  " Create a hidden terminal window to communicate with gdb
171fe386641SBram Moolenaar  let s:commbuf = term_start('NONE', {
172fe386641SBram Moolenaar        \ 'term_name': 'gdb communication',
173fe386641SBram Moolenaar        \ 'out_cb': function('s:CommOutput'),
174fe386641SBram Moolenaar        \ 'hidden': 1,
175fe386641SBram Moolenaar        \ })
176fe386641SBram Moolenaar  if s:commbuf == 0
177fe386641SBram Moolenaar    echoerr 'Failed to open the communication terminal window'
178fe386641SBram Moolenaar    exe 'bwipe! ' . s:ptybuf
179fe386641SBram Moolenaar    return
180fe386641SBram Moolenaar  endif
181fe386641SBram Moolenaar  let commpty = job_info(term_getjob(s:commbuf))['tty_out']
182c572da5fSBram Moolenaar
183c572da5fSBram Moolenaar  " Open a terminal window to run the debugger.
184c3632516SBram Moolenaar  " Add -quiet to avoid the intro message causing a hit-enter prompt.
18532c67ba7SBram Moolenaar  let gdb_args = get(a:dict, 'gdb_args', [])
18632c67ba7SBram Moolenaar  let proc_args = get(a:dict, 'proc_args', [])
18732c67ba7SBram Moolenaar
18832c67ba7SBram Moolenaar  let cmd = [g:termdebugger, '-quiet', '-tty', pty] + gdb_args
189b3307b5eSBram Moolenaar  call ch_log('executing "' . join(cmd) . '"')
19060e73f2aSBram Moolenaar  let s:gdbbuf = term_start(cmd, {
191fe386641SBram Moolenaar        \ 'term_finish': 'close',
192c572da5fSBram Moolenaar        \ })
19360e73f2aSBram Moolenaar  if s:gdbbuf == 0
194fe386641SBram Moolenaar    echoerr 'Failed to open the gdb terminal window'
195*ef3c6a5bSBram Moolenaar    call s:CloseBuffers()
196fe386641SBram Moolenaar    return
197fe386641SBram Moolenaar  endif
19845d5f26dSBram Moolenaar  let s:gdbwin = win_getid(winnr())
199fe386641SBram Moolenaar
20032c67ba7SBram Moolenaar  " Set arguments to be run
20132c67ba7SBram Moolenaar  if len(proc_args)
20232c67ba7SBram Moolenaar    call term_sendkeys(s:gdbbuf, 'set args ' . join(proc_args) . "\r")
20332c67ba7SBram Moolenaar  endif
20432c67ba7SBram Moolenaar
205fe386641SBram Moolenaar  " Connect gdb to the communication pty, using the GDB/MI interface
20660e73f2aSBram Moolenaar  call term_sendkeys(s:gdbbuf, 'new-ui mi ' . commpty . "\r")
20760e73f2aSBram Moolenaar
2083e4b84d0SBram Moolenaar  " Wait for the response to show up, users may not notice the error and wonder
2093e4b84d0SBram Moolenaar  " why the debugger doesn't work.
2103e4b84d0SBram Moolenaar  let try_count = 0
2113e4b84d0SBram Moolenaar  while 1
212*ef3c6a5bSBram Moolenaar    let gdbproc = term_getjob(s:gdbbuf)
213*ef3c6a5bSBram Moolenaar    if gdbproc == v:null || job_status(gdbproc) !=# 'run'
214*ef3c6a5bSBram Moolenaar      echoerr string(g:termdebugger) . ' exited unexpectedly'
215*ef3c6a5bSBram Moolenaar      call s:CloseBuffers()
216*ef3c6a5bSBram Moolenaar      return
217*ef3c6a5bSBram Moolenaar    endif
218*ef3c6a5bSBram Moolenaar
2193e4b84d0SBram Moolenaar    let response = ''
220b3623a38SBram Moolenaar    for lnum in range(1,200)
2213e4b84d0SBram Moolenaar      if term_getline(s:gdbbuf, lnum) =~ 'new-ui mi '
222f63db65bSBram Moolenaar        " response can be in the same line or the next line
223f63db65bSBram Moolenaar        let response = term_getline(s:gdbbuf, lnum) . term_getline(s:gdbbuf, lnum + 1)
2243e4b84d0SBram Moolenaar        if response =~ 'Undefined command'
225f3ba14ffSBram Moolenaar          echoerr 'Sorry, your gdb is too old, gdb 7.12 is required'
226*ef3c6a5bSBram Moolenaar	  call s:CloseBuffers()
2273e4b84d0SBram Moolenaar          return
2283e4b84d0SBram Moolenaar        endif
2293e4b84d0SBram Moolenaar        if response =~ 'New UI allocated'
2303e4b84d0SBram Moolenaar          " Success!
2313e4b84d0SBram Moolenaar          break
2323e4b84d0SBram Moolenaar        endif
2333e4b84d0SBram Moolenaar      endif
2343e4b84d0SBram Moolenaar    endfor
2353e4b84d0SBram Moolenaar    if response =~ 'New UI allocated'
2363e4b84d0SBram Moolenaar      break
2373e4b84d0SBram Moolenaar    endif
2383e4b84d0SBram Moolenaar    let try_count += 1
2393e4b84d0SBram Moolenaar    if try_count > 100
2403e4b84d0SBram Moolenaar      echoerr 'Cannot check if your gdb works, continuing anyway'
2413e4b84d0SBram Moolenaar      break
2423e4b84d0SBram Moolenaar    endif
2433e4b84d0SBram Moolenaar    sleep 10m
2443e4b84d0SBram Moolenaar  endwhile
2453e4b84d0SBram Moolenaar
24660e73f2aSBram Moolenaar  " Interpret commands while the target is running.  This should usualy only be
24760e73f2aSBram Moolenaar  " exec-interrupt, since many commands don't work properly while the target is
24860e73f2aSBram Moolenaar  " running.
24960e73f2aSBram Moolenaar  call s:SendCommand('-gdb-set mi-async on')
250b3307b5eSBram Moolenaar  " Older gdb uses a different command.
251b3307b5eSBram Moolenaar  call s:SendCommand('-gdb-set target-async on')
252e09ba7baSBram Moolenaar
253f3ba14ffSBram Moolenaar  " Disable pagination, it causes everything to stop at the gdb
254f3ba14ffSBram Moolenaar  " "Type <return> to continue" prompt.
255b3307b5eSBram Moolenaar  call s:SendCommand('set pagination off')
256f3ba14ffSBram Moolenaar
257*ef3c6a5bSBram Moolenaar  call job_setoptions(gdbproc, {'exit_cb': function('s:EndTermDebug')})
258b3307b5eSBram Moolenaar  call s:StartDebugCommon(a:dict)
259b3307b5eSBram Moolenaarendfunc
260b3307b5eSBram Moolenaar
261b3307b5eSBram Moolenaarfunc s:StartDebug_prompt(dict)
262b3307b5eSBram Moolenaar  " Open a window with a prompt buffer to run gdb in.
263b3307b5eSBram Moolenaar  if s:vertical
264b3307b5eSBram Moolenaar    vertical new
265b3307b5eSBram Moolenaar  else
266b3307b5eSBram Moolenaar    new
267b3307b5eSBram Moolenaar  endif
268b3307b5eSBram Moolenaar  let s:gdbwin = win_getid(winnr())
269b3307b5eSBram Moolenaar  let s:promptbuf = bufnr('')
270b3307b5eSBram Moolenaar  call prompt_setprompt(s:promptbuf, 'gdb> ')
271b3307b5eSBram Moolenaar  set buftype=prompt
272b3307b5eSBram Moolenaar  file gdb
273b3307b5eSBram Moolenaar  call prompt_setcallback(s:promptbuf, function('s:PromptCallback'))
274b3307b5eSBram Moolenaar  call prompt_setinterrupt(s:promptbuf, function('s:PromptInterrupt'))
275b3307b5eSBram Moolenaar
276b3307b5eSBram Moolenaar  if s:vertical
277b3307b5eSBram Moolenaar    " Assuming the source code window will get a signcolumn, use two more
278b3307b5eSBram Moolenaar    " columns for that, thus one less for the terminal window.
279b3307b5eSBram Moolenaar    exe (&columns / 2 - 1) . "wincmd |"
280b3307b5eSBram Moolenaar  endif
281b3307b5eSBram Moolenaar
282b3307b5eSBram Moolenaar  " Add -quiet to avoid the intro message causing a hit-enter prompt.
283b3307b5eSBram Moolenaar  let gdb_args = get(a:dict, 'gdb_args', [])
284b3307b5eSBram Moolenaar  let proc_args = get(a:dict, 'proc_args', [])
285b3307b5eSBram Moolenaar
286b3307b5eSBram Moolenaar  let cmd = [g:termdebugger, '-quiet', '--interpreter=mi2'] + gdb_args
287b3307b5eSBram Moolenaar  call ch_log('executing "' . join(cmd) . '"')
288b3307b5eSBram Moolenaar
289b3307b5eSBram Moolenaar  let s:gdbjob = job_start(cmd, {
290b3307b5eSBram Moolenaar        \ 'exit_cb': function('s:EndPromptDebug'),
291b3307b5eSBram Moolenaar        \ 'out_cb': function('s:GdbOutCallback'),
292b3307b5eSBram Moolenaar        \ })
293b3307b5eSBram Moolenaar  if job_status(s:gdbjob) != "run"
294b3307b5eSBram Moolenaar    echoerr 'Failed to start gdb'
295b3307b5eSBram Moolenaar    exe 'bwipe! ' . s:promptbuf
296b3307b5eSBram Moolenaar    return
297b3307b5eSBram Moolenaar  endif
2984551c0a9SBram Moolenaar  " Mark the buffer modified so that it's not easy to close.
2994551c0a9SBram Moolenaar  set modified
300b3307b5eSBram Moolenaar  let s:gdb_channel = job_getchannel(s:gdbjob)
301b3307b5eSBram Moolenaar
302b3307b5eSBram Moolenaar  " Interpret commands while the target is running.  This should usualy only
303b3307b5eSBram Moolenaar  " be exec-interrupt, since many commands don't work properly while the
304b3307b5eSBram Moolenaar  " target is running.
305b3307b5eSBram Moolenaar  call s:SendCommand('-gdb-set mi-async on')
306b3307b5eSBram Moolenaar  " Older gdb uses a different command.
307b3307b5eSBram Moolenaar  call s:SendCommand('-gdb-set target-async on')
308b3307b5eSBram Moolenaar
309b3307b5eSBram Moolenaar  let s:ptybuf = 0
310b3307b5eSBram Moolenaar  if has('win32')
311b3307b5eSBram Moolenaar    " MS-Windows: run in a new console window for maximum compatibility
312b3307b5eSBram Moolenaar    call s:SendCommand('set new-console on')
313b3307b5eSBram Moolenaar  elseif has('terminal')
314b3307b5eSBram Moolenaar    " Unix: Run the debugged program in a terminal window.  Open it below the
315b3307b5eSBram Moolenaar    " gdb window.
316b3307b5eSBram Moolenaar    belowright let s:ptybuf = term_start('NONE', {
317b3307b5eSBram Moolenaar          \ 'term_name': 'debugged program',
318b3307b5eSBram Moolenaar          \ })
319b3307b5eSBram Moolenaar    if s:ptybuf == 0
320b3307b5eSBram Moolenaar      echoerr 'Failed to open the program terminal window'
321b3307b5eSBram Moolenaar      call job_stop(s:gdbjob)
322b3307b5eSBram Moolenaar      return
323b3307b5eSBram Moolenaar    endif
324b3307b5eSBram Moolenaar    let s:ptywin = win_getid(winnr())
325b3307b5eSBram Moolenaar    let pty = job_info(term_getjob(s:ptybuf))['tty_out']
326b3307b5eSBram Moolenaar    call s:SendCommand('tty ' . pty)
327b3307b5eSBram Moolenaar
328b3307b5eSBram Moolenaar    " Since GDB runs in a prompt window, the environment has not been set to
329b3307b5eSBram Moolenaar    " match a terminal window, need to do that now.
330b3307b5eSBram Moolenaar    call s:SendCommand('set env TERM = xterm-color')
331b3307b5eSBram Moolenaar    call s:SendCommand('set env ROWS = ' . winheight(s:ptywin))
332b3307b5eSBram Moolenaar    call s:SendCommand('set env LINES = ' . winheight(s:ptywin))
333b3307b5eSBram Moolenaar    call s:SendCommand('set env COLUMNS = ' . winwidth(s:ptywin))
334b3307b5eSBram Moolenaar    call s:SendCommand('set env COLORS = ' . &t_Co)
335b3307b5eSBram Moolenaar    call s:SendCommand('set env VIM_TERMINAL = ' . v:version)
336b3307b5eSBram Moolenaar  else
337b3307b5eSBram Moolenaar    " TODO: open a new terminal get get the tty name, pass on to gdb
338b3307b5eSBram Moolenaar    call s:SendCommand('show inferior-tty')
339b3307b5eSBram Moolenaar  endif
340b3307b5eSBram Moolenaar  call s:SendCommand('set print pretty on')
341b3307b5eSBram Moolenaar  call s:SendCommand('set breakpoint pending on')
342b3307b5eSBram Moolenaar  " Disable pagination, it causes everything to stop at the gdb
343b3307b5eSBram Moolenaar  call s:SendCommand('set pagination off')
344b3307b5eSBram Moolenaar
345b3307b5eSBram Moolenaar  " Set arguments to be run
346b3307b5eSBram Moolenaar  if len(proc_args)
347b3307b5eSBram Moolenaar    call s:SendCommand('set args ' . join(proc_args))
348b3307b5eSBram Moolenaar  endif
349b3307b5eSBram Moolenaar
350b3307b5eSBram Moolenaar  call s:StartDebugCommon(a:dict)
351b3307b5eSBram Moolenaar  startinsert
352b3307b5eSBram Moolenaarendfunc
353b3307b5eSBram Moolenaar
354b3307b5eSBram Moolenaarfunc s:StartDebugCommon(dict)
35538baa3e6SBram Moolenaar  " Sign used to highlight the line where the program has stopped.
35638baa3e6SBram Moolenaar  " There can be only one.
35738baa3e6SBram Moolenaar  sign define debugPC linehl=debugPC
35838baa3e6SBram Moolenaar
35945d5f26dSBram Moolenaar  " Install debugger commands in the text window.
360b3307b5eSBram Moolenaar  call win_gotoid(s:sourcewin)
361e09ba7baSBram Moolenaar  call s:InstallCommands()
36245d5f26dSBram Moolenaar  call win_gotoid(s:gdbwin)
363e09ba7baSBram Moolenaar
36451b0f370SBram Moolenaar  " Enable showing a balloon with eval info
365246fe03dSBram Moolenaar  if has("balloon_eval") || has("balloon_eval_term")
366246fe03dSBram Moolenaar    set balloonexpr=TermDebugBalloonExpr()
36751b0f370SBram Moolenaar    if has("balloon_eval")
36851b0f370SBram Moolenaar      set ballooneval
369246fe03dSBram Moolenaar    endif
37051b0f370SBram Moolenaar    if has("balloon_eval_term")
37151b0f370SBram Moolenaar      set balloonevalterm
37251b0f370SBram Moolenaar    endif
37351b0f370SBram Moolenaar  endif
37451b0f370SBram Moolenaar
3755378e1cfSBram Moolenaar  " Contains breakpoints that have been placed, key is a string with the GDB
3765378e1cfSBram Moolenaar  " breakpoint number.
37737402ed5SBram Moolenaar  " Each entry is a dict, containing the sub-breakpoints.  Key is the subid.
37837402ed5SBram Moolenaar  " For a breakpoint that is just a number the subid is zero.
37937402ed5SBram Moolenaar  " For a breakpoint "123.4" the id is "123" and subid is "4".
38037402ed5SBram Moolenaar  " Example, when breakpoint "44", "123", "123.1" and "123.2" exist:
38137402ed5SBram Moolenaar  " {'44': {'0': entry}, '123': {'0': entry, '1': entry, '2': entry}}
382e09ba7baSBram Moolenaar  let s:breakpoints = {}
3831b9645deSBram Moolenaar
38437402ed5SBram Moolenaar  " Contains breakpoints by file/lnum.  The key is "fname:lnum".
38537402ed5SBram Moolenaar  " Each entry is a list of breakpoint IDs at that position.
38637402ed5SBram Moolenaar  let s:breakpoint_locations = {}
38737402ed5SBram Moolenaar
3881b9645deSBram Moolenaar  augroup TermDebug
3891b9645deSBram Moolenaar    au BufRead * call s:BufRead()
3901b9645deSBram Moolenaar    au BufUnload * call s:BufUnloaded()
391f07f9e73SBram Moolenaar    au OptionSet background call s:Highlight(0, v:option_old, v:option_new)
3921b9645deSBram Moolenaar  augroup END
39332c67ba7SBram Moolenaar
394b3307b5eSBram Moolenaar  " Run the command if the bang attribute was given and got to the debug
395b3307b5eSBram Moolenaar  " window.
39632c67ba7SBram Moolenaar  if get(a:dict, 'bang', 0)
39732c67ba7SBram Moolenaar    call s:SendCommand('-exec-run')
39832c67ba7SBram Moolenaar    call win_gotoid(s:ptywin)
39932c67ba7SBram Moolenaar  endif
400c572da5fSBram Moolenaarendfunc
401c572da5fSBram Moolenaar
402b3307b5eSBram Moolenaar" Send a command to gdb.  "cmd" is the string without line terminator.
403b3307b5eSBram Moolenaarfunc s:SendCommand(cmd)
404b3307b5eSBram Moolenaar  call ch_log('sending to gdb: ' . a:cmd)
405b3307b5eSBram Moolenaar  if s:way == 'prompt'
406b3307b5eSBram Moolenaar    call ch_sendraw(s:gdb_channel, a:cmd . "\n")
407b3307b5eSBram Moolenaar  else
408b3307b5eSBram Moolenaar    call term_sendkeys(s:commbuf, a:cmd . "\r")
409b3307b5eSBram Moolenaar  endif
410b3307b5eSBram Moolenaarendfunc
411b3307b5eSBram Moolenaar
412b3307b5eSBram Moolenaar" This is global so that a user can create their mappings with this.
413b3307b5eSBram Moolenaarfunc TermDebugSendCommand(cmd)
414b3307b5eSBram Moolenaar  if s:way == 'prompt'
415b3307b5eSBram Moolenaar    call ch_sendraw(s:gdb_channel, a:cmd . "\n")
416b3307b5eSBram Moolenaar  else
417b3307b5eSBram Moolenaar    let do_continue = 0
418b3307b5eSBram Moolenaar    if !s:stopped
419b3307b5eSBram Moolenaar      let do_continue = 1
420b3307b5eSBram Moolenaar      call s:SendCommand('-exec-interrupt')
421b3307b5eSBram Moolenaar      sleep 10m
422b3307b5eSBram Moolenaar    endif
423b3307b5eSBram Moolenaar    call term_sendkeys(s:gdbbuf, a:cmd . "\r")
424b3307b5eSBram Moolenaar    if do_continue
425b3307b5eSBram Moolenaar      Continue
426b3307b5eSBram Moolenaar    endif
427b3307b5eSBram Moolenaar  endif
428b3307b5eSBram Moolenaarendfunc
429b3307b5eSBram Moolenaar
430b3307b5eSBram Moolenaar" Function called when entering a line in the prompt buffer.
431b3307b5eSBram Moolenaarfunc s:PromptCallback(text)
432b3307b5eSBram Moolenaar  call s:SendCommand(a:text)
433b3307b5eSBram Moolenaarendfunc
434b3307b5eSBram Moolenaar
4354551c0a9SBram Moolenaar" Function called when pressing CTRL-C in the prompt buffer and when placing a
4364551c0a9SBram Moolenaar" breakpoint.
437b3307b5eSBram Moolenaarfunc s:PromptInterrupt()
4382ed890f1SBram Moolenaar  call ch_log('Interrupting gdb')
4392ed890f1SBram Moolenaar  if has('win32')
4402ed890f1SBram Moolenaar    " Using job_stop() does not work on MS-Windows, need to send SIGTRAP to
4412ed890f1SBram Moolenaar    " the debugger program so that gdb responds again.
4424551c0a9SBram Moolenaar    if s:pid == 0
4434551c0a9SBram Moolenaar      echoerr 'Cannot interrupt gdb, did not find a process ID'
4444551c0a9SBram Moolenaar    else
4454551c0a9SBram Moolenaar      call debugbreak(s:pid)
4464551c0a9SBram Moolenaar    endif
4472ed890f1SBram Moolenaar  else
4482ed890f1SBram Moolenaar    call job_stop(s:gdbjob, 'int')
4492ed890f1SBram Moolenaar  endif
450b3307b5eSBram Moolenaarendfunc
451b3307b5eSBram Moolenaar
452b3307b5eSBram Moolenaar" Function called when gdb outputs text.
453b3307b5eSBram Moolenaarfunc s:GdbOutCallback(channel, text)
454b3307b5eSBram Moolenaar  call ch_log('received from gdb: ' . a:text)
455b3307b5eSBram Moolenaar
456b3307b5eSBram Moolenaar  " Drop the gdb prompt, we have our own.
457b3307b5eSBram Moolenaar  " Drop status and echo'd commands.
458a15b0a93SBram Moolenaar  if a:text == '(gdb) ' || a:text == '^done' || a:text[0] == '&'
459b3307b5eSBram Moolenaar    return
460b3307b5eSBram Moolenaar  endif
461b3307b5eSBram Moolenaar  if a:text =~ '^^error,msg='
462b3307b5eSBram Moolenaar    let text = s:DecodeMessage(a:text[11:])
463b3307b5eSBram Moolenaar    if exists('s:evalexpr') && text =~ 'A syntax error in expression, near\|No symbol .* in current context'
464b3307b5eSBram Moolenaar      " Silently drop evaluation errors.
465b3307b5eSBram Moolenaar      unlet s:evalexpr
466b3307b5eSBram Moolenaar      return
467b3307b5eSBram Moolenaar    endif
468b3307b5eSBram Moolenaar  elseif a:text[0] == '~'
469b3307b5eSBram Moolenaar    let text = s:DecodeMessage(a:text[1:])
470b3307b5eSBram Moolenaar  else
471b3307b5eSBram Moolenaar    call s:CommOutput(a:channel, a:text)
472b3307b5eSBram Moolenaar    return
473b3307b5eSBram Moolenaar  endif
474b3307b5eSBram Moolenaar
475b3307b5eSBram Moolenaar  let curwinid = win_getid(winnr())
476b3307b5eSBram Moolenaar  call win_gotoid(s:gdbwin)
477b3307b5eSBram Moolenaar
478b3307b5eSBram Moolenaar  " Add the output above the current prompt.
479b3307b5eSBram Moolenaar  call append(line('$') - 1, text)
4804551c0a9SBram Moolenaar  set modified
481b3307b5eSBram Moolenaar
482b3307b5eSBram Moolenaar  call win_gotoid(curwinid)
483b3307b5eSBram Moolenaarendfunc
484b3307b5eSBram Moolenaar
485b3307b5eSBram Moolenaar" Decode a message from gdb.  quotedText starts with a ", return the text up
486b3307b5eSBram Moolenaar" to the next ", unescaping characters.
487b3307b5eSBram Moolenaarfunc s:DecodeMessage(quotedText)
488b3307b5eSBram Moolenaar  if a:quotedText[0] != '"'
489a15b0a93SBram Moolenaar    echoerr 'DecodeMessage(): missing quote in ' . a:quotedText
490b3307b5eSBram Moolenaar    return
491b3307b5eSBram Moolenaar  endif
492b3307b5eSBram Moolenaar  let result = ''
493b3307b5eSBram Moolenaar  let i = 1
494b3307b5eSBram Moolenaar  while a:quotedText[i] != '"' && i < len(a:quotedText)
495b3307b5eSBram Moolenaar    if a:quotedText[i] == '\'
496b3307b5eSBram Moolenaar      let i += 1
497b3307b5eSBram Moolenaar      if a:quotedText[i] == 'n'
498b3307b5eSBram Moolenaar        " drop \n
499b3307b5eSBram Moolenaar        let i += 1
500b3307b5eSBram Moolenaar        continue
501b3307b5eSBram Moolenaar      endif
502b3307b5eSBram Moolenaar    endif
503b3307b5eSBram Moolenaar    let result .= a:quotedText[i]
504b3307b5eSBram Moolenaar    let i += 1
505b3307b5eSBram Moolenaar  endwhile
506b3307b5eSBram Moolenaar  return result
507b3307b5eSBram Moolenaarendfunc
508b3307b5eSBram Moolenaar
509a15b0a93SBram Moolenaar" Extract the "name" value from a gdb message with fullname="name".
510a15b0a93SBram Moolenaarfunc s:GetFullname(msg)
5115378e1cfSBram Moolenaar  if a:msg !~ 'fullname'
5125378e1cfSBram Moolenaar    return ''
5135378e1cfSBram Moolenaar  endif
514a15b0a93SBram Moolenaar  let name = s:DecodeMessage(substitute(a:msg, '.*fullname=', '', ''))
515a15b0a93SBram Moolenaar  if has('win32') && name =~ ':\\\\'
516a15b0a93SBram Moolenaar    " sometimes the name arrives double-escaped
517a15b0a93SBram Moolenaar    let name = substitute(name, '\\\\', '\\', 'g')
518a15b0a93SBram Moolenaar  endif
519a15b0a93SBram Moolenaar  return name
520a15b0a93SBram Moolenaarendfunc
521a15b0a93SBram Moolenaar
522b3307b5eSBram Moolenaarfunc s:EndTermDebug(job, status)
523fe386641SBram Moolenaar  exe 'bwipe! ' . s:commbuf
524b3623a38SBram Moolenaar  unlet s:gdbwin
525e09ba7baSBram Moolenaar
526b3307b5eSBram Moolenaar  call s:EndDebugCommon()
527b3307b5eSBram Moolenaarendfunc
528b3307b5eSBram Moolenaar
529b3307b5eSBram Moolenaarfunc s:EndDebugCommon()
530e09ba7baSBram Moolenaar  let curwinid = win_getid(winnr())
531e09ba7baSBram Moolenaar
532b3307b5eSBram Moolenaar  if exists('s:ptybuf') && s:ptybuf
533b3307b5eSBram Moolenaar    exe 'bwipe! ' . s:ptybuf
534b3307b5eSBram Moolenaar  endif
535b3307b5eSBram Moolenaar
536b3307b5eSBram Moolenaar  call win_gotoid(s:sourcewin)
537e09ba7baSBram Moolenaar  let &signcolumn = s:startsigncolumn
538e09ba7baSBram Moolenaar  call s:DeleteCommands()
539e09ba7baSBram Moolenaar
540e09ba7baSBram Moolenaar  call win_gotoid(curwinid)
541b3307b5eSBram Moolenaar
54238baa3e6SBram Moolenaar  if s:save_columns > 0
54338baa3e6SBram Moolenaar    let &columns = s:save_columns
54438baa3e6SBram Moolenaar  endif
5451b9645deSBram Moolenaar
546246fe03dSBram Moolenaar  if has("balloon_eval") || has("balloon_eval_term")
547246fe03dSBram Moolenaar    set balloonexpr=
54851b0f370SBram Moolenaar    if has("balloon_eval")
54951b0f370SBram Moolenaar      set noballooneval
550246fe03dSBram Moolenaar    endif
55151b0f370SBram Moolenaar    if has("balloon_eval_term")
55251b0f370SBram Moolenaar      set noballoonevalterm
55351b0f370SBram Moolenaar    endif
55451b0f370SBram Moolenaar  endif
55551b0f370SBram Moolenaar
5561b9645deSBram Moolenaar  au! TermDebug
557fe386641SBram Moolenaarendfunc
558fe386641SBram Moolenaar
559b3307b5eSBram Moolenaarfunc s:EndPromptDebug(job, status)
560b3307b5eSBram Moolenaar  let curwinid = win_getid(winnr())
561b3307b5eSBram Moolenaar  call win_gotoid(s:gdbwin)
5624551c0a9SBram Moolenaar  set nomodified
563b3307b5eSBram Moolenaar  close
564b3307b5eSBram Moolenaar  if curwinid != s:gdbwin
565b3307b5eSBram Moolenaar    call win_gotoid(curwinid)
566b3307b5eSBram Moolenaar  endif
567b3307b5eSBram Moolenaar
568b3307b5eSBram Moolenaar  call s:EndDebugCommon()
569b3307b5eSBram Moolenaar  unlet s:gdbwin
570b3307b5eSBram Moolenaar  call ch_log("Returning from EndPromptDebug()")
571b3307b5eSBram Moolenaarendfunc
572b3307b5eSBram Moolenaar
573fe386641SBram Moolenaar" Handle a message received from gdb on the GDB/MI interface.
574fe386641SBram Moolenaarfunc s:CommOutput(chan, msg)
575fe386641SBram Moolenaar  let msgs = split(a:msg, "\r")
576fe386641SBram Moolenaar
577fe386641SBram Moolenaar  for msg in msgs
578fe386641SBram Moolenaar    " remove prefixed NL
579fe386641SBram Moolenaar    if msg[0] == "\n"
580fe386641SBram Moolenaar      let msg = msg[1:]
581fe386641SBram Moolenaar    endif
582fe386641SBram Moolenaar    if msg != ''
5831b9645deSBram Moolenaar      if msg =~ '^\(\*stopped\|\*running\|=thread-selected\)'
584e09ba7baSBram Moolenaar        call s:HandleCursor(msg)
58545d5f26dSBram Moolenaar      elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,'
586e09ba7baSBram Moolenaar        call s:HandleNewBreakpoint(msg)
587e09ba7baSBram Moolenaar      elseif msg =~ '^=breakpoint-deleted,'
588e09ba7baSBram Moolenaar        call s:HandleBreakpointDelete(msg)
5894551c0a9SBram Moolenaar      elseif msg =~ '^=thread-group-started'
5904551c0a9SBram Moolenaar        call s:HandleProgramRun(msg)
59145d5f26dSBram Moolenaar      elseif msg =~ '^\^done,value='
59245d5f26dSBram Moolenaar        call s:HandleEvaluate(msg)
59345d5f26dSBram Moolenaar      elseif msg =~ '^\^error,msg='
59445d5f26dSBram Moolenaar        call s:HandleError(msg)
595e09ba7baSBram Moolenaar      endif
596e09ba7baSBram Moolenaar    endif
597e09ba7baSBram Moolenaar  endfor
598e09ba7baSBram Moolenaarendfunc
599e09ba7baSBram Moolenaar
600e09ba7baSBram Moolenaar" Install commands in the current window to control the debugger.
601e09ba7baSBram Moolenaarfunc s:InstallCommands()
602963c1ad5SBram Moolenaar  let save_cpo = &cpo
603963c1ad5SBram Moolenaar  set cpo&vim
604963c1ad5SBram Moolenaar
605e09ba7baSBram Moolenaar  command Break call s:SetBreakpoint()
60671137fedSBram Moolenaar  command Clear call s:ClearBreakpoint()
607e09ba7baSBram Moolenaar  command Step call s:SendCommand('-exec-step')
60845d5f26dSBram Moolenaar  command Over call s:SendCommand('-exec-next')
609e09ba7baSBram Moolenaar  command Finish call s:SendCommand('-exec-finish')
61060e73f2aSBram Moolenaar  command -nargs=* Run call s:Run(<q-args>)
61160e73f2aSBram Moolenaar  command -nargs=* Arguments call s:SendCommand('-exec-arguments ' . <q-args>)
61260e73f2aSBram Moolenaar  command Stop call s:SendCommand('-exec-interrupt')
613b3307b5eSBram Moolenaar
614b3307b5eSBram Moolenaar  " using -exec-continue results in CTRL-C in gdb window not working
615b3307b5eSBram Moolenaar  if s:way == 'prompt'
616b3307b5eSBram Moolenaar    command Continue call s:SendCommand('continue')
617b3307b5eSBram Moolenaar  else
618b3307b5eSBram Moolenaar    command Continue call term_sendkeys(s:gdbbuf, "continue\r")
619b3307b5eSBram Moolenaar  endif
620b3307b5eSBram Moolenaar
62145d5f26dSBram Moolenaar  command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>)
62245d5f26dSBram Moolenaar  command Gdb call win_gotoid(s:gdbwin)
62345d5f26dSBram Moolenaar  command Program call win_gotoid(s:ptywin)
624b3307b5eSBram Moolenaar  command Source call s:GotoSourcewinOrCreateIt()
62571137fedSBram Moolenaar  command Winbar call s:InstallWinbar()
62645d5f26dSBram Moolenaar
62745d5f26dSBram Moolenaar  " TODO: can the K mapping be restored?
62845d5f26dSBram Moolenaar  nnoremap K :Evaluate<CR>
6291b9645deSBram Moolenaar
630f0b03c4eSBram Moolenaar  if has('menu') && &mouse != ''
63171137fedSBram Moolenaar    call s:InstallWinbar()
63271137fedSBram Moolenaar
63371137fedSBram Moolenaar    if !exists('g:termdebug_popup') || g:termdebug_popup != 0
63471137fedSBram Moolenaar      let s:saved_mousemodel = &mousemodel
63571137fedSBram Moolenaar      let &mousemodel = 'popup_setpos'
63671137fedSBram Moolenaar      an 1.200 PopUp.-SEP3-	<Nop>
63771137fedSBram Moolenaar      an 1.210 PopUp.Set\ breakpoint	:Break<CR>
63871137fedSBram Moolenaar      an 1.220 PopUp.Clear\ breakpoint	:Clear<CR>
63971137fedSBram Moolenaar      an 1.230 PopUp.Evaluate		:Evaluate<CR>
64071137fedSBram Moolenaar    endif
64171137fedSBram Moolenaar  endif
642963c1ad5SBram Moolenaar
643963c1ad5SBram Moolenaar  let &cpo = save_cpo
64471137fedSBram Moolenaarendfunc
64571137fedSBram Moolenaar
64671137fedSBram Moolenaarlet s:winbar_winids = []
64771137fedSBram Moolenaar
64871137fedSBram Moolenaar" Install the window toolbar in the current window.
64971137fedSBram Moolenaarfunc s:InstallWinbar()
650c4b533e1SBram Moolenaar  if has('menu') && &mouse != ''
65124a98a0eSBram Moolenaar    nnoremenu WinBar.Step   :Step<CR>
65224a98a0eSBram Moolenaar    nnoremenu WinBar.Next   :Over<CR>
65324a98a0eSBram Moolenaar    nnoremenu WinBar.Finish :Finish<CR>
65424a98a0eSBram Moolenaar    nnoremenu WinBar.Cont   :Continue<CR>
65560e73f2aSBram Moolenaar    nnoremenu WinBar.Stop   :Stop<CR>
65624a98a0eSBram Moolenaar    nnoremenu WinBar.Eval   :Evaluate<CR>
65771137fedSBram Moolenaar    call add(s:winbar_winids, win_getid(winnr()))
658c4b533e1SBram Moolenaar  endif
659e09ba7baSBram Moolenaarendfunc
660e09ba7baSBram Moolenaar
661e09ba7baSBram Moolenaar" Delete installed debugger commands in the current window.
662e09ba7baSBram Moolenaarfunc s:DeleteCommands()
663e09ba7baSBram Moolenaar  delcommand Break
66471137fedSBram Moolenaar  delcommand Clear
665e09ba7baSBram Moolenaar  delcommand Step
66645d5f26dSBram Moolenaar  delcommand Over
667e09ba7baSBram Moolenaar  delcommand Finish
66860e73f2aSBram Moolenaar  delcommand Run
66960e73f2aSBram Moolenaar  delcommand Arguments
67060e73f2aSBram Moolenaar  delcommand Stop
671e09ba7baSBram Moolenaar  delcommand Continue
67245d5f26dSBram Moolenaar  delcommand Evaluate
67345d5f26dSBram Moolenaar  delcommand Gdb
67445d5f26dSBram Moolenaar  delcommand Program
675b3623a38SBram Moolenaar  delcommand Source
67671137fedSBram Moolenaar  delcommand Winbar
67745d5f26dSBram Moolenaar
67845d5f26dSBram Moolenaar  nunmap K
6791b9645deSBram Moolenaar
6801b9645deSBram Moolenaar  if has('menu')
68171137fedSBram Moolenaar    " Remove the WinBar entries from all windows where it was added.
68271137fedSBram Moolenaar    let curwinid = win_getid(winnr())
68371137fedSBram Moolenaar    for winid in s:winbar_winids
68471137fedSBram Moolenaar      if win_gotoid(winid)
6851b9645deSBram Moolenaar        aunmenu WinBar.Step
6861b9645deSBram Moolenaar        aunmenu WinBar.Next
6871b9645deSBram Moolenaar        aunmenu WinBar.Finish
6881b9645deSBram Moolenaar        aunmenu WinBar.Cont
68960e73f2aSBram Moolenaar        aunmenu WinBar.Stop
6901b9645deSBram Moolenaar        aunmenu WinBar.Eval
6911b9645deSBram Moolenaar      endif
69271137fedSBram Moolenaar    endfor
69371137fedSBram Moolenaar    call win_gotoid(curwinid)
69471137fedSBram Moolenaar    let s:winbar_winids = []
69571137fedSBram Moolenaar
69671137fedSBram Moolenaar    if exists('s:saved_mousemodel')
69771137fedSBram Moolenaar      let &mousemodel = s:saved_mousemodel
69871137fedSBram Moolenaar      unlet s:saved_mousemodel
69971137fedSBram Moolenaar      aunmenu PopUp.-SEP3-
70071137fedSBram Moolenaar      aunmenu PopUp.Set\ breakpoint
70171137fedSBram Moolenaar      aunmenu PopUp.Clear\ breakpoint
70271137fedSBram Moolenaar      aunmenu PopUp.Evaluate
70371137fedSBram Moolenaar    endif
70471137fedSBram Moolenaar  endif
7051b9645deSBram Moolenaar
70645d5f26dSBram Moolenaar  exe 'sign unplace ' . s:pc_id
70737402ed5SBram Moolenaar  for [id, entries] in items(s:breakpoints)
70837402ed5SBram Moolenaar    for subid in keys(entries)
70937402ed5SBram Moolenaar      exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid)
71037402ed5SBram Moolenaar    endfor
71145d5f26dSBram Moolenaar  endfor
71245d5f26dSBram Moolenaar  unlet s:breakpoints
71337402ed5SBram Moolenaar  unlet s:breakpoint_locations
714a15b0a93SBram Moolenaar
715a15b0a93SBram Moolenaar  sign undefine debugPC
716a15b0a93SBram Moolenaar  for val in s:BreakpointSigns
717a15b0a93SBram Moolenaar    exe "sign undefine debugBreakpoint" . val
718a15b0a93SBram Moolenaar  endfor
7194551c0a9SBram Moolenaar  let s:BreakpointSigns = []
720e09ba7baSBram Moolenaarendfunc
721e09ba7baSBram Moolenaar
722e09ba7baSBram Moolenaar" :Break - Set a breakpoint at the cursor position.
723e09ba7baSBram Moolenaarfunc s:SetBreakpoint()
72460e73f2aSBram Moolenaar  " Setting a breakpoint may not work while the program is running.
72560e73f2aSBram Moolenaar  " Interrupt to make it work.
72660e73f2aSBram Moolenaar  let do_continue = 0
72760e73f2aSBram Moolenaar  if !s:stopped
72860e73f2aSBram Moolenaar    let do_continue = 1
729b3307b5eSBram Moolenaar    if s:way == 'prompt'
7304551c0a9SBram Moolenaar      call s:PromptInterrupt()
731b3307b5eSBram Moolenaar    else
73260e73f2aSBram Moolenaar      call s:SendCommand('-exec-interrupt')
733b3307b5eSBram Moolenaar    endif
73460e73f2aSBram Moolenaar    sleep 10m
73560e73f2aSBram Moolenaar  endif
736a15b0a93SBram Moolenaar  " Use the fname:lnum format, older gdb can't handle --source.
737a15b0a93SBram Moolenaar  call s:SendCommand('-break-insert '
738a15b0a93SBram Moolenaar        \ . fnameescape(expand('%:p')) . ':' . line('.'))
73960e73f2aSBram Moolenaar  if do_continue
74060e73f2aSBram Moolenaar    call s:SendCommand('-exec-continue')
74160e73f2aSBram Moolenaar  endif
742e09ba7baSBram Moolenaarendfunc
743e09ba7baSBram Moolenaar
74471137fedSBram Moolenaar" :Clear - Delete a breakpoint at the cursor position.
74571137fedSBram Moolenaarfunc s:ClearBreakpoint()
746e09ba7baSBram Moolenaar  let fname = fnameescape(expand('%:p'))
747e09ba7baSBram Moolenaar  let lnum = line('.')
74837402ed5SBram Moolenaar  let bploc = printf('%s:%d', fname, lnum)
74937402ed5SBram Moolenaar  if has_key(s:breakpoint_locations, bploc)
75037402ed5SBram Moolenaar    let idx = 0
75137402ed5SBram Moolenaar    for id in s:breakpoint_locations[bploc]
75237402ed5SBram Moolenaar      if has_key(s:breakpoints, id)
75337402ed5SBram Moolenaar        " Assume this always works, the reply is simply "^done".
75437402ed5SBram Moolenaar        call s:SendCommand('-break-delete ' . id)
75537402ed5SBram Moolenaar        for subid in keys(s:breakpoints[id])
75637402ed5SBram Moolenaar          exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid)
75737402ed5SBram Moolenaar        endfor
75837402ed5SBram Moolenaar        unlet s:breakpoints[id]
75937402ed5SBram Moolenaar        unlet s:breakpoint_locations[bploc][idx]
760e09ba7baSBram Moolenaar        break
76137402ed5SBram Moolenaar      else
76237402ed5SBram Moolenaar	let idx += 1
763e09ba7baSBram Moolenaar      endif
764e09ba7baSBram Moolenaar    endfor
76537402ed5SBram Moolenaar    if empty(s:breakpoint_locations[bploc])
76637402ed5SBram Moolenaar      unlet s:breakpoint_locations[bploc]
76737402ed5SBram Moolenaar    endif
76837402ed5SBram Moolenaar  endif
769e09ba7baSBram Moolenaarendfunc
770e09ba7baSBram Moolenaar
77160e73f2aSBram Moolenaarfunc s:Run(args)
77260e73f2aSBram Moolenaar  if a:args != ''
77360e73f2aSBram Moolenaar    call s:SendCommand('-exec-arguments ' . a:args)
77460e73f2aSBram Moolenaar  endif
77560e73f2aSBram Moolenaar  call s:SendCommand('-exec-run')
77660e73f2aSBram Moolenaarendfunc
77760e73f2aSBram Moolenaar
77851b0f370SBram Moolenaarfunc s:SendEval(expr)
77951b0f370SBram Moolenaar  call s:SendCommand('-data-evaluate-expression "' . a:expr . '"')
78051b0f370SBram Moolenaar  let s:evalexpr = a:expr
78151b0f370SBram Moolenaarendfunc
78251b0f370SBram Moolenaar
78345d5f26dSBram Moolenaar" :Evaluate - evaluate what is under the cursor
78445d5f26dSBram Moolenaarfunc s:Evaluate(range, arg)
78545d5f26dSBram Moolenaar  if a:arg != ''
78645d5f26dSBram Moolenaar    let expr = a:arg
78745d5f26dSBram Moolenaar  elseif a:range == 2
78845d5f26dSBram Moolenaar    let pos = getcurpos()
78945d5f26dSBram Moolenaar    let reg = getreg('v', 1, 1)
79045d5f26dSBram Moolenaar    let regt = getregtype('v')
79145d5f26dSBram Moolenaar    normal! gv"vy
79245d5f26dSBram Moolenaar    let expr = @v
79345d5f26dSBram Moolenaar    call setpos('.', pos)
79445d5f26dSBram Moolenaar    call setreg('v', reg, regt)
79545d5f26dSBram Moolenaar  else
79645d5f26dSBram Moolenaar    let expr = expand('<cexpr>')
79745d5f26dSBram Moolenaar  endif
79822f1d0e3SBram Moolenaar  let s:ignoreEvalError = 0
79951b0f370SBram Moolenaar  call s:SendEval(expr)
80045d5f26dSBram Moolenaarendfunc
80145d5f26dSBram Moolenaar
80222f1d0e3SBram Moolenaarlet s:ignoreEvalError = 0
80351b0f370SBram Moolenaarlet s:evalFromBalloonExpr = 0
80451b0f370SBram Moolenaar
80545d5f26dSBram Moolenaar" Handle the result of data-evaluate-expression
80645d5f26dSBram Moolenaarfunc s:HandleEvaluate(msg)
8071b9645deSBram Moolenaar  let value = substitute(a:msg, '.*value="\(.*\)"', '\1', '')
8081b9645deSBram Moolenaar  let value = substitute(value, '\\"', '"', 'g')
80951b0f370SBram Moolenaar  if s:evalFromBalloonExpr
81051b0f370SBram Moolenaar    if s:evalFromBalloonExprResult == ''
81151b0f370SBram Moolenaar      let s:evalFromBalloonExprResult = s:evalexpr . ': ' . value
81251b0f370SBram Moolenaar    else
81351b0f370SBram Moolenaar      let s:evalFromBalloonExprResult .= ' = ' . value
81451b0f370SBram Moolenaar    endif
81551b0f370SBram Moolenaar    call balloon_show(s:evalFromBalloonExprResult)
81651b0f370SBram Moolenaar  else
8171b9645deSBram Moolenaar    echomsg '"' . s:evalexpr . '": ' . value
81851b0f370SBram Moolenaar  endif
8191b9645deSBram Moolenaar
8207f2e9d7cSBram Moolenaar  if s:evalexpr[0] != '*' && value =~ '^0x' && value != '0x0' && value !~ '"$'
8211b9645deSBram Moolenaar    " Looks like a pointer, also display what it points to.
82222f1d0e3SBram Moolenaar    let s:ignoreEvalError = 1
82351b0f370SBram Moolenaar    call s:SendEval('*' . s:evalexpr)
82451b0f370SBram Moolenaar  else
82551b0f370SBram Moolenaar    let s:evalFromBalloonExpr = 0
8261b9645deSBram Moolenaar  endif
82745d5f26dSBram Moolenaarendfunc
82845d5f26dSBram Moolenaar
82951b0f370SBram Moolenaar" Show a balloon with information of the variable under the mouse pointer,
83051b0f370SBram Moolenaar" if there is any.
83151b0f370SBram Moolenaarfunc TermDebugBalloonExpr()
832b3307b5eSBram Moolenaar  if v:beval_winid != s:sourcewin
833b3307b5eSBram Moolenaar    return
834b3307b5eSBram Moolenaar  endif
835b3307b5eSBram Moolenaar  if !s:stopped
836b3307b5eSBram Moolenaar    " Only evaluate when stopped, otherwise setting a breakpoint using the
837b3307b5eSBram Moolenaar    " mouse triggers a balloon.
83851b0f370SBram Moolenaar    return
83951b0f370SBram Moolenaar  endif
84051b0f370SBram Moolenaar  let s:evalFromBalloonExpr = 1
84151b0f370SBram Moolenaar  let s:evalFromBalloonExprResult = ''
84222f1d0e3SBram Moolenaar  let s:ignoreEvalError = 1
84322f1d0e3SBram Moolenaar  call s:SendEval(v:beval_text)
84451b0f370SBram Moolenaar  return ''
84551b0f370SBram Moolenaarendfunc
84651b0f370SBram Moolenaar
84745d5f26dSBram Moolenaar" Handle an error.
84845d5f26dSBram Moolenaarfunc s:HandleError(msg)
84922f1d0e3SBram Moolenaar  if s:ignoreEvalError
85051b0f370SBram Moolenaar    " Result of s:SendEval() failed, ignore.
85122f1d0e3SBram Moolenaar    let s:ignoreEvalError = 0
85222f1d0e3SBram Moolenaar    let s:evalFromBalloonExpr = 0
85351b0f370SBram Moolenaar    return
85451b0f370SBram Moolenaar  endif
85545d5f26dSBram Moolenaar  echoerr substitute(a:msg, '.*msg="\(.*\)"', '\1', '')
85645d5f26dSBram Moolenaarendfunc
85745d5f26dSBram Moolenaar
858b3307b5eSBram Moolenaarfunc s:GotoSourcewinOrCreateIt()
859b3307b5eSBram Moolenaar  if !win_gotoid(s:sourcewin)
860c4b533e1SBram Moolenaar    new
861b3307b5eSBram Moolenaar    let s:sourcewin = win_getid(winnr())
862c4b533e1SBram Moolenaar    call s:InstallWinbar()
863c4b533e1SBram Moolenaar  endif
864c4b533e1SBram Moolenaarendfunc
865c4b533e1SBram Moolenaar
866e09ba7baSBram Moolenaar" Handle stopping and running message from gdb.
867e09ba7baSBram Moolenaar" Will update the sign that shows the current position.
868e09ba7baSBram Moolenaarfunc s:HandleCursor(msg)
869fe386641SBram Moolenaar  let wid = win_getid(winnr())
870fe386641SBram Moolenaar
87160e73f2aSBram Moolenaar  if a:msg =~ '^\*stopped'
8724551c0a9SBram Moolenaar    call ch_log('program stopped')
87360e73f2aSBram Moolenaar    let s:stopped = 1
87460e73f2aSBram Moolenaar  elseif a:msg =~ '^\*running'
8754551c0a9SBram Moolenaar    call ch_log('program running')
87660e73f2aSBram Moolenaar    let s:stopped = 0
87760e73f2aSBram Moolenaar  endif
87860e73f2aSBram Moolenaar
879a15b0a93SBram Moolenaar  if a:msg =~ 'fullname='
880a15b0a93SBram Moolenaar    let fname = s:GetFullname(a:msg)
881a15b0a93SBram Moolenaar  else
882a15b0a93SBram Moolenaar    let fname = ''
883a15b0a93SBram Moolenaar  endif
8841b9645deSBram Moolenaar  if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname)
885e09ba7baSBram Moolenaar    let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '')
886fe386641SBram Moolenaar    if lnum =~ '^[0-9]*$'
8874551c0a9SBram Moolenaar    call s:GotoSourcewinOrCreateIt()
8881b9645deSBram Moolenaar      if expand('%:p') != fnamemodify(fname, ':p')
889fe386641SBram Moolenaar        if &modified
890fe386641SBram Moolenaar          " TODO: find existing window
891fe386641SBram Moolenaar          exe 'split ' . fnameescape(fname)
892b3307b5eSBram Moolenaar          let s:sourcewin = win_getid(winnr())
893c4b533e1SBram Moolenaar          call s:InstallWinbar()
894fe386641SBram Moolenaar        else
895fe386641SBram Moolenaar          exe 'edit ' . fnameescape(fname)
896fe386641SBram Moolenaar        endif
897fe386641SBram Moolenaar      endif
898fe386641SBram Moolenaar      exe lnum
89901164a65SBram Moolenaar      exe 'sign unplace ' . s:pc_id
9001b9645deSBram Moolenaar      exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fname
901fe386641SBram Moolenaar      setlocal signcolumn=yes
902fe386641SBram Moolenaar    endif
9034551c0a9SBram Moolenaar  elseif !s:stopped || fname != ''
904fe386641SBram Moolenaar    exe 'sign unplace ' . s:pc_id
905fe386641SBram Moolenaar  endif
906fe386641SBram Moolenaar
907fe386641SBram Moolenaar  call win_gotoid(wid)
908e09ba7baSBram Moolenaarendfunc
909e09ba7baSBram Moolenaar
910de1a8314SBram Moolenaarlet s:BreakpointSigns = []
911a15b0a93SBram Moolenaar
91237402ed5SBram Moolenaarfunc s:CreateBreakpoint(id, subid)
91337402ed5SBram Moolenaar  let nr = printf('%d.%d', a:id, a:subid)
91437402ed5SBram Moolenaar  if index(s:BreakpointSigns, nr) == -1
91537402ed5SBram Moolenaar    call add(s:BreakpointSigns, nr)
91637402ed5SBram Moolenaar    exe "sign define debugBreakpoint" . nr . " text=" . substitute(nr, '\..*', '', '') . " texthl=debugBreakpoint"
917de1a8314SBram Moolenaar  endif
918de1a8314SBram Moolenaarendfunc
919de1a8314SBram Moolenaar
92037402ed5SBram Moolenaarfunc! s:SplitMsg(s)
92137402ed5SBram Moolenaar  return split(a:s, '{.\{-}}\zs')
9225378e1cfSBram Moolenaarendfunction
9235378e1cfSBram Moolenaar
924e09ba7baSBram Moolenaar" Handle setting a breakpoint
925e09ba7baSBram Moolenaar" Will update the sign that shows the breakpoint
926e09ba7baSBram Moolenaarfunc s:HandleNewBreakpoint(msg)
9276dccc962SBram Moolenaar  if a:msg !~ 'fullname='
9286dccc962SBram Moolenaar    " a watch does not have a file name
9296dccc962SBram Moolenaar    return
9306dccc962SBram Moolenaar  endif
9315378e1cfSBram Moolenaar  for msg in s:SplitMsg(a:msg)
9325378e1cfSBram Moolenaar    let fname = s:GetFullname(msg)
9335378e1cfSBram Moolenaar    if empty(fname)
9345378e1cfSBram Moolenaar      continue
9355378e1cfSBram Moolenaar    endif
9365378e1cfSBram Moolenaar    let nr = substitute(msg, '.*number="\([0-9.]*\)\".*', '\1', '')
9375378e1cfSBram Moolenaar    if empty(nr)
938e09ba7baSBram Moolenaar      return
939fe386641SBram Moolenaar    endif
940e09ba7baSBram Moolenaar
94137402ed5SBram Moolenaar    " If "nr" is 123 it becomes "123.0" and subid is "0".
94237402ed5SBram Moolenaar    " If "nr" is 123.4 it becomes "123.4.0" and subid is "4"; "0" is discarded.
94337402ed5SBram Moolenaar    let [id, subid; _] = map(split(nr . '.0', '\.'), 'v:val + 0')
94437402ed5SBram Moolenaar    call s:CreateBreakpoint(id, subid)
94537402ed5SBram Moolenaar
94637402ed5SBram Moolenaar    if has_key(s:breakpoints, id)
94737402ed5SBram Moolenaar      let entries = s:breakpoints[id]
94837402ed5SBram Moolenaar    else
94937402ed5SBram Moolenaar      let entries = {}
95037402ed5SBram Moolenaar      let s:breakpoints[id] = entries
95137402ed5SBram Moolenaar    endif
95237402ed5SBram Moolenaar    if has_key(entries, subid)
95337402ed5SBram Moolenaar      let entry = entries[subid]
954e09ba7baSBram Moolenaar    else
955e09ba7baSBram Moolenaar      let entry = {}
95637402ed5SBram Moolenaar      let entries[subid] = entry
957fe386641SBram Moolenaar    endif
958e09ba7baSBram Moolenaar
9595378e1cfSBram Moolenaar    let lnum = substitute(msg, '.*line="\([^"]*\)".*', '\1', '')
960e09ba7baSBram Moolenaar    let entry['fname'] = fname
961e09ba7baSBram Moolenaar    let entry['lnum'] = lnum
9621b9645deSBram Moolenaar
96337402ed5SBram Moolenaar    let bploc = printf('%s:%d', fname, lnum)
96437402ed5SBram Moolenaar    if !has_key(s:breakpoint_locations, bploc)
96537402ed5SBram Moolenaar      let s:breakpoint_locations[bploc] = []
96637402ed5SBram Moolenaar    endif
96737402ed5SBram Moolenaar    let s:breakpoint_locations[bploc] += [id]
96837402ed5SBram Moolenaar
9691b9645deSBram Moolenaar    if bufloaded(fname)
97037402ed5SBram Moolenaar      call s:PlaceSign(id, subid, entry)
9711b9645deSBram Moolenaar    endif
9725378e1cfSBram Moolenaar  endfor
9731b9645deSBram Moolenaarendfunc
9741b9645deSBram Moolenaar
97537402ed5SBram Moolenaarfunc s:PlaceSign(id, subid, entry)
97637402ed5SBram Moolenaar  let nr = printf('%d.%d', a:id, a:subid)
97737402ed5SBram Moolenaar  exe 'sign place ' . s:Breakpoint2SignNumber(a:id, a:subid) . ' line=' . a:entry['lnum'] . ' name=debugBreakpoint' . nr . ' file=' . a:entry['fname']
9781b9645deSBram Moolenaar  let a:entry['placed'] = 1
979e09ba7baSBram Moolenaarendfunc
980e09ba7baSBram Moolenaar
981e09ba7baSBram Moolenaar" Handle deleting a breakpoint
982e09ba7baSBram Moolenaar" Will remove the sign that shows the breakpoint
983e09ba7baSBram Moolenaarfunc s:HandleBreakpointDelete(msg)
98437402ed5SBram Moolenaar  let id = substitute(a:msg, '.*id="\([0-9]*\)\".*', '\1', '') + 0
98537402ed5SBram Moolenaar  if empty(id)
986e09ba7baSBram Moolenaar    return
987e09ba7baSBram Moolenaar  endif
98837402ed5SBram Moolenaar  if has_key(s:breakpoints, id)
98937402ed5SBram Moolenaar    for [subid, entry] in items(s:breakpoints[id])
9901b9645deSBram Moolenaar      if has_key(entry, 'placed')
99137402ed5SBram Moolenaar        exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid)
9921b9645deSBram Moolenaar        unlet entry['placed']
9931b9645deSBram Moolenaar      endif
9945378e1cfSBram Moolenaar    endfor
99537402ed5SBram Moolenaar    unlet s:breakpoints[id]
99637402ed5SBram Moolenaar  endif
997c572da5fSBram Moolenaarendfunc
9981b9645deSBram Moolenaar
9994551c0a9SBram Moolenaar" Handle the debugged program starting to run.
10004551c0a9SBram Moolenaar" Will store the process ID in s:pid
10014551c0a9SBram Moolenaarfunc s:HandleProgramRun(msg)
10024551c0a9SBram Moolenaar  let nr = substitute(a:msg, '.*pid="\([0-9]*\)\".*', '\1', '') + 0
10034551c0a9SBram Moolenaar  if nr == 0
10044551c0a9SBram Moolenaar    return
10054551c0a9SBram Moolenaar  endif
10064551c0a9SBram Moolenaar  let s:pid = nr
10074551c0a9SBram Moolenaar  call ch_log('Detected process ID: ' . s:pid)
10084551c0a9SBram Moolenaarendfunc
10094551c0a9SBram Moolenaar
10101b9645deSBram Moolenaar" Handle a BufRead autocommand event: place any signs.
10111b9645deSBram Moolenaarfunc s:BufRead()
10121b9645deSBram Moolenaar  let fname = expand('<afile>:p')
101337402ed5SBram Moolenaar  for [id, entries] in items(s:breakpoints)
101437402ed5SBram Moolenaar    for [subid, entry] in items(entries)
10151b9645deSBram Moolenaar      if entry['fname'] == fname
101637402ed5SBram Moolenaar        call s:PlaceSign(id, subid, entry)
10171b9645deSBram Moolenaar      endif
10181b9645deSBram Moolenaar    endfor
101937402ed5SBram Moolenaar  endfor
10201b9645deSBram Moolenaarendfunc
10211b9645deSBram Moolenaar
10221b9645deSBram Moolenaar" Handle a BufUnloaded autocommand event: unplace any signs.
10231b9645deSBram Moolenaarfunc s:BufUnloaded()
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
10281b9645deSBram Moolenaar        let entry['placed'] = 0
10291b9645deSBram Moolenaar      endif
10301b9645deSBram Moolenaar    endfor
103137402ed5SBram Moolenaar  endfor
10321b9645deSBram Moolenaarendfunc
1033ca4cc018SBram Moolenaar
1034ca4cc018SBram Moolenaarlet &cpo = s:keepcpo
1035ca4cc018SBram Moolenaarunlet s:keepcpo
1036