diff options
| author | Philip Kaludercic | 2022-10-20 20:00:32 +0200 |
|---|---|---|
| committer | Philip Kaludercic | 2022-10-20 20:00:32 +0200 |
| commit | 37bfb623e4b253443e8280c3de4ff91f8db5f51b (patch) | |
| tree | 39e48d687ace2d9b88b10bf82412301c4e723743 | |
| parent | e08e9bc40f2c309bf119659a6496759493bd35e1 (diff) | |
| parent | 937ae0cf55d31c332fba3d96061e2ac3653e5437 (diff) | |
| download | emacs-37bfb623e4b253443e8280c3de4ff91f8db5f51b.tar.gz emacs-37bfb623e4b253443e8280c3de4ff91f8db5f51b.zip | |
Merge remote-tracking branch 'origin/master' into feature/package+vc
34 files changed, 5396 insertions, 138 deletions
diff --git a/admin/automerge b/admin/automerge index c7c17dfb5ec..d2c92948e17 100755 --- a/admin/automerge +++ b/admin/automerge | |||
| @@ -35,18 +35,7 @@ | |||
| 35 | ## it with the -d option in the repository directory, in case a pull | 35 | ## it with the -d option in the repository directory, in case a pull |
| 36 | ## updates this script while it is working. | 36 | ## updates this script while it is working. |
| 37 | 37 | ||
| 38 | set -o nounset | 38 | source "${0%/*}/emacs-shell-lib" |
| 39 | |||
| 40 | die () # write error to stderr and exit | ||
| 41 | { | ||
| 42 | [ $# -gt 0 ] && echo "$PN: $*" >&2 | ||
| 43 | exit 1 | ||
| 44 | } | ||
| 45 | |||
| 46 | PN=${0##*/} # basename of script | ||
| 47 | PD=${0%/*} | ||
| 48 | |||
| 49 | [ "$PD" = "$0" ] && PD=. # if PATH includes PWD | ||
| 50 | 39 | ||
| 51 | usage () | 40 | usage () |
| 52 | { | 41 | { |
| @@ -129,13 +118,7 @@ OPTIND=1 | |||
| 129 | [ "$test" ] && build=1 | 118 | [ "$test" ] && build=1 |
| 130 | 119 | ||
| 131 | 120 | ||
| 132 | if [ -x "$(command -v mktemp)" ]; then | 121 | tempfile="$(emacs_mktemp)" |
| 133 | tempfile=$(mktemp "/tmp/$PN.XXXXXXXXXX") | ||
| 134 | else | ||
| 135 | tempfile=/tmp/$PN.$$ | ||
| 136 | fi | ||
| 137 | |||
| 138 | trap 'rm -f $tempfile 2> /dev/null' EXIT | ||
| 139 | 122 | ||
| 140 | 123 | ||
| 141 | [ -e Makefile ] && [ "$build" ] && { | 124 | [ -e Makefile ] && [ "$build" ] && { |
| @@ -263,5 +246,3 @@ git push || die "push error" | |||
| 263 | 246 | ||
| 264 | 247 | ||
| 265 | exit 0 | 248 | exit 0 |
| 266 | |||
| 267 | ### automerge ends here | ||
diff --git a/admin/diff-tar-files b/admin/diff-tar-files index 6ab39eab2f5..869c9421502 100755 --- a/admin/diff-tar-files +++ b/admin/diff-tar-files | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | #! /bin/sh | 1 | #!/bin/bash |
| 2 | 2 | ||
| 3 | # Copyright (C) 2001-2022 Free Software Foundation, Inc. | 3 | # Copyright (C) 2001-2022 Free Software Foundation, Inc. |
| 4 | 4 | ||
| @@ -17,6 +17,7 @@ | |||
| 17 | # You should have received a copy of the GNU General Public License | 17 | # You should have received a copy of the GNU General Public License |
| 18 | # along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. | 18 | # along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. |
| 19 | 19 | ||
| 20 | source "${0%/*}/emacs-shell-lib" | ||
| 20 | 21 | ||
| 21 | if [ $# != 2 ]; then | 22 | if [ $# != 2 ]; then |
| 22 | cat <<EOF | 23 | cat <<EOF |
| @@ -31,9 +32,8 @@ fi | |||
| 31 | old_tar=$1 | 32 | old_tar=$1 |
| 32 | new_tar=$2 | 33 | new_tar=$2 |
| 33 | 34 | ||
| 34 | old_tmp=/tmp/old.$$ | 35 | old_tmp="$(emacs_mktemp ${PN}-old)" |
| 35 | new_tmp=/tmp/new.$$ | 36 | new_tmp="$(emacs_mktemp ${PN}-new)" |
| 36 | trap "rm -f $old_tmp $new_tmp; exit 1" 1 2 15 | ||
| 37 | 37 | ||
| 38 | tar tf "$old_tar" | sed -e 's,^[^/]*,,' | sort > $old_tmp | 38 | tar tf "$old_tar" | sed -e 's,^[^/]*,,' | sort > $old_tmp |
| 39 | tar tf "$new_tar" | sed -e 's,^[^/]*,,' | sort > $new_tmp | 39 | tar tf "$new_tar" | sed -e 's,^[^/]*,,' | sort > $new_tmp |
diff --git a/admin/emacs-shell-lib b/admin/emacs-shell-lib new file mode 100644 index 00000000000..750f81e0577 --- /dev/null +++ b/admin/emacs-shell-lib | |||
| @@ -0,0 +1,87 @@ | |||
| 1 | #!/bin/bash | ||
| 2 | ### emacs-shell-lib - shared code for Emacs shell scripts | ||
| 3 | |||
| 4 | ## Copyright (C) 2022 Free Software Foundation, Inc. | ||
| 5 | |||
| 6 | ## Author: Stefan Kangas <stefankangas@gmail.com> | ||
| 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 <https://www.gnu.org/licenses/>. | ||
| 22 | |||
| 23 | ### Code: | ||
| 24 | |||
| 25 | # Set an explicit umask. | ||
| 26 | umask 077 | ||
| 27 | |||
| 28 | # Treat unset variables as an error. | ||
| 29 | set -o nounset | ||
| 30 | |||
| 31 | # Exit immediately on error. | ||
| 32 | set -o errexit | ||
| 33 | |||
| 34 | # Avoid non-standard command output from non-C locales. | ||
| 35 | unset LANG LC_ALL LC_MESSAGES | ||
| 36 | |||
| 37 | PN=${0##*/} # basename of script | ||
| 38 | PD=${0%/*} # script directory | ||
| 39 | |||
| 40 | [ "$PD" = "$0" ] && PD=. # if PATH includes PWD | ||
| 41 | |||
| 42 | die () # write error to stderr and exit | ||
| 43 | { | ||
| 44 | [ $# -gt 0 ] && echo "$PN: $@" >&2 | ||
| 45 | exit 1 | ||
| 46 | } | ||
| 47 | |||
| 48 | emacs_tempfiles=() | ||
| 49 | |||
| 50 | emacs_tempfiles_cleanup () | ||
| 51 | { | ||
| 52 | for file in ${emacs_tempfiles[@]}; do | ||
| 53 | rm -f "${file}" 2> /dev/null | ||
| 54 | done | ||
| 55 | } | ||
| 56 | |||
| 57 | trap ' | ||
| 58 | ret=$? | ||
| 59 | emacs_tempfiles_cleanup | ||
| 60 | exit $ret | ||
| 61 | ' EXIT | ||
| 62 | |||
| 63 | emacs_mktemp () | ||
| 64 | { | ||
| 65 | local readonly file="${1-}" | ||
| 66 | local tempfile | ||
| 67 | local prefix | ||
| 68 | |||
| 69 | if [ -z "$file" ]; then | ||
| 70 | prefix="$PN" | ||
| 71 | else | ||
| 72 | prefix="$1" | ||
| 73 | fi | ||
| 74 | |||
| 75 | if [ -x "$(command -v mktemp)" ]; then | ||
| 76 | tempfile=$(mktemp "${TMPDIR-/tmp}/${prefix}.XXXXXXXXXX") | ||
| 77 | else | ||
| 78 | tempfile="${TMPDIR-/tmp}/${prefix}.$RANDOM$$" | ||
| 79 | (umask 077 && touch "$tempfile") | ||
| 80 | fi | ||
| 81 | |||
| 82 | [ -z "${tempfile}" ] && die "Creating temporary file failed" | ||
| 83 | |||
| 84 | emacs_tempfiles+=("${tempfile}") | ||
| 85 | |||
| 86 | echo "$tempfile" | ||
| 87 | } | ||
diff --git a/admin/make-manuals b/admin/make-manuals index cb0c00a423f..a252bf20f1e 100755 --- a/admin/make-manuals +++ b/admin/make-manuals | |||
| @@ -33,15 +33,7 @@ | |||
| 33 | 33 | ||
| 34 | ### Code: | 34 | ### Code: |
| 35 | 35 | ||
| 36 | set -o nounset | 36 | source "${0%/*}/emacs-shell-lib" |
| 37 | |||
| 38 | die () # write error to stderr and exit | ||
| 39 | { | ||
| 40 | [ $# -gt 0 ] && echo "$PN: $@" >&2 | ||
| 41 | exit 1 | ||
| 42 | } | ||
| 43 | |||
| 44 | PN=${0##*/} # basename of script | ||
| 45 | 37 | ||
| 46 | usage () | 38 | usage () |
| 47 | { | 39 | { |
| @@ -96,8 +88,7 @@ OPTIND=1 | |||
| 96 | [ -e admin/admin.el ] || die "admin/admin.el not found" | 88 | [ -e admin/admin.el ] || die "admin/admin.el not found" |
| 97 | 89 | ||
| 98 | 90 | ||
| 99 | tempfile=/tmp/$PN.$$ | 91 | tempfile="$(emacs_mktemp)" |
| 100 | trap "rm -f $tempfile 2> /dev/null" EXIT | ||
| 101 | 92 | ||
| 102 | 93 | ||
| 103 | [ "$continue" ] || rm -rf $outdir | 94 | [ "$continue" ] || rm -rf $outdir |
diff --git a/admin/update_autogen b/admin/update_autogen index d1f49d9f25e..55e11be95c7 100755 --- a/admin/update_autogen +++ b/admin/update_autogen | |||
| @@ -32,18 +32,7 @@ | |||
| 32 | 32 | ||
| 33 | ### Code: | 33 | ### Code: |
| 34 | 34 | ||
| 35 | set -o nounset | 35 | source "${0%/*}/emacs-shell-lib" |
| 36 | |||
| 37 | die () # write error to stderr and exit | ||
| 38 | { | ||
| 39 | [ $# -gt 0 ] && echo "$PN: $@" >&2 | ||
| 40 | exit 1 | ||
| 41 | } | ||
| 42 | |||
| 43 | PN=${0##*/} # basename of script | ||
| 44 | PD=${0%/*} | ||
| 45 | |||
| 46 | [ "$PD" = "$0" ] && PD=. # if PATH includes PWD | ||
| 47 | 36 | ||
| 48 | ## This should be the admin directory. | 37 | ## This should be the admin directory. |
| 49 | cd $PD || exit | 38 | cd $PD || exit |
| @@ -102,10 +91,7 @@ done | |||
| 102 | 91 | ||
| 103 | [ "$basegen" ] || die "internal error" | 92 | [ "$basegen" ] || die "internal error" |
| 104 | 93 | ||
| 105 | tempfile=/tmp/$PN.$$ | 94 | tempfile="$(emacs_mktemp)" |
| 106 | |||
| 107 | trap 'rm -f $tempfile 2> /dev/null' EXIT | ||
| 108 | |||
| 109 | 95 | ||
| 110 | while getopts ":hcfqA:CL" option ; do | 96 | while getopts ":hcfqA:CL" option ; do |
| 111 | case $option in | 97 | case $option in |
| @@ -312,5 +298,3 @@ commit "loaddefs" $modified || die "commit error" | |||
| 312 | 298 | ||
| 313 | 299 | ||
| 314 | exit 0 | 300 | exit 0 |
| 315 | |||
| 316 | ### update_autogen ends here | ||
diff --git a/admin/upload-manuals b/admin/upload-manuals index 50336ee64c0..04f7c3acc72 100755 --- a/admin/upload-manuals +++ b/admin/upload-manuals | |||
| @@ -36,15 +36,7 @@ | |||
| 36 | 36 | ||
| 37 | ### Code: | 37 | ### Code: |
| 38 | 38 | ||
| 39 | set -o nounset | 39 | source "${0%/*}/emacs-shell-lib" |
| 40 | |||
| 41 | die () # write error to stderr and exit | ||
| 42 | { | ||
| 43 | [ $# -gt 0 ] && echo "$PN: $@" >&2 | ||
| 44 | exit 1 | ||
| 45 | } | ||
| 46 | |||
| 47 | PN=${0##*/} # basename of script | ||
| 48 | 40 | ||
| 49 | usage () | 41 | usage () |
| 50 | { | 42 | { |
diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi index ad4a3ea3506..94171b3a089 100644 --- a/doc/emacs/maintaining.texi +++ b/doc/emacs/maintaining.texi | |||
| @@ -2095,6 +2095,13 @@ that it only knows about subunits that were loaded into the | |||
| 2095 | interpreter.) | 2095 | interpreter.) |
| 2096 | 2096 | ||
| 2097 | @item | 2097 | @item |
| 2098 | If Eglot is activated for the current buffer's project | ||
| 2099 | (@pxref{Projects}) and the current buffer's major mode, Eglot consults | ||
| 2100 | an external language server program and provides the data supplied by | ||
| 2101 | the server regarding the definitions of the identifiers in the | ||
| 2102 | project. @xref{Eglot Features,,, eglot, Eglot: The Emacs LSP Client}. | ||
| 2103 | |||
| 2104 | @item | ||
| 2098 | An external program can extract references by scanning the relevant | 2105 | An external program can extract references by scanning the relevant |
| 2099 | files, and build a database of these references. A backend can then | 2106 | files, and build a database of these references. A backend can then |
| 2100 | access this database whenever it needs to list or look up references. | 2107 | access this database whenever it needs to list or look up references. |
diff --git a/doc/emacs/programs.texi b/doc/emacs/programs.texi index 818deb39415..b5e577d96a4 100644 --- a/doc/emacs/programs.texi +++ b/doc/emacs/programs.texi | |||
| @@ -287,6 +287,13 @@ they occur in the buffer; if you want alphabetic sorting, use the | |||
| 287 | symbol @code{imenu--sort-by-name} as the value. You can also | 287 | symbol @code{imenu--sort-by-name} as the value. You can also |
| 288 | define your own comparison function by writing Lisp code. | 288 | define your own comparison function by writing Lisp code. |
| 289 | 289 | ||
| 290 | If Eglot is activated for the current buffer's project | ||
| 291 | (@pxref{Projects}) and the current buffer's major mode, Eglot provides | ||
| 292 | its own facility for producing the buffer's index based on the | ||
| 293 | analysis of the program source by the language-server which manages | ||
| 294 | the current buffer. @xref{Eglot Features,,, eglot, Eglot: The Emacs | ||
| 295 | LSP Client}. | ||
| 296 | |||
| 290 | Imenu provides the information to guide Which Function mode | 297 | Imenu provides the information to guide Which Function mode |
| 291 | @ifnottex | 298 | @ifnottex |
| 292 | (@pxref{Which Function}). | 299 | (@pxref{Which Function}). |
| @@ -1439,6 +1446,13 @@ candidates: | |||
| 1439 | 1446 | ||
| 1440 | @itemize @bullet | 1447 | @itemize @bullet |
| 1441 | @item | 1448 | @item |
| 1449 | If Eglot is activated for the current buffer's project | ||
| 1450 | (@pxref{Projects}) and the current buffer's major mode, the command | ||
| 1451 | tries to use the corresponding language server for producing the list | ||
| 1452 | of completion candidates. @xref{Eglot Features,,, eglot, Eglot: The | ||
| 1453 | Emacs LSP Client}. | ||
| 1454 | |||
| 1455 | @item | ||
| 1442 | If Semantic mode is enabled (@pxref{Semantic}), the command tries to | 1456 | If Semantic mode is enabled (@pxref{Semantic}), the command tries to |
| 1443 | use the Semantic parser data for completion. | 1457 | use the Semantic parser data for completion. |
| 1444 | 1458 | ||
diff --git a/doc/lispref/functions.texi b/doc/lispref/functions.texi index 8b858e0aa01..7ffde7d43d1 100644 --- a/doc/lispref/functions.texi +++ b/doc/lispref/functions.texi | |||
| @@ -533,6 +533,44 @@ Instead, use the @code{advertised-calling-convention} declaration | |||
| 533 | compiler emit a warning message when it compiles Lisp programs which | 533 | compiler emit a warning message when it compiles Lisp programs which |
| 534 | use the deprecated calling convention. | 534 | use the deprecated calling convention. |
| 535 | 535 | ||
| 536 | @cindex computed documentation string | ||
| 537 | @kindex :documentation | ||
| 538 | Documentation strings are usually static, but occasionally it can be | ||
| 539 | necessary to generate them dynamically. In some cases you can do so | ||
| 540 | by writing a macro which generates at compile time the code of the | ||
| 541 | function, including the desired documentation string. But you can | ||
| 542 | also generate the docstring dynamically by writing | ||
| 543 | @code{(:documentation @var{form})} instead of the documentation | ||
| 544 | string. This will evaluate @var{form} at run-time when the function | ||
| 545 | is defined and use it as the documentation string@footnote{This only | ||
| 546 | works in code using @code{lexical-binding}.}. You can also compute | ||
| 547 | the documentation string on the fly when it is requested, by setting | ||
| 548 | the @code{function-documentation} property of the function's symbol to | ||
| 549 | a Lisp form that evaluates to a string. | ||
| 550 | |||
| 551 | For example: | ||
| 552 | @example | ||
| 553 | @group | ||
| 554 | (defun adder (x) | ||
| 555 | (lambda (y) | ||
| 556 | (:documentation (format "Add %S to the argument Y." x)) | ||
| 557 | (+ x y))) | ||
| 558 | (defalias 'adder5 (adder 5)) | ||
| 559 | (documentation 'adder5) | ||
| 560 | @result{} "Add 5 to the argument Y." | ||
| 561 | @end group | ||
| 562 | |||
| 563 | @group | ||
| 564 | (put 'adder5 'function-documentation | ||
| 565 | '(concat (documentation (symbol-function 'adder5) 'raw) | ||
| 566 | " Consulted at " (format-time-string "%H:%M:%S"))) | ||
| 567 | (documentation 'adder5) | ||
| 568 | @result{} "Add 5 to the argument Y. Consulted at 15:52:13" | ||
| 569 | (documentation 'adder5) | ||
| 570 | @result{} "Add 5 to the argument Y. Consulted at 15:52:18" | ||
| 571 | @end group | ||
| 572 | @end example | ||
| 573 | |||
| 536 | @node Function Names | 574 | @node Function Names |
| 537 | @section Naming a Function | 575 | @section Naming a Function |
| 538 | @cindex function definition | 576 | @cindex function definition |
diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi index 73c5fcd44d7..434538dcbf8 100644 --- a/doc/lispref/modes.texi +++ b/doc/lispref/modes.texi | |||
| @@ -1851,7 +1851,9 @@ to enable or disable the buffer-local minor mode @var{mode} in all (or | |||
| 1851 | some; see below) buffers. It also executes the @var{body} forms. To | 1851 | some; see below) buffers. It also executes the @var{body} forms. To |
| 1852 | turn on the minor mode in a buffer, it uses the function | 1852 | turn on the minor mode in a buffer, it uses the function |
| 1853 | @var{turn-on}; to turn off the minor mode, it calls @var{mode} with | 1853 | @var{turn-on}; to turn off the minor mode, it calls @var{mode} with |
| 1854 | @minus{}1 as argument. | 1854 | @minus{}1 as argument. (The function @var{turn-on} is a separate |
| 1855 | function so it could determine whether to enable the minor mode or not | ||
| 1856 | when it is not a priori clear that it should always be enabled.) | ||
| 1855 | 1857 | ||
| 1856 | Globally enabling the mode also affects buffers subsequently created | 1858 | Globally enabling the mode also affects buffers subsequently created |
| 1857 | by visiting files, and buffers that use a major mode other than | 1859 | by visiting files, and buffers that use a major mode other than |
diff --git a/doc/misc/Makefile.in b/doc/misc/Makefile.in index 1d881a5fc7f..b6eef7ea799 100644 --- a/doc/misc/Makefile.in +++ b/doc/misc/Makefile.in | |||
| @@ -68,7 +68,7 @@ DOCMISC_W32 = @DOCMISC_W32@ | |||
| 68 | 68 | ||
| 69 | ## Info files to build and install on all platforms. | 69 | ## Info files to build and install on all platforms. |
| 70 | INFO_COMMON = auth autotype bovine calc ccmode cl \ | 70 | INFO_COMMON = auth autotype bovine calc ccmode cl \ |
| 71 | dbus dired-x ebrowse ede ediff edt eieio \ | 71 | dbus dired-x ebrowse ede ediff edt eglot eieio \ |
| 72 | emacs-mime epa erc ert eshell eudc efaq eww \ | 72 | emacs-mime epa erc ert eshell eudc efaq eww \ |
| 73 | flymake forms gnus emacs-gnutls htmlfontify idlwave ido info.info \ | 73 | flymake forms gnus emacs-gnutls htmlfontify idlwave ido info.info \ |
| 74 | mairix-el message mh-e modus-themes newsticker nxml-mode octave-mode \ | 74 | mairix-el message mh-e modus-themes newsticker nxml-mode octave-mode \ |
diff --git a/doc/misc/eglot.texi b/doc/misc/eglot.texi new file mode 100644 index 00000000000..033464f9909 --- /dev/null +++ b/doc/misc/eglot.texi | |||
| @@ -0,0 +1,1138 @@ | |||
| 1 | \input texinfo @c -*-texinfo-*- | ||
| 2 | @c %**start of header | ||
| 3 | @setfilename ../../eglot.info | ||
| 4 | @settitle Eglot: The Emacs Client for the Language Server Protocol | ||
| 5 | @include docstyle.texi | ||
| 6 | @syncodeindex vr cp | ||
| 7 | @syncodeindex fn cp | ||
| 8 | @c %**end of header | ||
| 9 | |||
| 10 | @copying | ||
| 11 | This manual is for Eglot, the Emacs LSP client. | ||
| 12 | |||
| 13 | Copyright @copyright{} 2022 Free Software Foundation, Inc. | ||
| 14 | |||
| 15 | @quotation | ||
| 16 | Permission is granted to copy, distribute and/or modify this document | ||
| 17 | under the terms of the GNU Free Documentation License, Version 1.3 or | ||
| 18 | any later version published by the Free Software Foundation; with no | ||
| 19 | Invariant Sections, with the Front-Cover Texts being ``A GNU Manual'', | ||
| 20 | and with the Back-Cover Texts as in (a) below. A copy of the license | ||
| 21 | is included in the section entitled ``GNU Free Documentation License''. | ||
| 22 | |||
| 23 | (a) The FSF's Back-Cover Text is: ``You have the freedom to copy and | ||
| 24 | modify this GNU manual.'' | ||
| 25 | @end quotation | ||
| 26 | @end copying | ||
| 27 | |||
| 28 | @dircategory Emacs misc features | ||
| 29 | @direntry | ||
| 30 | * Eglot: (eglot). Language Server Protocol client for Emacs. | ||
| 31 | @end direntry | ||
| 32 | |||
| 33 | @titlepage | ||
| 34 | @sp 4 | ||
| 35 | @c The title is printed in a large font. | ||
| 36 | @center @titlefont{User's Guide} | ||
| 37 | @sp 1 | ||
| 38 | @center @titlefont{to} | ||
| 39 | @sp 1 | ||
| 40 | @center @titlefont{Eglot: The Emacs LSP Client} | ||
| 41 | @ignore | ||
| 42 | @sp 2 | ||
| 43 | @center release 1.8 | ||
| 44 | @c -release- | ||
| 45 | @end ignore | ||
| 46 | @sp 3 | ||
| 47 | @center Jo@~ao T@'avora & Eli Zaretskii | ||
| 48 | @c -date- | ||
| 49 | |||
| 50 | @page | ||
| 51 | @vskip 0pt plus 1filll | ||
| 52 | @insertcopying | ||
| 53 | @end titlepage | ||
| 54 | |||
| 55 | @contents | ||
| 56 | |||
| 57 | @ifnottex | ||
| 58 | @node Top | ||
| 59 | @top Eglot | ||
| 60 | |||
| 61 | @cindex LSP | ||
| 62 | @cindex language server protocol | ||
| 63 | Eglot is the Emacs client for the @dfn{Language Server Protocol} | ||
| 64 | (@acronym{LSP}). The name ``Eglot'' is an acronym that stands for | ||
| 65 | @ifhtml | ||
| 66 | ``@emph{E}macs Poly@emph{glot}''. | ||
| 67 | @end ifhtml | ||
| 68 | @ifnothtml | ||
| 69 | ``Emacs polyGLOT''. | ||
| 70 | @end ifnothtml | ||
| 71 | @footnote{ | ||
| 72 | A @dfn{polyglot} is a | ||
| 73 | person who is able to use several languages. | ||
| 74 | } Eglot provides infrastructure and a set of commands for enriching | ||
| 75 | the source code editing capabilities of Emacs via LSP. LSP is a | ||
| 76 | standardized communications protocol between source code editors (such | ||
| 77 | as Emacs) and language servers---programs external to Emacs which | ||
| 78 | analyze the source code on behalf of Emacs. The protocol allows Emacs | ||
| 79 | to receive various source code services from the server, such as | ||
| 80 | description and location of functions calls, types of variables, class | ||
| 81 | definitions, syntactic errors, etc. This way, Emacs doesn't need to | ||
| 82 | implement the language-specific parsing and analysis capabilities in | ||
| 83 | its own code, but is still capable of providing sophisticated editing | ||
| 84 | features that rely on such capabilities, such as automatic code | ||
| 85 | completion, go-to definition of function/class, documentation of | ||
| 86 | symbol at-point, refactoring, on-the-fly diagnostics, and more. | ||
| 87 | |||
| 88 | Eglot itself is completely language-agnostic, but it can support any | ||
| 89 | programming language for which there is a language server and an Emacs | ||
| 90 | major mode. | ||
| 91 | |||
| 92 | This manual documents how to configure, use, and customize Eglot. | ||
| 93 | |||
| 94 | @insertcopying | ||
| 95 | |||
| 96 | @menu | ||
| 97 | * Quick Start:: For the impatient. | ||
| 98 | * Eglot and LSP Servers:: How to work with language servers. | ||
| 99 | * Using Eglot:: Important Eglot commands and variables. | ||
| 100 | * Customizing Eglot:: Eglot customization and advanced features. | ||
| 101 | * Troubleshooting Eglot:: Troubleshooting and reporting bugs. | ||
| 102 | * GNU Free Documentation License:: The license for this manual. | ||
| 103 | * Index:: | ||
| 104 | @end menu | ||
| 105 | @end ifnottex | ||
| 106 | |||
| 107 | @node Quick Start | ||
| 108 | @chapter Quick Start | ||
| 109 | @cindex quick start | ||
| 110 | |||
| 111 | This chapter provides concise instructions for setting up and using | ||
| 112 | Eglot with your programming project in common usage scenarios. For | ||
| 113 | more detailed instructions regarding Eglot setup, @pxref{Eglot and LSP | ||
| 114 | Servers}. @xref{Using Eglot}, for detailed description of using Eglot, | ||
| 115 | and see @ref{Customizing Eglot}, for adapting Eglot to less common use | ||
| 116 | patterns. | ||
| 117 | |||
| 118 | Here's how to start using Eglot with your programming project: | ||
| 119 | |||
| 120 | @enumerate | ||
| 121 | @item | ||
| 122 | Select and install a language server. | ||
| 123 | |||
| 124 | Eglot comes pre-configured with many popular language servers, see the | ||
| 125 | value of @code{eglot-server-programs}. If the server(s) mentioned | ||
| 126 | there satisfy your needs for the programming language(s) with which | ||
| 127 | you want to use Eglot, you just need to make sure those servers are | ||
| 128 | installed on your system. Alternatively, install one or more servers | ||
| 129 | of your choice and add them to the value of | ||
| 130 | @code{eglot-server-programs}, as described in @ref{Setting Up LSP | ||
| 131 | Servers}. | ||
| 132 | |||
| 133 | @item | ||
| 134 | Turn on Eglot for your project. | ||
| 135 | |||
| 136 | To start using Eglot for a project, type @kbd{M-x eglot @key{RET}} in | ||
| 137 | a buffer visiting any file that belongs to the project. This starts | ||
| 138 | the language server configured for the programming language of that | ||
| 139 | buffer, and causes Eglot to start managing all the files of the | ||
| 140 | project which use the same programming language. The notion of a | ||
| 141 | ``project'' used by Eglot is the same Emacs uses (@pxref{Projects,,, | ||
| 142 | emacs, GNU Emacs Manual}): in the simplest case, the ``project'' is | ||
| 143 | the single file you are editing, but it can also be all the files in a | ||
| 144 | single directory or a directory tree under some version control | ||
| 145 | system, such as Git. | ||
| 146 | |||
| 147 | Alternatively, you can start Eglot automatically from the major-mode | ||
| 148 | hook of the mode used for the programming language; see @ref{Starting | ||
| 149 | Eglot}. | ||
| 150 | |||
| 151 | @item | ||
| 152 | Use Eglot. | ||
| 153 | |||
| 154 | Most Eglot facilities are integrated into Emacs features, such as | ||
| 155 | ElDoc, Flymake, Xref, and Imenu. However, Eglot also provides | ||
| 156 | commands of its own, mainly to perform tasks by the LSP server, such | ||
| 157 | as @kbd{M-x eglot-rename} (to rename an identifier across the entire | ||
| 158 | project), @kbd{M-x eglot-format} (to reformat and reindent code), and | ||
| 159 | some others. @xref{Eglot Commands}, for the detailed list of Eglot | ||
| 160 | commands. | ||
| 161 | |||
| 162 | @item | ||
| 163 | That's it! | ||
| 164 | @end enumerate | ||
| 165 | |||
| 166 | @node Eglot and LSP Servers | ||
| 167 | @chapter Eglot and LSP Servers | ||
| 168 | |||
| 169 | This chapter describes how to set up Eglot for your needs, and how to | ||
| 170 | start it. | ||
| 171 | |||
| 172 | @menu | ||
| 173 | * Setting Up LSP Servers:: How to configure LSP servers for your needs. | ||
| 174 | * Starting Eglot:: Ways of starting Eglot for your project. | ||
| 175 | * Shutting Down LSP Servers:: | ||
| 176 | @end menu | ||
| 177 | |||
| 178 | @node Setting Up LSP Servers | ||
| 179 | @section Setting Up LSP Servers | ||
| 180 | @cindex setting up LSP server for Eglot | ||
| 181 | @cindex language server for Eglot | ||
| 182 | |||
| 183 | For Eglot to be useful, it must first be combined with a suitable | ||
| 184 | language server. Usually, that means running the server program | ||
| 185 | locally as a child process of Emacs (@pxref{Processes,,, elisp, GNU | ||
| 186 | Emacs Lisp Reference Manual}) and communicating with it via the | ||
| 187 | standard input and output streams. | ||
| 188 | |||
| 189 | The language server program must be installed separately, and is not | ||
| 190 | further discussed in this manual; refer to the documentation of the | ||
| 191 | particular server(s) you want to install. | ||
| 192 | |||
| 193 | To use a language server, Eglot must know how to start it and which | ||
| 194 | programming languages each server supports. This information is | ||
| 195 | provided by the variable @code{eglot-server-programs}. | ||
| 196 | |||
| 197 | @defvar eglot-server-programs | ||
| 198 | This variable associates major modes with names and command-line | ||
| 199 | arguments of the language server programs corresponding to the | ||
| 200 | programming language of each major mode. It provides all the | ||
| 201 | information that Eglot needs to know about the programming language of | ||
| 202 | the source you are editing. | ||
| 203 | |||
| 204 | The value of the variable is an alist, whose elements are of the form | ||
| 205 | @w{@code{(@var{major-mode} . @var{server})}}. | ||
| 206 | |||
| 207 | The @var{major-mode} of the alist elements can be either a symbol of | ||
| 208 | an Emacs major mode or a list of the form @w{@code{(@var{mode} | ||
| 209 | :language-id @var{id})}}, with @var{mode} being a major-mode symbol | ||
| 210 | and @var{id} a string that identifies the language to the server (if | ||
| 211 | Eglot cannot by itself convert the major-mode to the language | ||
| 212 | identifier string required by the server). In addition, | ||
| 213 | @var{major-mode} can be a list of several major modes specified in one | ||
| 214 | of the above forms -- this means a running instance of the associated | ||
| 215 | server is responsible for files of multiple major modes or languages | ||
| 216 | in the project. | ||
| 217 | |||
| 218 | The @var{server} part of the alist elements can be one of the | ||
| 219 | following: | ||
| 220 | |||
| 221 | @table @code | ||
| 222 | @item (@var{program} @var{args}@dots{}) | ||
| 223 | This says to invoke @var{program} with zero or more arguments | ||
| 224 | @var{args}; the program is expected to communicate with Emacs via the | ||
| 225 | standard input and standard output streams. | ||
| 226 | |||
| 227 | @item (@var{program} @var{args}@dots{} :initializationOptions @var{options}@dots{}) | ||
| 228 | Like above, but with @var{options} specifying the options to be | ||
| 229 | used for constructing the @samp{initializationOptions} JSON object for | ||
| 230 | the server. @var{options} can also be a function of one argument, in | ||
| 231 | which case it will be called with the server instance as the argument, | ||
| 232 | and should return the JSON object to use for initialization. | ||
| 233 | |||
| 234 | @item (@var{host} @var{port} @var{args}@dots{}) | ||
| 235 | Here @var{host} is a string and @var{port} is a positive integer | ||
| 236 | specifying a TCP connection to a remote server. The @var{args} are | ||
| 237 | passed to @code{open-network-stream}, e.g.@: if the connection needs | ||
| 238 | to use encryption or other non-default parameters (@pxref{Network,,, | ||
| 239 | elisp, GNU Emacs Lisp Reference Manual}). | ||
| 240 | |||
| 241 | @item (@var{program} @var{args}@dots{} :autoport @var{moreargs}@dots{}) | ||
| 242 | @var{program} is started with a command line constructed from | ||
| 243 | @var{args} followed by an available server port and the rest of | ||
| 244 | arguments in @var{moreargs}; Eglot then establishes a TCP connection | ||
| 245 | with the server via that port on the local host. | ||
| 246 | |||
| 247 | @item @var{function} | ||
| 248 | This should be a function of a single argument: non-@code{nil} if the | ||
| 249 | connection was requested interactively (e.g., by the @code{eglot} | ||
| 250 | command), otherwise @code{nil}. The function should return a value of | ||
| 251 | any of the forms described above. This allows interaction with the | ||
| 252 | user for determining the program to start and its command-line | ||
| 253 | arguments. | ||
| 254 | @end table | ||
| 255 | |||
| 256 | @end defvar | ||
| 257 | |||
| 258 | Eglot comes with a fairly complete set of associations of major-modes | ||
| 259 | to popular language servers predefined. If you need to add server | ||
| 260 | associations to the default list, use @code{add-to-list}. For | ||
| 261 | example, if there is a hypothetical language server program | ||
| 262 | @command{fools} for the language @code{Foo} which is supported by an | ||
| 263 | Emacs major-mode @code{foo-mode}, you can add it to the alist like | ||
| 264 | this: | ||
| 265 | |||
| 266 | @lisp | ||
| 267 | (add-to-list 'eglot-server-programs | ||
| 268 | '(foo-mode . ("fools" "--stdio"))) | ||
| 269 | @end lisp | ||
| 270 | |||
| 271 | This will invoke the program @command{fools} with the command-line | ||
| 272 | argument @option{--stdio} in support of editing source files for which | ||
| 273 | Emacs turns on @code{foo-mode}, and will communicate with the program | ||
| 274 | via the standard streams. As usual with invoking programs, the | ||
| 275 | executable file @file{fools} should be in one of the directories | ||
| 276 | mentioned by the @code{exec-path} variable (@pxref{Subprocess | ||
| 277 | Creation,,, elisp, GNU Emacs Lisp Reference Manual}), for Eglot to be | ||
| 278 | able to find it. | ||
| 279 | |||
| 280 | @node Starting Eglot | ||
| 281 | @section Starting Eglot | ||
| 282 | @cindex starting Eglot | ||
| 283 | @cindex activating Eglot for a project | ||
| 284 | |||
| 285 | @findex eglot | ||
| 286 | The most common way to start Eglot is to simply visit a source file of | ||
| 287 | a given language and use the command @kbd{M-x eglot}. This starts the | ||
| 288 | language server suitable for the visited file's major-mode, and | ||
| 289 | attempts to connect to it. If the connection to the language server | ||
| 290 | is successful, you will see the @code{[eglot:@var{project}]} indicator | ||
| 291 | on the mode line which reflects the server that was started. If the | ||
| 292 | server program couldn't be started or connection to it failed, you | ||
| 293 | will see an error message; in that case, try to troubleshoot the | ||
| 294 | problem as described in @ref{Troubleshooting Eglot}. Once a language | ||
| 295 | server was successfully started and Eglot connected to it, you can | ||
| 296 | immediately start using the Emacs features supported by Eglot, as | ||
| 297 | described in @ref{Eglot Features}. | ||
| 298 | |||
| 299 | A single Eglot session for a certain major-mode usually serves all the | ||
| 300 | buffers under that mode which visit files from the same project, so | ||
| 301 | you don't need to invoke @kbd{M-x eglot} again when you visit another | ||
| 302 | file from the same project which is edited using the same major-mode. | ||
| 303 | This is because Eglot uses the Emacs project infrastructure, as | ||
| 304 | described in @ref{Eglot and Buffers}, and this knows about files that | ||
| 305 | belong to the same project. Thus, after starting an Eglot session for | ||
| 306 | some buffer, that session is automatically reused when visiting files | ||
| 307 | in the same project with the same major-mode. | ||
| 308 | |||
| 309 | @findex eglot-ensure | ||
| 310 | Alternatively, you could configure Eglot to start automatically for | ||
| 311 | one or more major-modes from the respective mode hooks. Here's an | ||
| 312 | example for a hypothetical @code{foo-mode}: | ||
| 313 | |||
| 314 | @lisp | ||
| 315 | (add-hook 'foo-mode-hook 'eglot-ensure) | ||
| 316 | @end lisp | ||
| 317 | |||
| 318 | @noindent | ||
| 319 | The function @code{eglot-ensure} will start an Eglot session for each | ||
| 320 | buffer in which @code{foo-mode} is turned on, if there isn't already | ||
| 321 | an Eglot session that handles the buffer. Note that this variant of | ||
| 322 | starting an Eglot session is non-interactive, so it should be used | ||
| 323 | only when you are confident that Eglot can be started reliably for any | ||
| 324 | file which may be visited with the major-mode in question. | ||
| 325 | |||
| 326 | When Eglot connects to a language server for the first time in an | ||
| 327 | Emacs session, it runs the hook @code{eglot-connect-hook} | ||
| 328 | (@pxref{Eglot Variables}). | ||
| 329 | |||
| 330 | @node Shutting Down LSP Servers | ||
| 331 | @section Shutting Down LSP Servers | ||
| 332 | @cindex shutting down LSP server | ||
| 333 | |||
| 334 | When Eglot is turned on, it arranges for turning itself off | ||
| 335 | automatically if the language server process terminates. Turning off | ||
| 336 | Eglot means that it shuts down the server connection, ceases its | ||
| 337 | management of all the buffers that use the server connection which was | ||
| 338 | terminated, deactivates its minor mode, and restores the original | ||
| 339 | values of the Emacs variables that Eglot changed when it was turned | ||
| 340 | on. @xref{Eglot and Buffers}, for more details of what Eglot | ||
| 341 | management of a buffer entails. | ||
| 342 | |||
| 343 | @findex eglot-shutdown | ||
| 344 | You can also shut down a language server manually, by using the | ||
| 345 | command @kbd{M-x eglot-shutdown}. This prompts for the server (unless | ||
| 346 | there's only one connection and it's used in the current buffer), and | ||
| 347 | then shuts it down. By default, it also kills the server's events | ||
| 348 | buffer (@pxref{Troubleshooting Eglot}), but a prefix argument prevents | ||
| 349 | that. | ||
| 350 | |||
| 351 | Alternatively, you can customize the variable | ||
| 352 | @code{eglot-autoshutdown} to a non-@code{nil} value, in which case | ||
| 353 | Eglot will automatically shut down the language server process when | ||
| 354 | the last buffer served by that language server is killed. The default | ||
| 355 | of this variable is @code{nil}, so that visiting another file would | ||
| 356 | automatically activate Eglot even when the project which started Eglot | ||
| 357 | with the server no longer has any buffer associated with it. This | ||
| 358 | default allows you to start a server only once in each Emacs session. | ||
| 359 | |||
| 360 | @node Using Eglot | ||
| 361 | @chapter Using Eglot | ||
| 362 | |||
| 363 | This chapter describes in detail the features that Eglot provides and | ||
| 364 | how it does that. It also provides reference sections for Eglot | ||
| 365 | commands and variables. | ||
| 366 | |||
| 367 | @menu | ||
| 368 | * Eglot Features:: | ||
| 369 | * Eglot and Buffers:: | ||
| 370 | * Eglot Commands:: | ||
| 371 | * Eglot Variables:: | ||
| 372 | @end menu | ||
| 373 | |||
| 374 | @node Eglot Features | ||
| 375 | @section Eglot Features | ||
| 376 | @cindex features in buffers supported by Eglot | ||
| 377 | |||
| 378 | Once Eglot is enabled in a buffer, it uses LSP and the language-server | ||
| 379 | capabilities to activate, enable, and enhance modern IDE features in | ||
| 380 | Emacs. The features themselves are usually provided via other Emacs | ||
| 381 | packages. Here's the list of the main features that Eglot enables and | ||
| 382 | provides: | ||
| 383 | |||
| 384 | @itemize @bullet | ||
| 385 | @item | ||
| 386 | At-point documentation: when point is at or near a symbol or an | ||
| 387 | identifier, the information about the symbol/identifier, such as the | ||
| 388 | signature of a function or class method and server-generated | ||
| 389 | diagnostics, is made available via the ElDoc package (@pxref{Lisp | ||
| 390 | Doc,,, emacs, GNU Emacs Manual}). This allows major modes to provide | ||
| 391 | extensive help and documentation about the program identifiers. | ||
| 392 | |||
| 393 | @item | ||
| 394 | On-the-fly diagnostic annotations with server-suggested fixes, via the | ||
| 395 | Flymake package (@pxref{Top,,, flymake, GNU Flymake manual}). This | ||
| 396 | improves and enhances the Flymake diagnostics, replacing the other | ||
| 397 | Flymake backends. | ||
| 398 | |||
| 399 | @item | ||
| 400 | Finding definitions and uses of identifiers, via Xref (@pxref{Xref,,, | ||
| 401 | emacs, GNU Emacs Manual}). Eglot provides a backend for the Xref | ||
| 402 | capabilities which uses the language-server understanding of the | ||
| 403 | program source. In particular, it eliminates the need to generate | ||
| 404 | tags tables (@pxref{Tags tables,,, emacs, GNU Emacs Manual}) for | ||
| 405 | languages which are only supported by the @code{etags} backend. | ||
| 406 | |||
| 407 | @item | ||
| 408 | Buffer navigation by name of function, class, method, etc., via Imenu | ||
| 409 | (@pxref{Imenu,,, emacs, GNU Emacs Manual}). Eglot provides its own | ||
| 410 | variant of @code{imenu-create-index-function}, which generates the | ||
| 411 | index for the buffer based on language-server program source analysis. | ||
| 412 | |||
| 413 | @item | ||
| 414 | Enhanced completion of symbol at point by the | ||
| 415 | @code{completion-at-point} command (@pxref{Symbol Completion,,, emacs, | ||
| 416 | GNU Emacs Manual}). This uses the language-server's parser data for | ||
| 417 | the completion candidates. | ||
| 418 | |||
| 419 | @item | ||
| 420 | Automatic reformatting of source code as you type it. This is similar | ||
| 421 | to what the @code{eglot-format} command does (see below), but is | ||
| 422 | activated automatically as you type. | ||
| 423 | |||
| 424 | @item | ||
| 425 | If a completion package such as @code{company-mode}, a popular | ||
| 426 | third-party completion package (or any other completion package), is | ||
| 427 | installed, Eglot enhances it by providing completion candidates based | ||
| 428 | on the language-server analysis of the source code. | ||
| 429 | (@code{company-mode} can be installed from GNU ELPA.) | ||
| 430 | |||
| 431 | @item | ||
| 432 | If @code{yasnippet}, a popular third-party package for automatic | ||
| 433 | insertion of code templates (snippets), is installed, and the language | ||
| 434 | server supports snippet completion candidates, Eglot arranges for the | ||
| 435 | completion package to instantiate these snippets using | ||
| 436 | @code{yasnippet}. (@code{yasnippet} can be installed from GNU ELPA.) | ||
| 437 | |||
| 438 | @item | ||
| 439 | If the popular third-party package @code{markdown-mode} is installed, | ||
| 440 | and the server provides at-point documentation formatted as Markdown | ||
| 441 | in addition to plain text, Eglot arranges for the ElDoc package to | ||
| 442 | enrich this text with fontifications and other nice formatting before | ||
| 443 | displaying it to the user. This makes the documentation shown by | ||
| 444 | ElDoc look nicer on display. | ||
| 445 | |||
| 446 | @item | ||
| 447 | In addition to enabling and enhancing other features and packages, | ||
| 448 | Eglot also provides a small number of user commands based directly on | ||
| 449 | the capabilities of language servers. These commands are: | ||
| 450 | |||
| 451 | @table @kbd | ||
| 452 | @item M-x eglot-rename | ||
| 453 | This prompts for a new name for the symbol at point, and then modifies | ||
| 454 | all the project source files to rename the symbol to the new name, | ||
| 455 | based on editing data received from the language-server. @xref{Eglot | ||
| 456 | and Buffers}, for the details of how project files are defined. | ||
| 457 | |||
| 458 | @item M-x eglot-format | ||
| 459 | This reformats and prettifies the current active region according to | ||
| 460 | source formatting rules of the language-server. If the region is not | ||
| 461 | active, it reformats the entire buffer instead. | ||
| 462 | |||
| 463 | @item M-x eglot-format-buffer | ||
| 464 | This reformats and prettifies the current buffer according to source | ||
| 465 | formatting rules of the language-server. | ||
| 466 | |||
| 467 | @cindex code actions | ||
| 468 | @item M-x eglot-code-actions | ||
| 469 | @itemx M-x eglot-code-action-organize-imports | ||
| 470 | @itemx M-x eglot-code-action-quickfix | ||
| 471 | @itemx M-x eglot-code-action-extract | ||
| 472 | @itemx M-x eglot-code-action-inline | ||
| 473 | @itemx M-x eglot-code-action-rewrite | ||
| 474 | These command allow you to invoke the so-called @dfn{code actions}: | ||
| 475 | requests for the language-server to provide editing commands for | ||
| 476 | various code fixes, typically either to fix an error diagnostic or to | ||
| 477 | beautify/refactor code. For example, | ||
| 478 | @code{eglot-code-action-organize-imports} rearranges the program | ||
| 479 | @dfn{imports}---declarations of modules whose capabilities the program | ||
| 480 | uses. These commands affect all the files that belong to the | ||
| 481 | project. The command @kbd{M-x eglot-code-actions} will pop up a menu | ||
| 482 | of code applicable actions at point. | ||
| 483 | @end table | ||
| 484 | |||
| 485 | @end itemize | ||
| 486 | |||
| 487 | Not all servers support the full set of LSP capabilities, but most of | ||
| 488 | them support enough to enable the basic set of features mentioned | ||
| 489 | above. Conversely, some servers offer capabilities for which no | ||
| 490 | equivalent Emacs package exists yet, and so Eglot cannot (yet) expose | ||
| 491 | these capabilities to Emacs users. | ||
| 492 | |||
| 493 | @node Eglot and Buffers | ||
| 494 | @section Buffers, Projects, and Eglot | ||
| 495 | @cindex buffers managed by Eglot | ||
| 496 | @cindex projects and Eglot | ||
| 497 | |||
| 498 | @cindex workspace | ||
| 499 | One of the main strong points of using a language server is that a | ||
| 500 | language server has a broad view of the program: it considers more | ||
| 501 | than just the single source file you are editing. Ideally, the | ||
| 502 | language server should know about all the source files of your program | ||
| 503 | which are written in the language supported by the server. In the | ||
| 504 | language-server parlance, the set of the source files of a program is | ||
| 505 | known as a @dfn{workspace}. The Emacs equivalent of a workspace is a | ||
| 506 | @dfn{project} (@pxref{Projects,,, emacs, GNU Emacs Manual}). Eglot | ||
| 507 | fully supports Emacs projects, and considers the file in whose buffer | ||
| 508 | Eglot is turned on as belonging to a project. In the simplest case, | ||
| 509 | that file is the entire project, i.e.@: your project consists of a | ||
| 510 | single file. But there are other more complex projects: | ||
| 511 | |||
| 512 | @itemize @bullet | ||
| 513 | @item | ||
| 514 | A single-directory project: several source files in a single common | ||
| 515 | directory. | ||
| 516 | |||
| 517 | @item | ||
| 518 | A VC project: source files in a directory hierarchy under some VCS, | ||
| 519 | e.g.@: a VCS repository (@pxref{Version Control,,, emacs, GNU Emacs | ||
| 520 | Manual}). | ||
| 521 | |||
| 522 | @item | ||
| 523 | An EDE project: source files in a directory hierarchy managed via the | ||
| 524 | Emacs Development Environment (@pxref{EDE,,, emacs, GNU Emacs | ||
| 525 | Manual}). | ||
| 526 | @end itemize | ||
| 527 | |||
| 528 | Eglot uses the Emacs's project management infrastructure to figure out | ||
| 529 | which files and buffers belong to what project, so any kind of project | ||
| 530 | supported by that infrastructure is automatically supported by Eglot. | ||
| 531 | |||
| 532 | When Eglot starts a server program, it does so in the project's root | ||
| 533 | directory, which is usually the top-level directory of the project's | ||
| 534 | directory hierarchy. This ensures the language server has the same | ||
| 535 | comprehensive view of the project's files as you do. | ||
| 536 | |||
| 537 | For example, if you visit the file @file{~/projects/fooey/lib/x.foo} | ||
| 538 | and @file{x.foo} belongs to a project rooted at | ||
| 539 | @file{~/projects/fooey} (perhaps because a @file{.git} directory | ||
| 540 | exists there), then @kbd{M-x eglot} causes the server program to start | ||
| 541 | with that root as the current working directory. The server then will | ||
| 542 | analyze not only the file @file{lib/x.foo} you visited, but likely | ||
| 543 | also all the other @file{*.foo} files under the | ||
| 544 | @file{~/projects/fooey} directory. | ||
| 545 | |||
| 546 | In some cases, additional information specific to a given project will | ||
| 547 | need to be provided to the language server when starting it. The | ||
| 548 | variable @code{eglot-workspace-configuration} (@pxref{Customizing | ||
| 549 | Eglot}) exists for that purpose. It specifies the parameters and | ||
| 550 | their values to communicate to each language server which needs that. | ||
| 551 | |||
| 552 | When Eglot is active for a project, it performs several background | ||
| 553 | activities on behalf of the project and its buffers: | ||
| 554 | |||
| 555 | @itemize @bullet | ||
| 556 | @cindex mode-line indication of language server | ||
| 557 | @cindex mouse clicks on mode-line, and Eglot | ||
| 558 | @vindex eglot-menu | ||
| 559 | @item | ||
| 560 | All of the project's file-visiting buffers under the same major-mode | ||
| 561 | are served by a single language-server connection. (If the project | ||
| 562 | uses several programming languages, there will usually be a separate | ||
| 563 | server connection for each group of files written in the same language | ||
| 564 | and using the same Emacs major-mode.) Eglot adds the | ||
| 565 | @samp{[eglot:@var{project}]} indication to the mode line of | ||
| 566 | each such buffer, where @var{server} is the name of the server and | ||
| 567 | @var{project} identifies the project by its root directory. Clicking | ||
| 568 | the mouse on the Eglot mode-line indication activates a menu with | ||
| 569 | server-specific items. | ||
| 570 | |||
| 571 | @item | ||
| 572 | For each buffer in which Eglot is active, it notifies the language | ||
| 573 | server that Eglot is @dfn{managing} the file visited by that buffer. | ||
| 574 | This tells the language server that the file's contents on disk may no | ||
| 575 | longer be up-to-date due to unsaved edits. Eglot reports to the | ||
| 576 | server any changes in the text of each managed buffer, to make the | ||
| 577 | server aware of unsaved changes. This includes your editing of the | ||
| 578 | buffer and also changes done automatically by other Emacs features and | ||
| 579 | commands. Killing a buffer relinquishes its management by Eglot and | ||
| 580 | notifies the server that the file on disk is up-to-date. | ||
| 581 | |||
| 582 | @vindex eglot-managed-mode-hook | ||
| 583 | @vindex eglot-managed-p | ||
| 584 | @item | ||
| 585 | Eglot turns on a special minor mode in each buffer it manages. This | ||
| 586 | minor mode ensures the server is notified about files Eglot manages, | ||
| 587 | and also arranges for other Emacs features supported by Eglot | ||
| 588 | (@pxref{Eglot Features}) to receive information from the language | ||
| 589 | server, by changing the settings of these features. Unlike other | ||
| 590 | minor-modes, this special minor mode is not activated manually by the | ||
| 591 | user, but automatically as result of starting an Eglot session for the | ||
| 592 | buffer. However, this minor mode provides a hook variable | ||
| 593 | @code{eglot-managed-mode-hook} that can be used to customize the Eglot | ||
| 594 | management of the buffer. This hook is run both when the minor mode | ||
| 595 | is turned on and when it's turned off; use the variable | ||
| 596 | @code{eglot-managed-p} to tell if current buffer is still being | ||
| 597 | managed or not. When Eglot stops managing the buffer, this minor mode | ||
| 598 | is turned off, and all the settings that Eglot changed are restored to | ||
| 599 | their original values. | ||
| 600 | |||
| 601 | @item | ||
| 602 | When you visit a file under the same project, whether an existing or a | ||
| 603 | new file, its buffer is automatically added to the set of buffers | ||
| 604 | managed by Eglot, and the server which supports the buffer's | ||
| 605 | major-mode is notified about that. Thus, visiting a non-existent file | ||
| 606 | @file{/home/joe/projects/fooey/lib/y.foo} in the above example will | ||
| 607 | notify the server of the @file{*.foo} files' language that a new file | ||
| 608 | was added to the project, even before the file appears on disk. The | ||
| 609 | special Eglot minor mode is also turned on automatically in the buffer | ||
| 610 | visiting the file. | ||
| 611 | @end itemize | ||
| 612 | |||
| 613 | @node Eglot Commands | ||
| 614 | @section Eglot Commands | ||
| 615 | @cindex commands, Eglot | ||
| 616 | |||
| 617 | This section provides a reference of the most commonly used Eglot | ||
| 618 | commands: | ||
| 619 | |||
| 620 | @ftable @code | ||
| 621 | @item M-x eglot | ||
| 622 | This command adds the current buffer and the file it visits to the | ||
| 623 | group of buffers and files managed by Eglot on behalf of a suitable | ||
| 624 | language server. If a language server for the buffer's | ||
| 625 | @code{major-mode} (@pxref{Major Modes,,, emacs, GNU Emacs Manual}) is | ||
| 626 | not yet running, it will be started; otherwise the buffer and its file | ||
| 627 | will be added to those managed by an existing server session. | ||
| 628 | |||
| 629 | The command attempts to figure out the buffer's major mode and the | ||
| 630 | suitable language server; in case it fails, it might prompt for the | ||
| 631 | major mode to use and for the server program to start. If invoked | ||
| 632 | with @kbd{C-u}, it always prompts for the server program, and if | ||
| 633 | invoked with @kbd{C-u C-u}, it also prompts for the major mode. | ||
| 634 | |||
| 635 | If the language server is successfully started and contacted, this | ||
| 636 | command arranges for any other buffers belonging to the same project | ||
| 637 | and using the same major mode to use the same language-server session. | ||
| 638 | That includes any buffers created by visiting files after this command | ||
| 639 | succeeds to connect to a language server. | ||
| 640 | |||
| 641 | All the Emacs features that are capable of using Eglot services | ||
| 642 | (@pxref{Eglot Features}) are automatically configured by this command | ||
| 643 | to start using the language server via Eglot. To customize which | ||
| 644 | Emacs features will be configured to use Eglot, use the | ||
| 645 | @code{eglot-stay-out-of} option (@pxref{Customizing Eglot}). | ||
| 646 | |||
| 647 | @item M-x eglot-reconnect | ||
| 648 | This command shuts down the current connection to the language | ||
| 649 | server and immediately restarts it using the same options used | ||
| 650 | originally. This can sometimes be useful to unclog a partially | ||
| 651 | malfunctioning server connection. | ||
| 652 | |||
| 653 | @item M-x eglot-shutdown | ||
| 654 | This command shuts down a language server. It prompts for a language | ||
| 655 | server to shut down (unless there's only one server session, and it | ||
| 656 | manages the current buffer). Then the command shuts down the server | ||
| 657 | and stops managing the buffers the server was used for. Emacs | ||
| 658 | features (@pxref{Eglot Features}) that Eglot configured to work with | ||
| 659 | the language server are restored back to their original configuration. | ||
| 660 | |||
| 661 | Normally, this command kills the buffers used for communicating with | ||
| 662 | the language server, but if invoked with a prefix argument @kbd{C-u}, | ||
| 663 | the command doesn't kill those buffers, allowing them to be used for | ||
| 664 | diagnostics and problem reporting (@pxref{Troubleshooting Eglot}). | ||
| 665 | |||
| 666 | @item M-x eglot-shutdown-all | ||
| 667 | This command shuts down all the language servers active in the current | ||
| 668 | Emacs session. As with @code{eglot-shutdown}, invoking this command | ||
| 669 | with a prefix argument avoids killing the buffers used for | ||
| 670 | communications with the language servers. | ||
| 671 | |||
| 672 | @item M-x eglot-rename | ||
| 673 | This command renames the program symbol (a.k.a.@: @dfn{identifier}) at | ||
| 674 | point to another name. It prompts for the new name of the symbol, and | ||
| 675 | then modifies all the files in the project which arte managed by the | ||
| 676 | language server of the current buffer to implement the renaming. | ||
| 677 | |||
| 678 | @item M-x eglot-format | ||
| 679 | This command reformats the active region according to the | ||
| 680 | language-server rules. If no region is active, it reformats the | ||
| 681 | entire current buffer. | ||
| 682 | |||
| 683 | @item M-x eglot-format-buffer | ||
| 684 | This command reformats the current buffer, in the same manner as | ||
| 685 | @code{eglot-format} does. | ||
| 686 | |||
| 687 | @item M-x eglot-code-actions | ||
| 688 | @itemx mouse-1 | ||
| 689 | This command asks the server for any @dfn{code actions} applicable at | ||
| 690 | point. It can also be invoked by @kbd{mouse-1} clicking on | ||
| 691 | diagnostics provided by the server. | ||
| 692 | |||
| 693 | @item M-x eglot-code-action-organize-imports | ||
| 694 | @itemx M-x eglot-code-action-quickfix | ||
| 695 | @itemx M-x eglot-code-action-extract | ||
| 696 | @itemx M-x eglot-code-action-inline | ||
| 697 | @itemx M-x eglot-code-action-rewrite | ||
| 698 | These commands invoke specific code actions supported by the language | ||
| 699 | server. | ||
| 700 | @c FIXME: Need more detailed description of each action. | ||
| 701 | @end ftable | ||
| 702 | |||
| 703 | The following Eglot commands are used less commonly, mostly for | ||
| 704 | diagnostic and troubleshooting purposes: | ||
| 705 | |||
| 706 | @ftable @code | ||
| 707 | @item M-x eglot-events-buffer | ||
| 708 | This command pops up the events buffer used for communication with the | ||
| 709 | language server of the current buffer. | ||
| 710 | |||
| 711 | @item M-x eglot-stderr-buffer | ||
| 712 | This command pops up the buffer with the debug info printed by the | ||
| 713 | language server to its standard error stream. | ||
| 714 | |||
| 715 | @item M-x eglot-forget-pending-continuations | ||
| 716 | Forget pending requests for the server of the current buffer. | ||
| 717 | @c FIXME: Better description of the need. | ||
| 718 | |||
| 719 | @item M-x eglot-signal-didChangeConfiguration | ||
| 720 | This command updates the language server configuration according to | ||
| 721 | the current value of the variable @code{eglot-workspace-configuration} | ||
| 722 | (@pxref{Customizing Eglot}). | ||
| 723 | |||
| 724 | @item M-x eglot-clear-status | ||
| 725 | Clear the last JSONRPC error for the server of the current buffer. | ||
| 726 | Eglot keeps track of erroneous situations encountered by the server in | ||
| 727 | its mode-line indication so that the user may inspect the | ||
| 728 | communication leading up to it (@pxref{Troubleshooting Eglot}). If | ||
| 729 | the situation is deemed uninteresting or temporary, this command can | ||
| 730 | be used to ``forget'' the error. Note that the command @code{M-x | ||
| 731 | eglot-reconnect} can sometimes be used to unclog a temporarily | ||
| 732 | malfunctioning server. | ||
| 733 | @end ftable | ||
| 734 | |||
| 735 | As described in @ref{Eglot Features} most features associated with | ||
| 736 | Eglot are actually provided by other Emacs packages and features, and | ||
| 737 | Eglot only enhances them by allowing them to use the information | ||
| 738 | coming from the language servers. For completeness, here's the list | ||
| 739 | of commands of those other packages that are very commonly used in | ||
| 740 | Eglot-managed buffers: | ||
| 741 | |||
| 742 | @c Not @ftable, because the index entries should mention Eglot | ||
| 743 | @table @code | ||
| 744 | @cindex eldoc, and Eglot | ||
| 745 | @cindex documentation using Eglot | ||
| 746 | @item M-x eldoc | ||
| 747 | Ask the ElDoc system for help at point. | ||
| 748 | |||
| 749 | @cindex flymake, and Eglot | ||
| 750 | @cindex on-the-fly diagnostics using Eglot | ||
| 751 | @item M-x flymake-show-buffer-diagnostics | ||
| 752 | Ask Flymake system to display diagnostics for the current buffer. | ||
| 753 | |||
| 754 | @item M-x flymake-show-project-diagnostics | ||
| 755 | Ask Flymake to list diagnostics for all the files in the current | ||
| 756 | project. | ||
| 757 | |||
| 758 | @cindex xref, and Eglot | ||
| 759 | @cindex finding definitions of identifiers using Eglot | ||
| 760 | @item M-x xref-find-definitions | ||
| 761 | Ask Xref to go the definition of the identifier at point. | ||
| 762 | |||
| 763 | @cindex imenu navigation using Eglot | ||
| 764 | @item M-x imenu | ||
| 765 | Let the user navigate the program source code using buffer index, | ||
| 766 | categorizing program elements by syntactic class (class, method, | ||
| 767 | variable, etc.) and offering completion. | ||
| 768 | |||
| 769 | @cindex symbol completion using Eglot | ||
| 770 | @item M-x completion-at-point | ||
| 771 | Request completion of the symbol at point. | ||
| 772 | @end table | ||
| 773 | |||
| 774 | @node Eglot Variables | ||
| 775 | @section Eglot Variables | ||
| 776 | @cindex variables, Eglot | ||
| 777 | |||
| 778 | This section provides a reference of the Eglot' user options. | ||
| 779 | |||
| 780 | @vtable @code | ||
| 781 | @item eglot-autoreconnect | ||
| 782 | This option controls the ability to reconnect automatically to the | ||
| 783 | language server when Eglot detects that the server process terminated | ||
| 784 | unexpectedly. The default value 3 means to attempt reconnection only | ||
| 785 | if the previous successful connection lasted for more than that number | ||
| 786 | of seconds; a different positive value changes the minimal length of | ||
| 787 | the connection to trigger reconnection. A value of @code{t} means | ||
| 788 | always reconnect automatically, and @code{nil} means never reconnect | ||
| 789 | (in which case you will need to reconnect manually using @kbd{M-x | ||
| 790 | eglot}). | ||
| 791 | |||
| 792 | @item eglot-connect-timeout | ||
| 793 | This specifies the number of seconds before connection attempt to a | ||
| 794 | language server times out. The value of @code{nil} means never time | ||
| 795 | out. The default is 30 seconds. | ||
| 796 | |||
| 797 | @item eglot-sync-connect | ||
| 798 | This setting is mainly important for connections which are slow to | ||
| 799 | establish. Whereas the variable @code{eglot-connect-timeout} controls | ||
| 800 | how long to wait for, this variable controls whether to block Emacs's | ||
| 801 | user interface while waiting. The default value is 3; a positive | ||
| 802 | value means block for that many seconds, then wait for the connection | ||
| 803 | in the background. The value of @code{t} means block during the whole | ||
| 804 | waiting period. The value of @code{nil} or zero means don't block at | ||
| 805 | all during the waiting period. | ||
| 806 | |||
| 807 | @item eglot-events-buffer-size | ||
| 808 | This determines the size of the Eglot events buffer. @xref{Eglot | ||
| 809 | Commands, eglot-events-buffer}, for how to display that buffer. If | ||
| 810 | the value is changed, for it to take effect the connection should be | ||
| 811 | restarted using @kbd{M-x eglot-reconnect}. | ||
| 812 | @c FIXME: Shouldn't the defcustom do this by itself using the :set | ||
| 813 | @c attribute? | ||
| 814 | @xref{Troubleshooting Eglot}, for when this could be useful. | ||
| 815 | |||
| 816 | @item eglot-autoshutdown | ||
| 817 | If this is non-@code{nil}, Eglot shuts down a language server when the | ||
| 818 | last buffer managed by it is killed. @xref{Shutting Down LSP Servers}. | ||
| 819 | The default is @code{nil}; if you want to shut down a server, use | ||
| 820 | @kbd{M-x eglot-shutdown} (@pxref{Eglot Commands}). | ||
| 821 | |||
| 822 | @item eglot-confirm-server-initiated-edits | ||
| 823 | Various Eglot commands and code actions result in the language server | ||
| 824 | sending editing commands to Emacs. If this option's value is | ||
| 825 | non-@code{nil} (the default), Eglot will ask for confirmation before | ||
| 826 | performing edits initiated by the server or edits whose scope affects | ||
| 827 | buffers other than the one where the user initiated the request. | ||
| 828 | |||
| 829 | @item eglot-ignored-server-capabilities | ||
| 830 | This variable's value is a list of language server capabilities that | ||
| 831 | Eglot should not use. The default is @code{nil}: Eglot uses all of | ||
| 832 | the capabilities supported by each server. | ||
| 833 | |||
| 834 | @item eglot-extend-to-xref | ||
| 835 | If this is non-@code{nil}, and @kbd{M-.} | ||
| 836 | (@code{xref-find-definitions}) lands you in a file outside of your | ||
| 837 | project, such as a system-installed library or header file, | ||
| 838 | transiently consider that file as managed by the same language server. | ||
| 839 | That file is still outside your project (i.e. @code{project-find-file} | ||
| 840 | won't find it), but Eglot and the server will consider it to be part | ||
| 841 | of the workspace. The default is @code{nil}. | ||
| 842 | |||
| 843 | @item eglot-mode-map | ||
| 844 | This variable is the keymap for binding Eglot-related command. It is | ||
| 845 | in effect only as long as the buffer is managed by Eglot. By default, | ||
| 846 | it is empty, with the single exception: @kbd{C-h .} is remapped to | ||
| 847 | invoke @code{eldoc-doc-buffer}. You can bind additional commands in | ||
| 848 | this map. For example: | ||
| 849 | |||
| 850 | @lisp | ||
| 851 | (define-key eglot-mode-map (kbd "C-c r") 'eglot-rename) | ||
| 852 | (define-key eglot-mode-map (kbd "C-c o") 'eglot-code-action-organize-imports) | ||
| 853 | (define-key eglot-mode-map (kbd "C-c h") 'eldoc) | ||
| 854 | (define-key eglot-mode-map (kbd "<f6>") 'xref-find-definitions) | ||
| 855 | @end lisp | ||
| 856 | |||
| 857 | @end vtable | ||
| 858 | |||
| 859 | Additional variables, which are relevant for customizing the server | ||
| 860 | connections, are documented in @ref{Customizing Eglot}. | ||
| 861 | |||
| 862 | @node Customizing Eglot | ||
| 863 | @chapter Customizing Eglot | ||
| 864 | @cindex customizing Eglot | ||
| 865 | |||
| 866 | Eglot itself has a relatively small number of customization options. | ||
| 867 | A large part of customizing Eglot to your needs and preferences should | ||
| 868 | actually be done via options of the Emacs packages and features which | ||
| 869 | Eglot supports and enhances (@pxref{Eglot Features}). For example: | ||
| 870 | |||
| 871 | @itemize @bullet | ||
| 872 | @item | ||
| 873 | To configure the face used for server-derived errors and warnings, | ||
| 874 | customize the Flymake faces @code{flymake-error} and | ||
| 875 | @code{flymake-error}. | ||
| 876 | |||
| 877 | @item | ||
| 878 | To configure the amount of space taken up by documentation in the | ||
| 879 | echo area, customize the ElDoc variable | ||
| 880 | @code{eldoc-echo-area-use-multiline-p}. | ||
| 881 | |||
| 882 | @item | ||
| 883 | To completely change how ElDoc displays the at-point documentation | ||
| 884 | destination, customize the ElDoc variable | ||
| 885 | @code{eldoc-display-functions}. | ||
| 886 | @end itemize | ||
| 887 | |||
| 888 | For this reason, this manual describes only how to customize the | ||
| 889 | Eglot's own operation, which mainly has to do with the server | ||
| 890 | connections and the server features to be used by Eglot. | ||
| 891 | |||
| 892 | @c @table, not @vtable, because some of the variables are indexed | ||
| 893 | @c elsewhere | ||
| 894 | @table @code | ||
| 895 | @item eglot-server-programs | ||
| 896 | This variable determines which language server to start for each | ||
| 897 | supported major mode, and how to invoke that server's program. | ||
| 898 | @xref{Setting Up LSP Servers}, for the details. | ||
| 899 | |||
| 900 | @vindex eglot-strict-mode | ||
| 901 | @item eglot-strict-mode | ||
| 902 | This is @code{nil} by default, meaning that Eglot is generally lenient | ||
| 903 | about non-conforming servers. If you need to debug a server, set this | ||
| 904 | to @w{@code{(disallow-non-standard-keys enforce-required-keys)}}. | ||
| 905 | |||
| 906 | @vindex eglot-server-initialized-hook | ||
| 907 | @item eglot-server-initialized-hook | ||
| 908 | A hook run after the server object is successfully initialized. | ||
| 909 | |||
| 910 | @vindex eglot-connect-hook | ||
| 911 | @item eglot-connect-hook | ||
| 912 | A hook run after connection to the server is successfully | ||
| 913 | established. @xref{Starting Eglot}. | ||
| 914 | |||
| 915 | @item eglot-managed-mode-hook | ||
| 916 | A hook run after Eglot started or stopped managing a buffer. | ||
| 917 | @xref{Eglot and Buffers}, for details of its usage. | ||
| 918 | |||
| 919 | @vindex eglot-stay-out-of | ||
| 920 | @item eglot-stay-out-of | ||
| 921 | This variable's value lists Emacs features that Eglot shouldn't | ||
| 922 | automatically try to manage on user's behalf. It is useful, for | ||
| 923 | example, when you need to use non-LSP Flymake or Company back-ends. | ||
| 924 | To have Eglot stay away of some Emacs feature, add that feature's | ||
| 925 | symbol or a regexp that will match a symbol's name to the list: for | ||
| 926 | example, the symbol @code{xref} to leave Xref alone, or the string | ||
| 927 | @samp{company} to stay away of your Company customizations. Here's an | ||
| 928 | example: | ||
| 929 | |||
| 930 | @lisp | ||
| 931 | (add-to-list 'eglot-stay-out-of 'flymake) | ||
| 932 | @end lisp | ||
| 933 | |||
| 934 | Note that you can still configure the excluded Emacs features manually | ||
| 935 | to use Eglot in your @code{eglot-managed-mode-hook} or via some other | ||
| 936 | mechanism. | ||
| 937 | |||
| 938 | @vindex eglot-workspace-configuration | ||
| 939 | @cindex server workspace configuration | ||
| 940 | @item eglot-workspace-configuration | ||
| 941 | This variable is meant to be set in the @file{.dir-locals.el} file, to | ||
| 942 | provide per-project settings, as described below in more detail. | ||
| 943 | @end table | ||
| 944 | |||
| 945 | Some language servers need to know project-specific settings, which | ||
| 946 | the LSP calls @dfn{workspace configuration}. Eglot allows such fine | ||
| 947 | tuning of per-project settings via the variable | ||
| 948 | @code{eglot-workspace-configuration}. Eglot sends the portion of the | ||
| 949 | settings contained in this variable to each server for which such | ||
| 950 | settings were defined in the variable. These settings are | ||
| 951 | communicated to the server initially (upon establishing the | ||
| 952 | connection) or when the settings are changed, or in response to the | ||
| 953 | configuration request from the server. | ||
| 954 | |||
| 955 | In many cases, servers can be configured globally using a | ||
| 956 | configuration file in the user's home directory or in the project | ||
| 957 | directory, which the language server reads. For example, the | ||
| 958 | @command{pylsp} server for Python reads the file | ||
| 959 | @file{~/.config/pycodestyle} and the @command{clangd} server reads the | ||
| 960 | file @file{.clangd} anywhere in the current project's directory tree. | ||
| 961 | If possible, we recommend to use these configuration files that are | ||
| 962 | independent of Eglot and Emacs; they have the advantage that they will | ||
| 963 | work with other LSP clients as well. | ||
| 964 | |||
| 965 | If you do need to provide Emacs-specific configuration for a language | ||
| 966 | server, we recommend to define the appropriate value in the | ||
| 967 | @file{.dir-locals.el} file in the project's directory. The value of | ||
| 968 | this variable should be a property list of the following format: | ||
| 969 | |||
| 970 | @lisp | ||
| 971 | (:@var{server} @var{plist}@dots{}) | ||
| 972 | @end lisp | ||
| 973 | |||
| 974 | @noindent | ||
| 975 | Here @code{:@var{server}} identifies a particular language server and | ||
| 976 | @var{plist} is the corresponding keyword-value property list of one or | ||
| 977 | more parameter settings for that server, serialized by Eglot as a JSON | ||
| 978 | object. @var{plist} may be arbitrarity complex, generally containing | ||
| 979 | other keywork-value property sublists corresponding to JSON subobjects. | ||
| 980 | The JSON values @code{true}, @code{false}, @code{null} and @code{@{@}} | ||
| 981 | are represented by the Lisp values @code{t}, @code{:json-false}, | ||
| 982 | @code{nil}, and @code{eglot-@{@}}, respectively. | ||
| 983 | |||
| 984 | @findex eglot-show-workspace-configuration | ||
| 985 | When experimenting with workspace settings, you can use the command | ||
| 986 | @kbd{M-x eglot-show-workspace-configuration} to inspect and debug the | ||
| 987 | JSON value to be sent to the server. This helper command works even | ||
| 988 | before actually connecting to the server. | ||
| 989 | |||
| 990 | Here's an example of defining the workspace-configuration settings for | ||
| 991 | a project that uses two different language servers, one for Python, | ||
| 992 | whose server is @command{pylsp}, the other one for Go, with | ||
| 993 | @command{gopls} as its server (presumably, the project is written in a | ||
| 994 | combination of these two languages): | ||
| 995 | |||
| 996 | @lisp | ||
| 997 | ((python-mode | ||
| 998 | . ((eglot-workspace-configuration | ||
| 999 | . (:pylsp (:plugins (:jedi_completion (:include_params t | ||
| 1000 | :fuzzy t) | ||
| 1001 | :pylint (:enabled :json-false))))))) | ||
| 1002 | (go-mode | ||
| 1003 | . ((eglot-workspace-configuration | ||
| 1004 | . (:gopls (:usePlaceholders t)))))) | ||
| 1005 | @end lisp | ||
| 1006 | |||
| 1007 | @noindent | ||
| 1008 | This should go into the @file{.dir-locals.el} file in the project's | ||
| 1009 | root directory. It sets up the value of | ||
| 1010 | @code{eglot-workspace-configuration} separately for each major mode. | ||
| 1011 | |||
| 1012 | Alternatively, the same configuration could be defined as follows: | ||
| 1013 | |||
| 1014 | @lisp | ||
| 1015 | ((nil | ||
| 1016 | . ((eglot-workspace-configuration | ||
| 1017 | . (:pylsp (:plugins (:jedi_completion (:include_params t | ||
| 1018 | :fuzzy t) | ||
| 1019 | :pylint (:enabled :json-false))) | ||
| 1020 | :gopls (:usePlaceholders t)))))) | ||
| 1021 | @end lisp | ||
| 1022 | |||
| 1023 | This is an equivalent setup which sets the value for all the | ||
| 1024 | major-modes inside the project; Eglot will use for each server only | ||
| 1025 | the section of the parameters intended for that server. | ||
| 1026 | |||
| 1027 | As yet another alternative, you can set the value of | ||
| 1028 | @code{eglot-workspace-configuration} programmatically, via the | ||
| 1029 | @code{dir-locals-set-class-variables} function, @pxref{Directory Local | ||
| 1030 | Variables,,, elisp, GNU Emacs Lisp Reference Manual}. | ||
| 1031 | |||
| 1032 | Finally, if one needs to determine the workspace configuration based | ||
| 1033 | on some dynamic context, @code{eglot-workspace-configuration} can be | ||
| 1034 | set to a function. The function is called with the | ||
| 1035 | @code{eglot-lsp-server} instance of the connected server (if any) and | ||
| 1036 | with @code{default-directory} set to the root of the project. The | ||
| 1037 | function should return a value of the form described above. | ||
| 1038 | |||
| 1039 | Some servers need special hand-holding to operate correctly. If your | ||
| 1040 | server has some quirks or non-conformity, it's possible to extend | ||
| 1041 | Eglot via Elisp to adapt to it, by defining a suitable | ||
| 1042 | @code{eglot-initialization-options} method via @code{cl-defmethod} | ||
| 1043 | (@pxref{Generic Functions,,, elisp, GNU Emacs Lisp Reference Manual}). | ||
| 1044 | |||
| 1045 | Here's an example: | ||
| 1046 | |||
| 1047 | @lisp | ||
| 1048 | (add-to-list 'eglot-server-programs | ||
| 1049 | '((c++ mode c-mode) . (eglot-cquery "cquery"))) | ||
| 1050 | |||
| 1051 | (defclass eglot-cquery (eglot-lsp-server) () | ||
| 1052 | :documentation "A custom class for cquery's C/C++ langserver.") | ||
| 1053 | |||
| 1054 | (cl-defmethod eglot-initialization-options ((server eglot-cquery)) | ||
| 1055 | "Passes through required cquery initialization options" | ||
| 1056 | (let* ((root (car (project-roots (eglot--project server)))) | ||
| 1057 | (cache (expand-file-name ".cquery_cached_index/" root))) | ||
| 1058 | (list :cacheDirectory (file-name-as-directory cache) | ||
| 1059 | :progressReportFrequencyMs -1))) | ||
| 1060 | @end lisp | ||
| 1061 | |||
| 1062 | @noindent | ||
| 1063 | See the doc string of @code{eglot-initialization-options} for more | ||
| 1064 | details. | ||
| 1065 | @c FIXME: The doc string of eglot-initialization-options should be | ||
| 1066 | @c enhanced and extended. | ||
| 1067 | |||
| 1068 | @node Troubleshooting Eglot | ||
| 1069 | @chapter Troubleshooting Eglot | ||
| 1070 | @cindex troubleshooting Eglot | ||
| 1071 | |||
| 1072 | This section documents commands and variables that can be used to | ||
| 1073 | troubleshoot Eglot problems. It also provides guidelines for | ||
| 1074 | reporting Eglot bugs in a way that facilitates their resolution. | ||
| 1075 | |||
| 1076 | When you encounter problems with Eglot, try first using the commands | ||
| 1077 | @kbd{M-x eglot-events-server} and @kbd{M-x eglot-stderr-buffer}. They | ||
| 1078 | pop up special buffers that can be used to inspect the communications | ||
| 1079 | between the Eglot and language server. In many cases, this will | ||
| 1080 | indicate the problems or at least provide a hint. | ||
| 1081 | |||
| 1082 | A common and easy-to-fix cause of performance problems is the length | ||
| 1083 | of these two buffers. If Eglot is operating correctly but slowly, | ||
| 1084 | customize the variable @code{eglot-events-buffer-size} (@pxref{Eglot | ||
| 1085 | Variables}) to limit logging, and thus speed things up. | ||
| 1086 | |||
| 1087 | If you need to report an Eglot bug, please keep in mind that, because | ||
| 1088 | there are so many variables involved, it is generally both very | ||
| 1089 | @emph{difficult} and @emph{absolutely essential} to reproduce bugs | ||
| 1090 | exactly as they happened to you, the user. Therefore, every bug | ||
| 1091 | report should include: | ||
| 1092 | |||
| 1093 | @enumerate | ||
| 1094 | @item | ||
| 1095 | The transcript of events obtained from the buffer popped up by | ||
| 1096 | @kbd{M-x eglot-events-buffer}. If the transcript can be narrowed down | ||
| 1097 | to show the problematic exchange, so much the better. This is | ||
| 1098 | invaluable for the investigation and reproduction of the problem. | ||
| 1099 | |||
| 1100 | @item | ||
| 1101 | If Emacs signaled an error (an error message was seen or heard), make | ||
| 1102 | sure to repeat the process after toggling @code{debug-on-error} on | ||
| 1103 | (via @kbd{M-x toggle-debug-on-error}). This normally produces a | ||
| 1104 | backtrace of the error that should also be attached to the bug report. | ||
| 1105 | |||
| 1106 | @item | ||
| 1107 | An explanation how to obtain and install the language server you used. | ||
| 1108 | If possible, try to replicate the problem with the C/C@t{++} or Python | ||
| 1109 | servers, as these are very easy to install. | ||
| 1110 | |||
| 1111 | @item | ||
| 1112 | A description of how to setup the @emph{minimal} project (one or two | ||
| 1113 | files and their contents) where the problem happens. | ||
| 1114 | |||
| 1115 | @item | ||
| 1116 | A recipe to replicate the problem with @emph{a clean Emacs run}. This | ||
| 1117 | means @kbd{emacs -Q} invocation or a very minimal (no more that 10 | ||
| 1118 | lines) @file{.emacs} initialization file. @code{eglot-ensure} and | ||
| 1119 | @code{use-package} calls are generally @emph{not} needed. | ||
| 1120 | |||
| 1121 | @item | ||
| 1122 | Make sure to double check all the above elements and re-run the | ||
| 1123 | recipe to see that the problem is reproducible. | ||
| 1124 | @end enumerate | ||
| 1125 | |||
| 1126 | Please keep in mind that some problems reported against Eglot may | ||
| 1127 | actually be bugs in the language server or the Emacs feature/package | ||
| 1128 | that used Eglot to communicate with the language server. | ||
| 1129 | |||
| 1130 | @node GNU Free Documentation License | ||
| 1131 | @appendix GNU Free Documentation License | ||
| 1132 | @include doclicense.texi | ||
| 1133 | |||
| 1134 | @node Index | ||
| 1135 | @unnumbered Index | ||
| 1136 | @printindex cp | ||
| 1137 | |||
| 1138 | @bye | ||
| @@ -1357,6 +1357,16 @@ The default input method for the Tamil language environment is now | |||
| 1357 | change the input method's translation rules, customize the user option | 1357 | change the input method's translation rules, customize the user option |
| 1358 | 'tamil-translation-rules'. | 1358 | 'tamil-translation-rules'. |
| 1359 | 1359 | ||
| 1360 | --- | ||
| 1361 | *** New tamil99 input method for the Tamil language. | ||
| 1362 | This supports the keyboard layout specifically designed for the Tamil | ||
| 1363 | language. | ||
| 1364 | |||
| 1365 | --- | ||
| 1366 | *** New input method 'slovak-qwerty'. | ||
| 1367 | This is a variant of the 'slovak' input method, which corresponds to | ||
| 1368 | the QWERTY Slovak keyboards. | ||
| 1369 | |||
| 1360 | 1370 | ||
| 1361 | * Changes in Specialized Modes and Packages in Emacs 29.1 | 1371 | * Changes in Specialized Modes and Packages in Emacs 29.1 |
| 1362 | 1372 | ||
diff --git a/etc/PROBLEMS b/etc/PROBLEMS index ed2bc1ae051..aaecc41f6e8 100644 --- a/etc/PROBLEMS +++ b/etc/PROBLEMS | |||
| @@ -1229,6 +1229,17 @@ you should use an Emacs input method instead. | |||
| 1229 | 1229 | ||
| 1230 | ** X keyboard problems | 1230 | ** X keyboard problems |
| 1231 | 1231 | ||
| 1232 | *** `x-focus-frame' fails to activate the frame. | ||
| 1233 | |||
| 1234 | Some window managers prevent `x-focus-frame' from activating the given | ||
| 1235 | frame when Emacs is in the background. | ||
| 1236 | |||
| 1237 | Emacs tries to work around this problem by default, but the workaround | ||
| 1238 | does not work on all window managers. You can try different | ||
| 1239 | workarounds by changing the value of `x-allow-focus-stealing' (see its | ||
| 1240 | doc string for more details). The value `imitate-pager' may be | ||
| 1241 | required on some versions of KWin. | ||
| 1242 | |||
| 1232 | *** You "lose characters" after typing Compose Character key. | 1243 | *** You "lose characters" after typing Compose Character key. |
| 1233 | 1244 | ||
| 1234 | This is because the Compose Character key is defined as the keysym | 1245 | This is because the Compose Character key is defined as the keysym |
diff --git a/lib-src/rcs2log b/lib-src/rcs2log index bc7875cfdd2..2a72404d9e5 100755 --- a/lib-src/rcs2log +++ b/lib-src/rcs2log | |||
| @@ -209,7 +209,7 @@ month_data=' | |||
| 209 | if type mktemp >/dev/null 2>&1; then | 209 | if type mktemp >/dev/null 2>&1; then |
| 210 | logdir=`mktemp -d` | 210 | logdir=`mktemp -d` |
| 211 | else | 211 | else |
| 212 | logdir=$TMPDIR/rcs2log$$ | 212 | logdir="${TMPDIR-/tmp}/rcs2log$$" |
| 213 | (umask 077 && mkdir "$logdir") | 213 | (umask 077 && mkdir "$logdir") |
| 214 | fi || exit | 214 | fi || exit |
| 215 | case $logdir in | 215 | case $logdir in |
diff --git a/lisp/emacs-lisp/comp.el b/lisp/emacs-lisp/comp.el index 2c9b79334ba..5a05fe4854b 100644 --- a/lisp/emacs-lisp/comp.el +++ b/lisp/emacs-lisp/comp.el | |||
| @@ -3928,6 +3928,7 @@ processes from `comp-async-compilations'" | |||
| 3928 | "Start compiling files from `comp-files-queue' asynchronously. | 3928 | "Start compiling files from `comp-files-queue' asynchronously. |
| 3929 | When compilation is finished, run `native-comp-async-all-done-hook' and | 3929 | When compilation is finished, run `native-comp-async-all-done-hook' and |
| 3930 | display a message." | 3930 | display a message." |
| 3931 | (cl-assert (null comp-no-spawn)) | ||
| 3931 | (if (or comp-files-queue | 3932 | (if (or comp-files-queue |
| 3932 | (> (comp-async-runnings) 0)) | 3933 | (> (comp-async-runnings) 0)) |
| 3933 | (unless (>= (comp-async-runnings) (comp-effective-async-max-jobs)) | 3934 | (unless (>= (comp-async-runnings) (comp-effective-async-max-jobs)) |
| @@ -4048,7 +4049,7 @@ the deferred compilation mechanism." | |||
| 4048 | (stringp function-or-file)) | 4049 | (stringp function-or-file)) |
| 4049 | (signal 'native-compiler-error | 4050 | (signal 'native-compiler-error |
| 4050 | (list "Not a function symbol or file" function-or-file))) | 4051 | (list "Not a function symbol or file" function-or-file))) |
| 4051 | (unless comp-no-spawn | 4052 | (when (or (null comp-no-spawn) comp-async-compilation) |
| 4052 | (catch 'no-native-compile | 4053 | (catch 'no-native-compile |
| 4053 | (let* ((print-symbols-bare t) | 4054 | (let* ((print-symbols-bare t) |
| 4054 | (data function-or-file) | 4055 | (data function-or-file) |
diff --git a/lisp/eshell/esh-util.el b/lisp/eshell/esh-util.el index 9b464a0a137..f47373c115f 100644 --- a/lisp/eshell/esh-util.el +++ b/lisp/eshell/esh-util.el | |||
| @@ -455,7 +455,7 @@ list." | |||
| 455 | ;; runs while point is in the minibuffer and the users attempt | 455 | ;; runs while point is in the minibuffer and the users attempt |
| 456 | ;; to use completion. Don't ask me. | 456 | ;; to use completion. Don't ask me. |
| 457 | (condition-case nil | 457 | (condition-case nil |
| 458 | (sit-for 0 0) | 458 | (sit-for 0) |
| 459 | (error nil))) | 459 | (error nil))) |
| 460 | 460 | ||
| 461 | (defun eshell-read-passwd-file (file) | 461 | (defun eshell-read-passwd-file (file) |
diff --git a/lisp/info-look.el b/lisp/info-look.el index ce0a08dcbe6..2eec6f49f5c 100644 --- a/lisp/info-look.el +++ b/lisp/info-look.el | |||
| @@ -1051,6 +1051,7 @@ Return nil if there is nothing appropriate in the buffer near point." | |||
| 1051 | ("eieio" "Function Index") | 1051 | ("eieio" "Function Index") |
| 1052 | ("gnutls" "(emacs-gnutls)Variable Index" "(emacs-gnutls)Function Index") | 1052 | ("gnutls" "(emacs-gnutls)Variable Index" "(emacs-gnutls)Function Index") |
| 1053 | ("mm" "(emacs-mime)Index") | 1053 | ("mm" "(emacs-mime)Index") |
| 1054 | ("eglot" "Index") | ||
| 1054 | ("epa" "Variable Index" "Function Index") | 1055 | ("epa" "Variable Index" "Function Index") |
| 1055 | ("ert" "Index") | 1056 | ("ert" "Index") |
| 1056 | ("eshell" "Function and Variable Index") | 1057 | ("eshell" "Function and Variable Index") |
diff --git a/lisp/leim/quail/indian.el b/lisp/leim/quail/indian.el index 048e16e8d80..31a34bc1de2 100644 --- a/lisp/leim/quail/indian.el +++ b/lisp/leim/quail/indian.el | |||
| @@ -30,6 +30,8 @@ | |||
| 30 | 30 | ||
| 31 | ;;; Code: | 31 | ;;; Code: |
| 32 | 32 | ||
| 33 | (require 'pcase) | ||
| 34 | (require 'seq) | ||
| 33 | (require 'quail) | 35 | (require 'quail) |
| 34 | (require 'ind-util) | 36 | (require 'ind-util) |
| 35 | 37 | ||
| @@ -699,6 +701,165 @@ is." | |||
| 699 | "tamil-inscript-digits" "Tamil" "TmlISD" | 701 | "tamil-inscript-digits" "Tamil" "TmlISD" |
| 700 | "Tamil keyboard Inscript with Tamil digits support.") | 702 | "Tamil keyboard Inscript with Tamil digits support.") |
| 701 | 703 | ||
| 704 | ;; Tamil99 input method | ||
| 705 | ;; | ||
| 706 | ;; Tamil99 is a keyboard layout and input method that is specifically | ||
| 707 | ;; designed for the Tamil language. Vowels and vowel modifiers are | ||
| 708 | ;; input with your left hand, and consonants are input with your right | ||
| 709 | ;; hand. See https://en.wikipedia.org/wiki/Tamil_99 | ||
| 710 | ;; | ||
| 711 | ;; தமிழ்99 உள்ளீட்டு முறை | ||
| 712 | ;; | ||
| 713 | ;; தமிழ்99 தமிழுக்கென்றே உருவாக்கப்பட்ட விசைப்பலகை அமைப்பும் உள்ளீட்டு முறையும் | ||
| 714 | ;; ஆகும். உயிர்களை இடக்கையுடனும் மெய்களை வலக்கையுடனும் தட்டச்சிடும்படி | ||
| 715 | ;; அமைக்கப்பட்டது. https://ta.wikipedia.org/wiki/%E0%AE%A4%E0%AE%AE%E0%AE%BF%E0%AE%B4%E0%AF%8D_99 | ||
| 716 | ;; காண்க. | ||
| 717 | |||
| 718 | (quail-define-package | ||
| 719 | "tamil99" "Tamil" "தமிழ்99" | ||
| 720 | t "Tamil99 input method" | ||
| 721 | nil t t t t nil nil nil nil nil t) | ||
| 722 | |||
| 723 | (defconst tamil99-vowels | ||
| 724 | '(("q" "ஆ") | ||
| 725 | ("w" "ஈ") | ||
| 726 | ("e" "ஊ") | ||
| 727 | ("r" "ஐ") | ||
| 728 | ("t" "ஏ") | ||
| 729 | ("a" "அ") | ||
| 730 | ("s" "இ") | ||
| 731 | ("d" "உ") | ||
| 732 | ("g" "எ") | ||
| 733 | ("z" "ஔ") | ||
| 734 | ("x" "ஓ") | ||
| 735 | ("c" "ஒ")) | ||
| 736 | "Mapping for vowels.") | ||
| 737 | |||
| 738 | (defconst tamil99-vowel-modifiers | ||
| 739 | '(("q" "ா") | ||
| 740 | ("w" "ீ") | ||
| 741 | ("e" "ூ") | ||
| 742 | ("r" "ை") | ||
| 743 | ("t" "ே") | ||
| 744 | ("a" "") | ||
| 745 | ("s" "ி") | ||
| 746 | ("d" "ு") | ||
| 747 | ("g" "ெ") | ||
| 748 | ("z" "ௌ") | ||
| 749 | ("x" "ோ") | ||
| 750 | ("c" "ொ") | ||
| 751 | ("f" "்")) | ||
| 752 | "Mapping for vowel modifiers.") | ||
| 753 | |||
| 754 | (defconst tamil99-hard-consonants | ||
| 755 | '(("h" "க") | ||
| 756 | ("[" "ச") | ||
| 757 | ("o" "ட") | ||
| 758 | ("l" "த") | ||
| 759 | ("j" "ப") | ||
| 760 | ("u" "ற")) | ||
| 761 | "Mapping for hard consonants (வல்லினம்).") | ||
| 762 | |||
| 763 | (defconst tamil99-soft-consonants | ||
| 764 | '(("b" "ங") | ||
| 765 | ("]" "ஞ") | ||
| 766 | ("p" "ண") | ||
| 767 | (";" "ந") | ||
| 768 | ("k" "ம") | ||
| 769 | ("i" "ன")) | ||
| 770 | "Mapping for soft consonants (மெல்லினம்).") | ||
| 771 | |||
| 772 | (defconst tamil99-medium-consonants | ||
| 773 | '(("'" "ய") | ||
| 774 | ("m" "ர") | ||
| 775 | ("n" "ல") | ||
| 776 | ("v" "வ") | ||
| 777 | ("/" "ழ") | ||
| 778 | ("y" "ள")) | ||
| 779 | "Mapping for medium consonants (இடையினம்).") | ||
| 780 | |||
| 781 | (defconst tamil99-grantham-consonants | ||
| 782 | '(("Q" "ஸ") | ||
| 783 | ("W" "ஷ") | ||
| 784 | ("E" "ஜ") | ||
| 785 | ("R" "ஹ")) | ||
| 786 | "Mapping for grantham consonants (கிரந்தம்).") | ||
| 787 | |||
| 788 | (defconst tamil99-consonants | ||
| 789 | (append tamil99-hard-consonants | ||
| 790 | tamil99-soft-consonants | ||
| 791 | tamil99-medium-consonants | ||
| 792 | tamil99-grantham-consonants) | ||
| 793 | "Mapping for all consonants.") | ||
| 794 | |||
| 795 | (defconst tamil99-other | ||
| 796 | `(("T" ,(vector "க்ஷ")) | ||
| 797 | ("Y" ,(vector "ஶஂரீ")) | ||
| 798 | ("O" "[") | ||
| 799 | ("P" "]") | ||
| 800 | ("A" "௹") | ||
| 801 | ("S" "௺") | ||
| 802 | ("D" "௸") | ||
| 803 | ("F" "ஃ") | ||
| 804 | ("K" "\"") | ||
| 805 | ("L" ":") | ||
| 806 | (":" ";") | ||
| 807 | ("\"" "'") | ||
| 808 | ("Z" "௳") | ||
| 809 | ("X" "௴") | ||
| 810 | ("C" "௵") | ||
| 811 | ("V" "௶") | ||
| 812 | ("B" "௷") | ||
| 813 | ("M" "/")) | ||
| 814 | "Mapping for miscellaneous characters.") | ||
| 815 | |||
| 816 | ;; உயிர் | ||
| 817 | ;; vowel | ||
| 818 | (mapc (pcase-lambda (`(,vowel-key ,vowel)) | ||
| 819 | (quail-defrule vowel-key vowel)) | ||
| 820 | tamil99-vowels) | ||
| 821 | |||
| 822 | (mapc (pcase-lambda (`(,consonant-key ,consonant)) | ||
| 823 | ;; அகர உயிர்மெய் | ||
| 824 | ;; consonant symbol (consonant combined with the first vowel அ) | ||
| 825 | (quail-defrule consonant-key consonant) | ||
| 826 | ;; மெய்யொற்று பின் அகர உயிர்மெய் | ||
| 827 | ;; pulli on double consonant | ||
| 828 | (quail-defrule (concat consonant-key consonant-key) | ||
| 829 | (vector (concat consonant "்" consonant))) | ||
| 830 | (mapc (pcase-lambda (`(,vowel-key ,vowel-modifier)) | ||
| 831 | ;; உயிர்மெய் | ||
| 832 | ;; vowelised consonant | ||
| 833 | (quail-defrule (concat consonant-key vowel-key) | ||
| 834 | (vector (concat consonant vowel-modifier))) | ||
| 835 | ;; மெய்யொற்று பின் பிற உயிர்மெய் | ||
| 836 | ;; vowelised consonant after double consonant | ||
| 837 | (quail-defrule (concat consonant-key consonant-key vowel-key) | ||
| 838 | (vector (concat consonant "்" consonant vowel-modifier)))) | ||
| 839 | tamil99-vowel-modifiers)) | ||
| 840 | tamil99-consonants) | ||
| 841 | |||
| 842 | (seq-mapn (pcase-lambda (`(,soft-consonant-key ,soft-consonant) | ||
| 843 | `(,hard-consonant-key ,hard-consonant)) | ||
| 844 | ;; மெல்லினம் பின் வல்லினம் | ||
| 845 | ;; hard consonant after soft consonant | ||
| 846 | (quail-defrule (concat soft-consonant-key hard-consonant-key) | ||
| 847 | (vector (concat soft-consonant "்" hard-consonant))) | ||
| 848 | (mapc (pcase-lambda (`(,vowel-key ,vowel-modifier)) | ||
| 849 | ;; மெல்லின ஒற்றொட்டிய வல்லினம் பின் உயிர்மெய் | ||
| 850 | ;; vowelised consonant after soft-hard consonant pair | ||
| 851 | (quail-defrule (concat soft-consonant-key hard-consonant-key vowel-key) | ||
| 852 | (vector (concat soft-consonant "்" hard-consonant vowel-modifier)))) | ||
| 853 | tamil99-vowel-modifiers)) | ||
| 854 | tamil99-soft-consonants | ||
| 855 | tamil99-hard-consonants) | ||
| 856 | |||
| 857 | ;; பிற வரியுருக்கள் | ||
| 858 | ;; other characters | ||
| 859 | (mapc (pcase-lambda (`(,key ,translation)) | ||
| 860 | (quail-defrule key translation)) | ||
| 861 | tamil99-other) | ||
| 862 | |||
| 702 | ;; Probhat Input Method | 863 | ;; Probhat Input Method |
| 703 | (quail-define-package | 864 | (quail-define-package |
| 704 | "bengali-probhat" "Bengali" "BngPB" t | 865 | "bengali-probhat" "Bengali" "BngPB" t |
diff --git a/lisp/leim/quail/slovak.el b/lisp/leim/quail/slovak.el index acde11d02a7..8ddd92d5b4d 100644 --- a/lisp/leim/quail/slovak.el +++ b/lisp/leim/quail/slovak.el | |||
| @@ -3,7 +3,8 @@ | |||
| 3 | ;; Copyright (C) 1998, 2001-2022 Free Software Foundation, Inc. | 3 | ;; Copyright (C) 1998, 2001-2022 Free Software Foundation, Inc. |
| 4 | 4 | ||
| 5 | ;; Authors: Tibor Šimko <tibor.simko@fmph.uniba.sk> | 5 | ;; Authors: Tibor Šimko <tibor.simko@fmph.uniba.sk> |
| 6 | ;; Milan Zamazal <pdm@zamazal.org> | 6 | ;; Milan Zamazal <pdm@zamazal.org> |
| 7 | ;; Rudolf Adamkovič <salutis@me.com> | ||
| 7 | ;; Maintainer: Pavel Janík <Pavel@Janik.cz> | 8 | ;; Maintainer: Pavel Janík <Pavel@Janik.cz> |
| 8 | ;; Keywords: i18n, multilingual, input method, Slovak | 9 | ;; Keywords: i18n, multilingual, input method, Slovak |
| 9 | 10 | ||
| @@ -25,8 +26,9 @@ | |||
| 25 | ;;; Commentary: | 26 | ;;; Commentary: |
| 26 | 27 | ||
| 27 | ;; This file defines the following Slovak keyboards: | 28 | ;; This file defines the following Slovak keyboards: |
| 28 | ;; - standard Slovak keyboard | 29 | ;; - standard Slovak keyboards, QWERTZ and QWERTY variants |
| 29 | ;; - three Slovak keyboards for programmers | 30 | ;; - three Slovak keyboards for programmers |
| 31 | ;; LocalWords: QWERTZ | ||
| 30 | 32 | ||
| 31 | ;;; Code: | 33 | ;;; Code: |
| 32 | 34 | ||
| @@ -35,7 +37,7 @@ | |||
| 35 | 37 | ||
| 36 | (quail-define-package | 38 | (quail-define-package |
| 37 | "slovak" "Slovak" "SK" t | 39 | "slovak" "Slovak" "SK" t |
| 38 | "Standard Slovak keyboard." | 40 | "Standard Slovak QWERTZ keyboard." |
| 39 | nil t nil nil t nil nil nil nil nil t) | 41 | nil t nil nil t nil nil nil nil nil t) |
| 40 | 42 | ||
| 41 | (quail-define-rules | 43 | (quail-define-rules |
| @@ -155,6 +157,123 @@ | |||
| 155 | 157 | ||
| 156 | 158 | ||
| 157 | (quail-define-package | 159 | (quail-define-package |
| 160 | "slovak-qwerty" "Slovak" "SK" t | ||
| 161 | "Standard Slovak QWERTY keyboard." | ||
| 162 | nil t nil nil t nil nil nil nil nil t) | ||
| 163 | |||
| 164 | (quail-define-rules | ||
| 165 | ("1" ?+) | ||
| 166 | ("2" ?ľ) | ||
| 167 | ("3" ?š) | ||
| 168 | ("4" ?č) | ||
| 169 | ("5" ?ť) | ||
| 170 | ("6" ?ž) | ||
| 171 | ("7" ?ý) | ||
| 172 | ("8" ?á) | ||
| 173 | ("9" ?í) | ||
| 174 | ("0" ?é) | ||
| 175 | ("!" ?1) | ||
| 176 | ("@" ?2) | ||
| 177 | ("#" ?3) | ||
| 178 | ("$" ?4) | ||
| 179 | ("%" ?5) | ||
| 180 | ("^" ?6) | ||
| 181 | ("&" ?7) | ||
| 182 | ("*" ?8) | ||
| 183 | ("(" ?9) | ||
| 184 | (")" ?0) | ||
| 185 | ("-" ?=) | ||
| 186 | ("_" ?%) | ||
| 187 | ("=" ?') | ||
| 188 | ("[" ?ú) | ||
| 189 | ("{" ?/) | ||
| 190 | ("]" ?ä) | ||
| 191 | ("}" ?\() | ||
| 192 | ("\\" ?ň) | ||
| 193 | ("|" ?\)) | ||
| 194 | (";" ?ô) | ||
| 195 | (":" ?\") | ||
| 196 | ("'" ?§) | ||
| 197 | ("\"" ?!) | ||
| 198 | ("<" ??) | ||
| 199 | (">" ?:) | ||
| 200 | ("/" ?-) | ||
| 201 | ("?" ?_) | ||
| 202 | ("`" ?\;) | ||
| 203 | ("~" ?^) | ||
| 204 | ("=a" ?á) | ||
| 205 | ("+a" ?ä) | ||
| 206 | ("+=a" ?ä) | ||
| 207 | ("+c" ?č) | ||
| 208 | ("+d" ?ď) | ||
| 209 | ("=e" ?é) | ||
| 210 | ("+e" ?ě) | ||
| 211 | ("=i" ?í) | ||
| 212 | ("=l" ?ĺ) | ||
| 213 | ("+l" ?ľ) | ||
| 214 | ("+n" ?ň) | ||
| 215 | ("=o" ?ó) | ||
| 216 | ("+o" ?ô) | ||
| 217 | ("~o" ?ô) | ||
| 218 | ("+=o" ?ö) | ||
| 219 | ("=r" ?ŕ) | ||
| 220 | ("+r" ?ř) | ||
| 221 | ("=s" ?ß) | ||
| 222 | ("+s" ?š) | ||
| 223 | ("+t" ?ť) | ||
| 224 | ("=u" ?ú) | ||
| 225 | ("+u" ?ů) | ||
| 226 | ("+=u" ?ü) | ||
| 227 | ("=y" ?ý) | ||
| 228 | ("+z" ?ž) | ||
| 229 | ("=A" ?Á) | ||
| 230 | ("+A" ?Ä) | ||
| 231 | ("+=A" ?Ä) | ||
| 232 | ("+C" ?Č) | ||
| 233 | ("+D" ?Ď) | ||
| 234 | ("=E" ?É) | ||
| 235 | ("+E" ?Ě) | ||
| 236 | ("=I" ?Í) | ||
| 237 | ("=L" ?Ĺ) | ||
| 238 | ("+L" ?Ľ) | ||
| 239 | ("+N" ?Ň) | ||
| 240 | ("=O" ?Ó) | ||
| 241 | ("+O" ?Ô) | ||
| 242 | ("~O" ?Ô) | ||
| 243 | ("+=O" ?Ö) | ||
| 244 | ("=R" ?Ŕ) | ||
| 245 | ("+R" ?Ř) | ||
| 246 | ("=S" ?ß) | ||
| 247 | ("+S" ?Š) | ||
| 248 | ("+T" ?Ť) | ||
| 249 | ("=U" ?Ú) | ||
| 250 | ("+U" ?Ů) | ||
| 251 | ("+=U" ?Ü) | ||
| 252 | ("=Y" ?Ý) | ||
| 253 | ("+Z" ?Ž) | ||
| 254 | ("=q" ?`) | ||
| 255 | ("=2" ?@) | ||
| 256 | ("=3" ?#) | ||
| 257 | ("=4" ?$) | ||
| 258 | ("=5" ?%) | ||
| 259 | ("=6" ?^) | ||
| 260 | ("=7" ?&) | ||
| 261 | ("=8" ?*) | ||
| 262 | ("=9" ?\() | ||
| 263 | ("=0" ?\)) | ||
| 264 | ("+1" ?!) | ||
| 265 | ("+2" ?@) | ||
| 266 | ("+3" ?#) | ||
| 267 | ("+4" ?$) | ||
| 268 | ("+5" ?%) | ||
| 269 | ("+6" ?^) | ||
| 270 | ("+7" ?&) | ||
| 271 | ("+8" ?*) | ||
| 272 | ("+9" ?\() | ||
| 273 | ("+0" ?\))) | ||
| 274 | |||
| 275 | |||
| 276 | (quail-define-package | ||
| 158 | "slovak-prog-1" "Slovak" "SK" t | 277 | "slovak-prog-1" "Slovak" "SK" t |
| 159 | "Slovak (non-standard) keyboard for programmers #1. | 278 | "Slovak (non-standard) keyboard for programmers #1. |
| 160 | 279 | ||
diff --git a/lisp/menu-bar.el b/lisp/menu-bar.el index 526bccbbac9..849e0f77236 100644 --- a/lisp/menu-bar.el +++ b/lisp/menu-bar.el | |||
| @@ -1847,6 +1847,10 @@ mail status in mode line")) | |||
| 1847 | :help "Toggle automatic parsing in source code buffers (Semantic mode)" | 1847 | :help "Toggle automatic parsing in source code buffers (Semantic mode)" |
| 1848 | :button (:toggle . (bound-and-true-p semantic-mode)))) | 1848 | :button (:toggle . (bound-and-true-p semantic-mode)))) |
| 1849 | 1849 | ||
| 1850 | (bindings--define-key menu [eglot] | ||
| 1851 | '(menu-item "Language Server Support (Eglot)" eglot | ||
| 1852 | :help "Start language server suitable for this buffer's major-mode")) | ||
| 1853 | |||
| 1850 | (bindings--define-key menu [ede] | 1854 | (bindings--define-key menu [ede] |
| 1851 | '(menu-item "Project Support (EDE)" | 1855 | '(menu-item "Project Support (EDE)" |
| 1852 | global-ede-mode | 1856 | global-ede-mode |
diff --git a/lisp/net/ldap.el b/lisp/net/ldap.el index 062ff05d69c..ccad8c4edb1 100644 --- a/lisp/net/ldap.el +++ b/lisp/net/ldap.el | |||
| @@ -715,14 +715,14 @@ an alist of attribute/value pairs." | |||
| 715 | (eq (string-match "/\\(.:.*\\)$" value) 0)) | 715 | (eq (string-match "/\\(.:.*\\)$" value) 0)) |
| 716 | (setq value (match-string 1 value))) | 716 | (setq value (match-string 1 value))) |
| 717 | ;; Do not try to open non-existent files | 717 | ;; Do not try to open non-existent files |
| 718 | (if (equal value "") | 718 | (if (match-string 3) |
| 719 | (setq value " ") | 719 | (with-current-buffer bufval |
| 720 | (with-current-buffer bufval | ||
| 721 | (erase-buffer) | 720 | (erase-buffer) |
| 722 | (set-buffer-multibyte nil) | 721 | (set-buffer-multibyte nil) |
| 723 | (insert-file-contents-literally value) | 722 | (insert-file-contents-literally value) |
| 724 | (delete-file value) | 723 | (delete-file value) |
| 725 | (setq value (buffer-string)))) | 724 | (setq value (buffer-string))) |
| 725 | (setq value " ")) | ||
| 726 | (setq record (cons (list name value) | 726 | (setq record (cons (list name value) |
| 727 | record)) | 727 | record)) |
| 728 | (forward-line 1)) | 728 | (forward-line 1)) |
diff --git a/lisp/play/zone.el b/lisp/play/zone.el index 5ea5bbc9267..e3a9507f1cc 100644 --- a/lisp/play/zone.el +++ b/lisp/play/zone.el | |||
| @@ -139,7 +139,7 @@ run a specific program. The program must be a member of | |||
| 139 | (untabify (point-min) (point-max)) | 139 | (untabify (point-min) (point-max)) |
| 140 | (set-window-start (selected-window) (point-min)) | 140 | (set-window-start (selected-window) (point-min)) |
| 141 | (set-window-point (selected-window) wp) | 141 | (set-window-point (selected-window) wp) |
| 142 | (sit-for 0 500) | 142 | (sit-for 0.500) |
| 143 | (let ((ct (and f (frame-parameter f 'cursor-type))) | 143 | (let ((ct (and f (frame-parameter f 'cursor-type))) |
| 144 | (show-trailing-whitespace nil) | 144 | (show-trailing-whitespace nil) |
| 145 | restore) | 145 | restore) |
| @@ -249,7 +249,7 @@ run a specific program. The program must be a member of | |||
| 249 | (while (not (input-pending-p)) | 249 | (while (not (input-pending-p)) |
| 250 | (funcall (elt ops (random (length ops)))) | 250 | (funcall (elt ops (random (length ops)))) |
| 251 | (goto-char (point-min)) | 251 | (goto-char (point-min)) |
| 252 | (sit-for 0 10)))) | 252 | (sit-for 0.01)))) |
| 253 | 253 | ||
| 254 | 254 | ||
| 255 | ;;;; whacking chars | 255 | ;;;; whacking chars |
| @@ -262,7 +262,7 @@ run a specific program. The program must be a member of | |||
| 262 | (aset tbl i (+ 48 (random (- 123 48)))) | 262 | (aset tbl i (+ 48 (random (- 123 48)))) |
| 263 | (setq i (1+ i))) | 263 | (setq i (1+ i))) |
| 264 | (translate-region (point-min) (point-max) tbl) | 264 | (translate-region (point-min) (point-max) tbl) |
| 265 | (sit-for 0 2))))) | 265 | (sit-for 0.002))))) |
| 266 | 266 | ||
| 267 | (put 'zone-pgm-whack-chars 'wc-tbl | 267 | (put 'zone-pgm-whack-chars 'wc-tbl |
| 268 | (let ((tbl (make-string 128 ?x)) | 268 | (let ((tbl (make-string 128 ?x)) |
| @@ -290,7 +290,7 @@ run a specific program. The program must be a member of | |||
| 290 | (delete-char 1) | 290 | (delete-char 1) |
| 291 | (insert " "))) | 291 | (insert " "))) |
| 292 | (forward-char 1)))) | 292 | (forward-char 1)))) |
| 293 | (sit-for 0 2)))) | 293 | (sit-for 0.002)))) |
| 294 | 294 | ||
| 295 | (defun zone-pgm-dissolve () | 295 | (defun zone-pgm-dissolve () |
| 296 | (zone-remove-text) | 296 | (zone-remove-text) |
diff --git a/lisp/progmodes/cc-engine.el b/lisp/progmodes/cc-engine.el index 596cccdf48e..e71560fa25f 100644 --- a/lisp/progmodes/cc-engine.el +++ b/lisp/progmodes/cc-engine.el | |||
| @@ -9106,7 +9106,9 @@ multi-line strings (but not C++, for example)." | |||
| 9106 | (when (save-excursion | 9106 | (when (save-excursion |
| 9107 | (goto-char post-prefix-pos) | 9107 | (goto-char post-prefix-pos) |
| 9108 | (looking-at c-self-contained-typename-key)) | 9108 | (looking-at c-self-contained-typename-key)) |
| 9109 | (c-add-type pos (point))) | 9109 | (c-add-type pos (save-excursion |
| 9110 | (c-backward-syntactic-ws) | ||
| 9111 | (point)))) | ||
| 9110 | (when (and c-record-type-identifiers | 9112 | (when (and c-record-type-identifiers |
| 9111 | c-last-identifier-range) | 9113 | c-last-identifier-range) |
| 9112 | (c-record-type-id c-last-identifier-range))) | 9114 | (c-record-type-id c-last-identifier-range))) |
| @@ -9191,7 +9193,10 @@ multi-line strings (but not C++, for example)." | |||
| 9191 | (goto-char id-end) | 9193 | (goto-char id-end) |
| 9192 | (if (or res c-promote-possible-types) | 9194 | (if (or res c-promote-possible-types) |
| 9193 | (progn | 9195 | (progn |
| 9194 | (c-add-type id-start id-end) | 9196 | (c-add-type id-start (save-excursion |
| 9197 | (goto-char id-end) | ||
| 9198 | (c-backward-syntactic-ws) | ||
| 9199 | (point))) | ||
| 9195 | (when (and c-record-type-identifiers id-range) | 9200 | (when (and c-record-type-identifiers id-range) |
| 9196 | (c-record-type-id id-range)) | 9201 | (c-record-type-id id-range)) |
| 9197 | (unless res | 9202 | (unless res |
| @@ -10762,8 +10767,16 @@ This function might do hidden buffer changes." | |||
| 10762 | (setq backup-if-not-cast t) | 10767 | (setq backup-if-not-cast t) |
| 10763 | (throw 'at-decl-or-cast t))) | 10768 | (throw 'at-decl-or-cast t))) |
| 10764 | 10769 | ||
| 10765 | (setq backup-if-not-cast t) | 10770 | ;; If we're in declaration or template delimiters, or one |
| 10766 | (throw 'at-decl-or-cast t))) | 10771 | ;; of a certain set of characters follows, we've got a |
| 10772 | ;; type and variable. | ||
| 10773 | (if (or (memq context '(decl <>)) | ||
| 10774 | (memq (char-after) '(?\; ?, ?= ?\( ?{ ?:))) | ||
| 10775 | (progn | ||
| 10776 | (setq backup-if-not-cast t) | ||
| 10777 | (throw 'at-decl-or-cast t)) | ||
| 10778 | ;; We're probably just typing a statement. | ||
| 10779 | (throw 'at-decl-or-cast nil)))) | ||
| 10767 | 10780 | ||
| 10768 | ;; CASE 4 | 10781 | ;; CASE 4 |
| 10769 | (when (and got-suffix | 10782 | (when (and got-suffix |
| @@ -10879,8 +10892,13 @@ This function might do hidden buffer changes." | |||
| 10879 | 10892 | ||
| 10880 | ;; CASE 10 | 10893 | ;; CASE 10 |
| 10881 | (when at-decl-or-cast | 10894 | (when at-decl-or-cast |
| 10882 | ;; By now we've located the type in the declaration that we know | 10895 | ;; By now we've located the type in the declaration that we think |
| 10883 | ;; we're in. | 10896 | ;; we're in. Do we have enough evidence to promote the putative |
| 10897 | ;; type to a found type? The user may be halfway through typing | ||
| 10898 | ;; a statement beginning with an identifier. | ||
| 10899 | (when (and (eq at-type 'maybe) | ||
| 10900 | (not (eq context 'top))) | ||
| 10901 | (setq c-record-type-identifiers nil)) | ||
| 10884 | (throw 'at-decl-or-cast t)) | 10902 | (throw 'at-decl-or-cast t)) |
| 10885 | 10903 | ||
| 10886 | ;; CASE 11 | 10904 | ;; CASE 11 |
| @@ -11123,7 +11141,10 @@ This function might do hidden buffer changes." | |||
| 11123 | (not (c-on-identifier))))))))) | 11141 | (not (c-on-identifier))))))))) |
| 11124 | 11142 | ||
| 11125 | ;; Handle the cast. | 11143 | ;; Handle the cast. |
| 11126 | (when (and c-record-type-identifiers at-type (not (eq at-type t))) | 11144 | (when (and c-record-type-identifiers |
| 11145 | at-type | ||
| 11146 | (not (memq at-type '(t maybe)))) ; 'maybe isn't strong enough | ||
| 11147 | ; evidence to promote the type. | ||
| 11127 | (let ((c-promote-possible-types t)) | 11148 | (let ((c-promote-possible-types t)) |
| 11128 | (goto-char type-start) | 11149 | (goto-char type-start) |
| 11129 | (c-forward-type))) | 11150 | (c-forward-type))) |
diff --git a/lisp/progmodes/cc-fonts.el b/lisp/progmodes/cc-fonts.el index b4ff32b9070..aa16da70703 100644 --- a/lisp/progmodes/cc-fonts.el +++ b/lisp/progmodes/cc-fonts.el | |||
| @@ -1197,8 +1197,21 @@ casts and declarations are fontified. Used on level 2 and higher." | |||
| 1197 | ;; arguments lists (i.e. lists enclosed by <...>) is more strict about what | 1197 | ;; arguments lists (i.e. lists enclosed by <...>) is more strict about what |
| 1198 | ;; characters it allows within the list. | 1198 | ;; characters it allows within the list. |
| 1199 | (let ((type (and (> match-pos (point-min)) | 1199 | (let ((type (and (> match-pos (point-min)) |
| 1200 | (c-get-char-property (1- match-pos) 'c-type)))) | 1200 | (c-get-char-property (1- match-pos) 'c-type))) |
| 1201 | (cond ((not (memq (char-before match-pos) '(?\( ?, ?\[ ?< ?{))) | 1201 | id-pos) |
| 1202 | (cond | ||
| 1203 | ;; Are we just after something like "(foo((bar))" ? | ||
| 1204 | ((and (eq (char-before match-pos) ?\)) | ||
| 1205 | (c-go-list-backward match-pos) | ||
| 1206 | (progn | ||
| 1207 | (c-backward-syntactic-ws) | ||
| 1208 | (and (setq id-pos (c-on-identifier)) | ||
| 1209 | (goto-char id-pos) | ||
| 1210 | (progn | ||
| 1211 | (c-backward-syntactic-ws) | ||
| 1212 | (eq (char-before) ?\())))) | ||
| 1213 | (c-get-fontification-context (point) not-front-decl toplev)) | ||
| 1214 | ((not (memq (char-before match-pos) '(?\( ?, ?\[ ?< ?{))) | ||
| 1202 | (cons (and toplev 'top) nil)) | 1215 | (cons (and toplev 'top) nil)) |
| 1203 | ;; A control flow expression or a decltype | 1216 | ;; A control flow expression or a decltype |
| 1204 | ((and (eq (char-before match-pos) ?\() | 1217 | ((and (eq (char-before match-pos) ?\() |
diff --git a/lisp/progmodes/cc-mode.el b/lisp/progmodes/cc-mode.el index dce300f33c9..2aa6b90dea3 100644 --- a/lisp/progmodes/cc-mode.el +++ b/lisp/progmodes/cc-mode.el | |||
| @@ -2080,13 +2080,14 @@ with // and /*, not more generic line and block comments." | |||
| 2080 | (defun c-update-new-id (end) | 2080 | (defun c-update-new-id (end) |
| 2081 | ;; Note the bounds of any identifier that END is in or just after, in | 2081 | ;; Note the bounds of any identifier that END is in or just after, in |
| 2082 | ;; `c-new-id-start' and `c-new-id-end'. Otherwise set these variables to | 2082 | ;; `c-new-id-start' and `c-new-id-end'. Otherwise set these variables to |
| 2083 | ;; nil. | 2083 | ;; nil. Set `c-new-id-is-type' unconditionally to nil. |
| 2084 | (save-excursion | 2084 | (save-excursion |
| 2085 | (goto-char end) | 2085 | (goto-char end) |
| 2086 | (let ((id-beg (c-on-identifier))) | 2086 | (let ((id-beg (c-on-identifier))) |
| 2087 | (setq c-new-id-start id-beg | 2087 | (setq c-new-id-start id-beg |
| 2088 | c-new-id-end (and id-beg | 2088 | c-new-id-end (and id-beg |
| 2089 | (progn (c-end-of-current-token) (point))))))) | 2089 | (progn (c-end-of-current-token) (point))) |
| 2090 | c-new-id-is-type nil)))) | ||
| 2090 | 2091 | ||
| 2091 | (defun c-post-command () | 2092 | (defun c-post-command () |
| 2092 | ;; If point was inside of a new identifier and no longer is, record that | 2093 | ;; If point was inside of a new identifier and no longer is, record that |
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el new file mode 100644 index 00000000000..ada8b01fec2 --- /dev/null +++ b/lisp/progmodes/eglot.el | |||
| @@ -0,0 +1,3461 @@ | |||
| 1 | ;;; eglot.el --- The Emacs Client for LSP servers -*- lexical-binding: t; -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2018-2022 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Version: 1.9 | ||
| 6 | ;; Author: João Távora <joaotavora@gmail.com> | ||
| 7 | ;; Maintainer: João Távora <joaotavora@gmail.com> | ||
| 8 | ;; URL: https://github.com/joaotavora/eglot | ||
| 9 | ;; Keywords: convenience, languages | ||
| 10 | ;; Package-Requires: ((emacs "26.3") (jsonrpc "1.0.14") (flymake "1.2.1") (project "0.3.0") (xref "1.0.1") (eldoc "1.11.0") (seq "2.23")) | ||
| 11 | |||
| 12 | ;; This is is a GNU ELPA :core package. Avoid adding functionality | ||
| 13 | ;; that is not available in the version of Emacs recorded above or any | ||
| 14 | ;; of the package dependencies. | ||
| 15 | |||
| 16 | ;; This file is part of GNU Emacs. | ||
| 17 | |||
| 18 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 19 | ;; it under the terms of the GNU General Public License as published by | ||
| 20 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 21 | ;; (at your option) any later version. | ||
| 22 | |||
| 23 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 24 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 25 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 26 | ;; GNU General Public License for more details. | ||
| 27 | |||
| 28 | ;; You should have received a copy of the GNU General Public License | ||
| 29 | ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. | ||
| 30 | |||
| 31 | ;;; Commentary: | ||
| 32 | |||
| 33 | ;; Eglot ("Emacs Polyglot") is an Emacs LSP client that stays out of | ||
| 34 | ;; your way. | ||
| 35 | ;; | ||
| 36 | ;; Typing M-x eglot in some source file is often enough to get you | ||
| 37 | ;; started, if the language server you're looking to use is installed | ||
| 38 | ;; in your system. Please refer to the manual, available from | ||
| 39 | ;; https://joaotavora.github.io/eglot/ or from M-x info for more usage | ||
| 40 | ;; instructions. | ||
| 41 | ;; | ||
| 42 | ;; If you wish to contribute changes to Eglot, please do read the user | ||
| 43 | ;; manual first. Additionally, take the following in consideration: | ||
| 44 | |||
| 45 | ;; * Eglot's main job is to hook up the information that language | ||
| 46 | ;; servers offer via LSP to Emacs's UI facilities: Xref for | ||
| 47 | ;; definition-chasing, Flymake for diagnostics, Eldoc for at-point | ||
| 48 | ;; documentation, etc. Eglot's job is generally *not* to provide | ||
| 49 | ;; such a UI itself, though a small number of simple | ||
| 50 | ;; counter-examples do exist, for example in the `eglot-rename' | ||
| 51 | ;; command. When a new UI is evidently needed, consider adding a | ||
| 52 | ;; new package to Emacs, or extending an existing one. | ||
| 53 | ;; | ||
| 54 | ;; * Eglot was designed to function with just the UI facilities found | ||
| 55 | ;; in the latest Emacs core, as long as those facilities are also | ||
| 56 | ;; available as GNU ELPA :core packages. Historically, a number of | ||
| 57 | ;; :core packages were added or reworked in Emacs to make this | ||
| 58 | ;; possible. This principle should be upheld when adding new LSP | ||
| 59 | ;; features or tweaking exising ones. Design any new facilities in | ||
| 60 | ;; a way that they could work in the absence of LSP or using some | ||
| 61 | ;; different protocol, then make sure Eglot can link up LSP | ||
| 62 | ;; information to it. | ||
| 63 | |||
| 64 | ;; * There are few Eglot configuration variables. This principle | ||
| 65 | ;; should also be upheld. If Eglot had these variables, it could be | ||
| 66 | ;; duplicating configuration found elsewhere, bloating itself up, | ||
| 67 | ;; and making it generally hard to integrate with the ever growing | ||
| 68 | ;; set of LSP features and Emacs packages. For instance, this is | ||
| 69 | ;; why one finds a single variable | ||
| 70 | ;; `eglot-ignored-server-capabilities' instead of a number of | ||
| 71 | ;; capability-specific flags, or why customizing the display of | ||
| 72 | ;; LSP-provided documentation is done via ElDoc's variables, not | ||
| 73 | ;; Eglot's. | ||
| 74 | ;; | ||
| 75 | ;; * Linking up LSP information to other libraries is generally done | ||
| 76 | ;; in the `eglot--managed-mode' minor mode function, by | ||
| 77 | ;; buffer-locally setting the other library's variables to | ||
| 78 | ;; Eglot-specific versions. When deciding what to set the variable | ||
| 79 | ;; to, the general idea is to choose a good default for beginners | ||
| 80 | ;; that doesn't clash with Emacs's defaults. The settings are only | ||
| 81 | ;; in place during Eglot's LSP-enriched tenure over a project. Even | ||
| 82 | ;; so, some of those decisions will invariably aggravate a minority | ||
| 83 | ;; of Emacs power users, but these users can use `eglot-stay-out-of' | ||
| 84 | ;; and `eglot-managed-mode-hook' to quench their OCD. | ||
| 85 | ;; | ||
| 86 | ;; * On occasion, to enable new features, Eglot can have soft | ||
| 87 | ;; dependencies on popular libraries that are not in Emacs core. | ||
| 88 | ;; "Soft" means that the dependency doesn't impair any other use of | ||
| 89 | ;; Eglot beyond that feature. Such is the case of the snippet | ||
| 90 | ;; functionality, via the Yasnippet package, Markdown formatting of | ||
| 91 | ;; at-point documentation via the markdown-mode package, and nicer | ||
| 92 | ;; looking completions when the Company package is used. | ||
| 93 | |||
| 94 | ;;; Code: | ||
| 95 | |||
| 96 | (require 'imenu) | ||
| 97 | (require 'cl-lib) | ||
| 98 | (require 'project) | ||
| 99 | (require 'url-parse) | ||
| 100 | (require 'url-util) | ||
| 101 | (require 'pcase) | ||
| 102 | (require 'compile) ; for some faces | ||
| 103 | (require 'warnings) | ||
| 104 | (require 'flymake) | ||
| 105 | (require 'xref) | ||
| 106 | (eval-when-compile | ||
| 107 | (require 'subr-x)) | ||
| 108 | (require 'jsonrpc) | ||
| 109 | (require 'filenotify) | ||
| 110 | (require 'ert) | ||
| 111 | (require 'array) | ||
| 112 | |||
| 113 | ;; ElDoc is preloaded in Emacs, so `require'-ing won't guarantee we are | ||
| 114 | ;; using the latest version from GNU Elpa when we load eglot.el. Use an | ||
| 115 | ;; heuristic to see if we need to `load' it in Emacs < 28. | ||
| 116 | (if (and (< emacs-major-version 28) | ||
| 117 | (not (boundp 'eldoc-documentation-strategy))) | ||
| 118 | (load "eldoc") | ||
| 119 | (require 'eldoc)) | ||
| 120 | |||
| 121 | ;; Similar issue as above for Emacs 26.3 and seq.el. | ||
| 122 | (if (< emacs-major-version 27) | ||
| 123 | (load "seq") | ||
| 124 | (require 'seq)) | ||
| 125 | |||
| 126 | ;; forward-declare, but don't require (Emacs 28 doesn't seem to care) | ||
| 127 | (defvar markdown-fontify-code-blocks-natively) | ||
| 128 | (defvar company-backends) | ||
| 129 | (defvar company-tooltip-align-annotations) | ||
| 130 | |||
| 131 | |||
| 132 | |||
| 133 | ;;; User tweakable stuff | ||
| 134 | (defgroup eglot nil | ||
| 135 | "Interaction with Language Server Protocol servers." | ||
| 136 | :prefix "eglot-" | ||
| 137 | :group 'applications) | ||
| 138 | |||
| 139 | (defun eglot-alternatives (alternatives) | ||
| 140 | "Compute server-choosing function for `eglot-server-programs'. | ||
| 141 | Each element of ALTERNATIVES is a string PROGRAM or a list of | ||
| 142 | strings (PROGRAM ARGS...) where program names an LSP server | ||
| 143 | program to start with ARGS. Returns a function of one argument. | ||
| 144 | When invoked, that function will return a list (ABSPATH ARGS), | ||
| 145 | where ABSPATH is the absolute path of the PROGRAM that was | ||
| 146 | chosen (interactively or automatically)." | ||
| 147 | (lambda (&optional interactive) | ||
| 148 | ;; JT@2021-06-13: This function is way more complicated than it | ||
| 149 | ;; could be because it accounts for the fact that | ||
| 150 | ;; `eglot--executable-find' may take much longer to execute on | ||
| 151 | ;; remote files. | ||
| 152 | (let* ((listified (cl-loop for a in alternatives | ||
| 153 | collect (if (listp a) a (list a)))) | ||
| 154 | (err (lambda () | ||
| 155 | (error "None of '%s' are valid executables" | ||
| 156 | (mapconcat #'car listified ", "))))) | ||
| 157 | (cond (interactive | ||
| 158 | (let* ((augmented (mapcar (lambda (a) | ||
| 159 | (let ((found (eglot--executable-find | ||
| 160 | (car a) t))) | ||
| 161 | (and found | ||
| 162 | (cons (car a) (cons found (cdr a)))))) | ||
| 163 | listified)) | ||
| 164 | (available (remove nil augmented))) | ||
| 165 | (cond ((cdr available) | ||
| 166 | (cdr (assoc | ||
| 167 | (completing-read | ||
| 168 | "[eglot] More than one server executable available:" | ||
| 169 | (mapcar #'car available) | ||
| 170 | nil t nil nil (car (car available))) | ||
| 171 | available #'equal))) | ||
| 172 | ((cdr (car available))) | ||
| 173 | (t | ||
| 174 | ;; Don't error when used interactively, let the | ||
| 175 | ;; Eglot prompt the user for alternative (github#719) | ||
| 176 | nil)))) | ||
| 177 | (t | ||
| 178 | (cl-loop for (p . args) in listified | ||
| 179 | for probe = (eglot--executable-find p t) | ||
| 180 | when probe return (cons probe args) | ||
| 181 | finally (funcall err))))))) | ||
| 182 | |||
| 183 | (defvar eglot-server-programs `((rust-mode . ,(eglot-alternatives '("rust-analyzer" "rls"))) | ||
| 184 | (cmake-mode . ("cmake-language-server")) | ||
| 185 | (vimrc-mode . ("vim-language-server" "--stdio")) | ||
| 186 | (python-mode | ||
| 187 | . ,(eglot-alternatives | ||
| 188 | '("pylsp" "pyls" ("pyright-langserver" "--stdio") "jedi-language-server"))) | ||
| 189 | ((js-mode typescript-mode) | ||
| 190 | . ("typescript-language-server" "--stdio")) | ||
| 191 | (sh-mode . ("bash-language-server" "start")) | ||
| 192 | ((php-mode phps-mode) | ||
| 193 | . ("php" "vendor/felixfbecker/\ | ||
| 194 | language-server/bin/php-language-server.php")) | ||
| 195 | ((c++-mode c-mode) . ,(eglot-alternatives | ||
| 196 | '("clangd" "ccls"))) | ||
| 197 | (((caml-mode :language-id "ocaml") | ||
| 198 | (tuareg-mode :language-id "ocaml") reason-mode) | ||
| 199 | . ("ocamllsp")) | ||
| 200 | (ruby-mode | ||
| 201 | . ("solargraph" "socket" "--port" :autoport)) | ||
| 202 | (haskell-mode | ||
| 203 | . ("haskell-language-server-wrapper" "--lsp")) | ||
| 204 | (elm-mode . ("elm-language-server")) | ||
| 205 | (mint-mode . ("mint" "ls")) | ||
| 206 | (kotlin-mode . ("kotlin-language-server")) | ||
| 207 | (go-mode . ("gopls")) | ||
| 208 | ((R-mode ess-r-mode) . ("R" "--slave" "-e" | ||
| 209 | "languageserver::run()")) | ||
| 210 | (java-mode . ("jdtls")) | ||
| 211 | (dart-mode . ("dart" "language-server" | ||
| 212 | "--client-id" "emacs.eglot-dart")) | ||
| 213 | (elixir-mode . ("language_server.sh")) | ||
| 214 | (ada-mode . ("ada_language_server")) | ||
| 215 | (scala-mode . ("metals-emacs")) | ||
| 216 | (racket-mode . ("racket" "-l" "racket-langserver")) | ||
| 217 | ((tex-mode context-mode texinfo-mode bibtex-mode) | ||
| 218 | . ("digestif")) | ||
| 219 | (erlang-mode . ("erlang_ls" "--transport" "stdio")) | ||
| 220 | (yaml-mode . ("yaml-language-server" "--stdio")) | ||
| 221 | (nix-mode . ("rnix-lsp")) | ||
| 222 | (gdscript-mode . ("localhost" 6008)) | ||
| 223 | ((fortran-mode f90-mode) . ("fortls")) | ||
| 224 | (futhark-mode . ("futhark" "lsp")) | ||
| 225 | (lua-mode . ("lua-lsp")) | ||
| 226 | (zig-mode . ("zls")) | ||
| 227 | (css-mode . ,(eglot-alternatives '(("vscode-css-language-server" "--stdio") ("css-languageserver" "--stdio")))) | ||
| 228 | (html-mode . ,(eglot-alternatives '(("vscode-html-language-server" "--stdio") ("html-languageserver" "--stdio")))) | ||
| 229 | (json-mode . ,(eglot-alternatives '(("vscode-json-language-server" "--stdio") ("json-languageserver" "--stdio")))) | ||
| 230 | (dockerfile-mode . ("docker-langserver" "--stdio")) | ||
| 231 | ((clojure-mode clojurescript-mode clojurec-mode) | ||
| 232 | . ("clojure-lsp")) | ||
| 233 | (csharp-mode . ("omnisharp" "-lsp")) | ||
| 234 | (purescript-mode . ("purescript-language-server" "--stdio")) | ||
| 235 | (perl-mode . ("perl" "-MPerl::LanguageServer" "-e" "Perl::LanguageServer::run")) | ||
| 236 | (markdown-mode . ("marksman" "server"))) | ||
| 237 | "How the command `eglot' guesses the server to start. | ||
| 238 | An association list of (MAJOR-MODE . CONTACT) pairs. MAJOR-MODE | ||
| 239 | identifies the buffers that are to be managed by a specific | ||
| 240 | language server. The associated CONTACT specifies how to connect | ||
| 241 | to a server for those buffers. | ||
| 242 | |||
| 243 | MAJOR-MODE can be: | ||
| 244 | |||
| 245 | * In the most common case, a symbol such as `c-mode'; | ||
| 246 | |||
| 247 | * A list (MAJOR-MODE-SYMBOL :LANGUAGE-ID ID) where | ||
| 248 | MAJOR-MODE-SYMBOL is the aforementioned symbol and ID is a | ||
| 249 | string identifying the language to the server; | ||
| 250 | |||
| 251 | * A list combining the previous two alternatives, meaning | ||
| 252 | multiple major modes will be associated with a single server | ||
| 253 | program. This association is such that the same resulting | ||
| 254 | server process will manage buffers of different major modes. | ||
| 255 | |||
| 256 | CONTACT can be: | ||
| 257 | |||
| 258 | * In the most common case, a list of strings (PROGRAM [ARGS...]). | ||
| 259 | PROGRAM is called with ARGS and is expected to serve LSP requests | ||
| 260 | over the standard input/output channels. | ||
| 261 | |||
| 262 | * A list (PROGRAM [ARGS...] :initializationOptions OPTIONS), | ||
| 263 | whereupon PROGRAM is called with ARGS as in the first option, | ||
| 264 | and the LSP \"initializationOptions\" JSON object is | ||
| 265 | constructed from OPTIONS. If OPTIONS is a unary function, it | ||
| 266 | is called with the server instance and should return a JSON | ||
| 267 | object. | ||
| 268 | |||
| 269 | * A list (HOST PORT [TCP-ARGS...]) where HOST is a string and | ||
| 270 | PORT is a positive integer for connecting to a server via TCP. | ||
| 271 | Remaining ARGS are passed to `open-network-stream' for | ||
| 272 | upgrading the connection with encryption or other capabilities. | ||
| 273 | |||
| 274 | * A list (PROGRAM [ARGS...] :autoport [MOREARGS...]), whereupon a | ||
| 275 | combination of previous options is used. First, an attempt is | ||
| 276 | made to find an available server port, then PROGRAM is launched | ||
| 277 | with ARGS; the `:autoport' keyword substituted for that number; | ||
| 278 | and MOREARGS. Eglot then attempts to establish a TCP | ||
| 279 | connection to that port number on the localhost. | ||
| 280 | |||
| 281 | * A cons (CLASS-NAME . INITARGS) where CLASS-NAME is a symbol | ||
| 282 | designating a subclass of `eglot-lsp-server', for representing | ||
| 283 | experimental LSP servers. INITARGS is a keyword-value plist | ||
| 284 | used to initialize the object of CLASS-NAME, or a plain list | ||
| 285 | interpreted as the previous descriptions of CONTACT. In the | ||
| 286 | latter case that plain list is used to produce a plist with a | ||
| 287 | suitable :PROCESS initarg to CLASS-NAME. The class | ||
| 288 | `eglot-lsp-server' descends from `jsonrpc-process-connection', | ||
| 289 | which you should see for the semantics of the mandatory | ||
| 290 | :PROCESS argument. | ||
| 291 | |||
| 292 | * A function of a single argument producing any of the above | ||
| 293 | values for CONTACT. The argument's value is non-nil if the | ||
| 294 | connection was requested interactively (e.g. from the `eglot' | ||
| 295 | command), and nil if it wasn't (e.g. from `eglot-ensure'). If | ||
| 296 | the call is interactive, the function can ask the user for | ||
| 297 | hints on finding the required programs, etc. Otherwise, it | ||
| 298 | should not ask the user for any input, and return nil or signal | ||
| 299 | an error if it can't produce a valid CONTACT.") | ||
| 300 | |||
| 301 | (defface eglot-highlight-symbol-face | ||
| 302 | '((t (:inherit bold))) | ||
| 303 | "Face used to highlight the symbol at point.") | ||
| 304 | |||
| 305 | (defface eglot-mode-line | ||
| 306 | '((t (:inherit font-lock-constant-face :weight bold))) | ||
| 307 | "Face for package-name in Eglot's mode line.") | ||
| 308 | |||
| 309 | (defface eglot-diagnostic-tag-unnecessary-face | ||
| 310 | '((t (:inherit shadow))) | ||
| 311 | "Face used to render unused or unnecessary code.") | ||
| 312 | |||
| 313 | (defface eglot-diagnostic-tag-deprecated-face | ||
| 314 | '((t . (:inherit shadow :strike-through t))) | ||
| 315 | "Face used to render deprecated or obsolete code.") | ||
| 316 | |||
| 317 | (defcustom eglot-autoreconnect 3 | ||
| 318 | "Control ability to reconnect automatically to the LSP server. | ||
| 319 | If t, always reconnect automatically (not recommended). If nil, | ||
| 320 | never reconnect automatically after unexpected server shutdowns, | ||
| 321 | crashes or network failures. A positive integer number says to | ||
| 322 | only autoreconnect if the previous successful connection attempt | ||
| 323 | lasted more than that many seconds." | ||
| 324 | :type '(choice (boolean :tag "Whether to inhibit autoreconnection") | ||
| 325 | (integer :tag "Number of seconds"))) | ||
| 326 | |||
| 327 | (defcustom eglot-connect-timeout 30 | ||
| 328 | "Number of seconds before timing out LSP connection attempts. | ||
| 329 | If nil, never time out." | ||
| 330 | :type 'number) | ||
| 331 | |||
| 332 | (defcustom eglot-sync-connect 3 | ||
| 333 | "Control blocking of LSP connection attempts. | ||
| 334 | If t, block for `eglot-connect-timeout' seconds. A positive | ||
| 335 | integer number means block for that many seconds, and then wait | ||
| 336 | for the connection in the background. nil has the same meaning | ||
| 337 | as 0, i.e. don't block at all." | ||
| 338 | :type '(choice (boolean :tag "Whether to inhibit autoreconnection") | ||
| 339 | (integer :tag "Number of seconds"))) | ||
| 340 | |||
| 341 | (defcustom eglot-autoshutdown nil | ||
| 342 | "If non-nil, shut down server after killing last managed buffer." | ||
| 343 | :type 'boolean) | ||
| 344 | |||
| 345 | (defcustom eglot-send-changes-idle-time 0.5 | ||
| 346 | "Don't tell server of changes before Emacs's been idle for this many seconds." | ||
| 347 | :type 'number) | ||
| 348 | |||
| 349 | (defcustom eglot-events-buffer-size 2000000 | ||
| 350 | "Control the size of the Eglot events buffer. | ||
| 351 | If a number, don't let the buffer grow larger than that many | ||
| 352 | characters. If 0, don't use an event's buffer at all. If nil, | ||
| 353 | let the buffer grow forever. | ||
| 354 | |||
| 355 | For changes on this variable to take effect on a connection | ||
| 356 | already started, you need to restart the connection. That can be | ||
| 357 | done by `eglot-reconnect'." | ||
| 358 | :type '(choice (const :tag "No limit" nil) | ||
| 359 | (integer :tag "Number of characters"))) | ||
| 360 | |||
| 361 | (defcustom eglot-confirm-server-initiated-edits 'confirm | ||
| 362 | "Non-nil if server-initiated edits should be confirmed with user." | ||
| 363 | :type '(choice (const :tag "Don't show confirmation prompt" nil) | ||
| 364 | (symbol :tag "Show confirmation prompt" 'confirm))) | ||
| 365 | |||
| 366 | (defcustom eglot-extend-to-xref nil | ||
| 367 | "If non-nil, activate Eglot in cross-referenced non-project files." | ||
| 368 | :type 'boolean) | ||
| 369 | |||
| 370 | (defcustom eglot-menu-string "eglot" | ||
| 371 | "String displayed in mode line when Eglot is active." | ||
| 372 | :type 'string) | ||
| 373 | |||
| 374 | (defvar eglot-withhold-process-id nil | ||
| 375 | "If non-nil, Eglot will not send the Emacs process id to the language server. | ||
| 376 | This can be useful when using docker to run a language server.") | ||
| 377 | |||
| 378 | ;; Customizable via `completion-category-overrides'. | ||
| 379 | (when (assoc 'flex completion-styles-alist) | ||
| 380 | (add-to-list 'completion-category-defaults '(eglot (styles flex basic)))) | ||
| 381 | |||
| 382 | |||
| 383 | ;;; Constants | ||
| 384 | ;;; | ||
| 385 | (defconst eglot--symbol-kind-names | ||
| 386 | `((1 . "File") (2 . "Module") | ||
| 387 | (3 . "Namespace") (4 . "Package") (5 . "Class") | ||
| 388 | (6 . "Method") (7 . "Property") (8 . "Field") | ||
| 389 | (9 . "Constructor") (10 . "Enum") (11 . "Interface") | ||
| 390 | (12 . "Function") (13 . "Variable") (14 . "Constant") | ||
| 391 | (15 . "String") (16 . "Number") (17 . "Boolean") | ||
| 392 | (18 . "Array") (19 . "Object") (20 . "Key") | ||
| 393 | (21 . "Null") (22 . "EnumMember") (23 . "Struct") | ||
| 394 | (24 . "Event") (25 . "Operator") (26 . "TypeParameter"))) | ||
| 395 | |||
| 396 | (defconst eglot--kind-names | ||
| 397 | `((1 . "Text") (2 . "Method") (3 . "Function") (4 . "Constructor") | ||
| 398 | (5 . "Field") (6 . "Variable") (7 . "Class") (8 . "Interface") | ||
| 399 | (9 . "Module") (10 . "Property") (11 . "Unit") (12 . "Value") | ||
| 400 | (13 . "Enum") (14 . "Keyword") (15 . "Snippet") (16 . "Color") | ||
| 401 | (17 . "File") (18 . "Reference") (19 . "Folder") (20 . "EnumMember") | ||
| 402 | (21 . "Constant") (22 . "Struct") (23 . "Event") (24 . "Operator") | ||
| 403 | (25 . "TypeParameter"))) | ||
| 404 | |||
| 405 | (defconst eglot--tag-faces | ||
| 406 | `((1 . eglot-diagnostic-tag-unnecessary-face) | ||
| 407 | (2 . eglot-diagnostic-tag-deprecated-face))) | ||
| 408 | |||
| 409 | (defvaralias 'eglot-{} 'eglot--{}) | ||
| 410 | (defconst eglot--{} (make-hash-table :size 1) "The empty JSON object.") | ||
| 411 | |||
| 412 | (defun eglot--executable-find (command &optional remote) | ||
| 413 | "Like Emacs 27's `executable-find', ignore REMOTE on Emacs 26." | ||
| 414 | (if (>= emacs-major-version 27) (executable-find command remote) | ||
| 415 | (executable-find command))) | ||
| 416 | |||
| 417 | |||
| 418 | ;;; Message verification helpers | ||
| 419 | ;;; | ||
| 420 | (eval-and-compile | ||
| 421 | (defvar eglot--lsp-interface-alist | ||
| 422 | `( | ||
| 423 | (CodeAction (:title) (:kind :diagnostics :edit :command :isPreferred)) | ||
| 424 | (ConfigurationItem () (:scopeUri :section)) | ||
| 425 | (Command ((:title . string) (:command . string)) (:arguments)) | ||
| 426 | (CompletionItem (:label) | ||
| 427 | (:kind :detail :documentation :deprecated :preselect | ||
| 428 | :sortText :filterText :insertText :insertTextFormat | ||
| 429 | :textEdit :additionalTextEdits :commitCharacters | ||
| 430 | :command :data :tags)) | ||
| 431 | (Diagnostic (:range :message) (:severity :code :source :relatedInformation :codeDescription :tags)) | ||
| 432 | (DocumentHighlight (:range) (:kind)) | ||
| 433 | (FileSystemWatcher (:globPattern) (:kind)) | ||
| 434 | (Hover (:contents) (:range)) | ||
| 435 | (InitializeResult (:capabilities) (:serverInfo)) | ||
| 436 | (Location (:uri :range)) | ||
| 437 | (LocationLink (:targetUri :targetRange :targetSelectionRange) (:originSelectionRange)) | ||
| 438 | (LogMessageParams (:type :message)) | ||
| 439 | (MarkupContent (:kind :value)) | ||
| 440 | (ParameterInformation (:label) (:documentation)) | ||
| 441 | (Position (:line :character)) | ||
| 442 | (Range (:start :end)) | ||
| 443 | (Registration (:id :method) (:registerOptions)) | ||
| 444 | (ResponseError (:code :message) (:data)) | ||
| 445 | (ShowMessageParams (:type :message)) | ||
| 446 | (ShowMessageRequestParams (:type :message) (:actions)) | ||
| 447 | (SignatureHelp (:signatures) (:activeSignature :activeParameter)) | ||
| 448 | (SignatureInformation (:label) (:documentation :parameters :activeParameter)) | ||
| 449 | (SymbolInformation (:name :kind :location) | ||
| 450 | (:deprecated :containerName)) | ||
| 451 | (DocumentSymbol (:name :range :selectionRange :kind) | ||
| 452 | ;; `:containerName' isn't really allowed , but | ||
| 453 | ;; it simplifies the impl of `eglot-imenu'. | ||
| 454 | (:detail :deprecated :children :containerName)) | ||
| 455 | (TextDocumentEdit (:textDocument :edits) ()) | ||
| 456 | (TextEdit (:range :newText)) | ||
| 457 | (VersionedTextDocumentIdentifier (:uri :version) ()) | ||
| 458 | (WorkspaceEdit () (:changes :documentChanges)) | ||
| 459 | (WorkspaceSymbol (:name :kind) (:containerName :location :data))) | ||
| 460 | "Alist (INTERFACE-NAME . INTERFACE) of known external LSP interfaces. | ||
| 461 | |||
| 462 | INTERFACE-NAME is a symbol designated by the spec as | ||
| 463 | \"interface\". INTERFACE is a list (REQUIRED OPTIONAL) where | ||
| 464 | REQUIRED and OPTIONAL are lists of KEYWORD designating field | ||
| 465 | names that must be, or may be, respectively, present in a message | ||
| 466 | adhering to that interface. KEY can be a keyword or a cons (SYM | ||
| 467 | TYPE), where type is used by `cl-typep' to check types at | ||
| 468 | runtime. | ||
| 469 | |||
| 470 | Here's what an element of this alist might look like: | ||
| 471 | |||
| 472 | (Command ((:title . string) (:command . string)) (:arguments))")) | ||
| 473 | |||
| 474 | (eval-and-compile | ||
| 475 | (defvar eglot-strict-mode | ||
| 476 | '(;; Uncomment next lines for fun and debugging | ||
| 477 | ;; disallow-non-standard-keys | ||
| 478 | ;; enforce-required-keys | ||
| 479 | ;; enforce-optional-keys | ||
| 480 | ) | ||
| 481 | "How strictly to check LSP interfaces at compile- and run-time. | ||
| 482 | |||
| 483 | Value is a list of symbols (if the list is empty, no checks are | ||
| 484 | performed). | ||
| 485 | |||
| 486 | If the symbol `disallow-non-standard-keys' is present, an error | ||
| 487 | is raised if any extraneous fields are sent by the server. At | ||
| 488 | compile-time, a warning is raised if a destructuring spec | ||
| 489 | includes such a field. | ||
| 490 | |||
| 491 | If the symbol `enforce-required-keys' is present, an error is | ||
| 492 | raised if any required fields are missing from the message sent | ||
| 493 | from the server. At compile-time, a warning is raised if a | ||
| 494 | destructuring spec doesn't use such a field. | ||
| 495 | |||
| 496 | If the symbol `enforce-optional-keys' is present, nothing special | ||
| 497 | happens at run-time. At compile-time, a warning is raised if a | ||
| 498 | destructuring spec doesn't use all optional fields. | ||
| 499 | |||
| 500 | If the symbol `disallow-unknown-methods' is present, Eglot warns | ||
| 501 | on unknown notifications and errors on unknown requests.")) | ||
| 502 | |||
| 503 | (cl-defun eglot--check-object (interface-name | ||
| 504 | object | ||
| 505 | &optional | ||
| 506 | (enforce-required t) | ||
| 507 | (disallow-non-standard t) | ||
| 508 | (check-types t)) | ||
| 509 | "Check that OBJECT conforms to INTERFACE. Error otherwise." | ||
| 510 | (cl-destructuring-bind | ||
| 511 | (&key types required-keys optional-keys &allow-other-keys) | ||
| 512 | (eglot--interface interface-name) | ||
| 513 | (when-let ((missing (and enforce-required | ||
| 514 | (cl-set-difference required-keys | ||
| 515 | (eglot--plist-keys object))))) | ||
| 516 | (eglot--error "A `%s' must have %s" interface-name missing)) | ||
| 517 | (when-let ((excess (and disallow-non-standard | ||
| 518 | (cl-set-difference | ||
| 519 | (eglot--plist-keys object) | ||
| 520 | (append required-keys optional-keys))))) | ||
| 521 | (eglot--error "A `%s' mustn't have %s" interface-name excess)) | ||
| 522 | (when check-types | ||
| 523 | (cl-loop | ||
| 524 | for (k v) on object by #'cddr | ||
| 525 | for type = (or (cdr (assoc k types)) t) ;; FIXME: enforce nil type? | ||
| 526 | unless (cl-typep v type) | ||
| 527 | do (eglot--error "A `%s' must have a %s as %s, but has %s" | ||
| 528 | interface-name ))) | ||
| 529 | t)) | ||
| 530 | |||
| 531 | (eval-and-compile | ||
| 532 | (defun eglot--keywordize-vars (vars) | ||
| 533 | (mapcar (lambda (var) (intern (format ":%s" var))) vars)) | ||
| 534 | |||
| 535 | (defun eglot--ensure-type (k) (if (consp k) k (cons k t))) | ||
| 536 | |||
| 537 | (defun eglot--interface (interface-name) | ||
| 538 | (let* ((interface (assoc interface-name eglot--lsp-interface-alist)) | ||
| 539 | (required (mapcar #'eglot--ensure-type (car (cdr interface)))) | ||
| 540 | (optional (mapcar #'eglot--ensure-type (cadr (cdr interface))))) | ||
| 541 | (list :types (append required optional) | ||
| 542 | :required-keys (mapcar #'car required) | ||
| 543 | :optional-keys (mapcar #'car optional)))) | ||
| 544 | |||
| 545 | (defun eglot--check-dspec (interface-name dspec) | ||
| 546 | "Check destructuring spec DSPEC against INTERFACE-NAME." | ||
| 547 | (cl-destructuring-bind (&key required-keys optional-keys &allow-other-keys) | ||
| 548 | (eglot--interface interface-name) | ||
| 549 | (cond ((or required-keys optional-keys) | ||
| 550 | (let ((too-many | ||
| 551 | (and | ||
| 552 | (memq 'disallow-non-standard-keys eglot-strict-mode) | ||
| 553 | (cl-set-difference | ||
| 554 | (eglot--keywordize-vars dspec) | ||
| 555 | (append required-keys optional-keys)))) | ||
| 556 | (ignored-required | ||
| 557 | (and | ||
| 558 | (memq 'enforce-required-keys eglot-strict-mode) | ||
| 559 | (cl-set-difference | ||
| 560 | required-keys (eglot--keywordize-vars dspec)))) | ||
| 561 | (missing-out | ||
| 562 | (and | ||
| 563 | (memq 'enforce-optional-keys eglot-strict-mode) | ||
| 564 | (cl-set-difference | ||
| 565 | optional-keys (eglot--keywordize-vars dspec))))) | ||
| 566 | (when too-many (byte-compile-warn | ||
| 567 | "Destructuring for %s has extraneous %s" | ||
| 568 | interface-name too-many)) | ||
| 569 | (when ignored-required (byte-compile-warn | ||
| 570 | "Destructuring for %s ignores required %s" | ||
| 571 | interface-name ignored-required)) | ||
| 572 | (when missing-out (byte-compile-warn | ||
| 573 | "Destructuring for %s is missing out on %s" | ||
| 574 | interface-name missing-out)))) | ||
| 575 | (t | ||
| 576 | (byte-compile-warn "Unknown LSP interface %s" interface-name)))))) | ||
| 577 | |||
| 578 | (cl-defmacro eglot--dbind (vars object &body body) | ||
| 579 | "Destructure OBJECT, binding VARS in BODY. | ||
| 580 | VARS is ([(INTERFACE)] SYMS...) | ||
| 581 | Honour `eglot-strict-mode'." | ||
| 582 | (declare (indent 2) (debug (sexp sexp &rest form))) | ||
| 583 | (let ((interface-name (if (consp (car vars)) | ||
| 584 | (car (pop vars)))) | ||
| 585 | (object-once (make-symbol "object-once")) | ||
| 586 | (fn-once (make-symbol "fn-once"))) | ||
| 587 | (cond (interface-name | ||
| 588 | (eglot--check-dspec interface-name vars) | ||
| 589 | `(let ((,object-once ,object)) | ||
| 590 | (cl-destructuring-bind (&key ,@vars &allow-other-keys) ,object-once | ||
| 591 | (eglot--check-object ',interface-name ,object-once | ||
| 592 | (memq 'enforce-required-keys eglot-strict-mode) | ||
| 593 | (memq 'disallow-non-standard-keys eglot-strict-mode) | ||
| 594 | (memq 'check-types eglot-strict-mode)) | ||
| 595 | ,@body))) | ||
| 596 | (t | ||
| 597 | `(let ((,object-once ,object) | ||
| 598 | (,fn-once (lambda (,@vars) ,@body))) | ||
| 599 | (if (memq 'disallow-non-standard-keys eglot-strict-mode) | ||
| 600 | (cl-destructuring-bind (&key ,@vars) ,object-once | ||
| 601 | (funcall ,fn-once ,@vars)) | ||
| 602 | (cl-destructuring-bind (&key ,@vars &allow-other-keys) ,object-once | ||
| 603 | (funcall ,fn-once ,@vars)))))))) | ||
| 604 | |||
| 605 | |||
| 606 | (cl-defmacro eglot--lambda (cl-lambda-list &body body) | ||
| 607 | "Function of args CL-LAMBDA-LIST for processing INTERFACE objects. | ||
| 608 | Honour `eglot-strict-mode'." | ||
| 609 | (declare (indent 1) (debug (sexp &rest form))) | ||
| 610 | (let ((e (cl-gensym "jsonrpc-lambda-elem"))) | ||
| 611 | `(lambda (,e) (eglot--dbind ,cl-lambda-list ,e ,@body)))) | ||
| 612 | |||
| 613 | (cl-defmacro eglot--dcase (obj &rest clauses) | ||
| 614 | "Like `pcase', but for the LSP object OBJ. | ||
| 615 | CLAUSES is a list (DESTRUCTURE FORMS...) where DESTRUCTURE is | ||
| 616 | treated as in `eglot-dbind'." | ||
| 617 | (declare (indent 1) (debug (sexp &rest (sexp &rest form)))) | ||
| 618 | (let ((obj-once (make-symbol "obj-once"))) | ||
| 619 | `(let ((,obj-once ,obj)) | ||
| 620 | (cond | ||
| 621 | ,@(cl-loop | ||
| 622 | for (vars . body) in clauses | ||
| 623 | for vars-as-keywords = (eglot--keywordize-vars vars) | ||
| 624 | for interface-name = (if (consp (car vars)) | ||
| 625 | (car (pop vars))) | ||
| 626 | for condition = | ||
| 627 | (cond (interface-name | ||
| 628 | (eglot--check-dspec interface-name vars) | ||
| 629 | ;; In this mode, in runtime, we assume | ||
| 630 | ;; `eglot-strict-mode' is partially on, otherwise we | ||
| 631 | ;; can't disambiguate between certain types. | ||
| 632 | `(ignore-errors | ||
| 633 | (eglot--check-object | ||
| 634 | ',interface-name ,obj-once | ||
| 635 | t | ||
| 636 | (memq 'disallow-non-standard-keys eglot-strict-mode) | ||
| 637 | t))) | ||
| 638 | (t | ||
| 639 | ;; In this interface-less mode we don't check | ||
| 640 | ;; `eglot-strict-mode' at all: just check that the object | ||
| 641 | ;; has all the keys the user wants to destructure. | ||
| 642 | `(null (cl-set-difference | ||
| 643 | ',vars-as-keywords | ||
| 644 | (eglot--plist-keys ,obj-once))))) | ||
| 645 | collect `(,condition | ||
| 646 | (cl-destructuring-bind (&key ,@vars &allow-other-keys) | ||
| 647 | ,obj-once | ||
| 648 | ,@body))) | ||
| 649 | (t | ||
| 650 | (eglot--error "%S didn't match any of %S" | ||
| 651 | ,obj-once | ||
| 652 | ',(mapcar #'car clauses))))))) | ||
| 653 | |||
| 654 | |||
| 655 | ;;; API (WORK-IN-PROGRESS!) | ||
| 656 | ;;; | ||
| 657 | (cl-defmacro eglot--when-live-buffer (buf &rest body) | ||
| 658 | "Check BUF live, then do BODY in it." (declare (indent 1) (debug t)) | ||
| 659 | (let ((b (cl-gensym))) | ||
| 660 | `(let ((,b ,buf)) (if (buffer-live-p ,b) (with-current-buffer ,b ,@body))))) | ||
| 661 | |||
| 662 | (cl-defmacro eglot--when-buffer-window (buf &body body) | ||
| 663 | "Check BUF showing somewhere, then do BODY in it." (declare (indent 1) (debug t)) | ||
| 664 | (let ((b (cl-gensym))) | ||
| 665 | `(let ((,b ,buf)) | ||
| 666 | ;;notice the exception when testing with `ert' | ||
| 667 | (when (or (get-buffer-window ,b) (ert-running-test)) | ||
| 668 | (with-current-buffer ,b ,@body))))) | ||
| 669 | |||
| 670 | (cl-defmacro eglot--widening (&rest body) | ||
| 671 | "Save excursion and restriction. Widen. Then run BODY." (declare (debug t)) | ||
| 672 | `(save-excursion (save-restriction (widen) ,@body))) | ||
| 673 | |||
| 674 | (cl-defgeneric eglot-handle-request (server method &rest params) | ||
| 675 | "Handle SERVER's METHOD request with PARAMS.") | ||
| 676 | |||
| 677 | (cl-defgeneric eglot-handle-notification (server method &rest params) | ||
| 678 | "Handle SERVER's METHOD notification with PARAMS.") | ||
| 679 | |||
| 680 | (cl-defgeneric eglot-execute-command (server command arguments) | ||
| 681 | "Ask SERVER to execute COMMAND with ARGUMENTS.") | ||
| 682 | |||
| 683 | (cl-defgeneric eglot-initialization-options (server) | ||
| 684 | "JSON object to send under `initializationOptions'." | ||
| 685 | (:method (s) | ||
| 686 | (let ((probe (plist-get (eglot--saved-initargs s) :initializationOptions))) | ||
| 687 | (cond ((functionp probe) (funcall probe s)) | ||
| 688 | (probe) | ||
| 689 | (t eglot--{}))))) | ||
| 690 | |||
| 691 | (cl-defgeneric eglot-register-capability (server method id &rest params) | ||
| 692 | "Ask SERVER to register capability METHOD marked with ID." | ||
| 693 | (:method | ||
| 694 | (_s method _id &rest _params) | ||
| 695 | (eglot--warn "Server tried to register unsupported capability `%s'" | ||
| 696 | method))) | ||
| 697 | |||
| 698 | (cl-defgeneric eglot-unregister-capability (server method id &rest params) | ||
| 699 | "Ask SERVER to register capability METHOD marked with ID." | ||
| 700 | (:method | ||
| 701 | (_s method _id &rest _params) | ||
| 702 | (eglot--warn "Server tried to unregister unsupported capability `%s'" | ||
| 703 | method))) | ||
| 704 | |||
| 705 | (cl-defgeneric eglot-client-capabilities (server) | ||
| 706 | "What the Eglot LSP client supports for SERVER." | ||
| 707 | (:method (s) | ||
| 708 | (list | ||
| 709 | :workspace (list | ||
| 710 | :applyEdit t | ||
| 711 | :executeCommand `(:dynamicRegistration :json-false) | ||
| 712 | :workspaceEdit `(:documentChanges t) | ||
| 713 | :didChangeWatchedFiles | ||
| 714 | `(:dynamicRegistration | ||
| 715 | ,(if (eglot--trampish-p s) :json-false t)) | ||
| 716 | :symbol `(:dynamicRegistration :json-false) | ||
| 717 | :configuration t | ||
| 718 | :workspaceFolders t) | ||
| 719 | :textDocument | ||
| 720 | (list | ||
| 721 | :synchronization (list | ||
| 722 | :dynamicRegistration :json-false | ||
| 723 | :willSave t :willSaveWaitUntil t :didSave t) | ||
| 724 | :completion (list :dynamicRegistration :json-false | ||
| 725 | :completionItem | ||
| 726 | `(:snippetSupport | ||
| 727 | ,(if (eglot--snippet-expansion-fn) | ||
| 728 | t | ||
| 729 | :json-false) | ||
| 730 | :deprecatedSupport t | ||
| 731 | :tagSupport (:valueSet [1])) | ||
| 732 | :contextSupport t) | ||
| 733 | :hover (list :dynamicRegistration :json-false | ||
| 734 | :contentFormat | ||
| 735 | (if (fboundp 'gfm-view-mode) | ||
| 736 | ["markdown" "plaintext"] | ||
| 737 | ["plaintext"])) | ||
| 738 | :signatureHelp (list :dynamicRegistration :json-false | ||
| 739 | :signatureInformation | ||
| 740 | `(:parameterInformation | ||
| 741 | (:labelOffsetSupport t) | ||
| 742 | :activeParameterSupport t)) | ||
| 743 | :references `(:dynamicRegistration :json-false) | ||
| 744 | :definition (list :dynamicRegistration :json-false | ||
| 745 | :linkSupport t) | ||
| 746 | :declaration (list :dynamicRegistration :json-false | ||
| 747 | :linkSupport t) | ||
| 748 | :implementation (list :dynamicRegistration :json-false | ||
| 749 | :linkSupport t) | ||
| 750 | :typeDefinition (list :dynamicRegistration :json-false | ||
| 751 | :linkSupport t) | ||
| 752 | :documentSymbol (list | ||
| 753 | :dynamicRegistration :json-false | ||
| 754 | :hierarchicalDocumentSymbolSupport t | ||
| 755 | :symbolKind `(:valueSet | ||
| 756 | [,@(mapcar | ||
| 757 | #'car eglot--symbol-kind-names)])) | ||
| 758 | :documentHighlight `(:dynamicRegistration :json-false) | ||
| 759 | :codeAction (list | ||
| 760 | :dynamicRegistration :json-false | ||
| 761 | :codeActionLiteralSupport | ||
| 762 | '(:codeActionKind | ||
| 763 | (:valueSet | ||
| 764 | ["quickfix" | ||
| 765 | "refactor" "refactor.extract" | ||
| 766 | "refactor.inline" "refactor.rewrite" | ||
| 767 | "source" "source.organizeImports"])) | ||
| 768 | :isPreferredSupport t) | ||
| 769 | :formatting `(:dynamicRegistration :json-false) | ||
| 770 | :rangeFormatting `(:dynamicRegistration :json-false) | ||
| 771 | :rename `(:dynamicRegistration :json-false) | ||
| 772 | :publishDiagnostics (list :relatedInformation :json-false | ||
| 773 | ;; TODO: We can support :codeDescription after | ||
| 774 | ;; adding an appropriate UI to | ||
| 775 | ;; Flymake. | ||
| 776 | :codeDescriptionSupport :json-false | ||
| 777 | :tagSupport | ||
| 778 | `(:valueSet | ||
| 779 | [,@(mapcar | ||
| 780 | #'car eglot--tag-faces)]))) | ||
| 781 | :experimental eglot--{}))) | ||
| 782 | |||
| 783 | (cl-defgeneric eglot-workspace-folders (server) | ||
| 784 | "Return workspaceFolders for SERVER." | ||
| 785 | (let ((project (eglot--project server))) | ||
| 786 | (vconcat | ||
| 787 | (mapcar (lambda (dir) | ||
| 788 | (list :uri (eglot--path-to-uri dir) | ||
| 789 | :name (abbreviate-file-name dir))) | ||
| 790 | `(,(project-root project) ,@(project-external-roots project)))))) | ||
| 791 | |||
| 792 | (defclass eglot-lsp-server (jsonrpc-process-connection) | ||
| 793 | ((project-nickname | ||
| 794 | :documentation "Short nickname for the associated project." | ||
| 795 | :accessor eglot--project-nickname | ||
| 796 | :reader eglot-project-nickname) | ||
| 797 | (major-modes | ||
| 798 | :documentation "Major modes server is responsible for in a given project." | ||
| 799 | :accessor eglot--major-modes) | ||
| 800 | (language-id | ||
| 801 | :documentation "Language ID string for the mode." | ||
| 802 | :accessor eglot--language-id) | ||
| 803 | (capabilities | ||
| 804 | :documentation "JSON object containing server capabilities." | ||
| 805 | :accessor eglot--capabilities) | ||
| 806 | (server-info | ||
| 807 | :documentation "JSON object containing server info." | ||
| 808 | :accessor eglot--server-info) | ||
| 809 | (shutdown-requested | ||
| 810 | :documentation "Flag set when server is shutting down." | ||
| 811 | :accessor eglot--shutdown-requested) | ||
| 812 | (project | ||
| 813 | :documentation "Project associated with server." | ||
| 814 | :accessor eglot--project) | ||
| 815 | (spinner | ||
| 816 | :documentation "List (ID DOING-WHAT DONE-P) representing server progress." | ||
| 817 | :initform `(nil nil t) :accessor eglot--spinner) | ||
| 818 | (inhibit-autoreconnect | ||
| 819 | :initform t | ||
| 820 | :documentation "Generalized boolean inhibiting auto-reconnection if true." | ||
| 821 | :accessor eglot--inhibit-autoreconnect) | ||
| 822 | (file-watches | ||
| 823 | :documentation "Map ID to list of WATCHES for `didChangeWatchedFiles'." | ||
| 824 | :initform (make-hash-table :test #'equal) :accessor eglot--file-watches) | ||
| 825 | (managed-buffers | ||
| 826 | :documentation "List of buffers managed by server." | ||
| 827 | :accessor eglot--managed-buffers) | ||
| 828 | (saved-initargs | ||
| 829 | :documentation "Saved initargs for reconnection purposes." | ||
| 830 | :accessor eglot--saved-initargs) | ||
| 831 | (inferior-process | ||
| 832 | :documentation "Server subprocess started automatically." | ||
| 833 | :accessor eglot--inferior-process)) | ||
| 834 | :documentation | ||
| 835 | "Represents a server. Wraps a process for LSP communication.") | ||
| 836 | |||
| 837 | (cl-defmethod initialize-instance :before ((_server eglot-lsp-server) &optional args) | ||
| 838 | (cl-remf args :initializationOptions)) | ||
| 839 | |||
| 840 | |||
| 841 | ;;; Process management | ||
| 842 | (defvar eglot--servers-by-project (make-hash-table :test #'equal) | ||
| 843 | "Keys are projects. Values are lists of processes.") | ||
| 844 | |||
| 845 | (defun eglot-shutdown (server &optional _interactive timeout preserve-buffers) | ||
| 846 | "Politely ask SERVER to quit. | ||
| 847 | Interactively, read SERVER from the minibuffer unless there is | ||
| 848 | only one and it's managing the current buffer. | ||
| 849 | |||
| 850 | Forcefully quit it if it doesn't respond within TIMEOUT seconds. | ||
| 851 | TIMEOUT defaults to 1.5 seconds. Don't leave this function with | ||
| 852 | the server still running. | ||
| 853 | |||
| 854 | If PRESERVE-BUFFERS is non-nil (interactively, when called with a | ||
| 855 | prefix argument), do not kill events and output buffers of | ||
| 856 | SERVER." | ||
| 857 | (interactive (list (eglot--read-server "Shutdown which server" | ||
| 858 | (eglot-current-server)) | ||
| 859 | t nil current-prefix-arg)) | ||
| 860 | (eglot--message "Asking %s politely to terminate" (jsonrpc-name server)) | ||
| 861 | (unwind-protect | ||
| 862 | (progn | ||
| 863 | (setf (eglot--shutdown-requested server) t) | ||
| 864 | (jsonrpc-request server :shutdown nil :timeout (or timeout 1.5)) | ||
| 865 | (jsonrpc-notify server :exit nil)) | ||
| 866 | ;; Now ask jsonrpc.el to shut down the server. | ||
| 867 | (jsonrpc-shutdown server (not preserve-buffers)) | ||
| 868 | (unless preserve-buffers (kill-buffer (jsonrpc-events-buffer server))))) | ||
| 869 | |||
| 870 | (defun eglot-shutdown-all (&optional preserve-buffers) | ||
| 871 | "Politely ask all language servers to quit, in order. | ||
| 872 | PRESERVE-BUFFERS as in `eglot-shutdown', which see." | ||
| 873 | (interactive (list current-prefix-arg)) | ||
| 874 | (cl-loop for ss being the hash-values of eglot--servers-by-project | ||
| 875 | do (cl-loop for s in ss do (eglot-shutdown s nil preserve-buffers)))) | ||
| 876 | |||
| 877 | (defun eglot--on-shutdown (server) | ||
| 878 | "Called by jsonrpc.el when SERVER is already dead." | ||
| 879 | ;; Turn off `eglot--managed-mode' where appropriate. | ||
| 880 | (dolist (buffer (eglot--managed-buffers server)) | ||
| 881 | (let (;; Avoid duplicate shutdowns (github#389) | ||
| 882 | (eglot-autoshutdown nil)) | ||
| 883 | (eglot--when-live-buffer buffer (eglot--managed-mode-off)))) | ||
| 884 | ;; Kill any expensive watches | ||
| 885 | (maphash (lambda (_id watches) | ||
| 886 | (mapcar #'file-notify-rm-watch watches)) | ||
| 887 | (eglot--file-watches server)) | ||
| 888 | ;; Kill any autostarted inferior processes | ||
| 889 | (when-let (proc (eglot--inferior-process server)) | ||
| 890 | (delete-process proc)) | ||
| 891 | ;; Sever the project/server relationship for `server' | ||
| 892 | (setf (gethash (eglot--project server) eglot--servers-by-project) | ||
| 893 | (delq server | ||
| 894 | (gethash (eglot--project server) eglot--servers-by-project))) | ||
| 895 | (cond ((eglot--shutdown-requested server) | ||
| 896 | t) | ||
| 897 | ((not (eglot--inhibit-autoreconnect server)) | ||
| 898 | (eglot--warn "Reconnecting after unexpected server exit.") | ||
| 899 | (eglot-reconnect server)) | ||
| 900 | ((timerp (eglot--inhibit-autoreconnect server)) | ||
| 901 | (eglot--warn "Not auto-reconnecting, last one didn't last long.")))) | ||
| 902 | |||
| 903 | (defun eglot--all-major-modes () | ||
| 904 | "Return all known major modes." | ||
| 905 | (let ((retval)) | ||
| 906 | (mapatoms (lambda (sym) | ||
| 907 | (when (plist-member (symbol-plist sym) 'derived-mode-parent) | ||
| 908 | (push sym retval)))) | ||
| 909 | retval)) | ||
| 910 | |||
| 911 | (defvar eglot--command-history nil | ||
| 912 | "History of CONTACT arguments to `eglot'.") | ||
| 913 | |||
| 914 | (defun eglot--lookup-mode (mode) | ||
| 915 | "Lookup `eglot-server-programs' for MODE. | ||
| 916 | Return (MANAGED-MODES LANGUAGE-ID CONTACT-PROXY). | ||
| 917 | |||
| 918 | MANAGED-MODES is a list with MODE as its first elements. | ||
| 919 | Subsequent elements are other major modes also potentially | ||
| 920 | managed by the server that is to manage MODE. | ||
| 921 | |||
| 922 | If not specified in `eglot-server-programs' (which see), | ||
| 923 | LANGUAGE-ID is determined from MODE's name. | ||
| 924 | |||
| 925 | CONTACT-PROXY is the value of the corresponding | ||
| 926 | `eglot-server-programs' entry." | ||
| 927 | (cl-loop | ||
| 928 | for (modes . contact) in eglot-server-programs | ||
| 929 | for mode-symbols = (cons mode | ||
| 930 | (delete mode | ||
| 931 | (mapcar #'car | ||
| 932 | (mapcar #'eglot--ensure-list | ||
| 933 | (eglot--ensure-list modes))))) | ||
| 934 | thereis (cl-some | ||
| 935 | (lambda (spec) | ||
| 936 | (cl-destructuring-bind (probe &key language-id &allow-other-keys) | ||
| 937 | (eglot--ensure-list spec) | ||
| 938 | (and (provided-mode-derived-p mode probe) | ||
| 939 | (list | ||
| 940 | mode-symbols | ||
| 941 | (or language-id | ||
| 942 | (or (get mode 'eglot-language-id) | ||
| 943 | (get spec 'eglot-language-id) | ||
| 944 | (string-remove-suffix "-mode" (symbol-name mode)))) | ||
| 945 | contact)))) | ||
| 946 | (if (or (symbolp modes) (keywordp (cadr modes))) | ||
| 947 | (list modes) modes)))) | ||
| 948 | |||
| 949 | (defun eglot--guess-contact (&optional interactive) | ||
| 950 | "Helper for `eglot'. | ||
| 951 | Return (MANAGED-MODE PROJECT CLASS CONTACT LANG-ID). If INTERACTIVE is | ||
| 952 | non-nil, maybe prompt user, else error as soon as something can't | ||
| 953 | be guessed." | ||
| 954 | (let* ((guessed-mode (if buffer-file-name major-mode)) | ||
| 955 | (main-mode | ||
| 956 | (cond | ||
| 957 | ((and interactive | ||
| 958 | (or (>= (prefix-numeric-value current-prefix-arg) 16) | ||
| 959 | (not guessed-mode))) | ||
| 960 | (intern | ||
| 961 | (completing-read | ||
| 962 | "[eglot] Start a server to manage buffers of what major mode? " | ||
| 963 | (mapcar #'symbol-name (eglot--all-major-modes)) nil t | ||
| 964 | (symbol-name guessed-mode) nil (symbol-name guessed-mode) nil))) | ||
| 965 | ((not guessed-mode) | ||
| 966 | (eglot--error "Can't guess mode to manage for `%s'" (current-buffer))) | ||
| 967 | (t guessed-mode))) | ||
| 968 | (triplet (eglot--lookup-mode main-mode)) | ||
| 969 | (managed-modes (car triplet)) | ||
| 970 | (language-id (or (cadr triplet) | ||
| 971 | (string-remove-suffix "-mode" (symbol-name guessed-mode)))) | ||
| 972 | (guess (caddr triplet)) | ||
| 973 | (guess (if (functionp guess) | ||
| 974 | (funcall guess interactive) | ||
| 975 | guess)) | ||
| 976 | (class (or (and (consp guess) (symbolp (car guess)) | ||
| 977 | (prog1 (unless current-prefix-arg (car guess)) | ||
| 978 | (setq guess (cdr guess)))) | ||
| 979 | 'eglot-lsp-server)) | ||
| 980 | (program (and (listp guess) | ||
| 981 | (stringp (car guess)) | ||
| 982 | ;; A second element might be the port of a (host, port) | ||
| 983 | ;; pair, but in that case it is not a string. | ||
| 984 | (or (null (cdr guess)) (stringp (cadr guess))) | ||
| 985 | (car guess))) | ||
| 986 | (base-prompt | ||
| 987 | (and interactive | ||
| 988 | "Enter program to execute (or <host>:<port>): ")) | ||
| 989 | (full-program-invocation | ||
| 990 | (and program | ||
| 991 | (cl-every #'stringp guess) | ||
| 992 | (combine-and-quote-strings guess))) | ||
| 993 | (prompt | ||
| 994 | (and base-prompt | ||
| 995 | (cond (current-prefix-arg base-prompt) | ||
| 996 | ((null guess) | ||
| 997 | (format "[eglot] Sorry, couldn't guess for `%s'!\n%s" | ||
| 998 | main-mode base-prompt)) | ||
| 999 | ((and program | ||
| 1000 | (not (file-name-absolute-p program)) | ||
| 1001 | (not (eglot--executable-find program t))) | ||
| 1002 | (if full-program-invocation | ||
| 1003 | (concat (format "[eglot] I guess you want to run `%s'" | ||
| 1004 | full-program-invocation) | ||
| 1005 | (format ", but I can't find `%s' in PATH!" | ||
| 1006 | program) | ||
| 1007 | "\n" base-prompt) | ||
| 1008 | (eglot--error | ||
| 1009 | (concat "`%s' not found in PATH, but can't form" | ||
| 1010 | " an interactive prompt for to fix %s!") | ||
| 1011 | program guess)))))) | ||
| 1012 | (contact | ||
| 1013 | (or (and prompt | ||
| 1014 | (split-string-and-unquote | ||
| 1015 | (read-shell-command | ||
| 1016 | prompt | ||
| 1017 | full-program-invocation | ||
| 1018 | 'eglot-command-history))) | ||
| 1019 | guess))) | ||
| 1020 | (list managed-modes (eglot--current-project) class contact language-id))) | ||
| 1021 | |||
| 1022 | (defvar eglot-lsp-context) | ||
| 1023 | (put 'eglot-lsp-context 'variable-documentation | ||
| 1024 | "Dynamically non-nil when searching for projects in LSP context.") | ||
| 1025 | |||
| 1026 | (defvar eglot--servers-by-xrefed-file | ||
| 1027 | (make-hash-table :test 'equal :weakness 'value)) | ||
| 1028 | |||
| 1029 | (defun eglot--current-project () | ||
| 1030 | "Return a project object for Eglot's LSP purposes. | ||
| 1031 | This relies on `project-current' and thus on | ||
| 1032 | `project-find-functions'. Functions in the latter | ||
| 1033 | variable (which see) can query the value `eglot-lsp-context' to | ||
| 1034 | decide whether a given directory is a project containing a | ||
| 1035 | suitable root directory for a given LSP server's purposes." | ||
| 1036 | (let ((eglot-lsp-context t)) | ||
| 1037 | (or (project-current) `(transient . ,default-directory)))) | ||
| 1038 | |||
| 1039 | ;;;###autoload | ||
| 1040 | (defun eglot (managed-major-mode project class contact language-id | ||
| 1041 | &optional interactive) | ||
| 1042 | "Start LSP server in support of PROJECT's buffers under MANAGED-MAJOR-MODE. | ||
| 1043 | |||
| 1044 | This starts a Language Server Protocol (LSP) server suitable for the | ||
| 1045 | buffers of PROJECT whose `major-mode' is MANAGED-MAJOR-MODE. | ||
| 1046 | CLASS is the class of the LSP server to start and CONTACT specifies | ||
| 1047 | how to connect to the server. | ||
| 1048 | |||
| 1049 | Interactively, the command attempts to guess MANAGED-MAJOR-MODE | ||
| 1050 | from the current buffer's `major-mode', CLASS and CONTACT from | ||
| 1051 | `eglot-server-programs' looked up by the major mode, and PROJECT from | ||
| 1052 | `project-find-functions'. The search for active projects in this | ||
| 1053 | context binds `eglot-lsp-context' (which see). | ||
| 1054 | |||
| 1055 | If it can't guess, it prompts the user for the mode and the server. | ||
| 1056 | With a single \\[universal-argument] prefix arg, it always prompts for COMMAND. | ||
| 1057 | With two \\[universal-argument], it also always prompts for MANAGED-MAJOR-MODE. | ||
| 1058 | |||
| 1059 | The LSP server of CLASS is started (or contacted) via CONTACT. | ||
| 1060 | If this operation is successful, current *and future* file | ||
| 1061 | buffers of MANAGED-MAJOR-MODE inside PROJECT become \"managed\" | ||
| 1062 | by the LSP server, meaning the information about their contents is | ||
| 1063 | exchanged periodically with the server to provide enhanced | ||
| 1064 | code-analysis via `xref-find-definitions', `flymake-mode', | ||
| 1065 | `eldoc-mode', and `completion-at-point', among others. | ||
| 1066 | |||
| 1067 | PROJECT is a project object as returned by `project-current'. | ||
| 1068 | |||
| 1069 | CLASS is a subclass of `eglot-lsp-server'. | ||
| 1070 | |||
| 1071 | CONTACT specifies how to contact the server. It is a | ||
| 1072 | keyword-value plist used to initialize CLASS or a plain list as | ||
| 1073 | described in `eglot-server-programs', which see. | ||
| 1074 | |||
| 1075 | LANGUAGE-ID is the language ID string to send to the server for | ||
| 1076 | MANAGED-MAJOR-MODE, which matters to a minority of servers. | ||
| 1077 | |||
| 1078 | INTERACTIVE is t if called interactively." | ||
| 1079 | (interactive (append (eglot--guess-contact t) '(t))) | ||
| 1080 | (let* ((current-server (eglot-current-server)) | ||
| 1081 | (live-p (and current-server (jsonrpc-running-p current-server)))) | ||
| 1082 | (if (and live-p | ||
| 1083 | interactive | ||
| 1084 | (y-or-n-p "[eglot] Live process found, reconnect instead? ")) | ||
| 1085 | (eglot-reconnect current-server interactive) | ||
| 1086 | (when live-p (ignore-errors (eglot-shutdown current-server))) | ||
| 1087 | (eglot--connect managed-major-mode project class contact language-id)))) | ||
| 1088 | |||
| 1089 | (defun eglot-reconnect (server &optional interactive) | ||
| 1090 | "Reconnect to SERVER. | ||
| 1091 | INTERACTIVE is t if called interactively." | ||
| 1092 | (interactive (list (eglot--current-server-or-lose) t)) | ||
| 1093 | (when (jsonrpc-running-p server) | ||
| 1094 | (ignore-errors (eglot-shutdown server interactive nil 'preserve-buffers))) | ||
| 1095 | (eglot--connect (eglot--major-modes server) | ||
| 1096 | (eglot--project server) | ||
| 1097 | (eieio-object-class-name server) | ||
| 1098 | (eglot--saved-initargs server) | ||
| 1099 | (eglot--language-id server)) | ||
| 1100 | (eglot--message "Reconnected!")) | ||
| 1101 | |||
| 1102 | (defvar eglot--managed-mode) ; forward decl | ||
| 1103 | |||
| 1104 | ;;;###autoload | ||
| 1105 | (defun eglot-ensure () | ||
| 1106 | "Start Eglot session for current buffer if there isn't one." | ||
| 1107 | (let ((buffer (current-buffer))) | ||
| 1108 | (cl-labels | ||
| 1109 | ((maybe-connect | ||
| 1110 | () | ||
| 1111 | (remove-hook 'post-command-hook #'maybe-connect nil) | ||
| 1112 | (eglot--when-live-buffer buffer | ||
| 1113 | (unless eglot--managed-mode | ||
| 1114 | (apply #'eglot--connect (eglot--guess-contact)))))) | ||
| 1115 | (when buffer-file-name | ||
| 1116 | (add-hook 'post-command-hook #'maybe-connect 'append nil))))) | ||
| 1117 | |||
| 1118 | (defun eglot-events-buffer (server) | ||
| 1119 | "Display events buffer for SERVER. | ||
| 1120 | Use current server's or first available Eglot events buffer." | ||
| 1121 | (interactive (list (eglot-current-server))) | ||
| 1122 | (let ((buffer (if server (jsonrpc-events-buffer server) | ||
| 1123 | (cl-find "\\*EGLOT.*events\\*" | ||
| 1124 | (buffer-list) | ||
| 1125 | :key #'buffer-name :test #'string-match)))) | ||
| 1126 | (if buffer (display-buffer buffer) | ||
| 1127 | (eglot--error "Can't find an Eglot events buffer!")))) | ||
| 1128 | |||
| 1129 | (defun eglot-stderr-buffer (server) | ||
| 1130 | "Display stderr buffer for SERVER." | ||
| 1131 | (interactive (list (eglot--current-server-or-lose))) | ||
| 1132 | (display-buffer (jsonrpc-stderr-buffer server))) | ||
| 1133 | |||
| 1134 | (defun eglot-forget-pending-continuations (server) | ||
| 1135 | "Forget pending requests for SERVER." | ||
| 1136 | (interactive (list (eglot--current-server-or-lose))) | ||
| 1137 | (jsonrpc-forget-pending-continuations server)) | ||
| 1138 | |||
| 1139 | (defvar eglot-connect-hook | ||
| 1140 | '(eglot-signal-didChangeConfiguration) | ||
| 1141 | "Hook run after connecting in `eglot--connect'.") | ||
| 1142 | |||
| 1143 | (defvar eglot-server-initialized-hook | ||
| 1144 | '() | ||
| 1145 | "Hook run after a `eglot-lsp-server' instance is created. | ||
| 1146 | |||
| 1147 | That is before a connection was established. Use | ||
| 1148 | `eglot-connect-hook' to hook into when a connection was | ||
| 1149 | successfully established and the server on the other side has | ||
| 1150 | received the initializing configuration. | ||
| 1151 | |||
| 1152 | Each function is passed the server as an argument") | ||
| 1153 | |||
| 1154 | (defun eglot--cmd (contact) | ||
| 1155 | "Helper for `eglot--connect'." | ||
| 1156 | (if (file-remote-p default-directory) | ||
| 1157 | ;; TODO: this seems like a bug, although it’s everywhere. For | ||
| 1158 | ;; some reason, for remote connections only, over a pipe, we | ||
| 1159 | ;; need to turn off line buffering on the tty. | ||
| 1160 | ;; | ||
| 1161 | ;; Not only does this seem like there should be a better way, | ||
| 1162 | ;; but it almost certainly doesn’t work on non-unix systems. | ||
| 1163 | (list "sh" "-c" | ||
| 1164 | (string-join (cons "stty raw > /dev/null;" | ||
| 1165 | (mapcar #'shell-quote-argument contact)) | ||
| 1166 | " ")) | ||
| 1167 | contact)) | ||
| 1168 | |||
| 1169 | (defvar-local eglot--cached-server nil | ||
| 1170 | "A cached reference to the current Eglot server.") | ||
| 1171 | |||
| 1172 | (defun eglot--connect (managed-modes project class contact language-id) | ||
| 1173 | "Connect to MANAGED-MODES, LANGUAGE-ID, PROJECT, CLASS and CONTACT. | ||
| 1174 | This docstring appeases checkdoc, that's all." | ||
| 1175 | (let* ((default-directory (project-root project)) | ||
| 1176 | (nickname (file-name-base (directory-file-name default-directory))) | ||
| 1177 | (readable-name (format "EGLOT (%s/%s)" nickname managed-modes)) | ||
| 1178 | autostart-inferior-process | ||
| 1179 | server-info | ||
| 1180 | (contact (if (functionp contact) (funcall contact) contact)) | ||
| 1181 | (initargs | ||
| 1182 | (cond ((keywordp (car contact)) contact) | ||
| 1183 | ((integerp (cadr contact)) | ||
| 1184 | (setq server-info (list (format "%s:%s" (car contact) | ||
| 1185 | (cadr contact)))) | ||
| 1186 | `(:process ,(lambda () | ||
| 1187 | (apply #'open-network-stream | ||
| 1188 | readable-name nil | ||
| 1189 | (car contact) (cadr contact) | ||
| 1190 | (cddr contact))))) | ||
| 1191 | ((and (stringp (car contact)) (memq :autoport contact)) | ||
| 1192 | (setq server-info (list "<inferior process>")) | ||
| 1193 | `(:process ,(lambda () | ||
| 1194 | (pcase-let ((`(,connection . ,inferior) | ||
| 1195 | (eglot--inferior-bootstrap | ||
| 1196 | readable-name | ||
| 1197 | contact))) | ||
| 1198 | (setq autostart-inferior-process inferior) | ||
| 1199 | connection)))) | ||
| 1200 | ((stringp (car contact)) | ||
| 1201 | (let* ((probe (cl-position-if #'keywordp contact)) | ||
| 1202 | (more-initargs (and probe (cl-subseq contact probe))) | ||
| 1203 | (contact (cl-subseq contact 0 probe))) | ||
| 1204 | `(:process | ||
| 1205 | ,(lambda () | ||
| 1206 | (let ((default-directory default-directory)) | ||
| 1207 | (make-process | ||
| 1208 | :name readable-name | ||
| 1209 | :command (setq server-info (eglot--cmd contact)) | ||
| 1210 | :connection-type 'pipe | ||
| 1211 | :coding 'utf-8-emacs-unix | ||
| 1212 | :noquery t | ||
| 1213 | :stderr (get-buffer-create | ||
| 1214 | (format "*%s stderr*" readable-name)) | ||
| 1215 | :file-handler t))) | ||
| 1216 | ,@more-initargs))))) | ||
| 1217 | (spread (lambda (fn) (lambda (server method params) | ||
| 1218 | (let ((eglot--cached-server server)) | ||
| 1219 | (apply fn server method (append params nil)))))) | ||
| 1220 | (server | ||
| 1221 | (apply | ||
| 1222 | #'make-instance class | ||
| 1223 | :name readable-name | ||
| 1224 | :events-buffer-scrollback-size eglot-events-buffer-size | ||
| 1225 | :notification-dispatcher (funcall spread #'eglot-handle-notification) | ||
| 1226 | :request-dispatcher (funcall spread #'eglot-handle-request) | ||
| 1227 | :on-shutdown #'eglot--on-shutdown | ||
| 1228 | initargs)) | ||
| 1229 | (cancelled nil) | ||
| 1230 | (tag (make-symbol "connected-catch-tag"))) | ||
| 1231 | (when server-info | ||
| 1232 | (jsonrpc--debug server "Running language server: %s" | ||
| 1233 | (string-join server-info " "))) | ||
| 1234 | (setf (eglot--saved-initargs server) initargs) | ||
| 1235 | (setf (eglot--project server) project) | ||
| 1236 | (setf (eglot--project-nickname server) nickname) | ||
| 1237 | (setf (eglot--major-modes server) (eglot--ensure-list managed-modes)) | ||
| 1238 | (setf (eglot--language-id server) language-id) | ||
| 1239 | (setf (eglot--inferior-process server) autostart-inferior-process) | ||
| 1240 | (run-hook-with-args 'eglot-server-initialized-hook server) | ||
| 1241 | ;; Now start the handshake. To honour `eglot-sync-connect' | ||
| 1242 | ;; maybe-sync-maybe-async semantics we use `jsonrpc-async-request' | ||
| 1243 | ;; and mimic most of `jsonrpc-request'. | ||
| 1244 | (unwind-protect | ||
| 1245 | (condition-case _quit | ||
| 1246 | (let ((retval | ||
| 1247 | (catch tag | ||
| 1248 | (jsonrpc-async-request | ||
| 1249 | server | ||
| 1250 | :initialize | ||
| 1251 | (list :processId | ||
| 1252 | (unless (or eglot-withhold-process-id | ||
| 1253 | (file-remote-p default-directory) | ||
| 1254 | (eq (jsonrpc-process-type server) | ||
| 1255 | 'network)) | ||
| 1256 | (emacs-pid)) | ||
| 1257 | ;; Maybe turn trampy `/ssh:foo@bar:/path/to/baz.py' | ||
| 1258 | ;; into `/path/to/baz.py', so LSP groks it. | ||
| 1259 | :rootPath (file-local-name | ||
| 1260 | (expand-file-name default-directory)) | ||
| 1261 | :rootUri (eglot--path-to-uri default-directory) | ||
| 1262 | :initializationOptions (eglot-initialization-options | ||
| 1263 | server) | ||
| 1264 | :capabilities (eglot-client-capabilities server) | ||
| 1265 | :workspaceFolders (eglot-workspace-folders server)) | ||
| 1266 | :success-fn | ||
| 1267 | (eglot--lambda ((InitializeResult) capabilities serverInfo) | ||
| 1268 | (unless cancelled | ||
| 1269 | (push server | ||
| 1270 | (gethash project eglot--servers-by-project)) | ||
| 1271 | (setf (eglot--capabilities server) capabilities) | ||
| 1272 | (setf (eglot--server-info server) serverInfo) | ||
| 1273 | (jsonrpc-notify server :initialized eglot--{}) | ||
| 1274 | (dolist (buffer (buffer-list)) | ||
| 1275 | (with-current-buffer buffer | ||
| 1276 | ;; No need to pass SERVER as an argument: it has | ||
| 1277 | ;; been registered in `eglot--servers-by-project', | ||
| 1278 | ;; so that it can be found (and cached) from | ||
| 1279 | ;; `eglot--maybe-activate-editing-mode' in any | ||
| 1280 | ;; managed buffer. | ||
| 1281 | (eglot--maybe-activate-editing-mode))) | ||
| 1282 | (setf (eglot--inhibit-autoreconnect server) | ||
| 1283 | (cond | ||
| 1284 | ((booleanp eglot-autoreconnect) | ||
| 1285 | (not eglot-autoreconnect)) | ||
| 1286 | ((cl-plusp eglot-autoreconnect) | ||
| 1287 | (run-with-timer | ||
| 1288 | eglot-autoreconnect nil | ||
| 1289 | (lambda () | ||
| 1290 | (setf (eglot--inhibit-autoreconnect server) | ||
| 1291 | (null eglot-autoreconnect))))))) | ||
| 1292 | (let ((default-directory (project-root project)) | ||
| 1293 | (major-mode (car managed-modes))) | ||
| 1294 | (hack-dir-local-variables-non-file-buffer) | ||
| 1295 | (run-hook-with-args 'eglot-connect-hook server)) | ||
| 1296 | (eglot--message | ||
| 1297 | "Connected! Server `%s' now managing `%s' buffers \ | ||
| 1298 | in project `%s'." | ||
| 1299 | (or (plist-get serverInfo :name) | ||
| 1300 | (jsonrpc-name server)) | ||
| 1301 | managed-modes | ||
| 1302 | (eglot-project-nickname server)) | ||
| 1303 | (when tag (throw tag t)))) | ||
| 1304 | :timeout eglot-connect-timeout | ||
| 1305 | :error-fn (eglot--lambda ((ResponseError) code message) | ||
| 1306 | (unless cancelled | ||
| 1307 | (jsonrpc-shutdown server) | ||
| 1308 | (let ((msg (format "%s: %s" code message))) | ||
| 1309 | (if tag (throw tag `(error . ,msg)) | ||
| 1310 | (eglot--error msg))))) | ||
| 1311 | :timeout-fn (lambda () | ||
| 1312 | (unless cancelled | ||
| 1313 | (jsonrpc-shutdown server) | ||
| 1314 | (let ((msg (format "Timed out after %s seconds" | ||
| 1315 | eglot-connect-timeout))) | ||
| 1316 | (if tag (throw tag `(error . ,msg)) | ||
| 1317 | (eglot--error msg)))))) | ||
| 1318 | (cond ((numberp eglot-sync-connect) | ||
| 1319 | (accept-process-output nil eglot-sync-connect)) | ||
| 1320 | (eglot-sync-connect | ||
| 1321 | (while t (accept-process-output | ||
| 1322 | nil eglot-connect-timeout))))))) | ||
| 1323 | (pcase retval | ||
| 1324 | (`(error . ,msg) (eglot--error msg)) | ||
| 1325 | (`nil (eglot--message "Waiting in background for server `%s'" | ||
| 1326 | (jsonrpc-name server)) | ||
| 1327 | nil) | ||
| 1328 | (_ server))) | ||
| 1329 | (quit (jsonrpc-shutdown server) (setq cancelled 'quit))) | ||
| 1330 | (setq tag nil)))) | ||
| 1331 | |||
| 1332 | (defun eglot--inferior-bootstrap (name contact &optional connect-args) | ||
| 1333 | "Use CONTACT to start a server, then connect to it. | ||
| 1334 | Return a cons of two process objects (CONNECTION . INFERIOR). | ||
| 1335 | Name both based on NAME. | ||
| 1336 | CONNECT-ARGS are passed as additional arguments to | ||
| 1337 | `open-network-stream'." | ||
| 1338 | (let* ((port-probe (make-network-process :name "eglot-port-probe-dummy" | ||
| 1339 | :server t | ||
| 1340 | :host "localhost" | ||
| 1341 | :service 0)) | ||
| 1342 | (port-number (unwind-protect | ||
| 1343 | (process-contact port-probe :service) | ||
| 1344 | (delete-process port-probe))) | ||
| 1345 | inferior connection) | ||
| 1346 | (unwind-protect | ||
| 1347 | (progn | ||
| 1348 | (setq inferior | ||
| 1349 | (make-process | ||
| 1350 | :name (format "autostart-inferior-%s" name) | ||
| 1351 | :stderr (format "*%s stderr*" name) | ||
| 1352 | :noquery t | ||
| 1353 | :command (cl-subst | ||
| 1354 | (format "%s" port-number) :autoport contact))) | ||
| 1355 | (setq connection | ||
| 1356 | (cl-loop | ||
| 1357 | repeat 10 for i from 1 | ||
| 1358 | do (accept-process-output nil 0.5) | ||
| 1359 | while (process-live-p inferior) | ||
| 1360 | do (eglot--message | ||
| 1361 | "Trying to connect to localhost and port %s (attempt %s)" | ||
| 1362 | port-number i) | ||
| 1363 | thereis (ignore-errors | ||
| 1364 | (apply #'open-network-stream | ||
| 1365 | (format "autoconnect-%s" name) | ||
| 1366 | nil | ||
| 1367 | "localhost" port-number connect-args)))) | ||
| 1368 | (cons connection inferior)) | ||
| 1369 | (cond ((and (process-live-p connection) | ||
| 1370 | (process-live-p inferior)) | ||
| 1371 | (eglot--message "Done, connected to %s!" port-number)) | ||
| 1372 | (t | ||
| 1373 | (when inferior (delete-process inferior)) | ||
| 1374 | (when connection (delete-process connection)) | ||
| 1375 | (eglot--error "Could not start and connect to server%s" | ||
| 1376 | (if inferior | ||
| 1377 | (format " started with %s" | ||
| 1378 | (process-command inferior)) | ||
| 1379 | "!"))))))) | ||
| 1380 | |||
| 1381 | |||
| 1382 | ;;; Helpers (move these to API?) | ||
| 1383 | ;;; | ||
| 1384 | (defun eglot--error (format &rest args) | ||
| 1385 | "Error out with FORMAT with ARGS." | ||
| 1386 | (error "[eglot] %s" (apply #'format format args))) | ||
| 1387 | |||
| 1388 | (defun eglot--message (format &rest args) | ||
| 1389 | "Message out with FORMAT with ARGS." | ||
| 1390 | (message "[eglot] %s" (apply #'format format args))) | ||
| 1391 | |||
| 1392 | (defun eglot--warn (format &rest args) | ||
| 1393 | "Warning message with FORMAT and ARGS." | ||
| 1394 | (apply #'eglot--message (concat "(warning) " format) args) | ||
| 1395 | (let ((warning-minimum-level :error)) | ||
| 1396 | (display-warning 'eglot (apply #'format format args) :warning))) | ||
| 1397 | |||
| 1398 | (defun eglot-current-column () (- (point) (line-beginning-position))) | ||
| 1399 | |||
| 1400 | (defvar eglot-current-column-function #'eglot-lsp-abiding-column | ||
| 1401 | "Function to calculate the current column. | ||
| 1402 | |||
| 1403 | This is the inverse operation of | ||
| 1404 | `eglot-move-to-column-function' (which see). It is a function of | ||
| 1405 | no arguments returning a column number. For buffers managed by | ||
| 1406 | fully LSP-compliant servers, this should be set to | ||
| 1407 | `eglot-lsp-abiding-column' (the default), and | ||
| 1408 | `eglot-current-column' for all others.") | ||
| 1409 | |||
| 1410 | (defun eglot-lsp-abiding-column (&optional lbp) | ||
| 1411 | "Calculate current COLUMN as defined by the LSP spec. | ||
| 1412 | LBP defaults to `line-beginning-position'." | ||
| 1413 | (/ (- (length (encode-coding-region (or lbp (line-beginning-position)) | ||
| 1414 | ;; Fix github#860 | ||
| 1415 | (min (point) (point-max)) 'utf-16 t)) | ||
| 1416 | 2) | ||
| 1417 | 2)) | ||
| 1418 | |||
| 1419 | (defun eglot--pos-to-lsp-position (&optional pos) | ||
| 1420 | "Convert point POS to LSP position." | ||
| 1421 | (eglot--widening | ||
| 1422 | (list :line (1- (line-number-at-pos pos t)) ; F!@&#$CKING OFF-BY-ONE | ||
| 1423 | :character (progn (when pos (goto-char pos)) | ||
| 1424 | (funcall eglot-current-column-function))))) | ||
| 1425 | |||
| 1426 | (defvar eglot-move-to-column-function #'eglot-move-to-lsp-abiding-column | ||
| 1427 | "Function to move to a column reported by the LSP server. | ||
| 1428 | |||
| 1429 | According to the standard, LSP column/character offsets are based | ||
| 1430 | on a count of UTF-16 code units, not actual visual columns. So | ||
| 1431 | when LSP says position 3 of a line containing just \"aXbc\", | ||
| 1432 | where X is a multi-byte character, it actually means `b', not | ||
| 1433 | `c'. However, many servers don't follow the spec this closely. | ||
| 1434 | |||
| 1435 | For buffers managed by fully LSP-compliant servers, this should | ||
| 1436 | be set to `eglot-move-to-lsp-abiding-column' (the default), and | ||
| 1437 | `eglot-move-to-column' for all others.") | ||
| 1438 | |||
| 1439 | (defun eglot-move-to-column (column) | ||
| 1440 | "Move to COLUMN without closely following the LSP spec." | ||
| 1441 | ;; We cannot use `move-to-column' here, because it moves to *visual* | ||
| 1442 | ;; columns, which can be different from LSP columns in case of | ||
| 1443 | ;; `whitespace-mode', `prettify-symbols-mode', etc. (github#296, | ||
| 1444 | ;; github#297) | ||
| 1445 | (goto-char (min (+ (line-beginning-position) column) | ||
| 1446 | (line-end-position)))) | ||
| 1447 | |||
| 1448 | (defun eglot-move-to-lsp-abiding-column (column) | ||
| 1449 | "Move to COLUMN abiding by the LSP spec." | ||
| 1450 | (save-restriction | ||
| 1451 | (cl-loop | ||
| 1452 | with lbp = (line-beginning-position) | ||
| 1453 | initially | ||
| 1454 | (narrow-to-region lbp (line-end-position)) | ||
| 1455 | (move-to-column column) | ||
| 1456 | for diff = (- column | ||
| 1457 | (eglot-lsp-abiding-column lbp)) | ||
| 1458 | until (zerop diff) | ||
| 1459 | do (condition-case eob-err | ||
| 1460 | (forward-char (/ (if (> diff 0) (1+ diff) (1- diff)) 2)) | ||
| 1461 | (end-of-buffer (cl-return eob-err)))))) | ||
| 1462 | |||
| 1463 | (defun eglot--lsp-position-to-point (pos-plist &optional marker) | ||
| 1464 | "Convert LSP position POS-PLIST to Emacs point. | ||
| 1465 | If optional MARKER, return a marker instead" | ||
| 1466 | (save-excursion | ||
| 1467 | (save-restriction | ||
| 1468 | (widen) | ||
| 1469 | (goto-char (point-min)) | ||
| 1470 | (forward-line (min most-positive-fixnum | ||
| 1471 | (plist-get pos-plist :line))) | ||
| 1472 | (unless (eobp) ;; if line was excessive leave point at eob | ||
| 1473 | (let ((tab-width 1) | ||
| 1474 | (col (plist-get pos-plist :character))) | ||
| 1475 | (unless (wholenump col) | ||
| 1476 | (eglot--warn | ||
| 1477 | "Caution: LSP server sent invalid character position %s. Using 0 instead." | ||
| 1478 | col) | ||
| 1479 | (setq col 0)) | ||
| 1480 | (funcall eglot-move-to-column-function col))) | ||
| 1481 | (if marker (copy-marker (point-marker)) (point))))) | ||
| 1482 | |||
| 1483 | (defconst eglot--uri-path-allowed-chars | ||
| 1484 | (let ((vec (copy-sequence url-path-allowed-chars))) | ||
| 1485 | (aset vec ?: nil) ;; see github#639 | ||
| 1486 | vec) | ||
| 1487 | "Like `url-path-allows-chars' but more restrictive.") | ||
| 1488 | |||
| 1489 | (defun eglot--path-to-uri (path) | ||
| 1490 | "URIfy PATH." | ||
| 1491 | (let ((truepath (file-truename path))) | ||
| 1492 | (concat "file://" | ||
| 1493 | ;; Add a leading "/" for local MS Windows-style paths. | ||
| 1494 | (if (and (eq system-type 'windows-nt) | ||
| 1495 | (not (file-remote-p truepath))) | ||
| 1496 | "/") | ||
| 1497 | (url-hexify-string | ||
| 1498 | ;; Again watch out for trampy paths. | ||
| 1499 | (directory-file-name (file-local-name truepath)) | ||
| 1500 | eglot--uri-path-allowed-chars)))) | ||
| 1501 | |||
| 1502 | (defun eglot--uri-to-path (uri) | ||
| 1503 | "Convert URI to file path, helped by `eglot--current-server'." | ||
| 1504 | (when (keywordp uri) (setq uri (substring (symbol-name uri) 1))) | ||
| 1505 | (let* ((server (eglot-current-server)) | ||
| 1506 | (remote-prefix (and server (eglot--trampish-p server))) | ||
| 1507 | (retval (url-unhex-string (url-filename (url-generic-parse-url uri)))) | ||
| 1508 | ;; Remove the leading "/" for local MS Windows-style paths. | ||
| 1509 | (normalized (if (and (not remote-prefix) | ||
| 1510 | (eq system-type 'windows-nt) | ||
| 1511 | (cl-plusp (length retval))) | ||
| 1512 | (substring retval 1) | ||
| 1513 | retval))) | ||
| 1514 | (concat remote-prefix normalized))) | ||
| 1515 | |||
| 1516 | (defun eglot--snippet-expansion-fn () | ||
| 1517 | "Compute a function to expand snippets. | ||
| 1518 | Doubles as an indicator of snippet support." | ||
| 1519 | (and (boundp 'yas-minor-mode) | ||
| 1520 | (symbol-value 'yas-minor-mode) | ||
| 1521 | 'yas-expand-snippet)) | ||
| 1522 | |||
| 1523 | (defun eglot--format-markup (markup) | ||
| 1524 | "Format MARKUP according to LSP's spec." | ||
| 1525 | (pcase-let ((`(,string ,mode) | ||
| 1526 | (if (stringp markup) (list markup 'gfm-view-mode) | ||
| 1527 | (list (plist-get markup :value) | ||
| 1528 | (pcase (plist-get markup :kind) | ||
| 1529 | ("markdown" 'gfm-view-mode) | ||
| 1530 | ("plaintext" 'text-mode) | ||
| 1531 | (_ major-mode)))))) | ||
| 1532 | (with-temp-buffer | ||
| 1533 | (setq-local markdown-fontify-code-blocks-natively t) | ||
| 1534 | (insert string) | ||
| 1535 | (let ((inhibit-message t) | ||
| 1536 | (message-log-max nil)) | ||
| 1537 | (ignore-errors (delay-mode-hooks (funcall mode)))) | ||
| 1538 | (font-lock-ensure) | ||
| 1539 | (string-trim (buffer-string))))) | ||
| 1540 | |||
| 1541 | (define-obsolete-variable-alias 'eglot-ignored-server-capabilites | ||
| 1542 | 'eglot-ignored-server-capabilities "1.8") | ||
| 1543 | |||
| 1544 | (defcustom eglot-ignored-server-capabilities (list) | ||
| 1545 | "LSP server capabilities that Eglot could use, but won't. | ||
| 1546 | You could add, for instance, the symbol | ||
| 1547 | `:documentHighlightProvider' to prevent automatic highlighting | ||
| 1548 | under cursor." | ||
| 1549 | :type '(set | ||
| 1550 | :tag "Tick the ones you're not interested in" | ||
| 1551 | (const :tag "Documentation on hover" :hoverProvider) | ||
| 1552 | (const :tag "Code completion" :completionProvider) | ||
| 1553 | (const :tag "Function signature help" :signatureHelpProvider) | ||
| 1554 | (const :tag "Go to definition" :definitionProvider) | ||
| 1555 | (const :tag "Go to type definition" :typeDefinitionProvider) | ||
| 1556 | (const :tag "Go to implementation" :implementationProvider) | ||
| 1557 | (const :tag "Go to declaration" :implementationProvider) | ||
| 1558 | (const :tag "Find references" :referencesProvider) | ||
| 1559 | (const :tag "Highlight symbols automatically" :documentHighlightProvider) | ||
| 1560 | (const :tag "List symbols in buffer" :documentSymbolProvider) | ||
| 1561 | (const :tag "List symbols in workspace" :workspaceSymbolProvider) | ||
| 1562 | (const :tag "Execute code actions" :codeActionProvider) | ||
| 1563 | (const :tag "Code lens" :codeLensProvider) | ||
| 1564 | (const :tag "Format buffer" :documentFormattingProvider) | ||
| 1565 | (const :tag "Format portion of buffer" :documentRangeFormattingProvider) | ||
| 1566 | (const :tag "On-type formatting" :documentOnTypeFormattingProvider) | ||
| 1567 | (const :tag "Rename symbol" :renameProvider) | ||
| 1568 | (const :tag "Highlight links in document" :documentLinkProvider) | ||
| 1569 | (const :tag "Decorate color references" :colorProvider) | ||
| 1570 | (const :tag "Fold regions of buffer" :foldingRangeProvider) | ||
| 1571 | (const :tag "Execute custom commands" :executeCommandProvider))) | ||
| 1572 | |||
| 1573 | (defun eglot--server-capable (&rest feats) | ||
| 1574 | "Determine if current server is capable of FEATS." | ||
| 1575 | (unless (cl-some (lambda (feat) | ||
| 1576 | (memq feat eglot-ignored-server-capabilities)) | ||
| 1577 | feats) | ||
| 1578 | (cl-loop for caps = (eglot--capabilities (eglot--current-server-or-lose)) | ||
| 1579 | then (cadr probe) | ||
| 1580 | for (feat . more) on feats | ||
| 1581 | for probe = (plist-member caps feat) | ||
| 1582 | if (not probe) do (cl-return nil) | ||
| 1583 | if (eq (cadr probe) :json-false) do (cl-return nil) | ||
| 1584 | if (not (listp (cadr probe))) do (cl-return (if more nil (cadr probe))) | ||
| 1585 | finally (cl-return (or (cadr probe) t))))) | ||
| 1586 | |||
| 1587 | (defun eglot--range-region (range &optional markers) | ||
| 1588 | "Return region (BEG . END) that represents LSP RANGE. | ||
| 1589 | If optional MARKERS, make markers." | ||
| 1590 | (let* ((st (plist-get range :start)) | ||
| 1591 | (beg (eglot--lsp-position-to-point st markers)) | ||
| 1592 | (end (eglot--lsp-position-to-point (plist-get range :end) markers))) | ||
| 1593 | (cons beg end))) | ||
| 1594 | |||
| 1595 | (defun eglot--read-server (prompt &optional dont-if-just-the-one) | ||
| 1596 | "Read a running Eglot server from minibuffer using PROMPT. | ||
| 1597 | If DONT-IF-JUST-THE-ONE and there's only one server, don't prompt | ||
| 1598 | and just return it. PROMPT shouldn't end with a question mark." | ||
| 1599 | (let ((servers (cl-loop for servers | ||
| 1600 | being hash-values of eglot--servers-by-project | ||
| 1601 | append servers)) | ||
| 1602 | (name (lambda (srv) | ||
| 1603 | (format "%s %s" (eglot-project-nickname srv) | ||
| 1604 | (eglot--major-modes srv))))) | ||
| 1605 | (cond ((null servers) | ||
| 1606 | (eglot--error "No servers!")) | ||
| 1607 | ((or (cdr servers) (not dont-if-just-the-one)) | ||
| 1608 | (let* ((default (when-let ((current (eglot-current-server))) | ||
| 1609 | (funcall name current))) | ||
| 1610 | (read (completing-read | ||
| 1611 | (if default | ||
| 1612 | (format "%s (default %s)? " prompt default) | ||
| 1613 | (concat prompt "? ")) | ||
| 1614 | (mapcar name servers) | ||
| 1615 | nil t | ||
| 1616 | nil nil | ||
| 1617 | default))) | ||
| 1618 | (cl-find read servers :key name :test #'equal))) | ||
| 1619 | (t (car servers))))) | ||
| 1620 | |||
| 1621 | (defun eglot--trampish-p (server) | ||
| 1622 | "Tell if SERVER's project root is `file-remote-p'." | ||
| 1623 | (file-remote-p (project-root (eglot--project server)))) | ||
| 1624 | |||
| 1625 | (defun eglot--plist-keys (plist) "Get keys of a plist." | ||
| 1626 | (cl-loop for (k _v) on plist by #'cddr collect k)) | ||
| 1627 | |||
| 1628 | (defun eglot--ensure-list (x) (if (listp x) x (list x))) | ||
| 1629 | |||
| 1630 | |||
| 1631 | ;;; Minor modes | ||
| 1632 | ;;; | ||
| 1633 | (defvar eglot-mode-map | ||
| 1634 | (let ((map (make-sparse-keymap))) | ||
| 1635 | (define-key map [remap display-local-help] #'eldoc-doc-buffer) | ||
| 1636 | map)) | ||
| 1637 | |||
| 1638 | (defvar-local eglot--current-flymake-report-fn nil | ||
| 1639 | "Current flymake report function for this buffer.") | ||
| 1640 | |||
| 1641 | (defvar-local eglot--saved-bindings nil | ||
| 1642 | "Bindings saved by `eglot--setq-saving'.") | ||
| 1643 | |||
| 1644 | (defvar eglot-stay-out-of '() | ||
| 1645 | "List of Emacs things that Eglot should try to stay of. | ||
| 1646 | Each element is a string, a symbol, or a regexp which is matched | ||
| 1647 | against a variable's name. Examples include the string | ||
| 1648 | \"company\" or the symbol `xref'. | ||
| 1649 | |||
| 1650 | Before Eglot starts \"managing\" a particular buffer, it | ||
| 1651 | opinionatedly sets some peripheral Emacs facilities, such as | ||
| 1652 | Flymake, Xref and Company. These overriding settings help ensure | ||
| 1653 | consistent Eglot behaviour and only stay in place until | ||
| 1654 | \"managing\" stops (usually via `eglot-shutdown'), whereupon the | ||
| 1655 | previous settings are restored. | ||
| 1656 | |||
| 1657 | However, if you wish for Eglot to stay out of a particular Emacs | ||
| 1658 | facility that you'd like to keep control of add an element to | ||
| 1659 | this list and Eglot will refrain from setting it. | ||
| 1660 | |||
| 1661 | For example, to keep your Company customization, add the symbol | ||
| 1662 | `company' to this variable.") | ||
| 1663 | |||
| 1664 | (defun eglot--stay-out-of-p (symbol) | ||
| 1665 | "Tell if Eglot should stay of of SYMBOL." | ||
| 1666 | (cl-find (symbol-name symbol) eglot-stay-out-of | ||
| 1667 | :test (lambda (s thing) | ||
| 1668 | (let ((re (if (symbolp thing) (symbol-name thing) thing))) | ||
| 1669 | (string-match re s))))) | ||
| 1670 | |||
| 1671 | (defmacro eglot--setq-saving (symbol binding) | ||
| 1672 | `(unless (or (not (boundp ',symbol)) (eglot--stay-out-of-p ',symbol)) | ||
| 1673 | (push (cons ',symbol (symbol-value ',symbol)) eglot--saved-bindings) | ||
| 1674 | (setq-local ,symbol ,binding))) | ||
| 1675 | |||
| 1676 | (defun eglot-managed-p () | ||
| 1677 | "Tell if current buffer is managed by Eglot." | ||
| 1678 | eglot--managed-mode) | ||
| 1679 | |||
| 1680 | (defvar eglot-managed-mode-hook nil | ||
| 1681 | "A hook run by Eglot after it started/stopped managing a buffer. | ||
| 1682 | Use `eglot-managed-p' to determine if current buffer is managed.") | ||
| 1683 | |||
| 1684 | (define-minor-mode eglot--managed-mode | ||
| 1685 | "Mode for source buffers managed by some Eglot project." | ||
| 1686 | :init-value nil :lighter nil :keymap eglot-mode-map | ||
| 1687 | (cond | ||
| 1688 | (eglot--managed-mode | ||
| 1689 | (add-hook 'after-change-functions 'eglot--after-change nil t) | ||
| 1690 | (add-hook 'before-change-functions 'eglot--before-change nil t) | ||
| 1691 | (add-hook 'kill-buffer-hook #'eglot--managed-mode-off nil t) | ||
| 1692 | ;; Prepend "didClose" to the hook after the "nonoff", so it will run first | ||
| 1693 | (add-hook 'kill-buffer-hook 'eglot--signal-textDocument/didClose nil t) | ||
| 1694 | (add-hook 'before-revert-hook 'eglot--signal-textDocument/didClose nil t) | ||
| 1695 | (add-hook 'after-revert-hook 'eglot--after-revert-hook nil t) | ||
| 1696 | (add-hook 'before-save-hook 'eglot--signal-textDocument/willSave nil t) | ||
| 1697 | (add-hook 'after-save-hook 'eglot--signal-textDocument/didSave nil t) | ||
| 1698 | (unless (eglot--stay-out-of-p 'xref) | ||
| 1699 | (add-hook 'xref-backend-functions 'eglot-xref-backend nil t)) | ||
| 1700 | (add-hook 'completion-at-point-functions #'eglot-completion-at-point nil t) | ||
| 1701 | (add-hook 'change-major-mode-hook #'eglot--managed-mode-off nil t) | ||
| 1702 | (add-hook 'post-self-insert-hook 'eglot--post-self-insert-hook nil t) | ||
| 1703 | (add-hook 'pre-command-hook 'eglot--pre-command-hook nil t) | ||
| 1704 | (eglot--setq-saving eldoc-documentation-functions | ||
| 1705 | '(eglot-signature-eldoc-function | ||
| 1706 | eglot-hover-eldoc-function)) | ||
| 1707 | (eglot--setq-saving eldoc-documentation-strategy | ||
| 1708 | #'eldoc-documentation-enthusiast) | ||
| 1709 | (eglot--setq-saving xref-prompt-for-identifier nil) | ||
| 1710 | (eglot--setq-saving flymake-diagnostic-functions '(eglot-flymake-backend)) | ||
| 1711 | (eglot--setq-saving company-backends '(company-capf)) | ||
| 1712 | (eglot--setq-saving company-tooltip-align-annotations t) | ||
| 1713 | (unless (eglot--stay-out-of-p 'imenu) | ||
| 1714 | (add-function :before-until (local 'imenu-create-index-function) | ||
| 1715 | #'eglot-imenu)) | ||
| 1716 | (unless (eglot--stay-out-of-p 'flymake) (flymake-mode 1)) | ||
| 1717 | (unless (eglot--stay-out-of-p 'eldoc) (eldoc-mode 1)) | ||
| 1718 | (cl-pushnew (current-buffer) (eglot--managed-buffers (eglot-current-server)))) | ||
| 1719 | (t | ||
| 1720 | (remove-hook 'after-change-functions 'eglot--after-change t) | ||
| 1721 | (remove-hook 'before-change-functions 'eglot--before-change t) | ||
| 1722 | (remove-hook 'kill-buffer-hook #'eglot--managed-mode-off t) | ||
| 1723 | (remove-hook 'kill-buffer-hook 'eglot--signal-textDocument/didClose t) | ||
| 1724 | (remove-hook 'before-revert-hook 'eglot--signal-textDocument/didClose t) | ||
| 1725 | (remove-hook 'after-revert-hook 'eglot--after-revert-hook t) | ||
| 1726 | (remove-hook 'before-save-hook 'eglot--signal-textDocument/willSave t) | ||
| 1727 | (remove-hook 'after-save-hook 'eglot--signal-textDocument/didSave t) | ||
| 1728 | (remove-hook 'xref-backend-functions 'eglot-xref-backend t) | ||
| 1729 | (remove-hook 'completion-at-point-functions #'eglot-completion-at-point t) | ||
| 1730 | (remove-hook 'change-major-mode-hook #'eglot--managed-mode-off t) | ||
| 1731 | (remove-hook 'post-self-insert-hook 'eglot--post-self-insert-hook t) | ||
| 1732 | (remove-hook 'pre-command-hook 'eglot--pre-command-hook t) | ||
| 1733 | (cl-loop for (var . saved-binding) in eglot--saved-bindings | ||
| 1734 | do (set (make-local-variable var) saved-binding)) | ||
| 1735 | (remove-function (local 'imenu-create-index-function) #'eglot-imenu) | ||
| 1736 | (when eglot--current-flymake-report-fn | ||
| 1737 | (eglot--report-to-flymake nil) | ||
| 1738 | (setq eglot--current-flymake-report-fn nil)) | ||
| 1739 | (let ((server eglot--cached-server)) | ||
| 1740 | (setq eglot--cached-server nil) | ||
| 1741 | (when server | ||
| 1742 | (setf (eglot--managed-buffers server) | ||
| 1743 | (delq (current-buffer) (eglot--managed-buffers server))) | ||
| 1744 | (when (and eglot-autoshutdown | ||
| 1745 | (null (eglot--managed-buffers server))) | ||
| 1746 | (eglot-shutdown server)))))) | ||
| 1747 | ;; Note: the public hook runs before the internal eglot--managed-mode-hook. | ||
| 1748 | (run-hooks 'eglot-managed-mode-hook)) | ||
| 1749 | |||
| 1750 | (defun eglot--managed-mode-off () | ||
| 1751 | "Turn off `eglot--managed-mode' unconditionally." | ||
| 1752 | (eglot--managed-mode -1)) | ||
| 1753 | |||
| 1754 | (defun eglot-current-server () | ||
| 1755 | "Return logical Eglot server for current buffer, nil if none." | ||
| 1756 | (setq eglot--cached-server | ||
| 1757 | (or eglot--cached-server | ||
| 1758 | (cl-find major-mode | ||
| 1759 | (gethash (eglot--current-project) eglot--servers-by-project) | ||
| 1760 | :key #'eglot--major-modes | ||
| 1761 | :test #'memq) | ||
| 1762 | (and eglot-extend-to-xref | ||
| 1763 | buffer-file-name | ||
| 1764 | (gethash (expand-file-name buffer-file-name) | ||
| 1765 | eglot--servers-by-xrefed-file))))) | ||
| 1766 | |||
| 1767 | (defun eglot--current-server-or-lose () | ||
| 1768 | "Return current logical Eglot server connection or error." | ||
| 1769 | (or (eglot-current-server) | ||
| 1770 | (jsonrpc-error "No current JSON-RPC connection"))) | ||
| 1771 | |||
| 1772 | (defvar-local eglot--diagnostics nil | ||
| 1773 | "Flymake diagnostics for this buffer.") | ||
| 1774 | |||
| 1775 | (defvar revert-buffer-preserve-modes) | ||
| 1776 | (defun eglot--after-revert-hook () | ||
| 1777 | "Eglot's `after-revert-hook'." | ||
| 1778 | (when revert-buffer-preserve-modes (eglot--signal-textDocument/didOpen))) | ||
| 1779 | |||
| 1780 | (defun eglot--maybe-activate-editing-mode () | ||
| 1781 | "Maybe activate `eglot--managed-mode'. | ||
| 1782 | |||
| 1783 | If it is activated, also signal textDocument/didOpen." | ||
| 1784 | (unless eglot--managed-mode | ||
| 1785 | ;; Called when `revert-buffer-in-progress-p' is t but | ||
| 1786 | ;; `revert-buffer-preserve-modes' is nil. | ||
| 1787 | (when (and buffer-file-name (eglot-current-server)) | ||
| 1788 | (setq eglot--diagnostics nil) | ||
| 1789 | (eglot--managed-mode) | ||
| 1790 | (eglot--signal-textDocument/didOpen)))) | ||
| 1791 | |||
| 1792 | (add-hook 'find-file-hook 'eglot--maybe-activate-editing-mode) | ||
| 1793 | (add-hook 'after-change-major-mode-hook 'eglot--maybe-activate-editing-mode) | ||
| 1794 | |||
| 1795 | (defun eglot-clear-status (server) | ||
| 1796 | "Clear the last JSONRPC error for SERVER." | ||
| 1797 | (interactive (list (eglot--current-server-or-lose))) | ||
| 1798 | (setf (jsonrpc-last-error server) nil)) | ||
| 1799 | |||
| 1800 | |||
| 1801 | ;;; Mode-line, menu and other sugar | ||
| 1802 | ;;; | ||
| 1803 | (defvar eglot--mode-line-format `(:eval (eglot--mode-line-format))) | ||
| 1804 | |||
| 1805 | (put 'eglot--mode-line-format 'risky-local-variable t) | ||
| 1806 | |||
| 1807 | (defun eglot--mouse-call (what) | ||
| 1808 | "Make an interactive lambda for calling WHAT from mode-line." | ||
| 1809 | (lambda (event) | ||
| 1810 | (interactive "e") | ||
| 1811 | (let ((start (event-start event))) (with-selected-window (posn-window start) | ||
| 1812 | (save-excursion | ||
| 1813 | (goto-char (or (posn-point start) | ||
| 1814 | (point))) | ||
| 1815 | (call-interactively what) | ||
| 1816 | (force-mode-line-update t)))))) | ||
| 1817 | |||
| 1818 | (defun eglot-manual () "Open on-line documentation." | ||
| 1819 | (interactive) (browse-url "https://github.com/joaotavora/eglot#readme")) | ||
| 1820 | |||
| 1821 | (easy-menu-define eglot-menu nil "Eglot" | ||
| 1822 | `("Eglot" | ||
| 1823 | ;; Commands for getting information and customization. | ||
| 1824 | ["Read manual" eglot-manual] | ||
| 1825 | ["Customize Eglot" (lambda () (interactive) (customize-group "eglot"))] | ||
| 1826 | "--" | ||
| 1827 | ;; xref like commands. | ||
| 1828 | ["Find definitions" xref-find-definitions | ||
| 1829 | :help "Find definitions of identifier at point" | ||
| 1830 | :active (eglot--server-capable :definitionProvider)] | ||
| 1831 | ["Find references" xref-find-references | ||
| 1832 | :help "Find references to identifier at point" | ||
| 1833 | :active (eglot--server-capable :referencesProvider)] | ||
| 1834 | ["Find symbols in workspace (apropos)" xref-find-apropos | ||
| 1835 | :help "Find symbols matching a query" | ||
| 1836 | :active (eglot--server-capable :workspaceSymbolProvider)] | ||
| 1837 | ["Find declaration" eglot-find-declaration | ||
| 1838 | :help "Find declaration for identifier at point" | ||
| 1839 | :active (eglot--server-capable :declarationProvider)] | ||
| 1840 | ["Find implementation" eglot-find-implementation | ||
| 1841 | :help "Find implementation for identifier at point" | ||
| 1842 | :active (eglot--server-capable :implementationProvider)] | ||
| 1843 | ["Find type definition" eglot-find-typeDefinition | ||
| 1844 | :help "Find type definition for identifier at point" | ||
| 1845 | :active (eglot--server-capable :typeDefinitionProvider)] | ||
| 1846 | "--" | ||
| 1847 | ;; LSP-related commands (mostly Eglot's own commands). | ||
| 1848 | ["Rename symbol" eglot-rename | ||
| 1849 | :active (eglot--server-capable :renameProvider)] | ||
| 1850 | ["Format buffer" eglot-format-buffer | ||
| 1851 | :active (eglot--server-capable :documentFormattingProvider)] | ||
| 1852 | ["Format active region" eglot-format | ||
| 1853 | :active (and (region-active-p) | ||
| 1854 | (eglot--server-capable :documentRangeFormattingProvider))] | ||
| 1855 | ["Show Flymake diagnostics for buffer" flymake-show-buffer-diagnostics] | ||
| 1856 | ["Show Flymake diagnostics for project" flymake-show-project-diagnostics] | ||
| 1857 | ["Show Eldoc documentation at point" eldoc-doc-buffer] | ||
| 1858 | "--" | ||
| 1859 | ["All possible code actions" eglot-code-actions | ||
| 1860 | :active (eglot--server-capable :codeActionProvider)] | ||
| 1861 | ["Organize imports" eglot-code-action-organize-imports | ||
| 1862 | :visible (eglot--server-capable :codeActionProvider)] | ||
| 1863 | ["Extract" eglot-code-action-extract | ||
| 1864 | :visible (eglot--server-capable :codeActionProvider)] | ||
| 1865 | ["Inline" eglot-code-action-inline | ||
| 1866 | :visible (eglot--server-capable :codeActionProvider)] | ||
| 1867 | ["Rewrite" eglot-code-action-rewrite | ||
| 1868 | :visible (eglot--server-capable :codeActionProvider)] | ||
| 1869 | ["Quickfix" eglot-code-action-quickfix | ||
| 1870 | :visible (eglot--server-capable :codeActionProvider)])) | ||
| 1871 | |||
| 1872 | (easy-menu-define eglot-server-menu nil "Monitor server communication" | ||
| 1873 | '("Debugging the server communication" | ||
| 1874 | ["Reconnect to server" eglot-reconnect] | ||
| 1875 | ["Quit server" eglot-shutdown] | ||
| 1876 | "--" | ||
| 1877 | ["LSP events buffer" eglot-events-buffer] | ||
| 1878 | ["Server stderr buffer" eglot-stderr-buffer] | ||
| 1879 | ["Customize event buffer size" | ||
| 1880 | (lambda () | ||
| 1881 | (interactive) | ||
| 1882 | (customize-variable 'eglot-events-buffer-size))])) | ||
| 1883 | |||
| 1884 | (defun eglot--mode-line-props (thing face defs &optional prepend) | ||
| 1885 | "Helper for function `eglot--mode-line-format'. | ||
| 1886 | Uses THING, FACE, DEFS and PREPEND." | ||
| 1887 | (cl-loop with map = (make-sparse-keymap) | ||
| 1888 | for (elem . rest) on defs | ||
| 1889 | for (key def help) = elem | ||
| 1890 | do (define-key map `[mode-line ,key] (eglot--mouse-call def)) | ||
| 1891 | concat (format "%s: %s" key help) into blurb | ||
| 1892 | when rest concat "\n" into blurb | ||
| 1893 | finally (return `(:propertize ,thing | ||
| 1894 | face ,face | ||
| 1895 | keymap ,map help-echo ,(concat prepend blurb) | ||
| 1896 | mouse-face mode-line-highlight)))) | ||
| 1897 | |||
| 1898 | (defun eglot--mode-line-format () | ||
| 1899 | "Compose the Eglot's mode-line." | ||
| 1900 | (pcase-let* ((server (eglot-current-server)) | ||
| 1901 | (nick (and server (eglot-project-nickname server))) | ||
| 1902 | (pending (and server (hash-table-count | ||
| 1903 | (jsonrpc--request-continuations server)))) | ||
| 1904 | (`(,_id ,doing ,done-p ,_detail) (and server (eglot--spinner server))) | ||
| 1905 | (last-error (and server (jsonrpc-last-error server)))) | ||
| 1906 | (append | ||
| 1907 | `(,(propertize | ||
| 1908 | eglot-menu-string | ||
| 1909 | 'face 'eglot-mode-line | ||
| 1910 | 'mouse-face 'mode-line-highlight | ||
| 1911 | 'help-echo "Eglot: Emacs LSP client\nmouse-1: Display minor mode menu" | ||
| 1912 | 'keymap (let ((map (make-sparse-keymap))) | ||
| 1913 | (define-key map [mode-line down-mouse-1] eglot-menu) | ||
| 1914 | map))) | ||
| 1915 | (when nick | ||
| 1916 | `(":" | ||
| 1917 | ,(propertize | ||
| 1918 | nick | ||
| 1919 | 'face 'eglot-mode-line | ||
| 1920 | 'mouse-face 'mode-line-highlight | ||
| 1921 | 'help-echo (format "Project '%s'\nmouse-1: LSP server control menu" nick) | ||
| 1922 | 'keymap (let ((map (make-sparse-keymap))) | ||
| 1923 | (define-key map [mode-line down-mouse-1] eglot-server-menu) | ||
| 1924 | map)) | ||
| 1925 | ,@(when last-error | ||
| 1926 | `("/" ,(eglot--mode-line-props | ||
| 1927 | "error" 'compilation-mode-line-fail | ||
| 1928 | '((mouse-3 eglot-clear-status "Clear this status")) | ||
| 1929 | (format "An error occurred: %s\n" (plist-get last-error | ||
| 1930 | :message))))) | ||
| 1931 | ,@(when (and doing (not done-p)) | ||
| 1932 | `("/" ,(eglot--mode-line-props doing | ||
| 1933 | 'compilation-mode-line-run '()))) | ||
| 1934 | ,@(when (cl-plusp pending) | ||
| 1935 | `("/" ,(eglot--mode-line-props | ||
| 1936 | (format "%d" pending) 'warning | ||
| 1937 | '((mouse-3 eglot-forget-pending-continuations | ||
| 1938 | "Forget pending continuations")) | ||
| 1939 | "Number of outgoing, \ | ||
| 1940 | still unanswered LSP requests to the server\n")))))))) | ||
| 1941 | |||
| 1942 | (add-to-list 'mode-line-misc-info | ||
| 1943 | `(eglot--managed-mode (" [" eglot--mode-line-format "] "))) | ||
| 1944 | |||
| 1945 | |||
| 1946 | ;;; Flymake customization | ||
| 1947 | ;;; | ||
| 1948 | (put 'eglot-note 'flymake-category 'flymake-note) | ||
| 1949 | (put 'eglot-warning 'flymake-category 'flymake-warning) | ||
| 1950 | (put 'eglot-error 'flymake-category 'flymake-error) | ||
| 1951 | |||
| 1952 | (defalias 'eglot--make-diag 'flymake-make-diagnostic) | ||
| 1953 | (defalias 'eglot--diag-data 'flymake-diagnostic-data) | ||
| 1954 | |||
| 1955 | (cl-loop for i from 1 | ||
| 1956 | for type in '(eglot-note eglot-warning eglot-error ) | ||
| 1957 | do (put type 'flymake-overlay-control | ||
| 1958 | `((mouse-face . highlight) | ||
| 1959 | (priority . ,(+ 50 i)) | ||
| 1960 | (keymap . ,(let ((map (make-sparse-keymap))) | ||
| 1961 | (define-key map [mouse-1] | ||
| 1962 | (eglot--mouse-call 'eglot-code-actions)) | ||
| 1963 | map))))) | ||
| 1964 | |||
| 1965 | |||
| 1966 | ;;; Protocol implementation (Requests, notifications, etc) | ||
| 1967 | ;;; | ||
| 1968 | (cl-defmethod eglot-handle-notification | ||
| 1969 | (_server method &key &allow-other-keys) | ||
| 1970 | "Handle unknown notification." | ||
| 1971 | (unless (or (string-prefix-p "$" (format "%s" method)) | ||
| 1972 | (not (memq 'disallow-unknown-methods eglot-strict-mode))) | ||
| 1973 | (eglot--warn "Server sent unknown notification method `%s'" method))) | ||
| 1974 | |||
| 1975 | (cl-defmethod eglot-handle-request | ||
| 1976 | (_server method &key &allow-other-keys) | ||
| 1977 | "Handle unknown request." | ||
| 1978 | (when (memq 'disallow-unknown-methods eglot-strict-mode) | ||
| 1979 | (jsonrpc-error "Unknown request method `%s'" method))) | ||
| 1980 | |||
| 1981 | (cl-defmethod eglot-execute-command | ||
| 1982 | (server command arguments) | ||
| 1983 | "Execute COMMAND on SERVER with `:workspace/executeCommand'. | ||
| 1984 | COMMAND is a symbol naming the command." | ||
| 1985 | (jsonrpc-request server :workspace/executeCommand | ||
| 1986 | `(:command ,(format "%s" command) :arguments ,arguments))) | ||
| 1987 | |||
| 1988 | (cl-defmethod eglot-handle-notification | ||
| 1989 | (_server (_method (eql window/showMessage)) &key type message) | ||
| 1990 | "Handle notification window/showMessage." | ||
| 1991 | (eglot--message (propertize "Server reports (type=%s): %s" | ||
| 1992 | 'face (if (<= type 1) 'error)) | ||
| 1993 | type message)) | ||
| 1994 | |||
| 1995 | (cl-defmethod eglot-handle-request | ||
| 1996 | (_server (_method (eql window/showMessageRequest)) &key type message actions) | ||
| 1997 | "Handle server request window/showMessageRequest." | ||
| 1998 | (let* ((actions (append actions nil)) ;; gh#627 | ||
| 1999 | (label (completing-read | ||
| 2000 | (concat | ||
| 2001 | (format (propertize "[eglot] Server reports (type=%s): %s" | ||
| 2002 | 'face (if (<= type 1) 'error)) | ||
| 2003 | type message) | ||
| 2004 | "\nChoose an option: ") | ||
| 2005 | (or (mapcar (lambda (obj) (plist-get obj :title)) actions) | ||
| 2006 | '("OK")) | ||
| 2007 | nil t (plist-get (elt actions 0) :title)))) | ||
| 2008 | (if label `(:title ,label) :null))) | ||
| 2009 | |||
| 2010 | (cl-defmethod eglot-handle-notification | ||
| 2011 | (_server (_method (eql window/logMessage)) &key _type _message) | ||
| 2012 | "Handle notification window/logMessage.") ;; noop, use events buffer | ||
| 2013 | |||
| 2014 | (cl-defmethod eglot-handle-notification | ||
| 2015 | (_server (_method (eql telemetry/event)) &rest _any) | ||
| 2016 | "Handle notification telemetry/event.") ;; noop, use events buffer | ||
| 2017 | |||
| 2018 | (cl-defmethod eglot-handle-notification | ||
| 2019 | (_server (_method (eql textDocument/publishDiagnostics)) &key uri diagnostics | ||
| 2020 | &allow-other-keys) ; FIXME: doesn't respect `eglot-strict-mode' | ||
| 2021 | "Handle notification publishDiagnostics." | ||
| 2022 | (cl-flet ((eglot--diag-type (sev) | ||
| 2023 | (cond ((null sev) 'eglot-error) | ||
| 2024 | ((<= sev 1) 'eglot-error) | ||
| 2025 | ((= sev 2) 'eglot-warning) | ||
| 2026 | (t 'eglot-note))) | ||
| 2027 | (mess (source code message) | ||
| 2028 | (concat source (and code (format " [%s]" code)) ": " message))) | ||
| 2029 | (if-let ((buffer (find-buffer-visiting (eglot--uri-to-path uri)))) | ||
| 2030 | (with-current-buffer buffer | ||
| 2031 | (cl-loop | ||
| 2032 | for diag-spec across diagnostics | ||
| 2033 | collect (eglot--dbind ((Diagnostic) range code message severity source tags) | ||
| 2034 | diag-spec | ||
| 2035 | (setq message (mess source code message)) | ||
| 2036 | (pcase-let | ||
| 2037 | ((`(,beg . ,end) (eglot--range-region range))) | ||
| 2038 | ;; Fallback to `flymake-diag-region' if server | ||
| 2039 | ;; botched the range | ||
| 2040 | (when (= beg end) | ||
| 2041 | (if-let* ((st (plist-get range :start)) | ||
| 2042 | (diag-region | ||
| 2043 | (flymake-diag-region | ||
| 2044 | (current-buffer) (1+ (plist-get st :line)) | ||
| 2045 | (plist-get st :character)))) | ||
| 2046 | (setq beg (car diag-region) end (cdr diag-region)) | ||
| 2047 | (eglot--widening | ||
| 2048 | (goto-char (point-min)) | ||
| 2049 | (setq beg | ||
| 2050 | (line-beginning-position | ||
| 2051 | (1+ (plist-get (plist-get range :start) :line)))) | ||
| 2052 | (setq end | ||
| 2053 | (line-end-position | ||
| 2054 | (1+ (plist-get (plist-get range :end) :line))))))) | ||
| 2055 | (eglot--make-diag | ||
| 2056 | (current-buffer) beg end | ||
| 2057 | (eglot--diag-type severity) | ||
| 2058 | message `((eglot-lsp-diag . ,diag-spec)) | ||
| 2059 | (when-let ((faces | ||
| 2060 | (cl-loop for tag across tags | ||
| 2061 | when (alist-get tag eglot--tag-faces) | ||
| 2062 | collect it))) | ||
| 2063 | `((face . ,faces)))))) | ||
| 2064 | into diags | ||
| 2065 | finally (cond ((and | ||
| 2066 | ;; only add to current report if Flymake | ||
| 2067 | ;; starts on idle-timer (github#958) | ||
| 2068 | (not (null flymake-no-changes-timeout)) | ||
| 2069 | eglot--current-flymake-report-fn) | ||
| 2070 | (eglot--report-to-flymake diags)) | ||
| 2071 | (t | ||
| 2072 | (setq eglot--diagnostics diags))))) | ||
| 2073 | (cl-loop | ||
| 2074 | with path = (expand-file-name (eglot--uri-to-path uri)) | ||
| 2075 | for diag-spec across diagnostics | ||
| 2076 | collect (eglot--dbind ((Diagnostic) code range message severity source) diag-spec | ||
| 2077 | (setq message (mess source code message)) | ||
| 2078 | (let* ((start (plist-get range :start)) | ||
| 2079 | (line (1+ (plist-get start :line))) | ||
| 2080 | (char (1+ (plist-get start :character)))) | ||
| 2081 | (eglot--make-diag | ||
| 2082 | path (cons line char) nil (eglot--diag-type severity) message))) | ||
| 2083 | into diags | ||
| 2084 | finally | ||
| 2085 | (setq flymake-list-only-diagnostics | ||
| 2086 | (assoc-delete-all path flymake-list-only-diagnostics #'string=)) | ||
| 2087 | (push (cons path diags) flymake-list-only-diagnostics))))) | ||
| 2088 | |||
| 2089 | (cl-defun eglot--register-unregister (server things how) | ||
| 2090 | "Helper for `registerCapability'. | ||
| 2091 | THINGS are either registrations or unregisterations (sic)." | ||
| 2092 | (cl-loop | ||
| 2093 | for thing in (cl-coerce things 'list) | ||
| 2094 | do (eglot--dbind ((Registration) id method registerOptions) thing | ||
| 2095 | (apply (cl-ecase how | ||
| 2096 | (register 'eglot-register-capability) | ||
| 2097 | (unregister 'eglot-unregister-capability)) | ||
| 2098 | server (intern method) id registerOptions)))) | ||
| 2099 | |||
| 2100 | (cl-defmethod eglot-handle-request | ||
| 2101 | (server (_method (eql client/registerCapability)) &key registrations) | ||
| 2102 | "Handle server request client/registerCapability." | ||
| 2103 | (eglot--register-unregister server registrations 'register)) | ||
| 2104 | |||
| 2105 | (cl-defmethod eglot-handle-request | ||
| 2106 | (server (_method (eql client/unregisterCapability)) | ||
| 2107 | &key unregisterations) ;; XXX: "unregisterations" (sic) | ||
| 2108 | "Handle server request client/unregisterCapability." | ||
| 2109 | (eglot--register-unregister server unregisterations 'unregister)) | ||
| 2110 | |||
| 2111 | (cl-defmethod eglot-handle-request | ||
| 2112 | (_server (_method (eql workspace/applyEdit)) &key _label edit) | ||
| 2113 | "Handle server request workspace/applyEdit." | ||
| 2114 | (eglot--apply-workspace-edit edit eglot-confirm-server-initiated-edits) | ||
| 2115 | `(:applied t)) | ||
| 2116 | |||
| 2117 | (cl-defmethod eglot-handle-request | ||
| 2118 | (server (_method (eql workspace/workspaceFolders))) | ||
| 2119 | "Handle server request workspace/workspaceFolders." | ||
| 2120 | (eglot-workspace-folders server)) | ||
| 2121 | |||
| 2122 | (defun eglot--TextDocumentIdentifier () | ||
| 2123 | "Compute TextDocumentIdentifier object for current buffer." | ||
| 2124 | `(:uri ,(eglot--path-to-uri (or buffer-file-name | ||
| 2125 | (ignore-errors | ||
| 2126 | (buffer-file-name | ||
| 2127 | (buffer-base-buffer))))))) | ||
| 2128 | |||
| 2129 | (defvar-local eglot--versioned-identifier 0) | ||
| 2130 | |||
| 2131 | (defun eglot--VersionedTextDocumentIdentifier () | ||
| 2132 | "Compute VersionedTextDocumentIdentifier object for current buffer." | ||
| 2133 | (append (eglot--TextDocumentIdentifier) | ||
| 2134 | `(:version ,eglot--versioned-identifier))) | ||
| 2135 | |||
| 2136 | (defun eglot--TextDocumentItem () | ||
| 2137 | "Compute TextDocumentItem object for current buffer." | ||
| 2138 | (append | ||
| 2139 | (eglot--VersionedTextDocumentIdentifier) | ||
| 2140 | (list :languageId | ||
| 2141 | (eglot--language-id (eglot--current-server-or-lose)) | ||
| 2142 | :text | ||
| 2143 | (eglot--widening | ||
| 2144 | (buffer-substring-no-properties (point-min) (point-max)))))) | ||
| 2145 | |||
| 2146 | (defun eglot--TextDocumentPositionParams () | ||
| 2147 | "Compute TextDocumentPositionParams." | ||
| 2148 | (list :textDocument (eglot--TextDocumentIdentifier) | ||
| 2149 | :position (eglot--pos-to-lsp-position))) | ||
| 2150 | |||
| 2151 | (defvar-local eglot--last-inserted-char nil | ||
| 2152 | "If non-nil, value of the last inserted character in buffer.") | ||
| 2153 | |||
| 2154 | (defun eglot--post-self-insert-hook () | ||
| 2155 | "Set `eglot--last-inserted-char', maybe call on-type-formatting." | ||
| 2156 | (setq eglot--last-inserted-char last-input-event) | ||
| 2157 | (let ((ot-provider (eglot--server-capable :documentOnTypeFormattingProvider))) | ||
| 2158 | (when (and ot-provider | ||
| 2159 | (ignore-errors ; github#906, some LS's send empty strings | ||
| 2160 | (or (eq last-input-event | ||
| 2161 | (seq-first (plist-get ot-provider :firstTriggerCharacter))) | ||
| 2162 | (cl-find last-input-event | ||
| 2163 | (plist-get ot-provider :moreTriggerCharacter) | ||
| 2164 | :key #'seq-first)))) | ||
| 2165 | (eglot-format (point) nil last-input-event)))) | ||
| 2166 | |||
| 2167 | (defvar eglot--workspace-symbols-cache (make-hash-table :test #'equal) | ||
| 2168 | "Cache of `workspace/Symbol' results used by `xref-find-definitions'.") | ||
| 2169 | |||
| 2170 | (defun eglot--pre-command-hook () | ||
| 2171 | "Reset some temporary variables." | ||
| 2172 | (clrhash eglot--workspace-symbols-cache) | ||
| 2173 | (setq eglot--last-inserted-char nil)) | ||
| 2174 | |||
| 2175 | (defun eglot--CompletionParams () | ||
| 2176 | (append | ||
| 2177 | (eglot--TextDocumentPositionParams) | ||
| 2178 | `(:context | ||
| 2179 | ,(if-let (trigger (and (characterp eglot--last-inserted-char) | ||
| 2180 | (cl-find eglot--last-inserted-char | ||
| 2181 | (eglot--server-capable :completionProvider | ||
| 2182 | :triggerCharacters) | ||
| 2183 | :key (lambda (str) (aref str 0)) | ||
| 2184 | :test #'char-equal))) | ||
| 2185 | `(:triggerKind 2 :triggerCharacter ,trigger) `(:triggerKind 1))))) | ||
| 2186 | |||
| 2187 | (defvar-local eglot--recent-changes nil | ||
| 2188 | "Recent buffer changes as collected by `eglot--before-change'.") | ||
| 2189 | |||
| 2190 | (cl-defmethod jsonrpc-connection-ready-p ((_server eglot-lsp-server) _what) | ||
| 2191 | "Tell if SERVER is ready for WHAT in current buffer." | ||
| 2192 | (and (cl-call-next-method) (not eglot--recent-changes))) | ||
| 2193 | |||
| 2194 | (defvar-local eglot--change-idle-timer nil "Idle timer for didChange signals.") | ||
| 2195 | |||
| 2196 | (defun eglot--before-change (beg end) | ||
| 2197 | "Hook onto `before-change-functions' with BEG and END." | ||
| 2198 | (when (listp eglot--recent-changes) | ||
| 2199 | ;; Records BEG and END, crucially convert them into LSP | ||
| 2200 | ;; (line/char) positions before that information is lost (because | ||
| 2201 | ;; the after-change thingy doesn't know if newlines were | ||
| 2202 | ;; deleted/added). Also record markers of BEG and END | ||
| 2203 | ;; (github#259) | ||
| 2204 | (push `(,(eglot--pos-to-lsp-position beg) | ||
| 2205 | ,(eglot--pos-to-lsp-position end) | ||
| 2206 | (,beg . ,(copy-marker beg nil)) | ||
| 2207 | (,end . ,(copy-marker end t))) | ||
| 2208 | eglot--recent-changes))) | ||
| 2209 | |||
| 2210 | (defun eglot--after-change (beg end pre-change-length) | ||
| 2211 | "Hook onto `after-change-functions'. | ||
| 2212 | Records BEG, END and PRE-CHANGE-LENGTH locally." | ||
| 2213 | (cl-incf eglot--versioned-identifier) | ||
| 2214 | (pcase (and (listp eglot--recent-changes) | ||
| 2215 | (car eglot--recent-changes)) | ||
| 2216 | (`(,lsp-beg ,lsp-end | ||
| 2217 | (,b-beg . ,b-beg-marker) | ||
| 2218 | (,b-end . ,b-end-marker)) | ||
| 2219 | ;; github#259 and github#367: With `capitalize-word' or somesuch, | ||
| 2220 | ;; `before-change-functions' always records the whole word's | ||
| 2221 | ;; `b-beg' and `b-end'. Similarly, when coalescing two lines | ||
| 2222 | ;; into one, `fill-paragraph' they mark the end of the first line | ||
| 2223 | ;; up to the end of the second line. In both situations, args | ||
| 2224 | ;; received here contradict that information: `beg' and `end' | ||
| 2225 | ;; will differ by 1 and will likely only encompass the letter | ||
| 2226 | ;; that was capitalized or, in the sentence-joining situation, | ||
| 2227 | ;; the replacement of the newline with a space. That's we keep | ||
| 2228 | ;; markers _and_ positions so we're able to detect and correct | ||
| 2229 | ;; this. We ignore `beg', `len' and `pre-change-len' and send | ||
| 2230 | ;; "fuller" information about the region from the markers. I've | ||
| 2231 | ;; also experimented with doing this unconditionally but it seems | ||
| 2232 | ;; to break when newlines are added. | ||
| 2233 | (if (and (= b-end b-end-marker) (= b-beg b-beg-marker) | ||
| 2234 | (or (/= beg b-beg) (/= end b-end))) | ||
| 2235 | (setcar eglot--recent-changes | ||
| 2236 | `(,lsp-beg ,lsp-end ,(- b-end-marker b-beg-marker) | ||
| 2237 | ,(buffer-substring-no-properties b-beg-marker | ||
| 2238 | b-end-marker))) | ||
| 2239 | (setcar eglot--recent-changes | ||
| 2240 | `(,lsp-beg ,lsp-end ,pre-change-length | ||
| 2241 | ,(buffer-substring-no-properties beg end))))) | ||
| 2242 | (_ (setf eglot--recent-changes :emacs-messup))) | ||
| 2243 | (when eglot--change-idle-timer (cancel-timer eglot--change-idle-timer)) | ||
| 2244 | (let ((buf (current-buffer))) | ||
| 2245 | (setq eglot--change-idle-timer | ||
| 2246 | (run-with-idle-timer | ||
| 2247 | eglot-send-changes-idle-time | ||
| 2248 | nil (lambda () (eglot--when-live-buffer buf | ||
| 2249 | (when eglot--managed-mode | ||
| 2250 | (eglot--signal-textDocument/didChange) | ||
| 2251 | (setq eglot--change-idle-timer nil)))))))) | ||
| 2252 | |||
| 2253 | ;; HACK! Launching a deferred sync request with outstanding changes is a | ||
| 2254 | ;; bad idea, since that might lead to the request never having a | ||
| 2255 | ;; chance to run, because `jsonrpc-connection-ready-p'. | ||
| 2256 | (advice-add #'jsonrpc-request :before | ||
| 2257 | (cl-function (lambda (_proc _method _params &key | ||
| 2258 | deferred &allow-other-keys) | ||
| 2259 | (when (and eglot--managed-mode deferred) | ||
| 2260 | (eglot--signal-textDocument/didChange)))) | ||
| 2261 | '((name . eglot--signal-textDocument/didChange))) | ||
| 2262 | |||
| 2263 | (defvar-local eglot-workspace-configuration () | ||
| 2264 | "Configure LSP servers specifically for a given project. | ||
| 2265 | |||
| 2266 | This variable's value should be a plist (SECTION VALUE ...). | ||
| 2267 | SECTION is a keyword naming a parameter section relevant to a | ||
| 2268 | particular server. VALUE is a plist or a primitive type | ||
| 2269 | converted to JSON also understood by that server. | ||
| 2270 | |||
| 2271 | Instead of a plist, an alist ((SECTION . VALUE) ...) can be used | ||
| 2272 | instead, but this variant is less reliable and not recommended. | ||
| 2273 | |||
| 2274 | This variable should be set as a directory-local variable. See | ||
| 2275 | See info node `(emacs)Directory Variables' for various ways to to | ||
| 2276 | that. | ||
| 2277 | |||
| 2278 | Here's an example value that establishes two sections relevant to | ||
| 2279 | the Pylsp and Gopls LSP servers: | ||
| 2280 | |||
| 2281 | (:pylsp (:plugins (:jedi_completion (:include_params t | ||
| 2282 | :fuzzy t) | ||
| 2283 | :pylint (:enabled :json-false))) | ||
| 2284 | :gopls (:usePlaceholders t)) | ||
| 2285 | |||
| 2286 | The value of this variable can also be a unary function of a | ||
| 2287 | single argument, which will be a connected `eglot-lsp-server' | ||
| 2288 | instance. The function runs with `default-directory' set to the | ||
| 2289 | root of the current project. It should return an object of the | ||
| 2290 | format described above.") | ||
| 2291 | |||
| 2292 | ;;;###autoload | ||
| 2293 | (put 'eglot-workspace-configuration 'safe-local-variable 'listp) | ||
| 2294 | |||
| 2295 | (defun eglot-show-workspace-configuration (&optional server) | ||
| 2296 | "Dump `eglot-workspace-configuration' as JSON for debugging." | ||
| 2297 | (interactive (list (and (eglot-current-server) | ||
| 2298 | (eglot--read-server "Server configuration" | ||
| 2299 | (eglot-current-server))))) | ||
| 2300 | (let ((conf (eglot--workspace-configuration-plist server))) | ||
| 2301 | (with-current-buffer (get-buffer-create "*EGLOT workspace configuration*") | ||
| 2302 | (erase-buffer) | ||
| 2303 | (insert (jsonrpc--json-encode conf)) | ||
| 2304 | (with-no-warnings | ||
| 2305 | (require 'json) | ||
| 2306 | (when (require 'json-mode nil t) (json-mode)) | ||
| 2307 | (json-pretty-print-buffer)) | ||
| 2308 | (pop-to-buffer (current-buffer))))) | ||
| 2309 | |||
| 2310 | (defun eglot--workspace-configuration (server) | ||
| 2311 | (if (functionp eglot-workspace-configuration) | ||
| 2312 | (funcall eglot-workspace-configuration server) | ||
| 2313 | eglot-workspace-configuration)) | ||
| 2314 | |||
| 2315 | (defun eglot--workspace-configuration-plist (server) | ||
| 2316 | "Returns `eglot-workspace-configuration' suitable for serialization." | ||
| 2317 | (let ((val (eglot--workspace-configuration server))) | ||
| 2318 | (or (and (consp (car val)) | ||
| 2319 | (cl-loop for (section . v) in val | ||
| 2320 | collect (if (keywordp section) section | ||
| 2321 | (intern (format ":%s" section))) | ||
| 2322 | collect v)) | ||
| 2323 | val))) | ||
| 2324 | |||
| 2325 | (defun eglot-signal-didChangeConfiguration (server) | ||
| 2326 | "Send a `:workspace/didChangeConfiguration' signal to SERVER. | ||
| 2327 | When called interactively, use the currently active server" | ||
| 2328 | (interactive (list (eglot--current-server-or-lose))) | ||
| 2329 | (jsonrpc-notify | ||
| 2330 | server :workspace/didChangeConfiguration | ||
| 2331 | (list | ||
| 2332 | :settings | ||
| 2333 | (or (eglot--workspace-configuration-plist server) | ||
| 2334 | eglot--{})))) | ||
| 2335 | |||
| 2336 | (cl-defmethod eglot-handle-request | ||
| 2337 | (server (_method (eql workspace/configuration)) &key items) | ||
| 2338 | "Handle server request workspace/configuration." | ||
| 2339 | (apply #'vector | ||
| 2340 | (mapcar | ||
| 2341 | (eglot--lambda ((ConfigurationItem) scopeUri section) | ||
| 2342 | (with-temp-buffer | ||
| 2343 | (let* ((uri-path (eglot--uri-to-path scopeUri)) | ||
| 2344 | (default-directory | ||
| 2345 | (if (and (not (string-empty-p uri-path)) | ||
| 2346 | (file-directory-p uri-path)) | ||
| 2347 | (file-name-as-directory uri-path) | ||
| 2348 | (project-root (eglot--project server))))) | ||
| 2349 | (setq-local major-mode (car (eglot--major-modes server))) | ||
| 2350 | (hack-dir-local-variables-non-file-buffer) | ||
| 2351 | (cl-loop for (wsection o) | ||
| 2352 | on (eglot--workspace-configuration-plist server) | ||
| 2353 | by #'cddr | ||
| 2354 | when (string= | ||
| 2355 | (if (keywordp wsection) | ||
| 2356 | (substring (symbol-name wsection) 1) | ||
| 2357 | wsection) | ||
| 2358 | section) | ||
| 2359 | return o)))) | ||
| 2360 | items))) | ||
| 2361 | |||
| 2362 | (defun eglot--signal-textDocument/didChange () | ||
| 2363 | "Send textDocument/didChange to server." | ||
| 2364 | (when eglot--recent-changes | ||
| 2365 | (let* ((server (eglot--current-server-or-lose)) | ||
| 2366 | (sync-capability (eglot--server-capable :textDocumentSync)) | ||
| 2367 | (sync-kind (if (numberp sync-capability) sync-capability | ||
| 2368 | (plist-get sync-capability :change))) | ||
| 2369 | (full-sync-p (or (eq sync-kind 1) | ||
| 2370 | (eq :emacs-messup eglot--recent-changes)))) | ||
| 2371 | (jsonrpc-notify | ||
| 2372 | server :textDocument/didChange | ||
| 2373 | (list | ||
| 2374 | :textDocument (eglot--VersionedTextDocumentIdentifier) | ||
| 2375 | :contentChanges | ||
| 2376 | (if full-sync-p | ||
| 2377 | (vector `(:text ,(eglot--widening | ||
| 2378 | (buffer-substring-no-properties (point-min) | ||
| 2379 | (point-max))))) | ||
| 2380 | (cl-loop for (beg end len text) in (reverse eglot--recent-changes) | ||
| 2381 | ;; github#259: `capitalize-word' and commands based | ||
| 2382 | ;; on `casify_region' will cause multiple duplicate | ||
| 2383 | ;; empty entries in `eglot--before-change' calls | ||
| 2384 | ;; without an `eglot--after-change' reciprocal. | ||
| 2385 | ;; Weed them out here. | ||
| 2386 | when (numberp len) | ||
| 2387 | vconcat `[,(list :range `(:start ,beg :end ,end) | ||
| 2388 | :rangeLength len :text text)])))) | ||
| 2389 | (setq eglot--recent-changes nil) | ||
| 2390 | (setf (eglot--spinner server) (list nil :textDocument/didChange t)) | ||
| 2391 | (jsonrpc--call-deferred server)))) | ||
| 2392 | |||
| 2393 | (defun eglot--signal-textDocument/didOpen () | ||
| 2394 | "Send textDocument/didOpen to server." | ||
| 2395 | (setq eglot--recent-changes nil eglot--versioned-identifier 0) | ||
| 2396 | (jsonrpc-notify | ||
| 2397 | (eglot--current-server-or-lose) | ||
| 2398 | :textDocument/didOpen `(:textDocument ,(eglot--TextDocumentItem)))) | ||
| 2399 | |||
| 2400 | (defun eglot--signal-textDocument/didClose () | ||
| 2401 | "Send textDocument/didClose to server." | ||
| 2402 | (with-demoted-errors | ||
| 2403 | "[eglot] error sending textDocument/didClose: %s" | ||
| 2404 | (jsonrpc-notify | ||
| 2405 | (eglot--current-server-or-lose) | ||
| 2406 | :textDocument/didClose `(:textDocument ,(eglot--TextDocumentIdentifier))))) | ||
| 2407 | |||
| 2408 | (defun eglot--signal-textDocument/willSave () | ||
| 2409 | "Send textDocument/willSave to server." | ||
| 2410 | (let ((server (eglot--current-server-or-lose)) | ||
| 2411 | (params `(:reason 1 :textDocument ,(eglot--TextDocumentIdentifier)))) | ||
| 2412 | (when (eglot--server-capable :textDocumentSync :willSave) | ||
| 2413 | (jsonrpc-notify server :textDocument/willSave params)) | ||
| 2414 | (when (eglot--server-capable :textDocumentSync :willSaveWaitUntil) | ||
| 2415 | (ignore-errors | ||
| 2416 | (eglot--apply-text-edits | ||
| 2417 | (jsonrpc-request server :textDocument/willSaveWaitUntil params | ||
| 2418 | :timeout 0.5)))))) | ||
| 2419 | |||
| 2420 | (defun eglot--signal-textDocument/didSave () | ||
| 2421 | "Send textDocument/didSave to server." | ||
| 2422 | (eglot--signal-textDocument/didChange) | ||
| 2423 | (jsonrpc-notify | ||
| 2424 | (eglot--current-server-or-lose) | ||
| 2425 | :textDocument/didSave | ||
| 2426 | (list | ||
| 2427 | ;; TODO: Handle TextDocumentSaveRegistrationOptions to control this. | ||
| 2428 | :text (buffer-substring-no-properties (point-min) (point-max)) | ||
| 2429 | :textDocument (eglot--TextDocumentIdentifier)))) | ||
| 2430 | |||
| 2431 | (defun eglot-flymake-backend (report-fn &rest _more) | ||
| 2432 | "A Flymake backend for Eglot. | ||
| 2433 | Calls REPORT-FN (or arranges for it to be called) when the server | ||
| 2434 | publishes diagnostics. Between calls to this function, REPORT-FN | ||
| 2435 | may be called multiple times (respecting the protocol of | ||
| 2436 | `flymake-backend-functions')." | ||
| 2437 | (cond (eglot--managed-mode | ||
| 2438 | (setq eglot--current-flymake-report-fn report-fn) | ||
| 2439 | (eglot--report-to-flymake eglot--diagnostics)) | ||
| 2440 | (t | ||
| 2441 | (funcall report-fn nil)))) | ||
| 2442 | |||
| 2443 | (defun eglot--report-to-flymake (diags) | ||
| 2444 | "Internal helper for `eglot-flymake-backend'." | ||
| 2445 | (save-restriction | ||
| 2446 | (widen) | ||
| 2447 | (funcall eglot--current-flymake-report-fn diags | ||
| 2448 | ;; If the buffer hasn't changed since last | ||
| 2449 | ;; call to the report function, flymake won't | ||
| 2450 | ;; delete old diagnostics. Using :region | ||
| 2451 | ;; keyword forces flymake to delete | ||
| 2452 | ;; them (github#159). | ||
| 2453 | :region (cons (point-min) (point-max)))) | ||
| 2454 | (setq eglot--diagnostics diags)) | ||
| 2455 | |||
| 2456 | (defun eglot-xref-backend () "Eglot xref backend." 'eglot) | ||
| 2457 | |||
| 2458 | (defvar eglot--temp-location-buffers (make-hash-table :test #'equal) | ||
| 2459 | "Helper variable for `eglot--handling-xrefs'.") | ||
| 2460 | |||
| 2461 | (defvar eglot-xref-lessp-function #'ignore | ||
| 2462 | "Compare two `xref-item' objects for sorting.") | ||
| 2463 | |||
| 2464 | (cl-defmacro eglot--collecting-xrefs ((collector) &rest body) | ||
| 2465 | "Sort and handle xrefs collected with COLLECTOR in BODY." | ||
| 2466 | (declare (indent 1) (debug (sexp &rest form))) | ||
| 2467 | (let ((collected (cl-gensym "collected"))) | ||
| 2468 | `(unwind-protect | ||
| 2469 | (let (,collected) | ||
| 2470 | (cl-flet ((,collector (xref) (push xref ,collected))) | ||
| 2471 | ,@body) | ||
| 2472 | (setq ,collected (nreverse ,collected)) | ||
| 2473 | (sort ,collected eglot-xref-lessp-function)) | ||
| 2474 | (maphash (lambda (_uri buf) (kill-buffer buf)) eglot--temp-location-buffers) | ||
| 2475 | (clrhash eglot--temp-location-buffers)))) | ||
| 2476 | |||
| 2477 | (defun eglot--xref-make-match (name uri range) | ||
| 2478 | "Like `xref-make-match' but with LSP's NAME, URI and RANGE. | ||
| 2479 | Try to visit the target file for a richer summary line." | ||
| 2480 | (pcase-let* | ||
| 2481 | ((file (eglot--uri-to-path uri)) | ||
| 2482 | (visiting (or (find-buffer-visiting file) | ||
| 2483 | (gethash uri eglot--temp-location-buffers))) | ||
| 2484 | (collect (lambda () | ||
| 2485 | (eglot--widening | ||
| 2486 | (pcase-let* ((`(,beg . ,end) (eglot--range-region range)) | ||
| 2487 | (bol (progn (goto-char beg) (line-beginning-position))) | ||
| 2488 | (substring (buffer-substring bol (line-end-position))) | ||
| 2489 | (hi-beg (- beg bol)) | ||
| 2490 | (hi-end (- (min (line-end-position) end) bol))) | ||
| 2491 | (add-face-text-property hi-beg hi-end 'xref-match | ||
| 2492 | t substring) | ||
| 2493 | (list substring (line-number-at-pos (point) t) | ||
| 2494 | (eglot-current-column) (- end beg)))))) | ||
| 2495 | (`(,summary ,line ,column ,length) | ||
| 2496 | (cond | ||
| 2497 | (visiting (with-current-buffer visiting (funcall collect))) | ||
| 2498 | ((file-readable-p file) (with-current-buffer | ||
| 2499 | (puthash uri (generate-new-buffer " *temp*") | ||
| 2500 | eglot--temp-location-buffers) | ||
| 2501 | (insert-file-contents file) | ||
| 2502 | (funcall collect))) | ||
| 2503 | (t ;; fall back to the "dumb strategy" | ||
| 2504 | (let* ((start (cl-getf range :start)) | ||
| 2505 | (line (1+ (cl-getf start :line))) | ||
| 2506 | (start-pos (cl-getf start :character)) | ||
| 2507 | (end-pos (cl-getf (cl-getf range :end) :character))) | ||
| 2508 | (list name line start-pos (- end-pos start-pos))))))) | ||
| 2509 | (setf (gethash (expand-file-name file) eglot--servers-by-xrefed-file) | ||
| 2510 | (eglot--current-server-or-lose)) | ||
| 2511 | (xref-make-match summary (xref-make-file-location file line column) length))) | ||
| 2512 | |||
| 2513 | (defun eglot--workspace-symbols (pat &optional buffer) | ||
| 2514 | "Ask for :workspace/symbol on PAT, return list of formatted strings. | ||
| 2515 | If BUFFER, switch to it before." | ||
| 2516 | (with-current-buffer (or buffer (current-buffer)) | ||
| 2517 | (unless (eglot--server-capable :workspaceSymbolProvider) | ||
| 2518 | (eglot--error "This LSP server isn't a :workspaceSymbolProvider")) | ||
| 2519 | (mapcar | ||
| 2520 | (lambda (wss) | ||
| 2521 | (eglot--dbind ((WorkspaceSymbol) name containerName kind) wss | ||
| 2522 | (propertize | ||
| 2523 | (format "%s%s %s" | ||
| 2524 | (if (zerop (length containerName)) "" | ||
| 2525 | (concat (propertize containerName 'face 'shadow) " ")) | ||
| 2526 | name | ||
| 2527 | (propertize (alist-get kind eglot--symbol-kind-names "Unknown") | ||
| 2528 | 'face 'shadow)) | ||
| 2529 | 'eglot--lsp-workspaceSymbol wss))) | ||
| 2530 | (jsonrpc-request (eglot--current-server-or-lose) :workspace/symbol | ||
| 2531 | `(:query ,pat))))) | ||
| 2532 | |||
| 2533 | (cl-defmethod xref-backend-identifier-completion-table ((_backend (eql eglot))) | ||
| 2534 | "Yet another tricky connection between LSP and Elisp completion semantics." | ||
| 2535 | (let ((buf (current-buffer)) (cache eglot--workspace-symbols-cache)) | ||
| 2536 | (cl-labels ((refresh (pat) (eglot--workspace-symbols pat buf)) | ||
| 2537 | (lookup-1 (pat) ;; check cache, else refresh | ||
| 2538 | (let ((probe (gethash pat cache :missing))) | ||
| 2539 | (if (eq probe :missing) (puthash pat (refresh pat) cache) | ||
| 2540 | probe))) | ||
| 2541 | (lookup (pat) | ||
| 2542 | (let ((res (lookup-1 pat)) | ||
| 2543 | (def (and (string= pat "") (gethash :default cache)))) | ||
| 2544 | (append def res nil))) | ||
| 2545 | (score (c) | ||
| 2546 | (cl-getf (get-text-property | ||
| 2547 | 0 'eglot--lsp-workspaceSymbol c) | ||
| 2548 | :score 0))) | ||
| 2549 | (lambda (string _pred action) | ||
| 2550 | (pcase action | ||
| 2551 | (`metadata `(metadata | ||
| 2552 | (cycle-sort-function | ||
| 2553 | . ,(lambda (completions) | ||
| 2554 | (cl-sort completions #'> :key #'score))) | ||
| 2555 | (category . eglot-indirection-joy))) | ||
| 2556 | (`(eglot--lsp-tryc . ,point) `(eglot--lsp-tryc . (,string . ,point))) | ||
| 2557 | (`(eglot--lsp-allc . ,_point) `(eglot--lsp-allc . ,(lookup string))) | ||
| 2558 | (_ nil)))))) | ||
| 2559 | |||
| 2560 | (defun eglot--recover-workspace-symbol-meta (string) | ||
| 2561 | "Search `eglot--workspace-symbols-cache' for rich entry of STRING." | ||
| 2562 | (catch 'found | ||
| 2563 | (maphash (lambda (_k v) | ||
| 2564 | (while (consp v) | ||
| 2565 | ;; Like mess? Ask minibuffer.el about improper lists. | ||
| 2566 | (when (equal (car v) string) (throw 'found (car v))) | ||
| 2567 | (setq v (cdr v)))) | ||
| 2568 | eglot--workspace-symbols-cache))) | ||
| 2569 | |||
| 2570 | (add-to-list 'completion-category-overrides | ||
| 2571 | '(eglot-indirection-joy (styles . (eglot--lsp-backend-style)))) | ||
| 2572 | |||
| 2573 | (cl-defmethod xref-backend-identifier-at-point ((_backend (eql eglot))) | ||
| 2574 | (let ((attempt | ||
| 2575 | (and (xref--prompt-p this-command) | ||
| 2576 | (puthash :default | ||
| 2577 | (ignore-errors | ||
| 2578 | (eglot--workspace-symbols (symbol-name (symbol-at-point)))) | ||
| 2579 | eglot--workspace-symbols-cache)))) | ||
| 2580 | (if attempt (car attempt) "LSP identifier at point"))) | ||
| 2581 | |||
| 2582 | (defvar eglot--lsp-xref-refs nil | ||
| 2583 | "`xref' objects for overriding `xref-backend-references''s.") | ||
| 2584 | |||
| 2585 | (cl-defun eglot--lsp-xrefs-for-method (method &key extra-params capability) | ||
| 2586 | "Make `xref''s for METHOD, EXTRA-PARAMS, check CAPABILITY." | ||
| 2587 | (unless (eglot--server-capable | ||
| 2588 | (or capability | ||
| 2589 | (intern | ||
| 2590 | (format ":%sProvider" | ||
| 2591 | (cadr (split-string (symbol-name method) | ||
| 2592 | "/")))))) | ||
| 2593 | (eglot--error "Sorry, this server doesn't do %s" method)) | ||
| 2594 | (let ((response | ||
| 2595 | (jsonrpc-request | ||
| 2596 | (eglot--current-server-or-lose) | ||
| 2597 | method (append (eglot--TextDocumentPositionParams) extra-params)))) | ||
| 2598 | (eglot--collecting-xrefs (collect) | ||
| 2599 | (mapc | ||
| 2600 | (lambda (loc-or-loc-link) | ||
| 2601 | (let ((sym-name (symbol-name (symbol-at-point)))) | ||
| 2602 | (eglot--dcase loc-or-loc-link | ||
| 2603 | (((LocationLink) targetUri targetSelectionRange) | ||
| 2604 | (collect (eglot--xref-make-match sym-name | ||
| 2605 | targetUri targetSelectionRange))) | ||
| 2606 | (((Location) uri range) | ||
| 2607 | (collect (eglot--xref-make-match sym-name | ||
| 2608 | uri range)))))) | ||
| 2609 | (if (vectorp response) response (and response (list response))))))) | ||
| 2610 | |||
| 2611 | (cl-defun eglot--lsp-xref-helper (method &key extra-params capability ) | ||
| 2612 | "Helper for `eglot-find-declaration' & friends." | ||
| 2613 | (let ((eglot--lsp-xref-refs (eglot--lsp-xrefs-for-method | ||
| 2614 | method | ||
| 2615 | :extra-params extra-params | ||
| 2616 | :capability capability))) | ||
| 2617 | (if eglot--lsp-xref-refs | ||
| 2618 | (xref-find-references "LSP identifier at point.") | ||
| 2619 | (eglot--message "%s returned no references" method)))) | ||
| 2620 | |||
| 2621 | (defun eglot-find-declaration () | ||
| 2622 | "Find declaration for SYM, the identifier at point." | ||
| 2623 | (interactive) | ||
| 2624 | (eglot--lsp-xref-helper :textDocument/declaration)) | ||
| 2625 | |||
| 2626 | (defun eglot-find-implementation () | ||
| 2627 | "Find implementation for SYM, the identifier at point." | ||
| 2628 | (interactive) | ||
| 2629 | (eglot--lsp-xref-helper :textDocument/implementation)) | ||
| 2630 | |||
| 2631 | (defun eglot-find-typeDefinition () | ||
| 2632 | "Find type definition for SYM, the identifier at point." | ||
| 2633 | (interactive) | ||
| 2634 | (eglot--lsp-xref-helper :textDocument/typeDefinition)) | ||
| 2635 | |||
| 2636 | (cl-defmethod xref-backend-definitions ((_backend (eql eglot)) id) | ||
| 2637 | (let ((probe (eglot--recover-workspace-symbol-meta id))) | ||
| 2638 | (if probe | ||
| 2639 | (eglot--dbind ((WorkspaceSymbol) name location) | ||
| 2640 | (get-text-property 0 'eglot--lsp-workspaceSymbol probe) | ||
| 2641 | (eglot--dbind ((Location) uri range) location | ||
| 2642 | (list (eglot--xref-make-match name uri range)))) | ||
| 2643 | (eglot--lsp-xrefs-for-method :textDocument/definition)))) | ||
| 2644 | |||
| 2645 | (cl-defmethod xref-backend-references ((_backend (eql eglot)) _identifier) | ||
| 2646 | (or | ||
| 2647 | eglot--lsp-xref-refs | ||
| 2648 | (eglot--lsp-xrefs-for-method | ||
| 2649 | :textDocument/references :extra-params `(:context (:includeDeclaration t))))) | ||
| 2650 | |||
| 2651 | (cl-defmethod xref-backend-apropos ((_backend (eql eglot)) pattern) | ||
| 2652 | (when (eglot--server-capable :workspaceSymbolProvider) | ||
| 2653 | (eglot--collecting-xrefs (collect) | ||
| 2654 | (mapc | ||
| 2655 | (eglot--lambda ((SymbolInformation) name location) | ||
| 2656 | (eglot--dbind ((Location) uri range) location | ||
| 2657 | (collect (eglot--xref-make-match name uri range)))) | ||
| 2658 | (jsonrpc-request (eglot--current-server-or-lose) | ||
| 2659 | :workspace/symbol | ||
| 2660 | `(:query ,pattern)))))) | ||
| 2661 | |||
| 2662 | (defun eglot-format-buffer () | ||
| 2663 | "Format contents of current buffer." | ||
| 2664 | (interactive) | ||
| 2665 | (eglot-format nil nil)) | ||
| 2666 | |||
| 2667 | (defun eglot-format (&optional beg end on-type-format) | ||
| 2668 | "Format region BEG END. | ||
| 2669 | If either BEG or END is nil, format entire buffer. | ||
| 2670 | Interactively, format active region, or entire buffer if region | ||
| 2671 | is not active. | ||
| 2672 | |||
| 2673 | If non-nil, ON-TYPE-FORMAT is a character just inserted at BEG | ||
| 2674 | for which LSP on-type-formatting should be requested." | ||
| 2675 | (interactive (and (region-active-p) (list (region-beginning) (region-end)))) | ||
| 2676 | (pcase-let ((`(,method ,cap ,args) | ||
| 2677 | (cond | ||
| 2678 | ((and beg on-type-format) | ||
| 2679 | `(:textDocument/onTypeFormatting | ||
| 2680 | :documentOnTypeFormattingProvider | ||
| 2681 | ,`(:position ,(eglot--pos-to-lsp-position beg) | ||
| 2682 | :ch ,(string on-type-format)))) | ||
| 2683 | ((and beg end) | ||
| 2684 | `(:textDocument/rangeFormatting | ||
| 2685 | :documentRangeFormattingProvider | ||
| 2686 | (:range ,(list :start (eglot--pos-to-lsp-position beg) | ||
| 2687 | :end (eglot--pos-to-lsp-position end))))) | ||
| 2688 | (t | ||
| 2689 | '(:textDocument/formatting :documentFormattingProvider nil))))) | ||
| 2690 | (unless (eglot--server-capable cap) | ||
| 2691 | (eglot--error "Server can't format!")) | ||
| 2692 | (eglot--apply-text-edits | ||
| 2693 | (jsonrpc-request | ||
| 2694 | (eglot--current-server-or-lose) | ||
| 2695 | method | ||
| 2696 | (cl-list* | ||
| 2697 | :textDocument (eglot--TextDocumentIdentifier) | ||
| 2698 | :options (list :tabSize tab-width | ||
| 2699 | :insertSpaces (if indent-tabs-mode :json-false t) | ||
| 2700 | :insertFinalNewline (if require-final-newline t :json-false) | ||
| 2701 | :trimFinalNewlines (if delete-trailing-lines t :json-false)) | ||
| 2702 | args) | ||
| 2703 | :deferred method)))) | ||
| 2704 | |||
| 2705 | (defun eglot-completion-at-point () | ||
| 2706 | "Eglot's `completion-at-point' function." | ||
| 2707 | ;; Commit logs for this function help understand what's going on. | ||
| 2708 | (when-let (completion-capability (eglot--server-capable :completionProvider)) | ||
| 2709 | (let* ((server (eglot--current-server-or-lose)) | ||
| 2710 | (sort-completions | ||
| 2711 | (lambda (completions) | ||
| 2712 | (cl-sort completions | ||
| 2713 | #'string-lessp | ||
| 2714 | :key (lambda (c) | ||
| 2715 | (or (plist-get | ||
| 2716 | (get-text-property 0 'eglot--lsp-item c) | ||
| 2717 | :sortText) | ||
| 2718 | ""))))) | ||
| 2719 | (metadata `(metadata (category . eglot) | ||
| 2720 | (display-sort-function . ,sort-completions))) | ||
| 2721 | resp items (cached-proxies :none) | ||
| 2722 | (proxies | ||
| 2723 | (lambda () | ||
| 2724 | (if (listp cached-proxies) cached-proxies | ||
| 2725 | (setq resp | ||
| 2726 | (jsonrpc-request server | ||
| 2727 | :textDocument/completion | ||
| 2728 | (eglot--CompletionParams) | ||
| 2729 | :deferred :textDocument/completion | ||
| 2730 | :cancel-on-input t)) | ||
| 2731 | (setq items (append | ||
| 2732 | (if (vectorp resp) resp (plist-get resp :items)) | ||
| 2733 | nil)) | ||
| 2734 | (setq cached-proxies | ||
| 2735 | (mapcar | ||
| 2736 | (jsonrpc-lambda | ||
| 2737 | (&rest item &key label insertText insertTextFormat | ||
| 2738 | &allow-other-keys) | ||
| 2739 | (let ((proxy | ||
| 2740 | (cond ((and (eql insertTextFormat 2) | ||
| 2741 | (eglot--snippet-expansion-fn)) | ||
| 2742 | (string-trim-left label)) | ||
| 2743 | ((and insertText | ||
| 2744 | (not (string-empty-p insertText))) | ||
| 2745 | insertText) | ||
| 2746 | (t | ||
| 2747 | (string-trim-left label))))) | ||
| 2748 | (unless (zerop (length proxy)) | ||
| 2749 | (put-text-property 0 1 'eglot--lsp-item item proxy)) | ||
| 2750 | proxy)) | ||
| 2751 | items))))) | ||
| 2752 | (resolved (make-hash-table)) | ||
| 2753 | (resolve-maybe | ||
| 2754 | ;; Maybe completion/resolve JSON object `lsp-comp' into | ||
| 2755 | ;; another JSON object, if at all possible. Otherwise, | ||
| 2756 | ;; just return lsp-comp. | ||
| 2757 | (lambda (lsp-comp) | ||
| 2758 | (or (gethash lsp-comp resolved) | ||
| 2759 | (setf (gethash lsp-comp resolved) | ||
| 2760 | (if (and (eglot--server-capable :completionProvider | ||
| 2761 | :resolveProvider) | ||
| 2762 | (plist-get lsp-comp :data)) | ||
| 2763 | (jsonrpc-request server :completionItem/resolve | ||
| 2764 | lsp-comp :cancel-on-input t) | ||
| 2765 | lsp-comp))))) | ||
| 2766 | (bounds (bounds-of-thing-at-point 'symbol))) | ||
| 2767 | (list | ||
| 2768 | (or (car bounds) (point)) | ||
| 2769 | (or (cdr bounds) (point)) | ||
| 2770 | (lambda (probe pred action) | ||
| 2771 | (cond | ||
| 2772 | ((eq action 'metadata) metadata) ; metadata | ||
| 2773 | ((eq action 'lambda) ; test-completion | ||
| 2774 | (test-completion probe (funcall proxies))) | ||
| 2775 | ((eq (car-safe action) 'boundaries) nil) ; boundaries | ||
| 2776 | ((null action) ; try-completion | ||
| 2777 | (try-completion probe (funcall proxies))) | ||
| 2778 | ((eq action t) ; all-completions | ||
| 2779 | (all-completions | ||
| 2780 | "" | ||
| 2781 | (funcall proxies) | ||
| 2782 | (lambda (proxy) | ||
| 2783 | (let* ((item (get-text-property 0 'eglot--lsp-item proxy)) | ||
| 2784 | (filterText (plist-get item :filterText))) | ||
| 2785 | (and (or (null pred) (funcall pred proxy)) | ||
| 2786 | (string-prefix-p | ||
| 2787 | probe (or filterText proxy) completion-ignore-case)))))))) | ||
| 2788 | :annotation-function | ||
| 2789 | (lambda (proxy) | ||
| 2790 | (eglot--dbind ((CompletionItem) detail kind) | ||
| 2791 | (get-text-property 0 'eglot--lsp-item proxy) | ||
| 2792 | (let* ((detail (and (stringp detail) | ||
| 2793 | (not (string= detail "")) | ||
| 2794 | detail)) | ||
| 2795 | (annotation | ||
| 2796 | (or detail | ||
| 2797 | (cdr (assoc kind eglot--kind-names))))) | ||
| 2798 | (when annotation | ||
| 2799 | (concat " " | ||
| 2800 | (propertize annotation | ||
| 2801 | 'face 'font-lock-function-name-face)))))) | ||
| 2802 | :company-kind | ||
| 2803 | ;; Associate each lsp-item with a lsp-kind symbol. | ||
| 2804 | (lambda (proxy) | ||
| 2805 | (when-let* ((lsp-item (get-text-property 0 'eglot--lsp-item proxy)) | ||
| 2806 | (kind (alist-get (plist-get lsp-item :kind) | ||
| 2807 | eglot--kind-names))) | ||
| 2808 | (intern (downcase kind)))) | ||
| 2809 | :company-deprecated | ||
| 2810 | (lambda (proxy) | ||
| 2811 | (when-let ((lsp-item (get-text-property 0 'eglot--lsp-item proxy))) | ||
| 2812 | (or (seq-contains-p (plist-get lsp-item :tags) | ||
| 2813 | 1) | ||
| 2814 | (eq t (plist-get lsp-item :deprecated))))) | ||
| 2815 | :company-docsig | ||
| 2816 | ;; FIXME: autoImportText is specific to the pyright language server | ||
| 2817 | (lambda (proxy) | ||
| 2818 | (when-let* ((lsp-comp (get-text-property 0 'eglot--lsp-item proxy)) | ||
| 2819 | (data (plist-get (funcall resolve-maybe lsp-comp) :data)) | ||
| 2820 | (import-text (plist-get data :autoImportText))) | ||
| 2821 | import-text)) | ||
| 2822 | :company-doc-buffer | ||
| 2823 | (lambda (proxy) | ||
| 2824 | (let* ((documentation | ||
| 2825 | (let ((lsp-comp (get-text-property 0 'eglot--lsp-item proxy))) | ||
| 2826 | (plist-get (funcall resolve-maybe lsp-comp) :documentation))) | ||
| 2827 | (formatted (and documentation | ||
| 2828 | (eglot--format-markup documentation)))) | ||
| 2829 | (when formatted | ||
| 2830 | (with-current-buffer (get-buffer-create " *eglot doc*") | ||
| 2831 | (erase-buffer) | ||
| 2832 | (insert formatted) | ||
| 2833 | (current-buffer))))) | ||
| 2834 | :company-require-match 'never | ||
| 2835 | :company-prefix-length | ||
| 2836 | (save-excursion | ||
| 2837 | (when (car bounds) (goto-char (car bounds))) | ||
| 2838 | (when (listp completion-capability) | ||
| 2839 | (looking-back | ||
| 2840 | (regexp-opt | ||
| 2841 | (cl-coerce (cl-getf completion-capability :triggerCharacters) 'list)) | ||
| 2842 | (line-beginning-position)))) | ||
| 2843 | :exit-function | ||
| 2844 | (lambda (proxy status) | ||
| 2845 | (when (memq status '(finished exact)) | ||
| 2846 | ;; To assist in using this whole `completion-at-point' | ||
| 2847 | ;; function inside `completion-in-region', ensure the exit | ||
| 2848 | ;; function runs in the buffer where the completion was | ||
| 2849 | ;; triggered from. This should probably be in Emacs itself. | ||
| 2850 | ;; (github#505) | ||
| 2851 | (with-current-buffer (if (minibufferp) | ||
| 2852 | (window-buffer (minibuffer-selected-window)) | ||
| 2853 | (current-buffer)) | ||
| 2854 | (eglot--dbind ((CompletionItem) insertTextFormat | ||
| 2855 | insertText textEdit additionalTextEdits label) | ||
| 2856 | (funcall | ||
| 2857 | resolve-maybe | ||
| 2858 | (or (get-text-property 0 'eglot--lsp-item proxy) | ||
| 2859 | ;; When selecting from the *Completions* | ||
| 2860 | ;; buffer, `proxy' won't have any properties. | ||
| 2861 | ;; A lookup should fix that (github#148) | ||
| 2862 | (get-text-property | ||
| 2863 | 0 'eglot--lsp-item | ||
| 2864 | (cl-find proxy (funcall proxies) :test #'string=)))) | ||
| 2865 | (let ((snippet-fn (and (eql insertTextFormat 2) | ||
| 2866 | (eglot--snippet-expansion-fn)))) | ||
| 2867 | (cond (textEdit | ||
| 2868 | ;; Undo (yes, undo) the newly inserted completion. | ||
| 2869 | ;; If before completion the buffer was "foo.b" and | ||
| 2870 | ;; now is "foo.bar", `proxy' will be "bar". We | ||
| 2871 | ;; want to delete only "ar" (`proxy' minus the | ||
| 2872 | ;; symbol whose bounds we've calculated before) | ||
| 2873 | ;; (github#160). | ||
| 2874 | (delete-region (+ (- (point) (length proxy)) | ||
| 2875 | (if bounds | ||
| 2876 | (- (cdr bounds) (car bounds)) | ||
| 2877 | 0)) | ||
| 2878 | (point)) | ||
| 2879 | (eglot--dbind ((TextEdit) range newText) textEdit | ||
| 2880 | (pcase-let ((`(,beg . ,end) | ||
| 2881 | (eglot--range-region range))) | ||
| 2882 | (delete-region beg end) | ||
| 2883 | (goto-char beg) | ||
| 2884 | (funcall (or snippet-fn #'insert) newText)))) | ||
| 2885 | (snippet-fn | ||
| 2886 | ;; A snippet should be inserted, but using plain | ||
| 2887 | ;; `insertText'. This requires us to delete the | ||
| 2888 | ;; whole completion, since `insertText' is the full | ||
| 2889 | ;; completion's text. | ||
| 2890 | (delete-region (- (point) (length proxy)) (point)) | ||
| 2891 | (funcall snippet-fn (or insertText label)))) | ||
| 2892 | (when (cl-plusp (length additionalTextEdits)) | ||
| 2893 | (eglot--apply-text-edits additionalTextEdits))) | ||
| 2894 | (eglot--signal-textDocument/didChange) | ||
| 2895 | (eldoc))))))))) | ||
| 2896 | |||
| 2897 | (defun eglot--hover-info (contents &optional _range) | ||
| 2898 | (mapconcat #'eglot--format-markup | ||
| 2899 | (if (vectorp contents) contents (list contents)) "\n")) | ||
| 2900 | |||
| 2901 | (defun eglot--sig-info (sigs active-sig sig-help-active-param) | ||
| 2902 | (cl-loop | ||
| 2903 | for (sig . moresigs) on (append sigs nil) for i from 0 | ||
| 2904 | concat | ||
| 2905 | (eglot--dbind ((SignatureInformation) label documentation parameters activeParameter) sig | ||
| 2906 | (with-temp-buffer | ||
| 2907 | (save-excursion (insert label)) | ||
| 2908 | (let ((active-param (or activeParameter sig-help-active-param)) | ||
| 2909 | params-start params-end) | ||
| 2910 | ;; Ad-hoc attempt to parse label as <name>(<params>) | ||
| 2911 | (when (looking-at "\\([^(]+\\)(\\([^)]+\\))") | ||
| 2912 | (setq params-start (match-beginning 2) params-end (match-end 2)) | ||
| 2913 | (add-face-text-property (match-beginning 1) (match-end 1) | ||
| 2914 | 'font-lock-function-name-face)) | ||
| 2915 | (when (eql i active-sig) | ||
| 2916 | ;; Decide whether to add one-line-summary to signature line | ||
| 2917 | (when (and (stringp documentation) | ||
| 2918 | (string-match "[[:space:]]*\\([^.\r\n]+[.]?\\)" | ||
| 2919 | documentation)) | ||
| 2920 | (setq documentation (match-string 1 documentation)) | ||
| 2921 | (unless (string-prefix-p (string-trim documentation) label) | ||
| 2922 | (goto-char (point-max)) | ||
| 2923 | (insert ": " (eglot--format-markup documentation)))) | ||
| 2924 | ;; Decide what to do with the active parameter... | ||
| 2925 | (when (and (eql i active-sig) active-param | ||
| 2926 | (< -1 active-param (length parameters))) | ||
| 2927 | (eglot--dbind ((ParameterInformation) label documentation) | ||
| 2928 | (aref parameters active-param) | ||
| 2929 | ;; ...perhaps highlight it in the formals list | ||
| 2930 | (when params-start | ||
| 2931 | (goto-char params-start) | ||
| 2932 | (pcase-let | ||
| 2933 | ((`(,beg ,end) | ||
| 2934 | (if (stringp label) | ||
| 2935 | (let ((case-fold-search nil)) | ||
| 2936 | (and (re-search-forward | ||
| 2937 | (concat "\\<" (regexp-quote label) "\\>") | ||
| 2938 | params-end t) | ||
| 2939 | (list (match-beginning 0) (match-end 0)))) | ||
| 2940 | (mapcar #'1+ (append label nil))))) | ||
| 2941 | (if (and beg end) | ||
| 2942 | (add-face-text-property | ||
| 2943 | beg end | ||
| 2944 | 'eldoc-highlight-function-argument)))) | ||
| 2945 | ;; ...and/or maybe add its doc on a line by its own. | ||
| 2946 | (when documentation | ||
| 2947 | (goto-char (point-max)) | ||
| 2948 | (insert "\n" | ||
| 2949 | (propertize | ||
| 2950 | (if (stringp label) | ||
| 2951 | label | ||
| 2952 | (apply #'buffer-substring (mapcar #'1+ label))) | ||
| 2953 | 'face 'eldoc-highlight-function-argument) | ||
| 2954 | ": " (eglot--format-markup documentation)))))) | ||
| 2955 | (buffer-string)))) | ||
| 2956 | when moresigs concat "\n")) | ||
| 2957 | |||
| 2958 | (defun eglot-signature-eldoc-function (cb) | ||
| 2959 | "A member of `eldoc-documentation-functions', for signatures." | ||
| 2960 | (when (eglot--server-capable :signatureHelpProvider) | ||
| 2961 | (let ((buf (current-buffer))) | ||
| 2962 | (jsonrpc-async-request | ||
| 2963 | (eglot--current-server-or-lose) | ||
| 2964 | :textDocument/signatureHelp (eglot--TextDocumentPositionParams) | ||
| 2965 | :success-fn | ||
| 2966 | (eglot--lambda ((SignatureHelp) | ||
| 2967 | signatures activeSignature activeParameter) | ||
| 2968 | (eglot--when-buffer-window buf | ||
| 2969 | (funcall cb | ||
| 2970 | (unless (seq-empty-p signatures) | ||
| 2971 | (eglot--sig-info signatures | ||
| 2972 | activeSignature | ||
| 2973 | activeParameter))))) | ||
| 2974 | :deferred :textDocument/signatureHelp)) | ||
| 2975 | t)) | ||
| 2976 | |||
| 2977 | (defun eglot-hover-eldoc-function (cb) | ||
| 2978 | "A member of `eldoc-documentation-functions', for hover." | ||
| 2979 | (when (eglot--server-capable :hoverProvider) | ||
| 2980 | (let ((buf (current-buffer))) | ||
| 2981 | (jsonrpc-async-request | ||
| 2982 | (eglot--current-server-or-lose) | ||
| 2983 | :textDocument/hover (eglot--TextDocumentPositionParams) | ||
| 2984 | :success-fn (eglot--lambda ((Hover) contents range) | ||
| 2985 | (eglot--when-buffer-window buf | ||
| 2986 | (let ((info (unless (seq-empty-p contents) | ||
| 2987 | (eglot--hover-info contents range)))) | ||
| 2988 | (funcall cb info :buffer t)))) | ||
| 2989 | :deferred :textDocument/hover)) | ||
| 2990 | (eglot--highlight-piggyback cb) | ||
| 2991 | t)) | ||
| 2992 | |||
| 2993 | (defvar eglot--highlights nil "Overlays for textDocument/documentHighlight.") | ||
| 2994 | |||
| 2995 | (defun eglot--highlight-piggyback (_cb) | ||
| 2996 | "Request and handle `:textDocument/documentHighlight'." | ||
| 2997 | ;; FIXME: Obviously, this is just piggy backing on eldoc's calls for | ||
| 2998 | ;; convenience, as shown by the fact that we just ignore cb. | ||
| 2999 | (let ((buf (current-buffer))) | ||
| 3000 | (when (eglot--server-capable :documentHighlightProvider) | ||
| 3001 | (jsonrpc-async-request | ||
| 3002 | (eglot--current-server-or-lose) | ||
| 3003 | :textDocument/documentHighlight (eglot--TextDocumentPositionParams) | ||
| 3004 | :success-fn | ||
| 3005 | (lambda (highlights) | ||
| 3006 | (mapc #'delete-overlay eglot--highlights) | ||
| 3007 | (setq eglot--highlights | ||
| 3008 | (eglot--when-buffer-window buf | ||
| 3009 | (mapcar | ||
| 3010 | (eglot--lambda ((DocumentHighlight) range) | ||
| 3011 | (pcase-let ((`(,beg . ,end) | ||
| 3012 | (eglot--range-region range))) | ||
| 3013 | (let ((ov (make-overlay beg end))) | ||
| 3014 | (overlay-put ov 'face 'eglot-highlight-symbol-face) | ||
| 3015 | (overlay-put ov 'modification-hooks | ||
| 3016 | `(,(lambda (o &rest _) (delete-overlay o)))) | ||
| 3017 | ov))) | ||
| 3018 | highlights)))) | ||
| 3019 | :deferred :textDocument/documentHighlight) | ||
| 3020 | nil))) | ||
| 3021 | |||
| 3022 | (defun eglot-imenu () | ||
| 3023 | "Eglot's `imenu-create-index-function'. | ||
| 3024 | Returns a list as described in docstring of `imenu--index-alist'." | ||
| 3025 | (cl-labels | ||
| 3026 | ((unfurl (obj) | ||
| 3027 | (eglot--dcase obj | ||
| 3028 | (((SymbolInformation)) (list obj)) | ||
| 3029 | (((DocumentSymbol) name children) | ||
| 3030 | (cons obj | ||
| 3031 | (mapcar | ||
| 3032 | (lambda (c) | ||
| 3033 | (plist-put | ||
| 3034 | c :containerName | ||
| 3035 | (let ((existing (plist-get c :containerName))) | ||
| 3036 | (if existing (format "%s::%s" name existing) | ||
| 3037 | name)))) | ||
| 3038 | (mapcan #'unfurl children))))))) | ||
| 3039 | (mapcar | ||
| 3040 | (pcase-lambda (`(,kind . ,objs)) | ||
| 3041 | (cons | ||
| 3042 | (alist-get kind eglot--symbol-kind-names "Unknown") | ||
| 3043 | (mapcan (pcase-lambda (`(,container . ,objs)) | ||
| 3044 | (let ((elems (mapcar | ||
| 3045 | (lambda (obj) | ||
| 3046 | (cons (plist-get obj :name) | ||
| 3047 | (car (eglot--range-region | ||
| 3048 | (eglot--dcase obj | ||
| 3049 | (((SymbolInformation) location) | ||
| 3050 | (plist-get location :range)) | ||
| 3051 | (((DocumentSymbol) selectionRange) | ||
| 3052 | selectionRange)))))) | ||
| 3053 | objs))) | ||
| 3054 | (if container (list (cons container elems)) elems))) | ||
| 3055 | (seq-group-by | ||
| 3056 | (lambda (e) (plist-get e :containerName)) objs)))) | ||
| 3057 | (seq-group-by | ||
| 3058 | (lambda (obj) (plist-get obj :kind)) | ||
| 3059 | (mapcan #'unfurl | ||
| 3060 | (jsonrpc-request (eglot--current-server-or-lose) | ||
| 3061 | :textDocument/documentSymbol | ||
| 3062 | `(:textDocument | ||
| 3063 | ,(eglot--TextDocumentIdentifier)) | ||
| 3064 | :cancel-on-input non-essential)))))) | ||
| 3065 | |||
| 3066 | (defun eglot--apply-text-edits (edits &optional version) | ||
| 3067 | "Apply EDITS for current buffer if at VERSION, or if it's nil." | ||
| 3068 | (unless (or (not version) (equal version eglot--versioned-identifier)) | ||
| 3069 | (jsonrpc-error "Edits on `%s' require version %d, you have %d" | ||
| 3070 | (current-buffer) version eglot--versioned-identifier)) | ||
| 3071 | (atomic-change-group | ||
| 3072 | (let* ((change-group (prepare-change-group)) | ||
| 3073 | (howmany (length edits)) | ||
| 3074 | (reporter (make-progress-reporter | ||
| 3075 | (format "[eglot] applying %s edits to `%s'..." | ||
| 3076 | howmany (current-buffer)) | ||
| 3077 | 0 howmany)) | ||
| 3078 | (done 0)) | ||
| 3079 | (mapc (pcase-lambda (`(,newText ,beg . ,end)) | ||
| 3080 | (let ((source (current-buffer))) | ||
| 3081 | (with-temp-buffer | ||
| 3082 | (insert newText) | ||
| 3083 | (let ((temp (current-buffer))) | ||
| 3084 | (with-current-buffer source | ||
| 3085 | (save-excursion | ||
| 3086 | (save-restriction | ||
| 3087 | (narrow-to-region beg end) | ||
| 3088 | |||
| 3089 | ;; On emacs versions < 26.2, | ||
| 3090 | ;; `replace-buffer-contents' is buggy - it calls | ||
| 3091 | ;; change functions with invalid arguments - so we | ||
| 3092 | ;; manually call the change functions here. | ||
| 3093 | ;; | ||
| 3094 | ;; See emacs bugs #32237, #32278: | ||
| 3095 | ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32237 | ||
| 3096 | ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=32278 | ||
| 3097 | (let ((inhibit-modification-hooks t) | ||
| 3098 | (length (- end beg)) | ||
| 3099 | (beg (marker-position beg)) | ||
| 3100 | (end (marker-position end))) | ||
| 3101 | (run-hook-with-args 'before-change-functions | ||
| 3102 | beg end) | ||
| 3103 | (replace-buffer-contents temp) | ||
| 3104 | (run-hook-with-args 'after-change-functions | ||
| 3105 | beg (+ beg (length newText)) | ||
| 3106 | length)))) | ||
| 3107 | (progress-reporter-update reporter (cl-incf done))))))) | ||
| 3108 | (mapcar (eglot--lambda ((TextEdit) range newText) | ||
| 3109 | (cons newText (eglot--range-region range 'markers))) | ||
| 3110 | (reverse edits))) | ||
| 3111 | (undo-amalgamate-change-group change-group) | ||
| 3112 | (progress-reporter-done reporter)))) | ||
| 3113 | |||
| 3114 | (defun eglot--apply-workspace-edit (wedit &optional confirm) | ||
| 3115 | "Apply the workspace edit WEDIT. If CONFIRM, ask user first." | ||
| 3116 | (eglot--dbind ((WorkspaceEdit) changes documentChanges) wedit | ||
| 3117 | (let ((prepared | ||
| 3118 | (mapcar (eglot--lambda ((TextDocumentEdit) textDocument edits) | ||
| 3119 | (eglot--dbind ((VersionedTextDocumentIdentifier) uri version) | ||
| 3120 | textDocument | ||
| 3121 | (list (eglot--uri-to-path uri) edits version))) | ||
| 3122 | documentChanges))) | ||
| 3123 | (unless (and changes documentChanges) | ||
| 3124 | ;; We don't want double edits, and some servers send both | ||
| 3125 | ;; changes and documentChanges. This unless ensures that we | ||
| 3126 | ;; prefer documentChanges over changes. | ||
| 3127 | (cl-loop for (uri edits) on changes by #'cddr | ||
| 3128 | do (push (list (eglot--uri-to-path uri) edits) prepared))) | ||
| 3129 | (if (or confirm | ||
| 3130 | (cl-notevery #'find-buffer-visiting | ||
| 3131 | (mapcar #'car prepared))) | ||
| 3132 | (unless (y-or-n-p | ||
| 3133 | (format "[eglot] Server wants to edit:\n %s\n Proceed? " | ||
| 3134 | (mapconcat #'identity (mapcar #'car prepared) "\n "))) | ||
| 3135 | (jsonrpc-error "User cancelled server edit"))) | ||
| 3136 | (cl-loop for edit in prepared | ||
| 3137 | for (path edits version) = edit | ||
| 3138 | do (with-current-buffer (find-file-noselect path) | ||
| 3139 | (eglot--apply-text-edits edits version)) | ||
| 3140 | finally (eldoc) (eglot--message "Edit successful!"))))) | ||
| 3141 | |||
| 3142 | (defun eglot-rename (newname) | ||
| 3143 | "Rename the current symbol to NEWNAME." | ||
| 3144 | (interactive | ||
| 3145 | (list (read-from-minibuffer | ||
| 3146 | (format "Rename `%s' to: " (or (thing-at-point 'symbol t) | ||
| 3147 | "unknown symbol")) | ||
| 3148 | nil nil nil nil | ||
| 3149 | (symbol-name (symbol-at-point))))) | ||
| 3150 | (unless (eglot--server-capable :renameProvider) | ||
| 3151 | (eglot--error "Server can't rename!")) | ||
| 3152 | (eglot--apply-workspace-edit | ||
| 3153 | (jsonrpc-request (eglot--current-server-or-lose) | ||
| 3154 | :textDocument/rename `(,@(eglot--TextDocumentPositionParams) | ||
| 3155 | :newName ,newname)) | ||
| 3156 | current-prefix-arg)) | ||
| 3157 | |||
| 3158 | (defun eglot--region-bounds () | ||
| 3159 | "Region bounds if active, else bounds of things at point." | ||
| 3160 | (if (use-region-p) `(,(region-beginning) ,(region-end)) | ||
| 3161 | (let ((boftap (bounds-of-thing-at-point 'sexp))) | ||
| 3162 | (list (car boftap) (cdr boftap))))) | ||
| 3163 | |||
| 3164 | (defun eglot-code-actions (beg &optional end action-kind interactive) | ||
| 3165 | "Find LSP code actions of type ACTION-KIND between BEG and END. | ||
| 3166 | Interactively, offer to execute them. | ||
| 3167 | If ACTION-KIND is nil, consider all kinds of actions. | ||
| 3168 | Interactively, default BEG and END to region's bounds else BEG is | ||
| 3169 | point and END is nil, which results in a request for code actions | ||
| 3170 | at point. With prefix argument, prompt for ACTION-KIND." | ||
| 3171 | (interactive | ||
| 3172 | `(,@(eglot--region-bounds) | ||
| 3173 | ,(and current-prefix-arg | ||
| 3174 | (completing-read "[eglot] Action kind: " | ||
| 3175 | '("quickfix" "refactor.extract" "refactor.inline" | ||
| 3176 | "refactor.rewrite" "source.organizeImports"))) | ||
| 3177 | t)) | ||
| 3178 | (unless (or (not interactive) | ||
| 3179 | (eglot--server-capable :codeActionProvider)) | ||
| 3180 | (eglot--error "Server can't execute code actions!")) | ||
| 3181 | (let* ((server (eglot--current-server-or-lose)) | ||
| 3182 | (actions | ||
| 3183 | (jsonrpc-request | ||
| 3184 | server | ||
| 3185 | :textDocument/codeAction | ||
| 3186 | (list :textDocument (eglot--TextDocumentIdentifier) | ||
| 3187 | :range (list :start (eglot--pos-to-lsp-position beg) | ||
| 3188 | :end (eglot--pos-to-lsp-position end)) | ||
| 3189 | :context | ||
| 3190 | `(:diagnostics | ||
| 3191 | [,@(cl-loop for diag in (flymake-diagnostics beg end) | ||
| 3192 | when (cdr (assoc 'eglot-lsp-diag | ||
| 3193 | (eglot--diag-data diag))) | ||
| 3194 | collect it)] | ||
| 3195 | ,@(when action-kind `(:only [,action-kind])))) | ||
| 3196 | :deferred t)) | ||
| 3197 | ;; Redo filtering, in case the `:only' didn't go through. | ||
| 3198 | (actions (cl-loop for a across actions | ||
| 3199 | when (or (not action-kind) | ||
| 3200 | (equal action-kind (plist-get a :kind))) | ||
| 3201 | collect a))) | ||
| 3202 | (if interactive | ||
| 3203 | (eglot--read-execute-code-action actions server action-kind) | ||
| 3204 | actions))) | ||
| 3205 | |||
| 3206 | (defun eglot--read-execute-code-action (actions server &optional action-kind) | ||
| 3207 | "Helper for interactive calls to `eglot-code-actions'" | ||
| 3208 | (let* ((menu-items | ||
| 3209 | (or (cl-loop for a in actions | ||
| 3210 | collect (cons (plist-get a :title) a)) | ||
| 3211 | (apply #'eglot--error | ||
| 3212 | (if action-kind `("No \"%s\" code actions here" ,action-kind) | ||
| 3213 | `("No code actions here"))))) | ||
| 3214 | (preferred-action (cl-find-if | ||
| 3215 | (lambda (menu-item) | ||
| 3216 | (plist-get (cdr menu-item) :isPreferred)) | ||
| 3217 | menu-items)) | ||
| 3218 | (default-action (car (or preferred-action (car menu-items)))) | ||
| 3219 | (chosen (if (and action-kind (null (cadr menu-items))) | ||
| 3220 | (cdr (car menu-items)) | ||
| 3221 | (if (listp last-nonmenu-event) | ||
| 3222 | (x-popup-menu last-nonmenu-event `("Eglot code actions:" | ||
| 3223 | ("dummy" ,@menu-items))) | ||
| 3224 | (cdr (assoc (completing-read | ||
| 3225 | (format "[eglot] Pick an action (default %s): " | ||
| 3226 | default-action) | ||
| 3227 | menu-items nil t nil nil default-action) | ||
| 3228 | menu-items)))))) | ||
| 3229 | (eglot--dcase chosen | ||
| 3230 | (((Command) command arguments) | ||
| 3231 | (eglot-execute-command server (intern command) arguments)) | ||
| 3232 | (((CodeAction) edit command) | ||
| 3233 | (when edit (eglot--apply-workspace-edit edit)) | ||
| 3234 | (when command | ||
| 3235 | (eglot--dbind ((Command) command arguments) command | ||
| 3236 | (eglot-execute-command server (intern command) arguments))))))) | ||
| 3237 | |||
| 3238 | (defmacro eglot--code-action (name kind) | ||
| 3239 | "Define NAME to execute KIND code action." | ||
| 3240 | `(defun ,name (beg &optional end) | ||
| 3241 | ,(format "Execute `%s' code actions between BEG and END." kind) | ||
| 3242 | (interactive (eglot--region-bounds)) | ||
| 3243 | (eglot-code-actions beg end ,kind))) | ||
| 3244 | |||
| 3245 | (eglot--code-action eglot-code-action-organize-imports "source.organizeImports") | ||
| 3246 | (eglot--code-action eglot-code-action-extract "refactor.extract") | ||
| 3247 | (eglot--code-action eglot-code-action-inline "refactor.inline") | ||
| 3248 | (eglot--code-action eglot-code-action-rewrite "refactor.rewrite") | ||
| 3249 | (eglot--code-action eglot-code-action-quickfix "quickfix") | ||
| 3250 | |||
| 3251 | |||
| 3252 | ;;; Dynamic registration | ||
| 3253 | ;;; | ||
| 3254 | (cl-defmethod eglot-register-capability | ||
| 3255 | (server (method (eql workspace/didChangeWatchedFiles)) id &key watchers) | ||
| 3256 | "Handle dynamic registration of workspace/didChangeWatchedFiles." | ||
| 3257 | (eglot-unregister-capability server method id) | ||
| 3258 | (let* (success | ||
| 3259 | (globs (mapcar | ||
| 3260 | (eglot--lambda ((FileSystemWatcher) globPattern) | ||
| 3261 | (eglot--glob-compile globPattern t t)) | ||
| 3262 | watchers)) | ||
| 3263 | (dirs-to-watch | ||
| 3264 | (delete-dups (mapcar #'file-name-directory | ||
| 3265 | (project-files | ||
| 3266 | (eglot--project server)))))) | ||
| 3267 | (cl-labels | ||
| 3268 | ((handle-event | ||
| 3269 | (event) | ||
| 3270 | (pcase-let ((`(,desc ,action ,file ,file1) event)) | ||
| 3271 | (cond | ||
| 3272 | ((and (memq action '(created changed deleted)) | ||
| 3273 | (cl-find file globs :test (lambda (f g) (funcall g f)))) | ||
| 3274 | (jsonrpc-notify | ||
| 3275 | server :workspace/didChangeWatchedFiles | ||
| 3276 | `(:changes ,(vector `(:uri ,(eglot--path-to-uri file) | ||
| 3277 | :type ,(cl-case action | ||
| 3278 | (created 1) | ||
| 3279 | (changed 2) | ||
| 3280 | (deleted 3))))))) | ||
| 3281 | ((eq action 'renamed) | ||
| 3282 | (handle-event `(,desc 'deleted ,file)) | ||
| 3283 | (handle-event `(,desc 'created ,file1))))))) | ||
| 3284 | (unwind-protect | ||
| 3285 | (progn | ||
| 3286 | (dolist (dir dirs-to-watch) | ||
| 3287 | (push (file-notify-add-watch dir '(change) #'handle-event) | ||
| 3288 | (gethash id (eglot--file-watches server)))) | ||
| 3289 | (setq | ||
| 3290 | success | ||
| 3291 | `(:message ,(format "OK, watching %s directories in %s watchers" | ||
| 3292 | (length dirs-to-watch) (length watchers))))) | ||
| 3293 | (unless success | ||
| 3294 | (eglot-unregister-capability server method id)))))) | ||
| 3295 | |||
| 3296 | (cl-defmethod eglot-unregister-capability | ||
| 3297 | (server (_method (eql workspace/didChangeWatchedFiles)) id) | ||
| 3298 | "Handle dynamic unregistration of workspace/didChangeWatchedFiles." | ||
| 3299 | (mapc #'file-notify-rm-watch (gethash id (eglot--file-watches server))) | ||
| 3300 | (remhash id (eglot--file-watches server)) | ||
| 3301 | (list t "OK")) | ||
| 3302 | |||
| 3303 | |||
| 3304 | ;;; Glob heroics | ||
| 3305 | ;;; | ||
| 3306 | (defun eglot--glob-parse (glob) | ||
| 3307 | "Compute list of (STATE-SYM EMITTER-FN PATTERN)." | ||
| 3308 | (with-temp-buffer | ||
| 3309 | (save-excursion (insert glob)) | ||
| 3310 | (cl-loop | ||
| 3311 | with grammar = '((:** "\\*\\*/?" eglot--glob-emit-**) | ||
| 3312 | (:* "\\*" eglot--glob-emit-*) | ||
| 3313 | (:? "\\?" eglot--glob-emit-?) | ||
| 3314 | (:{} "{[^][*{}]+}" eglot--glob-emit-{}) | ||
| 3315 | (:range "\\[\\^?[^][/,*{}]+\\]" eglot--glob-emit-range) | ||
| 3316 | (:literal "[^][,*?{}]+" eglot--glob-emit-self)) | ||
| 3317 | until (eobp) | ||
| 3318 | collect (cl-loop | ||
| 3319 | for (_token regexp emitter) in grammar | ||
| 3320 | thereis (and (re-search-forward (concat "\\=" regexp) nil t) | ||
| 3321 | (list (cl-gensym "state-") emitter (match-string 0))) | ||
| 3322 | finally (error "Glob '%s' invalid at %s" (buffer-string) (point)))))) | ||
| 3323 | |||
| 3324 | (defun eglot--glob-compile (glob &optional byte-compile noerror) | ||
| 3325 | "Convert GLOB into Elisp function. Maybe BYTE-COMPILE it. | ||
| 3326 | If NOERROR, return predicate, else erroring function." | ||
| 3327 | (let* ((states (eglot--glob-parse glob)) | ||
| 3328 | (body `(with-current-buffer (get-buffer-create " *eglot-glob-matcher*") | ||
| 3329 | (erase-buffer) | ||
| 3330 | (save-excursion (insert string)) | ||
| 3331 | (cl-labels ,(cl-loop for (this that) on states | ||
| 3332 | for (self emit text) = this | ||
| 3333 | for next = (or (car that) 'eobp) | ||
| 3334 | collect (funcall emit text self next)) | ||
| 3335 | (or (,(caar states)) | ||
| 3336 | (error "Glob done but more unmatched text: '%s'" | ||
| 3337 | (buffer-substring (point) (point-max))))))) | ||
| 3338 | (form `(lambda (string) ,(if noerror `(ignore-errors ,body) body)))) | ||
| 3339 | (if byte-compile (byte-compile form) form))) | ||
| 3340 | |||
| 3341 | (defun eglot--glob-emit-self (text self next) | ||
| 3342 | `(,self () (re-search-forward ,(concat "\\=" (regexp-quote text))) (,next))) | ||
| 3343 | |||
| 3344 | (defun eglot--glob-emit-** (_ self next) | ||
| 3345 | `(,self () (or (ignore-errors (save-excursion (,next))) | ||
| 3346 | (and (re-search-forward "\\=/?[^/]+/?") (,self))))) | ||
| 3347 | |||
| 3348 | (defun eglot--glob-emit-* (_ self next) | ||
| 3349 | `(,self () (re-search-forward "\\=[^/]") | ||
| 3350 | (or (ignore-errors (save-excursion (,next))) (,self)))) | ||
| 3351 | |||
| 3352 | (defun eglot--glob-emit-? (_ self next) | ||
| 3353 | `(,self () (re-search-forward "\\=[^/]") (,next))) | ||
| 3354 | |||
| 3355 | (defun eglot--glob-emit-{} (arg self next) | ||
| 3356 | (let ((alternatives (split-string (substring arg 1 (1- (length arg))) ","))) | ||
| 3357 | `(,self () | ||
| 3358 | (or (re-search-forward ,(concat "\\=" (regexp-opt alternatives)) nil t) | ||
| 3359 | (error "Failed matching any of %s" ',alternatives)) | ||
| 3360 | (,next)))) | ||
| 3361 | |||
| 3362 | (defun eglot--glob-emit-range (arg self next) | ||
| 3363 | (when (eq ?! (aref arg 1)) (aset arg 1 ?^)) | ||
| 3364 | `(,self () (re-search-forward ,(concat "\\=" arg)) (,next))) | ||
| 3365 | |||
| 3366 | |||
| 3367 | ;;; List connections mode | ||
| 3368 | |||
| 3369 | (define-derived-mode eglot-list-connections-mode tabulated-list-mode | ||
| 3370 | "" "Eglot mode for listing server connections | ||
| 3371 | \\{eglot-list-connections-mode-map}" | ||
| 3372 | (setq-local tabulated-list-format | ||
| 3373 | `[("Language server" 16) ("Project name" 16) ("Modes handled" 16)]) | ||
| 3374 | (tabulated-list-init-header)) | ||
| 3375 | |||
| 3376 | (defun eglot-list-connections () | ||
| 3377 | "List currently active Eglot connections." | ||
| 3378 | (interactive) | ||
| 3379 | (with-current-buffer | ||
| 3380 | (get-buffer-create "*EGLOT connections*") | ||
| 3381 | (let ((inhibit-read-only t)) | ||
| 3382 | (erase-buffer) | ||
| 3383 | (eglot-list-connections-mode) | ||
| 3384 | (setq-local tabulated-list-entries | ||
| 3385 | (mapcar | ||
| 3386 | (lambda (server) | ||
| 3387 | (list server | ||
| 3388 | `[,(or (plist-get (eglot--server-info server) :name) | ||
| 3389 | (jsonrpc-name server)) | ||
| 3390 | ,(eglot-project-nickname server) | ||
| 3391 | ,(mapconcat #'symbol-name | ||
| 3392 | (eglot--major-modes server) | ||
| 3393 | ", ")])) | ||
| 3394 | (cl-reduce #'append | ||
| 3395 | (hash-table-values eglot--servers-by-project)))) | ||
| 3396 | (revert-buffer) | ||
| 3397 | (pop-to-buffer (current-buffer))))) | ||
| 3398 | |||
| 3399 | |||
| 3400 | ;;; Hacks | ||
| 3401 | ;;; | ||
| 3402 | ;; FIXME: Although desktop.el compatibility is Emacs bug#56407, the | ||
| 3403 | ;; optimal solution agreed to there is a bit more work than what I | ||
| 3404 | ;; have time to right now. See | ||
| 3405 | ;; e.g. https://debbugs.gnu.org/cgi/bugreport.cgi?bug=bug%2356407#68. | ||
| 3406 | ;; For now, just use `with-eval-after-load' | ||
| 3407 | (with-eval-after-load 'desktop | ||
| 3408 | (add-to-list 'desktop-minor-mode-handlers '(eglot--managed-mode . ignore))) | ||
| 3409 | |||
| 3410 | |||
| 3411 | ;;; Obsolete | ||
| 3412 | ;;; | ||
| 3413 | |||
| 3414 | (make-obsolete-variable 'eglot--managed-mode-hook | ||
| 3415 | 'eglot-managed-mode-hook "1.6") | ||
| 3416 | (provide 'eglot) | ||
| 3417 | |||
| 3418 | |||
| 3419 | ;;; Backend completion | ||
| 3420 | |||
| 3421 | ;; Written by Stefan Monnier circa 2016. Something to move to | ||
| 3422 | ;; minibuffer.el "ASAP" (with all the `eglot--lsp-' replaced by | ||
| 3423 | ;; something else. The very same code already in SLY and stable for a | ||
| 3424 | ;; long time. | ||
| 3425 | |||
| 3426 | ;; This "completion style" delegates all the work to the "programmable | ||
| 3427 | ;; completion" table which is then free to implement its own | ||
| 3428 | ;; completion style. Typically this is used to take advantage of some | ||
| 3429 | ;; external tool which already has its own completion system and | ||
| 3430 | ;; doesn't give you efficient access to the prefix completion needed | ||
| 3431 | ;; by other completion styles. The table should recognize the symbols | ||
| 3432 | ;; 'eglot--lsp-tryc and 'eglot--lsp-allc as ACTION, reply with | ||
| 3433 | ;; (eglot--lsp-tryc COMP...) or (eglot--lsp-allc . (STRING . POINT)), | ||
| 3434 | ;; accordingly. tryc/allc names made akward/recognizable on purpose. | ||
| 3435 | |||
| 3436 | (add-to-list 'completion-styles-alist | ||
| 3437 | '(eglot--lsp-backend-style | ||
| 3438 | eglot--lsp-backend-style-try-completion | ||
| 3439 | eglot--lsp-backend-style-all-completions | ||
| 3440 | "Ad-hoc completion style provided by the completion table.")) | ||
| 3441 | |||
| 3442 | (defun eglot--lsp-backend-style-call (op string table pred point) | ||
| 3443 | (when (functionp table) | ||
| 3444 | (let ((res (funcall table string pred (cons op point)))) | ||
| 3445 | (when (eq op (car-safe res)) | ||
| 3446 | (cdr res))))) | ||
| 3447 | |||
| 3448 | (defun eglot--lsp-backend-style-try-completion (string table pred point) | ||
| 3449 | (eglot--lsp-backend-style-call 'eglot--lsp-tryc string table pred point)) | ||
| 3450 | |||
| 3451 | (defun eglot--lsp-backend-style-all-completions (string table pred point) | ||
| 3452 | (eglot--lsp-backend-style-call 'eglot--lsp-allc string table pred point)) | ||
| 3453 | |||
| 3454 | |||
| 3455 | ;; Local Variables: | ||
| 3456 | ;; bug-reference-bug-regexp: "\\(github#\\([0-9]+\\)\\)" | ||
| 3457 | ;; bug-reference-url-format: "https://github.com/joaotavora/eglot/issues/%s" | ||
| 3458 | ;; checkdoc-force-docstrings-flag: nil | ||
| 3459 | ;; End: | ||
| 3460 | |||
| 3461 | ;;; eglot.el ends here | ||
diff --git a/lisp/progmodes/modula2.el b/lisp/progmodes/modula2.el index e668570ba17..09cb848fd52 100644 --- a/lisp/progmodes/modula2.el +++ b/lisp/progmodes/modula2.el | |||
| @@ -65,39 +65,36 @@ | |||
| 65 | "Column for aligning the end of a comment, in Modula-2." | 65 | "Column for aligning the end of a comment, in Modula-2." |
| 66 | :type 'integer) | 66 | :type 'integer) |
| 67 | 67 | ||
| 68 | ;;; Added by TEP | 68 | (defvar-keymap m2-mode-map |
| 69 | (defvar m2-mode-map | 69 | :doc "Keymap used in Modula-2 mode." |
| 70 | (let ((map (make-sparse-keymap))) | 70 | ;; FIXME: Many of those bindings are contrary to coding conventions. |
| 71 | ;; FIXME: Many of those bindings are contrary to coding conventions. | 71 | "C-c b" #'m2-begin |
| 72 | (define-key map "\C-cb" #'m2-begin) | 72 | "C-c c" #'m2-case |
| 73 | (define-key map "\C-cc" #'m2-case) | 73 | "C-c d" #'m2-definition |
| 74 | (define-key map "\C-cd" #'m2-definition) | 74 | "C-c e" #'m2-else |
| 75 | (define-key map "\C-ce" #'m2-else) | 75 | "C-c f" #'m2-for |
| 76 | (define-key map "\C-cf" #'m2-for) | 76 | "C-c h" #'m2-header |
| 77 | (define-key map "\C-ch" #'m2-header) | 77 | "C-c i" #'m2-if |
| 78 | (define-key map "\C-ci" #'m2-if) | 78 | "C-c m" #'m2-module |
| 79 | (define-key map "\C-cm" #'m2-module) | 79 | "C-c l" #'m2-loop |
| 80 | (define-key map "\C-cl" #'m2-loop) | 80 | "C-c o" #'m2-or |
| 81 | (define-key map "\C-co" #'m2-or) | 81 | "C-c p" #'m2-procedure |
| 82 | (define-key map "\C-cp" #'m2-procedure) | 82 | "C-c C-w" #'m2-with |
| 83 | (define-key map "\C-c\C-w" #'m2-with) | 83 | "C-c r" #'m2-record |
| 84 | (define-key map "\C-cr" #'m2-record) | 84 | "C-c s" #'m2-stdio |
| 85 | (define-key map "\C-cs" #'m2-stdio) | 85 | "C-c t" #'m2-type |
| 86 | (define-key map "\C-ct" #'m2-type) | 86 | "C-c u" #'m2-until |
| 87 | (define-key map "\C-cu" #'m2-until) | 87 | "C-c v" #'m2-var |
| 88 | (define-key map "\C-cv" #'m2-var) | 88 | "C-c w" #'m2-while |
| 89 | (define-key map "\C-cw" #'m2-while) | 89 | "C-c x" #'m2-export |
| 90 | (define-key map "\C-cx" #'m2-export) | 90 | "C-c y" #'m2-import |
| 91 | (define-key map "\C-cy" #'m2-import) | 91 | "C-c {" #'m2-begin-comment |
| 92 | (define-key map "\C-c{" #'m2-begin-comment) | 92 | "C-c }" #'m2-end-comment |
| 93 | (define-key map "\C-c}" #'m2-end-comment) | 93 | "C-c C-z" #'suspend-emacs |
| 94 | (define-key map "\C-c\C-z" #'suspend-emacs) | 94 | "C-c C-v" #'m2-visit |
| 95 | (define-key map "\C-c\C-v" #'m2-visit) | 95 | "C-c C-t" #'m2-toggle |
| 96 | (define-key map "\C-c\C-t" #'m2-toggle) | 96 | "C-c C-l" #'m2-link |
| 97 | (define-key map "\C-c\C-l" #'m2-link) | 97 | "C-c C-c" #'m2-compile) |
| 98 | (define-key map "\C-c\C-c" #'m2-compile) | ||
| 99 | map) | ||
| 100 | "Keymap used in Modula-2 mode.") | ||
| 101 | 98 | ||
| 102 | (defcustom m2-indent 5 | 99 | (defcustom m2-indent 5 |
| 103 | "This variable gives the indentation in Modula-2 mode." | 100 | "This variable gives the indentation in Modula-2 mode." |
diff --git a/lisp/progmodes/perl-mode.el b/lisp/progmodes/perl-mode.el index 7b7a2cdf019..c5d5d703fc9 100644 --- a/lisp/progmodes/perl-mode.el +++ b/lisp/progmodes/perl-mode.el | |||
| @@ -215,11 +215,16 @@ | |||
| 215 | (eval-and-compile | 215 | (eval-and-compile |
| 216 | (defconst perl--syntax-exp-intro-keywords | 216 | (defconst perl--syntax-exp-intro-keywords |
| 217 | '("split" "if" "unless" "until" "while" "print" "printf" | 217 | '("split" "if" "unless" "until" "while" "print" "printf" |
| 218 | "grep" "map" "not" "or" "and" "for" "foreach" "return")) | 218 | "grep" "map" "not" "or" "and" "for" "foreach" "return" "die" |
| 219 | "warn" "eval")) | ||
| 219 | 220 | ||
| 220 | (defconst perl--syntax-exp-intro-regexp | 221 | (defconst perl--syntax-exp-intro-regexp |
| 221 | (concat "\\(?:\\(?:^\\|[^$@&%[:word:]]\\)" | 222 | (concat "\\(?:\\(?:^\\|[^$@&%[:word:]]\\)" |
| 222 | (regexp-opt perl--syntax-exp-intro-keywords) | 223 | (regexp-opt perl--syntax-exp-intro-keywords) |
| 224 | ;; A HERE document as an argument to printf? | ||
| 225 | ;; when printing to a filehandle. | ||
| 226 | "\\|printf?[ \t]*$?[_[:alpha:]][_[:alnum:]]*" | ||
| 227 | "\\|=>" | ||
| 223 | "\\|[?:.,;|&*=!~({[]" | 228 | "\\|[?:.,;|&*=!~({[]" |
| 224 | "\\|[^-+][-+]" ;Bug#42168: `+' is intro but `++' isn't! | 229 | "\\|[^-+][-+]" ;Bug#42168: `+' is intro but `++' isn't! |
| 225 | "\\|\\(^\\)\\)[ \t\n]*"))) | 230 | "\\|\\(^\\)\\)[ \t\n]*"))) |
| @@ -335,7 +340,7 @@ | |||
| 335 | "<<\\(~\\)?[ \t]*\\('[^'\n]*'\\|\"[^\"\n]*\"\\|\\\\[[:alpha:]][[:alnum:]]*\\)" | 340 | "<<\\(~\\)?[ \t]*\\('[^'\n]*'\\|\"[^\"\n]*\"\\|\\\\[[:alpha:]][[:alnum:]]*\\)" |
| 336 | ;; The <<EOF case which needs perl--syntax-exp-intro-regexp, to | 341 | ;; The <<EOF case which needs perl--syntax-exp-intro-regexp, to |
| 337 | ;; disambiguate with the left-bitshift operator. | 342 | ;; disambiguate with the left-bitshift operator. |
| 338 | "\\|" perl--syntax-exp-intro-regexp "<<\\(?2:\\sw+\\)\\)" | 343 | "\\|" perl--syntax-exp-intro-regexp "<<\\(?1:~\\)?\\(?2:\\sw+\\)\\)" |
| 339 | ".*\\(\n\\)") | 344 | ".*\\(\n\\)") |
| 340 | (4 (let* ((eol (match-beginning 4)) | 345 | (4 (let* ((eol (match-beginning 4)) |
| 341 | (st (get-text-property eol 'syntax-table)) | 346 | (st (get-text-property eol 'syntax-table)) |
diff --git a/lisp/subr.el b/lisp/subr.el index 08dfe7aa430..e49c22158f9 100644 --- a/lisp/subr.el +++ b/lisp/subr.el | |||
| @@ -3270,7 +3270,14 @@ An obsolete, but still supported form is | |||
| 3270 | where the optional arg MILLISECONDS specifies an additional wait period, | 3270 | where the optional arg MILLISECONDS specifies an additional wait period, |
| 3271 | in milliseconds; this was useful when Emacs was built without | 3271 | in milliseconds; this was useful when Emacs was built without |
| 3272 | floating point support." | 3272 | floating point support." |
| 3273 | (declare (advertised-calling-convention (seconds &optional nodisp) "22.1")) | 3273 | (declare (advertised-calling-convention (seconds &optional nodisp) "22.1") |
| 3274 | (compiler-macro | ||
| 3275 | (lambda (form) | ||
| 3276 | (if (not (or (numberp nodisp) obsolete)) form | ||
| 3277 | (macroexp-warn-and-return | ||
| 3278 | "Obsolete calling convention for 'sit-for'" | ||
| 3279 | `(,(car form) (+ ,seconds (/ (or ,nodisp 0) 1000.0)) ,obsolete) | ||
| 3280 | '(obsolete sit-for)))))) | ||
| 3274 | ;; This used to be implemented in C until the following discussion: | 3281 | ;; This used to be implemented in C until the following discussion: |
| 3275 | ;; https://lists.gnu.org/r/emacs-devel/2006-07/msg00401.html | 3282 | ;; https://lists.gnu.org/r/emacs-devel/2006-07/msg00401.html |
| 3276 | ;; Then it was moved here using an implementation based on an idle timer, | 3283 | ;; Then it was moved here using an implementation based on an idle timer, |
diff --git a/src/window.c b/src/window.c index 4e8b352e164..f116b9a9d72 100644 --- a/src/window.c +++ b/src/window.c | |||
| @@ -5441,12 +5441,13 @@ window_wants_mode_line (struct window *w) | |||
| 5441 | * Return 1 if window W wants a header line and is high enough to | 5441 | * Return 1 if window W wants a header line and is high enough to |
| 5442 | * accommodate it, 0 otherwise. | 5442 | * accommodate it, 0 otherwise. |
| 5443 | * | 5443 | * |
| 5444 | * W wants a header line if it's a leaf window and neither a minibuffer | 5444 | * W wants a header line if it's a leaf window and neither a |
| 5445 | * nor a pseudo window. Moreover, its 'window-mode-line-format' | 5445 | * minibuffer nor a pseudo window. Moreover, its |
| 5446 | * parameter must not be 'none' and either that parameter or W's | 5446 | * 'window-header-line-format' parameter must not be 'none' and either |
| 5447 | * buffer's 'mode-line-format' value must be non-nil. Finally, W must | 5447 | * that parameter or W's buffer's 'header-line-format' value must be |
| 5448 | * be higher than its frame's canonical character height and be able to | 5448 | * non-nil. Finally, W must be higher than its frame's canonical |
| 5449 | * accommodate a mode line too if necessary (the mode line prevails). | 5449 | * character height and be able to accommodate a mode line too if |
| 5450 | * necessary (the mode line prevails). | ||
| 5450 | */ | 5451 | */ |
| 5451 | bool | 5452 | bool |
| 5452 | window_wants_header_line (struct window *w) | 5453 | window_wants_header_line (struct window *w) |
| @@ -5474,9 +5475,9 @@ window_wants_header_line (struct window *w) | |||
| 5474 | * accommodate it, 0 otherwise. | 5475 | * accommodate it, 0 otherwise. |
| 5475 | * | 5476 | * |
| 5476 | * W wants a tab line if it's a leaf window and neither a minibuffer | 5477 | * W wants a tab line if it's a leaf window and neither a minibuffer |
| 5477 | * nor a pseudo window. Moreover, its 'window-mode-line-format' | 5478 | * nor a pseudo window. Moreover, its 'window-tab-line-format' |
| 5478 | * parameter must not be 'none' and either that parameter or W's | 5479 | * parameter must not be 'none' and either that parameter or W's |
| 5479 | * buffer's 'mode-line-format' value must be non-nil. Finally, W must | 5480 | * buffer's 'tab-line-format' value must be non-nil. Finally, W must |
| 5480 | * be higher than its frame's canonical character height and be able | 5481 | * be higher than its frame's canonical character height and be able |
| 5481 | * to accommodate a mode line and a header line too if necessary (the | 5482 | * to accommodate a mode line and a header line too if necessary (the |
| 5482 | * mode line and a header line prevail). | 5483 | * mode line and a header line prevail). |
diff --git a/src/xterm.c b/src/xterm.c index 7c3ab87e87b..8b3d6f77a6c 100644 --- a/src/xterm.c +++ b/src/xterm.c | |||
| @@ -17857,7 +17857,7 @@ x_handle_wm_state (struct frame *f, struct input_event *ie) | |||
| 17857 | 17857 | ||
| 17858 | static bool | 17858 | static bool |
| 17859 | x_handle_selection_monitor_event (struct x_display_info *dpyinfo, | 17859 | x_handle_selection_monitor_event (struct x_display_info *dpyinfo, |
| 17860 | XEvent *event) | 17860 | const XEvent *event) |
| 17861 | { | 17861 | { |
| 17862 | XFixesSelectionNotifyEvent *notify; | 17862 | XFixesSelectionNotifyEvent *notify; |
| 17863 | int i; | 17863 | int i; |
| @@ -17940,7 +17940,11 @@ handle_one_xevent (struct x_display_info *dpyinfo, | |||
| 17940 | GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (dpyinfo->display); | 17940 | GdkDisplay *gdpy = gdk_x11_lookup_xdisplay (dpyinfo->display); |
| 17941 | #endif | 17941 | #endif |
| 17942 | int dx, dy; | 17942 | int dx, dy; |
| 17943 | |||
| 17944 | /* Avoid warnings when SAFE_ALLOCA is not actually used. */ | ||
| 17945 | #if defined HAVE_XINPUT2 || defined HAVE_XKB || defined HAVE_X_I18N | ||
| 17943 | USE_SAFE_ALLOCA; | 17946 | USE_SAFE_ALLOCA; |
| 17947 | #endif | ||
| 17944 | 17948 | ||
| 17945 | /* This function is not reentrant, so input should be blocked before | 17949 | /* This function is not reentrant, so input should be blocked before |
| 17946 | it is called. */ | 17950 | it is called. */ |
| @@ -24220,7 +24224,10 @@ handle_one_xevent (struct x_display_info *dpyinfo, | |||
| 24220 | count++; | 24224 | count++; |
| 24221 | } | 24225 | } |
| 24222 | 24226 | ||
| 24227 | #if defined HAVE_XINPUT2 || defined HAVE_XKB || defined HAVE_X_I18N | ||
| 24223 | SAFE_FREE (); | 24228 | SAFE_FREE (); |
| 24229 | #endif | ||
| 24230 | |||
| 24224 | return count; | 24231 | return count; |
| 24225 | } | 24232 | } |
| 24226 | 24233 | ||
| @@ -25691,6 +25698,14 @@ x_new_font (struct frame *f, Lisp_Object font_object, int fontset) | |||
| 25691 | 25698 | ||
| 25692 | #ifdef HAVE_X11R6 | 25699 | #ifdef HAVE_X11R6 |
| 25693 | 25700 | ||
| 25701 | /* HAVE_X11R6 means Xlib conforms to the R6 specification or later. | ||
| 25702 | HAVE_X11R6_XIM, OTOH, means that Emacs should try to use R6-style | ||
| 25703 | callback driven input method initialization. They are separate | ||
| 25704 | because Sun apparently ships buggy Xlib with some versions of | ||
| 25705 | Solaris... */ | ||
| 25706 | |||
| 25707 | #ifdef HAVE_X11R6_XIM | ||
| 25708 | |||
| 25694 | /* If preedit text is set on F, cancel preedit, free the text, and | 25709 | /* If preedit text is set on F, cancel preedit, free the text, and |
| 25695 | generate the appropriate events to cancel the preedit display. | 25710 | generate the appropriate events to cancel the preedit display. |
| 25696 | 25711 | ||
| @@ -25756,6 +25771,8 @@ xim_destroy_callback (XIM xim, XPointer client_data, XPointer call_data) | |||
| 25756 | unblock_input (); | 25771 | unblock_input (); |
| 25757 | } | 25772 | } |
| 25758 | 25773 | ||
| 25774 | #endif | ||
| 25775 | |||
| 25759 | #endif /* HAVE_X11R6 */ | 25776 | #endif /* HAVE_X11R6 */ |
| 25760 | 25777 | ||
| 25761 | /* Open the connection to the XIM server on display DPYINFO. | 25778 | /* Open the connection to the XIM server on display DPYINFO. |
| @@ -27117,6 +27134,64 @@ xembed_request_focus (struct frame *f) | |||
| 27117 | XEMBED_REQUEST_FOCUS, 0, 0, 0); | 27134 | XEMBED_REQUEST_FOCUS, 0, 0, 0); |
| 27118 | } | 27135 | } |
| 27119 | 27136 | ||
| 27137 | static Bool | ||
| 27138 | server_timestamp_predicate (Display *display, XEvent *xevent, | ||
| 27139 | XPointer arg) | ||
| 27140 | { | ||
| 27141 | XID *args = (XID *) arg; | ||
| 27142 | |||
| 27143 | if (xevent->type == PropertyNotify | ||
| 27144 | && xevent->xproperty.window == args[0] | ||
| 27145 | && xevent->xproperty.atom == args[1]) | ||
| 27146 | return True; | ||
| 27147 | |||
| 27148 | return False; | ||
| 27149 | } | ||
| 27150 | |||
| 27151 | /* Get the server time. The X server is guaranteed to deliver the | ||
| 27152 | PropertyNotify event, so there is no reason to use x_if_event. */ | ||
| 27153 | |||
| 27154 | static Time | ||
| 27155 | x_get_server_time (struct frame *f) | ||
| 27156 | { | ||
| 27157 | Atom property_atom; | ||
| 27158 | XEvent property_dummy; | ||
| 27159 | struct x_display_info *dpyinfo; | ||
| 27160 | XID client_data[2]; | ||
| 27161 | #if defined HAVE_XSYNC && !defined USE_GTK && defined HAVE_CLOCK_GETTIME | ||
| 27162 | uint_fast64_t current_monotonic_time; | ||
| 27163 | #endif | ||
| 27164 | |||
| 27165 | /* If the server time is the same as the monotonic time, avoid a | ||
| 27166 | roundtrip by using that instead. */ | ||
| 27167 | |||
| 27168 | #if defined HAVE_XSYNC && !defined USE_GTK && defined HAVE_CLOCK_GETTIME | ||
| 27169 | if (FRAME_DISPLAY_INFO (f)->server_time_monotonic_p) | ||
| 27170 | { | ||
| 27171 | current_monotonic_time = x_sync_current_monotonic_time (); | ||
| 27172 | |||
| 27173 | if (current_monotonic_time) | ||
| 27174 | /* Truncate the time to CARD32. */ | ||
| 27175 | return (current_monotonic_time / 1000) & X_ULONG_MAX; | ||
| 27176 | } | ||
| 27177 | #endif | ||
| 27178 | |||
| 27179 | dpyinfo = FRAME_DISPLAY_INFO (f); | ||
| 27180 | property_atom = dpyinfo->Xatom_EMACS_SERVER_TIME_PROP; | ||
| 27181 | client_data[0] = FRAME_OUTER_WINDOW (f); | ||
| 27182 | client_data[1] = property_atom; | ||
| 27183 | |||
| 27184 | XChangeProperty (dpyinfo->display, FRAME_OUTER_WINDOW (f), | ||
| 27185 | property_atom, XA_ATOM, 32, | ||
| 27186 | PropModeReplace, | ||
| 27187 | (unsigned char *) &property_atom, 1); | ||
| 27188 | |||
| 27189 | XIfEvent (dpyinfo->display, &property_dummy, | ||
| 27190 | server_timestamp_predicate, (XPointer) client_data); | ||
| 27191 | |||
| 27192 | return property_dummy.xproperty.time; | ||
| 27193 | } | ||
| 27194 | |||
| 27120 | /* Activate frame with Extended Window Manager Hints */ | 27195 | /* Activate frame with Extended Window Manager Hints */ |
| 27121 | 27196 | ||
| 27122 | static void | 27197 | static void |
| @@ -27124,6 +27199,7 @@ x_ewmh_activate_frame (struct frame *f) | |||
| 27124 | { | 27199 | { |
| 27125 | XEvent msg; | 27200 | XEvent msg; |
| 27126 | struct x_display_info *dpyinfo; | 27201 | struct x_display_info *dpyinfo; |
| 27202 | Time time; | ||
| 27127 | 27203 | ||
| 27128 | dpyinfo = FRAME_DISPLAY_INFO (f); | 27204 | dpyinfo = FRAME_DISPLAY_INFO (f); |
| 27129 | 27205 | ||
| @@ -27144,6 +27220,43 @@ x_ewmh_activate_frame (struct frame *f) | |||
| 27144 | msg.xclient.data.l[3] = 0; | 27220 | msg.xclient.data.l[3] = 0; |
| 27145 | msg.xclient.data.l[4] = 0; | 27221 | msg.xclient.data.l[4] = 0; |
| 27146 | 27222 | ||
| 27223 | /* No frame is currently focused on that display, so apply any | ||
| 27224 | bypass for focus stealing prevention that the user has | ||
| 27225 | specified. */ | ||
| 27226 | if (!dpyinfo->x_focus_frame) | ||
| 27227 | { | ||
| 27228 | if (EQ (Vx_allow_focus_stealing, Qimitate_pager)) | ||
| 27229 | msg.xclient.data.l[0] = 2; | ||
| 27230 | else if (EQ (Vx_allow_focus_stealing, Qnewer_time)) | ||
| 27231 | { | ||
| 27232 | block_input (); | ||
| 27233 | time = x_get_server_time (f); | ||
| 27234 | #ifdef USE_GTK | ||
| 27235 | x_set_gtk_user_time (f, time); | ||
| 27236 | #endif | ||
| 27237 | /* Temporarily override dpyinfo->x_focus_frame so the | ||
| 27238 | user time property is set on the right window. */ | ||
| 27239 | dpyinfo->x_focus_frame = f; | ||
| 27240 | x_display_set_last_user_time (dpyinfo, time, true, true); | ||
| 27241 | dpyinfo->x_focus_frame = NULL; | ||
| 27242 | unblock_input (); | ||
| 27243 | |||
| 27244 | msg.xclient.data.l[1] = time; | ||
| 27245 | } | ||
| 27246 | else if (EQ (Vx_allow_focus_stealing, Qraise_and_focus)) | ||
| 27247 | { | ||
| 27248 | time = x_get_server_time (f); | ||
| 27249 | |||
| 27250 | x_ignore_errors_for_next_request (dpyinfo); | ||
| 27251 | XSetInputFocus (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f), | ||
| 27252 | RevertToParent, time); | ||
| 27253 | XRaiseWindow (FRAME_X_DISPLAY (f), FRAME_OUTER_WINDOW (f)); | ||
| 27254 | x_stop_ignoring_errors (dpyinfo); | ||
| 27255 | |||
| 27256 | return; | ||
| 27257 | } | ||
| 27258 | } | ||
| 27259 | |||
| 27147 | XSendEvent (dpyinfo->display, dpyinfo->root_window, | 27260 | XSendEvent (dpyinfo->display, dpyinfo->root_window, |
| 27148 | False, (SubstructureRedirectMask | 27261 | False, (SubstructureRedirectMask |
| 27149 | | SubstructureNotifyMask), &msg); | 27262 | | SubstructureNotifyMask), &msg); |
| @@ -30281,7 +30394,7 @@ mark_xterm (void) | |||
| 30281 | { | 30394 | { |
| 30282 | Lisp_Object val; | 30395 | Lisp_Object val; |
| 30283 | #if defined HAVE_XINPUT2 || defined USE_TOOLKIT_SCROLL_BARS \ | 30396 | #if defined HAVE_XINPUT2 || defined USE_TOOLKIT_SCROLL_BARS \ |
| 30284 | || defined HAVE_XRANDR || defined USE_GTK | 30397 | || defined HAVE_XRANDR || defined USE_GTK || defined HAVE_X_I18N |
| 30285 | struct x_display_info *dpyinfo; | 30398 | struct x_display_info *dpyinfo; |
| 30286 | #if defined HAVE_XINPUT2 || defined USE_TOOLKIT_SCROLL_BARS | 30399 | #if defined HAVE_XINPUT2 || defined USE_TOOLKIT_SCROLL_BARS |
| 30287 | int i; | 30400 | int i; |
| @@ -30505,8 +30618,14 @@ x_get_keyboard_modifiers (struct x_display_info *dpyinfo) | |||
| 30505 | /* This sometimes happens when the function is called during display | 30618 | /* This sometimes happens when the function is called during display |
| 30506 | initialization, which can happen while obtaining vendor specific | 30619 | initialization, which can happen while obtaining vendor specific |
| 30507 | keysyms. */ | 30620 | keysyms. */ |
| 30621 | |||
| 30622 | #ifdef HAVE_XKB | ||
| 30508 | if (!dpyinfo->xkb_desc && !dpyinfo->modmap) | 30623 | if (!dpyinfo->xkb_desc && !dpyinfo->modmap) |
| 30509 | x_find_modifier_meanings (dpyinfo); | 30624 | x_find_modifier_meanings (dpyinfo); |
| 30625 | #else | ||
| 30626 | if (!dpyinfo->modmap) | ||
| 30627 | x_find_modifier_meanings (dpyinfo); | ||
| 30628 | #endif | ||
| 30510 | 30629 | ||
| 30511 | return list5 (make_uint (dpyinfo->hyper_mod_mask), | 30630 | return list5 (make_uint (dpyinfo->hyper_mod_mask), |
| 30512 | make_uint (dpyinfo->super_mod_mask), | 30631 | make_uint (dpyinfo->super_mod_mask), |
| @@ -30626,6 +30745,9 @@ With MS Windows, Haiku windowing or Nextstep, the value is t. */); | |||
| 30626 | Fput (Qsuper, Qmodifier_value, make_fixnum (super_modifier)); | 30745 | Fput (Qsuper, Qmodifier_value, make_fixnum (super_modifier)); |
| 30627 | DEFSYM (QXdndSelection, "XdndSelection"); | 30746 | DEFSYM (QXdndSelection, "XdndSelection"); |
| 30628 | DEFSYM (Qx_selection_alias_alist, "x-selection-alias-alist"); | 30747 | DEFSYM (Qx_selection_alias_alist, "x-selection-alias-alist"); |
| 30748 | DEFSYM (Qimitate_pager, "imitate-pager"); | ||
| 30749 | DEFSYM (Qnewer_time, "newer-time"); | ||
| 30750 | DEFSYM (Qraise_and_focus, "raise-and-focus"); | ||
| 30629 | 30751 | ||
| 30630 | DEFVAR_LISP ("x-ctrl-keysym", Vx_ctrl_keysym, | 30752 | DEFVAR_LISP ("x-ctrl-keysym", Vx_ctrl_keysym, |
| 30631 | doc: /* Which keys Emacs uses for the ctrl modifier. | 30753 | doc: /* Which keys Emacs uses for the ctrl modifier. |
| @@ -30879,4 +31001,24 @@ connection setup. */); | |||
| 30879 | /* The default value of this variable is chosen so that updating the | 31001 | /* The default value of this variable is chosen so that updating the |
| 30880 | tool bar does not require a call to _XReply. */ | 31002 | tool bar does not require a call to _XReply. */ |
| 30881 | Vx_fast_selection_list = list1 (QCLIPBOARD); | 31003 | Vx_fast_selection_list = list1 (QCLIPBOARD); |
| 31004 | |||
| 31005 | DEFVAR_LISP ("x-allow-focus-stealing", Vx_allow_focus_stealing, | ||
| 31006 | doc: /* How to bypass window manager focus stealing prevention. | ||
| 31007 | |||
| 31008 | Some window managers prevent `x-focus-frame' from activating the given | ||
| 31009 | frame when Emacs is in the background, which is especially prone to | ||
| 31010 | cause problems when the Emacs server wants to activate itself. This | ||
| 31011 | variable specifies the strategy used to activate frames when that is | ||
| 31012 | the case, and has several valid values (any other value means to not | ||
| 31013 | bypass window manager focus stealing prevention): | ||
| 31014 | |||
| 31015 | - The symbol `imitate-pager', which means to pretend that Emacs is a | ||
| 31016 | pager. | ||
| 31017 | |||
| 31018 | - The symbol `newer-time', which means to fetch the current time | ||
| 31019 | from the X server and use it to activate the frame. | ||
| 31020 | |||
| 31021 | - The symbol `raise-and-focus', which means to raise the window and | ||
| 31022 | focus it manually. */); | ||
| 31023 | Vx_allow_focus_stealing = Qnewer_time; | ||
| 30882 | } | 31024 | } |
diff --git a/test/lisp/image/wallpaper-tests.el b/test/lisp/image/wallpaper-tests.el index cb6818f8c1b..a5d3343bd4d 100644 --- a/test/lisp/image/wallpaper-tests.el +++ b/test/lisp/image/wallpaper-tests.el | |||
| @@ -99,9 +99,12 @@ | |||
| 99 | ("touch" "touch" fil | 99 | ("touch" "touch" fil |
| 100 | :init-action (lambda () (setq called t))))) | 100 | :init-action (lambda () (setq called t))))) |
| 101 | (wallpaper-command (wallpaper--find-command)) | 101 | (wallpaper-command (wallpaper--find-command)) |
| 102 | (wallpaper-command-args (wallpaper--find-command-args))) | 102 | (wallpaper-command-args (wallpaper--find-command-args)) |
| 103 | process) | ||
| 103 | (should (functionp (wallpaper-setter-init-action wallpaper--current-setter))) | 104 | (should (functionp (wallpaper-setter-init-action wallpaper--current-setter))) |
| 104 | (wallpaper-set fil-jpg) | 105 | (setq process (wallpaper-set fil-jpg)) |
| 106 | ;; Wait for "touch" process to exit so temp file is removed. | ||
| 107 | (accept-process-output process 3) | ||
| 105 | (should called))))) | 108 | (should called))))) |
| 106 | 109 | ||
| 107 | (ert-deftest wallpaper-set/calls-wallpaper-set-function () | 110 | (ert-deftest wallpaper-set/calls-wallpaper-set-function () |
diff --git a/test/lisp/progmodes/cperl-mode-resources/here-docs.pl b/test/lisp/progmodes/cperl-mode-resources/here-docs.pl index bb3d4871a91..13d879bf762 100644 --- a/test/lisp/progmodes/cperl-mode-resources/here-docs.pl +++ b/test/lisp/progmodes/cperl-mode-resources/here-docs.pl | |||
| @@ -140,4 +140,70 @@ HERE | |||
| 140 | 140 | ||
| 141 | . 'indent-level'; # Continuation, should be indented | 141 | . 'indent-level'; # Continuation, should be indented |
| 142 | 142 | ||
| 143 | =head2 Test case 7 | ||
| 144 | |||
| 145 | An indented HERE document using a bare identifier. | ||
| 146 | |||
| 147 | =cut | ||
| 148 | |||
| 149 | ## test case | ||
| 150 | |||
| 151 | $text = <<~HERE; | ||
| 152 | look-here | ||
| 153 | HERE | ||
| 154 | |||
| 155 | $noindent = "New statement in this line"; | ||
| 156 | |||
| 157 | =head2 Test case 8 | ||
| 158 | |||
| 159 | A HERE document as an argument to print when printing to a filehandle. | ||
| 160 | |||
| 161 | =cut | ||
| 162 | |||
| 163 | ## test case | ||
| 164 | |||
| 165 | print $fh <<~HERE; | ||
| 166 | look-here | ||
| 167 | HERE | ||
| 168 | |||
| 169 | $noindent = "New statement in this line"; | ||
| 170 | |||
| 171 | =head2 Test case 9 | ||
| 172 | |||
| 173 | A HERE document as a hash value. | ||
| 174 | |||
| 175 | =cut | ||
| 176 | |||
| 177 | my %foo = ( | ||
| 178 | text => <<~HERE | ||
| 179 | look-here | ||
| 180 | HERE | ||
| 181 | ); | ||
| 182 | |||
| 183 | $noindent = "New statement in this line"; | ||
| 184 | |||
| 185 | =head2 Test case 10 | ||
| 186 | |||
| 187 | A HERE document as an argument to die. | ||
| 188 | |||
| 189 | =cut | ||
| 190 | |||
| 191 | 1 or die <<HERE; | ||
| 192 | look-here | ||
| 193 | HERE | ||
| 194 | |||
| 195 | $noindent = "New statement in this line"; | ||
| 196 | |||
| 197 | =head2 Test case 11 | ||
| 198 | |||
| 199 | A HERE document as an argument to warn. | ||
| 200 | |||
| 201 | =cut | ||
| 202 | |||
| 203 | 1 or warn <<HERE; | ||
| 204 | look-here | ||
| 205 | HERE | ||
| 206 | |||
| 207 | $noindent = "New statement in this line"; | ||
| 208 | |||
| 143 | __END__ | 209 | __END__ |