aboutsummaryrefslogtreecommitdiff
path: root/runtime/pack/dist/opt/termdebug
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-11-29 21:52:58 +0000
committerJosh Rahm <joshuarahm@gmail.com>2023-11-29 21:52:58 +0000
commit931bffbda3668ddc609fc1da8f9eb576b170aa52 (patch)
treed8c1843a95da5ea0bb4acc09f7e37843d9995c86 /runtime/pack/dist/opt/termdebug
parent142d9041391780ac15b89886a54015fdc5c73995 (diff)
parent4a8bf24ac690004aedf5540fa440e788459e5e34 (diff)
downloadrneovim-userreg.tar.gz
rneovim-userreg.tar.bz2
rneovim-userreg.zip
Merge remote-tracking branch 'upstream/master' into userreguserreg
Diffstat (limited to 'runtime/pack/dist/opt/termdebug')
-rw-r--r--runtime/pack/dist/opt/termdebug/plugin/termdebug.vim742
1 files changed, 510 insertions, 232 deletions
diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
index 99fd7dba42..1b5baa9a8b 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: 2022 Nov 10
+" Last Change: 2023 Nov 02
"
" 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 = []
@@ -87,6 +88,8 @@ func s:Breakpoint2SignNumber(id, subid)
return s:break_id + a:id * 1000 + a:subid
endfunction
+" Define or adjust the default highlighting, using background "new".
+" When the 'background' option is set then "old" has the old value.
func s:Highlight(init, old, new)
let default = a:init ? 'default ' : ''
if a:new ==# 'light' && a:old !=# 'light'
@@ -96,9 +99,21 @@ func s:Highlight(init, old, new)
endif
endfunc
-call s:Highlight(1, '', &background)
-hi default debugBreakpoint term=reverse ctermbg=red guibg=red
-hi default debugBreakpointDisabled term=reverse ctermbg=gray guibg=gray
+" Define the default highlighting, using the current 'background' value.
+func s:InitHighlight()
+ call s:Highlight(1, '', &background)
+ hi default debugBreakpoint term=reverse ctermbg=red guibg=red
+ hi default debugBreakpointDisabled term=reverse ctermbg=gray guibg=gray
+endfunc
+
+" Setup an autocommand to redefine the default highlight when the colorscheme
+" is changed.
+func s:InitAutocmd()
+ augroup TermDebug
+ autocmd!
+ autocmd ColorScheme * call s:InitHighlight()
+ augroup END
+endfunc
" Get the command to execute the debugger as a list, defaults to ["gdb"].
func s:GetCommand()
@@ -113,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})
@@ -125,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
@@ -145,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.
@@ -182,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)
@@ -193,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
@@ -206,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
@@ -223,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.
@@ -245,16 +280,16 @@ func s:StartDebug_term(dict)
" Create a hidden terminal window to communicate with gdb
let s:comm_job_id = jobstart('tail -f /dev/null;#gdb communication', {
- \ 'on_stdout': function('s:CommOutput'),
+ \ 'on_stdout': function('s:JobOutCallback', {'last_line': '', 'real_cb': function('s:CommOutput')}),
\ 'pty': v:true,
\ })
" 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
@@ -295,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
@@ -355,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
@@ -374,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
@@ -395,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
@@ -429,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: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')
@@ -449,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
@@ -585,46 +620,69 @@ 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
+" Wrapper around job callback that handles partial lines (:h channel-lines).
+" It should be called from a Dictionary with the following keys:
+" - last_line: the last (partial) line received
+" - real_cb: a callback that assumes full lines
+func s:JobOutCallback(jobid, data, event) dict
+ let eof = (a:data == [''])
+ let msgs = a:data
+ let msgs[0] = self.last_line .. msgs[0]
+ if eof
+ let self.last_line = ''
+ else
+ let self.last_line = msgs[-1]
+ unlet msgs[-1]
+ endif
+ call self.real_cb(a:jobid, msgs, a:event)
+endfunc
+
" Function called when gdb outputs text.
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:])
+ \ && 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:]))
- call remove(a:msgs, index)
+ call add(lines, s:DecodeMessage(msg[1:], v:false))
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.
@@ -636,35 +694,43 @@ 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:
-" - remove line breaks
-" - change \\t to \t
+" Decode a message from gdb. "quotedText" starts with a ", return the text up
+" 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)
+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
- return a:quotedText
- \ ->substitute('^"\|".*\|\\n', '', 'g')
- \ ->substitute('\\t', "\t", 'g')
- " multi-byte characters arrive in octal form
- " NULL-values must be kept encoded as those break the string otherwise
+ let msg = a:quotedText
+ \ ->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
+ \ ->substitute('\\t', "\t", 'g')
+ \ ->substitute('\\n', '', 'g')
+ else
+ return msg
+ endif
endfunc
const s:NullRepl = 'XXXNULLXXX'
@@ -673,7 +739,7 @@ func s:GetFullname(msg)
if a:msg !~ 'fullname'
return ''
endif
- let name = s:DecodeMessage(substitute(a:msg, '.*fullname=', '', ''))
+ let name = s:DecodeMessage(substitute(a:msg, '.*fullname=', '', ''), v:true)
if has('win32') && name =~ ':\\\\'
" sometimes the name arrives double-escaped
let name = substitute(name, '\\\\', '\\', 'g')
@@ -686,12 +752,12 @@ func s:GetAsmAddr(msg)
if a:msg !~ 'addr='
return ''
endif
- let addr = s:DecodeMessage(substitute(a:msg, '.*addr=', '', ''))
+ let addr = s:DecodeMessage(substitute(a:msg, '.*addr=', '', ''), v:false)
return addr
endfunc
-function s:EndTermDebug(job_id, exit_code, event)
- let s:running = v:false
+func s:EndTermDebug(job_id, exit_code, event)
+ let s:gdb_running = v:false
if s:starting
return
endif
@@ -701,16 +767,22 @@ function 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)
@@ -748,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()
@@ -760,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"
@@ -770,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
@@ -802,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')
@@ -820,12 +889,56 @@ 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="\([^"]*\)"')
+ " 'type' maybe is a url-like string,
+ " try to shorten it and show only the /tail
+ let dict['type'] = (a:varinfo[typeIdx[1] + 7 : typeIdx[2] - 2])->fnamemodify(':t')
+ 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
@@ -845,9 +958,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
@@ -888,12 +1004,17 @@ 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 Winbar call s:InstallWinbar()
+ command Var call s:GotoVariableswinOrCreateIt()
+ command Winbar call s:InstallWinbar(1)
let map = 1
if exists('g:termdebug_config')
@@ -902,26 +1023,37 @@ 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
- nnoremap K :Evaluate<CR>
+ let s:k_map_saved = maparg('K', 'n', 0, 1)
+ if !empty(s:k_map_saved) && !s:k_map_saved.buffer || empty(s:k_map_saved)
+ nnoremap K :Evaluate<CR>
+ endif
endif
- if has('menu') && &mouse != ''
- " install the window toolbar by default, can be disabled in the config
- let winbar = 1
- if exists('g:termdebug_config')
- let winbar = get(g:termdebug_config, 'winbar', 1)
+ 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)
+ if !empty(s:plus_map_saved) && !s:plus_map_saved.buffer || empty(s:plus_map_saved)
+ nnoremap <expr> + $'<Cmd>{v:count1}Up<CR>'
endif
- if winbar
- call s:InstallWinbar()
+ 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)
+ if !empty(s:minus_map_saved) && !s:minus_map_saved.buffer || empty(s:minus_map_saved)
+ nnoremap <expr> - $'<Cmd>{v:count1}Down<CR>'
endif
+ endif
+
+
+ if has('menu') && &mouse != ''
+ call s:InstallWinbar(0)
let popup = 1
if exists('g:termdebug_config')
@@ -932,11 +1064,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
@@ -946,7 +1078,7 @@ endfunc
" let s:winbar_winids = []
" Install the window toolbar in the current window.
-func s:InstallWinbar()
+func s:InstallWinbar(force)
" if has('menu') && &mouse != ''
" nnoremenu WinBar.Step :Step<CR>
" nnoremenu WinBar.Next :Over<CR>
@@ -954,7 +1086,7 @@ func s:InstallWinbar()
" 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
@@ -970,26 +1102,48 @@ 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')
- if empty(s:k_map_saved)
+ if !empty(s:k_map_saved) && !s:k_map_saved.buffer
+ nunmap K
+ call mapset(s:k_map_saved)
+ elseif empty(s:k_map_saved)
nunmap K
- else
- " call mapset(s:k_map_saved)
- call mapset('n', 0, s:k_map_saved)
endif
unlet s:k_map_saved
endif
+ if exists('s:plus_map_saved')
+ if !empty(s:plus_map_saved) && !s:plus_map_saved.buffer
+ nunmap +
+ call mapset(s:plus_map_saved)
+ elseif empty(s:plus_map_saved)
+ nunmap +
+ endif
+ unlet s:plus_map_saved
+ endif
+ if exists('s:minus_map_saved')
+ if !empty(s:minus_map_saved) && !s:minus_map_saved.buffer
+ nunmap -
+ call mapset(s:minus_map_saved)
+ elseif empty(s:minus_map_saved)
+ nunmap -
+ 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
@@ -1051,7 +1205,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
@@ -1088,7 +1242,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 . '.'
@@ -1102,6 +1256,37 @@ func s:Run(args)
call s:SendResumingCommand('-exec-run')
endfunc
+" :Frame - go to a specific 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 =~ "[=!<>]="
@@ -1240,101 +1425,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_win_set_option(float_win_id, 'filetype', a:filetype)
- 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_buf_set_option(buf, 'modified', v:false)
- call nvim_buf_set_option(buf, 'modifiable', v:false)
-
- " 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.
@@ -1346,14 +1531,14 @@ 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())
- call s:InstallWinbar()
+ let s:sourcewin = win_getid()
+ call s:InstallWinbar(0)
endif
endfunc
@@ -1379,28 +1564,37 @@ endfunc
func s:GotoAsmwinOrCreateIt()
if !win_gotoid(s:asmwin)
+ let mdf = ''
if win_gotoid(s:sourcewin)
- exe 'rightbelow new'
+ " 60 is approx spaceBuffer * 3
+ if winwidth(0) > (78 + 60)
+ let mdf = 'vert'
+ exe mdf .. ' ' .. 60 .. 'new'
+ else
+ exe 'rightbelow new'
+ endif
else
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
+ if mdf != 'vert' && s:GetDisasmWindowHeight() > 0
exe 'resize ' .. s:GetDisasmWindowHeight()
endif
endif
@@ -1418,17 +1612,82 @@ 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)
+ let mdf = ''
+ if win_gotoid(s:sourcewin)
+ " 60 is approx spaceBuffer * 3
+ if winwidth(0) > (78 + 60)
+ let mdf = 'vert'
+ exe mdf .. ' ' .. 60 .. 'new'
+ else
+ exe 'rightbelow new'
+ endif
+ 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 mdf != 'vert' && 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='
@@ -1442,21 +1701,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]*$'
@@ -1468,15 +1731,15 @@ 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())
- call s:InstallWinbar()
+ let s:sourcewin = win_getid()
+ call s:InstallWinbar(0)
else
exe 'edit ' . fnameescape(fname)
endif
@@ -1513,8 +1776,18 @@ func s:CreateBreakpoint(id, subid, enabled)
else
let hiName = "debugBreakpoint"
endif
+ let label = ''
+ if exists('g:termdebug_config')
+ let label = get(g:termdebug_config, 'sign', '')
+ endif
+ if label == ''
+ let label = printf('%02X', a:id)
+ if a:id > 255
+ let label = 'F+'
+ endif
+ endif
call sign_define('debugBreakpoint' .. nr,
- \ #{text: substitute(nr, '\..*', '', ''),
+ \ #{text: strpart(label, 0, 2),
\ texthl: hiName})
endif
endfunc
@@ -1654,5 +1927,10 @@ func s:BufUnloaded()
endfor
endfunc
+call s:InitHighlight()
+call s:InitAutocmd()
+
let &cpo = s:keepcpo
unlet s:keepcpo
+
+" vim: sw=2 sts=2 et