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 " breakpoint number is added to this 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 " Install debugger commands in the text window. 329 call win_gotoid(s:sourcewin) 330 call s:InstallCommands() 331 call win_gotoid(s:gdbwin) 332 333 " Enable showing a balloon with eval info 334 if has("balloon_eval") || has("balloon_eval_term") 335 set balloonexpr=TermDebugBalloonExpr() 336 if has("balloon_eval") 337 set ballooneval 338 endif 339 if has("balloon_eval_term") 340 set balloonevalterm 341 endif 342 endif 343 344 " Contains breakpoints that have been placed, key is the number. 345 let s:breakpoints = {} 346 347 augroup TermDebug 348 au BufRead * call s:BufRead() 349 au BufUnload * call s:BufUnloaded() 350 augroup END 351 352 " Run the command if the bang attribute was given and got to the debug 353 " window. 354 if get(a:dict, 'bang', 0) 355 call s:SendCommand('-exec-run') 356 call win_gotoid(s:ptywin) 357 endif 358endfunc 359 360" Send a command to gdb. "cmd" is the string without line terminator. 361func s:SendCommand(cmd) 362 call ch_log('sending to gdb: ' . a:cmd) 363 if s:way == 'prompt' 364 call ch_sendraw(s:gdb_channel, a:cmd . "\n") 365 else 366 call term_sendkeys(s:commbuf, a:cmd . "\r") 367 endif 368endfunc 369 370" This is global so that a user can create their mappings with this. 371func TermDebugSendCommand(cmd) 372 if s:way == 'prompt' 373 call ch_sendraw(s:gdb_channel, a:cmd . "\n") 374 else 375 let do_continue = 0 376 if !s:stopped 377 let do_continue = 1 378 call s:SendCommand('-exec-interrupt') 379 sleep 10m 380 endif 381 call term_sendkeys(s:gdbbuf, a:cmd . "\r") 382 if do_continue 383 Continue 384 endif 385 endif 386endfunc 387 388" Function called when entering a line in the prompt buffer. 389func s:PromptCallback(text) 390 call s:SendCommand(a:text) 391endfunc 392 393" Function called when pressing CTRL-C in the prompt buffer. 394func s:PromptInterrupt() 395 call ch_log('Interrupting gdb') 396 call job_stop(s:gdbjob, 'int') 397endfunc 398 399" Function called when gdb outputs text. 400func s:GdbOutCallback(channel, text) 401 call ch_log('received from gdb: ' . a:text) 402 403 " Drop the gdb prompt, we have our own. 404 " Drop status and echo'd commands. 405 if a:text == '(gdb) ' || a:text == '^done' || a:text[0] == '&' || a:text[0] == '=' 406 return 407 endif 408 if a:text =~ '^^error,msg=' 409 let text = s:DecodeMessage(a:text[11:]) 410 if exists('s:evalexpr') && text =~ 'A syntax error in expression, near\|No symbol .* in current context' 411 " Silently drop evaluation errors. 412 unlet s:evalexpr 413 return 414 endif 415 elseif a:text[0] == '~' 416 let text = s:DecodeMessage(a:text[1:]) 417 else 418 call s:CommOutput(a:channel, a:text) 419 return 420 endif 421 422 let curwinid = win_getid(winnr()) 423 call win_gotoid(s:gdbwin) 424 425 " Add the output above the current prompt. 426 call append(line('$') - 1, text) 427 set nomodified 428 429 call win_gotoid(curwinid) 430endfunc 431 432" Decode a message from gdb. quotedText starts with a ", return the text up 433" to the next ", unescaping characters. 434func s:DecodeMessage(quotedText) 435 if a:quotedText[0] != '"' 436 echoerr 'DecodeMessage(): missing quote' 437 return 438 endif 439 let result = '' 440 let i = 1 441 while a:quotedText[i] != '"' && i < len(a:quotedText) 442 if a:quotedText[i] == '\' 443 let i += 1 444 if a:quotedText[i] == 'n' 445 " drop \n 446 let i += 1 447 continue 448 endif 449 endif 450 let result .= a:quotedText[i] 451 let i += 1 452 endwhile 453 return result 454endfunc 455 456func s:EndTermDebug(job, status) 457 exe 'bwipe! ' . s:commbuf 458 unlet s:gdbwin 459 460 call s:EndDebugCommon() 461endfunc 462 463func s:EndDebugCommon() 464 let curwinid = win_getid(winnr()) 465 466 if exists('s:ptybuf') && s:ptybuf 467 exe 'bwipe! ' . s:ptybuf 468 endif 469 470 call win_gotoid(s:sourcewin) 471 let &signcolumn = s:startsigncolumn 472 call s:DeleteCommands() 473 474 call win_gotoid(curwinid) 475 476 if s:save_columns > 0 477 let &columns = s:save_columns 478 endif 479 480 if has("balloon_eval") || has("balloon_eval_term") 481 set balloonexpr= 482 if has("balloon_eval") 483 set noballooneval 484 endif 485 if has("balloon_eval_term") 486 set noballoonevalterm 487 endif 488 endif 489 490 au! TermDebug 491endfunc 492 493func s:EndPromptDebug(job, status) 494 let curwinid = win_getid(winnr()) 495 call win_gotoid(s:gdbwin) 496 close 497 if curwinid != s:gdbwin 498 call win_gotoid(curwinid) 499 endif 500 501 call s:EndDebugCommon() 502 unlet s:gdbwin 503 call ch_log("Returning from EndPromptDebug()") 504endfunc 505 506" Handle a message received from gdb on the GDB/MI interface. 507func s:CommOutput(chan, msg) 508 let msgs = split(a:msg, "\r") 509 510 for msg in msgs 511 " remove prefixed NL 512 if msg[0] == "\n" 513 let msg = msg[1:] 514 endif 515 if msg != '' 516 if msg =~ '^\(\*stopped\|\*running\|=thread-selected\)' 517 call s:HandleCursor(msg) 518 elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,' 519 call s:HandleNewBreakpoint(msg) 520 elseif msg =~ '^=breakpoint-deleted,' 521 call s:HandleBreakpointDelete(msg) 522 elseif msg =~ '^\^done,value=' 523 call s:HandleEvaluate(msg) 524 elseif msg =~ '^\^error,msg=' 525 call s:HandleError(msg) 526 endif 527 endif 528 endfor 529endfunc 530 531" Install commands in the current window to control the debugger. 532func s:InstallCommands() 533 command Break call s:SetBreakpoint() 534 command Clear call s:ClearBreakpoint() 535 command Step call s:SendCommand('-exec-step') 536 command Over call s:SendCommand('-exec-next') 537 command Finish call s:SendCommand('-exec-finish') 538 command -nargs=* Run call s:Run(<q-args>) 539 command -nargs=* Arguments call s:SendCommand('-exec-arguments ' . <q-args>) 540 command Stop call s:SendCommand('-exec-interrupt') 541 542 " using -exec-continue results in CTRL-C in gdb window not working 543 if s:way == 'prompt' 544 command Continue call s:SendCommand('continue') 545 else 546 command Continue call term_sendkeys(s:gdbbuf, "continue\r") 547 endif 548 549 command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>) 550 command Gdb call win_gotoid(s:gdbwin) 551 command Program call win_gotoid(s:ptywin) 552 command Source call s:GotoSourcewinOrCreateIt() 553 command Winbar call s:InstallWinbar() 554 555 " TODO: can the K mapping be restored? 556 nnoremap K :Evaluate<CR> 557 558 if has('menu') && &mouse != '' 559 call s:InstallWinbar() 560 561 if !exists('g:termdebug_popup') || g:termdebug_popup != 0 562 let s:saved_mousemodel = &mousemodel 563 let &mousemodel = 'popup_setpos' 564 an 1.200 PopUp.-SEP3- <Nop> 565 an 1.210 PopUp.Set\ breakpoint :Break<CR> 566 an 1.220 PopUp.Clear\ breakpoint :Clear<CR> 567 an 1.230 PopUp.Evaluate :Evaluate<CR> 568 endif 569 endif 570endfunc 571 572let s:winbar_winids = [] 573 574" Install the window toolbar in the current window. 575func s:InstallWinbar() 576 if has('menu') && &mouse != '' 577 nnoremenu WinBar.Step :Step<CR> 578 nnoremenu WinBar.Next :Over<CR> 579 nnoremenu WinBar.Finish :Finish<CR> 580 nnoremenu WinBar.Cont :Continue<CR> 581 nnoremenu WinBar.Stop :Stop<CR> 582 nnoremenu WinBar.Eval :Evaluate<CR> 583 call add(s:winbar_winids, win_getid(winnr())) 584 endif 585endfunc 586 587" Delete installed debugger commands in the current window. 588func s:DeleteCommands() 589 delcommand Break 590 delcommand Clear 591 delcommand Step 592 delcommand Over 593 delcommand Finish 594 delcommand Run 595 delcommand Arguments 596 delcommand Stop 597 delcommand Continue 598 delcommand Evaluate 599 delcommand Gdb 600 delcommand Program 601 delcommand Source 602 delcommand Winbar 603 604 nunmap K 605 606 if has('menu') 607 " Remove the WinBar entries from all windows where it was added. 608 let curwinid = win_getid(winnr()) 609 for winid in s:winbar_winids 610 if win_gotoid(winid) 611 aunmenu WinBar.Step 612 aunmenu WinBar.Next 613 aunmenu WinBar.Finish 614 aunmenu WinBar.Cont 615 aunmenu WinBar.Stop 616 aunmenu WinBar.Eval 617 endif 618 endfor 619 call win_gotoid(curwinid) 620 let s:winbar_winids = [] 621 622 if exists('s:saved_mousemodel') 623 let &mousemodel = s:saved_mousemodel 624 unlet s:saved_mousemodel 625 aunmenu PopUp.-SEP3- 626 aunmenu PopUp.Set\ breakpoint 627 aunmenu PopUp.Clear\ breakpoint 628 aunmenu PopUp.Evaluate 629 endif 630 endif 631 632 exe 'sign unplace ' . s:pc_id 633 for key in keys(s:breakpoints) 634 exe 'sign unplace ' . (s:break_id + key) 635 endfor 636 sign undefine debugPC 637 sign undefine debugBreakpoint 638 unlet s:breakpoints 639endfunc 640 641" :Break - Set a breakpoint at the cursor position. 642func s:SetBreakpoint() 643 " Setting a breakpoint may not work while the program is running. 644 " Interrupt to make it work. 645 let do_continue = 0 646 if !s:stopped 647 let do_continue = 1 648 if s:way == 'prompt' 649 " Need to send a signal to get the UI to listen. Strangely this is only 650 " needed once. 651 call job_stop(s:gdbjob, 'int') 652 else 653 call s:SendCommand('-exec-interrupt') 654 endif 655 sleep 10m 656 endif 657 call s:SendCommand('-break-insert --source ' 658 \ . fnameescape(expand('%:p')) . ' --line ' . line('.')) 659 if do_continue 660 call s:SendCommand('-exec-continue') 661 endif 662endfunc 663 664" :Clear - Delete a breakpoint at the cursor position. 665func s:ClearBreakpoint() 666 let fname = fnameescape(expand('%:p')) 667 let lnum = line('.') 668 for [key, val] in items(s:breakpoints) 669 if val['fname'] == fname && val['lnum'] == lnum 670 call s:SendCommand('-break-delete ' . key) 671 " Assume this always wors, the reply is simply "^done". 672 exe 'sign unplace ' . (s:break_id + key) 673 unlet s:breakpoints[key] 674 break 675 endif 676 endfor 677endfunc 678 679func s:Run(args) 680 if a:args != '' 681 call s:SendCommand('-exec-arguments ' . a:args) 682 endif 683 call s:SendCommand('-exec-run') 684endfunc 685 686func s:SendEval(expr) 687 call s:SendCommand('-data-evaluate-expression "' . a:expr . '"') 688 let s:evalexpr = a:expr 689endfunc 690 691" :Evaluate - evaluate what is under the cursor 692func s:Evaluate(range, arg) 693 if a:arg != '' 694 let expr = a:arg 695 elseif a:range == 2 696 let pos = getcurpos() 697 let reg = getreg('v', 1, 1) 698 let regt = getregtype('v') 699 normal! gv"vy 700 let expr = @v 701 call setpos('.', pos) 702 call setreg('v', reg, regt) 703 else 704 let expr = expand('<cexpr>') 705 endif 706 let s:ignoreEvalError = 0 707 call s:SendEval(expr) 708endfunc 709 710let s:ignoreEvalError = 0 711let s:evalFromBalloonExpr = 0 712 713" Handle the result of data-evaluate-expression 714func s:HandleEvaluate(msg) 715 let value = substitute(a:msg, '.*value="\(.*\)"', '\1', '') 716 let value = substitute(value, '\\"', '"', 'g') 717 if s:evalFromBalloonExpr 718 if s:evalFromBalloonExprResult == '' 719 let s:evalFromBalloonExprResult = s:evalexpr . ': ' . value 720 else 721 let s:evalFromBalloonExprResult .= ' = ' . value 722 endif 723 call balloon_show(s:evalFromBalloonExprResult) 724 else 725 echomsg '"' . s:evalexpr . '": ' . value 726 endif 727 728 if s:evalexpr[0] != '*' && value =~ '^0x' && value != '0x0' && value !~ '"$' 729 " Looks like a pointer, also display what it points to. 730 let s:ignoreEvalError = 1 731 call s:SendEval('*' . s:evalexpr) 732 else 733 let s:evalFromBalloonExpr = 0 734 endif 735endfunc 736 737" Show a balloon with information of the variable under the mouse pointer, 738" if there is any. 739func TermDebugBalloonExpr() 740 if v:beval_winid != s:sourcewin 741 return 742 endif 743 if !s:stopped 744 " Only evaluate when stopped, otherwise setting a breakpoint using the 745 " mouse triggers a balloon. 746 return 747 endif 748 let s:evalFromBalloonExpr = 1 749 let s:evalFromBalloonExprResult = '' 750 let s:ignoreEvalError = 1 751 call s:SendEval(v:beval_text) 752 return '' 753endfunc 754 755" Handle an error. 756func s:HandleError(msg) 757 if s:ignoreEvalError 758 " Result of s:SendEval() failed, ignore. 759 let s:ignoreEvalError = 0 760 let s:evalFromBalloonExpr = 0 761 return 762 endif 763 echoerr substitute(a:msg, '.*msg="\(.*\)"', '\1', '') 764endfunc 765 766func s:GotoSourcewinOrCreateIt() 767 if !win_gotoid(s:sourcewin) 768 new 769 let s:sourcewin = win_getid(winnr()) 770 call s:InstallWinbar() 771 endif 772endfunc 773 774" Handle stopping and running message from gdb. 775" Will update the sign that shows the current position. 776func s:HandleCursor(msg) 777 let wid = win_getid(winnr()) 778 779 if a:msg =~ '^\*stopped' 780 let s:stopped = 1 781 elseif a:msg =~ '^\*running' 782 let s:stopped = 0 783 endif 784 785 call s:GotoSourcewinOrCreateIt() 786 787 let fname = substitute(a:msg, '.*fullname="\([^"]*\)".*', '\1', '') 788 if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname) 789 let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '') 790 if lnum =~ '^[0-9]*$' 791 if expand('%:p') != fnamemodify(fname, ':p') 792 if &modified 793 " TODO: find existing window 794 exe 'split ' . fnameescape(fname) 795 let s:sourcewin = win_getid(winnr()) 796 call s:InstallWinbar() 797 else 798 exe 'edit ' . fnameescape(fname) 799 endif 800 endif 801 exe lnum 802 exe 'sign unplace ' . s:pc_id 803 exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fname 804 setlocal signcolumn=yes 805 endif 806 else 807 exe 'sign unplace ' . s:pc_id 808 endif 809 810 call win_gotoid(wid) 811endfunc 812 813func s:CreateBreakpoint(nr) 814 if !exists("s:BreakpointSigns") 815 let s:BreakpointSigns = [] 816 endif 817 if index(s:BreakpointSigns, a:nr) == -1 818 call add(s:BreakpointSigns, a:nr) 819 exe "sign define debugBreakpoint". a:nr . " text=" . a:nr . " texthl=debugBreakpoint" 820 endif 821endfunc 822 823" Handle setting a breakpoint 824" Will update the sign that shows the breakpoint 825func s:HandleNewBreakpoint(msg) 826 let nr = substitute(a:msg, '.*number="\([0-9]\)*\".*', '\1', '') + 0 827 if nr == 0 828 return 829 endif 830 call s:CreateBreakpoint(nr) 831 832 if has_key(s:breakpoints, nr) 833 let entry = s:breakpoints[nr] 834 else 835 let entry = {} 836 let s:breakpoints[nr] = entry 837 endif 838 839 let fname = substitute(a:msg, '.*fullname="\([^"]*\)".*', '\1', '') 840 let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '') 841 let entry['fname'] = fname 842 let entry['lnum'] = lnum 843 844 if bufloaded(fname) 845 call s:PlaceSign(nr, entry) 846 endif 847endfunc 848 849func s:PlaceSign(nr, entry) 850 exe 'sign place ' . (s:break_id + a:nr) . ' line=' . a:entry['lnum'] . ' name=debugBreakpoint' . a:nr . ' file=' . a:entry['fname'] 851 let a:entry['placed'] = 1 852endfunc 853 854" Handle deleting a breakpoint 855" Will remove the sign that shows the breakpoint 856func s:HandleBreakpointDelete(msg) 857 let nr = substitute(a:msg, '.*id="\([0-9]*\)\".*', '\1', '') + 0 858 if nr == 0 859 return 860 endif 861 if has_key(s:breakpoints, nr) 862 let entry = s:breakpoints[nr] 863 if has_key(entry, 'placed') 864 exe 'sign unplace ' . (s:break_id + nr) 865 unlet entry['placed'] 866 endif 867 unlet s:breakpoints[nr] 868 endif 869endfunc 870 871" Handle a BufRead autocommand event: place any signs. 872func s:BufRead() 873 let fname = expand('<afile>:p') 874 for [nr, entry] in items(s:breakpoints) 875 if entry['fname'] == fname 876 call s:PlaceSign(nr, entry) 877 endif 878 endfor 879endfunc 880 881" Handle a BufUnloaded autocommand event: unplace any signs. 882func s:BufUnloaded() 883 let fname = expand('<afile>:p') 884 for [nr, entry] in items(s:breakpoints) 885 if entry['fname'] == fname 886 let entry['placed'] = 0 887 endif 888 endfor 889endfunc 890 891