Giter Club home page Giter Club logo

haskell-ide-engine's Introduction

Deprecated

This repository is now an historical artifact. It was the original LSP support for haskell, which is now provided by

https://github.com/haskell/haskell-language-server

Haskell IDE Engine (HIE)

License BSD3 CircleCI Azure Pipeline AppVeyor Open Source Helpers

This project aims to be the universal interface to a growing number of Haskell tools, providing a fully-featured Language Server Protocol server for editors and IDEs that require Haskell-specific functionality.

Features

  • Supports plain GHC projects, cabal projects(sandboxed and non sandboxed) and stack projects

  • Fast due to caching of compile info

  • Uses LSP, so should be easy to integrate with a wide selection of editors

  • Diagnostics via hlint and GHC warnings/errors

    Diagnostics

  • Code actions and quick fixes via apply-refact

    Apply Refact

  • Type information and documentation(via haddock) on hover

    Hover

  • Jump to definition

    Find Def

  • List all top level definitions

    Doc Symbols

  • Highlight references in document

    Doc Highlight

  • Completion

    Completion

  • Formatting via brittany

    Formatting

  • Renaming via HaRe (NOTE: HaRe is temporarily disabled)

    Renaming

  • Add packages to cabal and hpack package files

    Adding package to hpack Adding import & deps

  • Typo quick fixes

    Quick fixes

  • Add missing imports (via hsimport)

    Missing imports

Installation

Installation with Nix

Follow the instructions at https://github.com/Infinisil/all-hies

Installation on Arch Linux

A haskell-ide-engine package is available on the AUR.

Install it using Aura:

# aura -A haskell-ide-engine

To change which GHC versions are supported by HIE, use

# aura -A --hotedit haskell-ide-engine

and modify the value of _enabled_ghc_versions.

Reducing the number of supported GHC versions makes HIE compile faster.

Installation with GHC and HIE as a VS Code Devcontainer

VS Code provides the ability to develop applications inside of a Docker container (called Devcontainers) https://code.visualstudio.com/docs/remote/containers

There is a community Devcontainer setup which installs and configures GHC + HIE 8.6.5 and the necessary VS Code extensions to integrate them into the editor.

https://github.com/hmemcpy/haskell-hie-devcontainer

Installation from source

To install HIE, you need stack version >= 2.1.1.

HIE builds from source code, so there's a couple of extra steps.

Common pre-requirements

  • stack must be in your PATH
  • git must be in your PATH
  • Stack local bin directory must be in your PATH. Get it with stack path --local-bin

Tip: you can quickly check if some command is in your path by running the command. If you receive some meaningful output instead of "command not found"-like message then it means you have the command in PATH.

Linux-specific pre-requirements

On Linux you will need install a couple of extra libraries (for Unicode (ICU) and NCURSES):

Debian 9/Ubuntu 18.04 or earlier:

sudo apt install libicu-dev libtinfo-dev libgmp-dev

Debian 10/Ubuntu 18.10 or later:

sudo apt install libicu-dev libncurses-dev libgmp-dev # also zlib1g-dev if not installed

Fedora:

sudo dnf install libicu-devel ncurses-devel # also zlib-devel if not already installed

Windows-specific pre-requirements

In order to avoid problems with long paths on Windows you can do either one of the following:

  1. Clone the haskell-ide-engine to a short path, for example the root of your logical drive (e.g. to C:\hie). If this doesn't work or you want to use a longer path, try the second option.

  2. If the Local Group Policy Editor is available on your system, go to: Local Computer Policy -> Computer Configuration -> Administrative Templates -> System -> Filesystem set Enable Win32 long paths to Enabled. If you don't have the policy editor you can use regedit by using the following instructions here. You also need to configure git to allow longer paths by using unicode paths. To set this for all your git repositories use git config --system core.longpaths true (you probably need an administrative shell for this) or for just this one repository use git config core.longpaths true.

In addition make sure hie.exe is not running by closing your editor, otherwise in case of an upgrade the executable can not be installed.

Download the source code

git clone https://github.com/haskell/haskell-ide-engine --recurse-submodules
cd haskell-ide-engine

Building

Uses the shake build system for predictable builds.

Note, on first invocation of the build script, a GHC is being installed for execution. The GHC used for the install.hs can be adjusted in shake.yaml by using a different resolver.

Available commands can be seen with:

stack ./install.hs help

Remember, this will take time to download a Stackage-LTS and an appropriate GHC. However, afterwards all commands should work as expected.

Install via cabal

The install-script can be invoked via cabal instead of stack with the command

cabal v2-run ./install.hs --project-file install/shake.project <target>

or using the existing alias script

./cabal-hie-install <target>

Running the script with cabal on windows requires a cabal version greater or equal to 3.0.0.0.

For brevity, only the stack-based commands are presented in the following sections.

Install specific GHC Version

Install hie for the latest available and supported GHC version (and hoogle docs):

stack ./install.hs hie

Install hie for a specific GHC version (and hoogle docs):

stack ./install.hs hie-8.6.5
stack ./install.hs data

The Haskell IDE Engine can also be built with cabal v2-build instead of stack build. This has the advantage that you can decide how the GHC versions have been installed. To see what GHC versions are available, the command cabal-hie-install ghcs can be used. It will list all GHC versions that are on the path and their respective installation directory. If you think, this list is incomplete, you can try to modify the PATH variable, such that the executables can be found. Note, that the targets hie and data depend on the found GHC versions. They install Haskell IDE Engine only for the found GHC versions.

An example output is:

> cabal-hie-install ghcs
******************************************************************
Found the following GHC paths:
ghc-8.4.4: /opt/bin/ghc-8.4.4
ghc-8.6.2: /opt/bin/ghc-8.6.2

******************************************************************

If your desired ghc has been found, you use it to install Haskell IDE Engine.

cabal-hie-install hie-8.4.4
cabal-hie-install data

In general, executing targets with cabal instead of stack have the same behaviour, except they do not install a GHC if it is missing but fail.

Multiple versions of HIE (optional)

If you installed multiple versions of HIE then you will need to use a wrapper script. Wrapper script will analyze your project, find suitable version of HIE and launch it. Enable it by editing VS Code settings like this:

"haskell.useCustomHieWrapper": true,
"haskell.useCustomHieWrapperPath": "hie-wrapper",

Configuration

There are some settings that can be configured via a settings.json file:

{
    "haskell": {
        "hlintOn": Boolean,
        "maxNumberOfProblems": Number
        "diagnosticsDebounceDuration" : Number
        "liquidOn"                    : Bool (default False)
        "completionSnippetsOn"        : Bool (default True)
        "formatOnImportOn"            : Bool (default True)
        "formattingProvider"          : String (default "brittany",
                                                alternate "floskell")
    }
}
  • VS Code: These settings will show up in the settings window
  • LanguageClient-neovim: Create this file in $projectdir/.vim/settings.json or set g:LanguageClient_settingsPath

