To: vim_dev@googlegroups.com Subject: Patch 8.1.0071 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 8.1.0071 Problem: Terminal debugger only works with the terminal feature. Solution: Make it also work with a prompt buffer. Makes it possible to use on MS-Windows. Various other improvements. (closes #3012) Files: runtime/doc/terminal.txt, runtime/pack/dist/opt/termdebug/plugin/termdebug.vim *** ../vim-8.1.0070/runtime/doc/terminal.txt 2018-06-12 18:04:28.034267877 +0200 --- runtime/doc/terminal.txt 2018-06-17 21:33:43.206011529 +0200 *************** *** 38,43 **** --- 38,44 ---- Stepping through code |termdebug-stepping| Inspecting variables |termdebug-variables| Other commands |termdebug-commands| + Prompt mode |termdebug-prompt| Communication |termdebug-communication| Customizing |termdebug-customizing| *************** *** 620,625 **** --- 621,630 ---- the source code in a Vim window. Since this is completely contained inside Vim this also works remotely over an ssh connection. + When the |+terminal| feature is missing, the plugin will use the "prompt" + buffer type, if possible. The running program will then use a newly opened + terminal window. See |termdebug-prompt| below for details. + Starting ~ *termdebug-starting* *************** *** 799,804 **** --- 804,826 ---- isn't one + Prompt mode ~ + *termdebug-prompt* + When the |+terminal| feature is not supported and on MS-Windows, gdb will run + in a buffer with 'buftype' set to "prompt". This works slightly differently: + - The gdb window will be in Insert mode while typing commands. Go to Normal + mode with , then you can move around in the buffer, copy/paste, etc. + Go back to editing the gdb command with any command that starts Insert mode, + such as `a` or `i`. + - The program being debugged will run in a separate window. On MS-Windows + this is a new console window. On Unix, if the |+terminal| feature is + available a Terminal window will be opened to run the debugged program in. + + *termdebug_use_prompt* + Prompt mode can be used even when the |+terminal| feature is present with: > + let g:termdebug_use_prompt = 1 + + Communication ~ *termdebug-communication* There is another, hidden, buffer, which is used for Vim to communicate with *************** *** 836,841 **** --- 858,871 ---- hi debugBreakpoint term=reverse ctermbg=red guibg=red + Shorcuts *termdebug_shortcuts* + + You can define your own shortcuts (mappings) to control gdb, that can work in + any window, using the TermDebugSendCommand() function. Example: > + map ,w :call TermDebugSendCommand('where') + The argument is the gdb command. + + Popup menu *termdebug_popup* By default the Termdebug plugin sets 'mousemodel' to "popup_setpos" and adds *** ../vim-8.1.0070/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim 2018-04-16 16:16:51.000000000 +0200 --- runtime/pack/dist/opt/termdebug/plugin/termdebug.vim 2018-06-17 21:27:00.040243063 +0200 *************** *** 1,27 **** " Debugger plugin using gdb. " ! " WORK IN PROGRESS - much doesn't work yet " ! " Open two visible terminal windows: ! " 1. run a pty, as with ":term NONE" ! " 2. run gdb, passing the pty ! " The current window is used to view source code and follows gdb. " " A third terminal window is hidden, it is used for communication with gdb. " " The communication with gdb uses GDB/MI. See: " https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html - " - " Author: Bram Moolenaar - " Copyright: Vim license applies, see ":help license" ! " In case this gets loaded twice. if exists(':Termdebug') finish endif ! " Uncomment this line to write logging in "debuglog". ! " call ch_logfile('debuglog', 'w') " The command that starts debugging, e.g. ":Termdebug vim". " To end type "quit" in the gdb window. --- 1,60 ---- " Debugger plugin using gdb. " ! " Author: Bram Moolenaar ! " Copyright: Vim license applies, see ":help license" ! " Last Update: 2018 Jun 3 ! " ! " WORK IN PROGRESS - Only the basics work ! " Note: On MS-Windows you need a recent version of gdb. The one included with ! " MingW is too old (7.6.1). ! " I used version 7.12 from http://www.equation.com/servlet/equation.cmd?fa=gdb ! " ! " There are two ways to run gdb: ! " - In a terminal window; used if possible, does not work on MS-Windows ! " Not used when g:termdebug_use_prompt is set to 1. ! " - Using a "prompt" buffer; may use a terminal window for the program " ! " For both the current window is used to view source code and shows the ! " current statement from gdb. " + " USING A TERMINAL WINDOW + " + " Opens two visible terminal windows: + " 1. runs a pty for the debugged program, as with ":term NONE" + " 2. runs gdb, passing the pty of the debugged program " A third terminal window is hidden, it is used for communication with gdb. " + " USING A PROMPT BUFFER + " + " Opens a window with a prompt buffer to communicate with gdb. + " Gdb is run as a job with callbacks for I/O. + " On Unix another terminal window is opened to run the debugged program + " On MS-Windows a separate console is opened to run the debugged program + " " The communication with gdb uses GDB/MI. See: " https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html ! " In case this gets sourced twice. if exists(':Termdebug') finish endif ! " Need either the +terminal feature or +channel and the prompt buffer. ! " The terminal feature does not work with gdb on win32. ! if has('terminal') && !has('win32') ! let s:way = 'terminal' ! elseif has('channel') && exists('*prompt_setprompt') ! let s:way = 'prompt' ! else ! if has('terminal') ! let s:err = 'Cannot debug, missing prompt buffer support' ! else ! let s:err = 'Cannot debug, +channel feature is not supported' ! endif ! command -nargs=* -complete=file -bang Termdebug echoerr s:err ! command -nargs=+ -complete=file -bang TermdebugCommand echoerr s:err ! finish ! endif " The command that starts debugging, e.g. ":Termdebug vim". " To end type "quit" in the gdb window. *************** *** 59,66 **** echoerr 'Terminal debugger already running' return endif ! let s:startwin = win_getid(winnr()) let s:startsigncolumn = &signcolumn let s:save_columns = 0 --- 92,103 ---- echoerr 'Terminal debugger already running' return endif + let s:ptywin = 0 ! " Uncomment this line to write logging in "debuglog". ! " call ch_logfile('debuglog', 'w') ! ! let s:sourcewin = win_getid(winnr()) let s:startsigncolumn = &signcolumn let s:save_columns = 0 *************** *** 69,83 **** let s:save_columns = &columns let &columns = g:termdebug_wide endif ! let vertical = 1 else ! let vertical = 0 endif ! " Open a terminal window without a job, to run the debugged program let s:ptybuf = term_start('NONE', { ! \ 'term_name': 'gdb program', ! \ 'vertical': vertical, \ }) if s:ptybuf == 0 echoerr 'Failed to open the program terminal window' --- 106,136 ---- let s:save_columns = &columns let &columns = g:termdebug_wide endif ! let s:vertical = 1 ! else ! let s:vertical = 0 ! endif ! ! " Override using a terminal window by setting g:termdebug_use_prompt to 1. ! let use_prompt = exists('g:termdebug_use_prompt') && g:termdebug_use_prompt ! if has('terminal') && !has('win32') && !use_prompt ! let s:way = 'terminal' else ! let s:way = 'prompt' endif ! if s:way == 'prompt' ! call s:StartDebug_prompt(a:dict) ! else ! call s:StartDebug_term(a:dict) ! endif ! endfunc ! ! func s:StartDebug_term(dict) ! " Open a terminal window without a job, to run the debugged program in. let s:ptybuf = term_start('NONE', { ! \ 'term_name': 'debugged program', ! \ 'vertical': s:vertical, \ }) if s:ptybuf == 0 echoerr 'Failed to open the program terminal window' *************** *** 85,91 **** endif let pty = job_info(term_getjob(s:ptybuf))['tty_out'] let s:ptywin = win_getid(winnr()) ! if vertical " Assuming the source code window will get a signcolumn, use two more " columns for that, thus one less for the terminal window. exe (&columns / 2 - 1) . "wincmd |" --- 138,144 ---- endif let pty = job_info(term_getjob(s:ptybuf))['tty_out'] let s:ptywin = win_getid(winnr()) ! if s:vertical " Assuming the source code window will get a signcolumn, use two more " columns for that, thus one less for the terminal window. exe (&columns / 2 - 1) . "wincmd |" *************** *** 110,118 **** let proc_args = get(a:dict, 'proc_args', []) let cmd = [g:termdebugger, '-quiet', '-tty', pty] + gdb_args ! echomsg 'executing "' . join(cmd) . '"' let s:gdbbuf = term_start(cmd, { ! \ 'exit_cb': function('s:EndDebug'), \ 'term_finish': 'close', \ }) if s:gdbbuf == 0 --- 163,171 ---- let proc_args = get(a:dict, 'proc_args', []) let cmd = [g:termdebugger, '-quiet', '-tty', pty] + gdb_args ! call ch_log('executing "' . join(cmd) . '"') let s:gdbbuf = term_start(cmd, { ! \ 'exit_cb': function('s:EndTermDebug'), \ 'term_finish': 'close', \ }) if s:gdbbuf == 0 *************** *** 166,176 **** " exec-interrupt, since many commands don't work properly while the target is " running. call s:SendCommand('-gdb-set mi-async on') " Disable pagination, it causes everything to stop at the gdb " "Type to continue" prompt. ! call s:SendCommand('-gdb-set pagination off') " Sign used to highlight the line where the program has stopped. " There can be only one. sign define debugPC linehl=debugPC --- 219,326 ---- " exec-interrupt, since many commands don't work properly while the target is " running. call s:SendCommand('-gdb-set mi-async on') + " Older gdb uses a different command. + call s:SendCommand('-gdb-set target-async on') " Disable pagination, it causes everything to stop at the gdb " "Type to continue" prompt. ! call s:SendCommand('set pagination off') ! ! call s:StartDebugCommon(a:dict) ! endfunc ! ! func s:StartDebug_prompt(dict) ! " Open a window with a prompt buffer to run gdb in. ! if s:vertical ! vertical new ! else ! new ! endif ! let s:gdbwin = win_getid(winnr()) ! let s:promptbuf = bufnr('') ! call prompt_setprompt(s:promptbuf, 'gdb> ') ! set buftype=prompt ! file gdb ! call prompt_setcallback(s:promptbuf, function('s:PromptCallback')) ! call prompt_setinterrupt(s:promptbuf, function('s:PromptInterrupt')) + if s:vertical + " Assuming the source code window will get a signcolumn, use two more + " columns for that, thus one less for the terminal window. + exe (&columns / 2 - 1) . "wincmd |" + endif + + " Add -quiet to avoid the intro message causing a hit-enter prompt. + let gdb_args = get(a:dict, 'gdb_args', []) + let proc_args = get(a:dict, 'proc_args', []) + + let cmd = [g:termdebugger, '-quiet', '--interpreter=mi2'] + gdb_args + call ch_log('executing "' . join(cmd) . '"') + + let s:gdbjob = job_start(cmd, { + \ 'exit_cb': function('s:EndPromptDebug'), + \ 'out_cb': function('s:GdbOutCallback'), + \ }) + if job_status(s:gdbjob) != "run" + echoerr 'Failed to start gdb' + exe 'bwipe! ' . s:promptbuf + return + endif + let s:gdb_channel = job_getchannel(s:gdbjob) + + " Interpret commands while the target is running. This should usualy only + " be exec-interrupt, since many commands don't work properly while the + " target is running. + call s:SendCommand('-gdb-set mi-async on') + " Older gdb uses a different command. + call s:SendCommand('-gdb-set target-async on') + + let s:ptybuf = 0 + if has('win32') + " MS-Windows: run in a new console window for maximum compatibility + call s:SendCommand('set new-console on') + elseif has('terminal') + " Unix: Run the debugged program in a terminal window. Open it below the + " gdb window. + belowright let s:ptybuf = term_start('NONE', { + \ 'term_name': 'debugged program', + \ }) + if s:ptybuf == 0 + echoerr 'Failed to open the program terminal window' + call job_stop(s:gdbjob) + return + endif + let s:ptywin = win_getid(winnr()) + let pty = job_info(term_getjob(s:ptybuf))['tty_out'] + call s:SendCommand('tty ' . pty) + + " Since GDB runs in a prompt window, the environment has not been set to + " match a terminal window, need to do that now. + call s:SendCommand('set env TERM = xterm-color') + call s:SendCommand('set env ROWS = ' . winheight(s:ptywin)) + call s:SendCommand('set env LINES = ' . winheight(s:ptywin)) + call s:SendCommand('set env COLUMNS = ' . winwidth(s:ptywin)) + call s:SendCommand('set env COLORS = ' . &t_Co) + call s:SendCommand('set env VIM_TERMINAL = ' . v:version) + else + " TODO: open a new terminal get get the tty name, pass on to gdb + call s:SendCommand('show inferior-tty') + endif + call s:SendCommand('set print pretty on') + call s:SendCommand('set breakpoint pending on') + " Disable pagination, it causes everything to stop at the gdb + call s:SendCommand('set pagination off') + + " Set arguments to be run + if len(proc_args) + call s:SendCommand('set args ' . join(proc_args)) + endif + + call s:StartDebugCommon(a:dict) + startinsert + endfunc + + func s:StartDebugCommon(dict) " Sign used to highlight the line where the program has stopped. " There can be only one. sign define debugPC linehl=debugPC *************** *** 180,186 **** sign define debugBreakpoint text=>> texthl=debugBreakpoint " Install debugger commands in the text window. ! call win_gotoid(s:startwin) call s:InstallCommands() call win_gotoid(s:gdbwin) --- 330,336 ---- sign define debugBreakpoint text=>> texthl=debugBreakpoint " Install debugger commands in the text window. ! call win_gotoid(s:sourcewin) call s:InstallCommands() call win_gotoid(s:gdbwin) *************** *** 202,228 **** au BufUnload * call s:BufUnloaded() augroup END ! " Run the command if the bang attribute was given ! " and got to the window if get(a:dict, 'bang', 0) call s:SendCommand('-exec-run') call win_gotoid(s:ptywin) endif endfunc ! func s:EndDebug(job, status) ! exe 'bwipe! ' . s:ptybuf exe 'bwipe! ' . s:commbuf unlet s:gdbwin let curwinid = win_getid(winnr()) ! call win_gotoid(s:startwin) let &signcolumn = s:startsigncolumn call s:DeleteCommands() call win_gotoid(curwinid) if s:save_columns > 0 let &columns = s:save_columns endif --- 352,481 ---- au BufUnload * call s:BufUnloaded() augroup END ! " Run the command if the bang attribute was given and got to the debug ! " window. if get(a:dict, 'bang', 0) call s:SendCommand('-exec-run') call win_gotoid(s:ptywin) endif + endfunc + + " Send a command to gdb. "cmd" is the string without line terminator. + func s:SendCommand(cmd) + call ch_log('sending to gdb: ' . a:cmd) + if s:way == 'prompt' + call ch_sendraw(s:gdb_channel, a:cmd . "\n") + else + call term_sendkeys(s:commbuf, a:cmd . "\r") + endif + endfunc + + " This is global so that a user can create their mappings with this. + func TermDebugSendCommand(cmd) + if s:way == 'prompt' + call ch_sendraw(s:gdb_channel, a:cmd . "\n") + else + let do_continue = 0 + if !s:stopped + let do_continue = 1 + call s:SendCommand('-exec-interrupt') + sleep 10m + endif + call term_sendkeys(s:gdbbuf, a:cmd . "\r") + if do_continue + Continue + endif + endif + endfunc + + " Function called when entering a line in the prompt buffer. + func s:PromptCallback(text) + call s:SendCommand(a:text) + endfunc + + " Function called when pressing CTRL-C in the prompt buffer. + func s:PromptInterrupt() + call ch_log('Interrupting gdb') + call job_stop(s:gdbjob, 'int') + endfunc + + " Function called when gdb outputs text. + func s:GdbOutCallback(channel, text) + call ch_log('received from gdb: ' . a:text) + + " Drop the gdb prompt, we have our own. + " Drop status and echo'd commands. + if a:text == '(gdb) ' || a:text == '^done' || a:text[0] == '&' || a:text[0] == '=' + return + endif + if a:text =~ '^^error,msg=' + let text = s:DecodeMessage(a:text[11:]) + if exists('s:evalexpr') && text =~ 'A syntax error in expression, near\|No symbol .* in current context' + " Silently drop evaluation errors. + unlet s:evalexpr + return + endif + elseif a:text[0] == '~' + let text = s:DecodeMessage(a:text[1:]) + else + call s:CommOutput(a:channel, a:text) + return + endif + let curwinid = win_getid(winnr()) + call win_gotoid(s:gdbwin) + + " Add the output above the current prompt. + call append(line('$') - 1, text) + set nomodified + + call win_gotoid(curwinid) endfunc ! " Decode a message from gdb. quotedText starts with a ", return the text up ! " to the next ", unescaping characters. ! func s:DecodeMessage(quotedText) ! if a:quotedText[0] != '"' ! echoerr 'DecodeMessage(): missing quote' ! return ! endif ! let result = '' ! let i = 1 ! while a:quotedText[i] != '"' && i < len(a:quotedText) ! if a:quotedText[i] == '\' ! let i += 1 ! if a:quotedText[i] == 'n' ! " drop \n ! let i += 1 ! continue ! endif ! endif ! let result .= a:quotedText[i] ! let i += 1 ! endwhile ! return result ! endfunc ! ! func s:EndTermDebug(job, status) exe 'bwipe! ' . s:commbuf unlet s:gdbwin + call s:EndDebugCommon() + endfunc + + func s:EndDebugCommon() let curwinid = win_getid(winnr()) ! if exists('s:ptybuf') && s:ptybuf ! exe 'bwipe! ' . s:ptybuf ! endif ! ! call win_gotoid(s:sourcewin) let &signcolumn = s:startsigncolumn call s:DeleteCommands() call win_gotoid(curwinid) + if s:save_columns > 0 let &columns = s:save_columns endif *************** *** 240,245 **** --- 493,511 ---- au! TermDebug endfunc + func s:EndPromptDebug(job, status) + let curwinid = win_getid(winnr()) + call win_gotoid(s:gdbwin) + close + if curwinid != s:gdbwin + call win_gotoid(curwinid) + endif + + call s:EndDebugCommon() + unlet s:gdbwin + call ch_log("Returning from EndPromptDebug()") + endfunc + " Handle a message received from gdb on the GDB/MI interface. func s:CommOutput(chan, msg) let msgs = split(a:msg, "\r") *************** *** 275,285 **** command -nargs=* Run call s:Run() command -nargs=* Arguments call s:SendCommand('-exec-arguments ' . ) command Stop call s:SendCommand('-exec-interrupt') ! command Continue call s:SendCommand('-exec-continue') command -range -nargs=* Evaluate call s:Evaluate(, ) command Gdb call win_gotoid(s:gdbwin) command Program call win_gotoid(s:ptywin) ! command Source call s:GotoStartwinOrCreateIt() command Winbar call s:InstallWinbar() " TODO: can the K mapping be restored? --- 541,558 ---- command -nargs=* Run call s:Run() command -nargs=* Arguments call s:SendCommand('-exec-arguments ' . ) command Stop call s:SendCommand('-exec-interrupt') ! ! " using -exec-continue results in CTRL-C in gdb window not working ! if s:way == 'prompt' ! command Continue call s:SendCommand('continue') ! else ! command Continue call term_sendkeys(s:gdbbuf, "continue\r") ! endif ! command -range -nargs=* Evaluate call s:Evaluate(, ) command Gdb call win_gotoid(s:gdbwin) command Program call win_gotoid(s:ptywin) ! command Source call s:GotoSourcewinOrCreateIt() command Winbar call s:InstallWinbar() " TODO: can the K mapping be restored? *************** *** 375,381 **** let do_continue = 0 if !s:stopped let do_continue = 1 ! call s:SendCommand('-exec-interrupt') sleep 10m endif call s:SendCommand('-break-insert --source ' --- 648,660 ---- let do_continue = 0 if !s:stopped let do_continue = 1 ! if s:way == 'prompt' ! " Need to send a signal to get the UI to listen. Strangely this is only ! " needed once. ! call job_stop(s:gdbjob, 'int') ! else ! call s:SendCommand('-exec-interrupt') ! endif sleep 10m endif call s:SendCommand('-break-insert --source ' *************** *** 391,397 **** let lnum = line('.') for [key, val] in items(s:breakpoints) if val['fname'] == fname && val['lnum'] == lnum ! call term_sendkeys(s:commbuf, '-break-delete ' . key . "\r") " Assume this always wors, the reply is simply "^done". exe 'sign unplace ' . (s:break_id + key) unlet s:breakpoints[key] --- 670,676 ---- let lnum = line('.') for [key, val] in items(s:breakpoints) if val['fname'] == fname && val['lnum'] == lnum ! call s:SendCommand('-break-delete ' . key) " Assume this always wors, the reply is simply "^done". exe 'sign unplace ' . (s:break_id + key) unlet s:breakpoints[key] *************** *** 400,410 **** endfor endfunc - " :Next, :Continue, etc - send a command to gdb - func s:SendCommand(cmd) - call term_sendkeys(s:commbuf, a:cmd . "\r") - endfunc - func s:Run(args) if a:args != '' call s:SendCommand('-exec-arguments ' . a:args) --- 679,684 ---- *************** *** 466,472 **** " Show a balloon with information of the variable under the mouse pointer, " if there is any. func TermDebugBalloonExpr() ! if v:beval_winid != s:startwin return endif let s:evalFromBalloonExpr = 1 --- 740,751 ---- " Show a balloon with information of the variable under the mouse pointer, " if there is any. func TermDebugBalloonExpr() ! if v:beval_winid != s:sourcewin ! return ! endif ! if !s:stopped ! " Only evaluate when stopped, otherwise setting a breakpoint using the ! " mouse triggers a balloon. return endif let s:evalFromBalloonExpr = 1 *************** *** 487,496 **** echoerr substitute(a:msg, '.*msg="\(.*\)"', '\1', '') endfunc ! func s:GotoStartwinOrCreateIt() ! if !win_gotoid(s:startwin) new ! let s:startwin = win_getid(winnr()) call s:InstallWinbar() endif endfunc --- 766,775 ---- echoerr substitute(a:msg, '.*msg="\(.*\)"', '\1', '') endfunc ! func s:GotoSourcewinOrCreateIt() ! if !win_gotoid(s:sourcewin) new ! let s:sourcewin = win_getid(winnr()) call s:InstallWinbar() endif endfunc *************** *** 506,512 **** let s:stopped = 0 endif ! call s:GotoStartwinOrCreateIt() let fname = substitute(a:msg, '.*fullname="\([^"]*\)".*', '\1', '') if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname) --- 785,791 ---- let s:stopped = 0 endif ! call s:GotoSourcewinOrCreateIt() let fname = substitute(a:msg, '.*fullname="\([^"]*\)".*', '\1', '') if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname) *************** *** 516,522 **** if &modified " TODO: find existing window exe 'split ' . fnameescape(fname) ! let s:startwin = win_getid(winnr()) call s:InstallWinbar() else exe 'edit ' . fnameescape(fname) --- 795,801 ---- if &modified " TODO: find existing window exe 'split ' . fnameescape(fname) ! let s:sourcewin = win_getid(winnr()) call s:InstallWinbar() else exe 'edit ' . fnameescape(fname) *** ../vim-8.1.0070/src/version.c 2018-06-17 20:10:36.208066158 +0200 --- src/version.c 2018-06-17 21:30:33.843055781 +0200 *************** *** 763,764 **** --- 763,766 ---- { /* Add new patch number below this line */ + /**/ + 71, /**/ -- hundred-and-one symptoms of being an internet addict: 66. You create a homepage with the impression to cure the afflicted...but your hidden agenda is to receive more e-mail. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\ \\\ an exciting new programming language -- http://www.Zimbu.org /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///