Commit 07fda863 authored by Adam Vogt's avatar Adam Vogt

Merge branch 'master' of https://github.com/gibiansky/IHaskell

parents 5c10b216 9c0883ff
...@@ -48,6 +48,7 @@ data-files: ...@@ -48,6 +48,7 @@ data-files:
library library
hs-source-dirs: src hs-source-dirs: src
build-depends: base ==4.6.*, build-depends: base ==4.6.*,
HTTP,
base64-bytestring >= 1.0, base64-bytestring >= 1.0,
process >= 1.1, process >= 1.1,
hlint, hlint,
...@@ -88,6 +89,7 @@ library ...@@ -88,6 +89,7 @@ library
IHaskell.Eval.Lint IHaskell.Eval.Lint
IHaskell.Eval.Parser IHaskell.Eval.Parser
IHaskell.Eval.Stdin IHaskell.Eval.Stdin
IHaskell.Eval.Hoogle
IHaskell.IPython IHaskell.IPython
IHaskell.Message.Parser IHaskell.Message.Parser
IHaskell.Message.UUID IHaskell.Message.UUID
...@@ -111,6 +113,7 @@ executable IHaskell ...@@ -111,6 +113,7 @@ executable IHaskell
IHaskell.Eval.Evaluate IHaskell.Eval.Evaluate
IHaskell.Eval.Parser IHaskell.Eval.Parser
IHaskell.Eval.Stdin IHaskell.Eval.Stdin
IHaskell.Eval.Hoogle
IHaskell.IPython IHaskell.IPython
IHaskell.Message.Parser IHaskell.Message.Parser
IHaskell.Message.UUID IHaskell.Message.UUID
...@@ -123,6 +126,7 @@ executable IHaskell ...@@ -123,6 +126,7 @@ executable IHaskell
-- Other library packages from which modules are imported. -- Other library packages from which modules are imported.
build-depends: base ==4.6.*, build-depends: base ==4.6.*,
HTTP,
base64-bytestring >= 1.0, base64-bytestring >= 1.0,
process >= 1.1, process >= 1.1,
hlint, hlint,
...@@ -162,6 +166,7 @@ Test-Suite hspec ...@@ -162,6 +166,7 @@ Test-Suite hspec
Ghc-Options: -threaded Ghc-Options: -threaded
Main-Is: Hspec.hs Main-Is: Hspec.hs
build-depends: base ==4.6.*, build-depends: base ==4.6.*,
HTTP,
base64-bytestring >= 1.0, base64-bytestring >= 1.0,
process >= 1.1, process >= 1.1,
hlint, hlint,
......
...@@ -2,6 +2,39 @@ ...@@ -2,6 +2,39 @@
Custom IHaskell CSS. Custom IHaskell CSS.
*/ */
/* Styles used for the Hoogle display in the pager */
.hoogle-doc {
display: block;
padding-bottom: 1.3em;
padding-left: 0.4em;
}
.hoogle-code {
display: block;
font-family: monospace;
white-space: pre;
}
.hoogle-text {
display: block;
}
.hoogle-name {
color: green;
font-weight: bold;
}
.hoogle-head {
font-weight: bold;
}
.hoogle-sub {
display: block;
margin-left: 0.4em;
}
.hoogle-package {
font-weight: bold;
font-style: italic;
}
.hoogle-module {
font-weight: bold;
}
/* Styles used for basic displays */ /* Styles used for basic displays */
.get-type { .get-type {
color: green; color: green;
......
...@@ -49,6 +49,11 @@ $([IPython.events]).on('app_initialized.NotebookApp', function(){ ...@@ -49,6 +49,11 @@ $([IPython.events]).on('app_initialized.NotebookApp', function(){
}); });
IPython.CodeCell.options_default['cm_config']['mode'] = 'haskell'; IPython.CodeCell.options_default['cm_config']['mode'] = 'haskell';
// Prevent the pager from surrounding everything with a <pre>
IPython.Pager.prototype.append_text = function (text) {
this.pager_element.find(".container").append($('<div/>').html(IPython.utils.autoLinkUrls(text)));
};
}); });
$([IPython.events]).on('shell_reply.Kernel', function() { $([IPython.events]).on('shell_reply.Kernel', function() {
......
...@@ -207,7 +207,8 @@ completePathWithExtensions extensions line = ...@@ -207,7 +207,8 @@ completePathWithExtensions extensions line =
completePathFilter (extensionIsOneOf extensions) acceptAll line "" completePathFilter (extensionIsOneOf extensions) acceptAll line ""
where where
acceptAll = const True acceptAll = const True
extensionIsOneOf exts str = any (\ext -> endswith ext str) exts extensionIsOneOf exts str = any correctEnding exts
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.
......
...@@ -64,6 +64,7 @@ import IHaskell.Types ...@@ -64,6 +64,7 @@ import IHaskell.Types
import IHaskell.Eval.Parser import IHaskell.Eval.Parser
import IHaskell.Eval.Lint import IHaskell.Eval.Lint
import IHaskell.Display import IHaskell.Display
import qualified IHaskell.Eval.Hoogle as Hoogle
import Paths_ihaskell (version) import Paths_ihaskell (version)
import Data.Version (versionBranch) import Data.Version (versionBranch)
...@@ -549,19 +550,23 @@ evalCommand _ (Directive GetHelp _) state = do ...@@ -549,19 +550,23 @@ evalCommand _ (Directive GetHelp _) state = do
} }
where out = plain $ intercalate "\n" where out = plain $ intercalate "\n"
["The following commands are available:" ["The following commands are available:"
," :extension <Extension> - enable a GHC extension." ," :extension <Extension> - Enable a GHC extension."
," :extension No<Extension> - disable a GHC extension." ," :extension No<Extension> - Disable a GHC extension."
," :type <expression> - Print expression type." ," :type <expression> - Print expression type."
," :info <name> - Print all info for a name." ," :info <name> - Print all info for a name."
," :hoogle <query> - Search for a query on Hoogle."
," :doc <ident> - Get documentation for an identifier via Hogole."
," :set <opt> - Set an option." ," :set <opt> - Set an option."
," :set no-<opt> - Unset an option." ," :set no-<opt> - Unset an option."
," :?, :help - Show this help text." ," :?, :help - Show this help text."
,"" ,""
,"Any prefix of the commands will also suffice, e.g. use :ty for :type." ,"Any prefix of the commands will also suffice, e.g. use :ty for :type."
,"" ,""
,"Options:" ,"Options:"
," lint - enable or disable linting." ," lint - enable or disable linting."
," svg - use svg output (cannot be resized)." ," svg - use svg output (cannot be resized)."
," show-types - show types of all bound names"
," show-errors - display Show instance missing errors normally."
] ]
-- This is taken largely from GHCi's info section in InteractiveUI. -- This is taken largely from GHCi's info section in InteractiveUI.
...@@ -603,6 +608,14 @@ evalCommand _ (Directive GetInfo str) state = safely state $ do ...@@ -603,6 +608,14 @@ evalCommand _ (Directive GetInfo str) state = safely state $ do
evalPager = unlines strings evalPager = unlines strings
} }
evalCommand _ (Directive SearchHoogle query) state = safely state $ do
results <- liftIO $ Hoogle.search query
return $ hoogleResults state results
evalCommand _ (Directive GetDoc query) state = safely state $ do
results <- liftIO $ Hoogle.document query
return $ hoogleResults state results
evalCommand output (Statement stmt) state = wrapExecution state $ do evalCommand output (Statement stmt) state = wrapExecution state $ do
write $ "Statement:\n" ++ stmt write $ "Statement:\n" ++ stmt
let outputter str = output $ IntermediateResult [plain str] let outputter str = output $ IntermediateResult [plain str]
...@@ -790,6 +803,21 @@ evalCommand _ (ParseError loc err) state = do ...@@ -790,6 +803,21 @@ evalCommand _ (ParseError loc err) state = do
evalPager = "" evalPager = ""
} }
hoogleResults :: KernelState -> [Hoogle.HoogleResult] -> EvalOut
hoogleResults state results = EvalOut {
evalStatus = Success,
evalResult = [],
evalState = state,
evalPager = output
}
where
fmt =
case getFrontend state of
IPythonNotebook -> Hoogle.HTML
IPythonConsole -> Hoogle.Plain
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 requested -- as many characters as requested
readChars :: Handle -> String -> Int -> IO String readChars :: Handle -> String -> Int -> IO String
......
{-# LANGUAGE NoImplicitPrelude, FlexibleInstances, OverloadedStrings #-}
module IHaskell.Eval.Hoogle (
search,
document,
render,
OutputFormat(..),
HoogleResult
) where
import ClassyPrelude hiding (span, div)
import Text.Printf
import Network.HTTP
import Data.Aeson
import Data.String.Utils
import Data.List (elemIndex, (!!), last)
import Control.Monad (guard)
import qualified Data.ByteString.Lazy.Char8 as Char
import IHaskell.IPython
-- | Types of formats to render output to.
data OutputFormat
= Plain -- ^ Render to plain text.
| HTML -- ^ Render to HTML.
data HoogleResponse = HoogleResponse {
location :: String,
self :: String,
docs :: String
}
deriving (Eq, Show)
data HoogleResult
= SearchResult HoogleResponse
| DocResult HoogleResponse
| NoResult String
deriving Show
instance FromJSON [HoogleResponse] where
parseJSON (Object obj) = do
results <- obj .: "results"
mapM parseJSON results
parseJSON _ = fail "Expected object with 'results' field."
instance FromJSON HoogleResponse where
parseJSON (Object obj) =
HoogleResponse <$>
obj .: "location" <*>
obj .: "self" <*>
obj .: "docs"
parseJSON _ = fail "Expected object with fields: location, self, docs"
-- | Query Hoogle for the given string.
-- This searches Hoogle using the internet. It returns either an error
-- message or the successful JSON result.
query :: String -> IO (Either String String)
query str = do
let request = getRequest $ queryUrl str
response <- simpleHTTP request
return $ case response of
Left err -> Left $ show err
Right resp -> Right $ rspBody resp
where
queryUrl :: String -> String
queryUrl = printf "http://www.haskell.org/hoogle/?hoogle=%s&mode=json" . urlEncode
-- | Search for a query on Hoogle.
-- Return all search results.
search :: String -> IO [HoogleResult]
search string = do
response <- query string
return $ case response of
Left err -> [NoResult err]
Right json ->
case eitherDecode $ Char.pack json of
Left err -> [NoResult err]
Right results ->
case map SearchResult results of
[] -> [NoResult "no matching identifiers found."]
res -> res
-- | Look up an identifier on Hoogle.
-- Return documentation for that identifier. If there are many
-- identifiers, include documentation for all of them.
document :: String -> IO [HoogleResult]
document string = do
matchingResults <- filter matches <$> search string
let results = map toDocResult matchingResults
return $ case results of
[] -> [NoResult "no matching identifiers found."]
res -> res
where
matches (SearchResult resp) =
case split " " $ self resp of
name:_ -> strip string == strip name
_ -> False
matches _ = False
toDocResult (SearchResult resp) = DocResult resp
-- | Render a Hoogle search result into an output format.
render :: OutputFormat -> HoogleResult -> String
render Plain = renderPlain
render HTML = renderHtml
-- | Render a Hoogle result to plain text.
renderPlain :: HoogleResult -> String
renderPlain (NoResult res) =
"No response available: " ++ res
renderPlain (SearchResult resp) =
printf "%s\nURL: %s\n%s"
(self resp)
(location resp)
(docs resp)
renderPlain (DocResult resp) =
printf "%s\nURL: %s\n%s"
(self resp)
(location resp)
(docs resp)
-- | Render a Hoogle result to HTML.
renderHtml :: HoogleResult -> String
renderHtml (NoResult resp) =
printf "<span class='err-msg'>No result: %s</span>" resp
renderHtml (DocResult resp) =
renderSelf (self resp) (location resp)
++
renderDocs (docs resp)
renderHtml (SearchResult resp) =
renderSelf (self resp) (location resp)
++
renderDocs (docs resp)
renderSelf :: String -> String -> String
renderSelf string loc
| startswith "package" string
= pkg ++ " " ++ span "hoogle-package" (link loc $ extractPackage string)
| startswith "module" string
= let package = extractPackageName loc in
mod ++ " " ++
span "hoogle-module" (link loc $ extractModule string) ++
packageSub package
| otherwise
= let [name, args] = split "::" string
package = extractPackageName loc
modname = extractModuleName loc in
span "hoogle-name" (unicodeReplace $
link loc (strip name) ++
" :: " ++
strip args)
++ packageAndModuleSub package modname
where
extractPackage = strip . replace "package" ""
extractModule = strip . replace "module" ""
pkg = span "hoogle-head" "package"
mod = span "hoogle-head" "module"
unicodeReplace :: String -> String
unicodeReplace =
replace "forall" "&#x2200;" .
replace "=>" "&#x21D2;" .
replace "->" "&#x2192;" .
replace "::" "&#x2237;"
packageSub Nothing = ""
packageSub (Just package) =
span "hoogle-sub" $
"(" ++ pkg ++ " " ++ span "hoogle-package" package ++ ")"
packageAndModuleSub Nothing _ = ""
packageAndModuleSub (Just package) Nothing = packageSub (Just package)
packageAndModuleSub (Just package) (Just modname) =
span "hoogle-sub" $
"(" ++ pkg ++ " " ++ span "hoogle-package" package ++
", " ++ mod ++ " " ++ span "hoogle-module" modname ++ ")"
renderDocs :: String -> String
renderDocs doc =
let groups = groupBy bothAreCode $ lines doc
nonull = filter (not . null . strip)
bothAreCode s1 s2 =
startswith ">" (strip s1) &&
startswith ">" (strip s2)
isCode (s:_) = startswith ">" $ strip s
makeBlock lines =
if isCode lines
then div "hoogle-code" $ unlines $ nonull lines
else div "hoogle-text" $ unlines $ nonull lines
in
div "hoogle-doc" $ unlines $ map makeBlock groups
extractPackageName :: String -> Maybe String
extractPackageName link = do
let pieces = split "/" link
archiveLoc <- elemIndex "archive" pieces
latestLoc <- elemIndex "latest" pieces
guard $ latestLoc - archiveLoc == 2
return $ pieces !! (latestLoc - 1)
extractModuleName :: String -> Maybe String
extractModuleName link = do
let pieces = split "/" link
guard $ not $ null pieces
let html = last pieces
mod = replace "-" "." $ takeWhile (/= '.') html
return mod
div :: String -> String -> String
div = printf "<div class='%s'>%s</div>"
span :: String -> String -> String
span = printf "<span class='%s'>%s</span>"
link :: String -> String -> String
link = printf "<a target='_blank' href='%s'>%s</a>"
...@@ -64,6 +64,8 @@ data DirectiveType ...@@ -64,6 +64,8 @@ data DirectiveType
| SetOpt -- ^ Set various options. | SetOpt -- ^ Set various options.
| ShellCmd -- ^ Execute a shell command. | ShellCmd -- ^ Execute a shell command.
| GetHelp -- ^ General help via ':?' or ':help'. | GetHelp -- ^ General help via ':?' or ':help'.
| SearchHoogle -- ^ Search for something via Hoogle.
| GetDoc -- ^ Get documentation for an identifier via Hoogle.
deriving (Show, Eq) deriving (Show, Eq)
-- | Unlocate something - drop the position. -- | Unlocate something - drop the position.
...@@ -238,6 +240,8 @@ parseDirective (':':directive) line = case find rightDirective directives of ...@@ -238,6 +240,8 @@ parseDirective (':':directive) line = case find rightDirective directives of
directives = directives =
[(GetType, "type") [(GetType, "type")
,(GetInfo, "info") ,(GetInfo, "info")
,(SearchHoogle, "hoogle")
,(GetDoc, "documentation")
,(SetExtension, "extension") ,(SetExtension, "extension")
,(LoadFile, "load") ,(LoadFile, "load")
,(SetOpt, "set") ,(SetOpt, "set")
......
...@@ -205,10 +205,13 @@ installPipDependencies = withTmpDir $ \tmpDir -> ...@@ -205,10 +205,13 @@ installPipDependencies = withTmpDir $ \tmpDir ->
mapM_ (installDependency tmpDir) mapM_ (installDependency tmpDir)
[ [
("pyzmq", "14.0.1") ("pyzmq", "14.0.1")
, ("setuptools", "2.0.2") -- This cannot go first in the dependenc list, because its setup.py is broken.
, ("MarkupSafe", "0.18") -- Neither can this
, ("tornado","3.1.1") , ("tornado","3.1.1")
, ("jinja2","2.7.1") , ("jinja2","2.7.1")
-- The following cannot go first in the dependency list, because
-- their setup.py are broken and require the directory to exist
-- already.
, ("MarkupSafe", "0.18")
--, ("setuptools", "2.0.2")
] ]
where where
installDependency :: FilePath -> (Text, Text) -> Sh () installDependency :: FilePath -> (Text, Text) -> Sh ()
......
...@@ -20,6 +20,7 @@ module IHaskell.Types ( ...@@ -20,6 +20,7 @@ module IHaskell.Types (
KernelState(..), KernelState(..),
LintStatus(..), LintStatus(..),
Width, Height, Width, Height,
FrontendType(..),
defaultKernelState, defaultKernelState,
extractPlain extractPlain
) where ) where
...@@ -76,6 +77,7 @@ instance ToJSON Profile where ...@@ -76,6 +77,7 @@ instance ToJSON Profile where
data KernelState = KernelState data KernelState = KernelState
{ getExecutionCounter :: Int, { 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.
getFrontend :: FrontendType,
useSvg :: Bool, useSvg :: Bool,
useShowErrors :: Bool, useShowErrors :: Bool,
useShowTypes :: Bool useShowTypes :: Bool
...@@ -86,16 +88,23 @@ defaultKernelState :: KernelState ...@@ -86,16 +88,23 @@ defaultKernelState :: KernelState
defaultKernelState = KernelState defaultKernelState = KernelState
{ getExecutionCounter = 1, { getExecutionCounter = 1,
getLintStatus = LintOn, getLintStatus = LintOn,
getFrontend = IPythonConsole,
useSvg = True, useSvg = True,
useShowErrors = False, useShowErrors = False,
useShowTypes = False useShowTypes = False
} }
data FrontendType
= IPythonConsole
| IPythonNotebook
deriving (Show, Eq, Read)
-- | Initialization information for the kernel. -- | Initialization information for the kernel.
data InitInfo = InitInfo { data InitInfo = InitInfo {
extensions :: [String], -- ^ Extensions to enable at start. extensions :: [String], -- ^ Extensions to enable at start.
initCells :: [String], -- ^ Code blocks to run before start. initCells :: [String], -- ^ Code blocks to run before start.
initDir :: String -- ^ Which directory this kernel should pretend to operate in. initDir :: String, -- ^ Which directory this kernel should pretend to operate in.
frontend :: FrontendType -- ^ What frontend this serves.
} }
deriving (Show, Read) deriving (Show, Read)
......
...@@ -145,7 +145,7 @@ ihaskell (Args Console flags) = showingHelp Console flags $ do ...@@ -145,7 +145,7 @@ ihaskell (Args Console flags) = showingHelp Console flags $ do
setupIPython setupIPython
flags <- addDefaultConfFile flags flags <- addDefaultConfFile flags
info <- initInfo flags info <- initInfo IPythonConsole flags
runConsole info runConsole info
ihaskell (Args (View (Just fmt) (Just name)) []) = ihaskell (Args (View (Just fmt) (Just name)) []) =
...@@ -160,7 +160,7 @@ ihaskell (Args Notebook flags) = showingHelp Notebook flags $ do ...@@ -160,7 +160,7 @@ ihaskell (Args Notebook flags) = showingHelp Notebook flags $ do
flags <- addDefaultConfFile flags flags <- addDefaultConfFile flags
undirInfo <- initInfo flags undirInfo <- initInfo IPythonNotebook flags
curdir <- getCurrentDirectory curdir <- getCurrentDirectory
let info = undirInfo { initDir = curdir } let info = undirInfo { initDir = curdir }
...@@ -198,10 +198,10 @@ showingHelp mode flags act = ...@@ -198,10 +198,10 @@ showingHelp mode flags act =
chooseMode UpdateIPython = update chooseMode UpdateIPython = update
-- | Parse initialization information from the flags. -- | Parse initialization information from the flags.
initInfo :: [Argument] -> IO InitInfo initInfo :: FrontendType -> [Argument] -> IO InitInfo
initInfo [] = return InitInfo { extensions = [], initCells = [], initDir = "."} initInfo front [] = return InitInfo { extensions = [], initCells = [], initDir = ".", frontend = front }
initInfo (flag:flags) = do initInfo front (flag:flags) = do
info <- initInfo flags info <- initInfo front flags
case flag of case flag of
Extension ext -> return info { extensions = ext:extensions info } Extension ext -> return info { extensions = ext:extensions info }
ConfFile filename -> do ConfFile filename -> do
...@@ -227,6 +227,8 @@ runKernel profileSrc initInfo = do ...@@ -227,6 +227,8 @@ runKernel profileSrc initInfo = do
-- Create initial state in the directory the kernel *should* be in. -- Create initial state in the directory the kernel *should* be in.
state <- initialKernelState state <- initialKernelState
modifyMVar_ state $ \kernelState -> return $
kernelState { getFrontend = frontend initInfo }
-- Receive and reply to all messages on the shell socket. -- Receive and reply to all messages on the shell socket.
interpret True $ do interpret True $ do
......
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