hledger-ui 0.27.5 → 1.0
raw patch · 17 files changed
+2740/−770 lines, 17 filesdep +ansi-terminaldep +megaparsecdep +microlensdep −lensdep ~brickdep ~hledgerdep ~hledger-lib
Dependencies added: ansi-terminal, megaparsec, microlens, microlens-platform, process, text, text-zipper
Dependencies removed: lens
Dependency ranges changed: brick, hledger, hledger-lib, vty
Files
- CHANGES +103/−4
- Hledger/UI/AccountsScreen.hs +264/−220
- Hledger/UI/Editor.hs +85/−0
- Hledger/UI/ErrorScreen.hs +132/−97
- Hledger/UI/Main.hs +42/−30
- Hledger/UI/RegisterScreen.hs +221/−139
- Hledger/UI/Theme.hs +1/−0
- Hledger/UI/TransactionScreen.hs +148/−100
- Hledger/UI/UIOptions.hs +2/−3
- Hledger/UI/UIState.hs +216/−0
- Hledger/UI/UITypes.hs +137/−35
- Hledger/UI/UIUtils.hs +158/−102
- doc/hledger-ui.1 +453/−0
- doc/hledger-ui.1.info +367/−0
- doc/hledger-ui.1.txt +351/−0
- hledger-ui.cabal +57/−40
- hledger-ui.hs +3/−0
CHANGES view
@@ -1,13 +1,112 @@ User-visible changes in hledger-ui. See also hledger's change log. -0.27.5 (2016/05/27) -- GHC 8 compatibility+# 1.0 (2016/10/26) -0.27.4 (2016/5/19)+## accounts screen -- ensure we have the Show instance for functions, needed since regex-tdfa 1.2.2+- at depth 0, show accounts on one "All" line and show all transactions in the register++- 0 now sets depth limit to 0 instead of clearing it++- always use --no-elide for a more regular accounts tree++## register screen++- registers can now include/exclude subaccount transactions.++ The register screen now includes subaccounts' transactions if the+ accounts screen was in tree mode, or when showing an account+ which was at the depth limit. Ie, it always shows the+ transactions contributing to the balance displayed on the+ accounts screen. As on the accounts screen, F toggles between+ tree mode/subaccount txns included by default and flat+ mode/subaccount txns excluded by default. (At least, it does when+ it would make a difference.)++- register transactions are filtered by realness and status (#354).++ Two fixes for the account transactions report when --real/--cleared/real:/status: + are in effect, affecting hledger-ui and hledger-web:+ + 1. exclude transactions which affect the current account via an excluded posting type.+ Eg when --real is in effect, a transaction posting to the current account with only+ virtual postings will not appear in the report.+ + 2. when showing historical balances, don't count excluded posting types in the+ starting balance. Eg with --real, the starting balance will be the sum of only the+ non-virtual prior postings.+ + This is complicated and there might be some ways to confuse it still, causing+ wrongly included/excluded transactions or wrong historical balances/running totals+ (transactions with both real and virtual postings to the current account, perhaps ?)++- show more accurate dates when postings have their own dates.++ If postings to the register account matched by the register's+ filter query have their own dates, we show the earliest of these+ as the transaction date.++## misc++- H toggles between showing "historical" or "period" balances (#392).++ By default hledger-ui now shows historical balances, which+ include transactions before the report start date (like hledger+ balance --historical). Use the H key to toggle to "period" mode,+ where balances start from 0 on the report start date.++- shift arrow keys allow quick period browsing++ - shift-down narrows to the next smaller standard period+ (year/quarter/month/week/day), shift-up does the reverse+ - when narrowed to a standard period, shift-right/left moves to+ the next/previous period+ - \`t\` sets the period to today.++- a runs the add command++- E runs $HLEDGER_UI_EDITOR or $EDITOR or a default editor (vi) on the journal file.++ When using emacs or vi, if a transaction is selected the cursor will be positioned at its journal entry.++- / key sets the filter query; BACKSPACE/DELETE clears it++- Z toggles display of zero items (like --empty), and they are shown by default.++ -E/--empty is now the default for hledger-ui, so accounts with 0 balance+ and transactions posting 0 change are shown by default. The Z key+ toggles this, entering "nonzero" mode which hides zero items.++- R toggles inclusion of only real (non-virtual) postings++- U toggles inclusion of only uncleared transactions/postings++- I toggles balance assertions checking, useful for troubleshooting++- vi-style movement keys are now supported (for help, you must now use ? not h) (#357)++- ESC cancels minibuffer/help or clears the filter query and jumps to top screen++- ENTER has been reserved for later use++- reloading now preserves any options and modes in effect++- reloading on the error screen now updates the message rather than entering a new error screen++- the help dialog is more detailed, includes the hledger-ui manual, and uses the full terminal width if needed++- the header/footer content is more efficient; historical/period and tree/flat modes are now indicated in the footer++- date: query args on the command line now affect the report period.++ A date2: arg or --date2 flag might also affect it (untested).++- hledger-ui now uses the quicker-building microlens+++ 0.27.3 (2016/1/12)
Hledger/UI/AccountsScreen.hs view
@@ -4,72 +4,71 @@ {-# LANGUAGE RecordWildCards #-} module Hledger.UI.AccountsScreen- (screen- ,initAccountsScreen+ (accountsScreen+ ,asInit ,asSetSelectedAccount ) where -import Control.Lens ((^.))--- import Control.Monad+import Brick+import Brick.Widgets.List+import Brick.Widgets.Edit+import Brick.Widgets.Border (borderAttr)+import Control.Monad import Control.Monad.IO.Class (liftIO)--- import Data.Default import Data.List import Data.Maybe import Data.Monoid+import qualified Data.Text as T import Data.Time.Calendar (Day)-import System.FilePath (takeFileName) import qualified Data.Vector as V-import Graphics.Vty as Vty-import Brick-import Brick.Widgets.List-import Brick.Widgets.Border (borderAttr)--- import Brick.Widgets.Center+import Graphics.Vty (Event(..),Key(..),Modifier(..))+import Lens.Micro.Platform+import System.Console.ANSI+import System.FilePath (takeFileName) import Hledger import Hledger.Cli hiding (progname,prognameandversion,green)--- import Hledger.Cli.CliOptions (defaultBalanceLineFormat) import Hledger.UI.UIOptions--- import Hledger.UI.Theme import Hledger.UI.UITypes+import Hledger.UI.UIState import Hledger.UI.UIUtils-import qualified Hledger.UI.RegisterScreen as RS (screen, rsSetCurrentAccount)-import qualified Hledger.UI.ErrorScreen as ES (screen)+import Hledger.UI.Editor+import Hledger.UI.RegisterScreen+import Hledger.UI.ErrorScreen -screen = AccountsScreen{- asState = (list "accounts" V.empty 1, "")- ,sInitFn = initAccountsScreen- ,sDrawFn = drawAccountsScreen- ,sHandleFn = handleAccountsScreen+accountsScreen :: Screen+accountsScreen = AccountsScreen{+ sInit = asInit+ ,sDraw = asDraw+ ,sHandle = asHandle+ ,_asList = list AccountsList V.empty 1+ ,_asSelectedAccount = "" } -asSetSelectedAccount a scr@AccountsScreen{asState=(l,_)} = scr{asState=(l,a)}-asSetSelectedAccount _ scr = scr--initAccountsScreen :: Day -> AppState -> AppState-initAccountsScreen d st@AppState{+asInit :: Day -> Bool -> UIState -> UIState+asInit d reset ui@UIState{ aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}, ajournal=j,- aScreen=s@AccountsScreen{asState=(_,selacct)}+ aScreen=s@AccountsScreen{} } =- st{aopts=uopts', aScreen=s{asState=(l',selacct)}}+ ui{aopts=uopts', aScreen=s & asList .~ newitems'} where- l = list (Name "accounts") (V.fromList displayitems) 1+ newitems = list AccountsList (V.fromList displayitems) 1 - -- keep the selection near the last known selected account if possible- l' | null selacct = l- | otherwise = maybe l (flip listMoveTo l) midx+ -- keep the selection near the last selected account+ -- (may need to move to the next leaf account when entering flat mode)+ newitems' = listMoveTo selidx newitems where- midx = findIndex (\((a,_,_),_) -> a==selacctclipped) items- selacctclipped = case depth_ ropts of- Nothing -> selacct- Just d -> clipAccountName d selacct-+ selidx = case (reset, listSelectedElement $ _asList s) of+ (True, _) -> 0+ (_, Nothing) -> 0+ (_, Just (_,AccountsScreenItem{asItemAccountName=a})) -> fromMaybe (fromMaybe 0 mprefixmatch) mexactmatch+ where+ mexactmatch = findIndex ((a ==) . asItemAccountName) displayitems+ mprefixmatch = findIndex ((a `isAccountNamePrefixOf`) . asItemAccountName) displayitems uopts' = uopts{cliopts_=copts{reportopts_=ropts'}}- ropts' = ropts {- -- XXX balanceReport doesn't respect this yet- balancetype_=HistoricalBalance- }+ ropts' = ropts{accountlistmode_=if flat_ ropts then ALFlat else ALTree} q = queryFromOpts d ropts @@ -80,230 +79,275 @@ valuedate = fromMaybe d $ queryEndDate False q -- run the report- (items,_total) = convert $ balanceReport ropts' q j+ (items,_total) = convert $ report ropts' q j+ where+ -- still using the old balanceReport for change reports as it+ -- does not include every account from before the report period+ report | balancetype_ ropts == HistoricalBalance = singleBalanceReport+ | otherwise = balanceReport + -- pre-render the list items- displayitem ((fullacct, shortacct, indent), bal) =- (indent- ,fullacct- ,if flat_ ropts' then fullacct else shortacct- ,map showAmountWithoutPrice amts -- like showMixedAmountOneLineWithoutPrice- )+ displayitem (fullacct, shortacct, indent, bal) =+ AccountsScreenItem{asItemIndentLevel = indent+ ,asItemAccountName = fullacct+ ,asItemDisplayAccountName = replaceHiddenAccountsNameWith "All" $ if flat_ ropts' then fullacct else shortacct+ ,asItemRenderedAmounts = map showAmountWithoutPrice amts -- like showMixedAmountOneLineWithoutPrice+ } where Mixed amts = normaliseMixedAmountSquashPricesForDisplay $ stripPrices bal stripPrices (Mixed as) = Mixed $ map stripprice as where stripprice a = a{aprice=NoPrice} displayitems = map displayitem items -initAccountsScreen _ _ = error "init function called with wrong screen type, should not happen"+asInit _ _ _ = error "init function called with wrong screen type, should not happen" -drawAccountsScreen :: AppState -> [Widget]-drawAccountsScreen _st@AppState{aopts=uopts, ajournal=j, aScreen=AccountsScreen{asState=(l,_)}} =- [ui]- where- toplabel = files- <+> str " accounts"- <+> borderQueryStr querystr- <+> cleared- <+> borderDepthStr mdepth- <+> str " ("- <+> cur- <+> str "/"- <+> total- <+> str ")"- files = case journalFilePaths j of- [] -> str ""- f:_ -> withAttr ("border" <> "bold") $ str $ takeFileName f- -- [f,_:[]] -> (withAttr ("border" <> "bold") $ str $ takeFileName f) <+> str " (& 1 included file)"- -- f:fs -> (withAttr ("border" <> "bold") $ str $ takeFileName f) <+> str (" (& " ++ show (length fs) ++ " included files)")- querystr = query_ $ reportopts_ $ cliopts_ uopts- mdepth = depth_ $ reportopts_ $ cliopts_ uopts- cleared = if (cleared_ $ reportopts_ $ cliopts_ uopts)- then str " with " <+> withAttr (borderAttr <> "query") (str "cleared") <+> str " txns"- else str ""- cur = str (case l^.listSelectedL of- Nothing -> "-"- Just i -> show (i + 1))- total = str $ show $ V.length $ l^.listElementsL+asDraw :: UIState -> [Widget Name]+asDraw UIState{aopts=UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}+ ,ajournal=j+ ,aScreen=s@AccountsScreen{}+ ,aMode=mode+ } =+ case mode of+ Help -> [helpDialog, maincontent]+ -- Minibuffer e -> [minibuffer e, maincontent]+ _ -> [maincontent]+ where+ maincontent = Widget Greedy Greedy $ do+ c <- getContext+ let+ availwidth =+ -- ltrace "availwidth" $+ c^.availWidthL+ - 2 -- XXX due to margin ? shouldn't be necessary (cf UIUtils)+ displayitems = s ^. asList . listElementsL+ maxacctwidthseen =+ -- ltrace "maxacctwidthseen" $+ V.maximum $+ V.map (\AccountsScreenItem{..} -> asItemIndentLevel + textWidth asItemDisplayAccountName) $+ -- V.filter (\(indent,_,_,_) -> (indent-1) <= fromMaybe 99999 mdepth) $+ displayitems+ maxbalwidthseen =+ -- ltrace "maxbalwidthseen" $+ V.maximum $ V.map (\AccountsScreenItem{..} -> sum (map strWidth asItemRenderedAmounts) + 2 * (length asItemRenderedAmounts - 1)) displayitems+ maxbalwidth =+ -- ltrace "maxbalwidth" $+ max 0 (availwidth - 2 - 4) -- leave 2 whitespace plus least 4 for accts+ balwidth =+ -- ltrace "balwidth" $+ min maxbalwidth maxbalwidthseen+ maxacctwidth =+ -- ltrace "maxacctwidth" $+ availwidth - 2 - balwidth+ acctwidth =+ -- ltrace "acctwidth" $+ min maxacctwidth maxacctwidthseen - bottomlabel = borderKeysStr [- -- ("up/down/pgup/pgdown/home/end", "move")- ("-=1234567890", "depth")- ,("F", "flat?")- ,("C", "cleared?")- ,("right/enter", "register")- ,("g", "reload")- ,("q", "quit")- ]+ -- XXX how to minimise the balance column's jumping around+ -- as you change the depth limit ? - ui = Widget Greedy Greedy $ do- c <- getContext- let- availwidth =- -- ltrace "availwidth" $- c^.availWidthL- - 2 -- XXX due to margin ? shouldn't be necessary (cf UIUtils)- displayitems = listElements l- maxacctwidthseen =- -- ltrace "maxacctwidthseen" $- V.maximum $- V.map (\(indent,_,displayacct,_) -> indent*2 + strWidth displayacct) $- -- V.filter (\(indent,_,_,_) -> (indent-1) <= fromMaybe 99999 mdepth) $- displayitems- maxbalwidthseen =- -- ltrace "maxbalwidthseen" $- V.maximum $ V.map (\(_,_,_,amts) -> sum (map strWidth amts) + 2 * (length amts-1)) displayitems- maxbalwidth =- -- ltrace "maxbalwidth" $- max 0 (availwidth - 2 - 4) -- leave 2 whitespace plus least 4 for accts- balwidth =- -- ltrace "balwidth" $- min maxbalwidth maxbalwidthseen- maxacctwidth =- -- ltrace "maxacctwidth" $- availwidth - 2 - balwidth- acctwidth =- -- ltrace "acctwidth" $- min maxacctwidth maxacctwidthseen+ colwidths = (acctwidth, balwidth) - -- XXX how to minimise the balance column's jumping around- -- as you change the depth limit ?+ render $ defaultLayout toplabel bottomlabel $ renderList (asDrawItem colwidths) True (_asList s) - colwidths = (acctwidth, balwidth)+ where+ ishistorical = balancetype_ ropts == HistoricalBalance - render $ defaultLayout toplabel bottomlabel $ renderList l (drawAccountsItem colwidths)+ toplabel =+ files+ -- <+> withAttr (borderAttr <> "query") (str (if flat_ ropts then " flat" else ""))+ <+> nonzero+ <+> str (if ishistorical then " accounts" else " account changes")+ -- <+> str (if ishistorical then " balances" else " changes")+ <+> borderPeriodStr (if ishistorical then "at end of" else "in") (period_ ropts)+ <+> borderQueryStr querystr+ <+> togglefilters+ <+> borderDepthStr mdepth+ <+> str " ("+ <+> cur+ <+> str "/"+ <+> total+ <+> str ")"+ <+> (if ignore_assertions_ copts+ then withAttr (borderAttr <> "query") (str " ignoring balance assertions")+ else str "")+ where+ files = case journalFilePaths j of+ [] -> str ""+ f:_ -> withAttr ("border" <> "bold") $ str $ takeFileName f+ -- [f,_:[]] -> (withAttr ("border" <> "bold") $ str $ takeFileName f) <+> str " (& 1 included file)"+ -- f:fs -> (withAttr ("border" <> "bold") $ str $ takeFileName f) <+> str (" (& " ++ show (length fs) ++ " included files)")+ querystr = query_ ropts+ mdepth = depth_ ropts+ togglefilters =+ case concat [+ uiShowClearedStatus $ clearedstatus_ ropts+ ,if real_ ropts then ["real"] else []+ ] of+ [] -> str ""+ fs -> str " from " <+> withAttr (borderAttr <> "query") (str $ intercalate ", " fs) <+> str " txns"+ nonzero | empty_ ropts = str ""+ | otherwise = withAttr (borderAttr <> "query") (str " nonzero")+ cur = str (case _asList s ^. listSelectedL of+ Nothing -> "-"+ Just i -> show (i + 1))+ total = str $ show $ V.length $ s ^. asList . listElementsL -drawAccountsScreen _ = error "draw function called with wrong screen type, should not happen"+ bottomlabel = case mode of+ Minibuffer ed -> minibuffer ed+ _ -> quickhelp+ where+ selectedstr = withAttr (borderAttr <> "query") . str+ quickhelp = borderKeysStr' [+ ("?", str "help")+ ,("right", str "register")+ ,("H"+ ,if ishistorical+ then selectedstr "historical" <+> str "/period"+ else str "historical/" <+> selectedstr "period")+ ,("F"+ ,if flat_ ropts+ then str "tree/" <+> selectedstr "flat"+ else selectedstr "tree" <+> str "/flat")+ ,("-+", str "depth")+ --,("/", "filter")+ --,("DEL", "unfilter")+ --,("ESC", "cancel/top")+ ,("a", str "add")+-- ,("g", "reload")+ ,("q", str "quit")+ ] -drawAccountsItem :: (Int,Int) -> Bool -> (Int, String, String, [String]) -> Widget-drawAccountsItem (acctwidth, balwidth) selected (indent, _fullacct, displayacct, balamts) =+asDraw _ = error "draw function called with wrong screen type, should not happen"++asDrawItem :: (Int,Int) -> Bool -> AccountsScreenItem -> Widget Name+asDrawItem (acctwidth, balwidth) selected AccountsScreenItem{..} = Widget Greedy Fixed $ do -- c <- getContext -- let showitem = intercalate "\n" . balanceReportItemAsText defreportopts fmt render $- addamts balamts $- str (fitString (Just acctwidth) (Just acctwidth) True True $ replicate (2*indent) ' ' ++ displayacct) <+>+ addamts asItemRenderedAmounts $+ str (T.unpack $ fitText (Just acctwidth) (Just acctwidth) True True $ T.replicate (asItemIndentLevel) " " <> asItemDisplayAccountName) <+> str " " <+>- str (balspace balamts)+ str (balspace asItemRenderedAmounts) where balspace as = replicate n ' ' where n = max 0 (balwidth - (sum (map strWidth as) + 2 * (length as - 1)))- addamts :: [String] -> Widget -> Widget+ addamts :: [String] -> Widget Name -> Widget Name addamts [] w = w addamts [a] w = (<+> renderamt a) w -- foldl' :: (b -> a -> b) -> b -> t a -> b -- foldl' (Widget -> String -> Widget) -> Widget -> [String] -> Widget addamts (a:as) w = foldl' addamt (addamts [a] w) as- addamt :: Widget -> String -> Widget+ addamt :: Widget Name -> String -> Widget Name addamt w a = ((<+> renderamt a) . (<+> str ", ")) w- renderamt :: String -> Widget+ renderamt :: String -> Widget Name renderamt a | '-' `elem` a = withAttr (sel $ "list" <> "balance" <> "negative") $ str a | otherwise = withAttr (sel $ "list" <> "balance" <> "positive") $ str a sel | selected = (<> "selected") | otherwise = id -handleAccountsScreen :: AppState -> Vty.Event -> EventM (Next AppState)-handleAccountsScreen st@AppState{- aScreen=scr@AccountsScreen{asState=(l,selacct)}+asHandle :: UIState -> Event -> EventM Name (Next UIState)+asHandle ui0@UIState{+ aScreen=scr@AccountsScreen{..}+ ,aopts=UIOpts{cliopts_=copts} ,ajournal=j- } e = do- d <- liftIO getCurrentDay- -- c <- getContext- -- let h = c^.availHeightL- -- moveSel n l = listMoveBy n l+ ,aMode=mode+ } ev = do+ d <- liftIO getCurrentDay+ -- c <- getContext+ -- let h = c^.availHeightL+ -- moveSel n l = listMoveBy n l - -- before we go anywhere, remember the currently selected account.- -- (This is preserved across screen changes, unlike List's selection state)- let- selacct' = case listSelectedElement l of- Just (_, (_, fullacct, _, _)) -> fullacct- Nothing -> selacct- st' = st{aScreen=scr{asState=(l,selacct')}}+ -- save the currently selected account, in case we leave this screen and lose the selection+ let+ selacct = case listSelectedElement _asList of+ Just (_, AccountsScreenItem{..}) -> asItemAccountName+ Nothing -> scr ^. asSelectedAccount+ ui = ui0{aScreen=scr & asSelectedAccount .~ selacct} - case e of- Vty.EvKey Vty.KEsc [] -> halt st'- Vty.EvKey (Vty.KChar 'q') [] -> halt st'- -- Vty.EvKey (Vty.KChar 'l') [Vty.MCtrl] -> do+ case mode of+ Minibuffer ed ->+ case ev of+ EvKey KEsc [] -> continue $ closeMinibuffer ui+ EvKey KEnter [] -> continue $ regenerateScreens j d $ setFilter s $ closeMinibuffer ui+ where s = chomp $ unlines $ map strip $ getEditContents ed+ ev -> do ed' <- handleEditorEvent ev ed+ continue $ ui{aMode=Minibuffer ed'} - Vty.EvKey (Vty.KChar 'g') [] -> do- ej <- liftIO $ journalReload j -- (ej, changed) <- liftIO $ journalReloadIfChanged copts j- case ej of- Right j' -> continue $ reload j' d st'- Left err -> continue $ screenEnter d ES.screen{esState=err} st'+ Help ->+ case ev of+ EvKey (KChar 'q') [] -> halt ui+ _ -> helpHandle ui ev - Vty.EvKey (Vty.KChar '-') [] -> continue $ reload j d $ decDepth st'- Vty.EvKey (Vty.KChar '+') [] -> continue $ reload j d $ incDepth st'- Vty.EvKey (Vty.KChar '=') [] -> continue $ reload j d $ incDepth st'- Vty.EvKey (Vty.KChar '1') [] -> continue $ reload j d $ setDepth 1 st'- Vty.EvKey (Vty.KChar '2') [] -> continue $ reload j d $ setDepth 2 st'- Vty.EvKey (Vty.KChar '3') [] -> continue $ reload j d $ setDepth 3 st'- Vty.EvKey (Vty.KChar '4') [] -> continue $ reload j d $ setDepth 4 st'- Vty.EvKey (Vty.KChar '5') [] -> continue $ reload j d $ setDepth 5 st'- Vty.EvKey (Vty.KChar '6') [] -> continue $ reload j d $ setDepth 6 st'- Vty.EvKey (Vty.KChar '7') [] -> continue $ reload j d $ setDepth 7 st'- Vty.EvKey (Vty.KChar '8') [] -> continue $ reload j d $ setDepth 8 st'- Vty.EvKey (Vty.KChar '9') [] -> continue $ reload j d $ setDepth 9 st'- Vty.EvKey (Vty.KChar '0') [] -> continue $ reload j d $ setDepth 0 st'- Vty.EvKey (Vty.KChar 'F') [] -> continue $ reload j d $ stToggleFlat st'- Vty.EvKey (Vty.KChar 'C') [] -> continue $ reload j d $ stToggleCleared st'- Vty.EvKey (Vty.KLeft) [] -> continue $ popScreen st'- Vty.EvKey (k) [] | k `elem` [Vty.KRight, Vty.KEnter] -> do- let- scr = RS.rsSetCurrentAccount selacct' RS.screen- st'' = screenEnter d scr st'- vScrollToBeginning $ viewportScroll "register"- continue st''+ Normal ->+ case ev of+ EvKey (KChar 'q') [] -> halt ui+ -- EvKey (KChar 'l') [MCtrl] -> do+ EvKey KEsc [] -> continue $ resetScreens d ui+ EvKey (KChar c) [] | c `elem` ['?'] -> continue $ setMode Help ui+ EvKey (KChar 'g') [] -> liftIO (uiReloadJournalIfChanged copts d j ui) >>= continue+ EvKey (KChar 'I') [] -> continue $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)+ EvKey (KChar 'a') [] -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> uiReloadJournalIfChanged copts d j ui+ EvKey (KChar 'E') [] -> suspendAndResume $ void (runEditor endPos (journalFilePath j)) >> uiReloadJournalIfChanged copts d j ui+ EvKey (KChar '0') [] -> continue $ regenerateScreens j d $ setDepth (Just 0) ui+ EvKey (KChar '1') [] -> continue $ regenerateScreens j d $ setDepth (Just 1) ui+ EvKey (KChar '2') [] -> continue $ regenerateScreens j d $ setDepth (Just 2) ui+ EvKey (KChar '3') [] -> continue $ regenerateScreens j d $ setDepth (Just 3) ui+ EvKey (KChar '4') [] -> continue $ regenerateScreens j d $ setDepth (Just 4) ui+ EvKey (KChar '5') [] -> continue $ regenerateScreens j d $ setDepth (Just 5) ui+ EvKey (KChar '6') [] -> continue $ regenerateScreens j d $ setDepth (Just 6) ui+ EvKey (KChar '7') [] -> continue $ regenerateScreens j d $ setDepth (Just 7) ui+ EvKey (KChar '8') [] -> continue $ regenerateScreens j d $ setDepth (Just 8) ui+ EvKey (KChar '9') [] -> continue $ regenerateScreens j d $ setDepth (Just 9) ui+ EvKey (KChar '-') [] -> continue $ regenerateScreens j d $ decDepth ui+ EvKey (KChar '_') [] -> continue $ regenerateScreens j d $ decDepth ui+ EvKey (KChar c) [] | c `elem` ['+','='] -> continue $ regenerateScreens j d $ incDepth ui+ EvKey (KChar 't') [] -> continue $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui+ EvKey (KChar 'H') [] -> continue $ regenerateScreens j d $ toggleHistorical ui+ EvKey (KChar 'F') [] -> continue $ regenerateScreens j d $ toggleFlat ui+ EvKey (KChar 'Z') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleEmpty ui)+ EvKey (KChar 'C') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleCleared ui)+ EvKey (KChar 'U') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleUncleared ui)+ EvKey (KChar 'R') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleReal ui)+ EvKey (KDown) [MShift] -> continue $ regenerateScreens j d $ shrinkReportPeriod d ui+ EvKey (KUp) [MShift] -> continue $ regenerateScreens j d $ growReportPeriod d ui+ EvKey (KRight) [MShift] -> continue $ regenerateScreens j d $ nextReportPeriod journalspan ui+ EvKey (KLeft) [MShift] -> continue $ regenerateScreens j d $ previousReportPeriod journalspan ui+ EvKey (KChar '/') [] -> continue $ regenerateScreens j d $ showMinibuffer ui+ EvKey k [] | k `elem` [KBS, KDel] -> (continue $ regenerateScreens j d $ resetFilter ui)+ EvKey k [] | k `elem` [KLeft, KChar 'h'] -> continue $ popScreen ui+ EvKey k [] | k `elem` [KRight, KChar 'l'] -> scrollTopRegister >> continue (screenEnter d scr ui)+ where+ scr = rsSetAccount selacct isdepthclipped registerScreen+ isdepthclipped = case getDepth ui of+ Just d -> accountNameLevel selacct >= d+ Nothing -> False -- fall through to the list's event handler (handles up/down)- ev -> do- l' <- handleEvent ev l- continue $ st'{aScreen=scr{asState=(l',selacct')}}- -- continue =<< handleEventLensed st' someLens ev-handleAccountsScreen _ _ = error "event handler called with wrong screen type, should not happen"--stToggleFlat :: AppState -> AppState-stToggleFlat st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =- st{aopts=uopts{cliopts_=copts{reportopts_=toggleFlatMode ropts}}}---- | Toggle between flat and tree mode. If in the third "default" mode, go to flat mode.-toggleFlatMode :: ReportOpts -> ReportOpts-toggleFlatMode ropts@ReportOpts{accountlistmode_=ALFlat} = ropts{accountlistmode_=ALTree}-toggleFlatMode ropts = ropts{accountlistmode_=ALFlat}---- | Get the maximum account depth in the current journal.-maxDepth :: AppState -> Int-maxDepth AppState{ajournal=j} = maximum $ map accountNameLevel $ journalAccountNames j+ ev -> do+ let ev' = case ev of+ EvKey (KChar 'k') [] -> EvKey (KUp) []+ EvKey (KChar 'j') [] -> EvKey (KDown) []+ _ -> ev+ newitems <- handleListEvent ev' _asList+ continue $ ui{aScreen=scr & asList .~ newitems+ & asSelectedAccount .~ selacct+ }+ -- continue =<< handleEventLensed ui someLens ev --- | Decrement the current depth limit towards 0. If there was no depth limit,--- set it to one less than the maximum account depth.-decDepth :: AppState -> AppState-decDepth st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts@ReportOpts{..}}}}- = st{aopts=uopts{cliopts_=copts{reportopts_=ropts{depth_=dec depth_}}}} where- dec (Just d) = Just $ max 0 (d-1)- dec Nothing = Just $ maxDepth st - 1+ -- Encourage a more stable scroll position when toggling list items.+ -- We scroll to the top, and the viewport will automatically+ -- scroll down just far enough to reveal the selection, which+ -- usually leaves it at bottom of screen).+ -- XXX better: scroll so selection is in middle of screen ?+ scrollTop = vScrollToBeginning $ viewportScroll AccountsViewport+ scrollTopRegister = vScrollToBeginning $ viewportScroll RegisterViewport+ journalspan = journalDateSpan False j --- | Increment the current depth limit. If this makes it equal to the--- the maximum account depth, remove the depth limit.-incDepth :: AppState -> AppState-incDepth st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts@ReportOpts{..}}}}- = st{aopts=uopts{cliopts_=copts{reportopts_=ropts{depth_=inc depth_}}}}- where- inc (Just d) | d < (maxDepth st - 1) = Just $ d+1- inc _ = Nothing+asHandle _ _ = error "event handler called with wrong screen type, should not happen" --- | Set the current depth limit to the specified depth, which should--- be a positive number. If it is zero, or equal to or greater than the--- current maximum account depth, the depth limit will be removed.--- (Slight inconsistency here: zero is currently a valid display depth--- which can be reached using the - key. But we need a key to remove--- the depth limit, and 0 is it.)-setDepth :: Int -> AppState -> AppState-setDepth depth st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}}- = st{aopts=uopts{cliopts_=copts{reportopts_=ropts{depth_=mdepth'}}}}- where- mdepth' | depth < 0 = depth_ ropts- | depth == 0 = Nothing- | depth >= maxDepth st = Nothing- | otherwise = Just depth+asSetSelectedAccount a s@AccountsScreen{} = s & asSelectedAccount .~ a+asSetSelectedAccount _ s = s
+ Hledger/UI/Editor.hs view
@@ -0,0 +1,85 @@+{- | Editor integration. -}++-- {-# LANGUAGE OverloadedStrings #-}++module Hledger.UI.Editor+where++import Control.Applicative ((<|>))+import Data.List+import Safe+import System.Environment+import System.Exit+import System.FilePath+import System.Process++import Hledger++-- | Editors we know how to create more specific command lines for.+data EditorType = Emacs | EmacsClient | Vi | Other++-- | A position we can move to in a text editor: a line and optional column number.+-- 1 (or 0) means the first and -1 means the last (and -2 means the second last, etc.+-- though this may not be well supported.)+type TextPosition = (Int, Maybe Int)++endPos :: Maybe TextPosition+endPos = Just (-1,Nothing)++-- | Try running the user's preferred text editor, or a default edit command,+-- on the main journal file, blocking until it exits, and returning the exit code;+-- or raise an error.+runEditor :: Maybe TextPosition -> FilePath -> IO ExitCode+runEditor mpos f = editorOpenPositionCommand mpos f >>= runCommand >>= waitForProcess++-- Get the basic shell command to start the user's preferred text editor.+-- This is the value of environment variable $HLEDGER_UI_EDITOR, or $EDITOR, or+-- a default (emacsclient -a '' -nw, start/connect to an emacs daemon in terminal mode).+editorCommand :: IO String+editorCommand = do+ hledger_ui_editor_env <- lookupEnv "HLEDGER_UI_EDITOR"+ editor_env <- lookupEnv "EDITOR"+ let Just cmd =+ hledger_ui_editor_env+ <|> editor_env+ <|> Just "emacsclient -a '' -nw"+ return cmd++-- | Get a shell command to start the user's preferred text editor, or a default,+-- and optionally jump to a given position in the file. This will be the basic+-- editor command, with the appropriate options added, if we know how.+-- Currently we know how to do this for emacs and vi.+-- Some examples:+-- $EDITOR=notepad -> "notepad FILE"+-- $EDITOR=vi -> "vi +LINE FILE"+-- $EDITOR=vi, line -1 -> "vi + FILE"+-- $EDITOR=emacs -> "emacs +LINE:COL FILE"+-- $EDITOR=emacs, line -1 -> "emacs FILE -f end-of-buffer"+-- $EDITOR not set -> "emacs -nw FILE -f end-of-buffer"+--+editorOpenPositionCommand :: Maybe TextPosition -> FilePath -> IO String+editorOpenPositionCommand mpos f = do+ cmd <- editorCommand+ let f' = singleQuoteIfNeeded f+ return $+ case (identifyEditor cmd, mpos) of+ (EmacsClient, Just (l,mc)) | l >= 0 -> cmd ++ " " ++ emacsposopt l mc ++ " " ++ f'+ (EmacsClient, Just (l,mc)) | l < 0 -> cmd ++ " " ++ emacsposopt 999999999 mc ++ " " ++ f'+ (Emacs, Just (l,mc)) | l >= 0 -> cmd ++ " " ++ emacsposopt l mc ++ " " ++ f'+ (Emacs, Just (l,_)) | l < 0 -> cmd ++ " " ++ f' ++ " -f end-of-buffer"+ (Vi, Just (l,_)) -> cmd ++ " " ++ viposopt l ++ " " ++ f'+ _ -> cmd ++ " " ++ f'+ where+ emacsposopt l mc = "+" ++ show l ++ maybe "" ((":"++).show) mc+ viposopt l = "+" ++ if l >= 0 then show l else ""++-- Identify which text editor is used in the basic editor command, if possible.+identifyEditor :: String -> EditorType+identifyEditor cmd+ | "emacsclient" `isPrefixOf` exe = EmacsClient+ | "emacs" `isPrefixOf` exe = Emacs+ | exe `elem` ["vi","vim","ex","view","gvim","gview","evim","eview","rvim","rview","rgvim","rgview"]+ = Vi+ | otherwise = Other+ where+ exe = lowercase $ takeFileName $ headDef "" $ words' cmd
Hledger/UI/ErrorScreen.hs view
@@ -1,125 +1,160 @@ -- The error screen, showing a current error condition (such as a parse error after reloading the journal) -{-# LANGUAGE OverloadedStrings, FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings, FlexibleContexts, RecordWildCards #-} module Hledger.UI.ErrorScreen- (screen)+ (errorScreen+ ,uiCheckBalanceAssertions+ ,uiReloadJournal+ ,uiReloadJournalIfChanged+ ) where --- import Control.Lens ((^.))+import Brick+-- import Brick.Widgets.Border (borderAttr)+import Control.Monad import Control.Monad.IO.Class (liftIO) import Data.Monoid--- import Data.Maybe import Data.Time.Calendar (Day)-import Graphics.Vty as Vty-import Brick--- import Brick.Widgets.List--- import Brick.Widgets.Border--- import Brick.Widgets.Border.Style--- import Brick.Widgets.Center--- import Text.Printf+import Graphics.Vty (Event(..),Key(..))+import Text.Megaparsec --- import Hledger import Hledger.Cli hiding (progname,prognameandversion,green) import Hledger.UI.UIOptions--- import Hledger.UI.Theme import Hledger.UI.UITypes+import Hledger.UI.UIState import Hledger.UI.UIUtils+import Hledger.UI.Editor -screen = ErrorScreen{- esState = ""- ,sInitFn = initErrorScreen- ,sDrawFn = drawErrorScreen- ,sHandleFn = handleErrorScreen+errorScreen :: Screen+errorScreen = ErrorScreen{+ sInit = esInit+ ,sDraw = esDraw+ ,sHandle = esHandle+ ,esError = "" } -initErrorScreen :: Day -> AppState -> AppState-initErrorScreen _ st@AppState{aScreen=ErrorScreen{}} = st-initErrorScreen _ _ = error "init function called with wrong screen type, should not happen"+esInit :: Day -> Bool -> UIState -> UIState+esInit _ _ ui@UIState{aScreen=ErrorScreen{}} = ui+esInit _ _ _ = error "init function called with wrong screen type, should not happen" -drawErrorScreen :: AppState -> [Widget]-drawErrorScreen AppState{ -- aopts=_uopts@UIOpts{cliopts_=_copts@CliOpts{reportopts_=_ropts@ReportOpts{query_=querystr}}},- aScreen=ErrorScreen{esState=err}} = [ui]+esDraw :: UIState -> [Widget Name]+esDraw UIState{ --aopts=UIOpts{cliopts_=copts@CliOpts{}}+ aScreen=ErrorScreen{..}+ ,aMode=mode} =+ case mode of+ Help -> [helpDialog, maincontent]+ -- Minibuffer e -> [minibuffer e, maincontent]+ _ -> [maincontent] where- toplabel = withAttr ("border" <> "bold") (str "Oops. Please fix this problem then press g to reload")- -- <+> str " transactions"- -- <+> borderQueryStr querystr -- no, account transactions report shows all transactions in the acct ?- -- <+> str " and subs"- -- <+> str " ("- -- <+> cur- -- <+> str "/"- -- <+> total- -- <+> str ")"- -- cur = str $ case l^.listSelectedL of- -- Nothing -> "-"- -- Just i -> show (i + 1)- -- total = str $ show $ length displayitems- -- displayitems = V.toList $ l^.listElementsL- bottomlabel = borderKeysStr [- -- ("up/down/pgup/pgdown/home/end", "move")- ("g", "reload")- -- ,("left", "return to accounts")- ]--- -- query = query_ $ reportopts_ $ cliopts_ opts+ maincontent = Widget Greedy Greedy $ do+ render $ defaultLayout toplabel bottomlabel $ withAttr "error" $ str $ esError+ where+ toplabel =+ withAttr ("border" <> "bold") (str "Oops. Please fix this problem then press g to reload")+ -- <+> (if ignore_assertions_ copts then withAttr (borderAttr <> "query") (str " ignoring") else str " not ignoring") - ui = Widget Greedy Greedy $ do+ bottomlabel = case mode of+ -- Minibuffer ed -> minibuffer ed+ _ -> quickhelp+ where+ quickhelp = borderKeysStr [+ ("h", "help")+ ,("ESC", "cancel/top")+ ,("E", "editor")+ ,("g", "reload")+ ,("q", "quit")+ ] - -- calculate column widths, based on current available width- -- c <- getContext- -- let- -- totalwidth = c^.availWidthL- -- - 2 -- XXX due to margin ? shouldn't be necessary (cf UIUtils)+esDraw _ = error "draw function called with wrong screen type, should not happen" - render $ defaultLayout toplabel bottomlabel $ withAttr "error" $ str err+esHandle :: UIState -> Event -> EventM Name (Next UIState)+esHandle ui@UIState{+ aScreen=ErrorScreen{..}+ ,aopts=UIOpts{cliopts_=copts}+ ,ajournal=j+ ,aMode=mode+ } ev =+ case mode of+ Help ->+ case ev of+ EvKey (KChar 'q') [] -> halt ui+ _ -> helpHandle ui ev -drawErrorScreen _ = error "draw function called with wrong screen type, should not happen"+ _ -> do+ d <- liftIO getCurrentDay+ case ev of+ EvKey (KChar 'q') [] -> halt ui+ EvKey KEsc [] -> continue $ uiCheckBalanceAssertions d $ resetScreens d ui+ EvKey (KChar c) [] | c `elem` ['h','?'] -> continue $ setMode Help ui+ EvKey (KChar 'E') [] -> suspendAndResume $ void (runEditor pos f) >> uiReloadJournalIfChanged copts d j (popScreen ui)+ where+ (pos,f) = case parsewithString hledgerparseerrorpositionp esError of+ Right (f,l,c) -> (Just (l, Just c),f)+ Left _ -> (endPos, journalFilePath j)+ EvKey (KChar 'g') [] -> liftIO (uiReloadJournalIfChanged copts d j (popScreen ui)) >>= continue . uiCheckBalanceAssertions d+-- (ej, _) <- liftIO $ journalReloadIfChanged copts d j+-- case ej of+-- Left err -> continue ui{aScreen=s{esError=err}} -- show latest parse error+-- Right j' -> continue $ regenerateScreens j' d $ popScreen ui -- return to previous screen, and reload it+ EvKey (KChar 'I') [] -> continue $ uiCheckBalanceAssertions d (popScreen $ toggleIgnoreBalanceAssertions ui)+ _ -> continue ui --- drawErrorItem :: (Int,Int,Int,Int,Int) -> Bool -> (String,String,String,String,String) -> Widget--- drawErrorItem (datewidth,descwidth,acctswidth,changewidth,balwidth) selected (date,desc,accts,change,bal) =--- Widget Greedy Fixed $ do--- render $--- str (fitString (Just datewidth) (Just datewidth) True True date) <+>--- str " " <+>--- str (fitString (Just descwidth) (Just descwidth) True True desc) <+>--- str " " <+>--- str (fitString (Just acctswidth) (Just acctswidth) True True accts) <+>--- str " " <+>--- withAttr changeattr (str (fitString (Just changewidth) (Just changewidth) True False change)) <+>--- str " " <+>--- withAttr balattr (str (fitString (Just balwidth) (Just balwidth) True False bal))--- where--- changeattr | '-' `elem` change = sel $ "list" <> "amount" <> "decrease"--- | otherwise = sel $ "list" <> "amount" <> "increase"--- balattr | '-' `elem` bal = sel $ "list" <> "balance" <> "negative"--- | otherwise = sel $ "list" <> "balance" <> "positive"--- sel | selected = (<> "selected")--- | otherwise = id+esHandle _ _ = error "event handler called with wrong screen type, should not happen" -handleErrorScreen :: AppState -> Vty.Event -> EventM (Next AppState)-handleErrorScreen st@AppState{- aScreen=s@ErrorScreen{esState=_err}- ,aopts=UIOpts{cliopts_=_copts}- ,ajournal=j- } e = do- case e of- Vty.EvKey Vty.KEsc [] -> halt st- Vty.EvKey (Vty.KChar 'q') [] -> halt st+-- | Parse the file name, line and column number from a hledger parse error message, if possible.+-- Temporary, we should keep the original parse error location. XXX+hledgerparseerrorpositionp :: ParsecT Dec String t (String, Int, Int)+hledgerparseerrorpositionp = do+ anyChar `manyTill` char '"'+ f <- anyChar `manyTill` (oneOf ['"','\n'])+ string " (line "+ l <- read <$> some digitChar+ string ", column "+ c <- read <$> some digitChar+ return (f, l, c) - Vty.EvKey (Vty.KChar 'g') [] -> do- d <- liftIO getCurrentDay- ej <- liftIO $ journalReload j -- (ej, changed) <- liftIO $ journalReloadIfChanged copts j- case ej of- Left err -> continue st{aScreen=s{esState=err}} -- show latest parse error- Right j' -> continue $ reload j' d $ popScreen st -- return to previous screen, and reload it+-- Unconditionally reload the journal, regenerating the current screen+-- and all previous screens in the history.+-- If reloading fails, enter the error screen, or if we're already+-- on the error screen, update the error displayed.+-- The provided CliOpts are used for reloading, and then saved+-- in the UIState if reloading is successful (otherwise the+-- ui state keeps its old cli opts.)+-- Defined here so it can reference the error screen.+uiReloadJournal :: CliOpts -> Day -> UIState -> IO UIState+uiReloadJournal copts d ui = do+ ej <- journalReload copts+ return $ case ej of+ Right j -> regenerateScreens j d ui{aopts=(aopts ui){cliopts_=copts}}+ Left err ->+ case ui of+ UIState{aScreen=s@ErrorScreen{}} -> ui{aScreen=s{esError=err}}+ _ -> screenEnter d errorScreen{esError=err} ui - -- Vty.EvKey (Vty.KLeft) [] -> continue $ popScreen st- -- Vty.EvKey (Vty.KRight) [] -> error (show curItem) where curItem = listSelectedElement is- -- fall through to the list's event handler (handles [pg]up/down)- _ -> do continue st- -- is' <- handleEvent ev is- -- continue st{aScreen=s{rsState=is'}}- -- continue =<< handleEventLensed st someLens e-handleErrorScreen _ _ = error "event handler called with wrong screen type, should not happen"+-- Like uiReloadJournal, but does not bother re-parsing the journal if+-- the file(s) have not changed since last loaded. Always regenerates+-- the current and previous screens though, since opts or date may have changed.+uiReloadJournalIfChanged :: CliOpts -> Day -> Journal -> UIState -> IO UIState+uiReloadJournalIfChanged copts d j ui = do+ (ej, _changed) <- journalReloadIfChanged copts d j+ return $ case ej of+ Right j' -> regenerateScreens j' d ui{aopts=(aopts ui){cliopts_=copts}}+ Left err ->+ case ui of+ UIState{aScreen=s@ErrorScreen{}} -> ui{aScreen=s{esError=err}}+ _ -> screenEnter d errorScreen{esError=err} ui +-- Re-check any balance assertions in the current journal, and if any+-- fail, enter (or update) the error screen. Or if balance assertions+-- are disabled, do nothing.+uiCheckBalanceAssertions :: Day -> UIState -> UIState+uiCheckBalanceAssertions d ui@UIState{aopts=UIOpts{cliopts_=copts}, ajournal=j}+ | ignore_assertions_ copts = ui+ | otherwise =+ case journalCheckBalanceAssertions j of+ Right _ -> ui+ Left err ->+ case ui of+ UIState{aScreen=s@ErrorScreen{}} -> ui{aScreen=s{esError=err}}+ _ -> screenEnter d errorScreen{esError=err} ui
Hledger/UI/Main.hs view
@@ -10,13 +10,15 @@ module Hledger.UI.Main where -- import Control.Applicative--- import Control.Lens ((^.))+-- import Lens.Micro.Platform ((^.)) import Control.Monad -- import Control.Monad.IO.Class (liftIO) -- import Data.Default -- import Data.Monoid -- import Data.List import Data.Maybe+-- import Data.Text (Text)+import qualified Data.Text as T -- import Data.Time.Calendar import Safe import System.Exit@@ -30,8 +32,8 @@ import Hledger.UI.UITypes -- import Hledger.UI.UIUtils import Hledger.UI.Theme-import Hledger.UI.AccountsScreen as AS-import Hledger.UI.RegisterScreen as RS+import Hledger.UI.AccountsScreen+import Hledger.UI.RegisterScreen ---------------------------------------------------------------------- @@ -42,8 +44,11 @@ run opts where run opts- | "help" `inRawOpts` (rawopts_ $ cliopts_ opts) = putStr (showModeHelp uimode) >> exitSuccess- | "version" `inRawOpts` (rawopts_ $ cliopts_ opts) = putStrLn prognameandversion >> exitSuccess+ | "h" `inRawOpts` (rawopts_ $ cliopts_ opts) = putStr (showModeUsage uimode) >> exitSuccess+ | "help" `inRawOpts` (rawopts_ $ cliopts_ opts) = printHelpForTopic (topicForMode uimode) >> exitSuccess+ | "man" `inRawOpts` (rawopts_ $ cliopts_ opts) = runManForTopic (topicForMode uimode) >> exitSuccess+ | "info" `inRawOpts` (rawopts_ $ cliopts_ opts) = runInfoForTopic (topicForMode uimode) >> exitSuccess+ | "version" `inRawOpts` (rawopts_ $ cliopts_ opts) = putStrLn prognameandversion >> exitSuccess | "binary-filename" `inRawOpts` (rawopts_ $ cliopts_ opts) = putStrLn (binaryfilename progname) | otherwise = withJournalDoUICommand opts runBrickUi @@ -71,11 +76,18 @@ uopts' = uopts{ cliopts_=copts{ reportopts_= ropts{- -- ensure depth_ also reflects depth: args- depth_=depthfromoptsandargs,- -- remove depth: args from query_- query_=unwords $ -- as in ReportOptions, with same limitations- [v | (k,v) <- rawopts_ copts, k=="args", not $ "depth" `isPrefixOf` v]+ -- incorporate any depth: query args into depth_,+ -- any date: query args into period_+ depth_ =depthfromoptsandargs,+ period_=periodfromoptsandargs,+ query_ =unwords -- as in ReportOptions, with same limitations+ [v | (k,v) <- rawopts_ copts, k=="args", not $ any (`isPrefixOf` v) ["depth","date"]],+ -- always disable boring account name eliding, unlike the CLI, for a more regular tree+ no_elide_=True,+ -- show items with zero amount by default, unlike the CLI+ empty_=True,+ -- show historical balances by default, unlike the CLI+ balancetype_=HistoricalBalance } } }@@ -83,6 +95,9 @@ q = queryFromOpts d ropts depthfromoptsandargs = case queryDepth q of 99999 -> Nothing d -> Just d+ datespanfromargs = queryDateSpan (date2_ ropts) $ fst $ parseQuery d (T.pack $ query_ ropts)+ periodfromoptsandargs =+ dateSpanAsPeriod $ spansIntersect [periodAsDateSpan $ period_ ropts, datespanfromargs] -- XXX move this stuff into Options, UIOpts theme = maybe defaultTheme (fromMaybe defaultTheme . getTheme) $@@ -90,48 +105,45 @@ mregister = maybestringopt "register" $ rawopts_ copts (scr, prevscrs) = case mregister of- Nothing -> (AS.screen, [])+ Nothing -> (accountsScreen, []) -- with --register, start on the register screen, and also put -- the accounts screen on the prev screens stack so you can exit -- to that as usual.- Just apat -> (rsSetCurrentAccount acct RS.screen, [ascr'])+ Just apat -> (rsSetAccount acct False registerScreen, [ascr']) where acct = headDef (error' $ "--register "++apat++" did not match any account")- $ filter (regexMatches apat) $ journalAccountNames j+ $ filter (regexMatches apat . T.unpack) $ journalAccountNames j -- Initialising the accounts screen is awkward, requiring- -- another temporary AppState value..+ -- another temporary UIState value.. ascr' = aScreen $- AS.initAccountsScreen d $- AppState{+ asInit d True $+ UIState{ aopts=uopts' ,ajournal=j- ,aScreen=asSetSelectedAccount acct AS.screen+ ,aScreen=asSetSelectedAccount acct accountsScreen ,aPrevScreens=[]+ ,aMode=Normal } - st = (sInitFn scr) d- AppState{+ ui = (sInit scr) d True+ UIState{ aopts=uopts' ,ajournal=j ,aScreen=scr ,aPrevScreens=prevscrs+ ,aMode=Normal }- - app :: App (AppState) V.Event- app = App {++ brickapp :: App (UIState) V.Event Name+ brickapp = App { appLiftVtyEvent = id , appStartEvent = return , appAttrMap = const theme , appChooseCursor = showFirstCursor- , appHandleEvent = \st ev -> sHandleFn (aScreen st) st ev- , appDraw = \st -> sDrawFn (aScreen st) st- -- XXX bizarro. removing the st arg and parameter above,- -- which according to GHCI does not change the type,- -- causes "Exception: draw function called with wrong screen type"- -- on entering a register. Likewise, removing the st ev args and parameters- -- causes an exception on exiting a register.+ , appHandleEvent = \ui ev -> sHandle (aScreen ui) ui ev+ , appDraw = \ui -> sDraw (aScreen ui) ui } - void $ defaultMain app st+ void $ defaultMain brickapp ui
Hledger/UI/RegisterScreen.hs view
@@ -1,129 +1,126 @@ -- The account register screen, showing transactions in an account, like hledger-web's register. -{-# LANGUAGE OverloadedStrings, FlexibleContexts #-}+{-# LANGUAGE OverloadedStrings, FlexibleContexts, RecordWildCards #-} module Hledger.UI.RegisterScreen- (screen- ,rsSetCurrentAccount+ (registerScreen+ ,rsSetAccount ) where -import Control.Lens ((^.))+import Lens.Micro.Platform ((^.))+import Control.Monad import Control.Monad.IO.Class (liftIO) import Data.List import Data.List.Split (splitOn) import Data.Monoid--- import Data.Maybe+import Data.Maybe+import qualified Data.Text as T import Data.Time.Calendar (Day) import qualified Data.Vector as V-import Graphics.Vty as Vty+import Graphics.Vty (Event(..),Key(..),Modifier(..)) import Brick import Brick.Widgets.List+import Brick.Widgets.Edit import Brick.Widgets.Border (borderAttr)--- import Brick.Widgets.Center--- import Text.Printf+import System.Console.ANSI + import Hledger import Hledger.Cli hiding (progname,prognameandversion,green) import Hledger.UI.UIOptions -- import Hledger.UI.Theme import Hledger.UI.UITypes+import Hledger.UI.UIState import Hledger.UI.UIUtils-import qualified Hledger.UI.TransactionScreen as TS (screen)-import qualified Hledger.UI.ErrorScreen as ES (screen)+import Hledger.UI.Editor+import Hledger.UI.TransactionScreen+import Hledger.UI.ErrorScreen -screen = RegisterScreen{- rsState = (list "register" V.empty 1, "")- ,sInitFn = initRegisterScreen- ,sDrawFn = drawRegisterScreen- ,sHandleFn = handleRegisterScreen+registerScreen :: Screen+registerScreen = RegisterScreen{+ sInit = rsInit+ ,sDraw = rsDraw+ ,sHandle = rsHandle+ ,rsList = list RegisterList V.empty 1+ ,rsAccount = ""+ ,rsForceInclusive = False } -rsSetCurrentAccount a scr@RegisterScreen{rsState=(l,_)} = scr{rsState=(l,a)}-rsSetCurrentAccount _ scr = scr+rsSetAccount :: AccountName -> Bool -> Screen -> Screen+rsSetAccount a forceinclusive scr@RegisterScreen{} =+ scr{rsAccount=replaceHiddenAccountsNameWith "*" a, rsForceInclusive=forceinclusive}+rsSetAccount _ _ scr = scr -initRegisterScreen :: Day -> AppState -> AppState-initRegisterScreen d st@AppState{aopts=opts, ajournal=j, aScreen=s@RegisterScreen{rsState=(_,acct)}} =- st{aScreen=s{rsState=(l,acct)}}+rsInit :: Day -> Bool -> UIState -> UIState+rsInit d reset ui@UIState{aopts=UIOpts{cliopts_=CliOpts{reportopts_=ropts}}, ajournal=j, aScreen=s@RegisterScreen{..}} =+ ui{aScreen=s{rsList=newitems'}} where -- gather arguments and queries- ropts = (reportopts_ $ cliopts_ opts)- {- depth_=Nothing,- balancetype_=HistoricalBalance- } -- XXX temp- thisacctq = Acct $ accountNameToAccountRegex acct -- includes subs- q = filterQuery (not . queryIsDepth) $ queryFromOpts d ropts+ inclusive = not (flat_ ropts) || rsForceInclusive+ thisacctq = Acct $ (if inclusive then accountNameToAccountRegex else accountNameToAccountOnlyRegex) rsAccount+ ropts' = ropts{+ depth_=Nothing+ }+ q = queryFromOpts d ropts'+-- reportq = filterQuery (not . queryIsDepth) q - -- run a transactions report, most recent last- q' =- -- ltrace "q"- q- thisacctq' =- -- ltrace "thisacctq"- thisacctq- (_label,items') = accountTransactionsReport ropts j q' thisacctq'- items = reverse items'+ (_label,items) = accountTransactionsReport ropts' j q thisacctq+ items' = (if empty_ ropts' then id else filter (not . isZeroMixedAmount . fifth6)) $ -- without --empty, exclude no-change txns+ reverse -- most recent last+ items - -- pre-render all items; these will be the List elements. This helps calculate column widths.- displayitem (t, _, _issplit, otheracctsstr, change, bal) =- (showDate $ tdate t- ,tdescription t- ,case splitOn ", " otheracctsstr of- [s] -> s- ss -> intercalate ", " ss- -- _ -> "<split>" -- should do this if accounts field width < 30- ,showMixedAmountOneLineWithoutPrice change- ,showMixedAmountOneLineWithoutPrice bal- ,t- )- displayitems = map displayitem items+ -- generate pre-rendered list items. This helps calculate column widths.+ displayitems = map displayitem items'+ where+ displayitem (t, _, _issplit, otheracctsstr, change, bal) =+ RegisterScreenItem{rsItemDate = showDate $ transactionRegisterDate q thisacctq t+ ,rsItemDescription = T.unpack $ tdescription t+ ,rsItemOtherAccounts = case splitOn ", " otheracctsstr of+ [s] -> s+ ss -> intercalate ", " ss+ -- _ -> "<split>" -- should do this if accounts field width < 30+ ,rsItemChangeAmount = showMixedAmountOneLineWithoutPrice change+ ,rsItemBalanceAmount = showMixedAmountOneLineWithoutPrice bal+ ,rsItemTransaction = t+ } - -- build the List, moving the selection to the end- l = listMoveTo (length items) $- list (Name "register") (V.fromList displayitems) 1+ -- build the List+ newitems = list RegisterList (V.fromList displayitems) 1 - -- (listName someList)+ -- keep the selection on the previously selected transaction if possible,+ -- (eg after toggling nonzero mode), otherwise select the last element.+ newitems' = listMoveTo newselidx newitems+ where+ newselidx = case (reset, listSelectedElement rsList) of+ (True, _) -> endidx+ (_, Nothing) -> endidx+ (_, Just (_,RegisterScreenItem{rsItemTransaction=Transaction{tindex=ti}}))+ -> fromMaybe endidx $ findIndex ((==ti) . tindex . rsItemTransaction) displayitems+ endidx = length displayitems -initRegisterScreen _ _ = error "init function called with wrong screen type, should not happen"+rsInit _ _ _ = error "init function called with wrong screen type, should not happen" -drawRegisterScreen :: AppState -> [Widget]-drawRegisterScreen AppState{aopts=uopts -- @UIOpts{cliopts_=_copts@CliOpts{reportopts_=_ropts@ReportOpts{query_=querystr}}- ,aScreen=RegisterScreen{rsState=(l,acct)}} = [ui]+rsDraw :: UIState -> [Widget Name]+rsDraw UIState{aopts=UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}+ ,aScreen=RegisterScreen{..}+ ,aMode=mode+ } =+ case mode of+ Help -> [helpDialog, maincontent]+ -- Minibuffer e -> [minibuffer e, maincontent]+ _ -> [maincontent] where- toplabel = withAttr ("border" <> "bold") (str acct)- <+> cleared- <+> str " transactions"- -- <+> borderQueryStr querystr -- no, account transactions report shows all transactions in the acct ?- -- <+> str " and subs"- <+> str " ("- <+> cur- <+> str "/"- <+> total- <+> str ")"- cleared = if (cleared_ $ reportopts_ $ cliopts_ uopts)- then withAttr (borderAttr <> "query") (str " cleared")- else str ""- cur = str $ case l^.listSelectedL of- Nothing -> "-"- Just i -> show (i + 1)- total = str $ show $ length displayitems- displayitems = V.toList $ l^.listElementsL-- -- query = query_ $ reportopts_ $ cliopts_ opts-- ui = Widget Greedy Greedy $ do-+ displayitems = V.toList $ rsList ^. listElementsL+ maincontent = Widget Greedy Greedy $ do -- calculate column widths, based on current available width c <- getContext let totalwidth = c^.availWidthL - 2 -- XXX due to margin ? shouldn't be necessary (cf UIUtils)- -- 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@@ -132,14 +129,13 @@ whitespacewidth = 10 -- inter-column whitespace, fixed width minnonamtcolswidth = datewidth + 2 + 2 -- date column plus at least 2 for desc and accts maxamtswidth = max 0 (totalwidth - minnonamtcolswidth - whitespacewidth)- maxchangewidthseen = maximum' $ map (strWidth . fourth6) displayitems- maxbalwidthseen = maximum' $ map (strWidth . fifth6) displayitems+ maxchangewidthseen = maximum' $ map (strWidth . rsItemChangeAmount) displayitems+ maxbalwidthseen = maximum' $ map (strWidth . rsItemBalanceAmount) displayitems changewidthproportion = fromIntegral maxchangewidthseen / fromIntegral (maxchangewidthseen + maxbalwidthseen) maxchangewidth = round $ changewidthproportion * fromIntegral maxamtswidth maxbalwidth = maxamtswidth - maxchangewidth changewidth = min maxchangewidth maxchangewidthseen balwidth = min maxbalwidth maxbalwidthseen- -- assign the remaining space to the description and accounts columns -- maxdescacctswidth = totalwidth - (whitespacewidth - 4) - changewidth - balwidth maxdescacctswidth =@@ -158,76 +154,162 @@ acctswidth = maxdescacctswidth - descwidth colwidths = (datewidth,descwidth,acctswidth,changewidth,balwidth) - bottomlabel = borderKeysStr [- -- ("up/down/pgup/pgdown/home/end", "move")- ("left", "back")- ,("C", "cleared?")- ,("right/enter", "transaction")- ,("g", "reload")- ,("q", "quit")- ]+ render $ defaultLayout toplabel bottomlabel $ renderList (rsDrawItem colwidths) True rsList - render $ defaultLayout toplabel bottomlabel $ renderList l (drawRegisterItem colwidths)+ where+ ishistorical = balancetype_ ropts == HistoricalBalance+ inclusive = not (flat_ ropts) || rsForceInclusive -drawRegisterScreen _ = error "draw function called with wrong screen type, should not happen"+ toplabel =+ withAttr ("border" <> "bold") (str $ T.unpack $ replaceHiddenAccountsNameWith "All" rsAccount)+-- <+> withAttr (borderAttr <> "query") (str $ if inclusive then "" else " exclusive")+ <+> togglefilters+ <+> str " transactions"+ -- <+> str (if ishistorical then " historical total" else " period total")+ <+> borderQueryStr (query_ ropts)+ -- <+> str " and subs"+ <+> borderPeriodStr "in" (period_ ropts)+ <+> str " ("+ <+> cur+ <+> str "/"+ <+> total+ <+> str ")"+ <+> (if ignore_assertions_ copts then withAttr (borderAttr <> "query") (str " ignoring balance assertions") else str "")+ where+ togglefilters =+ case concat [+ uiShowClearedStatus $ clearedstatus_ ropts+ ,if real_ ropts then ["real"] else []+ ,if empty_ ropts then [] else ["nonzero"]+ ] of+ [] -> str ""+ fs -> withAttr (borderAttr <> "query") (str $ " " ++ intercalate ", " fs)+ cur = str $ case rsList ^. listSelectedL of+ Nothing -> "-"+ Just i -> show (i + 1)+ total = str $ show $ length displayitems -drawRegisterItem :: (Int,Int,Int,Int,Int) -> Bool -> (String,String,String,String,String,Transaction) -> Widget-drawRegisterItem (datewidth,descwidth,acctswidth,changewidth,balwidth) selected (date,desc,accts,change,bal,_) =+ -- query = query_ $ reportopts_ $ cliopts_ opts++ bottomlabel = case mode of+ Minibuffer ed -> minibuffer ed+ _ -> quickhelp+ where+ selectedstr = withAttr (borderAttr <> "query") . str+ quickhelp = borderKeysStr' [+ ("?", str "help")+ ,("left", str "back")+ ,("right", str "transaction")+ ,("H"+ ,if ishistorical+ then selectedstr "historical" <+> str "/period"+ else str "historical/" <+> selectedstr "period")+ ,("F"+ ,if inclusive+ then selectedstr "inclusive" <+> str "/exclusive"+ else str "inclusive/" <+> selectedstr "exclusive")+-- ,("a", "add")+-- ,("g", "reload")+-- ,("q", "quit")+ ]++rsDraw _ = error "draw function called with wrong screen type, should not happen"++rsDrawItem :: (Int,Int,Int,Int,Int) -> Bool -> RegisterScreenItem -> Widget Name+rsDrawItem (datewidth,descwidth,acctswidth,changewidth,balwidth) selected RegisterScreenItem{..} = Widget Greedy Fixed $ do render $- str (fitString (Just datewidth) (Just datewidth) True True date) <+>+ str (fitString (Just datewidth) (Just datewidth) True True rsItemDate) <+> str " " <+>- str (fitString (Just descwidth) (Just descwidth) True True desc) <+>+ str (fitString (Just descwidth) (Just descwidth) True True rsItemDescription) <+> str " " <+>- str (fitString (Just acctswidth) (Just acctswidth) True True accts) <+>+ str (fitString (Just acctswidth) (Just acctswidth) True True rsItemOtherAccounts) <+> str " " <+>- withAttr changeattr (str (fitString (Just changewidth) (Just changewidth) True False change)) <+>+ withAttr changeattr (str (fitString (Just changewidth) (Just changewidth) True False rsItemChangeAmount)) <+> str " " <+>- withAttr balattr (str (fitString (Just balwidth) (Just balwidth) True False bal))+ withAttr balattr (str (fitString (Just balwidth) (Just balwidth) True False rsItemBalanceAmount)) where- changeattr | '-' `elem` change = sel $ "list" <> "amount" <> "decrease"- | otherwise = sel $ "list" <> "amount" <> "increase"- balattr | '-' `elem` bal = sel $ "list" <> "balance" <> "negative"- | otherwise = sel $ "list" <> "balance" <> "positive"+ changeattr | '-' `elem` rsItemChangeAmount = sel $ "list" <> "amount" <> "decrease"+ | otherwise = sel $ "list" <> "amount" <> "increase"+ balattr | '-' `elem` rsItemBalanceAmount = sel $ "list" <> "balance" <> "negative"+ | otherwise = sel $ "list" <> "balance" <> "positive" sel | selected = (<> "selected") | otherwise = id -handleRegisterScreen :: AppState -> Vty.Event -> EventM (Next AppState)-handleRegisterScreen st@AppState{- aScreen=s@RegisterScreen{rsState=(l,acct)}- ,aopts=UIOpts{cliopts_=_copts}+rsHandle :: UIState -> Event -> EventM Name (Next UIState)+rsHandle ui@UIState{+ aScreen=s@RegisterScreen{..}+ ,aopts=UIOpts{cliopts_=copts} ,ajournal=j- } e = do+ ,aMode=mode+ } ev = do d <- liftIO getCurrentDay- case e of- Vty.EvKey Vty.KEsc [] -> halt st- Vty.EvKey (Vty.KChar 'q') [] -> halt st - Vty.EvKey (Vty.KChar 'g') [] -> do- ej <- liftIO $ journalReload j -- (ej, changed) <- liftIO $ journalReloadIfChanged copts j- case ej of- Right j' -> continue $ reload j' d st- Left err -> continue $ screenEnter d ES.screen{esState=err} st-- Vty.EvKey (Vty.KChar 'C') [] -> continue $ reload j d $ stToggleCleared st-- Vty.EvKey (Vty.KLeft) [] -> continue $ popScreen st+ case mode of+ Minibuffer ed ->+ case ev of+ EvKey KEsc [] -> continue $ closeMinibuffer ui+ EvKey KEnter [] -> continue $ regenerateScreens j d $ setFilter s $ closeMinibuffer ui+ where s = chomp $ unlines $ map strip $ getEditContents ed+ ev -> do ed' <- handleEditorEvent ev ed+ continue $ ui{aMode=Minibuffer ed'} - Vty.EvKey (k) [] | k `elem` [Vty.KRight, Vty.KEnter] -> do- case listSelectedElement l of- Just (_, (_, _, _, _, _, t)) ->- let- ts = map sixth6 $ V.toList $ listElements l- numberedts = zip [1..] ts- i = fromIntegral $ maybe 0 (+1) $ elemIndex t ts -- XXX- in- continue $ screenEnter d TS.screen{tsState=((i,t),numberedts,acct)} st- Nothing -> continue st+ Help ->+ case ev of+ EvKey (KChar 'q') [] -> halt ui+ _ -> helpHandle ui ev - -- fall through to the list's event handler (handles [pg]up/down)- ev -> do- l' <- handleEvent ev l- continue st{aScreen=s{rsState=(l',acct)}}- -- continue =<< handleEventLensed st someLens ev+ Normal ->+ case ev of+ EvKey (KChar 'q') [] -> halt ui+ EvKey KEsc [] -> continue $ resetScreens d ui+ EvKey (KChar c) [] | c `elem` ['?'] -> continue $ setMode Help ui+ EvKey (KChar 'g') [] -> liftIO (uiReloadJournalIfChanged copts d j ui) >>= continue+ EvKey (KChar 'I') [] -> continue $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)+ EvKey (KChar 'a') [] -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> uiReloadJournalIfChanged copts d j ui+ EvKey (KChar 't') [] -> continue $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui+ EvKey (KChar 'E') [] -> suspendAndResume $ void (runEditor pos f) >> uiReloadJournalIfChanged copts d j ui+ where+ (pos,f) = case listSelectedElement rsList of+ Nothing -> (endPos, journalFilePath j)+ Just (_, RegisterScreenItem{rsItemTransaction=Transaction{tsourcepos=GenericSourcePos f l c}}) -> (Just (l, Just c),f)+ EvKey (KChar 'H') [] -> continue $ regenerateScreens j d $ toggleHistorical ui+ EvKey (KChar 'F') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleFlat ui)+ EvKey (KChar 'Z') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleEmpty ui)+ EvKey (KChar 'C') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleCleared ui)+ EvKey (KChar 'U') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleUncleared ui)+ EvKey (KChar 'R') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleReal ui)+ EvKey (KChar '/') [] -> (continue $ regenerateScreens j d $ showMinibuffer ui)+ EvKey (KDown) [MShift] -> continue $ regenerateScreens j d $ shrinkReportPeriod d ui+ EvKey (KUp) [MShift] -> continue $ regenerateScreens j d $ growReportPeriod d ui+ EvKey (KRight) [MShift] -> continue $ regenerateScreens j d $ nextReportPeriod journalspan ui+ EvKey (KLeft) [MShift] -> continue $ regenerateScreens j d $ previousReportPeriod journalspan ui+ EvKey k [] | k `elem` [KBS, KDel] -> (continue $ regenerateScreens j d $ resetFilter ui)+ EvKey k [] | k `elem` [KLeft, KChar 'h'] -> continue $ popScreen ui+ EvKey k [] | k `elem` [KRight, KChar 'l'] -> do+ case listSelectedElement rsList of+ Just (_, RegisterScreenItem{rsItemTransaction=t}) ->+ let+ ts = map rsItemTransaction $ V.toList $ listElements rsList+ numberedts = zip [1..] ts+ i = fromIntegral $ maybe 0 (+1) $ elemIndex t ts -- XXX+ in+ continue $ screenEnter d transactionScreen{tsTransaction=(i,t)+ ,tsTransactions=numberedts+ ,tsAccount=rsAccount} ui+ Nothing -> continue ui+ -- fall through to the list's event handler (handles [pg]up/down)+ ev -> do+ let ev' = case ev of+ EvKey (KChar 'k') [] -> EvKey (KUp) []+ EvKey (KChar 'j') [] -> EvKey (KDown) []+ _ -> ev+ newitems <- handleListEvent ev' rsList+ continue ui{aScreen=s{rsList=newitems}}+ -- continue =<< handleEventLensed ui someLens ev+ where+ -- Encourage a more stable scroll position when toggling list items (cf AccountsScreen.hs)+ scrollTop = vScrollToBeginning $ viewportScroll RegisterViewport+ journalspan = journalDateSpan False j -handleRegisterScreen _ _ = error "event handler called with wrong screen type, should not happen"+rsHandle _ _ = error "event handler called with wrong screen type, should not happen"
Hledger/UI/Theme.hs view
@@ -72,6 +72,7 @@ (borderAttr <> "query", cyan `on` black & bold), (borderAttr <> "depth", yellow `on` black & bold), (borderAttr <> "keys", white `on` black & bold),+ (borderAttr <> "minibuffer", white `on` black & bold), -- ("normal" , black `on` white), ("list" , black `on` white), -- regular list items ("list" <> "selected" , white `on` blue & bold), -- selected list items
Hledger/UI/TransactionScreen.hs view
@@ -1,130 +1,178 @@ -- The transaction screen, showing a single transaction's general journal entry. -{-# LANGUAGE OverloadedStrings, TupleSections #-} -- , FlexibleContexts+{-# LANGUAGE OverloadedStrings, TupleSections, RecordWildCards #-} -- , FlexibleContexts module Hledger.UI.TransactionScreen- (screen+ (transactionScreen+ ,rsSelect ) where --- import Control.Lens ((^.))+import Control.Monad import Control.Monad.IO.Class (liftIO)--- import Data.List--- import Data.List.Split (splitOn)--- import Data.Ord+import Data.List import Data.Monoid--- import Data.Maybe+import qualified Data.Text as T import Data.Time.Calendar (Day)--- import qualified Data.Vector as V-import Graphics.Vty as Vty--- import Safe (headDef, lastDef)+import Graphics.Vty (Event(..),Key(..)) import Brick import Brick.Widgets.List (listMoveTo)--- import Brick.Widgets.Border--- import Brick.Widgets.Border.Style--- import Brick.Widgets.Center--- import Text.Printf+import Brick.Widgets.Border (borderAttr) import Hledger import Hledger.Cli hiding (progname,prognameandversion,green) import Hledger.UI.UIOptions -- import Hledger.UI.Theme import Hledger.UI.UITypes+import Hledger.UI.UIState import Hledger.UI.UIUtils-import qualified Hledger.UI.ErrorScreen as ES (screen)+import Hledger.UI.Editor+import Hledger.UI.ErrorScreen -screen = TransactionScreen{- tsState = ((1,nulltransaction),[(1,nulltransaction)],"")- ,sInitFn = initTransactionScreen- ,sDrawFn = drawTransactionScreen- ,sHandleFn = handleTransactionScreen+transactionScreen :: Screen+transactionScreen = TransactionScreen{+ sInit = tsInit+ ,sDraw = tsDraw+ ,sHandle = tsHandle+ ,tsTransaction = (1,nulltransaction)+ ,tsTransactions = [(1,nulltransaction)]+ ,tsAccount = "" } -initTransactionScreen :: Day -> AppState -> AppState-initTransactionScreen _d st@AppState{aopts=_opts, ajournal=_j, aScreen=_s@TransactionScreen{tsState=_}} = st-initTransactionScreen _ _ = error "init function called with wrong screen type, should not happen"+tsInit :: Day -> Bool -> UIState -> UIState+tsInit _d _reset ui@UIState{aopts=UIOpts{cliopts_=CliOpts{reportopts_=_ropts}}+ ,ajournal=_j+ ,aScreen=TransactionScreen{..}} = ui+tsInit _ _ _ = error "init function called with wrong screen type, should not happen" -drawTransactionScreen :: AppState -> [Widget]-drawTransactionScreen AppState{ -- aopts=_uopts@UIOpts{cliopts_=_copts@CliOpts{reportopts_=_ropts@ReportOpts{query_=querystr}}},- aScreen=TransactionScreen{tsState=((i,t),nts,acct)}} = [ui]+tsDraw :: UIState -> [Widget Name]+tsDraw UIState{aopts=UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}+ ,aScreen=TransactionScreen{+ tsTransaction=(i,t)+ ,tsTransactions=nts+ ,tsAccount=acct}+ ,aMode=mode} =+ case mode of+ Help -> [helpDialog, maincontent]+ -- Minibuffer e -> [minibuffer e, maincontent]+ _ -> [maincontent] where- -- datedesc = show (tdate t) ++ " " ++ tdescription t- 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)++" in "++acct++")")- bottomlabel = borderKeysStr [- ("left", "back")- ,("up/down", "prev/next")- ,("g", "reload")- ,("q", "quit")- ]- ui = Widget Greedy Greedy $ do- render $ defaultLayout toplabel bottomlabel $ str $ showTransactionUnelidedOneLineAmounts t--drawTransactionScreen _ = error "draw function called with wrong screen type, should not happen"--handleTransactionScreen :: AppState -> Vty.Event -> EventM (Next AppState)-handleTransactionScreen st@AppState{- aScreen=s@TransactionScreen{tsState=((i,t),nts,acct)}- ,aopts=UIOpts{cliopts_=CliOpts{reportopts_=ropts}}- ,ajournal=j- } e = do- d <- liftIO getCurrentDay- let- (iprev,tprev) = maybe (i,t) ((i-1),) $ lookup (i-1) nts- (inext,tnext) = maybe (i,t) ((i+1),) $ lookup (i+1) nts- case e of- Vty.EvKey Vty.KEsc [] -> halt st- Vty.EvKey (Vty.KChar 'q') [] -> halt st-- Vty.EvKey (Vty.KChar 'g') [] -> do- d <- liftIO getCurrentDay- ej <- liftIO $ journalReload j -- (ej, changed) <- liftIO $ journalReloadIfChanged copts j- case ej of- Right j' -> do- -- got to redo the register screen's transactions report, to get the latest transactions list for this screen- -- XXX duplicates initRegisterScreen- 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- st' = st{aScreen=s{tsState=((i',t'),numberedts,acct)}}- continue $ reload j' d st'-- Left err -> continue $ screenEnter d ES.screen{esState=err} st-- -- Vty.EvKey (Vty.KChar 'C') [] -> continue $ reload j d $ stToggleCleared st+ maincontent = Widget Greedy Greedy $ do+ render $ defaultLayout toplabel bottomlabel $ str $+ showTransactionUnelidedOneLineAmounts $+ -- (if real_ ropts then filterTransactionPostings (Real True) else id) -- filter postings by --real+ t+ where+ 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)++")")+ <+> (if ignore_assertions_ copts then withAttr (borderAttr <> "query") (str " ignoring balance assertions") else str "")+ where+ togglefilters =+ case concat [+ uiShowClearedStatus $ clearedstatus_ ropts+ ,if real_ ropts then ["real"] else []+ ,if empty_ ropts then [] else ["nonzero"]+ ] of+ [] -> str ""+ fs -> withAttr (borderAttr <> "query") (str $ " " ++ intercalate ", " fs) - Vty.EvKey (Vty.KUp) [] -> continue $ reload j d st{aScreen=s{tsState=((iprev,tprev),nts,acct)}}- Vty.EvKey (Vty.KDown) [] -> continue $ reload j d st{aScreen=s{tsState=((inext,tnext),nts,acct)}}+ bottomlabel = case mode of+ -- Minibuffer ed -> minibuffer ed+ _ -> quickhelp+ where+ quickhelp = borderKeysStr [+ ("?", "help")+ ,("left", "back")+ ,("up/down", "prev/next")+ --,("ESC", "cancel/top")+ -- ,("a", "add")+ ,("E", "editor")+ ,("g", "reload")+ ,("q", "quit")+ ] - Vty.EvKey (Vty.KLeft) [] -> continue st''- where- st'@AppState{aScreen=scr} = popScreen st- st'' = st'{aScreen=rsSetSelectedTransaction (fromIntegral i) scr}+tsDraw _ = error "draw function called with wrong screen type, should not happen" - _ev -> continue st+tsHandle :: UIState -> Event -> EventM Name (Next UIState)+tsHandle ui@UIState{aScreen=s@TransactionScreen{tsTransaction=(i,t)+ ,tsTransactions=nts+ ,tsAccount=acct}+ ,aopts=UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}+ ,ajournal=j+ ,aMode=mode+ }+ ev =+ case mode of+ Help ->+ case ev of+ EvKey (KChar 'q') [] -> halt ui+ _ -> helpHandle ui ev -handleTransactionScreen _ _ = error "event handler called with wrong screen type, should not happen"+ _ -> do+ d <- liftIO getCurrentDay+ 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+ EvKey (KChar 'q') [] -> halt ui+ EvKey KEsc [] -> continue $ resetScreens d ui+ EvKey (KChar c) [] | c `elem` ['?'] -> continue $ setMode Help ui+ EvKey (KChar 'E') [] -> suspendAndResume $ void (runEditor pos f) >> uiReloadJournalIfChanged copts d j ui+ where+ (pos,f) = let GenericSourcePos f l c = tsourcepos t in (Just (l, Just c),f)+ EvKey (KChar 'g') [] -> do+ d <- liftIO getCurrentDay+ (ej, _) <- liftIO $ journalReloadIfChanged copts d j+ case ej of+ Left err -> continue $ screenEnter d errorScreen{esError=err} ui+ 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+ ui' = ui{aScreen=s{tsTransaction=(i',t')+ ,tsTransactions=numberedts+ ,tsAccount=acct}}+ continue $ regenerateScreens j' d ui'+ EvKey (KChar 'I') [] -> continue $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)+ -- if allowing toggling here, we should refresh the txn list from the parent register screen+ -- 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+ EvKey k [] | k `elem` [KUp, KChar 'k'] -> continue $ regenerateScreens j d ui{aScreen=s{tsTransaction=(iprev,tprev)}}+ EvKey k [] | k `elem` [KDown, KChar 'j'] -> continue $ regenerateScreens j d ui{aScreen=s{tsTransaction=(inext,tnext)}}+ EvKey k [] | k `elem` [KLeft, KChar 'h'] -> continue ui''+ where+ ui'@UIState{aScreen=scr} = popScreen ui+ ui'' = ui'{aScreen=rsSelect (fromIntegral i) scr}+ _ -> continue ui -rsSetSelectedTransaction i scr@RegisterScreen{rsState=(l,a)} = scr{rsState=(l',a)}- where l' = listMoveTo (i-1) l-rsSetSelectedTransaction _ scr = scr+tsHandle _ _ = error "event handler called with wrong screen type, should not happen" +-- | Select the nth item on the register screen.+rsSelect i scr@RegisterScreen{..} = scr{rsList=l'}+ where l' = listMoveTo (i-1) rsList+rsSelect _ scr = scr
Hledger/UI/UIOptions.hs view
@@ -5,12 +5,11 @@ module Hledger.UI.UIOptions where+import Data.Default #if !MIN_VERSION_base(4,8,0) import Data.Functor.Compat ((<$>)) #endif import Data.List (intercalate)-import System.Console.CmdArgs-import System.Console.CmdArgs.Explicit import Hledger.Cli hiding (progname,version,prognameandversion) import Hledger.UI.Theme (themeNames)@@ -33,7 +32,7 @@ -- ,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" ,flagNone ["no-elide"] (\opts -> setboolopt "no-elide" opts) "don't compress empty parent accounts on one line"- ,flagNone ["value","V"] (setboolopt "value") "show amounts as their market value in their default valuation commodity (accounts screen)"+ ,flagNone ["value","V"] (setboolopt "value") "show amounts as their current market value in their default valuation commodity (accounts screen)" ] --uimode :: Mode [([Char], [Char])]
+ Hledger/UI/UIState.hs view
@@ -0,0 +1,216 @@+{- | 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@ReportOpts{clearedstatus_=Just Cleared} = ropts{clearedstatus_=Nothing}+ toggleCleared ropts = ropts{clearedstatus_=Just Cleared}++-- | 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@ReportOpts{clearedstatus_=Just Pending} = ropts{clearedstatus_=Nothing}+ togglePending ropts = ropts{clearedstatus_=Just Pending}++-- | 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@ReportOpts{clearedstatus_=Just Uncleared} = ropts{clearedstatus_=Nothing}+ toggleUncleared ropts = ropts{clearedstatus_=Just Uncleared}++-- | 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 historical balances and period balances.+toggleHistorical :: UIState -> UIState+toggleHistorical ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =+ ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{balancetype_=b}}}}+ where+ b | balancetype_ ropts == HistoricalBalance = PeriodChange+ | otherwise = HistoricalBalance++-- | 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}++-- | Toggle the ignoring of balance assertions.+toggleIgnoreBalanceAssertions :: UIState -> UIState+toggleIgnoreBalanceAssertions ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{}}} =+ ui{aopts=uopts{cliopts_=copts{ignore_assertions_=not $ ignore_assertions_ copts}}}++-- | Step through larger report periods, up to all.+growReportPeriod :: Day -> UIState -> UIState+growReportPeriod _d ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =+ ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{period_=periodGrow $ period_ ropts}}}}++-- | Step through smaller report periods, down to a day.+shrinkReportPeriod :: Day -> UIState -> UIState+shrinkReportPeriod d ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =+ ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{period_=periodShrink d $ period_ ropts}}}}++-- | Step the report start/end dates to the next period of same duration.+nextReportPeriod :: DateSpan -> UIState -> UIState+nextReportPeriod journalspan ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts@ReportOpts{period_=p}}}} =+ ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{period_=periodNextIn journalspan p}}}}++-- | Step the report start/end dates to the next period of same duration.+previousReportPeriod :: DateSpan -> UIState -> UIState+previousReportPeriod journalspan ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts@ReportOpts{period_=p}}}} =+ ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{period_=periodPreviousIn journalspan p}}}}++-- | Set the report period.+setReportPeriod :: Period -> UIState -> UIState+setReportPeriod p ui@UIState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =+ ui{aopts=uopts{cliopts_=copts{reportopts_=ropts{period_=p}}}}++-- | 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+ ,clearedstatus_=Nothing+ ,real_=False+ ,query_=""+ --,period_=PeriodAll+ }}}}++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++getDepth :: UIState -> Maybe Int+getDepth UIState{aopts=UIOpts{cliopts_=CliOpts{reportopts_=ropts}}} = depth_ ropts++-- | 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 MinibufferEditor (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+
Hledger/UI/UITypes.hs view
@@ -1,59 +1,161 @@+{- |+Overview:+hledger-ui's UIState holds the currently active screen and any previously visited+screens (and their states).+The brick App delegates all event-handling and rendering+to the UIState's active screen.+Screens have their own screen state, render function, event handler, and app state+update function, so they have full control.++@+Brick.defaultMain brickapp st+ where+ brickapp :: App (UIState) V.Event+ brickapp = App {+ appLiftVtyEvent = id+ , appStartEvent = return+ , appAttrMap = const theme+ , appChooseCursor = showFirstCursor+ , appHandleEvent = \st ev -> sHandle (aScreen st) st ev+ , appDraw = \st -> sDraw (aScreen st) st+ }+ st :: UIState+ st = (sInit s) d+ UIState{+ aopts=uopts'+ ,ajournal=j+ ,aScreen=s+ ,aPrevScreens=prevscrs+ ,aMinibuffer=Nothing+ }+@+-}++{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+ module Hledger.UI.UITypes where import Data.Time.Calendar (Day)-import qualified Graphics.Vty as V+import Graphics.Vty (Event) import Brick-import Brick.Widgets.List (List)+import Brick.Widgets.List+import Brick.Widgets.Edit (Editor)+import Lens.Micro.Platform import Text.Show.Functions () -- import the Show instance for functions. Warning, this also re-exports it import Hledger import Hledger.UI.UIOptions -----------------------------------------------------------------------+instance Show (List n a) where show _ = "<List>"+instance Show (Editor n) where show _ = "<Editor>" --- | hledger-ui's application state. This is part of, but distinct--- from, brick's App.-data AppState = AppState {- aopts :: UIOpts -- ^ the command-line options and query currently in effect- ,ajournal :: Journal -- ^ the journal being viewed- ,aScreen :: Screen -- ^ the currently active screen- ,aPrevScreens :: [Screen] -- ^ previously visited screens, most recent first+-- | hledger-ui's application state. This holds one or more stateful screens.+-- As you navigate through screens, the old ones are saved in a stack.+-- The app can be in one of several modes: normal screen operation,+-- showing a help dialog, entering data in the minibuffer etc.+data UIState = UIState {+ aopts :: UIOpts -- ^ the command-line options and query arguments currently in effect+ ,ajournal :: Journal -- ^ the journal being viewed+ ,aPrevScreens :: [Screen] -- ^ previously visited screens, most recent first+ ,aScreen :: Screen -- ^ the currently active screen+ ,aMode :: Mode -- ^ the currently active mode } deriving (Show) --- | Types of screen available within the app, along with their state.--- Screen types are distinguished by their constructor and their state--- field, which must have unique names.------ This type causes partial functions, so take care.+-- | The mode modifies the screen's rendering and event handling.+-- It resets to Normal when entering a new screen.+data Mode =+ Normal+ | Help+ | Minibuffer (Editor Name)+ deriving (Show,Eq)++-- Ignore the editor when comparing Modes.+instance Eq (Editor n) where _ == _ = True++-- Unique names required for widgets, viewports, cursor locations etc.+data Name =+ HelpDialog+ | MinibufferEditor+ | AccountsViewport+ | AccountsList+ | RegisterViewport+ | RegisterList+ deriving (Ord, Show, Eq)++-- | hledger-ui screen types & instances.+-- Each screen type has generically named initialisation, draw, and event handling functions,+-- and zero or more uniquely named screen state fields, which hold the data for a particular+-- instance of this screen. Note the latter create partial functions, which means that some invalid+-- cases need to be handled, and also that their lenses are traversals, not single-value getters. data Screen = AccountsScreen {- asState :: (List (Int,String,String,[String]), AccountName) -- ^ list widget holding (indent level, full account name, full or short account name to display, rendered amounts);- -- the full name of the currently selected account (or "")- ,sInitFn :: Day -> AppState -> AppState -- ^ function to initialise the screen's state on entry- ,sHandleFn :: AppState -> V.Event -> EventM (Next AppState) -- ^ brick event handler to use for this screen- ,sDrawFn :: AppState -> [Widget] -- ^ brick renderer to use for this screen+ sInit :: Day -> Bool -> UIState -> UIState -- ^ function to initialise or update this screen's state+ ,sDraw :: UIState -> [Widget Name] -- ^ brick renderer for this screen+ ,sHandle :: UIState -> Event -> EventM Name (Next UIState) -- ^ brick event handler for this screen+ -- state fields.These ones have lenses:+ ,_asList :: List Name AccountsScreenItem -- ^ list widget showing account names & balances+ ,_asSelectedAccount :: AccountName -- ^ a backup of the account name from the list widget's selected item (or "") } | RegisterScreen {- rsState :: (List (String,String,String,String,String,Transaction), AccountName)- -- ^ list widget holding (date, description, other accts, change amt, balance amt, and the full transaction);- -- the full name of the account we are showing a register for- ,sInitFn :: Day -> AppState -> AppState- ,sHandleFn :: AppState -> V.Event -> EventM (Next AppState)- ,sDrawFn :: AppState -> [Widget]+ sInit :: Day -> Bool -> UIState -> UIState+ ,sDraw :: UIState -> [Widget Name]+ ,sHandle :: UIState -> Event -> EventM Name (Next UIState)+ --+ ,rsList :: List Name RegisterScreenItem -- ^ list widget showing transactions affecting this account+ ,rsAccount :: AccountName -- ^ the account this register is for+ ,rsForceInclusive :: Bool -- ^ should this register always include subaccount transactions,+ -- even when in flat mode ? (ie because entered from a+ -- depth-clipped accounts screen item) } | TransactionScreen {- tsState :: ((Integer,Transaction), [(Integer,Transaction)], AccountName) -- ^ the (numbered) transaction we are viewing, a numbered list of transactions we can step through, and the account whose register we entered this screen from- ,sInitFn :: Day -> AppState -> AppState- ,sHandleFn :: AppState -> V.Event -> EventM (Next AppState)- ,sDrawFn :: AppState -> [Widget]+ sInit :: Day -> Bool -> UIState -> UIState+ ,sDraw :: UIState -> [Widget Name]+ ,sHandle :: UIState -> Event -> EventM Name (Next UIState)+ --+ ,tsTransaction :: NumberedTransaction -- ^ the transaction we are currently viewing, and its position in the list+ ,tsTransactions :: [NumberedTransaction] -- ^ list of transactions we can step through+ ,tsAccount :: AccountName -- ^ the account whose register we entered this screen from } | ErrorScreen {- esState :: String -- ^ error message to display- ,sInitFn :: Day -> AppState -> AppState- ,sHandleFn :: AppState -> V.Event -> EventM (Next AppState)- ,sDrawFn :: AppState -> [Widget]+ sInit :: Day -> Bool -> UIState -> UIState+ ,sDraw :: UIState -> [Widget Name]+ ,sHandle :: UIState -> Event -> EventM Name (Next UIState)+ --+ ,esError :: String -- ^ error message to show } deriving (Show) -instance Show (List a) where show _ = "<List>"+-- | An item in the accounts screen's list of accounts and balances.+data AccountsScreenItem = AccountsScreenItem {+ asItemIndentLevel :: Int -- ^ indent level+ ,asItemAccountName :: AccountName -- ^ full account name+ ,asItemDisplayAccountName :: AccountName -- ^ full or short account name to display+ ,asItemRenderedAmounts :: [String] -- ^ rendered amounts+ }++-- | An item in the register screen's list of transactions in the current account.+data RegisterScreenItem = RegisterScreenItem {+ rsItemDate :: String -- ^ date+ ,rsItemDescription :: String -- ^ description+ ,rsItemOtherAccounts :: String -- ^ other accounts+ ,rsItemChangeAmount :: String -- ^ the change to the current account from this transaction+ ,rsItemBalanceAmount :: String -- ^ the balance or running total after this transaction+ ,rsItemTransaction :: Transaction -- ^ the full transaction+ }++type NumberedTransaction = (Integer, Transaction)++-- dummy monoid instance needed make lenses work with List fields not common across constructors+--instance Monoid (List n a)+-- where+-- mempty = list "" V.empty 1 -- XXX problem in 0.7, every list requires a unique Name+-- mappend l1 l2 = l1 & listElementsL .~ (l1^.listElementsL <> l2^.listElementsL)++concat <$> mapM makeLenses [+ ''Screen+ ]+
Hledger/UI/UIUtils.hs view
@@ -1,97 +1,135 @@+{- | Rendering & misc. helpers. -}+ {-# LANGUAGE OverloadedStrings #-} -module Hledger.UI.UIUtils (- pushScreen- ,popScreen- ,screenEnter- ,reload- ,getViewportSize- -- ,margin- ,withBorderAttr- ,topBottomBorderWithLabel- ,topBottomBorderWithLabels- ,defaultLayout- ,borderQueryStr- ,borderDepthStr- ,borderKeysStr- --- ,stToggleCleared- ) where+module Hledger.UI.UIUtils+where -import Control.Lens ((^.))--- import Control.Monad--- import Control.Monad.IO.Class--- import Data.Default-import Data.List-import Data.Monoid-import Data.Time.Calendar (Day) import Brick--- import Brick.Widgets.List import Brick.Widgets.Border import Brick.Widgets.Border.Style-import Graphics.Vty as Vty+-- import Brick.Widgets.Center+import Brick.Widgets.Dialog+import Brick.Widgets.Edit+import Data.List+import Data.Monoid+import Graphics.Vty (Event(..),Key(..),Color,Attr,currentAttr)+import Lens.Micro.Platform+import System.Process +import Hledger import Hledger.UI.UITypes-import Hledger.Data.Types (Journal)-import Hledger.UI.UIOptions-import Hledger.Cli.CliOptions-import Hledger.Reports.ReportOptions-import Hledger.Utils (applyN)--- import Hledger.Utils.Debug+import Hledger.UI.UIState -stToggleCleared :: AppState -> AppState-stToggleCleared st@AppState{aopts=uopts@UIOpts{cliopts_=copts@CliOpts{reportopts_=ropts}}} =- st{aopts=uopts{cliopts_=copts{reportopts_=toggleCleared ropts}}} --- | Toggle between showing all and showing only cleared items.-toggleCleared :: ReportOpts -> ReportOpts-toggleCleared ropts = ropts{cleared_=not $ cleared_ ropts}---- | Regenerate the content for the current and previous screens, from a new journal and current date.-reload :: Journal -> Day -> AppState -> AppState-reload j d st@AppState{aScreen=s,aPrevScreens=ss} =- -- clumsy due to entanglement of AppState and Screen.- -- sInitFn 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- st0 = st{ajournal=j, aScreen=first, aPrevScreens=[]}- st1 = (sInitFn first) d st0- st2 = foldl' (\st s -> (sInitFn s) d $ pushScreen s st) st1 rest- in- st2+runInfo = runCommand "hledger-ui --info" >>= waitForProcess+runMan = runCommand "hledger-ui --man" >>= waitForProcess+runHelp = runCommand "hledger-ui --help | less" >>= waitForProcess -pushScreen :: Screen -> AppState -> AppState-pushScreen scr st = st{aPrevScreens=(aScreen st:aPrevScreens st)- ,aScreen=scr- }+-- ui -popScreen :: AppState -> AppState-popScreen st@AppState{aPrevScreens=s:ss} = st{aScreen=s, aPrevScreens=ss}-popScreen st = st+uiShowClearedStatus mc =+ case mc of+ Just Cleared -> ["cleared"]+ Just Pending -> ["pending"]+ Just Uncleared -> ["uncleared"]+ Nothing -> [] --- clearScreens :: AppState -> AppState--- clearScreens st = st{aPrevScreens=[]}+-- | Draw the help dialog, called when help mode is active.+helpDialog :: Widget Name+helpDialog =+ Widget Fixed Fixed $ do+ c <- getContext+ render $+ renderDialog (dialog (Just "Help (?/LEFT/ESC to close)") Nothing (c^.availWidthL)) $ -- (Just (0,[("ok",())]))+ padTopBottom 1 $ padLeftRight 1 $+ vBox [+ hBox [+ padLeftRight 1 $+ vBox [+ str "NAVIGATION"+ ,renderKey ("UP/DOWN/k/j/PGUP/PGDN/HOME/END", "")+ ,str " move selection"+ ,renderKey ("RIGHT/l", "more detail")+ ,renderKey ("LEFT/h", "previous screen")+ ,renderKey ("ESC", "cancel / reset to top")+ ,str " "+ ,str "MISC"+ ,renderKey ("?", "toggle help")+ ,renderKey ("a", "add transaction")+ ,renderKey ("E", "open editor")+ ,renderKey ("g", "reload data")+ ,renderKey ("I", "toggle balance assertions")+ ,renderKey ("q", "quit")+ ,str " "+ ,str "MANUAL"+ ,str "from help dialog:"+ ,renderKey ("t", "text")+ ,renderKey ("m", "man page")+ ,renderKey ("i", "info")+ ]+ ,padLeftRight 1 $+ vBox [+ str "FILTERING"+ ,renderKey ("SHIFT-DOWN/UP", "shrink/grow report period")+ ,renderKey ("SHIFT-RIGHT/LEFT", "next/previous report period")+ ,renderKey ("t", "set report period to today")+ ,str " "+ ,renderKey ("/", "set a filter query")+ ,renderKey ("C", "toggle cleared/all")+ ,renderKey ("U", "toggle uncleared/all")+ ,renderKey ("R", "toggle real/all")+ ,renderKey ("Z", "toggle nonzero/all")+ ,renderKey ("DEL/BS", "remove filters")+ ,str " "+ ,str "accounts screen:"+ ,renderKey ("-+0123456789", "set depth limit")+ ,renderKey ("H", "toggle period balance (shows change) or\nhistorical balance (includes older postings)")+ ,renderKey ("F", "toggle tree (amounts include subaccounts) or\nflat mode (amounts exclude subaccounts\nexcept when account is depth-clipped)")+ ,str " "+ ,str "register screen:"+ ,renderKey ("H", "toggle period or historical total")+ ,renderKey ("F", "toggle subaccount transaction inclusion\n(and tree/flat mode)")+ ]+ ]+-- ,vBox [+-- str " "+-- ,hCenter $ padLeftRight 1 $+-- hCenter (str "MANUAL")+-- <=>+-- hCenter (hBox [+-- renderKey ("t", "text")+-- ,str " "+-- ,renderKey ("m", "man page")+-- ,str " "+-- ,renderKey ("i", "info")+-- ])+-- ]+ ]+ where+ renderKey (key,desc) = withAttr (borderAttr <> "keys") (str key) <+> str " " <+> str desc --- | Enter a new screen, saving the old screen & state in the--- navigation history and initialising the new screen's state.-screenEnter :: Day -> Screen -> AppState -> AppState-screenEnter d scr st = (sInitFn scr) d $- pushScreen scr- st+-- | Event handler used when help mode is active.+helpHandle :: UIState -> Event -> EventM Name (Next UIState)+helpHandle ui ev =+ case ev of+ EvKey k [] | k `elem` [KEsc, KLeft, KChar 'h', KChar '?'] -> continue $ setMode Normal ui+ EvKey (KChar 't') [] -> suspendAndResume $ runHelp >> return ui'+ EvKey (KChar 'm') [] -> suspendAndResume $ runMan >> return ui'+ EvKey (KChar 'i') [] -> suspendAndResume $ runInfo >> return ui'+ _ -> continue ui+ where+ ui' = setMode Normal ui --- | In the EventM monad, get the named current viewport's width and height,--- or (0,0) if the named viewport is not found.-getViewportSize :: Name -> EventM (Int,Int)-getViewportSize name = do- mvp <- lookupViewport name- let (w,h) = case mvp of- Just vp -> vp ^. vpSize- Nothing -> (0,0)- -- liftIO $ putStrLn $ show (w,h)- return (w,h)+-- | Draw the minibuffer.+minibuffer :: Editor Name -> Widget Name+minibuffer ed =+ forceAttr (borderAttr <> "minibuffer") $+ hBox $+ [txt "filter: ", renderEditor True ed] +-- | Wrap a widget in the default hledger-ui screen layout.+defaultLayout :: Widget Name -> Widget Name -> Widget Name -> Widget Name defaultLayout toplabel bottomlabel = topBottomBorderWithLabels (str " "<+>toplabel<+>str " ") (str " "<+>bottomlabel<+>str " ") . margin 1 0 Nothing@@ -99,6 +137,43 @@ -- padLeftRight 1 -- XXX should reduce inner widget's width by 2, but doesn't -- "the layout adjusts... if you use the core combinators" +borderQueryStr :: String -> Widget Name+borderQueryStr "" = str ""+borderQueryStr qry = str " matching " <+> withAttr (borderAttr <> "query") (str qry)++borderDepthStr :: Maybe Int -> Widget Name+borderDepthStr Nothing = str ""+borderDepthStr (Just d) = str " to " <+> withAttr (borderAttr <> "query") (str $ "depth "++show d)++borderPeriodStr :: String -> Period -> Widget Name+borderPeriodStr _ PeriodAll = str ""+borderPeriodStr preposition p = str (" "++preposition++" ") <+> withAttr (borderAttr <> "query") (str $ showPeriod p)++borderKeysStr :: [(String,String)] -> Widget Name+borderKeysStr = borderKeysStr' . map (\(a,b) -> (a, str b))++borderKeysStr' :: [(String,Widget Name)] -> Widget Name+borderKeysStr' keydescs =+ hBox $+ intersperse sep $+ [withAttr (borderAttr <> "keys") (str keys) <+> str ":" <+> desc | (keys, desc) <- keydescs]+ where+ -- sep = str " | "+ sep = str " "++-- temporary shenanigans:++-- | Convert the special account name "*" (from balance report with depth limit 0) to something clearer.+replaceHiddenAccountsNameWith :: AccountName -> AccountName -> AccountName+replaceHiddenAccountsNameWith anew a | a == hiddenAccountsName = anew+ | a == "*" = anew+ | otherwise = a++hiddenAccountsName = "..." -- for now++-- generic++topBottomBorderWithLabel :: Widget Name -> Widget Name -> Widget Name topBottomBorderWithLabel label = \wrapped -> Widget Greedy Greedy $ do c <- getContext@@ -115,6 +190,7 @@ <=> hBorder +topBottomBorderWithLabels :: Widget Name -> Widget Name -> Widget Name -> Widget Name topBottomBorderWithLabels toplabel bottomlabel = \wrapped -> Widget Greedy Greedy $ do c <- getContext@@ -132,6 +208,7 @@ hBorderWithLabel bottomlabel -- XXX should be equivalent to the above, but isn't (page down goes offscreen)+_topBottomBorderWithLabel2 :: Widget Name -> Widget Name -> Widget Name _topBottomBorderWithLabel2 label = \wrapped -> let debugmsg = "" in hBorderWithLabel (label <+> str debugmsg)@@ -145,8 +222,8 @@ -- thickness, using the current background colour or the specified -- colour. -- XXX May disrupt border style of inner widgets.--- XXX Should reduce the available size visible to inner widget, but doesn't seem to (cf drawRegisterScreen2).-margin :: Int -> Int -> Maybe Color -> Widget -> Widget+-- XXX Should reduce the available size visible to inner widget, but doesn't seem to (cf rsDraw2).+margin :: Int -> Int -> Maybe Color -> Widget Name -> Widget Name margin h v mcolour = \w -> Widget Greedy Greedy $ do c <- getContext@@ -165,27 +242,6 @@ -- withBorderStyle (borderStyleFromChar ' ') . -- applyN n border +withBorderAttr :: Attr -> Widget Name -> Widget Name withBorderAttr attr = updateAttrMap (applyAttrMappings [(borderAttr, attr)])---- _ui = vCenter $ vBox [ hCenter box--- , str " "--- , hCenter $ str "Press Esc to exit."--- ]--borderQueryStr :: String -> Widget-borderQueryStr "" = str ""-borderQueryStr qry = str " matching " <+> withAttr (borderAttr <> "query") (str qry)--borderDepthStr :: Maybe Int -> Widget-borderDepthStr Nothing = str ""-borderDepthStr (Just d) = str " to " <+> withAttr (borderAttr <> "depth") (str $ "depth "++show d)--borderKeysStr :: [(String,String)] -> Widget-borderKeysStr keydescs =- hBox $- intersperse sep $- [withAttr (borderAttr <> "keys") (str keys) <+> str ":" <+> str desc | (keys, desc) <- keydescs]- where- -- sep = str " | "- sep = str " "
+ doc/hledger-ui.1 view
@@ -0,0 +1,453 @@++.TH "hledger\-ui" "1" "October 2016" "hledger\-ui 1.0" "hledger User Manuals"++++.SH NAME+.PP+hledger\-ui \- curses\-style interface for the hledger accounting tool+.SH SYNOPSIS+.PP+\f[C]hledger\-ui\ [OPTIONS]\ [QUERYARGS]\f[]+.PD 0+.P+.PD+\f[C]hledger\ ui\ \-\-\ [OPTIONS]\ [QUERYARGS]\f[]+.SH DESCRIPTION+.PP+hledger is a cross\-platform program for tracking money, time, or any+other commodity, using double\-entry accounting and a simple, editable+file format.+hledger is inspired by and largely compatible with ledger(1).+.PP+hledger\-ui is hledger\[aq]s curses\-style interface, providing an+efficient full\-window text UI for viewing accounts and transactions,+and some limited data entry capability.+It is easier than hledger\[aq]s command\-line interface, and sometimes+quicker and more convenient than the web interface.+.PP+Like hledger, it reads data from one or more files in hledger journal,+timeclock, timedot, or CSV format specified with \f[C]\-f\f[], or+\f[C]$LEDGER_FILE\f[], or \f[C]$HOME/.hledger.journal\f[] (on windows,+perhaps \f[C]C:/Users/USER/.hledger.journal\f[]).+For more about this see hledger(1), hledger_journal(5) etc.+.SH OPTIONS+.PP+Note: if invoking hledger\-ui as a hledger subcommand, write+\f[C]\-\-\f[] before options as shown above.+.PP+Any QUERYARGS are interpreted as a hledger search query which filters+the data.+.TP+.B \f[C]\-\-flat\f[]+show full account names, unindented+.RS+.RE+.TP+.B \f[C]\-\-register=ACCTREGEX\f[]+start in the (first) matched account\[aq]s register screen+.RS+.RE+.TP+.B \f[C]\-\-theme=default|terminal|greenterm\f[]+use this custom display theme+.RS+.RE+.TP+.B \f[C]\-V\ \-\-value\f[]+show amounts as their current market value in their default valuation+commodity (accounts screen only)+.RS+.RE+.PP+hledger general options:+.TP+.B \f[C]\-h\f[]+show general usage (or after COMMAND, the command\[aq]s usage)+.RS+.RE+.TP+.B \f[C]\-\-help\f[]+show the current program\[aq]s manual as plain text (or after an add\-on+COMMAND, the add\-on\[aq]s manual)+.RS+.RE+.TP+.B \f[C]\-\-man\f[]+show the current program\[aq]s manual with man+.RS+.RE+.TP+.B \f[C]\-\-info\f[]+show the current program\[aq]s manual with info+.RS+.RE+.TP+.B \f[C]\-\-version\f[]+show version+.RS+.RE+.TP+.B \f[C]\-\-debug[=N]\f[]+show debug output (levels 1\-9, default: 1)+.RS+.RE+.TP+.B \f[C]\-f\ FILE\ \-\-file=FILE\f[]+use a different input file.+For stdin, use \-+.RS+.RE+.TP+.B \f[C]\-\-rules\-file=RULESFILE\f[]+Conversion rules file to use when reading CSV (default: FILE.rules)+.RS+.RE+.TP+.B \f[C]\-\-alias=OLD=NEW\f[]+display accounts named OLD as NEW+.RS+.RE+.TP+.B \f[C]\-I\ \-\-ignore\-assertions\f[]+ignore any failing balance assertions in the journal+.RS+.RE+.PP+hledger reporting options:+.TP+.B \f[C]\-b\ \-\-begin=DATE\f[]+include postings/txns on or after this date+.RS+.RE+.TP+.B \f[C]\-e\ \-\-end=DATE\f[]+include postings/txns before this date+.RS+.RE+.TP+.B \f[C]\-D\ \-\-daily\f[]+multiperiod/multicolumn report by day+.RS+.RE+.TP+.B \f[C]\-W\ \-\-weekly\f[]+multiperiod/multicolumn report by week+.RS+.RE+.TP+.B \f[C]\-M\ \-\-monthly\f[]+multiperiod/multicolumn report by month+.RS+.RE+.TP+.B \f[C]\-Q\ \-\-quarterly\f[]+multiperiod/multicolumn report by quarter+.RS+.RE+.TP+.B \f[C]\-Y\ \-\-yearly\f[]+multiperiod/multicolumn report by year+.RS+.RE+.TP+.B \f[C]\-p\ \-\-period=PERIODEXP\f[]+set start date, end date, and/or reporting interval all at once+(overrides the flags above)+.RS+.RE+.TP+.B \f[C]\-\-date2\f[]+show, and match with \-b/\-e/\-p/date:, secondary dates instead+.RS+.RE+.TP+.B \f[C]\-C\ \-\-cleared\f[]+include only cleared postings/txns+.RS+.RE+.TP+.B \f[C]\-\-pending\f[]+include only pending postings/txns+.RS+.RE+.TP+.B \f[C]\-U\ \-\-uncleared\f[]+include only uncleared (and pending) postings/txns+.RS+.RE+.TP+.B \f[C]\-R\ \-\-real\f[]+include only non\-virtual postings+.RS+.RE+.TP+.B \f[C]\-\-depth=N\f[]+hide accounts/postings deeper than N+.RS+.RE+.TP+.B \f[C]\-E\ \-\-empty\f[]+show items with zero amount, normally hidden+.RS+.RE+.TP+.B \f[C]\-B\ \-\-cost\f[]+show amounts in their cost price\[aq]s commodity+.RS+.RE+.TP+.B \f[C]\-\-pivot\ TAG\f[]+will transform the journal before any other processing by replacing the+account name of every posting having the tag TAG with content VALUE by+the account name "TAG:VALUE".+.RS+.RE+The TAG will only match if it is a full\-length match.+The pivot will only happen if the TAG is on a posting, not if it is on+the transaction.+If the tag value is a multi:level:account:name the new account name will+be "TAG:multi:level:account:name".+.RS+.RE+.TP+.B \f[C]\-\-anon\f[]+show anonymized accounts and payees+.RS+.RE+.SH KEYS+.PP+\f[C]?\f[] shows a help dialog listing all keys.+(Some of these also appear in the quick help at the bottom of each+screen.) Press \f[C]?\f[] again (or \f[C]ESCAPE\f[], or \f[C]LEFT\f[])+to close it.+The following keys work on most screens:+.PP+The cursor keys navigate: \f[C]right\f[] (or \f[C]enter\f[]) goes+deeper, \f[C]left\f[] returns to the previous screen,+\f[C]up\f[]/\f[C]down\f[]/\f[C]page\ up\f[]/\f[C]page\ down\f[]/\f[C]home\f[]/\f[C]end\f[]+move up and down through lists.+Vi\-style \f[C]h\f[]/\f[C]j\f[]/\f[C]k\f[]/\f[C]l\f[] movement keys are+also supported.+A tip: movement speed is limited by your keyboard repeat rate, to move+faster you may want to adjust it.+(If you\[aq]re on a mac, the Karabiner app is one way to do that.)+.PP+With shift pressed, the cursor keys adjust the report period, limiting+the transactions to be shown (by default, all are shown).+\f[C]shift\-down/up\f[] steps downward and upward through these standard+report period durations: year, quarter, month, week, day.+Then, \f[C]shift\-left/right\f[] moves to the previous/next period.+\f[C]t\f[] sets the report period to today.+(To set a non\-standard period, you can use \f[C]/\f[] and a+\f[C]date:\f[] query).+.PP+\f[C]/\f[] lets you set a general filter query limiting the data shown,+using the same query terms as in hledger and hledger\-web.+While editing the query, you can use CTRL\-a/e/d/k, BS, cursor keys;+press \f[C]ENTER\f[] to set it, or \f[C]ESCAPE\f[]to cancel.+There are also keys for quickly adjusting some common filters like+account depth and cleared/uncleared (see below).+\f[C]BACKSPACE\f[] or \f[C]DELETE\f[] removes all filters, showing all+transactions.+.PP+\f[C]ESCAPE\f[] removes all filters and jumps back to the top screen.+Or, it cancels a minibuffer edit or help dialog in progress.+.PP+\f[C]g\f[] reloads from the data file(s) and updates the current screen+and any previous screens.+(With large files, this could cause a noticeable pause.)+.PP+\f[C]I\f[] toggles balance assertion checking.+Disabling balance assertions temporarily can be useful for+troubleshooting.+.PP+\f[C]a\f[] runs command\-line hledger\[aq]s add command, and reloads the+updated file.+This allows some basic data entry.+.PP+\f[C]E\f[] runs $HLEDGER_UI_EDITOR, or $EDITOR, or a default+(\f[C]emacsclient\ \-a\ ""\ \-nw\f[]) on the journal file.+With some editors (emacs, vi), the cursor will be positioned at the+current transaction when invoked from the register and transaction+screens, and at the error location (if possible) when invoked from the+error screen.+.PP+\f[C]q\f[] quits the application.+.PP+Additional screen\-specific keys are described below.+.SH SCREENS+.SS Accounts screen+.PP+This is normally the first screen displayed.+It lists accounts and their balances, like hledger\[aq]s balance+command.+By default, it shows all accounts and their latest ending balances+(including the balances of subaccounts).+if you specify a query on the command line, it shows just the matched+accounts and the balances from matched transactions.+.PP+Account names are normally indented to show the hierarchy (tree mode).+To see less detail, set a depth limit by pressing a number key,+\f[C]1\f[] to \f[C]9\f[].+\f[C]0\f[] shows even less detail, collapsing all accounts to a single+total.+\f[C]\-\f[] and \f[C]+\f[] (or \f[C]=\f[]) decrease and increase the+depth limit.+To remove the depth limit, set it higher than the maximum account depth,+or press \f[C]ESCAPE\f[].+.PP+\f[C]F\f[] toggles flat mode, in which accounts are shown as a flat+list, with their full names.+In this mode, account balances exclude subaccounts, except for accounts+at the depth limit (as with hledger\[aq]s balance command).+.PP+\f[C]H\f[] toggles between showing historical balances or period+balances.+Historical balances (the default) are ending balances at the end of the+report period, taking into account all transactions before that date+(filtered by the filter query if any), including transactions before the+start of the report period.+In other words, historical balances are what you would see on a bank+statement for that account (unless disturbed by a filter query).+Period balances ignore transactions before the report start date, so+they show the change in balance during the report period.+They are more useful eg when viewing a time log.+.PP+\f[C]C\f[] toggles cleared mode, in which uncleared transactions and+postings are not shown.+\f[C]U\f[] toggles uncleared mode, in which only uncleared+transactions/postings are shown.+.PP+\f[C]R\f[] toggles real mode, in which virtual postings are ignored.+.PP+\f[C]Z\f[] toggles nonzero mode, in which only accounts with nonzero+balances are shown (hledger\-ui shows zero items by default, unlike+command\-line hledger).+.PP+Press \f[C]right\f[] or \f[C]enter\f[] to view an account\[aq]s+transactions register.+.SS Register screen+.PP+This screen shows the transactions affecting a particular account, like+a check register.+Each line represents one transaction and shows:+.IP \[bu] 2+the other account(s) involved, in abbreviated form.+(If there are both real and virtual postings, it shows only the accounts+affected by real postings.)+.IP \[bu] 2+the overall change to the current account\[aq]s balance; positive for an+inflow to this account, negative for an outflow.+.IP \[bu] 2+the running historical total or period total for the current account,+after the transaction.+This can be toggled with \f[C]H\f[].+Similar to the accounts screen, the historical total is affected by+transactions (filtered by the filter query) before the report start+date, while the period total is not.+If the historical total is not disturbed by a filter query, it will be+the running historical balance you would see on a bank register for the+current account.+.PP+If the accounts screen was in tree mode, the register screen will+include transactions from both the current account and its subaccounts.+If the accounts screen was in flat mode, and a non\-depth\-clipped+account was selected, the register screen will exclude transactions from+subaccounts.+In other words, the register always shows the transactions responsible+for the period balance shown on the accounts screen.+As on the accounts screen, this can be toggled with \f[C]F\f[].+.PP+\f[C]C\f[] toggles cleared mode, in which uncleared transactions and+postings are not shown.+\f[C]U\f[] toggles uncleared mode, in which only uncleared+transactions/postings are shown.+.PP+\f[C]R\f[] toggles real mode, in which virtual postings are ignored.+.PP+\f[C]Z\f[] toggles nonzero mode, in which only transactions posting a+nonzero change are shown (hledger\-ui shows zero items by default,+unlike command\-line hledger).+.PP+Press \f[C]right\f[] (or \f[C]enter\f[]) to view the selected+transaction in detail.+.SS Transaction screen+.PP+This screen shows a single transaction, as a general journal entry,+similar to hledger\[aq]s print command and journal format+(hledger_journal(5)).+.PP+The transaction\[aq]s date(s) and any cleared flag, transaction code,+description, comments, along with all of its account postings are shown.+Simple transactions have two postings, but there can be more (or in+certain cases, fewer).+.PP+\f[C]up\f[] and \f[C]down\f[] will step through all transactions listed+in the previous account register screen.+In the title bar, the numbers in parentheses show your position within+that account register.+They will vary depending on which account register you came from+(remember most transactions appear in multiple account registers).+The #N number preceding them is the transaction\[aq]s position within+the complete unfiltered journal, which is a more stable id (at least+until the next reload).+.SS Error screen+.PP+This screen will appear if there is a problem, such as a parse error,+when you press g to reload.+Once you have fixed the problem, press g again to reload and resume+normal operation.+(Or, you can press escape to cancel the reload attempt.)+.SH ENVIRONMENT+.PP+\f[B]COLUMNS\f[] The screen width to use.+Default: the full terminal width.+.PP+\f[B]LEDGER_FILE\f[] The journal file path when not specified with+\f[C]\-f\f[].+Default: \f[C]~/.hledger.journal\f[] (on windows, perhaps+\f[C]C:/Users/USER/.hledger.journal\f[]).+.SH FILES+.PP+Reads data from one or more files in hledger journal, timeclock,+timedot, or CSV format specified with \f[C]\-f\f[], or+\f[C]$LEDGER_FILE\f[], or \f[C]$HOME/.hledger.journal\f[] (on windows,+perhaps \f[C]C:/Users/USER/.hledger.journal\f[]).+.SH BUGS+.PP+The need to precede options with \f[C]\-\-\f[] when invoked from hledger+is awkward.+.PP+\f[C]\-f\-\f[] doesn\[aq]t work (hledger\-ui can\[aq]t read from stdin).+.PP+\f[C]\-V\f[] affects only the accounts screen.+.PP+When you press \f[C]g\f[], the current and all previous screens are+regenerated, which may cause a noticeable pause.+Also there is no visual indication that this is in progress.+.PP+The register screen\[aq]s switching between historic balance and running+total based on query arguments may be confusing, and there is no column+heading to indicate which is being displayed.+++.SH "REPORTING BUGS"+Report bugs at http://bugs.hledger.org+(or on the #hledger IRC channel or hledger mail list)++.SH AUTHORS+Simon Michael <simon@joyful.com> and contributors++.SH COPYRIGHT++Copyright (C) 2007-2016 Simon Michael.+.br+Released under GNU GPL v3 or later.++.SH SEE ALSO+hledger(1), hledger\-ui(1), hledger\-web(1), hledger\-api(1),+hledger_csv(5), hledger_journal(5), hledger_timeclock(5), hledger_timedot(5),+ledger(1)++http://hledger.org
+ doc/hledger-ui.1.info view
@@ -0,0 +1,367 @@+This is hledger-ui/doc/hledger-ui.1.info, produced by makeinfo version+4.8 from stdin.+++File: hledger-ui.1.info, Node: Top, Up: (dir)++hledger-ui(1) hledger-ui 1.0+****************************++hledger-ui is hledger's curses-style interface, providing an efficient+full-window text UI for viewing accounts and transactions, and some+limited data entry capability. It is easier than hledger's command-line+interface, and sometimes quicker and more convenient than the web+interface.++ Like hledger, it reads data from one or more files in hledger+journal, timeclock, timedot, or CSV format specified with `-f', or+`$LEDGER_FILE', or `$HOME/.hledger.journal' (on windows, perhaps+`C:/Users/USER/.hledger.journal'). For more about this see hledger(1),+hledger_journal(5) etc.++* Menu:++* OPTIONS::+* KEYS::+* SCREENS::+++File: hledger-ui.1.info, Node: OPTIONS, Next: KEYS, Prev: Top, Up: Top++1 OPTIONS+*********++Note: if invoking hledger-ui as a hledger subcommand, write `--' before+options as shown above.++ Any QUERYARGS are interpreted as a hledger search query which filters+the data.++`--flat'+ show full account names, unindented++`--register=ACCTREGEX'+ start in the (first) matched account's register screen++`--theme=default|terminal|greenterm'+ use this custom display theme++`-V --value'+ show amounts as their current market value in their default+ valuation commodity (accounts screen only)++ hledger general options:++`-h'+ show general usage (or after COMMAND, the command's usage)++`--help'+ show the current program's manual as plain text (or after an add-on+ COMMAND, the add-on's manual)++`--man'+ show the current program's manual with man++`--info'+ show the current program's manual with info++`--version'+ show version++`--debug[=N]'+ show debug output (levels 1-9, default: 1)++`-f FILE --file=FILE'+ use a different input file. For stdin, use -++`--rules-file=RULESFILE'+ Conversion rules file to use when reading CSV (default: FILE.rules)++`--alias=OLD=NEW'+ display accounts named OLD as NEW++`-I --ignore-assertions'+ ignore any failing balance assertions in the journal++ hledger reporting options:++`-b --begin=DATE'+ include postings/txns on or after this date++`-e --end=DATE'+ include postings/txns before this date++`-D --daily'+ multiperiod/multicolumn report by day++`-W --weekly'+ multiperiod/multicolumn report by week++`-M --monthly'+ multiperiod/multicolumn report by month++`-Q --quarterly'+ multiperiod/multicolumn report by quarter++`-Y --yearly'+ multiperiod/multicolumn report by year++`-p --period=PERIODEXP'+ set start date, end date, and/or reporting interval all at once+ (overrides the flags above)++`--date2'+ show, and match with -b/-e/-p/date:, secondary dates instead++`-C --cleared'+ include only cleared postings/txns++`--pending'+ include only pending postings/txns++`-U --uncleared'+ include only uncleared (and pending) postings/txns++`-R --real'+ include only non-virtual postings++`--depth=N'+ hide accounts/postings deeper than N++`-E --empty'+ show items with zero amount, normally hidden++`-B --cost'+ show amounts in their cost price's commodity++`--pivot TAG'+ will transform the journal before any other processing by+ replacing the account name of every posting having the tag TAG+ with content VALUE by the account name "TAG:VALUE". The TAG will+ only match if it is a full-length match. The pivot will only+ happen if the TAG is on a posting, not if it is on the transaction.+ If the tag value is a multi:level:account:name the new account+ name will be "TAG:multi:level:account:name".++`--anon'+ show anonymized accounts and payees+++File: hledger-ui.1.info, Node: KEYS, Next: SCREENS, Prev: OPTIONS, Up: Top++2 KEYS+******++`?' shows a help dialog listing all keys. (Some of these also appear in+the quick help at the bottom of each screen.) Press `?' again (or+`ESCAPE', or `LEFT') to close it. The following keys work on most+screens:++ The cursor keys navigate: `right' (or `enter') goes deeper, `left'+returns to the previous screen, `up'/`down'/`page up'/`page+down'/`home'/`end' move up and down through lists. Vi-style+`h'/`j'/`k'/`l' movement keys are also supported. A tip: movement speed+is limited by your keyboard repeat rate, to move faster you may want to+adjust it. (If you're on a mac, the Karabiner app is one way to do+that.)++ With shift pressed, the cursor keys adjust the report period,+limiting the transactions to be shown (by default, all are shown).+`shift-down/up' steps downward and upward through these standard report+period durations: year, quarter, month, week, day. Then,+`shift-left/right' moves to the previous/next period. `t' sets the+report period to today. (To set a non-standard period, you can use `/'+and a `date:' query).++ `/' lets you set a general filter query limiting the data shown,+using the same query terms as in hledger and hledger-web. While editing+the query, you can use CTRL-a/e/d/k, BS, cursor keys; press `ENTER' to+set it, or `ESCAPE'to cancel. There are also keys for quickly adjusting+some common filters like account depth and cleared/uncleared (see+below). `BACKSPACE' or `DELETE' removes all filters, showing all+transactions.++ `ESCAPE' removes all filters and jumps back to the top screen. Or,+it cancels a minibuffer edit or help dialog in progress.++ `g' reloads from the data file(s) and updates the current screen and+any previous screens. (With large files, this could cause a noticeable+pause.)++ `I' toggles balance assertion checking. Disabling balance assertions+temporarily can be useful for troubleshooting.++ `a' runs command-line hledger's add command, and reloads the updated+file. This allows some basic data entry.++ `E' runs $HLEDGER_UI_EDITOR, or $EDITOR, or a default (`emacsclient+-a "" -nw') on the journal file. With some editors (emacs, vi), the+cursor will be positioned at the current transaction when invoked from+the register and transaction screens, and at the error location (if+possible) when invoked from the error screen.++ `q' quits the application.++ Additional screen-specific keys are described below.+++File: hledger-ui.1.info, Node: SCREENS, Prev: KEYS, Up: Top++3 SCREENS+*********++* Menu:++* Accounts screen::+* Register screen::+* Transaction screen::+* Error screen::+++File: hledger-ui.1.info, Node: Accounts screen, Next: Register screen, Up: SCREENS++3.1 Accounts screen+===================++This is normally the first screen displayed. It lists accounts and their+balances, like hledger's balance command. By default, it shows all+accounts and their latest ending balances (including the balances of+subaccounts). if you specify a query on the command line, it shows just+the matched accounts and the balances from matched transactions.++ Account names are normally indented to show the hierarchy (tree+mode). To see less detail, set a depth limit by pressing a number key,+`1' to `9'. `0' shows even less detail, collapsing all accounts to a+single total. `-' and `+' (or `=') decrease and increase the depth+limit. To remove the depth limit, set it higher than the maximum+account depth, or press `ESCAPE'.++ `F' toggles flat mode, in which accounts are shown as a flat list,+with their full names. In this mode, account balances exclude+subaccounts, except for accounts at the depth limit (as with hledger's+balance command).++ `H' toggles between showing historical balances or period balances.+Historical balances (the default) are ending balances at the end of the+report period, taking into account all transactions before that date+(filtered by the filter query if any), including transactions before the+start of the report period. In other words, historical balances are what+you would see on a bank statement for that account (unless disturbed by+a filter query). Period balances ignore transactions before the report+start date, so they show the change in balance during the report period.+They are more useful eg when viewing a time log.++ `C' toggles cleared mode, in which uncleared transactions and+postings are not shown. `U' toggles uncleared mode, in which only+uncleared transactions/postings are shown.++ `R' toggles real mode, in which virtual postings are ignored.++ `Z' toggles nonzero mode, in which only accounts with nonzero+balances are shown (hledger-ui shows zero items by default, unlike+command-line hledger).++ Press `right' or `enter' to view an account's transactions register.+++File: hledger-ui.1.info, Node: Register screen, Next: Transaction screen, Prev: Accounts screen, Up: SCREENS++3.2 Register screen+===================++This screen shows the transactions affecting a particular account, like+a check register. Each line represents one transaction and shows:++ * the other account(s) involved, in abbreviated form. (If there are+ both real and virtual postings, it shows only the accounts+ affected by real postings.)++ * the overall change to the current account's balance; positive for+ an inflow to this account, negative for an outflow.++ * the running historical total or period total for the current+ account, after the transaction. This can be toggled with `H'.+ Similar to the accounts screen, the historical total is affected+ by transactions (filtered by the filter query) before the report+ start date, while the period total is not. If the historical total+ is not disturbed by a filter query, it will be the running+ historical balance you would see on a bank register for the+ current account.+++ If the accounts screen was in tree mode, the register screen will+include transactions from both the current account and its subaccounts.+If the accounts screen was in flat mode, and a non-depth-clipped account+was selected, the register screen will exclude transactions from+subaccounts. In other words, the register always shows the transactions+responsible for the period balance shown on the accounts screen. As on+the accounts screen, this can be toggled with `F'.++ `C' toggles cleared mode, in which uncleared transactions and+postings are not shown. `U' toggles uncleared mode, in which only+uncleared transactions/postings are shown.++ `R' toggles real mode, in which virtual postings are ignored.++ `Z' toggles nonzero mode, in which only transactions posting a+nonzero change are shown (hledger-ui shows zero items by default, unlike+command-line hledger).++ Press `right' (or `enter') to view the selected transaction in+detail.+++File: hledger-ui.1.info, Node: Transaction screen, Next: Error screen, Prev: Register screen, Up: SCREENS++3.3 Transaction screen+======================++This screen shows a single transaction, as a general journal entry,+similar to hledger's print command and journal format+(hledger_journal(5)).++ The transaction's date(s) and any cleared flag, transaction code,+description, comments, along with all of its account postings are shown.+Simple transactions have two postings, but there can be more (or in+certain cases, fewer).++ `up' and `down' will step through all transactions listed in the+previous account register screen. In the title bar, the numbers in+parentheses show your position within that account register. They will+vary depending on which account register you came from (remember most+transactions appear in multiple account registers). The #N number+preceding them is the transaction's position within the complete+unfiltered journal, which is a more stable id (at least until the next+reload).+++File: hledger-ui.1.info, Node: Error screen, Prev: Transaction screen, Up: SCREENS++3.4 Error screen+================++This screen will appear if there is a problem, such as a parse error,+when you press g to reload. Once you have fixed the problem, press g+again to reload and resume normal operation. (Or, you can press escape+to cancel the reload attempt.)++++Tag Table:+Node: Top88+Node: OPTIONS823+Ref: #options922+Node: KEYS3786+Ref: #keys3883+Node: SCREENS6284+Ref: #screens6371+Node: Accounts screen6461+Ref: #accounts-screen6591+Node: Register screen8629+Ref: #register-screen8786+Node: Transaction screen10674+Ref: #transaction-screen10834+Node: Error screen11701+Ref: #error-screen11825++End Tag Table
+ doc/hledger-ui.1.txt view
@@ -0,0 +1,351 @@++hledger-ui(1) hledger User Manuals hledger-ui(1)++++NAME+ hledger-ui - curses-style interface for the hledger accounting tool++SYNOPSIS+ hledger-ui [OPTIONS] [QUERYARGS]+ hledger ui -- [OPTIONS] [QUERYARGS]++DESCRIPTION+ hledger is a cross-platform program for tracking money, time, or any+ other commodity, using double-entry accounting and a simple, editable+ file format. hledger is inspired by and largely compatible with+ ledger(1).++ hledger-ui is hledger's curses-style interface, providing an efficient+ full-window text UI for viewing accounts and transactions, and some+ limited data entry capability. It is easier than hledger's com-+ mand-line interface, and sometimes quicker and more convenient than the+ web interface.++ Like hledger, it reads data from one or more files in hledger journal,+ timeclock, timedot, or CSV format specified with -f, or $LEDGER_FILE,+ or $HOME/.hledger.journal (on windows, perhaps+ C:/Users/USER/.hledger.journal). For more about this see hledger(1),+ hledger_journal(5) etc.++OPTIONS+ Note: if invoking hledger-ui as a hledger subcommand, write -- before+ options as shown above.++ Any QUERYARGS are interpreted as a hledger search query which filters+ the data.++ --flat show full account names, unindented++ --register=ACCTREGEX+ start in the (first) matched account's register screen++ --theme=default|terminal|greenterm+ use this custom display theme++ -V --value+ show amounts as their current market value in their default val-+ uation commodity (accounts screen only)++ hledger general options:++ -h show general usage (or after COMMAND, the command's usage)++ --help show the current program's manual as plain text (or after an+ add-on COMMAND, the add-on's manual)++ --man show the current program's manual with man++ --info show the current program's manual with info++ --version+ show version++ --debug[=N]+ show debug output (levels 1-9, default: 1)++ -f FILE --file=FILE+ use a different input file. For stdin, use -++ --rules-file=RULESFILE+ Conversion rules file to use when reading CSV (default:+ FILE.rules)++ --alias=OLD=NEW+ display accounts named OLD as NEW++ -I --ignore-assertions+ ignore any failing balance assertions in the journal++ hledger reporting options:++ -b --begin=DATE+ include postings/txns on or after this date++ -e --end=DATE+ include postings/txns before this date++ -D --daily+ multiperiod/multicolumn report by day++ -W --weekly+ multiperiod/multicolumn report by week++ -M --monthly+ multiperiod/multicolumn report by month++ -Q --quarterly+ multiperiod/multicolumn report by quarter++ -Y --yearly+ multiperiod/multicolumn report by year++ -p --period=PERIODEXP+ set start date, end date, and/or reporting interval all at once+ (overrides the flags above)++ --date2+ show, and match with -b/-e/-p/date:, secondary dates instead++ -C --cleared+ include only cleared postings/txns++ --pending+ include only pending postings/txns++ -U --uncleared+ include only uncleared (and pending) postings/txns++ -R --real+ include only non-virtual postings++ --depth=N+ hide accounts/postings deeper than N++ -E --empty+ show items with zero amount, normally hidden++ -B --cost+ show amounts in their cost price's commodity++ --pivot TAG+ will transform the journal before any other processing by+ replacing the account name of every posting having the tag TAG+ with content VALUE by the account name "TAG:VALUE".+ The TAG will only match if it is a full-length match. The pivot will+ only happen if the TAG is on a posting, not if it is on the transac-+ tion. If the tag value is a multi:level:account:name the new account+ name will be "TAG:multi:level:account:name".++ --anon show anonymized accounts and payees++KEYS+ ? shows a help dialog listing all keys. (Some of these also appear in+ the quick help at the bottom of each screen.) Press ? again (or ESCAPE,+ or LEFT) to close it. The following keys work on most screens:++ The cursor keys navigate: right (or enter) goes deeper, left returns to+ the previous screen, up/down/page up/page down/home/end move up and+ down through lists. Vi-style h/j/k/l movement keys are also supported.+ A tip: movement speed is limited by your keyboard repeat rate, to move+ faster you may want to adjust it. (If you're on a mac, the Karabiner+ app is one way to do that.)++ With shift pressed, the cursor keys adjust the report period, limiting+ the transactions to be shown (by default, all are shown).+ shift-down/up steps downward and upward through these standard report+ period durations: year, quarter, month, week, day. Then,+ shift-left/right moves to the previous/next period. t sets the report+ period to today. (To set a non-standard period, you can use / and a+ date: query).++ / lets you set a general filter query limiting the data shown, using+ the same query terms as in hledger and hledger-web. While editing the+ query, you can use CTRL-a/e/d/k, BS, cursor keys; press ENTER to set+ it, or ESCAPEto cancel. There are also keys for quickly adjusting some+ common filters like account depth and cleared/uncleared (see below).+ BACKSPACE or DELETE removes all filters, showing all transactions.++ ESCAPE removes all filters and jumps back to the top screen. Or, it+ cancels a minibuffer edit or help dialog in progress.++ g reloads from the data file(s) and updates the current screen and any+ previous screens. (With large files, this could cause a noticeable+ pause.)++ I toggles balance assertion checking. Disabling balance assertions+ temporarily can be useful for troubleshooting.++ a runs command-line hledger's add command, and reloads the updated+ file. This allows some basic data entry.++ E runs $HLEDGER_UI_EDITOR, or $EDITOR, or a default (emac-+ sclient -a "" -nw) on the journal file. With some editors (emacs, vi),+ the cursor will be positioned at the current transaction when invoked+ from the register and transaction screens, and at the error location+ (if possible) when invoked from the error screen.++ q quits the application.++ Additional screen-specific keys are described below.++SCREENS+ Accounts screen+ This is normally the first screen displayed. It lists accounts and+ their balances, like hledger's balance command. By default, it shows+ all accounts and their latest ending balances (including the balances+ of subaccounts). if you specify a query on the command line, it shows+ just the matched accounts and the balances from matched transactions.++ Account names are normally indented to show the hierarchy (tree mode).+ To see less detail, set a depth limit by pressing a number key, 1 to 9.+ 0 shows even less detail, collapsing all accounts to a single total. -+ and + (or =) decrease and increase the depth limit. To remove the+ depth limit, set it higher than the maximum account depth, or press+ ESCAPE.++ F toggles flat mode, in which accounts are shown as a flat list, with+ their full names. In this mode, account balances exclude subaccounts,+ except for accounts at the depth limit (as with hledger's balance com-+ mand).++ H toggles between showing historical balances or period balances. His-+ torical balances (the default) are ending balances at the end of the+ report period, taking into account all transactions before that date+ (filtered by the filter query if any), including transactions before+ the start of the report period. In other words, historical balances+ are what you would see on a bank statement for that account (unless+ disturbed by a filter query). Period balances ignore transactions+ before the report start date, so they show the change in balance during+ the report period. They are more useful eg when viewing a time log.++ C toggles cleared mode, in which uncleared transactions and postings+ are not shown. U toggles uncleared mode, in which only uncleared+ transactions/postings are shown.++ R toggles real mode, in which virtual postings are ignored.++ Z toggles nonzero mode, in which only accounts with nonzero balances+ are shown (hledger-ui shows zero items by default, unlike command-line+ hledger).++ Press right or enter to view an account's transactions register.++ Register screen+ This screen shows the transactions affecting a particular account, like+ a check register. Each line represents one transaction and shows:++ o the other account(s) involved, in abbreviated form. (If there are+ both real and virtual postings, it shows only the accounts affected+ by real postings.)++ o the overall change to the current account's balance; positive for an+ inflow to this account, negative for an outflow.++ o the running historical total or period total for the current account,+ after the transaction. This can be toggled with H. Similar to the+ accounts screen, the historical total is affected by transactions+ (filtered by the filter query) before the report start date, while+ the period total is not. If the historical total is not disturbed by+ a filter query, it will be the running historical balance you would+ see on a bank register for the current account.++ If the accounts screen was in tree mode, the register screen will+ include transactions from both the current account and its subaccounts.+ If the accounts screen was in flat mode, and a non-depth-clipped+ account was selected, the register screen will exclude transactions+ from subaccounts. In other words, the register always shows the trans-+ actions responsible for the period balance shown on the accounts+ screen. As on the accounts screen, this can be toggled with F.++ C toggles cleared mode, in which uncleared transactions and postings+ are not shown. U toggles uncleared mode, in which only uncleared+ transactions/postings are shown.++ R toggles real mode, in which virtual postings are ignored.++ Z toggles nonzero mode, in which only transactions posting a nonzero+ change are shown (hledger-ui shows zero items by default, unlike com-+ mand-line hledger).++ Press right (or enter) to view the selected transaction in detail.++ Transaction screen+ This screen shows a single transaction, as a general journal entry,+ similar to hledger's print command and journal format (hledger_jour-+ nal(5)).++ The transaction's date(s) and any cleared flag, transaction code,+ description, comments, along with all of its account postings are+ shown. Simple transactions have two postings, but there can be more+ (or in certain cases, fewer).++ up and down will step through all transactions listed in the previous+ account register screen. In the title bar, the numbers in parentheses+ show your position within that account register. They will vary+ depending on which account register you came from (remember most trans-+ actions appear in multiple account registers). The #N number preceding+ them is the transaction's position within the complete unfiltered jour-+ nal, which is a more stable id (at least until the next reload).++ Error screen+ This screen will appear if there is a problem, such as a parse error,+ when you press g to reload. Once you have fixed the problem, press g+ again to reload and resume normal operation. (Or, you can press escape+ to cancel the reload attempt.)++ENVIRONMENT+ COLUMNS The screen width to use. Default: the full terminal width.++ LEDGER_FILE The journal file path when not specified with -f. Default:+ ~/.hledger.journal (on windows, perhaps C:/Users/USER/.hledger.jour-+ nal).++FILES+ Reads data from one or more files in hledger journal, timeclock, time-+ dot, or CSV format specified with -f, or $LEDGER_FILE, or+ $HOME/.hledger.journal (on windows, perhaps+ C:/Users/USER/.hledger.journal).++BUGS+ The need to precede options with -- when invoked from hledger is awk-+ ward.++ -f- doesn't work (hledger-ui can't read from stdin).++ -V affects only the accounts screen.++ When you press g, the current and all previous screens are regenerated,+ which may cause a noticeable pause. Also there is no visual indication+ that this is in progress.++ The register screen's switching between historic balance and running+ total based on query arguments may be confusing, and there is no column+ heading to indicate which is being displayed.++++REPORTING BUGS+ Report bugs at http://bugs.hledger.org (or on the #hledger IRC channel+ or hledger mail list)+++AUTHORS+ Simon Michael <simon@joyful.com> and contributors+++COPYRIGHT+ Copyright (C) 2007-2016 Simon Michael.+ Released under GNU GPL v3 or later.+++SEE ALSO+ hledger(1), hledger-ui(1), hledger-web(1), hledger-api(1),+ hledger_csv(5), hledger_journal(5), hledger_timeclock(5), hledger_time-+ dot(5), ledger(1)++ http://hledger.org++++hledger-ui 1.0 October 2016 hledger-ui(1)
hledger-ui.cabal view
@@ -1,22 +1,22 @@--- This file has been generated from package.yaml by hpack version 0.5.4.+-- This file has been generated from package.yaml by hpack version 0.14.0. -- -- see: https://github.com/sol/hpack name: hledger-ui-version: 0.27.5+version: 1.0 stability: beta category: Finance, Console synopsis: Curses-style user interface for the hledger accounting tool-description:- This is hledger's curses-style interface.- It is simpler and more convenient for browsing data than the command-line interface,- but lighter and faster than hledger-web.- hledger is a cross-platform program for tracking money, time, or- any other commodity, using double-entry accounting and a simple,- editable file format. It is inspired by and largely compatible- with ledger(1). hledger provides command-line, curses and web- interfaces, and aims to be a reliable, practical tool for daily- use.+description: This is hledger's curses-style interface.+ It is simpler and more convenient for browsing data than the command-line interface,+ but lighter and faster than hledger-web.+ .+ hledger is a cross-platform program for tracking money, time, or+ any other commodity, using double-entry accounting and a simple,+ editable file format. It is inspired by and largely compatible+ with ledger(1). hledger provides command-line, curses and web+ interfaces, and aims to be a reliable, practical tool for daily+ use. license: GPL license-file: LICENSE author: Simon Michael <simon@joyful.com>@@ -25,68 +25,85 @@ bug-reports: http://bugs.hledger.org cabal-version: >= 1.10 build-type: Simple-tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.1+tested-with: GHC==7.10.3, GHC==8.0 extra-source-files: CHANGES README +data-files:+ doc/hledger-ui.1+ doc/hledger-ui.1.info+ doc/hledger-ui.1.txt+ source-repository head type: git location: https://github.com/simonmichael/hledger +flag oldtime+ description: If building with time < 1.5, also depend on old-locale. Set automatically by cabal.+ manual: False+ default: False+ flag threaded default: True- description:- Build with support for multithreaded execution--flag old-locale- default: False- description:- A compatibility flag, set automatically by cabal.- If false then depend on time >= 1.5,- if true then depend on time < 1.5 together with old-locale.+ description: Build with support for multithreaded execution+ manual: False executable hledger-ui main-is: hledger-ui.hs hs-source-dirs: . ghc-options: -Wall -fno-warn-unused-do-bind -fno-warn-name-shadowing -fno-warn-missing-signatures -fno-warn-type-defaults -fno-warn-orphans- if flag(threaded)- ghc-options: -threaded- cpp-options: -DVERSION="0.27.5"+ cpp-options: -DVERSION="1.0" build-depends:- hledger >= 0.27.1 && < 0.28- , hledger-lib >= 0.27.1 && < 0.28+ hledger >= 1.0 && < 1.1+ , hledger-lib >= 1.0 && < 1.1+ , ansi-terminal >= 0.6.2.3 && < 0.7 , base >= 3 && < 5 , base-compat >= 0.8.1- , brick >= 0.2 && < 0.7 , cmdargs >= 0.8 , containers , data-default , filepath , HUnit- , lens >= 4.12.3 && < 4.15+ , microlens >= 0.4 && < 0.5+ , microlens-platform >= 0.2.3.1 && < 0.4+ , megaparsec >= 5+ , pretty-show >=1.6.4+ , process >= 1.2 , safe >= 0.2 , split >= 0.1 && < 0.3+ , text >= 1.2 && < 1.3+ , text-zipper >= 0.4 && < 0.9 , transformers , vector- , vty >= 5.2 && < 5.6- if impl(ghc >= 7.4)- build-depends: pretty-show >= 1.6.4- if flag(old-locale)- build-depends: time < 1.5, old-locale+ if os(windows)+ buildable: False else- build-depends: time >= 1.5+ build-depends:+ brick >= 0.7 && < 0.9+ , vty >= 5.5 && < 5.12+ if flag(threaded)+ ghc-options: -threaded+ if flag(oldtime)+ build-depends:+ time < 1.5+ , old-locale+ else+ build-depends:+ time >= 1.5 other-modules: Hledger.UI- Hledger.UI.Main- Hledger.UI.UIOptions- Hledger.UI.Theme- Hledger.UI.UITypes- Hledger.UI.UIUtils Hledger.UI.AccountsScreen+ Hledger.UI.Editor Hledger.UI.ErrorScreen+ Hledger.UI.Main Hledger.UI.RegisterScreen+ Hledger.UI.Theme Hledger.UI.TransactionScreen+ Hledger.UI.UIOptions+ Hledger.UI.UIState+ Hledger.UI.UITypes+ Hledger.UI.UIUtils default-language: Haskell2010
hledger-ui.hs view
@@ -1,2 +1,5 @@ #!/usr/bin/env runhaskell+module Main+ (module Hledger.UI)+where import Hledger.UI (main)