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.Edit | ||||
| import Brick.Widgets.Border (borderAttr) | ||||
| import Control.Monad | ||||
| import Control.Monad.IO.Class (liftIO) | ||||
| import Data.List | ||||
| import Data.Maybe | ||||
| @ -32,6 +33,7 @@ import Hledger.UI.UIOptions | ||||
| import Hledger.UI.UITypes | ||||
| import Hledger.UI.UIState | ||||
| import Hledger.UI.UIUtils | ||||
| import Hledger.UI.Editor | ||||
| import Hledger.UI.RegisterScreen | ||||
| import Hledger.UI.ErrorScreen | ||||
| 
 | ||||
| @ -266,6 +268,7 @@ asHandle ui0@UIState{ | ||||
|         EvKey (KChar c)   [] | c `elem` ['h','?'] -> continue $ setMode Help ui | ||||
|         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 'E') [] -> suspendAndResume $ void (runEditor endPos j) >> uiReloadJournalIfChanged copts d j 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 '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 | ||||
| 
 | ||||
| import Control.Monad | ||||
| import Control.Monad.IO.Class (liftIO) | ||||
| import Data.Monoid | ||||
| import Data.Time.Calendar (Day) | ||||
| @ -19,6 +20,7 @@ import Hledger.UI.UIOptions | ||||
| import Hledger.UI.UITypes | ||||
| import Hledger.UI.UIState | ||||
| import Hledger.UI.UIUtils | ||||
| import Hledger.UI.Editor | ||||
| 
 | ||||
| errorScreen :: Screen | ||||
| 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 ui@UIState{ | ||||
|    aScreen=s@ErrorScreen{} | ||||
|    aScreen=ErrorScreen{} | ||||
|   ,aopts=UIOpts{cliopts_=copts} | ||||
|   ,ajournal=j | ||||
|   ,aMode=mode | ||||
| @ -76,11 +78,12 @@ esHandle ui@UIState{ | ||||
|         EvKey (KChar 'q') [] -> halt ui | ||||
|         EvKey KEsc        [] -> continue $ resetScreens d ui | ||||
|         EvKey (KChar c)   [] | c `elem` ['h','?'] -> continue $ setMode Help ui | ||||
|         EvKey (KChar 'g') [] -> do | ||||
|           (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 | ||||
|         EvKey (KChar 'E') [] -> suspendAndResume $ void (runEditor endPos j) >> uiReloadJournalIfChanged copts d j (popScreen ui) | ||||
|         EvKey (KChar 'g') [] -> liftIO (uiReloadJournalIfChanged copts d j (popScreen ui)) >>= continue | ||||
| --           (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 | ||||
|         _ -> continue ui | ||||
| 
 | ||||
| esHandle _ _ = error "event handler called with wrong screen type, should not happen" | ||||
|  | ||||
| @ -9,6 +9,7 @@ module Hledger.UI.RegisterScreen | ||||
| where | ||||
| 
 | ||||
| import Lens.Micro.Platform ((^.)) | ||||
| import Control.Monad | ||||
| import Control.Monad.IO.Class (liftIO) | ||||
| import Data.List | ||||
| import Data.List.Split (splitOn) | ||||
| @ -32,6 +33,7 @@ import Hledger.UI.UIOptions | ||||
| import Hledger.UI.UITypes | ||||
| import Hledger.UI.UIState | ||||
| import Hledger.UI.UIUtils | ||||
| import Hledger.UI.Editor | ||||
| import Hledger.UI.TransactionScreen | ||||
| import Hledger.UI.ErrorScreen | ||||
| 
 | ||||
| @ -248,6 +250,7 @@ rsHandle ui@UIState{ | ||||
|         EvKey (KChar c)   [] | c `elem` ['h','?'] -> continue $ setMode Help ui | ||||
|         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 'E') [] -> suspendAndResume $ void (runEditor endPos j) >> uiReloadJournalIfChanged copts d j ui | ||||
|         EvKey (KChar 'F') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleFlat ui) | ||||
|         EvKey (KChar 'Z') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleEmpty ui) | ||||
|         EvKey (KChar 'C') [] -> scrollTop >> (continue $ regenerateScreens j d $ toggleCleared ui) | ||||
|  | ||||
| @ -8,6 +8,7 @@ module Hledger.UI.TransactionScreen | ||||
|  ) | ||||
| where | ||||
| 
 | ||||
| import Control.Monad | ||||
| import Control.Monad.IO.Class (liftIO) | ||||
| import Data.List | ||||
| import Data.Monoid | ||||
| @ -25,6 +26,7 @@ import Hledger.UI.UIOptions | ||||
| import Hledger.UI.UITypes | ||||
| import Hledger.UI.UIState | ||||
| import Hledger.UI.UIUtils | ||||
| import Hledger.UI.Editor | ||||
| import Hledger.UI.ErrorScreen | ||||
| 
 | ||||
| transactionScreen :: Screen | ||||
| @ -122,6 +124,7 @@ tsHandle ui@UIState{aScreen=s@TransactionScreen{tsTransaction=(i,t) | ||||
|         EvKey (KChar 'q') [] -> halt ui | ||||
|         EvKey KEsc        [] -> continue $ resetScreens d 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 | ||||
|           d <- liftIO getCurrentDay | ||||
|           (ej, _) <- liftIO $ journalReloadIfChanged copts d j | ||||
|  | ||||
| @ -19,6 +19,8 @@ import Hledger | ||||
| import Hledger.UI.UITypes | ||||
| import Hledger.UI.UIState | ||||
| 
 | ||||
| -- ui | ||||
| 
 | ||||
| -- | Draw the help dialog, called when help mode is active. | ||||
| helpDialog :: Widget | ||||
| helpDialog = | ||||
| @ -33,6 +35,7 @@ helpDialog = | ||||
|              str "MISC" | ||||
|             ,renderKey ("h", "toggle help") | ||||
|             ,renderKey ("a", "add transaction") | ||||
|             ,renderKey ("E", "open editor") | ||||
|             ,renderKey ("g", "reload data") | ||||
|             ,renderKey ("q", "quit") | ||||
|             ,str " " | ||||
|  | ||||
| @ -70,6 +70,7 @@ executable hledger-ui | ||||
|     , HUnit | ||||
|     , microlens >= 0.4 && < 0.5 | ||||
|     , microlens-platform >= 0.2.3.1 && < 0.4 | ||||
|     , process >= 1.2 | ||||
|     , safe >= 0.2 | ||||
|     , split >= 0.1 && < 0.3 | ||||
|     , text >= 1.2 && < 1.3 | ||||
| @ -92,6 +93,7 @@ executable hledger-ui | ||||
|   other-modules: | ||||
|       Hledger.UI | ||||
|       Hledger.UI.AccountsScreen | ||||
|       Hledger.UI.Editor | ||||
|       Hledger.UI.ErrorScreen | ||||
|       Hledger.UI.Main | ||||
|       Hledger.UI.RegisterScreen | ||||
|  | ||||
| @ -75,6 +75,7 @@ executables: | ||||
|       - HUnit | ||||
|       - microlens >= 0.4 && < 0.5 | ||||
|       - microlens-platform >= 0.2.3.1 && < 0.4 | ||||
|       - process >= 1.2 | ||||
|       - safe >= 0.2 | ||||
|       - split >= 0.1 && < 0.3 | ||||
|       - text >= 1.2 && < 1.3 | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user