Repository: microsoft/ptvsd

Issue:
Send variable details so variable window doesn't collapse them while stepping
Microsoft/vscode-python#645


Discussion:
ptvsd has no information about the state of the variable window. There is no debug adapter message support for such a thing. This does not feel like a issue that can be fixed on the python side of things.
@karthiknadig from https://github.com/Microsoft/vscode/issues/32199#issuecomment-321597173, it seems that this is really from ptvsd... I haven't checked, but I know ptvsd does some translations back and forth from the `pydevd` structure to what the debug adapter expects -- is it possible that new ids are being generated instead of keeping an existing id in place for the frame or variables?
@fabioz That is a good suggestion. We do update the ids for frames and variables. That is something we can fix.
I am converting this to a tacking bug. We need to make two separate set of changes, to get this working correctly.

Resolving patch:
diff --git a/ptvsd/wrapper.py b/ptvsd/wrapper.py
--- a/ptvsd/wrapper.py
+++ b/ptvsd/wrapper.py
@@ -1607,6 +1607,17 @@ def get_source_reference(self, filename):
 
         return self.source_map.to_vscode(filename, autogen=True)
 
+    def _cleanup_frames_and_variables(self, pyd_tid, preserve_frames=()):
+        """ Delete frames and variables for a given thread, except for the ones in preserve list.
+        """
+        for pyd_fid, vsc_fid in self.frame_map.pairs():
+            if pyd_fid[0] == pyd_tid and pyd_fid[1] not in preserve_frames:
+                self.frame_map.remove(pyd_fid, vsc_fid)
+
+        for pyd_var, vsc_var in self.var_map.pairs():
+            if pyd_var[0] == pyd_tid and pyd_fid[1] not in preserve_frames:
+                self.var_map.remove(pyd_var, vsc_var)
+
     @async_handler
     def on_stackTrace(self, request, args):
         # TODO: docstring
@@ -1637,6 +1648,7 @@ def on_stackTrace(self, request, args):
             levels = totalFrames
 
         stackFrames = []
+        preserve_fids = []
         for xframe in xframes:
             if startFrame > 0:
                 startFrame -= 1
@@ -1646,7 +1658,9 @@ def on_stackTrace(self, request, args):
                 break
             levels -= 1
 
-            key = (pyd_tid, int(xframe['id']))
+            pyd_fid = int(xframe['id'])
+            preserve_fids.append(pyd_fid)
+            key = (pyd_tid, pyd_fid)
             fid = self.frame_map.to_vscode(key, autogen=True)
             name = unquote(xframe['name'])
             # pydevd encodes if necessary and then uses urllib.quote.
@@ -1681,6 +1695,8 @@ def on_stackTrace(self, request, args):
                 self._should_debug(path):
                 user_frames.append(frame)
 
+        self._cleanup_frames_and_variables(pyd_tid, preserve_fids)
+
         totalFrames = len(user_frames)
         self.send_response(request,
                            stackFrames=user_frames,
@@ -2410,6 +2426,17 @@ def on_pydevd_thread_create(self, seq, args):
     def on_pydevd_thread_kill(self, seq, args):
         # TODO: docstring
         pyd_tid = args.strip()
+
+        # All frames, and variables for
+        # this thread are now invalid; clear their IDs.
+        for pyd_fid, vsc_fid in self.frame_map.pairs():
+            if pyd_fid[0] == pyd_tid:
+                self.frame_map.remove(pyd_fid, vsc_fid)
+
+        for pyd_var, vsc_var in self.var_map.pairs():
+            if pyd_var[0] == pyd_tid:
+                self.var_map.remove(pyd_var, vsc_var)
+
         try:
             vsc_tid = self.thread_map.to_vscode(pyd_tid, autogen=False)
         except KeyError:
@@ -2497,16 +2524,6 @@ def on_pydevd_thread_run(self, seq, args):
         pyd_tid, _ = args.split('\t')
         pyd_tid = pyd_tid.strip()
 
-        # All frames, and variables for
-        # this thread are now invalid; clear their IDs.
-        for pyd_fid, vsc_fid in self.frame_map.pairs():
-            if pyd_fid[0] == pyd_tid:
-                self.frame_map.remove(pyd_fid, vsc_fid)
-
-        for pyd_var, vsc_var in self.var_map.pairs():
-            if pyd_var[0] == pyd_tid:
-                self.var_map.remove(pyd_var, vsc_var)
-
         try:
             vsc_tid = self.thread_map.to_vscode(pyd_tid, autogen=False)
         except KeyError:


Test patch:
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
@@ -784,7 +784,7 @@ def test_attach_breakpoints(self):
             self.new_response(req_stacktrace2.req, **{
                 'totalFrames': 1,
                 'stackFrames': [{
-                    'id': 2,  # TODO: Isn't this the same frame as before?
+                    'id': 1,
                     'name': '<module>',
                     'source': {
                         'path': filename,