[release] prepare v0.33.0 release

33d46b5 src/goDebugConfiguration: fix debugAdapter default selection
df181c8 package.json: clarify format and precedence of env-setting debug attributes
e01cc4b src/goVulncheck: prevent concurrent command execution
6a4ca67 src/goDebugConfiguration: default to dlv-dap for remote in preview mode
f4ccfc9 envUtils.ts: log envFile load error
d459a78 src/goExplorer.ts: show go explorer on extension activation
8b84a39 tools/docs2wiki: remove redundant title
9ea27e6 tools/docs2wiki: tool that rewrites local .md file urls with wiki links
3b3ac3f docs: fix _Sidebar and add TOC to Home.md
1e2bc88 .vscodeignore: ignore more config files
097d627 build/all.bash: add go test in ci test
a87c062 src/goTools: install staticcheck@v0.2.2 when using go1.16 or older
3549a9a tools/installtools: install staticcheck@v0.2.2 for go < 1.17
5127e14 src/goToolsInformation: mark go-outline as replacedByGopls
8b9a0e7 test/gopls/survey.test: removed the unread variable
7c3604a docs: update debug-adapter.md
ae41a2d docs: add Home/footer/sidebar/faq, move release_plan.md
33925ef .github/workflows: add wiki.yml that copies docs/ to wiki repo
9c33653 package.json: update to moment 2.29.2
e599c1a all: gofmt
8c5171d src/goVulncheck.ts: add package or workspace choice to vulncheck command
1a65a12 src/goTest: Use more robust method to find module name in go.mod
a387381 .github/workflows: update GabrielBB/xvfb-action and go versions
21fe4bb src/goMain: require configureLanguageServer for full activation
d0de08d test/gopls/survey: stub vscode.env.openExternal
089cf13 docs/advanced.md: remove go1.18 section, rearrange contents
28583ae docs/contributing.md: discuss kokoro and how to use it
9e9244c src/goVulncheck.ts: create vulncheck command
cc7344b Revert "build: run npm audit as part of ci test"
47a586b package.json: sync gopls@v0.8.2
ea58fe1 debugging.md: add FAQ on debugging binaries with missing debug info
106d188 build: run npm audit as part of ci test
c5bd652 package.json: describe enum values of lintOnSave/vetOnSave
aa3f7cc tools/installtools: remove temporarily hard-coded gopls version
0fa41b1 docs/contributing.md: add tips for contribution
e2a7fb5 docs/debugging.md: fix the default of envFile
d7f9578 src/language/goLanguageServer: restore gopls opt-out survey prompt
e694981 package-lock.json: update minimist to v1.2.6
3648e64 src/goExplorer.ts: make workspace go env editable via commands
7ab6cc5 src/goExplorer.ts: create opened files if they do not exist
b0d47a8 src/goExplorer.ts: consolidate tools information into single tree item
3999594 package.json: add go icon to explorer view
78cc836 src/language: move legacy provider registration to a separate module
cff188b src/language: move language features to the language directory
c14a3ee test/integration: test go explorer tree view ui
4e80a4f src/goExplorer.ts: consolidate usage of go env command
def9da9 src/goExplorer.ts: update tree item icons
5d86420 src/goExplorer.ts: display tools configuration detail in explorer view
3f5f0cd src/goExplorer.ts: add a go tree view to the explorer
8ab268f src/goDebugConfiguration: remove remote attach info pop-up
b48fe43 package.json: start v0.33.0 dev
bf63e1e CHANGELOG.md: change log for v0.32.0
2238caa package.json: reuse go.mod tmGrammar for go.work syntax highlight

Change-Id: I91a19c718b3066047254f2baaccc09eb5afa6bb5
diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml
index 3dc0e82..18af3bf 100644
--- a/.github/workflows/release-nightly.yml
+++ b/.github/workflows/release-nightly.yml
@@ -5,7 +5,7 @@
 on:
   schedule:
     - cron: "0 15 * * MON-THU"  # 15 UTC, monday-thursday daily
-  workflow_dispatch:   
+  workflow_dispatch:
 
 jobs:
   release:
@@ -28,7 +28,7 @@
       - name: Setup Go
         uses: actions/setup-go@v2
         with:
-         go-version: '1.17'
+         go-version: '1.18'
 
       - name: Install dependencies
         run: npm ci
@@ -52,7 +52,7 @@
         run: npm run unit-test
 
       - name: Run tests
-        uses: GabrielBB/xvfb-action@v1.0
+        uses: GabrielBB/xvfb-action@86d97bde4a65fe9b290c0b3fb92c2c4ed0e5302d
         with:
           run: npm run test
         env:
diff --git a/.github/workflows/test-long-all.yml b/.github/workflows/test-long-all.yml
index 8606e51..611f636 100644
--- a/.github/workflows/test-long-all.yml
+++ b/.github/workflows/test-long-all.yml
@@ -17,7 +17,7 @@
       matrix:
         os: [ubuntu-latest, windows-latest, macos-latest]
         version: ['stable', 'insiders']
-        go: ['1.15', '1.16', '1.17', '1.18.0-beta2']
+        go: ['1.15', '1.16', '1.17', '1.18']
 
     steps:
       - name: Clone repository
@@ -52,7 +52,7 @@
         run: npm run unit-test
 
       - name: Run tests
-        uses: GabrielBB/xvfb-action@v1.0
+        uses: GabrielBB/xvfb-action@86d97bde4a65fe9b290c0b3fb92c2c4ed0e5302d
         with:
           run: npm run test
         env:
diff --git a/.github/workflows/test-long.yml b/.github/workflows/test-long.yml
index a130034..aee3b90 100644
--- a/.github/workflows/test-long.yml
+++ b/.github/workflows/test-long.yml
@@ -16,7 +16,7 @@
       matrix:
         os: [ubuntu-latest, windows-latest] # TODO: reenable macos-latest
         version: ['stable']
-        go: ['1.15', '1.16', '1.17', '1.18.0-beta2']
+        go: ['1.15', '1.16', '1.17', '1.18']
 
     steps:
       - name: Clone repository
@@ -52,7 +52,7 @@
         run: npm run unit-test
         
       - name: Run tests
-        uses: GabrielBB/xvfb-action@v1.0
+        uses: GabrielBB/xvfb-action@86d97bde4a65fe9b290c0b3fb92c2c4ed0e5302d
         with:
           run: npm run test
         env:
diff --git a/.github/workflows/test-smoke.yml b/.github/workflows/test-smoke.yml
index 3954b7f..1bfc8ed 100644
--- a/.github/workflows/test-smoke.yml
+++ b/.github/workflows/test-smoke.yml
@@ -29,7 +29,7 @@
       - name: Setup Go
         uses: actions/setup-go@v2
         with:
-         go-version: '1.17'
+         go-version: '1.18'
 
       - name: Install dependencies
         run: npm ci
@@ -49,7 +49,7 @@
         run: npm run unit-test
         
       - name: Run tests
-        uses: GabrielBB/xvfb-action@v1.0
+        uses: GabrielBB/xvfb-action@86d97bde4a65fe9b290c0b3fb92c2c4ed0e5302d
         with:
           run: npm run test
         env:
diff --git a/.github/workflows/wiki.yml b/.github/workflows/wiki.yml
new file mode 100644
index 0000000..a2848f2
--- /dev/null
+++ b/.github/workflows/wiki.yml
@@ -0,0 +1,44 @@
+name: Wiki
+
+# Controls when the workflow will run
+on:
+  push:
+    branches: [ master ]
+    paths:
+      - 'docs/**'
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+  # This workflow contains a single job called "build"
+  publish:
+    # The type of runner that the job will run on
+    runs-on: ubuntu-latest
+
+    # Steps represent a sequence of tasks that will be executed as part of the job
+    steps:
+      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+      - name: Checkout vscode-go repo code
+        uses: actions/checkout@v3
+        with:
+          path: vscode-go
+      - name: Checkout vscode-go.wiki repo code
+        uses: actions/checkout@v3
+        with:
+          repository: ${{github.repository}}.wiki
+          path: wiki
+      - name: Setup Go
+        uses: actions/setup-go@v2
+      - name: Push to wiki
+        run: |
+          cd vscode-go
+          go run ./tools/docs2wiki -w ./docs
+          cd ..
+          cd wiki
+          diff -ruN --exclude=.git . ../vscode-go/docs > ../mypatch || patch -p3 -E -f < ../mypatch
+          git config --local user.email "action@github.com"
+          git config --local user.name "GitHub Action"
+          git add .
+          git commit -m "Reflecting changes from ${GITHUB_REPOSITORY}@${GITHUB_SHA}"
+          git remote -v
+          git push
+          git log -1
diff --git a/.vscodeignore b/.vscodeignore
index abc97ba..a6bbc5a 100644
--- a/.vscodeignore
+++ b/.vscodeignore
@@ -1,5 +1,9 @@
 **/*.map
 **/tslint.json
+.DS_Store
+.editorconfig
+.eslintignore
+.eslintrc.json
 .git/
 .github/
 .gitignore
@@ -9,7 +13,10 @@
 .vscode-test/
 SECURITY.md
 build/
+codereview.cfg
 docs/
+go.mod
+go.sum
 node_modules/
 out/
 src/
