module Agda.Setup.EmacsMode
( help
, locateFlag
, printEmacsModeFile
, setupFlag
, setupDotEmacs
, compileFlag
, compileElispFiles
, inform
)
where
import Control.Applicative ( liftA2 )
import Control.Exception as E ( bracket, catch, evaluate, IOException )
import Control.Monad ( unless )
import Data.Bifunctor ( first, second )
import Data.Bool ( bool )
import Data.Char ( isAscii, isPrint )
import Data.Functor ( (<&>) )
import Data.List ( isInfixOf )
import Data.Maybe ( catMaybes, fromMaybe )
import Numeric ( showHex )
import System.Directory ( doesFileExist, findExecutable, getTemporaryDirectory, removeFile )
import System.Exit ( exitFailure, ExitCode(ExitSuccess) )
import System.FilePath ( (</>), getSearchPath )
import System.IO ( IOMode(ReadMode)
, hClose, hSetEncoding, hGetContents, hPutStr, hPutStrLn
, openTempFile, stderr, stdout, utf8, withFile
)
import System.Process ( rawSystem, showCommandForUser )
import Agda.Setup ( getDataDir )
import Agda.Setup.DataFiles ( emacsLispFiles, emacsModeDir )
setupFlag :: String
setupFlag :: [Char]
setupFlag = [Char]
"setup"
locateFlag :: String
locateFlag :: [Char]
locateFlag = [Char]
"locate"
compileFlag :: String
compileFlag :: [Char]
compileFlag = [Char]
"compile"
help :: String
help :: [Char]
help = [[Char]] -> [Char]
unlines
[ [Char]
"Option --emacs-mode allows to administer the Agda Emacs mode."
, [Char]
"It accepts three commands:"
, [Char]
""
, [Char]
setupFlag
, [Char]
""
, [Char]
" This command unpacks Agda's data files, including the Emacs mode,"
, [Char]
" into Agda's data directory (see option --print-agda-data-dir)."
, [Char]
""
, [Char]
" It then tries to add setup code for Agda's Emacs mode to the"
, [Char]
" current user's .emacs file. It is assumed that the .emacs file"
, [Char]
" uses the character encoding specified by the locale."
, [Char]
""
, [Char]
compileFlag
, [Char]
""
, [Char]
" This command unpacks Agda's data files, including the Emacs mode,"
, [Char]
" into Agda's data directory."
, [Char]
""
, [Char]
" It then tries to compile Agda's Emacs mode's source files."
, [Char]
""
, [Char]
" WARNING: If you reinstall the Agda mode without recompiling the Emacs"
, [Char]
" Lisp files, then Emacs may continue using the old, compiled files."
, [Char]
""
, [Char]
locateFlag
, [Char]
""
, [Char]
" The path to the Emacs mode's main file is printed on standard"
, [Char]
" output (using the UTF-8 character encoding and no trailing"
, [Char]
" newline)."
]
printEmacsModeFile :: IO ()
printEmacsModeFile :: IO ()
printEmacsModeFile = do
dataDir <- IO [Char]
getDataDir
let path = [Char]
dataDir [Char] -> [Char] -> [Char]
</> [Char]
emacsModeDir [Char] -> [Char] -> [Char]
</> [Char]
"agda2.el"
hSetEncoding stdout utf8
putStr path
setupDotEmacs :: String -> IO ()
setupDotEmacs :: [Char] -> IO ()
setupDotEmacs [Char]
agda = do
let cmd :: [Char]
cmd = [Char]
agda [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
" --emacs-mode"
let elisp :: [Char]
elisp = [Char] -> [Char]
setupString [Char]
cmd
dotEmacs <- IO [Char]
findDotEmacs
informLn $ "The .emacs file used: " ++ dotEmacs
alreadyInstalled cmd dotEmacs >>= \case
Bool
True -> do
[Char] -> IO ()
informLn [Char]
"It seems as if setup has already been performed."
Bool
False -> do
[Char] -> [Char] -> IO ()
appendFile [Char]
dotEmacs [Char]
elisp
[Char] -> IO ()
inform ([Char] -> IO ()) -> [Char] -> IO ()
forall a b. (a -> b) -> a -> b
$ [[Char]] -> [Char]
unlines ([[Char]] -> [Char]) -> [[Char]] -> [Char]
forall a b. (a -> b) -> a -> b
$
[ [Char]
"Setup done. Try to (re)start Emacs and open an Agda file."
, [Char]
"The following text was appended to the .emacs file:"
] [[Char]] -> [[Char]] -> [[Char]]
forall a. [a] -> [a] -> [a]
++ [Char] -> [[Char]]
lines [Char]
elisp
findDotEmacs :: IO FilePath
findDotEmacs :: IO [Char]
findDotEmacs = [Char] -> IO [Char]
askEmacs [Char]
"(expand-file-name user-init-file)"
alreadyInstalled :: String -> FilePath -> IO Bool
alreadyInstalled :: [Char] -> [Char] -> IO Bool
alreadyInstalled [Char]
cmd [Char]
dotEmacs = do
[Char] -> IO Bool
doesFileExist [Char]
dotEmacs IO Bool -> (Bool -> IO Bool) -> IO Bool
forall a b. IO a -> (a -> IO b) -> IO b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
Bool
False -> Bool -> IO Bool
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return Bool
False
Bool
True -> [Char] -> IOMode -> (Handle -> IO Bool) -> IO Bool
forall r. [Char] -> IOMode -> (Handle -> IO r) -> IO r
withFile [Char]
dotEmacs IOMode
ReadMode \ Handle
f -> do
txt <- Handle -> IO [Char]
hGetContents Handle
f
evaluate $ identifier cmd `isInfixOf` txt
identifier :: String -> String
identifier :: [Char] -> [Char]
identifier [Char]
cmd = [Char]
cmd [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
" " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
locateFlag
setupString :: String -> String
setupString :: [Char] -> [Char]
setupString [Char]
cmd = [[Char]] -> [Char]
unlines
[ [Char]
""
, [Char]
"(load-file (let ((coding-system-for-read 'utf-8))"
, [Char]
" (shell-command-to-string \""
[Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char] -> [Char]
identifier [Char]
cmd [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"\")))"
]
askEmacs :: String -> IO String
askEmacs :: [Char] -> IO [Char]
askEmacs [Char]
query = do
tempDir <- IO [Char]
getTemporaryDirectory
bracket (openTempFile tempDir "askEmacs")
(removeFile . fst) $ \ ([Char]
file, Handle
h) -> do
Handle -> IO ()
hClose Handle
h
exit <- [Char] -> [[Char]] -> IO ExitCode
rawSystemWithDiagnostics [Char]
"emacs"
[ [Char]
"--batch"
, [Char]
"--user", [Char]
""
, [Char]
"--eval"
, [[Char]] -> [Char]
apply [ [Char]
"with-temp-file", [Char] -> [Char]
escape [Char]
file, [[Char]] -> [Char]
apply [ [Char]
"insert", [Char]
query ] ]
]
unless (exit == ExitSuccess) do
informLn "Unable to query Emacs."
exitFailure
withFile file ReadMode \ Handle
h -> do
result <- Handle -> IO [Char]
hGetContents Handle
h
_ <- evaluate (length result)
return result
rawSystemWithDiagnostics
:: FilePath
-> [String]
-> IO ExitCode
rawSystemWithDiagnostics :: [Char] -> [[Char]] -> IO ExitCode
rawSystemWithDiagnostics [Char]
cmd [[Char]]
args =
[Char] -> [[Char]] -> IO ExitCode
rawSystem [Char]
cmd [[Char]]
args
IO ExitCode -> (IOException -> IO ExitCode) -> IO ExitCode
forall e a. Exception e => IO a -> (e -> IO a) -> IO a
`E.catch` \ (IOException
e :: IOException) -> do
[Char] -> IO ()
informLn ([Char] -> IO ()) -> [Char] -> IO ()
forall a b. (a -> b) -> a -> b
$ [[Char]] -> [Char]
unwords [ [Char]
"FAILED:", [Char] -> [[Char]] -> [Char]
showCommandForUser [Char]
cmd [[Char]]
args ]
[Char] -> IO ()
informLn ([Char] -> IO ()) -> [Char] -> IO ()
forall a b. (a -> b) -> a -> b
$ [[Char]] -> [Char]
unwords [ [Char]
"Exception:", IOException -> [Char]
forall a. Show a => a -> [Char]
show IOException
e ]
path <- [Char] -> Maybe [Char] -> [Char]
forall a. a -> Maybe a -> a
fromMaybe [Char]
"(not found)" (Maybe [Char] -> [Char]) -> IO (Maybe [Char]) -> IO [Char]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> [Char] -> IO (Maybe [Char])
findExecutable [Char]
cmd
informLn $ unwords [ "Executable", cmd, "at:", path ]
informLn "PATH:"
mapM_ (informLn . (" - " ++)) =<< getSearchPath
exitFailure
escape :: FilePath -> FilePath
escape :: [Char] -> [Char]
escape [Char]
s = [Char]
"\"" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ (Char -> [Char]) -> [Char] -> [Char]
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Char -> [Char]
esc [Char]
s [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"\""
where
esc :: Char -> [Char]
esc Char
c | Char
c Char -> [Char] -> Bool
forall a. Eq a => a -> [a] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [Char
'\\', Char
'"'] = Char
'\\' Char -> [Char] -> [Char]
forall a. a -> [a] -> [a]
: [Char
c]
| Char -> Bool
isAscii Char
c Bool -> Bool -> Bool
&& Char -> Bool
isPrint Char
c = [Char
c]
| Bool
otherwise = [Char]
"\\x" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Int -> [Char] -> [Char]
forall a. Integral a => a -> [Char] -> [Char]
showHex (Char -> Int
forall a. Enum a => a -> Int
fromEnum Char
c) [Char]
"\\ "
compileElispFiles :: IO ()
compileElispFiles :: IO ()
compileElispFiles = do
dataDir <- IO [Char]
getDataDir IO [Char] -> ([Char] -> [Char]) -> IO [Char]
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> ([Char] -> [Char] -> [Char]
</> [Char]
emacsModeDir)
let elFiles = ([Char] -> [Char]) -> [[Char]] -> [[Char]]
forall a b. (a -> b) -> [a] -> [b]
map ([Char]
dataDir [Char] -> [Char] -> [Char]
</>) [[Char]]
emacsLispFiles
(existing, missing) <- partitionM doesFileExist elFiles
failed <- catMaybes <$> mapM (compile dataDir) existing
unless (null missing) $ do
informLn "Missing Emacs Lisp files:"
mapM_ (informLn . (" " ++)) missing
unless (null failed) $ do
informLn "Unable to compile the following Emacs Lisp files:"
mapM_ (informLn . (" " ++)) failed
unless (null missing && null failed) exitFailure
where
compile :: [Char] -> [Char] -> IO (Maybe [Char])
compile [Char]
dataDir [Char]
f = do
exit <- [Char] -> [[Char]] -> IO ExitCode
rawSystemWithDiagnostics [Char]
"emacs" ([[Char]] -> IO ExitCode) -> [[Char]] -> IO ExitCode
forall a b. (a -> b) -> a -> b
$
[ [Char]
"--quick"
, [Char]
"--directory", [Char]
dataDir
, [Char]
"--batch"
, [Char]
"--eval"
, [Char]
"(progn \
\(setq byte-compile-error-on-warn t) \
\(byte-compile-disable-warning 'cl-functions) \
\(batch-byte-compile))"
, [Char]
f
]
return $ if exit == ExitSuccess then Nothing else Just f
inform :: String -> IO ()
inform :: [Char] -> IO ()
inform = Handle -> [Char] -> IO ()
hPutStr Handle
stderr
informLn :: String -> IO ()
informLn :: [Char] -> IO ()
informLn = Handle -> [Char] -> IO ()
hPutStrLn Handle
stderr
parens :: String -> String
parens :: [Char] -> [Char]
parens [Char]
s = [[Char]] -> [Char]
forall (t :: * -> *) a. Foldable t => t [a] -> [a]
concat [ [Char]
"(", [Char]
s, [Char]
")" ]
apply :: [String] -> String
apply :: [[Char]] -> [Char]
apply = [Char] -> [Char]
parens ([Char] -> [Char]) -> ([[Char]] -> [Char]) -> [[Char]] -> [Char]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [[Char]] -> [Char]
unwords
partitionM :: Applicative m => (a -> m Bool) -> [a] -> m ([a], [a])
partitionM :: forall (m :: * -> *) a.
Applicative m =>
(a -> m Bool) -> [a] -> m ([a], [a])
partitionM a -> m Bool
f =
(a -> m ([a], [a]) -> m ([a], [a]))
-> m ([a], [a]) -> [a] -> m ([a], [a])
forall a b. (a -> b -> b) -> b -> [a] -> b
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr (\ a
x -> (Bool -> ([a], [a]) -> ([a], [a]))
-> m Bool -> m ([a], [a]) -> m ([a], [a])
forall a b c. (a -> b -> c) -> m a -> m b -> m c
forall (f :: * -> *) a b c.
Applicative f =>
(a -> b -> c) -> f a -> f b -> f c
liftA2 ((([a], [a]) -> ([a], [a]))
-> (([a], [a]) -> ([a], [a])) -> Bool -> ([a], [a]) -> ([a], [a])
forall a. a -> a -> Bool -> a
bool (([a] -> [a]) -> ([a], [a]) -> ([a], [a])
forall b c a. (b -> c) -> (a, b) -> (a, c)
forall (p :: * -> * -> *) b c a.
Bifunctor p =>
(b -> c) -> p a b -> p a c
second (a
xa -> [a] -> [a]
forall a. a -> [a] -> [a]
:)) (([a] -> [a]) -> ([a], [a]) -> ([a], [a])
forall a b c. (a -> b) -> (a, c) -> (b, c)
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first (a
xa -> [a] -> [a]
forall a. a -> [a] -> [a]
:))) (m Bool -> m ([a], [a]) -> m ([a], [a]))
-> m Bool -> m ([a], [a]) -> m ([a], [a])
forall a b. (a -> b) -> a -> b
$ a -> m Bool
f a
x)
(([a], [a]) -> m ([a], [a])
forall a. a -> m a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ([], []))