]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0143: termdebug: no support for thread and condition in :Break v9.2.0143
authorYinzuo Jiang <jiangyinzuo@foxmail.com>
Thu, 12 Mar 2026 18:28:34 +0000 (18:28 +0000)
committerChristian Brabandt <cb@256bit.org>
Thu, 12 Mar 2026 18:34:37 +0000 (18:34 +0000)
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 <jiangyinzuo@foxmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
runtime/doc/terminal.txt
runtime/pack/dist/opt/termdebug/plugin/termdebug.vim
src/testdir/test_plugin_termdebug.vim
src/version.c

index 4a2c4710cd8e2750e274da8ea5d4a78962d2f35b..b46899ceae571b6ca4f9ba4b3c33a1115db1d55f 100644 (file)
@@ -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
index 6627bf4b4fd6a79b61d20816d57e6ebbf3586d5c..3ac5c71d5b9b899a8d07dfd1c92fc672b9f491a0 100644 (file)
@@ -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.tiberi@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()
index deedffe8f7f4cc0ae243faeb3e4751108ca17961..f6518064835d3f917223e97b385e433f8649154d 100644 (file)
@@ -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('<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()
index 8121a008de1b2eafbfbee4029d27430ea3beeda6..4b6e592b33bb2469413589af3fd3b7da53b0cfc0 100644 (file)
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    143,
 /**/
     142,
 /**/