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