Add "run to cursor" support

We add a 'temporary' option to line breakpionts and try and clear any
temporary breakpionts on the line we end up stopping on. This might not
be art, but _probably_ works in almost all cases that matter.

it's a bit hacky the way we have to push the reason around, but we don't
know where we stopped until we actually get the stack trace response and
SetCurrentFrame

Move temporary breakpionts to match server response

Also delete any existing ones when adding a new one and add tests for
run-to-cursor.

Only continue after we successfully set the breakpoints. This makes it
work in go
This commit is contained in:
Ben Jackson 2020-10-13 22:43:52 +01:00
commit 80985148e7
18 changed files with 693 additions and 205 deletions

View file

@ -192,13 +192,13 @@ class ProjectBreakpoints( object ):
self.UpdateUI()
def SetLineBreakpoint( self, file_name, line_num, options ):
def SetLineBreakpoint( self, file_name, line_num, options, then = None ):
bp, _ = self._FindLineBreakpoint( file_name, line_num )
if bp is not None:
bp[ 'options' ] = options
return
self._PutLineBreakpoint( file_name, line_num, options )
self.UpdateUI()
self.UpdateUI( then )
def ClearLineBreakpoint( self, file_name, line_num ):
@ -209,6 +209,51 @@ class ProjectBreakpoints( object ):
self.UpdateUI()
def ClearTemporaryBreakpoint( self, file_name, line_num ):
bp, index = self._FindLineBreakpoint( file_name, line_num )
if bp is None:
return
if bp[ 'options' ].get( 'temporary' ):
self._DeleteLineBreakpoint( bp, file_name, index )
self.UpdateUI()
def ClearTemporaryBreakpoints( self ):
for file_name, breakpoints in self._line_breakpoints.items():
self._line_breakpoints[ file_name ] = list( filter(
lambda bp: not bp[ 'options' ].get( 'temporary' ),
breakpoints ) )
def _UpdateTemporaryBreakpoints( self, breakpoints, temp_idxs ):
# adjust any temporary breakpoints to match the server result
# TODO: Maybe now is the time to ditch the split breakpoints nonesense
for temp_idx, user_bp in temp_idxs:
if temp_idx >= len( breakpoints ):
# Just can't trust servers ?
self._logger.debug( "Server Error - invalid breakpoints list did not "
"contain entry for temporary breakpoint at index "
f"{ temp_idx } i.e. { user_bp }" )
continue
bp = breakpoints[ temp_idx ]
if 'line' not in bp or not bp[ 'verified' ]:
utils.UserMessage(
"Unable to set temporary breakpoint at line "
f"{ user_bp[ 'line' ] } execution will continue...",
persist = True,
error = True )
self._logger.debug( f"Updating temporary breakpoint { user_bp } line "
f"{ user_bp[ 'line' ] } to { bp[ 'line' ] }" )
# if it was moved, update the user-breakpoint so that we unset it
# again properly
user_bp[ 'line' ] = bp[ 'line' ]
def AddFunctionBreakpoint( self, function, options ):
self._func_breakpoints.append( {
'state': 'ENABLED',
@ -224,11 +269,13 @@ class ProjectBreakpoints( object ):
self.UpdateUI()
def UpdateUI( self ):
def UpdateUI( self, then = None ):
if self._connection:
self.SendBreakpoints()
self.SendBreakpoints( then )
else:
self._ShowBreakpoints()
if then:
then()
def SetBreakpointsHandler( self, handler ):
@ -254,9 +301,12 @@ class ProjectBreakpoints( object ):
if awaiting == 0 and doneHandler:
doneHandler()
def response_handler( source, msg ):
def response_handler( source, msg, temp_idxs = [] ):
if msg:
self._breakpoints_handler.AddBreakpoints( source, msg )
breakpoints = ( msg.get( 'body' ) or {} ).get( 'breakpoints' ) or []
self._UpdateTemporaryBreakpoints( breakpoints, temp_idxs )
response_received()
@ -267,9 +317,9 @@ class ProjectBreakpoints( object ):
# TODO: add the _configured_breakpoints to line_breakpoints
# TODO: the line numbers might have changed since pressing the F9 key!
for file_name, line_breakpoints in self._line_breakpoints.items():
temp_idxs = []
breakpoints = []
for bp in line_breakpoints:
self._SignToLine( file_name, bp )
@ -283,8 +333,15 @@ class ProjectBreakpoints( object ):
dap_bp = {}
dap_bp.update( bp[ 'options' ] )
dap_bp.update( { 'line': bp[ 'line' ] } )
dap_bp.pop( 'temporary', None )
if bp[ 'options' ].get( 'temporary' ):
temp_idxs.append( [ len( breakpoints ), bp ] )
breakpoints.append( dap_bp )
source = {
'name': os.path.basename( file_name ),
'path': file_name,
@ -295,7 +352,10 @@ class ProjectBreakpoints( object ):
# The source=source here is critical to ensure that we capture each
# source in the iteration, rather than ending up passing the same source
# to each callback.
lambda msg, source=source: response_handler( source, msg ),
lambda msg, source=source, temp_idxs=temp_idxs: response_handler(
source,
msg,
temp_idxs = temp_idxs ),
{
'command': 'setBreakpoints',
'arguments': {
@ -396,6 +456,12 @@ class ProjectBreakpoints( object ):
# pay any attention to them anyway.
self._exception_breakpoints[ 'exceptionOptions' ] = []
def Refresh( self, file_name ):
# TODO: Just this file ?
self._ShowBreakpoints()
def _ShowBreakpoints( self ):
for file_name, line_breakpoints in self._line_breakpoints.items():
for bp in line_breakpoints:
@ -410,7 +476,7 @@ class ProjectBreakpoints( object ):
else 'vimspectorBPCond' if 'condition' in bp[ 'options' ]
else 'vimspectorBP' )
if utils.BufferNumberForFile( file_name, False ) > 0:
if utils.BufferExists( file_name ):
signs.PlaceSign( bp[ 'sign_id' ],
'VimspectorBP',
sign,
@ -422,7 +488,7 @@ class ProjectBreakpoints( object ):
if 'sign_id' not in bp:
return bp[ 'line' ]
if utils.BufferNumberForFile( file_name, False ) <= 0:
if not utils.BufferExists( file_name ):
return bp[ 'line' ]
signs = vim.eval( "sign_getplaced( '{}', {} )".format(

View file

@ -93,16 +93,12 @@ class CodeView( object ):
sign = 'vimspectorPCBP'
break
try:
if utils.BufferExists( frame[ 'source' ][ 'path' ] ):
signs.PlaceSign( self._signs[ 'vimspectorPC' ],
'VimspectorCode',
sign,
frame[ 'source' ][ 'path' ],
frame[ 'line' ] )
except vim.error as e:
# Ignore 'invalid buffer name'
if 'E158' not in str( e ):
raise
def SetCurrentFrame( self, frame ):
@ -215,6 +211,11 @@ class CodeView( object ):
return
def Refresh( self, file_name ):
# TODO: jsut the file ?
self.ShowBreakpoints()
def _UndisplaySigns( self ):
for sign_id in self._signs[ 'breakpoints' ]:
signs.UnplaceSign( sign_id, 'VimspectorCode' )
@ -236,12 +237,13 @@ class CodeView( object ):
sign_id = self._next_sign_id
self._next_sign_id += 1
self._signs[ 'breakpoints' ].append( sign_id )
signs.PlaceSign( sign_id,
'VimspectorCode',
'vimspectorBP' if breakpoint[ 'verified' ]
else 'vimspectorBPDisabled',
file_name,
breakpoint[ 'line' ] )
if utils.BufferExists( file_name ):
signs.PlaceSign( sign_id,
'VimspectorCode',
'vimspectorBP' if breakpoint[ 'verified' ]
else 'vimspectorBPDisabled',
file_name,
breakpoint[ 'line' ] )
# We need to also check if there's a breakpoint on this PC line and chnge
# the PC

View file

@ -77,6 +77,7 @@ class DebugSession( object ):
self._launch_complete = False
self._on_init_complete_handlers = []
self._server_capabilities = {}
self.ClearTemporaryBreakpoints()
def Start( self, launch_variables = None ):
# We mutate launch_variables, so don't mutate the default argument.
@ -560,6 +561,13 @@ class DebugSession( object ):
return response[ 'body' ][ 'targets' ]
def RefreshSigns( self, file_name ):
if self._connection:
self._codeView.Refresh( file_name )
else:
self._breakpoints.Refresh( file_name )
def _SetUpUI( self ):
vim.command( 'tab split' )
self._uiTab = vim.current.tabpage
@ -622,7 +630,7 @@ class DebugSession( object ):
self.SetCurrentFrame( None )
@RequiresUI()
def SetCurrentFrame( self, frame ):
def SetCurrentFrame( self, frame, reason = '' ):
if not frame:
self._stackTraceView.Clear()
self._variablesView.Clear()
@ -630,11 +638,16 @@ class DebugSession( object ):
if not self._codeView.SetCurrentFrame( frame ):
return False
if frame:
self._variablesView.SetSyntax( self._codeView.current_syntax )
self._stackTraceView.SetSyntax( self._codeView.current_syntax )
self._variablesView.LoadScopes( frame )
self._variablesView.EvaluateWatches()
# the codeView.SetCurrentFrame already checked the frame was valid and
# countained a valid source
self._variablesView.SetSyntax( self._codeView.current_syntax )
self._stackTraceView.SetSyntax( self._codeView.current_syntax )
self._variablesView.LoadScopes( frame )
self._variablesView.EvaluateWatches()
if reason == 'stopped':
self._breakpoints.ClearTemporaryBreakpoint( frame[ 'source' ][ 'path' ],
frame[ 'line' ] )
return True
@ -1165,8 +1178,22 @@ class DebugSession( object ):
def ToggleBreakpoint( self, options ):
return self._breakpoints.ToggleBreakpoint( options )
def SetLineBreakpoint( self, file_name, line_num, options ):
return self._breakpoints.SetLineBreakpoint( file_name, line_num, options )
def RunTo( self, file_name, line ):
self.ClearTemporaryBreakpoints()
self.SetLineBreakpoint( file_name,
line,
{ 'temporary': True },
lambda: self.Continue() )
def ClearTemporaryBreakpoints( self ):
return self._breakpoints.ClearTemporaryBreakpoints()
def SetLineBreakpoint( self, file_name, line_num, options, then = None ):
return self._breakpoints.SetLineBreakpoint( file_name,
line_num,
options,
then )
def ClearLineBreakpoint( self, file_name, line_num ):
return self._breakpoints.ClearLineBreakpoint( file_name, line_num )

View file

@ -149,12 +149,15 @@ class StackTraceView( object ):
self._DrawStackTrace( thread )
def _LoadStackTrace( self, thread, infer_current_frame ):
def _LoadStackTrace( self,
thread,
infer_current_frame,
reason = '' ):
def consume_stacktrace( message ):
thread[ '_frames' ] = message[ 'body' ][ 'stackFrames' ]
if infer_current_frame:
for frame in thread[ '_frames' ]:
if self._JumpToFrame( frame ):
if self._JumpToFrame( frame, reason ):
break
self._DrawThreads()
@ -183,11 +186,11 @@ class StackTraceView( object ):
else:
self._LoadStackTrace( thread, False )
def _JumpToFrame( self, frame ):
def _JumpToFrame( self, frame, reason = '' ):
def do_jump():
if 'line' in frame and frame[ 'line' ] > 0:
self._current_frame = frame
return self._session.SetCurrentFrame( self._current_frame )
return self._session.SetCurrentFrame( self._current_frame, reason )
return False
source = frame.get( 'source' ) or {}
@ -211,7 +214,7 @@ class StackTraceView( object ):
if self._current_thread is not None:
for thread in self._threads:
if thread[ 'id' ] == self._current_thread:
self._LoadStackTrace( thread, True )
self._LoadStackTrace( thread, True, 'stopped' )
return
self.LoadThreads( True )

View file

@ -55,6 +55,10 @@ def BufferForFile( file_name ):
return vim.buffers[ BufferNumberForFile( file_name ) ]
def BufferExists( file_name ):
return bool( int ( vim.eval( f"bufexists( '{ Escape( file_name ) }' )" ) ) )
def NewEmptyBuffer():
bufnr = int( vim.eval( 'bufadd("")' ) )
Call( 'bufload', bufnr )