vim-patch:9.2.0143: termdebug: no support for thread and condition in :Break

Problem:  termdebug :Break does not support `thread` and `if` arguments
Solution: extend :Break and :Tbreak to accept optional location, thread
          {nr}, and if {expr} arguments (Yinzuo Jiang).

closes: vim/vim#19613

5890ea5397

AI-assisted: Codex
This commit is contained in:
Yinzuo Jiang
2026-03-14 10:42:10 +08:00
committed by zeertzjq
parent 9d025e6a4d
commit 6dd634b025
3 changed files with 189 additions and 11 deletions

View File

@@ -398,11 +398,25 @@ gdb:
`:Arguments` {args} set arguments for the next `:Run`
*:Break* set a breakpoint at the cursor position
:Break {position}
:Break [{position}] [thread {nr}] [if {expr}]
set a breakpoint at the specified position
if {position} is omitted, use the current file and line
thread {nr} limits the breakpoint to one thread
if {expr} sets a conditional breakpoint
Examples: >
:Break if argc == 1
:Break 42 thread 3 if x > 10
:Break main
<
*:Tbreak* set a temporary breakpoint at the cursor position
:Tbreak {position}
set a temporary breakpoint at the specified position
:Tbreak [{position}] [thread {nr}] [if {expr}]
like `:Break`, but the breakpoint is deleted after
it is hit once
Examples: >
:Tbreak if argc == 1
:Tbreak 42 thread 3 if x > 10
:Tbreak main
<
*:Clear* delete the breakpoint at the cursor position
*:Step* execute the gdb "step" command

View File