Project Configuration

For a full explanation of possible configurations, refer to hie-bios/README.

HIE will attempt to automatically detect your project configuration and set up the environment for GHC.

cabal.project stack.yaml *.cabal Project selected
- - Cabal v2
- Stack
Cabal (v2 or v1)
None

However, you can also place a hie.yaml file in the root of the workspace to explicitly describe how to setup the environment. For example, to state that you want to use stack then the configuration file would look like:

cradle:
  stack:
    component: "haskell-ide-engine:lib"

If you use cabal then you probably need to specify which component you want to use.

cradle:
  cabal:
    component: "lib:haskell-ide-engine"

If you have a project with multiple components, you can use a cabal-multi cradle:

cradle:
  cabal:
    - path: "./test/dispatcher/"
      component: "test:dispatcher-test"
    - path: "./test/functional/"
      component: "test:func-test"
    - path: "./test/unit/"
      component: "test:unit-test"
    - path: "./hie-plugin-api/"
      component: "lib:hie-plugin-api"
    - path: "./app/MainHie.hs"
      component: "exe:hie"
    - path: "./app/HieWrapper.hs"
      component: "exe:hie-wrapper"
    - path: "./"
      component: "lib:haskell-ide-engine"

Equivalently, you can use stack:

cradle:
  stack:
    - path: "./test/dispatcher/"
      component: "haskell-ide-engine:test:dispatcher-test"
    - path: "./test/functional/"
      component: "haskell-ide-engine:test:func-test"
    - path: "./test/unit/"
      component: "haskell-ide-engine:test:unit-test"
    - path: "./hie-plugin-api/"
      component: "hie-plugin-api:lib"
    - path: "./app/MainHie.hs"
      component: "haskell-ide-engine:exe:hie"
    - path: "./app/HieWrapper.hs"
      component: "haskell-ide-engine:exe:hie-wrapper"
    - path: "./"
      component: "haskell-ide-engine:lib"

Or you can explicitly state the program which should be used to collect the options by supplying the path to the program. It is interpreted relative to the current working directory if it is not an absolute path.

cradle:
  bios:
    program: ".hie-bios"

The complete configuration is a subset of

cradle:
  cabal:
    component: "optional component name"
  stack:
    component: "optional component name"
  bios:
    program: "program to run"
    dependency-program: "optional program to run"
  direct:
    arguments: ["list","of","ghc","arguments"]
  default:
  none:

dependencies:
  - someDep

There is also support for multiple cradles in a single hie.yaml. An example configuration for Haskell IDE Engine:

cradle:
  multi:
    - path: ./test/dispatcher/
      config:
        cradle:
          cabal:
            component: "test:dispatcher-test"
    - path: ./test/functional/
      config:
        cradle:
          cabal:
            component: "test:func-test"
    - path: ./test/unit/
      config:
        cradle:
          cabal:
            component: "test:unit-test"
    - path: ./hie-plugin-api/
      config:
        cradle:
          cabal:
            component: "lib:hie-plugin-api"
    - path: ./app/MainHie.hs
      config:
        cradle:
          cabal:
            component: "exe:hie"
    - path: ./app/HieWrapper.hs
      config:
        cradle:
          cabal:
            component: "exe:hie-wrapper"
    - path: ./
      config:
        cradle:
          cabal:
            component: "lib:haskell-ide-engine"

Editor Integration

Note to editor integrators: there is now a hie-wrapper executable, which is installed alongside the hie executable. When this is invoked in the project root directory, it attempts to work out the GHC version used in the project, and then launch the matching hie executable.

All of the editor integrations assume that you have already installed HIE (see above) and that stack put the hie binary in your path (usually ~/.local/bin on linux and macOS).

Using HIE with VS Code

Install from the VSCode marketplace, or manually from the repository vscode-hie-server.

Using VS Code with Nix

.config/nixpkgs/config.nix sample:

with import <nixpkgs> {};

let
  hie = (import (fetchFromGitHub {
                   owner="domenkozar";
                   repo="hie-nix";
                   rev="e3113da";
                   sha256="05rkzjvzywsg66iafm84xgjlkf27yfbagrdcb8sc9fd59hrzyiqk";
                 }) {}).hie84;
in
{
  allowUnfree = true;
  packageOverrides = pkgs: rec {

    vscode = pkgs.vscode.overrideDerivation (old: {
      postFixup = ''
        wrapProgram $out/bin/code --prefix PATH : ${lib.makeBinPath [hie]}
      '';
    });

  };
}

Using HIE with Sublime Text

  • Make sure HIE is installed (see above) and that the directory stack put the hie binary in is in your path
    • (usually ~/.local/bin on unix)
  • Install LSP using Package Control
  • From Sublime Text, press Command+Shift+P and search for Preferences: LSP Settings
  • Paste in these settings. Make sure to change the command path to your hie
{
"clients": {
  "haskell-ide-engine": {
    "command": ["hie", "--lsp"],
    "scopes": ["source.haskell"],
    "syntaxes": ["Packages/Haskell/Haskell.sublime-syntax"],
    "languageId": "haskell",
  },
},
}

Now open a Haskell project with Sublime Text. You should have these features available to you:

  1. Errors are underlined in red
  2. LSP: Show Diagnostics will show a list of hints and errors
  3. LSP: Format Document will prettify the file

Using HIE with Vim or Neovim

As above, make sure HIE is installed. Then you can use Coc, LanguageClient-neovim or any other vim Langauge server protocol client. Coc is recommend since it is the only complete LSP implementation for Vim and Neovim and offers snippets and floating documentation out of the box.

Coc

Follow Coc's installation instructions, Then issue :CocConfig and add the following to your Coc config file.

"languageserver": {
  "haskell": {
    "command": "hie-wrapper",
    "args": ["--lsp"],
    "rootPatterns": [
      "*.cabal",
      "stack.yaml",
      "cabal.project",
      "package.yaml"
    ],
    "filetypes": [
      "hs",
      "lhs",
      "haskell"
    ],
    "initializationOptions": {
      "haskell": {
      }
    }
  }
}

LanguageClient-neovim

vim-plug

If you use vim-plug, then you can do this by e.g., including the following line in the Plug section of your init.vim or ~/.vimrc:

Plug 'autozimu/LanguageClient-neovim', {
    \ 'branch': 'next',
    \ 'do': './install.sh'
    \ }

and issuing a :PlugInstall command within Neovim or Vim.

Clone the LanguageClient-neovim repo

