Merge remote-tracking branch 'origin/pr/850'
This commit is contained in:
commit
e214695a3e
@ -7,6 +7,10 @@
|
|||||||
{-# LANGUAGE ViewPatterns #-}
|
{-# LANGUAGE ViewPatterns #-}
|
||||||
{-# OPTIONS_GHC -Wno-unused-record-wildcards #-}
|
{-# OPTIONS_GHC -Wno-unused-record-wildcards #-}
|
||||||
{-# OPTIONS_GHC -Wno-unused-matches #-}
|
{-# OPTIONS_GHC -Wno-unused-matches #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE TemplateHaskell #-}
|
||||||
|
{-# LANGUAGE BangPatterns #-}
|
||||||
|
|
||||||
module BrickMain where
|
module BrickMain where
|
||||||
|
|
||||||
@ -23,13 +27,23 @@ import GHCup.Prelude.Process
|
|||||||
import GHCup.Prompts
|
import GHCup.Prompts
|
||||||
|
|
||||||
import Brick
|
import Brick
|
||||||
import Brick.Widgets.Border
|
( BrickEvent(VtyEvent, MouseDown),
|
||||||
import Brick.Widgets.Border.Style
|
App(..),
|
||||||
import Brick.Widgets.Center
|
Padding(Max, Pad),
|
||||||
import Brick.Widgets.List ( listSelectedFocusedAttr
|
AttrMap,
|
||||||
, listSelectedAttr
|
EventM,
|
||||||
, listAttr
|
Size(..),
|
||||||
)
|
Widget(..),
|
||||||
|
ViewportType (Vertical),
|
||||||
|
(<+>),
|
||||||
|
(<=>))
|
||||||
|
import qualified Brick
|
||||||
|
import Brick.Widgets.Border ( hBorder, borderWithLabel)
|
||||||
|
import Brick.Widgets.Border.Style ( unicode )
|
||||||
|
import Brick.Widgets.Center ( center, centerLayer )
|
||||||
|
import qualified Brick.Widgets.List as L
|
||||||
|
import Brick.Focus (FocusRing)
|
||||||
|
import qualified Brick.Focus as F
|
||||||
import Codec.Archive
|
import Codec.Archive
|
||||||
import Control.Applicative
|
import Control.Applicative
|
||||||
import Control.Exception.Safe
|
import Control.Exception.Safe
|
||||||
@ -41,13 +55,14 @@ import Control.Monad.Trans.Except
|
|||||||
import Control.Monad.Trans.Resource
|
import Control.Monad.Trans.Resource
|
||||||
import Data.Bool
|
import Data.Bool
|
||||||
import Data.Functor
|
import Data.Functor
|
||||||
|
import Data.Function ( (&), on)
|
||||||
import Data.List
|
import Data.List
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
import Data.IORef
|
import Data.IORef (IORef, readIORef, newIORef, writeIORef, modifyIORef)
|
||||||
import Data.Vector ( Vector
|
import Data.Vector ( Vector
|
||||||
, (!?)
|
|
||||||
)
|
)
|
||||||
import Data.Versions
|
import Data.Versions hiding (Lens')
|
||||||
import Haskus.Utils.Variant.Excepts
|
import Haskus.Utils.Variant.Excepts
|
||||||
import Prelude hiding ( appendFile )
|
import Prelude hiding ( appendFile )
|
||||||
import System.Exit
|
import System.Exit
|
||||||
@ -68,6 +83,175 @@ import System.FilePath
|
|||||||
import qualified System.Posix.Process as SPP
|
import qualified System.Posix.Process as SPP
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
import Optics.TH (makeLenses, makeLensesFor)
|
||||||
|
import Optics.State (use)
|
||||||
|
import Optics.State.Operators ( (.=), (%=), (<%=))
|
||||||
|
import Optics.Operators ((.~), (^.), (%~))
|
||||||
|
import Optics.Getter (view)
|
||||||
|
import Optics.Lens (Lens', lens, toLensVL)
|
||||||
|
|
||||||
|
{- Brick's widget:
|
||||||
|
It is a FocusRing over many list's. Each list contains the information for each tool. Each list has an internal name (for Brick's runtime)
|
||||||
|
and a label which we can use in rendering. This data-structure helps to reuse Brick.Widget.List and to navegate easily across
|
||||||
|
|
||||||
|
Consider this code as private. GenericSectionList should not be used directly as the FocusRing should be align with the Vector containing
|
||||||
|
the elements, otherwise you'd be focusing on a non-existent widget with unknown result (In theory the code is safe unless you have an empty section list).
|
||||||
|
|
||||||
|
- To build a SectionList use the safe constructor sectionList
|
||||||
|
- To access sections use the lens provider sectionL and the name of the section you'd like to access
|
||||||
|
- You can modify Brick.Widget.List.GenericList within GenericSectionList via sectionL but do not
|
||||||
|
modify the vector length
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
|
data GenericSectionList n t e
|
||||||
|
= GenericSectionList
|
||||||
|
{ sectionListFocusRing :: FocusRing n -- ^ The FocusRing for all sections
|
||||||
|
, sectionListElements :: !(Vector (L.GenericList n t e)) -- ^ A key-value vector
|
||||||
|
, sectionListName :: n -- ^ The section list name
|
||||||
|
}
|
||||||
|
|
||||||
|
makeLensesFor [("sectionListFocusRing", "sectionListFocusRingL"), ("sectionListElements", "sectionListElementsL"), ("sectionListName", "sectionListNameL")] ''GenericSectionList
|
||||||
|
|
||||||
|
type SectionList n e = GenericSectionList n V.Vector e
|
||||||
|
|
||||||
|
|
||||||
|
-- | Build a SectionList from nonempty list. If empty we could not defined sectionL lenses.
|
||||||
|
sectionList :: Foldable t
|
||||||
|
=> n -- The name of the section list
|
||||||
|
-> [(n, t e)] -- a list of tuples (section name, collection of elements)
|
||||||
|
-> Int
|
||||||
|
-> GenericSectionList n t e
|
||||||
|
sectionList name elements height
|
||||||
|
= GenericSectionList
|
||||||
|
{ sectionListFocusRing = F.focusRing [section_name | (section_name, _) <- elements]
|
||||||
|
, sectionListElements = V.fromList [L.list section_name els height | (section_name, els) <- elements]
|
||||||
|
, sectionListName = name
|
||||||
|
}
|
||||||
|
-- | This lens constructor, takes a name and looks if a section has such a name.
|
||||||
|
-- Used to dispatch events to sections. It is a partial function only meant to
|
||||||
|
-- be used with the FocusRing inside GenericSectionList
|
||||||
|
sectionL :: Eq n => n -> Lens' (GenericSectionList n t e) (L.GenericList n t e)
|
||||||
|
sectionL section_name = lens g s
|
||||||
|
where is_section_name = (== section_name) . L.listName
|
||||||
|
g section_list =
|
||||||
|
let elms = section_list ^. sectionListElementsL
|
||||||
|
zeroth = elms V.! 0 -- TODO: This crashes for empty vectors.
|
||||||
|
in fromMaybe zeroth (V.find is_section_name elms)
|
||||||
|
s gl@(GenericSectionList _ elms _) list =
|
||||||
|
case V.findIndex is_section_name elms of
|
||||||
|
Nothing -> gl
|
||||||
|
Just i -> let new_elms = V.update elms (V.fromList [(i, list)])
|
||||||
|
in gl & sectionListElementsL .~ new_elms
|
||||||
|
|
||||||
|
moveDown :: (L.Splittable t, Ord n, Foldable t) => EventM n (GenericSectionList n t e) ()
|
||||||
|
moveDown = do
|
||||||
|
ring <- use sectionListFocusRingL
|
||||||
|
case F.focusGetCurrent ring of
|
||||||
|
Nothing -> pure ()
|
||||||
|
Just l -> do -- If it is the last element, move to the first element of the next focus; else, just handle regular list event.
|
||||||
|
current_list <- use (sectionL l)
|
||||||
|
let current_idx = L.listSelected current_list
|
||||||
|
list_length = current_list & length
|
||||||
|
if current_idx == Just (list_length - 1)
|
||||||
|
then do
|
||||||
|
new_focus <- sectionListFocusRingL <%= F.focusNext
|
||||||
|
case F.focusGetCurrent new_focus of
|
||||||
|
Nothing -> pure () -- |- Optic.Zoom.zoom doesn't typecheck but Lens.Micro.Mtl.zoom does. It is re-exported by Brick
|
||||||
|
Just new_l -> Brick.zoom (toLensVL $ sectionL new_l) (Brick.modify L.listMoveToBeginning)
|
||||||
|
else Brick.zoom (toLensVL $ sectionL l) $ Brick.modify L.listMoveDown
|
||||||
|
|
||||||
|
moveUp :: (L.Splittable t, Ord n, Foldable t) => EventM n (GenericSectionList n t e) ()
|
||||||
|
moveUp = do
|
||||||
|
ring <- use sectionListFocusRingL
|
||||||
|
case F.focusGetCurrent ring of
|
||||||
|
Nothing -> pure ()
|
||||||
|
Just l -> do -- If it is the first element, move to the last element of the prev focus; else, just handle regular list event.
|
||||||
|
current_list <- use (sectionL l)
|
||||||
|
let current_idx = L.listSelected current_list
|
||||||
|
if current_idx == Just 0
|
||||||
|
then do
|
||||||
|
new_focus <- sectionListFocusRingL <%= F.focusPrev
|
||||||
|
case F.focusGetCurrent new_focus of
|
||||||
|
Nothing -> pure ()
|
||||||
|
Just new_l -> Brick.zoom (toLensVL $ sectionL new_l) (Brick.modify L.listMoveToEnd)
|
||||||
|
else Brick.zoom (toLensVL $ sectionL l) $ Brick.modify L.listMoveUp
|
||||||
|
|
||||||
|
-- | Handle events for list cursor movement. Events handled are:
|
||||||
|
--
|
||||||
|
-- * Up (up arrow key). If first element of section, then jump prev section
|
||||||
|
-- * Down (down arrow key). If last element of section, then jump next section
|
||||||
|
-- * Page Up (PgUp)
|
||||||
|
-- * Page Down (PgDown)
|
||||||
|
-- * Go to next section (Tab)
|
||||||
|
-- * Go to prev section (BackTab)
|
||||||
|
handleGenericListEvent :: (Foldable t, L.Splittable t, Ord n)
|
||||||
|
=> BrickEvent n a
|
||||||
|
-> EventM n (GenericSectionList n t e) ()
|
||||||
|
handleGenericListEvent (VtyEvent (Vty.EvResize _ _)) = pure ()
|
||||||
|
handleGenericListEvent (VtyEvent (Vty.EvKey (Vty.KChar '\t') [])) = sectionListFocusRingL %= F.focusNext
|
||||||
|
handleGenericListEvent (VtyEvent (Vty.EvKey Vty.KBackTab [])) = sectionListFocusRingL %= F.focusPrev
|
||||||
|
handleGenericListEvent (MouseDown _ Vty.BScrollDown _ _) = moveDown
|
||||||
|
handleGenericListEvent (MouseDown _ Vty.BScrollUp _ _) = moveUp
|
||||||
|
handleGenericListEvent (VtyEvent (Vty.EvKey Vty.KDown [])) = moveDown
|
||||||
|
handleGenericListEvent (VtyEvent (Vty.EvKey Vty.KUp [])) = moveUp
|
||||||
|
handleGenericListEvent (VtyEvent ev) = do
|
||||||
|
ring <- use sectionListFocusRingL
|
||||||
|
case F.focusGetCurrent ring of
|
||||||
|
Nothing -> pure ()
|
||||||
|
Just l -> Brick.zoom (toLensVL $ sectionL l) $ L.handleListEvent ev
|
||||||
|
handleGenericListEvent _ = pure ()
|
||||||
|
|
||||||
|
-- This re-uses Brick.Widget.List.renderList
|
||||||
|
renderSectionList :: (Traversable t, Ord n, Show n, Eq n, L.Splittable t)
|
||||||
|
=> (Bool -> e -> Widget n) -- ^ Rendering function of the list element, True for the selected element
|
||||||
|
-> Bool -- ^ Whether the section list has focus
|
||||||
|
-> GenericSectionList n t e -- ^ The section list to render
|
||||||
|
-> Widget n
|
||||||
|
renderSectionList render_elem section_focus (GenericSectionList focus elms sl_name) =
|
||||||
|
Brick.Widget Brick.Greedy Brick.Greedy $ do
|
||||||
|
c <- Brick.getContext
|
||||||
|
let -- A section is focused if the whole thing is focused, and the inner list has focus
|
||||||
|
section_is_focused l = section_focus && (Just (L.listName l) == F.focusGetCurrent focus)
|
||||||
|
-- We need to limit the widget size when the length of the list is higher than the size of the terminal
|
||||||
|
limit = min (Brick.windowHeight c) (Brick.availHeight c)
|
||||||
|
s_idx = fromMaybe 0 $ V.findIndex section_is_focused elms
|
||||||
|
render_inner_list has_focus l = Brick.vLimit (length l) $ L.renderList (\b -> render_elem (b && has_focus)) has_focus l
|
||||||
|
(widget, off) =
|
||||||
|
V.ifoldl' (\wacc i list ->
|
||||||
|
let has_focus_list = section_is_focused list
|
||||||
|
(!acc_widget, !acc_off) = wacc
|
||||||
|
new_widget = if i == 0 then render_inner_list has_focus_list list else hBorder <=> render_inner_list has_focus_list list
|
||||||
|
new_off
|
||||||
|
| i < s_idx = 1 + L.listItemHeight list * length list
|
||||||
|
| i == s_idx = 1 + L.listItemHeight list * fromMaybe 0 (L.listSelected list)
|
||||||
|
| otherwise = 0
|
||||||
|
in (acc_widget <=> new_widget, acc_off + new_off)
|
||||||
|
)
|
||||||
|
(Brick.emptyWidget, 0)
|
||||||
|
elms
|
||||||
|
Brick.render $ Brick.viewport sl_name Brick.Vertical $ Brick.translateBy (Brick.Location (0, min 0 (limit-off))) widget
|
||||||
|
|
||||||
|
-- | Equivalent to listSelectedElement
|
||||||
|
sectionListSelectedElement :: (Eq n, L.Splittable t, Traversable t, Semigroup (t e)) => GenericSectionList n t e -> Maybe (Int, e)
|
||||||
|
sectionListSelectedElement generic_section_list = do
|
||||||
|
current_focus <- generic_section_list ^. sectionListFocusRingL & F.focusGetCurrent
|
||||||
|
let current_section = generic_section_list ^. sectionL current_focus
|
||||||
|
L.listSelectedElement current_section
|
||||||
|
|
||||||
|
{- Brick app data structures.
|
||||||
|
|
||||||
|
In this section we define the state, the widgets and the core data structures which we will be using for the brick app.
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
|
data Name = AllTools -- The main list widget
|
||||||
|
| Singular Tool -- The particular list for each tool
|
||||||
|
| KeyInfoBox -- The text box widget with action informacion
|
||||||
|
| TutorialBox -- The tutorial widget
|
||||||
|
deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
|
data Mode = Navigation | KeyInfo | Tutorial deriving (Eq, Show, Ord)
|
||||||
|
|
||||||
installedSign :: String
|
installedSign :: String
|
||||||
#if IS_WINDOWS
|
#if IS_WINDOWS
|
||||||
@ -92,56 +276,45 @@ notInstalledSign = "✗ "
|
|||||||
|
|
||||||
|
|
||||||
data BrickData = BrickData
|
data BrickData = BrickData
|
||||||
{ lr :: [ListResult]
|
{ _lr :: [ListResult]
|
||||||
}
|
}
|
||||||
deriving Show
|
deriving Show
|
||||||
|
|
||||||
data BrickSettings = BrickSettings
|
makeLenses ''BrickData
|
||||||
{ showAllVersions :: Bool
|
|
||||||
}
|
|
||||||
deriving Show
|
|
||||||
|
|
||||||
data BrickInternalState = BrickInternalState
|
data BrickSettings = BrickSettings { _showAllVersions :: Bool}
|
||||||
{ clr :: Vector ListResult
|
--deriving Show
|
||||||
, ix :: Int
|
|
||||||
}
|
makeLenses ''BrickSettings
|
||||||
deriving Show
|
|
||||||
|
type BrickInternalState = SectionList Name ListResult
|
||||||
|
|
||||||
data BrickState = BrickState
|
data BrickState = BrickState
|
||||||
{ appData :: BrickData
|
{ _appData :: BrickData
|
||||||
, appSettings :: BrickSettings
|
, _appSettings :: BrickSettings
|
||||||
, appState :: BrickInternalState
|
, _appState :: BrickInternalState
|
||||||
, appKeys :: KeyBindings
|
, _appKeys :: KeyBindings
|
||||||
|
, _mode :: Mode
|
||||||
|
}
|
||||||
|
--deriving Show
|
||||||
|
|
||||||
|
makeLenses ''BrickState
|
||||||
|
|
||||||
|
app :: AttrMap -> AttrMap -> App BrickState () Name
|
||||||
|
app attrs dimAttrs =
|
||||||
|
App { appDraw = drawUI dimAttrs
|
||||||
|
, appHandleEvent = eventHandler
|
||||||
|
, appStartEvent = return ()
|
||||||
|
, appAttrMap = const attrs
|
||||||
|
, appChooseCursor = Brick.showFirstCursor
|
||||||
}
|
}
|
||||||
deriving Show
|
|
||||||
|
|
||||||
|
|
||||||
keyHandlers :: KeyBindings
|
{- Drawing.
|
||||||
-> [ ( KeyCombination
|
|
||||||
, BrickSettings -> String
|
|
||||||
, BrickState -> EventM String BrickState ()
|
|
||||||
)
|
|
||||||
]
|
|
||||||
keyHandlers KeyBindings {..} =
|
|
||||||
[ (bQuit, const "Quit" , \_ -> halt)
|
|
||||||
, (bInstall, const "Install" , withIOAction install')
|
|
||||||
, (bUninstall, const "Uninstall", withIOAction del')
|
|
||||||
, (bSet, const "Set" , withIOAction set')
|
|
||||||
, (bChangelog, const "ChangeLog", withIOAction changelog')
|
|
||||||
, ( bShowAllVersions
|
|
||||||
, \BrickSettings {..} ->
|
|
||||||
if showAllVersions then "Don't show all versions" else "Show all versions"
|
|
||||||
, hideShowHandler (not . showAllVersions)
|
|
||||||
)
|
|
||||||
, (bUp, const "Up", \BrickState {..} -> put BrickState{ appState = moveCursor 1 appState Up, .. })
|
|
||||||
, (bDown, const "Down", \BrickState {..} -> put BrickState{ appState = moveCursor 1 appState Down, .. })
|
|
||||||
]
|
|
||||||
where
|
|
||||||
hideShowHandler f BrickState{..} =
|
|
||||||
let newAppSettings = appSettings { showAllVersions = f appSettings }
|
|
||||||
newInternalState = constructList appData newAppSettings (Just appState)
|
|
||||||
in put (BrickState appData newAppSettings newInternalState appKeys)
|
|
||||||
|
|
||||||
|
The section for creating our widgets.
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
showKey :: Vty.Key -> String
|
showKey :: Vty.Key -> String
|
||||||
showKey (Vty.KChar c) = [c]
|
showKey (Vty.KChar c) = [c]
|
||||||
@ -153,38 +326,38 @@ showMod :: Vty.Modifier -> String
|
|||||||
showMod = tail . show
|
showMod = tail . show
|
||||||
|
|
||||||
|
|
||||||
ui :: AttrMap -> BrickState -> Widget String
|
drawNavigation :: AttrMap -> BrickState -> Widget Name
|
||||||
ui dimAttrs BrickState{ appSettings = as@BrickSettings{}, ..}
|
drawNavigation dimAttrs BrickState{ _appSettings = as@BrickSettings{}, ..}
|
||||||
= padBottom Max
|
= Brick.padBottom Max
|
||||||
( withBorderStyle unicode
|
( Brick.withBorderStyle unicode
|
||||||
$ borderWithLabel (str "GHCup")
|
$ borderWithLabel (Brick.str "GHCup")
|
||||||
(center (header <=> hBorder <=> renderList' appState))
|
(center (header <=> hBorder <=> renderList' _appState))
|
||||||
)
|
)
|
||||||
<=> footer
|
<=> footer
|
||||||
|
|
||||||
where
|
where
|
||||||
footer =
|
footer =
|
||||||
withAttr (attrName "help")
|
Brick.withAttr helpAttr
|
||||||
. txtWrap
|
. Brick.txtWrap
|
||||||
. T.pack
|
. T.pack
|
||||||
. foldr1 (\x y -> x <> " " <> y)
|
. foldr1 (\x y -> x <> " " <> y)
|
||||||
. fmap (\(KeyCombination key mods, s, _) -> intercalate "+" (showKey key : (showMod <$> mods)) <> ":" <> s as)
|
. fmap (\(KeyCombination key mods, s, _) -> intercalate "+" (showKey key : (showMod <$> mods)) <> ":" <> s as)
|
||||||
$ keyHandlers appKeys
|
$ keyHandlers _appKeys
|
||||||
header =
|
header =
|
||||||
minHSize 2 emptyWidget
|
minHSize 2 Brick.emptyWidget
|
||||||
<+> padLeft (Pad 2) (minHSize 6 $ str "Tool")
|
<+> Brick.padLeft (Pad 2) (minHSize 6 $ Brick.str "Tool")
|
||||||
<+> minHSize 15 (str "Version")
|
<+> minHSize 15 (Brick.str "Version")
|
||||||
<+> padLeft (Pad 1) (minHSize 25 $ str "Tags")
|
<+> Brick.padLeft (Pad 1) (minHSize 25 $ Brick.str "Tags")
|
||||||
<+> padLeft (Pad 5) (str "Notes")
|
<+> Brick.padLeft (Pad 5) (Brick.str "Notes")
|
||||||
renderList' bis@BrickInternalState{..} =
|
renderList' bis =
|
||||||
let minTagSize = V.maximum $ V.map (length . intercalate "," . fmap tagToString . lTag) clr
|
let allElements = V.concatMap L.listElements $ sectionListElements bis
|
||||||
minVerSize = V.maximum $ V.map (\ListResult{..} -> T.length $ tVerToText (GHCTargetVersion lCross lVer)) clr
|
minTagSize = V.maximum $ V.map (length . intercalate "," . fmap tagToString . lTag) allElements
|
||||||
in withDefAttr listAttr . drawListElements (renderItem minTagSize minVerSize) True $ bis
|
minVerSize = V.maximum $ V.map (\ListResult{..} -> T.length $ tVerToText (GHCTargetVersion lCross lVer)) allElements
|
||||||
renderItem minTagSize minVerSize _ b listResult@ListResult{lTag = lTag', ..} =
|
in Brick.withDefAttr L.listAttr $ renderSectionList (renderItem minTagSize minVerSize) True bis
|
||||||
|
renderItem minTagSize minVerSize b listResult@ListResult{lTag = lTag', ..} =
|
||||||
let marks = if
|
let marks = if
|
||||||
| lSet -> (withAttr (attrName "set") $ str setSign)
|
| lSet -> (Brick.withAttr setAttr $ Brick.str setSign)
|
||||||
| lInstalled -> (withAttr (attrName "installed") $ str installedSign)
|
| lInstalled -> (Brick.withAttr installedAttr $ Brick.str installedSign)
|
||||||
| otherwise -> (withAttr (attrName "not-installed") $ str notInstalledSign)
|
| otherwise -> (Brick.withAttr notInstalledAttr $ Brick.str notInstalledSign)
|
||||||
ver = case lCross of
|
ver = case lCross of
|
||||||
Nothing -> T.unpack . prettyVer $ lVer
|
Nothing -> T.unpack . prettyVer $ lVer
|
||||||
Just c -> T.unpack (c <> "-" <> prettyVer lVer)
|
Just c -> T.unpack (c <> "-" <> prettyVer lVer)
|
||||||
@ -192,127 +365,192 @@ ui dimAttrs BrickState{ appSettings = as@BrickSettings{}, ..}
|
|||||||
| lNoBindist && not lInstalled
|
| lNoBindist && not lInstalled
|
||||||
&& not b -- TODO: overloading dim and active ignores active
|
&& not b -- TODO: overloading dim and active ignores active
|
||||||
-- so we hack around it here
|
-- so we hack around it here
|
||||||
= updateAttrMap (const dimAttrs) . withAttr (attrName "no-bindist")
|
= Brick.updateAttrMap (const dimAttrs) . Brick.withAttr (Brick.attrName "no-bindist")
|
||||||
| otherwise = id
|
| otherwise = id
|
||||||
hooray
|
hooray
|
||||||
| elem Latest lTag' && not lInstalled =
|
| elem Latest lTag' && not lInstalled =
|
||||||
withAttr (attrName "hooray")
|
Brick.withAttr hoorayAttr
|
||||||
| otherwise = id
|
| otherwise = id
|
||||||
active = if b then putCursor "GHCup" (Location (0,0)) . forceAttr (attrName "active") else id
|
in hooray $ dim
|
||||||
in hooray $ active $ dim
|
|
||||||
( marks
|
( marks
|
||||||
<+> padLeft (Pad 2)
|
<+> Brick.padLeft (Pad 2)
|
||||||
( minHSize 6
|
( minHSize 6
|
||||||
(printTool lTool)
|
(printTool lTool)
|
||||||
)
|
)
|
||||||
<+> minHSize minVerSize (str ver)
|
<+> minHSize minVerSize (Brick.str ver)
|
||||||
<+> (let l = catMaybes . fmap printTag $ sort lTag'
|
<+> (let l = catMaybes . fmap printTag $ sort lTag'
|
||||||
in padLeft (Pad 1) $ minHSize minTagSize $ if null l
|
in Brick.padLeft (Pad 1) $ minHSize minTagSize $ if null l
|
||||||
then emptyWidget
|
then Brick.emptyWidget
|
||||||
else foldr1 (\x y -> x <+> str "," <+> y) l
|
else foldr1 (\x y -> x <+> Brick.str "," <+> y) l
|
||||||
)
|
)
|
||||||
<+> padLeft (Pad 5)
|
<+> Brick.padLeft (Pad 5)
|
||||||
( let notes = printNotes listResult
|
( let notes = printNotes listResult
|
||||||
in if null notes
|
in if null notes
|
||||||
then emptyWidget
|
then Brick.emptyWidget
|
||||||
else foldr1 (\x y -> x <+> str "," <+> y) notes
|
else foldr1 (\x y -> x <+> Brick.str "," <+> y) notes
|
||||||
)
|
)
|
||||||
<+> vLimit 1 (fill ' ')
|
<+> Brick.vLimit 1 (Brick.fill ' ')
|
||||||
)
|
)
|
||||||
|
|
||||||
printTag Recommended = Just $ withAttr (attrName "recommended") $ str "recommended"
|
printTag Recommended = Just $ Brick.withAttr recommendedAttr $ Brick.str "recommended"
|
||||||
printTag Latest = Just $ withAttr (attrName "latest") $ str "latest"
|
printTag Latest = Just $ Brick.withAttr latestAttr $ Brick.str "latest"
|
||||||
printTag Prerelease = Just $ withAttr (attrName "prerelease") $ str "prerelease"
|
printTag Prerelease = Just $ Brick.withAttr prereleaseAttr $ Brick.str "prerelease"
|
||||||
printTag Nightly = Just $ withAttr (attrName "nightly") $ str "nightly"
|
printTag Nightly = Just $ Brick.withAttr nightlyAttr $ Brick.str "nightly"
|
||||||
printTag (Base pvp'') = Just $ str ("base-" ++ T.unpack (prettyPVP pvp''))
|
printTag (Base pvp'') = Just $ Brick.str ("base-" ++ T.unpack (prettyPVP pvp''))
|
||||||
printTag Old = Nothing
|
printTag Old = Nothing
|
||||||
printTag LatestPrerelease = Just $ withAttr (attrName "latest-prerelease") $ str "latest-prerelease"
|
printTag LatestPrerelease = Just $ Brick.withAttr latestPrereleaseAttr $ Brick.str "latest-prerelease"
|
||||||
printTag LatestNightly = Just $ withAttr (attrName "latest-nightly") $ str "latest-nightly"
|
printTag LatestNightly = Just $ Brick.withAttr latestNightlyAttr $ Brick.str "latest-nightly"
|
||||||
printTag (UnknownTag t) = Just $ str t
|
printTag (UnknownTag t) = Just $ Brick.str t
|
||||||
|
|
||||||
printTool Cabal = str "cabal"
|
printTool Cabal = Brick.str "cabal"
|
||||||
printTool GHC = str "GHC"
|
printTool GHC = Brick.str "GHC"
|
||||||
printTool GHCup = str "GHCup"
|
printTool GHCup = Brick.str "GHCup"
|
||||||
printTool HLS = str "HLS"
|
printTool HLS = Brick.str "HLS"
|
||||||
printTool Stack = str "Stack"
|
printTool Stack = Brick.str "Stack"
|
||||||
|
|
||||||
printNotes ListResult {..} =
|
printNotes ListResult {..} =
|
||||||
(if hlsPowered then [withAttr (attrName "hls-powered") $ str "hls-powered"] else mempty
|
(if hlsPowered then [Brick.withAttr hlsPoweredAttr $ Brick.str "hls-powered"] else mempty
|
||||||
)
|
)
|
||||||
++ (if lStray then [withAttr (attrName "stray") $ str "stray"] else mempty)
|
++ (if lStray then [Brick.withAttr strayAttr $ Brick.str "stray"] else mempty)
|
||||||
++ (case lReleaseDay of
|
++ (case lReleaseDay of
|
||||||
Nothing -> mempty
|
Nothing -> mempty
|
||||||
Just d -> [withAttr (attrName "day") $ str (show d)])
|
Just d -> [Brick.withAttr dayAttr $ Brick.str (show d)])
|
||||||
|
|
||||||
-- | Draws the list elements.
|
minHSize s' = Brick.hLimit s' . Brick.vLimit 1 . (<+> Brick.fill ' ')
|
||||||
--
|
|
||||||
-- Evaluates the underlying container up to, and a bit beyond, the
|
drawTutorial :: Widget Name
|
||||||
-- selected element. The exact amount depends on available height
|
drawTutorial =
|
||||||
-- for drawing and 'listItemHeight'. At most, it will evaluate up to
|
|
||||||
-- element @(i + h + 1)@ where @i@ is the selected index and @h@ is the
|
|
||||||
-- available height.
|
|
||||||
drawListElements :: (Int -> Bool -> ListResult -> Widget String)
|
|
||||||
-> Bool
|
|
||||||
-> BrickInternalState
|
|
||||||
-> Widget String
|
|
||||||
drawListElements drawElem foc is@(BrickInternalState clr _) =
|
|
||||||
Widget Greedy Greedy $
|
|
||||||
let
|
let
|
||||||
es = clr
|
mkTextBox = Brick.hLimitPercent 70 . Brick.vBox . fmap (Brick.padRight Brick.Max)
|
||||||
listSelected = fmap fst $ listSelectedElement' is
|
txt_separator = hBorder <+> Brick.str " o " <+> hBorder
|
||||||
|
in centerLayer
|
||||||
|
$ Brick.hLimitPercent 75
|
||||||
|
$ Brick.vLimitPercent 50
|
||||||
|
$ Brick.withBorderStyle unicode
|
||||||
|
$ borderWithLabel (Brick.txt "Tutorial")
|
||||||
|
$ Brick.vBox
|
||||||
|
(fmap center
|
||||||
|
[ mkTextBox [Brick.txtWrap "GHCup is a distribution channel for Haskell's tools."]
|
||||||
|
, txt_separator
|
||||||
|
, mkTextBox [
|
||||||
|
Brick.hBox [
|
||||||
|
Brick.txt "This symbol "
|
||||||
|
, Brick.withAttr installedAttr (Brick.str installedSign)
|
||||||
|
, Brick.txtWrap " means that the tool is installed but not in used"
|
||||||
|
]
|
||||||
|
, Brick.hBox [
|
||||||
|
Brick.txt "This symbol "
|
||||||
|
, Brick.withAttr setAttr (Brick.str setSign)
|
||||||
|
, Brick.txtWrap " means that the tool is installed and in used"
|
||||||
|
]
|
||||||
|
, Brick.hBox [
|
||||||
|
Brick.txt "This symbol "
|
||||||
|
, Brick.withAttr notInstalledAttr (Brick.str notInstalledSign)
|
||||||
|
, Brick.txt " means that the tool isn't installed"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, txt_separator
|
||||||
|
, mkTextBox [
|
||||||
|
Brick.hBox [
|
||||||
|
Brick.withAttr recommendedAttr $ Brick.str "recommended"
|
||||||
|
, Brick.txtWrap " tag is based on community adoption, known bugs, etc... So It makes this version the least experimental"
|
||||||
|
]
|
||||||
|
, Brick.hBox [
|
||||||
|
Brick.withAttr latestAttr $ Brick.str "latest"
|
||||||
|
, Brick.txtWrap " tag is for the latest distributed version of the tool"
|
||||||
|
]
|
||||||
|
, Brick.hBox [
|
||||||
|
Brick.withAttr latestAttr $ Brick.str "hls-powered"
|
||||||
|
, Brick.txt " denotes the compiler version supported by the currently set ("
|
||||||
|
, Brick.withAttr setAttr (Brick.str setSign)
|
||||||
|
, Brick.txt ") hls"
|
||||||
|
]
|
||||||
|
, Brick.txtWrap "base-X.Y.Z.W tag is the minimun version of the base package admited in such ghc version"
|
||||||
|
]
|
||||||
|
, Brick.txt " "
|
||||||
|
])
|
||||||
|
<=> Brick.padRight Brick.Max (Brick.txt "Press q to exit the tutorial")
|
||||||
|
|
||||||
drawnElements = flip V.imap es $ \i' e ->
|
drawKeyInfo :: KeyBindings -> Widget Name
|
||||||
let addSeparator w = case es !? (i' - 1) of
|
drawKeyInfo KeyBindings {..} =
|
||||||
Just e' | lTool e' /= lTool e ->
|
let
|
||||||
hBorder <=> w
|
mkTextBox = Brick.hLimitPercent 70 . Brick.vBox . fmap (Brick.padRight Brick.Max)
|
||||||
_ -> w
|
keyToWidget (KeyCombination key mods) = Brick.str $ intercalate "+" (showKey key : (showMod <$> mods))
|
||||||
|
in centerLayer
|
||||||
|
$ Brick.hLimitPercent 75
|
||||||
|
$ Brick.vLimitPercent 50
|
||||||
|
$ Brick.withBorderStyle unicode
|
||||||
|
$ borderWithLabel (Brick.txt "Key Actions")
|
||||||
|
$ Brick.vBox [
|
||||||
|
center $
|
||||||
|
mkTextBox [
|
||||||
|
Brick.hBox [
|
||||||
|
Brick.txt "Press "
|
||||||
|
, keyToWidget bUp, Brick.txt " and ", keyToWidget bDown
|
||||||
|
, Brick.txtWrap " to navigate the list of tools"
|
||||||
|
]
|
||||||
|
, Brick.hBox [
|
||||||
|
Brick.txt "Press "
|
||||||
|
, keyToWidget bInstall
|
||||||
|
, Brick.txtWrap " to install the selected tool. Notice, you may need to set it as default afterwards"
|
||||||
|
]
|
||||||
|
, Brick.hBox [
|
||||||
|
Brick.txt "Press "
|
||||||
|
, keyToWidget bSet
|
||||||
|
, Brick.txtWrap " to set a tool as the one for use"
|
||||||
|
]
|
||||||
|
, Brick.hBox [
|
||||||
|
Brick.txt "Press "
|
||||||
|
, keyToWidget bUninstall
|
||||||
|
, Brick.txtWrap " to uninstall a tool"
|
||||||
|
]
|
||||||
|
, Brick.hBox [
|
||||||
|
Brick.txt "Press "
|
||||||
|
, keyToWidget bChangelog
|
||||||
|
, Brick.txtWrap " to open the tool's changelog. It will open a web browser"
|
||||||
|
]
|
||||||
|
, Brick.hBox [
|
||||||
|
Brick.txt "Press "
|
||||||
|
, keyToWidget bShowAllVersions
|
||||||
|
, Brick.txtWrap " to show older version of each tool"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
<=> Brick.hBox [Brick.txt "Press q to return to Navigation" <+> Brick.padRight Brick.Max (Brick.txt " ") <+> Brick.txt "Press Enter to go to the Tutorial"]
|
||||||
|
|
||||||
isSelected = Just i' == listSelected
|
drawUI :: AttrMap -> BrickState -> [Widget Name]
|
||||||
elemWidget = drawElem i' isSelected e
|
drawUI dimAttrs st =
|
||||||
selItemAttr = if foc
|
let navg = drawNavigation dimAttrs st
|
||||||
then withDefAttr listSelectedFocusedAttr
|
in case st ^. mode of
|
||||||
else withDefAttr listSelectedAttr
|
Navigation -> [navg]
|
||||||
makeVisible' = if isSelected then visible . selItemAttr else id
|
Tutorial -> [drawTutorial, navg]
|
||||||
in addSeparator $ makeVisible' elemWidget
|
KeyInfo -> [drawKeyInfo (st ^. appKeys), navg]
|
||||||
|
|
||||||
in render
|
{- Attributes
|
||||||
$ viewport "GHCup" Vertical
|
|
||||||
$ vBox
|
|
||||||
$ V.toList drawnElements
|
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
minHSize :: Int -> Widget n -> Widget n
|
|
||||||
minHSize s' = hLimit s' . vLimit 1 . (<+> fill ' ')
|
|
||||||
|
|
||||||
|
|
||||||
app :: AttrMap -> AttrMap -> App BrickState e String
|
|
||||||
app attrs dimAttrs =
|
|
||||||
App { appDraw = \st -> [ui dimAttrs st]
|
|
||||||
, appHandleEvent = \be -> get >>= \s -> eventHandler s be
|
|
||||||
, appStartEvent = return ()
|
|
||||||
, appAttrMap = const attrs
|
|
||||||
, appChooseCursor = showFirstCursor
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultAttributes :: Bool -> AttrMap
|
defaultAttributes :: Bool -> AttrMap
|
||||||
defaultAttributes no_color = attrMap
|
defaultAttributes no_color = Brick.attrMap
|
||||||
Vty.defAttr
|
Vty.defAttr
|
||||||
[ (attrName "active" , Vty.defAttr `withBackColor` Vty.blue)
|
[ (L.listSelectedFocusedAttr , Vty.defAttr `withBackColor` Vty.blue)
|
||||||
, (attrName "not-installed" , Vty.defAttr `withForeColor` Vty.red)
|
, (L.listSelectedAttr , Vty.defAttr)
|
||||||
, (attrName "set" , Vty.defAttr `withForeColor` Vty.green)
|
, (notInstalledAttr , Vty.defAttr `withForeColor` Vty.red)
|
||||||
, (attrName "installed" , Vty.defAttr `withForeColor` Vty.green)
|
, (setAttr , Vty.defAttr `withForeColor` Vty.green)
|
||||||
, (attrName "recommended" , Vty.defAttr `withForeColor` Vty.green)
|
, (installedAttr , Vty.defAttr `withForeColor` Vty.green)
|
||||||
, (attrName "hls-powered" , Vty.defAttr `withForeColor` Vty.green)
|
, (recommendedAttr , Vty.defAttr `withForeColor` Vty.green)
|
||||||
, (attrName "latest" , Vty.defAttr `withForeColor` Vty.yellow)
|
, (hlsPoweredAttr , Vty.defAttr `withForeColor` Vty.green)
|
||||||
, (attrName "latest-prerelease" , Vty.defAttr `withForeColor` Vty.red)
|
, (latestAttr , Vty.defAttr `withForeColor` Vty.yellow)
|
||||||
, (attrName "latest-nightly" , Vty.defAttr `withForeColor` Vty.red)
|
, (latestPrereleaseAttr , Vty.defAttr `withForeColor` Vty.red)
|
||||||
, (attrName "prerelease" , Vty.defAttr `withForeColor` Vty.red)
|
, (latestNightlyAttr , Vty.defAttr `withForeColor` Vty.red)
|
||||||
, (attrName "nightly" , Vty.defAttr `withForeColor` Vty.red)
|
, (prereleaseAttr , Vty.defAttr `withForeColor` Vty.red)
|
||||||
, (attrName "compiled" , Vty.defAttr `withForeColor` Vty.blue)
|
, (nightlyAttr , Vty.defAttr `withForeColor` Vty.red)
|
||||||
, (attrName "stray" , Vty.defAttr `withForeColor` Vty.blue)
|
, (compiledAttr , Vty.defAttr `withForeColor` Vty.blue)
|
||||||
, (attrName "day" , Vty.defAttr `withForeColor` Vty.blue)
|
, (strayAttr , Vty.defAttr `withForeColor` Vty.blue)
|
||||||
, (attrName "help" , Vty.defAttr `withStyle` Vty.italic)
|
, (dayAttr , Vty.defAttr `withForeColor` Vty.blue)
|
||||||
, (attrName "hooray" , Vty.defAttr `withForeColor` Vty.brightWhite)
|
, (helpAttr , Vty.defAttr `withStyle` Vty.italic)
|
||||||
|
, (hoorayAttr , Vty.defAttr `withForeColor` Vty.brightWhite)
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
withForeColor | no_color = const
|
withForeColor | no_color = const
|
||||||
@ -323,58 +561,130 @@ defaultAttributes no_color = attrMap
|
|||||||
|
|
||||||
withStyle = Vty.withStyle
|
withStyle = Vty.withStyle
|
||||||
|
|
||||||
|
|
||||||
|
notInstalledAttr, setAttr, installedAttr, recommendedAttr, hlsPoweredAttr:: Brick.AttrName
|
||||||
|
latestAttr, latestPrereleaseAttr, latestNightlyAttr, prereleaseAttr, nightlyAttr:: Brick.AttrName
|
||||||
|
compiledAttr, strayAttr, dayAttr, helpAttr, hoorayAttr:: Brick.AttrName
|
||||||
|
|
||||||
|
notInstalledAttr = Brick.attrName "not-installed"
|
||||||
|
setAttr = Brick.attrName "set"
|
||||||
|
installedAttr = Brick.attrName "installed"
|
||||||
|
recommendedAttr = Brick.attrName "recommended"
|
||||||
|
hlsPoweredAttr = Brick.attrName "hls-powered"
|
||||||
|
latestAttr = Brick.attrName "latest"
|
||||||
|
latestPrereleaseAttr = Brick.attrName "latest-prerelease"
|
||||||
|
latestNightlyAttr = Brick.attrName "latest-nightly"
|
||||||
|
prereleaseAttr = Brick.attrName "prerelease"
|
||||||
|
nightlyAttr = Brick.attrName "nightly"
|
||||||
|
compiledAttr = Brick.attrName "compiled"
|
||||||
|
strayAttr = Brick.attrName "stray"
|
||||||
|
dayAttr = Brick.attrName "day"
|
||||||
|
helpAttr = Brick.attrName "help"
|
||||||
|
hoorayAttr = Brick.attrName "hooray"
|
||||||
|
|
||||||
dimAttributes :: Bool -> AttrMap
|
dimAttributes :: Bool -> AttrMap
|
||||||
dimAttributes no_color = attrMap
|
dimAttributes no_color = Brick.attrMap
|
||||||
(Vty.defAttr `Vty.withStyle` Vty.dim)
|
(Vty.defAttr `Vty.withStyle` Vty.dim)
|
||||||
[ (attrName "active" , Vty.defAttr `withBackColor` Vty.blue) -- has no effect ??
|
[ (Brick.attrName "active" , Vty.defAttr `withBackColor` Vty.blue) -- has no effect ??
|
||||||
, (attrName "no-bindist", Vty.defAttr `Vty.withStyle` Vty.dim)
|
, (Brick.attrName "no-bindist", Vty.defAttr `Vty.withStyle` Vty.dim)
|
||||||
]
|
]
|
||||||
where
|
where
|
||||||
withBackColor | no_color = \attr _ -> attr `Vty.withStyle` Vty.reverseVideo
|
withBackColor | no_color = \attr _ -> attr `Vty.withStyle` Vty.reverseVideo
|
||||||
| otherwise = Vty.withBackColor
|
| otherwise = Vty.withBackColor
|
||||||
|
|
||||||
eventHandler :: BrickState -> BrickEvent String e -> EventM String BrickState ()
|
{- Handlers
|
||||||
eventHandler st@BrickState{..} ev = do
|
|
||||||
|
-}
|
||||||
|
|
||||||
|
keyHandlers :: KeyBindings
|
||||||
|
-> [ ( KeyCombination
|
||||||
|
, BrickSettings -> String
|
||||||
|
, EventM Name BrickState ()
|
||||||
|
)
|
||||||
|
]
|
||||||
|
keyHandlers KeyBindings {..} =
|
||||||
|
[ (bQuit, const "Quit" , Brick.halt)
|
||||||
|
, (bInstall, const "Install" , withIOAction install')
|
||||||
|
, (bUninstall, const "Uninstall", withIOAction del')
|
||||||
|
, (bSet, const "Set" , withIOAction set')
|
||||||
|
, (bChangelog, const "ChangeLog", withIOAction changelog')
|
||||||
|
, ( bShowAllVersions
|
||||||
|
, \BrickSettings {..} ->
|
||||||
|
if _showAllVersions then "Don't show all versions" else "Show all versions"
|
||||||
|
, hideShowHandler' (not . _showAllVersions)
|
||||||
|
)
|
||||||
|
, (bUp, const "Up", Brick.zoom (toLensVL appState) moveUp)
|
||||||
|
, (bDown, const "Down", Brick.zoom (toLensVL appState) moveDown)
|
||||||
|
, (KeyCombination (Vty.KChar 'h') [], const "help", mode .= KeyInfo)
|
||||||
|
]
|
||||||
|
where
|
||||||
|
--hideShowHandler' :: (BrickSettings -> Bool) -> (BrickSettings -> Bool) -> m ()
|
||||||
|
hideShowHandler' f = do
|
||||||
|
app_settings <- use appSettings
|
||||||
|
let
|
||||||
|
vers = f app_settings
|
||||||
|
newAppSettings = app_settings & showAllVersions .~ vers
|
||||||
|
ad <- use appData
|
||||||
|
current_app_state <- use appState
|
||||||
|
appSettings .= newAppSettings
|
||||||
|
appState .= constructList ad newAppSettings (Just current_app_state)
|
||||||
|
|
||||||
|
|
||||||
|
tutorialHandler :: BrickEvent Name e -> EventM Name BrickState ()
|
||||||
|
tutorialHandler ev =
|
||||||
|
case ev of
|
||||||
|
VtyEvent (Vty.EvKey (Vty.KChar 'q') _ ) -> mode .= Navigation
|
||||||
|
_ -> pure ()
|
||||||
|
|
||||||
|
keyInfoHandler :: BrickEvent Name e -> EventM Name BrickState ()
|
||||||
|
keyInfoHandler ev = do
|
||||||
|
case ev of
|
||||||
|
VtyEvent (Vty.EvKey (Vty.KChar 'q') _ ) -> mode .= Navigation
|
||||||
|
VtyEvent (Vty.EvKey Vty.KEnter _ ) -> mode .= Tutorial
|
||||||
|
_ -> pure ()
|
||||||
|
|
||||||
|
navigationHandler :: BrickEvent Name e -> EventM Name BrickState ()
|
||||||
|
navigationHandler ev = do
|
||||||
AppState { keyBindings = kb } <- liftIO $ readIORef settings'
|
AppState { keyBindings = kb } <- liftIO $ readIORef settings'
|
||||||
case ev of
|
case ev of
|
||||||
(MouseDown _ Vty.BScrollUp _ _) ->
|
inner_event@(VtyEvent (Vty.EvKey key _)) ->
|
||||||
put (BrickState { appState = moveCursor 1 appState Up, .. })
|
case find (\(key', _, _) -> key' == KeyCombination key []) (keyHandlers kb) of
|
||||||
(MouseDown _ Vty.BScrollDown _ _) ->
|
Nothing -> void $ Brick.zoom (toLensVL appState) $ handleGenericListEvent inner_event
|
||||||
put (BrickState { appState = moveCursor 1 appState Down, .. })
|
Just (_, _, handler) -> handler
|
||||||
(VtyEvent (Vty.EvResize _ _)) -> put st
|
inner_event -> Brick.zoom (toLensVL appState) $ handleGenericListEvent inner_event
|
||||||
(VtyEvent (Vty.EvKey Vty.KUp [])) ->
|
|
||||||
put BrickState{ appState = moveCursor 1 appState Up, .. }
|
eventHandler :: BrickEvent Name e -> EventM Name BrickState ()
|
||||||
(VtyEvent (Vty.EvKey Vty.KDown [])) ->
|
eventHandler ev = do
|
||||||
put BrickState{ appState = moveCursor 1 appState Down, .. }
|
m <- use mode
|
||||||
(VtyEvent (Vty.EvKey key mods)) ->
|
case m of
|
||||||
case find (\(keyCombo, _, _) -> keyCombo == KeyCombination key mods) (keyHandlers kb) of
|
KeyInfo -> keyInfoHandler ev
|
||||||
Nothing -> put st
|
Tutorial -> tutorialHandler ev
|
||||||
Just (_, _, handler) -> handler st
|
Navigation -> navigationHandler ev
|
||||||
_ -> put st
|
|
||||||
|
|
||||||
|
|
||||||
moveCursor :: Int -> BrickInternalState -> Direction -> BrickInternalState
|
{- Core Logic.
|
||||||
moveCursor steps ais@BrickInternalState{..} direction =
|
|
||||||
let newIx = if direction == Down then ix + steps else ix - steps
|
|
||||||
in case clr !? newIx of
|
|
||||||
Just _ -> BrickInternalState { ix = newIx, .. }
|
|
||||||
Nothing -> ais
|
|
||||||
|
|
||||||
|
This section defines the IO actions we can execute within the Brick App:
|
||||||
|
- Install
|
||||||
|
- Set
|
||||||
|
- UnInstall
|
||||||
|
- Launch the Changelog
|
||||||
|
|
||||||
|
-}
|
||||||
|
|
||||||
-- | Suspend the current UI and run an IO action in terminal. If the
|
-- | Suspend the current UI and run an IO action in terminal. If the
|
||||||
-- IO action returns a Left value, then it's thrown as userError.
|
-- IO action returns a Left value, then it's thrown as userError.
|
||||||
withIOAction :: Ord n
|
withIOAction :: (Ord n, Eq n)
|
||||||
=> (BrickState
|
=> ( (Int, ListResult) -> ReaderT AppState IO (Either String a))
|
||||||
-> (Int, ListResult)
|
|
||||||
-> ReaderT AppState IO (Either String a))
|
|
||||||
-> BrickState
|
|
||||||
-> EventM n BrickState ()
|
-> EventM n BrickState ()
|
||||||
withIOAction action as = case listSelectedElement' (appState as) of
|
withIOAction action = do
|
||||||
Nothing -> put as
|
as <- Brick.get
|
||||||
Just (ix, e) -> do
|
case sectionListSelectedElement (view appState as) of
|
||||||
suspendAndResume $ do
|
Nothing -> pure ()
|
||||||
|
Just (curr_ix, e) -> do
|
||||||
|
Brick.suspendAndResume $ do
|
||||||
settings <- readIORef settings'
|
settings <- readIORef settings'
|
||||||
flip runReaderT settings $ action as (ix, e) >>= \case
|
flip runReaderT settings $ action (curr_ix, e) >>= \case
|
||||||
Left err -> liftIO $ putStrLn ("Error: " <> err)
|
Left err -> liftIO $ putStrLn ("Error: " <> err)
|
||||||
Right _ -> liftIO $ putStrLn "Success"
|
Right _ -> liftIO $ putStrLn "Success"
|
||||||
getAppData Nothing >>= \case
|
getAppData Nothing >>= \case
|
||||||
@ -390,28 +700,35 @@ withIOAction action as = case listSelectedElement' (appState as) of
|
|||||||
-- and @BrickSettings@.
|
-- and @BrickSettings@.
|
||||||
updateList :: BrickData -> BrickState -> BrickState
|
updateList :: BrickData -> BrickState -> BrickState
|
||||||
updateList appD BrickState{..} =
|
updateList appD BrickState{..} =
|
||||||
let newInternalState = constructList appD appSettings (Just appState)
|
let newInternalState = constructList appD _appSettings (Just _appState)
|
||||||
in BrickState { appState = newInternalState
|
in BrickState { _appState = newInternalState
|
||||||
, appData = appD
|
, _appData = appD
|
||||||
, appSettings = appSettings
|
, _appSettings = _appSettings
|
||||||
, appKeys = appKeys
|
, _appKeys = _appKeys
|
||||||
|
, _mode = Navigation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
constructList :: BrickData
|
constructList :: BrickData
|
||||||
-> BrickSettings
|
-> BrickSettings
|
||||||
-> Maybe BrickInternalState
|
-> Maybe BrickInternalState
|
||||||
-> BrickInternalState
|
-> BrickInternalState
|
||||||
constructList appD appSettings =
|
constructList appD settings =
|
||||||
replaceLR (filterVisible (showAllVersions appSettings))
|
replaceLR (filterVisible (_showAllVersions settings))
|
||||||
(lr appD)
|
(_lr appD)
|
||||||
|
|
||||||
listSelectedElement' :: BrickInternalState -> Maybe (Int, ListResult)
|
-- | Focus on the tool section and the predicate which matches. If no result matches, focus on index 0
|
||||||
listSelectedElement' BrickInternalState{..} = fmap (ix, ) $ clr !? ix
|
selectBy :: Tool -> (ListResult -> Bool) -> BrickInternalState -> BrickInternalState
|
||||||
|
selectBy tool predicate internal_state =
|
||||||
|
let new_focus = F.focusSetCurrent (Singular tool) (view sectionListFocusRingL internal_state)
|
||||||
|
tool_lens = sectionL (Singular tool)
|
||||||
|
in internal_state
|
||||||
|
& sectionListFocusRingL .~ new_focus
|
||||||
|
& tool_lens %~ L.listMoveTo 0 -- We move to 0 first
|
||||||
|
& tool_lens %~ L.listFindBy predicate -- The lookup by the predicate.
|
||||||
|
|
||||||
|
-- | Select the latests GHC tool
|
||||||
selectLatest :: Vector ListResult -> Int
|
selectLatest :: BrickInternalState -> BrickInternalState
|
||||||
selectLatest = fromMaybe 0 . V.findIndex (\ListResult {..} -> lTool == GHC && Latest `elem` lTag)
|
selectLatest = selectBy GHC (elem Latest . lTag)
|
||||||
|
|
||||||
|
|
||||||
-- | Replace the @appState@ or construct it based on a filter function
|
-- | Replace the @appState@ or construct it based on a filter function
|
||||||
@ -421,14 +738,13 @@ replaceLR :: (ListResult -> Bool)
|
|||||||
-> [ListResult]
|
-> [ListResult]
|
||||||
-> Maybe BrickInternalState
|
-> Maybe BrickInternalState
|
||||||
-> BrickInternalState
|
-> BrickInternalState
|
||||||
replaceLR filterF lr s =
|
replaceLR filterF list_result s =
|
||||||
let oldElem = s >>= listSelectedElement'
|
let oldElem = s >>= sectionListSelectedElement -- Maybe (Int, e)
|
||||||
newVec = V.fromList . filter filterF $ lr
|
newVec = [(Singular $ lTool (head g), V.fromList g) | g <- groupBy ((==) `on` lTool ) (filter filterF list_result)]
|
||||||
newSelected =
|
newSectionList = sectionList AllTools newVec 1
|
||||||
case oldElem >>= \(_, oldE) -> V.findIndex (toolEqual oldE) newVec of
|
in case oldElem of
|
||||||
Just ix -> ix
|
Just (_, el) -> selectBy (lTool el) (toolEqual el) newSectionList
|
||||||
Nothing -> selectLatest newVec
|
Nothing -> selectLatest newSectionList
|
||||||
in BrickInternalState newVec newSelected
|
|
||||||
where
|
where
|
||||||
toolEqual e1 e2 =
|
toolEqual e1 e2 =
|
||||||
lTool e1 == lTool e2 && lVer e1 == lVer e2 && lCross e1 == lCross e2
|
lTool e1 == lTool e2 && lVer e1 == lVer e2 && lCross e1 == lCross e2
|
||||||
@ -446,10 +762,9 @@ filterVisible v e | lInstalled e = True
|
|||||||
|
|
||||||
|
|
||||||
install' :: (MonadReader AppState m, MonadIO m, MonadThrow m, MonadFail m, MonadMask m, MonadUnliftIO m, Alternative m)
|
install' :: (MonadReader AppState m, MonadIO m, MonadThrow m, MonadFail m, MonadMask m, MonadUnliftIO m, Alternative m)
|
||||||
=> BrickState
|
=> (Int, ListResult)
|
||||||
-> (Int, ListResult)
|
|
||||||
-> m (Either String ())
|
-> m (Either String ())
|
||||||
install' _ (_, ListResult {..}) = do
|
install' (_, ListResult {..}) = do
|
||||||
AppState { ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- ask
|
AppState { ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- ask
|
||||||
|
|
||||||
let run =
|
let run =
|
||||||
@ -531,10 +846,9 @@ install' _ (_, ListResult {..}) = do
|
|||||||
|
|
||||||
|
|
||||||
set' :: (MonadReader AppState m, MonadIO m, MonadThrow m, MonadFail m, MonadMask m, MonadUnliftIO m, Alternative m)
|
set' :: (MonadReader AppState m, MonadIO m, MonadThrow m, MonadFail m, MonadMask m, MonadUnliftIO m, Alternative m)
|
||||||
=> BrickState
|
=> (Int, ListResult)
|
||||||
-> (Int, ListResult)
|
|
||||||
-> m (Either String ())
|
-> m (Either String ())
|
||||||
set' bs input@(_, ListResult {..}) = do
|
set' input@(_, ListResult {..}) = do
|
||||||
settings <- liftIO $ readIORef settings'
|
settings <- liftIO $ readIORef settings'
|
||||||
|
|
||||||
let run =
|
let run =
|
||||||
@ -589,12 +903,12 @@ set' bs input@(_, ListResult {..}) = do
|
|||||||
promptAnswer <- getUserPromptResponse userPrompt
|
promptAnswer <- getUserPromptResponse userPrompt
|
||||||
case promptAnswer of
|
case promptAnswer of
|
||||||
PromptYes -> do
|
PromptYes -> do
|
||||||
res <- install' bs input
|
res <- install' input
|
||||||
case res of
|
case res of
|
||||||
(Left err) -> pure $ Left err
|
(Left err) -> pure $ Left err
|
||||||
(Right _) -> do
|
(Right _) -> do
|
||||||
logInfo "Setting now..."
|
logInfo "Setting now..."
|
||||||
set' bs input
|
set' input
|
||||||
|
|
||||||
PromptNo -> pure $ Left (prettyHFError e)
|
PromptNo -> pure $ Left (prettyHFError e)
|
||||||
where
|
where
|
||||||
@ -609,10 +923,9 @@ set' bs input@(_, ListResult {..}) = do
|
|||||||
|
|
||||||
|
|
||||||
del' :: (MonadReader AppState m, MonadIO m, MonadFail m, MonadMask m, MonadUnliftIO m)
|
del' :: (MonadReader AppState m, MonadIO m, MonadFail m, MonadMask m, MonadUnliftIO m)
|
||||||
=> BrickState
|
=> (Int, ListResult)
|
||||||
-> (Int, ListResult)
|
|
||||||
-> m (Either String ())
|
-> m (Either String ())
|
||||||
del' _ (_, ListResult {..}) = do
|
del' (_, ListResult {..}) = do
|
||||||
AppState { ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- ask
|
AppState { ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- ask
|
||||||
|
|
||||||
let run = runE @'[NotInstalled, UninstallFailed]
|
let run = runE @'[NotInstalled, UninstallFailed]
|
||||||
@ -636,10 +949,9 @@ del' _ (_, ListResult {..}) = do
|
|||||||
|
|
||||||
|
|
||||||
changelog' :: (MonadReader AppState m, MonadIO m)
|
changelog' :: (MonadReader AppState m, MonadIO m)
|
||||||
=> BrickState
|
=> (Int, ListResult)
|
||||||
-> (Int, ListResult)
|
|
||||||
-> m (Either String ())
|
-> m (Either String ())
|
||||||
changelog' _ (_, ListResult {..}) = do
|
changelog' (_, ListResult {..}) = do
|
||||||
AppState { pfreq, ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- ask
|
AppState { pfreq, ghcupInfo = GHCupInfo { _ghcupDownloads = dls }} <- ask
|
||||||
case getChangeLog dls lTool (ToolVersion lVer) of
|
case getChangeLog dls lTool (ToolVersion lVer) of
|
||||||
Nothing -> pure $ Left $
|
Nothing -> pure $ Left $
|
||||||
@ -678,7 +990,6 @@ settings' = unsafePerformIO $ do
|
|||||||
loggerConfig
|
loggerConfig
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
brickMain :: AppState
|
brickMain :: AppState
|
||||||
-> IO ()
|
-> IO ()
|
||||||
brickMain s = do
|
brickMain s = do
|
||||||
@ -687,12 +998,13 @@ brickMain s = do
|
|||||||
eAppData <- getAppData (Just $ ghcupInfo s)
|
eAppData <- getAppData (Just $ ghcupInfo s)
|
||||||
case eAppData of
|
case eAppData of
|
||||||
Right ad ->
|
Right ad ->
|
||||||
defaultMain
|
Brick.defaultMain
|
||||||
(app (defaultAttributes (noColor $ settings s)) (dimAttributes (noColor $ settings s)))
|
(app (defaultAttributes (noColor $ settings s)) (dimAttributes (noColor $ settings s)))
|
||||||
(BrickState ad
|
(BrickState ad
|
||||||
defaultAppSettings
|
defaultAppSettings
|
||||||
(constructList ad defaultAppSettings Nothing)
|
(constructList ad defaultAppSettings Nothing)
|
||||||
(keyBindings (s :: AppState))
|
(keyBindings (s :: AppState))
|
||||||
|
Navigation
|
||||||
|
|
||||||
)
|
)
|
||||||
$> ()
|
$> ()
|
||||||
@ -702,7 +1014,7 @@ brickMain s = do
|
|||||||
|
|
||||||
|
|
||||||
defaultAppSettings :: BrickSettings
|
defaultAppSettings :: BrickSettings
|
||||||
defaultAppSettings = BrickSettings { showAllVersions = False }
|
defaultAppSettings = BrickSettings { _showAllVersions = False}
|
||||||
|
|
||||||
|
|
||||||
getGHCupInfo :: IO (Either String GHCupInfo)
|
getGHCupInfo :: IO (Either String GHCupInfo)
|
||||||
|
@ -327,6 +327,8 @@ executable ghcup
|
|||||||
, brick ^>=2.1
|
, brick ^>=2.1
|
||||||
, transformers ^>=0.5
|
, transformers ^>=0.5
|
||||||
, vty ^>=6.0
|
, vty ^>=6.0
|
||||||
|
, unix ^>=2.7
|
||||||
|
, optics ^>=0.4
|
||||||
|
|
||||||
if os(windows)
|
if os(windows)
|
||||||
cpp-options: -DIS_WINDOWS
|
cpp-options: -DIS_WINDOWS
|
||||||
|
Loading…
Reference in New Issue
Block a user