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