sync: merge microsoft/vscode-go@2dbccbe into master

Change-Id: I8d6f2f0e99efb23f3d701f39da75566f7d39186f
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..05009b8
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,93 @@
+name: build
+
+on: [push, pull_request]
+
+jobs:
+  build:
+    name: ${{ matrix.os }} ${{ matrix.version }}
+    runs-on: ${{ matrix.os }}
+    
+    if: "!contains(github.event.head_commit.message, 'SKIP CI')"
+    timeout-minutes: 20
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ubuntu-latest]
+        version: ['stable']
+
+    steps:
+      - name: Clone repository
+        uses: actions/checkout@v2
+
+      - name: Setup Node
+        uses: actions/setup-node@v1
+        with:
+          node-version: '10.x'
+
+      - name: Setup Go
+        uses: actions/setup-go@v1
+        with:
+         go-version: '1.14'
+
+      - name: Install dependencies
+        run: npm install
+      
+      - name: Compile
+        run: npm run vscode:prepublish
+
+      - name: Install Go tools (Modules mode)
+        run: |
+            go version
+            go get github.com/acroca/go-symbols \
+               github.com/davidrjenni/reftools/cmd/fillstruct \
+               github.com/haya14busa/goplay/cmd/goplay \
+               github.com/mdempsky/gocode \
+               github.com/sqs/goreturns \
+               github.com/uudashr/gopkgs/v2/cmd/gopkgs \
+               github.com/zmb3/gogetdoc \
+               golang.org/x/lint/golint \
+               golang.org/x/tools/cmd/gorename \
+               golang.org/x/tools/gopls
+        env:
+          GO111MODULE: on   
+          
+      - name: Install Go tools (GOPATH mode)
+        run: |
+          go version
+          go get github.com/cweill/gotests/... \
+            github.com/rogpeppe/godef \
+            github.com/ramya-rao-a/go-outline
+            # Because some tests depend on the source code checked in GOPATH. TODO: FIX THEM.
+        env:
+          GO111MODULE: off
+      
+      - name: Run unit tests
+        run: npm run unit-test
+        continue-on-error: true
+        
+      - name: Run tests
+        uses: GabrielBB/xvfb-action@v1.0
+        with:
+          run: npm run test
+        env:
+          CODE_VERSION: ${{ matrix.version }}
+
+  eslint:
+    runs-on: ubuntu-latest
+    if: "!contains(github.event.head_commit.message, 'SKIP CI')"
+
+    steps:
+      - name: Clone repository
+        uses: actions/checkout@v1
+
+      - name: Setup Node
+        uses: actions/setup-node@v1
+        with:
+          node-version: '10.x'
+
+      - name: Install Dependencies
+        run: 'npm install --frozen-lockfile'
+        shell: bash
+
+      - name: Lint check
+        run: npm run lint
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..eff44f1
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,84 @@
+name: release
+
+# Daily release on 15:00 UTC, monday-thursday.
+# Or, force to release by triggering repository_dispatch events by using
+#   curl -v -H "Accept: application/vnd.github.everest-preview+json" -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/golang/vscode-go/dispatches -d '{ "event_type": "force-release" }'
+on:
+  schedule:
+    - cron: "0 15 * * MON-THU"  # 15 UTC, monday-thursday daily
+  repository_dispatch:
+    types: [force-release]
+
+env:
+  GOPATH: /tmp/go
+  # Because some tests require explicit setting of GOPATH. TODO: FIX THEM.
+
+jobs:
+  release:
+    name: Release Nightly
+    runs-on: ubuntu-latest
+    timeout-minutes: 20
+
+    steps:
+      - name: Clone repository
+        uses: actions/checkout@v1
+
+      - name: Setup Node
+        uses: actions/setup-node@v1
+        with:
+          node-version: '10.x'
+
+      - name: Setup Go
+        uses: actions/setup-go@v1
+        with:
+         go-version: '1.14'
+
+      - name: Install dependencies
+        run: npm install
+
+      - name: Install Go tools (Modules mode)
+        run: |
+            go version
+            go get github.com/acroca/go-symbols \
+               github.com/davidrjenni/reftools/cmd/fillstruct \
+               github.com/haya14busa/goplay/cmd/goplay \
+               github.com/mdempsky/gocode \
+               github.com/sqs/goreturns \
+               github.com/uudashr/gopkgs/v2/cmd/gopkgs \
+               github.com/zmb3/gogetdoc \
+               golang.org/x/lint/golint \
+               golang.org/x/tools/cmd/gorename
+        env:
+          GO111MODULE: on
+
+      - name: Install Go tools (GOPATH mode)
+        run: |
+          go version
+          go get github.com/cweill/gotests/... \
+            github.com/rogpeppe/godef \
+            github.com/ramya-rao-a/go-outline
+            # Because some tests depend on the source code checked in GOPATH. TODO: FIX THEM.
+        env:
+          GO111MODULE: off
+
+      - name: Prepare Release
+        run: build/all.bash prepare_nightly
+
+      - name: Run unit tests
+        run: npm run unit-test
+        continue-on-error: true
+
+      - name: Run tests
+        uses: GabrielBB/xvfb-action@v1.0
+        with:
+          run: npm run test
+        env:
+          CODE_VERSION: 'insiders'
+
+      - name: Publish
+        if: github.ref == 'refs/heads/master' && github.repository == 'golang/vscode-go'
+        uses: lannonbr/vsce-action@704da577da0f27de5cdb4ae018374c2f08b5f523
+        with:
+          args: "publish -p $VSCE_TOKEN"
+        env:
+          VSCE_TOKEN: ${{ secrets.VSCE_TOKEN }}
diff --git a/.vscodeignore b/.vscodeignore
index 5f7ffd2..a0b223f 100644
--- a/.vscodeignore
+++ b/.vscodeignore
@@ -9,3 +9,4 @@
 **/tslint.json
 build/**/*
 docs/
