0
0
mirror of https://github.com/vim/vim.git synced 2025-09-27 04:14:06 -04:00

patch 9.1.0508: termdebug plugin can be further improved

Problem:  termdebug plugin can be further improved
Solution: add sanity-check, timeout config, change vars to bool
          update docs, add more tests (Ubaldo Tiberi)

fixes: #15061
closes: #15057

Signed-off-by: Ubaldo Tiberi <ubaldo.tiberi@google.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Ubaldo Tiberi
2024-06-20 22:17:34 +02:00
committed by Christian Brabandt
parent 6ccf6da7a2
commit f7f8f0b76d
4 changed files with 129 additions and 99 deletions

View File

@@ -1,4 +1,4 @@
*terminal.txt* For Vim version 9.1. Last change: 2024 Jun 18 *terminal.txt* For Vim version 9.1. Last change: 2024 Jun 20
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@@ -1283,6 +1283,15 @@ When the debugger ends, typically by typing "quit" in the gdb window, the two
opened windows are closed. opened windows are closed.
Only one debugger can be active at a time. Only one debugger can be active at a time.
*termdebug-timeout*
Depending on how gdb is launched, termdebug startup time may vary.
To avoid termdebug to get stuck if the startup process of gdb takes too long,
a configurable timeout is included. Such time out is configurable in terms of
multiple of 10ms: >
let g:termdebug_config['timeout'] = 500 # 500 * 10ms = 5 seconds.
The default timeout is 3000 ms.
*:TermdebugCommand* *:TermdebugCommand*
If you want to give specific commands to the command being debugged, you can If you want to give specific commands to the command being debugged, you can
use the `:TermdebugCommand` command followed by the command name and use the `:TermdebugCommand` command followed by the command name and

View File

