hledger/hledger-ui/Hledger/UI/Editor.hs
Aerex 5808e289e6 feat(hledger-ui): added neovim as a supported editor
when neovim is set as EDITOR hleger will jump to the correct line number of the transaction; before
hledger will open journal at top of the file
2018-06-03 17:11:43 -07:00

91 lines
3.6 KiB
Haskell

{- | 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
-- | Editors we know how to create more specific command lines for.
data EditorType = Emacs | EmacsClient | Vi | Other
-- | A position we can move to in a text editor: a line and optional column number.
-- 1 (or 0) means the first and -1 means the last (and -2 means the second last, etc.
-- though this may not be well supported.)
type TextPosition = (Int, Maybe Int)
endPos :: Maybe TextPosition
endPos = Just (-1,Nothing)
-- | Run the hledger-iadd executable (an alternative to the built-in add command),
-- or raise an error.
runIadd :: FilePath -> IO ExitCode
runIadd f = runCommand ("hledger-iadd -f " ++ f) >>= waitForProcess
-- | Try running the user's preferred text 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 -> FilePath -> IO ExitCode
runEditor mpos f = editorOpenPositionCommand mpos f >>= runCommand >>= waitForProcess
-- 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
-- a default (emacsclient -a '' -nw, start/connect to an emacs daemon in terminal mode).
editorCommand :: IO String
editorCommand = do
hledger_ui_editor_env <- lookupEnv "HLEDGER_UI_EDITOR"
editor_env <- lookupEnv "EDITOR"
let Just cmd =
hledger_ui_editor_env
<|> editor_env
<|> Just "emacsclient -a '' -nw"
return cmd
-- | 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
(EmacsClient, Just (l,mc)) | l >= 0 -> cmd ++ " " ++ emacsposopt l mc ++ " " ++ f'
(EmacsClient, Just (l,mc)) | l < 0 -> cmd ++ " " ++ emacsposopt 999999999 mc ++ " " ++ f'
(Emacs, Just (l,mc)) | l >= 0 -> cmd ++ " " ++ emacsposopt l mc ++ " " ++ f'
(Emacs, Just (l,_)) | l < 0 -> cmd ++ " " ++ f' ++ " -f end-of-buffer"
(Vi, Just (l,_)) -> cmd ++ " " ++ viposopt l ++ " " ++ f'
_ -> cmd ++ " " ++ f'
where
emacsposopt l mc = "+" ++ show l ++ maybe "" ((":"++).show) mc
viposopt l = "+" ++ if l >= 0 then show l else ""
-- Identify which text editor is used in the basic editor command, if possible.
identifyEditor :: String -> EditorType
identifyEditor cmd
| "emacsclient" `isPrefixOf` exe = EmacsClient
| "emacs" `isPrefixOf` exe = Emacs
| exe `elem` ["vi","nvim","vim","ex","view","gvim","gview","evim","eview","rvim","rview","rgvim","rgview"]
= Vi
| otherwise = Other
where
exe = lowercase $ takeFileName $ headDef "" $ words' cmd