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