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.
77*37402ed5SBram Moolenaar" The breakpoint may contain a dot: 123.4 -> 123004
78*37402ed5SBram Moolenaar" The main breakpoint has a zero subid.
79*37402ed5SBram Moolenaarfunc s:Breakpoint2SignNumber(id, subid)
80*37402ed5SBram 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
145b3307b5eSBram Moolenaarfunc s:StartDebug_term(dict)
146b3307b5eSBram Moolenaar  " Open a terminal window without a job, to run the debugged program in.
147fe386641SBram Moolenaar  let s:ptybuf = term_start('NONE', {
148b3307b5eSBram Moolenaar        \ 'term_name': 'debugged program',
149b3307b5eSBram Moolenaar        \ 'vertical': s:vertical,
150fe386641SBram Moolenaar        \ })
151fe386641SBram Moolenaar  if s:ptybuf == 0
152fe386641SBram Moolenaar    echoerr 'Failed to open the program terminal window'
153fe386641SBram Moolenaar    return
154fe386641SBram Moolenaar  endif
155fe386641SBram Moolenaar  let pty = job_info(term_getjob(s:ptybuf))['tty_out']
15645d5f26dSBram Moolenaar  let s:ptywin = win_getid(winnr())
157b3307b5eSBram Moolenaar  if s:vertical
15851b0f370SBram Moolenaar    " Assuming the source code window will get a signcolumn, use two more
15951b0f370SBram Moolenaar    " columns for that, thus one less for the terminal window.
16051b0f370SBram Moolenaar    exe (&columns / 2 - 1) . "wincmd |"
16151b0f370SBram Moolenaar  endif
162fe386641SBram Moolenaar
163fe386641SBram Moolenaar  " Create a hidden terminal window to communicate with gdb
164fe386641SBram Moolenaar  let s:commbuf = term_start('NONE', {
165fe386641SBram Moolenaar        \ 'term_name': 'gdb communication',
166fe386641SBram Moolenaar        \ 'out_cb': function('s:CommOutput'),
167fe386641SBram Moolenaar        \ 'hidden': 1,
168fe386641SBram Moolenaar        \ })
169fe386641SBram Moolenaar  if s:commbuf == 0
170fe386641SBram Moolenaar    echoerr 'Failed to open the communication terminal window'
171fe386641SBram Moolenaar    exe 'bwipe! ' . s:ptybuf
172fe386641SBram Moolenaar    return
173fe386641SBram Moolenaar  endif
174fe386641SBram Moolenaar  let commpty = job_info(term_getjob(s:commbuf))['tty_out']
175c572da5fSBram Moolenaar
176c572da5fSBram Moolenaar  " Open a terminal window to run the debugger.
177c3632516SBram Moolenaar  " Add -quiet to avoid the intro message causing a hit-enter prompt.
17832c67ba7SBram Moolenaar  let gdb_args = get(a:dict, 'gdb_args', [])
17932c67ba7SBram Moolenaar  let proc_args = get(a:dict, 'proc_args', [])
18032c67ba7SBram Moolenaar
18132c67ba7SBram Moolenaar  let cmd = [g:termdebugger, '-quiet', '-tty', pty] + gdb_args
182b3307b5eSBram Moolenaar  call ch_log('executing "' . join(cmd) . '"')
18360e73f2aSBram Moolenaar  let s:gdbbuf = term_start(cmd, {
184b3307b5eSBram Moolenaar        \ 'exit_cb': function('s:EndTermDebug'),
185fe386641SBram Moolenaar        \ 'term_finish': 'close',
186c572da5fSBram Moolenaar        \ })
18760e73f2aSBram Moolenaar  if s:gdbbuf == 0
188fe386641SBram Moolenaar    echoerr 'Failed to open the gdb terminal window'
189fe386641SBram Moolenaar    exe 'bwipe! ' . s:ptybuf
190fe386641SBram Moolenaar    exe 'bwipe! ' . s:commbuf
191fe386641SBram Moolenaar    return
192fe386641SBram Moolenaar  endif
19345d5f26dSBram Moolenaar  let s:gdbwin = win_getid(winnr())
194fe386641SBram Moolenaar
19532c67ba7SBram Moolenaar  " Set arguments to be run
19632c67ba7SBram Moolenaar  if len(proc_args)
19732c67ba7SBram Moolenaar    call term_sendkeys(s:gdbbuf, 'set args ' . join(proc_args) . "\r")
19832c67ba7SBram Moolenaar  endif
19932c67ba7SBram Moolenaar
200fe386641SBram Moolenaar  " Connect gdb to the communication pty, using the GDB/MI interface
20160e73f2aSBram Moolenaar  call term_sendkeys(s:gdbbuf, 'new-ui mi ' . commpty . "\r")
20260e73f2aSBram Moolenaar
2033e4b84d0SBram Moolenaar  " Wait for the response to show up, users may not notice the error and wonder
2043e4b84d0SBram Moolenaar  " why the debugger doesn't work.
2053e4b84d0SBram Moolenaar  let try_count = 0
2063e4b84d0SBram Moolenaar  while 1
2073e4b84d0SBram Moolenaar    let response = ''
208b3623a38SBram Moolenaar    for lnum in range(1,200)
2093e4b84d0SBram Moolenaar      if term_getline(s:gdbbuf, lnum) =~ 'new-ui mi '
210f63db65bSBram Moolenaar        " response can be in the same line or the next line
211f63db65bSBram Moolenaar        let response = term_getline(s:gdbbuf, lnum) . term_getline(s:gdbbuf, lnum + 1)
2123e4b84d0SBram Moolenaar        if response =~ 'Undefined command'
213f3ba14ffSBram Moolenaar          echoerr 'Sorry, your gdb is too old, gdb 7.12 is required'
2143e4b84d0SBram Moolenaar          exe 'bwipe! ' . s:ptybuf
2153e4b84d0SBram Moolenaar          exe 'bwipe! ' . s:commbuf
2163e4b84d0SBram Moolenaar          return
2173e4b84d0SBram Moolenaar        endif
2183e4b84d0SBram Moolenaar        if response =~ 'New UI allocated'
2193e4b84d0SBram Moolenaar          " Success!
2203e4b84d0SBram Moolenaar          break
2213e4b84d0SBram Moolenaar        endif
2223e4b84d0SBram Moolenaar      endif
2233e4b84d0SBram Moolenaar    endfor
2243e4b84d0SBram Moolenaar    if response =~ 'New UI allocated'
2253e4b84d0SBram Moolenaar      break
2263e4b84d0SBram Moolenaar    endif
2273e4b84d0SBram Moolenaar    let try_count += 1
2283e4b84d0SBram Moolenaar    if try_count > 100
2293e4b84d0SBram Moolenaar      echoerr 'Cannot check if your gdb works, continuing anyway'
2303e4b84d0SBram Moolenaar      break
2313e4b84d0SBram Moolenaar    endif
2323e4b84d0SBram Moolenaar    sleep 10m
2333e4b84d0SBram Moolenaar  endwhile
2343e4b84d0SBram Moolenaar
23560e73f2aSBram Moolenaar  " Interpret commands while the target is running.  This should usualy only be
23660e73f2aSBram Moolenaar  " exec-interrupt, since many commands don't work properly while the target is
23760e73f2aSBram Moolenaar  " running.
23860e73f2aSBram Moolenaar  call s:SendCommand('-gdb-set mi-async on')
239b3307b5eSBram Moolenaar  " Older gdb uses a different command.
240b3307b5eSBram Moolenaar  call s:SendCommand('-gdb-set target-async on')
241e09ba7baSBram Moolenaar
242f3ba14ffSBram Moolenaar  " Disable pagination, it causes everything to stop at the gdb
243f3ba14ffSBram Moolenaar  " "Type <return> to continue" prompt.
244b3307b5eSBram Moolenaar  call s:SendCommand('set pagination off')
245f3ba14ffSBram Moolenaar
246b3307b5eSBram Moolenaar  call s:StartDebugCommon(a:dict)
247b3307b5eSBram Moolenaarendfunc
248b3307b5eSBram Moolenaar
249b3307b5eSBram Moolenaarfunc s:StartDebug_prompt(dict)
250b3307b5eSBram Moolenaar  " Open a window with a prompt buffer to run gdb in.
251b3307b5eSBram Moolenaar  if s:vertical
252b3307b5eSBram Moolenaar    vertical new
253b3307b5eSBram Moolenaar  else
254b3307b5eSBram Moolenaar    new
255b3307b5eSBram Moolenaar  endif
256b3307b5eSBram Moolenaar  let s:gdbwin = win_getid(winnr())
257b3307b5eSBram Moolenaar  let s:promptbuf = bufnr('')
258b3307b5eSBram Moolenaar  call prompt_setprompt(s:promptbuf, 'gdb> ')
259b3307b5eSBram Moolenaar  set buftype=prompt
260b3307b5eSBram Moolenaar  file gdb
261b3307b5eSBram Moolenaar  call prompt_setcallback(s:promptbuf, function('s:PromptCallback'))
262b3307b5eSBram Moolenaar  call prompt_setinterrupt(s:promptbuf, function('s:PromptInterrupt'))
263b3307b5eSBram Moolenaar
264b3307b5eSBram Moolenaar  if s:vertical
265b3307b5eSBram Moolenaar    " Assuming the source code window will get a signcolumn, use two more
266b3307b5eSBram Moolenaar    " columns for that, thus one less for the terminal window.
267b3307b5eSBram Moolenaar    exe (&columns / 2 - 1) . "wincmd |"
268b3307b5eSBram Moolenaar  endif
269b3307b5eSBram Moolenaar
270b3307b5eSBram Moolenaar  " Add -quiet to avoid the intro message causing a hit-enter prompt.
271b3307b5eSBram Moolenaar  let gdb_args = get(a:dict, 'gdb_args', [])
272b3307b5eSBram Moolenaar  let proc_args = get(a:dict, 'proc_args', [])
273b3307b5eSBram Moolenaar
274b3307b5eSBram Moolenaar  let cmd = [g:termdebugger, '-quiet', '--interpreter=mi2'] + gdb_args
275b3307b5eSBram Moolenaar  call ch_log('executing "' . join(cmd) . '"')
276b3307b5eSBram Moolenaar
277b3307b5eSBram Moolenaar  let s:gdbjob = job_start(cmd, {
278b3307b5eSBram Moolenaar        \ 'exit_cb': function('s:EndPromptDebug'),
279b3307b5eSBram Moolenaar        \ 'out_cb': function('s:GdbOutCallback'),
280b3307b5eSBram Moolenaar        \ })
281b3307b5eSBram Moolenaar  if job_status(s:gdbjob) != "run"
282b3307b5eSBram Moolenaar    echoerr 'Failed to start gdb'
283b3307b5eSBram Moolenaar    exe 'bwipe! ' . s:promptbuf
284b3307b5eSBram Moolenaar    return
285b3307b5eSBram Moolenaar  endif
2864551c0a9SBram Moolenaar  " Mark the buffer modified so that it's not easy to close.
2874551c0a9SBram Moolenaar  set modified
288b3307b5eSBram Moolenaar  let s:gdb_channel = job_getchannel(s:gdbjob)
289b3307b5eSBram Moolenaar
290b3307b5eSBram Moolenaar  " Interpret commands while the target is running.  This should usualy only
291b3307b5eSBram Moolenaar  " be exec-interrupt, since many commands don't work properly while the
292b3307b5eSBram Moolenaar  " target is running.
293b3307b5eSBram Moolenaar  call s:SendCommand('-gdb-set mi-async on')
294b3307b5eSBram Moolenaar  " Older gdb uses a different command.
295b3307b5eSBram Moolenaar  call s:SendCommand('-gdb-set target-async on')
296b3307b5eSBram Moolenaar
297b3307b5eSBram Moolenaar  let s:ptybuf = 0
298b3307b5eSBram Moolenaar  if has('win32')
299b3307b5eSBram Moolenaar    " MS-Windows: run in a new console window for maximum compatibility
300b3307b5eSBram Moolenaar    call s:SendCommand('set new-console on')
301b3307b5eSBram Moolenaar  elseif has('terminal')
302b3307b5eSBram Moolenaar    " Unix: Run the debugged program in a terminal window.  Open it below the
303b3307b5eSBram Moolenaar    " gdb window.
304b3307b5eSBram Moolenaar    belowright let s:ptybuf = term_start('NONE', {
305b3307b5eSBram Moolenaar          \ 'term_name': 'debugged program',
306b3307b5eSBram Moolenaar          \ })
307b3307b5eSBram Moolenaar    if s:ptybuf == 0
308b3307b5eSBram Moolenaar      echoerr 'Failed to open the program terminal window'
309b3307b5eSBram Moolenaar      call job_stop(s:gdbjob)
310b3307b5eSBram Moolenaar      return
311b3307b5eSBram Moolenaar    endif
312b3307b5eSBram Moolenaar    let s:ptywin = win_getid(winnr())
313b3307b5eSBram Moolenaar    let pty = job_info(term_getjob(s:ptybuf))['tty_out']
314b3307b5eSBram Moolenaar    call s:SendCommand('tty ' . pty)
315b3307b5eSBram Moolenaar
316b3307b5eSBram Moolenaar    " Since GDB runs in a prompt window, the environment has not been set to
317b3307b5eSBram Moolenaar    " match a terminal window, need to do that now.
318b3307b5eSBram Moolenaar    call s:SendCommand('set env TERM = xterm-color')
319b3307b5eSBram Moolenaar    call s:SendCommand('set env ROWS = ' . winheight(s:ptywin))
320b3307b5eSBram Moolenaar    call s:SendCommand('set env LINES = ' . winheight(s:ptywin))
321b3307b5eSBram Moolenaar    call s:SendCommand('set env COLUMNS = ' . winwidth(s:ptywin))
322b3307b5eSBram Moolenaar    call s:SendCommand('set env COLORS = ' . &t_Co)
323b3307b5eSBram Moolenaar    call s:SendCommand('set env VIM_TERMINAL = ' . v:version)
324b3307b5eSBram Moolenaar  else
325b3307b5eSBram Moolenaar    " TODO: open a new terminal get get the tty name, pass on to gdb
326b3307b5eSBram Moolenaar    call s:SendCommand('show inferior-tty')
327b3307b5eSBram Moolenaar  endif
328b3307b5eSBram Moolenaar  call s:SendCommand('set print pretty on')
329b3307b5eSBram Moolenaar  call s:SendCommand('set breakpoint pending on')
330b3307b5eSBram Moolenaar  " Disable pagination, it causes everything to stop at the gdb
331b3307b5eSBram Moolenaar  call s:SendCommand('set pagination off')
332b3307b5eSBram Moolenaar
333b3307b5eSBram Moolenaar  " Set arguments to be run
334b3307b5eSBram Moolenaar  if len(proc_args)
335b3307b5eSBram Moolenaar    call s:SendCommand('set args ' . join(proc_args))
336b3307b5eSBram Moolenaar  endif
337b3307b5eSBram Moolenaar
338b3307b5eSBram Moolenaar  call s:StartDebugCommon(a:dict)
339b3307b5eSBram Moolenaar  startinsert
340b3307b5eSBram Moolenaarendfunc
341b3307b5eSBram Moolenaar
342b3307b5eSBram Moolenaarfunc s:StartDebugCommon(dict)
34338baa3e6SBram Moolenaar  " Sign used to highlight the line where the program has stopped.
34438baa3e6SBram Moolenaar  " There can be only one.
34538baa3e6SBram Moolenaar  sign define debugPC linehl=debugPC
34638baa3e6SBram Moolenaar
34745d5f26dSBram Moolenaar  " Install debugger commands in the text window.
348b3307b5eSBram Moolenaar  call win_gotoid(s:sourcewin)
349e09ba7baSBram Moolenaar  call s:InstallCommands()
35045d5f26dSBram Moolenaar  call win_gotoid(s:gdbwin)
351e09ba7baSBram Moolenaar
35251b0f370SBram Moolenaar  " Enable showing a balloon with eval info
353246fe03dSBram Moolenaar  if has("balloon_eval") || has("balloon_eval_term")
354246fe03dSBram Moolenaar    set balloonexpr=TermDebugBalloonExpr()
35551b0f370SBram Moolenaar    if has("balloon_eval")
35651b0f370SBram Moolenaar      set ballooneval
357246fe03dSBram Moolenaar    endif
35851b0f370SBram Moolenaar    if has("balloon_eval_term")
35951b0f370SBram Moolenaar      set balloonevalterm
36051b0f370SBram Moolenaar    endif
36151b0f370SBram Moolenaar  endif
36251b0f370SBram Moolenaar
3635378e1cfSBram Moolenaar  " Contains breakpoints that have been placed, key is a string with the GDB
3645378e1cfSBram Moolenaar  " breakpoint number.
365*37402ed5SBram Moolenaar  " Each entry is a dict, containing the sub-breakpoints.  Key is the subid.
366*37402ed5SBram Moolenaar  " For a breakpoint that is just a number the subid is zero.
367*37402ed5SBram Moolenaar  " For a breakpoint "123.4" the id is "123" and subid is "4".
368*37402ed5SBram Moolenaar  " Example, when breakpoint "44", "123", "123.1" and "123.2" exist:
369*37402ed5SBram Moolenaar  " {'44': {'0': entry}, '123': {'0': entry, '1': entry, '2': entry}}
370e09ba7baSBram Moolenaar  let s:breakpoints = {}
3711b9645deSBram Moolenaar
372*37402ed5SBram Moolenaar  " Contains breakpoints by file/lnum.  The key is "fname:lnum".
373*37402ed5SBram Moolenaar  " Each entry is a list of breakpoint IDs at that position.
374*37402ed5SBram Moolenaar  let s:breakpoint_locations = {}
375*37402ed5SBram Moolenaar
3761b9645deSBram Moolenaar  augroup TermDebug
3771b9645deSBram Moolenaar    au BufRead * call s:BufRead()
3781b9645deSBram Moolenaar    au BufUnload * call s:BufUnloaded()
379f07f9e73SBram Moolenaar    au OptionSet background call s:Highlight(0, v:option_old, v:option_new)
3801b9645deSBram Moolenaar  augroup END
38132c67ba7SBram Moolenaar
382b3307b5eSBram Moolenaar  " Run the command if the bang attribute was given and got to the debug
383b3307b5eSBram Moolenaar  " window.
38432c67ba7SBram Moolenaar  if get(a:dict, 'bang', 0)
38532c67ba7SBram Moolenaar    call s:SendCommand('-exec-run')
38632c67ba7SBram Moolenaar    call win_gotoid(s:ptywin)
38732c67ba7SBram Moolenaar  endif
388c572da5fSBram Moolenaarendfunc
389c572da5fSBram Moolenaar
390b3307b5eSBram Moolenaar" Send a command to gdb.  "cmd" is the string without line terminator.
391b3307b5eSBram Moolenaarfunc s:SendCommand(cmd)
392b3307b5eSBram Moolenaar  call ch_log('sending to gdb: ' . a:cmd)
393b3307b5eSBram Moolenaar  if s:way == 'prompt'
394b3307b5eSBram Moolenaar    call ch_sendraw(s:gdb_channel, a:cmd . "\n")
395b3307b5eSBram Moolenaar  else
396b3307b5eSBram Moolenaar    call term_sendkeys(s:commbuf, a:cmd . "\r")
397b3307b5eSBram Moolenaar  endif
398b3307b5eSBram Moolenaarendfunc
399b3307b5eSBram Moolenaar
400b3307b5eSBram Moolenaar" This is global so that a user can create their mappings with this.
401b3307b5eSBram Moolenaarfunc TermDebugSendCommand(cmd)
402b3307b5eSBram Moolenaar  if s:way == 'prompt'
403b3307b5eSBram Moolenaar    call ch_sendraw(s:gdb_channel, a:cmd . "\n")
404b3307b5eSBram Moolenaar  else
405b3307b5eSBram Moolenaar    let do_continue = 0
406b3307b5eSBram Moolenaar    if !s:stopped
407b3307b5eSBram Moolenaar      let do_continue = 1
408b3307b5eSBram Moolenaar      call s:SendCommand('-exec-interrupt')
409b3307b5eSBram Moolenaar      sleep 10m
410b3307b5eSBram Moolenaar    endif
411b3307b5eSBram Moolenaar    call term_sendkeys(s:gdbbuf, a:cmd . "\r")
412b3307b5eSBram Moolenaar    if do_continue
413b3307b5eSBram Moolenaar      Continue
414b3307b5eSBram Moolenaar    endif
415b3307b5eSBram Moolenaar  endif
416b3307b5eSBram Moolenaarendfunc
417b3307b5eSBram Moolenaar
418b3307b5eSBram Moolenaar" Function called when entering a line in the prompt buffer.
419b3307b5eSBram Moolenaarfunc s:PromptCallback(text)
420b3307b5eSBram Moolenaar  call s:SendCommand(a:text)
421b3307b5eSBram Moolenaarendfunc
422b3307b5eSBram Moolenaar
4234551c0a9SBram Moolenaar" Function called when pressing CTRL-C in the prompt buffer and when placing a
4244551c0a9SBram Moolenaar" breakpoint.
425b3307b5eSBram Moolenaarfunc s:PromptInterrupt()
4262ed890f1SBram Moolenaar  call ch_log('Interrupting gdb')
4272ed890f1SBram Moolenaar  if has('win32')
4282ed890f1SBram Moolenaar    " Using job_stop() does not work on MS-Windows, need to send SIGTRAP to
4292ed890f1SBram Moolenaar    " the debugger program so that gdb responds again.
4304551c0a9SBram Moolenaar    if s:pid == 0
4314551c0a9SBram Moolenaar      echoerr 'Cannot interrupt gdb, did not find a process ID'
4324551c0a9SBram Moolenaar    else
4334551c0a9SBram Moolenaar      call debugbreak(s:pid)
4344551c0a9SBram Moolenaar    endif
4352ed890f1SBram Moolenaar  else
4362ed890f1SBram Moolenaar    call job_stop(s:gdbjob, 'int')
4372ed890f1SBram Moolenaar  endif
438b3307b5eSBram Moolenaarendfunc
439b3307b5eSBram Moolenaar
440b3307b5eSBram Moolenaar" Function called when gdb outputs text.
441b3307b5eSBram Moolenaarfunc s:GdbOutCallback(channel, text)
442b3307b5eSBram Moolenaar  call ch_log('received from gdb: ' . a:text)
443b3307b5eSBram Moolenaar
444b3307b5eSBram Moolenaar  " Drop the gdb prompt, we have our own.
445b3307b5eSBram Moolenaar  " Drop status and echo'd commands.
446a15b0a93SBram Moolenaar  if a:text == '(gdb) ' || a:text == '^done' || a:text[0] == '&'
447b3307b5eSBram Moolenaar    return
448b3307b5eSBram Moolenaar  endif
449b3307b5eSBram Moolenaar  if a:text =~ '^^error,msg='
450b3307b5eSBram Moolenaar    let text = s:DecodeMessage(a:text[11:])
451b3307b5eSBram Moolenaar    if exists('s:evalexpr') && text =~ 'A syntax error in expression, near\|No symbol .* in current context'
452b3307b5eSBram Moolenaar      " Silently drop evaluation errors.
453b3307b5eSBram Moolenaar      unlet s:evalexpr
454b3307b5eSBram Moolenaar      return
455b3307b5eSBram Moolenaar    endif
456b3307b5eSBram Moolenaar  elseif a:text[0] == '~'
457b3307b5eSBram Moolenaar    let text = s:DecodeMessage(a:text[1:])
458b3307b5eSBram Moolenaar  else
459b3307b5eSBram Moolenaar    call s:CommOutput(a:channel, a:text)
460b3307b5eSBram Moolenaar    return
461b3307b5eSBram Moolenaar  endif
462b3307b5eSBram Moolenaar
463b3307b5eSBram Moolenaar  let curwinid = win_getid(winnr())
464b3307b5eSBram Moolenaar  call win_gotoid(s:gdbwin)
465b3307b5eSBram Moolenaar
466b3307b5eSBram Moolenaar  " Add the output above the current prompt.
467b3307b5eSBram Moolenaar  call append(line('$') - 1, text)
4684551c0a9SBram Moolenaar  set modified
469b3307b5eSBram Moolenaar
470b3307b5eSBram Moolenaar  call win_gotoid(curwinid)
471b3307b5eSBram Moolenaarendfunc
472b3307b5eSBram Moolenaar
473b3307b5eSBram Moolenaar" Decode a message from gdb.  quotedText starts with a ", return the text up
474b3307b5eSBram Moolenaar" to the next ", unescaping characters.
475b3307b5eSBram Moolenaarfunc s:DecodeMessage(quotedText)
476b3307b5eSBram Moolenaar  if a:quotedText[0] != '"'
477a15b0a93SBram Moolenaar    echoerr 'DecodeMessage(): missing quote in ' . a:quotedText
478b3307b5eSBram Moolenaar    return
479b3307b5eSBram Moolenaar  endif
480b3307b5eSBram Moolenaar  let result = ''
481b3307b5eSBram Moolenaar  let i = 1
482b3307b5eSBram Moolenaar  while a:quotedText[i] != '"' && i < len(a:quotedText)
483b3307b5eSBram Moolenaar    if a:quotedText[i] == '\'
484b3307b5eSBram Moolenaar      let i += 1
485b3307b5eSBram Moolenaar      if a:quotedText[i] == 'n'
486b3307b5eSBram Moolenaar        " drop \n
487b3307b5eSBram Moolenaar        let i += 1
488b3307b5eSBram Moolenaar        continue
489b3307b5eSBram Moolenaar      endif
490b3307b5eSBram Moolenaar    endif
491b3307b5eSBram Moolenaar    let result .= a:quotedText[i]
492b3307b5eSBram Moolenaar    let i += 1
493b3307b5eSBram Moolenaar  endwhile
494b3307b5eSBram Moolenaar  return result
495b3307b5eSBram Moolenaarendfunc
496b3307b5eSBram Moolenaar
497a15b0a93SBram Moolenaar" Extract the "name" value from a gdb message with fullname="name".
498a15b0a93SBram Moolenaarfunc s:GetFullname(msg)
4995378e1cfSBram Moolenaar  if a:msg !~ 'fullname'
5005378e1cfSBram Moolenaar    return ''
5015378e1cfSBram Moolenaar  endif
502a15b0a93SBram Moolenaar  let name = s:DecodeMessage(substitute(a:msg, '.*fullname=', '', ''))
503a15b0a93SBram Moolenaar  if has('win32') && name =~ ':\\\\'
504a15b0a93SBram Moolenaar    " sometimes the name arrives double-escaped
505a15b0a93SBram Moolenaar    let name = substitute(name, '\\\\', '\\', 'g')
506a15b0a93SBram Moolenaar  endif
507a15b0a93SBram Moolenaar  return name
508a15b0a93SBram Moolenaarendfunc
509a15b0a93SBram Moolenaar
510b3307b5eSBram Moolenaarfunc s:EndTermDebug(job, status)
511fe386641SBram Moolenaar  exe 'bwipe! ' . s:commbuf
512b3623a38SBram Moolenaar  unlet s:gdbwin
513e09ba7baSBram Moolenaar
514b3307b5eSBram Moolenaar  call s:EndDebugCommon()
515b3307b5eSBram Moolenaarendfunc
516b3307b5eSBram Moolenaar
517b3307b5eSBram Moolenaarfunc s:EndDebugCommon()
518e09ba7baSBram Moolenaar  let curwinid = win_getid(winnr())
519e09ba7baSBram Moolenaar
520b3307b5eSBram Moolenaar  if exists('s:ptybuf') && s:ptybuf
521b3307b5eSBram Moolenaar    exe 'bwipe! ' . s:ptybuf
522b3307b5eSBram Moolenaar  endif
523b3307b5eSBram Moolenaar
524b3307b5eSBram Moolenaar  call win_gotoid(s:sourcewin)
525e09ba7baSBram Moolenaar  let &signcolumn = s:startsigncolumn
526e09ba7baSBram Moolenaar  call s:DeleteCommands()
527e09ba7baSBram Moolenaar
528e09ba7baSBram Moolenaar  call win_gotoid(curwinid)
529b3307b5eSBram Moolenaar
53038baa3e6SBram Moolenaar  if s:save_columns > 0
53138baa3e6SBram Moolenaar    let &columns = s:save_columns
53238baa3e6SBram Moolenaar  endif
5331b9645deSBram Moolenaar
534246fe03dSBram Moolenaar  if has("balloon_eval") || has("balloon_eval_term")
535246fe03dSBram Moolenaar    set balloonexpr=
53651b0f370SBram Moolenaar    if has("balloon_eval")
53751b0f370SBram Moolenaar      set noballooneval
538246fe03dSBram Moolenaar    endif
53951b0f370SBram Moolenaar    if has("balloon_eval_term")
54051b0f370SBram Moolenaar      set noballoonevalterm
54151b0f370SBram Moolenaar    endif
54251b0f370SBram Moolenaar  endif
54351b0f370SBram Moolenaar
5441b9645deSBram Moolenaar  au! TermDebug
545fe386641SBram Moolenaarendfunc
546fe386641SBram Moolenaar
547b3307b5eSBram Moolenaarfunc s:EndPromptDebug(job, status)
548b3307b5eSBram Moolenaar  let curwinid = win_getid(winnr())
549b3307b5eSBram Moolenaar  call win_gotoid(s:gdbwin)
5504551c0a9SBram Moolenaar  set nomodified
551b3307b5eSBram Moolenaar  close
552b3307b5eSBram Moolenaar  if curwinid != s:gdbwin
553b3307b5eSBram Moolenaar    call win_gotoid(curwinid)
554b3307b5eSBram Moolenaar  endif
555b3307b5eSBram Moolenaar
556b3307b5eSBram Moolenaar  call s:EndDebugCommon()
557b3307b5eSBram Moolenaar  unlet s:gdbwin
558b3307b5eSBram Moolenaar  call ch_log("Returning from EndPromptDebug()")
559b3307b5eSBram Moolenaarendfunc
560b3307b5eSBram Moolenaar
561fe386641SBram Moolenaar" Handle a message received from gdb on the GDB/MI interface.
562fe386641SBram Moolenaarfunc s:CommOutput(chan, msg)
563fe386641SBram Moolenaar  let msgs = split(a:msg, "\r")
564fe386641SBram Moolenaar
565fe386641SBram Moolenaar  for msg in msgs
566fe386641SBram Moolenaar    " remove prefixed NL
567fe386641SBram Moolenaar    if msg[0] == "\n"
568fe386641SBram Moolenaar      let msg = msg[1:]
569fe386641SBram Moolenaar    endif
570fe386641SBram Moolenaar    if msg != ''
5711b9645deSBram Moolenaar      if msg =~ '^\(\*stopped\|\*running\|=thread-selected\)'
572e09ba7baSBram Moolenaar        call s:HandleCursor(msg)
57345d5f26dSBram Moolenaar      elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,'
574e09ba7baSBram Moolenaar        call s:HandleNewBreakpoint(msg)
575e09ba7baSBram Moolenaar      elseif msg =~ '^=breakpoint-deleted,'
576e09ba7baSBram Moolenaar        call s:HandleBreakpointDelete(msg)
5774551c0a9SBram Moolenaar      elseif msg =~ '^=thread-group-started'
5784551c0a9SBram Moolenaar        call s:HandleProgramRun(msg)
57945d5f26dSBram Moolenaar      elseif msg =~ '^\^done,value='
58045d5f26dSBram Moolenaar        call s:HandleEvaluate(msg)
58145d5f26dSBram Moolenaar      elseif msg =~ '^\^error,msg='
58245d5f26dSBram Moolenaar        call s:HandleError(msg)
583e09ba7baSBram Moolenaar      endif
584e09ba7baSBram Moolenaar    endif
585e09ba7baSBram Moolenaar  endfor
586e09ba7baSBram Moolenaarendfunc
587e09ba7baSBram Moolenaar
588e09ba7baSBram Moolenaar" Install commands in the current window to control the debugger.
589e09ba7baSBram Moolenaarfunc s:InstallCommands()
590963c1ad5SBram Moolenaar  let save_cpo = &cpo
591963c1ad5SBram Moolenaar  set cpo&vim
592963c1ad5SBram Moolenaar
593e09ba7baSBram Moolenaar  command Break call s:SetBreakpoint()
59471137fedSBram Moolenaar  command Clear call s:ClearBreakpoint()
595e09ba7baSBram Moolenaar  command Step call s:SendCommand('-exec-step')
59645d5f26dSBram Moolenaar  command Over call s:SendCommand('-exec-next')
597e09ba7baSBram Moolenaar  command Finish call s:SendCommand('-exec-finish')
59860e73f2aSBram Moolenaar  command -nargs=* Run call s:Run(<q-args>)
59960e73f2aSBram Moolenaar  command -nargs=* Arguments call s:SendCommand('-exec-arguments ' . <q-args>)
60060e73f2aSBram Moolenaar  command Stop call s:SendCommand('-exec-interrupt')
601b3307b5eSBram Moolenaar
602b3307b5eSBram Moolenaar  " using -exec-continue results in CTRL-C in gdb window not working
603b3307b5eSBram Moolenaar  if s:way == 'prompt'
604b3307b5eSBram Moolenaar    command Continue call s:SendCommand('continue')
605b3307b5eSBram Moolenaar  else
606b3307b5eSBram Moolenaar    command Continue call term_sendkeys(s:gdbbuf, "continue\r")
607b3307b5eSBram Moolenaar  endif
608b3307b5eSBram Moolenaar
60945d5f26dSBram Moolenaar  command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>)
61045d5f26dSBram Moolenaar  command Gdb call win_gotoid(s:gdbwin)
61145d5f26dSBram Moolenaar  command Program call win_gotoid(s:ptywin)
612b3307b5eSBram Moolenaar  command Source call s:GotoSourcewinOrCreateIt()
61371137fedSBram Moolenaar  command Winbar call s:InstallWinbar()
61445d5f26dSBram Moolenaar
61545d5f26dSBram Moolenaar  " TODO: can the K mapping be restored?
61645d5f26dSBram Moolenaar  nnoremap K :Evaluate<CR>
6171b9645deSBram Moolenaar
618f0b03c4eSBram Moolenaar  if has('menu') && &mouse != ''
61971137fedSBram Moolenaar    call s:InstallWinbar()
62071137fedSBram Moolenaar
62171137fedSBram Moolenaar    if !exists('g:termdebug_popup') || g:termdebug_popup != 0
62271137fedSBram Moolenaar      let s:saved_mousemodel = &mousemodel
62371137fedSBram Moolenaar      let &mousemodel = 'popup_setpos'
62471137fedSBram Moolenaar      an 1.200 PopUp.-SEP3-	<Nop>
62571137fedSBram Moolenaar      an 1.210 PopUp.Set\ breakpoint	:Break<CR>
62671137fedSBram Moolenaar      an 1.220 PopUp.Clear\ breakpoint	:Clear<CR>
62771137fedSBram Moolenaar      an 1.230 PopUp.Evaluate		:Evaluate<CR>
62871137fedSBram Moolenaar    endif
62971137fedSBram Moolenaar  endif
630963c1ad5SBram Moolenaar
631963c1ad5SBram Moolenaar  let &cpo = save_cpo
63271137fedSBram Moolenaarendfunc
63371137fedSBram Moolenaar
63471137fedSBram Moolenaarlet s:winbar_winids = []
63571137fedSBram Moolenaar
63671137fedSBram Moolenaar" Install the window toolbar in the current window.
63771137fedSBram Moolenaarfunc s:InstallWinbar()
638c4b533e1SBram Moolenaar  if has('menu') && &mouse != ''
63924a98a0eSBram Moolenaar    nnoremenu WinBar.Step   :Step<CR>
64024a98a0eSBram Moolenaar    nnoremenu WinBar.Next   :Over<CR>
64124a98a0eSBram Moolenaar    nnoremenu WinBar.Finish :Finish<CR>
64224a98a0eSBram Moolenaar    nnoremenu WinBar.Cont   :Continue<CR>
64360e73f2aSBram Moolenaar    nnoremenu WinBar.Stop   :Stop<CR>
64424a98a0eSBram Moolenaar    nnoremenu WinBar.Eval   :Evaluate<CR>
64571137fedSBram Moolenaar    call add(s:winbar_winids, win_getid(winnr()))
646c4b533e1SBram Moolenaar  endif
647e09ba7baSBram Moolenaarendfunc
648e09ba7baSBram Moolenaar
649e09ba7baSBram Moolenaar" Delete installed debugger commands in the current window.
650e09ba7baSBram Moolenaarfunc s:DeleteCommands()
651e09ba7baSBram Moolenaar  delcommand Break
65271137fedSBram Moolenaar  delcommand Clear
653e09ba7baSBram Moolenaar  delcommand Step
65445d5f26dSBram Moolenaar  delcommand Over
655e09ba7baSBram Moolenaar  delcommand Finish
65660e73f2aSBram Moolenaar  delcommand Run
65760e73f2aSBram Moolenaar  delcommand Arguments
65860e73f2aSBram Moolenaar  delcommand Stop
659e09ba7baSBram Moolenaar  delcommand Continue
66045d5f26dSBram Moolenaar  delcommand Evaluate
66145d5f26dSBram Moolenaar  delcommand Gdb
66245d5f26dSBram Moolenaar  delcommand Program
663b3623a38SBram Moolenaar  delcommand Source
66471137fedSBram Moolenaar  delcommand Winbar
66545d5f26dSBram Moolenaar
66645d5f26dSBram Moolenaar  nunmap K
6671b9645deSBram Moolenaar
6681b9645deSBram Moolenaar  if has('menu')
66971137fedSBram Moolenaar    " Remove the WinBar entries from all windows where it was added.
67071137fedSBram Moolenaar    let curwinid = win_getid(winnr())
67171137fedSBram Moolenaar    for winid in s:winbar_winids
67271137fedSBram Moolenaar      if win_gotoid(winid)
6731b9645deSBram Moolenaar        aunmenu WinBar.Step
6741b9645deSBram Moolenaar        aunmenu WinBar.Next
6751b9645deSBram Moolenaar        aunmenu WinBar.Finish
6761b9645deSBram Moolenaar        aunmenu WinBar.Cont
67760e73f2aSBram Moolenaar        aunmenu WinBar.Stop
6781b9645deSBram Moolenaar        aunmenu WinBar.Eval
6791b9645deSBram Moolenaar      endif
68071137fedSBram Moolenaar    endfor
68171137fedSBram Moolenaar    call win_gotoid(curwinid)
68271137fedSBram Moolenaar    let s:winbar_winids = []
68371137fedSBram Moolenaar
68471137fedSBram Moolenaar    if exists('s:saved_mousemodel')
68571137fedSBram Moolenaar      let &mousemodel = s:saved_mousemodel
68671137fedSBram Moolenaar      unlet s:saved_mousemodel
68771137fedSBram Moolenaar      aunmenu PopUp.-SEP3-
68871137fedSBram Moolenaar      aunmenu PopUp.Set\ breakpoint
68971137fedSBram Moolenaar      aunmenu PopUp.Clear\ breakpoint
69071137fedSBram Moolenaar      aunmenu PopUp.Evaluate
69171137fedSBram Moolenaar    endif
69271137fedSBram Moolenaar  endif
6931b9645deSBram Moolenaar
69445d5f26dSBram Moolenaar  exe 'sign unplace ' . s:pc_id
695*37402ed5SBram Moolenaar  for [id, entries] in items(s:breakpoints)
696*37402ed5SBram Moolenaar    for subid in keys(entries)
697*37402ed5SBram Moolenaar      exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid)
698*37402ed5SBram Moolenaar    endfor
69945d5f26dSBram Moolenaar  endfor
70045d5f26dSBram Moolenaar  unlet s:breakpoints
701*37402ed5SBram Moolenaar  unlet s:breakpoint_locations
702a15b0a93SBram Moolenaar
703a15b0a93SBram Moolenaar  sign undefine debugPC
704a15b0a93SBram Moolenaar  for val in s:BreakpointSigns
705a15b0a93SBram Moolenaar    exe "sign undefine debugBreakpoint" . val
706a15b0a93SBram Moolenaar  endfor
7074551c0a9SBram Moolenaar  let s:BreakpointSigns = []
708e09ba7baSBram Moolenaarendfunc
709e09ba7baSBram Moolenaar
710e09ba7baSBram Moolenaar" :Break - Set a breakpoint at the cursor position.
711e09ba7baSBram Moolenaarfunc s:SetBreakpoint()
71260e73f2aSBram Moolenaar  " Setting a breakpoint may not work while the program is running.
71360e73f2aSBram Moolenaar  " Interrupt to make it work.
71460e73f2aSBram Moolenaar  let do_continue = 0
71560e73f2aSBram Moolenaar  if !s:stopped
71660e73f2aSBram Moolenaar    let do_continue = 1
717b3307b5eSBram Moolenaar    if s:way == 'prompt'
7184551c0a9SBram Moolenaar      call s:PromptInterrupt()
719b3307b5eSBram Moolenaar    else
72060e73f2aSBram Moolenaar      call s:SendCommand('-exec-interrupt')
721b3307b5eSBram Moolenaar    endif
72260e73f2aSBram Moolenaar    sleep 10m
72360e73f2aSBram Moolenaar  endif
724a15b0a93SBram Moolenaar  " Use the fname:lnum format, older gdb can't handle --source.
725a15b0a93SBram Moolenaar  call s:SendCommand('-break-insert '
726a15b0a93SBram Moolenaar        \ . fnameescape(expand('%:p')) . ':' . line('.'))
72760e73f2aSBram Moolenaar  if do_continue
72860e73f2aSBram Moolenaar    call s:SendCommand('-exec-continue')
72960e73f2aSBram Moolenaar  endif
730e09ba7baSBram Moolenaarendfunc
731e09ba7baSBram Moolenaar
73271137fedSBram Moolenaar" :Clear - Delete a breakpoint at the cursor position.
73371137fedSBram Moolenaarfunc s:ClearBreakpoint()
734e09ba7baSBram Moolenaar  let fname = fnameescape(expand('%:p'))
735e09ba7baSBram Moolenaar  let lnum = line('.')
736*37402ed5SBram Moolenaar  let bploc = printf('%s:%d', fname, lnum)
737*37402ed5SBram Moolenaar  if has_key(s:breakpoint_locations, bploc)
738*37402ed5SBram Moolenaar    let idx = 0
739*37402ed5SBram Moolenaar    for id in s:breakpoint_locations[bploc]
740*37402ed5SBram Moolenaar      if has_key(s:breakpoints, id)
741*37402ed5SBram Moolenaar        " Assume this always works, the reply is simply "^done".
742*37402ed5SBram Moolenaar        call s:SendCommand('-break-delete ' . id)
743*37402ed5SBram Moolenaar        for subid in keys(s:breakpoints[id])
744*37402ed5SBram Moolenaar          exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid)
745*37402ed5SBram Moolenaar        endfor
746*37402ed5SBram Moolenaar        unlet s:breakpoints[id]
747*37402ed5SBram Moolenaar        unlet s:breakpoint_locations[bploc][idx]
748e09ba7baSBram Moolenaar        break
749*37402ed5SBram Moolenaar      else
750*37402ed5SBram Moolenaar	let idx += 1
751e09ba7baSBram Moolenaar      endif
752e09ba7baSBram Moolenaar    endfor
753*37402ed5SBram Moolenaar    if empty(s:breakpoint_locations[bploc])
754*37402ed5SBram Moolenaar      unlet s:breakpoint_locations[bploc]
755*37402ed5SBram Moolenaar    endif
756*37402ed5SBram Moolenaar  endif
757e09ba7baSBram Moolenaarendfunc
758e09ba7baSBram Moolenaar
75960e73f2aSBram Moolenaarfunc s:Run(args)
76060e73f2aSBram Moolenaar  if a:args != ''
76160e73f2aSBram Moolenaar    call s:SendCommand('-exec-arguments ' . a:args)
76260e73f2aSBram Moolenaar  endif
76360e73f2aSBram Moolenaar  call s:SendCommand('-exec-run')
76460e73f2aSBram Moolenaarendfunc
76560e73f2aSBram Moolenaar
76651b0f370SBram Moolenaarfunc s:SendEval(expr)
76751b0f370SBram Moolenaar  call s:SendCommand('-data-evaluate-expression "' . a:expr . '"')
76851b0f370SBram Moolenaar  let s:evalexpr = a:expr
76951b0f370SBram Moolenaarendfunc
77051b0f370SBram Moolenaar
77145d5f26dSBram Moolenaar" :Evaluate - evaluate what is under the cursor
77245d5f26dSBram Moolenaarfunc s:Evaluate(range, arg)
77345d5f26dSBram Moolenaar  if a:arg != ''
77445d5f26dSBram Moolenaar    let expr = a:arg
77545d5f26dSBram Moolenaar  elseif a:range == 2
77645d5f26dSBram Moolenaar    let pos = getcurpos()
77745d5f26dSBram Moolenaar    let reg = getreg('v', 1, 1)
77845d5f26dSBram Moolenaar    let regt = getregtype('v')
77945d5f26dSBram Moolenaar    normal! gv"vy
78045d5f26dSBram Moolenaar    let expr = @v
78145d5f26dSBram Moolenaar    call setpos('.', pos)
78245d5f26dSBram Moolenaar    call setreg('v', reg, regt)
78345d5f26dSBram Moolenaar  else
78445d5f26dSBram Moolenaar    let expr = expand('<cexpr>')
78545d5f26dSBram Moolenaar  endif
78622f1d0e3SBram Moolenaar  let s:ignoreEvalError = 0
78751b0f370SBram Moolenaar  call s:SendEval(expr)
78845d5f26dSBram Moolenaarendfunc
78945d5f26dSBram Moolenaar
79022f1d0e3SBram Moolenaarlet s:ignoreEvalError = 0
79151b0f370SBram Moolenaarlet s:evalFromBalloonExpr = 0
79251b0f370SBram Moolenaar
79345d5f26dSBram Moolenaar" Handle the result of data-evaluate-expression
79445d5f26dSBram Moolenaarfunc s:HandleEvaluate(msg)
7951b9645deSBram Moolenaar  let value = substitute(a:msg, '.*value="\(.*\)"', '\1', '')
7961b9645deSBram Moolenaar  let value = substitute(value, '\\"', '"', 'g')
79751b0f370SBram Moolenaar  if s:evalFromBalloonExpr
79851b0f370SBram Moolenaar    if s:evalFromBalloonExprResult == ''
79951b0f370SBram Moolenaar      let s:evalFromBalloonExprResult = s:evalexpr . ': ' . value
80051b0f370SBram Moolenaar    else
80151b0f370SBram Moolenaar      let s:evalFromBalloonExprResult .= ' = ' . value
80251b0f370SBram Moolenaar    endif
80351b0f370SBram Moolenaar    call balloon_show(s:evalFromBalloonExprResult)
80451b0f370SBram Moolenaar  else
8051b9645deSBram Moolenaar    echomsg '"' . s:evalexpr . '": ' . value
80651b0f370SBram Moolenaar  endif
8071b9645deSBram Moolenaar
8087f2e9d7cSBram Moolenaar  if s:evalexpr[0] != '*' && value =~ '^0x' && value != '0x0' && value !~ '"$'
8091b9645deSBram Moolenaar    " Looks like a pointer, also display what it points to.
81022f1d0e3SBram Moolenaar    let s:ignoreEvalError = 1
81151b0f370SBram Moolenaar    call s:SendEval('*' . s:evalexpr)
81251b0f370SBram Moolenaar  else
81351b0f370SBram Moolenaar    let s:evalFromBalloonExpr = 0
8141b9645deSBram Moolenaar  endif
81545d5f26dSBram Moolenaarendfunc
81645d5f26dSBram Moolenaar
81751b0f370SBram Moolenaar" Show a balloon with information of the variable under the mouse pointer,
81851b0f370SBram Moolenaar" if there is any.
81951b0f370SBram Moolenaarfunc TermDebugBalloonExpr()
820b3307b5eSBram Moolenaar  if v:beval_winid != s:sourcewin
821b3307b5eSBram Moolenaar    return
822b3307b5eSBram Moolenaar  endif
823b3307b5eSBram Moolenaar  if !s:stopped
824b3307b5eSBram Moolenaar    " Only evaluate when stopped, otherwise setting a breakpoint using the
825b3307b5eSBram Moolenaar    " mouse triggers a balloon.
82651b0f370SBram Moolenaar    return
82751b0f370SBram Moolenaar  endif
82851b0f370SBram Moolenaar  let s:evalFromBalloonExpr = 1
82951b0f370SBram Moolenaar  let s:evalFromBalloonExprResult = ''
83022f1d0e3SBram Moolenaar  let s:ignoreEvalError = 1
83122f1d0e3SBram Moolenaar  call s:SendEval(v:beval_text)
83251b0f370SBram Moolenaar  return ''
83351b0f370SBram Moolenaarendfunc
83451b0f370SBram Moolenaar
83545d5f26dSBram Moolenaar" Handle an error.
83645d5f26dSBram Moolenaarfunc s:HandleError(msg)
83722f1d0e3SBram Moolenaar  if s:ignoreEvalError
83851b0f370SBram Moolenaar    " Result of s:SendEval() failed, ignore.
83922f1d0e3SBram Moolenaar    let s:ignoreEvalError = 0
84022f1d0e3SBram Moolenaar    let s:evalFromBalloonExpr = 0
84151b0f370SBram Moolenaar    return
84251b0f370SBram Moolenaar  endif
84345d5f26dSBram Moolenaar  echoerr substitute(a:msg, '.*msg="\(.*\)"', '\1', '')
84445d5f26dSBram Moolenaarendfunc
84545d5f26dSBram Moolenaar
846b3307b5eSBram Moolenaarfunc s:GotoSourcewinOrCreateIt()
847b3307b5eSBram Moolenaar  if !win_gotoid(s:sourcewin)
848c4b533e1SBram Moolenaar    new
849b3307b5eSBram Moolenaar    let s:sourcewin = win_getid(winnr())
850c4b533e1SBram Moolenaar    call s:InstallWinbar()
851c4b533e1SBram Moolenaar  endif
852c4b533e1SBram Moolenaarendfunc
853c4b533e1SBram Moolenaar
854e09ba7baSBram Moolenaar" Handle stopping and running message from gdb.
855e09ba7baSBram Moolenaar" Will update the sign that shows the current position.
856e09ba7baSBram Moolenaarfunc s:HandleCursor(msg)
857fe386641SBram Moolenaar  let wid = win_getid(winnr())
858fe386641SBram Moolenaar
85960e73f2aSBram Moolenaar  if a:msg =~ '^\*stopped'
8604551c0a9SBram Moolenaar    call ch_log('program stopped')
86160e73f2aSBram Moolenaar    let s:stopped = 1
86260e73f2aSBram Moolenaar  elseif a:msg =~ '^\*running'
8634551c0a9SBram Moolenaar    call ch_log('program running')
86460e73f2aSBram Moolenaar    let s:stopped = 0
86560e73f2aSBram Moolenaar  endif
86660e73f2aSBram Moolenaar
867a15b0a93SBram Moolenaar  if a:msg =~ 'fullname='
868a15b0a93SBram Moolenaar    let fname = s:GetFullname(a:msg)
869a15b0a93SBram Moolenaar  else
870a15b0a93SBram Moolenaar    let fname = ''
871a15b0a93SBram Moolenaar  endif
8721b9645deSBram Moolenaar  if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname)
873e09ba7baSBram Moolenaar    let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '')
874fe386641SBram Moolenaar    if lnum =~ '^[0-9]*$'
8754551c0a9SBram Moolenaar    call s:GotoSourcewinOrCreateIt()
8761b9645deSBram Moolenaar      if expand('%:p') != fnamemodify(fname, ':p')
877fe386641SBram Moolenaar        if &modified
878fe386641SBram Moolenaar          " TODO: find existing window
879fe386641SBram Moolenaar          exe 'split ' . fnameescape(fname)
880b3307b5eSBram Moolenaar          let s:sourcewin = win_getid(winnr())
881c4b533e1SBram Moolenaar          call s:InstallWinbar()
882fe386641SBram Moolenaar        else
883fe386641SBram Moolenaar          exe 'edit ' . fnameescape(fname)
884fe386641SBram Moolenaar        endif
885fe386641SBram Moolenaar      endif
886fe386641SBram Moolenaar      exe lnum
88701164a65SBram Moolenaar      exe 'sign unplace ' . s:pc_id
8881b9645deSBram Moolenaar      exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fname
889fe386641SBram Moolenaar      setlocal signcolumn=yes
890fe386641SBram Moolenaar    endif
8914551c0a9SBram Moolenaar  elseif !s:stopped || fname != ''
892fe386641SBram Moolenaar    exe 'sign unplace ' . s:pc_id
893fe386641SBram Moolenaar  endif
894fe386641SBram Moolenaar
895fe386641SBram Moolenaar  call win_gotoid(wid)
896e09ba7baSBram Moolenaarendfunc
897e09ba7baSBram Moolenaar
898de1a8314SBram Moolenaarlet s:BreakpointSigns = []
899a15b0a93SBram Moolenaar
900*37402ed5SBram Moolenaarfunc s:CreateBreakpoint(id, subid)
901*37402ed5SBram Moolenaar  let nr = printf('%d.%d', a:id, a:subid)
902*37402ed5SBram Moolenaar  if index(s:BreakpointSigns, nr) == -1
903*37402ed5SBram Moolenaar    call add(s:BreakpointSigns, nr)
904*37402ed5SBram Moolenaar    exe "sign define debugBreakpoint" . nr . " text=" . substitute(nr, '\..*', '', '') . " texthl=debugBreakpoint"
905de1a8314SBram Moolenaar  endif
906de1a8314SBram Moolenaarendfunc
907de1a8314SBram Moolenaar
908*37402ed5SBram Moolenaarfunc! s:SplitMsg(s)
909*37402ed5SBram Moolenaar  return split(a:s, '{.\{-}}\zs')
9105378e1cfSBram Moolenaarendfunction
9115378e1cfSBram Moolenaar
912e09ba7baSBram Moolenaar" Handle setting a breakpoint
913e09ba7baSBram Moolenaar" Will update the sign that shows the breakpoint
914e09ba7baSBram Moolenaarfunc s:HandleNewBreakpoint(msg)
9156dccc962SBram Moolenaar  if a:msg !~ 'fullname='
9166dccc962SBram Moolenaar    " a watch does not have a file name
9176dccc962SBram Moolenaar    return
9186dccc962SBram Moolenaar  endif
9195378e1cfSBram Moolenaar  for msg in s:SplitMsg(a:msg)
9205378e1cfSBram Moolenaar    let fname = s:GetFullname(msg)
9215378e1cfSBram Moolenaar    if empty(fname)
9225378e1cfSBram Moolenaar      continue
9235378e1cfSBram Moolenaar    endif
9245378e1cfSBram Moolenaar    let nr = substitute(msg, '.*number="\([0-9.]*\)\".*', '\1', '')
9255378e1cfSBram Moolenaar    if empty(nr)
926e09ba7baSBram Moolenaar      return
927fe386641SBram Moolenaar    endif
928e09ba7baSBram Moolenaar
929*37402ed5SBram Moolenaar    " If "nr" is 123 it becomes "123.0" and subid is "0".
930*37402ed5SBram Moolenaar    " If "nr" is 123.4 it becomes "123.4.0" and subid is "4"; "0" is discarded.
931*37402ed5SBram Moolenaar    let [id, subid; _] = map(split(nr . '.0', '\.'), 'v:val + 0')
932*37402ed5SBram Moolenaar    call s:CreateBreakpoint(id, subid)
933*37402ed5SBram Moolenaar
934*37402ed5SBram Moolenaar    if has_key(s:breakpoints, id)
935*37402ed5SBram Moolenaar      let entries = s:breakpoints[id]
936*37402ed5SBram Moolenaar    else
937*37402ed5SBram Moolenaar      let entries = {}
938*37402ed5SBram Moolenaar      let s:breakpoints[id] = entries
939*37402ed5SBram Moolenaar    endif
940*37402ed5SBram Moolenaar    if has_key(entries, subid)
941*37402ed5SBram Moolenaar      let entry = entries[subid]
942e09ba7baSBram Moolenaar    else
943e09ba7baSBram Moolenaar      let entry = {}
944*37402ed5SBram Moolenaar      let entries[subid] = entry
945fe386641SBram Moolenaar    endif
946e09ba7baSBram Moolenaar
9475378e1cfSBram Moolenaar    let lnum = substitute(msg, '.*line="\([^"]*\)".*', '\1', '')
948e09ba7baSBram Moolenaar    let entry['fname'] = fname
949e09ba7baSBram Moolenaar    let entry['lnum'] = lnum
9501b9645deSBram Moolenaar
951*37402ed5SBram Moolenaar    let bploc = printf('%s:%d', fname, lnum)
952*37402ed5SBram Moolenaar    if !has_key(s:breakpoint_locations, bploc)
953*37402ed5SBram Moolenaar      let s:breakpoint_locations[bploc] = []
954*37402ed5SBram Moolenaar    endif
955*37402ed5SBram Moolenaar    let s:breakpoint_locations[bploc] += [id]
956*37402ed5SBram Moolenaar
9571b9645deSBram Moolenaar    if bufloaded(fname)
958*37402ed5SBram Moolenaar      call s:PlaceSign(id, subid, entry)
9591b9645deSBram Moolenaar    endif
9605378e1cfSBram Moolenaar  endfor
9611b9645deSBram Moolenaarendfunc
9621b9645deSBram Moolenaar
963*37402ed5SBram Moolenaarfunc s:PlaceSign(id, subid, entry)
964*37402ed5SBram Moolenaar  let nr = printf('%d.%d', a:id, a:subid)
965*37402ed5SBram Moolenaar  exe 'sign place ' . s:Breakpoint2SignNumber(a:id, a:subid) . ' line=' . a:entry['lnum'] . ' name=debugBreakpoint' . nr . ' file=' . a:entry['fname']
9661b9645deSBram Moolenaar  let a:entry['placed'] = 1
967e09ba7baSBram Moolenaarendfunc
968e09ba7baSBram Moolenaar
969e09ba7baSBram Moolenaar" Handle deleting a breakpoint
970e09ba7baSBram Moolenaar" Will remove the sign that shows the breakpoint
971e09ba7baSBram Moolenaarfunc s:HandleBreakpointDelete(msg)
972*37402ed5SBram Moolenaar  let id = substitute(a:msg, '.*id="\([0-9]*\)\".*', '\1', '') + 0
973*37402ed5SBram Moolenaar  if empty(id)
974e09ba7baSBram Moolenaar    return
975e09ba7baSBram Moolenaar  endif
976*37402ed5SBram Moolenaar  if has_key(s:breakpoints, id)
977*37402ed5SBram Moolenaar    for [subid, entry] in items(s:breakpoints[id])
9781b9645deSBram Moolenaar      if has_key(entry, 'placed')
979*37402ed5SBram Moolenaar        exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid)
9801b9645deSBram Moolenaar        unlet entry['placed']
9811b9645deSBram Moolenaar      endif
9825378e1cfSBram Moolenaar    endfor
983*37402ed5SBram Moolenaar    unlet s:breakpoints[id]
984*37402ed5SBram Moolenaar  endif
985c572da5fSBram Moolenaarendfunc
9861b9645deSBram Moolenaar
9874551c0a9SBram Moolenaar" Handle the debugged program starting to run.
9884551c0a9SBram Moolenaar" Will store the process ID in s:pid
9894551c0a9SBram Moolenaarfunc s:HandleProgramRun(msg)
9904551c0a9SBram Moolenaar  let nr = substitute(a:msg, '.*pid="\([0-9]*\)\".*', '\1', '') + 0
9914551c0a9SBram Moolenaar  if nr == 0
9924551c0a9SBram Moolenaar    return
9934551c0a9SBram Moolenaar  endif
9944551c0a9SBram Moolenaar  let s:pid = nr
9954551c0a9SBram Moolenaar  call ch_log('Detected process ID: ' . s:pid)
9964551c0a9SBram Moolenaarendfunc
9974551c0a9SBram Moolenaar
9981b9645deSBram Moolenaar" Handle a BufRead autocommand event: place any signs.
9991b9645deSBram Moolenaarfunc s:BufRead()
10001b9645deSBram Moolenaar  let fname = expand('<afile>:p')
1001*37402ed5SBram Moolenaar  for [id, entries] in items(s:breakpoints)
1002*37402ed5SBram Moolenaar    for [subid, entry] in items(entries)
10031b9645deSBram Moolenaar      if entry['fname'] == fname
1004*37402ed5SBram Moolenaar        call s:PlaceSign(id, subid, entry)
10051b9645deSBram Moolenaar      endif
10061b9645deSBram Moolenaar    endfor
1007*37402ed5SBram Moolenaar  endfor
10081b9645deSBram Moolenaarendfunc
10091b9645deSBram Moolenaar
10101b9645deSBram Moolenaar" Handle a BufUnloaded autocommand event: unplace any signs.
10111b9645deSBram Moolenaarfunc s:BufUnloaded()
10121b9645deSBram Moolenaar  let fname = expand('<afile>:p')
1013*37402ed5SBram Moolenaar  for [id, entries] in items(s:breakpoints)
1014*37402ed5SBram Moolenaar    for [subid, entry] in items(entries)
10151b9645deSBram Moolenaar      if entry['fname'] == fname
10161b9645deSBram Moolenaar        let entry['placed'] = 0
10171b9645deSBram Moolenaar      endif
10181b9645deSBram Moolenaar    endfor
1019*37402ed5SBram Moolenaar  endfor
10201b9645deSBram Moolenaarendfunc
1021ca4cc018SBram Moolenaar
1022ca4cc018SBram Moolenaarlet &cpo = s:keepcpo
1023ca4cc018SBram Moolenaarunlet s:keepcpo
1024