Repository: microsoft/ptvsd

Issue:
Hexadecimal mode
We should respect hexadecimal mode for variable values in VS, as described here:

https://github.com/Microsoft/VSDebugAdapterHost/wiki/Stack-Trace-and-Value-Formatting


Discussion:

Resolving patch:
diff --git a/ptvsd/safe_repr.py b/ptvsd/safe_repr.py
--- a/ptvsd/safe_repr.py
+++ b/ptvsd/safe_repr.py
@@ -29,10 +29,12 @@ class SafeRepr(object):
         string_types = (str, bytes)
         set_info = (set, '{', '}', False)
         frozenset_info = (frozenset, 'frozenset({', '})', False)
+        int_types = (int,)
     else:
         string_types = (str, unicode)
         set_info = (set, 'set([', '])', False)
         frozenset_info = (frozenset, 'frozenset([', '])', False)
+        int_types = (int, long)
 
     # Collection types are recursively iterated for each limit in
     # maxcollection.
@@ -67,16 +69,16 @@ class SafeRepr(object):
     maxother_outer = 2 ** 16
     maxother_inner = 30
 
-    def __call__(self, obj):
+    def __call__(self, obj, convert_to_hex=False):
         try:
-            return ''.join(self._repr(obj, 0))
+            return ''.join(self._repr(obj, 0, convert_to_hex=convert_to_hex))
         except Exception:
             try:
                 return 'An exception was raised: %r' % sys.exc_info()[1]
             except Exception:
                 return 'An exception was raised'
 
-    def _repr(self, obj, level):
+    def _repr(self, obj, level, convert_to_hex=False):
         '''Returns an iterable of the parts in the final repr string.'''
 
         try:
@@ -93,21 +95,21 @@ def has_obj_repr(t):
 
         for t, prefix, suffix, comma in self.collection_types:
             if isinstance(obj, t) and has_obj_repr(t):
-                return self._repr_iter(obj, level, prefix, suffix, comma)
+                return self._repr_iter(obj, level, prefix, suffix, comma, convert_to_hex=convert_to_hex)
 
         for t, prefix, suffix, item_prefix, item_sep, item_suffix in self.dict_types:  # noqa
             if isinstance(obj, t) and has_obj_repr(t):
                 return self._repr_dict(obj, level, prefix, suffix,
-                                       item_prefix, item_sep, item_suffix)
+                                       item_prefix, item_sep, item_suffix, convert_to_hex=convert_to_hex)
 
         for t in self.string_types:
             if isinstance(obj, t) and has_obj_repr(t):
-                return self._repr_str(obj, level)
+                return self._repr_str(obj, level, convert_to_hex=convert_to_hex)
 
         if self._is_long_iter(obj):
-            return self._repr_long_iter(obj)
+            return self._repr_long_iter(obj, convert_to_hex=convert_to_hex)
 
-        return self._repr_other(obj, level)
+        return self._repr_other(obj, level, convert_to_hex=convert_to_hex)
 
     # Determines whether an iterable exceeds the limits set in
     # maxlimits, and is therefore unsafe to repr().
@@ -164,7 +166,7 @@ def _is_long_iter(self, obj, level=0):
             return True
 
     def _repr_iter(self, obj, level, prefix, suffix,
-                   comma_after_single_element=False):
+                   comma_after_single_element=False, convert_to_hex=False):
         yield prefix
 
         if level >= len(self.maxcollection):
