Repository: microsoft/ptvsd

Issue:
pydevd json support for gotoTargets and goto 



Discussion:

Resolving patch:
diff --git a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py
--- a/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py
+++ b/src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_process_net_command_json.py
@@ -6,11 +6,12 @@
 
 from _pydevd_bundle._debug_adapter import pydevd_base_schema
 from _pydevd_bundle._debug_adapter.pydevd_schema import (SourceBreakpoint, ScopesResponseBody, Scope,
-    VariablesResponseBody, SetVariableResponseBody, ModulesResponseBody, SourceResponseBody)
+    VariablesResponseBody, SetVariableResponseBody, ModulesResponseBody, SourceResponseBody,
+    GotoTargetsResponseBody)
 from _pydevd_bundle.pydevd_api import PyDevdAPI
 from _pydevd_bundle.pydevd_comm_constants import (
     CMD_RETURN, CMD_STEP_OVER_MY_CODE, CMD_STEP_OVER, CMD_STEP_INTO_MY_CODE,
-    CMD_STEP_INTO, CMD_STEP_RETURN_MY_CODE, CMD_STEP_RETURN)
+    CMD_STEP_INTO, CMD_STEP_RETURN_MY_CODE, CMD_STEP_RETURN, CMD_SET_NEXT_STATEMENT)
 from _pydevd_bundle.pydevd_filtering import ExcludeFilter
 from _pydevd_bundle.pydevd_json_debug_options import _extract_debug_options
 from _pydevd_bundle.pydevd_net_command import NetCommand
@@ -78,6 +79,25 @@ def _convert_rules_to_exclude_filters(rules, filename_to_server, on_error):
     return exclude_filters
 
 
+class IDMap(object):
+    def __init__(self):
+        self._value_to_key = {}
+        self._key_to_value = {}
+        self._next_id = partial(next, itertools.count(0))
+
+    def obtain_value(self, key):
+        return self._key_to_value[key]
+
+    def obtain_key(self, value):
+        try:
+            key = self._value_to_key[value]
+        except KeyError:
+            key = self._next_id()
+            self._key_to_value[key] = value
+            self._value_to_key[value] = key
+        return key
+
+
 class _PyDevJsonCommandProcessor(object):
 
     def __init__(self, from_json):
@@ -85,6 +105,7 @@ def __init__(self, from_json):
         self.api = PyDevdAPI()
         self._debug_options = {}
         self._next_breakpoint_id = partial(next, itertools.count(0))
