Skip to content

Commit 112272f

Browse files
committed
fix:ui:transaction, error screens: more robust updating, third fix [#2014], [#2288]
This makes both transaction screen and error screen below the transaction screen update as we'd expect when using the E key, g key, or --watch. No visible bugs known at the moment.
1 parent 549ccd2 commit 112272f

6 files changed

Lines changed: 98 additions & 55 deletions

File tree

hledger-ui/Hledger/UI/ErrorScreen.hs

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ where
1818

1919
import Brick
2020
-- import Brick.Widgets.Border ("border")
21-
import Control.Monad
2221
import Control.Monad.IO.Class (liftIO)
2322
import Data.Time.Calendar (Day)
2423
import Data.Void (Void)
2524
import Graphics.Vty (Event(..),Key(..),Modifier(..))
2625
import Lens.Micro ((^.))
26+
import Safe (headMay)
2727
import Text.Megaparsec
2828
import Text.Megaparsec.Char
2929

@@ -88,26 +88,42 @@ esHandle ev = do
8888
VtyEvent (EvKey (KChar 'q') []) -> halt
8989
VtyEvent (EvKey KEsc []) -> put' $ uiCheckBalanceAssertions d $ resetScreens d ui
9090
VtyEvent (EvKey (KChar c) []) | c `elem` ['h','?'] -> put' $ setMode Help ui
91-
VtyEvent (EvKey (KChar 'E') []) -> suspendAndResume $ void (runEditor pos f) >> uiReloadIfFileChanged copts d j (popScreen ui)
92-
-- XXX put ? uiCheckBalanceAssertions ?
93-
-- does the error screen update the state, and check balance assertions, after running editor ?
94-
where
95-
(pos,f) = case parsewithString hledgerparseerrorpositionp _essError of
96-
Right (f',l,c) -> (Just (l, Just c),f')
97-
Left _ -> (endPosition, journalFilePath j)
98-
e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] ->
99-
uiReload copts d (popScreen ui) >>= put' . uiCheckBalanceAssertions d
100-
-- (ej, _) <- liftIO $ journalReloadIfChanged copts d j
101-
-- case ej of
102-
-- Left err -> continue ui{aScreen=s{esError=err}} -- show latest parse error
103-
-- Right j' -> continue $ regenerateScreens j' d $ popScreen ui -- return to previous screen, and reload it
91+
92+
-- g or file change: reload the journal and rebuild app state.
93+
e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] -> esReload copts d ui
94+
95+
-- E: run editor, reload the journal.
96+
VtyEvent (EvKey (KChar 'E') []) -> do
97+
suspendAndResume' $ do
98+
let
99+
(pos,f) = case parsewithString hledgerparseerrorpositionp _essError of
100+
Right (f',l,c) -> (Just (l, Just c),f')
101+
Left _ -> (endPosition, journalFilePath j)
102+
runEditor pos f
103+
esReloadIfFileChanged copts d j ui
104+
104105
VtyEvent (EvKey (KChar 'I') []) -> put' $ uiCheckBalanceAssertions d (popScreen $ toggleIgnoreBalanceAssertions ui)
105106
VtyEvent (EvKey (KChar 'l') [MCtrl]) -> redraw
106107
VtyEvent (EvKey (KChar 'z') [MCtrl]) -> suspend ui
107108
_ -> return ()
108109

109110
_ -> errorWrongScreenType "esHandle"
110111

112+
where
113+
-- Reload and fully regenerate the error screen.
114+
-- XXX On an error screen below the transaction screen, this is tricky because of a current limitation of regenerateScreens.
115+
-- For now we try to work around by re-entering the transaction screen.
116+
-- This can show flicker in the UI and it's hard to handle all situations robustly.
117+
esReload copts d ui = uiReload copts d ui >>= maybeReloadErrorScreen d
118+
esReloadIfFileChanged copts d j ui = liftIO (uiReloadIfFileChanged copts d j ui) >>= maybeReloadErrorScreen d
119+
maybeReloadErrorScreen d ui =
120+
case headMay $ aPrevScreens ui of
121+
Just (TS _) -> do
122+
-- check balance assertions, exit to register screen, enter transaction screen, reload once more
123+
put' $ popScreen $ popScreen $ uiCheckBalanceAssertions d ui
124+
sendVtyEvents [EvKey KEnter [], EvKey (KChar 'g') []] -- XXX Might be disrupted if other events are queued ?
125+
_ -> return ()
126+
111127
-- | Parse the file name, line and column number from a hledger parse error message, if possible.
112128
-- Temporary, we should keep the original parse error location. XXX
113129
-- Keep in sync with 'Hledger.Data.Transaction.showGenericSourcePos'

