2015-10-30 04:05:02 +03:00
|
|
|
-- The transaction screen, showing a single transaction's general journal entry.
|
2015-10-30 03:45:57 +03:00
|
|
|
|
2016-06-08 22:15:58 +03:00
|
|
|
{-# LANGUAGE OverloadedStrings, TupleSections, RecordWildCards #-} -- , FlexibleContexts
|
2018-03-25 01:51:56 +03:00
|
|
|
{-# LANGUAGE CPP #-}
|
2015-10-30 03:45:57 +03:00
|
|
|
|
|
|
|
module Hledger.UI.TransactionScreen
|
2016-06-08 21:03:49 +03:00
|
|
|
(transactionScreen
|
2016-06-09 09:45:26 +03:00
|
|
|
,rsSelect
|
2015-10-30 03:45:57 +03:00
|
|
|
)
|
|
|
|
where
|
|
|
|
|
2016-06-19 19:00:04 +03:00
|
|
|
import Control.Monad
|
2015-10-30 03:45:57 +03:00
|
|
|
import Control.Monad.IO.Class (liftIO)
|
2016-06-04 04:00:38 +03:00
|
|
|
import Data.List
|
2018-03-25 01:51:56 +03:00
|
|
|
#if !(MIN_VERSION_base(4,11,0))
|
2015-10-30 03:45:57 +03:00
|
|
|
import Data.Monoid
|
2018-03-25 01:51:56 +03:00
|
|
|
#endif
|
lib: textification begins! account names
The first of several conversions from String to (strict) Text, hopefully
reducing space and time usage.
This one shows a small improvement, with GHC 7.10.3 and text-1.2.2.1:
hledger -f data/100x100x10.journal stats
string: <<ghc: 39471064 bytes, 77 GCs, 198421/275048 avg/max bytes residency (3 samples), 2M in use, 0.000 INIT (0.001 elapsed), 0.015 MUT (0.020 elapsed), 0.010 GC (0.014 elapsed) :ghc>>
text: <<ghc: 39268024 bytes, 77 GCs, 197018/270840 avg/max bytes residency (3 samples), 2M in use, 0.000 INIT (0.002 elapsed), 0.016 MUT (0.022 elapsed), 0.009 GC (0.011 elapsed) :ghc>>
hledger -f data/1000x100x10.journal stats
string: <<ghc: 318555920 bytes, 617 GCs, 2178997/7134472 avg/max bytes residency (7 samples), 16M in use, 0.000 INIT (0.001 elapsed), 0.129 MUT (0.136 elapsed), 0.067 GC (0.077 elapsed) :ghc>>
text: <<ghc: 314248496 bytes, 612 GCs, 2074045/6617960 avg/max bytes residency (7 samples), 16M in use, 0.000 INIT (0.003 elapsed), 0.137 MUT (0.145 elapsed), 0.067 GC (0.079 elapsed) :ghc>>
hledger -f data/10000x100x10.journal stats
string: <<ghc: 3114763608 bytes, 6026 GCs, 18858950/75552024 avg/max bytes residency (11 samples), 201M in use, 0.000 INIT (0.000 elapsed), 1.331 MUT (1.372 elapsed), 0.699 GC (0.812 elapsed) :ghc>>
text: <<ghc: 3071468920 bytes, 5968 GCs, 14120344/62951360 avg/max bytes residency (9 samples), 124M in use, 0.000 INIT (0.003 elapsed), 1.272 MUT (1.349 elapsed), 0.513 GC (0.578 elapsed) :ghc>>
hledger -f data/100000x100x10.journal stats
string: <<ghc: 31186579432 bytes, 60278 GCs, 135332581/740228992 avg/max bytes residency (13 samples), 1697M in use, 0.000 INIT (0.008 elapsed), 14.677 MUT (15.508 elapsed), 7.081 GC (8.074 elapsed) :ghc>>
text: <<ghc: 30753427672 bytes, 59763 GCs, 117595958/666457240 avg/max bytes residency (14 samples), 1588M in use, 0.000 INIT (0.008 elapsed), 13.713 MUT (13.966 elapsed), 6.220 GC (7.108 elapsed) :ghc>>
2016-05-24 04:16:21 +03:00
|
|
|
import qualified Data.Text as T
|
2015-10-30 03:45:57 +03:00
|
|
|
import Data.Time.Calendar (Day)
|
2019-01-19 03:27:42 +03:00
|
|
|
import Graphics.Vty (Event(..),Key(..),Modifier(..))
|
2015-10-30 03:45:57 +03:00
|
|
|
import Brick
|
2015-10-30 06:34:31 +03:00
|
|
|
import Brick.Widgets.List (listMoveTo)
|
2015-10-30 03:45:57 +03:00
|
|
|
|
|
|
|
import Hledger
|
2017-09-04 18:06:25 +03:00
|
|
|
import Hledger.Cli hiding (progname,prognameandversion)
|
2015-10-30 03:45:57 +03:00
|
|
|
import Hledger.UI.UIOptions
|
|
|
|
-- import Hledger.UI.Theme
|
|
|
|
import Hledger.UI.UITypes
|
2016-06-11 03:30:45 +03:00
|
|
|
import Hledger.UI.UIState
|
2015-10-30 03:45:57 +03:00
|
|
|
import Hledger.UI.UIUtils
|
2016-06-19 19:00:04 +03:00
|
|
|
import Hledger.UI.Editor
|
2016-06-08 21:03:49 +03:00
|
|
|
import Hledger.UI.ErrorScreen
|
2015-10-30 03:45:57 +03:00
|
|
|
|
2016-06-08 21:03:49 +03:00
|
|
|
transactionScreen :: Screen
|
|
|
|
transactionScreen = TransactionScreen{
|
2016-06-09 09:45:26 +03:00
|
|
|
sInit = tsInit
|
|
|
|
,sDraw = tsDraw
|
|
|
|
,sHandle = tsHandle
|
|
|
|
,tsTransaction = (1,nulltransaction)
|
|
|
|
,tsTransactions = [(1,nulltransaction)]
|
|
|
|
,tsAccount = ""
|
2015-10-30 03:45:57 +03:00
|
|
|
}
|
|
|
|
|
2016-06-11 03:30:45 +03:00
|
|
|
tsInit :: Day -> Bool -> UIState -> UIState
|
|
|
|
tsInit _d _reset ui@UIState{aopts=UIOpts{cliopts_=CliOpts{reportopts_=_ropts}}
|
2016-06-08 22:15:58 +03:00
|
|
|
,ajournal=_j
|
2016-06-11 03:30:45 +03:00
|
|
|
,aScreen=TransactionScreen{..}} = ui
|
2016-06-09 09:45:26 +03:00
|
|
|
tsInit _ _ _ = error "init function called with wrong screen type, should not happen"
|
2015-10-30 03:45:57 +03:00
|
|
|
|
2016-07-25 04:06:49 +03:00
|
|
|
tsDraw :: UIState -> [Widget Name]
|
2016-07-07 01:04:34 +03:00
|
|
|
tsDraw UIState{aopts=UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}
|
2019-01-15 17:15:39 +03:00
|
|
|
,aScreen=TransactionScreen{tsTransaction=(i,t)
|
|
|
|
,tsTransactions=nts
|
|
|
|
,tsAccount=acct
|
|
|
|
}
|
|
|
|
,aMode=mode
|
|
|
|
} =
|
2016-06-10 21:50:57 +03:00
|
|
|
case mode of
|
2017-06-19 02:11:18 +03:00
|
|
|
Help -> [helpDialog copts, maincontent]
|
2016-06-10 21:50:57 +03:00
|
|
|
-- Minibuffer e -> [minibuffer e, maincontent]
|
|
|
|
_ -> [maincontent]
|
2015-10-30 03:45:57 +03:00
|
|
|
where
|
2016-06-10 21:50:57 +03:00
|
|
|
maincontent = Widget Greedy Greedy $ do
|
2016-06-02 17:03:00 +03:00
|
|
|
render $ defaultLayout toplabel bottomlabel $ str $
|
2019-11-16 14:46:21 +03:00
|
|
|
showTransactionOneLineAmounts $
|
2016-06-02 17:03:00 +03:00
|
|
|
-- (if real_ ropts then filterTransactionPostings (Real True) else id) -- filter postings by --real
|
|
|
|
t
|
2016-06-10 21:50:57 +03:00
|
|
|
where
|
2016-08-02 17:09:08 +03:00
|
|
|
toplabel =
|
|
|
|
str "Transaction "
|
|
|
|
-- <+> withAttr ("border" <> "bold") (str $ "#" ++ show (tindex t))
|
|
|
|
-- <+> str (" ("++show i++" of "++show (length nts)++" in "++acct++")")
|
|
|
|
<+> (str $ "#" ++ show (tindex t))
|
|
|
|
<+> str " ("
|
|
|
|
<+> withAttr ("border" <> "bold") (str $ show i)
|
|
|
|
<+> str (" of "++show (length nts))
|
|
|
|
<+> togglefilters
|
|
|
|
<+> borderQueryStr (query_ ropts)
|
|
|
|
<+> str (" in "++T.unpack (replaceHiddenAccountsNameWith "All" acct)++")")
|
2018-10-23 16:43:21 +03:00
|
|
|
<+> (if ignore_assertions_ $ inputopts_ copts then withAttr ("border" <> "query") (str " ignoring balance assertions") else str "")
|
2016-08-02 17:09:08 +03:00
|
|
|
where
|
|
|
|
togglefilters =
|
|
|
|
case concat [
|
2017-06-19 02:11:18 +03:00
|
|
|
uiShowStatus copts $ statuses_ ropts
|
2016-08-02 17:09:08 +03:00
|
|
|
,if real_ ropts then ["real"] else []
|
|
|
|
,if empty_ ropts then [] else ["nonzero"]
|
|
|
|
] of
|
|
|
|
[] -> str ""
|
2018-10-23 16:43:21 +03:00
|
|
|
fs -> withAttr ("border" <> "query") (str $ " " ++ intercalate ", " fs)
|
2016-08-02 17:09:08 +03:00
|
|
|
|
2016-06-10 21:50:57 +03:00
|
|
|
bottomlabel = case mode of
|
|
|
|
-- Minibuffer ed -> minibuffer ed
|
|
|
|
_ -> quickhelp
|
2016-08-02 17:09:08 +03:00
|
|
|
where
|
|
|
|
quickhelp = borderKeysStr [
|
|
|
|
("?", "help")
|
2018-10-23 15:43:57 +03:00
|
|
|
,("LEFT", "back")
|
|
|
|
,("UP/DOWN", "prev/next")
|
2016-08-02 17:09:08 +03:00
|
|
|
--,("ESC", "cancel/top")
|
|
|
|
-- ,("a", "add")
|
|
|
|
,("E", "editor")
|
|
|
|
,("g", "reload")
|
|
|
|
,("q", "quit")
|
|
|
|
]
|
2015-10-30 03:45:57 +03:00
|
|
|
|
2016-06-09 09:45:26 +03:00
|
|
|
tsDraw _ = error "draw function called with wrong screen type, should not happen"
|
2015-10-30 03:45:57 +03:00
|
|
|
|
2016-11-24 20:10:50 +03:00
|
|
|
tsHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)
|
2016-06-11 03:30:45 +03:00
|
|
|
tsHandle ui@UIState{aScreen=s@TransactionScreen{tsTransaction=(i,t)
|
2019-01-15 17:15:39 +03:00
|
|
|
,tsTransactions=nts
|
|
|
|
,tsAccount=acct
|
|
|
|
}
|
|
|
|
,aopts=UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}
|
|
|
|
,ajournal=j
|
|
|
|
,aMode=mode
|
|
|
|
}
|
2016-06-10 21:50:57 +03:00
|
|
|
ev =
|
|
|
|
case mode of
|
|
|
|
Help ->
|
|
|
|
case ev of
|
2016-10-31 18:06:29 +03:00
|
|
|
VtyEvent (EvKey (KChar 'q') []) -> halt ui
|
2019-01-19 03:27:42 +03:00
|
|
|
VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui
|
2019-01-24 04:25:20 +03:00
|
|
|
VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
|
2016-06-11 03:30:45 +03:00
|
|
|
_ -> helpHandle ui ev
|
2016-06-10 21:50:57 +03:00
|
|
|
|
|
|
|
_ -> do
|
2015-10-30 03:45:57 +03:00
|
|
|
d <- liftIO getCurrentDay
|
2016-06-10 21:50:57 +03:00
|
|
|
let
|
|
|
|
(iprev,tprev) = maybe (i,t) ((i-1),) $ lookup (i-1) nts
|
|
|
|
(inext,tnext) = maybe (i,t) ((i+1),) $ lookup (i+1) nts
|
|
|
|
case ev of
|
2016-10-31 18:06:29 +03:00
|
|
|
VtyEvent (EvKey (KChar 'q') []) -> halt ui
|
|
|
|
VtyEvent (EvKey KEsc []) -> continue $ resetScreens d ui
|
|
|
|
VtyEvent (EvKey (KChar c) []) | c `elem` ['?'] -> continue $ setMode Help ui
|
|
|
|
VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor pos f) >> uiReloadJournalIfChanged copts d j ui
|
2016-06-20 00:52:55 +03:00
|
|
|
where
|
2017-02-05 23:00:45 +03:00
|
|
|
(pos,f) = case tsourcepos t of
|
|
|
|
GenericSourcePos f l c -> (Just (l, Just c),f)
|
2019-07-15 13:28:52 +03:00
|
|
|
JournalSourcePos f (l1,_) -> (Just (l1, Nothing),f)
|
2016-12-08 02:07:59 +03:00
|
|
|
AppEvent (DateChange old _) | isStandardPeriod p && p `periodContainsDate` old ->
|
2016-12-03 02:36:23 +03:00
|
|
|
continue $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui
|
2016-12-08 02:07:59 +03:00
|
|
|
where
|
|
|
|
p = reportPeriod ui
|
2016-11-24 22:03:32 +03:00
|
|
|
e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] -> do
|
2016-06-10 21:50:57 +03:00
|
|
|
d <- liftIO getCurrentDay
|
2016-11-25 07:23:14 +03:00
|
|
|
ej <- liftIO $ journalReload copts
|
2016-06-10 21:50:57 +03:00
|
|
|
case ej of
|
2016-06-11 03:30:45 +03:00
|
|
|
Left err -> continue $ screenEnter d errorScreen{esError=err} ui
|
2016-06-10 21:50:57 +03:00
|
|
|
Right j' -> do
|
|
|
|
-- got to redo the register screen's transactions report, to get the latest transactions list for this screen
|
|
|
|
-- XXX duplicates rsInit
|
|
|
|
let
|
|
|
|
ropts' = ropts {depth_=Nothing
|
|
|
|
,balancetype_=HistoricalBalance
|
|
|
|
}
|
|
|
|
q = filterQuery (not . queryIsDepth) $ queryFromOpts d ropts'
|
|
|
|
thisacctq = Acct $ accountNameToAccountRegex acct -- includes subs
|
|
|
|
items = reverse $ snd $ accountTransactionsReport ropts j' q thisacctq
|
|
|
|
ts = map first6 items
|
|
|
|
numberedts = zip [1..] ts
|
|
|
|
-- select the best current transaction from the new list
|
|
|
|
-- stay at the same index if possible, or if we are now past the end, select the last, otherwise select the first
|
|
|
|
(i',t') = case lookup i numberedts
|
|
|
|
of Just t'' -> (i,t'')
|
|
|
|
Nothing | null numberedts -> (0,nulltransaction)
|
|
|
|
| i > fst (last numberedts) -> last numberedts
|
|
|
|
| otherwise -> head numberedts
|
2016-06-11 03:30:45 +03:00
|
|
|
ui' = ui{aScreen=s{tsTransaction=(i',t')
|
2016-06-10 21:50:57 +03:00
|
|
|
,tsTransactions=numberedts
|
|
|
|
,tsAccount=acct}}
|
2016-06-11 03:30:45 +03:00
|
|
|
continue $ regenerateScreens j' d ui'
|
2016-10-31 18:06:29 +03:00
|
|
|
VtyEvent (EvKey (KChar 'I') []) -> continue $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)
|
2016-06-10 21:50:57 +03:00
|
|
|
-- if allowing toggling here, we should refresh the txn list from the parent register screen
|
2016-06-11 03:30:45 +03:00
|
|
|
-- EvKey (KChar 'E') [] -> continue $ regenerateScreens j d $ stToggleEmpty ui
|
|
|
|
-- EvKey (KChar 'C') [] -> continue $ regenerateScreens j d $ stToggleCleared ui
|
|
|
|
-- EvKey (KChar 'R') [] -> continue $ regenerateScreens j d $ stToggleReal ui
|
2017-06-30 18:37:10 +03:00
|
|
|
VtyEvent e | e `elem` moveUpEvents -> continue $ regenerateScreens j d ui{aScreen=s{tsTransaction=(iprev,tprev)}}
|
|
|
|
VtyEvent e | e `elem` moveDownEvents -> continue $ regenerateScreens j d ui{aScreen=s{tsTransaction=(inext,tnext)}}
|
|
|
|
VtyEvent e | e `elem` moveLeftEvents -> continue ui''
|
2016-06-10 21:50:57 +03:00
|
|
|
where
|
2016-06-11 03:30:45 +03:00
|
|
|
ui'@UIState{aScreen=scr} = popScreen ui
|
|
|
|
ui'' = ui'{aScreen=rsSelect (fromIntegral i) scr}
|
2019-01-19 03:27:42 +03:00
|
|
|
VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui
|
2019-01-24 04:25:20 +03:00
|
|
|
VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
|
2016-06-11 03:30:45 +03:00
|
|
|
_ -> continue ui
|
2015-10-30 03:45:57 +03:00
|
|
|
|
2016-06-09 09:45:26 +03:00
|
|
|
tsHandle _ _ = error "event handler called with wrong screen type, should not happen"
|
2015-10-30 06:34:31 +03:00
|
|
|
|
2016-06-09 09:45:26 +03:00
|
|
|
-- | Select the nth item on the register screen.
|
|
|
|
rsSelect i scr@RegisterScreen{..} = scr{rsList=l'}
|
|
|
|
where l' = listMoveTo (i-1) rsList
|
|
|
|
rsSelect _ scr = scr
|