118 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Haskell
		
	
	
	
	
	
			
		
		
	
	
			118 lines
		
	
	
		
			4.7 KiB
		
	
	
	
		
			Haskell
		
	
	
	
	
	
{- | Editor integration. -}
 | 
						|
 | 
						|
module Hledger.UI.Editor (
 | 
						|
   -- TextPosition
 | 
						|
   endPosition
 | 
						|
  ,runEditor
 | 
						|
  ,runIadd
 | 
						|
  )
 | 
						|
where
 | 
						|
 | 
						|
import Control.Applicative ((<|>))
 | 
						|
import Data.List (intercalate)
 | 
						|
import Data.Maybe (catMaybes)
 | 
						|
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.
 | 
						|
-- Line number 1 or 0 means the first line. A negative line number means the last line.
 | 
						|
type TextPosition = (Int, Maybe Int)
 | 
						|
 | 
						|
-- | The text position meaning "last line, first column".
 | 
						|
endPosition :: Maybe TextPosition
 | 
						|
endPosition = Just (-1,Nothing)
 | 
						|
 | 
						|
-- | Run the hledger-iadd executable on the given file, blocking until it exits,
 | 
						|
-- and return the exit code; or raise an error.
 | 
						|
-- hledger-iadd is an alternative to the built-in add command.
 | 
						|
runIadd :: FilePath -> IO ExitCode
 | 
						|
runIadd f = runCommand ("hledger-iadd -f " ++ f) >>= waitForProcess
 | 
						|
 | 
						|
-- | Run the user's preferred text editor (or try a default editor),
 | 
						|
-- on the given file, blocking until it exits, and return the exit
 | 
						|
-- code; or raise an error. If a text position is provided, the editor
 | 
						|
-- will be focussed at that position in the file, if we know how.
 | 
						|
runEditor :: Maybe TextPosition -> FilePath -> IO ExitCode
 | 
						|
runEditor mpos f = editFileAtPositionCommand mpos f >>= runCommand >>= waitForProcess
 | 
						|
 | 
						|
-- | Get a shell command line to open the user's preferred text editor
 | 
						|
-- (or a default editor) on the given file, and to focus it at the
 | 
						|
-- given text position if one is provided and if we know how.
 | 
						|
-- We know how to focus on position for: emacs, vim, nano, VS code, kakoune.
 | 
						|
-- We know how to focus on last line for: vi.
 | 
						|
--
 | 
						|
-- Some tests:
 | 
						|
-- @
 | 
						|
-- EDITOR program is:  LINE/COL specified ?  Command should be:                 
 | 
						|
-- ------------------  --------------------  ----------------------------------- 
 | 
						|
-- emacs,              LINE COL              EDITOR +LINE:COL FILE
 | 
						|
-- emacsclient,        LINE                  EDITOR +LINE     FILE
 | 
						|
-- kak                                       EDITOR           FILE
 | 
						|
--
 | 
						|
-- nano                LINE COL              EDITOR +LINE,COL FILE
 | 
						|
--                     LINE                  EDITOR +LINE     FILE
 | 
						|
--                                           EDITOR           FILE
 | 
						|
--
 | 
						|
-- code                LINE COL              EDITOR --goto FILE:LINE:COL
 | 
						|
--                     LINE                  EDITOR --goto FILE:LINE
 | 
						|
--                                           EDITOR        FILE
 | 
						|
--
 | 
						|
-- vi/vim variants     LINE [COL]            EDITOR +LINE FILE
 | 
						|
--                     LINE (negative)       EDITOR +     FILE
 | 
						|
--                                           EDITOR       FILE
 | 
						|
--
 | 
						|
-- other               [LINE [COL]]          EDITOR FILE
 | 
						|
--
 | 
						|
-- not set             LINE COL              emacsclient -a '' -nw +LINE:COL FILE
 | 
						|
--                     LINE                  emacsclient -a '' -nw +LINE     FILE
 | 
						|
--                                           emacsclient -a '' -nw           FILE
 | 
						|
-- @
 | 
						|
--
 | 
						|
-- Notes on opening editors at the last line of a file:
 | 
						|
-- @
 | 
						|
-- emacs:       emacs FILE -f end-of-buffer  # (-f must appear after FILE, +LINE:COL must appear before)
 | 
						|
-- emacsclient: can't
 | 
						|
-- vi:          vi + FILE
 | 
						|
-- kakoune:     kak +: FILE
 | 
						|
-- @
 | 
						|
--
 | 
						|
editFileAtPositionCommand :: Maybe TextPosition -> FilePath -> IO String
 | 
						|
editFileAtPositionCommand mpos f = do
 | 
						|
  cmd <- getEditCommand
 | 
						|
  let
 | 
						|
    editor = lowercase $ takeBaseName $ headDef "" $ words' cmd
 | 
						|
    f' = singleQuoteIfNeeded f
 | 
						|
    ml = show.fst <$> mpos
 | 
						|
    mc = maybe Nothing (fmap show.snd) mpos
 | 
						|
    args = case editor of
 | 
						|
             e | e `elem` ["emacs", "emacsclient",
 | 
						|
                           "kak"]  -> ['+' : join ":" [ml,mc], f']
 | 
						|
             e | e `elem` ["nano"] -> ['+' : join "," [ml,mc], f']
 | 
						|
             e | e `elem` ["code"] -> ["--goto " ++ join ":" [Just f',ml,mc]]
 | 
						|
             e | e `elem` ["vi",  "vim", "view", "nvim", "evim", "eview",
 | 
						|
                           "gvim", "gview", "rvim", "rview", "rgvim", "rgview",
 | 
						|
                           "ex"]   -> [maybe "" plusMaybeLine ml, f']
 | 
						|
             _ -> [f']
 | 
						|
           where
 | 
						|
             join sep = intercalate sep . catMaybes
 | 
						|
             plusMaybeLine l = "+" ++ if take 1 l == "-" then "" else l
 | 
						|
 | 
						|
  return $ unwords $ cmd:args
 | 
						|
 | 
						|
-- | Get the user's preferred edit command. This is the value of the
 | 
						|
-- $HLEDGER_UI_EDITOR environment variable, or of $EDITOR, or a
 | 
						|
-- default ("emacsclient -a '' -nw", which starts/connects to an emacs
 | 
						|
-- daemon in terminal mode).
 | 
						|
getEditCommand :: IO String
 | 
						|
getEditCommand = 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
 | 
						|
 |