As an alternative to using vim-plug shown above, clone LanguageClient-neovim into ~/.vim/pack/XXX/start/, where XXX is just a name for your "plugin suite".

Sample ~/.vimrc
set rtp+=~/.vim/pack/XXX/start/LanguageClient-neovim
let g:LanguageClient_serverCommands = { 'haskell': ['hie-wrapper', '--lsp'] }

You'll probably want to add some mappings for common commands:

nnoremap <F5> :call LanguageClient_contextMenu()<CR>
map <Leader>lk :call LanguageClient#textDocument_hover()<CR>
map <Leader>lg :call LanguageClient#textDocument_definition()<CR>
map <Leader>lr :call LanguageClient#textDocument_rename()<CR>
map <Leader>lf :call LanguageClient#textDocument_formatting()<CR>
map <Leader>lb :call LanguageClient#textDocument_references()<CR>
map <Leader>la :call LanguageClient#textDocument_codeAction()<CR>
map <Leader>ls :call LanguageClient#textDocument_documentSymbol()<CR>

Use Ctrl+xCtrl+o (<C-x><C-o>) to open up the auto-complete menu, or for asynchronous auto-completion, follow the setup instructions on LanguageClient.

If you'd like diagnostics to be highlighted, add a highlight group for ALEError/ALEWarning/ALEInfo, or customize g:LanguageClient_diagnosticsDisplay:

hi link ALEError Error
hi Warning term=underline cterm=underline ctermfg=Yellow gui=undercurl guisp=Gold
hi link ALEWarning Warning
hi link ALEInfo SpellCap

If you're finding that the server isn't starting at the correct project root, it may also be helpful to also specify root markers:

let g:LanguageClient_rootMarkers = ['*.cabal', 'stack.yaml']

Using HIE with Atom

Make sure HIE is installed, then install the two Atom packages atom-ide-ui and ide-haskell-hie,

$ apm install language-haskell atom-ide-ui ide-haskell-hie

Using HIE with Emacs

Install HIE along with the following emacs packages:

lsp-mode lsp-ui lsp-haskell

Make sure to follow the instructions in the README of each of these packages.

Using HIE with Spacemacs

Install HIE, and then add the following to your .spacemacs config,

(defun dotspacemacs/layers ()
  "..."
  (setq-default
   ;; ...
   dotspacemacs-configuration-layers
   '(
     (haskell :variables haskell-completion-backend 'lsp)
     lsp
     )
    ))

Now you should be able to use HIE in Spacemacs.

Using HIE with Oni

Oni (a Neovim GUI) added built-in support for HIE, using stack, in #1918. If you need to change the configuration for HIE, you can overwrite the following settings in your ~/.config/oni/config.tsx file (accessible via the command palette and Configuration: Edit User Config),

export const configuration = {
  "language.haskell.languageServer.command": "stack",
  "language.haskell.languageServer.arguments": ["exec", "--", "hie"],
  "language.haskell.languageServer.rootFiles": [".git"],
  "language.haskell.languageServer.configuration": {},
}

Docs on hover/completion

HIE supports fetching docs from haddock on hover. It will fallback on using a hoogle db(generally located in ~/.hoogle on linux) if no haddock documentation is found.

To generate haddock documentation for stack projects:

$ cd your-project-directory
$ stack haddock --keep-going

To enable documentation generation for cabal projects, add the following to your ~/.cabal/config

documentation: True

To generate a hoogle database that hie can use

$ cd haskell-ide-engine
$ stack --stack-yaml=<stack.yaml you used to build hie> exec hoogle generate

Or you can set the environment variable HIE_HOOGLE_DATABASE to specify a specific database.

Contributing

Please see the note above about the new haskell-language-server project.

This project is not started from scratch:

  1. See why we should supersede previous tools
  2. Check the list of existing tools and functionality
  3. See more other tools and IDEs for inspiration

It's time to join the project!

❤️ Haskell tooling dream is near, we need your help! ❤️

Hacking on haskell-ide-engine

Haskell-ide-engine can be used on its own project. We have supplied preset samples of hie.yaml files for stack and cabal, simply copy the appropriate template to hie.yaml and it shoule work.

  • hie.yaml.cbl for cabal
  • hie.yaml.stack for stack

Documentation

All the documentation is in the docs folder at the root of this project.

Architecture

Have a look at

Troubleshooting

Emacs

Parse errors, file state going out of sync

With the lsp-mode client for Emacs, it seems that the document can very easily get out of sync between, which leads to parse errors being displayed. To fix this, enable full document synchronization with