@@ -2,7 +2,7 @@
"
" Author: Bram Moolenaar
" Copyright: Vim license applies, see ":help license"
" Last Change: 2025 Jul 08
" Last Change: 2026 Mar 11
"
" WORK IN PROGRESS - The basics works stable, more to come
" Note: In general you need at least GDB 7.12 because this provides the
@@ -1186,6 +1186,86 @@ func s:QuoteArg(x)
return printf('"%s"', a:x->substitute('[\\"]', '\\&', 'g'))
endfunc
func s:DefaultBreakpointLocation()
" Use the fname:lnum format, older gdb can't handle --source.
return s:QuoteArg($"{expand('%:p')}:{line('.')}")
endfunc
func s:TokenizeBreakpointArguments(args)
let tokens = []
let start = -1
let escaped = v:false
let in_quotes = v:false
let i = 0
for ch in a:args
if start < 0 && ch !~# '\s'
let start = i
endif
if start >= 0
if escaped
let escaped = v:false
elseif ch ==# '\'
let escaped = v:true
elseif ch ==# '"'
let in_quotes = !in_quotes
elseif !in_quotes && ch =~# '\s'
eval tokens->add(#{text: a:args->slice(start, i), start: start, end: i - 1})
let start = -1
endif
endif
let i += 1
endfor
if start >= 0
eval tokens->add(#{text: a:args->slice(start), start: start, end: i - 1})
endif
return tokens
endfunc
func s:BuildBreakpointCommand(at, tbreak)
let args = trim(a:at)
let cmd = '-break-insert'
if a:tbreak
let cmd ..= ' -t'
endif
if empty(args)
return $'{cmd} {s:DefaultBreakpointLocation()}'
endif
let condition = ''
let prefix = args
for token in s:TokenizeBreakpointArguments(args)
if token.text ==# 'if' && token.end < strchars(args) - 1
let condition = trim(args->slice(token.end + 1))
let prefix = token.start > 0 ? trim(args->slice(0, token.start)) : ''
break
endif
endfor
let prefix_tokens = s:TokenizeBreakpointArguments(prefix)
let location = prefix
let thread = ''
if len(prefix_tokens) >= 2
\ && prefix_tokens[-2].text ==# 'thread'
\ && prefix_tokens[-1].text =~# '^\d\+$'
let thread = prefix_tokens[-1].text
let location = join(prefix_tokens[: -3]->mapnew('v:val.text'), ' ')
endif
if empty(trim(location))
let location = s:DefaultBreakpointLocation()
endif
if !empty(thread)
let cmd ..= $' -p {thread}'
endif
if !empty(condition)
let cmd ..= $' -c {s:QuoteArg(condition)}'
endif
return $'{cmd} {trim(location)}'
endfunc
" :Until - Execute until past a specified position or current line
func s:Until(at)
if s:stopped
@@ -1211,13 +1291,7 @@ func s:SetBreakpoint(at, tbreak=v:false)
sleep 10m
endif
" Use the fname:lnum format, older gdb can't handle --source.
let at = empty(a:at) ? s:QuoteArg($"{expand('%:p')}:{line('.')}") : a:at
if a:tbreak
let cmd = $'-break-insert -t {at}'
else
let cmd = $'-break-insert {at}'
endif
let cmd = s:BuildBreakpointCommand(a:at, a:tbreak)
call s:SendCommand(cmd)
if do_continue
Continue

View File

@@ -77,6 +77,96 @@ endfunction
packadd termdebug
func s:GetTermdebugFunction(name)
for line in execute('scriptnames')->split("\n")
if line =~# 'termdebug/plugin/termdebug\.vim$'
let sid = matchstr(line, '^\s*\zs\d\+')
return function('<SNR>' .. sid .. '_' .. a:name)
endif
endfor
throw 'termdebug script not found'
endfunc
func Test_termdebug_break_command_builder()
let bin_name = 'XTD_break_cmd'
let src_name = bin_name .. '.c'
let BuildBreakpointCommand = s:GetTermdebugFunction('BuildBreakpointCommand')
call s:generate_files(bin_name)
execute 'edit ' .. src_name
call cursor(22, 1)
let here = '"' .. fnamemodify(src_name, ':p') .. ':22"'
call assert_equal('-break-insert ' .. here, BuildBreakpointCommand('', v:false))
call assert_equal('-break-insert -t ' .. here, BuildBreakpointCommand('', v:true))
call assert_equal('-break-insert -c "argc == 1" ' .. here,
\ BuildBreakpointCommand('if argc == 1', v:false))
call assert_equal('-break-insert -p 2 ' .. here,
\ BuildBreakpointCommand('thread 2', v:false))
call assert_equal('-break-insert -p 2 -c "argc == 1" ' .. here,
\ BuildBreakpointCommand('thread 2 if argc == 1', v:false))
call assert_equal('-break-insert -p 2 -c "argc == 1" 22',
\ BuildBreakpointCommand('22 thread 2 if argc == 1', v:false))
call assert_equal('-break-insert -c "argc == 1" 22',
\ BuildBreakpointCommand('22 if argc == 1', v:false))
call assert_equal('-break-insert -c "é == 1" 断点',
\ BuildBreakpointCommand('断点 if é == 1', v:false))
call assert_equal('-break-insert -p 2 断点',
\ BuildBreakpointCommand('断点 thread 2', v:false))
call assert_equal('-break-insert 断点 if',
\ BuildBreakpointCommand('断点 if', v:false))
call assert_equal('-break-insert 断点 thread 2 if',
\ BuildBreakpointCommand('断点 thread 2 if', v:false))
call assert_equal('-break-insert foo\ if\ bar',
\ BuildBreakpointCommand('foo\ if\ bar', v:false))
call s:cleanup_files(bin_name)
%bw!
endfunc
func Test_termdebug_break_with_default_location_and_condition()
let g:test_is_flaky = 1
let bin_name = 'XTD_break_if'
let src_name = bin_name .. '.c'
call s:generate_files(bin_name)
execute 'edit ' .. src_name
execute '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
call cursor(22, 1)
Break if argc == 1
call Nterm_wait(gdb_buf)
redraw!
call WaitForAssert({-> assert_equal([
\ {'lnum': 22, 'id': 1014, 'name': 'debugBreakpoint1.0',
\ 'priority': 110, 'group': 'TermDebug'}],
\ sign_getplaced('', #{group: 'TermDebug'})[0].signs)})
Run
call Nterm_wait(gdb_buf, 400)
redraw!
call WaitForAssert({-> assert_equal([
\ {'lnum': 22, 'id': 12, 'name': 'debugPC', 'priority': 110,
\ 'group': 'TermDebug'},
\ {'lnum': 22, 'id': 1014, 'name': 'debugBreakpoint1.0',
\ 'priority': 110, 'group': 'TermDebug'}],
\ sign_getplaced('', #{group: 'TermDebug'})[0].signs->reverse())})
Continue
call Nterm_wait(gdb_buf)
wincmd t
quit!
redraw!
call WaitForAssert({-> assert_equal(1, winnr('$'))})
call s:cleanup_files(bin_name)
%bw!
endfunc
func Test_termdebug_basic()
let g:test_is_flaky = 1
let bin_name = 'XTD_basic'