hledger-ui 1.26.1 → 1.27
raw patch · 14 files changed
+638/−564 lines, 14 filesdep +breakpointdep ~basedep ~brickdep ~hledger
Dependencies added: breakpoint
Dependency ranges changed: base, brick, hledger, hledger-lib
Files
- CHANGES.md +10/−0
- Hledger/UI/AccountsScreen.hs +161/−165
- Hledger/UI/ErrorScreen.hs +41/−39
- Hledger/UI/Main.hs +8/−4
- Hledger/UI/RegisterScreen.hs +149/−150
- Hledger/UI/Theme.hs +30/−30
- Hledger/UI/TransactionScreen.hs +66/−64
- Hledger/UI/UIState.hs +5/−5
- Hledger/UI/UITypes.hs +14/−4
- Hledger/UI/UIUtils.hs +86/−39
- hledger-ui.1 +7/−6
- hledger-ui.cabal +9/−8
- hledger-ui.info +34/−33
- hledger-ui.txt +18/−17
CHANGES.md view
@@ -9,6 +9,16 @@ User-visible changes in hledger-ui. See also the hledger changelog. +# 1.27 2022-09-01++Improvements++- At --debug=2 and up, log debug output to ./debug.log.++- Use/require brick 1.0+. (#1889)++- Use hledger 1.27+ # 1.26.1 2022-07-11 - support doclayout 0.4, brick 0.72+
Hledger/UI/AccountsScreen.hs view
@@ -29,7 +29,7 @@ import Text.DocLayout (realLength) import Hledger-import Hledger.Cli hiding (progname,prognameandversion)+import Hledger.Cli hiding (mode, progname, prognameandversion) import Hledger.UI.UIOptions import Hledger.UI.UITypes import Hledger.UI.UIState@@ -54,7 +54,7 @@ aopts=UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}}, ajournal=j, aScreen=s@AccountsScreen{}- } =+ } = dlogUiTrace "asInit 1" $ ui{aScreen=s & asList .~ newitems'} where newitems = list AccountsList (V.fromList $ displayitems ++ blankitems) 1@@ -97,7 +97,7 @@ displayitems = map displayitem items -- blanks added for scrolling control, cf RegisterScreen. -- XXX Ugly. Changing to 0 helps when debugging.- blankitems = replicate 100+ blankitems = replicate uiNumBlankItems AccountsScreenItem{asItemIndentLevel = 0 ,asItemAccountName = "" ,asItemDisplayAccountName = ""@@ -105,14 +105,14 @@ } -asInit _ _ _ = error "init function called with wrong screen type, should not happen" -- PARTIAL:+asInit _ _ _ = dlogUiTrace "asInit 2" $ errorWrongScreenType "init function" -- PARTIAL: asDraw :: UIState -> [Widget Name] asDraw UIState{aopts=_uopts@UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec}} ,ajournal=j ,aScreen=s@AccountsScreen{} ,aMode=mode- } =+ } = dlogUiTrace "asDraw 1" $ case mode of Help -> [helpDialog copts, maincontent] -- Minibuffer e -> [minibuffer e, maincontent]@@ -152,7 +152,7 @@ ishistorical = balanceaccum_ ropts == Historical toplabel =- withAttr ("border" <> "filename") files+ withAttr (attrName "border" <> attrName "filename") files <+> toggles <+> str (" account " ++ if ishistorical then "balances" else "changes") <+> borderPeriodStr (if ishistorical then "at end of" else "in") (period_ ropts)@@ -160,7 +160,7 @@ <+> borderDepthStr mdepth <+> str (" ("++curidx++"/"++totidx++")") <+> (if ignore_assertions_ . balancingopts_ $ inputopts_ copts- then withAttr ("border" <> "query") (str " ignoring balance assertions")+ then withAttr (attrName "border" <> attrName "query") (str " ignoring balance assertions") else str "") where files = case journalFilePaths j of@@ -168,7 +168,7 @@ f:_ -> 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)")- toggles = withAttr ("border" <> "query") $ str $ unwords $ concat [+ toggles = withAttr (attrName "border" <> attrName "query") $ str $ unwords $ concat [ [""] ,if empty_ ropts then [] else ["nonzero"] ,uiShowStatus copts $ statuses_ ropts@@ -203,7 +203,7 @@ ,("q", str "quit") ] -asDraw _ = error "draw function called with wrong screen type, should not happen" -- PARTIAL:+asDraw _ = dlogUiTrace "asDraw 2" $ errorWrongScreenType "draw function" -- PARTIAL: asDrawItem :: (Int,Int) -> Bool -> AccountsScreenItem -> Widget Name asDrawItem (acctwidth, balwidth) selected AccountsScreenItem{..} =@@ -220,187 +220,183 @@ balspace = T.replicate (2 + balwidth - wbWidth balBuilder) " " splitAmounts = foldr1 (<+>) . intersperse (str ", ") . map renderamt . T.splitOn ", " . wbToText renderamt :: T.Text -> Widget Name- renderamt a | T.any (=='-') a = withAttr (sel $ "list" <> "balance" <> "negative") $ txt a- | otherwise = withAttr (sel $ "list" <> "balance" <> "positive") $ txt a- sel | selected = (<> "selected")+ renderamt a | T.any (=='-') a = withAttr (sel $ attrName "list" <> attrName "balance" <> attrName "negative") $ txt a+ | otherwise = withAttr (sel $ attrName "list" <> attrName "balance" <> attrName "positive") $ txt a+ sel | selected = (<> attrName "selected") | otherwise = id -asHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)-asHandle ui0@UIState{- aScreen=scr@AccountsScreen{..}- ,aopts=UIOpts{uoCliOpts=copts}- ,ajournal=j- ,aMode=mode- } ev = do- let- d = copts^.rsDay- nonblanks = V.takeWhile (not . T.null . asItemAccountName) $ _asList^.listElementsL- lastnonblankidx = max 0 (length nonblanks - 1)-- -- 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}+asHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()+asHandle ev = do+ ui0 <- get'+ dlogUiTraceM "asHandle 1"+ case ui0 of+ ui1@UIState{+ aScreen=scr@AccountsScreen{..}+ ,aopts=UIOpts{uoCliOpts=copts}+ ,ajournal=j+ ,aMode=mode+ } -> do - case mode of- Minibuffer _ ed ->- case ev of- VtyEvent (EvKey KEsc []) -> continue $ closeMinibuffer ui- VtyEvent (EvKey KEnter []) -> continue $ regenerateScreens j d $- case setFilter s $ closeMinibuffer ui of- Left bad -> showMinibuffer "Cannot compile regular expression" (Just bad) ui- Right ui' -> ui'- where s = chomp $ unlines $ map strip $ getEditContents ed- VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui- VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui- VtyEvent ev -> do- ed' <- handleEditorEvent -#if MIN_VERSION_brick(0,72,0)- (VtyEvent ev)-#else- ev-#endif- ed- continue $ ui{aMode=Minibuffer "filter" ed'}- AppEvent _ -> continue ui- MouseDown{} -> continue ui- MouseUp{} -> continue ui+ let+ -- save the currently selected account, in case we leave this screen and lose the selection+ selacct = case listSelectedElement _asList of+ Just (_, AccountsScreenItem{..}) -> asItemAccountName+ Nothing -> scr ^. asSelectedAccount+ ui = ui1{aScreen=scr & asSelectedAccount .~ selacct}+ nonblanks = V.takeWhile (not . T.null . asItemAccountName) $ _asList^.listElementsL+ lastnonblankidx = max 0 (length nonblanks - 1)+ journalspan = journalDateSpan False j+ d = copts^.rsDay - Help ->- case ev of- -- VtyEvent (EvKey (KChar 'q') []) -> halt ui- VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui- VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui- _ -> helpHandle ui ev+ case mode of+ Minibuffer _ ed ->+ case ev of+ VtyEvent (EvKey KEsc []) -> put' $ closeMinibuffer ui+ VtyEvent (EvKey KEnter []) -> put' $ regenerateScreens j d $+ case setFilter s $ closeMinibuffer ui of+ Left bad -> showMinibuffer "Cannot compile regular expression" (Just bad) ui+ Right ui' -> ui'+ where s = chomp $ unlines $ map strip $ getEditContents ed+ VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw+ VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui+ VtyEvent e -> do+ ed' <- nestEventM' ed $ handleEditorEvent (VtyEvent e)+ put' ui{aMode=Minibuffer "filter" ed'}+ AppEvent _ -> return ()+ MouseDown{} -> return ()+ MouseUp{} -> return () - Normal ->- case ev of- VtyEvent (EvKey (KChar 'q') []) -> halt ui- -- EvKey (KChar 'l') [MCtrl] -> do- VtyEvent (EvKey KEsc []) -> continue $ resetScreens d ui- VtyEvent (EvKey (KChar c) []) | c == '?' -> continue $ setMode Help ui- -- XXX AppEvents currently handled only in Normal mode- -- XXX be sure we don't leave unconsumed events piling up- AppEvent (DateChange old _) | isStandardPeriod p && p `periodContainsDate` old ->- continue $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui- where- p = reportPeriod ui- e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] ->- liftIO (uiReloadJournal copts d ui) >>= continue- VtyEvent (EvKey (KChar 'I') []) -> continue $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)- VtyEvent (EvKey (KChar 'a') []) -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> uiReloadJournalIfChanged copts d j ui- VtyEvent (EvKey (KChar 'A') []) -> suspendAndResume $ void (runIadd (journalFilePath j)) >> uiReloadJournalIfChanged copts d j ui- VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor endPosition (journalFilePath j)) >> uiReloadJournalIfChanged copts d j ui- VtyEvent (EvKey (KChar 'B') []) -> continue $ regenerateScreens j d $ toggleConversionOp ui- VtyEvent (EvKey (KChar 'V') []) -> continue $ regenerateScreens j d $ toggleValue ui- VtyEvent (EvKey (KChar '0') []) -> continue $ regenerateScreens j d $ setDepth (Just 0) ui- VtyEvent (EvKey (KChar '1') []) -> continue $ regenerateScreens j d $ setDepth (Just 1) ui- VtyEvent (EvKey (KChar '2') []) -> continue $ regenerateScreens j d $ setDepth (Just 2) ui- VtyEvent (EvKey (KChar '3') []) -> continue $ regenerateScreens j d $ setDepth (Just 3) ui- VtyEvent (EvKey (KChar '4') []) -> continue $ regenerateScreens j d $ setDepth (Just 4) ui- VtyEvent (EvKey (KChar '5') []) -> continue $ regenerateScreens j d $ setDepth (Just 5) ui- VtyEvent (EvKey (KChar '6') []) -> continue $ regenerateScreens j d $ setDepth (Just 6) ui- VtyEvent (EvKey (KChar '7') []) -> continue $ regenerateScreens j d $ setDepth (Just 7) ui- VtyEvent (EvKey (KChar '8') []) -> continue $ regenerateScreens j d $ setDepth (Just 8) ui- VtyEvent (EvKey (KChar '9') []) -> continue $ regenerateScreens j d $ setDepth (Just 9) ui- VtyEvent (EvKey (KChar '-') []) -> continue $ regenerateScreens j d $ decDepth ui- VtyEvent (EvKey (KChar '_') []) -> continue $ regenerateScreens j d $ decDepth ui- VtyEvent (EvKey (KChar c) []) | c `elem` ['+','='] -> continue $ regenerateScreens j d $ incDepth ui- VtyEvent (EvKey (KChar 'T') []) -> continue $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui+ Help ->+ case ev of+ -- VtyEvent (EvKey (KChar 'q') []) -> halt+ VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw+ VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui+ _ -> helpHandle ev - -- display mode/query toggles- VtyEvent (EvKey (KChar 'H') []) -> asCenterAndContinue $ regenerateScreens j d $ toggleHistorical ui- VtyEvent (EvKey (KChar 't') []) -> asCenterAndContinue $ regenerateScreens j d $ toggleTree ui- VtyEvent (EvKey (KChar c) []) | c `elem` ['z','Z'] -> asCenterAndContinue $ regenerateScreens j d $ toggleEmpty ui- VtyEvent (EvKey (KChar 'R') []) -> asCenterAndContinue $ regenerateScreens j d $ toggleReal ui- VtyEvent (EvKey (KChar 'U') []) -> asCenterAndContinue $ regenerateScreens j d $ toggleUnmarked ui- VtyEvent (EvKey (KChar 'P') []) -> asCenterAndContinue $ regenerateScreens j d $ togglePending ui- VtyEvent (EvKey (KChar 'C') []) -> asCenterAndContinue $ regenerateScreens j d $ toggleCleared ui- VtyEvent (EvKey (KChar 'F') []) -> continue $ regenerateScreens j d $ toggleForecast d ui+ Normal ->+ case ev of+ VtyEvent (EvKey (KChar 'q') []) -> halt+ -- EvKey (KChar 'l') [MCtrl] -> do+ VtyEvent (EvKey KEsc []) -> put' $ resetScreens d ui+ VtyEvent (EvKey (KChar c) []) | c == '?' -> put' $ setMode Help ui+ -- XXX AppEvents currently handled only in Normal mode+ -- XXX be sure we don't leave unconsumed events piling up+ AppEvent (DateChange old _) | isStandardPeriod p && p `periodContainsDate` old ->+ put' $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui+ where+ p = reportPeriod ui+ e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] ->+ liftIO (uiReloadJournal copts d ui) >>= put'+ VtyEvent (EvKey (KChar 'I') []) -> put' $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)+ VtyEvent (EvKey (KChar 'a') []) -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> uiReloadJournalIfChanged copts d j ui+ VtyEvent (EvKey (KChar 'A') []) -> suspendAndResume $ void (runIadd (journalFilePath j)) >> uiReloadJournalIfChanged copts d j ui+ VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor endPosition (journalFilePath j)) >> uiReloadJournalIfChanged copts d j ui+ VtyEvent (EvKey (KChar 'B') []) -> put' $ regenerateScreens j d $ toggleConversionOp ui+ VtyEvent (EvKey (KChar 'V') []) -> put' $ regenerateScreens j d $ toggleValue ui+ VtyEvent (EvKey (KChar '0') []) -> put' $ regenerateScreens j d $ setDepth (Just 0) ui+ VtyEvent (EvKey (KChar '1') []) -> put' $ regenerateScreens j d $ setDepth (Just 1) ui+ VtyEvent (EvKey (KChar '2') []) -> put' $ regenerateScreens j d $ setDepth (Just 2) ui+ VtyEvent (EvKey (KChar '3') []) -> put' $ regenerateScreens j d $ setDepth (Just 3) ui+ VtyEvent (EvKey (KChar '4') []) -> put' $ regenerateScreens j d $ setDepth (Just 4) ui+ VtyEvent (EvKey (KChar '5') []) -> put' $ regenerateScreens j d $ setDepth (Just 5) ui+ VtyEvent (EvKey (KChar '6') []) -> put' $ regenerateScreens j d $ setDepth (Just 6) ui+ VtyEvent (EvKey (KChar '7') []) -> put' $ regenerateScreens j d $ setDepth (Just 7) ui+ VtyEvent (EvKey (KChar '8') []) -> put' $ regenerateScreens j d $ setDepth (Just 8) ui+ VtyEvent (EvKey (KChar '9') []) -> put' $ regenerateScreens j d $ setDepth (Just 9) ui+ VtyEvent (EvKey (KChar '-') []) -> put' $ regenerateScreens j d $ decDepth ui+ VtyEvent (EvKey (KChar '_') []) -> put' $ regenerateScreens j d $ decDepth ui+ VtyEvent (EvKey (KChar c) []) | c `elem` ['+','='] -> put' $ regenerateScreens j d $ incDepth ui+ VtyEvent (EvKey (KChar 'T') []) -> put' $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui - VtyEvent (EvKey (KDown) [MShift]) -> continue $ regenerateScreens j d $ shrinkReportPeriod d ui- VtyEvent (EvKey (KUp) [MShift]) -> continue $ regenerateScreens j d $ growReportPeriod d ui- VtyEvent (EvKey (KRight) [MShift]) -> continue $ regenerateScreens j d $ nextReportPeriod journalspan ui- VtyEvent (EvKey (KLeft) [MShift]) -> continue $ regenerateScreens j d $ previousReportPeriod journalspan ui- VtyEvent (EvKey (KChar '/') []) -> continue $ regenerateScreens j d $ showMinibuffer "filter" Nothing ui- VtyEvent (EvKey k []) | k `elem` [KBS, KDel] -> (continue $ regenerateScreens j d $ resetFilter ui)- VtyEvent e | e `elem` moveLeftEvents -> continue $ popScreen ui- VtyEvent (EvKey (KChar 'l') [MCtrl]) -> scrollSelectionToMiddle _asList >> redraw ui- VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui+ -- display mode/query toggles+ VtyEvent (EvKey (KChar 'H') []) -> modify' (regenerateScreens j d . toggleHistorical) >> asCenterAndContinue+ VtyEvent (EvKey (KChar 't') []) -> modify' (regenerateScreens j d . toggleTree) >> asCenterAndContinue+ VtyEvent (EvKey (KChar c) []) | c `elem` ['z','Z'] -> modify' (regenerateScreens j d . toggleEmpty) >> asCenterAndContinue+ VtyEvent (EvKey (KChar 'R') []) -> modify' (regenerateScreens j d . toggleReal) >> asCenterAndContinue+ VtyEvent (EvKey (KChar 'U') []) -> modify' (regenerateScreens j d . toggleUnmarked) >> asCenterAndContinue+ VtyEvent (EvKey (KChar 'P') []) -> modify' (regenerateScreens j d . togglePending) >> asCenterAndContinue+ VtyEvent (EvKey (KChar 'C') []) -> modify' (regenerateScreens j d . toggleCleared) >> asCenterAndContinue+ VtyEvent (EvKey (KChar 'F') []) -> modify' (regenerateScreens j d . toggleForecast d) - -- enter register screen for selected account (if there is one),- -- centering its selected transaction if possible- VtyEvent e | e `elem` moveRightEvents- , not $ isBlankElement $ listSelectedElement _asList -> asEnterRegister d selacct ui+ VtyEvent (EvKey (KDown) [MShift]) -> put' $ regenerateScreens j d $ shrinkReportPeriod d ui+ VtyEvent (EvKey (KUp) [MShift]) -> put' $ regenerateScreens j d $ growReportPeriod d ui+ VtyEvent (EvKey (KRight) [MShift]) -> put' $ regenerateScreens j d $ nextReportPeriod journalspan ui+ VtyEvent (EvKey (KLeft) [MShift]) -> put' $ regenerateScreens j d $ previousReportPeriod journalspan ui+ VtyEvent (EvKey (KChar '/') []) -> put' $ regenerateScreens j d $ showMinibuffer "filter" Nothing ui+ VtyEvent (EvKey k []) | k `elem` [KBS, KDel] -> (put' $ regenerateScreens j d $ resetFilter ui)+ VtyEvent e | e `elem` moveLeftEvents -> put' $ popScreen ui+ VtyEvent (EvKey (KChar 'l') [MCtrl]) -> scrollSelectionToMiddle _asList >> redraw+ VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui - -- MouseDown is sometimes duplicated, https://github.com/jtdaugherty/brick/issues/347- -- just use it to move the selection- MouseDown _n BLeft _mods Location{loc=(_x,y)} | not $ (=="") clickedacct -> do- continue ui{aScreen=scr{_asList=listMoveTo y _asList}}- where clickedacct = maybe "" asItemAccountName $ listElements _asList !? y- -- and on MouseUp, enter the subscreen- MouseUp _n (Just BLeft) Location{loc=(_x,y)} | not $ (=="") clickedacct -> do- asEnterRegister d clickedacct ui- where clickedacct = maybe "" asItemAccountName $ listElements _asList !? y+ -- enter register screen for selected account (if there is one),+ -- centering its selected transaction if possible+ VtyEvent e | e `elem` moveRightEvents+ , not $ isBlankElement $ listSelectedElement _asList -> asEnterRegister d selacct ui - -- when selection is at the last item, DOWN scrolls instead of moving, until maximally scrolled- VtyEvent e | e `elem` moveDownEvents, isBlankElement mnextelement -> do- vScrollBy (viewportScroll $ _asList^.listNameL) 1 >> continue ui- where mnextelement = listSelectedElement $ listMoveDown _asList+ -- MouseDown is sometimes duplicated, https://github.com/jtdaugherty/brick/issues/347+ -- just use it to move the selection+ MouseDown _n BLeft _mods Location{loc=(_x,y)} | not $ (=="") clickedacct -> do+ put' ui{aScreen=scr} -- XXX does this do anything ?+ where clickedacct = maybe "" asItemAccountName $ listElements _asList !? y+ -- and on MouseUp, enter the subscreen+ MouseUp _n (Just BLeft) Location{loc=(_x,y)} | not $ (=="") clickedacct -> do+ asEnterRegister d clickedacct ui+ where clickedacct = maybe "" asItemAccountName $ listElements _asList !? y - -- mouse scroll wheel scrolls the viewport up or down to its maximum extent,- -- pushing the selection when necessary.- MouseDown name btn _mods _loc | btn `elem` [BScrollUp, BScrollDown] -> do- let scrollamt = if btn==BScrollUp then -1 else 1- list' <- listScrollPushingSelection name _asList (asListSize _asList) scrollamt- continue ui{aScreen=scr{_asList=list'}}+ -- when selection is at the last item, DOWN scrolls instead of moving, until maximally scrolled+ VtyEvent e | e `elem` moveDownEvents, isBlankElement mnextelement -> do+ vScrollBy (viewportScroll $ _asList^.listNameL) 1+ where mnextelement = listSelectedElement $ listMoveDown _asList - -- if page down or end leads to a blank padding item, stop at last non-blank- VtyEvent e@(EvKey k []) | k `elem` [KPageDown, KEnd] -> do- list <- handleListEvent e _asList- if isBlankElement $ listSelectedElement list- then do- let list' = listMoveTo lastnonblankidx list- scrollSelectionToMiddle list'- continue ui{aScreen=scr{_asList=list'}}- else- continue ui{aScreen=scr{_asList=list}}+ -- mouse scroll wheel scrolls the viewport up or down to its maximum extent,+ -- pushing the selection when necessary.+ MouseDown name btn _mods _loc | btn `elem` [BScrollUp, BScrollDown] -> do+ let scrollamt = if btn==BScrollUp then -1 else 1+ list' <- nestEventM' _asList $ listScrollPushingSelection name (asListSize _asList) scrollamt+ put' ui{aScreen=scr{_asList=list'}} - -- fall through to the list's event handler (handles up/down)- VtyEvent ev -> do- newitems <- handleListEvent (normaliseMovementKeys ev) _asList- continue $ ui{aScreen=scr & asList .~ newitems- & asSelectedAccount .~ selacct- }+ -- if page down or end leads to a blank padding item, stop at last non-blank+ VtyEvent e@(EvKey k []) | k `elem` [KPageDown, KEnd] -> do+ l <- nestEventM' _asList $ handleListEvent e+ if isBlankElement $ listSelectedElement l+ then do+ let l' = listMoveTo lastnonblankidx l+ scrollSelectionToMiddle l'+ put' ui{aScreen=scr{_asList=l'}}+ else+ put' ui{aScreen=scr{_asList=l}} - MouseDown{} -> continue ui- MouseUp{} -> continue ui- AppEvent _ -> continue ui+ -- fall through to the list's event handler (handles up/down)+ VtyEvent e -> do+ list' <- nestEventM' _asList $ handleListEvent (normaliseMovementKeys e)+ put' ui{aScreen=scr & asList .~ list' & asSelectedAccount .~ selacct } - where- journalspan = journalDateSpan False j+ MouseDown{} -> return ()+ MouseUp{} -> return ()+ AppEvent _ -> return () -asHandle _ _ = error "event handler called with wrong screen type, should not happen" -- PARTIAL:+ _ -> dlogUiTraceM "asHandle 2" >> errorWrongScreenType "event handler" +asEnterRegister :: Day -> AccountName -> UIState -> EventM Name UIState () asEnterRegister d selacct ui = do- rsCenterAndContinue $- -- flip rsHandle (VtyEvent (EvKey (KChar 'l') [MCtrl])) $- screenEnter d regscr ui+ dlogUiTraceM "asEnterRegister"+ let+ regscr = rsSetAccount selacct isdepthclipped registerScreen where- regscr = rsSetAccount selacct isdepthclipped registerScreen isdepthclipped = case getDepth ui of- Just d -> accountNameLevel selacct >= d- Nothing -> False+ Just de -> accountNameLevel selacct >= de+ Nothing -> False+ rsCenterSelection (screenEnter d regscr ui) >>= put' asSetSelectedAccount a s@AccountsScreen{} = s & asSelectedAccount .~ a asSetSelectedAccount _ s = s isBlankElement mel = ((asItemAccountName . snd) <$> mel) == Just "" -asCenterAndContinue ui = do- scrollSelectionToMiddle $ _asList $ aScreen ui- continue ui+asCenterAndContinue :: EventM Name UIState ()+asCenterAndContinue = do+ ui <- get'+ scrollSelectionToMiddle (_asList $ aScreen ui) asListSize = V.length . V.takeWhile ((/="").asItemAccountName) . listElements+
Hledger/UI/ErrorScreen.hs view
@@ -24,7 +24,7 @@ import Text.Megaparsec import Text.Megaparsec.Char -import Hledger.Cli hiding (progname,prognameandversion)+import Hledger.Cli hiding (mode, progname,prognameandversion) import Hledger.UI.UIOptions import Hledger.UI.UITypes import Hledger.UI.UIState@@ -54,10 +54,10 @@ _ -> [maincontent] where maincontent = Widget Greedy Greedy $ do- render $ defaultLayout toplabel bottomlabel $ withAttr "error" $ str $ esError+ render $ defaultLayout toplabel bottomlabel $ withAttr (attrName "error") $ str $ esError where toplabel =- withAttr ("border" <> "bold") (str "Oops. Please fix this problem then press g to reload")+ withAttr (attrName "border" <> attrName "bold") (str "Oops. Please fix this problem then press g to reload") -- <+> (if ignore_assertions_ copts then withAttr ("border" <> "query") (str " ignoring") else str " not ignoring") bottomlabel = quickhelp@@ -75,44 +75,46 @@ esDraw _ = error "draw function called with wrong screen type, should not happen" -- PARTIAL: -esHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)-esHandle ui@UIState{aScreen=ErrorScreen{..}- ,aopts=UIOpts{uoCliOpts=copts}- ,ajournal=j- ,aMode=mode- }- ev =- case mode of- Help ->- case ev of- VtyEvent (EvKey (KChar 'q') []) -> halt ui- VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui- VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui- _ -> helpHandle ui ev+esHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()+esHandle ev = do+ ui0 <- get'+ case ui0 of+ ui@UIState{aScreen=ErrorScreen{..}+ ,aopts=UIOpts{uoCliOpts=copts}+ ,ajournal=j+ ,aMode=mode+ } ->+ case mode of+ Help ->+ case ev of+ VtyEvent (EvKey (KChar 'q') []) -> halt+ VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw+ VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui+ _ -> helpHandle ev - _ -> do- let d = copts^.rsDay- case ev of- VtyEvent (EvKey (KChar 'q') []) -> halt ui- VtyEvent (EvKey KEsc []) -> continue $ uiCheckBalanceAssertions d $ resetScreens d ui- VtyEvent (EvKey (KChar c) []) | c `elem` ['h','?'] -> continue $ setMode Help ui- VtyEvent (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 _ -> (endPosition, journalFilePath j)- e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] ->- liftIO (uiReloadJournal copts d (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- VtyEvent (EvKey (KChar 'I') []) -> continue $ uiCheckBalanceAssertions d (popScreen $ toggleIgnoreBalanceAssertions ui)- VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui- VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui- _ -> continue ui+ _ -> do+ let d = copts^.rsDay+ case ev of+ VtyEvent (EvKey (KChar 'q') []) -> halt+ VtyEvent (EvKey KEsc []) -> put' $ uiCheckBalanceAssertions d $ resetScreens d ui+ VtyEvent (EvKey (KChar c) []) | c `elem` ['h','?'] -> put' $ setMode Help ui+ VtyEvent (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 _ -> (endPosition, journalFilePath j)+ e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] ->+ liftIO (uiReloadJournal copts d (popScreen ui)) >>= put' . 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+ VtyEvent (EvKey (KChar 'I') []) -> put' $ uiCheckBalanceAssertions d (popScreen $ toggleIgnoreBalanceAssertions ui)+ VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw+ VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui+ _ -> return () -esHandle _ _ = error "event handler called with wrong screen type, should not happen" -- PARTIAL:+ _ -> errorWrongScreenType "event handler" -- | 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
Hledger/UI/Main.hs view
@@ -1,3 +1,5 @@+-- TODO: brick 1 support+-- https://hackage.haskell.org/package/brick-1.0/changelog {-| hledger-ui - a hledger add-on providing a curses-style interface. Copyright (c) 2007-2015 Simon Michael <simon@joyful.com>@@ -33,6 +35,7 @@ import Hledger.UI.Theme import Hledger.UI.AccountsScreen import Hledger.UI.RegisterScreen+import Hledger.UI.UIUtils (dlogUiTrace) ---------------------------------------------------------------------- @@ -60,7 +63,8 @@ _ -> withJournalDo copts' (runBrickUi opts) runBrickUi :: UIOpts -> Journal -> IO ()-runBrickUi uopts@UIOpts{uoCliOpts=copts@CliOpts{inputopts_=_iopts,reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}} j = do+runBrickUi uopts@UIOpts{uoCliOpts=copts@CliOpts{inputopts_=_iopts,reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}} j =+ dlogUiTrace "========= runBrickUi" $ do let today = copts^.rsDay @@ -159,11 +163,11 @@ brickapp :: App UIState AppEvent Name brickapp = App {- appStartEvent = return+ appStartEvent = return () , appAttrMap = const $ fromMaybe defaultTheme $ getTheme =<< uoTheme uopts' , appChooseCursor = showFirstCursor- , appHandleEvent = \ui ev -> sHandle (aScreen ui) ui ev- , appDraw = \ui -> sDraw (aScreen ui) ui+ , appHandleEvent = \ev -> do ui' <- get; sHandle (aScreen ui') ev+ , appDraw = \ui' -> sDraw (aScreen ui') ui' } -- print (length (show ui)) >> exitSuccess -- show any debug output to this point & quit
Hledger/UI/RegisterScreen.hs view
@@ -4,14 +4,13 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}-{-# LANGUAGE NamedFieldPuns #-} {-# OPTIONS_GHC -Wno-incomplete-record-updates #-} module Hledger.UI.RegisterScreen (registerScreen ,rsHandle ,rsSetAccount- ,rsCenterAndContinue+ ,rsCenterSelection ) where @@ -32,7 +31,7 @@ import Hledger-import Hledger.Cli hiding (progname,prognameandversion)+import Hledger.Cli hiding (mode, progname,prognameandversion) import Hledger.UI.UIOptions -- import Hledger.UI.Theme import Hledger.UI.UITypes@@ -60,6 +59,7 @@ rsInit :: Day -> Bool -> UIState -> UIState rsInit d reset ui@UIState{aopts=_uopts@UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}}, ajournal=j, aScreen=s@RegisterScreen{..}} =+ dlogUiTrace "rsInit 1" $ ui{aScreen=s{rsList=newitems'}} where -- gather arguments and queries@@ -102,7 +102,7 @@ where showamt = showMixedAmountB oneLine{displayMaxWidth=Just 32} -- blank items are added to allow more control of scroll position; we won't allow movement over these. -- XXX Ugly. Changing to 0 helps when debugging.- blankitems = replicate 100 -- "100 ought to be enough for anyone"+ blankitems = replicate uiNumBlankItems RegisterScreenItem{rsItemDate = "" ,rsItemStatus = Unmarked ,rsItemDescription = ""@@ -137,13 +137,13 @@ ts = map rsItemTransaction displayitems endidx = max 0 $ length displayitems - 1 -rsInit _ _ _ = error "init function called with wrong screen type, should not happen" -- PARTIAL:+rsInit _ _ _ = dlogUiTrace "rsInit 2" $ errorWrongScreenType "init function" -- PARTIAL: rsDraw :: UIState -> [Widget Name] rsDraw UIState{aopts=_uopts@UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec}} ,aScreen=RegisterScreen{..} ,aMode=mode- } =+ } = dlogUiTrace "rsDraw 1" $ case mode of Help -> [helpDialog copts, maincontent] -- Minibuffer e -> [minibuffer e, maincontent]@@ -199,7 +199,7 @@ -- inclusive = tree_ ropts || rsForceInclusive toplabel =- withAttr ("border" <> "bold") (str $ T.unpack $ replaceHiddenAccountsNameWith "All" rsAccount)+ withAttr (attrName "border" <> attrName "bold") (str $ T.unpack $ replaceHiddenAccountsNameWith "All" rsAccount) -- <+> withAttr ("border" <> "query") (str $ if inclusive then "" else " exclusive") <+> togglefilters <+> str " transactions"@@ -212,7 +212,7 @@ <+> str "/" <+> total <+> str ")"- <+> (if ignore_assertions_ . balancingopts_ $ inputopts_ copts then withAttr ("border" <> "query") (str " ignoring balance assertions") else str "")+ <+> (if ignore_assertions_ . balancingopts_ $ inputopts_ copts then withAttr (attrName "border" <> attrName "query") (str " ignoring balance assertions") else str "") where togglefilters = case concat [@@ -221,7 +221,7 @@ ,if empty_ ropts then [] else ["nonzero"] ] of [] -> str ""- fs -> withAttr ("border" <> "query") (str $ " " ++ intercalate ", " fs)+ fs -> withAttr (attrName "border" <> attrName "query") (str $ " " ++ intercalate ", " fs) cur = str $ case rsList ^. listSelectedL of Nothing -> "-" Just i -> show (i + 1)@@ -251,7 +251,7 @@ -- ,("q", "quit") ] -rsDraw _ = error "draw function called with wrong screen type, should not happen" -- PARTIAL:+rsDraw _ = dlogUiTrace "rsDraw 2" $ errorWrongScreenType "draw function" -- PARTIAL: rsDrawItem :: (Int,Int,Int,Int,Int) -> Bool -> RegisterScreenItem -> Widget Name rsDrawItem (datewidth,descwidth,acctswidth,changewidth,balwidth) selected RegisterScreenItem{..} =@@ -271,164 +271,163 @@ where changeAmt = wbToText rsItemChangeAmount balanceAmt = wbToText rsItemBalanceAmount- changeattr | T.any (=='-') changeAmt = sel $ "list" <> "amount" <> "decrease"- | otherwise = sel $ "list" <> "amount" <> "increase"- balattr | T.any (=='-') balanceAmt = sel $ "list" <> "balance" <> "negative"- | otherwise = sel $ "list" <> "balance" <> "positive"- sel | selected = (<> "selected")+ changeattr | T.any (=='-') changeAmt = sel $ attrName "list" <> attrName "amount" <> attrName "decrease"+ | otherwise = sel $ attrName "list" <> attrName "amount" <> attrName "increase"+ balattr | T.any (=='-') balanceAmt = sel $ attrName "list" <> attrName "balance" <> attrName "negative"+ | otherwise = sel $ attrName "list" <> attrName "balance" <> attrName "positive"+ sel | selected = (<> attrName "selected") | otherwise = id -rsHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)-rsHandle ui@UIState{- aScreen=s@RegisterScreen{..}- ,aopts=UIOpts{uoCliOpts=copts}- ,ajournal=j- ,aMode=mode- } ev = do- let- d = copts^.rsDay- journalspan = journalDateSpan False j- nonblanks = V.takeWhile (not . T.null . rsItemDate) $ rsList^.listElementsL- lastnonblankidx = max 0 (length nonblanks - 1)+rsHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()+rsHandle ev = do+ ui0 <- get'+ dlogUiTraceM "rsHandle 1"+ case ui0 of+ ui@UIState{+ aScreen=scr@RegisterScreen{..}+ ,aopts=UIOpts{uoCliOpts=copts}+ ,ajournal=j+ ,aMode=mode+ } -> do+ let+ d = copts^.rsDay+ journalspan = journalDateSpan False j+ nonblanks = V.takeWhile (not . T.null . rsItemDate) $ rsList^.listElementsL+ lastnonblankidx = max 0 (length nonblanks - 1) - case mode of- Minibuffer _ ed ->- case ev of- VtyEvent (EvKey KEsc []) -> continue $ closeMinibuffer ui- VtyEvent (EvKey KEnter []) -> continue $ regenerateScreens j d $- case setFilter s $ closeMinibuffer ui of- Left bad -> showMinibuffer "Cannot compile regular expression" (Just bad) ui- Right ui' -> ui'- where s = chomp . unlines . map strip $ getEditContents ed- -- VtyEvent (EvKey (KChar '/') []) -> continue $ regenerateScreens j d $ showMinibuffer ui- VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui- VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui- VtyEvent ev -> do- ed' <- handleEditorEvent -#if MIN_VERSION_brick(0,72,0)- (VtyEvent ev)-#else- ev-#endif- ed- continue $ ui{aMode=Minibuffer "filter" ed'}- AppEvent _ -> continue ui- MouseDown{} -> continue ui- MouseUp{} -> continue ui+ case mode of+ Minibuffer _ ed ->+ case ev of+ VtyEvent (EvKey KEsc []) -> modify' closeMinibuffer+ VtyEvent (EvKey KEnter []) -> put' $ regenerateScreens j d $+ case setFilter s $ closeMinibuffer ui of+ Left bad -> showMinibuffer "Cannot compile regular expression" (Just bad) ui+ Right ui' -> ui'+ where s = chomp . unlines . map strip $ getEditContents ed+ -- VtyEvent (EvKey (KChar '/') []) -> put' $ regenerateScreens j d $ showMinibuffer ui+ VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw+ VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui+ VtyEvent e -> do+ ed' <- nestEventM' ed $ handleEditorEvent (VtyEvent e)+ put' ui{aMode=Minibuffer "filter" ed'}+ AppEvent _ -> return ()+ MouseDown{} -> return ()+ MouseUp{} -> return () - Help ->- case ev of- -- VtyEvent (EvKey (KChar 'q') []) -> halt ui- VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui- VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui- _ -> helpHandle ui ev+ Help ->+ case ev of+ -- VtyEvent (EvKey (KChar 'q') []) -> halt+ VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw+ VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui+ _ -> helpHandle ev - Normal ->- case ev of- VtyEvent (EvKey (KChar 'q') []) -> halt ui- VtyEvent (EvKey KEsc []) -> continue $ resetScreens d ui- VtyEvent (EvKey (KChar c) []) | c == '?' -> continue $ setMode Help ui- AppEvent (DateChange old _) | isStandardPeriod p && p `periodContainsDate` old ->- continue $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui- where- p = reportPeriod ui- e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] ->- liftIO (uiReloadJournal copts d ui) >>= continue- VtyEvent (EvKey (KChar 'I') []) -> continue $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)- VtyEvent (EvKey (KChar 'a') []) -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> uiReloadJournalIfChanged copts d j ui- VtyEvent (EvKey (KChar 'A') []) -> suspendAndResume $ void (runIadd (journalFilePath j)) >> uiReloadJournalIfChanged copts d j ui- VtyEvent (EvKey (KChar 'T') []) -> continue $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui- VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor pos f) >> uiReloadJournalIfChanged copts d j ui- where- (pos,f) = case listSelectedElement rsList of- Nothing -> (endPosition, journalFilePath j)- Just (_, RegisterScreenItem{- rsItemTransaction=Transaction{tsourcepos=(SourcePos f l c,_)}}) -> (Just (unPos l, Just $ unPos c),f)+ Normal ->+ case ev of+ VtyEvent (EvKey (KChar 'q') []) -> halt+ VtyEvent (EvKey KEsc []) -> put' $ resetScreens d ui+ VtyEvent (EvKey (KChar c) []) | c == '?' -> put' $ setMode Help ui+ AppEvent (DateChange old _) | isStandardPeriod p && p `periodContainsDate` old ->+ put' $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui+ where+ p = reportPeriod ui+ e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] ->+ liftIO (uiReloadJournal copts d ui) >>= put'+ VtyEvent (EvKey (KChar 'I') []) -> put' $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)+ VtyEvent (EvKey (KChar 'a') []) -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> uiReloadJournalIfChanged copts d j ui+ VtyEvent (EvKey (KChar 'A') []) -> suspendAndResume $ void (runIadd (journalFilePath j)) >> uiReloadJournalIfChanged copts d j ui+ VtyEvent (EvKey (KChar 'T') []) -> put' $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui+ VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor pos f) >> uiReloadJournalIfChanged copts d j ui+ where+ (pos,f) = case listSelectedElement rsList of+ Nothing -> (endPosition, journalFilePath j)+ Just (_, RegisterScreenItem{+ rsItemTransaction=Transaction{tsourcepos=(SourcePos f' l c,_)}}) -> (Just (unPos l, Just $ unPos c),f') - -- display mode/query toggles- VtyEvent (EvKey (KChar 'B') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleConversionOp ui- VtyEvent (EvKey (KChar 'V') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleValue ui- VtyEvent (EvKey (KChar 'H') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleHistorical ui- VtyEvent (EvKey (KChar 't') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleTree ui- VtyEvent (EvKey (KChar c) []) | c `elem` ['z','Z'] -> rsCenterAndContinue $ regenerateScreens j d $ toggleEmpty ui- VtyEvent (EvKey (KChar 'R') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleReal ui- VtyEvent (EvKey (KChar 'U') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleUnmarked ui- VtyEvent (EvKey (KChar 'P') []) -> rsCenterAndContinue $ regenerateScreens j d $ togglePending ui- VtyEvent (EvKey (KChar 'C') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleCleared ui- VtyEvent (EvKey (KChar 'F') []) -> rsCenterAndContinue $ regenerateScreens j d $ toggleForecast d ui+ -- display mode/query toggles+ VtyEvent (EvKey (KChar 'B') []) -> rsCenterSelection (regenerateScreens j d $ toggleConversionOp ui) >>= put'+ VtyEvent (EvKey (KChar 'V') []) -> rsCenterSelection (regenerateScreens j d $ toggleValue ui) >>= put'+ VtyEvent (EvKey (KChar 'H') []) -> rsCenterSelection (regenerateScreens j d $ toggleHistorical ui) >>= put'+ VtyEvent (EvKey (KChar 't') []) -> rsCenterSelection (regenerateScreens j d $ toggleTree ui) >>= put'+ VtyEvent (EvKey (KChar c) []) | c `elem` ['z','Z'] -> rsCenterSelection (regenerateScreens j d $ toggleEmpty ui) >>= put'+ VtyEvent (EvKey (KChar 'R') []) -> rsCenterSelection (regenerateScreens j d $ toggleReal ui) >>= put'+ VtyEvent (EvKey (KChar 'U') []) -> rsCenterSelection (regenerateScreens j d $ toggleUnmarked ui) >>= put'+ VtyEvent (EvKey (KChar 'P') []) -> rsCenterSelection (regenerateScreens j d $ togglePending ui) >>= put'+ VtyEvent (EvKey (KChar 'C') []) -> rsCenterSelection (regenerateScreens j d $ toggleCleared ui) >>= put'+ VtyEvent (EvKey (KChar 'F') []) -> rsCenterSelection (regenerateScreens j d $ toggleForecast d ui) >>= put' - VtyEvent (EvKey (KChar '/') []) -> continue $ regenerateScreens j d $ showMinibuffer "filter" Nothing ui- VtyEvent (EvKey (KDown) [MShift]) -> continue $ regenerateScreens j d $ shrinkReportPeriod d ui- VtyEvent (EvKey (KUp) [MShift]) -> continue $ regenerateScreens j d $ growReportPeriod d ui- VtyEvent (EvKey (KRight) [MShift]) -> continue $ regenerateScreens j d $ nextReportPeriod journalspan ui- VtyEvent (EvKey (KLeft) [MShift]) -> continue $ regenerateScreens j d $ previousReportPeriod journalspan ui- VtyEvent (EvKey k []) | k `elem` [KBS, KDel] -> (continue $ regenerateScreens j d $ resetFilter ui)- VtyEvent (EvKey (KChar 'l') [MCtrl]) -> scrollSelectionToMiddle rsList >> redraw ui- VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui+ VtyEvent (EvKey (KChar '/') []) -> put' $ regenerateScreens j d $ showMinibuffer "filter" Nothing ui+ VtyEvent (EvKey (KDown) [MShift]) -> put' $ regenerateScreens j d $ shrinkReportPeriod d ui+ VtyEvent (EvKey (KUp) [MShift]) -> put' $ regenerateScreens j d $ growReportPeriod d ui+ VtyEvent (EvKey (KRight) [MShift]) -> put' $ regenerateScreens j d $ nextReportPeriod journalspan ui+ VtyEvent (EvKey (KLeft) [MShift]) -> put' $ regenerateScreens j d $ previousReportPeriod journalspan ui+ VtyEvent (EvKey k []) | k `elem` [KBS, KDel] -> (put' $ regenerateScreens j d $ resetFilter ui)+ VtyEvent (EvKey (KChar 'l') [MCtrl]) -> scrollSelectionToMiddle rsList >> redraw+ VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui - -- exit screen on LEFT- VtyEvent e | e `elem` moveLeftEvents -> continue $ popScreen ui- -- or on a click in the app's left margin. This is a VtyEvent since not in a clickable widget.- VtyEvent (EvMouseUp x _y (Just BLeft)) | x==0 -> continue $ popScreen ui- -- or on clicking a blank list item.- MouseUp _ (Just BLeft) Location{loc=(_,y)} | clickeddate == "" -> continue $ popScreen ui- where clickeddate = maybe "" rsItemDate $ listElements rsList !? y+ -- exit screen on LEFT+ VtyEvent e | e `elem` moveLeftEvents -> put' $ popScreen ui+ -- or on a click in the app's left margin. This is a VtyEvent since not in a clickable widget.+ VtyEvent (EvMouseUp x _y (Just BLeft)) | x==0 -> put' $ popScreen ui+ -- or on clicking a blank list item.+ MouseUp _ (Just BLeft) Location{loc=(_,y)} | clickeddate == "" -> put' $ popScreen ui+ where clickeddate = maybe "" rsItemDate $ listElements rsList !? y - -- enter transaction screen on RIGHT- VtyEvent e | e `elem` moveRightEvents ->- case listSelectedElement rsList of- Just _ -> continue $ screenEnter d transactionScreen{tsAccount=rsAccount} ui- Nothing -> continue ui- -- or on transaction click- -- MouseDown is sometimes duplicated, https://github.com/jtdaugherty/brick/issues/347- -- just use it to move the selection- MouseDown _n BLeft _mods Location{loc=(_x,y)} | not $ (=="") clickeddate -> do- continue $ ui{aScreen=s{rsList=listMoveTo y rsList}}- where clickeddate = maybe "" rsItemDate $ listElements rsList !? y- -- and on MouseUp, enter the subscreen- MouseUp _n (Just BLeft) Location{loc=(_x,y)} | not $ (=="") clickeddate -> do- continue $ screenEnter d transactionScreen{tsAccount=rsAccount} ui- where clickeddate = maybe "" rsItemDate $ listElements rsList !? y+ -- enter transaction screen on RIGHT+ VtyEvent e | e `elem` moveRightEvents ->+ case listSelectedElement rsList of+ Just _ -> put' $ screenEnter d transactionScreen{tsAccount=rsAccount} ui+ Nothing -> put' ui+ -- or on transaction click+ -- MouseDown is sometimes duplicated, https://github.com/jtdaugherty/brick/issues/347+ -- just use it to move the selection+ MouseDown _n BLeft _mods Location{loc=(_x,y)} | not $ (=="") clickeddate -> do+ put' $ ui{aScreen=scr{rsList=listMoveTo y rsList}}+ where clickeddate = maybe "" rsItemDate $ listElements rsList !? y+ -- and on MouseUp, enter the subscreen+ MouseUp _n (Just BLeft) Location{loc=(_x,y)} | not $ (=="") clickeddate -> do+ put' $ screenEnter d transactionScreen{tsAccount=rsAccount} ui+ where clickeddate = maybe "" rsItemDate $ listElements rsList !? y - -- when selection is at the last item, DOWN scrolls instead of moving, until maximally scrolled- VtyEvent e | e `elem` moveDownEvents, isBlankElement mnextelement -> do- vScrollBy (viewportScroll $ rsList ^. listNameL) 1 >> continue ui- where mnextelement = listSelectedElement $ listMoveDown rsList+ -- when selection is at the last item, DOWN scrolls instead of moving, until maximally scrolled+ VtyEvent e | e `elem` moveDownEvents, isBlankElement mnextelement -> do+ vScrollBy (viewportScroll $ rsList ^. listNameL) 1+ where mnextelement = listSelectedElement $ listMoveDown rsList - -- mouse scroll wheel scrolls the viewport up or down to its maximum extent,- -- pushing the selection when necessary.- MouseDown name btn _mods _loc | btn `elem` [BScrollUp, BScrollDown] -> do- let scrollamt = if btn==BScrollUp then -1 else 1- list' <- listScrollPushingSelection name rsList (rsListSize rsList) scrollamt- continue ui{aScreen=s{rsList=list'}}+ -- mouse scroll wheel scrolls the viewport up or down to its maximum extent,+ -- pushing the selection when necessary.+ MouseDown name btn _mods _loc | btn `elem` [BScrollUp, BScrollDown] -> do+ let scrollamt = if btn==BScrollUp then -1 else 1+ list' <- nestEventM' rsList $ listScrollPushingSelection name (rsListSize rsList) scrollamt+ put' ui{aScreen=scr{rsList=list'}} - -- if page down or end leads to a blank padding item, stop at last non-blank- VtyEvent e@(EvKey k []) | k `elem` [KPageDown, KEnd] -> do- list <- handleListEvent e rsList- if isBlankElement $ listSelectedElement list- then do- let list' = listMoveTo lastnonblankidx list- scrollSelectionToMiddle list'- continue ui{aScreen=s{rsList=list'}}- else- continue ui{aScreen=s{rsList=list}}+ -- if page down or end leads to a blank padding item, stop at last non-blank+ VtyEvent e@(EvKey k []) | k `elem` [KPageDown, KEnd] -> do+ l <- nestEventM' rsList $ handleListEvent e+ if isBlankElement $ listSelectedElement l+ then do+ let l' = listMoveTo lastnonblankidx l+ scrollSelectionToMiddle l'+ put' ui{aScreen=scr{rsList=l'}}+ else+ put' ui{aScreen=scr{rsList=l}} - -- fall through to the list's event handler (handles other [pg]up/down events)- VtyEvent ev -> do- let ev' = normaliseMovementKeys ev- newitems <- handleListEvent ev' rsList- continue ui{aScreen=s{rsList=newitems}}+ -- fall through to the list's event handler (handles other [pg]up/down events)+ VtyEvent e -> do+ let e' = normaliseMovementKeys e+ newitems <- nestEventM' rsList $ handleListEvent e'+ put' ui{aScreen=scr{rsList=newitems}} - MouseDown{} -> continue ui- MouseUp{} -> continue ui- AppEvent _ -> continue ui+ MouseDown{} -> return ()+ MouseUp{} -> return ()+ AppEvent _ -> return () -rsHandle _ _ = error "event handler called with wrong screen type, should not happen" -- PARTIAL:+ _ -> dlogUiTrace "rsHandle 2" $ errorWrongScreenType "event handler" isBlankElement mel = ((rsItemDate . snd) <$> mel) == Just "" -rsCenterAndContinue ui = do+rsCenterSelection :: UIState -> EventM Name UIState UIState+rsCenterSelection ui = do scrollSelectionToMiddle $ rsList $ aScreen ui- continue ui+ return ui -- ui is unchanged, but this makes the function more chainable rsListSize = V.length . V.takeWhile ((/="").rsItemDate) . listElements
Hledger/UI/Theme.hs view
@@ -65,42 +65,42 @@ themesList :: [(String, AttrMap)] themesList = [ ("default", attrMap (black `on` white) [- ("border" , white `on` black & dim)- ,("border" <> "bold" , currentAttr & bold)- ,("border" <> "depth" , active)- ,("border" <> "filename" , currentAttr)- ,("border" <> "key" , active)- ,("border" <> "minibuffer" , white `on` black & bold)- ,("border" <> "query" , active)- ,("border" <> "selected" , active)- ,("error" , fg red)- ,("help" , white `on` black & dim)- ,("help" <> "heading" , fg yellow)- ,("help" <> "key" , active)- -- ,("list" , black `on` white)- -- ,("list" <> "amount" , currentAttr)- ,("list" <> "amount" <> "decrease" , fg red)- -- ,("list" <> "amount" <> "increase" , fg green)- ,("list" <> "amount" <> "decrease" <> "selected" , red `on` selectbg & bold)- -- ,("list" <> "amount" <> "increase" <> "selected" , green `on` selectbg & bold)- ,("list" <> "balance" , currentAttr & bold)- ,("list" <> "balance" <> "negative" , fg red)- ,("list" <> "balance" <> "positive" , fg black)- ,("list" <> "balance" <> "negative" <> "selected" , red `on` selectbg & bold)- ,("list" <> "balance" <> "positive" <> "selected" , select & bold)- ,("list" <> "selected" , select)- -- ,("list" <> "accounts" , white `on` brightGreen)- -- ,("list" <> "selected" , black `on` brightYellow)+ (attrName "border" , white `on` black & dim)+ ,(attrName "border" <> attrName "bold" , currentAttr & bold)+ ,(attrName "border" <> attrName "depth" , active)+ ,(attrName "border" <> attrName "filename" , currentAttr)+ ,(attrName "border" <> attrName "key" , active)+ ,(attrName "border" <> attrName "minibuffer" , white `on` black & bold)+ ,(attrName "border" <> attrName "query" , active)+ ,(attrName "border" <> attrName "selected" , active)+ ,(attrName "error" , fg red)+ ,(attrName "help" , white `on` black & dim)+ ,(attrName "help" <> attrName "heading" , fg yellow)+ ,(attrName "help" <> attrName "key" , active)+ -- ,(attrName "list" , black `on` white)+ -- ,(attrName "list" <> attrName "amount" , currentAttr)+ ,(attrName "list" <> attrName "amount" <> attrName "decrease" , fg red)+ -- ,(attrName "list" <> attrName "amount" <> attrName "increase" , fg green)+ ,(attrName "list" <> attrName "amount" <> attrName "decrease" <> attrName "selected" , red `on` selectbg & bold)+ -- ,(attrName "list" <> attrName "amount" <> attrName "increase" <> attrName "selected" , green `on` selectbg & bold)+ ,(attrName "list" <> attrName "balance" , currentAttr & bold)+ ,(attrName "list" <> attrName "balance" <> attrName "negative" , fg red)+ ,(attrName "list" <> attrName "balance" <> attrName "positive" , fg black)+ ,(attrName "list" <> attrName "balance" <> attrName "negative" <> attrName "selected" , red `on` selectbg & bold)+ ,(attrName "list" <> attrName "balance" <> attrName "positive" <> attrName "selected" , select & bold)+ ,(attrName "list" <> attrName "selected" , select)+ -- ,(attrName "list" <> attrName "accounts" , white `on` brightGreen)+ -- ,(attrName "list" <> attrName "selected" , black `on` brightYellow) ]) ,("greenterm", attrMap (green `on` black) [- ("list" <> "selected" , black `on` green)+ (attrName "list" <> attrName "selected" , black `on` green) ]) ,("terminal", attrMap defAttr [- ("border" , white `on` black),- ("list" , defAttr),- ("list" <> "selected" , defAttr & reverseVideo)+ (attrName "border" , white `on` black),+ (attrName "list" , defAttr),+ (attrName "list" <> attrName "selected" , defAttr & reverseVideo) ]) ]
Hledger/UI/TransactionScreen.hs view
@@ -22,7 +22,7 @@ import Brick.Widgets.List (listElementsL, listMoveTo, listSelectedElement) import Hledger-import Hledger.Cli hiding (progname,prognameandversion)+import Hledger.Cli hiding (mode, prices, progname,prognameandversion) import Hledger.UI.UIOptions -- import Hledger.UI.Theme import Hledger.UI.UITypes@@ -59,7 +59,7 @@ seltxn = maybe nulltransaction (rsItemTransaction . snd) $ listSelectedElement xs nonblanks = V.toList . V.takeWhile (not . T.null . rsItemDate) $ xs ^. listElementsL _ -> (t, nts)-tsInit _ _ _ = error "init function called with wrong screen type, should not happen" -- PARTIAL:+tsInit _ _ _ = errorWrongScreenType "init function" -- PARTIAL: -- Render a transaction suitably for the transaction screen. showTxn :: ReportOpts -> ReportSpec -> Journal -> Transaction -> T.Text@@ -107,12 +107,12 @@ -- <+> str (" ("++show i++" of "++show (length nts)++" in "++acct++")") <+> (str $ "#" ++ show (tindex t)) <+> str " ("- <+> withAttr ("border" <> "bold") (str $ show i)+ <+> withAttr (attrName "border" <> attrName "bold") (str $ show i) <+> str (" of "++show (length nts)) <+> togglefilters <+> borderQueryStr (unwords . map (quoteIfNeeded . T.unpack) $ querystring_ ropts) <+> str (" in "++T.unpack (replaceHiddenAccountsNameWith "All" acct)++")")- <+> (if ignore_assertions_ . balancingopts_ $ inputopts_ copts then withAttr ("border" <> "query") (str " ignoring balance assertions") else str "")+ <+> (if ignore_assertions_ . balancingopts_ $ inputopts_ copts then withAttr (attrName "border" <> attrName "query") (str " ignoring balance assertions") else str "") where togglefilters = case concat [@@ -121,7 +121,7 @@ ,if empty_ ropts then [] else ["nonzero"] ] of [] -> str ""- fs -> withAttr ("border" <> "query") (str $ " " ++ intercalate ", " fs)+ fs -> withAttr (attrName "border" <> attrName "query") (str $ " " ++ intercalate ", " fs) bottomlabel = quickhelp -- case mode of@@ -139,72 +139,74 @@ ,("q", "quit") ] -tsDraw _ = error "draw function called with wrong screen type, should not happen" -- PARTIAL:+tsDraw _ = errorWrongScreenType "draw function" -- PARTIAL: -tsHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)-tsHandle ui@UIState{aScreen=TransactionScreen{tsTransaction=(i,t), tsTransactions=nts}- ,aopts=UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}}- ,ajournal=j- ,aMode=mode- }- ev =- case mode of- Help ->- case ev of- -- VtyEvent (EvKey (KChar 'q') []) -> halt ui- VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui- VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui- _ -> helpHandle ui ev+tsHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()+tsHandle ev = do+ ui0 <- get'+ case ui0 of+ ui@UIState{aScreen=TransactionScreen{tsTransaction=(i,t), tsTransactions=nts}+ ,aopts=UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}}+ ,ajournal=j+ ,aMode=mode+ } ->+ case mode of+ Help ->+ case ev of+ -- VtyEvent (EvKey (KChar 'q') []) -> halt+ VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw+ VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui+ _ -> helpHandle ev - _ -> do- let- d = copts^.rsDay- (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- VtyEvent (EvKey (KChar 'q') []) -> halt ui- VtyEvent (EvKey KEsc []) -> continue $ resetScreens d ui- VtyEvent (EvKey (KChar c) []) | c == '?' -> continue $ setMode Help ui- VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor pos f) >> uiReloadJournalIfChanged copts d j ui- where- (pos,f) = case tsourcepos t of- (SourcePos f l1 c1,_) -> (Just (unPos l1, Just $ unPos c1),f)- AppEvent (DateChange old _) | isStandardPeriod p && p `periodContainsDate` old ->- continue $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui- where- p = reportPeriod ui- e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] -> do- -- plog (if e == AppEvent FileChange then "file change" else "manual reload") "" `seq` return ()- ej <- liftIO . runExceptT $ journalReload copts- case ej of- Left err -> continue $ screenEnter d errorScreen{esError=err} ui- Right j' -> continue $ regenerateScreens j' d ui- VtyEvent (EvKey (KChar 'I') []) -> continue $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)+ _ -> do+ let+ d = copts^.rsDay+ (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+ VtyEvent (EvKey (KChar 'q') []) -> halt+ VtyEvent (EvKey KEsc []) -> put' $ resetScreens d ui+ VtyEvent (EvKey (KChar c) []) | c == '?' -> put' $ setMode Help ui+ VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor pos f) >> uiReloadJournalIfChanged copts d j ui+ where+ (pos,f) = case tsourcepos t of+ (SourcePos f' l1 c1,_) -> (Just (unPos l1, Just $ unPos c1),f')+ AppEvent (DateChange old _) | isStandardPeriod p && p `periodContainsDate` old ->+ put' $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui+ where+ p = reportPeriod ui+ e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] -> do+ -- plog (if e == AppEvent FileChange then "file change" else "manual reload") "" `seq` return ()+ ej <- liftIO . runExceptT $ journalReload copts+ case ej of+ Left err -> put' $ screenEnter d errorScreen{esError=err} ui+ Right j' -> put' $ regenerateScreens j' d ui+ VtyEvent (EvKey (KChar 'I') []) -> put' $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui) - -- for toggles that may change the current/prev/next transactions,- -- we must regenerate the transaction list, like the g handler above ? with regenerateTransactions ? TODO WIP- -- 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- VtyEvent (EvKey (KChar 'B') []) -> continue . regenerateScreens j d $ toggleConversionOp ui- VtyEvent (EvKey (KChar 'V') []) -> continue . regenerateScreens j d $ toggleValue ui+ -- for toggles that may change the current/prev/next transactions,+ -- we must regenerate the transaction list, like the g handler above ? with regenerateTransactions ? TODO WIP+ -- EvKey (KChar 'E') [] -> put' $ regenerateScreens j d $ stToggleEmpty ui+ -- EvKey (KChar 'C') [] -> put' $ regenerateScreens j d $ stToggleCleared ui+ -- EvKey (KChar 'R') [] -> put' $ regenerateScreens j d $ stToggleReal ui+ VtyEvent (EvKey (KChar 'B') []) -> put' . regenerateScreens j d $ toggleConversionOp ui+ VtyEvent (EvKey (KChar 'V') []) -> put' . regenerateScreens j d $ toggleValue ui - VtyEvent e | e `elem` moveUpEvents -> continue $ tsSelect iprev tprev ui- VtyEvent e | e `elem` moveDownEvents -> continue $ tsSelect inext tnext ui+ VtyEvent e | e `elem` moveUpEvents -> put' $ tsSelect iprev tprev ui+ VtyEvent e | e `elem` moveDownEvents -> put' $ tsSelect inext tnext ui - -- exit screen on LEFT- VtyEvent e | e `elem` moveLeftEvents -> continue . popScreen $ tsSelect i t ui -- Probably not necessary to tsSelect here, but it's safe.- -- or on a click in the app's left margin.- VtyEvent (EvMouseUp x _y (Just BLeft)) | x==0 -> continue . popScreen $ tsSelect i t ui- -- or on clicking the blank area below the transaction.- MouseUp _ (Just BLeft) Location{loc=(_,y)} | y+1 > numentrylines -> continue . popScreen $ tsSelect i t ui- where numentrylines = length (T.lines $ showTxn ropts rspec j t) - 1+ -- exit screen on LEFT+ VtyEvent e | e `elem` moveLeftEvents -> put' . popScreen $ tsSelect i t ui -- Probably not necessary to tsSelect here, but it's safe.+ -- or on a click in the app's left margin.+ VtyEvent (EvMouseUp x _y (Just BLeft)) | x==0 -> put' . popScreen $ tsSelect i t ui+ -- or on clicking the blank area below the transaction.+ MouseUp _ (Just BLeft) Location{loc=(_,y)} | y+1 > numentrylines -> put' . popScreen $ tsSelect i t ui+ where numentrylines = length (T.lines $ showTxn ropts rspec j t) - 1 - VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw ui- VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui- _ -> continue ui+ VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw+ VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui+ _ -> return () -tsHandle _ _ = error "event handler called with wrong screen type, should not happen" -- PARTIAL:+ _ -> errorWrongScreenType "event handler" -- | Select a new transaction and update the previous register screen tsSelect i t ui@UIState{aScreen=s@TransactionScreen{}} = case aPrevScreens ui of
Hledger/UI/UIState.hs view
@@ -210,7 +210,7 @@ -- | Apply a new filter query, or return the failing query. setFilter :: String -> UIState -> Either String UIState-setFilter s = first (const s) . setEither querystring (words'' prefixes $ T.pack s)+setFilter s = first (const s) . setEither querystring (words'' queryprefixes $ T.pack s) -- | Reset some filters & toggles. resetFilter :: UIState -> UIState@@ -286,11 +286,11 @@ -- 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+ frst:rest = reverse $ s:ss :: [Screen]+ ui0 = ui{ajournal=j, aScreen=frst, aPrevScreens=[]} :: UIState - ui1 = (sInit first) d False ui0 :: UIState- ui2 = foldl' (\ui s -> (sInit s) d False $ pushScreen s ui) ui1 rest :: UIState+ ui1 = (sInit frst) d False ui0 :: UIState+ ui2 = foldl' (\ui' s' -> (sInit s') d False $ pushScreen s' ui') ui1 rest :: UIState in ui2
Hledger/UI/UITypes.hs view
@@ -33,11 +33,14 @@ {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TemplateHaskell #-} module Hledger.UI.UITypes where +-- import Control.Concurrent (threadDelay)+-- import GHC.IO (unsafePerformIO) import Data.Text (Text) import Data.Time.Calendar (Day) import Brick@@ -100,7 +103,7 @@ AccountsScreen { 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 -> BrickEvent Name AppEvent -> EventM Name (Next UIState) -- ^ brick event handler for this screen+ ,sHandle :: BrickEvent Name AppEvent -> EventM Name 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 "")@@ -108,7 +111,7 @@ | RegisterScreen { sInit :: Day -> Bool -> UIState -> UIState ,sDraw :: UIState -> [Widget Name]- ,sHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)+ ,sHandle :: BrickEvent Name AppEvent -> EventM Name UIState () -- ,rsList :: List Name RegisterScreenItem -- ^ list widget showing transactions affecting this account ,rsAccount :: AccountName -- ^ the account this register is for@@ -119,7 +122,7 @@ | TransactionScreen { sInit :: Day -> Bool -> UIState -> UIState ,sDraw :: UIState -> [Widget Name]- ,sHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)+ ,sHandle :: BrickEvent Name AppEvent -> EventM Name UIState () -- ,tsTransaction :: NumberedTransaction -- ^ the transaction we are currently viewing, and its position in the list ,tsTransactions :: [NumberedTransaction] -- ^ list of transactions we can step through@@ -128,11 +131,18 @@ | ErrorScreen { sInit :: Day -> Bool -> UIState -> UIState ,sDraw :: UIState -> [Widget Name]- ,sHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)+ ,sHandle :: BrickEvent Name AppEvent -> EventM Name UIState () -- ,esError :: String -- ^ error message to show } deriving (Show)+-- XXX check for ideas: https://github.com/jtdaugherty/brick/issues/379#issuecomment-1191993357++-- | Error message to use in case statements adapting to the different Screen shapes.+errorWrongScreenType :: String -> a+errorWrongScreenType lbl =+ -- unsafePerformIO $ threadDelay 2000000 >> -- delay to allow console output to be seen+ error' (unwords [lbl, "called with wrong screen type, should not happen"]) -- | An item in the accounts screen's list of accounts and balances. data AccountsScreenItem = AccountsScreenItem {
Hledger/UI/UIUtils.hs view
@@ -23,10 +23,17 @@ ,renderToggle1 ,replaceHiddenAccountsNameWith ,scrollSelectionToMiddle+ ,get'+ ,put'+ ,modify' ,suspend ,redraw ,reportSpecSetFutureAndForecast ,listScrollPushingSelection+ ,dlogUiTrace+ ,dlogUiTraceM+ ,uiDebugLevel+ ,uiNumBlankItems ) where @@ -35,7 +42,7 @@ import Brick.Widgets.Border.Style import Brick.Widgets.Dialog import Brick.Widgets.Edit-import Brick.Widgets.List (List, listSelectedL, listNameL, listItemHeightL, listSelected, listMoveDown, listMoveUp, GenericList, Splittable)+import Brick.Widgets.List (List, listSelectedL, listNameL, listItemHeightL, listSelected, listMoveDown, listMoveUp, GenericList) import Control.Monad.IO.Class import Data.Bifunctor (second) import Data.List@@ -60,20 +67,38 @@ suspendSignal = return () #else import System.Posix.Signals+import Data.Vector (Vector) suspendSignal :: IO () suspendSignal = raiseSignal sigSTOP #endif +-- Debug logging for UI state changes.++get' = do+ x <- get+ dlogUiTraceM $ "getting state: " ++ (head $ lines $ pshow $ aScreen x)+ return x++put' x = do+ dlogUiTraceM $ "putting state: " ++ (head $ lines $ pshow $ aScreen x)+ put x++modify' f = do+ x <- get+ let x' = f x+ dlogUiTraceM $ "modifying state: " ++ (head $ lines $ pshow $ aScreen x')+ modify f+ -- | On posix platforms, suspend the program using the STOP signal, -- like control-z in bash, returning to the original shell prompt, -- and when resumed, continue where we left off. -- On windows, does nothing.-suspend :: s -> EventM a (Next s)+suspend :: Ord a => s -> EventM a s () suspend st = suspendAndResume $ suspendSignal >> return st --- | Tell vty to redraw the whole screen, and continue.-redraw :: s -> EventM a (Next s)-redraw st = getVtyHandle >>= liftIO . refresh >> continue st+-- | Tell vty to redraw the whole screen.+redraw :: EventM a s ()+redraw = getVtyHandle >>= liftIO . refresh -- | Wrap a widget in the default hledger-ui screen layout. defaultLayout :: Widget Name -> Widget Name -> Widget Name -> Widget Name@@ -90,14 +115,14 @@ Widget Fixed Fixed $ do c <- getContext render $- withDefAttr "help" $+ withDefAttr (attrName "help") $ renderDialog (dialog (Just "Help (LEFT/ESC/?/q to close help)") Nothing (c^.availWidthL)) $ -- (Just (0,[("ok",())])) padTop (Pad 0) $ padLeft (Pad 1) $ padRight (Pad 1) $ vBox [ hBox [ padRight (Pad 1) $ vBox [- withAttr ("help" <> "heading") $ str "Navigation"+ withAttr (attrName "help" <> attrName "heading") $ str "Navigation" ,renderKey ("UP/DOWN/PUP/PDN/HOME/END/k/j/C-p/C-n", "") ,str " move selection up/down" ,renderKey ("RIGHT/l/C-f", "show txns, or txn detail")@@ -105,23 +130,23 @@ ,renderKey ("ESC ", "cancel, or reset app state") ,str " "- ,withAttr ("help" <> "heading") $ str "Accounts screen"+ ,withAttr (attrName "help" <> attrName "heading") $ str "Accounts screen" ,renderKey ("1234567890-+ ", "set/adjust depth limit") ,renderKey ("t ", "toggle accounts tree/list mode") ,renderKey ("H ", "toggle historical balance/change") ,str " "- ,withAttr ("help" <> "heading") $ str "Register screen"+ ,withAttr (attrName "help" <> attrName "heading") $ str "Register screen" ,renderKey ("t ", "toggle subaccount txns\n(and accounts tree/list mode)") ,renderKey ("H ", "toggle historical/period total") ,str " "- ,withAttr ("help" <> "heading") $ str "Help"+ ,withAttr (attrName "help" <> attrName "heading") $ str "Help" ,renderKey ("? ", "toggle this help") ,renderKey ("p/m/i", "while help is open:\nshow manual in pager/man/info") ,str " " ] ,padLeft (Pad 1) $ padRight (Pad 0) $ vBox [- withAttr ("help" <> "heading") $ str "Filtering"+ withAttr (attrName "help" <> attrName "heading") $ str "Filtering" ,renderKey ("/ ", "set a filter query") ,renderKey ("F ", "show future & periodic txns") ,renderKey ("R ", "show real/all postings")@@ -132,7 +157,7 @@ ,renderKey ("T ", "set period to today") ,renderKey ("DEL ", "reset filters") ,str " "- ,withAttr ("help" <> "heading") $ str "Other"+ ,withAttr (attrName "help" <> attrName "heading") $ str "Other" ,renderKey ("a ", "add transaction (hledger add)") ,renderKey ("A ", "add transaction (hledger-iadd)") ,renderKey ("B ", "show amounts/costs")@@ -160,39 +185,40 @@ -- ] ] where- renderKey (key,desc) = withAttr ("help" <> "key") (str key) <+> str " " <+> str desc+ renderKey (key,desc) = withAttr (attrName "help" <> attrName "key") (str key) <+> str " " <+> str desc -- | Event handler used when help mode is active. -- May invoke $PAGER, less, man or info, which is likely to fail on MS Windows, TODO.-helpHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)-helpHandle ui ev = do+helpHandle :: BrickEvent Name AppEvent -> EventM Name UIState ()+helpHandle ev = do+ ui <- get+ let ui' = setMode Normal ui case ev of- VtyEvent e | e `elem` closeHelpEvents -> continue $ setMode Normal ui- VtyEvent (EvKey (KChar 'p') []) -> suspendAndResume $ runPagerForTopic "hledger-ui" Nothing >> return ui'- VtyEvent (EvKey (KChar 'm') []) -> suspendAndResume $ runManForTopic "hledger-ui" Nothing >> return ui'- VtyEvent (EvKey (KChar 'i') []) -> suspendAndResume $ runInfoForTopic "hledger-ui" Nothing >> return ui'- _ -> continue ui+ VtyEvent e | e `elem` closeHelpEvents -> put' ui'+ VtyEvent (EvKey (KChar 'p') []) -> suspendAndResume (runPagerForTopic "hledger-ui" Nothing >> return ui')+ VtyEvent (EvKey (KChar 'm') []) -> suspendAndResume (runManForTopic "hledger-ui" Nothing >> return ui')+ VtyEvent (EvKey (KChar 'i') []) -> suspendAndResume (runInfoForTopic "hledger-ui" Nothing >> return ui')+ _ -> return () where- ui' = setMode Normal ui closeHelpEvents = moveLeftEvents ++ [EvKey KEsc [], EvKey (KChar '?') [], EvKey (KChar 'q') []] -- | Draw the minibuffer with the given label. minibuffer :: T.Text -> Editor String Name -> Widget Name minibuffer string ed =- forceAttr ("border" <> "minibuffer") $+ forceAttr (attrName "border" <> attrName "minibuffer") $ hBox [txt $ string <> ": ", renderEditor (str . unlines) True ed] borderQueryStr :: String -> Widget Name borderQueryStr "" = str ""-borderQueryStr qry = str " matching " <+> withAttr ("border" <> "query") (str qry)+borderQueryStr qry = str " matching " <+> withAttr (attrName "border" <> attrName "query") (str qry) borderDepthStr :: Maybe Int -> Widget Name borderDepthStr Nothing = str ""-borderDepthStr (Just d) = str " to depth " <+> withAttr ("border" <> "query") (str $ show d)+borderDepthStr (Just d) = str " to depth " <+> withAttr (attrName "border" <> attrName "query") (str $ show d) borderPeriodStr :: String -> Period -> Widget Name borderPeriodStr _ PeriodAll = str ""-borderPeriodStr preposition p = str (" "++preposition++" ") <+> withAttr ("border" <> "query") (str . T.unpack $ showPeriod p)+borderPeriodStr preposition p = str (" "++preposition++" ") <+> withAttr (attrName "border" <> attrName "query") (str . T.unpack $ showPeriod p) borderKeysStr :: [(String,String)] -> Widget Name borderKeysStr = borderKeysStr' . map (second str)@@ -201,7 +227,7 @@ borderKeysStr' keydescs = hBox $ intersperse sep $- [withAttr ("border" <> "key") (str keys) <+> str ":" <+> desc | (keys, desc) <- keydescs]+ [withAttr (attrName "border" <> attrName "key") (str keys) <+> str ":" <+> desc | (keys, desc) <- keydescs] where -- sep = str " | " sep = str " "@@ -209,7 +235,7 @@ -- | Show both states of a toggle ("aaa/bbb"), highlighting the active one. renderToggle :: Bool -> String -> String -> Widget Name renderToggle isright l r =- let bold = withAttr ("border" <> "selected") in+ let bold = withAttr (attrName "border" <> attrName "selected") in if isright then str (l++"/") <+> bold (str r) else bold (str l) <+> str ("/"++r)@@ -217,7 +243,7 @@ -- | Show a toggle's label, highlighted (bold) when the toggle is active. renderToggle1 :: Bool -> String -> Widget Name renderToggle1 isactive l =- let bold = withAttr ("border" <> "selected") in+ let bold = withAttr (attrName "border" <> attrName "selected") in if isactive then bold (str l) else str l@@ -262,11 +288,11 @@ "" -- " debug: "++show (_w,h') render $- hBorderWithLabel (withAttr "border" $ toplabel <+> str debugmsg)+ hBorderWithLabel (withAttr (attrName "border") $ toplabel <+> str debugmsg) <=> body' <=>- hBorderWithLabel (withAttr "border" bottomlabel)+ hBorderWithLabel (withAttr (attrName "border") bottomlabel) ---- XXX should be equivalent to the above, but isn't (page down goes offscreen) --_topBottomBorderWithLabel2 :: Widget Name -> Widget Name -> Widget Name@@ -286,8 +312,8 @@ -- 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- let w' = vLimit (c^.availHeightL - v*2) $ hLimit (c^.availWidthL - h*2) w+ ctx <- getContext+ let w' = vLimit (ctx^.availHeightL - v*2) $ hLimit (ctx^.availWidthL - h*2) w attr = maybe currentAttr (\c -> c `on` c) mcolour render $ withBorderAttr attr $@@ -303,7 +329,7 @@ -- applyN n border withBorderAttr :: Attr -> Widget Name -> Widget Name-withBorderAttr attr = updateAttrMap (applyAttrMappings [("border", attr)])+withBorderAttr attr = updateAttrMap (applyAttrMappings [(attrName "border", attr)]) ---- | Like brick's continue, but first run some action to modify brick's state. ---- This action does not affect the app state, but might eg adjust a widget's scroll position.@@ -319,7 +345,7 @@ -- | Scroll a list's viewport so that the selected item is centered in the -- middle of the display area.-scrollSelectionToMiddle :: List Name e -> EventM Name ()+scrollSelectionToMiddle :: List Name item -> EventM Name UIState () scrollSelectionToMiddle list = do case list^.listSelectedL of Nothing -> return ()@@ -349,8 +375,8 @@ -- and forecast transactions (generated by --forecast), if the given forecast DateSpan is Nothing, -- and include them otherwise. reportSpecSetFutureAndForecast :: Day -> Maybe DateSpan -> ReportSpec -> ReportSpec-reportSpecSetFutureAndForecast d forecast rspec =- rspec{_rsQuery=simplifyQuery $ And [_rsQuery rspec, periodq, excludeforecastq forecast]}+reportSpecSetFutureAndForecast d fcast rspec =+ rspec{_rsQuery=simplifyQuery $ And [_rsQuery rspec, periodq, excludeforecastq fcast]} where periodq = Date . periodAsDateSpan . period_ $ _rsReportOpts rspec -- Except in forecast mode, exclude future/forecast transactions.@@ -364,9 +390,9 @@ -- Vertically scroll the named list's viewport with the given number of non-empty items -- by the given positive or negative number of items (usually 1 or -1). -- The selection will be moved when necessary to keep it visible and allow the scroll.-listScrollPushingSelection :: (Ord n, Foldable t, Splittable t) => - n -> GenericList n t e -> Int -> Int -> EventM n (GenericList n t e)-listScrollPushingSelection name list listheight scrollamt = do+listScrollPushingSelection :: Name -> Int -> Int -> EventM Name (List Name item) (GenericList Name Vector item)+listScrollPushingSelection name listheight scrollamt = do+ list <- get viewportScroll name `vScrollBy` scrollamt mvp <- lookupViewport name case mvp of@@ -381,3 +407,24 @@ | otherwise = id _ -> return list _ -> return list++-- | Log a string to ./debug.log before returning the second argument,+-- if the global debug level is at or above a standard hledger-ui debug level.+-- Uses unsafePerformIO.+dlogUiTrace :: String -> a -> a+dlogUiTrace = dlogTraceAt uiDebugLevel++-- | Like dlogUiTrace, but within the hledger-ui brick event handler monad.+dlogUiTraceM :: String -> EventM Name UIState ()+dlogUiTraceM s = dlogUiTrace s $ return ()++-- | Log hledger-ui events at this debug level.+uiDebugLevel :: Int+uiDebugLevel = 2++-- | How many blank items to add to lists to fill the full window height.+uiNumBlankItems :: Int+uiNumBlankItems+ -- | debugLevel >= uiDebugLevel = 0 -- suppress to improve debug output.+ -- | otherwise + = 100 -- 100 ought to be enough for anyone
hledger-ui.1 view
@@ -1,5 +1,5 @@ -.TH "HLEDGER-UI" "1" "July 2022" "hledger-ui-1.26.1 " "hledger User Manuals"+.TH "HLEDGER-UI" "1" "September 2022" "hledger-ui-1.27 " "hledger User Manuals" @@ -7,7 +7,7 @@ .PP hledger-ui is a terminal interface (TUI) for the hledger accounting tool.-This manual is for hledger-ui 1.26.1.+This manual is for hledger-ui 1.27. .SH SYNOPSIS .PP \f[C]hledger-ui [OPTIONS] [QUERYARGS]\f[R]@@ -557,9 +557,10 @@ reboot. .PP On Windows computers, the default value is probably-\f[C]C:\[rs]Users\[rs]MyUserName\[rs].hledger.journal\f[R].+\f[C]C:\[rs]Users\[rs]YOURNAME\[rs].hledger.journal\f[R]. You can change this by running a command like this in a powershell-window:+window (let us know if you need to be an Administrator, and if this+persists across a reboot): .IP .nf \f[C]@@ -567,8 +568,8 @@ \f[R] .fi .PP-(Let us know if you need to be an Administrator, and if this persists-across a reboot.)+Or, change it in settings: see+https://www.java.com/en/download/help/path.html. .SH FILES .PP Reads data from one or more files in hledger journal, timeclock,
hledger-ui.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: hledger-ui-version: 1.26.1+version: 1.27 synopsis: Curses-style terminal interface for the hledger accounting system description: A simple curses-style terminal user interface for the hledger accounting system. It can be a more convenient way to browse your accounts than the CLI.@@ -27,7 +27,7 @@ license-file: LICENSE build-type: Simple tested-with:- GHC==8.6.5, GHC==8.8.4, GHC==8.10.4, GHC==9.0.1+ GHC==8.10.7, GHC==9.0.2, GHC==9.2.4 extra-source-files: CHANGES.md README.md@@ -62,13 +62,14 @@ Paths_hledger_ui hs-source-dirs: ./- ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-missing-signatures -Wno-name-shadowing -Wno-orphans -Wno-type-defaults -Wno-unused-do-bind- cpp-options: -DVERSION="1.26.1"+ ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-missing-signatures -Wno-orphans -Wno-type-defaults -Wno-unused-do-bind+ cpp-options: -DVERSION="1.27" build-depends: ansi-terminal >=0.9 , async- , base >=4.11 && <4.17- , brick >=0.23+ , base >=4.14 && <4.17+ , breakpoint+ , brick >=1.0 , cmdargs >=0.8 , containers >=0.5.9 , data-default@@ -77,8 +78,8 @@ , extra >=1.6.3 , filepath , fsnotify >=0.2.1.2 && <0.4- , hledger >=1.26.1 && <1.27- , hledger-lib >=1.26.1 && <1.27+ , hledger ==1.27.*+ , hledger-lib ==1.27.* , megaparsec >=7.0.0 && <9.3 , microlens >=0.4 , microlens-platform >=0.2.3.1
hledger-ui.info view
@@ -12,7 +12,7 @@ ************* hledger-ui is a terminal interface (TUI) for the hledger accounting-tool. This manual is for hledger-ui 1.26.1.+tool. This manual is for hledger-ui 1.27. 'hledger-ui [OPTIONS] [QUERYARGS]' 'hledger ui -- [OPTIONS] [QUERYARGS]'@@ -589,13 +589,14 @@ For this to take effect you might need to 'killall Dock', or reboot. On Windows computers, the default value is probably-'C:\Users\MyUserName\.hledger.journal'. You can change this by running-a command like this in a powershell window:+'C:\Users\YOURNAME\.hledger.journal'. You can change this by running a+command like this in a powershell window (let us know if you need to be+an Administrator, and if this persists across a reboot): > setx LEDGER_FILE "C:\Users\MyUserName\finance\2021.journal" - (Let us know if you need to be an Administrator, and if this persists-across a reboot.)+ Or, change it in settings: see+https://www.java.com/en/download/help/path.html. File: hledger-ui.info, Node: FILES, Next: BUGS, Prev: ENVIRONMENT, Up: Top@@ -639,34 +640,34 @@ Tag Table: Node: Top221-Node: OPTIONS1656-Ref: #options1754-Node: MOUSE6636-Ref: #mouse6731-Node: KEYS7013-Ref: #keys7106-Node: SCREENS11192-Ref: #screens11290-Node: Accounts screen11380-Ref: #accounts-screen11508-Node: Register screen13847-Ref: #register-screen14002-Node: Transaction screen15986-Ref: #transaction-screen16144-Node: Error screen17014-Ref: #error-screen17136-Node: TIPS17380-Ref: #tips17479-Node: Watch mode17531-Ref: #watch-mode17648-Node: Watch mode limitations18398-Ref: #watch-mode-limitations18539-Node: ENVIRONMENT19675-Ref: #environment19786-Node: FILES21094-Ref: #files21193-Node: BUGS21406-Ref: #bugs21483+Node: OPTIONS1654+Ref: #options1752+Node: MOUSE6634+Ref: #mouse6729+Node: KEYS7011+Ref: #keys7104+Node: SCREENS11190+Ref: #screens11288+Node: Accounts screen11378+Ref: #accounts-screen11506+Node: Register screen13845+Ref: #register-screen14000+Node: Transaction screen15984+Ref: #transaction-screen16142+Node: Error screen17012+Ref: #error-screen17134+Node: TIPS17378+Ref: #tips17477+Node: Watch mode17529+Ref: #watch-mode17646+Node: Watch mode limitations18396+Ref: #watch-mode-limitations18537+Node: ENVIRONMENT19673+Ref: #environment19784+Node: FILES21169+Ref: #files21268+Node: BUGS21481+Ref: #bugs21558 End Tag Table
hledger-ui.txt view
@@ -5,7 +5,7 @@ NAME hledger-ui is a terminal interface (TUI) for the hledger accounting- tool. This manual is for hledger-ui 1.26.1.+ tool. This manual is for hledger-ui 1.27. SYNOPSIS hledger-ui [OPTIONS] [QUERYARGS]@@ -489,23 +489,24 @@ For this to take effect you might need to killall Dock, or reboot. - On Windows computers, the default value is probably C:\Users\MyUser-- Name\.hledger.journal. You can change this by running a command like- this in a powershell window:+ On Windows computers, the default value is probably C:\Users\YOUR-+ NAME\.hledger.journal. You can change this by running a command like+ this in a powershell window (let us know if you need to be an Adminis-+ trator, and if this persists across a reboot): > setx LEDGER_FILE "C:\Users\MyUserName\finance\2021.journal" - (Let us know if you need to be an Administrator, and if this persists- across a reboot.)+ Or, change it in settings: see https://www.java.com/en/down-+ load/help/path.html. 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+ 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-+ The need to precede options with -- when invoked from hledger is awk- ward. -f- doesn't work (hledger-ui can't read from stdin).@@ -513,13 +514,13 @@ -V affects only the accounts screen. When you press g, the current and all previous screens are regenerated,- which may cause a noticeable pause with large files. Also there is no+ which may cause a noticeable pause with large files. Also there is no visual indication that this is in progress. - --watch is not yet fully robust. It works well for normal usage, but- many file changes in a short time (eg saving the file thousands of- times with an editor macro) can cause problems at least on OSX. Symp-- toms include: unresponsive UI, periodic resetting of the cursor posi-+ --watch is not yet fully robust. It works well for normal usage, but+ many file changes in a short time (eg saving the file thousands of+ times with an editor macro) can cause problems at least on OSX. Symp-+ toms include: unresponsive UI, periodic resetting of the cursor posi- tion, momentary display of parse errors, high CPU usage eventually sub- siding, and possibly a small but persistent build-up of CPU usage until the program is restarted.@@ -530,7 +531,7 @@ REPORTING BUGS- Report bugs at http://bugs.hledger.org (or on the #hledger IRC channel+ Report bugs at http://bugs.hledger.org (or on the #hledger IRC channel or hledger mail list) @@ -548,4 +549,4 @@ -hledger-ui-1.26.1 July 2022 HLEDGER-UI(1)+hledger-ui-1.27 September 2022 HLEDGER-UI(1)