(setq lsp-document-sync-method 'full)

emacs-direnv loads environment too late

emacs-direnv sometimes loads the environment too late, meaning lsp-mode won't be able to find correct GHC/cabal versions. To fix this, add a direnv update hook after adding the lsp hook for haskell-mode (meaning the direnv hook is executed first, because hooks are LIFO):

(add-hook 'haskell-mode-hook 'lsp)
(add-hook 'haskell-mode-hook 'direnv-update-environment)

DYLD on macOS

If you hit a problem that looks like can't load .so/.DLL for: libiconv.dylib (dlopen(libiconv.dylib, 5): image not found), it means that libraries cannot be found in the library path. We can hint where to look for them and append more paths to DYLD_LIBRARY_PATH.

export DYLD_LIBRARY_PATH="$DYLD_LIBRARY_PATH:/usr/lib:/usr/local/lib"

On practice /usr/local/lib is full of dylibs linked by brew. After you amend DYLD_LIBRARY_PATH, some of the previously compiled application might not work and yell about incorrect linking, for example, dyld: Symbol not found: __cg_jpeg_resync_to_restart. You may need to look up where it comes from and remove clashing links, in this case it were clashing images libs:

$ brew unlink libjpeg
$ brew unlink libtiff
$ brew unlink libpng

Recompile.

macOS: Got error while installing GHC 8.6.1 or 8.6.2 - dyld: Library not loaded: /usr/local/opt/gmp/lib/libgmp.10.dylib

These builds have a dependency on homebrew's gmp library. Install with brew: brew install gmp. Should be fixed in GHC 8.6.3.

macOS: Got error while processing diagnostics: unable to load package integer-gmp-1.0.2.0

Rename the file at ~/.stack/programs/x86_64-osx/ghc-8.4.3/lib/ghc-8.4.3/integer-gmp-1.0.2.0/HSinteger-gmp-1.0.2.0.o to a temporary name. Should be fixed in GHC 8.8.1.

cannot satisfy -package-id <package>

Is <package> base-x?

Make sure that the GHC version of HIE matches the one of the project. After that run

$ cabal configure

and then restart HIE (e.g. by restarting your editor).

Is there a hash (#) after <package>?

Delete any .ghc.environment* files in your project root and try again. (At the time of writing, cabal new-style projects are not supported with ghc-mod)

Otherwise

Try running cabal update.

Liquid Haskell

Liquid Haskell requires an SMT solver on the path. We do not take care of installing one, thus, Liquid Haskell will not run until one is installed. The recommended SMT solver is z3. To run the tests, it is also required to have an SMT solver on the path, otherwise the tests will fail for Liquid Haskell.

Profiling haskell-ide-engine.

If you think haskell-ide-engine is using a lot of memory then the most useful thing you can do is prepare a profile of the memory usage whilst you're using the program.

  1. Add profiling: True to the cabal.project file of haskell-ide-engine
  2. cabal new-build hie
  3. (IMPORTANT) Add profiling: True to the cabal.project file of the project you want to profile.
  4. Make a wrapper script which calls the hie you built in step 2 with the additional options +RTS -hd -l-au
  5. Modify your editor settings to call this wrapper script instead of looking for hie on the path
  6. Try using h-i-e as normal and then process the *.eventlog which will be created using eventlog2html.
  7. Repeat the process again using different profiling options if you like.

Using ghc-events-analyze

haskell-ide-engine contains the necessary tracing functions to work with ghc-events-analyze. Each request which is made will emit an event to the eventlog when it starts and finishes. This way you can see if there are any requests which are taking a long time to complete or are blocking.

  1. Make sure that hie is linked with the -eventlog option. This can be achieved by adding the flag to the ghc-options field in the cabal file.
  2. Run hie as normal but with the addition of +RTS -l. This will produce an eventlog called hie.eventlog.
  3. Run ghc-events-analyze on the hie.eventlog file to produce the rendered SVG. Warning, this might take a while and produce a big SVG file.

The default options for ghc-events-analyze will produce quite a wide chart which is difficult to view. You can try using less buckets in order to make the chart quicker to generate and faster to render.

ghc-events-analyze hie.eventlog -b 100

This support is similar to the logging capabilities built into GHC.

haskell-ide-engine's People

Contributors

alanz avatar ankhers avatar anrock avatar arrowd avatar avi-d-coder avatar bitemyapp avatar cocreature avatar cronokirby avatar danielg avatar davsanchez avatar dpren avatar expipiplus1 avatar fendor avatar gracjan avatar hasufell avatar hogeyama avatar jneira avatar jpmoresmau avatar leifmetcalf avatar lorenzo avatar lukel97 avatar mgsloan avatar mpickering avatar nponeccop avatar rvion avatar samuelpilz avatar tehnix avatar tobiasgwaaler avatar txsmith avatar wz1000 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

haskell-ide-engine's Issues

What changes to GHC error reporting are required

There are a number of trac tickets related to the way GHC reports errors, with a view to improving them.

https://ghc.haskell.org/trac/ghc/ticket/8809
https://ghc.haskell.org/trac/ghc/ticket/10073
https://ghc.haskell.org/trac/ghc/ticket/10179

For tooling support, there should be an option to return errors in some kind of typed structure that can be easily an unambiguously processed by the tool.

This is in general a hard task, with deep tentacles into GHC.

Is there any low hanging fruit we can harvest in time for GHC 8.1?

Use Idris's protocol?

As linked from the things-marginally-related list, Idris has quite a nice protocol. It'd be nice to be able to share editor integration effort with the Idris folks, but I'm not sure how much overhead would be involved in making this happen. The primary editor integration seems to be emacs, but it looks like there are official atom, sublime, and vim integrations as well.

I'm not sure how much code reuse we'd get out of this, though. Even with something closer to Haskell, like Idris, I imagine there'd be some things that would need to be done differently in the editor to support Haskell vs supporting Idris. I can imagine sharing code by doing one of the following, using idris-mode as an example:

A) Have idris-mode also directly support Haskell. Of course, things like syntax highlighting would still be the responsibility of haskell-mode.

B) Have idris-mode indirectly supoprt Haskell by providing the necessary hooks and options to make it work well with Haskell.

C) Factor out the shared common code into a separate package, relied on by both idris-mode and haskell-ide-mode (or whatever it's called). Seems like a maintenance burden.

D) Copy-modify code. Yuck!

If going this route, I think I prefer (B), as (C) and (D) seem undesirable, and (A) seems invasive to idris-mode. Considering that idris is written in Haskell, it's imaginable that the Idris folks would be keen on this idea. We'd certainly want their go ahead, as it could lead to additional complexity in their editor plugins.

How can we enhance API discoverability?

This thought is inspired by Tim Ottinger's recent Dot-Programming your Object Oriented Code and some subsequent Twitter discussion. Tim paints a pretty accurate picture of how IDEs work to great advantage in OO langs.

When you write a program, you will not read the documentation. You don't have time and inclination for that. [...] You type the name of some object and you press the dot. The list that pops up is your guide to the world of the object whose name lives on the left side of the dot you just pressed.

The discoverability is so powerful that when you pick up a new library, you often only need to read the documentation far enough to instantiate one core object, and any IDE can walk you through the rest.

Coming to haskell from OO feels like there's just a soup of functions; no context-sensitivity...
--@doublehelix

There are two big reasons I see that a naive code completion feature doesn't easily fit Haskell:

  1. The workflow doesn't tend to proceed strictly left-to-right, so completion isn't as simple as just inserting at the cursor. In a haskell translation of my previous example, the code z $ y x would be written from right to left if x leads to the discovery of y and then to z.
  2. What the IDE should suggest is a more complicated question. The OO case is trivial - just list the members of the type. With Haskell... I'm not even entirely sure what I'm asking for.

I think anything we can do to create a discovery experience akin to OO code completion would be a huge win.

Querying a graph database instead of using GHC-API?

There are some problems with the GHC-API as mentioned here:
https://github.com/fpco/haskell-ide/wiki/GHC-API

I think that several of these problems could be solved by storing code, type Information, cabal data in a graph database. The idea would be to query this database instead of using GHC-API. GHC-API is just used to fill the db: Neo4j.
The reason for me to come up with a graph database in the first place was to answer the following questions:

  • Which functions are most often applied to a certain type?
  • How do I get from a type A to a type B? E.g. I get from String to Text by Data.Text.pack

In order to achieve this we have to store all repos from github/hackage/stackage in this database.
The are a lot of advantages:

  • A graph instead of a constructor tree in GHC-API allows to find reverse dependencies (not on packages but on functions: Does anybody use this library function or can I deprecate it? With what parameters do others use this function?)
  • A query language like Cypher is more powerful than a traversal of a ghc tree of constructors.
  • A more general access to data sources like code, cabal-files, github

The only disadvantage is that this database would have to be maintained and therefore more likely would be a service instead of being installed locally.
I plan to implement this and have looked into ghc-exactprint and hoogle and made some experiments with GHC-API. Anything else I should look into? Would you like to have this?

