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