packages feed

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 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)