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
		
			
				
	
	
		
			91 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Haskell
		
	
	
	
	
	
			
		
		
	
	
			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
 |