aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSean Dewar <seandewar@users.noreply.github.com>2023-08-25 11:59:51 +0100
committerGitHub <noreply@github.com>2023-08-25 11:59:51 +0100
commitd4dc52a835d1d1de86e18744fd6344dd48b726ff (patch)
tree8eef4baec4606906b2d6b35ce7305236fa13f600
parent0b0d912763890652c20d219ad92ab8d91195ca02 (diff)
parent0bd82b540eb492c2c41f8b18be9ac749008ee340 (diff)
downloadrneovim-d4dc52a835d1d1de86e18744fd6344dd48b726ff.tar.gz
rneovim-d4dc52a835d1d1de86e18744fd6344dd48b726ff.tar.bz2
rneovim-d4dc52a835d1d1de86e18744fd6344dd48b726ff.zip
Merge pull request #24821 from seandewar/vim-9f2962141514
vim-patch:9f2962141514,f6fb52b667ee,19968fc4ec2c,a76f3221cdcf,2ae7ffe0bc3c,3d3a9152fa6d - Termdebug stuff
-rw-r--r--runtime/doc/nvim_terminal_emulator.txt35
-rw-r--r--runtime/pack/dist/opt/termdebug/plugin/termdebug.vim611
2 files changed, 444 insertions, 202 deletions
diff --git a/runtime/doc/nvim_terminal_emulator.txt b/runtime/doc/nvim_terminal_emulator.txt
index bbcc686207..d0d535566d 100644
--- a/runtime/doc/nvim_terminal_emulator.txt
+++ b/runtime/doc/nvim_terminal_emulator.txt
@@ -317,6 +317,18 @@ This is similar to using "print" in the gdb window.
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 ~
*termdebug-commands*
*:Gdb* jump to the gdb window
@@ -325,7 +337,9 @@ Other commands ~
isn't one
*:Asm* jump to the window with the disassembly, create it if there
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 ~
*termdebug-events*
@@ -386,11 +400,19 @@ If there is no g:termdebug_config you can use: >vim
let g:termdebug_use_prompt = 1
<
*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
If there is no g:termdebug_config you can use: >vim
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*
If you want the Asm window shown by default, set the "disasm_window" flag to
1. The "disasm_window_height" entry can be used to set the window height: >vim
@@ -400,6 +422,15 @@ If there is no g:termdebug_config you can use: >vim
let g:termdebug_disasm_window = 15
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 ~
*termdebug-communication*
There is another, hidden, buffer, which is used for Vim to communicate with
diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
index e8b78b3f5f..c436e015cc 100644
--- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
+++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
@@ -2,7 +2,7 @@
"
" Author: Bram Moolenaar
" 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
" 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:break_id = 14 " breakpoint number is added to this
let s:stopped = 1
+let s:running = 0
let s:parsing_disasm_msg = 0
let s:asm_lines = []
@@ -127,6 +128,10 @@ func s:GetCommand()
return type(cmd) == v:t_list ? copy(cmd) : [cmd]
endfunc
+func s:Echoerr(msg)
+ echohl ErrorMsg | echom '[termdebug] ' .. a:msg | echohl None
+endfunc
+
func s:StartDebug(bang, ...)
" First argument is the command to debug, second core file or process ID.
call s:StartDebug_internal({'gdb_args': a:000, 'bang': a:bang})
@@ -139,18 +144,21 @@ endfunc
func s:StartDebug_internal(dict)
if exists('s:gdbwin')
- echoerr 'Terminal debugger already running, cannot run two'
+ call s:Echoerr('Terminal debugger already running, cannot run two')
return
endif
let gdbcmd = s:GetCommand()
if !executable(gdbcmd[0])
- echoerr 'Cannot execute debugger program "' .. gdbcmd[0] .. '"'
+ call s:Echoerr('Cannot execute debugger program "' .. gdbcmd[0] .. '"')
return
endif
let s:ptywin = 0
let s:pid = 0
let s:asmwin = 0
+ let s:asmbuf = 0
+ let s:varwin = 0
+ let s:varbuf = 0
if exists('#User#TermdebugStartPre')
doauto <nomodeline> User TermdebugStartPre
@@ -159,7 +167,7 @@ func s:StartDebug_internal(dict)
" Uncomment this line to write logging in "debuglog".
" 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
" that we can restore the value for all buffers.
@@ -196,9 +204,9 @@ func s:StartDebug_internal(dict)
endif
if !has('win32') && !use_prompt
let s:way = 'terminal'
- else
+ else
let s:way = 'prompt'
- endif
+ endif
if s:way == 'prompt'
call s:StartDebug_prompt(a:dict)
@@ -207,11 +215,17 @@ func s:StartDebug_internal(dict)
endif
if s:GetDisasmWindow()
- let curwinid = win_getid(winnr())
+ let curwinid = win_getid()
call s:GotoAsmwinOrCreateIt()
call win_gotoid(curwinid)
endif
+ if s:GetVariablesWindow()
+ let curwinid = win_getid()
+ call s:GotoVariableswinOrCreateIt()
+ call win_gotoid(curwinid)
+ endif
+
if exists('#User#TermdebugStartPost')
doauto <nomodeline> User TermdebugStartPost
endif
@@ -220,14 +234,21 @@ endfunc
" Use when debugger didn't start or ended.
func s:CloseBuffers()
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
endfunc
func s:CheckGdbRunning()
- if !s:running
- echoerr string(s:GetCommand()[0]) . ' exited unexpectedly'
- call s:CloseBuffers()
- return ''
+ if !s:gdb_running
+ call s:Echoerr(string(s:GetCommand()[0]) . ' exited unexpectedly')
+ call s:CloseBuffers()
+ return ''
endif
return 'ok'
endfunc
@@ -237,16 +258,16 @@ func s:StartDebug_term(dict)
execute s:vertical ? 'vnew' : 'new'
let s:pty_job_id = termopen('tail -f /dev/null;#gdb program')
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
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
endif
let pty_job_info = nvim_get_chan_info(s:pty_job_id)
let s:ptybuf = pty_job_info['buffer']
let pty = pty_job_info['pty']
- let s:ptywin = win_getid(winnr())
+ let s:ptywin = win_getid()
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.
@@ -264,11 +285,11 @@ func s:StartDebug_term(dict)
\ })
" hide terminal buffer
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
return
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
return
endif
@@ -309,19 +330,19 @@ func s:StartDebug_term(dict)
" call ch_log('executing "' . join(gdb_cmd) . '"')
let s:gdb_job_id = termopen(gdb_cmd, {'on_exit': function('s:EndTermDebug')})
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
return
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()
return
endif
- let s:running = v:true
+ let s:gdb_running = v:true
let s:starting = v:true
let gdb_job_info = nvim_get_chan_info(s:gdb_job_id)
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.
let try_count = 0
@@ -369,7 +390,7 @@ func s:StartDebug_term(dict)
" response can be in the same line or the next line
let response = line1 . line2
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
call s:CloseBuffers()
return
@@ -388,7 +409,7 @@ func s:StartDebug_term(dict)
endif
let try_count += 1
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
endif
sleep 10m
@@ -409,7 +430,7 @@ func s:StartDebug_prompt(dict)
else
new
endif
- let s:gdbwin = win_getid(winnr())
+ let s:gdbwin = win_getid()
let s:promptbuf = bufnr('')
call prompt_setprompt(s:promptbuf, 'gdb> ')
set buftype=prompt
@@ -443,18 +464,19 @@ func s:StartDebug_prompt(dict)
" call ch_log('executing "' . join(gdb_cmd) . '"')
let s:gdbjob = jobstart(gdb_cmd, {
- \ 'on_exit': function('s:EndPromptDebug'),
- \ 'on_stdout': function('s:JobOutCallback', {'last_line': '', 'real_cb': function('s:GdbOutCallback')}),
- \ })
+ \ 'on_exit': function('s:EndPromptDebug'),
+ \ 'on_stdout': function('s:JobOutCallback', {'last_line': '', 'real_cb': function('s:GdbOutCallback')}),
+ \ })
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
return
elseif s:gdbjob == -1
- echoerr 'Failed to start the gdb job'
+ call s:Echoerr('Failed to start the gdb job')
call s:CloseBuffers()
return
endif
+ exe $'au BufUnload <buffer={s:promptbuf}> ++once call jobstop(s:gdbjob)'
let s:ptybuf = 0
if has('win32')
@@ -463,20 +485,19 @@ func s:StartDebug_prompt(dict)
else
" Unix: Run the debugged program in a terminal window. Open it below the
" gdb window.
- execute 'new'
- wincmd x | wincmd j
- belowright let s:pty_job_id = termopen('tail -f /dev/null;#gdb program')
+ belowright new
+ let s:pty_job_id = termopen('tail -f /dev/null;#gdb program')
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
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
endif
let pty_job_info = nvim_get_chan_info(s:pty_job_id)
let s:ptybuf = pty_job_info['buffer']
let pty = pty_job_info['pty']
- let s:ptywin = win_getid(winnr())
+ let s:ptywin = win_getid()
call s:SendCommand('tty ' . pty)
" 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
" the debugger program so that gdb responds again.
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
call debugbreak(s:pid)
endif
else
- call jobstop(s:gdbjob)
+ call v:lua.vim.uv.kill(jobpid(s:gdbjob), 'sigint')
endif
endfunc
@@ -629,33 +650,39 @@ endfunc
func s:GdbOutCallback(job_id, msgs, event)
"call ch_log('received from gdb: ' . a:text)
- " Drop the gdb prompt, we have our own.
- " Drop status and echo'd commands.
- call filter(a:msgs, { index, val ->
- \ val !=# '(gdb)' && val !=# '^done' && val[0] !=# '&'})
-
+ let comm_msgs = []
let lines = []
- let index = 0
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 exists('s:evalexpr')
\ && s:DecodeMessage(msg[11:], v:false)
\ =~ 'A syntax error in expression, near\|No symbol .* in current context'
" Silently drop evaluation errors.
- call remove(a:msgs, index)
unlet s:evalexpr
continue
endif
elseif msg[0] == '~'
call add(lines, s:DecodeMessage(msg[1:], v:false))
- call remove(a:msgs, index)
continue
endif
- let index += 1
+
+ call add(comm_msgs, msg)
endfor
- let curwinid = win_getid(winnr())
+ let curwinid = win_getid()
call win_gotoid(s:gdbwin)
" Add the output above the current prompt.
@@ -667,40 +694,42 @@ func s:GdbOutCallback(job_id, msgs, event)
endif
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
" 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)
+" - change \" to "
" - change \\t to \t (unless "literal" is v:true)
" - change \0xhh to \xhh (disabled for now)
" - change \ooo to octal
" - change \\ to \
func s:DecodeMessage(quotedText, literal)
if a:quotedText[0] != '"'
- echoerr 'DecodeMessage(): missing quote in ' . a:quotedText
+ call s:Echoerr('DecodeMessage(): missing quote in ' . a:quotedText)
return
endif
let msg = a:quotedText
- \ ->substitute('^"\|".*', '', 'g')
- " multi-byte characters arrive in octal form
- " NULL-values must be kept encoded as those break the string otherwise
+ \ ->substitute('^"\|[^\\]\zs".*', '', 'g')
+ \ ->substitute('\\"', '"', 'g')
+ "\ 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('\\\o\o\o', {-> eval('"' .. submatch(0) .. '"')}, 'g')
- " Note: GDB docs also mention hex encodings - the translations below work
- " but we keep them out for performance-reasons until we actually see
- " those in mi-returns
- " \ ->substitute('\\0x\(\x\x\)', {-> eval('"\x' .. submatch(1) .. '"')}, 'g')
- " \ ->substitute('\\0x00', s:NullRepl, 'g')
+ "\ Note: GDB docs also mention hex encodings - the translations below work
+ "\ but we keep them out for performance-reasons until we actually see
+ "\ those in mi-returns
+ "\ \ ->substitute('\\0x\(\x\x\)', {-> eval('"\x' .. submatch(1) .. '"')}, 'g')
+ "\ \ ->substitute('\\0x00', s:NullRepl, 'g')
\ ->substitute('\\\\', '\', 'g')
\ ->substitute(s:NullRepl, '\\000', 'g')
if !a:literal
- return msg
+ return msg
\ ->substitute('\\t', "\t", 'g')
\ ->substitute('\\n', '', 'g')
else
- return msg
+ return msg
endif
endfunc
const s:NullRepl = 'XXXNULLXXX'
@@ -728,7 +757,7 @@ func s:GetAsmAddr(msg)
endfunc
func s:EndTermDebug(job_id, exit_code, event)
- let s:running = v:false
+ let s:gdb_running = v:false
if s:starting
return
endif
@@ -738,16 +767,22 @@ func s:EndTermDebug(job_id, exit_code, event)
endif
unlet s:gdbwin
-
call s:EndDebugCommon()
endfunc
func s:EndDebugCommon()
- let curwinid = win_getid(winnr())
+ let curwinid = win_getid()
if exists('s:ptybuf') && s:ptybuf
exe 'bwipe! ' . s:ptybuf
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.
call win_gotoid(s:sourcewin)
@@ -785,11 +820,8 @@ func s:EndPromptDebug(job_id, exit_code, event)
doauto <nomodeline> User TermdebugStopPre
endif
- let curwinid = win_getid(winnr())
- call win_gotoid(s:gdbwin)
- close
- if curwinid != s:gdbwin
- call win_gotoid(curwinid)
+ if bufexists(s:promptbuf)
+ exe 'bwipe! ' . s:promptbuf
endif
call s:EndDebugCommon()
@@ -797,7 +829,6 @@ func s:EndPromptDebug(job_id, exit_code, event)
"call ch_log("Returning from EndPromptDebug()")
endfunc
-" - CommOutput: disassemble $pc
" - CommOutput: &"disassemble $pc\n"
" - CommOutput: ~"Dump of assembler code for function main(int, char**):\n"
" - CommOutput: ~" 0x0000555556466f69 <+0>:\tpush rbp\n"
@@ -807,15 +838,14 @@ endfunc
" - CommOutput: ~"End of assembler dump.\n"
" - CommOutput: ^done
-" - CommOutput: disassemble $pc
" - CommOutput: &"disassemble $pc\n"
" - CommOutput: &"No function contains specified address.\n"
" - CommOutput: ^error,msg="No function contains specified address."
func s:HandleDisasmMsg(msg)
if a:msg =~ '^\^done'
- let curwinid = win_getid(winnr())
+ let curwinid = win_getid()
if win_gotoid(s:asmwin)
- silent normal! gg0"_dG
+ silent! %delete _
call setline(1, s:asm_lines)
set nomodified
set filetype=asm
@@ -839,15 +869,17 @@ func s:HandleDisasmMsg(msg)
call s:SendCommand('disassemble $pc,+100')
endif
let s:parsing_disasm_msg = 0
- elseif a:msg =~ '\&\"disassemble \$pc'
+ elseif a:msg =~ '^&"disassemble \$pc'
if a:msg =~ '+100'
" This is our second disasm attempt
let s:parsing_disasm_msg = 2
endif
- else
+ elseif a:msg !~ '^&"disassemble'
let value = substitute(a:msg, '^\~\"[ ]*', '', '')
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, '\\t', ' ', 'g')
@@ -857,12 +889,54 @@ func s:HandleDisasmMsg(msg)
endif
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
- " remove prefixed NL
- if msg[0] == "\n"
- let msg = msg[1:]
+ " Nvim job lines are split on "\n", so trim a suffixed CR.
+ if msg[-1:] == "\r"
+ let msg = msg[:-2]
endif
if s:parsing_disasm_msg
@@ -882,9 +956,12 @@ func s:CommOutput(job_id, msgs, event)
call s:HandleEvaluate(msg)
elseif msg =~ '^\^error,msg='
call s:HandleError(msg)
- elseif msg =~ '^disassemble'
+ elseif msg =~ '^&"disassemble'
let s:parsing_disasm_msg = 1
let s:asm_lines = []
+ call s:HandleDisasmMsg(msg)
+ elseif msg =~ '^\^done,variables='
+ call s:HandleVariablesMsg(msg)
endif
endif
endfor
@@ -925,11 +1002,16 @@ func s:InstallCommands()
command Continue call chansend(s:gdb_job_id, "continue\r")
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 Gdb call win_gotoid(s:gdbwin)
command Program call s:GotoProgram()
command Source call s:GotoSourcewinOrCreateIt()
command Asm call s:GotoAsmwinOrCreateIt()
+ command Var call s:GotoVariableswinOrCreateIt()
command Winbar call s:InstallWinbar(1)
let map = 1
@@ -939,17 +1021,29 @@ func s:InstallCommands()
let map = g:termdebug_map_K
endif
if map
- " 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
+ let s:k_map_saved = maparg('K', 'n', 0, 1)
nnoremap K :Evaluate<CR>
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 != ''
call s:InstallWinbar(0)
@@ -962,11 +1056,11 @@ func s:InstallCommands()
if popup
let s:saved_mousemodel = &mousemodel
let &mousemodel = 'popup_setpos'
- an 1.200 PopUp.-SEP3- <Nop>
- an 1.210 PopUp.Set\ breakpoint :Break<CR>
- an 1.220 PopUp.Clear\ breakpoint :Clear<CR>
- an 1.230 PopUp.Run\ until :Until<CR>
- an 1.240 PopUp.Evaluate :Evaluate<CR>
+ an 1.200 PopUp.-SEP3- <Nop>
+ an 1.210 PopUp.Set\ breakpoint :Break<CR>
+ an 1.220 PopUp.Clear\ breakpoint :Clear<CR>
+ an 1.230 PopUp.Run\ until :Until<CR>
+ an 1.240 PopUp.Evaluate :Evaluate<CR>
endif
endif
@@ -984,7 +1078,7 @@ func s:InstallWinbar(force)
" nnoremenu WinBar.Cont :Continue<CR>
" nnoremenu WinBar.Stop :Stop<CR>
" nnoremenu WinBar.Eval :Evaluate<CR>
- " call add(s:winbar_winids, win_getid(winnr()))
+ " call add(s:winbar_winids, win_getid())
" endif
endfunc
@@ -1000,11 +1094,15 @@ func s:DeleteCommands()
delcommand Arguments
delcommand Stop
delcommand Continue
+ delcommand Frame
+ delcommand Up
+ delcommand Down
delcommand Evaluate
delcommand Gdb
delcommand Program
delcommand Source
delcommand Asm
+ delcommand Var
delcommand Winbar
if exists('s:k_map_saved')
@@ -1016,10 +1114,28 @@ func s:DeleteCommands()
endif
unlet s:k_map_saved
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')
" 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
" if win_gotoid(winid)
" aunmenu WinBar.Step
@@ -1081,7 +1197,7 @@ func s:SetBreakpoint(at)
" Use the fname:lnum format, older gdb can't handle --source.
let at = empty(a:at) ?
- \ fnameescape(expand('%:p')) . ':' . line('.') : a:at
+ \ fnameescape(expand('%:p')) . ':' . line('.') : a:at
call s:SendCommand('-break-insert ' . at)
if do_continue
Continue
@@ -1118,7 +1234,7 @@ func s:ClearBreakpoint()
endif
echomsg 'Breakpoint ' . id . ' cleared from line ' . lnum . '.'
else
- echoerr 'Internal error trying to remove breakpoint at line ' . lnum . '!'
+ call s:Echoerr('Internal error trying to remove breakpoint at line ' . lnum . '!')
endif
else
echomsg 'No breakpoint to remove at line ' . lnum . '.'
@@ -1132,6 +1248,37 @@ func s:Run(args)
call s:SendResumingCommand('-exec-run')
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)
" check for "likely" boolean expressions, in which case we take it as lhs
if a:expr =~ "[=!<>]="
@@ -1270,101 +1417,101 @@ function! s:CloseFloatingHoverOnCursorMove(win_id, opened) abort
endfunction
function! s:CloseFloatingHoverOnBufEnter(win_id, bufnr) abort
- let winnr = win_id2win(a:win_id)
- if winnr == 0
- " Float window was already closed
- autocmd! nvim_termdebug_close_hover
- return
- endif
- if winnr == winnr()
- " Cursor is moving into floating window. Do not close it
- return
- endif
- if bufnr('%') == a:bufnr
- " When current buffer opened hover window, it's not another buffer. Skipped
- return
- endif
+ let winnr = win_id2win(a:win_id)
+ if winnr == 0
+ " Float window was already closed
autocmd! nvim_termdebug_close_hover
- call nvim_win_close(a:win_id, v:true)
- endfunction
+ return
+ endif
+ if winnr == winnr()
+ " Cursor is moving into floating window. Do not close it
+ return
+ endif
+ if bufnr('%') == a:bufnr
+ " When current buffer opened hover window, it's not another buffer. Skipped
+ return
+ endif
+ autocmd! nvim_termdebug_close_hover
+ call nvim_win_close(a:win_id, v:true)
+endfunction
" Open preview window. Window is open in:
" - Floating window on Neovim (0.4.0 or later)
" - Preview window on Neovim (0.3.0 or earlier) or Vim
function! s:OpenHoverPreview(lines, filetype) abort
- " Use local variable since parameter is not modifiable
- let lines = a:lines
- let bufnr = bufnr('%')
-
- let use_float_win = s:ShouldUseFloatWindow()
- if use_float_win
- let pos = getpos('.')
-
- " Calculate width and height
- let width = 0
- for index in range(len(lines))
- let line = lines[index]
- let lw = strdisplaywidth(line)
- if lw > width
- let width = lw
- endif
- let lines[index] = line
- endfor
-
- let height = len(lines)
-
- " Calculate anchor
- " Prefer North, but if there is no space, fallback into South
- let bottom_line = line('w0') + winheight(0) - 1
- if pos[1] + height <= bottom_line
- let vert = 'N'
- let row = 1
- else
- let vert = 'S'
- let row = 0
+ " Use local variable since parameter is not modifiable
+ let lines = a:lines
+ let bufnr = bufnr('%')
+
+ let use_float_win = s:ShouldUseFloatWindow()
+ if use_float_win
+ let pos = getpos('.')
+
+ " Calculate width and height
+ let width = 0
+ for index in range(len(lines))
+ let line = lines[index]
+ let lw = strdisplaywidth(line)
+ if lw > width
+ let width = lw
endif
+ let lines[index] = line
+ endfor
- " Prefer West, but if there is no space, fallback into East
- if pos[2] + width <= &columns
- let hor = 'W'
- let col = 0
- else
- let hor = 'E'
- let col = 1
- endif
+ let height = len(lines)
- let buf = nvim_create_buf(v:false, v:true)
- call nvim_buf_set_lines(buf, 0, -1, v:true, lines)
- " using v:true for second argument of nvim_open_win make the floating
- " window disappear
- let float_win_id = nvim_open_win(buf, v:false, {
- \ 'relative': 'cursor',
- \ 'anchor': vert . hor,
- \ 'row': row,
- \ 'col': col,
- \ 'width': width,
- \ 'height': height,
- \ 'style': 'minimal',
- \ })
-
- if a:filetype isnot v:null
- call nvim_set_option_value('filetype', a:filetype, { 'win' : float_win_id })
- endif
+ " Calculate anchor
+ " Prefer North, but if there is no space, fallback into South
+ let bottom_line = line('w0') + winheight(0) - 1
+ if pos[1] + height <= bottom_line
+ let vert = 'N'
+ let row = 1
+ else
+ let vert = 'S'
+ let row = 0
+ endif
- call nvim_set_option_value('modified', v:false, { 'buf' : buf })
- call nvim_set_option_value('modifiable', v:false, { 'buf' : buf })
-
- " Unlike preview window, :pclose does not close window. Instead, close
- " hover window automatically when cursor is moved.
- let call_after_move = printf('<SID>CloseFloatingHoverOnCursorMove(%d, %s)', float_win_id, string(pos))
- let call_on_bufenter = printf('<SID>CloseFloatingHoverOnBufEnter(%d, %d)', float_win_id, bufnr)
- augroup nvim_termdebug_close_hover
- execute 'autocmd CursorMoved,CursorMovedI,InsertEnter <buffer> call ' . call_after_move
- execute 'autocmd BufEnter * call ' . call_on_bufenter
- augroup END
+ " Prefer West, but if there is no space, fallback into East
+ if pos[2] + width <= &columns
+ let hor = 'W'
+ let col = 0
else
- echomsg a:lines[0]
+ let hor = 'E'
+ let col = 1
+ endif
+
+ let buf = nvim_create_buf(v:false, v:true)
+ call nvim_buf_set_lines(buf, 0, -1, v:true, lines)
+ " using v:true for second argument of nvim_open_win make the floating
+ " window disappear
+ let float_win_id = nvim_open_win(buf, v:false, {
+ \ 'relative': 'cursor',
+ \ 'anchor': vert . hor,
+ \ 'row': row,
+ \ 'col': col,
+ \ 'width': width,
+ \ 'height': height,
+ \ 'style': 'minimal',
+ \ })
+
+ if a:filetype isnot v:null
+ call nvim_set_option_value('filetype', a:filetype, { 'win' : float_win_id })
endif
+
+ call nvim_set_option_value('modified', v:false, { 'buf' : buf })
+ call nvim_set_option_value('modifiable', v:false, { 'buf' : buf })
+
+ " Unlike preview window, :pclose does not close window. Instead, close
+ " hover window automatically when cursor is moved.
+ let call_after_move = printf('<SID>CloseFloatingHoverOnCursorMove(%d, %s)', float_win_id, string(pos))
+ let call_on_bufenter = printf('<SID>CloseFloatingHoverOnBufEnter(%d, %d)', float_win_id, bufnr)
+ augroup nvim_termdebug_close_hover
+ execute 'autocmd CursorMoved,CursorMovedI,InsertEnter <buffer> call ' . call_after_move
+ execute 'autocmd BufEnter * call ' . call_on_bufenter
+ augroup END
+ else
+ echomsg a:lines[0]
+ endif
endfunction
" Handle an error.
@@ -1376,13 +1523,13 @@ func s:HandleError(msg)
return
endif
let msgVal = substitute(a:msg, '.*msg="\(.*\)"', '\1', '')
- echoerr substitute(msgVal, '\\"', '"', 'g')
+ call s:Echoerr(substitute(msgVal, '\\"', '"', 'g'))
endfunc
func s:GotoSourcewinOrCreateIt()
if !win_gotoid(s:sourcewin)
new
- let s:sourcewin = win_getid(winnr())
+ let s:sourcewin = win_getid()
call s:InstallWinbar(0)
endif
endfunc
@@ -1415,19 +1562,21 @@ func s:GotoAsmwinOrCreateIt()
exe 'new'
endif
- let s:asmwin = win_getid(winnr())
+ let s:asmwin = win_getid()
setlocal nowrap
setlocal number
setlocal noswapfile
setlocal buftype=nofile
+ setlocal bufhidden=wipe
+ setlocal signcolumn=no
setlocal modifiable
- let asmbuf = bufnr('Termdebug-asm-listing')
- if asmbuf > 0
- exe 'buffer' . asmbuf
+ if s:asmbuf > 0 && bufexists(s:asmbuf)
+ exe 'buffer' . s:asmbuf
else
- exe 'file Termdebug-asm-listing'
+ silent file Termdebug-asm-listing
+ let s:asmbuf = bufnr('Termdebug-asm-listing')
endif
if s:GetDisasmWindowHeight() > 0
@@ -1448,17 +1597,75 @@ func s:GotoAsmwinOrCreateIt()
endif
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.
" Will update the sign that shows the current position.
func s:HandleCursor(msg)
- let wid = win_getid(winnr())
+ let wid = win_getid()
if a:msg =~ '^\*stopped'
"call ch_log('program stopped')
let s:stopped = 1
+ if a:msg =~ '^\*stopped,reason="exited-normally"'
+ let s:running = 0
+ endif
elseif a:msg =~ '^\*running'
"call ch_log('program running')
let s:stopped = 0
+ let s:running = 1
endif
if a:msg =~ 'fullname='
@@ -1472,21 +1679,25 @@ func s:HandleCursor(msg)
if asm_addr != ''
let s:asm_addr = asm_addr
- let curwinid = win_getid(winnr())
+ let curwinid = win_getid()
if win_gotoid(s:asmwin)
- let lnum = search('^' . s:asm_addr)
- if lnum == 0
- call s:SendCommand('disassemble $pc')
- else
- call sign_unplace('TermDebug', #{id: s:asm_id})
- call sign_place(s:asm_id, 'TermDebug', 'debugPC', '%', #{lnum: lnum})
- endif
+ let lnum = search('^' . s:asm_addr)
+ if lnum == 0
+ call s:SendCommand('disassemble $pc')
+ else
+ call sign_unplace('TermDebug', #{id: s:asm_id})
+ call sign_place(s:asm_id, 'TermDebug', 'debugPC', '%', #{lnum: lnum})
+ endif
- call win_gotoid(curwinid)
+ call win_gotoid(curwinid)
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)
let lnum = substitute(a:msg, '.*line="\([^"]*\)".*', '\1', '')
if lnum =~ '^[0-9]*$'
@@ -1498,14 +1709,14 @@ func s:HandleCursor(msg)
" prompt, since it is unlikely we want to edit the file.
" The file may be changed but not saved, warn for that.
au SwapExists * echohl WarningMsg
- \ | echo 'Warning: file is being edited elsewhere'
- \ | echohl None
- \ | let v:swapchoice = 'o'
+ \ | echo 'Warning: file is being edited elsewhere'
+ \ | echohl None
+ \ | let v:swapchoice = 'o'
augroup END
if &modified
" TODO: find existing window
exe 'split ' . fnameescape(fname)
- let s:sourcewin = win_getid(winnr())
+ let s:sourcewin = win_getid()
call s:InstallWinbar(0)
else
exe 'edit ' . fnameescape(fname)