Commit 7ba7c4d1 authored by Andrew Gibiansky's avatar Andrew Gibiansky

Merge pull request #444 from gibiansky/enforce-proper-formatting

Enforce proper formatting
parents be10d383 dadd074f
...@@ -16,3 +16,6 @@ todo ...@@ -16,3 +16,6 @@ todo
profile/profile.tar profile/profile.tar
.cabal-sandbox .cabal-sandbox
cabal.sandbox.config cabal.sandbox.config
.tmp1
.tmp2
.tmp3
...@@ -14,6 +14,7 @@ before_install: ...@@ -14,6 +14,7 @@ before_install:
- travis_retry sudo apt-get update - travis_retry sudo apt-get update
- travis_retry sudo apt-get install cabal-install-$CABALVER ghc-$GHCVER # see note about happy/alex - travis_retry sudo apt-get install cabal-install-$CABALVER ghc-$GHCVER # see note about happy/alex
- travis_retry sudo apt-get install libmagic-dev - travis_retry sudo apt-get install libmagic-dev
- travis_retry sudo apt-get install python3
- travis_retry git clone http://www.github.com/zeromq/zeromq4-x.git libzmq - travis_retry git clone http://www.github.com/zeromq/zeromq4-x.git libzmq
- export OLDPWD=$(pwd) && cd libzmq && travis_retry ./autogen.sh && travis_retry ./configure && make && travis_retry sudo make install && travis_retry sudo ldconfig && cd $OLDPWD - export OLDPWD=$(pwd) && cd libzmq && travis_retry ./autogen.sh && travis_retry ./configure && make && travis_retry sudo make install && travis_retry sudo ldconfig && cd $OLDPWD
- export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$HOME/.cabal/bin:$PATH - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$HOME/.cabal/bin:$PATH
...@@ -34,6 +35,8 @@ install: ...@@ -34,6 +35,8 @@ install:
- | - |
if [ ${GHCVER%.*} = "7.8" ]; then if [ ${GHCVER%.*} = "7.8" ]; then
travis_retry cabal install arithmoi==0.4.* -fllvm travis_retry cabal install arithmoi==0.4.* -fllvm
travis_retry git clone http://www.github.com/gibiansky/hindent
cd hindent && cabal install && cd ..
fi fi
# Here starts the actual work to be performed for the package under test; any command which exits with a non-zero exit code causes the build to fail. # Here starts the actual work to be performed for the package under test; any command which exits with a non-zero exit code causes the build to fail.
...@@ -47,7 +50,10 @@ script: ...@@ -47,7 +50,10 @@ script:
# Build and run the test suite # Build and run the test suite
- travis_retry cabal configure --enable-tests - travis_retry cabal configure --enable-tests
- travis_retry cabal test --show-details=always - travis_retry cabal test --show-details=always
- |
if [ ${GHCVER%.*} = "7.8" ]; then
./verify_formatting.py
fi
- cabal sdist - cabal sdist
# The following scriptlet checks that the resulting source distribution can be built & installed # The following scriptlet checks that the resulting source distribution can be built & installed
......
{-# LANGUAGE OverloadedStrings, NoImplicitPrelude, FlexibleContexts #-} {-# LANGUAGE OverloadedStrings, NoImplicitPrelude, FlexibleContexts #-}
module IHaskell.BrokenPackages (getBrokenPackages) where module IHaskell.BrokenPackages (getBrokenPackages) where
import ClassyPrelude hiding ((<|>)) import ClassyPrelude hiding ((<|>))
...@@ -11,17 +12,13 @@ import Data.String.Utils (startswith) ...@@ -11,17 +12,13 @@ import Data.String.Utils (startswith)
import Shelly import Shelly
data BrokenPackage = BrokenPackage { data BrokenPackage = BrokenPackage { packageID :: String, brokenDeps :: [String] }
packageID :: String,
brokenDeps :: [String]
}
instance Show BrokenPackage where instance Show BrokenPackage where
show = packageID show = packageID
-- | Get a list of broken packages. -- | Get a list of broken packages. This function internally shells out to `ghc-pkg`, and parses the
-- This function internally shells out to `ghc-pkg`, and parses the output -- output in order to determine what packages are broken.
-- in order to determine what packages are broken.
getBrokenPackages :: IO [String] getBrokenPackages :: IO [String]
getBrokenPackages = shelly $ do getBrokenPackages = shelly $ do
silently $ errExit False $ run "ghc-pkg" ["check"] silently $ errExit False $ run "ghc-pkg" ["check"]
...@@ -32,7 +29,8 @@ getBrokenPackages = shelly $ do ...@@ -32,7 +29,8 @@ getBrokenPackages = shelly $ do
startswith " dependency" str startswith " dependency" str
ghcPkgOutput = unlines . filter rightStart . lines $ unpack checkOut ghcPkgOutput = unlines . filter rightStart . lines $ unpack checkOut
return $ case parse (many check) "ghc-pkg output" ghcPkgOutput of return $
case parse (many check) "ghc-pkg output" ghcPkgOutput of
Left err -> [] Left err -> []
Right pkgs -> map show pkgs Right pkgs -> map show pkgs
......
-- | Description : mostly reversible conversion between ipynb and lhs -- | Description : mostly reversible conversion between ipynb and lhs
module IHaskell.Convert (convert) where module IHaskell.Convert (convert) where
import Control.Monad.Identity (Identity(Identity), unless, when) import Control.Monad.Identity (Identity(Identity), unless, when)
import IHaskell.Convert.Args (ConvertSpec(ConvertSpec, convertInput, convertLhsStyle, convertOutput, convertOverwriteFiles, convertToIpynb), fromJustConvertSpec, toConvertSpec) import IHaskell.Convert.Args (ConvertSpec(..), fromJustConvertSpec, toConvertSpec)
import IHaskell.Convert.IpynbToLhs (ipynbToLhs) import IHaskell.Convert.IpynbToLhs (ipynbToLhs)
import IHaskell.Convert.LhsToIpynb (lhsToIpynb) import IHaskell.Convert.LhsToIpynb (lhsToIpynb)
import IHaskell.Flags (Argument) import IHaskell.Flags (Argument)
...@@ -10,12 +11,15 @@ import Text.Printf (printf) ...@@ -10,12 +11,15 @@ import Text.Printf (printf)
-- | used by @IHaskell convert@ -- | used by @IHaskell convert@
convert :: [Argument] -> IO () convert :: [Argument] -> IO ()
convert args = case fromJustConvertSpec (toConvertSpec args) of convert args =
ConvertSpec { convertToIpynb = Identity toIpynb, case fromJustConvertSpec (toConvertSpec args) of
convertInput = Identity inputFile, ConvertSpec
convertOutput = Identity outputFile, { convertToIpynb = Identity toIpynb
convertLhsStyle = Identity lhsStyle, , convertInput = Identity inputFile
convertOverwriteFiles = force } , convertOutput = Identity outputFile
, convertLhsStyle = Identity lhsStyle
, convertOverwriteFiles = force
}
| toIpynb -> do | toIpynb -> do
unless force (failIfExists outputFile) unless force (failIfExists outputFile)
lhsToIpynb lhsStyle inputFile outputFile lhsToIpynb lhsStyle inputFile outputFile
...@@ -29,5 +33,3 @@ failIfExists file = do ...@@ -29,5 +33,3 @@ failIfExists file = do
exists <- doesFileExist file exists <- doesFileExist file
when exists $ fail $ when exists $ fail $
printf "File %s already exists. To force supply --force." file printf "File %s already exists. To force supply --force." file
-- | Description: interpret flags parsed by "IHaskell.Flags" -- | Description: interpret flags parsed by "IHaskell.Flags"
module IHaskell.Convert.Args module IHaskell.Convert.Args (ConvertSpec(..), fromJustConvertSpec, toConvertSpec) where
(ConvertSpec(..),
fromJustConvertSpec,
toConvertSpec,
) where
import Control.Applicative ((<$>)) import Control.Applicative ((<$>))
import Control.Monad.Identity (Identity(Identity)) import Control.Monad.Identity (Identity(Identity))
...@@ -15,35 +11,36 @@ import IHaskell.Flags (Argument(..), LhsStyle, lhsStyleBird, NotebookFormat(..)) ...@@ -15,35 +11,36 @@ import IHaskell.Flags (Argument(..), LhsStyle, lhsStyleBird, NotebookFormat(..))
import System.FilePath ((<.>), dropExtension, takeExtension) import System.FilePath ((<.>), dropExtension, takeExtension)
import Text.Printf (printf) import Text.Printf (printf)
-- | ConvertSpec is the accumulator for command line arguments -- | ConvertSpec is the accumulator for command line arguments
data ConvertSpec f = ConvertSpec data ConvertSpec f =
{ convertToIpynb :: f Bool, ConvertSpec
convertInput :: f FilePath, { convertToIpynb :: f Bool
convertOutput :: f FilePath, , convertInput :: f FilePath
convertLhsStyle :: f (LhsStyle T.Text), , convertOutput :: f FilePath
convertOverwriteFiles :: Bool , convertLhsStyle :: f (LhsStyle T.Text)
, convertOverwriteFiles :: Bool
} }
-- | Convert a possibly-incomplete specification for what to convert -- | Convert a possibly-incomplete specification for what to convert into one which can be executed.
-- into one which can be executed. Calls error when data is missing. -- Calls error when data is missing.
fromJustConvertSpec :: ConvertSpec Maybe -> ConvertSpec Identity fromJustConvertSpec :: ConvertSpec Maybe -> ConvertSpec Identity
fromJustConvertSpec convertSpec = convertSpec { fromJustConvertSpec convertSpec = convertSpec
convertToIpynb = Identity toIpynb, { convertToIpynb = Identity toIpynb
convertInput = Identity inputFile, , convertInput = Identity inputFile
convertOutput = Identity outputFile, , convertOutput = Identity outputFile
convertLhsStyle = Identity $ fromMaybe , convertLhsStyle = Identity $ fromMaybe (T.pack <$> lhsStyleBird) (convertLhsStyle convertSpec)
(T.pack <$> lhsStyleBird)
(convertLhsStyle convertSpec)
} }
where where
toIpynb = fromMaybe (error "Error: direction for conversion unknown") toIpynb = fromMaybe (error "Error: direction for conversion unknown")
(convertToIpynb convertSpec) (convertToIpynb convertSpec)
(inputFile, outputFile) = case (convertInput convertSpec, convertOutput convertSpec) of (inputFile, outputFile) =
case (convertInput convertSpec, convertOutput convertSpec) of
(Nothing, Nothing) -> error "Error: no files specified for conversion" (Nothing, Nothing) -> error "Error: no files specified for conversion"
(Just i, Nothing) | toIpynb -> (i, dropExtension i <.> "ipynb") (Just i, Nothing)
| toIpynb -> (i, dropExtension i <.> "ipynb")
| otherwise -> (i, dropExtension i <.> "lhs") | otherwise -> (i, dropExtension i <.> "lhs")
(Nothing, Just o) | toIpynb -> (dropExtension o <.> "lhs", o) (Nothing, Just o)
| toIpynb -> (dropExtension o <.> "lhs", o)
| otherwise -> (dropExtension o <.> "ipynb", o) | otherwise -> (dropExtension o <.> "ipynb", o)
(Just i, Just o) -> (i, o) (Just i, Just o) -> (i, o)
...@@ -53,10 +50,8 @@ isFormatSpec (ConvertToFormat _) = True ...@@ -53,10 +50,8 @@ isFormatSpec (ConvertToFormat _) = True
isFormatSpec (ConvertFromFormat _) = True isFormatSpec (ConvertFromFormat _) = True
isFormatSpec _ = False isFormatSpec _ = False
toConvertSpec :: [Argument] -> ConvertSpec Maybe toConvertSpec :: [Argument] -> ConvertSpec Maybe
toConvertSpec args = mergeArgs otherArgs toConvertSpec args = mergeArgs otherArgs (mergeArgs formatSpecArgs initialConvertSpec)
(mergeArgs formatSpecArgs initialConvertSpec)
where where
(formatSpecArgs, otherArgs) = partition isFormatSpec args (formatSpecArgs, otherArgs) = partition isFormatSpec args
initialConvertSpec = ConvertSpec Nothing Nothing Nothing Nothing False initialConvertSpec = ConvertSpec Nothing Nothing Nothing Nothing False
...@@ -68,40 +63,39 @@ mergeArg :: Argument -> ConvertSpec Maybe -> ConvertSpec Maybe ...@@ -68,40 +63,39 @@ mergeArg :: Argument -> ConvertSpec Maybe -> ConvertSpec Maybe
mergeArg OverwriteFiles convertSpec = convertSpec { convertOverwriteFiles = True } mergeArg OverwriteFiles convertSpec = convertSpec { convertOverwriteFiles = True }
mergeArg (ConvertLhsStyle lhsStyle) convertSpec mergeArg (ConvertLhsStyle lhsStyle) convertSpec
| Just previousLhsStyle <- convertLhsStyle convertSpec, | Just previousLhsStyle <- convertLhsStyle convertSpec,
previousLhsStyle /= fmap T.pack lhsStyle = error $ printf previousLhsStyle /= fmap T.pack lhsStyle
"Conflicting lhs styles requested: <%s> and <%s>" = error $ printf "Conflicting lhs styles requested: <%s> and <%s>" (show lhsStyle)
(show lhsStyle) (show previousLhsStyle) (show previousLhsStyle)
| otherwise = convertSpec { convertLhsStyle = Just (T.pack <$> lhsStyle) } | otherwise = convertSpec { convertLhsStyle = Just (T.pack <$> lhsStyle) }
mergeArg (ConvertFrom inputFile) convertSpec mergeArg (ConvertFrom inputFile) convertSpec
| Just previousInputFile <- convertInput convertSpec, | Just previousInputFile <- convertInput convertSpec,
previousInputFile /= inputFile = error $ printf "Multiple input files specified: <%s> and <%s>" previousInputFile /= inputFile
inputFile previousInputFile = error $ printf "Multiple input files specified: <%s> and <%s>" inputFile previousInputFile
| otherwise = convertSpec { | otherwise = convertSpec
convertInput = Just inputFile, { convertInput = Just inputFile
convertToIpynb = case (convertToIpynb convertSpec, fromExt inputFile) of , convertToIpynb = case (convertToIpynb convertSpec, fromExt inputFile) of
(prev, Nothing) -> prev (prev, Nothing) -> prev
(prev @ (Just _), _) -> prev (prev@(Just _), _) -> prev
(Nothing, format) -> fmap (== LhsMarkdown) format (Nothing, format) -> fmap (== LhsMarkdown) format
} }
mergeArg (ConvertTo outputFile) convertSpec mergeArg (ConvertTo outputFile) convertSpec
| Just previousOutputFile <- convertOutput convertSpec, | Just previousOutputFile <- convertOutput convertSpec,
previousOutputFile /= outputFile = error $ printf "Multiple output files specified: <%s> and <%s>" previousOutputFile /= outputFile
outputFile previousOutputFile = error $ printf "Multiple output files specified: <%s> and <%s>" outputFile previousOutputFile
| otherwise = convertSpec { | otherwise = convertSpec
convertOutput = Just outputFile, { convertOutput = Just outputFile
convertToIpynb = case (convertToIpynb convertSpec, fromExt outputFile) of , convertToIpynb = case (convertToIpynb convertSpec, fromExt outputFile) of
(prev, Nothing) -> prev (prev, Nothing) -> prev
(prev @ (Just _), _) -> prev (prev@(Just _), _) -> prev
(Nothing, format) -> fmap (== IpynbFile) format (Nothing, format) -> fmap (== IpynbFile) format
} }
mergeArg unexpectedArg _ = error $ "IHaskell.Convert.mergeArg: impossible argument: " mergeArg unexpectedArg _ = error $ "IHaskell.Convert.mergeArg: impossible argument: "
++ show unexpectedArg ++ show unexpectedArg
-- | Guess the format based on the file extension. -- | Guess the format based on the file extension.
fromExt :: FilePath -> Maybe NotebookFormat fromExt :: FilePath -> Maybe NotebookFormat
fromExt s = case map toLower (takeExtension s) of fromExt s =
case map toLower (takeExtension s) of
".lhs" -> Just LhsMarkdown ".lhs" -> Just LhsMarkdown
".ipynb" -> Just IpynbFile ".ipynb" -> Just IpynbFile
_ -> Nothing _ -> Nothing
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
module IHaskell.Convert.IpynbToLhs (ipynbToLhs) where module IHaskell.Convert.IpynbToLhs (ipynbToLhs) where
import Control.Applicative ((<$>)) import Control.Applicative ((<$>))
...@@ -12,7 +13,7 @@ import qualified Data.Text.Lazy as T (concat, fromStrict, Text, unlines) ...@@ -12,7 +13,7 @@ import qualified Data.Text.Lazy as T (concat, fromStrict, Text, unlines)
import qualified Data.Text.Lazy.IO as T (writeFile) import qualified Data.Text.Lazy.IO as T (writeFile)
import Data.Vector (Vector) import Data.Vector (Vector)
import qualified Data.Vector as V (map, mapM, toList) import qualified Data.Vector as V (map, mapM, toList)
import IHaskell.Flags (LhsStyle(lhsBeginCode, lhsBeginOutput, lhsCodePrefix, lhsEndCode, lhsEndOutput, lhsOutputPrefix)) import IHaskell.Flags (LhsStyle(..))
ipynbToLhs :: LhsStyle T.Text ipynbToLhs :: LhsStyle T.Text
-> FilePath -- ^ the filename of an ipython notebook -> FilePath -- ^ the filename of an ipython notebook
...@@ -22,8 +23,7 @@ ipynbToLhs sty from to = do ...@@ -22,8 +23,7 @@ ipynbToLhs sty from to = do
Just (js :: Object) <- decode <$> L.readFile from Just (js :: Object) <- decode <$> L.readFile from
case M.lookup "cells" js of case M.lookup "cells" js of
Just (Array cells) -> Just (Array cells) ->
T.writeFile to $ T.unlines $ V.toList T.writeFile to $ T.unlines $ V.toList $ V.map (\(Object y) -> convCell sty y) cells
$ V.map (\(Object y) -> convCell sty y) cells
_ -> error "IHaskell.Convert.ipynbTolhs: json does not follow expected schema" _ -> error "IHaskell.Convert.ipynbTolhs: json does not follow expected schema"
concatWithPrefix :: T.Text -- ^ the prefix to add to every line concatWithPrefix :: T.Text -- ^ the prefix to add to every line
...@@ -35,19 +35,21 @@ toStr :: Value -> Maybe T.Text ...@@ -35,19 +35,21 @@ toStr :: Value -> Maybe T.Text
toStr (String x) = Just (T.fromStrict x) toStr (String x) = Just (T.fromStrict x)
toStr _ = Nothing toStr _ = Nothing
-- | @convCell sty cell@ converts a single cell in JSON into text suitable -- | @convCell sty cell@ converts a single cell in JSON into text suitable for the type of lhs file
-- for the type of lhs file described by the @sty@ -- described by the @sty@
convCell :: LhsStyle T.Text -> Object -> T.Text convCell :: LhsStyle T.Text -> Object -> T.Text
convCell _sty object convCell _sty object
| Just (String "markdown") <- M.lookup "cell_type" object, | Just (String "markdown") <- M.lookup "cell_type" object,
Just (Array xs) <- M.lookup "source" object, Just (Array xs) <- M.lookup "source" object,
~ (Just s) <- concatWithPrefix "" xs = s ~(Just s) <- concatWithPrefix "" xs
= s
convCell sty object convCell sty object
| Just (String "code") <- M.lookup "cell_type" object, | Just (String "code") <- M.lookup "cell_type" object,
Just (Array i) <- M.lookup "source" object, Just (Array i) <- M.lookup "source" object,
Just (Array o) <- M.lookup "outputs" object, Just (Array o) <- M.lookup "outputs" object,
~ (Just i) <- concatWithPrefix (lhsCodePrefix sty) i, ~(Just i) <- concatWithPrefix (lhsCodePrefix sty) i,
o <- fromMaybe mempty (convOutputs sty o) = "\n" <> o <- fromMaybe mempty (convOutputs sty o)
= "\n" <>
lhsBeginCode sty <> i <> lhsEndCode sty <> "\n" <> o <> "\n" lhsBeginCode sty <> i <> lhsEndCode sty <> "\n" <> o <> "\n"
convCell _ _ = "IHaskell.Convert.convCell: unknown cell" convCell _ _ = "IHaskell.Convert.convCell: unknown cell"
......
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
module IHaskell.Convert.LhsToIpynb (lhsToIpynb) where module IHaskell.Convert.LhsToIpynb (lhsToIpynb) where
import Control.Applicative ((<$>)) import Control.Applicative ((<$>))
...@@ -19,7 +20,9 @@ lhsToIpynb sty from to = do ...@@ -19,7 +20,9 @@ lhsToIpynb sty from to = do
classed <- classifyLines sty . T.lines <$> T.readFile from classed <- classifyLines sty . T.lines <$> T.readFile from
L.writeFile to . encode . encodeCells $ groupClassified classed L.writeFile to . encode . encodeCells $ groupClassified classed
data CellLine a = CodeLine a | OutputLine a | MarkdownLine a data CellLine a = CodeLine a
| OutputLine a
| MarkdownLine a
deriving Show deriving Show
isCode :: CellLine t -> Bool isCode :: CellLine t -> Bool
...@@ -38,37 +41,39 @@ isEmptyMD :: (Eq a, Monoid a) => CellLine a -> Bool ...@@ -38,37 +41,39 @@ isEmptyMD :: (Eq a, Monoid a) => CellLine a -> Bool
isEmptyMD (MarkdownLine a) = a == mempty isEmptyMD (MarkdownLine a) = a == mempty
isEmptyMD _ = False isEmptyMD _ = False
untag :: CellLine t -> t untag :: CellLine t -> t
untag (CodeLine a) = a untag (CodeLine a) = a
untag (OutputLine a) = a untag (OutputLine a) = a
untag (MarkdownLine a) = a untag (MarkdownLine a) = a
data Cell a = Code a a | Markdown a data Cell a = Code a a
deriving (Show) | Markdown a
deriving Show
encodeCells :: [Cell [T.Text]] -> Value encodeCells :: [Cell [T.Text]] -> Value
encodeCells xs = object $ encodeCells xs = object $
[ "cells" .= Array (V.fromList (map cellToVal xs)) ] ["cells" .= Array (V.fromList (map cellToVal xs))]
++ boilerplate ++ boilerplate
cellToVal :: Cell [T.Text] -> Value cellToVal :: Cell [T.Text] -> Value
cellToVal (Code i o) = object $ cellToVal (Code i o) = object $
[ "cell_type" .= String "code", [ "cell_type" .= String "code"
"execution_count" .= Null, , "execution_count" .= Null
"metadata" .= object [ "collapsed" .= Bool False ], , "metadata" .= object ["collapsed" .= Bool False]
"source" .= arrayFromTxt i, , "source" .= arrayFromTxt i
"outputs" .= Array , "outputs" .= Array
(V.fromList ( (V.fromList
[ object ["text" .= arrayFromTxt o, ([object
"metadata" .= object [], [ "text" .= arrayFromTxt o
"output_type" .= String "display_data" ] , "metadata" .= object []
| _ <- take 1 o])) ] , "output_type" .= String "display_data"
] | _ <- take 1 o]))
]
cellToVal (Markdown txt) = object $ cellToVal (Markdown txt) = object $
[ "cell_type" .= String "markdown", [ "cell_type" .= String "markdown"
"metadata" .= object [ "hidden" .= Bool False ], , "metadata" .= object ["hidden" .= Bool False]
"source" .= arrayFromTxt txt ] , "source" .= arrayFromTxt txt
]
-- | arrayFromTxt makes a JSON array of string s -- | arrayFromTxt makes a JSON array of string s
arrayFromTxt :: [T.Text] -> Value arrayFromTxt :: [T.Text] -> Value
...@@ -76,44 +81,41 @@ arrayFromTxt i = Array (V.fromList $ map stringify i) ...@@ -76,44 +81,41 @@ arrayFromTxt i = Array (V.fromList $ map stringify i)
where where
stringify = String . T.toStrict . flip T.snoc '\n' stringify = String . T.toStrict . flip T.snoc '\n'
-- | ihaskell needs this boilerplate at the upper level to interpret the -- | ihaskell needs this boilerplate at the upper level to interpret the json describing cells and
-- json describing cells and output correctly. -- output correctly.
boilerplate :: [(TS.Text, Value)] boilerplate :: [(TS.Text, Value)]
boilerplate = boilerplate =
[ "metadata" .= object [ kernelspec, lang ] ["metadata" .= object [kernelspec, lang], "nbformat" .= Number 4, "nbformat_minor" .= Number 0]
, "nbformat" .= Number 4
, "nbformat_minor" .= Number 0
]
where where
kernelspec = "kernelspec" .= object [ kernelspec = "kernelspec" .= object
"display_name" .= String "Haskell" [ "display_name" .= String "Haskell"
, "language" .= String "haskell" , "language" .= String "haskell"
, "name" .= String "haskell" , "name" .= String "haskell"
] ]
lang = "language_info" .= object [ lang = "language_info" .= object ["name" .= String "haskell", "version" .= String VERSION_ghc]
"name" .= String "haskell"
, "version" .= String VERSION_ghc
]
groupClassified :: [CellLine T.Text] -> [Cell [T.Text]] groupClassified :: [CellLine T.Text] -> [Cell [T.Text]]
groupClassified (CodeLine a : x) groupClassified (CodeLine a:x)
| (c,x) <- span isCode x, | (c, x) <- span isCode x,
(_,x) <- span isEmptyMD x, (_, x) <- span isEmptyMD x,
(o,x) <- span isOutput x = Code (a : map untag c) (map untag o) : groupClassified x (o, x) <- span isOutput x
groupClassified (MarkdownLine a : x) = Code (a : map untag c) (map untag o) : groupClassified x
| (m,x) <- span isMD x = Markdown (a: map untag m) : groupClassified x groupClassified (MarkdownLine a:x)
groupClassified (OutputLine a : x ) = Markdown [a] : groupClassified x | (m, x) <- span isMD x = Markdown (a : map untag m) : groupClassified x
groupClassified (OutputLine a:x) = Markdown [a] : groupClassified x
groupClassified [] = [] groupClassified [] = []
classifyLines :: LhsStyle T.Text -> [T.Text] -> [CellLine T.Text] classifyLines :: LhsStyle T.Text -> [T.Text] -> [CellLine T.Text]
classifyLines sty@(LhsStyle c o _ _ _ _) (l:ls) = case (sp c, sp o) of classifyLines sty@(LhsStyle c o _ _ _ _) (l:ls) =
case (sp c, sp o) of
(Just a, Nothing) -> CodeLine a : classifyLines sty ls (Just a, Nothing) -> CodeLine a : classifyLines sty ls
(Nothing, Just a) -> OutputLine a : classifyLines sty ls (Nothing, Just a) -> OutputLine a : classifyLines sty ls
(Nothing,Nothing) -> MarkdownLine l : classifyLines sty ls (Nothing, Nothing) -> MarkdownLine l : classifyLines sty ls
_ -> error "IHaskell.Convert.classifyLines" _ -> error "IHaskell.Convert.classifyLines"
where where
sp x = T.stripPrefix (dropSpace x) (dropSpace l) `mplus` blankCodeLine x sp x = T.stripPrefix (dropSpace x) (dropSpace l) `mplus` blankCodeLine x
blankCodeLine x = if T.strip x == T.strip l then Just "" else Nothing blankCodeLine x = if T.strip x == T.strip l
then Just ""
else Nothing
dropSpace = T.dropWhile isSpace dropSpace = T.dropWhile isSpace
classifyLines _ [] = [] classifyLines _ [] = []
{-# LANGUAGE NoImplicitPrelude, OverloadedStrings, FlexibleInstances #-} {-# LANGUAGE NoImplicitPrelude, OverloadedStrings, FlexibleInstances #-}
-- | If you are interested in the IHaskell library for the purpose of -- | If you are interested in the IHaskell library for the purpose of augmenting the IHaskell
-- augmenting the IHaskell notebook or writing your own display mechanisms -- notebook or writing your own display mechanisms and widgets, this module contains all functions
-- and widgets, this module contains all functions you need. -- you need.
-- --
-- In order to create a display mechanism for a particular data type, write -- In order to create a display mechanism for a particular data type, write a module named (for
-- a module named (for example) @IHaskell.Display.YourThing@ in a package named @ihaskell-yourThing@. -- example) @IHaskell.Display.YourThing@ in a package named @ihaskell-yourThing@. (Note the
-- (Note the capitalization - it's important!) Then, in that module, add an -- capitalization - it's important!) Then, in that module, add an instance of @IHaskellDisplay@ for
-- instance of @IHaskellDisplay@ for your data type. Similarly, to create -- your data type. Similarly, to create a widget, add an instance of @IHaskellWidget@.
-- a widget, add an instance of @IHaskellWidget@.
-- --
-- An example of creating a display is provided in the <http://gibiansky.github.io/IHaskell/demo.html demo notebook>. -- An example of creating a display is provided in the
-- <http://gibiansky.github.io/IHaskell/demo.html demo notebook>.
-- --
module IHaskell.Display ( module IHaskell.Display (
-- * Rich display and interactive display typeclasses and types -- * Rich display and interactive display typeclasses and types
...@@ -23,11 +23,21 @@ module IHaskell.Display ( ...@@ -23,11 +23,21 @@ module IHaskell.Display (
printDisplay, printDisplay,
-- * Constructors for displays -- * Constructors for displays
plain, html, png, jpg, svg, latex, javascript, many, plain,
html,
png,
jpg,
svg,
latex,
javascript,
many,
-- ** Image and data encoding functions -- ** Image and data encoding functions
Width, Height, Base64(..), Width,
encode64, base64, Height,
Base64(..),
encode64,
base64,
-- ** Utilities -- ** Utilities
switchToTmpDir, switchToTmpDir,
...@@ -45,7 +55,7 @@ import Data.String.Utils (rstrip) ...@@ -45,7 +55,7 @@ import Data.String.Utils (rstrip)
import qualified Data.ByteString.Base64 as Base64 import qualified Data.ByteString.Base64 as Base64
import qualified Data.ByteString.Char8 as Char import qualified Data.ByteString.Char8 as Char
import Data.Aeson (Value) import Data.Aeson (Value)
import System.Directory(getTemporaryDirectory, setCurrentDirectory) import System.Directory (getTemporaryDirectory, setCurrentDirectory)
import Control.Concurrent.STM.TChan import Control.Concurrent.STM.TChan
import System.IO.Unsafe (unsafePerformIO) import System.IO.Unsafe (unsafePerformIO)
...@@ -61,8 +71,7 @@ type Base64 = Text ...@@ -61,8 +71,7 @@ type Base64 = Text
-- > IO [Display] -- > IO [Display]
-- > IO (IO Display) -- > IO (IO Display)
-- --
-- be run the IO and get rendered (if the frontend allows it) in the pretty -- be run the IO and get rendered (if the frontend allows it) in the pretty form.
-- form.
instance IHaskellDisplay a => IHaskellDisplay (IO a) where instance IHaskellDisplay a => IHaskellDisplay (IO a) where
display = (display =<<) display = (display =<<)
...@@ -101,15 +110,15 @@ latex = DisplayData MimeLatex . pack ...@@ -101,15 +110,15 @@ latex = DisplayData MimeLatex . pack
javascript :: String -> DisplayData javascript :: String -> DisplayData
javascript = DisplayData MimeJavascript . pack javascript = DisplayData MimeJavascript . pack
-- | Generate a PNG display of the given width and height. Data must be -- | Generate a PNG display of the given width and height. Data must be provided in a Base64 encoded
-- provided in a Base64 encoded manner, suitable for embedding into HTML. -- manner, suitable for embedding into HTML. The @base64@ function may be used to encode data into
-- The @base64@ function may be used to encode data into this format. -- this format.
png :: Width -> Height -> Base64 -> DisplayData png :: Width -> Height -> Base64 -> DisplayData
png width height = DisplayData (MimePng width height) png width height = DisplayData (MimePng width height)
-- | Generate a JPG display of the given width and height. Data must be -- | Generate a JPG display of the given width and height. Data must be provided in a Base64 encoded
-- provided in a Base64 encoded manner, suitable for embedding into HTML. -- manner, suitable for embedding into HTML. The @base64@ function may be used to encode data into
-- The @base64@ function may be used to encode data into this format. -- this format.
jpg :: Width -> Height -> Base64 -> DisplayData jpg :: Width -> Height -> Base64 -> DisplayData
jpg width height = DisplayData (MimeJpg width height) jpg width height = DisplayData (MimeJpg width height)
...@@ -121,42 +130,37 @@ encode64 str = base64 $ Char.pack str ...@@ -121,42 +130,37 @@ encode64 str = base64 $ Char.pack str
base64 :: ByteString -> Base64 base64 :: ByteString -> Base64
base64 = decodeUtf8 . Base64.encode base64 = decodeUtf8 . Base64.encode
-- | For internal use within IHaskell. -- | For internal use within IHaskell. Serialize displays to a ByteString.
-- Serialize displays to a ByteString.
serializeDisplay :: Display -> ByteString serializeDisplay :: Display -> ByteString
serializeDisplay = Serialize.encode serializeDisplay = Serialize.encode
-- | Items written to this chan will be included in the output sent -- | Items written to this chan will be included in the output sent to the frontend (ultimately the
-- to the frontend (ultimately the browser), the next time IHaskell -- browser), the next time IHaskell has an item to display.
-- has an item to display.
{-# NOINLINE displayChan #-} {-# NOINLINE displayChan #-}
displayChan :: TChan Display displayChan :: TChan Display
displayChan = unsafePerformIO newTChanIO displayChan = unsafePerformIO newTChanIO
-- | Take everything that was put into the 'displayChan' at that point -- | Take everything that was put into the 'displayChan' at that point out, and make a 'Display' out
-- out, and make a 'Display' out of it. -- of it.
displayFromChan :: IO (Maybe Display) displayFromChan :: IO (Maybe Display)
displayFromChan = displayFromChan =
Just . many <$> unfoldM (atomically $ tryReadTChan displayChan) Just . many <$> unfoldM (atomically $ tryReadTChan displayChan)
-- | This is unfoldM from monad-loops. It repeatedly runs an IO action -- | This is unfoldM from monad-loops. It repeatedly runs an IO action until it return Nothing, and
-- until it return Nothing, and puts all the Justs in a list. -- puts all the Justs in a list. If you find yourself using more functionality from monad-loops,
-- If you find yourself using more functionality from monad-loops, just add -- just add the package dependency instead of copying more code from it.
-- the package dependency instead of copying more code from it.
unfoldM :: IO (Maybe a) -> IO [a] unfoldM :: IO (Maybe a) -> IO [a]
unfoldM f = maybe (return []) (\r -> (r:) <$> unfoldM f) =<< f unfoldM f = maybe (return []) (\r -> (r :) <$> unfoldM f) =<< f
-- | Write to the display channel. The contents will be displayed in the -- | Write to the display channel. The contents will be displayed in the notebook once the current
-- notebook once the current execution call ends. -- execution call ends.
printDisplay :: IHaskellDisplay a => a -> IO () printDisplay :: IHaskellDisplay a => a -> IO ()
printDisplay disp = display disp >>= atomically . writeTChan displayChan printDisplay disp = display disp >>= atomically . writeTChan displayChan
-- | Convenience function for client libraries. Switch to a temporary -- | Convenience function for client libraries. Switch to a temporary directory so that any files we
-- directory so that any files we create aren't visible. On Unix, this is -- create aren't visible. On Unix, this is usually /tmp.
-- usually /tmp.
switchToTmpDir = void (try switchDir :: IO (Either SomeException ())) switchToTmpDir = void (try switchDir :: IO (Either SomeException ()))
where where
switchDir = switchDir =
getTemporaryDirectory >>= getTemporaryDirectory >>=
setCurrentDirectory setCurrentDirectory
{-# LANGUAGE CPP, NoImplicitPrelude, OverloadedStrings, DoAndIfThenElse #-} {-# LANGUAGE CPP, NoImplicitPrelude, OverloadedStrings, DoAndIfThenElse #-}
{-# LANGUAGE TypeFamilies, FlexibleContexts #-} {-# LANGUAGE TypeFamilies, FlexibleContexts #-}
{- | {- |
Description: Generates tab completion options. Description: Generates tab completion options.
...@@ -13,7 +14,6 @@ This has a limited amount of context sensitivity. It distinguishes between four ...@@ -13,7 +14,6 @@ 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 (init, last, head, liftIO) import ClassyPrelude hiding (init, last, head, liftIO)
--import Prelude
import Control.Applicative ((<$>)) import Control.Applicative ((<$>))
import Data.ByteString.UTF8 hiding (drop, take, lines, length) import Data.ByteString.UTF8 hiding (drop, take, lines, length)
...@@ -46,9 +46,7 @@ import IHaskell.Types ...@@ -46,9 +46,7 @@ import IHaskell.Types
import IHaskell.Eval.Evaluate (Interpreter) import IHaskell.Eval.Evaluate (Interpreter)
import IHaskell.Eval.ParseShell (parseShell) import IHaskell.Eval.ParseShell (parseShell)
data CompletionType = Empty
data CompletionType
= Empty
| Identifier String | Identifier String
| DynFlag String | DynFlag String
| Qualified String String | Qualified String String
...@@ -58,10 +56,15 @@ data CompletionType ...@@ -58,10 +56,15 @@ data CompletionType
| KernelOption String | KernelOption String
| Extension String | Extension String
deriving (Show, Eq) deriving (Show, Eq)
#if MIN_VERSION_ghc(7,10,0)
extName (FlagSpec { flagSpecName = name }) = name
#else
extName (name, _, _) = name
exposedName = id
#endif
complete :: String -> Int -> Interpreter (String, [String]) complete :: String -> Int -> Interpreter (String, [String])
complete code posOffset = do complete code posOffset = do
-- Get the line of code which is being completed and offset within that line -- Get the line of code which is being completed and offset within that line
let findLine offset (first:rest) = let findLine offset (first:rest) =
if offset <= length first if offset <= length first
...@@ -78,10 +81,6 @@ complete code posOffset = do ...@@ -78,10 +81,6 @@ complete code posOffset = do
unqualNames = nub $ filter (not . isQualified) rdrNames unqualNames = nub $ filter (not . isQualified) rdrNames
qualNames = nub $ scopeNames ++ filter isQualified rdrNames qualNames = nub $ scopeNames ++ filter isQualified rdrNames
#if !MIN_VERSION_ghc(7,10,0)
let exposedName = id
#endif
let Just db = pkgDatabase flags let Just db = pkgDatabase flags
getNames = map (moduleNameString . exposedName) . exposedModules getNames = map (moduleNameString . exposedName) . exposedModules
moduleNames = nub $ concatMap getNames db moduleNames = nub $ concatMap getNames db
...@@ -89,19 +88,13 @@ complete code posOffset = do ...@@ -89,19 +88,13 @@ complete code posOffset = do
let target = completionTarget line pos let target = completionTarget line pos
completion = completionType line pos target completion = completionType line pos target
let matchedText = case completion of let matchedText =
case completion of
HsFilePath _ match -> match HsFilePath _ match -> match
FilePath _ match -> match FilePath _ match -> match
otherwise -> intercalate "." target otherwise -> intercalate "." target
#if MIN_VERSION_ghc(7,10,0) options <- case completion of
let extName (FlagSpec {flagSpecName=name}) = name
#else
let extName (name, _, _) = name
#endif
options <-
case completion of
Empty -> return [] Empty -> return []
Identifier candidate -> Identifier candidate ->
...@@ -121,21 +114,20 @@ complete code posOffset = do ...@@ -121,21 +114,20 @@ complete code posOffset = do
return $ filter (prefix `isPrefixOf`) moduleNames return $ filter (prefix `isPrefixOf`) moduleNames
DynFlag ext -> do DynFlag ext -> do
-- Possibly leave out the fLangFlags? The -- Possibly leave out the fLangFlags? The -XUndecidableInstances vs. obsolete
-- -XUndecidableInstances vs. obsolete
-- -fallow-undecidable-instances. -- -fallow-undecidable-instances.
let kernelOptNames = concatMap getSetName kernelOpts let kernelOptNames = concatMap getSetName kernelOpts
otherNames = ["-package","-Wall","-w"] otherNames = ["-package", "-Wall", "-w"]
fNames = map extName fFlags ++ fNames = map extName fFlags ++
map extName fWarningFlags ++ map extName fWarningFlags ++
map extName fLangFlags map extName fLangFlags
fNoNames = map ("no"++) fNames fNoNames = map ("no" ++) fNames
fAllNames = map ("-f"++) (fNames ++ fNoNames) fAllNames = map ("-f" ++) (fNames ++ fNoNames)
xNames = map extName xFlags xNames = map extName xFlags
xNoNames = map ("No" ++) xNames xNoNames = map ("No" ++) xNames
xAllNames = map ("-X"++) (xNames ++ xNoNames) xAllNames = map ("-X" ++) (xNames ++ xNoNames)
allNames = xAllNames ++ otherNames ++ fAllNames allNames = xAllNames ++ otherNames ++ fAllNames
...@@ -146,7 +138,8 @@ complete code posOffset = do ...@@ -146,7 +138,8 @@ complete code posOffset = do
xNoNames = map ("No" ++) xNames xNoNames = map ("No" ++) xNames
return $ filter (ext `isPrefixOf`) $ xNames ++ xNoNames return $ filter (ext `isPrefixOf`) $ xNames ++ xNoNames
HsFilePath lineUpToCursor match -> completePathWithExtensions [".hs", ".lhs"] lineUpToCursor HsFilePath lineUpToCursor match -> completePathWithExtensions [".hs", ".lhs"]
lineUpToCursor
FilePath lineUpToCursor match -> completePath lineUpToCursor FilePath lineUpToCursor match -> completePath lineUpToCursor
...@@ -164,8 +157,8 @@ getTrueModuleName name = do ...@@ -164,8 +157,8 @@ getTrueModuleName name = do
-- Get all imports that we use. -- Get all imports that we use.
imports <- ClassyPrelude.catMaybes <$> map onlyImportDecl <$> getContext imports <- ClassyPrelude.catMaybes <$> map onlyImportDecl <$> getContext
-- Find the ones that have a qualified name attached. -- Find the ones that have a qualified name attached. If this name isn't one of them, it already is
-- If this name isn't one of them, it already is the true name. -- the true name.
flags <- getSessionDynFlags flags <- getSessionDynFlags
let qualifiedImports = filter (isJust . ideclAs) imports let qualifiedImports = filter (isJust . ideclAs) imports
hasName imp = name == (showPpr flags . fromJust . ideclAs) imp hasName imp = name == (showPpr flags . fromJust . ideclAs) imp
...@@ -180,40 +173,41 @@ completionType :: String -- ^ The line on which the completion is bei ...@@ -180,40 +173,41 @@ completionType :: String -- ^ The line on which the completion is bei
-> CompletionType -> CompletionType
completionType line loc target completionType line loc target
-- File and directory completions are special -- File and directory completions are special
| startswith ":!" stripped | startswith ":!" stripped =
= fileComplete FilePath fileComplete FilePath
| startswith ":l" stripped | startswith ":l" stripped =
= fileComplete HsFilePath fileComplete HsFilePath
-- Complete :set, :opt, and :ext -- Complete :set, :opt, and :ext
| startswith ":s" stripped | startswith ":s" stripped =
= DynFlag candidate DynFlag candidate
| startswith ":o" stripped | startswith ":o" stripped =
= KernelOption candidate KernelOption candidate
| startswith ":e" stripped | startswith ":e" stripped =
= Extension candidate Extension candidate
-- Use target for other completions. -- Use target for other completions. If it's empty, no completion.
-- If it's empty, no completion. | null target =
| null target Empty
= Empty
-- When in a string, complete filenames. -- When in a string, complete filenames.
| cursorInString line loc | cursorInString line loc =
= FilePath (getStringTarget lineUpToCursor) (getStringTarget lineUpToCursor) FilePath (getStringTarget lineUpToCursor) (getStringTarget lineUpToCursor)
-- Complete module names in imports and elsewhere. -- Complete module names in imports and elsewhere.
| startswith "import" stripped && isModName | startswith "import" stripped && isModName =
= ModuleName dotted candidate ModuleName dotted candidate
| isModName && (not . null . init) target | isModName && (not . null . init) target =
= Qualified dotted candidate Qualified dotted candidate
-- Default to completing identifiers. -- Default to completing identifiers.
| otherwise | otherwise =
= Identifier candidate Identifier candidate
where stripped = strip line where
stripped = strip line
dotted = dots target dotted = dots target
candidate | null target = "" candidate
| null target = ""
| otherwise = last target | otherwise = last target
dots = intercalate "." . init dots = intercalate "." . init
isModName = all isCapitalized (init target) isModName = all isCapitalized (init target)
...@@ -222,7 +216,8 @@ completionType line loc target ...@@ -222,7 +216,8 @@ completionType line loc target
isCapitalized (x:_) = isUpper x isCapitalized (x:_) = isUpper x
lineUpToCursor = take loc line lineUpToCursor = take loc line
fileComplete filePath = case parseShell lineUpToCursor of fileComplete filePath =
case parseShell lineUpToCursor of
Right xs -> filePath lineUpToCursor $ Right xs -> filePath lineUpToCursor $
if endswith (last xs) lineUpToCursor if endswith (last xs) lineUpToCursor
then last xs then last xs
...@@ -236,18 +231,18 @@ completionType line loc target ...@@ -236,18 +231,18 @@ completionType line loc target
nquotes (_:xs) = nquotes xs nquotes (_:xs) = nquotes xs
nquotes [] = 0 nquotes [] = 0
-- Get the bit of a string that might be a filename completion. -- Get the bit of a string that might be a filename completion. Logic is a bit convoluted, but
-- Logic is a bit convoluted, but basically go backwards from the -- basically go backwards from the end, stopping at any quote or space, unless they are escaped.
-- end, stopping at any quote or space, unless they are escaped.
getStringTarget :: String -> String getStringTarget :: String -> String
getStringTarget = go "" . reverse getStringTarget = go "" . reverse
where where
go acc rest = case rest of go acc rest =
'"':'\\':rem -> go ('"':acc) rem case rest of
'"':'\\':rem -> go ('"' : acc) rem
'"':rem -> acc '"':rem -> acc
' ':'\\':rem -> go (' ':acc) rem ' ':'\\':rem -> go (' ' : acc) rem
' ':rem -> acc ' ':rem -> acc
x:rem -> go (x:acc) rem x:rem -> go (x : acc) rem
[] -> acc [] -> acc
-- | Get the word under a given cursor location. -- | Get the word under a given cursor location.
...@@ -255,14 +250,14 @@ completionTarget :: String -> Int -> [String] ...@@ -255,14 +250,14 @@ completionTarget :: String -> Int -> [String]
completionTarget code cursor = expandCompletionPiece pieceToComplete completionTarget code cursor = expandCompletionPiece pieceToComplete
where where
pieceToComplete = map fst <$> find (elem cursor . map snd) pieces pieceToComplete = map fst <$> find (elem cursor . map snd) pieces
pieces = splitAlongCursor $ split splitter $ zip code [1 .. ] pieces = splitAlongCursor $ split splitter $ zip code [1 ..]
splitter = defaultSplitter { splitter = defaultSplitter
-- Split using only the characters, which are the first elements of {
-- the (char, index) tuple -- Split using only the characters, which are the first elements of the (char, index) tuple
delimiter = Delimiter [uncurry isDelim], delimiter = Delimiter [uncurry isDelim]
-- Condense multiple delimiters into one and then drop them. -- Condense multiple delimiters into one and then drop them.
condensePolicy = Condense, , condensePolicy = Condense
delimPolicy = Drop , delimPolicy = Drop
} }
isDelim :: Char -> Int -> Bool isDelim :: Char -> Int -> Bool
...@@ -272,8 +267,8 @@ completionTarget code cursor = expandCompletionPiece pieceToComplete ...@@ -272,8 +267,8 @@ completionTarget code cursor = expandCompletionPiece pieceToComplete
splitAlongCursor [] = [] splitAlongCursor [] = []
splitAlongCursor (x:xs) = splitAlongCursor (x:xs) =
case elemIndex cursor $ map snd x of case elemIndex cursor $ map snd x of
Nothing -> x:splitAlongCursor xs Nothing -> x : splitAlongCursor xs
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 :: String
...@@ -285,7 +280,8 @@ completionTarget code cursor = expandCompletionPiece pieceToComplete ...@@ -285,7 +280,8 @@ completionTarget code cursor = expandCompletionPiece pieceToComplete
getHome :: IO String getHome :: IO String
getHome = do getHome = do
homeEither <- try $ getEnv "HOME" :: IO (Either SomeException String) homeEither <- try $ getEnv "HOME" :: IO (Either SomeException String)
return $ case homeEither of return $
case homeEither of
Left _ -> "~" Left _ -> "~"
Right home -> home Right home -> home
...@@ -301,7 +297,8 @@ unDirExpand str = do ...@@ -301,7 +297,8 @@ unDirExpand str = do
completePath :: String -> Interpreter [String] completePath :: String -> Interpreter [String]
completePath line = completePathFilter acceptAll acceptAll line "" completePath line = completePathFilter acceptAll acceptAll line ""
where acceptAll = const True where
acceptAll = const True
completePathWithExtensions :: [String] -> String -> Interpreter [String] completePathWithExtensions :: [String] -> String -> Interpreter [String]
completePathWithExtensions extensions line = completePathWithExtensions extensions line =
...@@ -309,7 +306,8 @@ completePathWithExtensions extensions line = ...@@ -309,7 +306,8 @@ completePathWithExtensions extensions line =
where where
acceptAll = const True acceptAll = const True
extensionIsOneOf exts str = any correctEnding exts extensionIsOneOf exts str = any correctEnding exts
where correctEnding ext = endswith ext str where
correctEnding ext = endswith ext str
completePathFilter :: (String -> Bool) -- ^ File filter: test whether to include this file. completePathFilter :: (String -> Bool) -- ^ File filter: test whether to include this file.
-> (String -> Bool) -- ^ Directory filter: test whether to include this directory. -> (String -> Bool) -- ^ Directory filter: test whether to include this directory.
...@@ -321,15 +319,13 @@ completePathFilter includeFile includeDirectory left right = liftIO $ do ...@@ -321,15 +319,13 @@ completePathFilter includeFile includeDirectory left right = liftIO $ do
expanded <- dirExpand left expanded <- dirExpand left
completions <- map replacement <$> snd <$> completeFilename (reverse expanded, right) completions <- map replacement <$> snd <$> completeFilename (reverse expanded, right)
-- Split up into files and directories. -- Split up into files and directories. Filter out ones we don't want.
-- Filter out ones we don't want.
areDirs <- mapM doesDirectoryExist completions areDirs <- mapM doesDirectoryExist completions
let dirs = filter includeDirectory $ map fst $ filter snd $ zip completions areDirs let dirs = filter includeDirectory $ map fst $ filter snd $ zip completions areDirs
files = filter includeFile $ map fst $ filter (not . snd) $ zip completions areDirs files = filter includeFile $ map fst $ filter (not . snd) $ zip completions areDirs
-- Return directories before files. However, stick everything that starts -- Return directories before files. However, stick everything that starts with a dot after
-- with a dot after everything else. If we wanted to keep original -- everything else. If we wanted to keep original order, we could instead use
-- order, we could instead use
-- filter (`elem` (dirs ++ files)) completions -- filter (`elem` (dirs ++ files)) completions
suggestions <- mapM unDirExpand $ dirs ++ files suggestions <- mapM unDirExpand $ dirs ++ files
let isHidden str = startswith "." . last . StringUtils.split "/" $ let isHidden str = startswith "." . last . StringUtils.split "/" $
......
...@@ -6,7 +6,12 @@ ...@@ -6,7 +6,12 @@
This module exports all functions used for evaluation of IHaskell input. This module exports all functions used for evaluation of IHaskell input.
-} -}
module IHaskell.Eval.Evaluate ( module IHaskell.Eval.Evaluate (
interpret, evaluate, Interpreter, liftIO, typeCleaner, globalImports interpret,
evaluate,
Interpreter,
liftIO,
typeCleaner,
globalImports,
) where ) where
import ClassyPrelude hiding (init, last, liftIO, head, hGetContents, tail, try) import ClassyPrelude hiding (init, last, liftIO, head, hGetContents, tail, try)
...@@ -77,16 +82,25 @@ import qualified IHaskell.IPython.Message.UUID as UUID ...@@ -77,16 +82,25 @@ import qualified IHaskell.IPython.Message.UUID as UUID
import Paths_ihaskell (version) import Paths_ihaskell (version)
import Data.Version (versionBranch) import Data.Version (versionBranch)
data ErrorOccurred = Success | Failure deriving (Show, Eq) data ErrorOccurred = Success
| Failure
deriving (Show, Eq)
-- | Set GHC's verbosity for debugging -- | Set GHC's verbosity for debugging
ghcVerbosity :: Maybe Int ghcVerbosity :: Maybe Int
ghcVerbosity = Nothing -- Just 5 ghcVerbosity = Nothing -- Just 5
ignoreTypePrefixes :: [String] ignoreTypePrefixes :: [String]
ignoreTypePrefixes = ["GHC.Types", "GHC.Base", "GHC.Show", "System.IO", ignoreTypePrefixes = [ "GHC.Types"
"GHC.Float", ":Interactive", "GHC.Num", "GHC.IO", , "GHC.Base"
"GHC.Integer.Type"] , "GHC.Show"
, "System.IO"
, "GHC.Float"
, ":Interactive"
, "GHC.Num"
, "GHC.IO"
, "GHC.Integer.Type"
]
typeCleaner :: String -> String typeCleaner :: String -> String
typeCleaner = useStringType . foldl' (.) id (map (`replace` "") fullPrefixes) typeCleaner = useStringType . foldl' (.) id (map (`replace` "") fullPrefixes)
...@@ -98,14 +112,12 @@ write :: GhcMonad m => KernelState -> String -> m () ...@@ -98,14 +112,12 @@ write :: GhcMonad m => KernelState -> String -> m ()
write state x = when (kernelDebug state) $ liftIO $ hPutStrLn stderr $ "DEBUG: " ++ x write state x = when (kernelDebug state) $ liftIO $ hPutStrLn stderr $ "DEBUG: " ++ x
type Interpreter = Ghc type Interpreter = Ghc
#if MIN_VERSION_ghc(7, 8, 0) #if MIN_VERSION_ghc(7, 8, 0)
-- GHC 7.8 exports a MonadIO instance for Ghc -- GHC 7.8 exports a MonadIO instance for Ghc
#else #else
instance MonadIO.MonadIO Interpreter where instance MonadIO.MonadIO Interpreter where
liftIO = MonadUtils.liftIO liftIO = MonadUtils.liftIO
#endif #endif
globalImports :: [String] globalImports :: [String]
globalImports = globalImports =
[ "import IHaskell.Display()" [ "import IHaskell.Display()"
...@@ -118,23 +130,23 @@ globalImports = ...@@ -118,23 +130,23 @@ globalImports =
, "import qualified Language.Haskell.TH as IHaskellTH" , "import qualified Language.Haskell.TH as IHaskellTH"
] ]
-- | Run an interpreting action. This is effectively runGhc with -- | Run an interpreting action. This is effectively runGhc with initialization and importing. First
-- initialization and importing. First argument indicates whether `stdin` -- argument indicates whether `stdin` is handled specially, which cannot be done in a testing
-- is handled specially, which cannot be done in a testing environment. -- environment.
interpret :: String -> Bool -> Interpreter a -> IO a interpret :: String -> Bool -> Interpreter a -> IO a
interpret libdir allowedStdin action = runGhc (Just libdir) $ do interpret libdir allowedStdin action = runGhc (Just libdir) $ do
-- If we're in a sandbox, add the relevant package database -- If we're in a sandbox, add the relevant package database
sandboxPackages <- liftIO getSandboxPackageConf sandboxPackages <- liftIO getSandboxPackageConf
initGhci sandboxPackages initGhci sandboxPackages
case ghcVerbosity of case ghcVerbosity of
Just verb -> do dflags <- getSessionDynFlags Just verb -> do
dflags <- getSessionDynFlags
void $ setSessionDynFlags $ dflags { verbosity = verb } void $ setSessionDynFlags $ dflags { verbosity = verb }
Nothing -> return () Nothing -> return ()
initializeImports initializeImports
-- Close stdin so it can't be used. -- Close stdin so it can't be used. Otherwise it'll block the kernel forever.
-- Otherwise it'll block the kernel forever.
dir <- liftIO getIHaskellDir dir <- liftIO getIHaskellDir
let cmd = printf "IHaskell.IPython.Stdin.fixStdin \"%s\"" dir let cmd = printf "IHaskell.IPython.Stdin.fixStdin \"%s\"" dir
when allowedStdin $ void $ when allowedStdin $ void $
...@@ -144,48 +156,51 @@ interpret libdir allowedStdin action = runGhc (Just libdir) $ do ...@@ -144,48 +156,51 @@ interpret libdir allowedStdin action = runGhc (Just libdir) $ do
-- Run the rest of the interpreter -- Run the rest of the interpreter
action action
#if MIN_VERSION_ghc(7,10,0)
packageIdString' dflags = packageKeyPackageIdString dflags
#else
packageIdString' dflags = packageIdString
#endif
-- | Initialize our GHC session with imports and a value for 'it'. -- | Initialize our GHC session with imports and a value for 'it'.
initializeImports :: Interpreter () initializeImports :: Interpreter ()
initializeImports = do initializeImports = do
-- Load packages that start with ihaskell-*, aren't just IHaskell, -- Load packages that start with ihaskell-*, aren't just IHaskell, and depend directly on the right
-- and depend directly on the right version of the ihaskell library. -- version of the ihaskell library. Also verify that the packages we load are not broken.
-- Also verify that the packages we load are not broken.
dflags <- getSessionDynFlags dflags <- getSessionDynFlags
broken <- liftIO getBrokenPackages broken <- liftIO getBrokenPackages
displayPackages <- liftIO $ do displayPackages <- liftIO $ do
(dflags, _) <- initPackages dflags (dflags, _) <- initPackages dflags
let Just db = pkgDatabase dflags let Just db = pkgDatabase dflags
#if MIN_VERSION_ghc(7,10,0) packageNames = map (packageIdString' dflags . packageConfigId) db
packageIdString = packageKeyPackageIdString dflags
#endif
packageNames = map (packageIdString . packageConfigId) db
initStr = "ihaskell-" initStr = "ihaskell-"
-- Name of the ihaskell package, e.g. "ihaskell-1.2.3.4" -- Name of the ihaskell package, e.g. "ihaskell-1.2.3.4"
iHaskellPkgName = initStr ++ intercalate "." (map show (versionBranch version)) iHaskellPkgName = initStr ++ intercalate "."
(map show (versionBranch version))
dependsOnRight pkg = not $ null $ do dependsOnRight pkg = not $ null $ do
pkg <- db pkg <- db
depId <- depends pkg depId <- depends pkg
dep <- filter ((== depId) . installedPackageId) db dep <- filter ((== depId) . installedPackageId) db
guard (iHaskellPkgName `isPrefixOf` packageIdString (packageConfigId dep)) guard
(iHaskellPkgName `isPrefixOf` packageIdString (packageConfigId dep))
-- ideally the Paths_ihaskell module could provide a way to get the
-- hash too (ihaskell-0.2.0.5-f2bce922fa881611f72dfc4a854353b9), -- ideally the Paths_ihaskell module could provide a way to get the hash too
-- for now. Things will end badly if you also happen to have an -- (ihaskell-0.2.0.5-f2bce922fa881611f72dfc4a854353b9), for now. Things will end badly if you also
-- ihaskell-0.2.0.5-ce34eadc18cf2b28c8d338d0f3755502 installed. -- happen to have an ihaskell-0.2.0.5-ce34eadc18cf2b28c8d338d0f3755502 installed.
iHaskellPkg = case filter (== iHaskellPkgName) packageNames of iHaskellPkg =
case filter (== iHaskellPkgName) packageNames of
[x] -> x [x] -> x
[] -> error ("cannot find required haskell library: " ++ iHaskellPkgName) [] -> error
_ -> error ("multiple haskell packages " ++ iHaskellPkgName ++ " found") ("cannot find required haskell library: " ++ iHaskellPkgName)
_ -> error
("multiple haskell packages " ++ iHaskellPkgName ++ " found")
displayPkgs = [ pkgName displayPkgs = [pkgName | pkgName <- packageNames
| pkgName <- packageNames, , Just (x:_) <- [stripPrefix initStr pkgName]
Just (x:_) <- [stripPrefix initStr pkgName], , pkgName `notElem` broken
pkgName `notElem` broken, , isAlpha x]
isAlpha x]
return displayPkgs return displayPkgs
...@@ -214,25 +229,28 @@ initializeImports = do ...@@ -214,25 +229,28 @@ initializeImports = do
-- | Give a value for the `it` variable. -- | Give a value for the `it` variable.
initializeItVariable :: Interpreter () initializeItVariable :: Interpreter ()
initializeItVariable = do initializeItVariable = do
-- This is required due to the way we handle `it` in the wrapper -- This is required due to the way we handle `it` in the wrapper statements - if it doesn't exist,
-- statements - if it doesn't exist, the first statement will fail. -- the first statement will fail.
void $ runStmt "let it = ()" RunToCompletion void $ runStmt "let it = ()" RunToCompletion
-- | Publisher for IHaskell outputs. The first argument indicates whether -- | Publisher for IHaskell outputs. The first argument indicates whether this output is final
-- this output is final (true) or intermediate (false). -- (true) or intermediate (false).
type Publisher = (EvaluationResult -> IO ()) type Publisher = (EvaluationResult -> IO ())
-- | Output of a command evaluation. -- | Output of a command evaluation.
data EvalOut = EvalOut { data EvalOut =
evalStatus :: ErrorOccurred, EvalOut
evalResult :: Display, { evalStatus :: ErrorOccurred
evalState :: KernelState, , evalResult :: Display
evalPager :: String, , evalState :: KernelState
evalComms :: [CommInfo] , evalPager :: String
, evalComms :: [CommInfo]
} }
cleanString :: String -> String cleanString :: String -> String
cleanString x = if allBrackets then clean else str cleanString x = if allBrackets
then clean
else str
where where
str = strip x str = strip x
l = lines str l = lines str
...@@ -274,9 +292,8 @@ evaluate kernelState code output = do ...@@ -274,9 +292,8 @@ evaluate kernelState code output = do
liftIO $ output $ FinalResult (evalResult out) "" [] liftIO $ output $ FinalResult (evalResult out) "" []
return kernelState return kernelState
return updated { return updated { getExecutionCounter = execCount + 1 }
getExecutionCounter = execCount + 1
}
where where
noResults (Display res) = null res noResults (Display res) = null res
noResults (ManyDisplay res) = all noResults res noResults (ManyDisplay res) = all noResults res
...@@ -286,8 +303,7 @@ evaluate kernelState code output = do ...@@ -286,8 +303,7 @@ evaluate kernelState code output = do
runUntilFailure state (cmd:rest) = do runUntilFailure state (cmd:rest) = do
evalOut <- evalCommand output cmd state evalOut <- evalCommand output cmd state
-- Get displayed channel outputs. -- Get displayed channel outputs. Merge them with normal display outputs.
-- Merge them with normal display outputs.
dispsIO <- extractValue "IHaskell.Display.displayFromChan" dispsIO <- extractValue "IHaskell.Display.displayFromChan"
dispsMay <- liftIO dispsIO dispsMay <- liftIO dispsIO
let result = let result =
...@@ -322,12 +338,13 @@ safely state = ghandle handler . ghandle sourceErrorHandler ...@@ -322,12 +338,13 @@ safely state = ghandle handler . ghandle sourceErrorHandler
where where
handler :: SomeException -> Interpreter EvalOut handler :: SomeException -> Interpreter EvalOut
handler exception = handler exception =
return EvalOut { return
evalStatus = Failure, EvalOut
evalResult = displayError $ show exception, { evalStatus = Failure
evalState = state, , evalResult = displayError $ show exception
evalPager = "", , evalState = state
evalComms = [] , evalPager = ""
, evalComms = []
} }
sourceErrorHandler :: SourceError -> Interpreter EvalOut sourceErrorHandler :: SourceError -> Interpreter EvalOut
...@@ -340,28 +357,30 @@ safely state = ghandle handler . ghandle sourceErrorHandler ...@@ -340,28 +357,30 @@ safely state = ghandle handler . ghandle sourceErrorHandler
let fullErr = unlines errStrs let fullErr = unlines errStrs
return EvalOut { return
evalStatus = Failure, EvalOut
evalResult = displayError fullErr, { evalStatus = Failure
evalState = state, , evalResult = displayError fullErr
evalPager = "", , evalState = state
evalComms = [] , evalPager = ""
, evalComms = []
} }
wrapExecution :: KernelState wrapExecution :: KernelState
-> Interpreter Display -> Interpreter Display
-> Interpreter EvalOut -> Interpreter EvalOut
wrapExecution state exec = safely state $ exec >>= \res -> wrapExecution state exec = safely state $
return EvalOut { exec >>= \res ->
evalStatus = Success, return
evalResult = res, EvalOut
evalState = state, { evalStatus = Success
evalPager = "", , evalResult = res
evalComms = [] , evalState = state
, evalPager = ""
, evalComms = []
} }
-- | Return the display data for this command, as well as whether it -- | Return the display data for this command, as well as whether it resulted in an error.
-- resulted in an error.
evalCommand :: Publisher -> CodeBlock -> KernelState -> Interpreter EvalOut evalCommand :: Publisher -> CodeBlock -> KernelState -> Interpreter EvalOut
evalCommand _ (Import importStr) state = wrapExecution state $ do evalCommand _ (Import importStr) state = wrapExecution state $ do
write state $ "Import: " ++ importStr write state $ "Import: " ++ importStr
...@@ -393,21 +412,21 @@ evalCommand _ (Module contents) state = wrapExecution state $ do ...@@ -393,21 +412,21 @@ evalCommand _ (Module contents) state = wrapExecution state $ do
-- Remember which modules we've loaded before. -- Remember which modules we've loaded before.
importedModules <- getContext importedModules <- getContext
let -- Get the dot-delimited pieces of the module name. let
-- Get the dot-delimited pieces of the module name.
moduleNameOf :: InteractiveImport -> [String] moduleNameOf :: InteractiveImport -> [String]
moduleNameOf (IIDecl decl) = split "." . moduleNameString . unLoc . ideclName $ decl moduleNameOf (IIDecl decl) = split "." . moduleNameString . unLoc . ideclName $ decl
moduleNameOf (IIModule imp) = split "." . moduleNameString $ imp moduleNameOf (IIModule imp) = split "." . moduleNameString $ imp
-- Return whether this module prevents the loading of the one we're -- Return whether this module prevents the loading of the one we're trying to load. If a module B
-- trying to load. If a module B exist, we cannot load A.B. All -- exist, we cannot load A.B. All modules must have unique last names (where A.B has last name B).
-- modules must have unique last names (where A.B has last name B).
-- However, we *can* just reload a module. -- However, we *can* just reload a module.
preventsLoading mod = preventsLoading mod =
let pieces = moduleNameOf mod in let pieces = moduleNameOf mod
last namePieces == last pieces && namePieces /= pieces in last namePieces == last pieces && namePieces /= pieces
-- If we've loaded anything with the same last name, we can't use this. -- If we've loaded anything with the same last name, we can't use this. Otherwise, GHC tries to load
-- Otherwise, GHC tries to load the original *.hs fails and then fails. -- the original *.hs fails and then fails.
case find preventsLoading importedModules of case find preventsLoading importedModules of
-- If something prevents loading this module, return an error. -- If something prevents loading this module, return an error.
Just previous -> do Just previous -> do
...@@ -425,7 +444,8 @@ evalCommand output (Directive SetDynFlag flagsStr) state = safely state $ do ...@@ -425,7 +444,8 @@ evalCommand output (Directive SetDynFlag flagsStr) state = safely state $ do
-- Find which flags are IHaskell flags, and which are GHC flags -- Find which flags are IHaskell flags, and which are GHC flags
let flags = words flagsStr let flags = words flagsStr
-- Get the kernel state updater for any IHaskell flag; Nothing for things that aren't IHaskell flags. -- Get the kernel state updater for any IHaskell flag; Nothing for things that aren't IHaskell
-- flags.
ihaskellFlagUpdater :: String -> Maybe (KernelState -> KernelState) ihaskellFlagUpdater :: String -> Maybe (KernelState -> KernelState)
ihaskellFlagUpdater flag = getUpdateKernelState <$> find (elem flag . getSetName) kernelOpts ihaskellFlagUpdater flag = getUpdateKernelState <$> find (elem flag . getSetName) kernelOpts
...@@ -437,38 +457,44 @@ evalCommand output (Directive SetDynFlag flagsStr) state = safely state $ do ...@@ -437,38 +457,44 @@ evalCommand output (Directive SetDynFlag flagsStr) state = safely state $ do
if null flags if null flags
then do then do
flags <- getSessionDynFlags flags <- getSessionDynFlags
return EvalOut { return
evalStatus = Success, EvalOut
evalResult = Display [plain $ showSDoc flags $ vcat [pprDynFlags False flags, pprLanguages False flags]], { evalStatus = Success
evalState = state, , evalResult = Display
evalPager = "", [ plain $ showSDoc flags $ vcat
evalComms = [] [ pprDynFlags False flags
, pprLanguages False flags
]
]
, evalState = state
, evalPager = ""
, evalComms = []
} }
else do else do
-- Apply all IHaskell flag updaters to the state to get the new state -- Apply all IHaskell flag updaters to the state to get the new state
let state' = (foldl' (.) id (map (fromJust . ihaskellFlagUpdater) ihaskellFlags)) state let state' = (foldl' (.) id (map (fromJust . ihaskellFlagUpdater) ihaskellFlags)) state
errs <- setFlags ghcFlags errs <- setFlags ghcFlags
let display = case errs of let display =
case errs of
[] -> mempty [] -> mempty
_ -> displayError $ intercalate "\n" errs _ -> displayError $ intercalate "\n" errs
-- For -XNoImplicitPrelude, remove the Prelude import. -- For -XNoImplicitPrelude, remove the Prelude import. For -XImplicitPrelude, add it back in.
-- For -XImplicitPrelude, add it back in.
if "-XNoImplicitPrelude" `elem` flags if "-XNoImplicitPrelude" `elem` flags
then evalImport "import qualified Prelude as Prelude" then evalImport "import qualified Prelude as Prelude"
else else when ("-XImplicitPrelude" `elem` flags) $ do
when ("-XImplicitPrelude" `elem` flags) $ do
importDecl <- parseImportDecl "import Prelude" importDecl <- parseImportDecl "import Prelude"
let implicitPrelude = importDecl { ideclImplicit = True } let implicitPrelude = importDecl { ideclImplicit = True }
imports <- getContext imports <- getContext
setContext $ IIDecl implicitPrelude : imports setContext $ IIDecl implicitPrelude : imports
return EvalOut { return
evalStatus = Success, EvalOut
evalResult = display, { evalStatus = Success
evalState = state', , evalResult = display
evalPager = "", , evalState = state'
evalComms = [] , evalPager = ""
, evalComms = []
} }
evalCommand output (Directive SetExtension opts) state = do evalCommand output (Directive SetExtension opts) state = do
...@@ -485,8 +511,7 @@ evalCommand output (Directive LoadModule mods) state = wrapExecution state $ do ...@@ -485,8 +511,7 @@ evalCommand output (Directive LoadModule mods) state = wrapExecution state $ do
'-' -> (words remainder, True) '-' -> (words remainder, True)
_ -> (words stripped, False) _ -> (words stripped, False)
forM_ modules $ \modl -> forM_ modules $ \modl -> if removeModule
if removeModule
then removeImport modl then removeImport modl
else evalImport $ "import " ++ modl else evalImport $ "import " ++ modl
...@@ -496,25 +521,26 @@ evalCommand a (Directive SetOption opts) state = do ...@@ -496,25 +521,26 @@ evalCommand a (Directive SetOption opts) state = do
write state $ "Option: " ++ opts write state $ "Option: " ++ opts
let (existing, nonExisting) = partition optionExists $ words opts let (existing, nonExisting) = partition optionExists $ words opts
if not $ null nonExisting if not $ null nonExisting
then then let err = "No such options: " ++ intercalate ", " nonExisting
let err = "No such options: " ++ intercalate ", " nonExisting in in return
return EvalOut { EvalOut
evalStatus = Failure, { evalStatus = Failure
evalResult = displayError err, , evalResult = displayError err
evalState = state, , evalState = state
evalPager = "", , evalPager = ""
evalComms = [] , evalComms = []
} }
else else let options = mapMaybe findOption $ words opts
let options = mapMaybe findOption $ words opts updater = foldl' (.) id $ map getUpdateKernelState options
updater = foldl' (.) id $ map getUpdateKernelState options in in return
return EvalOut { EvalOut
evalStatus = Success, { evalStatus = Success
evalResult = mempty, , evalResult = mempty
evalState = updater state, , evalState = updater state
evalPager = "", , evalPager = ""
evalComms = [] , evalComms = []
} }
where where
optionExists = isJust . findOption optionExists = isJust . findOption
findOption opt = findOption opt =
...@@ -522,7 +548,7 @@ evalCommand a (Directive SetOption opts) state = do ...@@ -522,7 +548,7 @@ evalCommand a (Directive SetOption opts) state = do
evalCommand _ (Directive GetType expr) state = wrapExecution state $ do evalCommand _ (Directive GetType expr) state = wrapExecution state $ do
write state $ "Type: " ++ expr write state $ "Type: " ++ expr
formatType <$> ((expr ++ " :: ") ++ ) <$> getType expr formatType <$> ((expr ++ " :: ") ++) <$> getType expr
evalCommand _ (Directive GetKind expr) state = wrapExecution state $ do evalCommand _ (Directive GetKind expr) state = wrapExecution state $ do
write state $ "Kind: " ++ expr write state $ "Kind: " ++ expr
...@@ -546,7 +572,8 @@ evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $ ...@@ -546,7 +572,8 @@ evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $
"cd":dirs -> do "cd":dirs -> do
-- Get home so we can replace '~` with it. -- Get home so we can replace '~` with it.
homeEither <- liftIO (try $ getEnv "HOME" :: IO (Either SomeException String)) homeEither <- liftIO (try $ getEnv "HOME" :: IO (Either SomeException String))
let home = case homeEither of let home =
case homeEither of
Left _ -> "~" Left _ -> "~"
Right val -> val Right val -> val
...@@ -554,8 +581,8 @@ evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $ ...@@ -554,8 +581,8 @@ evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $
exists <- liftIO $ doesDirectoryExist directory exists <- liftIO $ doesDirectoryExist directory
if exists if exists
then do then do
-- Set the directory in IHaskell native code, for future shell -- Set the directory in IHaskell native code, for future shell commands. This doesn't set it for
-- commands. This doesn't set it for user code, though. -- user code, though.
liftIO $ setCurrentDirectory directory liftIO $ setCurrentDirectory directory
-- Set the directory for user code. -- Set the directory for user code.
...@@ -564,21 +591,14 @@ evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $ ...@@ -564,21 +591,14 @@ evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $
replace "\"" "\\\"" directory replace "\"" "\\\"" directory
runStmt cmd RunToCompletion runStmt cmd RunToCompletion
return mempty return mempty
else else return $ displayError $ printf "No such directory: '%s'" directory
return $ displayError $ printf "No such directory: '%s'" directory
cmd -> liftIO $ do cmd -> liftIO $ do
#if MIN_VERSION_base(4,8,0) (pipe, handle) <- createPipe'
(pipe, handle) <- createPipe
#else
(readEnd, writeEnd) <- createPipe
handle <- fdToHandle writeEnd
pipe <- fdToHandle readEnd
#endif
let initProcSpec = shell $ unwords cmd let initProcSpec = shell $ unwords cmd
procSpec = initProcSpec { procSpec = initProcSpec
std_in = Inherit, { std_in = Inherit
std_out = UseHandle handle, , std_out = UseHandle handle
std_err = UseHandle handle , std_err = UseHandle handle
} }
(_, _, _, process) <- createProcess procSpec (_, _, _, process) <- createProcess procSpec
...@@ -587,8 +607,8 @@ evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $ ...@@ -587,8 +607,8 @@ evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $
-- Start a loop to publish intermediate results. -- Start a loop to publish intermediate results.
let let
-- Compute how long to wait between reading pieces of the output. -- Compute how long to wait between reading pieces of the output. `threadDelay` takes an
-- `threadDelay` takes an argument of microseconds. -- argument of microseconds.
ms = 1000 ms = 1000
delay = 100 * ms delay = 100 * ms
...@@ -625,43 +645,56 @@ evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $ ...@@ -625,43 +645,56 @@ evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $
ExitFailure code -> do ExitFailure code -> do
let errMsg = "Process exited with error code " ++ show code let errMsg = "Process exited with error code " ++ show code
htmlErr = printf "<span class='err-msg'>%s</span>" errMsg htmlErr = printf "<span class='err-msg'>%s</span>" errMsg
return $ Display [plain $ out ++ "\n" ++ errMsg, return $ Display
html $ printf "<span class='mono'>%s</span>" out ++ htmlErr] [ plain $ out ++ "\n" ++ errMsg
, html $ printf "<span class='mono'>%s</span>" out ++ htmlErr
]
loop loop
where
#if MIN_VERSION_base(4,8,0)
createPipe' = createPipe
#else
createPipe' = do
(readEnd, writeEnd) <- createPipe
handle <- fdToHandle writeEnd
pipe <- fdToHandle readEnd
return (pipe, handle)
#endif
-- This is taken largely from GHCi's info section in InteractiveUI. -- This is taken largely from GHCi's info section in InteractiveUI.
evalCommand _ (Directive GetHelp _) state = do evalCommand _ (Directive GetHelp _) state = do
write state "Help via :help or :?." write state "Help via :help or :?."
return EvalOut { return
evalStatus = Success, EvalOut
evalResult = Display [out], { evalStatus = Success
evalState = state, , evalResult = Display [out]
evalPager = "", , evalState = state
evalComms = [] , evalPager = ""
, evalComms = []
} }
where out = plain $ intercalate "\n"
["The following commands are available:" where
," :extension <Extension> - Enable a GHC extension." out = plain $ intercalate "\n"
," :extension No<Extension> - Disable a GHC extension." [ "The following commands are available:"
," :type <expression> - Print expression type." , " :extension <Extension> - Enable a GHC extension."
," :info <name> - Print all info for a name." , " :extension No<Extension> - Disable a GHC extension."
," :hoogle <query> - Search for a query on Hoogle." , " :type <expression> - Print expression type."
," :doc <ident> - Get documentation for an identifier via Hogole." , " :info <name> - Print all info for a name."
," :set -XFlag -Wall - Set an option (like ghci)." , " :hoogle <query> - Search for a query on Hoogle."
," :option <opt> - Set an option." , " :doc <ident> - Get documentation for an identifier via Hogole."
," :option no-<opt> - Unset an option." , " :set -XFlag -Wall - Set an option (like ghci)."
," :?, :help - Show this help text." , " :option <opt> - Set an option."
,"" , " :option no-<opt> - Unset an option."
,"Any prefix of the commands will also suffice, e.g. use :ty for :type." , " :?, :help - Show this help text."
,"" , ""
,"Options:" , "Any prefix of the commands will also suffice, e.g. use :ty for :type."
," lint – enable or disable linting." , ""
," svg – use svg output (cannot be resized)." , "Options:"
," show-types – show types of all bound names" , " lint – enable or disable linting."
," show-errors – display Show instance missing errors normally." , " svg – use svg output (cannot be resized)."
," pager – use the pager to display results of :info, :doc, :hoogle, etc." , " show-types – show types of all bound names"
, " show-errors – display Show instance missing errors normally."
, " pager – use the pager to display results of :info, :doc, :hoogle, etc."
] ]
-- This is taken largely from GHCi's info section in InteractiveUI. -- This is taken largely from GHCi's info section in InteractiveUI.
...@@ -673,17 +706,20 @@ evalCommand _ (Directive GetInfo str) state = safely state $ do ...@@ -673,17 +706,20 @@ evalCommand _ (Directive GetInfo str) state = safely state $ do
-- TODO: Make pager work without html by porting to newer architecture -- TODO: Make pager work without html by porting to newer architecture
let output = unlines (map htmlify strings) let output = unlines (map htmlify strings)
htmlify str = htmlify str =
printf "<div style='background: rgb(247, 247, 247);'><form><textarea id='code'>%s</textarea></form></div>" str printf
"<div style='background: rgb(247, 247, 247);'><form><textarea id='code'>%s</textarea></form></div>"
str
++ script ++ script
script = script =
"<script>CodeMirror.fromTextArea(document.getElementById('code'), {mode: 'haskell', readOnly: 'nocursor'});</script>" "<script>CodeMirror.fromTextArea(document.getElementById('code'), {mode: 'haskell', readOnly: 'nocursor'});</script>"
return EvalOut { return
evalStatus = Success, EvalOut
evalResult = mempty, { evalStatus = Success
evalState = state, , evalResult = mempty
evalPager = output, , evalState = state
evalComms = [] , evalPager = output
, evalComms = []
} }
evalCommand _ (Directive SearchHoogle query) state = safely state $ do evalCommand _ (Directive SearchHoogle query) state = safely state $ do
...@@ -711,8 +747,7 @@ evalCommand output (Statement stmt) state = wrapExecution state $ do ...@@ -711,8 +747,7 @@ evalCommand output (Statement stmt) state = wrapExecution state $ do
write state $ "Names: " ++ show allNames write state $ "Names: " ++ show allNames
-- Display the types of all bound names if the option is on. -- Display the types of all bound names if the option is on. This is similar to GHCi :set +t.
-- This is similar to GHCi :set +t.
if not $ useShowTypes state if not $ useShowTypes state
then return $ Display output then return $ Display output
else do else do
...@@ -724,14 +759,12 @@ evalCommand output (Statement stmt) state = wrapExecution state $ do ...@@ -724,14 +759,12 @@ evalCommand output (Statement stmt) state = wrapExecution state $ do
let joined = unlines types let joined = unlines types
htmled = unlines $ map formatGetType types htmled = unlines $ map formatGetType types
return $ case extractPlain output of return $
case extractPlain output of
"" -> Display [html htmled] "" -> Display [html htmled]
-- Return plain and html versions. -- Return plain and html versions. Previously there was only a plain version.
-- Previously there was only a plain version. text -> Display [plain $ joined ++ "\n" ++ text, html $ htmled ++ mono text]
text -> Display
[plain $ joined ++ "\n" ++ text,
html $ htmled ++ mono text]
RunException exception -> throw exception RunException exception -> throw exception
RunBreak{} -> error "Should not break." RunBreak{} -> error "Should not break."
...@@ -739,10 +772,9 @@ evalCommand output (Statement stmt) state = wrapExecution state $ do ...@@ -739,10 +772,9 @@ evalCommand output (Statement stmt) state = wrapExecution state $ do
evalCommand output (Expression expr) state = do evalCommand output (Expression expr) state = do
write state $ "Expression:\n" ++ expr write state $ "Expression:\n" ++ expr
-- Try to use `display` to convert our type into the output -- Try to use `display` to convert our type into the output Dislay If typechecking fails and there
-- Dislay If typechecking fails and there is no appropriate -- is no appropriate typeclass instance, this will throw an exception and thus `attempt` will return
-- typeclass instance, this will throw an exception and thus `attempt` will -- False, and we just resort to plaintext.
-- return False, and we just resort to plaintext.
let displayExpr = printf "(IHaskell.Display.display (%s))" expr :: String let displayExpr = printf "(IHaskell.Display.display (%s))" expr :: String
canRunDisplay <- attempt $ exprType displayExpr canRunDisplay <- attempt $ exprType displayExpr
...@@ -751,7 +783,7 @@ evalCommand output (Expression expr) state = do ...@@ -751,7 +783,7 @@ evalCommand output (Expression expr) state = do
isWidget <- attempt $ exprType widgetExpr isWidget <- attempt $ exprType widgetExpr
-- Check if this is a template haskell declaration -- Check if this is a template haskell declaration
let declExpr = printf "((id :: IHaskellTH.DecsQ -> IHaskellTH.DecsQ) (%s))" expr::String let declExpr = printf "((id :: IHaskellTH.DecsQ -> IHaskellTH.DecsQ) (%s))" expr :: String
let anyExpr = printf "((id :: IHaskellPrelude.Int -> IHaskellPrelude.Int) (%s))" expr :: String let anyExpr = printf "((id :: IHaskellPrelude.Int -> IHaskellPrelude.Int) (%s))" expr :: String
isTHDeclaration <- liftM2 (&&) (attempt $ exprType declExpr) (not <$> attempt (exprType anyExpr)) isTHDeclaration <- liftM2 (&&) (attempt $ exprType declExpr) (not <$> attempt (exprType anyExpr))
...@@ -760,17 +792,19 @@ evalCommand output (Expression expr) state = do ...@@ -760,17 +792,19 @@ evalCommand output (Expression expr) state = do
write state $ "Is Declaration: " ++ show isTHDeclaration write state $ "Is Declaration: " ++ show isTHDeclaration
if isTHDeclaration if isTHDeclaration
-- If it typechecks as a DecsQ, we do not want to display the DecsQ, then
-- we just want the declaration made. -- If it typechecks as a DecsQ, we do not want to display the DecsQ, we just want the
then do -- declaration made.
do
write state $ "Suppressing display for template haskell declaration" write state $ "Suppressing display for template haskell declaration"
GHC.runDecls expr GHC.runDecls expr
return EvalOut { return
evalStatus = Success, EvalOut
evalResult = mempty, { evalStatus = Success
evalState = state, , evalResult = mempty
evalPager = "", , evalState = state
evalComms = [] , evalPager = ""
, evalComms = []
} }
else do else do
if canRunDisplay if canRunDisplay
...@@ -783,36 +817,37 @@ evalCommand output (Expression expr) state = do ...@@ -783,36 +817,37 @@ evalCommand output (Expression expr) state = do
then registerWidget out then registerWidget out
else return out else return out
else do else do
-- Evaluate this expression as though it's just a statement. -- Evaluate this expression as though it's just a statement. The output is bound to 'it', so we can
-- The output is bound to 'it', so we can then use it. -- then use it.
evalOut <- evalCommand output (Statement expr) state evalOut <- evalCommand output (Statement expr) state
let out = evalResult evalOut let out = evalResult evalOut
showErr = isShowError out showErr = isShowError out
-- If evaluation failed, return the failure. If it was successful, we -- If evaluation failed, return the failure. If it was successful, we may be able to use the
-- may be able to use the IHaskellDisplay typeclass. -- IHaskellDisplay typeclass.
return $ if not showErr || useShowErrors state return $ if not showErr || useShowErrors state
then evalOut then evalOut
else postprocessShowError evalOut else postprocessShowError evalOut
where where
-- Try to evaluate an action. Return True if it succeeds and False if -- Try to evaluate an action. Return True if it succeeds and False if it throws an exception. The
-- it throws an exception. The result of the action is discarded. -- result of the action is discarded.
attempt :: Interpreter a -> Interpreter Bool attempt :: Interpreter a -> Interpreter Bool
attempt action = gcatch (action >> return True) failure attempt action = gcatch (action >> return True) failure
where failure :: SomeException -> Interpreter Bool where
failure :: SomeException -> Interpreter Bool
failure _ = return False failure _ = return False
-- Check if the error is due to trying to print something that doesn't -- Check if the error is due to trying to print something that doesn't implement the Show typeclass.
-- implement the Show typeclass.
isShowError (ManyDisplay _) = False isShowError (ManyDisplay _) = False
isShowError (Display errs) = isShowError (Display errs) =
-- Note that we rely on this error message being 'type cleaned', so -- Note that we rely on this error message being 'type cleaned', so that `Show` is not displayed as
-- that `Show` is not displayed as GHC.Show.Show. This is also very fragile! -- GHC.Show.Show. This is also very fragile!
startswith "No instance for (Show" msg && startswith "No instance for (Show" msg &&
isInfixOf "print it" msg isInfixOf "print it" msg
where msg = extractPlain errs where
msg = extractPlain errs
isSvg (DisplayData mime _) = mime == MimeSvg isSvg (DisplayData mime _) = mime == MimeSvg
...@@ -821,16 +856,12 @@ evalCommand output (Expression expr) state = do ...@@ -821,16 +856,12 @@ evalCommand output (Expression expr) state = do
removeSvg (ManyDisplay disps) = ManyDisplay $ map removeSvg disps removeSvg (ManyDisplay disps) = ManyDisplay $ map removeSvg disps
useDisplay displayExpr = do useDisplay displayExpr = do
-- If there are instance matches, convert the object into -- If there are instance matches, convert the object into a Display. We also serialize it into a
-- a Display. We also serialize it into a bytestring. We get -- bytestring. We get the bytestring IO action as a dynamic and then convert back to a bytestring,
-- the bytestring IO action as a dynamic and then convert back to -- which we promptly unserialize. Note that attempting to do this without the serialization to
-- a bytestring, which we promptly unserialize. Note that -- binary and back gives very strange errors - all the types match but it refuses to decode back
-- attempting to do this without the serialization to binary and -- into a Display. Suppress output, so as not to mess up console. First, evaluate the expression in
-- back gives very strange errors - all the types match but it -- such a way that we have access to `it`.
-- refuses to decode back into a Display.
-- Suppress output, so as not to mess up console.
-- First, evaluate the expression in such a way that we have access to `it`.
io <- isIO expr io <- isIO expr
let stmtTemplate = if io let stmtTemplate = if io
then "it <- (%s)" then "it <- (%s)"
...@@ -872,9 +903,10 @@ evalCommand output (Expression expr) state = do ...@@ -872,9 +903,10 @@ evalCommand output (Expression expr) state = do
state' = state { openComms = newComms } state' = state { openComms = newComms }
-- Store the fact that we should start this comm. -- Store the fact that we should start this comm.
return evalOut { return
evalComms = CommInfo widget uuid (targetName widget) : evalComms evalOut, evalOut
evalState = state' { evalComms = CommInfo widget uuid (targetName widget) : evalComms evalOut
, evalState = state'
} }
isIO expr = attempt $ exprType $ printf "((\\x -> x) :: IO a -> IO a) (%s)" expr isIO expr = attempt $ exprType $ printf "((\\x -> x) :: IO a -> IO a) (%s)" expr
...@@ -885,16 +917,21 @@ evalCommand output (Expression expr) state = do ...@@ -885,16 +917,21 @@ evalCommand output (Expression expr) state = do
Display disps = evalResult evalOut Display disps = evalResult evalOut
text = extractPlain disps text = extractPlain disps
postprocess (DisplayData MimeHtml _) = html $ printf fmt unshowableType (formatErrorWithClass "err-msg collapse" text) script postprocess (DisplayData MimeHtml _) = html $ printf
fmt
unshowableType
(formatErrorWithClass "err-msg collapse"
text)
script
where where
fmt = "<div class='collapse-group'><span class='btn btn-default' href='#' id='unshowable'>Unshowable:<span class='show-type'>%s</span></span>%s</div><script>%s</script>" fmt = "<div class='collapse-group'><span class='btn btn-default' href='#' id='unshowable'>Unshowable:<span class='show-type'>%s</span></span>%s</div><script>%s</script>"
script = unlines [ script = unlines
"$('#unshowable').on('click', function(e) {", [ "$('#unshowable').on('click', function(e) {"
" e.preventDefault();", , " e.preventDefault();"
" var $this = $(this);", , " var $this = $(this);"
" var $collapse = $this.closest('.collapse-group').find('.err-msg');", , " var $collapse = $this.closest('.collapse-group').find('.err-msg');"
" $collapse.collapse('toggle');", , " $collapse.collapse('toggle');"
"});" , "});"
] ]
postprocess other = other postprocess other = other
...@@ -910,14 +947,12 @@ evalCommand output (Expression expr) state = do ...@@ -910,14 +947,12 @@ evalCommand output (Expression expr) state = do
else after else after
evalCommand _ (Declaration decl) state = wrapExecution state $ do evalCommand _ (Declaration decl) state = wrapExecution state $ do
write state $ "Declaration:\n" ++ decl write state $ "Declaration:\n" ++ decl
boundNames <- evalDeclarations decl boundNames <- evalDeclarations decl
let nonDataNames = filter (not . isUpper . head) boundNames let nonDataNames = filter (not . isUpper . head) boundNames
-- Display the types of all bound names if the option is on. -- Display the types of all bound names if the option is on. This is similar to GHCi :set +t.
-- This is similar to GHCi :set +t.
if not $ useShowTypes state if not $ useShowTypes state
then return mempty then return mempty
else do else do
...@@ -930,53 +965,50 @@ evalCommand _ (Declaration decl) state = wrapExecution state $ do ...@@ -930,53 +965,50 @@ evalCommand _ (Declaration decl) state = wrapExecution state $ do
return $ Display [html $ unlines $ map formatGetType types] return $ Display [html $ unlines $ map formatGetType types]
evalCommand _ (TypeSignature sig) state = wrapExecution state $ evalCommand _ (TypeSignature sig) state = wrapExecution state $
-- We purposefully treat this as a "success" because that way execution -- We purposefully treat this as a "success" because that way execution continues. Empty type
-- continues. Empty type signatures are likely due to a parse error later -- signatures are likely due to a parse error later on, and we want that to be displayed.
-- on, and we want that to be displayed. return $ displayError $ "The type signature " ++ sig ++ "\nlacks an accompanying binding."
return $ displayError $ "The type signature " ++ sig ++
"\nlacks an accompanying binding."
evalCommand _ (ParseError loc err) state = do evalCommand _ (ParseError loc err) state = do
write state "Parse Error." write state "Parse Error."
return EvalOut { return
evalStatus = Failure, EvalOut
evalResult = displayError $ formatParseError loc err, { evalStatus = Failure
evalState = state, , evalResult = displayError $ formatParseError loc err
evalPager = "", , evalState = state
evalComms = [] , evalPager = ""
, evalComms = []
} }
evalCommand _ (Pragma (PragmaUnsupported pragmaType) pragmas) state = wrapExecution state $ evalCommand _ (Pragma (PragmaUnsupported pragmaType) pragmas) state = wrapExecution state $
return $ displayError $ "Pragmas of type " ++ pragmaType ++ return $ displayError $ "Pragmas of type " ++ pragmaType ++ "\nare not supported."
"\nare not supported."
evalCommand output (Pragma PragmaLanguage pragmas) state = do evalCommand output (Pragma PragmaLanguage pragmas) state = do
write state $ "Got LANGUAGE pragma " ++ show pragmas write state $ "Got LANGUAGE pragma " ++ show pragmas
evalCommand output (Directive SetExtension $ unwords pragmas) state evalCommand output (Directive SetExtension $ unwords pragmas) state
hoogleResults :: KernelState -> [Hoogle.HoogleResult] -> EvalOut hoogleResults :: KernelState -> [Hoogle.HoogleResult] -> EvalOut
hoogleResults state results = EvalOut { hoogleResults state results =
evalStatus = Success, EvalOut
evalResult = mempty, { evalStatus = Success
evalState = state, , evalResult = mempty
evalPager = output, , evalState = state
evalComms = [] , evalPager = output
, evalComms = []
} }
where where
-- TODO: Make pager work with plaintext -- TODO: Make pager work with plaintext
fmt = Hoogle.HTML fmt = Hoogle.HTML
output = unlines $ map (Hoogle.render fmt) results output = unlines $ map (Hoogle.render fmt) results
-- Read from a file handle until we hit a delimiter or until we've read -- Read from a file handle until we hit a delimiter or until we've read as many characters as
-- as many characters as requested -- requested
readChars :: Handle -> String -> Int -> IO String readChars :: Handle -> String -> Int -> IO String
-- If we're done reading, return nothing. -- If we're done reading, return nothing.
readChars handle delims 0 = return [] readChars handle delims 0 = return []
readChars handle delims nchars = do readChars handle delims nchars = do
-- Try reading a single character. It will throw an exception if the -- Try reading a single character. It will throw an exception if the handle is already closed.
-- handle is already closed.
tryRead <- gtry $ hGetChar handle :: IO (Either SomeException Char) tryRead <- gtry $ hGetChar handle :: IO (Either SomeException Char)
case tryRead of case tryRead of
Right char -> Right char ->
...@@ -985,11 +1017,10 @@ readChars handle delims nchars = do ...@@ -985,11 +1017,10 @@ readChars handle delims nchars = do
then return [char] then return [char]
else do else do
next <- readChars handle delims (nchars - 1) next <- readChars handle delims (nchars - 1)
return $ char:next return $ char : next
-- An error occurs at the end of the stream, so just stop reading. -- An error occurs at the end of the stream, so just stop reading.
Left _ -> return [] Left _ -> return []
doLoadModule :: String -> String -> Ghc Display doLoadModule :: String -> String -> Ghc Display
doLoadModule name modName = do doLoadModule name modName = do
-- Remember which modules we've loaded before. -- Remember which modules we've loaded before.
...@@ -998,13 +1029,7 @@ doLoadModule name modName = do ...@@ -998,13 +1029,7 @@ doLoadModule name modName = do
flip gcatch (unload importedModules) $ do flip gcatch (unload importedModules) $ do
-- Compile loaded modules. -- Compile loaded modules.
flags <- getSessionDynFlags flags <- getSessionDynFlags
#if MIN_VERSION_ghc(7,8,0) setSessionDynFlags flags { hscTarget = objTarget flags }
let objTarget = defaultObjectTarget platform
platform = targetPlatform flags
#else
let objTarget = defaultObjectTarget
#endif
setSessionDynFlags flags{ hscTarget = objTarget }
-- Clear old targets to be sure. -- Clear old targets to be sure.
setTargets [] setTargets []
...@@ -1025,11 +1050,12 @@ doLoadModule name modName = do ...@@ -1025,11 +1050,12 @@ doLoadModule name modName = do
-- Switch back to interpreted mode. -- Switch back to interpreted mode.
flags <- getSessionDynFlags flags <- getSessionDynFlags
setSessionDynFlags flags{ hscTarget = HscInterpreted } setSessionDynFlags flags { hscTarget = HscInterpreted }
case result of case result of
Succeeded -> return mempty Succeeded -> return mempty
Failed -> return $ displayError $ "Failed to load module " ++ modName Failed -> return $ displayError $ "Failed to load module " ++ modName
where where
unload :: [InteractiveImport] -> SomeException -> Ghc Display unload :: [InteractiveImport] -> SomeException -> Ghc Display
unload imported exception = do unload imported exception = do
...@@ -1040,14 +1066,18 @@ doLoadModule name modName = do ...@@ -1040,14 +1066,18 @@ doLoadModule name modName = do
-- Switch to interpreted mode! -- Switch to interpreted mode!
flags <- getSessionDynFlags flags <- getSessionDynFlags
setSessionDynFlags flags{ hscTarget = HscInterpreted } setSessionDynFlags flags { hscTarget = HscInterpreted }
-- Return to old context, make sure we have `it`. -- Return to old context, make sure we have `it`.
setContext imported setContext imported
initializeItVariable initializeItVariable
return $ displayError $ "Failed to load module " ++ modName ++ ": " ++ show exception return $ displayError $ "Failed to load module " ++ modName ++ ": " ++ show exception
#if MIN_VERSION_ghc(7,8,0)
objTarget flags = defaultObjectTarget $ targetPlatform flags
#else
objTarget flags = defaultObjectTarget
#endif
keepingItVariable :: Interpreter a -> Interpreter a keepingItVariable :: Interpreter a -> Interpreter a
keepingItVariable act = do keepingItVariable act = do
-- Generate the it variable temp name -- Generate the it variable temp name
...@@ -1066,8 +1096,8 @@ capturedStatement :: (String -> IO ()) -- ^ Function used to publish int ...@@ -1066,8 +1096,8 @@ capturedStatement :: (String -> IO ()) -- ^ Function used to publish int
-> String -- ^ Statement to evaluate. -> String -- ^ Statement to evaluate.
-> Interpreter (String, RunResult) -- ^ Return the output and result. -> Interpreter (String, RunResult) -- ^ Return the output and result.
capturedStatement output stmt = do capturedStatement output stmt = do
-- Generate random variable names to use so that we cannot accidentally -- Generate random variable names to use so that we cannot accidentally override the variables by
-- override the variables by using the right names in the terminal. -- using the right names in the terminal.
gen <- liftIO getStdGen gen <- liftIO getStdGen
let let
-- Variable names generation. -- Variable names generation.
...@@ -1112,32 +1142,27 @@ capturedStatement output stmt = do ...@@ -1112,32 +1142,27 @@ capturedStatement output stmt = do
-- Initialize evaluation context. -- Initialize evaluation context.
void $ forM initStmts goStmt void $ forM initStmts goStmt
-- Get the pipe to read printed output from. -- Get the pipe to read printed output from. This is effectively the source code of dynCompileExpr
-- This is effectively the source code of dynCompileExpr from GHC API's -- from GHC API's InteractiveEval. However, instead of using a `Dynamic` as an intermediary, it just
-- InteractiveEval. However, instead of using a `Dynamic` as an -- directly reads the value. This is incredibly unsafe! However, for some reason the `getContext`
-- intermediary, it just directly reads the value. This is incredibly -- and `setContext` required by dynCompileExpr (to import and clear Data.Dynamic) cause issues with
-- unsafe! However, for some reason the `getContext` and `setContext` -- data declarations being updated (e.g. it drops newer versions of data declarations for older ones
-- required by dynCompileExpr (to import and clear Data.Dynamic) cause -- for unknown reasons). First, compile down to an HValue.
-- issues with data declarations being updated (e.g. it drops newer
-- versions of data declarations for older ones for unknown reasons).
-- First, compile down to an HValue.
Just (_, hValues, _) <- withSession $ liftIO . flip hscStmt pipeExpr Just (_, hValues, _) <- withSession $ liftIO . flip hscStmt pipeExpr
-- Then convert the HValue into an executable bit, and read the value. -- Then convert the HValue into an executable bit, and read the value.
pipe <- liftIO $ do pipe <- liftIO $ do
fd <- head <$> unsafeCoerce hValues fd <- head <$> unsafeCoerce hValues
fdToHandle fd fdToHandle fd
-- Read from a file handle until we hit a delimiter or until we've read -- Read from a file handle until we hit a delimiter or until we've read as many characters as
-- as many characters as requested -- requested
let let readChars :: Handle -> String -> Int -> IO String
readChars :: Handle -> String -> Int -> IO String
-- If we're done reading, return nothing. -- If we're done reading, return nothing.
readChars handle delims 0 = return [] readChars handle delims 0 = return []
readChars handle delims nchars = do readChars handle delims nchars = do
-- Try reading a single character. It will throw an exception if the -- Try reading a single character. It will throw an exception if the handle is already closed.
-- handle is already closed.
tryRead <- gtry $ hGetChar handle :: IO (Either SomeException Char) tryRead <- gtry $ hGetChar handle :: IO (Either SomeException Char)
case tryRead of case tryRead of
Right char -> Right char ->
...@@ -1146,7 +1171,7 @@ capturedStatement output stmt = do ...@@ -1146,7 +1171,7 @@ capturedStatement output stmt = do
then return [char] then return [char]
else do else do
next <- readChars handle delims (nchars - 1) next <- readChars handle delims (nchars - 1)
return $ char:next return $ char : next
-- An error occurs at the end of the stream, so just stop reading. -- An error occurs at the end of the stream, so just stop reading.
Left _ -> return [] Left _ -> return []
...@@ -1158,8 +1183,8 @@ capturedStatement output stmt = do ...@@ -1158,8 +1183,8 @@ capturedStatement output stmt = do
-- Start a loop to publish intermediate results. -- Start a loop to publish intermediate results.
let let
-- Compute how long to wait between reading pieces of the output. -- Compute how long to wait between reading pieces of the output. `threadDelay` takes an
-- `threadDelay` takes an argument of microseconds. -- argument of microseconds.
ms = 1000 ms = 1000
delay = 100 * ms delay = 100 * ms
...@@ -1200,9 +1225,8 @@ capturedStatement output stmt = do ...@@ -1200,9 +1225,8 @@ capturedStatement output stmt = do
-- Finalize evaluation context. -- Finalize evaluation context.
void $ forM postStmts goStmt void $ forM postStmts goStmt
-- Once context is finalized, reading can finish. -- Once context is finalized, reading can finish. Wait for reading to finish to that the output
-- Wait for reading to finish to that the output accumulator is -- accumulator is completely filled.
-- completely filled.
liftIO $ takeMVar finishedReading liftIO $ takeMVar finishedReading
printedOutput <- liftIO $ readMVar outputAccum printedOutput <- liftIO $ readMVar outputAccum
...@@ -1228,7 +1252,6 @@ formatErrorWithClass cls = ...@@ -1228,7 +1252,6 @@ formatErrorWithClass cls =
startswith "No instance for (Show" err && startswith "No instance for (Show" err &&
isInfixOf " arising from a use of `print'" err isInfixOf " arising from a use of `print'" err
formatParseError :: StringLoc -> String -> ErrMsg formatParseError :: StringLoc -> String -> ErrMsg
formatParseError (Loc line col) = formatParseError (Loc line col) =
printf "Parse error (line %d, column %d): %s" line col printf "Parse error (line %d, column %d): %s" line col
......
{-# LANGUAGE NoImplicitPrelude, FlexibleInstances, OverloadedStrings #-} {-# LANGUAGE NoImplicitPrelude, FlexibleInstances, OverloadedStrings #-}
module IHaskell.Eval.Hoogle ( module IHaskell.Eval.Hoogle (
search, search,
document, document,
render, render,
OutputFormat(..), OutputFormat(..),
HoogleResult HoogleResult,
) where ) where
import ClassyPrelude hiding (last, span, div) import ClassyPrelude hiding (last, span, div)
...@@ -22,19 +23,13 @@ import qualified Prelude as P ...@@ -22,19 +23,13 @@ import qualified Prelude as P
import IHaskell.IPython import IHaskell.IPython
-- | Types of formats to render output to. -- | Types of formats to render output to.
data OutputFormat data OutputFormat = Plain -- ^ Render to plain text.
= Plain -- ^ Render to plain text.
| HTML -- ^ Render to HTML. | HTML -- ^ Render to HTML.
data HoogleResponse = HoogleResponse { data HoogleResponse = HoogleResponse { location :: String, self :: String, docs :: String }
location :: String,
self :: String,
docs :: String
}
deriving (Eq, Show) deriving (Eq, Show)
data HoogleResult data HoogleResult = SearchResult HoogleResponse
= SearchResult HoogleResponse
| DocResult HoogleResponse | DocResult HoogleResponse
| NoResult String | NoResult String
deriving Show deriving Show
...@@ -48,23 +43,21 @@ instance FromJSON [HoogleResponse] where ...@@ -48,23 +43,21 @@ instance FromJSON [HoogleResponse] where
instance FromJSON HoogleResponse where instance FromJSON HoogleResponse where
parseJSON (Object obj) = parseJSON (Object obj) =
HoogleResponse <$> HoogleResponse <$> obj .: "location" <*> obj .: "self" <*> obj .: "docs"
obj .: "location" <*>
obj .: "self" <*>
obj .: "docs"
parseJSON _ = fail "Expected object with fields: location, self, docs" parseJSON _ = fail "Expected object with fields: location, self, docs"
-- | Query Hoogle for the given string. -- | Query Hoogle for the given string. This searches Hoogle using the internet. It returns either
-- This searches Hoogle using the internet. It returns either an error -- an error message or the successful JSON result.
-- message or the successful JSON result.
query :: String -> IO (Either String String) query :: String -> IO (Either String String)
query str = do query str = do
request <- parseUrl $ queryUrl $ urlEncode str request <- parseUrl $ queryUrl $ urlEncode str
response <- try $ withManager tlsManagerSettings $ httpLbs request response <- try $ withManager tlsManagerSettings $ httpLbs request
return $ case response of return $
case response of
Left err -> Left $ show (err :: SomeException) Left err -> Left $ show (err :: SomeException)
Right resp -> Right $ Char.unpack $ responseBody resp Right resp -> Right $ Char.unpack $ responseBody resp
where where
queryUrl :: String -> String queryUrl :: String -> String
queryUrl = printf "https://www.haskell.org/hoogle/?hoogle=%s&mode=json" queryUrl = printf "https://www.haskell.org/hoogle/?hoogle=%s&mode=json"
...@@ -78,27 +71,27 @@ urlEncode (ch:t) ...@@ -78,27 +71,27 @@ urlEncode (ch:t)
| otherwise = escape (P.fromEnum ch) (urlEncode t) | otherwise = escape (P.fromEnum ch) (urlEncode t)
where where
escape :: Int -> String -> String escape :: Int -> String -> String
escape b rs = '%':showH (b `P.div` 16) (showH (b `mod` 16) rs) escape b rs = '%' : showH (b `P.div` 16) (showH (b `mod` 16) rs)
showH :: Int -> String -> String showH :: Int -> String -> String
showH x xs showH x xs
| x <= 9 = toEnum (o_0 + x) : xs | x <= 9 = toEnum (o_0 + x) : xs
| otherwise = toEnum (o_A + (x-10)) : xs | otherwise = toEnum (o_A + (x - 10)) : xs
where where
o_0 = P.fromEnum '0' o_0 = P.fromEnum '0'
o_A = P.fromEnum 'A' o_A = P.fromEnum 'A'
eightBs :: [Int] -> Int -> [Int] eightBs :: [Int] -> Int -> [Int]
eightBs acc x eightBs acc x
| x <= 0xff = (x:acc) | x <= 255 = x : acc
| otherwise = eightBs ((x `mod` 256) : acc) (x `P.div` 256) | otherwise = eightBs ((x `mod` 256) : acc) (x `P.div` 256)
-- | Search for a query on Hoogle. -- | Search for a query on Hoogle. Return all search results.
-- Return all search results.
search :: String -> IO [HoogleResult] search :: String -> IO [HoogleResult]
search string = do search string = do
response <- query string response <- query string
return $ case response of return $
case response of
Left err -> [NoResult err] Left err -> [NoResult err]
Right json -> Right json ->
case eitherDecode $ Char.pack json of case eitherDecode $ Char.pack json of
...@@ -108,16 +101,17 @@ search string = do ...@@ -108,16 +101,17 @@ search string = do
[] -> [NoResult "no matching identifiers found."] [] -> [NoResult "no matching identifiers found."]
res -> res res -> res
-- | Look up an identifier on Hoogle. -- | Look up an identifier on Hoogle. Return documentation for that identifier. If there are many
-- Return documentation for that identifier. If there are many
-- identifiers, include documentation for all of them. -- identifiers, include documentation for all of them.
document :: String -> IO [HoogleResult] document :: String -> IO [HoogleResult]
document string = do document string = do
matchingResults <- filter matches <$> search string matchingResults <- filter matches <$> search string
let results = map toDocResult matchingResults let results = map toDocResult matchingResults
return $ case results of return $
case results of
[] -> [NoResult "no matching identifiers found."] [] -> [NoResult "no matching identifiers found."]
res -> res res -> res
where where
matches (SearchResult resp) = matches (SearchResult resp) =
case split " " $ self resp of case split " " $ self resp of
...@@ -134,21 +128,14 @@ render HTML = renderHtml ...@@ -134,21 +128,14 @@ render HTML = renderHtml
-- | Render a Hoogle result to plain text. -- | Render a Hoogle result to plain text.
renderPlain :: HoogleResult -> String renderPlain :: HoogleResult -> String
renderPlain (NoResult res) = renderPlain (NoResult res) =
"No response available: " ++ res "No response available: " ++ res
renderPlain (SearchResult resp) = renderPlain (SearchResult resp) =
printf "%s\nURL: %s\n%s" printf "%s\nURL: %s\n%s" (self resp) (location resp) (docs resp)
(self resp)
(location resp)
(docs resp)
renderPlain (DocResult resp) = renderPlain (DocResult resp) =
printf "%s\nURL: %s\n%s" printf "%s\nURL: %s\n%s" (self resp) (location resp) (docs resp)
(self resp)
(location resp)
(docs resp)
-- | Render a Hoogle result to HTML. -- | Render a Hoogle result to HTML.
renderHtml :: HoogleResult -> String renderHtml :: HoogleResult -> String
...@@ -167,37 +154,37 @@ renderHtml (SearchResult resp) = ...@@ -167,37 +154,37 @@ renderHtml (SearchResult resp) =
renderSelf :: String -> String -> String renderSelf :: String -> String -> String
renderSelf string loc renderSelf string loc
| startswith "package" string | startswith "package" string =
= pkg ++ " " ++ span "hoogle-package" (link loc $ extractPackage string) pkg ++ " " ++ span "hoogle-package" (link loc $ extractPackage string)
| startswith "module" string | startswith "module" string =
= let package = extractPackageName loc in let package = extractPackageName loc
mod ++ " " ++ in mod ++ " " ++
span "hoogle-module" (link loc $ extractModule string) ++ span "hoogle-module" (link loc $ extractModule string) ++
packageSub package packageSub package
| startswith "class" string | startswith "class" string =
= let package = extractPackageName loc in let package = extractPackageName loc
cls ++ " " ++ in cls ++ " " ++
span "hoogle-class" (link loc $ extractClass string) ++ span "hoogle-class" (link loc $ extractClass string) ++
packageSub package packageSub package
| startswith "data" string | startswith "data" string =
= let package = extractPackageName loc in let package = extractPackageName loc
dat ++ " " ++ in dat ++ " " ++
span "hoogle-class" (link loc $ extractData string) ++ span "hoogle-class" (link loc $ extractData string) ++
packageSub package packageSub package
| otherwise | otherwise =
= let [name, args] = split "::" string let [name, args] = split "::" string
package = extractPackageName loc package = extractPackageName loc
modname = extractModuleName loc in modname = extractModuleName loc
span "hoogle-name" (unicodeReplace $ in span "hoogle-name"
(unicodeReplace $
link loc (strip name) ++ link loc (strip name) ++
" :: " ++ " :: " ++
strip args) strip args)
++ packageAndModuleSub package modname ++ packageAndModuleSub package modname
where where
extractPackage = strip . replace "package" "" extractPackage = strip . replace "package" ""
extractModule = strip . replace "module" "" extractModule = strip . replace "module" ""
...@@ -239,8 +226,7 @@ renderDocs doc = ...@@ -239,8 +226,7 @@ renderDocs doc =
if isCode lines if isCode lines
then div "hoogle-code" $ unlines $ nonull lines then div "hoogle-code" $ unlines $ nonull lines
else div "hoogle-text" $ unlines $ nonull lines else div "hoogle-text" $ unlines $ nonull lines
in in div "hoogle-doc" $ unlines $ map makeBlock groups
div "hoogle-doc" $ unlines $ map makeBlock groups
extractPackageName :: String -> Maybe String extractPackageName :: String -> Maybe String
extractPackageName link = do extractPackageName link = do
......
{-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-} {-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}
{- | Description : Inspect type and function information and documentation.
-} {- | Description : Inspect type and function information and documentation. -}
module IHaskell.Eval.Info ( module IHaskell.Eval.Info (info) where
info
) where
import ClassyPrelude hiding (liftIO) import ClassyPrelude hiding (liftIO)
......
{-# LANGUAGE NoImplicitPrelude, QuasiQuotes, ViewPatterns #-} {-# LANGUAGE NoImplicitPrelude, QuasiQuotes, ViewPatterns #-}
module IHaskell.Eval.Lint (
lint module IHaskell.Eval.Lint (lint) where
) where
import Data.String.Utils (replace, startswith, strip, split) import Data.String.Utils (replace, startswith, strip, split)
import Prelude (head, tail, last) import Prelude (head, tail, last)
...@@ -32,13 +31,13 @@ import IHaskell.Eval.Parser hiding (line) ...@@ -32,13 +31,13 @@ import IHaskell.Eval.Parser hiding (line)
type ExtsModule = SrcExts.Module SrcSpanInfo type ExtsModule = SrcExts.Module SrcSpanInfo
data LintSuggestion data LintSuggestion =
= Suggest { Suggest
line :: LineNumber, { line :: LineNumber
found :: String, , found :: String
whyNot :: String, , whyNot :: String
severity :: Severity, , severity :: Severity
suggestion :: String , suggestion :: String
} }
deriving (Eq, Show) deriving (Eq, Show)
...@@ -51,8 +50,8 @@ hlintSettings = unsafePerformIO newEmptyMVar ...@@ -51,8 +50,8 @@ hlintSettings = unsafePerformIO newEmptyMVar
lintIdent :: String lintIdent :: String
lintIdent = "lintIdentAEjlkQeh" lintIdent = "lintIdentAEjlkQeh"
-- | Given parsed code chunks, perform linting and output a displayable -- | Given parsed code chunks, perform linting and output a displayable report on linting warnings
-- report on linting warnings and errors. -- and errors.
lint :: [Located CodeBlock] -> IO Display lint :: [Located CodeBlock] -> IO Display
lint blocks = do lint blocks = do
-- Initialize hlint settings -- Initialize hlint settings
...@@ -66,26 +65,25 @@ lint blocks = do ...@@ -66,26 +65,25 @@ lint blocks = do
-- create 'suggestions' -- create 'suggestions'
let modules = mapMaybe (createModule mode) blocks let modules = mapMaybe (createModule mode) blocks
ideas = applyHints classify hint (map (\m->(m,[])) modules) ideas = applyHints classify hint (map (\m -> (m, [])) modules)
suggestions = mapMaybe showIdea ideas suggestions = mapMaybe showIdea ideas
return $ Display $ return $ Display $
if null suggestions if null suggestions
then [] then []
else else [plain $ concatMap plainSuggestion suggestions, html $ htmlSuggestions suggestions]
[plain $ concatMap plainSuggestion suggestions,
html $ htmlSuggestions suggestions]
showIdea :: Idea -> Maybe LintSuggestion showIdea :: Idea -> Maybe LintSuggestion
showIdea idea = showIdea idea =
case ideaTo idea of case ideaTo idea of
Nothing -> Nothing Nothing -> Nothing
Just whyNot -> Just Suggest { Just whyNot -> Just
line = srcSpanStartLine $ ideaSpan idea, Suggest
found = showSuggestion $ ideaFrom idea, { line = srcSpanStartLine $ ideaSpan idea
whyNot = showSuggestion whyNot, , found = showSuggestion $ ideaFrom idea
severity = ideaSeverity idea, , whyNot = showSuggestion whyNot
suggestion = ideaHint idea , severity = ideaSeverity idea
, suggestion = ideaHint idea
} }
createModule :: ParseMode -> Located CodeBlock -> Maybe ExtsModule createModule :: ParseMode -> Located CodeBlock -> Maybe ExtsModule
...@@ -111,12 +109,12 @@ createModule mode (Located line block) = ...@@ -111,12 +109,12 @@ createModule mode (Located line block) =
unparse _ = Nothing unparse _ = Nothing
srcSpan :: SrcSpan srcSpan :: SrcSpan
srcSpan = SrcSpan { srcSpan = SrcSpan
srcSpanFilename = "<interactive>", { srcSpanFilename = "<interactive>"
srcSpanStartLine = line, , srcSpanStartLine = line
srcSpanStartColumn = 0, , srcSpanStartColumn = 0
srcSpanEndLine = line + length (lines blockStr), , srcSpanEndLine = line + length (lines blockStr)
srcSpanEndColumn = length $ last $ lines blockStr , srcSpanEndColumn = length $ last $ lines blockStr
} }
loc :: SrcSpanInfo loc :: SrcSpanInfo
...@@ -135,7 +133,8 @@ createModule mode (Located line block) = ...@@ -135,7 +133,8 @@ createModule mode (Located line block) =
exprToModule exp = moduleWithDecls <$> SpliceDecl loc <$> parseExpWithMode mode exp exprToModule exp = moduleWithDecls <$> SpliceDecl loc <$> parseExpWithMode mode exp
stmtToModule :: String -> ParseResult ExtsModule stmtToModule :: String -> ParseResult ExtsModule
stmtToModule stmtStr = case parseStmtWithMode mode stmtStr of stmtToModule stmtStr =
case parseStmtWithMode mode stmtStr of
ParseOk stmt -> ParseOk mod ParseOk stmt -> ParseOk mod
ParseFailed a b -> ParseFailed a b ParseFailed a b -> ParseFailed a b
where where
...@@ -158,10 +157,7 @@ createModule mode (Located line block) = ...@@ -158,10 +157,7 @@ createModule mode (Located line block) =
plainSuggestion :: LintSuggestion -> String plainSuggestion :: LintSuggestion -> String
plainSuggestion suggest = plainSuggestion suggest =
printf "Line %d: %s\nFound:\n%s\nWhy not:\n%s" printf "Line %d: %s\nFound:\n%s\nWhy not:\n%s" (line suggest) (suggestion suggest) (found suggest)
(line suggest)
(suggestion suggest)
(found suggest)
(whyNot suggest) (whyNot suggest)
htmlSuggestions :: [LintSuggestion] -> String htmlSuggestions :: [LintSuggestion] -> String
...@@ -169,18 +165,17 @@ htmlSuggestions = concatMap toHtml ...@@ -169,18 +165,17 @@ htmlSuggestions = concatMap toHtml
where where
toHtml :: LintSuggestion -> String toHtml :: LintSuggestion -> String
toHtml suggest = concat toHtml suggest = concat
[ [ named $ suggestion suggest
named $ suggestion suggest, , floating "left" $ style severityClass "Found:" ++
floating "left" $ style severityClass "Found:" ++
-- Things that look like this get highlighted. -- Things that look like this get highlighted.
styleId "highlight-code" "haskell" (found suggest), styleId "highlight-code" "haskell" (found suggest)
floating "left" $ style severityClass "Why Not:" ++ , floating "left" $ style severityClass "Why Not:" ++
-- Things that look like this get highlighted. -- Things that look like this get highlighted.
styleId "highlight-code" "haskell" (whyNot suggest) styleId "highlight-code" "haskell" (whyNot suggest)
] ]
where where
severityClass = case severity suggest of severityClass =
case severity suggest of
Error -> "error" Error -> "error"
Warning -> "warning" Warning -> "warning"
...@@ -199,7 +194,6 @@ htmlSuggestions = concatMap toHtml ...@@ -199,7 +194,6 @@ htmlSuggestions = concatMap toHtml
floating :: String -> String -> String floating :: String -> String -> String
floating dir thing = [i| <div class="suggestion-row" style="float: ${dir};">${thing}</div> |] floating dir thing = [i| <div class="suggestion-row" style="float: ${dir};">${thing}</div> |]
showSuggestion :: String -> String showSuggestion :: String -> String
showSuggestion = remove lintIdent . dropDo showSuggestion = remove lintIdent . dropDo
where where
...@@ -214,18 +208,14 @@ showSuggestion = remove lintIdent . dropDo ...@@ -214,18 +208,14 @@ showSuggestion = remove lintIdent . dropDo
else string else string
clean :: [String] -> [String] clean :: [String] -> [String]
-- If the first line starts with a `do`... -- If the first line starts with a `do`... Note that hlint always indents by two spaces in its
-- Note that hlint always indents by two spaces in its output. -- output.
clean ((stripPrefix " do " -> Just a) : as) = clean ((stripPrefix " do " -> Just a):as) =
-- Take all indented lines and unindent them. -- Take all indented lines and unindent them.
let unindented = catMaybes let unindented = catMaybes $ takeWhile isJust $ map (stripPrefix " ") as
$ takeWhile isJust fullDo = a : unindented
$ map (stripPrefix " ") as
fullDo = a:unindented
afterDo = drop (length unindented) as afterDo = drop (length unindented) as
in in fullDo ++ clean afterDo
--
fullDo ++ clean afterDo
-- Ignore other list elements - just proceed onwards. -- Ignore other list elements - just proceed onwards.
clean (x:xs) = x : clean xs clean (x:xs) = x : clean xs
......
-- | This module splits a shell command line into a list of strings, -- | This module splits a shell command line into a list of strings,
-- one for each command / filename -- one for each command / filename
module IHaskell.Eval.ParseShell (parseShell) where module IHaskell.Eval.ParseShell (parseShell) where
...@@ -20,9 +19,10 @@ manyTill p end = scan ...@@ -20,9 +19,10 @@ manyTill p end = scan
scan = end <|> do scan = end <|> do
x <- p x <- p
xs <- scan xs <- scan
return $ x:xs return $ x : xs
manyTill1 p end = do x <- p manyTill1 p end = do
x <- p
xs <- manyTill p end xs <- manyTill p end
return $ x : xs return $ x : xs
...@@ -37,7 +37,8 @@ quotedString = do ...@@ -37,7 +37,8 @@ quotedString = do
(manyTill anyChar (unescapedChar quote) <* quote) <?> "unexpected in quoted String " (manyTill anyChar (unescapedChar quote) <* quote) <?> "unexpected in quoted String "
unquotedString = manyTill1 anyChar end unquotedString = manyTill1 anyChar end
where end = unescapedChar space where
end = unescapedChar space
<|> (lookAhead eol >> return []) <|> (lookAhead eol >> return [])
word = quotedString <|> unquotedString <?> "word" word = quotedString <|> unquotedString <?> "word"
......
{-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-} {-# LANGUAGE NoImplicitPrelude, OverloadedStrings #-}
module IHaskell.Eval.Parser ( module IHaskell.Eval.Parser (
parseString, parseString,
CodeBlock(..), CodeBlock(..),
...@@ -26,28 +27,28 @@ import GHC hiding (Located) ...@@ -26,28 +27,28 @@ import GHC hiding (Located)
import Language.Haskell.GHC.Parser import Language.Haskell.GHC.Parser
import IHaskell.Eval.Util import IHaskell.Eval.Util
-- | A block of code to be evaluated. -- | A block of code to be evaluated. Each block contains a single element - one declaration,
-- Each block contains a single element - one declaration, statement, -- statement, expression, etc. If parsing of the block failed, the block is instead a ParseError,
-- expression, etc. If parsing of the block failed, the block is instead -- which has the error location and error message.
-- a ParseError, which has the error location and error message. data CodeBlock = Expression String -- ^ A Haskell expression.
data CodeBlock
= Expression String -- ^ A Haskell expression.
| Declaration String -- ^ A data type or function declaration. | Declaration String -- ^ A data type or function declaration.
| Statement String -- ^ A Haskell statement (as if in a `do` block). | Statement String -- ^ A Haskell statement (as if in a `do` block).
| Import String -- ^ An import statement. | Import String -- ^ An import statement.
| TypeSignature String -- ^ A lonely type signature (not above a function declaration). | TypeSignature String -- ^ A lonely type signature (not above a function
-- declaration).
| Directive DirectiveType String -- ^ An IHaskell directive. | Directive DirectiveType String -- ^ An IHaskell directive.
| Module String -- ^ A full Haskell module, to be compiled and loaded. | Module String -- ^ A full Haskell module, to be compiled and loaded.
| ParseError StringLoc ErrMsg -- ^ An error indicating that parsing the code block failed. | ParseError StringLoc ErrMsg -- ^ An error indicating that parsing the code block
| Pragma PragmaType [String] -- ^ A list of GHC pragmas (from a {-# LANGUAGE ... #-} block) -- failed.
| Pragma PragmaType [String] -- ^ A list of GHC pragmas (from a {-# LANGUAGE ... #-}
-- block)
deriving (Show, Eq) deriving (Show, Eq)
-- | Directive types. Each directive is associated with a string in the -- | Directive types. Each directive is associated with a string in the directive code block.
-- directive code block. data DirectiveType = GetType -- ^ Get the type of an expression via ':type' (or unique prefixes)
data DirectiveType
= GetType -- ^ Get the type of an expression via ':type' (or unique prefixes)
| GetInfo -- ^ Get info about the identifier via ':info' (or unique prefixes) | GetInfo -- ^ Get info about the identifier via ':info' (or unique prefixes)
| SetDynFlag -- ^ Enable or disable an extensions, packages etc. via `:set`. Emulates GHCi's `:set` | SetDynFlag -- ^ Enable or disable an extensions, packages etc. via `:set`.
-- Emulates GHCi's `:set`
| LoadFile -- ^ Load a Haskell module. | LoadFile -- ^ Load a Haskell module.
| SetOption -- ^ Set IHaskell kernel option `:option`. | SetOption -- ^ Set IHaskell kernel option `:option`.
| SetExtension -- ^ `:extension Foo` is a shortcut for `:set -XFoo` | SetExtension -- ^ `:extension Foo` is a shortcut for `:set -XFoo`
...@@ -59,10 +60,9 @@ data DirectiveType ...@@ -59,10 +60,9 @@ data DirectiveType
| LoadModule -- ^ Load and unload modules via ':module'. | LoadModule -- ^ Load and unload modules via ':module'.
deriving (Show, Eq) deriving (Show, Eq)
-- | Pragma types. Only LANGUAGE pragmas are currently supported. -- | Pragma types. Only LANGUAGE pragmas are currently supported. Other pragma types are kept around
-- Other pragma types are kept around as a string for error reporting. -- as a string for error reporting.
data PragmaType data PragmaType = PragmaLanguage
= PragmaLanguage
| PragmaUnsupported String | PragmaUnsupported String
deriving (Show, Eq) deriving (Show, Eq)
...@@ -73,18 +73,18 @@ parseString codeString = do ...@@ -73,18 +73,18 @@ parseString codeString = do
flags <- getSessionDynFlags flags <- getSessionDynFlags
let output = runParser flags parserModule codeString let output = runParser flags parserModule codeString
case output of case output of
Parsed mod | Just _ <- hsmodName (unLoc mod) -> return [Located 1 $ Module codeString] Parsed mod
| Just _ <- hsmodName (unLoc mod) -> return [Located 1 $ Module codeString]
_ -> do _ -> do
-- Split input into chunks based on indentation. -- Split input into chunks based on indentation.
let chunks = layoutChunks $ removeComments codeString let chunks = layoutChunks $ removeComments codeString
result <- joinFunctions <$> processChunks [] chunks result <- joinFunctions <$> processChunks [] chunks
-- Return to previous flags. When parsing, flags can be set to make -- Return to previous flags. When parsing, flags can be set to make sure parsing works properly. But
-- sure parsing works properly. But we don't want those flags to be -- we don't want those flags to be set during evaluation until the right time.
-- set during evaluation until the right time.
_ <- setSessionDynFlags flags _ <- setSessionDynFlags flags
return result return result
otherwise -> error "parseString failed, output was neither Parsed nor Failure"
where where
parseChunk :: GhcMonad m => String -> LineNumber -> m (Located CodeBlock) parseChunk :: GhcMonad m => String -> LineNumber -> m (Located CodeBlock)
parseChunk chunk line = Located line <$> handleChunk chunk line parseChunk chunk line = Located line <$> handleChunk chunk line
...@@ -137,14 +137,15 @@ parseCodeChunk code startLine = do ...@@ -137,14 +137,15 @@ parseCodeChunk code startLine = do
rawResults = map (tryParser code) (parsers flags) rawResults = map (tryParser code) (parsers flags)
-- Convert statements into expressions where we can -- Convert statements into expressions where we can
results = map (statementToExpression flags) rawResults in results = map (statementToExpression flags) rawResults
case successes results of case successes results of
-- If none of them succeeded, choose the best error message to -- If none of them succeeded, choose the best error message to display. Only one of the error
-- display. Only one of the error messages is actually relevant. -- messages is actually relevant.
[] -> return $ bestError $ failures results [] -> return $ bestError $ failures results
-- If one of the parsers succeeded -- If one of the parsers succeeded
result:_ -> return result result:_ -> return result
where where
successes :: [ParseOutput a] -> [a] successes :: [ParseOutput a] -> [a]
successes [] = [] successes [] = []
...@@ -164,19 +165,22 @@ parseCodeChunk code startLine = do ...@@ -164,19 +165,22 @@ parseCodeChunk code startLine = do
statementToExpression :: DynFlags -> ParseOutput CodeBlock -> ParseOutput CodeBlock statementToExpression :: DynFlags -> ParseOutput CodeBlock -> ParseOutput CodeBlock
statementToExpression flags (Parsed (Statement stmt)) = Parsed result statementToExpression flags (Parsed (Statement stmt)) = Parsed result
where result = if isExpr flags stmt where
result = if isExpr flags stmt
then Expression stmt then Expression stmt
else Statement stmt else Statement stmt
statementToExpression _ other = other statementToExpression _ other = other
-- Check whether a string is a valid expression. -- Check whether a string is a valid expression.
isExpr :: DynFlags -> String -> Bool isExpr :: DynFlags -> String -> Bool
isExpr flags str = case runParser flags parserExpression str of isExpr flags str =
Parsed {} -> True case runParser flags parserExpression str of
Parsed{} -> True
_ -> False _ -> False
tryParser :: String -> (String -> CodeBlock, String -> ParseOutput String) -> ParseOutput CodeBlock tryParser :: String -> (String -> CodeBlock, String -> ParseOutput String) -> ParseOutput CodeBlock
tryParser string (blockType, parser) = case parser string of tryParser string (blockType, parser) =
case parser string of
Parsed res -> Parsed (blockType res) Parsed res -> Parsed (blockType res)
Failure err loc -> Failure err loc Failure err loc -> Failure err loc
otherwise -> error "tryParser failed, output was neither Parsed nor Failure" otherwise -> error "tryParser failed, output was neither Parsed nor Failure"
...@@ -196,9 +200,9 @@ parseCodeChunk code startLine = do ...@@ -196,9 +200,9 @@ parseCodeChunk code startLine = do
Partial out strs -> Partial code strs Partial out strs -> Partial code strs
Failure err loc -> Failure err loc Failure err loc -> Failure err loc
-- | Find consecutive declarations of the same function and join them into -- | Find consecutive declarations of the same function and join them into a single declaration.
-- a single declaration. These declarations may also include a type -- These declarations may also include a type signature, which is also joined with the subsequent
-- signature, which is also joined with the subsequent declarations. -- declarations.
joinFunctions :: [Located CodeBlock] -> [Located CodeBlock] joinFunctions :: [Located CodeBlock] -> [Located CodeBlock]
joinFunctions [] = [] joinFunctions [] = []
joinFunctions blocks = joinFunctions blocks =
...@@ -221,7 +225,6 @@ joinFunctions blocks = ...@@ -221,7 +225,6 @@ joinFunctions blocks =
conjoin :: [CodeBlock] -> CodeBlock conjoin :: [CodeBlock] -> CodeBlock
conjoin = Declaration . intercalate "\n" . map str conjoin = Declaration . intercalate "\n" . map str
-- | Parse a pragma of the form {-# LANGUAGE ... #-} -- | Parse a pragma of the form {-# LANGUAGE ... #-}
parsePragma :: String -- ^ Pragma string. parsePragma :: String -- ^ Pragma string.
-> Int -- ^ Line number at which the directive appears. -> Int -- ^ Line number at which the directive appears.
...@@ -230,9 +233,10 @@ parsePragma ('{':'-':'#':pragma) line = ...@@ -230,9 +233,10 @@ parsePragma ('{':'-':'#':pragma) line =
let commaToSpace :: Char -> Char let commaToSpace :: Char -> Char
commaToSpace ',' = ' ' commaToSpace ',' = ' '
commaToSpace x = x commaToSpace x = x
pragmas = words $ takeWhile (/= '#') $ map commaToSpace pragma in pragmas = words $ takeWhile (/= '#') $ map commaToSpace pragma
case pragmas of in case pragmas of
[] -> Pragma (PragmaUnsupported "") [] --empty string pragmas are unsupported --empty string pragmas are unsupported
[] -> Pragma (PragmaUnsupported "") []
"LANGUAGE":xs -> Pragma PragmaLanguage xs "LANGUAGE":xs -> Pragma PragmaLanguage xs
x:xs -> Pragma (PragmaUnsupported x) xs x:xs -> Pragma (PragmaUnsupported x) xs
...@@ -240,19 +244,21 @@ parsePragma ('{':'-':'#':pragma) line = ...@@ -240,19 +244,21 @@ parsePragma ('{':'-':'#':pragma) line =
parseDirective :: String -- ^ Directive string. parseDirective :: String -- ^ Directive string.
-> Int -- ^ Line number at which the directive appears. -> Int -- ^ Line number at which the directive appears.
-> CodeBlock -- ^ Directive code block or a parse error. -> CodeBlock -- ^ Directive code block or a parse error.
parseDirective (':':'!':directive) line = Directive ShellCmd $ '!' : directive
parseDirective (':':'!':directive) line = Directive ShellCmd $ '!':directive parseDirective (':':directive) line =
parseDirective (':':directive) line = case find rightDirective directives of case find rightDirective directives of
Just (directiveType, _) -> Directive directiveType arg Just (directiveType, _) -> Directive directiveType arg
where arg = unwords restLine where arg = unwords restLine
_:restLine = words directive _:restLine = words directive
Nothing -> Nothing ->
let directiveStart = case words directive of let directiveStart =
case words directive of
[] -> "" [] -> ""
first:_ -> first in first:_ -> first
ParseError (Loc line 1) $ "Unknown directive: '" ++ directiveStart ++ "'." in ParseError (Loc line 1) $ "Unknown directive: '" ++ directiveStart ++ "'."
where where
rightDirective (_, dirname) = case words directive of rightDirective (_, dirname) =
case words directive of
[] -> False [] -> False
dir:_ -> dir `elem` tail (inits dirname) dir:_ -> dir `elem` tail (inits dirname)
directives = directives =
...@@ -271,15 +277,15 @@ parseDirective (':':directive) line = case find rightDirective directives of ...@@ -271,15 +277,15 @@ parseDirective (':':directive) line = case find rightDirective directives of
] ]
parseDirective _ _ = error "Directive must start with colon!" parseDirective _ _ = error "Directive must start with colon!"
-- | Parse a module and return the name declared in the 'module X where' -- | Parse a module and return the name declared in the 'module X where' line. That line is
-- line. That line is required, and if it does not exist, this will error. -- required, and if it does not exist, this will error. Names with periods in them are returned
-- Names with periods in them are returned piece y piece. -- piece y piece.
getModuleName :: GhcMonad m => String -> m [String] getModuleName :: GhcMonad m => String -> m [String]
getModuleName moduleSrc = do getModuleName moduleSrc = do
flags <- getSessionDynFlags flags <- getSessionDynFlags
let output = runParser flags parserModule moduleSrc let output = runParser flags parserModule moduleSrc
case output of case output of
Failure {} -> error "Module parsing failed." Failure{} -> error "Module parsing failed."
Parsed mod -> Parsed mod ->
case unLoc <$> hsmodName (unLoc mod) of case unLoc <$> hsmodName (unLoc mod) of
Nothing -> error "Module must have a name." Nothing -> error "Module must have a name."
......
{-# LANGUAGE CPP, NoImplicitPrelude #-} {-# LANGUAGE CPP, NoImplicitPrelude #-}
module IHaskell.Eval.Util ( module IHaskell.Eval.Util (
-- * Initialization -- * Initialization
initGhci, initGhci,
-- * Flags and extensions -- * Flags and extensions ** Set and unset flags.
-- ** Set and unset flags. extensionFlag,
extensionFlag, setExtension, setExtension,
ExtFlag(..), ExtFlag(..),
setFlags, setFlags,
...@@ -19,7 +20,7 @@ module IHaskell.Eval.Util ( ...@@ -19,7 +20,7 @@ module IHaskell.Eval.Util (
-- * Pretty printing -- * Pretty printing
doc, doc,
pprDynFlags, pprDynFlags,
pprLanguages pprLanguages,
) where ) where
import ClassyPrelude hiding ((<>)) import ClassyPrelude hiding ((<>))
...@@ -50,20 +51,17 @@ import Data.String.Utils (replace) ...@@ -50,20 +51,17 @@ import Data.String.Utils (replace)
import Data.List (nubBy) import Data.List (nubBy)
-- | A extension flag that can be set or unset. -- | A extension flag that can be set or unset.
data ExtFlag data ExtFlag = SetFlag ExtensionFlag
= SetFlag ExtensionFlag
| UnsetFlag ExtensionFlag | UnsetFlag ExtensionFlag
-- | Find the extension that corresponds to a given flag. Create the -- | Find the extension that corresponds to a given flag. Create the corresponding 'ExtFlag' via
-- corresponding 'ExtFlag' via @SetFlag@ or @UnsetFlag@. -- @SetFlag@ or @UnsetFlag@. If no such extension exist, yield @Nothing@.
-- If no such extension exist, yield @Nothing@.
extensionFlag :: String -- Extension name, such as @"DataKinds"@ extensionFlag :: String -- Extension name, such as @"DataKinds"@
-> Maybe ExtFlag -> Maybe ExtFlag
extensionFlag ext = extensionFlag ext =
case find (flagMatches ext) xFlags of case find (flagMatches ext) xFlags of
Just fs -> Just $ SetFlag $ flagSpecFlag fs Just fs -> Just $ SetFlag $ flagSpecFlag fs
-- If it doesn't match an extension name, try matching against -- If it doesn't match an extension name, try matching against disabling an extension.
-- disabling an extension.
Nothing -> Nothing ->
case find (flagMatchesNo ext) xFlags of case find (flagMatchesNo ext) xFlags of
Just fs -> Just $ UnsetFlag $ flagSpecFlag fs Just fs -> Just $ UnsetFlag $ flagSpecFlag fs
...@@ -72,48 +70,39 @@ extensionFlag ext = ...@@ -72,48 +70,39 @@ extensionFlag ext =
-- Check if a FlagSpec matches an extension name. -- Check if a FlagSpec matches an extension name.
flagMatches ext fs = ext == flagSpecName fs flagMatches ext fs = ext == flagSpecName fs
-- Check if a FlagSpec matches "No<ExtensionName>". -- Check if a FlagSpec matches "No<ExtensionName>". In that case, we disable the extension.
-- In that case, we disable the extension.
flagMatchesNo ext fs = ext == "No" ++ flagSpecName fs flagMatchesNo ext fs = ext == "No" ++ flagSpecName fs
#if !MIN_VERSION_ghc(7,10,0) #if !MIN_VERSION_ghc(7,10,0)
flagSpecName (name,_,_) = name flagSpecName (name, _, _) = name
flagSpecFlag (_,flag,_) = flag
#endif
#if !MIN_VERSION_ghc(7,10,0) flagSpecFlag (_, flag, _) = flag
flagSpecName (name,_,_) = name
flagSpecFlag (_,flag,_) = flag
#endif #endif
-- | Pretty-print dynamic flags (taken from 'InteractiveUI' module of `ghc-bin`) -- | Pretty-print dynamic flags (taken from 'InteractiveUI' module of `ghc-bin`)
pprDynFlags :: Bool -- ^ Whether to include flags which are on by default pprDynFlags :: Bool -- ^ Whether to include flags which are on by default
-> DynFlags -> DynFlags
-> SDoc -> SDoc
pprDynFlags show_all dflags = pprDynFlags show_all dflags =
vcat [ vcat
#if MIN_VERSION_ghc(7,8,0) [ text "GHCi-specific dynamic flag settings:" $$
text "GHCi-specific dynamic flag settings:" $$ nest 2 (vcat (map (setting opt) ghciFlags))
nest 2 (vcat (map (setting gopt) ghciFlags)), , text "other dynamic, non-language, flag settings:" $$
text "other dynamic, non-language, flag settings:" $$ nest 2 (vcat (map (setting opt) others))
nest 2 (vcat (map (setting gopt) others)), , text "warning settings:" $$
text "warning settings:" $$
nest 2 (vcat (map (setting wopt) DynFlags.fWarningFlags))
#else
text "GHCi-specific dynamic flag settings:" $$
nest 2 (vcat (map (setting dopt) ghciFlags)),
text "other dynamic, non-language, flag settings:" $$
nest 2 (vcat (map (setting dopt) others)),
text "warning settings:" $$
nest 2 (vcat (map (setting wopt) DynFlags.fWarningFlags)) nest 2 (vcat (map (setting wopt) DynFlags.fWarningFlags))
#endif
] ]
where where
#if MIN_VERSION_ghc(7,8,0)
opt = gopt
#else
opt = dopt
#endif
setting test flag setting test flag
| quiet = empty | quiet = empty
| is_on = fstr name | is_on = fstr name
| otherwise = fnostr name | otherwise = fnostr name
where name = flagSpecName flag where
name = flagSpecName flag
f = flagSpecFlag flag f = flagSpecFlag flag
is_on = test f dflags is_on = test f dflags
quiet = not show_all && test f default_dflags == is_on quiet = not show_all && test f default_dflags == is_on
...@@ -121,42 +110,43 @@ pprDynFlags show_all dflags = ...@@ -121,42 +110,43 @@ pprDynFlags show_all dflags =
default_dflags = defaultDynFlags (settings dflags) default_dflags = defaultDynFlags (settings dflags)
fstr str = text "-f" <> text str fstr str = text "-f" <> text str
fnostr str = text "-fno-" <> text str fnostr str = text "-fno-" <> text str
(ghciFlags,others) = partition (\f -> flagSpecFlag f `elem` flgs) (ghciFlags, others) = partition (\f -> flagSpecFlag f `elem` flgs) DynFlags.fFlags
DynFlags.fFlags
flgs = [ flgs = concat [flgs1, flgs2, flgs3]
Opt_PrintExplicitForalls
flgs1 = [Opt_PrintExplicitForalls]
#if MIN_VERSION_ghc(7,8,0) #if MIN_VERSION_ghc(7,8,0)
, Opt_PrintExplicitKinds flgs2 = [Opt_PrintExplicitKinds]
#else
flgs2 = []
#endif #endif
, Opt_PrintBindResult flgs3 = [Opt_PrintBindResult, Opt_BreakOnException, Opt_BreakOnError, Opt_PrintEvldWithShow]
, Opt_BreakOnException
, Opt_BreakOnError
, Opt_PrintEvldWithShow
]
-- | Pretty-print the base language and active options (taken from `InteractiveUI` module of `ghc-bin`) -- | Pretty-print the base language and active options (taken from `InteractiveUI` module of
-- `ghc-bin`)
pprLanguages :: Bool -- ^ Whether to include flags which are on by default pprLanguages :: Bool -- ^ Whether to include flags which are on by default
-> DynFlags -> DynFlags
-> SDoc -> SDoc
pprLanguages show_all dflags = pprLanguages show_all dflags =
vcat vcat
[ text "base language is: " <> [text "base language is: " <>
case language dflags of case language dflags of
Nothing -> text "Haskell2010" Nothing -> text "Haskell2010"
Just Haskell98 -> text "Haskell98" Just Haskell98 -> text "Haskell98"
Just Haskell2010 -> text "Haskell2010" Just Haskell2010 -> text "Haskell2010", (if show_all
, (if show_all then text "all active language options:" then text "all active language options:"
else text "with the following modifiers:") $$ else text "with the following modifiers:") $$
nest 2 (vcat (map (setting xopt) DynFlags.xFlags)) nest 2 (vcat (map (setting xopt) DynFlags.xFlags))]
]
where where
setting test flag setting test flag
| quiet = empty | quiet = empty
| is_on = text "-X" <> text name | is_on = text "-X" <> text name
| otherwise = text "-XNo" <> text name | otherwise = text "-XNo" <> text name
where name = flagSpecName flag where
name = flagSpecName flag
f = flagSpecFlag flag f = flagSpecFlag flag
is_on = test f dflags is_on = test f dflags
quiet = not show_all && test f default_dflags == is_on quiet = not show_all && test f default_dflags == is_on
...@@ -167,8 +157,8 @@ pprLanguages show_all dflags = ...@@ -167,8 +157,8 @@ pprLanguages show_all dflags =
Nothing -> Just Haskell2010 Nothing -> Just Haskell2010
other -> other other -> other
-- | Set an extension and update flags. -- | Set an extension and update flags. Return @Nothing@ on success. On failure, return an error
-- Return @Nothing@ on success. On failure, return an error message. -- message.
setExtension :: GhcMonad m => String -> m (Maybe String) setExtension :: GhcMonad m => String -> m (Maybe String)
setExtension ext = do setExtension ext = do
flags <- getSessionDynFlags flags <- getSessionDynFlags
...@@ -181,9 +171,8 @@ setExtension ext = do ...@@ -181,9 +171,8 @@ setExtension ext = do
UnsetFlag ghcFlag -> xopt_unset flags ghcFlag UnsetFlag ghcFlag -> xopt_unset flags ghcFlag
return Nothing return Nothing
-- | Set a list of flags, as per GHCi's `:set`. -- | Set a list of flags, as per GHCi's `:set`. This was adapted from GHC's InteractiveUI.hs
-- This was adapted from GHC's InteractiveUI.hs (newDynFlags). -- (newDynFlags). It returns a list of error messages.
-- It returns a list of error messages.
setFlags :: GhcMonad m => [String] -> m [String] setFlags :: GhcMonad m => [String] -> m [String]
setFlags ext = do setFlags ext = do
-- Try to parse flags. -- Try to parse flags.
...@@ -192,7 +181,7 @@ setFlags ext = do ...@@ -192,7 +181,7 @@ setFlags ext = do
-- First, try to check if this flag matches any extension name. -- First, try to check if this flag matches any extension name.
let restorePkg x = x { packageFlags = packageFlags flags } let restorePkg x = x { packageFlags = packageFlags flags }
let restoredPkgs = flags' { packageFlags = packageFlags flags} let restoredPkgs = flags' { packageFlags = packageFlags flags }
GHC.setProgramDynFlags restoredPkgs GHC.setProgramDynFlags restoredPkgs
GHC.setInteractiveDynFlags restoredPkgs GHC.setInteractiveDynFlags restoredPkgs
...@@ -203,11 +192,10 @@ setFlags ext = do ...@@ -203,11 +192,10 @@ setFlags ext = do
warnErrs = map ("Warning: " ++) allWarns warnErrs = map ("Warning: " ++) allWarns
return $ noParseErrs ++ warnErrs return $ noParseErrs ++ warnErrs
-- | Convert an 'SDoc' into a string. This is similar to the family of -- | Convert an 'SDoc' into a string. This is similar to the family of 'showSDoc' functions, but
-- 'showSDoc' functions, but does not impose an arbitrary width limit on -- does not impose an arbitrary width limit on the output (in terms of number of columns). Instead,
-- the output (in terms of number of columns). Instead, it respsects the -- it respsects the 'pprCols' field in the structure returned by 'getSessionDynFlags', and thus
-- 'pprCols' field in the structure returned by 'getSessionDynFlags', and -- gives a configurable width of output.
-- thus gives a configurable width of output.
doc :: GhcMonad m => SDoc -> m String doc :: GhcMonad m => SDoc -> m String
doc sdoc = do doc sdoc = do
flags <- getSessionDynFlags flags <- getSessionDynFlags
...@@ -216,15 +204,16 @@ doc sdoc = do ...@@ -216,15 +204,16 @@ doc sdoc = do
let cols = pprCols flags let cols = pprCols flags
d = runSDoc sdoc (initSDocContext flags style) d = runSDoc sdoc (initSDocContext flags style)
return $ Pretty.fullRender Pretty.PageMode cols 1.5 string_txt "" d return $ Pretty.fullRender Pretty.PageMode cols 1.5 string_txt "" d
where where
string_txt :: Pretty.TextDetails -> String -> String string_txt :: Pretty.TextDetails -> String -> String
string_txt (Pretty.Chr c) s = c:s string_txt (Pretty.Chr c) s = c : s
string_txt (Pretty.Str s1) s2 = s1 ++ s2 string_txt (Pretty.Str s1) s2 = s1 ++ s2
string_txt (Pretty.PStr s1) s2 = unpackFS s1 ++ s2 string_txt (Pretty.PStr s1) s2 = unpackFS s1 ++ s2
string_txt (Pretty.LStr s1 _) s2 = unpackLitString s1 ++ s2 string_txt (Pretty.LStr s1 _) s2 = unpackLitString s1 ++ s2
-- | Initialize the GHC API. Run this as the first thing in the `runGhc`. -- | Initialize the GHC API. Run this as the first thing in the `runGhc`. This initializes some dyn
-- This initializes some dyn flags (@ExtendedDefaultRules@, -- flags (@ExtendedDefaultRules@,
-- @NoMonomorphismRestriction@), sets the target to interpreted, link in -- @NoMonomorphismRestriction@), sets the target to interpreted, link in
-- memory, sets a reasonable output width, and potentially a few other -- memory, sets a reasonable output width, and potentially a few other
-- things. It should be invoked before other functions from this module. -- things. It should be invoked before other functions from this module.
...@@ -234,27 +223,28 @@ doc sdoc = do ...@@ -234,27 +223,28 @@ doc sdoc = do
-- (and only the first time) it is called. -- (and only the first time) it is called.
initGhci :: GhcMonad m => Maybe String -> m () initGhci :: GhcMonad m => Maybe String -> m ()
initGhci sandboxPackages = do initGhci sandboxPackages = do
-- Initialize dyn flags. -- Initialize dyn flags. Start with -XExtendedDefaultRules and -XNoMonomorphismRestriction.
-- Start with -XExtendedDefaultRules and -XNoMonomorphismRestriction.
originalFlags <- getSessionDynFlags originalFlags <- getSessionDynFlags
let flag = flip xopt_set let flag = flip xopt_set
unflag = flip xopt_unset unflag = flip xopt_unset
dflags = flag Opt_ExtendedDefaultRules . unflag Opt_MonomorphismRestriction $ originalFlags dflags = flag Opt_ExtendedDefaultRules . unflag Opt_MonomorphismRestriction $ originalFlags
pkgConfs = case sandboxPackages of pkgConfs =
case sandboxPackages of
Nothing -> extraPkgConfs originalFlags Nothing -> extraPkgConfs originalFlags
Just path -> Just path ->
let pkg = PkgConfFile path in let pkg = PkgConfFile path
(pkg:) . extraPkgConfs originalFlags in (pkg :) . extraPkgConfs originalFlags
void $ setSessionDynFlags $ dflags { hscTarget = HscInterpreted, void $ setSessionDynFlags $ dflags
ghcLink = LinkInMemory, { hscTarget = HscInterpreted
pprCols = 300, , ghcLink = LinkInMemory
extraPkgConfs = pkgConfs } , pprCols = 300
, extraPkgConfs = pkgConfs
-- | Evaluate a single import statement. }
-- If this import statement is importing a module which was previously
-- imported implicitly (such as `Prelude`) or if this module has a `hiding` -- | Evaluate a single import statement. If this import statement is importing a module which was
-- annotation, the previous import is removed. -- previously imported implicitly (such as `Prelude`) or if this module has a `hiding` annotation,
-- the previous import is removed.
evalImport :: GhcMonad m => String -> m () evalImport :: GhcMonad m => String -> m ()
evalImport imports = do evalImport imports = do
importDecl <- parseImportDecl imports importDecl <- parseImportDecl imports
...@@ -285,7 +275,8 @@ evalImport imports = do ...@@ -285,7 +275,8 @@ evalImport imports = do
-- Check whether an import is hidden. -- Check whether an import is hidden.
isHiddenImport :: ImportDecl RdrName -> Bool isHiddenImport :: ImportDecl RdrName -> Bool
isHiddenImport imp = case ideclHiding imp of isHiddenImport imp =
case ideclHiding imp of
Just (True, _) -> True Just (True, _) -> True
_ -> False _ -> False
...@@ -301,8 +292,7 @@ removeImport moduleName = do ...@@ -301,8 +292,7 @@ removeImport moduleName = do
isImportOf name (IIModule modName) = name == modName isImportOf name (IIModule modName) = name == modName
isImportOf name (IIDecl impDecl) = name == unLoc (ideclName impDecl) isImportOf name (IIDecl impDecl) = name == unLoc (ideclName impDecl)
-- | Evaluate a series of declarations. -- | Evaluate a series of declarations. Return all names which were bound by these declarations.
-- Return all names which were bound by these declarations.
evalDeclarations :: GhcMonad m => String -> m [String] evalDeclarations :: GhcMonad m => String -> m [String]
evalDeclarations decl = do evalDeclarations decl = do
names <- runDecls decl names <- runDecls decl
...@@ -321,16 +311,16 @@ cleanUpDuplicateInstances = modifySession $ \hscEnv -> ...@@ -321,16 +311,16 @@ cleanUpDuplicateInstances = modifySession $ \hscEnv ->
in hscEnv { hsc_IC = ic { ic_instances = (clsInsts', famInsts) } } in hscEnv { hsc_IC = ic { ic_instances = (clsInsts', famInsts) } }
where where
instEq :: ClsInst -> ClsInst -> Bool instEq :: ClsInst -> ClsInst -> Bool
instEq ClsInst{is_tvs = tpl_tvs,is_tys = tpl_tys, is_cls = cls} ClsInst{is_tys = tpl_tys', is_cls = cls'} =
#if MIN_VERSION_ghc(7,8,0) #if MIN_VERSION_ghc(7,8,0)
-- Only support replacing instances on GHC 7.8 and up -- Only support replacing instances on GHC 7.8 and up
let tpl_tv_set = mkVarSet tpl_tvs instEq c1 c2
| ClsInst { is_tvs = tpl_tvs, is_tys = tpl_tys, is_cls = cls } <- c1,
ClsInst { is_tys = tpl_tys', is_cls = cls' } <- c2
= let tpl_tv_set = mkVarSet tpl_tvs
in cls == cls' && isJust (tcMatchTys tpl_tv_set tpl_tys tpl_tys') in cls == cls' && isJust (tcMatchTys tpl_tv_set tpl_tys tpl_tys')
#else #else
False instEq _ _ = False
#endif #endif
-- | Get the type of an expression and convert it to a string. -- | Get the type of an expression and convert it to a string.
getType :: GhcMonad m => String -> m String getType :: GhcMonad m => String -> m String
getType expr = do getType expr = do
...@@ -345,18 +335,20 @@ getDescription str = do ...@@ -345,18 +335,20 @@ getDescription str = do
names <- parseName str names <- parseName str
maybeInfos <- mapM getInfo' names maybeInfos <- mapM getInfo' names
-- Filter out types that have parents in the same set. -- Filter out types that have parents in the same set. GHCi also does this.
-- GHCi also does this.
let infos = catMaybes maybeInfos let infos = catMaybes maybeInfos
allNames = mkNameSet $ map (getName . getType) infos allNames = mkNameSet $ map (getName . getType) infos
hasParent info = case tyThingParent_maybe (getType info) of hasParent info =
case tyThingParent_maybe (getType info) of
Just parent -> getName parent `elemNameSet` allNames Just parent -> getName parent `elemNameSet` allNames
Nothing -> False Nothing -> False
filteredOutput = filter (not . hasParent) infos filteredOutput = filter (not . hasParent) infos
-- Print nicely -- Print nicely
mapM (doc . printInfo) filteredOutput mapM (doc . printInfo) filteredOutput
where where
#if MIN_VERSION_ghc(7,8,0) #if MIN_VERSION_ghc(7,8,0)
getInfo' = getInfo False getInfo' = getInfo False
#else #else
...@@ -377,7 +369,8 @@ getDescription str = do ...@@ -377,7 +369,8 @@ getDescription str = do
vcat (map GHC.pprFamInst famInstances) vcat (map GHC.pprFamInst famInstances)
#else #else
printInfo (thing, fixity, classInstances) = printInfo (thing, fixity, classInstances) =
pprTyThingInContextLoc False thing $$ showFixity thing fixity $$ vcat (map GHC.pprInstance classInstances) pprTyThingInContextLoc False thing $$ showFixity thing fixity $$
vcat (map GHC.pprInstance classInstances)
#endif #endif
showFixity thing fixity = showFixity thing fixity =
if fixity == GHC.defaultFixity if fixity == GHC.defaultFixity
......
{-# LANGUAGE NoImplicitPrelude, DeriveFunctor #-} {-# LANGUAGE NoImplicitPrelude, DeriveFunctor #-}
module IHaskell.Flags ( module IHaskell.Flags (
IHaskellMode(..), IHaskellMode(..),
Argument(..), Argument(..),
...@@ -16,8 +17,7 @@ import System.Console.CmdArgs.Text ...@@ -16,8 +17,7 @@ import System.Console.CmdArgs.Text
import Data.List (findIndex) import Data.List (findIndex)
import IHaskell.Types import IHaskell.Types
-- Command line arguments to IHaskell. A set of aruments is annotated with -- Command line arguments to IHaskell. A set of arguments is annotated with the mode being invoked.
-- the mode being invoked.
data Args = Args IHaskellMode [Argument] data Args = Args IHaskellMode [Argument]
deriving Show deriving Show
...@@ -33,7 +33,9 @@ data Argument = ConfFile String -- ^ A file with commands to load at startup ...@@ -33,7 +33,9 @@ data Argument = ConfFile String -- ^ A file with commands to load at startup
| ConvertLhsStyle (LhsStyle String) | ConvertLhsStyle (LhsStyle String)
deriving (Eq, Show) deriving (Eq, Show)
data LhsStyle string = LhsStyle { lhsCodePrefix :: string -- ^ @>@ data LhsStyle string =
LhsStyle
{ lhsCodePrefix :: string -- ^ @>@
, lhsOutputPrefix :: string -- ^ @<<@ , lhsOutputPrefix :: string -- ^ @<<@
, lhsBeginCode :: string -- ^ @\\begin{code}@ , lhsBeginCode :: string -- ^ @\\begin{code}@
, lhsEndCode :: string -- ^ @\\end{code}@ , lhsEndCode :: string -- ^ @\\end{code}@
...@@ -42,21 +44,18 @@ data LhsStyle string = LhsStyle { lhsCodePrefix :: string -- ^ @>@ ...@@ -42,21 +44,18 @@ data LhsStyle string = LhsStyle { lhsCodePrefix :: string -- ^ @>@
} }
deriving (Eq, Functor, Show) deriving (Eq, Functor, Show)
data NotebookFormat = LhsMarkdown data NotebookFormat = LhsMarkdown
| IpynbFile | IpynbFile
deriving (Eq, Show) deriving (Eq, Show)
-- Which mode IHaskell is being invoked in. -- Which mode IHaskell is being invoked in.
-- `None` means no mode was specified.
data IHaskellMode = ShowHelp String data IHaskellMode = ShowHelp String
| InstallKernelSpec | InstallKernelSpec
| ConvertLhs | ConvertLhs
| Kernel (Maybe String) | Kernel (Maybe String)
deriving (Eq, Show) deriving (Eq, Show)
-- | Given a list of command-line arguments, return the IHaskell mode and -- | Given a list of command-line arguments, return the IHaskell mode and arguments to process.
-- arguments to process.
parseFlags :: [String] -> Either String Args parseFlags :: [String] -> Either String Args
parseFlags flags = parseFlags flags =
let modeIndex = findIndex (`elem` modeFlags) flags let modeIndex = findIndex (`elem` modeFlags) flags
...@@ -111,7 +110,8 @@ installKernelSpec = ...@@ -111,7 +110,8 @@ installKernelSpec =
[ghcLibFlag, kernelDebugFlag, confFlag, helpFlag] [ghcLibFlag, kernelDebugFlag, confFlag, helpFlag]
kernel :: Mode Args kernel :: Mode Args
kernel = mode "kernel" (Args (Kernel Nothing) []) "Invoke the IHaskell kernel." kernelArg [ghcLibFlag, kernelDebugFlag, confFlag] kernel = mode "kernel" (Args (Kernel Nothing) []) "Invoke the IHaskell kernel." kernelArg
[ghcLibFlag, kernelDebugFlag, confFlag]
where where
kernelArg = flagArg update "<json-kernel-file>" kernelArg = flagArg update "<json-kernel-file>"
update filename (Args _ flags) = Right $ Args (Kernel $ Just filename) flags update filename (Args _ flags) = Right $ Args (Kernel $ Just filename) flags
...@@ -154,6 +154,7 @@ convert = mode "convert" (Args ConvertLhs []) description unnamedArg convertFlag ...@@ -154,6 +154,7 @@ convert = mode "convert" (Args ConvertLhs []) description unnamedArg convertFlag
lhsStyleBird, lhsStyleTex :: LhsStyle String lhsStyleBird, lhsStyleTex :: LhsStyle String
lhsStyleBird = LhsStyle "> " "\n<< " "" "" "" "" lhsStyleBird = LhsStyle "> " "\n<< " "" "" "" ""
lhsStyleTex = LhsStyle "" "" "\\begin{code}" "\\end{code}" "\\begin{verbatim}" "\\end{verbatim}" lhsStyleTex = LhsStyle "" "" "\\begin{code}" "\\end{code}" "\\begin{verbatim}" "\\end{verbatim}"
ihaskellArgs :: Mode Args ihaskellArgs :: Mode Args
...@@ -161,8 +162,8 @@ ihaskellArgs = ...@@ -161,8 +162,8 @@ ihaskellArgs =
let descr = "Haskell for Interactive Computing." let descr = "Haskell for Interactive Computing."
helpStr = showText (Wrap 100) $ helpText [] HelpFormatAll ihaskellArgs helpStr = showText (Wrap 100) $ helpText [] HelpFormatAll ihaskellArgs
onlyHelp = [flagHelpSimple (add Help)] onlyHelp = [flagHelpSimple (add Help)]
noMode = mode "IHaskell" (Args (ShowHelp helpStr) []) descr noArgs onlyHelp in noMode = mode "IHaskell" (Args (ShowHelp helpStr) []) descr noArgs onlyHelp
noMode { modeGroupModes = toGroup allModes } in noMode { modeGroupModes = toGroup allModes }
where where
add flag (Args mode flags) = Args mode $ flag : flags add flag (Args mode flags) = Args mode $ flag : flags
......
...@@ -40,17 +40,20 @@ import qualified GHC.Paths ...@@ -40,17 +40,20 @@ import qualified GHC.Paths
import IHaskell.Types import IHaskell.Types
import System.Posix.Signals import System.Posix.Signals
data KernelSpecOptions =
data KernelSpecOptions = KernelSpecOptions { kernelSpecGhcLibdir :: String -- ^ GHC libdir. KernelSpecOptions
{ kernelSpecGhcLibdir :: String -- ^ GHC libdir.
, kernelSpecDebug :: Bool -- ^ Spew debugging output? , kernelSpecDebug :: Bool -- ^ Spew debugging output?
, kernelSpecConfFile :: IO (Maybe String) -- ^ Filename of profile JSON file. , kernelSpecConfFile :: IO (Maybe String) -- ^ Filename of profile JSON file.
} }
defaultKernelSpecOptions :: KernelSpecOptions defaultKernelSpecOptions :: KernelSpecOptions
defaultKernelSpecOptions = KernelSpecOptions { kernelSpecGhcLibdir = GHC.Paths.libdir defaultKernelSpecOptions = KernelSpecOptions
{ kernelSpecGhcLibdir = GHC.Paths.libdir
, kernelSpecDebug = False , kernelSpecDebug = False
, kernelSpecConfFile = defaultConfFile , kernelSpecConfFile = defaultConfFile
} }
-- | The IPython kernel name. -- | The IPython kernel name.
kernelName :: IsString a => a kernelName :: IsString a => a
kernelName = "haskell" kernelName = "haskell"
...@@ -133,6 +136,7 @@ verifyIPythonVersion = do ...@@ -133,6 +136,7 @@ verifyIPythonVersion = do
Just (1:_) -> oldIPython Just (1:_) -> oldIPython
Just (0:_) -> oldIPython Just (0:_) -> oldIPython
_ -> badIPython "Detected IPython, but could not parse version number." _ -> badIPython "Detected IPython, but could not parse version number."
where where
badIPython :: Text -> Sh () badIPython :: Text -> Sh ()
badIPython message = liftIO $ do badIPython message = liftIO $ do
...@@ -140,8 +144,8 @@ verifyIPythonVersion = do ...@@ -140,8 +144,8 @@ verifyIPythonVersion = do
exitFailure exitFailure
oldIPython = badIPython "Detected old version of IPython. IHaskell requires 3.0.0 or up." oldIPython = badIPython "Detected old version of IPython. IHaskell requires 3.0.0 or up."
-- | Install an IHaskell kernelspec into the right location. -- | Install an IHaskell kernelspec into the right location. The right location is determined by
-- The right location is determined by using `ipython kernelspec install --user`. -- using `ipython kernelspec install --user`.
installKernelspec :: Bool -> KernelSpecOptions -> Sh () installKernelspec :: Bool -> KernelSpecOptions -> Sh ()
installKernelspec replace opts = void $ do installKernelspec replace opts = void $ do
ihaskellPath <- getIHaskellPath ihaskellPath <- getIHaskellPath
...@@ -155,13 +159,14 @@ installKernelspec replace opts = void $ do ...@@ -155,13 +159,14 @@ installKernelspec replace opts = void $ do
Just file -> ["--conf", file]) Just file -> ["--conf", file])
++ ["--ghclib", kernelSpecGhcLibdir opts] ++ ["--ghclib", kernelSpecGhcLibdir opts]
let kernelSpec = KernelSpec { kernelDisplayName = "Haskell" let kernelSpec = KernelSpec
{ kernelDisplayName = "Haskell"
, kernelLanguage = kernelName , kernelLanguage = kernelName
, kernelCommand = [ihaskellPath, "kernel", "{connection_file}"] ++ kernelFlags , kernelCommand = [ihaskellPath, "kernel", "{connection_file}"] ++ kernelFlags
} }
-- Create a temporary directory. Use this temporary directory to make a kernelspec -- Create a temporary directory. Use this temporary directory to make a kernelspec directory; then,
-- directory; then, shell out to IPython to install this kernelspec directory. -- shell out to IPython to install this kernelspec directory.
withTmpDir $ \tmp -> do withTmpDir $ \tmp -> do
let kernelDir = tmp </> kernelName let kernelDir = tmp </> kernelName
let filename = kernelDir </> "kernel.json" let filename = kernelDir </> "kernel.json"
...@@ -185,16 +190,14 @@ kernelSpecCreated = do ...@@ -185,16 +190,14 @@ kernelSpecCreated = do
let kernelspecs = map T.strip $ lines out let kernelspecs = map T.strip $ lines out
return $ kernelName `elem` kernelspecs return $ kernelName `elem` kernelspecs
-- | Replace "~" with $HOME if $HOME is defined. -- | Replace "~" with $HOME if $HOME is defined. Otherwise, do nothing.
-- Otherwise, do nothing.
subHome :: String -> IO String subHome :: String -> IO String
subHome path = shelly $ do subHome path = shelly $ do
home <- unpack <$> fromMaybe "~" <$> get_env "HOME" home <- unpack <$> fromMaybe "~" <$> get_env "HOME"
return $ replace "~" home path return $ replace "~" home path
-- | Get the path to an executable. If it doensn't exist, fail with an error message complaining
-- | Get the path to an executable. If it doensn't exist, fail with an -- about it.
-- error message complaining about it.
path :: Text -> Sh FilePath path :: Text -> Sh FilePath
path exe = do path exe = do
path <- which $ fromText exe path <- which $ fromText exe
...@@ -229,9 +232,8 @@ getIHaskellPath = do ...@@ -229,9 +232,8 @@ getIHaskellPath = do
if FS.absolute f if FS.absolute f
then return $ FS.encodeString f then return $ FS.encodeString f
else else
-- Check whether this is a relative path, or just 'IHaskell' with $PATH -- Check whether this is a relative path, or just 'IHaskell' with $PATH resolution done by
-- resolution done by the shell. If it's just 'IHaskell', use the $PATH -- the shell. If it's just 'IHaskell', use the $PATH variable to find where IHaskell lives.
-- variable to find where IHaskell lives.
if FS.filename f == f if FS.filename f == f
then do then do
ihaskellPath <- which "ihaskell" ihaskellPath <- which "ihaskell"
......
{-# LANGUAGE NoImplicitPrelude, OverloadedStrings, DeriveDataTypeable, DeriveGeneric #-} {-# LANGUAGE NoImplicitPrelude, OverloadedStrings, DeriveDataTypeable, DeriveGeneric, ExistentialQuantification #-}
{-# LANGUAGE ExistentialQuantification #-}
-- | Description : All message type definitions. -- | Description : All message type definitions.
module IHaskell.Types ( module IHaskell.Types (
Message (..), Message(..),
MessageHeader (..), MessageHeader(..),
MessageType(..), MessageType(..),
Username, Username,
Metadata(..), Metadata(..),
replyType, replyType,
ExecutionState (..), ExecutionState(..),
StreamType(..), StreamType(..),
MimeType(..), MimeType(..),
DisplayData(..), DisplayData(..),
...@@ -16,7 +16,8 @@ module IHaskell.Types ( ...@@ -16,7 +16,8 @@ module IHaskell.Types (
ExecuteReplyStatus(..), ExecuteReplyStatus(..),
KernelState(..), KernelState(..),
LintStatus(..), LintStatus(..),
Width, Height, Width,
Height,
Display(..), Display(..),
defaultKernelState, defaultKernelState,
extractPlain, extractPlain,
...@@ -40,8 +41,8 @@ import IHaskell.IPython.Kernel ...@@ -40,8 +41,8 @@ import IHaskell.IPython.Kernel
-- | A class for displayable Haskell types. -- | A class for displayable Haskell types.
-- --
-- IHaskell's displaying of results behaves as if these two -- IHaskell's displaying of results behaves as if these two overlapping/undecidable instances also
-- overlapping/undecidable instances also existed: -- existed:
-- --
-- > instance (Show a) => IHaskellDisplay a -- > instance (Show a) => IHaskellDisplay a
-- > instance Show a where shows _ = id -- > instance Show a where shows _ = id
...@@ -50,12 +51,10 @@ class IHaskellDisplay a where ...@@ -50,12 +51,10 @@ class IHaskellDisplay a where
-- | Display as an interactive widget. -- | Display as an interactive widget.
class IHaskellDisplay a => IHaskellWidget a where class IHaskellDisplay a => IHaskellWidget a where
-- | Output target name for this widget. -- | Output target name for this widget. The actual input parameter should be ignored.
-- The actual input parameter should be ignored.
targetName :: a -> String targetName :: a -> String
-- | Called when the comm is opened. Allows additional messages to be sent -- | Called when the comm is opened. Allows additional messages to be sent after comm open.
-- after comm open.
open :: a -- ^ Widget to open a comm port with. open :: a -- ^ Widget to open a comm port with.
-> (Value -> IO ()) -- ^ Way to respond to the message. -> (Value -> IO ()) -- ^ Way to respond to the message.
-> IO () -> IO ()
...@@ -89,12 +88,12 @@ instance IHaskellWidget Widget where ...@@ -89,12 +88,12 @@ instance IHaskellWidget Widget where
instance Show Widget where instance Show Widget where
show _ = "<Widget>" show _ = "<Widget>"
-- | Wrapper for ipython-kernel's DisplayData which allows sending multiple results from the same
-- | Wrapper for ipython-kernel's DisplayData which allows sending multiple -- expression.
-- results from the same expression.
data Display = Display [DisplayData] data Display = Display [DisplayData]
| ManyDisplay [Display] | ManyDisplay [Display]
deriving (Show, Typeable, Generic) deriving (Show, Typeable, Generic)
instance Serialize Display instance Serialize Display
instance Monoid Display where instance Monoid Display where
...@@ -102,13 +101,15 @@ instance Monoid Display where ...@@ -102,13 +101,15 @@ instance Monoid Display where
ManyDisplay a `mappend` ManyDisplay b = ManyDisplay (a ++ b) ManyDisplay a `mappend` ManyDisplay b = ManyDisplay (a ++ b)
ManyDisplay a `mappend` b = ManyDisplay (a ++ [b]) ManyDisplay a `mappend` b = ManyDisplay (a ++ [b])
a `mappend` ManyDisplay b = ManyDisplay (a : b) a `mappend` ManyDisplay b = ManyDisplay (a : b)
a `mappend` b = ManyDisplay [a,b] a `mappend` b = ManyDisplay [a, b]
instance Semigroup Display where instance Semigroup Display where
a <> b = a `mappend` b a <> b = a `mappend` b
-- | All state stored in the kernel between executions. -- | All state stored in the kernel between executions.
data KernelState = KernelState { getExecutionCounter :: Int data KernelState =
KernelState
{ getExecutionCounter :: Int
, getLintStatus :: LintStatus -- Whether to use hlint, and what arguments to pass it. , getLintStatus :: LintStatus -- Whether to use hlint, and what arguments to pass it.
, useSvg :: Bool , useSvg :: Bool
, useShowErrors :: Bool , useShowErrors :: Bool
...@@ -120,7 +121,8 @@ data KernelState = KernelState { getExecutionCounter :: Int ...@@ -120,7 +121,8 @@ data KernelState = KernelState { getExecutionCounter :: Int
deriving Show deriving Show
defaultKernelState :: KernelState defaultKernelState :: KernelState
defaultKernelState = KernelState { getExecutionCounter = 1 defaultKernelState = KernelState
{ getExecutionCounter = 1
, getLintStatus = LintOn , getLintStatus = LintOn
, useSvg = True , useSvg = True
, useShowErrors = False , useShowErrors = False
...@@ -131,10 +133,12 @@ defaultKernelState = KernelState { getExecutionCounter = 1 ...@@ -131,10 +133,12 @@ defaultKernelState = KernelState { getExecutionCounter = 1
} }
-- | Kernel options to be set via `:set` and `:option`. -- | Kernel options to be set via `:set` and `:option`.
data KernelOpt = KernelOpt { data KernelOpt =
getOptionName :: [String], -- ^ Ways to set this option via `:option` KernelOpt
getSetName :: [String], -- ^ Ways to set this option via `:set` { getOptionName :: [String] -- ^ Ways to set this option via `:option`
getUpdateKernelState :: KernelState -> KernelState -- ^ Function to update the kernel state. , getSetName :: [String] -- ^ Ways to set this option via `:set`
, getUpdateKernelState :: KernelState -> KernelState -- ^ Function to update the kernel
-- state.
} }
kernelOpts :: [KernelOpt] kernelOpts :: [KernelOpt]
...@@ -152,23 +156,24 @@ kernelOpts = ...@@ -152,23 +156,24 @@ kernelOpts =
] ]
-- | Current HLint status. -- | Current HLint status.
data LintStatus data LintStatus = LintOn
= LintOn
| LintOff | LintOff
deriving (Eq, Show) deriving (Eq, Show)
data CommInfo = CommInfo Widget UUID String deriving Show data CommInfo = CommInfo Widget UUID String
deriving Show
-- | Output of evaluation. -- | Output of evaluation.
data EvaluationResult = data EvaluationResult =
-- | An intermediate result which communicates what has been printed thus -- | An intermediate result which communicates what has been printed thus
-- far. -- far.
IntermediateResult { IntermediateResult
outputs :: Display -- ^ Display outputs. { outputs :: Display -- ^ Display outputs.
} }
| FinalResult { |
outputs :: Display, -- ^ Display outputs. FinalResult
pagerOut :: String, -- ^ Text to display in the IPython pager. { outputs :: Display -- ^ Display outputs.
startComms :: [CommInfo] -- ^ Comms to start. , pagerOut :: String -- ^ Text to display in the IPython pager.
, startComms :: [CommInfo] -- ^ Comms to start.
} }
deriving Show deriving Show
{-# LANGUAGE NoImplicitPrelude, CPP, OverloadedStrings, ScopedTypeVariables, QuasiQuotes #-} {-# LANGUAGE NoImplicitPrelude, CPP, OverloadedStrings, ScopedTypeVariables, QuasiQuotes #-}
-- | Description : Argument parsing and basic messaging loop, using Haskell -- | Description : Argument parsing and basic messaging loop, using Haskell
-- Chans to communicate with the ZeroMQ sockets. -- Chans to communicate with the ZeroMQ sockets.
module Main where module Main (main) where
-- Prelude imports. -- Prelude imports.
import ClassyPrelude hiding (last, liftIO, readChan, writeChan) import ClassyPrelude hiding (last, liftIO, readChan, writeChan)
...@@ -71,7 +72,7 @@ ihaskell (Args (Kernel (Just filename)) args) = do ...@@ -71,7 +72,7 @@ ihaskell (Args (Kernel (Just filename)) args) = do
showingHelp :: IHaskellMode -> [Argument] -> IO () -> IO () showingHelp :: IHaskellMode -> [Argument] -> IO () -> IO ()
showingHelp mode flags act = showingHelp mode flags act =
case find (==Help) flags of case find (== Help) flags of
Just _ -> Just _ ->
putStrLn $ pack $ help mode putStrLn $ pack $ help mode
Nothing -> Nothing ->
...@@ -114,13 +115,11 @@ runKernel kernelOpts profileSrc = do ...@@ -114,13 +115,11 @@ runKernel kernelOpts profileSrc = do
-- Receive and reply to all messages on the shell socket. -- Receive and reply to all messages on the shell socket.
interpret libdir True $ do interpret libdir True $ do
-- Ignore Ctrl-C the first time. This has to go inside the -- Ignore Ctrl-C the first time. This has to go inside the `interpret`, because GHC API resets the
-- `interpret`, because GHC API resets the signal handlers for some -- signal handlers for some reason (completely unknown to me).
-- reason (completely unknown to me).
liftIO ignoreCtrlC liftIO ignoreCtrlC
-- Initialize the context by evaluating everything we got from the -- Initialize the context by evaluating everything we got from the command line flags.
-- command line flags.
let noPublish _ = return () let noPublish _ = return ()
evaluator line = void $ do evaluator line = void $ do
-- Create a new state each time. -- Create a new state each time.
...@@ -140,9 +139,8 @@ runKernel kernelOpts profileSrc = do ...@@ -140,9 +139,8 @@ runKernel kernelOpts profileSrc = do
-- Create a header for the reply. -- Create a header for the reply.
replyHeader <- createReplyHeader (header request) replyHeader <- createReplyHeader (header request)
-- We handle comm messages and normal ones separately. -- We handle comm messages and normal ones separately. The normal ones are a standard
-- The normal ones are a standard request/response style, while comms -- request/response style, while comms can be anything, and don't necessarily require a response.
-- can be anything, and don't necessarily require a response.
if isCommMessage request if isCommMessage request
then liftIO $ do then liftIO $ do
oldState <- takeMVar state oldState <- takeMVar state
...@@ -185,40 +183,39 @@ createReplyHeader parent = do ...@@ -185,40 +183,39 @@ createReplyHeader parent = do
let repType = fromMaybe err (replyType $ msgType parent) let repType = fromMaybe err (replyType $ msgType parent)
err = error $ "No reply for message " ++ show (msgType parent) err = error $ "No reply for message " ++ show (msgType parent)
return MessageHeader { return
identifiers = identifiers parent, MessageHeader
parentHeader = Just parent, { identifiers = identifiers parent
metadata = Map.fromList [], , parentHeader = Just parent
messageId = newMessageId, , metadata = Map.fromList []
sessionId = sessionId parent, , messageId = newMessageId
username = username parent, , sessionId = sessionId parent
msgType = repType , username = username parent
, msgType = repType
} }
-- | Compute a reply to a message. -- | Compute a reply to a message.
replyTo :: ZeroMQInterface -> Message -> MessageHeader -> KernelState -> Interpreter (KernelState, Message) replyTo :: ZeroMQInterface -> Message -> MessageHeader -> KernelState -> Interpreter (KernelState, Message)
-- Reply to kernel info requests with a kernel info reply. No computation needs to be done, as a
-- Reply to kernel info requests with a kernel info reply. No computation -- kernel info reply is a static object (all info is hard coded into the representation of that
-- needs to be done, as a kernel info reply is a static object (all info is -- message type).
-- hard coded into the representation of that message type).
replyTo _ KernelInfoRequest{} replyHeader state = replyTo _ KernelInfoRequest{} replyHeader state =
return (state, KernelInfoReply { return
header = replyHeader, (state, KernelInfoReply
language = "haskell", { header = replyHeader
versionList = ghcVersionInts , language = "haskell"
, versionList = ghcVersionInts
}) })
-- Reply to a shutdown request by exiting the main thread. -- Reply to a shutdown request by exiting the main thread. Before shutdown, reply to the request to
-- Before shutdown, reply to the request to let the frontend know shutdown -- let the frontend know shutdown is happening.
-- is happening. replyTo interface ShutdownRequest { restartPending = restartPending } replyHeader _ = liftIO $ do
replyTo interface ShutdownRequest{restartPending = restartPending} replyHeader _ = liftIO $ do
writeChan (shellReplyChannel interface) $ ShutdownReply replyHeader restartPending writeChan (shellReplyChannel interface) $ ShutdownReply replyHeader restartPending
exitSuccess exitSuccess
-- Reply to an execution request. The reply itself does not require -- Reply to an execution request. The reply itself does not require computation, but this causes
-- computation, but this causes messages to be sent to the IOPub socket -- messages to be sent to the IOPub socket with the output of the code in the execution request.
-- with the output of the code in the execution request. replyTo interface req@ExecuteRequest { getCode = code } replyHeader state = do
replyTo interface req@ExecuteRequest{ getCode = code } replyHeader state = do
-- Convenience function to send a message to the IOPub socket. -- Convenience function to send a message to the IOPub socket.
let send msg = liftIO $ writeChan (iopubChannel interface) msg let send msg = liftIO $ writeChan (iopubChannel interface) msg
...@@ -226,18 +223,16 @@ replyTo interface req@ExecuteRequest{ getCode = code } replyHeader state = do ...@@ -226,18 +223,16 @@ replyTo interface req@ExecuteRequest{ getCode = code } replyHeader state = do
dir <- liftIO getIHaskellDir dir <- liftIO getIHaskellDir
liftIO $ Stdin.recordParentHeader dir $ header req liftIO $ Stdin.recordParentHeader dir $ header req
-- Notify the frontend that the kernel is busy computing. -- Notify the frontend that the kernel is busy computing. All the headers are copies of the reply
-- All the headers are copies of the reply header with a different -- header with a different message type, because this preserves the session ID, parent header, and
-- message type, because this preserves the session ID, parent header, -- other important information.
-- and other important information.
busyHeader <- liftIO $ dupHeader replyHeader StatusMessage busyHeader <- liftIO $ dupHeader replyHeader StatusMessage
send $ PublishStatus busyHeader Busy send $ PublishStatus busyHeader Busy
-- Construct a function for publishing output as this is going. -- Construct a function for publishing output as this is going. This function accepts a boolean
-- This function accepts a boolean indicating whether this is the final -- indicating whether this is the final output and the thing to display. Store the final outputs in
-- output and the thing to display. Store the final outputs in a list so -- a list so that when we receive an updated non-final output, we can clear the entire output and
-- that when we receive an updated non-final output, we can clear the -- re-display with the updated output.
-- entire output and re-display with the updated output.
displayed <- liftIO $ newMVar [] displayed <- liftIO $ newMVar []
updateNeeded <- liftIO $ newMVar False updateNeeded <- liftIO $ newMVar False
pagerOutput <- liftIO $ newMVar "" pagerOutput <- liftIO $ newMVar ""
...@@ -254,7 +249,8 @@ replyTo interface req@ExecuteRequest{ getCode = code } replyHeader state = do ...@@ -254,7 +249,8 @@ replyTo interface req@ExecuteRequest{ getCode = code } replyHeader state = do
convertSvgToHtml x = x convertSvgToHtml x = x
makeSvgImg base64data = unpack $ "<img src=\"data:image/svg+xml;base64," ++ base64data ++ "\"/>" makeSvgImg base64data = unpack $ "<img src=\"data:image/svg+xml;base64," ++ base64data ++ "\"/>"
prependCss (DisplayData MimeHtml html) = DisplayData MimeHtml $ concat ["<style>", pack ihaskellCSS, "</style>", html] prependCss (DisplayData MimeHtml html) =
DisplayData MimeHtml $concat ["<style>", pack ihaskellCSS, "</style>", html]
prependCss x = x prependCss x = x
startComm :: CommInfo -> IO () startComm :: CommInfo -> IO ()
...@@ -271,9 +267,10 @@ replyTo interface req@ExecuteRequest{ getCode = code } replyHeader state = do ...@@ -271,9 +267,10 @@ replyTo interface req@ExecuteRequest{ getCode = code } replyHeader state = do
publish :: EvaluationResult -> IO () publish :: EvaluationResult -> IO ()
publish result = do publish result = do
let final = case result of let final =
IntermediateResult {} -> False case result of
FinalResult {} -> True IntermediateResult{} -> False
FinalResult{} -> True
outs = outputs result outs = outputs result
-- If necessary, clear all previous output and redraw. -- If necessary, clear all previous output and redraw.
...@@ -286,12 +283,11 @@ replyTo interface req@ExecuteRequest{ getCode = code } replyHeader state = do ...@@ -286,12 +283,11 @@ replyTo interface req@ExecuteRequest{ getCode = code } replyHeader state = do
-- Draw this message. -- Draw this message.
sendOutput outs sendOutput outs
-- If this is the final message, add it to the list of completed -- If this is the final message, add it to the list of completed messages. If it isn't, make sure we
-- messages. If it isn't, make sure we clear it later by marking -- clear it later by marking update needed as true.
-- update needed as true.
modifyMVar_ updateNeeded (const $ return $ not final) modifyMVar_ updateNeeded (const $ return $ not final)
when final $ do when final $ do
modifyMVar_ displayed (return . (outs:)) modifyMVar_ displayed (return . (outs :))
-- Start all comms that need to be started. -- Start all comms that need to be started.
mapM_ startComm $ startComms result mapM_ startComm $ startComms result
...@@ -319,11 +315,12 @@ replyTo interface req@ExecuteRequest{ getCode = code } replyHeader state = do ...@@ -319,11 +315,12 @@ replyTo interface req@ExecuteRequest{ getCode = code } replyHeader state = do
pager <- if usePager state pager <- if usePager state
then liftIO $ readMVar pagerOutput then liftIO $ readMVar pagerOutput
else return "" else return ""
return (updatedState, ExecuteReply { return
header = replyHeader, (updatedState, ExecuteReply
pagerOutput = pager, { header = replyHeader
executionCounter = execCount, , pagerOutput = pager
status = Ok , executionCounter = execCount
, status = Ok
}) })
...@@ -337,24 +334,25 @@ replyTo _ req@CompleteRequest{} replyHeader state = do ...@@ -337,24 +334,25 @@ replyTo _ req@CompleteRequest{} replyHeader state = do
reply = CompleteReply replyHeader (map pack completions) start end Map.empty True reply = CompleteReply replyHeader (map pack completions) start end Map.empty True
return (state, reply) return (state, reply)
-- Reply to the object_info_request message. Given an object name, return -- Reply to the object_info_request message. Given an object name, return the associated type
-- the associated type calculated by GHC. -- calculated by GHC.
replyTo _ ObjectInfoRequest{objectName = oname} replyHeader state = do replyTo _ ObjectInfoRequest { objectName = oname } replyHeader state = do
docs <- pack <$> info (unpack oname) docs <- pack <$> info (unpack oname)
let reply = ObjectInfoReply { let reply = ObjectInfoReply
header = replyHeader, { header = replyHeader
objectName = oname, , objectName = oname
objectFound = strip docs /= "", , objectFound = strip docs /= ""
objectTypeString = docs, , objectTypeString = docs
objectDocString = docs , objectDocString = docs
} }
return (state, reply) return (state, reply)
-- TODO: Implement history_reply. -- TODO: Implement history_reply.
replyTo _ HistoryRequest{} replyHeader state = do replyTo _ HistoryRequest{} replyHeader state = do
let reply = HistoryReply { let reply = HistoryReply
header = replyHeader, { header = replyHeader
historyReply = [] -- FIXME -- FIXME
, historyReply = []
} }
return (state, reply) return (state, reply)
......
#!/usr/bin/env python3
from __future__ import print_function
import sys
import os
import subprocess
def hindent(contents):
with open(".tmp3", "w") as f:
f.write(contents)
with open(".tmp3", "r") as f:
output = subprocess.check_output(["hindent", "--style", "gibiansky"],
stdin=f)
return output.decode('utf-8')
def diff(src1, src2):
# Ignore trailing newlines
if src1[-1] == "\n":
src1 = src1[:-1]
if src2[-1] == "\n":
src2 = src2[:-1]
with open(".tmp1", "w") as f1:
f1.write(src1)
with open(".tmp2", "w") as f2:
f2.write(src2)
try:
output = subprocess.check_output(["diff", ".tmp1", ".tmp2"])
return output.decode('utf-8')
except subprocess.CalledProcessError as e:
return e.output.decode('utf-8')
# Verify that we're in the right directory
try:
open("ihaskell.cabal", "r").close()
except:
print(sys.argv[0], "must be run from the ihaskell directory",
file=sys.stderr)
# Find all the source files
sources = []
for root, dirnames, filenames in os.walk("src"):
for filename in filenames:
if filename.endswith(".hs"):
sources.append(os.path.join(root, filename))
hindent_outputs = {}
for source_file in sources:
print("Formatting file", source_file)
with open(source_file, "r") as f:
original_source = f.read()
formatted_source = hindent(original_source)
hindent_outputs[source_file] = (original_source, formatted_source)
diffs = {filename: diff(original, formatted)
for (filename, (original, formatted)) in hindent_outputs.items()}
incorrect_formatting = False
for filename, diff in diffs.items():
if diff:
incorrect_formatting = True
print('Incorrect formatting in', filename)
print('=' * 10)
print(diff)
if incorrect_formatting:
sys.exit(1)
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