+        self._goto_targets_map = IDMap()
 
     def process_net_command_json(self, py_db, json_contents):
         '''
@@ -514,5 +535,36 @@ def on_source_request(self, py_db, request):
         response = pydevd_base_schema.build_response(request, kwargs=response_args)
         return NetCommand(CMD_RETURN, 0, response, is_json=True)
 
+    def on_gototargets_request(self, py_db, request):
+        path = request.arguments.source.path
+        line = request.arguments.line
+        target_id = self._goto_targets_map.obtain_key((path, line))
+        target = {
+            'id': target_id,
+            'label': '{}:{}'.format(path, line),
+            'line': line
+        }
+        body = GotoTargetsResponseBody(targets=[target])
+        response_args = {'body': body}
+        response = pydevd_base_schema.build_response(request, kwargs=response_args)
+        return NetCommand(CMD_RETURN, 0, response, is_json=True)
+
+    def on_goto_request(self, py_db, request):
+        target_id = int(request.arguments.targetId)
+        thread_id = request.arguments.threadId
+        try:
+            _, line = self._goto_targets_map.obtain_value(target_id)
+        except KeyError:
+            response = pydevd_base_schema.build_response(request,
+                kwargs={
+                    'body': {},
+                    'success': False,
+                    'message': 'Unknown goto target id: %d' % (target_id,),
+                })
+            return NetCommand(CMD_RETURN, 0, response, is_json=True)
+
+        self.api.request_set_next(py_db, thread_id, CMD_SET_NEXT_STATEMENT, line, '*')
+        response = pydevd_base_schema.build_response(request, kwargs={'body': {}})
+        return NetCommand(CMD_RETURN, 0, response, is_json=True)
 
 process_net_command_json = _PyDevJsonCommandProcessor(pydevd_base_schema.from_json).process_net_command_json
diff --git a/src/ptvsd/wrapper.py b/src/ptvsd/wrapper.py
--- a/src/ptvsd/wrapper.py
+++ b/src/ptvsd/wrapper.py
@@ -1232,7 +1232,6 @@ def __init__(
         self.new_thread_lock = threading.Lock()
 
         # goto
-        self.goto_target_map = IDMap()
         self.current_goto_request = None
 
         # adapter state
@@ -1756,14 +1755,12 @@ def on_stepOut(self, request, args):
 
     @async_handler
     def on_gotoTargets(self, request, args):
-        path = args['source']['path']
-        line = args['line']
-        target_id = self.goto_target_map.to_vscode((path, line), autogen=True)
-        self.send_response(request, targets=[{
-            'id': target_id,
-            'label': '{}:{}'.format(path, line),
-            'line': line,
-        }])
+        pydevd_request = copy.deepcopy(request)
+        del pydevd_request['seq']  # A new seq should be created for pydevd.
+        cmd_id = pydevd_comm.CMD_GET_NEXT_STATEMENT_TARGETS
+        _, _, resp_args = yield self.pydevd_request(cmd_id, pydevd_request, is_json=True)
+
+        self.send_response(request, targets=resp_args['body']['targets'])
 
     @async_handler
     def on_goto(self, request, args):
@@ -1771,16 +1768,16 @@ def on_goto(self, request, args):
             self.send_error_response(request, 'Already processing a "goto" request.')
             return
 
-        vsc_tid = args['threadId']
-        target_id = args['targetId']
-
-        pyd_tid = self.thread_map.to_pydevd(vsc_tid)
-        path, line = self.goto_target_map.to_pydevd(target_id)
+        pyd_tid = self.thread_map.to_pydevd(int(args['threadId']))
+        pydevd_request = copy.deepcopy(request)
+        del pydevd_request['seq']  # A new seq should be created for pydevd.
+        pydevd_request['arguments']['threadId'] = pyd_tid
 
         self.current_goto_request = request
-        self.pydevd_notify(
-            pydevd_comm.CMD_SET_NEXT_STATEMENT,
-            '{}\t{}\t*'.format(pyd_tid, line))
+        cmd_id = pydevd_comm.CMD_SET_NEXT_STATEMENT
+        yield self.pydevd_request(cmd_id, pydevd_request, is_json=True)
+        # response for this is received via set_next_statement event
+        # see on_pydevd_set_next_statement below
 
     @async_handler
     def on_setBreakpoints(self, request, args):


Test patch:
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_set_next_statement.py b/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_set_next_statement.py
--- a/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_set_next_statement.py
+++ b/src/ptvsd/_vendored/pydevd/tests_python/resources/_debugger_case_set_next_statement.py
@@ -1,9 +1,9 @@
 def method():
-    a = 1
+    a = 1 # Step here
     print('call %s' % (a,))
     a = 2
     print('call %s' % (a,))
-    a = 3
+    a = 3 # Break here
 
 if __name__ == '__main__':
     method()
diff --git a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
--- a/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
+++ b/src/ptvsd/_vendored/pydevd/tests_python/test_debugger_json.py
@@ -1021,6 +1021,63 @@ def test_exception_details(case_setup):
 
         writer.finished_ok = True
 
+
+def test_goto(case_setup):
+    with case_setup.test_file('_debugger_case_set_next_statement.py') as writer:
+        json_facade = JsonFacade(writer)
+
+        writer.write_set_protocol('http_json')
+        break_line = writer.get_line_index_with_content('Break here')
+        step_line = writer.get_line_index_with_content('Step here')
+        writer.write_add_breakpoint(break_line)
+
+        json_facade.write_make_initial_run()
+
+        hit = writer.wait_for_breakpoint_hit()
+
+        stack_trace_request = json_facade.write_request(
+            pydevd_schema.StackTraceRequest(pydevd_schema.StackTraceArguments(threadId=hit.thread_id)))
+        stack_trace_response = json_facade.wait_for_response(stack_trace_request)
+        stack_frame = next(iter(stack_trace_response.body.stackFrames))
+        assert stack_frame['line'] == break_line
+
+        goto_targets_request = json_facade.write_request(
+            pydevd_schema.GotoTargetsRequest(pydevd_schema.GotoTargetsArguments(
+                source=pydevd_schema.Source(path=writer.TEST_FILE, sourceReference=0),
+                line=step_line)))
+        goto_targets_response = json_facade.wait_for_response(goto_targets_request)
+        target_id = goto_targets_response.body.targets[0]['id']
+
+        goto_request = json_facade.write_request(
+            pydevd_schema.GotoRequest(pydevd_schema.GotoArguments(
+                threadId=hit.thread_id,
+                targetId=12345)))
+        goto_response = json_facade.wait_for_response(goto_request)
+        assert not goto_response.success
+
+        goto_request = json_facade.write_request(
+            pydevd_schema.GotoRequest(pydevd_schema.GotoArguments(
+                threadId=hit.thread_id,
+                targetId=target_id)))
+        goto_response = json_facade.wait_for_response(goto_request)
+
+        hit = writer.wait_for_breakpoint_hit(reason='127')
+
+        stack_trace_request = json_facade.write_request(
+            pydevd_schema.StackTraceRequest(pydevd_schema.StackTraceArguments(threadId=hit.thread_id)))
+        stack_trace_response = json_facade.wait_for_response(stack_trace_request)
+        stack_frame = next(iter(stack_trace_response.body.stackFrames))
+        assert stack_frame['line'] == step_line
+
+        writer.write_run_thread(hit.thread_id)
+
+        # we hit the breakpoint again. Since we moved back
+        hit = writer.wait_for_breakpoint_hit()
+        writer.write_run_thread(hit.thread_id)
+
+        writer.finished_ok = True
+
+
 @pytest.mark.skipif(IS_JYTHON, reason='Flaky on Jython.')
 def test_path_translation_and_source_reference(case_setup):