Merge remote-tracking branch 'origin/master' into hie-bios

This commit is contained in:
Matthew Pickering 2019-05-29 13:30:28 +01:00
commit 0f507e607f
77 changed files with 2275 additions and 670 deletions

View File

@ -40,11 +40,14 @@ defaults: &defaults
- run:
name: Build (we need the exe for tests)
command: stack -j 2 --stack-yaml=${STACK_FILE} install
command: stack -j 1 --stack-yaml=${STACK_FILE} install
# need j1, else ghc-lib-parser triggers OOM
no_output_timeout: 30m
- run:
name: Build Testsuite without running it
command: stack -j 2 --stack-yaml=${STACK_FILE} build --test --no-run-tests
no_output_timeout: 30m
- store_artifacts:
path: ~/.local/bin
@ -70,9 +73,7 @@ defaults: &defaults
- ~/build/submodules/brittany/.stack-work
- ~/build/submodules/ghc-mod/core/.stack-work
- ~/build/submodules/ghc-mod/.stack-work
- ~/build/submodules/haskell-lsp/.stack-work
- ~/build/submodules/cabal-helper/.stack-work
- ~/build/submodules/floskell/.stack-work
- run:
name: Test
@ -146,6 +147,11 @@ jobs:
- STACK_FILE: "stack-8.6.4.yaml"
<<: *defaults
ghc-8.6.5:
environment:
- STACK_FILE: "stack-8.6.5.yaml"
<<: *defaults
ghc-nightly:
environment:
- STACK_FILE: "stack.yaml"
@ -154,8 +160,7 @@ jobs:
cabal:
working_directory: ~/build
docker:
# - image: quay.io/haskell_works/ghc-8.4.3
- image: quay.io/haskell_works/ghc-8.6.1
- image: quay.io/haskell_works/ghc-8.6.5
steps:
- checkout
- run:
@ -178,7 +183,8 @@ jobs:
command: cabal new-configure --enable-tests
- run:
name: Build
command: cabal new-build -j2
command: cabal new-build -j1 # need j1, else ghc-lib-parser triggers OOM
no_output_timeout: 30m
- save_cache:
key: cabal-{{ .Environment.HIE_CACHE }}-{{ arch }}-{{ .Environment.CIRCLE_JOB }}
paths:
@ -196,7 +202,8 @@ workflows:
- ghc-8.4.4
- ghc-8.6.1
- ghc-8.6.2
- ghc-8.6.3
# - ghc-8.6.3
- ghc-8.6.4
- ghc-8.6.5
- ghc-nightly
- cabal

16
.gitmodules vendored
View File

@ -28,17 +28,5 @@
[submodule "submodules/ghc-mod"]
path = submodules/ghc-mod
# url = https://github.com/arbor/ghc-mod.git
# url = https://github.com/bubba/ghc-mod.git
url = https://github.com/mpickering/ghc-mod.git
[submodule "submodules/floskell"]
path = submodules/floskell
url = https://github.com/ennocramer/floskell
# url = https://github.com/alanz/floskell
[submodule "haskell-lsp"]
path = haskell-lsp
url = https://github.com/mpickering/haskell-lsp.git
[submodule "lsp-test"]
path = lsp-test
url = https://github.com/mpickering/lsp-test
url = https://github.com/alanz/ghc-mod.git
#url = https://github.com/mpickering/ghc-mod.git

View File

