9 |
10 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # Mozilla Public License Version 2.0
2 |
3 | ## 1. Definitions
4 |
5 | **1.1. “Contributor”**
6 | means each individual or legal entity that creates, contributes to
7 | the creation of, or owns Covered Software.
8 |
9 | **1.2. “Contributor Version”**
10 | means the combination of the Contributions of others (if any) used
11 | by a Contributor and that particular Contributor's Contribution.
12 |
13 | **1.3. “Contribution”**
14 | means Covered Software of a particular Contributor.
15 |
16 | **1.4. “Covered Software”**
17 | means Source Code Form to which the initial Contributor has attached
18 | the notice in Exhibit A, the Executable Form of such Source Code
19 | Form, and Modifications of such Source Code Form, in each case
20 | including portions thereof.
21 |
22 | **1.5. “Incompatible With Secondary Licenses”**
23 | means
24 |
25 | * **(a)** that the initial Contributor has attached the notice described
26 | in Exhibit B to the Covered Software; or
27 | * **(b)** that the Covered Software was made available under the terms of
28 | version 1.1 or earlier of the License, but not also under the
29 | terms of a Secondary License.
30 |
31 | **1.6. “Executable Form”**
32 | means any form of the work other than Source Code Form.
33 |
34 | **1.7. “Larger Work”**
35 | means a work that combines Covered Software with other material, in
36 | a separate file or files, that is not Covered Software.
37 |
38 | **1.8. “License”**
39 | means this document.
40 |
41 | **1.9. “Licensable”**
42 | means having the right to grant, to the maximum extent possible,
43 | whether at the time of the initial grant or subsequently, any and
44 | all of the rights conveyed by this License.
45 |
46 | **1.10. “Modifications”**
47 | means any of the following:
48 |
49 | * **(a)** any file in Source Code Form that results from an addition to,
50 | deletion from, or modification of the contents of Covered
51 | Software; or
52 | * **(b)** any new file in Source Code Form that contains any Covered
53 | Software.
54 |
55 | **1.11. “Patent Claims” of a Contributor**
56 | means any patent claim(s), including without limitation, method,
57 | process, and apparatus claims, in any patent Licensable by such
58 | Contributor that would be infringed, but for the grant of the
59 | License, by the making, using, selling, offering for sale, having
60 | made, import, or transfer of either its Contributions or its
61 | Contributor Version.
62 |
63 | **1.12. “Secondary License”**
64 | means either the GNU General Public License, Version 2.0, the GNU
65 | Lesser General Public License, Version 2.1, the GNU Affero General
66 | Public License, Version 3.0, or any later versions of those
67 | licenses.
68 |
69 | **1.13. “Source Code Form”**
70 | means the form of the work preferred for making modifications.
71 |
72 | **1.14. “You” (or “Your”)**
73 | means an individual or a legal entity exercising rights under this
74 | License. For legal entities, “You” includes any entity that
75 | controls, is controlled by, or is under common control with You. For
76 | purposes of this definition, “control” means **(a)** the power, direct
77 | or indirect, to cause the direction or management of such entity,
78 | whether by contract or otherwise, or **(b)** ownership of more than
79 | fifty percent (50%) of the outstanding shares or beneficial
80 | ownership of such entity.
81 |
82 | ## 2. License Grants and Conditions
83 |
84 | ### 2.1. Grants
85 |
86 | Each Contributor hereby grants You a world-wide, royalty-free,
87 | non-exclusive license:
88 |
89 | * **(a)** under intellectual property rights (other than patent or trademark)
90 | Licensable by such Contributor to use, reproduce, make available,
91 | modify, display, perform, distribute, and otherwise exploit its
92 | Contributions, either on an unmodified basis, with Modifications, or
93 | as part of a Larger Work; and
94 | * **(b)** under Patent Claims of such Contributor to make, use, sell, offer
95 | for sale, have made, import, and otherwise transfer either its
96 | Contributions or its Contributor Version.
97 |
98 | ### 2.2. Effective Date
99 |
100 | The licenses granted in Section 2.1 with respect to any Contribution
101 | become effective for each Contribution on the date the Contributor first
102 | distributes such Contribution.
103 |
104 | ### 2.3. Limitations on Grant Scope
105 |
106 | The licenses granted in this Section 2 are the only rights granted under
107 | this License. No additional rights or licenses will be implied from the
108 | distribution or licensing of Covered Software under this License.
109 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
110 | Contributor:
111 |
112 | * **(a)** for any code that a Contributor has removed from Covered Software;
113 | or
114 | * **(b)** for infringements caused by: **(i)** Your and any other third party's
115 | modifications of Covered Software, or **(ii)** the combination of its
116 | Contributions with other software (except as part of its Contributor
117 | Version); or
118 | * **(c)** under Patent Claims infringed by Covered Software in the absence of
119 | its Contributions.
120 |
121 | This License does not grant any rights in the trademarks, service marks,
122 | or logos of any Contributor (except as may be necessary to comply with
123 | the notice requirements in Section 3.4).
124 |
125 | ### 2.4. Subsequent Licenses
126 |
127 | No Contributor makes additional grants as a result of Your choice to
128 | distribute the Covered Software under a subsequent version of this
129 | License (see Section 10.2) or under the terms of a Secondary License (if
130 | permitted under the terms of Section 3.3).
131 |
132 | ### 2.5. Representation
133 |
134 | Each Contributor represents that the Contributor believes its
135 | Contributions are its original creation(s) or it has sufficient rights
136 | to grant the rights to its Contributions conveyed by this License.
137 |
138 | ### 2.6. Fair Use
139 |
140 | This License is not intended to limit any rights You have under
141 | applicable copyright doctrines of fair use, fair dealing, or other
142 | equivalents.
143 |
144 | ### 2.7. Conditions
145 |
146 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
147 | in Section 2.1.
148 |
149 | ## 3. Responsibilities
150 |
151 | ### 3.1. Distribution of Source Form
152 |
153 | All distribution of Covered Software in Source Code Form, including any
154 | Modifications that You create or to which You contribute, must be under
155 | the terms of this License. You must inform recipients that the Source
156 | Code Form of the Covered Software is governed by the terms of this
157 | License, and how they can obtain a copy of this License. You may not
158 | attempt to alter or restrict the recipients' rights in the Source Code
159 | Form.
160 |
161 | ### 3.2. Distribution of Executable Form
162 |
163 | If You distribute Covered Software in Executable Form then:
164 |
165 | * **(a)** such Covered Software must also be made available in Source Code
166 | Form, as described in Section 3.1, and You must inform recipients of
167 | the Executable Form how they can obtain a copy of such Source Code
168 | Form by reasonable means in a timely manner, at a charge no more
169 | than the cost of distribution to the recipient; and
170 |
171 | * **(b)** You may distribute such Executable Form under the terms of this
172 | License, or sublicense it under different terms, provided that the
173 | license for the Executable Form does not attempt to limit or alter
174 | the recipients' rights in the Source Code Form under this License.
175 |
176 | ### 3.3. Distribution of a Larger Work
177 |
178 | You may create and distribute a Larger Work under terms of Your choice,
179 | provided that You also comply with the requirements of this License for
180 | the Covered Software. If the Larger Work is a combination of Covered
181 | Software with a work governed by one or more Secondary Licenses, and the
182 | Covered Software is not Incompatible With Secondary Licenses, this
183 | License permits You to additionally distribute such Covered Software
184 | under the terms of such Secondary License(s), so that the recipient of
185 | the Larger Work may, at their option, further distribute the Covered
186 | Software under the terms of either this License or such Secondary
187 | License(s).
188 |
189 | ### 3.4. Notices
190 |
191 | You may not remove or alter the substance of any license notices
192 | (including copyright notices, patent notices, disclaimers of warranty,
193 | or limitations of liability) contained within the Source Code Form of
194 | the Covered Software, except that You may alter any license notices to
195 | the extent required to remedy known factual inaccuracies.
196 |
197 | ### 3.5. Application of Additional Terms
198 |
199 | You may choose to offer, and to charge a fee for, warranty, support,
200 | indemnity or liability obligations to one or more recipients of Covered
201 | Software. However, You may do so only on Your own behalf, and not on
202 | behalf of any Contributor. You must make it absolutely clear that any
203 | such warranty, support, indemnity, or liability obligation is offered by
204 | You alone, and You hereby agree to indemnify every Contributor for any
205 | liability incurred by such Contributor as a result of warranty, support,
206 | indemnity or liability terms You offer. You may include additional
207 | disclaimers of warranty and limitations of liability specific to any
208 | jurisdiction.
209 |
210 | ## 4. Inability to Comply Due to Statute or Regulation
211 |
212 | If it is impossible for You to comply with any of the terms of this
213 | License with respect to some or all of the Covered Software due to
214 | statute, judicial order, or regulation then You must: **(a)** comply with
215 | the terms of this License to the maximum extent possible; and **(b)**
216 | describe the limitations and the code they affect. Such description must
217 | be placed in a text file included with all distributions of the Covered
218 | Software under this License. Except to the extent prohibited by statute
219 | or regulation, such description must be sufficiently detailed for a
220 | recipient of ordinary skill to be able to understand it.
221 |
222 | ## 5. Termination
223 |
224 | **5.1.** The rights granted under this License will terminate automatically
225 | if You fail to comply with any of its terms. However, if You become
226 | compliant, then the rights granted under this License from a particular
227 | Contributor are reinstated **(a)** provisionally, unless and until such
228 | Contributor explicitly and finally terminates Your grants, and **(b)** on an
229 | ongoing basis, if such Contributor fails to notify You of the
230 | non-compliance by some reasonable means prior to 60 days after You have
231 | come back into compliance. Moreover, Your grants from a particular
232 | Contributor are reinstated on an ongoing basis if such Contributor
233 | notifies You of the non-compliance by some reasonable means, this is the
234 | first time You have received notice of non-compliance with this License
235 | from such Contributor, and You become compliant prior to 30 days after
236 | Your receipt of the notice.
237 |
238 | **5.2.** If You initiate litigation against any entity by asserting a patent
239 | infringement claim (excluding declaratory judgment actions,
240 | counter-claims, and cross-claims) alleging that a Contributor Version
241 | directly or indirectly infringes any patent, then the rights granted to
242 | You by any and all Contributors for the Covered Software under Section
243 | 2.1 of this License shall terminate.
244 |
245 | **5.3.** In the event of termination under Sections 5.1 or 5.2 above, all
246 | end user license agreements (excluding distributors and resellers) which
247 | have been validly granted by You or Your distributors under this License
248 | prior to termination shall survive termination.
249 |
250 | ## 6. Disclaimer of Warranty
251 |
252 | > Covered Software is provided under this License on an “as is”
253 | > basis, without warranty of any kind, either expressed, implied, or
254 | > statutory, including, without limitation, warranties that the
255 | > Covered Software is free of defects, merchantable, fit for a
256 | > particular purpose or non-infringing. The entire risk as to the
257 | > quality and performance of the Covered Software is with You.
258 | > Should any Covered Software prove defective in any respect, You
259 | > (not any Contributor) assume the cost of any necessary servicing,
260 | > repair, or correction. This disclaimer of warranty constitutes an
261 | > essential part of this License. No use of any Covered Software is
262 | > authorized under this License except under this disclaimer.
263 |
264 | ## 7. Limitation of Liability
265 |
266 | > Under no circumstances and under no legal theory, whether tort
267 | > (including negligence), contract, or otherwise, shall any
268 | > Contributor, or anyone who distributes Covered Software as
269 | > permitted above, be liable to You for any direct, indirect,
270 | > special, incidental, or consequential damages of any character
271 | > including, without limitation, damages for lost profits, loss of
272 | > goodwill, work stoppage, computer failure or malfunction, or any
273 | > and all other commercial damages or losses, even if such party
274 | > shall have been informed of the possibility of such damages. This
275 | > limitation of liability shall not apply to liability for death or
276 | > personal injury resulting from such party's negligence to the
277 | > extent applicable law prohibits such limitation. Some
278 | > jurisdictions do not allow the exclusion or limitation of
279 | > incidental or consequential damages, so this exclusion and
280 | > limitation may not apply to You.
281 |
282 | ## 8. Litigation
283 |
284 | Any litigation relating to this License may be brought only in the
285 | courts of a jurisdiction where the defendant maintains its principal
286 | place of business and such litigation shall be governed by laws of that
287 | jurisdiction, without reference to its conflict-of-law provisions.
288 | Nothing in this Section shall prevent a party's ability to bring
289 | cross-claims or counter-claims.
290 |
291 | ## 9. Miscellaneous
292 |
293 | This License represents the complete agreement concerning the subject
294 | matter hereof. If any provision of this License is held to be
295 | unenforceable, such provision shall be reformed only to the extent
296 | necessary to make it enforceable. Any law or regulation which provides
297 | that the language of a contract shall be construed against the drafter
298 | shall not be used to construe this License against a Contributor.
299 |
300 | ## 10. Versions of the License
301 |
302 | ### 10.1. New Versions
303 |
304 | Mozilla Foundation is the license steward. Except as provided in Section
305 | 10.3, no one other than the license steward has the right to modify or
306 | publish new versions of this License. Each version will be given a
307 | distinguishing version number.
308 |
309 | ### 10.2. Effect of New Versions
310 |
311 | You may distribute the Covered Software under the terms of the version
312 | of the License under which You originally received the Covered Software,
313 | or under the terms of any subsequent version published by the license
314 | steward.
315 |
316 | ### 10.3. Modified Versions
317 |
318 | If you create software not governed by this License, and you want to
319 | create a new license for such software, you may create and use a
320 | modified version of this License if you rename the license and remove
321 | any references to the name of the license steward (except to note that
322 | such modified license differs from this License).
323 |
324 | ### 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses
325 |
326 | If You choose to distribute Source Code Form that is Incompatible With
327 | Secondary Licenses under the terms of this version of the License, the
328 | notice described in Exhibit B of this License must be attached.
329 |
330 | ## Exhibit A - Source Code Form License Notice
331 |
332 | This Source Code Form is subject to the terms of the Mozilla Public
333 | License, v. 2.0. If a copy of the MPL was not distributed with this
334 | file, You can obtain one at https://mozilla.org/MPL/2.0/.
335 |
336 | If it is not possible or desirable to put the notice in a particular
337 | file, then You may include the notice in a location (such as a LICENSE
338 | file in a relevant directory) where a recipient would be likely to look
339 | for such a notice.
340 |
341 | You may add additional accurate notices of copyright ownership.
342 |
343 | ## Exhibit B - “Incompatible With Secondary Licenses” Notice
344 |
345 | This Source Code Form is "Incompatible With Secondary Licenses", as
346 | defined by the Mozilla Public License, v. 2.0.
347 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
8 |
9 | # `lsp-cli` — CLI Language Client for LSP Language Servers
10 |
11 | `lsp-cli` implements a language client according to the [Language Server Protocol (LSP)](https://microsoft.github.io/language-server-protocol/) with a command-line interface (CLI).
12 |
13 | Language servers written for the LSP can usually only be used with a language client, which is typically an editor like VS Code. With `lsp-cli`, language servers can also be used on the command line. This allows you to harness the power of language servers for scripting, build pipelines, etc.
14 |
15 | Most [existing language servers](https://microsoft.github.io/language-server-protocol/implementors/servers/) should be supported, but `lsp-cli` was created for [LTEX LS](https://github.com/valentjn/ltex-ls) (a language server for LanguageTool), which is primary use case of `lsp-cli` and drives its development.
16 |
17 | ## Features
18 |
19 | - Printing diagnostics (linting) for each checked file
20 | - Printing code actions for each diagnostic
21 | - Supplying client configuration to the server
22 | - Customization of usage message and default argument values via JSON file (path supplied by environment variable)
23 |
24 | If you'd like support for other LSP features as well, please open a feature request or a pull request.
25 |
26 | ## Known Limitations
27 |
28 | - Client-side requests of diagnostics are currently not supported by the LSP (see [microsoft/language-server-protocol#737](https://github.com/microsoft/language-server-protocol/issues/737) and [current proposal for LSP 3.17](https://github.com/microsoft/vscode-languageserver-node/blob/eba6a7308b21ab94bd412fbfa63e36964b6d82ad/protocol/src/common/proposed.diagnostics.md)). Therefore, `lsp-cli` relies on the server sending a `textDocument/publishDiagnostics` notification for every opened file, even if there are no diagnostics.
29 |
30 | ## Requirements
31 |
32 | - 64-bit Linux, Mac, or Windows operating system; alternatively, an arbitrary operating system with Java installed
33 |
34 | ## Installation
35 |
36 | 1. Download the [latest release](https://github.com/valentjn/lsp-cli/releases/latest) from GitHub.
37 | - It's recommended that you choose the archive corresponding to your platform (these archives are standalone, no Java installation necessary).
38 | - If you choose the platform-independent file `lsp-cli-VERSION.tar.gz`, then you need Java 11 or later on your computer.
39 | 2. Extract the archive to an arbitrary location on your computer.
40 |
41 | ## Startup
42 |
43 | It is recommended to use the startup scripts `bin/lsp-cli` (Linux, Mac) and `bin\lsp-cli.bat` (Windows) to start `lsp-cli`. These scripts are only part of the released versions. The startup scripts can be controlled by the following environment variables:
44 |
45 | - `JAVA_HOME`: Path to the directory of the Java distribution to use (contains `bin`, `lib`, and other subdirectories). If set, this overrides the included Java distribution when using a platform-dependent `lsp-cli` archive.
46 | - `JAVA_OPTS`: Java arguments to be fed to `java` (e.g., `-Xmx1024m`)
47 |
48 | It is also possible to start `lsp-cli` directly without the startup scripts (not recommended).
49 |
50 | ### Command-Line Arguments
51 |
52 | Any command-line arguments supplied to the startup scripts are processed by `lsp-cli` itself. The possible arguments are as follows:
53 |
54 | - `--client-configuration=`: Use the client configuration stored in the JSON file ``. The format is usually nested JSON objects (e.g., `{"latex": {"commands": ...}}`).\
55 | Only for LTEX LS: A flattened JSON object (`{"latex.commands": ...}`) is also allowed, and setting names may be prefixed by a top level named `ltex` (e.g., `{"ltex.latex.commands": ...}` is accepted as well).
56 | - `-h`, `--help`: Show help message and exit.
57 | - `--hide-commands`: Hide commands in lists of code actions for diagnostics, only show quick fixes.
58 | - `--server-command-line=`: Required. Command line to start the language server, starting with the path of its executable. If you want to supply arguments to the language server, separate them with spaces. If the path of the executable or one of the arguments contain spaces, you can escape them by using `\ ` instead. In `.lsp-cli.json`, this option can either be specified as an array of arguments or as a string with space-separated arguments.
59 | - `--server-working-directory=`: Working directory for `--server-command-line`. If omitted, use the parent directory of `.lsp-cli.json` if given, otherwise use the current working directory.
60 | - `-V`, `--version`: Print version information as JSON to the standard output and exit. The format is a JSON object with `"java"` and `"lsp-cli"` keys and string values. A key may be missing if no information about the corresponding version could be retrieved.
61 | - `--verbose`: Write to standard error output what is being done.
62 | - ` ...`: Required. Paths of files or directories to check. Directories are traversed recursively for supported file types. If `-` is given, standard input will be checked as plain text.
63 |
64 | Instead of using the equals sign `=` to separate option names and values, it is also possible to use one or more spaces.
65 |
66 | ### `.lsp-cli.json` Configuration File
67 |
68 | The appearance of help messages (e.g., `--help`) and the default values of arguments can also be controlled via a special JSON file, which is usually named `.lsp-cli.json`.
69 |
70 | The file is located via checking the environment variable `LSP_CLI_JSON_SETTINGS_PATH`. If this path points to a file, then that file will be used. If it points to a directory, then the file named `.lsp-cli.json` in that directory will be used. The behavior of `lsp-cli` is controlled by the command-line arguments as usual, if `LSP_CLI_JSON_SETTINGS_PATH` is not set or the path it contains does not exist.
71 |
72 | The JSON file is given below in TypeScript. When specifying arguments, always use their full names as strings (e.g., `"--server-working-directory"`).
73 |
74 | ```typescript
75 | interface LspCliJson {
76 | /**
77 | * Program name to use in help and error messages (default: `lsp-cli`).
78 | */
79 | programName?: string;
80 |
81 | helpMessage?: {
82 | /**
83 | * Description of the program.
84 | */
85 | description?: string;
86 |
87 | /**
88 | * List of arguments to hide.
89 | * If both `hiddenArguments` and `visibleArguments` are given, `visibleArguments` wins.
90 | */
91 | hiddenArguments?: string[];
92 |
93 | /**
94 | * List of arguments to show; hide all other arguments.
95 | * If both `hiddenArguments` and `visibleArguments` are given, `visibleArguments` wins.
96 | */
97 | visibleArguments?: string[];
98 | };
99 |
100 | defaultValues?: {
101 | /**
102 | * Default values for arguments, if they are not specified on the command line.
103 | * The types of the values depend on the arguments (e.g., use `true` for Boolean flags).
104 | */
105 | [argument: string]: any;
106 | };
107 | }
108 | ```
109 |
110 | ### Exit Codes
111 |
112 | - 0: `lsp-cli` exited successfully, and the language server reported no diagnostics for the checked files.
113 | - 1: An exception was thrown during the execution of `lsp-cli`.
114 | - 2: An invalid command-line argument was supplied to `lsp-cli`.
115 | - 3: The language server reported at least one diagnostic for the checked files.
116 |
--------------------------------------------------------------------------------
/changelog.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 | Changelog
12 | Julian Valentin, lsp-cli Development Community
13 |
14 |
15 |
16 |
17 | Bump Kotlin to 1.6.0
18 |
19 |
20 | Bump picocli to 4.6.2
21 |
22 |
23 |
24 |
25 | Fix language server sometimes not terminated
26 |
27 |
28 | Fix error when running `lsp-cli` from a different directory than `bin/`
29 |
30 |
31 | Fix usage help of command-line arguments
32 |
33 |
34 |
35 |
36 | Rename internal messages bundle for internationalization to avoid name clashes
37 |
38 |
39 |
40 |
41 | Initial release
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/lspcli.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 | 4.0.0
11 | org.bsplines
12 | lspcli
13 | 1.0.4-alpha.1.develop
14 | ${project.groupId}:${project.artifactId}
15 | lsp-cli: CLI language client for LSP language servers
16 | https://github.com/valentjn/lsp-cli
17 |
18 | scm:git:git://github.com/valentjn/lsp-cli.git
19 | scm:git:ssh://github.com:valentjn/lsp-cli.git
20 | https://github.com/valentjn/lsp-cli/tree/develop
21 |
22 |
23 |
24 | valentjn
25 | Julian Valentin
26 |
27 |
28 |
29 |
30 | Mozilla Public License, Version 2.0
31 | https://mozilla.org/MPL/2.0/
32 |
33 |
34 |
35 | 11
36 | 1.6.10
37 | 1.6
38 | ${java.version}
39 | true
40 | org.bsplines.lspcli.LspCliLauncher
41 | UTF-8
42 | 5.8.2
43 |
44 |
45 |
46 | org.jetbrains.kotlin
47 | kotlin-stdlib-jdk8
48 | ${kotlin.version}
49 |
50 |
51 | org.eclipse.lsp4j
52 | org.eclipse.lsp4j
53 | 0.12.0
54 |
55 |
56 | com.google.code.gson
57 | gson
58 | 2.8.9
59 |
60 |
61 | org.fusesource.jansi
62 | jansi
63 | 2.4.0
64 |
65 |
66 | info.picocli
67 | picocli
68 | 4.6.2
69 |
70 |
71 | org.jetbrains.kotlin
72 | kotlin-test-junit5
73 | ${kotlin.version}
74 | test
75 |
76 |
77 | org.junit.jupiter
78 | junit-jupiter
79 | ${junit.jupiter.version}
80 | test
81 |
82 |
83 |
84 | ${project.basedir}/src/main/kotlin
85 | ${project.basedir}/src/test/kotlin
86 |
87 |
88 | org.jetbrains.kotlin
89 | kotlin-maven-plugin
90 | ${kotlin.version}
91 |
92 |
93 |
94 | compile
95 |
96 | compile
97 |
98 |
99 |
100 |
101 | test-compile
102 |
103 | test-compile
104 |
105 |
106 |
107 |
108 |
109 | org.apache.maven.plugins
110 | maven-dependency-plugin
111 | 3.2.0
112 |
113 |
114 |
115 |
116 | properties
117 |
118 |
119 |
120 |
121 |
122 | com.github.ozsie
123 | detekt-maven-plugin
124 | 1.19.1
125 |
126 | true
127 | ${project.basedir}/.detekt.yml
128 |
129 |
130 |
131 | verify
132 |
133 | check
134 |
135 |
136 |
137 |
138 |
139 | org.apache.maven.plugins
140 | maven-surefire-plugin
141 | 2.22.2
142 |
143 |
144 | org.jacoco
145 | jacoco-maven-plugin
146 | 0.8.7
147 |
148 |
149 | prepare-agent
150 |
151 | prepare-agent
152 |
153 |
154 |
155 | check
156 |
157 | report
158 | check
159 |
160 |
161 |
162 |
163 | BUNDLE
164 |
165 |
166 | LINE
167 | COVEREDRATIO
168 | 0%
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 | org.apache.maven.plugins
179 | maven-jar-plugin
180 | 3.2.0
181 |
182 |
183 |
184 | true
185 | ${main.class}
186 |
187 |
188 |
189 |
190 |
191 | org.codehaus.mojo
192 | appassembler-maven-plugin
193 | 2.1.0
194 |
195 |
196 |
197 | ${main.class}
198 | lsp-cli
199 |
200 |
201 | true
202 | flat
203 | lib
204 | true
205 |
206 |
207 |
208 | package
209 |
210 | assemble
211 |
212 |
213 |
214 |
215 |
216 | org.apache.maven.plugins
217 | maven-antrun-plugin
218 | 3.0.0
219 |
220 |
221 | ktlint
222 | verify
223 |
224 |
225 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 | run
237 |
238 |
239 |
240 | patch-bin-scripts
241 |
242 |
243 |
244 |
245 | if "%JAVACMD%"=="" set JAVACMD=java
246 | NUL 2>&1
253 | if "%ERRORLEVEL%" == "0" goto init
254 |
255 | echo.
256 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
257 | echo.
258 | echo Please set the JAVA_HOME variable in your environment to match the
259 | echo location of your Java installation.
260 |
261 | goto error
262 |
263 | :findJavaFromJavaHome
264 | set JAVA_HOME=%JAVA_HOME:"=%
265 | set JAVACMD=%JAVA_HOME%/bin/java.exe
266 |
267 | if exist "%JAVACMD%" goto init
268 |
269 | echo.
270 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
271 | echo.
272 | echo Please set the JAVA_HOME variable in your environment to match the
273 | echo location of your Java installation.
274 |
275 | goto error
276 |
277 | :init]]>
278 |
279 |
280 | %JAVACMD% %JAVA_OPTS% -classpath %CLASSPATH%
281 | "%JAVACMD%" %JAVA_OPTS% -classpath %CLASSPATH%
282 |
283 |
284 | set ERROR_CODE=%ERRORLEVEL%
285 | set ERROR_CODE=%ERRORLEVEL%
286 | if %ERROR_CODE% EQU 0 set ERROR_CODE=1
287 |
288 |
289 |
290 | package
291 |
292 | run
293 |
294 |
295 |
296 |
297 |
298 | com.pinterest
299 | ktlint
300 | 0.43.2
301 |
302 |
303 |
304 |
305 | org.apache.maven.plugins
306 | maven-assembly-plugin
307 | 3.3.0
308 |
309 |
310 | .assembly.xml
311 |
312 | lsp-cli-${project.version}
313 | false
314 |
315 |
316 |
317 | make-assembly
318 | package
319 |
320 | single
321 |
322 |
323 |
324 |
325 |
326 | org.eluder.coveralls
327 | coveralls-maven-plugin
328 | 4.3.0
329 |
330 |
331 | javax.xml.bind
332 | jaxb-api
333 | 2.3.1
334 |
335 |
336 |
337 |
338 |
339 |
340 |
--------------------------------------------------------------------------------
/schemas/changes-1.0.0.xsd:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
23 | 1.0.0
24 |
25 | Record every release with their subsequent changes.
26 |
27 |
28 |
29 |
30 |
31 | 1.0.0
32 |
33 | Record every release with their subsequent changes.
34 |
35 |
36 |
37 |
38 |
39 | 1.0.0
40 |
41 | Contains the properties of this document.
42 |
43 |
44 |
45 |
46 |
47 | 1.0.0
48 |
49 | Contains the releases of this project with the actions taken
50 | for each of the releases.
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | 1.0.0
59 |
60 |
61 |
62 |
63 | 1.0.0
64 | The list of releases for this project.
65 |
66 |
67 |
68 |
69 |
70 |
71 | 1.0.0
72 | A single release of this project.
73 |
74 |
75 |
76 |
77 | 1.0.0
78 | The list of actions taken for this release.
79 |
80 |
81 |
82 |
83 |
84 | 1.0.0
85 |
86 | The version number associated with this release.
87 |
88 |
89 |
90 |
91 |
92 | 1.0.0
93 |
94 |
95 | <p>The date of this release.</p>
96 | <p>This field can be any String, such as "in SVN" when the version isn't yet released. </p>
97 |
98 |
99 |
100 |
101 |
102 |
103 | 1.0.0
104 |
105 | A short description of this release.
106 |
107 |
108 |
109 |
110 |
111 |
112 | 1.0.0
113 |
114 | A single action done on the project, during this release.
115 |
116 |
117 |
118 |
119 |
120 | 1.0.0
121 | A list of fix issues.
122 |
123 |
124 |
125 |
126 | 1.0.0
127 | A list of contibutors for this issue.
128 |
129 |
130 |
131 |
132 |
133 | 1.0.0
134 |
135 |
136 | <p>Name of developer who committed the change.</p>
137 | <p>This <b>MUST</b> be the name of the developer as described in the developers section of the pom.xml file.</p>
138 |
139 |
140 |
141 |
142 |
143 |
144 | 1.0.0
145 |
146 | Name of the person to be credited for this change. This can be used when a patch is submitted by a non-committer.
147 |
148 |
149 |
150 |
151 |
152 | 1.0.0
153 |
154 | Email of the person to be credited for this change.
155 |
156 |
157 |
158 |
159 |
160 | 1.0.0
161 |
162 |
163 | <p>Id of the issue related to this change. This is the id in your issue tracking system.</p>
164 | <p>The Changes plugin will generate a URL out of this id. The URL is constructed using the value of the issueLinkTemplate parameter.</p>
165 | <p>See the <a href="changes-report.html">changes-report mojo</a> for more details.</p>
166 |
167 |
168 |
169 |
170 |
171 |
172 | 1.0.0
173 |
174 |
175 | Supported action types are the following:
176 | <ul>
177 | <li>add : added functionnality to the project.</li>
178 | <li>fix : bug fix for the project.</li>
179 | <li>update : updated some part of the project.</li>
180 | <li>remove : removed some functionnality from the project.</li>
181 | </ul>
182 |
183 |
184 |
185 |
186 |
187 |
188 | 1.0.0
189 |
190 |
191 | <p>Id of issue tracking system. If empty 'default' value will be use.</p>
192 | <p>The Changes plugin will generate a URL out of this id. The URL is constructed using the value of the issueLinkTemplatePerSystem parameter.</p>
193 | <p>See the <a href="changes-report.html">changes-report mojo</a> for more details.</p>
194 |
195 |
196 |
197 |
198 |
199 |
200 | 1.0.0
201 | fix date
202 |
203 |
204 |
205 |
206 |
207 | 1.0.0
208 |
209 | A fixed issue.
210 |
211 |
212 |
213 |
214 | 1.0.0
215 |
216 |
217 | <p>Id of the issue related to this change. This is the id in your issue tracking system.</p>
218 | <p>The Changes plugin will generate a URL out of this id. The URL is constructed using the value of the issueLinkTemplate parameter.</p>
219 | <p>See the <a href="changes-report.html">changes-report mojo</a> for more details.</p>
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 | 1.0.0
228 |
229 | Name and Email of the person to be credited for this change. This can be used when a patch is submitted by a non-committer.
230 |
231 |
232 |
233 |
234 | 1.0.0
235 | Name of the person to be credited for this change.
236 |
237 |
238 |
239 |
240 | 1.0.0
241 | Email of the person to be credited for this change.
242 |
243 |
244 |
245 |
246 |
247 | 1.0.0
248 |
249 |
250 |
251 |
252 | 1.0.0
253 | Page Title.
254 |
255 |
256 |
257 |
258 | 1.0.0
259 | Page Author
260 |
261 |
262 |
263 |
264 |
265 |
266 | 1.0.0
267 |
268 | A description of the author page.
269 |
270 |
271 |
272 |
273 |
274 | 1.0.0
275 |
276 | The page author email.
277 |
278 |
279 |
280 |
281 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/bsplines/lspcli/LspCliLauncher.kt:
--------------------------------------------------------------------------------
1 | /* Copyright (C) 2021 Julian Valentin, lsp-cli Development Community
2 | *
3 | * This Source Code Form is subject to the terms of the Mozilla Public
4 | * License, v. 2.0. If a copy of the MPL was not distributed with this
5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 | */
7 |
8 | package org.bsplines.lspcli
9 |
10 | import com.google.gson.JsonElement
11 | import org.bsplines.lspcli.client.Checker
12 | import org.bsplines.lspcli.client.LspCliLanguageClient
13 | import org.bsplines.lspcli.tools.I18n
14 | import org.bsplines.lspcli.tools.Logging
15 | import org.bsplines.lspcli.tools.LspCliSettings
16 | import org.bsplines.lspcli.tools.VersionProvider
17 | import org.fusesource.jansi.AnsiConsole
18 | import picocli.CommandLine
19 | import java.nio.file.Path
20 | import java.util.concurrent.Callable
21 | import java.util.logging.Level
22 | import kotlin.system.exitProcess
23 |
24 | @CommandLine.Command(
25 | name = "lsp-cli",
26 | mixinStandardHelpOptions = true,
27 | showDefaultValues = true,
28 | versionProvider = VersionProvider::class,
29 | description = ["lsp-cli - CLI language client for LSP language servers"]
30 | )
31 | class LspCliLauncher : Callable {
32 | @CommandLine.Option(
33 | names = ["--server-command-line"],
34 | paramLabel = "",
35 | required = true,
36 | description = [
37 | "Required. Command line to start the language server, starting with the path of its "
38 | + "executable. If you want to supply arguments to the language server, separate them with "
39 | + "spaces. If the path of the executable or one of the arguments contain spaces, you can "
40 | + "escape them by using '\\ ' instead. In .lsp-cli.json, this option can either be "
41 | + "specified as an array of arguments or as a string with space-separated arguments.",
42 | ]
43 | )
44 | var serverCommandLineString: String? = null
45 |
46 | var serverCommandLineList: List? = null
47 |
48 | @CommandLine.Option(
49 | names = ["--server-working-directory"],
50 | paramLabel = "",
51 | description = [
52 | "Working directory for --server-command-line. If omitted, use the parent directory of "
53 | + "`.lsp-cli.json` if given, otherwise use the current working directory.",
54 | ]
55 | )
56 | var serverWorkingDirPath: Path? = null
57 |
58 | @CommandLine.Option(
59 | names = ["--client-configuration"],
60 | paramLabel = "",
61 | description = [
62 | "Use the client configuration stored in the JSON file . The format is usually nested "
63 | + "JSON objects (e.g., {\"latex\": {\"commands\": ...}}).",
64 | ]
65 | )
66 | var clientConfigurationFilePath: Path? = null
67 |
68 | @CommandLine.Option(
69 | names = ["--hide-commands"],
70 | description = [
71 | "Hide commands in lists of code actions for diagnostics, only show quick fixes.",
72 | ]
73 | )
74 | var hideCommands: Boolean = false
75 |
76 | @CommandLine.Option(
77 | names = ["--verbose"],
78 | description = [
79 | "Write to standard error output what is being done.",
80 | ]
81 | )
82 | var verbose: Boolean = false
83 |
84 | @CommandLine.Parameters(
85 | paramLabel = "",
86 | arity = "1..*",
87 | description = [
88 | "Paths of files or directories to check. "
89 | + "Directories are traversed recursively for supported file types. "
90 | + "If - is given, standard input will be checked as plain text.",
91 | ]
92 | )
93 | var inputFilePaths: List = emptyList()
94 |
95 | constructor()
96 |
97 | constructor(parseResult: CommandLine.ParseResult, lspCliSettings: LspCliSettings) {
98 | if (parseResult.hasMatchedOption("--server-command-line")) {
99 | this.serverCommandLineString = parseResult.matchedOptionValue("--server-command-line", null)
100 | } else {
101 | val jsonElement: JsonElement? =
102 | lspCliSettings.getValue("defaultValues", "--server-command-line")
103 |
104 | if (jsonElement?.isJsonArray == true) {
105 | this.serverCommandLineList = jsonElement.asJsonArray.map { it.asString }
106 | } else if (jsonElement != null) {
107 | this.serverCommandLineString = jsonElement.asString
108 | }
109 | }
110 |
111 | this.serverWorkingDirPath = if (parseResult.hasMatchedOption("--server-working-directory")) {
112 | parseResult.matchedOptionValue("--server-working-directory", null)
113 | } else {
114 | val string: String? = lspCliSettings.getValue(
115 | "defaultValues",
116 | "--server-working-directory",
117 | )?.asString
118 |
119 | if (string != null) {
120 | Path.of(string)
121 | } else {
122 | lspCliSettings.settingsFilePath?.toAbsolutePath()?.parent
123 | }
124 | }
125 |
126 | this.clientConfigurationFilePath = if (parseResult.hasMatchedOption("--client-configuration")) {
127 | parseResult.matchedOptionValue("--client-configuration", null)
128 | } else {
129 | lspCliSettings.getValue(
130 | "defaultValues",
131 | "--client-configuration",
132 | )?.asString?.let { Path.of(it) }
133 | }
134 |
135 | this.hideCommands = if (parseResult.hasMatchedOption("--hide-commands")) {
136 | parseResult.matchedOptionValue("--hide-commands", false)
137 | } else {
138 | lspCliSettings.getValue("defaultValues", "--hide-commands")?.asBoolean ?: false
139 | }
140 |
141 | this.verbose = if (parseResult.hasMatchedOption("--verbose")) {
142 | parseResult.matchedOptionValue("--verbose", false)
143 | } else {
144 | lspCliSettings.getValue("defaultValues", "--verbose")?.asBoolean ?: false
145 | }
146 |
147 | if (parseResult.matchedPositionals().isNotEmpty()) {
148 | this.inputFilePaths = parseResult.matchedPositionals()[0].getValue()
149 | }
150 | }
151 |
152 | override fun call(): Int {
153 | if (this.verbose) Logging.setLogLevel(Level.INFO)
154 |
155 | val serverCommandLine: List = (
156 | this.serverCommandLineList ?: this.serverCommandLineString?.let {
157 | serverCommandLineString: String ->
158 | NON_ESCAPED_SPACE_REGEX.split(serverCommandLineString).map {
159 | ESCAPED_SPACE_REGEX.replace(it, " ")
160 | }
161 | } ?: throw IllegalArgumentException(
162 | I18n.format("requiredArgumentNotSpecified", "--server-command-line"),
163 | )
164 | )
165 |
166 | val client = LspCliLanguageClient(
167 | serverCommandLine,
168 | this.serverWorkingDirPath,
169 | this.clientConfigurationFilePath,
170 | )
171 |
172 | try {
173 | val checker = Checker(client, hideCommands = this.hideCommands)
174 | val numberOfMatches: Int = checker.check(this.inputFilePaths)
175 | return (if (numberOfMatches == 0) 0 else EXIT_CODE_MATCHES_FOUND)
176 | } finally {
177 | client.languageServer.shutdown()
178 | client.languageServerProcess.destroy()
179 | }
180 | }
181 |
182 | companion object {
183 | private const val EXIT_CODE_MATCHES_FOUND = 3
184 |
185 | private val ESCAPED_SPACE_REGEX = Regex("\\\\ ")
186 | private const val NON_ESCAPED_SPACE_REGEX_STRING = "(?) {
192 | AnsiConsole.systemInstall()
193 |
194 | val lspCliSettingsFilePath: Path? = getLspCliSettingsFilePath()
195 | val lspCliSettings = LspCliSettings(lspCliSettingsFilePath)
196 |
197 | val commandLine: CommandLine = createCommandLine(lspCliSettings)
198 | val commandSpec: CommandLine.Model.CommandSpec = commandLine.commandSpec
199 |
200 | try {
201 | val parseResult: CommandLine.ParseResult = commandLine.parseArgs(*arguments)
202 |
203 | if (parseResult.isUsageHelpRequested) {
204 | commandLine.usage(commandLine.out)
205 | exitProcess(commandSpec.exitCodeOnUsageHelp())
206 | } else if (parseResult.isVersionHelpRequested) {
207 | commandLine.printVersionHelp(commandLine.out)
208 | exitProcess(commandSpec.exitCodeOnVersionHelp())
209 | }
210 |
211 | val launcher = LspCliLauncher(parseResult, lspCliSettings)
212 | val exitCode: Int = launcher.call()
213 | if (exitCode != 0) exitProcess(exitCode)
214 | } catch (e: CommandLine.ParameterException) {
215 | commandLine.err.println(e.message)
216 |
217 | if (!CommandLine.UnmatchedArgumentException.printSuggestions(e, commandLine.err)) {
218 | e.commandLine.usage(commandLine.err)
219 | }
220 |
221 | exitProcess(commandSpec.exitCodeOnInvalidInput())
222 | } catch (e: Exception) {
223 | e.printStackTrace(commandLine.err)
224 | exitProcess(commandSpec.exitCodeOnExecutionException())
225 | }
226 | }
227 |
228 | private fun getLspCliSettingsFilePath(): Path? {
229 | val environmentVariable: String = System.getenv("LSP_CLI_JSON_SETTINGS_PATH") ?: ""
230 | val path: Path? = if (environmentVariable.isNotEmpty()) {
231 | Path.of(environmentVariable)
232 | } else {
233 | val appHome: String = System.getProperty("app.home", "")
234 | if (appHome.isNotEmpty()) Path.of(appHome, "bin") else null
235 | }
236 |
237 | return if (path == null) {
238 | null
239 | } else if (path.toFile().isFile) {
240 | path
241 | } else if (path.toFile().isDirectory) {
242 | val filePath: Path = path.resolve(".lsp-cli.json")
243 | if (filePath.toFile().isFile) filePath else null
244 | } else {
245 | null
246 | }
247 | }
248 |
249 | private fun createCommandLine(lspCliSettings: LspCliSettings): CommandLine {
250 | val defaultValues: Map? = lspCliSettings.getValueAsMap("defaultValues")
251 | val hiddenArguments: List? =
252 | lspCliSettings.getValueAsListOfString("helpMessage", "hiddenArguments")
253 | val visibleArguments: List? =
254 | lspCliSettings.getValueAsListOfString("helpMessage", "visibleArguments")
255 |
256 | val classCommandSpec = CommandLine.Model.CommandSpec.forAnnotatedObject(LspCliLauncher())
257 | val commandSpec = CommandLine.Model.CommandSpec.create()
258 |
259 | commandSpec.usageMessage(classCommandSpec.usageMessage())
260 |
261 | val usageMessageDescription: String? =
262 | lspCliSettings.getValue("helpMessage", "description")?.asString
263 |
264 | if (usageMessageDescription != null) {
265 | commandSpec.usageMessage().description(usageMessageDescription)
266 | }
267 |
268 | for (classOptionSpec: CommandLine.Model.OptionSpec in classCommandSpec.options()) {
269 | commandSpec.addOption(
270 | createOptionSpec(classOptionSpec, defaultValues, hiddenArguments, visibleArguments),
271 | )
272 | }
273 |
274 | for (
275 | positionalParamSpec: CommandLine.Model.PositionalParamSpec
276 | in classCommandSpec.positionalParameters()
277 | ) {
278 | commandSpec.addPositional(positionalParamSpec)
279 | }
280 |
281 | val commandLine = CommandLine(commandSpec)
282 | commandLine.commandName = lspCliSettings.getValue("programName")?.asString ?: "lsp-cli"
283 | commandLine.isCaseInsensitiveEnumValuesAllowed = true
284 |
285 | return commandLine
286 | }
287 |
288 | private fun createOptionSpec(
289 | classOptionSpec: CommandLine.Model.OptionSpec,
290 | defaultValues: Map?,
291 | hiddenArguments: List?,
292 | visibleArguments: List?,
293 | ): CommandLine.Model.OptionSpec {
294 | val optionSpecBuilder: CommandLine.Model.OptionSpec.Builder = classOptionSpec.toBuilder()
295 |
296 | if (defaultValues != null) {
297 | for (name: String in optionSpecBuilder.names()) {
298 | val defaultValueString: String? = defaultValues[name]?.toString()
299 | if (defaultValueString != null) optionSpecBuilder.defaultValue(defaultValueString)
300 | }
301 | }
302 |
303 | val hidden = if (visibleArguments != null) {
304 | !visibleArguments.contains(classOptionSpec.longestName())
305 | } else {
306 | hiddenArguments?.contains(classOptionSpec.longestName()) ?: false
307 | }
308 |
309 | optionSpecBuilder.hidden(hidden)
310 | return optionSpecBuilder.build()
311 | }
312 | }
313 | }
314 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/bsplines/lspcli/client/Checker.kt:
--------------------------------------------------------------------------------
1 | /* Copyright (C) 2021 Julian Valentin, lsp-cli Development Community
2 | *
3 | * This Source Code Form is subject to the terms of the Mozilla Public
4 | * License, v. 2.0. If a copy of the MPL was not distributed with this
5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 | */
7 |
8 | package org.bsplines.lspcli.client
9 |
10 | import org.bsplines.lspcli.tools.FileIo
11 | import org.bsplines.lspcli.tools.I18n
12 | import org.bsplines.lspcli.tools.Logging
13 | import org.eclipse.lsp4j.CodeAction
14 | import org.eclipse.lsp4j.CodeActionContext
15 | import org.eclipse.lsp4j.CodeActionParams
16 | import org.eclipse.lsp4j.Command
17 | import org.eclipse.lsp4j.Diagnostic
18 | import org.eclipse.lsp4j.DiagnosticSeverity
19 | import org.eclipse.lsp4j.DidOpenTextDocumentParams
20 | import org.eclipse.lsp4j.Position
21 | import org.eclipse.lsp4j.TextDocumentIdentifier
22 | import org.eclipse.lsp4j.jsonrpc.messages.Either
23 | import org.fusesource.jansi.Ansi
24 | import org.fusesource.jansi.Ansi.Color
25 | import org.fusesource.jansi.AnsiConsole
26 | import java.io.ByteArrayOutputStream
27 | import java.nio.charset.StandardCharsets
28 | import java.nio.file.Files
29 | import java.nio.file.Path
30 | import java.util.stream.Collectors
31 | import kotlin.math.ceil
32 |
33 | class Checker(
34 | val languageClient: LspCliLanguageClient,
35 | val hideCommands: Boolean = false,
36 | ) {
37 | fun check(paths: List): Int {
38 | var numberOfMatches = 0
39 | for (path: Path in paths) numberOfMatches += check(path)
40 | return numberOfMatches
41 | }
42 |
43 | fun check(path: Path): Int {
44 | val text: String
45 | val codeLanguageId: String
46 |
47 | if (path.toString() == "-") {
48 | val outputStream = ByteArrayOutputStream()
49 | val buffer = ByteArray(STANDARD_INPUT_BUFFER_SIZE)
50 |
51 | while (true) {
52 | val length = System.`in`.read(buffer)
53 | if (length == -1) break
54 | outputStream.write(buffer, 0, length)
55 | }
56 |
57 | text = outputStream.toString(StandardCharsets.UTF_8)
58 | codeLanguageId = "plaintext"
59 | } else if (path.toFile().isDirectory) {
60 | return checkDirectory(path)
61 | } else {
62 | text = FileIo.readFileWithException(path)
63 | codeLanguageId = FileIo.getCodeLanguageIdFromPath(path) ?: "plaintext"
64 | }
65 |
66 | return checkFile(path, codeLanguageId, text)
67 | }
68 |
69 | private fun checkDirectory(path: Path): Int {
70 | var numberOfMatches = 0
71 |
72 | for (childPath: Path in Files.walk(path).collect(Collectors.toList())) {
73 | if (childPath.toFile().isFile) {
74 | val curCodeLanguageId: String? = FileIo.getCodeLanguageIdFromPath(childPath)
75 | if (curCodeLanguageId != null) numberOfMatches += check(childPath)
76 | }
77 | }
78 |
79 | return numberOfMatches
80 | }
81 |
82 | private fun checkFile(path: Path, languageId: String, text: String): Int {
83 | val uri: String = path.toUri().toString()
84 | val document = LspCliTextDocumentItem(uri, languageId, 1, text)
85 | Logging.logger.info(I18n.format("checkingFile", path.toString()))
86 |
87 | this.languageClient.languageServer.textDocumentService.didOpen(
88 | DidOpenTextDocumentParams(document),
89 | )
90 |
91 | Logging.logger.info(I18n.format("waitingForDiagnosticsForFile", path.toString()))
92 | val diagnostics: List
93 |
94 | while (true) {
95 | val curDiagnostics: List? = this.languageClient.diagnosticsMap[uri]
96 |
97 | if (curDiagnostics != null) {
98 | diagnostics = curDiagnostics
99 | break
100 | }
101 |
102 | Thread.sleep(WAIT_FOR_DIAGNOSTIC_MILLISECONDS)
103 | }
104 |
105 | val documentId = TextDocumentIdentifier(uri)
106 | val terminalWidth: Int = run {
107 | val terminalWidth: Int = AnsiConsole.getTerminalWidth()
108 | if (terminalWidth >= 2) terminalWidth else Integer.MAX_VALUE
109 | }
110 |
111 | for (diagnostic: Diagnostic in diagnostics) {
112 | val codeActionTitles = ArrayList()
113 | val codeActionResult: List> =
114 | this.languageClient.languageServer.textDocumentService.codeAction(
115 | CodeActionParams(documentId, diagnostic.range, CodeActionContext(listOf(diagnostic))),
116 | ).get()
117 |
118 | for (entry: Either in codeActionResult) {
119 | val command: Command? = entry.left
120 | val codeAction: CodeAction? = entry.right
121 |
122 | if ((command != null) && !this.hideCommands) {
123 | codeActionTitles.add(command.title)
124 | } else if ((codeAction != null) && ((codeAction.command == null) || !this.hideCommands)) {
125 | codeActionTitles.add(codeAction.title)
126 | }
127 | }
128 |
129 | printDiagnostic(path, document, diagnostic, codeActionTitles, terminalWidth)
130 | }
131 |
132 | return diagnostics.size
133 | }
134 |
135 | companion object {
136 | private val TRAILING_WHITESPACE_REGEX = Regex("[ \t\r\n]+$")
137 |
138 | private const val STANDARD_INPUT_BUFFER_SIZE = 1024
139 | private const val WAIT_FOR_DIAGNOSTIC_MILLISECONDS = 50L
140 | private const val TAB_SIZE = 8
141 |
142 | @Suppress("ComplexMethod")
143 | private fun printDiagnostic(
144 | path: Path,
145 | document: LspCliTextDocumentItem,
146 | diagnostic: Diagnostic,
147 | codeActionTitles: List,
148 | terminalWidth: Int,
149 | ) {
150 | val text: String = document.text
151 | val fromPosition: Position = diagnostic.range.start
152 | val toPosition: Position = diagnostic.range.end
153 | val fromPos: Int = document.convertPosition(fromPosition)
154 | val toPos: Int = document.convertPosition(toPosition)
155 |
156 | val color: Color = when (diagnostic.severity) {
157 | DiagnosticSeverity.Error -> Color.RED
158 | DiagnosticSeverity.Warning -> Color.YELLOW
159 | DiagnosticSeverity.Information -> Color.BLUE
160 | DiagnosticSeverity.Hint -> Color.BLUE
161 | else -> Color.BLUE
162 | }
163 | val typeString: String = when (diagnostic.severity) {
164 | DiagnosticSeverity.Error -> "error"
165 | DiagnosticSeverity.Warning -> "warning"
166 | DiagnosticSeverity.Information -> "info"
167 | DiagnosticSeverity.Hint -> "hint"
168 | else -> "info"
169 | }
170 |
171 | val diagnosticCode: String = diagnostic.code?.get()?.toString() ?: ""
172 | val ansi: Ansi = (Ansi.ansi().bold().a(path.toString()).a(":")
173 | .a(fromPosition.line + 1).a(":").a(fromPosition.character + 1).a(": ")
174 | .fg(color).a(typeString).a(":").reset().bold().a(" ").a(diagnostic.message))
175 | if (diagnosticCode.isNotEmpty()) ansi.a(" [").a(diagnosticCode).a("]")
176 | ansi.reset()
177 | println(ansi)
178 |
179 | val lineStartPos: Int = document.convertPosition(Position(fromPosition.line, 0))
180 | val lineEndPos: Int = document.convertPosition(Position(fromPosition.line + 1, 0))
181 | val line: String = text.substring(lineStartPos, lineEndPos)
182 |
183 | println(
184 | Ansi.ansi().a(line.substring(0, fromPos - lineStartPos)).bold().fg(color)
185 | .a(line.substring(fromPos - lineStartPos, toPos - lineStartPos)).reset()
186 | .a(line.substring(toPos - lineStartPos).replaceFirst(TRAILING_WHITESPACE_REGEX, "")),
187 | )
188 |
189 | var indentationSize = guessIndentationSize(text, lineStartPos, fromPos, terminalWidth)
190 |
191 | for (codeActionTitle: String in codeActionTitles) {
192 | if (indentationSize + codeActionTitle.length > terminalWidth) indentationSize = 0
193 | }
194 |
195 | val indentation: String = " ".repeat(indentationSize)
196 |
197 | for (codeActionTitle: String in codeActionTitles) {
198 | println(Ansi.ansi().a(indentation).fg(Color.GREEN).a(codeActionTitle).reset())
199 | }
200 | }
201 |
202 | private fun guessIndentationSize(
203 | text: String,
204 | lineStartPos: Int,
205 | fromPos: Int,
206 | terminalWidth: Int,
207 | ): Int {
208 | var indentationSize = 0
209 |
210 | for (pos in lineStartPos until fromPos) {
211 | if (text[pos] == '\t') {
212 | indentationSize = (ceil((indentationSize + 1.0) / TAB_SIZE) * TAB_SIZE).toInt()
213 | } else {
214 | indentationSize++
215 | }
216 |
217 | if (indentationSize >= terminalWidth) indentationSize = 0
218 | }
219 |
220 | return indentationSize
221 | }
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/bsplines/lspcli/client/LspCliLanguageClient.kt:
--------------------------------------------------------------------------------
1 | /* Copyright (C) 2021 Julian Valentin, lsp-cli Development Community
2 | *
3 | * This Source Code Form is subject to the terms of the Mozilla Public
4 | * License, v. 2.0. If a copy of the MPL was not distributed with this
5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 | */
7 |
8 | package org.bsplines.lspcli.client
9 |
10 | import com.google.gson.JsonElement
11 | import com.google.gson.JsonObject
12 | import com.google.gson.JsonParser
13 | import org.bsplines.lspcli.server.LspCliLanguageServer
14 | import org.bsplines.lspcli.tools.FileIo
15 | import org.bsplines.lspcli.tools.I18n
16 | import org.bsplines.lspcli.tools.Logging
17 | import org.eclipse.lsp4j.ConfigurationItem
18 | import org.eclipse.lsp4j.ConfigurationParams
19 | import org.eclipse.lsp4j.Diagnostic
20 | import org.eclipse.lsp4j.InitializeParams
21 | import org.eclipse.lsp4j.MessageActionItem
22 | import org.eclipse.lsp4j.MessageParams
23 | import org.eclipse.lsp4j.PublishDiagnosticsParams
24 | import org.eclipse.lsp4j.ShowMessageRequestParams
25 | import org.eclipse.lsp4j.jsonrpc.Launcher
26 | import org.eclipse.lsp4j.launch.LSPLauncher
27 | import org.eclipse.lsp4j.services.LanguageClient
28 | import org.eclipse.lsp4j.services.LanguageServer
29 | import java.nio.file.Path
30 | import java.util.concurrent.CompletableFuture
31 | import java.util.concurrent.ExecutorService
32 | import java.util.concurrent.Executors
33 |
34 | class LspCliLanguageClient(
35 | serverCommandLine: List,
36 | serverWorkingDirPath: Path? = null,
37 | clientConfigurationFilePath: Path? = null,
38 | ) : LanguageClient {
39 | val languageServerProcess: Process =
40 | startLanguageServerProcess(serverCommandLine, serverWorkingDirPath)
41 | val languageServer: LanguageServer = initializeLanguageServer()
42 | val clientConfiguration: JsonObject = if (clientConfigurationFilePath != null) {
43 | JsonParser.parseString(FileIo.readFileWithException(clientConfigurationFilePath)).asJsonObject
44 | } else {
45 | JsonObject()
46 | }
47 |
48 | private val _diagnosticsMap: MutableMap> = HashMap()
49 | val diagnosticsMap: Map>
50 | get() = _diagnosticsMap
51 |
52 | override fun telemetryEvent(params: Any?) {
53 | }
54 |
55 | override fun publishDiagnostics(params: PublishDiagnosticsParams?) {
56 | val uri: String = params?.uri ?: return
57 | val diagnostics: List = params.diagnostics ?: return
58 | this._diagnosticsMap[uri] = diagnostics
59 | }
60 |
61 | override fun showMessage(params: MessageParams?) {
62 | }
63 |
64 | override fun showMessageRequest(
65 | params: ShowMessageRequestParams?,
66 | ): CompletableFuture {
67 | return CompletableFuture.completedFuture(MessageActionItem())
68 | }
69 |
70 | override fun logMessage(params: MessageParams?) {
71 | }
72 |
73 | override fun configuration(
74 | configurationParams: ConfigurationParams?,
75 | ): CompletableFuture> {
76 | if (configurationParams == null) return CompletableFuture.completedFuture(emptyList())
77 | val result = ArrayList()
78 |
79 | for (ignored: ConfigurationItem in configurationParams.items) {
80 | result.add(clientConfiguration)
81 | }
82 |
83 | return CompletableFuture.completedFuture(result)
84 | }
85 |
86 | companion object {
87 | private fun startLanguageServerProcess(
88 | serverCommandLine: List,
89 | serverWorkingDirPath: Path? = null,
90 | ): Process {
91 | val absoluteServerCommandLine: MutableList = serverCommandLine.toMutableList()
92 |
93 | if (serverWorkingDirPath != null) {
94 | absoluteServerCommandLine[0] =
95 | serverWorkingDirPath.resolve(absoluteServerCommandLine[0]).toString()
96 | }
97 |
98 | Logging.logger.info(
99 | I18n.format(
100 | "startingLanguageServer",
101 | absoluteServerCommandLine.joinToString(" "),
102 | serverWorkingDirPath?.toFile(),
103 | ),
104 | )
105 |
106 | val processBuilder: ProcessBuilder =
107 | ProcessBuilder(absoluteServerCommandLine).directory(serverWorkingDirPath?.toFile())
108 | val process: Process = processBuilder.start()
109 | Runtime.getRuntime().addShutdownHook(Thread(process::destroy))
110 |
111 | return process
112 | }
113 | }
114 |
115 | private fun initializeLanguageServer(): LanguageServer {
116 | val executorService: ExecutorService = Executors.newSingleThreadScheduledExecutor()
117 |
118 | val launcherBuilder = LSPLauncher.Builder()
119 | launcherBuilder.setLocalService(this)
120 | launcherBuilder.setRemoteInterface(LspCliLanguageServer::class.javaObjectType)
121 | launcherBuilder.setInput(this.languageServerProcess.inputStream)
122 | launcherBuilder.setOutput(this.languageServerProcess.outputStream)
123 | launcherBuilder.setExecutorService(executorService)
124 |
125 | val launcher: Launcher = launcherBuilder.create()
126 | val server: LanguageServer = launcher.remoteProxy
127 | launcher.startListening()
128 | executorService.shutdown()
129 |
130 | Logging.logger.info(I18n.format("initializingLanguageServer"))
131 | server.initialize(InitializeParams()).get()
132 | return server
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/bsplines/lspcli/client/LspCliTextDocumentItem.kt:
--------------------------------------------------------------------------------
1 | /* Copyright (C) 2021 Julian Valentin, lsp-cli Development Community
2 | *
3 | * This Source Code Form is subject to the terms of the Mozilla Public
4 | * License, v. 2.0. If a copy of the MPL was not distributed with this
5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 | */
7 |
8 | package org.bsplines.lspcli.client
9 |
10 | import org.eclipse.lsp4j.Position
11 | import org.eclipse.lsp4j.TextDocumentItem
12 |
13 | class LspCliTextDocumentItem(
14 | uri: String,
15 | codeLanguageId: String,
16 | version: Int,
17 | text: String,
18 | ) : TextDocumentItem(uri, codeLanguageId, version, text) {
19 | private val lineStartPosList: MutableList = ArrayList()
20 |
21 | init {
22 | reinitializeLineStartPosList(text)
23 | }
24 |
25 | private fun reinitializeLineStartPosList(text: String) {
26 | this.lineStartPosList.clear()
27 | this.lineStartPosList.add(0)
28 |
29 | var i = 0
30 |
31 | while (i < text.length) {
32 | val c: Char = text[i]
33 |
34 | if (c == '\r') {
35 | if ((i + 1 < text.length) && (text[i + 1] == '\n')) i++
36 | this.lineStartPosList.add(i + 1)
37 | } else if (c == '\n') {
38 | this.lineStartPosList.add(i + 1)
39 | }
40 |
41 | i++
42 | }
43 | }
44 |
45 | @Suppress("NestedBlockDepth")
46 | fun convertPosition(position: Position): Int {
47 | val line: Int = position.line
48 | val character: Int = position.character
49 | val text: String = text
50 |
51 | return when {
52 | line < 0 -> 0
53 | line >= this.lineStartPosList.size -> text.length
54 | else -> {
55 | val lineStart: Int = this.lineStartPosList[line]
56 | val nextLineStart: Int = if (line < this.lineStartPosList.size - 1) {
57 | this.lineStartPosList[line + 1]
58 | } else {
59 | text.length
60 | }
61 | val lineLength: Int = nextLineStart - lineStart
62 |
63 | when {
64 | character < 0 -> lineStart
65 | character >= lineLength -> {
66 | var pos: Int = lineStart + lineLength
67 |
68 | if (pos >= 1) {
69 | if (text[pos - 1] == '\r') {
70 | pos--
71 | } else if (text[pos - 1] == '\n') {
72 | pos--
73 | if ((pos >= 1) && (text[pos - 1] == '\r')) pos--
74 | }
75 | }
76 |
77 | pos
78 | }
79 | else -> lineStart + character
80 | }
81 | }
82 | }
83 | }
84 |
85 | fun convertPosition(pos: Int): Position {
86 | var line: Int = this.lineStartPosList.binarySearch(pos)
87 |
88 | if (line < 0) {
89 | val insertionPoint: Int = -line - 1
90 | line = insertionPoint - 1
91 | }
92 |
93 | return Position(line, pos - this.lineStartPosList[line])
94 | }
95 |
96 | override fun setText(text: String) {
97 | super.setText(text)
98 | reinitializeLineStartPosList(text)
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/bsplines/lspcli/server/LspCliLanguageServer.kt:
--------------------------------------------------------------------------------
1 | /* Copyright (C) 2021 Julian Valentin, lsp-cli Development Community
2 | *
3 | * This Source Code Form is subject to the terms of the Mozilla Public
4 | * License, v. 2.0. If a copy of the MPL was not distributed with this
5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 | */
7 |
8 | package org.bsplines.lspcli.server
9 |
10 | import org.eclipse.lsp4j.services.LanguageServer
11 |
12 | interface LspCliLanguageServer : LanguageServer
13 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/bsplines/lspcli/tools/FileIo.kt:
--------------------------------------------------------------------------------
1 | /* Copyright (C) 2021 Julian Valentin, lsp-cli Development Community
2 | *
3 | * This Source Code Form is subject to the terms of the Mozilla Public
4 | * License, v. 2.0. If a copy of the MPL was not distributed with this
5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 | */
7 |
8 | package org.bsplines.lspcli.tools
9 |
10 | import java.io.IOException
11 | import java.nio.charset.StandardCharsets
12 | import java.nio.file.Files
13 | import java.nio.file.Path
14 | import java.nio.file.StandardOpenOption
15 |
16 | object FileIo {
17 | fun readFile(filePath: Path): String? {
18 | return try {
19 | readFileWithException(filePath)
20 | } catch (e: IOException) {
21 | Logging.logger.warning(I18n.format("couldNotReadFile", e, filePath.toString()))
22 | null
23 | }
24 | }
25 |
26 | fun readFileWithException(filePath: Path): String {
27 | return String(Files.readAllBytes(filePath), StandardCharsets.UTF_8)
28 | }
29 |
30 | fun writeFile(filePath: Path, text: String) {
31 | try {
32 | writeFileWithException(filePath, text)
33 | } catch (e: IOException) {
34 | Logging.logger.warning(I18n.format("couldNotWriteFile", e, filePath.toString()))
35 | }
36 | }
37 |
38 | fun writeFileWithException(filePath: Path, text: String) {
39 | Files.write(
40 | filePath,
41 | text.toByteArray(StandardCharsets.UTF_8),
42 | StandardOpenOption.CREATE,
43 | StandardOpenOption.TRUNCATE_EXISTING,
44 | StandardOpenOption.WRITE,
45 | StandardOpenOption.SYNC,
46 | )
47 | }
48 |
49 | @Suppress("ComplexCondition", "ComplexMethod", "LongMethod")
50 | fun getCodeLanguageIdFromPath(path: Path): String? {
51 | val fileName: String = path.fileName.toString()
52 |
53 | return if (fileName.endsWith(".bib")) {
54 | "bibtex"
55 | } else if (fileName.endsWith(".c")
56 | || fileName.endsWith(".h")) {
57 | "c"
58 | } else if (fileName.endsWith(".clj")) {
59 | "clojure"
60 | } else if (fileName.endsWith(".coffee")) {
61 | "coffeescript"
62 | } else if (fileName.endsWith(".cc")
63 | || fileName.endsWith(".cpp")
64 | || fileName.endsWith(".cxx")
65 | || fileName.endsWith(".hh")
66 | || fileName.endsWith(".hpp")
67 | || fileName.endsWith(".inl")) {
68 | "cpp"
69 | } else if (fileName.endsWith(".cs")) {
70 | "csharp"
71 | } else if (fileName.endsWith(".dart")) {
72 | "dart"
73 | } else if (fileName.endsWith(".ex")) {
74 | "elixir"
75 | } else if (fileName.endsWith(".elm")) {
76 | "elm"
77 | } else if (fileName.endsWith(".erl")) {
78 | "erlang"
79 | } else if (fileName.endsWith(".f90")) {
80 | "fortran-modern"
81 | } else if (fileName.endsWith(".fs")) {
82 | "fsharp"
83 | } else if (fileName.endsWith(".go")) {
84 | "go"
85 | } else if (fileName.endsWith(".groovy")) {
86 | "groovy"
87 | } else if (fileName.endsWith(".hs")) {
88 | "haskell"
89 | } else if (fileName.endsWith(".htm")
90 | || fileName.endsWith(".html")
91 | || fileName.endsWith(".xht")
92 | || fileName.endsWith(".xhtml")) {
93 | "html"
94 | } else if (fileName.endsWith(".java")) {
95 | "java"
96 | } else if (fileName.endsWith(".js")) {
97 | "javascript"
98 | } else if (fileName.endsWith(".jl")) {
99 | "julia"
100 | } else if (fileName.endsWith(".kt")) {
101 | "kotlin"
102 | } else if (fileName.endsWith(".tex")) {
103 | "latex"
104 | } else if (fileName.endsWith(".lisp")) {
105 | "lisp"
106 | } else if (fileName.endsWith(".lua")) {
107 | "lua"
108 | } else if (fileName.endsWith(".md")) {
109 | "markdown"
110 | } else if (fileName.endsWith(".m")) {
111 | "matlab"
112 | } else if (fileName.endsWith(".org")) {
113 | "org"
114 | } else if (fileName.endsWith(".pl")) {
115 | "perl"
116 | } else if (fileName.endsWith(".php")) {
117 | "php"
118 | } else if (fileName.endsWith(".txt")) {
119 | "plaintext"
120 | } else if (fileName.endsWith(".ps1")) {
121 | "powershell"
122 | } else if (fileName.endsWith(".pp")) {
123 | "puppet"
124 | } else if (fileName.endsWith(".py")) {
125 | "python"
126 | } else if (fileName.endsWith(".r")) {
127 | "r"
128 | } else if (fileName.endsWith(".rst")) {
129 | "restructuredtext"
130 | } else if (fileName.endsWith(".Rnw")
131 | || fileName.endsWith(".rnw")) {
132 | "rsweave"
133 | } else if (fileName.endsWith(".rb")) {
134 | "ruby"
135 | } else if (fileName.endsWith(".rs")) {
136 | "rust"
137 | } else if (fileName.endsWith(".scala")) {
138 | "scala"
139 | } else if (fileName.endsWith(".sh")) {
140 | "shellscript"
141 | } else if (fileName.endsWith(".sql")) {
142 | "sql"
143 | } else if (fileName.endsWith(".swift")) {
144 | "swift"
145 | } else if (fileName.endsWith(".ts")) {
146 | "typescript"
147 | } else if (fileName.endsWith(".vb")) {
148 | "vb"
149 | } else if (fileName.endsWith(".v")) {
150 | "verilog"
151 | } else {
152 | null
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/bsplines/lspcli/tools/I18n.kt:
--------------------------------------------------------------------------------
1 | /* Copyright (C) 2021 Julian Valentin, lsp-cli Development Community
2 | *
3 | * This Source Code Form is subject to the terms of the Mozilla Public
4 | * License, v. 2.0. If a copy of the MPL was not distributed with this
5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 | */
7 |
8 | package org.bsplines.lspcli.tools
9 |
10 | import java.io.PrintWriter
11 | import java.io.StringWriter
12 | import java.text.MessageFormat
13 | import java.util.Locale
14 | import java.util.MissingResourceException
15 | import java.util.ResourceBundle
16 |
17 | object I18n {
18 | private var messages: ResourceBundle? = null
19 |
20 | init {
21 | setDefaultLocale()
22 | }
23 |
24 | @Suppress("SwallowedException")
25 | fun setDefaultLocale() {
26 | try {
27 | setLocale(Locale.getDefault(), false)
28 | } catch (e: MissingResourceException) {
29 | setLocale(Locale.ENGLISH, false)
30 | }
31 | }
32 |
33 | fun setLocale(locale: Locale, log: Boolean = true) {
34 | if (log) Logging.logger.info(format("settingLocale", locale.language))
35 | messages = ResourceBundle.getBundle("LspCliMessagesBundle", locale)
36 | }
37 |
38 | fun format(key: String, vararg messageArguments: Any?): String {
39 | val messages: ResourceBundle? = messages
40 |
41 | val message: String = if ((messages != null) && messages.containsKey(key)) {
42 | messages.getString(key)
43 | } else {
44 | val builder = StringBuilder()
45 |
46 | if (messages == null) {
47 | builder.append("MessagesBundle is null while trying to get i18n message with key '")
48 | builder.append(key)
49 | builder.append("'")
50 | } else {
51 | builder.append("i18n message with key '")
52 | builder.append(key)
53 | builder.append("' not found")
54 | }
55 |
56 | builder.append(", message arguments: ")
57 |
58 | for (i in messageArguments.indices) {
59 | if (i > 0) builder.append(", ")
60 | builder.append("'{")
61 | builder.append(i.toString())
62 | builder.append("}'")
63 | }
64 |
65 | builder.toString()
66 | }
67 |
68 | val formatter = MessageFormat("")
69 | formatter.applyPattern(message.replace("'", "''"))
70 | val stringArguments: Array = Array(messageArguments.size) { "" }
71 |
72 | for (i in messageArguments.indices) {
73 | stringArguments[i] = (messageArguments[i]?.toString() ?: "null")
74 | }
75 |
76 | return formatter.format(stringArguments)
77 | }
78 |
79 | fun format(key: String, e: Exception, vararg messageArguments: Any?): String {
80 | val builder = StringBuilder()
81 | builder.append(format(key, messageArguments))
82 | builder.append(". ")
83 | builder.append(format(e))
84 | return builder.toString()
85 | }
86 |
87 | fun format(e: Exception): String {
88 | val writer = StringWriter()
89 | writer.write(format("followingExceptionOccurred"))
90 | writer.write("\n")
91 | e.printStackTrace(PrintWriter(writer))
92 | return writer.toString()
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/bsplines/lspcli/tools/Logging.kt:
--------------------------------------------------------------------------------
1 | /* Copyright (C) 2021 Julian Valentin, lsp-cli Development Community
2 | *
3 | * This Source Code Form is subject to the terms of the Mozilla Public
4 | * License, v. 2.0. If a copy of the MPL was not distributed with this
5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 | */
7 |
8 | package org.bsplines.lspcli.tools
9 |
10 | import java.util.logging.ConsoleHandler
11 | import java.util.logging.Level
12 | import java.util.logging.Logger
13 |
14 | object Logging {
15 | val logger: Logger = Logger.getLogger("org.bsplines.lspcli")
16 | private val loggerConsoleHandler = ConsoleHandler()
17 |
18 | init {
19 | logger.useParentHandlers = false
20 | logger.addHandler(loggerConsoleHandler)
21 | setLogLevel(Level.WARNING)
22 | }
23 |
24 | fun setLogLevel(logLevel: Level) {
25 | logger.level = logLevel
26 | loggerConsoleHandler.level = logLevel
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/bsplines/lspcli/tools/LspCliSettings.kt:
--------------------------------------------------------------------------------
1 | package org.bsplines.lspcli.tools
2 |
3 | import com.google.gson.JsonElement
4 | import com.google.gson.JsonObject
5 | import com.google.gson.JsonParser
6 | import java.nio.file.Path
7 |
8 | class LspCliSettings(
9 | val settingsFilePath: Path? = null,
10 | ) {
11 | val jsonSettings: JsonObject = if (settingsFilePath != null) {
12 | JsonParser.parseString(FileIo.readFileWithException(settingsFilePath)).asJsonObject
13 | } else {
14 | JsonObject()
15 | }
16 |
17 | fun getValue(vararg keys: String): JsonElement? {
18 | var jsonElement: JsonElement = this.jsonSettings
19 |
20 | for (key: String in keys) {
21 | val jsonObject: JsonObject? = if (jsonElement.isJsonObject) jsonElement.asJsonObject else null
22 |
23 | if (jsonObject?.has(key) == true) {
24 | jsonElement = jsonObject.get(key)
25 | } else {
26 | return null
27 | }
28 | }
29 |
30 | return jsonElement
31 | }
32 |
33 | fun getValueAsListOfString(vararg keys: String): List? {
34 | val jsonElement: JsonElement = getValue(*keys) ?: return null
35 | val list = ArrayList()
36 |
37 | for (entryElement: JsonElement in jsonElement.asJsonArray) {
38 | list.add(entryElement.asString)
39 | }
40 |
41 | return list
42 | }
43 |
44 | fun getValueAsMap(vararg keys: String): Map? {
45 | val jsonElement: JsonElement = getValue(*keys) ?: return null
46 | val map = HashMap()
47 |
48 | for ((mapKey: String, mapValueElement: JsonElement) in jsonElement.asJsonObject.entrySet()) {
49 | map[mapKey] = mapValueElement
50 | }
51 |
52 | return map
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/bsplines/lspcli/tools/VersionProvider.kt:
--------------------------------------------------------------------------------
1 | /* Copyright (C) 2021 Julian Valentin, lsp-cli Development Community
2 | *
3 | * This Source Code Form is subject to the terms of the Mozilla Public
4 | * License, v. 2.0. If a copy of the MPL was not distributed with this
5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 | */
7 |
8 | package org.bsplines.lspcli.tools
9 |
10 | import com.google.gson.Gson
11 | import com.google.gson.GsonBuilder
12 | import com.google.gson.JsonObject
13 | import org.bsplines.lspcli.LspCliLauncher
14 | import picocli.CommandLine
15 |
16 | class VersionProvider : CommandLine.IVersionProvider {
17 | override fun getVersion(): Array {
18 | val lspcliPackage: Package? = LspCliLauncher::class.java.getPackage()
19 | val jsonObject = JsonObject()
20 |
21 | if (lspcliPackage != null) {
22 | val lspcliVersion: String? = lspcliPackage.implementationVersion
23 | if (lspcliVersion != null) jsonObject.addProperty("lsp-cli", lspcliVersion)
24 | }
25 |
26 | val javaVersion: String? = System.getProperty("java.version")
27 | if (javaVersion != null) jsonObject.addProperty("java", javaVersion)
28 |
29 | val gsonBuilder: Gson = GsonBuilder().setPrettyPrinting().create()
30 | return arrayOf(gsonBuilder.toJson(jsonObject))
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/resources/LspCliMessagesBundle.properties:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2021 Julian Valentin, lsp-cli Development Community
2 | #
3 | # This Source Code Form is subject to the terms of the Mozilla Public
4 | # License, v. 2.0. If a copy of the MPL was not distributed with this
5 | # file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 |
7 | checkingFile = Checking '{0}'...
8 | couldNotReadFile = Could not read file '{0}'
9 | couldNotWriteFile = Could not write file '{0}'
10 | followingExceptionOccurred = The following exception occurred:
11 | initializingLanguageServer = Initializing language server...
12 | requiredArgumentNotSpecified = Required argument '{0}' not specified
13 | settingLocale = Setting locale to '{0}'
14 | startingLanguageServer = Starting language server with command line '{0}' in directory '{1}'...
15 | waitingForDiagnosticsForFile = Waiting for diagnostics for file '{0}'...
16 |
--------------------------------------------------------------------------------
/src/test/kotlin/org/bsplines/lspcli/tools/I18nTest.kt:
--------------------------------------------------------------------------------
1 | /* Copyright (C) 2021 Julian Valentin, lsp-cli Development Community
2 | *
3 | * This Source Code Form is subject to the terms of the Mozilla Public
4 | * License, v. 2.0. If a copy of the MPL was not distributed with this
5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 | */
7 |
8 | package org.bsplines.lspcli.tools
9 |
10 | import kotlin.test.Test
11 | import kotlin.test.assertContains
12 | import kotlin.test.assertEquals
13 |
14 | class I18nTest {
15 | @Test
16 | fun testFormat() {
17 | assertEquals("Checking 'test'...", I18n.format("checkingFile", "test"))
18 |
19 | assertEquals(
20 | "i18n message with key 'abc' not found, message arguments: 'def', '42', 'null'",
21 | I18n.format("abc", "def", 42, null),
22 | )
23 |
24 | assertContains(
25 | I18n.format(NullPointerException("abc")),
26 | Regex(
27 | "The following exception occurred:[\r\n]+java\\.lang\\.NullPointerException: abc[\r\n]+.*",
28 | RegexOption.DOT_MATCHES_ALL,
29 | ),
30 | )
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/test/kotlin/org/bsplines/lspcli/tools/VersionProviderTest.kt:
--------------------------------------------------------------------------------
1 | /* Copyright (C) 2021 Julian Valentin, lsp-cli Development Community
2 | *
3 | * This Source Code Form is subject to the terms of the Mozilla Public
4 | * License, v. 2.0. If a copy of the MPL was not distributed with this
5 | * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6 | */
7 |
8 | package org.bsplines.lspcli.tools
9 |
10 | import com.google.gson.JsonElement
11 | import com.google.gson.JsonObject
12 | import com.google.gson.JsonParser
13 | import kotlin.test.Test
14 | import kotlin.test.assertEquals
15 | import kotlin.test.assertTrue
16 |
17 | class VersionProviderTest {
18 | @Test
19 | fun testVersion() {
20 | val versionProvider = VersionProvider()
21 | val version: Array = versionProvider.version
22 | assertEquals(1, version.size)
23 |
24 | val rootJsonElement: JsonElement = JsonParser.parseString(version[0])
25 | assertTrue(rootJsonElement.isJsonObject)
26 |
27 | val rootJsonObject: JsonObject = rootJsonElement.asJsonObject
28 | assertTrue(rootJsonObject.has("java"))
29 |
30 | val javaJsonElement: JsonElement = rootJsonObject["java"]
31 | assertTrue(javaJsonElement.isJsonPrimitive)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tools/common.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | # Copyright (C) 2021 Julian Valentin, lsp-cli Development Community
4 | #
5 | # This Source Code Form is subject to the terms of the Mozilla Public
6 | # License, v. 2.0. If a copy of the MPL was not distributed with this
7 | # file, You can obtain one at https://mozilla.org/MPL/2.0/.
8 |
9 | import re
10 | import subprocess
11 | from typing import Tuple
12 |
13 |
14 |
15 | def getGitHubOrganizationRepository() -> Tuple[str, str]:
16 | output = subprocess.run(["git", "remote", "get-url", "origin"],
17 | stdout=subprocess.PIPE).stdout.decode()
18 | regexMatch = re.search(r"github.com[:/](.*?)/(.*?)(?:\.git)?$", output)
19 | assert regexMatch is not None, output
20 | organization, repository = regexMatch.group(1), regexMatch.group(2)
21 | return organization, repository
22 |
23 | organization, repository = getGitHubOrganizationRepository()
24 |
--------------------------------------------------------------------------------
/tools/convertChangelog.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | # Copyright (C) 2021 Julian Valentin, lsp-cli Development Community
4 | #
5 | # This Source Code Form is subject to the terms of the Mozilla Public
6 | # License, v. 2.0. If a copy of the MPL was not distributed with this
7 | # file, You can obtain one at https://mozilla.org/MPL/2.0/.
8 |
9 | import argparse
10 | import datetime
11 | import pathlib
12 | import re
13 | import sys
14 | from typing import Optional, Tuple
15 | import xml.etree.ElementTree as et
16 | import xml.dom.minidom
17 |
18 | sys.path.append(str(pathlib.Path(__file__).parent))
19 | import common
20 |
21 |
22 |
23 | def parseIssue(issue: str) -> Tuple[str, str, int, str]:
24 | issueMatch = re.search(r"(?:(.*?)/)?(.*?)?#([0-9]+)", issue)
25 | assert issueMatch is not None, issue
26 | issueOrganization = (issueMatch.group(1) if issueMatch.group(1) is not None else
27 | common.organization)
28 | issueRepository = (issueMatch.group(2) if issueMatch.group(2) != "" else common.repository)
29 | issueNumber = int(issueMatch.group(3))
30 |
31 | if issueOrganization == common.organization:
32 | if issueRepository == common.repository:
33 | normalizedIssue = f"#{issueNumber}"
34 | else:
35 | normalizedIssue = f"{issueRepository}#{issueNumber}"
36 | else:
37 | normalizedIssue = f"{issueOrganization}/{issueRepository}#{issueNumber}"
38 |
39 | return issueOrganization, issueRepository, issueNumber, normalizedIssue
40 |
41 |
42 |
43 | def replaceUnicodeWithXmlEntities(string: str) -> str:
44 | return re.sub(r"[\u007f-\U0010ffff]", (lambda x: f"{ord(x.group()):04x};"), string)
45 |
46 |
47 |
48 | def convertChangelogFromXmlToMarkdown(xmlFilePath: pathlib.Path,
49 | version: Optional[str] = None) -> str:
50 | document = et.parse(xmlFilePath).getroot()
51 |
52 | if version is None:
53 | markdown = """
60 |
61 | """
62 |
63 | title = document.findtext("./{http://maven.apache.org/changes/1.0.0}properties"
64 | "/{http://maven.apache.org/changes/1.0.0}title")
65 | markdown += f"# {title}\n"
66 | else:
67 | markdown = ""
68 |
69 | releases = document.findall("./{http://maven.apache.org/changes/1.0.0}body"
70 | "/{http://maven.apache.org/changes/1.0.0}release")
71 |
72 | for release in releases:
73 | curVersion = release.attrib["version"]
74 | if version == "latest": version = curVersion
75 | if (version is not None) and (curVersion != version): continue
76 |
77 | description = release.attrib.get("description", "")
78 | if len(description) > 0: description = f" \u2014 \u201c{description}\u201d"
79 | dateStr = release.attrib["date"]
80 | dateStr = (datetime.datetime.strptime(dateStr, "%Y-%m-%d").strftime("%B %d, %Y")
81 | if dateStr != "upcoming" else dateStr)
82 | if version is None: markdown += f"\n## {curVersion}{description} ({dateStr})\n\n"
83 |
84 | for action in release.findall("./{http://maven.apache.org/changes/1.0.0}action"):
85 | type_ = action.attrib["type"]
86 | typeEmoji = {
87 | "add" : "\u2728",
88 | "fix": "\U0001f41b",
89 | "remove" : "\U0001f5d1",
90 | "update" : "\U0001f527",
91 | }[type_]
92 | typeStr = {"add" : "New", "fix": "Bug fix", "remove" : "Removal", "update" : "Change"}[type_]
93 |
94 | additionalInfo = ""
95 |
96 | if "issue" in action.attrib:
97 | for issue in action.attrib["issue"].split(","):
98 | issueOrganization, issueRepository, issueNumber, issue = parseIssue(issue)
99 | additionalInfo += (" \u2014 " if additionalInfo == "" else ", ")
100 | additionalInfo += (f"[{issue}](https://github.com/{issueOrganization}/"
101 | f"{issueRepository}/issues/{issueNumber})")
102 |
103 | if "due-to" in action.attrib:
104 | for author in action.attrib["due-to"].split(","):
105 | additionalInfo += (" \u2014 " if additionalInfo == "" else ", ")
106 | userMatch = re.search(r"@([^)]+)", author)
107 | if userMatch is not None: author = f"[{author}](https://github.com/{userMatch.group(1)})"
108 | additionalInfo += author
109 |
110 | change = action.text
111 | assert change is not None
112 | change = change.strip()
113 | change = change.replace("LaTeX", "LATEX")
114 | change = re.sub(r"(?EX", change)
115 |
116 | markdown += f"- {typeEmoji} *{typeStr}:* {change}{additionalInfo}\n"
117 |
118 | markdown = replaceUnicodeWithXmlEntities(markdown)
119 | return markdown
120 |
121 |
122 |
123 | def convertReleaseFromMarkdownToXml(body: et.Element, version: str, name: str, dateStr: str,
124 | changes: str) -> et.Element:
125 | attributes = {"version" : version}
126 | if len(name) > 0: attributes["description"] = name
127 | attributes["date"] = (datetime.datetime.strptime(dateStr, "%B %d, %Y").strftime("%Y-%m-%d")
128 | if dateStr != "upcoming" else dateStr)
129 | release = et.SubElement(body, "release", attributes)
130 |
131 | changes = changes.strip()
132 |
133 | for change in changes.split("\n"):
134 | change = re.sub(r"^- ", "", change).strip()
135 | attributes = {}
136 |
137 | if change.startswith("Remove "):
138 | attributes["type"] = "remove"
139 | elif (change.startswith("Add ") or ("support" in change.lower())
140 | or (change == "Initial release")):
141 | attributes["type"] = "add"
142 | elif (change.startswith("Fix ")
143 | or any(x in change.lower() for x in ["error", "warning", "prevent"])):
144 | attributes["type"] = "fix"
145 | else:
146 | attributes["type"] = "update"
147 |
148 | issues = []
149 | authors = []
150 | issueFound = True
151 |
152 | while issueFound:
153 | issueMatch1 = re.search(
154 | r" \((?:fixes|fixes part of|see) \[([^\]]*?#[0-9]+)\]\(.*?\)\)", change)
155 | issueMatch2 = re.search(
156 | r"(?:[;,]| and) (?:fixes |see )?\[([^\]]*?#[0-9]+)\]\(.*?\)", change)
157 | issueMatch3 = re.search(
158 | r" \((?:\[PR |PR \[)(.*?#[0-9]+)\]\([^\]]*?\) by \[([^\]]*?)\]\(.*?\)\)", change)
159 | issueMatch4 = re.search(
160 | r"(?:[;,]| and) \[PR (.*?#[0-9]+)\]\([^\]]*?\) by \[([^\]]*?)\]\(.*?\)", change)
161 | issueFound = False
162 |
163 | for issueMatch in [issueMatch1, issueMatch2, issueMatch3, issueMatch4]:
164 | if issueMatch is not None:
165 | issueFound = True
166 | _, _, _, issue = parseIssue(issueMatch.group(1))
167 | issues.append(issue)
168 | if len(issueMatch.groups()) > 1: authors.append(issueMatch.group(2))
169 | change = change[:issueMatch.start()] + change[issueMatch.end():]
170 |
171 | if len(issues) > 0: attributes["issue"] = ",".join(sorted(issues))
172 | if len(authors) > 0: attributes["due-to"] = ",".join(sorted(authors))
173 |
174 | change = change.replace("TEX", "TeX").replace("LA", "La")
175 |
176 | assert f"github.com/{common.organization}" not in change, change
177 | assert " -" not in change, change
178 |
179 | action = et.SubElement(release, "action", attributes)
180 | action.text = f"\n {change}\n "
181 |
182 | return release
183 |
184 |
185 |
186 | def convertChangelogFromMarkdownToXml(markdownFilePath: pathlib.Path,
187 | version: Optional[str] = None) -> str:
188 | with open(markdownFilePath, "r") as f: changelog = f.read()
189 |
190 | document = et.Element("document", {
191 | "xmlns" : "http://maven.apache.org/changes/1.0.0",
192 | "xmlns:xsi" : "http://www.w3.org/2001/XMLSchema-instance",
193 | "xsi:schemaLocation" : "http://maven.apache.org/changes/1.0.0 "
194 | "https://maven.apache.org/xsd/changes-1.0.0.xsd"
195 | })
196 |
197 | properties = et.SubElement(document, "properties")
198 | title = et.SubElement(properties, "title")
199 | title.text = "Changelog"
200 | author = et.SubElement(properties, "author")
201 | author.text = "Julian Valentin, LTeX Development Community"
202 |
203 | body = et.SubElement(document, "body")
204 |
205 | regexMatches = re.findall(
206 | r"\n## ([^ ]+)(?: \u2014 \u201c(.*?)\u201d)? \((.*)\)\n\n((?:.|\n)+?)(?=$|\n## )", changelog)
207 | for regexMatch in regexMatches:
208 | curVersion = regexMatch[0]
209 | if version == "latest": version = curVersion
210 | if (version is not None) and (curVersion != version): continue
211 | release = convertReleaseFromMarkdownToXml(body, *regexMatch)
212 |
213 | if version is not None:
214 | document = release
215 | break
216 |
217 | xmlStr = et.tostring(document, encoding="unicode", xml_declaration=True)
218 | xmlStr = xml.dom.minidom.parseString(xmlStr).toprettyxml(indent=" ")
219 | xmlStr = re.sub(r"^<\?xml version=\"1.0\" \?>\n",
220 | ("""
221 |
228 | """ if version is None else ""), xmlStr)
229 | xmlStr = replaceUnicodeWithXmlEntities(xmlStr)
230 |
231 | return xmlStr
232 |
233 |
234 |
235 | def main() -> None:
236 | parser = argparse.ArgumentParser(
237 | description="Convert changelog from XML to Markdown and vice-versa.")
238 | xmlFileArgument = parser.add_argument("--xml-file", type=pathlib.Path, metavar="PATH",
239 | help="XML file to convert to Markdown")
240 | parser.add_argument("--markdown-file", type=pathlib.Path, metavar="PATH",
241 | help="Markdown file to convert to XML")
242 | parser.add_argument("--output-file", type=pathlib.Path, default=pathlib.Path("-"), metavar="PATH",
243 | help="Output file; '-' is standard output (default)")
244 | parser.add_argument("--version",
245 | help="Version to convert; 'latest' is permitted; all versions are converted if omitted")
246 | arguments = parser.parse_args()
247 |
248 | if arguments.xml_file is not None:
249 | output = convertChangelogFromXmlToMarkdown(arguments.xml_file, arguments.version)
250 | elif arguments.markdown_file is not None:
251 | output = convertChangelogFromMarkdownToXml(arguments.markdown_file, arguments.version)
252 | else:
253 | raise argparse.ArgumentError(xmlFileArgument,
254 | "One of --xml-file or --markdown-file is required")
255 |
256 | if str(arguments.output_file) == "-":
257 | print(output, end="")
258 | else:
259 | with open(arguments.output_file, "w") as f: f.write(output)
260 |
261 |
262 |
263 | if __name__ == "__main__":
264 | main()
265 |
--------------------------------------------------------------------------------
/tools/createBinaryArchives.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | # Copyright (C) 2021 Julian Valentin, lsp-cli Development Community
4 | #
5 | # This Source Code Form is subject to the terms of the Mozilla Public
6 | # License, v. 2.0. If a copy of the MPL was not distributed with this
7 | # file, You can obtain one at https://mozilla.org/MPL/2.0/.
8 |
9 | import pathlib
10 | import re
11 | import shutil
12 | import subprocess
13 | import tarfile
14 | import tempfile
15 | import urllib.parse
16 | import urllib.request
17 | import zipfile
18 |
19 | javaVersion = "11.0.12+7"
20 |
21 |
22 |
23 | def createBinaryArchive(platform: str, arch: str) -> None:
24 | print(f"Processing platform/arch '{platform}/{arch}'...")
25 | lspCliVersion = getLspCliVersion()
26 | targetDirPath = pathlib.Path(__file__).parent.parent.joinpath("target")
27 | lspCliArchivePath = pathlib.Path(__file__).parent.parent.joinpath(
28 | targetDirPath, f"lsp-cli-{lspCliVersion}.tar.gz")
29 |
30 | with tempfile.TemporaryDirectory() as tmpDirPathStr:
31 | tmpDirPath = pathlib.Path(tmpDirPathStr)
32 |
33 | print("Extracting lsp-cli archive...")
34 | with tarfile.open(lspCliArchivePath, "r:gz") as tarFile: tarFile.extractall(path=tmpDirPath)
35 |
36 | lspCliDirPath = tmpDirPath.joinpath(f"lsp-cli-{lspCliVersion}")
37 | relativeJavaDirPath = downloadJava(tmpDirPath, lspCliDirPath, platform, arch)
38 |
39 | print("Setting default for JAVA_HOME in startup script...")
40 |
41 | if platform == "windows":
42 | lspCliDirPath.joinpath("bin", "lsp-cli").unlink()
43 | binScriptPath = lspCliDirPath.joinpath("bin", "lsp-cli.bat")
44 | searchPattern = re.compile("^set REPO=.*$", flags=re.MULTILINE)
45 | else:
46 | lspCliDirPath.joinpath("bin", "lsp-cli.bat").unlink()
47 | binScriptPath = lspCliDirPath.joinpath("bin", "lsp-cli")
48 | searchPattern = re.compile("^BASEDIR=.*$", flags=re.MULTILINE)
49 |
50 | with open(binScriptPath, "r") as file: binScript = file.read()
51 |
52 | if platform == "windows":
53 | insertStr = f"\r\nif not defined JAVA_HOME set JAVA_HOME=\"%BASEDIR%\\{relativeJavaDirPath}\""
54 | else:
55 | insertStr = f"\n[ -z \"$JAVA_HOME\" ] && JAVA_HOME=\"$BASEDIR\"/{relativeJavaDirPath}"
56 |
57 | regexMatch = searchPattern.search(binScript)
58 | assert regexMatch is not None
59 | binScript = binScript[:regexMatch.end()] + insertStr + binScript[regexMatch.end():]
60 | with open(binScriptPath, "w") as file: file.write(binScript)
61 |
62 | lspCliBinaryArchiveFormat = ("zip" if platform == "windows" else "gztar")
63 | lspCliBinaryArchiveExtension = (".zip" if platform == "windows" else ".tar.gz")
64 | lspCliBinaryArchivePath = targetDirPath.joinpath(
65 | f"lsp-cli-{lspCliVersion}-{platform}-{arch}")
66 | print(f"Creating binary archive '{lspCliBinaryArchivePath}{lspCliBinaryArchiveExtension}'...")
67 | shutil.make_archive(str(lspCliBinaryArchivePath), lspCliBinaryArchiveFormat,
68 | root_dir=tmpDirPath)
69 | print("")
70 |
71 |
72 |
73 | def downloadJava(tmpDirPath: pathlib.Path, lspCliDirPath: pathlib.Path,
74 | platform: str, arch: str) -> str:
75 | javaArchiveExtension = (".zip" if platform == "windows" else ".tar.gz")
76 | javaArchiveName = (f"OpenJDK11U-jdk_{arch}_{platform}_hotspot_"
77 | f"{javaVersion.replace('+', '_')}{javaArchiveExtension}")
78 |
79 | javaUrl = ("https://github.com/adoptium/temurin11-binaries/releases/download/"
80 | f"jdk-{urllib.parse.quote_plus(javaVersion)}/{javaArchiveName}")
81 | javaArchivePath = lspCliDirPath.joinpath(javaArchiveName)
82 | print(f"Downloading JDK from '{javaUrl}' to '{javaArchivePath}'...")
83 | urllib.request.urlretrieve(javaUrl, javaArchivePath)
84 | print("Extracting JDK archive...")
85 |
86 | if javaArchiveExtension == ".zip":
87 | with zipfile.ZipFile(javaArchivePath, "r") as zipFile: zipFile.extractall(path=tmpDirPath)
88 | else:
89 | with tarfile.open(javaArchivePath, "r:gz") as tarFile: tarFile.extractall(path=tmpDirPath)
90 |
91 | print("Removing JDK archive...")
92 | javaArchivePath.unlink()
93 |
94 | relativeJavaDirPathString = f"jdk-{javaVersion}"
95 | jdkDirPath = tmpDirPath.joinpath(relativeJavaDirPathString)
96 | jmodsDirPath = (jdkDirPath.joinpath("jmods") if platform == "mac" else
97 | jdkDirPath.joinpath("Contents", "Home", "jmods"))
98 | javaTargetDirPath = lspCliDirPath.joinpath(relativeJavaDirPathString)
99 |
100 | print("Creating Java distribution...")
101 | subprocess.run(["jlink", "--module-path", str(jmodsDirPath), "--add-modules", "java.se",
102 | "--strip-debug", "--no-man-pages", "--no-header-files", "--compress=2",
103 | "--output", str(javaTargetDirPath)])
104 |
105 | print("Removing JDK directory...")
106 | shutil.rmtree(jdkDirPath)
107 |
108 | return relativeJavaDirPathString
109 |
110 |
111 |
112 | def getLspCliVersion() -> str:
113 | with open("pom.xml", "r") as file:
114 | regexMatch = re.search(r"(.*?)", file.read())
115 | assert regexMatch is not None
116 | return regexMatch.group(1)
117 |
118 |
119 |
120 | def main() -> None:
121 | createBinaryArchive("linux", "x64")
122 | createBinaryArchive("mac", "x64")
123 | createBinaryArchive("windows", "x64")
124 |
125 |
126 | if __name__ == "__main__":
127 | main()
128 |
--------------------------------------------------------------------------------
/tools/inspectWithIntellijIdea.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | # Copyright (C) 2021 Julian Valentin, lsp-cli Development Community
4 | #
5 | # This Source Code Form is subject to the terms of the Mozilla Public
6 | # License, v. 2.0. If a copy of the MPL was not distributed with this
7 | # file, You can obtain one at https://mozilla.org/MPL/2.0/.
8 |
9 | import argparse
10 | import pathlib
11 | import subprocess
12 | import sys
13 | import tarfile
14 | import tempfile
15 | import urllib.request
16 |
17 |
18 |
19 | def downloadIdea(tmpDirPath: pathlib.Path) -> pathlib.Path:
20 | print("Downloading IntelliJ IDEA...")
21 | archiveFilePath = tmpDirPath.joinpath("idea.tar.gz")
22 | urllib.request.urlretrieve("https://download.jetbrains.com/idea/ideaIC-2021.2.tar.gz",
23 | archiveFilePath)
24 |
25 | print("Extracting IntelliJ IDEA...")
26 | with tarfile.open(archiveFilePath) as f: f.extractall(tmpDirPath)
27 | archiveFilePath.unlink()
28 |
29 | return tmpDirPath.joinpath("idea-IC-212.4746.92")
30 |
31 |
32 |
33 | def runIdea(ideaDirPath: pathlib.Path, tmpDirPath: pathlib.Path) -> None:
34 | repoDirPath = pathlib.Path(__file__).parent.parent.resolve()
35 | resultsDirPath = tmpDirPath.joinpath("results")
36 |
37 | print("Running IntelliJ IDEA...")
38 | subprocess.run([str(ideaDirPath.joinpath("bin", "idea.sh")), "inspect", str(repoDirPath),
39 | repoDirPath.joinpath(".idea", "inspectionProfiles", "Project_Default.xml"),
40 | str(resultsDirPath), "-v2", "-d", "src"])
41 |
42 | hasProblems = False
43 |
44 | for childPath in sorted(resultsDirPath.iterdir()):
45 | if childPath.name == ".descriptions.xml": continue
46 |
47 | if not hasProblems:
48 | print("")
49 | print("Found problems with IntelliJ IDEA!")
50 |
51 | hasProblems = True
52 | print("")
53 | print(f"{childPath.name}:")
54 | print("")
55 | with open(childPath, "r") as f: print(f.read())
56 |
57 | if hasProblems:
58 | sys.exit(1)
59 | else:
60 | print("")
61 | print("No problems found with IntelliJ IDEA.")
62 |
63 |
64 |
65 | def main() -> None:
66 | parser = argparse.ArgumentParser(description="Inspect Code with IntelliJ IDEA.")
67 | parser.add_argument("--idea-path", type=pathlib.Path,
68 | help="Directory to IntelliJ IDEA to use; will be downloaded if omitted")
69 | arguments = parser.parse_args()
70 |
71 | with tempfile.TemporaryDirectory() as tmpDirPathStr:
72 | tmpDirPath = pathlib.Path(tmpDirPathStr)
73 | ideaDirPath = (arguments.idea_path if arguments.idea_path is not None else
74 | downloadIdea(tmpDirPath))
75 | runIdea(ideaDirPath, tmpDirPath)
76 |
77 |
78 |
79 | if __name__ == "__main__":
80 | main()
81 |
--------------------------------------------------------------------------------