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