Commit 1cda25e4 authored by Andrew Gibiansky's avatar Andrew Gibiansky

Merge pull request #103 from edechter/path_completion

Path completion
parents 92361c93 42365e54
...@@ -73,11 +73,12 @@ library ...@@ -73,11 +73,12 @@ library
directory, directory,
here, here,
system-filepath, system-filepath,
filemanip,
filepath, filepath,
cereal ==0.3.*, cereal ==0.3.*,
text >=0.11, text >=0.11,
mtl >= 2.1 mtl >= 2.1
transformers,
haskeline
exposed-modules: IHaskell.Display exposed-modules: IHaskell.Display
IHaskell.Eval.Completion IHaskell.Eval.Completion
IHaskell.Eval.Evaluate IHaskell.Eval.Evaluate
...@@ -145,11 +146,12 @@ executable IHaskell ...@@ -145,11 +146,12 @@ executable IHaskell
directory, directory,
here, here,
system-filepath, system-filepath,
filemanip,
filepath, filepath,
cereal ==0.3.*, cereal ==0.3.*,
text >=0.11, text >=0.11,
mtl >= 2.1 mtl >= 2.1,
transformers,
haskeline
Test-Suite hspec Test-Suite hspec
hs-source-dirs: src hs-source-dirs: src
...@@ -185,7 +187,9 @@ Test-Suite hspec ...@@ -185,7 +187,9 @@ Test-Suite hspec
filepath, filepath,
cereal ==0.3.*, cereal ==0.3.*,
text >=0.11, text >=0.11,
mtl >= 2.1 mtl >= 2.1,
transformers,
haskeline
extensions: DoAndIfThenElse extensions: DoAndIfThenElse
OverloadedStrings OverloadedStrings
ExtendedDefaultRules ExtendedDefaultRules
......
...@@ -75,9 +75,9 @@ completes string expected = completionTarget newString cursorloc `shouldBe` expe ...@@ -75,9 +75,9 @@ completes string expected = completionTarget newString cursorloc `shouldBe` expe
Nothing -> error "Expected cursor written as '!'." Nothing -> error "Expected cursor written as '!'."
Just idx -> (replace "!" "" string, idx) Just idx -> (replace "!" "" string, idx)
completionHas_ action string expected = do completionHas_ wrap string expected = do
(matched, completions) <- doGhc $ do (matched, completions) <- doGhc $ do
initCompleter action wrap $ do initCompleter
complete newString cursorloc complete newString cursorloc
let existsInCompletion = (`elem` completions) let existsInCompletion = (`elem` completions)
unmatched = filter (not . existsInCompletion) expected unmatched = filter (not . existsInCompletion) expected
...@@ -86,10 +86,12 @@ completionHas_ action string expected = do ...@@ -86,10 +86,12 @@ completionHas_ action string expected = do
Nothing -> error "Expected cursor written as '!'." Nothing -> error "Expected cursor written as '!'."
Just idx -> (replace "!" "" string, idx) Just idx -> (replace "!" "" string, idx)
completionHas = completionHas_ (return ()) completionHas = completionHas_ id
initCompleter :: GhcMonad m => m a -> m a initCompleter :: GhcMonad m => m ()
initCompleter action = do initCompleter = do
pwd <- Eval.liftIO $ getCurrentDirectory
--Eval.liftIO $ traceIO $ pwd
flags <- getSessionDynFlags flags <- getSessionDynFlags
setSessionDynFlags $ flags { hscTarget = HscInterpreted, ghcLink = LinkInMemory } setSessionDynFlags $ flags { hscTarget = HscInterpreted, ghcLink = LinkInMemory }
...@@ -99,7 +101,6 @@ initCompleter action = do ...@@ -99,7 +101,6 @@ initCompleter action = do
"import qualified Data.List as List", "import qualified Data.List as List",
"import Data.Maybe as Maybe"] "import Data.Maybe as Maybe"]
setContext $ map IIDecl imports setContext $ map IIDecl imports
action
withHsDirectory :: (FilePath -> Sh ()) -> IO () withHsDirectory :: (FilePath -> Sh ()) -> IO ()
withHsDirectory f = shelly $ withTmpDir $ \dirPath -> withHsDirectory f = shelly $ withTmpDir $ \dirPath ->
...@@ -141,7 +142,7 @@ completionTests = do ...@@ -141,7 +142,7 @@ completionTests = do
completionType "A.x" ["A", "x"] `shouldBe` Qualified "A" "x" completionType "A.x" ["A", "x"] `shouldBe` Qualified "A" "x"
completionType "a.x" ["a", "x"] `shouldBe` Identifier "x" completionType "a.x" ["a", "x"] `shouldBe` Identifier "x"
completionType "pri" ["pri"] `shouldBe` Identifier "pri" completionType "pri" ["pri"] `shouldBe` Identifier "pri"
completionType ":load A" [""] `shouldBe` HsFilePath "A" completionType ":load A" ["A"] `shouldBe` HsFilePath "A"
it "properly completes identifiers" $ do it "properly completes identifiers" $ do
"pri!" `completionHas` ["print"] "pri!" `completionHas` ["print"]
...@@ -166,16 +167,23 @@ completionTests = do ...@@ -166,16 +167,23 @@ completionTests = do
withHsDirectory $ \dirPath -> withHsDirectory $ \dirPath ->
let loading xs = ":load " ++ encodeString xs let loading xs = ":load " ++ encodeString xs
paths xs = map encodeString xs paths xs = map encodeString xs
completionHas' = completionHas_ $ completionHas' = completionHas_ fun
do Eval.evaluate defaultKernelState fun action = do pwd <- Eval.liftIO getCurrentDirectory
Eval.evaluate defaultKernelState
(":! cd " ++ dirPath) (":! cd " ++ dirPath)
(\b d -> return ()) (\b d -> return ())
out <- action
Eval.evaluate defaultKernelState
(":! cd " ++ pwd)
(\b d -> return ())
return out
in liftIO $ do in liftIO $ do
loading ("dir" </> "file!") `completionHas'` paths ["dir" </> "file2.hs", loading ("dir" </> "file!") `completionHas'` paths ["dir" </> "file2.hs",
"dir" </> "file2.lhs"] "dir" </> "file2.lhs"]
loading ("" </> "file1!") `completionHas'` paths ["" </> "file1.hs", loading ("" </> "file1!") `completionHas'` paths ["" </> "file1.hs",
"" </> "file1.lhs"] "" </> "file1.lhs"]
loading ("" </> "file1!") `completionHas'` paths ["" </> "file1.hs",
"" </> "file1.lhs"]
evalTests = do evalTests = do
describe "Code Evaluation" $ do describe "Code Evaluation" $ do
......
...@@ -27,15 +27,17 @@ import GhcMonad ...@@ -27,15 +27,17 @@ import GhcMonad
import PackageConfig import PackageConfig
import Outputable (showPpr) import Outputable (showPpr)
import qualified System.FilePath.Find as Find (find)
import System.FilePath.Find hiding (find)
import System.Directory import System.Directory
import System.FilePath.GlobPattern
import System.FilePath import System.FilePath
import MonadUtils (MonadIO) import MonadUtils (MonadIO)
import Control.Monad (filterM, mapM, liftM) import Control.Monad (filterM, mapM, liftM)
import System.Console.Haskeline.Completion
import qualified Control.Monad.IO.Class as MonadIO (MonadIO(), liftIO)
import IHaskell.Types import IHaskell.Types
import IHaskell.Eval.Evaluate (Interpreter)
data CompletionType data CompletionType
...@@ -45,9 +47,10 @@ data CompletionType ...@@ -45,9 +47,10 @@ data CompletionType
| Qualified String String | Qualified String String
| ModuleName String String | ModuleName String String
| HsFilePath String | HsFilePath String
| FilePath String
deriving (Show, Eq) deriving (Show, Eq)
complete :: GHC.GhcMonad m => String -> Int -> m (String, [String]) complete :: String -> Int -> Interpreter (String, [String])
complete line pos = do complete line pos = do
flags <- getSessionDynFlags flags <- getSessionDynFlags
rdrNames <- map (showPpr flags) <$> getRdrNamesInScope rdrNames <- map (showPpr flags) <$> getRdrNamesInScope
...@@ -89,12 +92,13 @@ complete line pos = do ...@@ -89,12 +92,13 @@ complete line pos = do
nonames = map ("No" ++) names nonames = map ("No" ++) names
return $ filter (ext `isPrefixOf`) $ names ++ nonames return $ filter (ext `isPrefixOf`) $ names ++ nonames
HsFilePath path -> do pwd <- liftIO getCurrentDirectory HsFilePath path -> completePathWithExtensions [".hs", ".lhs"] path
completePath pwd (Just [".hs", ".lhs"]) path
FilePath path -> completePath path
return (matchedText, options) return (matchedText, options)
getTrueModuleName :: GhcMonad m => String -> m String getTrueModuleName :: String -> Interpreter String
getTrueModuleName name = do getTrueModuleName name = do
-- Only use the things that were actually imported -- Only use the things that were actually imported
let onlyImportDecl (IIDecl decl) = Just decl let onlyImportDecl (IIDecl decl) = Just decl
...@@ -115,8 +119,10 @@ getTrueModuleName name = do ...@@ -115,8 +119,10 @@ getTrueModuleName name = do
completionType :: String -> [String] -> CompletionType completionType :: String -> [String] -> CompletionType
completionType line [] = Empty completionType line [] = Empty
completionType line target completionType line target
| startswith ":! " stripped
= FilePath complete_target
| startswith ":l" stripped | startswith ":l" stripped
= HsFilePath $ last $ splitOn " " stripped = HsFilePath complete_target
| startswith "import" stripped && isModName | startswith "import" stripped && isModName
= ModuleName dotted candidate = ModuleName dotted candidate
| isModName && (not . null . init) target | isModName && (not . null . init) target
...@@ -131,6 +137,7 @@ completionType line target ...@@ -131,6 +137,7 @@ completionType line target
dots = intercalate "." . init dots = intercalate "." . init
isModName = all isCapitalized (init target) isModName = all isCapitalized (init target)
isCapitalized = isUpper . head isCapitalized = isUpper . head
complete_target = intercalate "." target
...@@ -164,32 +171,24 @@ completionTarget code cursor = expandCompletionPiece pieceToComplete ...@@ -164,32 +171,24 @@ completionTarget code cursor = expandCompletionPiece pieceToComplete
expandCompletionPiece Nothing = [] expandCompletionPiece Nothing = []
expandCompletionPiece (Just str) = splitOn "." str expandCompletionPiece (Just str) = splitOn "." str
completePath :: MonadIO m => completePathFilter :: (String -> Bool) -- ^ filter files
String -- ^ Current directory -> (String -> Bool) -- ^ filter directories
-> Maybe [String] -- ^ list of file extensions -> String -- ^ line contents left of cursor
-> String -- ^ prefix to be completed -> String -- ^ line contents right of cursor
-> m [String] -- ^ completions, that is, if prefix is "Mai" one completion might be "Main.hs" -> Interpreter [String]
completePath currDir exts prefix completePathFilter fileFilter dirFilter loc roc =
= let absolutePrefix = combine currDir prefix do (_, comps) <- MonadIO.liftIO $ (completeFilename (reverse loc, roc))
searchDir = dropFileName absolutePrefix let completions = map replacement comps
pattern = absolutePrefix ++ "*" dirs <- liftIO $ filterM doesDirectoryExist completions
completions = liftIO $ Find.find always (filePath ~~? pattern) searchDir files <- liftIO $ filterM (liftM not . doesDirectoryExist) completions
allFileCompletions = completions >>= liftIO . filterM (liftM not . doesDirectoryExist) let dirs' = filter dirFilter files
fileCompletions = case exts of files' = filter fileFilter dirs
Nothing -> allFileCompletions return $ filter (\x -> elem x $ dirs' ++ files') completions
Just exts -> do xs <- allFileCompletions
return $ filter (\s -> or [endswith ext s | ext <- exts]) xs completePath :: String -> Interpreter [String]
dirCompletions = completions completePath loc = completePathFilter (const True) (const True) loc ""
>>= liftIO . filterM doesDirectoryExist
>>= \xs -> do return $ [x ++ [pathSeparator] | x <- xs] completePathWithExtensions :: [String] -> String -> Interpreter [String]
relativeCompletions = do validSearchDir <- liftIO $ doesDirectoryExist searchDir completePathWithExtensions extensions loc =
if validSearchDir then completePathFilter (\s -> any (\x -> endswith x s) extensions) (const True) loc ""
do xs <- fileCompletions
ys <- dirCompletions
return $ map (cut $ currDir ++ [pathSeparator]) $ xs ++ ys
else return []
cut :: String -> String -> String
cut (x:xs) z@(y:ys) | x == y = cut xs ys
| otherwise = z
cut _ z = z
in relativeCompletions
{-# LANGUAGE DoAndIfThenElse, NoOverloadedStrings #-} {-# LANGUAGE DoAndIfThenElse, NoOverloadedStrings, TypeSynonymInstances #-}
{- | Description : Wrapper around GHC API, exposing a single `evaluate` interface that runs {- | Description : Wrapper around GHC API, exposing a single `evaluate` interface that runs
a statement, declaration, import, or directive. a statement, declaration, import, or directive.
...@@ -29,6 +30,8 @@ import Control.Monad (guard) ...@@ -29,6 +30,8 @@ import Control.Monad (guard)
import System.Process import System.Process
import System.Exit import System.Exit
import Data.Maybe (fromJust) import Data.Maybe (fromJust)
import qualified Control.Monad.IO.Class as MonadIO (MonadIO, liftIO)
import qualified MonadUtils as MonadUtils (MonadIO, liftIO)
import NameSet import NameSet
import Name import Name
...@@ -81,6 +84,9 @@ write x = when debug $ liftIO $ hPutStrLn stderr $ "DEBUG: " ++ x ...@@ -81,6 +84,9 @@ write x = when debug $ liftIO $ hPutStrLn stderr $ "DEBUG: " ++ x
type Interpreter = Ghc type Interpreter = Ghc
instance MonadIO.MonadIO Interpreter where
liftIO = MonadUtils.liftIO
globalImports :: [String] globalImports :: [String]
globalImports = globalImports =
[ "import IHaskell.Display" [ "import IHaskell.Display"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment