33from typing import TYPE_CHECKING , Union
44
55from textual .app import App
6- from textual .containers import Horizontal , Vertical
6+ from textual .binding import Binding
7+ from textual .containers import Horizontal , ScrollableContainer , Vertical
78from textual .message import Message
9+ from textual .reactive import reactive
810from textual .widget import Widget
911from textual .widgets import Footer , Static
1012
1113from ..cterm import CTerm
1214from ..kast .inner import KApply , KRewrite
1315from ..kast .manip import flatten_label , minimize_term , push_down_rewrites
1416from ..prelude .kbool import TRUE
15- from ..utils import shorten_hashes , single
17+ from ..utils import ROOT , shorten_hashes , single
1618from .kcfg import KCFG
1719from .show import KCFGShow
1820
@@ -55,13 +57,85 @@ def on_click(self, click: Click) -> None:
5557 click .stop ()
5658
5759
58- class BehaviorView (Widget ):
60+ class NavWidget (ScrollableContainer , can_focus = True ):
61+ text : reactive [str ] = reactive ('' , init = False )
62+
63+ class Selected (Message ):
64+ def __init__ (self ) -> None :
65+ super ().__init__ ()
66+
67+ BINDINGS = [
68+ ('g' , 'scroll_home' , 'Go to vert start' ),
69+ ('G' , 'scroll_end' , 'Go to vert end' ),
70+ ]
71+
72+ def __init__ (self , id : str ):
73+ super ().__init__ (id = id )
74+
75+ def update (self , text : str ) -> None :
76+ self .text = text
77+
78+ def compose (self ) -> ComposeResult :
79+ yield Static (self .text )
80+
81+ def watch_text (self ) -> None :
82+ self .query_one (Static ).update (self .text )
83+
84+
85+ class Info (Widget , can_focus = False ):
86+ text : reactive [str ] = reactive ('' , init = False )
87+
88+ def __init__ (self ) -> None :
89+ super ().__init__ (id = 'info' )
90+
91+ def update (self , text : str ) -> None :
92+ self .text = text
93+
94+ def compose (self ) -> ComposeResult :
95+ yield Static (self .text )
96+
97+ def watch_text (self ) -> None :
98+ self .query_one (Static ).update (self .text )
99+
100+
101+ class Term (NavWidget ):
102+ def __init__ (self ) -> None :
103+ super ().__init__ (id = 'term' )
104+
105+ def on_click (self , click : Click ) -> None :
106+ click .stop ()
107+ self .post_message (Term .Selected ())
108+
109+
110+ class Constraint (NavWidget ):
111+ def __init__ (self ) -> None :
112+ super ().__init__ (id = 'constraint' )
113+
114+ def on_click (self , click : Click ) -> None :
115+ click .stop ()
116+ self .post_message (Constraint .Selected ())
117+
118+
119+ class Custom (NavWidget ):
120+ def __init__ (self ) -> None :
121+ super ().__init__ (id = 'custom' )
122+
123+ def on_click (self , click : Click ) -> None :
124+ click .stop ()
125+ self .post_message (Custom .Selected ())
126+
127+
128+ class BehaviorView (ScrollableContainer , can_focus = True ):
59129 _kcfg : KCFG
60130 _kprint : KPrint
61131 _minimize : bool
62132 _node_printer : NodePrinter | None
63133 _kcfg_nodes : Iterable [GraphChunk ]
64134
135+ class Selected (Message ):
136+ def __init__ (self ) -> None :
137+ super ().__init__ ()
138+
65139 def __init__ (
66140 self ,
67141 kcfg : KCFG ,
@@ -83,6 +157,10 @@ def __init__(
83157 def compose (self ) -> ComposeResult :
84158 return self ._kcfg_nodes
85159
160+ def on_click (self , click : Click ) -> None :
161+ click .stop ()
162+ self .post_message (BehaviorView .Selected ())
163+
86164
87165class NodeView (Widget ):
88166 _kprint : KPrint
@@ -129,14 +207,10 @@ def _info_text(self) -> str:
129207 return f'{ element_str } selected. { minimize_str } Minimize Output. { term_str } Term View. { constraint_str } Constraint View. { custom_str } Custom View.'
130208
131209 def compose (self ) -> ComposeResult :
132- yield Horizontal (Static (self ._info_text (), id = 'info' ), id = 'info-view' )
133- yield Horizontal (Static ('Term' , id = 'term' ), id = 'term-view' , classes = ('' if self ._term_on else 'hidden' ))
134- yield Horizontal (
135- Static ('Constraint' , id = 'constraint' ),
136- id = 'constraint-view' ,
137- classes = ('' if self ._constraint_on else 'hidden' ),
138- )
139- yield Horizontal (Static ('Custom' , id = 'custom' ), id = 'custom-view' , classes = ('' if self ._custom_on else 'hidden' ))
210+ yield Info ()
211+ yield Term ()
212+ yield Constraint ()
213+ yield Custom ()
140214
141215 def toggle_option (self , field : str ) -> bool :
142216 assert field in ['minimize' , 'term_on' , 'constraint_on' , 'custom_on' ]
@@ -147,21 +221,23 @@ def toggle_option(self, field: str) -> bool:
147221 if field == 'custom_on' and self ._custom_view is None :
148222 new_value = False
149223 setattr (self , field_attr , new_value )
150- self .query_one ('#info' , Static ).update (self ._info_text ())
151224 self ._update ()
152225 return new_value
153226
154227 def toggle_view (self , field : str ) -> None :
155228 assert field in ['term' , 'constraint' , 'custom' ]
156229 if self .toggle_option (f'{ field } _on' ):
157- self .query_one (f'#{ field } -view' , Horizontal ).remove_class ('hidden' )
230+ self .query_one (f'#{ field } ' ).remove_class ('hidden' )
158231 else :
159- self .query_one (f'#{ field } -view' , Horizontal ).add_class ('hidden' )
232+ self .query_one (f'#{ field } ' ).add_class ('hidden' )
160233
161234 def update (self , element : KCFGElem ) -> None :
162235 self ._element = element
163236 self ._update ()
164237
238+ def on_mount (self ) -> None :
239+ self ._update ()
240+
165241 def _update (self ) -> None :
166242 def _boolify (c : KInner ) -> KInner :
167243 if type (c ) is KApply and c .label .name == '#Equals' and c .args [0 ] == TRUE :
@@ -226,14 +302,26 @@ def _cterm_text(cterm: CTerm) -> tuple[str, str]:
226302 elif type (self ._element ) is KCFG .Successor :
227303 custom_str = '\n ' .join (self ._custom_view (self ._element ))
228304
229- self .query_one ('#info' , Static ).update (self ._info_text ())
230- self .query_one ('#term' , Static ).update (term_str )
231- self .query_one ('#constraint' , Static ).update (constraint_str )
232- self .query_one ('#custom' , Static ).update (custom_str )
305+ self .query_one ('#info' , Info ).text = self ._info_text ()
306+ self .query_one ('#term' , Term ).text = term_str
307+ self .query_one ('#constraint' , Constraint ).text = constraint_str
308+ self .query_one ('#custom' , Custom ).text = custom_str
309+
310+ def on_behavior_view_selected (self ) -> None :
311+ self .query_one ('#behavior' ).focus ()
312+
313+ def on_term_selected (self ) -> None :
314+ self .query_one (Term ).focus ()
315+
316+ def on_constraint_selected (self ) -> None :
317+ self .query_one (Constraint ).focus ()
318+
319+ def on_custom_selected (self ) -> None :
320+ self .query_one (Custom ).focus ()
233321
234322
235323class KCFGViewer (App ):
236- CSS_PATH = ' style.css'
324+ CSS_PATH = ROOT / 'kcfg/ style.css'
237325
238326 _kcfg : KCFG
239327 _kprint : KPrint
@@ -274,6 +362,8 @@ def compose(self) -> ComposeResult:
274362 yield Footer ()
275363
276364 def on_graph_chunk_selected (self , message : GraphChunk .Selected ) -> None :
365+ self .query_one ('#behavior' ).focus ()
366+
277367 if message .chunk_id .startswith ('node_' ):
278368 self ._selected_chunk = message .chunk_id
279369 node , * _ = message .chunk_id [5 :].split ('_' )
@@ -319,6 +409,7 @@ def on_graph_chunk_selected(self, message: GraphChunk.Selected) -> None:
319409 ('c' , 'keystroke("constraint")' , 'Toggle constraint.' ),
320410 ('v' , 'keystroke("custom")' , 'Toggle custom.' ),
321411 ('m' , 'keystroke("minimize")' , 'Toggle minimization.' ),
412+ Binding ('q' , 'quit' , priority = True ),
322413 ]
323414
324415 def action_keystroke (self , key : str ) -> None :
@@ -327,12 +418,12 @@ def action_keystroke(self, key: str) -> None:
327418 node_id = self ._selected_chunk [5 :]
328419 self ._hidden_chunks .append (self ._selected_chunk )
329420 self .query_one (f'#{ self ._selected_chunk } ' , GraphChunk ).add_class ('hidden' )
330- self .query_one ('#info' , Static ). update ( f'HIDDEN: node({ shorten_hashes (node_id )} )' )
421+ self .query_one ('#info' , Info ). text = f'HIDDEN: node({ shorten_hashes (node_id )} )'
331422 elif key == 'H' :
332423 for hc in self ._hidden_chunks :
333424 self .query_one (f'#{ hc } ' , GraphChunk ).remove_class ('hidden' )
334425 node_ids = [nid [5 :] for nid in self ._hidden_chunks ]
335- self .query_one ('#info' , Static ). update ( f'UNHIDDEN: nodes({ shorten_hashes (node_ids )} )' )
426+ self .query_one ('#info' , Info ). text = f'UNHIDDEN: nodes({ shorten_hashes (node_ids )} )'
336427 self ._hidden_chunks = []
337428 elif key in ['term' , 'constraint' , 'custom' ]:
338429 self .query_one ('#node-view' , NodeView ).toggle_view (key )
0 commit comments