+*.md.nightly
diff --git a/CHANGELOG.md.nightly b/CHANGELOG.md.nightly
new file mode 100644
index 0000000..f93a946
--- /dev/null
+++ b/CHANGELOG.md.nightly
@@ -0,0 +1,4 @@
+## 2020.3.x
+* Set the extension name for VS Code Go Nightly(`go-nightly`).
+* Pick up the pre-release version of `gopls` if available.
+* Disabled the telemetry report for VS Code Go.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..71e3b94
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,16 @@
+name: "vscode-go"
+description:
+    "Fork of github.com/Microsoft/vscode-go (the VSCode plugin for Go). "
+    "This fork is kept in sync with the upstream while includes "
+    "new features go-tools@google.com team is developing and experimenting. "
+    ""
+
+third_party {
+  url {
+    type: GIT
+    value: "https://github.com/Microsoft/vscode-go"
+  }
+  version: "master"
+  last_upgrade_date { year: 2020 month: 1 day: 8 }
+  license_type: NOTICE
+}
diff --git a/README.md.nightly b/README.md.nightly
new file mode 100644
index 0000000..0064e7e
--- /dev/null
+++ b/README.md.nightly
@@ -0,0 +1,27 @@
+# Go Nightly for VS Code
+
+> ### **ATTENTION**
+>**Go Nightly for VS Code** is the insider version of
+[VS Code Go extension](https://github.com/microsoft/vscode-go)
+for early feedback and testing. This extension works best with
+[VS Code Insiders](https://code.visualstudio.com/insiders).
+Go Nightly contains previews of new features and bug fixes that are still
+under review or testing, so can be unstable. If you are looking for the stable version,
+please use [the stable version](https://marketplace.visualstudio.com/items?itemName=ms-vscode.go) instead.
+>
+> **NOTE:**
+If you have both stable (aka "Go") and nightly version (aka "Go Nightly") installed,
+you MUST DISABLE one of them. Docs on how to disable an extension can be found
+[here](https://code.visualstudio.com/docs/editor/extension-gallery#_disable-an-extension).
+
+> ### Difference between VS Code Go and VS Code Go Nightly
+> - Go Nightly is maintained and released by Go Tools team at Google.
+> - Go Nightly is released more frequently than the stable version.
+> - Go Nightly includes features and bug fixes that are still under testing or not finalized yet.
+> - Go Nightly may use the latest pre-release versions of tools (e.g. `gopls`) instead of release versions.
+> - For now, Go and Go Nightly maintain separate repositories. Both repositories
+>   welcome all contributors. For contribution to Go Nightly repo, see the Go
+>   project's [contribution guide](https://golang.org/doc/contribute.html).
+>   Go team members who has signed the Microsoft CLA will send a syncing PR upstream to
+>   https://github.com/microsoft/vscode-go every two weeks.
+> - [Here](https://github.com/microsoft/vscode-go/compare/master...golang:master) is the full list of local modifications.
diff --git a/build/README.md b/build/README.md
new file mode 100644
index 0000000..cacccb2
--- /dev/null
+++ b/build/README.md
@@ -0,0 +1,88 @@
+## Continuous Integration Testing
+
+Currently we are using two separate CI systems to test all changes and pushed commits:
+Tests running in Google Cloud Build (GCB) and tests running with GitHub Action.
+It is a temporary setup; once GCB fully supports our desired workflow that works
+with the Go Git repository, we plan to use the GCB-based setup for CI.
+
+### Testing via GCB
+
+This workflow is triggered for Gerrit CLs (chosen by project members) and all
+the commits merged into the master branch.
+Note that our main repository is in `go.googlesource.com/vscode-go` and
+`github.com/golang/vscode-go` is a mirror of the Go Git repository.
+All PRs sent to `github.com/golang/vscode-go` will be converted as Gerrit CLs.
+Currently, the results of the CI Run are visible to only project members.
+We are working on improving this workflow - making the results visible to
+public and easily accessible through our Gerrit review UI.
+
+- `build/cloudbuild.yaml`, `build/all.bash` - define the GCB workflow.
+- `build/cloudbuild.container.yaml`, `build/Dockerfile` - define the Docker container used for CI.
+
+Project members (currently restricted to our GCP project members) can manually
+trigger cloud build and test their locally made changes. 
+Follow the [GCB instruction](https://cloud.google.com/cloud-build/docs/running-builds/start-build-manually)
+to set up the environment and tools, and then run
+
+```
+$ gcloud builds submit --config=build/cloudbuild.yaml
+```
+
+In order to modify and rebuild the docker container image, run
+
+```
+$ gcloud builds submit --config=build/cloudbuild.container.yaml
+```
+
+### Testing via GitHub Action
+
+This is the workflow triggered for every PR and commit made to our mirror repository in github.com/golang/vscode-go. We are using this CI to run tests
+in the platforms which GCB does not support yet, and allow contributors 
+to see the test results for their PRs. This workflow is not triggered by
+CLs sent via Gerrit yet.
+
+Until GCB-based CI is ready for general use, we recommend contributors
+to send PRs to github.com/golang/vscode-go as described in
+[the Go project contribution guide](https://golang.org/doc/contribute.html#sending_a_change_github). The results will be posted to the PR request.
+
+- `.github/workflows/ci.yml` - define the github action based CI workflow.
+
+## Nightly Release
+
+A new version is released based on what is committed on the `master` branch,
+at least once a day between Monday and Thursday. If there is no new commit,
+release does not happen. This nightly extension is a separate extension from
+the official Go extension, and is available at [the VS Code market place](https://marketplace.visualstudio.com/items?itemName=golang.go-nightly).
+
+The version number encodes the last commit timestamp of the master branch
+in the format of `YYYY.[M]M.[D]DHH`. For example, version 2020.3.702 indicates
+the extension is built with the last commit committed at ~2AM 2020/03/07 (UTC).
+
+- `.github/workflows/release.yml, build/all.bash` - define the daily release process.
+
+## Sync with upstream
+
+### Merging commits from upstream
+
+This is done manually by project members, probably before each nightly release.
+
+Once we consolidate the two repositories, this process becomes unnecessary.
+
+The merge script will create a Gerrit CL for merge and issue the GCB based test workflow. 
+The remote `origin` should be set to `https://go.googlesource.com/vscode-go`.
+Make sure you have access to the GCB project and `gcloud` tool 
+is available.
+
+```
+$ build/merge.sh
+```
+
+In case of conflicts, you will need to check out the cl, fix, and upload the
+updated cl again following the usual Gerrit CL workflow.
+
+### Reflecting commits to upstream
+
+Once the feature or bug fix tested with Nightly extension is stablized, create
+a PR to the upstream (github.com/microsoft/vscode-go).
+Please make sure to include all the gerrit CL numbers so the upstream code
+reviewers can find reference to all prior discussion.
diff --git a/build/all.bash b/build/all.bash
index 19ef780..6b72279 100755
--- a/build/all.bash
+++ b/build/all.bash
@@ -55,6 +55,33 @@
   docker run --workdir=/workspace -v "$(pwd):/workspace" vscode-test-env ci
 }
 
+prepare_nightly() {
+  # Version format: YYYY.MM.DDHH based on the latest commit timestamp.
+  # e.g. 2020.1.510 is the version built based on a commit that was made
+  #      on 2020/01/05 10:00
+  local VER=`git log -1 --format=%cd --date="format:%Y.%-m.%-d%H"`
+  local COMMIT=`git log -1 --format=%H`
+  echo "**** Preparing nightly release : $VER ***"
+
+  # Update package.json
+  (cat package.json | jq --arg VER "${VER}" '
+.version=$VER |
+.preview=true |
+.name="go-nightly" |
+.displayName="Go Nightly" |
+.publisher="golang" |
+.description="Rich Go language support for Visual Studio Code (Nightly)" |
+.author.name="Go Team at Google" |
+.repository.url="https://github.com/golang/vscode-go" |
+.bugs.url="https://github.com/golang/vscode-go/issues"
+') > /tmp/package.json && mv /tmp/package.json package.json
+
+  # Replace CHANGELOG.md with CHANGELOG.md.nightly + Release commit info.
+  printf "**Release ${VER} @ ${COMMIT}** \n\n" | cat - CHANGELOG.md.nightly > /tmp/CHANGELOG.md.new && mv /tmp/CHANGELOG.md.new CHANGELOG.md
+  # Replace the heading of README.md with the heading for Go Nightly.
+  sed '/^# Go for Visual Studio Code$/d' README.md | cat README.md.nightly - > /tmp/README.md.new && mv /tmp/README.md.new README.md
+}
+
 main() {
   cd "$(root_dir)"  # always run from the script root.
   case "$1" in
@@ -74,9 +101,12 @@
       setup_virtual_display
       run_test
       ;;
+    "prepare_nightly")
+      prepare_nightly
+      ;;
     *)
       usage
       exit 2
   esac
 }
-main $@
\ No newline at end of file
+main $@
diff --git a/build/cloudbuild.container.yaml b/build/cloudbuild.container.yaml
new file mode 100644
index 0000000..12d8ad7
--- /dev/null
+++ b/build/cloudbuild.container.yaml
@@ -0,0 +1,5 @@
+steps:
+- name: 'gcr.io/cloud-builders/docker'
+  args: ['build', '-t', 'gcr.io/$PROJECT_ID/vscode-test-env', '-f', 'build/Dockerfile', '.']
+images:
+    - 'gcr.io/$PROJECT_ID/vscode-test-env'
diff --git a/build/cloudbuild.yaml b/build/cloudbuild.yaml
new file mode 100644
index 0000000..a678121
--- /dev/null
+++ b/build/cloudbuild.yaml
@@ -0,0 +1,9 @@
+steps:
+- name: 'gcr.io/$PROJECT_ID/vscode-test-env'
+  entrypoint: "./build/all.bash"
+  args: ['ci']
+  env:
+    - 'BUILD=$BUILD_ID'
+    - 'PROJECT=$PROJECT_ID'
+    - 'REV=$REVISION_ID'
+timeout: 600s
diff --git a/build/merge.bash b/build/merge.bash
new file mode 100755
index 0000000..971f10a
--- /dev/null
+++ b/build/merge.bash
@@ -0,0 +1,34 @@
+#! /bin/bash
+set -euo pipefail
+
+# In order to sync with upstream, run merge.bash
+
+# TODO(hyangah): commands for building docker container and running tests locally with docker run.
+root_dir() {
+  local script_name=$(readlink -f "${0}")
+  local script_dir=$(dirname "${script_name}")
+  local parent_dir=$(dirname "${script_dir}")
+  echo "${parent_dir}"
+}
+
+ROOT="$(root_dir)"
+cd "${ROOT}"  # always run from the root directory.
+
+WORKTREE="$(mktemp -d)"
+BRANCH="sync/merge-upstream-$(date +%Y%m%d%H%M%S)"
+
+git fetch
+git worktree add --track -b "${BRANCH}" "${WORKTREE}" origin/master
+
+cd "${WORKTREE}"
+export GIT_GOFMT_HOOK=off
+git merge --no-commit "origin/upstream" || echo "Ignoring conflict..."
+
+COMMIT=`git log --format=%h -n 1 "origin/upstream"`
+
+gcloud builds submit --config=build/cloudbuild.yaml || echo "Build failed. Please address the issue..."
+
+git commit -m "sync: merge microsoft/vscode-go@${COMMIT} into master"
+
+git codereview mail HEAD
+cd - && git worktree remove "${WORKTREE}"
diff --git a/package-lock.json b/package-lock.json
index a3fbee8..7640a7b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
-  "name": "Go",
-  "version": "0.14.2",
+  "name": "go-nightly",
+  "version": "0.0.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -75,6 +75,15 @@
       "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
       "dev": true
     },
+    "@types/adm-zip": {
+      "version": "0.4.33",
+      "resolved": "https://registry.npmjs.org/@types/adm-zip/-/adm-zip-0.4.33.tgz",
+      "integrity": "sha512-WM0DCWFLjXtddl0fu0+iN2ZF+qz8RF9RddG5OSy/S90AQz01Fu8lHn/3oTIZDxvG8gVcnBLAHMHOdBLbV6m6Mw==",
+      "dev": true,
+      "requires": {
+        "@types/node": "*"
+      }
+    },
     "@types/deep-equal": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/@types/deep-equal/-/deep-equal-1.0.1.tgz",
@@ -155,6 +164,12 @@
       "integrity": "sha512-WJZtZlinE3meRdH+I7wTsIhpz/GLhqEQwmPGeh4s1irWLwMzCeTV8WZ+pgPTwrDXoafVUWwo1LiZ9HJVHFlJSQ==",
       "dev": true
     },
+    "adm-zip": {
+      "version": "0.4.14",
+      "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.14.tgz",
+      "integrity": "sha512-/9aQCnQHF+0IiCl0qhXoK7qs//SwYE7zX8lsr/DNk1BRAHYxeLZPL4pguwK29gUEqasYQjqPtEpDRSWEkdHn9g==",
+      "dev": true
+    },
     "agent-base": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
diff --git a/package.json b/package.json
index 9799f2b..dec77ba 100644
--- a/package.json
+++ b/package.json
@@ -1,11 +1,13 @@
 {
-  "name": "Go",
-  "version": "0.14.2",
-  "publisher": "ms-vscode",
-  "description": "Rich Go language support for Visual Studio Code",
+  "name": "go-nightly",
+  "displayName": "Go Nightly",
+  "version": "0.0.0",
+  "publisher": "golang",
+  "description": "Rich Go language support for Visual Studio Code (Nightly)",
   "author": {
-    "name": "Microsoft Corporation - Development Labs"
+    "name": "Go Team at Google"
   },
+  "preview": true,
   "license": "MIT",
   "icon": "images/go-logo-blue.png",
   "categories": [
@@ -22,7 +24,10 @@
   "private": true,
   "repository": {
     "type": "git",
-    "url": "https://github.com/Microsoft/vscode-go.git"
+    "url": "https://github.com/golang/vscode-go"
+  },
+  "bugs": {
+    "url": "https://github.com/golang/vscode-go/issues"
   },
   "keywords": [
     "multi-root ready"
@@ -40,6 +45,7 @@
   },
   "extensionDependencies": [],
   "dependencies": {
+    "deep-equal": "^2.0.2",
     "diff": "^4.0.2",
     "json-rpc2": "^1.0.2",
     "moment": "^2.24.0",
@@ -49,10 +55,12 @@
     "vscode-debugprotocol": "^1.40.0",
     "vscode-extension-telemetry": "^0.1.2",
     "vscode-languageclient": "6.1.0",
-    "web-request": "^1.0.7",
-    "deep-equal": "^2.0.2"
+    "web-request": "^1.0.7"
   },
   "devDependencies": {
+    "adm-zip": "^0.4.14",
+    "@types/adm-zip": "^0.4.33",
+    "@types/deep-equal": "^1.0.1",
     "@types/fs-extra": "^8.1.0",
     "@types/glob": "^7.1.1",
     "@types/mocha": "^7.0.2",
@@ -67,8 +75,7 @@
     "sinon": "^9.0.2",
     "tslint": "^6.1.1",
     "typescript": "^3.8.3",
-    "vscode-test": "^1.3.0",
-    "@types/deep-equal": "^1.0.1"
+    "vscode-test": "^1.3.0"
   },
   "engines": {
     "vscode": "^1.41.0"
@@ -1127,6 +1134,16 @@
           },
           "description": "Use this setting to enable/disable experimental features from the language server."
         },
+        "go.trace.server": {
+          "type": "string",
+          "enum": [
+            "off",
+            "messages",
+            "verbose"
+          ],
+          "default": "off",
+          "description": "Trace the communication between VS Code and the Go language server."
+        },
         "go.useGoProxyToCheckForToolUpdates": {
           "type": "boolean",
           "default": true,
diff --git a/src/avlTree.ts b/src/avlTree.ts
index 632ad90..56ac9ce 100644
--- a/src/avlTree.ts
+++ b/src/avlTree.ts
@@ -41,7 +41,7 @@
 	 * @param key The key of the new node.
 	 * @param value The value of the new node.
 	 */
-	constructor(public key: K, public value: V) {}
+	constructor(public key: K, public value: V) { }
 
 	/**
 	 * Convenience function to get the height of the left child of the node,
diff --git a/src/goCheck.ts b/src/goCheck.ts
index 2cc110a..597e018 100644
--- a/src/goCheck.ts
+++ b/src/goCheck.ts
@@ -8,7 +8,7 @@
 import path = require('path');
 import vscode = require('vscode');
 import { goBuild } from './goBuild';
-import { parseLanguageServerConfig } from './goLanguageServer';
+import { buildLanguageServerConfig } from './goLanguageServer';
 import { goLint } from './goLint';
 import { buildDiagnosticCollection, lintDiagnosticCollection, vetDiagnosticCollection } from './goMain';
 import { isModSupported } from './goModules';
@@ -59,7 +59,7 @@
 
 	// If a user has enabled diagnostics via a language server,
 	// then we disable running build or vet to avoid duplicate errors and warnings.
-	const lspConfig = parseLanguageServerConfig();
+	const lspConfig = buildLanguageServerConfig();
 	const disableBuildAndVet = lspConfig.enabled && lspConfig.features.diagnostics;
 
 	let testPromise: Thenable<boolean>;
diff --git a/src/goInstallTools.ts b/src/goInstallTools.ts
index 495913d..2475f32 100644
--- a/src/goInstallTools.ts
+++ b/src/goInstallTools.ts
@@ -11,6 +11,7 @@
 import { SemVer } from 'semver';
 import vscode = require('vscode');
 import { getLanguageServerToolPath } from './goLanguageServer';
+import { restartLanguageServer } from './goMain';
 import { envPath, getToolFromToolPath } from './goPath';
 import { hideGoStatus, outputChannel, showGoStatus } from './goStatus';
 import {
@@ -23,7 +24,8 @@
 	getTool,
 	hasModSuffix,
 	isGocode,
-	Tool
+	Tool,
+	ToolAtVersion
 } from './goTools';
 import {
 	getBinPath,
@@ -31,6 +33,7 @@
 	getGoConfig,
 	getGoVersion,
 	getTempFilePath,
+	getToolsEnvVars,
 	getToolsGopath,
 	GoVersion,
 	resolvePath
@@ -91,14 +94,6 @@
 }
 
 /**
- * ToolAtVersion is a Tool with version annotation.
- * Lack of version implies the latest version
- */
-export interface ToolAtVersion extends Tool {
-	version?: SemVer;
-}
-
-/**
  * Installs given array of missing tools. If no input is given, the all tools are installed
  *
  * @param missing array of tool names and optionally, their versions to be installed.
@@ -119,14 +114,12 @@
 
 	// http.proxy setting takes precedence over environment variables
 	const httpProxy = vscode.workspace.getConfiguration('http', null).get('proxy');
-	let envForTools = Object.assign({}, process.env);
+	const envForTools = Object.assign({}, process.env, getToolsEnvVars());
 	if (httpProxy) {
-		envForTools = Object.assign({}, process.env, {
-			http_proxy: httpProxy,
-			HTTP_PROXY: httpProxy,
-			https_proxy: httpProxy,
-			HTTPS_PROXY: httpProxy
-		});
+		envForTools['http_proxy'] = httpProxy;
+		envForTools['HTTP_PROXY'] = httpProxy;
+		envForTools['https_proxy'] = httpProxy;
+		envForTools['HTTPS_PROXY'] = httpProxy;
 	}
 
 	outputChannel.show();
@@ -197,7 +190,7 @@
 		.reduce((res: Promise<string[]>, tool: ToolAtVersion) => {
 			return res.then(
 				(sofar) =>
-					new Promise<string[]>((resolve, reject) => {
+					new Promise<string[]>(async (resolve, reject) => {
 						// Disable modules for tools which are installed with the "..." wildcard.
 						// TODO: ... will be supported in Go 1.13, so enable these tools to use modules then.
 						const modulesOffForTool = modulesOff || disableModulesForWildcard(tool, goVersion);
@@ -210,17 +203,18 @@
 							tmpGoModFile = path.join(toolsTmpDir, 'go.mod');
 							fs.writeFileSync(tmpGoModFile, 'module tools');
 						}
+						let importPath: string;
+						if (modulesOffForTool) {
+							importPath = getImportPath(tool, goVersion);
+						} else {
+							importPath = getImportPathWithVersion(tool, tool.version, goVersion);
+						}
 
-						const opts = {
-							env: envForTools,
-							cwd: toolsTmpDir
-						};
 						const callback = (err: Error, stdout: string, stderr: string) => {
 							// Make sure to delete the temporary go.mod file, if it exists.
 							if (tmpGoModFile && fs.existsSync(tmpGoModFile)) {
 								fs.unlinkSync(tmpGoModFile);
 							}
-							const importPath = getImportPathWithVersion(tool, tool.version, goVersion);
 							if (err) {
 								outputChannel.appendLine('Installing ' + importPath + ' FAILED');
 								const failureReason = tool.name + ';;' + err + stdout.toString() + stderr.toString();
@@ -231,66 +225,51 @@
 							}
 						};
 
-						let closeToolPromise = Promise.resolve(true);
-						const toolBinPath = getBinPath(tool.name);
-						if (path.isAbsolute(toolBinPath) && isGocode(tool)) {
-							closeToolPromise = new Promise<boolean>((innerResolve) => {
-								cp.execFile(toolBinPath, ['close'], {}, (err, stdout, stderr) => {
-									if (stderr && stderr.indexOf(`rpc: can't find service Server.`) > -1) {
-										outputChannel.appendLine(
-											'Installing gocode aborted as existing process cannot be closed. Please kill the running process for gocode and try again.'
-										);
-										return innerResolve(false);
-									}
-									innerResolve(true);
-								});
-							});
-						}
-
-						closeToolPromise.then((success) => {
-							if (!success) {
+						// Perform any on-close actions before reinstalling the tool.
+						if (tool.close) {
+							const errMsg = await tool.close();
+							if (errMsg) {
+								outputChannel.appendLine(errMsg);
 								resolve([...sofar, null]);
 								return;
 							}
-							const args = ['get', '-v'];
-							// Only get tools at master if we are not using modules.
-							if (modulesOffForTool) {
-								args.push('-u');
-							}
-							// Tools with a "mod" suffix should not be installed,
-							// instead we run "go build -o" to rename them.
-							if (hasModSuffix(tool)) {
-								args.push('-d');
-							}
-							let importPath: string;
-							if (modulesOffForTool) {
-								importPath = getImportPath(tool, goVersion);
+						}
+						const args = ['get', '-v'];
+						// Only get tools at master if we are not using modules.
+						if (modulesOffForTool) {
+							args.push('-u');
+						}
+						// Tools with a "mod" suffix should not be installed,
+						// instead we run "go build -o" to rename them.
+						if (hasModSuffix(tool)) {
+							args.push('-d');
+						}
+						args.push(importPath);
+						const opts = {
+							env: envForTools,
+							cwd: toolsTmpDir
+						};
+						cp.execFile(goRuntimePath, args, opts, (err, stdout, stderr) => {
+							if (stderr.indexOf('unexpected directory layout:') > -1) {
+								outputChannel.appendLine(
+									`Installing ${importPath} failed with error "unexpected directory layout". Retrying...`
+								);
+								cp.execFile(goRuntimePath, args, opts, callback);
+							} else if (!err && hasModSuffix(tool)) {
+								const outputFile = path.join(
+									toolsGopath,
+									'bin',
+									process.platform === 'win32' ? `${tool.name}.exe` : tool.name
+								);
+								cp.execFile(
+									goRuntimePath,
+									['build', '-o', outputFile, getImportPath(tool, goVersion)],
+									opts,
+									callback
+								);
 							} else {
-								importPath = getImportPathWithVersion(tool, tool.version, goVersion);
+								callback(err, stdout, stderr);
 							}
-							args.push(importPath);
-							cp.execFile(goRuntimePath, args, opts, (err, stdout, stderr) => {
-								if (stderr.indexOf('unexpected directory layout:') > -1) {
-									outputChannel.appendLine(
-										`Installing ${importPath} failed with error "unexpected directory layout". Retrying...`
-									);
-									cp.execFile(goRuntimePath, args, opts, callback);
-								} else if (!err && hasModSuffix(tool)) {
-									const outputFile = path.join(
-										toolsGopath,
-										'bin',
-										process.platform === 'win32' ? `${tool.name}.exe` : tool.name
-									);
-									cp.execFile(
-										goRuntimePath,
-										['build', '-o', outputFile, getImportPath(tool, goVersion)],
-										opts,
-										callback
-									);
-								} else {
-									callback(err, stdout, stderr);
-								}
-							});
 						});
 					})
 			);
@@ -299,10 +278,12 @@
 			outputChannel.appendLine(''); // Blank line for spacing
 			const failures = res.filter((x) => x != null);
 			if (failures.length === 0) {
-				if (containsString(missing, 'gopls')) {
-					outputChannel.appendLine('Reload VS Code window to use the Go language server');
-				}
 				outputChannel.appendLine('All tools successfully installed. You are ready to Go :).');
+
+				// Restart the language server since a new binary has been installed.
+				if (containsString(missing, 'gopls')) {
+					restartLanguageServer();
+				}
 				return;
 			}
 
@@ -324,23 +305,15 @@
 	}
 
 	const goVersion = await getGoVersion();
-	// Show error messages for outdated tools.
-	if (goVersion.lt('1.9')) {
-		let outdatedErrorMsg;
-		switch (tool.name) {
-			case 'golint':
-				outdatedErrorMsg =
-					'golint no longer supports go1.8 or below, update your settings to use golangci-lint as go.lintTool and install golangci-lint';
-				break;
-			case 'gotests':
-				outdatedErrorMsg =
-					'Generate unit tests feature is not supported as gotests tool needs go1.9 or higher.';
-				break;
-		}
-		if (outdatedErrorMsg) {
-			vscode.window.showInformationMessage(outdatedErrorMsg);
-			return;
-		}
+
+	// Show error messages for outdated tools or outdated Go versions.
+	if (tool.minimumGoVersion && goVersion.lt(tool.minimumGoVersion.format())) {
+		vscode.window.showInformationMessage(`You are using go${goVersion.format()}, but ${tool.name} requires at least go${tool.minimumGoVersion.format()}.`);
+		return;
+	}
+	if (tool.maximumGoVersion && goVersion.gt(tool.maximumGoVersion.format())) {
+		vscode.window.showInformationMessage(`You are using go${goVersion.format()}, but ${tool.name} only supports go${tool.maximumGoVersion.format()} and below.`);
+		return;
 	}
 
 	const installOptions = ['Install'];
@@ -389,7 +362,7 @@
 		choices.push('Release Notes');
 	}
 	if (newVersion) {
-		updateMsg = `New version of ${tool.name} (${newVersion}) is available. Please update for an improved experience.`;
+		updateMsg = `A new version of ${tool.name} (v${newVersion}) is available. Please update for an improved experience.`;
 	}
 	vscode.window.showInformationMessage(updateMsg, ...choices).then((selected) => {
 		switch (selected) {
@@ -520,9 +493,8 @@
 		vscode.window.showInformationMessage(promptMsg, installLabel, disableLabel).then((selected) => {
 			if (selected === installLabel) {
 				installTools([getTool('gopls')], goVersion).then(() => {
-					vscode.window.showInformationMessage(
-						'Reload VS Code window to enable the use of Go language server'
-					);
+					// Restart the language server since the binary has changed.
+					restartLanguageServer();
 				});
 			} else if (selected === disableLabel) {
 				const goConfig = getGoConfig();
diff --git a/src/goLanguageServer.ts b/src/goLanguageServer.ts
index 8a5ecdc..be540b8 100644
--- a/src/goLanguageServer.ts
+++ b/src/goLanguageServer.ts
@@ -7,28 +7,41 @@
 
 import cp = require('child_process');
 import deepEqual = require('deep-equal');
+import fs = require('fs');
 import moment = require('moment');
 import path = require('path');
 import semver = require('semver');
 import util = require('util');
 import vscode = require('vscode');
 import {
-	Command,
-	HandleDiagnosticsSignature,
-	LanguageClient,
-	ProvideCompletionItemsSignature,
-	ProvideDocumentLinksSignature,
-	RevealOutputChannelOn
+	CloseAction, Command, ErrorAction, HandleDiagnosticsSignature, InitializeError, LanguageClient,
+	Message, ProvideCompletionItemsSignature, ProvideDocumentLinksSignature, RevealOutputChannelOn,
 } from 'vscode-languageclient';
 import WebRequest = require('web-request');
+import { GoDefinitionProvider } from './goDeclaration';
+import { GoHoverProvider } from './goExtraInfo';
+import { GoDocumentFormattingEditProvider } from './goFormat';
+import { GoImplementationProvider } from './goImplementations';
 import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools';
+import { parseLiveFile } from './goLiveErrors';
+import { restartLanguageServer } from './goMain';
+import { GO_MODE } from './goMode';
+import { GoDocumentSymbolProvider } from './goOutline';
 import { getToolFromToolPath } from './goPath';
+import { GoReferenceProvider } from './goReferences';
+import { GoRenameProvider } from './goRename';
+import { GoSignatureHelpProvider } from './goSignature';
+import { GoCompletionItemProvider } from './goSuggest';
+import { GoWorkspaceSymbolProvider } from './goSymbol';
 import { getTool, Tool } from './goTools';
+import { GoTypeDefinitionProvider } from './goTypeDefinition';
+import { getFromGlobalState, updateGlobalState } from './stateUtils';
 import { getBinPath, getCurrentGoPath, getGoConfig, getToolsEnvVars } from './util';
 
 interface LanguageServerConfig {
 	serverName: string;
 	path: string;
+	modtime: Date;
 	enabled: boolean;
 	flags: string[];
 	env: any;
@@ -47,38 +60,50 @@
 let latestConfig: LanguageServerConfig;
 let serverOutputChannel: vscode.OutputChannel;
 
-// startLanguageServer starts the language server (if enabled), returning
-// true on success.
-export async function registerLanguageFeatures(ctx: vscode.ExtensionContext): Promise<boolean> {
-	// Subscribe to notifications for changes to the configuration of the language server.
-	ctx.subscriptions.push(vscode.workspace.onDidChangeConfiguration((e) => watchLanguageServerConfiguration(e)));
+// defaultLanguageProviders is the list of providers currently registered.
+let defaultLanguageProviders: vscode.Disposable[] = [];
 
-	const config = parseLanguageServerConfig();
-	if (!config.enabled) {
-		return false;
-	}
+// restartCommand is the command used by the user to restart the language
+// server.
+let restartCommand: vscode.Disposable;
 
-	// Support a command to restart the language server, if it's enabled.
-	ctx.subscriptions.push(vscode.commands.registerCommand('go.languageserver.restart', () => {
-		return startLanguageServer(ctx, parseLanguageServerConfig());
-	}));
+// When enabled, users may be prompted to fill out the gopls survey.
+const goplsSurveyOn: boolean = false;
+
+// startLanguageServerWithFallback starts the language server, if enabled,
+// or falls back to the default language providers.
+export async function startLanguageServerWithFallback(ctx: vscode.ExtensionContext, activation: boolean) {
+	const cfg = buildLanguageServerConfig();
 
 	// If the language server is gopls, we can check if the user needs to
-	// update their gopls version.
-	if (config.serverName === 'gopls') {
-		const tool = getTool(config.serverName);
-		if (!tool) {
-			return false;
-		}
-		const versionToUpdate = await shouldUpdateLanguageServer(tool, config.path, config.checkForUpdates);
-		if (versionToUpdate) {
-			promptForUpdatingTool(tool.name, versionToUpdate);
+	// update their gopls version. We do this only once per VS Code
+	// activation to avoid inundating the user.
+	if (activation && cfg.enabled && cfg.serverName === 'gopls') {
+		const tool = getTool(cfg.serverName);
+		if (tool) {
+			const versionToUpdate = await shouldUpdateLanguageServer(tool, cfg.path, cfg.checkForUpdates);
+			if (versionToUpdate) {
+				promptForUpdatingTool(tool.name, versionToUpdate);
+			} else if (goplsSurveyOn) {
+				// Only prompt users to fill out the gopls survey if we are not
+				// also prompting them to update (both would be too much).
+				const timeout = 1000 * 60 * 60; // 1 hour
+				setTimeout(async () => {
+					const surveyCfg = await maybePromptForGoplsSurvey();
+					flushSurveyConfig(surveyCfg);
+				}, timeout);
+			}
 		}
 	}
 
-	// This function handles the case when the server isn't started yet,
-	// so we can call it to start the language server.
-	return startLanguageServer(ctx, config);
+	const started = await startLanguageServer(ctx, cfg);
+
+	// 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);
+	}
 }
 
 async function startLanguageServer(ctx: vscode.ExtensionContext, config: LanguageServerConfig): Promise<boolean> {
@@ -97,29 +122,47 @@
 	// Check if we should recreate the language client. This may be necessary
 	// if the user has changed settings in their config.
 	if (!deepEqual(latestConfig, config)) {
-		// Track the latest config used to start the language server.
+		// Track the latest config used to start the language server,
+		// and rebuild the language client.
 		latestConfig = config;
-
-		// If the user has not enabled or installed the language server, return.
-		if (!config.enabled || !config.path) {
-			return false;
-		}
-		buildLanguageClient(config);
+		languageClient = await buildLanguageClient(config);
 	}
 
+	// If the user has not enabled the language server, return early.
+	if (!config.enabled) {
+		return false;
+	}
+
+	// Set up the command to allow the user to manually restart the
+	// language server.
+	if (!restartCommand) {
+		restartCommand = vscode.commands.registerCommand('go.languageserver.restart', async () => {
+			// TODO(rstambler): Enable this behavior when gopls reaches v1.0.
+			if (false) {
+				await suggestGoplsIssueReport(`Looks like you're about to manually restart the language server.`);
+			}
+			restartLanguageServer();
+		});
+		ctx.subscriptions.push(restartCommand);
+	}
+
+	// Before starting the language server, make sure to deregister any
+	// currently registered language providers.
+	disposeDefaultProviders();
+
 	languageServerDisposable = languageClient.start();
 	ctx.subscriptions.push(languageServerDisposable);
-
 	return true;
 }
 
-function buildLanguageClient(config: LanguageServerConfig) {
+async function buildLanguageClient(config: LanguageServerConfig): Promise<LanguageClient> {
 	// Reuse the same output channel for each instance of the server.
-	if (!serverOutputChannel) {
+	if (config.enabled && !serverOutputChannel) {
 		serverOutputChannel = vscode.window.createOutputChannel(config.serverName);
 	}
-	languageClient = new LanguageClient(
-		config.serverName,
+	const c = new LanguageClient(
+		'go',  // id
+		config.serverName,  // name
 		{
 			command: config.path,
 			args: ['-mode=stdio', ...config.flags],
@@ -136,6 +179,31 @@
 			},
 			outputChannel: serverOutputChannel,
 			revealOutputChannelOn: RevealOutputChannelOn.Never,
+			initializationFailedHandler: (error: WebRequest.ResponseError<InitializeError>): boolean => {
+				vscode.window.showErrorMessage(
+					`The language server is not able to serve any features. Initialization failed: ${error}. `
+				);
+				serverOutputChannel.show();
+				suggestGoplsIssueReport(`The gopls server failed to initialize.`);
+				return false;
+			},
+			errorHandler: {
+				error: (error: Error, message: Message, count: number): ErrorAction => {
+					vscode.window.showErrorMessage(
+						`Error communicating with the language server: ${error}: ${message}.`
+					);
+					// Stick with the default number of 5 crashes before shutdown.
+					if (count >= 5) {
+						return ErrorAction.Shutdown;
+					}
+					return ErrorAction.Continue;
+				},
+				closed: (): CloseAction => {
+					serverOutputChannel.show();
+					suggestGoplsIssueReport(`The connection to gopls has been closed. The gopls server may have crashed.`);
+					return CloseAction.DoNotRestart;
+				},
+			},
 			middleware: {
 				handleDiagnostics: (
 					uri: vscode.Uri,
@@ -212,60 +280,70 @@
 			}
 		}
 	);
-	languageClient.onReady().then(() => {
-		const capabilities = languageClient.initializeResult && languageClient.initializeResult.capabilities;
-		if (!capabilities) {
-			return vscode.window.showErrorMessage(
-				'The language server is not able to serve any features at the moment.'
-			);
-		}
-	});
+	return c;
 }
 
-function watchLanguageServerConfiguration(e: vscode.ConfigurationChangeEvent) {
+// 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));
+
+	for (const provider of defaultLanguageProviders) {
+		ctx.subscriptions.push(provider);
+	}
+}
+
+function disposeDefaultProviders() {
+	for (const disposable of defaultLanguageProviders) {
+		disposable.dispose();
+	}
+	defaultLanguageProviders = [];
+}
+
+export function watchLanguageServerConfiguration(e: vscode.ConfigurationChangeEvent) {
 	if (!e.affectsConfiguration('go')) {
 		return;
 	}
 
-	const config = parseLanguageServerConfig();
-	let reloadMessage: string;
-
-	// If the user has disabled or enabled the language server.
-	if (e.affectsConfiguration('go.useLanguageServer')) {
-		if (config.enabled) {
-			reloadMessage = 'Reload VS Code window to enable the use of language server';
-		} else {
-			reloadMessage = 'Reload VS Code window to disable the use of language server';
-		}
-	}
-
 	if (
+		e.affectsConfiguration('go.useLanguageServer') ||
 		e.affectsConfiguration('go.languageServerFlags') ||
 		e.affectsConfiguration('go.languageServerExperimentalFeatures')
 	) {
-		reloadMessage = 'Reload VS Code window for the changes in language server settings to take effect';
-	}
-
-	// If there was a change in the configuration of the language server,
-	// then ask the user to reload VS Code.
-	if (reloadMessage) {
-		vscode.window.showInformationMessage(reloadMessage, 'Reload').then((selected) => {
-			if (selected === 'Reload') {
-				vscode.commands.executeCommand('workbench.action.reloadWindow');
-			}
-		});
+		restartLanguageServer();
 	}
 }
 
-export function parseLanguageServerConfig(): LanguageServerConfig {
+export function buildLanguageServerConfig(): LanguageServerConfig {
 	const goConfig = getGoConfig();
 	const toolsEnv = getToolsEnvVars();
-	const languageServerPath = getLanguageServerToolPath();
-	const languageServerName = getToolFromToolPath(languageServerPath);
-	return {
-		serverName: languageServerName,
-		path: languageServerPath,
-		enabled: goConfig['useLanguageServer'],
+	const cfg: LanguageServerConfig = {
+		serverName: '',
+		path: '',
+		modtime: null,
+		enabled: goConfig['useLanguageServer'] === true,
 		flags: goConfig['languageServerFlags'] || [],
 		features: {
 			// TODO: We should have configs that match these names.
@@ -274,23 +352,47 @@
 			documentLink: goConfig['languageServerExperimentalFeatures']['documentLink']
 		},
 		env: toolsEnv,
-		checkForUpdates: goConfig['useGoProxyToCheckForToolUpdates']
+		checkForUpdates: goConfig['useGoProxyToCheckForToolUpdates'],
 	};
+	// Don't look for the path if the server is not enabled.
+	if (!cfg.enabled) {
+		return cfg;
+	}
+	const languageServerPath = getLanguageServerToolPath();
+	if (!languageServerPath) {
+		// Assume the getLanguageServerToolPath will show the relevant
+		// errors to the user. Disable the language server.
+		cfg.enabled = false;
+		return cfg;
+	}
+	cfg.path = languageServerPath;
+	cfg.serverName = getToolFromToolPath(cfg.path);
+
+	// Get the mtime of the language server binary so that we always pick up
+	// the right version.
+	const stats = fs.statSync(languageServerPath);
+	if (!stats) {
+		vscode.window.showErrorMessage(`Unable to stat path to language server binary: ${languageServerPath}.
+Please try reinstalling it.`);
+		// Disable the language server.
+		cfg.enabled = false;
+		return cfg;
+	}
+	cfg.modtime = stats.mtime;
+
+	return cfg;
 }
 
 /**
  *
- * If the user has enabled the language server, return the absolute path to the
- * correct binary. If the required tool is not available, prompt the user to
- * install it. Only gopls is officially supported.
+ * Return the absolute path to the correct binary. If the required tool is not available,
+ * prompt the user to install it. Only gopls is officially supported.
  */
 export function getLanguageServerToolPath(): string {
-	// If language server is not enabled, return
 	const goConfig = getGoConfig();
 	if (!goConfig['useLanguageServer']) {
 		return;
 	}
-
 	// Check that all workspace folders are configured with the same GOPATH.
 	if (!allFoldersHaveSameGopath()) {
 		vscode.window.showInformationMessage(
@@ -335,10 +437,8 @@
 	return vscode.workspace.workspaceFolders.find((x) => tempGopath !== getCurrentGoPath(x.uri)) ? false : true;
 }
 
-const acceptGoplsPrerelease = false;
-const defaultLatestVersion = semver.coerce('0.4.0');
-const defaultLatestVersionTime = moment('2020-04-08', 'YYYY-MM-DD');
-async function shouldUpdateLanguageServer(
+const acceptGoplsPrerelease = true;  // For nightly, we accept the prerelease version.
+export async function shouldUpdateLanguageServer(
 	tool: Tool,
 	languageServerToolPath: string,
 	makeProxyCall: boolean
@@ -349,19 +449,19 @@
 	}
 
 	// First, run the "gopls version" command and parse its results.
-	const usersVersion = await goplsVersion(languageServerToolPath);
+	const usersVersion = await getLocalGoplsVersion(languageServerToolPath);
 
 	// We might have a developer version. Don't make the user update.
 	if (usersVersion === '(devel)') {
 		return null;
 	}
 
-	// Get the latest gopls version.
-	let latestVersion = makeProxyCall ? await latestGopls(tool) : defaultLatestVersion;
+	// Get the latest gopls version. If it is for nightly, using the prereleased version is ok.
+	let latestVersion = makeProxyCall ? await getLatestGoplsVersion(tool) : tool.latestVersion;
 
 	// If we failed to get the gopls version, pick the one we know to be latest at the time of this extension's last update
 	if (!latestVersion) {
-		latestVersion = defaultLatestVersion;
+		latestVersion = tool.latestVersion;
 	}
 
 	// If "gopls" is so old that it doesn't have the "gopls version" command,
@@ -373,12 +473,12 @@
 
 	// The user may have downloaded golang.org/x/tools/gopls@master,
 	// which means that they have a pseudoversion.
-	const usersTime = parsePseudoversionTimestamp(usersVersion);
+	const usersTime = parseTimestampFromPseudoversion(usersVersion);
 	// If the user has a pseudoversion, get the timestamp for the latest gopls version and compare.
 	if (usersTime) {
-		let latestTime = makeProxyCall ? await goplsVersionTimestamp(tool, latestVersion) : defaultLatestVersionTime;
+		let latestTime = makeProxyCall ? await getTimestampForVersion(tool, latestVersion) : tool.latestVersionTimestamp;
 		if (!latestTime) {
-			latestTime = defaultLatestVersionTime;
+			latestTime = tool.latestVersionTimestamp;
 		}
 		return usersTime.isBefore(latestTime) ? latestVersion : null;
 	}
@@ -388,13 +488,13 @@
 	return semver.lt(usersVersion, latestVersion) ? latestVersion : null;
 }
 
-// Copied from src/cmd/go/internal/modfetch.
+// Copied from src/cmd/go/internal/modfetch.go.
 const pseudoVersionRE = /^v[0-9]+\.(0\.0-|\d+\.\d+-([^+]*\.)?0\.)\d{14}-[A-Za-z0-9]+(\+incompatible)?$/;
 
-// parsePseudoVersion reports whether v is a pseudo-version.
-// The timestamp is the center component, and it has the format "YYYYMMDDHHmmss".
-
-function parsePseudoversionTimestamp(version: string): moment.Moment {
+// parseTimestampFromPseudoversion returns the timestamp for the given
+// pseudoversion. The timestamp is the center component, and it has the
+// format "YYYYMMDDHHmmss".
+function parseTimestampFromPseudoversion(version: string): moment.Moment {
 	const split = version.split('-');
 	if (split.length < 2) {
 		return null;
@@ -430,7 +530,7 @@
 	return moment.utc(timestamp, 'YYYYMMDDHHmmss');
 }
 
-async function goplsVersionTimestamp(tool: Tool, version: semver.SemVer): Promise<moment.Moment> {
+export async function getTimestampForVersion(tool: Tool, version: semver.SemVer): Promise<moment.Moment> {
 	const data = await goProxyRequest(tool, `v${version.format()}.info`);
 	if (!data) {
 		return null;
@@ -439,7 +539,7 @@
 	return time;
 }
 
-async function latestGopls(tool: Tool): Promise<semver.SemVer> {
+export async function getLatestGoplsVersion(tool: Tool): Promise<semver.SemVer> {
 	// If the user has a version of gopls that we understand,
 	// ask the proxy for the latest version, and if the user's version is older,
 	// prompt them to update.
@@ -470,7 +570,10 @@
 	return versions.find((version) => !version.prerelease || !version.prerelease.length);
 }
 
-async function goplsVersion(goplsPath: string): Promise<string> {
+// getLocalGoplsVersion returns the version of gopls that is currently
+// installed on the user's machine. This is determined by running the
+// `gopls version` command.
+export async function getLocalGoplsVersion(goplsPath: string): Promise<string> {
 	const env = getToolsEnvVars();
 	const execFile = util.promisify(cp.execFile);
 	let output: any;
@@ -527,9 +630,15 @@
 }
 
 async function goProxyRequest(tool: Tool, endpoint: string): Promise<any> {
-	const proxies = goProxy();
+	// Get the user's value of GOPROXY.
+	// If it is not set, we cannot make the request.
+	const output: string = process.env['GOPROXY'];
+	if (!output || !output.trim()) {
+		return null;
+	}
 	// Try each URL set in the user's GOPROXY environment variable.
 	// If none is set, don't make the request.
+	const proxies = output.trim().split(',|');
 	for (const proxy of proxies) {
 		if (proxy === 'direct') {
 			continue;
@@ -548,11 +657,184 @@
 	return null;
 }
 
-function goProxy(): string[] {
-	const output: string = process.env['GOPROXY'];
-	if (!output || !output.trim()) {
-		return [];
+// SurveyConfig is the set of global properties used to determine if
+// we should prompt a user to take the gopls survey.
+export interface SurveyConfig {
+	// prompt is true if the user can be prompted to take the survey.
+	// It is false if the user has responded "Never" to the prompt.
+	prompt?: boolean;
+
+	// promptThisMonth is true if we have used a random number generator
+	// to determine if the user should be prompted this month.
+	// It is undefined if we have not yet made the determination.
+	promptThisMonth?: boolean;
+
+	// promptThisMonthTimestamp is the date on which we determined if the user
+	// should be prompted this month.
+	promptThisMonthTimestamp?: Date;
+
+	// lastDatePrompted is the most recent date that the user has been prompted.
+	lastDatePrompted?: Date;
+
+	// lastDateAccepted is the most recent date that the user responded "Yes"
+	// to the survey prompt. The user need not have completed the survey.
+	lastDateAccepted?: Date;
+}
+
+async function maybePromptForGoplsSurvey(): Promise<SurveyConfig> {
+	const now = new Date();
+	const cfg = getSurveyConfig();
+	const prompt = shouldPromptForGoplsSurvey(now, cfg);
+	if (!prompt) {
+		return cfg;
 	}
-	const split = output.trim().split(',');
-	return split;
+	const selected = await vscode.window.showInformationMessage(`Looks like you're using gopls, the Go language server.
+Would you be willing to fill out a quick survey about your experience with gopls?`, 'Yes', 'Not now', 'Never');
+
+	// Update the time last asked.
+	cfg.lastDatePrompted = now;
+
+	switch (selected) {
+		case 'Yes':
+			cfg.lastDateAccepted = now;
+			cfg.prompt = true;
+
+			// Open the link to the survey.
+			vscode.env.openExternal(vscode.Uri.parse('https://www.whattimeisitrightnow.com/'));
+			break;
+		case 'Not now':
+			cfg.prompt = true;
+
+			vscode.window.showInformationMessage(`No problem! We'll ask you again another time.`);
+			break;
+		case 'Never':
+			cfg.prompt = false;
+
+			vscode.window.showInformationMessage(`No problem! We won't ask again.`);
+			break;
+	}
+	return cfg;
+}
+
+export function shouldPromptForGoplsSurvey(now: Date, cfg: SurveyConfig): boolean {
+	// If the prompt value is not set, assume we haven't prompted the user
+	// and should do so.
+	if (cfg.prompt === undefined) {
+		cfg.prompt = true;
+	}
+	if (!cfg.prompt) {
+		return false;
+	}
+
+	// Check if the user has taken the survey in the last year.
+	// Don't prompt them if they have been.
+	if (cfg.lastDateAccepted) {
+		if (daysBetween(now, cfg.lastDateAccepted) < 365) {
+			return false;
+		}
+	}
+
+	// Check if the user has been prompted for the survey in the last 90 days.
+	// Don't prompt them if they have been.
+	if (cfg.lastDatePrompted) {
+		if (daysBetween(now, cfg.lastDatePrompted) < 90) {
+			return false;
+		}
+	}
+
+	// Check if the extension has been activated this month.
+	if (cfg.promptThisMonthTimestamp) {
+		// The extension has been activated this month, so we should have already
+		// decided if the user should be prompted.
+		if (daysBetween(now, cfg.promptThisMonthTimestamp) < 30) {
+			return cfg.promptThisMonth;
+		}
+	}
+	// This is the first activation this month (or ever), so decide if we
+	// should prompt the user. This is done by generating a random number
+	// and % 20 to get a 5% chance.
+	const r = Math.floor(Math.random() * 20);
+	cfg.promptThisMonth = (r % 20 === 0);
+	cfg.promptThisMonthTimestamp = now;
+
+	return cfg.promptThisMonth;
+}
+
+export const goplsSurveyConfig = 'goplsSurveyConfig';
+
+function getSurveyConfig(): SurveyConfig {
+	const saved = getFromGlobalState(goplsSurveyConfig);
+	if (saved === undefined) {
+		return {};
+	}
+	try {
+		const cfg = JSON.parse(saved, (key: string, value: any) => {
+			// Make sure values that should be dates are correctly converted.
+			if (key.includes('Date')) {
+				return new Date(value);
+			}
+			return value;
+		});
+		return cfg;
+	} catch (err) {
+		console.log(`Error parsing JSON from ${saved}: ${err}`);
+		return {};
+	}
+}
+
+function flushSurveyConfig(cfg: SurveyConfig) {
+	updateGlobalState(goplsSurveyConfig, JSON.stringify(cfg));
+}
+
+// suggestGoplsIssueReport prompts users to file an issue with gopls.
+async function suggestGoplsIssueReport(msg: string) {
+	if (latestConfig.serverName !== 'gopls') {
+		return;
+	}
+	const promptForIssueOnGoplsRestartKey = `promptForIssueOnGoplsRestart`;
+	let saved: any;
+	try {
+		saved = JSON.parse(getFromGlobalState(promptForIssueOnGoplsRestartKey, true));
+	} catch (err) {
+		console.log(`Failed to parse as JSON ${getFromGlobalState(promptForIssueOnGoplsRestartKey, true)}: ${err}`);
+		return;
+	}
+	// If the user has already seen this prompt, they may have opted-out for
+	// the future. Only prompt again if it's been more than a year since.
+	if (saved['date'] && saved['prompt']) {
+		const dateSaved = new Date(saved['date']);
+		const prompt = <boolean>saved['prompt'];
+		if (!prompt && daysBetween(new Date(), dateSaved) <= 365) {
+			return;
+		}
+	}
+	const selected = await vscode.window.showInformationMessage(`${msg} Would you like to report a gopls issue ? `, 'Yes', 'Next time', 'Never');
+	switch (selected) {
+		case 'Yes':
+			// Run the `gopls bug` command directly for now. When
+			// https://github.com/golang/go/issues/38942 is
+			// resolved, we'll be able to do this through the
+			// language client.
+
+			// Wait for the command to finish before restarting the
+			// server, but don't bother handling errors.
+			const execFile = util.promisify(cp.execFile);
+			await execFile(latestConfig.path, ['bug'], { env: getToolsEnvVars() });
+			break;
+		case 'Next time':
+			break;
+		case 'Never':
+			updateGlobalState(promptForIssueOnGoplsRestartKey, JSON.stringify({
+				prompt: false,
+				date: new Date(),
+			}));
+			break;
+	}
+}
+
+// daysBetween returns the number of days between a and b,
+// assuming that a occurs after b.
+function daysBetween(a: Date, b: Date) {
+	const ms = a.getTime() - b.getTime();
+	return ms / (1000 * 60 * 60 * 24);
 }
diff --git a/src/goMain.ts b/src/goMain.ts
index 7fda993..2d71c75 100644
--- a/src/goMain.ts
+++ b/src/goMain.ts
@@ -5,7 +5,6 @@
 
 'use strict';
 
-import fs = require('fs');
 import * as path from 'path';
 import vscode = require('vscode');
 import { browsePackages } from './goBrowsePackage';
@@ -13,82 +12,54 @@
 import { check, notifyIfGeneratedFile, removeTestStatus } from './goCheck';
 import { GoCodeActionProvider } from './goCodeAction';
 import {
-	applyCodeCoverage,
-	applyCodeCoverageToAllEditors,
-	initCoverageDecorators,
-	removeCodeCoverageOnFileSave,
-	toggleCoverageCurrentPackage,
-	trackCodeCoverageRemovalOnFileChange,
-	updateCodeCoverageDecorators
+	applyCodeCoverage, applyCodeCoverageToAllEditors, initCoverageDecorators, removeCodeCoverageOnFileSave,
+	toggleCoverageCurrentPackage, trackCodeCoverageRemovalOnFileChange, updateCodeCoverageDecorators
 } from './goCover';
 import { GoDebugConfigurationProvider } from './goDebugConfiguration';
-import { GoDefinitionProvider } from './goDeclaration';
 import { extractFunction, extractVariable } from './goDoctor';
-import { GoHoverProvider } from './goExtraInfo';
 import { runFillStruct } from './goFillStruct';
-import { GoDocumentFormattingEditProvider } from './goFormat';
 import * as goGenerateTests from './goGenerateTests';
 import { goGetPackage } from './goGetPackage';
 import { implCursor } from './goImpl';
-import { GoImplementationProvider } from './goImplementations';
 import { addImport, addImportToWorkspace } from './goImport';
 import { installCurrentPackage } from './goInstall';
 import {
-	installAllTools,
-	installTools,
-	offerToInstallTools,
-	promptForMissingTool,
+	installAllTools, installTools, offerToInstallTools, promptForMissingTool,
 	updateGoPathGoRootFromConfig
 } from './goInstallTools';
-import { registerLanguageFeatures } from './goLanguageServer';
+import { startLanguageServerWithFallback, watchLanguageServerConfiguration } from './goLanguageServer';
 import { lintCode } from './goLint';
-import { parseLiveFile } from './goLiveErrors';
 import { GO_MODE } from './goMode';
 import { addTags, removeTags } from './goModifytags';
 import { GO111MODULE, isModSupported } from './goModules';
-import { GoDocumentSymbolProvider } from './goOutline';
 import { clearCacheForTools, fileExists } from './goPath';
 import { playgroundCommand } from './goPlayground';
-import { GoReferenceProvider } from './goReferences';
 import { GoReferencesCodeLensProvider } from './goReferencesCodelens';
-import { GoRenameProvider } from './goRename';
 import { GoRunTestCodeLensProvider } from './goRunTestCodelens';
-import { GoSignatureHelpProvider } from './goSignature';
 import { outputChannel, showHideStatus } from './goStatus';
-import { GoCompletionItemProvider } from './goSuggest';
-import { GoWorkspaceSymbolProvider } from './goSymbol';
 import { testAtCursor, testCurrentFile, testCurrentPackage, testPrevious, testWorkspace } from './goTest';
 import { getConfiguredTools } from './goTools';
-import { GoTypeDefinitionProvider } from './goTypeDefinition';
 import { vetCode } from './goVet';
 import {
-	getFromGlobalState,
-	getFromWorkspaceState,
-	setGlobalState,
-	setWorkspaceState,
-	updateGlobalState,
+	getFromGlobalState, getFromWorkspaceState, setGlobalState, setWorkspaceState, updateGlobalState,
 	updateWorkspaceState
 } from './stateUtils';
 import { disposeTelemetryReporter, sendTelemetryEventForConfig } from './telemetry';
 import { cancelRunningTests, showTestOutput } from './testUtils';
 import {
-	cleanupTempDir,
-	getBinPath,
-	getCurrentGoPath,
-	getExtensionCommands,
-	getGoConfig,
-	getGoVersion,
-	getToolsEnvVars,
-	getToolsGopath,
-	getWorkspaceFolderPath,
-	handleDiagnosticErrors,
-	isGoPathSet
+	cleanupTempDir, getBinPath, getCurrentGoPath, getExtensionCommands, getGoConfig,
+	getGoVersion, getToolsEnvVars, getToolsGopath, getWorkspaceFolderPath, handleDiagnosticErrors, isGoPathSet
 } from './util';
 
 export let buildDiagnosticCollection: vscode.DiagnosticCollection;
 export let lintDiagnosticCollection: vscode.DiagnosticCollection;
 export let vetDiagnosticCollection: vscode.DiagnosticCollection;
 
+// restartLanguageServer wraps all of the logic needed to restart the
+// language server. It can be used to enable, disable, or otherwise change
+// the configuration of the server.
+export let restartLanguageServer = () => { return; };
+
 export function activate(ctx: vscode.ExtensionContext): void {
 	setGlobalState(ctx.globalState);
 	setWorkspaceState(ctx.workspaceState);
@@ -145,12 +116,21 @@
 
 		offerToInstallTools();
 
-		// This handles all of the configurations and registrations for the language server.
-		// It also registers the necessary language feature providers that the language server may not support.
-		const ok = await registerLanguageFeatures(ctx);
-		if (!ok) {
-			registerUsualProviders(ctx);
-		}
+		// Subscribe to notifications for changes to the configuration
+		// of the language server, even if it's not currently in use.
+		ctx.subscriptions.push(vscode.workspace.onDidChangeConfiguration(
+			(e) => watchLanguageServerConfiguration(e)
+		));
+
+		// Set the function that is used to restart the language server.
+		// This is necessary, even if the language server is not currently
+		// in use.
+		restartLanguageServer = async () => {
+			startLanguageServerWithFallback(ctx, false);
+		};
+
+		// Start the language server, or fallback to the default language providers.
+		startLanguageServerWithFallback(ctx, true);
 
 		if (
 			vscode.window.activeTextEditor &&
@@ -623,28 +603,6 @@
 	);
 }
 
-// registerUsualProviders registers the language feature providers if the language server is not enabled.
-function registerUsualProviders(ctx: vscode.ExtensionContext) {
-	const provider = new GoCompletionItemProvider(ctx.globalState);
-	ctx.subscriptions.push(provider);
-	ctx.subscriptions.push(vscode.languages.registerCompletionItemProvider(GO_MODE, provider, '.', '"'));
-	ctx.subscriptions.push(vscode.languages.registerHoverProvider(GO_MODE, new GoHoverProvider()));
-	ctx.subscriptions.push(vscode.languages.registerDefinitionProvider(GO_MODE, new GoDefinitionProvider()));
-	ctx.subscriptions.push(vscode.languages.registerReferenceProvider(GO_MODE, new GoReferenceProvider()));
-	ctx.subscriptions.push(vscode.languages.registerDocumentSymbolProvider(GO_MODE, new GoDocumentSymbolProvider()));
-	ctx.subscriptions.push(vscode.languages.registerWorkspaceSymbolProvider(new GoWorkspaceSymbolProvider()));
-	ctx.subscriptions.push(
-		vscode.languages.registerSignatureHelpProvider(GO_MODE, new GoSignatureHelpProvider(), '(', ',')
-	);
-	ctx.subscriptions.push(vscode.languages.registerImplementationProvider(GO_MODE, new GoImplementationProvider()));
-	ctx.subscriptions.push(
-		vscode.languages.registerDocumentFormattingEditProvider(GO_MODE, new GoDocumentFormattingEditProvider())
-	);
-	ctx.subscriptions.push(vscode.languages.registerTypeDefinitionProvider(GO_MODE, new GoTypeDefinitionProvider()));
-	ctx.subscriptions.push(vscode.languages.registerRenameProvider(GO_MODE, new GoRenameProvider()));
-	vscode.workspace.onDidChangeTextDocument(parseLiveFile, null, ctx.subscriptions);
-}
-
 function addOnChangeTextDocumentListeners(ctx: vscode.ExtensionContext) {
 	vscode.workspace.onDidChangeTextDocument(trackCodeCoverageRemovalOnFileChange, null, ctx.subscriptions);
 	vscode.workspace.onDidChangeTextDocument(removeTestStatus, null, ctx.subscriptions);
diff --git a/src/goModules.ts b/src/goModules.ts
index 35caeb7..6ec5017 100644
--- a/src/goModules.ts
+++ b/src/goModules.ts
@@ -7,6 +7,7 @@
 import path = require('path');
 import vscode = require('vscode');
 import { installTools } from './goInstallTools';
+import { restartLanguageServer } from './goMain';
 import { envPath, fixDriveCasingInWindows } from './goPath';
 import { getTool } from './goTools';
 import { getFromGlobalState, updateGlobalState } from './stateUtils';
@@ -135,12 +136,7 @@
 						if (goConfig.inspect('useLanguageServer').workspaceFolderValue === false) {
 							goConfig.update('useLanguageServer', true, vscode.ConfigurationTarget.WorkspaceFolder);
 						}
-						const reloadMsg = 'Reload VS Code window to enable the use of Go language server';
-						vscode.window.showInformationMessage(reloadMsg, 'Reload').then((selectedForReload) => {
-							if (selectedForReload === 'Reload') {
-								vscode.commands.executeCommand('workbench.action.reloadWindow');
-							}
-						});
+						restartLanguageServer();
 					}
 				});
 			}
diff --git a/src/goTools.ts b/src/goTools.ts
index 7ef0a9b..2a9bb8d 100644
--- a/src/goTools.ts
+++ b/src/goTools.ts
@@ -5,15 +5,45 @@
 
 'use strict';
 
-import { SemVer } from 'semver';
+import cp = require('child_process');
+import moment = require('moment');
+import path = require('path');
+import semver = require('semver');
+import util = require('util');
 import { goLiveErrorsEnabled } from './goLiveErrors';
-import { getGoConfig, GoVersion } from './util';
+import { getBinPath, getGoConfig, GoVersion } from './util';
 
 export interface Tool {
 	name: string;
 	importPath: string;
 	isImportant: boolean;
 	description: string;
+
+	// latestVersion and latestVersionTimestamp are hardcoded default values
+	// for the last known version of the given tool. We also hardcode values
+	// for the latest known pre-release of the tool for the Nightly extension.
+	latestVersion?: semver.SemVer;
+	latestVersionTimestamp?: moment.Moment;
+	latestPrereleaseVersion?: semver.SemVer;
+	latestPrereleaseVersionTimestamp?: moment.Moment;
+
+	// minimumGoVersion and maximumGoVersion set the range for the versions of
+	// Go with which this tool can be used.
+	minimumGoVersion?: semver.SemVer;
+	maximumGoVersion?: semver.SemVer;
+
+	// close performs any shutdown tasks that a tool must execute before a new
+	// version is installed. It returns a string containing an error message on
+	// failure.
+	close?: () => Promise<string>;
+}
+
+/**
+ * ToolAtVersion is a Tool at a specific version.
+ * Lack of version implies the latest version.
+ */
+export interface ToolAtVersion extends Tool {
+	version?: semver.SemVer;
 }
 
 /**
@@ -29,7 +59,7 @@
 	return tool.importPath;
 }
 
-export function getImportPathWithVersion(tool: Tool, version: SemVer, goVersion: GoVersion): string {
+export function getImportPathWithVersion(tool: Tool, version: semver.SemVer, goVersion: GoVersion): string {
 	const importPath = getImportPath(tool, goVersion);
 	if (version) {
 		return importPath + '@v' + version;
@@ -62,6 +92,10 @@
 	return allToolsInformation[name];
 }
 
+export function getToolAtVersion(name: string, version?: semver.SemVer): ToolAtVersion {
+	return { ...allToolsInformation[name], version };
+}
+
 // hasModSuffix returns true if the given tool has a different, module-specific
 // name to avoid conflicts.
 export function hasModSuffix(tool: Tool): boolean {
@@ -146,13 +180,30 @@
 		name: 'gocode',
 		importPath: 'github.com/mdempsky/gocode',
 		isImportant: true,
-		description: 'Auto-completion, does not work with modules'
+		description: 'Auto-completion, does not work with modules',
+		close: async (): Promise<string> => {
+			const toolBinPath = getBinPath('gocode');
+			if (!path.isAbsolute(toolBinPath)) {
+				return '';
+			}
+			try {
+				const execFile = util.promisify(cp.execFile);
+				const { stderr } = await execFile(toolBinPath, ['close']);
+				if (stderr.indexOf(`rpc: can't find service Server.`) > -1) {
+					return `Installing gocode aborted as existing process cannot be closed. Please kill the running process for gocode and try again.`;
+				}
+			} catch (err) {
+				return `Failed to close gocode process: ${err}.`;
+			}
+			return '';
+		},
 	},
 	'gocode-gomod': {
 		name: 'gocode-gomod',
 		importPath: 'github.com/stamblerre/gocode',
 		isImportant: true,
-		description: 'Auto-completion, works with modules'
+		description: 'Auto-completion, works with modules',
+		minimumGoVersion: semver.coerce('1.11'),
 	},
 	'gopkgs': {
 		name: 'gopkgs',
@@ -242,13 +293,15 @@
 		name: 'golint',
 		importPath: 'golang.org/x/lint/golint',
 		isImportant: true,
-		description: 'Linter'
+		description: 'Linter',
+		minimumGoVersion: semver.coerce('1.9'),
 	},
 	'gotests': {
 		name: 'gotests',
 		importPath: 'github.com/cweill/gotests/...',
 		isImportant: false,
-		description: 'Generate unit tests'
+		description: 'Generate unit tests',
+		minimumGoVersion: semver.coerce('1.9'),
 	},
 	'staticcheck': {
 		name: 'staticcheck',
@@ -272,7 +325,12 @@
 		name: 'gopls',
 		importPath: 'golang.org/x/tools/gopls',
 		isImportant: false,
-		description: 'Language Server from Google'
+		description: 'Language Server from Google',
+		minimumGoVersion: semver.coerce('1.12'),
+		latestVersion: semver.coerce('0.4.0'),
+		latestVersionTimestamp: moment('2020-04-08', 'YYYY-MM-DD'),
+		latestPrereleaseVersion: semver.coerce('0.4.1-pre2'),
+		latestPrereleaseVersionTimestamp: moment('2020-05-11', 'YYYY-MM-DD'),
 	},
 	'dlv': {
 		name: 'dlv',
diff --git a/src/stateUtils.ts b/src/stateUtils.ts
index 72402ac..c7b49c2 100644
--- a/src/stateUtils.ts
+++ b/src/stateUtils.ts
@@ -8,7 +8,7 @@
 let globalState: vscode.Memento;
 let workspaceState: vscode.Memento;
 
-export function getFromGlobalState(key: string, defaultValue?: any) {
+export function getFromGlobalState(key: string, defaultValue?: any): any {
 	if (!globalState) {
 		return defaultValue;
 	}
diff --git a/src/telemetry.ts b/src/telemetry.ts
index ad66384..d0e557e 100644
--- a/src/telemetry.ts
+++ b/src/telemetry.ts
@@ -6,10 +6,10 @@
 import vscode = require('vscode');
 import TelemetryReporter from 'vscode-extension-telemetry';
 
-export const extensionId: string = 'ms-vscode.Go';
+export const extensionId: string = 'golang.go-nightly';
 const extension = vscode.extensions.getExtension(extensionId);
 const extensionVersion: string = extension ? extension.packageJSON.version : '';
-const aiKey: string = 'AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217';
+const aiKey: string = '';  // Empty aiKey disables telemetry.
 
 export function sendTelemetryEventForModulesUsage() {
 	/* __GDPR__
diff --git a/src/util.ts b/src/util.ts
index 25ce19e..3e12248 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -330,7 +330,7 @@
 		case 1:
 			vendorSupport =
 				goVersion.sv.minor > 6 ||
-				((goVersion.sv.minor === 5 || goVersion.sv.minor === 6) && process.env['GO15VENDOREXPERIMENT'] === '1')
+					((goVersion.sv.minor === 5 || goVersion.sv.minor === 6) && process.env['GO15VENDOREXPERIMENT'] === '1')
 					? true
 					: false;
 			break;
@@ -905,9 +905,14 @@
 		fs.readdirSync(dir).forEach((file) => {
 			const relPath = path.join(dir, file);
 			if (fs.lstatSync(relPath).isDirectory()) {
-				rmdirRecursive(dir);
+				rmdirRecursive(relPath);
 			} else {
-				fs.unlinkSync(relPath);
+				try {
+					fs.unlinkSync(relPath);
+				} catch (err) {
+					console.log(err);
+				}
+
 			}
 		});
 		fs.rmdirSync(dir);
diff --git a/test/gopls/extension.test.ts b/test/gopls/extension.test.ts
index bde1fd8..d355b3f 100644
--- a/test/gopls/extension.test.ts
+++ b/test/gopls/extension.test.ts
@@ -7,9 +7,7 @@
 import * as fs from 'fs-extra';
 import * as path from 'path';
 import * as vscode from 'vscode';
-import { updateGoPathGoRootFromConfig } from '../../src/goInstallTools';
 import { extensionId } from '../../src/telemetry';
-import { getCurrentGoPath } from '../../src/util';
 
 // Env is a collection of test related variables
 // that define the test environment such as vscode workspace.
@@ -65,7 +63,7 @@
 					files.filter((filename) => filename !== '.gitignore').map((file) => {
 						fs.remove(path.resolve(this.workspaceDir, file));
 					}));
-				});
+			});
 
 			if (!fixtureDirName) {
 				return;
diff --git a/test/gopls/survey.test.ts b/test/gopls/survey.test.ts
new file mode 100644
index 0000000..bccd3d6
--- /dev/null
+++ b/test/gopls/survey.test.ts
@@ -0,0 +1,81 @@
+/*---------------------------------------------------------
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------*/
+
+import * as assert from 'assert';
+import sinon = require('sinon');
+import { shouldPromptForGoplsSurvey, SurveyConfig } from '../../src/goLanguageServer';
+
+suite('gopls survey tests', () => {
+	test('prompt for survey', () => {
+		// global state -> offer survey
+		const testCases: [SurveyConfig, boolean][] = [
+			// User who is activating the extension for the first time.
+			[
+				{},
+				true,
+			],
+			// User who has already taken the survey.
+			[
+				{
+					lastDateAccepted: new Date('2020-04-02'),
+					promptThisMonthTimestamp: new Date('2020-04-10'),
+					lastDatePrompted: new Date('2020-04-02'),
+					prompt: true,
+					promptThisMonth: false,
+				},
+				false,
+			],
+			// User who has declined survey prompting.
+			[
+				{
+					promptThisMonthTimestamp: new Date('2020-04-10'),
+					lastDatePrompted: new Date('2020-04-02'),
+					prompt: false,
+				},
+				false,
+			],
+			// User who hasn't activated the extension in a while, but has opted in to prompting.
+			[
+				{
+					promptThisMonthTimestamp: new Date('2019-04-10'),
+					lastDatePrompted: new Date('2019-01-02'),
+					prompt: true,
+				},
+				true,
+			],
+			// User who hasn't activated the extension in a while, and has never been prompted.
+			[
+				{
+					promptThisMonthTimestamp: new Date('2019-04-10'),
+					lastDatePrompted: new Date('2019-01-02'),
+				},
+				true,
+			],
+			// User who should get prompted this month, but hasn't been yet.
+			[
+				{
+					lastDateAccepted: undefined,
+					promptThisMonthTimestamp: new Date('2020-04-10'),
+					lastDatePrompted: new Date('2019-01-02'),
+					prompt: true,
+					promptThisMonth: true,
+				},
+				true,
+			],
+		];
+		testCases.map(([testConfig, wantPrompt], i) => {
+			// Replace Math.Random so that it always returns 1. This means
+			// that we will always choose to prompt, in the event that the
+			// user can be prompted that month.
+			sinon.replace(Math, 'random', () => 1);
+
+			const now = new Date('2020-04-29');
+			const gotPrompt = shouldPromptForGoplsSurvey(now, testConfig);
+			assert.equal(wantPrompt, gotPrompt, `prompt determination failed for ${i}`);
+
+			sinon.restore();
+		});
+	});
+});
diff --git a/test/gopls/update.test.ts b/test/gopls/update.test.ts
new file mode 100644
index 0000000..31f9ed9
--- /dev/null
+++ b/test/gopls/update.test.ts
@@ -0,0 +1,93 @@
+/*---------------------------------------------------------
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------*/
+
+import * as assert from 'assert';
+import semver = require('semver');
+import sinon = require('sinon');
+import lsp = require('../../src/goLanguageServer');
+import { getTool, Tool } from '../../src/goTools';
+
+suite('gopls update tests', () => {
+	test('prompt for update', () => {
+		const tool = getTool('gopls');
+		const testCases: [string, string, boolean, semver.SemVer][] = [
+			['outdated, tagged', 'v0.3.1', false, tool.latestVersion],
+			['outdated, tagged (pre-release)', '0.3.1', true, tool.latestPrereleaseVersion],
+			['up-to-date, tagged', 'v0.4.0', false, null],
+			['up-to-date tagged (pre-release)', 'v0.4.0', true, tool.latestPrereleaseVersion],
+			['developer version', '(devel)', false, null],
+			['developer version (pre-release)', '(devel)', true, null],
+			['nonsense version', 'nosuchversion', false, null],
+			['nonsense version (pre-release)', 'nosuchversion', true, null],
+			[
+				'latest pre-release',
+				'v0.4.1-pre1 h1:w6e4AmFe6sDSVrgaRkf4WqLyVAlByUrr9QM5xH7z1e4=',
+				false, null,
+			],
+			[
+				'latest pre-release (pre-release)',
+				'v0.4.1-pre1 h1:w6e4AmFe6sDSVrgaRkf4WqLyVAlByUrr9QM5xH7z1e4=',
+				true, null,
+			],
+			[
+				'outdated pre-release version',
+				'v0.3.1-pre1 h1:pBnJjmdcHy5AiRJleOWaakxFHykf8uXzSZKQMd0EA0Q=',
+				false, tool.latestVersion,
+			],
+			[
+				'outdated pre-release version (pre-release)',
+				'v0.3.1-pre1 h1:pBnJjmdcHy5AiRJleOWaakxFHykf8uXzSZKQMd0EA0Q=',
+				true, tool.latestPrereleaseVersion,
+			],
+			[
+				'recent pseudoversion after pre-release',
+				'v0.0.0-20200509030707-2212a7e161a5 h1:0gSpZ0Z2URJoo3oilGRq9ViMLDTlmNSDCyeZNHHrvd4=',
+				false, null,
+			],
+			[
+				'recent pseudoversion before pre-release',
+				'v0.0.0-20200501030707-2212a7e161a5 h1:0gSpZ0Z2URJoo3oilGRq9ViMLDTlmNSDCyeZNHHrvd4=',
+				false, null,
+			],
+			[
+				'recent pseudoversion before pre-release (pre-release)',
+				'v0.0.0-20200501030707-2212a7e161a5 h1:0gSpZ0Z2URJoo3oilGRq9ViMLDTlmNSDCyeZNHHrvd4=',
+				true, tool.latestPrereleaseVersion,
+			],
+			[
+				'outdated pseudoversion',
+				'v0.0.0-20200309030707-2212a7e161a5 h1:0gSpZ0Z2URJoo3oilGRq9ViMLDTlmNSDCyeZNHHrvd4=',
+				false, tool.latestVersion,
+			],
+			[
+				'outdated pseudoversion (pre-release)',
+				'v0.0.0-20200309030707-2212a7e161a5 h1:0gSpZ0Z2URJoo3oilGRq9ViMLDTlmNSDCyeZNHHrvd4=',
+				true, tool.latestPrereleaseVersion,
+			],
+		];
+		testCases.map(async ([name, usersVersion, acceptPrerelease, want], i) => {
+			sinon.replace(lsp, 'getLocalGoplsVersion', async () => {
+				return usersVersion;
+			});
+			sinon.replace(lsp, 'getLatestGoplsVersion', async () => {
+				if (acceptPrerelease) {
+					return tool.latestPrereleaseVersion;
+				}
+				return tool.latestVersion;
+			});
+			sinon.replace(lsp, 'getTimestampForVersion', async (_: Tool, version: semver.SemVer) => {
+				if (version === tool.latestVersion) {
+					return tool.latestVersionTimestamp;
+				}
+				if (version === tool.latestPrereleaseVersion) {
+					return tool.latestPrereleaseVersionTimestamp;
+				}
+			});
+			const got = await lsp.shouldUpdateLanguageServer(tool, 'bad/path/to/gopls', true);
+			assert.equal(got, want, `${name}@${i} failed`);
+			sinon.restore();
+		});
+	});
+});
diff --git a/test/integration/extension.test.ts b/test/integration/extension.test.ts
index 34569ef..63043d5 100644
--- a/test/integration/extension.test.ts
+++ b/test/integration/extension.test.ts
@@ -32,6 +32,7 @@
 import {
 	getBinPath,
 	getCurrentGoPath,
+	getGoConfig,
 	getGoVersion,
 	getImportPath,
 	getToolsGopath,
@@ -1310,7 +1311,7 @@
 					expected.length,
 					labels.length,
 					`expected number of completions: ${expected.length} Actual: ${labels.length} at position(${
-						position.line + 1
+					position.line + 1
 					},${position.character + 1}) ${labels}`
 				);
 				expected.forEach((entry, index) => {
@@ -1435,67 +1436,44 @@
 	});
 
 	test('Build Tags checking', async () => {
-		const config1 = Object.create(vscode.workspace.getConfiguration('go'), {
-			vetOnSave: { value: 'off' },
-			lintOnSave: { value: 'off' },
-			buildOnSave: { value: 'package' },
-			buildTags: { value: 'randomtag' }
-		});
+		// Note: The following checks can't be parallelized because the underlying go build command
+		// runner (goBuild) will cancel any outstanding go build commands.
 
-		const checkWithTags = check(vscode.Uri.file(path.join(fixturePath, 'buildTags', 'hello.go')), config1).then(
-			(diagnostics) => {
-				assert.equal(1, diagnostics.length, 'check with buildtag failed. Unexpected errors found');
-				assert.equal(1, diagnostics[0].errors.length, 'check with buildtag failed. Unexpected errors found');
-				assert.equal(diagnostics[0].errors[0].msg, 'undefined: fmt.Prinln');
-			}
+		const checkWithTags = async (tags: string) => {
+			const fileUri = vscode.Uri.file(path.join(fixturePath, 'buildTags', 'hello.go'));
+			const defaultGoCfg = getGoConfig(fileUri);
+			const cfg = Object.create(defaultGoCfg, {
+				vetOnSave: { value: 'off' },
+				lintOnSave: { value: 'off' },
+				buildOnSave: { value: 'package' },
+				buildTags: { value: tags }
+			}) as vscode.WorkspaceConfiguration;
+
+			const diagnostics = await check(fileUri, cfg);
+			return ([] as string[]).concat(...diagnostics.map<string[]>((d) => {
+				return d.errors.map((e) => e.msg) as string[];
+			}));
+		};
+
+		const errors1 = await checkWithTags('randomtag');
+		assert.deepEqual(errors1, ['undefined: fmt.Prinln'], 'check with buildtag "randomtag" failed. Unexpected errors found.');
+
+		// TODO(hyangah): after go1.13, -tags expects a comma-separated tag list.
+		// For backwards compatibility, space-separated tag lists are still recognized,
+		// but change to a space-separated list once we stop testing with go1.12.
+		const errors2 = await checkWithTags('randomtag other');
+		assert.deepEqual(errors2, ['undefined: fmt.Prinln'],
+			'check with multiple buildtags "randomtag,other" failed. Unexpected errors found.');
+
+		const errors3 = await checkWithTags('');
+		assert.equal(errors3.length, 1,
+			'check without buildtag failed. Unexpected number of errors found' + JSON.stringify(errors3));
+		const errMsg = errors3[0];
+		assert.ok(
+			errMsg.includes(`can't load package: package test/testfixture/buildTags`) ||
+			errMsg.includes(`build constraints exclude all Go files`),
+			`check without buildtags failed. Go files not excluded. ${errMsg}`
 		);
-
-		const config2 = Object.create(vscode.workspace.getConfiguration('go'), {
-			vetOnSave: { value: 'off' },
-			lintOnSave: { value: 'off' },
-			buildOnSave: { value: 'package' },
-			buildTags: { value: 'randomtag othertag' }
-		});
-
-		const checkWithMultipleTags = check(
-			vscode.Uri.file(path.join(fixturePath, 'buildTags', 'hello.go')),
-			config2
-		).then((diagnostics) => {
-			assert.equal(1, diagnostics.length, 'check with multiple buildtags failed. Unexpected errors found');
-			assert.equal(
-				1,
-				diagnostics[0].errors.length,
-				'check with multiple buildtags failed. Unexpected errors found'
-			);
-			assert.equal(diagnostics[0].errors[0].msg, 'undefined: fmt.Prinln');
-		});
-
-		const config3 = Object.create(vscode.workspace.getConfiguration('go'), {
-			vetOnSave: { value: 'off' },
-			lintOnSave: { value: 'off' },
-			buildOnSave: { value: 'package' },
-			buildTags: { value: '' }
-		});
-
-		const checkWithoutTags = check(vscode.Uri.file(path.join(fixturePath, 'buildTags', 'hello.go')), config3).then(
-			(diagnostics) => {
-				assert.equal(1, diagnostics.length, 'check without buildtags failed. Unexpected errors found');
-				assert.equal(
-					1,
-					diagnostics[0].errors.length,
-					'check without buildtags failed. Unexpected errors found'
-				);
-				const errMsg = diagnostics[0].errors[0].msg;
-				assert.equal(
-					errMsg.includes(`can't load package: package test/testfixture/buildTags`) ||
-						errMsg.includes(`build constraints exclude all Go files`),
-					true,
-					`check without buildtags failed. Go files not excluded. ${diagnostics[0].errors[0].msg}`
-				);
-			}
-		);
-
-		return Promise.all([checkWithTags, checkWithMultipleTags, checkWithoutTags]);
 	});
 
 	test('Test Tags checking', async () => {
diff --git a/test/integration/index.ts b/test/integration/index.ts
index 1184be9..12e0292 100644
--- a/test/integration/index.ts
+++ b/test/integration/index.ts
@@ -8,7 +8,7 @@
 export function run(): Promise<void> {
 	// Create the mocha test
 	const mocha = new Mocha({
-		ui: 'tdd'
+		ui: 'tdd',
 	});
 	mocha.useColors(true);
 
diff --git a/test/integration/install.test.ts b/test/integration/install.test.ts
new file mode 100644
index 0000000..778e5f2
--- /dev/null
+++ b/test/integration/install.test.ts
@@ -0,0 +1,96 @@
+/*---------------------------------------------------------
+ * Copyright (C) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------*/
+
+import AdmZip = require('adm-zip');
+import * as assert from 'assert';
+import fs = require('fs');
+import os = require('os');
+import path = require('path');
+import sinon = require('sinon');
+import util = require('util');
+import vscode = require('vscode');
+import { installTools } from '../../src/goInstallTools';
+import { getTool, getToolAtVersion } from '../../src/goTools';
+import { getGoVersion, rmdirRecursive } from '../../src/util';
+
+suite('Installation Tests', () => {
+	test('install tools', async () => {
+		const goVersion = await getGoVersion();
+		const testCases: string[][] = [
+			['gopls'],
+			['gopls', 'guru'],
+		];
+		const proxyDir = buildFakeProxy([].concat(...testCases));
+
+		for (const missing of testCases) {
+			// Create a temporary directory in which to install tools.
+			const tmpToolsGopath = fs.mkdtempSync(path.join(os.tmpdir(), 'install-test'));
+			fs.mkdirSync(path.join(tmpToolsGopath, 'bin'));
+			fs.mkdirSync(path.join(tmpToolsGopath, 'src'));
+
+			const sandbox = sinon.createSandbox();
+			const utils = require('../../src/util');
+			const toolsGopathStub = sandbox.stub(utils, 'getToolsGopath').returns(tmpToolsGopath);
+			const goConfig = Object.create(vscode.workspace.getConfiguration('go'), {
+				toolsEnvVars: {
+					value: {
+						GOPROXY: `file://${proxyDir}`,
+						GOSUMDB: 'off',
+					}
+				},
+			});
+			const configStub = sandbox.stub(vscode.workspace, 'getConfiguration').returns(goConfig);
+			// TODO(rstambler): Test with versions as well.
+			const missingTools = missing.map((tool) => getToolAtVersion(tool));
+			await installTools(missingTools, goVersion);
+
+			sinon.assert.calledWith(toolsGopathStub);
+			sinon.assert.calledWith(configStub);
+			sandbox.restore();
+
+			// Read the $GOPATH/bin to confirm that the expected tools were
+			// installed.
+			const readdir = util.promisify(fs.readdir);
+			const files = await readdir(path.join(tmpToolsGopath, 'bin'));
+			assert.deepEqual(files, missing, `tool installation failed for ${missing}`);
+
+			// TODO(rstambler): A module cache gets created in $GOPATH/pkg with
+			// different permissions, and fs.chown doesn't seem to work on it.
+			// Not sure how to remove the files so that the temporary directory
+			// can be deleted.
+		}
+
+		rmdirRecursive(proxyDir);
+	});
+});
+
+// buildFakeProxy creates a fake file-based proxy used for testing. The code is
+// mostly adapted from golang.org/x/tools/internal/proxydir/proxydir.go.
+function buildFakeProxy(tools: string[]) {
+	const proxyDir = fs.mkdtempSync(path.join(os.tmpdir(), 'proxydir'));
+	for (const toolName of tools) {
+		const tool = getTool(toolName);
+		const module = tool.importPath;
+		const version = `v1.0.0`; // hardcoded for now
+		const dir = path.join(proxyDir, module, '@v');
+		fs.mkdirSync(dir, { recursive: true });
+
+		// Write the list file.
+		fs.writeFileSync(path.join(dir, 'list'), `${version}\n`);
+
+		// Write the go.mod file.
+		fs.writeFileSync(path.join(dir, `${version}.mod`), `module ${module}\n`);
+
+		// Write the info file.
+		fs.writeFileSync(path.join(dir, `${version}.info`), `{ "Version": "${version}", "Time": "2020-04-07T14:45:07Z" } `);
+
+		// Write the zip file.
+		const zip = new AdmZip();
+		const content = `package main; func main() {};`;
+		zip.addFile(path.join(`${module}@${version}`, 'main.go'), Buffer.alloc(content.length, content));
+		zip.writeZip(path.join(dir, `${version}.zip`));
+	}
+	return proxyDir;
+}
diff --git a/test/runTest.ts b/test/runTest.ts
index ee29d48..b6dcd8a 100644
--- a/test/runTest.ts
+++ b/test/runTest.ts
@@ -8,6 +8,8 @@
 		// Passed to `--extensionDevelopmentPath`
 	const extensionDevelopmentPath = path.resolve(__dirname, '../../');
 
+	let failed = false;
+
 	try {
 		// The path to the extension test script
 		// Passed to --extensionTestsPath
@@ -17,7 +19,7 @@
 		await runTests({ extensionDevelopmentPath, extensionTestsPath });
 	} catch (err) {
 		console.error('Failed to run integration tests' + err);
-		process.exit(1);
+		failed = true;
 	}
 
 	// Integration tests using gopls.
@@ -40,6 +42,11 @@
 		});
 	} catch (err) {
 		console.error('Failed to run gopls tests' + err);
+		// failed = true; TODO(hyangah): reenable this after golang.org/cl/233517
+	}
+
+	if (failed) {
+		process.exit(1);
 	}
 }