├── .github ├── stale.yml └── workflows │ └── node.js.yml ├── .gitignore ├── .ignore ├── .markdownlint.yaml ├── .node-version ├── .yamllint.yaml ├── LICENSE ├── Readme.md ├── eslint.config.mjs ├── package-lock.json ├── package.json ├── server └── groovy-language-server-all.jar ├── src ├── classpath.ts ├── client.ts ├── commands.ts ├── constants.ts ├── context.ts ├── index.ts ├── requirements.ts ├── server.ts ├── settings.ts ├── system.ts └── typings.d.ts ├── tsconfig.json ├── utils ├── .DS_Store ├── gradle-classpath │ ├── bin │ │ ├── gradle-classpath │ │ └── gradle-classpath.bat │ └── lib │ │ ├── gradle-classpath-1.3.0.jar │ │ ├── gradle-tooling-api-8.1.1.jar │ │ ├── picocli-4.7.4.jar │ │ ├── slf4j-api-1.7.36.jar │ │ └── slf4j-simple-1.7.36.jar ├── update-gradle-classpath └── update_server └── webpack.config.js /.github/stale.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # Number of days of inactivity before an issue becomes stale 3 | daysUntilStale: 30 4 | # Number of days of inactivity before a stale issue is closed 5 | daysUntilClose: 7 6 | # Issues with these labels will never be considered stale 7 | exemptLabels: 8 | - pinned 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: > 18 | This issue has been automatically closed due to lack of activity. 19 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 2 | --- 3 | name: Node.js CI 4 | 5 | on: 6 | push: 7 | branches: [main] 8 | pull_request: 9 | branches: [main] 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [18.x, 20.x, 22.x] 18 | 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Use Node.js ${{ matrix.node-version }} 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | cache: npm 26 | - name: Install dependencies 27 | run: npm ci 28 | - name: Run Linters 29 | run: npm run lint 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules 3 | .vim/ 4 | -------------------------------------------------------------------------------- /.ignore: -------------------------------------------------------------------------------- 1 | server 2 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | extends: 'markdownlint/style/prettier' 3 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /.yamllint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 2.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION 5 | OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial content 12 | Distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | i) changes to the Program, and 16 | ii) additions to the Program; 17 | where such changes and/or additions to the Program originate from 18 | and are Distributed by that particular Contributor. A Contribution 19 | "originates" from a Contributor if it was added to the Program by 20 | such Contributor itself or anyone acting on such Contributor's behalf. 21 | Contributions do not include changes or additions to the Program that 22 | are not Modified Works. 23 | 24 | "Contributor" means any person or entity that Distributes the Program. 25 | 26 | "Licensed Patents" mean patent claims licensable by a Contributor which 27 | are necessarily infringed by the use or sale of its Contribution alone 28 | or when combined with the Program. 29 | 30 | "Program" means the Contributions Distributed in accordance with this 31 | Agreement. 32 | 33 | "Recipient" means anyone who receives the Program under this Agreement 34 | or any Secondary License (as applicable), including Contributors. 35 | 36 | "Derivative Works" shall mean any work, whether in Source Code or other 37 | form, that is based on (or derived from) the Program and for which the 38 | editorial revisions, annotations, elaborations, or other modifications 39 | represent, as a whole, an original work of authorship. 40 | 41 | "Modified Works" shall mean any work in Source Code or other form that 42 | results from an addition to, deletion from, or modification of the 43 | contents of the Program, including, for purposes of clarity any new file 44 | in Source Code form that contains any contents of the Program. Modified 45 | Works shall not include works that contain only declarations, 46 | interfaces, types, classes, structures, or files of the Program solely 47 | in each case in order to link to, bind by name, or subclass the Program 48 | or Modified Works thereof. 49 | 50 | "Distribute" means the acts of a) distributing or b) making available 51 | in any manner that enables the transfer of a copy. 52 | 53 | "Source Code" means the form of a Program preferred for making 54 | modifications, including but not limited to software source code, 55 | documentation source, and configuration files. 56 | 57 | "Secondary License" means either the GNU General Public License, 58 | Version 2.0, or any later versions of that license, including any 59 | exceptions or additional permissions as identified by the initial 60 | Contributor. 61 | 62 | 2. GRANT OF RIGHTS 63 | 64 | a) Subject to the terms of this Agreement, each Contributor hereby 65 | grants Recipient a non-exclusive, worldwide, royalty-free copyright 66 | license to reproduce, prepare Derivative Works of, publicly display, 67 | publicly perform, Distribute and sublicense the Contribution of such 68 | Contributor, if any, and such Derivative Works. 69 | 70 | b) Subject to the terms of this Agreement, each Contributor hereby 71 | grants Recipient a non-exclusive, worldwide, royalty-free patent 72 | license under Licensed Patents to make, use, sell, offer to sell, 73 | import and otherwise transfer the Contribution of such Contributor, 74 | if any, in Source Code or other form. This patent license shall 75 | apply to the combination of the Contribution and the Program if, at 76 | the time the Contribution is added by the Contributor, such addition 77 | of the Contribution causes such combination to be covered by the 78 | Licensed Patents. The patent license shall not apply to any other 79 | combinations which include the Contribution. No hardware per se is 80 | licensed hereunder. 81 | 82 | c) Recipient understands that although each Contributor grants the 83 | licenses to its Contributions set forth herein, no assurances are 84 | provided by any Contributor that the Program does not infringe the 85 | patent or other intellectual property rights of any other entity. 86 | Each Contributor disclaims any liability to Recipient for claims 87 | brought by any other entity based on infringement of intellectual 88 | property rights or otherwise. As a condition to exercising the 89 | rights and licenses granted hereunder, each Recipient hereby 90 | assumes sole responsibility to secure any other intellectual 91 | property rights needed, if any. For example, if a third party 92 | patent license is required to allow Recipient to Distribute the 93 | Program, it is Recipient's responsibility to acquire that license 94 | before distributing the Program. 95 | 96 | d) Each Contributor represents that to its knowledge it has 97 | sufficient copyright rights in its Contribution, if any, to grant 98 | the copyright license set forth in this Agreement. 99 | 100 | e) Notwithstanding the terms of any Secondary License, no 101 | Contributor makes additional grants to any Recipient (other than 102 | those set forth in this Agreement) as a result of such Recipient's 103 | receipt of the Program under the terms of a Secondary License 104 | (if permitted under the terms of Section 3). 105 | 106 | 3. REQUIREMENTS 107 | 108 | 3.1 If a Contributor Distributes the Program in any form, then: 109 | 110 | a) the Program must also be made available as Source Code, in 111 | accordance with section 3.2, and the Contributor must accompany 112 | the Program with a statement that the Source Code for the Program 113 | is available under this Agreement, and informs Recipients how to 114 | obtain it in a reasonable manner on or through a medium customarily 115 | used for software exchange; and 116 | 117 | b) the Contributor may Distribute the Program under a license 118 | different than this Agreement, provided that such license: 119 | i) effectively disclaims on behalf of all other Contributors all 120 | warranties and conditions, express and implied, including 121 | warranties or conditions of title and non-infringement, and 122 | implied warranties or conditions of merchantability and fitness 123 | for a particular purpose; 124 | 125 | ii) effectively excludes on behalf of all other Contributors all 126 | liability for damages, including direct, indirect, special, 127 | incidental and consequential damages, such as lost profits; 128 | 129 | iii) does not attempt to limit or alter the recipients' rights 130 | in the Source Code under section 3.2; and 131 | 132 | iv) requires any subsequent distribution of the Program by any 133 | party to be under a license that satisfies the requirements 134 | of this section 3. 135 | 136 | 3.2 When the Program is Distributed as Source Code: 137 | 138 | a) it must be made available under this Agreement, or if the 139 | Program (i) is combined with other material in a separate file or 140 | files made available under a Secondary License, and (ii) the initial 141 | Contributor attached to the Source Code the notice described in 142 | Exhibit A of this Agreement, then the Program may be made available 143 | under the terms of such Secondary Licenses, and 144 | 145 | b) a copy of this Agreement must be included with each copy of 146 | the Program. 147 | 148 | 3.3 Contributors may not remove or alter any copyright, patent, 149 | trademark, attribution notices, disclaimers of warranty, or limitations 150 | of liability ("notices") contained within the Program from any copy of 151 | the Program which they Distribute, provided that Contributors may add 152 | their own appropriate notices. 153 | 154 | 4. COMMERCIAL DISTRIBUTION 155 | 156 | Commercial distributors of software may accept certain responsibilities 157 | with respect to end users, business partners and the like. While this 158 | license is intended to facilitate the commercial use of the Program, 159 | the Contributor who includes the Program in a commercial product 160 | offering should do so in a manner which does not create potential 161 | liability for other Contributors. Therefore, if a Contributor includes 162 | the Program in a commercial product offering, such Contributor 163 | ("Commercial Contributor") hereby agrees to defend and indemnify every 164 | other Contributor ("Indemnified Contributor") against any losses, 165 | damages and costs (collectively "Losses") arising from claims, lawsuits 166 | and other legal actions brought by a third party against the Indemnified 167 | Contributor to the extent caused by the acts or omissions of such 168 | Commercial Contributor in connection with its distribution of the Program 169 | in a commercial product offering. The obligations in this section do not 170 | apply to any claims or Losses relating to any actual or alleged 171 | intellectual property infringement. In order to qualify, an Indemnified 172 | Contributor must: a) promptly notify the Commercial Contributor in 173 | writing of such claim, and b) allow the Commercial Contributor to control, 174 | and cooperate with the Commercial Contributor in, the defense and any 175 | related settlement negotiations. The Indemnified Contributor may 176 | participate in any such claim at its own expense. 177 | 178 | For example, a Contributor might include the Program in a commercial 179 | product offering, Product X. That Contributor is then a Commercial 180 | Contributor. If that Commercial Contributor then makes performance 181 | claims, or offers warranties related to Product X, those performance 182 | claims and warranties are such Commercial Contributor's responsibility 183 | alone. Under this section, the Commercial Contributor would have to 184 | defend claims against the other Contributors related to those performance 185 | claims and warranties, and if a court requires any other Contributor to 186 | pay any damages as a result, the Commercial Contributor must pay 187 | those damages. 188 | 189 | 5. NO WARRANTY 190 | 191 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 192 | PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" 193 | BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR 194 | IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF 195 | TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 196 | PURPOSE. Each Recipient is solely responsible for determining the 197 | appropriateness of using and distributing the Program and assumes all 198 | risks associated with its exercise of rights under this Agreement, 199 | including but not limited to the risks and costs of program errors, 200 | compliance with applicable laws, damage to or loss of data, programs 201 | or equipment, and unavailability or interruption of operations. 202 | 203 | 6. DISCLAIMER OF LIABILITY 204 | 205 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT 206 | PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS 207 | SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 208 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST 209 | PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 210 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 211 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 212 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE 213 | POSSIBILITY OF SUCH DAMAGES. 214 | 215 | 7. GENERAL 216 | 217 | If any provision of this Agreement is invalid or unenforceable under 218 | applicable law, it shall not affect the validity or enforceability of 219 | the remainder of the terms of this Agreement, and without further 220 | action by the parties hereto, such provision shall be reformed to the 221 | minimum extent necessary to make such provision valid and enforceable. 222 | 223 | If Recipient institutes patent litigation against any entity 224 | (including a cross-claim or counterclaim in a lawsuit) alleging that the 225 | Program itself (excluding combinations of the Program with other software 226 | or hardware) infringes such Recipient's patent(s), then such Recipient's 227 | rights granted under Section 2(b) shall terminate as of the date such 228 | litigation is filed. 229 | 230 | All Recipient's rights under this Agreement shall terminate if it 231 | fails to comply with any of the material terms or conditions of this 232 | Agreement and does not cure such failure in a reasonable period of 233 | time after becoming aware of such noncompliance. If all Recipient's 234 | rights under this Agreement terminate, Recipient agrees to cease use 235 | and distribution of the Program as soon as reasonably practicable. 236 | However, Recipient's obligations under this Agreement and any licenses 237 | granted by Recipient relating to the Program shall continue and survive. 238 | 239 | Everyone is permitted to copy and distribute copies of this Agreement, 240 | but in order to avoid inconsistency the Agreement is copyrighted and 241 | may only be modified in the following manner. The Agreement Steward 242 | reserves the right to publish new versions (including revisions) of 243 | this Agreement from time to time. No one other than the Agreement 244 | Steward has the right to modify this Agreement. The Eclipse Foundation 245 | is the initial Agreement Steward. The Eclipse Foundation may assign the 246 | responsibility to serve as the Agreement Steward to a suitable separate 247 | entity. Each new version of the Agreement will be given a distinguishing 248 | version number. The Program (including Contributions) may always be 249 | Distributed subject to the version of the Agreement under which it was 250 | received. In addition, after a new version of the Agreement is published, 251 | Contributor may elect to Distribute the Program (including its 252 | Contributions) under the new version. 253 | 254 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient 255 | receives no rights or licenses to the intellectual property of any 256 | Contributor under this Agreement, whether expressly, by implication, 257 | estoppel or otherwise. All rights in the Program not expressly granted 258 | under this Agreement are reserved. Nothing in this Agreement is intended 259 | to be enforceable by any entity that is not a Contributor or Recipient. 260 | No third-party beneficiary rights are created under this Agreement. 261 | 262 | Exhibit A - Form of Secondary Licenses Notice 263 | 264 | "This Source Code may also be made available under the following 265 | Secondary Licenses when the conditions for such availability set forth 266 | in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), 267 | version(s), and exceptions or additional permissions here}." 268 | 269 | Simply including a copy of this Agreement, including this Exhibit A 270 | is not sufficient to license the Source Code under Secondary Licenses. 271 | 272 | If it is not possible or desirable to put the notice in a particular 273 | file, then You may include the notice in a location (such as a LICENSE 274 | file in a relevant directory) where a recipient would be likely to 275 | look for such a notice. 276 | 277 | You may add additional accurate notices of copyright ownership. 278 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # coc-groovy 2 | 3 | An [extension for coc.nvim](https://github.com/neoclide/coc.nvim/wiki/Using-coc-extensions) to enable 4 | [Groovy language server](https://github.com/prominic/groovy-language-server) support. 5 | 6 | ## Features 7 | 8 | - Maven and Gradle project support 9 | - Code Completion 10 | - Find References 11 | - Go to Definition 12 | - Highlights 13 | - Refactor Rename 14 | - Signature Hover 15 | 16 | ## Quick Start 17 | 18 | 1. Download and install a recent Java Development Kit (latest Java 8 is the minimum requirement). 19 | 2. Install this extension by running this command in Vim: 20 | 21 | ```sh 22 | :CocInstall coc-groovy 23 | ``` 24 | 25 | 3. This extension is activated when you first open a Groovy file. 26 | 27 | ## Dependencies 28 | 29 | ### Maven Projects 30 | 31 | Maven project support requires having the [Maven Wrapper][0] installed in your 32 | project or having [Maven][1] installed on your environment path. 33 | 34 | - [Maven Wrapper][0] 35 | - [Installing Maven][1] 36 | 37 | ## Available commands 38 | 39 | The following coc.nvim commands are available: 40 | 41 | - `groovy.project.config.update` : This is available when the editor is focused on a Groovy file. It forces project configuration/classpath updates (eg. dependency changes) according to the project build descriptor. 42 | 43 | ## Supported settings 44 | 45 | The following settings are supported: 46 | 47 | - `groovy.enable` : Enable the coc-groovy extension, default: `true` 48 | - `groovy.java.home` : The absolute path to the JDK 8+ home directory. This is used to launch the Groovy language server. Requires a coc server restart. 49 | - `groovy.ls.vmargs` : Extra Java VM arguments used to launch the Groovy language server. Requires a coc server restart. 50 | - `groovy.ls.home` : The absolute path to the Groovy language server. This would be used instead of the bundled server when specified. 51 | - `groovy.project.referencedLibraries` : Configure additional paths (jar file or directory) for referencing libraries in a Groovy project. Note, Maven and Gradle projects will have their classpath automatically added. 52 | - Example: `["/path/to/lib.jar", "/path/to/lib/*"]` 53 | - `groovy.trace.server` : Traces the communication between the coc-groovy extension and the Groovy language server. 54 | 55 | ## Setting the JDK 56 | 57 | The path to the Java Development Kit is searched in the following order: 58 | 59 | 1. The `groovy.java.home` setting in coc.nvim settings (workspace then user settings). 60 | 2. The `JDK_HOME` environment variable. 61 | 3. The `JAVA_HOME` environment variable. 62 | 4. The current system path. 63 | 64 | ## License 65 | 66 | EPL 2.0, See [LICENSE](LICENSE) for more information. 67 | 68 | [0]: https://maven.apache.org/wrapper/ 69 | [1]: https://maven.apache.org/install.html 70 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import tseslint from '@typescript-eslint/eslint-plugin'; 2 | import tsparser from '@typescript-eslint/parser'; 3 | import prettierPlugin from 'eslint-plugin-prettier'; 4 | import prettierConfig from 'eslint-config-prettier'; 5 | 6 | export default [ 7 | { 8 | files: ['src/**/*.ts'], 9 | 10 | languageOptions: { 11 | parser: tsparser, 12 | sourceType: 'module', 13 | }, 14 | 15 | plugins: { 16 | '@typescript-eslint': tseslint, 17 | prettier: prettierPlugin, 18 | }, 19 | 20 | rules: { 21 | ...tseslint.configs.recommended.rules, 22 | ...prettierConfig.rules, 23 | '@typescript-eslint/no-unused-vars': 'warn', 24 | 'no-console': 'warn', 25 | semi: ['error', 'always'], 26 | 'prettier/prettier': 'error', 27 | }, 28 | }, 29 | ]; 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coc-groovy", 3 | "version": "1.4.0", 4 | "description": "Groovy langauage extension for coc.nvim", 5 | "author": "dansomething", 6 | "license": "EPL-2.0", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/dansomething/coc-groovy" 10 | }, 11 | "bugs": "https://github.com/dansomething/coc-groovy/issues", 12 | "main": "lib/index.js", 13 | "files": [ 14 | "/LICENSE", 15 | "/README.md", 16 | "/lib", 17 | "/server", 18 | "utils/gradle-classpath" 19 | ], 20 | "keywords": [ 21 | "coc.nvim", 22 | "groovy", 23 | "languageserver" 24 | ], 25 | "publisher": "dansomething", 26 | "publishConfig": { 27 | "registry": "https://registry.npmjs.org" 28 | }, 29 | "engines": { 30 | "coc": ">=0.0.79", 31 | "node": ">=18.20.0" 32 | }, 33 | "scripts": { 34 | "build": "webpack", 35 | "clean": "rimraf lib", 36 | "fix": "eslint --fix && prettier --write .", 37 | "lint": "eslint && prettier --check .", 38 | "prepare": "npm-run-all clean build", 39 | "release": "np --no-tests", 40 | "update": "ncu --peer --enginesNode", 41 | "update-gradle-classpath": "./utils/update-gradle-classpath", 42 | "watch": "webpack --watch" 43 | }, 44 | "prettier": { 45 | "singleQuote": true, 46 | "printWidth": 120, 47 | "semi": true 48 | }, 49 | "devDependencies": { 50 | "@types/glob": "8.1.0", 51 | "@types/node": "^12.20.55", 52 | "@typescript-eslint/eslint-plugin": "^8.33.1", 53 | "@typescript-eslint/parser": "^8.33.1", 54 | "coc.nvim": "^0.0.82", 55 | "eslint": "^9.28.0", 56 | "eslint-config-prettier": "^10.1.5", 57 | "eslint-plugin-prettier": "^5.4.1", 58 | "expand-home-dir": "^0.0.3", 59 | "find-java-home": "2.0.0", 60 | "find-up": "^7.0.0", 61 | "glob": "^10.4.5", 62 | "np": "10.2.0", 63 | "npm-check-updates": "^18.0.1", 64 | "npm-run-all": "^4.1.5", 65 | "path-exists": "^5.0.0", 66 | "prettier": "^3.5.3", 67 | "rimraf": "^5.0.10", 68 | "ts-loader": "^9.5.2", 69 | "typescript": "^4", 70 | "vscode-languageserver-protocol": "^3.17.5", 71 | "webpack": "^5.99.9", 72 | "webpack-cli": "^6.0.1" 73 | }, 74 | "activationEvents": [ 75 | "onLanguage:groovy", 76 | "workspaceContains:pom.xml", 77 | "workspaceContains:build.gradle" 78 | ], 79 | "contributes": { 80 | "rootPatterns": [ 81 | { 82 | "filetype": "groovy", 83 | "patterns": [ 84 | "package.json", 85 | ".project", 86 | ".classpath", 87 | "build.gradle" 88 | ] 89 | } 90 | ], 91 | "snippets": [ 92 | { 93 | "language": "groovy", 94 | "path": "./snippets/groovy.json" 95 | } 96 | ], 97 | "configuration": { 98 | "type": "object", 99 | "title": "Groovy configuration", 100 | "properties": { 101 | "groovy.enable": { 102 | "type": "boolean", 103 | "default": true, 104 | "description": "Enable the coc-groovy extension." 105 | }, 106 | "groovy.java.home": { 107 | "type": [ 108 | "string", 109 | "null" 110 | ], 111 | "default": null, 112 | "description": "Specifies the folder path to the JDK (8 or more recent) used to launch the Groovy Language Server.\nOn Windows, backslashes must be escaped, i.e.\n\"groovy.home\":\"C:\\\\Program Files\\\\Java\\\\jdk1.8.0_161\"", 113 | "scope": "window" 114 | }, 115 | "groovy.ls.vmargs": { 116 | "type": [ 117 | "string", 118 | "null" 119 | ], 120 | "default": "-noverify -Xmx1G -XX:+UseG1GC -XX:+UseStringDeduplication", 121 | "description": "Specifies extra VM arguments used to launch the Groovy Language Server. Eg. use `-noverify -Xmx1G -XX:+UseG1GC -XX:+UseStringDeduplication` to bypass class verification, increase the heap size to 1GB and enable String deduplication with the G1 Garbage collector.", 122 | "scope": "window" 123 | }, 124 | "groovy.ls.home": { 125 | "type": "string", 126 | "default": null, 127 | "description": "Absolute path to the Groovy Language Server home. The bundled language server is used by default.", 128 | "scope": "window" 129 | }, 130 | "groovy.project.referencedLibraries": { 131 | "type": [ 132 | "array" 133 | ], 134 | "default": [], 135 | "description": "Configure additional paths for referencing libraries in a Groovy project. Note, Maven and Gradle projects will have their classpath automatically added.", 136 | "scope": "window" 137 | }, 138 | "groovy.trace.server": { 139 | "type": "string", 140 | "enum": [ 141 | "off", 142 | "messages", 143 | "verbose" 144 | ], 145 | "default": "off", 146 | "description": "Traces the communication between coc.nvim and the Groovy language server.", 147 | "scope": "window" 148 | } 149 | } 150 | }, 151 | "commands": [ 152 | { 153 | "command": "groovy.project.config.update", 154 | "title": "Update project configuration.", 155 | "category": "Groovy" 156 | } 157 | ] 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /server/groovy-language-server-all.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dansomething/coc-groovy/da32ba9efe09024ebf0019ae4828767418b4bf30/server/groovy-language-server-all.jar -------------------------------------------------------------------------------- /src/classpath.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'coc.nvim'; 2 | import { workspace } from 'coc.nvim'; 3 | import { findUp } from 'find-up'; 4 | import fs from 'fs'; 5 | import * as path from 'path'; 6 | import { GROOVY, PLUGIN_NAME } from './constants'; 7 | import { getContext, getLogger } from './context'; 8 | import * as Settings from './settings'; 9 | import { IS_WINDOWS } from './system'; 10 | 11 | const CLASSPATH_FILE = '.groovy-classpath'; 12 | const SEPARATOR = IS_WINDOWS ? ';' : ':'; 13 | const TOOL_GRADLE = 'gradle'; 14 | const TOOL_MVN = 'mvn'; 15 | 16 | export async function getClasspath(storagePath: string, filepath: string, forceUpdate: boolean): Promise { 17 | if (forceUpdate) { 18 | window.showInformationMessage('Resetting loaded libraries.'); 19 | } 20 | 21 | const builtClassPath = await getBuiltClasspath(storagePath, filepath, forceUpdate); 22 | 23 | const config = workspace.getConfiguration(GROOVY); 24 | let classpath: Array | string = config.get(Settings.REFERENCED_LIBRARIES, []); 25 | if (!Array.isArray(classpath)) { 26 | const value = classpath as string; 27 | classpath = value.split(SEPARATOR) as string[]; 28 | } 29 | if (builtClassPath) { 30 | classpath = classpath.concat(builtClassPath); 31 | } 32 | window.showInformationMessage(`${PLUGIN_NAME} libraries loaded.`); 33 | return classpath; 34 | } 35 | 36 | export async function getBuiltClasspath( 37 | storagePath: string, 38 | filepath: string, 39 | forceUpdate: boolean, 40 | ): Promise { 41 | const buildFile = await findNearestBuildFile(filepath); 42 | if (!buildFile) { 43 | getLogger().info('getBuiltClasspath: No build file was found to use for classpath generation.'); 44 | return null; 45 | } 46 | 47 | const cwd = path.dirname(buildFile); 48 | const buildTool = buildFile.includes('pom') ? TOOL_MVN : TOOL_GRADLE; 49 | window.showInformationMessage( 50 | `${PLUGIN_NAME} project [${path.basename(cwd)}] loading libraries with [${buildTool}]...`, 51 | ); 52 | return buildClasspath(storagePath, cwd, buildTool, forceUpdate); 53 | } 54 | 55 | function getClasspathFilePath(storagePath: string): string { 56 | // Specifying the full path results in only one file being created for a multi-module project. 57 | return path.resolve(storagePath, CLASSPATH_FILE); 58 | } 59 | 60 | function deleteClasspathFile(storagePath: string): void { 61 | workspace.deleteFile(getClasspathFilePath(storagePath), { ignoreIfNotExists: true }); 62 | } 63 | 64 | async function buildClasspath( 65 | storagePath: string, 66 | cwd: string, 67 | tool: string, 68 | forceUpdate: boolean, 69 | ): Promise { 70 | const classpathFilePath = getClasspathFilePath(storagePath); 71 | const cmd = await getBuildCmd(tool, cwd, classpathFilePath, forceUpdate); 72 | 73 | if (!cmd) { 74 | getLogger().warn(`buildClassPath: Failed to find build command for [${tool}] [${cwd}]`); 75 | return null; 76 | } 77 | 78 | getLogger().debug(`buildClasspath cwd: ${cwd}`); 79 | getLogger().debug(`buildClasspath cmd: ${cmd}`); 80 | 81 | try { 82 | const result = await workspace.runCommand(cmd, cwd); 83 | if (!result?.includes('BUILD SUCCESS')) { 84 | getLogger().warn(`buildClasspath: build tool failed [${cmd}]. Result [${result}]`); 85 | deleteClasspathFile(storagePath); 86 | return null; 87 | } 88 | } catch (e) { 89 | // The build tool operation failed for some reason so there's nothing we can do. 90 | getLogger().warn(`buildClasspath: build tool failed [${cmd}]. Error [${JSON.stringify(e)}]`); 91 | window.showErrorMessage(`${PLUGIN_NAME} classpath command failed "cd ${cwd} && ${cmd}"`, 'error'); 92 | deleteClasspathFile(storagePath); 93 | return null; 94 | } 95 | 96 | const fileContent = fs.readFileSync(classpathFilePath, 'utf8'); 97 | if (!fileContent) { 98 | getLogger().warn(`buildClasspath: Empty classpath file generated? Deleting [${storagePath}]`); 99 | deleteClasspathFile(storagePath); 100 | return null; 101 | } 102 | 103 | return fileContent.split(SEPARATOR).sort(); 104 | } 105 | 106 | async function getBuildCmd( 107 | tool: string, 108 | cwd: string, 109 | classpathFilePath: string, 110 | forceUpdate: boolean, 111 | ): Promise { 112 | let cmd: string | null = null; 113 | 114 | if (tool === TOOL_MVN) { 115 | const mvnCmd = await findMvnCmd(cwd); 116 | if (mvnCmd) { 117 | cmd = `${mvnCmd} dependency:build-classpath -Dmdep.pathSeparator=${SEPARATOR} -Dmdep.outputFile=${classpathFilePath} -Dmdep.regenerateFile=${forceUpdate}`; 118 | } 119 | } else if (tool === TOOL_GRADLE) { 120 | const gradleCmd = await findGradleCmd(); 121 | if (gradleCmd) { 122 | cmd = `${gradleCmd} --path-separator=${SEPARATOR} --output-file=${classpathFilePath} --regenerate-file=${forceUpdate}`; 123 | } 124 | } 125 | 126 | return cmd; 127 | } 128 | 129 | async function findNearestBuildFile(filepath: string): Promise { 130 | const cwd = path.dirname(filepath); 131 | getLogger().debug(`findNearestBuildFile: cwd [${cwd}]`); 132 | let buildFile = await findUp('pom.xml', { cwd }); 133 | if (!buildFile) { 134 | buildFile = await findUp('build.gradle', { cwd }); 135 | } 136 | return buildFile; 137 | } 138 | 139 | async function findGradleCmd(): Promise { 140 | try { 141 | return path.resolve( 142 | getContext().extensionPath, 143 | 'utils', 144 | 'gradle-classpath', 145 | 'bin', 146 | 'gradle-classpath' + (IS_WINDOWS ? '.bat' : ''), 147 | ); 148 | } catch (e) { 149 | getLogger().error(`findGradleCmnd: Gradle classpath command failed. Error [${JSON.stringify(e)}]`); 150 | } 151 | 152 | return null; 153 | } 154 | 155 | async function findMvnCmd(cwd: string): Promise { 156 | try { 157 | const mvnw = IS_WINDOWS ? 'mvnw.cmd' : 'mvnw'; 158 | let mvn = await findUp(mvnw, { cwd }); 159 | if (!mvn) { 160 | mvn = IS_WINDOWS ? 'mvn.cmd' : 'mvn'; 161 | } 162 | 163 | const mvnVersion = await workspace.runCommand(`${mvn} --version`); 164 | if (mvnVersion.match(/Apache Maven \d\.\d+\.\d+/)) { 165 | return mvn; 166 | } 167 | } catch (e) { 168 | getLogger().error(`findMvnCmnd: Maven version check failed. Error [${JSON.stringify(e)}]`); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/client.ts: -------------------------------------------------------------------------------- 1 | import { DidChangeConfigurationSignature, LanguageClientOptions, workspace } from 'coc.nvim'; 2 | import { GROOVY } from './constants'; 3 | 4 | export function getClientOptions(onConfigChange: () => void): LanguageClientOptions { 5 | const config = workspace.getConfiguration(GROOVY); 6 | 7 | return { 8 | diagnosticCollectionName: GROOVY, 9 | documentSelector: [{ scheme: 'file', language: GROOVY }], 10 | synchronize: { 11 | configurationSection: GROOVY, 12 | }, 13 | initializationOptions: { 14 | settings: { groovy: config }, 15 | }, 16 | middleware: { 17 | workspace: { 18 | didChangeConfiguration: (sections: string[] | undefined, next: DidChangeConfigurationSignature): void => { 19 | if (sections?.length == 1 && sections[0] === GROOVY) { 20 | onConfigChange(); 21 | } else { 22 | next(sections); 23 | } 24 | }, 25 | }, 26 | }, 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /src/commands.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Commonly used commands 3 | */ 4 | 5 | /** 6 | * Open Browser 7 | */ 8 | export const OPEN_BROWSER = 'vscode.open'; 9 | 10 | /** 11 | * Update project configuration 12 | */ 13 | export const CONFIGURATION_UPDATE = 'groovy.project.config.update'; 14 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const GROOVY = 'groovy'; 2 | export const PLUGIN_NAME = 'Groovy Language Server'; 3 | -------------------------------------------------------------------------------- /src/context.ts: -------------------------------------------------------------------------------- 1 | import { ExtensionContext } from 'coc.nvim'; 2 | 3 | let _context: ExtensionContext; 4 | 5 | export function getContext(): ExtensionContext { 6 | return _context; 7 | } 8 | 9 | export function setContext(context: ExtensionContext) { 10 | _context = context; 11 | } 12 | 13 | /* eslint-disable @typescript-eslint/no-explicit-any */ 14 | /* eslint-disable no-console */ 15 | export interface Logger { 16 | debug(message?: any, ...optionalParams: any[]): void; 17 | error(message?: any, ...optionalParams: any[]): void; 18 | info(message?: any, ...optionalParams: any[]): void; 19 | warn(message?: any, ...optionalParams: any[]): void; 20 | } 21 | 22 | class ConsoleLogger implements Logger { 23 | debug(message?: any, ...optionalParams: any[]): void { 24 | console.log(message, ...optionalParams); 25 | } 26 | 27 | error(message?: any, ...optionalParams: any[]): void { 28 | console.error(message, ...optionalParams); 29 | } 30 | 31 | info(message?: any, ...optionalParams: any[]): void { 32 | console.info(message, ...optionalParams); 33 | } 34 | 35 | warn(message?: any, ...optionalParams: any[]): void { 36 | console.warn(message, ...optionalParams); 37 | } 38 | } 39 | /* eslint-enable @typescript-eslint/no-explicit-any */ 40 | /* eslint-enable no-console */ 41 | 42 | const logger = new ConsoleLogger(); 43 | 44 | export function getLogger(): Logger { 45 | return logger; 46 | } 47 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { window } from 'coc.nvim'; 2 | import { commands, ExtensionContext, LanguageClient, Uri, workspace } from 'coc.nvim'; 3 | import { createHash } from 'crypto'; 4 | import * as path from 'path'; 5 | import { DidChangeConfigurationNotification, Disposable } from 'vscode-languageserver-protocol'; 6 | import { getClasspath } from './classpath'; 7 | import { getClientOptions } from './client'; 8 | import * as Commands from './commands'; 9 | import { GROOVY, PLUGIN_NAME } from './constants'; 10 | import { setContext } from './context'; 11 | import { ErrorData, RequirementsData, resolveRequirements } from './requirements'; 12 | import { getServerOptions } from './server'; 13 | import { getTempWorkspace } from './system'; 14 | 15 | let languageClient: LanguageClient | null; 16 | let languageClientDisposable: Disposable; 17 | let storagePath: string; 18 | 19 | export async function activate(context: ExtensionContext): Promise { 20 | const config = workspace.getConfiguration(GROOVY); 21 | if (!config.enable) return; 22 | 23 | setContext(context); 24 | storagePath = getStoragePath(context); 25 | let requirements: RequirementsData; 26 | try { 27 | requirements = await resolveRequirements(); 28 | window.showInformationMessage( 29 | `${PLUGIN_NAME} using Java from ${requirements.java_home}, version: ${requirements.java_version}`, 30 | ); 31 | return startLanguageServer(context, requirements); 32 | } catch (err: unknown) { 33 | const e = err as ErrorData; 34 | const res = await window.showQuickpick(['Yes', 'No'], `${e.message}, ${e.label}?`); 35 | if (res == 0) { 36 | commands.executeCommand(Commands.OPEN_BROWSER, e.openUrl).catch(() => { 37 | // noop 38 | }); 39 | } 40 | } 41 | } 42 | 43 | export function deactivate(): void { 44 | languageClientDisposable.dispose(); 45 | languageClient = null; 46 | } 47 | 48 | function getStoragePath(context: ExtensionContext): string { 49 | let workspacePath = context.storagePath; 50 | if (!workspacePath) { 51 | workspacePath = getTempWorkspace(); 52 | } 53 | 54 | const id = createHash('md5').update(workspace.root).digest('hex'); 55 | const workspaceName = `groovy_ws_${id}`; 56 | return path.resolve(workspacePath, workspaceName); 57 | } 58 | 59 | async function startLanguageServer(context: ExtensionContext, requirements: RequirementsData): Promise { 60 | const clientOptions = getClientOptions(updateClasspath); 61 | const serverOptions = await getServerOptions(context, requirements); 62 | languageClient = new LanguageClient(GROOVY, PLUGIN_NAME, serverOptions, clientOptions); 63 | 64 | languageClient.onReady().then( 65 | () => { 66 | window.showInformationMessage(`${PLUGIN_NAME} started!`); 67 | registerCommands(context); 68 | }, 69 | (e) => { 70 | context.logger.error(e.message); 71 | }, 72 | ); 73 | 74 | window.showInformationMessage(`${PLUGIN_NAME} starting...`); 75 | languageClientDisposable = languageClient.start(); 76 | } 77 | 78 | function registerCommands(context: ExtensionContext): void { 79 | context.subscriptions.push(commands.registerCommand(Commands.CONFIGURATION_UPDATE, updateProjectConfig)); 80 | } 81 | 82 | async function updateProjectConfig(): Promise { 83 | await updateClasspath(true); 84 | window.showInformationMessage(`${PLUGIN_NAME} project config updated.`); 85 | } 86 | 87 | async function updateClasspath(forceUpdate?: boolean): Promise { 88 | const classpath = await getCurrentFileClasspath(forceUpdate); 89 | const config = workspace.getConfiguration(GROOVY); 90 | const groovy = { ...config, classpath }; 91 | // The Groovy language server only loads the classpath from a config change notification. 92 | // Ideally this would also be loaded with initializationOptions too. 93 | languageClient?.sendNotification(DidChangeConfigurationNotification.method, { 94 | settings: { groovy }, 95 | }); 96 | } 97 | 98 | async function getCurrentFileClasspath(forceUpdate = false): Promise { 99 | const { document } = await workspace.getCurrentState(); 100 | const uri = Uri.parse(document.uri); 101 | const filepath = uri.fsPath; 102 | return await getClasspath(storagePath, filepath, forceUpdate); 103 | } 104 | -------------------------------------------------------------------------------- /src/requirements.ts: -------------------------------------------------------------------------------- 1 | import cp from 'child_process'; 2 | import { Uri, workspace } from 'coc.nvim'; 3 | import expandHomeDir from 'expand-home-dir'; 4 | import findJavaHome from 'find-java-home'; 5 | import fs from 'fs'; 6 | import path from 'path'; 7 | import { pathExistsSync } from 'path-exists'; 8 | import { GROOVY } from './constants'; 9 | import { getLogger } from './context'; 10 | import * as Settings from './settings'; 11 | import { JAVAC_FILENAME, JAVA_FILENAME } from './system'; 12 | 13 | export interface ServerConfiguration { 14 | root: string; 15 | encoding: string; 16 | vmargs: string; 17 | } 18 | 19 | export interface RequirementsData { 20 | java_home: string; 21 | java_version: number; 22 | } 23 | 24 | export interface ErrorData { 25 | message: string; 26 | label: string; 27 | openUrl: Uri; 28 | replaceClose: boolean; 29 | } 30 | 31 | /** 32 | * Resolves the requirements needed to run this extension. 33 | * 34 | * Returns a promise that will resolve to a RequirementsData if all 35 | * requirements are resolved. Otherwise, it will reject with ErrorData if 36 | * any of the requirements fail to be met. 37 | */ 38 | export async function resolveRequirements(): Promise { 39 | const java_home = await checkJavaRuntime(); 40 | const javaVersion = await checkJavaVersion(java_home); 41 | return Promise.resolve({ java_home, java_version: javaVersion }); 42 | } 43 | 44 | function checkJavaRuntime(): Promise { 45 | return new Promise((resolve, reject) => { 46 | let source: string; 47 | let javaHome: string | undefined = readJavaHomeConfig(); 48 | if (javaHome) { 49 | source = 'The groovy.java.home variable defined in COC settings'; 50 | } else { 51 | javaHome = process.env['JDK_HOME']; 52 | if (javaHome) { 53 | source = 'The JDK_HOME environment variable'; 54 | } else { 55 | javaHome = process.env['JAVA_HOME']; 56 | source = 'The JAVA_HOME environment variable'; 57 | } 58 | } 59 | if (javaHome !== undefined) { 60 | let home: string = expandHomeDir(javaHome); 61 | const stat = fs.lstatSync(home); 62 | if (stat.isSymbolicLink()) { 63 | home = fs.realpathSync(home); 64 | } 65 | if (!pathExistsSync(home)) { 66 | openJDKDownload(reject, source + ' points to a missing folder'); 67 | } 68 | if (!pathExistsSync(path.resolve(home, 'bin', JAVAC_FILENAME))) { 69 | openJDKDownload(reject, source + ' does not point to a JDK.'); 70 | } 71 | return resolve(home); 72 | } 73 | // No settings, let's try to detect as last resort. 74 | findJavaHome({ allowJre: true }, (err, home) => { 75 | if (err) { 76 | getLogger().error(`findJavaHome: No Java runtime found. Error [${JSON.stringify(err)}]`); 77 | openJDKDownload(reject, 'Java runtime could not be located'); 78 | } else { 79 | resolve(home); 80 | } 81 | }); 82 | }); 83 | } 84 | 85 | function readJavaHomeConfig(): string | undefined { 86 | const config = workspace.getConfiguration(GROOVY); 87 | return config.get(Settings.JAVA_HOME, undefined); 88 | } 89 | 90 | function checkJavaVersion(java_home: string): Promise { 91 | return new Promise((resolve, reject) => { 92 | cp.execFile(java_home + '/bin/' + JAVA_FILENAME, ['-version'], {}, (_error, _stdout, stderr) => { 93 | const javaVersion = parseMajorVersion(stderr); 94 | if (javaVersion < 8) { 95 | openJDKDownload(reject, 'Java 8 or more recent is required to run. Please download and install a recent JDK'); 96 | } else { 97 | resolve(javaVersion); 98 | } 99 | }); 100 | }); 101 | } 102 | 103 | function parseMajorVersion(content: string): number { 104 | let regexp = /version "(.*)"/g; 105 | let match = regexp.exec(content); 106 | if (!match) { 107 | return 0; 108 | } 109 | let version = match[1]; 110 | // Ignore '1.' prefix for legacy Java versions 111 | if (version.startsWith('1.')) { 112 | version = version.substring(2); 113 | } 114 | 115 | // look into the interesting bits now 116 | regexp = /\d+/g; 117 | match = regexp.exec(version); 118 | let javaVersion = 0; 119 | if (match) { 120 | javaVersion = parseInt(match[0], 10); 121 | } 122 | return javaVersion; 123 | } 124 | 125 | function openJDKDownload(reject: (reason?: ErrorData) => void, cause: string): void { 126 | let jdkUrl = 'https://developers.redhat.com/products/openjdk/download/?sc_cid=701f2000000RWTnAAO'; 127 | if (process.platform === 'darwin') { 128 | jdkUrl = 'http://www.oracle.com/technetwork/java/javase/downloads/index.html'; 129 | } 130 | reject({ 131 | message: cause, 132 | label: 'Get the Java Development Kit', 133 | openUrl: Uri.parse(jdkUrl), 134 | replaceClose: false, 135 | } as ErrorData); 136 | } 137 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import { Executable, ExtensionContext, workspace } from 'coc.nvim'; 2 | import * as glob from 'glob'; 3 | import * as path from 'path'; 4 | import { GROOVY, PLUGIN_NAME } from './constants'; 5 | import { getLogger } from './context'; 6 | import { RequirementsData, ServerConfiguration } from './requirements'; 7 | import * as Settings from './settings'; 8 | import { DEBUG, JAVA_FILENAME } from './system'; 9 | 10 | export async function getServerOptions(context: ExtensionContext, requirements: RequirementsData): Promise { 11 | const config = workspace.getConfiguration(GROOVY); 12 | const root = config.get(Settings.LS_HOME, defaultServerHome(context)); 13 | const encoding = (await workspace.nvim.eval('&fileencoding')) as string; 14 | const serverConfig: ServerConfiguration = { 15 | root, 16 | encoding, 17 | vmargs: config.get(Settings.LS_VMARGS, ''), 18 | }; 19 | return prepareExecutable(requirements, serverConfig); 20 | } 21 | 22 | function defaultServerHome(context: ExtensionContext): string { 23 | return path.resolve(context.extensionPath, 'server'); 24 | } 25 | 26 | function prepareExecutable(requirements: RequirementsData, config: ServerConfiguration): Executable { 27 | const executable: Executable = Object.create(null); 28 | const options = Object.create(null); 29 | options.env = process.env; 30 | options.stdio = 'pipe'; 31 | executable.options = options; 32 | executable.command = path.resolve(requirements.java_home, 'bin', JAVA_FILENAME); 33 | executable.args = prepareParams(config); 34 | getLogger().info(`Starting ${PLUGIN_NAME} with: ` + executable.command + ' ' + executable.args?.join(' ')); 35 | return executable; 36 | } 37 | 38 | function prepareParams(config: ServerConfiguration): string[] | undefined { 39 | const params: string[] = []; 40 | if (DEBUG) { 41 | params.push('-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=localhost:1044,quiet=y'); 42 | // suspend=y is the default. Use this form if you need to debug the server startup code: 43 | // params.push('-agentlib:jdwp=transport=dt_socket,server=y,address=localhost:1044') 44 | params.push('-Dlog.level=ALL'); 45 | } 46 | 47 | const { vmargs, root } = config; 48 | const encodingKey = '-Dfile.encoding='; 49 | if (vmargs.indexOf(encodingKey) < 0 && config.encoding) { 50 | params.push(encodingKey + config.encoding); 51 | } 52 | 53 | parseVmArgs(params, vmargs); 54 | const jarsFound: string[] = glob.sync('**/groovy-language-server-all.jar', { cwd: root }); 55 | if (jarsFound.length) { 56 | params.push('-jar'); 57 | params.push(path.resolve(root, jarsFound[0])); 58 | } else { 59 | return undefined; 60 | } 61 | 62 | return params; 63 | } 64 | 65 | function parseVmArgs(params: string[], vmargsLine: string): void { 66 | if (!vmargsLine) { 67 | return; 68 | } 69 | const vmargs = vmargsLine.match(/(?:[^\s"]+|"[^"]*")+/g); 70 | if (vmargs === null) { 71 | return; 72 | } 73 | vmargs.forEach((arg) => { 74 | // remove all standalone double quotes 75 | arg = arg.replace(/(\\)?"/g, function ($0, $1) { 76 | return $1 ? $0 : ''; 77 | }); 78 | // unescape all escaped double quotes 79 | arg = arg.replace(/(\\)"/g, '"'); 80 | if (params.indexOf(arg) < 0) { 81 | params.push(arg); 82 | } 83 | }); 84 | } 85 | -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Available Groovy extension settings. 3 | */ 4 | 5 | /** 6 | * Name fo the Java home key in the config. 7 | */ 8 | export const JAVA_HOME = 'java.home'; 9 | 10 | /** 11 | * Name for the language server home key in the config. 12 | */ 13 | export const LS_HOME = 'ls.home'; 14 | 15 | /** 16 | * Name for the language server jvm args key in the config. 17 | */ 18 | export const LS_VMARGS = 'ls.vmargs'; 19 | 20 | /** 21 | * Name for the project referenced libraries key in the config. 22 | */ 23 | export const REFERENCED_LIBRARIES = 'project.referencedLibraries'; 24 | -------------------------------------------------------------------------------- /src/system.ts: -------------------------------------------------------------------------------- 1 | import * as os from 'os'; 2 | import * as path from 'path'; 3 | 4 | export const IS_WINDOWS = process.platform.indexOf('win') === 0; 5 | export const JAVAC_FILENAME = 'javac' + (IS_WINDOWS ? '.exe' : ''); 6 | export const JAVA_FILENAME = 'java' + (IS_WINDOWS ? '.exe' : ''); 7 | 8 | declare let v8debug: object | undefined; 9 | export const DEBUG = typeof v8debug === 'object' || startedInDebugMode(); 10 | 11 | function startedInDebugMode(): boolean { 12 | const args = process.execArgv; 13 | if (args) { 14 | return args.some( 15 | (arg: string) => /^--debug=?/.test(arg) || /^--debug-brk=?/.test(arg) || /^--inspect-brk=?/.test(arg), 16 | ); 17 | } 18 | return false; 19 | } 20 | 21 | export function isGroovyFile(path: string): boolean { 22 | return path?.endsWith('.groovy'); 23 | } 24 | 25 | export function getTempWorkspace(): string { 26 | return path.resolve(os.tmpdir(), 'coc-groovy'); 27 | } 28 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'expand-home-dir'; 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2017", 4 | "lib": ["es2017", "es2018"], 5 | "module": "commonjs", 6 | "declaration": false, 7 | "sourceMap": true, 8 | "outDir": "lib", 9 | "strict": true, 10 | "moduleResolution": "node", 11 | "noImplicitAny": false, 12 | "esModuleInterop": true 13 | }, 14 | "include": ["src"] 15 | } 16 | -------------------------------------------------------------------------------- /utils/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dansomething/coc-groovy/da32ba9efe09024ebf0019ae4828767418b4bf30/utils/.DS_Store -------------------------------------------------------------------------------- /utils/gradle-classpath/bin/gradle-classpath: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # gradle-classpath start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh gradle-classpath 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_CLASSPATH_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}.." && pwd -P ) || exit 84 | 85 | APP_NAME="gradle-classpath" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_CLASSPATH_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS="" 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/lib/gradle-classpath-1.3.0.jar:$APP_HOME/lib/gradle-tooling-api-8.1.1.jar:$APP_HOME/lib/picocli-4.7.4.jar:$APP_HOME/lib/slf4j-simple-1.7.36.jar:$APP_HOME/lib/slf4j-api-1.7.36.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_CLASSPATH_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_CLASSPATH_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | -classpath "$CLASSPATH" \ 204 | com.dansomething.gradle.classpath.App \ 205 | "$@" 206 | 207 | # Use "xargs" to parse quoted args. 208 | # 209 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 210 | # 211 | # In Bash we could simply go: 212 | # 213 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 214 | # set -- "${ARGS[@]}" "$@" 215 | # 216 | # but POSIX shell has neither arrays nor command substitution, so instead we 217 | # post-process each arg (as a line of input to sed) to backslash-escape any 218 | # character that might be a shell metacharacter, then use eval to reverse 219 | # that process (while maintaining the separation between arguments), and wrap 220 | # the whole thing up as a single "set" statement. 221 | # 222 | # This will of course break if any of these variables contains a newline or 223 | # an unmatched quote. 224 | # 225 | 226 | eval "set -- $( 227 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_CLASSPATH_OPTS" | 228 | xargs -n1 | 229 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 230 | tr '\n' ' ' 231 | )" '"$@"' 232 | 233 | exec "$JAVACMD" "$@" 234 | -------------------------------------------------------------------------------- /utils/gradle-classpath/bin/gradle-classpath.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem gradle-classpath startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME%.. 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_CLASSPATH_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS= 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\lib\gradle-classpath-1.3.0.jar;%APP_HOME%\lib\gradle-tooling-api-8.1.1.jar;%APP_HOME%\lib\picocli-4.7.4.jar;%APP_HOME%\lib\slf4j-simple-1.7.36.jar;%APP_HOME%\lib\slf4j-api-1.7.36.jar 71 | 72 | 73 | @rem Execute gradle-classpath 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_CLASSPATH_OPTS% -classpath "%CLASSPATH%" com.dansomething.gradle.classpath.App %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_CLASSPATH_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_CLASSPATH_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /utils/gradle-classpath/lib/gradle-classpath-1.3.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dansomething/coc-groovy/da32ba9efe09024ebf0019ae4828767418b4bf30/utils/gradle-classpath/lib/gradle-classpath-1.3.0.jar -------------------------------------------------------------------------------- /utils/gradle-classpath/lib/gradle-tooling-api-8.1.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dansomething/coc-groovy/da32ba9efe09024ebf0019ae4828767418b4bf30/utils/gradle-classpath/lib/gradle-tooling-api-8.1.1.jar -------------------------------------------------------------------------------- /utils/gradle-classpath/lib/picocli-4.7.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dansomething/coc-groovy/da32ba9efe09024ebf0019ae4828767418b4bf30/utils/gradle-classpath/lib/picocli-4.7.4.jar -------------------------------------------------------------------------------- /utils/gradle-classpath/lib/slf4j-api-1.7.36.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dansomething/coc-groovy/da32ba9efe09024ebf0019ae4828767418b4bf30/utils/gradle-classpath/lib/slf4j-api-1.7.36.jar -------------------------------------------------------------------------------- /utils/gradle-classpath/lib/slf4j-simple-1.7.36.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dansomething/coc-groovy/da32ba9efe09024ebf0019ae4828767418b4bf30/utils/gradle-classpath/lib/slf4j-simple-1.7.36.jar -------------------------------------------------------------------------------- /utils/update-gradle-classpath: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eou pipefail 3 | 4 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 5 | GRADLE_CLASSPATH_DIR="${DIR}/../../gradle-classpath" 6 | 7 | pushd "${GRADLE_CLASSPATH_DIR}" &>/dev/null || exit 8 | "${GRADLE_CLASSPATH_DIR}/gradlew" clean installDist 9 | popd &>/dev/null || exit 10 | 11 | rsync --delete --archive --verbose "${GRADLE_CLASSPATH_DIR}/build/install/gradle-classpath/" "${DIR}/gradle-classpath/" 12 | -------------------------------------------------------------------------------- /utils/update_server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eou pipefail 3 | 4 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 5 | GROOVY_LSP_DIR="${DIR}/../../groovy-language-server" 6 | 7 | pushd "${GROOVY_LSP_DIR}" &>/dev/null || exit 8 | git pull 9 | ./gradlew clean build 10 | popd &>/dev/null || exit 11 | 12 | cp -v "${GROOVY_LSP_DIR}/build/libs/groovy-language-server-all.jar" "${DIR}/../server/" 13 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | /** @type {import('webpack').Configuration} */ 4 | module.exports = { 5 | entry: './src/index.ts', 6 | target: 'node', 7 | mode: 'none', 8 | resolve: { 9 | mainFields: ['module', 'main'], 10 | extensions: ['.js', '.ts'], 11 | }, 12 | externals: { 13 | 'coc.nvim': 'commonjs coc.nvim', 14 | }, 15 | optimization: { 16 | minimize: true, 17 | }, 18 | module: { 19 | rules: [ 20 | { 21 | test: /\.ts$/, 22 | include: [path.resolve(__dirname, 'src')], 23 | use: [ 24 | { 25 | loader: 'ts-loader', 26 | options: { 27 | compilerOptions: { 28 | sourceMap: true, 29 | }, 30 | }, 31 | }, 32 | ], 33 | }, 34 | ], 35 | }, 36 | output: { 37 | path: path.join(__dirname, 'lib'), 38 | filename: 'index.js', 39 | libraryTarget: 'commonjs', 40 | }, 41 | plugins: [], 42 | node: { 43 | __dirname: false, 44 | __filename: false, 45 | }, 46 | }; 47 | --------------------------------------------------------------------------------