@@ -20,6 +20,7 @@ import Cabal.Matrix.ProcessRunner
2020import Cabal.Matrix.Record
2121import Cabal.Matrix.Scheduler
2222import Cabal.Matrix.Tui.Common
23+ import Control.Monad
2324import Data.Bifunctor
2425import Data.ByteString (ByteString )
2526import Data.Function
@@ -67,44 +68,73 @@ statusColor ss = case ss.exit of
6768 Just ExitSuccess -> brightGreen
6869 Just (ExitFailure _) -> brightRed
6970
70- stepOutputWidget :: DisplayRegion -> StepState -> Image
71- stepOutputWidget (width, height) ss = header <-> output
71+ data ComputedHeader = ComputedHeader
72+ { height :: Int
73+ , image :: Image
74+ }
75+
76+ computeHeader :: DisplayRegion -> StepState -> ComputedHeader
77+ computeHeader (width, _height) ss = ComputedHeader
78+ { image = vertCat
79+ [ horizCat $ padToCommonHeight
80+ [ ( headerBg
81+ , ' '
82+ , text' (headerBg `withForeColor` brightYellow) commandText
83+ )
84+ , ( headerBg
85+ , ' '
86+ , vertCat
87+ [ resizeWidthFill headerBg ' ' (width - safeWctwidth commandText)
88+ $ text' headerBg line
89+ | line <- wrappedCommandText
90+ ]
91+ )
92+ ]
93+ , resizeWidthFill headerBg ' ' width
94+ $ text' (headerBg `withForeColor` brightYellow) " Status: " <|>
95+ case ss. exit of
96+ _ | not ss. started -> text' status " Pending"
97+ Nothing -> text' status " Running"
98+ Just ExitSuccess -> text' status " Completed successfully"
99+ Just (ExitFailure code) -> text' status
100+ $ " Failed with exit code " <> Text. pack (show code)
101+ , charFill (headerBg `withForeColor` brightYellow) borderEW width 1
102+ ]
103+ , height = length wrappedCommandText + 1 + 1
104+ -- ^ should be equal to @imageHeight image@
105+ }
72106 where
73107 commandText = " Command: "
74108 headerBg = defAttr `withBackColor` blue
109+ wrappedCommandText = wrap (width - safeWctwidth commandText)
110+ $ Text. unwords $ NonEmpty. toList ss. cmdline
75111 status = headerBg `withForeColor` statusColor ss
76- header = vertCat
77- [ horizCat $ padToCommonHeight
78- [ ( headerBg
79- , ' '
80- , text' (headerBg `withForeColor` brightYellow) commandText
81- )
82- , ( headerBg
83- , ' '
84- , vertCat
85- [ resizeWidthFill headerBg ' ' (width - safeWctwidth commandText)
86- $ text' headerBg line
87- | line <- wrap (width - safeWctwidth commandText)
88- $ Text. unwords $ NonEmpty. toList ss. cmdline
89- ]
90- )
91- ]
92- , resizeWidthFill headerBg ' ' width
93- $ text' (headerBg `withForeColor` brightYellow) " Status: " <|>
94- case ss. exit of
95- _ | not ss. started -> text' status " Pending"
96- Nothing -> text' status " Running"
97- Just ExitSuccess -> text' status " Completed successfully"
98- Just (ExitFailure code) -> text' status
99- $ " Failed with exit code " <> Text. pack (show code)
100- , charFill (headerBg `withForeColor` brightYellow) borderEW width 1
101- ]
102- output = resize width (height - imageHeight header) $ vertCat
103- [ text' defAttr line
104- | line <- reverse . take (height - imageHeight header) . reverse
105- $ wrap width
106- $ Text. concat $ Text. decodeUtf8Lenient . snd <$> reverse ss. revOutput
107- ]
112+
113+ data ComputedOutput = ComputedOutput
114+ { wantedHeight :: Int
115+ , availableHeight :: Int
116+ , image :: Image
117+ }
118+
119+ computeOutput
120+ :: DisplayRegion
121+ -> ScrollState
122+ -> StepState
123+ -> ComputedOutput
124+ computeOutput (width, height) mScroll ss = ComputedOutput
125+ { wantedHeight = length wrappedOutputLines
126+ , availableHeight = height
127+ , image = resize width height $ vertCat
128+ [ text' defAttr line
129+ | line <- wrappedOutputLines
130+ & case mScroll of
131+ Nothing -> reverse . take height . reverse
132+ Just scroll -> take height . drop scroll
133+ ]
134+ }
135+ where
136+ wrappedOutputLines = wrap width
137+ $ Text. concat $ Text. decodeUtf8Lenient . snd <$> reverse ss. revOutput
108138
109139data TimerEvent = TimerEvent
110140
@@ -149,8 +179,13 @@ flavorHandleTimerEvent :: TimerEvent -> FlavorState -> FlavorState
149179flavorHandleTimerEvent ev fs
150180 = tabulateCabalStep' \ step -> stepHandleTimerEvent ev $ indexCabalStep fs step
151181
152- newtype OutputState = OutputState
182+ type ScrollState = Maybe Int
183+ -- ^ Nothing = scrolled to the bottom. Otherwise, number of word-wrapped
184+ -- lines to hide from the top.
185+
186+ data OutputState = OutputState
153187 { selectedStep :: CabalStep
188+ , scrollState :: ScrollState
154189 }
155190
156191initOutputState :: FlavorState -> OutputState
@@ -159,11 +194,26 @@ initOutputState fs = OutputState
159194 (\ step -> indexCabalStep fs step
160195 & \ ss -> ss. started && ss. exit /= Just ExitSuccess )
161196 [minBound .. maxBound ]
197+ , scrollState = Nothing
162198 }
163199
164- outputWidget
165- :: DisplayRegion -> PerCabalStep Bool -> FlavorState -> OutputState -> Image
166- outputWidget (width, height) enabledSteps fs os = tabSwitcher <-> output
200+ data ComputedWidget = ComputedWidget
201+ { wantedOutputHeight :: Int
202+ , availableOutputHeight :: Int
203+ , image :: Image
204+ }
205+
206+ computeOutputWidget
207+ :: DisplayRegion
208+ -> PerCabalStep Bool
209+ -> FlavorState
210+ -> OutputState
211+ -> ComputedWidget
212+ computeOutputWidget (width, height) enabledSteps fs os = ComputedWidget
213+ { wantedOutputHeight = output. wantedHeight
214+ , availableOutputHeight = output. availableHeight
215+ , image = tabSwitcher <-> header. image <-> output. image
216+ }
167217 where
168218 stepName = \ case
169219 DryRun -> " Planning"
@@ -175,22 +225,36 @@ outputWidget (width, height) enabledSteps fs os = tabSwitcher <-> output
175225 (if step == os. selectedStep
176226 then defAttr `withBackColor` blue
177227 else defAttr `withBackColor` brightBlack)
178- (if ss. started && isNothing ss. exit
179- then stepName step <> " " <> outputSpinner ss
228+ (if ss' . started && isNothing ss' . exit
229+ then stepName step <> " " <> outputSpinner ss'
180230 else stepName step)
181231 | step <- [minBound .. maxBound ]
182232 , indexCabalStep enabledSteps step || step == os. selectedStep
183- , let ss = indexCabalStep fs step
233+ , let ss' = indexCabalStep fs step
184234 ]
185- output = stepOutputWidget (width, height - imageHeight tabSwitcher)
186- (indexCabalStep fs os. selectedStep)
235+ tabSwitcherHeight = 1 -- should be equal to @imageHeight tabSwitcher@
236+
237+ ss = indexCabalStep fs os. selectedStep
238+ header = computeHeader (width, height - tabSwitcherHeight) ss
239+ output = computeOutput (width, height - tabSwitcherHeight - header. height)
240+ os. scrollState ss
241+
242+ outputWidget
243+ :: DisplayRegion -> PerCabalStep Bool -> FlavorState -> OutputState -> Image
244+ outputWidget displayRegion enabledSteps fs os
245+ = (computeOutputWidget displayRegion enabledSteps fs os). image
187246
188247outputSpinner :: StepState -> Text
189248outputSpinner ss = Text. singleton $ " |/-\\ " !! (ss. outputCount `mod` 4 )
190249
191250outputHandleEvent
192- :: PerCabalStep Bool -> Event -> OutputState -> OutputState
193- outputHandleEvent enabledSteps ev os = case ev of
251+ :: DisplayRegion
252+ -> PerCabalStep Bool
253+ -> Event
254+ -> FlavorState
255+ -> OutputState
256+ -> OutputState
257+ outputHandleEvent displayRegion enabledSteps ev fs os = case ev of
194258 -- Make sure to do something sensible if all steps are disabled
195259 EvKey KLeft _ -> os
196260 { selectedStep = fromMaybe os. selectedStep $ listToMaybe
@@ -199,6 +263,7 @@ outputHandleEvent enabledSteps ev os = case ev of
199263 , indexCabalStep enabledSteps step
200264 , step < os. selectedStep
201265 ]
266+ , scrollState = Nothing
202267 }
203268 EvKey KRight _ -> os
204269 { selectedStep = fromMaybe os. selectedStep $ listToMaybe
@@ -207,8 +272,30 @@ outputHandleEvent enabledSteps ev os = case ev of
207272 , indexCabalStep enabledSteps step
208273 , step > os. selectedStep
209274 ]
275+ , scrollState = Nothing
276+ }
277+ EvKey KUp _ -> os
278+ { scrollState = performScroll (- 1 )
279+ }
280+ EvKey KDown _ -> os
281+ { scrollState = performScroll 1
282+ }
283+ EvKey KPageUp _ -> os
284+ { scrollState = performScroll (- output. availableOutputHeight)
285+ }
286+ EvKey KPageDown _ -> os
287+ { scrollState = performScroll output. availableOutputHeight
210288 }
211289 _ -> os
290+ where
291+ output = computeOutputWidget displayRegion enabledSteps fs os
292+ performScroll by
293+ = mfilter (<= output. wantedOutputHeight - output. availableOutputHeight)
294+ $ Just
295+ $ max 0
296+ $ (+ by)
297+ $ fromMaybe (output. wantedOutputHeight - output. availableOutputHeight)
298+ $ os. scrollState
212299
213300outputKeybinds :: [(Text , Text )]
214301outputKeybinds =
0 commit comments