ui: solidified register screen, added themes
- register screen: - smarter width-sensitive layout, with multi-commodity amounts on one line - items are sorted in date order - jumps to the latest item by default, with consistent scroll position - more prerendering, might speed up movement/paging slightly - themes! --theme to select, --help to list (current themes: default, terminal, greenterm) - border tweaks - dropped side borders, added side padding
This commit is contained in:
parent
3a7a5d6035
commit
e7aa150e52
@ -5,6 +5,7 @@ Re-export the modules of the hledger-ui program.
|
|||||||
module Hledger.UI (
|
module Hledger.UI (
|
||||||
module Hledger.UI.Main,
|
module Hledger.UI.Main,
|
||||||
module Hledger.UI.Options,
|
module Hledger.UI.Options,
|
||||||
|
module Hledger.UI.Theme,
|
||||||
tests_Hledger_UI
|
tests_Hledger_UI
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
@ -12,6 +13,7 @@ import Test.HUnit
|
|||||||
|
|
||||||
import Hledger.UI.Main
|
import Hledger.UI.Main
|
||||||
import Hledger.UI.Options
|
import Hledger.UI.Options
|
||||||
|
import Hledger.UI.Theme
|
||||||
|
|
||||||
tests_Hledger_UI :: Test
|
tests_Hledger_UI :: Test
|
||||||
tests_Hledger_UI = TestList
|
tests_Hledger_UI = TestList
|
||||||
|
|||||||
@ -14,16 +14,17 @@ import Data.List
|
|||||||
-- import Data.Monoid --
|
-- import Data.Monoid --
|
||||||
import Data.Time.Calendar (Day)
|
import Data.Time.Calendar (Day)
|
||||||
import qualified Data.Vector as V
|
import qualified Data.Vector as V
|
||||||
import qualified Graphics.Vty as Vty
|
import Graphics.Vty as Vty
|
||||||
import Brick
|
import Brick
|
||||||
import Brick.Widgets.List
|
import Brick.Widgets.List
|
||||||
import Brick.Widgets.Border
|
-- import Brick.Widgets.Border
|
||||||
import Brick.Widgets.Center
|
-- import Brick.Widgets.Center
|
||||||
|
|
||||||
import Hledger
|
import Hledger
|
||||||
import Hledger.Cli hiding (progname,prognameandversion,green)
|
import Hledger.Cli hiding (progname,prognameandversion,green)
|
||||||
-- import Hledger.Cli.Options (defaultBalanceLineFormat)
|
-- import Hledger.Cli.Options (defaultBalanceLineFormat)
|
||||||
import Hledger.UI.Options
|
import Hledger.UI.Options
|
||||||
|
-- import Hledger.UI.Theme
|
||||||
import Hledger.UI.UITypes
|
import Hledger.UI.UITypes
|
||||||
import Hledger.UI.UIUtils
|
import Hledger.UI.UIUtils
|
||||||
import qualified Hledger.UI.RegisterScreen2 as RS2 (screen)
|
import qualified Hledger.UI.RegisterScreen2 as RS2 (screen)
|
||||||
@ -47,7 +48,10 @@ initAccountsScreen d args st@AppState{aopts=opts, ajournal=j, aScreen=s@Accounts
|
|||||||
--{query_=unwords' $ locArgs l}
|
--{query_=unwords' $ locArgs l}
|
||||||
ropts = (reportopts_ cliopts)
|
ropts = (reportopts_ cliopts)
|
||||||
{no_elide_=True}
|
{no_elide_=True}
|
||||||
{query_=unwords' args}
|
{
|
||||||
|
query_=unwords' args,
|
||||||
|
balancetype_=HistoricalBalance -- XXX balanceReport doesn't respect this yet
|
||||||
|
}
|
||||||
cliopts = cliopts_ opts
|
cliopts = cliopts_ opts
|
||||||
initAccountsScreen _ _ _ = error "init function called with wrong screen type, should not happen"
|
initAccountsScreen _ _ _ = error "init function called with wrong screen type, should not happen"
|
||||||
|
|
||||||
@ -59,15 +63,7 @@ drawAccountsScreen st@AppState{aScreen=AccountsScreen{asState=is}} = [ui]
|
|||||||
Nothing -> "-"
|
Nothing -> "-"
|
||||||
Just i -> show (i + 1))
|
Just i -> show (i + 1))
|
||||||
total = str $ show $ length $ is^.listElementsL
|
total = str $ show $ length $ is^.listElementsL
|
||||||
box = borderWithLabel label $
|
|
||||||
-- hLimit 25 $
|
|
||||||
-- vLimit 15 $
|
|
||||||
renderList is (drawAccountsItem fmt)
|
|
||||||
ui = box
|
|
||||||
_ui = vCenter $ vBox [ hCenter box
|
|
||||||
, str " "
|
|
||||||
, hCenter $ str "Press Esc to exit."
|
|
||||||
]
|
|
||||||
items = listElements is
|
items = listElements is
|
||||||
flat = flat_ $ reportopts_ $ cliopts_ $ aopts st
|
flat = flat_ $ reportopts_ $ cliopts_ $ aopts st
|
||||||
acctcolwidth = maximum $
|
acctcolwidth = maximum $
|
||||||
@ -82,16 +78,17 @@ drawAccountsScreen st@AppState{aScreen=AccountsScreen{asState=is}} = [ui]
|
|||||||
, FormatField False (Just 40) Nothing TotalField
|
, FormatField False (Just 40) Nothing TotalField
|
||||||
]
|
]
|
||||||
|
|
||||||
|
ui = defaultLayout label $ renderList is (drawAccountsItem fmt)
|
||||||
|
|
||||||
drawAccountsScreen _ = error "draw function called with wrong screen type, should not happen"
|
drawAccountsScreen _ = error "draw function called with wrong screen type, should not happen"
|
||||||
|
|
||||||
drawAccountsItem :: StringFormat -> Bool -> BalanceReportItem -> Widget
|
drawAccountsItem :: StringFormat -> Bool -> BalanceReportItem -> Widget
|
||||||
drawAccountsItem fmt sel item =
|
drawAccountsItem fmt _sel item =
|
||||||
let selStr i = if sel
|
Widget Greedy Fixed $ do
|
||||||
then withAttr customAttr (str $ showitem i)
|
-- c <- getContext
|
||||||
else str $ showitem i
|
let
|
||||||
showitem = intercalate "\n" . balanceReportItemAsText defreportopts fmt
|
showitem = intercalate "\n" . balanceReportItemAsText defreportopts fmt
|
||||||
in
|
render $ str $ showitem item
|
||||||
selStr item
|
|
||||||
|
|
||||||
handleAccountsScreen :: AppState -> Vty.Event -> EventM (Next AppState)
|
handleAccountsScreen :: AppState -> Vty.Event -> EventM (Next AppState)
|
||||||
handleAccountsScreen st@AppState{aScreen=scr@AccountsScreen{asState=is}} e = do
|
handleAccountsScreen st@AppState{aScreen=scr@AccountsScreen{asState=is}} e = do
|
||||||
@ -104,8 +101,9 @@ handleAccountsScreen st@AppState{aScreen=scr@AccountsScreen{asState=is}} e = do
|
|||||||
Vty.EvKey (Vty.KChar 'q') [] -> halt st
|
Vty.EvKey (Vty.KChar 'q') [] -> halt st
|
||||||
Vty.EvKey (Vty.KLeft) [] -> continue $ popScreen st
|
Vty.EvKey (Vty.KLeft) [] -> continue $ popScreen st
|
||||||
Vty.EvKey (Vty.KRight) [] -> do
|
Vty.EvKey (Vty.KRight) [] -> do
|
||||||
(w,h) <- getViewportSize "accounts"
|
let st' = screenEnter d args RS2.screen st
|
||||||
continue $ screenEnter d args RS2.screen{rs2Size=(w,h)} st
|
vScrollToBeginning $ viewportScroll "register"
|
||||||
|
continue st'
|
||||||
where
|
where
|
||||||
args = case listSelectedElement is of
|
args = case listSelectedElement is of
|
||||||
Just (_, ((acct, _, _), _)) -> ["acct:"++accountNameToAccountRegex acct]
|
Just (_, ((acct, _, _), _)) -> ["acct:"++accountNameToAccountRegex acct]
|
||||||
|
|||||||
@ -12,10 +12,11 @@ module Hledger.UI.Main where
|
|||||||
-- import Control.Applicative
|
-- import Control.Applicative
|
||||||
-- import Control.Lens ((^.))
|
-- import Control.Lens ((^.))
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
|
-- import Control.Monad.IO.Class (liftIO)
|
||||||
-- import Data.Default
|
-- import Data.Default
|
||||||
-- import Data.Monoid --
|
-- import Data.Monoid --
|
||||||
-- import Data.List
|
-- import Data.List
|
||||||
-- import Data.Maybe
|
import Data.Maybe
|
||||||
-- import Data.Time.Calendar
|
-- import Data.Time.Calendar
|
||||||
-- import Safe
|
-- import Safe
|
||||||
import System.Exit
|
import System.Exit
|
||||||
@ -27,19 +28,14 @@ import Hledger
|
|||||||
import Hledger.Cli hiding (progname,prognameandversion,green)
|
import Hledger.Cli hiding (progname,prognameandversion,green)
|
||||||
import Hledger.UI.Options
|
import Hledger.UI.Options
|
||||||
import Hledger.UI.UITypes
|
import Hledger.UI.UITypes
|
||||||
import Hledger.UI.UIUtils
|
-- import Hledger.UI.UIUtils
|
||||||
|
import Hledger.UI.Theme
|
||||||
import Hledger.UI.AccountsScreen as AS
|
import Hledger.UI.AccountsScreen as AS
|
||||||
-- import Hledger.UI.RegisterScreen as RS
|
-- import Hledger.UI.RegisterScreen as RS
|
||||||
import Hledger.UI.RegisterScreen2 as RS2
|
-- import Hledger.UI.RegisterScreen2 as RS2
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
-- | The available screens.
|
|
||||||
appScreens = [
|
|
||||||
AS.screen
|
|
||||||
,RS2.screen
|
|
||||||
]
|
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
opts <- getHledgerUIOpts
|
opts <- getHledgerUIOpts
|
||||||
@ -65,8 +61,10 @@ runBrickUi opts j = do
|
|||||||
d <- getCurrentDay
|
d <- getCurrentDay
|
||||||
|
|
||||||
let
|
let
|
||||||
|
theme = maybe defaultTheme (fromMaybe defaultTheme . getTheme) $
|
||||||
|
maybestringopt "theme" $ rawopts_ $ cliopts_ opts
|
||||||
args = words' $ query_ $ reportopts_ $ cliopts_ opts
|
args = words' $ query_ $ reportopts_ $ cliopts_ opts
|
||||||
scr = head appScreens
|
scr = AS.screen
|
||||||
st = (sInitFn scr) d args
|
st = (sInitFn scr) d args
|
||||||
AppState{
|
AppState{
|
||||||
aopts=opts
|
aopts=opts
|
||||||
@ -80,10 +78,11 @@ runBrickUi opts j = do
|
|||||||
app = App {
|
app = App {
|
||||||
appLiftVtyEvent = id
|
appLiftVtyEvent = id
|
||||||
, appStartEvent = return
|
, appStartEvent = return
|
||||||
, appAttrMap = const customAttrMap
|
, appAttrMap = const theme
|
||||||
, appChooseCursor = showFirstCursor
|
, appChooseCursor = showFirstCursor
|
||||||
, appHandleEvent = \st ev -> (sHandleFn $ aScreen st) st ev
|
, appHandleEvent = \st ev -> (sHandleFn $ aScreen st) st ev
|
||||||
, appDraw = \st -> (sDrawFn $ aScreen st) st
|
, appDraw = \st -> (sDrawFn $ aScreen st) st
|
||||||
}
|
}
|
||||||
|
|
||||||
void $ defaultMain app st
|
void $ defaultMain app st
|
||||||
|
|
||||||
|
|||||||
@ -5,10 +5,12 @@
|
|||||||
|
|
||||||
module Hledger.UI.Options
|
module Hledger.UI.Options
|
||||||
where
|
where
|
||||||
|
import Data.List (intercalate)
|
||||||
import System.Console.CmdArgs
|
import System.Console.CmdArgs
|
||||||
import System.Console.CmdArgs.Explicit
|
import System.Console.CmdArgs.Explicit
|
||||||
|
|
||||||
import Hledger.Cli hiding (progname,version,prognameandversion)
|
import Hledger.Cli hiding (progname,version,prognameandversion)
|
||||||
|
import Hledger.UI.Theme (themeNames)
|
||||||
|
|
||||||
progname, version :: String
|
progname, version :: String
|
||||||
progname = "hledger-ui"
|
progname = "hledger-ui"
|
||||||
@ -25,6 +27,7 @@ uiflags = [
|
|||||||
,flagNone ["flat"] (\opts -> setboolopt "flat" opts) "show full account names, unindented"
|
,flagNone ["flat"] (\opts -> setboolopt "flat" opts) "show full account names, unindented"
|
||||||
,flagReq ["drop"] (\s opts -> Right $ setopt "drop" s opts) "N" "with --flat, omit this many leading account name components"
|
,flagReq ["drop"] (\s opts -> Right $ setopt "drop" s opts) "N" "with --flat, omit this many leading account name components"
|
||||||
,flagReq ["format"] (\s opts -> Right $ setopt "format" s opts) "FORMATSTR" "use this custom line format"
|
,flagReq ["format"] (\s opts -> Right $ setopt "format" s opts) "FORMATSTR" "use this custom line format"
|
||||||
|
,flagReq ["theme"] (\s opts -> Right $ setopt "theme" s opts) "THEME" ("use this custom display theme ("++intercalate ", " themeNames++")")
|
||||||
,flagNone ["no-elide"] (\opts -> setboolopt "no-elide" opts) "no eliding at all, stronger than --empty"
|
,flagNone ["no-elide"] (\opts -> setboolopt "no-elide" opts) "no eliding at all, stronger than --empty"
|
||||||
-- ,flagNone ["no-total"] (\opts -> setboolopt "no-total" opts) "don't show the final total"
|
-- ,flagNone ["no-total"] (\opts -> setboolopt "no-total" opts) "don't show the final total"
|
||||||
]
|
]
|
||||||
@ -66,6 +69,10 @@ toUIOpts rawopts = do
|
|||||||
checkUIOpts :: UIOpts -> IO UIOpts
|
checkUIOpts :: UIOpts -> IO UIOpts
|
||||||
checkUIOpts opts = do
|
checkUIOpts opts = do
|
||||||
checkCliOpts $ cliopts_ opts
|
checkCliOpts $ cliopts_ opts
|
||||||
|
case maybestringopt "theme" $ rawopts_ $ cliopts_ opts of
|
||||||
|
Just t | not $ elem t themeNames ->
|
||||||
|
optserror $ "invalid theme name: "++t
|
||||||
|
_ -> return ()
|
||||||
return opts
|
return opts
|
||||||
|
|
||||||
getHledgerUIOpts :: IO UIOpts
|
getHledgerUIOpts :: IO UIOpts
|
||||||
|
|||||||
@ -7,6 +7,7 @@ module Hledger.UI.RegisterScreen
|
|||||||
where
|
where
|
||||||
|
|
||||||
import Control.Lens ((^.))
|
import Control.Lens ((^.))
|
||||||
|
-- import Control.Monad.IO.Class (liftIO)
|
||||||
import Data.List
|
import Data.List
|
||||||
import Data.Time.Calendar (Day)
|
import Data.Time.Calendar (Day)
|
||||||
import qualified Data.Vector as V
|
import qualified Data.Vector as V
|
||||||
@ -42,7 +43,7 @@ initRegisterScreen d args st@AppState{aopts=opts, ajournal=j, aScreen=s@Register
|
|||||||
-- query_="cur:\\$"} -- XXX limit to one commodity to ensure one-line items
|
-- query_="cur:\\$"} -- XXX limit to one commodity to ensure one-line items
|
||||||
--{query_=unwords' $ locArgs l}
|
--{query_=unwords' $ locArgs l}
|
||||||
ropts = (reportopts_ cliopts)
|
ropts = (reportopts_ cliopts)
|
||||||
{query_=unwords' args}
|
{ query_=unwords' args }
|
||||||
cliopts = cliopts_ opts
|
cliopts = cliopts_ opts
|
||||||
initRegisterScreen _ _ _ = error "init function called with wrong screen type, should not happen"
|
initRegisterScreen _ _ _ = error "init function called with wrong screen type, should not happen"
|
||||||
|
|
||||||
@ -72,12 +73,17 @@ drawRegisterScreen _ = error "draw function called with wrong screen type, shoul
|
|||||||
|
|
||||||
drawRegisterItem :: Bool -> PostingsReportItem -> Widget
|
drawRegisterItem :: Bool -> PostingsReportItem -> Widget
|
||||||
drawRegisterItem sel item =
|
drawRegisterItem sel item =
|
||||||
|
|
||||||
|
-- (w,_) <- getViewportSize "register" -- getCurrentViewportSize
|
||||||
|
-- st@AppState{aopts=opts} <- getAppState
|
||||||
|
-- let opts' = opts{width_=Just $ show w}
|
||||||
|
|
||||||
let selStr i = if sel
|
let selStr i = if sel
|
||||||
then withAttr customAttr (str $ showitem i)
|
then {- withAttr selectedAttr -} str $ showitem i
|
||||||
else str $ showitem i
|
else str $ showitem i
|
||||||
showitem (_,_,_,p,b) =
|
showitem (_,_,_,p,b) =
|
||||||
intercalate ", " $ map strip $ lines $
|
intercalate ", " $ map strip $ lines $
|
||||||
postingsReportItemAsText defcliopts $
|
postingsReportItemAsText defcliopts{width_=Just "160"} $ -- XXX
|
||||||
mkpostingsReportItem True True PrimaryDate Nothing p b
|
mkpostingsReportItem True True PrimaryDate Nothing p b
|
||||||
-- fmt = BottomAligned [
|
-- fmt = BottomAligned [
|
||||||
-- FormatField False (Just 20) Nothing TotalField
|
-- FormatField False (Just 20) Nothing TotalField
|
||||||
@ -89,7 +95,7 @@ drawRegisterItem sel item =
|
|||||||
selStr item
|
selStr item
|
||||||
|
|
||||||
handleRegisterScreen :: AppState -> Vty.Event -> EventM (Next AppState)
|
handleRegisterScreen :: AppState -> Vty.Event -> EventM (Next AppState)
|
||||||
handleRegisterScreen st@AppState{aScreen=s@RegisterScreen{rsState=is}} e =
|
handleRegisterScreen st@AppState{aopts=_opts,aScreen=s@RegisterScreen{rsState=is}} e = do
|
||||||
case e of
|
case e of
|
||||||
Vty.EvKey Vty.KEsc [] -> halt st
|
Vty.EvKey Vty.KEsc [] -> halt st
|
||||||
Vty.EvKey (Vty.KChar 'q') [] -> halt st
|
Vty.EvKey (Vty.KChar 'q') [] -> halt st
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
-- The register screen, showing account postings, like the CLI register command.
|
-- The register screen, showing account postings, like the CLI register command.
|
||||||
|
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings, FlexibleContexts #-}
|
||||||
|
|
||||||
module Hledger.UI.RegisterScreen2
|
module Hledger.UI.RegisterScreen2
|
||||||
(screen)
|
(screen)
|
||||||
@ -8,111 +8,147 @@ where
|
|||||||
|
|
||||||
import Control.Lens ((^.))
|
import Control.Lens ((^.))
|
||||||
-- import Control.Monad.IO.Class (liftIO)
|
-- import Control.Monad.IO.Class (liftIO)
|
||||||
import Data.List
|
-- import Data.List
|
||||||
import Data.List.Split (splitOn)
|
import Data.List.Split (splitOn)
|
||||||
|
-- import Data.Maybe
|
||||||
import Data.Time.Calendar (Day)
|
import Data.Time.Calendar (Day)
|
||||||
import qualified Data.Vector as V
|
import qualified Data.Vector as V
|
||||||
import qualified Graphics.Vty as Vty
|
import Graphics.Vty as Vty
|
||||||
import Brick
|
import Brick
|
||||||
import Brick.Widgets.List
|
import Brick.Widgets.List
|
||||||
import Brick.Widgets.Border
|
-- import Brick.Widgets.Border
|
||||||
import Brick.Widgets.Center
|
-- import Brick.Widgets.Border.Style
|
||||||
|
-- import Brick.Widgets.Center
|
||||||
|
-- import Text.Printf
|
||||||
|
|
||||||
import Hledger
|
import Hledger
|
||||||
import Hledger.Cli hiding (progname,prognameandversion,green)
|
import Hledger.Cli hiding (progname,prognameandversion,green)
|
||||||
import Hledger.UI.Options
|
import Hledger.UI.Options
|
||||||
|
-- import Hledger.UI.Theme
|
||||||
import Hledger.UI.UITypes
|
import Hledger.UI.UITypes
|
||||||
import Hledger.UI.UIUtils
|
import Hledger.UI.UIUtils
|
||||||
|
|
||||||
screen = RegisterScreen2{
|
screen = RegisterScreen2{
|
||||||
rs2State = list "register" V.empty 1
|
rs2State = list "register" V.empty 1
|
||||||
,rs2Size = (0,0)
|
|
||||||
,sInitFn = initRegisterScreen2
|
,sInitFn = initRegisterScreen2
|
||||||
,sDrawFn = drawRegisterScreen2
|
,sDrawFn = drawRegisterScreen2
|
||||||
,sHandleFn = handleRegisterScreen2
|
,sHandleFn = handleRegisterScreen2
|
||||||
}
|
}
|
||||||
|
|
||||||
initRegisterScreen2 :: Day -> [String] -> AppState -> AppState
|
initRegisterScreen2 :: Day -> [String] -> AppState -> AppState
|
||||||
initRegisterScreen2 d args st@AppState{aopts=opts, ajournal=j, aScreen=s@RegisterScreen2{rs2Size=size}} =
|
initRegisterScreen2 d args st@AppState{aopts=opts, ajournal=j, aScreen=s@RegisterScreen2{}} =
|
||||||
st{aScreen=s{rs2State=is'}}
|
st{aScreen=s{rs2State=l}}
|
||||||
where
|
where
|
||||||
is' =
|
-- gather arguments and queries
|
||||||
-- listMoveTo (length items) $
|
ropts = (reportopts_ $ cliopts_ opts)
|
||||||
list (Name "register") (V.fromList items') 1
|
{
|
||||||
|
query_=unwords' args,
|
||||||
|
balancetype_=HistoricalBalance
|
||||||
|
}
|
||||||
|
-- XXX temp
|
||||||
|
curacct = drop 5 $ head args -- should be "acct:..."
|
||||||
|
thisacctq = Acct $ curacct -- XXX why is this excluding subs: accountNameToAccountRegex curacct
|
||||||
|
q = queryFromOpts d ropts
|
||||||
|
-- query_="cur:\\$"} -- XXX limit to one commodity to ensure one-line items
|
||||||
|
--{query_=unwords' $ locArgs l}
|
||||||
|
|
||||||
-- XXX temporary hack: include saved viewport size in list elements
|
-- run a transactions report, most recent last
|
||||||
-- for element draw function
|
(_label,items') = accountTransactionsReport ropts j thisacctq q
|
||||||
items' = zip (repeat size) items
|
items = reverse items'
|
||||||
(_label,items) = accountTransactionsReport ropts j thisacctq q
|
|
||||||
where
|
-- pre-render all items; these will be the List elements. This helps calculate column widths.
|
||||||
-- XXX temp
|
displayitem (_, t, _issplit, otheracctsstr, change, bal) =
|
||||||
curacct = drop 5 $ head args -- should be "acct:..."
|
(showDate $ tdate t
|
||||||
thisacctq = Acct $ curacct -- XXX why is this excluding subs: accountNameToAccountRegex curacct
|
,tdescription t
|
||||||
|
,case splitOn ", " otheracctsstr of
|
||||||
|
[s] -> s
|
||||||
|
_ -> "<split>"
|
||||||
|
,showMixedAmountOneLineWithoutPrice change
|
||||||
|
,showMixedAmountOneLineWithoutPrice bal
|
||||||
|
)
|
||||||
|
displayitems = map displayitem items
|
||||||
|
|
||||||
|
-- build the List, moving the selection to the end
|
||||||
|
l = listMoveTo (length items) $
|
||||||
|
list (Name "register") (V.fromList displayitems) 1
|
||||||
|
|
||||||
|
-- (listName someList)
|
||||||
|
|
||||||
q = queryFromOpts d ropts
|
|
||||||
-- query_="cur:\\$"} -- XXX limit to one commodity to ensure one-line items
|
|
||||||
--{query_=unwords' $ locArgs l}
|
|
||||||
ropts = (reportopts_ cliopts)
|
|
||||||
{query_=unwords' args}
|
|
||||||
cliopts = cliopts_ opts
|
|
||||||
initRegisterScreen2 _ _ _ = error "init function called with wrong screen type, should not happen"
|
initRegisterScreen2 _ _ _ = error "init function called with wrong screen type, should not happen"
|
||||||
|
|
||||||
drawRegisterScreen2 :: AppState -> [Widget]
|
drawRegisterScreen2 :: AppState -> [Widget]
|
||||||
drawRegisterScreen2 AppState{aopts=_opts, aScreen=RegisterScreen2{rs2State=is}} = [ui]
|
drawRegisterScreen2 AppState{aopts=_opts, aScreen=RegisterScreen2{rs2State=l}} = [ui]
|
||||||
where
|
where
|
||||||
label = str "Transaction "
|
label = str "Transaction "
|
||||||
<+> cur
|
<+> cur
|
||||||
<+> str " of "
|
<+> str " of "
|
||||||
<+> total
|
<+> total
|
||||||
<+> str " to/from this account" -- " <+> str query <+> "and subaccounts"
|
<+> str " to/from this account" -- " <+> str query <+> "and subaccounts"
|
||||||
cur = str $ case is^.(listSelectedL) of
|
cur = str $ case l^.listSelectedL of
|
||||||
Nothing -> "-"
|
Nothing -> "-"
|
||||||
Just i -> show (i + 1)
|
Just i -> show (i + 1)
|
||||||
total = str $ show $ length $ is^.(listElementsL)
|
total = str $ show $ length displayitems
|
||||||
|
displayitems = V.toList $ l^.listElementsL
|
||||||
|
|
||||||
-- query = query_ $ reportopts_ $ cliopts_ opts
|
-- query = query_ $ reportopts_ $ cliopts_ opts
|
||||||
box = borderWithLabel label $
|
|
||||||
-- hLimit 25 $
|
ui = Widget Greedy Greedy $ do
|
||||||
-- vLimit 15 $
|
|
||||||
renderList is drawRegisterItem
|
-- calculate column widths, based on current available width
|
||||||
ui = box
|
c <- getContext
|
||||||
_ui = vCenter $ vBox [ hCenter box
|
let
|
||||||
, str " "
|
totalwidth = c^.availWidthL - 2 -- XXX trimmed.. for the margin ?
|
||||||
, hCenter $ str "Press Esc to exit."
|
|
||||||
]
|
-- the date column is fixed width
|
||||||
|
datewidth = 10
|
||||||
|
|
||||||
|
-- multi-commodity amounts rendered on one line can be
|
||||||
|
-- arbitrarily wide. Give the two amounts as much space as
|
||||||
|
-- they need, while reserving a minimum of space for other
|
||||||
|
-- columns and whitespace. If they don't get all they need,
|
||||||
|
-- allocate it to them proportionally to their maximum widths.
|
||||||
|
maxamtswidth = max 0 (totalwidth - 21)
|
||||||
|
changewidth' = maximum' $ map (length . fourth5) displayitems
|
||||||
|
balwidth' = maximum' $ map (length . fifth5) displayitems
|
||||||
|
changewidthproportion = (changewidth' + balwidth') `div` changewidth'
|
||||||
|
maxchangewidth = maxamtswidth `div` changewidthproportion
|
||||||
|
maxbalwidth = maxamtswidth - maxchangewidth
|
||||||
|
changewidth = min maxchangewidth changewidth'
|
||||||
|
balwidth = min maxbalwidth balwidth'
|
||||||
|
|
||||||
|
-- assign the remaining space to the description and accounts columns
|
||||||
|
maxdescacctswidth = totalwidth - 17 - changewidth - balwidth
|
||||||
|
-- allocating proportionally.
|
||||||
|
-- descwidth' = maximum' $ map (length . second5) displayitems
|
||||||
|
-- acctswidth' = maximum' $ map (length . third5) displayitems
|
||||||
|
-- descwidthproportion = (descwidth' + acctswidth') `div` descwidth'
|
||||||
|
-- maxdescwidth = min (maxdescacctswidth - 7) (maxdescacctswidth `div` descwidthproportion)
|
||||||
|
-- maxacctswidth = maxdescacctswidth - maxdescwidth
|
||||||
|
-- descwidth = min maxdescwidth descwidth'
|
||||||
|
-- acctswidth = min maxacctswidth acctswidth'
|
||||||
|
-- allocating equally.
|
||||||
|
descwidth = maxdescacctswidth `div` 2
|
||||||
|
acctswidth = maxdescacctswidth - descwidth
|
||||||
|
|
||||||
|
colwidths = (datewidth,descwidth,acctswidth,changewidth,balwidth)
|
||||||
|
|
||||||
|
render $ defaultLayout label $ renderList l (drawRegisterItem colwidths)
|
||||||
|
|
||||||
drawRegisterScreen2 _ = error "draw function called with wrong screen type, should not happen"
|
drawRegisterScreen2 _ = error "draw function called with wrong screen type, should not happen"
|
||||||
|
|
||||||
drawRegisterItem :: Bool -> ((Int,Int), AccountTransactionsReportItem) -> Widget
|
drawRegisterItem :: (Int,Int,Int,Int,Int) -> Bool -> (String,String,String,String,String) -> Widget
|
||||||
drawRegisterItem sel ((w,_h),item) =
|
drawRegisterItem (datewidth,descwidth,acctswidth,changewidth,balwidth) _sel (date,desc,accts,change,bal) =
|
||||||
|
Widget Greedy Fixed $ do
|
||||||
-- (w,_) <- getViewportSize "register" -- getCurrentViewportSize
|
render $
|
||||||
-- st@AppState{aopts=opts} <- getAppState
|
str (padright datewidth $ elideRight datewidth date) <+>
|
||||||
-- let opts' = opts{width_=Just $ show w}
|
str " " <+>
|
||||||
|
str (padright descwidth $ elideRight descwidth desc) <+>
|
||||||
let selStr i = if sel
|
str " " <+>
|
||||||
then withAttr customAttr (str $ showitem i)
|
str (padright acctswidth $ elideLeft acctswidth $ accts) <+>
|
||||||
else str $ showitem i
|
str " " <+>
|
||||||
showitem (_origt,t,split,acctsstr,postedamt,totalamt) =
|
str (padleft changewidth $ elideLeft changewidth change) <+>
|
||||||
-- make a fake posting to render
|
str " " <+>
|
||||||
let p = nullposting{
|
str (padleft balwidth $ elideLeft balwidth bal)
|
||||||
pdate=Just $ tdate t
|
|
||||||
,paccount=if split then intercalate ", " acctnames ++" (split)" else acctsstr
|
|
||||||
-- XXX elideAccountName doesn't elide combined split names well
|
|
||||||
,pamount=postedamt
|
|
||||||
,ptransaction=Just t
|
|
||||||
}
|
|
||||||
acctnames = nub $ sort $ splitOn ", " acctsstr -- XXX
|
|
||||||
in
|
|
||||||
intercalate ", " $ map strip $ lines $
|
|
||||||
postingsReportItemAsText defcliopts{width_=Just (show w)} $
|
|
||||||
mkpostingsReportItem True True PrimaryDate Nothing p totalamt
|
|
||||||
-- fmt = BottomAligned [
|
|
||||||
-- FormatField False (Just 20) Nothing TotalField
|
|
||||||
-- , FormatLiteral " "
|
|
||||||
-- , FormatField True (Just 2) Nothing DepthSpacerField
|
|
||||||
-- , FormatField True Nothing Nothing AccountField
|
|
||||||
-- ]
|
|
||||||
in
|
|
||||||
selStr item
|
|
||||||
|
|
||||||
handleRegisterScreen2 :: AppState -> Vty.Event -> EventM (Next AppState)
|
handleRegisterScreen2 :: AppState -> Vty.Event -> EventM (Next AppState)
|
||||||
handleRegisterScreen2 st@AppState{aopts=_opts,aScreen=s@RegisterScreen2{rs2State=is}} e = do
|
handleRegisterScreen2 st@AppState{aopts=_opts,aScreen=s@RegisterScreen2{rs2State=is}} e = do
|
||||||
|
|||||||
@ -1,34 +1,99 @@
|
|||||||
----------------------------------------------------------------------
|
-- | The all-important theming engine!
|
||||||
-- Theme
|
--
|
||||||
-- the all-important theming engine!
|
-- Cf
|
||||||
|
-- https://hackage.haskell.org/package/vty/docs/Graphics-Vty-Attributes.html
|
||||||
|
-- http://hackage.haskell.org/package/brick/docs/Brick-AttrMap.html
|
||||||
|
-- http://hackage.haskell.org/package/brick-0.1/docs/Brick-Util.html
|
||||||
|
-- http://hackage.haskell.org/package/brick-0.1/docs/Brick-Widgets-Core.html#g:5
|
||||||
|
-- http://hackage.haskell.org/package/brick-0.1/docs/Brick-Widgets-Border.html
|
||||||
|
|
||||||
-- theme = Restrained
|
|
||||||
-- -- theme = Colorful
|
|
||||||
-- -- theme = Blood
|
|
||||||
|
|
||||||
-- data UITheme = Restrained | Colorful | Blood
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
-- (defaultattr,
|
module Hledger.UI.Theme (
|
||||||
-- currentlineattr,
|
defaultTheme
|
||||||
-- statusattr
|
,getTheme
|
||||||
-- ) = case theme of
|
,themes
|
||||||
-- Restrained -> (defAttr
|
,themeNames
|
||||||
-- ,defAttr `withStyle` bold
|
) where
|
||||||
-- ,defAttr `withStyle` reverseVideo
|
|
||||||
-- )
|
|
||||||
-- Colorful -> (defAttr `withStyle` reverseVideo
|
|
||||||
-- ,defAttr `withForeColor` white `withBackColor` red
|
|
||||||
-- ,defAttr `withForeColor` black `withBackColor` green
|
|
||||||
-- )
|
|
||||||
-- Blood -> (defAttr `withStyle` reverseVideo
|
|
||||||
-- ,defAttr `withForeColor` white `withBackColor` red
|
|
||||||
-- ,defAttr `withStyle` reverseVideo
|
|
||||||
-- )
|
|
||||||
|
|
||||||
-- -- halfbrightattr = defAttr `withStyle` dim
|
import qualified Data.Map as M
|
||||||
-- -- reverseattr = defAttr `withStyle` reverseVideo
|
import Data.Maybe
|
||||||
-- -- redattr = defAttr `withForeColor` red
|
import Data.Monoid
|
||||||
-- -- greenattr = defAttr `withForeColor` green
|
import Graphics.Vty
|
||||||
-- -- reverseredattr = defAttr `withStyle` reverseVideo `withForeColor` red
|
import Brick
|
||||||
-- -- reversegreenattr= defAttr `withStyle` reverseVideo `withForeColor` green
|
import Brick.Widgets.Border
|
||||||
|
import Brick.Widgets.List
|
||||||
|
|
||||||
|
defaultTheme :: AttrMap
|
||||||
|
defaultTheme = fromMaybe (snd $ head themesList) $ getTheme "white"
|
||||||
|
-- the theme named here should exist;
|
||||||
|
-- otherwise it will take the first one from the list,
|
||||||
|
-- which must be non-empty.
|
||||||
|
|
||||||
|
-- | Look up the named theme, if it exists.
|
||||||
|
getTheme :: String -> Maybe AttrMap
|
||||||
|
getTheme name = M.lookup name themes
|
||||||
|
|
||||||
|
-- | A selection of named themes specifying terminal colours and styles.
|
||||||
|
-- One of these is active at a time.
|
||||||
|
--
|
||||||
|
-- A hledger-ui theme is a vty/brick AttrMap. Each theme specifies a
|
||||||
|
-- default style (Attr), plus extra styles which are applied when
|
||||||
|
-- their (hierarchical) name matches the widget rendering context. Eg
|
||||||
|
-- when rendering a widget named "b" which is inside a widget named
|
||||||
|
-- "a", the following styles will be applied if they exist: the
|
||||||
|
-- default style, then a style named "a", and finally a style named
|
||||||
|
-- "a" <> "b".
|
||||||
|
--
|
||||||
|
themes :: M.Map String AttrMap
|
||||||
|
themes = M.fromList themesList
|
||||||
|
|
||||||
|
themeNames :: [String]
|
||||||
|
themeNames = map fst themesList
|
||||||
|
|
||||||
|
(&) = withStyle
|
||||||
|
|
||||||
|
themesList :: [(String, AttrMap)]
|
||||||
|
themesList = [
|
||||||
|
("default", attrMap
|
||||||
|
(black `on` white & bold) [ -- default style for this theme
|
||||||
|
(borderAttr , white `on` black),
|
||||||
|
-- ("normal" , black `on` white),
|
||||||
|
("list" , black `on` white), -- regular list items
|
||||||
|
("list" <> "selected" , white `on` blue & bold) -- selected list items
|
||||||
|
-- ("list" <> "selected" , black `on` brightYellow),
|
||||||
|
-- ("list" <> "accounts" , white `on` brightGreen),
|
||||||
|
-- ("list" <> "amount" , black `on` white & bold)
|
||||||
|
]),
|
||||||
|
|
||||||
|
("terminal", attrMap
|
||||||
|
defAttr [ -- use the current terminal's default style
|
||||||
|
(borderAttr , white `on` black),
|
||||||
|
-- ("normal" , defAttr),
|
||||||
|
(listAttr , defAttr),
|
||||||
|
(listSelectedAttr , defAttr & reverseVideo & bold)
|
||||||
|
-- ("status" , defAttr & reverseVideo)
|
||||||
|
]),
|
||||||
|
|
||||||
|
("greenterm", attrMap
|
||||||
|
(green `on` black) [
|
||||||
|
-- (listAttr , green `on` black),
|
||||||
|
(listSelectedAttr , black `on` green & bold)
|
||||||
|
])
|
||||||
|
-- ("colorful", attrMap
|
||||||
|
-- defAttr [
|
||||||
|
-- (listAttr , defAttr & reverseVideo),
|
||||||
|
-- (listSelectedAttr , defAttr `withForeColor` white `withBackColor` red)
|
||||||
|
-- -- ("status" , defAttr `withForeColor` black `withBackColor` green)
|
||||||
|
-- ])
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
-- halfbrightattr = defAttr & dim
|
||||||
|
-- reverseattr = defAttr & reverseVideo
|
||||||
|
-- redattr = defAttr `withForeColor` red
|
||||||
|
-- greenattr = defAttr `withForeColor` green
|
||||||
|
-- reverseredattr = defAttr & reverseVideo `withForeColor` red
|
||||||
|
-- reversegreenattr= defAttr & reverseVideo `withForeColor` green
|
||||||
|
|
||||||
|
|||||||
@ -37,8 +37,7 @@ data Screen =
|
|||||||
,sDrawFn :: AppState -> [Widget]
|
,sDrawFn :: AppState -> [Widget]
|
||||||
}
|
}
|
||||||
| RegisterScreen2 {
|
| RegisterScreen2 {
|
||||||
rs2Size :: (Int,Int) -- ^ XXX prev screen's viewport size on entering this screen
|
rs2State :: List (String,String,String,String,String)
|
||||||
,rs2State :: List ((Int,Int), AccountTransactionsReportItem)
|
|
||||||
,sInitFn :: Day -> [String] -> AppState -> AppState
|
,sInitFn :: Day -> [String] -> AppState -> AppState
|
||||||
,sHandleFn :: AppState -> V.Event -> EventM (Next AppState)
|
,sHandleFn :: AppState -> V.Event -> EventM (Next AppState)
|
||||||
,sDrawFn :: AppState -> [Widget]
|
,sDrawFn :: AppState -> [Widget]
|
||||||
|
|||||||
@ -4,23 +4,27 @@ module Hledger.UI.UIUtils (
|
|||||||
pushScreen
|
pushScreen
|
||||||
,popScreen
|
,popScreen
|
||||||
,screenEnter
|
,screenEnter
|
||||||
,attrMap
|
|
||||||
,customAttrMap
|
|
||||||
,customAttr
|
|
||||||
,getViewportSize
|
,getViewportSize
|
||||||
|
,margin
|
||||||
|
,withBorderAttr
|
||||||
|
,topBottomBorderWithLabel
|
||||||
|
,defaultLayout
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Lens ((^.))
|
import Control.Lens ((^.))
|
||||||
-- import Control.Monad
|
-- import Control.Monad
|
||||||
import Control.Monad.IO.Class
|
-- import Control.Monad.IO.Class
|
||||||
-- import Data.Default
|
-- import Data.Default
|
||||||
import Data.Monoid --
|
-- import Data.Monoid --
|
||||||
import Data.Time.Calendar (Day)
|
import Data.Time.Calendar (Day)
|
||||||
import qualified Graphics.Vty as V
|
|
||||||
import Brick
|
import Brick
|
||||||
import Brick.Widgets.List
|
-- import Brick.Widgets.List
|
||||||
|
import Brick.Widgets.Border
|
||||||
|
import Brick.Widgets.Border.Style
|
||||||
|
import Graphics.Vty as Vty
|
||||||
|
|
||||||
import Hledger.UI.UITypes
|
import Hledger.UI.UITypes
|
||||||
|
import Hledger.Utils (applyN)
|
||||||
|
|
||||||
pushScreen :: Screen -> AppState -> AppState
|
pushScreen :: Screen -> AppState -> AppState
|
||||||
pushScreen scr st = st{aPrevScreens=(aScreen st:aPrevScreens st)
|
pushScreen scr st = st{aPrevScreens=(aScreen st:aPrevScreens st)
|
||||||
@ -43,15 +47,6 @@ screenEnter d args scr st = (sInitFn scr) d args $
|
|||||||
pushScreen scr
|
pushScreen scr
|
||||||
st
|
st
|
||||||
|
|
||||||
customAttrMap :: AttrMap
|
|
||||||
customAttrMap = attrMap V.defAttr
|
|
||||||
[ (listAttr, V.white `on` V.blue)
|
|
||||||
, (listSelectedAttr, V.black `on` V.white)
|
|
||||||
-- , (customAttr, fg V.cyan)
|
|
||||||
]
|
|
||||||
|
|
||||||
customAttr :: AttrName
|
|
||||||
|
|
||||||
-- | In the EventM monad, get the named current viewport's width and height,
|
-- | In the EventM monad, get the named current viewport's width and height,
|
||||||
-- or (0,0) if the named viewport is not found.
|
-- or (0,0) if the named viewport is not found.
|
||||||
getViewportSize :: Name -> EventM (Int,Int)
|
getViewportSize :: Name -> EventM (Int,Int)
|
||||||
@ -60,6 +55,55 @@ getViewportSize name = do
|
|||||||
let (w,h) = case mvp of
|
let (w,h) = case mvp of
|
||||||
Just vp -> vp ^. vpSize
|
Just vp -> vp ^. vpSize
|
||||||
Nothing -> (0,0)
|
Nothing -> (0,0)
|
||||||
|
-- liftIO $ putStrLn $ show (w,h)
|
||||||
return (w,h)
|
return (w,h)
|
||||||
|
|
||||||
customAttr = listSelectedAttr <> "custom"
|
defaultLayout label =
|
||||||
|
topBottomBorderWithLabel label .
|
||||||
|
margin 1 0 Nothing
|
||||||
|
-- margin 1 0 (Just white)
|
||||||
|
|
||||||
|
topBottomBorderWithLabel label = \wrapped ->
|
||||||
|
Widget Greedy Greedy $ do
|
||||||
|
c <- getContext
|
||||||
|
let (_w,h) = (c^.availWidthL, c^.availHeightL)
|
||||||
|
h' = h - 2
|
||||||
|
wrapped' = vLimit (h') wrapped
|
||||||
|
debugmsg =
|
||||||
|
""
|
||||||
|
-- " debug: "++show (_w,h')
|
||||||
|
render $
|
||||||
|
hBorderWithLabel (label <+> str debugmsg)
|
||||||
|
<=>
|
||||||
|
wrapped'
|
||||||
|
<=>
|
||||||
|
hBorder
|
||||||
|
|
||||||
|
-- | Wrap a widget in a margin with the given horizontal and vertical
|
||||||
|
-- thickness, using the current background colour or the specified
|
||||||
|
-- colour. XXX May disrupt border style of inner widgets.
|
||||||
|
margin :: Int -> Int -> Maybe Color -> Widget -> Widget
|
||||||
|
margin h v mcolour = \w ->
|
||||||
|
Widget Greedy Greedy $ do
|
||||||
|
c <- getContext
|
||||||
|
let w' = vLimit (c^.availHeightL - v*2) $ hLimit (c^.availWidthL - h*2) w
|
||||||
|
attr = maybe currentAttr (\c -> c `on` c) mcolour
|
||||||
|
render $
|
||||||
|
withBorderAttr attr $
|
||||||
|
withBorderStyle (borderStyleFromChar ' ') $
|
||||||
|
applyN v (hBorder <=>) $
|
||||||
|
applyN h (vBorder <+>) $
|
||||||
|
applyN v (<=> hBorder) $
|
||||||
|
applyN h (<+> vBorder) $
|
||||||
|
w'
|
||||||
|
|
||||||
|
-- withBorderAttr attr .
|
||||||
|
-- withBorderStyle (borderStyleFromChar ' ') .
|
||||||
|
-- applyN n border
|
||||||
|
|
||||||
|
withBorderAttr attr = updateAttrMap (applyAttrMappings [(borderAttr, attr)])
|
||||||
|
|
||||||
|
-- _ui = vCenter $ vBox [ hCenter box
|
||||||
|
-- , str " "
|
||||||
|
-- , hCenter $ str "Press Esc to exit."
|
||||||
|
-- ]
|
||||||
|
|||||||
@ -8,17 +8,31 @@ hledger-ui currently allows browsing the balance, register and print
|
|||||||
reports, with drill-down and scrolling.
|
reports, with drill-down and scrolling.
|
||||||
|
|
||||||
|
|
||||||
Backlog:
|
# HACKING
|
||||||
show journal entries
|
|
||||||
|
## Backlog:
|
||||||
|
```
|
||||||
|
merge to master
|
||||||
|
brick release
|
||||||
|
|
||||||
|
make it more useful
|
||||||
|
register
|
||||||
|
simplify/remove unhelpful account names ?
|
||||||
|
show journal entries
|
||||||
|
transaction dialog / journal screen
|
||||||
|
bs/is/cf-ish reports
|
||||||
|
save custom reports
|
||||||
|
|
||||||
fix -H
|
fix -H
|
||||||
fix --drop
|
fix --drop
|
||||||
track current account better
|
track current account better
|
||||||
|
show it in register title
|
||||||
track current query better
|
track current query better
|
||||||
search
|
search in page
|
||||||
filter
|
adjust query
|
||||||
depth adjustment
|
adjust depth
|
||||||
|
use color, selectable themes
|
||||||
switch to next brick release
|
switch to next brick release
|
||||||
reg: use full width
|
|
||||||
reg2: find subaccounts' transactions better
|
reg2: find subaccounts' transactions better
|
||||||
keep cursor at bottom of screen if jumping to end
|
keep cursor at bottom of screen if jumping to end
|
||||||
add
|
add
|
||||||
@ -28,3 +42,5 @@ reload
|
|||||||
on screen change
|
on screen change
|
||||||
on redraw
|
on redraw
|
||||||
on file change
|
on file change
|
||||||
|
|
||||||
|
```
|
||||||
@ -56,6 +56,7 @@ executable hledger-ui
|
|||||||
, base >= 3 && < 5
|
, base >= 3 && < 5
|
||||||
, brick
|
, brick
|
||||||
, cmdargs >= 0.8
|
, cmdargs >= 0.8
|
||||||
|
, containers
|
||||||
, data-default
|
, data-default
|
||||||
, HUnit
|
, HUnit
|
||||||
, lens >= 4.12.3 && < 4.13
|
, lens >= 4.12.3 && < 4.13
|
||||||
@ -74,6 +75,7 @@ executable hledger-ui
|
|||||||
Hledger.UI
|
Hledger.UI
|
||||||
Hledger.UI.Main
|
Hledger.UI.Main
|
||||||
Hledger.UI.Options
|
Hledger.UI.Options
|
||||||
|
Hledger.UI.Theme
|
||||||
Hledger.UI.UITypes
|
Hledger.UI.UITypes
|
||||||
Hledger.UI.UIUtils
|
Hledger.UI.UIUtils
|
||||||
Hledger.UI.AccountsScreen
|
Hledger.UI.AccountsScreen
|
||||||
|
|||||||
@ -66,6 +66,7 @@ executables:
|
|||||||
- hledger-lib == 0.26.98
|
- hledger-lib == 0.26.98
|
||||||
- base >= 3 && < 5
|
- base >= 3 && < 5
|
||||||
- cmdargs >= 0.8
|
- cmdargs >= 0.8
|
||||||
|
- containers
|
||||||
- HUnit
|
- HUnit
|
||||||
- safe >= 0.2
|
- safe >= 0.2
|
||||||
- split >= 0.1 && < 0.3
|
- split >= 0.1 && < 0.3
|
||||||
|
|||||||
@ -113,6 +113,10 @@ tests_postingsReportAsText = [
|
|||||||
--
|
--
|
||||||
-- date and description are shown for the first posting of a transaction only.
|
-- date and description are shown for the first posting of a transaction only.
|
||||||
--
|
--
|
||||||
|
-- Returns a string which can be multi-line, eg if the running balance
|
||||||
|
-- has multiple commodities. Does not yet support formatting control
|
||||||
|
-- like balance reports.
|
||||||
|
--
|
||||||
postingsReportItemAsText :: CliOpts -> PostingsReportItem -> String
|
postingsReportItemAsText :: CliOpts -> PostingsReportItem -> String
|
||||||
postingsReportItemAsText opts (mdate, menddate, mdesc, p, b) =
|
postingsReportItemAsText opts (mdate, menddate, mdesc, p, b) =
|
||||||
intercalate "\n" $
|
intercalate "\n" $
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user