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