Pagination of child variables (#1439)

This commit is contained in:
Pavel Minaev 2024-03-29 17:05:27 -07:00
parent b2c0b3e106
commit 7ecb6c3a4f
5 changed files with 107 additions and 87 deletions

View file

@ -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

View file

@ -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):

View file

@ -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)

View file

@ -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 = "<repr() error>"
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:

View file

@ -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):