hledger-ui/Hledger/UI/TransactionScreen.hs

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ module Hledger.UI.TransactionScreen
1212
,tsHandle
1313
) where
1414

15+
import Brick
1516
import Brick.Widgets.Edit (editorText, renderEditor)
17+
import Brick.Widgets.List (listMoveTo)
1618
import Control.Monad.IO.Class (liftIO)
1719
import Data.List
1820
import Data.Maybe
1921
import qualified Data.Text as T
2022
import Graphics.Vty (Event(..),Key(..),Modifier(..), Button (BLeft))
21-
import Brick
22-
import Brick.Widgets.List (listMoveTo)
23+
import System.Exit (ExitCode (..))
2324

2425
import Hledger
2526
import Hledger.Cli hiding (mode, prices, progname,prognameandversion)
@@ -31,8 +32,6 @@ import Hledger.UI.UIScreens
3132
import Hledger.UI.Editor
3233
import Hledger.UI.ErrorScreen (uiCheckBalanceAssertions, uiReload, uiReloadIfFileChanged)
3334
import Hledger.UI.RegisterScreen (rsHandle)
34-
import System.Exit (ExitCode(..))
35-
import Data.Function ((&))
3635

3736
tsDraw :: UIState -> [Widget Name]
3837
tsDraw UIState{aopts=UIOpts{uoCliOpts=copts@CliOpts{reportspec_=rspec@ReportSpec{_rsReportOpts=ropts}}}
@@ -140,25 +139,9 @@ tsHandle ev = do
140139
VtyEvent (EvKey KEsc []) -> put' $ resetScreens d ui
141140
VtyEvent (EvKey (KChar c) []) | c == '?' -> put' $ setMode Help ui
142141

143-
-- g or file change: reload the journal.
144-
e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] -> do
145-
-- Update app state. This is tricky: (XXX anywhere else we need to be this thorough ?)
146-
147-
-- Reload and regenerate screens
148-
ui1 <- uiReload copts d ui
149-
-- If that moved us to the error screen, save that and return to the transaction screen.
150-
let
151-
(merrscr, ui2) = case aScreen ui1 of
152-
s@(ES _) -> (Just s, popScreen ui1)
153-
_ -> (Nothing, ui1)
154-
-- put' ui2
155-
-- Now exit to register screen and make it regenerate the transaction screen,
156-
-- for best initialisation.
157-
put' $ popScreen ui2
158-
rsHandle (VtyEvent (EvKey KEnter [])) -- XXX PARTIAL assumes we are on the register screen
159-
-- Then re-enter the error screen if any, so error repair will return to the transaction screen.
160-
let ui3 = maybe ui2 (`pushScreen` ui2) merrscr
161-
put' ui3
142+
-- g or file change: reload the journal and rebuild app state.
143+
e | e `elem` [VtyEvent (EvKey (KChar 'g') []), AppEvent FileChange] ->
144+
tsReload copts d ui
162145

163146
-- for debugging; leaving these here because they were hard to find
164147
-- \u -> dbguiEv (pshow u) >> put' u -- doesn't log
@@ -167,14 +150,12 @@ tsHandle ev = do
167150
-- E: run editor, reload the journal.
168151
VtyEvent (EvKey (KChar 'E') []) -> do
169152
suspendAndResume' $ do
153+
let (pos,f) = case tsourcepos t of (SourcePos f' l1 c1,_) -> (Just (unPos l1, Just $ unPos c1),f')
170154
exitcode <- runEditor pos f
171155
case exitcode of
172156
ExitSuccess -> return ()
173157
ExitFailure c -> error' $ "running the text editor failed with exit code " ++ show c
174-
-- Update all state, similar to above.
175-
put' =<< liftIO (popScreen ui & uiReloadIfFileChanged copts d j)
176-
rsHandle (VtyEvent (EvKey KEnter []))
177-
where (pos,f) = case tsourcepos t of (SourcePos f' l1 c1,_) -> (Just (unPos l1, Just $ unPos c1),f')
158+
tsReloadIfFileChanged copts d j ui
178159

