sync: merge microsoft/vscode-go@ac02a87 into master
Change-Id: Iecbdc570fa39f27e50fd519c26e6e1cdaef4d841
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 1a8e4a8..7167864 100755
--- a/build/all.bash
+++ b/build/all.bash
@@ -51,6 +51,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
@@ -70,9 +97,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..3d865ca 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": {
diff --git a/package.json b/package.json
index 9799f2b..018baf6 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"
@@ -1127,6 +1132,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 72146cd..ac881e5 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 20c5d73..52683b3 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,
@@ -91,14 +93,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.
@@ -299,10 +293,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 +320,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 +377,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 +508,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 7aa4d0f..202cac8 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,39 @@
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());
- }));
+// 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);
+ }
}
}
- // 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 +111,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 +168,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 +269,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.
@@ -276,21 +343,45 @@
env: toolsEnv,
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 +426,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 +438,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 +462,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 +477,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 +519,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 +528,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 +559,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 +619,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 +646,55 @@
return null;
}
-function goProxy(): string[] {
- const output: string = process.env['GOPROXY'];
- if (!output || !output.trim()) {
- return [];
+// suggestGoplsIssueReport prompts users to file an issue with gopls.
+async function suggestGoplsIssueReport(msg: string) {
+ if (latestConfig.serverName !== 'gopls') {
+ return;
}
- const split = output.trim().split(',');
- return split;
+ 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 f9684d4..5003eff 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: () => {};
+
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 d50e75a..f203960 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 30fccb4..7b6819b 100644
--- a/src/goTools.ts
+++ b/src/goTools.ts
@@ -5,7 +5,8 @@
'use strict';
-import { SemVer } from 'semver';
+import moment = require('moment');
+import semver = require('semver');
import { goLiveErrorsEnabled } from './goLiveErrors';
import { getGoConfig, GoVersion } from './util';
@@ -14,6 +15,27 @@
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;
+}
+
+/**
+ * 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 +51,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;
@@ -146,13 +168,14 @@
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',
},
'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 +265,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 +297,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/telemetry.ts b/src/telemetry.ts
index c417c44..4070b43 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/test/gopls/extension.test.ts b/test/gopls/extension.test.ts
index 66028bd..9736b1f 100644
--- a/test/gopls/extension.test.ts
+++ b/test/gopls/extension.test.ts
@@ -3,9 +3,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.
@@ -61,7 +59,7 @@
files.filter((filename) => filename !== '.gitignore').map((file) => {
fs.remove(path.resolve(this.workspaceDir, file));
}));
- });
+ });
if (!fixtureDirName) {
return;
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/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);
}
}