├── .babelrc ├── .eslintrc ├── .flowconfig ├── .github └── issue_template.md ├── .gitignore ├── .prettierrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── Vagrantfile ├── appveyor.yml ├── flow-libs ├── LICENSE ├── atom-ide-ui-busy-signal.js.flow ├── atom-ide-ui-console.js.flow ├── atom-jasmine.js.flow ├── atom.js.flow └── jasmine.js.flow ├── keymaps └── go-plus.json ├── lib ├── ansi.js ├── autocomplete │ ├── gocodeprovider-helper.js │ ├── gocodeprovider.js │ ├── provider.js │ └── suggestions.js ├── bootstrap.js ├── build │ └── builder.js ├── config │ ├── environment.js │ ├── executor.js │ ├── locator.js │ ├── pathhelper.js │ └── service.js ├── doc │ ├── godoc-panel.js │ ├── godoc-view.js │ └── godoc.js ├── etch-component.js ├── format │ └── formatter.js ├── get │ ├── get-manager.js │ └── service.js ├── go.js ├── guru-utils.js ├── highlight │ └── highlight-provider.js ├── implements │ ├── implements-view.js │ └── implements.js ├── import │ ├── importer-view.js │ └── importer.js ├── info │ ├── information-view.js │ └── information.js ├── lint │ └── linter.js ├── main.js ├── navigator │ ├── definition-provider.js │ ├── definition-types.js │ ├── navigation-stack.js │ └── navigator.js ├── orchestrator.js ├── outline │ └── outline-provider.js ├── output-manager.js ├── output-panel.js ├── package-manager.js ├── panel │ ├── empty-tab-view.js │ ├── go-plus-panel.js │ ├── panel-manager.js │ └── tab.js ├── promise.js ├── references │ └── references-provider.js ├── rename │ └── gorename.js ├── simple-dialog.js ├── tags │ ├── gomodifytags.js │ └── tags-dialog.js ├── test │ ├── gocover-parser.js │ └── tester.js ├── tool-checker.js └── utils.js ├── menus └── go-plus.json ├── package-lock.json ├── package.json ├── spec ├── async-spec-helpers.js ├── autocomplete │ ├── gocodeprovider-helper-spec.js │ ├── gocodeprovider-spec.js │ └── suggestions-spec.js ├── build │ └── builder-spec.js ├── config │ ├── environment-spec.js │ ├── executor-spec.js │ ├── locator-spec.js │ ├── pathhelper-spec.js │ └── tools │ │ ├── env │ │ ├── env_darwin_amd64 │ │ ├── env_linux_amd64 │ │ ├── env_windows_amd64.exe │ │ └── main.go │ │ ├── go │ │ ├── env_darwin.go │ │ ├── env_linux.go │ │ ├── env_windows.go │ │ ├── go_darwin_amd64 │ │ ├── go_linux_amd64 │ │ ├── go_windows_amd64.exe │ │ └── main.go │ │ └── pwd │ │ ├── main.go │ │ ├── pwd_darwin_amd64 │ │ ├── pwd_linux_amd64 │ │ └── pwd_windows_amd64.exe ├── doc │ └── godoc-spec.js ├── fixtures │ ├── basic │ │ └── main.go │ ├── doc.go │ ├── format │ │ └── gofmt.go │ ├── go-plus-issue-307 │ │ └── main.go │ ├── go-plus-issue-745 │ │ └── main.go │ ├── gofmt.go │ ├── gomodifytags │ │ └── foo.go │ ├── gorename │ │ ├── expected │ │ └── main.go │ ├── implements │ │ └── main.go │ ├── main.go │ ├── navigator │ │ ├── bar.go │ │ └── foo.go │ ├── outline │ │ └── outline.go │ ├── test │ │ ├── coverage.out │ │ └── src │ │ │ └── github.com │ │ │ └── testuser │ │ │ └── example │ │ │ ├── go-plus.go │ │ │ └── go-plus_test.go │ └── usage │ │ └── referrers-1.json ├── format │ └── formatter-spec.js ├── get │ ├── get-manager-spec.js │ └── provider-spec.js ├── highlight │ └── highlight-provider-spec.js ├── implements │ └── implements-spec.js ├── import │ └── importer-spec.js ├── main │ └── main-spec.js ├── navigator │ └── navigator-spec.js ├── orchestrator-spec.js ├── outline │ └── outline-provider-spec.js ├── output-panel-spec.js ├── panel-manager-spec.js ├── references │ └── references-provider-spec.js ├── rename │ └── gorename-spec.js ├── spec-helpers.js ├── tags │ └── gomodifytags-spec.js ├── test │ ├── gocover-parser-spec.js │ └── tester-spec.js └── utils-spec.js └── styles ├── ansi.less ├── etch-octicon.less ├── go-plus-accordion.less ├── go-plus-guru.atom-text-editor.less ├── go-plus-output.less ├── go-plus-panel.less ├── go-plus-status-bar.less ├── go-plus-table.less ├── go-plus-test.atom-text-editor.less ├── godoc.less ├── gomodifytags.less └── overrides.less /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "sourceMap": "inline", 3 | "plugins": [ 4 | ["transform-class-properties", {}], 5 | ["transform-es2015-modules-commonjs", {"strictMode": false}], 6 | ["transform-object-rest-spread", {}], 7 | ["transform-flow-strip-types", {}], 8 | ["babel-plugin-transform-react-jsx"] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "es6": true, 5 | "browser": true 6 | }, 7 | "globals": { 8 | "advanceClock": true, 9 | "atom": true, 10 | "waitsForPromise": true 11 | }, 12 | "parser": "babel-eslint", 13 | "parserOptions": { 14 | "ecmaFeatures": { 15 | "jsx": true 16 | } 17 | }, 18 | "rules": { 19 | "react/jsx-uses-vars": "error", 20 | "react/jsx-uses-react": "error", 21 | "flowtype/define-flow-type": 1, 22 | "flowtype/use-flow-type": 1 23 | }, 24 | "plugins": [ 25 | "promise", 26 | "react", 27 | "eslint-plugin-flowtype" 28 | ], 29 | "extends": [ 30 | "eslint:recommended", 31 | "plugin:promise/recommended", 32 | "plugin:prettier/recommended" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | flow-libs/ 7 | 8 | [lints] 9 | 10 | [options] 11 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | ### Help, format on save stopped working when I upgraded to v6.0.0+ 2 | 3 | Format on save must now be enabled in the settings for `atom-ide-ui`. 4 | See the [release notes](https://github.com/joefitzgerald/go-plus/releases/tag/v6.0.0) 5 | for how to enable this. 6 | 7 | ### Prerequisites 8 | 9 | * [ ] Have you tried launching `atom .` from the terminal in your project's directory? 10 | * [ ] Have you verified the output from `go env` is correct? If it is, please include the output in this issue. 11 | * [ ] Have you updated Atom to the latest version? 12 | * [ ] Have you tried using [Atom Beta](https://atom.io/beta), which can be run side-by-side with Atom Stable? Is the behavior the same, or different? 13 | * [ ] Have you updated your Atom packages to the latest versions? 14 | * [ ] Have you read the [FAQ](https://github.com/joefitzgerald/go-plus/wiki/FAQ)? 15 | * [ ] Have you searched [the issues](https://github.com/joefitzgerald/go-plus/issues?utf8=%E2%9C%93&q=is%3Aissue) to see if others are experiencing the same issue? 16 | 17 | ### Description 18 | 19 | [Description of the bug or feature] 20 | 21 | ### Output from `atom -v && apm -v` 22 | 23 | [`atom -v && apm -v` output here] 24 | 25 | ### Output From `go env` 26 | 27 | [`go env` output here] 28 | 29 | ### Steps to Reproduce 30 | 31 | 1. [First Step] 32 | 2. [Second Step] 33 | 3. [and so on...] 34 | 35 | ### Expected Behavior 36 | 37 | [What you expected to happen] 38 | 39 | ### Actual Behavior 40 | 41 | [What actually happened] 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | /spec/config/tools/go/go.json 5 | /spec/config/tools/**/*openbsd* 6 | /spec/config/tools/**/*freebsd* 7 | /spec/config/tools/**/*netbsd* 8 | /spec/config/tools/**/*386* 9 | /spec/config/tools/**/*arm* 10 | /.vagrant 11 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "arrowParens": "avoid", 5 | 6 | "overrides": [ 7 | { 8 | "files": "*.js", 9 | "options": { 10 | "parser": "flow" 11 | } 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.12.x 5 | 6 | matrix: 7 | fast_finish: true 8 | 9 | os: 10 | - linux 11 | - osx 12 | 13 | env: 14 | global: 15 | - GO111MODULE=off 16 | - APM_TEST_PACKAGES="go-signature-statusbar go-debug" 17 | matrix: 18 | - ATOM_CHANNEL=beta 19 | - ATOM_CHANNEL=stable 20 | 21 | notifications: 22 | email: 23 | on_success: never 24 | on_failure: change 25 | 26 | install: 27 | - nvm install 8 28 | - go get -u golang.org/x/tools/cmd/goimports 29 | - go get -u golang.org/x/tools/cmd/gorename 30 | - go get -u github.com/sqs/goreturns 31 | - go get -u github.com/mdempsky/gocode 32 | - go get -u github.com/alecthomas/gometalinter 33 | - go get -u github.com/zmb3/gogetdoc 34 | - go get -u github.com/zmb3/goaddimport 35 | - go get -u github.com/rogpeppe/godef 36 | - go get -u github.com/fatih/gomodifytags 37 | - go get -u golang.org/x/tools/cmd/guru 38 | - go get -u github.com/ramya-rao-a/go-outline 39 | - go get -u github.com/tpng/gopkgs 40 | 41 | script: 42 | - 'curl -s https://raw.githubusercontent.com/atom/ci/master/build-package.sh | sh' 43 | 44 | sudo: false 45 | 46 | git: 47 | depth: 10 48 | 49 | addons: 50 | apt: 51 | packages: 52 | - build-essential 53 | - git 54 | - libgnome-keyring-dev 55 | - fakeroot 56 | 57 | branches: 58 | only: 59 | - master 60 | - /^greenkeeper-.*$/ 61 | - /^greenkeeper/.*$/ 62 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## `go-plus` Changelog 2 | 3 | Please visit [https://github.com/joefitzgerald/go-plus/releases](https://github.com/joefitzgerald/go-plus/releases) for the `go-plus` changelog. 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [go-plus](https://atom.io/packages/go-plus) [![Build Status](https://travis-ci.org/joefitzgerald/go-plus.svg?branch=master)](https://travis-ci.org/joefitzgerald/go-plus) [![Build status](https://ci.appveyor.com/api/projects/status/d0cekvaprt9wo1et/branch/master?svg=true)](https://ci.appveyor.com/project/joefitzgerald/go-plus/branch/master) [![Slack](https://img.shields.io/badge/atom_slack-%23go--plus-blue.svg?style=flat)](https://atom-slack.herokuapp.com) [![Slack](https://img.shields.io/badge/gophers_slack-%23go--plus-blue.svg?style=flat)](https://gophersinvite.herokuapp.com) 2 | 3 | [![Greenkeeper badge](https://badges.greenkeeper.io/joefitzgerald/go-plus.svg)](https://greenkeeper.io/) 4 | 5 | > An Improved [Go](https://www.golang.org) Experience For The [Atom Editor](https://atom.io) 6 | 7 | ![image](https://user-images.githubusercontent.com/7527103/48982584-cd537600-f0a1-11e8-9101-e43dc2064223.png) 8 | 9 | - Github: https://github.com/joefitzgerald/go-plus 10 | - Atom: https://atom.io/packages/go-plus 11 | 12 | ## Overview 13 | 14 | This package includes the following functionality: 15 | 16 | - Display information about your current go installation, by running `go version` and `go env` 17 | - Autocomplete using `gocode` 18 | - Format your code with `gofmt`, `goimports`, or `goreturns`; 19 | optionally run one of these tools on save of any `.go` file 20 | - Run `go install .` and `go test -c -o {tempdir} .` to verify your code compiles 21 | and to keep `gocode` suggestions up to date 22 | - Run a variety of linters (e.g. `golint`, `vet`, etc.) against your code using [`gometalinter`](https://github.com/alecthomas/gometalinter), [`revive`](https://github.com/mgechev/revive) or [`golangci-lint`](https://github.com/golangci/golangci-lint) 23 | - Run tests, display test output, and display test coverage using `go test -coverprofile` 24 | - Display documentation for identifiers in source code using 25 | [`gogetdoc`](https://github.com/zmb3/gogetdoc) 26 | - Rename the symbol under your cursor using `gorename` 27 | - Go to definition using `guru` or `godef` 28 | - Highlight occurrences of an identifier using `guru` 29 | - Find usages of an identifier using `guru` 30 | 31 | You can add debug functionality to Atom by installing the following package: 32 | 33 | - [go-debug](https://atom.io/packages/go-debug): Debug your package / tests using [`delve`](https://github.com/derekparker/delve) 34 | 35 | ## Builds 36 | 37 | ### How Are The Builds Performed? 38 | 39 | The following commands are run for the directory of the current file: 40 | 41 | - `go install .` (for normal `.go` files) 42 | - `go test -o {tmpdir} -c .` (for `_test.go` files) 43 | 44 | ### Why Are You Running `go install` Instead Of `go build`? 45 | 46 | `gocode` (and a few other tools, like `gotype`) work on `.a` files (i.e. the package object archive), and the way to keep these up to date is to run `go install` periodically. This ensures your autocomplete suggestions are kept up to date. 47 | 48 | ## Platforms 49 | 50 | The package has CI for OS X, Windows and Ubuntu. 51 | 52 | ## Installing Missing Tools 53 | 54 | If you are missing any required tools, you may be prompted to install them. You can also manually install the required tools in your terminal: 55 | 56 | ``` 57 | go get -u golang.org/x/tools/cmd/goimports 58 | go get -u golang.org/x/tools/cmd/gorename 59 | go get -u github.com/sqs/goreturns 60 | go get -u github.com/mdempsky/gocode 61 | go get -u github.com/alecthomas/gometalinter 62 | go get -u github.com/mgechev/revive 63 | go get -u github.com/golangci/golangci-lint/cmd/golangci-lint 64 | go get -u github.com/zmb3/gogetdoc 65 | go get -u github.com/zmb3/goaddimport 66 | go get -u github.com/rogpeppe/godef 67 | go get -u golang.org/x/tools/cmd/guru 68 | go get -u github.com/fatih/gomodifytags 69 | go get -u github.com/tpng/gopkgs 70 | go get -u github.com/ramya-rao-a/go-outline 71 | ``` 72 | 73 | ## Having Issues? 74 | 75 | Please consult the [FAQ](https://github.com/joefitzgerald/go-plus/wiki/FAQ) prior to [opening an issue](https://github.com/joefitzgerald/go-plus/issues/new): https://github.com/joefitzgerald/go-plus/wiki/FAQ 76 | 77 | If you have an issue with debugging, file an issue with [`go-debug`](https://github.com/lloiser/go-debug) [here](https://github.com/lloiser/go-debug/issues/new). 78 | 79 | ## Maintainers 80 | 81 | - Joe Fitzgerald ([@joefitzgerald](https://github.com/joefitzgerald)) 82 | - Zac Bergquist ([@zmb3](https://github.com/zmb3)) 83 | - Lukas Beranek ([@lloiser](https://github.com/lloiser)) 84 | 85 | ## Contributors 86 | 87 | A list of contributors can be found at https://github.com/joefitzgerald/go-plus/graphs/contributors. Thank you so much to everyone has contributed to the package :heart:. You are awesome! 88 | 89 | ## Contributing 90 | 91 | Contributions are greatly appreciated. Please fork this repository, make your 92 | changes, and open a pull request. See [Contributing](https://github.com/joefitzgerald/go-plus/wiki/Contributing) for detailed instructions. 93 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | config.vm.box = "mwrock/Windows2012R2" 6 | 7 | config.vm.provider "virtualbox" do |vb| 8 | vb.gui = true 9 | vb.cpus = 2 10 | vb.memory = "4096" 11 | end 12 | 13 | config.vm.provision "shell", inline: <<-SHELL 14 | choco install git golang gitkraken -y 15 | [Environment]::SetEnvironmentVariable("PATH", "$env:USERPROFILE\\go\\bin;$env:PATH", "User") 16 | (New-Object System.Net.WebClient).DownloadFile('https://atom.io/download/windows_x64?channel=beta', 'C:\Windows\Temp\atom.exe')" NUL 27 | - go version 28 | - go env 29 | - mkdir c:\gopath 30 | - go get -u golang.org/x/tools/cmd/goimports 31 | - go get -u golang.org/x/tools/cmd/gorename 32 | - go get -u github.com/sqs/goreturns 33 | - go get -u github.com/mdempsky/gocode 34 | - go get -u github.com/alecthomas/gometalinter 35 | - go get -u github.com/zmb3/gogetdoc 36 | - go get -u github.com/zmb3/goaddimport 37 | - go get -u github.com/rogpeppe/godef 38 | - go get -u github.com/fatih/gomodifytags 39 | - go get -u golang.org/x/tools/cmd/guru 40 | - go get -u github.com/tpng/gopkgs 41 | - go get -u github.com/ramya-rao-a/go-outline 42 | 43 | build_script: 44 | - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/atom/ci/master/build-package.ps1')) 45 | 46 | test: off 47 | deploy: off 48 | -------------------------------------------------------------------------------- /flow-libs/LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For atom-ide-ui software 4 | 5 | Copyright (c) 2017-present, Facebook, Inc. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Facebook nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /flow-libs/atom-ide-ui-busy-signal.js.flow: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @flow 10 | * @format 11 | */ 12 | 13 | type NuclideUri = any 14 | 15 | export type BusySignalOptions = {| 16 | // Can say that a busy signal will only appear when a given file is open. 17 | // Default = null, meaning the busy signal applies to all files. 18 | onlyForFile?: NuclideUri, 19 | // Is user waiting for computer to finish a task? (traditional busy spinner) 20 | // or is the computer waiting for user to finish a task? (action required) 21 | // Default = spinner. 22 | waitingFor?: 'computer' | 'user', 23 | // Debounce it? default = true for busy-signal, and false for action-required. 24 | debounce?: boolean, 25 | // If onClick is set, then the tooltip will be clickable. Default = null. 26 | onDidClick?: () => void, 27 | // If set to true, the busy signal tooltip will be immediately revealed 28 | // when it first becomes visible (without explicit mouse interaction). 29 | revealTooltip?: boolean, 30 | |}; 31 | 32 | export type BusySignalService = { 33 | // Activates the busy signal with the given title and returns the promise 34 | // from the provided callback. 35 | // The busy signal automatically deactivates when the returned promise 36 | // either resolves or rejects. 37 | reportBusyWhile( 38 | title: string, 39 | f: () => Promise, 40 | options?: BusySignalOptions, 41 | ): Promise, 42 | 43 | // Activates the busy signal. Set the title in the returned BusySignal 44 | // object (you can update the title multiple times) and dispose it when done. 45 | reportBusy(title: string, options?: BusySignalOptions): BusyMessage, 46 | 47 | // This is a no-op. When someone consumes the busy service, they get back a 48 | // reference to the single shared instance, so disposing of it would be wrong. 49 | dispose(): void, 50 | }; 51 | 52 | export type BusyMessage = { 53 | // You can set/update the title. 54 | setTitle(title: string): void, 55 | // Dispose of the signal when done to make it go away. 56 | dispose(): void, 57 | }; 58 | -------------------------------------------------------------------------------- /flow-libs/atom-ide-ui-console.js.flow: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2017-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | * @flow 10 | * @format 11 | */ 12 | 13 | export type ConsoleApi = { 14 | // The primary means of interacting with the console. 15 | // TODO: Update these to be `(object: any, ...objects: Array): void` to allow for logging objects. 16 | log(object: string, _: void): ?RecordToken, 17 | error(object: string, _: void): ?RecordToken, 18 | warn(object: string, _: void): ?RecordToken, 19 | info(object: string, _: void): ?RecordToken, 20 | success(object: string, _: void): ?RecordToken, 21 | 22 | // A generic API for sending a message of any level (log, error, etc.). 23 | append(message: Message): ?RecordToken, 24 | 25 | // Dispose of the console. Invoke this when your package is disabled. 26 | dispose(): void, 27 | 28 | // Set the status of the source. See "Stoppable Sources" below. 29 | setStatus(status: ConsoleSourceStatus): void, 30 | }; 31 | -------------------------------------------------------------------------------- /flow-libs/atom-jasmine.js.flow: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the license found in the LICENSE file in 6 | * the root directory of this source tree. 7 | * 8 | * @flow 9 | */ 10 | 11 | // Type declarations for Atom's extensions to Jasmine v1.3 12 | // https://github.com/atom/atom/blob/master/spec/spec-helper.coffee 13 | 14 | /** Note that waitsForPromise has an optional first argument. */ 15 | declare function waitsForPromise( 16 | optionsOrFunc: {timeout?: number, shouldReject?: boolean, label?: string} | () => Promise, 17 | func?: () => Promise 18 | ): void; 19 | 20 | /** 21 | * deltaInMilliseconds defaults to 1. 22 | */ 23 | declare function advanceClock(deltaInMilliseconds?: number): void; 24 | -------------------------------------------------------------------------------- /flow-libs/jasmine.js.flow: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the license found in the LICENSE file in 6 | * the root directory of this source tree. 7 | * 8 | * @flow 9 | */ 10 | 11 | // Type declarations for Jasmine v1.3 12 | // https://jasmine.github.io/1.3/introduction.html 13 | 14 | // Disabling Specs and Suites 15 | // https://jasmine.github.io/1.3/introduction.html#section-Disabling_Specs_and_Suites 16 | declare function xdescribe(title: string, spec: () => mixed): void; 17 | declare function xit(title: string, spec: () => mixed): void; 18 | 19 | // Declaring, describing, and grouping tests 20 | declare function afterEach(func: () => mixed): void; 21 | declare function beforeEach(func: () => mixed): void; 22 | declare function describe(title: string, spec: () => mixed): void; 23 | declare function expect(actual: mixed): any; 24 | declare function it(title: string, spec: () => mixed): void; 25 | 26 | // Spies 27 | // https://jasmine.github.io/1.3/introduction.html#section-Spies 28 | type JasmineSpyCall = { 29 | args: Array, 30 | }; 31 | 32 | type JasmineSpy = { 33 | (...args: Array): any, 34 | andCallFake(fake: (...args: Array) => mixed): JasmineSpy, 35 | andCallThrough(): JasmineSpy, 36 | argsForCall: Array>, 37 | andReturn(value: T): JasmineSpy, 38 | andThrow(error: mixed): JasmineSpy, 39 | callCount: number, 40 | calls: Array, 41 | identity: string, 42 | mostRecentCall: JasmineSpyCall, 43 | wasCalled: boolean, 44 | reset(): void, 45 | }; 46 | 47 | declare function spyOn(object: Object, method: string): JasmineSpy; 48 | 49 | // Mocking the JavaScript Clock 50 | // https://jasmine.github.io/1.3/introduction.html#section-Mocking_the_JavaScript_Clock 51 | type JasmineMockClock = { 52 | tick(milliseconds: number): void, 53 | useMock(): void, 54 | }; 55 | 56 | // Asynchronous Support 57 | // https://jasmine.github.io/1.3/introduction.html#section-Asynchronous_Support 58 | declare function runs(func: () => mixed): void; 59 | 60 | // Apparently the arguments for waitsFor() can be specified in any order. 61 | type WaitsForArg = string | number | () => mixed; 62 | 63 | declare function waitsFor( 64 | latchFunction?: WaitsForArg, failureMessage?: WaitsForArg, timeout?: WaitsForArg): void; 65 | 66 | declare function waits(milliseconds: number): void; 67 | 68 | type JasmineEnvironment = { 69 | currentSpec: { 70 | fail(message: string): void, 71 | }, 72 | defaultTimeoutInterval: number, 73 | afterEach: any, 74 | beforeEach: any, 75 | describe: any, 76 | it: any, 77 | }; 78 | 79 | type JasmineSpec = { 80 | addMatchers(matchersPrototype: {[methodName: string]: (expected: any) => boolean}): void, 81 | }; 82 | 83 | type JasmineMatchers = { 84 | message: () => string, 85 | }; 86 | 87 | // Jasmine global 88 | declare var jasmine: { 89 | // Default timeout. 90 | DEFAULT_TIMEOUT_INTERVAL: number, 91 | 92 | Clock: JasmineMockClock, 93 | Matchers: JasmineMatchers, 94 | any(expected: string | Object): mixed, 95 | 96 | /** 97 | * This is a non-standard method that Atom adds to Jasmine via spec-helper.coffee. 98 | * Ideally, we would declare this in atom-jasmine.js, but we can't extend this global here. 99 | */ 100 | attachToDOM(element: Element): ?HTMLElement, 101 | 102 | createSpy: (name?: string) => JasmineSpy, 103 | createSpyObj: (name: string, spyNames: Array) => {[key: string]: JasmineSpy}, 104 | getEnv: () => JasmineEnvironment, 105 | pp: (value: mixed) => string, 106 | unspy: (obj: Object, methodName: string) => void, 107 | useMockClock: () => void, 108 | useRealClock: () => void, 109 | }; 110 | -------------------------------------------------------------------------------- /keymaps/go-plus.json: -------------------------------------------------------------------------------- 1 | { 2 | "atom-workspace": { 3 | "ctrl-alt-shift-g p": "golang:toggle-panel", 4 | "ctrl-alt-shift-g g": "golang:get-package", 5 | "ctrl-alt-shift-g u": "golang:update-tools" 6 | }, 7 | "atom-text-editor[data-grammar~=\"go\"]:not([mini])": { 8 | "ctrl-alt-shift-g c": "golang:toggle-test-with-coverage", 9 | "ctrl-alt-shift-g o": "golang:import-package", 10 | "ctrl-alt-shift-g k": "golang:add-tags", 11 | "ctrl-alt-shift-g l": "golang:remove-tags", 12 | "ctrl-alt-shift-g t": "golang:run-tests", 13 | "ctrl-alt-shift-g b": "golang:implements", 14 | "ctrl-alt-shift-g x": "golang:hide-coverage", 15 | "alt-d": "golang:showdoc", 16 | "alt-r": "golang:gorename" 17 | }, 18 | ".platform-darwin atom-text-editor[data-grammar~=\"go\"]:not([mini])": { 19 | "alt-cmd-g": "golang:godef", 20 | "alt-cmd-shift-g": "golang:godef-return" 21 | }, 22 | ":not(.platform-darwin) atom-text-editor[data-grammar~=\"go\"]:not([mini])": { 23 | "alt-ctrl-g": "golang:godef", 24 | "alt-ctrl-shift-g": "golang:godef-return" 25 | }, 26 | ".gomodifytags": { 27 | "tab": "gomodifytags:focus-next", 28 | "shift-tab": "gomodifytags:focus-previous" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lib/ansi.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx etch.dom */ 3 | /* eslint-disable react/display-name */ 4 | 5 | import etch from 'etch' // eslint-disable-line 6 | import { EtchComponent } from './etch-component' 7 | import parser from 'ansi-style-parser' 8 | 9 | export class AnsiStyle extends EtchComponent { 10 | props: { text?: string, mapText?: string => any } 11 | 12 | constructor(props: Object) { 13 | props = props || {} 14 | super(props) 15 | } 16 | 17 | render() { 18 | const text = this.props.text || '' 19 | const map = this.props.mapText || (text => {text}) 20 | 21 | return ( 22 |
23 | {parser(text).map((chunk, i) => { 24 | const classes = chunk.styles 25 | .map(style => 'go-plus-ansi-' + style) 26 | .join(' ') 27 | return ( 28 | 29 | {map(chunk.text)} 30 | 31 | ) 32 | })} 33 |
34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/autocomplete/gocodeprovider-helper.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import path from 'path' 4 | 5 | const vendorString = '/vendor/' 6 | 7 | export function wantedPackage(buffer: atom$TextBuffer, pos: atom$Point) { 8 | // get the pkg the user tries to autocomplete from the current line 9 | const lineTillPos = buffer.getTextInRange([[pos.row, 0], pos]) 10 | const matches = lineTillPos.match(/(\w+)\.$/) 11 | if (!matches) { 12 | return null 13 | } 14 | return matches[1] 15 | } 16 | 17 | export function addImport( 18 | buffer: atom$TextBuffer, 19 | pkg: string, 20 | offset: number 21 | ) { 22 | // find the "package ..." statement 23 | let row = -1 24 | buffer.scan(/^package /, result => { 25 | row = result.row 26 | if (row === undefined && result.range && result.range.start) { 27 | row = result.range.start.row 28 | } 29 | }) 30 | if (row === -1) { 31 | return null 32 | } 33 | 34 | const text = buffer.getText() 35 | 36 | // import the "pkg" right after the package statement 37 | const importStmt = `import "${pkg}"\n` 38 | const index = buffer.characterIndexForPosition([row + 1, 0]) 39 | const newText = text.substr(0, index) + importStmt + text.substr(index) 40 | const newOffset = offset + importStmt.length 41 | return { text: newText, offset: newOffset } 42 | } 43 | 44 | export function getPackage( 45 | file: string, 46 | gopath: string, 47 | pkgs: string[], 48 | useVendor: boolean 49 | ) { 50 | if (useVendor) { 51 | const dir = path.dirname(file) 52 | const workspace = getCurrentGoWorkspaceFromGOPATH(gopath, dir) 53 | const vendorPkgs = pkgs.filter(pkg => pkg.lastIndexOf(vendorString) > 0) 54 | for (const vpkg of vendorPkgs) { 55 | const relativePath = getRelativePackagePath(dir, workspace, vpkg) 56 | if (relativePath) { 57 | return relativePath 58 | } 59 | } 60 | } 61 | 62 | // take the first non-vendor package 63 | return pkgs.find(pkg => pkg.lastIndexOf(vendorString) === -1) 64 | } 65 | 66 | export function getRelativePackagePath( 67 | currentDir: string, 68 | currentWorkspace: string, 69 | pkg: string 70 | ) { 71 | let magicVendorString = vendorString 72 | let vendorIndex = pkg.lastIndexOf(magicVendorString) 73 | if (vendorIndex === -1) { 74 | magicVendorString = 'vendor/' 75 | if (pkg.startsWith(magicVendorString)) { 76 | vendorIndex = 0 77 | } 78 | } 79 | // Check if current file and the vendor pkg belong to the same root project 80 | // If yes, then vendor pkg can be replaced with its relative path to the "vendor" folder 81 | // If not, then the vendor pkg should not be allowed to be imported. 82 | if (vendorIndex > -1) { 83 | let rootProjectForVendorPkg = path.join( 84 | currentWorkspace, 85 | pkg.substr(0, vendorIndex) 86 | ) 87 | let relativePathForVendorPkg = pkg.substring( 88 | vendorIndex + magicVendorString.length 89 | ) 90 | 91 | if ( 92 | relativePathForVendorPkg && 93 | currentDir.startsWith(rootProjectForVendorPkg) 94 | ) { 95 | return relativePathForVendorPkg 96 | } 97 | return '' 98 | } 99 | 100 | return pkg 101 | } 102 | 103 | export function getCurrentGoWorkspaceFromGOPATH( 104 | gopath: string, 105 | currentDir: string 106 | ) { 107 | let workspaces = gopath.split(path.delimiter) 108 | let currentWorkspace = '' 109 | 110 | // Find current workspace by checking if current file is 111 | // under any of the workspaces in $GOPATH 112 | for (let i = 0; i < workspaces.length; i++) { 113 | let possibleCurrentWorkspace = path.join(workspaces[i], 'src') 114 | if (currentDir.startsWith(possibleCurrentWorkspace)) { 115 | // In case of nested workspaces, (example: both /Users/me and /Users/me/src/a/b/c are in $GOPATH) 116 | // both parent & child workspace in the nested workspaces pair can make it inside the above if block 117 | // Therefore, the below check will take longer (more specific to current file) of the two 118 | if (possibleCurrentWorkspace.length > currentWorkspace.length) { 119 | currentWorkspace = possibleCurrentWorkspace 120 | } 121 | } 122 | } 123 | return currentWorkspace 124 | } 125 | -------------------------------------------------------------------------------- /lib/autocomplete/provider.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type SuggestionType = 4 | | 'package' 5 | | 'variable' 6 | | 'constant' 7 | | 'property' 8 | | 'value' 9 | | 'method' 10 | | 'function' 11 | | 'class' 12 | | 'type' 13 | | 'keyword' 14 | | 'tag' 15 | | 'snippet' 16 | | 'import' 17 | | 'require' 18 | | 'attribute' 19 | 20 | export type Suggestion = { 21 | text?: string, 22 | snippet?: string, 23 | 24 | displayText?: string, 25 | replacementPrefix?: string, 26 | type?: SuggestionType, 27 | leftLabel?: string, 28 | leftLabelHTML?: string, 29 | rightLabel?: string, 30 | rightLabelHTML?: string, 31 | className?: string, 32 | iconHTML?: string, 33 | description?: string, 34 | descriptionMoreURL?: string 35 | } 36 | 37 | export type SuggestionRequest = { 38 | editor: TextEditor, 39 | bufferPosition: atom$Point, 40 | scopeDescriptor: string, 41 | prefix: string, 42 | activatedManually: boolean 43 | } 44 | 45 | export interface AutocompleteProvider { 46 | selector: string; 47 | disableForSelector?: string; 48 | 49 | inclusionPriority?: number; 50 | excludeLowerPriority?: boolean; 51 | suggestionPriority?: number; 52 | filterSuggestions?: boolean; 53 | 54 | +dipose?: () => void; 55 | +onDidInsertSuggestion?: ({ 56 | editor: TextEditor, 57 | triggerPosition: atom$Point, 58 | suggestion: Suggestion 59 | }) => void; 60 | 61 | +getSuggestions: SuggestionRequest => 62 | | Array 63 | | Promise> 64 | | null; 65 | } 66 | -------------------------------------------------------------------------------- /lib/bootstrap.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { CompositeDisposable } from 'atom' 4 | import { isValidEditor } from './utils' 5 | 6 | const workspaceCommands = [ 7 | 'golang:get-package', 8 | 'golang:update-tools', 9 | 'golang:toggle-panel', 10 | 'golang:showdoc' 11 | ] 12 | 13 | const editorCommands = [ 14 | 'golang:run-tests', 15 | 'golang:hide-coverage', 16 | 'golang:gorename' 17 | ] 18 | 19 | class Bootstrap { 20 | onActivated: ?() => void 21 | subscriptions: CompositeDisposable 22 | grammarUsed: boolean 23 | commandUsed: boolean 24 | environmentLoaded: boolean 25 | activated: boolean 26 | 27 | constructor(onActivated: () => void) { 28 | this.onActivated = onActivated 29 | this.subscriptions = new CompositeDisposable() 30 | this.grammarUsed = false 31 | this.commandUsed = false 32 | this.environmentLoaded = false 33 | this.activated = false 34 | this.subscribeToCommands() 35 | this.subscribeToEvents() 36 | } 37 | 38 | subscribeToCommands() { 39 | for (const command of workspaceCommands) { 40 | this.subscriptions.add( 41 | atom.commands.add('atom-workspace', command, () => { 42 | this.setCommandUsed() 43 | }) 44 | ) 45 | } 46 | 47 | for (const command of editorCommands) { 48 | this.subscriptions.add( 49 | atom.commands.add( 50 | 'atom-text-editor[data-grammar~="go"]', 51 | command, 52 | () => { 53 | this.setCommandUsed() 54 | } 55 | ) 56 | ) 57 | } 58 | } 59 | 60 | subscribeToEvents() { 61 | const activationHook = (hookName, fn) => { 62 | const hooks = atom.packages.triggeredActivationHooks 63 | if (hooks && hooks.has(hookName)) { 64 | fn() 65 | return 66 | } 67 | this.subscriptions.add( 68 | atom.packages.onDidTriggerActivationHook(hookName, fn) 69 | ) 70 | } 71 | 72 | activationHook('core:loaded-shell-environment', () => { 73 | this.setEnvironmentLoaded() 74 | }) 75 | 76 | activationHook('language-go:grammar-used', () => { 77 | this.setGrammarUsed() 78 | }) 79 | 80 | this.subscriptions.add( 81 | atom.workspace.observeTextEditors(editor => { 82 | if (isValidEditor(editor)) { 83 | this.setGrammarUsed() 84 | } 85 | }) 86 | ) 87 | } 88 | 89 | setEnvironmentLoaded() { 90 | this.environmentLoaded = true 91 | this.check() 92 | } 93 | 94 | setGrammarUsed() { 95 | this.grammarUsed = true 96 | this.check() 97 | } 98 | 99 | setCommandUsed() { 100 | this.commandUsed = true 101 | this.check() 102 | } 103 | 104 | check() { 105 | if (this.activated) { 106 | return 107 | } 108 | 109 | if (this.environmentLoaded && (this.grammarUsed || this.commandUsed)) { 110 | this.activated = true 111 | this.subscriptions.dispose() 112 | if (this.onActivated) { 113 | this.onActivated() 114 | } 115 | } 116 | } 117 | 118 | dispose() { 119 | if (this.subscriptions) { 120 | this.subscriptions.dispose() 121 | } 122 | this.onActivated = null 123 | this.grammarUsed = false 124 | this.commandUsed = false 125 | this.environmentLoaded = false 126 | this.activated = false 127 | } 128 | } 129 | 130 | export { Bootstrap } 131 | -------------------------------------------------------------------------------- /lib/config/environment.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import * as pathhelper from './pathhelper' 4 | import path from 'path' 5 | 6 | const getenvironment = (): { [string]: ?string } => { 7 | const e = Object.assign({}, process.env) 8 | const g = getgopath() 9 | if (g) { 10 | e.GOPATH = g 11 | } 12 | e.GINKGO_EDITOR_INTEGRATION = 'true' 13 | return e 14 | } 15 | 16 | const getgopath = (): string => { 17 | // Preferred: The Environment 18 | let g = process.env.GOPATH 19 | if (g && g.trim() !== '') { 20 | return pathhelper.expand(process.env, g) 21 | } 22 | 23 | // Fallback: Atom Config 24 | g = (atom.config.get('go-plus.config.gopath'): any) 25 | if (g && g.trim() !== '') { 26 | return pathhelper.expand(process.env, g) 27 | } 28 | 29 | // Default gopath in go 1.8+ 30 | return path.join(pathhelper.home(), 'go') 31 | } 32 | 33 | export { getenvironment, getgopath } 34 | -------------------------------------------------------------------------------- /lib/config/pathhelper.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import path from 'path' 4 | import os from 'os' 5 | 6 | export function expand(env: { [string]: ?string }, thepath: string): string { 7 | if (!thepath || thepath.trim() === '') { 8 | return '' 9 | } 10 | 11 | if (!env) { 12 | return thepath 13 | } 14 | 15 | thepath = thepath.replace(/(~|\$[^\\/:]*|%[^\\;%]*%)+?/gim, (text, match) => { 16 | if (match === '~') { 17 | return home() 18 | } else { 19 | let m = match 20 | if (os.platform() === 'win32') { 21 | m = match.replace(/%/g, '') 22 | } else { 23 | m = match.replace(/\$/g, '') 24 | } 25 | 26 | if (env[m]) { 27 | if (m === 'GOPATH' && env[m].indexOf(path.delimiter) !== -1) { 28 | return expand(env, env[m].split(path.delimiter)[0].trim()) 29 | } else { 30 | return expand(env, env[m]) 31 | } 32 | } else { 33 | return match 34 | } 35 | } 36 | }) 37 | 38 | if (thepath.indexOf(path.delimiter) === -1) { 39 | return resolveAndNormalize(thepath) 40 | } 41 | 42 | const paths = thepath.split(path.delimiter) 43 | let result = '' 44 | for (let pathItem of paths) { 45 | pathItem = resolveAndNormalize(pathItem) 46 | if (result === '') { 47 | result = pathItem 48 | } else { 49 | result = result + path.delimiter + pathItem 50 | } 51 | } 52 | 53 | return result 54 | } 55 | 56 | export function resolveAndNormalize(pathitem: string): string { 57 | if (!pathitem || pathitem.trim() === '') { 58 | return '' 59 | } 60 | const result = path.resolve(path.normalize(pathitem)) 61 | return result 62 | } 63 | 64 | export function home() { 65 | return os.homedir() 66 | } 67 | -------------------------------------------------------------------------------- /lib/config/service.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { Executor } from './executor' 4 | import { Locator } from './locator' 5 | 6 | import type { ExecutorOptions, ExecResult } from './executor' 7 | import type { FindResult, Runtime } from './locator' 8 | 9 | interface PublicExecutor { 10 | exec(string, Array, ExecutorOptions): Promise; 11 | execSync(string, Array, ExecutorOptions): ExecResult; 12 | getOptions('file', editor: TextEditor): ExecutorOptions; 13 | getOptions('project', editor?: ?TextEditor): ExecutorOptions; 14 | } 15 | 16 | export type GoConfig = { 17 | executor: PublicExecutor, 18 | locator: { 19 | runtimes: () => Promise>, 20 | runtime: () => Promise, 21 | gopath: () => string, 22 | findTool: string => Promise 23 | }, 24 | environment: any 25 | } 26 | 27 | class ConfigService { 28 | executor: Executor 29 | locator: Locator 30 | 31 | constructor(getConsole: () => ?ConsoleApi) { 32 | this.executor = new Executor(getConsole) 33 | this.locator = new Locator() 34 | } 35 | 36 | dispose() { 37 | if (this.locator) { 38 | this.locator.dispose() 39 | 40 | // $FlowFixMe 41 | this.locator = null 42 | } 43 | 44 | if (this.executor) { 45 | this.executor.dispose() 46 | 47 | // $FlowFixMe 48 | this.executor = null 49 | } 50 | } 51 | 52 | provide(): GoConfig { 53 | const e = this.executor 54 | const l = this.locator 55 | return { 56 | executor: { 57 | exec: e.exec.bind(e), 58 | execSync: e.execSync.bind(e), 59 | getOptions: e.getOptions.bind(e) 60 | }, 61 | locator: { 62 | runtimes: l.runtimes.bind(l), 63 | runtime: l.runtime.bind(l), 64 | gopath: l.gopath.bind(l), 65 | findTool: l.findTool.bind(l) 66 | }, 67 | environment: l.environment.bind(l) 68 | } 69 | } 70 | } 71 | 72 | export { ConfigService } 73 | -------------------------------------------------------------------------------- /lib/doc/godoc-panel.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { Tab, PanelModel } from './../panel/tab' 4 | import type { GogetdocResult } from './godoc' 5 | import type { GodocView } from './godoc-view' 6 | 7 | export class GodocPanel implements PanelModel { 8 | key: string 9 | tab: Tab 10 | keymap: string 11 | msg: ?string 12 | requestFocus: ?() => Promise 13 | view: ?GodocView 14 | doc: ?GogetdocResult 15 | 16 | constructor() { 17 | this.key = 'reference' 18 | this.tab = { 19 | key: 'reference', 20 | name: 'Reference', 21 | packageName: 'go-plus', 22 | icon: 'book', 23 | order: 300 24 | } 25 | 26 | this.keymap = 'alt-d' 27 | const bindings = atom.keymaps.findKeyBindings({ command: 'golang:showdoc' }) 28 | if (bindings && bindings.length) { 29 | this.keymap = bindings[0].keystrokes 30 | } 31 | } 32 | 33 | dispose() { 34 | this.requestFocus = null 35 | this.view = null 36 | } 37 | 38 | updateMessage(msg: string) { 39 | this.msg = msg 40 | if (this.requestFocus) { 41 | this.requestFocus() 42 | } 43 | if (this.view) { 44 | this.view.update() 45 | } 46 | } 47 | 48 | updateContent(doc: ?GogetdocResult) { 49 | this.msg = null 50 | this.doc = doc 51 | if (!doc) { 52 | return 53 | } 54 | if (this.requestFocus) { 55 | this.requestFocus() 56 | } 57 | if (this.view) { 58 | this.view.update() 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/doc/godoc-view.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx etch.dom */ 3 | 4 | import etch from 'etch' 5 | import { EtchComponent } from './../etch-component' 6 | 7 | import type { GodocPanel } from './godoc-panel' 8 | 9 | export class GodocView extends EtchComponent { 10 | props: { model: GodocPanel } 11 | 12 | constructor(props: { model: GodocPanel }) { 13 | super(props) 14 | if (props.model) { 15 | props.model.view = this 16 | } 17 | this.props = props 18 | } 19 | 20 | render() { 21 | const { msg, doc, keymap } = this.props.model 22 | 23 | if (msg) { 24 | return ( 25 |
26 | 27 | {msg} 28 | 29 |
30 | ) 31 | } 32 | 33 | if (!doc || !doc.decl) { 34 | return ( 35 |
36 | 37 | {`Place the cursor on a symbol and run the "golang:showdoc" command (bound to ${keymap})...`} 38 | 39 |
40 | ) 41 | } 42 | let decl 43 | if (doc.gddo) { 44 | decl = {doc.decl} 45 | } else { 46 | decl = {doc.decl} 47 | } 48 | 49 | return ( 50 |
51 | {doc.import && doc.import.length && ( 52 |
53 | {`import "${doc.import}"`} 54 |
55 |
56 |
57 | )} 58 | {decl} 59 |
60 |
61 | {doc.doc} 62 |
63 | ) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/etch-component.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx etch.dom */ 3 | 4 | import etch from 'etch' 5 | 6 | export interface Renderable { 7 | render(): any; 8 | } 9 | 10 | /* 11 | Public: Abstract class for handling the initialization 12 | boilerplate of an Etch component. 13 | */ 14 | export class EtchComponent implements Renderable { 15 | props: any 16 | refs: Object 17 | element: HTMLElement 18 | 19 | constructor(props: Object) { 20 | this.props = props 21 | 22 | etch.initialize(this) 23 | EtchComponent.setScheduler(atom.views) 24 | } 25 | 26 | /* 27 | Public: Gets the scheduler Etch uses for coordinating DOM updates. 28 | 29 | Returns a {Scheduler} 30 | */ 31 | static getScheduler() { 32 | return etch.getScheduler() 33 | } 34 | 35 | /* 36 | Public: Sets the scheduler Etch uses for coordinating DOM updates. 37 | 38 | * `scheduler` {Scheduler} 39 | */ 40 | static setScheduler(scheduler: any) { 41 | etch.setScheduler(scheduler) 42 | } 43 | 44 | /* 45 | Public: Updates the component's properties and re-renders it. Only the 46 | properties you specify in this object will update – any other properties 47 | the component stores will be unaffected. 48 | 49 | * `props` an {Object} representing the properties you want to update 50 | */ 51 | update(props: any = {}) { 52 | const oldProps = this.props 53 | this.props = Object.assign({}, oldProps, props) 54 | return etch.update(this) 55 | } 56 | 57 | updateSync(props: any = {}) { 58 | const oldProps = this.props 59 | this.props = Object.assign({}, oldProps, props) 60 | return etch.updateSync(this) 61 | } 62 | 63 | /* 64 | Public: Destroys the component, removing it from the DOM. 65 | */ 66 | destroy(removeNode: boolean = false) { 67 | etch.destroy(this, removeNode) 68 | } 69 | 70 | render() { 71 | throw new Error('Etch components must implement a `render` method') 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/format/formatter.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { CompositeDisposable } from 'atom' 4 | import path from 'path' 5 | import { projectPath } from '../utils' 6 | 7 | import type { GoConfig } from './../config/service' 8 | 9 | class Formatter { 10 | subscriptions: CompositeDisposable 11 | goconfig: GoConfig 12 | tool: string // 'gofmt' 'goimports', 'goreturns' 13 | formatterCache: Map 14 | updatingFormatterCache: boolean 15 | priority: number = 2 16 | grammarScopes: Array = ['source.go', 'go'] 17 | 18 | constructor(goconfig: GoConfig) { 19 | this.goconfig = goconfig 20 | this.subscriptions = new CompositeDisposable() 21 | this.updatingFormatterCache = false 22 | atom.project.onDidChangePaths(() => this.updateFormatterCache()) 23 | this.observeConfig() 24 | this.updateFormatterCache() 25 | } 26 | 27 | dispose() { 28 | if (this.subscriptions) { 29 | this.subscriptions.dispose() 30 | } 31 | if (this.formatterCache) { 32 | this.formatterCache.clear() 33 | } 34 | } 35 | 36 | async formatEntireFile( 37 | editor: atom$TextEditor, 38 | range: atom$Range // eslint-disable-line no-unused-vars 39 | ): Promise { 43 | const tool = this.tool 44 | let cmd = this.cachedToolPath(tool) 45 | if (!cmd) { 46 | await this.updateFormatterCache() 47 | cmd = this.cachedToolPath(tool) 48 | } 49 | if (!cmd) { 50 | console.log('skipping format, could not find tool', tool) // eslint-disable-line no-console 51 | return null 52 | } 53 | const options = this.goconfig.executor.getOptions('project', editor) 54 | options.input = editor.getText() 55 | const args = [] 56 | if (tool === 'goimports') { 57 | const p = editor.getPath() 58 | if (p) { 59 | args.push('--srcdir') 60 | args.push(path.dirname(p)) 61 | } 62 | } 63 | const r = await this.goconfig.executor.exec(cmd, args, options) 64 | if (r.exitcode !== 0) return null 65 | const out = r.stdout instanceof Buffer ? r.stdout.toString() : r.stdout 66 | return { formatted: out } 67 | } 68 | 69 | observeConfig() { 70 | this.subscriptions.add( 71 | atom.config.observe('go-plus.format.tool', formatTool => { 72 | this.tool = formatTool 73 | this.updateFormatterCache() 74 | }) 75 | ) 76 | } 77 | 78 | resetFormatterCache() { 79 | this.formatterCache.clear() 80 | } 81 | 82 | async updateFormatterCache(): Promise { 83 | if (this.updatingFormatterCache) { 84 | return Promise.resolve(false) 85 | } 86 | this.updatingFormatterCache = true 87 | 88 | if (!this.goconfig) { 89 | this.updatingFormatterCache = false 90 | return Promise.resolve(false) 91 | } 92 | 93 | const cache: Map = new Map() 94 | const paths = atom.project.getPaths() 95 | const promises = [] 96 | for (const p of paths) { 97 | if (p && p.includes('://')) { 98 | continue 99 | } 100 | for (const tool of ['gofmt', 'goimports', 'goreturns']) { 101 | let key = tool + ':' + p 102 | if (!p) { 103 | key = tool 104 | } 105 | 106 | promises.push( 107 | this.goconfig.locator.findTool(tool).then(cmd => { 108 | if (cmd) { 109 | cache.set(key, cmd) 110 | return cmd 111 | } 112 | return false 113 | }) 114 | ) 115 | } 116 | } 117 | 118 | try { 119 | await Promise.all(promises) 120 | this.formatterCache = cache 121 | this.updatingFormatterCache = false 122 | return this.formatterCache 123 | } catch (e) { 124 | if (e.handle) { 125 | e.handle() 126 | } 127 | console.log(e) // eslint-disable-line no-console 128 | this.updatingFormatterCache = false 129 | } 130 | } 131 | 132 | cachedToolPath(toolName: string) { 133 | if (!this.formatterCache || !toolName) { 134 | return false 135 | } 136 | 137 | const p = projectPath() 138 | if (p) { 139 | const key = toolName + ':' + p 140 | const cmd = this.formatterCache.get(key) 141 | if (cmd) { 142 | return cmd 143 | } 144 | } 145 | 146 | return this.formatterCache.get(toolName) || false 147 | } 148 | } 149 | export { Formatter } 150 | -------------------------------------------------------------------------------- /lib/get/service.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { Disposable } from 'atom' 4 | import { GetManager } from './get-manager' 5 | import type { MultiGetResult } from './get-manager' 6 | import type { GoConfig } from './../config/service' 7 | 8 | import type { InteractiveGetOptions } from './get-manager' 9 | 10 | export type GoGet = { 11 | get: InteractiveGetOptions => Promise, 12 | register: (string, ?Function) => Disposable 13 | } 14 | 15 | class GetService { 16 | goconfig: GoConfig 17 | getmanager: GetManager 18 | 19 | constructor( 20 | goconfig: GoConfig, 21 | getOutput: Function, 22 | busySignal: () => ?BusySignalService 23 | ) { 24 | this.getmanager = new GetManager(goconfig, getOutput, busySignal) 25 | } 26 | 27 | dispose() { 28 | if (this.getmanager) { 29 | this.getmanager.dispose() 30 | } 31 | } 32 | 33 | provide(): GoGet { 34 | const m = this.getmanager 35 | return { 36 | get: m.get.bind(m), 37 | register: m.register.bind(m) 38 | } 39 | } 40 | } 41 | 42 | export { GetService } 43 | -------------------------------------------------------------------------------- /lib/go.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { GoConfig } from './config/service' 4 | 5 | let vendorSupported: ?boolean 6 | export async function isVendorSupported(goconfig: GoConfig): Promise { 7 | if (vendorSupported != null) { 8 | return vendorSupported 9 | } 10 | const runtime = await goconfig.locator.runtime() 11 | if (!runtime || !runtime.semver) { 12 | return goconfig.environment()['GO15VENDOREXPERIMENT'] !== '0' 13 | } 14 | const [major, minor] = runtime.semver.split('.').map(v => parseInt(v, 10)) 15 | 16 | switch (major) { 17 | case 0: 18 | vendorSupported = false 19 | break 20 | case 1: 21 | vendorSupported = 22 | minor > 6 || 23 | ((minor === 5 || minor === 6) && 24 | goconfig.environment()['GO15VENDOREXPERIMENT'] !== '0') 25 | break 26 | default: 27 | vendorSupported = true 28 | break 29 | } 30 | return vendorSupported 31 | } 32 | 33 | const populatePackages = async ( 34 | pkgs: Map, 35 | goconfig: GoConfig 36 | ) => { 37 | const gopkgs = await goconfig.locator.findTool('gopkgs') 38 | if (!gopkgs) return 39 | 40 | const options = goconfig.executor.getOptions('project') 41 | const r = await goconfig.executor.exec(gopkgs, [], options) 42 | const stderr = r.stderr instanceof Buffer ? r.stderr.toString() : r.stderr 43 | if (r.exitcode !== 0) { 44 | // eslint-disable-next-line no-console 45 | console.log( 46 | 'go-plus: "gopkgs" returned the following errors:', 47 | stderr.trim() || `exitcode ${r.exitcode}` 48 | ) 49 | } 50 | const data = r.stdout instanceof Buffer ? r.stdout.toString() : r.stdout 51 | if (!data || !data.trim()) { 52 | return 53 | } 54 | if (!pkgs) { 55 | return 56 | } 57 | 58 | data 59 | .trim() 60 | .split('\n') 61 | .forEach(path => { 62 | if (!pkgs) { 63 | return 64 | } 65 | const name = path 66 | .trim() 67 | .split('/') 68 | .pop() 69 | const p = pkgs.get(name) || [] 70 | pkgs.set(name, p.concat(path.trim())) 71 | }) 72 | 73 | pkgs.forEach(p => { 74 | p.sort() 75 | }) 76 | } 77 | 78 | // TODO: make this work for modules 79 | let pkgs: ?Map 80 | export function allPackages(goconfig: GoConfig): Map { 81 | if (pkgs) { 82 | return pkgs 83 | } 84 | pkgs = new Map() 85 | populatePackages(pkgs, goconfig) 86 | return pkgs 87 | } 88 | -------------------------------------------------------------------------------- /lib/guru-utils.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { 4 | getCursorPosition, 5 | isValidEditor, 6 | utf8OffsetForBufferPosition 7 | } from './utils' 8 | 9 | const scopedModes = [ 10 | 'callees', 11 | 'callers', 12 | 'callstack', 13 | 'pointsto', 14 | 'whicherrs', 15 | 'peers', 16 | 'referrers' 17 | ] 18 | 19 | function buildGuruArchive(editor: ?atom$TextEditor) { 20 | let archive = '' 21 | const editors = editor ? [editor] : atom.workspace.getTextEditors() 22 | for (const e of editors) { 23 | if (e.isModified() && isValidEditor(e)) { 24 | archive += (e.getPath() || '') + '\n' 25 | archive += Buffer.byteLength(e.getText(), 'utf8') + '\n' 26 | archive += e.getText() 27 | } 28 | } 29 | return archive 30 | } 31 | 32 | function computeArgs( 33 | mode: string, 34 | options: ?{ gopath: string }, 35 | editor: atom$TextEditor, 36 | pos: number = currentCursorOffset(editor) 37 | ): ?Array { 38 | if (!mode || !editor || (!pos && pos !== 0)) { 39 | return undefined 40 | } 41 | 42 | const filePath = editor.getPath() 43 | if (!filePath) { 44 | return undefined 45 | } 46 | 47 | const args = ['-json'] 48 | if (scopedModes.includes(mode)) { 49 | const src = 'src/' 50 | let relPath = atom.project.relativizePath(filePath) 51 | if (relPath && relPath.length > 0 && relPath[0] !== null) { 52 | let scope = relPath[0] 53 | const srcIndex = scope.indexOf(src) 54 | if (srcIndex !== -1) { 55 | scope = scope.substring(srcIndex + src.length, scope.length) 56 | } 57 | args.push('-scope', scope + '/...') 58 | } 59 | } 60 | 61 | args.push(mode, `${filePath}:#${pos}`) 62 | return args 63 | } 64 | 65 | function currentCursorOffset(editor: atom$TextEditor): number { 66 | let pos = getCursorPosition(editor) 67 | if (!pos) { 68 | return 0 69 | } 70 | pos = adjustPositionForGuru(pos, editor) 71 | return utf8OffsetForBufferPosition(pos, editor) 72 | } 73 | 74 | function adjustPositionForGuru(pos: atom$Point, editor: atom$TextEditor) { 75 | if (!pos) { 76 | return pos 77 | } 78 | // Unfortunately guru fails if the cursor is at the end of a word 79 | // e.g. "fmt.Println ()" 80 | // ↑ the cursor is here, between "ln" and "(" 81 | // In order to avoid this problem we have to check whether the char 82 | // at the given position is considered a part of an identifier. 83 | // If not step back 1 char as it might contain a valid identifier. 84 | const char = editor.getTextInBufferRange([pos, pos.translate([0, 1])]) 85 | const nonWordChars = editor.getNonWordCharacters(pos) 86 | if (nonWordChars.indexOf(char) >= 0 || /\s/.test(char)) { 87 | return pos.translate([0, -1]) 88 | } 89 | return pos 90 | } 91 | 92 | export { 93 | adjustPositionForGuru, 94 | buildGuruArchive, 95 | currentCursorOffset, 96 | computeArgs 97 | } 98 | -------------------------------------------------------------------------------- /lib/highlight/highlight-provider.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { CompositeDisposable, Range } from 'atom' 4 | import { utf8OffsetForBufferPosition, parseGoPosition } from './../utils' 5 | import { 6 | buildGuruArchive, 7 | computeArgs, 8 | adjustPositionForGuru 9 | } from './../guru-utils' 10 | 11 | import type { GoConfig } from './../config/service' 12 | 13 | class HighlightProvider { 14 | goconfig: GoConfig 15 | running: boolean 16 | subscriptions: CompositeDisposable 17 | shouldDecorate: boolean 18 | priority: number = 2 19 | grammarScopes: Array = ['source.go', 'go'] 20 | 21 | constructor(goconfig: GoConfig) { 22 | this.subscriptions = new CompositeDisposable() 23 | this.goconfig = goconfig 24 | this.running = false 25 | this.subscriptions.add( 26 | atom.config.observe('go-plus.guru.highlightIdentifiers', v => { 27 | this.shouldDecorate = v 28 | }) 29 | ) 30 | } 31 | 32 | async highlight( 33 | editor: atom$TextEditor, 34 | bufferPosition: atom$Point 35 | ): Promise> { 36 | if (this.running) return null 37 | if (!this.shouldDecorate) return null 38 | 39 | const pos = adjustPositionForGuru(bufferPosition, editor) 40 | const offset = utf8OffsetForBufferPosition(pos, editor) 41 | const args = computeArgs('what', null, editor, offset) 42 | if (!args) return null 43 | 44 | const options = {} 45 | options.timeout = 30000 46 | const archive = buildGuruArchive(editor) 47 | if (archive && archive.length) { 48 | options.input = archive 49 | args.unshift('-modified') 50 | } 51 | 52 | const cmd = await this.goconfig.locator.findTool('guru') 53 | if (!cmd) return null 54 | 55 | this.running = true 56 | try { 57 | const r = await this.goconfig.executor.exec(cmd, args, options) 58 | if (r.exitcode !== 0) return null 59 | 60 | const stdout = r.stdout instanceof Buffer ? r.stdout.toString() : r.stdout 61 | const result = JSON.parse(stdout) 62 | 63 | const ranges: Array = [] 64 | let length = 0 65 | for (const enclosing of result.enclosing) { 66 | if (enclosing.desc === 'identifier') { 67 | length = enclosing.end - enclosing.start 68 | break 69 | } 70 | } 71 | for (const id of result.sameids) { 72 | const parsed = parseGoPosition(id) 73 | if ( 74 | parsed && 75 | typeof parsed.column === 'number' && 76 | typeof parsed.line === 'number' 77 | ) { 78 | const start = [parsed.line - 1, parsed.column - 1] 79 | ranges.push(new Range(start, [start[0], start[1] + length])) 80 | } 81 | } 82 | return ranges 83 | } finally { 84 | this.running = false 85 | } 86 | } 87 | 88 | dispose() { 89 | if (this.subscriptions) { 90 | this.subscriptions.dispose() 91 | } 92 | this.running = false 93 | } 94 | } 95 | 96 | export { HighlightProvider } 97 | -------------------------------------------------------------------------------- /lib/implements/implements-view.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx etch.dom */ 3 | /* eslint-disable react/no-unknown-property */ 4 | /* eslint-disable react/jsx-key */ 5 | 6 | import { Point } from 'atom' 7 | import etch from 'etch' 8 | import { EtchComponent } from './../etch-component' 9 | import { parseGoPosition, openFile } from './../utils' 10 | 11 | import type { Implements } from './implements' 12 | import type { GoPos } from './../utils' 13 | 14 | const defaultMessage = 15 | 'To find interface implementations, select a type name and run the `golang:implements` command via the command palette.' 16 | 17 | type ImplementsType = { 18 | name: string, 19 | pos: string, 20 | kind: string 21 | } 22 | 23 | type GuruImplementsResult = { 24 | type: ImplementsType, 25 | to?: Array, 26 | from?: Array, 27 | fromptr?: Array 28 | } 29 | 30 | class ImplementsView extends EtchComponent { 31 | props: string | GuruImplementsResult 32 | 33 | constructor(props: { model?: Implements }) { 34 | super(props) 35 | if (props.model) { 36 | props.model.view = this 37 | } 38 | } 39 | 40 | openFile(gopos: string) { 41 | const pos: GoPos = parseGoPosition(gopos) 42 | if (!pos) { 43 | return 44 | } 45 | 46 | const { file, line = 1, column = 1 } = pos 47 | openFile(file, Point.fromObject([line - 1, column - 1])).catch(err => { 48 | console.log('could not access ' + file, err) // eslint-disable-line no-console 49 | }) 50 | } 51 | 52 | update(props: any) { 53 | this.props = props 54 | return etch.update(this) 55 | } 56 | 57 | structuredContent(obj: GuruImplementsResult) { 58 | // obj.type: the query input 59 | // obj.to: present for implementations of a queried interface 60 | // obj.from: present for interfaces implemented by the queried type 61 | // obj.fromptr: present for interfaces implemented by pointers to the queried type 62 | return ( 63 |
64 | {obj.to && obj.to.length ? this.to(obj) : null} 65 | {obj.from && obj.from.length ? this.from(obj) : null} 66 | {obj.fromptr && obj.fromptr.length ? this.fromptr(obj) : null} 67 |
68 | ) 69 | } 70 | 71 | to(obj: GuruImplementsResult) { 72 | return ( 73 |
74 | {this.header(obj, 'is implemented by')} 75 | {obj.to ? this.items(obj.to) : undefined} 76 |
77 | ) 78 | } 79 | 80 | from(obj: GuruImplementsResult) { 81 | return ( 82 |
83 | {this.header(obj, 'implements')} 84 | {obj.from ? this.items(obj.from) : undefined} 85 |
86 | ) 87 | } 88 | 89 | fromptr(obj: GuruImplementsResult) { 90 | return ( 91 |
92 | {this.header(obj, 'implements (by pointer)')} 93 | {obj.fromptr ? this.items(obj.fromptr) : undefined} 94 |
95 | ) 96 | } 97 | 98 | header(obj: GuruImplementsResult, subtitle: string) { 99 | return ( 100 | 101 | {obj.type.kind + ' type '} 102 | this.openFile(obj.type.pos)}>{obj.type.name} 103 | {' ' + subtitle} 104 | 105 | ) 106 | } 107 | 108 | items(arr: Array) { 109 | return ( 110 |
111 | 112 | {arr.map(item => { 113 | return ( 114 | 118 | 122 | 123 | ) 124 | })} 125 |
119 | {item.name} 120 | {' at ' + item.pos} 121 |
126 |
127 | ) 128 | } 129 | 130 | render() { 131 | if (typeof this.props === 'string') { 132 | return
{this.props}
133 | } 134 | if (!this.props.type) { 135 | return
{defaultMessage}
136 | } 137 | return this.structuredContent(this.props) 138 | } 139 | } 140 | 141 | export { ImplementsView } 142 | -------------------------------------------------------------------------------- /lib/implements/implements.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import os from 'os' 4 | import { CompositeDisposable } from 'atom' 5 | import { getEditor } from './../utils' 6 | import { buildGuruArchive, computeArgs } from './../guru-utils' 7 | 8 | import type { ImplementsView } from './implements-view' 9 | import type { GoConfig } from './../config/service' 10 | import type { PanelModel, Tab } from './../panel/tab' 11 | 12 | class Implements implements PanelModel { 13 | key: string 14 | tab: Tab 15 | goconfig: GoConfig 16 | subscriptions: CompositeDisposable 17 | requestFocus: ?() => Promise 18 | view: ImplementsView 19 | 20 | constructor(goconfig: GoConfig) { 21 | this.goconfig = goconfig 22 | 23 | this.key = 'implements' 24 | this.tab = { 25 | key: 'implements', 26 | name: 'Implements', 27 | packageName: 'go-plus', 28 | icon: 'tasklist', 29 | order: 450, 30 | suppressPadding: true 31 | } 32 | 33 | this.subscriptions = new CompositeDisposable() 34 | this.subscriptions.add( 35 | atom.commands.add('atom-workspace', { 36 | 'golang:implements': () => { 37 | this.handleCommand() 38 | } 39 | }) 40 | ) 41 | } 42 | 43 | handleCommand() { 44 | if (!this.goconfig || !this.goconfig.locator || !this.goconfig.executor) { 45 | return 46 | } 47 | const editor = getEditor() 48 | if (!editor) { 49 | return 50 | } 51 | const args = computeArgs('implements', null, editor) 52 | if (args && args.length) { 53 | return this.runGuru(args) 54 | } 55 | } 56 | 57 | async runGuru(args: Array) { 58 | const options = {} 59 | options.timeout = 20000 60 | const archive = buildGuruArchive() 61 | if (archive && archive.length) { 62 | options.input = archive 63 | args.unshift('-modified') 64 | } 65 | if (this.requestFocus) { 66 | await this.requestFocus() 67 | } 68 | if (this.view) { 69 | this.view.update('running guru ' + args.join(' ')) 70 | } 71 | const cmd = await this.goconfig.locator.findTool('guru') 72 | if (!cmd) { 73 | return false 74 | } 75 | const r = await this.goconfig.executor.exec(cmd, args, options) 76 | if (!r) { 77 | return false 78 | } 79 | 80 | const stderr: string = 81 | r.stderr instanceof Buffer ? r.stderr.toString() : r.stderr 82 | if (r.error || r.exitcode !== 0 || (stderr && stderr.trim() !== '')) { 83 | if (this.view) { 84 | if (r.exitcode === 124) { 85 | this.view.update( 86 | `guru failed: operation timed out after ${options.timeout} ms` 87 | ) 88 | } else { 89 | this.view.update('guru failed' + os.EOL + os.EOL + stderr.trim()) 90 | } 91 | } 92 | return false 93 | } 94 | const stdout: string = 95 | r.stdout instanceof Buffer ? r.stdout.toString() : r.stdout 96 | const obj = JSON.parse(stdout) 97 | if (obj && this.requestFocus) { 98 | this.requestFocus() 99 | .then(() => { 100 | if (this.view) { 101 | this.view.update(obj) 102 | } 103 | return 104 | }) 105 | .catch(e => console.log(e)) // eslint-disable-line no-console 106 | } 107 | } 108 | 109 | dispose() { 110 | if (this.subscriptions) { 111 | this.subscriptions.dispose() 112 | } 113 | } 114 | } 115 | 116 | export { Implements } 117 | -------------------------------------------------------------------------------- /lib/import/importer-view.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import SelectListView from 'atom-select-list' 4 | 5 | export class ImporterView { 6 | modalPanel: ?atom$Panel 7 | selectListView: SelectListView 8 | previouslyFocusedElement: ?HTMLElement 9 | 10 | constructor(props: Object) { 11 | const { items, didConfirmSelection } = props 12 | const font: string = (atom.config.get('editor.fontFamily'): any) 13 | this.selectListView = new SelectListView({ 14 | items, 15 | didConfirmSelection: item => { 16 | this.hide() 17 | didConfirmSelection(item) 18 | }, 19 | didCancelSelection: () => this.hide(), 20 | elementForItem: i => { 21 | const li = document.createElement('li') 22 | li.style.fontFamily = font 23 | li.textContent = i 24 | return li 25 | } 26 | }) 27 | } 28 | 29 | async show(items: Array = []) { 30 | this.previouslyFocusedElement = document.activeElement 31 | this.selectListView.reset() 32 | await this.selectListView.update({ 33 | items, 34 | query: 'Enter a package to import', 35 | selectQuery: true 36 | }) 37 | this.getModalPanel().show() 38 | this.selectListView.focus() 39 | } 40 | 41 | dispose() { 42 | this.destroy() 43 | } 44 | 45 | destroy() { 46 | this.selectListView.destroy() 47 | this.getModalPanel().destroy() 48 | if (this.previouslyFocusedElement) { 49 | this.previouslyFocusedElement.focus() 50 | this.previouslyFocusedElement = null 51 | } 52 | } 53 | 54 | hide() { 55 | this.getModalPanel().hide() 56 | if (this.previouslyFocusedElement) { 57 | this.previouslyFocusedElement.focus() 58 | this.previouslyFocusedElement = null 59 | } 60 | } 61 | 62 | getModalPanel() { 63 | if (!this.modalPanel) { 64 | this.modalPanel = atom.workspace.addModalPanel({ 65 | item: this.selectListView 66 | }) 67 | } 68 | return this.modalPanel 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/import/importer.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { CompositeDisposable } from 'atom' 4 | import path from 'path' 5 | import { ImporterView } from './importer-view' 6 | import { getEditor } from './../utils' 7 | import { allPackages } from './../go' 8 | import * as gcph from './../autocomplete/gocodeprovider-helper' 9 | 10 | import type { GoConfig } from './../config/service' 11 | 12 | // Filters an array of all possible import paths into those that are importable 13 | // from some source package. 14 | const importablePackages = ( 15 | sourceImportPath: string, 16 | packages: Array 17 | ): Array => { 18 | return packages 19 | .filter(pkg => { 20 | // filter out unimportable vendor and internal packages 21 | // https://golang.org/cmd/go/#hdr-Internal_Directories 22 | // https://golang.org/cmd/go/#hdr-Vendor_Directories 23 | const vendor = pkg.indexOf('/vendor/') 24 | const internal = pkg.indexOf('/internal/') 25 | 26 | if (vendor >= 0) { 27 | const vendorRoot = pkg.substr(0, vendor) 28 | if (!sourceImportPath.startsWith(vendorRoot)) { 29 | return false 30 | } 31 | } 32 | if (internal >= 0) { 33 | const internalRoot = pkg.substr(0, internal) 34 | if (!sourceImportPath.startsWith(internalRoot)) { 35 | return false 36 | } 37 | } 38 | 39 | return true 40 | }) 41 | .map(pkg => { 42 | // strip prefix from vendored packages 43 | // (the import should appear the same as non-vendored) 44 | const vs = '/vendor/' 45 | const vendor = pkg.indexOf(vs) 46 | if (vendor === -1) { 47 | return pkg 48 | } 49 | return pkg.substr(vendor + vs.length) 50 | }) 51 | } 52 | 53 | class Importer { 54 | goconfig: GoConfig 55 | subscriptions: CompositeDisposable 56 | view: ImporterView 57 | 58 | constructor(goconfig: GoConfig) { 59 | this.goconfig = goconfig 60 | this.view = new ImporterView({ 61 | items: [], 62 | didConfirmSelection: pkg => this.addImport(pkg) 63 | }) 64 | this.subscriptions = new CompositeDisposable() 65 | this.subscriptions.add( 66 | atom.commands.add( 67 | 'atom-text-editor[data-grammar~="go"]:not([mini])', 68 | 'golang:import-package', 69 | () => this.commandInvoked() 70 | ) 71 | ) 72 | this.subscriptions.add(this.view) 73 | } 74 | 75 | dispose() { 76 | this.subscriptions.dispose() 77 | } 78 | 79 | commandInvoked() { 80 | const pkgMap: Map> = allPackages(this.goconfig) 81 | const pkgs = [].concat.apply([], Array.from(pkgMap.values())) 82 | const editor = getEditor() 83 | const editorPath = editor ? editor.getPath() : null 84 | if (editor && typeof editorPath === 'string') { 85 | const dir = path.dirname(editorPath) 86 | const workspace = gcph.getCurrentGoWorkspaceFromGOPATH( 87 | this.goconfig.locator.gopath(), 88 | dir 89 | ) 90 | 91 | // get the import path of the package we're currently editing 92 | const currentPkg = dir.replace(new RegExp(`^${workspace}/`), '') 93 | 94 | const importable = importablePackages(currentPkg, pkgs) 95 | this.view.show(importable) 96 | } 97 | } 98 | 99 | async addImport(pkg: string) { 100 | const editor = getEditor() 101 | if (!editor) { 102 | return { success: false } 103 | } 104 | const cmd = await this.goconfig.locator.findTool('goaddimport') 105 | if (!cmd) { 106 | atom.notifications.addError('Missing Tool', { 107 | detail: 'Unable to find the `goaddimport` tool.', 108 | dismissable: true 109 | }) 110 | return { success: false } 111 | } 112 | const r = await this.goconfig.executor.exec(cmd, [pkg], { 113 | input: editor.getText() 114 | }) 115 | if (r.error) { 116 | atom.notifications.addError('Error', { 117 | detail: r.error.message, 118 | dismissable: true 119 | }) 120 | return { success: false, r } 121 | } 122 | 123 | if (r.exitcode === 0) { 124 | const stdout = r.stdout instanceof Buffer ? r.stdout.toString() : r.stdout 125 | editor.getBuffer().setTextViaDiff(stdout) 126 | return { success: true, r } 127 | } 128 | 129 | return { success: false, r } 130 | } 131 | } 132 | 133 | export { Importer, importablePackages } 134 | -------------------------------------------------------------------------------- /lib/info/information-view.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx etch.dom */ 3 | /* eslint-disable react/no-unknown-property */ 4 | /* eslint-disable react/no-string-refs */ 5 | 6 | import etch from 'etch' 7 | import { EtchComponent } from './../etch-component' 8 | 9 | import type { Information } from './information' 10 | 11 | type Props = { 12 | model?: Information, 13 | style?: string, 14 | content: string 15 | } 16 | 17 | export class InformationView extends EtchComponent { 18 | props: Props 19 | 20 | constructor(props: Props) { 21 | if (!props.content) { 22 | props.content = 'empty' 23 | } 24 | super(props) 25 | if (props.model) { 26 | props.model.view = this 27 | props.model.updateContent() 28 | } 29 | } 30 | 31 | render() { 32 | let style = 'white-space: pre-wrap;' 33 | if (this.props.style) { 34 | style = style + ' ' + this.props.style 35 | } 36 | return ( 37 | 38 | {this.props.content} 39 | 40 | ) 41 | } 42 | 43 | dispose() { 44 | this.destroy() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/info/information.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import os from 'os' 4 | 5 | import type { GoConfig } from './../config/service' 6 | import type { PanelModel, Tab } from './../panel/tab' 7 | import type { InformationView } from './information-view' 8 | 9 | class Information implements PanelModel { 10 | goconfig: GoConfig 11 | key: string 12 | tab: Tab 13 | running: boolean 14 | view: InformationView 15 | 16 | constructor(goconfig: GoConfig) { 17 | this.goconfig = goconfig 18 | this.key = 'go' 19 | this.tab = { 20 | key: 'go', 21 | name: 'Go', 22 | packageName: 'go-plus', 23 | icon: 'info', 24 | order: 100 25 | } 26 | } 27 | 28 | dispose() {} 29 | 30 | async updateContent() { 31 | if (!this.view || atom.config.get('go-plus.testing')) { 32 | return 33 | } 34 | 35 | const go = await this.goconfig.locator.findTool('go') 36 | if (!go) { 37 | return 38 | } 39 | const opt = this.goconfig.executor.getOptions('project') 40 | try { 41 | const results = await Promise.all([ 42 | this.goconfig.executor.exec(go, ['version'], opt), 43 | this.goconfig.executor.exec(go, ['env'], opt) 44 | ]) 45 | const verStdout = 46 | results[0].stdout instanceof Buffer 47 | ? results[0].stdout.toString() 48 | : results[0].stdout 49 | const verStderr = 50 | results[0].stderr instanceof Buffer 51 | ? results[0].stderr.toString() 52 | : results[0].stderr 53 | const envStdout = 54 | results[1].stdout instanceof Buffer 55 | ? results[1].stdout.toString() 56 | : results[1].stdout 57 | const envStderr = 58 | results[1].stderr instanceof Buffer 59 | ? results[1].stderr.toString() 60 | : results[1].stderr 61 | 62 | let content = '$ go version' + os.EOL 63 | if (verStderr && verStderr.trim()) { 64 | content += verStderr.trim() 65 | } 66 | if (verStdout && verStdout.trim()) { 67 | content += verStdout.trim() 68 | } 69 | content += os.EOL + os.EOL + '$ go env' + os.EOL 70 | if (envStderr && envStderr.trim()) { 71 | content += envStderr.trim() 72 | } 73 | if (envStdout && envStdout.trim()) { 74 | content += envStdout.trim() 75 | } 76 | this.view.update({ content }) 77 | } catch (e) { 78 | if (e.handle) { 79 | e.handle() 80 | } 81 | console.log(e) // eslint-disable-line no-console 82 | this.running = false 83 | return Promise.resolve() 84 | } 85 | } 86 | } 87 | 88 | export { Information } 89 | -------------------------------------------------------------------------------- /lib/navigator/definition-provider.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { Navigator } from './navigator' 4 | import { isValidEditor } from '../utils' 5 | 6 | type Definition = { 7 | path: string, // Path of the file in which the definition is located. 8 | position: atom$Point, // First character of the definition's identifier. 9 | range?: atom$Range, // the range of the entire definition. 10 | name?: string, // display a more human-readable title inside Hyperclick 11 | projectRoot?: string, // used to display a relativized version of path 12 | language: string 13 | } 14 | 15 | type DefinitionQueryResult = { 16 | queryRange: ?Array, 17 | definitions: Array // Must be non-empty. 18 | } 19 | 20 | class DefinitionProvider { 21 | priority: number 22 | grammarScopes: Array 23 | navigator: null | (() => Navigator) 24 | maybeIdentifier: RegExp 25 | disableForSelector: Array 26 | disposed: boolean 27 | 28 | constructor(navigatorFunc: () => Navigator) { 29 | this.priority = 10 30 | this.grammarScopes = ['source.go', 'go'] 31 | this.navigator = navigatorFunc 32 | this.maybeIdentifier = /^[$0-9\w]+$/ 33 | 34 | this.disableForSelector = [ 35 | // original textmate selectors 36 | '.storage.type', 37 | '.string.quoted', 38 | '.keyword', 39 | '.support.function.builtin', 40 | '.constant.numeric.integer', 41 | '.constant.language', 42 | '.variable.other.assignment', 43 | '.variable.other.declaration', 44 | '.comment.line', 45 | 'entity.name.import.go', 46 | 47 | // tree-sitter selectors 48 | 'comment.block', 49 | 'comment.line', 50 | 'string.quoted.double', 51 | 'constant.character.escape', 52 | 'constant.other.rune', 53 | 'constant.numeric.float', 54 | 'constant.language.nil', 55 | 'constant.language.false', 56 | 'constant.language.true', 57 | 'keyword.operator', 58 | 'keyword.import' 59 | ] 60 | this.disposed = false 61 | } 62 | 63 | dispose() { 64 | this.disposed = true 65 | this.navigator = null 66 | this.disableForSelector = [] 67 | } 68 | 69 | async getDefinition( 70 | editor: atom$TextEditor, 71 | position: atom$Point 72 | ): Promise { 73 | if (!isValidEditor(editor)) { 74 | return null 75 | } 76 | 77 | const scopes = editor 78 | .scopeDescriptorForBufferPosition(position) 79 | .getScopesArray() 80 | const disabled = this.disableForSelector.some(s => scopes.includes(s)) 81 | if (disabled) { 82 | console.log('skipping Go definition - current scopes:', scopes) // eslint-disable-line no-console 83 | return null 84 | } 85 | 86 | const nav = this.navigator ? this.navigator() : null 87 | if (!nav) return null 88 | 89 | const loc = await nav.definitionForBufferPosition(position, editor) 90 | if (!loc) return null 91 | 92 | const def = { 93 | path: loc.filepath, 94 | position: loc.pos, 95 | language: 'Go' 96 | } 97 | return { definitions: [def], queryRange: null } 98 | } 99 | } 100 | 101 | export { DefinitionProvider } 102 | -------------------------------------------------------------------------------- /lib/navigator/definition-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type DefLocation = { 4 | filepath: string, 5 | pos: atom$Point, 6 | raw?: string 7 | } 8 | -------------------------------------------------------------------------------- /lib/navigator/navigation-stack.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { openFile } from '../utils' 4 | 5 | import type { DefLocation } from './definition-types' 6 | 7 | class NavigationStack { 8 | maxSize: number 9 | stack: Array 10 | 11 | constructor(maxSize: number = 500) { 12 | this.maxSize = maxSize >= 1 ? maxSize : 1 13 | this.stack = [] 14 | } 15 | 16 | dispose() { 17 | this.stack = [] 18 | } 19 | 20 | isEmpty() { 21 | return this.stack.length === 0 22 | } 23 | 24 | reset() { 25 | this.stack = [] 26 | } 27 | 28 | pushCurrentLocation() { 29 | const editor = atom.workspace.getActiveTextEditor() 30 | if (!editor) { 31 | return 32 | } 33 | const loc: DefLocation = { 34 | pos: editor.getCursorBufferPosition(), 35 | filepath: editor.getURI() || '' 36 | } 37 | 38 | if (!loc.pos.row || !loc.pos.column) { 39 | return 40 | } 41 | 42 | this.push(loc) 43 | } 44 | 45 | // Returns a promise that is complete when navigation is done. 46 | restorePreviousLocation(): Promise { 47 | if (this.isEmpty()) { 48 | return Promise.resolve() 49 | } 50 | 51 | if (!this.stack || this.stack.length < 1) { 52 | return Promise.resolve() 53 | } 54 | 55 | const lastLocation = this.stack.shift() 56 | return openFile(lastLocation.filepath, lastLocation.pos) 57 | } 58 | 59 | push(loc: DefLocation) { 60 | if (!this.stack || !loc) { 61 | return 62 | } 63 | 64 | if (!this.isEmpty() && this.compareLoc(this.stack[0], loc)) { 65 | return 66 | } 67 | this.stack.unshift(loc) 68 | if (this.stack.length > this.maxSize) { 69 | this.stack.splice(-1, this.stack.length - this.maxSize) 70 | } 71 | } 72 | 73 | compareLoc(loc1: DefLocation, loc2: DefLocation) { 74 | if (!loc1 && !loc2) { 75 | return true 76 | } 77 | 78 | if (!loc1 || !loc2) { 79 | return false 80 | } 81 | 82 | const posEqual = (pos1, pos2) => { 83 | if (!pos1 && !pos2) { 84 | return true 85 | } 86 | if (!pos1 || !pos2) { 87 | return false 88 | } 89 | return pos1.column === pos2.column && pos1.row === pos2.row 90 | } 91 | 92 | return loc1.filepath === loc2.filepath && posEqual(loc1.pos, loc2.pos) 93 | } 94 | } 95 | 96 | export { NavigationStack } 97 | -------------------------------------------------------------------------------- /lib/orchestrator.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { CompositeDisposable, Disposable } from 'atom' 4 | import { isValidEditor } from './utils' 5 | 6 | type WillSaveCallback = TextEditor => boolean 7 | type DidSaveCallback = (TextEditor, string) => ?Promise 8 | 9 | export type CallbackKind = 'willSave' | 'didSave' 10 | export type Callback = WillSaveCallback | DidSaveCallback 11 | 12 | class Orchestrator { 13 | subscriptions: CompositeDisposable 14 | willSaveCallbacks: Set 15 | didSaveCallbacks: Map 16 | 17 | constructor() { 18 | this.subscriptions = new CompositeDisposable() 19 | this.willSaveCallbacks = new Set() 20 | this.didSaveCallbacks = new Map() 21 | this.subscribeToEvents() 22 | } 23 | 24 | dispose() { 25 | this.subscriptions.dispose() 26 | 27 | this.willSaveCallbacks.clear() 28 | this.didSaveCallbacks.clear() 29 | } 30 | 31 | // Register a task to be run by the orchestrator at the appropriate time. 32 | // 33 | // Type should be one of 'willSave' or 'didSave'; didSave is the default. The 34 | // distinction is only present to allow formatting to occur at the appropriate 35 | // time. 36 | // 37 | // willSave callbacks: 38 | // These callbacks are invoked synchronously in the order they are registered. 39 | // They should have the form `bool callback(editor)`, and should return true 40 | // to indicate success so that the callback chain can continue. A non-true 41 | // return value will prevent subsequent callbacks from executing. 42 | // 43 | // didSave callbacks: 44 | // These callbacks should have the form `Promise callback(editor, path)`. 45 | // The resulting promise should resolve to indicate success so that the chain 46 | // can continue. A promise that is rejected will prevent future callbacks 47 | // from executing. 48 | // 49 | // Register returns a disposable that can be used to unsubscribe the callback. 50 | register(name: string, callback: Callback, type: CallbackKind = 'didSave') { 51 | if (typeof callback !== 'function') { 52 | throw new Error('callback must be a function') 53 | } 54 | if (type !== 'didSave' && type !== 'willSave') { 55 | throw new Error('type must be a willSave or didSave') 56 | } 57 | if (type === 'willSave') { 58 | const cb: WillSaveCallback = ((callback: any): WillSaveCallback) 59 | this.willSaveCallbacks.add(cb) 60 | return new Disposable(() => { 61 | if (this.willSaveCallbacks) { 62 | this.willSaveCallbacks.delete(cb) 63 | } 64 | }) 65 | } 66 | 67 | const cb: DidSaveCallback = ((callback: any): DidSaveCallback) 68 | this.didSaveCallbacks.set(name, cb) 69 | return new Disposable(() => { 70 | if (this.didSaveCallbacks) { 71 | this.didSaveCallbacks.delete(name) 72 | } 73 | }) 74 | } 75 | 76 | subscribeToEvents() { 77 | this.subscriptions.add( 78 | atom.workspace.observeTextEditors(editor => { 79 | if (!isValidEditor(editor)) { 80 | return 81 | } 82 | // subscribe to buffer will-save events: 83 | const buffer = editor.getBuffer() 84 | const bufferWillSaveSubscription = buffer.onWillSave(() => { 85 | this.bufferWillSave(editor) 86 | }) 87 | 88 | // subscribe to buffer did-save events: 89 | const editorDidSaveSubscription = editor.onDidSave(evt => { 90 | this.editorDidSave(editor, evt.path).catch(() => {}) 91 | }) 92 | 93 | // subscribe to editor destroyed events: 94 | const editorDestroySubscription = editor.onDidDestroy(() => { 95 | bufferWillSaveSubscription.dispose() 96 | editorDestroySubscription.dispose() 97 | editorDidSaveSubscription.dispose() 98 | this.subscriptions.remove(bufferWillSaveSubscription) 99 | this.subscriptions.remove(editorDidSaveSubscription) 100 | this.subscriptions.remove(editorDestroySubscription) 101 | }) 102 | 103 | // add all subscriptions 104 | this.subscriptions.add(bufferWillSaveSubscription) 105 | this.subscriptions.add(editorDidSaveSubscription) 106 | this.subscriptions.add(editorDestroySubscription) 107 | }) 108 | ) 109 | } 110 | 111 | bufferWillSave(editor: TextEditor) { 112 | for (const cb of this.willSaveCallbacks) { 113 | const succeeded = cb(editor) 114 | if (succeeded !== true) { 115 | break 116 | } 117 | } 118 | } 119 | 120 | editorDidSave(editor: TextEditor, path: string): Promise { 121 | return Array.from(this.didSaveCallbacks.entries()).reduce( 122 | (p, [, func]) => p.then(() => func(editor, path) || Promise.resolve()), 123 | Promise.resolve() 124 | ) 125 | } 126 | } 127 | 128 | export { Orchestrator } 129 | -------------------------------------------------------------------------------- /lib/output-manager.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { PanelModel, Tab } from './panel/tab' 4 | import type { OutputPanel } from './output-panel' 5 | 6 | class OutputManager implements PanelModel { 7 | key: string 8 | tab: Tab 9 | output: string 10 | props: Object 11 | view: OutputPanel 12 | requestFocus: ?() => Promise 13 | 14 | constructor() { 15 | this.key = 'output' 16 | this.tab = { 17 | key: 'output', 18 | name: 'Output', 19 | packageName: 'go-plus', 20 | icon: 'check', 21 | order: 200 22 | } 23 | this.output = '' 24 | } 25 | 26 | update(props: Object) { 27 | const oldProps = this.props 28 | this.props = Object.assign({}, oldProps, props) 29 | 30 | const { exitcode = 0 } = this.props 31 | if (exitcode !== 0 && this.requestFocus) { 32 | if ( 33 | atom.config.get('go-plus.panel.focusOnFailure') && 34 | this.requestFocus 35 | ) { 36 | this.requestFocus() 37 | } 38 | } 39 | 40 | if (this.view) { 41 | this.view.update() 42 | } 43 | } 44 | } 45 | 46 | export { OutputManager } 47 | -------------------------------------------------------------------------------- /lib/output-panel.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx etch.dom */ 3 | /* eslint-disable react/no-unknown-property */ 4 | /* eslint-disable react/no-string-refs */ 5 | 6 | import { Point } from 'atom' 7 | import etch from 'etch' // eslint-disable-line no-unused-vars 8 | import path from 'path' 9 | import { EtchComponent } from './etch-component' 10 | import { AnsiStyle } from './ansi' 11 | import { openFile, parseGoPosition, projectPath } from './utils' 12 | 13 | const locationRegex = /([\w-/.\\:]*.go:\d+(:\d+)?)/g 14 | 15 | export class OutputPanel extends EtchComponent { 16 | scrollHeight: number 17 | 18 | constructor(props: Object = {}) { 19 | super(props) 20 | if (this.props.model) { 21 | this.props.model.view = this 22 | } 23 | } 24 | 25 | makeLink(text: string) { 26 | const elements = [] 27 | let lastIndex = 0 28 | let match 29 | 30 | do { 31 | match = locationRegex.exec(text) 32 | if (match && match.hasOwnProperty('index')) { 33 | // take raw text up to this match 34 | elements.push({text.slice(lastIndex, match.index)}) 35 | 36 | const linkText = match[0] 37 | // convert the match to a link 38 | elements.push( 39 | 41 | this.linkClicked(linkText, this.props.model.props.dir) 42 | } 43 | > 44 | {linkText} 45 | 46 | ) 47 | lastIndex = match.index + match[0].length 48 | } 49 | } while (match) 50 | 51 | // raw text from last match to the end 52 | if (lastIndex < text.length) { 53 | elements.push({text.slice(lastIndex)}) 54 | } 55 | 56 | return elements 57 | } 58 | 59 | render() { 60 | let style = '' 61 | let output = '' 62 | if ( 63 | this.props.model && 64 | this.props.model.props && 65 | this.props.model.props.output 66 | ) { 67 | output = this.props.model.props.output 68 | } 69 | 70 | return ( 71 |
77 | 78 |
79 | ) 80 | } 81 | 82 | linkClicked(text: string, dir: string) { 83 | const { file, line = 1, column = 0 } = parseGoPosition(text) 84 | 85 | let filepath 86 | if (path.isAbsolute(file)) { 87 | filepath = file 88 | } else { 89 | const base = dir || projectPath() 90 | if (!base) { 91 | return 92 | } 93 | filepath = path.join(base, file) 94 | } 95 | 96 | const col = column && column > 0 ? column - 1 : 0 97 | openFile(filepath, Point.fromObject([line - 1, col])).catch(err => { 98 | console.log('could not access ' + file, err) // eslint-disable-line no-console 99 | }) 100 | } 101 | 102 | readAfterUpdate() { 103 | const content = this.refs.content 104 | if (!content) { 105 | return 106 | } 107 | 108 | const scrollHeight = content.scrollHeight 109 | if (scrollHeight && this.scrollHeight !== scrollHeight) { 110 | this.scrollHeight = scrollHeight 111 | content.scrollTop = this.scrollHeight 112 | this.update() 113 | } 114 | } 115 | 116 | dispose() { 117 | this.destroy() 118 | } 119 | 120 | destroy() { 121 | super.destroy() 122 | this.props = {} 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/panel/empty-tab-view.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx etch.dom */ 3 | /* eslint-disable react/no-unknown-property */ 4 | 5 | import etch from 'etch' 6 | import { EtchComponent } from './../etch-component' 7 | import Octicon from 'etch-octicon' 8 | 9 | export class EmptyTabView extends EtchComponent { 10 | render() { 11 | return ( 12 |
13 | 14 | 15 | {'The go-plus panel is active when a Go project is loaded.'} 16 |
17 | {'Open a .go file to get started.'} 18 |
19 |
20 | ) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/panel/go-plus-panel.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx etch.dom */ 3 | /* eslint-disable react/no-unknown-property */ 4 | /* eslint-disable react/no-string-refs */ 5 | 6 | import etch from 'etch' 7 | import ResizeObserver from 'resize-observer-polyfill' 8 | import { EtchComponent } from './../etch-component' 9 | import { EmptyTabView } from './empty-tab-view' 10 | import Octicon from 'etch-octicon' 11 | import type { PanelManager } from './panel-manager' 12 | import type { Tab } from './tab' 13 | 14 | export class GoPlusPanel extends EtchComponent { 15 | props: { model: PanelManager } 16 | ro: ResizeObserver 17 | isNarrow: boolean 18 | scrollHeight: number 19 | 20 | constructor(props: { model: PanelManager }) { 21 | super(props) 22 | this.ro = new ResizeObserver(entries => { 23 | for (const entry of entries) { 24 | const { width } = entry.contentRect 25 | const narrow = width < 600 26 | if (this.isNarrow !== narrow) { 27 | this.isNarrow = narrow 28 | this.update() 29 | } 30 | } 31 | }) 32 | this.ro.observe(this.element) 33 | } 34 | 35 | render() { 36 | const panelBodyStyle = { 37 | padding: '10px' 38 | } 39 | 40 | let panelClass = 'go-plus-panel' 41 | if (this.isNarrow) { 42 | panelClass += ' is-narrow' 43 | } 44 | 45 | let tabs: Tab[] = [] 46 | let ActiveView 47 | let activeModel 48 | let packageName = 'unknown' 49 | const activeRef = this.props.model.activeItem + 'view' 50 | this.props.model.viewProviders.forEach(({ view, model }) => { 51 | if (this.props.model.activeItem === model.key) { 52 | ActiveView = view 53 | activeModel = model 54 | if (model && model.isActive) { 55 | model.isActive(true) 56 | } 57 | } else { 58 | if (model && model.isActive) { 59 | model.isActive(false) 60 | } 61 | } 62 | if (tabs.find(({ key }) => key === model.key)) { 63 | return 64 | } 65 | tabs.push( 66 | Object.assign( 67 | ({ 68 | key: model.key, 69 | order: 999, 70 | icon: 'question', 71 | packageName: 'unknown', 72 | name: '' 73 | }: Tab), 74 | model.tab 75 | ) 76 | ) 77 | }) 78 | if (!ActiveView || !activeModel) { 79 | ActiveView = EmptyTabView 80 | } 81 | 82 | if (activeModel && activeModel.tab && activeModel.tab.suppressPadding) { 83 | panelBodyStyle.padding = '0px' 84 | } 85 | 86 | tabs = tabs 87 | .map(item => { 88 | item.className = 'panel-nav-item' 89 | if (this.props.model.activeItem === item.key) { 90 | item.className = item.className + ' is-selected' 91 | } 92 | return item 93 | }) 94 | .sort((a, b) => a.order - b.order || a.name.localeCompare(b.name)) 95 | 96 | return ( 97 |
98 |
99 | 114 |
115 |
121 | 126 |
127 |
128 | ) 129 | } 130 | 131 | readAfterUpdate() { 132 | const content = this.refs.content 133 | if (!content) { 134 | return 135 | } 136 | 137 | const scrollHeight = content.scrollHeight 138 | if (scrollHeight && this.scrollHeight !== scrollHeight) { 139 | this.scrollHeight = scrollHeight 140 | this.update() 141 | } 142 | } 143 | 144 | handleTabClick(item: Tab) { 145 | if ( 146 | item && 147 | item.key && 148 | item.key.length && 149 | this.props.model.activeItem !== item.key 150 | ) { 151 | this.props.model.activeItem = item.key 152 | this.update() 153 | } 154 | } 155 | 156 | dispose() { 157 | this.destroy() 158 | } 159 | 160 | destroy() { 161 | this.ro.unobserve(this.element) 162 | this.ro = (null: any) 163 | super.destroy(true) 164 | } 165 | } 166 | 167 | export const PANEL_URI = 'atom://go-plus/panel' 168 | -------------------------------------------------------------------------------- /lib/panel/panel-manager.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import { CompositeDisposable, Disposable } from 'atom' 4 | import { GoPlusPanel, PANEL_URI } from './go-plus-panel' 5 | 6 | import type { PanelModel } from './tab' 7 | import type { GoConfig } from './../config/service' 8 | import type { Renderable } from '../etch-component' 9 | 10 | class PanelManager { 11 | activeItem: string 12 | item: { 13 | element?: any, 14 | getURI: () => string, 15 | getTitle: () => string, 16 | getIconName: () => string, 17 | getDefaultLocation: () => string, 18 | getAllowedLocations: () => string[] 19 | } 20 | subscriptions: CompositeDisposable 21 | viewProviders: Map, model: PanelModel }> 22 | goPlusPanel: ?GoPlusPanel 23 | goconfig: GoConfig 24 | 25 | constructor() { 26 | this.activeItem = 'go' 27 | 28 | this.item = { 29 | getURI: () => PANEL_URI, 30 | getTitle: () => 'go-plus', 31 | getIconName: () => 'diff-added', 32 | getDefaultLocation: () => 'bottom', 33 | getAllowedLocations: () => ['right', 'left', 'bottom'] 34 | } 35 | 36 | this.subscriptions = new CompositeDisposable() 37 | this.viewProviders = new Map() 38 | 39 | this.subscribeToCommands() 40 | } 41 | 42 | createPanel(visible: boolean): Promise { 43 | if (this.goPlusPanel) { 44 | this.goPlusPanel.destroy() 45 | } 46 | this.goPlusPanel = new GoPlusPanel({ model: this }) 47 | this.item.element = this.goPlusPanel.element 48 | //$FlowFixMe 49 | return atom.workspace 50 | .open(this.item, { 51 | activatePane: visible 52 | }) 53 | .then(() => this.requestUpdate()) 54 | } 55 | 56 | requestUpdate(): Promise { 57 | if (this.goPlusPanel) { 58 | return this.goPlusPanel.update() 59 | } else { 60 | return this.createPanel( 61 | atom.config.get('go-plus.panel.displayMode') === 'open' 62 | ) 63 | } 64 | } 65 | 66 | subscribeToCommands() { 67 | if (!this.subscriptions) { 68 | return 69 | } 70 | this.subscriptions.add( 71 | atom.commands.add('atom-workspace', 'golang:toggle-panel', () => { 72 | this.togglePanel() 73 | }) 74 | ) 75 | } 76 | 77 | dispose() { 78 | if (this.subscriptions) { 79 | this.subscriptions.dispose() 80 | } 81 | 82 | const pane = atom.workspace.paneForURI(PANEL_URI) 83 | if (pane) { 84 | pane.destroyItem(this.item) 85 | } 86 | 87 | if (this.goPlusPanel) { 88 | this.goPlusPanel.destroy() 89 | } 90 | this.goPlusPanel = null 91 | this.viewProviders.clear() 92 | } 93 | 94 | registerViewProvider(view: Class, model: PanelModel): Disposable { 95 | if (!view || !model || !model.key) { 96 | return new Disposable() 97 | } 98 | const key = model.key 99 | model.requestFocus = () => { 100 | this.activeItem = key 101 | return this.togglePanel(true) 102 | } 103 | this.viewProviders.set(key, { view, model }) 104 | if (this.goPlusPanel) { 105 | this.goPlusPanel.update() 106 | } 107 | 108 | return new Disposable(() => { 109 | if (this.viewProviders && this.viewProviders.has(key)) { 110 | this.viewProviders.delete(key) 111 | } 112 | }) 113 | } 114 | 115 | togglePanel(visible?: boolean): Promise { 116 | //$FlowFixMe 117 | const container = atom.workspace.paneContainerForURI(PANEL_URI) 118 | if (!container) { 119 | return this.createPanel(true) 120 | } 121 | 122 | const pane = atom.workspace.paneForURI(PANEL_URI) 123 | if (visible === undefined) { 124 | const currentlyVisible = 125 | container.isVisible() && pane && pane.getActiveItem() === this.item 126 | visible = !currentlyVisible 127 | } 128 | 129 | if (!visible) { 130 | container.hide() 131 | for (const { model } of this.viewProviders.values()) { 132 | if (model.isActive) { 133 | model.isActive(false) 134 | } 135 | } 136 | return Promise.resolve() 137 | } 138 | container.show() 139 | //$FlowFixMe 140 | pane.activateItemForURI(PANEL_URI) 141 | 142 | if (this.goPlusPanel) { 143 | return this.goPlusPanel.update() 144 | } else { 145 | return Promise.resolve() 146 | } 147 | } 148 | } 149 | 150 | export { PanelManager } 151 | -------------------------------------------------------------------------------- /lib/panel/tab.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | export type Tab = { 4 | key: string, 5 | name: string, 6 | packageName: string, 7 | icon: string, 8 | order: number, 9 | className?: string, 10 | suppressPadding?: boolean 11 | } 12 | 13 | export interface PanelModel { 14 | key: string; 15 | tab: Tab; 16 | requestFocus?: ?() => Promise; 17 | isActive?: ?(active: boolean) => void; 18 | } 19 | -------------------------------------------------------------------------------- /lib/promise.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 'use babel' 3 | 4 | const promiseWaterfall = (tasks: Array<() => Promise>): Promise => { 5 | const p = Promise.resolve() 6 | const results = [] 7 | const finalTaskPromise = tasks.reduce((prevTaskPromise, task) => { 8 | return prevTaskPromise.then(r => { 9 | if (r) { 10 | results.push(r) 11 | } 12 | 13 | return task() 14 | }) 15 | }, p) 16 | 17 | return finalTaskPromise.then(r => { 18 | if (r) { 19 | results.push(r) 20 | } 21 | 22 | return results 23 | }) 24 | } 25 | 26 | export { promiseWaterfall } 27 | -------------------------------------------------------------------------------- /lib/references/references-provider.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import os from 'os' 4 | import { Point, Range } from 'atom' 5 | import { buildGuruArchive, computeArgs } from './../guru-utils' 6 | import { 7 | parseGoPosition, 8 | isValidEditor, 9 | utf8OffsetForBufferPosition 10 | } from './../utils' 11 | 12 | import type { GoConfig } from './../config/service' 13 | 14 | type Reference = { 15 | uri: string, 16 | name: ?string, // name of calling method/function/symbol 17 | range: atom$Range 18 | } 19 | 20 | type FindReferencesData = { 21 | type: 'data', 22 | baseUri: string, 23 | referencedSymbolName: string, 24 | references: Array, 25 | title?: string // defaults to 'Symbol References' 26 | } 27 | 28 | type FindReferencesError = { 29 | type: 'error', 30 | message: string 31 | } 32 | 33 | type FindReferencesReturn = FindReferencesData | FindReferencesError 34 | 35 | class ReferencesProvider { 36 | goconfig: GoConfig 37 | 38 | constructor(goconfig: GoConfig) { 39 | this.goconfig = goconfig 40 | } 41 | 42 | isEditorSupported(editor: TextEditor): Promise { 43 | return Promise.resolve(isValidEditor(editor)) 44 | } 45 | 46 | getWordAtPosition(editor: TextEditor, pos: atom$Point) { 47 | const cursor = editor.getLastCursor() 48 | const wordRegexp = cursor.wordRegExp() 49 | // $FlowFixMe 50 | const ranges = editor 51 | .getBuffer() 52 | .findAllInRangeSync( 53 | wordRegexp, 54 | new Range(new Point(pos.row, 0), new Point(pos.row, Infinity)) 55 | ) 56 | const range = 57 | ranges.find( 58 | range => 59 | range.end.column >= pos.column && range.start.column <= pos.column 60 | ) || new Range(pos, pos) 61 | 62 | return editor.getTextInBufferRange(range) 63 | } 64 | 65 | async findReferences( 66 | editor: TextEditor, 67 | position: atom$Point 68 | ): Promise { 69 | const cmd = await this.goconfig.locator.findTool('guru') 70 | if (!cmd) { 71 | return { 72 | type: 'error', 73 | message: 'Cannot find references. The `guru` tool could not be located.' 74 | } 75 | } 76 | 77 | const offset = utf8OffsetForBufferPosition(position, editor) 78 | const args = computeArgs('referrers', null, editor, offset) || [] 79 | const options = {} 80 | options.timeout = 30000 81 | const archive = buildGuruArchive(editor) 82 | if (archive && archive.length) { 83 | options.input = archive 84 | args.unshift('-modified') 85 | } 86 | 87 | const r = await this.goconfig.executor.exec(cmd, args, options) 88 | const stderr = r.stderr instanceof Buffer ? r.stderr.toString() : r.stderr 89 | const stdout = r.stdout instanceof Buffer ? r.stdout.toString() : r.stdout 90 | if (r.error || r.exitcode !== 0) { 91 | let message 92 | if (r.exitcode === 124) { 93 | message = `operation timed out after ${options.timeout}ms` 94 | } else { 95 | message = stderr.trim() + os.EOL + stdout.trim() 96 | if (r.error && r.error.message) { 97 | message = r.error.message + os.EOL + message 98 | } 99 | } 100 | return { type: 'error', message } 101 | } 102 | 103 | const stream = this.parseStream(stdout) 104 | const refs = this.parse(stream) 105 | return { 106 | type: 'data', 107 | baseUri: atom.project.getDirectories()[0].getPath(), 108 | references: refs, 109 | referencedSymbolName: 110 | this.getWordAtPosition(editor, position) || stream[0].desc 111 | } 112 | } 113 | 114 | parseStream(jsonStream: string): Array { 115 | if (!jsonStream || !jsonStream.length) { 116 | return [] 117 | } 118 | // A JSON stream is invalid json; characterized by a concatenation of 119 | // multiple JSON objects 120 | const r = new RegExp('^}$', 'igm') 121 | const result = [] 122 | const objects = jsonStream.split(r) 123 | for (const obj of objects) { 124 | if (obj.trim() !== '') { 125 | result.push(JSON.parse(obj + '}')) 126 | } 127 | } 128 | return result 129 | } 130 | 131 | parse(obj: Array): Array { 132 | if (!obj || !obj.length) { 133 | return [] 134 | } 135 | 136 | const refs: Array = [] 137 | for (const pkg of obj.slice(1)) { 138 | if (!pkg || !pkg.refs || !pkg.refs.length) { 139 | continue 140 | } 141 | 142 | for (const ref of pkg.refs) { 143 | const parsed = parseGoPosition(ref.pos) 144 | if ( 145 | parsed && 146 | typeof parsed.column === 'number' && 147 | typeof parsed.line === 'number' 148 | ) { 149 | const point = [parsed.line, parsed.column] 150 | refs.push({ 151 | uri: parsed.file, 152 | range: new Range(point, point), 153 | name: ref.text 154 | }) 155 | } 156 | } 157 | } 158 | 159 | return refs 160 | } 161 | } 162 | 163 | export { ReferencesProvider } 164 | -------------------------------------------------------------------------------- /lib/rename/gorename.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import path from 'path' 4 | import { CompositeDisposable } from 'atom' 5 | import { SimpleDialog } from './../simple-dialog' 6 | import { isValidEditor, wordAndOffset } from '../utils' 7 | 8 | import type { GoConfig } from './../config/service' 9 | 10 | class Gorename { 11 | goconfig: GoConfig 12 | subscriptions: CompositeDisposable 13 | 14 | constructor(goconfig: GoConfig) { 15 | this.goconfig = goconfig 16 | this.subscriptions = new CompositeDisposable() 17 | this.subscriptions.add( 18 | atom.commands.add('atom-text-editor', 'golang:gorename', () => 19 | this.commandInvoked() 20 | ) 21 | ) 22 | } 23 | 24 | dispose() { 25 | this.subscriptions.dispose() 26 | } 27 | 28 | async commandInvoked() { 29 | const editor = atom.workspace.getActiveTextEditor() 30 | if (!editor || !isValidEditor(editor)) { 31 | return 32 | } 33 | try { 34 | const cmd = await this.goconfig.locator.findTool('gorename') 35 | if (!cmd) { 36 | return 37 | } 38 | const { word, offset } = wordAndOffset(editor) 39 | const cursor = editor.getCursorBufferPosition() 40 | 41 | const dialog = new SimpleDialog({ 42 | prompt: `Rename ${word} to:`, 43 | initialValue: word, 44 | onConfirm: newName => { 45 | this.saveAllEditors() 46 | .then(() => { 47 | const file = editor.getBuffer().getPath() 48 | if (!file) { 49 | return 50 | } 51 | const cwd = path.dirname(file) 52 | 53 | // restore cursor position after gorename completes and the buffer is reloaded 54 | if (cursor) { 55 | const disp = editor.getBuffer().onDidReload(() => { 56 | editor.setCursorBufferPosition(cursor, { autoscroll: false }) 57 | const element = atom.views.getView(editor) 58 | if (element) { 59 | element.focus() 60 | } 61 | disp.dispose() 62 | }) 63 | } 64 | this.runGorename(file, offset, cwd, newName, cmd) 65 | return 66 | }) 67 | .catch(e => console.log(e)) // eslint-disable-line no-console 68 | }, 69 | onCancel: () => { 70 | editor.setCursorBufferPosition(cursor, { autoscroll: false }) 71 | const element = atom.views.getView(editor) 72 | if (element) { 73 | element.focus() 74 | } 75 | } 76 | }) 77 | 78 | dialog.attach() 79 | } catch (e) { 80 | if (e.handle) { 81 | e.handle() 82 | } 83 | console.log(e) // eslint-disable-line no-console 84 | } 85 | } 86 | 87 | saveAllEditors() { 88 | const promises = [] 89 | for (const editor of atom.workspace.getTextEditors()) { 90 | if (editor.isModified() && isValidEditor(editor)) { 91 | promises.push(editor.save()) 92 | } 93 | } 94 | return Promise.all(promises) 95 | } 96 | 97 | async runGorename( 98 | file: string, 99 | offset: number, 100 | cwd: string, 101 | newName: string, 102 | cmd: string 103 | ) { 104 | if (!this.goconfig || !this.goconfig.executor) { 105 | return { success: false, result: null } 106 | } 107 | 108 | const args = ['-offset', `${file}:#${offset}`, '-to', newName] 109 | const options = { 110 | cwd: cwd, 111 | env: this.goconfig.environment(), 112 | timeout: 20000 113 | } 114 | const notification = atom.notifications.addInfo('Renaming...', { 115 | dismissable: true 116 | }) 117 | const r = await this.goconfig.executor.exec(cmd, args, options) 118 | notification.dismiss() 119 | if (r.exitcode === 124) { 120 | atom.notifications.addError('Operation timed out', { 121 | detail: 'gorename ' + args.join(' '), 122 | dismissable: true 123 | }) 124 | return { success: false, result: r } 125 | } else if (r.error) { 126 | if (r.error.code === 'ENOENT') { 127 | atom.notifications.addError('Missing Rename Tool', { 128 | detail: 129 | 'The gorename tool is required to perform a rename. Please run go get -u golang.org/x/tools/cmd/gorename to get it.', 130 | dismissable: true 131 | }) 132 | } else { 133 | atom.notifications.addError('Rename Error', { 134 | detail: r.error.message, 135 | dismissable: true 136 | }) 137 | } 138 | return { success: false, result: r } 139 | } 140 | 141 | const stderr = r.stderr instanceof Buffer ? r.stderr.toString() : r.stderr 142 | const stdout = r.stdout instanceof Buffer ? r.stdout.toString() : r.stdout 143 | const message = stderr.trim() + '\r\n' + stdout.trim() 144 | if (r.exitcode !== 0 || (stderr && stderr.trim() !== '')) { 145 | atom.notifications.addWarning('Rename Error', { 146 | detail: message.trim(), 147 | dismissable: true 148 | }) 149 | return { success: false, result: r } 150 | } 151 | 152 | atom.notifications.addSuccess(message.trim()) 153 | return { success: true, result: r } 154 | } 155 | } 156 | 157 | export { Gorename } 158 | -------------------------------------------------------------------------------- /lib/simple-dialog.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** @jsx etch.dom */ 3 | 4 | import { CompositeDisposable, TextEditor } from 'atom' 5 | import etch from 'etch' 6 | import { EtchComponent } from './etch-component' 7 | 8 | type Props = { 9 | prompt: string, 10 | initialValue?: string, 11 | onConfirm: string => void, 12 | onCancel?: () => void 13 | } 14 | 15 | export class SimpleDialog extends EtchComponent { 16 | props: Props 17 | subscriptions: CompositeDisposable 18 | panel: ?atom$Panel 19 | previouslyFocusedElement: ?HTMLElement 20 | 21 | constructor(props: Props) { 22 | super(props) 23 | 24 | this.subscriptions = new CompositeDisposable() 25 | this.subscriptions.add( 26 | atom.commands.add(this.element, 'core:cancel', () => this.cancel()) 27 | ) 28 | this.subscriptions.add( 29 | atom.commands.add(this.element, 'core:confirm', () => this.confirm()) 30 | ) 31 | } 32 | 33 | render() { 34 | const { prompt } = this.props 35 | /* eslint-disable react/no-string-refs */ 36 | return ( 37 |
38 |
{prompt}
39 | 40 |
41 | ) 42 | /* eslint-enable react/no-string-refs */ 43 | } 44 | 45 | attach() { 46 | const { input, initialValue } = this.refs 47 | if (input) { 48 | this.panel = atom.workspace.addModalPanel({ item: this }) 49 | if (initialValue) { 50 | input.setText(initialValue) 51 | input.selectAll() 52 | } 53 | this.previouslyFocusedElement = document.activeElement 54 | input.element.focus() 55 | } 56 | } 57 | 58 | confirm() { 59 | const value = this.refs.input.getText() 60 | const { onConfirm } = this.props 61 | this.destroy() 62 | if (onConfirm) { 63 | onConfirm(value) 64 | } 65 | } 66 | 67 | cancel() { 68 | const { onCancel } = this.props 69 | this.destroy() 70 | if (onCancel) { 71 | onCancel() 72 | } 73 | } 74 | 75 | dispose() { 76 | this.destroy() 77 | } 78 | 79 | destroy() { 80 | super.destroy() 81 | this.subscriptions.dispose() 82 | if (this.panel) { 83 | this.panel.destroy() 84 | this.panel = null 85 | } 86 | if (this.previouslyFocusedElement) { 87 | this.previouslyFocusedElement.focus() 88 | this.previouslyFocusedElement = null 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/test/gocover-parser.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import fs from 'fs' 4 | import { Range } from 'atom' 5 | 6 | export type CoverageRange = { 7 | range: Range, 8 | count: number, 9 | file: string 10 | } 11 | 12 | export function ranges(coverageFile: string): Array { 13 | let data 14 | const ranges = [] 15 | try { 16 | data = fs.readFileSync(coverageFile, { encoding: 'utf8' }) 17 | } catch (e) { 18 | return ranges 19 | } 20 | 21 | // https://code.google.com/p/go/source/browse/cmd/cover/profile.go?repo=tools&name=a2a0f87c4b38&r=92b0a64343bc62160c1c10d335d375b0defa4c18#113 22 | const pattern = /^(.+):(\d+).(\d+),(\d+).(\d+) (\d+) (\d+)$/gim 23 | 24 | const extract = match => { 25 | if (!match) { 26 | return 27 | } 28 | const filePath = match[1] 29 | const count = match[7] 30 | const range = new Range( 31 | [parseInt(match[2], 10) - 1, parseInt(match[3], 10) - 1], 32 | [parseInt(match[4], 10) - 1, parseInt(match[5], 10) - 1] 33 | ) 34 | ranges.push({ range: range, count: parseInt(count, 10), file: filePath }) 35 | } 36 | 37 | let match 38 | while ((match = pattern.exec(data)) !== null) { 39 | extract(match) 40 | } 41 | 42 | return ranges 43 | } 44 | -------------------------------------------------------------------------------- /lib/tool-checker.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import type { GoConfig } from './config/service' 4 | 5 | class ToolChecker { 6 | goconfig: GoConfig 7 | 8 | constructor(goconfig: GoConfig) { 9 | this.goconfig = goconfig 10 | } 11 | 12 | async checkForTools(tools: Array) { 13 | if (!tools || !tools.length) { 14 | return 15 | } 16 | const promises = tools 17 | .filter(tool => !!tool) 18 | .map(tool => this.goconfig.locator.findTool(tool)) 19 | const results = await Promise.all(promises) 20 | if (results.some(cmd => !cmd)) { 21 | atom.commands.dispatch( 22 | atom.views.getView(atom.workspace), 23 | 'golang:update-tools' 24 | ) 25 | } 26 | } 27 | } 28 | 29 | export { ToolChecker } 30 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | import fs from 'fs' 4 | import { TextEditor, Point } from 'atom' 5 | 6 | const isValidEditor = (e: ?TextEditor): boolean %checks => { 7 | return !!e && hasGoGrammar(e.getGrammar()) 8 | } 9 | 10 | const hasGoGrammar = (g: ?atom$Grammar): boolean %checks => { 11 | return !!g && (g.scopeName === 'source.go' || g.scopeName === 'go') 12 | } 13 | 14 | const getEditor = (): ?TextEditor => { 15 | if (!atom || !atom.workspace) { 16 | return 17 | } 18 | const editor = atom.workspace.getActiveTextEditor() 19 | if (!isValidEditor(editor)) { 20 | return 21 | } 22 | 23 | return editor 24 | } 25 | 26 | const getWordPosition = ( 27 | editor: ?TextEditor = getEditor() 28 | ): ?atom$PointLike => { 29 | if (!editor) { 30 | return undefined 31 | } 32 | 33 | const cursor = editor.getLastCursor() 34 | const buffer = editor.getBuffer() 35 | 36 | if (!cursor || !buffer) { 37 | return undefined 38 | } 39 | 40 | let wordPosition = cursor.getCurrentWordBufferRange() 41 | let start = buffer.characterIndexForPosition(wordPosition.start) 42 | let end = buffer.characterIndexForPosition(wordPosition.end) 43 | return [start, end] 44 | } 45 | 46 | const getCursorPosition = (editor: TextEditor) => { 47 | if (!editor) { 48 | return undefined 49 | } 50 | const cursor = editor.getLastCursor() 51 | if (!cursor) { 52 | return undefined 53 | } 54 | return cursor.getBufferPosition() 55 | } 56 | 57 | const currentCursorOffset = (editor: TextEditor) => { 58 | if (!editor) { 59 | return undefined 60 | } 61 | 62 | const pos = getCursorPosition(editor) 63 | if (!pos) { 64 | return undefined 65 | } 66 | 67 | return utf8OffsetForBufferPosition(pos, editor) 68 | } 69 | 70 | const utf8OffsetForBufferPosition = ( 71 | pos: atom$PointLike, 72 | editor: TextEditor 73 | ): number => { 74 | if (!editor || !editor.getBuffer() || !pos) { 75 | return -1 76 | } 77 | const characterOffset = editor.getBuffer().characterIndexForPosition(pos) 78 | const text = editor.getText().substring(0, characterOffset) 79 | return Buffer.byteLength(text, 'utf8') 80 | } 81 | 82 | const wordAndOffset = ( 83 | editor: TextEditor 84 | ): { word: string, offset: number } => { 85 | const cursor = editor.getLastCursor() 86 | const range = cursor.getCurrentWordBufferRange() 87 | const middle = new Point( 88 | range.start.row, 89 | Math.floor((range.start.column + range.end.column) / 2) 90 | ) 91 | const charOffset = editor.getBuffer().characterIndexForPosition(middle) 92 | const text = editor.getText().substring(0, charOffset) 93 | return { 94 | word: editor.getTextInBufferRange(range), 95 | offset: Buffer.byteLength(text, 'utf8') 96 | } 97 | } 98 | 99 | /** 100 | * Opens the `file` and centers the editor around the `pos` 101 | * @param {string} file Path to the file to open. 102 | * @param {object} [pos] An optional object containing `row` and `column` to scroll to. 103 | * @return {Promise} Returns a promise which resolves with the opened editor 104 | */ 105 | const openFile = async ( 106 | file: string, 107 | pos?: ?atom$Point 108 | ): Promise => { 109 | await new Promise((resolve, reject) => { 110 | fs.access(file, fs.constants.F_OK | fs.constants.R_OK, err => { 111 | if (err) { 112 | reject(err) 113 | return 114 | } 115 | resolve() 116 | }) 117 | }) 118 | // searchAllPanes avoids opening a file in another split pane if it is already open in one 119 | const options = {} 120 | options.searchAllPanes = true 121 | if (pos && pos.row) { 122 | options.initialLine = pos.row 123 | } 124 | if (pos && pos.column) { 125 | options.initialColumn = pos.column 126 | } 127 | const editor = await atom.workspace.open(file, options) 128 | if (pos) { 129 | editor.scrollToBufferPosition(pos, { center: true }) 130 | } 131 | return editor 132 | } 133 | 134 | const stat = (loc: string): Promise => { 135 | return new Promise((resolve, reject) => { 136 | fs.stat(loc, (err, stats) => { 137 | if (err) { 138 | reject(err) 139 | } 140 | resolve(stats) 141 | }) 142 | }) 143 | } 144 | 145 | const projectPath = (): ?string => { 146 | const dirs = atom.project 147 | .getDirectories() 148 | .filter(dir => !dir.getPath().includes('://')) 149 | if (dirs && dirs.length > 0) { 150 | return dirs[0].getPath() 151 | } 152 | return undefined 153 | } 154 | 155 | export type GoPos = { 156 | file: string, 157 | line?: number, 158 | column?: number 159 | } 160 | 161 | const parseGoPosition = (identifier: string): GoPos => { 162 | if (!identifier.includes(':')) { 163 | return { file: identifier } 164 | } 165 | 166 | const windows = identifier[1] === ':' 167 | const offset = windows ? 1 : 0 168 | 169 | const components = identifier.trim().split(':') 170 | const hasLine = components.length >= 2 + offset 171 | const hasColumn = components.length > 2 + offset 172 | 173 | const column = hasColumn ? parseInt(components.pop(), 10) : undefined 174 | const line = hasLine ? parseInt(components.pop(), 10) : undefined 175 | const file = components.join(':') 176 | 177 | return { file, line, column } 178 | } 179 | 180 | export { 181 | isValidEditor, 182 | getEditor, 183 | projectPath, 184 | openFile, 185 | getWordPosition, 186 | utf8OffsetForBufferPosition, 187 | wordAndOffset, 188 | currentCursorOffset, 189 | getCursorPosition, 190 | parseGoPosition, 191 | stat 192 | } 193 | -------------------------------------------------------------------------------- /menus/go-plus.json: -------------------------------------------------------------------------------- 1 | { 2 | "context-menu": { 3 | "atom-text-editor[data-grammar~=\"go\"]:not([mini])": [ 4 | { 5 | "label": "Go", 6 | "submenu": [ 7 | { 8 | "label": "Go To Definition", 9 | "command": "golang:godef" 10 | }, 11 | { 12 | "label": "Implements", 13 | "command": "golang:implements" 14 | }, 15 | { 16 | "label": "Return From Definition", 17 | "command": "golang:godef-return" 18 | }, 19 | { 20 | "label": "Run Tests", 21 | "command": "golang:run-tests" 22 | }, 23 | { 24 | "label": "Clear Test Coverage", 25 | "command": "golang:hide-coverage" 26 | }, 27 | { 28 | "label": "Rename symbol under cursor", 29 | "command": "golang:gorename" 30 | }, 31 | { 32 | "label": "Get godoc for symbol under cursor", 33 | "command": "golang:showdoc" 34 | }, 35 | { 36 | "label": "Add Tags", 37 | "command": "golang:add-tags" 38 | }, 39 | { 40 | "label": "Remove Tags", 41 | "command": "golang:remove-tags" 42 | } 43 | ] 44 | } 45 | ] 46 | }, 47 | "menu": [ 48 | { 49 | "label": "Packages", 50 | "submenu": [ 51 | { 52 | "label": "Go", 53 | "submenu": [ 54 | { 55 | "label": "Go To Definition", 56 | "command": "golang:godef" 57 | }, 58 | { 59 | "label": "Return From Definition", 60 | "command": "golang:godef-return" 61 | }, 62 | { 63 | "label": "Format With gofmt", 64 | "command": "golang:gofmt" 65 | }, 66 | { 67 | "label": "Format With goimports", 68 | "command": "golang:goimports" 69 | }, 70 | { 71 | "label": "Format With goreturns", 72 | "command": "golang:goreturns" 73 | }, 74 | { 75 | "label": "Run Tests", 76 | "command": "golang:run-tests" 77 | }, 78 | { 79 | "label": "Clear Test Coverage", 80 | "command": "golang:hide-coverage" 81 | }, 82 | { 83 | "label": "Get Package", 84 | "command": "golang:get-package" 85 | }, 86 | { 87 | "label": "Update Tools", 88 | "command": "golang:update-tools" 89 | } 90 | ] 91 | } 92 | ] 93 | } 94 | ] 95 | } 96 | -------------------------------------------------------------------------------- /spec/async-spec-helpers.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | 3 | /* eslint-disable promise/no-callback-in-promise */ 4 | /* eslint-disable promise/catch-or-return */ 5 | 6 | exports.beforeEach = function beforeEach(fn) { 7 | global.beforeEach(function() { 8 | const result = fn() 9 | if (result instanceof Promise) { 10 | waitsForPromise(() => result) 11 | } 12 | }) 13 | } 14 | 15 | exports.runs = function runs(fn) { 16 | global.runs(function() { 17 | const result = fn() 18 | if (result instanceof Promise) { 19 | waitsForPromise(() => result) 20 | } 21 | }) 22 | } 23 | 24 | exports.afterEach = function afterEach(fn) { 25 | global.afterEach(function() { 26 | const result = fn() 27 | if (result instanceof Promise) { 28 | waitsForPromise(() => result) 29 | } 30 | }) 31 | } 32 | 33 | const funcs = ['it', 'fit', 'ffit', 'fffit'] 34 | 35 | funcs.forEach(function(name) { 36 | exports[name] = function(description, fn) { 37 | if (fn === undefined) { 38 | global[name](description) 39 | return 40 | } 41 | 42 | global[name](description, function() { 43 | const result = fn() 44 | if (result instanceof Promise) { 45 | waitsForPromise(() => result) 46 | } 47 | }) 48 | } 49 | }) 50 | 51 | function waitsForPromise(fn) { 52 | const promise = fn() 53 | global.waitsFor('spec promise to resolve', function(done) { 54 | promise.then(done, function(error) { 55 | jasmine.getEnv().currentSpec.fail(error) 56 | done() 57 | }) 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /spec/autocomplete/gocodeprovider-helper-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | /* eslint-env jasmine */ 3 | 4 | import { getPackage } from '../../lib/autocomplete/gocodeprovider-helper' 5 | import * as path from 'path' 6 | 7 | describe('gocodeprovider-helper', () => { 8 | describe('getPackage', () => { 9 | function normalize(v) { 10 | return (process.platform === 'win32' ? 'C:' : '') + path.normalize(v) 11 | } 12 | it('returns a non-vendored package if `useVendor` is false', () => { 13 | const pkg = getPackage( 14 | normalize('/Users/me/go/src/github.com/foo/server/main.go'), 15 | normalize('/Users/me/go'), 16 | [ 17 | 'github.com/foo/server/vendor/github.com/bar/lib', 18 | 'github.com/foo/lib' 19 | ], 20 | false // <-- vendor is not active 21 | ) 22 | expect(pkg).toBe('github.com/foo/lib') 23 | }) 24 | 25 | it('returns a vendored package if `useVendor` is true', () => { 26 | const pkg = getPackage( 27 | normalize('/Users/me/go/src/github.com/foo/server/main.go'), 28 | normalize('/Users/me/go'), 29 | [ 30 | 'github.com/foo/server/vendor/github.com/bar/lib', 31 | 'github.com/foo/lib' 32 | ], 33 | true // <-- vendor is active 34 | ) 35 | expect(pkg).toBe('github.com/bar/lib') 36 | }) 37 | 38 | it('gets vendored package if inside sub package', () => { 39 | const pkg = getPackage( 40 | normalize('/Users/me/go/src/github.com/foo/server/sub/sub.go'), // <-- inside sub package 41 | normalize('/Users/me/go'), 42 | [ 43 | 'github.com/foo/server/vendor/github.com/bar/lib', 44 | 'github.com/foo/lib' 45 | ], 46 | true 47 | ) 48 | expect(pkg).toBe('github.com/bar/lib') 49 | }) 50 | 51 | it('ignores nested vendored packages', () => { 52 | const pkg = getPackage( 53 | normalize('/Users/me/go/src/github.com/foo/server/main.go'), 54 | normalize('/Users/me/go'), 55 | [ 56 | 'github.com/foo/server/vendor/github.com/bar/lib/vendor/github.com/baz/other' 57 | ], 58 | true 59 | ) 60 | expect(pkg).toBeFalsy() 61 | }) 62 | 63 | it('returns non-vendored package if vendor does not match', () => { 64 | const pkg = getPackage( 65 | normalize('/Users/me/go/src/github.com/foo/server/main.go'), 66 | normalize('/Users/me/go'), 67 | [ 68 | // ignores this package because it is inside the "bar" package not "foo" 69 | 'github.com/bar/server/vendor/github.com/baz/lib', 70 | // returns this because no vendored package matches 71 | 'github.com/qux/lib' 72 | ], 73 | true 74 | ) 75 | expect(pkg).toBe('github.com/qux/lib') 76 | }) 77 | 78 | it('returns another vendored package inside a vendored package', () => { 79 | const pkg = getPackage( 80 | // a file inside a vendored package ... 81 | normalize( 82 | '/Users/me/go/src/github.com/foo/server/vendor/github.com/bar/lib/lib.go' 83 | ), 84 | normalize('/Users/me/go'), 85 | [ 86 | // ... is allowed to use another vendored package 87 | 'github.com/foo/server/vendor/github.com/baz/other' 88 | ], 89 | true 90 | ) 91 | expect(pkg).toBe('github.com/baz/other') 92 | }) 93 | }) 94 | }) 95 | -------------------------------------------------------------------------------- /spec/build/builder-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | /* eslint-env jasmine */ 3 | 4 | import { Builder } from '../../lib/build/builder' 5 | import { ConfigService } from '../../lib/config/service' 6 | import { lifecycle } from './../spec-helpers' 7 | import { it, fit, ffit, beforeEach, runs } from '../async-spec-helpers' // eslint-disable-line 8 | 9 | describe('builder', () => { 10 | let builder = null 11 | let linter 12 | 13 | beforeEach(async () => { 14 | lifecycle.setup() 15 | 16 | // mock the Linter V1 Indie API 17 | linter = { 18 | deleteMessages: () => {}, 19 | setMessages: () => {}, 20 | dispose: () => {} 21 | } 22 | const goconfig = new ConfigService().provide() 23 | builder = new Builder(goconfig, linter, null) 24 | }) 25 | 26 | describe('test executable', () => { 27 | it('provides a standard set of flags for compilation', () => { 28 | ;['', ' '].forEach(setting => { 29 | const args = builder.testCompileArgs('output', setting) 30 | expect(args[0]).toEqual('test') 31 | expect(args).toContain('-c') 32 | expect(args).toContain('-o') 33 | expect(args).toContain('.') 34 | expect(args.includes('output')).toEqual(true) 35 | }) 36 | }) 37 | 38 | it('includes additional args', () => { 39 | const args = builder.testCompileArgs('output', '-foo -bar 5') 40 | expect(args[0]).toEqual('test') 41 | expect(args).toContain('-c') 42 | expect(args).toContain('-o') 43 | expect(args.includes('output')).toEqual(true) 44 | expect(args).toContain('.') 45 | expect(args).toContain('-foo') 46 | expect(args).toContain('-bar') 47 | expect(args).toContain('5') 48 | }) 49 | 50 | it('puts additional args before the package path', () => { 51 | const args = builder.testCompileArgs('output', '-foo') 52 | const dot = args.indexOf('.') 53 | const foo = args.indexOf('-foo') 54 | expect(dot).not.toEqual(-1) 55 | expect(foo).not.toEqual(-1) 56 | expect(foo).toBeLessThan(dot) 57 | }) 58 | 59 | it('does not duplicate args', () => { 60 | const args = builder.testCompileArgs('output', '-c') 61 | expect(args.filter(x => x === '-c').length).toEqual(1) 62 | }) 63 | 64 | it('does not allow overriding the output file', () => { 65 | const args = builder.testCompileArgs('output', '-o /root/dont_write_here') 66 | const i = args.indexOf('-o') 67 | expect(i).not.toEqual(-1) 68 | expect(args[i + 1]).not.toEqual('/root/dont_write_here') 69 | }) 70 | }) 71 | 72 | describe('build command', () => { 73 | it('runs go build for code outside gopath', () => { 74 | ;[ 75 | { 76 | gopath: 'C:\\Users\\jsmith\\go', 77 | cwd: 'C:\\projects\\go\\test', 78 | sep: '\\' 79 | }, 80 | { 81 | gopath: '/home/jsmith/go', 82 | cwd: '/home/jsmith/go', 83 | sep: '/' 84 | }, 85 | { 86 | gopath: '/home/jsmith/go', 87 | cwd: '/home/jsmith/code/', 88 | sep: '/' 89 | }, 90 | { 91 | gopath: '/Users/jsmith/go', 92 | cwd: '/Users/jsmith/documents', 93 | sep: '/' 94 | } 95 | ].forEach(({ gopath, cwd, sep }) => { 96 | expect(builder.buildCommand(gopath, cwd, sep)).toBe('build', cwd) 97 | }) 98 | }) 99 | 100 | it('runs go install for code in gopath', () => { 101 | ;[ 102 | { 103 | gopath: 'C:\\Users\\jsmith\\go', 104 | cwd: 'C:\\Users\\jsmith\\go\\src\\github.com\\foo', 105 | sep: '\\' 106 | }, 107 | { 108 | gopath: '/home/jsmith/go', 109 | cwd: '/home/jsmith/go/src/bar', 110 | sep: '/' 111 | }, 112 | { 113 | gopath: '/Users/jsmith/go', 114 | cwd: '/Users/jsmith/go/src/github.com/foo/bar', 115 | sep: '/' 116 | }, 117 | { 118 | gopath: '/Users/jsmith/go/', 119 | cwd: '/Users/jsmith/go/src/github.com/foo/bar', 120 | sep: '/' 121 | } 122 | ].forEach(({ gopath, cwd, sep }) => { 123 | expect(builder.buildCommand(gopath, cwd, sep)).toBe('install', cwd) 124 | }) 125 | }) 126 | }) 127 | }) 128 | -------------------------------------------------------------------------------- /spec/config/environment-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | /* eslint-env jasmine */ 3 | 4 | import { getgopath } from './../../lib/config/environment' 5 | import * as pathhelper from './../../lib/config/pathhelper' 6 | import path from 'path' 7 | import { lifecycle } from './../spec-helpers' 8 | import temp from '@atom/temp' 9 | 10 | describe('executor', () => { 11 | let [envDir, configDir] = [] 12 | beforeEach(() => { 13 | lifecycle.setup() 14 | envDir = temp.path('gopathenv') 15 | configDir = temp.path('gopathconfig') 16 | }) 17 | 18 | afterEach(() => { 19 | lifecycle.teardown() 20 | }) 21 | 22 | describe('there is a gopath in the environment', () => { 23 | beforeEach(() => { 24 | process.env.GOPATH = envDir 25 | atom.config.set('go-plus.config.gopath', configDir) 26 | }) 27 | 28 | it("uses the environment's gopath", () => { 29 | expect(getgopath()).toBeTruthy() 30 | expect(getgopath()).toBe(envDir) 31 | }) 32 | }) 33 | 34 | describe('there is no gopath in the environment or config', () => { 35 | beforeEach(() => { 36 | delete process.env.GOPATH 37 | atom.config.set('go-plus.config.gopath', '') 38 | }) 39 | 40 | it('uses the default gopath', () => { 41 | expect(getgopath()).toBeTruthy() 42 | expect(getgopath()).toBe(path.join(pathhelper.home(), 'go')) 43 | }) 44 | }) 45 | 46 | describe('there is a gopath in config and not in the environment', () => { 47 | beforeEach(() => { 48 | delete process.env.GOPATH 49 | atom.config.set('go-plus.config.gopath', configDir) 50 | }) 51 | 52 | it("uses the config's gopath", () => { 53 | expect(getgopath()).toBeTruthy() 54 | expect(getgopath()).toBe(configDir) 55 | }) 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /spec/config/executor-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | /* eslint-env jasmine */ 3 | 4 | import { Executor } from './../../lib/config/executor' 5 | import * as pathhelper from './../../lib/config/pathhelper' 6 | import os from 'os' 7 | import path from 'path' 8 | import { lifecycle } from './../spec-helpers' 9 | import { it, fit, ffit, beforeEach, runs } from '../async-spec-helpers' // eslint-disable-line 10 | 11 | describe('executor', () => { 12 | let executor = null 13 | let prefix = null 14 | 15 | beforeEach(() => { 16 | lifecycle.setup() 17 | prefix = '/' 18 | if (os.platform() === 'win32') { 19 | prefix = 'C:\\' 20 | } 21 | executor = new Executor() 22 | }) 23 | 24 | describe('when asynchronously executing a command', () => { 25 | it('succeeds', async () => { 26 | let command = 'env' 27 | if (os.platform() === 'win32') { 28 | command = path.resolve( 29 | __dirname, 30 | 'tools', 31 | 'env', 32 | 'env_windows_amd64.exe' 33 | ) 34 | } 35 | 36 | const result = await executor.exec(command, [], { cwd: prefix }) 37 | expect(result).toBeDefined() 38 | expect(result.exitcode).toBeDefined() 39 | expect(result.exitcode).toBe(0) 40 | expect(result.stdout).toBeDefined() 41 | expect(result.stdout).not.toBe('') 42 | expect(result.stderr).toBeDefined() 43 | expect(result.stderr).toBe('') 44 | expect(result.error).toBeFalsy() 45 | }) 46 | 47 | it('sets the working directory correctly', async () => { 48 | let command = 'pwd' 49 | if (os.platform() === 'win32') { 50 | command = path.resolve( 51 | __dirname, 52 | 'tools', 53 | 'pwd', 54 | 'pwd_windows_amd64.exe' 55 | ) 56 | } 57 | 58 | const result = await executor.exec(command, [], { 59 | cwd: pathhelper.home() 60 | }) 61 | expect(result).toBeDefined() 62 | expect(result.exitcode).toBeDefined() 63 | expect(result.exitcode).toBe(0) 64 | expect(result.stdout).toBeDefined() 65 | expect(result.stdout).toBe(pathhelper.home() + '\n') 66 | expect(result.stderr).toBeDefined() 67 | expect(result.stderr).toBe('') 68 | expect(result.error).toBeFalsy() 69 | }) 70 | 71 | it('sets the environment correctly', async () => { 72 | let command = 'env' 73 | if (os.platform() === 'win32') { 74 | command = path.resolve( 75 | __dirname, 76 | 'tools', 77 | 'env', 78 | 'env_windows_amd64.exe' 79 | ) 80 | } 81 | let env = { testenv: 'testing' } 82 | const result = await executor.exec(command, [], { env }) 83 | 84 | expect(result).toBeDefined() 85 | expect(result.exitcode).toBeDefined() 86 | expect(result.exitcode).toBe(0) 87 | expect(result.stdout).toBeDefined() 88 | expect(result.stdout).toContain('testenv=testing\n') 89 | expect(result.stderr).toBeDefined() 90 | expect(result.stderr).toBe('') 91 | expect(result.error).toBeFalsy() 92 | }) 93 | 94 | it('handles and returns an ENOENT error if the command was not found', async () => { 95 | const result = await executor.exec( 96 | 'nonexistentcommand', 97 | [], 98 | executor.getOptions() 99 | ) 100 | expect(result).toBeTruthy() 101 | expect(result.error).toBeTruthy() 102 | expect(result.error.errno).toBe('ENOENT') 103 | expect(result.error.message).toBe('spawn nonexistentcommand ENOENT') 104 | expect(result.error.path).toBe('nonexistentcommand') 105 | expect(result.exitcode).toBe(127) 106 | expect(result.stdout).toBe('') 107 | expect(result.stderr).toBeDefined() 108 | if (os.platform() === 'win32') { 109 | expect(result.stderr).toBe( 110 | "'nonexistentcommand' is not recognized as an internal or external command,\r\noperable program or batch file.\r\n" 111 | ) 112 | } else { 113 | expect(result.stderr).toBe('') 114 | } 115 | }) 116 | }) 117 | 118 | describe('when synchronously executing a command', () => { 119 | it('succeeds', () => { 120 | let command = 'env' 121 | if (os.platform() === 'win32') { 122 | command = path.resolve( 123 | __dirname, 124 | 'tools', 125 | 'env', 126 | 'env_windows_amd64.exe' 127 | ) 128 | } 129 | 130 | let result = executor.execSync(command, [], executor.getOptions()) 131 | expect(result.exitcode).toBeDefined() 132 | expect(result.exitcode).toBe(0) 133 | expect(result.stdout).toBeDefined() 134 | expect(result.stdout).not.toBe('') 135 | expect(result.stderr).toBeDefined() 136 | expect(result.stderr).toBe('') 137 | expect(result.error).toBeFalsy() 138 | }) 139 | 140 | it('returns a message if the command was not found', () => { 141 | let result = executor.execSync( 142 | 'nonexistentcommand', 143 | [], 144 | executor.getOptions() 145 | ) 146 | expect(result.exitcode).toBeDefined() 147 | expect(result.exitcode).toBe(127) 148 | expect(result.stdout).toBeDefined() 149 | expect(result.stdout).toBe('') 150 | expect(result.stderr).toBeDefined() 151 | expect(result.stderr).toBe('') 152 | expect(result.error).toBeTruthy() 153 | expect(result.error.code).toBe('ENOENT') 154 | expect(result.error.errno).toBe('ENOENT') 155 | expect(result.error.message).toBe('spawnSync nonexistentcommand ENOENT') 156 | expect(result.error.path).toBe('nonexistentcommand') 157 | }) 158 | }) 159 | }) 160 | -------------------------------------------------------------------------------- /spec/config/pathhelper-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | /* eslint-env jasmine */ 3 | 4 | import * as pathhelper from './../../lib/config/pathhelper' 5 | import os from 'os' 6 | import path from 'path' 7 | import { lifecycle } from './../spec-helpers' 8 | 9 | describe('pathhelper', () => { 10 | let gopathToken = '' 11 | 12 | beforeEach(() => { 13 | lifecycle.setup() 14 | runs(() => { 15 | gopathToken = '$GOPATH' 16 | if (os.platform() === 'win32') { 17 | gopathToken = '%GOPATH%' 18 | } 19 | }) 20 | }) 21 | 22 | describe('when working with a single-item path', () => { 23 | it('expands the path', () => { 24 | let env = Object.assign({}, process.env) 25 | env.GOPATH = '~' + path.sep + 'go' 26 | 27 | let result = pathhelper.expand( 28 | env, 29 | path.join('~', 'go', 'go', '..', 'bin', 'goimports') 30 | ) 31 | expect(result).toBeDefined() 32 | expect(result).toBeTruthy() 33 | expect(result).toBe( 34 | path.join(pathhelper.home(), 'go', 'bin', 'goimports') 35 | ) 36 | 37 | result = pathhelper.expand( 38 | env, 39 | path.join(gopathToken, 'go', '..', 'bin', 'goimports') 40 | ) 41 | expect(result).toBeDefined() 42 | expect(result).toBeTruthy() 43 | expect(result).toBe( 44 | path.join(pathhelper.home(), 'go', 'bin', 'goimports') 45 | ) 46 | 47 | let root = path.sep 48 | let nonexistentKey = '$NONEXISTENT' 49 | if (os.platform() === 'win32') { 50 | root = 'c:' + path.sep 51 | nonexistentKey = '%NONEXISTENT%' 52 | } 53 | result = pathhelper.expand( 54 | env, 55 | path.join(root, nonexistentKey, 'go', '..', 'bin', 'goimports') 56 | ) 57 | expect(result).toBeDefined() 58 | expect(result).toBeTruthy() 59 | expect(result).toBe(path.join(root, nonexistentKey, 'bin', 'goimports')) 60 | }) 61 | }) 62 | 63 | describe('when working with a multi-item path', () => { 64 | it('expands the path', () => { 65 | let env = Object.assign({}, process.env) 66 | env.GOPATH = 67 | '~' + path.sep + 'go' + path.delimiter + '~' + path.sep + 'othergo' 68 | 69 | let result = pathhelper.expand( 70 | env, 71 | path.join('~', 'go', 'go', '..', 'bin', 'goimports') 72 | ) 73 | expect(result).toBeDefined() 74 | expect(result).toBeTruthy() 75 | expect(result).toBe( 76 | path.join(pathhelper.home(), 'go', 'bin', 'goimports') 77 | ) 78 | 79 | result = pathhelper.expand( 80 | env, 81 | path.join(gopathToken, 'go', '..', 'bin', 'goimports') 82 | ) 83 | expect(result).toBeDefined() 84 | expect(result).toBeTruthy() 85 | expect(result).toBe( 86 | path.join(pathhelper.home(), 'go', 'bin', 'goimports') 87 | ) 88 | }) 89 | }) 90 | }) 91 | -------------------------------------------------------------------------------- /spec/config/tools/env/env_darwin_amd64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefitzgerald/go-plus/0192d4719894d347e8f0cbc1826031fbcf49b002/spec/config/tools/env/env_darwin_amd64 -------------------------------------------------------------------------------- /spec/config/tools/env/env_linux_amd64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefitzgerald/go-plus/0192d4719894d347e8f0cbc1826031fbcf49b002/spec/config/tools/env/env_linux_amd64 -------------------------------------------------------------------------------- /spec/config/tools/env/env_windows_amd64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefitzgerald/go-plus/0192d4719894d347e8f0cbc1826031fbcf49b002/spec/config/tools/env/env_windows_amd64.exe -------------------------------------------------------------------------------- /spec/config/tools/env/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | env := os.Environ() 10 | for _, item := range env { 11 | fmt.Println(item) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /spec/config/tools/go/env_darwin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const env = `{ 4 | "CC": "clang", 5 | "CGO_CFLAGS": "-g -O2", 6 | "CGO_CPPFLAGS": "", 7 | "CGO_CXXFLAGS": "-g -O2", 8 | "CGO_ENABLED": "1", 9 | "CGO_FFLAGS": "-g -O2", 10 | "CGO_LDFLAGS": "-g -O2", 11 | "CXX": "clang++", 12 | "GCCGO": "gccgo", 13 | "GOARCH": "amd64", 14 | "GOBIN": "", 15 | "GOCACHE": "/Users/foo/Library/Caches/go-build", 16 | "GOEXE": "", 17 | "GOFLAGS": "", 18 | "GOGCCFLAGS": "-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/cn/99kczpg14fg5bmchk2kk8wyh0000gn/T/go-build813444343=/tmp/go-build -gno-record-gcc-switches -fno-common", 19 | "GOHOSTARCH": "amd64", 20 | "GOHOSTOS": "darwin", 21 | "GOMOD": "", 22 | "GOOS": "darwin", 23 | "GOPATH": "/Users/foo/go", 24 | "GOPROXY": "", 25 | "GORACE": "", 26 | "GOROOT": "/usr/local/Cellar/go/1.11.1/libexec", 27 | "GOTMPDIR": "", 28 | "GOTOOLDIR": "/usr/local/Cellar/go/1.11.1/libexec/pkg/tool/darwin_amd64", 29 | "PKG_CONFIG": "pkg-config" 30 | } 31 | ` 32 | -------------------------------------------------------------------------------- /spec/config/tools/go/env_linux.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const env = `{ 4 | "CC": "clang", 5 | "CGO_CFLAGS": "-g -O2", 6 | "CGO_CPPFLAGS": "", 7 | "CGO_CXXFLAGS": "-g -O2", 8 | "CGO_ENABLED": "1", 9 | "CGO_FFLAGS": "-g -O2", 10 | "CGO_LDFLAGS": "-g -O2", 11 | "CXX": "clang++", 12 | "GCCGO": "gccgo", 13 | "GOARCH": "amd64", 14 | "GOBIN": "", 15 | "GOCACHE": "/home/foo/.cache/go-build", 16 | "GOEXE": "", 17 | "GOFLAGS": "", 18 | "GOGCCFLAGS": "-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/cn/99kczpg14fg5bmchk2kk8wyh0000gn/T/go-build813444343=/tmp/go-build -gno-record-gcc-switches -fno-common", 19 | "GOHOSTARCH": "amd64", 20 | "GOHOSTOS": "linux", 21 | "GOMOD": "", 22 | "GOOS": "linux", 23 | "GOPATH": "/home/foo/go", 24 | "GOPROXY": "", 25 | "GORACE": "", 26 | "GOROOT": "/usr/local/go", 27 | "GOTMPDIR": "", 28 | "GOTOOLDIR": "/usr/local/go/pkg/tool/linux_amd64", 29 | "PKG_CONFIG": "pkg-config" 30 | } 31 | ` 32 | -------------------------------------------------------------------------------- /spec/config/tools/go/env_windows.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const env = `{ 4 | "CC": "clang", 5 | "CGO_CFLAGS": "-g -O2", 6 | "CGO_CPPFLAGS": "", 7 | "CGO_CXXFLAGS": "-g -O2", 8 | "CGO_ENABLED": "1", 9 | "CGO_FFLAGS": "-g -O2", 10 | "CGO_LDFLAGS": "-g -O2", 11 | "CXX": "clang++", 12 | "GCCGO": "gccgo", 13 | "GOARCH": "amd64", 14 | "GOBIN": "", 15 | "GOCACHE": "C:\\Users\\foo\\AppData\\local\\go-build", 16 | "GOEXE": ".exe", 17 | "GOFLAGS": "", 18 | "GOGCCFLAGS": "-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=C:\\Users\\foo\\AppData\\local\\go-build\\asdfasdfa -gno-record-gcc-switches -fno-common", 19 | "GOHOSTARCH": "amd64", 20 | "GOHOSTOS": "windows", 21 | "GOMOD": "", 22 | "GOOS": "windows", 23 | "GOPATH": "C:\\Users\\foo\\go", 24 | "GOPROXY": "", 25 | "GORACE": "", 26 | "GOROOT": "C:\\Go", 27 | "GOTMPDIR": "", 28 | "GOTOOLDIR": "C:\\Go\\pkg\\tool\\windows_amd64", 29 | "PKG_CONFIG": "pkg-config" 30 | } 31 | ` 32 | -------------------------------------------------------------------------------- /spec/config/tools/go/go_darwin_amd64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefitzgerald/go-plus/0192d4719894d347e8f0cbc1826031fbcf49b002/spec/config/tools/go/go_darwin_amd64 -------------------------------------------------------------------------------- /spec/config/tools/go/go_linux_amd64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefitzgerald/go-plus/0192d4719894d347e8f0cbc1826031fbcf49b002/spec/config/tools/go/go_linux_amd64 -------------------------------------------------------------------------------- /spec/config/tools/go/go_windows_amd64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefitzgerald/go-plus/0192d4719894d347e8f0cbc1826031fbcf49b002/spec/config/tools/go/go_windows_amd64.exe -------------------------------------------------------------------------------- /spec/config/tools/go/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "go/build" 7 | "io/ioutil" 8 | "os" 9 | "path" 10 | "path/filepath" 11 | "runtime" 12 | "strings" 13 | ) 14 | 15 | func main() { 16 | if len(os.Args) <= 1 { 17 | os.Exit(1) 18 | } 19 | 20 | command := os.Args[1] 21 | switch command { 22 | case "version": 23 | fmt.Printf("go version go1.99.1 %s/%s\n", runtime.GOOS, runtime.GOARCH) 24 | return 25 | case "get": 26 | if os.Args[2] == "-u" { 27 | get(os.Args[3]) 28 | } else { 29 | get(os.Args[2]) 30 | } 31 | return 32 | case "env": 33 | j := len(os.Args) == 3 && os.Args[2] == "-json" 34 | printEnv(j) 35 | return 36 | default: 37 | fmt.Println("unknown command", command) 38 | os.Exit(1) 39 | } 40 | } 41 | 42 | // get simulates `go get` for a command line tool. 43 | // it writes a dummy file go $GOPATH/bin 44 | func get(packagePath string) { 45 | if packagePath == "" { 46 | fmt.Println("no package path was supplied to go get") 47 | os.Exit(1) 48 | } 49 | 50 | paths := strings.Split(packagePath, "/") 51 | if len(paths) <= 1 { 52 | fmt.Println("invalid package path:" + packagePath) 53 | os.Exit(1) 54 | } 55 | p := paths[len(paths)-1] 56 | suffix := "" 57 | if runtime.GOOS == "windows" { 58 | suffix = ".exe" 59 | } 60 | 61 | gopath := os.Getenv("GOPATH") 62 | if gopath == "" { 63 | gopath = build.Default.GOPATH 64 | } 65 | 66 | bin := path.Join(gopath, "bin", p+suffix) 67 | if err := ioutil.WriteFile(bin, []byte("dummy file"), 0755); err != nil { 68 | fmt.Printf("couldnt write to %s: %v", bin, err) 69 | os.Exit(1) 70 | } 71 | } 72 | 73 | func printEnv(inJSON bool) { 74 | var m map[string]string 75 | json.Unmarshal([]byte(env), &m) 76 | 77 | m["GOPATH"] = os.Getenv("GOPATH") 78 | m["GORACE"] = os.Getenv("GORACE") 79 | if gr := os.Getenv("GOROOT"); gr != "" { 80 | m["GOROOT"] = gr 81 | } 82 | m["GOTOOLDIR"] = filepath.Join(m["GOROOT"], "pkg", "tool", runtime.GOOS+"_"+runtime.GOARCH) 83 | 84 | if inJSON { 85 | json.NewEncoder(os.Stdout).Encode(m) 86 | return 87 | } 88 | prefix := "" 89 | if m["GOOS"] == "windows" { 90 | prefix = "set " 91 | } 92 | for k, v := range m { 93 | fmt.Printf("%s%s=%s\n", prefix, k, v) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /spec/config/tools/pwd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | ) 7 | 8 | func main() { 9 | pwd, err := os.Getwd() 10 | if err != nil { 11 | fmt.Println(err) 12 | os.Exit(1) 13 | } 14 | fmt.Println(pwd) 15 | } 16 | -------------------------------------------------------------------------------- /spec/config/tools/pwd/pwd_darwin_amd64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefitzgerald/go-plus/0192d4719894d347e8f0cbc1826031fbcf49b002/spec/config/tools/pwd/pwd_darwin_amd64 -------------------------------------------------------------------------------- /spec/config/tools/pwd/pwd_linux_amd64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefitzgerald/go-plus/0192d4719894d347e8f0cbc1826031fbcf49b002/spec/config/tools/pwd/pwd_linux_amd64 -------------------------------------------------------------------------------- /spec/config/tools/pwd/pwd_windows_amd64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joefitzgerald/go-plus/0192d4719894d347e8f0cbc1826031fbcf49b002/spec/config/tools/pwd/pwd_windows_amd64.exe -------------------------------------------------------------------------------- /spec/doc/godoc-spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | // @flow 3 | 4 | import path from 'path' 5 | import fs from 'fs-extra' 6 | import { lifecycle } from './../spec-helpers' 7 | import { it, fit, ffit, beforeEach, runs } from '../async-spec-helpers' // eslint-disable-line 8 | 9 | describe('godoc', () => { 10 | let godoc 11 | let editor 12 | let gopath 13 | let source = null 14 | let target = null 15 | 16 | beforeEach(async () => { 17 | lifecycle.setup() 18 | gopath = fs.realpathSync(lifecycle.temp.mkdirSync('gopath-')) 19 | process.env.GOPATH = gopath 20 | await lifecycle.activatePackage() 21 | const { mainModule } = lifecycle 22 | mainModule.provideGoConfig() 23 | mainModule.provideGoGet() 24 | godoc = mainModule.loadDoc() 25 | }) 26 | 27 | afterEach(() => { 28 | lifecycle.teardown() 29 | }) 30 | 31 | describe('when determining if the decl is a method', () => { 32 | it('returns the type of the method receiver (non-pointer)', () => { 33 | const receiverType = godoc.declIsMethod('func (a Auth) Foo() error') 34 | expect(receiverType).toBeDefined() 35 | expect(receiverType).toBe('Auth') 36 | }) 37 | 38 | it('returns the type of the method receiver (pointer)', () => { 39 | const receiverType = godoc.declIsMethod('func (a *Auth) Foo() error') 40 | expect(receiverType).toBeDefined() 41 | expect(receiverType).toBe('Auth') 42 | }) 43 | 44 | it('returns undefined for non-methods', () => { 45 | for (const decl of [ 46 | 'func Foo() error', 47 | 'var w io.Writer', 48 | 'const Foo = "Bar"' 49 | ]) { 50 | const result = godoc.declIsMethod(decl) 51 | expect(result).not.toBeDefined() 52 | } 53 | }) 54 | }) 55 | 56 | describe('when the godoc command is invoked on a valid go file', () => { 57 | let result 58 | beforeEach(async () => { 59 | source = path.join(__dirname, '..', 'fixtures') 60 | target = path.join(gopath, 'src', 'godoctest') 61 | fs.copySync(source, target) 62 | atom.project.setPaths([target]) 63 | editor = await atom.workspace.open(path.join(target, 'doc.go')) 64 | editor.setCursorBufferPosition([24, 10]) 65 | }) 66 | 67 | it('provides tooltips', async () => { 68 | const pos = editor.getCursorBufferPosition() 69 | const tip = await godoc.datatip(editor, pos) 70 | expect(tip).toBeTruthy() 71 | expect(tip.range.start).toBe(pos) 72 | expect(tip.range.end).toBe(pos) 73 | expect(tip.markedStrings.length).toEqual(3) 74 | }) 75 | 76 | it('executes gogetdoc successfully', () => { 77 | runs(async () => { 78 | result = await godoc.commandInvoked() 79 | expect(result).toBeTruthy() 80 | expect(result.success).toBe(true) 81 | }) 82 | }) 83 | 84 | it('returns a godoc.org link', () => { 85 | expect(result.doc.gddo).toBe( 86 | 'https://godoc.org/godoctest#Foo.ChangeMessage' 87 | ) 88 | }) 89 | }) 90 | 91 | describe('when the godoc command is invoked on an unsaved go file', () => { 92 | beforeEach(async () => { 93 | source = path.join(__dirname, '..', 'fixtures') 94 | target = path.join(gopath, 'src', 'godoctest') 95 | fs.copySync(source, target) 96 | atom.project.setPaths([target]) 97 | editor = await atom.workspace.open(path.join(target, 'doc.go')) 98 | expect(editor).toBeDefined() 99 | editor.setCursorBufferPosition([24, 35]) 100 | editor.selectLinesContainingCursors() 101 | editor.insertText('fmt.Printf("this line has been modified\\n")\n') 102 | expect(editor.isModified()).toBe(true) 103 | }) 104 | 105 | it('gets the correct documentation', async () => { 106 | let result = false 107 | editor.setCursorBufferPosition([24, 7]) 108 | result = await godoc.commandInvoked() 109 | expect(result).toBeTruthy() 110 | expect(result.success).toBe(true) 111 | expect(result.doc).toBeTruthy() 112 | expect(result.doc.import).toBe('fmt') 113 | expect(result.doc.decl).toBe( 114 | 'func Printf(format string, a ...interface{}) (n int, err error)' 115 | ) 116 | expect(result.doc.gddo).toBe('https://godoc.org/fmt#Printf') 117 | }) 118 | }) 119 | }) 120 | -------------------------------------------------------------------------------- /spec/fixtures/basic/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println() 7 | fmt.Println("") 8 | 9 | } 10 | -------------------------------------------------------------------------------- /spec/fixtures/doc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | // Foo has a message. 11 | type Foo struct { 12 | // Message is a test message. 13 | Message string 14 | } 15 | 16 | // ChangeMessage changes the Foo's message. 17 | func (f *Foo) ChangeMessage(msg string) { 18 | f.Message = msg 19 | } 20 | 21 | func main() { 22 | fmt.Println("Hello, World") 23 | f := &Foo{"This is a test\n"} 24 | io.Copy(os.Stdout, strings.NewReader(f.Message)) 25 | f.ChangeMessage("This is the new message\n") 26 | } 27 | -------------------------------------------------------------------------------- /spec/fixtures/format/gofmt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() { 4 | } 5 | 6 | -------------------------------------------------------------------------------- /spec/fixtures/go-plus-issue-307/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | func SayHello(name string) *log.Logger { 9 | fmt.Printf("Hello, %s\n", name) 10 | return &log.Logger{} 11 | } 12 | 13 | func main() { 14 | 15 | } 16 | -------------------------------------------------------------------------------- /spec/fixtures/go-plus-issue-745/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | func main() { 9 | fmt.Println(string(http.MethodGet)) 10 | } 11 | -------------------------------------------------------------------------------- /spec/fixtures/gofmt.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("Hello, world!") 7 | } 8 | -------------------------------------------------------------------------------- /spec/fixtures/gomodifytags/foo.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | type Bar struct { 4 | QuickBrownFox int 5 | LazyDog string 6 | } 7 | -------------------------------------------------------------------------------- /spec/fixtures/gorename/expected: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | var bar = "Hello, World" 6 | 7 | func main() { 8 | fmt.Println(bar) 9 | } 10 | -------------------------------------------------------------------------------- /spec/fixtures/gorename/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | var foo = "Hello, World" 6 | 7 | func main() { 8 | fmt.Println(foo) 9 | } 10 | -------------------------------------------------------------------------------- /spec/fixtures/implements/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "io" 4 | 5 | type Impl struct{} 6 | 7 | func (Impl) Read(p []byte) (n int, err error) { 8 | return 0, nil 9 | } 10 | 11 | func (Impl) Foo() error { 12 | return nil 13 | } 14 | 15 | type Fooer interface { 16 | Foo() error 17 | } 18 | 19 | func read(r io.Reader) { 20 | 21 | } 22 | 23 | func main() { 24 | i := Impl{} 25 | read(i) 26 | } 27 | -------------------------------------------------------------------------------- /spec/fixtures/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println() 7 | } 8 | -------------------------------------------------------------------------------- /spec/fixtures/navigator/bar.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | func Bar() string { 4 | return "bar" 5 | } 6 | -------------------------------------------------------------------------------- /spec/fixtures/navigator/foo.go: -------------------------------------------------------------------------------- 1 | package foo 2 | 3 | func Foo() string { 4 | return "Foo" + Bar() 5 | } 6 | -------------------------------------------------------------------------------- /spec/fixtures/outline/outline.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | ) 7 | 8 | const ( 9 | A = "a" 10 | B = "b" 11 | C = "c" 12 | ) 13 | 14 | const Answer = 42 15 | 16 | var r io.Reader 17 | 18 | type Number int 19 | 20 | type Fooer interface { 21 | Foo() Number 22 | } 23 | 24 | func (n Number) ToInt() int { 25 | return int(n) 26 | } 27 | 28 | type S struct { 29 | a string 30 | b io.Reader 31 | } 32 | 33 | func Hello() { 34 | fmt.Println("Hello") 35 | } 36 | -------------------------------------------------------------------------------- /spec/fixtures/test/coverage.out: -------------------------------------------------------------------------------- 1 | mode: set 2 | /Users/zmb3/github/go-plus/spec/fixtures/test/src/github.com/testuser/example/go-plus.go:5.13,7.2 1 0 3 | /Users/zmb3/github/go-plus/spec/fixtures/test/src/github.com/testuser/example/go-plus.go:9.21,11.2 1 1 4 | -------------------------------------------------------------------------------- /spec/fixtures/test/src/github.com/testuser/example/go-plus.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println(Hello()) 7 | } 8 | 9 | func Hello() string { 10 | return "Hello, 世界" 11 | } 12 | -------------------------------------------------------------------------------- /spec/fixtures/test/src/github.com/testuser/example/go-plus_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestHello(t *testing.T) { 6 | result := Hello() 7 | if result != "Hello, 世界" { 8 | t.Errorf("Expected %s - got %s", "Hello, 世界", result) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /spec/fixtures/usage/referrers-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "objpos": "/Users/joe/go/src/github.com/kelseyhightower/envconfig/envconfig.go:33:6", 3 | "desc": "type github.com/kelseyhightower/envconfig.Decoder interface{Decode(value string) error}" 4 | } 5 | { 6 | "package": "github.com/kelseyhightower/envconfig", 7 | "refs": [ 8 | { 9 | "pos": "/Users/joe/go/src/github.com/kelseyhightower/envconfig/envconfig.go:306:42", 10 | "text": "func decoderFrom(field reflect.Value) (d Decoder) {" 11 | }, 12 | { 13 | "pos": "/Users/joe/go/src/github.com/kelseyhightower/envconfig/envconfig.go:307:67", 14 | "text": "\tinterfaceFrom(field, func(v interface{}, ok *bool) { d, *ok = v.(Decoder) })" 15 | }, 16 | { 17 | "pos": "/Users/joe/go/src/github.com/kelseyhightower/envconfig/usage.go:40:37", 18 | "text": "\tdecoderType = reflect.TypeOf((*Decoder)(nil)).Elem()" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /spec/format/formatter-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | /* eslint-env jasmine */ 3 | 4 | import path from 'path' 5 | import { ConfigService } from '../../lib/config/service' 6 | import { Formatter } from '../../lib/format/formatter' 7 | import { it, fit, ffit, beforeEach, runs } from '../async-spec-helpers' // eslint-disable-line 8 | 9 | const nl = '\n' 10 | const formattedText = 'package main' + nl + nl + 'func main() {' + nl + '}' + nl 11 | 12 | describe('formatter', () => { 13 | let formatter = null 14 | 15 | beforeEach(async () => { 16 | await atom.packages.activatePackage('language-go') 17 | atom.config.set('editor.defaultLineEnding', 'LF') 18 | atom.config.set('go-plus.test.runTestsOnSave', false) 19 | formatter = new Formatter(new ConfigService().provide()) 20 | }) 21 | 22 | afterEach(() => { 23 | formatter.dispose() 24 | }) 25 | 26 | describe('when a simple file is opened', () => { 27 | let editor 28 | 29 | beforeEach(async () => { 30 | const filePath = path.join( 31 | __dirname, 32 | '..', 33 | 'fixtures', 34 | 'format', 35 | 'gofmt.go' 36 | ) 37 | editor = await atom.workspace.open(filePath) 38 | }) 39 | 40 | describe('for each tool', () => { 41 | for (const tool of ['gofmt', 'goimports', 'goreturns']) { 42 | it('formats on save using ' + tool, () => { 43 | runs(() => { 44 | atom.config.set('go-plus.format.tool', tool) 45 | }) 46 | waitsFor(() => { 47 | return formatter.tool === tool 48 | }) 49 | runs(async () => { 50 | const result = await formatter.formatEntireFile(editor, null) 51 | expect(result).toBeTruthy() 52 | expect(result.formatted).toEqual(formattedText) 53 | }) 54 | }) 55 | } 56 | }) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /spec/get/get-manager-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | /* eslint-env jasmine */ 3 | 4 | import fs from 'fs-extra' 5 | import path from 'path' 6 | import { lifecycle } from './../spec-helpers' 7 | import { it, fit, ffit, beforeEach, runs } from '../async-spec-helpers' // eslint-disable-line 8 | 9 | describe('go-get', () => { 10 | let manager = null 11 | let gopath 12 | let platform 13 | let arch 14 | let executableSuffix = '' 15 | let pathkey = 'PATH' 16 | let go 17 | 18 | beforeEach(async () => { 19 | lifecycle.setup() 20 | gopath = fs.realpathSync(lifecycle.temp.mkdirSync('gopath-')) 21 | const goroot = fs.realpathSync(lifecycle.temp.mkdirSync('goroot-')) 22 | const gorootbin = path.join(goroot, 'bin') 23 | fs.mkdirSync(gorootbin) 24 | platform = process.platform 25 | if (process.arch === 'arm') { 26 | arch = 'arm' 27 | } else if (process.arch === 'ia32') { 28 | // Ugh, Atom is 32-bit on Windows... for now. 29 | if (platform === 'win32') { 30 | arch = 'amd64' 31 | } else { 32 | arch = '386' 33 | } 34 | } else { 35 | arch = 'amd64' 36 | } 37 | 38 | if (process.platform === 'win32') { 39 | platform = 'windows' 40 | executableSuffix = '.exe' 41 | pathkey = 'Path' 42 | } 43 | const fakeexecutable = 'go_' + platform + '_' + arch + executableSuffix 44 | const configPath = path.join(__dirname, '..', 'config') 45 | const fakego = path.join(configPath, 'tools', 'go', fakeexecutable) 46 | go = path.join(gorootbin, 'go' + executableSuffix) 47 | fs.copySync(fakego, go) 48 | process.env[pathkey] = gorootbin 49 | process.env['GOPATH'] = gopath 50 | process.env['GOROOT'] = goroot 51 | 52 | await lifecycle.activatePackage() 53 | const { mainModule } = lifecycle 54 | mainModule.provideGoGet() 55 | manager = mainModule.getservice.getmanager 56 | }) 57 | 58 | afterEach(() => { 59 | lifecycle.teardown() 60 | }) 61 | 62 | describe('manager', () => { 63 | let gocodebinary 64 | let goimportsbinary 65 | beforeEach(() => { 66 | fs.mkdirSync(path.join(gopath, 'bin')) 67 | gocodebinary = path.join(gopath, 'bin', 'gocode' + executableSuffix) 68 | fs.writeFileSync(gocodebinary, '', { encoding: 'utf8', mode: 511 }) 69 | goimportsbinary = path.join(gopath, 'bin', 'goimports' + executableSuffix) 70 | fs.writeFileSync(goimportsbinary, '', { encoding: 'utf8', mode: 511 }) 71 | }) 72 | 73 | it('updates packages', async () => { 74 | let stat = fs.statSync(gocodebinary) 75 | expect(stat.size).toBe(0) 76 | 77 | manager.register('github.com/mdempsky/gocode') 78 | manager.register('golang.org/x/tools/cmd/goimports') 79 | const outcome = await manager.updateTools() 80 | expect(outcome.success).toEqual(true, 'outcome is ', outcome) 81 | expect(outcome.results.length).toBe(2) 82 | 83 | stat = fs.statSync(gocodebinary) 84 | expect(stat.size).toBeGreaterThan(0) 85 | stat = fs.statSync(goimportsbinary) 86 | expect(stat.size).toBeGreaterThan(0) 87 | }) 88 | 89 | it('calls the callback after updating packages, if provided', async () => { 90 | let callbackOutcome 91 | let callbackCalled 92 | let stat = fs.statSync(gocodebinary) 93 | expect(stat.size).toBe(0) 94 | manager.register('golang.org/x/tools/cmd/goimports', o => { 95 | callbackCalled = true 96 | callbackOutcome = o 97 | }) 98 | let outcome = await manager.updateTools() 99 | expect(callbackCalled).toBe(true) 100 | expect(outcome).toBeTruthy() 101 | expect(outcome.success).toBe(true) 102 | expect(outcome.results).toBeTruthy() 103 | expect(outcome.results.length).toBe(1) 104 | expect(outcome.results[0].pack).toBe('golang.org/x/tools/cmd/goimports') 105 | expect(callbackOutcome).toBe(outcome) 106 | }) 107 | }) 108 | }) 109 | -------------------------------------------------------------------------------- /spec/get/provider-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | /* eslint-env jasmine */ 3 | 4 | import { lifecycle } from './../spec-helpers' 5 | import mainModule from './../../lib/main' 6 | 7 | describe('go-get service provider', () => { 8 | beforeEach(() => { 9 | lifecycle.setup() 10 | mainModule.activate() 11 | }) 12 | 13 | afterEach(() => { 14 | lifecycle.teardown() 15 | mainModule.deactivate() 16 | }) 17 | 18 | describe('the provider', () => { 19 | it('is truthy', () => { 20 | expect(mainModule.provideGoGet).toBeDefined() 21 | expect(mainModule.provideGoGet()).toBeTruthy() 22 | }) 23 | }) 24 | 25 | describe('the 2.0.0 provider', () => { 26 | it('is truthy', () => { 27 | expect(mainModule.provideGoGet).toBeDefined() 28 | expect(mainModule.provideGoGet()).toBeTruthy() 29 | }) 30 | 31 | it('has a get function', () => { 32 | expect(mainModule.provideGoGet().get).toBeDefined() 33 | }) 34 | 35 | it('has a register function', () => { 36 | expect(mainModule.provideGoGet().register).toBeDefined() 37 | }) 38 | 39 | describe('register()', () => { 40 | let manager 41 | let provider 42 | beforeEach(() => { 43 | provider = mainModule.provideGoGet() 44 | manager = mainModule.getservice.getmanager 45 | expect(manager).toBeTruthy() 46 | expect(manager.packages).toBeTruthy() 47 | expect(manager.packages.size).toBe(0) 48 | }) 49 | 50 | it('registers a package', () => { 51 | provider.register('github.com/mdempsky/gocode') 52 | expect(manager.packages.size).toBe(1) 53 | provider.register('github.com/mdempsky/gocode') 54 | expect(manager.packages.size).toBe(1) 55 | }) 56 | 57 | it('registers the same package multiple times', () => { 58 | provider.register('github.com/mdempsky/gocode') 59 | expect(manager.packages.size).toBe(1) 60 | provider.register('github.com/mdempsky/gocode') 61 | expect(manager.packages.size).toBe(1) 62 | provider.register('github.com/mdempsky/gocode') 63 | expect(manager.packages.size).toBe(1) 64 | }) 65 | 66 | it('allows a package registration to be disposed', () => { 67 | let registration = provider.register('github.com/mdempsky/gocode') 68 | expect(registration).toBeTruthy() 69 | expect(registration.dispose).toBeDefined() 70 | expect(manager.packages.size).toBe(1) 71 | registration.dispose() 72 | expect(manager.packages.size).toBe(0) 73 | }) 74 | 75 | it('dispose is aware of the number of package registrations', () => { 76 | let registration1 = provider.register('github.com/mdempsky/gocode') 77 | expect(registration1).toBeTruthy() 78 | expect(registration1.dispose).toBeDefined() 79 | expect(manager.packages.size).toBe(1) 80 | let registration2 = provider.register('github.com/mdempsky/gocode') 81 | expect(registration2).toBeTruthy() 82 | expect(registration2.dispose).toBeDefined() 83 | expect(manager.packages.size).toBe(1) 84 | registration1.dispose() 85 | expect(manager.packages.size).toBe(1) 86 | registration2.dispose() 87 | expect(manager.packages.size).toBe(0) 88 | registration1.dispose() 89 | registration2.dispose() 90 | expect(manager.packages.size).toBe(0) 91 | }) 92 | }) 93 | }) 94 | }) 95 | -------------------------------------------------------------------------------- /spec/highlight/highlight-provider-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | /* eslint-env jasmine */ 3 | 4 | import fs from 'fs-extra' 5 | import path from 'path' 6 | import { lifecycle } from './../spec-helpers' 7 | import { it, fit, ffit, beforeEach, runs } from '../async-spec-helpers' // eslint-disable-line 8 | 9 | describe('Highlight Provider', () => { 10 | let highlight 11 | 12 | beforeEach(async () => { 13 | lifecycle.setup() 14 | await lifecycle.activatePackage() 15 | 16 | const { mainModule } = lifecycle 17 | mainModule.provideGoConfig() 18 | highlight = mainModule.provideCodeHighlight() 19 | }) 20 | 21 | afterEach(() => { 22 | lifecycle.teardown() 23 | }) 24 | 25 | it('monitors the config', () => { 26 | atom.config.set('go-plus.guru.highlightIdentifiers', false) 27 | expect(highlight.shouldDecorate).toBe(false) 28 | atom.config.set('go-plus.guru.highlightIdentifiers', true) 29 | expect(highlight.shouldDecorate).toBe(true) 30 | atom.config.set('go-plus.guru.highlightIdentifiers', false) 31 | expect(highlight.shouldDecorate).toBe(false) 32 | }) 33 | 34 | describe('when run on a valid go file', () => { 35 | let editor 36 | let gopath = null 37 | let source = null 38 | let target = null 39 | 40 | beforeEach(async () => { 41 | gopath = fs.realpathSync(lifecycle.temp.mkdirSync('gopath-')) 42 | process.env.GOPATH = gopath 43 | 44 | source = path.join(__dirname, '..', 'fixtures') 45 | target = path.join(gopath, 'src', 'what') 46 | fs.copySync(source, target) 47 | 48 | atom.config.set('go-plus.guru.highlightIdentifiers', true) 49 | 50 | editor = await atom.workspace.open(path.join(target || '.', 'doc.go')) 51 | }) 52 | 53 | it('returns the appropriate ranges', async () => { 54 | editor.setCursorBufferPosition([22, 2]) 55 | 56 | const ranges = await highlight.highlight( 57 | editor, 58 | editor.getCursorBufferPosition() 59 | ) 60 | expect(ranges.length).toEqual(3) 61 | expect(ranges[0].start.row).toEqual(22) 62 | expect(ranges[0].start.column).toEqual(1) 63 | expect(ranges[1].start.row).toEqual(23) 64 | expect(ranges[1].start.column).toEqual(38) 65 | expect(ranges[2].start.row).toEqual(24) 66 | expect(ranges[2].start.column).toEqual(1) 67 | 68 | ranges.forEach(r => { 69 | expect(editor.getTextInBufferRange(r)).toEqual('f') 70 | }) 71 | }) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /spec/implements/implements-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | /* eslint-env jasmine */ 3 | 4 | import fs from 'fs-extra' 5 | import path from 'path' 6 | import { lifecycle } from './../spec-helpers' 7 | import { it, fit, ffit, beforeEach, runs } from '../async-spec-helpers' // eslint-disable-line 8 | 9 | describe('implements', () => { 10 | let impl 11 | let editor 12 | let gopath 13 | let source 14 | let target 15 | 16 | beforeEach(async () => { 17 | lifecycle.setup() 18 | 19 | gopath = fs.realpathSync(lifecycle.temp.mkdirSync('gopath-')) 20 | process.env.GOPATH = gopath 21 | 22 | source = path.join(__dirname, '..', 'fixtures', 'implements') 23 | target = path.join(gopath, 'src', 'implements') 24 | fs.copySync(source, target) 25 | await lifecycle.activatePackage() 26 | const { mainModule } = lifecycle 27 | mainModule.provideGoConfig() 28 | impl = mainModule.loadImplements() 29 | impl.view = {} 30 | impl.view.update = jasmine.createSpy('update') 31 | impl.requestFocus = jasmine 32 | .createSpy('requestFocus') 33 | .andReturn(Promise.resolve()) 34 | 35 | editor = await atom.workspace.open(path.join(target, 'main.go')) 36 | }) 37 | 38 | afterEach(() => { 39 | lifecycle.teardown() 40 | }) 41 | 42 | it('updates the view when invoking guru', async () => { 43 | await impl.handleCommand() 44 | expect(impl.view.update).toHaveBeenCalled() 45 | }) 46 | 47 | it('updates the view when guru fails', async () => { 48 | await impl.handleCommand() 49 | expect(impl.view.update).toHaveBeenCalled() 50 | expect(impl.view.update.calls.length).toBe(2) 51 | 52 | const args0 = impl.view.update.calls[0].args[0] 53 | const args1 = impl.view.update.calls[1].args[0] 54 | expect(args0.startsWith('running guru')).toBe(true) 55 | expect(args1.startsWith('guru failed')).toBe(true) 56 | }) 57 | 58 | it('updates the view when guru succeeds', async () => { 59 | editor.setCursorBufferPosition([4, 9]) 60 | await impl.handleCommand() 61 | expect(impl.view.update).toHaveBeenCalled() 62 | expect(impl.view.update.calls.length).toBe(2) 63 | 64 | const args0 = impl.view.update.calls[0].args[0] 65 | const args1 = impl.view.update.calls[1].args[0] 66 | expect(args0.startsWith('running guru')).toBe(true) 67 | 68 | expect(typeof args1).toBe('object') 69 | expect(args1.type.kind).toBe('struct') 70 | expect(args1.type.name).toBe('implements.Impl') 71 | expect(args1.from.length).toBe(2) 72 | expect(args1.from[0].name).toBe('implements.Fooer') 73 | expect(args1.from[1].name).toBe('io.Reader') 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /spec/import/importer-spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | 3 | import path from 'path' 4 | 5 | import { importablePackages } from './../../lib/import/importer' 6 | import { lifecycle } from './../spec-helpers' 7 | import { it, fit, ffit, beforeEach, runs } from '../async-spec-helpers' // eslint-disable-line 8 | 9 | describe('importablePackages', () => { 10 | const all = [ 11 | 'github.com/user1/project1/', 12 | 'github.com/user1/project1/subpackage', 13 | 'github.com/user1/project1/internal/privatelib', 14 | 'github.com/user2/project1/vendor/github.com/author/lib/subpackage', 15 | 'github.com/user2/project1/nested/package', 16 | 'github.com/user2/project2/subpackage/vendor/github.com/author/lib/subpackage' 17 | ] 18 | 19 | it('does not present vendor or internal directories from other projects', () => { 20 | const importable = importablePackages('github.com/user3/newproject', all) 21 | expect(importable).toEqual([all[0], all[1], all[4]]) 22 | }) 23 | 24 | it('presents internal packages for the same project', () => { 25 | const importable = importablePackages('github.com/user1/project1/foo', all) 26 | expect(importable).toContain( 27 | 'github.com/user1/project1/internal/privatelib' 28 | ) 29 | }) 30 | 31 | it('presents vendor packages for the same project', () => { 32 | const importable = importablePackages( 33 | 'github.com/user2/project1/foo/bar', 34 | all 35 | ) 36 | expect(importable).toContain('github.com/author/lib/subpackage') 37 | }) 38 | 39 | it('handles nested vendor packages correctly', () => { 40 | const nestedVendor = 'github.com/author/lib/subpackage' 41 | expect( 42 | importablePackages('github.com/user2/project2/foo/bar', all) 43 | ).not.toContain(nestedVendor) 44 | expect( 45 | importablePackages('github.com/user2/project2/subpackage/a/b', all) 46 | ).toContain(nestedVendor) 47 | }) 48 | }) 49 | 50 | describe('importer', () => { 51 | let importer = null 52 | let editor = null 53 | 54 | beforeEach(async () => { 55 | lifecycle.setup() 56 | await lifecycle.activatePackage() 57 | const { mainModule } = lifecycle 58 | mainModule.provideGoConfig() 59 | importer = mainModule.loadImporter() 60 | editor = await atom.workspace.open( 61 | path.join(__dirname, '..', 'fixtures', 'doc.go') 62 | ) 63 | }) 64 | 65 | afterEach(() => lifecycle.teardown()) 66 | 67 | it('adds imports', async () => { 68 | const result = await importer.addImport('bufio') 69 | expect(result.success).toBe(true) 70 | expect(editor.lineTextForBufferRow(3).trim()).toEqual('"bufio"') 71 | }) 72 | }) 73 | -------------------------------------------------------------------------------- /spec/main/main-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | /* eslint-env jasmine */ 3 | 4 | import { lifecycle } from './../spec-helpers' 5 | import { it, fit, ffit, beforeEach, runs } from '../async-spec-helpers' // eslint-disable-line 6 | 7 | describe('go-plus', () => { 8 | beforeEach(async () => { 9 | lifecycle.setup() 10 | await lifecycle.activatePackage() 11 | }) 12 | 13 | afterEach(() => { 14 | lifecycle.teardown() 15 | }) 16 | 17 | describe('when the go-plus package is activated', () => { 18 | it('activates successfully', () => { 19 | const { mainModule } = lifecycle 20 | expect(mainModule).toBeDefined() 21 | expect(mainModule).toBeTruthy() 22 | expect(mainModule.activate).toBeDefined() 23 | expect(mainModule.deactivate).toBeDefined() 24 | }) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /spec/navigator/navigator-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | /* eslint-env jasmine */ 3 | 4 | import path from 'path' 5 | import fs from 'fs-extra' 6 | import { lifecycle } from './../spec-helpers' 7 | import { it, fit, ffit, beforeEach, runs } from '../async-spec-helpers' // eslint-disable-line 8 | 9 | describe('go to definition', () => { 10 | let navigator = null 11 | let gopath = null 12 | 13 | beforeEach(async () => { 14 | lifecycle.setup() 15 | gopath = fs.realpathSync(lifecycle.temp.mkdirSync('gopath-')) 16 | process.env.GOPATH = gopath 17 | await lifecycle.activatePackage() 18 | const { mainModule } = lifecycle 19 | mainModule.provideGoConfig() 20 | mainModule.provideGoGet() 21 | navigator = mainModule.loadNavigator() 22 | }) 23 | 24 | afterEach(() => { 25 | lifecycle.teardown() 26 | }) 27 | 28 | describe('when invoked on a valid project file', () => { 29 | let sourceDir 30 | let targetDir 31 | let editor 32 | 33 | beforeEach(async () => { 34 | sourceDir = path.join(__dirname, '..', 'fixtures', 'navigator') 35 | targetDir = path.join(gopath, 'src', 'godeftest') 36 | fs.copySync(sourceDir, targetDir) 37 | editor = await atom.workspace.open(path.join(targetDir, 'foo.go')) 38 | }) 39 | 40 | describe('when using the godef navigator mode', () => { 41 | beforeEach(() => { 42 | atom.config.set('go-plus.navigator.mode', 'godef') 43 | }) 44 | 45 | it('navigates to the correct location', async () => { 46 | editor.setCursorBufferPosition([3, 17]) 47 | await navigator.gotoDefinitionForWordAtCursor() 48 | const activeEditor = atom.workspace.getActiveTextEditor() 49 | expect(activeEditor.getTitle()).toBe('bar.go') 50 | 51 | const pos = activeEditor.getCursorBufferPosition() 52 | expect(pos.row).toBe(2) 53 | expect(pos.column).toBe(5) 54 | expect(navigator.navigationStack.isEmpty()).toBe(false) 55 | }) 56 | }) 57 | 58 | describe('when using the guru navigator mode', () => { 59 | beforeEach(() => { 60 | atom.config.set('go-plus.navigator.mode', 'guru') 61 | }) 62 | 63 | it('navigates to the correct location', async () => { 64 | editor.setCursorBufferPosition([3, 17]) // at the beginning of -> `Bar()` 65 | await navigator.gotoDefinitionForWordAtCursor() 66 | const activeEditor = atom.workspace.getActiveTextEditor() 67 | expect(activeEditor.getTitle()).toBe('bar.go') 68 | const pos = activeEditor.getCursorBufferPosition() 69 | expect(pos.row).toBe(2) 70 | expect(pos.column).toBe(5) 71 | expect(navigator.navigationStack.isEmpty()).toBe(false) 72 | }) 73 | 74 | it('navigates to the correct location if at the end of a word', async () => { 75 | editor.setCursorBufferPosition([3, 20]) // at the end of `Bar()` <- 76 | await navigator.gotoDefinitionForWordAtCursor() 77 | 78 | const activeEditor = atom.workspace.getActiveTextEditor() 79 | expect(activeEditor.getTitle()).toBe('bar.go') 80 | 81 | const pos = activeEditor.getCursorBufferPosition() 82 | expect(pos.row).toBe(2) 83 | expect(pos.column).toBe(5) 84 | 85 | expect(navigator.navigationStack.isEmpty()).toBe(false) 86 | }) 87 | }) 88 | }) 89 | }) 90 | -------------------------------------------------------------------------------- /spec/outline/outline-provider-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | /* eslint-env jasmine */ 3 | 4 | import path from 'path' 5 | import { OutlineProvider } from '../../lib/outline/outline-provider' 6 | import { ConfigService } from '../../lib/config/service' 7 | import { it, fit, ffit, beforeEach, runs } from '../async-spec-helpers' // eslint-disable-line 8 | 9 | describe('Outline Provider', () => { 10 | let editor 11 | let provider 12 | let outline 13 | 14 | beforeEach(async () => { 15 | provider = new OutlineProvider(new ConfigService().provide()) 16 | 17 | const p = path.join(__dirname, '..', 'fixtures', 'outline', 'outline.go') 18 | editor = await atom.workspace.open(p) 19 | 20 | outline = await provider.getOutline(editor) 21 | }) 22 | 23 | it('returns an outline', () => { 24 | expect(outline).toBeDefined() 25 | expect(outline.outlineTrees).toBeDefined() 26 | expect(outline.outlineTrees.length).toEqual(1) 27 | }) 28 | 29 | it('returns the file at the root of the outline', () => { 30 | const f = outline.outlineTrees[0] 31 | expect(f.kind).toEqual('file') 32 | expect(f.plainText).toEqual('main') 33 | expect(f.representativeName).toEqual('main') 34 | expect(f.startPosition.row).toEqual(0) 35 | expect(f.startPosition.column).toEqual(0) 36 | expect(f.children.length).toEqual(12) 37 | }) 38 | 39 | it('returns packages for imports', () => { 40 | const f = outline.outlineTrees[0] 41 | const packages = f.children.filter(o => o.kind === 'package') 42 | expect(packages.length).toEqual(2) 43 | 44 | expect(packages[0].plainText).toEqual('"fmt"') 45 | expect(packages[0].startPosition.row).toEqual(3) 46 | expect(packages[0].startPosition.column).toEqual(1) 47 | expect(packages[0].endPosition.row).toEqual(3) 48 | expect(packages[0].endPosition.column).toEqual(6) 49 | 50 | expect(packages[1].plainText).toEqual('"io"') 51 | expect(packages[1].startPosition.row).toEqual(4) 52 | expect(packages[1].startPosition.column).toEqual(1) 53 | expect(packages[1].endPosition.row).toEqual(4) 54 | expect(packages[1].endPosition.column).toEqual(5) 55 | }) 56 | 57 | it('identifies single-line constants', () => { 58 | const f = outline.outlineTrees[0] 59 | const consts = f.children.filter(o => o.plainText === 'Answer') 60 | expect(consts.length).toEqual(1) 61 | expect(consts[0].kind).toEqual('constant') 62 | }) 63 | 64 | it('identifies interfaces', () => { 65 | const f = outline.outlineTrees[0] 66 | const ifaces = f.children.filter(o => o.kind === 'interface') 67 | expect(ifaces.length).toEqual(1) 68 | expect(ifaces[0].plainText).toEqual('Fooer') 69 | expect(ifaces[0].startPosition.row).toEqual(19) 70 | expect(ifaces[0].startPosition.column).toEqual(5) 71 | expect(ifaces[0].endPosition.row).toEqual(21) 72 | expect(ifaces[0].endPosition.column).toEqual(1) 73 | }) 74 | 75 | it('identifies methods', () => { 76 | const f = outline.outlineTrees[0] 77 | const methods = f.children.filter(o => o.kind === 'method') 78 | expect(methods.length).toEqual(1) 79 | expect(methods[0].plainText).toEqual('(Number).ToInt') 80 | }) 81 | 82 | it('identifies functions', () => { 83 | const f = outline.outlineTrees[0] 84 | const funcs = f.children.filter(o => o.kind === 'function') 85 | expect(funcs.length).toEqual(1) 86 | expect(funcs[0].plainText).toEqual('Hello') 87 | }) 88 | 89 | it('identifies structs', () => { 90 | const f = outline.outlineTrees[0] 91 | const ss = f.children.filter(o => o.plainText === 'S') 92 | expect(ss.length).toEqual(1) 93 | const s = ss[0] 94 | expect(s.kind).toEqual('class') 95 | }) 96 | 97 | it('identifies type definitions', () => { 98 | const f = outline.outlineTrees[0] 99 | const nums = f.children.filter(o => o.plainText === 'Number') 100 | expect(nums.length).toEqual(1) 101 | 102 | // TODO: there's no icon for type, so provide a custom icon here.. 103 | expect(nums[0].kind).toEqual('type') // there's no icon for type 104 | }) 105 | 106 | it('identifies variables', () => { 107 | const f = outline.outlineTrees[0] 108 | const rs = f.children.filter(o => o.plainText === 'r') 109 | expect(rs.length).toEqual(1) 110 | expect(rs[0].kind).toEqual('variable') 111 | }) 112 | 113 | it('identifies constants/enums', () => { 114 | // go-outline doesn't provide this for us 115 | const f = outline.outlineTrees[0] 116 | const items = f.children.filter(o => ['A', 'B', 'C'].includes(o.plainText)) 117 | expect(items.length).toEqual(3) 118 | 119 | // TODO: expect kind to be constant or enum instead 120 | items.forEach(i => expect(i.kind).toEqual('variable')) 121 | }) 122 | 123 | it('handles multi-byte characters in the input file', () => { 124 | // TODO ... 125 | }) 126 | }) 127 | -------------------------------------------------------------------------------- /spec/output-panel-spec.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | /* eslint-env jasmine */ 3 | 4 | import { OutputPanel } from './../lib/output-panel' 5 | 6 | describe('test panel', () => { 7 | let outputPanel 8 | 9 | beforeEach(() => { 10 | outputPanel = new OutputPanel() 11 | }) 12 | 13 | describe('make link', () => { 14 | it('renders text without links', () => { 15 | const elements = outputPanel.makeLink('test') 16 | expect(elements.length).toBe(1) 17 | }) 18 | 19 | it('renders a link', () => { 20 | const elements = outputPanel.makeLink('/Users/user/go/src/foo/bar.go:23') 21 | expect(elements).toBeTruthy() 22 | expect(elements.length).toBe(2) 23 | expect(elements[0].tag).toBe('span') 24 | expect(elements[1].tag).toBe('a') 25 | expect(elements[0].children[0].text).toBe('') 26 | expect(elements[1].children[0].text).toBe( 27 | '/Users/user/go/src/foo/bar.go:23' 28 | ) 29 | }) 30 | 31 | it('renders a link to a go file (relative path)', () => { 32 | const elements = outputPanel.makeLink('failure at foo/bar.go:23') 33 | expect(elements.length).toBe(2) 34 | expect(elements[0].tag).toBe('span') 35 | expect(elements[1].tag).toBe('a') 36 | expect(elements[1].children[0].text).toBe('foo/bar.go:23') 37 | }) 38 | 39 | it('renders a link to a go file (absolute path)', () => { 40 | const elements = outputPanel.makeLink( 41 | 'failure at /home/user/go/src/foo/bar.go:23' 42 | ) 43 | expect(elements.length).toBe(2) 44 | expect(elements[0].tag).toBe('span') 45 | expect(elements[1].tag).toBe('a') 46 | expect(elements[1].children[0].text).toBe( 47 | '/home/user/go/src/foo/bar.go:23' 48 | ) 49 | }) 50 | 51 | it('renders a link to a test go file (relative path)', () => { 52 | const elements = outputPanel.makeLink('failure at foo/bar_test.go:23') 53 | expect(elements.length).toBe(2) 54 | expect(elements[0].tag).toBe('span') 55 | expect(elements[1].tag).toBe('a') 56 | expect(elements[1].children[0].text).toBe('foo/bar_test.go:23') 57 | }) 58 | 59 | it('renders a link to a test go file (absolute path)', () => { 60 | const elements = outputPanel.makeLink( 61 | 'failure at /home/user/go/src/foo/bar_test.go:23' 62 | ) 63 | expect(elements.length).toBe(2) 64 | expect(elements[0].tag).toBe('span') 65 | expect(elements[1].tag).toBe('a') 66 | expect(elements[1].children[0].text).toBe( 67 | '/home/user/go/src/foo/bar_test.go:23' 68 | ) 69 | }) 70 | 71 | it('renders links to Windows paths', () => { 72 | const elements = outputPanel.makeLink( 73 | 'failure at C:\\Users\\Me\\go\\src\\foo\\bar_test.go:23' 74 | ) 75 | expect(elements.length).toBe(2) 76 | expect(elements[0].tag).toBe('span') 77 | expect(elements[1].tag).toBe('a') 78 | expect(elements[1].children[0].text).toBe( 79 | 'C:\\Users\\Me\\go\\src\\foo\\bar_test.go:23' 80 | ) 81 | }) 82 | 83 | it('renders multiple links', () => { 84 | const elements = outputPanel.makeLink( 85 | 'failures at foo/bar.go:12 and baz/quux.go:34' 86 | ) 87 | expect(elements.length).toBe(4) 88 | expect(elements[0].tag).toBe('span') 89 | expect(elements[1].tag).toBe('a') 90 | expect(elements[2].tag).toBe('span') 91 | expect(elements[3].tag).toBe('a') 92 | 93 | expect(elements[1].children[0].text).toBe('foo/bar.go:12') 94 | expect(elements[3].children[0].text).toBe('baz/quux.go:34') 95 | }) 96 | 97 | it('renders a link with prefix and suffix', () => { 98 | const elements = outputPanel.makeLink('prefix foo/bar.go:23 suffix') 99 | expect(elements.length).toBe(3) 100 | expect(elements[0].tag).toBe('span') 101 | expect(elements[1].tag).toBe('a') 102 | expect(elements[2].tag).toBe('span') 103 | 104 | expect(elements[0].children[0].text).toBe('prefix ') 105 | expect(elements[1].children[0].text).toBe('foo/bar.go:23') 106 | expect(elements[2].children[0].text).toBe(' suffix') 107 | }) 108 | 109 | it('renders links in multi-line text', () => { 110 | const elements = outputPanel.makeLink( 111 | '--- FAIL: TestFail (0.00s)\n\tbar_test.go:23: Error!\nFAIL' 112 | ) 113 | expect(elements.length).toBe(3) 114 | expect(elements[0].children[0].text).toBe( 115 | '--- FAIL: TestFail (0.00s)\n\t' 116 | ) 117 | expect(elements[1].children[0].text).toBe('bar_test.go:23') 118 | expect(elements[2].children[0].text).toBe(': Error!\nFAIL') 119 | }) 120 | }) 121 | }) 122 | -------------------------------------------------------------------------------- /spec/panel-manager-spec.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | /* eslint-env jasmine */ 3 | 4 | import { lifecycle } from './spec-helpers' 5 | import { EmptyTabView } from './../lib/panel/empty-tab-view' 6 | import { it, fit, ffit, beforeEach, runs } from './async-spec-helpers' // eslint-disable-line 7 | 8 | describe('panel manager', () => { 9 | let pm = null 10 | 11 | beforeEach(async () => { 12 | lifecycle.setup() 13 | 14 | await lifecycle.activatePackage() 15 | 16 | const { mainModule } = lifecycle 17 | pm = mainModule.getPanelManager() 18 | }) 19 | 20 | afterEach(() => { 21 | lifecycle.teardown() 22 | }) 23 | 24 | describe('registerViewProvider', () => { 25 | let view 26 | let model 27 | let disp 28 | 29 | beforeEach(() => { 30 | view = new EmptyTabView() 31 | model = { key: 'foo', tab: { name: 'dummy' } } 32 | disp = pm.registerViewProvider(view, model) 33 | }) 34 | 35 | afterEach(() => { 36 | disp.dispose() 37 | }) 38 | 39 | it('records the view provider by key', () => { 40 | const { view: v, model: m } = pm.viewProviders.get(model.key) 41 | expect(v).toBe(view) 42 | expect(m).toBe(model) 43 | }) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /spec/references/references-provider-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | /* eslint-env jasmine */ 3 | 4 | import { lifecycle } from './../spec-helpers' 5 | import { ReferencesProvider } from './../../lib/references/references-provider' 6 | import path from 'path' 7 | import fs from 'fs' 8 | 9 | describe('References Provider', () => { 10 | let references 11 | 12 | beforeEach(() => { 13 | lifecycle.setup() 14 | references = new ReferencesProvider() 15 | }) 16 | 17 | afterEach(() => { 18 | lifecycle.teardown() 19 | }) 20 | 21 | describe('parseStream', () => { 22 | it('is able to handle a json object stream correctly', () => { 23 | // A JSON object stream is another name for malformed JSON 24 | const file = fs.readFileSync( 25 | path.join(__dirname, '..', 'fixtures', 'usage', 'referrers-1.json'), 26 | 'utf8' 27 | ) 28 | const result = references.parseStream(file) 29 | expect(result).toBeTruthy() 30 | expect(result.length).toBe(2) 31 | expect(result[0].objpos).toBe( 32 | '/Users/joe/go/src/github.com/kelseyhightower/envconfig/envconfig.go:33:6' 33 | ) 34 | expect(result[0].desc).toBe( 35 | 'type github.com/kelseyhightower/envconfig.Decoder interface{Decode(value string) error}' 36 | ) 37 | expect(result[1].package).toBe('github.com/kelseyhightower/envconfig') 38 | expect(result[1].refs).toBeTruthy() 39 | expect(result[1].refs.length).toBe(3) 40 | }) 41 | }) 42 | 43 | describe('referrers', () => { 44 | it('parses output correctly', () => { 45 | const file = fs.readFileSync( 46 | path.join(__dirname, '..', 'fixtures', 'usage', 'referrers-1.json'), 47 | 'utf8' 48 | ) 49 | const j = references.parseStream(file) 50 | const result = references.parse(j) 51 | expect(result).toBeTruthy() 52 | expect(result.length).toEqual(3) 53 | expect(result[0].uri).toEqual( 54 | '/Users/joe/go/src/github.com/kelseyhightower/envconfig/envconfig.go' 55 | ) 56 | expect(result[0].range.start.row).toBe(306) 57 | expect(result[0].range.start.column).toBe(42) 58 | expect(result[0].name).toBe( 59 | 'func decoderFrom(field reflect.Value) (d Decoder) {' 60 | ) 61 | }) 62 | }) 63 | }) 64 | -------------------------------------------------------------------------------- /spec/rename/gorename-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | /* eslint-env jasmine */ 3 | 4 | import path from 'path' 5 | import fs from 'fs-extra' 6 | import { wordAndOffset } from './../../lib/utils' 7 | import { lifecycle } from './../spec-helpers' 8 | import { it, fit, ffit, beforeEach, runs } from '../async-spec-helpers' // eslint-disable-line 9 | 10 | describe('gorename', () => { 11 | let gorename = null 12 | let editor = null 13 | let gopath = null 14 | let source = null 15 | let target = null 16 | 17 | beforeEach(async () => { 18 | lifecycle.setup() 19 | gopath = fs.realpathSync(lifecycle.temp.mkdirSync('gopath-')) 20 | process.env.GOPATH = gopath 21 | await lifecycle.activatePackage() 22 | const { mainModule } = lifecycle 23 | mainModule.provideGoConfig() 24 | mainModule.provideGoGet() 25 | gorename = mainModule.loadGorename() 26 | }) 27 | 28 | afterEach(() => { 29 | lifecycle.teardown() 30 | }) 31 | 32 | describe('when a simple file is open', () => { 33 | beforeEach(async () => { 34 | source = path.join(__dirname, '..', 'fixtures', 'gorename') 35 | target = path.join(gopath, 'src', 'basic') 36 | fs.copySync(source, target) 37 | editor = await atom.workspace.open(path.join(target, 'main.go')) 38 | }) 39 | 40 | it('renames a single token', async () => { 41 | editor.setCursorBufferPosition([4, 5]) 42 | const info = wordAndOffset(editor) 43 | expect(info.word).toBe('foo') 44 | expect(info.offset).toBe(33) 45 | 46 | const file = editor.getBuffer().getPath() 47 | const cwd = path.dirname(file) 48 | 49 | const cmd = await lifecycle.mainModule 50 | .provideGoConfig() 51 | .locator.findTool('gorename') 52 | expect(cmd).toBeTruthy() 53 | 54 | const result = await gorename.runGorename( 55 | file, 56 | info.offset, 57 | cwd, 58 | 'bar', 59 | cmd 60 | ) 61 | expect(result).toBeTruthy() 62 | expect(result.success).toBe(true) 63 | expect(result.result.stdout.trim()).toBe( 64 | 'Renamed 2 occurrences in 1 file in 1 package.' 65 | ) 66 | 67 | editor.destroy() 68 | editor = await atom.workspace.open(path.join(target, 'main.go')) 69 | 70 | const expected = fs.readFileSync( 71 | path.join(__dirname, '..', 'fixtures', 'gorename', 'expected'), 72 | 'utf8' 73 | ) 74 | const actual = editor.getText() 75 | expect(actual).toBe(expected) 76 | }) 77 | }) 78 | }) 79 | -------------------------------------------------------------------------------- /spec/spec-helpers.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | /* eslint-env jasmine */ 4 | 5 | import temp from '@atom/temp' 6 | 7 | class Lifecycle { 8 | env: Object 9 | temp: temp 10 | mainModule: any 11 | 12 | constructor() { 13 | this.env = Object.assign({}, process.env) 14 | this.temp = temp 15 | this.temp.track() 16 | this.mainModule = null 17 | } 18 | 19 | dispose() { 20 | this.temp = null 21 | this.mainModule = null 22 | } 23 | 24 | setup() { 25 | this.env = Object.assign({}, process.env) 26 | atom.config.set('go-plus.disableToolCheck', true) 27 | atom.config.set('go-plus.testing', true) 28 | atom.config.set('go-plus.guru.highlightIdentifiers', false) 29 | } 30 | 31 | activatePackage() { 32 | atom.packages.triggerDeferredActivationHooks() 33 | atom.packages.triggerActivationHook('language-go:grammar-used') 34 | atom.packages.triggerActivationHook('core:loaded-shell-environment') 35 | 36 | return Promise.all([ 37 | atom.packages.activatePackage('language-go').catch(e => { 38 | // eslint-disable-next-line no-console 39 | jasmine.getEnv().currentSpec.fail(e) 40 | throw e 41 | }), 42 | atom.packages.activatePackage('go-plus').then( 43 | pkg => { 44 | this.mainModule = pkg.mainModule 45 | return pkg 46 | }, 47 | e => { 48 | jasmine.getEnv().currentSpec.fail(e) 49 | throw e 50 | } 51 | ) 52 | ]).catch(e => { 53 | jasmine.getEnv().currentSpec.fail(e) 54 | throw e 55 | }) 56 | } 57 | 58 | teardown() { 59 | if (this.env) { 60 | process.env = this.env 61 | } 62 | if (this.mainModule) this.mainModule.dispose() 63 | this.mainModule = null 64 | atom.config.set('go-plus.testing', false) 65 | } 66 | } 67 | 68 | const lifecycle = new Lifecycle() 69 | 70 | export { lifecycle } 71 | -------------------------------------------------------------------------------- /spec/test/gocover-parser-spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-env jasmine */ 2 | 3 | import { ranges } from './../../lib/test/gocover-parser' 4 | import path from 'path' 5 | import { it, fit, ffit, beforeEach, runs } from '../async-spec-helpers' // eslint-disable-line 6 | 7 | describe('gocover-parser', () => { 8 | it('parses the file for a single package', async () => { 9 | const testDir = path.join(__dirname, '..', 'fixtures', 'test') 10 | const file = path.join(testDir, 'coverage.out') 11 | const src = 12 | '/Users/zmb3/github/go-plus/spec/fixtures/test/src/github.com/testuser/example/go-plus.go' 13 | 14 | const result = ranges(file) 15 | expect(result.length).toBe(2) 16 | expect(result[0].range.start.column).toBe(12) 17 | expect(result[0].range.start.row).toBe(4) 18 | expect(result[0].range.end.column).toBe(1) 19 | expect(result[0].range.end.row).toBe(6) 20 | expect(result[0].count).toBe(0) 21 | expect(result[0].file).toBe(src) 22 | 23 | expect(result[1].range.start.column).toBe(20) 24 | expect(result[1].range.start.row).toBe(8) 25 | expect(result[1].range.end.column).toBe(1) 26 | expect(result[1].range.end.row).toBe(10) 27 | expect(result[1].count).toBe(1) 28 | expect(result[1].file).toBe(src) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /spec/utils-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel' 2 | /* eslint-env jasmine */ 3 | 4 | import { lifecycle } from './spec-helpers' 5 | import { parseGoPosition, stat } from './../lib/utils' 6 | 7 | describe('utils', () => { 8 | beforeEach(() => { 9 | lifecycle.setup() 10 | }) 11 | 12 | afterEach(() => { 13 | lifecycle.teardown() 14 | }) 15 | 16 | describe('parseGoPosition(identifier)', () => { 17 | it('parses unix paths', () => { 18 | const parsed = parseGoPosition( 19 | '/private/temp/src/gopath-11726-3832-1xl0vhg.4128uayvi/src/what/doc.go:23:2' 20 | ) 21 | expect(parsed).toBeTruthy() 22 | expect(parsed.file).toBe( 23 | '/private/temp/src/gopath-11726-3832-1xl0vhg.4128uayvi/src/what/doc.go' 24 | ) 25 | expect(parsed.line).toBe(23) 26 | expect(parsed.column).toBe(2) 27 | }) 28 | 29 | it('parses windows paths', () => { 30 | const parsed = parseGoPosition( 31 | 'C:\\Users\\vagrant\\AppData\\Local\\Temp\\2\\gopath-11726-3832-1xl0vhg.4128uayvi\\src\\what\\doc.go:23:2' 32 | ) 33 | expect(parsed).toBeTruthy() 34 | expect(parsed.file).toBe( 35 | 'C:\\Users\\vagrant\\AppData\\Local\\Temp\\2\\gopath-11726-3832-1xl0vhg.4128uayvi\\src\\what\\doc.go' 36 | ) 37 | expect(parsed.line).toBe(23) 38 | expect(parsed.column).toBe(2) 39 | }) 40 | }) 41 | 42 | describe('stat', () => { 43 | it('is rejected for nonexistent files', () => { 44 | let result, err 45 | 46 | waitsForPromise(() => { 47 | return stat('nonexistentthing') 48 | .then(r => (result = r)) 49 | .catch(e => (err = e)) 50 | }) 51 | 52 | runs(() => { 53 | expect(result).toBeFalsy() 54 | expect(err).toBeTruthy() 55 | }) 56 | }) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /styles/ansi.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | .go-plus-ansi-bold { 4 | font-weight: bold; 5 | } 6 | 7 | .go-plus-ansi-dim { 8 | font-weight: lighter; 9 | } 10 | 11 | .go-plus-ansi-italic { 12 | font-style: italic; 13 | } 14 | 15 | .go-plus-ansi-underline { 16 | text-decoration: underline; 17 | } 18 | 19 | .go-plus-ansi-inverse { 20 | color: @input-background-color; 21 | background-color: @text-color; 22 | } 23 | 24 | .go-plus-ansi-hidden { 25 | color: transparent; 26 | } 27 | 28 | .go-plus-ansi-strikethrough { 29 | text-decoration: line-through; 30 | } 31 | 32 | // Text Colors 33 | // ----------- 34 | 35 | .go-plus-ansi-black { 36 | color: black; 37 | } 38 | 39 | .go-plus-ansi-green { 40 | color: green; 41 | } 42 | 43 | .go-plus-ansi-yellow { 44 | color: yellow; 45 | } 46 | 47 | .go-plus-ansi-blue { 48 | color: blue; 49 | } 50 | 51 | .go-plus-ansi-magenta { 52 | color: magenta; 53 | } 54 | 55 | .go-plus-ansi-cyan { 56 | color: cyan; 57 | } 58 | 59 | .go-plus-ansi-white { 60 | // the 'white' color code from ansi-style-parser is \e[37m (light gray) 61 | color: lighten(gray, 20%); 62 | } 63 | 64 | .go-plus-ansi-gray { 65 | color: gray; 66 | } 67 | 68 | .go-plus-ansi-red { 69 | color: red; 70 | } 71 | 72 | .go-plus-ansi-lightRed { 73 | color: lighten(red, 20%); 74 | } 75 | 76 | .go-plus-ansi-lightGreen { 77 | color: lighten(green, 20%); 78 | } 79 | 80 | .go-plus-ansi-lightYellow { 81 | color: lighten(yellow, 20%); 82 | } 83 | 84 | .go-plus-ansi-lightBlue { 85 | color: lighten(blue, 20%); 86 | } 87 | 88 | .go-plus-ansi-lightMagenta { 89 | color: lighten(magenta, 20%); 90 | } 91 | 92 | .go-plus-ansi-lightCyan { 93 | color: lighten(cyan, 20%); 94 | } 95 | 96 | .go-plus-ansi-lightWhite { 97 | // default white: \e[97m 98 | color: white; 99 | } 100 | 101 | // Background Colors 102 | // ----------------- 103 | 104 | .go-plus-ansi-bgBlack { 105 | background-color: black; 106 | } 107 | 108 | .go-plus-ansi-bgGreen { 109 | background-color: green; 110 | } 111 | 112 | .go-plus-ansi-bgYellow { 113 | background-color: yellow; 114 | } 115 | 116 | .go-plus-ansi-bgBlue { 117 | background-color: blue; 118 | } 119 | 120 | .go-plus-ansi-bgMagenta { 121 | background-color: magenta; 122 | } 123 | 124 | .go-plus-ansi-bgCyan { 125 | background-color: cyan; 126 | } 127 | 128 | .go-plus-ansi-bgWhite { 129 | // the 'white' color code from ansi-style-parser is \e[37m (light gray) 130 | background-color: lighten(gray, 20%); 131 | } 132 | 133 | .go-plus-ansi-bgGray { 134 | background-color: gray; 135 | } 136 | 137 | .go-plus-ansi-bgLightRed { 138 | background-color: lighten(red, 20%); 139 | } 140 | 141 | .go-plus-ansi-bgLightGreen { 142 | background-color: lighten(green, 20%); 143 | } 144 | 145 | .go-plus-ansi-bgLightYellow { 146 | background-color: lighten(yellow, 20%); 147 | } 148 | 149 | .go-plus-ansi-bgLightBlue { 150 | background-color: lighten(blue, 20%); 151 | } 152 | 153 | .go-plus-ansi-bgLightMagenta { 154 | background-color: lighten(magenta, 20%); 155 | } 156 | 157 | .go-plus-ansi-bgLightCyan { 158 | background-color: lighten(cyan, 20%); 159 | } 160 | 161 | .go-plus-ansi-bgLightWhite { 162 | // default white: \e[97m 163 | background-color: white; 164 | } 165 | -------------------------------------------------------------------------------- /styles/etch-octicon.less: -------------------------------------------------------------------------------- 1 | 2 | // Etch Octicon 3 | 4 | .etch-octicon, 5 | .mega-etch-octicon { 6 | display: inline-block; 7 | line-height: inherit; 8 | margin-right: .2em; 9 | vertical-align: middle; 10 | fill: currentColor; 11 | 12 | svg { 13 | width: auto; 14 | height: 1em; 15 | vertical-align: text-top; 16 | } 17 | 18 | &.no-margin { 19 | margin-right: 0; 20 | } 21 | } 22 | 23 | // Default size 24 | .etch-octicon { 25 | font-size: 16px; 26 | } 27 | 28 | // Double size 29 | .mega-etch-octicon { 30 | font-size: 32px; 31 | } 32 | 33 | // Automatically change size based on the font-size 34 | .etch-octicon.auto-size { 35 | font-size: 1em; 36 | } 37 | -------------------------------------------------------------------------------- /styles/go-plus-accordion.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | // 4 | // Accordion Menu 5 | // -------------------------------- 6 | 7 | .go-plus-accordion { 8 | padding: 0; 9 | margin: 0; 10 | } 11 | 12 | .go-plus-accordion-header { 13 | padding: @component-padding/4 @component-padding/2; 14 | background-color: @background-color-highlight; 15 | cursor: default; 16 | -webkit-user-select: none; 17 | } 18 | 19 | .go-plus-accordion-content { 20 | padding: @component-padding / 2; 21 | } 22 | 23 | 24 | // States ------------------------------ 25 | 26 | .go-plus-accordion-item[open] > .go-plus-accordion-header { 27 | border-bottom: 1px solid @base-border-color; 28 | } 29 | 30 | 31 | // Special ------------------------------ 32 | 33 | // Only add a border to the following item (not first-child) 34 | .go-plus-accordion-item + .go-plus-accordion-item .go-plus-accordion-header { 35 | border-top: 1px solid @base-border-color; 36 | } 37 | 38 | // Add an "Empty" label for empty items 39 | .go-plus-accordion-content:empty::before { 40 | content: "Empty"; 41 | display: block; 42 | font-style: italic; 43 | text-align: center; 44 | color: @text-color-subtle; 45 | } 46 | -------------------------------------------------------------------------------- /styles/go-plus-guru.atom-text-editor.less: -------------------------------------------------------------------------------- 1 | @import "syntax-variables"; 2 | 3 | .highlight.sameid .region { 4 | background-color: @syntax-selection-color; 5 | } 6 | -------------------------------------------------------------------------------- /styles/go-plus-output.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | .go-plus-output-panel { 4 | overflow: auto; 5 | width: 100%; 6 | } 7 | -------------------------------------------------------------------------------- /styles/go-plus-panel.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | // Panel -------------------- 4 | 5 | .go-plus-panel.is-narrow { 6 | .panel-nav-label { 7 | display: none; 8 | } 9 | } 10 | 11 | .go-plus-panel { 12 | position: relative; 13 | display: flex; 14 | flex-direction: row; 15 | -webkit-user-select: none; 16 | 17 | .panel-heading { 18 | border: none; 19 | text-align: center; 20 | flex-direction: column; 21 | flex: 0 0 auto; 22 | display: flex; 23 | align-items: left; 24 | padding: 0; 25 | cursor: default; 26 | background-color: @tool-panel-background-color; 27 | } 28 | 29 | .go-plus-panel-pre { 30 | white-space: pre; 31 | } 32 | 33 | .panel-nav { 34 | border: none; 35 | flex: 1 1 0; 36 | padding: 0; 37 | margin-bottom: -1px; // cover border 38 | border-right: 1px solid @base-border-color; 39 | white-space: nowrap; 40 | overflow-x: auto; 41 | 42 | &::-webkit-scrollbar { 43 | display: none; 44 | } 45 | 46 | &-label { 47 | display: inline; 48 | } 49 | } 50 | 51 | .panel-nav-item { 52 | display: block; 53 | color: @text-color-subtle; 54 | border: 1px solid transparent; 55 | border-width: 1px 0; 56 | line-height: 1; 57 | text-align: left; 58 | padding: @component-padding/1.5 @component-padding; 59 | 60 | &:hover { 61 | color: @text-color-highlight; 62 | } 63 | &:active, 64 | &.is-selected { 65 | color: @text-color-selected; 66 | border-color: @panel-heading-border-color; 67 | background-color: @base-background-color; 68 | } 69 | &:first-child { 70 | border-top: 0; 71 | } 72 | } 73 | 74 | .panel-icon-button { 75 | color: inherit; 76 | padding: 0; 77 | border: none; 78 | background-color: transparent; 79 | cursor: default; 80 | & + .panel-icon-button { 81 | margin-left: @component-padding; 82 | } 83 | 84 | &:hover { 85 | color: @text-color-highlight; 86 | } 87 | &:active { 88 | color: @text-color-subtle; 89 | } 90 | 91 | &:before { 92 | margin-right: 0; 93 | } 94 | } 95 | 96 | .panel-body { 97 | position: relative; 98 | overflow: auto; 99 | display: flex; 100 | flex: 1; 101 | min-height: 0; 102 | 103 | background-color: @base-background-color; 104 | -webkit-user-select: text; 105 | } 106 | 107 | .go-plus-empty-tab { 108 | display: flex; 109 | margin: auto; 110 | font-size: 1.5em; 111 | font-weight: bold; 112 | align-items: center; 113 | justify-content: center; 114 | text-align: center; 115 | } 116 | 117 | .go-plus-panel-body { 118 | font-family: var(--editor-font-family); 119 | font-size: var(--editor-font-size); 120 | line-height: var(--editor-line-height); 121 | } 122 | 123 | .padded-content { 124 | padding: 10px; 125 | } 126 | 127 | .content { 128 | padding: 0px; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /styles/go-plus-status-bar.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | // Status Bar -------------------- 4 | 5 | .go-plus-status-bar { 6 | color: @text-color-success; 7 | &:hover { 8 | color: lighten(@text-color-success, 15%); 9 | } 10 | 11 | &.go-plus-status-unknown { 12 | color: @text-color-info; 13 | } 14 | 15 | &.go-plus-status-pending { 16 | color: @text-color-warning; 17 | } 18 | 19 | &.go-plus-status-success { 20 | color: @text-color-success; 21 | } 22 | 23 | &.go-plus-status-fail { 24 | color: @text-color-error; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /styles/go-plus-table.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | // 4 | // Table 5 | // -------------------------------- 6 | 7 | .go-plus-table { 8 | width: 100%; 9 | border-collapse: collapse; 10 | border-spacing: 0; 11 | } 12 | 13 | .go-plus-table-header { 14 | border-bottom: 1px solid @base-border-color; 15 | } 16 | 17 | .go-plus-table-row:hover { 18 | background-color: @background-color-selected; 19 | } 20 | 21 | .go-plus-table-cell { 22 | padding: @component-padding/2 @component-padding; 23 | } 24 | 25 | .go-plus-left-pad { 26 | padding-left: 24px; 27 | } 28 | -------------------------------------------------------------------------------- /styles/go-plus-test.atom-text-editor.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | .highlight.uncovered .region { 4 | background-color: @background-color-error; 5 | opacity: 0.2; 6 | } 7 | 8 | .highlight.covered .region { 9 | background-color: @background-color-success; 10 | opacity: 0.2; 11 | } 12 | 13 | .line-number.uncovered{ 14 | color: @text-color-selected !important; 15 | background-color: @background-color-error; 16 | } 17 | 18 | .line-number.covered{ 19 | color: @text-color-selected !important; 20 | background-color: @background-color-success; 21 | } 22 | -------------------------------------------------------------------------------- /styles/godoc.less: -------------------------------------------------------------------------------- 1 | .godoc-panel { 2 | white-space: pre-wrap; 3 | } 4 | -------------------------------------------------------------------------------- /styles/gomodifytags.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | @margin: .5 * @component-padding; 4 | 5 | .gomodifytags { 6 | font-size: @font-size; 7 | 8 | label { 9 | font-size: 1.25 * @font-size; 10 | padding-right: @component-padding; 11 | } 12 | 13 | .go-tags-flex-container { 14 | display: flex; 15 | flex-direction: row; 16 | flex-wrap: nowrap; 17 | justify-content: flex-start; 18 | align-content: stretch; 19 | align-items: center; 20 | vertical-align: middle; 21 | 22 | button { 23 | order: 0; 24 | flex: 0 1 auto; 25 | align-self: auto; 26 | margin-bottom: 10px; 27 | } 28 | 29 | div { 30 | margin-left: 2px; 31 | margin-right: 2px; 32 | order: 0; 33 | flex: 2 1 auto; 34 | align-self: auto; 35 | } 36 | } 37 | 38 | .go-tags-list { 39 | font-size: 16px; 40 | 41 | h3 { 42 | font-size: 115%; 43 | } 44 | } 45 | 46 | .btn { 47 | margin-right: @margin; 48 | margin-left: @margin; 49 | } 50 | 51 | .case-radio-buttons { 52 | .input-label { 53 | padding-right: @component-padding; 54 | padding-bottom: @component-padding; 55 | } 56 | } 57 | 58 | atom-text-editor[mini] { 59 | &.invalid-input-value { 60 | border-color: @text-color-error; 61 | box-shadow: 0 0 0 1px @text-color-error; 62 | background-color: mix(@text-color-error, @input-background-color, 10%); 63 | 64 | .selection .region { 65 | background-color: mix(@text-color-error, @input-background-color, 50%); 66 | } 67 | 68 | .cursor { 69 | border-color: @text-color-error; 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /styles/overrides.less: -------------------------------------------------------------------------------- 1 | // This overrides themes and other packages 2 | 3 | // atom-material-ui 4 | 5 | .theme-atom-material-ui .go-plus-panel { 6 | 7 | .panel-heading { 8 | font-size: 0.8rem; 9 | font-weight: normal; 10 | margin-bottom: 0; 11 | } 12 | 13 | .panel-icon-button { 14 | width: 2.25rem; 15 | text-align: center; 16 | .etch-octicon { 17 | margin-right: 0; 18 | } 19 | } 20 | 21 | .panel-nav-item { 22 | border: none; 23 | border-right: 0.125rem solid transparent; 24 | &.is-selected { 25 | border-color: currentColor; 26 | } 27 | } 28 | 29 | &.is-vertical { 30 | .panel-nav-item { 31 | border-bottom: none; 32 | border-right: 0.125rem solid transparent; 33 | &.is-selected { 34 | border-color: currentColor; 35 | } 36 | } 37 | } 38 | 39 | } 40 | --------------------------------------------------------------------------------