ui: basic editor integration
The E key (on all screens) edits the main journal file using $HLEDGER_UI_EDITOR or $EDITOR or "emacs -nw", jumping to the end if it's Emacs.
This commit is contained in:
parent
c4b3a4f996
commit
4923efefb9
@ -14,6 +14,7 @@ import Brick
|
|||||||
import Brick.Widgets.List
|
import Brick.Widgets.List
|
||||||
import Brick.Widgets.Edit
|
import Brick.Widgets.Edit
|
||||||
import Brick.Widgets.Border (borderAttr)
|
import Brick.Widgets.Border (borderAttr)
|
||||||
|
import Control.Monad
|
||||||
import Control.Monad.IO.Class (liftIO)
|
import Control.Monad.IO.Class (liftIO)
|
||||||
import Data.List
|
import Data.List
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
@ -32,6 +33,7 @@ import Hledger.UI.UIOptions
|
|||||||
import Hledger.UI.UITypes
|
import Hledger.UI.UITypes
|
||||||
import Hledger.UI.UIState
|
import Hledger.UI.UIState
|
||||||
import Hledger.UI.UIUtils
|
import Hledger.UI.UIUtils
|
||||||
|
import Hledger.UI.Editor
|
||||||
import Hledger.UI.RegisterScreen
|
import Hledger.UI.RegisterScreen
|
||||||
import Hledger.UI.ErrorScreen
|
import Hledger.UI.ErrorScreen
|
||||||
|
|
||||||
@ -266,6 +268,7 @@ asHandle ui0@UIState{
|
|||||||
EvKey (KChar c) [] | c `elem` ['h','?'] -> continue $ setMode Help ui
|
EvKey (KChar c) [] | c `elem` ['h','?'] -> continue $ setMode Help ui
|
||||||
EvKey (KChar 'g') [] -> liftIO (uiReloadJournalIfChanged copts d j ui) >>= continue
|
EvKey (KChar 'g') [] -> liftIO (uiReloadJournalIfChanged copts d j ui) >>= continue
|
||||||
EvKey (KChar 'a') [] -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> uiReloadJournalIfChanged copts d j ui
|
EvKey (KChar 'a') [] -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> uiReloadJournalIfChanged copts d j ui
|
||||||
|
EvKey (KChar 'E') [] -> suspendAndResume $ void (runEditor endPos j) >> uiReloadJournalIfChanged copts d j ui
|
||||||
EvKey (KChar '0') [] -> continue $ regenerateScreens j d $ setDepth (Just 0) ui
|
EvKey (KChar '0') [] -> continue $ regenerateScreens j d $ setDepth (Just 0) ui
|
||||||
EvKey (KChar '1') [] -> continue $ regenerateScreens j d $ setDepth (Just 1) ui
|
EvKey (KChar '1') [] -> continue $ regenerateScreens j d $ setDepth (Just 1) ui
|
||||||
EvKey (KChar '2') [] -> continue $ regenerateScreens j d $ setDepth (Just 2) ui
|
EvKey (KChar '2') [] -> continue $ regenerateScreens j d $ setDepth (Just 2) ui
|
||||||
|
|||||||
75
hledger-ui/Hledger/UI/Editor.hs
Normal file
75
hledger-ui/Hledger/UI/Editor.hs
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
{- | Editor integration. -}
|
||||||
|
|
||||||
|
-- {-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
module Hledger.UI.Editor
|
||||||
|
where
|
||||||
|
|
||||||
|
import Control.Applicative ((<|>))
|
||||||
|
import Data.List
|
||||||
|
import Safe
|
||||||
|
import System.Environment
|
||||||
|
import System.Exit
|
||||||
|
import System.FilePath
|
||||||
|
import System.Process
|
||||||
|
|
||||||
|
import Hledger
|
||||||
|
|
||||||
|
-- | A shell command line for invoking an editor, containing one placeholder ("FILE")
|
||||||
|
-- which will be replaced with a quoted file path. This exists because some desirable
|
||||||
|
-- editor commands do not fit the simple "$EDITOR FILE" pattern.
|
||||||
|
type EditorCommandTemplate = String
|
||||||
|
|
||||||
|
-- | Editors we know how to create more specific command lines for.
|
||||||
|
data EditorType = Emacs | Other
|
||||||
|
|
||||||
|
-- | A position we can move to in a text editor: a line number
|
||||||
|
-- and optionally character number. 1 (or 0) means the first; a negative number
|
||||||
|
-- counts back from the end (so -1 means the last line, -2 the second last etc.)
|
||||||
|
type TextPosition = (Int, Maybe Int)
|
||||||
|
|
||||||
|
endPos :: Maybe TextPosition
|
||||||
|
endPos = Just (1,Nothing)
|
||||||
|
|
||||||
|
-- | Construct a shell command template for starting the user's preferred text editor,
|
||||||
|
-- optionally at a given position.
|
||||||
|
-- XXX The position parameter is currently ignored and assumed to be end-of-file.
|
||||||
|
--
|
||||||
|
-- The basic editor command will be the value of environment variable $HLEDGER_UI_EDITOR,
|
||||||
|
-- or $EDITOR, or "emacs -nw". If a position is specified, and the command looks like one of
|
||||||
|
-- the editors we know (currently only emacs and emacsclient), it is modified so as to jump
|
||||||
|
-- to that position.
|
||||||
|
--
|
||||||
|
-- Some examples:
|
||||||
|
-- $EDITOR=vi -> "vi FILE"
|
||||||
|
-- $EDITOR=emacs -> "emacs FILE -f end-of-buffer"
|
||||||
|
-- $EDITOR not set -> "emacs -nw FILE -f end-of-buffer"
|
||||||
|
--
|
||||||
|
editorCommandTemplate :: Maybe TextPosition -> IO EditorCommandTemplate
|
||||||
|
editorCommandTemplate mpos = do
|
||||||
|
hledger_ui_editor_env <- lookupEnv "HLEDGER_UI_EDITOR"
|
||||||
|
editor_env <- lookupEnv "EDITOR"
|
||||||
|
let Just exe = hledger_ui_editor_env <|> editor_env <|> Just "emacs -nw"
|
||||||
|
return $
|
||||||
|
case (identifyEditor exe, mpos) of
|
||||||
|
(Emacs,_) -> exe ++ " FILE -f end-of-buffer"
|
||||||
|
_ -> exe ++ " FILE"
|
||||||
|
|
||||||
|
-- Identify the editor type, if we know it, from the value of $HLEDGER_EDITOR_UI or $EDITOR.
|
||||||
|
identifyEditor :: String -> EditorType
|
||||||
|
identifyEditor cmd
|
||||||
|
| "emacs" `isPrefixOf` exe = Emacs
|
||||||
|
| otherwise = Other
|
||||||
|
where
|
||||||
|
exe = lowercase $ takeFileName $ headDef "" $ words' cmd
|
||||||
|
|
||||||
|
fillEditorCommandTemplate :: FilePath -> EditorCommandTemplate -> String
|
||||||
|
fillEditorCommandTemplate f t = regexReplace "FILE" (singleQuoteIfNeeded f) t
|
||||||
|
|
||||||
|
-- | Try running $EDITOR, or a default edit command, on the main journal file,
|
||||||
|
-- blocking until it exits, and returning the exit code; or raise an error.
|
||||||
|
runEditor :: Maybe TextPosition -> Journal -> IO ExitCode
|
||||||
|
runEditor mpos j = do
|
||||||
|
fillEditorCommandTemplate (journalFilePath j) <$> editorCommandTemplate mpos
|
||||||
|
>>= runCommand
|
||||||
|
>>= waitForProcess
|
||||||
@ -8,6 +8,7 @@ module Hledger.UI.ErrorScreen
|
|||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
import Control.Monad
|
||||||
import Control.Monad.IO.Class (liftIO)
|
import Control.Monad.IO.Class (liftIO)
|
||||||
import Data.Monoid
|
import Data.Monoid
|
||||||
import Data.Time.Calendar (Day)
|
import Data.Time.Calendar (Day)
|
||||||
@ -19,6 +20,7 @@ import Hledger.UI.UIOptions
|
|||||||
import Hledger.UI.UITypes
|
import Hledger.UI.UITypes
|
||||||
import Hledger.UI.UIState
|
import Hledger.UI.UIState
|
||||||
import Hledger.UI.UIUtils
|
import Hledger.UI.UIUtils
|
||||||
|
import Hledger.UI.Editor
|
||||||
|
|
||||||
errorScreen :: Screen
|
errorScreen :: Screen
|
||||||
errorScreen = ErrorScreen{
|
errorScreen = ErrorScreen{
|
||||||
@ -59,7 +61,7 @@ esDraw _ = error "draw function called with wrong screen type, should not happen
|
|||||||
|
|
||||||
esHandle :: UIState -> Event -> EventM (Next UIState)
|
esHandle :: UIState -> Event -> EventM (Next UIState)
|
||||||
esHandle ui@UIState{
|
esHandle ui@UIState{
|
||||||
aScreen=s@ErrorScreen{}
|
aScreen=ErrorScreen{}
|
||||||
,aopts=UIOpts{cliopts_=copts}
|
,aopts=UIOpts{cliopts_=copts}
|
||||||
,ajournal=j
|
,ajournal=j
|
||||||
,aMode=mode
|
,aMode=mode
|
||||||
@ -76,11 +78,12 @@ esHandle ui@UIState{
|
|||||||
EvKey (KChar 'q') [] -> halt ui
|
EvKey (KChar 'q') [] -> halt ui
|
||||||
EvKey KEsc [] -> continue $ resetScreens d ui
|
EvKey KEsc [] -> continue $ resetScreens d ui
|
||||||
EvKey (KChar c) [] | c `elem` ['h','?'] -> continue $ setMode Help ui
|
EvKey (KChar c) [] | c `elem` ['h','?'] -> continue $ setMode Help ui
|
||||||
EvKey (KChar 'g') [] -> do
|
EvKey (KChar 'E') [] -> suspendAndResume $ void (runEditor endPos j) >> uiReloadJournalIfChanged copts d j (popScreen ui)
|
||||||
(ej, _) <- liftIO $ journalReloadIfChanged copts d j
|
EvKey (KChar 'g') [] -> liftIO (uiReloadJournalIfChanged copts d j (popScreen ui)) >>= continue
|
||||||
case ej of
|
-- (ej, _) <- liftIO $ journalReloadIfChanged copts d j
|
||||||
Left err -> continue ui{aScreen=s{esError=err}} -- show latest parse error
|
-- case ej of
|
||||||
Right j' -> continue $ regenerateScreens j' d $ popScreen ui -- return to previous screen, and reload it
|
-- 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
|
||||||
_ -> continue ui
|
_ -> continue ui
|
||||||
|
|
||||||
esHandle _ _ = error "event handler called with wrong screen type, should not happen"
|
esHandle _ _ = error "event handler called with wrong screen type, should not happen"
|
||||||
|
|||||||
@ -9,6 +9,7 @@ module Hledger.UI.RegisterScreen
|
|||||||
where
|
where
|
||||||
|
|
||||||
import Lens.Micro.Platform ((^.))
|
import Lens.Micro.Platform ((^.))
|
||||||
|
import Control.Monad
|
||||||
import Control.Monad.IO.Class (liftIO)
|
import Control.Monad.IO.Class (liftIO)
|
||||||
import Data.List
|
import Data.List
|
||||||
import Data.List.Split (splitOn)
|
import Data.List.Split (splitOn)
|
||||||
@ -32,6 +33,7 @@ import Hledger.UI.UIOptions
|
|||||||
import Hledger.UI.UITypes
|
import Hledger.UI.UITypes
|
||||||
import Hledger.UI.UIState
|
import Hledger.UI.UIState
|
||||||
import Hledger.UI.UIUtils
|
import Hledger.UI.UIUtils
|
||||||
|
import Hledger.UI.Editor
|
||||||
import Hledger.UI.TransactionScreen
|
import Hledger.UI.TransactionScreen
|
||||||
import Hledger.UI.ErrorScreen
|
import Hledger.UI.ErrorScreen
|
||||||
|
|
||||||
@ -248,6 +250,7 @@ rsHandle ui@UIState{
|
|||||||
EvKey (KChar c) [] | c `elem` ['h','?'] -> continue $ setMode Help ui
|
EvKey (KChar c) [] | c `elem` ['h','?'] -> continue $ setMode Help ui
|
||||||
EvKey (KChar 'g') [] -> liftIO (uiReloadJournalIfChanged copts d j ui) >>= continue
|
EvKey (KChar 'g') [] -> liftIO (uiReloadJournalIfChanged copts d j ui) >>= continue
|
||||||
EvKey (KChar 'a') [] -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> uiReloadJournalIfChanged copts d j ui
|
EvKey (KChar 'a') [] -> suspendAndResume $ clearScreen >> setCursorPosition 0 0 >> add copts j >> uiReloadJournalIfChanged copts d j ui
|
||||||
|
EvKey (KChar 'E') [] -> suspendAndResume $ void (runEditor endPos j) >> uiReloadJournalIfChanged copts d j ui
|
||||||
EvKey (KChar 'F') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleFlat ui)
|
EvKey (KChar 'F') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleFlat ui)
|
||||||
EvKey (KChar 'Z') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleEmpty ui)
|
EvKey (KChar 'Z') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleEmpty ui)
|
||||||
EvKey (KChar 'C') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleCleared ui)
|
EvKey (KChar 'C') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleCleared ui)
|
||||||
|
|||||||
@ -8,6 +8,7 @@ module Hledger.UI.TransactionScreen
|
|||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
|
import Control.Monad
|
||||||
import Control.Monad.IO.Class (liftIO)
|
import Control.Monad.IO.Class (liftIO)
|
||||||
import Data.List
|
import Data.List
|
||||||
import Data.Monoid
|
import Data.Monoid
|
||||||
@ -25,6 +26,7 @@ import Hledger.UI.UIOptions
|
|||||||
import Hledger.UI.UITypes
|
import Hledger.UI.UITypes
|
||||||
import Hledger.UI.UIState
|
import Hledger.UI.UIState
|
||||||
import Hledger.UI.UIUtils
|
import Hledger.UI.UIUtils
|
||||||
|
import Hledger.UI.Editor
|
||||||
import Hledger.UI.ErrorScreen
|
import Hledger.UI.ErrorScreen
|
||||||
|
|
||||||
transactionScreen :: Screen
|
transactionScreen :: Screen
|
||||||
@ -122,6 +124,7 @@ tsHandle ui@UIState{aScreen=s@TransactionScreen{tsTransaction=(i,t)
|
|||||||
EvKey (KChar 'q') [] -> halt ui
|
EvKey (KChar 'q') [] -> halt ui
|
||||||
EvKey KEsc [] -> continue $ resetScreens d ui
|
EvKey KEsc [] -> continue $ resetScreens d ui
|
||||||
EvKey (KChar c) [] | c `elem` ['h','?'] -> continue $ setMode Help ui
|
EvKey (KChar c) [] | c `elem` ['h','?'] -> continue $ setMode Help ui
|
||||||
|
EvKey (KChar 'E') [] -> suspendAndResume $ void (runEditor endPos j) >> uiReloadJournalIfChanged copts d j ui
|
||||||
EvKey (KChar 'g') [] -> do
|
EvKey (KChar 'g') [] -> do
|
||||||
d <- liftIO getCurrentDay
|
d <- liftIO getCurrentDay
|
||||||
(ej, _) <- liftIO $ journalReloadIfChanged copts d j
|
(ej, _) <- liftIO $ journalReloadIfChanged copts d j
|
||||||
|
|||||||
@ -19,6 +19,8 @@ import Hledger
|
|||||||
import Hledger.UI.UITypes
|
import Hledger.UI.UITypes
|
||||||
import Hledger.UI.UIState
|
import Hledger.UI.UIState
|
||||||
|
|
||||||
|
-- ui
|
||||||
|
|
||||||
-- | Draw the help dialog, called when help mode is active.
|
-- | Draw the help dialog, called when help mode is active.
|
||||||
helpDialog :: Widget
|
helpDialog :: Widget
|
||||||
helpDialog =
|
helpDialog =
|
||||||
@ -33,6 +35,7 @@ helpDialog =
|
|||||||
str "MISC"
|
str "MISC"
|
||||||
,renderKey ("h", "toggle help")
|
,renderKey ("h", "toggle help")
|
||||||
,renderKey ("a", "add transaction")
|
,renderKey ("a", "add transaction")
|
||||||
|
,renderKey ("E", "open editor")
|
||||||
,renderKey ("g", "reload data")
|
,renderKey ("g", "reload data")
|
||||||
,renderKey ("q", "quit")
|
,renderKey ("q", "quit")
|
||||||
,str " "
|
,str " "
|
||||||
|
|||||||
@ -70,6 +70,7 @@ executable hledger-ui
|
|||||||
, HUnit
|
, HUnit
|
||||||
, microlens >= 0.4 && < 0.5
|
, microlens >= 0.4 && < 0.5
|
||||||
, microlens-platform >= 0.2.3.1 && < 0.4
|
, microlens-platform >= 0.2.3.1 && < 0.4
|
||||||
|
, process >= 1.2
|
||||||
, safe >= 0.2
|
, safe >= 0.2
|
||||||
, split >= 0.1 && < 0.3
|
, split >= 0.1 && < 0.3
|
||||||
, text >= 1.2 && < 1.3
|
, text >= 1.2 && < 1.3
|
||||||
@ -92,6 +93,7 @@ executable hledger-ui
|
|||||||
other-modules:
|
other-modules:
|
||||||
Hledger.UI
|
Hledger.UI
|
||||||
Hledger.UI.AccountsScreen
|
Hledger.UI.AccountsScreen
|
||||||
|
Hledger.UI.Editor
|
||||||
Hledger.UI.ErrorScreen
|
Hledger.UI.ErrorScreen
|
||||||
Hledger.UI.Main
|
Hledger.UI.Main
|
||||||
Hledger.UI.RegisterScreen
|
Hledger.UI.RegisterScreen
|
||||||
|
|||||||
@ -75,6 +75,7 @@ executables:
|
|||||||
- HUnit
|
- HUnit
|
||||||
- microlens >= 0.4 && < 0.5
|
- microlens >= 0.4 && < 0.5
|
||||||
- microlens-platform >= 0.2.3.1 && < 0.4
|
- microlens-platform >= 0.2.3.1 && < 0.4
|
||||||
|
- process >= 1.2
|
||||||
- safe >= 0.2
|
- safe >= 0.2
|
||||||
- split >= 0.1 && < 0.3
|
- split >= 0.1 && < 0.3
|
||||||
- text >= 1.2 && < 1.3
|
- text >= 1.2 && < 1.3
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user