├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── dependabot.yml
└── workflows
│ ├── package.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .vscode-test.js
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── .vscodeignore
├── .yarnclean
├── Changelog.md
├── GenChangelogs.hs
├── LICENSE
├── README.md
├── default.nix
├── docs
├── Contributing.md
└── Release.md
├── eslint.config.mjs
├── images
└── hls-logo.png
├── package.json
├── src
├── commands
│ └── constants.ts
├── config.ts
├── docsBrowser.ts
├── errors.ts
├── extension.ts
├── ghcup.ts
├── hlsBinaries.ts
├── logger.ts
├── metadata.ts
├── statusBar.ts
└── utils.ts
├── test
└── suite
│ ├── extension.test.ts
│ └── index.ts
├── tsconfig.json
├── webpack.config.js
└── yarn.lock
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: 'status: needs triage'
6 | assignees: ''
7 | ---
8 |
9 |
14 |
15 | ### Your environment
16 |
17 | Which OS do you use:
18 |
19 |
20 |
21 | ### Steps to reproduce
22 |
23 |
24 |
25 | ### Expected behaviour
26 |
27 |
28 |
29 | ### Actual behaviour
30 |
31 |
32 |
33 | ### Include debug information
34 |
35 | Execute in the root of your project the command `haskell-language-server-wrapper --debug .` and paste the logs here (you can find the executable location [here](https://github.com/haskell/vscode-haskell#downloaded-binaries)):
36 |
37 |
38 |
39 | Debug output:
40 |
41 |
42 | ```
43 |
44 | ```
45 |
46 |
47 |
48 | Paste the contents of extension specific log, you can check instructions about how to find it [here](https://github.com/haskell/vscode-haskell#troubleshooting)
49 |
50 |
51 |
52 | Extension log:
53 |
54 |
55 | ```
56 |
57 | ```
58 |
59 |
60 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
9 | **Is your feature request related to a problem? Please describe.**
10 |
11 |
12 |
13 | **Describe the solution you'd like**
14 |
15 |
16 |
17 | **Describe alternatives you've considered**
18 |
19 |
20 |
21 | **Additional context**
22 |
23 |
24 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | # NOTE: Dependabot official configuration documentation:
4 | # https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/configuration-options-for-dependency-updates#package-ecosystem
5 |
6 | # Maintain dependencies for internal GitHub Actions CI for pull requests
7 | - package-ecosystem: 'github-actions'
8 | directory: '/'
9 | schedule:
10 | interval: 'weekly'
11 |
12 | - package-ecosystem: 'npm'
13 | directory: '/'
14 | schedule:
15 | interval: 'weekly'
16 |
--------------------------------------------------------------------------------
/.github/workflows/package.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - master
5 | pull_request:
6 | branches:
7 | - '**'
8 |
9 | jobs:
10 | build:
11 | strategy:
12 | fail-fast: false
13 | matrix:
14 | os: [ubuntu-latest]
15 | runs-on: ${{ matrix.os }}
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v5
19 |
20 | - name: Install Node.js
21 | uses: actions/setup-node@v5
22 | with:
23 | ## make sure this corresponds with the version in release.yml
24 | node-version: latest
25 |
26 | - run: yarn install --immutable --immutable-cache --check-cache
27 |
28 | - name: Package extension
29 | run: npx vsce package
30 | - name: Upload extension vsix to workflow artifacts
31 | uses: actions/upload-artifact@v4
32 | with:
33 | name: haskell-${{ github.sha }}.vsix
34 | path: haskell-*.vsix
35 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | on:
2 | release:
3 | types: [prereleased, released]
4 |
5 | name: Deploy Extension
6 | jobs:
7 | publish-extension:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v5
11 | - uses: actions/setup-node@v5
12 | with:
13 | ## make sure this corresponds with the version in test.yml
14 | node-version: latest
15 | - run: yarn install --immutable --immutable-cache --check-cache
16 |
17 | - name: Package Extension
18 | id: packageExtension
19 | uses: HaaLeo/publish-vscode-extension@v2
20 | with:
21 | pat: stub
22 | dryRun: true
23 | preRelease: ${{ github.event.action == 'prereleased' }}
24 | yarn: true
25 |
26 | ## Make sure the artifact is added to the release.
27 | - name: Upload extension vsix to workflow artifacts
28 | uses: actions/upload-artifact@v4
29 | with:
30 | name: haskell-${{ github.event.release.tag_name }}.vsix
31 | path: ${{ steps.packageExtension.outputs.vsixPath }}
32 |
33 | ## If this is a release job, publish to VSCode Marketplace,
34 | ## otherwise publish a pre-release to VSCode Marketplace
35 | - name: Publish to Visual Studio Marketplace
36 | id: publishToVSMarketplace
37 | uses: HaaLeo/publish-vscode-extension@v2
38 | with:
39 | pat: ${{ secrets.VS_MARKETPLACE_TOKEN }}
40 | registryUrl: https://marketplace.visualstudio.com
41 | extensionFile: ${{ steps.packageExtension.outputs.vsixPath }}
42 | yarn: true
43 | preRelease: ${{ github.event.action == 'prereleased' }}
44 |
45 | ## If this is a release job, publish to VSX Marketplace,
46 | ## otherwise publish a pre-release to VSX Marketplace
47 | - name: Publish to Open VSX Registry
48 | id: publishToOpenVSX
49 | continue-on-error: true
50 | uses: HaaLeo/publish-vscode-extension@v2
51 | with:
52 | pat: ${{ secrets.OPEN_VSX_TOKEN }}
53 | extensionFile: ${{ steps.packageExtension.outputs.vsixPath }}
54 | yarn: true
55 | preRelease: ${{ github.event.action == 'prereleased' }}
56 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - master
5 | pull_request:
6 | branches:
7 | - '**'
8 |
9 | jobs:
10 | build:
11 | strategy:
12 | fail-fast: false
13 | matrix:
14 | os: [macos-latest, ubuntu-latest, windows-latest]
15 | ghc: [8.10.7, 9.6.7, 9.8.4, 9.12.2]
16 | runs-on: ${{ matrix.os }}
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v5
20 |
21 | - name: Install Node.js
22 | uses: actions/setup-node@v5
23 | with:
24 | ## make sure this corresponds with the version in release.yml
25 | node-version: latest
26 |
27 | # Install test dependencies
28 | - run: yarn install --immutable --immutable-cache --check-cache
29 | - run: yarn run webpack
30 |
31 | # Setup toolchains, install ghcup, install ghc, etc...
32 | - name: Install GHCup
33 | run: |
34 | curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | sh
35 | shell: bash
36 | env:
37 | BOOTSTRAP_HASKELL_NONINTERACTIVE: 1
38 | BOOTSTRAP_HASKELL_MINIMAL: 1
39 |
40 | - name: Check GHCup (Windows)
41 | run: |
42 | echo "c:/ghcup/bin" >> $GITHUB_PATH
43 | shell: bash
44 | if: runner.os == 'Windows'
45 |
46 | - name: Check GHCup (Unix)
47 | run: |
48 | echo "${HOME}/.ghcup/bin" >> $GITHUB_PATH
49 | shell: bash
50 | if: runner.os != 'Windows'
51 |
52 | - name: Toolchain settings
53 | run: |
54 | ghcup upgrade -i -f
55 | export GHCUP_INSTALL_BASE_PREFIX=$(pwd)/test-workspace/bin
56 | ghcup config set cache true
57 |
58 | ghcup install stack latest
59 | ghcup install cabal latest
60 |
61 | ghcup install ghc ${{ matrix.ghc }}
62 | ghcup set ghc ${{ matrix.ghc }}
63 |
64 | # This is a prefetched, fallback HLS version.
65 | # We want to make sure, we still support old GHC versions
66 | # and graciously fallback to an HLS version that supports the old GHC version, such as 8.10.7
67 | ghcup install hls 2.2.0.0
68 | ghcup install hls latest
69 | shell: bash
70 |
71 | # Run the tests
72 | - name: Run the test on Linux
73 | run: |
74 | export GHCUP_INSTALL_BASE_PREFIX=$(pwd)/test-workspace/bin
75 | export PATH="$(pwd)/test-workspace/bin/.ghcup/bin:$PATH"
76 | xvfb-run -s '-screen 0 640x480x16' -a yarn run test
77 | shell: bash
78 | if: runner.os == 'Linux'
79 | - name: Run the test on macOS
80 | run: |
81 | export GHCUP_INSTALL_BASE_PREFIX=$(pwd)/test-workspace/bin
82 | export PATH="$(pwd)/test-workspace/bin/.ghcup/bin:$PATH"
83 | yarn run test
84 | shell: bash
85 | if: runner.os == 'macOS'
86 | - name: Run the test on Windows
87 | run: |
88 | export GHCUP_INSTALL_BASE_PREFIX=$(pwd)/test-workspace/bin
89 | export PATH="$(pwd)/test-workspace/bin/ghcup/bin:$PATH"
90 | yarn run test
91 | shell: bash
92 | if: runner.os == 'Windows'
93 |
94 | # Create package artefacts
95 | - name: Delete test artefacts
96 | # The test-suite doesn't clean up correctly after itself.
97 | # This is a poor man's workaround that after test execution,
98 | # the test-workspace still contains binaries and caches.
99 | run: |
100 | rm -rf test-workspace
101 | rm -rf out
102 | shell: bash
103 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | node_modules
3 | .vscode-test
4 | test-workspace
5 | .DS_Store
6 | dist
7 | *.vsix
8 | .husky
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | test-workspace/
3 | .vscode/
4 | .vscode-test/
5 | out/
6 | dist/
7 | webpack.config.js
8 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | printWidth: 120
2 | singleQuote: true
3 |
--------------------------------------------------------------------------------
/.vscode-test.js:
--------------------------------------------------------------------------------
1 | const { defineConfig } = require('@vscode/test-cli');
2 |
3 | module.exports = defineConfig([
4 | {
5 | label: 'integration-tests',
6 | files: 'out/test/**/*.test.js',
7 | version: 'stable',
8 | workspaceFolder: './test-workspace',
9 | installExtensions: ['justusadam.language-haskell'],
10 | mocha: {
11 | timeout: 120 * 1000, // 2 minute timeout
12 | },
13 | },
14 | // you can specify additional test configurations, too
15 | ]);
16 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint",
4 | "esbenp.prettier-vscode"
5 | ]
6 | }
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | // A launch configuration that compiles the extension and then opens it inside a new window
2 | {
3 | "version": "0.2.0",
4 | "configurations": [
5 | {
6 | "name": "Extension",
7 | "type": "extensionHost",
8 | "request": "launch",
9 | "runtimeExecutable": "${execPath}",
10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}"],
11 | "sourceMaps": true,
12 | "outFiles": ["${workspaceRoot}/dist/**/*.js"],
13 | "preLaunchTask": "npm: webpack"
14 | },
15 | {
16 | "name": "Extension Tests",
17 | "type": "extensionHost",
18 | "request": "launch",
19 | "runtimeExecutable": "${execPath}",
20 | "testConfiguration": "${workspaceFolder}/.vscode-test.js",
21 | "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test"],
22 | "sourceMaps": true,
23 | "outFiles": ["${workspaceRoot}/out/test/**/*.js"],
24 | "preLaunchTask": "npm: pretest"
25 | }
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "files.exclude": {
4 | "dist": true, // set this to true to hide the "dist" folder with the compiled JS files
5 | ".vscode-test": true,
6 | "node_modules": true
7 | },
8 | "search.exclude": {
9 | "dist": true // set this to false to include "dist" folder in search results
10 | },
11 | "typescript.tsdk": "./node_modules/typescript/lib",
12 | "editor.tabSize": 2, // we want to use the TS server from our node_modules folder to control its version,
13 | "editor.formatOnSave": true,
14 | "files.associations": {
15 | ".prettierrc": "yaml"
16 | },
17 | "files.eol": "\n",
18 | "haskell.formattingProvider": "stylish-haskell"
19 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // See https://go.microsoft.com/fwlink/?LinkId=733558
2 | // for the documentation about the tasks.json format
3 | {
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "type": "npm",
8 | "script": "watch",
9 | "problemMatcher": {
10 | "owner": "typescript",
11 | "applyTo": "closedDocuments",
12 | "fileLocation": ["absolute"],
13 | "pattern": {
14 | "regexp": ""
15 | },
16 | "background": {
17 | "activeOnStart": true,
18 | "beginsPattern": {
19 | "regexp": "Compilation (.*?)starting…"
20 | },
21 | "endsPattern": {
22 | "regexp": "Compilation (.*?)finished"
23 | }
24 | }
25 | },
26 | "isBackground": true,
27 | "presentation": {
28 | "reveal": "never"
29 | },
30 | "group": {
31 | "kind": "build",
32 | "isDefault": true
33 | }
34 | },
35 | {
36 | "type": "npm",
37 | "script": "test",
38 | "group": {
39 | "kind": "test",
40 | "isDefault": true
41 | }
42 | }
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | .vscode/**
2 | .vscode-test/**
3 | typings/**
4 | out/test/**
5 | test/**
6 | src/**
7 | **/*.map
8 | .gitignore
9 | tsconfig.json
10 | node_modules
11 | out
12 | src
13 | webpack.config.json
14 |
--------------------------------------------------------------------------------
/.yarnclean:
--------------------------------------------------------------------------------
1 | # test directories
2 | __tests__
3 | test
4 | tests
5 | powered-test
6 |
7 | # asset directories
8 | docs
9 | doc
10 | website
11 | images
12 | assets
13 |
14 | # examples
15 | example
16 | examples
17 |
18 | # code coverage directories
19 | coverage
20 | .nyc_output
21 |
22 | # build scripts
23 | Makefile
24 | Gulpfile.js
25 | Gruntfile.js
26 |
27 | # configs
28 | appveyor.yml
29 | circle.yml
30 | codeship-services.yml
31 | codeship-steps.yml
32 | wercker.yml
33 | .tern-project
34 | .gitattributes
35 | .editorconfig
36 | .*ignore
37 | .eslintrc
38 | .jshintrc
39 | .flowconfig
40 | .documentup.json
41 | .yarn-metadata.json
42 | .travis.yml
43 |
44 | # misc
45 | *.md
46 |
--------------------------------------------------------------------------------
/Changelog.md:
--------------------------------------------------------------------------------
1 | # Changelog for vscode-haskell
2 |
3 | ## 2.7.0
4 |
5 | - Introduce the `StatusBarItem`
6 | ([#1237](https://github.com/haskell/vscode-haskell/pull/1237)) by @fendor
7 |
8 | ## 2.6.1
9 |
10 | - Prefer the `set` version for `cabal` and `stack` if there is any
11 | ([#1275](https://github.com/haskell/vscode-haskell/pull/1275)) by @fendor
12 | - Make js debugger work
13 | ([#1258](https://github.com/haskell/vscode-haskell/pull/1258)) by @dyniec
14 | - Prepare release 2.6.0
15 | ([#1103](https://github.com/haskell/vscode-haskell/pull/1103)) by @fendor
16 |
17 | ## 2.6.0
18 |
19 | - Add option to enable/disable `.cabal` file support
20 | ([#1223](https://github.com/haskell/vscode-haskell/pull/1223)) by @fendor
21 | - Upgrade project to use latest eslint version
22 | ([#1150](https://github.com/haskell/vscode-haskell/pull/1150)) by @fendor
23 | - Fix windows CI
24 | ([#1149](https://github.com/haskell/vscode-haskell/pull/1149)) by @fendor
25 | - Manually install ghcup into image
26 | ([#1119](https://github.com/haskell/vscode-haskell/pull/1119)) by @fendor
27 | - bump vscode-languageclient version to 9.0.1
28 | ([#1108](https://github.com/haskell/vscode-haskell/pull/1108)) by @jetjinser
29 | - Add cabalFormattingProvider to package.json
30 | ([#1100](https://github.com/haskell/vscode-haskell/pull/1100)) by @fendor
31 |
32 | ## 2.5.3
33 |
34 | - Split out packaging action
35 | ([#1080](https://github.com/haskell/vscode-haskell/pull/1080)) by @fendor
36 | - Add Session Loading style to list of known configs
37 | ([#1077](https://github.com/haskell/vscode-haskell/pull/1077)) by @fendor
38 | - Tooling update
39 | ([#1043](https://github.com/haskell/vscode-haskell/pull/1043)) by @bzm3r
40 | - Add `haskell.plugin.fourmolu.config.path` option
41 | ([#987](https://github.com/haskell/vscode-haskell/pull/987)) by @georgefst
42 |
43 | ## 2.5.2
44 |
45 | - Includes changes of the 2.4.3 release
46 |
47 | ## 2.5.1
48 |
49 | - Includes changes of the 2.4.2 release
50 |
51 | ## 2.5.0
52 |
53 | - Add tracking of cabal files
54 | ([#618](https://github.com/haskell/vscode-haskell/pull/618)) by @fendor
55 |
56 | ## 2.4.3
57 |
58 | - Address invalid byte sequence error #1022
59 | ([#1022](https://github.com/haskell/vscode-haskell/pull/1022)) by @felixlinker
60 | - Always set the cwd for the executable (#1011)
61 | ([#1011](https://github.com/haskell/vscode-haskell/pull/1011)) by @fendor
62 |
63 | ## 2.4.2
64 |
65 | - Add stan plugin option #1000
66 | ([#1000](https://github.com/haskell/vscode-haskell/pull/1000)) by @fendor
67 | - Probe for GHCup binary wrt #962
68 | ([#963](https://github.com/haskell/vscode-haskell/pull/963)) by @hasufell
69 | - Bump old hls version and upgrade test runner to macos-latest
70 | ([#960](https://github.com/haskell/vscode-haskell/pull/960)) by @July541
71 | - Increase time limitation to make test on Windows more stable
72 | ([#959](https://github.com/haskell/vscode-haskell/pull/959)) by @July541
73 | - Update release docs for refreshing CI tokens
74 | ([#942](https://github.com/haskell/vscode-haskell/pull/942)) by @fendor
75 |
76 | ## 2.4.1
77 |
78 | - Downgrade vscode-languageclient
79 | ([#934](https://github.com/haskell/vscode-haskell/pull/934)) by @fendor
80 | - Bump vscode to 1.80.0
81 | ([#912](https://github.com/haskell/vscode-haskell/pull/912)) by @July541
82 |
83 | ## 2.4.0
84 |
85 | - Prepare release 2.4.0
86 | ([#906](https://github.com/haskell/vscode-haskell/pull/906)) by @VeryMilkyJoe
87 | - Simplify tests
88 | ([#904](https://github.com/haskell/vscode-haskell/pull/904)) by @July541
89 | - Remove unused code
90 | ([#898](https://github.com/haskell/vscode-haskell/pull/898)) by @fendor
91 | - Remove hoogle command from vscode extension
92 | ([#896](https://github.com/haskell/vscode-haskell/pull/896)) by @fendor
93 | - Update readme
94 | ([#886](https://github.com/haskell/vscode-haskell/pull/886)) by @VeryMilkyJoe
95 | - Fix broken tests
96 | ([#880](https://github.com/haskell/vscode-haskell/pull/880)) by @July541
97 | - Update README.md: clarify how to use Stack with vscode-haskell extension
98 | ([#874](https://github.com/haskell/vscode-haskell/pull/874)) by @miguel-negrao
99 | - Remove debugger tools from CI
100 | ([#873](https://github.com/haskell/vscode-haskell/pull/873)) by @fendor
101 | - Refactor tests to work correctly
102 | ([#872](https://github.com/haskell/vscode-haskell/pull/872)) by @July541
103 | - Downgrade vscode language client to 7.0.0
104 | ([#853](https://github.com/haskell/vscode-haskell/pull/853)) by @fendor
105 | - Update badge url for VSCode Marketplace
106 | ([#851](https://github.com/haskell/vscode-haskell/pull/851)) by @fendor
107 |
108 | ## 2.2.4
109 |
110 | - Downgrade vscode language client to 7.0.0
111 | ([#843](https://github.com/haskell/vscode-haskell/pull/853)) by @fendor
112 |
113 | ## 2.2.3
114 |
115 | - Prepare release 2.2.3
116 | ([#843](https://github.com/haskell/vscode-haskell/pull/843)) by @fendor
117 | - Add new plugins fields
118 | ([#842](https://github.com/haskell/vscode-haskell/pull/842)) by @fendor
119 | - Migrate to eslint
120 | ([#782](https://github.com/haskell/vscode-haskell/pull/782)) by @fendor
121 | - Bump minor versions of package dependencies
122 | ([#781](https://github.com/haskell/vscode-haskell/pull/781)) by @fendor
123 | - Update unsupported GHC doc link
124 | ([#776](https://github.com/haskell/vscode-haskell/pull/776)) by @limaak
125 | - Fix release CI
126 | ([#775](https://github.com/haskell/vscode-haskell/pull/775)) by @fendor
127 | - Fix mistake in generated ChangeLog
128 | ([#774](https://github.com/haskell/vscode-haskell/pull/774)) by @fendor
129 |
130 | ## 2.2.2
131 |
132 | - Add link to HLS installation webpage
133 | ([#751](https://github.com/haskell/vscode-haskell/pull/751)) by @fendor
134 | - Change scope of serverExecutablePath to machine-overridable
135 | ([#742](https://github.com/haskell/vscode-haskell/pull/742)) by @fendor
136 | - Add Fourmolu config property
137 | ([#736](https://github.com/haskell/vscode-haskell/pull/736)) by @georgefst
138 | - Add missing configuration options for the latest HLS version
139 | ([#717](https://github.com/haskell/vscode-haskell/pull/717)) by @fendor
140 | - Change sensible to sensitive
141 | ([#709](https://github.com/haskell/vscode-haskell/pull/709)) by @ploeh
142 |
143 | ## 2.2.1
144 |
145 | - Fix test-suite for new GHCUp release
146 | ([#672](https://github.com/haskell/vscode-haskell/pull/672)) by @fendor
147 | - Bump webpack from 5.73.0 to 5.74.0
148 | ([#657](https://github.com/haskell/vscode-haskell/pull/657)) by @fendor
149 | - Bump typescript from 4.4.0 to 4.7.4
150 | ([#657](https://github.com/haskell/vscode-haskell/pull/657)) by @fendor
151 | - Bump @types/node from 18.0.4 to 18.6.1
152 | ([#657](https://github.com/haskell/vscode-haskell/pull/657)) by @fendor
153 | - Bump @typescript-eslint/eslint-plugin from 5.30.6 to 5.31.0
154 | ([#657](https://github.com/haskell/vscode-haskell/pull/657)) by @fendor
155 | - Bump @typescript-eslint/parser from 5.30.6 to 5.31.0
156 | ([#657](https://github.com/haskell/vscode-haskell/pull/657)) by @fendor
157 | - Bump prettier from 2.6.2 to 2.7.1
158 | ([#657](https://github.com/haskell/vscode-haskell/pull/657)) by @fendor
159 | - Bump mocha from 9.2.1 to 10.0.0
160 | ([#657](https://github.com/haskell/vscode-haskell/pull/657)) by @fendor
161 | - Add dependabot.yml
162 | ([#633](https://github.com/haskell/vscode-haskell/pull/633)) by @fendor
163 | - Replace x32 with ia32 for Architecture matching
164 | ([#631](https://github.com/haskell/vscode-haskell/pull/631)) by @fendor
165 | - Toolchain management dialog: add hint for beginners
166 | ([#621](https://github.com/haskell/vscode-haskell/pull/621)) by @runeksvendsen
167 | - Fix trace.server option
168 | ([#617](https://github.com/haskell/vscode-haskell/pull/617)) by @coltenwebb
169 | - Add TOC
170 | ([#615](https://github.com/haskell/vscode-haskell/pull/615)) by @hasufell
171 | - Cleanups
172 | ([#605](https://github.com/haskell/vscode-haskell/pull/605)) by @hasufell
173 | - Link to VSCode settings page
174 | ([#603](https://github.com/haskell/vscode-haskell/pull/603)) by @hasufell
175 | - Refactor toInstall shenanigans
176 | ([#600](https://github.com/haskell/vscode-haskell/pull/600)) by @hasufell
177 | - Fix confusing download dialog popup
178 | ([#599](https://github.com/haskell/vscode-haskell/pull/599)) by @hasufell
179 | - More troubleshooting
180 | ([#598](https://github.com/haskell/vscode-haskell/pull/598)) by @hasufell
181 |
182 | ## 2.2.0
183 |
184 | - Bump version to 2.2.0 (Syncs up pre-release and release version)
185 | ([#594](https://github.com/haskell/vscode-haskell/pull/594)) by @fendor
186 |
187 | ## 2.0.1
188 |
189 | - Bad error message when ghcup is not installed
190 | ([#591](https://github.com/haskell/vscode-haskell/pull/591)) by @hasufell
191 | - Better error message if we can't find a HLS version for a given GHC
192 | ([#588](https://github.com/haskell/vscode-haskell/pull/588)) by @hasufell
193 | - Properly convert release metadata from json
194 | ([#585](https://github.com/haskell/vscode-haskell/pull/585)) by @fendor
195 | - Ignore missing entries in Release Metadata
196 | ([#582](https://github.com/haskell/vscode-haskell/pull/582)) by @fendor
197 | - Add Tool class and print stacktraces
198 | ([#579](https://github.com/haskell/vscode-haskell/pull/579)) by @fendor
199 | - List Env Vars we care about only
200 | ([#578](https://github.com/haskell/vscode-haskell/pull/578)) by @fendor
201 | - Prepare pre-release 2.1.0
202 | ([#574](https://github.com/haskell/vscode-haskell/pull/574)) by @fendor
203 | - Enable pre-release feature for VSX Marketplace
204 | ([#573](https://github.com/haskell/vscode-haskell/pull/573)) by @fendor
205 | - Add prettier script
206 | ([#566](https://github.com/haskell/vscode-haskell/pull/566)) by @fendor
207 | - Remove accidental run command
208 | ([#565](https://github.com/haskell/vscode-haskell/pull/565)) by @fendor
209 | - Upgrade dependencies
210 | ([#564](https://github.com/haskell/vscode-haskell/pull/564)) by @fendor
211 | - Add new configuration options for rename plugin
212 | ([#563](https://github.com/haskell/vscode-haskell/pull/563)) by @OliverMadine
213 | - Introduce 'haskell.toolchain' setting
214 | ([#562](https://github.com/haskell/vscode-haskell/pull/562)) by @hasufell
215 | - Improve
216 | ([#558](https://github.com/haskell/vscode-haskell/pull/558)) by @hasufell
217 | - Remove stdout/sterr from user error message
218 | ([#556](https://github.com/haskell/vscode-haskell/pull/556)) by @fendor
219 | - Fix npm security issue
220 | ([#555](https://github.com/haskell/vscode-haskell/pull/555)) by @fendor
221 | - No colour output for GHCup
222 | ([#554](https://github.com/haskell/vscode-haskell/pull/554)) by @fendor
223 | - Add eval plugin configuration
224 | ([#549](https://github.com/haskell/vscode-haskell/pull/549)) by @xsebek
225 | - Manage all the Haskell things
226 | ([#547](https://github.com/haskell/vscode-haskell/pull/547)) by @hasufell
227 | - Consider user installed HLSes (e.g. via ghcup compile)
228 | ([#543](https://github.com/haskell/vscode-haskell/pull/543)) by @hasufell
229 | - Update README.MD GHC support
230 | ([#537](https://github.com/haskell/vscode-haskell/pull/537)) by @cptwunderlich
231 | - fix: change deprecated Haskell Platform install link to GHCup
232 | ([#536](https://github.com/haskell/vscode-haskell/pull/536)) by @HEIGE-PCloud
233 | - Update HLS installation method
234 | ([#533](https://github.com/haskell/vscode-haskell/pull/533)) by @hasufell
235 | - Fixes related with paths
236 | ([#518](https://github.com/haskell/vscode-haskell/pull/518)) by @jneira
237 | - Reorganize troubleshooting section
238 | ([#516](https://github.com/haskell/vscode-haskell/pull/516)) by @jneira
239 |
240 | ## 1.8.0
241 |
242 | This release includes some interesting new features:
243 |
244 | - You can now pass custom environment variables to the lsp server
245 | with the `haskell.serverEnvironment` config option per project basis,
246 | thanks to [@jacobprudhomme](https://github.com/jacobprudhomme).
247 | - For example: `"haskell.serverEnvironment": { "XDG_CACHE_HOME": "/path/to/my/cache" }`
248 | - With this version the extension will try to use the newer lsp server version
249 | which supports the ghc used by the project being loaded, thanks to [@mduerig](https://github.com/mduerig)
250 | - WARNING: This will suppose it will use an older version than the latest one,
251 | without its features and bug fixes.
252 | - The extension has lot of more log traces now, which hopefully will help to
253 | identify the cause of issues
254 |
255 | ### Pull requests merged for 1.8.0
256 |
257 | - Update supported ghc versions for hls-1.5.1
258 | ([#514](https://github.com/haskell/vscode-haskell/pull/514)) by @jneira
259 | - Fix hole_severity option: Use integer instead of string
260 | ([#511](https://github.com/haskell/vscode-haskell/pull/511)) by @mirko-plowtech
261 | - Update issue templates
262 | ([#509](https://github.com/haskell/vscode-haskell/pull/509)) by @jneira
263 | - Add traces for download hls
264 | ([#508](https://github.com/haskell/vscode-haskell/pull/508)) by @jneira
265 | - support old hls versions compatible with the requested ghc version
266 | ([#506](https://github.com/haskell/vscode-haskell/pull/506)) by @mduerig
267 | - Fix ci: ensure we have a supported ghc version in PATH
268 | ([#496](https://github.com/haskell/vscode-haskell/pull/496)) by @jneira
269 | - Trace environment variables
270 | ([#495](https://github.com/haskell/vscode-haskell/pull/495)) by @jneira
271 | - Pass environment variables to LSP
272 | ([#494](https://github.com/haskell/vscode-haskell/pull/494)) by @jacobprudhomme
273 | - Reorganize README
274 | ([#491](https://github.com/haskell/vscode-haskell/pull/491)) by @jneira
275 | - Fix error handling of server exec discovery in windows
276 | ([#486](https://github.com/haskell/vscode-haskell/pull/486)) by @jneira
277 | - Bump versions of ts, cheerio, mocha
278 | ([#485](https://github.com/haskell/vscode-haskell/pull/485)) by @jneira
279 | - Improve serverExecutablePath description and error when pointing to a directory
280 | ([#484](https://github.com/haskell/vscode-haskell/pull/484)) by @jneira
281 | - Add integration smoke test
282 | ([#481](https://github.com/haskell/vscode-haskell/pull/481)) by @jneira
283 | - Setup the test suite
284 | ([#475](https://github.com/haskell/vscode-haskell/pull/475)) by @jneira
285 |
286 | ## 1.7.1
287 |
288 | - Bug fix release due to #471 and fixed with #469 thanks to [@berberman](https://github.com/berberman)
289 |
290 | ## 1.7.0
291 |
292 | - Add an option to set server command line arguments thanks to [@cdsmith](https://github.com/cdsmith)
293 | - It includes a new config option `haskell.serverExtraArgs` to being able to pass extra argument to the lsp server executable
294 | - Update config options to match last haskell-language-server version
295 | - It removes `haskell.diagnosticsOnChange` and `haskell.formatOnImportOn` cause they were unused in the server
296 | - It adds `haskell.checkProject`, `haskell.maxCompletions` and `haskell.plugin.refineImports.globalOn`
297 | - Fix showDocumentation command thanks to [@pranaysashank](https://github.com/pranaysashank)
298 | - It fixes partially showing the documentation directly in vscode. The documentation is rendered but internal links still does not work
299 | - Two config options has been added: `haskell.openDocumentationInHackage` and `haskell.openSourceInHackage` with default value `true`
300 | - So documentation will be opened using the hackage url in an external navigator by default
301 | - If you prefer having them in vscode you will need to change them to `false`
302 | - Create output channel only if there are no existing clients thanks to [@pranaysashank](https://github.com/pranaysashank)
303 | - This fixes the creation of several output channels for the extension
304 |
305 | ## 1.6.1
306 |
307 | - Fix wrapper call to get project ghc version in windows with spaces in path ()
308 |
309 | ## 1.6.0
310 |
311 | - Bump up vscode version to 1.52.0 (#424) by [@berberman](https://github.com/berberman)
312 | - To match the lsp spec version used in haskell-language-version and fix
313 |
314 | ## 1.5.1
315 |
316 | - Add much more logging in the client side, configured with `haskell.trace.client`
317 | - Fix error handling of `working out project ghc` and a bug when the path to the executable contains spaces (See #421)
318 | - And dont use a shell to spawn the subprocess in non windows systems
319 | - Show the progress as a cancellable notification
320 | - Add commands `Start Haskell LSP server` and `Stop Haskell LSP server`
321 |
322 | ## 1.5.0
323 |
324 | - Emit warning about limited support for ghc-9.x on hls executable download
325 | - Fix `working out project ghc` progress notificacion
326 | - Fix tactics config, thanks to @isovector
327 | - Update server config to match haskell-language-server-1.3.0 one
328 |
329 | ## 1.4.0
330 |
331 | - Restore `resource` scope for `haskell.serverExecutablePath` temporary. The `machine` scope will be set again after giving users a period of time to let them adapt theirs workflows and changing or adding some option in the extension itself to help that adjustement (see #387).
332 |
333 | ## 1.3.0
334 |
335 | - Add `haskell.releasesURL` option to override where to look for HLS releases search for HLS downloads, thanks to @soiamsoNG
336 | - With this version _the only supported lsp server variant is [`haskell-language-server`](https://github.com/haskell/haskell-language-server)_
337 | - Add support for generic plugin configuration. Thanks to it, each plugin capability (diagnostics, code actions, code lenses, etc) or the entire plugin can be disabled
338 | - Add some plugin specic options:
339 | - [wingman](https://haskellwingman.dev/) (aka tactics) plugin
340 | - `haskell.plugin.tactic.config.features`: Feature set used by the plugin
341 | - `haskell.plugin.tactics.config.hole_severity`: The severity to use when showing hole diagnostics
342 | - `haskell.plugin.tactic.config.max_use_ctor_actions`: Maximum number of `Use constructor ` code actions that can appear
343 | - `haskell.plugin.tactics.config.timeout_duration`: The timeout for Wingman actions, in seconds
344 | - completions
345 | - `haskell.plugin.ghcide-completions.config.autoExtendOn`: Extends the import list automatically when completing a out-of-scope identifier
346 | - `haskell.plugin.ghcide-completions.config.snippetsOn`: Inserts snippets when using code completions
347 | - type signature lenses - `haskell.plugin.ghcide-type-lenses.config.mode`: Control how type lenses are shown
348 | - The option `haskell.serverExecutablePath` has now `machine` scope, so it can be only changed globally by the user. It avoids a potential security vulnerability as folders containing `.vscode/settings.json` with that option could execute arbitrary programs.
349 | - Deprecated options:
350 | - `haskell.hlintOn`: use `haskell.plugin.hlint.globalOn` instead.
351 | - `haskell.completionSnippetsOn`: use `haskell.plugin.ghcide-completions.config.snippetsOn`
352 | - Fixed a small typo that caused the server not to be loaded in `.lhs` files, thanks to @Max7cd
353 |
354 | ## 1.2.0
355 |
356 | - Add option to open local documentation on Hackage (@DunetsNM)
357 | - Add `haskell.updateBehaviour` option to configure when to check for updates
358 | (@WorldSEnder)
359 | - Use locally installed servers on connection failure (@WorldSEnder)
360 |
361 | ## 1.1.0
362 |
363 | - Add Fourmolu as a plugin formatter provider (@georgefst)
364 | - Remove the `haskell.enable` configuration option, since VS Code now allows
365 | you to disable extensions on a per workspace basis
366 | - Display errors when fetching from the GitHub API properly
367 |
368 | ## 1.0.1
369 |
370 | - Switch the default formatter to Ormolu to match haskell-language-server
371 | - Fix `haskell.serverExecutablePath` not working with absolute paths on Windows
372 | (@winestone)
373 | - Improve the help text and error message when `haskell.serverExecutablePath`
374 | is not found
375 | - Fix the rendering of the markdown table in the README (@Darren8098)
376 |
377 | ## 1.0.0
378 |
379 | - vscode-haskell now lives under the Haskell organisation
380 | - Can now download zip archived binaries, which the Windows binaries are now distributed as
381 | - Improve README (@pepeiborra @jaspervdj)
382 |
383 | ## 0.1.1
384 |
385 | - Fix the restart server and import identifier commands
386 |
387 | ## 0.1.0
388 |
389 | `vscode-hie-server`/`Haskell Language Server` is now just Haskell, and will soon
390 | be published under the Haskell organisation as `haskell-vscode`.
391 | This release makes haskell-language-server the default langauge server of choice
392 | and automatically downloads and installs binaries. Installation from source is
393 | still supported though and any binaries located on your PATH for the selected
394 | langauge server will be used instead.
395 |
396 | ### Important!
397 |
398 | As part of this, your configuration may be reset as the keys move from
399 | `languageServerHaskell.completionSnippetsOn` to `haskell.completionSnippetsOn`.
400 |
401 | - Fix the document and source browser
402 | - Remove obselete commands that are no longer supported by any of the language
403 | servers
404 | - Show type command
405 | - Insert type command
406 | - HaRe commands
407 | - Case split commands
408 |
409 | ## 0.0.40
410 |
411 | Change the way the backend is configured, simplifying it.
412 |
413 | - remove wrapper scripts (hie-vscode.sh/hie-vscode.bat)
414 | - dropdown choice between `haskell-ide-engine`, `haskell-language-server` or
415 | `ghcide` in the `hieVariant` setting.
416 | - this can be overridden by an explicit `hieExecutablePath`, as before.
417 |
418 | ## 0.0.39
419 |
420 | Remove verbose logging option, it is not longer supported.
421 |
422 | ## 0.0.38
423 |
424 | Bump dependencies
425 |
426 | ## 0.0.37
427 |
428 | Trying again, working 0.0.35
429 |
430 | - Add Restart command (@gdziadkiewicz)
431 | - Add Ormolu as a formatter option (@DavSanchez)
432 | - Update README
433 |
434 | ## 0.0.36
435 |
436 | - Roll back to 0.0.34
437 |
438 | ## 0.0.35
439 |
440 | - Add Restart command (@gdziadkiewicz)
441 | - Add Ormolu as a formatter option (@DavSanchez)
442 | - Update README
443 |
444 | ## 0.0.34
445 |
446 | - Remove --lsp parameter from hie-vscode.bat
447 |
448 | ## 0.0.33
449 |
450 | - Introduced configuration setting `noLspParam`, default `false` to control
451 | setting the `--lsp` flag for the hie server. So by default we will set the
452 | command line argument for the server, but it can be turned off.
453 |
454 | ## 0.0.32
455 |
456 | - Re-enable the `--lsp` flag for the hie server
457 | - Update some deps for security vulnerabilities
458 |
459 | ## 0.0.31
460 |
461 | - Log to stderr (vscode output) by default, add option for logfile (@bubba)
462 |
463 | ## 0.0.30
464 |
465 | - Bundle using webpack (@chrismwendt)
466 | - Bump protocol version to 3.15 prerelease (@alanz)
467 | This allows working progress reporting from hie.
468 | - Update casesplit plugin (@Avi-D-coder)
469 |
470 | ## 0.0.29
471 |
472 | - bump protocol version to 3.15 (prerelease) (@alanz)
473 | - upgrade deps, including avoiding vulnerabilities on lodash (@alanz)
474 | - warn about compile time and wrapped hie (@janat08)
475 |
476 | ## 0.0.28
477 |
478 | - remove unused `lsp` flag (@bubba)
479 | - do not start `hie` if `hie-wrapper` crashes (@bubba)
480 | - Expose diagnosticsOnChange option for settings (Frederik Ramcke)
481 | - Avoid CVE on `extend` package
482 | - Enable displaying window progress (@bubba)
483 |
484 | ## 0.0.27
485 |
486 | - Re-enable search feature for documentation (@anonimitoraf)
487 | Accesed via `ctrl-f`.
488 |
489 | ## 0.0.26
490 |
491 | - Show documentation content using Webview API (@EdAllonby)
492 | - npm audit fix (@alanz)
493 |
494 | ## 0.0.25
495 |
496 | - Add vsce dependency to "Contributing" document (@EdAllonby)
497 | - Add formatterProvider config (@bubba)
498 | - Bugfix for stack version on windows (@beauzeaux)
499 | - Update settings to match hie version 0.7.0.0 (@alanz)
500 | - npm audit fix (@bubba)
501 |
502 | ## 0.0.24
503 |
504 | - Add snippet config option (@bubba)
505 |
506 | ## 0.0.23
507 |
508 | - Fix multi-process issue, where vscode would launch multiple hie instances.
509 | By @kfigiela
510 |
511 | ## 0.0.22
512 |
513 | - Add configuration option to enable liquid haskell processing. This
514 | is a preview feature of hie from
515 | ca2d3eaa19da8ec9d55521b461d8e2e8cffee697 on 2019-09-05.
516 |
517 | ## 0.0.21
518 |
519 | - Remove languageServerHaskell.useHieWrapper, We now use hie-wrapper
520 | by default.
521 | - Update the vscode-languageclient to v4.4.0
522 | - Fix #98 Import identifier insertion line `moduleLine` is now the
523 | first line that is (trimmed) `where` or ends with `where` or ends
524 | with `)where`. (@mpilgrem)
525 |
526 | ## 0.0.20
527 |
528 | - Add the case-split function (@txsmith). Required hie >= 0.2.1.0
529 | - Update the vscode-languageclient to v4.2.0 (@Bubba)
530 | - Use the hie-wrapper executable now installed with hie to choose the
531 | right version of hie to use for the given project.
532 |
533 | ## 0.0.19
534 |
535 | - Fix hie launch on windows with logging off (#90). Thanks @Tehnix.
536 |
537 | ## 0.0.18
538 |
539 | - Support GHC 8.4.3 in the wrapper file
540 | - The `languageServerHaskell.trace.server` parameter now affects
541 | `/tmp/hie.log`, as well as ghc-mod `--vomit` output.
542 | - Add an Import identifier command, by @chrismwendt
543 |
544 | ## 0.0.17
545 |
546 | - Support GHC 8.4.2 in the wrapper file
547 | - Update dependencies to avoid security vulnerability.
548 | - Use os.tmpdir() for the hie.log file
549 |
550 | ## 0.0.15
551 |
552 | Support the new webview-api for the documentation browser, thanks to @AlexeyRaga.
553 |
554 | ## 0.0.14
555 |
556 | Revert `vscode-languageclient` dependency to version 3.5.0, since version 4.x for some
557 | reason breaks the documentation browser.
558 |
559 | ## 0.0.13
560 |
561 | Add configuration to set the path to your HIE executable, if it's not on your PATH. Note
562 | that this adds the `--lsp` argument to the call of this executable.
563 |
564 | ## 0.0.12
565 |
566 | Add configuration to enable/disable HIE, useful for multi-root workspaces.
567 |
568 | ## 0.0.11
569 |
570 | Add additional marketplace categories.
571 |
572 | ## 0.0.10
573 |
574 | Add support for multi-root workspaces, thanks to @tehnix. See the README section
575 | on [_Using multi-root workspaces_](https://github.com/alanz/vscode-hie-server#using-multi-root-workspaces) for more.
576 |
577 | ## 0.0.9
578 |
579 | Publish to the visual studio marketplace through travis CI via git tags. E.g.
580 | `git tag -a 0.0.9 -m "Version 0.0.9"` and then `git push origin 0.0.9`.
581 |
582 | ## 0.0.8
583 |
584 | Add new haskell-ide-engine logo, thanks to @damienflament
585 |
586 | Add rudimentary support for detecting the project GHC version and using the
587 | appropriate hie version. This currently only works on Linux (contributors on
588 | other platforms, please jump in with appropriate scripts) and requires
589 | `haskell-ide-engine` built via the `Makefile` added in
590 | https://github.com/haskell/haskell-ide-engine/pull/447. Thanks to @Tehnix
591 |
592 | ## 0.0.7
593 |
594 | Update `package-lock.json` to fresh dependencies.
595 |
596 | Add show type _of selected expression_ on hover feature, by @halhenke
597 |
598 | Added options for how to display the same information when using the show type
599 | command menu, by @halhenke
600 |
601 | Moved the configuration setting about showing trace information into the proper
602 | scope, by @halhenke
603 |
604 | ## 0.0.6
605 |
606 | Update `package-lock.json` to fresh dependencies.
607 |
608 | Update the installation check on Win32 platforms, by @soylens.
609 |
610 | Use `tslint` on the plugin sources, by @halhenke.
611 |
612 | ## 0.0.5
613 |
614 | Stop the output channel from taking focus on startup, by @Tehnix and @halhenke
615 |
616 | Rework and improve the document layout, for gihub and the marketplace, by @Tehnix
617 |
618 | Set up Travis testing an potential auto-deply to marketplace, by @Tehnix
619 |
620 | ## 0.0.4
621 |
622 | Show documents in a tab, by @AlexeyRaga
623 |
624 | Add a configuration option to enable/disable `hlint`.
625 |
626 | ## 0.0.3
627 |
628 | Add "Haskell: Show type" command, bound to Ctrl-alt-t (Cmd-alt-t on mac). This
629 | calls the `ghc-mod` `type` command on the current cursor location or highlighted
630 | region. Thanks to @AlexeyRaga
631 |
632 | Add a check for having the `hie` executable in the path on startup, to prevent
633 | an endless failure to start if the executable is not there. Thanks to @DavidEichman
634 |
635 | ## 0.0.2
636 |
637 | Add some HaRe commands, accesible via the command palette.
638 |
639 | ## 0.0.1
640 |
641 | Initial release of haskell-ide-engine VS Code extension, for brave pioneers.
642 |
--------------------------------------------------------------------------------
/GenChangelogs.hs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env cabal
2 | {- cabal:
3 | build-depends: base, bytestring, process, text, github, time >= 1.9
4 | -}
5 |
6 | {-# LANGUAGE OverloadedStrings #-}
7 | {-# LANGUAGE RecordWildCards #-}
8 |
9 | import Control.Monad
10 | import qualified Data.ByteString.Char8 as BS
11 | import Data.List
12 | import Data.Maybe
13 | import qualified Data.Text as T
14 | import Data.Time.Format.ISO8601
15 | import Data.Time.LocalTime
16 | import GitHub
17 | import System.Environment
18 | import System.Process
19 |
20 | main = do
21 | callCommand "git fetch --tags"
22 | tag <- last . lines <$>
23 | readProcess "git" ["tag", "--list", "--sort=v:refname"] ""
24 |
25 | lastDateStr <- last . lines <$> readProcess "git" ["show", "-s", "--format=%cI", "-1", tag] ""
26 | lastDate <- zonedTimeToUTC <$> iso8601ParseM lastDateStr
27 |
28 | args <- getArgs
29 | let githubReq = case args of
30 | [] -> github'
31 | token:_ -> github (OAuth $ BS.pack token)
32 | prs <- githubReq $ pullRequestsForR "haskell" "vscode-haskell" stateClosed FetchAll
33 | let prsAfterLastTag = either (error . show)
34 | (foldMap (\pr -> [pr | inRange pr, isNotDependabot pr]))
35 | prs
36 | inRange pr
37 | | Just mergedDate <- simplePullRequestMergedAt pr = mergedDate > lastDate
38 | | otherwise = False
39 |
40 | isNotDependabot SimplePullRequest{..} =
41 | untagName (simpleUserLogin simplePullRequestUser) /= "dependabot[bot]"
42 |
43 | forM_ prsAfterLastTag $ \SimplePullRequest{..} ->
44 | putStrLn $ T.unpack $ "- " <> simplePullRequestTitle <> "\n" <>
45 | " ([#" <> T.pack (show $ unIssueNumber simplePullRequestNumber) <> "](" <> getUrl simplePullRequestHtmlUrl <> "))" <>
46 | " by @" <> untagName (simpleUserLogin simplePullRequestUser)
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Based on https://github.com/Microsoft/vscode-languageserver-node-example
2 | which has the following license requirement :
3 |
4 | -----------------------------------------------------
5 |
6 | Copyright (c) Microsoft Corporation
7 |
8 | All rights reserved.
9 |
10 | MIT License
11 |
12 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Haskell for Visual Studio Code
2 |
3 | [](https://marketplace.visualstudio.com/items?itemName=haskell.haskell)
4 |
5 | This extension adds language support for [Haskell](https://haskell.org), powered by the [Haskell Language Server](https://github.com/haskell/haskell-language-server).
6 | As almost all features are provided by the server you might find interesting read its [documentation](https://haskell-language-server.readthedocs.io).
7 |
8 | ## Table of Contents
9 |
10 | - [Haskell for Visual Studio Code](#haskell-for-visual-studio-code)
11 | - [Table of Contents](#table-of-contents)
12 | - [Setup](#setup)
13 | - [Features](#features)
14 | - [Requirements](#requirements)
15 | - [Configuration options](#configuration-options)
16 | - [Path to server executable](#path-to-server-executable)
17 | - [Security warning](#security-warning)
18 | - [Set additional environment variables for the server](#set-additional-environment-variables-for-the-server)
19 | - [Downloaded binaries](#downloaded-binaries)
20 | - [Setting a specific toolchain](#setting-a-specific-toolchain)
21 | - [Supported GHC versions](#supported-ghc-versions)
22 | - [Using multi-root workspaces](#using-multi-root-workspaces)
23 | - [Investigating and reporting problems](#investigating-and-reporting-problems)
24 | - [FAQ](#faq)
25 | - [Troubleshooting](#troubleshooting)
26 | - [Check issues and tips in the haskell-language-server project](#check-issues-and-tips-in-the-haskell-language-server-project)
27 | - [Restarting the language server](#restarting-the-language-server)
28 | - [`Failed to get project GHC version` on darwin M1 with stack](#failed-to-get-project-ghc-version-on-darwin-m1-with-stack)
29 | - [`GHC ABIs don't match`](#ghc-abis-dont-match)
30 | - [Using an old configuration](#using-an-old-configuration)
31 | - [Stack/Cabal/GHC can not be found](#stackcabalghc-can-not-be-found)
32 | - [Contributing](#contributing)
33 | - [Release Notes](#release-notes)
34 |
35 | ## Setup
36 |
37 | This Extension comes with "batteries"-included and can manage your Haskell Language Server installations for you,
38 | powered by [GHCup](https://www.haskell.org/ghcup/).
39 | Installation of [GHCup](https://www.haskell.org/ghcup/) can not happen automatically, so if you want your HLS installations to be
40 | managed by the Extension, you will have to follow the [installation instructions for GHCup](https://www.haskell.org/ghcup/).
41 |
42 | **Note:** Make sure you have a working `ghcup` installation, before launching the Extension.
43 |
44 | ## Features
45 |
46 | You can watch demos for some of these features [here](https://haskell-language-server.readthedocs.io/en/latest/features.html#demos).
47 |
48 | - Warning and error diagnostics from GHC
49 | - Type information and documentation on hover
50 | - Jump to definition: [for now only for local code definitions](https://github.com/haskell/haskell-language-server/issues/708)
51 | - Document symbols
52 | - Highlight references in document
53 | - Code completion
54 | - Show documentation and sources in hackage
55 | - Formatting via [Brittany](https://github.com/lspitzner/brittany), [Floskell](https://github.com/ennocramer/floskell), [Fourmolu](https://github.com/fourmolu/fourmolu), [Ormolu](https://github.com/tweag/ormolu) or [Stylish Haskell](https://github.com/haskell/stylish-haskell)
56 | - [Multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces) support
57 | - [Code evaluation](https://haskell-language-server.readthedocs.io/en/latest/features.html#code-evaluation), see its [Tutorial](https://github.com/haskell/haskell-language-server/blob/master/plugins/hls-eval-plugin/README.md)
58 | - [Integration with](https://haskell-language-server.readthedocs.io/en/latest/features.html#retrie-integration) [retrie](https://hackage.haskell.org/package/retrie), a powerful, easy-to-use codemodding tool
59 | - [Code lenses for explicit import lists](https://haskell-language-server.readthedocs.io/en/latest/features.html#explicit-import-lists)
60 | - [Generate functions from type signatures, and intelligently complete holes using](https://haskell-language-server.readthedocs.io/en/latest/features.html#wingman) [Wingman (tactics)](https://github.com/haskell/haskell-language-server/tree/master/plugins/hls-tactics-plugin)
61 | - [Integration](https://haskell-language-server.readthedocs.io/en/latest/features.html#hlint) with [hlint](https://github.com/ndmitchell/hlint), the most used haskell linter, to show diagnostics and apply hints via [apply-refact](https://github.com/mpickering/apply-refact)
62 | - [Module name suggestions](https://haskell-language-server.readthedocs.io/en/latest/features.html#module-names) for insertion or correction
63 | - [Call hierarchy support](https://haskell-language-server.readthedocs.io/en/latest/features.html#call-hierarchy)
64 |
65 | ## Requirements
66 |
67 | - For standalone `.hs`/`.lhs` files, [ghc](https://www.haskell.org/ghc/) must be installed and on the PATH. The easiest way to install it is with [ghcup](https://www.haskell.org/ghcup/).
68 | - For Cabal based projects, both ghc and [cabal-install](https://www.haskell.org/cabal/) must be installed and on the PATH. It can also be installed with [ghcup](https://www.haskell.org/ghcup/) or [Chocolatey](https://www.haskell.org/platform/windows.html) on Windows.
69 | - For Stack based projects, [stack](http://haskellstack.org) must be installed and on the PATH and must be [configured to use GHC binaries installed by GHCup](https://www.haskell.org/ghcup/guide/#stack-integration).
70 | - If you are installing from an offline VSIX file, you need to install [language-haskell](https://github.com/JustusAdam/language-haskell) too after installation (either from the marketplace or offline).
71 | - Alternatively, you can let the extension manage your entire toolchain automatically (you'll be asked on first startup) via
72 | [ghcup](https://www.haskell.org/ghcup/), which should be pre-installed
73 |
74 | ## Configuration options
75 |
76 | For a general picture about the server configuration, including the project setup, [you can consult the server documentation about the topic](https://haskell-language-server.readthedocs.io/en/latest/configuration.html).
77 |
78 | For information on how to set configuration in VSCode, see [here](https://code.visualstudio.com/docs/getstarted/settings).
79 |
80 | ### Path to server executable
81 |
82 | If your server is manually installed and not on your path, you can also manually set the path to the executable.
83 |
84 | ```json
85 | "haskell.serverExecutablePath": "~/.local/bin/haskell-language-server"
86 | ```
87 |
88 | There are a few placeholders which will be expanded:
89 |
90 | - `~`, `${HOME}` and `${home}` will be expanded into your users' home folder.
91 | - `${workspaceFolder}` and `${workspaceRoot}` will expand into your current project root.
92 |
93 | #### Security warning
94 |
95 | The option has `machine-overridable` scope so it can be changed per workspace.
96 | This supposes it could be used to execute arbitrary programs adding a `.vscode/settings.json` in the workspace folder including this option with the appropriate path.
97 | See [#387](https://github.com/haskell/vscode-haskell/issues/387) for more details.
98 |
99 | ### Set additional environment variables for the server
100 |
101 | You can add additional environment variables for the lsp server using the configuration option `haskell.serverEnvironment`. For example, to change the cache directory used by the server you could set:
102 |
103 | ```json
104 | { "haskell.serverEnvironment": { "XDG_CACHE_HOME": "/path/to/my/cache" } }
105 | ```
106 |
107 | as the server uses the XDG specification for cache directories.
108 |
109 | The environment _only will be visible for the lsp server_, not for other extension tasks like find the server executable.
110 |
111 | ### Downloaded binaries
112 |
113 | This extension will download `haskell-language-server` binaries and the rest of the toolchain if you selected to use GHCup during
114 | first start. Check the `haskell.manageHLS` setting.
115 |
116 | It will then download the newest version of haskell-language-server which has support for the required ghc.
117 | That means it could use an older version than the latest one, without the last features and bug fixes.
118 | For example, if a project needs ghc-8.10.4 the extension will download and use haskell-language-server-1.4.0, the latest version which supported ghc-8.10.4. Even if the latest global haskell language-server version is 1.5.1.
119 |
120 | If you have disk space issues, check `ghcup gc --help`.
121 |
122 | You can also instruct the extension to use a different installation directory for the toolchain,
123 | e.g. to not interfere with system GHCup installation. Depending on your platform, add the full
124 | resolved path like so:
125 |
126 | ```json
127 | "haskell.serverEnvironment": {
128 | "GHCUP_INSTALL_BASE_PREFIX": "/home/foo/.config/Code/User/globalStorage/haskell.haskell/"
129 | }
130 | ```
131 |
132 | The internal storage paths for the extension depend on the platform:
133 |
134 | | Platform | Path |
135 | | -------- | ------------------------------------------------------------------------------- |
136 | | macOS | `~/Library/Application\ Support/Code/User/globalStorage/haskell.haskell/.ghcup` |
137 | | Windows | `%APPDATA%\Code\User\globalStorage\haskell.haskell\ghcup` |
138 | | Linux | `$HOME/.config/Code/User/globalStorage/haskell.haskell/.ghcup` |
139 |
140 | If you want to manage HLS yourself, set `haskell.manageHLS` to `PATH` and make sure HLS is in your PATH
141 | or set `haskell.serverExecutablePath` (overrides all other settings) to a valid executable.
142 |
143 | If you need to set mirrors for ghcup download info, check the settings `haskell.metadataURL` and `haskell.releasesURL`.
144 |
145 | ### Setting a specific toolchain
146 |
147 | When `manageHLS` is set to `GHCup`, you can define a specific toolchain (`hls`, `ghc`, `cabal` and `stack`),
148 | either globally or per project. E.g.:
149 |
150 | ```json
151 | {
152 | "haskell.toolchain": {
153 | "hls": "1.6.1.1",
154 | "cabal": "recommended",
155 | "stack": null
156 | }
157 | }
158 | ```
159 |
160 | This means:
161 |
162 | 1. install the `ghc` version corresponding to the project (default, because it's omitted)
163 | 2. install `hls` 1.6.1.1
164 | 3. install the recommended `cabal` version from ghcup
165 | 4. don't install any `stack` version
166 |
167 | Another config could be:
168 |
169 | ```json
170 | {
171 | "haskell.toolchain": {
172 | "ghc": "9.2.2",
173 | "hls": "latest",
174 | "cabal": "recommended"
175 | }
176 | }
177 | ```
178 |
179 | Meaning:
180 |
181 | 1. install `ghc` 9.2.2 regardless of what the project requires
182 | 2. always install latest `hls`, even if it doesn't support the given GHC version
183 | 3. install recommended `cabal`
184 | 4. install latest `stack` (default, because it's omitted)
185 |
186 | The defaults (when omitted) are as follows:
187 |
188 | 1. install the project required `ghc` (corresponding to `with-compiler` setting in `cabal.project` for example)
189 | 2. install the latest `hls` version that supports the project required ghc version
190 | 3. install latest `cabal`
191 | 4. install latest `stack`
192 |
193 | When a the value is `null`, the extension will refrain from installing it.
194 |
195 | At last, if you don't want `ghcup` to manage any of the external tools except `hls`, you can use:
196 |
197 | ```json
198 | {
199 | "haskell.toolchain": {
200 | "ghc": null,
201 | "cabal": null,
202 | "stack": null
203 | }
204 | }
205 | ```
206 |
207 | ### Supported GHC versions
208 |
209 | You can check each GHC version's support status and the policy followed for deprecations [here](https://haskell-language-server.readthedocs.io/en/latest/support/ghc-version-support.html).
210 |
211 | [Building from source](https://haskell-language-server.readthedocs.io/en/latest/installation.html) may support more versions!
212 |
213 | The exact list of binaries can be checked in the last release of haskell-language-server:
214 |
215 | ## Using multi-root workspaces
216 |
217 | First, check out [what multi-root workspaces](https://code.visualstudio.com/docs/editor/multi-root-workspaces) are. The idea of using multi-root workspaces, is to be able to work on several different Haskell projects, where the GHC version or stackage LTS could differ, and have it work smoothly.
218 |
219 | The language server is now started for each workspace folder you have in your multi-root workspace, and several configurations are on a resource (i.e. folder) scope, instead of window (i.e. global) scope.
220 |
221 | ## Investigating and reporting problems
222 |
223 | 1. Go to extensions and right click `Haskell` and choose `Extensions Settings`
224 | 2. Scroll down to `Haskell › Trace: Server` and set it to `messages`.
225 | 3. Set `Haskell › Trace: Client` to `debug`. It will print all the environment variables so take care it does not contain any sensitive information before sharing it.
226 | 4. Restart vscode and reproduce your problem
227 | 5. Go to the main menu and choose `View -> Output` (`Ctrl + Shift + U`)
228 | 6. On the new Output panel that opens on the right side in the drop down menu choose `Haskell ()`
229 |
230 | Please include the output when filing any issues on the [haskell-language-server](https://github.com/haskell/haskell-language-server/issues/new) issue tracker.
231 |
232 | ## FAQ
233 |
234 | ### Troubleshooting
235 |
236 | #### Check issues and tips in the haskell-language-server project
237 |
238 | - Usually the error or unexpected behaviour is already reported in the [haskell language server issue tracker](https://github.com/haskell/haskell-language-server/issues). Finding the issue could be useful to help resolve it and sometimes includes a workaround for the issue.
239 | - You can also check the [troubleshooting section](https://haskell-language-server.readthedocs.io/en/latest/troubleshooting.html) in the server documentation.
240 |
241 | #### Restarting the language server
242 |
243 | - Sometimes the language server might get stuck in a rut and stop responding to your latest changes.
244 | Should this occur you can try restarting the language server with Ctrl shift P/⌘ shift P > Restart Haskell LSP Server.
245 |
246 | #### `Failed to get project GHC version` on darwin M1 with stack
247 |
248 | If you have installed stack via the official cannels, the binary will not be M1 native, but x86 and trigger the rosetta compatibility layer. GHCup provides real stack/HLS M1 binaries, so make sure you install stack via GHCup. Also see https://github.com/haskell/haskell-language-server/issues/2864
249 |
250 | #### `GHC ABIs don't match`
251 |
252 | If you are using certain versions of GHC (such as 9.0.2 or 9.2.4) while running Stack, you may encounter this issue due to an outdated GHC bindist installed by Stack. The vscode-haskell extension does not support the use of GHC binaries managed by Stack. Therefore, it is recommended to configure Stack to allow GHCup to install these binaries instead. Please [refer to the instructions provided by ghcup for this purpose](https://www.haskell.org/ghcup/guide/#stack-integration).
253 |
254 | If you really want to use GHC 9.0.2 managed by Stack, force it to install the fixed bindist (that includes profiling libs) by adding this to your stack.yaml (depending on your platform):
255 |
256 | ```yml
257 | setup-info:
258 | ghc:
259 | linux64-tinfo6:
260 | 9.0.2:
261 | url: 'https://downloads.haskell.org/ghc/9.0.2/ghc-9.0.2a-x86_64-fedora27-linux.tar.xz'
262 | ```
263 |
264 | Now make sure to remove cached/installed libraries to avoid getting segfaults at runtime.
265 |
266 | As a final workaround, you can try to compile HLS from source (the extension should pick it up) via ghcup, see [https://haskell-language-server.readthedocs.io/en/stable/installation.html#ghcup](https://haskell-language-server.readthedocs.io/en/stable/installation.html#ghcup). In any case, the recommended approach is to let GHCup install the GHC binaries.
267 |
268 | #### `hGetContents: invalid argument (invalid byte sequence)`
269 |
270 | This problem was encountered on darwin M2 with ghcup.
271 | Should you see the error that the "Haskell server crashed 5 times in the last 3 minutes," you can check the Haskell output to see whether this was due to an error mentioning `hGetContents: invalid argument (invalid byte sequence)`.
272 | If this is the case, setting `terminal.integrated.detectLocale` to `off` might resolve your issue.
273 |
274 | #### Using an old configuration
275 |
276 | If something just doesn't work, but you recall an old configuration that did, you
277 | may try forcing a particular setting, e.g. by disabling all automatic installations
278 | except HLS:
279 |
280 | ```json
281 | "haskell.toolchain": {
282 | "hls": "1.6.1.0",
283 | "ghc": null,
284 | "cabal": null,
285 | "stack": null
286 | }
287 | ```
288 |
289 | #### Stack/Cabal/GHC can not be found
290 |
291 | Also make sure GHCup is installed and in `$PATH`. If you're not starting VSCode from the terminal, you might need to add `${HOME}/.ghcup/bin` to PATH like so:
292 |
293 | ```json
294 | "haskell.serverEnvironment": {
295 | "PATH": "${HOME}/.ghcup/bin:$PATH"
296 | }
297 | ```
298 |
299 | ## Contributing
300 |
301 | If you want to help, get started by reading [Contributing](./docs/Contributing.md) for more details.
302 |
303 | ## Release Notes
304 |
305 | See the [Changelog](./Changelog.md) for more details.
306 |
--------------------------------------------------------------------------------
/default.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import {} }:
2 | with pkgs;
3 |
4 | # Please run `yarn import` first to generate yarn.lock!
5 | pkgs.mkYarnPackage rec {
6 | name = "haskell";
7 | src = ./.;
8 | packageJSON = ./package.json;
9 | yarnLock = ./yarn.lock;
10 |
11 | installPhase = ''
12 | mkdir -p "$out/dist"
13 | yarn vscode:prepublish --output-path "$out/dist"
14 | mv deps/${name}/package.json "$out"
15 | '';
16 |
17 | distPhase = ''
18 | true
19 | '';
20 | }
21 |
--------------------------------------------------------------------------------
/docs/Contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Dependencies and Building
4 |
5 | Run `yarn install` in the project root to install the development dependencies.
6 |
7 | You can also package up the extension with
8 |
9 | - `yarn global add vsce` to get the Extension Manager,
10 | - `yarn install` to build the extension
11 | - `vsce package` which creates an extension package at `haskell-.vsix`.
12 |
13 | _Note:_ that if you get errors running `vsce package`, it might help running `yarn run pretest` directly, since that gives the actual error output of the TypeScript compilation.
14 |
15 | ## Developing inside VS Code
16 |
17 | - Launch VS Code, press `File` > `Open Folder`, open the `vscode-haskell` folder;
18 | - press `F5` to open a new window with the `vscode-haskell` loaded (this will overwrite existing ones, e.g. from the marketplace);
19 | - open a Haskell file with the **new** editor to test the LSP client;
20 |
21 | You are now ready to make changes and debug. You can,
22 |
23 | - set breakpoints in your code inside `src/extension.ts` to debug your extension;
24 | - find output from your extension in the debug console;
25 | - make changes to the code, and then
26 | - relaunch the extension from the debug toolbar
27 |
28 | _Note_: you can also reload (`Ctrl+R` or `Cmd+R` on macOS) the VS Code window with your extension to load your changes
29 |
30 | #### Formatting
31 |
32 | [prettier](https://prettier.io) is automatically run on each commit via husky. If you are developing within VS Code, the settings are set to auto format on save.
33 | The configurations for prettier are located in `.prettierrc`.
34 |
35 | ## Navigating the Files
36 |
37 | A brief overview of the files,
38 |
39 | - `package.json` contains the basic information about the package, see [the full manifest for more](https://code.visualstudio.com/docs/extensionAPI/extension-manifest), such as telling VS Code which scope the LSP works on (Haskell and Literate Haskell in our case), and possible configuration
40 | - `src/extension.ts` is the main entrypoint to the extension, and handles launching the language server.
41 | - `src/hlsBinaries.ts` handles automatically installing the pre-built `haskell-language-server` binaries
42 | - `src/utils.ts` has some functions for downloading files and checking if executables are on the path
43 | - `src/docsBrowser.ts` contains the logic for displaying the documentation browser (e.g. hover over a type like `mapM_` and click `Documentation` or `Source`)
44 |
45 | ## Helpful Reading Material
46 |
47 | We recommend checking out [Your First VS Code Extension](https://code.visualstudio.com/docs/extensions/example-hello-world) and [Creating a Language Server](https://code.visualstudio.com/docs/extensions/example-language-server) for some introduction to VS Code extensions.
48 |
--------------------------------------------------------------------------------
/docs/Release.md:
--------------------------------------------------------------------------------
1 | # Release Checklist
2 |
3 | Follow this list for items that must be completed for release of the `vscode-haskell` extension.
4 |
5 | - [ ] Run `yarn audit` for security vulnerabilities.
6 | - Fix vulnerabilities.
7 | - [ ] Run `yarn outdated` to find outdated package version, review what needs to be updated.
8 | - `yarn upgrade-interactive` and `yarn upgrade-interactive --latest` is helpful here.
9 | - [ ] Run `cat test/testdata/schema/*/vscode-extension-schema.golden.json | jq --sort-keys -s add` in the `haskell-language-server` repo and add new configuration items.
10 | - [ ] SemVer Compatible Version Bump in `package.json`
11 | - For pre-releases, we follow the version convention at: https://code.visualstudio.com/api/working-with-extensions/publishing-extension#prerelease-extensions. We use `major.EVEN_NUMBER.patch` for release versions and `major.ODD_NUMBER.patch` for pre-release versions. For example: `2.0.*` for release and `2.1.*` for pre-release.
12 | - [ ] Update ChangeLog.md. The output of `./GenChangelogs.hs` usually suffices.
13 | - [ ] Update the README.md to have no outdated information.
14 | - [ ] Make sure CI is succeeding.
15 | - [ ] Perform the release by creating a [release in Github](https://github.com/haskell/vscode-haskell/releases)
16 | - Github actions will automatically release the extension to VSCode- and VSX-Marketplace.
17 | - If you want to create a pre-release, create a [pre-release in Github](https://github.com/haskell/vscode-haskell/releases). The github action will perform the appropriate actions automatically and publish the pre-release of the extension to VSCode- and VSX-Marketplace.
18 |
19 | ## Branching policy
20 |
21 | Sometimes there is a release (stable) and pre-release (unstable) at the same time and we need to do a release for the stable release and sometimes we need to do a release for the pre-release series.
22 | To simplify the release management, the following policy is in place:
23 |
24 | - The branch `master` contains the current pre-release
25 | - As such, its `package.json` must always have the form `major.ODD_NUMBER.patch`
26 | - Dependency version bumps are automatically performed by dependabot against `master`
27 | - For each release, a tag must be created
28 | - Stable releases are located on a separate branch called `release-`
29 | - Before a release, the branch is rebased on top of current master
30 | - For each stable release, a tag must be created of the form `major.EVEN_NUMBER.patch`
31 |
32 | ## Release CI
33 |
34 | The release CI has access tokens for VSX Marketplace and the VSCode Marketplace.
35 |
36 | Seemingly, the VSX Marketplace token does not expire. If it is lost for some reason, follow the steps below. Fendor can also generate a VSX Marketplace token.
37 |
38 | The latter needs to be refreshed once a year.
39 |
40 | - Send an email to `committee@haskell.org` requesting the token
41 | - Include your public GPG key so they can send you the token encrypted
42 | - Update the repository secrets
43 | - People from the [@haskell-ide](https://github.com/orgs/haskell/teams/haskell-ide) have full access to the vscode-haskell repo and can update secrets
44 |
45 | Last time the VSCode Marketplace token was updated: 2023-08-17
46 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import globals from 'globals';
2 | import eslint from '@eslint/js';
3 | import tseslint from 'typescript-eslint';
4 |
5 | export default tseslint.config(
6 | { files: ['**/*.{js,mjs,cjs,ts}'] },
7 | {
8 | languageOptions: {
9 | globals: globals.node,
10 | parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname },
11 | },
12 | },
13 | {
14 | // disables type checking for this file only
15 | files: ['eslint.config.mjs'],
16 | extends: [tseslint.configs.disableTypeChecked],
17 | },
18 | eslint.configs.recommended,
19 | tseslint.configs.recommendedTypeChecked,
20 | {
21 | rules: {
22 | // turn off these lints as we access workspaceConfiguration fields.
23 | // So far, there was no bug found with these unsafe accesses.
24 | '@typescript-eslint/no-unsafe-assignment': 'off',
25 | '@typescript-eslint/no-unsafe-member-access': 'off',
26 | // Sometimes, the 'any' just saves too much time.
27 | '@typescript-eslint/no-explicit-any': 'off',
28 | '@typescript-eslint/no-floating-promises': 'error',
29 | '@typescript-eslint/no-unused-vars': [
30 | 'error',
31 | {
32 | args: 'all',
33 | argsIgnorePattern: '^_',
34 | caughtErrors: 'all',
35 | caughtErrorsIgnorePattern: '^_',
36 | destructuredArrayIgnorePattern: '^_',
37 | varsIgnorePattern: '^_',
38 | ignoreRestSiblings: true,
39 | },
40 | ],
41 | },
42 | },
43 | );
44 |
--------------------------------------------------------------------------------
/images/hls-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/haskell/vscode-haskell/HEAD/images/hls-logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "haskell",
3 | "displayName": "Haskell",
4 | "description": "Haskell language support powered by the Haskell Language Server",
5 | "version": "2.7.0",
6 | "license": "MIT",
7 | "publisher": "haskell",
8 | "engines": {
9 | "vscode": "^1.102.0"
10 | },
11 | "keywords": [
12 | "language",
13 | "haskell",
14 | "cabal",
15 | "stack",
16 | "lsp",
17 | "multi-root ready"
18 | ],
19 | "homepage": "https://github.com/haskell/vscode-haskell",
20 | "repository": "https://github.com/haskell/vscode-haskell.git",
21 | "bugs": {
22 | "url": "https://github.com/haskell/vscode-haskell/issues"
23 | },
24 | "categories": [
25 | "Programming Languages",
26 | "Formatters",
27 | "Linters",
28 | "Other"
29 | ],
30 | "icon": "images/hls-logo.png",
31 | "galleryBanner": {
32 | "color": "#22172A",
33 | "theme": "dark"
34 | },
35 | "activationEvents": [
36 | "onLanguage:haskell",
37 | "onLanguage:literate haskell",
38 | "onLanguage:cabal"
39 | ],
40 | "main": "./dist/extension",
41 | "contributes": {
42 | "languages": [
43 | {
44 | "id": "haskell",
45 | "aliases": [
46 | "Haskell",
47 | "haskell"
48 | ],
49 | "extensions": [
50 | ".hs"
51 | ]
52 | },
53 | {
54 | "id": "cabal",
55 | "aliases": [
56 | "Cabal"
57 | ],
58 | "extensions": [
59 | ".cabal"
60 | ]
61 | },
62 | {
63 | "id": "literate haskell",
64 | "aliases": [
65 | "Literate Haskell",
66 | "literate Haskell"
67 | ],
68 | "extensions": [
69 | ".lhs"
70 | ]
71 | }
72 | ],
73 | "configuration": {
74 | "type": "object",
75 | "title": "Haskell",
76 | "properties": {
77 | "haskell.formattingProvider": {
78 | "scope": "resource",
79 | "type": "string",
80 | "enum": [
81 | "brittany",
82 | "floskell",
83 | "fourmolu",
84 | "ormolu",
85 | "stylish-haskell",
86 | "none"
87 | ],
88 | "default": "ormolu",
89 | "description": "The formatter to use when formatting a document or range. Ensure the plugin is enabled."
90 | },
91 | "haskell.cabalFormattingProvider": {
92 | "scope": "resource",
93 | "type": "string",
94 | "enum": [
95 | "cabal-gild",
96 | "cabal-fmt",
97 | "none"
98 | ],
99 | "default": "cabal-gild",
100 | "description": "The formatter to use when formatting a document or range of a cabal formatter. Ensure the plugin is enabled."
101 | },
102 | "haskell.openDocumentationInHackage": {
103 | "scope": "resource",
104 | "type": "boolean",
105 | "default": true,
106 | "description": "When opening 'Documentation' for external libraries, open in hackage by default. Set to false to instead open in vscode."
107 | },
108 | "haskell.openSourceInHackage": {
109 | "scope": "resource",
110 | "type": "boolean",
111 | "default": true,
112 | "description": "When opening 'Source' for external libraries, open in hackage by default. Set to false to instead open in vscode."
113 | },
114 | "haskell.trace.server": {
115 | "scope": "resource",
116 | "type": "string",
117 | "enum": [
118 | "off",
119 | "messages",
120 | "verbose"
121 | ],
122 | "default": "off",
123 | "description": "Traces the communication between VS Code and the language server."
124 | },
125 | "haskell.trace.client": {
126 | "scope": "resource",
127 | "type": "string",
128 | "enum": [
129 | "off",
130 | "error",
131 | "info",
132 | "debug"
133 | ],
134 | "default": "info",
135 | "description": "Sets the log level in the client side."
136 | },
137 | "haskell.logFile": {
138 | "scope": "resource",
139 | "type": "string",
140 | "default": "",
141 | "description": "If set, redirects the logs to a file."
142 | },
143 | "haskell.releasesURL": {
144 | "scope": "resource",
145 | "type": "string",
146 | "default": "",
147 | "description": "An optional URL to override where ghcup checks for HLS-GHC compatibility list (usually at: https://raw.githubusercontent.com/haskell/ghcup-metadata/master/hls-metadata-0.0.1.json)"
148 | },
149 | "haskell.metadataURL": {
150 | "scope": "resource",
151 | "type": "string",
152 | "default": "",
153 | "description": "An optional URL to override where ghcup checks for tool download info (usually at: https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-0.0.7.yaml)"
154 | },
155 | "haskell.releasesDownloadStoragePath": {
156 | "scope": "resource",
157 | "type": "string",
158 | "default": "",
159 | "markdownDescription": "An optional path where downloaded metadata will be stored. Check the default value [here](https://github.com/haskell/vscode-haskell#downloaded-binaries)"
160 | },
161 | "haskell.serverExecutablePath": {
162 | "scope": "machine-overridable",
163 | "type": "string",
164 | "default": "",
165 | "markdownDescription": "Manually set a language server executable. Can be something on the $PATH or the full path to the executable itself. Works with `~,` `${HOME}` and `${workspaceFolder}`."
166 | },
167 | "haskell.serverExtraArgs": {
168 | "scope": "resource",
169 | "type": "string",
170 | "default": "",
171 | "markdownDescription": "Pass additional arguments to the language server."
172 | },
173 | "haskell.ghcupExecutablePath": {
174 | "scope": "resource",
175 | "type": "string",
176 | "default": "",
177 | "markdownDescription": "Manually set a ghcup executable path."
178 | },
179 | "haskell.serverEnvironment": {
180 | "scope": "resource",
181 | "type": "object",
182 | "default": {},
183 | "markdownDescription": "Define environment variables for the language server."
184 | },
185 | "haskell.promptBeforeDownloads": {
186 | "scope": "machine",
187 | "type": "boolean",
188 | "default": "true",
189 | "markdownDescription": "Prompt before performing any downloads."
190 | },
191 | "haskell.manageHLS": {
192 | "scope": "resource",
193 | "type": "string",
194 | "default": "PATH",
195 | "description": "How to manage/find HLS installations.",
196 | "enum": [
197 | "GHCup",
198 | "PATH"
199 | ],
200 | "enumDescriptions": [
201 | "Will use ghcup and manage Haskell toolchain in the default location (usually '~/.ghcup')",
202 | "Discovers HLS and other executables in system PATH"
203 | ]
204 | },
205 | "haskell.toolchain": {
206 | "scope": "resource",
207 | "type": "object",
208 | "default": {},
209 | "description": "When manageHLS is set to GHCup, this can overwrite the automatic toolchain configuration with a more specific one. When a tool is omitted, the extension will manage the version (for 'ghc' we try to figure out the version the project requires). The format is '{\"tool\": \"version\", ...}'. 'version' accepts all identifiers that 'ghcup' accepts."
210 | },
211 | "haskell.upgradeGHCup": {
212 | "scope": "resource",
213 | "type": "boolean",
214 | "default": true,
215 | "description": "Whether to upgrade GHCup automatically when 'manageHLS' is set to 'GHCup'."
216 | },
217 | "haskell.checkProject": {
218 | "scope": "resource",
219 | "type": "boolean",
220 | "default": true,
221 | "description": "Whether to typecheck the entire project on load. It could drive to bad performance in large projects."
222 | },
223 | "haskell.sessionLoading": {
224 | "scope": "resource",
225 | "type": "string",
226 | "enum": [
227 | "singleComponent",
228 | "multipleComponents"
229 | ],
230 | "default": "singleComponent",
231 | "description": "Preferred approach for loading package components. Setting this to 'multiple components' (EXPERIMENTAL) allows the build tool (such as `cabal` or `stack`) to [load multiple components at once](https://github.com/haskell/cabal/pull/8726), which is a significant improvement.",
232 | "enumDescriptions": [
233 | "Always load only a single component at a time. This is the most reliable option if you encountered any issues with the other options.",
234 | "Prefer a multiple component session, if the build tool supports it. At the moment, only `cabal` supports multiple components session loading. If the `cabal` version does not support loading multiple components at once, we gracefully fall back to \"singleComponent\" mode."
235 | ]
236 | },
237 | "haskell.supportCabalFiles": {
238 | "scope": "resource",
239 | "default": "automatic",
240 | "type": "string",
241 | "enum": [
242 | "enable",
243 | "disable",
244 | "automatic"
245 | ],
246 | "description": "Enable Language Server support for `.cabal` files. Requires Haskell Language Server version >= 1.9.0.0.",
247 | "enumDescriptions": [
248 | "Enable Language Server support for `.cabal` files",
249 | "Disable Language Server support for `.cabal` files",
250 | "Enable Language Server support for `.cabal` files if the HLS version supports it."
251 | ]
252 | },
253 | "haskell.maxCompletions": {
254 | "scope": "resource",
255 | "default": 40,
256 | "type": "integer",
257 | "description": "Maximum number of completions sent to the editor."
258 | },
259 | "haskell.plugin.alternateNumberFormat.globalOn": {
260 | "default": true,
261 | "description": "Enables alternateNumberFormat plugin",
262 | "scope": "resource",
263 | "type": "boolean"
264 | },
265 | "haskell.plugin.cabal-fmt.config.path": {
266 | "default": "cabal-fmt",
267 | "markdownDescription": "Set path to 'cabal-fmt' executable",
268 | "scope": "resource",
269 | "type": "string"
270 | },
271 | "haskell.plugin.cabal-gild.config.path": {
272 | "default": "cabal-gild",
273 | "markdownDescription": "Set path to 'cabal-gild' executable",
274 | "scope": "resource",
275 | "type": "string"
276 | },
277 | "haskell.plugin.cabal.codeActionsOn": {
278 | "default": true,
279 | "description": "Enables cabal code actions",
280 | "scope": "resource",
281 | "type": "boolean"
282 | },
283 | "haskell.plugin.cabal.completionOn": {
284 | "default": true,
285 | "description": "Enables cabal completions",
286 | "scope": "resource",
287 | "type": "boolean"
288 | },
289 | "haskell.plugin.cabal.diagnosticsOn": {
290 | "default": true,
291 | "description": "Enables cabal diagnostics",
292 | "scope": "resource",
293 | "type": "boolean"
294 | },
295 | "haskell.plugin.cabal.hoverOn": {
296 | "default": true,
297 | "description": "Enables cabal hover",
298 | "scope": "resource",
299 | "type": "boolean"
300 | },
301 | "haskell.plugin.cabal.symbolsOn": {
302 | "default": true,
303 | "description": "Enables cabal symbols",
304 | "scope": "resource",
305 | "type": "boolean"
306 | },
307 | "haskell.plugin.cabalHaskellIntegration.globalOn": {
308 | "default": true,
309 | "description": "Enables cabalHaskellIntegration plugin",
310 | "scope": "resource",
311 | "type": "boolean"
312 | },
313 | "haskell.plugin.callHierarchy.globalOn": {
314 | "default": true,
315 | "description": "Enables callHierarchy plugin",
316 | "scope": "resource",
317 | "type": "boolean"
318 | },
319 | "haskell.plugin.changeTypeSignature.globalOn": {
320 | "default": true,
321 | "description": "Enables changeTypeSignature plugin",
322 | "scope": "resource",
323 | "type": "boolean"
324 | },
325 | "haskell.plugin.class.codeActionsOn": {
326 | "default": true,
327 | "description": "Enables class code actions",
328 | "scope": "resource",
329 | "type": "boolean"
330 | },
331 | "haskell.plugin.class.codeLensOn": {
332 | "default": true,
333 | "description": "Enables class code lenses",
334 | "scope": "resource",
335 | "type": "boolean"
336 | },
337 | "haskell.plugin.eval.codeActionsOn": {
338 | "default": true,
339 | "description": "Enables eval code actions",
340 | "scope": "resource",
341 | "type": "boolean"
342 | },
343 | "haskell.plugin.eval.codeLensOn": {
344 | "default": true,
345 | "description": "Enables eval code lenses",
346 | "scope": "resource",
347 | "type": "boolean"
348 | },
349 | "haskell.plugin.eval.config.diff": {
350 | "default": true,
351 | "markdownDescription": "Enable the diff output (WAS/NOW) of eval lenses",
352 | "scope": "resource",
353 | "type": "boolean"
354 | },
355 | "haskell.plugin.eval.config.exception": {
356 | "default": false,
357 | "markdownDescription": "Enable marking exceptions with `*** Exception:` similarly to doctest and GHCi.",
358 | "scope": "resource",
359 | "type": "boolean"
360 | },
361 | "haskell.plugin.explicit-fields.codeActionsOn": {
362 | "default": true,
363 | "description": "Enables explicit-fields code actions",
364 | "scope": "resource",
365 | "type": "boolean"
366 | },
367 | "haskell.plugin.explicit-fields.inlayHintsOn": {
368 | "default": true,
369 | "description": "Enables explicit-fields inlay hints",
370 | "scope": "resource",
371 | "type": "boolean"
372 | },
373 | "haskell.plugin.explicit-fixity.globalOn": {
374 | "default": true,
375 | "description": "Enables explicit-fixity plugin",
376 | "scope": "resource",
377 | "type": "boolean"
378 | },
379 | "haskell.plugin.fourmolu.config.external": {
380 | "default": false,
381 | "markdownDescription": "Call out to an external \"fourmolu\" executable, rather than using the bundled library.",
382 | "scope": "resource",
383 | "type": "boolean"
384 | },
385 | "haskell.plugin.fourmolu.config.path": {
386 | "default": "fourmolu",
387 | "markdownDescription": "Set path to executable (for \"external\" mode).",
388 | "scope": "resource",
389 | "type": "string"
390 | },
391 | "haskell.plugin.gadt.globalOn": {
392 | "default": true,
393 | "description": "Enables gadt plugin",
394 | "scope": "resource",
395 | "type": "boolean"
396 | },
397 | "haskell.plugin.ghcide-code-actions-bindings.globalOn": {
398 | "default": true,
399 | "description": "Enables ghcide-code-actions-bindings plugin",
400 | "scope": "resource",
401 | "type": "boolean"
402 | },
403 | "haskell.plugin.ghcide-code-actions-fill-holes.globalOn": {
404 | "default": true,
405 | "description": "Enables ghcide-code-actions-fill-holes plugin",
406 | "scope": "resource",
407 | "type": "boolean"
408 | },
409 | "haskell.plugin.ghcide-code-actions-imports-exports.globalOn": {
410 | "default": true,
411 | "description": "Enables ghcide-code-actions-imports-exports plugin",
412 | "scope": "resource",
413 | "type": "boolean"
414 | },
415 | "haskell.plugin.ghcide-code-actions-type-signatures.globalOn": {
416 | "default": true,
417 | "description": "Enables ghcide-code-actions-type-signatures plugin",
418 | "scope": "resource",
419 | "type": "boolean"
420 | },
421 | "haskell.plugin.ghcide-completions.config.autoExtendOn": {
422 | "default": true,
423 | "markdownDescription": "Extends the import list automatically when completing a out-of-scope identifier",
424 | "scope": "resource",
425 | "type": "boolean"
426 | },
427 | "haskell.plugin.ghcide-completions.config.snippetsOn": {
428 | "default": true,
429 | "markdownDescription": "Inserts snippets when using code completions",
430 | "scope": "resource",
431 | "type": "boolean"
432 | },
433 | "haskell.plugin.ghcide-completions.globalOn": {
434 | "default": true,
435 | "description": "Enables ghcide-completions plugin",
436 | "scope": "resource",
437 | "type": "boolean"
438 | },
439 | "haskell.plugin.ghcide-hover-and-symbols.hoverOn": {
440 | "default": true,
441 | "description": "Enables ghcide-hover-and-symbols hover",
442 | "scope": "resource",
443 | "type": "boolean"
444 | },
445 | "haskell.plugin.ghcide-hover-and-symbols.symbolsOn": {
446 | "default": true,
447 | "description": "Enables ghcide-hover-and-symbols symbols",
448 | "scope": "resource",
449 | "type": "boolean"
450 | },
451 | "haskell.plugin.ghcide-type-lenses.config.mode": {
452 | "default": "always",
453 | "description": "Control how type lenses are shown",
454 | "enum": [
455 | "always",
456 | "exported",
457 | "diagnostics"
458 | ],
459 | "enumDescriptions": [
460 | "Always displays type lenses of global bindings",
461 | "Only display type lenses of exported global bindings",
462 | "Follows error messages produced by GHC about missing signatures"
463 | ],
464 | "scope": "resource",
465 | "type": "string"
466 | },
467 | "haskell.plugin.ghcide-type-lenses.globalOn": {
468 | "default": true,
469 | "description": "Enables ghcide-type-lenses plugin",
470 | "scope": "resource",
471 | "type": "boolean"
472 | },
473 | "haskell.plugin.hlint.codeActionsOn": {
474 | "default": true,
475 | "description": "Enables hlint code actions",
476 | "scope": "resource",
477 | "type": "boolean"
478 | },
479 | "haskell.plugin.hlint.config.flags": {
480 | "default": [],
481 | "markdownDescription": "Flags used by hlint",
482 | "scope": "resource",
483 | "type": "array"
484 | },
485 | "haskell.plugin.hlint.diagnosticsOn": {
486 | "default": true,
487 | "description": "Enables hlint diagnostics",
488 | "scope": "resource",
489 | "type": "boolean"
490 | },
491 | "haskell.plugin.importLens.codeActionsOn": {
492 | "default": true,
493 | "description": "Enables importLens code actions",
494 | "scope": "resource",
495 | "type": "boolean"
496 | },
497 | "haskell.plugin.importLens.codeLensOn": {
498 | "default": true,
499 | "description": "Enables importLens code lenses",
500 | "scope": "resource",
501 | "type": "boolean"
502 | },
503 | "haskell.plugin.importLens.inlayHintsOn": {
504 | "default": true,
505 | "description": "Enables importLens inlay hints",
506 | "scope": "resource",
507 | "type": "boolean"
508 | },
509 | "haskell.plugin.moduleName.globalOn": {
510 | "default": true,
511 | "description": "Enables moduleName plugin",
512 | "scope": "resource",
513 | "type": "boolean"
514 | },
515 | "haskell.plugin.ormolu.config.external": {
516 | "default": false,
517 | "markdownDescription": "Call out to an external \"ormolu\" executable, rather than using the bundled library",
518 | "scope": "resource",
519 | "type": "boolean"
520 | },
521 | "haskell.plugin.overloaded-record-dot.globalOn": {
522 | "default": true,
523 | "description": "Enables overloaded-record-dot plugin",
524 | "scope": "resource",
525 | "type": "boolean"
526 | },
527 | "haskell.plugin.pragmas-completion.globalOn": {
528 | "default": true,
529 | "description": "Enables pragmas-completion plugin",
530 | "scope": "resource",
531 | "type": "boolean"
532 | },
533 | "haskell.plugin.pragmas-disable.globalOn": {
534 | "default": true,
535 | "description": "Enables pragmas-disable plugin",
536 | "scope": "resource",
537 | "type": "boolean"
538 | },
539 | "haskell.plugin.pragmas-suggest.globalOn": {
540 | "default": true,
541 | "description": "Enables pragmas-suggest plugin",
542 | "scope": "resource",
543 | "type": "boolean"
544 | },
545 | "haskell.plugin.qualifyImportedNames.globalOn": {
546 | "default": true,
547 | "description": "Enables qualifyImportedNames plugin",
548 | "scope": "resource",
549 | "type": "boolean"
550 | },
551 | "haskell.plugin.rename.config.crossModule": {
552 | "default": false,
553 | "markdownDescription": "Enable experimental cross-module renaming",
554 | "scope": "resource",
555 | "type": "boolean"
556 | },
557 | "haskell.plugin.rename.globalOn": {
558 | "default": true,
559 | "description": "Enables rename plugin",
560 | "scope": "resource",
561 | "type": "boolean"
562 | },
563 | "haskell.plugin.retrie.globalOn": {
564 | "default": true,
565 | "description": "Enables retrie plugin",
566 | "scope": "resource",
567 | "type": "boolean"
568 | },
569 | "haskell.plugin.semanticTokens.config.classMethodToken": {
570 | "default": "method",
571 | "description": "LSP semantic token type to use for typeclass methods",
572 | "enum": [
573 | "namespace",
574 | "type",
575 | "class",
576 | "enum",
577 | "interface",
578 | "struct",
579 | "typeParameter",
580 | "parameter",
581 | "variable",
582 | "property",
583 | "enumMember",
584 | "event",
585 | "function",
586 | "method",
587 | "macro",
588 | "keyword",
589 | "modifier",
590 | "comment",
591 | "string",
592 | "number",
593 | "regexp",
594 | "operator",
595 | "decorator"
596 | ],
597 | "enumDescriptions": [
598 | "LSP Semantic Token Type: namespace",
599 | "LSP Semantic Token Type: type",
600 | "LSP Semantic Token Type: class",
601 | "LSP Semantic Token Type: enum",
602 | "LSP Semantic Token Type: interface",
603 | "LSP Semantic Token Type: struct",
604 | "LSP Semantic Token Type: typeParameter",
605 | "LSP Semantic Token Type: parameter",
606 | "LSP Semantic Token Type: variable",
607 | "LSP Semantic Token Type: property",
608 | "LSP Semantic Token Type: enumMember",
609 | "LSP Semantic Token Type: event",
610 | "LSP Semantic Token Type: function",
611 | "LSP Semantic Token Type: method",
612 | "LSP Semantic Token Type: macro",
613 | "LSP Semantic Token Type: keyword",
614 | "LSP Semantic Token Type: modifier",
615 | "LSP Semantic Token Type: comment",
616 | "LSP Semantic Token Type: string",
617 | "LSP Semantic Token Type: number",
618 | "LSP Semantic Token Type: regexp",
619 | "LSP Semantic Token Type: operator",
620 | "LSP Semantic Token Type: decorator"
621 | ],
622 | "scope": "resource",
623 | "type": "string"
624 | },
625 | "haskell.plugin.semanticTokens.config.classToken": {
626 | "default": "class",
627 | "description": "LSP semantic token type to use for typeclasses",
628 | "enum": [
629 | "namespace",
630 | "type",
631 | "class",
632 | "enum",
633 | "interface",
634 | "struct",
635 | "typeParameter",
636 | "parameter",
637 | "variable",
638 | "property",
639 | "enumMember",
640 | "event",
641 | "function",
642 | "method",
643 | "macro",
644 | "keyword",
645 | "modifier",
646 | "comment",
647 | "string",
648 | "number",
649 | "regexp",
650 | "operator",
651 | "decorator"
652 | ],
653 | "enumDescriptions": [
654 | "LSP Semantic Token Type: namespace",
655 | "LSP Semantic Token Type: type",
656 | "LSP Semantic Token Type: class",
657 | "LSP Semantic Token Type: enum",
658 | "LSP Semantic Token Type: interface",
659 | "LSP Semantic Token Type: struct",
660 | "LSP Semantic Token Type: typeParameter",
661 | "LSP Semantic Token Type: parameter",
662 | "LSP Semantic Token Type: variable",
663 | "LSP Semantic Token Type: property",
664 | "LSP Semantic Token Type: enumMember",
665 | "LSP Semantic Token Type: event",
666 | "LSP Semantic Token Type: function",
667 | "LSP Semantic Token Type: method",
668 | "LSP Semantic Token Type: macro",
669 | "LSP Semantic Token Type: keyword",
670 | "LSP Semantic Token Type: modifier",
671 | "LSP Semantic Token Type: comment",
672 | "LSP Semantic Token Type: string",
673 | "LSP Semantic Token Type: number",
674 | "LSP Semantic Token Type: regexp",
675 | "LSP Semantic Token Type: operator",
676 | "LSP Semantic Token Type: decorator"
677 | ],
678 | "scope": "resource",
679 | "type": "string"
680 | },
681 | "haskell.plugin.semanticTokens.config.dataConstructorToken": {
682 | "default": "enumMember",
683 | "description": "LSP semantic token type to use for data constructors",
684 | "enum": [
685 | "namespace",
686 | "type",
687 | "class",
688 | "enum",
689 | "interface",
690 | "struct",
691 | "typeParameter",
692 | "parameter",
693 | "variable",
694 | "property",
695 | "enumMember",
696 | "event",
697 | "function",
698 | "method",
699 | "macro",
700 | "keyword",
701 | "modifier",
702 | "comment",
703 | "string",
704 | "number",
705 | "regexp",
706 | "operator",
707 | "decorator"
708 | ],
709 | "enumDescriptions": [
710 | "LSP Semantic Token Type: namespace",
711 | "LSP Semantic Token Type: type",
712 | "LSP Semantic Token Type: class",
713 | "LSP Semantic Token Type: enum",
714 | "LSP Semantic Token Type: interface",
715 | "LSP Semantic Token Type: struct",
716 | "LSP Semantic Token Type: typeParameter",
717 | "LSP Semantic Token Type: parameter",
718 | "LSP Semantic Token Type: variable",
719 | "LSP Semantic Token Type: property",
720 | "LSP Semantic Token Type: enumMember",
721 | "LSP Semantic Token Type: event",
722 | "LSP Semantic Token Type: function",
723 | "LSP Semantic Token Type: method",
724 | "LSP Semantic Token Type: macro",
725 | "LSP Semantic Token Type: keyword",
726 | "LSP Semantic Token Type: modifier",
727 | "LSP Semantic Token Type: comment",
728 | "LSP Semantic Token Type: string",
729 | "LSP Semantic Token Type: number",
730 | "LSP Semantic Token Type: regexp",
731 | "LSP Semantic Token Type: operator",
732 | "LSP Semantic Token Type: decorator"
733 | ],
734 | "scope": "resource",
735 | "type": "string"
736 | },
737 | "haskell.plugin.semanticTokens.config.functionToken": {
738 | "default": "function",
739 | "description": "LSP semantic token type to use for functions",
740 | "enum": [
741 | "namespace",
742 | "type",
743 | "class",
744 | "enum",
745 | "interface",
746 | "struct",
747 | "typeParameter",
748 | "parameter",
749 | "variable",
750 | "property",
751 | "enumMember",
752 | "event",
753 | "function",
754 | "method",
755 | "macro",
756 | "keyword",
757 | "modifier",
758 | "comment",
759 | "string",
760 | "number",
761 | "regexp",
762 | "operator",
763 | "decorator"
764 | ],
765 | "enumDescriptions": [
766 | "LSP Semantic Token Type: namespace",
767 | "LSP Semantic Token Type: type",
768 | "LSP Semantic Token Type: class",
769 | "LSP Semantic Token Type: enum",
770 | "LSP Semantic Token Type: interface",
771 | "LSP Semantic Token Type: struct",
772 | "LSP Semantic Token Type: typeParameter",
773 | "LSP Semantic Token Type: parameter",
774 | "LSP Semantic Token Type: variable",
775 | "LSP Semantic Token Type: property",
776 | "LSP Semantic Token Type: enumMember",
777 | "LSP Semantic Token Type: event",
778 | "LSP Semantic Token Type: function",
779 | "LSP Semantic Token Type: method",
780 | "LSP Semantic Token Type: macro",
781 | "LSP Semantic Token Type: keyword",
782 | "LSP Semantic Token Type: modifier",
783 | "LSP Semantic Token Type: comment",
784 | "LSP Semantic Token Type: string",
785 | "LSP Semantic Token Type: number",
786 | "LSP Semantic Token Type: regexp",
787 | "LSP Semantic Token Type: operator",
788 | "LSP Semantic Token Type: decorator"
789 | ],
790 | "scope": "resource",
791 | "type": "string"
792 | },
793 | "haskell.plugin.semanticTokens.config.moduleToken": {
794 | "default": "namespace",
795 | "description": "LSP semantic token type to use for modules",
796 | "enum": [
797 | "namespace",
798 | "type",
799 | "class",
800 | "enum",
801 | "interface",
802 | "struct",
803 | "typeParameter",
804 | "parameter",
805 | "variable",
806 | "property",
807 | "enumMember",
808 | "event",
809 | "function",
810 | "method",
811 | "macro",
812 | "keyword",
813 | "modifier",
814 | "comment",
815 | "string",
816 | "number",
817 | "regexp",
818 | "operator",
819 | "decorator"
820 | ],
821 | "enumDescriptions": [
822 | "LSP Semantic Token Type: namespace",
823 | "LSP Semantic Token Type: type",
824 | "LSP Semantic Token Type: class",
825 | "LSP Semantic Token Type: enum",
826 | "LSP Semantic Token Type: interface",
827 | "LSP Semantic Token Type: struct",
828 | "LSP Semantic Token Type: typeParameter",
829 | "LSP Semantic Token Type: parameter",
830 | "LSP Semantic Token Type: variable",
831 | "LSP Semantic Token Type: property",
832 | "LSP Semantic Token Type: enumMember",
833 | "LSP Semantic Token Type: event",
834 | "LSP Semantic Token Type: function",
835 | "LSP Semantic Token Type: method",
836 | "LSP Semantic Token Type: macro",
837 | "LSP Semantic Token Type: keyword",
838 | "LSP Semantic Token Type: modifier",
839 | "LSP Semantic Token Type: comment",
840 | "LSP Semantic Token Type: string",
841 | "LSP Semantic Token Type: number",
842 | "LSP Semantic Token Type: regexp",
843 | "LSP Semantic Token Type: operator",
844 | "LSP Semantic Token Type: decorator"
845 | ],
846 | "scope": "resource",
847 | "type": "string"
848 | },
849 | "haskell.plugin.semanticTokens.config.operatorToken": {
850 | "default": "operator",
851 | "description": "LSP semantic token type to use for operators",
852 | "enum": [
853 | "namespace",
854 | "type",
855 | "class",
856 | "enum",
857 | "interface",
858 | "struct",
859 | "typeParameter",
860 | "parameter",
861 | "variable",
862 | "property",
863 | "enumMember",
864 | "event",
865 | "function",
866 | "method",
867 | "macro",
868 | "keyword",
869 | "modifier",
870 | "comment",
871 | "string",
872 | "number",
873 | "regexp",
874 | "operator",
875 | "decorator"
876 | ],
877 | "enumDescriptions": [
878 | "LSP Semantic Token Type: namespace",
879 | "LSP Semantic Token Type: type",
880 | "LSP Semantic Token Type: class",
881 | "LSP Semantic Token Type: enum",
882 | "LSP Semantic Token Type: interface",
883 | "LSP Semantic Token Type: struct",
884 | "LSP Semantic Token Type: typeParameter",
885 | "LSP Semantic Token Type: parameter",
886 | "LSP Semantic Token Type: variable",
887 | "LSP Semantic Token Type: property",
888 | "LSP Semantic Token Type: enumMember",
889 | "LSP Semantic Token Type: event",
890 | "LSP Semantic Token Type: function",
891 | "LSP Semantic Token Type: method",
892 | "LSP Semantic Token Type: macro",
893 | "LSP Semantic Token Type: keyword",
894 | "LSP Semantic Token Type: modifier",
895 | "LSP Semantic Token Type: comment",
896 | "LSP Semantic Token Type: string",
897 | "LSP Semantic Token Type: number",
898 | "LSP Semantic Token Type: regexp",
899 | "LSP Semantic Token Type: operator",
900 | "LSP Semantic Token Type: decorator"
901 | ],
902 | "scope": "resource",
903 | "type": "string"
904 | },
905 | "haskell.plugin.semanticTokens.config.patternSynonymToken": {
906 | "default": "macro",
907 | "description": "LSP semantic token type to use for pattern synonyms",
908 | "enum": [
909 | "namespace",
910 | "type",
911 | "class",
912 | "enum",
913 | "interface",
914 | "struct",
915 | "typeParameter",
916 | "parameter",
917 | "variable",
918 | "property",
919 | "enumMember",
920 | "event",
921 | "function",
922 | "method",
923 | "macro",
924 | "keyword",
925 | "modifier",
926 | "comment",
927 | "string",
928 | "number",
929 | "regexp",
930 | "operator",
931 | "decorator"
932 | ],
933 | "enumDescriptions": [
934 | "LSP Semantic Token Type: namespace",
935 | "LSP Semantic Token Type: type",
936 | "LSP Semantic Token Type: class",
937 | "LSP Semantic Token Type: enum",
938 | "LSP Semantic Token Type: interface",
939 | "LSP Semantic Token Type: struct",
940 | "LSP Semantic Token Type: typeParameter",
941 | "LSP Semantic Token Type: parameter",
942 | "LSP Semantic Token Type: variable",
943 | "LSP Semantic Token Type: property",
944 | "LSP Semantic Token Type: enumMember",
945 | "LSP Semantic Token Type: event",
946 | "LSP Semantic Token Type: function",
947 | "LSP Semantic Token Type: method",
948 | "LSP Semantic Token Type: macro",
949 | "LSP Semantic Token Type: keyword",
950 | "LSP Semantic Token Type: modifier",
951 | "LSP Semantic Token Type: comment",
952 | "LSP Semantic Token Type: string",
953 | "LSP Semantic Token Type: number",
954 | "LSP Semantic Token Type: regexp",
955 | "LSP Semantic Token Type: operator",
956 | "LSP Semantic Token Type: decorator"
957 | ],
958 | "scope": "resource",
959 | "type": "string"
960 | },
961 | "haskell.plugin.semanticTokens.config.recordFieldToken": {
962 | "default": "property",
963 | "description": "LSP semantic token type to use for record fields",
964 | "enum": [
965 | "namespace",
966 | "type",
967 | "class",
968 | "enum",
969 | "interface",
970 | "struct",
971 | "typeParameter",
972 | "parameter",
973 | "variable",
974 | "property",
975 | "enumMember",
976 | "event",
977 | "function",
978 | "method",
979 | "macro",
980 | "keyword",
981 | "modifier",
982 | "comment",
983 | "string",
984 | "number",
985 | "regexp",
986 | "operator",
987 | "decorator"
988 | ],
989 | "enumDescriptions": [
990 | "LSP Semantic Token Type: namespace",
991 | "LSP Semantic Token Type: type",
992 | "LSP Semantic Token Type: class",
993 | "LSP Semantic Token Type: enum",
994 | "LSP Semantic Token Type: interface",
995 | "LSP Semantic Token Type: struct",
996 | "LSP Semantic Token Type: typeParameter",
997 | "LSP Semantic Token Type: parameter",
998 | "LSP Semantic Token Type: variable",
999 | "LSP Semantic Token Type: property",
1000 | "LSP Semantic Token Type: enumMember",
1001 | "LSP Semantic Token Type: event",
1002 | "LSP Semantic Token Type: function",
1003 | "LSP Semantic Token Type: method",
1004 | "LSP Semantic Token Type: macro",
1005 | "LSP Semantic Token Type: keyword",
1006 | "LSP Semantic Token Type: modifier",
1007 | "LSP Semantic Token Type: comment",
1008 | "LSP Semantic Token Type: string",
1009 | "LSP Semantic Token Type: number",
1010 | "LSP Semantic Token Type: regexp",
1011 | "LSP Semantic Token Type: operator",
1012 | "LSP Semantic Token Type: decorator"
1013 | ],
1014 | "scope": "resource",
1015 | "type": "string"
1016 | },
1017 | "haskell.plugin.semanticTokens.config.typeConstructorToken": {
1018 | "default": "enum",
1019 | "description": "LSP semantic token type to use for type constructors",
1020 | "enum": [
1021 | "namespace",
1022 | "type",
1023 | "class",
1024 | "enum",
1025 | "interface",
1026 | "struct",
1027 | "typeParameter",
1028 | "parameter",
1029 | "variable",
1030 | "property",
1031 | "enumMember",
1032 | "event",
1033 | "function",
1034 | "method",
1035 | "macro",
1036 | "keyword",
1037 | "modifier",
1038 | "comment",
1039 | "string",
1040 | "number",
1041 | "regexp",
1042 | "operator",
1043 | "decorator"
1044 | ],
1045 | "enumDescriptions": [
1046 | "LSP Semantic Token Type: namespace",
1047 | "LSP Semantic Token Type: type",
1048 | "LSP Semantic Token Type: class",
1049 | "LSP Semantic Token Type: enum",
1050 | "LSP Semantic Token Type: interface",
1051 | "LSP Semantic Token Type: struct",
1052 | "LSP Semantic Token Type: typeParameter",
1053 | "LSP Semantic Token Type: parameter",
1054 | "LSP Semantic Token Type: variable",
1055 | "LSP Semantic Token Type: property",
1056 | "LSP Semantic Token Type: enumMember",
1057 | "LSP Semantic Token Type: event",
1058 | "LSP Semantic Token Type: function",
1059 | "LSP Semantic Token Type: method",
1060 | "LSP Semantic Token Type: macro",
1061 | "LSP Semantic Token Type: keyword",
1062 | "LSP Semantic Token Type: modifier",
1063 | "LSP Semantic Token Type: comment",
1064 | "LSP Semantic Token Type: string",
1065 | "LSP Semantic Token Type: number",
1066 | "LSP Semantic Token Type: regexp",
1067 | "LSP Semantic Token Type: operator",
1068 | "LSP Semantic Token Type: decorator"
1069 | ],
1070 | "scope": "resource",
1071 | "type": "string"
1072 | },
1073 | "haskell.plugin.semanticTokens.config.typeFamilyToken": {
1074 | "default": "interface",
1075 | "description": "LSP semantic token type to use for type families",
1076 | "enum": [
1077 | "namespace",
1078 | "type",
1079 | "class",
1080 | "enum",
1081 | "interface",
1082 | "struct",
1083 | "typeParameter",
1084 | "parameter",
1085 | "variable",
1086 | "property",
1087 | "enumMember",
1088 | "event",
1089 | "function",
1090 | "method",
1091 | "macro",
1092 | "keyword",
1093 | "modifier",
1094 | "comment",
1095 | "string",
1096 | "number",
1097 | "regexp",
1098 | "operator",
1099 | "decorator"
1100 | ],
1101 | "enumDescriptions": [
1102 | "LSP Semantic Token Type: namespace",
1103 | "LSP Semantic Token Type: type",
1104 | "LSP Semantic Token Type: class",
1105 | "LSP Semantic Token Type: enum",
1106 | "LSP Semantic Token Type: interface",
1107 | "LSP Semantic Token Type: struct",
1108 | "LSP Semantic Token Type: typeParameter",
1109 | "LSP Semantic Token Type: parameter",
1110 | "LSP Semantic Token Type: variable",
1111 | "LSP Semantic Token Type: property",
1112 | "LSP Semantic Token Type: enumMember",
1113 | "LSP Semantic Token Type: event",
1114 | "LSP Semantic Token Type: function",
1115 | "LSP Semantic Token Type: method",
1116 | "LSP Semantic Token Type: macro",
1117 | "LSP Semantic Token Type: keyword",
1118 | "LSP Semantic Token Type: modifier",
1119 | "LSP Semantic Token Type: comment",
1120 | "LSP Semantic Token Type: string",
1121 | "LSP Semantic Token Type: number",
1122 | "LSP Semantic Token Type: regexp",
1123 | "LSP Semantic Token Type: operator",
1124 | "LSP Semantic Token Type: decorator"
1125 | ],
1126 | "scope": "resource",
1127 | "type": "string"
1128 | },
1129 | "haskell.plugin.semanticTokens.config.typeSynonymToken": {
1130 | "default": "type",
1131 | "description": "LSP semantic token type to use for type synonyms",
1132 | "enum": [
1133 | "namespace",
1134 | "type",
1135 | "class",
1136 | "enum",
1137 | "interface",
1138 | "struct",
1139 | "typeParameter",
1140 | "parameter",
1141 | "variable",
1142 | "property",
1143 | "enumMember",
1144 | "event",
1145 | "function",
1146 | "method",
1147 | "macro",
1148 | "keyword",
1149 | "modifier",
1150 | "comment",
1151 | "string",
1152 | "number",
1153 | "regexp",
1154 | "operator",
1155 | "decorator"
1156 | ],
1157 | "enumDescriptions": [
1158 | "LSP Semantic Token Type: namespace",
1159 | "LSP Semantic Token Type: type",
1160 | "LSP Semantic Token Type: class",
1161 | "LSP Semantic Token Type: enum",
1162 | "LSP Semantic Token Type: interface",
1163 | "LSP Semantic Token Type: struct",
1164 | "LSP Semantic Token Type: typeParameter",
1165 | "LSP Semantic Token Type: parameter",
1166 | "LSP Semantic Token Type: variable",
1167 | "LSP Semantic Token Type: property",
1168 | "LSP Semantic Token Type: enumMember",
1169 | "LSP Semantic Token Type: event",
1170 | "LSP Semantic Token Type: function",
1171 | "LSP Semantic Token Type: method",
1172 | "LSP Semantic Token Type: macro",
1173 | "LSP Semantic Token Type: keyword",
1174 | "LSP Semantic Token Type: modifier",
1175 | "LSP Semantic Token Type: comment",
1176 | "LSP Semantic Token Type: string",
1177 | "LSP Semantic Token Type: number",
1178 | "LSP Semantic Token Type: regexp",
1179 | "LSP Semantic Token Type: operator",
1180 | "LSP Semantic Token Type: decorator"
1181 | ],
1182 | "scope": "resource",
1183 | "type": "string"
1184 | },
1185 | "haskell.plugin.semanticTokens.config.typeVariableToken": {
1186 | "default": "typeParameter",
1187 | "description": "LSP semantic token type to use for type variables",
1188 | "enum": [
1189 | "namespace",
1190 | "type",
1191 | "class",
1192 | "enum",
1193 | "interface",
1194 | "struct",
1195 | "typeParameter",
1196 | "parameter",
1197 | "variable",
1198 | "property",
1199 | "enumMember",
1200 | "event",
1201 | "function",
1202 | "method",
1203 | "macro",
1204 | "keyword",
1205 | "modifier",
1206 | "comment",
1207 | "string",
1208 | "number",
1209 | "regexp",
1210 | "operator",
1211 | "decorator"
1212 | ],
1213 | "enumDescriptions": [
1214 | "LSP Semantic Token Type: namespace",
1215 | "LSP Semantic Token Type: type",
1216 | "LSP Semantic Token Type: class",
1217 | "LSP Semantic Token Type: enum",
1218 | "LSP Semantic Token Type: interface",
1219 | "LSP Semantic Token Type: struct",
1220 | "LSP Semantic Token Type: typeParameter",
1221 | "LSP Semantic Token Type: parameter",
1222 | "LSP Semantic Token Type: variable",
1223 | "LSP Semantic Token Type: property",
1224 | "LSP Semantic Token Type: enumMember",
1225 | "LSP Semantic Token Type: event",
1226 | "LSP Semantic Token Type: function",
1227 | "LSP Semantic Token Type: method",
1228 | "LSP Semantic Token Type: macro",
1229 | "LSP Semantic Token Type: keyword",
1230 | "LSP Semantic Token Type: modifier",
1231 | "LSP Semantic Token Type: comment",
1232 | "LSP Semantic Token Type: string",
1233 | "LSP Semantic Token Type: number",
1234 | "LSP Semantic Token Type: regexp",
1235 | "LSP Semantic Token Type: operator",
1236 | "LSP Semantic Token Type: decorator"
1237 | ],
1238 | "scope": "resource",
1239 | "type": "string"
1240 | },
1241 | "haskell.plugin.semanticTokens.config.variableToken": {
1242 | "default": "variable",
1243 | "description": "LSP semantic token type to use for variables",
1244 | "enum": [
1245 | "namespace",
1246 | "type",
1247 | "class",
1248 | "enum",
1249 | "interface",
1250 | "struct",
1251 | "typeParameter",
1252 | "parameter",
1253 | "variable",
1254 | "property",
1255 | "enumMember",
1256 | "event",
1257 | "function",
1258 | "method",
1259 | "macro",
1260 | "keyword",
1261 | "modifier",
1262 | "comment",
1263 | "string",
1264 | "number",
1265 | "regexp",
1266 | "operator",
1267 | "decorator"
1268 | ],
1269 | "enumDescriptions": [
1270 | "LSP Semantic Token Type: namespace",
1271 | "LSP Semantic Token Type: type",
1272 | "LSP Semantic Token Type: class",
1273 | "LSP Semantic Token Type: enum",
1274 | "LSP Semantic Token Type: interface",
1275 | "LSP Semantic Token Type: struct",
1276 | "LSP Semantic Token Type: typeParameter",
1277 | "LSP Semantic Token Type: parameter",
1278 | "LSP Semantic Token Type: variable",
1279 | "LSP Semantic Token Type: property",
1280 | "LSP Semantic Token Type: enumMember",
1281 | "LSP Semantic Token Type: event",
1282 | "LSP Semantic Token Type: function",
1283 | "LSP Semantic Token Type: method",
1284 | "LSP Semantic Token Type: macro",
1285 | "LSP Semantic Token Type: keyword",
1286 | "LSP Semantic Token Type: modifier",
1287 | "LSP Semantic Token Type: comment",
1288 | "LSP Semantic Token Type: string",
1289 | "LSP Semantic Token Type: number",
1290 | "LSP Semantic Token Type: regexp",
1291 | "LSP Semantic Token Type: operator",
1292 | "LSP Semantic Token Type: decorator"
1293 | ],
1294 | "scope": "resource",
1295 | "type": "string"
1296 | },
1297 | "haskell.plugin.semanticTokens.globalOn": {
1298 | "default": false,
1299 | "description": "Enables semanticTokens plugin",
1300 | "scope": "resource",
1301 | "type": "boolean"
1302 | },
1303 | "haskell.plugin.splice.globalOn": {
1304 | "default": true,
1305 | "description": "Enables splice plugin",
1306 | "scope": "resource",
1307 | "type": "boolean"
1308 | },
1309 | "haskell.plugin.stan.globalOn": {
1310 | "default": false,
1311 | "description": "Enables stan plugin",
1312 | "scope": "resource",
1313 | "type": "boolean"
1314 | }
1315 | }
1316 | },
1317 | "commands": [
1318 | {
1319 | "command": "haskell.commands.restartExtension",
1320 | "title": "Haskell: Restart vscode-haskell extension",
1321 | "description": "Restart the vscode-haskell extension. Reloads configuration."
1322 | },
1323 | {
1324 | "command": "haskell.commands.restartServer",
1325 | "title": "Haskell: Restart Haskell LSP server",
1326 | "description": "Restart the Haskell LSP server"
1327 | },
1328 | {
1329 | "command": "haskell.commands.startServer",
1330 | "title": "Haskell: Start Haskell LSP server",
1331 | "description": "Start the Haskell LSP server"
1332 | },
1333 | {
1334 | "command": "haskell.commands.stopServer",
1335 | "title": "Haskell: Stop Haskell LSP server",
1336 | "description": "Stop the Haskell LSP server"
1337 | }
1338 | ]
1339 | },
1340 | "scripts": {
1341 | "vscode:prepublish": "webpack --mode production",
1342 | "webpack": "webpack --mode none",
1343 | "watch": "webpack --mode development --watch",
1344 | "lint": "eslint -c eslint.config.mjs src",
1345 | "lint-fix": "eslint --fix -c eslint.config.mjs src",
1346 | "push-tag": "git tag -a $npm_package_version -m \"Version $npm_package_version\" && git push origin $npm_package_version",
1347 | "pretest": "tsc --alwaysStrict -p ./",
1348 | "format": "prettier . --write",
1349 | "test": "vscode-test"
1350 | },
1351 | "husky": {
1352 | "hooks": {
1353 | "pre-commit": "pretty-quick --staged"
1354 | }
1355 | },
1356 | "devDependencies": {
1357 | "@eslint/js": "^9.37.0",
1358 | "@types/mocha": "^10.0.10",
1359 | "@types/node": "^22.15.29",
1360 | "@types/vscode": "^1.102.0",
1361 | "@types/which": "^3.0.4",
1362 | "@typescript-eslint/eslint-plugin": "^8.29.0",
1363 | "@typescript-eslint/parser": "^8.45.0",
1364 | "@vscode/test-cli": "^0.0.11",
1365 | "@vscode/test-electron": "^2.5.2",
1366 | "eslint": "^9.37.0",
1367 | "eslint-webpack-plugin": "^5.0.0",
1368 | "glob": "^11.1.0",
1369 | "globals": "^16.2.0",
1370 | "husky": "^9.1.7",
1371 | "mocha": "^11.1.0",
1372 | "prettier": "^3.5.3",
1373 | "ts-loader": "^9.5.2",
1374 | "typescript": "^5.8.3",
1375 | "typescript-eslint": "^8.45.0",
1376 | "webpack": "^5.98.0",
1377 | "webpack-cli": "^6.0.1"
1378 | },
1379 | "extensionDependencies": [
1380 | "justusadam.language-haskell"
1381 | ],
1382 | "dependencies": {
1383 | "ts-pattern": "^5.7.0",
1384 | "vscode-languageclient": "9.0.1",
1385 | "which": "^5.0.0"
1386 | }
1387 | }
1388 |
--------------------------------------------------------------------------------
/src/commands/constants.ts:
--------------------------------------------------------------------------------
1 | export const RestartExtensionCommandName = 'haskell.commands.restartExtension';
2 | export const RestartServerCommandName = 'haskell.commands.restartServer';
3 | export const StartServerCommandName = 'haskell.commands.startServer';
4 | export const StopServerCommandName = 'haskell.commands.stopServer';
5 | export const OpenLogsCommandName = 'haskell.commands.openLogs';
6 | export const ShowExtensionVersions = 'haskell.commands.showVersions';
7 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | import { OutputChannel, Uri, window, WorkspaceConfiguration, WorkspaceFolder } from 'vscode';
2 | import { expandHomeDir, IEnvVars } from './utils';
3 | import * as path from 'path';
4 | import { Logger } from 'vscode-languageclient';
5 | import { ExtensionLogger } from './logger';
6 | import { GHCupConfig } from './ghcup';
7 |
8 | export type LogLevel = 'off' | 'messages' | 'verbose';
9 | export type ClientLogLevel = 'off' | 'error' | 'info' | 'debug';
10 |
11 | export type Config = {
12 | /**
13 | * Unique name per workspace folder (useful for multi-root workspaces).
14 | */
15 | langName: string;
16 | logLevel: LogLevel;
17 | clientLogLevel: ClientLogLevel;
18 | logFilePath?: string;
19 | workingDir: string;
20 | outputChannel: OutputChannel;
21 | serverArgs: string[];
22 | serverEnvironment: IEnvVars;
23 | ghcupConfig: GHCupConfig;
24 | };
25 |
26 | export function initConfig(workspaceConfig: WorkspaceConfiguration, uri: Uri, folder?: WorkspaceFolder): Config {
27 | // Set a unique name per workspace folder (useful for multi-root workspaces).
28 | const langName = 'Haskell' + (folder ? ` (${folder.name})` : '');
29 | const currentWorkingDir = folder ? folder.uri.fsPath : path.dirname(uri.fsPath);
30 |
31 | const logLevel = getLogLevel(workspaceConfig);
32 | const clientLogLevel = getClientLogLevel(workspaceConfig);
33 |
34 | const logFile = getLogFile(workspaceConfig);
35 | const logFilePath = resolveLogFilePath(logFile, currentWorkingDir);
36 |
37 | const outputChannel: OutputChannel = window.createOutputChannel(langName);
38 | const serverArgs = getServerArgs(workspaceConfig, logLevel, logFilePath);
39 |
40 | return {
41 | langName: langName,
42 | logLevel: logLevel,
43 | clientLogLevel: clientLogLevel,
44 | logFilePath: logFilePath,
45 | workingDir: currentWorkingDir,
46 | outputChannel: outputChannel,
47 | serverArgs: serverArgs,
48 | serverEnvironment: workspaceConfig.serverEnvironment,
49 | ghcupConfig: {
50 | metadataUrl: workspaceConfig.metadataURL as string,
51 | upgradeGHCup: workspaceConfig.get('upgradeGHCup') as boolean,
52 | executablePath: workspaceConfig.get('ghcupExecutablePath') as string,
53 | },
54 | };
55 | }
56 |
57 | export function initLoggerFromConfig(config: Config): ExtensionLogger {
58 | return new ExtensionLogger('client', config.clientLogLevel, config.outputChannel, config.logFilePath);
59 | }
60 |
61 | export function logConfig(logger: Logger, config: Config) {
62 | if (config.logFilePath) {
63 | logger.info(`Writing client log to file ${config.logFilePath}`);
64 | }
65 | logger.log('Environment variables:');
66 | Object.entries(process.env).forEach(([key, value]: [string, string | undefined]) => {
67 | // only list environment variables that we actually care about.
68 | // this makes it safe for users to just paste the logs to whoever,
69 | // and avoids leaking secrets.
70 | if (['PATH'].includes(key)) {
71 | logger.log(` ${key}: ${value}`);
72 | }
73 | });
74 | }
75 |
76 | function getLogFile(workspaceConfig: WorkspaceConfiguration) {
77 | const logFile_: unknown = workspaceConfig.logFile;
78 | let logFile: string | undefined;
79 | if (typeof logFile_ === 'string') {
80 | logFile = logFile_ !== '' ? logFile_ : undefined;
81 | }
82 | return logFile;
83 | }
84 |
85 | function getClientLogLevel(workspaceConfig: WorkspaceConfiguration): ClientLogLevel {
86 | const clientLogLevel_: unknown = workspaceConfig.trace.client;
87 | let clientLogLevel;
88 | if (typeof clientLogLevel_ === 'string') {
89 | switch (clientLogLevel_) {
90 | case 'off':
91 | case 'error':
92 | case 'info':
93 | case 'debug':
94 | clientLogLevel = clientLogLevel_;
95 | break;
96 | default:
97 | throw new Error("Option \"haskell.trace.client\" is expected to be one of 'off', 'error', 'info', 'debug'.");
98 | }
99 | } else {
100 | throw new Error('Option "haskell.trace.client" is expected to be a string');
101 | }
102 | return clientLogLevel;
103 | }
104 |
105 | function getLogLevel(workspaceConfig: WorkspaceConfiguration): LogLevel {
106 | const logLevel_: unknown = workspaceConfig.trace.server;
107 | let logLevel;
108 | if (typeof logLevel_ === 'string') {
109 | switch (logLevel_) {
110 | case 'off':
111 | case 'messages':
112 | case 'verbose':
113 | logLevel = logLevel_;
114 | break;
115 | default:
116 | throw new Error("Option \"haskell.trace.server\" is expected to be one of 'off', 'messages', 'verbose'.");
117 | }
118 | } else {
119 | throw new Error('Option "haskell.trace.server" is expected to be a string');
120 | }
121 | return logLevel;
122 | }
123 |
124 | function resolveLogFilePath(logFile: string | undefined, currentWorkingDir: string): string | undefined {
125 | return logFile !== undefined ? path.resolve(currentWorkingDir, expandHomeDir(logFile)) : undefined;
126 | }
127 |
128 | function getServerArgs(workspaceConfig: WorkspaceConfiguration, logLevel: LogLevel, logFilePath?: string): string[] {
129 | const serverArgs = ['--lsp']
130 | .concat(logLevel === 'messages' ? ['-d'] : [])
131 | .concat(logFilePath !== undefined ? ['-l', logFilePath] : []);
132 |
133 | const rawExtraArgs: unknown = workspaceConfig.serverExtraArgs;
134 | if (typeof rawExtraArgs === 'string' && rawExtraArgs !== '') {
135 | const e = rawExtraArgs.split(' ');
136 | serverArgs.push(...e);
137 | }
138 |
139 | // We don't want empty strings in our args
140 | return serverArgs.map((x) => x.trim()).filter((x) => x !== '');
141 | }
142 |
--------------------------------------------------------------------------------
/src/docsBrowser.ts:
--------------------------------------------------------------------------------
1 | import { dirname } from 'path';
2 | import {
3 | CancellationToken,
4 | commands,
5 | CompletionContext,
6 | CompletionItem,
7 | CompletionList,
8 | Disposable,
9 | env,
10 | Hover,
11 | MarkdownString,
12 | MarkedString,
13 | Position,
14 | ProviderResult,
15 | TextDocument,
16 | Uri,
17 | ViewColumn,
18 | window,
19 | workspace,
20 | } from 'vscode';
21 | import { ProvideCompletionItemsSignature, ProvideHoverSignature } from 'vscode-languageclient';
22 |
23 | async function showDocumentation({
24 | title,
25 | localPath,
26 | hackageUri,
27 | }: {
28 | title: string;
29 | localPath: string;
30 | hackageUri: string;
31 | }) {
32 | const arr = localPath.match(/([^/]+)\.[^.]+$/);
33 | const ttl = arr !== null && arr.length === 2 ? arr[1].replace(/-/gi, '.') : title;
34 | const documentationDirectory = dirname(localPath);
35 | let panel;
36 | try {
37 | const docUri = Uri.parse(documentationDirectory);
38 |
39 | // Make sure to use Uri.parse here, as path will already have 'file:///' in it
40 | panel = window.createWebviewPanel('haskell.showDocumentationPanel', ttl, ViewColumn.Beside, {
41 | localResourceRoots: [docUri],
42 | enableFindWidget: true,
43 | enableCommandUris: true,
44 | enableScripts: true,
45 | });
46 |
47 | const encoded = encodeURIComponent(JSON.stringify({ hackageUri, inWebView: true }));
48 | const hackageCmd = 'command:haskell.openDocumentationOnHackage?' + encoded;
49 |
50 | const bytes = await workspace.fs.readFile(Uri.parse(localPath));
51 |
52 | const addBase = `
53 |
54 | `;
55 |
56 | panel.webview.html = `
57 |
58 | ${addBase}
59 |
60 |
61 | ${bytes.toString()}
62 |
63 |
64 | `;
65 | } catch (e) {
66 | if (e instanceof Error) {
67 | await window.showErrorMessage(e.message);
68 | }
69 | }
70 | return panel;
71 | }
72 |
73 | // registers the browser in VSCode infrastructure
74 | export function registerDocsBrowser(): Disposable {
75 | return commands.registerCommand('haskell.showDocumentation', showDocumentation);
76 | }
77 |
78 | async function openDocumentationOnHackage({
79 | hackageUri,
80 | inWebView = false,
81 | }: {
82 | hackageUri: string;
83 | inWebView: boolean;
84 | }) {
85 | try {
86 | // open on Hackage and close the original webview in VS code
87 | await env.openExternal(Uri.parse(hackageUri));
88 | if (inWebView) {
89 | await commands.executeCommand('workbench.action.closeActiveEditor');
90 | }
91 | } catch (e) {
92 | if (e instanceof Error) {
93 | await window.showErrorMessage(e.message);
94 | }
95 | }
96 | }
97 |
98 | export function registerDocsOpenOnHackage(): Disposable {
99 | return commands.registerCommand('haskell.openDocumentationOnHackage', openDocumentationOnHackage);
100 | }
101 |
102 | export function hoverLinksMiddlewareHook(
103 | document: TextDocument,
104 | position: Position,
105 | token: CancellationToken,
106 | next: ProvideHoverSignature,
107 | ): ProviderResult {
108 | const res = next(document, position, token);
109 | return Promise.resolve(res).then((r) => {
110 | if (r !== null && r !== undefined) {
111 | r.contents = r.contents.map(processLink);
112 | }
113 | return r;
114 | });
115 | }
116 |
117 | export function completionLinksMiddlewareHook(
118 | document: TextDocument,
119 | position: Position,
120 | context: CompletionContext,
121 | token: CancellationToken,
122 | next: ProvideCompletionItemsSignature,
123 | ): ProviderResult {
124 | const res = next(document, position, context, token);
125 |
126 | function processCI(ci: CompletionItem): void {
127 | if (ci.documentation) {
128 | ci.documentation = processLink(ci.documentation);
129 | }
130 | }
131 |
132 | return Promise.resolve(res).then((r) => {
133 | if (r instanceof Array) {
134 | r.forEach(processCI);
135 | } else if (r) {
136 | r.items.forEach(processCI);
137 | }
138 | return r;
139 | });
140 | }
141 |
142 | function processLink(ms: MarkdownString | MarkedString): string | MarkdownString {
143 | const openDocsInHackage = workspace.getConfiguration('haskell').get('openDocumentationInHackage');
144 | const openSourceInHackage = workspace.getConfiguration('haskell').get('openSourceInHackage');
145 | function transform(s: string): string {
146 | return s.replace(
147 | /\[(.+)\]\((file:.+\/doc\/(?:.*html\/libraries\/)?([^/]+)\/(?:.*\/)?(.+\.html#?.*))\)/gi,
148 | (_all, title, localPath, packageName, fileAndAnchor) => {
149 | let hackageUri: string;
150 | if (title === 'Documentation') {
151 | hackageUri = `https://hackage.haskell.org/package/${packageName}/docs/${fileAndAnchor}`;
152 | const encoded = encodeURIComponent(JSON.stringify({ title, localPath, hackageUri }));
153 | let cmd: string;
154 | if (openDocsInHackage) {
155 | cmd = 'command:haskell.openDocumentationOnHackage?' + encoded;
156 | } else {
157 | cmd = 'command:haskell.showDocumentation?' + encoded;
158 | }
159 | return `[${title}](${cmd})`;
160 | } else if (title === 'Source' && typeof fileAndAnchor === 'string') {
161 | const moduleLocation = fileAndAnchor.replace(/-/gi, '.');
162 | hackageUri = `https://hackage.haskell.org/package/${packageName}/docs/src/${moduleLocation}`;
163 | const encoded = encodeURIComponent(JSON.stringify({ title, localPath, hackageUri }));
164 | let cmd: string;
165 | if (openSourceInHackage) {
166 | cmd = 'command:haskell.openDocumentationOnHackage?' + encoded;
167 | } else {
168 | cmd = 'command:haskell.showDocumentation?' + encoded;
169 | }
170 | return `[${title}](${cmd})`;
171 | } else {
172 | return s;
173 | }
174 | },
175 | );
176 | }
177 | if (typeof ms === 'string') {
178 | return transform(ms);
179 | } else if (ms instanceof MarkdownString) {
180 | const mstr = new MarkdownString(transform(ms.value));
181 | mstr.isTrusted = true;
182 | return mstr;
183 | } else {
184 | return ms.value;
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/src/errors.ts:
--------------------------------------------------------------------------------
1 | import { Uri } from 'vscode';
2 |
3 | export class HlsError extends Error {}
4 |
5 | export class MissingToolError extends HlsError {
6 | public readonly tool: string;
7 | constructor(tool: string) {
8 | let prettyTool: string;
9 | switch (tool.toLowerCase()) {
10 | case 'stack':
11 | prettyTool = 'Stack';
12 | break;
13 | case 'cabal':
14 | prettyTool = 'Cabal';
15 | break;
16 | case 'ghc':
17 | prettyTool = 'GHC';
18 | break;
19 | case 'ghcup':
20 | prettyTool = 'GHCup';
21 | break;
22 | case 'haskell-language-server':
23 | case 'hls':
24 | prettyTool = 'HLS';
25 | break;
26 | default:
27 | prettyTool = tool;
28 | break;
29 | }
30 | super(`Project requires ${prettyTool} but it isn't installed`);
31 | this.tool = prettyTool;
32 | }
33 |
34 | public installLink(): Uri | null {
35 | switch (this.tool) {
36 | case 'Stack':
37 | return Uri.parse('https://docs.haskellstack.org/en/stable/install_and_upgrade/');
38 | case 'GHCup':
39 | case 'Cabal':
40 | case 'HLS':
41 | case 'GHC':
42 | return Uri.parse('https://www.haskell.org/ghcup/');
43 | default:
44 | return null;
45 | }
46 | }
47 | }
48 |
49 | export class NoMatchingHls extends Error {
50 | constructor(readonly ghcProjVersion: string) {
51 | super(`HLS does not support GHC ${ghcProjVersion} yet.`);
52 | }
53 | public docLink(): Uri {
54 | return Uri.parse('https://haskell-language-server.readthedocs.io/en/latest/support/ghc-version-support.html');
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/extension.ts:
--------------------------------------------------------------------------------
1 | import { commands, env, ExtensionContext, TextDocument, Uri, window, workspace, WorkspaceFolder } from 'vscode';
2 | import {
3 | ExecutableOptions,
4 | LanguageClient,
5 | LanguageClientOptions,
6 | Logger,
7 | RevealOutputChannelOn,
8 | ServerOptions,
9 | } from 'vscode-languageclient/node';
10 | import * as constants from './commands/constants';
11 | import * as DocsBrowser from './docsBrowser';
12 | import { HlsError, MissingToolError, NoMatchingHls } from './errors';
13 | import { findHaskellLanguageServer, HlsExecutable, IEnvVars, fetchConfig } from './hlsBinaries';
14 | import { addPathToProcessPath, comparePVP, callAsync } from './utils';
15 | import { Config, initConfig, initLoggerFromConfig, logConfig } from './config';
16 | import { HaskellStatusBar } from './statusBar';
17 |
18 | /**
19 | * Global information about the running clients.
20 | */
21 | type Client = {
22 | client: LanguageClient;
23 | config: Config;
24 | };
25 |
26 | // The current map of documents & folders to language servers.
27 | // It may be null to indicate that we are in the process of launching a server,
28 | // in which case don't try to launch another one for that uri
29 | const clients: Map = new Map();
30 |
31 | // This is the entrypoint to our extension
32 | export async function activate(context: ExtensionContext) {
33 | const statusBar = new HaskellStatusBar(context.extension.packageJSON.version as string | undefined);
34 | context.subscriptions.push(statusBar);
35 |
36 | // (Possibly) launch the language server every time a document is opened, so
37 | // it works across multiple workspace folders. Eventually, haskell-lsp should
38 | // just support
39 | // https://microsoft.github.io/language-server-protocol/specifications/specification-3-15/#workspace_workspaceFolders
40 | // and then we can just launch one server
41 | workspace.onDidOpenTextDocument(async (document: TextDocument) => await activateServer(context, document));
42 | for (const document of workspace.textDocuments) {
43 | await activateServer(context, document);
44 | }
45 |
46 | // Stop the server from any workspace folders that are removed.
47 | workspace.onDidChangeWorkspaceFolders(async (event) => {
48 | for (const folder of event.removed) {
49 | const client = clients.get(folder.uri.toString());
50 | if (client) {
51 | const uri = folder.uri.toString();
52 | client.client.info(`Deleting folder for clients: ${uri}`);
53 | clients.delete(uri);
54 | client.client.info('Stopping the server');
55 | await client.client.stop();
56 | }
57 | }
58 | });
59 |
60 | // Register editor commands for HIE, but only register the commands once at activation.
61 | const restartCmd = commands.registerCommand(constants.RestartServerCommandName, async () => {
62 | for (const langClient of clients.values()) {
63 | langClient?.client.info('Stopping the server');
64 | await langClient?.client.stop();
65 | langClient?.client.info('Starting the server');
66 | await langClient?.client.start();
67 | }
68 | });
69 |
70 | context.subscriptions.push(restartCmd);
71 |
72 | const openLogsCmd = commands.registerCommand(constants.OpenLogsCommandName, () => {
73 | for (const langClient of clients.values()) {
74 | langClient?.config.outputChannel.show();
75 | }
76 | });
77 |
78 | context.subscriptions.push(openLogsCmd);
79 |
80 | const restartExtensionCmd = commands.registerCommand(constants.RestartExtensionCommandName, async () => {
81 | for (const langClient of clients.values()) {
82 | langClient?.client.info('Stopping the server');
83 | await langClient?.client.stop();
84 | }
85 | clients.clear();
86 | fetchConfig();
87 |
88 | for (const document of workspace.textDocuments) {
89 | await activateServer(context, document);
90 | }
91 | });
92 |
93 | context.subscriptions.push(restartExtensionCmd);
94 |
95 | const showVersionsCmd = commands.registerCommand(constants.ShowExtensionVersions, () => {
96 | void window.showInformationMessage(`Extension Version: ${context.extension.packageJSON.version ?? ''}`);
97 | });
98 |
99 | context.subscriptions.push(showVersionsCmd);
100 |
101 | const stopCmd = commands.registerCommand(constants.StopServerCommandName, async () => {
102 | for (const langClient of clients.values()) {
103 | langClient?.client.info('Stopping the server');
104 | await langClient?.client.stop();
105 | langClient?.client.info('Server stopped');
106 | }
107 | });
108 |
109 | context.subscriptions.push(stopCmd);
110 |
111 | const startCmd = commands.registerCommand(constants.StartServerCommandName, async () => {
112 | for (const langClient of clients.values()) {
113 | langClient?.client.info('Starting the server');
114 | await langClient?.client.start();
115 | langClient?.client.info('Server started');
116 | }
117 | });
118 |
119 | context.subscriptions.push(startCmd);
120 |
121 | // Set up the documentation browser.
122 | const docsDisposable = DocsBrowser.registerDocsBrowser();
123 | context.subscriptions.push(docsDisposable);
124 |
125 | const openOnHackageDisposable = DocsBrowser.registerDocsOpenOnHackage();
126 | context.subscriptions.push(openOnHackageDisposable);
127 |
128 | statusBar.refresh();
129 | statusBar.show();
130 | }
131 |
132 | async function activateServer(context: ExtensionContext, document: TextDocument) {
133 | // We are only interested in Haskell files.
134 | if (
135 | (document.languageId !== 'haskell' &&
136 | document.languageId !== 'cabal' &&
137 | document.languageId !== 'literate haskell') ||
138 | (document.uri.scheme !== 'file' && document.uri.scheme !== 'untitled')
139 | ) {
140 | return;
141 | }
142 |
143 | const uri = document.uri;
144 | const folder = workspace.getWorkspaceFolder(uri);
145 |
146 | await activateServerForFolder(context, uri, folder);
147 | }
148 |
149 | async function activateServerForFolder(context: ExtensionContext, uri: Uri, folder?: WorkspaceFolder) {
150 | const clientsKey = folder ? folder.uri.toString() : uri.toString();
151 | // If the client already has an LSP server for this uri/folder, then don't start a new one.
152 | if (clients.has(clientsKey)) {
153 | return;
154 | }
155 | // Set the key to null to prevent multiple servers being launched at once
156 | clients.set(clientsKey, null);
157 |
158 | const config = initConfig(workspace.getConfiguration('haskell', uri), uri, folder);
159 | const logger: Logger = initLoggerFromConfig(config);
160 |
161 | logConfig(logger, config);
162 |
163 | let hlsExecutable: HlsExecutable;
164 | try {
165 | hlsExecutable = await findHaskellLanguageServer(context, logger, config.ghcupConfig, config.workingDir, folder);
166 | } catch (e) {
167 | await handleInitializationError(e, logger);
168 | // Make sure to release the key again.
169 | clients.delete(clientsKey);
170 | return;
171 | }
172 |
173 | const serverEnvironment: IEnvVars = initServerEnvironment(config, hlsExecutable);
174 | const exeOptions: ExecutableOptions = {
175 | cwd: config.workingDir,
176 | env: { ...process.env, ...serverEnvironment },
177 | };
178 |
179 | // For our intents and purposes, the server should be launched the same way in
180 | // both debug and run mode.
181 | const serverOptions: ServerOptions = {
182 | run: { command: hlsExecutable.location, args: config.serverArgs, options: exeOptions },
183 | debug: { command: hlsExecutable.location, args: config.serverArgs, options: exeOptions },
184 | };
185 |
186 | // If we're operating on a standalone file (i.e. not in a folder) then we need
187 | // to launch the server in a reasonable current directory. Otherwise the cradle
188 | // guessing logic in hie-bios will be wrong!
189 | let cwdMsg = `Activating the language server in working dir: ${config.workingDir}`;
190 | if (folder) {
191 | cwdMsg += ' (the workspace folder)';
192 | } else {
193 | cwdMsg += ` (parent dir of loaded file ${uri.fsPath})`;
194 | }
195 | logger.info(cwdMsg);
196 |
197 | logger.info(`run command: ${hlsExecutable.location} ${config.serverArgs.join(' ')}`);
198 | logger.info(`debug command: ${hlsExecutable.location} ${config.serverArgs.join(' ')}`);
199 | if (exeOptions.cwd) {
200 | logger.info(`server cwd: ${exeOptions.cwd}`);
201 | }
202 | if (serverEnvironment) {
203 | logger.info('server environment variables:');
204 | Object.entries(serverEnvironment).forEach(([key, val]: [string, string | undefined]) => {
205 | logger.info(` ${key}=${val}`);
206 | });
207 | }
208 |
209 | const pat = folder ? `${folder.uri.fsPath}/**/*` : '**/*';
210 | logger.log(`document selector patten: ${pat}`);
211 |
212 | const cabalDocumentSelector = { scheme: 'file', language: 'cabal', pattern: pat };
213 | const haskellDocumentSelector = [
214 | { scheme: 'file', language: 'haskell', pattern: pat },
215 | { scheme: 'file', language: 'literate haskell', pattern: pat },
216 | ];
217 |
218 | const documentSelector = [...haskellDocumentSelector];
219 |
220 | const cabalFileSupport: 'automatic' | 'enable' | 'disable' = workspace.getConfiguration(
221 | 'haskell',
222 | uri,
223 | ).supportCabalFiles;
224 | logger.info(`Support for '.cabal' files: ${cabalFileSupport}`);
225 |
226 | switch (cabalFileSupport) {
227 | case 'automatic': {
228 | const hlsVersion = await callAsync(
229 | hlsExecutable.location,
230 | ['--numeric-version'],
231 | logger,
232 | config.workingDir,
233 | undefined /* this command is very fast, don't show anything */,
234 | false,
235 | serverEnvironment,
236 | );
237 | if (comparePVP(hlsVersion, '1.9.0.0') >= 0) {
238 | // If hlsVersion is >= '1.9.0.0'
239 | documentSelector.push(cabalDocumentSelector);
240 | }
241 | break;
242 | }
243 | case 'enable':
244 | documentSelector.push(cabalDocumentSelector);
245 | break;
246 | case 'disable':
247 | break;
248 | default:
249 | break;
250 | }
251 |
252 | const clientOptions: LanguageClientOptions = {
253 | // Use the document selector to only notify the LSP on files inside the folder
254 | // path for the specific workspace.
255 | documentSelector: [...documentSelector],
256 | synchronize: {
257 | // Synchronize the setting section 'haskell' to the server.
258 | configurationSection: 'haskell',
259 | },
260 | diagnosticCollectionName: config.langName,
261 | revealOutputChannelOn: RevealOutputChannelOn.Never,
262 | outputChannel: config.outputChannel,
263 | outputChannelName: config.langName,
264 | middleware: {
265 | provideHover: DocsBrowser.hoverLinksMiddlewareHook,
266 | provideCompletionItem: DocsBrowser.completionLinksMiddlewareHook,
267 | },
268 | // Launch the server in the directory of the workspace folder.
269 | workspaceFolder: folder,
270 | };
271 |
272 | // Create the LSP client.
273 | const langClient = new LanguageClient('haskell', config.langName, serverOptions, clientOptions);
274 |
275 | // Register ClientCapabilities for stuff like window/progress
276 | langClient.registerProposedFeatures();
277 |
278 | // Finally start the client and add it to the list of clients.
279 | logger.info('Starting language server');
280 | clients.set(clientsKey, {
281 | client: langClient,
282 | config,
283 | });
284 | await langClient.start();
285 | }
286 |
287 | /**
288 | * Handle errors the extension may throw. Errors are expected to be fatal.
289 | *
290 | * @param e Error thrown during the extension initialization.
291 | * @param logger
292 | */
293 | async function handleInitializationError(e: unknown, logger: Logger) {
294 | if (e instanceof MissingToolError) {
295 | const link = e.installLink();
296 | if (link) {
297 | if (await window.showErrorMessage(e.message, `Install ${e.tool}`)) {
298 | env.openExternal(link);
299 | }
300 | } else {
301 | await window.showErrorMessage(e.message);
302 | }
303 | } else if (e instanceof HlsError) {
304 | logger.error(`General HlsError: ${e.message}`);
305 | window.showErrorMessage(e.message);
306 | } else if (e instanceof NoMatchingHls) {
307 | const link = e.docLink();
308 | logger.error(`${e.message}`);
309 | if (await window.showErrorMessage(e.message, 'Open documentation')) {
310 | env.openExternal(link);
311 | }
312 | } else if (e instanceof Error) {
313 | logger.error(`Internal Error: ${e.message}`);
314 | window.showErrorMessage(e.message);
315 | }
316 | if (e instanceof Error) {
317 | // general stack trace printing
318 | if (e.stack) {
319 | logger.error(`${e.stack}`);
320 | }
321 | }
322 | }
323 |
324 | function initServerEnvironment(config: Config, hlsExecutable: HlsExecutable) {
325 | let serverEnvironment: IEnvVars = config.serverEnvironment;
326 | if (hlsExecutable.tag === 'ghcup') {
327 | const newPath = addPathToProcessPath(hlsExecutable.binaryDirectory);
328 | serverEnvironment = {
329 | ...serverEnvironment,
330 | ...{ PATH: newPath },
331 | };
332 | }
333 | return serverEnvironment;
334 | }
335 |
336 | /*
337 | * Deactivate each of the LSP servers.
338 | */
339 | export async function deactivate() {
340 | const promises: Thenable[] = [];
341 | for (const client of clients.values()) {
342 | if (client) {
343 | promises.push(client.client.stop());
344 | }
345 | }
346 | await Promise.all(promises);
347 | }
348 |
--------------------------------------------------------------------------------
/src/ghcup.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import * as os from 'os';
3 | import * as process from 'process';
4 | import { WorkspaceFolder } from 'vscode';
5 | import { Logger } from 'vscode-languageclient';
6 | import { MissingToolError } from './errors';
7 | import { resolvePathPlaceHolders, executableExists, callAsync, ProcessCallback, IEnvVars } from './utils';
8 | import { match } from 'ts-pattern';
9 |
10 | export type Tool = 'hls' | 'ghc' | 'cabal' | 'stack';
11 |
12 | export type ToolConfig = Map;
13 |
14 | export function initDefaultGHCup(config: GHCupConfig, logger: Logger, folder?: WorkspaceFolder): GHCup {
15 | const ghcupLoc = findGHCup(logger, config.executablePath, folder);
16 | return new GHCup(logger, ghcupLoc, config, {
17 | // omit colourful output because the logs are uglier
18 | NO_COLOR: '1',
19 | });
20 | }
21 |
22 | export type GHCupConfig = {
23 | metadataUrl?: string;
24 | upgradeGHCup: boolean;
25 | executablePath?: string;
26 | };
27 |
28 | export type ToolInfo = {
29 | tool: Tool;
30 | version: string;
31 | tags: string[];
32 | };
33 |
34 | export class GHCup {
35 | constructor(
36 | readonly logger: Logger,
37 | readonly location: string,
38 | readonly config: GHCupConfig,
39 | readonly environment: IEnvVars,
40 | ) {}
41 |
42 | /**
43 | * Most generic way to run the `ghcup` binary.
44 | * @param args Arguments to run the `ghcup` binary with.
45 | * @param title Displayed to the user for long-running tasks.
46 | * @param cancellable Whether this invocation can be cancelled by the user.
47 | * @param callback Handle success or failures.
48 | * @returns The output of the `ghcup` invocation. If no {@link callback} is given, this is the stdout. Otherwise, whatever {@link callback} produces.
49 | */
50 | public async call(
51 | args: string[],
52 | title?: string,
53 | cancellable?: boolean,
54 | callback?: ProcessCallback,
55 | ): Promise {
56 | const metadataUrl = this.config.metadataUrl; // ;
57 | return await callAsync(
58 | this.location,
59 | ['--no-verbose'].concat(metadataUrl ? ['-s', metadataUrl] : []).concat(args),
60 | this.logger,
61 | undefined,
62 | title,
63 | cancellable,
64 | this.environment,
65 | callback,
66 | );
67 | }
68 |
69 | /**
70 | * Upgrade the `ghcup` binary unless this option was disabled by the user.
71 | */
72 | public async upgrade(): Promise {
73 | const upgrade = this.config.upgradeGHCup;
74 | if (upgrade) {
75 | await this.call(['upgrade'], 'Upgrading ghcup', true);
76 | }
77 | }
78 |
79 | /**
80 | * Find the `set` version of a {@link Tool} in GHCup.
81 | * If no version is set, return null.
82 | * @param tool Tool you want to know the latest version of.
83 | * @returns The latest installed or generally available version of the {@link tool}
84 | */
85 | public async getSetVersion(tool: Tool): Promise {
86 | // these might be custom/stray/compiled, so we try first
87 | const installedVersions = await this.listTool(tool, 'set');
88 | const latestInstalled = installedVersions.pop();
89 | if (latestInstalled) {
90 | return latestInstalled;
91 | } else {
92 | return null;
93 | }
94 | }
95 |
96 | /**
97 | * Find the latest version of a {@link Tool} that we can find in GHCup.
98 | * Prefer already installed versions, but fall back to all available versions, if there aren't any.
99 | * @param tool Tool you want to know the latest version of.
100 | * @returns The latest installed or generally available version of the {@link tool}
101 | */
102 | public async getAnyLatestVersion(tool: Tool): Promise {
103 | // these might be custom/stray/compiled, so we try first
104 | const installedVersions = await this.listTool(tool, 'installed');
105 | const latestInstalled = installedVersions.pop();
106 | if (latestInstalled) {
107 | return latestInstalled;
108 | } else {
109 | return this.getLatestAvailableVersion(tool);
110 | }
111 | }
112 |
113 | /**
114 | * Find the latest available version that we can find in GHCup with a certain {@link tag}.
115 | * Corresponds to the `ghcup list -t -c available -r` command.
116 | * The tag can be used to further filter the list of versions, for example you can provide
117 | * @param tool Tool you want to know the latest version of.
118 | * @param tag The tag to filter the available versions with. By default `"latest"`.
119 | * @returns The latest available version filtered by {@link tag}.
120 | */
121 | public async getLatestAvailableVersion(tool: Tool, tag: string = 'latest'): Promise {
122 | // fall back to installable versions
123 | const availableVersions = await this.listTool(tool, 'available');
124 |
125 | let latestAvailable: ToolInfo | null = null;
126 | availableVersions.forEach((toolInfo) => {
127 | if (toolInfo.tags.includes(tag)) {
128 | latestAvailable = toolInfo;
129 | }
130 | });
131 | if (!latestAvailable) {
132 | throw new Error(`Unable to find ${tag} tool ${tool}`);
133 | } else {
134 | return latestAvailable;
135 | }
136 | }
137 |
138 | private async listTool(tool: Tool, category: string): Promise {
139 | // fall back to installable versions
140 | const availableVersions = await this.call(['list', '-t', tool, '-c', category, '-r'], undefined, false).then((s) =>
141 | s.split(/\r?\n/),
142 | );
143 |
144 | return availableVersions.map((toolString) => {
145 | const toolParts = toolString.split(/\s+/);
146 | return {
147 | tool: tool,
148 | version: toolParts[1],
149 | tags: toolParts[2]?.split(',') ?? [],
150 | };
151 | });
152 | }
153 |
154 | public async findLatestUserInstalledTool(tool: Tool): Promise {
155 | let toolInfo = null;
156 | toolInfo = await this.getSetVersion(tool);
157 | if (toolInfo) return toolInfo;
158 | toolInfo = await this.getAnyLatestVersion(tool);
159 | if (toolInfo) return toolInfo;
160 | throw new Error(`Unable to find a version for tool ${tool}`);
161 | }
162 | }
163 |
164 | function findGHCup(logger: Logger, exePath?: string, folder?: WorkspaceFolder): string {
165 | logger.info('Checking for ghcup installation');
166 | if (exePath) {
167 | logger.info(`Trying to find the ghcup executable in: ${exePath}`);
168 | exePath = resolvePathPlaceHolders(exePath, folder);
169 | logger.log(`Location after path variables substitution: ${exePath}`);
170 | if (executableExists(exePath)) {
171 | return exePath;
172 | } else {
173 | throw new Error(`Could not find a ghcup binary at ${exePath}!`);
174 | }
175 | } else {
176 | const localGHCup = ['ghcup'].find(executableExists);
177 | if (!localGHCup) {
178 | logger.info(`probing for GHCup binary`);
179 | const ghcupExe: string | null = match(process.platform)
180 | .with('win32', () => {
181 | const ghcupPrefix = process.env.GHCUP_INSTALL_BASE_PREFIX;
182 | if (ghcupPrefix) {
183 | return path.join(ghcupPrefix, 'ghcup', 'bin', 'ghcup.exe');
184 | } else {
185 | return path.join('C:\\', 'ghcup', 'bin', 'ghcup.exe');
186 | }
187 | })
188 | .otherwise(() => {
189 | const useXDG = process.env.GHCUP_USE_XDG_DIRS;
190 | if (useXDG) {
191 | const xdgBin = process.env.XDG_BIN_HOME;
192 | if (xdgBin) {
193 | return path.join(xdgBin, 'ghcup');
194 | } else {
195 | return path.join(os.homedir(), '.local', 'bin', 'ghcup');
196 | }
197 | } else {
198 | const ghcupPrefix = process.env.GHCUP_INSTALL_BASE_PREFIX;
199 | if (ghcupPrefix) {
200 | return path.join(ghcupPrefix, '.ghcup', 'bin', 'ghcup');
201 | } else {
202 | return path.join(os.homedir(), '.ghcup', 'bin', 'ghcup');
203 | }
204 | }
205 | });
206 | if (ghcupExe !== null && executableExists(ghcupExe)) {
207 | return ghcupExe;
208 | } else {
209 | logger.warn(`ghcup at ${ghcupExe} does not exist`);
210 | throw new MissingToolError('ghcup');
211 | }
212 | } else {
213 | logger.info(`found ghcup at ${localGHCup}`);
214 | return localGHCup;
215 | }
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/src/hlsBinaries.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import * as path from 'path';
3 | import { ConfigurationTarget, ExtensionContext, window, workspace, WorkspaceFolder } from 'vscode';
4 | import { Logger } from 'vscode-languageclient';
5 | import { HlsError, MissingToolError, NoMatchingHls } from './errors';
6 | import {
7 | addPathToProcessPath,
8 | callAsync,
9 | comparePVP,
10 | executableExists,
11 | IEnvVars,
12 | resolvePathPlaceHolders,
13 | } from './utils';
14 | import { ToolConfig, Tool, initDefaultGHCup, GHCup, GHCupConfig } from './ghcup';
15 | import { getHlsMetadata } from './metadata';
16 | export { IEnvVars, fetchConfig };
17 |
18 | export type Context = {
19 | manageHls: ManageHLS;
20 | storagePath: string;
21 | serverExecutable?: HlsExecutable;
22 | logger: Logger;
23 | };
24 |
25 | /**
26 | * Global configuration for this extension.
27 | */
28 | let haskellConfig = workspace.getConfiguration('haskell');
29 |
30 | /**
31 | * On Windows the executable needs to be stored somewhere with an .exe extension
32 | */
33 | const exeExt = process.platform === 'win32' ? '.exe' : '';
34 |
35 | type ManageHLS = 'GHCup' | 'PATH';
36 | let manageHLS = haskellConfig.get('manageHLS') as ManageHLS;
37 |
38 | function fetchConfig() {
39 | haskellConfig = workspace.getConfiguration('haskell');
40 | manageHLS = haskellConfig.get('manageHLS') as ManageHLS;
41 | }
42 |
43 | /**
44 | * Gets serverExecutablePath and fails if it's not set.
45 | * @param logger Log progress.
46 | * @param folder Workspace folder. Used for resolving variables in the `serverExecutablePath`.
47 | * @returns Path to an HLS executable binary.
48 | */
49 | function findServerExecutable(logger: Logger, folder?: WorkspaceFolder): string {
50 | const rawExePath = haskellConfig.get('serverExecutablePath') as string;
51 | logger.info(`Trying to find the server executable in: ${rawExePath}`);
52 | const resolvedExePath = resolvePathPlaceHolders(rawExePath, folder);
53 | logger.log(`Location after path variables substitution: ${resolvedExePath}`);
54 | if (executableExists(resolvedExePath)) {
55 | return resolvedExePath;
56 | } else {
57 | const msg = `Could not find a HLS binary at ${resolvedExePath}! Consider installing HLS via ghcup or change "haskell.manageHLS" in your settings.`;
58 | throw new HlsError(msg);
59 | }
60 | }
61 |
62 | /**
63 | * Searches the `PATH` for `haskell-language-server` or `haskell-language-server-wrapper` binary.
64 | * Fails if nothing is found.
65 | * @param logger Log all the stuff!
66 | * @returns Location of the `haskell-language-server` or `haskell-language-server-wrapper` binary if found.
67 | */
68 | function findHlsInPath(logger: Logger): string {
69 | // try PATH
70 | const exes: string[] = ['haskell-language-server-wrapper', 'haskell-language-server'];
71 | logger.info(`Searching for server executables ${exes.join(',')} in $PATH`);
72 | logger.info(`$PATH environment variable: ${process.env.PATH}`);
73 | for (const exe of exes) {
74 | if (executableExists(exe)) {
75 | logger.info(`Found server executable in $PATH: ${exe}`);
76 | return exe;
77 | }
78 | }
79 | throw new MissingToolError('hls');
80 | }
81 |
82 | export type HlsExecutable = HlsOnPath | HlsViaVSCodeConfig | HlsViaGhcup;
83 |
84 | export type HlsOnPath = {
85 | location: string;
86 | tag: 'path';
87 | };
88 |
89 | export type HlsViaVSCodeConfig = {
90 | location: string;
91 | tag: 'config';
92 | };
93 |
94 | export type HlsViaGhcup = {
95 | location: string;
96 | /**
97 | * if we download HLS, add that bin dir to PATH
98 | */
99 | binaryDirectory: string;
100 | tag: 'ghcup';
101 | };
102 |
103 | /**
104 | * Find and setup the Haskell Language Server.
105 | *
106 | * We support three ways of finding the HLS binary:
107 | *
108 | * 1. Let the user provide a location via `haskell.serverExecutablePath` option.
109 | * 2. Find a `haskell-language-server` binary on the `$PATH` if the user wants to do that.
110 | * 3. Use GHCup to install and locate HLS and other required tools, such as cabal, stack and ghc.
111 | *
112 | * @param context Context of the extension, required for metadata.
113 | * @param logger Logger for progress updates.
114 | * @param workingDir Working directory in VSCode.
115 | * @param folder Optional workspace folder. If given, will be preferred over {@link workingDir} for finding configuration entries.
116 | * @returns Path to haskell-language-server, paired with additional data required for setting up.
117 | */
118 | export async function findHaskellLanguageServer(
119 | context: ExtensionContext,
120 | logger: Logger,
121 | ghcupConfig: GHCupConfig,
122 | workingDir: string,
123 | folder?: WorkspaceFolder,
124 | ): Promise {
125 | logger.info('Finding haskell-language-server');
126 |
127 | const hasConfigForExecutable = haskellConfig.get('serverExecutablePath') as string;
128 | if (hasConfigForExecutable) {
129 | const exe = findServerExecutable(logger, folder);
130 | return {
131 | location: exe,
132 | tag: 'config',
133 | };
134 | }
135 |
136 | const storagePath: string = getStoragePath(context);
137 | if (!fs.existsSync(storagePath)) {
138 | fs.mkdirSync(storagePath);
139 | }
140 |
141 | // first extension initialization
142 | manageHLS = await promptUserForManagingHls(context, manageHLS);
143 |
144 | // based on the user-decision
145 | if (manageHLS === 'PATH') {
146 | const exe = findHlsInPath(logger);
147 | return {
148 | location: exe,
149 | tag: 'path',
150 | };
151 | } else {
152 | // we manage HLS, make sure ghcup is installed/available
153 | const ghcup = initDefaultGHCup(ghcupConfig, logger, folder);
154 | await ghcup.upgrade();
155 |
156 | // boring init
157 | let latestHLS: string | undefined | null;
158 | let latestCabal: string | undefined | null;
159 | let latestStack: string | undefined | null;
160 | let recGHC: string | undefined | null = 'recommended';
161 | let projectHls: string | undefined | null;
162 | let projectGhc: string | undefined | null;
163 |
164 | // support explicit toolchain config
165 | const toolchainConfig = new Map(Object.entries(haskellConfig.get('toolchain') as ToolConfig)) as ToolConfig;
166 | if (toolchainConfig) {
167 | latestHLS = toolchainConfig.get('hls');
168 | latestCabal = toolchainConfig.get('cabal');
169 | latestStack = toolchainConfig.get('stack');
170 | recGHC = toolchainConfig.get('ghc');
171 |
172 | projectHls = latestHLS;
173 | projectGhc = recGHC;
174 | }
175 |
176 | // get a preliminary toolchain for finding the correct project GHC version
177 | // (we need HLS and cabal/stack and ghc as fallback),
178 | // later we may install a different toolchain that's more project-specific
179 | if (latestHLS === undefined) {
180 | latestHLS = await ghcup.getAnyLatestVersion('hls').then((tool) => tool?.version);
181 | }
182 | if (latestCabal === undefined) {
183 | latestCabal = (await ghcup.findLatestUserInstalledTool('cabal')).version;
184 | }
185 | if (latestStack === undefined) {
186 | latestStack = (await ghcup.findLatestUserInstalledTool('stack')).version;
187 | }
188 | if (recGHC === undefined) {
189 | recGHC = !executableExists('ghc') ? (await ghcup.getLatestAvailableVersion('ghc', 'recommended')).version : null;
190 | }
191 |
192 | // download popups
193 | const promptBeforeDownloads = haskellConfig.get('promptBeforeDownloads') as boolean;
194 | if (promptBeforeDownloads) {
195 | const hlsInstalled = latestHLS ? await installationStatusOfGhcupTool(ghcup, 'hls', latestHLS) : undefined;
196 | const cabalInstalled = latestCabal ? await installationStatusOfGhcupTool(ghcup, 'cabal', latestCabal) : undefined;
197 | const stackInstalled = latestStack ? await installationStatusOfGhcupTool(ghcup, 'stack', latestStack) : undefined;
198 | const ghcInstalled = executableExists('ghc')
199 | ? new ToolStatus(
200 | 'ghc',
201 | await callAsync(`ghc${exeExt}`, ['--numeric-version'], logger, undefined, undefined, false),
202 | )
203 | : // if recGHC is null, that means user disabled automatic handling,
204 | recGHC !== null
205 | ? await installationStatusOfGhcupTool(ghcup, 'ghc', recGHC)
206 | : undefined;
207 | const toInstall: ToolStatus[] = [hlsInstalled, cabalInstalled, stackInstalled, ghcInstalled].filter(
208 | (tool) => tool && !tool.installed,
209 | ) as ToolStatus[];
210 | if (toInstall.length > 0) {
211 | const decision = await window.showInformationMessage(
212 | `Need to download ${toInstall.map((t) => t.nameWithVersion).join(', ')}, continue?`,
213 | 'Yes',
214 | 'No',
215 | "Yes, don't ask again",
216 | );
217 | if (decision === 'Yes') {
218 | logger.info(`User accepted download for ${toInstall.map((t) => t.nameWithVersion).join(', ')}.`);
219 | } else if (decision === "Yes, don't ask again") {
220 | logger.info(
221 | `User accepted download for ${toInstall.map((t) => t.nameWithVersion).join(', ')} and won't be asked again.`,
222 | );
223 | haskellConfig.update('promptBeforeDownloads', false);
224 | } else {
225 | toInstall.forEach((tool) => {
226 | if (tool !== undefined && !tool.installed) {
227 | if (tool.name === 'hls') {
228 | throw new MissingToolError('hls');
229 | } else if (tool.name === 'cabal') {
230 | latestCabal = null;
231 | } else if (tool.name === 'stack') {
232 | latestStack = null;
233 | } else if (tool.name === 'ghc') {
234 | recGHC = null;
235 | }
236 | }
237 | });
238 | }
239 | }
240 | }
241 |
242 | // our preliminary toolchain
243 | const latestToolchainBindir = await ghcup.call(
244 | [
245 | 'run',
246 | ...(latestHLS ? ['--hls', latestHLS] : []),
247 | ...(latestCabal ? ['--cabal', latestCabal] : []),
248 | ...(latestStack ? ['--stack', latestStack] : []),
249 | ...(recGHC ? ['--ghc', recGHC] : []),
250 | '--install',
251 | ],
252 | 'Installing latest toolchain for bootstrap',
253 | true,
254 | (err, stdout, _stderr, resolve, reject) => {
255 | if (err) {
256 | reject("Couldn't install latest toolchain");
257 | } else {
258 | resolve(stdout?.trim());
259 | }
260 | },
261 | );
262 |
263 | // now figure out the actual project GHC version and the latest supported HLS version
264 | // we need for it (e.g. this might in fact be a downgrade for old GHCs)
265 | if (projectHls === undefined || projectGhc === undefined) {
266 | const res = await getLatestProjectHls(ghcup, logger, storagePath, workingDir, latestToolchainBindir);
267 | if (projectHls === undefined) {
268 | projectHls = res[0];
269 | }
270 | if (projectGhc === undefined) {
271 | projectGhc = res[1];
272 | }
273 | }
274 |
275 | // more download popups
276 | if (promptBeforeDownloads) {
277 | const hlsInstalled = projectHls ? await installationStatusOfGhcupTool(ghcup, 'hls', projectHls) : undefined;
278 | const ghcInstalled = projectGhc ? await installationStatusOfGhcupTool(ghcup, 'ghc', projectGhc) : undefined;
279 | const toInstall: ToolStatus[] = [hlsInstalled, ghcInstalled].filter(
280 | (tool) => tool && !tool.installed,
281 | ) as ToolStatus[];
282 | if (toInstall.length > 0) {
283 | const decision = await window.showInformationMessage(
284 | `Need to download ${toInstall.map((t) => t.nameWithVersion).join(', ')}, continue?`,
285 | { modal: true },
286 | 'Yes',
287 | 'No',
288 | "Yes, don't ask again",
289 | );
290 | if (decision === 'Yes') {
291 | logger.info(`User accepted download for ${toInstall.map((t) => t.nameWithVersion).join(', ')}.`);
292 | } else if (decision === "Yes, don't ask again") {
293 | logger.info(
294 | `User accepted download for ${toInstall.map((t) => t.nameWithVersion).join(', ')} and won't be asked again.`,
295 | );
296 | haskellConfig.update('promptBeforeDownloads', false);
297 | } else {
298 | toInstall.forEach((tool) => {
299 | if (!tool.installed) {
300 | if (tool.name === 'hls') {
301 | throw new MissingToolError('hls');
302 | } else if (tool.name === 'ghc') {
303 | projectGhc = null;
304 | }
305 | }
306 | });
307 | }
308 | }
309 | }
310 |
311 | // now install the proper versions
312 | const hlsBinDir = await ghcup.call(
313 | [
314 | 'run',
315 | ...(projectHls ? ['--hls', projectHls] : []),
316 | ...(latestCabal ? ['--cabal', latestCabal] : []),
317 | ...(latestStack ? ['--stack', latestStack] : []),
318 | ...(projectGhc ? ['--ghc', projectGhc] : []),
319 | '--install',
320 | ],
321 | `Installing project specific toolchain: ${[
322 | ['hls', projectHls],
323 | ['GHC', projectGhc],
324 | ['cabal', latestCabal],
325 | ['stack', latestStack],
326 | ]
327 | .filter((t) => t[1])
328 | .map((t) => `${t[0]}-${t[1]}`)
329 | .join(', ')}`,
330 | true,
331 | );
332 |
333 | if (projectHls) {
334 | return {
335 | binaryDirectory: hlsBinDir,
336 | location: path.join(hlsBinDir, `haskell-language-server-wrapper${exeExt}`),
337 | tag: 'ghcup',
338 | };
339 | } else {
340 | return {
341 | binaryDirectory: hlsBinDir,
342 | location: findHlsInPath(logger),
343 | tag: 'ghcup',
344 | };
345 | }
346 | }
347 | }
348 |
349 | async function promptUserForManagingHls(context: ExtensionContext, manageHlsSetting: ManageHLS): Promise {
350 | if (manageHlsSetting !== 'GHCup' && (!context.globalState.get('pluginInitialized') as boolean | null)) {
351 | const promptMessage = `How do you want the extension to manage/discover HLS and the relevant toolchain?
352 |
353 | Choose "Automatically" if you're in doubt.
354 | `;
355 |
356 | const popup = window.showInformationMessage(
357 | promptMessage,
358 | { modal: true },
359 | 'Automatically via GHCup',
360 | 'Manually via PATH',
361 | );
362 |
363 | const decision = (await popup) || null;
364 | let howToManage: ManageHLS;
365 | if (decision === 'Automatically via GHCup') {
366 | howToManage = 'GHCup';
367 | } else if (decision === 'Manually via PATH') {
368 | howToManage = 'PATH';
369 | } else {
370 | window.showWarningMessage(
371 | "Choosing default PATH method for HLS discovery. You can change this via 'haskell.manageHLS' in the settings.",
372 | );
373 | howToManage = 'PATH';
374 | }
375 | haskellConfig.update('manageHLS', howToManage, ConfigurationTarget.Global);
376 | context.globalState.update('pluginInitialized', true);
377 | return howToManage;
378 | } else {
379 | return manageHlsSetting;
380 | }
381 | }
382 |
383 | async function getLatestProjectHls(
384 | ghcup: GHCup,
385 | logger: Logger,
386 | storagePath: string,
387 | workingDir: string,
388 | toolchainBindir: string,
389 | ): Promise<[string, string]> {
390 | // get project GHC version, but fallback to system ghc if necessary.
391 | const projectGhc = toolchainBindir
392 | ? await getProjectGhcVersion(toolchainBindir, workingDir, logger).catch(async (e) => {
393 | logger.error(`${e}`);
394 | window.showWarningMessage(
395 | `I had trouble figuring out the exact GHC version for the project. Falling back to using 'ghc${exeExt}'.`,
396 | );
397 | return await callAsync(`ghc${exeExt}`, ['--numeric-version'], logger, undefined, undefined, false);
398 | })
399 | : await callAsync(`ghc${exeExt}`, ['--numeric-version'], logger, undefined, undefined, false);
400 |
401 | // first we get supported GHC versions from available HLS bindists (whether installed or not)
402 | const metadataMap = (await getHlsMetadata(storagePath, logger)) || new Map();
403 | // then we get supported GHC versions from currently installed HLS versions
404 | const ghcupMap = (await findAvailableHlsBinariesFromGHCup(ghcup)) || new Map();
405 | // since installed HLS versions may support a different set of GHC versions than the bindists
406 | // (e.g. because the user ran 'ghcup compile hls'), we need to merge both maps, preferring
407 | // values from already installed HLSes
408 | const merged = new Map([...metadataMap, ...ghcupMap]); // right-biased
409 | // now sort and get the latest suitable version
410 | const latest = [...merged]
411 | .filter(([_k, v]) => v.some((x) => x === projectGhc))
412 | .sort(([k1, _v1], [k2, _v2]) => comparePVP(k1, k2))
413 | .pop();
414 |
415 | if (!latest) {
416 | throw new NoMatchingHls(projectGhc);
417 | } else {
418 | return [latest[0], projectGhc];
419 | }
420 | }
421 |
422 | /**
423 | * Obtain the project ghc version from the HLS - Wrapper (which must be in PATH now).
424 | * Also, serves as a sanity check.
425 | * @param toolchainBindir Path to the toolchain bin directory (added to PATH)
426 | * @param workingDir Directory to run the process, usually the root of the workspace.
427 | * @param logger Logger for feedback.
428 | * @returns The GHC version, or fail with an `Error`.
429 | */
430 | export async function getProjectGhcVersion(
431 | toolchainBindir: string,
432 | workingDir: string,
433 | logger: Logger,
434 | ): Promise {
435 | const title = 'Working out the project GHC version. This might take a while...';
436 | logger.info(title);
437 |
438 | const args = ['--project-ghc-version'];
439 |
440 | const newPath = addPathToProcessPath(toolchainBindir);
441 | const environmentNew: IEnvVars = {
442 | PATH: newPath,
443 | };
444 |
445 | return callAsync(
446 | 'haskell-language-server-wrapper',
447 | args,
448 | logger,
449 | workingDir,
450 | title,
451 | false,
452 | environmentNew,
453 | (err, stdout, stderr, resolve, reject) => {
454 | if (err) {
455 | // Error message emitted by HLS-wrapper
456 | const regex =
457 | /Cradle requires (.+) but couldn't find it|The program '(.+)' version .* is required but the version of.*could.*not be determined|Cannot find the program '(.+)'\. User-specified/;
458 | const res = regex.exec(stderr);
459 | if (res) {
460 | for (let i = 1; i < res.length; i++) {
461 | if (res[i]) {
462 | reject(new MissingToolError(res[i]));
463 | }
464 | }
465 | reject(new MissingToolError('unknown'));
466 | }
467 | reject(
468 | Error(
469 | `haskell-language-server --project-ghc-version exited with exit code ${err.code}:\n${stdout}\n${stderr}`,
470 | ),
471 | );
472 | } else {
473 | logger.info(`The GHC version for the project or file: ${stdout?.trim()}`);
474 | resolve(stdout?.trim());
475 | }
476 | },
477 | );
478 | }
479 |
480 | /**
481 | * Find the storage path for the extension.
482 | * If no custom location was given
483 | *
484 | * @param context Extension context for the 'Storage Path'.
485 | * @returns
486 | */
487 | export function getStoragePath(context: ExtensionContext): string {
488 | let storagePath: string | undefined = haskellConfig.get('releasesDownloadStoragePath');
489 |
490 | if (!storagePath) {
491 | storagePath = context.globalStorageUri.fsPath;
492 | } else {
493 | storagePath = resolvePathPlaceHolders(storagePath);
494 | }
495 |
496 | return storagePath;
497 | }
498 |
499 | /**
500 | *
501 | * Complements {@link getReleaseMetadata}, by checking possibly locally compiled
502 | * HLS in ghcup
503 | * If 'targetGhc' is omitted, picks the latest 'haskell-language-server-wrapper',
504 | * otherwise ensures the specified GHC is supported.
505 | *
506 | * @param ghcup GHCup wrapper.
507 | * @returns A Map of the locally installed HLS versions and with which `GHC` versions they are compatible.
508 | */
509 |
510 | async function findAvailableHlsBinariesFromGHCup(ghcup: GHCup): Promise