Merge pull request #24821 from seandewar/vim-9f2962141514

vim-patch:9f2962141514,f6fb52b667ee,19968fc4ec2c,a76f3221cdcf,2ae7ffe0bc3c,3d3a9152fa6d - Termdebug stuff
This commit is contained in:
Sean Dewar
2023-08-25 11:59:51 +01:00
committed by GitHub
2 changed files with 443 additions and 201 deletions

View File

@@ -317,6 +317,18 @@ This is similar to using "print" in the gdb window.
You can usually shorten `:Evaluate` to `:Ev`. You can usually shorten `:Evaluate` to `:Ev`.
Navigating stack frames ~
*termdebug-frames* *:Frame* *:Up* *:Down*
`:Frame` [frame] select frame [frame], which is a frame number,
address, or function name (default: current frame)
`:Up` [count] go up [count] frames (default: 1; the frame that
called the current)
`+` same (see |termdebug_map_plus| to disable)
`:Down` [count] go down [count] frames (default: 1; the frame called
by the current)
`-` same (see |termdebug_map_minus| to disable)
Other commands ~ Other commands ~
*termdebug-commands* *termdebug-commands*
*:Gdb* jump to the gdb window *:Gdb* jump to the gdb window
@@ -325,7 +337,9 @@ Other commands ~
isn't one isn't one
*:Asm* jump to the window with the disassembly, create it if there *:Asm* jump to the window with the disassembly, create it if there
isn't one isn't one
*:Var* jump to the window with the local and argument variables,
create it if there isn't one. This window updates whenever the
program is stopped
Events ~ Events ~
*termdebug-events* *termdebug-events*
@@ -386,10 +400,18 @@ If there is no g:termdebug_config you can use: >vim
let g:termdebug_use_prompt = 1 let g:termdebug_use_prompt = 1
< <
*termdebug_map_K* *termdebug_map_K*
The K key is normally mapped to :Evaluate. If you do not want this use: >vim The K key is normally mapped to |:Evaluate|. If you do not want this use: >vim
let g:termdebug_config['map_K'] = 0 let g:termdebug_config['map_K'] = 0
If there is no g:termdebug_config you can use: >vim If there is no g:termdebug_config you can use: >vim
let g:termdebug_map_K = 0 let g:termdebug_map_K = 0
<
*termdebug_map_minus*
The - key is normally mapped to |:Down|. If you do not want this use: >vim
let g:termdebug_config['map_minus'] = 0
<
*termdebug_map_plus*
The + key is normally mapped to |:Up|. If you do not want this use: >vim
let g:termdebug_config['map_plus'] = 0
< <
*termdebug_disasm_window* *termdebug_disasm_window*
If you want the Asm window shown by default, set the "disasm_window" flag to If you want the Asm window shown by default, set the "disasm_window" flag to
@@ -400,6 +422,15 @@ If there is no g:termdebug_config you can use: >vim
let g:termdebug_disasm_window = 15 let g:termdebug_disasm_window = 15
Any value greater than 1 will set the Asm window height to that value. Any value greater than 1 will set the Asm window height to that value.
*termdebug_variables_window*
If you want the Var window shown by default, set the flag to 1.
the "variables_window_height" entry can be used to set the window height: >vim
let g:termdebug_config['variables_window'] = 1
let g:termdebug_config['variables_window_height'] = 15
If there is no g:termdebug_config you can use: >vim
let g:termdebug_variables_window = 15
Any value greater than 1 will set the Var window height to that value.
Communication ~ Communication ~
*termdebug-communication* *termdebug-communication*
There is another, hidden, buffer, which is used for Vim to communicate with There is another, hidden, buffer, which is used for Vim to communicate with

View File

