Commit 87cf416d authored by Adam Vogt's avatar Adam Vogt

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

parents 7cda8c7a 144fa549
...@@ -40,47 +40,49 @@ category: Development ...@@ -40,47 +40,49 @@ category: Development
build-type: Simple build-type: Simple
-- Constraint on the version of Cabal needed to build this package. -- Constraint on the version of Cabal needed to build this package.
cabal-version: >=1.8 cabal-version: >=1.16
data-files: data-files:
profile/profile.tar profile/profile.tar
library library
hs-source-dirs: src hs-source-dirs: src
build-depends: base ==4.6.*, default-language: Haskell2010
cereal == 0.3.*, build-depends:
HTTP, base ==4.6.*,
base64-bytestring >= 1.0,
process >= 1.1,
hlint,
cmdargs >= 0.10,
tar,
ipython-kernel,
ghc-parser,
unix >= 2.6,
hspec,
aeson >=0.6, aeson >=0.6,
MissingH >=1.2, base64-bytestring >=1.0,
classy-prelude >=0.7,
bytestring >=0.10, bytestring >=0.10,
cereal ==0.3.*,
classy-prelude >=0.7,
cmdargs >=0.10,
containers >=0.5, containers >=0.5,
directory -any,
filepath -any,
ghc ==7.6.*, ghc ==7.6.*,
ghc-parser -any,
ghc-paths ==0.1.*, ghc-paths ==0.1.*,
haskeline -any,
here -any,
hlint -any,
hspec -any,
HTTP -any,
HUnit -any,
ipython-kernel -any,
MissingH >=1.2,
mtl >=2.1,
parsec -any,
process >=1.1,
random >=1.0, random >=1.0,
shelly >=1.3,
split >= 0.2, split >= 0.2,
utf8-string,
strict >=0.3, strict >=0.3,
shelly >=1.3, system-argv0 -any,
system-argv0, system-filepath -any,
directory, tar -any,
here, transformers -any,
system-filepath, unix >= 2.6,
filepath, utf8-string -any
mtl >= 2.1,
transformers,
haskeline,
HUnit,
parsec
exposed-modules: IHaskell.Display exposed-modules: IHaskell.Display
IHaskell.Eval.Completion IHaskell.Eval.Completion
...@@ -121,81 +123,84 @@ executable IHaskell ...@@ -121,81 +123,84 @@ executable IHaskell
extensions: DoAndIfThenElse extensions: DoAndIfThenElse
-- Other library packages from which modules are imported. -- Other library packages from which modules are imported.
build-depends: base ==4.6.*, default-language: Haskell2010
cereal == 0.3.*, build-depends:
HTTP, base ==4.6.*,
base64-bytestring >= 1.0,
process >= 1.1,
hlint,
cmdargs >= 0.10,
tar,
ghc-parser,
ipython-kernel,
unix >= 2.6,
hspec,
aeson >=0.6, aeson >=0.6,
MissingH >=1.2, base64-bytestring >=1.0,
classy-prelude >=0.7,
bytestring >=0.10, bytestring >=0.10,
cereal ==0.3.*,
classy-prelude >=0.7,
cmdargs >=0.10,
containers >=0.5, containers >=0.5,
directory -any,
filepath -any,
ghc ==7.6.*, ghc ==7.6.*,
ghc-parser -any,
ghc-paths ==0.1.*, ghc-paths ==0.1.*,
haskeline -any,
here -any,
hlint -any,
hspec -any,
HTTP -any,
HUnit -any,
ipython-kernel -any,
MissingH >=1.2,
mtl >=2.1,
parsec -any,
process >=1.1,
random >=1.0, random >=1.0,
shelly >=1.3,
split >= 0.2, split >= 0.2,
utf8-string,
strict >=0.3, strict >=0.3,
shelly >=1.3, system-argv0 -any,
system-argv0, system-filepath -any,
directory, tar -any,
here, transformers -any,
system-filepath, unix >= 2.6,
filepath, utf8-string -any
mtl >= 2.1,
transformers,
haskeline,
HUnit,
parsec
Test-Suite hspec Test-Suite hspec
hs-source-dirs: src hs-source-dirs: src
Type: exitcode-stdio-1.0 Type: exitcode-stdio-1.0
Ghc-Options: -threaded Ghc-Options: -threaded
Main-Is: Hspec.hs Main-Is: Hspec.hs
build-depends: base ==4.6.*, default-language: Haskell2010
cereal == 0.3.*, build-depends:
HTTP, base ==4.6.*,
base64-bytestring >= 1.0,
process >= 1.1,
hlint,
cmdargs >= 0.10,
tar,
ghc-parser,
ipython-kernel,
unix >= 2.6,
hspec,
aeson >=0.6, aeson >=0.6,
MissingH >=1.2, base64-bytestring >=1.0,
classy-prelude >=0.7,
bytestring >=0.10, bytestring >=0.10,
cereal ==0.3.*,
classy-prelude >=0.7,
cmdargs >=0.10,
containers >=0.5, containers >=0.5,
directory -any,
filepath -any,
ghc ==7.6.*, ghc ==7.6.*,
ghc-parser -any,
ghc-paths ==0.1.*, ghc-paths ==0.1.*,
haskeline -any,
here -any,
hlint -any,
hspec -any,
HTTP -any,
HUnit -any,
ipython-kernel -any,
MissingH >=1.2,
mtl >=2.1,
parsec -any,
process >=1.1,
random >=1.0, random >=1.0,
shelly >=1.3,
split >= 0.2, split >= 0.2,
utf8-string,
strict >=0.3, strict >=0.3,
shelly >=1.3, system-argv0 -any,
system-argv0, system-filepath -any,
directory, tar -any,
here, transformers -any,
system-filepath, unix >= 2.6,
filepath, utf8-string -any
mtl >= 2.1,
transformers,
haskeline,
HUnit,
setenv,
parsec
extensions: DoAndIfThenElse extensions: DoAndIfThenElse
OverloadedStrings OverloadedStrings
......
{
"metadata": {
"language": "haskell",
"name": ""
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- First of all, we can evaluate simple expressions.\n",
"3 + 5\n",
"\"Hello, \" ++ \"World!\""
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"text": [
"8"
]
},
{
"metadata": {},
"output_type": "display_data",
"text": [
"\"Hello, World!\""
]
}
],
"prompt_number": 1
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- Unlike in GHCi, we can have multi-line expressions.\n",
"concat [\n",
" \"Hello,\",\n",
" \", \",\n",
" \"World!\"\n",
" ] :: String\n",
" \n",
"-- We can also have normal Haskell declarations, without `let`.\n",
"-- As long as you group type signatures and declarations together,\n",
"-- you can use pattern matching and add type signatures.\n",
"thing :: String -> Int -> Int\n",
"thing \"no\" _ = 100\n",
"thing str int = int + length str\n",
"\n",
"thing \"no\" 10\n",
"thing \"ah\" 10"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"text": [
"\"Hello,, World!\""
]
},
{
"metadata": {},
"output_type": "display_data",
"text": [
"100"
]
},
{
"metadata": {},
"output_type": "display_data",
"text": [
"12"
]
}
],
"prompt_number": 2
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- We can also do IO.\n",
"print \"What's going on?\""
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"text": [
"\"What's going on?\""
]
}
],
"prompt_number": 3
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- We can disable extensions.\n",
":ext NoEmptyDataDecls\n",
"data Thing"
],
"language": "python",
"metadata": {},
"outputs": [
{
"html": [
"<span style='color: red; font-style: italic;'>`Thing' has no constructors<br/> (-XEmptyDataDecls permits this)</span>"
],
"metadata": {},
"output_type": "display_data",
"text": [
"`Thing' has no constructors\n",
" (-XEmptyDataDecls permits this)"
]
}
],
"prompt_number": 4
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- And enable extensions.\n",
":ext EmptyDataDecls\n",
"data Thing"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 5
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- Various data declarations work fine.\n",
"data One\n",
" = A String\n",
" | B Int\n",
" deriving Show\n",
"\n",
"print [A \"Hello\", B 10]"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"text": [
"[A \"Hello\",B 10]"
]
}
],
"prompt_number": 6
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- We can look at types like in GHCi.\n",
":ty 3 + 3"
],
"language": "python",
"metadata": {},
"outputs": [
{
"html": [
"<span style='font-weight: bold; color: green;'>forall a. Num a => a</span>"
],
"metadata": {},
"output_type": "display_data",
"text": [
"forall a. Num a => a"
]
}
],
"prompt_number": 7
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- And we can inspect info of things!\n",
":info Integral"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"text": [
"class (Real a, Enum a) => Integral a where\n",
" quot :: a -> a -> a\n",
" rem :: a -> a -> a\n",
" div :: a -> a -> a\n",
" mod :: a -> a -> a\n",
" quotRem :: a -> a -> (a, a)\n",
" divMod :: a -> a -> (a, a)\n",
" toInteger :: a -> Integer\n",
" \t-- Defined in `GHC.Real'\n",
"instance Integral Integer -- Defined in `GHC.Real'\n",
"instance Integral Int -- Defined in `GHC.Real'"
]
}
],
"prompt_number": 8
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- Results are printed as we go, even from a single expression.\n",
"import Control.Monad\n",
"import Control.Concurrent\n",
"\n",
"forM_ [1..5] $ \\x -> do\n",
" print x\n",
" threadDelay $ 100 * 1000"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"text": [
"1\n",
"2\n",
"3\n",
"4\n",
"5"
]
}
],
"prompt_number": 9
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- We can display Maybes fancily for Show-able types.\n",
"Just ()\n",
"Nothing\n",
"\n",
"-- But it dies if it's not showable.\n",
"data NoShow = X Int\n",
"Just (X 3)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"html": [
"<span style='color: green; font-weight: bold;'>Just</span><span style='font-family: monospace;'>()</span>"
],
"metadata": {},
"output_type": "display_data",
"text": [
"Just ()"
]
},
{
"html": [
"<span style='color: red; font-weight: bold;'>Nothing</span>"
],
"metadata": {},
"output_type": "display_data",
"text": [
"Nothing"
]
},
{
"html": [
"<span style='color: red; font-style: italic;'>No instance for (Show NoShow)<br/> arising from a use of `print'<br/>Possible fix:<br/> add an instance declaration for (Show NoShow)</span>"
],
"metadata": {},
"output_type": "display_data",
"text": [
"No instance for (Show NoShow)\n",
" arising from a use of `print'\n",
"Possible fix:\n",
" add an instance declaration for (Show NoShow)"
]
}
],
"prompt_number": 10
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- Aeson JSON data types are displayed nicely.\n",
":ext OverloadedStrings\n",
"\n",
"import Data.Aeson\n",
"\n",
"data Coord = Coord { x :: Double, y :: Double }\n",
"instance ToJSON Coord where\n",
" toJSON (Coord x y) = object [\"x\" .= x, \"y\" .= y]\n",
"\n",
"Null\n",
"Bool True\n",
"toJSON (Coord 3 2)"
],
"language": "python",
"metadata": {},
"outputs": [
{
"html": [
"<div class=\"highlight-code\" id=\"javascript\">null</div>"
],
"metadata": {},
"output_type": "display_data",
"text": [
"null"
]
},
{
"html": [
"<div class=\"highlight-code\" id=\"javascript\">true</div>"
],
"metadata": {},
"output_type": "display_data",
"text": [
"true"
]
},
{
"html": [
"<div class=\"highlight-code\" id=\"javascript\">{\n",
" \"x\": 3.0,\n",
" \"y\": 2.0\n",
"}</div>"
],
"metadata": {},
"output_type": "display_data",
"text": [
"{\n",
" \"x\": 3.0,\n",
" \"y\": 2.0\n",
"}"
]
}
],
"prompt_number": 11
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- Small bits of HTML generated via Blaze are displayed.\n",
"import Prelude hiding (div, id)\n",
"import Text.Blaze.Html4.Strict hiding (map, style)\n",
"import Text.Blaze.Html4.Strict.Attributes\n",
"\n",
"div ! style \"color: red\" $ do\n",
" p \"This is an example of BlazeMarkup syntax.\"\n",
" b \"Hello\"\n",
" \n",
"forM [1..5] $ \\size -> do\n",
" let s = toValue $ size * 80\n",
" img ! src \"/static/base/images/ipynblogo.png\" ! width s"
],
"language": "python",
"metadata": {},
"outputs": [
{
"html": [
"<div style=\"color: red\">\n",
" <p>\n",
" This is an example of BlazeMarkup syntax.\n",
" </p>\n",
" <b>\n",
" Hello\n",
" </b>\n",
"</div>\n"
],
"metadata": {},
"output_type": "display_data",
"text": [
"<div style=\"color: red\">\n",
" <p>\n",
" This is an example of BlazeMarkup syntax.\n",
" </p>\n",
" <b>\n",
" Hello\n",
" </b>\n",
"</div>"
]
},
{
"html": [
"<img src=\"/static/base/images/ipynblogo.png\" width=\"80\">\n",
"<img src=\"/static/base/images/ipynblogo.png\" width=\"160\">\n",
"<img src=\"/static/base/images/ipynblogo.png\" width=\"240\">\n",
"<img src=\"/static/base/images/ipynblogo.png\" width=\"320\">\n",
"<img src=\"/static/base/images/ipynblogo.png\" width=\"400\">\n"
],
"metadata": {},
"output_type": "display_data",
"text": [
"<img src=\"/static/base/images/ipynblogo.png\" width=\"80\">\n",
"<img src=\"/static/base/images/ipynblogo.png\" width=\"160\">\n",
"<img src=\"/static/base/images/ipynblogo.png\" width=\"240\">\n",
"<img src=\"/static/base/images/ipynblogo.png\" width=\"320\">\n",
"<img src=\"/static/base/images/ipynblogo.png\" width=\"400\">"
]
}
],
"prompt_number": 12
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- We can draw diagrams, right in the notebook.\n",
":extension NoMonomorphismRestriction\n",
"import Diagrams.Prelude\n",
"\n",
"-- By Brent Yorgey\n",
"-- Draw a Sierpinski triangle!\n",
"sierpinski 1 = eqTriangle 1\n",
"sierpinski n = s\n",
" ===\n",
" (s ||| s) # centerX\n",
" where s = sierpinski (n-1)\n",
"\n",
"-- The `diagram` function is used to display them in the notebook.\n",
"diagram $ sierpinski 4\n",
" # centerXY\n",
" # fc black\n",
" `atop` square 10\n",
" # fc white"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"png": "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"
}
],
"prompt_number": 13
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- We can draw small charts in the notebook.\n",
"-- This example is taken from the haskell-chart documentation.\n",
"import Graphics.Rendering.Chart \n",
"import Data.Default.Class\n",
"import Control.Lens\n",
"\n",
"let values = [\n",
" (\"Mexico City\" , 19.2, 0),\n",
" (\"Mumbai\" , 12.9, 10), \n",
" (\"Sydney\" , 4.3, 0),\n",
" (\"London\" , 8.3, 0), \n",
" (\"New York\" , 8.2, 25)]\n",
" \n",
"pitem (s, v, o) = pitem_value .~ v\n",
" $ pitem_label .~ s\n",
" $ pitem_offset .~ o\n",
" $ def \n",
"\n",
"-- Convert to a renderable in order to display it.\n",
"toRenderable \n",
" $ pie_title .~ \"Relative Population\"\n",
" $ pie_plot . pie_data .~ map pitem values\n",
" $ def"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"png": "iVBORw0KGgoAAAANSUhEUgAAAyAAAAJYCAIAAAAVFBUnAAAABmJLR0QA/wD/AP+gvaeTAAAgAElEQVR4nOzdd3xUVf7/8dekEHokSCgqLE1kERBX6QhBMAIWBAR2IRFs+BUQQVgVcLFgWxBcRXYXo8viooCFIgJKKNJ7UQIIBkIvoSQQUiaTub8/ZuXHQibcJDNzJzPv58M/NPnMve9RmXxyzrnn2AzDQEREREQ8J8TqACIiIiKBRg2WiIiIiIepwRIRERHxMDVYIiIiIh6mBktERETEw8KsDiAi3tW/f/9ffvnl8j+Gh4fXq1evV69eDz300HVf27Jly7y8vEWLFlWpUsVdzYkTJzIzM6tVq1auXDmgfv36hmGsWrWqRo0ank0eHR3dunXrYcOGlS9fvphXdsfa9ysigcSmbRpEAtudd965ffv2a7/+6aefDhw4sODXhoeHOxyOY8eOFdA9dO7cOTExcc6cOY8++ihgs9mAQ4cO1axZs3jB80/epk2b1atXu+7icda+XxEJJJoiFAkKL7300vr169evXz937tx69eoB7777rjdutGXLli1btlSrVs1TF3z++efXr1+/fPny//u//wPWrl27evVqT128+Dz+fkUkMKjBEgkKderUadmyZcuWLbt37/7iiy8CBw8edA1gp6WlDR48+Lbbbvvd737Xs2fPvXv35nuFQ4cO/fGPf7ztttsaNGgQFxc3b948YOjQoTt37gQmTpz44YcfAiNHjnzhhRfOnz8/bty4Dh06jB8/3vXyPXv2dOjQITY21m63m78p8Lvf/a5ly5YxMTFTpkxx9TGu4ry8vPHjxzdt2rR8+fINGzYcMWJEVlaW68odOnTo3r37qlWrevbsWa9evf79+x8/ftx1Ndd3O3TokJmZ6fpKTExMhw4dTp06Vcz3W0AkwOl0durUqVOnThs3buzZs2f9+vWfeeaZfEcWRSRAGCIS0Jo1awZMmzbt8lfi4+OBNm3aGIaRm5vbpEkToHLlyvXr1wcqVaq0a9cuV2VYWBhw7NgxwzDq1KkD1K5du02bNiEhITabbd26df3797/hhhuAhg0bvvXWW8ZvSw4OHTr0xRdfADVq1HBd6s033wR69ep13Ztelfz99993/WN6enqZMmWAmTNnGobRr18/172aNGkSEhICtG3b1ul0nj59GoiIiChbtmxUVJSrpnbt2llZWYZhuL4LXLhwwXVZ1xxfSkpKMd9vAZEMw8jLy3N9q3LlyrfffntoaChQtWrVnJwcL/w3FxHrqcESCXCuNiU6Orp+/fr169d39QeNGjXav3+/YRgzZswAGjRokJ2dbRiGa1XW008/7Xrt5YYjOTm5Q4cO/fr1c329VatWwOTJkw3D6NSpEzBnzhzXty43HJmZmRUrVgS2bdt2+SXffvvtdW96VfK2bds+++yzjz/+eK1atVydU3Jy8rZt21w32rhxo2EY+/fvd/Vec+fOvdxCueKtWLHCFSMhIcEw3WAV4f0WEMm4osH68MMPDcPYtWuX6x93797t4f/eIuIfNEUoEhTy8vLsdnt2dnZ6ejpgt9tdP/J37NgBOJ3O+Pj4Pn36uJ7aW758+VUvr1Onzueff96+ffv+/fvfcccd69evd12kgDuWKVOmd+/ewKJFi1JTUzdu3BgdHX3//febv6nLmjVrpk6d+umnnx46dKh69er/+te/6tSp4wrQuHHj5s2bA/Xq1Wvfvj2wbt0616tsNtugQYOADh06xMTEAElJSeb/dRXh/V43ksuDDz4INGrUyNXpZmRkmE8lIiWItmkQCQpvv/32U089BZw7d65NmzZ79+6dNm3ae++95/oBHxoa6nQ6gRo1avTq1evyzNplycnJrVq1Sk1NbdOmTbdu3SpXruyuH7pSfHx8QkLCd999d/PNNzudzn79+rmGiEze1GX48OF9+vQJCQmJjo6++eabXZNrrhVUrh7FxfX3l1dWlS1b1jWABERGRgKue11mGAaQm5tr5PckdRHe73UjubiG0wDXHKKIBCo1WCLBJSoqqlOnTnv37t29ezfQoEEDICwsbM6cOTabLSUlZdeuXdfuAvXll1+mpqZ269Zt4cKFhmE0bNjQzL3atm1bp06djRs3RkREAI899pjr6yZv6lKrVq0WLVpc9cXGjRsDGzZsSE9Pj4yMzMvLW7Zs2eWvA5cuXdq2bdudd96Zm5vr+pbr2cmoqKiIiIicnJxjx45VrFhx69at+d60CO/3upFEJKjoVyiRoFOpUiV+G1np06ePa4F5v379xo4d26ZNmwcffPDa2TTXUvT9+/d/+eWXzz77rGtSzzUmVLZsWSAhIWHRokVXvcpms8XFxTmdzpUrVzZt2rRp06aur5u8aQHuu+8+V/PUsGHDYcOG3XrrrampqTfddFNcXNzlmm7dug0aNKhevXrHjh2LjIz84x//CISGhv7+978HOnToMGjQoJ49e+a7pVYR3q+ZSCISRKxdAiYi3nbtU4Qff/wxULFixdOnTxuGsXr16ttuu831gRAWFjZu3Li8vDxX5eVF37m5uffee6+rF2nTpo1rS6ouXboYhvH111+7JuPi4+ON/32qzjCM5ORk11cmTZp0ZaoCbnpV8stPEV7lyJEj99xzz+WPsttvvz0pKcn4bRl7WFjYyy+/XLp0aSA6OjoxMfHyC9etW+caLQsPD3/99derVq3KNYvci/Z+3UUyrljkfu7cOddXXFOimzZtKvx/UhEpAbSTu4jgdDoPHDiQnp7esGFD1whNvo4dO2az2a7d5dzhcJw9e7Zs2bIVKlTw+E0LdurUqV9//bVmzZq33HKL6yupqanR0dFhYWG5ubmZmZmHDx+uX7++a+XWlQ4dOlS5cuWCT90p2vu9NpKIBCE1WCISUK5ssKzOIiLBS2uwRERERDxMTxGKSECJiorav3+/l06DFhExSVOEIiIiIh6mKUIRERERD1ODJSIiIuJharBEREREPEwNloiIiIiHqcESERER8TA1WCIiIiIepgZLRERExMPUYImIiIh4mBosEREREQ9TgyUiIiLiYWqwRERERDxMDZaIiIiIh6nBEhEREfEwNVgiIiIiHqYGS0RERMTD1GCJiIiIeJgaLBEREREPU4MlIiIi4mFqsEREREQ8TA2WiIiIiIepwRIRERHxMDVYIiIiIh6mBktERETEw9RgiYiIiHiYGiwRERERD1ODJSIiIuJharBEREREPEwNloiIiIiHqcESERER8TA1WCIiIiIepgZLRERExMPUYImIiIh4mBosEREREQ9TgyUiIiLiYWqwRETIzMxMSUkp8st3797tuSwiEgjUYImIsG3btqeeeqrIL+/Ro4cHw4hIAFCDJSKSD6fTOWLEiFq1avXp0ycpKQn47rvvxowZ06FDh9q1a3/11VdOp3P48OH169cfNmyYYRjXvuSqeqvfkIj4VJjVAURE/NGmTZuSkpK2b99++PDhV1555ZtvvklPT581a9aaNWt27tz56quvRkZG/vzzz2vWrJk+fXp6evq1L+nVq9eV9b169bL6PYmI72gES0QkH0uWLBkwYEBUVNQdd9xx8ODBS5cuAQ899FD16tVj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NHSo9uzhVBZChNutW2/V5Mlq1szuKEAw4y5C4Ia0bq1PP1XfvoqMtDsKcMOiojR4sDZupF0BN4ozWIA5Nm3S8OHatEkXL9odBcg5r1eNGmnSJFWvbncUICRQsADTpKVpzhw984wOH2aOA4KGy6VSpfT88+raVW6uagAmoWABJtu3TyNHKjlZ587ZHQW4lshIdeum0aPZzA6YjIIF5Io1azRmjL75hiuGCFBer2JjNX686te3OwoQiihYQG5JT9eyZRozRjt36tIlu9MAf/B4VK2anntOrVrJ5bI7DRCiKFhA7kpNVWKiJk7U8eNszILNXC4VKaLhwzVgAPe9ArmLggVYYc8ejRunhQt19qzdUeBILpeiotStm554QhUr2p0GcAAKFmCdL77QhAlas4b977BUZKTatdNTT6lqVbujAI5BwQKs9uWXmjBB77/P2Szkujx5lJCgoUN16612RwEchoIF2GPdOj3/vD77TBcu2B0Focjn0733asQINWxodxTAkShYgG0yMvT++5owQZs3U7NgmogINWmixx5TkyZ2RwEcjIIF2O+LL/Tyy1q+nL1ZuCFRUerZUw8/rGrV7I4COB4FCwgUW7fq9de1YIF++42BDsgBl0v586tbNz36qKpUsTsNAEkULCDQbN2qadO0eDE1C9fmcik6Wt2765FHFBNjdxoAl6FgAYFo927NmqV58/Tzz0yBh4GwMN10k/r3V8+eKlrU7jQAsqBgAYHr/HmtXKnp07V5M9uz8LuICDVqpCFD1LSp3G670wDwg4IFBLqMDK1fr5kz9e67OnOG64YO5XYrKkqtWqlfP917L88QBAIdBQsIGrt2acECvfmm9u3TxYt2p4FVwsN1883q1UtduqhMGbvTAMgeChYQZNLTtXGjkpK0YoVOnWKHVsjyeFS4sLp3V/fuqlEjW5/ywgsv9OnTp0iRIrkcDcC1UbCAYHX4sBYv1oIF2r5d58/bnQYmcbnk86lqVXXvrk6dVKJEDj63b9++5cqVGz16dK6lA5BdFCwguGVk6PPPtXy5kpN16BAT4YOYz6dy5dShgx54QHXrXs8rbNu27b777tu7d6/X6zU7HYCcoWABISI9XVu26N13tXChfvqJphU0wsNVurQSEtSqlerVu9EbAxs2bPjQQw917tzZcPXMmTN79uyJiYnx+XyZH9m1a1dERETp0qWv/rIpKSnnzp0rXrx4jsLs3Llz27Zt1apVu/nmm6/7RYAgRcECQs3Zs1q7Vm+/rfffV0oKVw8DVHi4ChXS/ferVSs1b648ecx52eTk5MmTJ3/22WeGqx999FHz5s1nz57drVs3SampqWXKlImPj585c+bVX/arr746dOhQq1atsp/kkUce+fnnn+++++6zZ89u2bLllVdeOXbs2KFDh6pVqzZnzpznnnsu+y8FBCOGqAChJjJSbdpozhwdPKiPP9bIkbrtNkVEcGO//TL3V912m0aO1Cef6OBBzZ2rdu1Ma1eS2rZte+jQoS1btvg7IDY2dunSpZn//N5779W97GLkpEmTKlWqdNddd23btu3ZZ5+dOnWqpMmTJ7/yyitHjx7ds2dPenr6wIEDy5Qp07x584MHD6anpw8dOrR8+fKdOnXavn375V/lnXfe2bdv3/Lly4cNGzZq1KhJkybt378/80V69+49derUl156qUePHlu3bpW0bdu23r17m/ZbAAQGChYQssLCdOedGjdOW7fqX//S2LGqU0d58sjjsTuZw7jdioxU7doaO1YbNmjrVo0bpzvvzJX/EB6Pp3///tOmTfN3QNGiRVNTU0+cOCFpyZIlrVu3zvz4rl273n777c2bNz/22GMJCQn9+vWbOHHipk2bpk+f3qNHj5MnTx48eHDz5s0//fTTvn372rZt+/rrr2/evHn79u1ff/31008//T8767/++ut69er9+cvKlSvHxsZmvsgrr7wSGxs7ZMiQqlWrvvPOO5JWrlxZtWpV838vAFuF2R0AQK7zeFS/vurX19ix2rFDH3+sTz7Rhg06flznzzO5NFe4XAoPV5EiatJEjRqpQQNVqGDRl+7fv3+lSpWOHDliuNspIyOjVatWycnJnTt3PnbsWNmyZb/55htJ7733XqVKldasWSPp6NGjERERTz75ZKNGjebOnVugQIHMz/3ggw86duzo8Xj69Olz5MiRN954o1evXtHR0dHR0Xv27Dlz5kxUVFTmkWFhYadPn756zvj4+B49eowaNWrVqlULFiww87cACAAULMBZKldW5crq21eS9u3Txo1av15r1+roUV28SNm6UV6v8uVTgwZq3FhNm6pKFRsyFCpUKD4+/vXXX/c3ryE+Pr5Xr14REREtWrT484MHDhzIyMjIPLM1cuRIr9ebJ0+eixcv5s2b989j9u7de8cdd0gKCwsrVqzY/v37Y2NjM5cuXrx4+Y7eSpUqvfTSS3/+8oMPPnj//ffvvPPOy2PExMScO3fu+++/P3fuXKVKlUz4NwcCCZcIAecqX17dumnWLO3YoQ8/1D//qfbtVbKkfD4ecpddbrd8PhUurBYtNH68Vq3S7t1avlyPPmpPu8o0cODAGTNmXPQz779MmTJnz56dNm1afHz8nx9s0aJF3rx5Bw4c2Lt37w8//DA1NXXcuHHvvvvu4MGDU1NTM49p2rTphg0bJL399tujRo2Ki4v717/+JWnPnj3R0dGXV7HmzZvv37//q6++knTx4sWJEye2a9fuz9X09PTMf2jbtu3gwYMfeOABc//1gUDAmygARUbqnns0dKiWLtWBA9qyRa+/rr59VaWKIiMVxpnuK3m9ypNHZcqoY0e9+KLWr9eBA1q1SiNGKC5Of1xPs1ONGjViYmKSk5P9HdCmTZvU1NSYmJg/P3LPPff88ssvTZs2rVGjRtu2bR9//PEBAwY0b968efPmzz77bOYxLVu23Lt3b8OGDcePH5+5un379iZNmtSrV2/IkCGXv36BAgXmz5//9NNPx8bGVq5cuWbNmvfee2/mUsWKFb/77rvM+xbbtWu3bt26Tp06mf9bANiNMQ0ArubgQX37rb79Vl9/rX//WwcO6OJFxz0JMSxM4eEqXlx16qhmTVWrpmrVVLFiQN+YuWzZsilTpvib1+DPkSNHfD5fwYIFr3JMSkpKgcta5OHDhwsUKJDHz52QhqtpaWnp6enh4eH79+/v2bPn+vXrcxQSCAoULAA58PPP+vZbbd2q777TDz/oxx+Vmqq0NKWlhcj+LZdLYWEKC1PevKpS5b9/3X67ypa1O1xOpKWlVaxYMTk5+fK7+QLK6tWrX3jhhTFjxjRu3NjuLID5KFgArl9amvbu1Y8/atcu/fijfvhBu3fr+HFduqRLl5SWpj822wQit1sejzweeb0qU0YVKigmRpUr6+abFROjcuWCfp7F888//8MPP8ybN8/uIMZOnz7tdrv9nfoCgh0FC4DJzpzRgQP66ScdOqRDh7R/v/bt07FjOnpUx48rPf2Kv3LvHcjtlsv1e4tyu+V2q2BBlSyp4sVVpozKl1fZsipT5ve/h4fnVgwbHTlypHz58ueDYZZ/cnLy5TvugRBAwQJgnYsX9csv+uUXHTumX37RiRP67Tf9+qtSUn7/+8mT+vVX/frrf5/wc/r0/z5X0eVSgQK/374XGSlJ+fIpXz5FRalAAeXLp7x5FRWlvHlVoICKFlWRIipRQkWKKCLC6n9fAI5FwQIAADAZYxoAAABMRsECAAAwGQULAADAZBQsAAAAk1GwAAAATEbBAgAAMBkFCwAAwGQULAAAAJNRsAAAAExGwQIAADAZBQsAAMBkFCwAAACTUbAAAABMRsECAAAwGQULAADAZBQsAAAAk1GwAAAATEbBAgAAMBkFCwAAwGQULAAAAJNRsAAAAExGwQIAADAZBQsAAMBkFCwAAACTUbAAAABMRsECAAAwGQULAADAZBQsAAAAk1GwAAAATEbBAgAAMBkFCwAAwGQULAAAAJNRsAAAAExGwQIAADAZBQsAAMBkFCwAAACTUbAAAABMRsECAAAwGQULAADAZBQsAAAAk1GwAAAATEbBAgAAMBkFCwAAwGQULAAAAJNRsAAAAExGwQIAADAZBQsAAMBkFCwAAACTUbAAAABMRsECAAAwGQULAADAZBQsAAAAk1GwAAAATEbBAgAAMBkFCwAAwGQULAAAAJNRsAAAAExGwQIAADAZBQsAAMBkFCwAAACTUbAAAABMRsECAAAwGQULAADAZBQsAAAAk/1/XBLJiIakI7EAAAAASUVORK5CYII="
}
],
"prompt_number": 14
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- There is also hlint integration enabled by default.\n",
"-- If you write sketchy code, it will tell you:\n",
"f :: Int -> Int\n",
"f x = x + 1\n",
"\n",
"-- Most warnings are orange...\n",
"f $ 3\n",
"\n",
"-- But more severe warnings are red.\n",
"putStrLn (show 3)\n",
"do\n",
" return 3"
],
"language": "python",
"metadata": {},
"outputs": [
{
"html": [
" <table class=\"suggestion-table\"> <tr class=\"suggestion-row\"> <td class=\"suggestion-cell\"> <div class=\"suggestion-name\"> Redundant $</div> </td> </tr> <tr class=\"suggestion-row\"> <td class=\"suggestion-cell\"> <div class=\"suggestion-warning\">Found:</div> <div class=\"highlight-code\" id=\"haskell\"> f $ 3\n",
"</div> </td> <td class=\"suggestion-cell\"> <div class=\"suggestion-warning\">Why Not:</div> <div class=\"highlight-code\" id=\"haskell\"> f 3\n",
"</div> </td> </tr> <tr class=\"suggestion-row\"> <td class=\"suggestion-cell\"> <div class=\"suggestion-name\"> Use print</div> </td> </tr> <tr class=\"suggestion-row\"> <td class=\"suggestion-cell\"> <div class=\"suggestion-error\">Found:</div> <div class=\"highlight-code\" id=\"haskell\"> putStrLn (show 3)\n",
"</div> </td> <td class=\"suggestion-cell\"> <div class=\"suggestion-error\">Why Not:</div> <div class=\"highlight-code\" id=\"haskell\"> print 3\n",
"</div> </td> </tr> <tr class=\"suggestion-row\"> <td class=\"suggestion-cell\"> <div class=\"suggestion-name\"> Redundant do</div> </td> </tr> <tr class=\"suggestion-row\"> <td class=\"suggestion-cell\"> <div class=\"suggestion-error\">Found:</div> <div class=\"highlight-code\" id=\"haskell\"> do return 3\n",
"</div> </td> <td class=\"suggestion-cell\"> <div class=\"suggestion-error\">Why Not:</div> <div class=\"highlight-code\" id=\"haskell\"> return 3\n",
"</div> </td> </tr> </table> "
],
"metadata": {},
"output_type": "display_data",
"text": [
"Line 3: Redundant $\n",
"Found:\n",
" f $ 3\n",
"\n",
"Why not:\n",
" f 3\n",
"Line 4: Use print\n",
"Found:\n",
" putStrLn (show 3)\n",
"\n",
"Why not:\n",
" print 3\n",
"Line 5: Redundant do\n",
"Found:\n",
" do return 3\n",
"\n",
"Why not:\n",
" return 3"
]
},
{
"metadata": {},
"output_type": "display_data",
"text": [
"4"
]
},
{
"metadata": {},
"output_type": "display_data",
"text": [
"3"
]
},
{
"metadata": {},
"output_type": "display_data",
"text": [
"3"
]
}
],
"prompt_number": 15
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- If hlint annoys you, though, you can turn it off.\n",
"-- Note that this only takes effect in the next cell execution.\n",
":hlint off"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 16
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- You could similarly use `:hlint on` to turn it back on.\n",
"f $ 3"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"text": [
"4"
]
}
],
"prompt_number": 17
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- If your code isn't running fast enough, you can just put it into a module.\n",
"module A.B where\n",
"\n",
"fib 0 = 1\n",
"fib 1 = 1\n",
"fib n = fib (n-1) + fib (n-2)"
],
"language": "python",
"metadata": {},
"outputs": [],
"prompt_number": 18
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- The module is automatically imported unqualified.\n",
"-- Note that since a new module is imported, all previous\n",
"-- bound identifiers are now unbound.\n",
"print $ A.B.fib 20\n",
"print $ fib 20"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"text": [
"10946"
]
},
{
"metadata": {},
"output_type": "display_data",
"text": [
"10946"
]
}
],
"prompt_number": 19
},
{
"cell_type": "code",
"collapsed": false,
"input": [
"-- But if you re-import it qualified, the previous import goes away.\n",
"import qualified A.B as Fib\n",
"\n",
"Fib.fib 20\n",
"fib 20"
],
"language": "python",
"metadata": {},
"outputs": [
{
"metadata": {},
"output_type": "display_data",
"text": [
"10946"
]
},
{
"html": [
"<span style='color: red; font-style: italic;'>Not in scope: `fib'<br/>Perhaps you meant `Fib.fib' (imported from A.B)</span>"
],
"metadata": {},
"output_type": "display_data",
"text": [
"Not in scope: `fib'\n",
"Perhaps you meant `Fib.fib' (imported from A.B)"
]
}
],
"prompt_number": 20
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Thanks!\n",
"---\n",
"\n",
"I hope you've enjoyed this little demo of **IHaskell**!\n",
"\n",
"All of this is running using [IPython notebook][http://ipython.org], communicating asynchronously with a language evaluator kernel written in pure Haskell. \n",
"\n",
"In addition to code cells, you can also type Markdown, and have it be displayed as HTML - just like I'm doing with this cell.\n",
"\n",
"I hope you find IHaskell useful, and please report any bugs or features requests [on Github](https://github.com/gibiansky/IHaskell/issues). If you have any comments, want to contribute, or just want to get in touch, don't hesitate to contact me at Andrew dot Gibiansky at Gmail."
]
}
],
"metadata": {}
}
]
}
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -14,7 +14,7 @@ maintainer: andrew.gibiansky@gmail.com ...@@ -14,7 +14,7 @@ maintainer: andrew.gibiansky@gmail.com
category: Language category: Language
build-type: Custom build-type: Custom
-- extra-source-files: -- extra-source-files:
cabal-version: >=1.10 cabal-version: >=1.16
extra-source-files: extra-source-files:
build-parser.sh build-parser.sh
...@@ -25,7 +25,8 @@ library ...@@ -25,7 +25,8 @@ library
Language.Haskell.GHC.HappyParser Language.Haskell.GHC.HappyParser
-- other-modules: -- other-modules:
-- other-extensions: -- other-extensions:
build-depends: base >=4.6 && <4.7, ghc == 7.6.3 build-depends: base >=4.6 && <4.7,
ghc ==7.6.*
-- hs-source-dirs: -- hs-source-dirs:
default-language: Haskell2010 default-language: Haskell2010
...@@ -10,7 +10,7 @@ import Data.String.Here ...@@ -10,7 +10,7 @@ import Data.String.Here
import IHaskell.Display import IHaskell.Display
instance IHaskellDisplay Value where instance IHaskellDisplay Value where
display renderable = return [plain json, html dom] display renderable = return $ Display [plain json, html dom]
where where
json = unpack $ decodeUtf8 $ encodePretty renderable json = unpack $ decodeUtf8 $ encodePretty renderable
dom = [i|<div class="highlight-code" id="javascript">${json}</div>|] dom = [i|<div class="highlight-code" id="javascript">${json}</div>|]
...@@ -6,7 +6,7 @@ import IHaskell.Display ...@@ -6,7 +6,7 @@ import IHaskell.Display
import Text.Printf import Text.Printf
instance Show a => IHaskellDisplay (Maybe a) where instance Show a => IHaskellDisplay (Maybe a) where
display just = return [stringDisplay, htmlDisplay] display just = return $ Display [stringDisplay, htmlDisplay]
where where
stringDisplay = plain (show just) stringDisplay = plain (show just)
htmlDisplay = html str htmlDisplay = html str
......
...@@ -10,7 +10,7 @@ import Text.Blaze.Internal ...@@ -10,7 +10,7 @@ import Text.Blaze.Internal
import Control.Monad import Control.Monad
instance IHaskellDisplay (MarkupM a) where instance IHaskellDisplay (MarkupM a) where
display val = return [stringDisplay, htmlDisplay] display val = return $ Display [stringDisplay, htmlDisplay]
where where
str = renderMarkup (void val) str = renderMarkup (void val)
stringDisplay = plain str stringDisplay = plain str
......
...@@ -26,7 +26,7 @@ instance IHaskellDisplay (Renderable a) where ...@@ -26,7 +26,7 @@ instance IHaskellDisplay (Renderable a) where
-- but SVGs are not resizable in the IPython notebook. -- but SVGs are not resizable in the IPython notebook.
svgDisp <- chartData renderable SVG svgDisp <- chartData renderable SVG
return [pngDisp, svgDisp] return $ Display [pngDisp, svgDisp]
chartData :: Renderable a -> FileFormat -> IO DisplayData chartData :: Renderable a -> FileFormat -> IO DisplayData
chartData renderable format = do chartData renderable format = do
......
...@@ -16,7 +16,7 @@ instance IHaskellDisplay (Diagram Cairo R2) where ...@@ -16,7 +16,7 @@ instance IHaskellDisplay (Diagram Cairo R2) where
display renderable = do display renderable = do
png <- diagramData renderable PNG png <- diagramData renderable PNG
svg <- diagramData renderable SVG svg <- diagramData renderable SVG
return [png, svg] return $ Display [png, svg]
diagramData :: Diagram Cairo R2 -> OutputType -> IO DisplayData diagramData :: Diagram Cairo R2 -> OutputType -> IO DisplayData
diagramData renderable format = do diagramData renderable format = do
......
...@@ -24,7 +24,7 @@ instance IHaskellDisplay B.ByteString where ...@@ -24,7 +24,7 @@ instance IHaskellDisplay B.ByteString where
m <- magicOpen [] m <- magicOpen []
magicLoadDefault m magicLoadDefault m
f <- B.unsafeUseAsCStringLen x (magicCString m) f <- B.unsafeUseAsCStringLen x (magicCString m)
return [withClass (parseMagic f) x] return $ Display [withClass (parseMagic f) x]
b64 :: B.ByteString -> String b64 :: B.ByteString -> String
b64 = Char.unpack . Base64.encode b64 = Char.unpack . Base64.encode
......
...@@ -27,11 +27,11 @@ library ...@@ -27,11 +27,11 @@ library
hs-source-dirs: src hs-source-dirs: src
default-language: Haskell2010 default-language: Haskell2010
build-depends: base >=4.6 && <4.7, build-depends: base >=4.6 && <4.7,
bytestring >= 0.10, aeson >=0.6,
aeson >= 0.6, bytestring >=0.10,
text >= 0.11, cereal ==0.3.*,
containers >= 0.5, containers >=0.5,
unix >= 2.6, text >=0.11,
uuid >= 1.3, unix >=2.6,
cereal == 0.3.*, uuid >=1.3,
zeromq4-haskell >= 0.1 zeromq4-haskell >=0.1
...@@ -101,7 +101,7 @@ instance ToJSON StreamType where ...@@ -101,7 +101,7 @@ instance ToJSON StreamType where
-- | Convert a MIME type and value into a JSON dictionary pair. -- | Convert a MIME type and value into a JSON dictionary pair.
displayDataToJson :: DisplayData -> (Text, Value) displayDataToJson :: DisplayData -> (Text, Value)
displayDataToJson (Display mimeType dataStr) = pack (show mimeType) .= dataStr displayDataToJson (DisplayData mimeType dataStr) = pack (show mimeType) .= dataStr
----- Constants ----- ----- Constants -----
......
...@@ -341,13 +341,13 @@ replyType ShutdownRequestMessage = Just ShutdownReplyMessage ...@@ -341,13 +341,13 @@ replyType ShutdownRequestMessage = Just ShutdownReplyMessage
replyType _ = Nothing replyType _ = Nothing
-- | Data for display: a string with associated MIME type. -- | Data for display: a string with associated MIME type.
data DisplayData = Display MimeType ByteString deriving (Typeable, Generic) data DisplayData = DisplayData MimeType ByteString deriving (Typeable, Generic)
-- We can't print the actual data, otherwise this will be printed every -- We can't print the actual data, otherwise this will be printed every
-- time it gets computed because of the way the evaluator is structured. -- time it gets computed because of the way the evaluator is structured.
-- See how `displayExpr` is computed. -- See how `displayExpr` is computed.
instance Show DisplayData where instance Show DisplayData where
show _ = "Display" show _ = "DisplayData"
-- Allow DisplayData serialization -- Allow DisplayData serialization
instance Serialize DisplayData instance Serialize DisplayData
...@@ -369,9 +369,9 @@ extractPlain :: [DisplayData] -> String ...@@ -369,9 +369,9 @@ extractPlain :: [DisplayData] -> String
extractPlain disps = extractPlain disps =
case find isPlain disps of case find isPlain disps of
Nothing -> "" Nothing -> ""
Just (Display PlainText bytestr) -> Char.unpack bytestr Just (DisplayData PlainText bytestr) -> Char.unpack bytestr
where where
isPlain (Display mime _) = mime == PlainText isPlain (DisplayData mime _) = mime == PlainText
instance Show MimeType where instance Show MimeType where
show PlainText = "text/plain" show PlainText = "text/plain"
......
...@@ -7,3 +7,7 @@ c.Session.keyfile = b'' ...@@ -7,3 +7,7 @@ c.Session.keyfile = b''
# Syntax highlight properly in Haskell notebooks. # Syntax highlight properly in Haskell notebooks.
c.NbConvertBase.default_language = "haskell" c.NbConvertBase.default_language = "haskell"
# Where to look for templates.
template_path = "/".join(__file__.split("/")[:-1] + ["templates"])
c.TemplateExporter.template_path = [template_path]
{%- extends 'html_full.tpl' -%}
{%- block header -%}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>{{resources['metadata']['name']}}</title>
{% for css in resources.inlining.css -%}
<style type="text/css">
{{ css }}
</style>
{% endfor %}
<style type="text/css">
/* Overrides of notebook CSS for static HTML export */
body {
overflow: visible;
padding: 8px;
}
.input_area {
padding: 0.2em;
}
pre {
padding: 0.2em;
border: none;
margin: 0px;
font-size: 13px;
}
</style>
<!-- Our custom CSS -->
<style type="text/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 */
.get-type {
color: green;
font-weight: bold;
font-family: monospace;
display: block;
white-space: pre;
}
.show-type {
color: green;
font-weight: bold;
font-family: monospace;
margin-left: 1em;
}
.mono {
font-family: monospace;
display: block;
}
.err-msg {
color: red;
font-style: italic;
font-family: monospace;
white-space: pre;
display: block;
}
#unshowable {
color: red;
font-weight: bold;
}
.err-msg.in.collapse {
padding-top: 0.7em;
}
/* Code that will get highlighted before it is highlighted */
.highlight-code {
white-space: pre;
font-family: monospace;
}
/* Hlint styles */
.suggestion-warning {
font-weight: bold;
color: rgb(200, 130, 0);
}
.suggestion-error {
font-weight: bold;
color: red;
}
.suggestion-name {
font-weight: bold;
}
</style>
<script src="https://c328740.ssl.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=TeX-AMS_HTML" type="text/javascript"></script>
<script type="text/javascript">
init_mathjax = function() {
if (window.MathJax) {
// MathJax loaded
MathJax.Hub.Config({
tex2jax: {
inlineMath: [ ['$','$'], ["\\(","\\)"] ],
displayMath: [ ['$$','$$'], ["\\[","\\]"] ]
},
displayAlign: 'left', // Change this to 'center' to center equations.
"HTML-CSS": {
styles: {'.MathJax_Display': {"margin": 0}}
}
});
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
}
}
init_mathjax();
</script>
</head>
{%- endblock header -%}
{% block body %}
<body>
{{ super() }}
</body>
{%- endblock body %}
...@@ -72,15 +72,13 @@ evaluationComparing comparison string = do ...@@ -72,15 +72,13 @@ evaluationComparing comparison string = do
becomes string expected = evaluationComparing comparison string becomes string expected = evaluationComparing comparison string
where where
comparison :: ([Display], String) -> IO ()
comparison (results, pageOut) = do comparison (results, pageOut) = do
when (length results /= length expected) $ when (length results /= length expected) $
expectationFailure $ "Expected result to have " ++ show (length expected) expectationFailure $ "Expected result to have " ++ show (length expected)
++ " results. Got " ++ show results ++ " results. Got " ++ show results
let isPlain (Display PlainText _) = True forM_ (zip results expected) $ \(Display result, expected) ->
isPlain _ = False
forM_ (zip results expected) $ \(result, expected) ->
case extractPlain result of case extractPlain result of
"" -> expectationFailure $ "No plain-text output in " ++ show result ++ "\nExpected: " ++ expected "" -> expectationFailure $ "No plain-text output in " ++ show result ++ "\nExpected: " ++ expected
str -> str `shouldBe` expected str -> str `shouldBe` expected
......
...@@ -5,12 +5,13 @@ module IHaskell.Display ( ...@@ -5,12 +5,13 @@ module IHaskell.Display (
serializeDisplay, serializeDisplay,
Width, Height, Base64, Width, Height, Base64,
encode64, base64, encode64, base64,
DisplayData Display(..),
DisplayData(..),
) where ) where
import ClassyPrelude import ClassyPrelude
import Data.Serialize as Serialize import Data.Serialize as Serialize
import Data.ByteString import Data.ByteString hiding (map)
import Data.String.Utils (rstrip) 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
...@@ -27,52 +28,62 @@ type Base64 = ByteString ...@@ -27,52 +28,62 @@ type Base64 = ByteString
-- > instance (Show a) => IHaskellDisplay a -- > instance (Show a) => IHaskellDisplay a
-- > instance Show a where shows _ = id -- > instance Show a where shows _ = id
class IHaskellDisplay a where class IHaskellDisplay a where
display :: a -> IO [DisplayData] display :: a -> IO Display
-- | these instances cause the image, html etc. which look like: -- | these instances cause the image, html etc. which look like:
-- --
-- > DisplayData -- > Display
-- > [DisplayData] -- > [Display]
-- > IO [DisplayData] -- > IO [Display]
-- > IO (IO DisplayData) -- > 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 =<<)
instance IHaskellDisplay DisplayData where
display disp = return [disp]
instance IHaskellDisplay [DisplayData] where instance IHaskellDisplay Display where
display = return display = return
instance IHaskellDisplay DisplayData where
display disp = return $ Display [disp]
instance IHaskellDisplay a => IHaskellDisplay [a] where
display disps = do
displays <- mapM display disps
return $ ManyDisplay displays
-- | Encode many displays into a single one. All will be output.
many :: [Display] -> Display
many = ManyDisplay
-- | Generate a plain text display. -- | Generate a plain text display.
plain :: String -> DisplayData plain :: String -> DisplayData
plain = Display PlainText . Char.pack . rstrip plain = DisplayData PlainText . Char.pack . rstrip
-- | Generate an HTML display. -- | Generate an HTML display.
html :: String -> DisplayData html :: String -> DisplayData
html = Display MimeHtml . Char.pack html = DisplayData MimeHtml . Char.pack
-- | Genreate an SVG display. -- | Genreate an SVG display.
svg :: String -> DisplayData svg :: String -> DisplayData
svg = Display MimeSvg . Char.pack svg = DisplayData MimeSvg . Char.pack
-- | Genreate a LaTeX display. -- | Genreate a LaTeX display.
latex :: String -> DisplayData latex :: String -> DisplayData
latex = Display MimeLatex . Char.pack latex = DisplayData MimeLatex . Char.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 manner, suitable for embedding into HTML. -- provided in a Base64 encoded manner, suitable for embedding into HTML.
-- The @base64@ function may be used to encode data into this format. -- The @base64@ function may be used to encode data into this format.
png :: Width -> Height -> Base64 -> DisplayData png :: Width -> Height -> Base64 -> DisplayData
png width height = Display (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 manner, suitable for embedding into HTML. -- provided in a Base64 encoded manner, suitable for embedding into HTML.
-- The @base64@ function may be used to encode data into this format. -- The @base64@ function may be used to encode data into this format.
jpg :: Width -> Height -> Base64 -> DisplayData jpg :: Width -> Height -> Base64 -> DisplayData
jpg width height = Display (MimeJpg width height) jpg width height = DisplayData (MimeJpg width height)
-- | Convert from a string into base 64 encoded data. -- | Convert from a string into base 64 encoded data.
encode64 :: String -> Base64 encode64 :: String -> Base64
...@@ -84,5 +95,5 @@ base64 = Base64.encode ...@@ -84,5 +95,5 @@ base64 = Base64.encode
-- | For internal use within IHaskell. -- | For internal use within IHaskell.
-- Serialize displays to a ByteString. -- Serialize displays to a ByteString.
serializeDisplay :: [DisplayData] -> ByteString serializeDisplay :: Display -> ByteString
serializeDisplay = Serialize.encode serializeDisplay = Serialize.encode
...@@ -116,9 +116,19 @@ interpret allowedStdin action = runGhc (Just libdir) $ do ...@@ -116,9 +116,19 @@ interpret allowedStdin action = runGhc (Just libdir) $ do
-- Set the dynamic session flags -- Set the dynamic session flags
originalFlags <- getSessionDynFlags originalFlags <- getSessionDynFlags
let dflags = xopt_set originalFlags Opt_ExtendedDefaultRules let dflags = xopt_set originalFlags Opt_ExtendedDefaultRules
-- If we're in a sandbox, add the relevant package database
sandboxPackages <- liftIO getSandboxPackageConf
let pkgConfs = case sandboxPackages of
Nothing -> extraPkgConfs dflags
Just path ->
let pkg = PkgConfFile path in
(pkg:) . extraPkgConfs dflags
void $ setSessionDynFlags $ dflags { hscTarget = HscInterpreted, void $ setSessionDynFlags $ dflags { hscTarget = HscInterpreted,
ghcLink = LinkInMemory, ghcLink = LinkInMemory,
pprCols = 300 } pprCols = 300,
extraPkgConfs = pkgConfs }
initializeImports initializeImports
...@@ -206,7 +216,7 @@ type Publisher = (EvaluationResult -> IO ()) ...@@ -206,7 +216,7 @@ type Publisher = (EvaluationResult -> IO ())
-- | Output of a command evaluation. -- | Output of a command evaluation.
data EvalOut = EvalOut { data EvalOut = EvalOut {
evalStatus :: ErrorOccurred, evalStatus :: ErrorOccurred,
evalResult :: [DisplayData], evalResult :: Display,
evalState :: KernelState, evalState :: KernelState,
evalPager :: String evalPager :: String
} }
...@@ -222,7 +232,7 @@ evaluate kernelState code output = do ...@@ -222,7 +232,7 @@ evaluate kernelState code output = do
when (getLintStatus kernelState /= LintOff) $ liftIO $ do when (getLintStatus kernelState /= LintOff) $ liftIO $ do
lintSuggestions <- lint cmds lintSuggestions <- lint cmds
unless (null lintSuggestions) $ unless (noResults lintSuggestions) $
output $ FinalResult lintSuggestions "" output $ FinalResult lintSuggestions ""
updated <- runUntilFailure kernelState (map unloc cmds ++ [storeItCommand execCount]) updated <- runUntilFailure kernelState (map unloc cmds ++ [storeItCommand execCount])
...@@ -230,6 +240,9 @@ evaluate kernelState code output = do ...@@ -230,6 +240,9 @@ evaluate kernelState code output = do
getExecutionCounter = execCount + 1 getExecutionCounter = execCount + 1
} }
where where
noResults (Display res) = null res
noResults (ManyDisplay res) = all noResults res
runUntilFailure :: KernelState -> [CodeBlock] -> Interpreter KernelState runUntilFailure :: KernelState -> [CodeBlock] -> Interpreter KernelState
runUntilFailure state [] = return state runUntilFailure state [] = return state
runUntilFailure state (cmd:rest) = do runUntilFailure state (cmd:rest) = do
...@@ -238,7 +251,7 @@ evaluate kernelState code output = do ...@@ -238,7 +251,7 @@ evaluate kernelState code output = do
-- Output things only if they are non-empty. -- Output things only if they are non-empty.
let result = evalResult evalOut let result = evalResult evalOut
helpStr = evalPager evalOut helpStr = evalPager evalOut
unless (null result && null helpStr) $ unless (noResults result && null helpStr) $
liftIO $ output $ FinalResult result helpStr liftIO $ output $ FinalResult result helpStr
let newState = evalState evalOut let newState = evalState evalOut
...@@ -280,8 +293,10 @@ safely state = ghandle handler . ghandle sourceErrorHandler ...@@ -280,8 +293,10 @@ safely state = ghandle handler . ghandle sourceErrorHandler
doc :: GhcMonad m => SDoc -> m String doc :: GhcMonad m => SDoc -> m String
doc sdoc = do doc sdoc = do
flags <- getSessionDynFlags flags <- getSessionDynFlags
unqual <- getPrintUnqual
let style = mkUserStyle unqual AllTheWay
let cols = pprCols flags let cols = pprCols flags
d = runSDoc sdoc (initSDocContext flags defaultUserStyle) 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
...@@ -292,7 +307,7 @@ doc sdoc = do ...@@ -292,7 +307,7 @@ doc sdoc = do
wrapExecution :: KernelState wrapExecution :: KernelState
-> Interpreter [DisplayData] -> Interpreter Display
-> Interpreter EvalOut -> Interpreter EvalOut
wrapExecution state exec = safely state $ exec >>= \res -> wrapExecution state exec = safely state $ exec >>= \res ->
return EvalOut { return EvalOut {
...@@ -318,7 +333,7 @@ evalCommand _ (Import importStr) state = wrapExecution state $ do ...@@ -318,7 +333,7 @@ evalCommand _ (Import importStr) state = wrapExecution state $ do
return $ if "Test.Hspec" `isInfixOf` importStr return $ if "Test.Hspec" `isInfixOf` importStr
then displayError $ "Warning: Hspec is unusable in IHaskell until the resolution of GHC bug #8639." ++ then displayError $ "Warning: Hspec is unusable in IHaskell until the resolution of GHC bug #8639." ++
"\nThe variable `it` is shadowed and cannot be accessed, even in qualified form." "\nThe variable `it` is shadowed and cannot be accessed, even in qualified form."
else [] else Display []
where where
implicitImportOf :: ImportDecl RdrName -> InteractiveImport -> Bool implicitImportOf :: ImportDecl RdrName -> InteractiveImport -> Bool
implicitImportOf _ (IIModule _) = False implicitImportOf _ (IIModule _) = False
...@@ -372,7 +387,7 @@ evalCommand _ (Directive SetExtension exts) state = wrapExecution state $ do ...@@ -372,7 +387,7 @@ evalCommand _ (Directive SetExtension exts) state = wrapExecution state $ do
write $ "Extension: " ++ exts write $ "Extension: " ++ exts
results <- mapM setExtension (words exts) results <- mapM setExtension (words exts)
case catMaybes results of case catMaybes results of
[] -> return [] [] -> return $ Display []
errors -> return $ displayError $ intercalate "\n" errors errors -> return $ displayError $ intercalate "\n" errors
evalCommand _ (Directive GetType expr) state = wrapExecution state $ do evalCommand _ (Directive GetType expr) state = wrapExecution state $ do
...@@ -404,7 +419,7 @@ evalCommand _ (Directive SetOpt option) state = do ...@@ -404,7 +419,7 @@ evalCommand _ (Directive SetOpt option) state = do
newState = setOpt opt state newState = setOpt opt state
out = case newState of out = case newState of
Nothing -> displayError $ "Unknown option: " ++ opt Nothing -> displayError $ "Unknown option: " ++ opt
Just _ -> [] Just _ -> Display []
return EvalOut { return EvalOut {
evalStatus = if isJust newState then Success else Failure, evalStatus = if isJust newState then Success else Failure,
...@@ -452,7 +467,7 @@ evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $ ...@@ -452,7 +467,7 @@ evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $
if exists if exists
then do then do
setCurrentDirectory directory setCurrentDirectory directory
return [] return $ Display []
else else
return $ displayError $ printf "No such directory: '%s'" directory return $ displayError $ printf "No such directory: '%s'" directory
cmd -> do cmd -> do
...@@ -480,7 +495,7 @@ evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $ ...@@ -480,7 +495,7 @@ evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $
-- Maximum size of the output (after which we truncate). -- Maximum size of the output (after which we truncate).
maxSize = 100 * 1000 maxSize = 100 * 1000
incSize = 200 incSize = 200
output str = publish $ IntermediateResult [plain str] output str = publish $ IntermediateResult $ Display [plain str]
loop = do loop = do
-- Wait and then check if the computation is done. -- Wait and then check if the computation is done.
...@@ -506,11 +521,11 @@ evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $ ...@@ -506,11 +521,11 @@ evalCommand publish (Directive ShellCmd ('!':cmd)) state = wrapExecution state $
else do else do
out <- readMVar outputAccum out <- readMVar outputAccum
case fromJust exitCode of case fromJust exitCode of
ExitSuccess -> return [plain out] ExitSuccess -> return $ Display [plain out]
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 [plain $ out ++ "\n" ++ errMsg, return $ Display [plain $ out ++ "\n" ++ errMsg,
html $ printf "<span class='mono'>%s</span>" out ++ htmlErr] html $ printf "<span class='mono'>%s</span>" out ++ htmlErr]
loop loop
...@@ -521,7 +536,7 @@ evalCommand _ (Directive GetHelp _) state = do ...@@ -521,7 +536,7 @@ evalCommand _ (Directive GetHelp _) state = do
write "Help via :help or :?." write "Help via :help or :?."
return EvalOut { return EvalOut {
evalStatus = Success, evalStatus = Success,
evalResult = [out], evalResult = Display [out],
evalState = state, evalState = state,
evalPager = "" evalPager = ""
} }
...@@ -585,7 +600,7 @@ evalCommand _ (Directive GetInfo str) state = safely state $ do ...@@ -585,7 +600,7 @@ evalCommand _ (Directive GetInfo str) state = safely state $ do
return EvalOut { return EvalOut {
evalStatus = Success, evalStatus = Success,
evalResult = [], evalResult = Display [],
evalState = state, evalState = state,
evalPager = output evalPager = output
} }
...@@ -600,7 +615,7 @@ evalCommand _ (Directive GetDoc query) state = safely state $ do ...@@ -600,7 +615,7 @@ evalCommand _ (Directive GetDoc query) state = safely state $ do
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 $ Display [plain str]
(printed, result) <- capturedStatement outputter stmt (printed, result) <- capturedStatement outputter stmt
case result of case result of
RunOk names -> do RunOk names -> do
...@@ -618,7 +633,7 @@ evalCommand output (Statement stmt) state = wrapExecution state $ do ...@@ -618,7 +633,7 @@ evalCommand output (Statement stmt) state = wrapExecution state $ do
-- 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 output then return $ Display output
else do else do
-- Get all the type strings. -- Get all the type strings.
types <- forM nonItNames $ \name -> do types <- forM nonItNames $ \name -> do
...@@ -629,11 +644,11 @@ evalCommand output (Statement stmt) state = wrapExecution state $ do ...@@ -629,11 +644,11 @@ evalCommand output (Statement stmt) state = wrapExecution state $ do
htmled = unlines $ map formatGetType types htmled = unlines $ map formatGetType types
return $ case extractPlain output of return $ case extractPlain output of
"" -> [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 -> text -> Display
[plain $ joined ++ "\n" ++ text, [plain $ joined ++ "\n" ++ text,
html $ htmled ++ mono text] html $ htmled ++ mono text]
...@@ -642,36 +657,29 @@ evalCommand output (Statement stmt) state = wrapExecution state $ do ...@@ -642,36 +657,29 @@ evalCommand output (Statement stmt) state = wrapExecution state $ do
evalCommand output (Expression expr) state = do evalCommand output (Expression expr) state = do
write $ "Expression:\n" ++ expr write $ "Expression:\n" ++ expr
-- Evaluate this expression as though it's just a statement.
-- The output is bound to 'it', so we can then use it.
evalOut <- evalCommand output (Statement expr) state
-- Try to use `display` to convert our type into the output -- Try to use `display` to convert our type into the output
-- DisplayData. If typechecking fails and there is no appropriate -- Dislay If typechecking fails and there is no appropriate
-- typeclass instance, this will throw an exception and thus `attempt` will -- typeclass instance, this will throw an exception and thus `attempt` will
-- return 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
if canRunDisplay
then useDisplay displayExpr
else do
-- Evaluate this expression as though it's just a statement.
-- The output is bound to 'it', so we can then use it.
evalOut <- evalCommand output (Statement expr) state
let out = evalResult evalOut let out = evalResult evalOut
showErr = isShowError out showErr = isShowError out
write $ printf "%s: Attempting %s" (if canRunDisplay then "Success" else "Failure") displayExpr
write $ "Show Error: " ++ show showErr
write $ show 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 IHaskellDisplay typeclass. -- may be able to use the IHaskellDisplay typeclass.
if not canRunDisplay return $ if not showErr || useShowErrors state
then return $ if not showErr || useShowErrors state
then evalOut then evalOut
else postprocessShowError evalOut else postprocessShowError evalOut
else case evalStatus evalOut of
Success -> useDisplay displayExpr
-- If something other than the show failed, don't use display, just
-- show the error message.
Failure -> if showErr
then useDisplay displayExpr
else return 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
...@@ -683,24 +691,27 @@ evalCommand output (Expression expr) state = do ...@@ -683,24 +691,27 @@ evalCommand output (Expression expr) state = do
-- 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 errs = isShowError (ManyDisplay _) = False
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 GHC.Show.Show. -- that `Show` is not displayed as GHC.Show.Show.
startswith "No instance for (Show" msg && startswith "No instance for (Show" msg &&
isInfixOf " arising from a use of `print'" msg isInfixOf " arising from a use of `print'" msg
where msg = extractPlain errs where msg = extractPlain errs
isPlain (Display mime _) = mime == PlainText isSvg (DisplayData mime _) = mime == MimeSvg
isSvg (Display mime _) = mime == MimeSvg
removeSvg (Display disps) = Display $ filter (not . isSvg) disps
removeSvg (ManyDisplay disps) = ManyDisplay $ map removeSvg disps
useDisplay displayExpr = wrapExecution state $ do useDisplay displayExpr = wrapExecution state $ do
-- If there are instance matches, convert the object into -- If there are instance matches, convert the object into
-- a [DisplayData]. We also serialize it into a bytestring. We get -- a Display. We also serialize it into a bytestring. We get
-- the bytestring as a dynamic and then convert back to -- the bytestring as a dynamic and then convert back to
-- a bytestring, which we promptly unserialize. Note that -- a bytestring, which we promptly unserialize. Note that
-- attempting to do this without the serialization to binary and -- attempting to do this without the serialization to binary and
-- back gives very strange errors - all the types match but it -- back gives very strange errors - all the types match but it
-- refuses to decode back into a [DisplayData]. -- refuses to decode back into a Display.
-- Suppress output, so as not to mess up console. -- Suppress output, so as not to mess up console.
out <- capturedStatement (const $ return ()) displayExpr out <- capturedStatement (const $ return ()) displayExpr
...@@ -710,20 +721,19 @@ evalCommand output (Expression expr) state = do ...@@ -710,20 +721,19 @@ evalCommand output (Expression expr) state = do
Just bytestring -> Just bytestring ->
case Serialize.decode bytestring of case Serialize.decode bytestring of
Left err -> error err Left err -> error err
Right displayData -> do Right display -> do
write $ show displayData
return $ return $
if useSvg state if useSvg state
then displayData then display
else filter (not . isSvg) displayData else removeSvg display
postprocessShowError :: EvalOut -> EvalOut postprocessShowError :: EvalOut -> EvalOut
postprocessShowError evalOut = evalOut { evalResult = map postprocess disps } postprocessShowError evalOut = evalOut { evalResult = Display $ map postprocess disps }
where where
disps = evalResult evalOut Display disps = evalResult evalOut
text = extractPlain disps text = extractPlain disps
postprocess (Display 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' href='#' id='unshowable'>Unshowable:<span class='show-type'>%s</span></span>%s</div><script>%s</script>" fmt = "<div class='collapse-group'><span class='btn' href='#' id='unshowable'>Unshowable:<span class='show-type'>%s</span></span>%s</div><script>%s</script>"
script = unlines [ script = unlines [
...@@ -760,14 +770,14 @@ evalCommand _ (Declaration decl) state = wrapExecution state $ do ...@@ -760,14 +770,14 @@ evalCommand _ (Declaration decl) state = wrapExecution state $ do
-- 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 [] then return $ Display []
else do else do
-- Get all the type strings. -- Get all the type strings.
types <- forM nonDataNames $ \name -> do types <- forM nonDataNames $ \name -> do
theType <- showSDocUnqual dflags . ppr <$> exprType name theType <- showSDocUnqual dflags . ppr <$> exprType name
return $ name ++ " :: " ++ theType return $ name ++ " :: " ++ theType
return [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
...@@ -789,7 +799,7 @@ evalCommand _ (ParseError loc err) state = do ...@@ -789,7 +799,7 @@ evalCommand _ (ParseError loc err) state = do
hoogleResults :: KernelState -> [Hoogle.HoogleResult] -> EvalOut hoogleResults :: KernelState -> [Hoogle.HoogleResult] -> EvalOut
hoogleResults state results = EvalOut { hoogleResults state results = EvalOut {
evalStatus = Success, evalStatus = Success,
evalResult = [], evalResult = Display [],
evalState = state, evalState = state,
evalPager = output evalPager = output
} }
...@@ -823,7 +833,7 @@ readChars handle delims nchars = do ...@@ -823,7 +833,7 @@ readChars handle delims nchars = do
Left _ -> return [] Left _ -> return []
doLoadModule :: String -> String -> Ghc [DisplayData] doLoadModule :: String -> String -> Ghc Display
doLoadModule name modName = flip gcatch unload $ do doLoadModule name modName = flip gcatch unload $ do
-- Compile loaded modules. -- Compile loaded modules.
flags <- getSessionDynFlags flags <- getSessionDynFlags
...@@ -851,10 +861,10 @@ doLoadModule name modName = flip gcatch unload $ do ...@@ -851,10 +861,10 @@ doLoadModule name modName = flip gcatch unload $ do
setSessionDynFlags flags{ hscTarget = HscInterpreted } setSessionDynFlags flags{ hscTarget = HscInterpreted }
case result of case result of
Succeeded -> return [] Succeeded -> return $ Display []
Failed -> return $ displayError $ "Failed to load module " ++ modName Failed -> return $ displayError $ "Failed to load module " ++ modName
where where
unload :: SomeException -> Ghc [DisplayData] unload :: SomeException -> Ghc Display
unload exception = do unload exception = do
-- Explicitly clear targets -- Explicitly clear targets
setTargets [] setTargets []
...@@ -1033,11 +1043,11 @@ formatParseError (Loc line col) = ...@@ -1033,11 +1043,11 @@ formatParseError (Loc line col) =
formatGetType :: String -> String formatGetType :: String -> String
formatGetType = printf "<span class='get-type'>%s</span>" formatGetType = printf "<span class='get-type'>%s</span>"
formatType :: String -> [DisplayData] formatType :: String -> Display
formatType typeStr = [plain typeStr, html $ formatGetType typeStr] formatType typeStr = Display [plain typeStr, html $ formatGetType typeStr]
displayError :: ErrMsg -> [DisplayData] displayError :: ErrMsg -> Display
displayError msg = [plain . fixStdinError . typeCleaner $ msg, html $ formatError msg] displayError msg = Display [plain . fixStdinError . typeCleaner $ msg, html $ formatError msg]
fixStdinError :: ErrMsg -> ErrMsg fixStdinError :: ErrMsg -> ErrMsg
fixStdinError err = fixStdinError err =
......
...@@ -38,7 +38,7 @@ lintIdent = "lintIdentAEjlkQeh" ...@@ -38,7 +38,7 @@ 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 and errors. -- report on linting warnings and errors.
lint :: [Located CodeBlock] -> IO [DisplayData] lint :: [Located CodeBlock] -> IO Display
lint blocks = do lint blocks = do
let validBlocks = map makeValid blocks let validBlocks = map makeValid blocks
fileContents = joinBlocks validBlocks fileContents = joinBlocks validBlocks
...@@ -50,8 +50,8 @@ lint blocks = do ...@@ -50,8 +50,8 @@ lint blocks = do
suggestions <- catMaybes <$> map parseSuggestion <$> hlint [filename, "--quiet"] suggestions <- catMaybes <$> map parseSuggestion <$> hlint [filename, "--quiet"]
return $ return $
if null suggestions if null suggestions
then [] then Display []
else else Display
[plain $ concatMap plainSuggestion suggestions, html $ htmlSuggestions suggestions] [plain $ concatMap plainSuggestion suggestions, html $ htmlSuggestions suggestions]
where where
-- Join together multiple valid file blocks into a single file. -- Join together multiple valid file blocks into a single file.
......
...@@ -12,6 +12,7 @@ module IHaskell.IPython ( ...@@ -12,6 +12,7 @@ module IHaskell.IPython (
readInitInfo, readInitInfo,
defaultConfFile, defaultConfFile,
getIHaskellDir, getIHaskellDir,
getSandboxPackageConf,
nbconvert, nbconvert,
ViewFormat(..), ViewFormat(..),
) where ) where
...@@ -23,7 +24,7 @@ import System.Argv0 ...@@ -23,7 +24,7 @@ import System.Argv0
import System.Directory import System.Directory
import qualified Filesystem.Path.CurrentOS as FS import qualified Filesystem.Path.CurrentOS as FS
import Data.List.Utils (split) import Data.List.Utils (split)
import Data.String.Utils (rstrip) import Data.String.Utils (rstrip, endswith)
import Text.Printf import Text.Printf
import qualified System.IO.Strict as StrictIO import qualified System.IO.Strict as StrictIO
...@@ -122,6 +123,7 @@ nbconvert fmt name = void . shellyNoDir $ do ...@@ -122,6 +123,7 @@ nbconvert fmt name = void . shellyNoDir $ do
Just notebook -> Just notebook ->
let viewArgs = case fmt of let viewArgs = case fmt of
Pdf -> ["--to=latex", "--post=pdf"] Pdf -> ["--to=latex", "--post=pdf"]
Html -> ["--to=html", "--template=ihaskell"]
fmt -> ["--to=" ++ show fmt] in fmt -> ["--to=" ++ show fmt] in
void $ runIHaskell ipythonProfile "nbconvert" $ viewArgs ++ [fpToString notebook] void $ runIHaskell ipythonProfile "nbconvert" $ viewArgs ++ [fpToString notebook]
...@@ -391,3 +393,19 @@ getIHaskellPath = do ...@@ -391,3 +393,19 @@ getIHaskellPath = do
-- If it's actually a relative path, make it absolute. -- If it's actually a relative path, make it absolute.
cd <- liftIO getCurrentDirectory cd <- liftIO getCurrentDirectory
return $ FS.encodeString $ FS.decodeString cd FS.</> f return $ FS.encodeString $ FS.decodeString cd FS.</> f
getSandboxPackageConf :: IO (Maybe String)
getSandboxPackageConf = shellyNoDir $ do
myPath <- getIHaskellPath
let sandboxName = ".cabal-sandbox"
if not $ sandboxName`isInfixOf` myPath
then return Nothing
else do
let pieces = split "/" myPath
sandboxDir = intercalate "/" $ takeWhile (/= sandboxName) pieces ++ [sandboxName]
subdirs <- ls $ fpFromString sandboxDir
let confdirs = filter (endswith "packages.conf.d") $ map fpToString subdirs
case confdirs of
[] -> return Nothing
dir:_ ->
return $ Just dir
...@@ -20,12 +20,15 @@ module IHaskell.Types ( ...@@ -20,12 +20,15 @@ module IHaskell.Types (
Width, Height, Width, Height,
FrontendType(..), FrontendType(..),
ViewFormat(..), ViewFormat(..),
Display(..),
defaultKernelState, defaultKernelState,
extractPlain extractPlain
) where ) where
import ClassyPrelude import ClassyPrelude
import qualified Data.ByteString.Char8 as Char import qualified Data.ByteString.Char8 as Char
import Data.Serialize
import GHC.Generics
import Text.Read as Read hiding (pfail, String) import Text.Read as Read hiding (pfail, String)
import Text.ParserCombinators.ReadP import Text.ParserCombinators.ReadP
...@@ -60,6 +63,12 @@ instance Read ViewFormat where ...@@ -60,6 +63,12 @@ instance Read ViewFormat where
"md" -> return Markdown "md" -> return Markdown
_ -> pfail _ -> pfail
-- | Wrapper for ipython-kernel's DisplayData which allows sending multiple
-- results from the same expression.
data Display = Display [DisplayData]
| ManyDisplay [Display]
deriving (Show, Typeable, Generic)
instance Serialize Display
-- | All state stored in the kernel between executions. -- | All state stored in the kernel between executions.
data KernelState = KernelState data KernelState = KernelState
...@@ -108,9 +117,9 @@ data EvaluationResult = ...@@ -108,9 +117,9 @@ 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 :: [DisplayData] -- ^ Display outputs. outputs :: Display -- ^ Display outputs.
} }
| FinalResult { | FinalResult {
outputs :: [DisplayData], -- ^ Display outputs. outputs :: Display, -- ^ Display outputs.
pagerOut :: String -- ^ Text to display in the IPython pager. pagerOut :: String -- ^ Text to display in the IPython pager.
} }
...@@ -252,7 +252,8 @@ replyTo interface req@ExecuteRequest{ getCode = code } replyHeader state = do ...@@ -252,7 +252,8 @@ replyTo interface req@ExecuteRequest{ getCode = code } replyHeader state = do
header <- dupHeader replyHeader ClearOutputMessage header <- dupHeader replyHeader ClearOutputMessage
send $ ClearOutput header True send $ ClearOutput header True
sendOutput outs = do sendOutput (ManyDisplay manyOuts) = mapM_ sendOutput manyOuts
sendOutput (Display outs) = do
header <- dupHeader replyHeader DisplayDataMessage header <- dupHeader replyHeader DisplayDataMessage
send $ PublishDisplayData header "haskell" outs send $ PublishDisplayData header "haskell" outs
......
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