@@ -182,7 +184,7 @@ def _repr_iter(self, obj, level, prefix, suffix,
                     yield '...'
                     break
 
-                for p in self._repr(item, 100 if item is obj else level + 1):
+                for p in self._repr(item, 100 if item is obj else level + 1, convert_to_hex=convert_to_hex):
                     yield p
             else:
                 if comma_after_single_element:
@@ -190,9 +192,10 @@ def _repr_iter(self, obj, level, prefix, suffix,
                         yield ','
         yield suffix
 
-    def _repr_long_iter(self, obj):
+    def _repr_long_iter(self, obj, convert_to_hex=False):
         try:
-            obj_repr = '<%s, len() = %s>' % (type(obj).__name__, len(obj))
+            length = hex(len(obj)) if convert_to_hex else len(obj)
+            obj_repr = '<%s, len() = %s>' % (type(obj).__name__, length)
         except Exception:
             try:
                 obj_repr = '<' + type(obj).__name__ + '>'
@@ -201,7 +204,7 @@ def _repr_long_iter(self, obj):
         yield obj_repr
 
     def _repr_dict(self, obj, level, prefix, suffix,
-                   item_prefix, item_sep, item_suffix):
+                   item_prefix, item_sep, item_suffix, convert_to_hex=False):
         if not obj:
             yield prefix + suffix
             return
@@ -230,7 +233,7 @@ def _repr_dict(self, obj, level, prefix, suffix,
                 break
 
             yield item_prefix
-            for p in self._repr(key, level + 1):
+            for p in self._repr(key, level + 1, convert_to_hex=convert_to_hex):
                 yield p
 
             yield item_sep
@@ -240,23 +243,26 @@ def _repr_dict(self, obj, level, prefix, suffix,
             except Exception:
                 yield '<?>'
             else:
-                for p in self._repr(item, 100 if item is obj else level + 1):
+                for p in self._repr(item, 100 if item is obj else level + 1, convert_to_hex=convert_to_hex):
                     yield p
             yield item_suffix
 
         yield suffix
 
-    def _repr_str(self, obj, level):
+    def _repr_str(self, obj, level, convert_to_hex=False):
         return self._repr_obj(obj, level,
-                              self.maxstring_inner, self.maxstring_outer)
+                              self.maxstring_inner, self.maxstring_outer, convert_to_hex=convert_to_hex)
 
-    def _repr_other(self, obj, level):
+    def _repr_other(self, obj, level, convert_to_hex=False):
         return self._repr_obj(obj, level,
-                              self.maxother_inner, self.maxother_outer)
+                              self.maxother_inner, self.maxother_outer, convert_to_hex=convert_to_hex)
 
-    def _repr_obj(self, obj, level, limit_inner, limit_outer):
+    def _repr_obj(self, obj, level, limit_inner, limit_outer, convert_to_hex=False):
         try:
-            obj_repr = repr(obj)
+            if isinstance(obj, self.int_types) and convert_to_hex:
+                obj_repr = hex(obj)
+            else:
+                obj_repr = repr(obj)
         except Exception:
             try:
                 obj_repr = object.__repr__(obj)
diff --git a/ptvsd/wrapper.py b/ptvsd/wrapper.py
--- a/ptvsd/wrapper.py
+++ b/ptvsd/wrapper.py
@@ -47,6 +47,31 @@
 WAIT_FOR_DISCONNECT_REQUEST_TIMEOUT = 2
 WAIT_FOR_THREAD_FINISH_TIMEOUT = 1
 
+
+class SafeReprPresentationProvider(pydevd_extapi.StrPresentationProvider):
+    """
+    Computes string representation of Python values by delegating them
+    to SafeRepr.
+    """
+
+    def __init__(self):
+        from ptvsd.safe_repr import SafeRepr
+        self.safe_repr = SafeRepr()
+        self.convert_to_hex = False
+
+    def can_provide(self, type_object, type_name):
+        return True
+
+    def get_str(self, val):
+        return self.safe_repr(val, self.convert_to_hex)
+            
+# Register our presentation provider as the first item on the list,
+# so that we're in full control of presentation.
+str_handlers = pydevd_extutil.EXTENSION_MANAGER_INSTANCE.type_to_instance.setdefault(pydevd_extapi.StrPresentationProvider, [])  # noqa
+safe_repr_provider = SafeReprPresentationProvider()
+str_handlers.insert(0, safe_repr_provider)
+
+
 class UnsupportedPyDevdCommandError(Exception):
 
     def __init__(self, cmdid):
@@ -575,6 +600,7 @@ def on_initialize(self, request, args):
             supportsSetVariable=True,
             supportsExceptionOptions=True,
             supportsEvaluateForHovers=True,
+            supportsValueFormattingOptions=True,
             exceptionBreakpointFilters=[
                 {
                     'filter': 'raised',
@@ -759,6 +785,8 @@ def on_variables(self, request, args):
         # TODO: docstring
         vsc_var = int(args['variablesReference'])
         pyd_var = self.var_map.to_pydevd(vsc_var)
+        
+        safe_repr_provider.convert_to_hex = args.get('format', {}).get('hex', False)
 
         if len(pyd_var) == 3:
             cmd = pydevd_comm.CMD_GET_FRAME
@@ -794,6 +822,9 @@ def on_variables(self, request, args):
                 var['evaluateName'] = eval_name
 
             variables.append(var)
+
+        # Reset hex format since this is per request.
+        safe_repr_provider.convert_to_hex = False
         self.send_response(request, variables=variables.get_sorted_variables())
 
     def __get_variable_evaluate_name(self, pyd_var_parent, var_name):
@@ -836,6 +867,8 @@ def on_setVariable(self, request, args):
         vsc_var = int(args['variablesReference'])
         pyd_var = self.var_map.to_pydevd(vsc_var)
 
+        safe_repr_provider.convert_to_hex = args.get('format', {}).get('hex', False)
+
         # VSC gives us variablesReference to the parent of the variable
         # being set, and variable name; but pydevd wants the ID
         # (or rather path) of the variable itself.
@@ -856,6 +889,9 @@ def on_setVariable(self, request, args):
         }
         if bool(xvar['isContainer']):
             response['variablesReference'] = vsc_var
+        
+        # Reset hex format since this is per request.
+        safe_repr_provider.convert_to_hex = False
         self.send_response(request, **response)
 
     @async_handler
@@ -866,6 +902,8 @@ def on_evaluate(self, request, args):
         vsc_fid = int(args['frameId'])
         pyd_tid, pyd_fid = self.frame_map.to_pydevd(vsc_fid)
 
+        safe_repr_provider.convert_to_hex = args.get('format', {}).get('hex', False)
+
         cmd_args = (pyd_tid, pyd_fid, 'LOCAL', expr, '1')
         _, _, resp_args = yield self.pydevd_request(
             pydevd_comm.CMD_EVALUATE_EXPRESSION,
@@ -890,6 +928,9 @@ def on_evaluate(self, request, args):
         }
         if bool(xvar['isContainer']):
             response['variablesReference'] = vsc_var
+        
+        # Reset hex format since this is per request.
+        safe_repr_provider.convert_to_hex = False
         self.send_response(request, **response)
 
     @async_handler
@@ -1283,25 +1324,3 @@ def start_client(host, port):
 pydevd_comm.start_server = start_server
 pydevd_comm.start_client = start_client
 
-
-class SafeReprPresentationProvider(pydevd_extapi.StrPresentationProvider):
-    """
-    Computes string representation of Python values by delegating them
-    to SafeRepr.
-    """
-
-    def __init__(self):
-        from ptvsd.safe_repr import SafeRepr
-        self.safe_repr = SafeRepr()
-
-    def can_provide(self, type_object, type_name):
-        return True
-
-    def get_str(self, val):
-        return self.safe_repr(val)
-
-
-# Register our presentation provider as the first item on the list,
-# so that we're in full control of presentation.
-str_handlers = pydevd_extutil.EXTENSION_MANAGER_INSTANCE.type_to_instance.setdefault(pydevd_extapi.StrPresentationProvider, [])  # noqa
-str_handlers.insert(0, SafeReprPresentationProvider())


Test patch: