From: Yinzuo Jiang Date: Thu, 12 Mar 2026 18:28:34 +0000 (+0000) Subject: patch 9.2.0143: termdebug: no support for thread and condition in :Break X-Git-Tag: v9.2.0143^0 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5890ea5397743c60c3cdeb93f50239d012120823;p=thirdparty%2Fvim.git 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 Signed-off-by: Christian Brabandt --- diff --git a/runtime/doc/terminal.txt b/runtime/doc/terminal.txt index 4a2c4710cd..b46899ceae 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 6627bf4b4f..3ac5c71d5b 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 # 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> + var tokens: list> = [] + 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 deedffe8f7..f651806483 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("\n") + if line =~# 'termdebug/plugin/termdebug\.vim$' + let sid = matchstr(line, '^\s*\zs\d\+') + return function('' .. 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 8121a008de..4b6e592b33 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, /**/