diff --git a/README.md b/README.md
index 906e4f5..7e18d2d 100644
--- a/README.md
+++ b/README.md
@@ -9,9 +9,9 @@
 [Go programming language](https://golang.org/).
 
 📣
-[Remote attach debugging](docs/debugging.md#connecting-to-headless-delve-with-target-specified-at-server-start-up) is now available via Delve's native DAP implementation with Delve v1.7.3 or newer.
-We plan to enable this as the default in early 2022 to enhance remote debugging with the same
-[debugging features](docs/debugging.md) that are already in use for local debugging.
+[Remote attach debugging](docs/debugging.md#connecting-to-headless-delve-with-target-specified-at-server-start-up) is now available via Delve's native DAP implementation with Delve v1.7.3 or newer. It enchances remote debugging with the same
+[debugging features](docs/debugging.md) that are already in use for local debugging. It is now the default with the
+[Go Nightly](docs/nightly.md) build of the extension and will become the default for the stable releases in mid 2022.
 We recommend switching your remote attach configurations in `launch.json` to use
 `"debugAdapter":"dlv-dap"` now to verify that this works for you.
 Please [file a new issue](https://github.com/golang/vscode-go/issues/new/choose) if you encounter any problems.
@@ -83,6 +83,13 @@
 [TextMate rule](https://github.com/jeff-hykin/better-go-syntax) embedded in VS
 Code, not by this extension.
 
+For better syntax highlighting, we recommend enabling
+[semantic highlighting](https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide)
+by turning on [Gopls' `ui.semanticTokens` setting](https://github.com/golang/vscode-go/blob/master/docs/settings.md#uisemantictokens).
+    ```
+    "gopls": { "ui.semanticTokens": true }
+    ```
+
 ## Tools
 
 The extension uses a few command-line tools developed by the Go community. In
diff --git a/build/all.bash b/build/all.bash
index de29a6a..a0d858a 100755
--- a/build/all.bash
+++ b/build/all.bash
@@ -55,6 +55,11 @@
 
   echo "**** Check if vsce works ****"
   vsce package
+
+  echo "**** Run Go tests ****"
+  go test ./...
+  # TODO(hyangah): see if go clean -modcache makes kokoro builder happy
+  go clean -modcache
 }
 
 run_test_in_docker() {
diff --git a/docs/Home.md b/docs/Home.md
new file mode 100644
index 0000000..d2a9e3b
--- /dev/null
+++ b/docs/Home.md
@@ -0,0 +1,28 @@
+Welcome to the VSCode Go Wiki!
+
+### 📣 News and Upcoming Changes
+
+[Remote attach debugging](./debugging#connecting-to-headless-delve-with-target-specified-at-server-start-up) is now available via Delve's native DAP implementation with Delve v1.7.3 or newer.
+We plan to enable this as the default in 2022 H1 to enhance remote debugging with the same
+[debugging features](./debugging.md) that are already in use for local debugging.
+We recommend switching your remote attach configurations in `launch.json` to use
+`"debugAdapter":"dlv-dap"` now to verify that this works for you.
+Please [file a new issue](https://github.com/golang/vscode-go/issues/new/choose) if you encounter any problems.
+
+### User Documentation
+
+* [Overview of Extension Features](features.md)
+
+* [Debugging Feature](debugging)
+* [Diagnostics](https://github.com/golang/tools/blob/master/gopls/doc/analyzers.md)
+* [Setting Up Your Workspace](https://github.com/golang/tools/blob/master/gopls/doc/workspace.md)
+
+* [Available Settings](settings.md)
+* [List of Extension Commands](commands.md)
+* [Commonly Used `tasks.json` Setup](tasks.md)
+* [3rd-party Tools Used By Extension](tools.md)
+* [User Interface](ui.md)
+* [FAQs](faq.md)
+* [Troubleshooting](troubleshooting.md)
+* [Advanced Topics](advanced.md)
+* [How to Contribute](contributing.md)
diff --git a/docs/_Footer.md b/docs/_Footer.md
new file mode 100644
index 0000000..e8b412b
--- /dev/null
+++ b/docs/_Footer.md
@@ -0,0 +1,5 @@
+
+
+### Want to contribute to this Wiki?
+
+Update the source in the [vscode-go project's docs](https://github.com/golang/vscode-go/tree/master/docs) directory, and send a pull request.
diff --git a/docs/_Sidebar.md b/docs/_Sidebar.md
new file mode 100644
index 0000000..d859891
--- /dev/null
+++ b/docs/_Sidebar.md
@@ -0,0 +1,21 @@
+**For Users**
+* [[features]]
+* [[commands]]
+* [[settings]]
+* [[debugging]]
+* [[tasks]]
+* [[tools]]
+* [[ui]]
+* [[faq]]
+* [[troubleshooting]]
+* [[advanced]]
+* [[nightly]]
+* [[contributing]]
+
+**For Developers**
+* [[release_plan]]
+* [[smoke test]]
+* [[test explorer]]
+* [[debug adapter]]
+* [Go Language Server](https://go.dev/s/gopls)
+* [Delve DAP](https://github.com/go-delve/delve/tree/master/Documentation/api/dap)
diff --git a/docs/advanced.md b/docs/advanced.md
index b4e367e..99136a3 100644
--- a/docs/advanced.md
+++ b/docs/advanced.md
@@ -3,61 +3,27 @@
 This document describes more advanced ways of working with the VS Code Go
 extension.
 
-## Using Go1.18
+## Formatting Code and Organizing Imports
 
-The latest Go extension (`v0.31.0+` or [Nightly](./nightly.md))
-contains experimental support for the [new Go 1.18 features](https://tip.golang.org/doc/go1.18).
+When you have multiple formatter extensions, be sure to set this
+extension as the default formatter for go language.
+```json5
+"[go]": {
+  "editor.defaultFormatter": "golang.go"
+}
+```
 
-* [Generics](https://go.dev/doc/tutorial/generics): IntelliSense, Code Editing, Diagnostics, Sytax Highlighting, etc.
-* [Fuzzing](https://go.dev/doc/tutorial/fuzz): Run/Debug Test using CodeLens and Test UI (available in Nightly).
-* [Go workspace mode](https://pkg.go.dev/cmd/go@go1.18beta2#hdr-Workspace_maintenance): _WIP_
+Formatting and organizing imports are enabled by default. You
+can choose to disable them by configuring the following settings.
 
-The latest Go extension (v0.31.0+, or [Nightly](./nightly.md)) supports the new Go 1.18 features with
-the following configuration.
-
-1. Get the preview of Go 1.18 by visiting [the official Go downloads page](https://go.dev/dl/#go1.18beta2).
-The following command will install `go1.18beta2` binary in your `$GOPATH/bin`
-or `GOBIN` directory, and download the Go 1.18 SDK.
-    ```sh
-    go install golang.org/dl/go1.18beta2@latest
-    go1.18beta2 download
-    ```
-
-    The location of the downloaded Go 1.18 SDK directory can be found with
-    ```sh
-    go1.18beta2 env GOROOT
-    ```
-
-2. Configure the extension to use `go1.18beta2`
-(or the `go` binary in the Go 1.18 SDK `bin` directory), using [one of
-the options listed below](https://github.com/golang/vscode-go/blob/master/docs/advanced.md#choosing-a-different-version-of-go).
-
-3. In order to process the new language features, [tools](./tools.md) this extension
-needs rebuilding with Go 1.18. **Please run the [`Go: Install/Update Tools`](commands.md#go-installupdate-tools)
-command to update tools**.
-
-4. (optional) for correct syntax highlighting, we recommend to enable 
-[semantic highlighting](https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide)
-by turning on [Gopls' `ui.semanticTokens` setting](https://github.com/golang/vscode-go/blob/master/docs/settings.md#uisemantictokens).
-    ```
-    "gopls": { "ui.semanticTokens": true }
-    ```
-
-### Known Issues
-
-The Go Tools team are actively working on fixing bugs and improving usability
-of the new Go 1.18 features. Please take a look at current known
-[`vscode-go` issues](https://github.com/golang/vscode-go/issues?q=is%3Aissue+label%3Ago1.18+)
-and [`gopls` issues](https://github.com/golang/go/milestone/244).
-
-  * Features that depend on 3rd party tools (`staticcheck`, `golangci-lint`, ...) may not work yet.
-  Please follow the [tracking issue](https://github.com/golang/go/issues/50558).
-  * Support for `go.work` is a work in progress.
-
-In order to pick up the latest fixes, please consider to use the [Nightly](./nightly.md) version of
-the extension. We plan to make prereleases of `gopls` v0.8.0 available frequently.
-The Nightly version installs the pre-release version of `gopls`, so you will be able to pick up the
-latest bug fixes of `gopls` without manual installation.
+```json5
+"[go]": {
+        "editor.formatOnSave": false,
+        "editor.codeActionsOnSave": {
+            "source.organizeImports": false
+        }
+}
+```
 
 ## Choosing a different version of Go
 
@@ -112,7 +78,6 @@
 configuration.
 
 First, you **must open the `src/` folder in VS Code**, not the Go tree root.
-(See [golang/go#32394](https://github.com/golang/go/issues/32394).)
 
 Then, you need to configure the workspace, by placing the following in
 `src/.vscode/settings.json`. [Command Palette] ->
@@ -146,26 +111,6 @@
 If you see an "inconsistent vendoring" error, please report it at
 [golang/go#40250](https://github.com/golang/go/issues/40250).
 
-## Formatting Code and Organizing Imports
 
-When you have multiple formatter extensions, be sure to set this
-extension as the default formatter for go language.
-```json5
-"[go]": {
-  "editor.defaultFormatter": "golang.go"
-}
-```
-
-Formatting and organizing imports are enabled by default. You
-can choose to disable them by configuring the following settings.
-
-```json5
-"[go]": {
-        "editor.formatOnSave": false,
-        "editor.codeActionsOnSave": {
-            "source.organizeImports": false
-        }
-}
-```
 
 [Command Palette]: https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette
diff --git a/docs/commands.md b/docs/commands.md
index ee71ec0..fede59f 100644
--- a/docs/commands.md
+++ b/docs/commands.md
@@ -242,3 +242,23 @@
 ### `Go: Reset Global State`
 
 Reset keys in global state to undefined.
+
+### `Go Explorer: Refresh`
+
+Refresh the Go explorer. Only available as a menu item in the explorer.
+
+### `Go Explorer: Open File`
+
+Open a file from the Go explorer. Only available as a menu item in the explorer.
+
+### `Go: Edit Workspace Env`
+
+Edit the Go Env for the active workspace.
+
+### `Go: Reset Workspace Env`
+
+Reset the Go Env for the active workspace.
+
+### `Go: Run Vulncheck (Experimental)`
+
+Run go vulncheck.
diff --git a/docs/contributing.md b/docs/contributing.md
index f05f617..e2fa033 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -15,6 +15,7 @@
   * [Test](#test)
   * [Sideload](#sideload)
 * [Mail your change for review](#mail-your-change-for-review)
+  * [Presubmit test in CI](#presubmit-test-in-ci)
 
 ## Before you start coding
 
@@ -26,9 +27,27 @@
 
 The VS Code Go maintainers are reachable via the issue tracker and the [#vscode-dev] channel in the [Gophers Slack]. Please reach out on Slack with questions, suggestions, or ideas. If you have trouble getting started on an issue, we'd be happy to give pointers and advice.
 
-### Debug Adapter
+### Language Server (`gopls`)
 
-Please note that extra configuration is required to build and run the [Debug Adapter](debug-adapter.md), which controls the debugging features of this extension. Refer to [the documentation for the Debug Adapter](debug-adapter.md) to set that up.
+Many of the language features like auto-completion, documentation, diagnostics are implemented
+by the Go language server ([`gopls`](https://pkg.go.dev/golang.org/x/tools/gopls)).
+This extension communicates with `gopls` using [vscode LSP client library](https://github.com/microsoft/vscode-languageserver-node) from [`language/goLanguageServer.ts`](https://github.com/golang/vscode-go/tree/master/src/language).
+
+For extending the language features or fixing bugs, please follow `gopls`'s
+[contribution guide](https://github.com/golang/tools/blob/master/gopls/doc/contributing.md).
+
+### Debug Adapter (`dlv dap`)
+
+Debugging features are implemented by Delve (`dlv`) and its native DAP implementation 
+([`dlv dap`](https://github.com/go-delve/delve/blob/master/Documentation/api/dap/README.md)).
+
+* goDebugConfiguration.ts: where launch configuration massaging occurs.
+* goDebugFactory.ts: where a thin adapter that communicates with the `dlv dap` process is defined.
+* [github.com/go-delve/delve](https://github.com/go-delve/delve/tree/master/service/dap): where native DAP implementation in Delve exists.
+
+For extending the features of Delve, please follow `Delve` project's [contribution guide](https://github.com/go-delve/delve/blob/master/CONTRIBUTING.md).
+
+The debugging feature documentation has a dedicated section for tips for development (See ["Developing"](https://github.com/golang/vscode-go/blob/master/docs/debugging.md#developing) section).
 
 ## Developing
 
@@ -62,16 +81,78 @@
 
 ## Test
 
-Simple unit tests that do not require interaction with VS Code are located in `test/unit`. 
-Tests in `test/integration` and `test/gopls` directories are integration tests. They involve invocation of the VS Code API and 
-require external Go tools installed in `GOPATH`. The command `installtools` in [`tools/installtools/main.go`](https://github.com/golang/vscode-go/blob/master/tools/installtools/main.go)
-installs all the tool dependencies in `GOPATH`. 
+**note**: Unfortunately, VS Code test framework inherits your user settings when running tests [Issue 43](https://github.com/golang/vscode-go/issues/43). Make sure VS Code user settings do not contain any go related configuration, except `go.gopath` or `go.toolsGopath` in case you installed the tools for testing in a different `GOPATH`.
+
 
 1. `export GOPATH=/path/to/gopath/for/test`
-1. `go run tools/installtools/main.go`
-1. Unfortunately, VS Code test framework inherits your user settings when running tests [Issue 43](https://github.com/golang/vscode-go/issues/43). Make sure VS Code user settings do not contain any go related configuration, except `go.gopath` or `go.toolsGopath` in case you installed the tools for testing in a different `GOPATH`.
+2. `go run tools/installtools/main.go` -- this will install all tools in the `GOPATH/bin` built from master/main.
+3. There are currently two different types of tests in this repo:
+  - `npm run unit-test`: this runs unit tests defined in `test/unit`. They are light-weight tests that don't require `vscode` APIs.
+  - `npm run test`: this runs the integration tests defined in `test/integration` and `test/gopls`. They test logic that involve `vscode` APIs - which requires actually downloading & running Visual Studio Code (`code`) and loading the compiled extension/test code in it.
+4. Before sending a CL, make sure to run
+  - `npm run lint`: this runs linter.
+  - `go run tools/generate.go -w=false -gopls=true`: this checks generated documentations are up-to-date.
 
-There are currently three test launch configurations: (1) `Launch Extension Tests`, (2) `Launch Extension Tests with Gopls`, and (3) `Launch Unit Tests`. To run the tests locally, open the Run view (`Ctrl+Shift+D`), select the relevant launch configuration, and hit the Play button (`F5`).
+### Testing Tips
+
+#### (1) Running only a subset of integration or unit tests:
+When running them from terminal:
+  - Option 1: Utilize `MOCHA_GREP` environment variable. That is equivalent with [`mocha --grep` flag](https://mochajs.org/#command-line-usage) that runs tests matching the given string or regexp. E.g. `MOCHA_GREP=gopls npm run test` which runs all integration tests whose suite/test names contain `"gopls"`.
+  - Option 2: modify the test source code and set the [`only`](https://mochajs.org/#exclusive-tests) or [`skip`](https://mochajs.org/#inclusive-tests) depending on your need. If necessary, you can also modify `test/integration/index.ts` or `test/gopls/index.ts` to include only the test files you want to focus on. Make sure to revert them before sending the changes for review.
+
+#### (2) Debugging tests from VS Code: 
+`.vscode/launch.json` defines test launch configurations. To run the tests locally, open the Run view (`Ctrl+Shift+D`), select the relevant launch configuration, and hit the Play button (`F5`). Output and results of the tests, including any logging written with `console.log` will appear in the `DEBUG CONSOLE` tab.
+You can supply environment variables (e.g. `MOCHA_GREP`) by modifying the launch configuration entry's `env` property.
+  - `Launch Unit Tests`: runs unit tests in `test/unit` (same as `npm run unit-test`)
+  - `Launch Extension Tests`: runs tests in `test/integration` directory (similar to `npm run test` but runs only tests under `test/integration` directory)
+  - `Launch Extension Tests with Gopls`: runs tests in `test/gopls directory (similar to `npm run test` but runs only tests under `test/gopls` directory)
+
+When you want to filter tests while debugging, utilize the `MOCAH_GREP` environment variable discussed previously - i.e., set the environment variable in the `env` property of the launch configuration.
+
+#### (3) Another way to run all tests:
+`build/all.bash test` is the script used by a CI (Linux).
+
+#### (4) Using different versions of tools.
+The tests will pick tools found from `GOPATH/bin` first. So, install the versions you want there.
+
+## Running/Debugging the Extension
+
+Select the [`Launch Extension`](https://github.com/golang/vscode-go/blob/e2a7fb523acffea3427ad7e369c3b2abc30b775b/.vscode/launch.json#L13) configuration, and hit the Play button (`F5`). This will build the extension and start a new VS Code window with the title `"[Extension Development Host]"` that uses the newly built extension. This instance has the node.js debugger attached automatically. You can debug using the VS Code Debug UI of the main window (e.g. set breakpoints, inspect variables, step, etc)
+
+The VS Code window may have the folder or file used during your previous testing. If you want to change the folder during testing, close the folder by using "File > Close Folder", and open a new folder from the VS Code window under test.
+
+### Debugging interaction with `gopls`
+
+When developing features in `gopls`, you may need to attach a debugger to `gopls` and configure the extension to connect to the `gopls` instance using [the gopls deamon mode](https://github.com/golang/tools/blob/master/gopls/doc/daemon.md).
+
+1. Start a gopls in deamon mode:
+```
+gopls -listen=:37374" -logfile=auto -debug=:0 serve
+```
+
+Or, if you use vscode for gopls development, you can configure `launch.json` of the `x/tools/gopls` project:
+
+```
+...
+  {
+    "name": "Launch Gopls",
+    "type": "go",
+    "request": "launch",
+    "mode": "auto",
+    "program": "${workspaceFolder}/gopls",
+    "args": ["-listen=:37374", "-logfile=auto", "-debug=:0"],
+    "cwd": "<... directory where you want to run your gopls from ...",
+  }
+...
+```
+
+2. Start the extension debug session using the `Launch Extension` configuration.
+
+3. Configure the settings.json of the project open in the `"[Extension Development Host]"` window to start `gopls` that connects to the gopls we started in the step 1.
+
+```
+  "go.languageServerFlags": ["-remote=:37374", "-rpc.trace"]
+```
 
 ## Sideload
 
@@ -97,7 +178,7 @@
 
 Once you have coded, built, and tested your change, it's ready for review! There are two ways to mail your change: (1) through [a GitHub pull request (PR)](https://golang.org/doc/contribute.html#sending_a_change_github), or (2) through a [Gerrit code review](https://golang.org/doc/contribute.html#sending_a_change_gerrit).
 
-In either case, code review will happen in [Gerrit](https://www.gerritcodereview.com/), which is used for all repositories in the Go project. GitHub pull requests will be mirrored into Gerrit, so you can follow a more traditional GitHub workflow, but you will still have to look at Gerrit to read comments.
+In either case, code review will happen in [Gerrit](https://www.gerritcodereview.com/), which is used for all repositories in the Go project. We strongly recommend the Gerrit code review if you plan to send many changes. GitHub pull requests will be mirrored into Gerrit, so you can follow a more traditional GitHub workflow, but you will still have to look at Gerrit to read comments.
 
 The easiest way to start is by reading this [detailed guide for contributing to the Go project](https://golang.org/doc/contribute.html). Important things to note are:
 
@@ -107,11 +188,18 @@
 
 Once you've sent out your change, a maintainer will take a look at your contribution within a few weeks. If you don't hear back, feel free to ping the issue or send a message to the [#vscode-dev] channel of the [Gophers Slack].
 
-## [Continuous Integration](testing.md)
+### Presubmit Test in CI
 
-The extension's test suite will run on your change once it has been mailed. If you have contributed via a GitHub PR, the test results will be provided via a [GitHub Action](testing.md#testing-via-github-actions) result on the PR. If you have mailed a Gerrit CL directly, tests will run in [Google Cloud Build](testing.md#testing-via-gcb), and the results will be posted back on the CL.
+When you mail your CL or upload a new patch to an existing CL, *AND*
+you or a fellow contributor assigns the `Run-TryBot=+1` label in Gerrit, the test command defined in 
+`build/all.bash` will run by `Kokoro`, which is Jenkins-like Google infrastructure
+for running Dockerized tests. `Kokoro` will post the result as a comment, and add its `TryBot-Result`
+vote after each test run completes.
 
-Note that, as of June 2020, the GCB and Gerrit integration is not yet ready, so the results of the CI run **will not** appear on the Gerrit changelist. Instead, if your change fails on GCB, we will notify you and provide you with the relevant logs.
+To force a re-run of the Kokoro CI,
+  * Remove `TryBot-Result` vote (hover over the label, and click the trashcan icon).
+  * Reply in Gerrit with the comment "kokoro rerun". Make sure to keep the `Run-TryBot` +1 vote.
+
 
 [#vscode-dev]: https://gophers.slack.com/archives/CUWGEKH5Z
 [Gophers Slack]: https://invite.slack.golangbridge.org/
diff --git a/docs/debug-adapter.md b/docs/debug-adapter.md
index 31b6a3d..b0dfbc5 100644
--- a/docs/debug-adapter.md
+++ b/docs/debug-adapter.md
@@ -37,12 +37,12 @@
 1. Open the `vscode-go` folder in VS Code.
 2. Go to the Run view and choose the `Extension + Debug server` debug configuration. This combines `Launch Extension` and `Launch as server` debug configurations.
 3. Add breakpoints as needed and start debugging (`F5`). It will start an Extension Development Host window and the Debug Adapter server process at port 4711. Debuggers are attached to both processes and the breakpoints will apply to both of them.
-4. In the Extension Development Host window, open the Go application source code you'd like to debug. Here, as above, create a debug configuration pointing to the program you want to debug. Add `"debugServer": 4711` to the root of the configuration. Then, run the debug configuration (`F5`), which will start debugging of the Go application.
-5. Combined debug information (call stacks, breakpoints, etc) of the debugged Extension Development Host and the Debug Adapter will be displayed in the debug view of the original VS Code window. You can use the dropdown menu in the Debug toolbar to switch between the two instances (`Launch Extension` and `Launch as server`).
+4. In the Extension Development Host window, open the Go application source code you'd like to debug. As above, create a debug configuration pointing to the program you want to debug. Add `"debugServer": 4711` to the root of the configuration. Then, run the debug configuration (`F5`), which will start debugging the Go application.
+5. Combined debug information (call stacks, breakpoints, etc.) of the debugged Extension Development Host and the Debug Adapter will be displayed in the debug view of the original VS Code window. You can use the drop-down menu in the Debug toolbar to switch between the two instances (`Launch Extension` and `Launch as server`).
 
 ## Debug VS Code and the Debug Adapter
 
-In some very rare cases, you may find it helpful to debug VS Code itself. An example of such a case might be veryfing workbench behavior and state before executing debug adapter API calls.
+In some very rare cases, you may find it helpful to debug VS Code itself. An example of such a case might be verifying workbench behavior and state before executing debug adapter API calls.
 
 First, ensure that you can [build and run VS Code](https://github.com/Microsoft/vscode/wiki/How-to-Contribute#build-and-run) from source successfully.
 
@@ -51,8 +51,8 @@
 1. Open an instance of VS Code that you have built from source.
 2. [Sideload](contributing.md#sideload) your local `vscode-go` extension to the local instance of VS Code. This can be done by copying the contents of the `vscode-go` directory into `$HOME/.vscode-oss-dev/extensions/ms-vscode.go` (the exact location may vary by OS).
 3. Open the `vscode` folder in Visual Studio Code.
-4. Launch the VS Code debug instance (OSS - Code) by choosing the `Launch VS Code` debug configuraion from the drop-down in the Run view. Add breakpoints as needed.
+4. Launch the VS Code debug instance (OSS - Code) by choosing the `Launch VS Code` debug configuration from the drop-down in the Run view. Add breakpoints as needed.
 5. In another instance of VS Code, open the `vscode-go` folder. Choose the `Launch as server` debug configuration in the Run view. Add breakpoints as desired in the [`vscode-go/src/debugAdapter/goDebug.ts`](../src/debugAdapter/goDebug.ts) file.
-6. Open the Go application that you want to debug in the OSS Code instance initiated in step 4.
+6. Open the Go application you want to debug in the OSS Code instance initiated in step 4.
 7. Create a debug configuration with the setting `"debugServer": 4711`.
 8. Start debugging your Go application. Observe that any breakpoints you set in the VS Code and debug adapter codebases will be triggered.
diff --git a/docs/debugging.md b/docs/debugging.md
index dc7ba45..4856e81 100644
--- a/docs/debugging.md
+++ b/docs/debugging.md
@@ -6,7 +6,8 @@
 The Go extension has been communicating with Delve through a custom debug adapter program (`legacy` mode).
 As the new [`Delve`'s native debug adapter implementation](https://github.com/go-delve/delve/tree/master/service/dap) has become available (since Delve v1.6.1), the Go extension is transitioning to deprecate the legacy debug adapter in favor of direct communication with Delve via [DAP](https://microsoft.github.io/debug-adapter-protocol/overview).
 
- 📣 **We are happy to announce that now this new mode of Delve integration (_`dlv-dap`_ mode) is enabled for _local_ _debugging_ by default and is available for [_remote_ _debugging_](#remote-debugging) on demand!**
+ 📣 **We are happy to announce that the new _`dlv-dap`_ mode of Delve integration is enabled for _local_ _debugging_ by default. For [_remote_ _debugging_](#remote-debugging) it is the default in [Go Nightly](docs/nightly.md) and is
+ available with stable builds on demand with `"debugAdapter": "dlv-dap"` attribute in `launch.json` or `settings.json`!**
 
 Many features and settings described in this document may be available only with the new `dlv-dap` mode.
 For troubleshooting and configuring the legacy debug adapter, see [the legacy debug adapter documentation](https://github.com/golang/vscode-go/tree/master/docs/debugging-legacy.md).
@@ -268,8 +269,8 @@
 | `cwd` | Workspace relative or absolute path to the working directory of the program being debugged if a non-empty value is specified. The `program` folder is used as the working directory if `cwd` is omitted or empty.<br/>(Default: `""`)<br/> | Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace.<br/>(Default: `"${workspaceFolder}"`)<br/> |
 | `debugAdapter` | Select which debug adapter to use with this launch configuration.<br/><p>Allowed Values: `"legacy"`, `"dlv-dap"`<br/>(Default: `dlv-dap`)<br/> | <center>_same as Launch_</center>|
 | `dlvFlags` | Extra flags for `dlv`. See `dlv help` for the full list of supported. Flags such as `--log-output`, `--log`, `--log-dest`, `--api-version`, `--output`, `--backend` already have corresponding properties in the debug configuration, and flags such as `--listen` and `--headless` are used internally. If they are specified in `dlvFlags`, they may be ignored or cause an error.<br/> | <center>_same as Launch_</center>|
-| `env` | Environment variables passed to the program.<br/> | <center>_n/a_</center> |
-| `envFile` | Absolute path to a file containing environment variable definitions. Multiple files can be specified by provided an array of absolute paths<br/>(Default: `${workspaceFolder}/.env`)<br/> | <center>_n/a_</center> |
+| `env` | Environment variables passed to the launched debuggee program. Format as string key:value pairs. Merged with `envFile` and `go.toolsEnvVars` with precedence `env` > `envFile` > `go.toolsEnvVars`.<br/> | <center>_n/a_</center> |
+| `envFile` | Absolute path to a file containing environment variable definitions, formatted as string key=value pairs. Multiple files can be specified by provided an array of absolute paths. Merged with `env` and `go.toolsEnvVars` with precedence `env` > `envFile` > `go.toolsEnvVars`. <br/> | <center>_n/a_</center> |
 | `hideSystemGoroutines` | Boolean value to indicate whether system goroutines should be hidden from call stack view.<br/>(Default: `false`)<br/> | <center>_same as Launch_</center>|
 | `host` | When applied to remote-attach configurations, will look for "dlv ... --headless --listen=<host>:<port>" server started externally. In dlv-dap mode this will apply to all other configurations as well. The extension will try to connect to an external server started with "dlv dap --listen=<host>:<port>" to ask it to launch/attach to the target process.<br/>(Default: `"127.0.0.1"`)<br/> | When applied to remote-attach configurations, will look for "dlv ... --headless --listen=<host>:<port>" server started externally. In dlv-dap mode, this will apply to all other configurations as well. The extension will try to connect to an external server started with "dlv dap --listen=<host>:<port>" to ask it to launch/attach to the target process.<br/>(Default: `"127.0.0.1"`)<br/> |
 | `logDest` | dlv's `--log-dest` flag. See `dlv log` for details. Number argument is not allowed. Supported only in `dlv-dap` mode, and on Linux and Mac OS.<br/> | dlv's `--log-dest` flag. See `dlv log` for details. Number argument is not allowed. Supported only in `dlv-dap` mode and on Linux and Mac OS.<br/> |
@@ -510,9 +511,9 @@
 
 The "debug test" CodeLens and the [test UI](https://github.com/golang/vscode-go/blob/master/docs/features.md#test-and-benchmark) do not use the `launch.json` configuration ([Issue 855](https://github.com/golang/vscode-go/issues/855)). As a workaround, use the `go.delveConfig` setting and the `go.testFlags` setting. Please note that these all apply to all debug sessions unless overwritten by a specific `launch.json` configuration.
 
-### Why can't I use local attach with a process started with `go run`?
+### Starting a debug session fails with `decoding dwarf section info at offset 0x0: too short` or `could not open debug info` error.
 
-Unlike `go build`, `go run` passes `-s -w` to the linker to strip the debug info. If you try attach to such a binary with a debugger, it will fail an error like `decoding dwarf section info at offset 0x0: too short`. Use `go build -gcflags='all=-N -l'` to build your binary instead. See Go Issue [24833](https://github.com/golang/go/issues/24833) for more information.
+These errors indicate that your binary was built with linker flags that stripped the symbol table (`-s`) or the DWARF debug information (`-w`), making debugging impossible. If the binary is built while launching the session, make sure your `launch.json` configuration does not contain `"buildFlags": "--ldflags '-s -w'"`. If you use `debug test` or Test Explorer, check `go.buildFlags` in `settings.json`. If the binary is built externally, check the command-line flags and do not use `go run`. Unlike `go build`, `go run` passes `-s -w` to the linker under the hood. If you try to attach to such a binary with a debugger, it will fail with one of the above errors (see Go Issue [24833](https://github.com/golang/go/issues/24833)). Instead let dlv build the binary for you or use `go build -gcflags='all=-N -l'`.
 
 ## Reporting Issues
 
diff --git a/docs/faq.md b/docs/faq.md
new file mode 100644
index 0000000..fb90dfb
--- /dev/null
+++ b/docs/faq.md
@@ -0,0 +1,28 @@
+
+The default syntax highlighting for Go files is provided by a
+[TextMate rule](https://github.com/jeff-hykin/better-go-syntax) embedded in VS Code,
+not by this extension.
+
+For better syntax highlighting (including generics support), we recommend enabling
+[semantic highlighting](https://code.visualstudio.com/api/language-extensions/semantic-highlight-guide)
+by turning on [Gopls' `ui.semanticTokens` setting](https://github.com/golang/vscode-go/blob/master/docs/settings.md#uisemantictokens).
+
+```json
+"gopls": { "ui.semanticTokens": true }
+```
+
+<!-- Topics
+  * The extension deletes my code on save?
+  * Help! The extension does not find 'go'.
+  * Intellisense is not working!
+  * Should I configure GOROOT/GOPATH?
+  * How can I work with multiple modules?
+  * How can I use my own formatter?
+  * How can I work with build tags?
+  * What is gopls?
+  * Does the extension work on WSL? How?
+  * Does the extension work on browser-based editors?
+  * Can I contribute to share my snippets?
+  * What is the extension's Go version support policy?
+  ...
+-->
\ No newline at end of file
diff --git a/docs/info.txt b/docs/info.txt
new file mode 100644
index 0000000..2b06747
--- /dev/null
+++ b/docs/info.txt
@@ -0,0 +1 @@
+This directory contains the source of the github.com/golang/vscode-go/wiki.
diff --git a/.github/release_plan.md b/docs/release_plan.md
similarity index 97%
rename from .github/release_plan.md
rename to docs/release_plan.md
index 0abb349..013712b 100644
--- a/.github/release_plan.md
+++ b/docs/release_plan.md
@@ -1,5 +1,7 @@
-# Release candidate (DATE)
+Use the following template to create an issue for release.
 
+```
+# Release candidate (DATE)
 -   [ ] Announce the release, leave enough time for teams to surface any last minute issues that need to get in before freeze. Make sure debugger and gopls teams are looped in as well.
 -   [ ] Create a milestone with the issues that are fixed by this release
 -	[ ] Update `master` for the release
@@ -36,3 +38,4 @@
     -   [ ] Bump the version number to the next monthly ("X.XX.X-dev") release in the `master` branch
         -   [ ] `package.json`
         -   [ ] `package-lock.json`
+```
\ No newline at end of file
diff --git a/docs/settings.md b/docs/settings.md
index 1a98e1e..bf0945c 100644
--- a/docs/settings.md
+++ b/docs/settings.md
@@ -48,7 +48,7 @@
 ```
 ### `go.alternateTools`
 
-Alternate tools or alternate paths for the same tools used by the Go extension. Provide either absolute path or the name of the binary in GOPATH/bin, GOROOT/bin or PATH. Useful when you want to use wrapper script for the Go tools or versioned tools from https://gopkg.in. When specified as a workspace setting, the setting is used only when the workspace is marked trusted with "Go: Toggle Workspace Trust Flag".
+Alternate tools or alternate paths for the same tools used by the Go extension. Provide either absolute path or the name of the binary in GOPATH/bin, GOROOT/bin or PATH. Useful when you want to use wrapper script for the Go tools.
 | Properties | Description |
 | --- | --- |
 | `dlv` | Alternate tool to use instead of the dlv binary or alternate path to use for the dlv binary. <br/> Default: `"dlv"` |
@@ -256,10 +256,10 @@
 Default: `"go"`
 ### `go.gopath`
 
-Specify GOPATH here to override the one that is set as environment variable. The inferred GOPATH from workspace root overrides this, if go.inferGopath is set to true. When specified as a workspace setting, the setting is used only when the workspace is marked trusted with "Go: Toggle Workspace Trust Flag".
+Specify GOPATH here to override the one that is set as environment variable. The inferred GOPATH from workspace root overrides this, if go.inferGopath is set to true.
 ### `go.goroot`
 
-Specifies the GOROOT to use when no environment variable is set. When specified as a workspace setting, the setting is used only when the workspace is marked trusted with "Go: Toggle Workspace Trust Flag".
+Specifies the GOROOT to use when no environment variable is set.
 ### `go.gotoSymbol.ignoreFolders`
 
 Folder names (not paths) to ignore while using Go to Symbol in Workspace feature. Not applicable when using the language server.
@@ -275,7 +275,7 @@
 Default: `false`
 ### `go.inferGopath`
 
-Infer GOPATH from the workspace root. This is ignored when using Go Modules. When specified as a workspace setting, the setting is used only when the workspace is marked trusted with "Go: Toggle Workspace Trust Flag".
+Infer GOPATH from the workspace root. This is ignored when using Go Modules.
 
 Default: `false`
 ### `go.installDependenciesWhenBuilding`
@@ -305,7 +305,13 @@
 ### `go.lintOnSave`
 
 Lints code on file save using the configured Lint tool. Options are 'file', 'package', 'workspace' or 'off'.<br/>
-Allowed Options: `file`, `package`, `workspace`, `off`
+Allowed Options:
+
+* `file`: lint the current file on file saving
+* `package`: lint the current package on file saving
+* `workspace`: lint all the packages in the current workspace root folder on file saving
+* `off`: do not run lint automatically
+
 
 Default: `"package"`
 ### `go.lintTool`
@@ -434,10 +440,10 @@
 Default: `"30s"`
 ### `go.toolsEnvVars`
 
-Environment variables that will be passed to the tools that run the Go tools (e.g. CGO_CFLAGS)
+Environment variables that will be passed to the tools that run the Go tools (e.g. CGO_CFLAGS) and debuggee process launched by Delve. Format as string key:value pairs. When debugging, merged with `envFile` and `env` values with precedence `env` > `envFile` > `go.toolsEnvVars`.
 ### `go.toolsGopath`
 
-Location to install the Go tools that the extension depends on if you don't want them in your GOPATH. When specified as a workspace setting, the setting is used only when the workspace is marked trusted with "Go: Toggle Workspace Trust Flag".
+Location to install the Go tools that the extension depends on if you don't want them in your GOPATH.
 ### `go.toolsManagement.autoUpdate`
 
 Automatically update the tools used by the extension, without prompting the user.
@@ -492,7 +498,12 @@
 ### `go.vetOnSave`
 
 Vets code on file save using 'go tool vet'. Not applicable when using the language server's diagnostics. See 'go.languageServerExperimentalFeatures.diagnostics' setting.<br/>
-Allowed Options: `package`, `workspace`, `off`
+Allowed Options:
+
+* `package`: vet the current package on file saving
+* `workspace`: vet all the packages in the current workspace root folder on file saving
+* `off`: do not run vet automatically
+
 
 Default: `"package"`
 ### `gopls`
@@ -636,7 +647,7 @@
 
 codelenses overrides the enabled/disabled state of code lenses. See the
 "Code Lenses" section of the
-[Settings page](https://github.com/golang/tools/blob/master/gopls/doc/settings.md)
+[Settings page](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#code-lenses)
 for the list of supported lenses.
 
 Example Usage:
@@ -673,7 +684,7 @@
 Default: `"100ms"`
 ### `ui.completion.experimentalPostfixCompletions`
 
-(Experimental) experimentalPostfixCompletions enables artifical method snippets
+(Experimental) experimentalPostfixCompletions enables artificial method snippets
 such as "someSlice.sort!".
 
 
diff --git a/docs/test-explorer.md b/docs/test-explorer.md
index a20bfdd..5afdb49 100644
--- a/docs/test-explorer.md
+++ b/docs/test-explorer.md
@@ -1,6 +1,7 @@
-# Test explorer implementation (src/goTest)
+# Test explorer
 
-## Mapping tests
+## Implementation (src/goTest)
+### Mapping tests
 
 `TestItem`s themselves cannot be used with `Map`s. For non-primitive (object)
 keys, Map uses strict equality. Two objects are only strictly equal to each
diff --git a/go.mod b/go.mod
index 8e50f37..c4861c0 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,10 @@
 go 1.16
 
 require (
+	github.com/google/go-cmp v0.5.7
 	github.com/stamblerre/work-stats v0.0.0-20211013195910-92098c96a21a
 	golang.org/x/build v0.0.0-20211222221018-ee978b38c739
+	golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f // indirect
+	golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
+	golang.org/x/text v0.3.7 // indirect
 )
diff --git a/go.sum b/go.sum
index bb7d945..241b301 100644
--- a/go.sum
+++ b/go.sum
@@ -302,8 +302,9 @@
 github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
+github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
 github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
 github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
 github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
@@ -636,8 +637,6 @@
 github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
 github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
 github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
-github.com/stamblerre/work-stats v0.0.0-20210726215650-a14fc877fae7 h1:yZLmB9oK2DoOIQ/62SSC4PVg0LtYTXvsak9cW+O2umw=
-github.com/stamblerre/work-stats v0.0.0-20210726215650-a14fc877fae7/go.mod h1:dM4zJ9OuZuchdonBFCaFef0ZAnZuuCgX4WDLlUm6+RM=
 github.com/stamblerre/work-stats v0.0.0-20211013195910-92098c96a21a h1:p2dmgMcxN88UcGsvwD0GjDAEfjFZGjDdvpuT+O3YV70=
 github.com/stamblerre/work-stats v0.0.0-20211013195910-92098c96a21a/go.mod h1:dM4zJ9OuZuchdonBFCaFef0ZAnZuuCgX4WDLlUm6+RM=
 github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
@@ -810,8 +809,9 @@
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985 h1:4CSI6oo7cOjJKajidEljs9h+uP0rRZBPPPhcCbj5mw8=
 golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
+golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20170207211851-4464e7848382/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -919,8 +919,9 @@
 golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -930,8 +931,9 @@
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff --git a/media/go-logo-white.svg b/media/go-logo-white.svg
new file mode 100644
index 0000000..727a62e
--- /dev/null
+++ b/media/go-logo-white.svg
@@ -0,0 +1 @@
+<svg height="78" viewBox="0 0 207 78" width="207" xmlns="http://www.w3.org/2000/svg"><g fill="#ffffff" fill-rule="evenodd"><path d="m16.2 24.1c-.4 0-.5-.2-.3-.5l2.1-2.7c.2-.3.7-.5 1.1-.5h35.7c.4 0 .5.3.3.6l-1.7 2.6c-.2.3-.7.6-1 .6z"/><path d="m1.1 33.3c-.4 0-.5-.2-.3-.5l2.1-2.7c.2-.3.7-.5 1.1-.5h45.6c.4 0 .6.3.5.6l-.8 2.4c-.1.4-.5.6-.9.6z"/><path d="m25.3 42.5c-.4 0-.5-.3-.3-.6l1.4-2.5c.2-.3.6-.6 1-.6h20c.4 0 .6.3.6.7l-.2 2.4c0 .4-.4.7-.7.7z"/><g transform="translate(55)"><path d="m74.1 22.3c-6.3 1.6-10.6 2.8-16.8 4.4-1.5.4-1.6.5-2.9-1-1.5-1.7-2.6-2.8-4.7-3.8-6.3-3.1-12.4-2.2-18.1 1.5-6.8 4.4-10.3 10.9-10.2 19 .1 8 5.6 14.6 13.5 15.7 6.8.9 12.5-1.5 17-6.6.9-1.1 1.7-2.3 2.7-3.7-3.6 0-8.1 0-19.3 0-2.1 0-2.6-1.3-1.9-3 1.3-3.1 3.7-8.3 5.1-10.9.3-.6 1-1.6 2.5-1.6h36.4c-.2 2.7-.2 5.4-.6 8.1-1.1 7.2-3.8 13.8-8.2 19.6-7.2 9.5-16.6 15.4-28.5 17-9.8 1.3-18.9-.6-26.9-6.6-7.4-5.6-11.6-13-12.7-22.2-1.3-10.9 1.9-20.7 8.5-29.3 7.1-9.3 16.5-15.2 28-17.3 9.4-1.7 18.4-.6 26.5 4.9 5.3 3.5 9.1 8.3 11.6 14.1.6.9.2 1.4-1 1.7z"/><path d="m107.2 77.6c-9.1-.2-17.4-2.8-24.4-8.8-5.9-5.1-9.6-11.6-10.8-19.3-1.8-11.3 1.3-21.3 8.1-30.2 7.3-9.6 16.1-14.6 28-16.7 10.2-1.8 19.8-.8 28.5 5.1 7.9 5.4 12.8 12.7 14.1 22.3 1.7 13.5-2.2 24.5-11.5 33.9-6.6 6.7-14.7 10.9-24 12.8-2.7.5-5.4.6-8 .9zm23.8-40.4c-.1-1.3-.1-2.3-.3-3.3-1.8-9.9-10.9-15.5-20.4-13.3-9.3 2.1-15.3 8-17.5 17.4-1.8 7.8 2 15.7 9.2 18.9 5.5 2.4 11 2.1 16.3-.6 7.9-4.1 12.2-10.5 12.7-19.1z" fill-rule="nonzero"/></g></g></svg>
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 49033a0..93f4da3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,19 +1,19 @@
 {
   "name": "go",
-  "version": "0.32.0",
+  "version": "0.33.0",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
       "name": "go",
-      "version": "0.32.0",
+      "version": "0.33.0",
       "license": "MIT",
       "dependencies": {
         "deep-equal": "2.0.5",
         "diff": "4.0.2",
         "glob": "7.1.7",
         "json-rpc2": "2.0.0",
-        "moment": "2.29.1",
+        "moment": "2.29.2",
         "semver": "7.3.4",
         "tree-kill": "file:third_party/tree-kill",
         "vscode-debugadapter": "1.45.0",
@@ -21,6 +21,7 @@
         "vscode-debugprotocol": "1.45.0",
         "vscode-languageclient": "7.0.0",
         "vscode-languageserver-protocol": "3.16.0",
+        "vscode-uri": "3.0.3",
         "web-request": "1.0.7"
       },
       "devDependencies": {
@@ -3494,9 +3495,9 @@
       }
     },
     "node_modules/minimist": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
-      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+      "version": "1.2.6",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+      "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
     },
     "node_modules/minimist-options": {
       "version": "4.1.0",
@@ -3656,9 +3657,9 @@
       }
     },
     "node_modules/moment": {
-      "version": "2.29.1",
-      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
-      "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==",
+      "version": "2.29.2",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
+      "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg==",
       "engines": {
         "node": "*"
       }
@@ -5526,6 +5527,11 @@
       "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz",
       "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA=="
     },
+    "node_modules/vscode-uri": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.3.tgz",
+      "integrity": "sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA=="
+    },
     "node_modules/web-request": {
       "version": "1.0.7",
       "resolved": "https://registry.npmjs.org/web-request/-/web-request-1.0.7.tgz",
@@ -8366,9 +8372,9 @@
       }
     },
     "minimist": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
-      "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+      "version": "1.2.6",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+      "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
     },
     "minimist-options": {
       "version": "4.1.0",
@@ -8486,9 +8492,9 @@
       }
     },
     "moment": {
-      "version": "2.29.1",
-      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
-      "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
+      "version": "2.29.2",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz",
+      "integrity": "sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg=="
     },
     "ms": {
       "version": "2.1.2",
@@ -9915,6 +9921,11 @@
       "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz",
       "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA=="
     },
+    "vscode-uri": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.3.tgz",
+      "integrity": "sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA=="
+    },
     "web-request": {
       "version": "1.0.7",
       "resolved": "https://registry.npmjs.org/web-request/-/web-request-1.0.7.tgz",
diff --git a/package.json b/package.json
index 30d1f88..59ffa98 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "go",
   "displayName": "Go",
-  "version": "0.32.0",
+  "version": "0.33.0-dev",
   "publisher": "golang",
   "description": "Rich Go language support for Visual Studio Code",
   "author": {
@@ -52,7 +52,7 @@
     "diff": "4.0.2",
     "glob": "7.1.7",
     "json-rpc2": "2.0.0",
-    "moment": "2.29.1",
+    "moment": "2.29.2",
     "semver": "7.3.4",
     "tree-kill": "file:third_party/tree-kill",
     "vscode-debugadapter": "1.45.0",
@@ -60,6 +60,7 @@
     "vscode-debugprotocol": "1.45.0",
     "vscode-languageclient": "7.0.0",
     "vscode-languageserver-protocol": "3.16.0",
+    "vscode-uri": "3.0.3",
     "web-request": "1.0.7"
   },
   "devDependencies": {
@@ -486,6 +487,40 @@
         "command": "go.global.resetState",
         "title": "Go: Reset Global State",
         "description": "Reset keys in global state to undefined."
+      },
+      {
+        "command": "go.explorer.refresh",
+        "title": "Go Explorer: Refresh",
+        "description": "Refresh the Go explorer. Only available as a menu item in the explorer.",
+        "category": "Explorer",
+        "icon": "$(refresh)"
+      },
+      {
+        "command": "go.explorer.open",
+        "title": "Go Explorer: Open File",
+        "description": "Open a file from the Go explorer. Only available as a menu item in the explorer.",
+        "category": "Explorer",
+        "icon": "$(go-to-file)"
+      },
+      {
+        "command": "go.workspace.editEnv",
+        "title": "Go: Edit Workspace Env",
+        "description": "Edit the Go Env for the active workspace.",
+        "icon": "$(settings-edit)",
+        "enablement": "workspaceFolderCount > 0"
+      },
+      {
+        "command": "go.workspace.resetEnv",
+        "title": "Go: Reset Workspace Env",
+        "description": "Reset the Go Env for the active workspace.",
+        "icon": "$(settings-remove)",
+        "enablement": "workspaceFolderCount > 0"
+      },
+      {
+        "command": "go.vulncheck.run",
+        "title": "Go: Run Vulncheck (Experimental)",
+        "description": "Run go vulncheck.",
+        "enablement": "go.goplsIsRunning"
       }
     ],
     "breakpoints": [
@@ -633,7 +668,7 @@
               },
               "env": {
                 "type": "object",
-                "description": "Environment variables passed to the program.",
+                "description": "Environment variables passed to the launched debuggee program. Format as string key:value pairs. Merged with `envFile` and `go.toolsEnvVars` with precedence `env` > `envFile` > `go.toolsEnvVars`.",
                 "default": {}
               },
               "substitutePath": {
@@ -700,8 +735,8 @@
                 "items": {
                   "type": "string"
                 },
-                "description": "Absolute path to a file containing environment variable definitions. Multiple files can be specified by provided an array of absolute paths",
-                "default": "${workspaceFolder}/.env"
+                "description": "Absolute path to a file containing environment variable definitions, formatted as string key=value pairs. Multiple files can be specified by provided an array of absolute paths. Merged with `env` and `go.toolsEnvVars` with precedence `env` > `envFile` > `go.toolsEnvVars`. ",
+                "default": ""
               },
               "backend": {
                 "type": "string",
@@ -1101,6 +1136,12 @@
             "workspace",
             "off"
           ],
+          "enumDescriptions": [
+            "lint the current file on file saving",
+            "lint the current package on file saving",
+            "lint all the packages in the current workspace root folder on file saving",
+            "do not run lint automatically"
+          ],
           "default": "package",
           "description": "Lints code on file save using the configured Lint tool. Options are 'file', 'package', 'workspace' or 'off'.",
           "scope": "resource"
@@ -1133,6 +1174,11 @@
             "workspace",
             "off"
           ],
+          "enumDescriptions": [
+            "vet the current package on file saving",
+            "vet all the packages in the current workspace root folder on file saving",
+            "do not run vet automatically"
+          ],
           "default": "package",
           "description": "Vets code on file save using 'go tool vet'. Not applicable when using the language server's diagnostics. See 'go.languageServerExperimentalFeatures.diagnostics' setting.",
           "scope": "resource"
@@ -1180,7 +1226,7 @@
         "go.inferGopath": {
           "type": "boolean",
           "default": false,
-          "description": "Infer GOPATH from the workspace root. This is ignored when using Go Modules. When specified as a workspace setting, the setting is used only when the workspace is marked trusted with \"Go: Toggle Workspace Trust Flag\".",
+          "description": "Infer GOPATH from the workspace root. This is ignored when using Go Modules.",
           "scope": "resource"
         },
         "go.gopath": {
@@ -1189,7 +1235,7 @@
             "null"
           ],
           "default": null,
-          "description": "Specify GOPATH here to override the one that is set as environment variable. The inferred GOPATH from workspace root overrides this, if go.inferGopath is set to true. When specified as a workspace setting, the setting is used only when the workspace is marked trusted with \"Go: Toggle Workspace Trust Flag\".",
+          "description": "Specify GOPATH here to override the one that is set as environment variable. The inferred GOPATH from workspace root overrides this, if go.inferGopath is set to true.",
           "scope": "machine-overridable"
         },
         "go.toolsGopath": {
@@ -1198,7 +1244,7 @@
             "null"
           ],
           "default": null,
-          "description": "Location to install the Go tools that the extension depends on if you don't want them in your GOPATH. When specified as a workspace setting, the setting is used only when the workspace is marked trusted with \"Go: Toggle Workspace Trust Flag\".",
+          "description": "Location to install the Go tools that the extension depends on if you don't want them in your GOPATH.",
           "scope": "machine-overridable"
         },
         "go.goroot": {
@@ -1207,7 +1253,7 @@
             "null"
           ],
           "default": null,
-          "description": "Specifies the GOROOT to use when no environment variable is set. When specified as a workspace setting, the setting is used only when the workspace is marked trusted with \"Go: Toggle Workspace Trust Flag\".",
+          "description": "Specifies the GOROOT to use when no environment variable is set.",
           "scope": "machine-overridable"
         },
         "go.testOnSave": {
@@ -1424,7 +1470,7 @@
         "go.toolsEnvVars": {
           "type": "object",
           "default": {},
-          "description": "Environment variables that will be passed to the tools that run the Go tools (e.g. CGO_CFLAGS)",
+          "description": "Environment variables that will be passed to the tools that run the Go tools (e.g. CGO_CFLAGS) and debuggee process launched by Delve. Format as string key:value pairs. When debugging, merged with `envFile` and `env` values with precedence `env` > `envFile` > `go.toolsEnvVars`.",
           "scope": "resource"
         },
         "go.gocodeFlags": {
@@ -1966,7 +2012,7 @@
         "go.alternateTools": {
           "type": "object",
           "default": {},
-          "description": "Alternate tools or alternate paths for the same tools used by the Go extension. Provide either absolute path or the name of the binary in GOPATH/bin, GOROOT/bin or PATH. Useful when you want to use wrapper script for the Go tools or versioned tools from https://gopkg.in. When specified as a workspace setting, the setting is used only when the workspace is marked trusted with \"Go: Toggle Workspace Trust Flag\".",
+          "description": "Alternate tools or alternate paths for the same tools used by the Go extension. Provide either absolute path or the name of the binary in GOPATH/bin, GOROOT/bin or PATH. Useful when you want to use wrapper script for the Go tools.",
           "scope": "resource",
           "properties": {
             "go": {
@@ -2092,7 +2138,7 @@
             },
             "ui.codelenses": {
               "type": "object",
-              "markdownDescription": "codelenses overrides the enabled/disabled state of code lenses. See the\n\"Code Lenses\" section of the\n[Settings page](https://github.com/golang/tools/blob/master/gopls/doc/settings.md)\nfor the list of supported lenses.\n\nExample Usage:\n\n```json5\n\"gopls\": {\n...\n  \"codelenses\": {\n    \"generate\": false,  // Don't show the `go generate` lens.\n    \"gc_details\": true  // Show a code lens toggling the display of gc's choices.\n  }\n...\n}\n```\n",
+              "markdownDescription": "codelenses overrides the enabled/disabled state of code lenses. See the\n\"Code Lenses\" section of the\n[Settings page](https://github.com/golang/tools/blob/master/gopls/doc/settings.md#code-lenses)\nfor the list of supported lenses.\n\nExample Usage:\n\n```json5\n\"gopls\": {\n...\n  \"codelenses\": {\n    \"generate\": false,  // Don't show the `go generate` lens.\n    \"gc_details\": true  // Show a code lens toggling the display of gc's choices.\n  }\n...\n}\n```\n",
               "scope": "resource",
               "properties": {
                 "gc_details": {
@@ -2140,7 +2186,7 @@
             },
             "ui.completion.experimentalPostfixCompletions": {
               "type": "boolean",
-              "markdownDescription": "(Experimental) experimentalPostfixCompletions enables artifical method snippets\nsuch as \"someSlice.sort!\".\n",
+              "markdownDescription": "(Experimental) experimentalPostfixCompletions enables artificial method snippets\nsuch as \"someSlice.sort!\".\n",
               "default": true,
               "scope": "resource"
             },
@@ -2553,6 +2599,14 @@
         {
           "command": "go.test.deleteProfile",
           "when": "false"
+        },
+        {
+          "command": "go.explorer.refresh",
+          "when": "false"
+        },
+        {
+          "command": "go.explorer.open",
+          "when": "false"
         }
       ],
       "editor/context": [
@@ -2654,14 +2708,43 @@
           "group": "profile"
         }
       ],
+      "view/title": [
+        {
+          "command": "go.explorer.refresh",
+          "when": "view == go.explorer",
+          "group": "navigation"
+        }
+      ],
       "view/item/context": [
         {
           "command": "go.test.deleteProfile",
           "when": "viewItem == go:test:file"
+        },
+        {
+          "command": "go.explorer.open",
+          "when": "viewItem == go:explorer:envfile",
+          "group": "inline"
+        },
+        {
+          "command": "go.workspace.editEnv",
+          "when": "viewItem =~ /(go:explorer:envtree|go:explorer:envitem)/ && workspaceFolderCount > 0",
+          "group": "inline"
+        },
+        {
+          "command": "go.workspace.resetEnv",
+          "when": "viewItem =~ /go:explorer:env/ && workspaceFolderCount > 0"
         }
       ]
     },
     "views": {
+      "explorer": [
+        {
+          "id": "go.explorer",
+          "name": "go",
+          "icon": "media/go-logo-white.svg",
+          "when": "go.showExplorer"
+        }
+      ],
       "test": [
         {
           "id": "go.test.profile",
diff --git a/src/goCheck.ts b/src/goCheck.ts
index 021b52d..82bf336 100644
--- a/src/goCheck.ts
+++ b/src/goCheck.ts
@@ -11,7 +11,7 @@
 import vscode = require('vscode');
 import { getGoplsConfig } from './config';
 import { goBuild } from './goBuild';
-import { buildLanguageServerConfig } from './goLanguageServer';
+import { buildLanguageServerConfig } from './language/goLanguageServer';
 import { goLint } from './goLint';
 import { buildDiagnosticCollection, lintDiagnosticCollection, vetDiagnosticCollection } from './goMain';
 import { isModSupported } from './goModules';
diff --git a/src/goDebugConfiguration.ts b/src/goDebugConfiguration.ts
index 77b5425..97fafeb 100644
--- a/src/goDebugConfiguration.ts
+++ b/src/goDebugConfiguration.ts
@@ -20,6 +20,7 @@
 	promptForUpdatingTool,
 	shouldUpdateTool
 } from './goInstallTools';
+import { extensionInfo } from './config';
 import { packagePathToGoModPathMap } from './goModules';
 import { getToolAtVersion } from './goTools';
 import { pickProcess, pickProcessByName } from './pickProcess';
@@ -159,6 +160,7 @@
 
 		// Figure out which debugAdapter is being used first, so we can use this to send warnings
 		// for properties that don't apply.
+		// If debugAdapter is not provided in launch.json, see if it's in settings.json.
 		if (!debugConfiguration.hasOwnProperty('debugAdapter') && dlvConfig.hasOwnProperty('debugAdapter')) {
 			const { globalValue, workspaceValue } = goConfig.inspect('delveConfig.debugAdapter');
 			// user configured the default debug adapter through settings.json.
@@ -166,19 +168,19 @@
 				debugConfiguration['debugAdapter'] = dlvConfig['debugAdapter'];
 			}
 		}
+		// If neither launch.json nor settings.json gave us the debugAdapter value, we go with the default
+		// from package.json (dlv-dap) unless this is remote attach with a stable release.
 		if (!debugConfiguration['debugAdapter']) {
-			// For local modes, default to dlv-dap. For remote - to legacy for now.
-			debugConfiguration['debugAdapter'] = debugConfiguration['mode'] !== 'remote' ? 'dlv-dap' : 'legacy';
+			debugConfiguration['debugAdapter'] = defaultConfig.debugAdapter.default;
+			if (debugConfiguration['mode'] === 'remote' && !extensionInfo.isPreview) {
+				debugConfiguration['debugAdapter'] = 'legacy';
+			}
 		}
 		if (debugConfiguration['debugAdapter'] === 'dlv-dap') {
 			if (debugConfiguration['mode'] === 'remote') {
-				// This is only possible if a user explicitely requests this combination. Let them, with a warning.
-				// They need to use dlv at version 'v1.7.3-0.20211026171155-b48ceec161d5' or later,
-				// but we have no way of detectng that with an external server.
-				this.showWarning(
-					'ignoreDlvDAPInRemoteModeWarning',
-					"Using new 'remote' mode with 'dlv-dap' to connect to an external `dlv --headless` server via DAP."
-				);
+				// This needs to use dlv at version 'v1.7.3-0.20211026171155-b48ceec161d5' or later,
+				// but we have no way of detectng that with an external server ahead of time.
+				// If an earlier version is used, the attach will fail with  warning about versions.
 			} else if (debugConfiguration['port']) {
 				this.showWarning(
 					'ignorePortUsedInDlvDapWarning',
diff --git a/src/goDeveloperSurvey.ts b/src/goDeveloperSurvey.ts
index d3cd5a5..a18e9c1 100644
--- a/src/goDeveloperSurvey.ts
+++ b/src/goDeveloperSurvey.ts
@@ -8,7 +8,7 @@
 
 import vscode = require('vscode');
 import { getGoConfig } from './config';
-import { lastUserAction } from './goLanguageServer';
+import { lastUserAction } from './language/goLanguageServer';
 import { daysBetween, flushSurveyConfig, getStateConfig, minutesBetween, timeMinute } from './goSurvey';
 
 // Start and end dates of the survey.
diff --git a/src/goExplorer.ts b/src/goExplorer.ts
new file mode 100644
index 0000000..b8d503f
--- /dev/null
+++ b/src/goExplorer.ts
@@ -0,0 +1,364 @@
+/*---------------------------------------------------------
+ * Copyright 2022 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+import vscode = require('vscode');
+import vscodeUri = require('vscode-uri');
+import os = require('os');
+import path = require('path');
+import { getGoConfig, getGoplsConfig } from './config';
+import { getBinPath, getGoVersion } from './util';
+import { getConfiguredTools } from './goTools';
+import { inspectGoToolVersion } from './goInstallTools';
+import { runGoEnv } from './goModules';
+
+/**
+ * GoExplorerProvider provides data for the Go tree view in the Explorer
+ * Tree View Container.
+ */
+export class GoExplorerProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
+	private goEnvCache = new Cache((uri) => GoEnv.get(uri ? vscode.Uri.parse(uri) : undefined), Time.MINUTE);
+	private toolDetailCache = new Cache((name) => getToolDetail(name), Time.HOUR);
+	private activeFolder?: vscode.WorkspaceFolder;
+	private activeDocument?: vscode.TextDocument;
+
+	static setup({ subscriptions }: vscode.ExtensionContext) {
+		const provider = new this();
+		const {
+			window: { registerTreeDataProvider },
+			commands: { registerCommand, executeCommand }
+		} = vscode;
+		subscriptions.push(registerTreeDataProvider('go.explorer', provider));
+		subscriptions.push(registerCommand('go.explorer.refresh', () => provider.update(true)));
+		subscriptions.push(registerCommand('go.explorer.open', (item) => provider.open(item)));
+		subscriptions.push(registerCommand('go.workspace.editEnv', (item) => provider.editEnv(item)));
+		subscriptions.push(registerCommand('go.workspace.resetEnv', (item) => provider.resetEnv(item)));
+		executeCommand('setContext', 'go.showExplorer', true);
+		return provider;
+	}
+
+	private _onDidChangeTreeData = new vscode.EventEmitter<vscode.TreeItem | void>();
+	readonly onDidChangeTreeData = this._onDidChangeTreeData.event;
+
+	constructor() {
+		this.update();
+		vscode.window.onDidChangeActiveTextEditor(() => this.update());
+		vscode.workspace.onDidChangeWorkspaceFolders(() => this.update());
+		vscode.workspace.onDidChangeConfiguration(() => this.update(true));
+		vscode.workspace.onDidCloseTextDocument((doc) => {
+			if (!this.activeFolder) {
+				this.goEnvCache.delete(vscodeUri.Utils.dirname(doc.uri).toString());
+			}
+		});
+	}
+
+	getTreeItem(element: vscode.TreeItem) {
+		return element;
+	}
+
+	getChildren(element?: vscode.TreeItem) {
+		if (!element) {
+			return [this.envTree(), this.toolTree()];
+		}
+		if (isEnvTree(element)) {
+			return this.envTreeItems(element.workspace);
+		}
+		if (isToolTree(element)) {
+			return this.toolTreeItems();
+		}
+		if (isToolTreeItem(element)) {
+			return element.children;
+		}
+	}
+
+	private update(clearCache = false) {
+		if (clearCache) {
+			this.goEnvCache.clear();
+			this.toolDetailCache.clear();
+		}
+		const { activeTextEditor } = vscode.window;
+		const { getWorkspaceFolder, workspaceFolders } = vscode.workspace;
+		this.activeDocument = activeTextEditor?.document;
+		this.activeFolder = activeTextEditor?.document
+			? getWorkspaceFolder(activeTextEditor.document.uri) || workspaceFolders?.[0]
+			: workspaceFolders?.[0];
+		this._onDidChangeTreeData.fire();
+	}
+
+	private async open(item: EnvTreeItem) {
+		if (typeof item.file === 'undefined') return;
+		const edit = new vscode.WorkspaceEdit();
+		edit.createFile(item.file, { ignoreIfExists: true });
+		await vscode.workspace.applyEdit(edit);
+		const doc = await vscode.workspace.openTextDocument(item.file);
+		await vscode.window.showTextDocument(doc);
+	}
+
+	private async editEnv(item?: EnvTreeItem) {
+		const uri = this.activeFolder?.uri;
+		if (!uri) {
+			return;
+		}
+		let pick: { label?: string; description?: string };
+		if (isEnvTreeItem(item)) {
+			pick = { label: item.key, description: item.value };
+		} else {
+			const items = Object.entries<string>(await runGoEnv(uri))
+				.filter(([label]) => !GoEnv.readonlyVars.has(label))
+				.map(([label, description]) => ({
+					label,
+					description
+				}));
+			pick = await vscode.window.showQuickPick(items, { title: 'Go: Edit Workspace Env' });
+		}
+		if (!pick) return;
+		const { label, description } = pick;
+		const value = await vscode.window.showInputBox({ title: label, value: description });
+		if (typeof value !== 'undefined') {
+			await GoEnv.edit({ [label]: value });
+		}
+	}
+
+	private async resetEnv(item?: EnvTreeItem) {
+		if (item?.key) {
+			await GoEnv.reset([item.key]);
+			return;
+		}
+		await GoEnv.reset();
+	}
+
+	private envTree() {
+		if (this.activeFolder) {
+			const { name, uri } = this.activeFolder;
+			return new EnvTree(name, uri);
+		}
+		if (this.activeDocument) {
+			const { fileName, uri } = this.activeDocument;
+			return new EnvTree(path.basename(fileName), vscodeUri.Utils.dirname(uri));
+		}
+		return new EnvTree();
+	}
+
+	private async envTreeItems(uri?: vscode.Uri) {
+		const env = await this.goEnvCache.get(uri?.toString());
+		const items = [];
+		for (const [k, v] of Object.entries(env)) {
+			if (v !== '') {
+				items.push(new EnvTreeItem(k, v));
+			}
+		}
+		return items;
+	}
+
+	private toolTree() {
+		return new ToolTree();
+	}
+
+	private async toolTreeItems() {
+		const goVersion = await getGoVersion();
+		const allTools = getConfiguredTools(goVersion, getGoConfig(), getGoplsConfig());
+		const toolsInfo = await Promise.all(allTools.map((tool) => this.toolDetailCache.get(tool.name)));
+		const items = [];
+		for (const t of toolsInfo) {
+			items.push(new ToolTreeItem(t));
+		}
+		return items;
+	}
+}
+
+class EnvTree implements vscode.TreeItem {
+	label = 'env';
+	contextValue = 'go:explorer:envtree';
+	collapsibleState = vscode.TreeItemCollapsibleState.Expanded;
+	iconPath = new vscode.ThemeIcon('symbol-folder');
+	constructor(public description = '', public workspace?: vscode.Uri) {}
+}
+
+function isEnvTree(item?: vscode.TreeItem): item is EnvTree {
+	return item?.contextValue === 'go:explorer:envtree';
+}
+
+class EnvTreeItem implements vscode.TreeItem {
+	file?: vscode.Uri;
+	label: string;
+	contextValue?: string;
+	tooltip?: string;
+	constructor(public key: string, public value: string) {
+		this.label = `${key}=${replaceHome(value)}`;
+		this.contextValue = 'go:explorer:envitem';
+		if (GoEnv.fileVars.has(key)) {
+			this.contextValue = 'go:explorer:envfile';
+			this.file = vscode.Uri.file(value);
+		}
+		this.tooltip = `${key}=${value}`;
+	}
+}
+
+function isEnvTreeItem(item?: vscode.TreeItem): item is EnvTreeItem {
+	return item?.contextValue === 'go:explorer:envitem';
+}
+
+class GoEnv {
+	/**
+	 * get returns a subset of go env vars, the union of this.vars and values
+	 * set with toolsEnvVars in the go workspace config.
+	 * @param uri the directory from which to run go env.
+	 * @returns the output of running go env -json VAR1 VAR2...
+	 */
+	static async get(uri?: vscode.Uri) {
+		const toolsEnv = await getGoConfig(uri)['toolsEnvVars'];
+		const output = await runGoEnv(uri, [...this.vars, ...Object.keys(toolsEnv)]);
+		return output as Record<string, string>;
+	}
+
+	/**
+	 * update writes to toolsEnvVars in the go workspace config.
+	 * @param vars a record of env vars to update.
+	 */
+	static async edit(vars: Record<string, string>) {
+		const config = getGoConfig();
+		await config.update('toolsEnvVars', { ...config['toolsEnvVars'], ...vars });
+	}
+
+	/**
+	 * reset removes entries from toolsEnvVars in the go workspace config.
+	 * @param vars env vars to reset.
+	 */
+	static async reset(vars?: string[]) {
+		const config = getGoConfig();
+		let env: Record<string, string> = {};
+		if (vars) {
+			env = { ...config['toolsEnvVars'] };
+			for (const v of vars) {
+				delete env[v];
+			}
+		}
+		await config.update('toolsEnvVars', env);
+	}
+
+	/** Vars that point to files. */
+	static fileVars = new Set(['GOMOD', 'GOWORK', 'GOENV']);
+
+	/** Vars available from 'go env' but not read from the environment */
+	static readonlyVars = new Set([
+		'GOEXE',
+		'GOGCCFLAGS',
+		'GOHOSTARCH',
+		'GOHOSTOS',
+		'GOMOD',
+		'GOTOOLDIR',
+		'GOVERSION',
+		'GOWORK'
+	]);
+
+	/** Vars that should always be visible if they contain a value. */
+	private static vars = ['GOPRIVATE', 'GOMOD', 'GOWORK', 'GOENV'];
+}
+
+class ToolTree implements vscode.TreeItem {
+	label = 'tools';
+	contextValue = 'go:explorer:tools';
+	collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
+	iconPath = new vscode.ThemeIcon('tools');
+}
+
+function isToolTree(item?: vscode.TreeItem): item is ToolTree {
+	return item?.contextValue === 'go:explorer:tools';
+}
+
+class ToolTreeItem implements vscode.TreeItem {
+	contextValue = 'go:explorer:toolitem';
+	description = 'not installed';
+	label: string;
+	children: vscode.TreeItem[];
+	collapsibleState?: vscode.TreeItemCollapsibleState;
+	tooltip: string;
+	constructor({ name, version, goVersion, binPath, error }: ToolDetail) {
+		this.label = name;
+		if (binPath) {
+			this.label = `${name}@${version}`;
+			this.description = `${replaceHome(binPath)} ${goVersion}`;
+			this.tooltip = `${this.label} ${this.description}`;
+		}
+		if (error) {
+			const msg = `go version -m failed: ${error}`;
+			this.description = msg;
+			this.tooltip = msg;
+		}
+	}
+}
+
+function isToolTreeItem(item?: vscode.TreeItem): item is ToolTreeItem {
+	return item?.contextValue === 'go:explorer:toolitem';
+}
+
+interface ToolDetail {
+	name: string;
+	goVersion?: string;
+	version?: string;
+	binPath?: string;
+	error?: Error;
+}
+
+async function getToolDetail(name: string): Promise<ToolDetail> {
+	const toolPath = getBinPath(name);
+	if (!path.isAbsolute(toolPath)) {
+		return { name: name };
+	}
+	try {
+		const { goVersion, moduleVersion } = await inspectGoToolVersion(toolPath);
+		return {
+			name: name,
+			binPath: toolPath,
+			goVersion: goVersion,
+			version: moduleVersion
+		};
+	} catch (e) {
+		return { name: name, error: e };
+	}
+}
+
+const enum Time {
+	SECOND = 1000,
+	MINUTE = SECOND * 60,
+	HOUR = MINUTE * 60
+}
+
+interface CacheEntry<T> {
+	entry: T;
+	updatedAt: number;
+}
+
+class Cache<T> {
+	private cache = new Map<string, CacheEntry<T>>();
+
+	constructor(private fn: (key: string) => Promise<T>, private ttl: number) {}
+
+	async get(key: string, ttl = this.ttl) {
+		const cache = this.cache.get(key);
+		const useCache = cache && Date.now() - cache.updatedAt < ttl;
+		if (useCache) {
+			return cache.entry;
+		}
+		const entry = await this.fn(key);
+		this.cache.set(key, { entry, updatedAt: Date.now() });
+		return entry;
+	}
+
+	clear() {
+		return this.cache.clear();
+	}
+
+	delete(key: string) {
+		return this.cache.delete(key);
+	}
+}
+
+/**
+ * replaceHome replaces the home directory prefix of a string with `~`.
+ * @param maybePath a string that might be a file system path.
+ * @returns the string with os.homedir() replaced by `~`.
+ */
+function replaceHome(maybePath: string) {
+	return maybePath.replace(new RegExp(`^${os.homedir()}`), '~');
+}
diff --git a/src/goGenerateTests.ts b/src/goGenerateTests.ts
index cf5c053..b5e02f7 100644
--- a/src/goGenerateTests.ts
+++ b/src/goGenerateTests.ts
@@ -14,7 +14,7 @@
 import { getGoConfig } from './config';
 import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool } from './goInstallTools';
-import { GoDocumentSymbolProvider } from './goOutline';
+import { GoDocumentSymbolProvider } from './language/legacy/goOutline';
 import { outputChannel } from './goStatus';
 import { getBinPath } from './util';
 
diff --git a/src/goImport.ts b/src/goImport.ts
index fa595a9..4a7a062 100644
--- a/src/goImport.ts
+++ b/src/goImport.ts
@@ -12,8 +12,8 @@
 import { ExecuteCommandRequest, ExecuteCommandParams } from 'vscode-languageserver-protocol';
 import { toolExecutionEnvironment } from './goEnv';
 import { promptForMissingTool } from './goInstallTools';
-import { languageClient, serverInfo } from './goLanguageServer';
-import { documentSymbols, GoOutlineImportsOptions } from './goOutline';
+import { languageClient, serverInfo } from './language/goLanguageServer';
+import { documentSymbols, GoOutlineImportsOptions } from './language/legacy/goOutline';
 import { getImportablePackages } from './goPackages';
 import { getBinPath, getImportPath, parseFilePrelude } from './util';
 import { envPath, getCurrentGoRoot } from './utils/pathUtils';
diff --git a/src/goMain.ts b/src/goMain.ts
index 5aa4557..a8b44fb 100644
--- a/src/goMain.ts
+++ b/src/goMain.ts
@@ -51,7 +51,7 @@
 	showServerOutputChannel,
 	startLanguageServerWithFallback,
 	watchLanguageServerConfiguration
-} from './goLanguageServer';
+} from './language/goLanguageServer';
 import { lintCode } from './goLint';
 import { logVerbose, setLogConfig } from './goLogging';
 import { GO_MODE } from './goMode';
@@ -103,12 +103,14 @@
 import { WelcomePanel } from './welcome';
 import semver = require('semver');
 import vscode = require('vscode');
-import { getFormatTool } from './goFormat';
+import { getFormatTool } from './language/legacy/goFormat';
 import { resetSurveyConfigs, showSurveyConfig, timeMinute } from './goSurvey';
 import { ExtensionAPI } from './export';
 import extensionAPI from './extensionAPI';
 import { GoTestExplorer, isVscodeTestingAPIAvailable } from './goTest/explore';
 import { killRunningPprof } from './goTest/profile';
+import { GoExplorerProvider } from './goExplorer';
+import { VulncheckProvider } from './goVulncheck';
 
 export let buildDiagnosticCollection: vscode.DiagnosticCollection;
 export let lintDiagnosticCollection: vscode.DiagnosticCollection;
@@ -147,6 +149,11 @@
 	if (!extensionInfo.isInCloudIDE) {
 		showGoWelcomePage(ctx);
 	}
+	ctx.subscriptions.push(
+		vscode.commands.registerCommand('go.welcome', () => {
+			WelcomePanel.createOrShow(ctx.extensionUri);
+		})
+	);
 
 	const configGOROOT = getGoConfig()['goroot'];
 	if (configGOROOT) {
@@ -182,24 +189,33 @@
 		}
 	}
 
-	updateGoVarsFromConfig().then(async () => {
-		suggestUpdates(ctx);
-		offerToInstallLatestGoVersion();
-		offerToInstallTools();
-		await configureLanguageServer(ctx);
+	return activateContinued(ctx, cfg);
+}
 
-		if (
-			!languageServerIsRunning &&
-			vscode.window.activeTextEditor &&
-			vscode.window.activeTextEditor.document.languageId === 'go' &&
-			isGoPathSet()
-		) {
-			// Check mod status so that cache is updated and then run build/lint/vet
-			isModSupported(vscode.window.activeTextEditor.document.uri).then(() => {
-				runBuilds(vscode.window.activeTextEditor.document, getGoConfig());
-			});
-		}
-	});
+async function activateContinued(
+	ctx: vscode.ExtensionContext,
+	cfg: vscode.WorkspaceConfiguration
+): Promise<ExtensionAPI> {
+	await updateGoVarsFromConfig();
+
+	suggestUpdates(ctx);
+	offerToInstallLatestGoVersion();
+	offerToInstallTools();
+
+	// TODO: let configureLanguageServer to return its status.
+	await configureLanguageServer(ctx);
+
+	if (
+		!languageServerIsRunning &&
+		vscode.window.activeTextEditor &&
+		vscode.window.activeTextEditor.document.languageId === 'go' &&
+		isGoPathSet()
+	) {
+		// Check mod status so that cache is updated and then run build/lint/vet
+		isModSupported(vscode.window.activeTextEditor.document.uri).then(() => {
+			runBuilds(vscode.window.activeTextEditor.document, getGoConfig());
+		});
+	}
 
 	initCoverageDecorators(ctx);
 
@@ -327,6 +343,9 @@
 		GoTestExplorer.setup(ctx);
 	}
 
+	GoExplorerProvider.setup(ctx);
+	VulncheckProvider.setup(ctx);
+
 	ctx.subscriptions.push(
 		vscode.commands.registerCommand('go.subtest.cursor', (args) => {
 			const goConfig = getGoConfig();
@@ -618,12 +637,6 @@
 	);
 
 	ctx.subscriptions.push(
-		vscode.commands.registerCommand('go.welcome', () => {
-			WelcomePanel.createOrShow(ctx.extensionUri);
-		})
-	);
-
-	ctx.subscriptions.push(
 		vscode.commands.registerCommand('go.workspace.resetState', () => {
 			resetWorkspaceState();
 		})
diff --git a/src/goModules.ts b/src/goModules.ts
index 5e02282..dcb770e 100644
--- a/src/goModules.ts
+++ b/src/goModules.ts
@@ -8,9 +8,10 @@
 import path = require('path');
 import util = require('util');
 import vscode = require('vscode');
+import vscodeUri = require('vscode-uri');
 import { getGoConfig } from './config';
 import { toolExecutionEnvironment } from './goEnv';
-import { getFormatTool } from './goFormat';
+import { getFormatTool } from './language/legacy/goFormat';
 import { installTools } from './goInstallTools';
 import { outputChannel } from './goStatus';
 import { getTool } from './goTools';
@@ -19,7 +20,7 @@
 import { envPath, fixDriveCasingInWindows, getCurrentGoRoot } from './utils/pathUtils';
 export let GO111MODULE: string;
 
-export async function runGoEnv(folderPath: string, envvars: string[]): Promise<any> {
+export async function runGoEnv(uri?: vscode.Uri, envvars: string[] = []): Promise<any> {
 	const goExecutable = getBinPath('go');
 	if (!goExecutable) {
 		console.warn(
@@ -27,18 +28,19 @@
 		);
 		return {};
 	}
-	const env = toolExecutionEnvironment();
+	const env = toolExecutionEnvironment(uri);
 	GO111MODULE = env['GO111MODULE'];
-	return new Promise((resolve) => {
-		const args = ['env', '-json'].concat(envvars);
-		cp.execFile(goExecutable, args, { cwd: folderPath, env }, (err, stdout) => {
-			if (err) {
-				console.warn(`Error when running go env ${args}: ${err}`);
-				return resolve({});
-			}
-			resolve(JSON.parse(stdout));
-		});
-	});
+	const args = ['env', '-json'].concat(envvars);
+	try {
+		const { stdout, stderr } = await util.promisify(cp.execFile)(goExecutable, args, { cwd: uri?.fsPath, env });
+		if (stderr) {
+			throw new Error(stderr);
+		}
+		return JSON.parse(stdout);
+	} catch (e) {
+		vscode.window.showErrorMessage(`Failed to run "go env ${args}": ${e.message}`);
+		return {};
+	}
 }
 
 export function isModSupported(fileuri: vscode.Uri, isDir?: boolean): Promise<boolean> {
@@ -48,7 +50,8 @@
 export const packagePathToGoModPathMap: { [key: string]: string } = {};
 
 export async function getModFolderPath(fileuri: vscode.Uri, isDir?: boolean): Promise<string> {
-	const pkgPath = isDir ? fileuri.fsPath : path.dirname(fileuri.fsPath);
+	const pkgUri = isDir ? fileuri : vscodeUri.Utils.dirname(fileuri);
+	const pkgPath = pkgUri.fsPath;
 	if (packagePathToGoModPathMap[pkgPath]) {
 		return packagePathToGoModPathMap[pkgPath];
 	}
@@ -64,7 +67,7 @@
 		return;
 	}
 
-	const goModEnvJSON = await runGoEnv(pkgPath, ['GOMOD']);
+	const goModEnvJSON = await runGoEnv(pkgUri, ['GOMOD']);
 	let goModEnvResult =
 		goModEnvJSON['GOMOD'] === '/dev/null' || goModEnvJSON['GOMOD'] === 'NUL' ? '' : goModEnvJSON['GOMOD'];
 	if (goModEnvResult) {
diff --git a/src/goReferencesCodelens.ts b/src/goReferencesCodelens.ts
index 3b76229..b78a3a2 100644
--- a/src/goReferencesCodelens.ts
+++ b/src/goReferencesCodelens.ts
@@ -10,8 +10,8 @@
 import { CancellationToken, CodeLens, Range, TextDocument } from 'vscode';
 import { getGoConfig } from './config';
 import { GoBaseCodeLensProvider } from './goBaseCodelens';
-import { GoDocumentSymbolProvider } from './goOutline';
-import { GoReferenceProvider } from './goReferences';
+import { GoDocumentSymbolProvider } from './language/legacy/goOutline';
+import { GoReferenceProvider } from './language/legacy/goReferences';
 import { getBinPath } from './util';
 import vscode = require('vscode');
 
diff --git a/src/goRunTestCodelens.ts b/src/goRunTestCodelens.ts
index 4902ec8..886fccf 100644
--- a/src/goRunTestCodelens.ts
+++ b/src/goRunTestCodelens.ts
@@ -11,7 +11,7 @@
 import { CancellationToken, CodeLens, TextDocument } from 'vscode';
 import { getGoConfig } from './config';
 import { GoBaseCodeLensProvider } from './goBaseCodelens';
-import { GoDocumentSymbolProvider } from './goOutline';
+import { GoDocumentSymbolProvider } from './language/legacy/goOutline';
 import { getBenchmarkFunctions, getTestFunctions } from './testUtils';
 
 export class GoRunTestCodeLensProvider extends GoBaseCodeLensProvider {
diff --git a/src/goStatus.ts b/src/goStatus.ts
index 078b21d..6925623 100644
--- a/src/goStatus.ts
+++ b/src/goStatus.ts
@@ -7,8 +7,8 @@
 
 'use strict';
 
-import path = require('path');
 import vscode = require('vscode');
+import vscodeUri = require('vscode-uri');
 import { getGoConfig } from './config';
 import { formatGoVersion, GoEnvironmentOption, terminalCreationListener } from './goEnvironmentStatus';
 import {
@@ -16,7 +16,7 @@
 	getLocalGoplsVersion,
 	languageServerIsRunning,
 	serverOutputChannel
-} from './goLanguageServer';
+} from './language/goLanguageServer';
 import { isGoFile } from './goMode';
 import { isModSupported, runGoEnv } from './goModules';
 import { allToolsInformation } from './goToolsInformation';
@@ -46,7 +46,7 @@
 	if (!!editor && isGoFile(editor.document)) {
 		const isMod = await isModSupported(editor.document.uri);
 		if (isMod) {
-			runGoEnv(path.dirname(editor.document.uri.fsPath), ['GOMOD', 'GOWORK']).then((p) => {
+			runGoEnv(vscodeUri.Utils.dirname(editor.document.uri), ['GOMOD', 'GOWORK']).then((p) => {
 				gomod = p['GOMOD'] === '/dev/null' || p['GOMOD'] === 'NUL' ? '' : p['GOMOD'];
 				gowork = p['GOWORK'];
 			});
diff --git a/src/goSurvey.ts b/src/goSurvey.ts
index 579b05f..a2d8747 100644
--- a/src/goSurvey.ts
+++ b/src/goSurvey.ts
@@ -7,7 +7,7 @@
 'use strict';
 
 import vscode = require('vscode');
-import { getLocalGoplsVersion, lastUserAction, latestConfig } from './goLanguageServer';
+import { getLocalGoplsVersion, lastUserAction, latestConfig } from './language/goLanguageServer';
 import { outputChannel } from './goStatus';
 import { extensionId } from './const';
 import { getFromGlobalState, getFromWorkspaceState, updateGlobalState } from './stateUtils';
diff --git a/src/goTest/explore.ts b/src/goTest/explore.ts
index 8f24762..6bf28bd 100644
--- a/src/goTest/explore.ts
+++ b/src/goTest/explore.ts
@@ -18,7 +18,7 @@
 	WorkspaceFoldersChangeEvent
 } from 'vscode';
 import vscode = require('vscode');
-import { GoDocumentSymbolProvider } from '../goOutline';
+import { GoDocumentSymbolProvider } from '../language/legacy/goOutline';
 import { outputChannel } from '../goStatus';
 import { dispose, disposeIfEmpty, findItem, GoTest, isInTest, Workspace } from './utils';
 import { GoTestResolver, ProvideSymbols } from './resolve';
diff --git a/src/goTest/resolve.ts b/src/goTest/resolve.ts
index a4b4491..e208482 100644
--- a/src/goTest/resolve.ts
+++ b/src/goTest/resolve.ts
@@ -22,7 +22,7 @@
 import { getModFolderPath } from '../goModules';
 import { getCurrentGoPath } from '../util';
 import { getGoConfig } from '../config';
-import { dispose, disposeIfEmpty, FileSystem, GoTest, GoTestKind, isInTest, Workspace } from './utils';
+import { dispose, disposeIfEmpty, FileSystem, GoTest, GoTestKind, findModuleName, isInTest, Workspace } from './utils';
 import { walk, WalkStop } from './walk';
 import { importsTestify } from '../testUtils';
 
@@ -286,12 +286,13 @@
 			return existing;
 		}
 
-		// Use the module name as the label
+		// Read go.mod
 		const goMod = Uri.joinPath(uri, 'go.mod');
 		const contents = await this.workspace.fs.readFile(goMod);
-		const modLine = contents.toString().split('\n', 2)[0];
-		const match = modLine.match(/^module (?<name>.*?)(?:\s|\/\/|$)/);
-		const item = this.getOrCreateItem(null, match.groups.name, uri, 'module');
+
+		// Use the module name as the label
+		const label = findModuleName(contents.toString());
+		const item = this.getOrCreateItem(null, label, uri, 'module');
 		item.canResolveChildren = true;
 		return item;
 	}
diff --git a/src/goTest/utils.ts b/src/goTest/utils.ts
index 95e872d..33040af 100644
--- a/src/goTest/utils.ts
+++ b/src/goTest/utils.ts
@@ -115,3 +115,14 @@
 	dispose(resolver, item);
 	disposeIfEmpty(resolver, item.parent);
 }
+
+// The 'name' group captures the module name, and the unnamed group ignores any comment that might follow the name.
+const moduleNameRegex = /^module.(?<name>.*?)(?:\s|\/\/|$)/mu;
+
+export function findModuleName(goModContent: string): string {
+	const match = goModContent.toString().match(moduleNameRegex);
+	if (match === null) {
+		throw new Error('failed to find module name in go.mod');
+	}
+	return match.groups.name;
+}
diff --git a/src/goTools.ts b/src/goTools.ts
index 7d27f53..a4b2007 100644
--- a/src/goTools.ts
+++ b/src/goTools.ts
@@ -11,8 +11,8 @@
 import path = require('path');
 import semver = require('semver');
 import util = require('util');
-import { getFormatTool, usingCustomFormatTool } from './goFormat';
-import { goLiveErrorsEnabled } from './goLiveErrors';
+import { getFormatTool, usingCustomFormatTool } from './language/legacy/goFormat';
+import { goLiveErrorsEnabled } from './language/legacy/goLiveErrors';
 import { allToolsInformation } from './goToolsInformation';
 import { getBinPath, GoVersion } from './util';
 
@@ -86,6 +86,11 @@
 			return importPath + '@' + version;
 		}
 	}
+	// staticcheck requires go1.17+ after v0.3.0.
+	// (golang/vscode-go#2162)
+	if (goVersion.lt('1.17') && tool.name === 'staticcheck') {
+		return importPath + '@v0.2.2';
+	}
 	return importPath + '@latest';
 }
 
diff --git a/src/goToolsInformation.ts b/src/goToolsInformation.ts
index 5edb78a..67d7daa 100644
--- a/src/goToolsInformation.ts
+++ b/src/goToolsInformation.ts
@@ -27,7 +27,7 @@
 		name: 'go-outline',
 		importPath: 'github.com/ramya-rao-a/go-outline',
 		modulePath: 'github.com/ramya-rao-a/go-outline',
-		replacedByGopls: false, // TODO(github.com/golang/vscode-go/issues/1020): replace with Gopls.
+		replacedByGopls: true,
 		isImportant: true,
 		description: 'Go to symbol in file' // GoDocumentSymbolProvider, used by 'run test' codelens
 	},
@@ -195,10 +195,10 @@
 		description: 'Language Server from Google',
 		usePrereleaseInPreviewMode: true,
 		minimumGoVersion: semver.coerce('1.13'),
-		latestVersion: semver.parse('v0.8.0'),
-		latestVersionTimestamp: moment('2022-03-03', 'YYYY-MM-DD'),
-		latestPrereleaseVersion: semver.parse('v0.8.0'),
-		latestPrereleaseVersionTimestamp: moment('2022-03-03', 'YYYY-MM-DD')
+		latestVersion: semver.parse('v0.8.3'),
+		latestVersionTimestamp: moment('2022-04-07', 'YYYY-MM-DD'),
+		latestPrereleaseVersion: semver.parse('v0.8.3'),
+		latestPrereleaseVersionTimestamp: moment('2022-04-07', 'YYYY-MM-DD')
 	},
 	'dlv': {
 		name: 'dlv',
diff --git a/src/goVulncheck.ts b/src/goVulncheck.ts
new file mode 100644
index 0000000..097ec47
--- /dev/null
+++ b/src/goVulncheck.ts
@@ -0,0 +1,180 @@
+/*---------------------------------------------------------
+ * Copyright 2022 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
+import path from 'path';
+import { pathToFileURL } from 'url';
+import * as vscode from 'vscode';
+import { ExecuteCommandRequest } from 'vscode-languageserver-protocol';
+
+import { languageClient, serverInfo } from './language/goLanguageServer';
+
+export class VulncheckProvider {
+	static scheme = 'govulncheck';
+	static setup({ subscriptions }: vscode.ExtensionContext) {
+		const channel = vscode.window.createOutputChannel('govulncheck');
+		const instance = new this(channel);
+		subscriptions.push(
+			vscode.commands.registerCommand('go.vulncheck.run', async () => {
+				instance.run();
+			})
+		);
+		return instance;
+	}
+
+	constructor(private channel: vscode.OutputChannel) {}
+
+	private running = false;
+
+	async run() {
+		if (this.running) {
+			vscode.window.showWarningMessage('another vulncheck is in progress');
+			return;
+		}
+		try {
+			this.running = true;
+			await this.runInternal();
+		} finally {
+			this.running = false;
+		}
+	}
+
+	private async runInternal() {
+		const pick = await vscode.window.showQuickPick(['Current Package', 'Workspace']);
+		let dir, pattern: string;
+		const filename = vscode.window.activeTextEditor?.document?.fileName;
+		switch (pick) {
+			case 'Current Package':
+				if (!filename) {
+					vscode.window.showErrorMessage('vulncheck error: no current package');
+					return;
+				}
+				dir = path.dirname(filename);
+				pattern = '.';
+				break;
+			case 'Workspace':
+				dir = await this.activeDir();
+				pattern = './...';
+				break;
+			default:
+				return;
+		}
+
+		let result = '\nNo known vulnerabilities found.';
+		try {
+			const vuln = await vulncheck(dir, pattern);
+			if (vuln.Vuln) {
+				result = vuln.Vuln.map(renderVuln).join('----------------------\n');
+			}
+		} catch (e) {
+			result = e;
+			vscode.window.showErrorMessage(`error running vulncheck: ${e}`);
+		}
+
+		result = `DIR=${dir} govulncheck ${pattern}\n${result}`;
+		this.channel.clear();
+		this.channel.append(result);
+		this.channel.show();
+	}
+
+	private async activeDir() {
+		const folders = vscode.workspace.workspaceFolders;
+		if (!folders || folders.length === 0) return;
+		let dir = '';
+		if (folders.length === 1) {
+			dir = folders[0].uri.path;
+		} else {
+			const pick = await vscode.window.showQuickPick(
+				folders.map((f) => ({ label: f.name, description: f.uri.path }))
+			);
+			dir = pick?.description;
+		}
+		return dir;
+	}
+}
+
+async function vulncheck(dir: string, pattern = './...'): Promise<VulncheckReponse | undefined> {
+	const COMMAND = 'gopls.run_vulncheck_exp';
+	if (languageClient && serverInfo?.Commands?.includes(COMMAND)) {
+		const request = {
+			command: COMMAND,
+			arguments: [
+				{
+					Dir: pathToFileURL(dir).toString(),
+					Pattern: pattern
+				}
+			]
+		};
+		const resp = await languageClient.sendRequest(ExecuteCommandRequest.type, request);
+		return resp;
+	}
+}
+
+const renderVuln = (v: Vuln) => `
+ID:               ${v.ID}
+Aliases:          ${v.Aliases?.join(', ') ?? 'None'}
+Symbol:           ${v.Symbol}
+Pkg Path:         ${v.PkgPath}
+Mod Path:         ${v.ModPath}
+URL:              ${v.URL}
+Current Version:  ${v.CurrentVersion}
+Fixed Version:    ${v.FixedVersion}
+
+${v.Details}
+${renderStack(v)}`;
+
+const renderStack = (v: Vuln) => {
+	const content = [];
+	for (const stack of v.CallStacks ?? []) {
+		for (const [, line] of stack.entries()) {
+			content.push(`\t${line.Name}`);
+			const loc = renderUri(line);
+			if (loc) {
+				content.push(`\t\t${loc}`);
+			}
+		}
+		content.push('');
+	}
+	return content.join('\n');
+};
+
+const renderUri = (stack: CallStack) => {
+	if (!stack.URI) {
+		// generated file or dummy location may not have a file name.
+		return '';
+	}
+	const parsed = vscode.Uri.parse(stack.URI);
+	const line = stack.Pos.line + 1; // Position uses 0-based line number.
+	const folder = vscode.workspace.getWorkspaceFolder(parsed);
+	if (folder) {
+		return `${parsed.path}:${line}:${stack.Pos.character}`;
+	}
+	return `${stack.URI}#${line}:${stack.Pos.character}`;
+};
+
+interface VulncheckReponse {
+	Vuln?: Vuln[];
+}
+
+interface Vuln {
+	ID: string;
+	Details: string;
+	Aliases: string[];
+	Symbol: string;
+	PkgPath: string;
+	ModPath: string;
+	URL: string;
+	CurrentVersion: string;
+	FixedVersion: string;
+	CallStacks?: CallStack[][];
+}
+
+interface CallStack {
+	Name: string;
+	URI: string;
+	Pos: {
+		line: number;
+		character: number;
+	};
+}
diff --git a/src/goLanguageServer.ts b/src/language/goLanguageServer.ts
similarity index 89%
rename from src/goLanguageServer.ts
rename to src/language/goLanguageServer.ts
index 1dfe7cc..db21fcd 100644
--- a/src/goLanguageServer.ts
+++ b/src/language/goLanguageServer.ts
@@ -34,32 +34,19 @@
 	RevealOutputChannelOn
 } from 'vscode-languageclient';
 import { LanguageClient } from 'vscode-languageclient/node';
-import { getGoConfig, getGoplsConfig, extensionInfo } from './config';
-import { GoCodeActionProvider } from './goCodeAction';
-import { GoDefinitionProvider } from './goDeclaration';
-import { toolExecutionEnvironment } from './goEnv';
-import { GoHoverProvider } from './goExtraInfo';
-import { GoDocumentFormattingEditProvider, usingCustomFormatTool } from './goFormat';
-import { GoImplementationProvider } from './goImplementations';
-import { installTools, latestToolVersion, promptForMissingTool, promptForUpdatingTool } from './goInstallTools';
-import { parseLiveFile } from './goLiveErrors';
+import { getGoConfig, getGoplsConfig, extensionInfo } from '../config';
+import { toolExecutionEnvironment } from '../goEnv';
+import { GoDocumentFormattingEditProvider, usingCustomFormatTool } from './legacy/goFormat';
+import { installTools, latestToolVersion, promptForMissingTool, promptForUpdatingTool } from '../goInstallTools';
 import {
 	buildDiagnosticCollection,
 	lintDiagnosticCollection,
 	restartLanguageServer,
 	vetDiagnosticCollection
-} from './goMain';
-import { GO_MODE } from './goMode';
-import { GoDocumentSymbolProvider } from './goOutline';
-import { GoReferenceProvider } from './goReferences';
-import { GoRenameProvider } from './goRename';
-import { GoSignatureHelpProvider } from './goSignature';
-import { outputChannel, updateLanguageServerIconGoStatusBar } from './goStatus';
-import { GoCompletionItemProvider } from './goSuggest';
-import { GoWorkspaceSymbolProvider } from './goSymbol';
-import { getTool, Tool } from './goTools';
-import { GoTypeDefinitionProvider } from './goTypeDefinition';
-import { getFromGlobalState, updateGlobalState, updateWorkspaceState } from './stateUtils';
+} from '../goMain';
+import { outputChannel, updateLanguageServerIconGoStatusBar } from '../goStatus';
+import { getTool, Tool } from '../goTools';
+import { getFromGlobalState, updateGlobalState, updateWorkspaceState } from '../stateUtils';
 import {
 	getBinPath,
 	getCheckForToolsUpdatesConfig,
@@ -67,14 +54,15 @@
 	getGoVersion,
 	getWorkspaceFolderPath,
 	removeDuplicateDiagnostics
-} from './util';
-import { Mutex } from './utils/mutex';
-import { getToolFromToolPath } from './utils/pathUtils';
+} from '../util';
+import { Mutex } from '../utils/mutex';
+import { getToolFromToolPath } from '../utils/pathUtils';
 import WebRequest = require('web-request');
 import { FoldingContext } from 'vscode';
 import { ProvideFoldingRangeSignature } from 'vscode-languageclient/lib/common/foldingRange';
-import { daysBetween, getStateConfig, maybePromptForGoplsSurvey, timeDay, timeMinute } from './goSurvey';
-import { maybePromptForDeveloperSurvey } from './goDeveloperSurvey';
+import { daysBetween, getStateConfig, maybePromptForGoplsSurvey, timeDay, timeMinute } from '../goSurvey';
+import { maybePromptForDeveloperSurvey } from '../goDeveloperSurvey';
+import { LegacyLanguageService } from './registerDefaultProviders';
 
 interface LanguageServerConfig {
 	serverName: string;
@@ -101,6 +89,7 @@
 export let latestConfig: LanguageServerConfig;
 export let serverOutputChannel: vscode.OutputChannel;
 export let languageServerIsRunning = false;
+
 // serverInfo is the information from the server received during initialization.
 export let serverInfo: ServerInfo = undefined;
 
@@ -111,6 +100,8 @@
 	Commands?: string[];
 }
 
+let legacyLanguageService: LegacyLanguageService = undefined;
+
 const languageServerStartMutex = new Mutex();
 
 let serverTraceChannel: vscode.OutputChannel;
@@ -149,9 +140,6 @@
 	}
 }
 
-// defaultLanguageProviders is the list of providers currently registered.
-let defaultLanguageProviders: vscode.Disposable[] = [];
-
 // restartCommand is the command used by the user to restart the language
 // server.
 let restartCommand: vscode.Disposable;
@@ -216,10 +204,12 @@
 		// If the server has been disabled, or failed to start,
 		// fall back to the default providers, while making sure not to
 		// re-register any providers.
-		if (!started && defaultLanguageProviders.length === 0) {
-			registerDefaultProviders(ctx);
+		if (!started && !legacyLanguageService) {
+			legacyLanguageService = new LegacyLanguageService(ctx);
+			ctx.subscriptions.push(legacyLanguageService);
 		}
 		languageServerIsRunning = started;
+		vscode.commands.executeCommand('setContext', 'go.goplsIsRunning', started);
 		updateLanguageServerIconGoStatusBar(started, goConfig['useLanguageServer'] === true);
 	} finally {
 		unlock();
@@ -265,8 +255,6 @@
 			if (cfg.serverName !== '' && cfg.serverName !== 'gopls') {
 				return;
 			}
-			// Prompt the user to enable gopls and record what actions they took.
-			await promptAboutGoplsOptOut();
 			// Check if the language server has now been enabled, and if so,
 			// it will be installed below.
 			cfg = buildLanguageServerConfig(getGoConfig());
@@ -296,8 +284,8 @@
 	setTimeout(survey, 30 * timeMinute);
 }
 
-// Ask users to enable gopls. If they still don't want it, ask to fill out opt-out survey with the probability.
-export async function promptAboutGoplsOptOut(probability = 0.5) {
+// Ask users to fill out opt-out survey.
+export async function promptAboutGoplsOptOut() {
 	// Check if the configuration is set in the workspace.
 	const useLanguageServer = getGoConfig().inspect('useLanguageServer');
 	const workspace = useLanguageServer.workspaceFolderValue === false || useLanguageServer.workspaceValue === false;
@@ -312,51 +300,10 @@
 			return cfg;
 		}
 		cfg.lastDatePrompted = new Date();
-
-		const selected = await vscode.window.showInformationMessage(
-			`We noticed that you have disabled the language server.
-It has [stabilized](https://blog.golang.org/gopls-vscode-go) and is now enabled by default in this extension.
-Would you like to enable it now?`,
-			{ title: 'Enable' },
-			{ title: 'Not now' },
-			{ title: 'Never' }
+		await promptForGoplsOptOutSurvey(
+			cfg,
+			"It looks like you've disabled the Go language server. Would you be willing to tell us why you've disabled it, so that we can improve it?"
 		);
-		if (!selected) {
-			return cfg;
-		}
-		switch (selected.title) {
-			case 'Enable':
-				{
-					// Change the user's Go configuration to enable the language server.
-					// Remove the setting entirely, since it's on by default now.
-					const goConfig = getGoConfig();
-					await goConfig.update('useLanguageServer', undefined, vscode.ConfigurationTarget.Global);
-					if (goConfig.inspect('useLanguageServer').workspaceValue === false) {
-						await goConfig.update('useLanguageServer', undefined, vscode.ConfigurationTarget.Workspace);
-					}
-					if (goConfig.inspect('useLanguageServer').workspaceFolderValue === false) {
-						await goConfig.update(
-							'useLanguageServer',
-							undefined,
-							vscode.ConfigurationTarget.WorkspaceFolder
-						);
-					}
-					cfg.prompt = false;
-				}
-				break;
-			case 'Not now':
-				cfg.prompt = true;
-				break;
-			case 'Never':
-				cfg.prompt = false;
-				if (Math.random() < probability) {
-					await promptForGoplsOptOutSurvey(
-						cfg,
-						'No problem. Would you be willing to tell us why you have opted out of the language server?'
-					);
-				}
-				break;
-		}
 		return cfg;
 	};
 	cfg = await promptFn();
@@ -401,12 +348,12 @@
 	return getStateConfig(goplsOptOutConfigKey, workspace) as GoplsOptOutConfig;
 };
 
-function flushGoplsOptOutConfig(cfg: GoplsOptOutConfig, workspace: boolean) {
+export const flushGoplsOptOutConfig = (cfg: GoplsOptOutConfig, workspace: boolean) => {
 	if (workspace) {
 		updateWorkspaceState(goplsOptOutConfigKey, JSON.stringify(cfg));
 	}
 	updateGlobalState(goplsOptOutConfigKey, JSON.stringify(cfg));
-}
+};
 
 async function startLanguageServer(ctx: vscode.ExtensionContext, config: LanguageServerConfig): Promise<boolean> {
 	// If the client has already been started, make sure to clear existing
@@ -451,7 +398,9 @@
 
 	// Before starting the language server, make sure to deregister any
 	// currently registered language providers.
-	disposeDefaultProviders();
+
+	legacyLanguageService?.dispose();
+	legacyLanguageService = undefined;
 
 	languageServerDisposable = languageClient.start();
 	ctx.subscriptions.push(languageServerDisposable);
@@ -904,48 +853,6 @@
 	];
 }
 
-// registerUsualProviders registers the language feature providers if the language server is not enabled.
-function registerDefaultProviders(ctx: vscode.ExtensionContext) {
-	const completionProvider = new GoCompletionItemProvider(ctx.globalState);
-	defaultLanguageProviders.push(completionProvider);
-	defaultLanguageProviders.push(
-		vscode.languages.registerCompletionItemProvider(GO_MODE, completionProvider, '.', '"')
-	);
-	defaultLanguageProviders.push(vscode.languages.registerHoverProvider(GO_MODE, new GoHoverProvider()));
-	defaultLanguageProviders.push(vscode.languages.registerDefinitionProvider(GO_MODE, new GoDefinitionProvider()));
-	defaultLanguageProviders.push(vscode.languages.registerReferenceProvider(GO_MODE, new GoReferenceProvider()));
-	defaultLanguageProviders.push(
-		vscode.languages.registerDocumentSymbolProvider(GO_MODE, new GoDocumentSymbolProvider())
-	);
-	defaultLanguageProviders.push(vscode.languages.registerWorkspaceSymbolProvider(new GoWorkspaceSymbolProvider()));
-	defaultLanguageProviders.push(
-		vscode.languages.registerSignatureHelpProvider(GO_MODE, new GoSignatureHelpProvider(), '(', ',')
-	);
-	defaultLanguageProviders.push(
-		vscode.languages.registerImplementationProvider(GO_MODE, new GoImplementationProvider())
-	);
-	defaultLanguageProviders.push(
-		vscode.languages.registerDocumentFormattingEditProvider(GO_MODE, new GoDocumentFormattingEditProvider())
-	);
-	defaultLanguageProviders.push(
-		vscode.languages.registerTypeDefinitionProvider(GO_MODE, new GoTypeDefinitionProvider())
-	);
-	defaultLanguageProviders.push(vscode.languages.registerRenameProvider(GO_MODE, new GoRenameProvider()));
-	defaultLanguageProviders.push(vscode.workspace.onDidChangeTextDocument(parseLiveFile, null, ctx.subscriptions));
-	defaultLanguageProviders.push(vscode.languages.registerCodeActionsProvider(GO_MODE, new GoCodeActionProvider()));
-
-	for (const provider of defaultLanguageProviders) {
-		ctx.subscriptions.push(provider);
-	}
-}
-
-function disposeDefaultProviders() {
-	for (const disposable of defaultLanguageProviders) {
-		disposable.dispose();
-	}
-	defaultLanguageProviders = [];
-}
-
 export async function watchLanguageServerConfiguration(e: vscode.ConfigurationChangeEvent) {
 	if (!e.affectsConfiguration('go')) {
 		return;
@@ -962,6 +869,10 @@
 	) {
 		restartLanguageServer('config change');
 	}
+
+	if (e.affectsConfiguration('go.useLanguageServer') && getGoConfig()['useLanguageServer'] === false) {
+		promptAboutGoplsOptOut();
+	}
 }
 
 export function buildLanguageServerConfig(goConfig: vscode.WorkspaceConfiguration): LanguageServerConfig {
diff --git a/src/goCodeAction.ts b/src/language/legacy/goCodeAction.ts
similarity index 96%
rename from src/goCodeAction.ts
rename to src/language/legacy/goCodeAction.ts
index 9d29436..488a5a9 100644
--- a/src/goCodeAction.ts
+++ b/src/language/legacy/goCodeAction.ts
@@ -7,7 +7,7 @@
 'use strict';
 
 import vscode = require('vscode');
-import { listPackages } from './goImport';
+import { listPackages } from '../../goImport';
 
 export class GoCodeActionProvider implements vscode.CodeActionProvider {
 	public provideCodeActions(
diff --git a/src/goDeclaration.ts b/src/language/legacy/goDeclaration.ts
similarity index 96%
rename from src/goDeclaration.ts
rename to src/language/legacy/goDeclaration.ts
index 8f69197..2a3f202 100644
--- a/src/goDeclaration.ts
+++ b/src/language/legacy/goDeclaration.ts
@@ -9,10 +9,10 @@
 import cp = require('child_process');
 import path = require('path');
 import vscode = require('vscode');
-import { getGoConfig } from './config';
-import { toolExecutionEnvironment } from './goEnv';
-import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools';
-import { getModFolderPath, promptToUpdateToolForModules } from './goModules';
+import { getGoConfig } from '../../config';
+import { toolExecutionEnvironment } from '../../goEnv';
+import { promptForMissingTool, promptForUpdatingTool } from '../../goInstallTools';
+import { getModFolderPath, promptToUpdateToolForModules } from '../../goModules';
 import {
 	byteOffsetAt,
 	getBinPath,
@@ -22,9 +22,9 @@
 	goKeywords,
 	isPositionInString,
 	runGodoc
-} from './util';
-import { getCurrentGoRoot } from './utils/pathUtils';
-import { killProcessTree } from './utils/processUtils';
+} from '../../util';
+import { getCurrentGoRoot } from '../../utils/pathUtils';
+import { killProcessTree } from '../../utils/processUtils';
 
 const missingToolMsg = 'Missing tool: ';
 
diff --git a/src/goExtraInfo.ts b/src/language/legacy/goExtraInfo.ts
similarity index 97%
rename from src/goExtraInfo.ts
rename to src/language/legacy/goExtraInfo.ts
index 16d5dcd..1c5c84c 100644
--- a/src/goExtraInfo.ts
+++ b/src/language/legacy/goExtraInfo.ts
@@ -9,7 +9,7 @@
 
 import vscode = require('vscode');
 import { CancellationToken, Hover, HoverProvider, Position, TextDocument, WorkspaceConfiguration } from 'vscode';
-import { getGoConfig } from './config';
+import { getGoConfig } from '../../config';
 import { definitionLocation } from './goDeclaration';
 
 export class GoHoverProvider implements HoverProvider {
diff --git a/src/goFormat.ts b/src/language/legacy/goFormat.ts
similarity index 93%
rename from src/goFormat.ts
rename to src/language/legacy/goFormat.ts
index abf9f8b..a67b130 100644
--- a/src/goFormat.ts
+++ b/src/language/legacy/goFormat.ts
@@ -9,11 +9,11 @@
 import cp = require('child_process');
 import path = require('path');
 import vscode = require('vscode');
-import { getGoConfig } from './config';
-import { toolExecutionEnvironment } from './goEnv';
-import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools';
-import { getBinPath } from './util';
-import { killProcessTree } from './utils/processUtils';
+import { getGoConfig } from '../../config';
+import { toolExecutionEnvironment } from '../../goEnv';
+import { promptForMissingTool, promptForUpdatingTool } from '../../goInstallTools';
+import { getBinPath } from '../../util';
+import { killProcessTree } from '../../utils/processUtils';
 
 export class GoDocumentFormattingEditProvider implements vscode.DocumentFormattingEditProvider {
 	public provideDocumentFormattingEdits(
diff --git a/src/goImplementations.ts b/src/language/legacy/goImplementations.ts
similarity index 92%
rename from src/goImplementations.ts
rename to src/language/legacy/goImplementations.ts
index 1907068..68aa8b0 100644
--- a/src/goImplementations.ts
+++ b/src/language/legacy/goImplementations.ts
@@ -9,12 +9,12 @@
 import cp = require('child_process');
 import path = require('path');
 import vscode = require('vscode');
-import { getGoConfig } from './config';
-import { toolExecutionEnvironment } from './goEnv';
-import { promptForMissingTool } from './goInstallTools';
-import { byteOffsetAt, canonicalizeGOPATHPrefix, getBinPath, getWorkspaceFolderPath } from './util';
-import { envPath, getCurrentGoRoot } from './utils/pathUtils';
-import { killProcessTree } from './utils/processUtils';
+import { getGoConfig } from '../../config';
+import { toolExecutionEnvironment } from '../../goEnv';
+import { promptForMissingTool } from '../../goInstallTools';
+import { byteOffsetAt, canonicalizeGOPATHPrefix, getBinPath, getWorkspaceFolderPath } from '../../util';
+import { envPath, getCurrentGoRoot } from '../../utils/pathUtils';
+import { killProcessTree } from '../../utils/processUtils';
 
 interface GoListOutput {
 	Dir: string;
diff --git a/src/goLiveErrors.ts b/src/language/legacy/goLiveErrors.ts
similarity index 92%
rename from src/goLiveErrors.ts
rename to src/language/legacy/goLiveErrors.ts
index 96bcf21..b70c671 100644
--- a/src/goLiveErrors.ts
+++ b/src/language/legacy/goLiveErrors.ts
@@ -9,12 +9,12 @@
 import cp = require('child_process');
 import path = require('path');
 import vscode = require('vscode');
-import { getGoConfig } from './config';
-import { toolExecutionEnvironment } from './goEnv';
-import { promptForMissingTool } from './goInstallTools';
-import { buildDiagnosticCollection } from './goMain';
-import { isModSupported } from './goModules';
-import { getBinPath } from './util';
+import { getGoConfig } from '../../config';
+import { toolExecutionEnvironment } from '../../goEnv';
+import { promptForMissingTool } from '../../goInstallTools';
+import { buildDiagnosticCollection } from '../../goMain';
+import { isModSupported } from '../../goModules';
+import { getBinPath } from '../../util';
 
 // Interface for settings configuration for adding and removing tags
 interface GoLiveErrorsConfig {
diff --git a/src/goOutline.ts b/src/language/legacy/goOutline.ts
similarity index 95%
rename from src/goOutline.ts
rename to src/language/legacy/goOutline.ts
index da1f453..73d65d4 100644
--- a/src/goOutline.ts
+++ b/src/language/legacy/goOutline.ts
@@ -9,12 +9,12 @@
 import cp = require('child_process');
 import vscode = require('vscode');
 import { ExecuteCommandParams, ExecuteCommandRequest } from 'vscode-languageserver-protocol';
-import { getGoConfig } from './config';
-import { toolExecutionEnvironment } from './goEnv';
-import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools';
-import { languageClient, serverInfo } from './goLanguageServer';
-import { getBinPath, getFileArchive, makeMemoizedByteOffsetConverter } from './util';
-import { killProcess } from './utils/processUtils';
+import { getGoConfig } from '../../config';
+import { toolExecutionEnvironment } from '../../goEnv';
+import { promptForMissingTool, promptForUpdatingTool } from '../../goInstallTools';
+import { languageClient, serverInfo } from '../goLanguageServer';
+import { getBinPath, getFileArchive, makeMemoizedByteOffsetConverter } from '../../util';
+import { killProcess } from '../../utils/processUtils';
 
 // Keep in sync with https://github.com/ramya-rao-a/go-outline
 export interface GoOutlineRange {
diff --git a/src/goRefactor.ts b/src/language/legacy/goRefactor.ts
similarity index 100%
rename from src/goRefactor.ts
rename to src/language/legacy/goRefactor.ts
diff --git a/src/goReferences.ts b/src/language/legacy/goReferences.ts
similarity index 92%
rename from src/goReferences.ts
rename to src/language/legacy/goReferences.ts
index 9ea71ae..674a1b7 100644
--- a/src/goReferences.ts
+++ b/src/language/legacy/goReferences.ts
@@ -9,11 +9,11 @@
 import cp = require('child_process');
 import path = require('path');
 import vscode = require('vscode');
-import { getGoConfig } from './config';
-import { toolExecutionEnvironment } from './goEnv';
-import { promptForMissingTool } from './goInstallTools';
-import { byteOffsetAt, canonicalizeGOPATHPrefix, getBinPath, getFileArchive } from './util';
-import { killProcessTree } from './utils/processUtils';
+import { getGoConfig } from '../../config';
+import { toolExecutionEnvironment } from '../../goEnv';
+import { promptForMissingTool } from '../../goInstallTools';
+import { byteOffsetAt, canonicalizeGOPATHPrefix, getBinPath, getFileArchive } from '../../util';
+import { killProcessTree } from '../../utils/processUtils';
 
 export class GoReferenceProvider implements vscode.ReferenceProvider {
 	public provideReferences(
diff --git a/src/goRename.ts b/src/language/legacy/goRename.ts
similarity index 89%
rename from src/goRename.ts
rename to src/language/legacy/goRename.ts
index 4f2666f..5a864c2 100644
--- a/src/goRename.ts
+++ b/src/language/legacy/goRename.ts
@@ -8,13 +8,13 @@
 
 import cp = require('child_process');
 import vscode = require('vscode');
-import { getGoConfig } from './config';
-import { Edit, FilePatch, getEditsFromUnifiedDiffStr, isDiffToolAvailable } from './diffUtils';
-import { toolExecutionEnvironment } from './goEnv';
-import { promptForMissingTool } from './goInstallTools';
-import { outputChannel } from './goStatus';
-import { byteOffsetAt, canonicalizeGOPATHPrefix, getBinPath } from './util';
-import { killProcessTree } from './utils/processUtils';
+import { getGoConfig } from '../../config';
+import { Edit, FilePatch, getEditsFromUnifiedDiffStr, isDiffToolAvailable } from '../../diffUtils';
+import { toolExecutionEnvironment } from '../../goEnv';
+import { promptForMissingTool } from '../../goInstallTools';
+import { outputChannel } from '../../goStatus';
+import { byteOffsetAt, canonicalizeGOPATHPrefix, getBinPath } from '../../util';
+import { killProcessTree } from '../../utils/processUtils';
 
 export class GoRenameProvider implements vscode.RenameProvider {
 	public provideRenameEdits(
diff --git a/src/goSignature.ts b/src/language/legacy/goSignature.ts
similarity index 97%
rename from src/goSignature.ts
rename to src/language/legacy/goSignature.ts
index b87131b..e4f84a6 100755
--- a/src/goSignature.ts
+++ b/src/language/legacy/goSignature.ts
@@ -16,9 +16,9 @@
 	TextDocument,
 	WorkspaceConfiguration
 } from 'vscode';
-import { getGoConfig } from './config';
+import { getGoConfig } from '../../config';
 import { definitionLocation } from './goDeclaration';
-import { getParametersAndReturnType, isPositionInComment, isPositionInString } from './util';
+import { getParametersAndReturnType, isPositionInComment, isPositionInString } from '../../util';
 
 export class GoSignatureHelpProvider implements SignatureHelpProvider {
 	constructor(private goConfig?: WorkspaceConfiguration) {}
diff --git a/src/goSuggest.ts b/src/language/legacy/goSuggest.ts
similarity index 97%
rename from src/goSuggest.ts
rename to src/language/legacy/goSuggest.ts
index 1f3361a..95afca8 100644
--- a/src/goSuggest.ts
+++ b/src/language/legacy/goSuggest.ts
@@ -9,12 +9,12 @@
 import cp = require('child_process');
 import path = require('path');
 import vscode = require('vscode');
-import { getGoConfig } from './config';
-import { toolExecutionEnvironment } from './goEnv';
-import { getTextEditForAddImport } from './goImport';
-import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools';
-import { isModSupported } from './goModules';
-import { getImportablePackages, PackageInfo } from './goPackages';
+import { getGoConfig } from '../../config';
+import { toolExecutionEnvironment } from '../../goEnv';
+import { getTextEditForAddImport } from '../../goImport';
+import { promptForMissingTool, promptForUpdatingTool } from '../../goInstallTools';
+import { isModSupported } from '../../goModules';
+import { getImportablePackages, PackageInfo } from '../../goPackages';
 import {
 	byteOffsetAt,
 	getBinPath,
@@ -27,8 +27,8 @@
 	isPositionInString,
 	parseFilePrelude,
 	runGodoc
-} from './util';
-import { getCurrentGoWorkspaceFromGOPATH } from './utils/pathUtils';
+} from '../../util';
+import { getCurrentGoWorkspaceFromGOPATH } from '../../utils/pathUtils';
 
 function vscodeKindFromGoCodeClass(kind: string, type: string): vscode.CompletionItemKind {
 	switch (kind) {
diff --git a/src/goSymbol.ts b/src/language/legacy/goSymbol.ts
similarity index 91%
rename from src/goSymbol.ts
rename to src/language/legacy/goSymbol.ts
index 543db2b..75588b6 100644
--- a/src/goSymbol.ts
+++ b/src/language/legacy/goSymbol.ts
@@ -8,12 +8,12 @@
 
 import cp = require('child_process');
 import vscode = require('vscode');
-import { getGoConfig } from './config';
-import { toolExecutionEnvironment } from './goEnv';
-import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools';
-import { getBinPath, getWorkspaceFolderPath } from './util';
-import { getCurrentGoRoot } from './utils/pathUtils';
-import { killProcessTree } from './utils/processUtils';
+import { getGoConfig } from '../../config';
+import { toolExecutionEnvironment } from '../../goEnv';
+import { promptForMissingTool, promptForUpdatingTool } from '../../goInstallTools';
+import { getBinPath, getWorkspaceFolderPath } from '../../util';
+import { getCurrentGoRoot } from '../../utils/pathUtils';
+import { killProcessTree } from '../../utils/processUtils';
 
 // Keep in sync with github.com/acroca/go-symbols'
 interface GoSymbolDeclaration {
diff --git a/src/goTypeDefinition.ts b/src/language/legacy/goTypeDefinition.ts
similarity index 93%
rename from src/goTypeDefinition.ts
rename to src/language/legacy/goTypeDefinition.ts
index a1000f2..c9c6e40 100644
--- a/src/goTypeDefinition.ts
+++ b/src/language/legacy/goTypeDefinition.ts
@@ -9,12 +9,12 @@
 import cp = require('child_process');
 import path = require('path');
 import vscode = require('vscode');
-import { getGoConfig } from './config';
+import { getGoConfig } from '../../config';
 import { adjustWordPosition, definitionLocation, parseMissingError } from './goDeclaration';
-import { toolExecutionEnvironment } from './goEnv';
-import { promptForMissingTool } from './goInstallTools';
-import { byteOffsetAt, canonicalizeGOPATHPrefix, getBinPath, getFileArchive, goBuiltinTypes } from './util';
-import { killProcessTree } from './utils/processUtils';
+import { toolExecutionEnvironment } from '../../goEnv';
+import { promptForMissingTool } from '../../goInstallTools';
+import { byteOffsetAt, canonicalizeGOPATHPrefix, getBinPath, getFileArchive, goBuiltinTypes } from '../../util';
+import { killProcessTree } from '../../utils/processUtils';
 
 interface GuruDescribeOutput {
 	desc: string;
diff --git a/src/language/registerDefaultProviders.ts b/src/language/registerDefaultProviders.ts
new file mode 100644
index 0000000..a5380ac
--- /dev/null
+++ b/src/language/registerDefaultProviders.ts
@@ -0,0 +1,60 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+/*---------------------------------------------------------
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ * Modification copyright 2020 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+'use strict';
+import vscode = require('vscode');
+import { GoCodeActionProvider } from './legacy/goCodeAction';
+import { GoDefinitionProvider } from './legacy/goDeclaration';
+import { GoHoverProvider } from './legacy/goExtraInfo';
+import { GoDocumentFormattingEditProvider } from './legacy/goFormat';
+import { GoImplementationProvider } from './legacy/goImplementations';
+import { parseLiveFile } from './legacy/goLiveErrors';
+import { GO_MODE } from '../goMode';
+import { GoDocumentSymbolProvider } from './legacy/goOutline';
+import { GoReferenceProvider } from './legacy/goReferences';
+import { GoRenameProvider } from './legacy/goRename';
+import { GoSignatureHelpProvider } from './legacy/goSignature';
+import { GoCompletionItemProvider } from './legacy/goSuggest';
+import { GoWorkspaceSymbolProvider } from './legacy/goSymbol';
+import { GoTypeDefinitionProvider } from './legacy/goTypeDefinition';
+
+export class LegacyLanguageService implements vscode.Disposable {
+	private _disposables: vscode.Disposable[] = [];
+
+	constructor(ctx: vscode.ExtensionContext) {
+		const completionProvider = new GoCompletionItemProvider(ctx.globalState);
+		this._disposables.push(completionProvider);
+		this._disposables.push(vscode.languages.registerCompletionItemProvider(GO_MODE, completionProvider, '.', '"'));
+		this._disposables.push(vscode.languages.registerHoverProvider(GO_MODE, new GoHoverProvider()));
+		this._disposables.push(vscode.languages.registerDefinitionProvider(GO_MODE, new GoDefinitionProvider()));
+		this._disposables.push(vscode.languages.registerReferenceProvider(GO_MODE, new GoReferenceProvider()));
+		this._disposables.push(
+			vscode.languages.registerDocumentSymbolProvider(GO_MODE, new GoDocumentSymbolProvider())
+		);
+		this._disposables.push(vscode.languages.registerWorkspaceSymbolProvider(new GoWorkspaceSymbolProvider()));
+		this._disposables.push(
+			vscode.languages.registerSignatureHelpProvider(GO_MODE, new GoSignatureHelpProvider(), '(', ',')
+		);
+		this._disposables.push(
+			vscode.languages.registerImplementationProvider(GO_MODE, new GoImplementationProvider())
+		);
+		this._disposables.push(
+			vscode.languages.registerDocumentFormattingEditProvider(GO_MODE, new GoDocumentFormattingEditProvider())
+		);
+		this._disposables.push(
+			vscode.languages.registerTypeDefinitionProvider(GO_MODE, new GoTypeDefinitionProvider())
+		);
+		this._disposables.push(vscode.languages.registerRenameProvider(GO_MODE, new GoRenameProvider()));
+		this._disposables.push(vscode.workspace.onDidChangeTextDocument(parseLiveFile, null, ctx.subscriptions));
+		this._disposables.push(vscode.languages.registerCodeActionsProvider(GO_MODE, new GoCodeActionProvider()));
+	}
+
+	dispose() {
+		for (const d of this._disposables) {
+			d.dispose();
+		}
+	}
+}
diff --git a/src/testUtils.ts b/src/testUtils.ts
index 2698bf6..eb89f54 100644
--- a/src/testUtils.ts
+++ b/src/testUtils.ts
@@ -15,7 +15,7 @@
 import { applyCodeCoverageToAllEditors } from './goCover';
 import { toolExecutionEnvironment } from './goEnv';
 import { getCurrentPackage } from './goModules';
-import { GoDocumentSymbolProvider } from './goOutline';
+import { GoDocumentSymbolProvider } from './language/legacy/goOutline';
 import { getNonVendorPackages } from './goPackages';
 import { getBinPath, getCurrentGoPath, getTempFilePath, LineBuffer, resolvePath } from './util';
 import { parseEnvFile } from './utils/envUtils';
diff --git a/src/util.ts b/src/util.ts
index ff69431..b64d64c 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -15,7 +15,7 @@
 import { getGoConfig } from './config';
 import { extensionId } from './const';
 import { toolExecutionEnvironment } from './goEnv';
-import { languageClient } from './goLanguageServer';
+import { languageClient } from './language/goLanguageServer';
 import { buildDiagnosticCollection, lintDiagnosticCollection, vetDiagnosticCollection } from './goMain';
 import { getCurrentPackage } from './goModules';
 import { outputChannel } from './goStatus';
diff --git a/src/utils/envUtils.ts b/src/utils/envUtils.ts
index bb5166a..81b6f55 100644
--- a/src/utils/envUtils.ts
+++ b/src/utils/envUtils.ts
@@ -39,7 +39,7 @@
 		});
 		return env;
 	} catch (e) {
-		throw new Error(`Cannot load environment variables from file ${envFilePath}`);
+		throw new Error(`Cannot load environment variables from file ${envFilePath}: ${e}`);
 	}
 }
 
diff --git a/test/gopls/configuration.test.ts b/test/gopls/configuration.test.ts
index 06859b7..c10c8d1 100644
--- a/test/gopls/configuration.test.ts
+++ b/test/gopls/configuration.test.ts
@@ -6,7 +6,7 @@
 
 import assert from 'assert';
 import { getGoplsConfig } from '../../src/config';
-import * as lsp from '../../src/goLanguageServer';
+import * as lsp from '../../src/language/goLanguageServer';
 
 suite('gopls configuration tests', () => {
 	test('filterGoplsDefaultConfigValues', async () => {
diff --git a/test/gopls/extension.test.ts b/test/gopls/extension.test.ts
index 47a67a9..53c1684 100644
--- a/test/gopls/extension.test.ts
+++ b/test/gopls/extension.test.ts
@@ -9,7 +9,11 @@
 import * as vscode from 'vscode';
 import { LanguageClient } from 'vscode-languageclient/node';
 import { getGoConfig } from '../../src/config';
-import { buildLanguageClient, BuildLanguageClientOption, buildLanguageServerConfig } from '../../src/goLanguageServer';
+import {
+	buildLanguageClient,
+	BuildLanguageClientOption,
+	buildLanguageServerConfig
+} from '../../src/language/goLanguageServer';
 import sinon = require('sinon');
 import { getGoVersion, GoVersion } from '../../src/util';
 
diff --git a/test/gopls/report.test.ts b/test/gopls/report.test.ts
index 8653b03..dd121e7 100644
--- a/test/gopls/report.test.ts
+++ b/test/gopls/report.test.ts
@@ -4,7 +4,7 @@
  *--------------------------------------------------------*/
 
 import assert from 'assert';
-import { sanitizeGoplsTrace } from '../../src/goLanguageServer';
+import { sanitizeGoplsTrace } from '../../src/language/goLanguageServer';
 
 suite('gopls issue report tests', () => {
 	test('sanitize user trace', () => {
diff --git a/test/gopls/survey.test.ts b/test/gopls/survey.test.ts
index 348d88b..51f1a20 100644
--- a/test/gopls/survey.test.ts
+++ b/test/gopls/survey.test.ts
@@ -6,7 +6,7 @@
 import assert from 'assert';
 import sinon = require('sinon');
 import vscode = require('vscode');
-import goLanguageServer = require('../../src/goLanguageServer');
+import goLanguageServer = require('../../src/language/goLanguageServer');
 import goSurvey = require('../../src/goSurvey');
 import goDeveloperSurvey = require('../../src/goDeveloperSurvey');
 
@@ -197,31 +197,43 @@
 		sandbox.restore();
 	});
 
-	// testConfig, choice, probability, wantCount
-	const testCases: [goLanguageServer.GoplsOptOutConfig, string, number, number][] = [
+	const today = new Date();
+	const yesterday = new Date(today.valueOf() - 1000 * 60 * 60 * 24);
+
+	// testConfig, choice, wantCount
+	const testCases: [goLanguageServer.GoplsOptOutConfig, string, number][] = [
 		// No saved config, different choices in the first dialog box.
-		[{}, 'Enable', undefined, 1],
-		[{}, 'Not now', undefined, 1],
-		[{}, 'Never', 1, 2],
-		[{}, 'Never', 0, 1],
-		[{}, 'Never', undefined, -1], // Non-deterministic. Skip callCount check.
-		// Saved config, doesn't matter what the user chooses.
-		[{ prompt: false }, '', undefined, 0],
-		[{ prompt: false, lastDatePrompted: new Date() }, '', undefined, 0],
-		[{ prompt: true }, '', undefined, 1],
-		[{ prompt: true, lastDatePrompted: new Date() }, '', undefined, 0]
+		[{}, 'Yes', 1],
+		[{}, 'No', 1],
+		[{}, '', 1],
+		[{ lastDatePrompted: new Date('2020-04-02') }, '', 1],
+		[{ lastDatePrompted: yesterday }, '', 0],
+		[{ prompt: false }, '', 0],
+		[{ prompt: false, lastDatePrompted: new Date('2020-04-02') }, '', 0],
+		[{ prompt: false, lastDatePrompted: yesterday }, '', 0],
+		[{ prompt: true }, '', 1],
+		[{ prompt: true, lastDatePrompted: new Date('2020-04-02') }, 'Yes', 1],
+		[{ prompt: true, lastDatePrompted: yesterday }, '', 0]
 	];
 
-	testCases.map(async ([testConfig, choice, probability, wantCount], i) => {
+	testCases.map(async ([testConfig, choice, wantCount], i) => {
 		test(`opt out: ${i}`, async () => {
 			const stub = sandbox.stub(vscode.window, 'showInformationMessage').resolves({ title: choice });
 			const getGoplsOptOutConfigStub = sandbox.stub(goLanguageServer, 'getGoplsOptOutConfig').returns(testConfig);
+			const flushGoplsOptOutConfigStub = sandbox.stub(goLanguageServer, 'flushGoplsOptOutConfig');
+			sandbox.stub(vscode.env, 'openExternal').resolves(true);
 
-			await goLanguageServer.promptAboutGoplsOptOut(probability);
-			if (wantCount >= 0) {
-				assert.strictEqual(stub.callCount, wantCount);
-			}
+			await goLanguageServer.promptAboutGoplsOptOut();
+			assert.strictEqual(stub.callCount, wantCount, 'unexpected call count');
 			sandbox.assert.called(getGoplsOptOutConfigStub);
+			sandbox.assert.calledOnce(flushGoplsOptOutConfigStub);
+			const got = flushGoplsOptOutConfigStub.getCall(0).args[0];
+			if (choice === 'Yes') assert.strictEqual(got.prompt, false, 'unexpected prompt config stored');
+			if (wantCount > 0)
+				assert(
+					got.lastDatePrompted >= today,
+					`unexpected lastDatePrompted: ${JSON.stringify(got.lastDatePrompted)}`
+				);
 		});
 	});
 });
diff --git a/test/gopls/update.test.ts b/test/gopls/update.test.ts
index c776588..f56b19b 100644
--- a/test/gopls/update.test.ts
+++ b/test/gopls/update.test.ts
@@ -9,7 +9,7 @@
 import assert from 'assert';
 import * as vscode from 'vscode';
 import { getGoConfig } from '../../src/config';
-import * as lsp from '../../src/goLanguageServer';
+import * as lsp from '../../src/language/goLanguageServer';
 import * as goInstallTools from '../../src/goInstallTools';
 import { getTool, Tool } from '../../src/goTools';
 import { getCheckForToolsUpdatesConfig as getCheckForToolUpdatesConfig } from '../../src/util';
diff --git a/test/integration/extension.test.ts b/test/integration/extension.test.ts
index 567b0f5..00c72e5 100644
--- a/test/integration/extension.test.ts
+++ b/test/integration/extension.test.ts
@@ -15,8 +15,8 @@
 import { getGoConfig, getGoplsConfig } from '../../src/config';
 import { FilePatch, getEdits, getEditsFromUnifiedDiffStr } from '../../src/diffUtils';
 import { check } from '../../src/goCheck';
-import { GoDefinitionProvider } from '../../src/goDeclaration';
-import { GoHoverProvider } from '../../src/goExtraInfo';
+import { GoDefinitionProvider } from '../../src/language/legacy/goDeclaration';
+import { GoHoverProvider } from '../../src/language/legacy/goExtraInfo';
 import { runFillStruct } from '../../src/goFillStruct';
 import {
 	generateTestCurrentFile,
@@ -25,14 +25,18 @@
 } from '../../src/goGenerateTests';
 import { getTextEditForAddImport, listPackages } from '../../src/goImport';
 import { updateGoVarsFromConfig } from '../../src/goInstallTools';
-import { buildLanguageServerConfig } from '../../src/goLanguageServer';
+import { buildLanguageServerConfig } from '../../src/language/goLanguageServer';
 import { goLint } from '../../src/goLint';
-import { documentSymbols, GoDocumentSymbolProvider, GoOutlineImportsOptions } from '../../src/goOutline';
+import {
+	documentSymbols,
+	GoDocumentSymbolProvider,
+	GoOutlineImportsOptions
+} from '../../src/language/legacy/goOutline';
 import { getAllPackages } from '../../src/goPackages';
 import { goPlay } from '../../src/goPlayground';
-import { GoSignatureHelpProvider } from '../../src/goSignature';
-import { GoCompletionItemProvider } from '../../src/goSuggest';
-import { getWorkspaceSymbols } from '../../src/goSymbol';
+import { GoSignatureHelpProvider } from '../../src/language/legacy/goSignature';
+import { GoCompletionItemProvider } from '../../src/language/legacy/goSuggest';
+import { getWorkspaceSymbols } from '../../src/language/legacy/goSymbol';
 import { testCurrentFile } from '../../src/goTest';
 import {
 	getBinPath,
diff --git a/test/integration/goDebugConfiguration.test.ts b/test/integration/goDebugConfiguration.test.ts
index a59926a..97aa335 100644
--- a/test/integration/goDebugConfiguration.test.ts
+++ b/test/integration/goDebugConfiguration.test.ts
@@ -7,7 +7,7 @@
 import path = require('path');
 import sinon = require('sinon');
 import vscode = require('vscode');
-import { getGoConfig } from '../../src/config';
+import { getGoConfig, extensionInfo } from '../../src/config';
 import { GoDebugConfigurationProvider } from '../../src/goDebugConfiguration';
 import { updateGoVarsFromConfig } from '../../src/goInstallTools';
 import { rmdirRecursive } from '../../src/util';
@@ -915,7 +915,7 @@
 		assert.strictEqual(resolvedConfig['debugAdapter'], 'dlv-dap');
 	});
 
-	test("default debugAdapter for remote mode should be always 'legacy'", async () => {
+	test("default debugAdapter for remote mode should be 'legacy' when not in Preview mode", async () => {
 		const config = {
 			name: 'Attach',
 			type: 'go',
@@ -925,7 +925,7 @@
 			cwd: '/path'
 		};
 
-		const want = 'legacy'; // Remote mode defaults to legacy mode.
+		const want = extensionInfo.isPreview ? 'dlv-dap' : 'legacy';
 		await debugConfigProvider.resolveDebugConfiguration(undefined, config);
 		const resolvedConfig = config as any;
 		assert.strictEqual(resolvedConfig['debugAdapter'], want);
diff --git a/test/integration/goExplorer.test.ts b/test/integration/goExplorer.test.ts
new file mode 100644
index 0000000..4afb9c3
--- /dev/null
+++ b/test/integration/goExplorer.test.ts
@@ -0,0 +1,66 @@
+/*---------------------------------------------------------
+ * Copyright 2022 The Go Authors. All rights reserved.
+ * Licensed under the MIT License. See LICENSE in the project root for license information.
+ *--------------------------------------------------------*/
+
+import assert from 'assert';
+import path from 'path';
+import { TreeItem, Uri, window, workspace } from 'vscode';
+import { getGoConfig, getGoplsConfig } from '../../src/config';
+
+import { GoExplorerProvider } from '../../src/goExplorer';
+import { getConfiguredTools } from '../../src/goTools';
+import { getGoVersion } from '../../src/util';
+import { resolveHomeDir } from '../../src/utils/pathUtils';
+import { MockExtensionContext } from '../mocks/MockContext';
+
+suite('GoExplorerProvider', () => {
+	const fixtureDir = path.join(__dirname, '../../../test/testdata/baseTest');
+	const ctx = MockExtensionContext.new();
+	let explorer: GoExplorerProvider;
+
+	suiteSetup(async () => {
+		explorer = GoExplorerProvider.setup(ctx);
+		const uri = Uri.file(path.join(fixtureDir, 'test.go'));
+		await workspace.openTextDocument(uri);
+		await window.showTextDocument(uri);
+	});
+
+	suiteTeardown(() => {
+		ctx.teardown();
+	});
+
+	test('env tree', async () => {
+		const [env] = await explorer.getChildren();
+		assert.strictEqual(env.label, 'env');
+		assert.strictEqual(env.contextValue, 'go:explorer:envtree');
+	});
+
+	test('env tree items', async () => {
+		const [env] = await explorer.getChildren();
+		const [goenv, gomod] = (await explorer.getChildren(env)) as { key: string; value: string }[];
+		assert.strictEqual(goenv.key, 'GOENV');
+		assert.strictEqual(gomod.key, 'GOMOD');
+		assert.strictEqual(resolveHomeDir(gomod.value), `${fixtureDir}/go.mod`);
+	});
+
+	test('tools tree', async () => {
+		const [, tools] = await explorer.getChildren();
+		assert(tools.label === 'tools');
+		assert.strictEqual(tools.contextValue, 'go:explorer:tools');
+	});
+
+	test('tools tree items', async () => {
+		const goVersion = await getGoVersion();
+		const allTools = getConfiguredTools(goVersion, getGoConfig(), getGoplsConfig());
+		const expectTools = allTools.map((t) => t.name);
+		const [, tools] = await explorer.getChildren();
+		const items = (await explorer.getChildren(tools)) as TreeItem[];
+		for (const idx in items) {
+			assert(
+				items[idx].label.toString().startsWith(expectTools[idx]),
+				`Unexpected tool tree item with label "${items[idx].label}"`
+			);
+		}
+	});
+});
diff --git a/test/integration/goTest.resolve.test.ts b/test/integration/goTest.resolve.test.ts
index a18e65c..93b89d2 100644
--- a/test/integration/goTest.resolve.test.ts
+++ b/test/integration/goTest.resolve.test.ts
@@ -40,6 +40,14 @@
 				},
 				expect: ['file:///src/proj?module']
 			},
+			'Module with leading comments': {
+				workspace: ['/src/proj'],
+				files: {
+					'/src/proj/go.mod': '// Example comment\nmodule test',
+					'/src/proj/main.go': 'package main'
+				},
+				expect: ['file:///src/proj?module']
+			},
 			'Basic workspace': {
 				workspace: ['/src/proj'],
 				files: {
diff --git a/test/testdata/vuln/go.mod b/test/testdata/vuln/go.mod
new file mode 100644
index 0000000..332a53a
--- /dev/null
+++ b/test/testdata/vuln/go.mod
@@ -0,0 +1,7 @@
+module github.com/golang/vscode-go/test/testdata/vuln
+
+go 1.14
+
+require (
+	golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c
+)
diff --git a/test/testdata/vuln/go.sum b/test/testdata/vuln/go.sum
new file mode 100644
index 0000000..0fb6f3f
--- /dev/null
+++ b/test/testdata/vuln/go.sum
@@ -0,0 +1,2 @@
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c h1:qgOY6WgZOaTkIIMiVjBQcw93ERBE4m30iBm00nkL0i8=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/test/testdata/vuln/test.go b/test/testdata/vuln/test.go
new file mode 100644
index 0000000..902850f
--- /dev/null
+++ b/test/testdata/vuln/test.go
@@ -0,0 +1,12 @@
+package main
+
+import (
+	"fmt"
+
+	"golang.org/x/text/language"
+)
+
+func main() {
+	tag, _ := language.Parse("hello")
+	fmt.Println(tag)
+}
diff --git a/tools/allTools.ts.in b/tools/allTools.ts.in
index 6d614d7..7088281 100644
--- a/tools/allTools.ts.in
+++ b/tools/allTools.ts.in
@@ -25,7 +25,7 @@
 		name: 'go-outline',
 		importPath: 'github.com/ramya-rao-a/go-outline',
 		modulePath: 'github.com/ramya-rao-a/go-outline',
-		replacedByGopls: false, // TODO(github.com/golang/vscode-go/issues/1020): replace with Gopls.
+		replacedByGopls: true,
 		isImportant: true,
 		description: 'Go to symbol in file' // GoDocumentSymbolProvider, used by 'run test' codelens
 	},
diff --git a/tools/docs2wiki/main.go b/tools/docs2wiki/main.go
new file mode 100644
index 0000000..baa41e4
--- /dev/null
+++ b/tools/docs2wiki/main.go
@@ -0,0 +1,130 @@
+//go:build !windows
+// +build !windows
+
+// Tool docs2wiki rewrites links in ./docs/* to wiki link format.
+// This program may call the 'diff' tool which may be missing on Windows.
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io/fs"
+	"io/ioutil"
+	"net/url"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"regexp"
+	"strings"
+)
+
+var writeFlag = flag.Bool("w", false, "Overwrite new file contents to disk.")
+
+func main() {
+	flag.Parse()
+
+	if len(flag.Args()) != 1 {
+		errorf("Usage: %v <dir>", os.Args[0])
+		os.Exit(1)
+	}
+	if err := rewriteLinks(flag.Arg(0), *writeFlag); err != nil {
+		errorf("failed to rewrite links: %v", err)
+		os.Exit(1)
+	}
+}
+
+func rewriteLinks(dir string, overwrite bool) error {
+	return filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
+		if err != nil {
+			return err
+		}
+		if d.IsDir() {
+			return nil
+		}
+		name := d.Name()
+		if filepath.Ext(name) != ".md" {
+			return nil
+		}
+
+		errorf("processing %v... %v", name, path)
+
+		data, err := ioutil.ReadFile(path)
+		if err != nil {
+			return fmt.Errorf("failed to read file %v: %w", name, err)
+		}
+		converted := stripTitleInPage(data)
+		converted = markdownLink2WikiLink(converted)
+		if overwrite {
+			return ioutil.WriteFile(path, converted, 0644)
+		}
+		tmp, err := writeToTempFile(converted)
+		if err != nil {
+			return fmt.Errorf("failed to write to temp file for diff: %w", err)
+		}
+		defer os.Remove(tmp)
+		diff(path, tmp)
+		return nil
+	})
+}
+
+func diff(f1, f2 string) {
+	cmd := exec.Command("diff", f1, f2)
+	cmd.Stderr = os.Stderr
+	cmd.Stdout = os.Stdout
+	if err := cmd.Run(); err != nil {
+		errorf("failed diff %v %v: %v", f1, f2, err)
+	}
+}
+
+func writeToTempFile(content []byte) (filename string, err error) {
+	dst, err := ioutil.TempFile("", "tmp")
+	if err != nil {
+		return "", fmt.Errorf("failed to write to a temporary file for diff: %v", err)
+	}
+	defer func() {
+		if err == nil {
+			err = dst.Close()
+		}
+	}()
+
+	dst.Write(content)
+	return dst.Name(), nil
+}
+
+func stripTitleInPage(src []byte) []byte {
+	// remove the first line if it starts with "#"
+	if len(src) == 0 || src[0] != '#' {
+		return src
+	}
+	index := bytes.Index(src, []byte("\n"))
+	if index < 0 {
+		return src
+	}
+	return src[index+1:]
+}
+
+// find pattern like '](link.md)'
+var markdownLinkRE = regexp.MustCompile(`\]\(\S+(:?\.md|\.md#[^)]*)\)`)
+
+func markdownLink2WikiLink(src []byte) []byte {
+	return markdownLinkRE.ReplaceAllFunc(src, func(s []byte) []byte {
+
+		part := string(s[2 : len(s)-1]) // remove leading `](` and ending `)`
+		u, err := url.Parse(part)
+		if err != nil {
+			return s
+		}
+		if u.Scheme != "" {
+			return s
+		}
+		u.Path = strings.TrimSuffix(u.Path, ".md")
+		b := &bytes.Buffer{}
+		fmt.Fprintf(b, "](%s)", u.String())
+		return b.Bytes()
+	})
+}
+
+func errorf(format string, a ...interface{}) {
+	fmt.Fprintf(os.Stderr, format+"\n", a...)
+}
diff --git a/tools/docs2wiki/main_test.go b/tools/docs2wiki/main_test.go
new file mode 100644
index 0000000..19b5471
--- /dev/null
+++ b/tools/docs2wiki/main_test.go
@@ -0,0 +1,84 @@
+//go:build !windows
+// +build !windows
+
+// Tool docs2wiki rewrites links in ./docs/* to wiki link format.
+package main
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestRewriteLinks(t *testing.T) {
+	for _, tt := range []struct{ filename, in, want string }{
+		{filename: "doc.md", in: markdownStyle, want: wikiStyle},
+		{filename: "hasTitle.md", in: "# Redundant Title \n" + markdownStyle, want: wikiStyle},
+		{filename: "sub/doc.md", in: markdownStyle, want: wikiStyle},
+		{filename: "doc.txt", in: markdownStyle, want: markdownStyle},
+	} {
+		t.Run(tt.filename, func(t *testing.T) {
+			// prepareTestData writes tt.in to tt.filename.
+			dir := prepareTestData(t, tt.filename, tt.in)
+			defer os.RemoveAll(dir)
+
+			// Use overwrite=true so `rewriteLinks` overwrite the original file
+			// which we will read back for comparison against tt.want.
+			// With overwrite=false, rewriteLinks just prints out the diff
+			// which will be difficult to test.
+			err := rewriteLinks(dir, true)
+			if err != nil {
+				t.Fatal(err)
+			}
+			// rewriteLinks overwrites the original file,
+			// so reread the content for comparison.
+			got, err := ioutil.ReadFile(filepath.Join(dir, tt.filename))
+			if err != nil {
+				t.Fatal(err)
+			}
+			if diff := cmp.Diff(tt.want, string(got)); diff != "" {
+				t.Errorf("(-want +got): %v", diff)
+			}
+		})
+	}
+}
+
+// prepareTestData writes a file in a temp directory and returns the temp
+// directory path.
+func prepareTestData(t *testing.T, file, content string) (dir string) {
+	dir, err := ioutil.TempDir("", "docs2wiki_test")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	fname := filepath.Join(dir, filepath.FromSlash(file))
+	os.MkdirAll(filepath.Dir(fname), 0755) // create intermediate dirs
+	if err := ioutil.WriteFile(fname, []byte(content), 0644); err != nil {
+		os.RemoveAll(dir)
+		t.Fatal(err)
+	}
+	return dir
+}
+
+var markdownStyle = `
+[This changes](doc.md)
+ [This changes too](./doc.md)
+   [This also changes](foo/doc.md)
+[Fragment works](foo.md#this-is-a-title)
+[A](doc.md)  [B](doc.md)  [C](doc.txt)
+
+[This doesn't change](https://go.dev/foo.md)
+[Untouchable.md](foo)`
+
+var wikiStyle = `
+[This changes](doc)
+ [This changes too](./doc)
+   [This also changes](foo/doc)
+[Fragment works](foo#this-is-a-title)
+[A](doc)  [B](doc)  [C](doc.txt)
+
+[This doesn't change](https://go.dev/foo.md)
+[Untouchable.md](foo)`
diff --git a/tools/generate.go b/tools/generate.go
index 41535a9..d806abe 100644
--- a/tools/generate.go
+++ b/tools/generate.go
@@ -6,10 +6,12 @@
 // the gopls's API and generate documentation from it.
 //
 // To update documentation based on the current package.json:
-//    go run tools/generate.go
+//
+//	go run tools/generate.go
 //
 // To update package.json and generate documentation.
-//    go run tools/generate.go -gopls
+//
+//	go run tools/generate.go -gopls
 package main
 
 import (
diff --git a/tools/installtools/main.go b/tools/installtools/main.go
index 8ada3cc..f33f19e 100644
--- a/tools/installtools/main.go
+++ b/tools/installtools/main.go
@@ -11,27 +11,50 @@
 	"strings"
 )
 
+// finalVersion encodes the fact that the specified tool version
+// is the known last version that can be buildable with goMinorVersion.
+type finalVersion struct {
+	goMinorVersion int
+	version        string
+}
+
 var tools = []struct {
-	path    string
-	version string
-	dest    string
+	path string
+	dest string
+	// versions is a list of supportedVersions sorted by
+	// goMinorVersion. If we want to pin a tool's version
+	// add a fake entry with a large goMinorVersion
+	// value and the pinned tool version as the last entry.
+	// Nil of empty list indicates we can use the `latest` version.
+	versions []finalVersion
 }{
 	// TODO: auto-generate based on allTools.ts.in.
-	{"golang.org/x/tools/gopls", "v0.8.0-pre.3", ""}, // TODO: make this not hardcoded
-	{"github.com/acroca/go-symbols", "", ""},
-	{"github.com/cweill/gotests/gotests", "", ""},
-	{"github.com/davidrjenni/reftools/cmd/fillstruct", "", ""},
-	{"github.com/haya14busa/goplay/cmd/goplay", "", ""},
-	{"github.com/stamblerre/gocode", "", "gocode-gomod"},
-	{"github.com/mdempsky/gocode", "", ""},
-	{"github.com/ramya-rao-a/go-outline", "", ""},
-	{"github.com/rogpeppe/godef", "", ""},
-	{"github.com/sqs/goreturns", "", ""},
-	{"github.com/uudashr/gopkgs/v2/cmd/gopkgs", "", ""},
-	{"github.com/zmb3/gogetdoc", "", ""},
-	{"honnef.co/go/tools/cmd/staticcheck", "", ""},
-	{"golang.org/x/tools/cmd/gorename", "", ""},
-	{"github.com/go-delve/delve/cmd/dlv", "", ""},
+	{"golang.org/x/tools/gopls", "", nil},
+	{"github.com/acroca/go-symbols", "", nil},
+	{"github.com/cweill/gotests/gotests", "", nil},
+	{"github.com/davidrjenni/reftools/cmd/fillstruct", "", nil},
+	{"github.com/haya14busa/goplay/cmd/goplay", "", nil},
+	{"github.com/stamblerre/gocode", "gocode-gomod", nil},
+	{"github.com/mdempsky/gocode", "", nil},
+	{"github.com/ramya-rao-a/go-outline", "", nil},
+	{"github.com/rogpeppe/godef", "", nil},
+	{"github.com/sqs/goreturns", "", nil},
+	{"github.com/uudashr/gopkgs/v2/cmd/gopkgs", "", nil},
+	{"github.com/zmb3/gogetdoc", "", nil},
+	{"honnef.co/go/tools/cmd/staticcheck", "", []finalVersion{{16, "v0.2.2"}}},
+	{"golang.org/x/tools/cmd/gorename", "", nil},
+	{"github.com/go-delve/delve/cmd/dlv", "", nil},
+}
+
+// pickVersion returns the version to install based on the supported
+// version list.
+func pickVersion(goMinorVersion int, versions []finalVersion) string {
+	for _, v := range versions {
+		if goMinorVersion <= v.goMinorVersion {
+			return v.version
+		}
+	}
+	return "latest"
 }
 
 func main() {
@@ -39,17 +62,15 @@
 	if err != nil {
 		exitf("failed to find go version: %v", err)
 	}
+	if ver < 1 {
+		exitf("unsupported go version: 1.%v", ver)
+	}
+
 	bin, err := goBin()
 	if err != nil {
 		exitf("failed to determine go tool installation directory: %v", err)
 	}
-	if ver < 1 {
-		exitf("unsupported go version: 1.%v", ver)
-	} else if ver < 16 {
-		err = installTools(bin, "get")
-	} else {
-		err = installTools(bin, "install")
-	}
+	err = installTools(bin, ver)
 	if err != nil {
 		exitf("failed to install tools: %v", err)
 	}
@@ -100,17 +121,19 @@
 	return filepath.Join(gopaths[0], "bin"), nil
 }
 
-func installTools(binDir, installCmd string) error {
+func installTools(binDir string, goMinorVersion int) error {
+	installCmd := "install"
+	if goMinorVersion < 16 {
+		installCmd = "get"
+	}
+
 	dir := ""
 	if installCmd == "get" { // run `go get` command from an empty directory.
 		dir = os.TempDir()
 	}
 	env := append(os.Environ(), "GO111MODULE=on")
 	for _, tool := range tools {
-		ver := tool.version
-		if ver == "" {
-			ver = "latest"
-		}
+		ver := pickVersion(goMinorVersion, tool.versions)
 		path := tool.path + "@" + ver
 		cmd := exec.Command("go", installCmd, path)
 		cmd.Env = env
diff --git a/tools/installtools/main_test.go b/tools/installtools/main_test.go
new file mode 100644
index 0000000..99e0961
--- /dev/null
+++ b/tools/installtools/main_test.go
@@ -0,0 +1,42 @@
+// Binary installtools is a helper that installs Go tools extension tests depend on.
+package main
+
+import "testing"
+
+func Test_pickVersion(t *testing.T) {
+	tests := []struct {
+		name     string
+		versions []finalVersion
+		want     map[int]string
+	}{
+		{
+			name:     "nil",
+			versions: nil,
+			want:     map[int]string{15: "latest", 16: "latest", 17: "latest", 18: "latest"},
+		},
+		{
+			name: "one_entry",
+			versions: []finalVersion{
+				{16, "v0.2.2"},
+			},
+			want: map[int]string{15: "v0.2.2", 16: "v0.2.2", 17: "latest", 18: "latest"},
+		},
+		{
+			name: "two_entries",
+			versions: []finalVersion{
+				{16, "v0.2.2"},
+				{17, "v0.3.0"},
+			},
+			want: map[int]string{15: "v0.2.2", 16: "v0.2.2", 17: "v0.3.0", 18: "latest"},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			for goMinorVersion, want := range tt.want {
+				if got := pickVersion(goMinorVersion, tt.versions); got != want {
+					t.Errorf("pickVersion(go 1.%v) = %v, want %v", goMinorVersion, got, want)
+				}
+			}
+		})
+	}
+}