Add user visible info to PluginDescriptor

At the moment a PluginDescriptor is defined as

data PluginDescriptor = PluginDescriptor
  { pdCommands        :: ![Command]
  , pdExposedServices :: ![Service]
  , pdUsedServices    :: ![Service]
  }

Add some fields to be used in the IDE UI as follows

data PluginDescriptor = PluginDescriptor
  { pdUIShortName     :: !T.Text
  , pdUIOverview      :: !T.Text
  , pdCommands        :: ![Command]
  , pdExposedServices :: ![Service]
  , pdUsedServices    :: ![Service]
  }

The pdUIShortName should be displayed in menus and the like, rather than the PluginId.
The pdUIOverview can be displayed in any kind of "more information" scenario about the plugin.

Travis build should be deterministic

It seems that TravisCI somehow does not produce deterministic builds. master branch at https://travis-ci.org/haskell/haskell-ide-engine/builds/89591048 compiles, after a useless change in README a pull request does not compile https://travis-ci.org/haskell/haskell-ide-engine/builds/89742925. It ends up with a warning in HaRe:

    src/Language/Haskell/Refact/Utils/Monad.hs:211:10: Warning:
      No explicit implementation for
          ‘GM.gmoAsk’
        In the instance declaration for ‘GM.GmOut (StateT RefactState IO)’

Build system must be reliable and deterministic.

struggling with ghci-ng

I have all the plumbing in place for the ghci-ng plugin, but the tests are failing with something really silly.

See alanz@2fc7448

The essence of it is that the startup routine for ghci-ng calls into

    (argv2, staticFlagWarnings) <- parseStaticFlags argv1'

Where argv1 is "--interactive" and we then get a "Blocked indefinitely on an MVar" in the test.

Help

Protocol definition

Requirements:

  1. Follows generally accepted json practices.
  2. Outside languages are untyped, so is json.
  3. Works in async manner.
  4. Enables easy error handling on Editor's side.

There seems to be a consensus that we would like to use some hints for types in the json itself, we can base a simple idea roughly on inverse of Type-indexed rows.

Lets define a set of possible responses as an open sum with following examples:

