mirror of
https://github.com/simonmichael/hledger.git
synced 2024-11-10 14:16:41 +03:00
174 lines
7.0 KiB
Haskell
174 lines
7.0 KiB
Haskell
{- | UIState operations. -}
|
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE RecordWildCards #-}
|
|
|
|
module Hledger.UI.UIState
|
|
where
|
|
|
|
import Brick
|
|
import Brick.Widgets.Edit
|
|
import Data.List
|
|
import Data.Text.Zipper (gotoEOL)
|
|
import Data.Time.Calendar (Day)
|
|
|
|
import Hledger
|
|
import Hledger.Cli.CliOptions
|
|
import Hledger.UI.UITypes
|
|
import Hledger.UI.UIOptions
|
|
|
|
-- | Toggle between showing only cleared items or all items.
|
|
toggleCleared :: UIState -> UIState
|
|
toggleCleared ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =
|
|
ui{aopts=uopts{cliopts_=copts{reportopts_=toggleCleared ropts}}}
|
|
where
|
|
toggleCleared ropts = ropts{cleared_=not $ cleared_ ropts, uncleared_=False, pending_=False}
|
|
|
|
-- | Toggle between showing only pending items or all items.
|
|
togglePending :: UIState -> UIState
|
|
togglePending ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =
|
|
ui{aopts=uopts{cliopts_=copts{reportopts_=togglePending ropts}}}
|
|
where
|
|
togglePending ropts = ropts{pending_=not $ pending_ ropts, uncleared_=False, cleared_=False}
|
|
|
|
-- | Toggle between showing only uncleared items or all items.
|
|
toggleUncleared :: UIState -> UIState
|
|
toggleUncleared ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =
|
|
ui{aopts=uopts{cliopts_=copts{reportopts_=toggleUncleared ropts}}}
|
|
where
|
|
toggleUncleared ropts = ropts{uncleared_=not $ uncleared_ ropts, cleared_=False, pending_=False}
|
|
|
|
-- | Toggle between showing all and showing only nonempty (more precisely, nonzero) items.
|
|
toggleEmpty :: UIState -> UIState
|
|
toggleEmpty ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =
|
|
ui{aopts=uopts{cliopts_=copts{reportopts_=toggleEmpty ropts}}}
|
|
where
|
|
toggleEmpty ropts = ropts{empty_=not $ empty_ ropts}
|
|
|
|
-- | Toggle between flat and tree mode. If in the third "default" mode, go to flat mode.
|
|
toggleFlat :: UIState -> UIState
|
|
toggleFlat ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =
|
|
ui{aopts=uopts{cliopts_=copts{reportopts_=toggleFlatMode ropts}}}
|
|
where
|
|
toggleFlatMode ropts@ReportOpts{accountlistmode_=ALFlat} = ropts{accountlistmode_=ALTree}
|
|
toggleFlatMode ropts = ropts{accountlistmode_=ALFlat}
|
|
|
|
-- | Toggle between showing all and showing only real (non-virtual) items.
|
|
toggleReal :: UIState -> UIState
|
|
toggleReal ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =
|
|
ui{aopts=uopts{cliopts_=copts{reportopts_=toggleReal ropts}}}
|
|
where
|
|
toggleReal ropts = ropts{real_=not $ real_ ropts}
|
|
|
|
-- | Apply a new filter query.
|
|
setFilter :: String -> UIState -> UIState
|
|
setFilter s ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =
|
|
ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{query_=s}}}}
|
|
|
|
-- | Clear all filters/flags.
|
|
resetFilter :: UIState -> UIState
|
|
resetFilter ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =
|
|
ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{
|
|
accountlistmode_=ALTree
|
|
,empty_=True
|
|
,cleared_=False
|
|
,pending_=False
|
|
,uncleared_=False
|
|
,real_=False
|
|
,query_=""
|
|
}}}}
|
|
|
|
resetDepth :: UIState -> UIState
|
|
resetDepth ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =
|
|
ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{depth_=Nothing}}}}
|
|
|
|
-- | Get the maximum account depth in the current journal.
|
|
maxDepth :: UIState -> Int
|
|
maxDepth UIState{ajournal=j} = maximum $ map accountNameLevel $ journalAccountNames j
|
|
|
|
-- | Decrement the current depth limit towards 0. If there was no depth limit,
|
|
-- set it to one less than the maximum account depth.
|
|
decDepth :: UIState -> UIState
|
|
decDepth ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts@ReportOpts{..}}}}
|
|
= ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{depth_=dec depth_}}}}
|
|
where
|
|
dec (Just d) = Just $ max 0 (d-1)
|
|
dec Nothing = Just $ maxDepth ui - 1
|
|
|
|
-- | Increment the current depth limit. If this makes it equal to the
|
|
-- the maximum account depth, remove the depth limit.
|
|
incDepth :: UIState -> UIState
|
|
incDepth ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts@ReportOpts{..}}}}
|
|
= ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{depth_=inc depth_}}}}
|
|
where
|
|
inc (Just d) | d < (maxDepth ui - 1) = Just $ d+1
|
|
inc _ = Nothing
|
|
|
|
-- | Set the current depth limit to the specified depth, or remove the depth limit.
|
|
-- Also remove the depth limit if the specified depth is greater than the current
|
|
-- maximum account depth. If the specified depth is negative, reset the depth limit
|
|
-- to whatever was specified at uiartup.
|
|
setDepth :: Maybe Int -> UIState -> UIState
|
|
setDepth mdepth ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}}
|
|
= ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{depth_=mdepth'}}}}
|
|
where
|
|
mdepth' = case mdepth of
|
|
Nothing -> Nothing
|
|
Just d | d < 0 -> depth_ ropts
|
|
| d >= maxDepth ui -> Nothing
|
|
| otherwise -> mdepth
|
|
|
|
-- | Open the minibuffer, setting its content to the current query with the cursor at the end.
|
|
showMinibuffer :: UIState -> UIState
|
|
showMinibuffer ui = setMode (Minibuffer e) ui
|
|
where
|
|
e = applyEdit gotoEOL $ editor "minibuffer" (str . unlines) (Just 1) oldq
|
|
oldq = query_ $ reportopts_ $ cliopts_ $ aopts ui
|
|
|
|
-- | Close the minibuffer, discarding any edit in progress.
|
|
closeMinibuffer :: UIState -> UIState
|
|
closeMinibuffer = setMode Normal
|
|
|
|
setMode :: Mode -> UIState -> UIState
|
|
setMode m ui = ui{aMode=m}
|
|
|
|
-- | Regenerate the content for the current and previous screens, from a new journal and current date.
|
|
regenerateScreens :: Journal -> Day -> UIState -> UIState
|
|
regenerateScreens j d ui@UIState{aScreen=s,aPrevScreens=ss} =
|
|
-- XXX clumsy due to entanglement of UIState and Screen.
|
|
-- sInit operates only on an appstate's current screen, so
|
|
-- remove all the screens from the appstate and then add them back
|
|
-- one at a time, regenerating as we go.
|
|
let
|
|
first:rest = reverse $ s:ss :: [Screen]
|
|
ui0 = ui{ajournal=j, aScreen=first, aPrevScreens=[]} :: UIState
|
|
|
|
ui1 = (sInit first) d False ui0 :: UIState
|
|
ui2 = foldl' (\ui s -> (sInit s) d False $ pushScreen s ui) ui1 rest :: UIState
|
|
in
|
|
ui2
|
|
|
|
pushScreen :: Screen -> UIState -> UIState
|
|
pushScreen scr ui = ui{aPrevScreens=(aScreen ui:aPrevScreens ui)
|
|
,aScreen=scr
|
|
}
|
|
|
|
popScreen :: UIState -> UIState
|
|
popScreen ui@UIState{aPrevScreens=s:ss} = ui{aScreen=s, aPrevScreens=ss}
|
|
popScreen ui = ui
|
|
|
|
resetScreens :: Day -> UIState -> UIState
|
|
resetScreens d ui@UIState{aScreen=s,aPrevScreens=ss} =
|
|
(sInit topscreen) d True $ resetDepth $ resetFilter $ closeMinibuffer ui{aScreen=topscreen, aPrevScreens=[]}
|
|
where
|
|
topscreen = case ss of _:_ -> last ss
|
|
[] -> s
|
|
|
|
-- | Enter a new screen, saving the old screen & state in the
|
|
-- navigation history and initialising the new screen's state.
|
|
screenEnter :: Day -> Screen -> UIState -> UIState
|
|
screenEnter d scr ui = (sInit scr) d True $
|
|
pushScreen scr
|
|
ui
|
|
|