From ef2e25bef04afd19354ab2ebc76dac64117ff270 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Wed, 29 Jun 2022 09:39:21 -0700 Subject: [PATCH 01/14] Use list.remove() to delete watches --- pudb/debugger.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pudb/debugger.py b/pudb/debugger.py index dc75b33e..262bf4a7 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -1025,9 +1025,10 @@ def change_var_state(w, size, key): iinfo.show_methods = not iinfo.show_methods elif key == "delete": fvi = self.get_frame_var_info(read_only=False) - for i, watch_expr in enumerate(fvi.watches): - if watch_expr is var.watch_expr: - del fvi.watches[i] + try: + fvi.watches.remove(var.watch_expr) + except ValueError: + pass self.update_var_view(focus_index=focus_index) @@ -1143,9 +1144,10 @@ def edit_inspector_detail(w, size, key): var.watch_expr.expression = watch_edit.get_edit_text() elif result == "del": - for i, watch_expr in enumerate(fvi.watches): - if watch_expr is var.watch_expr: - del fvi.watches[i] + try: + fvi.watches.remove(var.watch_expr) + except ValueError: + pass self.update_var_view() From 9464e5026f2a859f3010d3c39e2306eb189f72e9 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Wed, 29 Jun 2022 09:39:37 -0700 Subject: [PATCH 02/14] Extract watch expression evaluation --- pudb/var_view.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/pudb/var_view.py b/pudb/var_view.py index d330da3d..52c977cd 100644 --- a/pudb/var_view.py +++ b/pudb/var_view.py @@ -200,6 +200,12 @@ class WatchExpression: def __init__(self, expression): self.expression = expression + def eval(self, frame_globals, frame_locals): + try: + return eval(self.expression, frame_globals, frame_locals) + except Exception: + return WatchEvalError() + class WatchEvalError: def __str__(self): @@ -717,8 +723,8 @@ def add_item(self, parent, var_label, value_str, id_path, attr_prefix=None): SEPARATOR = urwid.AttrMap(urwid.Text(""), "variable separator") -def make_var_view(frame_var_info, locals, globals): - vars = list(locals.keys()) +def make_var_view(frame_var_info, frame_locals, frame_globals): + vars = list(frame_locals.keys()) vars.sort(key=str.lower) tmv_walker = TopAndMainVariableWalker(frame_var_info) @@ -726,21 +732,18 @@ def make_var_view(frame_var_info, locals, globals): watch_widget_list = [] for watch_expr in frame_var_info.watches: - try: - value = eval(watch_expr.expression, globals, locals) - except Exception: - value = WatchEvalError() + value = watch_expr.eval(frame_globals, frame_locals) WatchValueWalker(frame_var_info, watch_widget_list, watch_expr) \ .walk_value(None, watch_expr.expression, value) if "__return__" in vars: - ret_walker.walk_value(None, "Return", locals["__return__"], + ret_walker.walk_value(None, "Return", frame_locals["__return__"], attr_prefix="return") for var in vars: if not (var.startswith("__") and var.endswith("__")): - tmv_walker.walk_value(None, var, locals[var]) + tmv_walker.walk_value(None, var, frame_locals[var]) result = tmv_walker.main_widget_list From 7072824732d63ee310f634611fe122f056998309 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Wed, 29 Jun 2022 09:39:38 -0700 Subject: [PATCH 03/14] Remove unused locals/globals kwargs --- pudb/debugger.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pudb/debugger.py b/pudb/debugger.py index 262bf4a7..7d136a7e 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -2845,16 +2845,12 @@ def set_current_line(self, line, source_code_provider): self.current_line = self.source[line] self.current_line.set_current(True) - def update_var_view(self, locals=None, globals=None, focus_index=None): - if locals is None: - locals = self.debugger.curframe.f_locals - if globals is None: - globals = self.debugger.curframe.f_globals - + def update_var_view(self, focus_index=None): from pudb.var_view import make_var_view self.locals[:] = make_var_view( self.get_frame_var_info(read_only=True), - locals, globals) + self.debugger.curframe.f_locals, + self.debugger.curframe.f_globals) if focus_index is not None: # Have to set the focus _after_ updating the locals list, as there # appears to be a brief moment while reseting the list when the From 21543b3afb96fa6dd6b4d730f4aedbd098d5b38d Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Wed, 29 Jun 2022 09:39:39 -0700 Subject: [PATCH 04/14] Support a list of global watches --- pudb/debugger.py | 1 + pudb/var_view.py | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pudb/debugger.py b/pudb/debugger.py index 7d136a7e..833824fe 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -2848,6 +2848,7 @@ def set_current_line(self, line, source_code_provider): def update_var_view(self, focus_index=None): from pudb.var_view import make_var_view self.locals[:] = make_var_view( + self.global_watches, self.get_frame_var_info(read_only=True), self.debugger.curframe.f_locals, self.debugger.curframe.f_globals) diff --git a/pudb/var_view.py b/pudb/var_view.py index 52c977cd..e7c9758b 100644 --- a/pudb/var_view.py +++ b/pudb/var_view.py @@ -32,6 +32,7 @@ from abc import ABC, abstractmethod from collections.abc import Callable, Sized +from itertools import chain from typing import Tuple, List from pudb.lowlevel import ui_log from pudb.ui_tools import text_width @@ -723,7 +724,7 @@ def add_item(self, parent, var_label, value_str, id_path, attr_prefix=None): SEPARATOR = urwid.AttrMap(urwid.Text(""), "variable separator") -def make_var_view(frame_var_info, frame_locals, frame_globals): +def make_var_view(global_watches, frame_var_info, frame_locals, frame_globals): vars = list(frame_locals.keys()) vars.sort(key=str.lower) @@ -731,7 +732,7 @@ def make_var_view(frame_var_info, frame_locals, frame_globals): ret_walker = BasicValueWalker(frame_var_info) watch_widget_list = [] - for watch_expr in frame_var_info.watches: + for watch_expr in chain(global_watches, frame_var_info.watches): value = watch_expr.eval(frame_globals, frame_locals) WatchValueWalker(frame_var_info, watch_widget_list, watch_expr) \ @@ -762,6 +763,7 @@ def make_var_view(frame_var_info, frame_locals, frame_globals): class FrameVarInfoKeeper: def __init__(self): self.frame_var_info = {} + self.global_watches = [] def get_frame_var_info(self, read_only, ssid=None): if ssid is None: From 669e8c54d39786c5e5b1592d8aebba2d871d33e3 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Wed, 29 Jun 2022 09:39:40 -0700 Subject: [PATCH 05/14] Rearrange locals/globals order for consistency --- pudb/debugger.py | 4 ++-- pudb/var_view.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pudb/debugger.py b/pudb/debugger.py index 833824fe..10209325 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -2850,8 +2850,8 @@ def update_var_view(self, focus_index=None): self.locals[:] = make_var_view( self.global_watches, self.get_frame_var_info(read_only=True), - self.debugger.curframe.f_locals, - self.debugger.curframe.f_globals) + self.debugger.curframe.f_globals, + self.debugger.curframe.f_locals) if focus_index is not None: # Have to set the focus _after_ updating the locals list, as there # appears to be a brief moment while reseting the list when the diff --git a/pudb/var_view.py b/pudb/var_view.py index e7c9758b..c7e73ba2 100644 --- a/pudb/var_view.py +++ b/pudb/var_view.py @@ -724,7 +724,7 @@ def add_item(self, parent, var_label, value_str, id_path, attr_prefix=None): SEPARATOR = urwid.AttrMap(urwid.Text(""), "variable separator") -def make_var_view(global_watches, frame_var_info, frame_locals, frame_globals): +def make_var_view(global_watches, frame_var_info, frame_globals, frame_locals): vars = list(frame_locals.keys()) vars.sort(key=str.lower) From ec5c8f7515ada2a09bc7f1384411869ac126d454 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Wed, 29 Jun 2022 09:39:42 -0700 Subject: [PATCH 06/14] Add global watch expresssions --- pudb/debugger.py | 52 ++++++++++++++++++++++++++++++++++-------------- pudb/var_view.py | 15 +++++++++++--- 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/pudb/debugger.py b/pudb/debugger.py index 10209325..99ae5ebb 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -1152,23 +1152,45 @@ def edit_inspector_detail(w, size, key): self.update_var_view() def insert_watch(w, size, key): - watch_edit = urwid.Edit([ - ("label", "Watch expression: ") - ]) + from pudb.var_view import WatchExpression + watch_expr = WatchExpression() + + def set_watch_scope(radio_button, new_state, user_data): + if new_state == True: + watch_expr.scope = user_data + + watch_edit = urwid.Edit([("label", "Watch expression: ")]) + + scope_rbs = [] + urwid.RadioButton( + group=scope_rbs, + label="Local: watch in current frame only", + state=True, + on_state_change=set_watch_scope, + user_data="local", + ) + urwid.RadioButton( + group=scope_rbs, + label="Global: watch in all frames", + state=False, + on_state_change=set_watch_scope, + user_data="global", + ) if self.dialog( - urwid.ListBox(urwid.SimpleListWalker([ - urwid.AttrMap(watch_edit, "input", "focused input") - ])), - [ - ("OK", True), - ("Cancel", False), - ], title="Add Watch Expression"): - - from pudb.var_view import WatchExpression - we = WatchExpression(watch_edit.get_edit_text()) - fvi = self.get_frame_var_info(read_only=False) - fvi.watches.append(we) + urwid.ListBox(urwid.SimpleListWalker([ + urwid.AttrMap(watch_edit, "input", "focused input"), + urwid.Text(""), + urwid.Text("Scope:"), + ] + scope_rbs)), + [ + ("OK", True), + ("Cancel", False), + ], + title="Add Watch Expression" + ): + watch_expr.expression = watch_edit.get_edit_text() + self.add_watch(watch_expr) self.update_var_view() self.var_list.listen("\\", change_var_state) diff --git a/pudb/var_view.py b/pudb/var_view.py index c7e73ba2..e27e48ea 100644 --- a/pudb/var_view.py +++ b/pudb/var_view.py @@ -198,8 +198,9 @@ def __init__(self): class WatchExpression: - def __init__(self, expression): + def __init__(self, expression="", scope="local", method="expression"): self.expression = expression + self.scope = scope def eval(self, frame_globals, frame_locals): try: @@ -734,9 +735,9 @@ def make_var_view(global_watches, frame_var_info, frame_globals, frame_locals): for watch_expr in chain(global_watches, frame_var_info.watches): value = watch_expr.eval(frame_globals, frame_locals) - + label = f"[{watch_expr.scope[0]}] {watch_expr.expression}" WatchValueWalker(frame_var_info, watch_widget_list, watch_expr) \ - .walk_value(None, watch_expr.expression, value) + .walk_value(None, label, value) if "__return__" in vars: ret_walker.walk_value(None, "Return", frame_locals["__return__"], @@ -774,6 +775,14 @@ def get_frame_var_info(self, read_only, ssid=None): else: return self.frame_var_info.setdefault(ssid, FrameVarInfo()) + def add_watch(self, watch_expr: WatchExpression): + if watch_expr.scope == "local": + fvi = self.get_frame_var_info(read_only=False) + fvi.watches.append(watch_expr) + elif watch_expr.scope == "global": + self.global_watches.append(watch_expr) + + # }}} # vim: foldmethod=marker From 5d3246715810a761080776972c117ee4e4cd4f41 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Wed, 29 Jun 2022 09:55:35 -0700 Subject: [PATCH 07/14] Enable watching objects by reference --- pudb/debugger.py | 25 ++++++++++++++++++++++++- pudb/var_view.py | 19 ++++++++++++++----- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/pudb/debugger.py b/pudb/debugger.py index 99ae5ebb..7ae1905e 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -1159,6 +1159,10 @@ def set_watch_scope(radio_button, new_state, user_data): if new_state == True: watch_expr.scope = user_data + def set_watch_method(radio_button, new_state, user_data): + if new_state == True: + watch_expr.method = user_data + watch_edit = urwid.Edit([("label", "Watch expression: ")]) scope_rbs = [] @@ -1177,12 +1181,31 @@ def set_watch_scope(radio_button, new_state, user_data): user_data="global", ) + method_rbs = [] + urwid.RadioButton( + group=method_rbs, + label="Expression: always re-evaluate the expression", + state=True, + on_state_change=set_watch_method, + user_data="expression", + ) + urwid.RadioButton( + group=method_rbs, + label="Reference: evaluate once, watch the resulting value", + state=False, + on_state_change=set_watch_method, + user_data="reference", + ) + if self.dialog( urwid.ListBox(urwid.SimpleListWalker([ urwid.AttrMap(watch_edit, "input", "focused input"), urwid.Text(""), urwid.Text("Scope:"), - ] + scope_rbs)), + ] + scope_rbs + [ + urwid.Text(""), + urwid.Text("Method:"), + ] + method_rbs)), [ ("OK", True), ("Cancel", False), diff --git a/pudb/var_view.py b/pudb/var_view.py index e27e48ea..1d36e889 100644 --- a/pudb/var_view.py +++ b/pudb/var_view.py @@ -198,15 +198,22 @@ def __init__(self): class WatchExpression: + NOT_EVALUATED = object() + def __init__(self, expression="", scope="local", method="expression"): self.expression = expression self.scope = scope + self.method = method + self._value = self.NOT_EVALUATED def eval(self, frame_globals, frame_locals): - try: - return eval(self.expression, frame_globals, frame_locals) - except Exception: - return WatchEvalError() + if (self.method == "expression" + or self._value is self.NOT_EVALUATED): + try: + self._value = eval(self.expression, frame_globals, frame_locals) + except Exception: + return WatchEvalError() + return self._value class WatchEvalError: @@ -735,7 +742,9 @@ def make_var_view(global_watches, frame_var_info, frame_globals, frame_locals): for watch_expr in chain(global_watches, frame_var_info.watches): value = watch_expr.eval(frame_globals, frame_locals) - label = f"[{watch_expr.scope[0]}] {watch_expr.expression}" + scope_str = watch_expr.scope[0] + method_str = "=" if watch_expr.method == "expression" else "*" + label = f"[{scope_str}{method_str}] {watch_expr.expression}" WatchValueWalker(frame_var_info, watch_widget_list, watch_expr) \ .walk_value(None, label, value) From 218ca5b89853aa35d2cea058b7d030ce855d7051 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Wed, 29 Jun 2022 10:10:43 -0700 Subject: [PATCH 08/14] Show renames of "by reference" watched values --- pudb/var_view.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/pudb/var_view.py b/pudb/var_view.py index 1d36e889..af9c32f7 100644 --- a/pudb/var_view.py +++ b/pudb/var_view.py @@ -743,8 +743,23 @@ def make_var_view(global_watches, frame_var_info, frame_globals, frame_locals): for watch_expr in chain(global_watches, frame_var_info.watches): value = watch_expr.eval(frame_globals, frame_locals) scope_str = watch_expr.scope[0] - method_str = "=" if watch_expr.method == "expression" else "*" - label = f"[{scope_str}{method_str}] {watch_expr.expression}" + expression = watch_expr.expression + if watch_expr.method == "reference": + found = False + # locals first as that's the context the user is more likely to be + # interested in re: seeing renames. + for mapping in (frame_locals, frame_globals): + for k, v in mapping.items(): + if v is value: + expression = f"{expression} ({k})" + found = True + break + if found: + break + method_str = "*" + else: + method_str = "=" + label = f"[{scope_str}{method_str}] {expression}" WatchValueWalker(frame_var_info, watch_widget_list, watch_expr) \ .walk_value(None, label, value) From 6f39853524a9197881534558bed51b2b008ba422 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Wed, 29 Jun 2022 10:13:27 -0700 Subject: [PATCH 09/14] Refresh watched value if expression is edited --- pudb/debugger.py | 4 +++- pudb/var_view.py | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pudb/debugger.py b/pudb/debugger.py index 7ae1905e..edd6e314 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -1141,7 +1141,9 @@ def edit_inspector_detail(w, size, key): iinfo.access_level = "all" if var.watch_expr is not None: - var.watch_expr.expression = watch_edit.get_edit_text() + new_expression = watch_edit.get_edit_text() + if new_expression != var.watch_expr.expression: + var.watch_expr.set_expression(new_expression) elif result == "del": try: diff --git a/pudb/var_view.py b/pudb/var_view.py index af9c32f7..b2f6cfbd 100644 --- a/pudb/var_view.py +++ b/pudb/var_view.py @@ -215,6 +215,10 @@ def eval(self, frame_globals, frame_locals): return WatchEvalError() return self._value + def set_expression(self, expression): + self.expression = expression + self._value = self.NOT_EVALUATED + class WatchEvalError: def __str__(self): From 090d394c3db7eabfa2eb56e80296ab54b05d1be0 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Wed, 29 Jun 2022 10:36:15 -0700 Subject: [PATCH 10/14] Allow editing scope/method of watch expressions --- pudb/debugger.py | 124 ++++++++++++++++++++++++----------------------- pudb/var_view.py | 32 ++++++++++-- 2 files changed, 93 insertions(+), 63 deletions(-) diff --git a/pudb/debugger.py b/pudb/debugger.py index edd6e314..2a67c59c 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -1024,14 +1024,59 @@ def change_var_state(w, size, key): elif key == "m": iinfo.show_methods = not iinfo.show_methods elif key == "delete": - fvi = self.get_frame_var_info(read_only=False) - try: - fvi.watches.remove(var.watch_expr) - except ValueError: - pass + self.delete_watch(var.watch_expr) self.update_var_view(focus_index=focus_index) + def _watch_editors(watch_expr): + """ + Create widgets for editing the given expression. + """ + def set_watch_scope(radio_button, new_state, user_data): + if new_state: + watch_expr.set_scope(user_data) + + def set_watch_method(radio_button, new_state, user_data): + if new_state: + watch_expr.set_method(user_data) + + watch_edit = urwid.Edit([("label", "Watch expression: ")], + watch_expr.expression) + + scope_rbs = [] + urwid.RadioButton( + group=scope_rbs, + label="Local: watch in current frame only", + state=watch_expr.scope == "local", + on_state_change=set_watch_scope, + user_data="local", + ) + urwid.RadioButton( + group=scope_rbs, + label="Global: watch in all frames", + state=watch_expr.scope == "global", + on_state_change=set_watch_scope, + user_data="global", + ) + + method_rbs = [] + urwid.RadioButton( + group=method_rbs, + label="Expression: always re-evaluate the expression", + state=watch_expr.method == "expression", + on_state_change=set_watch_method, + user_data="expression", + ) + urwid.RadioButton( + group=method_rbs, + label="Reference: evaluate once, watch the resulting value", + state=watch_expr.method == "reference", + on_state_change=set_watch_method, + user_data="reference", + ) + + return watch_edit, scope_rbs, method_rbs + def edit_inspector_detail(w, size, key): var = self.var_list._w.focus @@ -1047,13 +1092,17 @@ def edit_inspector_detail(w, size, key): ] if var.watch_expr is not None: - watch_edit = urwid.Edit([ - ("label", "Watch expression: ") - ], var.watch_expr.expression) + watch_edit, scope_rbs, method_rbs = _watch_editors(var.watch_expr) id_segment = [ - urwid.AttrMap(watch_edit, "input", "focused input"), - urwid.Text(""), - ] + urwid.AttrMap(watch_edit, "input", "focused input"), + urwid.Text(""), + urwid.Text("Scope:"), + ] + scope_rbs + [ + urwid.Text(""), + urwid.Text("Method:"), + ] + method_rbs + [ + urwid.Text("") + ] buttons.extend([None, ("Delete", "del")]) @@ -1141,63 +1190,18 @@ def edit_inspector_detail(w, size, key): iinfo.access_level = "all" if var.watch_expr is not None: - new_expression = watch_edit.get_edit_text() - if new_expression != var.watch_expr.expression: - var.watch_expr.set_expression(new_expression) + var.watch_expr.set_expression(watch_edit.get_edit_text()) + self.change_watch_scope(var.watch_expr, fvi) elif result == "del": - try: - fvi.watches.remove(var.watch_expr) - except ValueError: - pass + self.delete_watch(var.watch_expr, fvi) self.update_var_view() def insert_watch(w, size, key): from pudb.var_view import WatchExpression watch_expr = WatchExpression() - - def set_watch_scope(radio_button, new_state, user_data): - if new_state == True: - watch_expr.scope = user_data - - def set_watch_method(radio_button, new_state, user_data): - if new_state == True: - watch_expr.method = user_data - - watch_edit = urwid.Edit([("label", "Watch expression: ")]) - - scope_rbs = [] - urwid.RadioButton( - group=scope_rbs, - label="Local: watch in current frame only", - state=True, - on_state_change=set_watch_scope, - user_data="local", - ) - urwid.RadioButton( - group=scope_rbs, - label="Global: watch in all frames", - state=False, - on_state_change=set_watch_scope, - user_data="global", - ) - - method_rbs = [] - urwid.RadioButton( - group=method_rbs, - label="Expression: always re-evaluate the expression", - state=True, - on_state_change=set_watch_method, - user_data="expression", - ) - urwid.RadioButton( - group=method_rbs, - label="Reference: evaluate once, watch the resulting value", - state=False, - on_state_change=set_watch_method, - user_data="reference", - ) + watch_edit, scope_rbs, method_rbs = _watch_editors(watch_expr) if self.dialog( urwid.ListBox(urwid.SimpleListWalker([ diff --git a/pudb/var_view.py b/pudb/var_view.py index b2f6cfbd..28627380 100644 --- a/pudb/var_view.py +++ b/pudb/var_view.py @@ -216,9 +216,17 @@ def eval(self, frame_globals, frame_locals): return self._value def set_expression(self, expression): - self.expression = expression + if expression != self.expression: + self.expression = expression + self._value = self.NOT_EVALUATED + + def set_method(self, method): + self.method = method self._value = self.NOT_EVALUATED + def set_scope(self, scope): + self.scope = scope + class WatchEvalError: def __str__(self): @@ -803,13 +811,31 @@ def get_frame_var_info(self, read_only, ssid=None): else: return self.frame_var_info.setdefault(ssid, FrameVarInfo()) - def add_watch(self, watch_expr: WatchExpression): + def add_watch(self, watch_expr: WatchExpression, fvi=None): if watch_expr.scope == "local": - fvi = self.get_frame_var_info(read_only=False) + if fvi is None: + fvi = self.get_frame_var_info(read_only=False) fvi.watches.append(watch_expr) elif watch_expr.scope == "global": self.global_watches.append(watch_expr) + def delete_watch(self, watch_expr: WatchExpression, fvi=None): + if fvi is None: + fvi = self.get_frame_var_info(read_only=False) + # Need to delete both locally and globally- could be in either! + # (The watch_expr.scope attribute may have changed) + try: + fvi.watches.remove(watch_expr) + except ValueError: + pass + try: + self.global_watches.remove(watch_expr) + except ValueError: + pass + + def change_watch_scope(self, watch_expr, fvi=None): + self.delete_watch(watch_expr, fvi) + self.add_watch(watch_expr, fvi) # }}} From 59edb7294b182477acada6ec0acc14cae41a1fac Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Wed, 29 Jun 2022 11:17:07 -0700 Subject: [PATCH 11/14] Add headings to other variable option groups --- pudb/debugger.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pudb/debugger.py b/pudb/debugger.py index 2a67c59c..645d9dc8 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -1150,8 +1150,11 @@ def edit_inspector_detail(w, size, key): lb = urwid.ListBox(urwid.SimpleListWalker( id_segment + + [urwid.Text("Stringifier:")] + rb_grp_show + [urwid.Text("")] + + [urwid.Text("Access:")] + rb_grp_access + [urwid.Text("")] + + [urwid.Text("Options:")] + [ wrap_checkbox, expanded_checkbox, From f130446e9d057d51d2755d11dce4706d54a51ac7 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Wed, 29 Jun 2022 11:35:12 -0700 Subject: [PATCH 12/14] Extract WatchExpression.label() --- pudb/var_view.py | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/pudb/var_view.py b/pudb/var_view.py index 28627380..a3c2dd59 100644 --- a/pudb/var_view.py +++ b/pudb/var_view.py @@ -215,6 +215,26 @@ def eval(self, frame_globals, frame_locals): return WatchEvalError() return self._value + def label(self, value, frame_globals, frame_locals): + scope_str = self.scope[0] + expression = self.expression + if self.method == "reference": + found = False + # locals first as that's the context the user is more likely to be + # interested in re: seeing renames. + for mapping in (frame_locals, frame_globals): + for k, v in mapping.items(): + if v is value: + expression = f"{expression} ({k})" + found = True + break + if found: + break + method_str = "*" + else: + method_str = "=" + return f"[{scope_str}{method_str}] {expression}" + def set_expression(self, expression): if expression != self.expression: self.expression = expression @@ -754,24 +774,7 @@ def make_var_view(global_watches, frame_var_info, frame_globals, frame_locals): for watch_expr in chain(global_watches, frame_var_info.watches): value = watch_expr.eval(frame_globals, frame_locals) - scope_str = watch_expr.scope[0] - expression = watch_expr.expression - if watch_expr.method == "reference": - found = False - # locals first as that's the context the user is more likely to be - # interested in re: seeing renames. - for mapping in (frame_locals, frame_globals): - for k, v in mapping.items(): - if v is value: - expression = f"{expression} ({k})" - found = True - break - if found: - break - method_str = "*" - else: - method_str = "=" - label = f"[{scope_str}{method_str}] {expression}" + label = watch_expr.label(value, frame_globals, frame_locals) WatchValueWalker(frame_var_info, watch_widget_list, watch_expr) \ .walk_value(None, label, value) From d5fd63b9b30010b3b753dad7960c8d32cf415ba6 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Mon, 11 Jul 2022 22:22:10 -0700 Subject: [PATCH 13/14] Let go of old global watch values when restarting --- pudb/debugger.py | 1 + pudb/var_view.py | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/pudb/debugger.py b/pudb/debugger.py index 645d9dc8..860b631e 100644 --- a/pudb/debugger.py +++ b/pudb/debugger.py @@ -319,6 +319,7 @@ def setup_state(self): def restart(self): from linecache import checkcache checkcache() + self.ui.reset_global_watch_values() self.ui.set_source_code_provider(NullSourceCodeProvider()) self.setup_state() diff --git a/pudb/var_view.py b/pudb/var_view.py index a3c2dd59..f352014e 100644 --- a/pudb/var_view.py +++ b/pudb/var_view.py @@ -247,6 +247,9 @@ def set_method(self, method): def set_scope(self, scope): self.scope = scope + def reset_value(self): + self._value = self.NOT_EVALUATED + class WatchEvalError: def __str__(self): @@ -840,6 +843,10 @@ def change_watch_scope(self, watch_expr, fvi=None): self.delete_watch(watch_expr, fvi) self.add_watch(watch_expr, fvi) + def reset_global_watch_values(self): + for watch_expr in self.global_watches: + watch_expr.reset_value() + # }}} # vim: foldmethod=marker From a590140a352474146c727065d813ea2d8cd15630 Mon Sep 17 00:00:00 2001 From: Michael van der Kamp Date: Sat, 30 Jul 2022 13:18:04 -0700 Subject: [PATCH 14/14] Persist InspectInfo for global watches --- pudb/var_view.py | 27 +++++++++++++++++++++++---- test/test_var_view.py | 2 +- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/pudb/var_view.py b/pudb/var_view.py index f352014e..45a1d5ba 100644 --- a/pudb/var_view.py +++ b/pudb/var_view.py @@ -170,11 +170,14 @@ def length(cls, mapping): # {{{ data class FrameVarInfo: - def __init__(self): + def __init__(self, global_watch_iinfo): self.id_path_to_iinfo = {} self.watches = [] + self.global_watch_iinfo = global_watch_iinfo def get_inspect_info(self, id_path, read_only): + if id_path in self.global_watch_iinfo: + return self.global_watch_iinfo[id_path] if read_only: return self.id_path_to_iinfo.get( id_path, InspectInfo()) @@ -206,6 +209,9 @@ def __init__(self, expression="", scope="local", method="expression"): self.method = method self._value = self.NOT_EVALUATED + def id_path(self): + return str(id(self)) + def eval(self, frame_globals, frame_locals): if (self.method == "expression" or self._value is self.NOT_EVALUATED): @@ -778,8 +784,9 @@ def make_var_view(global_watches, frame_var_info, frame_globals, frame_locals): for watch_expr in chain(global_watches, frame_var_info.watches): value = watch_expr.eval(frame_globals, frame_locals) label = watch_expr.label(value, frame_globals, frame_locals) + id_path = watch_expr.id_path() WatchValueWalker(frame_var_info, watch_widget_list, watch_expr) \ - .walk_value(None, label, value) + .walk_value(None, label, value, id_path) if "__return__" in vars: ret_walker.walk_value(None, "Return", frame_locals["__return__"], @@ -808,14 +815,24 @@ def __init__(self): self.frame_var_info = {} self.global_watches = [] + # In order to have the global watch expression presented the same way in + # all frames, we need persistent storage for global InspectInfo. + self.global_watch_iinfo = {} + def get_frame_var_info(self, read_only, ssid=None): if ssid is None: # self.debugger set by subclass ssid = self.debugger.get_stack_situation_id() # noqa: E501 # pylint: disable=no-member if read_only: - return self.frame_var_info.get(ssid, FrameVarInfo()) + return self.frame_var_info.get( + ssid, + FrameVarInfo(self.global_watch_iinfo), + ) else: - return self.frame_var_info.setdefault(ssid, FrameVarInfo()) + return self.frame_var_info.setdefault( + ssid, + FrameVarInfo(self.global_watch_iinfo), + ) def add_watch(self, watch_expr: WatchExpression, fvi=None): if watch_expr.scope == "local": @@ -824,6 +841,7 @@ def add_watch(self, watch_expr: WatchExpression, fvi=None): fvi.watches.append(watch_expr) elif watch_expr.scope == "global": self.global_watches.append(watch_expr) + self.global_watch_iinfo[watch_expr.id_path()] = InspectInfo() def delete_watch(self, watch_expr: WatchExpression, fvi=None): if fvi is None: @@ -836,6 +854,7 @@ def delete_watch(self, watch_expr: WatchExpression, fvi=None): pass try: self.global_watches.remove(watch_expr) + self.global_watch_iinfo.pop(watch_expr.id_path()) except ValueError: pass diff --git a/test/test_var_view.py b/test/test_var_view.py index e263a3a9..239d1771 100644 --- a/test/test_var_view.py +++ b/test/test_var_view.py @@ -49,7 +49,7 @@ def test_get_stringifier(): class FrameVarInfoForTesting(FrameVarInfo): def __init__(self, paths_to_expand=None): - super().__init__() + super().__init__(global_watch_iinfo={}) if paths_to_expand is None: paths_to_expand = set() self.paths_to_expand = paths_to_expand