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] == '&' 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 in ' . a:quotedText 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 462" Extract the "name" value from a gdb message with fullname="name". 463func s:GetFullname(msg) 464 let name = s:DecodeMessage(substitute(a:msg, '.*fullname=', '', '')) 465 if has('win32') && name =~ ':\\\\' 466 " sometimes the name arrives double-escaped 467 let name = substitute(name, '\\\\', '\\', 'g') 468 endif 469 return name 470endfunc 471 472func s:EndTermDebug(job, status) 473 exe 'bwipe! ' . s:commbuf 474 unlet s:gdbwin 475 476 call s:EndDebugCommon() 477endfunc 478 479func s:EndDebugCommon() 480 let curwinid = win_getid(winnr()) 481 482 if exists('s:ptybuf') && s:ptybuf 483 exe 'bwipe! ' . s:ptybuf 484 endif 485 486 call win_gotoid(s:sourcewin) 487 let &signcolumn = s:startsigncolumn 488 call s:DeleteCommands() 489 490 call win_gotoid(curwinid) 491 492 if s:save_columns > 0 493 let &columns = s:save_columns 494 endif 495 496 if has("balloon_eval") || has("balloon_eval_term") 497 set balloonexpr= 498 if has("balloon_eval") 499 set noballooneval 500 endif 501 if has("balloon_eval_term") 502 set noballoonevalterm 503 endif 504 endif 505 506 au! TermDebug 507endfunc 508 509func s:EndPromptDebug(job, status) 510 let curwinid = win_getid(winnr()) 511 call win_gotoid(s:gdbwin) 512 close 513 if curwinid != s:gdbwin 514 call win_gotoid(curwinid) 515 endif 516 517 call s:EndDebugCommon() 518 unlet s:gdbwin 519 call ch_log("Returning from EndPromptDebug()") 520endfunc 521 522" Handle a message received from gdb on the GDB/MI interface. 523func s:CommOutput(chan, msg) 524 let msgs = split(a:msg, "\r") 525 526 for msg in msgs 527 " remove prefixed NL 528 if msg[0] == "\n" 529 let msg = msg[1:] 530 endif 531 if msg != '' 532 if msg =~ '^\(\*stopped\|\*running\|=thread-selected\)' 533 call s:HandleCursor(msg) 534 elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,' 535 call s:HandleNewBreakpoint(msg) 536 elseif msg =~ '^=breakpoint-deleted,' 537 call s:HandleBreakpointDelete(msg) 538 elseif msg =~ '^\^done,value=' 539 call s:HandleEvaluate(msg) 540 elseif msg =~ '^\^error,msg=' 541 call s:HandleError(msg) 542 endif 543 endif 544 endfor 545endfunc 546 547" Install commands in the current window to control the debugger. 548func s:InstallCommands() 549 command Break call s:SetBreakpoint() 550 command Clear call s:ClearBreakpoint() 551 command Step call s:SendCommand('-exec-step') 552 command Over call s:SendCommand('-exec-next') 553 command Finish call s:SendCommand('-exec-finish') 554 command -nargs=* Run call s:Run(<q-args>) 555 command -nargs=* Arguments call s:SendCommand('-exec-arguments ' . <q-args>) 556 command Stop call s:SendCommand('-exec-interrupt') 557 558 " using -exec-continue results in CTRL-C in gdb window not working 559 if s:way == 'prompt' 560 command Continue call s:SendCommand('continue') 561 else 562 command Continue call term_sendkeys(s:gdbbuf, "continue\r") 563 endif 564 565 command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>) 566 command Gdb call win_gotoid(s:gdbwin) 567 command Program call win_gotoid(s:ptywin) 568 command Source call s:GotoSourcewinOrCreateIt() 569 command Winbar call s:InstallWinbar() 570 571 " TODO: can the K mapping be restored? 572 nnoremap K :Evaluate<CR> 573 574 if has('menu') && &mouse != '' 575 call s:InstallWinbar() 576 577 if !exists('g:termdebug_popup') || g:termdebug_popup != 0 578 let s:saved_mousemodel = &mousemodel 579 let &mousemodel = 'popup_setpos' 580 an 1.200 PopUp.-SEP3- <Nop> 581 an 1.210 PopUp.Set\ breakpoint :Break<CR> 582 an 1.220 PopUp.Clear\ breakpoint :Clear<CR> 583 an 1.230 PopUp.Evaluate :Evaluate<CR> 584 endif 585 endif 586endfunc 587 588let s:winbar_winids = [] 589 590" Install the window toolbar in the current window. 591func s:InstallWinbar() 592 if has('menu') && &mouse != '' 593 nnoremenu WinBar.Step :Step<CR> 594 nnoremenu WinBar.Next :Over<CR> 595 nnoremenu WinBar.Finish :Finish<CR> 596 nnoremenu WinBar.Cont :Continue<CR> 597 nnoremenu WinBar.Stop :Stop<CR> 598 nnoremenu WinBar.Eval :Evaluate<CR> 599 call add(s:winbar_winids, win_getid(winnr())) 600 endif 601endfunc 602 603" Delete installed debugger commands in the current window. 604func s:DeleteCommands() 605 delcommand Break 606 delcommand Clear 607 delcommand Step 608 delcommand Over 609 delcommand Finish 610 delcommand Run 611 delcommand Arguments 612 delcommand Stop 613 delcommand Continue 614 delcommand Evaluate 615 delcommand Gdb 616 delcommand Program 617 delcommand Source 618 delcommand Winbar 619 620 nunmap K 621 622 if has('menu') 623 " Remove the WinBar entries from all windows where it was added. 624 let curwinid = win_getid(winnr()) 625 for winid in s:winbar_winids 626 if win_gotoid(winid) 627 aunmenu WinBar.Step 628 aunmenu WinBar.Next 629 aunmenu WinBar.Finish 630 aunmenu WinBar.Cont 631 aunmenu WinBar.Stop 632 aunmenu WinBar.Eval 633 endif 634 endfor 635 call win_gotoid(curwinid) 636 let s:winbar_winids = [] 637 638 if exists('s:saved_mousemodel') 639 let &mousemodel = s:saved_mousemodel 640 unlet s:saved_mousemodel 641 aunmenu PopUp.-SEP3- 642 aunmenu PopUp.Set\ breakpoint 643 aunmenu PopUp.Clear\ breakpoint 644 aunmenu PopUp.Evaluate 645 endif 646 endif 647 648 exe 'sign unplace ' . s:pc_id 649 for key in keys(s:breakpoints) 650 exe 'sign unplace ' . (s:break_id + key) 651 endfor 652 unlet s:breakpoints 653 654 sign undefine debugPC 655 for val in s:BreakpointSigns 656 exe "sign undefine debugBreakpoint" . val 657 endfor 658 unlet s:BreakpointSigns 659endfunc 660 661" :Break - Set a breakpoint at the cursor position. 662func s:SetBreakpoint() 663 " Setting a breakpoint may not work while the program is running. 664 " Interrupt to make it work. 665 let do_continue = 0 666 if !s:stopped 667 let do_continue = 1 668 if s:way == 'prompt' 669 " Need to send a signal to get the UI to listen. Strangely this is only 670 " needed once. 671 call job_stop(s:gdbjob, 'int') 672 else 673 call s:SendCommand('-exec-interrupt') 674 endif 675 sleep 10m 676 endif 677 " Use the fname:lnum format, older gdb can't handle --source. 678 call s:SendCommand('-break-insert ' 679 \ . fnameescape(expand('%:p')) . ':' . line('.')) 680 if do_continue 681 call s:SendCommand('-exec-continue') 682 endif 683endfunc 684 685" :Clear - Delete a breakpoint at the cursor position. 686func s:ClearBreakpoint() 687 let fname = fnameescape(expand('%:p')) 688 let lnum = line('.') 689 for [key, val] in items(s:breakpoints) 690 if val['fname'] == fname && val['lnum'] == lnum 691 call s:SendCommand('-break-delete ' . key) 692 " Assume this always wors, the reply is simply "^done". 693 exe 'sign unplace ' . (s:break_id + key) 694 unlet s:breakpoints[key] 695 break 696 endif 697 endfor 698endfunc 699 700func s:Run(args) 701 if a:args != '' 702 call s:SendCommand('-exec-arguments ' . a:args) 703 endif 704 call s:SendCommand('-exec-run') 705endfunc 706 707func s:SendEval(expr) 708 call s:SendCommand('-data-evaluate-expression "' . a:expr . '"') 709 let s:evalexpr = a:expr 710endfunc 711 712" :Evaluate - evaluate what is under the cursor 713func s:Evaluate(range, arg) 714 if a:arg != '' 715 let expr = a:arg 716 elseif a:range == 2 717 let pos = getcurpos() 718 let reg = getreg('v', 1, 1) 719 let regt = getregtype('v') 720 normal! gv"vy 721 let expr = @v 722 call setpos('.', pos) 723 call setreg('v', reg, regt) 724 else 725 let expr = expand('<cexpr>') 726 endif 727 let s:ignoreEvalError = 0 728 call s:SendEval(expr) 729endfunc 730 731let s:ignoreEvalError = 0 732let s:evalFromBalloonExpr = 0 733 734" Handle the result of data-evaluate-expression 735func s:HandleEvaluate(msg) 736 let value = substitute(a:msg, '.*value="\(.*\)"', '\1', '') 737 let value = substitute(value, '\\"', '"', 'g') 738 if s:evalFromBalloonExpr 739 if s:evalFromBalloonExprResult == '' 740 let s:evalFromBalloonExprResult = s:evalexpr . ': ' . value 741 else 742 let s:evalFromBalloonExprResult .= ' = ' . value 743 endif 744 call balloon_show(s:evalFromBalloonExprResult) 745 else 746 echomsg '"' . s:evalexpr . '": ' . value 747 endif 748 749 if s:evalexpr[0] != '*' && value =~ '^0x' && value != '0x0' && value !~ '"$' 750 " Looks like a pointer, also display what it points to. 751 let s:ignoreEvalError = 1 752 call s:SendEval('*' . s:evalexpr) 753 else 754 let s:evalFromBalloonExpr = 0 755 endif 756endfunc 757 758" Show a balloon with information of the variable under the mouse pointer, 759" if there is any. 760func TermDebugBalloonExpr() 761 if v:beval_winid != s:sourcewin 762 return 763 endif 764 if !s:stopped 765 " Only evaluate when stopped, otherwise setting a breakpoint using the 766 " mouse triggers a balloon. 767 return 768 endif 769 let s:evalFromBalloonExpr = 1 770 let s:evalFromBalloonExprResult = '' 771 let s:ignoreEvalError = 1 772 call s:SendEval(v:beval_text) 773 return '' 774endfunc 775 776" Handle an error. 777func s:HandleError(msg) 778 if s:ignoreEvalError 779 " Result of s:SendEval() failed, ignore. 780 let s:ignoreEvalError = 0 781 let s:evalFromBalloonExpr = 0 782 return 783 endif 784 echoerr substitute(a:msg, '.*msg="\(.*\)"', '\1', '') 785endfunc 786 787func s:GotoSourcewinOrCreateIt() 788 if !win_gotoid(s:sourcewin) 789 new 790 let s:sourcewin = win_getid(winnr()) 791 call s:InstallWinbar() 792 endif 793endfunc 794 795" Handle stopping and running message from gdb. 796" Will update the sign that shows the current position. 797func s:HandleCursor(msg) 798 let wid = win_getid(winnr()) 799 800 if a:msg =~ '^\*stopped' 801 let s:stopped = 1 802 elseif a:msg =~ '^\*running' 803 let s:stopped = 0 804 endif 805 806 call s:GotoSourcewinOrCreateIt() 807 808 if a:msg =~ 'fullname=' 809 let fname = s:GetFullname(a:msg) 810 else 811 let fname = '' 812 endif 813 if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname) 814 let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '') 815 if lnum =~ '^[0-9]*$' 816 if expand('%:p') != fnamemodify(fname, ':p') 817 if &modified 818 " TODO: find existing window 819 exe 'split ' . fnameescape(fname) 820 let s:sourcewin = win_getid(winnr()) 821 call s:InstallWinbar() 822 else 823 exe 'edit ' . fnameescape(fname) 824 endif 825 endif 826 exe lnum 827 exe 'sign unplace ' . s:pc_id 828 exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fname 829 setlocal signcolumn=yes 830 endif 831 else 832 exe 'sign unplace ' . s:pc_id 833 endif 834 835 call win_gotoid(wid) 836endfunc 837 838let s:BreakpointSigns = [] 839 840func s:CreateBreakpoint(nr) 841 if index(s:BreakpointSigns, a:nr) == -1 842 call add(s:BreakpointSigns, a:nr) 843 exe "sign define debugBreakpoint" . a:nr . " text=" . a:nr . " texthl=debugBreakpoint" 844 endif 845endfunc 846 847" Handle setting a breakpoint 848" Will update the sign that shows the breakpoint 849func s:HandleNewBreakpoint(msg) 850 let nr = substitute(a:msg, '.*number="\([0-9]\)*\".*', '\1', '') + 0 851 if nr == 0 852 return 853 endif 854 call s:CreateBreakpoint(nr) 855 856 if has_key(s:breakpoints, nr) 857 let entry = s:breakpoints[nr] 858 else 859 let entry = {} 860 let s:breakpoints[nr] = entry 861 endif 862 863 let fname = s:GetFullname(a:msg) 864 let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '') 865 let entry['fname'] = fname 866 let entry['lnum'] = lnum 867 868 if bufloaded(fname) 869 call s:PlaceSign(nr, entry) 870 endif 871endfunc 872 873func s:PlaceSign(nr, entry) 874 exe 'sign place ' . (s:break_id + a:nr) . ' line=' . a:entry['lnum'] . ' name=debugBreakpoint' . a:nr . ' file=' . a:entry['fname'] 875 let a:entry['placed'] = 1 876endfunc 877 878" Handle deleting a breakpoint 879" Will remove the sign that shows the breakpoint 880func s:HandleBreakpointDelete(msg) 881 let nr = substitute(a:msg, '.*id="\([0-9]*\)\".*', '\1', '') + 0 882 if nr == 0 883 return 884 endif 885 if has_key(s:breakpoints, nr) 886 let entry = s:breakpoints[nr] 887 if has_key(entry, 'placed') 888 exe 'sign unplace ' . (s:break_id + nr) 889 unlet entry['placed'] 890 endif 891 unlet s:breakpoints[nr] 892 endif 893endfunc 894 895" Handle a BufRead autocommand event: place any signs. 896func s:BufRead() 897 let fname = expand('<afile>:p') 898 for [nr, entry] in items(s:breakpoints) 899 if entry['fname'] == fname 900 call s:PlaceSign(nr, entry) 901 endif 902 endfor 903endfunc 904 905" Handle a BufUnloaded autocommand event: unplace any signs. 906func s:BufUnloaded() 907 let fname = expand('<afile>:p') 908 for [nr, entry] in items(s:breakpoints) 909 if entry['fname'] == fname 910 let entry['placed'] = 0 911 endif 912 endfor 913endfunc 914