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

0 views
Skip to first unread message

Christian Brabandt

unread,
Mar 12, 2026, 2:46:59 PM (11 days ago) Mar 12
to vim...@googlegroups.com
patch 9.2.0143: termdebug: no support for thread and condition in :Break

Commit: https://github.com/vim/vim/commit/5890ea5397743c60c3cdeb93f50239d012120823
Author: Yinzuo Jiang <jiang...@foxmail.com>
Date: Thu Mar 12 18:28:34 2026 +0000

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: #19613

Signed-off-by: Yinzuo Jiang <jiang...@foxmail.com>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/terminal.txt b/runtime/doc/terminal.txt
index 4a2c4710c..b46899cea 100644
--- a/runtime/doc/terminal.txt
+++ b/runtime/doc/terminal.txt
@@ -1,4 +1,4 @@
-*terminal.txt* For Vim version 9.2. Last change: 2026 Feb 24
+*terminal.txt* For Vim version 9.2. Last change: 2026 Mar 12


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -1427,11 +1427,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
*:ToggleBreak* set a breakpoint at the cursor position or delete all
breakpoints at the cursor position
diff --git a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
index 6627bf4b4..3ac5c71d5 100644
--- a/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
+++ b/runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
@@ -4,7 +4,7 @@ vim9script

# Author: Bram Moolenaar
# Copyright: Vim license applies, see ":help license"
-# Last Change: 2025 Dec 26
+# Last Change: 2026 Mar 11
# Converted to Vim9: Ubaldo Tiberi <ubaldo...@gmail.com>

# WORK IN PROGRESS - The basics works stable, more to come
@@ -1592,6 +1592,87 @@ def QuoteArg(x: string): string
return printf('"%s"', x ->substitute('[\"]', '\&', 'g'))
enddef

+def DefaultBreakpointLocation(): string
+ # Use the fname:lnum format, older gdb can't handle --source.
+ var fname = Remote2LocalPath(expand('%:p'))
+ return QuoteArg($"{fname}:{line('.')}")
+enddef
+
+def TokenizeBreakpointArguments(args: string): list<dict<any>>
+ var tokens: list<dict<any>> = []
+ var start = -1
+ var escaped = false
+ var in_quotes = false
+
+ var i = 0
+ for ch in args
+ if start < 0 && ch !~ '\s'
+ start = i
+ endif
+ if start >= 0
+ if escaped
+ escaped = false
+ elseif ch == '\'
+ escaped = true
+ elseif ch == '"'
+ in_quotes = !in_quotes
+ elseif !in_quotes && ch =~ '\s'
+ tokens->add({text: args[start : i - 1], start: start, end: i - 1})
+ start = -1
+ endif
+ endif
+ i += 1
+ endfor
+
+ if start >= 0
+ tokens->add({text: args[start :], start: start, end: i - 1})
+ endif
+ return tokens
+enddef
+
+def BuildBreakpointCommand(at: string, tbreak=false): string
+ var args = trim(at)
+ var cmd = '-break-insert'
+ if tbreak
+ cmd ..= ' -t'
+ endif
+
+ if empty(args)
+ return $'{cmd} {DefaultBreakpointLocation()}'
+ endif
+
+ var condition = ''
+ var prefix = args
+ for token in TokenizeBreakpointArguments(args)
+ if token.text == 'if' && token.end < strchars(args) - 1
+ condition = trim(args[token.end + 1 :])
+ prefix = token.start > 0 ? trim(args[: token.start - 1]) : ''
+ break
+ endif
+ endfor
+
+ var prefix_tokens = TokenizeBreakpointArguments(prefix)
+ var location = prefix
+ var thread = ''
+ if len(prefix_tokens) >= 2
+ && prefix_tokens[-2].text == 'thread'
+ && prefix_tokens[-1].text =~ '^\d\+$'
+ thread = prefix_tokens[-1].text
+ location = join(prefix_tokens[: -3]->mapnew('v:val.text'), ' ')
+ endif
+
+ if empty(trim(location))
+ location = DefaultBreakpointLocation()
+ endif
+ if !empty(thread)
+ cmd ..= $' -p {thread}'
+ endif
+ if !empty(condition)
+ cmd ..= $' -c {QuoteArg(condition)}'
+ endif
+ return $'{cmd} {trim(location)}'
+enddef
+
# :Until - Execute until past a specified position or current line
def Until(at: string)

@@ -1620,15 +1701,7 @@ def SetBreakpoint(at: string, tbreak=false)
sleep 10m
endif

- # Use the fname:lnum format, older gdb can't handle --source.
- var fname = Remote2LocalPath(expand('%:p'))
- var AT = empty(at) ? QuoteArg($"{fname}:{line('.')}") : at
- var cmd = ''
- if tbreak
- cmd = $'-break-insert -t {AT}'
- else
- cmd = $'-break-insert {AT}'
- endif
+ var cmd = BuildBreakpointCommand(at, tbreak)
SendCommand(cmd)
if do_continue
ContinueCommand()
diff --git a/src/testdir/test_plugin_termdebug.vim b/src/testdir/test_plugin_termdebug.vim
index deedffe8f..f65180648 100644
--- a/src/testdir/test_plugin_termdebug.vim
+++ b/src/testdir/test_plugin_termdebug.vim
@@ -55,6 +55,96 @@ endfunction

packadd termdebug

+func s:GetTermdebugFunction(name)
+ for line in execute('scriptnames')->split("
")
+ 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 term_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 term_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)})
+
+ Continue
+ call term_wait(gdb_buf)
+ wincmd t
+ quit!
+ redraw!
+ call WaitForAssert({-> assert_equal(1, winnr('$'))})
+
+ call s:cleanup_files(bin_name)
+ %bw!
+endfunc
+
" should be the first test to run, since it validates the window layout with
" win ids
func Test_termdebug_basic()
diff --git a/src/version.c b/src/version.c
index 8121a008d..4b6e592b3 100644
--- a/src/version.c
+++ b/src/version.c
@@ -734,6 +734,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 143,
/**/
142,
/**/
Reply all
Reply to author
Forward
0 new messages