├── .dir-locals.el
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── config.yml
└── workflows
│ └── test.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── eglot-tests.el
├── eglot.el
└── gif-examples
├── eglot-code-actions.gif
├── eglot-completions.gif
├── eglot-diagnostics.gif
├── eglot-hover-on-symbol.gif
├── eglot-rename.gif
├── eglot-snippets-on-completion.gif
├── eglot-xref-find-definition.gif
└── eglot-xref-find-references.gif
/.dir-locals.el:
--------------------------------------------------------------------------------
1 | ;;; Directory Local Variables
2 | ;;; For more information see (info "(emacs) Directory Variables")
3 |
4 | ((nil . ((sentence-end-double-space . t)
5 | (fill-column . 70)))
6 | (log-edit-mode . ((log-edit-font-lock-gnu-style . t)
7 | (log-edit-setup-add-author . t)))
8 | (change-log-mode . ((add-log-time-zone-rule . t)
9 | (fill-column . 74)))
10 | (diff-mode . ((mode . whitespace)))
11 | (emacs-lisp-mode . ((indent-tabs-mode . nil)
12 | (electric-quote-comment . nil)
13 | (electric-quote-string . nil))))
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report with MRE recipe
3 | about: Something didn't work right, and you've written a perfect MRE recipe
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 | ---
8 |
39 |
40 | [1]: https://stackoverflow.com/help/minimal-reproducible-example
41 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: "Read Troubleshooting section of the Eglot manual"
4 | url: https://joaotavora.github.io/eglot/#Troubleshooting-Eglot
5 | name: How to troubleshoot and report problems related to Eglot
6 | - name: "Report bugs informally, discuss Eglot, request features"
7 | url: https://github.com/joaotavora/eglot/discussions
8 | about: Informally and freely report problems, ask questions, etc
9 | - name: "Start an email discussion in bug-gnu-emacs@gnu.org"
10 | url: https://www.gnu.org/software/emacs/manual/html_node/efaq/Reporting-bugs.html
11 | about: Informally and freely report problems, ask questions, etc
12 | - name: "Additional Support: Emacs Help"
13 | url: https://lists.gnu.org/mailman/listinfo/help-gnu-emacs
14 | about: Search, ask and answer questions
15 | - name: "Additional Support: Emacs StackExchange"
16 | url: https://emacs.stackexchange.com/questions/tagged/eglot
17 | about: Search, ask and answer questions
18 | - name: "Additional Support: Emacs Reddit"
19 | url: https://www.reddit.com/r/emacs
20 | about: Search, ask and answer questions
21 |
22 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 | on:
3 | push:
4 | pull_request:
5 | workflow_dispatch:
6 | inputs:
7 | debug_enabled:
8 | type: boolean
9 | description: 'Run the build with tmate debugging enabled (https://github.com/marketplace/actions/debugging-with-tmate)'
10 | required: false
11 | default: false
12 | jobs:
13 | test:
14 | runs-on: ubuntu-latest
15 |
16 | strategy:
17 | matrix:
18 | emacs_version:
19 | - 26.3
20 | - 27.2
21 | - 28.2
22 | - 29.4
23 | - snapshot
24 |
25 | steps:
26 | - uses: actions/checkout@v2
27 |
28 | - name: Install emacs
29 | uses: purcell/setup-emacs@master
30 | with:
31 | version: ${{ matrix.emacs_version }}
32 |
33 | - name: Install Python
34 | uses: actions/setup-python@v2
35 | with:
36 | python-version: "3.9"
37 | architecture: "x64"
38 |
39 | - name: Install Rust
40 | uses: actions-rs/toolchain@v1
41 | with:
42 | toolchain: stable
43 | override: true
44 | components: rust-analyzer
45 |
46 | - name: Install other depedencies
47 | run: |
48 | pip3 install python-lsp-server autopep8 rope pycodestyle pyflakes pydocstyle mccabe pylint
49 | sudo apt-get install clangd
50 | ln -sf `rustup which --toolchain stable rust-analyzer` ~/.cargo/bin/rust-analyzer
51 |
52 | - name: Build eglot
53 | run: make compile
54 |
55 | - name: Test eglot
56 | run: |
57 | make eglot-check
58 |
59 | - name: Setup debugging tmate session maybe
60 | uses: mxschmitt/action-tmate@v3
61 | if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
62 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ChangeLog
2 | *.x86f
3 | *.fasl*
4 | *.dfsl
5 | *.lx64fsl
6 | *.dx64fsl
7 | *.elc
8 | *.cls
9 | *~
10 | \#*\#
11 | .\#*
12 | .DS_Store
13 | # for the doc
14 | #
15 | doc/*.html
16 | doc/html
17 | doc/contributors.texi
18 | doc/*.info
19 | doc/*.pdf
20 | doc/*.ps
21 | doc/*.dvi
22 | doc/*.aux
23 | doc/*.cp
24 | doc/*.cps
25 | doc/*.fn
26 | doc/*.fns
27 | doc/*.ky
28 | doc/*.kys
29 | doc/*.log
30 | doc/*.pg
31 | doc/*.toc
32 | doc/*.tp
33 | doc/*.vr
34 | doc/*.op
35 | doc/*.ops
36 | doc/*.pgs
37 | doc/*.vrs
38 | doc/*.tgz
39 | doc/gh-pages
40 | /dist/
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 | {one line to give the program's name and a brief idea of what it does.}
635 | Copyright (C) {year} {name of author}
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | {project} Copyright (C) {year} {fullname}
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | ### Makefile for EGLOT
2 | ###
3 | # Variables
4 | #
5 | EMACS?=emacs
6 | SELECTOR?=t
7 | ERROR_ON_WARN=nil
8 |
9 | LOAD_PATH=-L .
10 |
11 | ELFILES := eglot.el eglot-tests.el
12 | ELCFILES := $(ELFILES:.el=.elc)
13 | ELPADIR := $(shell mktemp -d elpa-eglot-test.XXXXXXXX --tmpdir)
14 | ELPADEPS ?=--eval '(setq package-user-dir "$(ELPADIR)")' \
15 | --eval '(setq package-check-signature nil)' \
16 | --eval '(package-initialize)' \
17 | --eval '(package-refresh-contents)' \
18 | --eval '(defun install-latest (p) \
19 | (let ((desc (cadr \
20 | (assoc p \
21 | package-archive-contents\
22 | (quote equal))))) \
23 | (unless (package-installed-p \
24 | p \
25 | (package-desc-version desc)) \
26 | (package-install desc))))' \
27 | --eval '(install-latest (quote jsonrpc))' \
28 | --eval '(install-latest (quote project))' \
29 | --eval '(install-latest (quote xref))' \
30 | --eval '(install-latest (quote seq))' \
31 | --eval '(install-latest (quote eldoc))' \
32 | --eval '(unintern \
33 | (quote eldoc-documentation-function) nil)' \
34 | --eval '(load "eldoc")' \
35 | --eval '(install-latest (quote company))' \
36 | --eval '(install-latest (quote yasnippet))' \
37 | --eval '(install-latest (quote external-completion))'\
38 | --eval '(install-latest (quote flymake))'
39 |
40 | BYTECOMP_ERROR_ON_WARN := \
41 | --eval '(setq byte-compile-error-on-warn $(ERROR_ON_WARN))'
42 |
43 | all: compile
44 |
45 | # Compilation. Note BYTECOMP_ERROR_ON_WARN after ELPADEPS
46 | # so deps can still warn on compilation.
47 | #
48 | %.elc: %.el
49 | $(EMACS) -Q $(ELPADEPS) $(BYTECOMP_ERROR_ON_WARN) $(LOAD_PATH) \
50 | --batch -f batch-byte-compile $<
51 |
52 | compile: $(ELCFILES)
53 |
54 | # Automated tests
55 | #
56 | eglot-check: compile
57 | $(EMACS) -Q --batch \
58 | $(ELPADEPS) \
59 | $(LOAD_PATH) \
60 | -l eglot \
61 | -l eglot-tests \
62 | --eval '(setq ert-batch-backtrace-right-margin 200)' \
63 | --eval '(ert-run-tests-batch-and-exit (quote $(SELECTOR)))'
64 |
65 | eglot-check-noelpa: ELPADEPS=-f package-initialize
66 | eglot-check-noelpa: eglot-check
67 |
68 | interactive: compile
69 | $(EMACS) -Q \
70 | --eval '(setq debug-on-error t)' \
71 | $(ELPADEPS) \
72 | $(LOAD_PATH) \
73 | -l eglot \
74 | -l eglot-tests \
75 |
76 | check: eglot-check-noelpa
77 |
78 | # Cleanup
79 | #
80 | clean:
81 | find . -iname '*.elc' -exec rm {} \;
82 | .PHONY: all compile clean check
83 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [][build-status]
2 | [](https://elpa.gnu.org/packages/eglot.html)
3 | [](https://melpa.org/#/eglot)
4 |
5 | # M-x Eglot
6 |
7 | *E*macs Poly*glot* is the Emacs [LSP][lsp] client that stays out of
8 | your way:
9 |
10 | * 📽 Scroll down this README for some [pretty gifs](#animated_gifs)
11 | * 📚 Read Eglot's [manual][manual] and [release notes][release-notes]
12 | * 🏆 Folks over at Google [seem to like it][gospb]. Thanks!
13 | * 👾 Eglot now lives in [Emacs itself](#emacscore)!
14 |
15 | See also [eglot-x][eglot-x] for non-standard protocol extensions support.
16 |
17 | # Get stable [GNU ELPA][gnuelpa] version
18 |
19 | Just type `M-x package-install RET eglot RET` into Emacs 26.3+.
20 |
21 | Now find some source file, any source file, and type `M-x eglot`.
22 |
23 | *That's it*. If you're lucky, this guesses the LSP program to start
24 | for the language you're using. Otherwise, it prompts you to enter
25 | one.
26 |
27 | # Get latest development version from [GNU-Devel ELPA][gnudevelelpa]
28 |
29 | First, configure this repository.
30 | ```lisp
31 | (add-to-list 'package-archives '("gnu-devel" . "https://elpa.gnu.org/devel/"))
32 | ```
33 |
34 | Then, use `M-x package-install` or `M-x package-update` to install
35 | an ELPA package from the latest upstream.
36 |
37 |
38 | # Contribute to Eglot's development
39 |
40 | _**Eglot is now in Emacs's core!**_ Upcoming Emacs 29 will have `M-x
41 | eglot` built-in.
42 |
43 | The recommended way to experiment with changes to the latest Eglot is
44 | to [compile][compile-emacs1] [Emacs][compile-emacs2]
45 | [yourself][compile-emacs3-official].
46 |
47 | From a development perspective, moving to core allows us to work on
48 | Eglot in tandem with other related packages already in Emacs, such as
49 | [Flymake][flymake], [ElDoc][eldoc], [Xref][xref], [Project][project].
50 |
51 | This means adding or tweaking an Emacs LSP feature is a matter of
52 | submitting a single patch targetting multiple relevant packages, not
53 | just Eglot.
54 |
55 | These `:core` packages (Eglot included) are then released periodically
56 | to GNU ELPA, so users of other Emacs's versions can get them via
57 | `M-x package-install`.
58 |
59 | # Status of this GitHub repository
60 |
61 | This repository is **not the development upstream anymore**, but it's
62 | **not** dead (yet):
63 |
64 | * It may be used to start [discussions][discussion].
65 |
66 | Sometimes, it's possible the discussion or bug report will be moved
67 | to [Emacs's bug tracker][emacs-bug-tracker-eglot]. You may take the
68 | initiative and start discussion there using `M-x report-emacs-bug`
69 | or simply sending mail to `bug-gnu-emacs@gnu.org`.
70 |
71 | Please the [Eglot-specific bug-reporting instructions][bug-reporting].
72 |
73 | * The [`eglot.el`][eglot.el] file here is periodically updated to mirror
74 | the [Emacs upstream][upstream-eglot.el]
75 |
76 | * The existing tests of [`eglot-tests.el`][eglot-tests.el], also
77 | periodically updated, may be used to rehearse and validate patches
78 | using [GitHub CI infrastructure][build-status].
79 |
80 |
81 | # Connecting to a server
82 |
83 | These are just some of the servers that `M-x eglot` can use out of the
84 | box. The full list can be consulted in the `eglot-server-programs`
85 | variable, where you can [easily add your own servers][manual].
86 |
87 | * Ada's [ada_language_server][ada_language_server]
88 | * Bash's [bash-language-server][bash-language-server]
89 | * C/C++'s [clangd][clangd] or [ccls][ccls]
90 | * C#'s [omnisharp][omnisharp]
91 | * Clojure's [clojure-lsp][clojure-lsp]
92 | * CMake's [cmake-language-server][cmake-language-server]
93 | * CSS's [css-languageserver][css-languageserver]
94 | * Dart's [analysis_server][dart-analysis-server]
95 | * Dockerfile's [docker-langserver][docker-langserver]
96 | * Elixir's [elixir-ls][elixir-ls]
97 | * Elm's [elm-language-server][elm-language-server]
98 | * Erlang's [erlang_ls][erlang_ls]
99 | * Fortran's [fortls][fortls]
100 | * Futhark's [futhark lsp][futhark-lsp]
101 | * Go's [gopls][gopls]
102 | * Godot Engine's [built-in LSP][godot]
103 | * HTML [html-languageserver][html-languageserver]
104 | * Haskell's [haskell-language-server][haskell-language-server]
105 | * JSON's [vscode-json-languageserver][vscode-json-languageserver]
106 | * Java's [Eclipse JDT Language Server][eclipse-jdt]
107 | * Javascript's [TS & JS Language Server][typescript-language-server]
108 | * Kotlin's [kotlin-language-server][kotlin-language-server]
109 | * Lua's [lua-lsp][lua-lsp]
110 | * Markdown's [marksman][marksman]
111 | * Mint's [mint-ls][mint-ls]
112 | * Nix's [rnix-lsp][rnix-lsp]
113 | * Ocaml's [ocaml-lsp][ocaml-lsp]
114 | * Perl's [Perl::LanguageServer][perl-language-server]
115 | * PHP's [php-language-server][php-language-server]
116 | * PureScript's [purescript-language-server][purescript-language-server]
117 | * Python's [pylsp][pylsp], [pyls][pyls] [pyright][pyright], or [jedi-language-server][jedi-language-server]
118 | * R's [languageserver][r-languageserver]
119 | * Racket's [racket-langserver][racket-langserver]
120 | * Ruby's [solargraph][solargraph]
121 | * Rust's [rust-analyzer][rust-analyzer]
122 | * Scala's [metals][metals]
123 | * TeX/LaTeX's [Digestif][digestif] ot [texlab][texlab]
124 | * VimScript's [vim-language-server][vim-language-server]
125 | * YAML's [yaml-language-server][yaml-language-server]
126 | * Zig's [zls][zls]
127 |
128 |
129 | # _Obligatory animated gif section_
130 |
131 | ## Completion
132 | 
133 |
134 | The animation shows [company-mode][company] presenting the completion
135 | candidates to the user, but Eglot works with the built-in
136 | `completion-at-point` function as well, which is usually bound to
137 | `C-M-i`.
138 |
139 | ## Snippet completion
140 | 
141 |
142 | Eglot provides template based completion if the server supports
143 | snippet completion and [yasnippet][yasnippet] is enabled _before_
144 | Eglot connects to the server. The animation shows
145 | [company-mode][company], but `completion-at-point` also works with
146 | snippets.
147 |
148 | ## Diagnostics
149 | 
150 |
151 | Eglot relays the diagnostics information received from the LSP server
152 | to Emacs's [Flymake][flymake], which annotates/underlines the
153 | problematic parts of the buffer. The information is shared with the
154 | [ElDoc][eldoc] system, meaning that the commands `eldoc` and
155 | `eldoc-doc-buffer` (the latter bound to `C-h-.` for convenience) show
156 | diagnostics along with other documentation under point.
157 |
158 | [Flymake][flymake] provides other convenient ways to view and manage
159 | diagnostic errors. These are described in its [manual][flymake].
160 |
161 | When Eglot manages a buffer, it disables pre-existing Flymake
162 | backends. See variable `eglot-stay-out-of` to change that.
163 |
164 | ## Code Actions
165 | 
166 |
167 | The LSP server may provide code actions, for example, to fix a
168 | diagnostic error or to suggest refactoring edits. The commands are
169 | frequently associating with Flymake diagnostic annotations, so that
170 | left-clicking them shows a menu. Additionally, the command
171 | `eglot-code-actions` asks the server for any code spanning a given
172 | region.
173 |
174 | Sometimes, these code actions are initiated by the server. See
175 | `eglot-confirm-server-initiated-edits` to control that behaviour.
176 |
177 | ## Hover on symbol /function signature
178 | 
179 |
180 | Here, too, the LSP server's view of a given symbol or function
181 | signature is relayed to the [ElDoc][eldoc] system. The commands
182 | `eldoc` and `eldoc-doc-buffer` commands access that information.
183 |
184 | There are customization variables to help adjust [ElDoc][eldoc]'s
185 | liberal use of the lower "echo area", among other options. If you
186 | still find the solicitous nature of this LSP feature too distracing,
187 | you can use `eglot-ignored-server-capabilities` to turn it off.
188 |
189 | ## Rename
190 | 
191 |
192 | Type `M-x eglot-rename RET` to rename the symbol at point.
193 |
194 | ## Find definition
195 | 
196 |
197 | To jump to the definition of a symbol, use the built-in
198 | `xref-find-definitions` command, which is bound to `M-.`.
199 |
200 | ## Find references
201 | 
202 |
203 | Eglot here relies on Emacs' built-in functionality as well.
204 | `xref-find-references` is bound to `M-?`. Additionally, Eglot
205 | provides the following similar commands: `eglot-find-declaration`,
206 | `eglot-find-implementation`, `eglot-find-typeDefinition`.
207 |
208 | # Historical differences to lsp-mode.el
209 |
210 | Around May 2018, I wrote a comparison of Eglot to `lsp-mode.el`, and
211 | was discussed with its then-maintainer. That mode has since been
212 | refactored/rewritten and now
213 | [purports to support](https://github.com/joaotavora/eglot/issues/180)
214 | a lot of features that differentiated Eglot from it. It may now be
215 | very different or very similar to Eglot, or even sing with the birds
216 | in the trees, so [go check it out][emacs-lsp]. That said, here's the
217 | original comparison, which I will not be updating any more.
218 |
219 | "Eglot is considerably less code and hassle than lsp-mode.el. In most
220 | cases, there's nothing to configure. It's a minimalist approach
221 | focused on user experience and performance.
222 |
223 | User-visible differences:
224 |
225 | - The single most visible difference is the friendly entry point `M-x
226 | eglot`, not `M-x eglot-`. Also, there are no
227 | `eglot-` extra packages.
228 |
229 | - There's no "whitelisting" or "blacklisting" directories to
230 | languages. `M-x eglot` starts servers to handle file of a major
231 | mode inside a specific project, using Emacs's built-in `project.el`
232 | library to discover projects. Then it automatically detects current
233 | and future opened files under that project and syncs with server;
234 |
235 | - Easy way to quit/restart a server, just middle/right click on the
236 | connection name;
237 | - Pretty interactive mode-line section for live tracking of server
238 | communication;
239 | - Automatically restarts frequently crashing servers;
240 | - Slow-to-start servers start asynchronously in the background;
241 | - Server-initiated edits are confirmed with the user;
242 | - Diagnostics work out-of-the-box (no `flycheck.el` needed);
243 | - Smoother/more responsive (read below).
244 |
245 | Under the hood:
246 |
247 | - Message parser is much simpler.
248 | - Defers signature requests like `textDocument/hover` until server is
249 | ready.
250 | - Sends `textDocument/didChange` for groups of edits, not
251 | one per each tiny change.
252 | - Easier to read and maintain elisp. Yeah I know, *very subjective*,
253 | so judge for yourself.
254 | - Doesn't *require* anything other than Emacs, but will automatically
255 | upgrade to work with stuff outside Emacs, like `company`,
256 | `markdown-mode`, if you happen to have these installed.
257 | - Has automated tests that check against actual LSP servers."
258 |
259 | # Copyright Assignment
260 |
261 | `Eglot` is subject to the same [copyright assignment][copyright-assignment]
262 | policy as `GNU Emacs`.
263 |
264 | Any [legally significant][legally-significant] contributions can only
265 | be merged after the author has completed their paperwork. Please ask
266 | for the request form, and we'll send it to you.
267 |
268 |
269 | [ada_language_server]: https://github.com/AdaCore/ada_language_server
270 | [bash-language-server]: https://github.com/mads-hartmann/bash-language-server
271 | [clangd]: https://clang.llvm.org/extra/clangd.html
272 | [omnisharp]: https://github.com/OmniSharp/omnisharp-roslyn
273 | [clojure-lsp]: https://clojure-lsp.io
274 | [cmake-language-server]: https://github.com/regen100/cmake-language-server
275 | [css-languageserver]: https://github.com/hrsh7th/vscode-langservers-extracted
276 | [dart-analysis-server]: https://github.com/dart-lang/sdk/blob/master/pkg/analysis_server/tool/lsp_spec/README.md
277 | [elixir-ls]: https://github.com/elixir-lsp/elixir-ls
278 | [elm-language-server]: https://github.com/elm-tooling/elm-language-server
279 | [fortls]: https://github.com/hansec/fortran-language-server
280 | [futhark-lsp]: https://futhark-lang.org
281 | [gopls]: https://github.com/golang/tools/tree/master/gopls
282 | [godot]: https://godotengine.org
283 | [html-languageserver]: https://github.com/hrsh7th/vscode-langservers-extracted
284 | [haskell-language-server]: https://github.com/haskell/haskell-language-server
285 | [jedi-language-server]: https://github.com/pappasam/jedi-language-server
286 | [vscode-json-languageserver]: https://github.com/hrsh7th/vscode-langservers-extracted
287 | [eclipse-jdt]: https://github.com/eclipse/eclipse.jdt.ls
288 | [typescript-language-server]: https://github.com/theia-ide/typescript-language-server
289 | [kotlin-language-server]: https://github.com/fwcd/KotlinLanguageServer
290 | [lua-lsp]: https://github.com/Alloyed/lua-lsp
291 | [marksman]: https://github.com/artempyanykh/marksman
292 | [mint-ls]: https://www.mint-lang.com/
293 | [rnix-lsp]: https://github.com/nix-community/rnix-lsp
294 | [ocaml-lsp]: https://github.com/ocaml/ocaml-lsp/
295 | [perl-language-server]: https://github.com/richterger/Perl-LanguageServer
296 | [php-language-server]: https://github.com/felixfbecker/php-language-server
297 | [purescript-language-server]: https://github.com/nwolverson/purescript-language-server
298 | [pyls]: https://github.com/palantir/python-language-server
299 | [pylsp]: https://github.com/python-lsp/python-lsp-server
300 | [pyright]: https://github.com/microsoft/pyright
301 | [r-languageserver]: https://cran.r-project.org/package=languageserver
302 | [racket-langserver]: https://github.com/jeapostrophe/racket-langserver
303 | [solargraph]: https://github.com/castwide/solargraph
304 | [rust-analyzer]: https://github.com/rust-analyzer/rust-analyzer
305 | [metals]: https://scalameta.org/metals/
306 | [digestif]: https://github.com/astoff/digestif
307 | [texlab]: https://github.com/latex-lsp/texlab
308 | [vim-language-server]: https://github.com/iamcco/vim-language-server
309 | [yaml-language-server]: https://github.com/redhat-developer/yaml-language-server
310 | [zls]: https://github.com/zigtools/zls
311 |
312 |
313 | [manual]: https://joaotavora.github.io/eglot
314 | [lsp]: https://microsoft.github.io/language-server-protocol/
315 | [company-mode]: https://github.com/company-mode/company-mode
316 | [ccls]: https://github.com/MaskRay/ccls
317 | [cquery]: https://github.com/cquery-project/cquery
318 | [docker-langserver]: https://github.com/rcjsuen/dockerfile-language-server-nodejs
319 | [emacs-lsp-plugins]: https://github.com/emacs-lsp
320 | [emacs-lsp]: https://github.com/emacs-lsp/lsp-mode
321 | [erlang_ls]: https://github.com/erlang-ls/erlang_ls
322 | [gnuelpa]: https://elpa.gnu.org/packages/eglot.html
323 | [gnudevelelpa]: https://elpa.gnu.org/devel/eglot.html
324 | [melpa]: https://melpa.org/#/eglot
325 | [news]: https://github.com/joaotavora/eglot/blob/master/NEWS.md
326 | [windows-subprocess-hang]: https://www.gnu.org/software/emacs/manual/html_node/efaq-w32/Subprocess-hang.html
327 | [company]: https://elpa.gnu.org/packages/company.html
328 | [flymake]: https://www.gnu.org/software/emacs/manual/html_node/flymake/index.html#Top
329 | [xref]: https://www.gnu.org/software/emacs/manual/html_node/emacs/Xref.html
330 | [imenu]: https://www.gnu.org/software/emacs/manual/html_node/emacs/Imenu.html
331 | [eldoc]: https://github.com/emacs-mirror/emacs/blob/master/lisp/emacs-lisp/eldoc.el
332 | [yasnippet]: https://elpa.gnu.org/packages/yasnippet.html
333 | [markdown]: https://github.com/defunkt/markdown-mode
334 | [gospb]: https://opensource.googleblog.com/2020/10/announcing-latest-google-open-source.html
335 | [copyright-assignment]: https://www.fsf.org/licensing/contributor-faq
336 | [legally-significant]: https://www.gnu.org/prep/maintain/html_node/Legally-Significant.html#Legally-Significant
337 | [dir-locals-emacs-manual]: https://www.gnu.org/software/emacs/manual/html_node/emacs/Directory-Variables.html
338 | [configuration-request]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_configuration
339 | [did-change-configuration]: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_didChangeConfiguration
340 | [json-serialize]: https://www.gnu.org/software/emacs/manual/html_node/elisp/Parsing-JSON.html
341 | [plist]: https://www.gnu.org/software/emacs/manual/html_node/elisp/Property-Lists.html
342 | [discussion]: https://github.com/joaotavora/eglot/discussions
343 | [upstream-eglot.el]: https://github.com/emacs-mirror/emacs/blob/master/lisp/progmodes/eglot.el
344 | [eglot.el]: https://github.com/joaotavora/eglot/blob/master/eglot.el
345 | [eglot-tests.el]: https://github.com/joaotavora/eglot/blob/master/eglot-tests.el
346 | [announcement]: https://github.com/joaotavora/eglot/discussions
347 | [compile-emacs1]: https://lars.ingebrigtsen.no/2014/11/13/welcome-new-emacs-developers/
348 | [compile-emacs2]: https://batsov.com/articles/2021/12/19/building-emacs-from-source-with-pgtk/
349 | [compile-emacs3-official]: https://github.com/emacs-mirror/emacs/blob/master/INSTALL
350 | [emacs-bug-tracker-eglot]: https://debbugs.gnu.org/cgi/pkgreport.cgi?include=subject%3Aeglot;package=emacs
351 | [bug-reporting]: https://joaotavora.github.io/eglot/#Troubleshooting-Eglot
352 | [project]: https://www.gnu.org/software/emacs/manual/html_node/emacs/Projects.html
353 | [emacs-upstream]: https://github.com/emacs-mirror/emacs
354 | [release-notes]: https://github.com/emacs-mirror/emacs/blob/master/etc/EGLOT-NEWS
355 | [build-status]: https://github.com/joaotavora/eglot/actions/workflows/test.yml
356 | [eglot-x]: https://github.com/nemethf/eglot-x
357 |
--------------------------------------------------------------------------------
/eglot-tests.el:
--------------------------------------------------------------------------------
1 | ;;; eglot-tests.el --- Tests for eglot.el -*- lexical-binding: t; -*-
2 |
3 | ;; Copyright (C) 2018-2025 Free Software Foundation, Inc.
4 |
5 | ;; Author: João Távora
6 | ;; Keywords: tests
7 |
8 | ;; This file is part of GNU Emacs.
9 |
10 | ;; GNU Emacs is free software: you can redistribute it and/or modify
11 | ;; it under the terms of the GNU General Public License as published by
12 | ;; the Free Software Foundation, either version 3 of the License, or
13 | ;; (at your option) any later version.
14 |
15 | ;; GNU Emacs is distributed in the hope that it will be useful,
16 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | ;; GNU General Public License for more details.
19 |
20 | ;; You should have received a copy of the GNU General Public License
21 | ;; along with GNU Emacs. If not, see .
22 |
23 | ;;; Commentary:
24 |
25 | ;; Tests for lisp/progmodes/eglot.el
26 | ;;
27 | ;; Many of these tests rely on the availability of third-party LSP
28 | ;; servers. They are automatically skipped if the program is not
29 | ;; available.
30 | ;;
31 | ;; Some of these tests rely on the GNU ELPA package company.el and
32 | ;; yasnippet.el being available.
33 |
34 | ;; Some of the tests require access to a remote host files, which is
35 | ;; mocked in the simplest case. If you want to test a real Tramp
36 | ;; connection, override $REMOTE_TEMPORARY_FILE_DIRECTORY to a suitable
37 | ;; value (FIXME: like what?) in order to overwrite the default value.
38 | ;;
39 | ;; IMPORTANT: Since Eglot is a :core ELPA package, these tests are
40 | ;; supposed to run on Emacsen down to 26.3. Do not use bleeding-edge
41 | ;; functionality not compatible with that Emacs version.
42 |
43 | ;;; Code:
44 | (require 'eglot)
45 | (require 'cl-lib)
46 | (require 'ert)
47 | (require 'tramp)
48 | (require 'ert-x) ; ert-simulate-command
49 | (require 'edebug)
50 | (require 'cc-mode) ; c-mode-hook
51 | (require 'company nil t)
52 | (require 'yasnippet nil t)
53 | (require 'subr-x)
54 | (require 'flymake) ; project-diagnostics
55 |
56 | ;;; Helpers
57 |
58 | (defun eglot--test-message (format &rest args)
59 | "Message out with FORMAT with ARGS."
60 | (message "[eglot-tests] %s"
61 | (apply #'format format args)))
62 |
63 | (defmacro eglot--with-fixture (fixture &rest body)
64 | "Set up FIXTURE, call BODY, tear down FIXTURE.
65 | FIXTURE is a list. Its elements are of the form (FILE . CONTENT)
66 | to create a readable FILE with CONTENT. FILE may be a directory
67 | name and CONTENT another (FILE . CONTENT) list to specify a
68 | directory hierarchy."
69 | (declare (indent 1) (debug t))
70 | `(eglot--call-with-fixture ,fixture (lambda () ,@body)))
71 |
72 | (defun eglot--make-file-or-dir (ass)
73 | (let ((file-or-dir-name (expand-file-name (car ass)))
74 | (content (cdr ass)))
75 | (cond ((listp content)
76 | (make-directory file-or-dir-name 'parents)
77 | (let ((default-directory (file-name-as-directory file-or-dir-name)))
78 | (mapcan #'eglot--make-file-or-dir content)))
79 | ((stringp content)
80 | (with-temp-file file-or-dir-name
81 | (insert content))
82 | (list file-or-dir-name))
83 | (t
84 | (eglot--error "Expected a string or a directory spec")))))
85 |
86 | (defun eglot--call-with-fixture (fixture fn)
87 | "Helper for `eglot--with-fixture'. Run FN under FIXTURE."
88 | (let* ((fixture-directory (make-nearby-temp-file "eglot--fixture-" t))
89 | (default-directory (file-name-as-directory fixture-directory))
90 | created-files
91 | new-servers
92 | test-body-successful-p)
93 | (eglot--test-message "[%s]: test start" (ert-test-name (ert-running-test)))
94 | (unwind-protect
95 | (let ((process-environment
96 | `(;; Set XDG_CONFIG_HOME to /dev/null to prevent
97 | ;; user-configuration influencing language servers
98 | ;; (see github#441).
99 | ,(format "XDG_CONFIG_HOME=%s" null-device)
100 | ;; ... on the flip-side, a similar technique in
101 | ;; Emacs's `test/Makefile' spoofs HOME as
102 | ;; /nonexistent (and as `temporary-file-directory' in
103 | ;; `ert-remote-temporary-file-directory').
104 | ;; This breaks some common installations for LSP
105 | ;; servers like rust-analyzer, making these tests
106 | ;; mostly useless, so we hack around it here with a
107 | ;; great big hack.
108 | ,(format "HOME=%s"
109 | (expand-file-name (format "~%s" (user-login-name))))
110 | ,@process-environment))
111 | (eglot-server-initialized-hook
112 | (lambda (server) (push server new-servers))))
113 | (setq created-files (mapcan #'eglot--make-file-or-dir fixture))
114 | (prog1 (funcall fn)
115 | (setq test-body-successful-p t)))
116 | (eglot--test-message "[%s]: %s" (ert-test-name (ert-running-test))
117 | (if test-body-successful-p "OK" "FAILED"))
118 | (unwind-protect
119 | (let ((eglot-autoreconnect nil))
120 | (dolist (server new-servers)
121 | (when (jsonrpc-running-p server)
122 | (condition-case oops
123 | (eglot-shutdown
124 | server nil 3 (not test-body-successful-p))
125 | (error
126 | (eglot--test-message "Non-critical cleanup error: %S" oops))))
127 | (when (not test-body-successful-p)
128 | ;; We want to do this after the sockets have
129 | ;; shut down such that any pending data has been
130 | ;; consumed and is available in the process
131 | ;; buffers.
132 | (let ((buffers (delq nil (list
133 | ;; FIXME: Accessing "internal" symbol here.
134 | (process-buffer (jsonrpc--process server))
135 | (jsonrpc-stderr-buffer server)
136 | (jsonrpc-events-buffer server)))))
137 | (cond (noninteractive
138 | (dolist (buffer buffers)
139 | (eglot--test-message "contents of `%s' %S:" (buffer-name buffer) buffer)
140 | (if (buffer-live-p buffer)
141 | (princ (with-current-buffer buffer (buffer-string))
142 | 'external-debugging-output)
143 | (princ "Killed\n" #'external-debugging-output))))
144 | (t
145 | (eglot--test-message "Preserved for inspection: %s"
146 | (mapconcat #'buffer-name buffers ", "))))))))
147 | (eglot--cleanup-after-test fixture-directory created-files)))))
148 |
149 | (defun eglot--cleanup-after-test (fixture-directory created-files)
150 | (let ((buffers-to-delete
151 | (delq nil (mapcar #'find-buffer-visiting created-files))))
152 | (eglot--test-message "Killing %s, wiping %s"
153 | buffers-to-delete
154 | fixture-directory)
155 | (dolist (buf buffers-to-delete) ;; Have to save otherwise will get prompted.
156 | (with-current-buffer buf (save-buffer) (kill-buffer)))
157 | (delete-directory fixture-directory 'recursive)
158 | ;; Delete Tramp buffers if needed.
159 | (when (file-remote-p temporary-file-directory)
160 | (tramp-cleanup-connection
161 | (tramp-dissect-file-name temporary-file-directory) nil 'keep-password))))
162 |
163 | (cl-defmacro eglot--with-timeout (timeout &body body)
164 | (declare (indent 1) (debug t))
165 | `(eglot--call-with-timeout ,timeout (lambda () ,@body)))
166 |
167 | (defun eglot--call-with-timeout (timeout fn)
168 | (let* ((tag (gensym "eglot-test-timeout"))
169 | (timed-out (make-symbol "timeout"))
170 | (timeout-and-message
171 | (if (listp timeout) timeout
172 | (list timeout "waiting for test to finish")))
173 | (timeout (car timeout-and-message))
174 | (message (cadr timeout-and-message))
175 | (timer)
176 | (retval))
177 | (unwind-protect
178 | (setq retval
179 | (catch tag
180 | (setq timer
181 | (run-with-timer timeout nil
182 | (lambda ()
183 | (unless edebug-active
184 | (throw tag timed-out)))))
185 | (funcall fn)))
186 | (cancel-timer timer)
187 | (when (eq retval timed-out)
188 | (error "%s" (concat "Timed out " message))))))
189 |
190 | (defun eglot--find-file-noselect (file &optional noerror)
191 | (unless (or noerror
192 | (file-readable-p file)) (error "%s does not exist" file))
193 | (find-file-noselect file))
194 |
195 | (cl-defmacro eglot--sniffing ((&key server-requests
196 | server-notifications
197 | server-replies
198 | client-requests
199 | client-notifications
200 | client-replies)
201 | &rest body)
202 | "Run BODY saving LSP JSON messages in variables, most recent first."
203 | (declare (indent 1) (debug (sexp &rest form)))
204 | (let ((log-event-hook-sym (make-symbol "eglot--event-sniff")))
205 | `(let* (,@(delq nil (list server-requests
206 | server-notifications
207 | server-replies
208 | client-requests
209 | client-notifications
210 | client-replies)))
211 | (cl-flet ((,log-event-hook-sym (_connection
212 | origin
213 | &key _json kind message _foreign-message
214 | &allow-other-keys)
215 | (let ((req-p (eq kind 'request))
216 | (notif-p (eq kind 'notification))
217 | (reply-p (eql kind 'reply)))
218 | (cond
219 | ((eq origin 'server)
220 | (cond (req-p ,(when server-requests
221 | `(push message ,server-requests)))
222 | (notif-p ,(when server-notifications
223 | `(push message ,server-notifications)))
224 | (reply-p ,(when server-replies
225 | `(push message ,server-replies)))))
226 | ((eq origin 'client)
227 | (cond (req-p ,(when client-requests
228 | `(push message ,client-requests)))
229 | (notif-p ,(when client-notifications
230 | `(push message ,client-notifications)))
231 | (reply-p ,(when client-replies
232 | `(push message ,client-replies)))))))))
233 | (unwind-protect
234 | (progn
235 | (add-hook 'jsonrpc-event-hook #',log-event-hook-sym t)
236 | ,@body)
237 | (remove-hook 'jsonrpc-event-hook #',log-event-hook-sym))))))
238 |
239 | (cl-defmacro eglot--wait-for ((events-sym &optional (timeout 1) message) args &body body)
240 | (declare (indent 2) (debug (sexp sexp sexp &rest form)))
241 | `(eglot--with-timeout '(,timeout ,(or message
242 | (format "waiting for:\n%s" (pp-to-string body))))
243 | (eglot--test-message "waiting for `%s'" (with-output-to-string
244 | (mapc #'princ ',body)))
245 | (let ((events
246 | (cl-loop thereis (cl-loop for json in ,events-sym
247 | for method = (plist-get json :method)
248 | when (keywordp method)
249 | do (plist-put json :method
250 | (substring
251 | (symbol-name method)
252 | 1))
253 | when (funcall
254 | (jsonrpc-lambda ,args ,@body) json)
255 | return (cons json before)
256 | collect json into before)
257 | for i from 0
258 | when (zerop (mod i 5))
259 | ;; do (eglot--test-message "still struggling to find in %s"
260 | ;; ,events-sym)
261 | do
262 | ;; `read-event' is essential to have the file
263 | ;; watchers come through.
264 | (cond ((fboundp 'flush-standard-output)
265 | (read-event nil nil 0.1) (princ ".")
266 | (flush-standard-output))
267 | (t
268 | (read-event "." nil 0.1)))
269 | (accept-process-output nil 0.1))))
270 | (setq ,events-sym (cdr events))
271 | (cl-destructuring-bind (&key method id &allow-other-keys) (car events)
272 | (eglot--test-message "detected: %s"
273 | (or method (and id (format "id=%s" id))))))))
274 |
275 | ;; `rust-mode' is not a part of Emacs, so we define these two shims
276 | ;; which should be more than enough for testing.
277 | (unless (functionp 'rust-mode)
278 | (define-derived-mode rust-mode prog-mode "Rust")
279 | (add-to-list 'auto-mode-alist '("\\.rs\\'" . rust-mode)))
280 |
281 | ;; `typescript-mode' is not a part of Emacs, so we define these two
282 | ;; shims which should be more than enough for testing.
283 | (unless (functionp 'typescript-mode)
284 | (define-derived-mode typescript-mode prog-mode "TypeScript")
285 | (add-to-list 'auto-mode-alist '("\\.ts\\'" . typescript-mode)))
286 |
287 | (defun eglot--tests-connect (&optional timeout)
288 | (let* ((timeout (or timeout 10))
289 | (eglot-sync-connect t)
290 | (eglot-connect-timeout timeout))
291 | (apply #'eglot--connect (eglot--guess-contact))))
292 |
293 | (defun eglot--simulate-key-event (char)
294 | "Like (execute-kbd-macro (vector char)), but with `call-interactively'."
295 | ;; Also, this is a bit similar to what electric-tests.el does.
296 | (setq last-input-event char)
297 | (setq last-command-event char)
298 | (call-interactively (key-binding (vector char))))
299 |
300 | (defun eglot--clangd-version ()
301 | "Report on the clangd version used in various tests."
302 | (let ((version (shell-command-to-string "clangd --version")))
303 | (when (string-match "version[[:space:]]+\\([0-9.]*\\)"
304 | version)
305 | (match-string 1 version))))
306 |
307 |
308 | ;;; Unit tests
309 |
310 | (ert-deftest eglot-test-eclipse-connect ()
311 | "Connect to eclipse.jdt.ls server."
312 | (skip-unless (executable-find "jdtls"))
313 | (eglot--with-fixture
314 | '(("project/src/main/java/foo" . (("Main.java" . ""))))
315 | (with-current-buffer
316 | (eglot--find-file-noselect "project/src/main/java/foo/Main.java")
317 | (eglot--sniffing (:server-notifications s-notifs)
318 | (should (eglot--tests-connect 20))
319 | (eglot--wait-for (s-notifs 10)
320 | (&key _id method &allow-other-keys)
321 | (string= method "language/status"))))))
322 |
323 | (defun eglot-tests--auto-detect-running-server-1 ()
324 | (let (server)
325 | (eglot--with-fixture
326 | `(("project" . (("coiso.c" . "bla")
327 | ("merdix.c" . "bla")))
328 | ("anotherproject" . (("cena.c" . "bla"))))
329 | (with-current-buffer
330 | (eglot--find-file-noselect "project/coiso.c")
331 | (should (setq server (eglot--tests-connect)))
332 | (should (eglot-current-server)))
333 | (with-current-buffer
334 | (eglot--find-file-noselect "project/merdix.c")
335 | (should (eglot-current-server))
336 | (should (eq (eglot-current-server) server)))
337 | (with-current-buffer
338 | (eglot--find-file-noselect "anotherproject/cena.c")
339 | (should-error (eglot--current-server-or-lose))))))
340 |
341 | (ert-deftest eglot-test-auto-detect-running-server ()
342 | "Visit a file and \\[eglot], then visit a neighbor."
343 | (skip-unless (executable-find "clangd"))
344 | (eglot-tests--auto-detect-running-server-1))
345 |
346 | (ert-deftest eglot-test-auto-shutdown ()
347 | "Visit a file and \\[eglot], then kill buffer."
348 | (skip-unless (executable-find "clangd"))
349 | (let (server
350 | buffer)
351 | (eglot--with-fixture
352 | `(("project" . (("thingy.c" . "int main() {return 0;}"))))
353 | (with-current-buffer
354 | (setq buffer (eglot--find-file-noselect "project/thingy.c"))
355 | (should (setq server (eglot--tests-connect)))
356 | (should (eglot-current-server))
357 | (let ((eglot-autoshutdown nil)) (kill-buffer buffer))
358 | (should (jsonrpc-running-p server))
359 | ;; re-find file...
360 | (setq buffer (eglot--find-file-noselect (buffer-file-name buffer)))
361 | ;; ;; but now kill it with `eglot-autoshutdown' set to t
362 | (let ((eglot-autoshutdown t)) (kill-buffer buffer))
363 | (should (not (jsonrpc-running-p server)))))))
364 |
365 | (ert-deftest eglot-test-auto-reconnect ()
366 | "Start a server. Kill it. Watch it reconnect."
367 | (skip-unless (executable-find "clangd"))
368 | (let (server (eglot-autoreconnect 1))
369 | (eglot--with-fixture
370 | `(("project" . (("thingy.c" . "bla")
371 | ("thingy2.c" . "bla"))))
372 | (with-current-buffer
373 | (eglot--find-file-noselect "project/thingy.c")
374 | (should (setq server (eglot--tests-connect)))
375 | ;; In 1.2 seconds > `eglot-autoreconnect' kill servers. We
376 | ;; should have a automatic reconnection.
377 | (run-with-timer 1.2 nil (lambda () (delete-process
378 | (jsonrpc--process server))))
379 | (while (jsonrpc-running-p server) (accept-process-output nil 0.5))
380 | (should (eglot-current-server))
381 | ;; Now try again too quickly
382 | (setq server (eglot-current-server))
383 | (let ((proc (jsonrpc--process server)))
384 | (run-with-timer 0.5 nil (lambda () (delete-process proc)))
385 | (while (process-live-p proc) (accept-process-output nil 0.5)))
386 | (should (not (eglot-current-server)))))))
387 |
388 | (ert-deftest eglot-test-rust-analyzer-watches-files ()
389 | "Start rust-analyzer. Notify it when a critical file changes."
390 | (skip-unless (executable-find "rust-analyzer"))
391 | (skip-unless (executable-find "cargo"))
392 | (let ((eglot-autoreconnect 1))
393 | (eglot--with-fixture
394 | '(("watch-project" . (("coiso.rs" . "bla")
395 | ("merdix.rs" . "bla"))))
396 | (with-current-buffer
397 | (eglot--find-file-noselect "watch-project/coiso.rs")
398 | (should (zerop (shell-command "cargo init")))
399 | (eglot--sniffing (
400 | :server-requests s-requests
401 | :client-notifications c-notifs
402 | :client-replies c-replies
403 | )
404 | (should (eglot--tests-connect))
405 | (let (register-id)
406 | (eglot--wait-for (s-requests 3)
407 | (&key id method &allow-other-keys)
408 | (setq register-id id)
409 | (string= method "client/registerCapability"))
410 | (eglot--wait-for (c-replies 1)
411 | (&key id error &allow-other-keys)
412 | (and (eq id register-id) (null error))))
413 | (delete-file "Cargo.toml")
414 | (eglot--wait-for
415 | (c-notifs 3 "waiting for didChangeWatchedFiles notification")
416 | (&key method params &allow-other-keys)
417 | (and (string= method "workspace/didChangeWatchedFiles")
418 | (cl-destructuring-bind (&key uri type)
419 | (elt (plist-get params :changes) 0)
420 | (and (string= (eglot-path-to-uri "Cargo.toml") uri)
421 | (= type 3))))))))))
422 |
423 | (ert-deftest eglot-test-basic-diagnostics ()
424 | "Test basic diagnostics."
425 | (skip-unless (executable-find "clangd"))
426 | (eglot--with-fixture
427 | `(("diag-project" .
428 | (("main.c" . "int main(){froat a = 42.2; return 0;}"))))
429 | (with-current-buffer
430 | (eglot--find-file-noselect "diag-project/main.c")
431 | (eglot--sniffing (:server-notifications s-notifs)
432 | (eglot--tests-connect)
433 | (eglot--wait-for (s-notifs 10)
434 | (&key _id method &allow-other-keys)
435 | (string= method "textDocument/publishDiagnostics"))
436 | (flymake-start)
437 | (goto-char (point-min))
438 | (flymake-goto-next-error 1 '() t)
439 | (should (eq 'flymake-error (face-at-point)))))))
440 |
441 | (ert-deftest eglot-test-basic-symlink ()
442 | "Test basic symlink support."
443 | (skip-unless (executable-find "clangd"))
444 | ;; MS-Windows either fails symlink creation or pops up UAC prompts.
445 | (skip-unless (not (eq system-type 'windows-nt)))
446 | (eglot--with-fixture
447 | `(("symlink-project" .
448 | (("main.cpp" . "#include\"foo.h\"\nint main() { return foo(); }")
449 | ("foo.h" . "int foo();"))))
450 | (with-current-buffer
451 | (find-file-noselect "symlink-project/main.cpp")
452 | (make-symbolic-link "main.cpp" "mainlink.cpp")
453 | (eglot--tests-connect)
454 | (eglot--sniffing (:client-notifications c-notifs)
455 | (let ((eglot-autoshutdown nil)) (kill-buffer (current-buffer)))
456 | (eglot--wait-for (c-notifs 10)
457 | (&key method &allow-other-keys)
458 | (and (string= method "textDocument/didClose")))))
459 | (eglot--sniffing (:client-notifications c-notifs)
460 | (with-current-buffer
461 | (find-file-noselect "symlink-project/main.cpp")
462 | (should (eglot-current-server)))
463 | (eglot--wait-for (c-notifs 10)
464 | (&rest whole &key params method &allow-other-keys)
465 | (and (string= method "textDocument/didOpen")
466 | (string-match "main.cpp$"
467 | (plist-get (plist-get params :textDocument)
468 | :uri)))))
469 | ;; This last segment is deactivated, because it's likely not needed.
470 | ;; The only way the server would answer with '3' references is if we
471 | ;; had erroneously sent a 'didOpen' for anything other than
472 | ;; `main.cpp', but if we got this far is because we've just asserted
473 | ;; that we didn't.
474 | (when nil
475 | (with-current-buffer
476 | (find-file-noselect "symlink-project/foo.h")
477 | ;; Give clangd some time to settle its analysis so it can
478 | ;; accurately respond to `textDocument/references'
479 | (sleep-for 3)
480 | (search-forward "foo")
481 | (eglot--sniffing (:server-replies s-replies)
482 | (call-interactively 'xref-find-references)
483 | (eglot--wait-for (s-replies 10)
484 | (&key method result &allow-other-keys)
485 | ;; Expect xref buffer to not contain duplicate references to
486 | ;; main.cpp and mainlink.cpp. If it did, 'result's length
487 | ;; would be 3.
488 | (and (string= method "textDocument/references")
489 | (= (length result) 2))))))))
490 |
491 | (ert-deftest eglot-test-diagnostic-tags-unnecessary-code ()
492 | "Test rendering of diagnostics tagged \"unnecessary\"."
493 | (skip-unless (executable-find "clangd"))
494 | (skip-unless (version<= "14" (eglot--clangd-version)))
495 | (eglot--with-fixture
496 | `(("diag-project" .
497 | (("main.cpp" . "int main(){float a = 42.2; return 0;}"))))
498 | (with-current-buffer
499 | (eglot--find-file-noselect "diag-project/main.cpp")
500 | (eglot--make-file-or-dir '(".git"))
501 | (eglot--make-file-or-dir
502 | `("compile_commands.json" .
503 | ,(jsonrpc--json-encode
504 | `[(:directory ,default-directory :command "/usr/bin/c++ -Wall -c main.cpp"
505 | :file ,(expand-file-name "main.cpp"))])))
506 | (let ((eglot-server-programs '((c++-mode . ("clangd")))))
507 | (eglot--sniffing (:server-notifications s-notifs)
508 | (eglot--tests-connect)
509 | (eglot--wait-for (s-notifs 10)
510 | (&key _id method &allow-other-keys)
511 | (string= method "textDocument/publishDiagnostics"))
512 | (flymake-start)
513 | (goto-char (point-min))
514 | (flymake-goto-next-error 1 '() t)
515 | (should (eq 'eglot-diagnostic-tag-unnecessary-face (face-at-point))))))))
516 |
517 | (defun eglot--eldoc-on-demand ()
518 | ;; Trick ElDoc 1.1.0 into accepting on-demand calls.
519 | (eldoc t))
520 |
521 | (defun eglot--tests-force-full-eldoc ()
522 | ;; FIXME: This uses some ElDoc implementation details.
523 | (when (buffer-live-p eldoc--doc-buffer)
524 | (with-current-buffer eldoc--doc-buffer
525 | (let ((inhibit-read-only t))
526 | (erase-buffer))))
527 | (eglot--eldoc-on-demand)
528 | (cl-loop
529 | repeat 10
530 | for retval = (and (buffer-live-p eldoc--doc-buffer)
531 | (with-current-buffer eldoc--doc-buffer
532 | (let ((bs (buffer-string)))
533 | (unless (zerop (length bs)) bs))))
534 | when retval return retval
535 | do (sit-for 0.5)
536 | finally (error "eglot--tests-force-full-eldoc didn't deliver")))
537 |
538 | (ert-deftest eglot-test-rust-analyzer-hover-after-edit ()
539 | "Hover and highlightChanges."
540 | (skip-unless (executable-find "rust-analyzer"))
541 | (skip-unless (executable-find "cargo"))
542 | (eglot--with-fixture
543 | '(("hover-project" .
544 | (("main.rs" .
545 | "fn test() -> i32 { let test=3; return te; }"))))
546 | (with-current-buffer
547 | (eglot--find-file-noselect "hover-project/main.rs")
548 | (should (zerop (shell-command "cargo init")))
549 | (eglot--sniffing (
550 | :server-replies s-replies
551 | :client-requests c-reqs
552 | )
553 | (eglot--tests-connect)
554 | (goto-char (point-min))
555 | (search-forward "return te")
556 | (insert "st")
557 | (progn
558 | ;; simulate these two which don't happen when buffer isn't
559 | ;; visible in a window.
560 | (eglot--signal-textDocument/didChange)
561 | (eglot--eldoc-on-demand))
562 | (let (pending-id)
563 | (eglot--wait-for (c-reqs 2)
564 | (&key id method &allow-other-keys)
565 | (setq pending-id id)
566 | (string= method "textDocument/documentHighlight"))
567 | (eglot--wait-for (s-replies 2)
568 | (&key id &allow-other-keys)
569 | (eq id pending-id)))))))
570 |
571 | (ert-deftest eglot-test-rename-a-symbol ()
572 | "Test basic symbol renaming."
573 | (skip-unless (executable-find "clangd"))
574 | (eglot--with-fixture
575 | `(("rename-project"
576 | . (("main.c" .
577 | "int foo() {return 42;} int main() {return foo();}"))))
578 | (with-current-buffer
579 | (eglot--find-file-noselect "rename-project/main.c")
580 | (eglot--tests-connect)
581 | (goto-char (point-min)) (search-forward "foo")
582 | (eglot-rename "bar")
583 | (should (equal (buffer-string)
584 | "int bar() {return 42;} int main() {return bar();}")))))
585 |
586 | (defun eglot--wait-for-clangd ()
587 | (eglot--sniffing (:server-notifications s-notifs)
588 | (should (eglot--tests-connect))
589 | (eglot--wait-for (s-notifs 20) (&key method &allow-other-keys)
590 | (string= method "textDocument/publishDiagnostics"))))
591 |
592 | (defun eglot--wait-for-rust-analyzer ()
593 | (eglot--sniffing (:server-notifications s-notifs)
594 | (should (eglot--tests-connect))
595 | (eglot--wait-for (s-notifs 20) (&key method params &allow-other-keys)
596 | (and
597 | (string= method "$/progress")
598 | (equal (plist-get params :token) "rustAnalyzer/Roots Scanned")
599 | (equal (plist-get (plist-get params :value) :kind) "end")))
600 | ;; Annoyingly, waiting for that special progress report is still not
601 | ;; enough to make sure the server is ready to provide completions,
602 | ;; so here's two extra seconds.
603 | (sit-for 2)))
604 |
605 | (ert-deftest eglot-test-basic-completions ()
606 | "Test basic autocompletion in a clangd LSP."
607 | (skip-unless (executable-find "clangd"))
608 | (eglot--with-fixture
609 | `(("project" . (("coiso.c" . "#include \nint main () {fprin"))))
610 | (with-current-buffer
611 | (eglot--find-file-noselect "project/coiso.c")
612 | (eglot--wait-for-clangd)
613 | (goto-char (point-max))
614 | (completion-at-point)
615 | (message (buffer-string))
616 | (should (looking-back "fprintf.?")))))
617 |
618 | (ert-deftest eglot-test-non-unique-completions ()
619 | "Test completion resulting in 'Complete, but not unique'."
620 | (skip-unless (executable-find "clangd"))
621 | (eglot--with-fixture
622 | `(("project" . (("coiso.c" .
623 | ,(concat "int foo; int fooey;"
624 | "int main() {foo")))))
625 | (with-current-buffer
626 | (eglot--find-file-noselect "project/coiso.c")
627 | (eglot--wait-for-clangd)
628 | (goto-char (point-max))
629 | (completion-at-point)
630 | ;; FIXME: `current-message' doesn't work here :-(
631 | (with-current-buffer (messages-buffer)
632 | (save-excursion
633 | (goto-char (point-max))
634 | (forward-line -1)
635 | (should (looking-at "Complete, but not unique")))))))
636 |
637 | (ert-deftest eglot-test-stop-completion-on-nonprefix ()
638 | "Test completion also resulting in 'Complete, but not unique'."
639 | (skip-unless (executable-find "clangd"))
640 | (eglot--with-fixture
641 | `(("project" . (("coiso.c" .
642 | ,(concat "int foot; int footer; int fo_obar;"
643 | "int main() {foo")))))
644 | (with-current-buffer
645 | (eglot--find-file-noselect "project/coiso.c")
646 | (eglot--wait-for-clangd)
647 | (goto-char (point-max))
648 | (completion-at-point)
649 | (should (looking-back "foo")))))
650 |
651 | (defun eglot--kill-completions-buffer ()
652 | (when (buffer-live-p (get-buffer "*Completions*"))
653 | (kill-buffer "*Completions*")))
654 |
655 | (ert-deftest eglot-test-try-completion-nomatch ()
656 | "Test completion table with non-matching input, returning nil."
657 | (skip-unless (executable-find "clangd"))
658 | (eglot--with-fixture
659 | `(("project" . (("coiso.c" .
660 | ,(concat "int main() {abc")))))
661 | (with-current-buffer
662 | (eglot--find-file-noselect "project/coiso.c")
663 | (eglot--wait-for-clangd)
664 | (eglot--kill-completions-buffer)
665 | (goto-char (point-max))
666 | (completion-at-point)
667 | (should (looking-back "abc"))
668 | (should-not (get-buffer "*Completions*")))))
669 |
670 | (ert-deftest eglot-test-try-completion-inside-symbol ()
671 | "Test completion table inside symbol, with only prefix matching."
672 | (skip-unless (executable-find "clangd"))
673 | (eglot--with-fixture
674 | `(("project" . (("coiso.c" .
675 | ,(concat
676 | "int foobar;"
677 | "int foobarbaz;"
678 | "int main() {foo123")))))
679 | (with-current-buffer
680 | (eglot--find-file-noselect "project/coiso.c")
681 | (eglot--wait-for-clangd)
682 | (goto-char (- (point-max) 3))
683 | (eglot--kill-completions-buffer)
684 | (completion-at-point)
685 | (should (looking-back "foo"))
686 | (should (looking-at "123"))
687 | (should (get-buffer "*Completions*")))))
688 |
689 | (ert-deftest eglot-test-try-completion-inside-symbol-2 ()
690 | "Test completion table inside symbol, with only prefix matching."
691 | (skip-unless (executable-find "clangd"))
692 | (eglot--with-fixture
693 | `(("project" . (("coiso.c" .
694 | ,(concat
695 | "int foobar;"
696 | "int main() {foo123")))))
697 | (with-current-buffer
698 | (eglot--find-file-noselect "project/coiso.c")
699 | (eglot--wait-for-clangd)
700 | (goto-char (- (point-max) 3))
701 | (completion-at-point)
702 | (should (looking-back "foobar"))
703 | (should (looking-at "123")))))
704 |
705 | (ert-deftest eglot-test-rust-completion-exit-function ()
706 | "Ensure rust-analyzer exit function creates the expected contents."
707 | :tags '(:expensive-test)
708 | ;; This originally appeared in github#1339
709 | (skip-unless (executable-find "rust-analyzer"))
710 | (skip-unless (executable-find "cargo"))
711 | (eglot--with-fixture
712 | '(("cmpl-project" .
713 | (("main.rs" .
714 | "fn test() -> i32 { let v: usize = 1; v.count_on1234.1234567890;"))))
715 | (with-current-buffer
716 | (eglot--find-file-noselect "cmpl-project/main.rs")
717 | (should (zerop (shell-command "cargo init")))
718 | (search-forward "v.count_on")
719 | (eglot--wait-for-rust-analyzer)
720 | (completion-at-point)
721 | (should
722 | (equal
723 | (if (bound-and-true-p yas-minor-mode)
724 | "fn test() -> i32 { let v: usize = 1; v.count_ones().1234567890;"
725 | "fn test() -> i32 { let v: usize = 1; v.count_ones.1234567890;")
726 | (buffer-string))))))
727 |
728 | (ert-deftest eglot-test-zig-insert-replace-completion ()
729 | "Test zls's use of 'InsertReplaceEdit'."
730 | (skip-unless (functionp 'zig-ts-mode))
731 | (eglot--with-fixture
732 | `(("project" .
733 | (("main.zig" .
734 | ,(concat "const Foo = struct {correct_name: u32,\n};\n"
735 | "fn example(foo: Foo) u32 {return foo.correc_name; }")))))
736 | (with-current-buffer
737 | (eglot--find-file-noselect "project/main.zig")
738 | (should (eglot--tests-connect))
739 | (search-forward "foo.correc")
740 | (completion-at-point)
741 | (should (looking-back "correct_name")))))
742 |
743 | (ert-deftest eglot-test-basic-xref ()
744 | "Test basic xref functionality in a clangd LSP."
745 | (skip-unless (executable-find "clangd"))
746 | (eglot--with-fixture
747 | `(("project" . (("coiso.c" .
748 | ,(concat "int foo=42; int fooey;"
749 | "int main() {foo=82;}")))))
750 | (with-current-buffer
751 | (eglot--find-file-noselect "project/coiso.c")
752 | (should (eglot--tests-connect))
753 | (search-forward "{foo")
754 | (call-interactively 'xref-find-definitions)
755 | (should (looking-at "foo=42")))))
756 |
757 | (defvar eglot--test-c-buffer
758 | "\
759 | void foobarquux(int a, int b, int c){};
760 | void foobazquuz(int a, int b, int f){};
761 | int main() {
762 | ")
763 |
764 | (declare-function yas-minor-mode nil)
765 |
766 | (ert-deftest eglot-test-snippet-completions ()
767 | "Test simple snippet completion in a clangd LSP."
768 | (skip-unless (and (executable-find "clangd")
769 | (functionp 'yas-minor-mode)))
770 | (eglot--with-fixture
771 | `(("project" . (("coiso.c" . ,eglot--test-c-buffer))))
772 | (with-current-buffer
773 | (eglot--find-file-noselect "project/coiso.c")
774 | (yas-minor-mode 1)
775 | (eglot--wait-for-clangd)
776 | (goto-char (point-max))
777 | (insert "foobar")
778 | (completion-at-point)
779 | (should (looking-back "foobarquux("))
780 | (should (looking-at "int a, int b, int c)")))))
781 |
782 | (defvar company-candidates)
783 | (declare-function company-mode nil)
784 | (declare-function company-complete nil)
785 |
786 | (ert-deftest eglot-test-snippet-completions-with-company ()
787 | "Test simple snippet completion in a clangd LSP."
788 | (skip-unless (and (executable-find "clangd")
789 | (functionp 'yas-minor-mode)
790 | (functionp 'company-complete)))
791 | (eglot--with-fixture
792 | `(("project" . (("coiso.c" . ,eglot--test-c-buffer))))
793 | (with-current-buffer
794 | (eglot--find-file-noselect "project/coiso.c")
795 | (yas-minor-mode 1)
796 | (eglot--wait-for-clangd)
797 | (goto-char (point-max))
798 | (insert "foo")
799 | (company-mode)
800 | (company-complete)
801 | (should (= 2 (length company-candidates)))
802 | ;; this last one is brittle, since there it is possible that
803 | ;; clangd will change the representation of this candidate
804 | (should (member "foobazquuz(int a, int b, int f)" company-candidates)))))
805 |
806 | (ert-deftest eglot-test-eldoc-after-completions ()
807 | "Test documentation echo in a clangd LSP."
808 | (skip-unless (executable-find "clangd"))
809 | (eglot--with-fixture
810 | `(("project" . (("coiso.c" . "#include \nint main () {fprin"))))
811 | (with-current-buffer
812 | (eglot--find-file-noselect "project/coiso.c")
813 | (eglot--wait-for-clangd)
814 | (goto-char (point-max))
815 | (completion-at-point)
816 | (message (buffer-string))
817 | (should (looking-back "fprintf(?"))
818 | (unless (= (char-before) ?\() (insert "()") (backward-char))
819 | (eglot--signal-textDocument/didChange)
820 | (should (string-match "^fprintf" (eglot--tests-force-full-eldoc))))))
821 |
822 | (ert-deftest eglot-test-multiline-eldoc ()
823 | "Test ElDoc documentation from multiple osurces."
824 | (skip-unless (executable-find "clangd"))
825 | (eglot--with-fixture
826 | `(("project" . (("coiso.c" .
827 | "#include \nint main () {fprintf(blergh);}"))))
828 | (with-current-buffer
829 | (eglot--find-file-noselect "project/coiso.c")
830 | (search-forward "fprintf(ble")
831 | (eglot--wait-for-clangd)
832 | (flymake-start nil t) ;; thing brings in the "unknown identifier blergh"
833 | (let* ((captured-message (eglot--tests-force-full-eldoc)))
834 | ;; check for signature and error message in the result
835 | (should (string-match "fprintf" captured-message))
836 | (should (string-match "blergh" captured-message))
837 | (should (cl-find ?\n captured-message))))))
838 |
839 | (ert-deftest eglot-test-formatting ()
840 | "Test formatting in the clangd server."
841 | ;; Beware, default autopep rules can change over time, which may
842 | ;; affect this test.
843 | (skip-unless (executable-find "clangd"))
844 | (eglot--with-fixture
845 | `(("project" . (("coiso.c" . ,(concat "#include \n"
846 | "int main(){fprintf(blergh);}"
847 | "int ble{\n\nreturn 0;}")))))
848 | (with-current-buffer
849 | (eglot--find-file-noselect "project/coiso.c")
850 | (eglot--wait-for-clangd)
851 | (forward-line)
852 | ;; Try to format just the second line
853 | (eglot-format (line-beginning-position) (line-end-position))
854 | (should (looking-at "int main() { fprintf(blergh); }"))
855 | ;; ;; now format the whole buffer
856 | (eglot-format-buffer)
857 | (should
858 | (string= (buffer-string)
859 | "#include \nint main() { fprintf(blergh); }\nint ble { return 0; }")))))
860 |
861 | (ert-deftest eglot-test-rust-on-type-formatting ()
862 | "Test textDocument/onTypeFormatting against rust-analyzer."
863 | (skip-unless (executable-find "rust-analyzer"))
864 | (skip-unless (executable-find "cargo"))
865 | (eglot--with-fixture
866 | '(("on-type-formatting-project" .
867 | (("main.rs" .
868 | "fn main() -> () {\n foo\n .bar()\n "))))
869 | (with-current-buffer
870 | (eglot--find-file-noselect "on-type-formatting-project/main.rs")
871 | (let ((eglot-server-programs '((rust-mode . ("rust-analyzer")))))
872 | (should (zerop (shell-command "cargo init")))
873 | (eglot--sniffing (:server-notifications s-notifs)
874 | (should (eglot--tests-connect))
875 | (eglot--wait-for (s-notifs 20) (&key method &allow-other-keys)
876 | (string= method "textDocument/publishDiagnostics")))
877 | (goto-char (point-max))
878 | (eglot--simulate-key-event ?.)
879 | (should (looking-back "^ \\."))))))
880 |
881 | (ert-deftest eglot-test-javascript-basic ()
882 | "Test basic autocompletion in a JavaScript LSP."
883 | :tags '(:expensive-test)
884 | (skip-unless (and (executable-find "typescript-language-server")
885 | (executable-find "tsserver")))
886 | (eglot--with-fixture
887 | '(("project" . (("hello.js" . "console.log('Hello world!');"))))
888 | (with-current-buffer
889 | (eglot--find-file-noselect "project/hello.js")
890 | (let ((eglot-server-programs
891 | '((js-mode . ("typescript-language-server" "--stdio")))))
892 | (goto-char (point-max))
893 | (eglot--sniffing (:server-notifications
894 | s-notifs
895 | :client-notifications
896 | c-notifs)
897 | (should (eglot--tests-connect))
898 | (eglot--wait-for (s-notifs 10) (&key method &allow-other-keys)
899 | (string= method "textDocument/publishDiagnostics"))
900 | (should (not (eq 'flymake-error (face-at-point))))
901 | (insert "{")
902 | (eglot--signal-textDocument/didChange)
903 | (eglot--wait-for (c-notifs 1) (&key method &allow-other-keys)
904 | (string= method "textDocument/didChange"))
905 | (eglot--wait-for (s-notifs 10) (&key params method &allow-other-keys)
906 | (and (string= method "textDocument/publishDiagnostics")
907 | (cl-destructuring-bind (&key _uri diagnostics) params
908 | (cl-find-if (jsonrpc-lambda (&key severity &allow-other-keys)
909 | (= severity 1))
910 | diagnostics)))))))))
911 |
912 | (ert-deftest eglot-test-project-wide-diagnostics-typescript ()
913 | "Test diagnostics through multiple files in a TypeScript LSP."
914 | (skip-unless (and (executable-find "typescript-language-server")
915 | (executable-find "tsserver")))
916 | (eglot--with-fixture
917 | '(("project" . (("hello.ts" . "const thing = 5;\nexport { thin }")
918 | ("hello2.ts" . "import { thing } from './hello'"))))
919 | (eglot--make-file-or-dir '(".git"))
920 | (let ((eglot-server-programs
921 | '((typescript-mode . ("typescript-language-server" "--stdio")))))
922 | ;; Check both files because typescript-language-server doesn't
923 | ;; report all errors on startup, at least not with such a simple
924 | ;; setup.
925 | (with-current-buffer (eglot--find-file-noselect "project/hello2.ts")
926 | (eglot--sniffing (:server-notifications s-notifs)
927 | (eglot--tests-connect)
928 | (flymake-start)
929 | (eglot--wait-for (s-notifs 10)
930 | (&key _id method &allow-other-keys)
931 | (string= method "textDocument/publishDiagnostics"))
932 | (should (= 2 (length (flymake--project-diagnostics)))))
933 | (with-current-buffer (eglot--find-file-noselect "hello.ts")
934 | (eglot--sniffing (:server-notifications s-notifs)
935 | (flymake-start)
936 | (eglot--wait-for (s-notifs 10)
937 | (&key _id method &allow-other-keys)
938 | (string= method "textDocument/publishDiagnostics"))
939 | (should (= 4 (length (flymake--project-diagnostics))))))))))
940 |
941 | (ert-deftest eglot-test-project-wide-diagnostics-rust-analyzer ()
942 | "Test diagnostics through multiple files in rust-analyzer."
943 | (skip-unless (executable-find "rust-analyzer"))
944 | (skip-unless (executable-find "cargo"))
945 | (skip-unless (executable-find "git"))
946 | (eglot--with-fixture
947 | '(("project" .
948 | (("main.rs" .
949 | "fn main() -> i32 { return 42.2;}")
950 | ("other-file.rs" .
951 | "fn foo() -> () { let hi=3; }"))))
952 | (let ((eglot-server-programs '((rust-mode . ("rust-analyzer")))))
953 | ;; Open other-file.rs, and see diagnostics arrive for main.rs,
954 | ;; which we didn't open.
955 | (with-current-buffer (eglot--find-file-noselect "project/other-file.rs")
956 | (should (zerop (shell-command "git init")))
957 | (should (zerop (shell-command "cargo init")))
958 | (eglot--sniffing (:server-notifications s-notifs)
959 | (eglot--tests-connect)
960 | (flymake-start)
961 | (eglot--wait-for (s-notifs 20)
962 | (&key _id method params &allow-other-keys)
963 | (and (string= method "textDocument/publishDiagnostics")
964 | (string-suffix-p "main.rs" (plist-get params :uri))))
965 | (let* ((diags (flymake--project-diagnostics)))
966 | (should (cl-some (lambda (diag)
967 | (let ((locus (flymake-diagnostic-buffer diag)))
968 | (and (stringp (flymake-diagnostic-buffer diag))
969 | (string-suffix-p "main.rs" locus))))
970 | diags))))))))
971 |
972 | (ert-deftest eglot-test-json-basic ()
973 | "Test basic autocompletion in vscode-json-languageserver."
974 | (skip-unless (executable-find "vscode-json-languageserver"))
975 | (skip-unless (fboundp 'yas-minor-mode))
976 | (eglot--with-fixture
977 | '(("project" .
978 | (("p.json" . "{\"foo.b")
979 | ("s.json" . "{\"properties\":{\"foo.bar\":{\"default\":\"fb\"}}}")
980 | (".git" . nil))))
981 | (with-current-buffer
982 | (eglot--find-file-noselect "project/p.json")
983 | (yas-minor-mode)
984 | (goto-char 2)
985 | (insert "\"$schema\": \"file://"
986 | (file-name-directory buffer-file-name) "s.json\",")
987 | (let ((eglot-server-programs
988 | '((js-mode . ("vscode-json-languageserver" "--stdio")))))
989 | (goto-char (point-max))
990 | (should (eglot--tests-connect))
991 | (completion-at-point)
992 | (should (looking-back "\"foo.bar\": \""))
993 | (should (looking-at "fb\"$"))))))
994 |
995 | (defun eglot-tests--get (object path)
996 | (dolist (op path)
997 | (setq object (if (natnump op) (aref object op)
998 | (plist-get object op))))
999 | object)
1000 |
1001 | (defun eglot-tests--lsp-abiding-column-1 ()
1002 | (eglot--with-fixture
1003 | '(("project" .
1004 | (("foo.c" . "const char write_data[] = u8\"🚂🚃🚄🚅🚆🚈🚇🚈🚉🚊🚋🚌🚎🚝🚞🚟🚠🚡🛤🛲\";"))))
1005 | (let ((eglot-server-programs
1006 | '((c-mode . ("clangd")))))
1007 | (with-current-buffer
1008 | (eglot--find-file-noselect "project/foo.c")
1009 | (setq-local eglot-move-to-linepos-function #'eglot-move-to-utf-16-linepos)
1010 | (setq-local eglot-current-linepos-function #'eglot-utf-16-linepos)
1011 | (eglot--sniffing (:client-notifications c-notifs)
1012 | (eglot--tests-connect)
1013 | (end-of-line)
1014 | (insert "p ")
1015 | (eglot--signal-textDocument/didChange)
1016 | (eglot--wait-for (c-notifs 2) (&key params &allow-other-keys)
1017 | (message "PARAMS=%S" params)
1018 | (should (equal 71 (eglot-tests--get
1019 | params
1020 | '(:contentChanges 0
1021 | :range :start :character)))))
1022 | (beginning-of-line)
1023 | (should (eq eglot-move-to-linepos-function #'eglot-move-to-utf-16-linepos))
1024 | (funcall eglot-move-to-linepos-function 71)
1025 | (should (looking-at "p")))))))
1026 |
1027 | (ert-deftest eglot-test-lsp-abiding-column ()
1028 | "Test basic LSP character counting logic."
1029 | (skip-unless (executable-find "clangd"))
1030 | (eglot-tests--lsp-abiding-column-1))
1031 |
1032 | (ert-deftest eglot-test-ensure ()
1033 | "Test basic `eglot-ensure' functionality."
1034 | (skip-unless (executable-find "clangd"))
1035 | (eglot--with-fixture
1036 | `(("project" . (("foo.c" . "int foo() {return 42;}")
1037 | ("bar.c" . "int bar() {return 42;}"))))
1038 | (let ((c-mode-hook '(eglot-ensure))
1039 | server)
1040 | ;; need `ert-simulate-command' because `eglot-ensure'
1041 | ;; relies on `post-command-hook'.
1042 | (with-current-buffer
1043 | (ert-simulate-command
1044 | '(find-file "project/foo.c"))
1045 | ;; FIXME: This test fails without this sleep on my machine.
1046 | ;; Figure out why and solve this more cleanly.
1047 | (sleep-for 0.1)
1048 | (should (setq server (eglot-current-server))))
1049 | (with-current-buffer
1050 | (ert-simulate-command
1051 | '(find-file "project/bar.c"))
1052 | (should (eq server (eglot-current-server)))))))
1053 |
1054 | (ert-deftest eglot-test-slow-sync-connection-wait ()
1055 | "Connect with `eglot-sync-connect' set to t."
1056 | (skip-unless (executable-find "clangd"))
1057 | (eglot--with-fixture
1058 | `(("project" . (("something.c" . "int foo() {return 42;}"))))
1059 | (with-current-buffer
1060 | (eglot--find-file-noselect "project/something.c")
1061 | (let ((eglot-sync-connect t)
1062 | (eglot-server-programs
1063 | `((c-mode . ("sh" "-c" "sleep 1 && clangd")))))
1064 | (should (eglot--tests-connect 3))))))
1065 |
1066 | (ert-deftest eglot-test-slow-sync-connection-intime ()
1067 | "Connect synchronously with `eglot-sync-connect' set to 2."
1068 | (skip-unless (executable-find "clangd"))
1069 | (eglot--with-fixture
1070 | `(("project" . (("something.c" . "int foo() {return 42;}"))))
1071 | (with-current-buffer
1072 | (eglot--find-file-noselect "project/something.c")
1073 | (let ((eglot-sync-connect 2)
1074 | (eglot-server-programs
1075 | `((c-mode . ("sh" "-c" "sleep 1 && clangd")))))
1076 | (should (eglot--tests-connect 3))))))
1077 |
1078 | (ert-deftest eglot-test-slow-async-connection ()
1079 | "Connect asynchronously with `eglot-sync-connect' set to 2."
1080 | (skip-unless (executable-find "clangd"))
1081 | (eglot--with-fixture
1082 | `(("project" . (("something.c" . "int foo() {return 42;}"))))
1083 | (with-current-buffer
1084 | (eglot--find-file-noselect "project/something.c")
1085 | (let ((eglot-sync-connect 1)
1086 | (eglot-server-programs
1087 | `((c-mode . ("sh" "-c" "sleep 2 && clangd")))))
1088 | (should-not (apply #'eglot--connect (eglot--guess-contact)))
1089 | (eglot--with-timeout 3
1090 | (while (not (eglot-current-server))
1091 | (accept-process-output nil 0.2))
1092 | (should (eglot-current-server)))))))
1093 |
1094 | (ert-deftest eglot-test-slow-sync-timeout ()
1095 | "Failed attempt at connection synchronously."
1096 | (skip-unless (executable-find "clangd"))
1097 | (eglot--with-fixture
1098 | `(("project" . (("something.c" . "int foo() {return 42;}"))))
1099 | (with-current-buffer
1100 | (eglot--find-file-noselect "project/something.c")
1101 | (let ((eglot-sync-connect t)
1102 | (eglot-connect-timeout 1)
1103 | (eglot-server-programs
1104 | `((c-mode . ("sh" "-c" "sleep 2 && clangd")))))
1105 | (should-error (apply #'eglot--connect (eglot--guess-contact)))))))
1106 |
1107 | (ert-deftest eglot-test-capabilities ()
1108 | "Unit test for `eglot-server-capable'."
1109 | (cl-letf (((symbol-function 'eglot--capabilities)
1110 | (lambda (_dummy)
1111 | ;; test data lifted from Golangserver example at
1112 | ;; https://github.com/joaotavora/eglot/pull/74
1113 | (list :textDocumentSync 2 :hoverProvider t
1114 | :completionProvider '(:triggerCharacters ["."])
1115 | :signatureHelpProvider '(:triggerCharacters ["(" ","])
1116 | :definitionProvider t :typeDefinitionProvider t
1117 | :referencesProvider t :documentSymbolProvider t
1118 | :workspaceSymbolProvider t :implementationProvider t
1119 | :documentFormattingProvider t :xworkspaceReferencesProvider t
1120 | :xdefinitionProvider t :xworkspaceSymbolByProperties t)))
1121 | ((symbol-function 'eglot--current-server-or-lose)
1122 | (lambda () nil)))
1123 | (should (eql 2 (eglot-server-capable :textDocumentSync)))
1124 | (should (eglot-server-capable :completionProvider :triggerCharacters))
1125 | (should (equal '(:triggerCharacters ["."]) (eglot-server-capable :completionProvider)))
1126 | (should-not (eglot-server-capable :foobarbaz))
1127 | (should-not (eglot-server-capable :textDocumentSync :foobarbaz))))
1128 |
1129 | (defmacro eglot--without-interface-warnings (&rest body)
1130 | (let ((eglot-strict-mode nil))
1131 | (macroexpand-all (macroexp-progn body) macroexpand-all-environment)))
1132 |
1133 | (ert-deftest eglot-test-strict-interfaces ()
1134 | (let ((eglot--lsp-interface-alist
1135 | `((FooObject . ((:foo :bar) (:baz))))))
1136 | (eglot--without-interface-warnings
1137 | (should
1138 | (equal '("foo" . "bar")
1139 | (let ((eglot-strict-mode nil))
1140 | (eglot--dbind (foo bar) `(:foo "foo" :bar "bar")
1141 | (cons foo bar)))))
1142 | (should-error
1143 | (let ((eglot-strict-mode '(disallow-non-standard-keys)))
1144 | (eglot--dbind (foo bar) `(:foo "foo" :bar "bar" :fotrix bargh)
1145 | (cons foo bar))))
1146 | (should
1147 | (equal '("foo" . "bar")
1148 | (let ((eglot-strict-mode nil))
1149 | (eglot--dbind (foo bar) `(:foo "foo" :bar "bar" :fotrix bargh)
1150 | (cons foo bar)))))
1151 | (should-error
1152 | (let ((eglot-strict-mode '(disallow-non-standard-keys)))
1153 | (eglot--dbind ((FooObject) foo bar) `(:foo "foo" :bar "bar" :fotrix bargh)
1154 | (cons foo bar))))
1155 | (should
1156 | (equal '("foo" . "bar")
1157 | (let ((eglot-strict-mode '(disallow-non-standard-keys)))
1158 | (eglot--dbind ((FooObject) foo bar) `(:foo "foo" :bar "bar" :baz bargh)
1159 | (cons foo bar)))))
1160 | (should
1161 | (equal '("foo" . nil)
1162 | (let ((eglot-strict-mode nil))
1163 | (eglot--dbind ((FooObject) foo bar) `(:foo "foo" :baz bargh)
1164 | (cons foo bar)))))
1165 | (should
1166 | (equal '("foo" . "bar")
1167 | (let ((eglot-strict-mode '(enforce-required-keys)))
1168 | (eglot--dbind ((FooObject) foo bar) `(:foo "foo" :bar "bar" :baz bargh)
1169 | (cons foo bar)))))
1170 | (should-error
1171 | (let ((eglot-strict-mode '(enforce-required-keys)))
1172 | (eglot--dbind ((FooObject) foo bar) `(:foo "foo" :baz bargh)
1173 | (cons foo bar)))))))
1174 |
1175 | (ert-deftest eglot-test-dcase ()
1176 | (eglot--without-interface-warnings
1177 | (let ((eglot--lsp-interface-alist
1178 | `((FooObject . ((:foo :bar) (:baz)))
1179 | (CodeAction (:title) (:kind :diagnostics :edit :command))
1180 | (Command ((:title . string) (:command . string)) (:arguments)))))
1181 | (should
1182 | (equal
1183 | "foo"
1184 | (eglot--dcase `(:foo "foo" :bar "bar")
1185 | (((FooObject) foo)
1186 | foo))))
1187 | (should
1188 | (equal
1189 | (list "foo" '(:title "hey" :command "ho") "some edit")
1190 | (eglot--dcase '(:title "foo"
1191 | :command (:title "hey" :command "ho")
1192 | :edit "some edit")
1193 | (((Command) _title _command _arguments)
1194 | (ert-fail "Shouldn't have destructured this object as a Command"))
1195 | (((CodeAction) title edit command)
1196 | (list title command edit)))))
1197 | (should
1198 | (equal
1199 | (list "foo" "some command" nil)
1200 | (eglot--dcase '(:title "foo" :command "some command")
1201 | (((Command) title command arguments)
1202 | (list title command arguments))
1203 | (((CodeAction) _title _edit _command)
1204 | (ert-fail "Shouldn't have destructured this object as a CodeAction"))))))))
1205 |
1206 | (ert-deftest eglot-test-dcase-issue-452 ()
1207 | (let ((eglot--lsp-interface-alist
1208 | `((FooObject . ((:foo :bar) (:baz)))
1209 | (CodeAction (:title) (:kind :diagnostics :edit :command))
1210 | (Command ((string . :title) (:command . string)) (:arguments)))))
1211 | (should
1212 | (equal
1213 | (list "foo" '(:command "cmd" :title "alsofoo"))
1214 | (eglot--dcase '(:title "foo" :command (:command "cmd" :title "alsofoo"))
1215 | (((Command) _title _command _arguments)
1216 | (ert-fail "Shouldn't have destructured this object as a Command"))
1217 | (((CodeAction) title command)
1218 | (list title command)))))))
1219 |
1220 | (cl-defmacro eglot--guessing-contact ((interactive-sym
1221 | prompt-args-sym
1222 | guessed-class-sym guessed-contact-sym
1223 | &optional guessed-major-modes-sym
1224 | guessed-lang-ids-sym)
1225 | &body body)
1226 | "Guess LSP contact with `eglot--guessing-contact', evaluate BODY.
1227 |
1228 | BODY is evaluated twice, with INTERACTIVE bound to the boolean passed to
1229 | `eglot--guess-contact' each time.
1230 |
1231 | If the user would have been prompted, PROMPT-ARGS-SYM is bound to
1232 | the list of arguments that would have been passed to
1233 | `read-shell-command', else nil. GUESSED-CLASS-SYM,
1234 | GUESSED-CONTACT-SYM, GUESSED-LANG-IDS-SYM and
1235 | GUESSED-MAJOR-MODES-SYM are bound to the useful return values of
1236 | `eglot--guess-contact'. Unless the server program evaluates to
1237 | \"a-missing-executable.exe\", this macro will assume it exists."
1238 | (declare (indent 1) (debug t))
1239 | (let ((i-sym (gensym)))
1240 | `(dolist (,i-sym '(nil t))
1241 | (let ((,interactive-sym ,i-sym)
1242 | (buffer-file-name "_")
1243 | ,@(when prompt-args-sym `((,prompt-args-sym nil))))
1244 | (cl-letf (((symbol-function 'executable-find)
1245 | (lambda (name &optional _remote)
1246 | (unless (string-equal name "a-missing-executable.exe")
1247 | (format "/totally-mock-bin/%s" name))))
1248 | ((symbol-function 'read-shell-command)
1249 | ,(if prompt-args-sym
1250 | `(lambda (&rest args) (setq ,prompt-args-sym args) "")
1251 | `(lambda (&rest _dummy) ""))))
1252 | (cl-destructuring-bind
1253 | (,(or guessed-major-modes-sym '_)
1254 | _ ,guessed-class-sym ,guessed-contact-sym
1255 | ,(or guessed-lang-ids-sym '_))
1256 | (eglot--guess-contact ,i-sym)
1257 | ,@body))))))
1258 |
1259 | (ert-deftest eglot-test-server-programs-simple-executable ()
1260 | (let ((eglot-server-programs '((foo-mode "some-executable")))
1261 | (major-mode 'foo-mode))
1262 | (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
1263 | (should (not prompt-args))
1264 | (should (equal guessed-class 'eglot-lsp-server))
1265 | (should (equal guessed-contact '("some-executable"))))))
1266 |
1267 | (ert-deftest eglot-test-server-programs-simple-missing-executable ()
1268 | (let ((eglot-server-programs '((foo-mode "a-missing-executable.exe")))
1269 | (major-mode 'foo-mode))
1270 | (eglot--guessing-contact (interactive-p prompt-args guessed-class guessed-contact)
1271 | (should (equal (not prompt-args) (not interactive-p)))
1272 | (should (equal guessed-class 'eglot-lsp-server))
1273 | (should (or prompt-args
1274 | (equal guessed-contact '("a-missing-executable.exe")))))))
1275 |
1276 | (ert-deftest eglot-test-server-programs-executable-multiple-major-modes ()
1277 | (let ((eglot-server-programs '(((bar-mode foo-mode) "some-executable")))
1278 | (major-mode 'foo-mode))
1279 | (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
1280 | (should (not prompt-args))
1281 | (should (equal guessed-class 'eglot-lsp-server))
1282 | (should (equal guessed-contact '("some-executable"))))))
1283 |
1284 | (ert-deftest eglot-test-server-programs-executable-with-arg ()
1285 | (let ((eglot-server-programs '((foo-mode "some-executable" "arg1")))
1286 | (major-mode 'foo-mode))
1287 | (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
1288 | (should (not prompt-args))
1289 | (should (equal guessed-class 'eglot-lsp-server))
1290 | (should (equal guessed-contact '("some-executable" "arg1"))))))
1291 |
1292 | (ert-deftest eglot-test-server-programs-executable-with-args-and-autoport ()
1293 | (let ((eglot-server-programs '((foo-mode "some-executable" "arg1"
1294 | :autoport "arg2")))
1295 | (major-mode 'foo-mode))
1296 | (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
1297 | (should (not prompt-args))
1298 | (should (equal guessed-class 'eglot-lsp-server))
1299 | (should (equal guessed-contact '("some-executable" "arg1"
1300 | :autoport "arg2"))))))
1301 |
1302 | (ert-deftest eglot-test-server-programs-host-and-port ()
1303 | (let ((eglot-server-programs '((foo-mode "somehost.example.com" 7777)))
1304 | (major-mode 'foo-mode))
1305 | (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
1306 | (should (not prompt-args))
1307 | (should (equal guessed-class 'eglot-lsp-server))
1308 | (should (equal guessed-contact '("somehost.example.com" 7777))))))
1309 |
1310 | (ert-deftest eglot-test-server-programs-host-and-port-and-tcp-args ()
1311 | (let ((eglot-server-programs '((foo-mode "somehost.example.com" 7777
1312 | :type network)))
1313 | (major-mode 'foo-mode))
1314 | (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
1315 | (should (not prompt-args))
1316 | (should (equal guessed-class 'eglot-lsp-server))
1317 | (should (equal guessed-contact '("somehost.example.com" 7777
1318 | :type network))))))
1319 |
1320 | (ert-deftest eglot-test-server-programs-class-name-and-plist ()
1321 | (let ((eglot-server-programs '((foo-mode bar-class :init-key init-val)))
1322 | (major-mode 'foo-mode))
1323 | (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
1324 | (should (not prompt-args))
1325 | (should (equal guessed-class 'bar-class))
1326 | (should (equal guessed-contact '(:init-key init-val))))))
1327 |
1328 | (ert-deftest eglot-test-server-programs-class-name-and-contact-spec ()
1329 | (let ((eglot-server-programs '((foo-mode bar-class "some-executable" "arg1"
1330 | :autoport "arg2")))
1331 | (major-mode 'foo-mode))
1332 | (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
1333 | (should (not prompt-args))
1334 | (should (equal guessed-class 'bar-class))
1335 | (should (equal guessed-contact '("some-executable" "arg1"
1336 | :autoport "arg2"))))))
1337 |
1338 | (ert-deftest eglot-test-server-programs-function ()
1339 | (let ((eglot-server-programs '((foo-mode . (lambda (&optional _)
1340 | '("some-executable")))))
1341 | (major-mode 'foo-mode))
1342 | (eglot--guessing-contact (_ prompt-args guessed-class guessed-contact)
1343 | (should (not prompt-args))
1344 | (should (equal guessed-class 'eglot-lsp-server))
1345 | (should (equal guessed-contact '("some-executable"))))))
1346 |
1347 | (ert-deftest eglot-test-server-programs-guess-lang ()
1348 | (let ((major-mode 'foo-mode))
1349 | (let ((eglot-server-programs '((foo-mode . ("prog-executable")))))
1350 | (eglot--guessing-contact (_ nil _ _ _ guessed-langs)
1351 | (should (equal guessed-langs '("foo")))))
1352 | (let ((eglot-server-programs '(((foo-mode :language-id "bar")
1353 | . ("prog-executable")))))
1354 | (eglot--guessing-contact (_ nil _ _ _ guessed-langs)
1355 | (should (equal guessed-langs '("bar")))))
1356 | (let ((eglot-server-programs '(((baz-mode (foo-mode :language-id "bar"))
1357 | . ("prog-executable")))))
1358 | (eglot--guessing-contact (_ nil _ _ modes guessed-langs)
1359 | (should (equal guessed-langs '("baz" "bar")))
1360 | (should (equal modes '(baz-mode foo-mode)))))))
1361 |
1362 | (defun eglot--glob-match (glob str)
1363 | (funcall (eglot--glob-compile glob t t) str))
1364 |
1365 | (ert-deftest eglot-test-glob-test ()
1366 | (should (eglot--glob-match "foo/**/baz" "foo/bar/baz"))
1367 | (should (eglot--glob-match "foo/**/baz" "foo/baz"))
1368 | (should-not (eglot--glob-match "foo/**/baz" "foo/bar"))
1369 | (should (eglot--glob-match "foo/**/baz/**/quuz" "foo/baz/foo/quuz"))
1370 | (should (eglot--glob-match "foo/**/baz/**/quuz" "foo/foo/foo/baz/foo/quuz"))
1371 | (should-not (eglot--glob-match "foo/**/baz/**/quuz" "foo/foo/foo/ding/foo/quuz"))
1372 | (should (eglot--glob-match "*.js" "foo.js"))
1373 | (should-not (eglot--glob-match "*.js" "foo.jsx"))
1374 | (should (eglot--glob-match "foo/**/*.js" "foo/bar/baz/foo.js"))
1375 | (should-not (eglot--glob-match "foo/**/*.js" "foo/bar/baz/foo.jsx"))
1376 | (should (eglot--glob-match "*.{js,ts}" "foo.js"))
1377 | (should-not (eglot--glob-match "*.{js,ts}" "foo.xs"))
1378 | (should (eglot--glob-match "foo/**/*.{js,ts}" "foo/bar/baz/foo.ts"))
1379 | (should (eglot--glob-match "foo/**/*.{js,ts}x" "foo/bar/baz/foo.tsx"))
1380 | (should (eglot--glob-match "?oo.js" "foo.js"))
1381 | (should (eglot--glob-match "foo/**/*.{js,ts}?" "foo/bar/baz/foo.tsz"))
1382 | (should (eglot--glob-match "foo/**/*.{js,ts}?" "foo/bar/baz/foo.tsz"))
1383 | (should (eglot--glob-match "example.[!0-9]" "example.a"))
1384 | (should-not (eglot--glob-match "example.[!0-9]" "example.0"))
1385 | (should (eglot--glob-match "example.[0-9]" "example.0"))
1386 | (should-not (eglot--glob-match "example.[0-9]" "example.a"))
1387 | (should (eglot--glob-match "**/bar/" "foo/bar/"))
1388 | (should-not (eglot--glob-match "foo.hs" "fooxhs"))
1389 |
1390 | ;; Some more tests
1391 | (should (eglot--glob-match "**/.*" ".git"))
1392 | (should (eglot--glob-match ".?" ".o"))
1393 | (should (eglot--glob-match "**/.*" ".hidden.txt"))
1394 | (should (eglot--glob-match "**/.*" "path/.git"))
1395 | (should (eglot--glob-match "**/.*" "path/.hidden.txt"))
1396 | (should (eglot--glob-match "**/node_modules/**" "node_modules/"))
1397 | (should (eglot--glob-match "{foo,bar}/**" "foo/test"))
1398 | (should (eglot--glob-match "{foo,bar}/**" "bar/test"))
1399 | (should (eglot--glob-match "some/**/*" "some/foo.js"))
1400 | (should (eglot--glob-match "some/**/*" "some/folder/foo.js"))
1401 |
1402 | ;; VSCode supposedly supports this, not sure if good idea.
1403 | ;;
1404 | ;; (should (eglot--glob-match "**/node_modules/**" "node_modules"))
1405 | ;; (should (eglot--glob-match "{foo,bar}/**" "foo"))
1406 | ;; (should (eglot--glob-match "{foo,bar}/**" "bar"))
1407 |
1408 | ;; VSCode also supports nested blobs. Do we care? Apparently yes:
1409 | ;; github#1403
1410 | ;;
1411 | (should (eglot--glob-match "{**/*.d.ts,**/*.js}" "/testing/foo.js"))
1412 | (should (eglot--glob-match "{**/*.d.ts,**/*.js}" "testing/foo.d.ts"))
1413 | (should (eglot--glob-match "{**/*.d.ts,**/*.js,foo.[0-9]}" "foo.5"))
1414 | (should-not (eglot--glob-match "{**/*.d.ts,**/*.js,foo.[0-4]}" "foo.5"))
1415 | (should (eglot--glob-match "prefix/{**/*.d.ts,**/*.js,foo.[0-9]}"
1416 | "prefix/foo.8"))
1417 | (should (eglot--glob-match "prefix/{**/*.js,**/foo.[0-9]}.suffix"
1418 | "prefix/a/b/c/d/foo.5.suffix"))
1419 | (should (eglot--glob-match "prefix/{**/*.js,**/foo.[0-9]}.suffix"
1420 | "prefix/a/b/c/d/foo.js.suffix")))
1421 |
1422 | (defvar tramp-histfile-override)
1423 | (defun eglot--call-with-tramp-test (fn)
1424 | (unless (>= emacs-major-version 28)
1425 | (ert-skip "Tramp tests only work reliably on Emacs 28+"))
1426 | ;; Set up a Tramp method that’s just a shell so the remote host is
1427 | ;; really just the local host.
1428 | (let* ((tramp-remote-path (cons 'tramp-own-remote-path
1429 | tramp-remote-path))
1430 | (tramp-histfile-override t)
1431 | (tramp-allow-unsafe-temporary-files t)
1432 | (tramp-verbose 1)
1433 | (temporary-file-directory
1434 | (or (bound-and-true-p ert-remote-temporary-file-directory)
1435 | (prog1 (format "/mock::%s" temporary-file-directory)
1436 | (add-to-list
1437 | 'tramp-methods
1438 | '("mock"
1439 | (tramp-login-program "sh") (tramp-login-args (("-i")))
1440 | (tramp-direct-async ("-c")) (tramp-remote-shell "/bin/sh")
1441 | (tramp-remote-shell-args ("-c")) (tramp-connection-timeout 10)))
1442 | (add-to-list 'tramp-default-host-alist
1443 | `("\\`mock\\'" nil ,(system-name)))
1444 | (when (and noninteractive (not (file-directory-p "~/")))
1445 | (setenv "HOME" temporary-file-directory)))))
1446 | (default-directory temporary-file-directory))
1447 | ;; We must check the remote LSP server. So far, just "clangd" is used.
1448 | (unless (ignore-errors (executable-find "clangd" 'remote))
1449 | (ert-skip "Remote clangd not found"))
1450 | (funcall fn)))
1451 |
1452 | (ert-deftest eglot-test-tramp-test ()
1453 | "Ensure LSP servers can be used over TRAMP."
1454 | :tags '(:expensive-test)
1455 | (eglot--call-with-tramp-test #'eglot-tests--auto-detect-running-server-1))
1456 |
1457 | (ert-deftest eglot-test-tramp-test-2 ()
1458 | "Ensure LSP servers can be used over TRAMP."
1459 | :tags '(:expensive-test)
1460 | (eglot--call-with-tramp-test #'eglot-tests--lsp-abiding-column-1))
1461 |
1462 | (ert-deftest eglot-test-path-to-uri-windows ()
1463 | (skip-unless (eq system-type 'windows-nt))
1464 | (should (string-prefix-p "file:///"
1465 | (eglot-path-to-uri "c:/Users/Foo/bar.lisp")))
1466 | (should (string-suffix-p "c%3A/Users/Foo/bar.lisp"
1467 | (eglot-path-to-uri "c:/Users/Foo/bar.lisp"))))
1468 |
1469 | (ert-deftest eglot-test-same-server-multi-mode ()
1470 | "Check single LSP instance manages multiple modes in same project."
1471 | (skip-unless (executable-find "clangd"))
1472 | (let (server)
1473 | (eglot--with-fixture
1474 | `(("project" . (("foo.cpp" .
1475 | "#include \"foolib.h\"
1476 | int main() { return foo(); }")
1477 | ("foolib.h" .
1478 | "#ifdef __cplusplus\nextern \"C\" {\n#endif
1479 | int foo();
1480 | #ifdef __cplusplus\n}\n#endif")
1481 | ("foolib.c" .
1482 | "#include \"foolib.h\"
1483 | int foo() {return 42;}"))))
1484 | (with-current-buffer
1485 | (eglot--find-file-noselect "project/foo.cpp")
1486 | (should (setq server (eglot--tests-connect))))
1487 | (with-current-buffer
1488 | (eglot--find-file-noselect "project/foolib.h")
1489 | (should (eq (eglot-current-server) server)))
1490 | (with-current-buffer
1491 | (eglot--find-file-noselect "project/foolib.c")
1492 | (should (eq (eglot-current-server) server))))))
1493 |
1494 | (provide 'eglot-tests)
1495 |
1496 | ;; Local Variables:
1497 | ;; checkdoc-force-docstrings-flag: nil
1498 | ;; End:
1499 |
1500 | ;;; eglot-tests.el ends here
1501 |
--------------------------------------------------------------------------------
/gif-examples/eglot-code-actions.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joaotavora/eglot/355a167c625b58a0ff2c1b1bbcc8c18bf64b3b08/gif-examples/eglot-code-actions.gif
--------------------------------------------------------------------------------
/gif-examples/eglot-completions.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joaotavora/eglot/355a167c625b58a0ff2c1b1bbcc8c18bf64b3b08/gif-examples/eglot-completions.gif
--------------------------------------------------------------------------------
/gif-examples/eglot-diagnostics.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joaotavora/eglot/355a167c625b58a0ff2c1b1bbcc8c18bf64b3b08/gif-examples/eglot-diagnostics.gif
--------------------------------------------------------------------------------
/gif-examples/eglot-hover-on-symbol.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joaotavora/eglot/355a167c625b58a0ff2c1b1bbcc8c18bf64b3b08/gif-examples/eglot-hover-on-symbol.gif
--------------------------------------------------------------------------------
/gif-examples/eglot-rename.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joaotavora/eglot/355a167c625b58a0ff2c1b1bbcc8c18bf64b3b08/gif-examples/eglot-rename.gif
--------------------------------------------------------------------------------
/gif-examples/eglot-snippets-on-completion.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joaotavora/eglot/355a167c625b58a0ff2c1b1bbcc8c18bf64b3b08/gif-examples/eglot-snippets-on-completion.gif
--------------------------------------------------------------------------------
/gif-examples/eglot-xref-find-definition.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joaotavora/eglot/355a167c625b58a0ff2c1b1bbcc8c18bf64b3b08/gif-examples/eglot-xref-find-definition.gif
--------------------------------------------------------------------------------
/gif-examples/eglot-xref-find-references.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/joaotavora/eglot/355a167c625b58a0ff2c1b1bbcc8c18bf64b3b08/gif-examples/eglot-xref-find-references.gif
--------------------------------------------------------------------------------