From 9c1d168361ed6579c04c236e74f3aecb443aceae Mon Sep 17 00:00:00 2001 From: Divam Date: Sat, 6 Jul 2024 13:33:47 +0900 Subject: [PATCH 01/41] Allow menu to be multi-layered, move frontwardLayer + title to default draw --- lib-tui/GHCup/Brick/App.hs | 6 +++--- lib-tui/GHCup/Brick/Widgets/Menu.hs | 13 ++++++++----- lib-tui/GHCup/Brick/Widgets/Menus/AdvanceInstall.hs | 6 +++--- lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs | 6 +++--- lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs | 6 +++--- lib-tui/GHCup/Brick/Widgets/Menus/Context.hs | 4 ++-- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/lib-tui/GHCup/Brick/App.hs b/lib-tui/GHCup/Brick/App.hs index 2b1d5ddd..55cbc6d7 100644 --- a/lib-tui/GHCup/Brick/App.hs +++ b/lib-tui/GHCup/Brick/App.hs @@ -94,9 +94,9 @@ drawUI dimAttrs st = Tutorial -> [Tutorial.draw, navg] KeyInfo -> [KeyInfo.draw (st ^. appKeys), navg] ContextPanel -> [ContextMenu.draw (st ^. contextMenu), navg] - AdvanceInstallPanel -> [AdvanceInstall.draw (st ^. advanceInstallMenu), navg] - CompileGHCPanel -> [CompileGHC.draw (st ^. compileGHCMenu), navg] - CompileHLSPanel -> [CompileHLS.draw (st ^. compileHLSMenu), navg] + AdvanceInstallPanel -> AdvanceInstall.draw (st ^. advanceInstallMenu) ++ [navg] + CompileGHCPanel -> CompileGHC.draw (st ^. compileGHCMenu) ++ [navg] + CompileHLSPanel -> CompileHLS.draw (st ^. compileHLSMenu) ++ [navg] -- | On q, go back to navigation. -- On Enter, to go to tutorial diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index 5a0d1ba1..a9fe7505 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -291,12 +291,14 @@ data Menu s n , menuFocusRing :: FocusRing n -- ^ The focus ring with the resource name for each entry and each button, in the order you want to loop them. , menuExitKey :: KeyCombination -- ^ The key to exit the Menu , menuName :: n -- ^ The resource Name. + , menuTitle :: T.Text -- ^ Menu title. } makeLensesFor [ ("menuFields", "menuFieldsL"), ("menuState", "menuStateL"), ("menuValidator", "menuValidatorL") , ("menuButtons", "menuButtonsL"), ("menuFocusRing", "menuFocusRingL") , ("menuExitKey", "menuExitKeyL"), ("menuName", "menuNameL") + , ("menuTitle", "menuTitleL") ] ''Menu @@ -304,9 +306,9 @@ isValidMenu :: Menu s n -> Bool isValidMenu m = (all isValidField $ menuFields m) && (case (menuValidator m) (menuState m) of { Nothing -> True; _ -> False }) -createMenu :: n -> s -> (s -> Maybe ErrorMessage) +createMenu :: n -> s -> T.Text -> (s -> Maybe ErrorMessage) -> KeyCombination -> [Button s n] -> [MenuField s n] -> Menu s n -createMenu n initial validator exitK buttons fields = Menu fields initial validator buttons ring exitK n +createMenu n initial title validator exitK buttons fields = Menu fields initial validator buttons ring exitK n title where ring = F.focusRing $ [field & fieldName | field <- fields] ++ [button & fieldName | button <- buttons] handlerMenu :: forall n e s. Eq n => BrickEvent n e -> EventM n (Menu s n) () @@ -346,9 +348,11 @@ handlerMenu ev = else pure x -drawMenu :: (Eq n, Ord n, Show n, Brick.Named (MenuField s n) n) => Menu s n -> Widget n +drawMenu :: (Eq n, Ord n, Show n, Brick.Named (MenuField s n) n) => Menu s n -> [Widget n] drawMenu menu = - Brick.vBox + [Common.frontwardLayer (menu ^. menuTitleL) mainLayer] + where + mainLayer = Brick.vBox [ Brick.vBox buttonWidgets , Common.separator , Brick.vLimit (length fieldLabels) $ Brick.withVScrollBars Brick.OnRight @@ -360,7 +364,6 @@ drawMenu menu = <+> Common.keyToWidget (menu ^. menuExitKeyL) <+> Brick.txt " to go back" ] - where fieldLabels = [field & fieldLabel | field <- menu ^. menuFieldsL] buttonLabels = [button & fieldLabel | button <- menu ^. menuButtonsL] allLabels = fieldLabels ++ buttonLabels diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/AdvanceInstall.hs b/lib-tui/GHCup/Brick/Widgets/Menus/AdvanceInstall.hs index c9fc8635..47156803 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/AdvanceInstall.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/AdvanceInstall.hs @@ -66,7 +66,7 @@ makeLensesFor [ type AdvanceInstallMenu = Menu InstallOptions Name create :: KeyCombination -> AdvanceInstallMenu -create k = Menu.createMenu AdvanceInstallBox initialState validator k [ok] fields +create k = Menu.createMenu AdvanceInstallBox initialState "Advance Install" validator k [ok] fields where initialState = InstallOptions Nothing False Nothing False [] validator InstallOptions {..} = case (instSet, isolateDir) of @@ -114,5 +114,5 @@ handler :: BrickEvent Name e -> EventM Name AdvanceInstallMenu () handler = Menu.handlerMenu -draw :: AdvanceInstallMenu -> Widget Name -draw = Common.frontwardLayer "Advance Install" . Menu.drawMenu +draw :: AdvanceInstallMenu -> [Widget Name] +draw = Menu.drawMenu diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs index 2fb8d729..1a9d239c 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs @@ -80,7 +80,7 @@ makeLenses ''CompileGHCOptions type CompileGHCMenu = Menu CompileGHCOptions Name create :: KeyCombination -> CompileGHCMenu -create k = Menu.createMenu CompileGHCBox initialState validator k buttons fields +create k = Menu.createMenu CompileGHCBox initialState "Compile GHC" validator k buttons fields where initialState = CompileGHCOptions @@ -215,5 +215,5 @@ handler :: BrickEvent Name e -> EventM Name CompileGHCMenu () handler = Menu.handlerMenu -draw :: CompileGHCMenu -> Widget Name -draw = Common.frontwardLayer "Compile GHC" . Menu.drawMenu +draw :: CompileGHCMenu -> [Widget Name] +draw = Menu.drawMenu diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs index 08dae3c5..32b556e2 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs @@ -74,7 +74,7 @@ makeLenses ''CompileHLSOptions type CompileHLSMenu = Menu CompileHLSOptions Name create :: KeyCombination -> CompileHLSMenu -create k = Menu.createMenu CompileGHCBox initialState validator k buttons fields +create k = Menu.createMenu CompileGHCBox initialState "Compile HLS" validator k buttons fields where initialState = CompileHLSOptions @@ -186,5 +186,5 @@ handler :: BrickEvent Name e -> EventM Name CompileHLSMenu () handler = Menu.handlerMenu -draw :: CompileHLSMenu -> Widget Name -draw = Common.frontwardLayer "Compile HLS" . Menu.drawMenu +draw :: CompileHLSMenu -> [Widget Name] +draw = Menu.drawMenu diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/Context.hs b/lib-tui/GHCup/Brick/Widgets/Menus/Context.hs index 5a5a3067..f4e00af4 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/Context.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/Context.hs @@ -29,7 +29,7 @@ import Data.Foldable (foldl') type ContextMenu = Menu ListResult Name create :: ListResult -> KeyCombination -> ContextMenu -create lr exit_key = Menu.createMenu Common.ContextBox lr validator exit_key buttons [] +create lr exit_key = Menu.createMenu Common.ContextBox lr "" validator exit_key buttons [] where advInstallButton = Menu.createButtonField (MenuElement Common.AdvanceInstallButton) @@ -80,4 +80,4 @@ draw menu = Stack -> "Stack" handler :: BrickEvent Name e -> EventM Name ContextMenu () -handler = Menu.handlerMenu \ No newline at end of file +handler = Menu.handlerMenu From a119d37a4ecde56ecfa482deed1a1dbc0594d17a Mon Sep 17 00:00:00 2001 From: Divam Date: Sat, 6 Jul 2024 11:38:34 +0900 Subject: [PATCH 02/41] FieldInput can optionally create an overlay, which will receive all VtyEvents --- lib-tui/GHCup/Brick/Widgets/Menu.hs | 55 ++++++++++++++++++----------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index a9fe7505..1d773833 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -64,6 +64,7 @@ import qualified Brick.Focus as F import Data.Function ( (&)) import Prelude hiding ( appendFile ) +import Data.Maybe import qualified Data.Text as T @@ -75,7 +76,7 @@ import Optics.State (use) import GHCup.Types (KeyCombination) import Optics (Lens', to, lens) import Optics.Operators ( (^.), (.~) ) -import Data.Foldable (foldl') +import Data.Foldable (find, foldl') -- | Just some type synonym to make things explicit @@ -113,7 +114,7 @@ data FieldInput a b n = -> HelpMessage -> b -> (Widget n -> Widget n) - -> Widget n -- ^ How to draw the input, with focus a help message and input. + -> (Widget n, Maybe (Widget n)) -- ^ How to draw the input and optionally an overlay, with focus a help message and input. -- A extension function can be applied too , inputHandler :: BrickEvent n () -> EventM n b () -- ^ The handler } @@ -155,10 +156,14 @@ fieldHelpMsgL = lens g s -- | How to draw a field given a formater drawField :: Formatter n -> Bool -> MenuField s n -> Widget n drawField amp focus (MenuField { fieldInput = FieldInput {..}, ..}) = - let input = inputRender focus fieldStatus inputHelp inputState (amp focus) - in if focus - then Common.enableScreenReader fieldName $ Brick.visible input - else input + let (input, overlay) = inputRender focus fieldStatus inputHelp inputState (amp focus) + in case (focus, overlay) of + (True, Nothing) -> Common.enableScreenReader fieldName $ Brick.visible input + _ -> input + +drawFieldOverlay :: MenuField s n -> Maybe (Widget n) +drawFieldOverlay (MenuField { fieldInput = FieldInput {..}, ..}) = + snd $ inputRender True fieldStatus inputHelp inputState id instance Brick.Named (MenuField s n) n where getName :: MenuField s n -> n @@ -179,7 +184,7 @@ createCheckBoxInput = FieldInput False Right "" checkBoxRender checkBoxHandler if b then border . Brick.withAttr Attributes.installedAttr $ Brick.str Common.installedSign else border . Brick.withAttr Attributes.notInstalledAttr $ Brick.str Common.notInstalledSign - checkBoxRender focus _ help check f = + checkBoxRender focus _ help check f = (, Nothing) $ let core = f $ drawBool check in if focus then core @@ -200,7 +205,7 @@ type EditableField = MenuField createEditableInput :: (Ord n, Show n) => n -> (T.Text -> Either ErrorMessage a) -> FieldInput a (Edit.Editor T.Text n) n createEditableInput name validator = FieldInput initEdit validateEditContent "" drawEdit Edit.handleEditorEvent where - drawEdit focus errMsg help edi amp = + drawEdit focus errMsg help edi amp = (, Nothing) $ let borderBox w = amp (Brick.vLimit 1 $ Border.vBorder <+> Brick.padRight Brick.Max w <+> Border.vBorder) editorRender = Edit.renderEditor (Brick.txt . T.unlines) focus edi @@ -229,8 +234,8 @@ type Button = MenuField createButtonInput :: FieldInput () () n createButtonInput = FieldInput () Right "" drawButton (const $ pure ()) where - drawButton True (Invalid err) _ _ amp = amp . centerV . renderAsErrMsg $ err - drawButton _ _ help _ amp = amp . centerV . renderAsHelpMsg $ help + drawButton True (Invalid err) _ _ amp = (amp . centerV . renderAsErrMsg $ err, Nothing) + drawButton _ _ help _ amp = (amp . centerV . renderAsHelpMsg $ help, Nothing) createButtonField :: n -> Button s n createButtonField = MenuField emptyLens createButtonInput "" Valid @@ -312,16 +317,11 @@ createMenu n initial title validator exitK buttons fields = Menu fields initial where ring = F.focusRing $ [field & fieldName | field <- fields] ++ [button & fieldName | button <- buttons] handlerMenu :: forall n e s. Eq n => BrickEvent n e -> EventM n (Menu s n) () -handlerMenu ev = - case ev of - VtyEvent (Vty.EvKey (Vty.KChar '\t') []) -> menuFocusRingL %= F.focusNext - VtyEvent (Vty.EvKey Vty.KBackTab []) -> menuFocusRingL %= F.focusPrev - VtyEvent (Vty.EvKey Vty.KDown []) -> menuFocusRingL %= F.focusNext - VtyEvent (Vty.EvKey Vty.KUp []) -> menuFocusRingL %= F.focusPrev - VtyEvent e -> do - focused <- use $ menuFocusRingL % to F.focusGetCurrent - fields <- use menuFieldsL - case focused of +handlerMenu ev = do + fields <- use menuFieldsL + focused <- use $ menuFocusRingL % to F.focusGetCurrent + let focusedField = (\n -> find (\x -> Brick.getName x == n) fields) =<< focused + propagateEvent e = case focused of Nothing -> pure () Just n -> do updated_fields <- updateFields n (VtyEvent e) fields @@ -333,7 +333,17 @@ handlerMenu ev = Just err -> menuButtonsL %= fmap (fieldStatusL .~ Invalid err) else menuButtonsL %= fmap (fieldStatusL .~ Invalid "Some fields are invalid") menuFieldsL .= updated_fields - _ -> pure () + case (drawFieldOverlay =<< focusedField) of + Just _ -> case ev of + VtyEvent e -> propagateEvent e + _ -> pure () + Nothing -> case ev of + VtyEvent (Vty.EvKey (Vty.KChar '\t') []) -> menuFocusRingL %= F.focusNext + VtyEvent (Vty.EvKey Vty.KBackTab []) -> menuFocusRingL %= F.focusPrev + VtyEvent (Vty.EvKey Vty.KDown []) -> menuFocusRingL %= F.focusNext + VtyEvent (Vty.EvKey Vty.KUp []) -> menuFocusRingL %= F.focusPrev + VtyEvent e -> propagateEvent e + _ -> pure () where -- runs the Event with the inner handler of MenuField. updateFields :: n -> BrickEvent n () -> [MenuField s n] -> EventM n (Menu s n) [MenuField s n] @@ -350,6 +360,7 @@ handlerMenu ev = drawMenu :: (Eq n, Ord n, Show n, Brick.Named (MenuField s n) n) => Menu s n -> [Widget n] drawMenu menu = + overlays ++ [Common.frontwardLayer (menu ^. menuTitleL) mainLayer] where mainLayer = Brick.vBox @@ -382,3 +393,5 @@ drawMenu menu = in fmap (\f b -> ((leftify (maxWidth + 2) . Border.border $ f b) <+>) ) buttonAsWidgets drawButtons = fmap drawField buttonAmplifiers buttonWidgets = zipWith (F.withFocusRing (menu ^. menuFocusRingL)) drawButtons (menu ^. menuButtonsL) + + overlays = catMaybes $ fmap drawFieldOverlay (menu ^. menuFieldsL) From ebb834ab71533533c6b1692db3b528a25a654af8 Mon Sep 17 00:00:00 2001 From: Divam Date: Wed, 10 Jul 2024 17:27:54 +0900 Subject: [PATCH 03/41] Add SelectField --- lib-tui/GHCup/Brick/Widgets/Menu.hs | 111 +++++++++++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index 1d773833..0368148a 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -57,6 +57,8 @@ import Brick (<+>)) import qualified Brick import qualified Brick.Widgets.Border as Border +import qualified Brick.Widgets.Border.Style as Border +import qualified Brick.Widgets.Center as Brick import qualified Brick.Widgets.List as L import qualified Brick.Widgets.Edit as Edit import Brick.Focus (FocusRing) @@ -73,10 +75,12 @@ import qualified Graphics.Vty as Vty import Optics.State.Operators ((%=), (.=)) import Optics.Optic ((%)) import Optics.State (use) -import GHCup.Types (KeyCombination) +import GHCup.Types (KeyCombination(..)) import Optics (Lens', to, lens) import Optics.Operators ( (^.), (.~) ) import Data.Foldable (find, foldl') +import Data.List.NonEmpty ( NonEmpty (..) ) +import qualified Data.List.NonEmpty as NE -- | Just some type synonym to make things explicit @@ -147,6 +151,19 @@ makeLensesFor ] ''MenuField +data SelectState i = SelectState + { selectStateItems :: NonEmpty (Int, (i, Bool)) -- ^ All items along with their selected state + , selectStateFocusRing :: FocusRing Int -- ^ Focus ring using integeral values assigned to each item + , selectStateOverlayOpen :: Bool -- ^ Whether the select menu is open + } + +makeLensesFor + [ ("selectStateItems", "selectStateItemsL") + , ("selectStateFocusRing", "selectStateFocusRingL") + , ("selectStateOverlayOpen", "selectStateOverlayOpenL") + ] + ''SelectState + -- | A fancy lens to the help message fieldHelpMsgL :: Lens' (MenuField s n) HelpMessage fieldHelpMsgL = lens g s @@ -240,6 +257,89 @@ createButtonInput = FieldInput () Right "" drawButton (const $ pure ()) createButtonField :: n -> Button s n createButtonField = MenuField emptyLens createButtonInput "" Valid +{- ***************** + Select widget +***************** -} + +type SelectField = MenuField + +createSelectInput + :: NonEmpty i + -> (i -> T.Text) + -> (Int -> NonEmpty (Int, (i, Bool)) -> NonEmpty (Int, (i, Bool))) + -> ([i] -> k) + -> Label + -> KeyCombination + -> FieldInput k (SelectState i) n +createSelectInput items showItem updateSelection getSelection label exitKey@(KeyCombination {..}) + = FieldInput initState (Right . getSelection . getSelectedItems) "" selectRender selectHandler + where + initState = SelectState + (NE.zip (1 NE.:| [2..]) $ fmap (,False) items) + (F.focusRing [1..(length items)]) + False + getSelectedItems = fmap (fst . snd) . (filter (snd . snd)) . NE.toList . selectStateItems + + border w = Brick.txt "[" <+> (Brick.padRight (Brick.Pad 1) $ Brick.padLeft (Brick.Pad 1) w) <+> Brick.txt "]" + selectRender focus errMsg help s amp = (field, mOverlay) + where + field = amp $ case getSelectedItems s of + [] -> (Brick.padLeft (Brick.Pad 1) . renderAsHelpMsg $ help) + xs -> + let list = border $ Brick.hBox $ fmap (Brick.padRight (Brick.Pad 1) . Brick.txt . showItem) xs + in if focus + then list + else list <+> (Brick.padLeft (Brick.Pad 1) . renderAsHelpMsg $ help) + mOverlay = if selectStateOverlayOpen s + then Just (overlayLayer ("Select " <> label) $ overlay s) + else Nothing + overlay (SelectState {..}) = Brick.vBox $ + (NE.toList $ fmap (mkSelectRow focused) selectStateItems) ++ + [ Brick.padRight Brick.Max $ + Brick.txt "Press " + <+> Common.keyToWidget exitKey + <+> Brick.txt " to go back" + ] + where focused = fromMaybe 1 $ F.focusGetCurrent selectStateFocusRing + mkSelectRow focused (ix, (item, selected)) = + Brick.txt "[" <+> (Brick.padRight (Brick.Pad 1) $ Brick.padLeft (Brick.Pad 1) m) <+> Brick.txt "] " + <+> (renderAslabel (showItem item) (focused == ix)) + where m = if selected then Brick.txt "*" else Brick.txt " " + + selectHandler ev = do + s <- Brick.get + if selectStateOverlayOpen s + then case ev of + VtyEvent (Vty.EvKey k m) | k == key && m == mods -> selectStateOverlayOpenL .= False + VtyEvent (Vty.EvKey (Vty.KChar '\t') []) -> selectStateFocusRingL %= F.focusNext + VtyEvent (Vty.EvKey Vty.KBackTab []) -> selectStateFocusRingL %= F.focusPrev + VtyEvent (Vty.EvKey Vty.KDown []) -> selectStateFocusRingL %= F.focusNext + VtyEvent (Vty.EvKey Vty.KUp []) -> selectStateFocusRingL %= F.focusPrev + VtyEvent (Vty.EvKey Vty.KEnter []) -> do + focused <- use (selectStateFocusRingL % to F.focusGetCurrent) + selectStateItemsL %= updateSelection (fromMaybe 1 focused) + _ -> pure () + else case ev of + VtyEvent (Vty.EvKey Vty.KEnter []) -> selectStateOverlayOpenL .= True + _ -> pure () + +-- | Select Field with only single selection possible, aka radio button +createSelectField :: n -> Lens' s (Maybe i) -> NonEmpty i -> (i -> T.Text) -> Label -> KeyCombination -> SelectField s n +createSelectField name access items showItem label exitKey = MenuField access (createSelectInput items showItem singleSelect getSelection label exitKey) label Valid name + where + singleSelect :: Int -> NonEmpty (Int, (i, Bool)) -> NonEmpty (Int, (i, Bool)) + singleSelect ix = fmap (\(ix', (i, b)) -> if ix' == ix then (ix', (i, True)) else (ix', (i, False))) + + getSelection = fmap NE.head . NE.nonEmpty + +-- | Select Field with multiple selections possible +createMultiSelectField :: n -> Lens' s [i] -> NonEmpty i -> (i -> T.Text) -> Label -> KeyCombination -> SelectField s n +createMultiSelectField name access items showItem label exitKey = MenuField access (createSelectInput items showItem multiSelect id label exitKey) label Valid name + where + multiSelect :: Int -> NonEmpty (Int, (i, Bool)) -> NonEmpty (Int, (i, Bool)) + multiSelect ix = fmap (\(ix', (i, b)) -> if ix' == ix then (ix', (i, not b)) else (ix', (i, b))) + + {- ***************** Utilities ***************** -} @@ -281,6 +381,15 @@ renderAsHelpMsg = Brick.withAttr Attributes.helpMsgAttr . Brick.txt renderAsErrMsg :: T.Text -> Widget n renderAsErrMsg = Brick.withAttr Attributes.errMsgAttr . Brick.txt +-- | Used to create a layer on top of menu +overlayLayer :: T.Text -> Brick.Widget n -> Brick.Widget n +overlayLayer layer_name = + Brick.centerLayer + . Brick.hLimitPercent 70 + . Brick.vLimitPercent 65 + . Brick.withBorderStyle Border.unicode + . Border.borderWithLabel (Brick.txt layer_name) + {- ***************** Menu widget ***************** -} From 1951209d1cfba62106fb9a29fc96acacb00fd351 Mon Sep 17 00:00:00 2001 From: Divam Date: Wed, 10 Jul 2024 17:28:21 +0900 Subject: [PATCH 04/41] compileHLSHandler pass events to overlay --- lib-tui/GHCup/Brick/App.hs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib-tui/GHCup/Brick/App.hs b/lib-tui/GHCup/Brick/App.hs index 55cbc6d7..8b852dea 100644 --- a/lib-tui/GHCup/Brick/App.hs +++ b/lib-tui/GHCup/Brick/App.hs @@ -170,11 +170,13 @@ compileHLSHandler :: BrickEvent Name e -> EventM Name BrickState () compileHLSHandler ev = do ctx <- use compileHLSMenu let focusedElement = ctx ^. Menu.menuFocusRingL % to F.focusGetCurrent + focusedField = (\n -> find (\x -> Brick.getName x == n) $ ctx ^. Menu.menuFieldsL) =<< focusedElement (KeyCombination exitKey mods) = ctx ^. Menu.menuExitKeyL - case (ev, focusedElement) of - (_ , Nothing) -> pure () - (VtyEvent (Vty.EvKey k m), Just n) | k == exitKey && m == mods -> mode .= ContextPanel - (VtyEvent (Vty.EvKey Vty.KEnter []), Just (MenuElement Common.OkButton)) -> do + case (ev, focusedElement, Menu.drawFieldOverlay =<< focusedField) of + (_ , Nothing, _) -> pure () + (_ , _, Just _) -> Common.zoom compileHLSMenu $ CompileHLS.handler ev + (VtyEvent (Vty.EvKey k m), Just n, _) | k == exitKey && m == mods -> mode .= ContextPanel + (VtyEvent (Vty.EvKey Vty.KEnter []), Just (MenuElement Common.OkButton), _) -> do let iopts = ctx ^. Menu.menuStateL when (Menu.isValidMenu ctx) (Actions.withIOAction $ Actions.compileHLS iopts) From 8dd944d5573055782d95ad33dd06fe09100665df Mon Sep 17 00:00:00 2001 From: Divam Date: Wed, 10 Jul 2024 17:28:47 +0900 Subject: [PATCH 05/41] Use selectField in CompileHLS Target GHCs --- .../GHCup/Brick/Widgets/Menus/CompileHLS.hs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs index 32b556e2..2c6893b5 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs @@ -48,12 +48,14 @@ import GHCup.Types (KeyCombination, VersionPattern, ToolVersion) import URI.ByteString (URI) import qualified Data.Text as T import Data.Bifunctor (Bifunctor(..)) +import qualified Data.List.NonEmpty as NE import Data.Function ((&)) import Optics ((.~)) import Data.Char (isSpace) import Control.Applicative (Alternative((<|>))) import Text.Read (readEither) import qualified GHCup.Utils.Parsers as Utils +import Text.PrettyPrint.HughesPJClass ( prettyShow ) data CompileHLSOptions = CompileHLSOptions { _jobs :: Maybe Int @@ -73,8 +75,8 @@ makeLenses ''CompileHLSOptions type CompileHLSMenu = Menu CompileHLSOptions Name -create :: KeyCombination -> CompileHLSMenu -create k = Menu.createMenu CompileGHCBox initialState "Compile HLS" validator k buttons fields +create :: KeyCombination -> [ToolVersion] -> CompileHLSMenu +create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile HLS" validator k buttons fields where initialState = CompileHLSOptions @@ -140,6 +142,15 @@ create k = Menu.createMenu CompileGHCBox initialState "Compile HLS" validator k additionalValidator :: T.Text -> Either Menu.ErrorMessage [T.Text] additionalValidator = Right . T.split isSpace + targetGHCsField = + let label = "target GHC(s)" + in case NE.nonEmpty availableGHCs of + Just ne -> Menu.createMultiSelectField (Common.MenuElement Common.TargetGhcEditBox) targetGHCs ne (T.pack . prettyShow) label k + & Menu.fieldHelpMsgL .~ "GHC versions to compile for (Press Enter to edit)" + _ -> Menu.createEditableField (Common.MenuElement Common.TargetGhcEditBox) ghcVersionTagEither targetGHCs + & Menu.fieldLabelL .~ label + & Menu.fieldHelpMsgL .~ "space separated list of GHC versions to compile for" + fields = [ Menu.createCheckBoxField (Common.MenuElement Common.UpdateCabalCheckBox) updateCabal & Menu.fieldLabelL .~ "cabal update" @@ -147,9 +158,7 @@ create k = Menu.createMenu CompileGHCBox initialState "Compile HLS" validator k , Menu.createEditableField (Common.MenuElement Common.JobsEditBox) jobsV jobs & Menu.fieldLabelL .~ "jobs" & Menu.fieldHelpMsgL .~ "How many jobs to use for make" - , Menu.createEditableField (Common.MenuElement Common.TargetGhcEditBox) ghcVersionTagEither targetGHCs - & Menu.fieldLabelL .~ "target GHC(s)" - & Menu.fieldHelpMsgL .~ "space separated list of GHC versions to compile for" + , targetGHCsField , Menu.createCheckBoxField (Common.MenuElement Common.SetCheckBox) setCompile & Menu.fieldLabelL .~ "set" & Menu.fieldHelpMsgL .~ "Set as active version after install" From 463912b1e7225f29974278eb7925974831e3110c Mon Sep 17 00:00:00 2001 From: Divam Date: Wed, 10 Jul 2024 17:31:43 +0900 Subject: [PATCH 06/41] provide installedGHCs --- lib-tui/GHCup/BrickMain.hs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib-tui/GHCup/BrickMain.hs b/lib-tui/GHCup/BrickMain.hs index c0f91c74..4f76cc6c 100644 --- a/lib-tui/GHCup/BrickMain.hs +++ b/lib-tui/GHCup/BrickMain.hs @@ -15,8 +15,9 @@ This module contains the entrypoint for the brick application and nothing else. module GHCup.BrickMain where +import GHCup.List ( ListResult (..)) import GHCup.Types - ( Settings(noColor), + ( Settings(noColor), ToolVersion(..), Tool (GHC), AppState(ghcupInfo, settings, keyBindings, loggerConfig), KeyCombination (KeyCombination) ) import GHCup.Prelude.Logger ( logError ) import qualified GHCup.Brick.Actions as Actions @@ -62,6 +63,8 @@ brickMain s = do BrickApp.app (Attributes.defaultAttributes $ noColor $ settings s) (Attributes.dimAttributes $ noColor $ settings s) + installedGHCs = fmap (ToolVersion . lVer) $ + filter (\(ListResult {..}) -> lInstalled && lTool == GHC) (Common._lr ad) initstate = AppState.BrickState ad Common.defaultAppSettings @@ -69,7 +72,7 @@ brickMain s = do (ContextMenu.create e exit_key) (AdvanceInstall.create exit_key) (CompileGHC.create exit_key) - (CompileHLS.create exit_key) + (CompileHLS.create exit_key installedGHCs) (keyBindings s) Common.Navigation in Brick.defaultMain initapp initstate From 09bee3d82a0db4d662fcbf25aea88ce951e1ae0f Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 12 Jul 2024 12:34:16 +0900 Subject: [PATCH 07/41] Reduce layer size --- lib-tui/GHCup/Brick/Widgets/Menu.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index 0368148a..2d07a182 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -385,7 +385,7 @@ renderAsErrMsg = Brick.withAttr Attributes.errMsgAttr . Brick.txt overlayLayer :: T.Text -> Brick.Widget n -> Brick.Widget n overlayLayer layer_name = Brick.centerLayer - . Brick.hLimitPercent 70 + . Brick.hLimitPercent 50 . Brick.vLimitPercent 65 . Brick.withBorderStyle Border.unicode . Border.borderWithLabel (Brick.txt layer_name) From f91be28b92a84e7bc8b7245e4f8eb85ff9d7280a Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 12 Jul 2024 13:06:40 +0900 Subject: [PATCH 08/41] Make the select menu list scrollable --- lib-tui/GHCup/Brick/Widgets/Menu.hs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index 2d07a182..5519f1df 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -263,15 +263,16 @@ createButtonField = MenuField emptyLens createButtonInput "" Valid type SelectField = MenuField -createSelectInput - :: NonEmpty i +createSelectInput :: (Ord n, Show n) + => NonEmpty i -> (i -> T.Text) -> (Int -> NonEmpty (Int, (i, Bool)) -> NonEmpty (Int, (i, Bool))) -> ([i] -> k) -> Label + -> n -> KeyCombination -> FieldInput k (SelectState i) n -createSelectInput items showItem updateSelection getSelection label exitKey@(KeyCombination {..}) +createSelectInput items showItem updateSelection getSelection label fieldName exitKey@(KeyCombination {..}) = FieldInput initState (Right . getSelection . getSelectedItems) "" selectRender selectHandler where initState = SelectState @@ -294,14 +295,16 @@ createSelectInput items showItem updateSelection getSelection label exitKey@(Key then Just (overlayLayer ("Select " <> label) $ overlay s) else Nothing overlay (SelectState {..}) = Brick.vBox $ - (NE.toList $ fmap (mkSelectRow focused) selectStateItems) ++ [ Brick.padRight Brick.Max $ Brick.txt "Press " <+> Common.keyToWidget exitKey <+> Brick.txt " to go back" + , Brick.vLimit (length items) $ Brick.withVScrollBars Brick.OnRight + $ Brick.viewport fieldName Brick.Vertical + $ Brick.vBox $ (NE.toList $ fmap (mkSelectRow focused) selectStateItems) ] where focused = fromMaybe 1 $ F.focusGetCurrent selectStateFocusRing - mkSelectRow focused (ix, (item, selected)) = + mkSelectRow focused (ix, (item, selected)) = (if focused == ix then Brick.visible else id) $ Brick.txt "[" <+> (Brick.padRight (Brick.Pad 1) $ Brick.padLeft (Brick.Pad 1) m) <+> Brick.txt "] " <+> (renderAslabel (showItem item) (focused == ix)) where m = if selected then Brick.txt "*" else Brick.txt " " @@ -324,8 +327,8 @@ createSelectInput items showItem updateSelection getSelection label exitKey@(Key _ -> pure () -- | Select Field with only single selection possible, aka radio button -createSelectField :: n -> Lens' s (Maybe i) -> NonEmpty i -> (i -> T.Text) -> Label -> KeyCombination -> SelectField s n -createSelectField name access items showItem label exitKey = MenuField access (createSelectInput items showItem singleSelect getSelection label exitKey) label Valid name +createSelectField :: (Ord n, Show n) => n -> Lens' s (Maybe i) -> NonEmpty i -> (i -> T.Text) -> Label -> KeyCombination -> SelectField s n +createSelectField name access items showItem label exitKey = MenuField access (createSelectInput items showItem singleSelect getSelection label name exitKey) label Valid name where singleSelect :: Int -> NonEmpty (Int, (i, Bool)) -> NonEmpty (Int, (i, Bool)) singleSelect ix = fmap (\(ix', (i, b)) -> if ix' == ix then (ix', (i, True)) else (ix', (i, False))) @@ -333,8 +336,8 @@ createSelectField name access items showItem label exitKey = MenuField access (c getSelection = fmap NE.head . NE.nonEmpty -- | Select Field with multiple selections possible -createMultiSelectField :: n -> Lens' s [i] -> NonEmpty i -> (i -> T.Text) -> Label -> KeyCombination -> SelectField s n -createMultiSelectField name access items showItem label exitKey = MenuField access (createSelectInput items showItem multiSelect id label exitKey) label Valid name +createMultiSelectField :: (Ord n, Show n) => n -> Lens' s [i] -> NonEmpty i -> (i -> T.Text) -> Label -> KeyCombination -> SelectField s n +createMultiSelectField name access items showItem label exitKey = MenuField access (createSelectInput items showItem multiSelect id label name exitKey) label Valid name where multiSelect :: Int -> NonEmpty (Int, (i, Bool)) -> NonEmpty (Int, (i, Bool)) multiSelect ix = fmap (\(ix', (i, b)) -> if ix' == ix then (ix', (i, not b)) else (ix', (i, b))) From bc5356ff3469d21478de5a1f8ccee2e7554da34e Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 12 Jul 2024 13:15:40 +0900 Subject: [PATCH 09/41] Minor fix to help message --- lib-opt/GHCup/OptParse/Compile.hs | 2 +- lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib-opt/GHCup/OptParse/Compile.hs b/lib-opt/GHCup/OptParse/Compile.hs index c7345806..83a4970a 100644 --- a/lib-opt/GHCup/OptParse/Compile.hs +++ b/lib-opt/GHCup/OptParse/Compile.hs @@ -218,7 +218,7 @@ ghcCompileOpts = ( long "hadrian-ghc" <> metavar "HADRIAN_GHC" <> help - "The GHC version (or full path) to GHC that will be used to compile hadrian (must be installed)" + "The GHC version (or full path) that will be used to compile hadrian (must be installed)" <> (completer $ versionCompleter [] GHC) )) <*> optional diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs index 1a9d239c..863a1ab1 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs @@ -168,7 +168,7 @@ create k = Menu.createMenu CompileGHCBox initialState "Compile GHC" validator k & Menu.fieldStatusL .~ Menu.Invalid "Invalid Empty value" , Menu.createEditableField (Common.MenuElement Common.HadrianGhcEditBox) hadrianstrapV hadrianGhc & Menu.fieldLabelL .~ "hadrian-ghc" - & Menu.fieldHelpMsgL .~ "The GHC version (or full path) to GHC that will be used to compile hadrian (must be installed)" + & Menu.fieldHelpMsgL .~ "The GHC version (or full path) that will be used to compile hadrian (must be installed)" , Menu.createEditableField (Common.MenuElement Common.JobsEditBox) jobsV jobs & Menu.fieldLabelL .~ "jobs" & Menu.fieldHelpMsgL .~ "How many jobs to use for make" From 51f1d3d4fad722d9f88f8e64d85b849c69cfaeb0 Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 12 Jul 2024 14:55:23 +0900 Subject: [PATCH 10/41] Pass key events to overlay in compileGHCHandler --- lib-tui/GHCup/Brick/App.hs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib-tui/GHCup/Brick/App.hs b/lib-tui/GHCup/Brick/App.hs index 8b852dea..3b018314 100644 --- a/lib-tui/GHCup/Brick/App.hs +++ b/lib-tui/GHCup/Brick/App.hs @@ -155,17 +155,18 @@ compileGHCHandler :: BrickEvent Name e -> EventM Name BrickState () compileGHCHandler ev = do ctx <- use compileGHCMenu let focusedElement = ctx ^. Menu.menuFocusRingL % to F.focusGetCurrent + focusedField = (\n -> find (\x -> Brick.getName x == n) $ ctx ^. Menu.menuFieldsL) =<< focusedElement (KeyCombination exitKey mods) = ctx ^. Menu.menuExitKeyL - case (ev, focusedElement) of - (_ , Nothing) -> pure () - (VtyEvent (Vty.EvKey k m), Just n) | k == exitKey && m == mods -> mode .= ContextPanel - (VtyEvent (Vty.EvKey Vty.KEnter []), Just (MenuElement Common.OkButton)) -> do + case (ev, focusedElement, Menu.drawFieldOverlay =<< focusedField) of + (_ , Nothing, _) -> pure () + (_ , _, Just _) -> Common.zoom compileGHCMenu $ CompileGHC.handler ev + (VtyEvent (Vty.EvKey k m), Just n, _) | k == exitKey && m == mods -> mode .= ContextPanel + (VtyEvent (Vty.EvKey Vty.KEnter []), Just (MenuElement Common.OkButton), _) -> do let iopts = ctx ^. Menu.menuStateL when (Menu.isValidMenu ctx) (Actions.withIOAction $ Actions.compileGHC iopts) _ -> Common.zoom compileGHCMenu $ CompileGHC.handler ev - compileHLSHandler :: BrickEvent Name e -> EventM Name BrickState () compileHLSHandler ev = do ctx <- use compileHLSMenu From ea893ccd8f9aade4a164297fa384007c3e77ba1a Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 12 Jul 2024 14:56:39 +0900 Subject: [PATCH 11/41] Use selectField for Build system selection --- .../GHCup/Brick/Widgets/Menus/CompileGHC.hs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs index 863a1ab1..97a17c3c 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs @@ -48,11 +48,13 @@ import qualified GHCup.Brick.Common as Common import GHCup.Types ( KeyCombination, BuildSystem(..), VersionPattern ) import URI.ByteString (URI) +import Control.Monad (join) import qualified Data.Text as T import Data.Bifunctor (Bifunctor(..)) import Data.Function ((&)) -import Optics ((.~)) +import Optics ((.~), iso, (%)) import Data.Char (isSpace) +import Data.List.NonEmpty ( NonEmpty (..) ) import Data.Versions (Version, version) import System.FilePath (isPathSeparator) import Control.Applicative (Alternative((<|>))) @@ -153,13 +155,11 @@ create k = Menu.createMenu CompileGHCBox initialState "Compile GHC" validator k additionalValidator :: T.Text -> Either Menu.ErrorMessage [T.Text] additionalValidator = Right . T.split isSpace - systemV :: T.Text -> Either Menu.ErrorMessage (Maybe BuildSystem) - systemV = whenEmpty Nothing readSys - where - readSys i - | T.toLower i == "hadrian" = Right $ Just Hadrian - | T.toLower i == "make" = Right $ Just Make - | otherwise = Left "Not a valid Build System" + showMaybeBuildSystem :: Maybe BuildSystem -> T.Text + showMaybeBuildSystem = \case + Nothing -> "Auto select (prefer hadrian if available, and build config is not specified)" + Just Hadrian -> "hadrian" + Just Make -> "make" fields = [ Menu.createEditableField (Common.MenuElement Common.BootstrapGhcEditBox) bootstrapV bootstrapGhc @@ -190,9 +190,9 @@ create k = Menu.createMenu CompileGHCBox initialState "Compile GHC" validator k , Menu.createEditableField (Common.MenuElement Common.CrossTargetEditBox) (Right . Just) crossTarget & Menu.fieldLabelL .~ "cross target" & Menu.fieldHelpMsgL .~ "Build cross-compiler for this platform" - , Menu.createEditableField (Common.MenuElement Common.BuildSystemEditBox) systemV buildSystem + , Menu.createSelectField (Common.MenuElement Common.BuildSystemEditBox) (buildSystem % (iso Just join)) (Nothing :| [Just Hadrian, Just Make]) showMaybeBuildSystem "" k & Menu.fieldLabelL .~ "build system" - & Menu.fieldHelpMsgL .~ "either 'make' or 'hadrian'" + & Menu.fieldHelpMsgL .~ "Select the build system" , Menu.createEditableField (Common.MenuElement Common.OvewrwiteVerEditBox) versionV overwriteVer & Menu.fieldLabelL .~ "overwrite-version" & Menu.fieldHelpMsgL .~ "Allows to overwrite the finally installed VERSION with a different one" From 4a1306e44e9a05699243b0116ea0f8c18e29325f Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 12 Jul 2024 15:56:46 +0900 Subject: [PATCH 12/41] Edit input overlay support --- lib-tui/GHCup/Brick/Widgets/Menu.hs | 73 +++++++++++++++++++++-------- 1 file changed, 53 insertions(+), 20 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index 5519f1df..cff5bd50 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -164,6 +164,17 @@ makeLensesFor ] ''SelectState +data EditState n = EditState + { editState :: Edit.Editor T.Text n + , editStateOverlayOpen :: Bool -- ^ Whether the edit menu is open + } + +makeLensesFor + [ ("editState", "editStateL") + , ("editStateOverlayOpen", "editStateOverlayOpenL") + ] + ''EditState + -- | A fancy lens to the help message fieldHelpMsgL :: Lens' (MenuField s n) HelpMessage fieldHelpMsgL = lens g s @@ -219,28 +230,50 @@ createCheckBoxField name access = MenuField access createCheckBoxInput "" Valid type EditableField = MenuField -createEditableInput :: (Ord n, Show n) => n -> (T.Text -> Either ErrorMessage a) -> FieldInput a (Edit.Editor T.Text n) n -createEditableInput name validator = FieldInput initEdit validateEditContent "" drawEdit Edit.handleEditorEvent +createEditableInput :: (Ord n, Show n) => n -> (T.Text -> Either ErrorMessage a) -> KeyCombination -> FieldInput a (EditState n) n +createEditableInput name validator exitKey@(KeyCombination {..}) = FieldInput initEdit validateEditContent "" drawEdit handler where - drawEdit focus errMsg help edi amp = (, Nothing) $ - let - borderBox w = amp (Brick.vLimit 1 $ Border.vBorder <+> Brick.padRight Brick.Max w <+> Border.vBorder) - editorRender = Edit.renderEditor (Brick.txt . T.unlines) focus edi - isEditorEmpty = Edit.getEditContents edi == [mempty] - in case errMsg of - Valid | isEditorEmpty -> borderBox $ renderAsHelpMsg help - | otherwise -> borderBox editorRender - Invalid msg - | focus && isEditorEmpty -> borderBox $ renderAsHelpMsg help - | focus -> borderBox editorRender - | otherwise -> borderBox $ renderAsErrMsg msg - validateEditContent = validator . T.init . T.unlines . Edit.getEditContents - initEdit = Edit.editorText name (Just 1) "" - -createEditableField :: (Eq n, Ord n, Show n) => n -> (T.Text -> Either ErrorMessage a) -> Lens' s a -> EditableField s n -createEditableField name validator access = MenuField access input "" Valid name + drawEdit focus errMsg help (EditState edi overlayOpen) amp = (field, mOverlay) + where + field = + let + borderBox w = amp (Brick.vLimit 1 $ Border.vBorder <+> Brick.padRight Brick.Max w <+> Border.vBorder) + editorContents = Brick.txt $ T.unlines $ Edit.getEditContents edi + isEditorEmpty = Edit.getEditContents edi == [mempty] + in case errMsg of + Valid | isEditorEmpty -> borderBox $ renderAsHelpMsg help + | otherwise -> borderBox editorContents + Invalid msg + | focus && isEditorEmpty -> borderBox $ renderAsHelpMsg help + | focus -> borderBox editorContents + | otherwise -> borderBox $ renderAsErrMsg msg + mOverlay = if overlayOpen + then Just (overlayLayer ("Edit") $ overlay) + else Nothing + overlay = Brick.vBox $ + [ Edit.renderEditor (Brick.txt . T.unlines) focus edi + , Brick.txt " " + , Brick.padRight Brick.Max $ + Brick.txt "Press " + <+> Common.keyToWidget exitKey + <+> Brick.txt " to go back" + ] + handler ev = do + (EditState edi overlayOpen) <- Brick.get + if overlayOpen + then case ev of + VtyEvent (Vty.EvKey k m) | k == key && m == mods -> editStateOverlayOpenL .= False + _ -> Common.zoom editStateL $ Edit.handleEditorEvent ev + else case ev of + VtyEvent (Vty.EvKey Vty.KEnter []) -> editStateOverlayOpenL .= True + _ -> pure () + validateEditContent = validator . T.init . T.unlines . Edit.getEditContents . editState + initEdit = EditState (Edit.editorText name (Just 1) "") False + +createEditableField :: (Eq n, Ord n, Show n) => n -> (T.Text -> Either ErrorMessage a) -> Lens' s a -> KeyCombination -> EditableField s n +createEditableField name validator access exitKey = MenuField access input "" Valid name where - input = createEditableInput name validator + input = createEditableInput name validator exitKey {- ***************** Button widget From 0dbc97a481f54316d46b5cb4d8b5a490647ef7f2 Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 12 Jul 2024 15:57:52 +0900 Subject: [PATCH 13/41] Pass exitKey to createEditableField --- .../Brick/Widgets/Menus/AdvanceInstall.hs | 6 ++--- .../GHCup/Brick/Widgets/Menus/CompileGHC.hs | 22 +++++++++---------- .../GHCup/Brick/Widgets/Menus/CompileHLS.hs | 18 +++++++-------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/AdvanceInstall.hs b/lib-tui/GHCup/Brick/Widgets/Menus/AdvanceInstall.hs index 47156803..4b6f4363 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/AdvanceInstall.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/AdvanceInstall.hs @@ -89,19 +89,19 @@ create k = Menu.createMenu AdvanceInstallBox initialState "Advance Install" vali additionalValidator = Right . T.split isSpace fields = - [ Menu.createEditableField (Common.MenuElement Common.UrlEditBox) uriValidator instBindistL + [ Menu.createEditableField (Common.MenuElement Common.UrlEditBox) uriValidator instBindistL k & Menu.fieldLabelL .~ "url" & Menu.fieldHelpMsgL .~ "Install the specified version from this bindist" , Menu.createCheckBoxField (Common.MenuElement Common.SetCheckBox) instSetL & Menu.fieldLabelL .~ "set" & Menu.fieldHelpMsgL .~ "Set as active version after install" - , Menu.createEditableField (Common.MenuElement Common.IsolateEditBox) filepathValidator isolateDirL + , Menu.createEditableField (Common.MenuElement Common.IsolateEditBox) filepathValidator isolateDirL k & Menu.fieldLabelL .~ "isolated" & Menu.fieldHelpMsgL .~ "install in an isolated absolute directory instead of the default one" , Menu.createCheckBoxField (Common.MenuElement Common.ForceCheckBox) forceInstallL & Menu.fieldLabelL .~ "force" & Menu.fieldHelpMsgL .~ "Force install (THIS IS UNSAFE, only use it in Dockerfiles or CI)" - , Menu.createEditableField (Common.MenuElement Common.AdditionalEditBox) additionalValidator addConfArgsL + , Menu.createEditableField (Common.MenuElement Common.AdditionalEditBox) additionalValidator addConfArgsL k & Menu.fieldLabelL .~ "CONFIGURE_ARGS" & Menu.fieldHelpMsgL .~ "Additional arguments to bindist configure" ] diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs index 97a17c3c..cdfb7785 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs @@ -162,44 +162,44 @@ create k = Menu.createMenu CompileGHCBox initialState "Compile GHC" validator k Just Make -> "make" fields = - [ Menu.createEditableField (Common.MenuElement Common.BootstrapGhcEditBox) bootstrapV bootstrapGhc + [ Menu.createEditableField (Common.MenuElement Common.BootstrapGhcEditBox) bootstrapV bootstrapGhc k & Menu.fieldLabelL .~ "bootstrap-ghc" & Menu.fieldHelpMsgL .~ "The GHC version (or full path) to bootstrap with (must be installed)" & Menu.fieldStatusL .~ Menu.Invalid "Invalid Empty value" - , Menu.createEditableField (Common.MenuElement Common.HadrianGhcEditBox) hadrianstrapV hadrianGhc + , Menu.createEditableField (Common.MenuElement Common.HadrianGhcEditBox) hadrianstrapV hadrianGhc k & Menu.fieldLabelL .~ "hadrian-ghc" & Menu.fieldHelpMsgL .~ "The GHC version (or full path) that will be used to compile hadrian (must be installed)" - , Menu.createEditableField (Common.MenuElement Common.JobsEditBox) jobsV jobs + , Menu.createEditableField (Common.MenuElement Common.JobsEditBox) jobsV jobs k & Menu.fieldLabelL .~ "jobs" & Menu.fieldHelpMsgL .~ "How many jobs to use for make" , Menu.createCheckBoxField (Common.MenuElement Common.SetCheckBox) setCompile & Menu.fieldLabelL .~ "set" & Menu.fieldHelpMsgL .~ "Set as active version after install" - , Menu.createEditableField (Common.MenuElement Common.BuildFlavourEditBox) (Right . Just . T.unpack) buildFlavour + , Menu.createEditableField (Common.MenuElement Common.BuildFlavourEditBox) (Right . Just . T.unpack) buildFlavour k & Menu.fieldLabelL .~ "flavour" & Menu.fieldHelpMsgL .~ "Set the compile build flavour (this value depends on the build system type: 'make' vs 'hadrian')" - , Menu.createEditableField (Common.MenuElement Common.AdditionalEditBox) additionalValidator addConfArgs + , Menu.createEditableField (Common.MenuElement Common.AdditionalEditBox) additionalValidator addConfArgs k & Menu.fieldLabelL .~ "CONFIGURE_ARGS" & Menu.fieldHelpMsgL .~ "Additional arguments to compile configure" - , Menu.createEditableField (Common.MenuElement Common.BuildConfigEditBox) filepathV buildConfig + , Menu.createEditableField (Common.MenuElement Common.BuildConfigEditBox) filepathV buildConfig k & Menu.fieldLabelL .~ "build config" & Menu.fieldHelpMsgL .~ "Absolute path to build config file (make build system only)" - , Menu.createEditableField (Common.MenuElement Common.PatchesEditBox) patchesV patches + , Menu.createEditableField (Common.MenuElement Common.PatchesEditBox) patchesV patches k & Menu.fieldLabelL .~ "patches" & Menu.fieldHelpMsgL .~ "Either a URI to a patch (https/http/file) or Absolute path to patch directory" - , Menu.createEditableField (Common.MenuElement Common.CrossTargetEditBox) (Right . Just) crossTarget + , Menu.createEditableField (Common.MenuElement Common.CrossTargetEditBox) (Right . Just) crossTarget k & Menu.fieldLabelL .~ "cross target" & Menu.fieldHelpMsgL .~ "Build cross-compiler for this platform" , Menu.createSelectField (Common.MenuElement Common.BuildSystemEditBox) (buildSystem % (iso Just join)) (Nothing :| [Just Hadrian, Just Make]) showMaybeBuildSystem "" k & Menu.fieldLabelL .~ "build system" & Menu.fieldHelpMsgL .~ "Select the build system" - , Menu.createEditableField (Common.MenuElement Common.OvewrwiteVerEditBox) versionV overwriteVer + , Menu.createEditableField (Common.MenuElement Common.OvewrwiteVerEditBox) versionV overwriteVer k & Menu.fieldLabelL .~ "overwrite-version" & Menu.fieldHelpMsgL .~ "Allows to overwrite the finally installed VERSION with a different one" - , Menu.createEditableField (Common.MenuElement Common.IsolateEditBox) filepathV isolateDir + , Menu.createEditableField (Common.MenuElement Common.IsolateEditBox) filepathV isolateDir k & Menu.fieldLabelL .~ "isolated" & Menu.fieldHelpMsgL .~ "install in an isolated absolute directory instead of the default one" - , Menu.createEditableField (Common.MenuElement Common.GitRefEditBox) (Right . Just . T.unpack) gitRef + , Menu.createEditableField (Common.MenuElement Common.GitRefEditBox) (Right . Just . T.unpack) gitRef k & Menu.fieldLabelL .~ "git-ref" & Menu.fieldHelpMsgL .~ "The git commit/branch/ref to build from" ] diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs index 2c6893b5..73f040e9 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs @@ -147,7 +147,7 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile HLS in case NE.nonEmpty availableGHCs of Just ne -> Menu.createMultiSelectField (Common.MenuElement Common.TargetGhcEditBox) targetGHCs ne (T.pack . prettyShow) label k & Menu.fieldHelpMsgL .~ "GHC versions to compile for (Press Enter to edit)" - _ -> Menu.createEditableField (Common.MenuElement Common.TargetGhcEditBox) ghcVersionTagEither targetGHCs + _ -> Menu.createEditableField (Common.MenuElement Common.TargetGhcEditBox) ghcVersionTagEither targetGHCs k & Menu.fieldLabelL .~ label & Menu.fieldHelpMsgL .~ "space separated list of GHC versions to compile for" @@ -155,32 +155,32 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile HLS [ Menu.createCheckBoxField (Common.MenuElement Common.UpdateCabalCheckBox) updateCabal & Menu.fieldLabelL .~ "cabal update" & Menu.fieldHelpMsgL .~ "Run 'cabal update' before the build" - , Menu.createEditableField (Common.MenuElement Common.JobsEditBox) jobsV jobs + , Menu.createEditableField (Common.MenuElement Common.JobsEditBox) jobsV jobs k & Menu.fieldLabelL .~ "jobs" & Menu.fieldHelpMsgL .~ "How many jobs to use for make" , targetGHCsField , Menu.createCheckBoxField (Common.MenuElement Common.SetCheckBox) setCompile & Menu.fieldLabelL .~ "set" & Menu.fieldHelpMsgL .~ "Set as active version after install" - , Menu.createEditableField (Common.MenuElement Common.AdditionalEditBox) additionalValidator cabalArgs + , Menu.createEditableField (Common.MenuElement Common.AdditionalEditBox) additionalValidator cabalArgs k & Menu.fieldLabelL .~ "CABAL_ARGS" & Menu.fieldHelpMsgL .~ "Additional arguments to cabal install" - , Menu.createEditableField (Common.MenuElement Common.IsolateEditBox) filepathV isolateDir + , Menu.createEditableField (Common.MenuElement Common.IsolateEditBox) filepathV isolateDir k & Menu.fieldLabelL .~ "isolated" & Menu.fieldHelpMsgL .~ "install in an isolated absolute directory instead of the default one" - , Menu.createEditableField (Common.MenuElement Common.OvewrwiteVerEditBox) overWriteVersionParser overwriteVer + , Menu.createEditableField (Common.MenuElement Common.OvewrwiteVerEditBox) overWriteVersionParser overwriteVer k & Menu.fieldLabelL .~ "overwrite version" & Menu.fieldHelpMsgL .~ "Allows to overwrite the finally installed VERSION with a different one" - , Menu.createEditableField (Common.MenuElement Common.PatchesEditBox) patchesV patches + , Menu.createEditableField (Common.MenuElement Common.PatchesEditBox) patchesV patches k & Menu.fieldLabelL .~ "patches" & Menu.fieldHelpMsgL .~ "Either a URI to a patch (https/http/file) or Absolute path to patch directory" - , Menu.createEditableField (Common.MenuElement Common.CabalProjectEditBox) cabalProjectV cabalProject + , Menu.createEditableField (Common.MenuElement Common.CabalProjectEditBox) cabalProjectV cabalProject k & Menu.fieldLabelL .~ "cabal project" & Menu.fieldHelpMsgL .~ "If relative filepath, specifies the path to cabal.project inside the unpacked HLS tarball/checkout. Otherwise expects a full URI with https/http/file scheme." - , Menu.createEditableField (Common.MenuElement Common.CabalProjectLocalEditBox) cabalProjectLocalV cabalProjectLocal + , Menu.createEditableField (Common.MenuElement Common.CabalProjectLocalEditBox) cabalProjectLocalV cabalProjectLocal k & Menu.fieldLabelL .~ "cabal project local" & Menu.fieldHelpMsgL .~ "URI (https/http/file) to a cabal.project.local to be used for the build. Will be copied over." - , Menu.createEditableField (Common.MenuElement Common.GitRefEditBox) (Right . Just . T.unpack) gitRef + , Menu.createEditableField (Common.MenuElement Common.GitRefEditBox) (Right . Just . T.unpack) gitRef k & Menu.fieldLabelL .~ "git-ref" & Menu.fieldHelpMsgL .~ "The git commit/branch/ref to build from" ] From 01088661eeef72474eb1d9a86989593ba00aad39 Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 12 Jul 2024 17:34:23 +0900 Subject: [PATCH 14/41] Factor out core logic from menu handlers, fix advanceInstallHandler Ctrl+C behaviour --- lib-tui/GHCup/Brick/App.hs | 52 +++++++++++++++----------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/lib-tui/GHCup/Brick/App.hs b/lib-tui/GHCup/Brick/App.hs index 3b018314..790c56ec 100644 --- a/lib-tui/GHCup/Brick/App.hs +++ b/lib-tui/GHCup/Brick/App.hs @@ -35,6 +35,7 @@ import qualified GHCup.Brick.Widgets.Tutorial as Tutorial import qualified GHCup.Brick.Widgets.Menu as Menu import qualified GHCup.Brick.Widgets.Menus.AdvanceInstall as AdvanceInstall +import GHCup.List (ListResult) import GHCup.Types (AppState (AppState, keyBindings), KeyCombination (KeyCombination)) import qualified Brick.Focus as F @@ -48,7 +49,7 @@ import Brick ( ) import qualified Brick import Control.Monad.Reader ( - MonadIO (liftIO), + MonadIO (liftIO), ReaderT, void, ) import Data.IORef (readIORef) @@ -59,6 +60,7 @@ import qualified Graphics.Vty as Vty import qualified Data.Text as T +import Optics (Lens') import Optics.Getter (to) import Optics.Operators ((^.)) import Optics.Optic ((%)) @@ -138,50 +140,36 @@ contextMenuHandler ev = do _ -> Common.zoom contextMenu $ ContextMenu.handler ev -- advanceInstallHandler :: BrickEvent Name e -> EventM Name BrickState () -advanceInstallHandler ev = do - ctx <- use advanceInstallMenu - let focusedElement = ctx ^. Menu.menuFocusRingL % to F.focusGetCurrent - (KeyCombination exitKey mods) = ctx ^. Menu.menuExitKeyL - case (ev, focusedElement) of - (_ , Nothing) -> pure () - (VtyEvent (Vty.EvKey k m), Just n) | k == exitKey && m == mods -> mode .= ContextPanel - (VtyEvent (Vty.EvKey Vty.KEnter []), Just (MenuElement Common.OkButton)) -> do - let iopts = ctx ^. Menu.menuStateL - when (Menu.isValidMenu ctx) $ - Actions.withIOAction $ Actions.installWithOptions iopts - _ -> Common.zoom advanceInstallMenu $ AdvanceInstall.handler ev +advanceInstallHandler = menuWithOverlayHandler advanceInstallMenu Actions.installWithOptions AdvanceInstall.handler compileGHCHandler :: BrickEvent Name e -> EventM Name BrickState () -compileGHCHandler ev = do - ctx <- use compileGHCMenu - let focusedElement = ctx ^. Menu.menuFocusRingL % to F.focusGetCurrent - focusedField = (\n -> find (\x -> Brick.getName x == n) $ ctx ^. Menu.menuFieldsL) =<< focusedElement - (KeyCombination exitKey mods) = ctx ^. Menu.menuExitKeyL - case (ev, focusedElement, Menu.drawFieldOverlay =<< focusedField) of - (_ , Nothing, _) -> pure () - (_ , _, Just _) -> Common.zoom compileGHCMenu $ CompileGHC.handler ev - (VtyEvent (Vty.EvKey k m), Just n, _) | k == exitKey && m == mods -> mode .= ContextPanel - (VtyEvent (Vty.EvKey Vty.KEnter []), Just (MenuElement Common.OkButton), _) -> do - let iopts = ctx ^. Menu.menuStateL - when (Menu.isValidMenu ctx) - (Actions.withIOAction $ Actions.compileGHC iopts) - _ -> Common.zoom compileGHCMenu $ CompileGHC.handler ev +compileGHCHandler = menuWithOverlayHandler compileGHCMenu Actions.compileGHC CompileGHC.handler compileHLSHandler :: BrickEvent Name e -> EventM Name BrickState () -compileHLSHandler ev = do - ctx <- use compileHLSMenu +compileHLSHandler = menuWithOverlayHandler compileHLSMenu Actions.compileHLS CompileHLS.handler + +-- | Passes all events to innerHandler if an overlay is opened +-- else handles the exitKey and Enter key for the Menu's "OkButton" +menuWithOverlayHandler + :: Lens' BrickState (Menu.Menu t Name) + -> (t -> ((Int, ListResult) -> ReaderT AppState IO (Either String a))) + -> (BrickEvent Name e -> EventM Name (Menu.Menu t Name) ()) + -> BrickEvent Name e + -> EventM Name BrickState () +menuWithOverlayHandler accessor action innerHandler ev = do + ctx <- use accessor let focusedElement = ctx ^. Menu.menuFocusRingL % to F.focusGetCurrent focusedField = (\n -> find (\x -> Brick.getName x == n) $ ctx ^. Menu.menuFieldsL) =<< focusedElement (KeyCombination exitKey mods) = ctx ^. Menu.menuExitKeyL case (ev, focusedElement, Menu.drawFieldOverlay =<< focusedField) of (_ , Nothing, _) -> pure () - (_ , _, Just _) -> Common.zoom compileHLSMenu $ CompileHLS.handler ev + (_ , _, Just _) -> Common.zoom accessor $ innerHandler ev (VtyEvent (Vty.EvKey k m), Just n, _) | k == exitKey && m == mods -> mode .= ContextPanel (VtyEvent (Vty.EvKey Vty.KEnter []), Just (MenuElement Common.OkButton), _) -> do let iopts = ctx ^. Menu.menuStateL when (Menu.isValidMenu ctx) - (Actions.withIOAction $ Actions.compileHLS iopts) - _ -> Common.zoom compileHLSMenu $ CompileHLS.handler ev + (Actions.withIOAction $ action iopts) + _ -> Common.zoom accessor $ innerHandler ev eventHandler :: BrickEvent Name e -> EventM Name BrickState () eventHandler ev = do From 33ee7fb137ab4f76385a095f824d528490c325ac Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 12 Jul 2024 17:40:06 +0900 Subject: [PATCH 15/41] Also allow Enter to close the edit text overlay --- lib-tui/GHCup/Brick/Widgets/Menu.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index cff5bd50..78a68a06 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -256,13 +256,14 @@ createEditableInput name validator exitKey@(KeyCombination {..}) = FieldInput in , Brick.padRight Brick.Max $ Brick.txt "Press " <+> Common.keyToWidget exitKey - <+> Brick.txt " to go back" + <+> Brick.txt " or Enter to go back" ] handler ev = do (EditState edi overlayOpen) <- Brick.get if overlayOpen then case ev of VtyEvent (Vty.EvKey k m) | k == key && m == mods -> editStateOverlayOpenL .= False + VtyEvent (Vty.EvKey Vty.KEnter []) -> editStateOverlayOpenL .= False _ -> Common.zoom editStateL $ Edit.handleEditorEvent ev else case ev of VtyEvent (Vty.EvKey Vty.KEnter []) -> editStateOverlayOpenL .= True From 089deadc3a26b3a12e5819f636b4d3f988e7685c Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 12 Jul 2024 17:43:23 +0900 Subject: [PATCH 16/41] Specify that user need to press Enter to edit --- lib-tui/GHCup/Brick/Widgets/Menu.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index 78a68a06..c32f8c79 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -519,7 +519,7 @@ drawMenu menu = , Brick.padRight Brick.Max $ Brick.txt "Press " <+> Common.keyToWidget (menu ^. menuExitKeyL) - <+> Brick.txt " to go back" + <+> Brick.txt " to go back, Press Enter to edit the highlighted field" ] fieldLabels = [field & fieldLabel | field <- menu ^. menuFieldsL] buttonLabels = [button & fieldLabel | button <- menu ^. menuButtonsL] From dd4dad6919a903b27094b4c7a0037ae086f03933 Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 12 Jul 2024 17:52:57 +0900 Subject: [PATCH 17/41] Pass fieldLabel to inputRender --- lib-tui/GHCup/Brick/Widgets/Menu.hs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index c32f8c79..bccf8683 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -116,6 +116,7 @@ data FieldInput a b n = , inputRender :: Bool -> ErrorStatus -> HelpMessage + -> Label -> b -> (Widget n -> Widget n) -> (Widget n, Maybe (Widget n)) -- ^ How to draw the input and optionally an overlay, with focus a help message and input. @@ -184,14 +185,14 @@ fieldHelpMsgL = lens g s -- | How to draw a field given a formater drawField :: Formatter n -> Bool -> MenuField s n -> Widget n drawField amp focus (MenuField { fieldInput = FieldInput {..}, ..}) = - let (input, overlay) = inputRender focus fieldStatus inputHelp inputState (amp focus) + let (input, overlay) = inputRender focus fieldStatus inputHelp fieldLabel inputState (amp focus) in case (focus, overlay) of (True, Nothing) -> Common.enableScreenReader fieldName $ Brick.visible input _ -> input drawFieldOverlay :: MenuField s n -> Maybe (Widget n) drawFieldOverlay (MenuField { fieldInput = FieldInput {..}, ..}) = - snd $ inputRender True fieldStatus inputHelp inputState id + snd $ inputRender True fieldStatus inputHelp fieldLabel inputState id instance Brick.Named (MenuField s n) n where getName :: MenuField s n -> n @@ -212,7 +213,7 @@ createCheckBoxInput = FieldInput False Right "" checkBoxRender checkBoxHandler if b then border . Brick.withAttr Attributes.installedAttr $ Brick.str Common.installedSign else border . Brick.withAttr Attributes.notInstalledAttr $ Brick.str Common.notInstalledSign - checkBoxRender focus _ help check f = (, Nothing) $ + checkBoxRender focus _ help _ check f = (, Nothing) $ let core = f $ drawBool check in if focus then core @@ -233,7 +234,7 @@ type EditableField = MenuField createEditableInput :: (Ord n, Show n) => n -> (T.Text -> Either ErrorMessage a) -> KeyCombination -> FieldInput a (EditState n) n createEditableInput name validator exitKey@(KeyCombination {..}) = FieldInput initEdit validateEditContent "" drawEdit handler where - drawEdit focus errMsg help (EditState edi overlayOpen) amp = (field, mOverlay) + drawEdit focus errMsg help label (EditState edi overlayOpen) amp = (field, mOverlay) where field = let @@ -248,7 +249,7 @@ createEditableInput name validator exitKey@(KeyCombination {..}) = FieldInput in | focus -> borderBox editorContents | otherwise -> borderBox $ renderAsErrMsg msg mOverlay = if overlayOpen - then Just (overlayLayer ("Edit") $ overlay) + then Just (overlayLayer ("Edit " <> label) $ overlay) else Nothing overlay = Brick.vBox $ [ Edit.renderEditor (Brick.txt . T.unlines) focus edi @@ -285,8 +286,8 @@ type Button = MenuField createButtonInput :: FieldInput () () n createButtonInput = FieldInput () Right "" drawButton (const $ pure ()) where - drawButton True (Invalid err) _ _ amp = (amp . centerV . renderAsErrMsg $ err, Nothing) - drawButton _ _ help _ amp = (amp . centerV . renderAsHelpMsg $ help, Nothing) + drawButton True (Invalid err) _ _ _ amp = (amp . centerV . renderAsErrMsg $ err, Nothing) + drawButton _ _ help _ _ amp = (amp . centerV . renderAsHelpMsg $ help, Nothing) createButtonField :: n -> Button s n createButtonField = MenuField emptyLens createButtonInput "" Valid @@ -316,7 +317,7 @@ createSelectInput items showItem updateSelection getSelection label fieldName ex getSelectedItems = fmap (fst . snd) . (filter (snd . snd)) . NE.toList . selectStateItems border w = Brick.txt "[" <+> (Brick.padRight (Brick.Pad 1) $ Brick.padLeft (Brick.Pad 1) w) <+> Brick.txt "]" - selectRender focus errMsg help s amp = (field, mOverlay) + selectRender focus errMsg help label s amp = (field, mOverlay) where field = amp $ case getSelectedItems s of [] -> (Brick.padLeft (Brick.Pad 1) . renderAsHelpMsg $ help) From 85a3e2f14d67badf98f63700d7adc5751d00a670 Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 12 Jul 2024 17:56:28 +0900 Subject: [PATCH 18/41] Remove label from createSelectInput --- lib-tui/GHCup/Brick/Widgets/Menu.hs | 11 +++++------ lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index bccf8683..a09d9640 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -303,11 +303,10 @@ createSelectInput :: (Ord n, Show n) -> (i -> T.Text) -> (Int -> NonEmpty (Int, (i, Bool)) -> NonEmpty (Int, (i, Bool))) -> ([i] -> k) - -> Label -> n -> KeyCombination -> FieldInput k (SelectState i) n -createSelectInput items showItem updateSelection getSelection label fieldName exitKey@(KeyCombination {..}) +createSelectInput items showItem updateSelection getSelection fieldName exitKey@(KeyCombination {..}) = FieldInput initState (Right . getSelection . getSelectedItems) "" selectRender selectHandler where initState = SelectState @@ -362,8 +361,8 @@ createSelectInput items showItem updateSelection getSelection label fieldName ex _ -> pure () -- | Select Field with only single selection possible, aka radio button -createSelectField :: (Ord n, Show n) => n -> Lens' s (Maybe i) -> NonEmpty i -> (i -> T.Text) -> Label -> KeyCombination -> SelectField s n -createSelectField name access items showItem label exitKey = MenuField access (createSelectInput items showItem singleSelect getSelection label name exitKey) label Valid name +createSelectField :: (Ord n, Show n) => n -> Lens' s (Maybe i) -> NonEmpty i -> (i -> T.Text) -> KeyCombination -> SelectField s n +createSelectField name access items showItem exitKey = MenuField access (createSelectInput items showItem singleSelect getSelection name exitKey) "" Valid name where singleSelect :: Int -> NonEmpty (Int, (i, Bool)) -> NonEmpty (Int, (i, Bool)) singleSelect ix = fmap (\(ix', (i, b)) -> if ix' == ix then (ix', (i, True)) else (ix', (i, False))) @@ -371,8 +370,8 @@ createSelectField name access items showItem label exitKey = MenuField access (c getSelection = fmap NE.head . NE.nonEmpty -- | Select Field with multiple selections possible -createMultiSelectField :: (Ord n, Show n) => n -> Lens' s [i] -> NonEmpty i -> (i -> T.Text) -> Label -> KeyCombination -> SelectField s n -createMultiSelectField name access items showItem label exitKey = MenuField access (createSelectInput items showItem multiSelect id label name exitKey) label Valid name +createMultiSelectField :: (Ord n, Show n) => n -> Lens' s [i] -> NonEmpty i -> (i -> T.Text) -> KeyCombination -> SelectField s n +createMultiSelectField name access items showItem exitKey = MenuField access (createSelectInput items showItem multiSelect id name exitKey) "" Valid name where multiSelect :: Int -> NonEmpty (Int, (i, Bool)) -> NonEmpty (Int, (i, Bool)) multiSelect ix = fmap (\(ix', (i, b)) -> if ix' == ix then (ix', (i, not b)) else (ix', (i, b))) diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs index cdfb7785..f16f1dd6 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs @@ -190,7 +190,7 @@ create k = Menu.createMenu CompileGHCBox initialState "Compile GHC" validator k , Menu.createEditableField (Common.MenuElement Common.CrossTargetEditBox) (Right . Just) crossTarget k & Menu.fieldLabelL .~ "cross target" & Menu.fieldHelpMsgL .~ "Build cross-compiler for this platform" - , Menu.createSelectField (Common.MenuElement Common.BuildSystemEditBox) (buildSystem % (iso Just join)) (Nothing :| [Just Hadrian, Just Make]) showMaybeBuildSystem "" k + , Menu.createSelectField (Common.MenuElement Common.BuildSystemEditBox) (buildSystem % (iso Just join)) (Nothing :| [Just Hadrian, Just Make]) showMaybeBuildSystem k & Menu.fieldLabelL .~ "build system" & Menu.fieldHelpMsgL .~ "Select the build system" , Menu.createEditableField (Common.MenuElement Common.OvewrwiteVerEditBox) versionV overwriteVer k From fbb53ccf6859b4ef408f621cf3084bd7e04e20cc Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 12 Jul 2024 18:04:51 +0900 Subject: [PATCH 19/41] Indicate "Press Enter to select" --- lib-tui/GHCup/Brick/Widgets/Menu.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index a09d9640..729bc387 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -332,7 +332,7 @@ createSelectInput items showItem updateSelection getSelection fieldName exitKey@ [ Brick.padRight Brick.Max $ Brick.txt "Press " <+> Common.keyToWidget exitKey - <+> Brick.txt " to go back" + <+> Brick.txt " to go back, Press Enter to select" , Brick.vLimit (length items) $ Brick.withVScrollBars Brick.OnRight $ Brick.viewport fieldName Brick.Vertical $ Brick.vBox $ (NE.toList $ fmap (mkSelectRow focused) selectStateItems) From 0f8fefaca1d433bd76a5a45effa7f81a1d5f2710 Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 12 Jul 2024 18:05:13 +0900 Subject: [PATCH 20/41] Show the help msg over edit field, and error below --- lib-tui/GHCup/Brick/Widgets/Menu.hs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index 729bc387..62463c50 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -252,8 +252,11 @@ createEditableInput name validator exitKey@(KeyCombination {..}) = FieldInput in then Just (overlayLayer ("Edit " <> label) $ overlay) else Nothing overlay = Brick.vBox $ - [ Edit.renderEditor (Brick.txt . T.unlines) focus edi - , Brick.txt " " + [ Brick.txt help + , Border.border $ Edit.renderEditor (Brick.txt . T.unlines) focus edi + , case errMsg of + Invalid msg -> renderAsErrMsg msg + _ -> Brick.txt " " , Brick.padRight Brick.Max $ Brick.txt "Press " <+> Common.keyToWidget exitKey From 44e8f7e632d2e87054295a58db401e33c3ae2fb6 Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 12 Jul 2024 18:30:34 +0900 Subject: [PATCH 21/41] Filter out cross compilers for HLS target GHCs, Bootstrap, and Hadrian GHCs --- lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs | 10 ++++++---- lib-tui/GHCup/BrickMain.hs | 6 +++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs index 73f040e9..8bcac31e 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs @@ -44,7 +44,7 @@ import Brick import Prelude hiding ( appendFile ) import Optics.TH (makeLenses) import qualified GHCup.Brick.Common as Common -import GHCup.Types (KeyCombination, VersionPattern, ToolVersion) +import GHCup.Types (KeyCombination, VersionPattern, ToolVersion(..)) import URI.ByteString (URI) import qualified Data.Text as T import Data.Bifunctor (Bifunctor(..)) @@ -52,6 +52,7 @@ import qualified Data.List.NonEmpty as NE import Data.Function ((&)) import Optics ((.~)) import Data.Char (isSpace) +import Data.Versions import Control.Applicative (Alternative((<|>))) import Text.Read (readEither) import qualified GHCup.Utils.Parsers as Utils @@ -75,7 +76,7 @@ makeLenses ''CompileHLSOptions type CompileHLSMenu = Menu CompileHLSOptions Name -create :: KeyCombination -> [ToolVersion] -> CompileHLSMenu +create :: KeyCombination -> [Version] -> CompileHLSMenu create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile HLS" validator k buttons fields where initialState = @@ -144,8 +145,9 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile HLS targetGHCsField = let label = "target GHC(s)" - in case NE.nonEmpty availableGHCs of - Just ne -> Menu.createMultiSelectField (Common.MenuElement Common.TargetGhcEditBox) targetGHCs ne (T.pack . prettyShow) label k + in case NE.nonEmpty (fmap ToolVersion availableGHCs) of + Just ne -> Menu.createMultiSelectField (Common.MenuElement Common.TargetGhcEditBox) targetGHCs ne (T.pack . prettyShow) k + & Menu.fieldLabelL .~ label & Menu.fieldHelpMsgL .~ "GHC versions to compile for (Press Enter to edit)" _ -> Menu.createEditableField (Common.MenuElement Common.TargetGhcEditBox) ghcVersionTagEither targetGHCs k & Menu.fieldLabelL .~ label diff --git a/lib-tui/GHCup/BrickMain.hs b/lib-tui/GHCup/BrickMain.hs index 4f76cc6c..729b6bbd 100644 --- a/lib-tui/GHCup/BrickMain.hs +++ b/lib-tui/GHCup/BrickMain.hs @@ -17,7 +17,7 @@ module GHCup.BrickMain where import GHCup.List ( ListResult (..)) import GHCup.Types - ( Settings(noColor), ToolVersion(..), Tool (GHC), + ( Settings(noColor), Tool (GHC), AppState(ghcupInfo, settings, keyBindings, loggerConfig), KeyCombination (KeyCombination) ) import GHCup.Prelude.Logger ( logError ) import qualified GHCup.Brick.Actions as Actions @@ -63,8 +63,8 @@ brickMain s = do BrickApp.app (Attributes.defaultAttributes $ noColor $ settings s) (Attributes.dimAttributes $ noColor $ settings s) - installedGHCs = fmap (ToolVersion . lVer) $ - filter (\(ListResult {..}) -> lInstalled && lTool == GHC) (Common._lr ad) + installedGHCs = fmap lVer $ + filter (\(ListResult {..}) -> lInstalled && lTool == GHC && lCross == Nothing) (Common._lr ad) initstate = AppState.BrickState ad Common.defaultAppSettings From 5ba6c37b683799d648193dab50e17bb9d26baec2 Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 12 Jul 2024 18:42:57 +0900 Subject: [PATCH 22/41] Use selectField in bootstrap-ghc, to select from installed GHCs --- .../GHCup/Brick/Widgets/Menus/CompileGHC.hs | 18 ++++++++++++++---- lib-tui/GHCup/BrickMain.hs | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs index f16f1dd6..f8e52596 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs @@ -55,11 +55,13 @@ import Data.Function ((&)) import Optics ((.~), iso, (%)) import Data.Char (isSpace) import Data.List.NonEmpty ( NonEmpty (..) ) +import qualified Data.List.NonEmpty as NE import Data.Versions (Version, version) import System.FilePath (isPathSeparator) import Control.Applicative (Alternative((<|>))) import Text.Read (readEither) import qualified GHCup.Utils.Parsers as Utils +import Text.PrettyPrint.HughesPJClass ( prettyShow ) data CompileGHCOptions = CompileGHCOptions { _bootstrapGhc :: Either Version FilePath @@ -81,8 +83,8 @@ makeLenses ''CompileGHCOptions type CompileGHCMenu = Menu CompileGHCOptions Name -create :: KeyCombination -> CompileGHCMenu -create k = Menu.createMenu CompileGHCBox initialState "Compile GHC" validator k buttons fields +create :: KeyCombination -> [Version] -> CompileGHCMenu +create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile GHC" validator k buttons fields where initialState = CompileGHCOptions @@ -161,11 +163,19 @@ create k = Menu.createMenu CompileGHCBox initialState "Compile GHC" validator k Just Hadrian -> "hadrian" Just Make -> "make" - fields = - [ Menu.createEditableField (Common.MenuElement Common.BootstrapGhcEditBox) bootstrapV bootstrapGhc k + bootstrapGHCField = case NE.nonEmpty availableGHCs of + Just ne -> + let bootstrapGhc' = bootstrapGhc % (iso (either Just (const Nothing)) (maybe (Right "") Left)) + in Menu.createSelectField (Common.MenuElement Common.BootstrapGhcEditBox) bootstrapGhc' ne (T.pack . prettyShow) k + & Menu.fieldLabelL .~ "bootstrap-ghc" + & Menu.fieldHelpMsgL .~ "The GHC version to bootstrap with" + _ -> Menu.createEditableField (Common.MenuElement Common.BootstrapGhcEditBox) bootstrapV bootstrapGhc k & Menu.fieldLabelL .~ "bootstrap-ghc" & Menu.fieldHelpMsgL .~ "The GHC version (or full path) to bootstrap with (must be installed)" & Menu.fieldStatusL .~ Menu.Invalid "Invalid Empty value" + + fields = + [ bootstrapGHCField , Menu.createEditableField (Common.MenuElement Common.HadrianGhcEditBox) hadrianstrapV hadrianGhc k & Menu.fieldLabelL .~ "hadrian-ghc" & Menu.fieldHelpMsgL .~ "The GHC version (or full path) that will be used to compile hadrian (must be installed)" diff --git a/lib-tui/GHCup/BrickMain.hs b/lib-tui/GHCup/BrickMain.hs index 729b6bbd..49dfdafc 100644 --- a/lib-tui/GHCup/BrickMain.hs +++ b/lib-tui/GHCup/BrickMain.hs @@ -71,7 +71,7 @@ brickMain s = do initial_list (ContextMenu.create e exit_key) (AdvanceInstall.create exit_key) - (CompileGHC.create exit_key) + (CompileGHC.create exit_key installedGHCs) (CompileHLS.create exit_key installedGHCs) (keyBindings s) Common.Navigation From 92fc08c7632cfa493276553e2aa6d7f1b7d1dd30 Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 12 Jul 2024 18:49:20 +0900 Subject: [PATCH 23/41] Use selectField in hadrian-ghc --- lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs index f8e52596..3a675b4f 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs @@ -174,11 +174,19 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile GHC & Menu.fieldHelpMsgL .~ "The GHC version (or full path) to bootstrap with (must be installed)" & Menu.fieldStatusL .~ Menu.Invalid "Invalid Empty value" - fields = - [ bootstrapGHCField - , Menu.createEditableField (Common.MenuElement Common.HadrianGhcEditBox) hadrianstrapV hadrianGhc k + hadrianGHCField = case NE.nonEmpty availableGHCs of + Just ne -> + let hadrianGhc' = hadrianGhc % (iso ((=<<) (either Just (const Nothing))) (fmap Left)) + in Menu.createSelectField (Common.MenuElement Common.HadrianGhcEditBox) hadrianGhc' ne (T.pack . prettyShow) k + & Menu.fieldLabelL .~ "hadrian-ghc" + & Menu.fieldHelpMsgL .~ "The GHC version that will be used to compile hadrian" + _ -> Menu.createEditableField (Common.MenuElement Common.HadrianGhcEditBox) hadrianstrapV hadrianGhc k & Menu.fieldLabelL .~ "hadrian-ghc" & Menu.fieldHelpMsgL .~ "The GHC version (or full path) that will be used to compile hadrian (must be installed)" + + fields = + [ bootstrapGHCField + , hadrianGHCField , Menu.createEditableField (Common.MenuElement Common.JobsEditBox) jobsV jobs k & Menu.fieldLabelL .~ "jobs" & Menu.fieldHelpMsgL .~ "How many jobs to use for make" From e6e046e3c61890ae06a24b5af4170581ba90e6bd Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 19 Jul 2024 10:21:44 +0900 Subject: [PATCH 24/41] Wrap text of Edit text field's help message --- lib-tui/GHCup/Brick/Widgets/Menu.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index 62463c50..429af52e 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -252,7 +252,7 @@ createEditableInput name validator exitKey@(KeyCombination {..}) = FieldInput in then Just (overlayLayer ("Edit " <> label) $ overlay) else Nothing overlay = Brick.vBox $ - [ Brick.txt help + [ Brick.txtWrap help , Border.border $ Edit.renderEditor (Brick.txt . T.unlines) focus edi , case errMsg of Invalid msg -> renderAsErrMsg msg From 691feca3162f65d0e7cd20b11e635cf955b344e2 Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 19 Jul 2024 10:25:50 +0900 Subject: [PATCH 25/41] Move target GHCs field first, as it is a required field --- lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs index 8bcac31e..e35c91db 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs @@ -154,13 +154,13 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile HLS & Menu.fieldHelpMsgL .~ "space separated list of GHC versions to compile for" fields = - [ Menu.createCheckBoxField (Common.MenuElement Common.UpdateCabalCheckBox) updateCabal + [ targetGHCsField + , Menu.createCheckBoxField (Common.MenuElement Common.UpdateCabalCheckBox) updateCabal & Menu.fieldLabelL .~ "cabal update" & Menu.fieldHelpMsgL .~ "Run 'cabal update' before the build" , Menu.createEditableField (Common.MenuElement Common.JobsEditBox) jobsV jobs k & Menu.fieldLabelL .~ "jobs" & Menu.fieldHelpMsgL .~ "How many jobs to use for make" - , targetGHCsField , Menu.createCheckBoxField (Common.MenuElement Common.SetCheckBox) setCompile & Menu.fieldLabelL .~ "set" & Menu.fieldHelpMsgL .~ "Set as active version after install" From 5e1f281baa1d003430437ab777ddfa14fdcf7955 Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 19 Jul 2024 10:45:57 +0900 Subject: [PATCH 26/41] Allow multi line help message for buttons --- lib-tui/GHCup/Brick/Widgets/Menu.hs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index 429af52e..435f9970 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -289,8 +289,10 @@ type Button = MenuField createButtonInput :: FieldInput () () n createButtonInput = FieldInput () Right "" drawButton (const $ pure ()) where - drawButton True (Invalid err) _ _ _ amp = (amp . centerV . renderAsErrMsg $ err, Nothing) - drawButton _ _ help _ _ amp = (amp . centerV . renderAsHelpMsg $ help, Nothing) + drawButton True (Invalid err) _ _ _ amp = (amp . renderAsErrMsg $ err, Nothing) + drawButton _ _ help _ _ amp = + let pad = if length (T.lines help) == 1 then Brick.padTop (Brick.Pad 1) else id + in (amp . pad . renderAsHelpMsg $ help, Nothing) createButtonField :: n -> Button s n createButtonField = MenuField emptyLens createButtonInput "" Valid @@ -409,10 +411,6 @@ leftify i = Brick.hLimit i . Brick.padRight Brick.Max rightify :: Int -> Brick.Widget n -> Brick.Widget n rightify i = Brick.hLimit i . Brick.padLeft Brick.Max --- | center a line in three rows. -centerV :: Widget n -> Widget n -centerV = Brick.padTopBottom 1 - -- | render some Text using helpMsgAttr renderAsHelpMsg :: T.Text -> Widget n renderAsHelpMsg = Brick.withAttr Attributes.helpMsgAttr . Brick.txt From 25ee8e4c083d1bf759ceb631d6e543d73926f821 Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 19 Jul 2024 10:47:13 +0900 Subject: [PATCH 27/41] Specify the required fields in the top help message --- lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs | 2 +- lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs index 3a675b4f..146929e3 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs @@ -225,7 +225,7 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile GHC buttons = [ Menu.createButtonField (Common.MenuElement Common.OkButton) & Menu.fieldLabelL .~ "Compile" - & Menu.fieldHelpMsgL .~ "Compile GHC from source with options below" + & Menu.fieldHelpMsgL .~ "Compile GHC from source with options below\nRequired fields: bootstrap-ghc" & Menu.fieldStatusL .~ Menu.Invalid "bootstrap GHC is mandatory" ] diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs index e35c91db..d5f0cf3b 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs @@ -190,7 +190,7 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile HLS buttons = [ Menu.createButtonField (Common.MenuElement Common.OkButton) & Menu.fieldLabelL .~ "Compile" - & Menu.fieldHelpMsgL .~ "Compile HLS from source with options below" + & Menu.fieldHelpMsgL .~ "Compile HLS from source with options below\nRequired fields: target GHC(s)" ] handler :: BrickEvent Name e -> EventM Name CompileHLSMenu () From ec561357af4d1e1fadff067bfe02e63dc57481a1 Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 19 Jul 2024 10:54:20 +0900 Subject: [PATCH 28/41] Include the overwrite patterns in help message since the popup shows the entire message now --- lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs | 2 +- lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs index 146929e3..7e75af4b 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs @@ -213,7 +213,7 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile GHC & Menu.fieldHelpMsgL .~ "Select the build system" , Menu.createEditableField (Common.MenuElement Common.OvewrwiteVerEditBox) versionV overwriteVer k & Menu.fieldLabelL .~ "overwrite-version" - & Menu.fieldHelpMsgL .~ "Allows to overwrite the finally installed VERSION with a different one" + & Menu.fieldHelpMsgL .~ "Allows to overwrite the finally installed VERSION with a different one. Allows to specify patterns: %v (version), %b (branch name), %h (short commit hash), %H (long commit hash), %g ('git describe' output)" , Menu.createEditableField (Common.MenuElement Common.IsolateEditBox) filepathV isolateDir k & Menu.fieldLabelL .~ "isolated" & Menu.fieldHelpMsgL .~ "install in an isolated absolute directory instead of the default one" diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs index d5f0cf3b..fa5c3b7f 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs @@ -172,7 +172,7 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile HLS & Menu.fieldHelpMsgL .~ "install in an isolated absolute directory instead of the default one" , Menu.createEditableField (Common.MenuElement Common.OvewrwiteVerEditBox) overWriteVersionParser overwriteVer k & Menu.fieldLabelL .~ "overwrite version" - & Menu.fieldHelpMsgL .~ "Allows to overwrite the finally installed VERSION with a different one" + & Menu.fieldHelpMsgL .~ "Allows to overwrite the finally installed VERSION with a different one. Allows to specify patterns: %v (version), %b (branch name), %h (short commit hash), %H (long commit hash), %g ('git describe' output)" , Menu.createEditableField (Common.MenuElement Common.PatchesEditBox) patchesV patches k & Menu.fieldLabelL .~ "patches" & Menu.fieldHelpMsgL .~ "Either a URI to a patch (https/http/file) or Absolute path to patch directory" From b339668ec48059c1b02e831365a7f3a8ca8e1221 Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 19 Jul 2024 11:49:02 +0900 Subject: [PATCH 29/41] Make HadrianGhcEditBox resourceId unique to avoid possible conflicts --- lib-tui/GHCup/Brick/Common.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib-tui/GHCup/Brick/Common.hs b/lib-tui/GHCup/Brick/Common.hs index 684220f3..11472935 100644 --- a/lib-tui/GHCup/Brick/Common.hs +++ b/lib-tui/GHCup/Brick/Common.hs @@ -98,7 +98,7 @@ pattern TargetGhcEditBox = ResourceId 6 pattern BootstrapGhcEditBox :: ResourceId pattern BootstrapGhcEditBox = ResourceId 7 pattern HadrianGhcEditBox :: ResourceId -pattern HadrianGhcEditBox = ResourceId 17 +pattern HadrianGhcEditBox = ResourceId 20 pattern JobsEditBox :: ResourceId pattern JobsEditBox = ResourceId 8 pattern BuildConfigEditBox :: ResourceId From 8e87c12164adf2a567a1b8196ff182ded8471935 Mon Sep 17 00:00:00 2001 From: Divam Date: Fri, 19 Jul 2024 12:08:04 +0900 Subject: [PATCH 30/41] Always include editable fields for bootstrap-ghc and hadrian-ghc Such that user can specify paths to ghc or installed version not managed by ghcup --- lib-tui/GHCup/Brick/Common.hs | 6 ++++ .../GHCup/Brick/Widgets/Menus/CompileGHC.hs | 32 +++++++++++-------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/lib-tui/GHCup/Brick/Common.hs b/lib-tui/GHCup/Brick/Common.hs index 11472935..db3b54ff 100644 --- a/lib-tui/GHCup/Brick/Common.hs +++ b/lib-tui/GHCup/Brick/Common.hs @@ -48,6 +48,7 @@ module GHCup.Brick.Common ( , BuildFlavourEditBox, BuildSystemEditBox, OkButton, AdvanceInstallButton , CompileGHCButton, CompileHLSButton, CabalProjectEditBox , CabalProjectLocalEditBox, UpdateCabalCheckBox, GitRefEditBox + , BootstrapGhcSelectBox, HadrianGhcSelectBox ) ) where import GHCup.List ( ListResult ) @@ -126,6 +127,11 @@ pattern UpdateCabalCheckBox = ResourceId 18 pattern GitRefEditBox :: ResourceId pattern GitRefEditBox = ResourceId 19 +pattern BootstrapGhcSelectBox :: ResourceId +pattern BootstrapGhcSelectBox = ResourceId 21 +pattern HadrianGhcSelectBox :: ResourceId +pattern HadrianGhcSelectBox = ResourceId 22 + -- | Name data type. Uniquely identifies each widget in the TUI. -- some constructors might end up unused, but still is a good practise -- to have all of them defined, just in case diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs index 7e75af4b..81877d30 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs @@ -163,31 +163,35 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile GHC Just Hadrian -> "hadrian" Just Make -> "make" - bootstrapGHCField = case NE.nonEmpty availableGHCs of + bootstrapGHCFields = case NE.nonEmpty availableGHCs of Just ne -> let bootstrapGhc' = bootstrapGhc % (iso (either Just (const Nothing)) (maybe (Right "") Left)) - in Menu.createSelectField (Common.MenuElement Common.BootstrapGhcEditBox) bootstrapGhc' ne (T.pack . prettyShow) k + in [ Menu.createSelectField (Common.MenuElement Common.BootstrapGhcSelectBox) bootstrapGhc' ne (T.pack . prettyShow) k & Menu.fieldLabelL .~ "bootstrap-ghc" & Menu.fieldHelpMsgL .~ "The GHC version to bootstrap with" - _ -> Menu.createEditableField (Common.MenuElement Common.BootstrapGhcEditBox) bootstrapV bootstrapGhc k - & Menu.fieldLabelL .~ "bootstrap-ghc" + , editableField] + _ -> [editableField] + where + editableField = Menu.createEditableField (Common.MenuElement Common.BootstrapGhcEditBox) bootstrapV bootstrapGhc k + & Menu.fieldLabelL .~ "bootstrap-ghc-path" & Menu.fieldHelpMsgL .~ "The GHC version (or full path) to bootstrap with (must be installed)" - & Menu.fieldStatusL .~ Menu.Invalid "Invalid Empty value" + -- & Menu.fieldStatusL .~ Menu.Invalid "Invalid Empty value" - hadrianGHCField = case NE.nonEmpty availableGHCs of + hadrianGHCFields = case NE.nonEmpty availableGHCs of Just ne -> let hadrianGhc' = hadrianGhc % (iso ((=<<) (either Just (const Nothing))) (fmap Left)) - in Menu.createSelectField (Common.MenuElement Common.HadrianGhcEditBox) hadrianGhc' ne (T.pack . prettyShow) k + in [ Menu.createSelectField (Common.MenuElement Common.HadrianGhcSelectBox) hadrianGhc' ne (T.pack . prettyShow) k & Menu.fieldLabelL .~ "hadrian-ghc" & Menu.fieldHelpMsgL .~ "The GHC version that will be used to compile hadrian" - _ -> Menu.createEditableField (Common.MenuElement Common.HadrianGhcEditBox) hadrianstrapV hadrianGhc k - & Menu.fieldLabelL .~ "hadrian-ghc" + , editableField] + _ -> [editableField] + where + editableField = Menu.createEditableField (Common.MenuElement Common.HadrianGhcEditBox) hadrianstrapV hadrianGhc k + & Menu.fieldLabelL .~ "hadrian-ghc-path" & Menu.fieldHelpMsgL .~ "The GHC version (or full path) that will be used to compile hadrian (must be installed)" - fields = - [ bootstrapGHCField - , hadrianGHCField - , Menu.createEditableField (Common.MenuElement Common.JobsEditBox) jobsV jobs k + fields = bootstrapGHCFields ++ hadrianGHCFields ++ + [ Menu.createEditableField (Common.MenuElement Common.JobsEditBox) jobsV jobs k & Menu.fieldLabelL .~ "jobs" & Menu.fieldHelpMsgL .~ "How many jobs to use for make" , Menu.createCheckBoxField (Common.MenuElement Common.SetCheckBox) setCompile @@ -225,7 +229,7 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile GHC buttons = [ Menu.createButtonField (Common.MenuElement Common.OkButton) & Menu.fieldLabelL .~ "Compile" - & Menu.fieldHelpMsgL .~ "Compile GHC from source with options below\nRequired fields: bootstrap-ghc" + & Menu.fieldHelpMsgL .~ "Compile GHC from source with options below\nEither bootstrap-ghc or bootstrap-ghc-path must be specified\nAll other fields are optional. One of hadrian-ghc or hadrian-ghc-path can be specified" & Menu.fieldStatusL .~ Menu.Invalid "bootstrap GHC is mandatory" ] From 2bad5ce1d37c0911679fea236c54a33784bee224 Mon Sep 17 00:00:00 2001 From: Divam Date: Wed, 31 Jul 2024 12:08:35 +0900 Subject: [PATCH 31/41] Add editable field support with select input --- lib-tui/GHCup/Brick/Widgets/Menu.hs | 112 ++++++++++++++++++++-------- 1 file changed, 80 insertions(+), 32 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index 435f9970..430393db 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -74,9 +74,9 @@ import Optics.TH (makeLensesFor) import qualified Graphics.Vty as Vty import Optics.State.Operators ((%=), (.=)) import Optics.Optic ((%)) -import Optics.State (use) +import Optics.State (use, assign) import GHCup.Types (KeyCombination(..)) -import Optics (Lens', to, lens) +import Optics (Lens', to, lens, _1, over) import Optics.Operators ( (^.), (.~) ) import Data.Foldable (find, foldl') import Data.List.NonEmpty ( NonEmpty (..) ) @@ -152,14 +152,17 @@ makeLensesFor ] ''MenuField -data SelectState i = SelectState - { selectStateItems :: NonEmpty (Int, (i, Bool)) -- ^ All items along with their selected state +data SelectState i n = SelectState + { selectStateItems :: (NonEmpty (Int, (i, Bool)), Bool) -- ^ All items along with their selected state + -- And Bool to indicate if editable field is selected + , selectStateEditState :: Maybe (Edit.Editor T.Text n) -- ^ Editable field's editor state , selectStateFocusRing :: FocusRing Int -- ^ Focus ring using integeral values assigned to each item , selectStateOverlayOpen :: Bool -- ^ Whether the select menu is open } makeLensesFor [ ("selectStateItems", "selectStateItemsL") + , ("selectStateEditState", "selectStateEditStateL") , ("selectStateFocusRing", "selectStateFocusRingL") , ("selectStateOverlayOpen", "selectStateOverlayOpenL") ] @@ -306,48 +309,74 @@ type SelectField = MenuField createSelectInput :: (Ord n, Show n) => NonEmpty i -> (i -> T.Text) - -> (Int -> NonEmpty (Int, (i, Bool)) -> NonEmpty (Int, (i, Bool))) - -> ([i] -> k) + -> (Int -> (NonEmpty (Int, (i, Bool)), Bool) -> ((NonEmpty (Int, (i, Bool))), Bool)) + -> (([i], Maybe T.Text) -> Either ErrorMessage k) -> n + -> Maybe n -> KeyCombination - -> FieldInput k (SelectState i) n -createSelectInput items showItem updateSelection getSelection fieldName exitKey@(KeyCombination {..}) - = FieldInput initState (Right . getSelection . getSelectedItems) "" selectRender selectHandler + -> FieldInput k (SelectState i n) n +createSelectInput items showItem updateSelection validator viewportFieldName mEditFieldName exitKey@(KeyCombination {..}) + = FieldInput initState (validator . getSelectedItems) "" selectRender selectHandler where + totalRows = (if isJust mEditFieldName then (+) 1 else id) $ length items initState = SelectState - (NE.zip (1 NE.:| [2..]) $ fmap (,False) items) - (F.focusRing [1..(length items)]) + (NE.zip (1 NE.:| [2..]) $ fmap (,False) items, False) + ((\n -> Edit.editorText n (Just 1) "") <$> mEditFieldName) + (F.focusRing [1.. totalRows]) False - getSelectedItems = fmap (fst . snd) . (filter (snd . snd)) . NE.toList . selectStateItems + getSelectedItems (SelectState {..}) = + ( fmap (fst . snd) . (filter (snd . snd)) . NE.toList . fst $ selectStateItems + , if snd selectStateItems then (T.init . T.unlines . Edit.getEditContents <$> selectStateEditState) else Nothing) border w = Brick.txt "[" <+> (Brick.padRight (Brick.Pad 1) $ Brick.padLeft (Brick.Pad 1) w) <+> Brick.txt "]" selectRender focus errMsg help label s amp = (field, mOverlay) where - field = amp $ case getSelectedItems s of - [] -> (Brick.padLeft (Brick.Pad 1) . renderAsHelpMsg $ help) - xs -> - let list = border $ Brick.hBox $ fmap (Brick.padRight (Brick.Pad 1) . Brick.txt . showItem) xs - in if focus - then list - else list <+> (Brick.padLeft (Brick.Pad 1) . renderAsHelpMsg $ help) + field = + let mContents = case getSelectedItems s of + ([], Nothing) -> Nothing + (xs, mTxt) -> Just $ fmap (Brick.padRight (Brick.Pad 1) . Brick.txt . showItem) xs + ++ (case mTxt of Just t -> [Brick.txt t]; Nothing -> []) + in amp $ case (errMsg, mContents) of + (Valid, Nothing) -> (Brick.padLeft (Brick.Pad 1) . renderAsHelpMsg $ help) + (Valid, Just contents) -> border $ Brick.hBox contents + (Invalid msg, Nothing) + | focus -> Brick.padLeft (Brick.Pad 1) . renderAsHelpMsg $ help + | otherwise -> Brick.padLeft (Brick.Pad 1) $ renderAsErrMsg msg + (Invalid msg, Just contents) + | focus -> border $ Brick.hBox contents + | otherwise -> Brick.padLeft (Brick.Pad 1) $ renderAsErrMsg msg + mOverlay = if selectStateOverlayOpen s - then Just (overlayLayer ("Select " <> label) $ overlay s) + then Just (overlayLayer ("Select " <> label) $ overlay s errMsg help) else Nothing - overlay (SelectState {..}) = Brick.vBox $ + overlay (SelectState {..}) errMsg help = Brick.vBox $ [ Brick.padRight Brick.Max $ Brick.txt "Press " <+> Common.keyToWidget exitKey <+> Brick.txt " to go back, Press Enter to select" - , Brick.vLimit (length items) $ Brick.withVScrollBars Brick.OnRight - $ Brick.viewport fieldName Brick.Vertical - $ Brick.vBox $ (NE.toList $ fmap (mkSelectRow focused) selectStateItems) + , case errMsg of Invalid msg -> renderAsErrMsg msg; _ -> Brick.emptyWidget + , Brick.vLimit (totalRows) $ Brick.withVScrollBars Brick.OnRight + $ Brick.viewport viewportFieldName Brick.Vertical + $ Brick.vBox $ mEditableField ++ (NE.toList $ fmap (mkSelectRow focused) (fst selectStateItems)) ] where focused = fromMaybe 1 $ F.focusGetCurrent selectStateFocusRing + txtFieldFocused = focused == totalRows + mEditableField = case selectStateEditState of + Just edi -> [ mkEditTextRow txtFieldFocused edi (snd selectStateItems) help ] + Nothing -> [] + mkSelectRow focused (ix, (item, selected)) = (if focused == ix then Brick.visible else id) $ Brick.txt "[" <+> (Brick.padRight (Brick.Pad 1) $ Brick.padLeft (Brick.Pad 1) m) <+> Brick.txt "] " <+> (renderAslabel (showItem item) (focused == ix)) where m = if selected then Brick.txt "*" else Brick.txt " " + mkEditTextRow focused edi selected help = (if focused then Brick.visible else id) $ + Brick.txt "[" <+> (Brick.padRight (Brick.Pad 1) $ Brick.padLeft (Brick.Pad 1) m) <+> Brick.txt "] " + <+> if not focused && Edit.getEditContents edi == [mempty] + then Brick.txt "(Specify custom text value)" + else Brick.vLimit 1 $ Border.vBorder <+> Brick.padRight Brick.Max (Edit.renderEditor (Brick.txt . T.unlines) focused edi) <+> Border.vBorder + where m = if selected then Brick.txt "*" else Brick.txt " " + selectHandler ev = do s <- Brick.get if selectStateOverlayOpen s @@ -360,26 +389,45 @@ createSelectInput items showItem updateSelection getSelection fieldName exitKey@ VtyEvent (Vty.EvKey Vty.KEnter []) -> do focused <- use (selectStateFocusRingL % to F.focusGetCurrent) selectStateItemsL %= updateSelection (fromMaybe 1 focused) - _ -> pure () + _ -> do + focused <- use (selectStateFocusRingL % to F.focusGetCurrent) + mEditState <- use selectStateEditStateL + case (focused, mEditState) of + (Just ix, Just edi) + | ix == totalRows -> do + newEdi <- Brick.nestEventM' edi $ Edit.handleEditorEvent ev + assign selectStateEditStateL (Just newEdi) + selectStateItemsL %= updateSelection ix + _ -> pure () else case ev of VtyEvent (Vty.EvKey Vty.KEnter []) -> selectStateOverlayOpenL .= True _ -> pure () -- | Select Field with only single selection possible, aka radio button createSelectField :: (Ord n, Show n) => n -> Lens' s (Maybe i) -> NonEmpty i -> (i -> T.Text) -> KeyCombination -> SelectField s n -createSelectField name access items showItem exitKey = MenuField access (createSelectInput items showItem singleSelect getSelection name exitKey) "" Valid name +createSelectField name access items showItem exitKey = MenuField access (createSelectInput items showItem singleSelect getSelection name Nothing exitKey) "" Valid name where - singleSelect :: Int -> NonEmpty (Int, (i, Bool)) -> NonEmpty (Int, (i, Bool)) - singleSelect ix = fmap (\(ix', (i, b)) -> if ix' == ix then (ix', (i, True)) else (ix', (i, False))) + singleSelect :: Int -> (NonEmpty (Int, (i, Bool)), a) -> (NonEmpty (Int, (i, Bool)), a) + singleSelect ix = over _1 $ fmap (\(ix', (i, b)) -> if ix' == ix then (ix', (i, True)) else (ix', (i, False))) - getSelection = fmap NE.head . NE.nonEmpty + getSelection = Right . fmap NE.head . NE.nonEmpty . fst -- | Select Field with multiple selections possible createMultiSelectField :: (Ord n, Show n) => n -> Lens' s [i] -> NonEmpty i -> (i -> T.Text) -> KeyCombination -> SelectField s n -createMultiSelectField name access items showItem exitKey = MenuField access (createSelectInput items showItem multiSelect id name exitKey) "" Valid name +createMultiSelectField name access items showItem exitKey = MenuField access (createSelectInput items showItem multiSelect (Right . fst) name Nothing exitKey) "" Valid name where - multiSelect :: Int -> NonEmpty (Int, (i, Bool)) -> NonEmpty (Int, (i, Bool)) - multiSelect ix = fmap (\(ix', (i, b)) -> if ix' == ix then (ix', (i, not b)) else (ix', (i, b))) + multiSelect :: Int -> (NonEmpty (Int, (i, Bool)), a) -> (NonEmpty (Int, (i, Bool)), a) + multiSelect ix = over _1 $ fmap (\(ix', (i, b)) -> if ix' == ix then (ix', (i, not b)) else (ix', (i, b))) + +-- | Select Field with only single selection possible, along with an editable field +createSelectFieldWithEditable :: (Ord n, Show n) => n -> n -> Lens' s (Either a i) -> (T.Text -> Either ErrorMessage a) -> NonEmpty i -> (i -> T.Text) -> KeyCombination -> SelectField s n +createSelectFieldWithEditable name editFieldName access validator items showItem exitKey = MenuField access (createSelectInput items showItem singleSelect getSelection name (Just editFieldName) exitKey) "" Valid name + where + singleSelect :: Int -> (NonEmpty (Int, (i, Bool)), Bool) -> (NonEmpty (Int, (i, Bool)), Bool) + singleSelect ix (ne, a) = (fmap (\(ix', (i, b)) -> if ix' == ix then (ix', (i, True)) else (ix', (i, False))) ne, ix == length ne + 1) + + getSelection (_, Just txt) = either Left (Right . Left) $ validator txt + getSelection (ls, _) = maybe (either Left (Right . Left) $ validator "") (Right . Right . NE.head) $ NE.nonEmpty ls {- ***************** From fdacec44c2abab13869d066a38ea268018cd038a Mon Sep 17 00:00:00 2001 From: Divam Date: Wed, 31 Jul 2024 12:33:08 +0900 Subject: [PATCH 32/41] Use the select field with editable field in bootstrap-ghc and hadrian-ghc Plus some minor modifications of help / error text messages --- .../GHCup/Brick/Widgets/Menus/CompileGHC.hs | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs index 81877d30..e6a6bb66 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs @@ -119,12 +119,12 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile GHC readPath = do mfilepath <- filepathV i case mfilepath of - Nothing -> Left "Invalid Empty value" + Nothing -> Left "Invalid path" Just f -> Right (Right f) in if T.any isPathSeparator i then readPath else readVersion - False -> Left "Invalid Empty value" + False -> Left "No version selected / no path specified" hadrianstrapV :: T.Text -> Either Menu.ErrorMessage (Maybe (Either Version FilePath)) hadrianstrapV i' = @@ -165,30 +165,29 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile GHC bootstrapGHCFields = case NE.nonEmpty availableGHCs of Just ne -> - let bootstrapGhc' = bootstrapGhc % (iso (either Just (const Nothing)) (maybe (Right "") Left)) - in [ Menu.createSelectField (Common.MenuElement Common.BootstrapGhcSelectBox) bootstrapGhc' ne (T.pack . prettyShow) k - & Menu.fieldLabelL .~ "bootstrap-ghc" - & Menu.fieldHelpMsgL .~ "The GHC version to bootstrap with" - , editableField] - _ -> [editableField] - where - editableField = Menu.createEditableField (Common.MenuElement Common.BootstrapGhcEditBox) bootstrapV bootstrapGhc k - & Menu.fieldLabelL .~ "bootstrap-ghc-path" - & Menu.fieldHelpMsgL .~ "The GHC version (or full path) to bootstrap with (must be installed)" - -- & Menu.fieldStatusL .~ Menu.Invalid "Invalid Empty value" + let bootstrapGhc' = bootstrapGhc % (iso (either (Left . Left) (Left . Right)) (either id Left)) + in [ Menu.createSelectFieldWithEditable (Common.MenuElement Common.BootstrapGhcSelectBox) (Common.MenuElement Common.BootstrapGhcEditBox) bootstrapGhc' bootstrapV ne (T.pack . prettyShow) k + & Menu.fieldLabelL .~ "bootstrap-ghc" + & Menu.fieldHelpMsgL .~ "The GHC version (or full path) to bootstrap with (must be installed)" + & Menu.fieldStatusL .~ Menu.Invalid "No version selected / no path specified" + ] + _ -> [ Menu.createEditableField (Common.MenuElement Common.BootstrapGhcEditBox) bootstrapV bootstrapGhc k + & Menu.fieldLabelL .~ "bootstrap-ghc" + & Menu.fieldHelpMsgL .~ "The GHC version (or full path) to bootstrap with (must be installed)" + & Menu.fieldStatusL .~ Menu.Invalid "Invalid empty value" + ] hadrianGHCFields = case NE.nonEmpty availableGHCs of Just ne -> - let hadrianGhc' = hadrianGhc % (iso ((=<<) (either Just (const Nothing))) (fmap Left)) - in [ Menu.createSelectField (Common.MenuElement Common.HadrianGhcSelectBox) hadrianGhc' ne (T.pack . prettyShow) k - & Menu.fieldLabelL .~ "hadrian-ghc" - & Menu.fieldHelpMsgL .~ "The GHC version that will be used to compile hadrian" - , editableField] - _ -> [editableField] - where - editableField = Menu.createEditableField (Common.MenuElement Common.HadrianGhcEditBox) hadrianstrapV hadrianGhc k - & Menu.fieldLabelL .~ "hadrian-ghc-path" - & Menu.fieldHelpMsgL .~ "The GHC version (or full path) that will be used to compile hadrian (must be installed)" + let hadrianGhc' = hadrianGhc % (iso Left (either id (Just . Left))) + in [ Menu.createSelectFieldWithEditable (Common.MenuElement Common.HadrianGhcSelectBox) (Common.MenuElement Common.HadrianGhcEditBox) hadrianGhc' hadrianstrapV ne (T.pack . prettyShow) k + & Menu.fieldLabelL .~ "hadrian-ghc" + & Menu.fieldHelpMsgL .~ "The GHC version (or full path) that will be used to compile hadrian (must be installed)" + ] + _ -> [ Menu.createEditableField (Common.MenuElement Common.HadrianGhcEditBox) hadrianstrapV hadrianGhc k + & Menu.fieldLabelL .~ "hadrian-ghc" + & Menu.fieldHelpMsgL .~ "The GHC version (or full path) that will be used to compile hadrian (must be installed)" + ] fields = bootstrapGHCFields ++ hadrianGHCFields ++ [ Menu.createEditableField (Common.MenuElement Common.JobsEditBox) jobsV jobs k @@ -229,7 +228,7 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile GHC buttons = [ Menu.createButtonField (Common.MenuElement Common.OkButton) & Menu.fieldLabelL .~ "Compile" - & Menu.fieldHelpMsgL .~ "Compile GHC from source with options below\nEither bootstrap-ghc or bootstrap-ghc-path must be specified\nAll other fields are optional. One of hadrian-ghc or hadrian-ghc-path can be specified" + & Menu.fieldHelpMsgL .~ "Compile GHC from source with options below\nRequired fields: bootstrap-ghc" & Menu.fieldStatusL .~ Menu.Invalid "bootstrap GHC is mandatory" ] From 7a877e9ba85d56c7ba748849fc60f4faf0db1141 Mon Sep 17 00:00:00 2001 From: Divam Date: Wed, 31 Jul 2024 13:25:39 +0900 Subject: [PATCH 33/41] Set default status of target-ghc's field to error --- lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs index fa5c3b7f..0867026e 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs @@ -149,9 +149,11 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile HLS Just ne -> Menu.createMultiSelectField (Common.MenuElement Common.TargetGhcEditBox) targetGHCs ne (T.pack . prettyShow) k & Menu.fieldLabelL .~ label & Menu.fieldHelpMsgL .~ "GHC versions to compile for (Press Enter to edit)" + & Menu.fieldStatusL .~ Menu.Invalid "No version selected" _ -> Menu.createEditableField (Common.MenuElement Common.TargetGhcEditBox) ghcVersionTagEither targetGHCs k & Menu.fieldLabelL .~ label & Menu.fieldHelpMsgL .~ "space separated list of GHC versions to compile for" + & Menu.fieldStatusL .~ Menu.Invalid "Invalid empty value" fields = [ targetGHCsField From fadd12350982eb3c4cc803bdf926d53b8259c661 Mon Sep 17 00:00:00 2001 From: Divam Date: Wed, 7 Aug 2024 13:57:03 +0900 Subject: [PATCH 34/41] Change help msg color to yellow --- lib-tui/GHCup/Brick/Attributes.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib-tui/GHCup/Brick/Attributes.hs b/lib-tui/GHCup/Brick/Attributes.hs index 8226f46a..e6ed39c9 100644 --- a/lib-tui/GHCup/Brick/Attributes.hs +++ b/lib-tui/GHCup/Brick/Attributes.hs @@ -40,7 +40,7 @@ defaultAttributes no_color = Brick.attrMap , (dayAttr , Vty.defAttr `withForeColor` Vty.brightCyan) , (helpAttr , Vty.defAttr `withStyle` Vty.italic) , (hoorayAttr , Vty.defAttr `withForeColor` Vty.brightWhite) - , (helpMsgAttr , Vty.defAttr `withForeColor` Vty.brightBlack) + , (helpMsgAttr , Vty.defAttr `withForeColor` Vty.yellow) , (errMsgAttr , Vty.defAttr `withForeColor` Vty.red) ] where From b4e871961a2362937f4faeb939da52f2e32bd9df Mon Sep 17 00:00:00 2001 From: Divam Date: Wed, 7 Aug 2024 16:56:44 +0900 Subject: [PATCH 35/41] Use bQuit key in tutorial menu --- lib-tui/GHCup/Brick/App.hs | 10 ++++++---- lib-tui/GHCup/Brick/Widgets/Tutorial.hs | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lib-tui/GHCup/Brick/App.hs b/lib-tui/GHCup/Brick/App.hs index 790c56ec..135c862a 100644 --- a/lib-tui/GHCup/Brick/App.hs +++ b/lib-tui/GHCup/Brick/App.hs @@ -36,7 +36,7 @@ import qualified GHCup.Brick.Widgets.Menu as Menu import qualified GHCup.Brick.Widgets.Menus.AdvanceInstall as AdvanceInstall import GHCup.List (ListResult) -import GHCup.Types (AppState (AppState, keyBindings), KeyCombination (KeyCombination)) +import GHCup.Types (AppState (AppState, keyBindings), KeyCombination (KeyCombination), KeyBindings (..)) import qualified Brick.Focus as F import Brick ( @@ -93,7 +93,7 @@ drawUI dimAttrs st = navg = Navigation.draw dimAttrs (st ^. appState) <=> footer in case st ^. mode of Navigation -> [navg] - Tutorial -> [Tutorial.draw, navg] + Tutorial -> [Tutorial.draw (bQuit $ st ^. appKeys), navg] KeyInfo -> [KeyInfo.draw (st ^. appKeys), navg] ContextPanel -> [ContextMenu.draw (st ^. contextMenu), navg] AdvanceInstallPanel -> AdvanceInstall.draw (st ^. advanceInstallMenu) ++ [navg] @@ -110,9 +110,11 @@ keyInfoHandler ev = case ev of -- | On q, go back to navigation. Else, do nothing tutorialHandler :: BrickEvent Name e -> EventM Name BrickState () -tutorialHandler ev = +tutorialHandler ev = do + AppState { keyBindings = kb } <- liftIO $ readIORef Actions.settings' case ev of - VtyEvent (Vty.EvKey (Vty.KChar 'c') [Vty.MCtrl]) -> mode .= Navigation + VtyEvent (Vty.EvKey key mods) + | bQuit kb == KeyCombination key mods -> mode .= Navigation _ -> pure () -- | Tab/Arrows to navigate. diff --git a/lib-tui/GHCup/Brick/Widgets/Tutorial.hs b/lib-tui/GHCup/Brick/Widgets/Tutorial.hs index cc1ac680..738ff694 100644 --- a/lib-tui/GHCup/Brick/Widgets/Tutorial.hs +++ b/lib-tui/GHCup/Brick/Widgets/Tutorial.hs @@ -16,19 +16,20 @@ module GHCup.Brick.Widgets.Tutorial (draw) where import qualified GHCup.Brick.Common as Common import qualified GHCup.Brick.Attributes as Attributes +import GHCup.Types (KeyCombination(..)) import Brick ( Padding(Max), Widget(..), - (<=>)) + (<=>), (<+>)) import qualified Brick import Brick.Widgets.Center ( center ) import Prelude hiding ( appendFile ) -draw :: Widget Common.Name -draw = +draw :: KeyCombination -> Widget Common.Name +draw exitKey = let mkTextBox = Brick.hLimitPercent 70 . Brick.vBox . fmap (Brick.padRight Brick.Max) @@ -74,4 +75,5 @@ draw = ] , Brick.txt " " ]) - <=> Brick.padRight Brick.Max (Brick.txt "Press c+ctrl to exit the tutorial") + <=> (Brick.padRight Brick.Max $ + Brick.txt "Press " <+> Common.keyToWidget exitKey <+> Brick.txt " to exit the tutorial") From d8ade25297579694e399bd3b80072d9a6dd2cec8 Mon Sep 17 00:00:00 2001 From: Divam Date: Wed, 7 Aug 2024 17:00:39 +0900 Subject: [PATCH 36/41] Use bQuit key in key info menu --- lib-tui/GHCup/Brick/App.hs | 11 +++++++---- lib-tui/GHCup/Brick/Widgets/KeyInfo.hs | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib-tui/GHCup/Brick/App.hs b/lib-tui/GHCup/Brick/App.hs index 135c862a..7e2ecc11 100644 --- a/lib-tui/GHCup/Brick/App.hs +++ b/lib-tui/GHCup/Brick/App.hs @@ -103,10 +103,13 @@ drawUI dimAttrs st = -- | On q, go back to navigation. -- On Enter, to go to tutorial keyInfoHandler :: BrickEvent Name e -> EventM Name BrickState () -keyInfoHandler ev = case ev of - VtyEvent (Vty.EvKey (Vty.KChar 'c') [Vty.MCtrl]) -> mode .= Navigation - VtyEvent (Vty.EvKey Vty.KEnter _ ) -> mode .= Tutorial - _ -> pure () +keyInfoHandler ev = do + AppState { keyBindings = kb } <- liftIO $ readIORef Actions.settings' + case ev of + VtyEvent (Vty.EvKey Vty.KEnter _ ) -> mode .= Tutorial + VtyEvent (Vty.EvKey key mods) + | bQuit kb == KeyCombination key mods -> mode .= Navigation + _ -> pure () -- | On q, go back to navigation. Else, do nothing tutorialHandler :: BrickEvent Name e -> EventM Name BrickState () diff --git a/lib-tui/GHCup/Brick/Widgets/KeyInfo.hs b/lib-tui/GHCup/Brick/Widgets/KeyInfo.hs index dfa4dd4e..9122cfb9 100644 --- a/lib-tui/GHCup/Brick/Widgets/KeyInfo.hs +++ b/lib-tui/GHCup/Brick/Widgets/KeyInfo.hs @@ -69,4 +69,4 @@ draw KeyBindings {..} = ] ] ] - <=> Brick.hBox [Brick.txt "Press c+ctrl to return to Navigation" <+> Brick.padRight Brick.Max (Brick.txt " ") <+> Brick.txt "Press Enter to go to the Tutorial"] + <=> Brick.hBox [Brick.txt "Press " <+> Common.keyToWidget bQuit <+> Brick.txt " to return to Navigation" <+> Brick.padRight Brick.Max (Brick.txt " ") <+> Brick.txt "Press Enter to go to the Tutorial"] From 2666e701b20fb6190b6d588f1daa9cb6507f4953 Mon Sep 17 00:00:00 2001 From: Divam Date: Wed, 7 Aug 2024 17:10:41 +0900 Subject: [PATCH 37/41] Use bQuit key in advance options menus --- lib-tui/GHCup/Brick/Actions.hs | 3 +-- lib-tui/GHCup/BrickMain.hs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib-tui/GHCup/Brick/Actions.hs b/lib-tui/GHCup/Brick/Actions.hs index 4883a124..67aaddb3 100644 --- a/lib-tui/GHCup/Brick/Actions.hs +++ b/lib-tui/GHCup/Brick/Actions.hs @@ -714,14 +714,13 @@ keyHandlers KeyBindings {..} = where createMenuforTool = do e <- use (appState % to sectionListSelectedElement) - let exitKey = KeyCombination (Vty.KChar 'c') [Vty.MCtrl] case e of Nothing -> pure () Just (_, r) -> do -- Create new ContextMenu, but maintain the state of Install/Compile -- menus. This is especially useful in case the user made a typo and -- would like to retry the action. - contextMenu .= ContextMenu.create r exitKey + contextMenu .= ContextMenu.create r bQuit -- Set mode to context mode .= ContextPanel pure () diff --git a/lib-tui/GHCup/BrickMain.hs b/lib-tui/GHCup/BrickMain.hs index 49dfdafc..f684bfd6 100644 --- a/lib-tui/GHCup/BrickMain.hs +++ b/lib-tui/GHCup/BrickMain.hs @@ -18,7 +18,7 @@ module GHCup.BrickMain where import GHCup.List ( ListResult (..)) import GHCup.Types ( Settings(noColor), Tool (GHC), - AppState(ghcupInfo, settings, keyBindings, loggerConfig), KeyCombination (KeyCombination) ) + AppState(ghcupInfo, settings, keyBindings, loggerConfig), KeyCombination (KeyCombination), bQuit ) import GHCup.Prelude.Logger ( logError ) import qualified GHCup.Brick.Actions as Actions import qualified GHCup.Brick.Common as Common @@ -53,7 +53,7 @@ brickMain s = do Right ad -> do let initial_list = Actions.constructList ad Common.defaultAppSettings Nothing current_element = Navigation.sectionListSelectedElement initial_list - exit_key = KeyCombination (Vty.KChar 'c') [Vty.MCtrl] -- bQuit . keyBindings $ s + exit_key = bQuit . keyBindings $ s case current_element of Nothing -> do flip runReaderT s $ logError "Error building app state: empty ResultList" From 9ae87c5a8aa2dd681a35968875839874fe2a6f46 Mon Sep 17 00:00:00 2001 From: Divam Date: Wed, 7 Aug 2024 17:44:56 +0900 Subject: [PATCH 38/41] Remove exitKey from editable input, only use Enter to go back --- lib-tui/GHCup/Brick/Widgets/Menu.hs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index 430393db..57724a90 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -234,8 +234,8 @@ createCheckBoxField name access = MenuField access createCheckBoxInput "" Valid type EditableField = MenuField -createEditableInput :: (Ord n, Show n) => n -> (T.Text -> Either ErrorMessage a) -> KeyCombination -> FieldInput a (EditState n) n -createEditableInput name validator exitKey@(KeyCombination {..}) = FieldInput initEdit validateEditContent "" drawEdit handler +createEditableInput :: (Ord n, Show n) => n -> (T.Text -> Either ErrorMessage a) -> FieldInput a (EditState n) n +createEditableInput name validator = FieldInput initEdit validateEditContent "" drawEdit handler where drawEdit focus errMsg help label (EditState edi overlayOpen) amp = (field, mOverlay) where @@ -261,15 +261,12 @@ createEditableInput name validator exitKey@(KeyCombination {..}) = FieldInput in Invalid msg -> renderAsErrMsg msg _ -> Brick.txt " " , Brick.padRight Brick.Max $ - Brick.txt "Press " - <+> Common.keyToWidget exitKey - <+> Brick.txt " or Enter to go back" + Brick.txt "Press Enter to go back" ] handler ev = do (EditState edi overlayOpen) <- Brick.get if overlayOpen then case ev of - VtyEvent (Vty.EvKey k m) | k == key && m == mods -> editStateOverlayOpenL .= False VtyEvent (Vty.EvKey Vty.KEnter []) -> editStateOverlayOpenL .= False _ -> Common.zoom editStateL $ Edit.handleEditorEvent ev else case ev of @@ -281,7 +278,7 @@ createEditableInput name validator exitKey@(KeyCombination {..}) = FieldInput in createEditableField :: (Eq n, Ord n, Show n) => n -> (T.Text -> Either ErrorMessage a) -> Lens' s a -> KeyCombination -> EditableField s n createEditableField name validator access exitKey = MenuField access input "" Valid name where - input = createEditableInput name validator exitKey + input = createEditableInput name validator {- ***************** Button widget From f26b5c7717ec9d1a0073502e28c9aca51f2484d4 Mon Sep 17 00:00:00 2001 From: Divam Date: Wed, 7 Aug 2024 18:10:51 +0900 Subject: [PATCH 39/41] Change editable input fields key handling in select field to allow entering 'q', only handle Enter, Up and Down keys for navigation. --- lib-tui/GHCup/Brick/Widgets/Menu.hs | 47 +++++++++++++++++------------ 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index 57724a90..d2f526e4 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -347,8 +347,9 @@ createSelectInput items showItem updateSelection validator viewportFieldName mEd then Just (overlayLayer ("Select " <> label) $ overlay s errMsg help) else Nothing overlay (SelectState {..}) errMsg help = Brick.vBox $ - [ Brick.padRight Brick.Max $ - Brick.txt "Press " + [ if txtFieldFocused + then Brick.txtWrap "Press Enter to finish editing and select custom value. Press Up/Down keys to navigate" + else Brick.txt "Press " <+> Common.keyToWidget exitKey <+> Brick.txt " to go back, Press Enter to select" , case errMsg of Invalid msg -> renderAsErrMsg msg; _ -> Brick.emptyWidget @@ -377,24 +378,30 @@ createSelectInput items showItem updateSelection validator viewportFieldName mEd selectHandler ev = do s <- Brick.get if selectStateOverlayOpen s - then case ev of - VtyEvent (Vty.EvKey k m) | k == key && m == mods -> selectStateOverlayOpenL .= False - VtyEvent (Vty.EvKey (Vty.KChar '\t') []) -> selectStateFocusRingL %= F.focusNext - VtyEvent (Vty.EvKey Vty.KBackTab []) -> selectStateFocusRingL %= F.focusPrev - VtyEvent (Vty.EvKey Vty.KDown []) -> selectStateFocusRingL %= F.focusNext - VtyEvent (Vty.EvKey Vty.KUp []) -> selectStateFocusRingL %= F.focusPrev - VtyEvent (Vty.EvKey Vty.KEnter []) -> do - focused <- use (selectStateFocusRingL % to F.focusGetCurrent) - selectStateItemsL %= updateSelection (fromMaybe 1 focused) - _ -> do - focused <- use (selectStateFocusRingL % to F.focusGetCurrent) - mEditState <- use selectStateEditStateL - case (focused, mEditState) of - (Just ix, Just edi) - | ix == totalRows -> do - newEdi <- Brick.nestEventM' edi $ Edit.handleEditorEvent ev - assign selectStateEditStateL (Just newEdi) - selectStateItemsL %= updateSelection ix + then do + focused <- use (selectStateFocusRingL % to F.focusGetCurrent) + mEditState <- use selectStateEditStateL + case (focused, mEditState) of + (Just ix, Just edi) + | ix == totalRows -> case ev of + VtyEvent (Vty.EvKey Vty.KEnter []) -> do + selectStateItemsL %= updateSelection ix + selectStateFocusRingL %= F.focusNext + VtyEvent (Vty.EvKey Vty.KDown []) -> selectStateFocusRingL %= F.focusNext + VtyEvent (Vty.EvKey Vty.KUp []) -> selectStateFocusRingL %= F.focusPrev + _ -> do + newEdi <- Brick.nestEventM' edi $ Edit.handleEditorEvent ev + assign selectStateEditStateL (Just newEdi) + selectStateItemsL %= updateSelection ix + _ -> case ev of + VtyEvent (Vty.EvKey k m) | k == key && m == mods -> selectStateOverlayOpenL .= False + VtyEvent (Vty.EvKey (Vty.KChar '\t') []) -> selectStateFocusRingL %= F.focusNext + VtyEvent (Vty.EvKey Vty.KBackTab []) -> selectStateFocusRingL %= F.focusPrev + VtyEvent (Vty.EvKey Vty.KDown []) -> selectStateFocusRingL %= F.focusNext + VtyEvent (Vty.EvKey Vty.KUp []) -> selectStateFocusRingL %= F.focusPrev + VtyEvent (Vty.EvKey Vty.KEnter []) -> do + focused <- use (selectStateFocusRingL % to F.focusGetCurrent) + selectStateItemsL %= updateSelection (fromMaybe 1 focused) _ -> pure () else case ev of VtyEvent (Vty.EvKey Vty.KEnter []) -> selectStateOverlayOpenL .= True From 6eb3c80f5daa066130cb5552dd7f326955cd0cdf Mon Sep 17 00:00:00 2001 From: Divam Date: Thu, 8 Aug 2024 16:34:06 +0900 Subject: [PATCH 40/41] Use a new MenuKeyBindings data type, to capture menu navigation keybindings --- lib-tui/GHCup/Brick/Actions.hs | 4 +- lib-tui/GHCup/Brick/App.hs | 4 +- lib-tui/GHCup/Brick/Widgets/Menu.hs | 64 +++++++++++-------- .../Brick/Widgets/Menus/AdvanceInstall.hs | 11 ++-- .../GHCup/Brick/Widgets/Menus/CompileGHC.hs | 28 ++++---- .../GHCup/Brick/Widgets/Menus/CompileHLS.hs | 24 +++---- lib-tui/GHCup/Brick/Widgets/Menus/Context.hs | 10 +-- lib-tui/GHCup/BrickMain.hs | 7 +- 8 files changed, 84 insertions(+), 68 deletions(-) diff --git a/lib-tui/GHCup/Brick/Actions.hs b/lib-tui/GHCup/Brick/Actions.hs index 67aaddb3..c000cbc5 100644 --- a/lib-tui/GHCup/Brick/Actions.hs +++ b/lib-tui/GHCup/Brick/Actions.hs @@ -30,6 +30,7 @@ import qualified GHCup.Brick.Widgets.Menus.Context as ContextMenu import GHCup.Brick.Widgets.Navigation (BrickInternalState) import qualified GHCup.Brick.Widgets.Menus.AdvanceInstall as AdvanceInstall import qualified GHCup.Brick.Widgets.Menus.CompileGHC as CompileGHC +import GHCup.Brick.Widgets.Menu (MenuKeyBindings(..)) import qualified Brick import qualified Brick.Widgets.List as L @@ -720,7 +721,8 @@ keyHandlers KeyBindings {..} = -- Create new ContextMenu, but maintain the state of Install/Compile -- menus. This is especially useful in case the user made a typo and -- would like to retry the action. - contextMenu .= ContextMenu.create r bQuit + contextMenu .= ContextMenu.create r + (MenuKeyBindings { mKbUp = bUp, mKbDown = bDown, mKbQuit = bQuit}) -- Set mode to context mode .= ContextPanel pure () diff --git a/lib-tui/GHCup/Brick/App.hs b/lib-tui/GHCup/Brick/App.hs index 7e2ecc11..3343503d 100644 --- a/lib-tui/GHCup/Brick/App.hs +++ b/lib-tui/GHCup/Brick/App.hs @@ -135,7 +135,7 @@ contextMenuHandler :: BrickEvent Name e -> EventM Name BrickState () contextMenuHandler ev = do ctx <- use contextMenu let focusedElement = ctx ^. Menu.menuFocusRingL % to F.focusGetCurrent - (KeyCombination exitKey mods) = ctx ^. Menu.menuExitKeyL + (KeyCombination exitKey mods) = ctx ^. Menu.menuKeyBindingsL % Menu.mKbQuitL case (ev, focusedElement) of (_ , Nothing) -> pure () (VtyEvent (Vty.EvKey k m), Just n) | k == exitKey && m == mods -> mode .= Navigation @@ -165,7 +165,7 @@ menuWithOverlayHandler accessor action innerHandler ev = do ctx <- use accessor let focusedElement = ctx ^. Menu.menuFocusRingL % to F.focusGetCurrent focusedField = (\n -> find (\x -> Brick.getName x == n) $ ctx ^. Menu.menuFieldsL) =<< focusedElement - (KeyCombination exitKey mods) = ctx ^. Menu.menuExitKeyL + (KeyCombination exitKey mods) = ctx ^. Menu.menuKeyBindingsL % Menu.mKbQuitL case (ev, focusedElement, Menu.drawFieldOverlay =<< focusedField) of (_ , Nothing, _) -> pure () (_ , _, Just _) -> Common.zoom accessor $ innerHandler ev diff --git a/lib-tui/GHCup/Brick/Widgets/Menu.hs b/lib-tui/GHCup/Brick/Widgets/Menu.hs index d2f526e4..289d2c89 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menu.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menu.hs @@ -179,6 +179,20 @@ makeLensesFor ] ''EditState +data MenuKeyBindings = MenuKeyBindings + { mKbUp :: KeyCombination + , mKbDown :: KeyCombination + , mKbQuit :: KeyCombination + } + deriving (Show) + +makeLensesFor + [ ("mKbUp", "mKbUpL") + , ("mKbDown", "mKbDownL") + , ("mKbQuit", "mKbQuitL") + ] + ''MenuKeyBindings + -- | A fancy lens to the help message fieldHelpMsgL :: Lens' (MenuField s n) HelpMessage fieldHelpMsgL = lens g s @@ -275,8 +289,8 @@ createEditableInput name validator = FieldInput initEdit validateEditContent "" validateEditContent = validator . T.init . T.unlines . Edit.getEditContents . editState initEdit = EditState (Edit.editorText name (Just 1) "") False -createEditableField :: (Eq n, Ord n, Show n) => n -> (T.Text -> Either ErrorMessage a) -> Lens' s a -> KeyCombination -> EditableField s n -createEditableField name validator access exitKey = MenuField access input "" Valid name +createEditableField :: (Eq n, Ord n, Show n) => n -> (T.Text -> Either ErrorMessage a) -> Lens' s a -> EditableField s n +createEditableField name validator access = MenuField access input "" Valid name where input = createEditableInput name validator @@ -310,9 +324,9 @@ createSelectInput :: (Ord n, Show n) -> (([i], Maybe T.Text) -> Either ErrorMessage k) -> n -> Maybe n - -> KeyCombination + -> MenuKeyBindings -> FieldInput k (SelectState i n) n -createSelectInput items showItem updateSelection validator viewportFieldName mEditFieldName exitKey@(KeyCombination {..}) +createSelectInput items showItem updateSelection validator viewportFieldName mEditFieldName kb = FieldInput initState (validator . getSelectedItems) "" selectRender selectHandler where totalRows = (if isJust mEditFieldName then (+) 1 else id) $ length items @@ -350,7 +364,7 @@ createSelectInput items showItem updateSelection validator viewportFieldName mEd [ if txtFieldFocused then Brick.txtWrap "Press Enter to finish editing and select custom value. Press Up/Down keys to navigate" else Brick.txt "Press " - <+> Common.keyToWidget exitKey + <+> Common.keyToWidget (kb ^. mKbQuitL) <+> Brick.txt " to go back, Press Enter to select" , case errMsg of Invalid msg -> renderAsErrMsg msg; _ -> Brick.emptyWidget , Brick.vLimit (totalRows) $ Brick.withVScrollBars Brick.OnRight @@ -394,13 +408,11 @@ createSelectInput items showItem updateSelection validator viewportFieldName mEd assign selectStateEditStateL (Just newEdi) selectStateItemsL %= updateSelection ix _ -> case ev of - VtyEvent (Vty.EvKey k m) | k == key && m == mods -> selectStateOverlayOpenL .= False - VtyEvent (Vty.EvKey (Vty.KChar '\t') []) -> selectStateFocusRingL %= F.focusNext - VtyEvent (Vty.EvKey Vty.KBackTab []) -> selectStateFocusRingL %= F.focusPrev - VtyEvent (Vty.EvKey Vty.KDown []) -> selectStateFocusRingL %= F.focusNext - VtyEvent (Vty.EvKey Vty.KUp []) -> selectStateFocusRingL %= F.focusPrev + VtyEvent (Vty.EvKey k m) + | KeyCombination k m == kb ^. mKbQuitL -> selectStateOverlayOpenL .= False + | KeyCombination k m == kb ^. mKbUpL -> selectStateFocusRingL %= F.focusPrev + | KeyCombination k m == kb ^. mKbDownL -> selectStateFocusRingL %= F.focusNext VtyEvent (Vty.EvKey Vty.KEnter []) -> do - focused <- use (selectStateFocusRingL % to F.focusGetCurrent) selectStateItemsL %= updateSelection (fromMaybe 1 focused) _ -> pure () else case ev of @@ -408,8 +420,8 @@ createSelectInput items showItem updateSelection validator viewportFieldName mEd _ -> pure () -- | Select Field with only single selection possible, aka radio button -createSelectField :: (Ord n, Show n) => n -> Lens' s (Maybe i) -> NonEmpty i -> (i -> T.Text) -> KeyCombination -> SelectField s n -createSelectField name access items showItem exitKey = MenuField access (createSelectInput items showItem singleSelect getSelection name Nothing exitKey) "" Valid name +createSelectField :: (Ord n, Show n) => n -> Lens' s (Maybe i) -> NonEmpty i -> (i -> T.Text) -> MenuKeyBindings -> SelectField s n +createSelectField name access items showItem keyBindings = MenuField access (createSelectInput items showItem singleSelect getSelection name Nothing keyBindings) "" Valid name where singleSelect :: Int -> (NonEmpty (Int, (i, Bool)), a) -> (NonEmpty (Int, (i, Bool)), a) singleSelect ix = over _1 $ fmap (\(ix', (i, b)) -> if ix' == ix then (ix', (i, True)) else (ix', (i, False))) @@ -417,15 +429,15 @@ createSelectField name access items showItem exitKey = MenuField access (createS getSelection = Right . fmap NE.head . NE.nonEmpty . fst -- | Select Field with multiple selections possible -createMultiSelectField :: (Ord n, Show n) => n -> Lens' s [i] -> NonEmpty i -> (i -> T.Text) -> KeyCombination -> SelectField s n -createMultiSelectField name access items showItem exitKey = MenuField access (createSelectInput items showItem multiSelect (Right . fst) name Nothing exitKey) "" Valid name +createMultiSelectField :: (Ord n, Show n) => n -> Lens' s [i] -> NonEmpty i -> (i -> T.Text) -> MenuKeyBindings -> SelectField s n +createMultiSelectField name access items showItem keyBindings = MenuField access (createSelectInput items showItem multiSelect (Right . fst) name Nothing keyBindings) "" Valid name where multiSelect :: Int -> (NonEmpty (Int, (i, Bool)), a) -> (NonEmpty (Int, (i, Bool)), a) multiSelect ix = over _1 $ fmap (\(ix', (i, b)) -> if ix' == ix then (ix', (i, not b)) else (ix', (i, b))) -- | Select Field with only single selection possible, along with an editable field -createSelectFieldWithEditable :: (Ord n, Show n) => n -> n -> Lens' s (Either a i) -> (T.Text -> Either ErrorMessage a) -> NonEmpty i -> (i -> T.Text) -> KeyCombination -> SelectField s n -createSelectFieldWithEditable name editFieldName access validator items showItem exitKey = MenuField access (createSelectInput items showItem singleSelect getSelection name (Just editFieldName) exitKey) "" Valid name +createSelectFieldWithEditable :: (Ord n, Show n) => n -> n -> Lens' s (Either a i) -> (T.Text -> Either ErrorMessage a) -> NonEmpty i -> (i -> T.Text) -> MenuKeyBindings -> SelectField s n +createSelectFieldWithEditable name editFieldName access validator items showItem keyBindings = MenuField access (createSelectInput items showItem singleSelect getSelection name (Just editFieldName) keyBindings) "" Valid name where singleSelect :: Int -> (NonEmpty (Int, (i, Bool)), Bool) -> (NonEmpty (Int, (i, Bool)), Bool) singleSelect ix (ne, a) = (fmap (\(ix', (i, b)) -> if ix' == ix then (ix', (i, True)) else (ix', (i, False))) ne, ix == length ne + 1) @@ -493,7 +505,7 @@ data Menu s n , menuValidator :: s -> Maybe ErrorMessage -- ^ A validator function , menuButtons :: [Button s n] -- ^ The buttons. Commonly, the handlers for buttons are defined outside the menu handler. , menuFocusRing :: FocusRing n -- ^ The focus ring with the resource name for each entry and each button, in the order you want to loop them. - , menuExitKey :: KeyCombination -- ^ The key to exit the Menu + , menuKeyBindings :: MenuKeyBindings -- ^ KeyBindings for navigation , menuName :: n -- ^ The resource Name. , menuTitle :: T.Text -- ^ Menu title. } @@ -501,7 +513,7 @@ data Menu s n makeLensesFor [ ("menuFields", "menuFieldsL"), ("menuState", "menuStateL"), ("menuValidator", "menuValidatorL") , ("menuButtons", "menuButtonsL"), ("menuFocusRing", "menuFocusRingL") - , ("menuExitKey", "menuExitKeyL"), ("menuName", "menuNameL") + , ("menuKeyBindings", "menuKeyBindingsL"), ("menuName", "menuNameL") , ("menuTitle", "menuTitleL") ] ''Menu @@ -511,13 +523,14 @@ isValidMenu m = (all isValidField $ menuFields m) && (case (menuValidator m) (menuState m) of { Nothing -> True; _ -> False }) createMenu :: n -> s -> T.Text -> (s -> Maybe ErrorMessage) - -> KeyCombination -> [Button s n] -> [MenuField s n] -> Menu s n -createMenu n initial title validator exitK buttons fields = Menu fields initial validator buttons ring exitK n title + -> MenuKeyBindings -> [Button s n] -> [MenuField s n] -> Menu s n +createMenu n initial title validator keys buttons fields = Menu fields initial validator buttons ring keys n title where ring = F.focusRing $ [field & fieldName | field <- fields] ++ [button & fieldName | button <- buttons] handlerMenu :: forall n e s. Eq n => BrickEvent n e -> EventM n (Menu s n) () handlerMenu ev = do fields <- use menuFieldsL + kb <- use menuKeyBindingsL focused <- use $ menuFocusRingL % to F.focusGetCurrent let focusedField = (\n -> find (\x -> Brick.getName x == n) fields) =<< focused propagateEvent e = case focused of @@ -537,10 +550,9 @@ handlerMenu ev = do VtyEvent e -> propagateEvent e _ -> pure () Nothing -> case ev of - VtyEvent (Vty.EvKey (Vty.KChar '\t') []) -> menuFocusRingL %= F.focusNext - VtyEvent (Vty.EvKey Vty.KBackTab []) -> menuFocusRingL %= F.focusPrev - VtyEvent (Vty.EvKey Vty.KDown []) -> menuFocusRingL %= F.focusNext - VtyEvent (Vty.EvKey Vty.KUp []) -> menuFocusRingL %= F.focusPrev + VtyEvent (Vty.EvKey k m) + | KeyCombination k m == kb ^. mKbUpL -> menuFocusRingL %= F.focusPrev + | KeyCombination k m == kb ^. mKbDownL -> menuFocusRingL %= F.focusNext VtyEvent e -> propagateEvent e _ -> pure () where @@ -571,7 +583,7 @@ drawMenu menu = , Brick.txt " " , Brick.padRight Brick.Max $ Brick.txt "Press " - <+> Common.keyToWidget (menu ^. menuExitKeyL) + <+> Common.keyToWidget (menu ^. menuKeyBindingsL % mKbQuitL) <+> Brick.txt " to go back, Press Enter to edit the highlighted field" ] fieldLabels = [field & fieldLabel | field <- menu ^. menuFieldsL] diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/AdvanceInstall.hs b/lib-tui/GHCup/Brick/Widgets/Menus/AdvanceInstall.hs index 4b6f4363..19845678 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/AdvanceInstall.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/AdvanceInstall.hs @@ -27,7 +27,7 @@ module GHCup.Brick.Widgets.Menus.AdvanceInstall ( addConfArgsL, ) where -import GHCup.Brick.Widgets.Menu (Menu) +import GHCup.Brick.Widgets.Menu (Menu, MenuKeyBindings) import qualified GHCup.Brick.Widgets.Menu as Menu import GHCup.Brick.Common(Name(..)) import Brick @@ -37,7 +37,6 @@ import Brick import Prelude hiding ( appendFile ) import Optics.TH (makeLensesFor) import qualified GHCup.Brick.Common as Common -import GHCup.Types (KeyCombination) import URI.ByteString (URI) import qualified Data.Text as T import Data.Bifunctor (Bifunctor(..)) @@ -65,7 +64,7 @@ makeLensesFor [ type AdvanceInstallMenu = Menu InstallOptions Name -create :: KeyCombination -> AdvanceInstallMenu +create :: MenuKeyBindings -> AdvanceInstallMenu create k = Menu.createMenu AdvanceInstallBox initialState "Advance Install" validator k [ok] fields where initialState = InstallOptions Nothing False Nothing False [] @@ -89,19 +88,19 @@ create k = Menu.createMenu AdvanceInstallBox initialState "Advance Install" vali additionalValidator = Right . T.split isSpace fields = - [ Menu.createEditableField (Common.MenuElement Common.UrlEditBox) uriValidator instBindistL k + [ Menu.createEditableField (Common.MenuElement Common.UrlEditBox) uriValidator instBindistL & Menu.fieldLabelL .~ "url" & Menu.fieldHelpMsgL .~ "Install the specified version from this bindist" , Menu.createCheckBoxField (Common.MenuElement Common.SetCheckBox) instSetL & Menu.fieldLabelL .~ "set" & Menu.fieldHelpMsgL .~ "Set as active version after install" - , Menu.createEditableField (Common.MenuElement Common.IsolateEditBox) filepathValidator isolateDirL k + , Menu.createEditableField (Common.MenuElement Common.IsolateEditBox) filepathValidator isolateDirL & Menu.fieldLabelL .~ "isolated" & Menu.fieldHelpMsgL .~ "install in an isolated absolute directory instead of the default one" , Menu.createCheckBoxField (Common.MenuElement Common.ForceCheckBox) forceInstallL & Menu.fieldLabelL .~ "force" & Menu.fieldHelpMsgL .~ "Force install (THIS IS UNSAFE, only use it in Dockerfiles or CI)" - , Menu.createEditableField (Common.MenuElement Common.AdditionalEditBox) additionalValidator addConfArgsL k + , Menu.createEditableField (Common.MenuElement Common.AdditionalEditBox) additionalValidator addConfArgsL & Menu.fieldLabelL .~ "CONFIGURE_ARGS" & Menu.fieldHelpMsgL .~ "Additional arguments to bindist configure" ] diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs index e6a6bb66..705110f6 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileGHC.hs @@ -35,7 +35,7 @@ module GHCup.Brick.Widgets.Menus.CompileGHC ( gitRef, ) where -import GHCup.Brick.Widgets.Menu (Menu) +import GHCup.Brick.Widgets.Menu (Menu, MenuKeyBindings) import qualified GHCup.Brick.Widgets.Menu as Menu import GHCup.Brick.Common(Name(..)) import Brick @@ -46,7 +46,7 @@ import Prelude hiding ( appendFile ) import Optics.TH (makeLenses) import qualified GHCup.Brick.Common as Common import GHCup.Types - ( KeyCombination, BuildSystem(..), VersionPattern ) + ( BuildSystem(..), VersionPattern ) import URI.ByteString (URI) import Control.Monad (join) import qualified Data.Text as T @@ -83,7 +83,7 @@ makeLenses ''CompileGHCOptions type CompileGHCMenu = Menu CompileGHCOptions Name -create :: KeyCombination -> [Version] -> CompileGHCMenu +create :: MenuKeyBindings -> [Version] -> CompileGHCMenu create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile GHC" validator k buttons fields where initialState = @@ -171,7 +171,7 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile GHC & Menu.fieldHelpMsgL .~ "The GHC version (or full path) to bootstrap with (must be installed)" & Menu.fieldStatusL .~ Menu.Invalid "No version selected / no path specified" ] - _ -> [ Menu.createEditableField (Common.MenuElement Common.BootstrapGhcEditBox) bootstrapV bootstrapGhc k + _ -> [ Menu.createEditableField (Common.MenuElement Common.BootstrapGhcEditBox) bootstrapV bootstrapGhc & Menu.fieldLabelL .~ "bootstrap-ghc" & Menu.fieldHelpMsgL .~ "The GHC version (or full path) to bootstrap with (must be installed)" & Menu.fieldStatusL .~ Menu.Invalid "Invalid empty value" @@ -184,43 +184,43 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile GHC & Menu.fieldLabelL .~ "hadrian-ghc" & Menu.fieldHelpMsgL .~ "The GHC version (or full path) that will be used to compile hadrian (must be installed)" ] - _ -> [ Menu.createEditableField (Common.MenuElement Common.HadrianGhcEditBox) hadrianstrapV hadrianGhc k + _ -> [ Menu.createEditableField (Common.MenuElement Common.HadrianGhcEditBox) hadrianstrapV hadrianGhc & Menu.fieldLabelL .~ "hadrian-ghc" & Menu.fieldHelpMsgL .~ "The GHC version (or full path) that will be used to compile hadrian (must be installed)" ] fields = bootstrapGHCFields ++ hadrianGHCFields ++ - [ Menu.createEditableField (Common.MenuElement Common.JobsEditBox) jobsV jobs k + [ Menu.createEditableField (Common.MenuElement Common.JobsEditBox) jobsV jobs & Menu.fieldLabelL .~ "jobs" & Menu.fieldHelpMsgL .~ "How many jobs to use for make" , Menu.createCheckBoxField (Common.MenuElement Common.SetCheckBox) setCompile & Menu.fieldLabelL .~ "set" & Menu.fieldHelpMsgL .~ "Set as active version after install" - , Menu.createEditableField (Common.MenuElement Common.BuildFlavourEditBox) (Right . Just . T.unpack) buildFlavour k + , Menu.createEditableField (Common.MenuElement Common.BuildFlavourEditBox) (Right . Just . T.unpack) buildFlavour & Menu.fieldLabelL .~ "flavour" & Menu.fieldHelpMsgL .~ "Set the compile build flavour (this value depends on the build system type: 'make' vs 'hadrian')" - , Menu.createEditableField (Common.MenuElement Common.AdditionalEditBox) additionalValidator addConfArgs k + , Menu.createEditableField (Common.MenuElement Common.AdditionalEditBox) additionalValidator addConfArgs & Menu.fieldLabelL .~ "CONFIGURE_ARGS" & Menu.fieldHelpMsgL .~ "Additional arguments to compile configure" - , Menu.createEditableField (Common.MenuElement Common.BuildConfigEditBox) filepathV buildConfig k + , Menu.createEditableField (Common.MenuElement Common.BuildConfigEditBox) filepathV buildConfig & Menu.fieldLabelL .~ "build config" & Menu.fieldHelpMsgL .~ "Absolute path to build config file (make build system only)" - , Menu.createEditableField (Common.MenuElement Common.PatchesEditBox) patchesV patches k + , Menu.createEditableField (Common.MenuElement Common.PatchesEditBox) patchesV patches & Menu.fieldLabelL .~ "patches" & Menu.fieldHelpMsgL .~ "Either a URI to a patch (https/http/file) or Absolute path to patch directory" - , Menu.createEditableField (Common.MenuElement Common.CrossTargetEditBox) (Right . Just) crossTarget k + , Menu.createEditableField (Common.MenuElement Common.CrossTargetEditBox) (Right . Just) crossTarget & Menu.fieldLabelL .~ "cross target" & Menu.fieldHelpMsgL .~ "Build cross-compiler for this platform" , Menu.createSelectField (Common.MenuElement Common.BuildSystemEditBox) (buildSystem % (iso Just join)) (Nothing :| [Just Hadrian, Just Make]) showMaybeBuildSystem k & Menu.fieldLabelL .~ "build system" & Menu.fieldHelpMsgL .~ "Select the build system" - , Menu.createEditableField (Common.MenuElement Common.OvewrwiteVerEditBox) versionV overwriteVer k + , Menu.createEditableField (Common.MenuElement Common.OvewrwiteVerEditBox) versionV overwriteVer & Menu.fieldLabelL .~ "overwrite-version" & Menu.fieldHelpMsgL .~ "Allows to overwrite the finally installed VERSION with a different one. Allows to specify patterns: %v (version), %b (branch name), %h (short commit hash), %H (long commit hash), %g ('git describe' output)" - , Menu.createEditableField (Common.MenuElement Common.IsolateEditBox) filepathV isolateDir k + , Menu.createEditableField (Common.MenuElement Common.IsolateEditBox) filepathV isolateDir & Menu.fieldLabelL .~ "isolated" & Menu.fieldHelpMsgL .~ "install in an isolated absolute directory instead of the default one" - , Menu.createEditableField (Common.MenuElement Common.GitRefEditBox) (Right . Just . T.unpack) gitRef k + , Menu.createEditableField (Common.MenuElement Common.GitRefEditBox) (Right . Just . T.unpack) gitRef & Menu.fieldLabelL .~ "git-ref" & Menu.fieldHelpMsgL .~ "The git commit/branch/ref to build from" ] diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs index 0867026e..97b8711b 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/CompileHLS.hs @@ -34,7 +34,7 @@ module GHCup.Brick.Widgets.Menus.CompileHLS ( ) where -import GHCup.Brick.Widgets.Menu (Menu) +import GHCup.Brick.Widgets.Menu (Menu, MenuKeyBindings) import qualified GHCup.Brick.Widgets.Menu as Menu import GHCup.Brick.Common(Name(..)) import Brick @@ -44,7 +44,7 @@ import Brick import Prelude hiding ( appendFile ) import Optics.TH (makeLenses) import qualified GHCup.Brick.Common as Common -import GHCup.Types (KeyCombination, VersionPattern, ToolVersion(..)) +import GHCup.Types (VersionPattern, ToolVersion(..)) import URI.ByteString (URI) import qualified Data.Text as T import Data.Bifunctor (Bifunctor(..)) @@ -76,7 +76,7 @@ makeLenses ''CompileHLSOptions type CompileHLSMenu = Menu CompileHLSOptions Name -create :: KeyCombination -> [Version] -> CompileHLSMenu +create :: MenuKeyBindings -> [Version] -> CompileHLSMenu create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile HLS" validator k buttons fields where initialState = @@ -150,7 +150,7 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile HLS & Menu.fieldLabelL .~ label & Menu.fieldHelpMsgL .~ "GHC versions to compile for (Press Enter to edit)" & Menu.fieldStatusL .~ Menu.Invalid "No version selected" - _ -> Menu.createEditableField (Common.MenuElement Common.TargetGhcEditBox) ghcVersionTagEither targetGHCs k + _ -> Menu.createEditableField (Common.MenuElement Common.TargetGhcEditBox) ghcVersionTagEither targetGHCs & Menu.fieldLabelL .~ label & Menu.fieldHelpMsgL .~ "space separated list of GHC versions to compile for" & Menu.fieldStatusL .~ Menu.Invalid "Invalid empty value" @@ -160,31 +160,31 @@ create k availableGHCs = Menu.createMenu CompileGHCBox initialState "Compile HLS , Menu.createCheckBoxField (Common.MenuElement Common.UpdateCabalCheckBox) updateCabal & Menu.fieldLabelL .~ "cabal update" & Menu.fieldHelpMsgL .~ "Run 'cabal update' before the build" - , Menu.createEditableField (Common.MenuElement Common.JobsEditBox) jobsV jobs k + , Menu.createEditableField (Common.MenuElement Common.JobsEditBox) jobsV jobs & Menu.fieldLabelL .~ "jobs" & Menu.fieldHelpMsgL .~ "How many jobs to use for make" , Menu.createCheckBoxField (Common.MenuElement Common.SetCheckBox) setCompile & Menu.fieldLabelL .~ "set" & Menu.fieldHelpMsgL .~ "Set as active version after install" - , Menu.createEditableField (Common.MenuElement Common.AdditionalEditBox) additionalValidator cabalArgs k + , Menu.createEditableField (Common.MenuElement Common.AdditionalEditBox) additionalValidator cabalArgs & Menu.fieldLabelL .~ "CABAL_ARGS" & Menu.fieldHelpMsgL .~ "Additional arguments to cabal install" - , Menu.createEditableField (Common.MenuElement Common.IsolateEditBox) filepathV isolateDir k + , Menu.createEditableField (Common.MenuElement Common.IsolateEditBox) filepathV isolateDir & Menu.fieldLabelL .~ "isolated" & Menu.fieldHelpMsgL .~ "install in an isolated absolute directory instead of the default one" - , Menu.createEditableField (Common.MenuElement Common.OvewrwiteVerEditBox) overWriteVersionParser overwriteVer k + , Menu.createEditableField (Common.MenuElement Common.OvewrwiteVerEditBox) overWriteVersionParser overwriteVer & Menu.fieldLabelL .~ "overwrite version" & Menu.fieldHelpMsgL .~ "Allows to overwrite the finally installed VERSION with a different one. Allows to specify patterns: %v (version), %b (branch name), %h (short commit hash), %H (long commit hash), %g ('git describe' output)" - , Menu.createEditableField (Common.MenuElement Common.PatchesEditBox) patchesV patches k + , Menu.createEditableField (Common.MenuElement Common.PatchesEditBox) patchesV patches & Menu.fieldLabelL .~ "patches" & Menu.fieldHelpMsgL .~ "Either a URI to a patch (https/http/file) or Absolute path to patch directory" - , Menu.createEditableField (Common.MenuElement Common.CabalProjectEditBox) cabalProjectV cabalProject k + , Menu.createEditableField (Common.MenuElement Common.CabalProjectEditBox) cabalProjectV cabalProject & Menu.fieldLabelL .~ "cabal project" & Menu.fieldHelpMsgL .~ "If relative filepath, specifies the path to cabal.project inside the unpacked HLS tarball/checkout. Otherwise expects a full URI with https/http/file scheme." - , Menu.createEditableField (Common.MenuElement Common.CabalProjectLocalEditBox) cabalProjectLocalV cabalProjectLocal k + , Menu.createEditableField (Common.MenuElement Common.CabalProjectLocalEditBox) cabalProjectLocalV cabalProjectLocal & Menu.fieldLabelL .~ "cabal project local" & Menu.fieldHelpMsgL .~ "URI (https/http/file) to a cabal.project.local to be used for the build. Will be copied over." - , Menu.createEditableField (Common.MenuElement Common.GitRefEditBox) (Right . Just . T.unpack) gitRef k + , Menu.createEditableField (Common.MenuElement Common.GitRefEditBox) (Right . Just . T.unpack) gitRef & Menu.fieldLabelL .~ "git-ref" & Menu.fieldHelpMsgL .~ "The git commit/branch/ref to build from" ] diff --git a/lib-tui/GHCup/Brick/Widgets/Menus/Context.hs b/lib-tui/GHCup/Brick/Widgets/Menus/Context.hs index f4e00af4..7807cecf 100644 --- a/lib-tui/GHCup/Brick/Widgets/Menus/Context.hs +++ b/lib-tui/GHCup/Brick/Widgets/Menus/Context.hs @@ -10,12 +10,12 @@ import Prelude hiding (appendFile) import Data.Versions (prettyVer) import GHCup.List ( ListResult(..) ) -import GHCup.Types (KeyCombination, Tool (..)) +import GHCup.Types (Tool (..)) import qualified GHCup.Brick.Common as Common import qualified GHCup.Brick.Widgets.Menu as Menu import GHCup.Brick.Common (Name (..)) -import GHCup.Brick.Widgets.Menu (Menu) +import GHCup.Brick.Widgets.Menu (Menu, MenuKeyBindings) import qualified Brick.Widgets.Core as Brick import qualified Brick.Widgets.Border as Border import qualified Brick.Focus as F @@ -28,8 +28,8 @@ import Data.Foldable (foldl') type ContextMenu = Menu ListResult Name -create :: ListResult -> KeyCombination -> ContextMenu -create lr exit_key = Menu.createMenu Common.ContextBox lr "" validator exit_key buttons [] +create :: ListResult -> MenuKeyBindings -> ContextMenu +create lr keyBindings = Menu.createMenu Common.ContextBox lr "" validator keyBindings buttons [] where advInstallButton = Menu.createButtonField (MenuElement Common.AdvanceInstallButton) @@ -59,7 +59,7 @@ draw menu = , Brick.txt " " , Brick.padRight Brick.Max $ Brick.txt "Press " - <+> Common.keyToWidget (menu ^. Menu.menuExitKeyL) + <+> Common.keyToWidget (menu ^. Menu.menuKeyBindingsL % Menu.mKbQuitL) <+> Brick.txt " to go back" ] where diff --git a/lib-tui/GHCup/BrickMain.hs b/lib-tui/GHCup/BrickMain.hs index f684bfd6..f1399c15 100644 --- a/lib-tui/GHCup/BrickMain.hs +++ b/lib-tui/GHCup/BrickMain.hs @@ -18,7 +18,7 @@ module GHCup.BrickMain where import GHCup.List ( ListResult (..)) import GHCup.Types ( Settings(noColor), Tool (GHC), - AppState(ghcupInfo, settings, keyBindings, loggerConfig), KeyCombination (KeyCombination), bQuit ) + AppState(ghcupInfo, settings, keyBindings, loggerConfig), KeyBindings(..) ) import GHCup.Prelude.Logger ( logError ) import qualified GHCup.Brick.Actions as Actions import qualified GHCup.Brick.Common as Common @@ -29,6 +29,7 @@ import qualified GHCup.Brick.Widgets.Menus.Context as ContextMenu import qualified GHCup.Brick.Widgets.SectionList as Navigation import qualified GHCup.Brick.Widgets.Menus.AdvanceInstall as AdvanceInstall import qualified GHCup.Brick.Widgets.Menus.CompileGHC as CompileGHC +import GHCup.Brick.Widgets.Menu (MenuKeyBindings(..)) import qualified Brick import qualified Graphics.Vty as Vty @@ -53,7 +54,9 @@ brickMain s = do Right ad -> do let initial_list = Actions.constructList ad Common.defaultAppSettings Nothing current_element = Navigation.sectionListSelectedElement initial_list - exit_key = bQuit . keyBindings $ s + exit_key = + let KeyBindings {..} = keyBindings s + in MenuKeyBindings { mKbUp = bUp, mKbDown = bDown, mKbQuit = bQuit} case current_element of Nothing -> do flip runReaderT s $ logError "Error building app state: empty ResultList" From 6d48956026bf856b59ad1e46831f9bd908a4eabd Mon Sep 17 00:00:00 2001 From: Divam Date: Thu, 26 Sep 2024 18:23:11 +0900 Subject: [PATCH 41/41] minor code reformat --- lib-tui/GHCup/Brick/App.hs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib-tui/GHCup/Brick/App.hs b/lib-tui/GHCup/Brick/App.hs index 3343503d..fac6c1b9 100644 --- a/lib-tui/GHCup/Brick/App.hs +++ b/lib-tui/GHCup/Brick/App.hs @@ -155,8 +155,7 @@ compileHLSHandler = menuWithOverlayHandler compileHLSMenu Actions.compileHLS Com -- | Passes all events to innerHandler if an overlay is opened -- else handles the exitKey and Enter key for the Menu's "OkButton" -menuWithOverlayHandler - :: Lens' BrickState (Menu.Menu t Name) +menuWithOverlayHandler :: Lens' BrickState (Menu.Menu t Name) -> (t -> ((Int, ListResult) -> ReaderT AppState IO (Either String a))) -> (BrickEvent Name e -> EventM Name (Menu.Menu t Name) ()) -> BrickEvent Name e