@@ -2,7 +2,7 @@
" "
" Author: Bram Moolenaar " Author: Bram Moolenaar
" Copyright: Vim license applies, see ":help license" " Copyright: Vim license applies, see ":help license"
" Last Change: 2023 Jun 24 " Last Change: 2023 Aug 23
" "
" WORK IN PROGRESS - The basics works stable, more to come " WORK IN PROGRESS - The basics works stable, more to come
" Note: In general you need at least GDB 7.12 because this provides the " Note: In general you need at least GDB 7.12 because this provides the
@@ -75,6 +75,7 @@ let s:pc_id = 12
let s:asm_id = 13 let s:asm_id = 13
let s:break_id = 14 " breakpoint number is added to this let s:break_id = 14 " breakpoint number is added to this
let s:stopped = 1 let s:stopped = 1
let s:running = 0
let s:parsing_disasm_msg = 0 let s:parsing_disasm_msg = 0
let s:asm_lines = [] let s:asm_lines = []
@@ -127,6 +128,10 @@ func s:GetCommand()
return type(cmd) == v:t_list ? copy(cmd) : [cmd] return type(cmd) == v:t_list ? copy(cmd) : [cmd]
endfunc endfunc
func s:Echoerr(msg)
echohl ErrorMsg | echom '[termdebug] ' .. a:msg | echohl None
endfunc
func s:StartDebug(bang, ...) func s:StartDebug(bang, ...)
" First argument is the command to debug, second core file or process ID. " First argument is the command to debug, second core file or process ID.
call s:StartDebug_internal({'gdb_args': a:000, 'bang': a:bang}) call s:StartDebug_internal({'gdb_args': a:000, 'bang': a:bang})
@@ -139,18 +144,21 @@ endfunc
func s:StartDebug_internal(dict) func s:StartDebug_internal(dict)
if exists('s:gdbwin') if exists('s:gdbwin')
echoerr 'Terminal debugger already running, cannot run two' call s:Echoerr('Terminal debugger already running, cannot run two')
return return
endif endif
let gdbcmd = s:GetCommand() let gdbcmd = s:GetCommand()
if !executable(gdbcmd[0]) if !executable(gdbcmd[0])
echoerr 'Cannot execute debugger program "' .. gdbcmd[0] .. '"' call s:Echoerr('Cannot execute debugger program "' .. gdbcmd[0] .. '"')
return return
endif endif
let s:ptywin = 0 let s:ptywin = 0
let s:pid = 0 let s:pid = 0
let s:asmwin = 0 let s:asmwin = 0
let s:asmbuf = 0
let s:varwin = 0
let s:varbuf = 0
if exists('#User#TermdebugStartPre') if exists('#User#TermdebugStartPre')
doauto <nomodeline> User TermdebugStartPre doauto <nomodeline> User TermdebugStartPre
@@ -159,7 +167,7 @@ func s:StartDebug_internal(dict)
" Uncomment this line to write logging in "debuglog". " Uncomment this line to write logging in "debuglog".
" call ch_logfile('debuglog', 'w') " call ch_logfile('debuglog', 'w')
let s:sourcewin = win_getid(winnr()) let s:sourcewin = win_getid()
" Remember the old value of 'signcolumn' for each buffer that it's set in, so " Remember the old value of 'signcolumn' for each buffer that it's set in, so
" that we can restore the value for all buffers. " that we can restore the value for all buffers.
@@ -207,11 +215,17 @@ func s:StartDebug_internal(dict)
endif endif
if s:GetDisasmWindow() if s:GetDisasmWindow()
let curwinid = win_getid(winnr()) let curwinid = win_getid()
call s:GotoAsmwinOrCreateIt() call s:GotoAsmwinOrCreateIt()
call win_gotoid(curwinid) call win_gotoid(curwinid)
endif endif
if s:GetVariablesWindow()
let curwinid = win_getid()
call s:GotoVariableswinOrCreateIt()
call win_gotoid(curwinid)
endif
if exists('#User#TermdebugStartPost') if exists('#User#TermdebugStartPost')
doauto <nomodeline> User TermdebugStartPost doauto <nomodeline> User TermdebugStartPost
endif endif
@@ -220,12 +234,19 @@ endfunc
" Use when debugger didn't start or ended. " Use when debugger didn't start or ended.
func s:CloseBuffers() func s:CloseBuffers()
exe 'bwipe! ' . s:ptybuf exe 'bwipe! ' . s:ptybuf
if s:asmbuf > 0 && bufexists(s:asmbuf)
exe 'bwipe! ' . s:asmbuf
endif
if s:varbuf > 0 && bufexists(s:varbuf)
exe 'bwipe! ' . s:varbuf
endif
let s:running = 0
unlet! s:gdbwin unlet! s:gdbwin
endfunc endfunc
func s:CheckGdbRunning() func s:CheckGdbRunning()
if !s:running if !s:gdb_running
echoerr string(s:GetCommand()[0]) . ' exited unexpectedly' call s:Echoerr(string(s:GetCommand()[0]) . ' exited unexpectedly')
call s:CloseBuffers() call s:CloseBuffers()
return '' return ''
endif endif
@@ -237,16 +258,16 @@ func s:StartDebug_term(dict)
execute s:vertical ? 'vnew' : 'new' execute s:vertical ? 'vnew' : 'new'
let s:pty_job_id = termopen('tail -f /dev/null;#gdb program') let s:pty_job_id = termopen('tail -f /dev/null;#gdb program')
if s:pty_job_id == 0 if s:pty_job_id == 0
echoerr 'invalid argument (or job table is full) while opening terminal window' call s:Echoerr('Invalid argument (or job table is full) while opening terminal window')
return return
elseif s:pty_job_id == -1 elseif s:pty_job_id == -1
echoerr 'Failed to open the program terminal window' call s:Echoerr('Failed to open the program terminal window')
return return
endif endif
let pty_job_info = nvim_get_chan_info(s:pty_job_id) let pty_job_info = nvim_get_chan_info(s:pty_job_id)
let s:ptybuf = pty_job_info['buffer'] let s:ptybuf = pty_job_info['buffer']
let pty = pty_job_info['pty'] let pty = pty_job_info['pty']
let s:ptywin = win_getid(winnr()) let s:ptywin = win_getid()
if s:vertical if s:vertical
" Assuming the source code window will get a signcolumn, use two more " Assuming the source code window will get a signcolumn, use two more
" columns for that, thus one less for the terminal window. " columns for that, thus one less for the terminal window.
@@ -264,11 +285,11 @@ func s:StartDebug_term(dict)
\ }) \ })
" hide terminal buffer " hide terminal buffer
if s:comm_job_id == 0 if s:comm_job_id == 0
echoerr 'invalid argument (or job table is full) while opening communication terminal window' call s:Echoerr('Invalid argument (or job table is full) while opening communication terminal window')
exe 'bwipe! ' . s:ptybuf exe 'bwipe! ' . s:ptybuf
return return
elseif s:comm_job_id == -1 elseif s:comm_job_id == -1
echoerr 'Failed to open the communication terminal window' call s:Echoerr('Failed to open the communication terminal window')
exe 'bwipe! ' . s:ptybuf exe 'bwipe! ' . s:ptybuf
return return
endif endif
@@ -309,19 +330,19 @@ func s:StartDebug_term(dict)
" call ch_log('executing "' . join(gdb_cmd) . '"') " call ch_log('executing "' . join(gdb_cmd) . '"')
let s:gdb_job_id = termopen(gdb_cmd, {'on_exit': function('s:EndTermDebug')}) let s:gdb_job_id = termopen(gdb_cmd, {'on_exit': function('s:EndTermDebug')})
if s:gdb_job_id == 0 if s:gdb_job_id == 0
echoerr 'invalid argument (or job table is full) while opening gdb terminal window' call s:Echoerr('Invalid argument (or job table is full) while opening gdb terminal window')
exe 'bwipe! ' . s:ptybuf exe 'bwipe! ' . s:ptybuf
return return
elseif s:gdb_job_id == -1 elseif s:gdb_job_id == -1
echoerr 'Failed to open the gdb terminal window' call s:Echoerr('Failed to open the gdb terminal window')
call s:CloseBuffers() call s:CloseBuffers()
return return
endif endif
let s:running = v:true let s:gdb_running = v:true
let s:starting = v:true let s:starting = v:true
let gdb_job_info = nvim_get_chan_info(s:gdb_job_id) let gdb_job_info = nvim_get_chan_info(s:gdb_job_id)
let s:gdbbuf = gdb_job_info['buffer'] let s:gdbbuf = gdb_job_info['buffer']
let s:gdbwin = win_getid(winnr()) let s:gdbwin = win_getid()
" Wait for the "startupdone" message before sending any commands. " Wait for the "startupdone" message before sending any commands.
let try_count = 0 let try_count = 0
@@ -369,7 +390,7 @@ func s:StartDebug_term(dict)
" response can be in the same line or the next line " response can be in the same line or the next line
let response = line1 . line2 let response = line1 . line2
if response =~ 'Undefined command' if response =~ 'Undefined command'
echoerr 'Sorry, your gdb is too old, gdb 7.12 is required' call s:Echoerr('Sorry, your gdb is too old, gdb 7.12 is required')
" CHECKME: possibly send a "server show version" here " CHECKME: possibly send a "server show version" here
call s:CloseBuffers() call s:CloseBuffers()
return return
@@ -388,7 +409,7 @@ func s:StartDebug_term(dict)
endif endif
let try_count += 1 let try_count += 1
if try_count > 100 if try_count > 100
echoerr 'Cannot check if your gdb works, continuing anyway' call s:Echoerr('Cannot check if your gdb works, continuing anyway')
break break
endif endif
sleep 10m sleep 10m
@@ -409,7 +430,7 @@ func s:StartDebug_prompt(dict)
else else
new new
endif endif
let s:gdbwin = win_getid(winnr()) let s:gdbwin = win_getid()
let s:promptbuf = bufnr('') let s:promptbuf = bufnr('')
call prompt_setprompt(s:promptbuf, 'gdb> ') call prompt_setprompt(s:promptbuf, 'gdb> ')
set buftype=prompt set buftype=prompt
@@ -447,14 +468,15 @@ func s:StartDebug_prompt(dict)
\ 'on_stdout': function('s:JobOutCallback', {'last_line': '', 'real_cb': function('s:GdbOutCallback')}), \ 'on_stdout': function('s:JobOutCallback', {'last_line': '', 'real_cb': function('s:GdbOutCallback')}),
\ }) \ })
if s:gdbjob == 0 if s:gdbjob == 0
echoerr 'invalid argument (or job table is full) while starting gdb job' call s:Echoerr('Invalid argument (or job table is full) while starting gdb job')
exe 'bwipe! ' . s:ptybuf exe 'bwipe! ' . s:ptybuf
return return
elseif s:gdbjob == -1 elseif s:gdbjob == -1
echoerr 'Failed to start the gdb job' call s:Echoerr('Failed to start the gdb job')
call s:CloseBuffers() call s:CloseBuffers()
return return
endif endif
exe $'au BufUnload <buffer={s:promptbuf}> ++once call jobstop(s:gdbjob)'
let s:ptybuf = 0 let s:ptybuf = 0
if has('win32') if has('win32')
@@ -463,20 +485,19 @@ func s:StartDebug_prompt(dict)
else else
" Unix: Run the debugged program in a terminal window. Open it below the " Unix: Run the debugged program in a terminal window. Open it below the
" gdb window. " gdb window.
execute 'new' belowright new
wincmd x | wincmd j let s:pty_job_id = termopen('tail -f /dev/null;#gdb program')
belowright let s:pty_job_id = termopen('tail -f /dev/null;#gdb program')
if s:pty_job_id == 0 if s:pty_job_id == 0
echoerr 'invalid argument (or job table is full) while opening terminal window' call s:Echoerr('Invalid argument (or job table is full) while opening terminal window')
return return
elseif s:pty_job_id == -1 elseif s:pty_job_id == -1
echoerr 'Failed to open the program terminal window' call s:Echoerr('Failed to open the program terminal window')
return return
endif endif
let pty_job_info = nvim_get_chan_info(s:pty_job_id) let pty_job_info = nvim_get_chan_info(s:pty_job_id)
let s:ptybuf = pty_job_info['buffer'] let s:ptybuf = pty_job_info['buffer']
let pty = pty_job_info['pty'] let pty = pty_job_info['pty']
let s:ptywin = win_getid(winnr()) let s:ptywin = win_getid()
call s:SendCommand('tty ' . pty) call s:SendCommand('tty ' . pty)
" Since GDB runs in a prompt window, the environment has not been set to " Since GDB runs in a prompt window, the environment has not been set to
@@ -599,12 +620,12 @@ func s:PromptInterrupt()
" Using job_stop() does not work on MS-Windows, need to send SIGTRAP to " Using job_stop() does not work on MS-Windows, need to send SIGTRAP to
" the debugger program so that gdb responds again. " the debugger program so that gdb responds again.
if s:pid == 0 if s:pid == 0
echoerr 'Cannot interrupt gdb, did not find a process ID' call s:Echoerr('Cannot interrupt gdb, did not find a process ID')
else else
call debugbreak(s:pid) call debugbreak(s:pid)
endif endif
else else
call jobstop(s:gdbjob) call v:lua.vim.uv.kill(jobpid(s:gdbjob), 'sigint')
endif endif
endfunc endfunc
@@ -629,33 +650,39 @@ endfunc
func s:GdbOutCallback(job_id, msgs, event) func s:GdbOutCallback(job_id, msgs, event)
"call ch_log('received from gdb: ' . a:text) "call ch_log('received from gdb: ' . a:text)
" Drop the gdb prompt, we have our own. let comm_msgs = []
" Drop status and echo'd commands.
call filter(a:msgs, { index, val ->
\ val !=# '(gdb)' && val !=# '^done' && val[0] !=# '&'})
let lines = [] let lines = []
let index = 0
for msg in a:msgs for msg in a:msgs
" Disassembly messages need to be forwarded as-is.
if s:parsing_disasm_msg || msg =~ '^&"disassemble'
call s:CommOutput(a:job_id, [msg], a:event)
continue
endif
" Drop the gdb prompt, we have our own.
" Drop status and echo'd commands.
if msg == '(gdb) ' || msg == '^done' || msg[0] == '&'
continue
endif
if msg =~ '^\^error,msg=' if msg =~ '^\^error,msg='
if exists('s:evalexpr') if exists('s:evalexpr')
\ && s:DecodeMessage(msg[11:], v:false) \ && s:DecodeMessage(msg[11:], v:false)
\ =~ 'A syntax error in expression, near\|No symbol .* in current context' \ =~ 'A syntax error in expression, near\|No symbol .* in current context'
" Silently drop evaluation errors. " Silently drop evaluation errors.
call remove(a:msgs, index)
unlet s:evalexpr unlet s:evalexpr
continue continue
endif endif
elseif msg[0] == '~' elseif msg[0] == '~'
call add(lines, s:DecodeMessage(msg[1:], v:false)) call add(lines, s:DecodeMessage(msg[1:], v:false))
call remove(a:msgs, index)
continue continue
endif endif
let index += 1
call add(comm_msgs, msg)
endfor endfor
let curwinid = win_getid(winnr()) let curwinid = win_getid()
call win_gotoid(s:gdbwin) call win_gotoid(s:gdbwin)
" Add the output above the current prompt. " Add the output above the current prompt.
@@ -667,32 +694,34 @@ func s:GdbOutCallback(job_id, msgs, event)
endif endif
call win_gotoid(curwinid) call win_gotoid(curwinid)
call s:CommOutput(a:job_id, a:msgs, a:event) call s:CommOutput(a:job_id, comm_msgs, a:event)
endfunc endfunc
" Decode a message from gdb. "quotedText" starts with a ", return the text up " Decode a message from gdb. "quotedText" starts with a ", return the text up
" to the next ", unescaping characters: " to the next unescaped ", unescaping characters:
" - remove line breaks (unless "literal" is v:true) " - remove line breaks (unless "literal" is v:true)
" - change \" to "
" - change \\t to \t (unless "literal" is v:true) " - change \\t to \t (unless "literal" is v:true)
" - change \0xhh to \xhh (disabled for now) " - change \0xhh to \xhh (disabled for now)
" - change \ooo to octal " - change \ooo to octal
" - change \\ to \ " - change \\ to \
func s:DecodeMessage(quotedText, literal) func s:DecodeMessage(quotedText, literal)
if a:quotedText[0] != '"' if a:quotedText[0] != '"'
echoerr 'DecodeMessage(): missing quote in ' . a:quotedText call s:Echoerr('DecodeMessage(): missing quote in ' . a:quotedText)
return return
endif endif
let msg = a:quotedText let msg = a:quotedText
\ ->substitute('^"\|".*', '', 'g') \ ->substitute('^"\|[^\\]\zs".*', '', 'g')
" multi-byte characters arrive in octal form \ ->substitute('\\"', '"', 'g')
" NULL-values must be kept encoded as those break the string otherwise "\ multi-byte characters arrive in octal form
"\ NULL-values must be kept encoded as those break the string otherwise
\ ->substitute('\\000', s:NullRepl, 'g') \ ->substitute('\\000', s:NullRepl, 'g')
\ ->substitute('\\\o\o\o', {-> eval('"' .. submatch(0) .. '"')}, 'g') \ ->substitute('\\\o\o\o', {-> eval('"' .. submatch(0) .. '"')}, 'g')
" Note: GDB docs also mention hex encodings - the translations below work "\ Note: GDB docs also mention hex encodings - the translations below work
" but we keep them out for performance-reasons until we actually see "\ but we keep them out for performance-reasons until we actually see
" those in mi-returns "\ those in mi-returns
" \ ->substitute('\\0x\(\x\x\)', {-> eval('"\x' .. submatch(1) .. '"')}, 'g') "\ \ ->substitute('\\0x\(\x\x\)', {-> eval('"\x' .. submatch(1) .. '"')}, 'g')
" \ ->substitute('\\0x00', s:NullRepl, 'g') "\ \ ->substitute('\\0x00', s:NullRepl, 'g')
\ ->substitute('\\\\', '\', 'g') \ ->substitute('\\\\', '\', 'g')
\ ->substitute(s:NullRepl, '\\000', 'g') \ ->substitute(s:NullRepl, '\\000', 'g')
if !a:literal if !a:literal
@@ -728,7 +757,7 @@ func s:GetAsmAddr(msg)
endfunc endfunc
func s:EndTermDebug(job_id, exit_code, event) func s:EndTermDebug(job_id, exit_code, event)
let s:running = v:false let s:gdb_running = v:false
if s:starting if s:starting
return return
endif endif
@@ -738,16 +767,22 @@ func s:EndTermDebug(job_id, exit_code, event)
endif endif
unlet s:gdbwin unlet s:gdbwin
call s:EndDebugCommon() call s:EndDebugCommon()
endfunc endfunc
func s:EndDebugCommon() func s:EndDebugCommon()
let curwinid = win_getid(winnr()) let curwinid = win_getid()
if exists('s:ptybuf') && s:ptybuf if exists('s:ptybuf') && s:ptybuf
exe 'bwipe! ' . s:ptybuf exe 'bwipe! ' . s:ptybuf
endif endif
if s:asmbuf > 0 && bufexists(s:asmbuf)
exe 'bwipe! ' . s:asmbuf
endif
if s:varbuf > 0 && bufexists(s:varbuf)
exe 'bwipe! ' . s:varbuf
endif
let s:running = 0
" Restore 'signcolumn' in all buffers for which it was set. " Restore 'signcolumn' in all buffers for which it was set.
call win_gotoid(s:sourcewin) call win_gotoid(s:sourcewin)
@@ -785,11 +820,8 @@ func s:EndPromptDebug(job_id, exit_code, event)
doauto <nomodeline> User TermdebugStopPre doauto <nomodeline> User TermdebugStopPre
endif endif
let curwinid = win_getid(winnr()) if bufexists(s:promptbuf)
call win_gotoid(s:gdbwin) exe 'bwipe! ' . s:promptbuf
close
if curwinid != s:gdbwin
call win_gotoid(curwinid)
endif endif
call s:EndDebugCommon() call s:EndDebugCommon()
@@ -797,7 +829,6 @@ func s:EndPromptDebug(job_id, exit_code, event)
"call ch_log("Returning from EndPromptDebug()") "call ch_log("Returning from EndPromptDebug()")
endfunc endfunc
" - CommOutput: disassemble $pc
" - CommOutput: &"disassemble $pc\n" " - CommOutput: &"disassemble $pc\n"
" - CommOutput: ~"Dump of assembler code for function main(int, char**):\n" " - CommOutput: ~"Dump of assembler code for function main(int, char**):\n"
" - CommOutput: ~" 0x0000555556466f69 <+0>:\tpush rbp\n" " - CommOutput: ~" 0x0000555556466f69 <+0>:\tpush rbp\n"
@@ -807,15 +838,14 @@ endfunc
" - CommOutput: ~"End of assembler dump.\n" " - CommOutput: ~"End of assembler dump.\n"
" - CommOutput: ^done " - CommOutput: ^done
" - CommOutput: disassemble $pc
" - CommOutput: &"disassemble $pc\n" " - CommOutput: &"disassemble $pc\n"
" - CommOutput: &"No function contains specified address.\n" " - CommOutput: &"No function contains specified address.\n"
" - CommOutput: ^error,msg="No function contains specified address." " - CommOutput: ^error,msg="No function contains specified address."
func s:HandleDisasmMsg(msg) func s:HandleDisasmMsg(msg)
if a:msg =~ '^\^done' if a:msg =~ '^\^done'
let curwinid = win_getid(winnr()) let curwinid = win_getid()
if win_gotoid(s:asmwin) if win_gotoid(s:asmwin)
silent normal! gg0"_dG silent! %delete _
call setline(1, s:asm_lines) call setline(1, s:asm_lines)
set nomodified set nomodified
set filetype=asm set filetype=asm
@@ -839,15 +869,17 @@ func s:HandleDisasmMsg(msg)
call s:SendCommand('disassemble $pc,+100') call s:SendCommand('disassemble $pc,+100')
endif endif
let s:parsing_disasm_msg = 0 let s:parsing_disasm_msg = 0
elseif a:msg =~ '\&\"disassemble \$pc' elseif a:msg =~ '^&"disassemble \$pc'
if a:msg =~ '+100' if a:msg =~ '+100'
" This is our second disasm attempt " This is our second disasm attempt
let s:parsing_disasm_msg = 2 let s:parsing_disasm_msg = 2
endif endif
else elseif a:msg !~ '^&"disassemble'
let value = substitute(a:msg, '^\~\"[ ]*', '', '') let value = substitute(a:msg, '^\~\"[ ]*', '', '')
let value = substitute(value, '^=>[ ]*', '', '') let value = substitute(value, '^=>[ ]*', '', '')
let value = substitute(value, '\\n\"\r$', '', '') " Nvim already trims the final "\r" in s:CommOutput()
" let value = substitute(value, '\\n\"\r$', '', '')
let value = substitute(value, '\\n\"$', '', '')
let value = substitute(value, '\r', '', '') let value = substitute(value, '\r', '', '')
let value = substitute(value, '\\t', ' ', 'g') let value = substitute(value, '\\t', ' ', 'g')
@@ -857,12 +889,54 @@ func s:HandleDisasmMsg(msg)
endif endif
endfunc endfunc
func s:CommOutput(job_id, msgs, event) func s:ParseVarinfo(varinfo)
let dict = {}
let nameIdx = matchstrpos(a:varinfo, '{name="\([^"]*\)"')
let dict['name'] = a:varinfo[nameIdx[1] + 7 : nameIdx[2] - 2]
let typeIdx = matchstrpos(a:varinfo, ',type="\([^"]*\)"')
let dict['type'] = a:varinfo[typeIdx[1] + 7 : typeIdx[2] - 2]
let valueIdx = matchstrpos(a:varinfo, ',value="\(.*\)"}')
if valueIdx[1] == -1
let dict['value'] = 'Complex value'
else
let dict['value'] = a:varinfo[valueIdx[1] + 8 : valueIdx[2] - 3]
endif
return dict
endfunc
func s:HandleVariablesMsg(msg)
let curwinid = win_getid()
if win_gotoid(s:varwin)
silent! %delete _
let spaceBuffer = 20
call setline(1, 'Type' .
\ repeat(' ', 16) .
\ 'Name' .
\ repeat(' ', 16) .
\ 'Value')
let cnt = 1
let capture = '{name=".\{-}",\%(arg=".\{-}",\)\{0,1\}type=".\{-}"\%(,value=".\{-}"\)\{0,1\}}'
let varinfo = matchstr(a:msg, capture, 0, cnt)
while varinfo != ''
let vardict = s:ParseVarinfo(varinfo)
call setline(cnt + 1, vardict['type'] .
\ repeat(' ', max([20 - len(vardict['type']), 1])) .
\ vardict['name'] .
\ repeat(' ', max([20 - len(vardict['name']), 1])) .
\ vardict['value'])
let cnt += 1
let varinfo = matchstr(a:msg, capture, 0, cnt)
endwhile
endif
call win_gotoid(curwinid)
endfunc
func s:CommOutput(job_id, msgs, event)
for msg in a:msgs for msg in a:msgs
" remove prefixed NL " Nvim job lines are split on "\n", so trim a suffixed CR.
if msg[0] == "\n" if msg[-1:] == "\r"
let msg = msg[1:] let msg = msg[:-2]
endif endif
if s:parsing_disasm_msg if s:parsing_disasm_msg
@@ -882,9 +956,12 @@ func s:CommOutput(job_id, msgs, event)
call s:HandleEvaluate(msg) call s:HandleEvaluate(msg)
elseif msg =~ '^\^error,msg=' elseif msg =~ '^\^error,msg='
call s:HandleError(msg) call s:HandleError(msg)
elseif msg =~ '^disassemble' elseif msg =~ '^&"disassemble'
let s:parsing_disasm_msg = 1 let s:parsing_disasm_msg = 1
let s:asm_lines = [] let s:asm_lines = []
call s:HandleDisasmMsg(msg)
elseif msg =~ '^\^done,variables='
call s:HandleVariablesMsg(msg)
endif endif
endif endif
endfor endfor
@@ -925,11 +1002,16 @@ func s:InstallCommands()
command Continue call chansend(s:gdb_job_id, "continue\r") command Continue call chansend(s:gdb_job_id, "continue\r")
endif endif
command -nargs=* Frame call s:Frame(<q-args>)
command -count=1 Up call s:Up(<count>)
command -count=1 Down call s:Down(<count>)
command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>) command -range -nargs=* Evaluate call s:Evaluate(<range>, <q-args>)
command Gdb call win_gotoid(s:gdbwin) command Gdb call win_gotoid(s:gdbwin)
command Program call s:GotoProgram() command Program call s:GotoProgram()
command Source call s:GotoSourcewinOrCreateIt() command Source call s:GotoSourcewinOrCreateIt()
command Asm call s:GotoAsmwinOrCreateIt() command Asm call s:GotoAsmwinOrCreateIt()
command Var call s:GotoVariableswinOrCreateIt()
command Winbar call s:InstallWinbar(1) command Winbar call s:InstallWinbar(1)
let map = 1 let map = 1
@@ -939,17 +1021,29 @@ func s:InstallCommands()
let map = g:termdebug_map_K let map = g:termdebug_map_K
endif endif
if map if map
" let s:k_map_saved = maparg('K', 'n', 0, 1) let s:k_map_saved = maparg('K', 'n', 0, 1)
let s:k_map_saved = {}
for map in nvim_get_keymap('n')
if map.lhs ==# 'K'
let s:k_map_saved = map
break
endif
endfor
nnoremap K :Evaluate<CR> nnoremap K :Evaluate<CR>
endif endif
let map = 1
if exists('g:termdebug_config')
let map = get(g:termdebug_config, 'map_plus', 1)
endif
if map
let s:plus_map_saved = maparg('+', 'n', 0, 1)
nnoremap <expr> + $'<Cmd>{v:count1}Up<CR>'
endif
let map = 1
if exists('g:termdebug_config')
let map = get(g:termdebug_config, 'map_minus', 1)
endif
if map
let s:minus_map_saved = maparg('-', 'n', 0, 1)
nnoremap <expr> - $'<Cmd>{v:count1}Down<CR>'
endif
if has('menu') && &mouse != '' if has('menu') && &mouse != ''
call s:InstallWinbar(0) call s:InstallWinbar(0)
@@ -984,7 +1078,7 @@ func s:InstallWinbar(force)
" nnoremenu WinBar.Cont :Continue<CR> " nnoremenu WinBar.Cont :Continue<CR>
" nnoremenu WinBar.Stop :Stop<CR> " nnoremenu WinBar.Stop :Stop<CR>
" nnoremenu WinBar.Eval :Evaluate<CR> " nnoremenu WinBar.Eval :Evaluate<CR>
" call add(s:winbar_winids, win_getid(winnr())) " call add(s:winbar_winids, win_getid())
" endif " endif
endfunc endfunc
@@ -1000,11 +1094,15 @@ func s:DeleteCommands()
delcommand Arguments delcommand Arguments
delcommand Stop delcommand Stop
delcommand Continue delcommand Continue
delcommand Frame
delcommand Up
delcommand Down
delcommand Evaluate delcommand Evaluate
delcommand Gdb delcommand Gdb
delcommand Program delcommand Program
delcommand Source delcommand Source
delcommand Asm delcommand Asm
delcommand Var
delcommand Winbar delcommand Winbar
if exists('s:k_map_saved') if exists('s:k_map_saved')
@@ -1016,10 +1114,28 @@ func s:DeleteCommands()
endif endif
unlet s:k_map_saved unlet s:k_map_saved
endif endif
if exists('s:plus_map_saved')
if empty(s:plus_map_saved)
nunmap +
else
" call mapset(s:plus_map_saved)
call mapset('n', 0, s:plus_map_saved)
endif
unlet s:plus_map_saved
endif
if exists('s:minus_map_saved')
if empty(s:minus_map_saved)
nunmap -
else
" call mapset(s:minus_map_saved)
call mapset('n', 0, s:minus_map_saved)
endif
unlet s:minus_map_saved
endif
if has('menu') if has('menu')
" Remove the WinBar entries from all windows where it was added. " Remove the WinBar entries from all windows where it was added.
" let curwinid = win_getid(winnr()) " let curwinid = win_getid()
" for winid in s:winbar_winids " for winid in s:winbar_winids
" if win_gotoid(winid) " if win_gotoid(winid)
" aunmenu WinBar.Step " aunmenu WinBar.Step
@@ -1118,7 +1234,7 @@ func s:ClearBreakpoint()
endif endif
echomsg 'Breakpoint ' . id . ' cleared from line ' . lnum . '.' echomsg 'Breakpoint ' . id . ' cleared from line ' . lnum . '.'
else else
echoerr 'Internal error trying to remove breakpoint at line ' . lnum . '!' call s:Echoerr('Internal error trying to remove breakpoint at line ' . lnum . '!')
endif endif
else else
echomsg 'No breakpoint to remove at line ' . lnum . '.' echomsg 'No breakpoint to remove at line ' . lnum . '.'
@@ -1132,6 +1248,37 @@ func s:Run(args)
call s:SendResumingCommand('-exec-run') call s:SendResumingCommand('-exec-run')
endfunc endfunc
" :Frame - go to a specfic frame in the stack
func s:Frame(arg)
" Note: we explicit do not use mi's command
" call s:SendCommand('-stack-select-frame "' . a:arg .'"')
" as we only get a "done" mi response and would have to open the file
" 'manually' - using cli command "frame" provides us with the mi response
" already parsed and allows for more formats
if a:arg =~ '^\d\+$' || a:arg == ''
" specify frame by number
call s:SendCommand('-interpreter-exec mi "frame ' . a:arg .'"')
elseif a:arg =~ '^0x[0-9a-fA-F]\+$'
" specify frame by stack address
call s:SendCommand('-interpreter-exec mi "frame address ' . a:arg .'"')
else
" specify frame by function name
call s:SendCommand('-interpreter-exec mi "frame function ' . a:arg .'"')
endif
endfunc
" :Up - go a:count frames in the stack "higher"
func s:Up(count)
" the 'correct' one would be -stack-select-frame N, but we don't know N
call s:SendCommand($'-interpreter-exec console "up {a:count}"')
endfunc
" :Down - go a:count frames in the stack "below"
func s:Down(count)
" the 'correct' one would be -stack-select-frame N, but we don't know N
call s:SendCommand($'-interpreter-exec console "down {a:count}"')
endfunc
func s:SendEval(expr) func s:SendEval(expr)
" check for "likely" boolean expressions, in which case we take it as lhs " check for "likely" boolean expressions, in which case we take it as lhs
if a:expr =~ "[=!<>]=" if a:expr =~ "[=!<>]="
@@ -1376,13 +1523,13 @@ func s:HandleError(msg)
let s:evalFromBalloonExpr = 0 let s:evalFromBalloonExpr = 0
return return
endif endif
let msgVal = substitute(a:msg, '.*msg="\(.*\)"', '\1', '') let msgVal = substitute(a:msg, '.*msg="\(.*\)"', '\1', '')
call s:Echoerr(substitute(msgVal, '\\"', '"', 'g')) call s:Echoerr(substitute(msgVal, '\\"', '"', 'g'))
endfunc endfunc
func s:GotoSourcewinOrCreateIt() func s:GotoSourcewinOrCreateIt()
if !win_gotoid(s:sourcewin) if !win_gotoid(s:sourcewin)
new new
let s:sourcewin = win_getid() let s:sourcewin = win_getid()
call s:InstallWinbar(0) call s:InstallWinbar(0)
endif endif
@@ -1415,19 +1562,21 @@ func s:GotoAsmwinOrCreateIt()
else else
exe 'new' exe 'new'
endif endif
let s:asmwin = win_getid() let s:asmwin = win_getid()
setlocal nowrap setlocal nowrap
setlocal number setlocal number
setlocal noswapfile setlocal noswapfile
setlocal buftype=nofile
setlocal bufhidden=wipe
setlocal signcolumn=no setlocal signcolumn=no
setlocal modifiable setlocal modifiable
let asmbuf = bufnr('Termdebug-asm-listing') if s:asmbuf > 0 && bufexists(s:asmbuf)
if asmbuf > 0
exe 'buffer' . s:asmbuf exe 'buffer' . s:asmbuf
else else
silent file Termdebug-asm-listing
let s:asmbuf = bufnr('Termdebug-asm-listing') let s:asmbuf = bufnr('Termdebug-asm-listing')
endif endif
@@ -1448,17 +1597,75 @@ func s:GotoAsmwinOrCreateIt()
endif endif
endif endif
endfunc endfunc
func s:GetVariablesWindow()
if exists('g:termdebug_config')
return get(g:termdebug_config, 'variables_window', 0)
endif
if exists('g:termdebug_disasm_window')
return g:termdebug_variables_window
endif
return 0
endfunc
func s:GetVariablesWindowHeight()
if exists('g:termdebug_config')
return get(g:termdebug_config, 'variables_window_height', 0)
endif
if exists('g:termdebug_variables_window') && g:termdebug_variables_window > 1
return g:termdebug_variables_window
endif
return 0
endfunc
func s:GotoVariableswinOrCreateIt()
if !win_gotoid(s:varwin)
if win_gotoid(s:sourcewin)
exe 'rightbelow new'
else
exe 'new'
endif
let s:varwin = win_getid()
setlocal nowrap
setlocal noswapfile
setlocal buftype=nofile
setlocal bufhidden=wipe
setlocal signcolumn=no
setlocal modifiable
if s:varbuf > 0 && bufexists(s:varbuf)
exe 'buffer' . s:varbuf
else
silent file Termdebug-variables-listing
let s:varbuf = bufnr('Termdebug-variables-listing')
endif
if s:GetVariablesWindowHeight() > 0
exe 'resize ' .. s:GetVariablesWindowHeight()
endif
endif
if s:running
call s:SendCommand('-stack-list-variables 2')
endif
endfunc
" Handle stopping and running message from gdb. " Handle stopping and running message from gdb.
" Will update the sign that shows the current position. " Will update the sign that shows the current position.
func s:HandleCursor(msg) func s:HandleCursor(msg)
let wid = win_getid() let wid = win_getid()
if a:msg =~ '^\*stopped' if a:msg =~ '^\*stopped'
"call ch_log('program stopped') "call ch_log('program stopped')
let s:stopped = 1
if a:msg =~ '^\*stopped,reason="exited-normally"'
let s:running = 0
endif endif
elseif a:msg =~ '^\*running' elseif a:msg =~ '^\*running'
"call ch_log('program running') "call ch_log('program running')
let s:stopped = 0
let s:running = 1 let s:running = 1
endif endif
@@ -1472,7 +1679,7 @@ func s:HandleCursor(msg)
let asm_addr = s:GetAsmAddr(a:msg) let asm_addr = s:GetAsmAddr(a:msg)
if asm_addr != '' if asm_addr != ''
let s:asm_addr = asm_addr let s:asm_addr = asm_addr
let curwinid = win_getid() let curwinid = win_getid()
if win_gotoid(s:asmwin) if win_gotoid(s:asmwin)
let lnum = search('^' . s:asm_addr) let lnum = search('^' . s:asm_addr)
@@ -1487,6 +1694,10 @@ func s:HandleCursor(msg)
endif endif
endif endif
endif endif
if s:running && s:stopped && bufwinnr('Termdebug-variables-listing') != -1
call s:SendCommand('-stack-list-variables 2')
endif
if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname) if a:msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname)
let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '') let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '')
@@ -1505,7 +1716,7 @@ func s:HandleCursor(msg)
augroup END augroup END
if &modified if &modified
" TODO: find existing window " TODO: find existing window
exe 'split ' . fnameescape(fname) exe 'split ' . fnameescape(fname)
let s:sourcewin = win_getid() let s:sourcewin = win_getid()
call s:InstallWinbar(0) call s:InstallWinbar(0)
else else