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