mirror of
https://github.com/vim/vim.git
synced 2025-10-16 07:24:23 -04:00
runtime(termdebug): Add remote debugging capabilities
closes: #18429 Co-authored-by: Christian Brabandt <cb@256bit.org> Signed-off-by: Miguel Barro <miguel.barro@live.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
committed by
Christian Brabandt
parent
143686b3c4
commit
3c5221f8ee
@@ -10952,8 +10952,12 @@ termdebug-example terminal.txt /*termdebug-example*
|
|||||||
termdebug-frames terminal.txt /*termdebug-frames*
|
termdebug-frames terminal.txt /*termdebug-frames*
|
||||||
termdebug-mappings terminal.txt /*termdebug-mappings*
|
termdebug-mappings terminal.txt /*termdebug-mappings*
|
||||||
termdebug-prompt terminal.txt /*termdebug-prompt*
|
termdebug-prompt terminal.txt /*termdebug-prompt*
|
||||||
|
termdebug-remote terminal.txt /*termdebug-remote*
|
||||||
|
termdebug-remote-example terminal.txt /*termdebug-remote-example*
|
||||||
|
termdebug-remote-window terminal.txt /*termdebug-remote-window*
|
||||||
termdebug-starting terminal.txt /*termdebug-starting*
|
termdebug-starting terminal.txt /*termdebug-starting*
|
||||||
termdebug-stepping terminal.txt /*termdebug-stepping*
|
termdebug-stepping terminal.txt /*termdebug-stepping*
|
||||||
|
termdebug-substitute-path terminal.txt /*termdebug-substitute-path*
|
||||||
termdebug-timeout terminal.txt /*termdebug-timeout*
|
termdebug-timeout terminal.txt /*termdebug-timeout*
|
||||||
termdebug-variables terminal.txt /*termdebug-variables*
|
termdebug-variables terminal.txt /*termdebug-variables*
|
||||||
termdebug_contributing terminal.txt /*termdebug_contributing*
|
termdebug_contributing terminal.txt /*termdebug_contributing*
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
*terminal.txt* For Vim version 9.1. Last change: 2025 Sep 15
|
*terminal.txt* For Vim version 9.1. Last change: 2025 Oct 08
|
||||||
|
|
||||||
|
|
||||||
VIM REFERENCE MANUAL by Bram Moolenaar
|
VIM REFERENCE MANUAL by Bram Moolenaar
|
||||||
@@ -44,6 +44,7 @@ If the result is "1" you have it.
|
|||||||
Prompt mode |termdebug-prompt|
|
Prompt mode |termdebug-prompt|
|
||||||
Mappings |termdebug-mappings|
|
Mappings |termdebug-mappings|
|
||||||
Communication |termdebug-communication|
|
Communication |termdebug-communication|
|
||||||
|
Remote Debugging |termdebug-remote|
|
||||||
Customizing |termdebug-customizing|
|
Customizing |termdebug-customizing|
|
||||||
|
|
||||||
{only available when compiled with the |+terminal| feature}
|
{only available when compiled with the |+terminal| feature}
|
||||||
@@ -1635,12 +1636,103 @@ interrupt the running program. But after using the MI command
|
|||||||
communication channel.
|
communication channel.
|
||||||
|
|
||||||
|
|
||||||
|
Remote debugging ~
|
||||||
|
*termdebug-remote*
|
||||||
|
One of the main issues of remote debugging is the access to the debuggee's
|
||||||
|
source files. The plugin can profit from system and vim's networking
|
||||||
|
capabilities to workaround this.
|
||||||
|
*termdebug-remote-example*
|
||||||
|
The |termdebug-example| can be replicated by running the `gdb` debugger to
|
||||||
|
debug Vim on a remote Linux machine accessible via `ssh`.
|
||||||
|
|
||||||
|
- Build Vim as explained in the local example.
|
||||||
|
|
||||||
|
- If "socat" is not available in the remote machine 'terminal' mode will not
|
||||||
|
work properly. Fall back to |termdebug_use_prompt|: >
|
||||||
|
:let g:termdebug_config = {}
|
||||||
|
:let g:termdebug_config['use_prompt'] = v:true
|
||||||
|
|
||||||
|
- Specify the command line to run the remote `gdb` instance: >
|
||||||
|
:let g:termdebug_config['command'] = ['ssh', 'hostname', 'gdb']
|
||||||
|
< Explaining `ssh` is beyond the scope of this example, but notice the
|
||||||
|
command line can be greatly simplified by specifying the user, keys and
|
||||||
|
other options into the `$HOME/.ssh/config` file.
|
||||||
|
|
||||||
|
- Provide a hint for translating remote paths into |netrw| paths: >
|
||||||
|
:let g:termdebug_config['substitute_path'] = { '/': 'scp://hostname//' }
|
||||||
|
|
||||||
|
- Load the termdebug plugin and start debugging Vim: >
|
||||||
|
:packadd termdebug
|
||||||
|
:Termdebug vim
|
||||||
|
|
||||||
|
You now have the same three windows of the local example and can follow the
|
||||||
|
very same steps. The only difference is that the source windows displays a
|
||||||
|
netrw buffer instead of a local one.
|
||||||
|
|
||||||
|
*termdebug-substitute-path*
|
||||||
|
Use the `g:termdebug_config['substitute_path']` entry to map remote to local
|
||||||
|
files using the same strategy that gdb's `substitute-path` command uses.
|
||||||
|
For example:
|
||||||
|
- Use |netrw| to access files remoting via ssh: >
|
||||||
|
let g:termdebug_config['command'] = ['ssh', 'hostname', 'gdb']
|
||||||
|
let g:termdebug_config['substitute_path'] = { '/': 'scp://hostname//' }
|
||||||
|
< Note: that the key specifies the remote machine root path and the value
|
||||||
|
the local one.
|
||||||
|
- Use Windows' `UNC` paths to access `WSL2` sources: >
|
||||||
|
let g:termdebug_config['command'] = ['wsl', 'gdb']
|
||||||
|
let g:termdebug_config['substitute_path'] = {
|
||||||
|
\ '/': '\\wsl.localhost\Ubuntu-22.04\',
|
||||||
|
\ '/mnt/c/': 'C:/' }
|
||||||
|
< Note: that several mappings are required: one for each drive unit
|
||||||
|
and one for the linux filesystem (queried via `wslpath`).
|
||||||
|
|
||||||
|
In this mode any `ssh` or `wsl` command would be detected and a similar
|
||||||
|
command would be used to launch `socat` in a remote `tty` terminal session
|
||||||
|
and connect it to `gdb`.
|
||||||
|
If `socat` is not available a plain remote terminal would be used as
|
||||||
|
fallback.
|
||||||
|
The next session shows how to override this default behaviour.
|
||||||
|
|
||||||
|
*termdebug-remote-window*
|
||||||
|
In order to use another remote terminal client, set "remote_window" entry
|
||||||
|
in `g:termdebug_config` variable before invoking `:Termdebug`. For example:
|
||||||
|
- Debugging inside a docker container using "prompt" mode: >
|
||||||
|
let g:termdebug_config['use_prompt'] = v:true
|
||||||
|
let g:termdebug_config['command'] = ['docker', 'run', '-i',
|
||||||
|
\ '--rm', '--name', 'container-name', 'image-name', 'gdb']
|
||||||
|
let g:termdebug_config['remote_window'] =
|
||||||
|
\ ['docker', 'exec', '-ti', 'container-name'
|
||||||
|
\ ,'socat', '-dd', '-', 'PTY,raw,echo=0']
|
||||||
|
|
||||||
|
- Debugging inside a docker container using a "terminal buffer".
|
||||||
|
The container should be already running because unlike the previous
|
||||||
|
case for `terminal mode` "program" and "communication" ptys are created
|
||||||
|
before the gdb one: >
|
||||||
|
$ docker run -ti --rm --name container-name immage-name
|
||||||
|
|
||||||
|
< Then, launch the debugger: >
|
||||||
|
let g:termdebug_config['use_prompt'] = v:false " default
|
||||||
|
let g:termdebug_config['command'] =
|
||||||
|
\ ['docker', 'exec', '-ti', 'container-name', 'gdb']
|
||||||
|
let g:termdebug_config['remote_window'] =
|
||||||
|
\ ['docker', 'exec', '-ti', 'container-name'
|
||||||
|
\ ,'socat', '-dd', '-', 'PTY,raw,echo=0']
|
||||||
|
|
||||||
|
Note: "command" cannot use `-t` on |termdebug-prompt| mode because prompt
|
||||||
|
buffers cannot handle `tty` connections.
|
||||||
|
The "remote_window" command must use `-t` because otherwise it will lack
|
||||||
|
a `pty slave device` for gdb to connect.
|
||||||
|
Note: "socat" must be available in the remote machine on "terminal" mode.
|
||||||
|
Note: docker container sources can be accessible combining `volumes`
|
||||||
|
with mappings (see |termdebug-substitute-path|).
|
||||||
|
|
||||||
GDB command ~
|
GDB command ~
|
||||||
*g:termdebugger*
|
*g:termdebugger*
|
||||||
To change the name of the gdb command, set "debugger" entry in
|
To change the name of the gdb command, set "debugger" entry in
|
||||||
g:termdebug_config or the "g:termdebugger" variable before invoking
|
g:termdebug_config or the "g:termdebugger" variable before invoking
|
||||||
`:Termdebug`: >
|
`:Termdebug`: >
|
||||||
let g:termdebug_config['command'] = "mygdb"
|
let g:termdebug_config['command'] = "mygdb"
|
||||||
|
|
||||||
If there is no g:termdebug_config you can use: >
|
If there is no g:termdebug_config you can use: >
|
||||||
let g:termdebugger = "mygdb"
|
let g:termdebugger = "mygdb"
|
||||||
|
|
||||||
@@ -1648,6 +1740,7 @@ However, the latter form will be deprecated in future releases.
|
|||||||
|
|
||||||
If the command needs an argument use a List: >
|
If the command needs an argument use a List: >
|
||||||
let g:termdebug_config['command'] = ['rr', 'replay', '--']
|
let g:termdebug_config['command'] = ['rr', 'replay', '--']
|
||||||
|
|
||||||
If there is no g:termdebug_config you can use: >
|
If there is no g:termdebug_config you can use: >
|
||||||
let g:termdebugger = ['rr', 'replay', '--']
|
let g:termdebugger = ['rr', 'replay', '--']
|
||||||
|
|
||||||
@@ -1655,6 +1748,13 @@ Several arguments will be added to make gdb work well for the debugger.
|
|||||||
If you want to modify them, add a function to filter the argument list: >
|
If you want to modify them, add a function to filter the argument list: >
|
||||||
let g:termdebug_config['command_filter'] = MyDebugFilter
|
let g:termdebug_config['command_filter'] = MyDebugFilter
|
||||||
|
|
||||||
|
A "command_filter" scenario is solving escaping issues on remote debugging
|
||||||
|
over "ssh". For convenience a default filter is provided for escaping
|
||||||
|
whitespaces inside the arguments. It is automatically configured for "ssh",
|
||||||
|
but can be employed in other use cases like this: >
|
||||||
|
let g:termdebug_config['command_filter'] =
|
||||||
|
/ function('g:Termdebug_escape_whitespace')
|
||||||
|
|
||||||
If you do not want the arguments to be added, but you do need to set the
|
If you do not want the arguments to be added, but you do need to set the
|
||||||
"pty", use a function to add the necessary arguments: >
|
"pty", use a function to add the necessary arguments: >
|
||||||
let g:termdebug_config['command_add_args'] = MyAddArguments
|
let g:termdebug_config['command_add_args'] = MyAddArguments
|
||||||
@@ -1717,7 +1817,8 @@ than 99 will be displayed as "9+".
|
|||||||
If you want to customize the breakpoint signs to show `>>` in the signcolumn: >
|
If you want to customize the breakpoint signs to show `>>` in the signcolumn: >
|
||||||
let g:termdebug_config['sign'] = '>>'
|
let g:termdebug_config['sign'] = '>>'
|
||||||
You can also specify individual signs for the first several breakpoints: >
|
You can also specify individual signs for the first several breakpoints: >
|
||||||
let g:termdebug_config['signs'] = ['>1', '>2', '>3', '>4', '>5', '>6', '>7', '>8', '>9']
|
let g:termdebug_config['signs'] = ['>1', '>2', '>3', '>4', '>5',
|
||||||
|
\ '>6', '>7', '>8', '>9']
|
||||||
let g:termdebug_config['sign'] = '>>'
|
let g:termdebug_config['sign'] = '>>'
|
||||||
If you would like to use decimal (base 10) breakpoint signs: >
|
If you would like to use decimal (base 10) breakpoint signs: >
|
||||||
let g:termdebug_config['sign_decimal'] = 1
|
let g:termdebug_config['sign_decimal'] = 1
|
||||||
|
336
runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
vendored
336
runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
vendored
@@ -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: 2025 Sep 15
|
# Last Change: 2025 Oct 08
|
||||||
# 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
|
||||||
@@ -34,6 +34,7 @@ vim9script
|
|||||||
# Gdb is run as a job with callbacks for I/O.
|
# Gdb is run as a job with callbacks for I/O.
|
||||||
# On Unix another terminal window is opened to run the debugged program
|
# On Unix another terminal window is opened to run the debugged program
|
||||||
# On MS-Windows a separate console is opened to run the debugged program
|
# On MS-Windows a separate console is opened to run the debugged program
|
||||||
|
# but a terminal window is used to run remote debugged programs.
|
||||||
|
|
||||||
# The communication with gdb uses GDB/MI. See:
|
# The communication with gdb uses GDB/MI. See:
|
||||||
# https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html
|
# https://sourceware.org/gdb/current/onlinedocs/gdb/GDB_002fMI.html
|
||||||
@@ -435,8 +436,73 @@ def IsGdbStarted(): bool
|
|||||||
return true
|
return true
|
||||||
enddef
|
enddef
|
||||||
|
|
||||||
def CreateProgramPty(): string
|
# Check if the debugger is running remotely and return a suitable command to pty remotely
|
||||||
ptybufnr = term_start('NONE', {
|
def GetRemotePtyCmd(gdb_cmd: list<string>): list<string>
|
||||||
|
# Check if the user provided a command to launch the program window
|
||||||
|
var term_cmd = null_list
|
||||||
|
if exists('g:termdebug_config') && has_key(g:termdebug_config, 'remote_window')
|
||||||
|
term_cmd = g:termdebug_config['remote_window']
|
||||||
|
term_cmd = type(term_cmd) == v:t_list ? copy(term_cmd) : [term_cmd]
|
||||||
|
else
|
||||||
|
# Check if it is a remote gdb, the program terminal should be started
|
||||||
|
# on the remote machine.
|
||||||
|
const remote_pattern = '^\(ssh\|wsl\)'
|
||||||
|
if gdb_cmd[0] =~? remote_pattern
|
||||||
|
var gdb_pos = indexof(gdb_cmd, $'v:val =~? "^{GetCommand()[-1]}"')
|
||||||
|
if gdb_pos > 0
|
||||||
|
# strip debugger call
|
||||||
|
term_cmd = gdb_cmd[0 : gdb_pos - 1]
|
||||||
|
# roundtrip to check if socat is available on the remote side
|
||||||
|
silent call system(join(term_cmd, ' ') .. ' socat -h')
|
||||||
|
if v:shell_error
|
||||||
|
Echowarn('Install socat on the remote machine for a program window better experience')
|
||||||
|
else
|
||||||
|
# create a devoted tty slave device and link to stdin/stdout
|
||||||
|
term_cmd += ['socat', '-dd', '-', 'PTY,raw,echo=0']
|
||||||
|
ch_log($'launching remote ttys using "{join(term_cmd)}"')
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
endif
|
||||||
|
return term_cmd
|
||||||
|
enddef
|
||||||
|
|
||||||
|
# Retrieve the remote pty device from a remote terminal
|
||||||
|
# If interact is true, use remote tty command to get the pty device
|
||||||
|
def GetRemotePtyDev(bufnr: number, interact: bool): string
|
||||||
|
var pty: string = null_string
|
||||||
|
var line = null_string
|
||||||
|
|
||||||
|
for j in range(5)
|
||||||
|
|
||||||
|
if interact
|
||||||
|
term_sendkeys(bufnr, "tty\<CR>")
|
||||||
|
endif
|
||||||
|
|
||||||
|
for i in range(0, term_getsize(bufnr)[0])
|
||||||
|
line = term_getline(bufnr, i)
|
||||||
|
if line =~? "/dev/pts"
|
||||||
|
pty = line
|
||||||
|
break
|
||||||
|
endif
|
||||||
|
term_wait(bufnr, 100)
|
||||||
|
endfor # i
|
||||||
|
|
||||||
|
if pty != null_string
|
||||||
|
# Clear the terminal window
|
||||||
|
if interact
|
||||||
|
term_sendkeys(bufnr, "clear\<CR>")
|
||||||
|
endif
|
||||||
|
break
|
||||||
|
endif
|
||||||
|
|
||||||
|
endfor # j
|
||||||
|
|
||||||
|
return pty
|
||||||
|
enddef
|
||||||
|
|
||||||
|
def CreateProgramPty(cmd: list<string> = null_list): string
|
||||||
|
ptybufnr = term_start(!cmd ? 'NONE' : cmd, {
|
||||||
term_name: ptybufname,
|
term_name: ptybufname,
|
||||||
vertical: vvertical})
|
vertical: vvertical})
|
||||||
if ptybufnr == 0
|
if ptybufnr == 0
|
||||||
@@ -454,20 +520,76 @@ def CreateProgramPty(): string
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
return job_info(term_getjob(ptybufnr))['tty_out']
|
if !cmd
|
||||||
|
return job_info(term_getjob(ptybufnr))['tty_out']
|
||||||
|
else
|
||||||
|
var interact = indexof(cmd, 'v:val =~? "^socat"') < 0
|
||||||
|
var pty = GetRemotePtyDev(ptybufnr, interact)
|
||||||
|
|
||||||
|
if pty !~? "/dev/pts"
|
||||||
|
Echoerr('Failed to get the program window tty')
|
||||||
|
exe $'bwipe! {ptybufnr}'
|
||||||
|
pty = null_string
|
||||||
|
elseif pty !~? "^/dev/pts"
|
||||||
|
# remove the prompt
|
||||||
|
pty = pty->matchstr('/dev/pts/\d\+')
|
||||||
|
endif
|
||||||
|
|
||||||
|
return pty
|
||||||
|
endif
|
||||||
enddef
|
enddef
|
||||||
|
|
||||||
def CreateCommunicationPty(): string
|
def CreateCommunicationPty(cmd: list<string> = null_list): string
|
||||||
# Create a hidden terminal window to communicate with gdb
|
# Create a hidden terminal window to communicate with gdb
|
||||||
commbufnr = term_start('NONE', {
|
var options: dict<any> = { term_name: commbufname, out_cb: CommOutput, hidden: 1 }
|
||||||
term_name: commbufname,
|
|
||||||
out_cb: CommOutput,
|
if !cmd
|
||||||
hidden: 1
|
commbufnr = term_start('NONE', options)
|
||||||
})
|
else
|
||||||
|
# avoid message wrapping that prevents proper parsing
|
||||||
|
options['term_cols'] = 500
|
||||||
|
commbufnr = term_start(cmd, options)
|
||||||
|
endif
|
||||||
|
|
||||||
if commbufnr == 0
|
if commbufnr == 0
|
||||||
return null_string
|
return null_string
|
||||||
endif
|
endif
|
||||||
return job_info(term_getjob(commbufnr))['tty_out']
|
|
||||||
|
if !cmd
|
||||||
|
return job_info(term_getjob(commbufnr))['tty_out']
|
||||||
|
else
|
||||||
|
# CommunicationPty only will be reliable with socat
|
||||||
|
if indexof(cmd, 'v:val =~? "^socat"') < 0
|
||||||
|
Echoerr('Communication window should be started with socat')
|
||||||
|
exe $'bwipe! {commbufnr}'
|
||||||
|
return null_string
|
||||||
|
endif
|
||||||
|
|
||||||
|
var pty = GetRemotePtyDev(commbufnr, false)
|
||||||
|
|
||||||
|
if pty !~? "/dev/pts"
|
||||||
|
Echoerr('Failed to get the communication window tty')
|
||||||
|
exe $'bwipe! {commbufnr}'
|
||||||
|
pty = null_string
|
||||||
|
elseif pty !~? "^/dev/pts"
|
||||||
|
# remove the prompt
|
||||||
|
pty = pty->matchstr('/dev/pts/\d\+')
|
||||||
|
endif
|
||||||
|
|
||||||
|
return pty
|
||||||
|
endif
|
||||||
|
enddef
|
||||||
|
|
||||||
|
# Convenient filter to workaround remote escaping issues.
|
||||||
|
# For example, ssh doesn't escape spaces for the gdb arguments.
|
||||||
|
# Workaround doing:
|
||||||
|
# let g:termdebug_config['command_filter'] = function('g:Termdebug_escape_whitespace')
|
||||||
|
def g:Termdebug_escape_whitespace(args: list<string>): list<string>
|
||||||
|
var new_args: list<string> = []
|
||||||
|
for arg in args
|
||||||
|
new_args += [substitute(arg, ' ', '\\ ', 'g')]
|
||||||
|
endfor
|
||||||
|
return new_args
|
||||||
enddef
|
enddef
|
||||||
|
|
||||||
def CreateGdbConsole(dict: dict<any>, pty: string, commpty: string): string
|
def CreateGdbConsole(dict: dict<any>, pty: string, commpty: string): string
|
||||||
@@ -497,6 +619,12 @@ def CreateGdbConsole(dict: dict<any>, pty: string, commpty: string): string
|
|||||||
gdb_cmd += ['-ex', 'echo startupdone\n']
|
gdb_cmd += ['-ex', 'echo startupdone\n']
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# Escape whitespaces in the gdb arguments for ssh remoting
|
||||||
|
if exists('g:termdebug_config') && !has_key(g:termdebug_config, 'command_filter') &&
|
||||||
|
gdb_cmd[0] =~? '^ssh'
|
||||||
|
g:termdebug_config['command_filter'] = function('g:Termdebug_escape_whitespace')
|
||||||
|
endif
|
||||||
|
|
||||||
if exists('g:termdebug_config') && has_key(g:termdebug_config, 'command_filter')
|
if exists('g:termdebug_config') && has_key(g:termdebug_config, 'command_filter')
|
||||||
gdb_cmd = g:termdebug_config.command_filter(gdb_cmd)
|
gdb_cmd = g:termdebug_config.command_filter(gdb_cmd)
|
||||||
endif
|
endif
|
||||||
@@ -599,14 +727,18 @@ enddef
|
|||||||
# Open a terminal window without a job, to run the debugged program in.
|
# Open a terminal window without a job, to run the debugged program in.
|
||||||
def StartDebug_term(dict: dict<any>)
|
def StartDebug_term(dict: dict<any>)
|
||||||
|
|
||||||
var programpty = CreateProgramPty()
|
# Retrieve command if remote pty is needed
|
||||||
|
var gdb_cmd = GetCommand()
|
||||||
|
var term_cmd = GetRemotePtyCmd(gdb_cmd)
|
||||||
|
|
||||||
|
var programpty = CreateProgramPty(term_cmd)
|
||||||
if programpty is null_string
|
if programpty is null_string
|
||||||
Echoerr('Failed to open the program terminal window')
|
Echoerr('Failed to open the program terminal window')
|
||||||
CloseBuffers()
|
CloseBuffers()
|
||||||
return
|
return
|
||||||
endif
|
endif
|
||||||
|
|
||||||
var commpty = CreateCommunicationPty()
|
var commpty = CreateCommunicationPty(term_cmd)
|
||||||
if commpty is null_string
|
if commpty is null_string
|
||||||
Echoerr('Failed to open the communication terminal window')
|
Echoerr('Failed to open the communication terminal window')
|
||||||
CloseBuffers()
|
CloseBuffers()
|
||||||
@@ -656,16 +788,27 @@ 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', [])
|
||||||
|
|
||||||
# Add -quiet to avoid the intro message causing a hit-enter prompt.
|
# directly communicate via mi2. This option must precede any -iex options for proper
|
||||||
gdb_cmd += ['-quiet']
|
# interpretation.
|
||||||
|
gdb_cmd += ['--interpreter=mi2']
|
||||||
# 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
|
||||||
gdb_cmd += ['-iex', 'set pagination off']
|
gdb_cmd += ['-iex', 'set pagination off']
|
||||||
# Interpret commands while the target is running. This should usually only
|
# Interpret commands while the target is running. This should usually only
|
||||||
# be exec-interrupt, since many commands don't work properly while the
|
# be exec-interrupt, since many commands don't work properly while the
|
||||||
# target is running (so execute during startup).
|
# target is running (so execute during startup).
|
||||||
gdb_cmd += ['-iex', 'set mi-async on']
|
gdb_cmd += ['-iex', 'set mi-async on']
|
||||||
# directly communicate via mi2
|
# Add -quiet to avoid the intro message causing a hit-enter prompt.
|
||||||
gdb_cmd += ['--interpreter=mi2']
|
gdb_cmd += ['-quiet']
|
||||||
|
|
||||||
|
# Escape whitespaces in the gdb arguments for ssh remoting
|
||||||
|
if exists('g:termdebug_config') && !has_key(g:termdebug_config, 'command_filter') &&
|
||||||
|
gdb_cmd[0] =~? '^ssh'
|
||||||
|
g:termdebug_config['command_filter'] = function('g:Termdebug_escape_whitespace')
|
||||||
|
endif
|
||||||
|
|
||||||
|
if exists('g:termdebug_config') && has_key(g:termdebug_config, 'command_filter')
|
||||||
|
gdb_cmd = g:termdebug_config.command_filter(gdb_cmd)
|
||||||
|
endif
|
||||||
|
|
||||||
# Adding arguments requested by the user
|
# Adding arguments requested by the user
|
||||||
gdb_cmd += gdb_args
|
gdb_cmd += gdb_args
|
||||||
@@ -686,24 +829,61 @@ def StartDebug_prompt(dict: dict<any>)
|
|||||||
set modified
|
set modified
|
||||||
gdb_channel = job_getchannel(gdbjob)
|
gdb_channel = job_getchannel(gdbjob)
|
||||||
|
|
||||||
ptybufnr = 0
|
# Retrieve command if remote pty is needed
|
||||||
if has('win32')
|
var term_cmd = GetRemotePtyCmd(gdb_cmd)
|
||||||
# MS-Windows: run in a new console window for maximum compatibility
|
|
||||||
SendCommand('set new-console on')
|
# If we are not using socat maybe is a shell:
|
||||||
elseif has('terminal')
|
var interact = indexof(term_cmd, 'v:val =~? "^socat"') < 0
|
||||||
# Unix: Run the debugged program in a terminal window. Open it below the
|
|
||||||
# gdb window.
|
if has('terminal') && (term_cmd != null || !has('win32'))
|
||||||
belowright ptybufnr = term_start('NONE', {
|
|
||||||
term_name: 'debugged program',
|
# Try open terminal twice because sync with gdbjob may not succeed
|
||||||
vertical: vvertical
|
# the first time (docker daemon for example)
|
||||||
})
|
var trials: number = 2
|
||||||
if ptybufnr == 0
|
var pty: string = null_string
|
||||||
Echoerr('Failed to open the program terminal window')
|
|
||||||
|
while trials > 0
|
||||||
|
|
||||||
|
# Run the debugged program in a window. Open it below the
|
||||||
|
# gdb window.
|
||||||
|
belowright ptybufnr = term_start(
|
||||||
|
term_cmd != null ? term_cmd : 'NONE', {
|
||||||
|
term_name: 'debugged program',
|
||||||
|
vertical: vvertical
|
||||||
|
})
|
||||||
|
|
||||||
|
if ptybufnr == 0
|
||||||
|
Echoerr('Failed to open the program terminal window')
|
||||||
|
job_stop(gdbjob)
|
||||||
|
return
|
||||||
|
endif
|
||||||
|
|
||||||
|
ptywin = win_getid()
|
||||||
|
|
||||||
|
if term_cmd is null
|
||||||
|
pty = job_info(term_getjob(ptybufnr))['tty_out']
|
||||||
|
else
|
||||||
|
# Retrieve remote pty value
|
||||||
|
pty = GetRemotePtyDev(ptybufnr, interact)
|
||||||
|
endif
|
||||||
|
|
||||||
|
if pty !~? "/dev/pts"
|
||||||
|
exe $'bwipe! {ptybufnr}'
|
||||||
|
--trials
|
||||||
|
pty = null_string
|
||||||
|
else
|
||||||
|
break
|
||||||
|
endif
|
||||||
|
endwhile
|
||||||
|
|
||||||
|
if pty !~? "/dev/pts"
|
||||||
|
Echoerr('Failed to get the program windows tty')
|
||||||
job_stop(gdbjob)
|
job_stop(gdbjob)
|
||||||
return
|
elseif pty !~? "^/dev/pts"
|
||||||
|
# remove the prompt
|
||||||
|
pty = pty->matchstr('/dev/pts/\d\+')
|
||||||
endif
|
endif
|
||||||
ptywin = win_getid()
|
|
||||||
var pty = job_info(term_getjob(ptybufnr))['tty_out']
|
|
||||||
SendCommand($'tty {pty}')
|
SendCommand($'tty {pty}')
|
||||||
|
|
||||||
# Since GDB runs in a prompt window, the environment has not been set to
|
# Since GDB runs in a prompt window, the environment has not been set to
|
||||||
@@ -714,6 +894,9 @@ def StartDebug_prompt(dict: dict<any>)
|
|||||||
SendCommand($'set env COLUMNS = {winwidth(ptywin)}')
|
SendCommand($'set env COLUMNS = {winwidth(ptywin)}')
|
||||||
SendCommand($'set env COLORS = {&t_Co}')
|
SendCommand($'set env COLORS = {&t_Co}')
|
||||||
SendCommand($'set env VIM_TERMINAL = {v:version}')
|
SendCommand($'set env VIM_TERMINAL = {v:version}')
|
||||||
|
elseif has('win32')
|
||||||
|
# MS-Windows: run in a new console window for maximum compatibility
|
||||||
|
SendCommand('set new-console on')
|
||||||
else
|
else
|
||||||
# TODO: open a new terminal, get the tty name, pass on to gdb
|
# TODO: open a new terminal, get the tty name, pass on to gdb
|
||||||
SendCommand('show inferior-tty')
|
SendCommand('show inferior-tty')
|
||||||
@@ -930,7 +1113,7 @@ enddef
|
|||||||
const NullRepl = 'XXXNULLXXX'
|
const NullRepl = 'XXXNULLXXX'
|
||||||
|
|
||||||
# Extract the "name" value from a gdb message with fullname="name".
|
# Extract the "name" value from a gdb message with fullname="name".
|
||||||
def GetFullname(msg: string): string
|
def GetLocalFullname(msg: string): string
|
||||||
if msg !~ 'fullname'
|
if msg !~ 'fullname'
|
||||||
return ''
|
return ''
|
||||||
endif
|
endif
|
||||||
@@ -944,6 +1127,50 @@ def GetFullname(msg: string): string
|
|||||||
return name
|
return name
|
||||||
enddef
|
enddef
|
||||||
|
|
||||||
|
# Turn a remote machine local path into a remote one.
|
||||||
|
def Local2RemotePath(path: string): string
|
||||||
|
# If no mappings are provided keep the path.
|
||||||
|
if !exists('g:termdebug_config') || !has_key(g:termdebug_config, 'substitute_path')
|
||||||
|
return path
|
||||||
|
endif
|
||||||
|
|
||||||
|
var mappings: list<any> = items(g:termdebug_config['substitute_path'])
|
||||||
|
|
||||||
|
# Try to match the longest local path first.
|
||||||
|
sort(mappings, (a, b) => len(b[0]) - len(a[0]))
|
||||||
|
|
||||||
|
for [local, remote] in mappings
|
||||||
|
const pattern = '^' .. escape(local, '\.*~()')
|
||||||
|
if path =~ pattern
|
||||||
|
return substitute(path, pattern, escape(remote, '\.*~()'), '')
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
|
||||||
|
return path
|
||||||
|
enddef
|
||||||
|
|
||||||
|
# Turn a remote path into a local one to the remote machine.
|
||||||
|
def Remote2LocalPath(path: string): string
|
||||||
|
# If no mappings are provided keep the path.
|
||||||
|
if !exists('g:termdebug_config') || !has_key(g:termdebug_config, 'substitute_path')
|
||||||
|
return path
|
||||||
|
endif
|
||||||
|
|
||||||
|
var mappings: list<any> = items(g:termdebug_config['substitute_path'])
|
||||||
|
|
||||||
|
# Try to match the longest remote path first.
|
||||||
|
sort(mappings, (a, b) => len(b[1]) - len(a[1]))
|
||||||
|
|
||||||
|
for [local, remote] in mappings
|
||||||
|
const pattern = '^' .. escape(substitute(remote, '[\/]', '[\\/]', 'g'), '.*~()')
|
||||||
|
if path =~ pattern
|
||||||
|
return substitute(path, pattern, local, '')
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
|
||||||
|
return path
|
||||||
|
enddef
|
||||||
|
|
||||||
# Extract the "addr" value from a gdb message with addr="0x0001234".
|
# Extract the "addr" value from a gdb message with addr="0x0001234".
|
||||||
def GetAsmAddr(msg: string): string
|
def GetAsmAddr(msg: string): string
|
||||||
if msg !~ 'addr='
|
if msg !~ 'addr='
|
||||||
@@ -1159,7 +1386,7 @@ def CommOutput(chan: channel, message: string)
|
|||||||
enddef
|
enddef
|
||||||
|
|
||||||
def GotoProgram()
|
def GotoProgram()
|
||||||
if has('win32')
|
if has('win32') && !ptywin
|
||||||
if executable('powershell')
|
if executable('powershell')
|
||||||
system(printf('powershell -Command "add-type -AssemblyName microsoft.VisualBasic;[Microsoft.VisualBasic.Interaction]::AppActivate(%d);"', pid))
|
system(printf('powershell -Command "add-type -AssemblyName microsoft.VisualBasic;[Microsoft.VisualBasic.Interaction]::AppActivate(%d);"', pid))
|
||||||
endif
|
endif
|
||||||
@@ -1374,7 +1601,8 @@ def Until(at: string)
|
|||||||
ch_log('assume that program is running after this command')
|
ch_log('assume that program is running after this command')
|
||||||
|
|
||||||
# Use the fname:lnum format
|
# Use the fname:lnum format
|
||||||
var AT = empty(at) ? QuoteArg($"{expand('%:p')}:{line('.')}") : at
|
var fname = Remote2LocalPath(expand('%:p'))
|
||||||
|
var AT = empty(at) ? QuoteArg($"{fname}:{line('.')}") : at
|
||||||
SendCommand($'-exec-until {AT}')
|
SendCommand($'-exec-until {AT}')
|
||||||
else
|
else
|
||||||
ch_log('dropping command, program is running: exec-until')
|
ch_log('dropping command, program is running: exec-until')
|
||||||
@@ -1393,7 +1621,8 @@ def SetBreakpoint(at: string, tbreak=false)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
# Use the fname:lnum format, older gdb can't handle --source.
|
# Use the fname:lnum format, older gdb can't handle --source.
|
||||||
var AT = empty(at) ? QuoteArg($"{expand('%:p')}:{line('.')}") : at
|
var fname = Remote2LocalPath(expand('%:p'))
|
||||||
|
var AT = empty(at) ? QuoteArg($"{fname}:{line('.')}") : at
|
||||||
var cmd = ''
|
var cmd = ''
|
||||||
if tbreak
|
if tbreak
|
||||||
cmd = $'-break-insert -t {AT}'
|
cmd = $'-break-insert -t {AT}'
|
||||||
@@ -1407,7 +1636,8 @@ def SetBreakpoint(at: string, tbreak=false)
|
|||||||
enddef
|
enddef
|
||||||
|
|
||||||
def ClearBreakpoint()
|
def ClearBreakpoint()
|
||||||
var fname = fnameescape(expand('%:p'))
|
var fname = Remote2LocalPath(expand('%:p'))
|
||||||
|
fname = fnameescape(fname)
|
||||||
var lnum = line('.')
|
var lnum = line('.')
|
||||||
var bploc = printf('%s:%d', fname, lnum)
|
var bploc = printf('%s:%d', fname, lnum)
|
||||||
var nr = 0
|
var nr = 0
|
||||||
@@ -1444,7 +1674,8 @@ def ClearBreakpoint()
|
|||||||
enddef
|
enddef
|
||||||
|
|
||||||
def ToggleBreak()
|
def ToggleBreak()
|
||||||
var fname = fnameescape(expand('%:p'))
|
var fname = Remote2LocalPath(expand('%:p'))
|
||||||
|
fname = fnameescape(fname)
|
||||||
var lnum = line('.')
|
var lnum = line('.')
|
||||||
var bploc = printf('%s:%d', fname, lnum)
|
var bploc = printf('%s:%d', fname, lnum)
|
||||||
if has_key(breakpoint_locations, bploc)
|
if has_key(breakpoint_locations, bploc)
|
||||||
@@ -1859,7 +2090,7 @@ def HandleCursor(msg: string)
|
|||||||
|
|
||||||
var fname = ''
|
var fname = ''
|
||||||
if msg =~ 'fullname='
|
if msg =~ 'fullname='
|
||||||
fname = GetFullname(msg)
|
fname = GetLocalFullname(msg)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if msg =~ 'addr='
|
if msg =~ 'addr='
|
||||||
@@ -1887,12 +2118,15 @@ def HandleCursor(msg: string)
|
|||||||
SendCommand('-stack-list-variables 2')
|
SendCommand('-stack-list-variables 2')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if msg =~ '^\(\*stopped\|=thread-selected\)' && filereadable(fname)
|
# Translate to remote file name if needed.
|
||||||
|
const fremote = Local2RemotePath(fname)
|
||||||
|
|
||||||
|
if msg =~ '^\(\*stopped\|=thread-selected\)' && (fremote != fname || filereadable(fname))
|
||||||
var lnum = substitute(msg, '.*line="\([^"]*\)".*', '\1', '')
|
var lnum = substitute(msg, '.*line="\([^"]*\)".*', '\1', '')
|
||||||
if lnum =~ '^[0-9]*$'
|
if lnum =~ '^[0-9]*$'
|
||||||
GotoSourcewinOrCreateIt()
|
GotoSourcewinOrCreateIt()
|
||||||
if expand('%:p') != fnamemodify(fname, ':p')
|
if expand('%:p') != fnamemodify(fremote, ':p')
|
||||||
echomsg $"different fname: '{expand('%:p')}' vs '{fnamemodify(fname, ':p')}'"
|
echomsg $"different fname: '{expand('%:p')}' vs '{fnamemodify(fremote, ':p')}'"
|
||||||
augroup Termdebug
|
augroup Termdebug
|
||||||
# Always open a file read-only instead of showing the ATTENTION
|
# Always open a file read-only instead of showing the ATTENTION
|
||||||
# prompt, since it is unlikely we want to edit the file.
|
# prompt, since it is unlikely we want to edit the file.
|
||||||
@@ -1904,11 +2138,11 @@ def HandleCursor(msg: string)
|
|||||||
augroup END
|
augroup END
|
||||||
if &modified
|
if &modified
|
||||||
# TODO: find existing window
|
# TODO: find existing window
|
||||||
exe $'split {fnameescape(fname)}'
|
exe $'split {fnameescape(fremote)}'
|
||||||
sourcewin = win_getid()
|
sourcewin = win_getid()
|
||||||
InstallWinbar(false)
|
InstallWinbar(false)
|
||||||
else
|
else
|
||||||
exe $'edit {fnameescape(fname)}'
|
exe $'edit {fnameescape(fremote)}'
|
||||||
endif
|
endif
|
||||||
augroup Termdebug
|
augroup Termdebug
|
||||||
au! SwapExists
|
au! SwapExists
|
||||||
@@ -1917,7 +2151,7 @@ def HandleCursor(msg: string)
|
|||||||
exe $":{lnum}"
|
exe $":{lnum}"
|
||||||
normal! zv
|
normal! zv
|
||||||
sign_unplace('TermDebug', {id: pc_id})
|
sign_unplace('TermDebug', {id: pc_id})
|
||||||
sign_place(pc_id, 'TermDebug', 'debugPC', fname,
|
sign_place(pc_id, 'TermDebug', 'debugPC', fremote,
|
||||||
{lnum: str2nr(lnum), priority: 110})
|
{lnum: str2nr(lnum), priority: 110})
|
||||||
if !exists('b:save_signcolumn')
|
if !exists('b:save_signcolumn')
|
||||||
b:save_signcolumn = &signcolumn
|
b:save_signcolumn = &signcolumn
|
||||||
@@ -1991,10 +2225,11 @@ def HandleNewBreakpoint(msg: string, modifiedFlag: bool)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
for mm in SplitMsg(msg)
|
for mm in SplitMsg(msg)
|
||||||
var fname = GetFullname(mm)
|
var fname = GetLocalFullname(mm)
|
||||||
if empty(fname)
|
if empty(fname)
|
||||||
continue
|
continue
|
||||||
endif
|
endif
|
||||||
|
var fremote = Local2RemotePath(fname)
|
||||||
nr = substitute(mm, '.*number="\([0-9.]*\)\".*', '\1', '')
|
nr = substitute(mm, '.*number="\([0-9.]*\)\".*', '\1', '')
|
||||||
if empty(nr)
|
if empty(nr)
|
||||||
return
|
return
|
||||||
@@ -2034,11 +2269,11 @@ def HandleNewBreakpoint(msg: string, modifiedFlag: bool)
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
var posMsg = ''
|
var posMsg = ''
|
||||||
if bufloaded(fname)
|
if bufloaded(fremote)
|
||||||
PlaceSign(id, subid, entry)
|
PlaceSign(id, subid, entry)
|
||||||
posMsg = $' at line {lnum}.'
|
posMsg = $' at line {lnum}.'
|
||||||
else
|
else
|
||||||
posMsg = $' in {fname} at line {lnum}.'
|
posMsg = $' in {fremote} at line {lnum}.'
|
||||||
endif
|
endif
|
||||||
var actionTaken = ''
|
var actionTaken = ''
|
||||||
if !modifiedFlag
|
if !modifiedFlag
|
||||||
@@ -2055,8 +2290,9 @@ enddef
|
|||||||
|
|
||||||
def PlaceSign(id: number, subid: number, entry: dict<any>)
|
def PlaceSign(id: number, subid: number, entry: dict<any>)
|
||||||
var nr = printf('%d.%d', id, subid)
|
var nr = printf('%d.%d', id, subid)
|
||||||
|
var remote = Local2RemotePath(entry['fname'])
|
||||||
sign_place(Breakpoint2SignNumber(id, subid), 'TermDebug',
|
sign_place(Breakpoint2SignNumber(id, subid), 'TermDebug',
|
||||||
$'debugBreakpoint{nr}', entry['fname'],
|
$'debugBreakpoint{nr}', remote,
|
||||||
{lnum: entry['lnum'], priority: 110})
|
{lnum: entry['lnum'], priority: 110})
|
||||||
entry['placed'] = 1
|
entry['placed'] = 1
|
||||||
enddef
|
enddef
|
||||||
|
@@ -689,4 +689,86 @@ func Test_termdebug_toggle_break()
|
|||||||
%bw!
|
%bw!
|
||||||
endfunc
|
endfunc
|
||||||
|
|
||||||
|
" Check substitution capabilities and simulate remote debugging
|
||||||
|
func Test_termdebug_remote_basic()
|
||||||
|
let bin_name = 'XTD_basicremote'
|
||||||
|
let src_name = bin_name .. '.c'
|
||||||
|
call s:generate_files(bin_name)
|
||||||
|
defer s:cleanup_files(bin_name)
|
||||||
|
|
||||||
|
" Duplicate sources to test the mapping
|
||||||
|
const pwd = getcwd()
|
||||||
|
const src_shadow_dir = "shadow"
|
||||||
|
call mkdir(src_shadow_dir)
|
||||||
|
const src_shadow_file = $"{src_shadow_dir}/{src_name}"
|
||||||
|
call filecopy(src_name, src_shadow_file)
|
||||||
|
defer delete(src_shadow_dir, 'rf')
|
||||||
|
|
||||||
|
let modes = [v:true]
|
||||||
|
" termdebug only wokrs fine if socat is available on the remote machine
|
||||||
|
" otherwise the communication pty will be unstable
|
||||||
|
if executable('socat')
|
||||||
|
let modes += [v:false]
|
||||||
|
endif
|
||||||
|
|
||||||
|
for use_prompt in modes
|
||||||
|
" Set up mock remote and mapping
|
||||||
|
let g:termdebug_config = {}
|
||||||
|
|
||||||
|
let g:termdebug_config['use_prompt'] = use_prompt
|
||||||
|
" favor socat if available
|
||||||
|
if executable('socat')
|
||||||
|
let g:termdebug_config['remote_window'] =
|
||||||
|
\ ['socat', '-d', '-d', '-', 'PTY,raw,echo=0']
|
||||||
|
else
|
||||||
|
let g:termdebug_config['remote_window'] = ['sh']
|
||||||
|
endif
|
||||||
|
|
||||||
|
let g:termdebug_config['substitute_path'] = {}
|
||||||
|
let g:termdebug_config['substitute_path'][pwd] = pwd . '/' . src_shadow_dir
|
||||||
|
defer execute("unlet g:termdebug_config")
|
||||||
|
|
||||||
|
" Launch the debugger and set breakpoints in the shadow file instead
|
||||||
|
exe $"edit {src_shadow_file}"
|
||||||
|
exe $"Termdebug ./{bin_name}"
|
||||||
|
call WaitForAssert({-> assert_true(get(g:, "termdebug_is_running", v:false))})
|
||||||
|
call WaitForAssert({-> assert_equal(3, winnr('$'))})
|
||||||
|
let gdb_buf = winbufnr(1)
|
||||||
|
wincmd b
|
||||||
|
Break 9
|
||||||
|
sleep 100m
|
||||||
|
redraw!
|
||||||
|
call assert_equal([
|
||||||
|
\ {'lnum': 9, 'id': 1014, 'name': 'debugBreakpoint1.0',
|
||||||
|
\ 'priority': 110, 'group': 'TermDebug'}],
|
||||||
|
\ sign_getplaced('', #{group: 'TermDebug'})[0].signs)
|
||||||
|
Run
|
||||||
|
call term_wait(gdb_buf, 400)
|
||||||
|
redraw!
|
||||||
|
call WaitForAssert({-> assert_equal([
|
||||||
|
\ {'lnum': 9, 'id': 12, 'name': 'debugPC', 'priority': 110,
|
||||||
|
\ 'group': 'TermDebug'},
|
||||||
|
\ {'lnum': 9, 'id': 1014, 'name': 'debugBreakpoint1.0',
|
||||||
|
\ 'priority': 110, 'group': 'TermDebug'}],
|
||||||
|
\ sign_getplaced('', #{group: 'TermDebug'})[0].signs)})
|
||||||
|
Finish
|
||||||
|
call term_wait(gdb_buf)
|
||||||
|
redraw!
|
||||||
|
call WaitForAssert({-> assert_equal([
|
||||||
|
\ {'lnum': 9, 'id': 1014, 'name': 'debugBreakpoint1.0',
|
||||||
|
\ 'priority': 110, 'group': 'TermDebug'},
|
||||||
|
\ {'lnum': 20, 'id': 12, 'name': 'debugPC',
|
||||||
|
\ 'priority': 110, 'group': 'TermDebug'}],
|
||||||
|
\ sign_getplaced('', #{group: 'TermDebug'})[0].signs)})
|
||||||
|
|
||||||
|
" Cleanup, make sure the gdb job is terminated before return
|
||||||
|
" otherwise may interfere with next test
|
||||||
|
Gdb
|
||||||
|
bw!
|
||||||
|
call WaitForAssert({-> assert_equal(1, winnr('$'))})
|
||||||
|
endfor
|
||||||
|
|
||||||
|
%bw!
|
||||||
|
endfunc
|
||||||
|
|
||||||
" vim: shiftwidth=2 sts=2 expandtab
|
" vim: shiftwidth=2 sts=2 expandtab
|
||||||
|
Reference in New Issue
Block a user