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