├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── hie-wrapper.sh ├── lib └── ide-haskell-hie.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | package-lock.json 5 | .vscode 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.11.0 2 | * Improve hie-wrapper support for stack projects 3 | * Pass options to custom wrappers that should be passed to hie 4 | 5 | ## 0.10.0 6 | * Add restart command (accessible from the command palette) 7 | * Major cleanup in code 8 | 9 | ## 0.9.0 10 | * Update LSP dependencies 11 | * Add support for using a custom hie wrapper 12 | 13 | ## 0.8.0 14 | * Add configuration to use hie-wrapper, which tries to automatically select the correct hie executable matching the GHC version of your project 15 | 16 | ## 0.7.3 17 | * Add basic settings that are passed on to HIE, via @domenkozar #7 18 | 19 | ## 0.6.0 20 | * Change name from `atom-haskell-hie` to `ide-haskell-hie` to follow atom-languageclient conventions, and for automatic discoverability 21 | 22 | ## 0.5.0 23 | * Remove cabal support because HIE was giving errors 24 | 25 | ## 0.1.0 - First Release 26 | * Every feature added 27 | * Every bug fixed 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Christian Kjær Laustsen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Atom plugin for HIE LSP server 2 | Rudimentary support for [HIE](https://github.com/haskell/haskell-ide-engine), relies on [atom-ide-ui](https://atom.io/packages/atom-ide-ui) for displaying LSP interactions, and [language-haskell](https://atom.io/packages/language-haskell) to identify Haskell files. Internatlly uses [atom-languageclient](https://github.com/atom/atom-languageclient) for the LSP client, and [HIE](https://github.com/haskell/haskell-ide-engine) as the LSP server. 3 | 4 | To get hie to automatically detect the correct hie version to use based on your projects GHC version, enable the experimental flag 'Use hie-wrapper', and make sure to build your project using the Makefile in the [HIE](https://github.com/haskell/haskell-ide-engine) repository (builds multiple versions of hie). 5 | 6 | ## Installation 7 | 8 | You can install `ide-haskell-hie` by using [`apm`](https://github.com/atom/apm). 9 | ``` 10 | apm install ide-haskell-hie 11 | ``` 12 | Or via _Atom > Settings view > Install Packages > Search packages > ide-haskell-hie_ 13 | 14 | ### From source 15 | To contribute to `ide-haskell-hie` you might want to install it from source: 16 | ``` 17 | # Get source from `ide-haskell-hie` repository 18 | git clone git@github.com:Tehnix/ide-haskell-hie.git 19 | cd ide-haskell-hie 20 | # install dependencies 21 | npm install 22 | # link local version in `dev` mode 23 | apm link --dev 24 | # start Atom in `dev` mode 25 | atom --dev 26 | # To unlink local version of `ide-haskell-hie` run 27 | apm unlink --dev 28 | ``` 29 | For more information about `apm` and `link` check [Contributing to Official Atom Packages]( https://flight-manual.atom.io/hacking-atom/sections/contributing-to-official-atom-packages/#contributing-to-official-atom-packages). 30 | 31 | 32 | ## Configuration 33 | 34 | The plugin should work out-the-box, but your environment may differ for many reasons, and the following are some configurations that might help you get it working. 35 | 36 | - `Absolute path to hie executable` will set the path to hie, in case it's not on your $PATH. 37 | - `Use hie wrapper` makes Atom use the `hie-wrapper.sh` file to start hie through. This does assume that you built the hie executable using `make build`, but will fall back to plain `hie`. 38 | - `Use custom hie wrapper` enables you to use your own custom hie wrapper script, if the standard one doesn't suit your need (e.g. to use with nix). 39 | - `The path to your custom hie wrapper` specifies the path to the custom wrapper, and is required for it to take effect. 40 | - `Turn on debugging output` passes the `--debug` flag to hie (although not if using a custom wrapper, then you're on your own). 41 | - `Log to a file (if debugging is on)` will set the log file that debug writes to. 42 | 43 | For additional debugging (e.g. stderr), you can enable `Settings -> Core -> Debug L S P` or add `debugLSP: true` to the core section in `Debug -> Config... -> config.cson`, and then view the output in the Atom Developer Console. 44 | 45 | 46 | ## A few screenshots of the working things 47 | #### Type/Datatips information on hover & Definitions/Hyperclick 48 | ![Definitions/Hypercick support](https://user-images.githubusercontent.com/1189998/30351887-6a3f4d70-9858-11e7-87ae-ab90be448023.png) 49 | 50 | #### Linter/diagnostics on save 51 | ![Linter Errors](https://user-images.githubusercontent.com/1189998/30351907-7d3d585e-9858-11e7-9a2f-66a8a1582010.png) 52 | 53 | #### Outline view & Highlighting 54 | ![Outline view on the right side and highlight of anotherFunc](https://user-images.githubusercontent.com/1189998/30351896-71e56dca-9858-11e7-85d7-1d90eee11807.png) 55 | 56 | #### Code actions 57 | ![Code actions being applied](https://user-images.githubusercontent.com/1189998/32152232-092b5aaa-bd66-11e7-8b48-583f21a9231e.gif) 58 | 59 | #### Code format 60 | No screenshot really necessary here. 61 | 62 | ## Not implemented 63 | 64 | - Find references (HIE does not support this yet, see [issue #361](https://github.com/haskell/haskell-ide-engine/issues/361)) 65 | - Rename (HIE supports it, but [atom-languageclient](https://github.com/atom/atom-languageclient#capabilities) is TBD, see [issue #13](https://github.com/atom/atom-languageclient/issues/13)) 66 | 67 | # Miscellaneous 68 | The code for the providers that HIE supports can be found [here](https://github.com/haskell/haskell-ide-engine/blob/master/src/Haskell/Ide/Engine/Transport/LspStdio.hs#L758) (permanent link [here](https://github.com/haskell/haskell-ide-engine/blob/0e520cf8f93dbc6a41723bfc95c8c43f87fa6757/src/Haskell/Ide/Engine/Transport/LspStdio.hs#L758)). 69 | -------------------------------------------------------------------------------- /hie-wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | DEBUG=1 3 | indent="" 4 | function debug { 5 | if [[ $DEBUG == 1 ]]; then 6 | printf '%s' "$indent" 7 | echo "$@" 8 | fi >> /tmp/hie-wrapper.log 9 | } 10 | 11 | curDir=`pwd` 12 | debug "Launching HIE for project located at $curDir" 13 | indent=" " 14 | 15 | USE_STACK= 16 | GHCBIN='ghc' 17 | HIE_PREFIX= 18 | # If a .stack-work exists, assume we are using stack. 19 | if [ -d ".stack-work" ]; then 20 | debug 'Using stack GHC and HIE' 21 | USE_STACK=1 22 | GHCBIN='stack ghc --' 23 | HIE_PREFIX='stack exec --' 24 | else 25 | debug 'Using plain GHC and HIE' 26 | fi 27 | versionNumber=`$GHCBIN --version` 28 | debug $versionNumber 29 | 30 | HIEBIN='hie' 31 | BACKUP_HIEBIN='hie' 32 | # Match the version number with a HIE version, and provide a fallback without 33 | # the patch number. 34 | if [[ $versionNumber = *"8.0.1"* ]]; then 35 | debug "Project is using GHC 8.0.1" 36 | HIEBIN='hie-8.0.1' 37 | BACKUP_HIEBIN='hie-8.0' 38 | elif [[ $versionNumber = *"8.0.2"* ]]; then 39 | debug "Project is using GHC 8.0.2" 40 | HIEBIN='hie-8.0.2' 41 | BACKUP_HIEBIN='hie-8.0' 42 | elif [[ $versionNumber = *"8.0"* ]]; then 43 | debug "Project is using GHC 8.0.*" 44 | HIEBIN='hie-8.0' 45 | elif [[ $versionNumber = *"8.2.1"* ]]; then 46 | debug "Project is using GHC 8.2.1" 47 | HIEBIN='hie-8.2.1' 48 | BACKUP_HIEBIN='hie-8.2' 49 | elif [[ $versionNumber = *"8.2.2"* ]]; then 50 | debug "Project is using GHC 8.2.2" 51 | HIEBIN='hie-8.2.2' 52 | BACKUP_HIEBIN='hie-8.2' 53 | elif [[ $versionNumber = *"8.2"* ]]; then 54 | debug "Project is using GHC 8.2.*" 55 | HIEBIN='hie-8.2' 56 | elif [[ $versionNumber = *"8.4.3"* ]]; then 57 | debug "Project is using GHC 8.4.3" 58 | HIEBIN='hie-8.4.3' 59 | BACKUP_HIEBIN='hie-8.4' 60 | elif [[ $versionNumber = *"8.4.4"* ]]; then 61 | debug "Project is using GHC 8.4.4" 62 | HIEBIN='hie-8.4.4' 63 | BACKUP_HIEBIN='hie-8.4' 64 | elif [[ $versionNumber = *"8.4"* ]]; then 65 | debug "Project is using GHC 8.4.*" 66 | HIEBIN='hie-8.4' 67 | else 68 | debug "WARNING: GHC version does not match any of the checked ones." 69 | fi 70 | 71 | find-hie-path () { 72 | if [[ -n "$USE_STACK" ]]; then 73 | stack exec -- which "$1" 74 | else 75 | command -v "$1" 76 | fi 77 | } 78 | 79 | if [ -x "$(find-hie-path $HIEBIN)" ]; then 80 | debug "$HIEBIN was found on path" 81 | elif [ -x "$(find-hie-path $BACKUP_HIEBIN)" ]; then 82 | debug "Backup $BACKUP_HIEBIN was found on path" 83 | HIEBIN=$BACKUP_HIEBIN 84 | elif [ -x "$(find-hie-path hie)" ]; then 85 | debug "Falling back to plain hie" 86 | HIEBIN='hie' 87 | else 88 | error_message='{"jsonrpc":"2.0","id":1,"error":{"code":-32099,"message":"Cannot find hie in the path"}}' 89 | printf 'Content-Length: %s\r\n\r\n%s' ${#error_message} "$error_message" 90 | exit 1 91 | fi 92 | 93 | if [[ -n $USE_STACK ]]; then 94 | debug 'Checking if a stack-managed Hoogle database exists' 95 | indent=' ' 96 | # try to find the project's Hoogle database. --local-hoogle-root is 97 | # only available on Stack 2.x+, so just try it and see if it succeeds. 98 | if hoogle_root=$(stack path --local-hoogle-root 2>/dev/null) \ 99 | && [[ -n $hoogle_root ]]; then 100 | hoogle_database="$hoogle_root/database.hoo" 101 | # don't bother setting HIE_HOOGLE_DATABASE unless a local database 102 | # has actually been built (so someone can set it themselves to a 103 | # more global database if they want) 104 | if [[ -f $hoogle_database ]]; then 105 | debug "Using Hoogle database at $hoogle_database" 106 | export HIE_HOOGLE_DATABASE=$hoogle_database 107 | else 108 | debug "No Hoogle database exists at $hoogle_database" 109 | debug 'continuing without setting HIE_HOOGLE_DATABASE' 110 | fi 111 | else 112 | debug '`stack path --local-hoogle-root` failed (maybe `stack` is too old?)' 113 | debug 'continuing without setting HIE_HOOGLE_DATABASE' 114 | fi 115 | indent=' ' 116 | fi 117 | 118 | debug "Starting HIE" 119 | exec $HIE_PREFIX "$HIEBIN" "$@" 120 | -------------------------------------------------------------------------------- /lib/ide-haskell-hie.js: -------------------------------------------------------------------------------- 1 | const childProcess = require('child_process'); 2 | const os = require('os'); 3 | const path = require('path'); 4 | const { shell } = require('electron'); 5 | const { AutoLanguageClient } = require('atom-languageclient'); 6 | const { CompositeDisposable } = require('atom'); 7 | 8 | class HaskellLanguageClient extends AutoLanguageClient { 9 | 10 | getGrammarScopes() { 11 | return ['source.haskell', 'text.tex.latex.haskell'] 12 | } 13 | 14 | getLanguageName() { 15 | return 'Haskell' 16 | } 17 | 18 | getServerName() { 19 | return 'hie' 20 | } 21 | 22 | /* 23 | * `startServerProcess` will be called automatically for us by the 24 | * LSP client, and is where the server will be launched from. 25 | */ 26 | startServerProcess(projectPath) { 27 | return this.spawnServer(projectPath) 28 | } 29 | 30 | /* 31 | * Restart the LSP (hie) server, notify the user, and clear the subscriptions. 32 | */ 33 | restartServer() { 34 | this.subscriptions.dispose(); 35 | this.restartAllServers().then(() => { 36 | atom.notifications.addInfo("HIE is has been restarted, and is currently initializing!"); 37 | }).catch(() => { 38 | atom.notifications.addError("Something went wrong trying to restart HIE!"); 39 | }); 40 | } 41 | 42 | /* 43 | * The core logic for starting the LSP server. 44 | */ 45 | spawnServer(projectPath) { 46 | let hiePath = atom.config.get("ide-haskell-hie.hiePath"), 47 | // We need to set the current working directory, else it will default to the 48 | // location of the script. 49 | processOptions = { 'cwd': projectPath }, 50 | args = ['--lsp']; 51 | const useHieWrapper = atom.config.get("ide-haskell-hie.useHieWrapper"), 52 | useCustomHieWrapper = atom.config.get("ide-haskell-hie.useCustomHieWrapper"), 53 | useCustomHieWrapperPath = atom.config.get("ide-haskell-hie.useCustomHieWrapperPath"), 54 | hieDebug = atom.config.get("ide-haskell-hie.isDebug"), 55 | hieLoggingPath = atom.config.get("ide-haskell-hie.hieLoggingPath"), 56 | pluginPath = atom.packages.resolvePackagePath('ide-haskell-hie'); 57 | 58 | if (hieDebug) { 59 | args.push('--debug', '-l', hieLoggingPath); 60 | } 61 | 62 | // If both `useCustomHieWrapper` is true and `useCustomHieWrapperPath` contains 63 | // a value (the path), then go ahead and set up hie to launch from the custom 64 | // wrapper, which also means substituting path placeholders. 65 | if (useCustomHieWrapper && useCustomHieWrapperPath) { 66 | hiePath = useCustomHieWrapperPath; 67 | // Expand project path and $HOME placeholders. 68 | hiePath = hiePath 69 | .replace('${workspaceFolder}', projectPath) 70 | .replace('${workspaceRoot}', projectPath) 71 | .replace('${projectPath}', projectPath) 72 | .replace('${HOME}', os.homedir) 73 | .replace('${home}', os.homedir) 74 | .replace(/^~/, os.homedir); 75 | } else if (pluginPath && useHieWrapper) { 76 | // If `useHieWrapper` is set to true, set up the path for the hie-wrapper script. 77 | hiePath = path.join(pluginPath, 'hie-wrapper.sh'); 78 | } 79 | 80 | // Add a command to restart the HIE server. 81 | this.subscriptions = new CompositeDisposable(); 82 | this.subscriptions.add(atom.commands.add('atom-workspace', 83 | { 'hie:restart-server': () => this.restartServer() }) 84 | ); 85 | 86 | // Start the hie process, and assume HIE is not installed, if the launch 87 | // fails. 88 | const lspServer = childProcess.spawn(hiePath, args, processOptions); 89 | lspServer.on('error', err => atom.notifications.addError('Unable to start the HIE (Haskell IDE Engine).', { 90 | dismissable: true, 91 | buttons: [ 92 | { 93 | text: 'Setup HIE', 94 | onDidClick: () => shell.openExternal('https://github.com/haskell/haskell-ide-engine/blob/master/README.md#installation') 95 | }, { 96 | text: 'Open Dev Tools', 97 | onDidClick: () => atom.openDevTools() 98 | } 99 | ], 100 | description: 'This can occur if you do not hie on your path, or you have yet to install it.' 101 | })); 102 | return lspServer; 103 | } 104 | 105 | /* 106 | * Enable the LSP debugging found in Settings -> Core -> Debug LSP. 107 | */ 108 | enableDebug(enabled) { 109 | atom 110 | .config 111 | .set('core.debugLSP', enabled); 112 | } 113 | } 114 | 115 | module.exports = new HaskellLanguageClient() 116 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ide-haskell-hie", 3 | "main": "./lib/ide-haskell-hie", 4 | "version": "0.12.0", 5 | "description": "Haskell LSP plugin for HIE (Haskell IDE Engine)", 6 | "keywords": [ 7 | "haskell", 8 | "lsp", 9 | "ide", 10 | "hie" 11 | ], 12 | "activationCommands": {}, 13 | "repository": { 14 | "type": "git", 15 | "url": "git@github.com:Tehnix/ide-haskell-hie.git" 16 | }, 17 | "bugs": { 18 | "url": "https://github.com/Tehnix/ide-haskell-hie" 19 | }, 20 | "license": "MIT", 21 | "engines": { 22 | "atom": ">=1.21.0 <2.0.0" 23 | }, 24 | "dependencies": { 25 | "atom-languageclient": "^0.9.2", 26 | "atom-package-deps": "^4.6.1" 27 | }, 28 | "package-deps": [ 29 | "atom-ide-ui", 30 | "language-haskell" 31 | ], 32 | "enhancedScopes": [ 33 | "source.haskell", 34 | "text.tex.latex.haskell" 35 | ], 36 | "prettier": { 37 | "printWidth": 100 38 | }, 39 | "eslintConfig": { 40 | "parserOptions": { 41 | "ecmaVersion": 6 42 | }, 43 | "globals": [ 44 | "atom" 45 | ], 46 | "rules": { 47 | "space-before-function-paren": [ 48 | "error", 49 | "never" 50 | ] 51 | } 52 | }, 53 | "configSchema": { 54 | "hiePath": { 55 | "title": "Absolute path to hie executable", 56 | "order": 1, 57 | "type": "string", 58 | "default": "hie", 59 | "description": "" 60 | }, 61 | "useHieWrapper": { 62 | "title": "Use the hie-wrapper", 63 | "order": 2, 64 | "type": "boolean", 65 | "default": false, 66 | "description": "Automatically try to select a hie version that matches the GHC version of the project, and enable additional support for stack projects (overwrites the hie path)." 67 | }, 68 | "useCustomHieWrapper": { 69 | "title": "Use a custom hie wrapper", 70 | "order": 3, 71 | "type": "boolean", 72 | "default": false, 73 | "description": "Use your own custom wrapper for hie (remember to specify the path!). This will take effect over useHieWrapper." 74 | }, 75 | "useCustomHieWrapperPath": { 76 | "title": "The path to your custom hie wrapper", 77 | "order": 4, 78 | "type": "string", 79 | "default": "", 80 | "description": "Specify the path to your own custom hie wrapper (e.g. /Users/Alice/.hie-wrapper.sh, ${HOME}/wrapper.sh or ${workspaceFolder}/wrapper.sh)." 81 | }, 82 | "isDebug": { 83 | "title": "Turn on debugging output", 84 | "order": 5, 85 | "type": "boolean", 86 | "default": false, 87 | "description": "" 88 | }, 89 | "hieLoggingPath": { 90 | "title": "Log to a file (if debugging is on)", 91 | "order": 6, 92 | "type": "string", 93 | "default": "/tmp/hie.log", 94 | "description": "" 95 | } 96 | }, 97 | "consumedServices": { 98 | "linter-indie": { 99 | "versions": { 100 | "2.0.0": "consumeLinterV2" 101 | } 102 | }, 103 | "status-bar": { 104 | "versions": { 105 | "^1.0.0": "consumeStatusBar" 106 | } 107 | }, 108 | "atom-ide-busy-signal": { 109 | "versions": { 110 | "0.1.0": "consumeBusySignal" 111 | } 112 | }, 113 | "datatip": { 114 | "versions": { 115 | "0.1.0": "consumeDatatip" 116 | } 117 | } 118 | }, 119 | "providedServices": { 120 | "autocomplete.provider": { 121 | "versions": { 122 | "2.0.0": "provideAutocomplete" 123 | } 124 | }, 125 | "code-format.range": { 126 | "versions": { 127 | "0.1.0": "provideCodeFormat" 128 | } 129 | }, 130 | "code-highlight": { 131 | "versions": { 132 | "0.1.0": "provideCodeHighlight" 133 | } 134 | }, 135 | "definitions": { 136 | "versions": { 137 | "0.1.0": "provideDefinitions" 138 | } 139 | }, 140 | "hyperclick": { 141 | "versions": { 142 | "0.1.0": "provideHyperclick" 143 | } 144 | }, 145 | "find-references": { 146 | "versions": { 147 | "0.1.0": "provideFindReferences" 148 | } 149 | }, 150 | "outline-view": { 151 | "versions": { 152 | "0.1.0": "provideOutlines" 153 | } 154 | }, 155 | "code-actions": { 156 | "versions": { 157 | "0.1.0": "provideCodeActions" 158 | } 159 | } 160 | } 161 | } 162 | --------------------------------------------------------------------------------