ui: editor: cleanup, vi support, positioning support
This commit is contained in:
parent
4923efefb9
commit
c79750bafd
@ -268,7 +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 'E') [] -> suspendAndResume $ void (journalRunEditor 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
|
||||||
|
|||||||
@ -15,61 +15,69 @@ import System.Process
|
|||||||
|
|
||||||
import Hledger
|
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.
|
-- | Editors we know how to create more specific command lines for.
|
||||||
data EditorType = Emacs | Other
|
data EditorType = Emacs | EmacsClient | Vi | Other
|
||||||
|
|
||||||
-- | A position we can move to in a text editor: a line number
|
-- | A position we can move to in a text editor: a line and optional column number.
|
||||||
-- and optionally character number. 1 (or 0) means the first; a negative number
|
-- 1 (or 0) means the first and -1 means the last (and -2 means the second last, etc.
|
||||||
-- counts back from the end (so -1 means the last line, -2 the second last etc.)
|
-- though this may not be well supported.)
|
||||||
type TextPosition = (Int, Maybe Int)
|
type TextPosition = (Int, Maybe Int)
|
||||||
|
|
||||||
endPos :: Maybe TextPosition
|
endPos :: Maybe TextPosition
|
||||||
endPos = Just (1,Nothing)
|
endPos = Just (-1,Nothing)
|
||||||
|
|
||||||
-- | Construct a shell command template for starting the user's preferred text editor,
|
-- | Try running the user's preferred text editor, or a default edit command,
|
||||||
-- optionally at a given position.
|
-- on the main journal file, blocking until it exits, and returning the exit code;
|
||||||
-- XXX The position parameter is currently ignored and assumed to be end-of-file.
|
-- or raise an error.
|
||||||
--
|
journalRunEditor :: Maybe TextPosition -> Journal -> IO ExitCode
|
||||||
-- The basic editor command will be the value of environment variable $HLEDGER_UI_EDITOR,
|
journalRunEditor mpos j =
|
||||||
-- or $EDITOR, or "emacs -nw". If a position is specified, and the command looks like one of
|
editorOpenPositionCommand mpos (journalFilePath j) >>= runCommand >>= waitForProcess
|
||||||
-- the editors we know (currently only emacs and emacsclient), it is modified so as to jump
|
|
||||||
-- to that position.
|
-- Get the basic shell command to start the user's preferred text editor.
|
||||||
--
|
-- This is the value of environment variable $HLEDGER_UI_EDITOR, or $EDITOR, or
|
||||||
-- Some examples:
|
-- a default (emacsclient -a '' -nw, start/connect to an emacs daemon in terminal mode).
|
||||||
-- $EDITOR=vi -> "vi FILE"
|
editorCommand :: IO String
|
||||||
-- $EDITOR=emacs -> "emacs FILE -f end-of-buffer"
|
editorCommand = do
|
||||||
-- $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"
|
hledger_ui_editor_env <- lookupEnv "HLEDGER_UI_EDITOR"
|
||||||
editor_env <- lookupEnv "EDITOR"
|
editor_env <- lookupEnv "EDITOR"
|
||||||
let Just exe = hledger_ui_editor_env <|> editor_env <|> Just "emacs -nw"
|
let Just cmd =
|
||||||
return $
|
hledger_ui_editor_env
|
||||||
case (identifyEditor exe, mpos) of
|
<|> editor_env
|
||||||
(Emacs,_) -> exe ++ " FILE -f end-of-buffer"
|
<|> Just "emacsclient -a '' -nw"
|
||||||
_ -> exe ++ " FILE"
|
return cmd
|
||||||
|
|
||||||
-- Identify the editor type, if we know it, from the value of $HLEDGER_EDITOR_UI or $EDITOR.
|
-- | Get a shell command to start the user's preferred text editor, or a default,
|
||||||
|
-- and optionally jump to a given position in the file. This will be the basic
|
||||||
|
-- editor command, with the appropriate options added, if we know how.
|
||||||
|
-- Currently we know how to do this for emacs and vi.
|
||||||
|
-- Some examples:
|
||||||
|
-- $EDITOR=notepad -> "notepad FILE"
|
||||||
|
-- $EDITOR=vi -> "vi +LINE FILE"
|
||||||
|
-- $EDITOR=vi, line -1 -> "vi + FILE"
|
||||||
|
-- $EDITOR=emacs -> "emacs +LINE:COL FILE"
|
||||||
|
-- $EDITOR=emacs, line -1 -> "emacs FILE -f end-of-buffer"
|
||||||
|
-- $EDITOR not set -> "emacs -nw FILE -f end-of-buffer"
|
||||||
|
--
|
||||||
|
editorOpenPositionCommand :: Maybe TextPosition -> FilePath -> IO String
|
||||||
|
editorOpenPositionCommand mpos f = do
|
||||||
|
cmd <- editorCommand
|
||||||
|
let f' = singleQuoteIfNeeded f
|
||||||
|
return $
|
||||||
|
case (identifyEditor cmd, mpos) of
|
||||||
|
(Emacs, Just (line,_)) | line < 0 -> cmd ++ " " ++ f' ++ " -f end-of-buffer"
|
||||||
|
(Emacs, Just (line,mcol)) | line >= 0 -> cmd ++ " " ++ posopt ++ " " ++ f'
|
||||||
|
where posopt = "+" ++ show line ++ maybe "" ((":"++).show) mcol
|
||||||
|
(Vi, Just (line,_)) -> cmd ++ " " ++ posopt ++ " " ++ f'
|
||||||
|
where posopt = "+" ++ if line >= 0 then show line else ""
|
||||||
|
_ -> cmd ++ " " ++ f'
|
||||||
|
|
||||||
|
-- Identify which text editor is used in the basic editor command, if possible.
|
||||||
identifyEditor :: String -> EditorType
|
identifyEditor :: String -> EditorType
|
||||||
identifyEditor cmd
|
identifyEditor cmd
|
||||||
| "emacs" `isPrefixOf` exe = Emacs
|
| "emacsclient" `isPrefixOf` exe = EmacsClient
|
||||||
| otherwise = Other
|
| "emacs" `isPrefixOf` exe = Emacs
|
||||||
|
| exe `elem` ["vi","vim","ex","view","gvim","gview","evim","eview","rvim","rview","rgvim","rgview"]
|
||||||
|
= Vi
|
||||||
|
| otherwise = Other
|
||||||
where
|
where
|
||||||
exe = lowercase $ takeFileName $ headDef "" $ words' cmd
|
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
|
|
||||||
|
|||||||
@ -78,7 +78,7 @@ 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 'E') [] -> suspendAndResume $ void (runEditor endPos j) >> uiReloadJournalIfChanged copts d j (popScreen ui)
|
EvKey (KChar 'E') [] -> suspendAndResume $ void (journalRunEditor endPos j) >> uiReloadJournalIfChanged copts d j (popScreen ui)
|
||||||
EvKey (KChar 'g') [] -> liftIO (uiReloadJournalIfChanged copts d j (popScreen ui)) >>= continue
|
EvKey (KChar 'g') [] -> liftIO (uiReloadJournalIfChanged copts d j (popScreen ui)) >>= continue
|
||||||
-- (ej, _) <- liftIO $ journalReloadIfChanged copts d j
|
-- (ej, _) <- liftIO $ journalReloadIfChanged copts d j
|
||||||
-- case ej of
|
-- case ej of
|
||||||
|
|||||||
@ -250,7 +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 'E') [] -> suspendAndResume $ void (journalRunEditor 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)
|
||||||
|
|||||||
@ -124,7 +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 'E') [] -> suspendAndResume $ void (journalRunEditor 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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user