diff --git a/src/debugpy/adapter/__main__.py b/src/debugpy/adapter/__main__.py index e18ecd56..4413fcf4 100644 --- a/src/debugpy/adapter/__main__.py +++ b/src/debugpy/adapter/__main__.py @@ -42,11 +42,11 @@ def main(args): stdio.close() if args.log_stderr: - log.stderr.levels |= set(log.LEVELS) + log.stderr.levels |= {"info", "warning", "error"} if args.log_dir is not None: log.log_dir = args.log_dir - log.to_file(prefix="debugpy.adapter") + log.to_file(prefix="debugpy.adapter", levels=("info", "warning", "error")) log.describe_environment("debugpy.adapter startup environment:") servers.access_token = args.server_access_token diff --git a/src/debugpy/server/adapters.py b/src/debugpy/server/adapters.py index 136a6e85..ad31576b 100644 --- a/src/debugpy/server/adapters.py +++ b/src/debugpy/server/adapters.py @@ -370,9 +370,11 @@ class Adapter: def variables_request(self, request: Request): start = request("start", 0) + count = request("count", int, optional=True) if count == (): count = None + filter = request("filter", str, optional=True) match filter: case (): @@ -381,10 +383,12 @@ class Adapter: filter = {filter} case _: raise request.isnt_valid(f'Invalid "filter": {filter!r}') + container_id = request("variablesReference", int) container = eval.VariableContainer.get(container_id) if container is None: raise request.isnt_valid(f'Invalid "variablesReference": {container_id}') + return {"variables": list(container.variables(filter, start, count))} def evaluate_request(self, request: Request): diff --git a/src/debugpy/server/eval.py b/src/debugpy/server/eval.py index 06fcc76a..40697b2a 100644 --- a/src/debugpy/server/eval.py +++ b/src/debugpy/server/eval.py @@ -6,9 +6,9 @@ import ctypes import itertools import debugpy import threading -from collections.abc import Iterable, Set +from collections.abc import Iterable, Set from debugpy.common import log -from debugpy.server.inspect import inspect +from debugpy.server.inspect import ObjectInspector, inspect from typing import ClassVar, Literal, Optional, Self type StackFrame = "debugpy.server.tracing.StackFrame" @@ -65,18 +65,22 @@ class VariableContainer: class Value(VariableContainer): value: object + inspector: ObjectInspector # TODO: memoryReference, presentationHint def __init__(self, frame: StackFrame, value: object): super().__init__(frame) self.value = value + self.inspector = inspect(value) def __getstate__(self) -> dict[str, object]: state = super().__getstate__() state.update( { - "value": self.repr, "type": self.typename, + "value": self.repr(), + "namedVariables": self.inspector.named_children_count(), + "indexedVariables": self.inspector.indexed_children_count(), } ) return state @@ -88,22 +92,28 @@ class Value(VariableContainer): except: return "" - @property def repr(self) -> str: - return "".join(inspect(self.value).repr()) + return "".join(self.inspector.repr()) def variables( self, filter: VariableFilter, start: int = 0, count: Optional[int] = None ) -> Iterable["Variable"]: - children = inspect(self.value).children( - include_attrs=("named" in filter), - include_items=("indexed" in filter), - ) stop = None if count is None else start + count - log.info("Computing {0} children of {1!r} in range({2}, {3}).", filter, self, start, stop) + log.info( + "Computing {0} children of {1!r} in range({2}, {3}).", + filter, + self, + start, + stop, + ) + + children = itertools.chain( + self.inspector.named_children() if "named" in filter else (), + self.inspector.indexed_children() if "indexed" in filter else (), + ) children = itertools.islice(children, start, stop) for child in children: - yield Variable(self.frame, child.name, child.value) + yield Variable(self.frame, child.key, child.value) def set_variable(self, name: str, value_expr: str) -> "Value": value = self.frame.evaluate(value_expr) diff --git a/src/debugpy/server/inspect/__init__.py b/src/debugpy/server/inspect/__init__.py index c22d7cac..f831ddb5 100644 --- a/src/debugpy/server/inspect/__init__.py +++ b/src/debugpy/server/inspect/__init__.py @@ -10,7 +10,7 @@ from collections.abc import Iterable class ChildObject: - name: str + key: str value: object def __init__(self, value: object): @@ -20,17 +20,47 @@ class ChildObject: raise NotImplementedError -class ChildAttribute(ChildObject): - name: str - +class NamedChildObject(ChildObject): def __init__(self, name: str, value: object): super().__init__(value) - self.name = name + self.key = name + + @property + def name(self) -> str: + return self.key def expr(self, parent_expr: str) -> str: return f"({parent_expr}).{self.name}" +class LenChildObject(NamedChildObject): + def __init__(self, parent: object): + super().__init__("len()", len(parent)) + + def expr(self, parent_expr: str) -> str: + return f"len({parent_expr})" + + +class IndexedChildObject(ChildObject): + key_object: object + indexer: str + + def __init__(self, key: object, value: object): + super().__init__(value) + self.key_object = key + self.indexer = None + + @property + def key(self) -> str: + if self.indexer is None: + key_repr = "".join(inspect(self.key_object).repr()) + self.indexer = f"[{key_repr}]" + return self.indexer + + def expr(self, parent_expr: str) -> str: + return f"({parent_expr}){self.key}" + + class ObjectInspector: """ Inspects a generic object. Uses builtins.repr() to render values and dir() to enumerate children. @@ -51,34 +81,50 @@ class ObjectInspector: result = "" yield result - def children( - self, *, include_attrs: bool = True, include_items: bool = True - ) -> Iterable[ChildObject]: - return ( - sorted(self._attributes(), key=lambda var: var.name) - if include_attrs - else () - ) - - def _attributes(self) -> Iterable[ChildObject]: - # TODO: group class/instance/function/special + def children(self) -> Iterable[ChildObject]: + yield from self.named_children() + yield from self.indexed_children() + + def indexed_children_count(self) -> int: try: - names = dir(self.obj) + return len(self.obj) except: - names = [] - for name in names: - if name.startswith("__"): - continue + return 0 + + def indexed_children(self) -> Iterable[IndexedChildObject]: + return () + + def named_children_count(self) -> int: + return len(tuple(self.named_children())) + + def named_children(self) -> Iterable[NamedChildObject]: + def attrs(): try: - value = getattr(self.obj, name) - except BaseException as exc: - value = exc - try: - if hasattr(value, "__call__"): + names = dir(self.obj) + except: + names = () + + # TODO: group class/instance/function/special + for name in names: + if name.startswith("__"): continue + try: + value = getattr(self.obj, name) + except BaseException as exc: + value = exc + try: + if hasattr(value, "__call__"): + continue + except: + pass + yield NamedChildObject(name, value) + + try: + yield LenChildObject(self.obj) except: pass - yield ChildAttribute(name, value) + + return sorted(attrs(), key=lambda var: var.name) def inspect(obj: object) -> ObjectInspector: diff --git a/src/debugpy/server/inspect/stdlib.py b/src/debugpy/server/inspect/stdlib.py index 1b1d4ca5..bb3caa6f 100644 --- a/src/debugpy/server/inspect/stdlib.py +++ b/src/debugpy/server/inspect/stdlib.py @@ -8,46 +8,13 @@ from collections.abc import Iterable from itertools import count from debugpy.common import log -from debugpy.server.inspect import ChildObject, ObjectInspector, inspect +from debugpy.server.inspect import ObjectInspector, IndexedChildObject, LenChildObject from debugpy.server.safe_repr import SafeRepr -class ChildLen(ChildObject): - name = "len()" - - def __init__(self, parent: object): - super().__init__(len(parent)) - - def expr(self, parent_expr: str) -> str: - return f"len({parent_expr})" - - -class ChildItem(ChildObject): - key: object - - def __init__(self, key: object, value: object): - super().__init__(value) - self.key = key - - @property - def name(self) -> str: - key_repr = "".join(inspect(self.key).repr()) - return f"[{key_repr}]" - - def expr(self, parent_expr: str) -> str: - return f"({parent_expr}){self.name}" - - class SequenceInspector(ObjectInspector): - def children( - self, *, include_attrs: bool = True, include_items: bool = True - ) -> Iterable[ChildObject]: - yield from super().children( - include_attrs=include_attrs, include_items=include_items - ) - if not include_items: - return - yield ChildLen(self.obj) + def indexed_children(self) -> Iterable[IndexedChildObject]: + yield from super().indexed_children() try: it = iter(self.obj) except: @@ -60,19 +27,12 @@ class SequenceInspector(ObjectInspector): except: log.exception("Error retrieving next item.") break - yield ChildItem(i, item) + yield IndexedChildObject(i, item) class MappingInspector(ObjectInspector): - def children( - self, *, include_attrs: bool = True, include_items: bool = True - ) -> Iterable[ChildObject]: - yield from super().children( - include_attrs=include_attrs, include_items=include_items - ) - if not include_items: - return - yield ChildLen(self.obj) + def indexed_children(self) -> Iterable[IndexedChildObject]: + yield from super().indexed_children() try: keys = self.obj.keys() except: @@ -89,7 +49,7 @@ class MappingInspector(ObjectInspector): value = self.obj[key] except BaseException as exc: value = exc - yield ChildItem(key, value) + yield IndexedChildObject(key, value) class ListInspector(SequenceInspector):