1" Debugger plugin using gdb. 2" 3" Author: Bram Moolenaar 4" Copyright: Vim license applies, see ":help license" 5" Last Change: 2020 Feb 19 6" 7" WORK IN PROGRESS - Only the basics work 8" Note: On MS-Windows you need a recent version of gdb. The one included with 9" MingW is too old (7.6.1). 10" I used version 7.12 from http://www.equation.com/servlet/equation.cmd?fa=gdb 11" 12" There are two ways to run gdb: 13" - In a terminal window; used if possible, does not work on MS-Windows 14" Not used when g:termdebug_use_prompt is set to 1. 15" - Using a "prompt" buffer; may use a terminal window for the program 16" 17" For both the current window is used to view source code and shows the 18" current statement from gdb. 19" 20" USING A TERMINAL WINDOW 21" 22" Opens two visible terminal windows: 23" 1. runs a pty for the debugged program, as with ":term NONE" 24" 2. runs gdb, passing the pty of the debugged program 25" A third terminal window is hidden, it is used for communication with gdb. 26" 27" USING A PROMPT BUFFER 28" 29" Opens a window with a prompt buffer to communicate with gdb. 30" Gdb is run as a job with callbacks for I/O. 31" On Unix another terminal window is opened to run the debugged program 32" On MS-Windows a separate console is opened to run the debugged program 33" 34" The communication with gdb uses GDB/MI. See: 35" https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html 36 37" In case this gets sourced twice. 38if exists(':Termdebug') 39 finish 40endif 41 42" Need either the +terminal feature or +channel and the prompt buffer. 43" The terminal feature does not work with gdb on win32. 44if has('terminal') && !has('win32') 45 let s:way = 'terminal' 46elseif has('channel') && exists('*prompt_setprompt') 47 let s:way = 'prompt' 48else 49 if has('terminal') 50 let s:err = 'Cannot debug, missing prompt buffer support' 51 else 52 let s:err = 'Cannot debug, +channel feature is not supported' 53 endif 54 command -nargs=* -complete=file -bang Termdebug echoerr s:err 55 command -nargs=+ -complete=file -bang TermdebugCommand echoerr s:err 56 finish 57endif 58 59let s:keepcpo = &cpo 60set cpo&vim 61 62" The command that starts debugging, e.g. ":Termdebug vim". 63" To end type "quit" in the gdb window. 64command -nargs=* -complete=file -bang Termdebug call s:StartDebug(<bang>0, <f-args>) 65command -nargs=+ -complete=file -bang TermdebugCommand call s:StartDebugCommand(<bang>0, <f-args>) 66 67" Name of the gdb command, defaults to "gdb". 68if !exists('g:termdebugger') 69 let g:termdebugger = 'gdb' 70endif 71 72let s:pc_id = 12 73let s:break_id = 13 " breakpoint number is added to this 74let s:stopped = 1 75 76" Take a breakpoint number as used by GDB and turn it into an integer. 77" The breakpoint may contain a dot: 123.4 -> 123004 78" The main breakpoint has a zero subid. 79func s:Breakpoint2SignNumber(id, subid) 80 return s:break_id + a:id * 1000 + a:subid 81endfunction 82 83func s:Highlight(init, old, new) 84 let default = a:init ? 'default ' : '' 85 if a:new ==# 'light' && a:old !=# 'light' 86 exe "hi " . default . "debugPC term=reverse ctermbg=lightblue guibg=lightblue" 87 elseif a:new ==# 'dark' && a:old !=# 'dark' 88 exe "hi " . default . "debugPC term=reverse ctermbg=darkblue guibg=darkblue" 89 endif 90endfunc 91 92call s:Highlight(1, '', &background) 93hi default debugBreakpoint term=reverse ctermbg=red guibg=red 94 95func s:StartDebug(bang, ...) 96 " First argument is the command to debug, second core file or process ID. 97 call s:StartDebug_internal({'gdb_args': a:000, 'bang': a:bang}) 98endfunc 99 100func s:StartDebugCommand(bang, ...) 101 " First argument is the command to debug, rest are run arguments. 102 call s:StartDebug_internal({'gdb_args': [a:1], 'proc_args': a:000[1:], 'bang': a:bang}) 103endfunc 104 105func s:StartDebug_internal(dict) 106 if exists('s:gdbwin') 107 echoerr 'Terminal debugger already running, cannot run two' 108 return 109 endif 110 if !executable(g:termdebugger) 111 echoerr 'Cannot execute debugger program "' .. g:termdebugger .. '"' 112 return 113 endif 114 115 let s:ptywin = 0 116 let s:pid = 0 117 118 " Uncomment this line to write logging in "debuglog". 119 " call ch_logfile('debuglog', 'w') 120 121 let s:sourcewin = win_getid(winnr()) 122 let s:startsigncolumn = &signcolumn 123 124 let s:save_columns = 0 125 let s:allleft = 0 126 if exists('g:termdebug_wide') 127 if &columns < g:termdebug_wide 128 let s:save_columns = &columns 129 let &columns = g:termdebug_wide 130 " If we make the Vim window wider, use the whole left halve for the debug 131 " windows. 132 let s:allleft = 1 133 endif 134 let s:vertical = 1 135 else 136 let s:vertical = 0 137 endif 138 139 " Override using a terminal window by setting g:termdebug_use_prompt to 1. 140 let use_prompt = exists('g:termdebug_use_prompt') && g:termdebug_use_prompt 141 if has('terminal') && !has('win32') && !use_prompt 142 let s:way = 'terminal' 143 else 144 let s:way = 'prompt' 145 endif 146 147 if s:way == 'prompt' 148 call s:StartDebug_prompt(a:dict) 149 else 150 call s:StartDebug_term(a:dict) 151 endif 152endfunc 153 154" Use when debugger didn't start or ended. 155func s:CloseBuffers() 156 exe 'bwipe! ' . s:ptybuf 157 exe 'bwipe! ' . s:commbuf 158 unlet! s:gdbwin 159endfunc 160 161func s:StartDebug_term(dict) 162 " Open a terminal window without a job, to run the debugged program in. 163 let s:ptybuf = term_start('NONE', { 164 \ 'term_name': 'debugged program', 165 \ 'vertical': s:vertical, 166 \ }) 167 if s:ptybuf == 0 168 echoerr 'Failed to open the program terminal window' 169 return 170 endif 171 let pty = job_info(term_getjob(s:ptybuf))['tty_out'] 172 let s:ptywin = win_getid(winnr()) 173 if s:vertical 174 " Assuming the source code window will get a signcolumn, use two more 175 " columns for that, thus one less for the terminal window. 176 exe (&columns / 2 - 1) . "wincmd |" 177 if s:allleft 178 " use the whole left column 179 wincmd H 180 endif 181 endif 182 183 " Create a hidden terminal window to communicate with gdb 184 let s:commbuf = term_start('NONE', { 185 \ 'term_name': 'gdb communication', 186 \ 'out_cb': function('s:CommOutput'), 187 \ 'hidden': 1, 188 \ }) 189 if s:commbuf == 0 190 echoerr 'Failed to open the communication terminal window' 191 exe 'bwipe! ' . s:ptybuf 192 return 193 endif 194 let commpty = job_info(term_getjob(s:commbuf))['tty_out'] 195 196 " Open a terminal window to run the debugger. 197 " Add -quiet to avoid the intro message causing a hit-enter prompt. 198 let gdb_args = get(a:dict, 'gdb_args', []) 199 let proc_args = get(a:dict, 'proc_args', []) 200 201 let cmd = [g:termdebugger, '-quiet', '-tty', pty] + gdb_args 202 call ch_log('executing "' . join(cmd) . '"') 203 let s:gdbbuf = term_start(cmd, { 204 \ 'term_finish': 'close', 205 \ }) 206 if s:gdbbuf == 0 207 echoerr 'Failed to open the gdb terminal window' 208 call s:CloseBuffers() 209 return 210 endif 211 let s:gdbwin = win_getid(winnr()) 212 213 " Set arguments to be run 214 if len(proc_args) 215 call term_sendkeys(s:gdbbuf, 'set args ' . join(proc_args) . "\r") 216 endif 217 218 " Connect gdb to the communication pty, using the GDB/MI interface 219 call term_sendkeys(s:gdbbuf, 'new-ui mi ' . commpty . "\r") 220 221 " Wait for the response to show up, users may not notice the error and wonder 222 " why the debugger doesn't work. 223 let try_count = 0 224 while 1 225 let gdbproc = term_getjob(s:gdbbuf) 226 if gdbproc == v:null || job_status(gdbproc) !=# 'run' 227 echoerr string(g:termdebugger) . ' exited unexpectedly' 228 call s:CloseBuffers() 229 return 230 endif 231 232 let response = '' 233 for lnum in range(1, 200) 234 let line1 = term_getline(s:gdbbuf, lnum) 235 let line2 = term_getline(s:gdbbuf, lnum + 1) 236 if line1 =~ 'new-ui mi ' 237 " response can be in the same line or the next line 238 let response = line1 . line2 239 if response =~ 'Undefined command' 240 echoerr 'Sorry, your gdb is too old, gdb 7.12 is required' 241 call s:CloseBuffers() 242 return 243 endif 244 if response =~ 'New UI allocated' 245 " Success! 246 break 247 endif 248 elseif line1 =~ 'Reading symbols from' && line2 !~ 'new-ui mi ' 249 " Reading symbols might take a while, try more times 250 let try_count -= 1 251 endif 252 endfor 253 if response =~ 'New UI allocated' 254 break 255 endif 256 let try_count += 1 257 if try_count > 100 258 echoerr 'Cannot check if your gdb works, continuing anyway' 259 break 260 endif 261 sleep 10m 262 endwhile 263 264 " Interpret commands while the target is running. This should usually only be 265 " exec-interrupt, since many commands don't work properly while the target is 266 " running. 267 call s:SendCommand('-gdb-set mi-async on') 268 " Older gdb uses a different command. 269 call s:SendCommand('-gdb-set target-async on') 270 271 " Disable pagination, it causes everything to stop at the gdb 272 " "Type <return> to continue" prompt. 273 call s:SendCommand('set pagination off') 274 275 call job_setoptions(gdbproc, {'exit_cb': function('s:EndTermDebug')}) 276 call s:StartDebugCommon(a:dict) 277endfunc 278 279func s:StartDebug_prompt(dict) 280 " Open a window with a prompt buffer to run gdb in. 281 if s:vertical 282 vertical new 283 else 284 new 285 endif 286 let s:gdbwin = win_getid(winnr()) 287 let s:promptbuf = bufnr('') 288 call prompt_setprompt(s:promptbuf, 'gdb> ') 289 set buftype=prompt 290 file gdb 291 call prompt_setcallback(s:promptbuf, function('s:PromptCallback')) 292 call prompt_setinterrupt(s:promptbuf, function('s:PromptInterrupt')) 293 294 if s:vertical 295 " Assuming the source code window will get a signcolumn, use two more 296 " columns for that, thus one less for the terminal window. 297 exe (&columns / 2 - 1) . "wincmd |" 298 endif 299 300 " Add -quiet to avoid the intro message causing a hit-enter prompt. 301 let gdb_args = get(a:dict, 'gdb_args', []) 302 let proc_args = get(a:dict, 'proc_args', []) 303 304 let cmd = [g:termdebugger, '-quiet', '--interpreter=mi2'] + gdb_args 305 call ch_log('executing "' . join(cmd) . '"') 306 307 let s:gdbjob = job_start(cmd, { 308 \ 'exit_cb': function('s:EndPromptDebug'), 309 \ 'out_cb': function('s:GdbOutCallback'), 310 \ }) 311 if job_status(s:gdbjob) != "run" 312 echoerr 'Failed to start gdb' 313 exe 'bwipe! ' . s:promptbuf 314 return 315 endif 316 " Mark the buffer modified so that it's not easy to close. 317 set modified 318 let s:gdb_channel = job_getchannel(s:gdbjob) 319 320 " Interpret commands while the target is running. This should usually only 321 " be exec-interrupt, since many commands don't work properly while the 322 " target is running. 323 call s:SendCommand('-gdb-set mi-async on') 324 " Older gdb uses a different command. 325 call s:SendCommand('-gdb-set target-async on') 326 327 let s:ptybuf = 0 328 if has('win32') 329 " MS-Windows: run in a new console window for maximum compatibility 330 call s:SendCommand('set new-console on') 331 elseif has('terminal') 332 " Unix: Run the debugged program in a terminal window. Open it below the 333 " gdb window. 334 belowright let s:ptybuf = term_start('NONE', { 335 \ 'term_name': 'debugged program', 336 \ }) 337 if s:ptybuf == 0 338 echoerr 'Failed to open the program terminal window' 339 call job_stop(s:gdbjob) 340 return 341 endif 342 let s:ptywin = win_getid(winnr()) 343 let pty = job_info(term_getjob(s:ptybuf))['tty_out'] 344 call s:SendCommand('tty ' . pty) 345 346 " Since GDB runs in a prompt window, the environment has not been set to 347 " match a terminal window, need to do that now. 348 call s:SendCommand('set env TERM = xterm-color') 349 call s:SendCommand('set env ROWS = ' . winheight(s:ptywin)) 350 call s:SendCommand('set env LINES = ' . winheight(s:ptywin)) 351 call s:SendCommand('set env COLUMNS = ' . winwidth(s:ptywin)) 352 call s:SendCommand('set env COLORS = ' . &t_Co) 353 call s:SendCommand('set env VIM_TERMINAL = ' . v:version) 354 else 355 " TODO: open a new terminal get get the tty name, pass on to gdb 356 call s:SendCommand('show inferior-tty') 357 endif 358 call s:SendCommand('set print pretty on') 359 call s:SendCommand('set breakpoint pending on') 360 " Disable pagination, it causes everything to stop at the gdb 361 call s:SendCommand('set pagination off') 362 363 " Set arguments to be run 364 if len(proc_args) 365 call s:SendCommand('set args ' . join(proc_args)) 366 endif 367 368 call s:StartDebugCommon(a:dict) 369 startinsert 370endfunc 371 372func s:StartDebugCommon(dict) 373 " Sign used to highlight the line where the program has stopped. 374 " There can be only one. 375 sign define debugPC linehl=debugPC 376 377 " Install debugger commands in the text window. 378 call win_gotoid(s:sourcewin) 379 call s:InstallCommands() 380 call win_gotoid(s:gdbwin) 381 382 " Enable showing a balloon with eval info 383 if has("balloon_eval") || has("balloon_eval_term") 384 set balloonexpr=TermDebugBalloonExpr() 385 if has("balloon_eval") 386 set ballooneval 387 endif 388 if has("balloon_eval_term") 389 set balloonevalterm 390 endif 391 endif 392 393 " Contains breakpoints that have been placed, key is a string with the GDB 394 " breakpoint number. 395 " Each entry is a dict, containing the sub-breakpoints. Key is the subid. 396 " For a breakpoint that is just a number the subid is zero. 397 " For a breakpoint "123.4" the id is "123" and subid is "4". 398 " Example, when breakpoint "44", "123", "123.1" and "123.2" exist: 399 " {'44': {'0': entry}, '123': {'0': entry, '1': entry, '2': entry}} 400 let s:breakpoints = {} 401 402 " Contains breakpoints by file/lnum. The key is "fname:lnum". 403 " Each entry is a list of breakpoint IDs at that position. 404 let s:breakpoint_locations = {} 405 406 augroup TermDebug 407 au BufRead * call s:BufRead() 408 au BufUnload * call s:BufUnloaded() 409 au OptionSet background call s:Highlight(0, v:option_old, v:option_new) 410 augroup END 411 412 " Run the command if the bang attribute was given and got to the debug 413 " window. 414 if get(a:dict, 'bang', 0) 415 call s:SendCommand('-exec-run') 416 call win_gotoid(s:ptywin) 417 endif 418endfunc 419 420" Send a command to gdb. "cmd" is the string without line terminator. 421func s:SendCommand(cmd) 422 call ch_log('sending to gdb: ' . a:cmd) 423 if s:way == 'prompt' 424 call ch_sendraw(s:gdb_channel, a:cmd . "\n") 425 else 426 call term_sendkeys(s:commbuf, a:cmd . "\r") 427 endif 428endfunc 429 430" This is global so that a user can create their mappings with this. 431func TermDebugSendCommand(cmd) 432 if s:way == 'prompt' 433 call ch_sendraw(s:gdb_channel, a:cmd . "\n") 434 else 435 let do_continue = 0 436 if !s:stopped 437 let do_continue = 1 438 call s:SendCommand('-exec-interrupt') 439 sleep 10m 440 endif 441 call term_sendkeys(s:gdbbuf, a:cmd . "\r") 442 if do_continue 443 Continue 444 endif 445 endif 446endfunc 447 448" Function called when entering a line in the prompt buffer. 449func s:PromptCallback(text) 450 call s:SendCommand(a:text) 451endfunc 452 453" Function called when pressing CTRL-C in the prompt buffer and when placing a 454" breakpoint. 455func s:PromptInterrupt() 456 call ch_log('Interrupting gdb') 457 if has('win32') 458 " Using job_stop() does not work on MS-Windows, need to send SIGTRAP to 459 " the debugger program so that gdb responds again. 460 if s:pid == 0 461 echoerr 'Cannot interrupt gdb, did not find a process ID' 462 else 463 call debugbreak(s:pid) 464 endif 465 else 466 call job_stop(s:gdbjob, 'int') 467 endif 468endfunc 469 470" Function called when gdb outputs text. 471func s:GdbOutCallback(channel, text) 472 call ch_log('received from gdb: ' . a:text) 473 474 " Drop the gdb prompt, we have our own. 475 " Drop status and echo'd commands. 476 if a:text == '(gdb) ' || a:text == '^done' || a:text[0] == '&' 477 return 478 endif 479 if a:text =~ '^^error,msg=' 480 let text = s:DecodeMessage(a:text[11:]) 481 if exists('s:evalexpr') && text =~ 'A syntax error in expression, near\|No symbol .* in current context' 482 " Silently drop evaluation errors. 483 unlet s:evalexpr 484 return 485 endif 486 elseif a:text[0] == '~' 487 let text = s:DecodeMessage(a:text[1:]) 488 else 489 call s:CommOutput(a:channel, a:text) 490 return 491 endif 492 493 let curwinid = win_getid(winnr()) 494 call win_gotoid(s:gdbwin) 495 496 " Add the output above the current prompt. 497 call append(line('$') - 1, text) 498 set modified 499 500 call win_gotoid(curwinid) 501endfunc 502 503" Decode a message from gdb. quotedText starts with a ", return the text up 504" to the next ", unescaping characters. 505func s:DecodeMessage(quotedText) 506 if a:quotedText[0] != '"' 507 echoerr 'DecodeMessage(): missing quote in ' . a:quotedText 508 return 509 endif 510 let result = '' 511 let i = 1 512 while a:quotedText[i] != '"' && i < len(a:quotedText) 513 if a:quotedText[i] == '\' 514 let i += 1 515 if a:quotedText[i] == 'n' 516 " drop \n 517 let i += 1 518 continue 519 elseif a:quotedText[i] == 't' 520 " append \t 521 let i += 1 522 let result .= "\t" 523 continue 524 endif 525 endif 526 let result .= a:quotedText[i] 527 let i += 1 528 endwhile 529 return result 530endfunc 531 532" Extract the "name" value from a gdb message with fullname="name". 533func s:GetFullname(msg) 534 if a:msg !~ 'fullname' 535 return '' 536 endif 537 let name = s:DecodeMessage(substitute(a:msg, '.*fullname=', '', '')) 538 if has('win32') && name =~ ':\\\\' 539 " sometimes the name arrives double-escaped 540 let name = substitute(name, '\\\\', '\\', 'g') 541 endif 542 return name 543endfunc 544 545func s:EndTermDebug(job, status) 546 exe 'bwipe! ' . s:commbuf 547 unlet s:gdbwin 548 549 call s:EndDebugCommon() 550endfunc 551 552func s:EndDebugCommon() 553 let curwinid = win_getid(winnr()) 554 555 if exists('s:ptybuf') && s:ptybuf 556 exe 'bwipe! ' . s:ptybuf 557 endif 558 559 call win_gotoid(s:sourcewin) 560 let &signcolumn = s:startsigncolumn 561 call s:DeleteCommands() 562 563 call win_gotoid(curwinid) 564 565 if s:save_columns > 0 566 let &columns = s:save_columns 567 endif 568 569 if has("balloon_eval") || has("balloon_eval_term") 570 set balloonexpr= 571 if has("balloon_eval") 572 set noballooneval 573 endif 574 if has("balloon_eval_term") 575 set noballoonevalterm 576 endif 577 endif 578 579 au! TermDebug 580endfunc 581 582func s:EndPromptDebug(job, status) 583 let curwinid = win_getid(winnr()) 584 call win_gotoid(s:gdbwin) 585 set nomodified 586 close 587 if curwinid != s:gdbwin 588 call win_gotoid(curwinid) 589 endif 590 591 call s:EndDebugCommon() 592 unlet s:gdbwin 593 call ch_log("Returning from EndPromptDebug()") 594endfunc 595 596" Handle a message received from gdb on the GDB/MI interface. 597func s:CommOutput(chan, msg) 598 let msgs = split(a:msg, "\r") 599 600 for msg in msgs 601 " remove prefixed NL 602 if msg[0] == "\n" 603 let msg = msg[1:] 604 endif 605 if msg != '' 606 if msg =~ '^\(\*stopped\|\*running\|=thread-selected\)' 607 call s:HandleCursor(msg) 608 elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,' 609 call s:HandleNewBreakpoint(msg) 610 elseif msg =~ '^=breakpoint-deleted,' 611 call s:HandleBreakpointDelete(msg) 612 elseif msg =~ '^=thread-group-started' 613 call s:HandleProgramRun(msg) 614 elseif msg =~ '^\^done,value=' 615 call s:HandleEvaluate(msg) 616 elseif msg =~ '^\^error,msg=' 617 call s:HandleError(msg) 618 endif 619 endif 620 endfor 621endfunc 622 623func s:GotoProgram() 624 if has('win32') 625 if executable('powershell') 626 call system(printf('powershell -Command "add-type -AssemblyName microsoft.VisualBasic;[Microsoft.VisualBasic.Interaction]::AppActivate(%d);"', s:pid)) 627 endif 628 else 629 call win_gotoid(s:ptywin) 630 endif 631endfunc 632 633" Install commands in the current window to control the debugger. 634func s:InstallCommands() 635 let save_cpo = &cpo 636 set cpo&vim 637 638 command -nargs=? Break call s:SetBreakpoint(<q-args>) 639 command Clear call s:ClearBreakpoint() 640 command Step call s:SendCommand('-exec-step') 641 command Over call s:SendCommand('-exec-next') 642 command Finish call s:SendCommand('-exec-finish') 643 command -nargs=* Run call s:Run(<q-args>) 644 command -nargs=* Arguments call s:SendCommand('-exec-arguments ' . <q-args>) 645 command Stop call s:SendCommand('-exec-interrupt') 646 647 " using -exec-continue results in CTRL-C in gdb window not working 648 if s:way == 'prompt' 649 command Continue call s:SendCommand('continue') 650 else 651 command Continue call term_sendkeys(s:gdbbuf, "continue\r") 652 endif 653 654 command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>) 655 command Gdb call win_gotoid(s:gdbwin) 656 command Program call s:GotoProgram() 657 command Source call s:GotoSourcewinOrCreateIt() 658 command Winbar call s:InstallWinbar() 659 660 " TODO: can the K mapping be restored? 661 nnoremap K :Evaluate<CR> 662 663 if has('menu') && &mouse != '' 664 call s:InstallWinbar() 665 666 if !exists('g:termdebug_popup') || g:termdebug_popup != 0 667 let s:saved_mousemodel = &mousemodel 668 let &mousemodel = 'popup_setpos' 669 an 1.200 PopUp.-SEP3- <Nop> 670 an 1.210 PopUp.Set\ breakpoint :Break<CR> 671 an 1.220 PopUp.Clear\ breakpoint :Clear<CR> 672 an 1.230 PopUp.Evaluate :Evaluate<CR> 673 endif 674 endif 675 676 let &cpo = save_cpo 677endfunc 678 679let s:winbar_winids = [] 680 681" Install the window toolbar in the current window. 682func s:InstallWinbar() 683 if has('menu') && &mouse != '' 684 nnoremenu WinBar.Step :Step<CR> 685 nnoremenu WinBar.Next :Over<CR> 686 nnoremenu WinBar.Finish :Finish<CR> 687 nnoremenu WinBar.Cont :Continue<CR> 688 nnoremenu WinBar.Stop :Stop<CR> 689 nnoremenu WinBar.Eval :Evaluate<CR> 690 call add(s:winbar_winids, win_getid(winnr())) 691 endif 692endfunc 693 694" Delete installed debugger commands in the current window. 695func s:DeleteCommands() 696 delcommand Break 697 delcommand Clear 698 delcommand Step 699 delcommand Over 700 delcommand Finish 701 delcommand Run 702 delcommand Arguments 703 delcommand Stop 704 delcommand Continue 705 delcommand Evaluate 706 delcommand Gdb 707 delcommand Program 708 delcommand Source 709 delcommand Winbar 710 711 nunmap K 712 713 if has('menu') 714 " Remove the WinBar entries from all windows where it was added. 715 let curwinid = win_getid(winnr()) 716 for winid in s:winbar_winids 717 if win_gotoid(winid) 718 aunmenu WinBar.Step 719 aunmenu WinBar.Next 720 aunmenu WinBar.Finish 721 aunmenu WinBar.Cont 722 aunmenu WinBar.Stop 723 aunmenu WinBar.Eval 724 endif 725 endfor 726 call win_gotoid(curwinid) 727 let s:winbar_winids = [] 728 729 if exists('s:saved_mousemodel') 730 let &mousemodel = s:saved_mousemodel 731 unlet s:saved_mousemodel 732 aunmenu PopUp.-SEP3- 733 aunmenu PopUp.Set\ breakpoint 734 aunmenu PopUp.Clear\ breakpoint 735 aunmenu PopUp.Evaluate 736 endif 737 endif 738 739 exe 'sign unplace ' . s:pc_id 740 for [id, entries] in items(s:breakpoints) 741 for subid in keys(entries) 742 exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid) 743 endfor 744 endfor 745 unlet s:breakpoints 746 unlet s:breakpoint_locations 747 748 sign undefine debugPC 749 for val in s:BreakpointSigns 750 exe "sign undefine debugBreakpoint" . val 751 endfor 752 let s:BreakpointSigns = [] 753endfunc 754 755" :Break - Set a breakpoint at the cursor position. 756func s:SetBreakpoint(at) 757 " Setting a breakpoint may not work while the program is running. 758 " Interrupt to make it work. 759 let do_continue = 0 760 if !s:stopped 761 let do_continue = 1 762 if s:way == 'prompt' 763 call s:PromptInterrupt() 764 else 765 call s:SendCommand('-exec-interrupt') 766 endif 767 sleep 10m 768 endif 769 770 " Use the fname:lnum format, older gdb can't handle --source. 771 let at = empty(a:at) ? 772 \ fnameescape(expand('%:p')) . ':' . line('.') : a:at 773 call s:SendCommand('-break-insert ' . at) 774 if do_continue 775 call s:SendCommand('-exec-continue') 776 endif 777endfunc 778 779" :Clear - Delete a breakpoint at the cursor position. 780func s:ClearBreakpoint() 781 let fname = fnameescape(expand('%:p')) 782 let lnum = line('.') 783 let bploc = printf('%s:%d', fname, lnum) 784 if has_key(s:breakpoint_locations, bploc) 785 let idx = 0 786 for id in s:breakpoint_locations[bploc] 787 if has_key(s:breakpoints, id) 788 " Assume this always works, the reply is simply "^done". 789 call s:SendCommand('-break-delete ' . id) 790 for subid in keys(s:breakpoints[id]) 791 exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid) 792 endfor 793 unlet s:breakpoints[id] 794 unlet s:breakpoint_locations[bploc][idx] 795 break 796 else 797 let idx += 1 798 endif 799 endfor 800 if empty(s:breakpoint_locations[bploc]) 801 unlet s:breakpoint_locations[bploc] 802 endif 803 endif 804endfunc 805 806func s:Run(args) 807 if a:args != '' 808 call s:SendCommand('-exec-arguments ' . a:args) 809 endif 810 call s:SendCommand('-exec-run') 811endfunc 812 813func s:SendEval(expr) 814 call s:SendCommand('-data-evaluate-expression "' . a:expr . '"') 815 let s:evalexpr = a:expr 816endfunc 817 818" :Evaluate - evaluate what is under the cursor 819func s:Evaluate(range, arg) 820 if a:arg != '' 821 let expr = a:arg 822 elseif a:range == 2 823 let pos = getcurpos() 824 let reg = getreg('v', 1, 1) 825 let regt = getregtype('v') 826 normal! gv"vy 827 let expr = @v 828 call setpos('.', pos) 829 call setreg('v', reg, regt) 830 else 831 let expr = expand('<cexpr>') 832 endif 833 let s:ignoreEvalError = 0 834 call s:SendEval(expr) 835endfunc 836 837let s:ignoreEvalError = 0 838let s:evalFromBalloonExpr = 0 839 840" Handle the result of data-evaluate-expression 841func s:HandleEvaluate(msg) 842 let value = substitute(a:msg, '.*value="\(.*\)"', '\1', '') 843 let value = substitute(value, '\\"', '"', 'g') 844 if s:evalFromBalloonExpr 845 if s:evalFromBalloonExprResult == '' 846 let s:evalFromBalloonExprResult = s:evalexpr . ': ' . value 847 else 848 let s:evalFromBalloonExprResult .= ' = ' . value 849 endif 850 call balloon_show(s:evalFromBalloonExprResult) 851 else 852 echomsg '"' . s:evalexpr . '": ' . value 853 endif 854 855 if s:evalexpr[0] != '*' && value =~ '^0x' && value != '0x0' && value !~ '"$' 856 " Looks like a pointer, also display what it points to. 857 let s:ignoreEvalError = 1 858 call s:SendEval('*' . s:evalexpr) 859 else 860 let s:evalFromBalloonExpr = 0 861 endif 862endfunc 863 864" Show a balloon with information of the variable under the mouse pointer, 865" if there is any. 866func TermDebugBalloonExpr() 867 if v:beval_winid != s:sourcewin 868 return '' 869 endif 870 if !s:stopped 871 " Only evaluate when stopped, otherwise setting a breakpoint using the 872 " mouse triggers a balloon. 873 return '' 874 endif 875 let s:evalFromBalloonExpr = 1 876 let s:evalFromBalloonExprResult = '' 877 let s:ignoreEvalError = 1 878 call s:SendEval(v:beval_text) 879 return '' 880endfunc 881 882" Handle an error. 883func s:HandleError(msg) 884 if s:ignoreEvalError 885 " Result of s:SendEval() failed, ignore. 886 let s:ignoreEvalError = 0 887 let s:evalFromBalloonExpr = 0 888 return 889 endif 890 echoerr substitute(a:msg, '.*msg="\(.*\)"', '\1', '') 891endfunc 892 893func s:GotoSourcewinOrCreateIt() 894 if !win_gotoid(s:sourcewin) 895 new 896 let s:sourcewin = win_getid(winnr()) 897 call s:InstallWinbar() 898 endif 899endfunc 900 901" Handle stopping and running message from gdb. 902" Will update the sign that shows the current position. 903func s:HandleCursor(msg) 904 let wid = win_getid(winnr()) 905 906 if a:msg =~ '^\*stopped' 907 call ch_log('program stopped') 908 let s:stopped = 1 909 elseif a:msg =~ '^\*running' 910 call ch_log('program running') 911 let s:stopped = 0 912 endif 913 914 if a:msg =~ 'fullname=' 915 let fname = s:GetFullname(a:msg) 916 else 917 let fname = '' 918 endif 919 if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname) 920 let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '') 921 if lnum =~ '^[0-9]*$' 922 call s:GotoSourcewinOrCreateIt() 923 if expand('%:p') != fnamemodify(fname, ':p') 924 if &modified 925 " TODO: find existing window 926 exe 'split ' . fnameescape(fname) 927 let s:sourcewin = win_getid(winnr()) 928 call s:InstallWinbar() 929 else 930 exe 'edit ' . fnameescape(fname) 931 endif 932 endif 933 exe lnum 934 exe 'sign unplace ' . s:pc_id 935 exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fname 936 setlocal signcolumn=yes 937 endif 938 elseif !s:stopped || fname != '' 939 exe 'sign unplace ' . s:pc_id 940 endif 941 942 call win_gotoid(wid) 943endfunc 944 945let s:BreakpointSigns = [] 946 947func s:CreateBreakpoint(id, subid) 948 let nr = printf('%d.%d', a:id, a:subid) 949 if index(s:BreakpointSigns, nr) == -1 950 call add(s:BreakpointSigns, nr) 951 exe "sign define debugBreakpoint" . nr . " text=" . substitute(nr, '\..*', '', '') . " texthl=debugBreakpoint" 952 endif 953endfunc 954 955func! s:SplitMsg(s) 956 return split(a:s, '{.\{-}}\zs') 957endfunction 958 959" Handle setting a breakpoint 960" Will update the sign that shows the breakpoint 961func s:HandleNewBreakpoint(msg) 962 if a:msg !~ 'fullname=' 963 " a watch does not have a file name 964 return 965 endif 966 for msg in s:SplitMsg(a:msg) 967 let fname = s:GetFullname(msg) 968 if empty(fname) 969 continue 970 endif 971 let nr = substitute(msg, '.*number="\([0-9.]*\)\".*', '\1', '') 972 if empty(nr) 973 return 974 endif 975 976 " If "nr" is 123 it becomes "123.0" and subid is "0". 977 " If "nr" is 123.4 it becomes "123.4.0" and subid is "4"; "0" is discarded. 978 let [id, subid; _] = map(split(nr . '.0', '\.'), 'v:val + 0') 979 call s:CreateBreakpoint(id, subid) 980 981 if has_key(s:breakpoints, id) 982 let entries = s:breakpoints[id] 983 else 984 let entries = {} 985 let s:breakpoints[id] = entries 986 endif 987 if has_key(entries, subid) 988 let entry = entries[subid] 989 else 990 let entry = {} 991 let entries[subid] = entry 992 endif 993 994 let lnum = substitute(msg, '.*line="\([^"]*\)".*', '\1', '') 995 let entry['fname'] = fname 996 let entry['lnum'] = lnum 997 998 let bploc = printf('%s:%d', fname, lnum) 999 if !has_key(s:breakpoint_locations, bploc) 1000 let s:breakpoint_locations[bploc] = [] 1001 endif 1002 let s:breakpoint_locations[bploc] += [id] 1003 1004 if bufloaded(fname) 1005 call s:PlaceSign(id, subid, entry) 1006 endif 1007 endfor 1008endfunc 1009 1010func s:PlaceSign(id, subid, entry) 1011 let nr = printf('%d.%d', a:id, a:subid) 1012 exe 'sign place ' . s:Breakpoint2SignNumber(a:id, a:subid) . ' line=' . a:entry['lnum'] . ' name=debugBreakpoint' . nr . ' file=' . a:entry['fname'] 1013 let a:entry['placed'] = 1 1014endfunc 1015 1016" Handle deleting a breakpoint 1017" Will remove the sign that shows the breakpoint 1018func s:HandleBreakpointDelete(msg) 1019 let id = substitute(a:msg, '.*id="\([0-9]*\)\".*', '\1', '') + 0 1020 if empty(id) 1021 return 1022 endif 1023 if has_key(s:breakpoints, id) 1024 for [subid, entry] in items(s:breakpoints[id]) 1025 if has_key(entry, 'placed') 1026 exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid) 1027 unlet entry['placed'] 1028 endif 1029 endfor 1030 unlet s:breakpoints[id] 1031 endif 1032endfunc 1033 1034" Handle the debugged program starting to run. 1035" Will store the process ID in s:pid 1036func s:HandleProgramRun(msg) 1037 let nr = substitute(a:msg, '.*pid="\([0-9]*\)\".*', '\1', '') + 0 1038 if nr == 0 1039 return 1040 endif 1041 let s:pid = nr 1042 call ch_log('Detected process ID: ' . s:pid) 1043endfunc 1044 1045" Handle a BufRead autocommand event: place any signs. 1046func s:BufRead() 1047 let fname = expand('<afile>:p') 1048 for [id, entries] in items(s:breakpoints) 1049 for [subid, entry] in items(entries) 1050 if entry['fname'] == fname 1051 call s:PlaceSign(id, subid, entry) 1052 endif 1053 endfor 1054 endfor 1055endfunc 1056 1057" Handle a BufUnloaded autocommand event: unplace any signs. 1058func s:BufUnloaded() 1059 let fname = expand('<afile>:p') 1060 for [id, entries] in items(s:breakpoints) 1061 for [subid, entry] in items(entries) 1062 if entry['fname'] == fname 1063 let entry['placed'] = 0 1064 endif 1065 endfor 1066 endfor 1067endfunc 1068 1069let &cpo = s:keepcpo 1070unlet s:keepcpo 1071