@ -1,3 +1,45 @@
# 0.9.0.0
- GHC 8.6.5 preliminary support added via the nightly build (@alanz)
- Resolver bumped, LTS 13.19 for GHC 8.6.4 (@alanz)
- Add `diagnosticsOnChange` config parameter, default `True`
(preserving prior hie behaviour). Setting it `False` only generates
diagnostics on file save. ([#1164](https://github.com/haskell/haskell-ide-engine/pull/1164), @mpickering/@lorenzo)
- The `Hsimport` plugin now formats the resulting change using the
formatter configured for hie. ([#1167](https://github.com/haskell/haskell-ide-engine/pull/1167),@fendor)
- Actually enable type definition requests, if supported by the client
(e.g. vscode). ([#1169](https://github.com/haskell/haskell-ide-engine/pull/1169)/@fendor, [#1172](https://github.com/haskell/haskell-ide-engine/pull/1172)/@bubba)
- Use LSP MarkupContent for generated documentation ([#1181](https://github.com/haskell/haskell-ide-engine/pull/1181), @alanz)
- remove installation of Cabal by cabal ([#1184](https://github.com/haskell/haskell-ide-engine/pull/1184), @power-fungus)
- Add EmptyDataDecls to available pragmas, for generating code actions
to insert if needed. ([#1187](https://github.com/haskell/haskell-ide-engine/pull/1187),@fendor)
- Make sure the end of formatted text is properly indicated for marked
up documentation ([#1189](https://github.com/haskell/haskell-ide-engine/pull/1189), @alanz)
- Fix some of the tests with cabal new-build ([#1194](https://github.com/haskell/haskell-ide-engine/pull/1194), @michaelpj)
- Update build-tool-depends for func-test ([#1198](https://github.com/haskell/haskell-ide-engine/pull/1198), @bubba)
- Fix version of lsp-test so `cabal new-build` works ([#1211](https://github.com/haskell/haskell-ide-engine/pull/1211), @power-fungus)
- Bump hlint to 2.1.17 ([#1213](https://github.com/haskell/haskell-ide-engine/pull/1213), @alanz)
- Use cabal helper that searches with exe extension on windows ([#1217](https://github.com/haskell/haskell-ide-engine/pull/1217), @alanz)
- Stability improvements
- Avoid crash in case of nonsensical hoogle db ([#1174](https://github.com/haskell/haskell-ide-engine/pull/1174), @fendor)
- Prevent hie crash if apply-refact crashes ([#1220](https://github.com/haskell/haskell-ide-engine/pull/1220), @Hogeyama)
- Documentation improvements
- Improve code documentation about formatters ([#1165](https://github.com/haskell/haskell-ide-engine/pull/1165),@fendor)
- Add code documentation for the Hoogle plugin ([#1173](https://github.com/haskell/haskell-ide-engine/pull/1173),@fendor)
- Change 'build-docs' to 'build-doc' in README ([#1185](https://github.com/haskell/haskell-ide-engine/pull/1185), @ajeetdsouza)
- README Nix - replace old.postFixup -> postFixup ([#1193](https://github.com/haskell/haskell-ide-engine/pull/1193), @backuitist)
- Expand documentation on the build system ([#1200](https://github.com/haskell/haskell-ide-engine/pull/1200), @power-fungus)
- Fixed a typo. ([#1212](https://github.com/haskell/haskell-ide-engine/pull/1212), @rashadg1030)
- Add documentation about building hie with profiling
enabled. ([#1225](https://github.com/haskell/haskell-ide-engine/pull/1225), @skress)
- Add Documentation for Pragmas Plugin ([#1222](https://github.com/haskell/haskell-ide-engine/pull/1222), @fendor)
- Build system improvements
- Further improvements and simplification of the `./install.hs`
build system ([#1168](https://github.com/haskell/haskell-ide-engine/pull/1168), @power-fungus)
# 0.8.0.0
- GHC 8.6.4 support added.

View File

@ -52,6 +52,9 @@ we talk to clients.__
- [Documentation](#documentation)
- [Architecture](#architecture)
- [Troubleshooting](#troubleshooting)
- [Emacs](#emacs)
- [Parse errors, file state going out of sync](#parse-errors-file-state-going-out-of-sync)
- [`emacs-direnv` loads environment too late](#emacs-direnv-loads-environment-too-late)
- [DYLD on macOS](#dyld-on-macos)
- [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](#macos-got-error-while-installing-ghc-861-or-862---dyld-library-not-loaded-usrlocaloptgmpliblibgmp10dylib)
- [macOS: Got error while processing diagnostics: unable to load package `integer-gmp-1.0.2.0`](#macos-got-error-while-processing-diagnostics-unable-to-load-package-integer-gmp-1020)
@ -59,6 +62,7 @@ we talk to clients.__
- [Is \<package\> base-x?](#is-package-base-x)
- [Is there a hash (#) after \<package\>?](#is-there-a-hash--after-package)
- [Otherwise](#otherwise)
- [Nix: cabal-helper, No such file or directory](#nix-cabal-helper-no-such-file-or-directory)
## Features
@ -119,7 +123,7 @@ we talk to clients.__
### Installation with Nix
Follow the instructions at https://github.com/domenkozar/hie-nix
Follow the instructions at https://github.com/Infinisil/all-hies
### Installation on ArchLinux
@ -311,7 +315,7 @@ in
packageOverrides = pkgs: rec {
vscode = pkgs.vscode.overrideDerivation (old: {
postFixup = old.postFixup + ''
postFixup = ''
wrapProgram $out/bin/code --prefix PATH : ${lib.makeBinPath [hie]}
'';
});
@ -558,6 +562,22 @@ 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
```elisp
(setq lsp-document-sync-method 'full)
```
#### [`emacs-direnv`](https://github.com/wbolster/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):
```elisp
(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`.
@ -597,3 +617,16 @@ Delete any `.ghc.environment*` files in your project root and try again. (At the
#### Otherwise
Try running `cabal update`.
### Nix: cabal-helper, No such file or directory
An error on stderr like
```
cabal-helper-wrapper: /home/<...>/.cache/cabal-helper/cabal-helper<...>: createProcess: runInteractiveProcess:
exec: does not exist (No such file or directory)
```
can happen because cabal-helper compiles and runs above executable at runtime without using nix-build, which means a Nix garbage collection can delete the paths it depends on. Delete ~/.cache/cabal-helper and restart HIE to fix this.

View File

@ -2,16 +2,13 @@ packages:
./
./hie-plugin-api/
./hie-bios/
./haskell-lsp/
./lsp-test
./submodules/HaRe
./submodules/brittany
./submodules/cabal-helper/
./submodules/floskell
./submodules/ghc-mod/
./submodules/ghc-mod/core/
./submodules/floskell
./submodules/ghc-mod/ghc-project-types
allow-newer: floskell:all
@ -19,3 +16,4 @@ executable-dynamic: True
ghc-options: -Werror

View File

@ -10,41 +10,100 @@ The design of the build system has the following main goals:
* works identically on every platform
* has minimal run-time dependencies:
- a working installation of `stack`
- `stack`
- `git`
* is completely functional right after simple `git clone`
* one-stop-shop for building all executables required for using `hie` in IDEs.
* is completely functional right after a simple `git clone` and after every `git pull`
* one-stop-shop for building and naming all executables required for using `hie` in IDEs.
* prevents certain build failures by either identifying a failed precondition (such as wrong `stack` version) or by performing the necessary steps so users can't forget them (such as invoking `git` to update submodules)
* is able to modify the environment such that `hie` can be run
- setup `hoogle` database
- setup `hlint` data-files
See the project's `README` for detailed information about installing `hie`.
### Targets
The build script `install.hs` defines several targets using the `shake` build system. The targets are roughly:
* `hie-*`: builds and installs the `hie` binaries. Also renames the binaries to contain the correct version-number.
* `build`: builds and installs `hie` binaries for all supported `ghc` versions.
* `build-doc`: builds the hoogle-db required by `hie`
* `cabal-*`: execute the same task as the original target, but with `cabal` instead of `stack`
Each `stack-*.yaml` contains references to packages in the submodules. Calling `stack` with one of those causes the build to fail if the submodules have not been initialized already. The file `shake.yaml` solves this issue invoking the `git` binary itself to update the submodules. Moreover, it specifies the correct version of `shake` and is used for installing all run-time dependencies such as `cabal` and `hoogle` if necessary.
### Run-time dependencies
`hie` depends on a correct environment in order to function properly:
* `cabal-install`: If no `cabal` executable can be found or has an outdated version, `cabal-install` is installed via `stack`.
* The `hoogle` database: `hoogle generate` needs to be called with the most-recent `hoogle` version.
### Steps to build `hie`
Installing `hie` is a multi-step process:
1. `git submodule sync && git submodule update --init`
2. `hoogle generate` (`hoogle>=5.0.17` to be safe)
3. ensure that `cabal-install` is installed in the correct version
4. `stack --stack-yaml=stack-<X>.yaml install` or `cabal new-install -w ghc-<X>`
5. rename `hie` binary to `hie-<X>` in `$HOME/.local/bin`, where `<X>` is the GHC version used
6. repeat step 4 and 5 for all desired GHC versions
This ensures that a complete install is always possible after each `git pull` or a `git clone`.
#### Building `hie` with profiling support
To build `hie` with profiling enabled `cabal new-install` needs to be used instead of `stack`.
Configure `cabal` to enable profiling by setting `profiling: True` in `cabal.project.local` for all packages. If that file does not already exist, create it as follows:
```bash
cat << EOF > cabal.project.local
package *
profiling: True
EOF
```
Then `hie` can be compiled for a specific GHC version:
```bash
export GHCP=<path-to-ghc-binary>
cabal new-install exe:hie -w $GHCP \
--write-ghc-environment-files=never --symlink-bindir=$HOME/.local/bin \
--overwrite-policy=always --reinstall
```
The final step is to configure the `hie` client to use a custom `hie-wrapper` script that enables the runtime options for profiling. Such a script could look like this:
```bash
#!/bin/sh
~/.local/bin/hie-wrapper "$@" +RTS -xc
```
(Note: If no profiling information is shown when using `hie` with a certain project, it may help to build that project itself with profiling support, e.g. `stack build --profile`.)
### Safety checks
The `install.hs` script performs some checks to ensure that a correct installation is possible and provide meaningful error messages for known issues.
* `stack` needs to be up-to-date. Version `1.9.3` is required
* `ghc-8.6.3` is broken on windows. Trying to install `hie-8.6.3` on windows is not possible.
* `cabal new-build` does not work on windows at the moment. All `cabal-*` targets exit with an error message about that.
* When the build fails, an error message, that suggests to remove `.stack-work` directory, is displayed.
### Tradeoffs
#### `shake.yaml`
A `shake.yaml` is required for executing the `install.hs` file.
* It contains the required version of `shake`.
* In contrast to the other `*.yaml` it does not contain the submodules, which is necessary for `stack` to work even before the submodules have been initialized.
It is necessary to update the `resolver` field of the `shake.yaml` if the script should run with a different `GHC`.
#### `install.hs` installs a GHC
Before the code in `install.hs` can be executed, `stack` installs a `GHC`, depending on the `resolver` field in `shake.yaml`. This is necessary if `install.hs` should be completely functional right after a fresh `git clone` without further configuration.
This may lead to an extra `GHC` to be installed by `stack` if not all versions of `haskell-ide-engine` are installed.
#### `stack` is a build dependency
Currently, it is not possible to build all `hie-*` executables automatically without `stack`, since the `install.hs` script is executed by `stack`.
Other parts of the script also depend on `stack`:
We are open to suggestions of other build systems that honor the requirements above, but are executable without `stack`.
* finding the local install-dir `stack path --local-bin`
* finding and installing different `ghc` versions
#### `install.hs` installs a GHC before running
#### `install.hs` executes `cabal install Cabal`
Before the code in `install.hs` can be executed, `stack` installs a `GHC`, depending on the `resolver` field in `shake.yaml`. This is necessary if `install.hs` should be completely functional right after a fresh `git clone` without further configuration.
`ghc-mod` installs `cabal-helper` at runtime depending on the `ghc` used by the project, which can take a long time upon startup of `hie`. The `install.hs` script speeds up this process by calling `cabal install Cabal` upon build.
Hopefully, this behaviour can be removed in the future.
This may lead to an extra `GHC` to be installed by `stack` if not all versions of `haskell-ide-engine` are installed.

View File

@ -62,27 +62,29 @@ library
, data-default
, directory
, filepath
, floskell
, floskell == 0.10.*
, fold-debounce
, ghc >= 8.0.1
, ghc-exactprint
, gitrev >= 1.1
, haddock-api
, haddock-library
, haskell-lsp == 0.10.*
, haskell-lsp-types == 0.10.*
, haskell-lsp == 0.13.*
, haskell-lsp-types == 0.13.*
, haskell-src-exts
, hie-plugin-api
, hlint >= 2.0.11
, hlint (>= 2.0.11 && < 2.1.18) || >= 2.1.22
, hoogle >= 5.0.13
, hsimport
, hslogger
, lifted-async
, lens >= 4.15.2
, monoid-subclasses > 0.4
, mtl
, optparse-simple >= 0.0.3
, parsec
, process
, rope-utf16-splay >= 0.3.1.0
, safe
, sorted-list >= 0.2.1.0
, stm
@ -92,7 +94,6 @@ library
, unordered-containers
, vector
, yaml >= 0.8.31
, yi-rope
, hie-bios
, bytestring-trie
, unliftio
@ -174,11 +175,14 @@ test-suite unit-test
DiffSpec
ExtensibleStateSpec
GhcModPluginSpec
LiquidSpec
HaRePluginSpec
HooglePluginSpec
JsonSpec
LiquidSpec
PackagePluginSpec
Spec
-- Technically cabal-helper should be a 'run-tool-depends', but that doesn't exist yet
build-tool-depends: cabal-helper:cabal-helper-main, hspec-discover:hspec-discover
build-depends: QuickCheck
, aeson
, base
@ -201,7 +205,6 @@ test-suite unit-test
if flag(pedantic)
ghc-options: -Werror
default-language: Haskell2010
build-tool-depends: hspec-discover:hspec-discover
test-suite dispatcher-test
type: exitcode-stdio-1.0
@ -270,27 +273,29 @@ test-suite func-test
, SymbolsSpec
, TypeDefinitionSpec
, Utils
-- This cannot currently be handled by hie (cabal-helper)
-- build-tool-depends: haskell-ide-engine:hie
build-depends: aeson
, base
, data-default
, directory
, filepath
, lsp-test >= 0.5.1.3
, lsp-test >= 0.5.2
, haskell-ide-engine
, haskell-lsp-types == 0.10.*
, haskell-lsp-types == 0.13.*
, haskell-lsp == 0.13.*
, hie-test-utils
, hie-plugin-api
, hspec
, lens
, text
, unordered-containers
, containers
ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -Wredundant-constraints
if flag(pedantic)
ghc-options: -Werror
default-language: Haskell2010
build-tool-depends: hspec-discover:hspec-discover
, haskell-ide-engine:hie
, cabal-helper:cabal-helper-main
test-suite wrapper-test
type: exitcode-stdio-1.0

@ -1 +0,0 @@
Subproject commit 807482a037bcf936c4df249403fdd3a8f44712d9

View File

@ -1,4 +1,4 @@
module Haskell.Ide.Engine.ArtifactMap where
module Haskell.Ide.Engine.ArtifactMap where
import Data.Maybe
import qualified Data.IntervalMap.FingerTree as IM

View File

@ -1,6 +1,14 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ViewPatterns #-}
module Haskell.Ide.Engine.Compat where
import qualified GHC
import qualified Type
import qualified TcHsSyn
import qualified TysWiredIn
import qualified Var
#if MIN_VERSION_filepath(1,4,2)
#else
import Data.List
@ -27,3 +35,108 @@ isExtensionOf :: String -> FilePath -> Bool
isExtensionOf ext@('.':_) = isSuffixOf ext . takeExtensions
isExtensionOf ext = isSuffixOf ('.':ext) . takeExtensions
#endif
#if MIN_VERSION_ghc(8, 4, 0)
type GhcTc = GHC.GhcTc
#else
type GhcTc = GHC.Id
#endif
pattern HsOverLitType :: Type.Type -> GHC.HsExpr GhcTc
pattern HsOverLitType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.HsOverLit _ (GHC.overLitType -> t)
#elif MIN_VERSION_ghc(8, 4, 0)
GHC.HsOverLit (GHC.overLitType -> t)
#else
GHC.HsOverLit (GHC.overLitType -> t)
#endif
pattern HsLitType :: Type.Type -> GHC.HsExpr GhcTc
pattern HsLitType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.HsLit _ (TcHsSyn.hsLitType -> t)
#elif MIN_VERSION_ghc(8, 4, 0)
GHC.HsLit (TcHsSyn.hsLitType -> t)
#else
GHC.HsLit (TcHsSyn.hsLitType -> t)
#endif
pattern HsLamType :: Type.Type -> GHC.HsExpr GhcTc
pattern HsLamType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.HsLam _ ((\(GHC.MG { GHC.mg_ext = groupTy }) -> matchGroupType groupTy) -> t)
#elif MIN_VERSION_ghc(8, 4, 0)
GHC.HsLam (\GHC.MG { GHC.mg_res_ty = res, GHC.mg_arg_tys = args } -> Type.mkFunTys args res -> t)
#else
GHC.HsLam (\GHC.MG { GHC.mg_res_ty = res, GHC.mg_arg_tys = args } -> Type.mkFunTys args res -> t)
#endif
pattern HsLamCaseType :: Type.Type -> GHC.HsExpr GhcTc
pattern HsLamCaseType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.HsLamCase _ ((\(GHC.MG { GHC.mg_ext = groupTy }) -> matchGroupType groupTy) -> t)
#elif MIN_VERSION_ghc(8, 4, 0)
GHC.HsLamCase (\GHC.MG { GHC.mg_res_ty = res, GHC.mg_arg_tys = args } -> Type.mkFunTys args res -> t)
#else
GHC.HsLamCase (\GHC.MG { GHC.mg_res_ty = res, GHC.mg_arg_tys = args } -> Type.mkFunTys args res -> t)
#endif
pattern HsCaseType :: Type.Type -> GHC.HsExpr GhcTc
pattern HsCaseType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.HsCase _ _ ((\(GHC.MG { GHC.mg_ext = groupTy }) -> matchGroupType groupTy) -> t)
#elif MIN_VERSION_ghc(8, 4, 0)
GHC.HsCase _ (\GHC.MG { GHC.mg_res_ty = res, GHC.mg_arg_tys = args } -> Type.mkFunTys args res -> t)
#else
GHC.HsCase _ (\GHC.MG { GHC.mg_res_ty = res, GHC.mg_arg_tys = args } -> Type.mkFunTys args res -> t)
#endif
pattern ExplicitListType :: Type.Type -> GHC.HsExpr GhcTc
pattern ExplicitListType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.ExplicitList (TysWiredIn.mkListTy -> t) _ _
#elif MIN_VERSION_ghc(8, 4, 0)
GHC.ExplicitList (TysWiredIn.mkListTy -> t) _ _
#else
GHC.ExplicitList (TysWiredIn.mkListTy -> t) _ _
#endif
pattern ExplicitSumType :: Type.Type -> GHC.HsExpr GhcTc
pattern ExplicitSumType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.ExplicitSum (TysWiredIn.mkSumTy -> t) _ _ _
#elif MIN_VERSION_ghc(8, 4, 0)
GHC.ExplicitSum _ _ _ (TysWiredIn.mkSumTy -> t)
#else
GHC.ExplicitSum _ _ _ (TysWiredIn.mkSumTy -> t)
#endif
pattern HsMultiIfType :: Type.Type -> GHC.HsExpr GhcTc
pattern HsMultiIfType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.HsMultiIf t _
#elif MIN_VERSION_ghc(8, 4, 0)
GHC.HsMultiIf t _
#else
GHC.HsMultiIf t _
#endif
pattern FunBindType :: Type.Type -> GHC.HsBindLR GhcTc GhcTc
pattern FunBindType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.FunBind _ (GHC.L _ (Var.varType -> t)) _ _ _
#elif MIN_VERSION_ghc(8, 4, 0)
GHC.FunBind (GHC.L _ (Var.varType -> t)) _ _ _ _
#else
GHC.FunBind (GHC.L _ (Var.varType -> t)) _ _ _ _
#endif
#if MIN_VERSION_ghc(8, 6, 0)
matchGroupType :: GHC.MatchGroupTc -> GHC.Type
matchGroupType (GHC.MatchGroupTc args res) = Type.mkFunTys args res
#endif

View File

@ -0,0 +1,288 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
-- | This module provides the interface to GHC, mainly for loading
-- modules while updating the module cache.
module Haskell.Ide.Engine.Ghc
(
setTypecheckedModule
, Diagnostics
, AdditionalErrs
, cabalModuleGraphs
, makeRevRedirMapFunc
) where
import Bag
import Control.Monad.IO.Class
import Data.IORef
import qualified Data.Map.Strict as Map
import Data.Monoid ((<>))
import qualified Data.Set as Set
import qualified Data.Text as T
import ErrUtils
import Haskell.Ide.Engine.MonadFunctions
import Haskell.Ide.Engine.MonadTypes
import Haskell.Ide.Engine.PluginUtils
import System.FilePath
import DynFlags
import GHC
import IOEnv as G
import HscTypes
import Outputable (renderWithStyle)
import Bag
import Control.Monad.IO.Class
import Data.IORef
import qualified Data.Map.Strict as Map
import Data.Monoid ((<>))
import qualified Data.Set as Set
import qualified Data.Text as T
import ErrUtils
import System.FilePath
import Haskell.Ide.Engine.MonadFunctions
import Haskell.Ide.Engine.MonadTypes
import Haskell.Ide.Engine.PluginUtils
import Haskell.Ide.Engine.GhcUtils
--import qualified Haskell.Ide.Engine.Plugin.HieExtras as Hie
import DynFlags
import GHC
import IOEnv as G
import HscTypes
import Outputable hiding ((<>))
-- This function should be defined in HIE probably, nothing in particular
-- to do with BIOS
import qualified HIE.Bios.GHCApi as BIOS (withDynFlags, CradleError)
import qualified HIE.Bios as BIOS
import Debug.Trace
import qualified HscMain as G
import System.Directory
type Diagnostics = Map.Map Uri (Set.Set Diagnostic)
type AdditionalErrs = [T.Text]
-- ---------------------------------------------------------------------
lspSev :: Severity -> DiagnosticSeverity
lspSev SevWarning = DsWarning
lspSev SevError = DsError
lspSev SevFatal = DsError
lspSev SevInfo = DsInfo
lspSev _ = DsInfo
-- | Turn a 'SourceError' into the HIE 'Diagnostics' format.
srcErrToDiag :: MonadIO m
=> DynFlags
-> (FilePath -> FilePath)
-> SourceError -> m (Diagnostics, AdditionalErrs)
srcErrToDiag df rfm se = do
debugm "in srcErrToDiag"
let errMsgs = bagToList $ srcErrorMessages se
processMsg err = do
let sev = Just DsError
unqual = errMsgContext err
st = mkErrStyle df unqual
msgTxt = T.pack $ renderWithStyle df (pprLocErrMsg err) st
eloc <- srcSpan2Loc rfm $ errMsgSpan err
case eloc of
Right (Location uri range) ->
return $ Right (uri, Diagnostic range sev Nothing (Just "bios") msgTxt Nothing)
Left _ -> return $ Left msgTxt
processMsgs [] = return (Map.empty,[])
processMsgs (x:xs) = do
res <- processMsg x
(m,es) <- processMsgs xs
case res of
Right (uri, diag) ->
return (Map.insertWith Set.union uri (Set.singleton diag) m, es)
Left e -> return (m, e:es)
processMsgs errMsgs
-- | Run a Ghc action and capture any diagnostics and errors produced.
captureDiagnostics :: (MonadIO m, GhcMonad m)
=> (FilePath -> FilePath)
-> m r
-> m (Diagnostics, AdditionalErrs, Maybe r)
captureDiagnostics rfm action = do
env <- getSession
diagRef <- liftIO $ newIORef Map.empty
errRef <- liftIO $ newIORef []
let setLogger df = df { log_action = logDiag rfm errRef diagRef }
setDeferTypedHoles = setGeneralFlag' Opt_DeferTypedHoles
ghcErrRes msg = do
diags <- liftIO $ readIORef diagRef
errs <- liftIO $ readIORef errRef
return (diags, (T.pack msg) : errs, Nothing)
to_diag x = do
(d1, e1) <- srcErrToDiag (hsc_dflags env) rfm x
diags <- liftIO $ readIORef diagRef
errs <- liftIO $ readIORef errRef
return (Map.unionWith Set.union d1 diags, e1 ++ errs, Nothing)
handlers = errorHandlers ghcErrRes to_diag
action' = do
r <- BIOS.withDynFlags (setLogger . setDeferTypedHoles) action
diags <- liftIO $ readIORef diagRef
errs <- liftIO $ readIORef errRef
return (diags,errs, Just r)
gcatches action' handlers
-- | Create a 'LogAction' which will be invoked by GHC when it tries to
-- write anything to `stdout`.
logDiag :: (FilePath -> FilePath) -> IORef AdditionalErrs -> IORef Diagnostics -> LogAction
-- type LogAction = DynFlags -> WarnReason -> Severity -> SrcSpan -> PprStyle -> MsgDoc -> IO ()
logDiag rfm eref dref df _reason sev spn style msg = do
eloc <- srcSpan2Loc rfm spn
traceShowM (spn, eloc)
let msgTxt = T.pack $ renderWithStyle df msg style
case eloc of
Right (Location uri range) -> do
let update = Map.insertWith Set.union uri l
where l = Set.singleton diag
diag = Diagnostic range (Just $ lspSev sev) Nothing (Just "bios") msgTxt Nothing
debugm $ "Writing diag" <> (show diag)
modifyIORef' dref update
Left _ -> do
debugm $ "Writing err" <> (show msgTxt)
modifyIORef' eref (msgTxt:)
return ()
errorHandlers :: (String -> m a) -> (SourceError -> m a) -> [ErrorHandler m a]
errorHandlers ghcErrRes renderSourceError = handlers
where
-- ghc throws GhcException, SourceError, GhcApiError and
-- IOEnvFailure. ghc-mod-core throws GhcModError.
handlers =
[ ErrorHandler $ \(ex :: IOEnvFailure) ->
ghcErrRes (show ex)
, ErrorHandler $ \(ex :: GhcApiError) ->
ghcErrRes (show ex)
, ErrorHandler $ \(ex :: SourceError) ->
renderSourceError ex
, ErrorHandler $ \(ex :: IOError) ->
ghcErrRes (show ex)
, ErrorHandler $ \(ex :: BIOS.CradleError) ->
ghcErrRes (show ex)
]
-- | Load a module from a filepath into the cache, first check the cache
-- to see if it's already there.
setTypecheckedModule :: Uri -> IdeGhcM (IdeResult (Diagnostics, AdditionalErrs))
setTypecheckedModule uri =
pluginGetFile "setTypecheckedModule: " uri $ \_fp -> do
debugm "setTypecheckedModule: before ghc-mod"
debugm "Loading file"
-- mapped_fp <- persistVirtualFile uri
-- ifCachedModuleM mapped_fp (setTypecheckedModule_load uri) cont
setTypecheckedModule_load uri
-- Hacky, need to copy hs-boot file if one exists for a module
-- This is because the virtual file gets created at VFS-1234.hs and
-- then GHC looks for the boot file at VFS-1234.hs-boot
--
-- This strategy doesn't work if the user wants to edit the boot file but
-- not save it and expect the VFS to save them. However, I expect that HIE
-- already didn't deal with boot files correctly.
copyHsBoot :: FilePath -> FilePath -> IO ()
copyHsBoot fp mapped_fp = do
ex <- doesFileExist (fp <> "-boot")
if ex
then copyFile (fp <> "-boot") (mapped_fp <> "-boot")
else return ()
loadFile :: (FilePath -> FilePath) -> (FilePath, FilePath)
-> IdeGhcM (Diagnostics, AdditionalErrs,
Maybe (Maybe TypecheckedModule, [TypecheckedModule]))
loadFile rfm t = do
withProgress "loading" NotCancellable $ \f -> (captureDiagnostics rfm $ BIOS.loadFileWithMessage (Just $ toMessager f) t)
-- | Actually load the module if it's not in the cache
setTypecheckedModule_load :: Uri -> IdeGhcM (IdeResult (Diagnostics, AdditionalErrs))
setTypecheckedModule_load uri =
pluginGetFile "setTypecheckedModule: " uri $ \fp -> do
debugm "setTypecheckedModule: before ghc-mod"
debugm "Loading file"
mapped_fp <- persistVirtualFile uri
liftIO $ copyHsBoot fp mapped_fp
rfm <- reverseFileMap
let progTitle = "Typechecking " <> T.pack (takeFileName fp)
(diags', errs, mmods) <- loadFile rfm (fp, mapped_fp)
debugm "File, loaded"
canonUri <- canonicalizeUri uri
let diags = Map.insertWith Set.union canonUri Set.empty diags'
debugm "setTypecheckedModule: after ghc-mod"
debugm ("Diags: " <> show diags')
let collapse Nothing = (Nothing, [])
collapse (Just (n, xs)) = (n, xs)
diags2 <- case collapse mmods of
--Just (Just pm, Nothing) -> do
-- debugm $ "setTypecheckedModule: Did get parsed module for: " ++ show fp
-- cacheModule fp (Left pm)
-- debugm "setTypecheckedModule: done"
-- return diags
(Just _tm, ts) -> do
debugm $ "setTypecheckedModule: Did get typechecked module for: " ++ show fp
--sess <- fmap GM.gmgsSession . GM.gmGhcSession <$> GM.gmsGet
-- set the session before we cache the module, so that deferred
-- responses triggered by cacheModule can access it
--modifyMTS (\s -> s {ghcSession = sess})
cacheModules rfm ts
--cacheModules rfm [tm]
debugm "setTypecheckedModule: done"
return diags
(Nothing, ts) -> do
debugm $ "setTypecheckedModule: Didn't get typechecked or parsed module for: " ++ show fp
--debugm $ "setTypecheckedModule: errs: " ++ show errs
cacheModules rfm ts
failModule fp
let sev = Just DsError
range = Range (Position 0 0) (Position 1 0)
msgTxt = T.unlines errs
let d = Diagnostic range sev Nothing (Just "bios") msgTxt Nothing
return $ Map.insertWith Set.union canonUri (Set.singleton d) diags
return $ IdeResultOk (diags2,errs)
--
cabalModuleGraphs = undefined
{-
cabalModuleGraphs :: IdeGhcM [GM.GmModuleGraph]
cabalModuleGraphs = doCabalModuleGraphs
where
doCabalModuleGraphs :: (GM.IOish m) => GM.GhcModT m [GM.GmModuleGraph]
doCabalModuleGraphs = do
crdl <- GM.cradle
case GM.cradleCabalFile crdl of
Just _ -> do
mcs <- GM.cabalResolvedComponents
let graph = map GM.gmcHomeModuleGraph $ Map.elems mcs
return graph
Nothing -> return []
-}
-- ---------------------------------------------------------------------
makeRevRedirMapFunc :: IdeGhcM (FilePath -> FilePath)
makeRevRedirMapFunc = reverseFileMap
-- ---------------------------------------------------------------------

View File

@ -27,6 +27,7 @@ module Haskell.Ide.Engine.ModuleCache
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Control
import Control.Monad.Trans.Free
import Data.Dynamic (toDyn, fromDynamic, Dynamic)
import Data.Generics (Proxy(..), TypeRep, typeRep, typeOf)
@ -64,7 +65,7 @@ modifyCache f = do
-- then runs the action in the default cradle.
-- Sets the current directory to the cradle root dir
-- in either case
runActionWithContext :: (MonadIde m, GHC.GhcMonad m, HasGhcModuleCache m, MonadUnliftIO m)
runActionWithContext :: (MonadIde m, GHC.GhcMonad m, HasGhcModuleCache m, MonadBaseControl IO m)
=> GHC.DynFlags -> Maybe FilePath -> m a -> m a
runActionWithContext _df Nothing action = do
-- Cradle with no additional flags
@ -77,7 +78,7 @@ runActionWithContext df (Just uri) action = do
getCradle uri (\lc -> loadCradle df lc >> action)
loadCradle :: (MonadIde m, HasGhcModuleCache m, GHC.GhcMonad m
, MonadUnliftIO m) => GHC.DynFlags -> LookupCradleResult -> m ()
, MonadBaseControl IO m) => GHC.DynFlags -> LookupCradleResult -> m ()
loadCradle _ ReuseCradle = do
traceM ("Reusing cradle")
loadCradle iniDynFlags (NewCradle fp) = do
@ -90,7 +91,7 @@ loadCradle iniDynFlags (NewCradle fp) = do
traceShowM crdl
liftIO (GHC.newHscEnv iniDynFlags) >>= GHC.setSession
liftIO $ setCurrentDirectory (BIOS.cradleRootDir crdl)
withProgress "Initialising Cradle" $ \f ->
withProgress "Initialising Cradle" NotCancellable $ \f ->
BIOS.initializeFlagsWithCradleWithMessage (Just $ toMessager f) fp crdl
setCurrentCradle crdl
loadCradle _iniDynFlags (LoadCradle (CachedCradle crd env)) = do

View File

@ -0,0 +1,80 @@
-- | This module provides an API that software intented to be
-- integrated into HIE can use, so that they can make use of the
-- shared BIOS features.
{-
-- Stuff used in HaRe currently
Options(..)
defaultOptions
GmModuleGraph(..)
ModulePath(..)
GmComponent(..)
GmComponentType(..)
CachedInfo(..)
HasGhcModuleCache(..)
IdeGhcM
cabalModuleGraphs
filePathToUri
makeRevRedirMapFunc
MonadIO(..)
ifCachedModule
runIdeGhcMBare
setTypecheckedModule
-}
module Haskell.Ide.Engine.PluginApi
(
-- ** Re-exported from ghc-mod via ghc-project-types
GP.GmModuleGraph(..)
, GP.ModulePath(..)
, GP.GmComponent(..)
, GP.GmComponentType(..)
-- * IDE monads
, HIE.IdeState(..)
, HIE.IdeGhcM
, HIE.runIdeGhcM
, runIdeGhcMBare
, HIE.IdeM
, HIE.runIdeM
, HIE.IdeDeferM
, HIE.MonadIde
, HIE.iterT
, HIE.LiftsToGhc(..)
, HIE.HasGhcModuleCache(..)
, HIE.cabalModuleGraphs
, HIE.makeRevRedirMapFunc
-- * Using the HIE module cache etc
, HIE.setTypecheckedModule
, HIE.Diagnostics
, HIE.AdditionalErrs
, LSP.filePathToUri
, HIE.ifCachedModule
, HIE.CachedInfo(..)
-- * used for tests in HaRe
-- , HIE.BiosLogLevel(..)
, BiosOptions(..)
, defaultOptions
) where
import qualified GhcProject.Types as GP
import qualified Haskell.Ide.Engine.Ghc as HIE
import qualified Haskell.Ide.Engine.GhcModuleCache as HIE (CachedInfo(..),HasGhcModuleCache(..))
import qualified Haskell.Ide.Engine.ModuleCache as HIE (ifCachedModule)
import qualified Haskell.Ide.Engine.PluginsIdeMonads as HIE
import qualified Language.Haskell.LSP.Types as LSP ( filePathToUri )
import qualified HIE.Bios.Types as HIE
defaultOptions = HIE.defaultCradleOpts
type BiosLogLevel = HIE.BIOSVerbosity
type BiosOptions = HIE.CradleOpts
runIdeGhcMBare = error "Not implemented"

View File

@ -60,7 +60,7 @@ import SrcLoc
import Exception
import System.Directory
import System.FilePath
import qualified Yi.Rope as Yi
import qualified Data.Rope.UTF16 as Rope
-- ---------------------------------------------------------------------
@ -95,6 +95,8 @@ srcSpan2Range :: SrcSpan -> Either T.Text Range
srcSpan2Range spn =
realSrcSpan2Range <$> getRealSrcSpan spn
reverseMapFile :: MonadIO m => (FilePath -> FilePath) -> FilePath -> m FilePath
reverseMapFile rfm fp = do
fp' <- liftIO $ canonicalizePath fp
@ -276,7 +278,7 @@ readVFS :: (MonadIde m, MonadIO m) => Uri -> m (Maybe T.Text)
readVFS uri = do
mvf <- getVirtualFile uri
case mvf of
Just (VirtualFile _ txt _) -> return $ Just (Yi.toText txt)
Just (VirtualFile _ txt _) -> return $ Just (Rope.toText txt)
Nothing -> return Nothing
getRangeFromVFS :: (MonadIde m, MonadIO m) => Uri -> Range -> m (Maybe T.Text)
@ -286,13 +288,6 @@ getRangeFromVFS uri rg = do
Just vfs -> return $ Just $ rangeLinesFromVfs vfs rg
Nothing -> return Nothing
rangeLinesFromVfs :: VirtualFile -> Range -> T.Text
rangeLinesFromVfs (VirtualFile _ yitext _) (Range (Position lf _cf) (Position lt _ct)) = r
where
(_ ,s1) = Yi.splitAtLine lf yitext
(s2, _) = Yi.splitAtLine (lt - lf) s1
r = Yi.toText s2
-- Error catching utilities

View File

@ -12,6 +12,9 @@
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE DerivingVia #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
-- | IdeGhcM and associated types
module Haskell.Ide.Engine.PluginsIdeMonads
@ -49,6 +52,7 @@ module Haskell.Ide.Engine.PluginsIdeMonads
, IdeState(..)
, IdeGhcM
, runIdeGhcM
-- , runIdeGhcMBare
, IdeM
, runIdeM
, IdeDeferM
@ -61,10 +65,10 @@ module Haskell.Ide.Engine.PluginsIdeMonads
, getPlugins
, withProgress
, withIndefiniteProgress
, withIndefiniteProgressIO
, persistVirtualFile
, reverseFileMap
, Core.Progress(..)
, Core.ProgressCancellable(..)
-- ** Lifting
, iterT
, LiftsToGhc(..)
@ -96,9 +100,12 @@ where
import Control.Monad.IO.Class
import Control.Monad.Reader
import Control.Monad.Trans.Free
import Control.Monad.Trans.Control
import Control.Monad.Base
import UnliftIO
import Control.Applicative
import Data.Aeson
import Data.Aeson hiding (defaultOptions)
import qualified Data.ConstrainedDynamic as CD
import Data.Default
import qualified Data.List as List
@ -119,8 +126,8 @@ import Exception
import Haskell.Ide.Engine.Compat
import Haskell.Ide.Engine.Config
import Haskell.Ide.Engine.MultiThreadState
import Haskell.Ide.Engine.GhcModuleCache
import Haskell.Ide.Engine.MultiThreadState
import qualified Language.Haskell.LSP.Core as Core
import Language.Haskell.LSP.Types.Capabilities
@ -221,13 +228,16 @@ type HoverProvider = Uri -> Position -> IdeM (IdeResult [Hover])
type SymbolProvider = Uri -> IdeDeferM (IdeResult [DocumentSymbol])
-- | Format the document either as a whole or only a given Range of it.
data FormattingType = FormatDocument
-- | Format the given Text as a whole or only a @Range@ of it.
-- Range must be relative to the text to format.
-- To format the whole document, read the Text from the file and use 'FormatText'
-- as the FormattingType.
data FormattingType = FormatText
| FormatRange Range
-- | Formats the given Text associated with the given Uri.
-- Should, but might not, honor the provided formatting options (e.g. Floskell does not).
-- A formatting type can be given to either format the whole document or only a Range.
-- Should, but might not, honour the provided formatting options (e.g. Floskell does not).
-- A formatting type can be given to either format the whole text or only a Range.
--
-- Text to format, may or may not, originate from the associated Uri.
-- E.g. it is ok, to modify the text and then reformat it through this API.
@ -238,6 +248,11 @@ data FormattingType = FormatDocument
-- Failing means here that a IdeResultFail is returned.
-- This can be used to display errors to the user, unless the error is an Internal one.
-- The record 'IdeError' and 'IdeErrorCode' can be used to determine the type of error.
--
--
-- To format a whole document, the 'FormatText' @FormattingType@ can be used.
-- It is required to pass in the whole Document Text for that to happen, an empty text
-- and file uri, does not suffice.
type FormattingProvider = T.Text -- ^ Text to format
-> Uri -- ^ Uri of the file being formatted
-> FormattingType -- ^ How much to format
@ -339,11 +354,18 @@ runIdeGhcM plugins mlf stateVar f = do
env <- IdeEnv <$> pure mlf <*> getProcessID <*> pure plugins
eres <- flip runReaderT stateVar $ flip runReaderT env $ BIOS.withGhcT f
return eres
{-
case eres of
Left err -> liftIO $ throwIO err
Right res -> return res
-}
{-
-- | Run an IdeGhcM in an external context (e.g. HaRe), with no plugins or LSP functions
runIdeGhcMBare :: BiosOptions -> IdeGhcM a -> IO a
runIdeGhcMBare biosOptions f = do
let
plugins = IdePlugins Map.empty
mlf = Nothing
initialState = IdeState emptyModuleCache Map.empty Map.empty Nothing
stateVar <- newTVarIO initialState
runIdeGhcM biosOptions plugins mlf stateVar f
-}
-- | A computation that is deferred until the module is cached.
-- Note that the module may not typecheck, in which case 'UriCacheFailed' is passed
@ -424,36 +446,30 @@ getClientCapabilities = do
getPlugins :: MonadIde m => m IdePlugins
getPlugins = idePlugins <$> getIdeEnv
-- | 'withProgress' @title f@ wraps a progress reporting session for long running tasks.
-- | 'withProgress' @title cancellable f@ wraps a progress reporting session for long running tasks.
-- f is passed a reporting function that can be used to give updates on the progress
-- of the task.
withProgress :: forall m a . (MonadIde m, MonadUnliftIO m) => T.Text -> ((Core.Progress -> IO ()) -> m a) -> m a
withProgress t f = do
withProgress :: (MonadIde m , MonadIO m, MonadBaseControl IO m)
=> T.Text -> Core.ProgressCancellable
-> ((Core.Progress -> IO ()) -> m a) -> m a
withProgress t c f = do
lf <- ideEnvLspFuncs <$> getIdeEnv
let mWp = Core.withProgress <$> lf
case mWp of
Nothing -> f (const $ return ())
Just wp -> withRunInIO $ \u -> wp t (u . f)
Just wp -> control $ \run -> wp t c $ \update -> run (f update)
-- | 'withIndefiniteProgress' @title f@ is the same as the 'withProgress' but for tasks
-- | 'withIndefiniteProgress' @title cancellable f@ is the same as the 'withProgress' but for tasks
-- which do not continuously report their progress.
withIndefiniteProgress :: (MonadIde m, MonadUnliftIO m) => T.Text -> m a -> m a
withIndefiniteProgress t f = do
withIndefiniteProgress :: (MonadIde m, MonadBaseControl IO m)
=> T.Text -> Core.ProgressCancellable -> m a -> m a
withIndefiniteProgress t c f = do
lf <- ideEnvLspFuncs <$> getIdeEnv
let mWp = Core.withIndefiniteProgress <$> lf
case mWp of
Nothing -> f
Just wp -> withRunInIO $ \u -> wp t (u f)
-- | 'withIndefiniteProgress' @title f@ is the same as the 'withProgress' but for tasks
-- which do not continuously report their progress.
withIndefiniteProgressIO :: (MonadIO m, MonadIde m) => T.Text -> IO a -> m a
withIndefiniteProgressIO t f = do
lf <- ideEnvLspFuncs <$> getIdeEnv
let mWp = Core.withIndefiniteProgress <$> lf
liftIO $ case mWp of
Nothing -> f
Just wp -> wp t f
Just wp -> control $ \run -> wp t c (run f)
data IdeState = IdeState
{ moduleCache :: GhcModuleCache
@ -504,6 +520,17 @@ instance HasGhcModuleCache IdeM where
tvar <- lift ask
atomically $ modifyTVar' tvar (\st -> st { moduleCache = mc })
-- ---------------------------------------------------------------------
{-
instance GHC.HasDynFlags IdeGhcM where
getDynFlags = GHC.hsc_dflags <$> GHC.getSession
instance GHC.GhcMonad IdeGhcM where
getSession = GM.unGmlT GM.gmlGetSession
setSession env = GM.unGmlT (GM.gmlSetSession env)
-}
-- ---------------------------------------------------------------------
-- Results
-- ---------------------------------------------------------------------
@ -587,3 +614,7 @@ instance MonadTrans GhcT where
deriving via (ReaderT Session IO) instance MonadUnliftIO Ghc
deriving via (ReaderT Session IdeM) instance MonadUnliftIO (GhcT IdeM)
deriving via (ReaderT Session IdeM) instance MonadBaseControl IO (GhcT IdeM)
deriving via (ReaderT Session IdeM) instance MonadBase IO (GhcT IdeM)
deriving via (ReaderT Session IdeM) instance MonadPlus (GhcT IdeM)
deriving via (ReaderT Session IdeM) instance Alternative (GhcT IdeM)

View File

@ -15,28 +15,36 @@ import Data.Data as Data
import Control.Monad.IO.Class
import Data.Maybe
import qualified TcHsSyn
import qualified TysWiredIn
import qualified CoreUtils
import qualified Type
import qualified Desugar
import Haskell.Ide.Engine.Compat
import Haskell.Ide.Engine.ArtifactMap
-- | Generate a mapping from an Interval to types.
-- Intervals may overlap and return more specific results.
genTypeMap :: GHC.GhcMonad m => TypecheckedModule -> m TypeMap
genTypeMap tm = do
let typecheckedSource = GHC.tm_typechecked_source tm
hs_env <- GHC.getSession
hs_env <- GHC.getSession
liftIO $ types hs_env typecheckedSource
everythingInTypecheckedSourceM
:: Data x => (forall a . Data a => a -> IO TypeMap) -> x -> IO TypeMap
everythingInTypecheckedSourceM = everythingButTypeM @GHC.Id
-- | Obtain details map for types.
types :: GHC.HscEnv -> GHC.TypecheckedSource -> IO TypeMap
types hs_env = everythingInTypecheckedSourceM (ty `combineM` fun)
types hs_env = everythingInTypecheckedSourceM (ty `combineM` fun `combineM` funBind)
where
ty :: forall a . Data a => a -> IO TypeMap
ty term = case cast term of
(Just lhsExprGhc@(GHC.L (GHC.RealSrcSpan spn) _)) ->
getType hs_env lhsExprGhc >>= \case
Nothing -> return IM.empty
Nothing -> return IM.empty
Just (_, typ) -> return (IM.singleton (rspToInt spn) typ)
_ -> return IM.empty
@ -46,19 +54,17 @@ types hs_env = everythingInTypecheckedSourceM (ty `combineM` fun)
return (IM.singleton (rspToInt spn) (TcHsSyn.hsPatType hsPatType))
_ -> return IM.empty
everythingInTypecheckedSourceM
:: Data x
=> (forall a . Data a => a -> IO TypeMap)
-> x
-> IO TypeMap
everythingInTypecheckedSourceM f = everythingButTypeM @GHC.Id f
funBind :: forall a . Data a => a -> IO TypeMap
funBind term = case cast term of
(Just (GHC.L (GHC.RealSrcSpan spn) (FunBindType t))) ->
return (IM.singleton (rspToInt spn) t)
_ -> return IM.empty
-- | Combine two queries into one using alternative combinator.
combineM
:: (forall a. Data a => a -> IO TypeMap)
-> (forall a. Data a => a -> IO TypeMap)
-> (forall a. Data a => a -> IO TypeMap)
:: (forall a . Data a => a -> IO TypeMap)
-> (forall a . Data a => a -> IO TypeMap)
-> (forall a . Data a => a -> IO TypeMap)
combineM f g x = do
a <- f x
b <- g x
@ -97,53 +103,38 @@ everythingButM f x = do
(everythingButM f)
x
-- | This instance tries to construct 'HieAST' nodes which include the type of
-- the expression. It is not yet possible to do this efficiently for all
-- expression forms, so we skip filling in the type for those inputs.
-- | Attempts to get the type for expressions in a lazy and cost saving way.
-- Avoids costly desugaring of Expressions and only obtains the type at the leaf of an expression.
--
-- 'HsApp', for example, doesn't have any type information available directly on
-- the node. Our next recourse would be to desugar it into a 'CoreExpr' then
-- query the type of that. Yet both the desugaring call and the type query both
-- involve recursive calls to the function and argument! This is particularly
-- problematic when you realize that the HIE traversal will eventually visit
-- those nodes too and ask for their types again.
-- Implementation is taken from: HieAst.hs<https://gitlab.haskell.org/ghc/ghc/blob/1f5cc9dc8aeeafa439d6d12c3c4565ada524b926/compiler/hieFile/HieAst.hs>
-- Slightly adapted to work for the supported GHC versions 8.2.1 - 8.6.4
--
-- Since the above is quite costly, we just skip cases where computing the
-- expression's type is going to be expensive.
--
-- See #16233
-- See #16233<https://gitlab.haskell.org/ghc/ghc/issues/16233>
getType
:: GHC.HscEnv -> GHC.LHsExpr GHC.GhcTc -> IO (Maybe (GHC.SrcSpan, Type.Type))
:: GHC.HscEnv -> GHC.LHsExpr GhcTc -> IO (Maybe (GHC.SrcSpan, Type.Type))
getType hs_env e@(GHC.L spn e') =
-- Some expression forms have their type immediately available
let
tyOpt = case e' of
GHC.HsLit _ l -> Just (TcHsSyn.hsLitType l)
GHC.HsOverLit _ o -> Just (GHC.overLitType o)
GHC.HsLam _ GHC.MG { GHC.mg_ext = groupTy } ->
Just (matchGroupType groupTy)
GHC.HsLamCase _ GHC.MG { GHC.mg_ext = groupTy } ->
Just (matchGroupType groupTy)
GHC.HsCase _ _ GHC.MG { GHC.mg_ext = groupTy } ->
Just (GHC.mg_res_ty groupTy)
GHC.ExplicitList ty _ _ -> Just (TysWiredIn.mkListTy ty)
GHC.ExplicitSum ty _ _ _ -> Just (TysWiredIn.mkSumTy ty)
GHC.HsDo ty _ _ -> Just ty
GHC.HsMultiIf ty _ -> Just ty
HsOverLitType t -> Just t
HsLitType t -> Just t
HsLamType t -> Just t
HsLamCaseType t -> Just t
HsCaseType t -> Just t
ExplicitListType t -> Just t
ExplicitSumType t -> Just t
HsMultiIfType t -> Just t
_ -> Nothing
in case tyOpt of
_
Just t -> return $ Just (spn ,t)
Nothing
| skipDesugaring e' -> pure Nothing
| otherwise -> do
(_, mbe) <- Desugar.deSugarExpr hs_env e
let res = (spn, ) . CoreUtils.exprType <$> mbe
pure res
where
matchGroupType :: GHC.MatchGroupTc -> GHC.Type
matchGroupType (GHC.MatchGroupTc args res) = Type.mkFunTys args res
-- | Skip desugaring of these expressions for performance reasons.
--
-- See impact on Haddock output (esp. missing type annotations or links)

View File

@ -1,5 +1,5 @@
name: hie-plugin-api
version: 0.6.0.0
version: 0.9.0.0
synopsis: Haskell IDE API for plugin communication
license: BSD3
license-file: LICENSE
@ -24,13 +24,15 @@ library
Haskell.Ide.Engine.GhcUtils
Haskell.Ide.Engine.Config
Haskell.Ide.Engine.Context
Haskell.Ide.Engine.Ghc
Haskell.Ide.Engine.GhcModuleCache
Haskell.Ide.Engine.ModuleCache
Haskell.Ide.Engine.MonadFunctions
Haskell.Ide.Engine.MonadTypes
Haskell.Ide.Engine.MultiThreadState
Haskell.Ide.Engine.PluginsIdeMonads
Haskell.Ide.Engine.PluginApi
Haskell.Ide.Engine.PluginUtils
Haskell.Ide.Engine.PluginsIdeMonads
Haskell.Ide.Engine.TypeMap
build-depends: base >= 4.9 && < 5
, Diff
@ -48,16 +50,19 @@ library
, ghc
, ghc-mod-core >= 5.9.0.0
, hie-bios
, haskell-lsp == 0.10.*
, ghc-project-types >= 5.9.0.0
, haskell-lsp == 0.13.*
, hslogger
, unliftio
, monad-control
, mtl
, rope-utf16-splay >= 0.3.1.0
, stm
, syb
, text
, transformers
, unordered-containers
, yi-rope
, transformers-base
if os(windows)
build-depends: Win32
else

View File

@ -222,7 +222,7 @@ cabalInstallHie versionNumber = do
cabalBuildDoc :: Action ()
cabalBuildDoc = do
execCabal_ ["new-build", "hoogle", "generate"]
execCabal_ ["new-build", "hoogle"]
execCabal_ ["new-exec", "hoogle", "generate"]
cabalTest :: VersionNumber -> Action ()

@ -1 +0,0 @@
Subproject commit f740ca4229806616fde0c2d6add0c9c1a202b4e9

View File

@ -13,7 +13,7 @@ where
import qualified Control.Concurrent.STM.TChan as TChan
import qualified Control.Concurrent.STM as STM
-- | The writing end of a STM channel, only values of type 'a' cam be written
-- | The writing end of a STM channel, only values of type 'a' can be written
-- to the channel
newtype InChan a = InChan (TChan.TChan a)

View File

@ -6,6 +6,9 @@ module Haskell.Ide.Engine.Plugin.ApplyRefact where
import Control.Arrow
import Control.Exception ( IOException
, ErrorCall
, Handler(..)
, catches
, try
)
import Control.Lens hiding ( List )
@ -252,10 +255,17 @@ applyHint fp mhint fileMap = do
-- If we provide "applyRefactorings" with "Just (1,13)" then
-- the "Redundant bracket" hint will never be executed
-- because SrcSpan (1,20,??,??) doesn't contain position (1,13).
appliedFile <- liftIO $ applyRefactorings Nothing commands fp
diff <- ExceptT $ Right <$> makeDiffResult fp (T.pack appliedFile) fileMap
liftIO $ logm $ "applyHint:diff=" ++ show diff
return diff
res <- liftIO $ (Right <$> applyRefactorings Nothing commands fp) `catches`
[ Handler $ \e -> return (Left (show (e :: IOException)))
, Handler $ \e -> return (Left (show (e :: ErrorCall)))
]
case res of
Right appliedFile -> do
diff <- ExceptT $ Right <$> makeDiffResult fp (T.pack appliedFile) fileMap
liftIO $ logm $ "applyHint:diff=" ++ show diff
return diff
Left err ->
throwE (show err)
-- | Gets HLint ideas for
getIdeas :: MonadIO m => FilePath -> Maybe OneHint -> ExceptT String m [Idea]

View File

@ -34,6 +34,7 @@ import qualified HIE.Bios.GHCApi as BIOS (withDynFlags, CradleError)
import qualified HIE.Bios as BIOS
import Debug.Trace
import qualified HscMain as G
import Haskell.Ide.Engine.Ghc
import System.Directory
@ -54,203 +55,10 @@ biosDescriptor plId = PluginDescriptor
, pluginFormattingProvider = Nothing
}
-- ---------------------------------------------------------------------
type Diagnostics = Map.Map Uri (Set.Set Diagnostic)
type AdditionalErrs = [T.Text]
checkCmd :: CommandFunc Uri (Diagnostics, AdditionalErrs)
checkCmd = CmdSync setTypecheckedModule
-- ---------------------------------------------------------------------
lspSev :: Severity -> DiagnosticSeverity
lspSev SevWarning = DsWarning
lspSev SevError = DsError
lspSev SevFatal = DsError
lspSev SevInfo = DsInfo
lspSev _ = DsInfo
-- | Turn a 'SourceError' into the HIE 'Diagnostics' format.
srcErrToDiag :: MonadIO m
=> DynFlags
-> (FilePath -> FilePath)
-> SourceError -> m (Diagnostics, AdditionalErrs)
srcErrToDiag df rfm se = do
debugm "in srcErrToDiag"
let errMsgs = bagToList $ srcErrorMessages se
processMsg err = do
let sev = Just DsError
unqual = errMsgContext err
st = mkErrStyle df unqual
msgTxt = T.pack $ renderWithStyle df (pprLocErrMsg err) st
eloc <- srcSpan2Loc rfm $ errMsgSpan err
case eloc of
Right (Location uri range) ->
return $ Right (uri, Diagnostic range sev Nothing (Just "bios") msgTxt Nothing)
Left _ -> return $ Left msgTxt
processMsgs [] = return (Map.empty,[])
processMsgs (x:xs) = do
res <- processMsg x
(m,es) <- processMsgs xs
case res of
Right (uri, diag) ->
return (Map.insertWith Set.union uri (Set.singleton diag) m, es)
Left e -> return (m, e:es)
processMsgs errMsgs
-- | Run a Ghc action and capture any diagnostics and errors produced.
captureDiagnostics :: (MonadIO m, GhcMonad m)
=> (FilePath -> FilePath)
-> m r
-> m (Diagnostics, AdditionalErrs, Maybe r)
captureDiagnostics rfm action = do
env <- getSession
diagRef <- liftIO $ newIORef Map.empty
errRef <- liftIO $ newIORef []
let setLogger df = df { log_action = logDiag rfm errRef diagRef }
setDeferTypedHoles = setGeneralFlag' Opt_DeferTypedHoles
ghcErrRes msg = do
diags <- liftIO $ readIORef diagRef
errs <- liftIO $ readIORef errRef
return (diags, (T.pack msg) : errs, Nothing)
to_diag x = do
(d1, e1) <- srcErrToDiag (hsc_dflags env) rfm x
diags <- liftIO $ readIORef diagRef
errs <- liftIO $ readIORef errRef
return (Map.unionWith Set.union d1 diags, e1 ++ errs, Nothing)
handlers = errorHandlers ghcErrRes to_diag
action' = do
r <- BIOS.withDynFlags (setLogger . setDeferTypedHoles) action
diags <- liftIO $ readIORef diagRef
errs <- liftIO $ readIORef errRef
return (diags,errs, Just r)
gcatches action' handlers
-- | Create a 'LogAction' which will be invoked by GHC when it tries to
-- write anything to `stdout`.
logDiag :: (FilePath -> FilePath) -> IORef AdditionalErrs -> IORef Diagnostics -> LogAction
-- type LogAction = DynFlags -> WarnReason -> Severity -> SrcSpan -> PprStyle -> MsgDoc -> IO ()
logDiag rfm eref dref df _reason sev spn style msg = do
eloc <- srcSpan2Loc rfm spn
traceShowM (spn, eloc)
let msgTxt = T.pack $ renderWithStyle df msg style
case eloc of
Right (Location uri range) -> do
let update = Map.insertWith Set.union uri l
where l = Set.singleton diag
diag = Diagnostic range (Just $ lspSev sev) Nothing (Just "bios") msgTxt Nothing
debugm $ "Writing diag" <> (show diag)
modifyIORef' dref update
Left _ -> do
debugm $ "Writing err" <> (show msgTxt)
modifyIORef' eref (msgTxt:)
return ()
errorHandlers :: (String -> m a) -> (SourceError -> m a) -> [ErrorHandler m a]
errorHandlers ghcErrRes renderSourceError = handlers
where
-- ghc throws GhcException, SourceError, GhcApiError and
-- IOEnvFailure. ghc-mod-core throws GhcModError.
handlers =
[ ErrorHandler $ \(ex :: IOEnvFailure) ->
ghcErrRes (show ex)
, ErrorHandler $ \(ex :: GhcApiError) ->
ghcErrRes (show ex)
, ErrorHandler $ \(ex :: SourceError) ->
renderSourceError ex
, ErrorHandler $ \(ex :: IOError) ->
ghcErrRes (show ex)
, ErrorHandler $ \(ex :: BIOS.CradleError) ->
ghcErrRes (show ex)
]
-- | Load a module from a filepath into the cache, first check the cache
-- to see if it's already there.
setTypecheckedModule :: Uri -> IdeGhcM (IdeResult (Diagnostics, AdditionalErrs))
setTypecheckedModule uri =
pluginGetFile "setTypecheckedModule: " uri $ \_fp -> do
debugm "setTypecheckedModule: before ghc-mod"
debugm "Loading file"
-- mapped_fp <- persistVirtualFile uri
-- ifCachedModuleM mapped_fp (setTypecheckedModule_load uri) cont
setTypecheckedModule_load uri
-- Hacky, need to copy hs-boot file if one exists for a module
-- This is because the virtual file gets created at VFS-1234.hs and
-- then GHC looks for the boot file at VFS-1234.hs-boot
--
-- This strategy doesn't work if the user wants to edit the boot file but
-- not save it and expect the VFS to save them. However, I expect that HIE
-- already didn't deal with boot files correctly.
copyHsBoot :: FilePath -> FilePath -> IO ()
copyHsBoot fp mapped_fp = do
ex <- doesFileExist (fp <> "-boot")
if ex
then copyFile (fp <> "-boot") (mapped_fp <> "-boot")
else return ()
loadFile :: (FilePath -> FilePath) -> (FilePath, FilePath)
-> IdeGhcM (Diagnostics, AdditionalErrs,
Maybe (Maybe TypecheckedModule, [TypecheckedModule]))
loadFile rfm t = do
withProgress "loading" $ \f -> (captureDiagnostics rfm $ BIOS.loadFileWithMessage (Just $ toMessager f) t)
-- | Actually load the module if it's not in the cache
setTypecheckedModule_load :: Uri -> IdeGhcM (IdeResult (Diagnostics, AdditionalErrs))
setTypecheckedModule_load uri =
pluginGetFile "setTypecheckedModule: " uri $ \fp -> do
debugm "setTypecheckedModule: before ghc-mod"
debugm "Loading file"
mapped_fp <- persistVirtualFile uri
liftIO $ copyHsBoot fp mapped_fp
rfm <- reverseFileMap
let progTitle = "Typechecking " <> T.pack (takeFileName fp)
(diags', errs, mmods) <- loadFile rfm (fp, mapped_fp)
debugm "File, loaded"
canonUri <- canonicalizeUri uri
let diags = Map.insertWith Set.union canonUri Set.empty diags'
debugm "setTypecheckedModule: after ghc-mod"
debugm ("Diags: " <> show diags')
let collapse Nothing = (Nothing, [])
collapse (Just (n, xs)) = (n, xs)
diags2 <- case collapse mmods of
--Just (Just pm, Nothing) -> do
-- debugm $ "setTypecheckedModule: Did get parsed module for: " ++ show fp
-- cacheModule fp (Left pm)
-- debugm "setTypecheckedModule: done"
-- return diags
(Just _tm, ts) -> do
debugm $ "setTypecheckedModule: Did get typechecked module for: " ++ show fp
--sess <- fmap GM.gmgsSession . GM.gmGhcSession <$> GM.gmsGet
-- set the session before we cache the module, so that deferred
-- responses triggered by cacheModule can access it
--modifyMTS (\s -> s {ghcSession = sess})
cacheModules rfm ts
--cacheModules rfm [tm]
debugm "setTypecheckedModule: done"
return diags
(Nothing, ts) -> do
debugm $ "setTypecheckedModule: Didn't get typechecked or parsed module for: " ++ show fp
--debugm $ "setTypecheckedModule: errs: " ++ show errs
cacheModules rfm ts
failModule fp
let sev = Just DsError
range = Range (Position 0 0) (Position 1 0)
msgTxt = T.unlines errs
let d = Diagnostic range sev Nothing (Just "bios") msgTxt Nothing
return $ Map.insertWith Set.union canonUri (Set.singleton d) diags
return $ IdeResultOk (diags2,errs)

View File

@ -43,8 +43,8 @@ provider
provider text uri formatType opts = pluginGetFile "brittanyCmd: " uri $ \fp -> do
confFile <- liftIO $ getConfFile fp
let (range, selectedContents) = case formatType of
FormatDocument -> (fullRange text, text)
FormatRange r -> (normalize r, extractRange r text)
FormatText -> (fullRange text, text)
FormatRange r -> (normalize r, extractRange r text)
res <- formatText confFile opts selectedContents
case res of
@ -65,21 +65,28 @@ formatText
=> Maybe FilePath -- ^ Path to configs. If Nothing, default configs will be used.
-> FormattingOptions -- ^ Options for the formatter such as indentation.
-> Text -- ^ Text to format
-> m (Either [BrittanyError] Text) -- ^ Either formatted Text or a error from Brittany.
formatText confFile opts text =
-> m (Either [BrittanyError] Text) -- ^ Either formatted Text or a error from Brittany.
formatText confFile opts text =
liftIO $ runBrittany tabSize confFile text
where tabSize = opts ^. J.tabSize
-- | Extend to the line below to replace newline character, as above.
-- | Extend to the line below and above to replace newline character.
normalize :: Range -> Range
normalize (Range (Position sl _) (Position el _)) =
Range (Position sl 0) (Position (el + 1) 0)
-- | Recursively search in every directory of the given filepath for brittany.yaml
-- | Recursively search in every directory of the given filepath for brittany.yaml.
-- If no such file has been found, return Nothing.
getConfFile :: FilePath -> IO (Maybe FilePath)
getConfFile = findLocalConfigPath . takeDirectory
-- | Run Brittany on the given text with the given tab size and
-- a configuration path. If no configuration path is given, a
-- default configuration is chosen. The configuration may overwrite
-- tab size parameter.
--
-- Returns either a list of Brittany Errors or the reformatted text.
-- May not throw an exception.
runBrittany :: Int -- ^ tab size
-> Maybe FilePath -- ^ local config file
-> Text -- ^ text to format

View File

@ -35,9 +35,9 @@ provider contents uri typ _opts =
pluginGetFile "Floskell: " uri $ \file -> do
config <- liftIO $ findConfigOrDefault file
let (range, selectedContents) = case typ of
FormatDocument -> (fullRange contents, contents)
FormatRange r -> (r, extractRange r contents)
result = reformat config (uriToFilePath uri) (BS.fromStrict (T.encodeUtf8 selectedContents))
FormatText -> (fullRange contents, contents)
FormatRange r -> (r, extractRange r contents)
result = reformat config (Just file) (BS.fromStrict (T.encodeUtf8 selectedContents))
case result of
Left err -> return $ IdeResultFail (IdeError PluginError (T.pack $ "floskellCmd: " ++ err) Null)
Right new -> return $ IdeResultOk [TextEdit range (T.decodeUtf8 (BS.toStrict new))]

View File

@ -16,8 +16,6 @@ import Data.List
import Data.Maybe
import Data.Monoid ((<>))
import qualified Data.Text as T
import System.FilePath
import ErrUtils
import Name
import GHC.Generics
import Haskell.Ide.Engine.MonadFunctions

View File

@ -242,7 +242,11 @@ runHareCommand name cmd = do
-- ---------------------------------------------------------------------
runHareCommand' :: RefactGhc a
-- newtype RefactGhc a = RefactGhc
-- { unRefactGhc :: StateT RefactState HIE.IdeGhcM a
-- }
runHareCommand' :: forall a. RefactGhc a
-> IdeGhcM (Either String a)
runHareCommand' cmd =
do let initialState =
@ -255,11 +259,11 @@ runHareCommand' cmd =
,rsStorage = StorageNone
,rsCurrentTarget = Nothing
,rsModule = Nothing}
let cmd' = unRefactGhc cmd
let
cmd' :: StateT RefactState IdeGhcM a
cmd' = unRefactGhc cmd
embeddedCmd =
GM.unGmlT $
hoist (liftIO . flip evalStateT initialState)
(GM.GmlT cmd')
evalStateT cmd' initialState
handlers
:: Applicative m
=> [GM.GHandler m (Either String a)]
@ -273,6 +277,7 @@ runHareCommand' cmd =
(Left err, _) -> error (show err)
-- ---------------------------------------------------------------------
-- | This is like hoist from the mmorph package, but build on
-- `MonadTransControl` since we dont have an `MFunctor` instance.

View File

@ -188,12 +188,12 @@ renderMarkDown =
, markupProperty = \s -> T.unlines
["\n```haskell"
,"prop> " <> removeInner (T.pack s)
,"\n```\n"]
,"```\n"]
, markupExample = T.unlines . map (\e -> T.pack $ unlines $
["\n```haskell"
,"> " <> exampleExpression e
] ++ exampleResult e ++
["\n```\n"])
["```\n"])
, markupHeader = \h ->
T.replicate (headerLevel h) "#" <> " " <> headerTitle h <> "\n"
#if __GLASGOW_HASKELL__ >= 804

View File

@ -2,6 +2,7 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE LambdaCase #-}
module Haskell.Ide.Engine.Plugin.HsImport where
import Control.Lens.Operators
@ -41,32 +42,44 @@ hsimportDescriptor plId = PluginDescriptor
, pluginFormattingProvider = Nothing
}
-- | Import Parameters for Modules.
-- Can be used to import every symbol from a module,
-- or to import only a specific function from a module.
data ImportParams = ImportParams
{ file :: Uri
, moduleToImport :: T.Text
{ file :: Uri -- ^ Uri to the file to import the module to.
, addToImportList :: Maybe T.Text -- ^ If set, an import-list will be created.
, moduleToImport :: T.Text -- ^ Name of the module to import.
}
deriving (Show, Eq, Generics.Generic, ToJSON, FromJSON)
importCmd :: CommandFunc ImportParams J.WorkspaceEdit
importCmd = CmdSync $ \(ImportParams uri modName) -> importModule uri modName
importCmd = CmdSync $ \(ImportParams uri importList modName) ->
importModule uri importList modName
importModule :: Uri -> T.Text -> IdeGhcM (IdeResult J.WorkspaceEdit)
importModule uri modName = pluginGetFile "hsimport cmd: " uri $ \origInput -> do
-- | Import the given module for the given file.
-- May take an explicit function name to perform an import-list import.
-- Multiple import-list imports will result in merged imports,
-- e.g. two consecutive imports for the same module will result in a single
-- import line.
importModule
:: Uri -> Maybe T.Text -> T.Text -> IdeGhcM (IdeResult J.WorkspaceEdit)
importModule uri importList modName =
pluginGetFile "hsimport cmd: " uri $ \origInput -> do
shouldFormat <- formatOnImportOn <$> getConfig
fileMap <- return id -- TODO: GM.mkRevRedirMapFunc
-- GM.withMappedFile origInput $ \input -> do
let input = origInput
do
tmpDir <- liftIO getTemporaryDirectory
(output, outputH) <- liftIO $ openTempFile tmpDir "hsimportOutput"
liftIO $ hClose outputH
let args = defaultArgs { moduleName = T.unpack modName
, inputSrcFile = input
, symbolName = T.unpack $ fromMaybe "" importList
, outputSrcFile = output
}
-- execute hsimport on the given file and write into a temporary file.
maybeErr <- liftIO $ hsimportWithArgs defaultConfig args
case maybeErr of
Just err -> do
@ -74,29 +87,59 @@ importModule uri modName = pluginGetFile "hsimport cmd: " uri $ \origInput -> do
let msg = T.pack $ show err
return $ IdeResultFail (IdeError PluginError msg Null)
Nothing -> do
-- Since no error happened, calculate the differences of
-- the original file and after the import has been done.
newText <- liftIO $ T.readFile output
liftIO $ removeFile output
J.WorkspaceEdit mChanges mDocChanges <- liftToGhc
$ makeDiffResult input newText fileMap
-- If the client wants its import formatted,
-- it can be configured in the config.
if shouldFormat
then do
config <- getConfig
plugins <- getPlugins
let mprovider = Hie.getFormattingPlugin config plugins
case mprovider of
-- Client may have no formatter selected
-- but still the option to format on import.
Nothing ->
return $ IdeResultOk (J.WorkspaceEdit mChanges mDocChanges)
Just (_, provider) -> do
let formatEdit :: J.TextEdit -> IdeGhcM J.TextEdit
formatEdit origEdit@(J.TextEdit _ t) = do
let
-- | Dirty little hack.
-- Necessary in the following case:
-- We want to add an item to an existing import-list.
-- The diff algorithm does not count the newline character
-- as part of the diff between new and old text.
-- However, some formatters (Brittany), add a trailing
-- newline nevertheless.
-- This leads to the problem that an additional
-- newline is inserted into the source.
-- This function makes sure, that if the original text
-- did not have a newline, none will be added, assuming
-- that the diff algorithm continues to not count newlines
-- as part of the diff.
-- This is only save to do in this very specific environment.
-- In any other case, this function may not be copy-pasted
-- to solve a similar problem.
renormalise :: T.Text -> T.Text -> T.Text
renormalise orig formatted
| T.null orig || T.null formatted = orig <> formatted
| T.last orig /= '\n' && T.last formatted == '\n' = T.init formatted
| otherwise = formatted
formatEdit :: J.TextEdit -> IdeGhcM J.TextEdit
formatEdit origEdit@(J.TextEdit r t) = do
-- TODO: are these default FormattingOptions ok?
res <- liftToGhc $ provider t uri FormatDocument (FormattingOptions 2 True)
let formatEdits = case res of
IdeResultOk xs -> xs
_ -> []
return $ foldl' J.editTextEdit origEdit formatEdits
formatEdits <-
liftToGhc $ provider t uri FormatText (FormattingOptions 2 True) >>= \case
IdeResultOk xs -> return xs
_ -> return [origEdit]
-- let edits = foldl' J.editTextEdit origEdit formatEdits -- TODO: this seems broken.
return (J.TextEdit r (renormalise t . J._newText $ head formatEdits))
-- behold: the legendary triple mapM
newChanges <- (mapM . mapM . mapM) formatEdit mChanges
@ -111,48 +154,132 @@ importModule uri modName = pluginGetFile "hsimport cmd: " uri $ \origInput -> do
$ IdeResultOk (J.WorkspaceEdit newChanges newDocChanges)
else return $ IdeResultOk (J.WorkspaceEdit mChanges mDocChanges)
-- | Search style for Hoogle.
-- Can be used to look either for the exact term,
-- only the exact name or a relaxed form of the term.
data SearchStyle
= Exact -- ^ If you want to match exactly the search string.
| ExactName -- ^ If you want to match exactly a function name.
-- Same as @Exact@ if the term is just a function name.
| Relax (T.Text -> T.Text) -- ^ Relax the search term to match even more.
-- | Produces code actions.
codeActionProvider :: CodeActionProvider
codeActionProvider plId docId _ context = do
let J.List diags = context ^. J.diagnostics
terms = mapMaybe getImportables diags
res <- mapM (bimapM return Hoogle.searchModules) terms
actions <- catMaybes <$> mapM (uncurry mkImportAction) (concatTerms res)
terms = mapMaybe getImportables diags
-- Search for the given diagnostics and produce appropiate import actions.
actions <- importActionsForTerms Exact terms
if null actions
then do
let relaxedTerms = map (bimap id (head . T.words)) terms
relaxedRes <- mapM (bimapM return Hoogle.searchModules) relaxedTerms
relaxedActions <- catMaybes <$> mapM (uncurry mkImportAction) (concatTerms relaxedRes)
return $ IdeResultOk relaxedActions
else return $ IdeResultOk actions
then do
-- If we didn't find any exact matches, relax the search terms.
-- Only looks for the function names, not the exact siganture.
relaxedActions <- importActionsForTerms ExactName terms
return $ IdeResultOk relaxedActions
else return $ IdeResultOk actions
where
concatTerms = concatMap (\(d, ts) -> map (d,) ts)
where
-- | Creates CodeActions from the diagnostics to add imports.
-- Takes a relaxation Function. Used to relax the search term,
-- e.g. instead of `take :: Int -> [a] -> [a]` use `take` as the search term.
--
-- List of Diagnostics with the associated term to look for.
-- Diagnostic that is supposed to import the appropiate term.
--
-- Result may produce several import actions, or none.
importActionsForTerms
:: SearchStyle -> [(J.Diagnostic, T.Text)] -> IdeM [J.CodeAction]
importActionsForTerms style terms = do
let searchTerms = map (bimap id (applySearchStyle style)) terms
-- Get the function names for a nice import-list title.
let functionNames = map (head . T.words . snd) terms
searchResults' <- mapM (bimapM return Hoogle.searchModules) searchTerms
let searchResults = zip functionNames searchResults'
let normalise =
concatMap (\(a, b) -> zip (repeat a) (concatTerms b)) searchResults
--TODO: Check if package is already installed
mkImportAction :: J.Diagnostic -> T.Text -> IdeM (Maybe J.CodeAction)
mkImportAction diag modName = do
cmd <- mkLspCommand plId "import" title (Just cmdParams)
return (Just (codeAction cmd))
where
codeAction cmd = J.CodeAction title (Just J.CodeActionQuickFix) (Just (J.List [diag])) Nothing (Just cmd)
title = "Import module " <> modName
cmdParams = [toJSON (ImportParams (docId ^. J.uri) modName)]
concat <$> mapM (uncurry (termToActions style)) normalise
getImportables :: J.Diagnostic -> Maybe (J.Diagnostic, T.Text)
getImportables diag@(J.Diagnostic _ _ _ (Just "ghcmod") msg _) = (diag,) <$> extractImportableTerm msg
getImportables _ = Nothing
-- | Apply the search style to given term.
-- Can be used to look for a term that matches exactly the search term,
-- or one that matches only the exact name.
-- At last, a custom relaxation function can be passed for more control.
applySearchStyle :: SearchStyle -> T.Text -> T.Text
applySearchStyle Exact term = "is:exact " <> term
applySearchStyle ExactName term = case T.words term of
[] -> term
(x : _) -> "is:exact " <> x
applySearchStyle (Relax relax) term = relax term
-- | Turn a search term with function name into Import Actions.
-- Function name may be of only the exact phrase to import.
-- The resulting CodeAction's contain a general import of a module or
-- uses an Import-List.
--
-- Note, that repeated use of the Import-List will add imports to
-- the appropriate import line, e.g. no module import is duplicated, except
-- for qualified imports.
--
-- If the search term is relaxed in a custom way,
-- no import list can be offered, since the function name
-- may be not the one we expect.
termToActions
:: SearchStyle -> T.Text -> (J.Diagnostic, T.Text) -> IdeM [J.CodeAction]
termToActions style functionName (diagnostic, termName) = do
let useImportList = case style of
Relax _ -> Nothing
_ -> Just (mkImportAction (Just functionName) diagnostic termName)
catMaybes <$> sequenceA
(mkImportAction Nothing diagnostic termName : maybeToList useImportList)
concatTerms :: (a, [b]) -> [(a, b)]
concatTerms (a, b) = zip (repeat a) b
--TODO: Check if package is already installed
mkImportAction
:: Maybe T.Text -> J.Diagnostic -> T.Text -> IdeM (Maybe J.CodeAction)
mkImportAction importList diag modName = do
cmd <- mkLspCommand plId "import" title (Just cmdParams)
return (Just (codeAction cmd))
where
codeAction cmd = J.CodeAction title
(Just J.CodeActionQuickFix)
(Just (J.List [diag]))
Nothing
(Just cmd)
title =
"Import module "
<> modName
<> maybe "" (\name -> " (" <> name <> ")") importList
cmdParams = [toJSON (ImportParams (docId ^. J.uri) importList modName)]
-- | For a Diagnostic, get an associated function name.
-- If Ghc-Mod can not find any candidates, Nothing is returned.
getImportables :: J.Diagnostic -> Maybe (J.Diagnostic, T.Text)
getImportables diag@(J.Diagnostic _ _ _ (Just "ghcmod") msg _) =
(diag, ) <$> extractImportableTerm msg
getImportables _ = Nothing
-- | Extract from an error message an appropriate term to search for.
-- This looks at the error message and tries to extract the expected
-- signature of an unknown function.
-- If this is not possible, Nothing is returned.
extractImportableTerm :: T.Text -> Maybe T.Text
extractImportableTerm dirtyMsg = T.strip <$> asum
[ T.stripPrefix "Variable not in scope: " msg
, T.init <$> T.stripPrefix "Not in scope: type constructor or class " msg
, T.stripPrefix "Data constructor not in scope: " msg]
where msg = head
-- Get rid of the rename suggestion parts
$ T.splitOn "Perhaps you meant "
$ T.replace "\n" " "
-- Get rid of trailing/leading whitespace on each individual line
$ T.unlines $ map T.strip $ T.lines
$ T.replace "" "" dirtyMsg
, T.stripPrefix "Data constructor not in scope: " msg
]
where
msg =
head
-- Get rid of the rename suggestion parts
$ T.splitOn "Perhaps you meant "
$ T.replace "\n" " "
-- Get rid of trailing/leading whitespace on each individual line
$ T.unlines
$ map T.strip
$ T.lines
$ T.replace "" "" dirtyMsg

View File

@ -5,7 +5,7 @@
{-# LANGUAGE NamedFieldPuns #-}
module Haskell.Ide.Engine.Plugin.Liquid where
--import Control.Concurrent.Async
import Control.Concurrent.Async.Lifted
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Class
@ -31,7 +31,6 @@ import System.FilePath
import System.Process
import Text.Parsec
import Text.Parsec.Text
import UnliftIO.Async
-- ---------------------------------------------------------------------
@ -123,7 +122,9 @@ diagnosticProvider DiagnosticOnSave uri cb = pluginGetFile "Liquid.diagnosticPro
mapM_ (liftIO . cancel) mtid
let progTitle = "Running Liquid Haskell on " <> T.pack (takeFileName file)
tid <- lift $ async $ withIndefiniteProgress progTitle $ (liftIO $ generateDiagnosics cb uri file)
tid <- lift $ async $
withIndefiniteProgress progTitle NotCancellable $
liftIO $ generateDiagnosics cb uri file
put (LiquidData (Just tid))

View File

@ -63,17 +63,38 @@ packageDescriptor plId = PluginDescriptor
}
data AddParams = AddParams
{ rootDirParam :: FilePath -- ^ The root directory.
, fileParam :: FilePath -- ^ A path to a module inside the
-- library/executable/test-suite you want to
-- add the package to.
, packageParam :: T.Text -- ^ The name of the package to add.
{ rootDirParam :: FilePath -- ^ The root directory.
, fileParam :: ModulePath -- ^ A path to a module inside the
-- library/executable/test-suite you want to
-- add the package to. May be a relative or
-- absolute path, thus, must be normalised.
, packageParam :: Package -- ^ The name of the package to add.
}
deriving (Eq, Show, Read, Generic, ToJSON, FromJSON)
addCmd :: CommandFunc AddParams J.WorkspaceEdit
addCmd = CmdSync $ \(AddParams rootDir modulePath pkg) -> do
-- | FilePath to a cabal package description file.
type CabalFilePath = FilePath
-- | FilePath to a package.yaml package description file.
type HpackFilePath = FilePath
-- | FilePath to a module within the project.
-- May be used to establish what component the dependency shall be added to.
type ModulePath = FilePath
-- | Name of the Package to add.
type Package = T.Text
-- | Add a package to the project's dependencies.
-- May fail if no project dependency specification can be found.
-- Supported are `*.cabal` and `package.yaml` specifications.
-- Moreover, may fail with an IOException in case of a filesystem problem.
addCmd :: CommandFunc AddParams J.WorkspaceEdit
addCmd = CmdSync addCmd'
-- | Add a package to the project's dependencies.
-- May fail if no project dependency specification can be found.
-- Supported are `*.cabal` and `package.yaml` specifications.
-- Moreover, may fail with an IOException in case of a filesystem problem.
addCmd' :: AddParams -> IdeGhcM (IdeResult J.WorkspaceEdit)
addCmd' (AddParams rootDir modulePath pkg) = do
packageType <- liftIO $ findPackageType rootDir
fileMap <- reverseFileMap
@ -82,30 +103,51 @@ addCmd = CmdSync $ \(AddParams rootDir modulePath pkg) -> do
absFp <- liftIO $ canonicalizePath relFp
let relModulePath = makeRelative (takeDirectory absFp) modulePath
liftToGhc $ editCabalPackage absFp relModulePath (T.unpack pkg) fileMap
liftToGhc $ editCabalPackage absFp relModulePath pkg fileMap
HpackPackage relFp -> do
absFp <- liftIO $ canonicalizePath relFp
let relModulePath = makeRelative (takeDirectory absFp) modulePath
liftToGhc $ editHpackPackage absFp relModulePath pkg
NoPackage -> return $ IdeResultFail (IdeError PluginError "No package.yaml or .cabal found" Null)
data PackageType = CabalPackage FilePath
| HpackPackage FilePath
| NoPackage
data PackageType = CabalPackage FilePath -- ^ Location of Cabal File. May be relative.
| HpackPackage FilePath -- ^ Location of `package.yaml`. May be relative.
| NoPackage -- ^ No package format has been found.
deriving (Show, Eq)
-- | Find the package type the project with the given root uses.
-- Might have weird results if there is more than one cabal package specification
-- in the root directory.
-- The `package.yaml` is preferred in case both files are present.
-- May fail with various IOException's, for example if the given
-- directory does not exist, a Hardware Failure happens or
-- the permissions deny it.
-- However, normally this command should succeeed without any exceptions.
findPackageType :: FilePath -> IO PackageType
findPackageType rootDir = do
files <- getDirectoryContents rootDir
-- Search for all files that have '.cabal' as a file ending,
-- since that is the format of the cabal file. May be more to one or zero.
let mCabal = listToMaybe $ filter (isExtensionOf "cabal") files
mHpack <- findFile [rootDir] "package.yaml"
return $ fromMaybe NoPackage $ asum [HpackPackage <$> mHpack, CabalPackage <$> mCabal]
-- | Edit a hpack package to add the given package to the package.yaml.
-- If package.yaml is not in an expected format, will fail fatally.
--
-- Currently does not preserve format.
-- Keep an eye out on this other GSOC project!
-- https://github.com/wisn/format-preserving-yaml
editHpackPackage :: FilePath -> FilePath -> T.Text -> IdeM (IdeResult WorkspaceEdit)
editHpackPackage :: HpackFilePath -- ^ Path to the package.yaml file
-- containing the package description.
-> ModulePath -- ^ Path to the module where the command has
-- been issued in.
-- Used to find out what component the
-- dependency shall be added to.
-> Package -- ^ Name of the package to add as a dependency.
-> IdeM (IdeResult WorkspaceEdit)
editHpackPackage fp modulePath pkgName = do
contents <- liftIO $ B.readFile fp
@ -113,19 +155,29 @@ editHpackPackage fp modulePath pkgName = do
case Y.decodeThrow contents :: Maybe Object of
Just obj -> do
-- Map over all major components, such as "executable", "executables",
-- "tests" and "benchmarks". Note, that "library" is a major component,
-- but its structure is different and can not be mapped over in the same way.
--
-- Only adds the package if the declared "source-dirs" field is part of the
-- module path, or if no "source-dirs" is declared.
let compsMapped = mapComponentTypes (ensureObject $ mapComponents (ensureObject $ mapCompDependencies addDep)) obj
let addDepToMainLib = fromMaybe True $ do
Object lib <- HM.lookup "library" compsMapped
sourceDirs <- HM.lookup "source-dirs" lib
return $ isInSourceDir sourceDirs
-- Is there a global "dependencies" yaml object?
let addDepToMainDep = fromMaybe False $ do
Array _ <- HM.lookup "dependencies" compsMapped
return True
let newPkg = if addDepToMainLib
then mapMainDependencies addDep compsMapped
else compsMapped
-- Either add the package to only the top-level "dependencies",
-- or to all main components of which the given module is part of.
let newPkg
| addDepToMainDep = mapMainDependencies addDep obj
-- Map over the library component at last, since it has different structure.
| otherwise = mapLibraryDependency addDep compsMapped
newPkgText = T.decodeUtf8 $ Y.encode newPkg
let newPkgText = T.decodeUtf8 $ Y.encode newPkg
-- Construct the WorkSpaceEdit
let numOldLines = length $ T.lines $ T.decodeUtf8 contents
range = J.Range (J.Position 0 0) (J.Position numOldLines 0)
textEdit = J.TextEdit range newPkgText
@ -144,10 +196,18 @@ editHpackPackage fp modulePath pkgName = do
mapMainDependencies :: (Value -> Value) -> Object -> Object
mapMainDependencies f o =
let g "dependencies" = f
let g :: T.Text -> Value -> Value
g "dependencies" = f
g _ = id
in HM.mapWithKey g o
mapLibraryDependency :: (Value -> Value) -> Object -> Object
mapLibraryDependency f o =
let g :: T.Text -> Value -> Value
g "library" (Y.Object o') = Y.Object (mapCompDependencies f o')
g _ x = x
in HM.mapWithKey g o
mapComponentTypes :: (Value -> Value) -> Object -> Object
mapComponentTypes f o =
let g "executables" = f
@ -185,9 +245,16 @@ editHpackPackage fp modulePath pkgName = do
addDep (Array deps) = Array $ fromList (String pkgName:GHC.Exts.toList deps)
addDep _ = error "Not an array in addDep"
-- | Takes a cabal file and a path to a module in the dependency you want to edit.
editCabalPackage :: FilePath -> FilePath -> String -> (FilePath -> FilePath) -> IdeM (IdeResult J.WorkspaceEdit)
-- | Takes a cabal file and a path to a module in the project and a package name to add
-- to the cabal file. Reverse file map is needed to find the correct file in the project.
-- May fail with an IOException if the Cabal file is invalid.
editCabalPackage :: CabalFilePath -- ^ Path to the cabal file to add the dependency to.
-> ModulePath -- ^ Path to the module that wants to add the package.
-- Used to find out what component the
-- dependency shall be added to.
-> Package -- ^ Name of the package added as a dependency.
-> (FilePath -> FilePath) -- ^ Reverse File for computing file diffs.
-> IdeM (IdeResult J.WorkspaceEdit)
editCabalPackage file modulePath pkgName fileMap = do
package <- liftIO $ readGenericPackageDescription normal file
@ -203,7 +270,6 @@ editCabalPackage file modulePath pkgName fileMap = do
IdeResultOk <$> makeAdditiveDiffResult file newContents fileMap
where
applyLens :: L.HasBuildInfo a => Lens' GenericPackageDescription [(b, CondTree v c a)]
@ -217,7 +283,7 @@ editCabalPackage file modulePath pkgName fileMap = do
updateTree = mapIfHasModule modulePath (addDep pkgName)
mapIfHasModule :: L.HasBuildInfo a => FilePath -> (a -> a) -> CondTree v c a -> CondTree v c a
mapIfHasModule :: L.HasBuildInfo a => ModulePath -> (a -> a) -> CondTree v c a -> CondTree v c a
mapIfHasModule modFp f = mapTreeData g
where g x
| null (sourceDirs x) = f x
@ -226,18 +292,26 @@ editCabalPackage file modulePath pkgName fileMap = do
hasThisModule = any (`isPrefixOf` modFp) . sourceDirs
sourceDirs x = x ^. L.buildInfo . L.hsSourceDirs
addDep :: L.HasBuildInfo a => String -> a -> a
-- | Add the given package name to the cabal file.
-- Package is appended to the dependency list.
addDep :: L.HasBuildInfo a => Package -> a -> a
addDep dep x = L.buildInfo . L.targetBuildDepends .~ newDeps $ x
where oldDeps = x ^. L.buildInfo . L.targetBuildDepends
-- Add it to the bottom of the dependencies list
newDeps = oldDeps ++ [Dependency (mkPackageName dep) anyVersion]
-- TODO: we could sort the depencies and then insert it,
-- or insert it in order iff the list is already sorted.
newDeps = oldDeps ++ [Dependency (mkPackageName (T.unpack dep)) anyVersion]
-- | Provide a code action to add a package to the local package.yaml or cabal file.
-- Reads from diagnostics the unknown import module path and searches for it on Hoogle.
-- If found, offer a code action to add the package to the local package description.
codeActionProvider :: CodeActionProvider
codeActionProvider plId docId _ context = do
mRootDir <- getRootPath
let J.List diags = context ^. J.diagnostics
pkgs = mapMaybe getAddablePackages diags
-- Search for packages that define the given module.
res <- mapM (bimapM return Hoogle.searchPackages) pkgs
actions <- catMaybes <$> mapM (uncurry (mkAddPackageAction mRootDir)) (concatPkgs res)
@ -246,7 +320,8 @@ codeActionProvider plId docId _ context = do
where
concatPkgs = concatMap (\(d, ts) -> map (d,) ts)
mkAddPackageAction :: Maybe FilePath -> J.Diagnostic -> T.Text -> IdeM (Maybe J.CodeAction)
-- | Create the Add Package Action with the given diagnostics and the found package name.
mkAddPackageAction :: Maybe FilePath -> J.Diagnostic -> Package -> IdeM (Maybe J.CodeAction)
mkAddPackageAction mRootDir diag pkgName = case (mRootDir, J.uriToFilePath (docId ^. J.uri)) of
(Just rootDir, Just docFp) -> do
let title = "Add " <> pkgName <> " as a dependency"
@ -255,11 +330,12 @@ codeActionProvider plId docId _ context = do
return $ Just (J.CodeAction title (Just J.CodeActionQuickFix) (Just (J.List [diag])) Nothing (Just cmd))
_ -> return Nothing
getAddablePackages :: J.Diagnostic -> Maybe (J.Diagnostic, T.Text)
getAddablePackages :: J.Diagnostic -> Maybe (J.Diagnostic, Package)
getAddablePackages diag@(J.Diagnostic _ _ _ (Just "ghcmod") msg _) = (diag,) <$> extractModuleName msg
getAddablePackages _ = Nothing
extractModuleName :: T.Text -> Maybe T.Text
-- | Extract a module name from an error message.
extractModuleName :: T.Text -> Maybe Package
extractModuleName msg
| T.isPrefixOf "Could not find module " msg = Just $ T.tail $ T.init nameAndQuotes
| T.isPrefixOf "Could not load module " msg = Just $ T.tail $ T.init nameAndQuotes
@ -267,6 +343,7 @@ extractModuleName msg
where line = head $ T.lines msg
nameAndQuotes = T.dropWhileEnd (/= '') $ T.dropWhile (/= '') line
-- Example error messages
{- GHC 8.6.2 error message is
"Could not load module \8216Data.Text\8217\n" ++

View File

@ -32,12 +32,17 @@ pragmasDescriptor plId = PluginDescriptor
-- ---------------------------------------------------------------------
-- | Parameters for the addPragma PluginCommand.
data AddPragmaParams = AddPragmaParams
{ file :: Uri
, pragma :: T.Text
{ file :: Uri -- ^ Uri of the file to add the pragma to
, pragma :: T.Text -- ^ Name of the Pragma to add
}
deriving (Show, Eq, Generics.Generic, ToJSON, FromJSON)
-- | Add a Pragma to the given URI at the top of the file.
-- Pragma is added to the first line of the Uri.
-- It is assumed that the pragma name is a valid pragma,
-- thus, not validated.
addPragmaCmd :: CommandFunc AddPragmaParams J.WorkspaceEdit
addPragmaCmd = CmdSync $ \(AddPragmaParams uri pragmaName) -> do
let
@ -53,15 +58,20 @@ addPragmaCmd = CmdSync $ \(AddPragmaParams uri pragmaName) -> do
-- ---------------------------------------------------------------------
-- | Offer to add a missing Language Pragma to the top of a file.
-- Pragmas are defined by a curated list of known pragmas, see 'possiblePragmas'.
codeActionProvider :: CodeActionProvider
codeActionProvider plId docId _ (J.CodeActionContext (J.List diags) _monly) = do
cmds <- mapM mkCommand pragmas
return $ IdeResultOk cmds
where
-- Filter diagnostics that are from ghcmod
ghcDiags = filter (\d -> d ^. J.source == Just "ghcmod") diags
-- Get all potential Pragmas for all diagnostics.
pragmas = concatMap (\d -> findPragma (d ^. J.message)) ghcDiags
mkCommand pragmaName = do
let
-- | Code Action for the given command.
codeAction :: J.Command -> J.CodeAction
codeAction cmd = J.CodeAction title (Just J.CodeActionQuickFix) (Just (J.List [])) Nothing (Just cmd)
title = "Add \"" <> pragmaName <> "\""
@ -71,6 +81,7 @@ codeActionProvider plId docId _ (J.CodeActionContext (J.List diags) _monly) = do
-- ---------------------------------------------------------------------
-- | Find all Pragmas are an infix of the search term.
findPragma :: T.Text -> [T.Text]
findPragma str = concatMap check possiblePragmas
where
@ -78,6 +89,8 @@ findPragma str = concatMap check possiblePragmas
-- ---------------------------------------------------------------------
-- | Possible Pragma names.
-- Is non-exhaustive, and may be extended.
possiblePragmas :: [T.Text]
possiblePragmas =
[

View File

@ -16,7 +16,7 @@ module Haskell.Ide.Engine.Support.HieExtras
, findTypeDef
, showName
, safeTyThingId
, PosPrefixInfo(..)
, VFS.PosPrefixInfo(..)
, HarePoint(..)
, customOptions
-- , splitCaseCmd'
@ -57,6 +57,7 @@ import qualified Haskell.Ide.Engine.Support.Fuzzy as Fuzzy
import HscTypes
import qualified Language.Haskell.LSP.Types as J
import qualified Language.Haskell.LSP.Types.Lens as J
import qualified Language.Haskell.LSP.VFS as VFS
import Language.Haskell.Refact.API (showGhc)
import Language.Haskell.Refact.Utils.MonadFunctions
import Name
@ -180,24 +181,6 @@ safeTyThingId _ = Nothing
-- Associates a module's qualifier with its members
type QualCompls = Map.Map T.Text [CompItem]
-- | Describes the line at the current cursor position
data PosPrefixInfo = PosPrefixInfo
{ fullLine :: T.Text
-- ^ The full contents of the line the cursor is at
, prefixModule :: T.Text
-- ^ If any, the module name that was typed right before the cursor position.
-- For example, if the user has typed "Data.Maybe.from", then this property
-- will be "Data.Maybe"
, prefixText :: T.Text
-- ^ The word right before the cursor position, after removing the module part.
-- For example if the user has typed "Data.Maybe.from",
-- then this property will be "from"
, cursorPos :: J.Position
-- ^ The cursor position
}
data CachedCompletions = CC
{ allModNamesAsNS :: [T.Text]
, unqualCompls :: [CompItem]
@ -331,7 +314,7 @@ instance ModuleCache CachedCompletions where
newtype WithSnippets = WithSnippets Bool
-- | Returns the cached completions for the given module and position.
getCompletions :: Uri -> PosPrefixInfo -> WithSnippets -> IdeM (IdeResult [J.CompletionItem])
getCompletions :: Uri -> VFS.PosPrefixInfo -> WithSnippets -> IdeM (IdeResult [J.CompletionItem])
getCompletions uri prefixInfo (WithSnippets withSnippets) =
pluginGetFile "getCompletions: " uri $ \file -> do
let snippetLens = (^? J.textDocument
@ -349,7 +332,7 @@ getCompletions uri prefixInfo (WithSnippets withSnippets) =
, J._insertText = Nothing
}
PosPrefixInfo { fullLine, prefixModule, prefixText } = prefixInfo
VFS.PosPrefixInfo { VFS.fullLine, VFS.prefixModule, VFS.prefixText } = prefixInfo
debugm $ "got prefix" ++ show (prefixModule, prefixText)
let enteredQual = if T.null prefixModule then "" else prefixModule <> "."
fullPrefix = enteredQual <> prefixText
@ -366,7 +349,7 @@ getCompletions uri prefixInfo (WithSnippets withSnippets) =
^
-}
pos =
let newPos = cursorPos prefixInfo
let newPos = VFS.cursorPos prefixInfo
Position l c = fromMaybe newPos (newPosToOld newPos)
typeStuff = [isSpace, (`elem` (">-." :: String))]
stripTypeStuff = T.dropWhileEnd (\x -> any (\f -> f x) typeStuff)

View File

@ -27,7 +27,6 @@ import qualified Data.Aeson as A
import Control.Monad.STM
import Data.Aeson ( (.=) )
import qualified Data.ByteString.Lazy as BL
import Data.Char (isUpper, isAlphaNum)
import Data.Coerce (coerce)
import Data.Default
import Data.Foldable
@ -40,6 +39,7 @@ import qualified Data.SortedList as SL
import qualified Data.Text as T
import Data.Text.Encoding
import Haskell.Ide.Engine.Config
import qualified Haskell.Ide.Engine.Ghc as HIE
import Haskell.Ide.Engine.LSP.CodeActions
import Haskell.Ide.Engine.LSP.Reactor
import Haskell.Ide.Engine.MonadFunctions
@ -70,8 +70,8 @@ import qualified Language.Haskell.LSP.Types.Lens as J
import qualified Language.Haskell.LSP.Utility as U
import qualified Language.Haskell.LSP.VFS as VFS
import System.Exit
import qualified System.Log.Logger as L
import qualified Yi.Rope as Yi
import qualified System.Log.Logger as L
import qualified Data.Rope.UTF16 as Rope
import Outputable hiding ((<>))
@ -207,29 +207,10 @@ configVal field = field <$> getClientConfig
getPrefixAtPos :: (MonadIO m, MonadReader REnv m)
=> Uri -> Position -> m (Maybe Hie.PosPrefixInfo)
getPrefixAtPos uri pos@(Position l c) = do
getPrefixAtPos uri pos = do
mvf <- liftIO =<< asksLspFuncs Core.getVirtualFileFunc <*> pure uri
case mvf of
Just (VFS.VirtualFile _ yitext _) ->
return $ Just $ fromMaybe (Hie.PosPrefixInfo "" "" "" pos) $ do
let headMaybe [] = Nothing
headMaybe (x:_) = Just x
lastMaybe [] = Nothing
lastMaybe xs = Just $ last xs
curLine <- headMaybe $ Yi.lines $ snd $ Yi.splitAtLine l yitext
let beforePos = Yi.take c curLine
curWord <- case Yi.last beforePos of
Just ' ' -> Just "" -- don't count abc as the curword in 'abc '
_ -> Yi.toText <$> lastMaybe (Yi.words beforePos)
let parts = T.split (=='.')
$ T.takeWhileEnd (\x -> isAlphaNum x || x `elem` ("._'"::String)) curWord
case reverse parts of
[] -> Nothing
(x:xs) -> do
let modParts = dropWhile (not . isUpper . T.head)
$ reverse $ filter (not .T.null) xs
modName = T.intercalate "." modParts
return $ Hie.PosPrefixInfo (Yi.toText curLine) modName x pos
Just vf -> VFS.getCompletionPrefix pos vf
Nothing -> return Nothing
-- ---------------------------------------------------------------------
@ -735,7 +716,7 @@ reactor inp diagIn = do
doc = params ^. J.textDocument . J.uri
withDocumentContents (req ^. J.id) doc $ \text ->
let callback = reactorSend . RspDocumentFormatting . Core.makeResponseMessage req . J.List
hreq = IReq tn (req ^. J.id) callback $ lift $ provider text doc FormatDocument (params ^. J.options)
hreq = IReq tn (req ^. J.id) callback $ lift $ provider text doc FormatText (params ^. J.options)
in makeRequest hreq
-- -------------------------------
@ -825,7 +806,7 @@ withDocumentContents reqId uri f = do
(J.responseId reqId)
J.InvalidRequest
"Document was not open"
Just (VFS.VirtualFile _ txt _) -> f (Yi.toText txt)
Just (VFS.VirtualFile _ txt _) -> f (Rope.toText txt)
-- | Get the currently configured formatter provider.
-- The currently configured formatter provider is defined in @Config@ by PluginId.

View File

@ -7,26 +7,28 @@ extra-deps:
- ./submodules/HaRe
- ./submodules/brittany
- ./submodules/cabal-helper
- ./submodules/floskell
- ./submodules/ghc-mod
- ./submodules/ghc-mod/core
- ./submodules/ghc-mod/ghc-project-types
# - brittany-0.11.0.0
- butcher-1.3.1.1
- cabal-plan-0.3.0.0
- constrained-dynamic-0.1.0.0
- czipwith-1.0.1.0
- floskell-0.10.0
- ghc-exactprint-0.5.8.2
- haddock-api-2.18.1
- haddock-library-1.4.4
- haskell-lsp-0.10.0.0
- haskell-lsp-types-0.10.0.0
- haskell-lsp-0.13.0.0
- haskell-lsp-types-0.13.0.0@rev:2
- hlint-2.0.11
- hsimport-0.8.6
- lsp-test-0.5.1.3
- hsimport-0.10.0
- lsp-test-0.5.2.3
- monad-dijkstra-0.1.1.2
- mtl-2.2.2
- pretty-show-1.8.2
- rope-utf16-splay-0.3.1.0
- sorted-list-0.2.1.0
- syz-0.2.0.0
- yaml-0.8.32

View File

@ -7,9 +7,9 @@ extra-deps:
- ./submodules/HaRe
- ./submodules/brittany
- ./submodules/cabal-helper
- ./submodules/floskell
- ./submodules/ghc-mod
- ./submodules/ghc-mod/core
- ./submodules/ghc-mod/ghc-project-types
# - brittany-0.11.0.0
- butcher-1.3.1.1
@ -17,19 +17,21 @@ extra-deps:
- conduit-parse-0.2.1.0
- constrained-dynamic-0.1.0.0
- czipwith-1.0.1.0
- floskell-0.10.0
- ghc-exactprint-0.5.8.2
- haddock-api-2.18.1
- haddock-library-1.4.4
- haskell-lsp-0.10.0.0
- haskell-lsp-types-0.10.0.0
- haskell-lsp-0.13.0.0
- haskell-lsp-types-0.13.0.0@rev:2
- haskell-src-exts-1.21.0
- haskell-src-exts-util-0.2.5
- hlint-2.1.16
- hlint-2.1.17 # last hlint supporting GHC 8.2
- hoogle-5.0.17.6
- hsimport-0.8.8
- lsp-test-0.5.1.3
- lsp-test-0.5.2.3
- monad-dijkstra-0.1.1.2
- pretty-show-1.8.2
- rope-utf16-splay-0.3.1.0
- sorted-list-0.2.1.0
- syz-0.2.0.0

View File

@ -7,27 +7,30 @@ extra-deps:
- ./submodules/HaRe
- ./submodules/brittany
- ./submodules/cabal-helper
- ./submodules/floskell
- ./submodules/ghc-mod
- ./submodules/ghc-mod/core
- ./submodules/ghc-mod/ghc-project-types
# - brittany-0.11.0.0
- base-compat-0.9.3
- cabal-plan-0.3.0.0
- constrained-dynamic-0.1.0.0
- floskell-0.10.0
- ghc-exactprint-0.5.8.2
- ghc-lib-parser-0.20190523
- haddock-api-2.20.0
- haddock-library-1.6.0
- haskell-lsp-0.10.0.0
- haskell-lsp-types-0.10.0.0
- haskell-lsp-0.13.0.0
- haskell-lsp-types-0.13.0.0@rev:2
- haskell-src-exts-1.21.0
- haskell-src-exts-util-0.2.5
- hlint-2.1.16
- hlint-2.1.22
- hoogle-5.0.17.6
- hsimport-0.8.8
- lsp-test-0.5.1.3
- hsimport-0.10.0
- lsp-test-0.5.2.3
- monad-dijkstra-0.1.1.2
- pretty-show-1.8.2
- rope-utf16-splay-0.3.1.0
- syz-0.2.0.0
- temporary-1.2.1.1
- windns-0.1.0.0

View File

@ -7,26 +7,29 @@ extra-deps:
- ./submodules/HaRe
- ./submodules/brittany
- ./submodules/cabal-helper
- ./submodules/floskell
- ./submodules/ghc-mod
- ./submodules/ghc-mod/core
- ./submodules/ghc-mod/ghc-project-types
- base-compat-0.9.3
- cabal-plan-0.3.0.0
- constrained-dynamic-0.1.0.0
- floskell-0.10.0
- ghc-exactprint-0.5.8.2
- ghc-lib-parser-0.20190523
- haddock-api-2.20.0
- haddock-library-1.6.0
- haskell-lsp-0.10.0.0
- haskell-lsp-types-0.10.0.0
- haskell-lsp-0.13.0.0
- haskell-lsp-types-0.13.0.0@rev:2
- haskell-src-exts-1.21.0
- haskell-src-exts-util-0.2.5
- hlint-2.1.16
- hlint-2.1.22
- hoogle-5.0.17.6
- hsimport-0.8.8
- lsp-test-0.5.1.3
- hsimport-0.10.0
- lsp-test-0.5.2.3
- monad-dijkstra-0.1.1.2
- pretty-show-1.8.2
- rope-utf16-splay-0.3.1.0
- syz-0.2.0.0
- temporary-1.2.1.1

View File

@ -7,27 +7,30 @@ extra-deps:
- ./submodules/HaRe
- ./submodules/brittany
- ./submodules/cabal-helper
- ./submodules/floskell
- ./submodules/ghc-mod
- ./submodules/ghc-mod/core
- ./submodules/ghc-mod/ghc-project-types
# - brittany-0.11.0.0
- cabal-plan-0.4.0.0
- constrained-dynamic-0.1.0.0
- floskell-0.10.0
- ghc-exactprint-0.5.8.2
- ghc-lib-parser-0.20190523
- haddock-api-2.20.0
- haddock-library-1.6.0
- haskell-lsp-0.10.0.0
- haskell-lsp-types-0.10.0.0
- haskell-lsp-0.13.0.0
- haskell-lsp-types-0.13.0.0
- haskell-src-exts-1.21.0
- haskell-src-exts-util-0.2.5
- hlint-2.1.16
- hlint-2.1.22
- hoogle-5.0.17.6
- hsimport-0.8.8
- lsp-test-0.5.1.3
- hsimport-0.10.0
- lsp-test-0.5.2.3
- monad-dijkstra-0.1.1.2
- optparse-simple-0.1.0
- pretty-show-1.9.5
- rope-utf16-splay-0.3.1.0
- syz-0.2.0.0
- temporary-1.2.1.1

View File

@ -7,9 +7,9 @@ extra-deps:
- ./submodules/HaRe
- ./submodules/brittany
- ./submodules/cabal-helper
- ./submodules/floskell
- ./submodules/ghc-mod
- ./submodules/ghc-mod/core
- ./submodules/ghc-mod/ghc-project-types
- apply-refact-0.6.0.0
- butcher-1.3.2.1
@ -18,21 +18,24 @@ extra-deps:
- constrained-dynamic-0.1.0.0
- czipwith-1.0.1.1
- data-tree-print-0.1.0.2
- floskell-0.10.0
- ghc-lib-parser-0.20190523
- haddock-api-2.21.0
- haskell-lsp-0.10.0.0
- haskell-lsp-types-0.10.0.0
- haskell-lsp-0.13.0.0
- haskell-lsp-types-0.13.0.0
- haskell-src-exts-1.21.0
- haskell-src-exts-util-0.2.5
- hlint-2.1.16
- hlint-2.1.22
- hoogle-5.0.17.6
- hsimport-0.8.8
- lsp-test-0.5.1.3
- hsimport-0.10.0
- lsp-test-0.5.2.3
- monad-dijkstra-0.1.1.2
- monad-memo-0.4.1
- monoid-subclasses-0.4.6.1
- multistate-0.8.0.1
- primes-0.2.1.0
- resolv-0.1.1.2
- rope-utf16-splay-0.3.1.0
- syz-0.2.0.0
- temporary-1.2.1.1
- yaml-0.8.32

View File

@ -7,25 +7,28 @@ extra-deps:
- ./submodules/HaRe
- ./submodules/brittany
- ./submodules/cabal-helper
- ./submodules/floskell
- ./submodules/ghc-mod
- ./submodules/ghc-mod/core
- ./submodules/ghc-mod/ghc-project-types
- butcher-1.3.2.1
- cabal-plan-0.4.0.0
- constrained-dynamic-0.1.0.0
- floskell-0.10.0
- ghc-lib-parser-0.20190523
- haddock-api-2.21.0
- haskell-lsp-0.10.0.0
- haskell-lsp-types-0.10.0.0
- haskell-lsp-0.13.0.0
- haskell-lsp-types-0.13.0.0
- haskell-src-exts-1.21.0
- haskell-src-exts-util-0.2.5
- hlint-2.1.16
- hlint-2.1.22
- hoogle-5.0.17.6
- hsimport-0.8.8
- lsp-test-0.5.1.3
- hsimport-0.10.0
- lsp-test-0.5.2.3
- monad-dijkstra-0.1.1.2
- monad-memo-0.4.1
- multistate-0.8.0.1
- rope-utf16-splay-0.3.1.0
- syz-0.2.0.0
- temporary-1.2.1.1
- yaml-0.8.32

View File

@ -8,29 +8,30 @@ extra-deps:
- ./submodules/HaRe
- ./submodules/brittany
- ./submodules/cabal-helper
- ./submodules/floskell
- ./submodules/ghc-mod
- ./submodules/ghc-mod/core
- ./submodules/floskell
- ./haskell-lsp
- ./submodules/ghc-mod/ghc-project-types
- bytestring-trie-0.2.5.0
- butcher-1.3.2.1
- cabal-plan-0.4.0.0
- constrained-dynamic-0.1.0.0
- floskell-0.10.0
- ghc-lib-parser-0.20190523
- haddock-api-2.21.0
- haskell-lsp-0.10.0.0
- haskell-lsp-types-0.10.0.0
- haskell-lsp-0.13.0.0
- haskell-lsp-types-0.13.0.0
- haskell-src-exts-1.21.0
- haskell-src-exts-util-0.2.5
- hlint-2.1.16
- hlint-2.1.22
- hoogle-5.0.17.6
- hsimport-0.8.8
- lsp-test-0.5.1.3
- hsimport-0.10.0
- lsp-test-0.5.2.3
- monad-dijkstra-0.1.1.2
- monad-memo-0.4.1
- multistate-0.8.0.1
- optparse-simple-0.1.0
- rope-utf16-splay-0.3.1.0
- syz-0.2.0.0
- temporary-1.2.1.1
- yaml-0.8.32

View File

@ -1,4 +1,4 @@
resolver: lts-13.15 # GHC 8.6.4
resolver: lts-13.19 # GHC 8.6.4
packages:
- .
- hie-plugin-api
@ -8,28 +8,27 @@ extra-deps:
- ./submodules/HaRe
- ./submodules/brittany
- ./submodules/cabal-helper
- ./submodules/floskell
- ./submodules/ghc-mod
- ./submodules/ghc-mod/core
- ./haskell-lsp
- ./lsp-test
- butcher-1.3.2.1
- bytestring-trie-0.2.5.0
- cabal-plan-0.4.0.0
- constrained-dynamic-0.1.0.0
- floskell-0.10.0
- ghc-lib-parser-0.20190523
- haddock-api-2.22.0
- haskell-lsp-0.10.0.0
- haskell-lsp-types-0.10.0.0
- haskell-lsp-0.13.0.0
- haskell-lsp-types-0.13.0.0
- haskell-src-exts-1.21.0
- haskell-src-exts-util-0.2.5
- hlint-2.1.16
- hlint-2.1.22
- hoogle-5.0.17.6
- hsimport-0.8.8
- lsp-test-0.5.1.3
- hsimport-0.10.0
- lsp-test-0.5.2.3
- monad-dijkstra-0.1.1.2@rev:1
- monad-memo-0.4.1
- multistate-0.8.0.1
- rope-utf16-splay-0.3.1.0
- syz-0.2.0.0
- temporary-1.2.1.1
- yaml-0.8.32

47
stack-8.6.5.yaml Normal file
View File

@ -0,0 +1,47 @@
resolver: nightly-2019-04-30 # First GHC 8.6.5
packages:
- .
- hie-plugin-api
extra-deps:
- ./submodules/HaRe
- ./submodules/brittany
- ./submodules/cabal-helper
- ./submodules/ghc-mod
- ./submodules/ghc-mod/core
- ./submodules/ghc-mod/ghc-project-types
- ansi-terminal-0.8.2
- butcher-1.3.2.1
- cabal-plan-0.4.0.0
- constrained-dynamic-0.1.0.0
- deque-0.2.7
- floskell-0.10.0
- ghc-exactprint-0.5.8.2
- ghc-lib-parser-0.20190523
- haddock-api-2.22.0
- haskell-lsp-0.13.0.0
- haskell-lsp-types-0.13.0.0
- hlint-2.1.22
- hsimport-0.10.0
- lsp-test-0.5.2.3
- monad-dijkstra-0.1.1.2@rev:1
- monad-memo-0.4.1
- multistate-0.8.0.1
- rope-utf16-splay-0.3.1.0
- syz-0.2.0.0
- temporary-1.2.1.1
- yaml-0.8.32
flags:
haskell-ide-engine:
pedantic: true
hie-plugin-api:
pedantic: true
# allow-newer: true
nix:
packages: [ icu libcxx zlib ]
concurrent-tests: false

View File

@ -1,4 +1,4 @@
resolver: nightly-2019-04-05 # GHC 8.6.4
resolver: nightly-2019-04-30 # First GHC 8.6.5
packages:
- .
- hie-plugin-api
@ -8,10 +8,9 @@ extra-deps:
- ./submodules/HaRe
- ./submodules/brittany
- ./submodules/cabal-helper
- ./submodules/floskell
- ./submodules/ghc-mod
- ./submodules/ghc-mod/core
- ./haskell-lsp
- ./submodules/ghc-mod/ghc-project-types
- ansi-terminal-0.8.2
- butcher-1.3.2.1
@ -19,17 +18,19 @@ extra-deps:
- cabal-plan-0.4.0.0
- constrained-dynamic-0.1.0.0
- deque-0.2.7
- floskell-0.10.0
- ghc-exactprint-0.5.8.2
- ghc-lib-parser-0.20190523
- haddock-api-2.22.0
- haskell-lsp-0.10.0.0
- haskell-lsp-types-0.10.0.0
- haskell-src-exts-util-0.2.5
- hlint-2.1.16
- hsimport-0.8.8
- lsp-test-0.5.1.3
- haskell-lsp-0.13.0.0
- haskell-lsp-types-0.13.0.0
- hlint-2.1.22
- hsimport-0.10.0
- lsp-test-0.5.2.3
- monad-dijkstra-0.1.1.2@rev:1
- monad-memo-0.4.1
- multistate-0.8.0.1
- rope-utf16-splay-0.3.1.0
- syz-0.2.0.0
- temporary-1.2.1.1
- yaml-0.8.32

@ -1 +1 @@
Subproject commit 53979f062bebcaa132390d1fd0cec74a51662952
Subproject commit dfab0004320c28e1aa0331a507a9428952f2c938

@ -1 +1 @@
Subproject commit 8fecf6a7754424ed1653dce632382707e8f03499
Subproject commit eafed5e8c1d82b8daa35775b52361132f2e70261

@ -1 +0,0 @@
Subproject commit 9b9fe8b651b432209b7d7c170697cb6400a41185

@ -1 +1 @@
Subproject commit 1544779ac040ca30d5b3e0c5b9156e2007a51c4d
Subproject commit 43476965b5d715f7fcdadd9e14d5e0c53cdb9385

View File

@ -29,7 +29,7 @@ spec = do
skipMany anyNotification
hoverRsp <- message :: Session HoverResponse
liftIO $ hoverRsp ^? result . _Just . _Just . contents `shouldBe` (Just HoverContentsEmpty)
liftIO $ hoverRsp ^? result . _Just . _Just . contents `shouldBe` Nothing
liftIO $ hoverRsp ^. LSP.id `shouldBe` responseId id1
id2 <- sendRequest TextDocumentDocumentSymbol (DocumentSymbolParams doc)

View File

@ -1,4 +1,5 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE CPP #-}
module FunctionalCodeActionsSpec where
@ -9,6 +10,7 @@ import Control.Monad.IO.Class
import Data.Aeson
import Data.Default
import qualified Data.HashMap.Strict as HM
import qualified Data.Set as Set
import Data.Maybe
import Data.Monoid ((<>))
import qualified Data.Text as T
@ -125,63 +127,62 @@ spec = describe "code actions" $ do
liftIO $ x `shouldBe` "foo = putStrLn \"world\""
describe "import suggestions" $ do
it "works with 3.8 code action kinds" $ runSession hieCommand fullCaps "test/testdata" $ do
doc <- openDoc "CodeActionImport.hs" "haskell"
-- ignore the first empty hlint diagnostic publish
[_,diag:_] <- count 2 waitForDiagnostics
liftIO $ diag ^. L.message `shouldBe` "Variable not in scope: when :: Bool -> IO () -> IO ()"
actionsOrCommands <- getAllCodeActions doc
let actns = map fromAction actionsOrCommands
liftIO $ do
head actns ^. L.title `shouldBe` "Import module Control.Monad"
forM_ actns $ \a -> do
a ^. L.kind `shouldBe` Just CodeActionQuickFix
a ^. L.command `shouldSatisfy` isJust
a ^. L.edit `shouldBe` Nothing
let hasOneDiag (Just (List [_])) = True
hasOneDiag _ = False
a ^. L.diagnostics `shouldSatisfy` hasOneDiag
length actns `shouldBe` 5
executeCodeAction (head actns)
contents <- getDocumentEdit doc
liftIO $ contents `shouldBe` "import Control.Monad\nmain :: IO ()\nmain = when True $ putStrLn \"hello\""
it "formats with brittany" $ runSession hieCommand fullCaps "test/testdata" $ do
doc <- openDoc "CodeActionImportBrittany.hs" "haskell"
_ <- waitForDiagnosticsSource "ghcmod"
actionsOrCommands <- getAllCodeActions doc
let action:_ = map fromAction actionsOrCommands
executeCodeAction action
contents <- getDocumentEdit doc
liftIO $ do
let l1:l2:_ = T.lines contents
l1 `shouldBe` "import qualified Data.Maybe"
l2 `shouldBe` "import Control.Monad"
it "respects format config" $ runSession hieCommand fullCaps "test/testdata" $ do
doc <- openDoc "CodeActionImportBrittany.hs" "haskell"
_ <- waitForDiagnosticsSource "ghcmod"
let config = def { formatOnImportOn = False }
sendNotification WorkspaceDidChangeConfiguration (DidChangeConfigurationParams (toJSON config))
actionsOrCommands <- getAllCodeActions doc
let action:_ = map fromAction actionsOrCommands
executeCodeAction action
contents <- getDocumentEdit doc
liftIO $ do
let l1:l2:_ = T.lines contents
l1 `shouldBe` "import qualified Data.Maybe"
l2 `shouldBe` "import Control.Monad"
hsImportSpec "brittany"
[ -- Expected output for simple format.
[ "import qualified Data.Maybe"
, "import Control.Monad"
, "main :: IO ()"
, "main = when True $ putStrLn \"hello\""
]
, -- Use an import list and format the output.
[ "import qualified Data.Maybe"
, "import Control.Monad ( when )"
, "main :: IO ()"
, "main = when True $ putStrLn \"hello\""
]
, -- Multiple import lists, should not introduce multiple newlines.
[ "import System.IO ( stdout"
, " , hPutStrLn"
, " )"
, "import Control.Monad ( when )"
, "import Data.Maybe ( fromMaybe )"
, "-- | Main entry point to the program"
, "main :: IO ()"
, "main ="
, " when True"
, " $ hPutStrLn stdout"
, " $ fromMaybe \"Good night, World!\" (Just \"Hello, World!\")"
]
]
hsImportSpec "floskell"
[ -- Expected output for simple format.
[ "import qualified Data.Maybe"
, "import Control.Monad"
, "main :: IO ()"
, "main = when True $ putStrLn \"hello\""
]
, -- Use an import list and format the output.
[ "import qualified Data.Maybe"
, "import Control.Monad (when)"
, "main :: IO ()"
, "main = when True $ putStrLn \"hello\""
]
, -- Multiple import lists, should not introduce multiple newlines.
[ "import System.IO (stdout, hPutStrLn)"
, "import Control.Monad (when)"
, "import Data.Maybe (fromMaybe)"
, "-- | Main entry point to the program"
, "main :: IO ()"
, "main ="
, " when True"
, " $ hPutStrLn stdout"
, " $ fromMaybe \"Good night, World!\" (Just \"Hello, World!\")"
]
]
describe "add package suggestions" $ do
it "adds to .cabal files" $ runSession hieCommand fullCaps "test/testdata/addPackageTest/cabal" $ do
-- Only execute this test with ghc 8.4.4, below seems to be broken in the package.
#if (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(8,4,0,0)))
it "adds to .cabal files" $ runSession hieCommand fullCaps "test/testdata/addPackageTest/cabal-exe" $ do
doc <- openDoc "AddPackage.hs" "haskell"
-- ignore the first empty hlint diagnostic publish
@ -205,9 +206,9 @@ spec = describe "code actions" $ do
contents <- getDocumentEdit . TextDocumentIdentifier =<< getDocUri "add-package-test.cabal"
liftIO $ T.lines contents `shouldSatisfy` \x -> any (\l -> "text -any" `T.isSuffixOf` (x !! l)) [15, 16]
#endif
it "adds to hpack package.yaml files" $
runSession hieCommand fullCaps "test/testdata/addPackageTest/hpack" $ do
runSession hieCommand fullCaps "test/testdata/addPackageTest/hpack-exe" $ do
doc <- openDoc "app/Asdf.hs" "haskell"
-- ignore the first empty hlint diagnostic publish
@ -231,9 +232,35 @@ spec = describe "code actions" $ do
contents <- getDocumentEdit . TextDocumentIdentifier =<< getDocUri "package.yaml"
liftIO $ do
T.lines contents !! 33 `shouldSatisfy` T.isSuffixOf "zlib"
T.lines contents !! 12 `shouldNotSatisfy` T.isSuffixOf "zlib"
T.lines contents !! 13 `shouldNotSatisfy` T.isSuffixOf "zlib"
T.lines contents !! 3 `shouldSatisfy` T.isSuffixOf "zlib"
T.lines contents !! 21 `shouldNotSatisfy` T.isSuffixOf "zlib"
it "adds to hpack package.yaml files if both are present" $
runSession hieCommand fullCaps "test/testdata/addPackageTest/hybrid-exe" $ do
doc <- openDoc "app/Asdf.hs" "haskell"
-- ignore the first empty hlint diagnostic publish
[_,diag:_] <- count 2 waitForDiagnostics
let preds = [ T.isPrefixOf "Could not load module Codec.Compression.GZip"
, T.isPrefixOf "Could not find module Codec.Compression.GZip"
]
in liftIO $ diag ^. L.message `shouldSatisfy` \x -> any (\f -> f x) preds
mActions <- getAllCodeActions doc
let allActions = map fromAction mActions
action = head allActions
liftIO $ do
action ^. L.title `shouldBe` "Add zlib as a dependency"
forM_ allActions $ \a -> a ^. L.kind `shouldBe` Just CodeActionQuickFix
forM_ allActions $ \a -> a ^. L.command . _Just . L.command `shouldSatisfy` T.isSuffixOf "package:add"
executeCodeAction action
contents <- getDocumentEdit . TextDocumentIdentifier =<< getDocUri "package.yaml"
liftIO $
T.lines contents !! 21 `shouldSatisfy` T.isSuffixOf "zlib"
-- -----------------------------------
@ -320,10 +347,11 @@ spec = describe "code actions" $ do
contents <- documentContents doc
liftIO $ contents `shouldBe`
"module TypedHoles where\n\
\foo :: [Int] -> Int\n\
\foo x = " <> suggestion
liftIO $ contents `shouldBe` T.concat
[ "module TypedHoles where\n"
, "foo :: [Int] -> Int\n"
, "foo x = " <> suggestion
]
it "shows more suggestions" $
runSession hieCommand fullCaps "test/testdata" $ do
@ -473,6 +501,178 @@ spec = describe "code actions" $ do
kinds `shouldSatisfy` all (Just CodeActionRefactorInline ==)
-- ---------------------------------------------------------------------
-- Parameterized HsImport Spec.
-- ---------------------------------------------------------------------
hsImportSpec :: T.Text -> [[T.Text]]-> Spec
hsImportSpec formatterName [e1, e2, e3] =
describe ("Execute HsImport with formatter " <> T.unpack formatterName) $ do
it "works with 3.8 code action kinds" $ runSession hieCommand fullCaps "test/testdata" $ do
doc <- openDoc "CodeActionImport.hs" "haskell"
-- No Formatting:
let config = def { formattingProvider = "none" }
sendNotification WorkspaceDidChangeConfiguration (DidChangeConfigurationParams (toJSON config))
-- ignore the first empty hlint diagnostic publish
[_,diag:_] <- count 2 waitForDiagnostics
liftIO $ diag ^. L.message `shouldBe` "Variable not in scope: when :: Bool -> IO () -> IO ()"
actionsOrCommands <- getAllCodeActions doc
let actns = map fromAction actionsOrCommands
liftIO $ do
head actns ^. L.title `shouldBe` "Import module Control.Monad"
head (tail actns) ^. L.title `shouldBe` "Import module Control.Monad (when)"
forM_ actns $ \a -> do
a ^. L.kind `shouldBe` Just CodeActionQuickFix
a ^. L.command `shouldSatisfy` isJust
a ^. L.edit `shouldBe` Nothing
let hasOneDiag (Just (List [_])) = True
hasOneDiag _ = False
a ^. L.diagnostics `shouldSatisfy` hasOneDiag
length actns `shouldBe` 10
executeCodeAction (head actns)
contents <- getDocumentEdit doc
liftIO $ contents `shouldBe` "import Control.Monad\nmain :: IO ()\nmain = when True $ putStrLn \"hello\""
it "formats" $ runSession hieCommand fullCaps "test/testdata" $ do
doc <- openDoc "CodeActionImportBrittany.hs" "haskell"
_ <- waitForDiagnosticsSource "ghcmod"
let config = def { formattingProvider = formatterName }
sendNotification WorkspaceDidChangeConfiguration (DidChangeConfigurationParams (toJSON config))
actionsOrCommands <- getAllCodeActions doc
let action:_ = map fromAction actionsOrCommands
executeCodeAction action
contents <- getDocumentEdit doc
liftIO $ T.lines contents `shouldMatchList` e1
it "import-list formats" $ runSession hieCommand fullCaps "test/testdata" $ do
doc <- openDoc "CodeActionImportBrittany.hs" "haskell"
_ <- waitForDiagnosticsSource "ghcmod"
let config = def { formattingProvider = formatterName }
sendNotification WorkspaceDidChangeConfiguration (DidChangeConfigurationParams (toJSON config))
actionsOrCommands <- getAllCodeActions doc
let _:action:_ = map fromAction actionsOrCommands
executeCodeAction action
contents <- getDocumentEdit doc
liftIO $ T.lines contents `shouldMatchList` e2
it "multiple import-list formats" $ runSession hieCommand fullCaps "test/testdata" $ do
doc <- openDoc "CodeActionImportList.hs" "haskell"
let config = def { formattingProvider = formatterName }
sendNotification WorkspaceDidChangeConfiguration (DidChangeConfigurationParams (toJSON config))
let wantedCodeActionTitles = [ "Import module System.IO (hPutStrLn)"
, "Import module System.IO (stdout)"
, "Import module Control.Monad (when)"
, "Import module Data.Maybe (fromMaybe)"
]
executeAllCodeActions doc wantedCodeActionTitles
contents <- documentContents doc
liftIO $ Set.fromList (T.lines contents) `shouldBe` Set.fromList e3
it "respects format config, multiple import-list" $ runSession hieCommand fullCaps "test/testdata" $ do
doc <- openDoc "CodeActionImportList.hs" "haskell"
let config = def { formatOnImportOn = False, formattingProvider = formatterName }
sendNotification WorkspaceDidChangeConfiguration (DidChangeConfigurationParams (toJSON config))
let wantedCodeActionTitles = [ "Import module System.IO (hPutStrLn)"
, "Import module System.IO (stdout)"
, "Import module Control.Monad (when)"
, "Import module Data.Maybe (fromMaybe)"
]
executeAllCodeActions doc wantedCodeActionTitles
contents <- documentContents doc
liftIO $ Set.fromList (T.lines contents) `shouldBe`
Set.fromList
[ "import System.IO (stdout, hPutStrLn)"
, "import Control.Monad (when)"
, "import Data.Maybe (fromMaybe)"
, "-- | Main entry point to the program"
, "main :: IO ()"
, "main ="
, " when True"
, " $ hPutStrLn stdout"
, " $ fromMaybe \"Good night, World!\" (Just \"Hello, World!\")"
]
it "respects format config" $ runSession hieCommand fullCaps "test/testdata" $ do
doc <- openDoc "CodeActionImportBrittany.hs" "haskell"
_ <- waitForDiagnosticsSource "ghcmod"
let config = def { formatOnImportOn = False, formattingProvider = formatterName }
sendNotification WorkspaceDidChangeConfiguration (DidChangeConfigurationParams (toJSON config))
actionsOrCommands <- getAllCodeActions doc
let action:_ = map fromAction actionsOrCommands
executeCodeAction action
contents <- getDocumentEdit doc
liftIO $ do
let [l1, l2, l3, l4] = T.lines contents
l1 `shouldBe` "import qualified Data.Maybe"
l2 `shouldBe` "import Control.Monad"
l3 `shouldBe` "main :: IO ()"
l4 `shouldBe` "main = when True $ putStrLn \"hello\""
it ("import-list respects format config with " <> T.unpack formatterName) $ runSession hieCommand fullCaps "test/testdata" $ do
doc <- openDoc "CodeActionImportBrittany.hs" "haskell"
_ <- waitForDiagnosticsSource "ghcmod"
let config = def { formatOnImportOn = False, formattingProvider = formatterName }
sendNotification WorkspaceDidChangeConfiguration (DidChangeConfigurationParams (toJSON config))
actionsOrCommands <- getAllCodeActions doc
let _:action:_ = map fromAction actionsOrCommands
executeCodeAction action
contents <- getDocumentEdit doc
liftIO $ do
let [l1, l2, l3, l4] = T.lines contents
l1 `shouldBe` "import qualified Data.Maybe"
l2 `shouldBe` "import Control.Monad (when)"
l3 `shouldBe` "main :: IO ()"
l4 `shouldBe` "main = when True $ putStrLn \"hello\""
where
executeAllCodeActions :: TextDocumentIdentifier -> [T.Text] -> Session ()
executeAllCodeActions doc names =
replicateM_ (length names) $ do
_ <- waitForDiagnosticsSource "ghcmod"
executeCodeActionByName doc names
_ <- skipManyTill publishDiagnosticsNotification $ getDocumentEdit doc
waitForDiagnosticsSource "ghcmod"
executeCodeActionByName :: TextDocumentIdentifier -> [T.Text] -> Session ()
executeCodeActionByName doc names = do
actionsOrCommands <- getAllCodeActions doc
let allActions = map fromAction actionsOrCommands
let actions = filter (\actn -> actn ^. L.title `elem` names) allActions
case actions of
(action:_) -> executeCodeAction action
xs ->
error
$ "Found an unexpected amount of action. Expected 1, but got: "
++ show (length xs)
++ "\n. Titles: " ++ show (map (^. L.title) allActions)
-- Silence warnings
hsImportSpec formatter args =
error $ "Not the right amount of arguments for \"hsImportSpec ("
++ T.unpack formatter
++ ")\", expected 3, got "
++ show (length args)
-- ---------------------------------------------------------------------
fromAction :: CAResult -> CodeAction
fromAction (CACodeAction action) = action

View File

@ -21,7 +21,7 @@ spec = describe "hover" $
h ^. range `shouldBe` Just (Range (Position 1 16) (Position 1 19))
let
hasType (HoverContents (MarkupContent MkMarkdown s))
= "```haskell\nsum :: [Int] -> Int\n```" `T.isPrefixOf`s
= "\n```haskell\nsum :: [Int] -> Int\n```" `T.isPrefixOf`s
hasType _ = False
sumDoc = "The `sum` function computes the sum of the numbers of a structure."

View File

@ -4,7 +4,11 @@ module ProgressSpec where
import Control.Applicative.Combinators
import Control.Lens
import Control.Monad.IO.Class
import Data.Aeson
import Data.Default
import Haskell.Ide.Engine.Config
import Language.Haskell.LSP.Test
import Language.Haskell.LSP.Messages -- TODO: Move this into haskell-lsp-types
import Language.Haskell.LSP.Types
import qualified Language.Haskell.LSP.Types.Lens as L
import Language.Haskell.LSP.Types.Capabilities
@ -12,7 +16,7 @@ import Test.Hspec
import TestUtils
spec :: Spec
spec = describe "window/progress" $
spec = describe "window/progress" $ do
it "sends indefinite progress notifications" $
-- Testing that ghc-mod sends progress notifications
runSession hieCommand progressCaps "test/testdata" $ do
@ -23,7 +27,7 @@ spec = describe "window/progress" $
-- Initial hlint notifications
_ <- publishDiagnosticsNotification
startNotification <- message :: Session ProgressStartNotification
startNotification <- message :: Session ProgressStartNotification
liftIO $ do
startNotification ^. L.params . L.title `shouldBe` "Typechecking ApplyRefact2.hs"
startNotification ^. L.params . L.id `shouldBe` "0"
@ -40,17 +44,52 @@ spec = describe "window/progress" $
-- hlint notifications
_ <- publishDiagnosticsNotification
startNotification' <- message :: Session ProgressStartNotification
startNotification' <- message :: Session ProgressStartNotification
liftIO $ do
startNotification' ^. L.params . L.title `shouldBe` "Typechecking ApplyRefact2.hs"
startNotification' ^. L.params . L.id `shouldBe` "1"
doneNotification' <- message :: Session ProgressDoneNotification
liftIO $ doneNotification' ^. L.params . L.id `shouldBe` "1"
-- the ghc-mod diagnostics
const () <$> publishDiagnosticsNotification
it "sends indefinite progress notifications with liquid" $
-- Testing that Liquid Haskell sends progress notifications
runSession hieCommand progressCaps "test/testdata" $ do
doc <- openDoc "liquid/Evens.hs" "haskell"
skipMany loggingNotification
-- Initial hlint notifications
_ <- publishDiagnosticsNotification
_ <- message :: Session ProgressStartNotification
_ <- message :: Session ProgressDoneNotification
-- the ghc-mod diagnostics
_ <- publishDiagnosticsNotification
-- Enable liquid haskell plugin
let config = def { liquidOn = True, hlintOn = False }
sendNotification WorkspaceDidChangeConfiguration (DidChangeConfigurationParams (toJSON config))
-- Test liquid
sendNotification TextDocumentDidSave (DidSaveTextDocumentParams doc)
-- hlint notifications
_ <- publishDiagnosticsNotification
let startPred (NotProgressStart m) =
m ^. L.params . L.title == "Running Liquid Haskell on Evens.hs"
startPred _ = False
let donePred (NotProgressDone _) = True
donePred _ = False
_ <- skipManyTill anyMessage $ between (satisfy startPred) (satisfy donePred) $
many (satisfy (\x -> not (startPred x || donePred x)))
return ()
progressCaps :: ClientCapabilities
progressCaps = fullCaps { _window = Just (WindowClientCapabilities (Just True)) }

2
test/testdata/ApplyRefactError.hs vendored Normal file
View File

@ -0,0 +1,2 @@
foo :: forall a. (a -> a) -> a -> a
foo f x = f $ x

6
test/testdata/CodeActionImportList.hs vendored Normal file
View File

@ -0,0 +1,6 @@
-- | Main entry point to the program
main :: IO ()
main =
when True
$ hPutStrLn stdout
$ fromMaybe "Good night, World!" (Just "Hello, World!")

33
test/testdata/Types.hs vendored Normal file
View File

@ -0,0 +1,33 @@
module Types where
import Control.Applicative
foo :: Maybe Int -> Int
foo (Just x) = x
foo Nothing = 0
bar :: Maybe Int -> Int
bar x = case x of
Just y -> y + 1
Nothing -> 0
maybeMonad :: Maybe Int -> Maybe Int
maybeMonad x = do
y <- x
let z = return (y + 10)
b <- z
return (b + y)
funcTest :: (a -> a) -> a -> a
funcTest f a = f a
compTest :: (b -> c) -> (a -> b) -> a -> c
compTest f g = let h = f . g in h
monadStuff :: (a -> b) -> IO a -> IO b
monadStuff f action = f <$> action
data Test
= TestC Int
| TestM String
deriving (Show, Eq, Ord)

View File

@ -0,0 +1,14 @@
name: add-package-test
version: 0.1.0.0
license: BSD3
author: Luke Lau
maintainer: luke_lau@icloud.com
build-type: Simple
extra-source-files: ChangeLog.md
cabal-version: >=1.10
executable AddPackage
exposed-modules: ./.
main-is: AddPackage.hs
build-depends: base >=4.7 && <5
default-language: Haskell2010

View File

@ -0,0 +1,4 @@
module AddPackage where
import Data.Text
foo = pack "I'm a Text"

View File

@ -22,9 +22,6 @@ description: Please see the README on GitHub at <https://github.com/gith
dependencies:
- base >= 4.7 && < 5
library:
source-dirs: src
executables:
asdf-exe:
main: Main.hs
@ -34,15 +31,4 @@ executables:
- -rtsopts
- -with-rtsopts=-N
dependencies:
- asdf
tests:
asdf-test:
main: Spec.hs
source-dirs: test
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- asdf
- asdf

View File

@ -0,0 +1,7 @@
{-# LANGUAGE OverloadedStrings #-}
module Asdf where
import Codec.Compression.GZip
main = return $ compress "hello"

View File

@ -0,0 +1,25 @@
name: asdf
version: 0.1.0.0
github: "githubuser/asdf"
license: BSD3
author: "Author name here"
maintainer: "example@example.com"
copyright: "2018 Author name here"
extra-source-files:
- README.md
- ChangeLog.md
# Metadata used when publishing your package
# synopsis: Short description of your package
# category: Web
# To avoid duplicated efforts in documentation and dealing with the
# complications of embedding Haddock markup inside cabal files, it is
# common to point users to the README.md file.
description: Please see the README on GitHub at <https://github.com/githubuser/asdf#readme>
library:
source-dirs: app
dependencies:
- base >= 4.7 && < 5

View File

@ -0,0 +1,2 @@
import Data.Text
foo = pack "I'm a Text"

View File

@ -0,0 +1,5 @@
{-# LANGUAGE OverloadedStrings #-}
import Codec.Compression.GZip
main = return $ compress "hello"

View File

@ -0,0 +1,60 @@
cabal-version: 1.12
-- This file has been generated from package.yaml by hpack version 0.31.2.
--
-- see: https://github.com/sol/hpack
--
-- hash: a63a1c272a979a805027c5855cbe062ec4698b6ea6dbe59dd5f7aa34b15656a6
name: asdf
version: 0.1.0.0
description: Please see the README on GitHub at <https://github.com/githubuser/asdf#readme>
homepage: https://github.com/githubuser/asdf#readme
bug-reports: https://github.com/githubuser/asdf/issues
author: Author name here
maintainer: example@example.com
copyright: 2018 Author name here
license: BSD3
build-type: Simple
extra-source-files:
README.md
ChangeLog.md
source-repository head
type: git
location: https://github.com/githubuser/asdf
library
other-modules:
Paths_asdf
hs-source-dirs:
src
build-depends:
base >=4.7 && <5
default-language: Haskell2010
executable asdf-exe
main-is: Main.hs
other-modules:
Asdf
Paths_asdf
hs-source-dirs:
app
ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends:
asdf
, base >=4.7 && <5
default-language: Haskell2010
test-suite asdf-test
type: exitcode-stdio-1.0
main-is: Spec.hs
other-modules:
Paths_asdf
hs-source-dirs:
test
ghc-options: -threaded -rtsopts -with-rtsopts=-N
build-depends:
asdf
, base >=4.7 && <5
default-language: Haskell2010

View File

@ -0,0 +1,34 @@
name: asdf
version: 0.1.0.0
github: "githubuser/asdf"
license: BSD3
author: "Author name here"
maintainer: "example@example.com"
copyright: "2018 Author name here"
extra-source-files:
- README.md
- ChangeLog.md
# Metadata used when publishing your package
# synopsis: Short description of your package
# category: Web
# To avoid duplicated efforts in documentation and dealing with the
# complications of embedding Haddock markup inside cabal files, it is
# common to point users to the README.md file.
description: Please see the README on GitHub at <https://github.com/githubuser/asdf#readme>
library:
source-dirs: src
executables:
asdf-exe:
main: Main.hs
source-dirs: app
ghc-options:
- -threaded
- -rtsopts
- -with-rtsopts=-N
dependencies:
- asdf

View File

@ -0,0 +1,5 @@
{-# LANGUAGE OverloadedStrings #-}
module Asdf where
import Codec.Compression.GZip
main = return $ compress "hello"

View File

@ -0,0 +1,36 @@
cabal-version: 1.12
-- This file has been generated from package.yaml by hpack version 0.31.2.
--
-- see: https://github.com/sol/hpack
--
-- hash: 0a09a2280cfeb48f88861d105a48255e71ec34cc865390f1d038119511564661
name: asdf
version: 0.1.0.0
description: Please see the README on GitHub at <https://github.com/githubuser/asdf#readme>
homepage: https://github.com/githubuser/asdf#readme
bug-reports: https://github.com/githubuser/asdf/issues
author: Author name here
maintainer: example@example.com
copyright: 2018 Author name here
license: BSD3
build-type: Simple
extra-source-files:
README.md
ChangeLog.md
source-repository head
type: git
location: https://github.com/githubuser/asdf
library
exposed-modules:
Asdf
other-modules:
Paths_asdf
hs-source-dirs:
app
build-depends:
base >=4.7 && <5
default-language: Haskell2010

View File

@ -0,0 +1,25 @@
name: asdf
version: 0.1.0.0
github: "githubuser/asdf"
license: BSD3
author: "Author name here"
maintainer: "example@example.com"
copyright: "2018 Author name here"
extra-source-files:
- README.md
- ChangeLog.md
# Metadata used when publishing your package
# synopsis: Short description of your package
# category: Web
# To avoid duplicated efforts in documentation and dealing with the
# complications of embedding Haddock markup inside cabal files, it is
# common to point users to the README.md file.
description: Please see the README on GitHub at <https://github.com/githubuser/asdf#readme>
library:
source-dirs: app
dependencies:
- base >= 4.7 && < 5

View File

@ -0,0 +1,2 @@
import Data.Text
foo = pack "I'm a Text"

View File

@ -4,6 +4,7 @@
module ApplyRefactPluginSpec where
import qualified Data.HashMap.Strict as H
import qualified Data.Text as T
import Haskell.Ide.Engine.Plugin.ApplyRefact
import Haskell.Ide.Engine.MonadTypes
import Haskell.Ide.Engine.PluginUtils
@ -89,7 +90,8 @@ applyRefactSpec = do
-- ---------------------------------
it "returns hlint parse error as DsInfo ignored diagnostic" $ do
filePath <- filePathToUri <$> makeAbsolute "./test/testdata/HlintParseFail.hs"
filePathNoUri <- makeAbsolute "./test/testdata/HlintParseFail.hs"
let filePath = filePathToUri filePathNoUri
let act = lintCmd' arg
arg = filePath
@ -97,7 +99,15 @@ applyRefactSpec = do
PublishDiagnosticsParams
{ _uri = filePath
, _diagnostics = List
#if (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(8,2,2,0)))
#if (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(8,4,0,0)))
[Diagnostic {_range = Range { _start = Position {_line = 13, _character = 0}
, _end = Position {_line = 13, _character = 100000}}
, _severity = Just DsInfo
, _code = Just "parser"
, _source = Just "hlint"
, _message = T.pack filePathNoUri <> ":13:24: error:\n Operator applied to too few arguments: +\n data instance Sing (z :: (a :~: b)) where\n SRefl :: Sing Refl +\n> \n\n"
, _relatedInformation = Nothing }]}
#elif (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(8,2,2,0)))
[Diagnostic {_range = Range { _start = Position {_line = 13, _character = 0}
, _end = Position {_line = 13, _character = 100000}}
, _severity = Just DsInfo
@ -153,3 +163,15 @@ applyRefactSpec = do
, _diagnostics = List []
}
))
-- ---------------------------------
it "reports error without crash" $ do
filePath <- filePathToUri <$> makeAbsolute "./test/testdata/ApplyRefactError.hs"
let req = applyAllCmd' filePath
isExpectedError (IdeResultFail (IdeError PluginError err _)) =
"Illegal symbol '.' in type" `T.isInfixOf` err
isExpectedError _ = False
r <- withCurrentDirectory "./test/testdata" $ runIGM testPlugins req
r `shouldSatisfy` isExpectedError

View File

@ -0,0 +1,23 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
module GhcModPluginSpec where
import Control.Exception
import qualified Data.HashMap.Strict as H
import qualified Data.Map as Map
#if __GLASGOW_HASKELL__ < 804
import Data.Monoid
#endif
import qualified Data.Set as S
import qualified Data.Text as T
import Haskell.Ide.Engine.MonadTypes
import Haskell.Ide.Engine.Plugin.GhcMod
import Haskell.Ide.Engine.PluginUtils
import Haskell.Ide.Engine.Support.HieExtras
import Language.Haskell.LSP.Types (TextEdit (..))
import System.Directory
import TestUtils
import Test.Hspec
-- ---------------------------------------------------------------------

View File

@ -0,0 +1,31 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module HaRePluginSpec where
import Control.Monad.Trans.Free
import Control.Monad.IO.Class
import Data.Aeson
import qualified Data.Map as M
import qualified Data.HashMap.Strict as H
import Haskell.Ide.Engine.MonadTypes
import Haskell.Ide.Engine.PluginUtils
import Haskell.Ide.Engine.Plugin.GhcMod
import Haskell.Ide.Engine.Plugin.HaRe
import Haskell.Ide.Engine.Support.HieExtras
import Language.Haskell.LSP.Types ( Location(..)
, TextEdit(..)
)
import System.Directory
import System.FilePath
import TestUtils
import Test.Hspec
-- ---------------------------------------------------------------------
{-# ANN module ("hlint: ignore Eta reduce" :: String) #-}
{-# ANN module ("hlint: ignore Redundant do" :: String) #-}
-- ---------------------------------------------------------------------

View File

@ -37,7 +37,7 @@ jsonSpec = do
describe "General JSON instances round trip" $ do
-- Plugin params
prop "ApplyOneParams" (propertyJsonRoundtrip :: ApplyOneParams -> Bool)
prop "InfoParams" (propertyJsonRoundtrip :: InfoParams -> Bool)
prop "TypeParams" (propertyJsonRoundtrip :: TypeParams -> Bool)
prop "HarePoint" (propertyJsonRoundtrip :: HarePoint -> Bool)
prop "HarePointWithText" (propertyJsonRoundtrip :: HarePointWithText -> Bool)
prop "HareRange" (propertyJsonRoundtrip :: HareRange -> Bool)
@ -62,8 +62,8 @@ smallList = resize 3 . listOf
instance Arbitrary ApplyOneParams where
arbitrary = AOP <$> arbitrary <*> arbitrary <*> arbitrary
instance Arbitrary InfoParams where
arbitrary = IP <$> arbitrary <*> arbitrary
instance Arbitrary TypeParams where
arbitrary = TP <$> arbitrary <*> arbitrary <*> arbitrary
instance Arbitrary HarePoint where
arbitrary = HP <$> arbitrary <*> arbitrary

View File

@ -0,0 +1,326 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE CPP #-}
module PackagePluginSpec where
import Control.Monad ( forM_ )
import qualified Data.Aeson as Json
import qualified Data.Text as T
import qualified Data.HashMap.Strict as H
import Haskell.Ide.Engine.MonadTypes
import Haskell.Ide.Engine.Plugin.Package
import System.FilePath
import System.Directory
import Test.Hspec
import TestUtils
main :: IO ()
main = hspec spec
spec :: Spec
spec = describe "Package plugin" packageSpec
testdata :: FilePath
testdata = "test/testdata/addPackageTest"
testPlugins :: IdePlugins
testPlugins = pluginDescToIdePlugins [packageDescriptor "package"]
cabalProject :: [FilePath]
cabalProject = ["cabal-lib", "cabal-exe"]
hpackProject :: [FilePath]
hpackProject = ["hpack-lib", "hpack-exe", "hybrid-lib", "hybrid-exe"]
packageSpec :: Spec
packageSpec = do
cwd <- runIO getCurrentDirectory
describe "Find correct package type" $ do
forM_ hpackProject $ \hpack ->
it ("hpack project find package.yaml (\"" ++ hpack ++ "\")") $ do
let fp = testdata </> hpack
packageType <- findPackageType fp
packageType `shouldBe` HpackPackage (fp </> "package.yaml")
forM_ cabalProject $ \cabal ->
it ("hpack project find cabal file (\"" ++ cabal ++ "\")") $ do
let fp = testdata </> cabal
packageType <- findPackageType fp
packageType `shouldBe` CabalPackage "add-package-test.cabal"
it "Find no project description if none is present " $ do
let fp = cwd </> testdata </> "invalid"
packageType <- findPackageType fp
packageType `shouldBe` NoPackage
it "Throws exception if path is invalid" $ do
let fp = testdata </> "unknownPath"
findPackageType fp `shouldThrow` anyIOException
describe "Add the package to the correct file" $ do
it "Add package to .cabal to executable component"
$ withCurrentDirectory (testdata </> "cabal-exe")
$ do
let
fp = cwd </> testdata </> "cabal-exe"
uri = filePathToUri $ fp </> "add-package-test.cabal"
args = AddParams fp (fp </> "AddPackage.hs") "text"
act = addCmd' args
textEdits =
#if (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(8,4,0,0)))
List
[ TextEdit (Range (Position 0 0) (Position 7 27)) $ T.concat
[ "cabal-version: >=1.10\n"
, "name: add-package-test\n"
, "version: 0.1.0.0\n"
, "license: BSD3\n"
, "maintainer: luke_lau@icloud.com\n"
, "author: Luke Lau\n"
, "build-type: Simple\n"
, "extra-source-files:\n"
, " ChangeLog.md"
]
, TextEdit (Range (Position 10 0) (Position 13 34)) $ T.concat
[ " main-is: AddPackage.hs\n"
, " default-language: Haskell2010\n"
, " build-depends:\n"
, " base >=4.7 && <5,\n"
, " text -any"
]
]
#else
List -- TODO: this seems to indicate that the command does nothing
[ TextEdit (Range (Position 0 0) (Position 7 27)) $ T.concat
[ "name: add-package-test\n"
, "version: 0.1.0.0\n"
, "cabal-version: >=1.10\n"
, "build-type: Simple\n"
, "license: BSD3\n"
, "maintainer: luke_lau@icloud.com\n"
, "author: Luke Lau\n"
, "extra-source-files:\n"
, " ChangeLog.md"
]
, TextEdit (Range (Position 9 0) (Position 13 34)) $ T.concat
[ "executable AddPackage\n"
, " main-is: AddPackage.hs\n"
]
]
#endif
res = IdeResultOk
$ WorkspaceEdit (Just $ H.singleton uri textEdits) Nothing
testCommand testPlugins act "package" "add" args res
it "Add package to .cabal to library component"
$ withCurrentDirectory (testdata </> "cabal-lib")
$ do
let
fp = cwd </> testdata </> "cabal-lib"
uri = filePathToUri $ fp </> "add-package-test.cabal"
args = AddParams fp (fp </> "AddPackage.hs") "text"
act = addCmd' args
textEdits =
#if (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(8,4,0,0)))
List
[ TextEdit (Range (Position 0 0) (Position 7 27)) $ T.concat
[ "cabal-version: >=1.10\n"
, "name: add-package-test\n"
, "version: 0.1.0.0\n"
, "license: BSD3\n"
, "maintainer: luke_lau@icloud.com\n"
, "author: Luke Lau\n"
, "build-type: Simple\n"
, "extra-source-files:\n"
, " ChangeLog.md"
]
, TextEdit (Range (Position 10 0) (Position 13 34)) $ T.concat
[ " exposed-modules:\n"
, " AddPackage\n"
, " default-language: Haskell2010\n"
, " build-depends:\n"
, " base >=4.7 && <5,\n"
, " text -any"
]
]
#else
List
[ TextEdit (Range (Position 0 0) (Position 7 27)) $ T.concat
[ "name: add-package-test\n"
, "version: 0.1.0.0\n"
, "cabal-version: >=1.10\n"
, "build-type: Simple\n"
, "license: BSD3\n"
, "maintainer: luke_lau@icloud.com\n"
, "author: Luke Lau\n"
, "extra-source-files:\n"
, " ChangeLog.md"
]
, TextEdit (Range (Position 10 0) (Position 13 34)) $ T.concat
[ " exposed-modules:\n"
, " AddPackage\n"
, " build-depends:\n"
, " base >=4.7 && <5,\n"
, " text -any\n"
, " default-language: Haskell2010\n"
]
]
#endif
res = IdeResultOk
$ WorkspaceEdit (Just $ H.singleton uri textEdits) Nothing
testCommand testPlugins act "package" "add" args res
it "Add package to package.yaml to executable component"
$ withCurrentDirectory (testdata </> "hpack-exe")
$ do
let
fp = cwd </> testdata </> "hpack-exe"
uri = filePathToUri $ fp </> "package.yaml"
args = AddParams fp (fp </> "app" </> "Asdf.hs") "zlib"
act = addCmd' args
res = IdeResultOk
$ WorkspaceEdit (Just $ H.singleton uri textEdits) Nothing
textEdits = List
[ TextEdit (Range (Position 0 0) (Position 34 0)) $ T.concat
[ "copyright: 2018 Author name here\n"
, "maintainer: example@example.com\n"
, "dependencies:\n"
, "- zlib\n"
, "- base >= 4.7 && < 5\n"
, "name: asdf\n"
, "version: 0.1.0.0\n"
, "extra-source-files:\n"
, "- README.md\n"
, "- ChangeLog.md\n"
, "author: Author name here\n"
, "github: githubuser/asdf\n"
, "license: BSD3\n"
, "executables:\n"
, " asdf-exe:\n"
, " source-dirs: app\n"
, " main: Main.hs\n"
, " ghc-options:\n"
, " - -threaded\n"
, " - -rtsopts\n"
, " - -with-rtsopts=-N\n"
, " dependencies:\n"
, " - asdf\n"
, "description: Please see the README on GitHub at <https://github.com/githubuser/asdf#readme>\n"
]
]
testCommand testPlugins act "package" "add" args res
it "Add package to package.yaml to library component"
$ withCurrentDirectory (testdata </> "hpack-lib")
$ do
let
fp = cwd </> testdata </> "hpack-lib"
uri = filePathToUri $ fp </> "package.yaml"
args = AddParams fp (fp </> "app" </> "Asdf.hs") "zlib"
act = addCmd' args
res = IdeResultOk
$ WorkspaceEdit (Just $ H.singleton uri textEdits) Nothing
textEdits =
List
[ TextEdit (Range (Position 0 0) (Position 25 0)) $ T.concat
[ "library:\n"
, " source-dirs: app\n"
, " dependencies:\n"
, " - zlib\n"
, " - base >= 4.7 && < 5\n"
, "copyright: 2018 Author name here\n"
, "maintainer: example@example.com\n"
, "name: asdf\n"
, "version: 0.1.0.0\n"
, "extra-source-files:\n"
, "- README.md\n"
, "- ChangeLog.md\n"
, "author: Author name here\n"
, "github: githubuser/asdf\n"
, "license: BSD3\n"
, "description: Please see the README on GitHub at <https://github.com/githubuser/asdf#readme>\n"
]
]
testCommand testPlugins act "package" "add" args res
it
"Add package to package.yaml in hpack project with generated cabal to executable component"
$ withCurrentDirectory (testdata </> "hybrid-exe")
$ do
let
fp = cwd </> testdata </> "hybrid-exe"
uri = filePathToUri $ fp </> "package.yaml"
args = AddParams fp (fp </> "app" </> "Asdf.hs") "zlib"
act = addCmd' args
res = IdeResultOk
$ WorkspaceEdit (Just $ H.singleton uri textEdits) Nothing
textEdits = List
[ TextEdit (Range (Position 0 0) (Position 34 0)) $ T.concat
[ "library:\n"
, " source-dirs: src\n"
, "copyright: 2018 Author name here\n"
, "maintainer: example@example.com\n"
, "name: asdf\n"
, "version: 0.1.0.0\n"
, "extra-source-files:\n"
, "- README.md\n"
, "- ChangeLog.md\n"
, "author: Author name here\n"
, "github: githubuser/asdf\n"
, "license: BSD3\n"
, "executables:\n"
, " asdf-exe:\n"
, " source-dirs: app\n"
, " main: Main.hs\n"
, " ghc-options:\n"
, " - -threaded\n"
, " - -rtsopts\n"
, " - -with-rtsopts=-N\n"
, " dependencies:\n"
, " - zlib\n"
, " - asdf\n"
, "description: Please see the README on GitHub at <https://github.com/githubuser/asdf#readme>\n"
]
]
testCommand testPlugins act "package" "add" args res
it "Add package to package.yaml in hpack project with generated cabal to library component"
$ withCurrentDirectory (testdata </> "hybrid-lib")
$ do
let
fp = cwd </> testdata </> "hybrid-lib"
uri = filePathToUri $ fp </> "package.yaml"
args = AddParams fp (fp </> "app" </> "Asdf.hs") "zlib"
act = addCmd' args
res = IdeResultOk
$ WorkspaceEdit (Just $ H.singleton uri textEdits) Nothing
textEdits = List
[ TextEdit (Range (Position 0 0) (Position 25 0)) $ T.concat
[ "library:\n"
, " source-dirs: app\n"
, " dependencies:\n"
, " - zlib\n"
, " - base >= 4.7 && < 5\n"
, "copyright: 2018 Author name here\n"
, "maintainer: example@example.com\n"
, "name: asdf\n"
, "version: 0.1.0.0\n"
, "extra-source-files:\n"
, "- README.md\n"
, "- ChangeLog.md\n"
, "author: Author name here\n"
, "github: githubuser/asdf\n"
, "license: BSD3\n"
, "description: Please see the README on GitHub at <https://github.com/githubuser/asdf#readme>\n"
]
]
testCommand testPlugins act "package" "add" args res
it "Do nothing on NoPackage"
$ withCurrentDirectory (testdata </> "invalid")
$ do
let
fp = cwd </> testdata </> "invalid"
args = AddParams fp (fp </> "app" </> "Asdf.hs") "zlib"
act = addCmd' args
res =
IdeResultFail
(IdeError PluginError
"No package.yaml or .cabal found"
Json.Null
)
testCommand testPlugins act "package" "add" args res

View File

@ -24,6 +24,8 @@ import Data.Typeable
import Data.Yaml
import qualified Data.Map as Map
import Data.Maybe
-- import qualified GhcMod.Monad as GM
-- import qualified GhcMod.Types as GM
import qualified Language.Haskell.LSP.Core as Core
import Haskell.Ide.Engine.MonadTypes
import System.Directory
@ -90,8 +92,12 @@ setupStackFiles =
files :: [FilePath]
files =
[ "./test/testdata/"
, "./test/testdata/addPackageTest/cabal/"
, "./test/testdata/addPackageTest/hpack/"
, "./test/testdata/addPackageTest/cabal-exe/"
, "./test/testdata/addPackageTest/hpack-exe/"
, "./test/testdata/addPackageTest/hybrid-exe/"
, "./test/testdata/addPackageTest/cabal-lib/"
, "./test/testdata/addPackageTest/hpack-lib/"
, "./test/testdata/addPackageTest/hybrid-lib/"
, "./test/testdata/addPragmas/"
, "./test/testdata/badProjects/cabal/"
, "./test/testdata/completion/"
@ -118,7 +124,9 @@ ghcVersion = GHCPre84
stackYaml :: FilePath
stackYaml =
#if (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(8,6,4,0)))
#if (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(8,6,5,0)))
"stack.yaml"
#elif (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(8,6,4,0)))
"stack-8.6.4.yaml"
#elif (defined(MIN_VERSION_GLASGOW_HASKELL) && (MIN_VERSION_GLASGOW_HASKELL(8,6,3,0)))
"stack-8.6.3.yaml"