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