mirror of
https://github.com/simonmichael/hledger.git
synced 2024-12-27 12:24:43 +03:00
167 lines
6.5 KiB
Haskell
167 lines
6.5 KiB
Haskell
-- The error screen, showing a current error condition (such as a parse error after reloading the journal)
|
|
|
|
{-# LANGUAGE OverloadedStrings, FlexibleContexts, RecordWildCards #-}
|
|
{-# LANGUAGE CPP #-}
|
|
|
|
module Hledger.UI.ErrorScreen
|
|
(errorScreen
|
|
,uiCheckBalanceAssertions
|
|
,uiReloadJournal
|
|
,uiReloadJournalIfChanged
|
|
)
|
|
where
|
|
|
|
import Brick
|
|
-- import Brick.Widgets.Border (borderAttr)
|
|
import Control.Monad
|
|
import Control.Monad.IO.Class (liftIO)
|
|
#if !(MIN_VERSION_base(4,11,0))
|
|
import Data.Monoid
|
|
#endif
|
|
import Data.Time.Calendar (Day)
|
|
import Data.Void (Void)
|
|
import Graphics.Vty (Event(..),Key(..))
|
|
import Text.Megaparsec
|
|
import Text.Megaparsec.Char
|
|
|
|
import Hledger.Cli hiding (progname,prognameandversion)
|
|
import Hledger.UI.UIOptions
|
|
import Hledger.UI.UITypes
|
|
import Hledger.UI.UIState
|
|
import Hledger.UI.UIUtils
|
|
import Hledger.UI.Editor
|
|
|
|
errorScreen :: Screen
|
|
errorScreen = ErrorScreen{
|
|
sInit = esInit
|
|
,sDraw = esDraw
|
|
,sHandle = esHandle
|
|
,esError = ""
|
|
}
|
|
|
|
esInit :: Day -> Bool -> UIState -> UIState
|
|
esInit _ _ ui@UIState{aScreen=ErrorScreen{}} = ui
|
|
esInit _ _ _ = error "init function called with wrong screen type, should not happen"
|
|
|
|
esDraw :: UIState -> [Widget Name]
|
|
esDraw UIState{aopts=UIOpts{cliopts_=copts@CliOpts{}}
|
|
,aScreen=ErrorScreen{..}
|
|
,aMode=mode} =
|
|
case mode of
|
|
Help -> [helpDialog copts, maincontent]
|
|
-- Minibuffer e -> [minibuffer e, maincontent]
|
|
_ -> [maincontent]
|
|
where
|
|
maincontent = Widget Greedy Greedy $ do
|
|
render $ defaultLayout toplabel bottomlabel $ withAttr "error" $ str $ esError
|
|
where
|
|
toplabel =
|
|
withAttr ("border" <> "bold") (str "Oops. Please fix this problem then press g to reload")
|
|
-- <+> (if ignore_assertions_ copts then withAttr (borderAttr <> "query") (str " ignoring") else str " not ignoring")
|
|
|
|
bottomlabel = case mode of
|
|
-- Minibuffer ed -> minibuffer ed
|
|
_ -> quickhelp
|
|
where
|
|
quickhelp = borderKeysStr [
|
|
("h", "help")
|
|
,("ESC", "cancel/top")
|
|
,("E", "editor")
|
|
,("g", "reload")
|
|
,("q", "quit")
|
|
]
|
|
|
|
esDraw _ = error "draw function called with wrong screen type, should not happen"
|
|
|
|
esHandle :: UIState -> BrickEvent Name AppEvent -> EventM Name (Next UIState)
|
|
esHandle ui@UIState{
|
|
aScreen=ErrorScreen{..}
|
|
,aopts=UIOpts{cliopts_=copts}
|
|
,ajournal=j
|
|
,aMode=mode
|
|
} ev =
|
|
case mode of
|
|
Help ->
|
|
case ev of
|
|
VtyEvent (EvKey (KChar 'q') []) -> halt ui
|
|
_ -> helpHandle ui ev
|
|
|
|
_ -> do
|
|
d <- liftIO getCurrentDay
|
|
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 _ -> (endPos, 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)
|
|
_ -> continue ui
|
|
|
|
esHandle _ _ = error "event handler called with wrong screen type, should not happen"
|
|
|
|
-- | Parse the file name, line and column number from a hledger parse error message, if possible.
|
|
-- Temporary, we should keep the original parse error location. XXX
|
|
hledgerparseerrorpositionp :: ParsecT Void String t (String, Int, Int)
|
|
hledgerparseerrorpositionp = do
|
|
anySingle `manyTill` char '"'
|
|
f <- anySingle `manyTill` (oneOf ['"','\n'])
|
|
string " (line "
|
|
l <- read <$> some digitChar
|
|
string ", column "
|
|
c <- read <$> some digitChar
|
|
return (f, l, c)
|
|
|
|
-- Unconditionally reload the journal, regenerating the current screen
|
|
-- and all previous screens in the history.
|
|
-- If reloading fails, enter the error screen, or if we're already
|
|
-- on the error screen, update the error displayed.
|
|
-- The provided CliOpts are used for reloading, and then saved
|
|
-- in the UIState if reloading is successful (otherwise the
|
|
-- ui state keeps its old cli opts.)
|
|
-- Defined here so it can reference the error screen.
|
|
uiReloadJournal :: CliOpts -> Day -> UIState -> IO UIState
|
|
uiReloadJournal copts d ui = do
|
|
ej <- journalReload copts
|
|
return $ case ej of
|
|
Right j -> regenerateScreens j d ui{aopts=(aopts ui){cliopts_=copts}}
|
|
Left err ->
|
|
case ui of
|
|
UIState{aScreen=s@ErrorScreen{}} -> ui{aScreen=s{esError=err}}
|
|
_ -> screenEnter d errorScreen{esError=err} ui
|
|
|
|
-- Like uiReloadJournal, but does not bother re-parsing the journal if
|
|
-- the file(s) have not changed since last loaded. Always regenerates
|
|
-- the current and previous screens though, since opts or date may have changed.
|
|
uiReloadJournalIfChanged :: CliOpts -> Day -> Journal -> UIState -> IO UIState
|
|
uiReloadJournalIfChanged copts d j ui = do
|
|
(ej, _changed) <- journalReloadIfChanged copts d j
|
|
return $ case ej of
|
|
Right j' -> regenerateScreens j' d ui{aopts=(aopts ui){cliopts_=copts}}
|
|
Left err ->
|
|
case ui of
|
|
UIState{aScreen=s@ErrorScreen{}} -> ui{aScreen=s{esError=err}}
|
|
_ -> screenEnter d errorScreen{esError=err} ui
|
|
|
|
-- Re-check any balance assertions in the current journal, and if any
|
|
-- fail, enter (or update) the error screen. Or if balance assertions
|
|
-- are disabled, do nothing.
|
|
uiCheckBalanceAssertions :: Day -> UIState -> UIState
|
|
uiCheckBalanceAssertions d ui@UIState{aopts=UIOpts{cliopts_=copts}, ajournal=j}
|
|
| ignore_assertions_ $ inputopts_ copts = ui
|
|
| otherwise =
|
|
case journalCheckBalanceAssertions j of
|
|
Right _ -> ui
|
|
Left err ->
|
|
case ui of
|
|
UIState{aScreen=s@ErrorScreen{}} -> ui{aScreen=s{esError=err}}
|
|
_ -> screenEnter d errorScreen{esError=err} ui
|