86 lines
		
	
	
		
			3.4 KiB
		
	
	
	
		
			Haskell
		
	
	
	
	
	
			
		
		
	
	
			86 lines
		
	
	
		
			3.4 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)
 | |
| 
 | |
| -- | 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","vim","ex","view","gvim","gview","evim","eview","rvim","rview","rgvim","rgview"]
 | |
|                                    = Vi
 | |
|   | otherwise                      = Other
 | |
|   where
 | |
|     exe = lowercase $ takeFileName $ headDef "" $ words' cmd
 |