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