Repository: microsoft/ptvsd

Issue:
Thread stopped event should set preserveFocusHint
`preserveFocusHint=False` for step, break point, and exception. `preserveFocusHint=True` for all others. If this is not set then VSC and VS wont know which thread to show to the user, in multi threaded scenario.




Discussion:

Resolving patch:
diff --git a/ptvsd/wrapper.py b/ptvsd/wrapper.py
--- a/ptvsd/wrapper.py
+++ b/ptvsd/wrapper.py
@@ -2422,6 +2422,7 @@ def on_pydevd_thread_suspend(self, seq, args):
 
         exc_desc = None
         exc_name = None
+        extra = {}
         if reason in STEP_REASONS:
             reason = 'step'
         elif reason in EXCEPTION_REASONS:
@@ -2431,6 +2432,9 @@ def on_pydevd_thread_suspend(self, seq, args):
         else:
             reason = 'pause'
 
+        extra['preserveFocusHint'] = \
+            reason not in ['step', 'exception', 'breakpoint']
+
         if reason == 'exception':
             cmdid = pydevd_comm.CMD_GET_EXCEPTION_DETAILS
             _, _, resp_args = yield self.pydevd_request(cmdid, pyd_tid)
@@ -2442,7 +2446,8 @@ def on_pydevd_thread_suspend(self, seq, args):
             reason=reason,
             threadId=vsc_tid,
             text=exc_name,
-            description=exc_desc)
+            description=exc_desc,
+            **extra)
 
     @pydevd_events.handler(pydevd_comm.CMD_THREAD_RUN)
     def on_pydevd_thread_run(self, seq, args):


Test patch:
diff --git a/tests/highlevel/test_live_pydevd.py b/tests/highlevel/test_live_pydevd.py
--- a/tests/highlevel/test_live_pydevd.py
+++ b/tests/highlevel/test_live_pydevd.py
@@ -336,6 +336,7 @@ def test_breakpoints_single_file(self):
                            threadId=tid,
                            text=None,
                            description=None,
+                           preserveFocusHint=False,
                            ),
             self.new_response(req_continue1, allThreadsContinued=True),
             self.new_event('continued', threadId=tid),
@@ -344,6 +345,7 @@ def test_breakpoints_single_file(self):
                            threadId=tid,
                            text=None,
                            description=None,
+                           preserveFocusHint=False,
                            ),
             self.new_response(req_continue2, allThreadsContinued=True),
             self.new_event('continued', threadId=tid),
@@ -352,6 +354,7 @@ def test_breakpoints_single_file(self):
                            threadId=tid,
                            text=None,
                            description=None,
+                           preserveFocusHint=False,
                            ),
             self.new_response(req_continue_last, allThreadsContinued=True),
             self.new_event('continued', threadId=tid),
@@ -396,7 +399,8 @@ def test_exception_breakpoints(self):
                  reason='exception',
                  threadId=tid,
                  text='__main__.MyError',
-                 description='ka-boom')),
+                 description='ka-boom',
+                 preserveFocusHint=False)),
             self.new_response(req_continue_last, allThreadsContinued=True),
             self.new_event('continued', **dict(threadId=tid, )),
         ])
diff --git a/tests/highlevel/test_messages.py b/tests/highlevel/test_messages.py
--- a/tests/highlevel/test_messages.py
+++ b/tests/highlevel/test_messages.py
@@ -2590,6 +2590,7 @@ def test_step_into(self):
                 threadId=tid,
                 text=None,
                 description=None,
+                preserveFocusHint=False,
             ),
         ])
         self.assert_received(self.debugger, [])
@@ -2607,6 +2608,7 @@ def test_step_over(self):
                 threadId=tid,
                 text=None,
                 description=None,
+                preserveFocusHint=False,
             ),
         ])
         self.assert_received(self.debugger, [])
@@ -2624,6 +2626,7 @@ def test_step_return(self):
                 threadId=tid,
                 text=None,
                 description=None,
+                preserveFocusHint=False,
             ),
         ])
         self.assert_received(self.debugger, [])
@@ -2648,6 +2651,7 @@ def test_caught_exception(self):
                 threadId=tid,
                 text='RuntimeError',
                 description='something went wrong',
+                preserveFocusHint=False,
             ),
         ])
         self.assert_received(self.debugger, [
@@ -2676,6 +2680,7 @@ def test_exception_break(self):
                 threadId=tid,
                 text='RuntimeError',
                 description='something went wrong',
+                preserveFocusHint=False,
             ),
         ])
         self.assert_received(self.debugger, [
@@ -2697,6 +2702,7 @@ def test_suspend(self):
                 threadId=tid,
                 text=None,
                 description=None,
+                preserveFocusHint=True,
             ),
         ])
         self.assert_received(self.debugger, [])
@@ -2715,6 +2721,7 @@ def test_unknown_reason(self):
                 threadId=tid,
                 text=None,
                 description=None,
+                preserveFocusHint=True,
             ),
         ])
         self.assert_received(self.debugger, [])
@@ -2734,6 +2741,7 @@ def test_no_reason(self):
                 threadId=tid,
                 text=None,
                 description=None,
+                preserveFocusHint=True,
             ),
         ])
         self.assert_received(self.debugger, [])
@@ -2754,6 +2762,7 @@ def test_str_reason(self):
                 threadId=tid,
                 text=None,
                 description=None,
+                preserveFocusHint=True,
             ),
         ])
         self.assert_received(self.debugger, [])
diff --git a/tests/system_tests/test_breakpoints.py b/tests/system_tests/test_breakpoints.py
--- a/tests/system_tests/test_breakpoints.py
+++ b/tests/system_tests/test_breakpoints.py
@@ -82,6 +82,7 @@ def run_test_with_break_points(self, debug_info, bp_filename, bp_line):
                 threadId=tid,
                 text=None,
                 description=None,
+                preserveFocusHint=False,
             ),
             self.new_event('continued', threadId=tid),
             self.new_event('output', category='stdout', output='yes'),
diff --git a/tests/system_tests/test_main.py b/tests/system_tests/test_main.py
--- a/tests/system_tests/test_main.py
+++ b/tests/system_tests/test_main.py
@@ -468,6 +468,7 @@ def test_detach_clear_and_resume(self):
                 reason='breakpoint',
                 description=None,
                 text=None,
+                preserveFocusHint=False,
             ),
             self.new_response(req_threads2.req, **{
                 'threads': [{
@@ -726,6 +727,7 @@ def test_attach_breakpoints(self):
                 reason='breakpoint',
                 description=None,
                 text=None,
+                preserveFocusHint=False,
             ),
             self.new_response(req_threads2.req, **{
                 'threads': [{
@@ -771,6 +773,7 @@ def test_attach_breakpoints(self):
                 reason='breakpoint',
                 description=None,
                 text=None,
+                preserveFocusHint=False,
             ),
             self.new_response(req_threads3.req, **{
                 'threads': [{
diff --git a/tests/system_tests/test_web_frameworks.py b/tests/system_tests/test_web_frameworks.py
--- a/tests/system_tests/test_web_frameworks.py
+++ b/tests/system_tests/test_web_frameworks.py
@@ -172,6 +172,7 @@ def run_test_with_break_points(self, debug_info, **kwargs):
                 threadId=tid,
                 text=None,
                 description=None,
+                preserveFocusHint=False,
             ),
             self.new_event('continued', threadId=tid),
         ])