├── .github ├── FUNDING.yml └── workflows │ └── build-haskell.yml ├── .gitignore ├── .hlint.yaml ├── CHANGELOG.md ├── CONTRIBUTORS ├── LICENSE ├── README.md ├── app ├── main.css ├── package.json ├── preferences.css ├── preferences.html ├── src │ ├── fileformat.js │ ├── glitter.js │ ├── interpreter-worksheet.js │ ├── interpreter.js │ ├── main.js │ ├── preferences-preload.js │ ├── preferences.js │ ├── sequence.js │ ├── worksheet-preload.js │ └── worksheet.js ├── vendor │ ├── FiraCode_1.203 │ │ ├── LICENSE │ │ ├── eot │ │ │ ├── FiraCode-Bold.eot │ │ │ ├── FiraCode-Light.eot │ │ │ ├── FiraCode-Medium.eot │ │ │ └── FiraCode-Regular.eot │ │ ├── fira_code.css │ │ ├── otf │ │ │ ├── FiraCode-Bold.otf │ │ │ ├── FiraCode-Light.otf │ │ │ ├── FiraCode-Medium.otf │ │ │ ├── FiraCode-Regular.otf │ │ │ └── FiraCode-Retina.otf │ │ ├── specimen.html │ │ ├── ttf │ │ │ ├── FiraCode-Bold.ttf │ │ │ ├── FiraCode-Light.ttf │ │ │ ├── FiraCode-Medium.ttf │ │ │ ├── FiraCode-Regular.ttf │ │ │ └── FiraCode-Retina.ttf │ │ ├── woff │ │ │ ├── FiraCode-Bold.woff │ │ │ ├── FiraCode-Light.woff │ │ │ ├── FiraCode-Medium.woff │ │ │ └── FiraCode-Regular.woff │ │ └── woff2 │ │ │ ├── FiraCode-Bold.woff2 │ │ │ ├── FiraCode-Light.woff2 │ │ │ ├── FiraCode-Medium.woff2 │ │ │ └── FiraCode-Regular.woff2 │ ├── codemirror-5.6.css │ ├── codemirror-5.7.js │ ├── codemirror-hs-5.6.js │ ├── jquery-3.1.0.js │ ├── quill-1.1.0.core.css │ └── quill-1.1.0.min.js ├── worksheet.css └── worksheet.html ├── cabal.project ├── docs ├── INSTALL.md ├── TODO.md ├── hcar │ ├── hcar.sty │ ├── html │ │ └── worksheet-diagrams.png │ ├── hyperhaskell-2016-11.tex │ ├── hyperhaskell-2017-05.tex │ ├── hyperhaskell-2017-11.tex │ └── template.tex ├── misc.md └── screenshots │ ├── app-osx.png │ ├── settings-back-end-cabal.png │ ├── settings-back-end-stack.png │ └── worksheet-diagrams.png ├── flake.lock ├── flake.nix ├── haskell ├── hyper-extra │ ├── LICENSE │ ├── hyper-extra.cabal │ └── src │ │ └── Hyper │ │ └── Extra.hs ├── hyper-haskell-server.nix ├── hyper-haskell-server │ ├── LICENSE │ ├── hyper-haskell-server.cabal │ └── src │ │ ├── HyperHaskell │ │ └── Interpreter.hs │ │ └── Main.hs ├── hyper │ ├── LICENSE │ ├── hyper.cabal │ └── src │ │ ├── Hyper.hs │ │ └── Hyper │ │ └── Internal.hs └── stack.yaml ├── justfile ├── resources ├── LICENSE.electron.txt ├── hyper-haskell.desktop ├── icons │ ├── icon.graffle │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ ├── icon.svg │ └── makeicns.sh ├── macOS-Info.plist └── shared-mime-info.xml ├── runHyperHaskell.command └── worksheets ├── Csound.hhs ├── Diagrams.hhs ├── Prelude.hhs ├── Test.hhs └── Test.hs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: http://buymeacoffee.com/hapfelmus 2 | -------------------------------------------------------------------------------- /.github/workflows/build-haskell.yml: -------------------------------------------------------------------------------- 1 | name: Build cabal project 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | types: 6 | - synchronize 7 | - opened 8 | - reopened 9 | merge_group: 10 | push: 11 | branches: 12 | - master 13 | schedule: 14 | # Run once per day (at UTC 18:00) to maintain cache: 15 | - cron: 0 18 * * * 16 | jobs: 17 | build: 18 | name: ${{ matrix.os }}-ghc-${{ matrix.ghc }} 19 | runs-on: ${{ matrix.os }} 20 | continue-on-error: ${{ matrix.allow-failure }} 21 | strategy: 22 | matrix: 23 | allow-failure: 24 | - false 25 | os: 26 | - ubuntu-latest 27 | cabal: 28 | - '3.12' 29 | ghc: 30 | - '8.10' 31 | - '9.2' 32 | - '9.4' 33 | - '9.6' 34 | - '9.8' 35 | - '9.10' 36 | include: 37 | - allow-failure: true 38 | os: ubuntu-latest 39 | cabal: '3.12' 40 | ghc: '9.12' 41 | steps: 42 | - name: Checkout 43 | uses: actions/checkout@v4 44 | 45 | - name: Environment 46 | uses: haskell-actions/setup@v2 47 | id: setup 48 | with: 49 | ghc-version: ${{ matrix.ghc }} 50 | cabal-version: ${{ matrix.cabal }} 51 | 52 | - name: Configure 53 | run: | 54 | cabal configure \ 55 | --enable-tests \ 56 | --enable-benchmarks \ 57 | --enable-documentation \ 58 | --test-show-details=direct \ 59 | --write-ghc-environment-files=always 60 | cabal build all --dry-run 61 | # The last step generates dist-newstyle/cache/plan.json for the cache key. 62 | 63 | # For a description of how the Caching works, see 64 | # https://github.com/haskell-actions/setup?tab=readme-ov-file#model-cabal-workflow-with-caching 65 | - name: Dependencies (Restore from cache) 66 | uses: actions/cache/restore@v4 67 | id: cache 68 | env: 69 | key: ${{ matrix.os }}-ghc-${{ matrix.ghc }} 70 | hash: ${{ hashFiles('**/plan.json') }} 71 | with: 72 | key: ${{ env.key }}-${{ env.hash }} 73 | restore-keys: ${{ env.key }}- 74 | path: ${{ steps.setup.outputs.cabal-store }} 75 | 76 | - name: Dependencies (Install) 77 | if: steps.cache.outputs.cache-hit != 'true' 78 | run: cabal build all --only-dependencies 79 | 80 | # Cache dependencies already here, 81 | # so that we do not have to rebuild them should the subsequent steps fail. 82 | - name: Dependencies (Save cache) 83 | uses: actions/cache/save@v4 84 | # If we had an exact cache hit, 85 | # trying to save the cache would error because of key clash. 86 | if: steps.cache.outputs.cache-hit != 'true' 87 | with: 88 | key: ${{ steps.cache.outputs.cache-primary-key }} 89 | path: ${{ steps.setup.outputs.cabal-store }} 90 | 91 | - name: Build 92 | run: > 93 | cabal build all 94 | --enable-tests 95 | --enable-benchmarks 96 | 97 | - name: Test 98 | run: > 99 | cabal test all 100 | 101 | - name: Benchmark 102 | run: > 103 | cabal bench all 104 | || true 105 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | .cabal-sandbox/ 3 | cabal.sandbox.config 4 | dist/ 5 | dist-newstyle/ 6 | .stack-work 7 | /.dist-buildwrapper 8 | /.project 9 | /.externalToolBuilders 10 | *.sublime-workspace 11 | node_modules 12 | /build 13 | /haskell/vendor/ 14 | -------------------------------------------------------------------------------- /.hlint.yaml: -------------------------------------------------------------------------------- 1 | # HLint configuration file 2 | # https://github.com/ndmitchell/hlint 3 | ########################## 4 | 5 | # This file contains a template configuration file, which is typically 6 | # placed as .hlint.yaml in the root of your project 7 | 8 | 9 | # Warnings currently triggered by your code 10 | - ignore: {name: "Avoid lambda"} # 1 hint 11 | - ignore: {name: "Avoid lambda using `infix`"} # 6 hints 12 | - ignore: {name: "Eta reduce"} # 20 hints 13 | - ignore: {name: "Evaluate"} # 7 hints 14 | - ignore: {name: "Hoist not"} # 2 hints 15 | - ignore: {name: "Missing NOINLINE pragma"} # 1 hint 16 | - ignore: {name: "Move brackets to avoid $"} # 12 hints 17 | - ignore: {name: "Redundant $"} # 5 hints 18 | - ignore: {name: "Redundant bracket"} # 10 hints 19 | - ignore: {name: "Redundant flip"} # 1 hint 20 | - ignore: {name: "Redundant lambda"} # 1 hint 21 | - ignore: {name: "Use <$>"} # 2 hints 22 | - ignore: {name: "Use const"} # 1 hint 23 | - ignore: {name: "Use fromMaybe"} # 1 hint 24 | - ignore: {name: "Use lambda-case"} # 1 hint 25 | - ignore: {name: "Use list literal"} # 13 hints 26 | - ignore: {name: "Use list literal pattern"} # 4 hints 27 | - ignore: {name: "Use map once"} # 2 hints 28 | - ignore: {name: "Use newtype instead of data"} # 1 hint 29 | - ignore: {name: "Use section"} # 8 hints 30 | - ignore: {name: "Use tuple-section"} # 7 hints 31 | 32 | 33 | # Specify additional command line arguments 34 | # 35 | # - arguments: [--color, --cpp-simple, -XQuasiQuotes] 36 | 37 | 38 | # Control which extensions/flags/modules/functions can be used 39 | # 40 | # - extensions: 41 | # - default: false # all extension are banned by default 42 | # - name: [PatternGuards, ViewPatterns] # only these listed extensions can be used 43 | # - {name: CPP, within: CrossPlatform} # CPP can only be used in a given module 44 | # 45 | # - flags: 46 | # - {name: -w, within: []} # -w is allowed nowhere 47 | # 48 | # - modules: 49 | # - {name: [Data.Set, Data.HashSet], as: Set} # if you import Data.Set qualified, it must be as 'Set' 50 | # - {name: Control.Arrow, within: []} # Certain modules are banned entirely 51 | # 52 | # - functions: 53 | # - {name: unsafePerformIO, within: []} # unsafePerformIO can only appear in no modules 54 | 55 | 56 | # Add custom hints for this project 57 | # 58 | # Will suggest replacing "wibbleMany [myvar]" with "wibbleOne myvar" 59 | # - error: {lhs: "wibbleMany [x]", rhs: wibbleOne x} 60 | 61 | # The hints are named by the string they display in warning messages. 62 | # For example, if you see a warning starting like 63 | # 64 | # Main.hs:116:51: Warning: Redundant == 65 | # 66 | # You can refer to that hint with `{name: Redundant ==}` (see below). 67 | 68 | # Turn on hints that are off by default 69 | # 70 | # Ban "module X(module X) where", to require a real export list 71 | # - warn: {name: Use explicit module export list} 72 | # 73 | # Replace a $ b $ c with a . b $ c 74 | # - group: {name: dollar, enabled: true} 75 | # 76 | # Generalise map to fmap, ++ to <> 77 | # - group: {name: generalise, enabled: true} 78 | # 79 | # Warn on use of partial functions 80 | # - group: {name: partial, enabled: true} 81 | 82 | 83 | # Ignore some builtin hints 84 | # - ignore: {name: Use let} 85 | # - ignore: {name: Use const, within: SpecialModule} # Only within certain modules 86 | 87 | 88 | # Define some custom infix operators 89 | # - fixity: infixr 3 ~^#^~ 90 | 91 | 92 | # To generate a suitable file for HLint do: 93 | # $ hlint --default > .hlint.yaml 94 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog for HyperHaskell 2 | 3 | 4 | ### HyperHaskell app bundle 5 | 6 | **v2.3.0** — Snapshot release. 7 | 8 | * Add `~/.ghcup` directory to PATH by default. 9 | * Add `View` menu and zoom functionality. 10 | * Update Electron to version 10.1.5. 11 | 12 | **v2.2.0** — Snapshot release. 13 | 14 | * Implement "Search path" field properly. 15 | * Add "Open Recent…" menu item on macOS. 16 | 17 | * Update Electron to version 3.1.8. 18 | * Support for packaging binaries on Windows (by Nicholas Silvestrin). 19 | 20 | * Fix "Save As…" menu item to work correctly again. 21 | * Fix bug where no new input cell would be created when evaluating the last one. 22 | 23 | **v2.1.0** — Snapshot release. 24 | 25 | * Upgrade file format to version `0.2.1.0`. 26 | * Add input field for setting the language extensions that are used for interpreting code. 27 | 28 | **v2.0.0** — Snapshot release. 29 | 30 | * Upgrade file format to version `0.2.0.0`. Conversion of old files happens *automatically* when loading them in the application. 31 | * Support different cell types: 32 | 1. 'code' cells -- for source code to be evaluated 33 | 2. 'text' cells -- plain text format 34 | * Compatibility with the [Nix package manager][nix] (by Rodney Lorrimar). 35 | 36 | [nix]: https://nixos.org/nix/ 37 | 38 | **v1.0.0** 39 | 40 | * Initial release. 41 | 42 | ### `hyper-haskell-server` package 43 | 44 | **v0.2.3.1** — Bump dependencies 45 | 46 | **v0.2.3.0** 47 | 48 | * Compatibility with GHC 8.8. 49 | * Remove support for `hyper == 0.1.*`. 50 | 51 | **v0.2.2.0** 52 | 53 | * Add `:type` command for querying type signatures. 54 | * Add support for qualified imports with optional aliases. For example, lines in the "Module imports" field can now have the form `import qualified Data.Map as Map`. [#27][] 55 | 56 | [#27]: https://github.com/HeinrichApfelmus/hyper-haskell/issues/27 57 | 58 | **v0.2.1.0** 59 | 60 | * Add interpretation of `let` statements and generator statements with patterns, `pat <- stmt`. The same syntax as GHC is accepted. 61 | * Add `setExtensions` method to set the language extensions that are used for interpreting code. 62 | * Compatibility with GHC 8.4. 63 | 64 | **v0.2.0.0** 65 | 66 | * Add interpretation of statements of the form `name <- stmt`. This allows us to bind values and functions to names. Here, `stmt` is an IO action with a result. 67 | 68 | **v0.1.0.2** — Bump dependencies for compatibility with GHC 8.2 69 | 70 | **v0.1.0.1** — Bump dependencies. 71 | 72 | **v0.1.0.0** — Initial release. 73 | 74 | ### `hyper-extra` package 75 | 76 | **v0.2.0.1** — Bump dependencies 77 | 78 | **v0.2.0.0** 79 | 80 | * Add preliminary support for the `svg-builder` package. 81 | * Compatibility with GHC 8.8 82 | 83 | **v0.1.1.0** 84 | 85 | * Add preliminary support for the `QuickCheck` package. 86 | 87 | **v0.1.0.3** — Bump dependencies for compatibility with GHC 8.4 88 | 89 | **v0.1.0.2** — Bump dependencies for compatibility with GHC 8.2 90 | 91 | **v0.1.0.1** — Bump dependencies. 92 | 93 | **v0.1.0.0** — Initial release. 94 | 95 | ### `hyper` package 96 | 97 | **v0.2.1.1** — Bump dependencies 98 | 99 | **v0.2.1.0** 100 | 101 | * Add `addFinalizerSession` function that allows running an IO action when the worksheet is reloaded. 102 | * Compatibility with GHC 8.8. 103 | 104 | **v0.2.0.0** 105 | 106 | * Remove `displayIO` class. 107 | * Make `string` wrap its contents in a `
` element. 108 | 109 | **v0.1.0.3** — Bump dependencies for compatibility with GHC 8.4 110 | 111 | **v0.1.0.2** — Bump dependencies for compatibility with GHC 8.2 112 | 113 | **v0.1.0.1** — Bump dependencies. 114 | 115 | **v0.1.0.0** — Initial release. 116 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Moritz Angermann 2 | Heinrich Apfelmus 3 | Xavier Góngora 4 | Gabor Greif 5 | Simon Jakobi 6 | Rodney Lorrimar 7 | Raunak Ramakrishnan 8 | Karshan Sharma 9 | Nicholas Silvestrin 10 | Henning Thielemann 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software consists of different parts with different licenses. 2 | Most parts of the software are released under a BSD3-style license, 3 | specified in a corresponding LICENSE file. 4 | 5 | However, some parts of this software are from third parties, 6 | in particular those files contained in any subdirectory named `lib`. 7 | The corresponding licenses are either described in the source code files, 8 | or appropriate LICENSE files. 9 | 10 | Aside from these exceptions, the following license applies: 11 | 12 | ---- 13 | 14 | Copyright (c) 2016-2024 Heinrich Apfelmus 15 | 16 | All rights reserved. 17 | 18 | Redistribution and use in source and binary forms, with or without 19 | modification, are permitted provided that the following conditions are met: 20 | 21 | * Redistributions of source code must retain the above copyright 22 | notice, this list of conditions and the following disclaimer. 23 | 24 | * Redistributions in binary form must reproduce the above 25 | copyright notice, this list of conditions and the following 26 | disclaimer in the documentation and/or other materials provided 27 | with the distribution. 28 | 29 | * Neither the name of Heinrich Apfelmus nor the names of other 30 | contributors may be used to endorse or promote products derived 31 | from this software without specific prior written permission. 32 | 33 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 34 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 35 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 36 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 37 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 38 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 39 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 40 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 41 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 42 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 43 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to HyperHaskell, 2 | 3 | … the strongly hyped Haskell interpreter. 4 | 5 | [![Hackage](https://img.shields.io/hackage/v/hyper-haskell-server.svg)](https://hackage.haskell.org/package/hyper-haskell-server) 6 | ![Build Status](https://github.com/HeinrichApfelmus/hyper-haskell/actions/workflows/build-haskell.yml/badge.svg?branch=master) 7 | ![Hype level](https://img.shields.io/badge/hype-level_%CE%B1-ee40bb.svg) 8 | 9 | 10 | *HyperHaskell* is a graphical interpreter for the programming language [Haskell][]. You use worksheets to enter expressions and evaluate them. Results are displayed graphically using HTML. 11 | 12 | *HyperHaskell* is intended to be *easy to install*. It is cross-platform and should run on Linux, Mac and Windows. Internally, it uses the [GHC][] API to interpret Haskell programs, and the graphical front-end is built on the cross-platform [Electron][] framework. 13 | 14 | *HyperHaskell*'s main attraction is a [`Display`][display] class that supersedes the good old [`Show`][show] class. The result looks like this: 15 | 16 | 17 | 18 | [haskell]: https://haskell.org 19 | [show]: http://hackage.haskell.org/package/base/docs/Prelude.html#t:Show 20 | [display]: https://hackage.haskell.org/package/hyper/docs/Hyper.html#t:Display 21 | [ghc]: https://www.haskell.org/ghc/ 22 | [electron]: http://electron.atom.io/ 23 | [stack]: https://www.haskellstack.org 24 | [release]: ../../releases 25 | 26 | # Installation 27 | ## Overview 28 | 29 | *HyperHaskell* is intended to be easy to install. The easiest way to install it is to download the binary distribution. This is explained in the next subsection. However, there is a pitfall which you have to know about, and which requires knowledge of the installation structure. 30 | 31 | A HyperHaskell installation consists of two parts: 32 | 33 | 1. The graphical front-end. 34 | 35 | Currently written in HTML and JavaScript, packaged with the [Electron][] framework. 36 | 37 | 2. The interpreter back-end. 38 | 39 | Consists of an executable `hyper-haskell-server`, 40 | written in Haskell using the [GHC API][ghc], 41 | and a library (module) `Hyper` for visualizing and pretty printing Haskell values. 42 | 43 | Both parts depend on several different Haskell packages. 44 | Unfortunately, the versions of the packages used to compile the executable 45 | and to compile the library have to be exactly the same. 46 | 47 | This is why, at the moment, 48 | the front-end does *not* come with the back-end executable included. 49 | Instead, the user is asked to install the `hyper-haskell-server` back-end 50 | into his or her own database of Haskell packages, 51 | and then tell the front-end about it. 52 | This way, the user is free to use different package or compiler versions. 53 | 54 | ## Installation of the binary distribution 55 | 56 | Installation from the binary distribution follows the structure explained above. 57 | 58 | 1. [Download the graphical front-end from the latest release][release] and unpack it. 59 | 60 | ![App](docs/screenshots/app-osx.png) 61 | 62 | At the moment, binaries for macOS and Windows (thanks to Nicholas Silvestrin) are provided. Since Heinrich Apfelmus only has access to a macOS machine, help is appreciated! 63 | 64 | 2. Install the back-end server 65 | 66 | 1. Make sure that you have a working installation of the [GHC][] Haskell compiler. 67 | 68 | 2. Install the back-end with Cabal by executing 69 | 70 | cabal install hyper hyper-haskell-server 71 | 72 | It is also recommended (but not necessary) that you install the additional support for other popular Haskell packages, e.g. the [Diagrams][] library by additionally executing 73 | 74 | cabal install hyper-extra 75 | 76 | 3. Now you can start the front-end application and create a new worksheet, or open an existing one. Make sure that the "Interpreter Back-end" in the "Settings" section of the worksheet is set to "cabal". (The path field does not matter in this case.) 77 | 78 | ![Settings](docs/screenshots/settings-back-end-cabal.png) 79 | 80 | It is also possible to use [Stack][] by using `stack install`, but that is not fully explained here, only to some extent below. 81 | 82 | That's it! Happy hyper! 83 | 84 | [diagrams]: https://github.com/diagrams 85 | 86 | ## Run from source 87 | 88 | When developing HyperHaskell itself, it is also possible to run it from source. Follow these steps: 89 | 90 | 1. [Download and install Electron](http://electron.atom.io/releases/) 91 | 92 | The whole thing is currently developed and tested with Electron version 10.1.5. 93 | 94 | (If you use the [npm][] package manager, you can install it in your home directory with `cd ~ && npm install electron@10.5.1`. 95 | On Debian-based Linux distributions, Electron [currently](https://github.com/electron-userland/electron-prebuilt/issues/70#issuecomment-192520913) requires the `nodejs-legacy` package.) 96 | 97 | 2. Make sure that you have a working installation of 98 | * the [GHC][] Haskell compiler 99 | * the [`cabal`][cabal] utility 100 | 101 | (See the [Haskell homepage][haskell] for more on how to obtain these.) 102 | 103 | 3. You also need the `just` utility, which should be available for any UN*X platform. Edit the file named [`justfile`](justfile) and tell it where to find the Electron executable 104 | 105 | On macOS: Typically, 106 | 107 | ELECTRON := "/Applications/Electron.app/Contents/MacOS/Electron" 108 | 109 | On Linux: Typically, 110 | 111 | ELECTRON := "/usr/local/bin/electron" 112 | 113 | On Windows: You can locate `electron.exe` and double-click it. Then simply drop the `hyper-haskell\app` folder onto the lower pane of the window. Alternatively, from the terminal invoke what is suggested in the upper portion of the `Electron` window, i.e. 114 | 115 | \electron.exe hyper-haskell\app 116 | 117 | 4. Go into the root directory of this repository and type `just run`. 118 | 119 | $ cd hyper-haskell 120 | $ just run 121 | 122 | This will call the `cabal` utility to build the server back-end, 123 | and finally run the front-end. 124 | 125 | 5. Use the *File* menu to open one of the example worksheets from the [worksheets](worksheets/) folder. Voilà! 126 | 127 | You can also create a new worksheet, but note that you have to set the back-end path in the "Settings" section. The path is relative to the directory where the worksheet was saved. For instance, if you run a worksheet from the [worksheets](worksheets/) directory, the path `../haskell/stack.yaml` will point to the right `hyper-haskell-server` executable. Screenshot: 128 | 129 | ![Settings](docs/screenshots/settings-back-end-stack.png) 130 | 131 | Note that for this setting, the `stack` utility has to be in your path. You can also set an explicit path for this utility in the "Preferences…" menu item. 132 | 133 | 134 | ## Packaging 135 | 136 | The normal way to obtain HyperHaskell is to download the application bundle in binary form. This section describes how to generate this from source. 137 | 138 | We use the [`electron-packager`][pkg] utility. To install it, you need to use the [npm][] package manager and execute the following commands: 139 | 140 | cd ~ 141 | npm install electron-packager 142 | npm install electron@10.1.5 --save-dev 143 | 144 | To create an application bundle and compress it in a zip-file, use the following commands: 145 | 146 | * On macOS: 147 | 148 | make pkg-darwin 149 | make zip-darwin 150 | 151 | * On Windows: You need the [7zip](https://www.7-zip.org) utility in your path. 152 | 153 | make pkg-win32 154 | nake zip-win32 155 | 156 | * On Linux: not implemented yet 157 | 158 | [npm]: https://www.npmjs.com/ 159 | [pkg]: https://github.com/electron-userland/electron-packager 160 | 161 | # Thanks 162 | 163 | Many thanks to everyone who contributed, provided feedback or simply found a nice application for HyperHaskell! In particular, many thanks to: 164 | 165 | Moritz Angermann, Simon Jakobi, Rodney Lorrimar, Karshan Sharma, Nicholas Silvestrin, [*and many others*](CONTRIBUTORS). 166 | 167 | The project was started by *Heinrich Apfelmus*. 168 | 169 | Development of this project has been generously supported by [Predictable Network Solutions Ltd. — the network performance science company][pnsol]. 📡 📶 170 | 171 | [pnsol]: https://www.pnsol.com 172 | -------------------------------------------------------------------------------- /app/main.css: -------------------------------------------------------------------------------- 1 | /* ************************************************************* 2 | General settings 3 | ************************************************************* */ 4 | 5 | .window { margin: 0px; } 6 | 7 | body { 8 | font-family: "Helvetica"; 9 | } 10 | 11 | input { 12 | font-size: 80%; 13 | padding: 0.8ex; 14 | padding-bottom: 0.5ex; 15 | } 16 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "HyperHaskell", 3 | "version": "2.3.1", 4 | "main": "src/main.js", 5 | "devDependencies": { 6 | "electron": "^31.4.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /app/preferences.css: -------------------------------------------------------------------------------- 1 | .window { margin: 1em; } 2 | label { display:block; } 3 | input, label { 4 | margin-top:2ex; 5 | } 6 | input { 7 | width:100%; 8 | } -------------------------------------------------------------------------------- /app/preferences.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | Preferences 13 | 14 | 15 |
16 |
17 | 18 | 19 |
20 |
21 | 22 | -------------------------------------------------------------------------------- /app/src/fileformat.js: -------------------------------------------------------------------------------- 1 | /* ************************************************************* 2 | Worksheet file format 3 | 4 | Compatibility and conversion utilities. 5 | 6 | We try to be loosely compatible with the Jupyter notebook format, 7 | . 8 | Right now, this is very limited, and only applies to some naming 9 | conventions. 10 | 11 | Sorry, the specification of the file format 12 | has to be guess from the source code in `worksheet.js` 13 | for now. 14 | ************************************************************* */ 15 | 16 | window.fileformat = {} 17 | 18 | window.fileformat.parse = (data) => { return JSON.parse(data) } 19 | window.fileformat.stringify = (json) => { return JSON.stringify(json,null,2) } 20 | 21 | // Document with a single, empty cell 22 | window.fileformat.single = () => { return { 23 | version : '0.2.1.0', 24 | cells : [{ 25 | 'cell_type' : 'code', 26 | source : '', 27 | }], 28 | extensions : '', 29 | importModules : '', 30 | loadFiles : '', 31 | settings : { 32 | packageTool : 'cabal', 33 | packagePath : '', 34 | searchPath : '', 35 | }, 36 | } 37 | } 38 | 39 | // Update the file format to a new version 40 | window.fileformat.update = (versionNew, input) => { 41 | // FIXME: Make sure that the JSON object is actually copied. 42 | let output = input 43 | let versionOld = input.version 44 | 45 | // Remark: We use lexicographic ordering to test whether we should upgrade 46 | if (versionOld === '0.1.0.0' && versionNew >= '0.2.0.0') { 47 | output.cells = input.cells.map( (source) => { 48 | return { 49 | 'cell_type' : 'code', 50 | 'source' : source 51 | } 52 | }) 53 | versionOld = '0.2.0.0' 54 | output.version = versionOld 55 | } 56 | 57 | // We can perform several upgrades in sequence. 58 | 59 | return output 60 | } 61 | -------------------------------------------------------------------------------- /app/src/glitter.js: -------------------------------------------------------------------------------- 1 | /* ************************************************************* 2 | Visual enhancements for the worksheet window 3 | ************************************************************* */ 4 | 5 | $(document).ready(() => { 6 | $('h1').click(function () { 7 | $(this).toggleClass('closed') 8 | // hide/show the next elements, which should be
containing the section 9 | $(this).next().toggle() 10 | }) 11 | }) -------------------------------------------------------------------------------- /app/src/interpreter-worksheet.js: -------------------------------------------------------------------------------- 1 | /* ************************************************************* 2 | Interacting with a Haskell interpreter instance 3 | Renderer 4 | ************************************************************* */ 5 | 6 | /* Requires 7 | window.electron.startInterpreter 8 | window.electron.onDoneInterpreterStart 9 | */ 10 | window.newInterpreter = () => { 11 | var that = {} 12 | 13 | let myPort = 0 // port number of the connected interpreter 14 | let down = 'not started' // error message why the interpreter may be down 15 | let packagePath = null // last used path for the interpreter executable 16 | let packageTool = null // last used package tool 17 | let cwd = null // last used current working directory 18 | let files = [] // list of previously loaded files 19 | 20 | // send an ajax request to the interpreter process 21 | const ajax = (config, cont) => { 22 | if (!down) { 23 | // interpreter is up and running 24 | if (cont) { $.ajax(config).done(cont) } else { $.ajax(config) } 25 | } else { 26 | // interpreter is down, return reason. 27 | // + JSON.stringify(require('process').env) 28 | cont({ 29 | status: 'error', 30 | errors: ['Interpreter not running (Error: ' + down + ')'] 31 | }) 32 | } 33 | } 34 | 35 | // Evaluate an expression 36 | // cont = ({ status : 'ok'/'error' , value:... }) => { ... } 37 | that.eval = (expr, cont) => { 38 | console.log('interpreter.eval') 39 | ajax({ 40 | method : 'POST', 41 | url : 'http://localhost:' + myPort.toString() + '/eval', 42 | data : { query: expr }, 43 | dataType: 'json' 44 | }, cont) 45 | } 46 | 47 | // Cancel evaluation 48 | that.cancel = () => { 49 | console.log('interpreter.cancel') 50 | ajax({ 51 | method : 'POST', 52 | url : 'http://localhost:' + myPort.toString() + '/cancel' 53 | }) 54 | } 55 | 56 | // config.imports. = list of module imports 57 | // config.extensions = list of extension to use 58 | // config.files = new list of files to load 59 | // config.searchPath = search path for finding these files 60 | // config.cwd = directory that the notebook is contained in 61 | // cont = continuation in case of error or success 62 | const loadImports = (config, cont) => { 63 | const withLoadedFiles = (importModules) => { 64 | // load source code files only if absolutely necessary, 65 | // because that resets the interpreter state 66 | if (files !== config.files) { 67 | ajax({ 68 | method : 'POST', 69 | url : 'http://localhost:' + myPort.toString() + '/setSearchPath', 70 | data : { query: config.searchPath, dir: config.cwd }, 71 | dataType: 'json' 72 | }, (result) => { 73 | if (result.status === 'ok') { 74 | ajax({ 75 | method : 'POST', 76 | url : 'http://localhost:' + myPort.toString() + '/loadFiles', 77 | data : { query: config.files.join(',') }, 78 | dataType: 'json' 79 | }, (result) => { 80 | if (result.status === 'ok') { 81 | files = config.files 82 | importModules() 83 | } else { cont(result) } 84 | }) 85 | } else { cont(result) } 86 | }) 87 | } else { importModules() } 88 | } 89 | 90 | withLoadedFiles( () => { 91 | ajax({ 92 | method : 'POST', 93 | url : 'http://localhost:' + myPort.toString() + '/setImports', 94 | data : { query: config.imports.join(',') }, 95 | dataType: 'json' 96 | }, (result) => { 97 | if (result.status === 'ok') { 98 | ajax({ 99 | method : 'POST', 100 | url : 'http://localhost:' + myPort.toString() + '/setExtensions', 101 | data : { query: config.extensions.join(',') }, 102 | dataType: 'json' 103 | }, cont) 104 | } else { cont(result) } 105 | }) 106 | }) 107 | } 108 | 109 | // Load modules, perhaps spawn a new process 110 | that.loadImports = (config, cont) => { 111 | console.log('interpreter.loadImports') 112 | const doImports = () => { 113 | console.log('interpreter.doImports') 114 | loadImports(config, cont) 115 | } 116 | if ( config.packagePath !== packagePath 117 | || config.packageTool !== packageTool 118 | || config.cwd !== cwd) { 119 | // we have to spawn a new interpreter process with the right package tool and path 120 | packagePath = config.packagePath 121 | packageTool = config.packageTool 122 | cwd = config.cwd 123 | // load imports when interpreter is done loading 124 | that.onInterpreterStarted(doImports) 125 | window.electron.startInterpreter(cwd, packageTool, packagePath) 126 | } else { 127 | // the interpreter is running and we simply reload imports 128 | doImports() 129 | } 130 | } 131 | 132 | // Register a callback for when the the interpreter has started. 133 | that.callbackInterpreterStarted = () => {} 134 | that.onInterpreterStarted = (callback) => { 135 | that.callbackInterpreterStarted = callback 136 | } 137 | that.init = (window) => { 138 | console.log('interpreter.init') 139 | window.electron.onDoneInterpreterStart( (port, err) => { 140 | down = err 141 | myPort = port 142 | that.callbackInterpreterStarted() 143 | }) 144 | } 145 | 146 | return that 147 | } 148 | -------------------------------------------------------------------------------- /app/src/interpreter.js: -------------------------------------------------------------------------------- 1 | /* ************************************************************* 2 | Managing Haskell interpreter instances 3 | Main process 4 | ************************************************************* */ 5 | 6 | /* ************************************************************* 7 | NOTE [InterpreterLifetime] 8 | ************************************************************* */ 9 | /* 10 | This note describes the communication flow for starting 11 | interpreter processes. 12 | 13 | The browser window does not care whether an interpreter 14 | process has been started or not. The window simply calls 15 | the function `interpreter.loadImports()` to bring 16 | imports into scope. This function has to take care of 17 | starting or stopping interpreter instances accordingly. 18 | 19 | Interpreter processes are managed by the main process. 20 | The `interpreter.loadImports()` function sends 21 | an IPC event 'startInterpreter' to the main process 22 | in order to start an interpreter process. 23 | Once the interpreter has been started, the main process 24 | sends an IPC event 'doneInterpreterStart' to the 25 | browser window. 26 | 27 | As starting the interpreter happens asynchronously, 28 | two classes of errors may happen: 29 | 30 | 1) The browser window is closed before the 31 | 'doneInterpreterStart' event could be sent. 32 | In this case, the main process kills the interpreter again. 33 | 34 | 2) The browser window attempts to evaluate 35 | code before the interpreter has started. 36 | 37 | */ 38 | 39 | const child_process = require('child_process') 40 | const {app , BrowserWindow , ipcMain} = require('electron') 41 | const lib = { 42 | path: require('node:path'), 43 | process: require('process') 44 | } 45 | 46 | let ghcs = [] // interpreter processes for different window IDs 47 | // FIXME: Obtain temporary ports more properly 48 | let ports = 14200 // keep track of assigned port numbers 49 | 50 | let stackPath = '' // path to the stack utility 51 | exports.setPaths = (p) => { 52 | stackPath = p 53 | } 54 | 55 | // Initialize the interpreter in the main process 56 | exports.init = () => { 57 | const isWindows = lib.process.platform === "win32"; 58 | const appdir = lib.path.dirname(app.getAppPath()) 59 | 60 | // function that spawns a new interpreter process 61 | ipcMain.on('startInterpreter', (event, cwd, packageTool, packagePath) => { 62 | // kill previous interpreter if necessary 63 | const id = BrowserWindow.fromWebContents(event.sender).id 64 | exports.kill(id) 65 | 66 | // assign a new port number 67 | let port = ports 68 | ports += 1 69 | 70 | // setup environment 71 | let env = {} 72 | env['HOME'] = app.getPath('home') // necessary for finding package database 73 | // FIXME: Where to we get the path from as a standalone application? 74 | // pick up path from external environment if possible 75 | env['PATH'] = lib.process.env['PATH'] + ':/usr/bin:/usr/local/bin:' + env['HOME'] + '/.ghcup/bin' 76 | env['PORT'] = port.toString() 77 | let cmd = '' 78 | let args = [] 79 | 80 | if (packageTool == 'stack') { 81 | if (stackPath) { 82 | cmd = stackPath 83 | } else { 84 | cmd = 'stack' 85 | } 86 | args = ['--stack-yaml=' + packagePath, 'exec', '--', 'hyper-haskell-server'] 87 | } else if (packageTool == 'cabal') { 88 | if (isWindows) { 89 | cmd = env['HOME'] + '\\AppData\\Roaming\\cabal\\bin\\hyper-haskell-server' 90 | } else { 91 | cmd = env['HOME'] + '/.cabal/bin/hyper-haskell-server' 92 | } 93 | } else if (packageTool == 'cabal.project') { 94 | cmd = 'cabal' 95 | // FIXME: Add cabal project dir that is relative to the current worksheet 96 | // args = ['exec', '--project-dir', lib.path.dirname(packagePath), 'hyper-haskell-server'] 97 | args = ['exec', 'hyper-haskell-server'] 98 | } else if (packageTool == 'nix') { 99 | cmd = 'hyper-haskell-server' 100 | env = Object.assign({}, lib.process.env, { PORT: port.toString() }) 101 | } 102 | 103 | // spawn process 104 | let spawning = true 105 | let pipeFD = 3 // file descriptor for pipe 106 | let ghc = child_process.spawn(cmd, args.concat([pipeFD.toString()]), { 107 | cwd : cwd, 108 | env : env, 109 | stdio : ['inherit', 'inherit', 'inherit', 'pipe'], 110 | encoding : 'utf8', 111 | }) 112 | 113 | const doReady = (error) => { 114 | spawning = false 115 | if (!error) { ghcs[id] = ghc } else { port = 0 } 116 | // Indicate that the interpreter is ready only when the window is still alive 117 | // See Note [InterpreterLifetime] 118 | if (event.sender.isDestroyed()) { 119 | exports.main.kill(id) 120 | } else { 121 | event.sender.send('done-interpreter-start', port, error) 122 | } 123 | } 124 | 125 | // Interpreter reports that it is ready 126 | ghc.stdio[pipeFD].on('data', (data) => { 127 | if (data.toString() === "ready") { 128 | doReady(null) 129 | } else { 130 | doReady('Malformed interpreter response: ' + data.toString()) 131 | } 132 | }) 133 | ghc.on('error', (err) => { 134 | // set an error code when the interpreter process could not be started 135 | console.log('Interpreter not running: ' + err.message) 136 | doReady(err.message) 137 | }) 138 | ghc.on('exit', (code, signal) => { 139 | // FIXME: Report a more useful error here. 140 | // Unfortuntaley, we can't read `stderr` once the process is dead. 141 | // Perhaps cache it in some way? 142 | // Idea: The interpreter executable can print the error to 143 | // the pipeFD descriptor, as opposed to stdout 144 | error = code ? code.toString() : '0'; 145 | console.log('Interpreter stopped (Exit code ' + error + ')') 146 | if (spawning) { doReady(error) } 147 | // FIXME: Tell browser window something appropriate when the interpreter dies unexpectedly 148 | }) 149 | setTimeout( () => { 150 | if (spawning) { doReady('Interpreter timeout') } 151 | }, 10*1000) 152 | }) 153 | } 154 | 155 | // Kill interpreter associated to a particular window ID 156 | exports.kill = (id) => { 157 | // FIXME: only send signal if child process is still running! 158 | // otherwise another process with that PID could get the signal. 159 | if (ghcs[id]) { 160 | ghcs[id].kill('SIGTERM') 161 | ghcs[id] = null 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /app/src/main.js: -------------------------------------------------------------------------------- 1 | /* ************************************************************* 2 | Main program 3 | ************************************************************* */ 4 | let TESTING = require('process').env['TESTING'] ? true : false 5 | 6 | const electron = require('electron') 7 | const { app, BrowserWindow, dialog, ipcMain, Menu, MenuItem } = electron 8 | const fs = require('node:fs') 9 | const interpreter = require('./interpreter.js') 10 | 11 | const lib = { 12 | path: require('node:path'), 13 | process: require('process') 14 | } 15 | const appdir = lib.path.normalize(__dirname + "/..") 16 | const resolvePath = lib.path.resolve 17 | 18 | /* **************************************************************** 19 | Initialization 20 | **************************************************************** */ 21 | 22 | app.on('ready', () => { 23 | // initialize interpreter 24 | interpreter.init() 25 | // setup menu bar 26 | Menu.setApplicationMenu(Menu.buildFromTemplate(template)) 27 | 28 | // load preferences and apply them 29 | applyPreferences(loadPreferences()) 30 | ipcMain.on('save-preferences', (event, prefs) => { 31 | applyPreferences(prefs) 32 | savePreferences(prefs) 33 | }) 34 | 35 | // [setDocumentEdited] 36 | // We use an event handler to call "setDocumentEdited" on a window 37 | ipcMain.on('setDocumentEdited', (event, value) => { 38 | BrowserWindow.fromWebContents(event.sender).setDocumentEdited(value) 39 | }) 40 | 41 | ipcMain.handle('getRepresentedFilename', async (event) => { 42 | return BrowserWindow.fromWebContents(event.sender).getFilePath() 43 | }) 44 | ipcMain.handle('getCurrentWorkingDirectory', async (event) => { 45 | const filepath = BrowserWindow.fromWebContents(event.sender).getFilePath() 46 | return filepath ? lib.path.dirname(filepath) : lib.process.env['HOME'] 47 | }) 48 | ipcMain.handle('readFileSync', async (event, ...args) => { 49 | return fs.readFileSync(...args) 50 | }) 51 | ipcMain.on('writeFileSync', async (event, ...args) => { 52 | fs.writeFileSync(...args) 53 | }) 54 | 55 | // restore 'open-file' to open files directly, instead of queuing them 56 | app.removeListener('open-file', addPathToOpen) 57 | app.on('open-file', (event, path) => { 58 | event.preventDefault() 59 | newWorksheet(path) 60 | }) 61 | // open all previously queued files 62 | for (let i=0; i < pathsToOpen.length; i++) { 63 | fs.access(pathsToOpen[i], (err) => { 64 | if (!err) { newWorksheet(pathsToOpen[i]) } 65 | }) 66 | } 67 | // always open a window on startup 68 | if (pathsToOpen.length === 0) { newWorksheet() } 69 | }) 70 | 71 | // The `app` object may receive an 'open-file' event *before* the 'ready' event. 72 | // In this case, simply queue the filepath. 73 | let pathsToOpen = [] 74 | // load an example worksheet right away on startup 75 | if (TESTING) { pathsToOpen.push(appdir + '/../worksheets/Test.hhs') } 76 | if (process.argv[2]) { pathsToOpen.push(process.argv[2]) } 77 | 78 | let addPathToOpen = (event, path) => { 79 | event.preventDefault() 80 | pathsToOpen.push(path) 81 | } 82 | app.on('open-file', addPathToOpen) 83 | 84 | // quit when all windows are closed 85 | app.on('window-all-closed', () => { 86 | if (process.platform !== 'darwin') { app.quit() } 87 | }) 88 | 89 | /* **************************************************************** 90 | "Preferences" window 91 | **************************************************************** */ 92 | const prefPath = app.getPath('userData') + '/preferences.json' 93 | const prefDefault = { 94 | stackPath: '' 95 | } 96 | 97 | let loadPreferences = () => { 98 | try { 99 | json = JSON.parse(fs.readFileSync(prefPath, 'utf8')) 100 | } catch(err) { 101 | json = prefDefault 102 | } 103 | return json 104 | } 105 | let savePreferences = (prefs) => { 106 | fs.writeFileSync(prefPath, JSON.stringify(prefs), 'utf8') 107 | } 108 | let applyPreferences = (prefs) => { 109 | interpreter.setPaths(prefs.stackPath) 110 | } 111 | 112 | let prefWindow = null 113 | let menuPreferences = (item, focusedWindow) => { 114 | const win = new BrowserWindow({ 115 | x:20, 116 | y:20, 117 | width: 400, 118 | height: 150, 119 | webPreferences: { 120 | preload: lib.path.join(appdir, 'src/preferences-preload.js') 121 | } 122 | }) 123 | // remember global reference to window, due to garbage collection 124 | prefWindow = win 125 | win.on('closed', () => { prefWindow = null }) 126 | 127 | win.loadURL('file://' + appdir + '/preferences.html') 128 | win.webContents.on('did-finish-load', () => { 129 | win.webContents.send('window-ready', loadPreferences()) 130 | }) 131 | } 132 | 133 | 134 | /* **************************************************************** 135 | Worksheet management 136 | **************************************************************** */ 137 | let fileExtensions = [{ name: 'Worksheet' , extensions: ['hhs'] }] 138 | 139 | // Global references to the window objects. 140 | // Necessary, because a window will be closed if its JavaScript is garbage collected. 141 | let windows = {} 142 | 143 | // Create a new worksheet window and associate it to a path if necessary. 144 | let newWorksheet = (path) => { 145 | const win = new BrowserWindow({ 146 | x: 20, 147 | y: 20, 148 | width: 800, 149 | height: 600, 150 | // FIXME: More security consciousness! 151 | // While the interpreter can execute arbitrary Haskell actions, 152 | // we may want to prevent dynamically loaded JavaScript 153 | // from accessing the node.js environment. 154 | webPreferences: { 155 | preload: lib.path.join(appdir, 'src/worksheet-preload.js') 156 | } 157 | }) 158 | let id = win.id 159 | 160 | windows[id] = win // keep a reference 161 | 162 | win.on('closed', () => { 163 | windows[id] = null // remove reference 164 | // kill associated interpreter 165 | // FIXME: Do we really need to send this signal, or does 166 | // the Electron framework do that for us? 167 | interpreter.kill(id) 168 | }) 169 | win.on('close', (event) => { 170 | // don't close the window if there have been unsaved changes. 171 | if (win.isDocumentEdited()) { 172 | const result = dialog.showMessageBox(win, 173 | { 174 | type : "question", 175 | message: "Do you want to save the changes you made in the document?", 176 | details: "Your changes will be lost if you don't save them.", 177 | buttons: ["Save", "Cancel", "Don't Save"], 178 | }) 179 | switch (result) { 180 | case 0: win.saveFile(); break 181 | case 1: event.preventDefault(); break 182 | case 2: break 183 | } 184 | } 185 | }) 186 | 187 | win.loadURL('file://' + appdir + '/worksheet.html') 188 | if (TESTING) { win.openDevTools({ detach : true }) } 189 | 190 | // make sure that this in an absolute path 191 | const filepath = path ? resolvePath(path) : '' 192 | win.setTitle('(untitled)') 193 | win.webContents.on('did-finish-load', () => { 194 | if (filepath) { win.setFilepath(filepath) } 195 | win.webContents.send('window-ready', filepath) 196 | }) 197 | 198 | return win 199 | } 200 | 201 | let menuNew = (item, focusedWindow) => { newWorksheet() } 202 | 203 | let menuOpen = (item, focusedWindow) => { 204 | dialog.showOpenDialog(null, 205 | { 206 | properties: ['openFile'], 207 | filters: fileExtensions 208 | }, (paths) => { 209 | if (paths && paths.length > 0) { newWorksheet(paths[0]) } 210 | }) 211 | } 212 | 213 | // NOTE: If we add new methods to an object with the help of `prototype`, 214 | // we have to use `function` in order to bind `this` to the correct value. 215 | BrowserWindow.prototype.setFilepath = function (path) { 216 | app.addRecentDocument(path) 217 | this._filepath = path 218 | this.setRepresentedFilename(path) 219 | this.setTitle(require('path').basename(path)) 220 | } 221 | 222 | BrowserWindow.prototype.getFilePath = function () { 223 | return this._filepath ? this._filepath : null; 224 | } 225 | 226 | BrowserWindow.prototype.saveFile = function () { 227 | let path = this.getFilePath() 228 | if (path) { 229 | this.webContents.send('save-file', path) 230 | this.setDocumentEdited(false) 231 | } else { 232 | this.saveFileAs() 233 | } 234 | } 235 | 236 | BrowserWindow.prototype.saveFileAs = function () { 237 | let win = this 238 | dialog.showSaveDialog(win, 239 | { filters: fileExtensions }, (path) => { 240 | if (path) { 241 | win.setFilepath(path) 242 | win.saveFile() 243 | } 244 | }) 245 | } 246 | 247 | let menuSave = (item, win) => { win.saveFile() } 248 | let menuSaveAs = (item, win) => { win.saveFileAs() } 249 | 250 | 251 | /* **************************************************************** 252 | Menu 253 | **************************************************************** */ 254 | 255 | let handle = (name) => { 256 | return (item, focusedWindow) => { focusedWindow.webContents.send(name) } 257 | } 258 | 259 | let template = [ 260 | { label: 'File', 261 | submenu: [ 262 | { label: 'New', accelerator: 'CmdOrCtrl+N' , click: menuNew }, 263 | { label: 'Open…', accelerator: 'CmdOrCtrl+O', click: menuOpen }, 264 | { type: 'separator' }, 265 | { label: 'Close', accelerator: 'CmdOrCtrl+W', role: 'close' }, 266 | { label: 'Save', accelerator: 'CmdOrCtrl+S', click: menuSave }, 267 | { label: 'Save As…', accelerator: 'Shift+CmdOrCtrl+S', click: menuSaveAs }, 268 | ] 269 | }, 270 | { label: 'Edit', 271 | submenu: [ 272 | { role: 'undo' }, 273 | { role: 'redo' }, 274 | { type: 'separator' }, 275 | { role: 'cut' }, 276 | { role: 'copy' }, 277 | { role: 'paste' }, 278 | { role: 'selectall' }, 279 | ] 280 | }, 281 | { label: 'View', 282 | submenu: [ 283 | { role: 'resetzoom' }, 284 | { role: 'zoomin' }, 285 | { role: 'zoomout' }, 286 | ] 287 | }, 288 | { label: 'Cells', 289 | submenu: [ 290 | { label: 'Insert Evaluation Cell Above', click: handle('cell-insert-eval') }, 291 | { label: 'Insert Text Cell Above', click: handle('cell-insert-text') }, 292 | { type : 'separator' }, 293 | { label: 'Delete Cell', click: handle('cell-delete') }, 294 | ] 295 | }, 296 | { label: 'Evaluation', 297 | submenu: [ 298 | { label: 'Reload imports and options', accelerator: 'CmdOrCtrl+R', click: handle('reload-imports') }, 299 | { type : 'separator' }, 300 | { label: 'Evaluate Cell', accelerator: 'CmdOrCtrl+Enter', click: handle('evaluation-start') }, 301 | { label: 'Interrupt Evaluation', accelerator: 'CmdOrCtrl+.', click: handle('evaluation-cancel') }, 302 | ] 303 | }, 304 | ] 305 | 306 | if (process.platform == 'darwin') { 307 | let name = app.getName() 308 | template[0].submenu.splice(2,0, 309 | { label: 'Open Recent…', role: 'recentdocuments', 310 | submenu: [ 311 | { type : 'separator' }, 312 | { label: 'Clear Recent', role: 'clearrecentdocuments' } 313 | ] 314 | }); 315 | template.unshift({ 316 | label: name, 317 | submenu: [ 318 | { role : 'about' }, 319 | { type : 'separator' }, 320 | { label: 'Preferences…', accelerator: 'Command+,', click: menuPreferences }, 321 | { type : 'separator' }, 322 | { role : 'services' }, 323 | { type : 'separator' }, 324 | { role : 'hide' }, 325 | { role : 'hideothers' }, 326 | { role : 'unhide' }, 327 | { type : 'separator' }, 328 | { label: 'Quit', accelerator: 'Command+Q', click: () => { app.quit() } }, 329 | ] 330 | }) 331 | } else { 332 | template[1].submenu.splice(template[1].submenu.length, 0, 333 | { type : 'separator' }, 334 | { label: 'Preferences…', click: menuPreferences }) 335 | } 336 | -------------------------------------------------------------------------------- /app/src/preferences-preload.js: -------------------------------------------------------------------------------- 1 | /* **************************************************************************** 2 | Setting up IPC for the browser window 3 | **************************************************************************** */ 4 | 5 | const { contextBridge, ipcRenderer } = require('electron') 6 | 7 | contextBridge.exposeInMainWorld('electron', { 8 | 9 | /* Events */ 10 | 11 | onWindowReady: (callback) => ipcRenderer.on( 12 | 'window-ready', (event, ...args) => callback(...args) 13 | ), 14 | 15 | /* Functions */ 16 | 17 | savePreferences: (...args) => { 18 | ipcRenderer.send('save-preferences', ...args) 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /app/src/preferences.js: -------------------------------------------------------------------------------- 1 | /* ************************************************************* 2 | The "Preferences" window 3 | ************************************************************* */ 4 | 5 | window.electron.onWindowReady( (prefs) => { 6 | // show current preferences 7 | $('#stackPath').val(prefs.stackPath) 8 | 9 | const save = () => { 10 | window.electron.savePreferences({ 11 | stackPath: $('#stackPath').val() 12 | }) 13 | } 14 | 15 | // save preferences on enter 16 | $('form').submit( (event) => { 17 | save() 18 | event.preventDefault() 19 | }) 20 | 21 | // save on close as well 22 | window.onbeforeunload = save 23 | }) 24 | -------------------------------------------------------------------------------- /app/src/sequence.js: -------------------------------------------------------------------------------- 1 | /* ************************************************************* 2 | A data structure for storing sequences of things 3 | 4 | Currently a very simple-minded implementation as an array. 5 | ************************************************************* */ 6 | window.newSequence = function () { 7 | var xs = []; 8 | var that = {}; 9 | 10 | that.at = function (index) { return xs[index]; }; 11 | that.length = function () { return xs.length; }; 12 | that.index = function (obj) { 13 | for (var i=0; i < xs.length; i++ ) { 14 | if (xs[i] === obj) { return i; } 15 | } 16 | return null; 17 | }; 18 | 19 | that.push = function (obj) { xs.push(obj); }; 20 | that.empty = function () { xs = []; }; 21 | that.insertBefore = function (obj, index) { 22 | xs.splice(index, 0, obj); 23 | }; 24 | that.remove = function (index) { 25 | if (0 <= index && index < xs.length) { 26 | xs.splice(index, 1); 27 | } 28 | }; 29 | 30 | return that; 31 | }; 32 | -------------------------------------------------------------------------------- /app/src/worksheet-preload.js: -------------------------------------------------------------------------------- 1 | /* **************************************************************************** 2 | Setting up IPC for the browser window 3 | **************************************************************************** */ 4 | 5 | const { contextBridge, ipcRenderer } = require('electron') 6 | 7 | contextBridge.exposeInMainWorld('electron', { 8 | 9 | /* Events */ 10 | 11 | onCellDelete: (callback) => ipcRenderer.on( 12 | 'cell-delete', (event, ...args) => callback(...args) 13 | ), 14 | onCellInsertEval: (callback) => ipcRenderer.on( 15 | 'cell-insert-eval', (event, ...args) => callback(...args) 16 | ), 17 | onCellInsertText: (callback) => ipcRenderer.on( 18 | 'cell-insert-text', (event, ...args) => callback(...args) 19 | ), 20 | onDoneInterpreterStart: (callback) => ipcRenderer.on( 21 | 'done-interpreter-start', (event, ...args) => callback(...args) 22 | ), 23 | onEvaluationStart: (callback) => ipcRenderer.on( 24 | 'evaluation-start', (event, ...args) => callback(...args) 25 | ), 26 | onEvaluationCancel: (callback) => ipcRenderer.on( 27 | 'evaluation-cancel', (event, ...args) => callback(...args) 28 | ), 29 | onReloadImports: (callback) => ipcRenderer.on( 30 | 'reload-imports', (event, ...args) => callback(...args) 31 | ), 32 | onSaveFile: (callback) => ipcRenderer.on( 33 | 'save-file', (event, ...args) => callback(...args) 34 | ), 35 | onWindowReady: (callback) => ipcRenderer.on( 36 | 'window-ready', (event, ...args) => callback(...args) 37 | ), 38 | 39 | /* Functions */ 40 | 41 | setDocumentEdited: (...args) => { 42 | // Note [setDocumentEdited] 43 | // We do not use the 'remote' object here, 44 | // because doing so while handling a CodeMirror 'changes' event 45 | // will lead to a duplicate Enter keypress. I have no idea why, 46 | // but using IPC to call 'setDocumentEdited' solves the issue. 47 | ipcRenderer.send('setDocumentEdited', ...args) 48 | }, 49 | startInterpreter: (...args) => { 50 | ipcRenderer.send('startInterpreter', ...args) 51 | }, 52 | getRepresentedFilename: (...args) => { 53 | return ipcRenderer.invoke('getRepresentedFilename', ...args) 54 | }, 55 | getCurrentWorkingDirectory: (...args) => { 56 | return ipcRenderer.invoke('getCurrentWorkingDirectory', ...args) 57 | }, 58 | fs: { 59 | readFileSync: (...args) => { 60 | return ipcRenderer.invoke('readFileSync', ...args) 61 | }, 62 | writeFileSync: (...args) => { ipcRenderer.send('writeFileSync', ...args) } 63 | } 64 | 65 | }) 66 | -------------------------------------------------------------------------------- /app/src/worksheet.js: -------------------------------------------------------------------------------- 1 | /* ************************************************************* 2 | The worksheet window 3 | ************************************************************* */ 4 | const interpreter = window.newInterpreter() 5 | const fileformat = window.fileformat 6 | 7 | /* ************************************************************* 8 | Window setup 9 | ************************************************************* */ 10 | 11 | // Initial entry point, sent by the main process 12 | window.electron.onWindowReady( (path) => { 13 | // initialize interpreter code 14 | interpreter.init(window) 15 | 16 | let cells = NewCells($('#cells')) 17 | cells.setCells(fileformat.single().cells) 18 | 19 | const reloadImports = async () => { 20 | const cwd = await window.electron.getCurrentWorkingDirectory() 21 | 22 | // tell interpreter to load imports 23 | $('#status').empty() 24 | interpreter.loadImports({ 25 | cwd : cwd, 26 | searchPath : $('#searchPath').val(), 27 | packageTool : $('#packageTool').val(), 28 | packagePath : $('#packagePath').val(), 29 | extensions : cmExtensions.getDoc().getValue().split(','), 30 | imports : cmImportModules.getDoc().getValue().split('\n'), 31 | files : cmLoadFiles.getDoc().getValue().split('\n'), 32 | }, (result) => { 33 | if (result.status === 'ok') { 34 | $('#status').text('Imports loaded ok.') 35 | } else { 36 | $('#status').text('Could not load imports: ') 37 | $('#status').append($('
').text(result.errors.toString()))
 38 |       }
 39 |     })
 40 |   }
 41 | 
 42 |   const fromMaybe = (def,x) => { return x ? x : def }
 43 | 
 44 |   const loadFile = async (path) => {
 45 |     // FIXME: Better error reporting when loading from a file fails
 46 |     const data = await window.electron.fs.readFileSync(path, 'utf8')
 47 |     const json = fileformat.update('0.2.1.0', fileformat.parse(data))
 48 | 
 49 |     cmExtensions.getDoc().setValue(fromMaybe('', json.extensions))
 50 |     cmImportModules.getDoc().setValue(fromMaybe('', json.importModules))
 51 |     cmLoadFiles.getDoc().setValue(fromMaybe('', json.loadFiles))
 52 |     $("#searchPath").val(json.settings.searchPath)
 53 |     $("#packagePath").val(json.settings.packagePath)
 54 |     $("#packageTool").val(json.settings.packageTool)
 55 |     cells.setCells(json.cells)
 56 |     window.electron.setDocumentEdited(false)
 57 |   }
 58 |   const init = async () => {
 59 |     if (path) { await loadFile(path) }
 60 |     await reloadImports()
 61 |   }
 62 |   init() // fire off the promise
 63 | 
 64 |   /* NOTE [SemanticVersioning]
 65 | 
 66 |   When it comes to semantic versioning, the conventions between Haskell and npm differ.
 67 |     Haskell: major.major.minor.patch  e.g. 0.1.0.0
 68 |     npm    : major.minor.patch        e.g.   1.0.0
 69 |   The Haskell scheme was chosen to allow a major versions starting with 0, e.g. `0.7`
 70 |   and the first "solid" release being e.g. `1.0`.
 71 |   It appears that many projects built with packages on npm, e.g. Electron itself,
 72 |   do not follow semantics versioning precisely whenever the major version is 0.
 73 |   There is even a special case in the specification for that:
 74 | 
 75 |   " 4. Major version zero (0.y.z) is for initial development.
 76 |     Anything may change at any time. The public API should not be considered stable. "
 77 | 
 78 |   Oh well...
 79 | 
 80 |   Whenever possible, we use the Haskell semantic versioning scheme.
 81 |   If we have to align with the npm scheme, we translate Haskell semvers into npm semvers
 82 |   by using hundreds for the first major version number, that is
 83 | 
 84 |     A.B.C.D --> 100*A+B.C.D
 85 |     0.3.0.1 -->       3.0.1
 86 |     1.2.0.4 -->     102.0.4
 87 |   */
 88 |   window.electron.onSaveFile( (path) => {
 89 |     const json = {
 90 |       version        : '0.2.1.0',
 91 |       cells          : cells.getCells(),
 92 |       extensions     : cmExtensions.getDoc().getValue(),
 93 |       importModules  : cmImportModules.getDoc().getValue(),
 94 |       loadFiles      : cmLoadFiles.getDoc().getValue(),
 95 |       settings       : {
 96 |         packageTool : $('#packageTool').val(),
 97 |         packagePath : $('#packagePath').val(),
 98 |         searchPath  : $('#searchPath').val(),
 99 |       },
100 |     }
101 |     window.electron.fs.writeFileSync(path, fileformat.stringify(json), 'utf8')
102 |   })
103 | 
104 |   // FIXME: Call  setDocumentEdited()  also when settings are changed
105 |   window.electron.onCellInsertText( () => {
106 |     cells.insertBeforeCurrent('code')
107 |     window.electron.setDocumentEdited(true)
108 |   })
109 |   window.electron.onCellInsertText( () => {
110 |     cells.insertBeforeCurrent('text')
111 |     window.electron.setDocumentEdited(true)
112 |   })
113 |   window.electron.onCellDelete(() => {
114 |     cells.removeCurrent()
115 |     window.electron.setDocumentEdited(true)
116 |   })
117 | 
118 |   window.electron.onReloadImports(reloadImports)
119 |   window.electron.onEvaluationStart(cells.evaluateCurrent)
120 |   window.electron.onEvaluationCancel(interpreter.cancel)
121 | })
122 | 
123 | /* *************************************************************
124 |     Display interpreter results
125 | ************************************************************* */
126 | const formatResult = (element, result) => {
127 |   if (result.status === 'ok') {
128 |     if (result.value.type === 'string') {
129 |       $(element).text(result.value.value)
130 |     } else if (result.value.type === 'html') {
131 |       $(element).html(result.value.value)
132 |     }
133 |   } else {
134 |     $(element).empty().append($("
").text(result.errors.join('\n')))
135 |   }
136 | }
137 | 
138 | /* *************************************************************
139 |     Global supply of IDs
140 | ************************************************************* */
141 | let totalSupply = 0;
142 | 
143 | const supply = {}
144 | supply.newId = () => {
145 |   totalSupply = totalSupply + 1
146 |   return ("id-" + totalSupply.toString())
147 | }
148 | 
149 | /* *************************************************************
150 |     Manage a list of cells
151 | ************************************************************* */
152 | 
153 | const NewCells = (parent) => {
154 |   let that  = {}
155 |   let cells = window.newSequence()
156 | 
157 |   let focus           = -1
158 |   const currentIsLast = () => { return (focus === cells.length() - 1) }
159 |   that.current        = () => { return cells.at(focus) }
160 | 
161 |   // set focus on a particular cell
162 |   const setFocus = (index) => {
163 |     if (0 <= focus && focus < cells.length()) {
164 |       cells.at(focus).focus(false)
165 |     }
166 |     focus = index
167 |     cells.at(focus).focus(true)
168 |   }
169 |   // update focus if necessary
170 |   const updateFocus = (cell) => {
171 |     const index = cells.index(cell)
172 |     if (focus !== index) { setFocus(index) }
173 |   }
174 | 
175 |   // move the cursor up or down
176 |   // return value: the cursor did move
177 |   const move = (cell, delta, ch) => {
178 |     const index  = cells.index(cell)
179 |     const index2 = index + delta
180 |     // move cursor to a new cell if it exists
181 |     if (cells.at(index2)) {
182 |       setFocus(index2)
183 |       if (delta >= 1) { // from below
184 |         cells.at(index2).setCursor({ line: 0, ch: ch })
185 |       } else if (delta <= -1) { // from above
186 |         cells.at(index2).setCursor({ line: cells.at(index2).lineCount()-1, ch: ch})
187 |       }
188 |       return true
189 |     } else { return false }
190 |   }
191 | 
192 |   // Initialize from an array of expressions.
193 |   that.setCells = (xs) => {
194 |     cells.empty()
195 |     focus = -1
196 |     parent.empty()
197 |     for (let i=0; i 0 && xs[xs.length-1]['source'] !== '') {
202 |       that.appendCell('code').setValue('')
203 |     }
204 |     setFocus(0)
205 |   }
206 | 
207 |   // Retrieve the represented expressions.
208 |   that.getCells = () => {
209 |     let result = ['']
210 |     for (let i=0; i0 && result[i]['source'] === '') { i--; }
219 |     result = result.slice(0,i+1)
220 |     return result
221 |   }
222 | 
223 |   // Create cells
224 |   // Create a new evaluation cell at the end.
225 |   that.appendCell = (cell_type) => {
226 |     const insertDOM = (el) => { parent.append(el) }
227 |     const cell = ( cell_type === 'code' ?
228 |       NewEvaluationCell(insertDOM, move) : NewTextCell(insertDOM, move) )
229 |     cell.on('focus', () => { updateFocus(cell) })
230 |     cells.push(cell)
231 |     return cell
232 |   }
233 |   // Create cell and insert before current cell
234 |   that.insertBeforeCurrent = (cell_type) => {
235 |     if (focus >= 0) {
236 |       const insertDOM = (el) => { cells.at(focus).dom().before(el) }
237 |       const cell = ( cell_type === 'code' ?
238 |         NewEvaluationCell(insertDOM, move) : NewTextCell(insertDOM, move) )
239 |       cell.on('focus', () => { updateFocus(cell) })
240 | 
241 |       cells.at(focus).focus(false)
242 |       cells.insertBefore(cell, focus)
243 |       setFocus(focus)
244 |       return cell
245 |     }
246 |   }
247 | 
248 |   // Delete the current cell
249 |   that.removeCurrent = () => {
250 |     if (focus >= 0) {
251 |       let index = focus
252 |       // remove cell from DOM
253 |       cells.at(index).focus(false)
254 |       cells.at(index).remove()
255 |       cells.remove(index)
256 | 
257 |       // set new focus
258 |       if (index < cells.length() ) {
259 |         setFocus(index)
260 |       } else {
261 |         setFocus(cells.length() - 1)
262 |       }
263 |     }
264 |   }
265 | 
266 |   // Evaluate the current cell and move the focus to the next one.
267 |   that.evaluateCurrent = () => {
268 |     if (focus >= 0) {
269 |       cells.at(focus).evaluate()
270 |       if (currentIsLast()) { that.appendCell('code') }
271 |       setFocus(focus + 1)
272 |     }
273 |   }
274 | 
275 |   return that
276 | }
277 | 
278 | /* *************************************************************
279 |     Text cell
280 | ************************************************************* */
281 | const NewTextCell = (insertDOM, move) => {
282 |   let that = {}
283 |   that.cell_type = 'text'
284 | 
285 |   // create DOM elements
286 |   const div   = $("
") 287 | insertDOM(div) 288 | const div2 = $("
") 289 | div2.appendTo(div) 290 | const quill = new Quill(div2.get(0)) 291 | 292 | that.dom = () => { return div } 293 | that.remove = () => { div.detach() } 294 | 295 | that.setValue = (s) => { quill.setText(s) } 296 | that.getValue = () => { return quill.getText() } 297 | that.lineCount = () => { return 1 } 298 | 299 | that.evaluate = () => { } // do nothing 300 | 301 | // signal that the document has been edited 302 | quill.on('text-change', () => { window.electron.setDocumentEdited(true) }) 303 | 304 | // Focus and cursor management 305 | that.on = (event, fun) => { 306 | if (event === 'focus') { 307 | quill.on('selection-change', (range) => { if (range) { fun() } }) 308 | }} 309 | that.setCursor = (cursor) => { 310 | if (cursor.line === 0) { quill.setSelection(0,0) } 311 | if (cursor.line > 0) { quill.setSelection(quill.getLength()-1,0) } 312 | } 313 | that.focus = (bool) => { 314 | // focus or unfocus the cell 315 | div.toggleClass('focus', bool) 316 | if (bool) { quill.getSelection(true) } // looks funny, but this focuses the editor 317 | } 318 | 319 | div.on('keydown', (event) => { 320 | // moving the cursor "out" of the cell will seamlessly move to the next cell 321 | if (event.keyCode === 38 && !event.shiftKey) { // if (up key) { 322 | // hack to find out whether the cursor is at the top of the editor 323 | const atTop = (quill.getBounds(0).top === quill.getBounds(quill.getSelection()).top) 324 | if (atTop) { 325 | quill.setSelection(0) // remove any selection 326 | move(that, -1, 0) 327 | event.preventDefault() 328 | } 329 | } else if (event.keyCode === 40 && !event.shiftKey) { // if (down key) { 330 | const atBottom = (quill.getBounds(quill.getLength()).top === quill.getBounds(quill.getSelection()).top) 331 | if (atBottom) { 332 | if (move(that, 1, 0)) { 333 | quill.setSelection(null) // remove any selection 334 | event.preventDefault() 335 | } 336 | } 337 | }}) 338 | 339 | return that 340 | } 341 | 342 | /* ************************************************************* 343 | Evaluation cell 344 | ************************************************************* */ 345 | const NewEvaluationCell = (insertDOM, move) => { 346 | let that = {} 347 | that.cell_type = 'code' 348 | 349 | // create DOM elements and CodeMirror editor 350 | const div = $("
") 351 | insertDOM(div) 352 | const cm = CodeMirror( (el) => { $(el).appendTo(div) } ) 353 | cm.setOption('indentUnit', 4) 354 | cm.setOption('extraKeys', { Tab: betterTab }) 355 | const out = $("
") 356 | out.appendTo(div) 357 | 358 | that.dom = () => { return div } // return associated DOM element 359 | that.remove = () => { div.detach() } 360 | 361 | that.setValue = (s) => { cm.getDoc().setValue(s) } 362 | that.getValue = () => { return cm.getDoc().getValue() } 363 | that.lineCount = () => { return cm.getDoc().lineCount() } 364 | 365 | that.evaluate = () => { 366 | // evaluate the cell 367 | div.addClass('evaluating') 368 | out.empty() 369 | out.show() 370 | interpreter.eval(cm.getDoc().getValue(), (result) => { 371 | div.removeClass('evaluating') 372 | formatResult(out, result) 373 | }) 374 | } 375 | 376 | // signal that the document has been edited 377 | cm.on('changes', () => { window.electron.setDocumentEdited(true) }) 378 | 379 | // Focus and cursor management 380 | that.on = (event, fun) => { 381 | if (event === 'focus') { cm.on('focus', fun) } 382 | } 383 | that.setCursor = (x) => { cm.getDoc().setCursor(x) } 384 | that.focus = (bool) => { 385 | div.toggleClass('focus', bool) 386 | if (bool) { cm.focus() } 387 | } 388 | 389 | cm.on('keydown', (instance, event) => { 390 | // moving the cursor "out" of the cell will seamlessly move to the next cell 391 | let doc = cm.getDoc() 392 | let ch = doc.getCursor().ch 393 | if (event.keyCode === 38 && !event.shiftKey) { // if(up key) { 394 | if (doc.getCursor().line <= 0) { 395 | doc.setCursor(0,0) // remove any selection 396 | move(that, -1, ch) 397 | event.preventDefault() 398 | } 399 | } else if (event.keyCode === 40 && !event.shiftKey) { // if(down key) { 400 | if (doc.getCursor().line + 1 >= doc.lineCount()) { 401 | if (move(that, 1, ch)) { 402 | doc.setCursor(0,0) // remove any selection 403 | event.preventDefault() 404 | } 405 | } 406 | }}) 407 | 408 | return that 409 | } 410 | 411 | 412 | /* ************************************************************* 413 | CodeMirror 414 | ************************************************************* */ 415 | const betterTab = (cm) => { 416 | if (cm.somethingSelected()) { 417 | cm.indentSelection('add') 418 | } else { 419 | cm.execCommand('insertSoftTab') 420 | } 421 | } 422 | -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Nikita Prokopov http://tonsky.me 2 | with Reserved Font Name Fira Code 3 | Website: https://github.com/tonsky/FiraCode 4 | 5 | Copyright (c) 2014, Mozilla Foundation https://mozilla.org/ 6 | with Reserved Font Name Fira Sans. 7 | 8 | Copyright (c) 2014, Mozilla Foundation https://mozilla.org/ 9 | with Reserved Font Name Fira Mono. 10 | 11 | Copyright (c) 2014, Telefonica S.A. 12 | 13 | This Font Software is licensed under the SIL Open Font License, Version 1.1. 14 | This license is copied below, and is also available with a FAQ at: 15 | http://scripts.sil.org/OFL 16 | 17 | 18 | ----------------------------------------------------------- 19 | SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 20 | ----------------------------------------------------------- 21 | 22 | PREAMBLE 23 | The goals of the Open Font License (OFL) are to stimulate worldwide 24 | development of collaborative font projects, to support the font creation 25 | efforts of academic and linguistic communities, and to provide a free and 26 | open framework in which fonts may be shared and improved in partnership 27 | with others. 28 | 29 | The OFL allows the licensed fonts to be used, studied, modified and 30 | redistributed freely as long as they are not sold by themselves. The 31 | fonts, including any derivative works, can be bundled, embedded, 32 | redistributed and/or sold with any software provided that any reserved 33 | names are not used by derivative works. The fonts and derivatives, 34 | however, cannot be released under any other type of license. The 35 | requirement for fonts to remain under this license does not apply 36 | to any document created using the fonts or their derivatives. 37 | 38 | DEFINITIONS 39 | "Font Software" refers to the set of files released by the Copyright 40 | Holder(s) under this license and clearly marked as such. This may 41 | include source files, build scripts and documentation. 42 | 43 | "Reserved Font Name" refers to any names specified as such after the 44 | copyright statement(s). 45 | 46 | "Original Version" refers to the collection of Font Software components as 47 | distributed by the Copyright Holder(s). 48 | 49 | "Modified Version" refers to any derivative made by adding to, deleting, 50 | or substituting -- in part or in whole -- any of the components of the 51 | Original Version, by changing formats or by porting the Font Software to a 52 | new environment. 53 | 54 | "Author" refers to any designer, engineer, programmer, technical 55 | writer or other person who contributed to the Font Software. 56 | 57 | PERMISSION & CONDITIONS 58 | Permission is hereby granted, free of charge, to any person obtaining 59 | a copy of the Font Software, to use, study, copy, merge, embed, modify, 60 | redistribute, and sell modified and unmodified copies of the Font 61 | Software, subject to the following conditions: 62 | 63 | 1) Neither the Font Software nor any of its individual components, 64 | in Original or Modified Versions, may be sold by itself. 65 | 66 | 2) Original or Modified Versions of the Font Software may be bundled, 67 | redistributed and/or sold with any software, provided that each copy 68 | contains the above copyright notice and this license. These can be 69 | included either as stand-alone text files, human-readable headers or 70 | in the appropriate machine-readable metadata fields within text or 71 | binary files as long as those fields can be easily viewed by the user. 72 | 73 | 3) No Modified Version of the Font Software may use the Reserved Font 74 | Name(s) unless explicit written permission is granted by the corresponding 75 | Copyright Holder. This restriction only applies to the primary font name as 76 | presented to the users. 77 | 78 | 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font 79 | Software shall not be used to promote, endorse or advertise any 80 | Modified Version, except to acknowledge the contribution(s) of the 81 | Copyright Holder(s) and the Author(s) or with their explicit written 82 | permission. 83 | 84 | 5) The Font Software, modified or unmodified, in part or in whole, 85 | must be distributed entirely under this license, and must not be 86 | distributed under any other license. The requirement for fonts to 87 | remain under this license does not apply to any document created 88 | using the Font Software. 89 | 90 | TERMINATION 91 | This license becomes null and void if any of the above conditions are 92 | not met. 93 | 94 | DISCLAIMER 95 | THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 96 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF 97 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT 98 | OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE 99 | COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 100 | INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL 101 | DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 102 | FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM 103 | OTHER DEALINGS IN THE FONT SOFTWARE. 104 | -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/eot/FiraCode-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/eot/FiraCode-Bold.eot -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/eot/FiraCode-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/eot/FiraCode-Light.eot -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/eot/FiraCode-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/eot/FiraCode-Medium.eot -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/eot/FiraCode-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/eot/FiraCode-Regular.eot -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/fira_code.css: -------------------------------------------------------------------------------- 1 | @font-face{ 2 | font-family: 'Fira Code'; 3 | src: url('eot/FiraCode-Light.eot'); 4 | src: url('eot/FiraCode-Light.eot') format('embedded-opentype'), 5 | url('woff2/FiraCode-Light.woff2') format('woff2'), 6 | url('woff/FiraCode-Light.woff') format('woff'), 7 | url('ttf/FiraCode-Light.ttf') format('truetype'); 8 | font-weight: 300; 9 | font-style: normal; 10 | } 11 | 12 | @font-face{ 13 | font-family: 'Fira Code'; 14 | src: url('eot/FiraCode-Regular.eot'); 15 | src: url('eot/FiraCode-Regular.eot') format('embedded-opentype'), 16 | url('woff2/FiraCode-Regular.woff2') format('woff2'), 17 | url('woff/FiraCode-Regular.woff') format('woff'), 18 | url('ttf/FiraCode-Regular.ttf') format('truetype'); 19 | font-weight: 400; 20 | font-style: normal; 21 | } 22 | 23 | @font-face{ 24 | font-family: 'Fira Code'; 25 | src: url('eot/FiraCode-Medium.eot'); 26 | src: url('eot/FiraCode-Medium.eot') format('embedded-opentype'), 27 | url('woff2/FiraCode-Medium.woff2') format('woff2'), 28 | url('woff/FiraCode-Medium.woff') format('woff'), 29 | url('ttf/FiraCode-Medium.ttf') format('truetype'); 30 | font-weight: 500; 31 | font-style: normal; 32 | } 33 | 34 | @font-face{ 35 | font-family: 'Fira Code'; 36 | src: url('eot/FiraCode-Bold.eot'); 37 | src: url('eot/FiraCode-Bold.eot') format('embedded-opentype'), 38 | url('woff2/FiraCode-Bold.woff2') format('woff2'), 39 | url('woff/FiraCode-Bold.woff') format('woff'), 40 | url('ttf/FiraCode-Bold.ttf') format('truetype'); 41 | font-weight: 700; 42 | font-style: normal; 43 | } -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/otf/FiraCode-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/otf/FiraCode-Bold.otf -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/otf/FiraCode-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/otf/FiraCode-Light.otf -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/otf/FiraCode-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/otf/FiraCode-Medium.otf -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/otf/FiraCode-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/otf/FiraCode-Regular.otf -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/otf/FiraCode-Retina.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/otf/FiraCode-Retina.otf -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/specimen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Fira Code Specimen 7 | 8 | 9 | 26 | 27 | 28 |
# Fira Code Light 29 | 30 | take = (n, [x, ...xs]:list) --> 31 | | n <= 0 => [] 32 | | empty list => [] 33 | | otherwise => [x] ++ take n-1, xs 34 | 35 | last3 = reverse >> take 3 >> reverse
36 | 37 | 38 |
# Fira Code Regular 39 | 40 | take = (n, [x, ...xs]:list) --> 41 | | n <= 0 => [] 42 | | empty list => [] 43 | | otherwise => [x] ++ take n-1, xs 44 | 45 | last3 = reverse >> take 3 >> reverse
46 | 47 | 48 |
# Fira Code Medium 49 | 50 | take = (n, [x, ...xs]:list) --> 51 | | n <= 0 => [] 52 | | empty list => [] 53 | | otherwise => [x] ++ take n-1, xs 54 | 55 | last3 = reverse >> take 3 >> reverse
56 | 57 | 58 |
# Fira Code Bold 59 | 60 | take = (n, [x, ...xs]:list) --> 61 | | n <= 0 => [] 62 | | empty list => [] 63 | | otherwise => [x] ++ take n-1, xs 64 | 65 | last3 = reverse >> take 3 >> reverse
66 | -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/ttf/FiraCode-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/ttf/FiraCode-Bold.ttf -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/ttf/FiraCode-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/ttf/FiraCode-Light.ttf -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/ttf/FiraCode-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/ttf/FiraCode-Medium.ttf -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/ttf/FiraCode-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/ttf/FiraCode-Regular.ttf -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/ttf/FiraCode-Retina.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/ttf/FiraCode-Retina.ttf -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/woff/FiraCode-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/woff/FiraCode-Bold.woff -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/woff/FiraCode-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/woff/FiraCode-Light.woff -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/woff/FiraCode-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/woff/FiraCode-Medium.woff -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/woff/FiraCode-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/woff/FiraCode-Regular.woff -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/woff2/FiraCode-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/woff2/FiraCode-Bold.woff2 -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/woff2/FiraCode-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/woff2/FiraCode-Light.woff2 -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/woff2/FiraCode-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/woff2/FiraCode-Medium.woff2 -------------------------------------------------------------------------------- /app/vendor/FiraCode_1.203/woff2/FiraCode-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/app/vendor/FiraCode_1.203/woff2/FiraCode-Regular.woff2 -------------------------------------------------------------------------------- /app/vendor/codemirror-5.6.css: -------------------------------------------------------------------------------- 1 | /* 2 | License: https://codemirror.net/LICENSE 3 | */ 4 | 5 | /* BASICS */ 6 | 7 | .CodeMirror { 8 | /* Set height, width, borders, and global font properties here */ 9 | font-family: monospace; 10 | height: 300px; 11 | color: black; 12 | } 13 | 14 | /* PADDING */ 15 | 16 | .CodeMirror-lines { 17 | padding: 4px 0; /* Vertical padding around content */ 18 | } 19 | .CodeMirror pre { 20 | padding: 0 4px; /* Horizontal padding of content */ 21 | } 22 | 23 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 24 | background-color: white; /* The little square between H and V scrollbars */ 25 | } 26 | 27 | /* GUTTER */ 28 | 29 | .CodeMirror-gutters { 30 | border-right: 1px solid #ddd; 31 | background-color: #f7f7f7; 32 | white-space: nowrap; 33 | } 34 | .CodeMirror-linenumbers {} 35 | .CodeMirror-linenumber { 36 | padding: 0 3px 0 5px; 37 | min-width: 20px; 38 | text-align: right; 39 | color: #999; 40 | white-space: nowrap; 41 | } 42 | 43 | .CodeMirror-guttermarker { color: black; } 44 | .CodeMirror-guttermarker-subtle { color: #999; } 45 | 46 | /* CURSOR */ 47 | 48 | .CodeMirror-cursor { 49 | border-left: 1px solid black; 50 | border-right: none; 51 | width: 0; 52 | } 53 | /* Shown when moving in bi-directional text */ 54 | .CodeMirror div.CodeMirror-secondarycursor { 55 | border-left: 1px solid silver; 56 | } 57 | .cm-fat-cursor .CodeMirror-cursor { 58 | width: auto; 59 | border: 0; 60 | background: #7e7; 61 | } 62 | .cm-fat-cursor div.CodeMirror-cursors { 63 | z-index: 1; 64 | } 65 | 66 | .cm-animate-fat-cursor { 67 | width: auto; 68 | border: 0; 69 | -webkit-animation: blink 1.06s steps(1) infinite; 70 | -moz-animation: blink 1.06s steps(1) infinite; 71 | animation: blink 1.06s steps(1) infinite; 72 | background-color: #7e7; 73 | } 74 | @-moz-keyframes blink { 75 | 0% {} 76 | 50% { background-color: transparent; } 77 | 100% {} 78 | } 79 | @-webkit-keyframes blink { 80 | 0% {} 81 | 50% { background-color: transparent; } 82 | 100% {} 83 | } 84 | @keyframes blink { 85 | 0% {} 86 | 50% { background-color: transparent; } 87 | 100% {} 88 | } 89 | 90 | /* Can style cursor different in overwrite (non-insert) mode */ 91 | .CodeMirror-overwrite .CodeMirror-cursor {} 92 | 93 | .cm-tab { display: inline-block; text-decoration: inherit; } 94 | 95 | .CodeMirror-ruler { 96 | border-left: 1px solid #ccc; 97 | position: absolute; 98 | } 99 | 100 | /* DEFAULT THEME */ 101 | 102 | .cm-s-default .cm-header {color: blue;} 103 | .cm-s-default .cm-quote {color: #090;} 104 | .cm-negative {color: #d44;} 105 | .cm-positive {color: #292;} 106 | .cm-header, .cm-strong {font-weight: bold;} 107 | .cm-em {font-style: italic;} 108 | .cm-link {text-decoration: underline;} 109 | .cm-strikethrough {text-decoration: line-through;} 110 | 111 | .cm-s-default .cm-keyword {color: #708;} 112 | .cm-s-default .cm-atom {color: #219;} 113 | .cm-s-default .cm-number {color: #164;} 114 | .cm-s-default .cm-def {color: #00f;} 115 | .cm-s-default .cm-variable, 116 | .cm-s-default .cm-punctuation, 117 | .cm-s-default .cm-property, 118 | .cm-s-default .cm-operator {} 119 | .cm-s-default .cm-variable-2 {color: #05a;} 120 | .cm-s-default .cm-variable-3 {color: #085;} 121 | .cm-s-default .cm-comment {color: #a50;} 122 | .cm-s-default .cm-string {color: #a11;} 123 | .cm-s-default .cm-string-2 {color: #f50;} 124 | .cm-s-default .cm-meta {color: #555;} 125 | .cm-s-default .cm-qualifier {color: #555;} 126 | .cm-s-default .cm-builtin {color: #30a;} 127 | .cm-s-default .cm-bracket {color: #997;} 128 | .cm-s-default .cm-tag {color: #170;} 129 | .cm-s-default .cm-attribute {color: #00c;} 130 | .cm-s-default .cm-hr {color: #999;} 131 | .cm-s-default .cm-link {color: #00c;} 132 | 133 | .cm-s-default .cm-error {color: #f00;} 134 | .cm-invalidchar {color: #f00;} 135 | 136 | .CodeMirror-composing { border-bottom: 2px solid; } 137 | 138 | /* Default styles for common addons */ 139 | 140 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;} 141 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;} 142 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 143 | .CodeMirror-activeline-background {background: #e8f2ff;} 144 | 145 | /* STOP */ 146 | 147 | /* The rest of this file contains styles related to the mechanics of 148 | the editor. You probably shouldn't touch them. */ 149 | 150 | .CodeMirror { 151 | position: relative; 152 | overflow: hidden; 153 | background: white; 154 | } 155 | 156 | .CodeMirror-scroll { 157 | overflow: scroll !important; /* Things will break if this is overridden */ 158 | /* 30px is the magic margin used to hide the element's real scrollbars */ 159 | /* See overflow: hidden in .CodeMirror */ 160 | margin-bottom: -30px; margin-right: -30px; 161 | padding-bottom: 30px; 162 | height: 100%; 163 | outline: none; /* Prevent dragging from highlighting the element */ 164 | position: relative; 165 | } 166 | .CodeMirror-sizer { 167 | position: relative; 168 | border-right: 30px solid transparent; 169 | } 170 | 171 | /* The fake, visible scrollbars. Used to force redraw during scrolling 172 | before actuall scrolling happens, thus preventing shaking and 173 | flickering artifacts. */ 174 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 175 | position: absolute; 176 | z-index: 6; 177 | display: none; 178 | } 179 | .CodeMirror-vscrollbar { 180 | right: 0; top: 0; 181 | overflow-x: hidden; 182 | overflow-y: scroll; 183 | } 184 | .CodeMirror-hscrollbar { 185 | bottom: 0; left: 0; 186 | overflow-y: hidden; 187 | overflow-x: scroll; 188 | } 189 | .CodeMirror-scrollbar-filler { 190 | right: 0; bottom: 0; 191 | } 192 | .CodeMirror-gutter-filler { 193 | left: 0; bottom: 0; 194 | } 195 | 196 | .CodeMirror-gutters { 197 | position: absolute; left: 0; top: 0; 198 | z-index: 3; 199 | } 200 | .CodeMirror-gutter { 201 | white-space: normal; 202 | height: 100%; 203 | display: inline-block; 204 | margin-bottom: -30px; 205 | /* Hack to make IE7 behave */ 206 | *zoom:1; 207 | *display:inline; 208 | } 209 | .CodeMirror-gutter-wrapper { 210 | position: absolute; 211 | z-index: 4; 212 | background: none !important; 213 | border: none !important; 214 | } 215 | .CodeMirror-gutter-background { 216 | position: absolute; 217 | top: 0; bottom: 0; 218 | z-index: 4; 219 | } 220 | .CodeMirror-gutter-elt { 221 | position: absolute; 222 | cursor: default; 223 | z-index: 4; 224 | } 225 | .CodeMirror-gutter-wrapper { 226 | -webkit-user-select: none; 227 | -moz-user-select: none; 228 | user-select: none; 229 | } 230 | 231 | .CodeMirror-lines { 232 | cursor: text; 233 | min-height: 1px; /* prevents collapsing before first draw */ 234 | } 235 | .CodeMirror pre { 236 | /* Reset some styles that the rest of the page might have set */ 237 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 238 | border-width: 0; 239 | background: transparent; 240 | font-family: inherit; 241 | font-size: inherit; 242 | margin: 0; 243 | white-space: pre; 244 | word-wrap: normal; 245 | line-height: inherit; 246 | color: inherit; 247 | z-index: 2; 248 | position: relative; 249 | overflow: visible; 250 | -webkit-tap-highlight-color: transparent; 251 | } 252 | .CodeMirror-wrap pre { 253 | word-wrap: break-word; 254 | white-space: pre-wrap; 255 | word-break: normal; 256 | } 257 | 258 | .CodeMirror-linebackground { 259 | position: absolute; 260 | left: 0; right: 0; top: 0; bottom: 0; 261 | z-index: 0; 262 | } 263 | 264 | .CodeMirror-linewidget { 265 | position: relative; 266 | z-index: 2; 267 | overflow: auto; 268 | } 269 | 270 | .CodeMirror-widget {} 271 | 272 | .CodeMirror-code { 273 | outline: none; 274 | } 275 | 276 | /* Force content-box sizing for the elements where we expect it */ 277 | .CodeMirror-scroll, 278 | .CodeMirror-sizer, 279 | .CodeMirror-gutter, 280 | .CodeMirror-gutters, 281 | .CodeMirror-linenumber { 282 | -moz-box-sizing: content-box; 283 | box-sizing: content-box; 284 | } 285 | 286 | .CodeMirror-measure { 287 | position: absolute; 288 | width: 100%; 289 | height: 0; 290 | overflow: hidden; 291 | visibility: hidden; 292 | } 293 | 294 | .CodeMirror-cursor { position: absolute; } 295 | .CodeMirror-measure pre { position: static; } 296 | 297 | div.CodeMirror-cursors { 298 | visibility: hidden; 299 | position: relative; 300 | z-index: 3; 301 | } 302 | div.CodeMirror-dragcursors { 303 | visibility: visible; 304 | } 305 | 306 | .CodeMirror-focused div.CodeMirror-cursors { 307 | visibility: visible; 308 | } 309 | 310 | .CodeMirror-selected { background: #d9d9d9; } 311 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 312 | .CodeMirror-crosshair { cursor: crosshair; } 313 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } 314 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } 315 | 316 | .cm-searching { 317 | background: #ffa; 318 | background: rgba(255, 255, 0, .4); 319 | } 320 | 321 | /* IE7 hack to prevent it from returning funny offsetTops on the spans */ 322 | .CodeMirror span { *vertical-align: text-bottom; } 323 | 324 | /* Used to force a border model for a node */ 325 | .cm-force-border { padding-right: .1px; } 326 | 327 | @media print { 328 | /* Hide the cursor when printing */ 329 | .CodeMirror div.CodeMirror-cursors { 330 | visibility: hidden; 331 | } 332 | } 333 | 334 | /* See issue #2901 */ 335 | .cm-tab-wrap-hack:after { content: ''; } 336 | 337 | /* Help users use markselection to safely style text background */ 338 | span.CodeMirror-selectedtext { background: none; } 339 | -------------------------------------------------------------------------------- /app/vendor/quill-1.1.0.core.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Quill Editor v1.1.0 3 | * https://quilljs.com/ 4 | * Copyright (c) 2014, Jason Chen 5 | * Copyright (c) 2013, salesforce.com 6 | 7 | * License: https://github.com/quilljs/quill/blob/v1.1.0/LICENSE 8 | 9 | */ 10 | .ql-container { 11 | box-sizing: border-box; 12 | font-family: Helvetica, Arial, sans-serif; 13 | font-size: 13px; 14 | height: 100%; 15 | margin: 0px; 16 | position: relative; 17 | } 18 | .ql-container.ql-disabled .ql-tooltip { 19 | visibility: hidden; 20 | } 21 | .ql-clipboard { 22 | left: -100000px; 23 | height: 1px; 24 | overflow-y: hidden; 25 | position: absolute; 26 | top: 50%; 27 | } 28 | .ql-clipboard p { 29 | margin: 0; 30 | padding: 0; 31 | } 32 | .ql-editor { 33 | box-sizing: border-box; 34 | cursor: text; 35 | line-height: 1.42; 36 | height: 100%; 37 | outline: none; 38 | overflow-y: auto; 39 | padding: 12px 15px; 40 | tab-size: 4; 41 | -moz-tab-size: 4; 42 | text-align: left; 43 | white-space: pre-wrap; 44 | word-wrap: break-word; 45 | } 46 | .ql-editor p, 47 | .ql-editor ol, 48 | .ql-editor ul, 49 | .ql-editor pre, 50 | .ql-editor blockquote, 51 | .ql-editor h1, 52 | .ql-editor h2, 53 | .ql-editor h3, 54 | .ql-editor h4, 55 | .ql-editor h5, 56 | .ql-editor h6 { 57 | margin: 0; 58 | padding: 0; 59 | counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; 60 | } 61 | .ql-editor ol, 62 | .ql-editor ul { 63 | padding-left: 1.5em; 64 | } 65 | .ql-editor ol > li, 66 | .ql-editor ul > li { 67 | list-style-type: none; 68 | } 69 | .ql-editor ul > li::before { 70 | content: '\25CF'; 71 | } 72 | .ql-editor li::before { 73 | display: inline-block; 74 | margin-right: 0.3em; 75 | text-align: right; 76 | white-space: nowrap; 77 | width: 1.2em; 78 | } 79 | .ql-editor li:not(.ql-direction-rtl)::before { 80 | margin-left: -1.5em; 81 | } 82 | .ql-editor ol li, 83 | .ql-editor ul li { 84 | padding-left: 1.5em; 85 | } 86 | .ql-editor ol li { 87 | counter-reset: list-1 list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; 88 | counter-increment: list-num; 89 | } 90 | .ql-editor ol li:before { 91 | content: counter(list-num, decimal) '. '; 92 | } 93 | .ql-editor ol li.ql-indent-1 { 94 | counter-increment: list-1; 95 | } 96 | .ql-editor ol li.ql-indent-1:before { 97 | content: counter(list-1, lower-alpha) '. '; 98 | } 99 | .ql-editor ol li.ql-indent-1 { 100 | counter-reset: list-2 list-3 list-4 list-5 list-6 list-7 list-8 list-9; 101 | } 102 | .ql-editor ol li.ql-indent-2 { 103 | counter-increment: list-2; 104 | } 105 | .ql-editor ol li.ql-indent-2:before { 106 | content: counter(list-2, lower-roman) '. '; 107 | } 108 | .ql-editor ol li.ql-indent-2 { 109 | counter-reset: list-3 list-4 list-5 list-6 list-7 list-8 list-9; 110 | } 111 | .ql-editor ol li.ql-indent-3 { 112 | counter-increment: list-3; 113 | } 114 | .ql-editor ol li.ql-indent-3:before { 115 | content: counter(list-3, decimal) '. '; 116 | } 117 | .ql-editor ol li.ql-indent-3 { 118 | counter-reset: list-4 list-5 list-6 list-7 list-8 list-9; 119 | } 120 | .ql-editor ol li.ql-indent-4 { 121 | counter-increment: list-4; 122 | } 123 | .ql-editor ol li.ql-indent-4:before { 124 | content: counter(list-4, lower-alpha) '. '; 125 | } 126 | .ql-editor ol li.ql-indent-4 { 127 | counter-reset: list-5 list-6 list-7 list-8 list-9; 128 | } 129 | .ql-editor ol li.ql-indent-5 { 130 | counter-increment: list-5; 131 | } 132 | .ql-editor ol li.ql-indent-5:before { 133 | content: counter(list-5, lower-roman) '. '; 134 | } 135 | .ql-editor ol li.ql-indent-5 { 136 | counter-reset: list-6 list-7 list-8 list-9; 137 | } 138 | .ql-editor ol li.ql-indent-6 { 139 | counter-increment: list-6; 140 | } 141 | .ql-editor ol li.ql-indent-6:before { 142 | content: counter(list-6, decimal) '. '; 143 | } 144 | .ql-editor ol li.ql-indent-6 { 145 | counter-reset: list-7 list-8 list-9; 146 | } 147 | .ql-editor ol li.ql-indent-7 { 148 | counter-increment: list-7; 149 | } 150 | .ql-editor ol li.ql-indent-7:before { 151 | content: counter(list-7, lower-alpha) '. '; 152 | } 153 | .ql-editor ol li.ql-indent-7 { 154 | counter-reset: list-8 list-9; 155 | } 156 | .ql-editor ol li.ql-indent-8 { 157 | counter-increment: list-8; 158 | } 159 | .ql-editor ol li.ql-indent-8:before { 160 | content: counter(list-8, lower-roman) '. '; 161 | } 162 | .ql-editor ol li.ql-indent-8 { 163 | counter-reset: list-9; 164 | } 165 | .ql-editor ol li.ql-indent-9 { 166 | counter-increment: list-9; 167 | } 168 | .ql-editor ol li.ql-indent-9:before { 169 | content: counter(list-9, decimal) '. '; 170 | } 171 | .ql-editor .ql-indent-1:not(.ql-direction-rtl) { 172 | padding-left: 3em; 173 | } 174 | .ql-editor li.ql-indent-1:not(.ql-direction-rtl) { 175 | padding-left: 4.5em; 176 | } 177 | .ql-editor .ql-indent-1.ql-direction-rtl.ql-align-right { 178 | padding-right: 3em; 179 | } 180 | .ql-editor li.ql-indent-1.ql-direction-rtl.ql-align-right { 181 | padding-right: 4.5em; 182 | } 183 | .ql-editor .ql-indent-2:not(.ql-direction-rtl) { 184 | padding-left: 6em; 185 | } 186 | .ql-editor li.ql-indent-2:not(.ql-direction-rtl) { 187 | padding-left: 7.5em; 188 | } 189 | .ql-editor .ql-indent-2.ql-direction-rtl.ql-align-right { 190 | padding-right: 6em; 191 | } 192 | .ql-editor li.ql-indent-2.ql-direction-rtl.ql-align-right { 193 | padding-right: 7.5em; 194 | } 195 | .ql-editor .ql-indent-3:not(.ql-direction-rtl) { 196 | padding-left: 9em; 197 | } 198 | .ql-editor li.ql-indent-3:not(.ql-direction-rtl) { 199 | padding-left: 10.5em; 200 | } 201 | .ql-editor .ql-indent-3.ql-direction-rtl.ql-align-right { 202 | padding-right: 9em; 203 | } 204 | .ql-editor li.ql-indent-3.ql-direction-rtl.ql-align-right { 205 | padding-right: 10.5em; 206 | } 207 | .ql-editor .ql-indent-4:not(.ql-direction-rtl) { 208 | padding-left: 12em; 209 | } 210 | .ql-editor li.ql-indent-4:not(.ql-direction-rtl) { 211 | padding-left: 13.5em; 212 | } 213 | .ql-editor .ql-indent-4.ql-direction-rtl.ql-align-right { 214 | padding-right: 12em; 215 | } 216 | .ql-editor li.ql-indent-4.ql-direction-rtl.ql-align-right { 217 | padding-right: 13.5em; 218 | } 219 | .ql-editor .ql-indent-5:not(.ql-direction-rtl) { 220 | padding-left: 15em; 221 | } 222 | .ql-editor li.ql-indent-5:not(.ql-direction-rtl) { 223 | padding-left: 16.5em; 224 | } 225 | .ql-editor .ql-indent-5.ql-direction-rtl.ql-align-right { 226 | padding-right: 15em; 227 | } 228 | .ql-editor li.ql-indent-5.ql-direction-rtl.ql-align-right { 229 | padding-right: 16.5em; 230 | } 231 | .ql-editor .ql-indent-6:not(.ql-direction-rtl) { 232 | padding-left: 18em; 233 | } 234 | .ql-editor li.ql-indent-6:not(.ql-direction-rtl) { 235 | padding-left: 19.5em; 236 | } 237 | .ql-editor .ql-indent-6.ql-direction-rtl.ql-align-right { 238 | padding-right: 18em; 239 | } 240 | .ql-editor li.ql-indent-6.ql-direction-rtl.ql-align-right { 241 | padding-right: 19.5em; 242 | } 243 | .ql-editor .ql-indent-7:not(.ql-direction-rtl) { 244 | padding-left: 21em; 245 | } 246 | .ql-editor li.ql-indent-7:not(.ql-direction-rtl) { 247 | padding-left: 22.5em; 248 | } 249 | .ql-editor .ql-indent-7.ql-direction-rtl.ql-align-right { 250 | padding-right: 21em; 251 | } 252 | .ql-editor li.ql-indent-7.ql-direction-rtl.ql-align-right { 253 | padding-right: 22.5em; 254 | } 255 | .ql-editor .ql-indent-8:not(.ql-direction-rtl) { 256 | padding-left: 24em; 257 | } 258 | .ql-editor li.ql-indent-8:not(.ql-direction-rtl) { 259 | padding-left: 25.5em; 260 | } 261 | .ql-editor .ql-indent-8.ql-direction-rtl.ql-align-right { 262 | padding-right: 24em; 263 | } 264 | .ql-editor li.ql-indent-8.ql-direction-rtl.ql-align-right { 265 | padding-right: 25.5em; 266 | } 267 | .ql-editor .ql-indent-9:not(.ql-direction-rtl) { 268 | padding-left: 27em; 269 | } 270 | .ql-editor li.ql-indent-9:not(.ql-direction-rtl) { 271 | padding-left: 28.5em; 272 | } 273 | .ql-editor .ql-indent-9.ql-direction-rtl.ql-align-right { 274 | padding-right: 27em; 275 | } 276 | .ql-editor li.ql-indent-9.ql-direction-rtl.ql-align-right { 277 | padding-right: 28.5em; 278 | } 279 | .ql-editor .ql-video { 280 | display: block; 281 | max-width: 100%; 282 | } 283 | .ql-editor .ql-video.ql-align-center { 284 | margin: 0 auto; 285 | } 286 | .ql-editor .ql-video.ql-align-right { 287 | margin: 0 0 0 auto; 288 | } 289 | .ql-editor .ql-bg-black { 290 | background-color: #000; 291 | } 292 | .ql-editor .ql-bg-red { 293 | background-color: #e60000; 294 | } 295 | .ql-editor .ql-bg-orange { 296 | background-color: #f90; 297 | } 298 | .ql-editor .ql-bg-yellow { 299 | background-color: #ff0; 300 | } 301 | .ql-editor .ql-bg-green { 302 | background-color: #008a00; 303 | } 304 | .ql-editor .ql-bg-blue { 305 | background-color: #06c; 306 | } 307 | .ql-editor .ql-bg-purple { 308 | background-color: #93f; 309 | } 310 | .ql-editor .ql-color-white { 311 | color: #fff; 312 | } 313 | .ql-editor .ql-color-red { 314 | color: #e60000; 315 | } 316 | .ql-editor .ql-color-orange { 317 | color: #f90; 318 | } 319 | .ql-editor .ql-color-yellow { 320 | color: #ff0; 321 | } 322 | .ql-editor .ql-color-green { 323 | color: #008a00; 324 | } 325 | .ql-editor .ql-color-blue { 326 | color: #06c; 327 | } 328 | .ql-editor .ql-color-purple { 329 | color: #93f; 330 | } 331 | .ql-editor .ql-font-serif { 332 | font-family: Georgia, Times New Roman, serif; 333 | } 334 | .ql-editor .ql-font-monospace { 335 | font-family: Monaco, Courier New, monospace; 336 | } 337 | .ql-editor .ql-size-small { 338 | font-size: 0.75em; 339 | } 340 | .ql-editor .ql-size-large { 341 | font-size: 1.5em; 342 | } 343 | .ql-editor .ql-size-huge { 344 | font-size: 2.5em; 345 | } 346 | .ql-editor .ql-direction-rtl { 347 | direction: rtl; 348 | text-align: inherit; 349 | } 350 | .ql-editor .ql-align-center { 351 | text-align: center; 352 | } 353 | .ql-editor .ql-align-justify { 354 | text-align: justify; 355 | } 356 | .ql-editor .ql-align-right { 357 | text-align: right; 358 | } 359 | .ql-editor.ql-blank::before { 360 | color: rgba(0,0,0,0.6); 361 | content: attr(data-placeholder); 362 | font-style: italic; 363 | pointer-events: none; 364 | position: absolute; 365 | } 366 | -------------------------------------------------------------------------------- /app/worksheet.css: -------------------------------------------------------------------------------- 1 | /* ************************************************************* 2 | General layout 3 | ************************************************************* */ 4 | h1 { 5 | margin-left: 1em; 6 | margin-top: 2ex; 7 | } 8 | .section { 9 | position:relative; 10 | margin-left: 2em; 11 | margin-right: 1em; 12 | } 13 | 14 | h1.closed:before { content: "▸ "; } 15 | h1:before { content: "▾ "; } 16 | 17 | h1 { 18 | font-size:110%; 19 | font-weight:bold; 20 | } 21 | 22 | label { display: inline-block; } 23 | form.table label { width:10em; } 24 | input { 25 | width:20em; 26 | } 27 | 28 | /* ************************************************************* 29 | General editing 30 | ************************************************************* */ 31 | div.CodeMirror { 32 | height: auto; 33 | background: #eee; 34 | padding: 0.7ex; 35 | line-height: 1.3; 36 | } 37 | 38 | pre, div.CodeMirror { 39 | font-family: "Fira Code"; 40 | font-size: 95%; 41 | } 42 | 43 | .ql-container { 44 | font-family: "Helvetica"; 45 | font-size: 100%; 46 | min-height: 2em; 47 | } 48 | 49 | /* ************************************************************* 50 | Text and Evaluation Cells 51 | ************************************************************* */ 52 | .cell .out { 53 | display:none; 54 | margin-top: 1ex; 55 | padding: 0.7ex; 56 | } 57 | 58 | .cell { 59 | margin-bottom: 2ex; 60 | } 61 | .cell.eval { 62 | padding: 1ex 1ex 1ex; 63 | } 64 | /* Color coding for focus and evaluation */ 65 | .cell { border-left: 4px solid #ddd; } 66 | .cell.focus { border-left-color: black; } 67 | .cell.evaluating { border-left-color: #f9f; } 68 | .cell.evaluating.focus { border-left-color: #c0c; } 69 | .cell .error { 70 | color:red; 71 | } 72 | -------------------------------------------------------------------------------- /app/worksheet.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 |

Settings

26 | 46 | 47 |

Imports and Options

48 |
49 |

50 | 51 |

52 | 53 |

54 | 55 |

56 | 57 |

58 | 59 |

60 | 61 |

62 | Status:   63 |

64 | 65 |

Worksheet

66 |
67 | 68 |
69 | 70 |
71 |
72 |
73 | 74 | 75 | -------------------------------------------------------------------------------- /cabal.project: -------------------------------------------------------------------------------- 1 | packages: 2 | haskell/hyper 3 | haskell/hyper-extra 4 | haskell/hyper-haskell-server 5 | -------------------------------------------------------------------------------- /docs/INSTALL.md: -------------------------------------------------------------------------------- 1 | **work in progress** 2 | 3 | 4 | # Installation 5 | 6 | A HyperHaskell installation consists of two parts: 7 | 8 | 1. The graphical front-end. 9 | 10 | Download the application binary for your platform: OS X, (Linux), (Windows) 11 | 12 | 2. The interpreter back-end. 13 | 14 | HyperHaskell requires several Haskell packages to work properly. 15 | In order to not restrict you to outdated package versions, 16 | the front-end asks you choose your own package database. 17 | However, this means that you have to install the interpreter back-end separately. 18 | 19 | If you use [Cabal][]: 20 | 21 | * Install the back-end by executing the command 22 | 23 | cabal update && cabal install hyper-haskell 24 | 25 | * The front-end will, by default, look for this option first. 26 | You only need to start the application and should be ready to go. 27 | 28 | If you use [Stack][]: 29 | 30 | * Make a new `stack.yml` file somewhere 31 | * Use `stack install hyper-haskell` to install the back-end. 32 | * Start the front-end and point it to the `stack.yml` file in the settings. 33 | 34 | [cabal]: https://www.haskell.org/cabal/ 35 | [stack]: https://www.haskellstack.org 36 | 37 | -------------------------------------------------------------------------------- /docs/TODO.md: -------------------------------------------------------------------------------- 1 | TODO list 2 | ========= 3 | 4 | Immediate 5 | --------- 6 | 7 | * Level β: 8 | 9 | * Implement a way to evaluate cells concurrently. 10 | We still want to be able to bind variables sequentially. 11 | Using `forkIO` explicitely would do, but then we can't stop it again. 12 | 13 | Maybe a "suspended result"? 14 | 15 | Later 16 | ----- 17 | 18 | * Integrate a viewer for Haddock documentation 19 | 20 | * Integrate a way to query type signatures 21 | 22 | * Maybe implement editor for `.hs` files? The `FiraCode` font is quite beautiful. 23 | 24 | * Switch programming of user interface to some other language / approach. 25 | * sodium.js ? 26 | * threepenny-gui ? 27 | * GHCJS + reactive-banana ? 28 | 29 | * FIXME: Maybe do *not* move the cursor after evaluating a cell? 30 | 31 | * Level γ: 32 | 33 | * Integrate some form of a JavaScript FFI 34 | * Export Haskell functions to JavaScript 35 | * Suspended computations -- display infinite list 36 | 37 | Much later 38 | ---------- 39 | 40 | * Bundle an interpreter back-end with the front-end application. #10 41 | 42 | -------------------------------------------------------------------------------- /docs/hcar/hcar.sty: -------------------------------------------------------------------------------- 1 | \ProvidesPackage{hcar} 2 | 3 | \newif\ifhcarfinal 4 | \hcarfinalfalse 5 | \DeclareOption{final}{\hcarfinaltrue} 6 | \ProcessOptions 7 | 8 | \RequirePackage{keyval} 9 | \RequirePackage{color} 10 | \RequirePackage{array} 11 | 12 | \ifhcarfinal 13 | \RequirePackage[T1]{fontenc} 14 | \RequirePackage{lmodern} 15 | \RequirePackage{tabularx} 16 | \RequirePackage{booktabs} 17 | \RequirePackage{framed} 18 | \RequirePackage[obeyspaces,T1]{url} 19 | \RequirePackage 20 | [bookmarks=true,colorlinks=true, 21 | urlcolor=urlcolor, 22 | linkcolor=linkcolor, 23 | breaklinks=true, 24 | pdftitle={Haskell Communities and Activities Report}]% 25 | {hyperref} 26 | \else 27 | \RequirePackage[obeyspaces]{url} 28 | \fi 29 | \urlstyle{sf} 30 | 31 | \definecolor{urlcolor}{rgb}{0.1,0.3,0} 32 | \definecolor{linkcolor}{rgb}{0.3,0,0} 33 | \definecolor{shadecolor}{rgb}{0.9,0.95,1}%{0.98,1.0,0.95} 34 | \definecolor{framecolor}{gray}{0.9} 35 | \definecolor{oldgray}{gray}{0.7} 36 | 37 | \newcommand{\Contact}{\subsubsection*{Contact}} 38 | \newcommand{\FurtherReading}{\subsubsection*{Further reading}} 39 | \newcommand{\FuturePlans}{\subsubsection*{Future plans}} 40 | \newcommand{\WhatsNew}{\subsubsection*{What is new?}} 41 | 42 | \newcommand{\Separate}{\smallskip\noindent} 43 | \newcommand{\FinalNote}{\smallskip\noindent} 44 | 45 | \newcommand{\urlpart}{\begingroup\urlstyle{sf}\Url} 46 | \newcommand{\email}[1]{\href{mailto:\EMailRepl{#1}{ at }}{$\langle$\urlpart{#1}$\rangle$}} 47 | \newcommand{\cref}[1]{($\rightarrow\,$\ref{#1})} 48 | 49 | \ifhcarfinal 50 | \let\hcarshaded=\shaded 51 | \let\endhcarshaded=\endshaded 52 | \else 53 | \newsavebox{\shadedbox} 54 | \newlength{\shadedboxwidth} 55 | \def\hcarshaded 56 | {\begingroup 57 | \setlength{\shadedboxwidth}{\linewidth}% 58 | \addtolength{\shadedboxwidth}{-2\fboxsep}% 59 | \begin{lrbox}{\shadedbox}% 60 | \begin{minipage}{\shadedboxwidth}\ignorespaces} 61 | \def\endhcarshaded 62 | {\end{minipage}% 63 | \end{lrbox}% 64 | \noindent 65 | \colorbox{shadecolor}{\usebox{\shadedbox}}% 66 | \endgroup} 67 | \fi 68 | 69 | \ifhcarfinal 70 | \newenvironment{hcartabularx} 71 | {\tabularx{\linewidth}{l>{\raggedleft}X}} 72 | {\endtabularx} 73 | \else 74 | \newenvironment{hcartabularx} 75 | {\begin{tabular}{@{}m{.3\linewidth}@{}>{\raggedleft}p{.7\linewidth}@{}}} 76 | {\end{tabular}} 77 | \fi 78 | 79 | \ifhcarfinal 80 | \let\hcartoprule=\toprule 81 | \let\hcarbottomrule=\bottomrule 82 | \else 83 | \let\hcartoprule=\hline 84 | \let\hcarbottomrule=\hline 85 | \fi 86 | 87 | \define@key{hcarentry}{chapter}[]{\let\level\chapter} 88 | \define@key{hcarentry}{section}[]{\let\level\section} 89 | \define@key{hcarentry}{subsection}[]{\let\level\subsection} 90 | \define@key{hcarentry}{subsubsection}[]{\let\level\subsubsection} 91 | \define@key{hcarentry}{level}{\let\level=#1} 92 | %\define@key{hcarentry}{label}{\def\entrylabel{\label{#1}}} 93 | \define@key{hcarentry}{new}[]% 94 | {\let\startnew=\hcarshaded\let\stopnew=\endhcarshaded 95 | \def\startupdated{\let\orig@addv\addvspace\let\addvspace\@gobble}% 96 | \def\stopupdated{\let\addvspace\orig@addv}} 97 | \define@key{hcarentry}{old}[]{\def\normalcolor{\color{oldgray}}\color{oldgray}}% 98 | \define@key{hcarentry}{updated}[]% 99 | {\def\startupdated 100 | {\leavevmode\let\orig@addv\addvspace\let\addvspace\@gobble\hcarshaded}% 101 | \def\stopupdated{\endhcarshaded\let\addvspace\orig@addv}} 102 | 103 | \def\@makeheadererror{\PackageError{hcar}{hcarentry without header}{}} 104 | 105 | \newenvironment{hcarentry}[2][]% 106 | {\let\level\subsection 107 | \let\startupdated=\empty\let\stopupdated=\empty 108 | \let\startnew=\empty\let\stopnew=\empty 109 | %\let\entrylabel=\empty 110 | \global\let\@makeheaderwarning\@makeheadererror 111 | \setkeys{hcarentry}{#1}% 112 | \startnew\startupdated 113 | \level{#2}% 114 | % test: 115 | \global\let\@currentlabel\@currentlabel 116 | %\stopupdated 117 | \let\report@\empty 118 | \let\groupleaders@\empty 119 | \let\members@\empty 120 | \let\contributors@\empty 121 | \let\participants@\empty 122 | \let\developers@\empty 123 | \let\maintainer@\empty 124 | \let\status@\empty 125 | \let\release@\empty 126 | \let\portability@\empty 127 | \let\entry@\empty}% 128 | {\stopnew\@makeheaderwarning}% 129 | 130 | \renewcommand{\labelitemi}{$\circ$} 131 | \settowidth{\leftmargini}{\labelitemi} 132 | \addtolength{\leftmargini}{\labelsep} 133 | 134 | \newcommand*\MakeKey[2]% 135 | {\expandafter\def\csname #1\endcsname##1% 136 | {\expandafter\def\csname #1@\endcsname{\Key@{#2}{##1}}\ignorespaces}} 137 | \MakeKey{report}{Report by:} 138 | \MakeKey{status}{Status:} 139 | \MakeKey{groupleaders}{Group leaders:} 140 | \MakeKey{members}{Members:} 141 | \MakeKey{contributors}{Contributors:} 142 | \MakeKey{participants}{Participants:} 143 | \MakeKey{developers}{Developers:} 144 | \MakeKey{maintainer}{Maintainer:} 145 | \MakeKey{release}{Current release:} 146 | \MakeKey{portability}{Portability:} 147 | \MakeKey{entry}{Entry:} 148 | 149 | \newcommand\Key@[2]{#1 & #2\tabularnewline} 150 | 151 | \newcommand\makeheader 152 | {\smallskip 153 | \begingroup 154 | \sffamily 155 | \small 156 | \noindent 157 | \let\ohrule\hrule 158 | \def\hrule{\color{framecolor}\ohrule}% 159 | \begin{hcartabularx} 160 | \hline 161 | \report@ 162 | \groupleaders@ 163 | \members@ 164 | \participants@ 165 | \developers@ 166 | \contributors@ 167 | \maintainer@ 168 | \status@ 169 | \release@ 170 | \portability@ 171 | \hcarbottomrule 172 | \end{hcartabularx} 173 | \endgroup 174 | \stopupdated 175 | \global\let\@makeheaderwarning\empty 176 | \@afterindentfalse 177 | \@xsect\smallskipamount} 178 | 179 | % columns/linebreaks, interchanged 180 | \newcommand\NCi{&\let\NX\NCii}% 181 | \newcommand\NCii{&\let\NX\NL}% 182 | \newcommand\NL{\\\let\NX\NCi}% 183 | \let\NX\NCi 184 | \newcommand\hcareditor[1]{ (ed.)&\\} 185 | \newcommand\hcarauthor[1]{#1\NX}% 186 | \newcommand\hcareditors[1]{\multicolumn{3}{c}{#1 (eds.)}\\[2ex]} 187 | -------------------------------------------------------------------------------- /docs/hcar/html/worksheet-diagrams.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/docs/hcar/html/worksheet-diagrams.png -------------------------------------------------------------------------------- /docs/hcar/hyperhaskell-2016-11.tex: -------------------------------------------------------------------------------- 1 | % hyperhaskell-Hh.tex 2 | \begin{hcarentry}[new]{HyperHaskell -- The strongly hyped Haskell interpreter} 3 | \label{hyper-haskell} 4 | \report{Heinrich Apfelmus}%11/16 5 | \status{available, active development} 6 | \makeheader 7 | 8 | \emph{HyperHaskell} is a graphical Haskell interpreter, not unlike GHCi, 9 | but hopefully more awesome. You use worksheets to enter expressions and 10 | evaluate them. Results are displayed graphically using HTML. 11 | 12 | \emph{HyperHaskell} is intended to be \emph{easy to install}. It is cross-platform and should run on Linux, Mac and Windows. Internally, it uses the GHC API to interpret Haskell programs, and the graphical front-end is built on the Electron framework. \emph{HyperHaskell} is open source. 13 | 14 | \emph{HyperHaskell}'s main attraction is a \verb`Display` class that supersedes the good old \verb`Show` class. The result looks like this: 15 | %** 16 | %*ignore 17 | \begin{center} 18 | \includegraphics[width=\textwidth]{html/worksheet-diagrams.png} 19 | \end{center} 20 | %*endignore 21 | 22 | \subsubsection*{Current status} 23 | 24 | The very first release, \emph{Level $\alpha$}, version \verb`0.1.0.0` has been published. Basic features are working, but there is plenty of room to grow. Please send me any feedback, suggestions, bug reports, contributions ... that you might have! 25 | 26 | \subsubsection*{Future development} 27 | 28 | Programming a computer usually involves writing a program text in a particular language, a ``verbal'' activity. But computers can also be instructed by gestures, say, a mouse click, which is a ``nonverbal'' activity. The long term goal of \emph{HyperHaskell} is to blur the lines between programming ``verbally'' and ``nonverbally'' in Haskell. This begins with an interpreter that has graphical representations for values, but also includes editing a program text while it's running (``live coding'') and interactive representations of values (e.g. ``tangible values''). This territory is still largely uncharted from a purely functional perspective, probably due to a lack of easily installed graphical facilities. It is my hope that \emph{HyperHaskell} may provide a common ground for exploration and experimentation in this direction, in particular by offering the \verb!Display! class which may, perhaps one day, replace our good old \verb!Show! class. 29 | 30 | A simple form of live coding is planned for \emph{Level $\beta$}. 31 | 32 | \FurtherReading 33 | \begin{compactitem} 34 | \item Project page and downloads: \url{https://github.com/HeinrichApfelmus/hyper-haskell} 35 | \end{compactitem} 36 | \end{hcarentry} 37 | -------------------------------------------------------------------------------- /docs/hcar/hyperhaskell-2017-05.tex: -------------------------------------------------------------------------------- 1 | % HyperHaskellThestronglyhy-HH.tex 2 | \begin{hcarentry}[updated]{HyperHaskell -- The strongly hyped Haskell interpreter} 3 | \label{hyper-haskell} 4 | \report{Heinrich Apfelmus}%05/17 5 | \release{0.1.0.0} 6 | \status{available, active development} 7 | \makeheader 8 | 9 | \emph{HyperHaskell} is a graphical Haskell interpreter, not unlike GHCi, but 10 | hopefully more awesome. You use worksheets to enter expressions and evaluate 11 | them. Results are displayed graphically using HTML. 12 | 13 | \emph{HyperHaskell} is intended to be \emph{easy to install}. It is 14 | cross-platform and should run on Linux, Mac and Windows. Internally, it uses 15 | the GHC API to interpret Haskell programs, and the graphical front-end is 16 | built on the Electron framework. \emph{HyperHaskell} is open source. 17 | 18 | \emph{HyperHaskell}'s main attraction is a \verb`Display` class that 19 | supersedes the good old \verb`Show` class. The result looks like this: 20 | 21 | %** 22 | %*ignore 23 | \begin{center} 24 | \includegraphics[width=\columnwidth]{html/worksheet-diagrams.png} 25 | \end{center} 26 | %*endignore 27 | 28 | \subsubsection*{Current status} 29 | 30 | \emph{HyperHaskell} is currently \emph{Level $\alpha$}. Compared to the previous report, no new release has been made, but basic features are working. A new cell type, the \emph{text cell}, has been implemented, but not yet released. 31 | 32 | I am looking for help in setting up binary releases on the Windows platform! 33 | 34 | \subsubsection*{Future development} 35 | 36 | Programming a computer usually involves writing a program text in a particular 37 | language, a ``verbal'' activity. But computers can also be instructed by 38 | gestures, say, a mouse click, which is a ``nonverbal'' activity. The long term 39 | goal of \emph{HyperHaskell} is to blur the lines between programming 40 | ``verbally'' and ``nonverbally'' in Haskell. This begins with an interpreter 41 | that has graphical representations for values, but also includes editing a 42 | program text while it's running (``live coding'') and interactive 43 | representations of values (e.g. ``tangible values''). This territory is still 44 | largely uncharted from a purely functional perspective, probably due to a lack 45 | of easily installed graphical facilities. It is my hope that 46 | \emph{HyperHaskell} may provide a common ground for exploration and 47 | experimentation in this direction, in particular by offering the 48 | \verb!Display! class which may, perhaps one day, replace our good old 49 | \verb!Show! class. 50 | 51 | A simple form of live coding is planned for \emph{Level $\beta$}. 52 | 53 | \FurtherReading 54 | \begin{compactitem} 55 | \item Project homepage and downloads: \url{https://github.com/HeinrichApfelmus/hyper-haskell} 56 | \end{compactitem} 57 | \end{hcarentry} 58 | -------------------------------------------------------------------------------- /docs/hcar/hyperhaskell-2017-11.tex: -------------------------------------------------------------------------------- 1 | % HyperHaskellThestronglyhy-HH.tex 2 | \begin{hcarentry}[updated]{HyperHaskell -- The strongly hyped Haskell interpreter} 3 | \label{hyper-haskell} 4 | \report{Heinrich Apfelmus}%11/17 5 | \status{available, active development} 6 | \makeheader 7 | 8 | \emph{HyperHaskell} is a graphical Haskell interpreter, not unlike GHCi, but 9 | hopefully more awesome. You use worksheets to enter expressions and evaluate 10 | them. Results are displayed graphically using HTML. 11 | 12 | \emph{HyperHaskell} is intended to be \emph{easy to install}. It is 13 | cross-platform and should run on Linux, Mac and Windows. Internally, it uses 14 | the GHC API to interpret Haskell programs, and the graphical front-end is 15 | built on the Electron framework. \emph{HyperHaskell} is open source. 16 | 17 | \emph{HyperHaskell}'s main attraction is a \verb`Display` class that 18 | supersedes the good old \verb`Show` class. The result looks like this: 19 | 20 | %** 21 | %*ignore 22 | \begin{center} 23 | \includegraphics[width=\columnwidth]{html/worksheet-diagrams.png} 24 | \end{center} 25 | %*endignore 26 | 27 | \subsubsection*{Status} 28 | 29 | \emph{HyperHaskell} is currently \emph{Level $\alpha$}. The latest stable release is \verb`0.1.0.2`. 30 | Compared to the previous report, no new release has been made, but basic features are working. 31 | It is now possible to interpret statements in the \verb`IO` monad and to bind variables, greatly enhancing the usefulness of the interpreter. 32 | 33 | Support for the Nix package manager has been implemented by Rodney Lorrimar. 34 | I am looking for help in setting up binary releases on the Windows platform! 35 | 36 | \subsubsection*{Future development} 37 | 38 | Programming a computer usually involves writing a program text in a particular 39 | language, a ``verbal'' activity. But computers can also be instructed by 40 | gestures, say, a mouse click, which is a ``nonverbal'' activity. The long term 41 | goal of \emph{HyperHaskell} is to blur the lines between programming 42 | ``verbally'' and ``nonverbally'' in Haskell. This begins with an interpreter 43 | that has graphical representations for values, but also includes editing a 44 | program text while it's running (``live coding'') and interactive 45 | representations of values (e.g. ``tangible values''). This territory is still 46 | largely uncharted from a purely functional perspective, probably due to a lack 47 | of easily installed graphical facilities. It is my hope that 48 | \emph{HyperHaskell} may provide a common ground for exploration and 49 | experimentation in this direction, in particular by offering the 50 | \verb!Display! class which may, perhaps one day, replace our good old 51 | \verb!Show! class. 52 | 53 | A simple form of live coding is planned for \emph{Level $\beta$}, and I am experimenting with interactive music programming. 54 | 55 | \FurtherReading 56 | \begin{compactitem} 57 | \item Project homepage and downloads: \url{https://github.com/HeinrichApfelmus/hyper-haskell} 58 | \end{compactitem} 59 | \end{hcarentry} 60 | -------------------------------------------------------------------------------- /docs/hcar/template.tex: -------------------------------------------------------------------------------- 1 | \documentclass{scrreprt} 2 | \usepackage{paralist} 3 | \usepackage{graphicx} 4 | \usepackage[final]{hcar} 5 | 6 | %include polycode.fmt 7 | 8 | \begin{document} 9 | \include{hyperhaskell-2017-11} 10 | \end{document} 11 | -------------------------------------------------------------------------------- /docs/misc.md: -------------------------------------------------------------------------------- 1 | Misc 2 | ==== 3 | 4 | This document records snippets of information that are useful for developing HyperHaskell. 5 | 6 | Haskell 7 | ======= 8 | 9 | How does GHCi evaluate expressions on the prompt? 10 | ------------------------------------------------- 11 | 12 | In particular, the question is how GHCi distinguishes between expressions of `IO` type, which are executed, and expressions of non-IO type, which are wrapped in a call to `show`. 13 | 14 | Let's have a look at the [GHC source code](https://github.com/ghc/ghc). Following the execution path, we find the following functions: 15 | 16 | * [GHCi.UI.runStmt](https://github.com/ghc/ghc/blob/ghc-8.0/ghc/GHCi/UI.hs#L941) 17 | 18 | "Entry point to execute some haskell code from user" 19 | 20 | The prompt analyzed and falls into 3 cases: "import", statement, declaration. 21 | 22 | * [GHCi.UI.Monad.runStmt](https://github.com/ghc/ghc/blob/ghc-8.0/ghc/GHCi/UI/Monad.hs#L310) 23 | 24 | "Run a single Haskell expression" 25 | 26 | This function essentially just calls `GHC.execStmt`. 27 | 28 | * [GHC.execStmt](https://github.com/ghc/ghc/blob/master/compiler/main/InteractiveEval.hs#L164) 29 | 30 | "Run a statement in the current interactive context." 31 | 32 | This function is part of the public GHC API. 33 | 34 | Unfortunately, this is as far as I got. I don't quite understand what a `HValue` is. 35 | 36 | Fortunately, a Google search reveals that the relevant code is actually contained in the type checker. 37 | 38 | * [compiler/typecheck/TcRnDriver.hs](https://github.com/ghc/ghc/blob/ghc-8.0/compiler/typecheck/TcRnDriver.hs#L1737) 39 | 40 | Section "Typechecking Statements in GHCi". 41 | 42 | > By 'lift' and 'environment we mean that the code is changed to 43 | execute properly in an IO monad. See Note [Interactively-bound Ids 44 | in GHCi] in HscTypes for more details. We do this lifting by trying 45 | different ways ('plans') of lifting the code into the IO monad and 46 | type checking each plan until one succeeds. 47 | 48 | Apparently, 49 | 50 | > The plans are: 51 | > 52 | > A. [it <- e; print it] but not if it::() 53 | > 54 | > B. [it <- e] 55 | > 56 | > C. [let it = e; print it] 57 | 58 | In other words, GHC tries to typecheck the expression at the prompt in different contexts, and uses the first plan that works. 59 | 60 | 61 | Haskell path information 62 | ------------------------ 63 | 64 | **Cabal** 65 | 66 | The configuration file is in 67 | 68 | ~/.cabal/config 69 | 70 | It contains several fields that are relevant: 71 | 72 | extra-prog-path: /Users/apfelmus/.cabal/bin 73 | 74 | 75 | **Stack** 76 | 77 | Once we know where a particular `stack.yml` file is, we can use 78 | 79 | stack --stack-yaml=foo/stack.yaml 80 | 81 | to use the stack utility relative to this location. We can query various paths, in particular `GHC_PACKAGE_PATH`: 82 | 83 | stack path --ghc-package-path 84 | 85 | But to execute the interpreter server, including the right `GHC_PACKAGE_PATH`, it is actually simpler to just call 86 | 87 | stack exec hyper-haskell-server # execute binary 88 | 89 | The path of the interpreter executable can be obtained from 90 | 91 | stack exec -- which hyper-haskell-server # retrieve full path of the binary 92 | 93 | For my setup, a minimal environment is given by 94 | 95 | env -i \ 96 | PATH=/usr/bin:/Applications/ghc-7.10.3.app/Contents/bin \ 97 | HOME=/Users/hgz \ 98 | stack exec -- hyper-haskell-server 99 | 100 | JavaScript 101 | ========== 102 | 103 | Node.js — External Processes 104 | ----------------------------- 105 | 106 | We can run external processes and read their `stdout`. Example: 107 | 108 | let result = process.spawnSync(ghcbindir + '/stack', 109 | ['exec', 'printenv', '--', 'GHC_PACKAGE_PATH'], { 110 | cwd : dir, 111 | encoding : 'utf8', 112 | env : newEnv(), 113 | }).stdout.trim() 114 | 115 | Note that most UNIX utilities will traditionally emit a newline at the end of the output, and we use `trim()` to get rid of it. 116 | 117 | 118 | Cabal and GHC package environments 119 | ---------------------------------- 120 | 121 | A working installation of the HyperHaskell interpreter backend consists of two things: 122 | 123 | 1. The interpreter executable `hyper-haskell-server`, linked against a compilation A of the `hyper` package. 124 | 2. A package database that contains this compilation A of the `hyper` package. This database needs to be in scope when running the interpreter excutable. 125 | 126 | How to provision these? 127 | 128 | As of December 2024, it seemed like GHC [package environments][package-env] might be useful to provision the above setup, but unfortunately, this does not work. 129 | 130 | The following command will install the package `hyper` into the package environment `PKG_ENV`: 131 | 132 | ``` 133 | cabal install --lib hyper --package-env ${PKG_ENV} 134 | ``` 135 | 136 | The following command will install `hyper-haskell-server` into the directory `DIR` while referring to packages from the package environment `PKG_ENV`: 137 | 138 | ``` 139 | cabal install hyper-haskell-server --package-env ${PKG_ENV} --installdir=${DIR} 140 | ``` 141 | 142 | By using the same package environment `PKG_ENV`, we make sure that both the interpreter exectuable and the package environment refer to the same compilation of the `hyper` package. 143 | 144 | Unfortunately, the two commands above do not produce a working setup: `cabal install --lib` does [not install dependencies][cabal-issue-6263], and the GHC API as used by `hyper-haskell-server` is unable to handle that; I get error messages of the form 145 | 146 | ``` 147 | cannot satisfy -package-id hypr-xtr-0.2.0.1-71ddd09d: 148 | hypr-xtr-0.2.0.1-71ddd09d is unusable due to missing dependencies: 149 | […] 150 | ``` 151 | 152 | (The "missing dependency" appears to be present in the package database, and `ghci` is able to handle the situation, but the use of the GHC API by `hyper-haskell-server` is not.) 153 | 154 | To summarize, as of December 2024, I can't get GHC package environments to yield a working installation of the HyperHaskell interpreter backend. 155 | 156 | [package-env]: https://ghc.gitlab.haskell.org/ghc/doc/users_guide/packages.html#package-environments 157 | [cabal-issue-6263]: https://github.com/haskell/cabal/issues/6263 158 | 159 | 160 | Tools 161 | ===== 162 | 163 | Testing POST requests 164 | --------------------- 165 | 166 | The `curl` utility is able to send POST requests and receive a response. Example: 167 | 168 | $ curl --data "query=Prelude" http://localhost:8024/setImports 169 | {"status":"ok"} 170 | $ curl --data "query=3*4::Int" http://localhost:8024/eval 171 | {"status":"ok","value":"12"} 172 | 173 | Note that `curl` will not, by default, add a newline to the end of the received request; this may screw up the terminal. However, the option `-w '\n'` will add said newline. To make it a default, use the `~/.curlrc` file [[StackOverflow]][12849584]. 174 | 175 | echo '-w "\n"' >> ~/.curlrc 176 | 177 | [12849584]: http://stackoverflow.com/questions/12849584/automatically-add-newline-at-end-of-curl-response-body 178 | 179 | How to create an .icns file on OS X? 180 | ------------------------------------ 181 | 182 | Application icons on OS X are stored in `.icns` files. There is a command line utility called `iconutil` that is supposed to be able to create such files, but unfortunately, it only creates garbled icons for me. No idea why. Online converters seem to work fine. 183 | 184 | How to remove color profiles from images on OS X? 185 | ------------------------------------------------- 186 | 187 | sips -d profile --deleteColorManagementProperties *filename* 188 | -------------------------------------------------------------------------------- /docs/screenshots/app-osx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/docs/screenshots/app-osx.png -------------------------------------------------------------------------------- /docs/screenshots/settings-back-end-cabal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/docs/screenshots/settings-back-end-cabal.png -------------------------------------------------------------------------------- /docs/screenshots/settings-back-end-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/docs/screenshots/settings-back-end-stack.png -------------------------------------------------------------------------------- /docs/screenshots/worksheet-diagrams.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/docs/screenshots/worksheet-diagrams.png -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1681378341, 9 | "narHash": "sha256-2qUN04W6X9cHHytEsJTM41CmusifPTC0bgTtYsHSNY8=", 10 | "owner": "hamishmack", 11 | "repo": "flake-utils", 12 | "rev": "2767bafdb189cd623354620c2dacbeca8fd58b17", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "hamishmack", 17 | "ref": "hkm/nested-hydraJobs", 18 | "repo": "flake-utils", 19 | "type": "github" 20 | } 21 | }, 22 | "nixpkgs": { 23 | "locked": { 24 | "lastModified": 1726423290, 25 | "narHash": "sha256-KjzvVbfO9MdFZJWVYh5j70uwoF/ZQB2z385jv3Q4sjs=", 26 | "owner": "NixOS", 27 | "repo": "nixpkgs", 28 | "rev": "dbfe1b17a208c5715acd6586ca0e28879aa1339c", 29 | "type": "github" 30 | }, 31 | "original": { 32 | "owner": "NixOS", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs" 41 | } 42 | }, 43 | "systems": { 44 | "locked": { 45 | "lastModified": 1681028828, 46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 | "owner": "nix-systems", 48 | "repo": "default", 49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-systems", 54 | "repo": "default", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = ''HyperHaskell''; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs"; 6 | flake-utils.url = "github:hamishmack/flake-utils/hkm/nested-hydraJobs"; 7 | }; 8 | 9 | outputs = inputs: 10 | let 11 | supportedSystems = [ 12 | "x86_64-linux" 13 | "x86_64-darwin" 14 | "aarch64-linux" 15 | "aarch64-darwin" 16 | ]; in 17 | inputs.flake-utils.lib.eachSystem supportedSystems (system: 18 | let 19 | # ########################################### 20 | # Imports 21 | 22 | # Packages with patches 23 | pkgs = inputs.nixpkgs.legacyPackages.${system}; 24 | 25 | # Library 26 | lib = pkgs.lib; 27 | 28 | # ########################################### 29 | # Helpers 30 | 31 | in rec { 32 | packages = { }; 33 | 34 | apps = { }; 35 | 36 | devShells.default = pkgs.mkShell { 37 | buildInputs = [ 38 | pkgs.just 39 | 40 | pkgs.ghc 41 | pkgs.pkg-config 42 | pkgs.zlib 43 | 44 | pkgs.nodejs_22 45 | pkgs.electron 46 | ] ++ lib.optionals pkgs.stdenv.isDarwin ([ 47 | pkgs.darwin.apple_sdk.frameworks.Cocoa 48 | pkgs.darwin.apple_sdk.frameworks.CoreServices 49 | pkgs.darwin.objc4.all 50 | ]); 51 | }; 52 | 53 | shellHook = '' 54 | # add shell hook here 55 | ''; 56 | } 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /haskell/hyper-extra/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2024 Heinrich Apfelmus 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Heinrich Apfelmus nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /haskell/hyper-extra/hyper-extra.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 3.0 2 | name: hyper-extra 3 | version: 0.2.0.2 4 | synopsis: Display instances for the HyperHaskell graphical Haskell interpreter 5 | description: 6 | This package is part of the /HyperHaskell/ project and provides 7 | visual representations for various data types, in particular 8 | diagrams from the @diagrams@ package. 9 | . 10 | category: Graphics, Pretty Printer 11 | homepage: https://github.com/HeinrichApfelmus/hyper-haskell 12 | license: BSD-3-Clause 13 | license-file: LICENSE 14 | 15 | author: Heinrich Apfelmus 16 | maintainer: Heinrich Apfelmus 17 | copyright: (c) Heinrich Apfelmus 2016-2024 18 | 19 | tested-with: 20 | , GHC == 8.10.7 21 | , GHC == 9.2.8 22 | , GHC == 9.4.7 23 | , GHC == 9.6.6 24 | , GHC == 9.8.4 25 | , GHC == 9.10.1 26 | 27 | source-repository head 28 | type: git 29 | location: git://github.com/HeinrichApfelmus/hyper-haskell.git 30 | subdir: haskell/hyper-extra/ 31 | 32 | library 33 | hs-source-dirs: src 34 | default-language: Haskell2010 35 | build-depends: 36 | , base >= 4.6 && < 4.22 37 | , diagrams-lib >= 1.3 && < 1.5 38 | , diagrams-svg >= 1.4 && < 1.5 39 | , hyper >= 0.2 && < 0.3 40 | , QuickCheck >= 2.3.0.2 && < 2.16 41 | , svg-builder >= 0.1 && < 0.2 42 | , text >= 0.11 && < 2.2 43 | exposed-modules: 44 | Hyper.Extra 45 | -------------------------------------------------------------------------------- /haskell/hyper-extra/src/Hyper/Extra.hs: -------------------------------------------------------------------------------- 1 | module Hyper.Extra ( 2 | -- * Synopsis 3 | -- | Visual representation for various data types. 4 | 5 | -- * SVG 6 | fromSvg, 7 | -- * Diagrams 8 | dia, 9 | 10 | -- * QuickCheck 11 | hyperCheck, 12 | ) where 13 | 14 | import Hyper 15 | 16 | import qualified Data.Text as Text 17 | import qualified Data.Text.Lazy as Text.Lazy 18 | 19 | import Diagrams.Prelude hiding (pre) 20 | import Diagrams.Backend.SVG 21 | import qualified Graphics.Svg 22 | 23 | import qualified Test.QuickCheck as Q 24 | 25 | {----------------------------------------------------------------------------- 26 | Integration of the `svg-builder` and `diagrams-svg` spackages 27 | ------------------------------------------------------------------------------} 28 | -- | Render a SVG document. 29 | -- This assumes that the argument is indeed a complete SVG document, 30 | -- i.e. an @@ tag.s 31 | fromSvg :: Graphics.Svg.Element -> Graphic 32 | fromSvg = html . Text.Lazy.toStrict . Graphics.Svg.renderText 33 | 34 | -- | Render a diagram via SVG. 35 | dia :: QDiagram SVG V2 Double Any -> Graphic 36 | dia = fromSvg . renderDia SVG (SVGOptions (mkWidth 250) Nothing (Text.pack "") [] True) 37 | 38 | {----------------------------------------------------------------------------- 39 | Integration of the `QuickCheck` package 40 | ------------------------------------------------------------------------------} 41 | hyperCheck :: Q.Testable prop => prop -> IO Graphic 42 | hyperCheck = 43 | fmap (pre . Q.output) . Q.quickCheckWithResult (Q.stdArgs{ Q.chatty = False }) 44 | 45 | pre s = html . Text.pack $ "
" ++ s ++ "
" 46 | -------------------------------------------------------------------------------- /haskell/hyper-haskell-server.nix: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Nix derivation for the server back-end of the 3 | # HyperHaskell graphical Haskell interpreter. 4 | # 5 | # Install this file with: 6 | # nix-env -if haskell/hyper-haskell-server.nix 7 | # 8 | # Then run the application with: 9 | # nix-shell -p electron --run "electron app" 10 | # 11 | # Remember to set the Interpreter Back-end to "nix" in Settings, 12 | # and then Ctrl-R to Reload imports. 13 | # 14 | # Other haskell packages can be added to the environment by using the 15 | # --arg extra parameter of nix-env. 16 | # 17 | ############################################################################ 18 | 19 | { pkgs ? import {}, extra ? ["hyper-extra"] }: 20 | 21 | let 22 | executableHaskellDepends = ps: with ps; [ 23 | aeson base bytestring deepseq exceptions hint hyper scotty text 24 | transformers 25 | ] ++ (map (p: ps.${p}) extra); 26 | 27 | ghc = pkgs.haskellPackages.ghcWithPackages executableHaskellDepends; 28 | 29 | drv = ghc.haskellPackages.mkDerivation { 30 | pname = "hyper-haskell-server"; 31 | version = "0.1.0.1"; 32 | sha256 = "43b0d770896ca0c38aee876bb23ee03b20009ce7afab4d6b5ca07a99f6e7f290"; 33 | isLibrary = false; 34 | isExecutable = true; 35 | executableHaskellDepends = executableHaskellDepends ghc.haskellPackages; 36 | description = "Server back-end for the HyperHaskell graphical Haskell interpreter"; 37 | license = pkgs.stdenv.lib.licenses.bsd3; 38 | 39 | buildTools = [ pkgs.makeWrapper ]; 40 | postInstall = '' 41 | wrapProgram "$out/bin/hyper-haskell-server" \ 42 | --set NIX_GHC ${ghc}/bin/ghc \ 43 | --set NIX_GHCPKG ${ghc}/bin/ghc-pkg \ 44 | --set NIX_GHC_LIBDIR ${ghc}/lib/ghc-8.0.2 45 | ''; 46 | }; 47 | 48 | in 49 | drv 50 | -------------------------------------------------------------------------------- /haskell/hyper-haskell-server/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2024 Heinrich Apfelmus 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Heinrich Apfelmus nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /haskell/hyper-haskell-server/hyper-haskell-server.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 3.0 2 | name: hyper-haskell-server 3 | version: 0.2.3.2 4 | synopsis: Server back-end for the HyperHaskell graphical Haskell interpreter 5 | description: 6 | This package is part of the /HyperHaskell/ project and provides 7 | the server back-end. 8 | . 9 | category: Compilers/Interpreters 10 | homepage: https://github.com/HeinrichApfelmus/hyper-haskell 11 | license: BSD-3-Clause 12 | license-file: LICENSE 13 | 14 | author: Heinrich Apfelmus 15 | maintainer: Heinrich Apfelmus 16 | copyright: (c) Heinrich Apfelmus 2016-2024 17 | 18 | tested-with: 19 | , GHC == 8.10.7 20 | , GHC == 9.2.8 21 | , GHC == 9.4.7 22 | , GHC == 9.6.6 23 | , GHC == 9.8.4 24 | , GHC == 9.10.1 25 | 26 | source-repository head 27 | type: git 28 | location: git://github.com/HeinrichApfelmus/hyper-haskell.git 29 | subdir: haskell/hyper-haskell-server/ 30 | 31 | executable hyper-haskell-server 32 | ghc-options: -threaded 33 | cpp-options: -DCABAL 34 | default-language: Haskell2010 35 | 36 | build-depends: 37 | , aeson (>= 0.7 && < 0.10) || == 0.11.* || (>= 1.0 && < 2.3) 38 | , base >= 4.6 && < 4.22 39 | , bytestring >= 0.9 && < 0.13 40 | , deepseq >= 1.2 && < 1.7 41 | , exceptions >= 0.6 && < 0.11 42 | , filepath >= 1.0 && < 1.6 43 | , haskell-src-exts >= 1.17 && < 1.24 44 | , hint >= 0.8 && < 0.10 45 | , hyper >= 0.2 && < 0.3 46 | , text >= 0.11 && < 2.2 47 | , transformers >= 0.3 && < 0.7 48 | , scotty >= 0.20 && < 0.23 49 | 50 | hs-source-dirs: src 51 | main-is: Main.hs 52 | other-modules: 53 | HyperHaskell.Interpreter 54 | -------------------------------------------------------------------------------- /haskell/hyper-haskell-server/src/HyperHaskell/Interpreter.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE GADTSyntax #-} 3 | {-# LANGUAGE ExistentialQuantification #-} 4 | {-# LANGUAGE RankNTypes #-} 5 | {-# LANGUAGE ScopedTypeVariables #-} 6 | 7 | module HyperHaskell.Interpreter 8 | ( -- * Interpreter session 9 | Hint 10 | , Result (..) 11 | , Hint.InterpreterError (..) 12 | , Hint.errMsg 13 | , newInterpreter 14 | 15 | -- * Interpreter actions 16 | , cancel 17 | , setExtensions 18 | , setImports 19 | , setSearchPath 20 | , loadFiles 21 | , eval 22 | ) where 23 | 24 | import Control.Concurrent 25 | ( MVar 26 | , forkIO 27 | , myThreadId 28 | , throwTo 29 | , newEmptyMVar 30 | , putMVar 31 | , readMVar 32 | , takeMVar 33 | ) 34 | import Control.DeepSeq 35 | ( force 36 | ) 37 | import Control.Exception 38 | ( AsyncException (UserInterrupt) 39 | , evaluate 40 | ) 41 | import Control.Monad 42 | ( forever 43 | , forM 44 | , void 45 | ) 46 | import Control.Monad.Catch 47 | ( SomeException 48 | , catch 49 | , displayException 50 | , fromException 51 | ) 52 | import Control.Monad.IO.Class 53 | ( liftIO 54 | ) 55 | import Data.List 56 | ( groupBy 57 | ) 58 | import Data.Maybe 59 | ( fromMaybe 60 | , catMaybes 61 | ) 62 | import Hyper.Internal 63 | ( Graphic (..) 64 | ) 65 | import Text.Read 66 | ( readMaybe 67 | ) 68 | 69 | import qualified Data.Text as T 70 | import qualified Language.Haskell.Interpreter as Hint 71 | import qualified Language.Haskell.Exts as Haskell 72 | import qualified Hyper.Internal as Hyper 73 | import qualified System.FilePath.Posix as System 74 | 75 | {----------------------------------------------------------------------------- 76 | Interpreter 77 | Exports 78 | ------------------------------------------------------------------------------} 79 | -- | Set Haskell language extensions used in the current interpreter session. 80 | setExtensions :: Hint -> [String] -> IO (Result ()) 81 | setExtensions hint xs = 82 | run hint $ Hint.set [Hint.languageExtensions Hint.:= ys] 83 | where 84 | readExtension :: String -> Hint.Extension 85 | readExtension x = case readMaybe x of 86 | Nothing -> error $ "Unknown language extension: " ++ x 87 | Just x -> x 88 | ys = map readExtension $ filter (not . null) xs 89 | 90 | -- | Set module imports used in the current interpreter session. 91 | -- 92 | -- NOTE: We implicitly always load the "Prelude" and "Hyper" modules. 93 | setImports :: Hint -> [String] -> IO (Result ()) 94 | setImports hint = 95 | run hint . Hint.setImportsF 96 | . (++ map simpleImport ["Prelude", "Hyper"]) 97 | . map (parseImport . words) 98 | . filter (not . null) 99 | 100 | mkModuleImport :: String -> Hint.ModuleQualification -> Hint.ModuleImport 101 | mkModuleImport m q = Hint.ModuleImport m q Hint.NoImportList 102 | 103 | simpleImport :: String -> Hint.ModuleImport 104 | simpleImport m = mkModuleImport m Hint.NotQualified 105 | 106 | parseImport :: [String] -> Hint.ModuleImport 107 | parseImport (x:xs) = 108 | if x == "import" then parse xs else parse (x:xs) 109 | where 110 | parse ["qualified", m, "as", alias] = 111 | mkModuleImport m (Hint.QualifiedAs $ Just alias) 112 | parse ["qualified", m] = mkModuleImport m (Hint.QualifiedAs Nothing) 113 | parse [m, "as", alias] = mkModuleImport m (Hint.ImportAs alias) 114 | parse [m] = mkModuleImport m Hint.NotQualified 115 | 116 | -- | Set search path for loading `.hs` source files 117 | setSearchPath :: Hint -> String -> FilePath -> IO (Result ()) 118 | setSearchPath hint xs dir = 119 | run hint $ Hint.set [Hint.searchPath Hint.:= paths] 120 | where 121 | paths = 122 | [ if System.isRelative x then dir System. x else x 123 | | x <- System.splitSearchPath xs 124 | ] 125 | 126 | -- | Load `.hs` source files. 127 | loadFiles :: Hint -> [FilePath] -> IO (Result ()) 128 | loadFiles hint xs = run hint $ do 129 | -- liftIO . print =<< Hint.get Hint.searchPath 130 | finalizeSession -- finalize the old session 131 | Hint.loadModules $ filter (not . null) xs 132 | 133 | -- | Evaluate an input cell. 134 | eval :: Hint -> String -> IO (Result Graphic) 135 | eval hint input = run hint $ do 136 | extensions <- Hint.get Hint.languageExtensions 137 | mgs <- forM (parsePrompts extensions input) $ \prompt -> case prompt of 138 | Expr code -> do 139 | -- To show the result of an expression, 140 | -- we wrap results into an implicit call to Hyper.display 141 | m <- Hint.interpret ("Hyper.displayIO " ++ Hint.parens code) 142 | (Hint.as :: IO Graphic) 143 | liftIO $ do 144 | g <- m 145 | x <- evaluate (force g) -- See NOTE [EvaluateToNF] 146 | pure $ Just x 147 | Other code -> do 148 | -- Otherwise, there is nothing to show and we pass the code on to GHC 149 | Hint.runStmt code 150 | pure Nothing 151 | TypeOf code -> do 152 | -- Query type information 153 | let pre s = "
" ++ code ++ " :: "++ s ++ "
" 154 | Just . Hyper.html . T.pack . pre <$> Hint.typeOf code 155 | Unknown code -> do 156 | pure . Just . Hyper.string 157 | $ "Unknown interpreter command :" ++ code 158 | pure . combineGraphics $ catMaybes mgs 159 | 160 | combineGraphics :: [Graphic] -> Graphic 161 | combineGraphics xs = Graphic { gHtml = T.concat $ map gHtml xs } 162 | 163 | -- | Statements that we can evaluate. 164 | type Stmt = Haskell.Stmt Haskell.SrcSpanInfo 165 | 166 | data Prompt 167 | = Expr String 168 | | TypeOf String 169 | | Other String 170 | | Unknown String 171 | 172 | -- | Parse an input cell into a list of prompts to evaluate 173 | parsePrompts :: [Hint.Extension] -> String -> [Prompt] 174 | parsePrompts extensions = 175 | map parsePrompt . map unlines . groupByIndent . stripIndent . lines 176 | where 177 | indent xs = if null xs then 0 else length . takeWhile (== ' ') $ head xs 178 | stripIndent xs = map (drop $ indent xs) xs 179 | groupByIndent = groupBy (\x y -> indent [y] > 0) 180 | 181 | parsePrompt code@(':':command) = case words command of 182 | ("type":xs) -> TypeOf $ unwords xs 183 | (x:_) -> Unknown x 184 | [] -> Unknown "(empty)" 185 | parsePrompt code = 186 | case Haskell.parseStmtWithMode mode code :: Haskell.ParseResult Stmt of 187 | Haskell.ParseOk (Haskell.Qualifier _ _) -> Expr code 188 | _ -> Other code 189 | 190 | exts = map (Haskell.parseExtension . show) extensions 191 | mode = Haskell.defaultParseMode { Haskell.extensions = exts } 192 | 193 | {- NOTE [EvaluateToNF] 194 | 195 | We evaluate the result in the interpreter thread to full normal form, 196 | because it may be that this evaluation does not terminate, 197 | in which case the user is likely to trigger a `UserInterrupt` asynchronous exception. 198 | But this exception is only delivered to and handled by the interpreter thread. 199 | Otherwise, the web server would be stuck trying to evaluate the result 200 | in order to serialize it, with no way for the user to interrupt this. 201 | 202 | -} 203 | 204 | {----------------------------------------------------------------------------- 205 | Interpreter 206 | Internals 207 | ------------------------------------------------------------------------------} 208 | -- | Finalize a session. 209 | finalizeSession :: Hint.Interpreter () 210 | finalizeSession = do 211 | setImportsInternal 212 | Hint.runStmt ("Hyper.Internal.finalizeSession") 213 | 214 | -- | Clear imports and import only the "Hyper.Internal" module qualified. 215 | setImportsInternal :: Hint.Interpreter () 216 | setImportsInternal = do 217 | let name = "Hyper.Internal" 218 | Hint.setImportsF 219 | [Hint.ModuleImport name (Hint.QualifiedAs $ Just name) Hint.NoImportList] 220 | 221 | {- 222 | -- | Run an interpreter action with only the "Hyper.Internal" module loaded. 223 | withInternal :: Interpreter a -> Interpreter a 224 | withInternal m = do 225 | xs <- Hint.getLoadedModules 226 | let name = "Hyper.Internal" 227 | Hint.setImportsQ [Hint.ModuleImport name (QualifiedAs $ Just name) NoImportList] 228 | a <- m 229 | Hint.setImportsQ xs 230 | pure a 231 | -} 232 | 233 | {----------------------------------------------------------------------------- 234 | Interpreter 235 | Thread 236 | ------------------------------------------------------------------------------} 237 | type Result a = Either Hint.InterpreterError a 238 | 239 | toInterpreterError :: SomeException -> Hint.InterpreterError 240 | toInterpreterError e = case fromException e of 241 | Just e -> e 242 | Nothing -> Hint.UnknownError (displayException e) 243 | 244 | #if MIN_VERSION_base(4,8,0) 245 | #else 246 | displayException :: SomeException -> String 247 | displayException = show 248 | #endif 249 | 250 | -- | Haskell Interpreter. 251 | data Hint = Hint 252 | { run :: forall a. Hint.Interpreter a -> IO (Result a) 253 | , cancel :: IO () 254 | } 255 | 256 | data Action where 257 | Action :: Hint.Interpreter a -> MVar (Result a) -> Action 258 | 259 | debug :: Hint.MonadIO m => String -> m () 260 | debug s = liftIO $ putStrLn s 261 | 262 | -- | Create and initialize a Haskell interpreter. 263 | -- Arguments: 264 | -- writeReady = function to call when the interpreter is ready to evaluate expressions. 265 | newInterpreter :: IO () -> IO (Hint, IO ()) -- ^ (send commands, interpreter loop) 266 | newInterpreter writeReady = do 267 | vin <- newEmptyMVar 268 | evalThreadId <- newEmptyMVar -- ThreadID of the thread responsible for evaluation 269 | 270 | let 271 | handler :: Hint.Interpreter () 272 | handler = do 273 | debug "Waiting for Haskell expression" 274 | Action ma vout <- liftIO $ takeMVar vin 275 | let right = liftIO . putMVar vout . Right =<< ma 276 | let left = liftIO . putMVar vout . Left . toInterpreterError 277 | debug "Got Haskell expression, evaluating" 278 | right `catch` left 279 | debug "Wrote result" 280 | 281 | run :: Hint.Interpreter a -> IO (Result a) 282 | run ma = do 283 | vout <- newEmptyMVar 284 | putMVar vin (Action ma vout) 285 | a <- takeMVar vout 286 | case a of 287 | Right _ -> pure () 288 | Left e -> debug $ show e 289 | pure a 290 | 291 | cancel :: IO () 292 | cancel = do 293 | t <- readMVar evalThreadId 294 | throwTo t UserInterrupt 295 | -- NOTE: `throwTo` may block if the thread `t` has masked asynchronous execptions. 296 | debug "UserInterrupt, evaluation cancelled" 297 | 298 | interpreterLoop :: IO () 299 | interpreterLoop = do 300 | putMVar evalThreadId =<< myThreadId 301 | -- NOTE: The failure branch of `catch` will `mask` asynchronous exceptions. 302 | let go = forever $ handler `catch` (\UserInterrupt -> pure ()) 303 | (e :: Either Hint.InterpreterError ()) <- Hint.runInterpreter $ do 304 | liftIO writeReady 305 | go 306 | case e of 307 | Left e' -> print e' 308 | Right _ -> pure () 309 | 310 | pure (Hint run cancel, interpreterLoop) 311 | -------------------------------------------------------------------------------- /haskell/hyper-haskell-server/src/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE ScopedTypeVariables #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE InstanceSigs #-} 4 | module Main where 5 | 6 | import Control.Concurrent 7 | ( forkIO 8 | ) 9 | import Control.Monad.Catch 10 | ( SomeException 11 | , catch 12 | , displayException 13 | , fromException 14 | ) 15 | import Control.Monad.IO.Class 16 | ( liftIO 17 | ) 18 | import Data.Aeson 19 | ( toJSON 20 | , (.=) 21 | ) 22 | import Data.Maybe 23 | ( fromMaybe 24 | ) 25 | import Data.String 26 | ( fromString 27 | ) 28 | import Foreign.C.Types 29 | ( CInt 30 | ) 31 | import GHC.IO.Handle.FD 32 | ( fdToHandle 33 | ) 34 | import Hyper.Internal 35 | ( Graphic (..) 36 | ) 37 | import Text.Read 38 | ( readMaybe 39 | ) 40 | 41 | import qualified Data.Aeson as JSON 42 | import qualified Data.Text as T 43 | import qualified Data.Text.Lazy as TL 44 | import qualified HyperHaskell.Interpreter as H 45 | import qualified System.Environment as System 46 | import qualified System.FilePath.Posix as System 47 | import qualified System.IO as System 48 | import qualified Web.Scotty as Web 49 | 50 | -- Interpreter 51 | say :: String -> IO () 52 | say = putStrLn 53 | 54 | {----------------------------------------------------------------------------- 55 | Main 56 | ------------------------------------------------------------------------------} 57 | defaultPort :: Int 58 | defaultPort = 8024 59 | 60 | main :: IO () 61 | main = do 62 | -- get port number from environment 63 | env <- System.getEnvironment 64 | let port = fromMaybe defaultPort $ readMaybe =<< Prelude.lookup "PORT" env 65 | 66 | -- get file descriptor (pipe) from the first argument 67 | args <- System.getArgs 68 | let writeReady = case args of 69 | (x:_) -> maybe (pure ()) writeReadyMsgToFD $ readMaybe x 70 | _ -> pure () 71 | 72 | -- Start interpreter and web server. See NOTE [MainThread] 73 | (hint, interpreterLoop) <- H.newInterpreter writeReady 74 | forkIO $ Web.scotty port (jsonAPI hint) 75 | interpreterLoop 76 | 77 | -- | Write the message "ready" to the specified file descriptor (pipe). 78 | writeReadyMsgToFD :: CInt -> IO () 79 | writeReadyMsgToFD fd0 = do 80 | handle <- GHC.IO.Handle.FD.fdToHandle (fromIntegral fd0) 81 | System.hSetBinaryMode handle False 82 | System.hSetEncoding handle System.utf8 83 | System.hSetBuffering handle System.LineBuffering 84 | System.hPutStr handle "ready" 85 | 86 | {- NOTE [MainThread] 87 | 88 | We fork the web server and run GHC in the main thread. 89 | This is because GHC registers handlers for signals like SIGTERM, 90 | but for some reason, these handlers only work correctly if GHC 91 | also has the main thread. 92 | 93 | -} 94 | 95 | {----------------------------------------------------------------------------- 96 | Exported JSON REST API 97 | ------------------------------------------------------------------------------} 98 | jsonAPI :: H.Hint -> Web.ScottyM () 99 | jsonAPI hint = do 100 | Web.post "/cancel" $ do 101 | liftIO $ H.cancel hint 102 | Web.json $ JSON.object [ "status" .= t "ok" ] 103 | Web.post "/setExtensions" $ 104 | Web.json . result =<< liftIO . H.setExtensions hint =<< getParam "query" 105 | Web.post "/setImports" $ 106 | Web.json . result =<< liftIO . H.setImports hint =<< getParam "query" 107 | Web.post "/setSearchPath" $ do 108 | x <- getParam "query" 109 | y <- getParam "dir" 110 | Web.json . result =<< liftIO (H.setSearchPath hint x y) 111 | Web.post "/loadFiles" $ 112 | Web.json . result =<< liftIO . H.loadFiles hint =<< getParam "query" 113 | Web.post "/eval" $ do 114 | Web.json . result =<< liftIO . H.eval hint =<< getParam "query" 115 | 116 | getParam :: Web.Parsable a => TL.Text -> Web.ActionM a 117 | getParam = Web.formParam 118 | 119 | t :: String -> T.Text 120 | t = fromString 121 | 122 | -- | Convert an interpreter result to JSON. 123 | result :: JSON.ToJSON a => H.Result a -> JSON.Value 124 | result (Left e) = JSON.object [ "status" .= t "error", "errors" .= err e] 125 | where 126 | err e = toJSON $ case e of 127 | H.UnknownError s -> [t s] 128 | H.WontCompile xs -> map (t . H.errMsg) xs 129 | H.NotAllowed s -> [t s] 130 | H.GhcException s -> [t s] 131 | result (Right x) = JSON.object [ "status" .= t "ok", "value" .= toJSON x ] 132 | 133 | instance JSON.ToJSON Graphic where 134 | toJSON :: Graphic -> JSON.Value 135 | toJSON g = JSON.object [ "type" .= t "html", "value" .= gHtml g ] 136 | -------------------------------------------------------------------------------- /haskell/hyper/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Heinrich Apfelmus 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Heinrich Apfelmus nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /haskell/hyper/hyper.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 3.0 2 | name: hyper 3 | version: 0.2.1.2 4 | synopsis: Display class for the HyperHaskell graphical Haskell interpreter 5 | description: 6 | This package is part of the /HyperHaskell/ project and provides 7 | the @Display@ class for visualizing and pretty printing Haskell values. 8 | . 9 | category: Graphics, Pretty Printer 10 | homepage: https://github.com/HeinrichApfelmus/hyper-haskell 11 | license: BSD-3-Clause 12 | license-file: LICENSE 13 | 14 | author: Heinrich Apfelmus 15 | maintainer: Heinrich Apfelmus 16 | copyright: (c) Heinrich Apfelmus 2016-2024 17 | 18 | tested-with: 19 | , GHC == 8.10.7 20 | , GHC == 9.2.8 21 | , GHC == 9.4.7 22 | , GHC == 9.6.6 23 | , GHC == 9.8.4 24 | , GHC == 9.10.1 25 | 26 | source-repository head 27 | type: git 28 | location: git://github.com/HeinrichApfelmus/hyper-haskell.git 29 | subdir: haskell/hyper/ 30 | 31 | library 32 | hs-source-dirs: src 33 | default-language: Haskell2010 34 | build-depends: 35 | , base >= 4.5 && < 4.22 36 | , deepseq >= 1.2 && < 1.7 37 | , blaze-html >= 0.7 && < 0.10 38 | , text >= 0.11 && < 2.2 39 | exposed-modules: 40 | Hyper 41 | Hyper.Internal 42 | -------------------------------------------------------------------------------- /haskell/hyper/src/Hyper.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances #-} 2 | module Hyper ( 3 | -- * Synopsis 4 | -- | Visualizing and representing Haskell values in the HyperHaskell interpreter. 5 | 6 | -- * Graphics 7 | Graphic, string, html, 8 | 9 | -- * Display class 10 | Display(..), 11 | 12 | -- * Internal 13 | displayIO, addFinalizerSession, 14 | ) where 15 | 16 | import Hyper.Internal 17 | -------------------------------------------------------------------------------- /haskell/hyper/src/Hyper/Internal.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE FlexibleInstances, UndecidableInstances #-} 2 | {-# LANGUAGE DeriveDataTypeable #-} 3 | module Hyper.Internal ( 4 | -- * Synopsis 5 | -- | Internal data types used by the HyperHaskell back-end 6 | -- to analyze values constructed with the 'Hyper' module. 7 | 8 | -- * Documentation 9 | Graphic(..), string, html, 10 | Display(..), 11 | finalizeSession, addFinalizerSession, 12 | ) where 13 | 14 | import Control.DeepSeq 15 | import Data.IORef 16 | import Data.List (isPrefixOf) 17 | import Data.Typeable 18 | import System.IO.Unsafe (unsafePerformIO) 19 | 20 | import qualified Data.Text as T 21 | import qualified Data.Text.Lazy as TL 22 | import qualified Text.Blaze.Html as H 23 | import qualified Text.Blaze.Html5 as H 24 | import qualified Text.Blaze.Html.Renderer.Text as H 25 | 26 | {----------------------------------------------------------------------------- 27 | Graphics 28 | ------------------------------------------------------------------------------} 29 | -- | A graphical representation of data. 30 | data Graphic = Graphic { gHtml :: T.Text } deriving (Typeable) 31 | 32 | instance NFData Graphic where rnf g = rnf (gHtml g) 33 | 34 | -- | Render a 'String' as a 'Graphic'. 35 | string :: String -> Graphic 36 | string = Graphic . TL.toStrict . H.renderHtml . H.div . H.toHtml 37 | 38 | -- | Render arbitrary HTML code as a 'Graphic'. 39 | -- 40 | -- NOTE: This function does not do check whether the input is well-formed HTML. 41 | -- 42 | -- NOTE: This function will probably deprecated once we figure out 43 | -- how to do this properly, but for now, just use it. 44 | html :: T.Text -> Graphic 45 | html = Graphic 46 | 47 | {----------------------------------------------------------------------------- 48 | Display class 49 | ------------------------------------------------------------------------------} 50 | -- | Class for displaying Haskell values. 51 | class Display a where 52 | display :: a -> Graphic 53 | displayIO :: a -> IO Graphic 54 | 55 | displayIO = return . display 56 | 57 | instance Display () where display x = x `seq` fromShow x 58 | instance Display Graphic where display = id 59 | instance Display Bool where display = fromShow 60 | instance Display Double where display = fromShow 61 | instance Display Integer where display = fromShow 62 | instance Display Int where display = fromShow 63 | instance Display String where display = fromShow 64 | instance Display [Int] where display = displayList 65 | instance Display [String] where display = displayList 66 | 67 | instance Display a => Display (IO a) where 68 | display _ = string "<>" 69 | displayIO m = fmap display m 70 | 71 | fromShow :: Show a => a -> Graphic 72 | fromShow = string . show 73 | 74 | displayList :: Show a => [a] -> Graphic 75 | displayList = fromShow 76 | 77 | {----------------------------------------------------------------------------- 78 | Interpreter management 79 | ------------------------------------------------------------------------------} 80 | refFinalizers :: IORef [IO ()] 81 | refFinalizers = unsafePerformIO $ newIORef [] 82 | 83 | addFinalizerSession :: IO () -> IO () 84 | addFinalizerSession m = modifyIORef refFinalizers (m:) 85 | 86 | finalizeSession :: IO () 87 | finalizeSession = do 88 | sequence_ =<< readIORef refFinalizers 89 | writeIORef refFinalizers [] 90 | 91 | -------------------------------------------------------------------------------- /haskell/stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-18.19 2 | extra-deps: 3 | # diagrams and SVG 4 | - diagrams-core-1.5.0 5 | - diagrams-lib-1.4.5.1 6 | - diagrams-svg-1.4.3 7 | - svg-builder-0.1.1 8 | # Csound 9 | - wl-pprint-1.2.1 10 | - csound-expression-dynamic-0.3.3 11 | - csound-expression-opcodes-0.0.4.0 12 | - csound-expression-typed-0.2.2.0 13 | - temporal-media-0.6.3 14 | - data-fix-0.3.2 15 | - data-fix-cse-0.0.3 16 | - csound-expression-5.3.2 17 | - csound-catalog-0.7.3 18 | - csound-sampler-0.0.9.0 19 | - sharc-timbre-0.2 20 | # port midi 21 | - PortMidi-0.2.0.0 22 | # Threepenny-GUI for nice JavaScript experiments 23 | # - file-embed-0.0.10.1 24 | # - threepenny-gui-0.8.2.0 25 | # - exceptions-0.10.0 26 | # - hint-0.8.0 27 | # Henning's stuff 28 | # - blas-ffi-0.1 29 | # - comfort-array-0.3.1 30 | # - guarded-allocation-0.0.1 31 | # - lapack-ffi-0.0.2 32 | # - netlib-ffi-0.1.1 33 | # - primitive-0.7.1.0 34 | # - boxes-0.1.5 35 | # - tfp-1.0.1.1 36 | - active-0.2.0.15@sha256:56d95c2205a8e52911bf08784b9958be430036e6ea16521b957c3e27cc71235c,1792 37 | - dual-tree-0.2.3.0@sha256:be4b8ecb2ecef798f88ee569d043f3f19c32f6dd083329cd7cfcb482f0bc6233,2882 38 | - monoid-extras-0.6.1@sha256:294eaeb51e0d047500119131f133bc4a7988f330baa4bb20256995aae3385b33,2220 39 | packages: 40 | - hyper 41 | - hyper-extra 42 | - hyper-haskell-server 43 | 44 | -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | ELECTRON := "electron" 2 | 3 | VERSION := "0.2.3.0" 4 | 5 | ###################################################################### 6 | # Development targets 7 | 8 | test: 9 | just build 10 | TESTING=1 {{ELECTRON}} app 11 | killall hyper-haskell-server 12 | 13 | run: 14 | just build 15 | {{ELECTRON}} app 16 | 17 | build: 18 | cabal build all 19 | 20 | ###################################################################### 21 | # Release targets 22 | 23 | APP_NAME := "HyperHaskell" 24 | 25 | DIR_DARWIN := "build/" + APP_NAME + "-darwin-arm64" 26 | DIR_WIN32 := "build\\" + APP_NAME + "-win32-x64" 27 | 28 | # You need to call 29 | # 30 | # npm install --save-dev @electron/packager 31 | # npm install electron --save-dev 32 | # 33 | # before the following recipe has a chance of working 34 | pkg-darwin: 35 | mkdir -p build \ 36 | && npx @electron/packager \ 37 | app \ 38 | --out=build \ 39 | --overwrite \ 40 | --platform=darwin \ 41 | --arch=arm64 \ 42 | --icon=resources/icons/icon.icns \ 43 | --extend-info=resources/macOS-Info.plist \ 44 | && rm {{DIR_DARWIN}}/LICENSE \ 45 | && cp resources/LICENSE.electron.txt {{DIR_DARWIN}}/LICENSE.electron.txt \ 46 | && rm {{DIR_DARWIN}}/version 47 | 48 | pkg-win32: 49 | cmd /C "(if not exist build md build) && (electron-packager app --out=build\ --overwrite --platform=win32 --icon=resources\icons\icon.ico) && (del $(DIR_WIN32)\LICENSE) && (echo F|xcopy resources\LICENSE.electron.txt $(DIR_WIN32)\LICENSE.electron.txt) && (del $(DIR_WIN32)\version)" 50 | 51 | zip-darwin: 52 | cd {{DIR_DARWIN}} && zip -r ../HyperHaskell-v{{VERSION}}-darwin-x64.zip * 53 | 54 | zip-win32: 55 | cmd /C "(cd {{DIR_WIN32}} && (7z a -tzip ..\HyperHaskell-v{{VERSION}}-win32-x64.zip .\)" 56 | 57 | hackage: 58 | cabal sdist \ 59 | && cabal upload haskell/hyper \ 60 | && cabal upload haskell/hyper-extra \ 61 | && cabal upload haskell/hyper-haskell-server 62 | -------------------------------------------------------------------------------- /resources/LICENSE.electron.txt: -------------------------------------------------------------------------------- 1 | HyperHaskell was built with the 2 | 3 | Electron framework 4 | http://electron.atom.io/ 5 | 6 | The Electron framework is subject to the following license: 7 | 8 | Copyright (c) 2014 GitHub Inc. 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining 11 | a copy of this software and associated documentation files (the 12 | "Software"), to deal in the Software without restriction, including 13 | without limitation the rights to use, copy, modify, merge, publish, 14 | distribute, sublicense, and/or sell copies of the Software, and to 15 | permit persons to whom the Software is furnished to do so, subject to 16 | the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be 19 | included in all copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 22 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 23 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 24 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 25 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 26 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 27 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | Note that this license does NOT apply to the HyperHaskell software 30 | in its entirety, only to Electron framework contained within it. 31 | -------------------------------------------------------------------------------- /resources/hyper-haskell.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Hyper Haskell 3 | GenericName=Graphical Haskell Interpreter 4 | Comment=The strongly hyped Haskell interpreter 5 | Icon=hyper-haskell 6 | Type=Application 7 | Categories=Application;Development; 8 | StartupNotify=false 9 | Terminal=false 10 | Exec=hyper-haskell %F 11 | MimeType=application/x.hyper-haskell+json 12 | -------------------------------------------------------------------------------- /resources/icons/icon.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/resources/icons/icon.graffle -------------------------------------------------------------------------------- /resources/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/resources/icons/icon.icns -------------------------------------------------------------------------------- /resources/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/resources/icons/icon.ico -------------------------------------------------------------------------------- /resources/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HeinrichApfelmus/hyper-haskell/7b8775ff1798e33089b62d3904aa49bf8c24cd14/resources/icons/icon.png -------------------------------------------------------------------------------- /resources/icons/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2016-09-25 17:35ZCanvas 1Layer 1 4 | -------------------------------------------------------------------------------- /resources/icons/makeicns.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | CONVERT=sips 3 | echo ${CONVERT} 4 | mkdir MyIcon.iconset 5 | ${CONVERT} -z 16 16 "$1" --out MyIcon.iconset/icon_16x16.png 6 | ${CONVERT} -z 32 32 "$1" --out MyIcon.iconset/icon_16x16@2x.png 7 | ${CONVERT} -z 32 32 "$1" --out MyIcon.iconset/icon_32x32.png 8 | ${CONVERT} -z 64 64 "$1" --out MyIcon.iconset/icon_32x32@2x.png 9 | ${CONVERT} -z 128 128 "$1" --out MyIcon.iconset/icon_128x128.png 10 | ${CONVERT} -z 256 256 "$1" --out MyIcon.iconset/icon_128x128@2x.png 11 | ${CONVERT} -z 256 256 "$1" --out MyIcon.iconset/icon_256x256.png 12 | ${CONVERT} -z 512 512 "$1" --out MyIcon.iconset/icon_256x256@2x.png 13 | ${CONVERT} -z 512 512 "$1" --out MyIcon.iconset/icon_512x512.png 14 | ${CONVERT} -z 1024 1024 "$1" --out MyIcon.iconset/icon_512x512@2x.png 15 | iconutil -c icns MyIcon.iconset 16 | # rm MyIcon.iconset/*.png && rmdir MyIcon.iconset -------------------------------------------------------------------------------- /resources/macOS-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDocumentTypes 6 | 7 | 8 | CFBundleTypeExtensions 9 | 10 | hhs 11 | 12 | 15 | CFBundleTypeName 16 | HyperHaskell worksheet 17 | CFBundleTypeRole 18 | Editor 19 | LSIsAppleDefaultForType 20 | 21 | LSTypeIsPackage 22 | 23 | 24 | 25 | NSHumanReadableCopyright 26 | — The strongly hyped Haskell interpreter — 27 | 28 | 29 | -------------------------------------------------------------------------------- /resources/shared-mime-info.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hyper Haskell Workbook 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /runHyperHaskell.command: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # macOS: Double-click this file to run 3 | DIR=`dirname $0` 4 | cd "$DIR" && just run 5 | -------------------------------------------------------------------------------- /worksheets/Csound.hhs: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.1.0", 3 | "cells": [ 4 | { 5 | "cell_type": "text", 6 | "source": "HyperHaskell will execute expressions that have an IO type.\nTo interrupt a long-running computation, press the keyboard shortcut \"Cmd + .\", or choose \"Evaluation -> Interrupt Evaluation\" from the menu bar.\n" 7 | }, 8 | { 9 | "cell_type": "code", 10 | "source": "let\n bass = pat [3,3,2] bd\n snare = del 2 $ pat [4] sn\n hats = pat' [1,0.5,0.3,0.2] [1] chh\n \n drums = str 0.8 $ sum [bass, hats]\n \n pad = toSam $ atMidi razorLead\nin\n\tvdac $ sum [drums, pad]" 11 | }, 12 | { 13 | "cell_type": "code", 14 | "source": "vdac $ atMidi chalandiPlates" 15 | }, 16 | { 17 | "cell_type": "code", 18 | "source": "let\n cps = constSeq (fmap (220 *) [1, 9/8, 5/4, 3/2]) 4\n\n res = mlp (2500 * (uosc 0.1 + 0.1)) 0.1 $ saw cps\n pulsar = sqrSeq [1, 0.5, 0.25, 0.8, 0.4, 0.2, 0.9, 0.4] 8\nin\n dac $ res * pulsar" 19 | } 20 | ], 21 | "extensions": "", 22 | "importModules": "Csound.Base\nCsound.Patch\nCsound.Catalog.Drum.Tr808\nCsound.Sam", 23 | "loadFiles": "", 24 | "settings": { 25 | "packageTool": "stack", 26 | "packagePath": "../haskell/stack.yaml", 27 | "searchPath": "../worksheets" 28 | } 29 | } -------------------------------------------------------------------------------- /worksheets/Diagrams.hhs: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.1.0", 3 | "cells": [ 4 | { 5 | "cell_type": "text", 6 | "source": "HyperHaskell supports graphical output!\n\nNote that the `Hyper.Extra` and `Diagrams.Prelude` modules have been added to the \"Imports\" section. This brings the functions for drawing and displaying diagrams into scope.\n" 7 | }, 8 | { 9 | "cell_type": "code", 10 | "source": "dia $ circle 1 # fc green # scaleX 2.0" 11 | }, 12 | { 13 | "cell_type": "code", 14 | "source": "dia $ beside (r2 (3,2))\n (circle 1.0 # fc orange)\n (square 1.5 # fc purple # rotate (45 @@ deg))" 15 | }, 16 | { 17 | "cell_type": "code", 18 | "source": "dia $ cat (r2 (2, -1)) $ map (\\n -> regPoly n 1) [3..8]" 19 | } 20 | ], 21 | "extensions": "", 22 | "importModules": "Hyper.Extra\nDiagrams.Prelude", 23 | "loadFiles": "", 24 | "settings": { 25 | "packageTool": "stack", 26 | "packagePath": "../haskell/stack.yaml", 27 | "searchPath": "../worksheets" 28 | } 29 | } -------------------------------------------------------------------------------- /worksheets/Prelude.hhs: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.1.0", 3 | "cells": [ 4 | { 5 | "cell_type": "text", 6 | "source": "In HyperHaskell, you may have to add a type signature in order to avoid type ambiguities.\n" 7 | }, 8 | { 9 | "cell_type": "code", 10 | "source": "2 * 3 * 7 :: Int" 11 | }, 12 | { 13 | "cell_type": "text", 14 | "source": "You can enter arbitrary Haskell expressions.\n" 15 | }, 16 | { 17 | "cell_type": "code", 18 | "source": "let fibs = 1 : 1 : zipWith (+) fibs (tail fibs) in take 20 fibs :: [Int]" 19 | }, 20 | { 21 | "cell_type": "text", 22 | "source": "It is also possible to bind new variables in the IO monad, via a statement.\n" 23 | }, 24 | { 25 | "cell_type": "code", 26 | "source": "test <- return \"Hello\"" 27 | }, 28 | { 29 | "cell_type": "code", 30 | "source": "test" 31 | }, 32 | { 33 | "cell_type": "text", 34 | "source": "Input cells can span multiple lines. Statements are executed in sequence.\n" 35 | }, 36 | { 37 | "cell_type": "code", 38 | "source": "test <- return \"Multiple lines\"\ntest" 39 | }, 40 | { 41 | "cell_type": "text", 42 | "source": "Let statements are supported as well.\n" 43 | }, 44 | { 45 | "cell_type": "code", 46 | "source": "let test2 = \"Hello Test!\"\ntest2" 47 | } 48 | ], 49 | "extensions": "", 50 | "importModules": "Prelude", 51 | "loadFiles": "", 52 | "settings": { 53 | "packageTool": "stack", 54 | "packagePath": "../haskell/stack.yaml", 55 | "searchPath": "../worksheets" 56 | } 57 | } -------------------------------------------------------------------------------- /worksheets/Test.hhs: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.1.0", 3 | "cells": [ 4 | { 5 | "cell_type": "text", 6 | "source": "Welcome to HyperHaskell!\nThis is a worksheet used for testing and debugging the application. But you may still have fun with it, if you like.\n" 7 | }, 8 | { 9 | "cell_type": "code", 10 | "source": "1 + 2 :: Int" 11 | }, 12 | { 13 | "cell_type": "code", 14 | "source": ":type 1 + 2" 15 | }, 16 | { 17 | "cell_type": "code", 18 | "source": "return hello :: IO String" 19 | }, 20 | { 21 | "cell_type": "code", 22 | "source": "hyperCheck $ \\xs -> reverse (reverse xs) == (xs :: [Int])" 23 | } 24 | ], 25 | "extensions": "", 26 | "importModules": "import Main\nimport Hyper.Extra\nimport Test.QuickCheck", 27 | "loadFiles": "Test.hs", 28 | "settings": { 29 | "packageTool": "cabal.project", 30 | "packagePath": "../", 31 | "searchPath": "." 32 | } 33 | } -------------------------------------------------------------------------------- /worksheets/Test.hs: -------------------------------------------------------------------------------- 1 | hello = "Hurray, we can load external source files!" --------------------------------------------------------------------------------