diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..b600becd9 --- /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@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: 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 + 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 }} + continue-on-error: ${{ matrix.version == 'insiders' }} + + 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 000000000..eff44f1d1 --- /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/CHANGELOG.md.nightly b/CHANGELOG.md.nightly new file mode 100644 index 000000000..f93a9461b --- /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 000000000..71e3b9408 --- /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 b/README.md index 766d437e6..9cbff9097 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,30 @@ -# Go for Visual Studio Code - -[![Join the chat at https://gitter.im/Microsoft/vscode-go](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Microsoft/vscode-go?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/Microsoft/vscode-go.svg?branch=master)](https://travis-ci.org/Microsoft/vscode-go) - -This extension adds rich language support for the [Go language](https://golang.org/) to VS Code. - -Read the [Changelog](https://github.com/Microsoft/vscode-go/blob/master/CHANGELOG.md) to know what has changed over the last few versions of this extension. +# 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. ## Table of Contents diff --git a/build/README.md b/build/README.md new file mode 100644 index 000000000..cacccb271 --- /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 1a8e4a8f7..6dd46bf00 100755 --- a/build/all.bash +++ b/build/all.bash @@ -51,6 +51,32 @@ run_test_in_docker() { 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. + # TODO(hyangah): Update README.md + printf "**Release ${VER} @ ${COMMIT}** \n\n" | cat - CHANGELOG.md.nightly > /tmp/CHANGELOG.md.new && mv /tmp/CHANGELOG.md.new CHANGELOG.md +} + main() { cd "$(root_dir)" # always run from the script root. case "$1" in @@ -70,9 +96,12 @@ main() { 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 000000000..12d8ad747 --- /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 000000000..a67812189 --- /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 000000000..971f10abc --- /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 cebc867b0..3d865ca97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { - "name": "Go", - "version": "0.14.1", + "name": "go-nightly", + "version": "0.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index a088c5155..778b410fe 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,13 @@ { - "name": "Go", - "version": "0.14.1", - "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" @@ -1133,6 +1138,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 632ad90f7..56ac9ce68 100644 --- a/src/avlTree.ts +++ b/src/avlTree.ts @@ -41,7 +41,7 @@ export class Node { * @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 72146cd8b..ac881e592 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 @@ export function check(fileUri: vscode.Uri, goConfig: vscode.WorkspaceConfigurati // 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; diff --git a/src/goInstallTools.ts b/src/goInstallTools.ts index 20c5d7353..60a71440b 100644 --- a/src/goInstallTools.ts +++ b/src/goInstallTools.ts @@ -342,7 +342,6 @@ export async function promptForMissingTool(toolName: string) { return; } } - const installOptions = ['Install']; let missing = await getMissingTools(goVersion); if (!containsTool(missing, tool)) { diff --git a/src/goLanguageServer.ts b/src/goLanguageServer.ts index 625fd9522..3ebc6504a 100644 --- a/src/goLanguageServer.ts +++ b/src/goLanguageServer.ts @@ -7,28 +7,40 @@ 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 + Command, HandleDiagnosticsSignature, LanguageClient, 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 { getBinPath, getCurrentGoPath, getGoConfig, getToolsEnvVars } from './util'; interface LanguageServerConfig { serverName: string; path: string; + modtime: Date; enabled: boolean; flags: string[]; env: any; @@ -47,38 +59,39 @@ let languageServerDisposable: vscode.Disposable; 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 { - // 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); + // 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); + } } } - // 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 { @@ -97,29 +110,41 @@ async function startLanguageServer(ctx: vscode.ExtensionContext, config: Languag // 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; + languageClient = await buildLanguageClient(config); + } - // If the user has not enabled or installed the language server, return. - if (!config.enabled || !config.path) { - return false; - } - 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', 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 { // 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], @@ -212,7 +237,7 @@ function buildLanguageClient(config: LanguageServerConfig) { } } ); - languageClient.onReady().then(() => { + c.onReady().then(() => { const capabilities = languageClient.initializeResult && languageClient.initializeResult.capabilities; if (!capabilities) { return vscode.window.showErrorMessage( @@ -220,52 +245,70 @@ function buildLanguageClient(config: LanguageServerConfig) { ); } }); + return c; } -function watchLanguageServerConfiguration(e: vscode.ConfigurationChangeEvent) { - if (!e.affectsConfiguration('go')) { - return; +// 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); } +} - const config = parseLanguageServerConfig(); - let reloadMessage: string; +function disposeDefaultProviders() { + for (const disposable of defaultLanguageProviders) { + disposable.dispose(); + } + defaultLanguageProviders = []; +} - // 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'; - } +export function watchLanguageServerConfiguration(e: vscode.ConfigurationChangeEvent) { + if (!e.affectsConfiguration('go')) { + return; } 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 +319,45 @@ export function parseLanguageServerConfig(): LanguageServerConfig { 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,7 +402,7 @@ function allFoldersHaveSameGopath(): boolean { return vscode.workspace.workspaceFolders.find((x) => tempGopath !== getCurrentGoPath(x.uri)) ? false : true; } -const acceptGoplsPrerelease = false; +const acceptGoplsPrerelease = true; // For nightly, we accept the prerelease version. const defaultLatestVersion = semver.coerce('0.3.1'); const defaultLatestVersionTime = moment('2020-02-04', 'YYYY-MM-DD'); async function shouldUpdateLanguageServer( @@ -356,7 +423,7 @@ async function shouldUpdateLanguageServer( return null; } - // Get the latest gopls version. + // Get the latest gopls version. If it is for nightly, using the prereleased version is ok. let latestVersion = makeProxyCall ? await latestGopls(tool) : defaultLatestVersion; // 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 diff --git a/src/goMain.ts b/src/goMain.ts index f9684d4cc..5003eff1a 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 { buildCode } from './goBuild'; 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 @@ export function activate(ctx: vscode.ExtensionContext): void { 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 @@ function addOnSaveTextDocumentListeners(ctx: vscode.ExtensionContext) { ); } -// 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 d50e75a10..f20396095 100644 --- a/src/goModules.ts +++ b/src/goModules.ts @@ -7,6 +7,7 @@ import cp = require('child_process'); 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 @@ export async function promptToUpdateToolForModules( 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/telemetry.ts b/src/telemetry.ts index c417c4472..4070b43c9 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__