hledger-ui 1.50 → 1.50.1
raw patch · 13 files changed
+248/−130 lines, 13 filesdep +stmdep ~hledgerdep ~hledger-libPVP ok
version bump matches the API change (PVP)
Dependencies added: stm
Dependency ranges changed: hledger, hledger-lib
API changes (from Hackage documentation)
Files
- CHANGES.md +30/−0
- Hledger/UI/AccountsScreen.hs +7/−7
- Hledger/UI/ErrorScreen.hs +58/−36
- Hledger/UI/Main.hs +1/−1
- Hledger/UI/MenuScreen.hs +7/−8
- Hledger/UI/RegisterScreen.hs +10/−15
- Hledger/UI/TransactionScreen.hs +74/−19
- Hledger/UI/UIState.hs +23/−17
- Hledger/UI/UIUtils.hs +10/−0
- hledger-ui.1 +2/−2
- hledger-ui.cabal +7/−6
- hledger-ui.info +17/−17
- hledger-ui.txt +2/−2
CHANGES.md view
@@ -23,6 +23,36 @@ See also the hledger changelog. +# 1.50.1 2025-09-16++Fixes++- The transaction screen and error screen now update on data changes like other screens,+ eg when using the E key, g key, or --watch.+ [#2014], [#2288]++- When the journal is reloaded by the `g` key or `--watch`, the+ --pivot (and --obfuscate) options are now preserved,+ and spurious errors are avoided.+ [#2451]++- The Z key (and the -E command line flag) toggles zero-balance accounts again.+ (Stephen Morgan, [#2454])++Improvements++- Debug output has improved, eg it's easier to see changes to the screen stack.++API++- Hledger.UI.ErrorScreen:+ uiReloadJournal -> uiReload,+ uiReloadJournalIfChanged -> uiReloadIfFileChanged+- Hledger.UI.UIState:+ enableForecastPreservingPeriod -> enableForecast+++ # 1.50 2025-09-03 Breaking changes
Hledger/UI/AccountsScreen.hs view
@@ -48,7 +48,7 @@ import Hledger.UI.UIUtils import Hledger.UI.UIScreens import Hledger.UI.Editor-import Hledger.UI.ErrorScreen (uiReloadJournal, uiCheckBalanceAssertions, uiReloadJournalIfChanged)+import Hledger.UI.ErrorScreen (uiCheckBalanceAssertions, uiReload, uiReloadIfFileChanged) import Hledger.UI.RegisterScreen (rsCenterSelection) import Data.Either (fromRight) import Control.Arrow ((>>>))@@ -69,7 +69,7 @@ asDrawHelper UIState{aScreen=scr, aopts=uopts, ajournal=j, aMode=mode} ropts scrname = dbgui "asDrawHelper" $ case toAccountsLikeScreen scr of- Nothing -> dbgui "asDrawHelper" $ errorWrongScreenType "draw helper" -- PARTIAL:+ Nothing -> dbgui "asDrawHelper" $ errorWrongScreenType "asDrawHelper" -- PARTIAL: Just (ALS _ ass) -> case mode of Help -> [helpDialog, maincontent] _ -> [maincontent]@@ -189,7 +189,7 @@ dbguiEv "asHandle" ui0@UIState{aScreen=scr, aMode=mode} <- get' case toAccountsLikeScreen scr of- Nothing -> dbgui "asHandle" $ errorWrongScreenType "event handler" -- PARTIAL:+ Nothing -> dbgui "asHandle" $ errorWrongScreenType "asHandle" -- PARTIAL: Just als@(ALS scons ass) -> do -- save the currently selected account, in case we leave this screen and lose the selection put' ui0{aScreen=scons ass{_assSelectedAccount=asSelectedAccount ass}}@@ -231,7 +231,7 @@ -- XXX be sure we don't leave unconsumed app events piling up -- A data file has changed (or the user has pressed g): reload. e | e `elem` [AppEvent FileChange, VtyEvent (EvKey (KChar 'g') [])] ->- liftIO (uiReloadJournal copts d ui) >>= put'+ uiReload copts d ui >>= put' -- The date has changed (and we are viewing a standard period which contained the old date): -- adjust the viewed period and regenerate, just in case needed.@@ -245,9 +245,9 @@ VtyEvent (EvKey k []) | k `elem` [KBS, KDel] -> modify' (resetFilter >>> regenerateScreens j d) -- run external programs:- VtyEvent (EvKey (KChar 'a') []) -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add (cliOptsDropArgs 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 'a') []) -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add (cliOptsDropArgs copts) j >> uiReloadIfFileChanged copts d j ui+ VtyEvent (EvKey (KChar 'A') []) -> suspendAndResume $ void (runIadd (journalFilePath j)) >> uiReloadIfFileChanged copts d j ui+ VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor endPosition (journalFilePath j)) >> uiReloadIfFileChanged copts d j ui -- adjust the period displayed: VtyEvent (EvKey (KChar 'T') []) -> modify' (setReportPeriod (DayPeriod d) >>> regenerateScreens j d)
Hledger/UI/ErrorScreen.hs view
@@ -11,6 +11,8 @@ ,esDraw ,esHandle ,uiCheckBalanceAssertions+ ,uiReload+ ,uiReloadIfFileChanged ,uiReloadJournal ,uiReloadJournalIfChanged )@@ -18,12 +20,12 @@ import Brick -- import Brick.Widgets.Border ("border")-import Control.Monad import Control.Monad.IO.Class (liftIO) import Data.Time.Calendar (Day) import Data.Void (Void) import Graphics.Vty (Event(..),Key(..),Modifier(..)) import Lens.Micro ((^.))+import Safe (headMay) import Text.Megaparsec import Text.Megaparsec.Char @@ -35,6 +37,10 @@ import Hledger.UI.UIScreens import Hledger.UI.Editor +-- legacy aliases+uiReloadJournal = uiReload+uiReloadJournalIfChanged = uiReloadIfFileChanged+ esDraw :: UIState -> [Widget Name] esDraw UIState{aScreen=ES ESS{..} ,aMode=mode@@ -88,24 +94,42 @@ 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 _essError 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++ -- g or file change: reload the journal and rebuild app state.+ e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] -> esReload copts d ui++ -- E: run editor, reload the journal.+ VtyEvent (EvKey (KChar 'E') []) -> do+ suspendAndResume' $ do+ let+ (pos,f) = case parsewithString hledgerparseerrorpositionp _essError of+ Right (f',l,c) -> (Just (l, Just c),f')+ Left _ -> (endPosition, journalFilePath j)+ runEditor pos f+ esReloadIfFileChanged copts d j ui+ VtyEvent (EvKey (KChar 'I') []) -> put' $ uiCheckBalanceAssertions d (popScreen $ toggleIgnoreBalanceAssertions ui) VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui _ -> return () - _ -> errorWrongScreenType "event handler"+ _ -> errorWrongScreenType "esHandle" + where+ -- Reload and fully regenerate the error screen.+ -- XXX On an error screen below the transaction screen, this is tricky because of a current limitation of regenerateScreens.+ -- For now we try to work around by re-entering the transaction screen.+ -- This can show flicker in the UI and it's hard to handle all situations robustly.+ esReload copts d ui = uiReload copts d ui >>= maybeReloadErrorScreen copts d+ esReloadIfFileChanged copts d j ui = liftIO (uiReloadIfFileChanged copts d j ui) >>= maybeReloadErrorScreen copts d+ maybeReloadErrorScreen copts d ui =+ case headMay $ aPrevScreens ui of+ Just (TS _) -> do+ -- check balance assertions, exit to register screen, enter transaction screen, reload once more+ put' $ popScreen $ popScreen $ uiCheckBalanceAssertions d ui+ sendVtyEvents [EvKey KEnter [], EvKey (KChar 'g') []] -- XXX Might be disrupted if other events are queued ?+ _ -> uiReload copts d (popScreen ui) >>= put' . uiCheckBalanceAssertions d+ -- | 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 -- Keep in sync with 'Hledger.Data.Transaction.showGenericSourcePos'@@ -130,29 +154,27 @@ ] --- | Unconditionally reload the journal, regenerating the current screen--- and all previous screens in the history as of the provided today-date.--- If reloading fails, enter the error screen, or if we're already--- on the error screen, update the error displayed.--- Defined here so it can reference the error screen.+-- Defined here so it can reference the error screen:++-- | Reload the journal from its input files, then update the ui app state accordingly.+-- This means regenerate the entire screen stack from top level down to the current screen, using the provided today-date.+-- As a convenience (usually), if journal reloading fails, this enters the error screen, or if already there, updates its message. ----- The provided CliOpts are used for reloading, and then saved in the--- UIState if reloading is successful (otherwise the UIState keeps its old--- CliOpts.) (XXX needed for.. ?)+-- The provided cli options can influence reloading; then if reloading succeeds they are saved in the ui state,+-- otherwise the UIState keeps its old options. (XXX needed for.. ?) ----- Forecasted transactions are always generated, as at hledger-ui startup.--- If a forecast period is specified in the provided opts, or was specified--- at startup, it is preserved.+-- Like at hledger-ui startup, --forecast is always enabled.+-- A forecast period specified in the provided opts, or at startup, is preserved. ---uiReloadJournal :: CliOpts -> Day -> UIState -> IO UIState-uiReloadJournal copts d ui = do+uiReload :: CliOpts -> Day -> UIState -> EventM Name UIState UIState+uiReload copts d ui = liftIO $ do ej <-- let copts' = enableForecastPreservingPeriod ui copts- in runExceptT $ journalReload copts'- -- dbg1IO "uiReloadJournal before reload" (map tdescription $ jtxns $ ajournal ui)+ let copts' = enableForecast (astartupopts ui) copts+ in runExceptT $ journalTransform copts' <$> journalReload copts'+ -- dbg1IO "uiReload before reload" (map tdescription $ jtxns $ ajournal ui) return $ case ej of Right j ->- -- dbg1 "uiReloadJournal after reload" (map tdescription $ jtxns j) $+ -- dbg1 "uiReload after reload" (map tdescription $ jtxns j) $ regenerateScreens j d ui Left err -> case ui of@@ -167,12 +189,12 @@ -- RegisterScreen _ _ _ _ _ _ -- TransactionScreen _ _ _ _ _ _ --- | Like uiReloadJournal, but does not re-parse the journal if the file(s)--- have not changed since last loaded. Always regenerates the screens though,--- since the provided options or today-date may have changed.-uiReloadJournalIfChanged :: CliOpts -> Day -> Journal -> UIState -> IO UIState-uiReloadJournalIfChanged copts d j ui = do- let copts' = enableForecastPreservingPeriod ui copts+-- | Like uiReload, except it skips re-reading the journal if its file(s) have not changed+-- since it was last loaded. The up app state is always updated, since the options or today-date may have changed.+-- Also, this one runs in IO, suitable for suspendAndResume.+uiReloadIfFileChanged :: CliOpts -> Day -> Journal -> UIState -> IO UIState+uiReloadIfFileChanged copts d j ui = do+ let copts' = enableForecast (astartupopts ui) copts ej <- runExceptT $ journalReloadIfChanged copts' d j return $ case ej of Right (j', _) -> regenerateScreens j' d ui
Hledger/UI/Main.hs view
@@ -118,7 +118,7 @@ _ | boolopt "man" rawopts -> runManForTopic "hledger-ui" Nothing _ | boolopt "version" rawopts -> putStrLn prognameandversion -- _ | boolopt "binary-filename" rawopts -> putStrLn (binaryfilename progname)- _ -> withJournalDo copts' (runBrickUi opts)+ _ -> withJournal copts' (runBrickUi opts) when (ghcDebugMode == GDPauseAtEnd) $ ghcDebugPause'
Hledger/UI/MenuScreen.hs view
@@ -33,7 +33,7 @@ import Hledger.UI.UIState import Hledger.UI.UIUtils import Hledger.UI.UIScreens-import Hledger.UI.ErrorScreen (uiReloadJournal, uiCheckBalanceAssertions, uiReloadJournalIfChanged)+import Hledger.UI.ErrorScreen (uiCheckBalanceAssertions, uiReload, uiReloadIfFileChanged) import Hledger.UI.Editor (runIadd, runEditor, endPosition) import Brick.Widgets.Edit (getEditContents, handleEditorEvent) import Control.Arrow ((>>>))@@ -87,7 +87,7 @@ ,("q", str "quit") ] -msDraw _ = dbgui "msDraw" $ errorWrongScreenType "draw function" -- PARTIAL:+msDraw _ = dbgui "msDraw" $ errorWrongScreenType "msDraw" -- PARTIAL: -- msDrawItem :: (Int,Int) -> Bool -> MenuScreenItem -> Widget Name -- msDrawItem (_acctwidth, _balwidth) _selected MenuScreenItem{..} =@@ -155,12 +155,11 @@ 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'+ e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] -> uiReload copts d ui >>= put' VtyEvent (EvKey (KChar 'I') []) -> put' $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)- VtyEvent (EvKey (KChar 'a') []) -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add (cliOptsDropArgs 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 'a') []) -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add (cliOptsDropArgs copts) j >> uiReloadIfFileChanged copts d j ui+ VtyEvent (EvKey (KChar 'A') []) -> suspendAndResume $ void (runIadd (journalFilePath j)) >> uiReloadIfFileChanged copts d j ui+ VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor endPosition (journalFilePath j)) >> uiReloadIfFileChanged copts d j ui -- VtyEvent (EvKey (KChar 'B') []) -> put' $ regenerateScreens j d $ toggleConversionOp ui -- VtyEvent (EvKey (KChar 'V') []) -> put' $ regenerateScreens j d $ toggleValue ui@@ -257,7 +256,7 @@ MouseUp{} -> return () AppEvent _ -> return () - _ -> dbguiEv "msHandle" >> errorWrongScreenType "event handler"+ _ -> dbguiEv "msHandle" >> errorWrongScreenType "msHandle" msEnterScreen :: Day -> ScreenName -> UIState -> EventM Name UIState () msEnterScreen d scrname ui@UIState{ajournal=j, aopts=uopts} = do
Hledger/UI/RegisterScreen.hs view
@@ -43,13 +43,13 @@ import Hledger.UI.UIUtils import Hledger.UI.UIScreens import Hledger.UI.Editor-import Hledger.UI.ErrorScreen (uiReloadJournal, uiCheckBalanceAssertions, uiReloadJournalIfChanged)+import Hledger.UI.ErrorScreen (uiCheckBalanceAssertions, uiReload, uiReloadIfFileChanged) rsDraw :: UIState -> [Widget Name] rsDraw UIState{aopts=_uopts@UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec}} ,aScreen=RS RSS{..} ,aMode=mode- } = dbgui "rsDraw 1" $+ } = dbgui "rsDraw" $ case mode of Help -> [helpDialog, maincontent] _ -> [maincontent]@@ -156,7 +156,7 @@ -- ,("q", "quit") ] -rsDraw _ = dbgui "rsDraw 2" $ errorWrongScreenType "draw function" -- PARTIAL:+rsDraw _ = dbgui "rsDraw" $ errorWrongScreenType "rsDraw" -- PARTIAL: rsDrawItem :: (Int,Int,Int,Int,Int) -> Bool -> RegisterScreenItem -> Widget Name rsDrawItem (datewidth,descwidth,acctswidth,changewidth,balwidth) selected RegisterScreenItem{..} =@@ -187,7 +187,7 @@ rsHandle :: BrickEvent Name AppEvent -> EventM Name UIState () rsHandle ev = do ui0 <- get'- dbguiEv "rsHandle 1"+ dbguiEv "rsHandle" case ui0 of ui@UIState{ aScreen=RS sst@RSS{..}@@ -245,14 +245,13 @@ where p = reportPeriod ui - e | e `elem` [AppEvent FileChange, VtyEvent (EvKey (KChar 'g') [])] ->- liftIO (uiReloadJournal copts d ui) >>= put'+ e | e `elem` [AppEvent FileChange, VtyEvent (EvKey (KChar 'g') [])] -> uiReload copts d ui >>= put' VtyEvent (EvKey (KChar 'I') []) -> put' $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui)- VtyEvent (EvKey (KChar 'a') []) -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add (cliOptsDropArgs copts) j >> uiReloadJournalIfChanged copts d j ui- VtyEvent (EvKey (KChar 'A') []) -> suspendAndResume $ void (runIadd (journalFilePath j)) >> uiReloadJournalIfChanged copts d j ui+ VtyEvent (EvKey (KChar 'a') []) -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add (cliOptsDropArgs copts) j >> uiReloadIfFileChanged copts d j ui+ VtyEvent (EvKey (KChar 'A') []) -> suspendAndResume $ void (runIadd (journalFilePath j)) >> uiReloadIfFileChanged 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+ VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor pos f) >> uiReloadIfFileChanged copts d j ui where (pos,f) = case listSelectedElement _rssList of Nothing -> (endPosition, journalFilePath j)@@ -332,7 +331,7 @@ MouseUp{} -> return () AppEvent _ -> return () - _ -> dbgui "rsHandle 2" $ errorWrongScreenType "event handler"+ _ -> errorWrongScreenType "rsHandle" isBlankElement mel = ((rsItemDate . snd) <$> mel) == Just "" @@ -354,8 +353,4 @@ rsEnterTransactionScreen :: AccountName -> [NumberedTransaction] -> NumberedTransaction -> UIState -> EventM Name UIState () rsEnterTransactionScreen acct nts nt ui = do dbguiEv "rsEnterTransactionScreen"- put' $- pushScreen (tsNew acct nts nt)- ui--+ put' $ pushScreen (tsNew acct nts nt) ui
Hledger/UI/TransactionScreen.hs view
@@ -12,14 +12,15 @@ ,tsHandle ) where -import Control.Monad+import Brick+import Brick.Widgets.Edit (editorText, renderEditor)+import Brick.Widgets.List (listMoveTo) import Control.Monad.IO.Class (liftIO) import Data.List import Data.Maybe import qualified Data.Text as T import Graphics.Vty (Event(..),Key(..),Modifier(..), Button (BLeft))-import Brick-import Brick.Widgets.List (listMoveTo)+import System.Exit (ExitCode (..)) import Hledger import Hledger.Cli hiding (mode, prices, progname,prognameandversion)@@ -29,8 +30,8 @@ import Hledger.UI.UIUtils import Hledger.UI.UIScreens import Hledger.UI.Editor-import Brick.Widgets.Edit (editorText, renderEditor)-import Hledger.UI.ErrorScreen (uiReloadJournalIfChanged, uiCheckBalanceAssertions, uiReloadJournal)+import Hledger.UI.ErrorScreen (uiCheckBalanceAssertions, uiReload, uiReloadIfFileChanged)+import Hledger.UI.RegisterScreen (rsHandle) tsDraw :: UIState -> [Widget Name] tsDraw UIState{aopts=UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}}@@ -95,7 +96,7 @@ -- ,("q", "quit") ] -tsDraw _ = errorWrongScreenType "draw function" -- PARTIAL:+tsDraw _ = errorWrongScreenType "tsDraw" -- PARTIAL: -- Render a transaction suitably for the transaction screen. showTxn :: ReportOpts -> ReportSpec -> Journal -> Transaction -> T.Text@@ -137,23 +138,30 @@ 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')++ -- g or file change: reload the journal and rebuild app state.+ e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] ->+ tsReload copts d ui++ -- for debugging; leaving these here because they were hard to find+ -- \u -> dbguiEv (pshow u) >> put' u -- doesn't log+ -- \UIState{aScreen=TS tss} -> error' $ pshow $ _tssTransaction tss++ -- E: run editor, reload the journal.+ VtyEvent (EvKey (KChar 'E') []) -> do+ suspendAndResume' $ do+ let (pos,f) = case tsourcepos t of (SourcePos f' l1 c1,_) -> (Just (unPos l1, Just $ unPos c1),f')+ exitcode <- runEditor pos f+ case exitcode of+ ExitSuccess -> return ()+ ExitFailure c -> error' $ "running the text editor failed with exit code " ++ show c+ tsReloadIfFileChanged copts d j ui+ AppEvent (DateChange old _) | isStandardPeriod p && p `periodContainsDate` old -> put' $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui where p = reportPeriod ui - -- Reload. Warning, this updates parent screens but not the transaction screen itself (see tsUpdate).- -- To see the updated transaction, one must exit and re-enter the transaction screen.- e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] ->- liftIO (uiReloadJournal copts d ui) >>= put'- -- debugging.. leaving these here because they were hard to find- -- \u -> dbguiEv (pshow u) >> put' u -- doesn't log- -- \UIState{aScreen=TS tss} -> error' $ pshow $ _tssTransaction tss- VtyEvent (EvKey (KChar 'I') []) -> put' $ uiCheckBalanceAssertions d (toggleIgnoreBalanceAssertions ui) -- for toggles that may change the current/prev/next transactions,@@ -176,7 +184,54 @@ VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui _ -> return () - _ -> errorWrongScreenType "event handler"+ _ -> errorWrongScreenType "tsHandle"++ where+ -- Reload and fully regenerate the transaction screen.+ -- XXX On transaction screen or below, this is tricky because of a current limitation of regenerateScreens.+ -- For now we try to work around by re-entering the screen(s).+ -- This can show flicker in the UI and it's hard to handle all situations robustly.+ tsReload copts d ui = uiReload copts d ui >>= reEnterTransactionScreen copts d+ tsReloadIfFileChanged copts d j ui = liftIO (uiReloadIfFileChanged copts d j ui) >>= reEnterTransactionScreen copts d+ + reEnterTransactionScreen _copts d ui = do+ -- 1. If uiReload (or checking balance assertions) moved us to the error screen, save that, and return to the transaction screen.+ let+ (merrscr, uiTxn) = case aScreen $ uiCheckBalanceAssertions d ui of+ s@(ES _) -> (Just s, popScreen ui)+ _ -> (Nothing, ui)+ -- 2. Exit to register screen+ let uiReg = popScreen uiTxn+ put' uiReg+ -- 3. Re-enter the transaction screen+ rsHandle (VtyEvent (EvKey KEnter [])) -- PARTIAL assumes we are on the register screen.+ -- 4. Return to the error screen (below the transaction screen) if there was one.+ -- Next events will be handled by esHandle. Error repair will return to the transaction screen.+ maybe (return ()) (put' . flip pushScreen uiTxn) merrscr+ -- doesn't uiTxn have old state from before step 3 ? seems to work++ -- XXX some problem:+ -- 4. Reload once more, possibly re-entering the error screen, by sending a g event.+ -- sendVtyEvents [EvKey (KChar 'g') []] -- XXX Might be disrupted if other events are queued++ -- XXX doesn't update on non-error change:+ -- 4. Reload once more, possibly re-entering the error screen.+ -- uiTxnOrErr <- uiReload copts d uiTxn+ -- uiReloadIfChanged ?+ -- uiCheckBalanceAssertions ? seems unneeded+ -- put' uiTxnOrErr++ -- XXX not working right:+ -- -- 1. If uiReload (or checking balance assertions) moved us to the error screen, exit to the transaction screen.+ -- let+ -- uiTxn = case aScreen $ uiCheckBalanceAssertions d ui of+ -- ES _ -> popScreen ui+ -- _ -> ui+ -- -- 2. Exit to register screen+ -- put' $ popScreen uiTxn+ -- -- 3. Re-enter the transaction screen, and reload once more.+ -- sendVtyEvents [EvKey KEnter [], EvKey (KChar 'g') []] -- XXX Might be disrupted if other events are queued+ -- | Select a new transaction and update the previous register screen tsSelect :: Integer -> Transaction -> UIState -> UIState
Hledger/UI/UIState.hs view
@@ -35,7 +35,7 @@ ,resetDepth ,popScreen ,pushScreen-,enableForecastPreservingPeriod+,enableForecast ,resetFilter ,resetScreens ,regenerateScreens@@ -60,6 +60,7 @@ import Hledger.UI.UITypes import Hledger.UI.UIOptions (UIOpts(uoCliOpts)) import Hledger.UI.UIScreens (screenUpdate)+import Hledger.UI.UIUtils (showScreenId, showScreenStack) -- | Make an initial UI state with the given options, journal, -- parent screen stack if any, and starting screen.@@ -201,20 +202,19 @@ where newForecast = case ui^.forecast of Just _ -> Nothing- Nothing -> enableForecastPreservingPeriod ui (ui^.cliOpts) ^. forecast+ Nothing -> enableForecast (astartupopts ui) (ui^.cliOpts) ^. forecast --- | Ensure this CliOpts enables forecasted transactions.--- If a forecast period was specified in the old CliOpts,--- or in the provided UIState's startup options,--- it is preserved.-enableForecastPreservingPeriod :: UIState -> CliOpts -> CliOpts-enableForecastPreservingPeriod ui copts = set forecast mforecast copts+-- | Enable forecasting in this CliOpts.+-- If it previously specified a forecast period, or else if the given ui startup options did,+-- preserve that as the forecast period.+enableForecast :: UIOpts -> CliOpts -> CliOpts+enableForecast startopts currentopts = set forecast mforecast currentopts where- mforecast = asum [mprovidedforecastperiod, mstartupforecastperiod, mdefaultforecastperiod]+ mforecast = asum [mcurrentforecastperiod, mstartupforecastperiod, mdefaultforecastperiod] where- mprovidedforecastperiod = copts ^. forecast- mstartupforecastperiod = astartupopts ui ^. forecast- mdefaultforecastperiod = Just nulldatespan+ mcurrentforecastperiod = currentopts ^. forecast+ mstartupforecastperiod = startopts ^. forecast+ mdefaultforecastperiod = Just nulldatespan -- | Toggle between showing all and showing only real (non-virtual) items. toggleReal :: UIState -> UIState@@ -340,12 +340,16 @@ setMode m ui = ui{aMode=m} pushScreen :: Screen -> UIState -> UIState-pushScreen scr ui = ui{aPrevScreens=(aScreen ui:aPrevScreens ui)- ,aScreen=scr- }+pushScreen scr ui =+ dbg1Msg ("pushing screen " <> showScreenId scr <> ". " <> showScreenStack "" showScreenId ui1)+ ui1+ where ui1 = ui{aPrevScreens=aScreen ui:aPrevScreens ui, aScreen=scr } popScreen :: UIState -> UIState-popScreen ui@UIState{aPrevScreens=s:ss} = ui{aScreen=s, aPrevScreens=ss}+popScreen ui@UIState{aPrevScreens = s : ss} =+ dbg1Msg ("popping screen " <> showScreenId (aScreen ui) <> ". " <> showScreenStack "" showScreenId ui1)+ ui1+ where ui1 = ui{aPrevScreens = ss ,aScreen = s } popScreen ui = ui -- | Reset options to their startup values, discard screen navigation history,@@ -361,7 +365,9 @@ -- then regenerate the content of all screens in the stack -- (using the ui state's current options), preserving the screen navigation history. -- Note, does not save the reporting date.+--+-- XXX Currently this does not properly regenerate the transaction screen or error screen,+-- which depend on state from their parent(s); those screens' handlers must do additional work, which is fragile. regenerateScreens :: Journal -> Day -> UIState -> UIState regenerateScreens j d ui@UIState{aopts=opts, aScreen=s,aPrevScreens=ss} = ui{ajournal=j, aScreen=screenUpdate opts d j s, aPrevScreens=map (screenUpdate opts d j) ss}-
Hledger/UI/UIUtils.hs view
@@ -42,6 +42,7 @@ ,mapScreens ,uiNumBlankItems ,showScreenStack+ ,sendVtyEvents ) where @@ -51,6 +52,7 @@ import Brick.Widgets.Dialog import Brick.Widgets.Edit import Brick.Widgets.List (List, listSelectedL, listNameL, listItemHeightL, listSelected, listMoveDown, listMoveUp, GenericList, listElements)+import Control.Concurrent.STM (atomically, writeTChan) -- GHC only import Control.Monad.IO.Class import Data.Bifunctor (second) import Data.List@@ -59,6 +61,7 @@ import Graphics.Vty (Event(..),Key(..),Modifier(..),Vty(..),Color,Attr,currentAttr,refresh, displayBounds -- ,Output(displayBounds,mkDisplayContext),DisplayContext(..)+ ,Vty (inputIface), InternalEvent (InputEvent), Input (eventChannel) ) import Lens.Micro.Platform @@ -530,3 +533,10 @@ -- | debugLevel >= uiDebugLevel = 0 -- suppress to improve debug output. -- | otherwise = 100 -- 100 ought to be enough for anyone++-- Send some events to vty, atomically so they won't have other events interleaved.+-- (But there may be events already in the channel ahead of them.)+sendVtyEvents :: [Event] -> EventM n s ()+sendVtyEvents evs = do+ input <- eventChannel . inputIface <$> getVtyHandle+ liftIO $ atomically $ mapM_ (writeTChan input . InputEvent) evs
hledger-ui.1 view
@@ -1,5 +1,5 @@ -.TH "HLEDGER\-UI" "1" "September 2025" "hledger-ui-1.50 " "hledger User Manuals"+.TH "HLEDGER\-UI" "1" "September 2025" "hledger-ui-1.50.1 " "hledger User Manuals" @@ -17,7 +17,7 @@ .PD \f[CR]hledger ui [OPTS] [QUERYARGS]\f[R] .SH DESCRIPTION-This manual is for hledger\[aq]s terminal interface, version 1.50.+This manual is for hledger\[aq]s terminal interface, version 1.50.1. See also the hledger manual for common concepts and file formats. .PP hledger is a robust, user\-friendly, cross\-platform set of programs for
hledger-ui.cabal view
@@ -1,11 +1,11 @@ cabal-version: 2.2 --- This file has been generated from package.yaml by hpack version 0.38.2.+-- This file has been generated from package.yaml by hpack version 0.38.1. -- -- see: https://github.com/sol/hpack name: hledger-ui-version: 1.50+version: 1.50.1 synopsis: Terminal interface for the hledger accounting system description: A simple terminal user interface for the hledger accounting system. It can be a more convenient way to browse your accounts than the CLI.@@ -74,7 +74,7 @@ hs-source-dirs: ./ ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-missing-signatures -Wno-orphans -Wno-type-defaults -Wno-unused-do-bind- cpp-options: -DVERSION="1.50" -DVERSION="1.50"+ cpp-options: -DVERSION="1.50.1" -DVERSION="1.50.1" build-depends: ansi-terminal >=0.9 , async@@ -89,8 +89,8 @@ , filepath , fsnotify >=0.4.2.0 && <0.5 , githash >=0.1.6.2- , hledger ==1.50.*- , hledger-lib ==1.50.*+ , hledger >=1.50.1 && <1.51+ , hledger-lib >=1.50.1 && <1.51 , megaparsec >=7.0.0 && <9.8 , microlens >=0.4 , microlens-platform >=0.2.3.1@@ -98,6 +98,7 @@ , process >=1.2 , safe >=0.3.20 , split >=0.1+ , stm , text >=1.2.4.1 , text-zipper >=0.4 , time >=1.5@@ -124,7 +125,7 @@ hs-source-dirs: app ghc-options: -Wall -Wno-incomplete-uni-patterns -Wno-missing-signatures -Wno-orphans -Wno-type-defaults -Wno-unused-do-bind -with-rtsopts=-T- cpp-options: -DVERSION="1.50"+ cpp-options: -DVERSION="1.50.1" build-depends: base >=4.18 && <4.22 , hledger-ui
hledger-ui.info view
@@ -18,7 +18,7 @@ or 'hledger ui [OPTS] [QUERYARGS]' - This manual is for hledger's terminal interface, version 1.50. See+ This manual is for hledger's terminal interface, version 1.50.1. See also the hledger manual for common concepts and file formats. hledger is a robust, user-friendly, cross-platform set of programs@@ -565,22 +565,22 @@ Tag Table: Node: Top221-Node: OPTIONS1867-Node: MOUSE8753-Node: KEYS9085-Node: SCREENS14089-Node: Menu screen14829-Node: Cash accounts screen15145-Node: Balance sheet accounts screen15506-Node: Income statement accounts screen15842-Node: All accounts screen16227-Node: Register screen16590-Node: Transaction screen19033-Node: Error screen20608-Node: WATCH MODE20974-Node: --watch problems21872-Node: ENVIRONMENT23225-Node: BUGS23458+Node: OPTIONS1869+Node: MOUSE8755+Node: KEYS9087+Node: SCREENS14091+Node: Menu screen14831+Node: Cash accounts screen15147+Node: Balance sheet accounts screen15508+Node: Income statement accounts screen15844+Node: All accounts screen16229+Node: Register screen16592+Node: Transaction screen19035+Node: Error screen20610+Node: WATCH MODE20976+Node: --watch problems21874+Node: ENVIRONMENT23227+Node: BUGS23460 End Tag Table
hledger-ui.txt view
@@ -11,7 +11,7 @@ hledger ui [OPTS] [QUERYARGS] DESCRIPTION- This manual is for hledger's terminal interface, version 1.50. See+ This manual is for hledger's terminal interface, version 1.50.1. See also the hledger manual for common concepts and file formats. hledger is a robust, user-friendly, cross-platform set of programs for@@ -471,4 +471,4 @@ SEE ALSO hledger(1), hledger-ui(1), hledger-web(1), ledger(1) -hledger-ui-1.50 September 2025 HLEDGER-UI(1)+hledger-ui-1.50.1 September 2025 HLEDGER-UI(1)