Skip to content

Commit 381057e

Browse files
authored
Merge pull request crytic#1386 from crytic/feat-kb-scroll
Add keyboard support for scrolling up/down and focusing
2 parents b3d3e8e + ca09d0b commit 381057e

File tree

2 files changed

+44
-7
lines changed

2 files changed

+44
-7
lines changed

lib/Echidna/UI.hs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,9 @@ ui vm dict initialCorpus cliSelectedContract = do
112112
-- UI initialization
113113
let buildVty = do
114114
v <- mkVty =<< vtyConfig
115-
Vty.setMode (Vty.outputIface v) Vty.Mouse True
115+
let output = Vty.outputIface v
116+
when (Vty.supportsMode output Vty.Mouse) $
117+
Vty.setMode output Vty.Mouse True
116118
pure v
117119
initialVty <- liftIO buildVty
118120
app <- customMain initialVty buildVty (Just uiChannel) <$> monitor
@@ -134,6 +136,7 @@ ui vm dict initialCorpus cliSelectedContract = do
134136
, displayFetchedDialog = False
135137
, displayLogPane = True
136138
, displayTestsPane = True
139+
, focusedPane = TestsPane
137140
, events = mempty
138141
, corpusSize = 0
139142
, coverage = 0
@@ -264,6 +267,19 @@ monitor = do
264267
else emptyWidget
265268
, runReader (campaignStatus uiState) conf ]
266269

270+
toggleFocus :: UIState -> UIState
271+
toggleFocus state =
272+
case state.focusedPane of
273+
TestsPane | state.displayLogPane -> state { focusedPane = LogPane }
274+
LogPane | state.displayTestsPane -> state { focusedPane = TestsPane }
275+
_ -> state
276+
277+
refocusIfNeeded :: UIState -> UIState
278+
refocusIfNeeded state = if
279+
(state.focusedPane == TestsPane && not state.displayTestsPane) ||
280+
(state.focusedPane == LogPane && not state.displayLogPane)
281+
then toggleFocus state else state
282+
267283
onEvent = \case
268284
AppEvent (CampaignUpdated now tests c') ->
269285
modify' $ \state -> state { campaigns = c', status = Running, now, tests }
@@ -295,15 +311,25 @@ monitor = do
295311
state { displayFetchedDialog = not state.displayFetchedDialog }
296312
VtyEvent (EvKey (KChar 'l') _) ->
297313
modify' $ \state ->
298-
state { displayLogPane = not state.displayLogPane }
314+
refocusIfNeeded $ state { displayLogPane = not state.displayLogPane }
299315
VtyEvent (EvKey (KChar 't') _) ->
300316
modify' $ \state ->
301-
state { displayTestsPane = not state.displayTestsPane }
317+
refocusIfNeeded $ state { displayTestsPane = not state.displayTestsPane }
318+
VtyEvent (EvKey direction _) | direction == KPageUp || direction == KPageDown -> do
319+
state <- get
320+
let vp = case state.focusedPane of
321+
TestsPane -> viewportScroll TestsViewPort
322+
LogPane -> viewportScroll LogViewPort
323+
vScrollBy vp (if direction == KPageDown then 10 else -10)
324+
VtyEvent (EvKey k []) | k == KChar '\t' || k == KBackTab ->
325+
-- just two panes, so both keybindings just toggle the active one
326+
modify' toggleFocus
302327
VtyEvent (EvKey KEsc _) -> halt
303328
VtyEvent (EvKey (KChar 'c') l) | MCtrl `elem` l -> halt
304329
MouseDown (SBClick el n) _ _ _ ->
305330
case n of
306331
TestsViewPort -> do
332+
modify' $ \state -> state { focusedPane = TestsPane }
307333
let vp = viewportScroll TestsViewPort
308334
case el of
309335
SBHandleBefore -> vScrollBy vp (-1)
@@ -312,6 +338,7 @@ monitor = do
312338
SBTroughAfter -> vScrollBy vp 10
313339
SBBar -> pure ()
314340
LogViewPort -> do
341+
modify' $ \state -> state { focusedPane = LogPane }
315342
let vp = viewportScroll LogViewPort
316343
case el of
317344
SBHandleBefore -> vScrollBy vp (-1)

lib/Echidna/UI/Widgets.hs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ data UIState = UIState
4949
, displayFetchedDialog :: Bool
5050
, displayLogPane :: Bool
5151
, displayTestsPane :: Bool
52+
, focusedPane :: FocusedPane
5253

5354
, events :: Seq (LocalTime, CampaignEvent)
5455
, workersAlive :: Int
@@ -64,6 +65,9 @@ data UIState = UIState
6465

6566
data UIStateStatus = Uninitialized | Running
6667

68+
data FocusedPane = TestsPane | LogPane
69+
deriving (Eq)
70+
6771
attrs :: A.AttrMap
6872
attrs = A.attrMap (V.white `on` V.black)
6973
[ (attrName "alert", fg V.brightRed `V.withStyle` V.blink `V.withStyle` V.bold)
@@ -117,15 +121,13 @@ campaignStatus uiState = do
117121
summaryWidget env uiState
118122
<=>
119123
(if uiState.displayTestsPane then
120-
hBorderWithLabel (withAttr (attrName "subtitle") $ str $
121-
(" Tests (" <> show (length uiState.tests)) <> ") ")
124+
hBorderWithLabel testsTitle
122125
<=>
123126
inner
124127
else emptyWidget)
125128
<=>
126129
(if uiState.displayLogPane then
127-
hBorderWithLabel (withAttr (attrName "subtitle") $ str $
128-
" Log (" <> show (length uiState.events) <> ") ")
130+
hBorderWithLabel logTitle
129131
<=>
130132
logPane uiState
131133
else emptyWidget)
@@ -140,6 +142,14 @@ campaignStatus uiState = do
140142
(str $ "Echidna " <> showVersion Paths_echidna.version <> projectTitle) <+>
141143
str " ]"
142144
finalStatus s = hBorder <=> hCenter (bold $ str s)
145+
testsTitle =
146+
withAttr (attrName "subtitle") $ str $
147+
" Tests (" <> show (length uiState.tests) <> ") " <>
148+
if uiState.focusedPane == TestsPane then "[*]" else ""
149+
logTitle =
150+
withAttr (attrName "subtitle") $ str $
151+
" Log (" <> show (length uiState.events) <> ") " <>
152+
if uiState.focusedPane == LogPane then "[*]" else ""
143153

144154
logPane :: UIState -> Widget Name
145155
logPane uiState =

0 commit comments

Comments
 (0)