179160
AppEvent (DateChange old _) | isStandardPeriod p && p `periodContainsDate` old ->
180161
put' $ regenerateScreens j d $ setReportPeriod (DayPeriod d) ui
@@ -205,6 +186,53 @@ tsHandle ev = do
205186

206187
_ -> errorWrongScreenType "tsHandle"
207188

189+
where
190+
-- Reload and fully regenerate the transaction screen.
191+
-- XXX On transaction screen or below, this is tricky because of a current limitation of regenerateScreens.
192+
-- For now we try to work around by re-entering the screen(s).
193+
-- This can show flicker in the UI and it's hard to handle all situations robustly.
194+
tsReload copts d ui = uiReload copts d ui >>= reEnterTransactionScreen copts d
195+
tsReloadIfFileChanged copts d j ui = liftIO (uiReloadIfFileChanged copts d j ui) >>= reEnterTransactionScreen copts d
196+
197+
reEnterTransactionScreen _copts d ui = do
198+
-- 1. If uiReload (or checking balance assertions) moved us to the error screen, save that, and return to the transaction screen.
199+
let
200+
(merrscr, uiTxn) = case aScreen $ uiCheckBalanceAssertions d ui of
201+
s@(ES _) -> (Just s, popScreen ui)
202+
_ -> (Nothing, ui)
203+
-- 2. Exit to register screen
204+
let uiReg = popScreen uiTxn
205+
put' uiReg
206+
-- 3. Re-enter the transaction screen
207+
rsHandle (VtyEvent (EvKey KEnter [])) -- PARTIAL assumes we are on the register screen.
208+
-- 4. Return to the error screen (below the transaction screen) if there was one.
209+
-- Next events will be handled by esHandle. Error repair will return to the transaction screen.
210+
maybe (return ()) (put' . flip pushScreen uiTxn) merrscr
211+
-- doesn't uiTxn have old state from before step 3 ? seems to work
212+
213+
-- XXX some problem:
214+
-- 4. Reload once more, possibly re-entering the error screen, by sending a g event.
215+
-- sendVtyEvents [EvKey (KChar 'g') []] -- XXX Might be disrupted if other events are queued
216+
217+
-- XXX doesn't update on non-error change:
218+
-- 4. Reload once more, possibly re-entering the error screen.
219+
-- uiTxnOrErr <- uiReload copts d uiTxn
220+
-- uiReloadIfChanged ?
221+
-- uiCheckBalanceAssertions ? seems unneeded
222+
-- put' uiTxnOrErr
223+
224+
-- XXX not working right:
225+
-- -- 1. If uiReload (or checking balance assertions) moved us to the error screen, exit to the transaction screen.
226+
-- let
227+
-- uiTxn = case aScreen $ uiCheckBalanceAssertions d ui of
228+
-- ES _ -> popScreen ui
229+
-- _ -> ui
230+
-- -- 2. Exit to register screen
231+
-- put' $ popScreen uiTxn
232+
-- -- 3. Re-enter the transaction screen, and reload once more.
233+
-- sendVtyEvents [EvKey KEnter [], EvKey (KChar 'g') []] -- XXX Might be disrupted if other events are queued
234+
235+
208236
-- | Select a new transaction and update the previous register screen
209237
tsSelect :: Integer -> Transaction -> UIState -> UIState
210238
tsSelect i t ui@UIState{aScreen=TS sst} = case aPrevScreens ui of

hledger-ui/Hledger/UI/UIState.hs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -366,9 +366,8 @@ resetScreens d ui@UIState{astartupopts=origopts, ajournal=j, aScreen=s,aPrevScre
366366
-- (using the ui state's current options), preserving the screen navigation history.
367367
-- Note, does not save the reporting date.
368368
--
369-
-- Currently this does not properly regenerate the transaction screen or error screen,
370-
-- which depend on state from their parent(s). (Eg rsHandle generates TS's nts from its list items.)
371-
-- As a workaround we can additionally exit and reenter those screens; but this is fragile.
369+
-- XXX Currently this does not properly regenerate the transaction screen or error screen,
370+
-- which depend on state from their parent(s); those screens' handlers must do additional work, which is fragile.
372371
regenerateScreens :: Journal -> Day -> UIState -> UIState
373372
regenerateScreens j d ui@UIState{aopts=opts, aScreen=s,aPrevScreens=ss} =
374373
ui{ajournal=j, aScreen=screenUpdate opts d j s, aPrevScreens=map (screenUpdate opts d j) ss}

hledger-ui/Hledger/UI/UIUtils.hs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ module Hledger.UI.UIUtils (
4242
,mapScreens
4343
,uiNumBlankItems
4444
,showScreenStack
45+
,sendVtyEvents
4546
)
4647
where
4748

@@ -51,6 +52,7 @@ import Brick.Widgets.Border.Style
5152
import Brick.Widgets.Dialog
5253
import Brick.Widgets.Edit
5354
import Brick.Widgets.List (List, listSelectedL, listNameL, listItemHeightL, listSelected, listMoveDown, listMoveUp, GenericList, listElements)
55+
import Control.Concurrent.STM (atomically, writeTChan) -- GHC only
5456
import Control.Monad.IO.Class
5557
import Data.Bifunctor (second)
5658
import Data.List
@@ -59,6 +61,7 @@ import Data.Time (addDays)
5961
import Graphics.Vty
6062
(Event(..),Key(..),Modifier(..),Vty(..),Color,Attr,currentAttr,refresh, displayBounds
6163
-- ,Output(displayBounds,mkDisplayContext),DisplayContext(..)
64+
,Vty (inputIface), InternalEvent (InputEvent), Input (eventChannel)
6265
)
6366
import Lens.Micro.Platform
6467

@@ -530,3 +533,10 @@ uiNumBlankItems
530533
-- | debugLevel >= uiDebugLevel = 0 -- suppress to improve debug output.
531534
-- | otherwise
532535
= 100 -- 100 ought to be enough for anyone
536+
537+
-- Send some events to vty, atomically so they won't have other events interleaved.
538+
-- (But there may be events already in the channel ahead of them.)
539+
sendVtyEvents :: [Event] -> EventM n s ()
540+
sendVtyEvents evs = do
541+
input <- eventChannel . inputIface <$> getVtyHandle
542+
liftIO $ atomically $ mapM_ (writeTChan input . InputEvent) evs

hledger-ui/hledger-ui.m4.md

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -291,13 +291,6 @@ reload).
291291
On this screen (and the register screen), the `E` key will open your text editor
292292
with the cursor positioned at the current transaction if possible.
293293

294-
This screen has a limitation with showing file updates:
295-
it will not show them until you exit and re-enter it.
296-
So eg to see the effect of using the `E` key, currently you must:
297-
- press `E`, edit and save the file, then exit the editor, returning to hledger-ui
298-
- press `g` to reload the file (or use `-w/--watch` mode)
299-
- press `LEFT` then `RIGHT` to exit and re-enter the transaction screen.
300-
301294
## Error screen
302295

303296
This screen will appear if there is a problem, such as a parse error,
@@ -365,8 +358,4 @@ Some known issues:
365358

366359
`--watch` is not robust, especially with large files (see WATCH MODE above).
367360

368-
The Transaction screen does not update after file changes, even if you press `g`,
369-
until you exit and re-enter it.
370-
([#2288](https://github.com/simonmichael/hledger/issues/2288))
371-
372361
If you press `g` with large files, there could be a noticeable pause with the UI unresponsive.

hledger-ui/package.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ library:
106106
- process >=1.2
107107
- safe >=0.3.20
108108
- split >=0.1
109+
- stm
109110
- text >=1.2.4.1
110111
- text-zipper >=0.4
111112
- time >=1.5

0 commit comments

Comments
 (0)