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