|
| 1 | +{-# LANGUAGE OverloadedStrings #-} |
| 2 | +{-# LANGUAGE GADTs #-} |
| 3 | +module Haskell.Ide.ApplyRefactPlugin where |
| 4 | + |
| 5 | +import Control.Exception |
| 6 | +import Control.Monad.IO.Class |
| 7 | +import qualified Data.Text as T |
| 8 | +import qualified Data.Text.IO as T |
| 9 | +import Data.Vinyl |
| 10 | +import Haskell.Ide.Engine.MonadFunctions |
| 11 | +import Haskell.Ide.Engine.PluginDescriptor |
| 12 | +import Haskell.Ide.Engine.PluginUtils |
| 13 | +import Haskell.Ide.Engine.SemanticTypes |
| 14 | +import qualified Language.Haskell.GhcMod as GM (defaultOptions) |
| 15 | +import Language.Haskell.HLint |
| 16 | +import Language.Haskell.HLint3 |
| 17 | +import Refact.Apply |
| 18 | +import qualified Refact.Types as R |
| 19 | +import Refact.Types hiding (SrcSpan) |
| 20 | +import System.Directory |
| 21 | +import System.Exit |
| 22 | +import System.FilePath.Posix |
| 23 | +import System.IO |
| 24 | +import System.IO.Extra |
| 25 | + |
| 26 | +-- --------------------------------------------------------------------- |
| 27 | + |
| 28 | +applyRefactDescriptor :: PluginDescriptor |
| 29 | +applyRefactDescriptor = PluginDescriptor |
| 30 | + { |
| 31 | + pdUIShortName = "ApplyRefact" |
| 32 | + , pdUIOverview = "apply-refact applies refactorings specified by the refact package. It is currently integrated into hlint to enable the automatic application of suggestions." |
| 33 | + , pdCommands = |
| 34 | + [ |
| 35 | + buildCommand applyOneCmd "applyOne" "Apply a single hint" |
| 36 | + [".hs"] [CtxPoint] [] |
| 37 | + |
| 38 | + , buildCommand applyAllCmd "applyAll" "Apply all hints to the file" |
| 39 | + [".hs"] [CtxFile] [] |
| 40 | + |
| 41 | + ] |
| 42 | + , pdExposedServices = [] |
| 43 | + , pdUsedServices = [] |
| 44 | + } |
| 45 | + |
| 46 | +-- --------------------------------------------------------------------- |
| 47 | + |
| 48 | +applyOneCmd :: CommandFunc HieDiff |
| 49 | +applyOneCmd = CmdSync $ \_ctxs req -> do |
| 50 | + case getParams (IdFile "file" :& IdPos "start_pos" :& RNil) req of |
| 51 | + Left err -> return err |
| 52 | + Right (ParamFile fileName :& ParamPos pos :& RNil) -> do |
| 53 | + res <- liftIO $ applyHint (T.unpack fileName) (Just pos) |
| 54 | + logm $ "applyOneCmd:res=" ++ show res |
| 55 | + case res of |
| 56 | + Left err -> return $ IdeResponseFail (IdeError PluginError |
| 57 | + (T.pack $ "applyOne: " ++ show err) Nothing) |
| 58 | + Right fs -> return (IdeResponseOk fs) |
| 59 | + Right _ -> return $ IdeResponseError (IdeError InternalError |
| 60 | + "ApplyRefactPlugin.applyOneCmd: ghc’s exhaustiveness checker is broken" Nothing) |
| 61 | + |
| 62 | + |
| 63 | +-- --------------------------------------------------------------------- |
| 64 | + |
| 65 | +applyAllCmd :: CommandFunc HieDiff |
| 66 | +applyAllCmd = CmdSync $ \_ctxs req -> do |
| 67 | + case getParams (IdFile "file" :& RNil) req of |
| 68 | + Left err -> return err |
| 69 | + Right (ParamFile fileName :& RNil) -> do |
| 70 | + res <- liftIO $ applyHint (T.unpack fileName) Nothing |
| 71 | + logm $ "applyAllCmd:res=" ++ show res |
| 72 | + case res of |
| 73 | + Left err -> return $ IdeResponseFail (IdeError PluginError |
| 74 | + (T.pack $ "applyOne: " ++ show err) Nothing) |
| 75 | + Right fs -> return (IdeResponseOk fs) |
| 76 | + Right _ -> return $ IdeResponseError (IdeError InternalError |
| 77 | + "ApplyRefactPlugin.applyOneCmd: ghc’s exhaustiveness checker is broken" Nothing) |
| 78 | + |
| 79 | + |
| 80 | +-- --------------------------------------------------------------------- |
| 81 | + |
| 82 | +applyHint :: FilePath -> Maybe Pos -> IO (Either String HieDiff) |
| 83 | +applyHint file mpos = do |
| 84 | + withTempFile $ \f -> do |
| 85 | + -- absFile <- makeAbsolute file |
| 86 | + -- hlint /tmp/Foo.hs --refactor --refactor-options="-o /tmp/Bar.hs --pos 2,8" |
| 87 | + |
| 88 | + let |
| 89 | + optsf = "-o " ++ f |
| 90 | + opts = case mpos of |
| 91 | + Nothing -> optsf |
| 92 | + Just (r,c) -> optsf ++ " --pos " ++ show r ++ "," ++ show c |
| 93 | + -- let hlintOpts = [file, "--quiet", "--refactor", "--refactor-options=" ++ opts ] |
| 94 | + let hlintOpts = [file, "--quiet" ] |
| 95 | + logm $ "applyHint=" ++ show hlintOpts |
| 96 | + res <- catchException $ hlint hlintOpts |
| 97 | + logm $ "applyHint:res=" ++ show res |
| 98 | + -- res <- hlint hlintOpts |
| 99 | + case res of |
| 100 | + Left x -> return $ Left (show x) |
| 101 | + Right x -> do |
| 102 | + let commands = makeApplyRefact x |
| 103 | + logm $ "applyHint:commands=" ++ show commands |
| 104 | + appliedFile <- applyRefactorings mpos commands file |
| 105 | + diff <- makeDiffResult file (T.pack appliedFile) |
| 106 | + logm $ "applyHint:diff=" ++ show diff |
| 107 | + return $ Right diff |
| 108 | + |
| 109 | +-- --------------------------------------------------------------------- |
| 110 | + |
| 111 | +makeApplyRefact :: [Suggestion] -> [(String, [Refactoring R.SrcSpan])] |
| 112 | +makeApplyRefact suggestions = |
| 113 | + map (\(Suggestion i) -> (show i, ideaRefactoring i)) suggestions |
| 114 | + |
| 115 | +-- --------------------------------------------------------------------- |
| 116 | + |
| 117 | + |
| 118 | +makeDiffResult :: FilePath -> T.Text -> IO HieDiff |
| 119 | +makeDiffResult orig new = do |
| 120 | + origText <- T.readFile orig |
| 121 | + let (HieDiff f s d) = diffText (orig,origText) ("changed",new) |
| 122 | + f' <- liftIO $ makeRelativeToCurrentDirectory f |
| 123 | + -- return (HieDiff f' s' d) |
| 124 | + return (HieDiff f' "changed" d) |
| 125 | + |
| 126 | +-- --------------------------------------------------------------------- |
| 127 | + |
| 128 | +catchException :: (IO t) -> IO (Either String t) |
| 129 | +catchException f = do |
| 130 | + res <- handle handler (f >>= \r -> return $ Right r) |
| 131 | + return res |
| 132 | + where |
| 133 | + handler:: SomeException -> IO (Either String t) |
| 134 | + handler e = return (Left (show e)) |
0 commit comments