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 elseif a:quotedText[i] == 't' 515 " append \t 516 let i += 1 517 let result .= "\t" 518 continue 519 endif 520 endif 521 let result .= a:quotedText[i] 522 let i += 1 523 endwhile 524 return result 525endfunc 526 527" Extract the "name" value from a gdb message with fullname="name". 528func s:GetFullname(msg) 529 if a:msg !~ 'fullname' 530 return '' 531 endif 532 let name = s:DecodeMessage(substitute(a:msg, '.*fullname=', '', '')) 533 if has('win32') && name =~ ':\\\\' 534 " sometimes the name arrives double-escaped 535 let name = substitute(name, '\\\\', '\\', 'g') 536 endif 537 return name 538endfunc 539 540func s:EndTermDebug(job, status) 541 exe 'bwipe! ' . s:commbuf 542 unlet s:gdbwin 543 544 call s:EndDebugCommon() 545endfunc 546 547func s:EndDebugCommon() 548 let curwinid = win_getid(winnr()) 549 550 if exists('s:ptybuf') && s:ptybuf 551 exe 'bwipe! ' . s:ptybuf 552 endif 553 554 call win_gotoid(s:sourcewin) 555 let &signcolumn = s:startsigncolumn 556 call s:DeleteCommands() 557 558 call win_gotoid(curwinid) 559 560 if s:save_columns > 0 561 let &columns = s:save_columns 562 endif 563 564 if has("balloon_eval") || has("balloon_eval_term") 565 set balloonexpr= 566 if has("balloon_eval") 567 set noballooneval 568 endif 569 if has("balloon_eval_term") 570 set noballoonevalterm 571 endif 572 endif 573 574 au! TermDebug 575endfunc 576 577func s:EndPromptDebug(job, status) 578 let curwinid = win_getid(winnr()) 579 call win_gotoid(s:gdbwin) 580 set nomodified 581 close 582 if curwinid != s:gdbwin 583 call win_gotoid(curwinid) 584 endif 585 586 call s:EndDebugCommon() 587 unlet s:gdbwin 588 call ch_log("Returning from EndPromptDebug()") 589endfunc 590 591" Handle a message received from gdb on the GDB/MI interface. 592func s:CommOutput(chan, msg) 593 let msgs = split(a:msg, "\r") 594 595 for msg in msgs 596 " remove prefixed NL 597 if msg[0] == "\n" 598 let msg = msg[1:] 599 endif 600 if msg != '' 601 if msg =~ '^\(\*stopped\|\*running\|=thread-selected\)' 602 call s:HandleCursor(msg) 603 elseif msg =~ '^\^done,bkpt=' || msg =~ '^=breakpoint-created,' 604 call s:HandleNewBreakpoint(msg) 605 elseif msg =~ '^=breakpoint-deleted,' 606 call s:HandleBreakpointDelete(msg) 607 elseif msg =~ '^=thread-group-started' 608 call s:HandleProgramRun(msg) 609 elseif msg =~ '^\^done,value=' 610 call s:HandleEvaluate(msg) 611 elseif msg =~ '^\^error,msg=' 612 call s:HandleError(msg) 613 endif 614 endif 615 endfor 616endfunc 617 618func s:GotoProgram() 619 if has('win32') 620 if executable('powershell') 621 call system(printf('powershell -Command "add-type -AssemblyName microsoft.VisualBasic;[Microsoft.VisualBasic.Interaction]::AppActivate(%d);"', s:pid)) 622 endif 623 else 624 win_gotoid(s:ptywin) 625 endif 626endfunc 627 628" Install commands in the current window to control the debugger. 629func s:InstallCommands() 630 let save_cpo = &cpo 631 set cpo&vim 632 633 command -nargs=? Break call s:SetBreakpoint(<q-args>) 634 command Clear call s:ClearBreakpoint() 635 command Step call s:SendCommand('-exec-step') 636 command Over call s:SendCommand('-exec-next') 637 command Finish call s:SendCommand('-exec-finish') 638 command -nargs=* Run call s:Run(<q-args>) 639 command -nargs=* Arguments call s:SendCommand('-exec-arguments ' . <q-args>) 640 command Stop call s:SendCommand('-exec-interrupt') 641 642 " using -exec-continue results in CTRL-C in gdb window not working 643 if s:way == 'prompt' 644 command Continue call s:SendCommand('continue') 645 else 646 command Continue call term_sendkeys(s:gdbbuf, "continue\r") 647 endif 648 649 command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>) 650 command Gdb call win_gotoid(s:gdbwin) 651 command Program call s:GotoProgram() 652 command Source call s:GotoSourcewinOrCreateIt() 653 command Winbar call s:InstallWinbar() 654 655 " TODO: can the K mapping be restored? 656 nnoremap K :Evaluate<CR> 657 658 if has('menu') && &mouse != '' 659 call s:InstallWinbar() 660 661 if !exists('g:termdebug_popup') || g:termdebug_popup != 0 662 let s:saved_mousemodel = &mousemodel 663 let &mousemodel = 'popup_setpos' 664 an 1.200 PopUp.-SEP3- <Nop> 665 an 1.210 PopUp.Set\ breakpoint :Break<CR> 666 an 1.220 PopUp.Clear\ breakpoint :Clear<CR> 667 an 1.230 PopUp.Evaluate :Evaluate<CR> 668 endif 669 endif 670 671 let &cpo = save_cpo 672endfunc 673 674let s:winbar_winids = [] 675 676" Install the window toolbar in the current window. 677func s:InstallWinbar() 678 if has('menu') && &mouse != '' 679 nnoremenu WinBar.Step :Step<CR> 680 nnoremenu WinBar.Next :Over<CR> 681 nnoremenu WinBar.Finish :Finish<CR> 682 nnoremenu WinBar.Cont :Continue<CR> 683 nnoremenu WinBar.Stop :Stop<CR> 684 nnoremenu WinBar.Eval :Evaluate<CR> 685 call add(s:winbar_winids, win_getid(winnr())) 686 endif 687endfunc 688 689" Delete installed debugger commands in the current window. 690func s:DeleteCommands() 691 delcommand Break 692 delcommand Clear 693 delcommand Step 694 delcommand Over 695 delcommand Finish 696 delcommand Run 697 delcommand Arguments 698 delcommand Stop 699 delcommand Continue 700 delcommand Evaluate 701 delcommand Gdb 702 delcommand Program 703 delcommand Source 704 delcommand Winbar 705 706 nunmap K 707 708 if has('menu') 709 " Remove the WinBar entries from all windows where it was added. 710 let curwinid = win_getid(winnr()) 711 for winid in s:winbar_winids 712 if win_gotoid(winid) 713 aunmenu WinBar.Step 714 aunmenu WinBar.Next 715 aunmenu WinBar.Finish 716 aunmenu WinBar.Cont 717 aunmenu WinBar.Stop 718 aunmenu WinBar.Eval 719 endif 720 endfor 721 call win_gotoid(curwinid) 722 let s:winbar_winids = [] 723 724 if exists('s:saved_mousemodel') 725 let &mousemodel = s:saved_mousemodel 726 unlet s:saved_mousemodel 727 aunmenu PopUp.-SEP3- 728 aunmenu PopUp.Set\ breakpoint 729 aunmenu PopUp.Clear\ breakpoint 730 aunmenu PopUp.Evaluate 731 endif 732 endif 733 734 exe 'sign unplace ' . s:pc_id 735 for [id, entries] in items(s:breakpoints) 736 for subid in keys(entries) 737 exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid) 738 endfor 739 endfor 740 unlet s:breakpoints 741 unlet s:breakpoint_locations 742 743 sign undefine debugPC 744 for val in s:BreakpointSigns 745 exe "sign undefine debugBreakpoint" . val 746 endfor 747 let s:BreakpointSigns = [] 748endfunc 749 750" :Break - Set a breakpoint at the cursor position. 751func s:SetBreakpoint(at) 752 " Setting a breakpoint may not work while the program is running. 753 " Interrupt to make it work. 754 let do_continue = 0 755 if !s:stopped 756 let do_continue = 1 757 if s:way == 'prompt' 758 call s:PromptInterrupt() 759 else 760 call s:SendCommand('-exec-interrupt') 761 endif 762 sleep 10m 763 endif 764 765 " Use the fname:lnum format, older gdb can't handle --source. 766 let at = empty(a:at) ? 767 \ fnameescape(expand('%:p')) . ':' . line('.') : a:at 768 call s:SendCommand('-break-insert ' . at) 769 if do_continue 770 call s:SendCommand('-exec-continue') 771 endif 772endfunc 773 774" :Clear - Delete a breakpoint at the cursor position. 775func s:ClearBreakpoint() 776 let fname = fnameescape(expand('%:p')) 777 let lnum = line('.') 778 let bploc = printf('%s:%d', fname, lnum) 779 if has_key(s:breakpoint_locations, bploc) 780 let idx = 0 781 for id in s:breakpoint_locations[bploc] 782 if has_key(s:breakpoints, id) 783 " Assume this always works, the reply is simply "^done". 784 call s:SendCommand('-break-delete ' . id) 785 for subid in keys(s:breakpoints[id]) 786 exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid) 787 endfor 788 unlet s:breakpoints[id] 789 unlet s:breakpoint_locations[bploc][idx] 790 break 791 else 792 let idx += 1 793 endif 794 endfor 795 if empty(s:breakpoint_locations[bploc]) 796 unlet s:breakpoint_locations[bploc] 797 endif 798 endif 799endfunc 800 801func s:Run(args) 802 if a:args != '' 803 call s:SendCommand('-exec-arguments ' . a:args) 804 endif 805 call s:SendCommand('-exec-run') 806endfunc 807 808func s:SendEval(expr) 809 call s:SendCommand('-data-evaluate-expression "' . a:expr . '"') 810 let s:evalexpr = a:expr 811endfunc 812 813" :Evaluate - evaluate what is under the cursor 814func s:Evaluate(range, arg) 815 if a:arg != '' 816 let expr = a:arg 817 elseif a:range == 2 818 let pos = getcurpos() 819 let reg = getreg('v', 1, 1) 820 let regt = getregtype('v') 821 normal! gv"vy 822 let expr = @v 823 call setpos('.', pos) 824 call setreg('v', reg, regt) 825 else 826 let expr = expand('<cexpr>') 827 endif 828 let s:ignoreEvalError = 0 829 call s:SendEval(expr) 830endfunc 831 832let s:ignoreEvalError = 0 833let s:evalFromBalloonExpr = 0 834 835" Handle the result of data-evaluate-expression 836func s:HandleEvaluate(msg) 837 let value = substitute(a:msg, '.*value="\(.*\)"', '\1', '') 838 let value = substitute(value, '\\"', '"', 'g') 839 if s:evalFromBalloonExpr 840 if s:evalFromBalloonExprResult == '' 841 let s:evalFromBalloonExprResult = s:evalexpr . ': ' . value 842 else 843 let s:evalFromBalloonExprResult .= ' = ' . value 844 endif 845 call balloon_show(s:evalFromBalloonExprResult) 846 else 847 echomsg '"' . s:evalexpr . '": ' . value 848 endif 849 850 if s:evalexpr[0] != '*' && value =~ '^0x' && value != '0x0' && value !~ '"$' 851 " Looks like a pointer, also display what it points to. 852 let s:ignoreEvalError = 1 853 call s:SendEval('*' . s:evalexpr) 854 else 855 let s:evalFromBalloonExpr = 0 856 endif 857endfunc 858 859" Show a balloon with information of the variable under the mouse pointer, 860" if there is any. 861func TermDebugBalloonExpr() 862 if v:beval_winid != s:sourcewin 863 return '' 864 endif 865 if !s:stopped 866 " Only evaluate when stopped, otherwise setting a breakpoint using the 867 " mouse triggers a balloon. 868 return '' 869 endif 870 let s:evalFromBalloonExpr = 1 871 let s:evalFromBalloonExprResult = '' 872 let s:ignoreEvalError = 1 873 call s:SendEval(v:beval_text) 874 return '' 875endfunc 876 877" Handle an error. 878func s:HandleError(msg) 879 if s:ignoreEvalError 880 " Result of s:SendEval() failed, ignore. 881 let s:ignoreEvalError = 0 882 let s:evalFromBalloonExpr = 0 883 return 884 endif 885 echoerr substitute(a:msg, '.*msg="\(.*\)"', '\1', '') 886endfunc 887 888func s:GotoSourcewinOrCreateIt() 889 if !win_gotoid(s:sourcewin) 890 new 891 let s:sourcewin = win_getid(winnr()) 892 call s:InstallWinbar() 893 endif 894endfunc 895 896" Handle stopping and running message from gdb. 897" Will update the sign that shows the current position. 898func s:HandleCursor(msg) 899 let wid = win_getid(winnr()) 900 901 if a:msg =~ '^\*stopped' 902 call ch_log('program stopped') 903 let s:stopped = 1 904 elseif a:msg =~ '^\*running' 905 call ch_log('program running') 906 let s:stopped = 0 907 endif 908 909 if a:msg =~ 'fullname=' 910 let fname = s:GetFullname(a:msg) 911 else 912 let fname = '' 913 endif 914 if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname) 915 let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '') 916 if lnum =~ '^[0-9]*$' 917 call s:GotoSourcewinOrCreateIt() 918 if expand('%:p') != fnamemodify(fname, ':p') 919 if &modified 920 " TODO: find existing window 921 exe 'split ' . fnameescape(fname) 922 let s:sourcewin = win_getid(winnr()) 923 call s:InstallWinbar() 924 else 925 exe 'edit ' . fnameescape(fname) 926 endif 927 endif 928 exe lnum 929 exe 'sign unplace ' . s:pc_id 930 exe 'sign place ' . s:pc_id . ' line=' . lnum . ' name=debugPC file=' . fname 931 setlocal signcolumn=yes 932 endif 933 elseif !s:stopped || fname != '' 934 exe 'sign unplace ' . s:pc_id 935 endif 936 937 call win_gotoid(wid) 938endfunc 939 940let s:BreakpointSigns = [] 941 942func s:CreateBreakpoint(id, subid) 943 let nr = printf('%d.%d', a:id, a:subid) 944 if index(s:BreakpointSigns, nr) == -1 945 call add(s:BreakpointSigns, nr) 946 exe "sign define debugBreakpoint" . nr . " text=" . substitute(nr, '\..*', '', '') . " texthl=debugBreakpoint" 947 endif 948endfunc 949 950func! s:SplitMsg(s) 951 return split(a:s, '{.\{-}}\zs') 952endfunction 953 954" Handle setting a breakpoint 955" Will update the sign that shows the breakpoint 956func s:HandleNewBreakpoint(msg) 957 if a:msg !~ 'fullname=' 958 " a watch does not have a file name 959 return 960 endif 961 for msg in s:SplitMsg(a:msg) 962 let fname = s:GetFullname(msg) 963 if empty(fname) 964 continue 965 endif 966 let nr = substitute(msg, '.*number="\([0-9.]*\)\".*', '\1', '') 967 if empty(nr) 968 return 969 endif 970 971 " If "nr" is 123 it becomes "123.0" and subid is "0". 972 " If "nr" is 123.4 it becomes "123.4.0" and subid is "4"; "0" is discarded. 973 let [id, subid; _] = map(split(nr . '.0', '\.'), 'v:val + 0') 974 call s:CreateBreakpoint(id, subid) 975 976 if has_key(s:breakpoints, id) 977 let entries = s:breakpoints[id] 978 else 979 let entries = {} 980 let s:breakpoints[id] = entries 981 endif 982 if has_key(entries, subid) 983 let entry = entries[subid] 984 else 985 let entry = {} 986 let entries[subid] = entry 987 endif 988 989 let lnum = substitute(msg, '.*line="\([^"]*\)".*', '\1', '') 990 let entry['fname'] = fname 991 let entry['lnum'] = lnum 992 993 let bploc = printf('%s:%d', fname, lnum) 994 if !has_key(s:breakpoint_locations, bploc) 995 let s:breakpoint_locations[bploc] = [] 996 endif 997 let s:breakpoint_locations[bploc] += [id] 998 999 if bufloaded(fname) 1000 call s:PlaceSign(id, subid, entry) 1001 endif 1002 endfor 1003endfunc 1004 1005func s:PlaceSign(id, subid, entry) 1006 let nr = printf('%d.%d', a:id, a:subid) 1007 exe 'sign place ' . s:Breakpoint2SignNumber(a:id, a:subid) . ' line=' . a:entry['lnum'] . ' name=debugBreakpoint' . nr . ' file=' . a:entry['fname'] 1008 let a:entry['placed'] = 1 1009endfunc 1010 1011" Handle deleting a breakpoint 1012" Will remove the sign that shows the breakpoint 1013func s:HandleBreakpointDelete(msg) 1014 let id = substitute(a:msg, '.*id="\([0-9]*\)\".*', '\1', '') + 0 1015 if empty(id) 1016 return 1017 endif 1018 if has_key(s:breakpoints, id) 1019 for [subid, entry] in items(s:breakpoints[id]) 1020 if has_key(entry, 'placed') 1021 exe 'sign unplace ' . s:Breakpoint2SignNumber(id, subid) 1022 unlet entry['placed'] 1023 endif 1024 endfor 1025 unlet s:breakpoints[id] 1026 endif 1027endfunc 1028 1029" Handle the debugged program starting to run. 1030" Will store the process ID in s:pid 1031func s:HandleProgramRun(msg) 1032 let nr = substitute(a:msg, '.*pid="\([0-9]*\)\".*', '\1', '') + 0 1033 if nr == 0 1034 return 1035 endif 1036 let s:pid = nr 1037 call ch_log('Detected process ID: ' . s:pid) 1038endfunc 1039 1040" Handle a BufRead autocommand event: place any signs. 1041func s:BufRead() 1042 let fname = expand('<afile>:p') 1043 for [id, entries] in items(s:breakpoints) 1044 for [subid, entry] in items(entries) 1045 if entry['fname'] == fname 1046 call s:PlaceSign(id, subid, entry) 1047 endif 1048 endfor 1049 endfor 1050endfunc 1051 1052" Handle a BufUnloaded autocommand event: unplace any signs. 1053func s:BufUnloaded() 1054 let fname = expand('<afile>:p') 1055 for [id, entries] in items(s:breakpoints) 1056 for [subid, entry] in items(entries) 1057 if entry['fname'] == fname 1058 let entry['placed'] = 0 1059 endif 1060 endfor 1061 endfor 1062endfunc 1063 1064let &cpo = s:keepcpo 1065unlet s:keepcpo 1066