├── .gitignore ├── .travis.yml ├── .vscode ├── launch.json └── settings.json ├── .vscodeignore ├── LICENSE ├── README.md ├── fileicons └── vs_goplus-icon-theme.json ├── images ├── example1.gif ├── example2.gif ├── example3.gif └── icon.png ├── language-configuration.json ├── package-lock.json ├── package.json ├── snippets ├── goplus.json └── spx.json ├── src ├── avlTree.ts ├── goDeclaration.ts ├── goEnv.ts ├── goModules.ts ├── goOutline.ts ├── goPath.ts ├── goTools.ts ├── gopExtraInfo.ts ├── gopFormat.ts ├── gopImport.ts ├── gopInstallTools.ts ├── gopMain.ts ├── gopMode.ts ├── gopPackages.ts ├── gopStatus.ts ├── gopSuggest.ts ├── stateUtils.ts └── util.ts ├── syntaxes ├── gop.tmLanguage.json └── spx.tmLanguage.json ├── third_party ├── README.md └── tree-kill │ ├── LICENSE │ ├── README.md │ ├── cli.js │ ├── index.d.ts │ ├── index.js │ └── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Output of nodejs and typescript 15 | out 16 | node_modules 17 | dist 18 | 19 | # Other 20 | .vscode-test 21 | .DS_Store 22 | 23 | # Dependency directories (remove the comment below to include it) 24 | # vendor/ 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8.11.1" 5 | 6 | install: 7 | - npm install -g typescript 8 | - npm install 9 | 10 | script: 11 | - tsc -p ./ -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Run Extension", 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "runtimeExecutable": "${execPath}", 9 | "args": [ 10 | "--extensionDevelopmentPath=${workspaceFolder}" 11 | ], 12 | "outFiles": [ 13 | "${workspaceFolder}/out/**/*.js" 14 | ], 15 | "preLaunchTask": "${defaultBuildTask}" 16 | }, 17 | { 18 | "name": "Extension Tests", 19 | "type": "extensionHost", 20 | "request": "launch", 21 | "runtimeExecutable": "${execPath}", 22 | "args": [ 23 | "--extensionDevelopmentPath=${workspaceFolder}", 24 | "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" 25 | ], 26 | "outFiles": [ 27 | "${workspaceFolder}/out/test/**/*.js" 28 | ], 29 | "preLaunchTask": "${defaultBuildTask}" 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | src/ 2 | test/ 3 | third_party/ 4 | typings/**/* 5 | .vscode/**/* 6 | tsconfig.json 7 | .gitignore 8 | **/*.map 9 | **/tslint.json 10 | build/ 11 | docs/ 12 | *.md.nightly 13 | .github/**/* 14 | .prettierrc.json 15 | out/ 16 | .vscode-test/** 17 | SECURITY.md 18 | node_modules/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](./images/icon.png) 2 | 3 | [![Build Status](https://travis-ci.org/gopcode/vscode-goplus.svg?branch=master)](https://travis-ci.org/github/gopcode/vscode-goplus) 4 | [![License](https://img.shields.io/badge/license-Apache-blue.svg)](https://raw.githubusercontent.com/gopcode/vscode-goplus/master/LICENSE) 5 | 6 | # GoPlus (Go+) Plugin for vscode 7 | 8 | This extension provides rich language support for the Go+ programming language in VS Code. 9 | 10 | ## Supported Vscode versions 11 | 12 | - 1.46.0+ 13 | 14 | ## Example 15 | 16 | ![example](https://raw.githubusercontent.com/gopcode/vscode-goplus/master/images/example3.gif) 17 | 18 | ## Install Go 19 | Before you start coding, make sure that you have already installed Go, as explained in the Go installation guide. 20 | 21 | If you are unsure whether you have installed Go, open the Command Palette in VS Code (Ctrl+Shift+P) and run the goplus 22 | 23 | ## Feature Support 24 | 25 | - [x] Syntax Highlight 26 | - [x] Auto Snippet 27 | - [x] Format Source Code 28 | - [ ] Semantic Highlight 29 | - [x] Auto Completion 30 | - [x] Hover function Display 31 | - [X] Auto import 32 | - [ ] Code Diagnostics 33 | - [ ] Help With Function and Method Signatures 34 | - [x] Show Definitions of a Symbol 35 | - [ ] Find All References to a Symbol 36 | - [ ] Highlight All Occurrences of a Symbol in a Document 37 | -------------------------------------------------------------------------------- /fileicons/vs_goplus-icon-theme.json: -------------------------------------------------------------------------------- 1 | { 2 | "iconDefinitions": { 3 | "_goplus_": { 4 | "iconPath": "../images/icon.png" 5 | } 6 | }, 7 | 8 | "fileExtensions": { 9 | "gop": "_goplus_" 10 | }, 11 | "languageIds": { 12 | "gop": "_goplus_" 13 | } 14 | } -------------------------------------------------------------------------------- /images/example1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gopcode/vscode-goplus/53a954e267c12c8bad2169be281e923bb085afbf/images/example1.gif -------------------------------------------------------------------------------- /images/example2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gopcode/vscode-goplus/53a954e267c12c8bad2169be281e923bb085afbf/images/example2.gif -------------------------------------------------------------------------------- /images/example3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gopcode/vscode-goplus/53a954e267c12c8bad2169be281e923bb085afbf/images/example3.gif -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gopcode/vscode-goplus/53a954e267c12c8bad2169be281e923bb085afbf/images/icon.png -------------------------------------------------------------------------------- /language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | // symbol used for single line comment. Remove this entry if your language does not support line comments 4 | "lineComment": "//", 5 | // symbols used for start and end a block comment. Remove this entry if your language does not support block comments 6 | "blockComment": [ "/*", "*/" ] 7 | }, 8 | // symbols used as brackets 9 | "brackets": [ 10 | ["{", "}"], 11 | ["[", "]"], 12 | ["(", ")"] 13 | ], 14 | // symbols that are auto closed when typing 15 | "autoClosingPairs": [ 16 | ["{", "}"], 17 | ["[", "]"], 18 | ["(", ")"], 19 | ["\"", "\""], 20 | ["'", "'"] 21 | ], 22 | // symbols that can be used to surround a selection 23 | "surroundingPairs": [ 24 | ["{", "}"], 25 | ["[", "]"], 26 | ["(", ")"], 27 | ["\"", "\""], 28 | ["'", "'"] 29 | ] 30 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gop", 3 | "displayName": "goplus", 4 | "description": "GoPlus", 5 | "version": "0.7.8", 6 | "engines": { 7 | "vscode": "^1.46.0" 8 | }, 9 | "publisher": "goplus", 10 | "activationEvents": [ 11 | "onLanguage:gop", 12 | "onLanguage:spx" 13 | ], 14 | "license": "Apache-2.0", 15 | "icon": "images/icon.png", 16 | "main": "./dist/gopMain.js", 17 | "categories": [ 18 | "Programming Languages", 19 | "Themes", 20 | "Formatters" 21 | ], 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/gopcode/vscode-goplus" 25 | }, 26 | "scripts": { 27 | "vscode:prepublish": "npm run compile", 28 | "bundle": "esbuild src/gopMain.ts --bundle --outdir=dist --external:vscode --format=cjs --platform=node", 29 | "bundle-dev": "npm run bundle -- --sourcemap", 30 | "bundle-watch": "npm run bundle -- --sourcemap --watch", 31 | "compile": "npm run bundle", 32 | "watch": "tsc -watch -p ./" 33 | }, 34 | "contributes": { 35 | "languages": [ 36 | { 37 | "id": "gop", 38 | "aliases": [ 39 | "goplus", 40 | "gop" 41 | ], 42 | "extensions": [ 43 | ".gop" 44 | ], 45 | "configuration": "./language-configuration.json" 46 | }, 47 | { 48 | "id": "spx", 49 | "aliases": [ 50 | "goplus", 51 | "gop", 52 | "spx" 53 | ], 54 | "extensions": [ 55 | ".gmx", 56 | ".spx" 57 | ], 58 | "configuration": "./language-configuration.json" 59 | } 60 | ], 61 | "grammars": [ 62 | { 63 | "language": "gop", 64 | "scopeName": "source.gop", 65 | "path": "./syntaxes/gop.tmLanguage.json" 66 | }, 67 | { 68 | "language": "spx", 69 | "scopeName": "source.spx", 70 | "path": "./syntaxes/spx.tmLanguage.json" 71 | } 72 | ], 73 | "snippets": [ 74 | { 75 | "language": "gop", 76 | "path": "./snippets/goplus.json" 77 | }, 78 | { 79 | "language": "spx", 80 | "path": "./snippets/spx.json" 81 | } 82 | ], 83 | "configurationDefaults": { 84 | "[gop]": { 85 | "editor.insertSpaces": false, 86 | "editor.formatOnSave": true, 87 | "editor.codeActionsOnSave": { 88 | "source.organizeImports": true 89 | } 90 | }, 91 | "[spx]": { 92 | "editor.insertSpaces": false, 93 | "editor.formatOnSave": true, 94 | "editor.codeActionsOnSave": { 95 | "source.organizeImports": true 96 | } 97 | } 98 | }, 99 | "configuration": { 100 | "type": "object", 101 | "title": "GoPlus", 102 | "properties": { 103 | "goplus.formatTool": { 104 | "type": "string", 105 | "default": "gopfmt", 106 | "description": "Pick 'gopfmt' to run on format.", 107 | "scope": "resource", 108 | "enum": [ 109 | "gopfmt" 110 | ] 111 | }, 112 | "goplus.formatFlags": { 113 | "type": "array", 114 | "items": { 115 | "type": "string" 116 | }, 117 | "default": [], 118 | "description": "Flags to pass to format tool (e.g. [\"-w\"])", 119 | "scope": "resource" 120 | }, 121 | "goplus.toolsEnvVars": { 122 | "type": "object", 123 | "default": {}, 124 | "description": "Environment variables that will passed to the processes that run the Go tools (e.g. CGO_CFLAGS)", 125 | "scope": "resource" 126 | }, 127 | "goplus.alternateTools": { 128 | "type": "object", 129 | "default": {}, 130 | "description": "Alternate tools or alternate paths for the same tools used by the Go extension. Provide either absolute path or the name of the binary in GOPATH/bin, GOROOT/bin or PATH. Useful when you want to use wrapper script for the Go tools or versioned tools from https://gopkg.in.", 131 | "scope": "resource", 132 | "properties": { 133 | "gopfmt": { 134 | "type": "string", 135 | "default": "gopfmt", 136 | "description": "Alternate tool to use instead of the gopfmt binary or alternate path to use for the gopfmt binary." 137 | } 138 | } 139 | }, 140 | "goplus.docsTool": { 141 | "type": "string", 142 | "default": "godoc", 143 | "description": "Pick 'godoc' or 'gogetdoc' to get documentation. Not applicable when using the language server.", 144 | "scope": "resource", 145 | "enum": [ 146 | "godoc" 147 | ] 148 | }, 149 | "goplus.gocodeFlags": { 150 | "type": "array", 151 | "items": { 152 | "type": "string" 153 | }, 154 | "default": [ 155 | "-builtin", 156 | "-ignore-case" 157 | ], 158 | "description": "Additional flags to pass to gocode. Not applicable when using the language server.", 159 | "scope": "resource" 160 | } 161 | } 162 | }, 163 | "commands": [ 164 | { 165 | "command": "goplus.import.add", 166 | "title": "GoPlus: Add Import", 167 | "description": "Add an import declaration" 168 | } 169 | ], 170 | "iconThemes": [ 171 | { 172 | "id": "vs-goplus", 173 | "label": "GoPlus (Visual Studio Code)", 174 | "path": "./fileicons/vs_goplus-icon-theme.json" 175 | } 176 | ] 177 | }, 178 | "dependencies": { 179 | "vscode-debugadapter": "^1.40.0", 180 | "vscode-debugprotocol": "^1.40.0", 181 | "vscode-languageclient": "6.1.0", 182 | "moment": "^2.24.0", 183 | "semver": "^7.3.2", 184 | "lodash": "^4.17.21", 185 | "ansi-regex": ">=5.0.1", 186 | "tree-kill": "file:third_party/tree-kill" 187 | }, 188 | "devDependencies": { 189 | "@types/glob": "^7.1.1", 190 | "@types/mocha": "^7.0.2", 191 | "@types/node": "^13.11.0", 192 | "@types/semver": "^7.1.0", 193 | "@types/vscode": "^1.46.0", 194 | "@typescript-eslint/eslint-plugin": "^3.0.0", 195 | "@typescript-eslint/parser": "^3.0.0", 196 | "esbuild": "^0.13.4", 197 | "eslint": "^7.28.0", 198 | "glob": "^7.1.6", 199 | "mocha": "^9.1.2", 200 | "typescript": "^3.8.3", 201 | "vscode-test": "^1.3.0" 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /snippets/goplus.json: -------------------------------------------------------------------------------- 1 | { 2 | ".source.go": { 3 | "single import": { 4 | "prefix": "im", 5 | "body": "import \"${1:package}\"", 6 | "description": "Snippet for import statement" 7 | }, 8 | "multiple imports": { 9 | "prefix": "ims", 10 | "body": "import (\n\t\"${1:package}\"\n)", 11 | "description": "Snippet for a import block" 12 | }, 13 | "single constant": { 14 | "prefix": "co", 15 | "body": "const ${1:name} = ${2:value}", 16 | "description": "Snippet for a constant" 17 | }, 18 | "multiple constants": { 19 | "prefix": "cos", 20 | "body": "const (\n\t${1:name} = ${2:value}\n)", 21 | "description": "Snippet for a constant block" 22 | }, 23 | "type interface declaration": { 24 | "prefix": "tyi", 25 | "body": "type ${1:name} interface {\n\t$0\n}", 26 | "description": "Snippet for a type interface" 27 | }, 28 | "type struct declaration": { 29 | "prefix": "tys", 30 | "body": "type ${1:name} struct {\n\t$0\n}", 31 | "description": "Snippet for a struct declaration" 32 | }, 33 | "package main and main function": { 34 | "prefix": "pkgm", 35 | "body": "package main\n\nfunc main() {\n\t$0\n}", 36 | "description": "Snippet for main package & function" 37 | }, 38 | "function declaration": { 39 | "prefix": "func", 40 | "body": "func $1($2) $3 {\n\t$0\n}", 41 | "description": "Snippet for function declaration" 42 | }, 43 | "variable declaration": { 44 | "prefix": "var", 45 | "body": "var ${1:name} ${2:type}", 46 | "description": "Snippet for a variable" 47 | }, 48 | "switch statement": { 49 | "prefix": "switch", 50 | "body": "switch ${1:expression} {\ncase ${2:condition}:\n\t$0\n}", 51 | "description": "Snippet for switch statement" 52 | }, 53 | "select statement": { 54 | "prefix": "sel", 55 | "body": "select {\ncase ${1:condition}:\n\t$0\n}", 56 | "description": "Snippet for select statement" 57 | }, 58 | "case clause": { 59 | "prefix": "cs", 60 | "body": "case ${1:condition}:$0", 61 | "description": "Snippet for case clause" 62 | }, 63 | "for statement": { 64 | "prefix": "for", 65 | "body": "for ${1:i} := 0; $1 < ${2:count}; $1${3:++} {\n\t$0\n}", 66 | "description": "Snippet for a for loop" 67 | }, 68 | "for range statement": { 69 | "prefix": "forr", 70 | "body": "for ${1:_, }${2:var} := range ${3:var} {\n\t$0\n}", 71 | "description": "Snippet for a for range loop" 72 | }, 73 | "for arrow statement": { 74 | "prefix": "for-", 75 | "body": "for ${1:i}, ${2:j} <- ${3:k} {\n\t$0\n}", 76 | "description": "Snippet for a for arrow loop" 77 | }, 78 | "channel declaration": { 79 | "prefix": "ch", 80 | "body": "chan ${1:type}", 81 | "description": "Snippet for a channel" 82 | }, 83 | "map declaration": { 84 | "prefix": "map", 85 | "body": "map[${1:type}]${2:type}", 86 | "description": "Snippet for a map" 87 | }, 88 | "empty interface": { 89 | "prefix": "in", 90 | "body": "interface{}", 91 | "description": "Snippet for empty interface" 92 | }, 93 | "if statement": { 94 | "prefix": "if", 95 | "body": "if ${1:condition} {\n\t$0\n}", 96 | "description": "Snippet for if statement" 97 | }, 98 | "else branch": { 99 | "prefix": "el", 100 | "body": "else {\n\t$0\n}", 101 | "description": "Snippet for else branch" 102 | }, 103 | "if else statement": { 104 | "prefix": "ie", 105 | "body": "if ${1:condition} {\n\t$2\n} else {\n\t$0\n}", 106 | "description": "Snippet for if else" 107 | }, 108 | "if err != nil": { 109 | "prefix": "iferr", 110 | "body": "if err != nil {\n\t${1:return ${2:nil, }${3:err}}\n}", 111 | "description": "Snippet for if err != nil" 112 | }, 113 | "println": { 114 | "prefix": "pr", 115 | "body": "println(\"$1\")", 116 | "description": "Snippet for println()" 117 | }, 118 | "fmt.Println": { 119 | "prefix": "fp", 120 | "body": "fmt.Println(\"$1\")", 121 | "description": "Snippet for fmt.Println()" 122 | }, 123 | "fmt.Printf": { 124 | "prefix": "ff", 125 | "body": "fmt.Printf(\"$1\", ${2:var})", 126 | "description": "Snippet for fmt.Printf()" 127 | }, 128 | "log.Println": { 129 | "prefix": "lp", 130 | "body": "log.Println(\"$1\")", 131 | "description": "Snippet for log.Println()" 132 | }, 133 | "log.Printf": { 134 | "prefix": "lf", 135 | "body": "log.Printf(\"$1\", ${2:var})", 136 | "description": "Snippet for log.Printf()" 137 | }, 138 | "log variable content": { 139 | "prefix": "lv", 140 | "body": "log.Printf(\"${1:var}: %#+v\\\\n\", ${1:var})", 141 | "description": "Snippet for log.Printf() with variable content" 142 | }, 143 | "t.Log": { 144 | "prefix": "tl", 145 | "body": "t.Log(\"$1\")", 146 | "description": "Snippet for t.Log()" 147 | }, 148 | "t.Logf": { 149 | "prefix": "tlf", 150 | "body": "t.Logf(\"$1\", ${2:var})", 151 | "description": "Snippet for t.Logf()" 152 | }, 153 | "t.Logf variable content": { 154 | "prefix": "tlv", 155 | "body": "t.Logf(\"${1:var}: %#+v\\\\n\", ${1:var})", 156 | "description": "Snippet for t.Logf() with variable content" 157 | }, 158 | "make(...)": { 159 | "prefix": "make", 160 | "body": "make(${1:type}, ${2:0})", 161 | "description": "Snippet for make statement" 162 | }, 163 | "new(...)": { 164 | "prefix": "new", 165 | "body": "new(${1:type})", 166 | "description": "Snippet for new statement" 167 | }, 168 | "panic(...)": { 169 | "prefix": "pn", 170 | "body": "panic(\"$0\")", 171 | "description": "Snippet for panic" 172 | }, 173 | "http ResponseWriter *Request": { 174 | "prefix": "wr", 175 | "body": "${1:w} http.ResponseWriter, ${2:r} *http.Request", 176 | "description": "Snippet for http Response" 177 | }, 178 | "http.HandleFunc": { 179 | "prefix": "hf", 180 | "body": "${1:http}.HandleFunc(\"${2:/}\", ${3:handler})", 181 | "description": "Snippet for http.HandleFunc()" 182 | }, 183 | "http handler declaration": { 184 | "prefix": "hand", 185 | "body": "func $1(${2:w} http.ResponseWriter, ${3:r} *http.Request) {\n\t$0\n}", 186 | "description": "Snippet for http handler declaration" 187 | }, 188 | "http.Redirect": { 189 | "prefix": "rd", 190 | "body": "http.Redirect(${1:w}, ${2:r}, \"${3:/}\", ${4:http.StatusFound})", 191 | "description": "Snippet for http.Redirect()" 192 | }, 193 | "http.Error": { 194 | "prefix": "herr", 195 | "body": "http.Error(${1:w}, ${2:err}.Error(), ${3:http.StatusInternalServerError})", 196 | "description": "Snippet for http.Error()" 197 | }, 198 | "http.ListenAndServe": { 199 | "prefix": "las", 200 | "body": "http.ListenAndServe(\"${1::8080}\", ${2:nil})", 201 | "description": "Snippet for http.ListenAndServe" 202 | }, 203 | "http.Serve": { 204 | "prefix": "sv", 205 | "body": "http.Serve(\"${1::8080}\", ${2:nil})", 206 | "description": "Snippet for http.Serve" 207 | }, 208 | "goroutine anonymous function": { 209 | "prefix": "go", 210 | "body": "go func($1) {\n\t$0\n}($2)", 211 | "description": "Snippet for anonymous goroutine declaration" 212 | }, 213 | "goroutine function": { 214 | "prefix": "gf", 215 | "body": "go ${1:func}($0)", 216 | "description": "Snippet for goroutine declaration" 217 | }, 218 | "defer statement": { 219 | "prefix": "df", 220 | "body": "defer ${1:func}($0)", 221 | "description": "Snippet for defer statement" 222 | }, 223 | "test function": { 224 | "prefix": "tf", 225 | "body": "func Test$1(t *testing.T) {\n\t$0\n}", 226 | "description": "Snippet for Test function" 227 | }, 228 | "benchmark function": { 229 | "prefix": "bf", 230 | "body": "func Benchmark$1(b *testing.B) {\n\tfor ${2:i} := 0; ${2:i} < b.N; ${2:i}++ {\n\t\t$0\n\t}\n}", 231 | "description": "Snippet for Benchmark function" 232 | }, 233 | "example function": { 234 | "prefix": "ef", 235 | "body": "func Example$1() {\n\t$2\n\t//Output:\n\t$3\n}", 236 | "description": "Snippet for Example function" 237 | }, 238 | "table driven test": { 239 | "prefix": "tdt", 240 | "body": "func Test$1(t *testing.T) {\n\ttestCases := []struct {\n\t\tdesc\tstring\n\t\t$2\n\t}{\n\t\t{\n\t\t\tdesc: \"$3\",\n\t\t\t$4\n\t\t},\n\t}\n\tfor _, tC := range testCases {\n\t\tt.Run(tC.desc, func(t *testing.T) {\n\t\t\t$0\n\t\t})\n\t}\n}", 241 | "description": "Snippet for table driven test" 242 | }, 243 | "init function": { 244 | "prefix": "finit", 245 | "body": "func init() {\n\t$1\n}", 246 | "description": "Snippet for init function" 247 | }, 248 | "main function": { 249 | "prefix": "fmain", 250 | "body": "func main() {\n\t$1\n}", 251 | "description": "Snippet for main function" 252 | }, 253 | "method declaration": { 254 | "prefix": "meth", 255 | "body": "func (${1:receiver} ${2:type}) ${3:method}($4) $5 {\n\t$0\n}", 256 | "description": "Snippet for method declaration" 257 | }, 258 | "hello world web app": { 259 | "prefix": "helloweb", 260 | "body": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"time\"\n)\n\nfunc greet(w http.ResponseWriter, r *http.Request) {\n\tfmt.Fprintf(w, \"Hello World! %s\", time.Now())\n}\n\nfunc main() {\n\thttp.HandleFunc(\"/\", greet)\n\thttp.ListenAndServe(\":8080\", nil)\n}", 261 | "description": "Snippet for sample hello world webapp" 262 | }, 263 | "sort implementation": { 264 | "prefix": "sort", 265 | "body": "type ${1:SortBy} []${2:Type}\n\nfunc (a $1) Len() int { return len(a) }\nfunc (a $1) Swap(i, j int) { a[i], a[j] = a[j], a[i] }\nfunc (a $1) Less(i, j int) bool { ${3:return a[i] < a[j]} }", 266 | "description": "Snippet for a custom sort.Sort interface implementation, for a given slice type." 267 | } 268 | } 269 | } -------------------------------------------------------------------------------- /snippets/spx.json: -------------------------------------------------------------------------------- 1 | { 2 | ".source.spx": { 3 | "single import": { 4 | "prefix": "im", 5 | "body": "import \"${1:package}\"", 6 | "description": "import statement" 7 | }, 8 | "multiple imports": { 9 | "prefix": "ims", 10 | "body": "import (\n\t\"${1:package}\"\n)", 11 | "description": "a import block" 12 | }, 13 | "single constant": { 14 | "prefix": "co", 15 | "body": "const ${1:name} = ${2:value}", 16 | "description": "a constant" 17 | }, 18 | "multiple constants": { 19 | "prefix": "cos", 20 | "body": "const (\n\t${1:name} = ${2:value}\n)", 21 | "description": "a constant block" 22 | }, 23 | "type interface declaration": { 24 | "prefix": "tyi", 25 | "body": "type ${1:name} interface {\n\t$0\n}", 26 | "description": "a type interface" 27 | }, 28 | "type struct declaration": { 29 | "prefix": "tys", 30 | "body": "type ${1:name} struct {\n\t$0\n}", 31 | "description": "a struct declaration" 32 | }, 33 | "package main and main function": { 34 | "prefix": "pkgm", 35 | "body": "package main\n\nfunc main() {\n\t$0\n}", 36 | "description": "main package & function" 37 | }, 38 | "function declaration": { 39 | "prefix": "func", 40 | "body": "func $1($2) $3 {\n\t$0\n}", 41 | "description": "function declaration" 42 | }, 43 | "variable declaration": { 44 | "prefix": "var", 45 | "body": "var ${1:name} ${2:type}", 46 | "description": "a variable" 47 | }, 48 | "switch statement": { 49 | "prefix": "switch", 50 | "body": "switch ${1:expression} {\ncase ${2:condition}:\n\t$0\n}", 51 | "description": "switch statement" 52 | }, 53 | "select statement": { 54 | "prefix": "sel", 55 | "body": "select {\ncase ${1:condition}:\n\t$0\n}", 56 | "description": "select statement" 57 | }, 58 | "case clause": { 59 | "prefix": "cs", 60 | "body": "case ${1:condition}:$0", 61 | "description": "case clause" 62 | }, 63 | "for statement": { 64 | "prefix": "for", 65 | "body": "for ${1:i} := 0; $1 < ${2:count}; $1${3:++} {\n\t$0\n}", 66 | "description": "a for loop" 67 | }, 68 | "for range statement": { 69 | "prefix": "forr", 70 | "body": "for ${1:_, }${2:var} := range ${3:var} {\n\t$0\n}", 71 | "description": "a for range loop" 72 | }, 73 | "for arrow statement": { 74 | "prefix": "for-", 75 | "body": "for ${1:i}, ${2:j} <- ${3:k} {\n\t$0\n}", 76 | "description": "a for arrow loop" 77 | }, 78 | "channel declaration": { 79 | "prefix": "ch", 80 | "body": "chan ${1:type}", 81 | "description": "a channel" 82 | }, 83 | "map declaration": { 84 | "prefix": "map", 85 | "body": "map[${1:type}]${2:type}", 86 | "description": "a map" 87 | }, 88 | "empty interface": { 89 | "prefix": "in", 90 | "body": "interface{}", 91 | "description": "empty interface" 92 | }, 93 | "if statement": { 94 | "prefix": "if", 95 | "body": "if ${1:condition} {\n\t$0\n}", 96 | "description": "if statement" 97 | }, 98 | "else branch": { 99 | "prefix": "el", 100 | "body": "else {\n\t$0\n}", 101 | "description": "else branch" 102 | }, 103 | "if else statement": { 104 | "prefix": "ie", 105 | "body": "if ${1:condition} {\n\t$2\n} else {\n\t$0\n}", 106 | "description": "if else" 107 | }, 108 | "if err != nil": { 109 | "prefix": "iferr", 110 | "body": "if err != nil {\n\t${1:return ${2:nil, }${3:err}}\n}", 111 | "description": "if err != nil" 112 | }, 113 | "println": { 114 | "prefix": "pr", 115 | "body": "println(\"$1\")", 116 | "description": "println()" 117 | }, 118 | "fmt.Println": { 119 | "prefix": "fp", 120 | "body": "fmt.Println(\"$1\")", 121 | "description": "fmt.Println()" 122 | }, 123 | "fmt.Printf": { 124 | "prefix": "ff", 125 | "body": "fmt.Printf(\"$1\", ${2:var})", 126 | "description": "fmt.Printf()" 127 | }, 128 | "log.Println": { 129 | "prefix": "lp", 130 | "body": "log.Println(\"$1\")", 131 | "description": "log.Println()" 132 | }, 133 | "log.Printf": { 134 | "prefix": "lf", 135 | "body": "log.Printf(\"$1\", ${2:var})", 136 | "description": "log.Printf()" 137 | }, 138 | "log variable content": { 139 | "prefix": "lv", 140 | "body": "log.Printf(\"${1:var}: %#+v\\\\n\", ${1:var})", 141 | "description": "log.Printf() with variable content" 142 | }, 143 | "t.Log": { 144 | "prefix": "tl", 145 | "body": "t.Log(\"$1\")", 146 | "description": "t.Log()" 147 | }, 148 | "t.Logf": { 149 | "prefix": "tlf", 150 | "body": "t.Logf(\"$1\", ${2:var})", 151 | "description": "t.Logf()" 152 | }, 153 | "t.Logf variable content": { 154 | "prefix": "tlv", 155 | "body": "t.Logf(\"${1:var}: %#+v\\\\n\", ${1:var})", 156 | "description": "t.Logf() with variable content" 157 | }, 158 | "make(...)": { 159 | "prefix": "make", 160 | "body": "make(${1:type}, ${2:0})", 161 | "description": "make statement" 162 | }, 163 | "new(...)": { 164 | "prefix": "new", 165 | "body": "new(${1:type})", 166 | "description": "new statement" 167 | }, 168 | "panic(...)": { 169 | "prefix": "pn", 170 | "body": "panic(\"$0\")", 171 | "description": "panic" 172 | }, 173 | "http ResponseWriter *Request": { 174 | "prefix": "wr", 175 | "body": "${1:w} http.ResponseWriter, ${2:r} *http.Request", 176 | "description": "http Response" 177 | }, 178 | "http.HandleFunc": { 179 | "prefix": "hf", 180 | "body": "${1:http}.HandleFunc(\"${2:/}\", ${3:handler})", 181 | "description": "http.HandleFunc()" 182 | }, 183 | "http handler declaration": { 184 | "prefix": "hand", 185 | "body": "func $1(${2:w} http.ResponseWriter, ${3:r} *http.Request) {\n\t$0\n}", 186 | "description": "http handler declaration" 187 | }, 188 | "http.Redirect": { 189 | "prefix": "rd", 190 | "body": "http.Redirect(${1:w}, ${2:r}, \"${3:/}\", ${4:http.StatusFound})", 191 | "description": "http.Redirect()" 192 | }, 193 | "http.Error": { 194 | "prefix": "herr", 195 | "body": "http.Error(${1:w}, ${2:err}.Error(), ${3:http.StatusInternalServerError})", 196 | "description": "http.Error()" 197 | }, 198 | "http.ListenAndServe": { 199 | "prefix": "las", 200 | "body": "http.ListenAndServe(\"${1::8080}\", ${2:nil})", 201 | "description": "http.ListenAndServe" 202 | }, 203 | "http.Serve": { 204 | "prefix": "sv", 205 | "body": "http.Serve(\"${1::8080}\", ${2:nil})", 206 | "description": "http.Serve" 207 | }, 208 | "goroutine anonymous function": { 209 | "prefix": "go", 210 | "body": "go func($1) {\n\t$0\n}($2)", 211 | "description": "anonymous goroutine declaration" 212 | }, 213 | "goroutine function": { 214 | "prefix": "gf", 215 | "body": "go ${1:func}($0)", 216 | "description": "goroutine declaration" 217 | }, 218 | "defer statement": { 219 | "prefix": "df", 220 | "body": "defer ${1:func}($0)", 221 | "description": "defer statement" 222 | }, 223 | "test function": { 224 | "prefix": "tf", 225 | "body": "func Test$1(t *testing.T) {\n\t$0\n}", 226 | "description": "Test function" 227 | }, 228 | "benchmark function": { 229 | "prefix": "bf", 230 | "body": "func Benchmark$1(b *testing.B) {\n\tfor ${2:i} := 0; ${2:i} < b.N; ${2:i}++ {\n\t\t$0\n\t}\n}", 231 | "description": "Benchmark function" 232 | }, 233 | "example function": { 234 | "prefix": "ef", 235 | "body": "func Example$1() {\n\t$2\n\t//Output:\n\t$3\n}", 236 | "description": "Example function" 237 | }, 238 | "table driven test": { 239 | "prefix": "tdt", 240 | "body": "func Test$1(t *testing.T) {\n\ttestCases := []struct {\n\t\tdesc\tstring\n\t\t$2\n\t}{\n\t\t{\n\t\t\tdesc: \"$3\",\n\t\t\t$4\n\t\t},\n\t}\n\tfor _, tC := range testCases {\n\t\tt.Run(tC.desc, func(t *testing.T) {\n\t\t\t$0\n\t\t})\n\t}\n}", 241 | "description": "table driven test" 242 | }, 243 | "init function": { 244 | "prefix": "finit", 245 | "body": "func init() {\n\t$1\n}", 246 | "description": "init function" 247 | }, 248 | "main function": { 249 | "prefix": "fmain", 250 | "body": "func main() {\n\t$1\n}", 251 | "description": "main function" 252 | }, 253 | "method declaration": { 254 | "prefix": "meth", 255 | "body": "func (${1:receiver} ${2:type}) ${3:method}($4) $5 {\n\t$0\n}", 256 | "description": "method declaration" 257 | }, 258 | "hello world web app": { 259 | "prefix": "helloweb", 260 | "body": "package main\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n\t\"time\"\n)\n\nfunc greet(w http.ResponseWriter, r *http.Request) {\n\tfmt.Fprintf(w, \"Hello World! %s\", time.Now())\n}\n\nfunc main() {\n\thttp.HandleFunc(\"/\", greet)\n\thttp.ListenAndServe(\":8080\", nil)\n}", 261 | "description": "sample hello world webapp" 262 | }, 263 | "sort implementation": { 264 | "prefix": "sort", 265 | "body": "type ${1:SortBy} []${2:Type}\n\nfunc (a $1) Len() int { return len(a) }\nfunc (a $1) Swap(i, j int) { a[i], a[j] = a[j], a[i] }\nfunc (a $1) Less(i, j int) bool { ${3:return a[i] < a[j]} }", 266 | "description": "a custom sort.Sort interface implementation, for a given slice type." 267 | }, 268 | "spx move N steps": { 269 | "prefix": "step", 270 | "body": "step ${1:N}", 271 | "description": "spx move N steps" 272 | }, 273 | "spx move N step": { 274 | "prefix": "move", 275 | "body": "move ${1:N}", 276 | "description": "spx move N steps" 277 | }, 278 | "spx turn X": { 279 | "prefix": "turn", 280 | "body": "turn ${1:X}", 281 | "description": "spx turn X" 282 | }, 283 | "spx changeHeading N": { 284 | "prefix": "changeHeading", 285 | "body": "changeHeading ${1:N}", 286 | "description": "spx changeHeading N" 287 | }, 288 | "spx goto o": { 289 | "prefix": "goto", 290 | "body": "goto ${1:O}", 291 | "description": "spx goto o" 292 | }, 293 | "spx setXYpos x, y": { 294 | "prefix": "setXYpos", 295 | "body": "setXYpos ${1:X}, ${2:Y}", 296 | "description": "spx setXYpos x, y" 297 | }, 298 | "spx glide O, N": { 299 | "prefix": "glide", 300 | "body": "glide ${1:O}, ${2:N}", 301 | "description": "spx glide O, N" 302 | }, 303 | "spx glide X, Y, N": { 304 | "prefix": "glide", 305 | "body": "glide ${1:X}, ${2:Y}, ${3:N}", 306 | "description": "spx glide X, Y, N" 307 | }, 308 | "spx turnTo N": { 309 | "prefix": "turnTo", 310 | "body": "turnTo ${1:N}", 311 | "description": "spx turnTo N" 312 | }, 313 | "spx turnTo O": { 314 | "prefix": "turnTo", 315 | "body": "turnTo ${1:O}", 316 | "description": "spx turnTo O" 317 | }, 318 | "spx changeXpos Dx": { 319 | "prefix": "changeXpos", 320 | "body": "changeXpos ${1:Dx}", 321 | "description": "spx changeXpos Dx" 322 | }, 323 | "spx setXpos X": { 324 | "prefix": "setXpos", 325 | "body": "setXpos ${1:X}", 326 | "description": "spx setXpos X" 327 | }, 328 | "spx changeXpos Dy": { 329 | "prefix": "changeYpos", 330 | "body": "changeYpos ${1:Dy}", 331 | "description": "spx changeXpos Dy" 332 | }, 333 | "spx setYpos Y": { 334 | "prefix": "setYpos", 335 | "body": "setYpos ${1:Y}", 336 | "description": "spx setYpos Y" 337 | }, 338 | "spx changeXYpos Dx, Dy": { 339 | "prefix": "changeXYpos", 340 | "body": "changeXYpos ${1:Dx}, ${2:Dy}", 341 | "description": "spx changeXYpos Dx, Dy" 342 | }, 343 | "spx bounceOffEdge": { 344 | "prefix": "bounceOffEdge", 345 | "body": "bounceOffEdge", 346 | "description": "spx bounceOffEdge" 347 | }, 348 | "spx setRotationStyle S": { 349 | "prefix": "setRotationStyle", 350 | "body": "setRotationStyle ${1:S}", 351 | "description": "spx setRotationStyle S" 352 | }, 353 | "spx xpos": { 354 | "prefix": "xpos", 355 | "body": "xpos", 356 | "description": "spx xpos" 357 | }, 358 | "spx ypos": { 359 | "prefix": "ypos", 360 | "body": "ypos", 361 | "description": "spx ypos" 362 | }, 363 | "spx heading": { 364 | "prefix": "heading", 365 | "body": "heading", 366 | "description": "spx heading" 367 | }, 368 | "spx say V, N": { 369 | "prefix": "say", 370 | "body": "say ${1:V}, ${2:N}", 371 | "description": "spx say V, N" 372 | }, 373 | "spx say V": { 374 | "prefix": "say", 375 | "body": "say ${1:V}", 376 | "description": "spx say V" 377 | }, 378 | "spx think V, N": { 379 | "prefix": "think", 380 | "body": "think ${1:V}, ${2:N}", 381 | "description": "spx think V, N" 382 | }, 383 | "spx think V": { 384 | "prefix": "think", 385 | "body": "think ${1:V}", 386 | "description": "spx think V" 387 | }, 388 | "spx setCostume N": { 389 | "prefix": "setCostume", 390 | "body": "setCostume ${1:N}", 391 | "description": "spx setCostume N" 392 | }, 393 | "spx nextCostume": { 394 | "prefix": "nextCostume", 395 | "body": "nextCostume", 396 | "description": "spx nextCostume" 397 | }, 398 | "spx prevCostume": { 399 | "prefix": "prevCostume", 400 | "body": "prevCostume", 401 | "description": "spx prevCostume" 402 | }, 403 | "spx animate name": { 404 | "prefix": "animate", 405 | "body": "animate ${1:Name}", 406 | "description": "spx animate name" 407 | }, 408 | "spx startScene N": { 409 | "prefix": "startScene", 410 | "body": "startScene ${1:N}", 411 | "description": "spx startScene N" 412 | }, 413 | "spx startScene N, Waitt": { 414 | "prefix": "startScene", 415 | "body": "startScene ${1:N}, ${2:Wait}", 416 | "description": "spx startScene N, Wait" 417 | }, 418 | "spx nextScene": { 419 | "prefix": "nextScene", 420 | "body": "nextScene", 421 | "description": "spx nextScene" 422 | }, 423 | "spx prevScene": { 424 | "prefix": "prevScene", 425 | "body": "prevScene", 426 | "description": "spx prevScene" 427 | }, 428 | "spx changeSize": { 429 | "prefix": "changeSize", 430 | "body": "changeSize", 431 | "description": "spx changeSize" 432 | }, 433 | "spx setSize": { 434 | "prefix": "setSize", 435 | "body": "setSize", 436 | "description": "spx setSize" 437 | }, 438 | "spx changeEffect g,n": { 439 | "prefix": "changeEffect", 440 | "body": "changeEffect ${1:G}, ${2:N}", 441 | "description": "spx changeEffect g,n" 442 | }, 443 | "spx setEffect g,n": { 444 | "prefix": "setEffect", 445 | "body": "setEffect ${1:G}, ${2:N}", 446 | "description": "spx setEffect g,n" 447 | }, 448 | "spx clearGraphEffects": { 449 | "prefix": "clearGraphEffects", 450 | "body": "clearGraphEffects", 451 | "description": "spx clearGraphEffects" 452 | }, 453 | "spx show": { 454 | "prefix": "show", 455 | "body": "show", 456 | "description": "spx show" 457 | }, 458 | "spx hide": { 459 | "prefix": "hide", 460 | "body": "hide", 461 | "description": "spx hide" 462 | }, 463 | "spx gotoFront": { 464 | "prefix": "gotoFront", 465 | "body": "gotoFront", 466 | "description": "spx gotoFront" 467 | }, 468 | "spx gotoBackr": { 469 | "prefix": "gotoBack", 470 | "body": "gotoBack", 471 | "description": "spx gotoBack" 472 | }, 473 | "spx goBackLayer N": { 474 | "prefix": "goBackLayer", 475 | "body": "goBackLayer ${1:N}", 476 | "description": "spx goBackLayer N" 477 | }, 478 | "spx costumeIndex": { 479 | "prefix": "costumeIndex", 480 | "body": "costumeIndex", 481 | "description": "spx costumeIndex" 482 | }, 483 | "spx costumeName": { 484 | "prefix": "costumeName", 485 | "body": "costumeName", 486 | "description": "spx costumeName" 487 | }, 488 | "spx sceneIndex": { 489 | "prefix": "sceneIndex", 490 | "body": "sceneIndex", 491 | "description": "spx sceneIndex" 492 | }, 493 | "spx sceneName": { 494 | "prefix": "sceneName", 495 | "body": "sceneName", 496 | "description": "spx sceneName" 497 | }, 498 | "spx size": { 499 | "prefix": "size", 500 | "body": "size", 501 | "description": "spx size" 502 | }, 503 | "spx width": { 504 | "prefix": "width", 505 | "body": "width", 506 | "description": "spx width" 507 | }, 508 | "spx height": { 509 | "prefix": "height", 510 | "body": "height", 511 | "description": "spx height" 512 | }, 513 | "spx play S, true": { 514 | "prefix": "play", 515 | "body": "play ${1:S}, true", 516 | "description": "spx play S, true" 517 | }, 518 | "spx play S": { 519 | "prefix": "play", 520 | "body": "play ${1:S}", 521 | "description": "spx play S" 522 | }, 523 | "spx stopAllSounds": { 524 | "prefix": "stopAllSounds", 525 | "body": "stopAllSounds", 526 | "description": "spx stopAllSounds" 527 | }, 528 | "spx changeEffect s,n": { 529 | "prefix": "changeEffect", 530 | "body": "changeEffect ${1:S}, ${2:N}", 531 | "description": "spx changeEffect s,n" 532 | }, 533 | "spx setEffect s,n": { 534 | "prefix": "setEffect", 535 | "body": "setEffect ${1:S}, ${2:N}", 536 | "description": "spx setEffect s,n" 537 | }, 538 | "spx clearSoundEffect": { 539 | "prefix": "clearSoundEffect", 540 | "body": "clearSoundEffect", 541 | "description": "spx clearSoundEffect" 542 | }, 543 | "spx changeVolume N": { 544 | "prefix": "changeVolume", 545 | "body": "changeVolume N", 546 | "description": "spx changeVolume N" 547 | }, 548 | "spx setVolume N": { 549 | "prefix": "setVolume", 550 | "body": "setVolume ${1:N}", 551 | "description": "spx setVolume N" 552 | }, 553 | "spx onStart": { 554 | "prefix": "onStart", 555 | "body": "onStart => {\n\t$0\n}", 556 | "description": "spx onStart" 557 | }, 558 | "spx onAnyKey": { 559 | "prefix": "onAnyKey", 560 | "body": "onAnyKey ${1:key} => {\n\t$0\n}", 561 | "description": "spx onAnyKey" 562 | }, 563 | "spx onAnyKeys": { 564 | "prefix": "onAnyKey", 565 | "body": "onAnyKey ${1:key}, => {\n\t$0\n}", 566 | "description": "spx onAnyKey" 567 | }, 568 | "spx onAnyKey slice": { 569 | "prefix": "onAnyKey", 570 | "body": "onAnyKey [${1:key}], => {\n\t$0\n}", 571 | "description": "spx onAnyKey" 572 | }, 573 | "spx onClick": { 574 | "prefix": "onClick", 575 | "body": "onClick => {\n\t$0\n}", 576 | "description": "spx onClick" 577 | }, 578 | "spx onAnyScene name": { 579 | "prefix": "onAnyScene", 580 | "body": "onAnyScene ${1:name} => {\n\t$0\n}", 581 | "description": "spx onAnyScene name" 582 | }, 583 | "spx onScene name": { 584 | "prefix": "onScene", 585 | "body": "onScene ${1:N} => {\n\t$0\n}", 586 | "description": "spx onScene name" 587 | }, 588 | "spx onMsg": { 589 | "prefix": "onMsg", 590 | "body": "onMsg ${1:Msg} => {\n\t$0\n}", 591 | "description": "spx onMsg" 592 | }, 593 | "spx onMoving": { 594 | "prefix": "onMoving", 595 | "body": "onMoving => {\n\t$0\n}", 596 | "description": "spx onMoving" 597 | }, 598 | "spx onTurning": { 599 | "prefix": "onTurning", 600 | "body": "onTurning => {\n\t$0\n}", 601 | "description": "spx onTurning" 602 | }, 603 | "spx broadcast": { 604 | "prefix": "broadcast", 605 | "body": "broadcast ${1:Msg}", 606 | "description": "spx broadcast" 607 | }, 608 | "spx broadcast Msg,true": { 609 | "prefix": "broadcast", 610 | "body": "broadcast ${1:Msg}, true", 611 | "description": "spx broadcast Msg,true" 612 | }, 613 | "spx wait N": { 614 | "prefix": "wait", 615 | "body": "wait ${1:N}", 616 | "description": "spx wait N" 617 | }, 618 | "spx onClone": { 619 | "prefix": "onClone", 620 | "body": "onClone => {\n\t$0\n}", 621 | "description": "spx onClone" 622 | }, 623 | "spx clone": { 624 | "prefix": "clone", 625 | "body": "clone", 626 | "description": "spx clone" 627 | }, 628 | "spx Sprite.clone": { 629 | "prefix": "Sprite.clone", 630 | "body": "Sprite.clone", 631 | "description": "spx Sprite.clone" 632 | }, 633 | "spx destroy": { 634 | "prefix": "destroy", 635 | "body": "destroy", 636 | "description": "spx destroy" 637 | }, 638 | "spx die": { 639 | "prefix": "die", 640 | "body": "die", 641 | "description": "spx die" 642 | }, 643 | "spx setDying": { 644 | "prefix": "setDying", 645 | "body": "setDying", 646 | "description": "spx setDying" 647 | }, 648 | "spx stopped": { 649 | "prefix": "stopped", 650 | "body": "stopped", 651 | "description": "spx stopped" 652 | }, 653 | "spx cloned": { 654 | "prefix": "cloned", 655 | "body": "cloned", 656 | "description": "spx cloned" 657 | }, 658 | "spx touching O": { 659 | "prefix": "touching", 660 | "body": "touching ${1:O}", 661 | "description": "spx touching O" 662 | }, 663 | "spx onTouched": { 664 | "prefix": "onTouched", 665 | "body": "onTouched => {\n\t$0\n}", 666 | "description": "spx onTouched" 667 | }, 668 | "spx touchingColor": { 669 | "prefix": "touchingColor", 670 | "body": "touchingColor ${1:Color}", 671 | "description": "spx touchingColor" 672 | }, 673 | "spx distanceTo": { 674 | "prefix": "distanceTo", 675 | "body": "distanceTo($0)", 676 | "description": "spx distanceTo" 677 | }, 678 | "spx ask Msg": { 679 | "prefix": "ask", 680 | "body": "ask ${1:Msg}", 681 | "description": "spx ask Msg" 682 | }, 683 | "spx answer": { 684 | "prefix": "answer", 685 | "body": "answer", 686 | "description": "spx answer" 687 | }, 688 | "spx keyPressed": { 689 | "prefix": "keyPressed", 690 | "body": "keyPressed", 691 | "description": "spx keyPressed" 692 | }, 693 | "spx mousePressed": { 694 | "prefix": "mousePressed", 695 | "body": "mousePressed", 696 | "description": "spx mousePressed" 697 | }, 698 | "spx mouseX": { 699 | "prefix": "mouseX", 700 | "body": "mouseX", 701 | "description": "spx mouseX" 702 | }, 703 | "spx mouseY": { 704 | "prefix": "mouseY", 705 | "body": "mouseY", 706 | "description": "spx mouseY" 707 | }, 708 | "spx timer": { 709 | "prefix": "timer", 710 | "body": "timer", 711 | "description": "spx timer" 712 | }, 713 | "spx resetTimer": { 714 | "prefix": "resetTimer", 715 | "body": "resetTimer", 716 | "description": "spx resetTimer" 717 | }, 718 | "spx username": { 719 | "prefix": "username", 720 | "body": "username", 721 | "description": "spx username" 722 | }, 723 | "spx clear": { 724 | "prefix": "clear", 725 | "body": "clear", 726 | "description": "spx clear" 727 | }, 728 | "spx stamp": { 729 | "prefix": "stamp", 730 | "body": "stamp", 731 | "description": "spx stamp" 732 | }, 733 | "spx penDown": { 734 | "prefix": "penDown", 735 | "body": "penDown", 736 | "description": "spx penDown" 737 | }, 738 | "spx penUp": { 739 | "prefix": "penUp", 740 | "body": "penUp", 741 | "description": "spx penUp" 742 | }, 743 | "spx setPenColor C": { 744 | "prefix": "setPenColor", 745 | "body": "setPenColor ${1:C}", 746 | "description": "spx setPenColor C" 747 | }, 748 | "spx changePenSize N": { 749 | "prefix": "changePenSize", 750 | "body": "changePenSize ${1:N}", 751 | "description": "spx changePenSize N" 752 | }, 753 | "spx setPenSize N": { 754 | "prefix": "setPenSize", 755 | "body": "setPenSize ${1:N}", 756 | "description": "spx setPenSize N" 757 | } 758 | } 759 | } -------------------------------------------------------------------------------- /src/avlTree.ts: -------------------------------------------------------------------------------- 1 | export class Node { 2 | public left: Node | null = null; 3 | public right: Node | null = null; 4 | public height: number = 0; 5 | 6 | /** 7 | * Creates a new AVL Tree node. 8 | * @param key The key of the new node. 9 | * @param value The value of the new node. 10 | */ 11 | constructor(public key: K, public value: V | undefined) {} 12 | 13 | /** 14 | * Convenience function to get the height of the left child of the node, 15 | * returning -1 if the node is null. 16 | * @return The height of the left child, or -1 if it doesn't exist. 17 | */ 18 | public get leftHeight(): number { 19 | if (!this.left) { 20 | return -1; 21 | } 22 | return this.left.height; 23 | } 24 | 25 | /** 26 | * Convenience function to get the height of the right child of the node, 27 | * returning -1 if the node is null. 28 | * @return The height of the right child, or -1 if it doesn't exist. 29 | */ 30 | public get rightHeight(): number { 31 | if (!this.right) { 32 | return -1; 33 | } 34 | return this.right.height; 35 | } 36 | 37 | /** 38 | * Performs a right rotate on this node. 39 | * @return The root of the sub-tree; the node where this node used to be. 40 | */ 41 | public rotateRight(): Node { 42 | // b a 43 | // / \ / \ 44 | // a e -> b.rotateRight() -> c b 45 | // / \ / \ 46 | // c d d e 47 | const other = >this.left; 48 | this.left = other.right; 49 | other.right = this; 50 | this.height = Math.max(this.leftHeight, this.rightHeight) + 1; 51 | other.height = Math.max(other.leftHeight, this.height) + 1; 52 | return other; 53 | } 54 | 55 | /** 56 | * Performs a left rotate on this node. 57 | * @return The root of the sub-tree; the node where this node used to be. 58 | */ 59 | public rotateLeft(): Node { 60 | // a b 61 | // / \ / \ 62 | // c b -> a.rotateLeft() -> a e 63 | // / \ / \ 64 | // d e c d 65 | const other = >this.right; 66 | this.right = other.left; 67 | other.left = this; 68 | this.height = Math.max(this.leftHeight, this.rightHeight) + 1; 69 | other.height = Math.max(other.rightHeight, this.height) + 1; 70 | return other; 71 | } 72 | } 73 | 74 | export type DistanceFunction = (a: K, b: K) => number; 75 | export type CompareFunction = (a: K, b: K) => number; 76 | 77 | /** 78 | * Represents how balanced a node's left and right children are. 79 | */ 80 | const enum BalanceState { 81 | /** Right child's height is 2+ greater than left child's height */ 82 | UNBALANCED_RIGHT, 83 | /** Right child's height is 1 greater than left child's height */ 84 | SLIGHTLY_UNBALANCED_RIGHT, 85 | /** Left and right children have the same height */ 86 | BALANCED, 87 | /** Left child's height is 1 greater than right child's height */ 88 | SLIGHTLY_UNBALANCED_LEFT, 89 | /** Left child's height is 2+ greater than right child's height */ 90 | UNBALANCED_LEFT 91 | } 92 | 93 | export class NearestNeighborDict { 94 | public static NUMERIC_DISTANCE_FUNCTION = (a: number, b: number) => (a > b ? a - b : b - a); 95 | public static DEFAULT_COMPARE_FUNCTION = (a: any, b: any) => (a > b ? 1 : a < b ? -1 : 0); 96 | 97 | protected root: Node|null = null; 98 | 99 | /** 100 | * Creates a new AVL Tree. 101 | */ 102 | constructor( 103 | start: Node, 104 | private distance: DistanceFunction, 105 | private compare: CompareFunction = NearestNeighborDict.DEFAULT_COMPARE_FUNCTION 106 | ) { 107 | this.insert(start.key, start.value); 108 | } 109 | 110 | public height() { 111 | return this.root ? this.root.height : 0; 112 | } 113 | 114 | /** 115 | * Inserts a new node with a specific key into the tree. 116 | * @param key The key being inserted. 117 | * @param value The value being inserted. 118 | */ 119 | public insert(key: K, value?: V): void { 120 | this.root = this._insert(key, value, this.root); 121 | } 122 | 123 | /** 124 | * Gets a node within the tree with a specific key, or the nearest neighbor to that node if it does not exist. 125 | * @param key The key being searched for. 126 | * @return The (key, value) pair of the node with key nearest the given key in value. 127 | */ 128 | public getNearest(key: K): Node { 129 | return this._getNearest(key, this.root!, this.root!); 130 | } 131 | 132 | /** 133 | * Inserts a new node with a specific key into the tree. 134 | * @param key The key being inserted. 135 | * @param root The root of the tree to insert in. 136 | * @return The new tree root. 137 | */ 138 | private _insert(key: K, value: V | undefined, root: Node | null): Node { 139 | // Perform regular BST insertion 140 | if (root === null) { 141 | return new Node(key, value); 142 | } 143 | 144 | if (this.compare(key, root.key) < 0) { 145 | root.left = this._insert(key, value, root.left); 146 | } else if (this.compare(key, root.key) > 0) { 147 | root.right = this._insert(key, value, root.right); 148 | } else { 149 | return root; 150 | } 151 | 152 | // Update height and rebalance tree 153 | root.height = Math.max(root.leftHeight, root.rightHeight) + 1; 154 | const balanceState = this._getBalanceState(root); 155 | 156 | if (balanceState === BalanceState.UNBALANCED_LEFT) { 157 | if (this.compare(key, root.left!.key) < 0) { 158 | // Left left case 159 | root = root.rotateRight(); 160 | } else { 161 | // Left right case 162 | root.left = root.left!.rotateLeft(); 163 | return root.rotateRight(); 164 | } 165 | } 166 | 167 | if (balanceState === BalanceState.UNBALANCED_RIGHT) { 168 | if (this.compare(key, root.right!.key) > 0) { 169 | // Right right case 170 | root = root.rotateLeft(); 171 | } else { 172 | // Right left case 173 | root.right = root.right!.rotateRight(); 174 | return root.rotateLeft(); 175 | } 176 | } 177 | 178 | return root; 179 | } 180 | 181 | /** 182 | * Gets a node within the tree with a specific key, or the node closest (as measured by this._distance) 183 | * to that node if the key is not present 184 | * @param key The key being searched for. 185 | * @param root The root of the tree to search in. 186 | * @param closest The current best estimate of the node closest to the node being searched for, 187 | * as measured by this._distance 188 | * @return The (key, value) pair of the node with key nearest the given key in value. 189 | */ 190 | private _getNearest(key: K, root: Node, closest: Node): Node { 191 | const result = this.compare(key, root.key); 192 | if (result === 0) { 193 | return root; 194 | } 195 | 196 | closest = this.distance(key, root.key) < this.distance(key, closest.key) ? root : closest; 197 | 198 | if (result < 0) { 199 | return root.left ? this._getNearest(key, root.left, closest) : closest; 200 | } else { 201 | return root.right ? this._getNearest(key, root.right, closest) : closest; 202 | } 203 | } 204 | 205 | /** 206 | * Gets the balance state of a node, indicating whether the left or right 207 | * sub-trees are unbalanced. 208 | * @param node The node to get the difference from. 209 | * @return The BalanceState of the node. 210 | */ 211 | private _getBalanceState(node: Node): BalanceState { 212 | const heightDifference = node.leftHeight - node.rightHeight; 213 | switch (heightDifference) { 214 | case -2: 215 | return BalanceState.UNBALANCED_RIGHT; 216 | case -1: 217 | return BalanceState.SLIGHTLY_UNBALANCED_RIGHT; 218 | case 1: 219 | return BalanceState.SLIGHTLY_UNBALANCED_LEFT; 220 | case 2: 221 | return BalanceState.UNBALANCED_LEFT; 222 | case 0: 223 | return BalanceState.BALANCED; 224 | default: { 225 | console.error('Internal error: Avl tree should never be more than two levels unbalanced'); 226 | if (heightDifference > 0) { 227 | return BalanceState.UNBALANCED_LEFT; 228 | } 229 | return BalanceState.UNBALANCED_RIGHT; // heightDifference can't be 0 230 | } 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/goDeclaration.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------- 2 | * Copyright (C) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See LICENSE in the project root for license information. 4 | *--------------------------------------------------------*/ 5 | 6 | 'use strict'; 7 | 8 | import cp = require('child_process'); 9 | import path = require('path'); 10 | import vscode = require('vscode'); 11 | import { toolExecutionEnvironment } from './goEnv'; 12 | import { promptForMissingTool, promptForUpdatingTool } from './gopInstallTools'; 13 | import { getModFolderPath, promptToUpdateToolForModules } from './goModules'; 14 | import { 15 | byteOffsetAt, 16 | getBinPath, 17 | getGoPlusConfig, 18 | getModuleCache, 19 | getWorkspaceFolderPath, 20 | goKeywords, 21 | isPositionInString, 22 | killTree, 23 | runGodoc 24 | } from './util'; 25 | 26 | 27 | 28 | export interface GoDefinitionInformation { 29 | file: string; 30 | line: number; 31 | column: number; 32 | doc: string; 33 | declarationlines: string[]; 34 | name: string; 35 | toolUsed: string; 36 | } 37 | 38 | interface GoDefinitionInput { 39 | document: vscode.TextDocument; 40 | position: vscode.Position; 41 | word: string; 42 | includeDocs: boolean; 43 | isMod: boolean; 44 | cwd: string; 45 | } 46 | 47 | export function definitionLocation( 48 | document: vscode.TextDocument, 49 | position: vscode.Position, 50 | goPlusConfig: vscode.WorkspaceConfiguration, 51 | includeDocs: boolean, 52 | token: vscode.CancellationToken 53 | ): Promise { 54 | const adjustedPos = adjustWordPosition(document, position); 55 | if (!adjustedPos[0]) { 56 | return Promise.resolve(null); 57 | } 58 | const word = adjustedPos[1]; 59 | position = adjustedPos[2]; 60 | 61 | if (!goPlusConfig) { 62 | goPlusConfig = getGoPlusConfig(document.uri); 63 | } 64 | const toolForDocs = goPlusConfig['docsTool'] || 'godoc'; 65 | return getModFolderPath(document.uri).then((modFolderPath) => { 66 | const input: GoDefinitionInput = { 67 | document, 68 | position, 69 | word, 70 | includeDocs, 71 | isMod: !!modFolderPath, 72 | cwd: 73 | modFolderPath && modFolderPath !== getModuleCache() 74 | ? modFolderPath 75 | : getWorkspaceFolderPath(document.uri) || path.dirname(document.fileName) 76 | }; 77 | return definitionLocation_godef(input, token); 78 | }); 79 | } 80 | 81 | const missingToolMsg = 'Missing tool: '; 82 | 83 | export function adjustWordPosition( 84 | document: vscode.TextDocument, 85 | position: vscode.Position 86 | ): [boolean, string, vscode.Position] { 87 | const wordRange = document.getWordRangeAtPosition(position); 88 | const lineText = document.lineAt(position.line).text; 89 | const word = wordRange ? document.getText(wordRange) : ''; 90 | if ( 91 | !wordRange || 92 | lineText.startsWith('//') || 93 | isPositionInString(document, position) || 94 | word.match(/^\d+.?\d+$/) || 95 | goKeywords.indexOf(word) > 0 96 | ) { 97 | return [false, null, null]; 98 | } 99 | if (position.isEqual(wordRange.end) && position.isAfter(wordRange.start)) { 100 | position = position.translate(0, -1); 101 | } 102 | 103 | return [true, word, position]; 104 | } 105 | 106 | const godefImportDefinitionRegex = /^import \(.* ".*"\)$/; 107 | function definitionLocation_godef( 108 | input: GoDefinitionInput, 109 | token: vscode.CancellationToken, 110 | useReceivers: boolean = true 111 | ): Promise { 112 | const godefTool = 'godef'; 113 | const godefPath = getBinPath(godefTool); 114 | if (!path.isAbsolute(godefPath)) { 115 | return Promise.reject(missingToolMsg + godefTool); 116 | } 117 | let offset = byteOffsetAt(input.document, input.position); 118 | let inputText = input.document.getText(); 119 | 120 | if (!inputText.match(/package\s+(\w+)/)) { 121 | let addtText = "package main\r\n\r\n" 122 | offset = offset +addtText.length 123 | inputText = addtText + inputText 124 | } 125 | const env = toolExecutionEnvironment(); 126 | let p: cp.ChildProcess; 127 | if (token) { 128 | token.onCancellationRequested(() => killTree(p.pid)); 129 | } 130 | 131 | return new Promise((resolve, reject) => { 132 | // Spawn `godef` process 133 | const args = ['-t', '-i', '-f', input.document.fileName, '-o', offset.toString()]; 134 | // if (useReceivers) { 135 | // args.push('-r'); 136 | // } 137 | 138 | console.log(godefPath,args); 139 | p = cp.execFile(godefPath, args, { env, cwd: input.cwd }, (err, stdout, stderr) => { 140 | try { 141 | if (err && (err).code === 'ENOENT') { 142 | return reject(missingToolMsg + godefTool); 143 | } 144 | if (err) { 145 | if ( 146 | input.isMod && 147 | !input.includeDocs && 148 | stderr && 149 | stderr.startsWith(`godef: no declaration found for`) 150 | ) { 151 | promptToUpdateToolForModules( 152 | 'godef', 153 | `To get the Go to Definition feature when using Go modules, please update your version of the "godef" tool.` 154 | ); 155 | return reject(stderr); 156 | } 157 | if (stderr.indexOf('flag provided but not defined: -r') !== -1) { 158 | promptForUpdatingTool('godef'); 159 | p = null; 160 | return definitionLocation_godef(input, token, false).then(resolve, reject); 161 | } 162 | return reject(err.message || stderr); 163 | } 164 | const result = stdout.toString(); 165 | const lines = result.split('\n'); 166 | let match = /(.*):(\d+):(\d+)/.exec(lines[0]); 167 | if (!match) { 168 | // TODO: Gotodef on pkg name: 169 | // /usr/local/go/src/html/template\n 170 | console.log("not match") 171 | return resolve(null); 172 | } 173 | const [_, file, line, col] = match; 174 | const pkgPath = path.dirname(file); 175 | const definitionInformation: GoDefinitionInformation = { 176 | file, 177 | line: +line - 1, 178 | column: +col - 1, 179 | declarationlines: lines.slice(1), 180 | toolUsed: 'godef', 181 | doc: null, 182 | name: null 183 | }; 184 | if (!input.includeDocs || godefImportDefinitionRegex.test(definitionInformation.declarationlines[0])) { 185 | return resolve(definitionInformation); 186 | } 187 | match = /^\w+ \(\*?(\w+)\)/.exec(lines[1]); 188 | runGodoc(input.cwd, pkgPath, match ? match[1] : '', input.word, token) 189 | .then((doc) => { 190 | if (doc) { 191 | definitionInformation.doc = doc; 192 | } 193 | resolve(definitionInformation); 194 | }) 195 | .catch((runGoDocErr) => { 196 | resolve(definitionInformation); 197 | }); 198 | } catch (e) { 199 | reject(e); 200 | } 201 | }); 202 | if (p.pid) { 203 | p.stdin.end(inputText); 204 | } 205 | }); 206 | } 207 | 208 | export class GoDefinitionProvider implements vscode.DefinitionProvider { 209 | private goConfig: vscode.WorkspaceConfiguration = null; 210 | 211 | constructor(goConfig?: vscode.WorkspaceConfiguration) { 212 | this.goConfig = goConfig; 213 | } 214 | 215 | public provideDefinition( 216 | document: vscode.TextDocument, 217 | position: vscode.Position, 218 | token: vscode.CancellationToken 219 | ): Thenable { 220 | return definitionLocation(document, position, this.goConfig, false, token).then( 221 | (definitionInfo) => { 222 | if (definitionInfo == null || definitionInfo.file == null) { 223 | return null; 224 | } 225 | const definitionResource = vscode.Uri.file(definitionInfo.file); 226 | const pos = new vscode.Position(definitionInfo.line, definitionInfo.column); 227 | return new vscode.Location(definitionResource, pos); 228 | }, 229 | (err) => { 230 | const miss = parseMissingError(err); 231 | if (miss[0]) { 232 | promptForMissingTool(miss[1]); 233 | } else if (err) { 234 | return Promise.reject(err); 235 | } 236 | return Promise.resolve(null); 237 | } 238 | ); 239 | } 240 | } 241 | 242 | export function parseMissingError(err: any): [boolean, string] { 243 | if (err) { 244 | // Prompt for missing tool is located here so that the 245 | // prompts dont show up on hover or signature help 246 | if (typeof err === 'string' && err.startsWith(missingToolMsg)) { 247 | return [true, err.substr(missingToolMsg.length)]; 248 | } 249 | } 250 | return [false, null]; 251 | } -------------------------------------------------------------------------------- /src/goEnv.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------- 2 | * Copyright (C) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See LICENSE in the project root for license information. 4 | *--------------------------------------------------------*/ 5 | 6 | 'use strict'; 7 | 8 | import path = require('path'); 9 | import vscode = require('vscode'); 10 | import { getCurrentGoPath, getGoPlusConfig, getToolsGopath } from './util'; 11 | 12 | // toolInstallationEnvironment returns the environment in which tools should 13 | // be installed. It always returns a new object. 14 | export function toolInstallationEnvironment(): NodeJS.Dict { 15 | const env = newEnvironment(); 16 | 17 | // If the go.toolsGopath is set, use its value as the GOPATH for `go` processes. 18 | // Else use the Current Gopath 19 | let toolsGopath = getToolsGopath(); 20 | if (toolsGopath) { 21 | // User has explicitly chosen to use toolsGopath, so ignore GOBIN. 22 | env['GOBIN'] = ''; 23 | } else { 24 | toolsGopath = getCurrentGoPath(); 25 | } 26 | if (!toolsGopath) { 27 | const msg = 'Cannot install Go tools. Set either go.gopath or go.toolsGopath in settings.'; 28 | vscode.window.showInformationMessage(msg, 'Open User Settings', 'Open Workspace Settings').then((selected) => { 29 | switch (selected) { 30 | case 'Open User Settings': 31 | vscode.commands.executeCommand('workbench.action.openGlobalSettings'); 32 | break; 33 | case 'Open Workspace Settings': 34 | vscode.commands.executeCommand('workbench.action.openWorkspaceSettings'); 35 | break; 36 | } 37 | }); 38 | return; 39 | } 40 | env['GOPATH'] = toolsGopath; 41 | 42 | return env; 43 | } 44 | 45 | // toolExecutionEnvironment returns the environment in which tools should 46 | // be executed. It always returns a new object. 47 | export function toolExecutionEnvironment(): NodeJS.Dict { 48 | const env = newEnvironment(); 49 | const gopath = getCurrentGoPath(); 50 | if (gopath) { 51 | env['GOPATH'] = gopath; 52 | } 53 | return env; 54 | } 55 | 56 | function newEnvironment(): NodeJS.Dict { 57 | const toolsEnvVars = getGoPlusConfig()['toolsEnvVars']; 58 | const env = Object.assign({}, process.env, toolsEnvVars); 59 | 60 | // The http.proxy setting takes precedence over environment variables. 61 | const httpProxy = vscode.workspace.getConfiguration('http', null).get('proxy'); 62 | if (httpProxy && typeof httpProxy === 'string') { 63 | env['http_proxy'] = httpProxy; 64 | env['HTTP_PROXY'] = httpProxy; 65 | env['https_proxy'] = httpProxy; 66 | env['HTTPS_PROXY'] = httpProxy; 67 | } 68 | return env; 69 | } -------------------------------------------------------------------------------- /src/goModules.ts: -------------------------------------------------------------------------------- 1 | import cp = require('child_process'); 2 | import path = require('path'); 3 | import vscode = require('vscode'); 4 | import { toolExecutionEnvironment } from './goEnv'; 5 | import { envPath, fixDriveCasingInWindows, getCurrentGoRoot } from './goPath'; 6 | import { getTool } from './goTools'; 7 | import { installTools } from './gopInstallTools'; 8 | import { 9 | getFromGlobalState, getFromWorkspaceState, setGlobalState, setWorkspaceState, updateGlobalState, 10 | updateWorkspaceState 11 | } from './stateUtils'; 12 | import { getBinPath, getGoConfig, getGoVersion, getModuleCache } from './util'; 13 | 14 | export let GO111MODULE: string; 15 | const folderToPackageMapping: { [key: string]: string } = {}; 16 | export async function getCurrentPackage(cwd: string): Promise { 17 | if (folderToPackageMapping[cwd]) { 18 | return folderToPackageMapping[cwd]; 19 | } 20 | 21 | const moduleCache = getModuleCache(); 22 | if (cwd.startsWith(moduleCache)) { 23 | let importPath = cwd.substr(moduleCache.length + 1); 24 | const matches = /@v\d+(\.\d+)?(\.\d+)?/.exec(importPath); 25 | if (matches) { 26 | importPath = importPath.substr(0, matches.index); 27 | } 28 | 29 | folderToPackageMapping[cwd] = importPath; 30 | return importPath; 31 | } 32 | 33 | const goRuntimePath = getBinPath('go'); 34 | if (!goRuntimePath) { 35 | console.warn( 36 | `Failed to run "go list" to find current package as the "go" binary cannot be found in either GOROOT(${getCurrentGoRoot()}) or PATH(${envPath})` 37 | ); 38 | return; 39 | } 40 | return new Promise((resolve) => { 41 | const childProcess = cp.spawn(goRuntimePath, ['list'], { cwd, env: toolExecutionEnvironment() }); 42 | const chunks: any[] = []; 43 | childProcess.stdout.on('data', (stdout) => { 44 | chunks.push(stdout); 45 | }); 46 | 47 | childProcess.on('close', () => { 48 | // Ignore lines that are empty or those that have logs about updating the module cache 49 | const pkgs = chunks 50 | .join('') 51 | .toString() 52 | .split('\n') 53 | .filter((line) => line && line.indexOf(' ') === -1); 54 | if (pkgs.length !== 1) { 55 | resolve(); 56 | return; 57 | } 58 | folderToPackageMapping[cwd] = pkgs[0]; 59 | resolve(pkgs[0]); 60 | }); 61 | }); 62 | } 63 | 64 | async function runGoModEnv(folderPath: string): Promise { 65 | const goExecutable = getBinPath('go'); 66 | if (!goExecutable) { 67 | console.warn( 68 | `Failed to run "go env GOMOD" to find mod file as the "go" binary cannot be found in either GOROOT(${getCurrentGoRoot()}) or PATH(${envPath})` 69 | ); 70 | return; 71 | } 72 | const env = toolExecutionEnvironment(); 73 | GO111MODULE = env['GO111MODULE']; 74 | return new Promise((resolve) => { 75 | cp.execFile(goExecutable, ['env', 'GOMOD'], { cwd: folderPath, env }, (err, stdout) => { 76 | if (err) { 77 | console.warn(`Error when running go env GOMOD: ${err}`); 78 | return resolve(); 79 | } 80 | const [goMod] = stdout.split('\n'); 81 | resolve(goMod); 82 | }); 83 | }); 84 | } 85 | 86 | let moduleUsageLogged = false; 87 | function logModuleUsage() { 88 | if (moduleUsageLogged) { 89 | return; 90 | } 91 | moduleUsageLogged = true; 92 | } 93 | 94 | export const packagePathToGoModPathMap: { [key: string]: string } = {}; 95 | 96 | export async function getModFolderPath(fileuri: vscode.Uri): Promise { 97 | const pkgPath = path.dirname(fileuri.fsPath); 98 | if (packagePathToGoModPathMap[pkgPath]) { 99 | return packagePathToGoModPathMap[pkgPath]; 100 | } 101 | 102 | // We never would be using the path under module cache for anything 103 | // So, dont bother finding where exactly is the go.mod file 104 | const moduleCache = getModuleCache(); 105 | if (fixDriveCasingInWindows(fileuri.fsPath).startsWith(moduleCache)) { 106 | return moduleCache; 107 | } 108 | const goVersion = await getGoVersion(); 109 | if (goVersion.lt('1.11')) { 110 | return; 111 | } 112 | 113 | let goModEnvResult = await runGoModEnv(pkgPath); 114 | if (goModEnvResult) { 115 | logModuleUsage(); 116 | goModEnvResult = path.dirname(goModEnvResult); 117 | const goConfig = getGoConfig(fileuri); 118 | 119 | if (goConfig['inferGopath'] === true) { 120 | goConfig.update('inferGopath', false, vscode.ConfigurationTarget.WorkspaceFolder); 121 | vscode.window.showInformationMessage( 122 | 'The "inferGopath" setting is disabled for this workspace because Go modules are being used.' 123 | ); 124 | } 125 | } 126 | packagePathToGoModPathMap[pkgPath] = goModEnvResult; 127 | return goModEnvResult; 128 | } 129 | 130 | const promptedToolsForCurrentSession = new Set(); 131 | export async function promptToUpdateToolForModules( 132 | tool: string, 133 | promptMsg: string, 134 | goConfig?: vscode.WorkspaceConfiguration 135 | ): Promise { 136 | if (promptedToolsForCurrentSession.has(tool)) { 137 | return false; 138 | } 139 | const promptedToolsForModules = getFromGlobalState('promptedToolsForModules', {}); 140 | if (promptedToolsForModules[tool]) { 141 | return false; 142 | } 143 | const goVersion = await getGoVersion(); 144 | const selected = await vscode.window.showInformationMessage(promptMsg, 'Update', 'Later', `Don't show again`); 145 | let choseToUpdate = false; 146 | switch (selected) { 147 | case 'Update': 148 | choseToUpdate = true; 149 | if (!goConfig) { 150 | goConfig = getGoConfig(); 151 | } 152 | if (tool === 'switchFormatToolToGoimports') { 153 | goConfig.update('formatTool', 'goimports', vscode.ConfigurationTarget.Global); 154 | } else { 155 | await installTools([getTool(tool)], goVersion); 156 | } 157 | promptedToolsForModules[tool] = true; 158 | updateGlobalState('promptedToolsForModules', promptedToolsForModules); 159 | break; 160 | case `Don't show again`: 161 | promptedToolsForModules[tool] = true; 162 | updateGlobalState('promptedToolsForModules', promptedToolsForModules); 163 | break; 164 | case 'Later': 165 | default: 166 | promptedToolsForCurrentSession.add(tool); 167 | break; 168 | } 169 | return choseToUpdate; 170 | } -------------------------------------------------------------------------------- /src/goOutline.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------- 2 | * Copyright (C) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See LICENSE in the project root for license information. 4 | *--------------------------------------------------------*/ 5 | 6 | 'use strict'; 7 | 8 | import cp = require('child_process'); 9 | import vscode = require('vscode'); 10 | import { toolExecutionEnvironment } from './goEnv'; 11 | import { promptForMissingTool, promptForUpdatingTool } from './gopInstallTools'; 12 | import { 13 | getBinPath, 14 | getFileArchive, 15 | getGoConfig, 16 | killProcess, 17 | makeMemoizedByteOffsetConverter 18 | } from './util'; 19 | 20 | // Keep in sync with https://github.com/ramya-rao-a/go-outline 21 | export interface GoOutlineRange { 22 | start: number; 23 | end: number; 24 | } 25 | 26 | export interface GoOutlineDeclaration { 27 | label: string; 28 | type: string; 29 | receiverType?: string; 30 | icon?: string; // icon class or null to use the default images based on the type 31 | start: number; 32 | end: number; 33 | children?: GoOutlineDeclaration[]; 34 | signature?: GoOutlineRange; 35 | comment?: GoOutlineRange; 36 | } 37 | 38 | export enum GoOutlineImportsOptions { 39 | Include, 40 | Exclude, 41 | Only 42 | } 43 | 44 | export interface GoOutlineOptions { 45 | /** 46 | * Path of the file for which outline is needed 47 | */ 48 | fileName: string; 49 | 50 | /** 51 | * Option to decide if the output includes, excludes or only includes imports 52 | * If the option is to only include imports, then the file will be parsed only till imports are collected 53 | */ 54 | importsOption: GoOutlineImportsOptions; 55 | 56 | /** 57 | * Document to be parsed. If not provided, saved contents of the given fileName is used 58 | */ 59 | document?: vscode.TextDocument; 60 | } 61 | 62 | export async function documentSymbols( 63 | options: GoOutlineOptions, 64 | token: vscode.CancellationToken 65 | ): Promise { 66 | const decls = await runGoOutline(options, token); 67 | return convertToCodeSymbols( 68 | options.document, 69 | decls, 70 | options.importsOption !== GoOutlineImportsOptions.Exclude, 71 | makeMemoizedByteOffsetConverter(Buffer.from(options.document.getText())) 72 | ); 73 | } 74 | 75 | export function runGoOutline( 76 | options: GoOutlineOptions, 77 | token: vscode.CancellationToken 78 | ): Promise { 79 | return new Promise((resolve, reject) => { 80 | const gooutline = getBinPath('go-outline'); 81 | const gooutlineFlags = ['-f', options.fileName]; 82 | if (options.importsOption === GoOutlineImportsOptions.Only) { 83 | gooutlineFlags.push('-imports-only'); 84 | } 85 | if (options.document) { 86 | gooutlineFlags.push('-modified'); 87 | } 88 | 89 | let p: cp.ChildProcess; 90 | if (token) { 91 | token.onCancellationRequested(() => killProcess(p)); 92 | } 93 | 94 | // Spawn `go-outline` process 95 | p = cp.execFile(gooutline, gooutlineFlags, { env: toolExecutionEnvironment() }, (err, stdout, stderr) => { 96 | try { 97 | if (err && (err).code === 'ENOENT') { 98 | promptForMissingTool('go-outline'); 99 | } 100 | if (stderr && stderr.startsWith('flag provided but not defined: ')) { 101 | promptForUpdatingTool('go-outline'); 102 | if (stderr.startsWith('flag provided but not defined: -imports-only')) { 103 | options.importsOption = GoOutlineImportsOptions.Include; 104 | } 105 | if (stderr.startsWith('flag provided but not defined: -modified')) { 106 | options.document = null; 107 | } 108 | p = null; 109 | return runGoOutline(options, token).then((results) => { 110 | return resolve(results); 111 | }); 112 | } 113 | if (err) { 114 | return resolve(null); 115 | } 116 | const result = stdout.toString(); 117 | const decls = JSON.parse(result); 118 | return resolve(decls); 119 | } catch (e) { 120 | reject(e); 121 | } 122 | }); 123 | if (options.document && p.pid) { 124 | p.stdin.end(getFileArchive(options.document)); 125 | } 126 | }); 127 | } 128 | 129 | const goKindToCodeKind: { [key: string]: vscode.SymbolKind } = { 130 | package: vscode.SymbolKind.Package, 131 | import: vscode.SymbolKind.Namespace, 132 | variable: vscode.SymbolKind.Variable, 133 | constant: vscode.SymbolKind.Constant, 134 | type: vscode.SymbolKind.TypeParameter, 135 | function: vscode.SymbolKind.Function, 136 | struct: vscode.SymbolKind.Struct, 137 | interface: vscode.SymbolKind.Interface 138 | }; 139 | 140 | function convertToCodeSymbols( 141 | document: vscode.TextDocument, 142 | decls: GoOutlineDeclaration[], 143 | includeImports: boolean, 144 | byteOffsetToDocumentOffset: (byteOffset: number) => number 145 | ): vscode.DocumentSymbol[] { 146 | const symbols: vscode.DocumentSymbol[] = []; 147 | (decls || []).forEach((decl) => { 148 | if (!includeImports && decl.type === 'import') { 149 | return; 150 | } 151 | if (decl.label === '_' && decl.type === 'variable') { 152 | return; 153 | } 154 | 155 | const label = decl.receiverType ? `(${decl.receiverType}).${decl.label}` : decl.label; 156 | 157 | const start = byteOffsetToDocumentOffset(decl.start - 1); 158 | const end = byteOffsetToDocumentOffset(decl.end - 1); 159 | const startPosition = document.positionAt(start); 160 | const endPosition = document.positionAt(end); 161 | const symbolRange = new vscode.Range(startPosition, endPosition); 162 | const selectionRange = 163 | startPosition.line === endPosition.line 164 | ? symbolRange 165 | : new vscode.Range(startPosition, document.lineAt(startPosition.line).range.end); 166 | 167 | if (decl.type === 'type') { 168 | const line = document.lineAt(document.positionAt(start)); 169 | const regexStruct = new RegExp(`^\\s*type\\s+${decl.label}\\s+struct\\b`); 170 | const regexInterface = new RegExp(`^\\s*type\\s+${decl.label}\\s+interface\\b`); 171 | decl.type = regexStruct.test(line.text) ? 'struct' : regexInterface.test(line.text) ? 'interface' : 'type'; 172 | } 173 | 174 | const symbolInfo = new vscode.DocumentSymbol( 175 | label, 176 | decl.type, 177 | goKindToCodeKind[decl.type], 178 | symbolRange, 179 | selectionRange 180 | ); 181 | 182 | symbols.push(symbolInfo); 183 | if (decl.children) { 184 | symbolInfo.children = convertToCodeSymbols( 185 | document, 186 | decl.children, 187 | includeImports, 188 | byteOffsetToDocumentOffset 189 | ); 190 | } 191 | }); 192 | return symbols; 193 | } -------------------------------------------------------------------------------- /src/goPath.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------- 2 | * Copyright (C) Microsoft Corporation. All rights reserved. 3 | * Modification copyright 2020 The Go Authors. All rights reserved. 4 | * Licensed under the MIT License. See LICENSE in the project root for license information. 5 | *--------------------------------------------------------*/ 6 | 7 | 'use strict'; 8 | 9 | /** 10 | * This file is loaded by both the extension and debug adapter, so it cannot import 'vscode' 11 | */ 12 | import fs = require('fs'); 13 | import os = require('os'); 14 | import path = require('path'); 15 | 16 | let binPathCache: { [bin: string]: string } = {}; 17 | 18 | export const envPath = process.env['PATH'] || (process.platform === 'win32' ? process.env['Path'] : null); 19 | 20 | export function getBinPathFromEnvVar(toolName: string, envVarValue: string, appendBinToPath: boolean): string { 21 | toolName = correctBinname(toolName); 22 | if (envVarValue) { 23 | const paths = envVarValue.split(path.delimiter); 24 | for (const p of paths) { 25 | const binpath = path.join(p, appendBinToPath ? 'bin' : '', toolName); 26 | if (executableFileExists(binpath)) { 27 | return binpath; 28 | } 29 | } 30 | } 31 | return null; 32 | } 33 | 34 | export function getBinPathWithPreferredGopathGoroot( 35 | toolName: string, 36 | preferredGopaths: string[], 37 | preferredGoroot?: string, 38 | alternateTool?: string, 39 | useCache = true, 40 | ) { 41 | if (alternateTool && path.isAbsolute(alternateTool) && executableFileExists(alternateTool)) { 42 | binPathCache[toolName] = alternateTool; 43 | return alternateTool; 44 | } 45 | 46 | // FIXIT: this cache needs to be invalidated when go.goroot or go.alternateTool is changed. 47 | if (useCache && binPathCache[toolName]) { 48 | return binPathCache[toolName]; 49 | } 50 | 51 | const binname = alternateTool && !path.isAbsolute(alternateTool) ? alternateTool : toolName; 52 | const pathFromGoBin = getBinPathFromEnvVar(binname, process.env['GOBIN'], false); 53 | if (pathFromGoBin) { 54 | binPathCache[toolName] = pathFromGoBin; 55 | return pathFromGoBin; 56 | } 57 | 58 | for (const preferred of preferredGopaths) { 59 | if (typeof preferred === 'string') { 60 | // Search in the preferred GOPATH workspace's bin folder 61 | const pathFrompreferredGoPath = getBinPathFromEnvVar(binname, preferred, true); 62 | if (pathFrompreferredGoPath) { 63 | binPathCache[toolName] = pathFrompreferredGoPath; 64 | return pathFrompreferredGoPath; 65 | } 66 | } 67 | } 68 | 69 | // Check GOROOT (go, gofmt, godoc would be found here) 70 | const pathFromGoRoot = getBinPathFromEnvVar(binname, preferredGoroot || getCurrentGoRoot(), true); 71 | if (pathFromGoRoot) { 72 | binPathCache[toolName] = pathFromGoRoot; 73 | return pathFromGoRoot; 74 | } 75 | 76 | // Finally search PATH parts 77 | const pathFromPath = getBinPathFromEnvVar(binname, envPath, false); 78 | if (pathFromPath) { 79 | binPathCache[toolName] = pathFromPath; 80 | return pathFromPath; 81 | } 82 | 83 | // Check default path for go 84 | if (toolName === 'go') { 85 | const defaultPathForGo = process.platform === 'win32' ? 'C:\\Go\\bin\\go.exe' : '/usr/local/go/bin/go'; 86 | if (executableFileExists(defaultPathForGo)) { 87 | binPathCache[toolName] = defaultPathForGo; 88 | return defaultPathForGo; 89 | } 90 | return; 91 | } 92 | 93 | // Else return the binary name directly (this will likely always fail downstream) 94 | return toolName; 95 | } 96 | 97 | /** 98 | * Returns the goroot path if it exists, otherwise returns an empty string 99 | */ 100 | let currentGoRoot = ''; 101 | export function getCurrentGoRoot(): string { 102 | return currentGoRoot || process.env['GOROOT'] || ''; 103 | } 104 | 105 | export function setCurrentGoRoot(goroot: string) { 106 | currentGoRoot = goroot; 107 | } 108 | 109 | function correctBinname(toolName: string) { 110 | if (process.platform === 'win32') { 111 | return toolName + '.exe'; 112 | } 113 | return toolName; 114 | } 115 | 116 | function executableFileExists(filePath: string): boolean { 117 | let exists = true; 118 | try { 119 | exists = fs.statSync(filePath).isFile(); 120 | if (exists) { 121 | fs.accessSync(filePath, fs.constants.F_OK | fs.constants.X_OK); 122 | } 123 | } catch (e) { 124 | exists = false; 125 | } 126 | return exists; 127 | } 128 | 129 | export function fileExists(filePath: string): boolean { 130 | try { 131 | return fs.statSync(filePath).isFile(); 132 | } catch (e) { 133 | return false; 134 | } 135 | } 136 | 137 | export function clearCacheForTools() { 138 | binPathCache = {}; 139 | } 140 | 141 | /** 142 | * Exapnds ~ to homedir in non-Windows platform 143 | */ 144 | export function resolveHomeDir(inputPath: string): string { 145 | if (!inputPath || !inputPath.trim()) { 146 | return inputPath; 147 | } 148 | return inputPath.startsWith('~') ? path.join(os.homedir(), inputPath.substr(1)) : inputPath; 149 | } 150 | 151 | export function stripBOM(s: string): string { 152 | if (s && s[0] === '\uFEFF') { 153 | s = s.substr(1); 154 | } 155 | return s; 156 | } 157 | 158 | export function parseEnvFile(envFilePath: string): { [key: string]: string } { 159 | const env: { [key: string]: any } = {}; 160 | if (!envFilePath) { 161 | return env; 162 | } 163 | 164 | try { 165 | const buffer = stripBOM(fs.readFileSync(envFilePath, 'utf8')); 166 | buffer.split('\n').forEach((line) => { 167 | const r = line.match(/^\s*([\w\.\-]+)\s*=\s*(.*)?\s*$/); 168 | if (r !== null) { 169 | let value = r[2] || ''; 170 | if (value.length > 0 && value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') { 171 | value = value.replace(/\\n/gm, '\n'); 172 | } 173 | env[r[1]] = value.replace(/(^['"]|['"]$)/g, ''); 174 | } 175 | }); 176 | return env; 177 | } catch (e) { 178 | throw new Error(`Cannot load environment variables from file ${envFilePath}`); 179 | } 180 | } 181 | 182 | // Walks up given folder path to return the closest ancestor that has `src` as a child 183 | export function getInferredGopath(folderPath: string): string { 184 | if (!folderPath) { 185 | return; 186 | } 187 | 188 | const dirs = folderPath.toLowerCase().split(path.sep); 189 | 190 | // find src directory closest to given folder path 191 | const srcIdx = dirs.lastIndexOf('src'); 192 | if (srcIdx > 0) { 193 | return folderPath.substr(0, dirs.slice(0, srcIdx).join(path.sep).length); 194 | } 195 | } 196 | 197 | /** 198 | * Returns the workspace in the given Gopath to which given directory path belongs to 199 | * @param gopath string Current Gopath. Can be ; or : separated (as per os) to support multiple paths 200 | * @param currentFileDirPath string 201 | */ 202 | export function getCurrentGoWorkspaceFromGOPATH(gopath: string, currentFileDirPath: string): string { 203 | if (!gopath) { 204 | return; 205 | } 206 | const workspaces: string[] = gopath.split(path.delimiter); 207 | let currentWorkspace = ''; 208 | currentFileDirPath = fixDriveCasingInWindows(currentFileDirPath); 209 | 210 | // Find current workspace by checking if current file is 211 | // under any of the workspaces in $GOPATH 212 | for (const workspace of workspaces) { 213 | const possibleCurrentWorkspace = path.join(workspace, 'src'); 214 | if ( 215 | currentFileDirPath.startsWith(possibleCurrentWorkspace) || 216 | (process.platform === 'win32' && 217 | currentFileDirPath.toLowerCase().startsWith(possibleCurrentWorkspace.toLowerCase())) 218 | ) { 219 | // In case of nested workspaces, (example: both /Users/me and /Users/me/src/a/b/c are in $GOPATH) 220 | // both parent & child workspace in the nested workspaces pair can make it inside the above if block 221 | // Therefore, the below check will take longer (more specific to current file) of the two 222 | if (possibleCurrentWorkspace.length > currentWorkspace.length) { 223 | currentWorkspace = currentFileDirPath.substr(0, possibleCurrentWorkspace.length); 224 | } 225 | } 226 | } 227 | return currentWorkspace; 228 | } 229 | 230 | // Workaround for issue in https://github.com/Microsoft/vscode/issues/9448#issuecomment-244804026 231 | export function fixDriveCasingInWindows(pathToFix: string): string { 232 | return process.platform === 'win32' && pathToFix 233 | ? pathToFix.substr(0, 1).toUpperCase() + pathToFix.substr(1) 234 | : pathToFix; 235 | } 236 | 237 | /** 238 | * Returns the tool name from the given path to the tool 239 | * @param toolPath 240 | */ 241 | export function getToolFromToolPath(toolPath: string): string | undefined { 242 | if (!toolPath) { 243 | return; 244 | } 245 | let tool = path.basename(toolPath); 246 | if (process.platform === 'win32' && tool.endsWith('.exe')) { 247 | tool = tool.substr(0, tool.length - 4); 248 | } 249 | return tool; 250 | } -------------------------------------------------------------------------------- /src/goTools.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------- 2 | * Copyright (C) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See LICENSE in the project root for license information. 4 | *--------------------------------------------------------*/ 5 | 6 | 'use strict'; 7 | 8 | import cp = require('child_process'); 9 | import fs = require('fs'); 10 | import moment = require('moment'); 11 | import path = require('path'); 12 | import semver = require('semver'); 13 | import util = require('util'); 14 | import { getBinPath, getGoPlusConfig,GoVersion} from './util'; 15 | 16 | export interface Tool { 17 | name: string; 18 | importPath: string; 19 | isImportant: boolean; 20 | description: string; 21 | 22 | // latestVersion and latestVersionTimestamp are hardcoded default values 23 | // for the last known version of the given tool. We also hardcode values 24 | // for the latest known pre-release of the tool for the Nightly extension. 25 | latestVersion?: semver.SemVer; 26 | latestVersionTimestamp?: moment.Moment; 27 | latestPrereleaseVersion?: semver.SemVer; 28 | latestPrereleaseVersionTimestamp?: moment.Moment; 29 | 30 | // minimumGoVersion and maximumGoVersion set the range for the versions of 31 | // Go with which this tool can be used. 32 | minimumGoVersion?: semver.SemVer; 33 | maximumGoVersion?: semver.SemVer; 34 | 35 | // close performs any shutdown tasks that a tool must execute before a new 36 | // version is installed. It returns a string containing an error message on 37 | // failure. 38 | close?: () => Promise; 39 | } 40 | 41 | /** 42 | * Returns the import path for a given tool, at a given Go version. 43 | * @param tool Object of type `Tool` for the Go tool. 44 | * @param goVersion The current Go version. 45 | */ 46 | export function getImportPath(tool: Tool): string { 47 | return tool.importPath; 48 | } 49 | 50 | export function getImportPathWithVersion(tool: Tool, version: semver.SemVer): string { 51 | const importPath = getImportPath(tool); 52 | if (version) { 53 | return importPath + '@v' + version; 54 | } 55 | return importPath; 56 | } 57 | 58 | /** 59 | * Returns boolean denoting if the import path for the given tool ends with `/...` 60 | * and if the version of Go supports installing wildcard paths in module mode. 61 | * @param tool Object of type `Tool` for the Go tool. 62 | * @param goVersion The current Go version. 63 | */ 64 | export function disableModulesForWildcard(tool: Tool, goVersion: GoVersion): boolean { 65 | const importPath = getImportPath(tool); 66 | const isWildcard = importPath.endsWith('...'); 67 | 68 | // Only Go >= 1.13 supports installing wildcards in module mode. 69 | return isWildcard && goVersion.lt('1.13'); 70 | } 71 | 72 | 73 | export function containsTool(tools: Tool[], tool: Tool): boolean { 74 | return tools.indexOf(tool) > -1; 75 | } 76 | 77 | export function containsString(tools: Tool[], toolName: string): boolean { 78 | return tools.some((tool) => tool.name === toolName); 79 | } 80 | 81 | export function getTool(name: string): Tool { 82 | return allToolsInformation[name]; 83 | } 84 | 85 | // hasModSuffix returns true if the given tool has a different, module-specific 86 | // name to avoid conflicts. 87 | export function hasModSuffix(tool: Tool): boolean { 88 | return tool.name.endsWith('-gomod'); 89 | } 90 | 91 | export function isGocode(tool: Tool): boolean { 92 | return tool.name === 'gocode' || tool.name === 'gocode-gomod'; 93 | } 94 | 95 | export function getConfiguredTools(): Tool[] { 96 | const tools: Tool[] = []; 97 | function maybeAddTool(name: string) { 98 | const tool = allToolsInformation[name]; 99 | if (tool) { 100 | tools.push(tool); 101 | } 102 | } 103 | 104 | const goPlusConfig = getGoPlusConfig(); 105 | 106 | 107 | // Add the format tool that was chosen by the user. 108 | maybeAddTool(goPlusConfig['formatTool']); 109 | return tools; 110 | } 111 | 112 | export const allToolsInformation: { [key: string]: Tool } = { 113 | 'gopfmt': { 114 | name: 'gopfmt', 115 | importPath: 'github.com/goplus/gop/cmd/gopfmt', 116 | isImportant: true, 117 | description: 'Formatter' 118 | } 119 | }; 120 | 121 | /** 122 | * ToolAtVersion is a Tool at a specific version. 123 | * Lack of version implies the latest version. 124 | */ 125 | export interface ToolAtVersion extends Tool { 126 | version?: semver.SemVer; 127 | } 128 | 129 | export function getToolAtVersion(name: string, version?: semver.SemVer): ToolAtVersion { 130 | return { ...allToolsInformation[name], version }; 131 | } -------------------------------------------------------------------------------- /src/gopExtraInfo.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------- 2 | * Copyright (C) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See LICENSE in the project root for license information. 4 | *--------------------------------------------------------*/ 5 | 6 | 'use strict'; 7 | 8 | import vscode = require('vscode'); 9 | import { CancellationToken, Hover, HoverProvider, Position, TextDocument, WorkspaceConfiguration } from 'vscode'; 10 | import { definitionLocation } from './goDeclaration'; 11 | import { getGoPlusConfig } from './util'; 12 | 13 | export class GoHoverProvider implements HoverProvider { 14 | private goPlusConfig: WorkspaceConfiguration | undefined; 15 | 16 | constructor(goPlusConfig?: WorkspaceConfiguration) { 17 | this.goPlusConfig = goPlusConfig; 18 | } 19 | 20 | public provideHover(document: TextDocument, position: Position, token: CancellationToken): Thenable { 21 | if (!this.goPlusConfig) { 22 | this.goPlusConfig = getGoPlusConfig(document.uri); 23 | } 24 | let goPlusConfig = this.goPlusConfig; 25 | 26 | // Temporary fix to fall back to godoc if guru is the set docsTool 27 | if (goPlusConfig['docsTool'] === 'guru') { 28 | goPlusConfig = Object.assign({}, goPlusConfig, { docsTool: 'godoc' }); 29 | } 30 | return definitionLocation(document, position, goPlusConfig, true, token).then( 31 | (definitionInfo) => { 32 | if (definitionInfo == null) { 33 | return null; 34 | } 35 | const lines = definitionInfo.declarationlines 36 | .filter((line) => line !== '') 37 | .map((line) => line.replace(/\t/g, ' ')); 38 | let text; 39 | text = lines.join('\n').replace(/\n+$/, ''); 40 | const hoverTexts = new vscode.MarkdownString(); 41 | hoverTexts.appendCodeblock(text, 'go'); 42 | if (definitionInfo.doc != null) { 43 | hoverTexts.appendMarkdown(definitionInfo.doc); 44 | } 45 | const hover = new Hover(hoverTexts); 46 | return hover; 47 | }, 48 | () => { 49 | return null; 50 | } 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/gopFormat.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------- 2 | * Copyright (C) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See LICENSE in the project root for license information. 4 | *--------------------------------------------------------*/ 5 | 6 | 'use strict'; 7 | 8 | import cp = require('child_process'); 9 | import path = require('path'); 10 | import vscode = require('vscode'); 11 | import { toolExecutionEnvironment } from './goEnv'; 12 | import { promptForMissingTool, promptForUpdatingTool } from './gopInstallTools'; 13 | import { getBinPath, getGoPlusConfig, killTree } from './util'; 14 | 15 | export class GoPlusDocumentFormattingEditProvider implements vscode.DocumentFormattingEditProvider { 16 | public provideDocumentFormattingEdits( 17 | document: vscode.TextDocument, 18 | options: vscode.FormattingOptions, 19 | token: vscode.CancellationToken 20 | ): vscode.ProviderResult { 21 | if (vscode.window.visibleTextEditors.every((e) => e.document.fileName !== document.fileName)) { 22 | return []; 23 | } 24 | 25 | const filename = document.fileName; 26 | const goPlusConfig = getGoPlusConfig(document.uri); 27 | const formatTool = goPlusConfig['formatTool'] || 'gopfmt'; 28 | const formatFlags = goPlusConfig['formatFlags'].slice() || []; 29 | 30 | // We ignore the -w flag that updates file on disk because that would break undo feature 31 | if (formatFlags.indexOf('-w') > -1) { 32 | formatFlags.splice(formatFlags.indexOf('-w'), 1); 33 | } 34 | return this.runFormatter(formatTool, formatFlags, document, token).then( 35 | (edits) => edits, 36 | (err) => { 37 | if (typeof err === 'string' && err.startsWith('flag provided but not defined: -srcdir')) { 38 | promptForUpdatingTool(formatTool); 39 | return Promise.resolve([]); 40 | } 41 | if (err) { 42 | console.log(err); 43 | return Promise.reject('Check the console in dev tools to find errors when formatting.'); 44 | } 45 | } 46 | ); 47 | } 48 | 49 | private runFormatter( 50 | formatTool: string, 51 | formatFlags: string[], 52 | document: vscode.TextDocument, 53 | token: vscode.CancellationToken 54 | ): Thenable { 55 | const formatCommandBinPath = getBinPath(formatTool); 56 | return new Promise((resolve, reject) => { 57 | if (!path.isAbsolute(formatCommandBinPath)) { 58 | promptForMissingTool(formatTool); 59 | return reject(); 60 | } 61 | 62 | const env = toolExecutionEnvironment(); 63 | const cwd = path.dirname(document.fileName); 64 | let stdout = ''; 65 | let stderr = ''; 66 | 67 | // Use spawn instead of exec to avoid maxBufferExceeded error 68 | const p = cp.spawn(formatCommandBinPath, formatFlags, { env, cwd }); 69 | token.onCancellationRequested(() => !p.killed && killTree(p.pid)); 70 | p.stdout.setEncoding('utf8'); 71 | p.stdout.on('data', (data) => { 72 | (stdout += data); 73 | }); 74 | p.stderr.on('data', (data) => { 75 | (stderr += data); 76 | }); 77 | p.on('error', (err) => { 78 | if (err && (err).code === 'ENOENT') { 79 | promptForMissingTool(formatTool); 80 | return reject(); 81 | } 82 | }); 83 | p.on('close', (code) => { 84 | if (code !== 0) { 85 | return reject(stderr); 86 | } 87 | 88 | // Return the complete file content in the edit. 89 | // VS Code will calculate minimal edits to be applied 90 | const fileStart = new vscode.Position(0, 0); 91 | const fileEnd = document.lineAt(document.lineCount - 1).range.end; 92 | const textEdits: vscode.TextEdit[] = [ 93 | new vscode.TextEdit(new vscode.Range(fileStart, fileEnd), stdout) 94 | ]; 95 | return resolve(textEdits); 96 | }); 97 | if (p.pid) { 98 | p.stdin.end(document.getText()); 99 | } 100 | }); 101 | } 102 | } -------------------------------------------------------------------------------- /src/gopImport.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import vscode = require('vscode'); 4 | import { getImportablePackages } from './gopPackages'; 5 | import { promptForMissingTool } from './gopInstallTools'; 6 | import { documentSymbols, GoOutlineImportsOptions } from './goOutline'; 7 | import { parseFilePrelude } from './util'; 8 | 9 | export function getTextEditForAddImport(inputText: string, arg: string): vscode.TextEdit[] { 10 | // Import name wasn't provided 11 | if (arg === undefined) { 12 | return null; 13 | } 14 | 15 | const { imports, pkg } = parseFilePrelude(inputText); 16 | if (imports.some((block) => block.pkgs.some((pkgpath) => pkgpath === arg))) { 17 | return []; 18 | } 19 | 20 | const multis = imports.filter((x) => x.kind === 'multi'); 21 | const minusCgo = imports.filter((x) => x.kind !== 'pseudo'); 22 | 23 | if (multis.length > 0) { 24 | // There is a multiple import declaration, add to the last one 25 | const lastImportSection = multis[multis.length - 1]; 26 | if (lastImportSection.end === -1) { 27 | // For some reason there was an empty import section like `import ()` 28 | return [vscode.TextEdit.insert(new vscode.Position(lastImportSection.start + 1, 0), `import "${arg}"\n`)]; 29 | } 30 | // Add import at the start of the block so that goimports/goreturns can order them correctly 31 | return [vscode.TextEdit.insert(new vscode.Position(lastImportSection.start + 1, 0), '\t"' + arg + '"\n')]; 32 | } else if (minusCgo.length > 0) { 33 | // There are some number of single line imports, which can just be collapsed into a block import. 34 | const edits = []; 35 | 36 | edits.push(vscode.TextEdit.insert(new vscode.Position(minusCgo[0].start - 1, 0), 'import (\n\t"' + arg + '"\n')); 37 | minusCgo.forEach((element) => { 38 | const currentLine = vscode.window.activeTextEditor.document.lineAt(element.start - 1).text; 39 | const updatedLine = currentLine.replace(/^\s*import\s*/, '\t'); 40 | edits.push( 41 | vscode.TextEdit.replace( 42 | new vscode.Range(element.start - 1, 0, element.start - 1, currentLine.length), 43 | updatedLine 44 | ) 45 | ); 46 | }); 47 | edits.push(vscode.TextEdit.insert(new vscode.Position(minusCgo[minusCgo.length - 1].end - 1, 0), ')\n')); 48 | 49 | return edits; 50 | } else if (pkg && pkg.start >= 0) { 51 | // There are no import declarations, but there is a package declaration 52 | return [vscode.TextEdit.insert(new vscode.Position(0, 0), '\nimport (\n\t"' + arg + '"\n)\n')]; 53 | } else { 54 | // There are no imports and no package declaration - give up 55 | return []; 56 | } 57 | } 58 | 59 | const missingToolMsg = 'Missing tool: '; 60 | 61 | export async function listPackages(excludeImportedPkgs: boolean = false): Promise { 62 | const importedPkgs = 63 | excludeImportedPkgs && vscode.window.activeTextEditor 64 | ? await getImports(vscode.window.activeTextEditor.document) 65 | : []; 66 | const pkgMap = await getImportablePackages(vscode.window.activeTextEditor.document.fileName, true); 67 | const stdLibs: string[] = []; 68 | const nonStdLibs: string[] = []; 69 | pkgMap.forEach((value, key) => { 70 | if (importedPkgs.some((imported) => imported === key)) { 71 | return; 72 | } 73 | if (value.isStd) { 74 | stdLibs.push(key); 75 | } else { 76 | nonStdLibs.push(key); 77 | } 78 | }); 79 | return [...stdLibs.sort(), ...nonStdLibs.sort()]; 80 | } 81 | 82 | /** 83 | * Returns the imported packages in the given file 84 | * 85 | * @param document TextDocument whose imports need to be returned 86 | * @returns Array of imported package paths wrapped in a promise 87 | */ 88 | async function getImports(document: vscode.TextDocument): Promise { 89 | const options = { 90 | fileName: document.fileName, 91 | importsOption: GoOutlineImportsOptions.Only, 92 | document 93 | }; 94 | 95 | const symbols = await documentSymbols(options, null); 96 | if (!symbols || !symbols.length) { 97 | return []; 98 | } 99 | // import names will be of the form "math", so strip the quotes in the beginning and the end 100 | const imports = symbols[0].children 101 | .filter((x: any) => x.kind === vscode.SymbolKind.Namespace) 102 | .map((x: any) => x.name.substr(1, x.name.length - 2)); 103 | return imports; 104 | } 105 | 106 | async function askUserForImport(): Promise { 107 | try { 108 | const packages = await listPackages(true); 109 | return vscode.window.showQuickPick(packages); 110 | } catch (err) { 111 | if (typeof err === 'string' && err.startsWith(missingToolMsg)) { 112 | promptForMissingTool(err.substr(missingToolMsg.length)); 113 | } 114 | } 115 | } 116 | 117 | export function addImport(arg: { importPath: string; from: string }) { 118 | const editor = vscode.window.activeTextEditor; 119 | if (!editor) { 120 | vscode.window.showErrorMessage('No active editor found to add imports.'); 121 | return; 122 | } 123 | const p = arg && arg.importPath ? Promise.resolve(arg.importPath) : askUserForImport(); 124 | p.then((imp) => { 125 | if (!imp) { 126 | return; 127 | } 128 | const edits = getTextEditForAddImport(vscode.window.activeTextEditor.document.getText(), imp); 129 | if (edits && edits.length > 0) { 130 | const edit = new vscode.WorkspaceEdit(); 131 | edit.set(editor.document.uri, edits); 132 | vscode.workspace.applyEdit(edit); 133 | } 134 | }); 135 | } -------------------------------------------------------------------------------- /src/gopInstallTools.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import cp = require('child_process'); 4 | import fs = require('fs'); 5 | import path = require('path'); 6 | import { SemVer } from 'semver'; 7 | import util = require('util'); 8 | import vscode = require('vscode'); 9 | import { toolInstallationEnvironment } from './goEnv'; 10 | import { envPath, getCurrentGoRoot, getToolFromToolPath, setCurrentGoRoot } from './goPath'; 11 | import { outputChannel } from './gopStatus'; 12 | import { 13 | containsTool, 14 | disableModulesForWildcard, 15 | getConfiguredTools, 16 | getImportPath, 17 | getImportPathWithVersion, 18 | getTool, 19 | hasModSuffix, 20 | Tool, 21 | ToolAtVersion, 22 | } from './goTools'; 23 | import { 24 | getBinPath, 25 | getGoPlusConfig, 26 | getGoVersion, 27 | getTempFilePath, 28 | GoVersion, 29 | rmdirRecursive, 30 | } from './util'; 31 | 32 | // declinedUpdates tracks the tools that the user has declined to update. 33 | const declinedUpdates: Tool[] = []; 34 | 35 | // declinedUpdates tracks the tools that the user has declined to install. 36 | const declinedInstalls: Tool[] = []; 37 | 38 | export async function installAllTools(updateExistingToolsOnly: boolean = false) { 39 | const goVersion = await getGoVersion(); 40 | let allTools = getConfiguredTools(); 41 | 42 | // exclude tools replaced by alternateTools. 43 | const alternateTools: { [key: string]: string } = getGoPlusConfig().get('alternateTools'); 44 | allTools = allTools.filter((tool) => { 45 | return !alternateTools[tool.name]; 46 | }); 47 | 48 | // Update existing tools by finding all tools the user has already installed. 49 | if (updateExistingToolsOnly) { 50 | await installTools( 51 | allTools.filter((tool) => { 52 | const toolPath = getBinPath(tool.name); 53 | return toolPath && path.isAbsolute(toolPath); 54 | }), 55 | goVersion 56 | ); 57 | return; 58 | } 59 | 60 | // Otherwise, allow the user to select which tools to install or update. 61 | const selected = await vscode.window.showQuickPick( 62 | allTools.map((x) => { 63 | const item: vscode.QuickPickItem = { 64 | label: x.name, 65 | description: x.description 66 | }; 67 | return item; 68 | }), 69 | { 70 | canPickMany: true, 71 | placeHolder: 'Select the tools to install/update.' 72 | } 73 | ); 74 | if (!selected) { 75 | return; 76 | } 77 | await installTools(selected.map((x) => getTool(x.label)), goVersion); 78 | } 79 | 80 | /** 81 | * Installs given array of missing tools. If no input is given, the all tools are installed 82 | * 83 | * @param missing array of tool names and optionally, their versions to be installed. 84 | * If a tool's version is not specified, it will install the latest. 85 | * @param goVersion version of Go that affects how to install the tool. (e.g. modules vs legacy GOPATH mode) 86 | */ 87 | export async function installTools(missing: ToolAtVersion[], goVersion: GoVersion): Promise { 88 | if (!missing) { 89 | return; 90 | } 91 | 92 | outputChannel.show(); 93 | outputChannel.clear(); 94 | 95 | const envForTools = toolInstallationEnvironment(); 96 | const toolsGopath = envForTools['GOPATH']; 97 | let envMsg = `Tools environment: GOPATH=${toolsGopath}`; 98 | if (envForTools['GOBIN']) { 99 | envMsg += `, GOBIN=${envForTools['GOBIN']}`; 100 | } 101 | outputChannel.appendLine(envMsg); 102 | 103 | let installingMsg = `Installing ${missing.length} ${missing.length > 1 ? 'tools' : 'tool'} at `; 104 | if (envForTools['GOBIN']) { 105 | installingMsg += `the configured GOBIN: ${envForTools['GOBIN']}`; 106 | } else { 107 | installingMsg += `${toolsGopath}${path.sep}bin`; 108 | } 109 | 110 | // If the user is on Go >= 1.11, tools should be installed with modules enabled. 111 | // This ensures that users get the latest tagged version, rather than master, 112 | // which may be unstable. 113 | let modulesOff = true; 114 | // if (goVersion.lt('1.11')) { 115 | // modulesOff = true; 116 | // } else { 117 | // installingMsg += ' in module mode.'; 118 | // } 119 | 120 | outputChannel.appendLine(installingMsg); 121 | missing.forEach((missingTool) => { 122 | let toolName = missingTool.name; 123 | if (missingTool.version) { 124 | toolName += '@' + missingTool.version; 125 | } 126 | outputChannel.appendLine(' ' + toolName); 127 | }); 128 | 129 | outputChannel.appendLine(''); // Blank line for spacing. 130 | 131 | const toInstall: Promise<{ tool: Tool, reason: string }>[] = []; 132 | for (const tool of missing) { 133 | // Disable modules for tools which are installed with the "..." wildcard. 134 | const modulesOffForTool = modulesOff || disableModulesForWildcard(tool, goVersion); 135 | 136 | const reason = installTool(tool, goVersion, envForTools, !modulesOffForTool); 137 | toInstall.push(Promise.resolve({ tool, reason: await reason })); 138 | } 139 | 140 | const results = await Promise.all(toInstall); 141 | 142 | const failures: { tool: ToolAtVersion, reason: string }[] = []; 143 | for (const result of results) { 144 | if (result.reason != "") { 145 | failures.push(result); 146 | } 147 | } 148 | 149 | // Report detailed information about any failures. 150 | outputChannel.appendLine(''); // blank line for spacing 151 | if (failures.length === 0) { 152 | outputChannel.appendLine('All tools successfully installed. You are ready to Go :).'); 153 | } else { 154 | outputChannel.appendLine(failures.length + ' tools failed to install.\n'); 155 | for (const failure of failures) { 156 | outputChannel.appendLine(`${failure.tool.name}: ${failure.reason} `); 157 | } 158 | } 159 | } 160 | 161 | export async function installTool( 162 | tool: ToolAtVersion, goVersion: GoVersion, 163 | envForTools: NodeJS.Dict, modulesOn: boolean): Promise { 164 | // Some tools may have to be closed before we reinstall them. 165 | if (tool.close) { 166 | const reason = await tool.close(); 167 | if (reason) { 168 | return reason; 169 | } 170 | } 171 | // Install tools in a temporary directory, to avoid altering go.mod files. 172 | const mkdtemp = util.promisify(fs.mkdtemp); 173 | const toolsTmpDir = await mkdtemp(getTempFilePath('go-tools-')); 174 | const env = Object.assign({}, envForTools); 175 | let tmpGoModFile: string; 176 | if (modulesOn) { 177 | env['GO111MODULE'] = 'on'; 178 | 179 | // Write a temporary go.mod file to avoid version conflicts. 180 | tmpGoModFile = path.join(toolsTmpDir, 'go.mod'); 181 | const writeFile = util.promisify(fs.writeFile); 182 | await writeFile(tmpGoModFile, 'module tools'); 183 | } else { 184 | envForTools['GO111MODULE'] = 'off'; 185 | } 186 | 187 | // Build the arguments list for the tool installation. 188 | const args = ['get', '-v']; 189 | // Only get tools at master if we are not using modules. 190 | if (!modulesOn) { 191 | args.push('-u'); 192 | } 193 | // Tools with a "mod" suffix should not be installed, 194 | // instead we run "go build -o" to rename them. 195 | if (hasModSuffix(tool)) { 196 | args.push('-d'); 197 | } 198 | let importPath: string; 199 | if (!modulesOn) { 200 | importPath = getImportPath(tool); 201 | } else { 202 | importPath = getImportPathWithVersion(tool, tool.version); 203 | } 204 | args.push(importPath); 205 | 206 | let output: string; 207 | let result: string = ''; 208 | try { 209 | const opts = { 210 | env, 211 | cwd: toolsTmpDir, 212 | }; 213 | const execFile = util.promisify(cp.execFile); 214 | const { stdout, stderr } = await execFile(goVersion.binaryPath, args, opts); 215 | output = `${stdout} ${stderr}`; 216 | 217 | // TODO(rstambler): Figure out why this happens and maybe delete it. 218 | if (stderr.indexOf('unexpected directory layout:') > -1) { 219 | await execFile(goVersion.binaryPath, args, opts); 220 | } else if (hasModSuffix(tool)) { 221 | const gopath = env['GOPATH']; 222 | if (!gopath) { 223 | return `GOPATH not configured in environment`; 224 | } 225 | const outputFile = path.join(gopath, 'bin', process.platform === 'win32' ? `${tool.name}.exe` : tool.name); 226 | await execFile(goVersion.binaryPath, ['build', '-o', outputFile, importPath], opts); 227 | } 228 | outputChannel.appendLine(`Installing ${importPath} SUCCEEDED`); 229 | } catch (e) { 230 | outputChannel.appendLine(`Installing ${importPath} FAILED`); 231 | result = `failed to install ${tool}: ${e} ${output} `; 232 | } 233 | 234 | // Delete the temporary installation directory. 235 | rmdirRecursive(toolsTmpDir); 236 | 237 | return result; 238 | } 239 | 240 | export async function promptForMissingTool(toolName: string) { 241 | const tool = getTool(toolName); 242 | 243 | // If user has declined to install this tool, don't prompt for it. 244 | if (containsTool(declinedInstalls, tool)) { 245 | return; 246 | } 247 | 248 | const goVersion = await getGoVersion(); 249 | if (!goVersion) { 250 | return; 251 | } 252 | 253 | // Show error messages for outdated tools or outdated Go versions. 254 | if (tool.minimumGoVersion && goVersion.lt(tool.minimumGoVersion.format())) { 255 | vscode.window.showInformationMessage(`You are using go${goVersion.format()}, but ${tool.name} requires at least go${tool.minimumGoVersion.format()}.`); 256 | return; 257 | } 258 | if (tool.maximumGoVersion && goVersion.gt(tool.maximumGoVersion.format())) { 259 | vscode.window.showInformationMessage(`You are using go${goVersion.format()}, but ${tool.name} only supports go${tool.maximumGoVersion.format()} and below.`); 260 | return; 261 | } 262 | 263 | const installOptions = ['Install']; 264 | let missing = await getMissingTools(); 265 | if (!containsTool(missing, tool)) { 266 | return; 267 | } 268 | missing = missing.filter((x) => x === tool || tool.isImportant); 269 | if (missing.length > 1) { 270 | // Offer the option to install all tools. 271 | installOptions.push('Install All'); 272 | } 273 | const msg = `The "${tool.name}" command is not available. 274 | Run "go get -v ${getImportPath(tool)}" to install.`; 275 | const selected = await vscode.window.showInformationMessage(msg, ...installOptions); 276 | switch (selected) { 277 | case 'Install': 278 | await installTools([tool], goVersion); 279 | break; 280 | case 'Install All': 281 | await installTools(missing, goVersion); 282 | break; 283 | default: 284 | // The user has declined to install this tool. 285 | declinedInstalls.push(tool); 286 | break; 287 | } 288 | } 289 | 290 | export async function promptForUpdatingTool(toolName: string, newVersion?: SemVer) { 291 | const tool = getTool(toolName); 292 | const toolVersion = { ...tool, version: newVersion }; // ToolWithVersion 293 | 294 | // If user has declined to update, then don't prompt. 295 | if (containsTool(declinedUpdates, tool)) { 296 | return; 297 | } 298 | const goVersion = await getGoVersion(); 299 | let updateMsg = `Your version of ${tool.name} appears to be out of date. Please update for an improved experience.`; 300 | const choices: string[] = ['Update']; 301 | if (toolName === `gopls`) { 302 | choices.push('Release Notes'); 303 | } 304 | if (newVersion) { 305 | updateMsg = `A new version of ${tool.name} (v${newVersion}) is available. Please update for an improved experience.`; 306 | } 307 | const selected = await vscode.window.showInformationMessage(updateMsg, ...choices); 308 | switch (selected) { 309 | case 'Update': 310 | await installTools([toolVersion], goVersion); 311 | break; 312 | case 'Release Notes': 313 | vscode.commands.executeCommand( 314 | 'vscode.open', 315 | vscode.Uri.parse('https://github.com/golang/go/issues/33030#issuecomment-510151934') 316 | ); 317 | break; 318 | default: 319 | declinedUpdates.push(tool); 320 | break; 321 | } 322 | } 323 | 324 | export function updateGoVarsFromConfig(): Promise { 325 | // FIXIT: when user changes the environment variable settings or go.gopath, the following 326 | // condition prevents from updating the process.env accordingly, so the extension will lie. 327 | // Needs to clean up. 328 | if (process.env['GOPATH'] && process.env['GOPROXY'] && process.env['GOBIN']) { 329 | return Promise.resolve(); 330 | } 331 | 332 | // FIXIT: if updateGoVarsFromConfig is called again after addGoRuntimeBaseToPATH sets PATH, 333 | // the go chosen by getBinPath based on PATH will not change. 334 | const goRuntimePath = getBinPath('go', false); 335 | if (!goRuntimePath) { 336 | vscode.window.showErrorMessage( 337 | `Failed to run "go env" to find GOPATH as the "go" binary cannot be found in either GOROOT(${getCurrentGoRoot()}) or PATH(${envPath})` 338 | ); 339 | return; 340 | } 341 | 342 | return new Promise((resolve, reject) => { 343 | cp.execFile(goRuntimePath, ['env', 'GOPATH', 'GOROOT', 'GOPROXY', 'GOBIN'], (err, stdout, stderr) => { 344 | if (err) { 345 | return reject(); 346 | } 347 | const envOutput = stdout.split('\n'); 348 | if (!process.env['GOPATH'] && envOutput[0].trim()) { 349 | process.env['GOPATH'] = envOutput[0].trim(); 350 | } 351 | if (envOutput[1] && envOutput[1].trim()) { 352 | setCurrentGoRoot(envOutput[1].trim()); 353 | } 354 | if (!process.env['GOPROXY'] && envOutput[2] && envOutput[2].trim()) { 355 | process.env['GOPROXY'] = envOutput[2].trim(); 356 | } 357 | if (!process.env['GOBIN'] && envOutput[3] && envOutput[3].trim()) { 358 | process.env['GOBIN'] = envOutput[3].trim(); 359 | } 360 | 361 | // cgo, gopls, and other underlying tools will inherit the environment and attempt 362 | // to locate 'go' from the PATH env var. 363 | addGoRuntimeBaseToPATH(path.join(getCurrentGoRoot(), 'bin')); 364 | return resolve(); 365 | }); 366 | }); 367 | } 368 | 369 | // PATH value cached before addGoRuntimeBaseToPath modified. 370 | let defaultPathEnv = ''; 371 | 372 | // addGoRuntimeBaseToPATH adds the given path to the front of the PATH environment variable. 373 | // It removes duplicates. 374 | // TODO: can we avoid changing PATH but utilize toolExecutionEnv? 375 | function addGoRuntimeBaseToPATH(newGoRuntimeBase: string) { 376 | if (!newGoRuntimeBase) { 377 | return; 378 | } 379 | 380 | let pathEnvVar: string; 381 | if (process.env.hasOwnProperty('PATH')) { 382 | pathEnvVar = 'PATH'; 383 | } else if (process.platform === 'win32' && process.env.hasOwnProperty('Path')) { 384 | pathEnvVar = 'Path'; 385 | } else { 386 | return; 387 | } 388 | 389 | if (!defaultPathEnv) { // cache the default value 390 | defaultPathEnv = process.env[pathEnvVar]; 391 | } 392 | 393 | let pathVars = defaultPathEnv.split(path.delimiter); 394 | pathVars = pathVars.filter((p) => p !== newGoRuntimeBase); 395 | pathVars.unshift(newGoRuntimeBase); 396 | process.env[pathEnvVar] = pathVars.join(path.delimiter); 397 | } 398 | 399 | let alreadyOfferedToInstallTools = false; 400 | 401 | export async function offerToInstallTools() { 402 | if (alreadyOfferedToInstallTools) { 403 | return; 404 | } 405 | alreadyOfferedToInstallTools = true; 406 | 407 | const goVersion = await getGoVersion(); 408 | let missing = await getMissingTools(); 409 | missing = missing.filter((x) => x.isImportant); 410 | if (missing.length > 0) { 411 | vscode.commands.registerCommand('go.promptforinstall', () => { 412 | const installItem = { 413 | title: 'Install', 414 | async command() { 415 | await installTools(missing, goVersion); 416 | } 417 | }; 418 | const showItem = { 419 | title: 'Show', 420 | command() { 421 | outputChannel.clear(); 422 | outputChannel.appendLine('Below tools are needed for the basic features of the Go extension.'); 423 | missing.forEach((x) => outputChannel.appendLine(x.name)); 424 | } 425 | }; 426 | vscode.window 427 | .showInformationMessage( 428 | 'Failed to find some of the Go analysis tools. Would you like to install them?', 429 | installItem, 430 | showItem 431 | ) 432 | .then((selection) => { 433 | if (selection) { 434 | selection.command(); 435 | } 436 | }); 437 | }); 438 | } 439 | } 440 | 441 | function getMissingTools(): Promise { 442 | const keys = getConfiguredTools(); 443 | return Promise.all( 444 | keys.map( 445 | (tool) => 446 | new Promise((resolve, reject) => { 447 | const toolPath = getBinPath(tool.name); 448 | resolve(path.isAbsolute(toolPath) ? null : tool); 449 | }) 450 | ) 451 | ).then((res) => { 452 | return res.filter((x) => x != null); 453 | }); 454 | } 455 | 456 | export async function getActiveGoRoot(): Promise { 457 | // look for current current go binary 458 | let goroot = getCurrentGoRoot(); 459 | if (!goroot) { 460 | await updateGoVarsFromConfig(); 461 | goroot = getCurrentGoRoot(); 462 | } 463 | return goroot || undefined; 464 | } -------------------------------------------------------------------------------- /src/gopMain.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import vscode = require('vscode'); 4 | import { GoPlusCompletionItemProvider } from './gopSuggest'; 5 | import { GOP_MODE,SPX_MODE } from './gopMode'; 6 | import { GoPlusDocumentFormattingEditProvider } from './gopFormat'; 7 | import { GoHoverProvider } from './gopExtraInfo'; 8 | import { GoDefinitionProvider } from './goDeclaration'; 9 | import { addImport } from './gopImport'; 10 | 11 | export function activate(ctx: vscode.ExtensionContext): void { 12 | vscode.languages.registerCompletionItemProvider(GOP_MODE, new GoPlusCompletionItemProvider(ctx.globalState), '.', '"') 13 | vscode.languages.registerDocumentFormattingEditProvider(GOP_MODE, new GoPlusDocumentFormattingEditProvider()) 14 | vscode.languages.registerHoverProvider(GOP_MODE, new GoHoverProvider()); 15 | vscode.languages.registerDefinitionProvider(GOP_MODE, new GoDefinitionProvider()); 16 | 17 | vscode.languages.registerCompletionItemProvider(SPX_MODE, new GoPlusCompletionItemProvider(ctx.globalState), '.', '"') 18 | vscode.languages.registerDocumentFormattingEditProvider(SPX_MODE, new GoPlusDocumentFormattingEditProvider()) 19 | vscode.languages.registerHoverProvider(SPX_MODE, new GoHoverProvider()); 20 | vscode.languages.registerDefinitionProvider(SPX_MODE, new GoDefinitionProvider()); 21 | 22 | vscode.commands.registerCommand('goplus.import.add', (arg) => { 23 | return addImport(arg); 24 | }) 25 | } -------------------------------------------------------------------------------- /src/gopMode.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------- 2 | * Copyright (C) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See LICENSE in the project root for license information. 4 | *--------------------------------------------------------*/ 5 | 6 | 'use strict'; 7 | 8 | import vscode = require('vscode'); 9 | 10 | export const GOP_MODE: vscode.DocumentFilter = { language: 'gop', scheme: 'file' }; 11 | export const SPX_MODE: vscode.DocumentFilter = { language: 'spx', scheme: 'file' }; -------------------------------------------------------------------------------- /src/gopPackages.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------- 2 | * Copyright (C) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See LICENSE in the project root for license information. 4 | *--------------------------------------------------------*/ 5 | 6 | import cp = require('child_process'); 7 | import path = require('path'); 8 | import vscode = require('vscode'); 9 | import { toolExecutionEnvironment } from './goEnv'; 10 | import { promptForMissingTool, promptForUpdatingTool } from './gopInstallTools'; 11 | import { envPath, fixDriveCasingInWindows, getCurrentGoRoot, getCurrentGoWorkspaceFromGOPATH } from './goPath'; 12 | import { getBinPath, getCurrentGoPath, getGoVersion } from './util'; 13 | 14 | type GopkgsDone = (res: Map) => void; 15 | interface Cache { 16 | entry: Map; 17 | lastHit: number; 18 | } 19 | 20 | export interface PackageInfo { 21 | name: string; 22 | isStd: boolean; 23 | } 24 | 25 | 26 | let gopkgsNotified: boolean = false; 27 | const allPkgsCache: Map = new Map(); 28 | const pkgRootDirs = new Map(); 29 | let cacheTimeout: number = 5000; 30 | const gopkgsSubscriptions: Map = new Map(); 31 | const gopkgsRunning: Set = new Set(); 32 | 33 | 34 | function gopkgs(workDir?: string): Promise> { 35 | return new Promise>((resolve, reject) => { 36 | const pkgs = new Map(); 37 | var output = "errors;errors\nflag;flag\nfmt;fmt\nio;io\nnet;net\nos;os\nreflect;reflect\nstrconv;strconv\nstrings;strings\nsync;sync\n" 38 | output.split('\n').forEach((pkgDetail) => { 39 | const [pkgName, pkgPath] = pkgDetail.trim().split(';'); 40 | pkgs.set(pkgPath, { 41 | name: pkgName, 42 | isStd: true 43 | }); 44 | }); 45 | return resolve(pkgs); 46 | }); 47 | } 48 | /** 49 | * Returns mapping of import path and package name for packages that can be imported 50 | * Possible to return empty if useCache options is used. 51 | * @param filePath. Used to determine the right relative path for vendor pkgs 52 | * @param useCache. Force to use cache 53 | * @returns Map mapping between package import path and package name 54 | */ 55 | export function getImportablePackages(filePath: string, useCache: boolean = false): Promise> { 56 | filePath = fixDriveCasingInWindows(filePath); 57 | const fileDirPath = path.dirname(filePath); 58 | 59 | let foundPkgRootDir = pkgRootDirs.get(fileDirPath); 60 | const workDir = foundPkgRootDir || fileDirPath; 61 | const cache = allPkgsCache.get(workDir); 62 | 63 | const getAllPackagesPromise: Promise> = 64 | useCache && cache ? Promise.race([getAllPackages(workDir), cache.entry]) : getAllPackages(workDir); 65 | 66 | return Promise.all([getAllPackagesPromise]).then(([pkgs]) => { 67 | const pkgMap = new Map(); 68 | if (!pkgs) { 69 | return pkgMap; 70 | } 71 | 72 | // const currentWorkspace = getCurrentGoWorkspaceFromGOPATH(getCurrentGoPath(), fileDirPath); 73 | pkgs.forEach((info, pkgPath) => { 74 | if (info.name === 'main') { 75 | return; 76 | } 77 | pkgMap.set(pkgPath, info); 78 | }); 79 | return pkgMap; 80 | }); 81 | } 82 | 83 | function getAllPackagesNoCache(workDir: string): Promise> { 84 | return new Promise>((resolve, reject) => { 85 | // Use subscription style to guard costly/long running invocation 86 | const callback = (pkgMap: Map) => { 87 | resolve(pkgMap); 88 | }; 89 | 90 | let subs = gopkgsSubscriptions.get(workDir); 91 | if (!subs) { 92 | subs = []; 93 | gopkgsSubscriptions.set(workDir, subs); 94 | } 95 | subs.push(callback); 96 | 97 | // Ensure only single gokpgs running 98 | if (!gopkgsRunning.has(workDir)) { 99 | gopkgsRunning.add(workDir); 100 | 101 | gopkgs(workDir).then((pkgMap) => { 102 | gopkgsRunning.delete(workDir); 103 | gopkgsSubscriptions.delete(workDir); 104 | subs.forEach((cb) => cb(pkgMap)); 105 | }); 106 | } 107 | }); 108 | } 109 | 110 | /** 111 | * Runs gopkgs 112 | * @argument workDir. The workspace directory of the project. 113 | * @returns Map mapping between package import path and package name 114 | */ 115 | export async function getAllPackages(workDir: string): Promise> { 116 | const cache = allPkgsCache.get(workDir); 117 | const useCache = cache && new Date().getTime() - cache.lastHit < cacheTimeout; 118 | if (useCache) { 119 | cache.lastHit = new Date().getTime(); 120 | return Promise.resolve(cache.entry); 121 | } 122 | 123 | const pkgs = await getAllPackagesNoCache(workDir); 124 | if (!pkgs || pkgs.size === 0) { 125 | if (!gopkgsNotified) { 126 | vscode.window.showInformationMessage( 127 | 'Could not find packages. Ensure `gopkgs -format {{.Name}};{{.ImportPath}}` runs successfully.' 128 | ); 129 | gopkgsNotified = true; 130 | } 131 | } 132 | allPkgsCache.set(workDir, { 133 | entry: pkgs, 134 | lastHit: new Date().getTime() 135 | }); 136 | return pkgs; 137 | } -------------------------------------------------------------------------------- /src/gopStatus.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------- 2 | * Copyright (C) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See LICENSE in the project root for license information. 4 | *--------------------------------------------------------*/ 5 | 6 | 'use strict'; 7 | 8 | import vscode = require('vscode'); 9 | 10 | export let outputChannel = vscode.window.createOutputChannel('GoPlus'); -------------------------------------------------------------------------------- /src/stateUtils.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------- 2 | * Copyright (C) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See LICENSE in the project root for license information. 4 | *--------------------------------------------------------*/ 5 | 6 | import vscode = require('vscode'); 7 | 8 | let globalState: vscode.Memento; 9 | let workspaceState: vscode.Memento; 10 | 11 | export function getFromGlobalState(key: string, defaultValue?: any): any { 12 | if (!globalState) { 13 | return defaultValue; 14 | } 15 | return globalState.get(key, defaultValue); 16 | } 17 | 18 | export function updateGlobalState(key: string, value: any) { 19 | if (!globalState) { 20 | return; 21 | } 22 | return globalState.update(key, value); 23 | } 24 | 25 | export function setGlobalState(state: vscode.Memento) { 26 | globalState = state; 27 | } 28 | 29 | export function getFromWorkspaceState(key: string, defaultValue?: any) { 30 | if (!workspaceState) { 31 | return defaultValue; 32 | } 33 | return workspaceState.get(key, defaultValue); 34 | } 35 | 36 | export function updateWorkspaceState(key: string, value: any) { 37 | if (!workspaceState) { 38 | return; 39 | } 40 | return workspaceState.update(key, value); 41 | } 42 | 43 | export function setWorkspaceState(state: vscode.Memento) { 44 | workspaceState = state; 45 | } 46 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------- 2 | * Copyright (C) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See LICENSE in the project root for license information. 4 | *--------------------------------------------------------*/ 5 | 6 | import cp = require('child_process'); 7 | import vscode = require('vscode'); 8 | import util = require('util'); 9 | import kill = require('tree-kill'); 10 | import { NearestNeighborDict, Node } from './avlTree'; 11 | 12 | import { 13 | envPath, 14 | fixDriveCasingInWindows, 15 | getBinPathWithPreferredGopathGoroot, 16 | getCurrentGoRoot, 17 | getInferredGopath, 18 | resolveHomeDir, 19 | } from './goPath'; 20 | import { toolExecutionEnvironment } from './goEnv'; 21 | import { getCurrentPackage } from './goModules'; 22 | import fs = require('fs'); 23 | import os = require('os'); 24 | import semver = require('semver'); 25 | import path = require('path'); 26 | import { outputChannel } from './gopStatus'; 27 | 28 | export const goKeywords: string[] = [ 29 | 'break', 30 | 'case', 31 | 'chan', 32 | 'const', 33 | 'continue', 34 | 'default', 35 | 'defer', 36 | 'else', 37 | 'fallthrough', 38 | 'for', 39 | 'func', 40 | 'go', 41 | 'goto', 42 | 'if', 43 | 'import', 44 | 'interface', 45 | 'map', 46 | 'package', 47 | 'range', 48 | 'return', 49 | 'select', 50 | 'struct', 51 | 'switch', 52 | 'type', 53 | 'var' 54 | ]; 55 | 56 | export const goBuiltinTypes: Set = new Set([ 57 | 'bool', 58 | 'byte', 59 | 'complex128', 60 | 'complex64', 61 | 'error', 62 | 'float32', 63 | 'float64', 64 | 'int', 65 | 'int16', 66 | 'int32', 67 | 'int64', 68 | 'int8', 69 | 'rune', 70 | 'string', 71 | 'uint', 72 | 'uint16', 73 | 'uint32', 74 | 'uint64', 75 | 'uint8', 76 | 'uintptr' 77 | ]); 78 | 79 | let toolsGopath: string; 80 | let cachedGoBinPath: string | undefined; 81 | let cachedGoVersion: GoVersion | undefined; 82 | let vendorSupport: boolean | undefined; 83 | 84 | export class GoVersion { 85 | public sv?: semver.SemVer; 86 | public isDevel?: boolean; 87 | private commit?: string; 88 | 89 | constructor(public binaryPath: string, version: string) { 90 | const matchesRelease = /go version go(\d.\d+).*/.exec(version); 91 | const matchesDevel = /go version devel \+(.[a-zA-Z0-9]+).*/.exec(version); 92 | if (matchesRelease) { 93 | const sv = semver.coerce(matchesRelease[0]); 94 | if (sv) { 95 | this.sv = sv; 96 | } 97 | } else if (matchesDevel) { 98 | this.isDevel = true; 99 | this.commit = matchesDevel[0]; 100 | } 101 | } 102 | 103 | public isValid(): boolean { 104 | return !!this.sv || !!this.isDevel; 105 | } 106 | 107 | public format(): string { 108 | if (this.sv) { 109 | return this.sv.format(); 110 | } 111 | return `devel +${this.commit}`; 112 | } 113 | 114 | public lt(version: string): boolean { 115 | // Assume a developer version is always above any released version. 116 | // This is not necessarily true. 117 | if (this.isDevel || !this.sv) { 118 | return false; 119 | } 120 | const v = semver.coerce(version); 121 | if (!v) { 122 | return false; 123 | } 124 | return semver.lt(this.sv, v); 125 | } 126 | 127 | public gt(version: string): boolean { 128 | // Assume a developer version is always above any released version. 129 | // This is not necessarily true. 130 | if (this.isDevel || !this.sv) { 131 | return true; 132 | } 133 | const v = semver.coerce(version); 134 | if (!v) { 135 | return false; 136 | } 137 | return semver.gt(this.sv, v); 138 | } 139 | } 140 | 141 | /** 142 | * Gets version of Go based on the output of the command `go version`. 143 | * Returns null if go is being used from source/tip in which case `go version` will not return release tag like go1.6.3 144 | */ 145 | export async function getGoVersion(): Promise { 146 | const goRuntimePath = getBinPath('go'); 147 | 148 | const warn = (msg: string) => { 149 | outputChannel.appendLine(msg); 150 | console.warn(msg); 151 | }; 152 | 153 | if (!goRuntimePath) { 154 | warn(`unable to locate "go" binary in GOROOT (${getCurrentGoRoot()}) or PATH (${envPath})`); 155 | return; 156 | } 157 | if (cachedGoBinPath === goRuntimePath && cachedGoVersion) { 158 | if (cachedGoVersion.isValid()) { 159 | return Promise.resolve(cachedGoVersion); 160 | } 161 | warn(`cached Go version (${cachedGoVersion}) is invalid, recomputing`); 162 | } 163 | try { 164 | const execFile = util.promisify(cp.execFile); 165 | const { stdout, stderr } = await execFile(goRuntimePath, ['version']); 166 | if (stderr) { 167 | warn(`failed to run "${goRuntimePath} version": stdout: ${stdout}, stderr: ${stderr}`); 168 | return; 169 | } 170 | cachedGoBinPath = goRuntimePath; 171 | cachedGoVersion = new GoVersion(goRuntimePath, stdout); 172 | } catch (err) { 173 | warn(`failed to run "${goRuntimePath} version": ${err}`); 174 | return; 175 | } 176 | return cachedGoVersion; 177 | } 178 | 179 | // getGoPlusConfig is declared as an exported const rather than a function, so it can be stubbbed in testing. 180 | export const getGoPlusConfig = (uri?: vscode.Uri) => { 181 | if (!uri) { 182 | if (vscode.window.activeTextEditor) { 183 | uri = vscode.window.activeTextEditor.document.uri; 184 | } else { 185 | uri = null; 186 | } 187 | } 188 | return vscode.workspace.getConfiguration('goplus', uri); 189 | }; 190 | 191 | // getGoConfig is declared as an exported const rather than a function, so it can be stubbbed in testing. 192 | export const getGoConfig = (uri?: vscode.Uri) => { 193 | if (!uri) { 194 | if (vscode.window.activeTextEditor) { 195 | uri = vscode.window.activeTextEditor.document.uri; 196 | } else { 197 | uri = null; 198 | } 199 | } 200 | return vscode.workspace.getConfiguration('go', uri); 201 | }; 202 | 203 | export function getBinPath(tool: string, useCache = true): string { 204 | const cfg = getGoPlusConfig(); 205 | const alternateTools: { [key: string]: string } = cfg.get('alternateTools'); 206 | const alternateToolPath: string = alternateTools[tool]; 207 | 208 | return getBinPathWithPreferredGopathGoroot( 209 | tool, 210 | tool === 'go' ? [] : [getToolsGopath(), getCurrentGoPath()], 211 | tool === 'go' && cfg.get('goroot') ? cfg.get('goroot') : undefined, 212 | resolvePath(alternateToolPath), 213 | useCache 214 | ); 215 | } 216 | 217 | /** 218 | * Expands ~ to homedir in non-Windows platform and resolves ${workspaceFolder} or ${workspaceRoot} 219 | */ 220 | export function resolvePath(inputPath: string, workspaceFolder?: string): string { 221 | if (!inputPath || !inputPath.trim()) { 222 | return inputPath; 223 | } 224 | 225 | if (!workspaceFolder && vscode.workspace.workspaceFolders) { 226 | workspaceFolder = getWorkspaceFolderPath( 227 | vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.uri 228 | ); 229 | } 230 | 231 | if (workspaceFolder) { 232 | inputPath = inputPath.replace(/\${workspaceFolder}|\${workspaceRoot}/g, workspaceFolder); 233 | } 234 | return resolveHomeDir(inputPath); 235 | } 236 | 237 | export function getWorkspaceFolderPath(fileUri?: vscode.Uri): string { 238 | if (fileUri) { 239 | const workspace = vscode.workspace.getWorkspaceFolder(fileUri); 240 | if (workspace) { 241 | return fixDriveCasingInWindows(workspace.uri.fsPath); 242 | } 243 | } 244 | 245 | // fall back to the first workspace 246 | const folders = vscode.workspace.workspaceFolders; 247 | if (folders && folders.length) { 248 | return fixDriveCasingInWindows(folders[0].uri.fsPath); 249 | } 250 | } 251 | 252 | export function getToolsGopath(useCache: boolean = true): string { 253 | if (!useCache || !toolsGopath) { 254 | toolsGopath = resolveToolsGopath(); 255 | } 256 | return toolsGopath; 257 | } 258 | 259 | function resolveToolsGopath(): string { 260 | let toolsGopathForWorkspace = substituteEnv(getGoConfig()['toolsGopath'] || ''); 261 | 262 | // In case of single root 263 | if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length <= 1) { 264 | return resolvePath(toolsGopathForWorkspace); 265 | } 266 | 267 | // In case of multi-root, resolve ~ and ${workspaceFolder} 268 | if (toolsGopathForWorkspace.startsWith('~')) { 269 | toolsGopathForWorkspace = path.join(os.homedir(), toolsGopathForWorkspace.substr(1)); 270 | } 271 | if ( 272 | toolsGopathForWorkspace && 273 | toolsGopathForWorkspace.trim() && 274 | !/\${workspaceFolder}|\${workspaceRoot}/.test(toolsGopathForWorkspace) 275 | ) { 276 | return toolsGopathForWorkspace; 277 | } 278 | 279 | // If any of the folders in multi root have toolsGopath set, use it. 280 | for (const folder of vscode.workspace.workspaceFolders) { 281 | let toolsGopathFromConfig = getGoConfig(folder.uri).inspect('toolsGopath').workspaceFolderValue; 282 | toolsGopathFromConfig = resolvePath(toolsGopathFromConfig, folder.uri.fsPath); 283 | if (toolsGopathFromConfig) { 284 | return toolsGopathFromConfig; 285 | } 286 | } 287 | } 288 | 289 | let currentGopath = ''; 290 | export function getCurrentGoPath(workspaceUri?: vscode.Uri): string { 291 | const activeEditorUri = vscode.window.activeTextEditor && vscode.window.activeTextEditor.document.uri; 292 | const currentFilePath = fixDriveCasingInWindows(activeEditorUri && activeEditorUri.fsPath); 293 | const currentRoot = (workspaceUri && workspaceUri.fsPath) || getWorkspaceFolderPath(activeEditorUri); 294 | const config = getGoConfig(workspaceUri || activeEditorUri); 295 | 296 | // Infer the GOPATH from the current root or the path of the file opened in current editor 297 | // Last resort: Check for the common case where GOPATH itself is opened directly in VS Code 298 | let inferredGopath: string; 299 | if (config['inferGopath'] === true) { 300 | inferredGopath = getInferredGopath(currentRoot) || getInferredGopath(currentFilePath); 301 | if (!inferredGopath) { 302 | try { 303 | if (fs.statSync(path.join(currentRoot, 'src')).isDirectory()) { 304 | inferredGopath = currentRoot; 305 | } 306 | } catch (e) { 307 | // No op 308 | } 309 | } 310 | if (inferredGopath && process.env['GOPATH'] && inferredGopath !== process.env['GOPATH']) { 311 | inferredGopath += path.delimiter + process.env['GOPATH']; 312 | } 313 | } 314 | 315 | const configGopath = config['gopath'] ? resolvePath(substituteEnv(config['gopath']), currentRoot) : ''; 316 | currentGopath = inferredGopath ? inferredGopath : configGopath || process.env['GOPATH']; 317 | return currentGopath; 318 | } 319 | 320 | export function substituteEnv(input: string): string { 321 | return input.replace(/\${env:([^}]+)}/g, (match, capture) => { 322 | return process.env[capture.trim()] || ''; 323 | }); 324 | } 325 | 326 | export const killTree = (processId: number): void => { 327 | try { 328 | kill(processId, (err) => { 329 | if (err) { 330 | console.log(`Error killing process tree: ${err}`); 331 | } 332 | }); 333 | } catch (err) { 334 | console.log(`Error killing process tree: ${err}`); 335 | } 336 | }; 337 | 338 | export function rmdirRecursive(dir: string) { 339 | if (fs.existsSync(dir)) { 340 | fs.readdirSync(dir).forEach((file) => { 341 | const relPath = path.join(dir, file); 342 | if (fs.lstatSync(relPath).isDirectory()) { 343 | rmdirRecursive(relPath); 344 | } else { 345 | try { 346 | fs.unlinkSync(relPath); 347 | } catch (err) { 348 | console.log(`failed to remove ${relPath}: ${err}`); 349 | } 350 | } 351 | }); 352 | fs.rmdirSync(dir); 353 | } 354 | } 355 | 356 | let tmpDir: string; 357 | /** 358 | * Returns file path for given name in temp dir 359 | * @param name Name of the file 360 | */ 361 | export function getTempFilePath(name: string): string { 362 | if (!tmpDir) { 363 | tmpDir = fs.mkdtempSync(os.tmpdir() + path.sep + 'vscode-go'); 364 | } 365 | 366 | if (!fs.existsSync(tmpDir)) { 367 | fs.mkdirSync(tmpDir); 368 | } 369 | 370 | return path.normalize(path.join(tmpDir, name)); 371 | } 372 | 373 | /** 374 | * Returns a boolean whether the current position lies within a comment or not 375 | * @param document 376 | * @param position 377 | */ 378 | export function isPositionInComment(document: vscode.TextDocument, position: vscode.Position): boolean { 379 | const lineText = document.lineAt(position.line).text; 380 | const commentIndex = lineText.indexOf('//'); 381 | 382 | if (commentIndex >= 0 && position.character > commentIndex) { 383 | const commentPosition = new vscode.Position(position.line, commentIndex); 384 | const isCommentInString = isPositionInString(document, commentPosition); 385 | 386 | return !isCommentInString; 387 | } 388 | return false; 389 | } 390 | 391 | export function isPositionInString(document: vscode.TextDocument, position: vscode.Position): boolean { 392 | const lineText = document.lineAt(position.line).text; 393 | const lineTillCurrentPosition = lineText.substr(0, position.character); 394 | 395 | // Count the number of double quotes in the line till current position. Ignore escaped double quotes 396 | let doubleQuotesCnt = (lineTillCurrentPosition.match(/\"/g) || []).length; 397 | const escapedDoubleQuotesCnt = (lineTillCurrentPosition.match(/\\\"/g) || []).length; 398 | 399 | doubleQuotesCnt -= escapedDoubleQuotesCnt; 400 | return doubleQuotesCnt % 2 === 1; 401 | } 402 | 403 | export function byteOffsetAt(document: vscode.TextDocument, position: vscode.Position): number { 404 | const offset = document.offsetAt(position); 405 | const text = document.getText(); 406 | return Buffer.byteLength(text.substr(0, offset)); 407 | } 408 | 409 | // Takes a Go function signature like: 410 | // (foo, bar string, baz number) (string, string) 411 | // and returns an array of parameter strings: 412 | // ["foo", "bar string", "baz string"] 413 | // Takes care of balancing parens so to not get confused by signatures like: 414 | // (pattern string, handler func(ResponseWriter, *Request)) { 415 | export function getParametersAndReturnType(signature: string): { params: string[]; returnType: string } { 416 | const params: string[] = []; 417 | let parenCount = 0; 418 | let lastStart = 1; 419 | for (let i = 1; i < signature.length; i++) { 420 | switch (signature[i]) { 421 | case '(': 422 | parenCount++; 423 | break; 424 | case ')': 425 | parenCount--; 426 | if (parenCount < 0) { 427 | if (i > lastStart) { 428 | params.push(signature.substring(lastStart, i)); 429 | } 430 | return { 431 | params, 432 | returnType: i < signature.length - 1 ? signature.substr(i + 1) : '' 433 | }; 434 | } 435 | break; 436 | case ',': 437 | if (parenCount === 0) { 438 | params.push(signature.substring(lastStart, i)); 439 | lastStart = i + 2; 440 | } 441 | break; 442 | } 443 | } 444 | return { params: [], returnType: '' }; 445 | } 446 | 447 | export interface Prelude { 448 | imports: Array<{ kind: string; start: number; end: number; pkgs: string[] }>; 449 | pkg: { start: number; end: number; name: string }; 450 | } 451 | 452 | export function parseFilePrelude(text: string): Prelude { 453 | const lines = text.split('\n'); 454 | const ret: Prelude = { imports: [], pkg: null }; 455 | for (let i = 0; i < lines.length; i++) { 456 | const line = lines[i]; 457 | const pkgMatch = line.match(/^(\s)*package(\s)+(\w+)/); 458 | if (pkgMatch) { 459 | ret.pkg = { start: i, end: i, name: pkgMatch[3] }; 460 | } 461 | if (line.match(/^(\s)*import(\s)+\(/)) { 462 | ret.imports.push({ kind: 'multi', start: i, end: -1, pkgs: [] }); 463 | } else if (line.match(/^\s*import\s+"C"/)) { 464 | ret.imports.push({ kind: 'pseudo', start: i, end: i, pkgs: [] }); 465 | } else if (line.match(/^(\s)*import(\s)+[^\(]/)) { 466 | ret.imports.push({ kind: 'single', start: i, end: i, pkgs: [] }); 467 | } 468 | if (line.match(/^(\s)*(\/\*.*\*\/)*\s*\)/)) { // /* comments */ 469 | if (ret.imports[ret.imports.length - 1].end === -1) { 470 | ret.imports[ret.imports.length - 1].end = i; 471 | } 472 | } else if (ret.imports.length) { 473 | if (ret.imports[ret.imports.length - 1].end === -1) { 474 | const importPkgMatch = line.match(/"([^"]+)"/); 475 | if (importPkgMatch) { 476 | ret.imports[ret.imports.length - 1].pkgs.push(importPkgMatch[1]); 477 | } 478 | } 479 | } 480 | 481 | if (line.match(/^(\s)*(func|const|type|var)\s/)) { 482 | break; 483 | } 484 | } 485 | return ret; 486 | } 487 | 488 | 489 | /** 490 | * Runs `go doc` to get documentation for given symbol 491 | * @param cwd The cwd where the go doc process will be run 492 | * @param packagePath Either the absolute path or import path of the package. 493 | * @param symbol Symbol for which docs need to be found 494 | * @param token Cancellation token 495 | */ 496 | export function runGodoc( 497 | cwd: string, 498 | packagePath: string, 499 | receiver: string, 500 | symbol: string, 501 | token: vscode.CancellationToken 502 | ) { 503 | if (!packagePath) { 504 | return Promise.reject(new Error('Package Path not provided')); 505 | } 506 | if (!symbol) { 507 | return Promise.reject(new Error('Symbol not provided')); 508 | } 509 | 510 | 511 | const goRuntimePath = getBinPath('go'); 512 | if (!goRuntimePath) { 513 | return Promise.reject(new Error('Cannot find "go" binary. Update PATH or GOROOT appropriately')); 514 | } 515 | 516 | const getCurrentPackagePromise = path.isAbsolute(packagePath) 517 | ? getCurrentPackage(packagePath) 518 | : Promise.resolve(packagePath); 519 | return getCurrentPackagePromise.then((packageImportPath) => { 520 | return new Promise((resolve, reject) => { 521 | if (receiver) { 522 | receiver = receiver.replace(/^\*/, ''); 523 | symbol = receiver + '.' + symbol; 524 | } 525 | 526 | const env = toolExecutionEnvironment(); 527 | const args = ['doc', '-c', '-cmd', '-u', packageImportPath, symbol]; 528 | console.log(goRuntimePath,args) 529 | const p = cp.execFile(goRuntimePath, args, { env, cwd }, (err, stdout, stderr) => { 530 | if (err) { 531 | return reject(err.message || stderr); 532 | } 533 | let doc = ''; 534 | const godocLines = stdout.split('\n'); 535 | if (!godocLines.length) { 536 | return resolve(doc); 537 | } 538 | 539 | // Recent versions of Go have started to include the package statement 540 | // tht we dont need. 541 | if (godocLines[0].startsWith('package ')) { 542 | godocLines.splice(0, 1); 543 | if (!godocLines[0].trim()) { 544 | godocLines.splice(0, 1); 545 | } 546 | } 547 | 548 | // Skip trailing empty lines 549 | let lastLine = godocLines.length - 1; 550 | for (; lastLine > 1; lastLine--) { 551 | if (godocLines[lastLine].trim()) { 552 | break; 553 | } 554 | } 555 | 556 | for (let i = 1; i <= lastLine; i++) { 557 | if (godocLines[i].startsWith(' ')) { 558 | doc += godocLines[i].substring(4) + '\n'; 559 | } else if (!godocLines[i].trim()) { 560 | doc += '\n'; 561 | } 562 | } 563 | return resolve(doc); 564 | }); 565 | 566 | if (token) { 567 | token.onCancellationRequested(() => { 568 | killTree(p.pid); 569 | }); 570 | } 571 | }); 572 | }); 573 | } 574 | 575 | /** 576 | * Guess the package name based on parent directory name of the given file 577 | * 578 | * Cases: 579 | * - dir 'go-i18n' -> 'i18n' 580 | * - dir 'go-spew' -> 'spew' 581 | * - dir 'kingpin' -> 'kingpin' 582 | * - dir 'go-expand-tilde' -> 'tilde' 583 | * - dir 'gax-go' -> 'gax' 584 | * - dir 'go-difflib' -> 'difflib' 585 | * - dir 'jwt-go' -> 'jwt' 586 | * - dir 'go-radix' -> 'radix' 587 | * 588 | * @param {string} filePath. 589 | */ 590 | export function guessPackageNameFromFile(filePath: string): Promise { 591 | return new Promise((resolve, reject) => { 592 | const goFilename = path.basename(filePath); 593 | if (goFilename === 'main.go') { 594 | return resolve(['main']); 595 | } 596 | 597 | const directoryPath = path.dirname(filePath); 598 | const dirName = path.basename(directoryPath); 599 | let segments = dirName.split(/[\.-]/); 600 | segments = segments.filter((val) => val !== 'go'); 601 | 602 | if (segments.length === 0 || !/[a-zA-Z_]\w*/.test(segments[segments.length - 1])) { 603 | return reject(); 604 | } 605 | 606 | const proposedPkgName = segments[segments.length - 1]; 607 | 608 | fs.stat(path.join(directoryPath, 'main.go'), (err, stats) => { 609 | if (stats && stats.isFile()) { 610 | return resolve(['main']); 611 | } 612 | 613 | if (goFilename.endsWith('_test.go')) { 614 | return resolve([proposedPkgName, proposedPkgName + '_test']); 615 | } 616 | 617 | return resolve([proposedPkgName]); 618 | }); 619 | }); 620 | } 621 | 622 | export function getModuleCache(): string { 623 | if (currentGopath) { 624 | return path.join(currentGopath.split(path.delimiter)[0], 'pkg', 'mod'); 625 | } 626 | } 627 | 628 | export function getFileArchive(document: vscode.TextDocument): string { 629 | const fileContents = document.getText(); 630 | return document.fileName + '\n' + Buffer.byteLength(fileContents, 'utf8') + '\n' + fileContents; 631 | } 632 | 633 | export function killProcess(p: cp.ChildProcess) { 634 | if (p) { 635 | try { 636 | p.kill(); 637 | } catch (e) { 638 | console.log('Error killing process: ' + e); 639 | } 640 | } 641 | } 642 | 643 | export function makeMemoizedByteOffsetConverter(buffer: Buffer): (byteOffset: number) => number { 644 | const defaultValue = new Node(0, 0); // 0 bytes will always be 0 characters 645 | const memo = new NearestNeighborDict(defaultValue, NearestNeighborDict.NUMERIC_DISTANCE_FUNCTION); 646 | return (byteOffset: number) => { 647 | const nearest = memo.getNearest(byteOffset); 648 | const byteDelta = byteOffset - nearest.key; 649 | 650 | if (byteDelta === 0) { 651 | return nearest.value; 652 | } 653 | 654 | let charDelta: number; 655 | if (byteDelta > 0) { 656 | charDelta = buffer.toString('utf8', nearest.key, byteOffset).length; 657 | } else { 658 | charDelta = -buffer.toString('utf8', byteOffset, nearest.key).length; 659 | } 660 | 661 | memo.insert(byteOffset, nearest.value + charDelta); 662 | return nearest.value + charDelta; 663 | }; 664 | } -------------------------------------------------------------------------------- /syntaxes/gop.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopeName": "source.gop", 3 | "comment": "GoPlus language", 4 | "fileTypes": [ 5 | "gop", 6 | "gmx" 7 | ], 8 | "foldingStartMarker": "(?x)/\\*\\*(?!\\*)|^(?![^{]*?//|[^{]*?/\\*(?!.*?\\*/.*?\\{)).*?\\{\\s*($|//|/\\*(?!.*?\\*/.*\\S))", 9 | "foldingStopMarker": "(?<!\\*)\\*\\*/|^\\s*\\}", 10 | "keyEquivalent": "^~G", 11 | "name": "GoPlus", 12 | "patterns": [ 13 | { 14 | "include": "#comments" 15 | }, 16 | { 17 | "comment": "Interpreted string literals", 18 | "begin": "\"", 19 | "beginCaptures": { 20 | "0": { 21 | "name": "punctuation.definition.string.begin.gop" 22 | } 23 | }, 24 | "end": "\"", 25 | "endCaptures": { 26 | "0": { 27 | "name": "punctuation.definition.string.end.gop" 28 | } 29 | }, 30 | "name": "string.quoted.double.gop", 31 | "patterns": [ 32 | { 33 | "include": "#string_escaped_char" 34 | }, 35 | { 36 | "include": "#string_placeholder" 37 | } 38 | ] 39 | }, 40 | { 41 | "comment": "Raw string literals", 42 | "begin": "`", 43 | "beginCaptures": { 44 | "0": { 45 | "name": "punctuation.definition.string.begin.gop" 46 | } 47 | }, 48 | "end": "`", 49 | "endCaptures": { 50 | "0": { 51 | "name": "punctuation.definition.string.end.gop" 52 | } 53 | }, 54 | "name": "string.quoted.raw.gop", 55 | "patterns": [ 56 | { 57 | "include": "#string_placeholder" 58 | } 59 | ] 60 | }, 61 | { 62 | "comment": "Syntax error receiving channels", 63 | "match": "<\\-([\\t ]+)chan\\b", 64 | "captures": { 65 | "1": { 66 | "name": "invalid.illegal.receive-channel.gop" 67 | } 68 | } 69 | }, 70 | { 71 | "comment": "Syntax error sending channels", 72 | "match": "\\bchan([\\t ]+)<-", 73 | "captures": { 74 | "1": { 75 | "name": "invalid.illegal.send-channel.gop" 76 | } 77 | } 78 | }, 79 | { 80 | "comment": "Syntax error using slices", 81 | "match": "\\[\\](\\s+)", 82 | "captures": { 83 | "1": { 84 | "name": "invalid.illegal.slice.gop" 85 | } 86 | } 87 | }, 88 | { 89 | "comment": "Syntax error numeric literals", 90 | "match": "\\b0[0-7]*[89]\\d*\\b", 91 | "name": "invalid.illegal.numeric.gop" 92 | }, 93 | { 94 | "comment": "Built-in functions", 95 | "match": "\\b(append|cap|close|complex|copy|delete|imag|len|make|new|panic|print|println|real|recover)\\b(?=\\()", 96 | "name": "support.function.builtin.gop" 97 | }, 98 | { 99 | "comment": "Function declarations", 100 | "match": "^(\\bfunc\\b)(?:\\s+(\\([^\\)]+\\)\\s+)?(\\w+)(?=\\())?", 101 | "captures": { 102 | "1": { 103 | "name": "keyword.function.gop" 104 | }, 105 | "2": { 106 | "patterns": [ 107 | { 108 | "include": "#brackets" 109 | }, 110 | { 111 | "include": "#operators" 112 | } 113 | ] 114 | }, 115 | "3": { 116 | "patterns": [ 117 | { 118 | "match": "\\d\\w*", 119 | "name": "invalid.illegal.identifier.gop" 120 | }, 121 | { 122 | "match": "\\w+", 123 | "name": "entity.name.function.gop" 124 | } 125 | ] 126 | } 127 | } 128 | }, 129 | { 130 | "comment": "Functions", 131 | "match": "(\\bfunc\\b)|(\\w+)(?=\\()", 132 | "captures": { 133 | "1": { 134 | "name": "keyword.function.gop" 135 | }, 136 | "2": { 137 | "patterns": [ 138 | { 139 | "match": "\\d\\w*", 140 | "name": "invalid.illegal.identifier.gop" 141 | }, 142 | { 143 | "match": "\\w+", 144 | "name": "support.function.gop" 145 | } 146 | ] 147 | } 148 | } 149 | }, 150 | { 151 | "comment": "Floating-point literals", 152 | "match": "(\\.\\d+([Ee][-+]\\d+)?i?)\\b|\\b\\d+\\.\\d*(([Ee][-+]\\d+)?i?\\b)?", 153 | "name": "constant.numeric.floating-point.gop" 154 | }, 155 | { 156 | "comment": "Integers", 157 | "match": "\\b((0x[0-9a-fA-F]+)|(0[0-7]+i?)|(\\d+([Ee]\\d+)?i?)|(\\d+[Ee][-+]\\d+i?))\\b", 158 | "name": "constant.numeric.integer.gop" 159 | }, 160 | { 161 | "comment": "Language constants", 162 | "match": "\\b(true|false|nil|iota)\\b", 163 | "name": "constant.language.gop" 164 | }, 165 | { 166 | "begin": "\\b(package)\\s+", 167 | "beginCaptures": { 168 | "1": { 169 | "name": "keyword.package.gop" 170 | } 171 | }, 172 | "end": "(?!\\G)", 173 | "patterns": [ 174 | { 175 | "match": "\\d\\w*", 176 | "name": "invalid.illegal.identifier.gop" 177 | }, 178 | { 179 | "match": "\\w+", 180 | "name": "entity.name.package.gop" 181 | } 182 | ] 183 | }, 184 | { 185 | "begin": "\\b(type)\\s+", 186 | "beginCaptures": { 187 | "1": { 188 | "name": "keyword.type.gop" 189 | } 190 | }, 191 | "end": "(?!\\G)", 192 | "patterns": [ 193 | { 194 | "match": "\\d\\w*", 195 | "name": "invalid.illegal.identifier.gop" 196 | }, 197 | { 198 | "match": "\\w+", 199 | "name": "entity.name.type.gop" 200 | } 201 | ] 202 | }, 203 | { 204 | "begin": "\\b(import)\\s+", 205 | "beginCaptures": { 206 | "1": { 207 | "name": "keyword.import.gop" 208 | } 209 | }, 210 | "end": "(?!\\G)", 211 | "patterns": [ 212 | { 213 | "include": "#imports" 214 | } 215 | ] 216 | }, 217 | { 218 | "begin": "\\b(var)\\s+", 219 | "beginCaptures": { 220 | "1": { 221 | "name": "keyword.var.gop" 222 | } 223 | }, 224 | "end": "(?!\\G)", 225 | "patterns": [ 226 | { 227 | "include": "#variables" 228 | } 229 | ] 230 | }, 231 | { 232 | "match": "(?,\\s*\\w+(?:\\.\\w+)*)*)(?=\\s*=(?!=))", 233 | "captures": { 234 | "1": { 235 | "patterns": [ 236 | { 237 | "match": "\\d\\w*", 238 | "name": "invalid.illegal.identifier.gop" 239 | }, 240 | { 241 | "match": "\\w+(?:\\.\\w+)*", 242 | "name": "variable.other.assignment.gop", 243 | "captures": { 244 | "0": { 245 | "patterns": [ 246 | { 247 | "include": "#delimiters" 248 | } 249 | ] 250 | } 251 | } 252 | }, 253 | { 254 | "include": "#delimiters" 255 | } 256 | ] 257 | } 258 | } 259 | }, 260 | { 261 | "match": "\\w+(?:,\\s*\\w+)*(?=\\s*:=)", 262 | "captures": { 263 | "0": { 264 | "patterns": [ 265 | { 266 | "match": "\\d\\w*", 267 | "name": "invalid.illegal.identifier.gop" 268 | }, 269 | { 270 | "match": "\\w+", 271 | "name": "variable.other.assignment.gop" 272 | }, 273 | { 274 | "include": "#delimiters" 275 | } 276 | ] 277 | } 278 | } 279 | }, 280 | { 281 | "comment": "Terminators", 282 | "match": ";", 283 | "name": "punctuation.terminator.gop" 284 | }, 285 | { 286 | "include": "#brackets" 287 | }, 288 | { 289 | "include": "#delimiters" 290 | }, 291 | { 292 | "include": "#keywords" 293 | }, 294 | { 295 | "include": "#operators" 296 | }, 297 | { 298 | "include": "#runes" 299 | }, 300 | { 301 | "include": "#storage_types" 302 | } 303 | ], 304 | "repository": { 305 | "brackets": { 306 | "patterns": [ 307 | { 308 | "begin": "{", 309 | "beginCaptures": { 310 | "0": { 311 | "name": "punctuation.definition.begin.bracket.curly.gop" 312 | } 313 | }, 314 | "end": "}", 315 | "endCaptures": { 316 | "0": { 317 | "name": "punctuation.definition.end.bracket.curly.gop" 318 | } 319 | }, 320 | "patterns": [ 321 | { 322 | "include": "$self" 323 | } 324 | ] 325 | }, 326 | { 327 | "begin": "\\(", 328 | "beginCaptures": { 329 | "0": { 330 | "name": "punctuation.definition.begin.bracket.round.gop" 331 | } 332 | }, 333 | "end": "\\)", 334 | "endCaptures": { 335 | "0": { 336 | "name": "punctuation.definition.end.bracket.round.gop" 337 | } 338 | }, 339 | "patterns": [ 340 | { 341 | "include": "$self" 342 | } 343 | ] 344 | }, 345 | { 346 | "match": "\\[|\\]", 347 | "name": "punctuation.definition.bracket.square.gop" 348 | } 349 | ] 350 | }, 351 | "comments": { 352 | "patterns": [ 353 | { 354 | "begin": "/\\*", 355 | "end": "\\*/", 356 | "captures": { 357 | "0": { 358 | "name": "punctuation.definition.comment.gop" 359 | } 360 | }, 361 | "name": "comment.block.gop" 362 | }, 363 | { 364 | "begin": "//", 365 | "beginCaptures": { 366 | "0": { 367 | "name": "punctuation.definition.comment.gop" 368 | } 369 | }, 370 | "end": "$", 371 | "name": "comment.line.double-slash.gop" 372 | } 373 | ] 374 | }, 375 | "delimiters": { 376 | "patterns": [ 377 | { 378 | "match": ",", 379 | "name": "punctuation.other.comma.gop" 380 | }, 381 | { 382 | "match": "\\.(?!\\.\\.)", 383 | "name": "punctuation.other.period.gop" 384 | }, 385 | { 386 | "match": ":(?!=)", 387 | "name": "punctuation.other.colon.gop" 388 | } 389 | ] 390 | }, 391 | "imports": { 392 | "patterns": [ 393 | { 394 | "match": "((?!\\s+\")[^\\s]*)?\\s*((\")([^\"]*)(\"))", 395 | "captures": { 396 | "1": { 397 | "name": "entity.alias.import.gop" 398 | }, 399 | "2": { 400 | "name": "string.quoted.double.gop" 401 | }, 402 | "3": { 403 | "name": "punctuation.definition.string.begin.gop" 404 | }, 405 | "4": { 406 | "name": "entity.name.import.gop" 407 | }, 408 | "5": { 409 | "name": "punctuation.definition.string.end.gop" 410 | } 411 | } 412 | }, 413 | { 414 | "begin": "\\(", 415 | "beginCaptures": { 416 | "0": { 417 | "name": "punctuation.definition.imports.begin.bracket.round.gop" 418 | } 419 | }, 420 | "end": "\\)", 421 | "endCaptures": { 422 | "0": { 423 | "name": "punctuation.definition.imports.end.bracket.round.gop" 424 | } 425 | }, 426 | "patterns": [ 427 | { 428 | "include": "#comments" 429 | }, 430 | { 431 | "include": "#imports" 432 | } 433 | ] 434 | } 435 | ] 436 | }, 437 | "keywords": { 438 | "patterns": [ 439 | { 440 | "comment": "Flow control keywords", 441 | "match": "\\b(break|case|continue|default|defer|else|fallthrough|for|go|goto|if|range|return|select|switch)\\b", 442 | "name": "keyword.control.gop" 443 | }, 444 | { 445 | "match": "\\bchan\\b", 446 | "name": "keyword.channel.gop" 447 | }, 448 | { 449 | "match": "\\bconst\\b", 450 | "name": "keyword.const.gop" 451 | }, 452 | { 453 | "match": "\\bfunc\\b", 454 | "name": "keyword.function.gop" 455 | }, 456 | { 457 | "match": "\\binterface\\b", 458 | "name": "keyword.interface.gop" 459 | }, 460 | { 461 | "match": "\\bmap\\b", 462 | "name": "keyword.map.gop" 463 | }, 464 | { 465 | "match": "\\bstruct\\b", 466 | "name": "keyword.struct.gop" 467 | } 468 | ] 469 | }, 470 | "operators": { 471 | "comment": "Note that the order here is very important!", 472 | "patterns": [ 473 | { 474 | "match": "(\\*|&)(?=\\w)", 475 | "name": "keyword.operator.address.gop" 476 | }, 477 | { 478 | "match": "<\\-", 479 | "name": "keyword.operator.channel.gop" 480 | }, 481 | { 482 | "match": "\\-\\-", 483 | "name": "keyword.operator.decrement.gop" 484 | }, 485 | { 486 | "match": "\\+\\+", 487 | "name": "keyword.operator.increment.gop" 488 | }, 489 | { 490 | "match": "(==|!=|<=|>=|<(?!<)|>(?!>))", 491 | "name": "keyword.operator.comparison.gop" 492 | }, 493 | { 494 | "match": "(&&|\\|\\||!)", 495 | "name": "keyword.operator.logical.gop" 496 | }, 497 | { 498 | "match": "(=|\\+=|\\-=|\\|=|\\^=|\\*=|/=|:=|%=|<<=|>>=|&\\^=|&=)", 499 | "name": "keyword.operator.assignment.gop" 500 | }, 501 | { 502 | "match": "(\\+|\\-|\\*|/|%)", 503 | "name": "keyword.operator.arithmetic.gop" 504 | }, 505 | { 506 | "match": "(&(?!\\^)|\\||\\^|&\\^|<<|>>)", 507 | "name": "keyword.operator.arithmetic.bitwise.gop" 508 | }, 509 | { 510 | "match": "\\.\\.\\.", 511 | "name": "keyword.operator.ellipsis.gop" 512 | } 513 | ] 514 | }, 515 | "runes": { 516 | "patterns": [ 517 | { 518 | "begin": "'", 519 | "beginCaptures": { 520 | "0": { 521 | "name": "punctuation.definition.string.begin.gop" 522 | } 523 | }, 524 | "end": "'", 525 | "endCaptures": { 526 | "0": { 527 | "name": "punctuation.definition.string.end.gop" 528 | } 529 | }, 530 | "name": "string.quoted.rune.gop", 531 | "patterns": [ 532 | { 533 | "match": "\\G(\\\\([0-7]{3}|[abfnrtv\\\\'\"]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})|.)(?=')", 534 | "name": "constant.other.rune.gop" 535 | }, 536 | { 537 | "match": "[^']+", 538 | "name": "invalid.illegal.unknown-rune.gop" 539 | } 540 | ] 541 | } 542 | ] 543 | }, 544 | "storage_types": { 545 | "patterns": [ 546 | { 547 | "match": "\\bbool\\b", 548 | "name": "storage.type.boolean.go" 549 | }, 550 | { 551 | "match": "\\bbyte\\b", 552 | "name": "storage.type.byte.go" 553 | }, 554 | { 555 | "match": "\\berror\\b", 556 | "name": "storage.type.error.go" 557 | }, 558 | { 559 | "match": "\\b(complex(64|128)|float(32|64)|u?int(8|16|32|64)?)\\b", 560 | "name": "storage.type.numeric.go" 561 | }, 562 | { 563 | "match": "\\brune\\b", 564 | "name": "storage.type.rune.go" 565 | }, 566 | { 567 | "match": "\\bstring\\b", 568 | "name": "storage.type.string.go" 569 | }, 570 | { 571 | "match": "\\buintptr\\b", 572 | "name": "storage.type.uintptr.go" 573 | } 574 | ] 575 | }, 576 | "string_escaped_char": { 577 | "patterns": [ 578 | { 579 | "match": "\\\\([0-7]{3}|[abfnrtv\\\\'\"]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})", 580 | "name": "constant.character.escape.gop" 581 | }, 582 | { 583 | "match": "\\\\[^0-7xuUabfnrtv\\'\"]", 584 | "name": "invalid.illegal.unknown-escape.gop" 585 | } 586 | ] 587 | }, 588 | "string_placeholder": { 589 | "patterns": [ 590 | { 591 | "match": "%(\\[\\d+\\])?([\\+#\\-0\\x20]{,2}((\\d+|\\*)?(\\.?(\\d+|\\*|(\\[\\d+\\])\\*?)?(\\[\\d+\\])?)?))?[vT%tbcdoqxXUbeEfFgGsp]", 592 | "name": "constant.other.placeholder.gop" 593 | } 594 | ] 595 | }, 596 | "variables": { 597 | "patterns": [ 598 | { 599 | "match": "(\\w+(?:,\\s*\\w+)*)(\\s+\\*?\\w+(?:\\.\\w+)?\\s*)?(?=\\s*=)", 600 | "captures": { 601 | "1": { 602 | "patterns": [ 603 | { 604 | "match": "\\d\\w*", 605 | "name": "invalid.illegal.identifier.gop" 606 | }, 607 | { 608 | "match": "\\w+", 609 | "name": "variable.other.assignment.gop" 610 | }, 611 | { 612 | "include": "#delimiters" 613 | } 614 | ] 615 | }, 616 | "2": { 617 | "patterns": [ 618 | { 619 | "include": "$self" 620 | } 621 | ] 622 | } 623 | } 624 | }, 625 | { 626 | "match": "(\\w+(?:,\\s*\\w+)*)(\\s+(\\[(\\d*|\\.\\.\\.)\\])*\\*?(<-)?\\w+(?:\\.\\w+)?\\s*[^=].*)", 627 | "captures": { 628 | "1": { 629 | "patterns": [ 630 | { 631 | "match": "\\d\\w*", 632 | "name": "invalid.illegal.identifier.gop" 633 | }, 634 | { 635 | "match": "\\w+", 636 | "name": "variable.other.declaration.gop" 637 | }, 638 | { 639 | "include": "#delimiters" 640 | } 641 | ] 642 | }, 643 | "2": { 644 | "patterns": [ 645 | { 646 | "include": "$self" 647 | } 648 | ] 649 | } 650 | } 651 | }, 652 | { 653 | "begin": "\\(", 654 | "beginCaptures": { 655 | "0": { 656 | "name": "punctuation.definition.variables.begin.bracket.round.gop" 657 | } 658 | }, 659 | "end": "\\)", 660 | "endCaptures": { 661 | "0": { 662 | "name": "punctuation.definition.variables.end.bracket.round.gop" 663 | } 664 | }, 665 | "patterns": [ 666 | { 667 | "include": "$self" 668 | }, 669 | { 670 | "include": "#variables" 671 | } 672 | ] 673 | } 674 | ] 675 | } 676 | } 677 | } -------------------------------------------------------------------------------- /syntaxes/spx.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "scopeName": "source.spx", 3 | "comment": "GoPlus language", 4 | "fileTypes": [ 5 | "spx" 6 | ], 7 | "foldingStartMarker": "(?x)/\\*\\*(?!\\*)|^(?![^{]*?//|[^{]*?/\\*(?!.*?\\*/.*?\\{)).*?\\{\\s*($|//|/\\*(?!.*?\\*/.*\\S))", 8 | "foldingStopMarker": "(?<!\\*)\\*\\*/|^\\s*\\}", 9 | "keyEquivalent": "^~G", 10 | "name": "GoPlus", 11 | "patterns": [ 12 | { 13 | "include": "#comments" 14 | }, 15 | { 16 | "comment": "Interpreted string literals", 17 | "begin": "\"", 18 | "beginCaptures": { 19 | "0": { 20 | "name": "punctuation.definition.string.begin.gop" 21 | } 22 | }, 23 | "end": "\"", 24 | "endCaptures": { 25 | "0": { 26 | "name": "punctuation.definition.string.end.gop" 27 | } 28 | }, 29 | "name": "string.quoted.double.gop", 30 | "patterns": [ 31 | { 32 | "include": "#string_escaped_char" 33 | }, 34 | { 35 | "include": "#string_placeholder" 36 | } 37 | ] 38 | }, 39 | { 40 | "comment": "Raw string literals", 41 | "begin": "`", 42 | "beginCaptures": { 43 | "0": { 44 | "name": "punctuation.definition.string.begin.gop" 45 | } 46 | }, 47 | "end": "`", 48 | "endCaptures": { 49 | "0": { 50 | "name": "punctuation.definition.string.end.gop" 51 | } 52 | }, 53 | "name": "string.quoted.raw.gop", 54 | "patterns": [ 55 | { 56 | "include": "#string_placeholder" 57 | } 58 | ] 59 | }, 60 | { 61 | "comment": "Syntax error receiving channels", 62 | "match": "<\\-([\\t ]+)chan\\b", 63 | "captures": { 64 | "1": { 65 | "name": "invalid.illegal.receive-channel.gop" 66 | } 67 | } 68 | }, 69 | { 70 | "comment": "Syntax error sending channels", 71 | "match": "\\bchan([\\t ]+)<-", 72 | "captures": { 73 | "1": { 74 | "name": "invalid.illegal.send-channel.gop" 75 | } 76 | } 77 | }, 78 | { 79 | "comment": "Syntax error using slices", 80 | "match": "\\[\\](\\s+)", 81 | "captures": { 82 | "1": { 83 | "name": "invalid.illegal.slice.gop" 84 | } 85 | } 86 | }, 87 | { 88 | "comment": "Syntax error numeric literals", 89 | "match": "\\b0[0-7]*[89]\\d*\\b", 90 | "name": "invalid.illegal.numeric.gop" 91 | }, 92 | { 93 | "comment": "Built-in functions", 94 | "match": "\\b(append|cap|close|complex|copy|delete|imag|len|make|new|panic|print|println|real|recover)\\b", 95 | "name": "support.function.builtin.gop" 96 | }, 97 | { 98 | "comment": "Spx Built-in functions", 99 | "match": "\\b(step|move|turn|changeHeading|goto|setXYpos|glide|turnTo|setHeading|changeXpos|setXpos|changeYpos|setYpos|changeXYpos|bounceOffEdge|setRotationStyle|xpos|ypos|heading|say|think|setCostume|nextCostume|prevCostume|animate|startScene|nextScene|prevScene|changeSize|setSize|changeEffect|setEffect|clearGraphEffects|show|hide|gotoFront|gotoBack|goBackLayers|costumeIndex|costumeName|size|visible|width|height|play|stopAllSounds|changeEffect|setEffect|clearSoundEffect|changeVolume|setVolume|volume|onStart|onAnyKey|onKey|onClick|onAnyScene|onMsg|onMoving|onTurning|broadcast|wait|onClone|clone|Sprite|destroy|die|setDying|stopped|cloned|touching|onTouched|touchingColor|distanceTo|ask|answer|keyPressed|mousePressed|mouseX|mouseY|timer|resetTimer|username|clear|stamp|penDown|penUp|setPenColor|changePenSize|setPenSize)\\b", 100 | "name": "support.function.builtin.spx" 101 | }, 102 | { 103 | "comment": "Function declarations", 104 | "match": "^(\\bfunc\\b)(?:\\s+(\\([^\\)]+\\)\\s+)?(\\w+)(?=\\())?", 105 | "captures": { 106 | "1": { 107 | "name": "keyword.function.gop" 108 | }, 109 | "2": { 110 | "patterns": [ 111 | { 112 | "include": "#brackets" 113 | }, 114 | { 115 | "include": "#operators" 116 | } 117 | ] 118 | }, 119 | "3": { 120 | "patterns": [ 121 | { 122 | "match": "\\d\\w*", 123 | "name": "invalid.illegal.identifier.gop" 124 | }, 125 | { 126 | "match": "\\w+", 127 | "name": "entity.name.function.gop" 128 | } 129 | ] 130 | } 131 | } 132 | }, 133 | { 134 | "comment": "Functions", 135 | "match": "(\\bfunc\\b)|(\\w+)(?=\\()", 136 | "captures": { 137 | "1": { 138 | "name": "keyword.function.gop" 139 | }, 140 | "2": { 141 | "patterns": [ 142 | { 143 | "match": "\\d\\w*", 144 | "name": "invalid.illegal.identifier.gop" 145 | }, 146 | { 147 | "match": "\\w+", 148 | "name": "support.function.gop" 149 | } 150 | ] 151 | } 152 | } 153 | }, 154 | { 155 | "comment": "Floating-point literals", 156 | "match": "(\\.\\d+([Ee][-+]\\d+)?i?)\\b|\\b\\d+\\.\\d*(([Ee][-+]\\d+)?i?\\b)?", 157 | "name": "constant.numeric.floating-point.gop" 158 | }, 159 | { 160 | "comment": "Integers", 161 | "match": "\\b((0x[0-9a-fA-F]+)|(0[0-7]+i?)|(\\d+([Ee]\\d+)?i?)|(\\d+[Ee][-+]\\d+i?))\\b", 162 | "name": "constant.numeric.integer.gop" 163 | }, 164 | { 165 | "comment": "Language constants", 166 | "match": "\\b(true|false|nil|iota)\\b", 167 | "name": "constant.language.gop" 168 | }, 169 | { 170 | "begin": "\\b(package)\\s+", 171 | "beginCaptures": { 172 | "1": { 173 | "name": "keyword.package.gop" 174 | } 175 | }, 176 | "end": "(?!\\G)", 177 | "patterns": [ 178 | { 179 | "match": "\\d\\w*", 180 | "name": "invalid.illegal.identifier.gop" 181 | }, 182 | { 183 | "match": "\\w+", 184 | "name": "entity.name.package.gop" 185 | } 186 | ] 187 | }, 188 | { 189 | "begin": "\\b(type)\\s+", 190 | "beginCaptures": { 191 | "1": { 192 | "name": "keyword.type.gop" 193 | } 194 | }, 195 | "end": "(?!\\G)", 196 | "patterns": [ 197 | { 198 | "match": "\\d\\w*", 199 | "name": "invalid.illegal.identifier.gop" 200 | }, 201 | { 202 | "match": "\\w+", 203 | "name": "entity.name.type.gop" 204 | } 205 | ] 206 | }, 207 | { 208 | "begin": "\\b(import)\\s+", 209 | "beginCaptures": { 210 | "1": { 211 | "name": "keyword.import.gop" 212 | } 213 | }, 214 | "end": "(?!\\G)", 215 | "patterns": [ 216 | { 217 | "include": "#imports" 218 | } 219 | ] 220 | }, 221 | { 222 | "begin": "\\b(var)\\s+", 223 | "beginCaptures": { 224 | "1": { 225 | "name": "keyword.var.gop" 226 | } 227 | }, 228 | "end": "(?!\\G)", 229 | "patterns": [ 230 | { 231 | "include": "#variables" 232 | } 233 | ] 234 | }, 235 | { 236 | "match": "(?,\\s*\\w+(?:\\.\\w+)*)*)(?=\\s*=(?!=))", 237 | "captures": { 238 | "1": { 239 | "patterns": [ 240 | { 241 | "match": "\\d\\w*", 242 | "name": "invalid.illegal.identifier.gop" 243 | }, 244 | { 245 | "match": "\\w+(?:\\.\\w+)*", 246 | "name": "variable.other.assignment.gop", 247 | "captures": { 248 | "0": { 249 | "patterns": [ 250 | { 251 | "include": "#delimiters" 252 | } 253 | ] 254 | } 255 | } 256 | }, 257 | { 258 | "include": "#delimiters" 259 | } 260 | ] 261 | } 262 | } 263 | }, 264 | { 265 | "match": "\\w+(?:,\\s*\\w+)*(?=\\s*:=)", 266 | "captures": { 267 | "0": { 268 | "patterns": [ 269 | { 270 | "match": "\\d\\w*", 271 | "name": "invalid.illegal.identifier.gop" 272 | }, 273 | { 274 | "match": "\\w+", 275 | "name": "variable.other.assignment.gop" 276 | }, 277 | { 278 | "include": "#delimiters" 279 | } 280 | ] 281 | } 282 | } 283 | }, 284 | { 285 | "comment": "Terminators", 286 | "match": ";", 287 | "name": "punctuation.terminator.gop" 288 | }, 289 | { 290 | "include": "#brackets" 291 | }, 292 | { 293 | "include": "#delimiters" 294 | }, 295 | { 296 | "include": "#keywords" 297 | }, 298 | { 299 | "include": "#operators" 300 | }, 301 | { 302 | "include": "#runes" 303 | }, 304 | { 305 | "include": "#storage_types" 306 | } 307 | ], 308 | "repository": { 309 | "brackets": { 310 | "patterns": [ 311 | { 312 | "begin": "{", 313 | "beginCaptures": { 314 | "0": { 315 | "name": "punctuation.definition.begin.bracket.curly.gop" 316 | } 317 | }, 318 | "end": "}", 319 | "endCaptures": { 320 | "0": { 321 | "name": "punctuation.definition.end.bracket.curly.gop" 322 | } 323 | }, 324 | "patterns": [ 325 | { 326 | "include": "$self" 327 | } 328 | ] 329 | }, 330 | { 331 | "begin": "\\(", 332 | "beginCaptures": { 333 | "0": { 334 | "name": "punctuation.definition.begin.bracket.round.gop" 335 | } 336 | }, 337 | "end": "\\)", 338 | "endCaptures": { 339 | "0": { 340 | "name": "punctuation.definition.end.bracket.round.gop" 341 | } 342 | }, 343 | "patterns": [ 344 | { 345 | "include": "$self" 346 | } 347 | ] 348 | }, 349 | { 350 | "match": "\\[|\\]", 351 | "name": "punctuation.definition.bracket.square.gop" 352 | } 353 | ] 354 | }, 355 | "comments": { 356 | "patterns": [ 357 | { 358 | "begin": "/\\*", 359 | "end": "\\*/", 360 | "captures": { 361 | "0": { 362 | "name": "punctuation.definition.comment.gop" 363 | } 364 | }, 365 | "name": "comment.block.gop" 366 | }, 367 | { 368 | "begin": "//", 369 | "beginCaptures": { 370 | "0": { 371 | "name": "punctuation.definition.comment.gop" 372 | } 373 | }, 374 | "end": "$", 375 | "name": "comment.line.double-slash.gop" 376 | } 377 | ] 378 | }, 379 | "delimiters": { 380 | "patterns": [ 381 | { 382 | "match": ",", 383 | "name": "punctuation.other.comma.gop" 384 | }, 385 | { 386 | "match": "\\.(?!\\.\\.)", 387 | "name": "punctuation.other.period.gop" 388 | }, 389 | { 390 | "match": ":(?!=)", 391 | "name": "punctuation.other.colon.gop" 392 | } 393 | ] 394 | }, 395 | "imports": { 396 | "patterns": [ 397 | { 398 | "match": "((?!\\s+\")[^\\s]*)?\\s*((\")([^\"]*)(\"))", 399 | "captures": { 400 | "1": { 401 | "name": "entity.alias.import.gop" 402 | }, 403 | "2": { 404 | "name": "string.quoted.double.gop" 405 | }, 406 | "3": { 407 | "name": "punctuation.definition.string.begin.gop" 408 | }, 409 | "4": { 410 | "name": "entity.name.import.gop" 411 | }, 412 | "5": { 413 | "name": "punctuation.definition.string.end.gop" 414 | } 415 | } 416 | }, 417 | { 418 | "begin": "\\(", 419 | "beginCaptures": { 420 | "0": { 421 | "name": "punctuation.definition.imports.begin.bracket.round.gop" 422 | } 423 | }, 424 | "end": "\\)", 425 | "endCaptures": { 426 | "0": { 427 | "name": "punctuation.definition.imports.end.bracket.round.gop" 428 | } 429 | }, 430 | "patterns": [ 431 | { 432 | "include": "#comments" 433 | }, 434 | { 435 | "include": "#imports" 436 | } 437 | ] 438 | } 439 | ] 440 | }, 441 | "keywords": { 442 | "patterns": [ 443 | { 444 | "comment": "Flow control keywords", 445 | "match": "\\b(break|case|continue|default|defer|else|fallthrough|for|go|goto|if|range|return|select|switch)\\b", 446 | "name": "keyword.control.gop" 447 | }, 448 | { 449 | "match": "\\bchan\\b", 450 | "name": "keyword.channel.gop" 451 | }, 452 | { 453 | "match": "\\bconst\\b", 454 | "name": "keyword.const.gop" 455 | }, 456 | { 457 | "match": "\\bfunc\\b", 458 | "name": "keyword.function.gop" 459 | }, 460 | { 461 | "match": "\\binterface\\b", 462 | "name": "keyword.interface.gop" 463 | }, 464 | { 465 | "match": "\\bmap\\b", 466 | "name": "keyword.map.gop" 467 | }, 468 | { 469 | "match": "\\bstruct\\b", 470 | "name": "keyword.struct.gop" 471 | } 472 | ] 473 | }, 474 | "operators": { 475 | "comment": "Note that the order here is very important!", 476 | "patterns": [ 477 | { 478 | "match": "(\\*|&)(?=\\w)", 479 | "name": "keyword.operator.address.gop" 480 | }, 481 | { 482 | "match": "<\\-", 483 | "name": "keyword.operator.channel.gop" 484 | }, 485 | { 486 | "match": "\\-\\-", 487 | "name": "keyword.operator.decrement.gop" 488 | }, 489 | { 490 | "match": "\\+\\+", 491 | "name": "keyword.operator.increment.gop" 492 | }, 493 | { 494 | "match": "(==|!=|<=|>=|<(?!<)|>(?!>))", 495 | "name": "keyword.operator.comparison.gop" 496 | }, 497 | { 498 | "match": "(&&|\\|\\||!)", 499 | "name": "keyword.operator.logical.gop" 500 | }, 501 | { 502 | "match": "(=|\\+=|\\-=|\\|=|\\^=|\\*=|/=|:=|%=|<<=|>>=|&\\^=|&=)", 503 | "name": "keyword.operator.assignment.gop" 504 | }, 505 | { 506 | "match": "(\\+|\\-|\\*|/|%)", 507 | "name": "keyword.operator.arithmetic.gop" 508 | }, 509 | { 510 | "match": "(&(?!\\^)|\\||\\^|&\\^|<<|>>)", 511 | "name": "keyword.operator.arithmetic.bitwise.gop" 512 | }, 513 | { 514 | "match": "\\.\\.\\.", 515 | "name": "keyword.operator.ellipsis.gop" 516 | } 517 | ] 518 | }, 519 | "runes": { 520 | "patterns": [ 521 | { 522 | "begin": "'", 523 | "beginCaptures": { 524 | "0": { 525 | "name": "punctuation.definition.string.begin.gop" 526 | } 527 | }, 528 | "end": "'", 529 | "endCaptures": { 530 | "0": { 531 | "name": "punctuation.definition.string.end.gop" 532 | } 533 | }, 534 | "name": "string.quoted.rune.gop", 535 | "patterns": [ 536 | { 537 | "match": "\\G(\\\\([0-7]{3}|[abfnrtv\\\\'\"]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})|.)(?=')", 538 | "name": "constant.other.rune.gop" 539 | }, 540 | { 541 | "match": "[^']+", 542 | "name": "invalid.illegal.unknown-rune.gop" 543 | } 544 | ] 545 | } 546 | ] 547 | }, 548 | "storage_types": { 549 | "patterns": [ 550 | { 551 | "match": "\\bbool\\b", 552 | "name": "storage.type.boolean.go" 553 | }, 554 | { 555 | "match": "\\bbyte\\b", 556 | "name": "storage.type.byte.go" 557 | }, 558 | { 559 | "match": "\\berror\\b", 560 | "name": "storage.type.error.go" 561 | }, 562 | { 563 | "match": "\\b(complex(64|128)|float(32|64)|u?int(8|16|32|64)?)\\b", 564 | "name": "storage.type.numeric.go" 565 | }, 566 | { 567 | "match": "\\brune\\b", 568 | "name": "storage.type.rune.go" 569 | }, 570 | { 571 | "match": "\\bstring\\b", 572 | "name": "storage.type.string.go" 573 | }, 574 | { 575 | "match": "\\buintptr\\b", 576 | "name": "storage.type.uintptr.go" 577 | } 578 | ] 579 | }, 580 | "string_escaped_char": { 581 | "patterns": [ 582 | { 583 | "match": "\\\\([0-7]{3}|[abfnrtv\\\\'\"]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})", 584 | "name": "constant.character.escape.gop" 585 | }, 586 | { 587 | "match": "\\\\[^0-7xuUabfnrtv\\'\"]", 588 | "name": "invalid.illegal.unknown-escape.gop" 589 | } 590 | ] 591 | }, 592 | "string_placeholder": { 593 | "patterns": [ 594 | { 595 | "match": "%(\\[\\d+\\])?([\\+#\\-0\\x20]{,2}((\\d+|\\*)?(\\.?(\\d+|\\*|(\\[\\d+\\])\\*?)?(\\[\\d+\\])?)?))?[vT%tbcdoqxXUbeEfFgGsp]", 596 | "name": "constant.other.placeholder.gop" 597 | } 598 | ] 599 | }, 600 | "variables": { 601 | "patterns": [ 602 | { 603 | "match": "(\\w+(?:,\\s*\\w+)*)(\\s+\\*?\\w+(?:\\.\\w+)?\\s*)?(?=\\s*=)", 604 | "captures": { 605 | "1": { 606 | "patterns": [ 607 | { 608 | "match": "\\d\\w*", 609 | "name": "invalid.illegal.identifier.gop" 610 | }, 611 | { 612 | "match": "\\w+", 613 | "name": "variable.other.assignment.gop" 614 | }, 615 | { 616 | "include": "#delimiters" 617 | } 618 | ] 619 | }, 620 | "2": { 621 | "patterns": [ 622 | { 623 | "include": "$self" 624 | } 625 | ] 626 | } 627 | } 628 | }, 629 | { 630 | "match": "(\\w+(?:,\\s*\\w+)*)(\\s+(\\[(\\d*|\\.\\.\\.)\\])*\\*?(<-)?\\w+(?:\\.\\w+)?\\s*[^=].*)", 631 | "captures": { 632 | "1": { 633 | "patterns": [ 634 | { 635 | "match": "\\d\\w*", 636 | "name": "invalid.illegal.identifier.gop" 637 | }, 638 | { 639 | "match": "\\w+", 640 | "name": "variable.other.declaration.gop" 641 | }, 642 | { 643 | "include": "#delimiters" 644 | } 645 | ] 646 | }, 647 | "2": { 648 | "patterns": [ 649 | { 650 | "include": "$self" 651 | } 652 | ] 653 | } 654 | } 655 | }, 656 | { 657 | "begin": "\\(", 658 | "beginCaptures": { 659 | "0": { 660 | "name": "punctuation.definition.variables.begin.bracket.round.gop" 661 | } 662 | }, 663 | "end": "\\)", 664 | "endCaptures": { 665 | "0": { 666 | "name": "punctuation.definition.variables.end.bracket.round.gop" 667 | } 668 | }, 669 | "patterns": [ 670 | { 671 | "include": "$self" 672 | }, 673 | { 674 | "include": "#variables" 675 | } 676 | ] 677 | } 678 | ] 679 | } 680 | } 681 | } -------------------------------------------------------------------------------- /third_party/README.md: -------------------------------------------------------------------------------- 1 | # Vendored dependencies 2 | 3 | third_party directory contains code from the third party including 4 | vendored modules that need local modifications (e.g. bug fixes or 5 | necessary enhancement before they are incorporated and released 6 | in the upstream). Every directory must contain LICENSE files. 7 | 8 | The vendored node modules still need to be specified in the dependencies. 9 | For example, after copying the `tree-kill` module to this directory 10 | and applying necessary local modification, run from the root of this 11 | project directory: 12 | 13 | ``` 14 | $ npm install --save ./third_party/tree-kill 15 | 16 | ``` 17 | 18 | This will update `package.json` and `package-lock.json` to point to 19 | the local dependency. 20 | 21 | Note: We didn't test vendoring platform-dependent modules yet. 22 | 23 | 24 | ## List of local modification 25 | 26 | `tree-kill`: vendored 1.2.2 with a fix for https://github.com/golang/vscode-go/issues/90 27 | -------------------------------------------------------------------------------- /third_party/tree-kill/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Peter Krumins 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /third_party/tree-kill/README.md: -------------------------------------------------------------------------------- 1 | Tree Kill 2 | ========= 3 | 4 | Kill all processes in the process tree, including the root process. 5 | 6 | Examples 7 | ======= 8 | 9 | Kill all the descendent processes of the process with pid `1`, including the process with pid `1` itself: 10 | ```js 11 | var kill = require('tree-kill'); 12 | kill(1); 13 | ``` 14 | 15 | Send a signal other than SIGTERM.: 16 | ```js 17 | var kill = require('tree-kill'); 18 | kill(1, 'SIGKILL'); 19 | ``` 20 | 21 | Run a callback when done killing the processes. Passes an error argument if there was an error. 22 | ```js 23 | var kill = require('tree-kill'); 24 | kill(1, 'SIGKILL', function(err) { 25 | // Do things 26 | }); 27 | ``` 28 | 29 | You can also install tree-kill globally and use it as a command: 30 | ```sh 31 | tree-kill 1 # sends SIGTERM to process 1 and its descendents 32 | tree-kill 1 SIGTERM # same 33 | tree-kill 1 SIGKILL # sends KILL instead of TERMINATE 34 | ``` 35 | 36 | Methods 37 | ======= 38 | 39 | ## require('tree-kill')(pid, [signal], [callback]); 40 | 41 | Sends signal `signal` to all children processes of the process with pid `pid`, including `pid`. Signal defaults to `SIGTERM`. 42 | 43 | For Linux, this uses `ps -o pid --no-headers --ppid PID` to find the parent pids of `PID`. 44 | 45 | For Darwin/OSX, this uses `pgrep -P PID` to find the parent pids of `PID`. 46 | 47 | For Windows, this uses `'taskkill /pid PID /T /F'` to kill the process tree. Note that on Windows, sending the different kinds of POSIX signals is not possible. 48 | 49 | Install 50 | ======= 51 | 52 | With [npm](https://npmjs.org) do: 53 | 54 | ``` 55 | npm install tree-kill 56 | ``` 57 | 58 | License 59 | ======= 60 | 61 | MIT 62 | 63 | Changelog 64 | ========= 65 | 66 | 67 | ## [1.2.2] - 2019-12-11 68 | ### Changed 69 | - security fix: sanitize `pid` parameter to fix arbitrary code execution vulnerability 70 | 71 | ## [1.2.1] - 2018-11-05 72 | ### Changed 73 | - added missing LICENSE file 74 | - updated TypeScript definitions 75 | 76 | ## [1.2.0] - 2017-09-19 77 | ### Added 78 | - TypeScript definitions 79 | ### Changed 80 | - `kill(pid, callback)` works. Before you had to use `kill(pid, signal, callback)` 81 | 82 | ## [1.1.0] - 2016-05-13 83 | ### Added 84 | - A `tree-kill` CLI 85 | 86 | ## [1.0.0] - 2015-09-17 87 | ### Added 88 | - optional callback 89 | - Darwin support 90 | -------------------------------------------------------------------------------- /third_party/tree-kill/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | kill = require('.') 3 | try { 4 | kill(process.argv[2], process.argv[3], function(err){ 5 | if (err) { 6 | console.log(err.message) 7 | process.exit(1) 8 | } 9 | }) 10 | } 11 | catch (err) { 12 | console.log(err.message) 13 | process.exit(1) 14 | } 15 | -------------------------------------------------------------------------------- /third_party/tree-kill/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Kills process identified by `pid` and all its children 3 | * 4 | * @param pid 5 | * @param signal 'SIGTERM' by default 6 | * @param callback 7 | */ 8 | declare function treeKill(pid: number, callback?: (error?: Error) => void): void; 9 | declare function treeKill(pid: number, signal?: string | number, callback?: (error?: Error) => void): void; 10 | 11 | declare namespace treeKill {} 12 | 13 | export = treeKill; 14 | -------------------------------------------------------------------------------- /third_party/tree-kill/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var childProcess = require('child_process'); 4 | const { existsSync } = require('fs'); 5 | var spawn = childProcess.spawn; 6 | var exec = childProcess.exec; 7 | 8 | module.exports = function (pid, signal, callback) { 9 | if (typeof signal === 'function' && callback === undefined) { 10 | callback = signal; 11 | signal = undefined; 12 | } 13 | 14 | pid = parseInt(pid); 15 | if (Number.isNaN(pid)) { 16 | if (callback) { 17 | return callback(new Error("pid must be a number")); 18 | } else { 19 | throw new Error("pid must be a number"); 20 | } 21 | } 22 | 23 | var tree = {}; 24 | var pidsToProcess = {}; 25 | tree[pid] = []; 26 | pidsToProcess[pid] = 1; 27 | 28 | switch (process.platform) { 29 | case 'win32': 30 | exec('taskkill /pid ' + pid + ' /T /F', callback); 31 | break; 32 | case 'darwin': 33 | buildProcessTree(pid, tree, pidsToProcess, function (parentPid) { 34 | return spawn(pathToPgrep(), ['-P', parentPid]); 35 | }, function () { 36 | killAll(tree, signal, callback); 37 | }); 38 | break; 39 | // case 'sunos': 40 | // buildProcessTreeSunOS(pid, tree, pidsToProcess, function () { 41 | // killAll(tree, signal, callback); 42 | // }); 43 | // break; 44 | default: // Linux 45 | buildProcessTree(pid, tree, pidsToProcess, function (parentPid) { 46 | return spawn('ps', ['-o', 'pid', '--no-headers', '--ppid', parentPid]); 47 | }, function () { 48 | killAll(tree, signal, callback); 49 | }); 50 | break; 51 | } 52 | }; 53 | 54 | function killAll (tree, signal, callback) { 55 | var killed = {}; 56 | try { 57 | Object.keys(tree).forEach(function (pid) { 58 | tree[pid].forEach(function (pidpid) { 59 | if (!killed[pidpid]) { 60 | killPid(pidpid, signal); 61 | killed[pidpid] = 1; 62 | } 63 | }); 64 | if (!killed[pid]) { 65 | killPid(pid, signal); 66 | killed[pid] = 1; 67 | } 68 | }); 69 | } catch (err) { 70 | if (callback) { 71 | return callback(err); 72 | } else { 73 | throw err; 74 | } 75 | } 76 | if (callback) { 77 | return callback(); 78 | } 79 | } 80 | 81 | function killPid(pid, signal) { 82 | try { 83 | process.kill(parseInt(pid, 10), signal); 84 | } 85 | catch (err) { 86 | if (err.code !== 'ESRCH') throw err; 87 | } 88 | } 89 | 90 | function buildProcessTree (parentPid, tree, pidsToProcess, spawnChildProcessesList, cb) { 91 | var ps = spawnChildProcessesList(parentPid); 92 | var allData = ''; 93 | ps.stdout.on('data', function (data) { 94 | var data = data.toString('ascii'); 95 | allData += data; 96 | }); 97 | 98 | var onClose = function (code) { 99 | delete pidsToProcess[parentPid]; 100 | 101 | if (code != 0) { 102 | // no more parent processes 103 | if (Object.keys(pidsToProcess).length == 0) { 104 | cb(); 105 | } 106 | return; 107 | } 108 | 109 | allData.match(/\d+/g).forEach(function (pid) { 110 | pid = parseInt(pid, 10); 111 | tree[parentPid].push(pid); 112 | tree[pid] = []; 113 | pidsToProcess[pid] = 1; 114 | buildProcessTree(pid, tree, pidsToProcess, spawnChildProcessesList, cb); 115 | }); 116 | }; 117 | 118 | ps.on('close', onClose); 119 | } 120 | 121 | var pgrep = ''; 122 | function pathToPgrep () { 123 | if (pgrep) { 124 | return pgrep; 125 | } 126 | // Use the default pgrep, available since os x mountain lion. 127 | // proctools' pgrep does not implement `-P` correctly and returns 128 | // unrelated processes. 129 | // https://github.com/golang/vscode-go/issues/90#issuecomment-634430428 130 | try { 131 | pgrep = existsSync('/usr/bin/pgrep') ? '/usr/bin/pgrep' : 'pgrep'; 132 | } catch (e) { 133 | pgrep = 'pgrep'; 134 | } 135 | return pgrep; 136 | } -------------------------------------------------------------------------------- /third_party/tree-kill/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_args": [ 3 | [ 4 | "tree-kill@1.2.2", 5 | "/Users/hakim/projects/google/vscode-go" 6 | ] 7 | ], 8 | "_from": "tree-kill@1.2.2", 9 | "_id": "tree-kill@1.2.2", 10 | "_inBundle": false, 11 | "_integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", 12 | "_location": "/tree-kill", 13 | "_phantomChildren": {}, 14 | "_requested": { 15 | "type": "version", 16 | "registry": true, 17 | "raw": "tree-kill@1.2.2", 18 | "name": "tree-kill", 19 | "escapedName": "tree-kill", 20 | "rawSpec": "1.2.2", 21 | "saveSpec": null, 22 | "fetchSpec": "1.2.2" 23 | }, 24 | "_requiredBy": [ 25 | "/" 26 | ], 27 | "_resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", 28 | "_spec": "1.2.2", 29 | "_where": "/Users/hakim/projects/google/vscode-go", 30 | "author": { 31 | "name": "Peteris Krumins", 32 | "email": "peteris.krumins@gmail.com", 33 | "url": "http://www.catonmat.net" 34 | }, 35 | "bin": { 36 | "tree-kill": "cli.js" 37 | }, 38 | "bugs": { 39 | "url": "https://github.com/pkrumins/node-tree-kill/issues" 40 | }, 41 | "contributors": [ 42 | { 43 | "name": "Todd Wolfson", 44 | "email": "todd@twolfson.com", 45 | "url": "http://twolfson.com/" 46 | }, 47 | { 48 | "name": "William Hilton", 49 | "email": "wmhilton@gmail.com", 50 | "url": "http://wmhilton.com/" 51 | }, 52 | { 53 | "name": "Fabrício Matté", 54 | "url": "http://ultcombo.js.org/" 55 | } 56 | ], 57 | "description": "kill trees of processes", 58 | "devDependencies": { 59 | "mocha": "^2.2.5" 60 | }, 61 | "homepage": "https://github.com/pkrumins/node-tree-kill", 62 | "keywords": [ 63 | "tree", 64 | "trees", 65 | "process", 66 | "processes", 67 | "kill", 68 | "signal" 69 | ], 70 | "license": "MIT", 71 | "main": "index.js", 72 | "name": "tree-kill", 73 | "repository": { 74 | "type": "git", 75 | "url": "git://github.com/pkrumins/node-tree-kill.git" 76 | }, 77 | "scripts": { 78 | "test": "mocha" 79 | }, 80 | "types": "index.d.ts", 81 | "version": "1.2.2" 82 | } 83 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "outDir": "out", 5 | "sourceMap": true, 6 | "target": "es6", 7 | "lib": [ 8 | "es2015" 9 | ], 10 | //"strict": true, 11 | "noImplicitAny": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "strictBindCallApply": true, 15 | "strictFunctionTypes": true, 16 | //"strictNullChecks": true, 17 | //"strictPropertyInitialization": true, 18 | }, 19 | "exclude": [ 20 | "node_modules", 21 | "third_party" 22 | ] 23 | } --------------------------------------------------------------------------------