Skip to content

Commit ee96e49

Browse files
iFrostizzFrançois Guyotrv-auditor
authored andcommitted
Refactor of the KCFGViewer (runtimeverification/pyk#578)
Going in a more step-by-step fashion compared to runtimeverification/pyk#460 Rolling out our own key bindings was messing up with the defaults of textual and adding a lot of unecessary boilerpalte. This PR also closes it. - Dedicated classes for each widget in the `NodeView` (Term, Constraint, Info, Custom) - Relevant ones all inherits from `ScrollableContainer` and can be focused. Adds native key bindings and can be selected using `Tab` or `Tab+Shift` to loop the other way around - using an absolute path for the css file so that the class `APRProofViewer` inherits `KCFGViewer`'s css. `@import` is not something that works with textual, but as we may want in the future to add stuff only for the `APRProofViewer` css. --------- Co-authored-by: François Guyot <[email protected]> Co-authored-by: devops <[email protected]>
1 parent ce0bf48 commit ee96e49

File tree

7 files changed

+157
-116
lines changed

7 files changed

+157
-116
lines changed

pyk/package/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.401
1+
0.1.402

pyk/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
66
name = "pyk"
7-
version = "0.1.401"
7+
version = "0.1.402"
88
description = ""
99
authors = [
1010
"Runtime Verification, Inc. <[email protected]>",

pyk/src/pyk/kcfg/style.css

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,58 +4,66 @@
44
height: 100%;
55
layout: vertical;
66
}
7+
78
#behavior {
89
height: 1fr;
910
border: solid;
1011
overflow-x: auto;
1112
overflow-y: auto;
1213
}
13-
14-
.cfg-node {
15-
height: auto;
16-
width: auto;
14+
#behavior:focus {
15+
border: solid blue;
1716
}
1817

19-
.hidden {
20-
display: none;
21-
}
22-
23-
#display {
24-
layout: vertical;
25-
}
26-
#info-view {
27-
height: 2fr;
18+
#info {
19+
width: 100%;
20+
height: auto;
2821
border: solid;
2922
overflow: auto scroll;
3023
}
31-
#term-view {
24+
#info:focus {
25+
border: solid blue;
26+
}
27+
28+
#term {
29+
width: 100%;
3230
height: 15fr;
3331
border: solid;
3432
overflow: auto scroll;
3533
}
36-
#constraint-view {
34+
#term:focus {
35+
border: solid blue;
36+
}
37+
38+
#constraint {
39+
width: 100%;
3740
height: 5fr;
3841
border: solid;
3942
overflow: auto scroll;
4043
}
41-
#custom-view {
44+
#constraint:focus {
45+
border: solid blue;
46+
}
47+
48+
#custom {
49+
width: 100%;
4250
height: 15fr;
4351
border: solid;
4452
overflow: auto scroll;
4553
}
46-
#info {
47-
width: auto;
48-
height: auto;
54+
#custom:focus {
55+
border: solid blue;
4956
}
50-
#term {
51-
width: auto;
57+
58+
.cfg-node {
5259
height: auto;
53-
}
54-
#constraint {
5560
width: auto;
56-
height: auto;
5761
}
58-
#custom {
59-
width: auto;
60-
height: auto;
62+
63+
.hidden {
64+
display: none;
65+
}
66+
67+
#display {
68+
layout: vertical;
6169
}

pyk/src/pyk/kcfg/tui.py

Lines changed: 112 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@
33
from typing import TYPE_CHECKING, Union
44

55
from 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
78
from textual.message import Message
9+
from textual.reactive import reactive
810
from textual.widget import Widget
911
from textual.widgets import Footer, Static
1012

1113
from ..cterm import CTerm
1214
from ..kast.inner import KApply, KRewrite
1315
from ..kast.manip import flatten_label, minimize_term, push_down_rewrites
1416
from ..prelude.kbool import TRUE
15-
from ..utils import shorten_hashes, single
17+
from ..utils import ROOT, shorten_hashes, single
1618
from .kcfg import KCFG
1719
from .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

87165
class 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

235323
class 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)

pyk/src/pyk/proof/style.css

Lines changed: 0 additions & 61 deletions
This file was deleted.

0 commit comments

Comments
 (0)