104 lines
3.9 KiB
Haskell
104 lines
3.9 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
|
|
|
|
-- | 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
|
|
|
|
-- | Editors which we know how to open at a specific file position,
|
|
-- and Other for the rest.
|
|
data EditorType = Emacs | EmacsClient | Vi | Nano | Other
|
|
|
|
-- Identify which text editor is being 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
|
|
| "nano" `isPrefixOf` exe = Nano
|
|
| otherwise = Other
|
|
where
|
|
exe = lowercase $ takeFileName $ headDef "" $ words' 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, vi and nano.
|
|
--
|
|
-- @
|
|
-- Some tests:
|
|
-- When EDITOR is: The command should be:
|
|
-- --------------- -----------------------------------
|
|
-- notepad notepad FILE
|
|
-- vi vi +LINE FILE
|
|
-- emacs emacs +LINE:COL FILE
|
|
-- (unset) emacs -nw FILE -f end-of-buffer
|
|
-- @
|
|
--
|
|
-- How to open editors at the last line of a file:
|
|
-- @
|
|
-- emacs: emacs FILE -f end-of-buffer
|
|
-- vi: vi + FILE
|
|
-- @
|
|
--
|
|
editorOpenPositionCommand :: Maybe TextPosition -> FilePath -> IO String
|
|
editorOpenPositionCommand mpos f = do
|
|
cmd <- editorCommand
|
|
return $ cmd ++ " " ++
|
|
case (identifyEditor cmd, mpos) of
|
|
(EmacsClient , Just (l,mc)) | l >= 0 -> emacsposopt l mc ++ " " ++ f'
|
|
(EmacsClient , Just (l,mc)) | l < 0 -> emacsposopt 999999999 mc ++ " " ++ f'
|
|
(Emacs , Just (l,mc)) | l >= 0 -> emacsposopt l mc ++ " " ++ f'
|
|
(Emacs , Just (l,_)) | l < 0 -> f' ++ " -f end-of-buffer"
|
|
(Vi , Just (l,_)) -> viposopt l ++ " " ++ f'
|
|
(Nano , Just (l,_)) -> nanoposopt l ++ " " ++ f'
|
|
_ -> f'
|
|
where
|
|
f' = singleQuoteIfNeeded f
|
|
emacsposopt l mc = "+" ++ show l ++ maybe "" ((":"++).show) mc
|
|
viposopt l = "+" ++ if l >= 0 then show l else ""
|
|
nanoposopt l mc = "+" ++ show l ++ maybe "" ((","++).show) mc
|