{"error": {"ui_message": ..., "code": ...}}
{"type_info": {"type":"IO () -> IO ()"}}
{"diff": {"XYZ.hs" : { diff description}}
{"plugins": {"p1": {?}, "p2": {?}}
... more follow

this translates to Haskell data declarations:

data Error = Error { ui_message :: Text, code :: Int }
instance ValidResponse Error where ...
data TypeInfo = TypeInfo { typem :: Text }
instance ValidResponse TypeInfo where ...
data Diff = Diff { diff :: ??? }
instance ValidResponse Diff where ...
data Plugins = Plugins { plugins :: ??? }
instance ValidResponse Plugins where ...
...etc

Rationale:

  1. On the editors side there is no confusion when using labels: response.type_info.type
  2. On Haskell side label name map one to one with type (on the same level of nesting in json).
  3. Error handling on editors side is pretty easy, just handle one case "error".
  4. When there is a lack of error handling on editors side there is no confusion (right now "contents" may be valid response or error string, only known after inspecting "tag")

HIE should start web server only on demand

If there are multiple hie engines started they all try to listen on the same port.

If the only interface used is stdin/stdout it is not wise to make startup fail just due to inet port being busy.

Decide how haskell-ide project is run

Process needs to be sorted out, for example:

  • ways to communicate within the project
  • what are github issues for and how to use them
  • how github issues are pushed forward in process and when are they closed
  • describe github issue labels and operational meaning of the labels (if used)
  • who, how, where and how often publishes progress report
  • who, how, where and how often publishes release notes
  • what is current project focus
  • standards for code contributions (checklist)
  • pull request review and merging process
  • documentation standards

Create a diff type

Create a general type that can be returned to the IDE containing a diff between to file.

Perhaps base it on http://hackage.haskell.org/package/filediff

This would be used to convert the list of changed files from HaRe into a set of diffs on those files, as well as in any other plugin requiring this feature.

All IDEs can also implement standard processing for this type.

Plugin startup / private data

Some plugins need to do some initial startup, and then store some state.

We need to come up with a way to do this.

This will probably include some kind of life-cycle methods exposed as part of the plugin, together with a way to store the state.

It may be simplest to define the state as being of type Data.Dynamic, as each plugin will know how to convert it for its own internal use.

So we add

startPlugin :: IO Dynamic

and each CommandFunc gets an additional Dynamic parameter passed in.

Representation of arbitrary types for a plugin

My thinking is that we have an "inner" and an "outer" protocol. The inner/logical one is processed by all the plugins, the outer one comes about when a specific inner message is wrapped in a transport for delivery on the wire.

A possible version of the inner protocol is defined in https://github.com/haskell/haskell-ide-engine/blob/plugins-definitions-play/haskell-ide-plugin-api/Haskell/Ide/PluginDescriptor.hs.

A IdeRequest is defined as

data IdeRequest = IdeRequest
  { ideCommand :: CommandName
  , ideSession :: SessionContext
  , ideContext :: CommandContext
  , ideParams  :: Map.Map ParamId ParamVal
  } deriving Show

type ParamId = String
type ParamVal = String

This initially expects any additional parameters required for a command are String values.

The response is defined as

data IdeResponse = IdeResponseOk String -- ^ Command Succeeded
                 | IdeResponseFail String -- ^ Command Failed
                 | IdeResponseError String -- ^ some error in haskell-ide-engine
                                           -- driver. Equivalent to HTTP 500
                                           -- status
                 deriving Show

The String type is a problem.

What is a good way to represent the value for the IdeResponse inner protocol so that it can be serialised/deserialied via an arbitrary transport mechanism, such as JSON, MSGPACK, etc.

Loose comments from editor (emacs) perspective

Loose comments from editor (emacs) perspective:

  1. Buffers (open files) can be not yet saved and therefore differ from what is on disk currently. Generally we want to operate with the visible contents.
  2. Files-while-editing have the tendency to by not-valid Haskell. Or at least no type correct. There were multiple issues raised so that haskell-mode works with somewhat valid Haskell instead of totally not working.
  3. Editors have cursor position that can serve as indicator what to act on.
  4. Editors have marked region that can serve as indicator what to act on.
  5. Smooth interaction requires latency hiding because 200ms is when people start to notice non-smoothness. Editor needs to be ready to handle longer-running queries, actually any query can run 'too long'. This means communication needs to be asynchronous.

Add a signal handler to flush logs on exit

At the moment if HIE is killed for any reason, it discards any logs that have not been flushed.

Add a set of signal handlers to flush the logs on SIGTERM etc.

Possible resource: http://zguide.zeromq.org/hs:interrupt

https://hackage.haskell.org/package/logging-3.0.2/docs/Control-Logging.html
https://hackage.haskell.org/package/fast-logger-2.4.1/docs/System-Log-FastLogger.html

Note: there is nothing holy about this logging package, it was just convenient.

ghci-ng plugin

I am currently working on a ghci-ng plugin.

The work is happening in the following places

The initial intent is to

  • expose ghci-ng as a library
  • modify haskeline to support a Behaviour that writes to/from a Chan instead of a file Handle
  • make a set of commands in a plugin to interact with these Chan s.

First pass will be to just wrap stdio/stdin to ghci as a proof of concept, allowing an embedded ghci session in a supporting IDE

Second pass will be to extend the data type in the Chan for the various commands supported in ghci, so that they can be used as first-class commands in the IDE.

The ghci threading model to support this is unknown right now, but it is a proof of concept.

Feedback welcome.

Initial Release

I am keen to get an initial release out by end Nov, even if it is just of the plugin API so we can start writing plugins as things exposed by a tool, rather than somehing in HIE.

The things that need to be in it should be captured in https://github.com/haskell/haskell-ide-engine/milestones/P1:%20Must

Ideally any kind of IDE integration would be good too, but I know that that is the hardest part initially.

What all should be in there?

Possible configuration mechanism

At the moment HIE has a list of dependencies in its cabal file, for plugins available to it.
Then in the MainHIE.hs it has a variable of type Plugins which enumerates the plugins to be available.

plugins :: Plugins
plugins = Map.fromList
  [
    -- Note: statically including known plugins. In future this map could be set
    -- up via a config file of some kind.
    ("eg2",  example2Descriptor)
  , ("hare", hareDescriptor)
    -- The base plugin, able to answer questions about the IDE Engine environment.
  , ("base", baseDescriptor)
  ]

A possible future configuration process would be to have a separate exe that can read a HIE config file, containing a list of (plugin name,plugin descriptor,originating package). This exe would then generate a cabal file and Main.hs that calls for the packages to be installed, and sets up the plugins variable.

Hence a local configuration can have whatever plugins are required. This can work since we assume we are doing haskell dev, and have a compiler on hand.

thread/concurrency/memory usage safety?

Just want to mention these while things are still in the planning phase.

this may be spurious (or obvious), but making sure that its easy to have nonblocking interaction with editors and their plugins. (i've definitely seen a number of haskell plugins which have bad cpu/memory usage or needless editor blocking from bad integration)

Plugin function definition

At the moment the plugin callback function for a particular command is defined as

type Dispatcher = forall m. (MonadIO m,GHC.GhcMonad m,HasIdeState m) 
                         => IdeRequest -> m IdeResponse

The monad used inside HIE is defined as

newtype IdeM a = IdeM { unIdeM :: GM.GhcModT (GM.GmOutT (StateT IdeState IO)) a}
      deriving ( Functor
               , Applicative
               , Alternative
               , Monad
               , MonadPlus
               , MonadIO
               , GM.GmEnv
               , GM.GmOut
               , GM.MonadIO
               , ExceptionMonad
               )

data IdeState = IdeState
  {
    idePlugins :: Plugins
  } deriving (Show)

There is a specific haskell-ide-plugin-api package for the Dispatcher type, and IdeM is defined
in the main haskell-ide-egine package.

There are a number of options for this.

  1. Leave things as they are. But then the GhcModT is not available to a plugin action.
  2. Make Dispatcher have the signature IdeRequest -> IdeM IdeResponse
    This means the IdeM type has to be in haskell-ide-plugin-api, and the various instances
    required become orphans, or need to be in haskell-ide-plugin-api.
  3. Include GhcModT in the constraints on the Dispatcher

Another consideration is that the haskell-ide-engine looks like it may be a
general IDE backend, usable for other languages e.g. PureScript, Idris, et al.
In this case having anything GHC related is unneccessary. In this case,
perhaps the type should be simply

type Dispatcher = forall m. (MonadIO m,HasIdeState m) 
                         => IdeRequest -> m IdeResponse

make jsonHttpListener only start via a CLI flag

At the moment in MainHie.hs we have

    -- TODO: pass port in as a param from GlobalOpts
    _ <- forkIO (jsonHttpListener cin)

Modify Options.hs to have a flag to enable the listener, and only launch it if the flag is set.

This should be a copy-paste of the option for "--console", just renamed.

As a bonus, consider adding a port parameter too.

Different behavior between launch stack exec hie and hie directly

via stack:

jpmoresmau:~/Documents/haskell-ide-engine$ stack exec hie
{"cmd":"ghcmod:type","params":{"file":{"file":"/home/jpmoresmau/Documents/haskell-ide-engine/src/Haskell/Ide/Engine/Types.hs"},"start_pos":{"pos":[21,9]}}}
^B
{"type_info":"21 5 21 17 \"TChan ChannelResponse\"\n21 5 21 17 \"ChannelRequest -> TChan ChannelResponse\"\n21 5 21 17 \"ChannelRequest\"\n21 5 21 17 \"TChan ChannelResponse\"\n"}

This seems ok.
Via the executable directly:

jpmoresmau:~/Documents/haskell-ide-engine$ .stack-work/dist/x86_64-linux/Cabal-1.22.4.0/build/hie/hie
{"cmd":"ghcmod:type","params":{"file":{"file":"/home/jpmoresmau/Documents/haskell-ide-engine/src/Haskell/Ide/Engine/Types.hs"},"start_pos":{"pos":[21,9]}}}
^B
EXCEPTION: types:
       Could not find module ‘Data.Aeson’
       Perhaps you meant Data.Version (from base-4.8.1.0)
       Use -v to see a list of the files searched for.
       Could not find module ‘Control.Concurrent.STM.TChan’
       Perhaps you meant
         Control.Concurrent.Chan (from base-4.8.1.0)
         Control.Concurrent.MVar (from base-4.8.1.0)
       Use -v to see a list of the files searched for.
       Could not find module ‘Haskell.Ide.Engine.PluginDescriptor’
       Use -v to see a list of the files searched for.
{"type_info":""}

What does stack exec do that calling the executable directly doesn't do? Surely calling the executable directly should also work.

Bring in async processing

<alanz> And then to experiment with some sort of ghci/inferior repl option
<gracjan> guy, I need the async part to get going forward, I mean request-id, response-id
<gracjan> Id like to setup emacs to handle responses in async way only
<gracjan> so this has to be in before I start
<alanz> ok, that is actually one of the things I have been thinking quite a bit about.
<alanz> I know how to do it in the dispatcher, maybe I should make a version. I have been waiting for the other stuff going through the dispatcher to settle a bit first.
* fgaz ([email protected]) has joined #haskell-ide-engine
<alanz> The key is the the reply Chan in the  ChannelRequest.
<gracjan> emacs is fully callback based
<gracjan> so I’ll make it completely async from day one
<alanz> We can let the plugin dispatcher decide whether to do a sync or async call into the plugin, and so long as the reply eventually goes to the Chan, which goes into the transport, we are good.
<gracjan> okay
<alanz> Once the framing in the stdio is settled, it is a simple matter to split it into a pair of listeners.
<alanz> One listens on stdin and sends to dispatcher. The other listens on reply Chan and forwards to stdout.
<alanz> The only think is to decide what the management process around async is at the plugin level.
<alanz> One part of me says every plugin should run in its own thread as a matter of routine, so the dispatcher is never blocked
<gracjan> true
<alanz> but it is coming....
<gracjan> or even every request should work in a fresh thread
<alanz> that is up to the plugin dispatcher.
<alanz> So we repeat the process. a plugin listens on a chan, then either does a sync process or spawns off a new thread, based on any plugin-specific criteria.
<alanz> Either way the response goes back into the Chan that ends up at the transpot
<alanz> It is quite a general way of doing it
<alanz> so it can cater for whatever has to happen.
<alanz> We will probably have to put syncronisation points in somewhere so we do not trigger a whole lot of project rebuilds for example
<alanz> But that is why the service layer is in the plugin descriptor

Response content type negotiation

Based on an IRC discussion with @hamishmack
#61 And #66 are also relevant.

There are two different but related issues

  1. Given that we end up with a specific set of well-known types that can be managed by a given IDE, we need to be able to manage versioning of these, if the format changes.
  2. If we end up with a set of semantic types / widgets that can be meaningfully applied in an IDE, we will end up with uneven implementation of these in the set of supporting IDEs.

Both of these seem to require some kind of content negotiation.

A possible solution would be to include a description of the content types (in the sense of semantic type / widget) together with explicit versions in each request from the IDE for a given command. The supported types for a given command could potentially be returned as part of the CommandDesc.

Web based IDE

Now that stack and stack-ide are here, it should be fairly straightforward to create a web based ide.

I can think of

We can also leverage docker for packaging and deploying components, or even offering users some quick scripts to package apps, etc.

What do you think ?

Make all ToJSON instances clearly visible

Because JSON is used for external interfaces for many of the transports, the JSON generated must be stable. Hence it should not be generated by automated means that may change with compiler or library version.

Replace all ToJSON instances with hand-written ones, which make the encoding explicit.

Decide on matching an existing IDE protocol or designing a new one

We can either use / extend an existing protocol, or have an entirely new one. Lets discuss what will work best.

Pick an existing protocol

If we pick an existing protocol, then ideally, this would be one that has many editor integrations. So, the primary benefit of doing this would be to get lots of code reuse and editor support "for free". Unfortunately, it doesn't seem like there is yet a de-facto standard here. Even if there was, I'm thinking that picking something which is intended for a non-haskell-ey language will be too much impedance mismatch.

There may well be some potential for cross-polination with haskell-like languages, though. Idris's protocol seems like an interesting possibility (see #3).

New protocol

Do a new protocol. To do this I think it'd make sense to take a look at all the existing protocols, and consider the attributes of its design. Here are a few GHC specific protocols to take a look at:

  1. stack-ide, which also use the types from ide-backend-common. The particularly nice thing here is the amount of juicy details yielded by ide-backend-common, particularly in IdInfo. However, it also ends up being up to the editor what to do with all this this info.

Something I think should be done differently than this protocol is to provide less mutation. Instead, the query protocol should just give ways to ask for info about code, based on what's in the filesystem and the CLI arguments. Being able to change GHC flags on the fly is pretty cool, but getting it perfectly correct is tricky.

  1. ghc-server has some rather nice protocol types, but for Chris has been superseded by

  2. ghci-ng, which simply adds a few new ghci commands which yield machine-readable output. This is also the approach taken by Idris's repl based IDE integration.

I've got some more thoughts on what such a protocol ought to look like / provide, but I'll save it for later :)

Initial info plugin based on code from ide-backend

I'm thinking of calling this haskell-ide-plugin-info (these names are going to get unwieldy, maybe abbreviated to hie-plugin-info or hiep-info.

ide-backend has some pretty excellent type information for sub-expressions. So, I'm thinking it would be a good first step to try implementing that, copying / referring to ide-backend's implementation. So, this will look like having a plugin with the following command:

  CommandDescriptor
  { cmdName = "type"
  , cmdUiDescription = "get the type of a sub-expression"
  , cmdContexts = [CtxRegion]
  , cmdAdditionalParams = []
  }

It'll return a string with the type pretty printed.

Provide async synchronisation methods

We now have an ability to run an async plugin, through

data CommandFunc resp = CmdSync (SyncCommandFunc resp)
                      | CmdAsync (AsyncCommandFunc resp)
                        -- ^ Note: does not forkIO, the command must decide when
                        -- to do this.

type SyncCommandFunc resp = forall m. (MonadIO m,GHC.GhcMonad m,HasIdeState m)
                => [AcceptedContext] -> IdeRequest -> m (IdeResponse resp)

type AsyncCommandFunc resp = forall m. (MonadIO m,GHC.GhcMonad m,HasIdeState m)
                => (IdeResponse resp -> IO ()) -> [AcceptedContext] -> IdeRequest -> m ()

So an async command function gets given a function to call in IO to return the IdeResponse, but is otherwise unconstrained.

The intention is that there can either be a single command function which can server as a dispatcher to other async processes, or the function can fork off a separate process to handle the request.

In either case we potentially have multiple processes running, all wanting to make use of information from the underlying project.

At the moment a PluginDescriptor is defined as

data PluginDescriptor = PluginDescriptor
  { pdCommands        :: [Command]
  , pdExposedServices :: [Service]
  , pdUsedServices    :: [Service]
  }

The Service type is not really defined an unused at this point.

The intention is that a Service can be used as a synchronisation mechanism, and can expose access functions that are specifically multiprocess aware.

So there would be a base service providing access to the underlying project, which would make sure that e.g. more than one command does not trigger ghc-mod to reconfigure the project.

The question is, how should a Service be defined in concrete terms.

Consume invalid input

pipes-parse doesn't consume the input if it doesn't parse which results in us repeatedly trying to parse the same input which results in an perpetual flood of error messages.

My pipes foo is too weak to figure out how to fix this right now.

Define Emacs menu

Initial work on Emacs menu:

emacs-menu

This issue tracks the progress and will be closed by merging this work to master.

Use an error handler in the dispatcher

If there is an error in any of the plugin commands, catch the error and return it wrapped in IdeResponseError

This should be done in the main dispatcher loop:

doDispatch :: PluginId -> PluginDescriptor -> Dispatcher
doDispatch pn desc req = do
  plugins <- getPlugins
  debugm $ "doDispatch:desc=" ++ show desc
  debugm $ "doDispatch:req=" ++ show req
  case Map.lookup (pn,ideCommand req) (pluginCache plugins) of
    Nothing -> return (IdeResponseError (toJSON $ "No such command:" <> ideCommand req))
    Just cmd -> (cmdFunc cmd) req

The call to (cmdFunc cmd) req should be wrapped in an error handler.

Encourage stateless programming style

Each command should be as self-contained as possible, It should preferably not depend on hidden state at all. Visible state, like contents of visible files, is less of a problem.

Encourage stateless programming in what is supposed to be async system.

Encourage using full absolute paths to files because current working directory is state.

Name of project module hierarchy root question

Where should the top of the project set

Language.Haskell.Support
Language.Haskell.Daemon
Language.Haskell.IDE.Daemon
Tools.Haskell.Daemon
Tools.Haskell.Tools

Let the bikeshedding begin

Commands should define what they return

Following a discussion on IRC between @alanz and @gracjan, we think commands should be explicit in the type of response they return, and we should offer some concrete types out of the box, so that plugin authors can reuse the types for responses, which in turn means that editors using the API can have a good chance at supporting a wide range of plugin commands.

The idea is to define a type class that contains a function to serialize to something generic like JSON.

I have started some code and it looks promising (the fact that command definition carry the type they return). I obviously need a bit more time.

Check PluginDescriptor on loading

When HIE starts up, it is presented with a list of Plugins.

Each of these defines commands in a CommandDescriptor

For each CommandDescriptor, check that the parameter names introduced via cmdContexts and cmdAdditionalParameters are unique.

If this is not the case, exit with an error.

Rework the Console

At the moment the Console is incredibly basic.

It should be reworked to provide the following basics

  • History. Pressing up/down should scroll through prior history, allow editing
  • Better command parsing
    At the moment the command parsing is as simple as possible, for the POC. This should be extended in some way, perhaps by generating parsers from plugin command descriptors UiCommand
  • Better representation of output
  • Built in commands to browse the plugins, and get help on them. This should just be wrappers around the functions exposed via the base plugin
  • Anything else

Provide REST API explorer

The fledgeling HIE has a JSON API. This includes some introspection as to what plugins are available, and what commands they provide, together with their parameters.

Provide an application that can interact with the API, and present the results in a meaningful way.

i.e.

$ stack build
$ stack exec hie -- --repl -d

Then access http://localhost:8001/ as in

curl -v http://localhost:8081/req/base -X POST -H Content-Type:application/json --data-binary '{"ideParams":{},"ideCommand":"version","ideContext":{"ctxEndPos":null,"ctxCabal":null,"ctxStartPos":null,"ctxFile":null}}'

Possibly consider using http://swagger.io, especially as there is now servant-swagger

Or any other appropriate technology. It needs to be able to run locally though.

cc @kritzcreek

What would the smallest useful haskell-ide look like

haskell-ide has a potentially very large scope.

A useful approach to managing this is to identify some useful, uncontroversial, small functionality that can test our architectural choices.

I think there are three broad categories of things that can be provided via haskell-ide

  • A place where a tool writer can host a tool as a plugin, and make use of standardised services to access the raw project and communication with the IDE, thus minimising this effort for all to get the tool integrated into multiple environments.

  • A way to query information about a project, and present it to the user. This in turn is a large topic, and can cover anything from contents of available libraries, to type information of loaded modules, to information to support syntax highlighting.

    This may include on the fly error checking

  • Providing access to a REPL

So I suggest we come up with something from one of these categories, and aim to let it communicate with at least 2 different IDEs via the process identified in #2 / #3

What should these be?

Decide on package name for the plugin API / plugin packages

At the moment the plugin API package is called haskell-ide-plugin-api.

Before this goes on to hackage, we must agree on an appropriate package name for it, and a naming scheme for packages in general.

Some ideas to kick off the discussion

  • haskell-ide-plugin-api (i.e. stays as is)
  • haskell-ide-engine-api
  • hie-plugin-api
  • others?

In a related fashion, a given plugin can be called

  • haskell-ide-plugin-hare
  • hie-hare
  • hiep-hare
  • hie-plugin-hare

Note: I fully expect future tools to provide the PluginDescriptor as part of the normal package, so HaRe would simply export

hareDescriptor :: PluginDescriptor

Do plugins the same way as GHC

I'm thinking that a good way to implement plugins is by emulating ghc's plugin system, perhaps even calling functions from it. Based on a quick look at the DynamicLoading module, it seems like it'd be a good approach to just copy+modify the loadPlugin function.

It makes sense to have something similar to the Plugin datatype, and have a field per "callback". Perhaps this is getting into details too early, but maybe these callbacks should be wrapped in Maybes, like the aptly named Hooks datatype. It's informative to know which things a particular plugin hooks into.

Worker function parameter passing

A CommandFunc is defined as

type CommandFunc = forall m. (MonadIO m,GHC.GhcMonad m,HasIdeState m)
                => [AcceptedContext] -> IdeRequest -> m IdeResponse

It will only be called by the dispatcher if at least one context matches, and and the required parameters are present and of the right type.

At the moment the command processing function is very messy

checkCmd :: CommandFunc
checkCmd _ctxs req = do
  case getParams ["file"] req of
    Left err -> return err
    Right [ParamFile fileName] -> do
      liftIO $ runGhcModCommand (GM.checkSyntax [T.unpack fileName])
    Right x -> error $ "GhcModPlugin.checkCmd: got unexpected file param:" ++ show x

In this case there is only one valid context, so it it is called at all the "file" parameter is guaranteed to be valid. The getParams call returns a list of ParamValue in the order of the param names given to it, or a error result if they are missing. This call is actually redundant now.

It may be simpler to define the following types

data ActualRequiredParams =
    AR0
  | AR1 ParamVal
  | AR2 ParamVal ParamVal
 ...
data ActualOptionalParams =
    AO0
  | AO1 (Maybe ParamVal)
  | AO2 (Maybe ParamVal) (Maybe ParamVal)
 ...

as well as something similar for the matching contexts.

The CommandFunc would then be defined as

type CommandFunc = forall m. (MonadIO m,GHC.GhcMonad m,HasIdeState m)
                => [ActualContextContext] -> ActualRequiredParams -> ActualOptionalParams -> m IdeResponse

This will still leave is with "missing case" warning messages though.

Is there some clever way to do this with types?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.