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