Commit 9e9446a6 authored by Andrew Gibiansky's avatar Andrew Gibiansky

Fixing path completion (does ~, completes empty things, etc)

parent 28738119
...@@ -11,15 +11,19 @@ This has a limited amount of context sensitivity. It distinguishes between four ...@@ -11,15 +11,19 @@ This has a limited amount of context sensitivity. It distinguishes between four
-} -}
module IHaskell.Eval.Completion (complete, completionTarget, completionType, CompletionType(..)) where module IHaskell.Eval.Completion (complete, completionTarget, completionType, CompletionType(..)) where
import ClassyPrelude hiding (liftIO)
--import Prelude
import Control.Applicative ((<$>)) import Control.Applicative ((<$>))
import Data.ByteString.UTF8 hiding (drop, take) import Data.ByteString.UTF8 hiding (drop, take)
import Data.Char import Data.Char
import Data.List (find, isPrefixOf, nub, findIndex, intercalate, elemIndex) import Data.List (nub, init, last, head, elemIndex)
import Data.List.Split import Data.List.Split
import Data.List.Split.Internals import Data.List.Split.Internals
import Data.Maybe import Data.Maybe
import Data.String.Utils (strip, startswith, endswith, replace) import Data.String.Utils (strip, startswith, endswith, replace)
import Prelude import qualified Data.String.Utils as StringUtils
import System.Environment (getEnv)
import GHC import GHC
import DynFlags import DynFlags
...@@ -31,10 +35,8 @@ import Outputable (showPpr) ...@@ -31,10 +35,8 @@ import Outputable (showPpr)
import System.Directory import System.Directory
import System.FilePath import System.FilePath
import MonadUtils (MonadIO) import MonadUtils (MonadIO)
import Control.Monad (filterM, mapM, liftM)
import System.Console.Haskeline.Completion 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) import IHaskell.Eval.Evaluate (Interpreter)
...@@ -67,7 +69,7 @@ complete line pos = do ...@@ -67,7 +69,7 @@ complete line pos = do
matchedText = intercalate "." target matchedText = intercalate "." target
options <- options <-
case completionType line target of case completionType line pos target of
Empty -> return [] Empty -> return []
Identifier candidate -> Identifier candidate ->
...@@ -116,13 +118,22 @@ getTrueModuleName name = do ...@@ -116,13 +118,22 @@ getTrueModuleName name = do
Nothing -> return name Nothing -> return name
Just trueImp -> return $ showPpr flags $ unLoc $ ideclName trueImp Just trueImp -> return $ showPpr flags $ unLoc $ ideclName trueImp
completionType :: String -> [String] -> CompletionType -- | Get which type of completion this is from the surrounding context.
completionType line [] = Empty completionType :: String -- ^ The line on which the completion is being done.
completionType line target -> Int -- ^ Location of the cursor in the line.
| startswith ":! " stripped -> [String] -- ^ The identifier being completed (pieces separated by dots).
= FilePath complete_target -> CompletionType
completionType line loc target
-- File and directory completions are special
| startswith ":!" stripped
= FilePath lineUpToCursor
| startswith ":l" stripped | startswith ":l" stripped
= HsFilePath complete_target = HsFilePath lineUpToCursor
-- Use target for other completions.
-- If it's empty, no completion.
| null target
= Empty
| startswith "import" stripped && isModName | startswith "import" stripped && isModName
= ModuleName dotted candidate = ModuleName dotted candidate
| isModName && (not . null . init) target | isModName && (not . null . init) target
...@@ -137,9 +148,7 @@ completionType line target ...@@ -137,9 +148,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 lineUpToCursor = take loc line
-- | Get the word under a given cursor location. -- | Get the word under a given cursor location.
completionTarget :: String -> Int -> [String] completionTarget :: String -> Int -> [String]
...@@ -156,6 +165,7 @@ completionTarget code cursor = expandCompletionPiece pieceToComplete ...@@ -156,6 +165,7 @@ completionTarget code cursor = expandCompletionPiece pieceToComplete
delimPolicy = Drop delimPolicy = Drop
} }
isDelim :: Char -> Int -> Bool
isDelim char idx = char `elem` neverIdent || isSymbol char isDelim char idx = char `elem` neverIdent || isSymbol char
splitAlongCursor :: [[(Char, Int)]] -> [[(Char, Int)]] splitAlongCursor :: [[(Char, Int)]] -> [[(Char, Int)]]
...@@ -166,29 +176,66 @@ completionTarget code cursor = expandCompletionPiece pieceToComplete ...@@ -166,29 +176,66 @@ completionTarget code cursor = expandCompletionPiece pieceToComplete
Just idx -> take (idx + 1) x:drop (idx + 1) x:splitAlongCursor xs Just idx -> take (idx + 1) x:drop (idx + 1) x:splitAlongCursor xs
-- These are never part of an identifier. -- These are never part of an identifier.
neverIdent :: String
neverIdent = " \n\t(),{}[]\\'\"`" neverIdent = " \n\t(),{}[]\\'\"`"
expandCompletionPiece Nothing = [] expandCompletionPiece Nothing = []
expandCompletionPiece (Just str) = splitOn "." str expandCompletionPiece (Just str) = splitOn "." str
completePathFilter :: (String -> Bool) -- ^ filter files getHome :: IO String
-> (String -> Bool) -- ^ filter directories getHome = do
-> String -- ^ line contents left of cursor homeEither <- try $ getEnv "HOME" :: IO (Either SomeException String)
-> String -- ^ line contents right of cursor return $ case homeEither of
-> Interpreter [String] Left _ -> "~"
completePathFilter fileFilter dirFilter loc roc = Right home -> home
do (_, comps) <- MonadIO.liftIO $ (completeFilename (reverse loc, roc))
let completions = map replacement comps dirExpand :: String -> IO String
dirs <- liftIO $ filterM doesDirectoryExist completions dirExpand str = do
files <- liftIO $ filterM (liftM not . doesDirectoryExist) completions home <- getHome
let dirs' = filter dirFilter files return $ replace "~" home str
files' = filter fileFilter dirs
return $ filter (\x -> elem x $ dirs' ++ files') completions unDirExpand :: String -> IO String
unDirExpand str = do
home <- getHome
return $ replace home "~" str
completePath :: String -> Interpreter [String] completePath :: String -> Interpreter [String]
completePath loc = completePathFilter (const True) (const True) loc "" completePath line = completePathFilter acceptAll acceptAll line ""
where acceptAll = const True
completePathWithExtensions :: [String] -> String -> Interpreter [String] completePathWithExtensions :: [String] -> String -> Interpreter [String]
completePathWithExtensions extensions loc = completePathWithExtensions extensions line =
completePathFilter (\s -> any (\x -> endswith x s) extensions) (const True) loc "" completePathFilter (extensionIsOneOf extensions) acceptAll line ""
where
acceptAll = const True
extensionIsOneOf exts str = any (str `endswith`) exts
completePathFilter :: (String -> Bool) -- ^ File filter: test whether to include this file.
-> (String -> Bool) -- ^ Directory filter: test whether to include this directory.
-> String -- ^ Line contents to the left of the cursor.
-> String -- ^ Line contents to the right of the cursor.
-> Interpreter [String]
completePathFilter includeFile includeDirectory left right = liftIO $ do
-- Get the completions from Haskeline. It has a bit of a strange API.
expanded <- dirExpand left
completions <- map replacement <$> snd <$> completeFilename (reverse expanded, right)
-- Split up into files and directories.
-- Filter out ones we don't want.
areDirs <- mapM doesDirectoryExist completions
let dirs = filter includeDirectory $ map fst $ filter snd $ zip completions areDirs
files = filter includeFile $ map fst $ filter (not . snd) $ zip completions areDirs
-- Return directories before files. However, stick everything that starts
-- with a dot after everything else. If we wanted to keep original
-- order, we could instead use
-- filter (`elem` (dirs ++ files)) completions
suggestions <- mapM unDirExpand $ dirs ++ files
let isHidden str = startswith "." . last . StringUtils.split "/" $
if endswith "/" str
then init str
else str
visible = filter (not . isHidden) suggestions
hidden = filter isHidden suggestions
return $ visible ++ hidden
...@@ -32,6 +32,7 @@ import System.Exit ...@@ -32,6 +32,7 @@ import System.Exit
import Data.Maybe (fromJust) import Data.Maybe (fromJust)
import qualified Control.Monad.IO.Class as MonadIO (MonadIO, liftIO) import qualified Control.Monad.IO.Class as MonadIO (MonadIO, liftIO)
import qualified MonadUtils (MonadIO, liftIO) import qualified MonadUtils (MonadIO, liftIO)
import System.Environment (getEnv)
import NameSet import NameSet
import Name import Name
...@@ -236,7 +237,7 @@ evaluate kernelState code output = do ...@@ -236,7 +237,7 @@ evaluate kernelState code output = do
storeItCommand execCount = Statement $ printf "let it%d = it" execCount storeItCommand execCount = Statement $ printf "let it%d = it" execCount
safely :: KernelState -> Interpreter EvalOut -> Interpreter EvalOut safely :: KernelState -> Interpreter EvalOut -> Interpreter EvalOut
safely state exec = ghandle handler exec safely state = ghandle handler
where where
handler :: SomeException -> Interpreter EvalOut handler :: SomeException -> Interpreter EvalOut
handler exception = handler exception =
...@@ -424,15 +425,21 @@ evalCommand _ (Directive SetOpt option) state = do ...@@ -424,15 +425,21 @@ evalCommand _ (Directive SetOpt option) state = do
evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $ liftIO $ evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $ liftIO $
case words cmd of case words cmd of
"cd":dirs -> "cd":dirs -> do
let directory = unwords dirs in do -- Get home so we can replace '~` with it.
exists <- doesDirectoryExist directory homeEither <- try $ getEnv "HOME" :: IO (Either SomeException String)
if exists let home = case homeEither of
then do Left _ -> "~"
setCurrentDirectory directory Right val -> val
return []
else let directory = replace "~" home $ unwords dirs
return $ displayError $ printf "No such directory: '%s'" directory exists <- doesDirectoryExist directory
if exists
then do
setCurrentDirectory directory
return []
else
return $ displayError $ printf "No such directory: '%s'" directory
cmd -> do cmd -> do
(readEnd, writeEnd) <- createPipe (readEnd, writeEnd) <- createPipe
handle <- fdToHandle writeEnd handle <- fdToHandle writeEnd
......
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