1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# 4# Copyright (c) 2024-2025 Huawei Device Co., Ltd. 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16# 17 18from collections.abc import Sequence 19from pathlib import Path 20from typing import Iterable, List, Set 21 22import trio 23from rich import box 24from rich.columns import Columns 25from rich.console import Group, RenderableType 26from rich.panel import Panel 27from rich.pretty import Pretty 28from rich.syntax import Syntax 29from rich.table import Column, Table 30from rich.text import Text 31 32from .debug_types import DEFAULT_DEPTH, Frame, Paused, Scope 33 34 35def source_file(src_file: Path, **kwargs) -> Syntax: 36 return Syntax.from_path( 37 path=str(src_file), 38 lexer="ts", 39 start_line=0, 40 line_numbers=True, 41 **kwargs, 42 ) 43 44 45async def _add_rows(table: Table, scope: Scope): 46 table.add_row( 47 f":red_triangle_pointed_down: [table.header]{scope.data.type_}[/table.header]", 48 Text(f"{scope.data.object_.description}", style="repr.str"), 49 ) 50 props = await scope.mirror_variables() 51 if len(props): 52 for k, v in props.items(): 53 table.add_row(k, Pretty(v)) 54 else: 55 table.add_row("none", "", style="italic") 56 57 58async def frame_layout( 59 frame: Frame, 60 scopes: Set[str] | None = None, 61 skip_scopes: Sequence[str] | None = None, 62) -> RenderableType: 63 table = Table( 64 Column("name"), 65 Column("value"), 66 box=box.SIMPLE, 67 show_edge=False, 68 ) 69 this = frame.this() 70 if this is not None: 71 table.add_row("this", Pretty(await this.mirror_value(depth=DEFAULT_DEPTH))) 72 else: 73 await trio.lowlevel.checkpoint() 74 # NOTE(dslynko, #22497): now global scope is ignored by default, because it enumerates all classes from boot. 75 # Need to enable enumerating globals after debugger server enumerates only non-boot classes. 76 skip_scopes = skip_scopes if skip_scopes is not None else ("global",) 77 for scope in frame.scopes(): 78 if (scopes is None or scope.data.type_ in scopes) and (scope.data.type_ not in skip_scopes): 79 await _add_rows(table, scope) 80 else: 81 table.add_row( 82 f":red_triangle_pointed_up: [table.header]{scope.data.type_}[/table.header]", 83 f"[repr.str]{scope.data.object_.description}[/repr.str]", 84 ) 85 86 title = "[inspect.class]%s[/inspect.class] [repr.filename]%s[/repr.filename]:[repr.number]%s[/repr.number]" % ( 87 frame.data.function_name, 88 Path(frame.data.url).name, 89 frame.data.location.line_number, 90 ) 91 return Panel(table, title=title, title_align="left") 92 93 94async def frames_layout( 95 call_frames: Iterable[Frame], 96 scopes: Set[str] | None = None, 97 skip_scopes: Sequence[str] | None = None, 98) -> List[RenderableType]: 99 100 await trio.lowlevel.checkpoint() 101 return [await frame_layout(f, scopes, skip_scopes) for f in call_frames] 102 103 104async def paused_layout( 105 paused: Paused, 106 url: Path, 107 scopes: Set[str] | None = None, 108 skip_scopes: Sequence[str] | None = None, 109) -> RenderableType: 110 frames = await frames_layout(paused.frames(), scopes=scopes, skip_scopes=skip_scopes) 111 highlight_lines = [f.location.line_number for f in paused.data.call_frames] 112 code = source_file(url, highlight_lines=highlight_lines) 113 return Columns([code, Group(*frames)]) 114