@@ -4,7 +4,7 @@ vim9script
# Author: Bram Moolenaar # Author: Bram Moolenaar
# Copyright: Vim license applies, see ":help license" # Copyright: Vim license applies, see ":help license"
# Last Change: 2024 Jun 16 # Last Change: 2024 Jun 20
# Converted to Vim9: Ubaldo Tiberi <ubaldo.tiberi@gmail.com> # Converted to Vim9: Ubaldo Tiberi <ubaldo.tiberi@gmail.com>
# WORK IN PROGRESS - The basics works stable, more to come # WORK IN PROGRESS - The basics works stable, more to come
@@ -50,6 +50,13 @@ if exists('g:termdebug_loaded')
finish finish
endif endif
g:termdebug_loaded = true g:termdebug_loaded = true
g:termdebug_is_running = false
# The command that starts debugging, e.g. ":Termdebug vim".
# To end type "quit" in the gdb window.
command -nargs=* -complete=file -bang Termdebug StartDebug(<bang>0, <f-args>)
command -nargs=+ -complete=file -bang TermdebugCommand StartDebugCommand(<bang>0, <f-args>)
# Script variables declaration. These variables are re-initialized at every # Script variables declaration. These variables are re-initialized at every
# Termdebug instance # Termdebug instance
@@ -198,18 +205,40 @@ def InitScriptVariables()
winbar_winids = [] winbar_winids = []
saved_K_map = maparg('K', 'n', 0, 1) saved_K_map = maparg('K', 'n', false, true)
saved_plus_map = maparg('+', 'n', 0, 1) saved_plus_map = maparg('+', 'n', false, true)
saved_minus_map = maparg('-', 'n', 0, 1) saved_minus_map = maparg('-', 'n', false, true)
if has('menu') if has('menu')
saved_mousemodel = &mousemodel saved_mousemodel = &mousemodel
endif endif
enddef enddef
# The command that starts debugging, e.g. ":Termdebug vim".
# To end type "quit" in the gdb window. def SanityCheck(): bool
command -nargs=* -complete=file -bang Termdebug StartDebug(<bang>0, <f-args>) var gdb_cmd = GetCommand()[0]
command -nargs=+ -complete=file -bang TermdebugCommand StartDebugCommand(<bang>0, <f-args>) var is_check_ok = true
# Need either the +terminal feature or +channel and the prompt buffer.
# The terminal feature does not work with gdb on win32.
if (way ==# 'prompt') && !has('channel')
err = 'Cannot debug, +channel feature is not supported'
elseif way ==# 'prompt' && !exists('*prompt_setprompt')
err = 'Cannot debug, missing prompt buffer support'
elseif way ==# 'prompt' && !empty(glob(gdb_cmd))
err = $"You have a file/folder named '{gdb_cmd}' in the current directory Termdebug may not work properly. Please exit and rename such a file/folder."
elseif !empty(glob(asmbufname))
err = $"You have a file/folder named '{asmbufname}' in the current directory Termdebug may not work properly. Please exit and rename such a file/folder."
elseif !empty(glob(varbufname))
err = $"You have a file/folder named '{varbufname}' in the current directory Termdebug may not work properly. Please exit and rename such a file/folder."
elseif !executable(gdb_cmd)
err = $"Cannot execute debugger program '{gdb_cmd}'"
endif
if !empty(err)
Echoerr(err)
is_check_ok = false
endif
return is_check_ok
enddef
# Take a breakpoint number as used by GDB and turn it into an integer. # Take a breakpoint number as used by GDB and turn it into an integer.
@@ -248,18 +277,19 @@ enddef
# Get the command to execute the debugger as a list, defaults to ["gdb"]. # Get the command to execute the debugger as a list, defaults to ["gdb"].
def GetCommand(): list<string> def GetCommand(): list<string>
var cmd = 'gdb' var cmd: any
if exists('g:termdebug_config') if exists('g:termdebug_config')
cmd = get(g:termdebug_config, 'command', 'gdb') cmd = get(g:termdebug_config, 'command', 'gdb')
elseif exists('g:termdebugger') elseif exists('g:termdebugger')
cmd = g:termdebugger cmd = g:termdebugger
else
cmd = 'gdb'
endif endif
return type(cmd) == v:t_list ? copy(cmd) : [cmd] return type(cmd) == v:t_list ? copy(cmd) : [cmd]
enddef enddef
def StartDebug(bang: bool, ...gdb_args: list<string>) def StartDebug(bang: bool, ...gdb_args: list<string>)
InitScriptVariables()
# 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.
StartDebug_internal({gdb_args: gdb_args, bang: bang}) StartDebug_internal({gdb_args: gdb_args, bang: bang})
enddef enddef
@@ -269,15 +299,14 @@ def StartDebugCommand(bang: bool, ...args: list<string>)
StartDebug_internal({gdb_args: [args[0]], proc_args: args[1 : ], bang: bang}) StartDebug_internal({gdb_args: [args[0]], proc_args: args[1 : ], bang: bang})
enddef enddef
def StartDebug_internal(dict: dict<any>) def StartDebug_internal(dict: dict<any>)
if gdbwin > 0 if g:termdebug_is_running
Echoerr('Terminal debugger already running, cannot run two') Echoerr('Terminal debugger already running, cannot run two')
return return
endif endif
var gdbcmd = GetCommand()
if !executable(gdbcmd[0]) InitScriptVariables()
Echoerr($'Cannot execute debugger program "{gdbcmd[0]}"') if !SanityCheck()
return return
endif endif
@@ -285,6 +314,9 @@ def StartDebug_internal(dict: dict<any>)
doauto <nomodeline> User TermdebugStartPre doauto <nomodeline> User TermdebugStartPre
endif endif
# Uncomment this line to write logging in "debuglog".
# call ch_logfile('debuglog', 'w')
# Assume current window is the source code window # Assume current window is the source code window
sourcewin = win_getid() sourcewin = win_getid()
var wide = 0 var wide = 0
@@ -327,6 +359,7 @@ def StartDebug_internal(dict: dict<any>)
if exists('#User#TermdebugStartPost') if exists('#User#TermdebugStartPost')
doauto <nomodeline> User TermdebugStartPost doauto <nomodeline> User TermdebugStartPost
endif endif
g:termdebug_is_running = true
enddef enddef
# Use when debugger didn't start or ended. # Use when debugger didn't start or ended.
@@ -338,7 +371,7 @@ def CloseBuffers()
endif endif
endfor endfor
running = 0 running = false
gdbwin = 0 gdbwin = 0
enddef enddef
@@ -436,6 +469,10 @@ def StartDebug_term(dict: dict<any>)
# Wait for the "startupdone" message before sending any commands. # Wait for the "startupdone" message before sending any commands.
var counter = 0 var counter = 0
var counter_max = 300 var counter_max = 300
if exists('g:termdebug_config') && has_key(g:termdebug_config, 'timeout')
counter_max = g:termdebug_config['timeout']
endif
var success = false var success = false
while !success && counter < counter_max while !success && counter < counter_max
if !IsGdbStarted() if !IsGdbStarted()
@@ -524,6 +561,9 @@ enddef
# Open a window with a prompt buffer to run gdb in. # Open a window with a prompt buffer to run gdb in.
def StartDebug_prompt(dict: dict<any>) def StartDebug_prompt(dict: dict<any>)
var gdb_cmd = GetCommand()
gdbbufname = gdb_cmd[0]
if vvertical if vvertical
vertical new vertical new
else else
@@ -533,17 +573,7 @@ def StartDebug_prompt(dict: dict<any>)
promptbuf = bufnr('') promptbuf = bufnr('')
prompt_setprompt(promptbuf, 'gdb> ') prompt_setprompt(promptbuf, 'gdb> ')
set buftype=prompt set buftype=prompt
exe $"file {gdbbufname}"
if empty(glob('gdb'))
file gdb
elseif empty(glob('Termdebug-gdb-console'))
file Termdebug-gdb-console
else
Echoerr("You have a file/folder named 'gdb' " ..
"or 'Termdebug-gdb-console'. " ..
"Please exit and rename them because Termdebug may not work " ..
"as expected.")
endif
prompt_setcallback(promptbuf, function('PromptCallback')) prompt_setcallback(promptbuf, function('PromptCallback'))
prompt_setinterrupt(promptbuf, function('PromptInterrupt')) prompt_setinterrupt(promptbuf, function('PromptInterrupt'))
@@ -557,7 +587,6 @@ def StartDebug_prompt(dict: dict<any>)
var gdb_args = get(dict, 'gdb_args', []) var gdb_args = get(dict, 'gdb_args', [])
var proc_args = get(dict, 'proc_args', []) var proc_args = get(dict, 'proc_args', [])
var gdb_cmd = GetCommand()
# Add -quiet to avoid the intro message causing a hit-enter prompt. # Add -quiet to avoid the intro message causing a hit-enter prompt.
gdb_cmd += ['-quiet'] gdb_cmd += ['-quiet']
# Disable pagination, it causes everything to stop at the gdb, needs to be run early # Disable pagination, it causes everything to stop at the gdb, needs to be run early
@@ -703,9 +732,9 @@ def g:TermDebugSendCommand(cmd: string)
if way == 'prompt' if way == 'prompt'
ch_sendraw(gdb_channel, $"{cmd}\n") ch_sendraw(gdb_channel, $"{cmd}\n")
else else
var do_continue = 0 var do_continue = false
if !stopped if !stopped
do_continue = 1 do_continue = true
StopCommand() StopCommand()
sleep 10m sleep 10m
endif endif
@@ -918,6 +947,7 @@ def EndDebugCommon()
endif endif
au! TermDebug au! TermDebug
g:termdebug_is_running = false
enddef enddef
def EndPromptDebug(job: any, status: any) def EndPromptDebug(job: any, status: any)
@@ -1122,11 +1152,11 @@ def InstallCommands()
command Source GotoSourcewinOrCreateIt() command Source GotoSourcewinOrCreateIt()
command Asm GotoAsmwinOrCreateIt() command Asm GotoAsmwinOrCreateIt()
command Var GotoVariableswinOrCreateIt() command Var GotoVariableswinOrCreateIt()
command Winbar InstallWinbar(1) command Winbar InstallWinbar(true)
var map = 1 var map = true
if exists('g:termdebug_config') if exists('g:termdebug_config')
map = get(g:termdebug_config, 'map_K', 1) map = get(g:termdebug_config, 'map_K', true)
elseif exists('g:termdebug_map_K') elseif exists('g:termdebug_map_K')
map = g:termdebug_map_K map = g:termdebug_map_K
endif endif
@@ -1137,9 +1167,9 @@ def InstallCommands()
endif endif
endif endif
map = 1 map = true
if exists('g:termdebug_config') if exists('g:termdebug_config')
map = get(g:termdebug_config, 'map_plus', 1) map = get(g:termdebug_config, 'map_plus', true)
endif endif
if map if map
if !empty(saved_plus_map) && !saved_plus_map.buffer || empty(saved_plus_map) if !empty(saved_plus_map) && !saved_plus_map.buffer || empty(saved_plus_map)
@@ -1147,9 +1177,9 @@ def InstallCommands()
endif endif
endif endif
map = 1 map = true
if exists('g:termdebug_config') if exists('g:termdebug_config')
map = get(g:termdebug_config, 'map_minus', 1) map = get(g:termdebug_config, 'map_minus', true)
endif endif
if map if map
if !empty(saved_minus_map) && !saved_minus_map.buffer || empty(saved_minus_map) if !empty(saved_minus_map) && !saved_minus_map.buffer || empty(saved_minus_map)
@@ -1159,11 +1189,11 @@ def InstallCommands()
if has('menu') && &mouse != '' if has('menu') && &mouse != ''
InstallWinbar(0) InstallWinbar(false)
var pup = 1 var pup = true
if exists('g:termdebug_config') if exists('g:termdebug_config')
pup = get(g:termdebug_config, 'popup', 1) pup = get(g:termdebug_config, 'popup', true)
elseif exists('g:termdebug_popup') elseif exists('g:termdebug_popup')
pup = g:termdebug_popup pup = g:termdebug_popup
endif endif
@@ -1181,11 +1211,11 @@ def InstallCommands()
enddef enddef
# Install the window toolbar in the current window. # Install the window toolbar in the current window.
def InstallWinbar(force: number) def InstallWinbar(force: bool)
# install the window toolbar by default, can be disabled in the config # install the window toolbar by default, can be disabled in the config
var winbar = 1 var winbar = true
if exists('g:termdebug_config') if exists('g:termdebug_config')
winbar = get(g:termdebug_config, 'winbar', 1) winbar = get(g:termdebug_config, 'winbar', true)
endif endif
if has('menu') && &mouse != '' && (winbar || force) if has('menu') && &mouse != '' && (winbar || force)
@@ -1301,9 +1331,9 @@ enddef
def SetBreakpoint(at: string, tbreak=false) def SetBreakpoint(at: string, tbreak=false)
# Setting a breakpoint may not work while the program is running. # Setting a breakpoint may not work while the program is running.
# Interrupt to make it work. # Interrupt to make it work.
var do_continue = 0 var do_continue = false
if !stopped if !stopped
do_continue = 1 do_continue = true
StopCommand() StopCommand()
sleep 10m sleep 10m
endif endif
@@ -1539,7 +1569,7 @@ def GotoSourcewinOrCreateIt()
if !win_gotoid(sourcewin) if !win_gotoid(sourcewin)
new new
sourcewin = win_getid() sourcewin = win_getid()
InstallWinbar(0) InstallWinbar(false)
endif endif
enddef enddef
@@ -1591,13 +1621,9 @@ def GotoAsmwinOrCreateIt()
if asmbufnr > 0 && bufexists(asmbufnr) if asmbufnr > 0 && bufexists(asmbufnr)
exe $'buffer {asmbufnr}' exe $'buffer {asmbufnr}'
elseif empty(glob('Termdebug-asm-listing'))
silent file Termdebug-asm-listing
asmbufnr = bufnr('Termdebug-asm-listing')
else else
Echoerr("You have a file/folder named 'Termdebug-asm-listing'. " .. exe $"silent file {asmbufname}"
"Please exit and rename it because Termdebug may not work " .. asmbufnr = bufnr(asmbufname)
"as expected.")
endif endif
if mdf != 'vert' && GetDisasmWindowHeight() > 0 if mdf != 'vert' && GetDisasmWindowHeight() > 0
@@ -1663,15 +1689,12 @@ def GotoVariableswinOrCreateIt()
setlocal signcolumn=no setlocal signcolumn=no
setlocal modifiable setlocal modifiable
# If exists, then open, otherwise create
if varbufnr > 0 && bufexists(varbufnr) if varbufnr > 0 && bufexists(varbufnr)
exe $'buffer {varbufnr}' exe $'buffer {varbufnr}'
elseif empty(glob('Termdebug-variables-listing'))
silent file Termdebug-variables-listing
varbufnr = bufnr('Termdebug-variables-listing')
else else
Echoerr("You have a file/folder named 'Termdebug-variables-listing'. " .. exe $"silent file {varbufname}"
"Please exit and rename it because Termdebug may not work " .. varbufnr = bufnr(varbufname)
"as expected.")
endif endif
if mdf != 'vert' && GetVariablesWindowHeight() > 0 if mdf != 'vert' && GetVariablesWindowHeight() > 0
@@ -1691,7 +1714,7 @@ def HandleCursor(msg: string)
if msg =~ '^\*stopped' if msg =~ '^\*stopped'
ch_log('program stopped') ch_log('program stopped')
stopped = 1 stopped = true
if msg =~ '^\*stopped,reason="exited-normally"' if msg =~ '^\*stopped,reason="exited-normally"'
running = false running = false
endif endif
@@ -1750,7 +1773,7 @@ def HandleCursor(msg: string)
# TODO: find existing window # TODO: find existing window
exe $'split {fnameescape(fname)}' exe $'split {fnameescape(fname)}'
sourcewin = win_getid() sourcewin = win_getid()
InstallWinbar(0) InstallWinbar(false)
else else
exe $'edit {fnameescape(fname)}' exe $'edit {fnameescape(fname)}'
endif endif

View File

@@ -337,44 +337,6 @@ func Test_termdebug_mapping()
%bw! %bw!
endfunc endfunc
func Test_termdebug_bufnames()
" Test if user has filename/folders named gdb, Termdebug-gdb-console,
" etc. in the current directory
let g:termdebug_config = {}
let g:termdebug_config['use_prompt'] = 1
let filename = 'gdb'
let replacement_filename = 'Termdebug-gdb-console'
call writefile(['This', 'is', 'a', 'test'], filename, 'D')
" Throw away the file once the test has done.
Termdebug
" Once termdebug has completed the startup you should have 3 windows on screen
call WaitForAssert({-> assert_equal(3, winnr('$'))})
" A file named filename already exists in the working directory,
" hence you must call the newly created buffer differently
call WaitForAssert({-> assert_false(bufexists(filename))})
call WaitForAssert({-> assert_true(bufexists(replacement_filename))})
quit!
call WaitForAssert({-> assert_equal(1, winnr('$'))})
" Check if error message is in :message
let g:termdebug_config['disasm_window'] = 1
let filename = 'Termdebug-asm-listing'
call writefile(['This', 'is', 'a', 'test'], filename, 'D')
" Check only the head of the error message
let error_message = "You have a file/folder named '" .. filename .. "'"
Termdebug
" Once termdebug has completed the startup you should have 4 windows on screen
call WaitForAssert({-> assert_equal(4, winnr('$'))})
call WaitForAssert({-> assert_notequal(-1, stridx(execute('messages'), error_message))})
quit!
wincmd b
wincmd q
call WaitForAssert({-> assert_equal(1, winnr('$'))})
unlet g:termdebug_config
endfunc
function Test_termdebug_save_restore_variables() function Test_termdebug_save_restore_variables()
" saved mousemodel " saved mousemodel
let &mousemodel='' let &mousemodel=''
@@ -413,5 +375,39 @@ function Test_termdebug_save_restore_variables()
unlet g:termdebug_config unlet g:termdebug_config
endfunction endfunction
function Test_termdebug_sanity_check()
" Test if user has filename/folders with wrong names
let g:termdebug_config = {}
let s:dict = {'disasm_window': 'Termdebug-asm-listing', 'use_prompt': 'gdb', 'variables_window': 'Termdebug-variables-listing'}
for key in keys(s:dict)
let s:filename = s:dict[key]
let g:termdebug_config[key] = 1
let s:error_message = "You have a file/folder named '" .. s:filename .. "'"
" Write dummy file with bad name
call writefile(['This', 'is', 'a', 'test'], s:filename)
Termdebug
call WaitForAssert({-> assert_true(execute('messages') =~ s:error_message)})
call WaitForAssert({-> assert_equal(1, winnr('$'))})
call delete(s:filename)
call remove(g:termdebug_config, key)
endfor
unlet g:termdebug_config
endfunction
function Test_termdebug_double_termdebug_instances()
let s:error_message = 'Terminal debugger already running, cannot run two'
Termdebug
call WaitForAssert({-> assert_equal(3, winnr('$'))})
Termdebug
call WaitForAssert({-> assert_true(execute('messages') =~ s:error_message)})
wincmd t
quit!
call WaitForAssert({-> assert_equal(1, winnr('$'))})
:%bw!
endfunction
" vim: shiftwidth=2 sts=2 expandtab " vim: shiftwidth=2 sts=2 expandtab

View File

@@ -704,6 +704,8 @@ static char *(features[]) =
static int included_patches[] = static int included_patches[] =
{ /* Add new patch number below this line */ { /* Add new patch number below this line */
/**/
508,
/**/ /**/
507, 507,
/**/ /**/