Eglot: allow customizing the mode line (bug#71823)

* lisp/progmodes/eglot.el (eglot-code-action-indications): Adjust docstring.
(eglot--mode-line-props): Tweak.
(eglot--mode-line-format): Delete.
(eglot-mode-line-format): New defcustom.
(eglot-mode-line-menu, eglot-mode-line-session)
(eglot-mode-line-error, eglot-mode-line-pending-requests)
(eglot-mode-line-progress, eglot-mode-line-action-suggestion): New
constants.
(mode-line-misc-info): Adjust setting

* etc/EGLOT-NEWS: Mention new feature.

* doc/misc/eglot.texi (Customization Variables): Mention new variable.
This commit is contained in:
João Távora 2025-01-28 10:39:25 +00:00
parent d6a502fc7a
commit b4e9115f4f
3 changed files with 171 additions and 69 deletions

View file

@ -576,7 +576,7 @@ buffer, where @var{server} is the name of the server and @var{project}
identifies the project by its root directory. Clicking the mouse on the
Eglot mode-line indication activates a menu with server-specific items.
The layout of the Eglot mode-line can be changed by customizing
@code{eglot-mode-line-format} (@pxref{Customizing Eglot}).
@code{eglot-mode-line-format} (@pxref{Customization Variables}).
@item
For each buffer in which Eglot is active, it notifies the language
@ -958,6 +958,28 @@ ElDoc will not hint about at-point actions.
@item eglot-code-action-indicator
This variable is a string determining what the special indicator looks
like.
@item eglot-mode-line-format
This variables controls the shape and look of Eglot's mode line. Its
value is a mode line ``construct'', generally a list of symbols and
strings. The following symbols are meaningful and useful for Eglot's
mode line:
@itemize @minus
@item
@code{eglot-mode-line-menu}: access Eglot's main menu. See also
@code{eglot-menu-string};
@item
@code{eglot-mode-line-session}: indicate current project and access
current session's menu;
@item
@code{eglot-mode-line-error}: an indication of recent LSP errors;
@item
@code{eglot-mode-line-pending-requests}: number of pending LSP requests;
@item
@code{eglot-mode-line-progress}: progress reporter widgets;
@item
@code{eglot-mode-line-action-suggestion}: LSP code actions at point.
@end itemize
@end vtable
@node Other Variables

View file

@ -34,6 +34,12 @@ execute them quickly via the usual 'eglot-code-actions' command.
Customize with 'eglot-code-action-indications' and
'eglot-code-action-indicator'.
** Mode line is customizable
The composition of Eglot's mode line can be fully customized by adding
or removing symbols and strings from the customizable variable
'eglot-mode-line-format'
* Changes in Eglot 1.18 (20/1/2025)

View file

@ -525,10 +525,6 @@ ACTION is the default value for commands not in the alist."
:type 'boolean
:package-version '(Eglot . "1.17.30"))
(defcustom eglot-menu-string "eglot"
"String displayed in mode line when Eglot is active."
:type 'string)
(defcustom eglot-report-progress t
"If non-nil, show progress of long running LSP server work.
If set to `messages', use *Messages* buffer, else use Eglot's
@ -584,18 +580,24 @@ servers."
"Face used for code action suggestions.")
(defcustom eglot-code-action-indications
'(eldoc-hint mode-line margin)
'(eldoc-hint margin)
"How Eglot indicates there's are code actions available at point.
Value is a list of symbols, more than one can be specified:
- `eldoc-hint': ElDoc is used to hint about at-point actions.
- `margin': A special indicator appears in the margin.
- `nearby': A special indicator appears near point.
- `eldoc-hint': ElDoc is used to hint about at-point actions;
- `margin': A special indicator appears in the margin;
- `nearby': A special indicator appears near point;
- `mode-line': A special indicator appears in the mode-line.
`margin' and `nearby' are incompatible. `margin's indicator is not
interactive. If the list is empty, Eglot will not hint about code
actions at point."
If the list is empty, Eglot will not hint about code actions at point.
Note additionally:
- `margin' and `nearby' are incompatible. If both are specified,
the latter takes priority;
- `margin's indicator is not interactive;
- `mode-line' only works if `eglot-mode-line-action-suggestion' exists in
`eglot-mode-line-format' (which see)."
:type '(set
:tag "Tick the ones you're interested in"
(const :tag "ElDoc textual hint" eldoc-hint)
@ -2204,12 +2206,8 @@ If it is activated, also signal textDocument/didOpen."
(setf (jsonrpc-last-error server) nil))
;;; Mode-line, menu and other sugar
;;; Menu and other sugar
;;;
(defvar eglot--mode-line-format `(:eval (eglot--mode-line-format)))
(put 'eglot--mode-line-format 'risky-local-variable t)
(defun eglot--mouse-call (what &optional update-mode-line)
"Make an interactive lambda for calling WHAT with the mouse."
(lambda (event)
@ -2298,6 +2296,44 @@ If it is activated, also signal textDocument/didOpen."
(interactive)
(customize-variable 'eglot-events-buffer-size))]))
;;; Mode-line
;;;
(defcustom eglot-mode-line-format
'(eglot-mode-line-menu
eglot-mode-line-session
eglot-mode-line-error
eglot-mode-line-pending-requests
eglot-mode-line-progress
eglot-mode-line-action-suggestion)
"Mode line construct for customizing Eglot information.
Meaningful symbols in this construct include:
- `eglot-mode-line-menu': access Eglot's main menu. See also
`eglot-menu-string';
- `eglot-mode-line-session': indicate current project and access current
session's menu;
- `eglot-mode-line-error': an indication of recent LSP errors;
- `eglot-mode-line-pending-requests': number of pending LSP requests;
- `eglot-mode-line-progress': progress reporter widgets;
- `eglot-mode-line-action-suggestion': LSP code action at point.
"
:type '(repeat (choice string symbol))
:package-version '(Eglot . "1.19"))
(put 'eglot-mode-line-format 'risky-local-variable t)
(put 'eglot-mode-line-menu 'risky-local-variable t)
(put 'eglot-mode-line-session 'risky-local-variable t)
(put 'eglot-mode-line-error 'risky-local-variable t)
(put 'eglot-mode-line-pending-requests 'risky-local-variable t)
(put 'eglot-mode-line-progress 'risky-local-variable t)
(put 'eglot-mode-line-action-suggestion 'risky-local-variable t)
(defun eglot--mode-line-props (thing face defs &optional prepend)
"Helper for function `eglot--mode-line-format'.
Uses THING, FACE, DEFS and PREPEND."
@ -2307,10 +2343,11 @@ Uses THING, FACE, DEFS and PREPEND."
do (define-key map `[mode-line ,key] (eglot--mouse-call def t))
concat (format "%s: %s" key help) into blurb
when rest concat "\n" into blurb
finally (return `(:propertize ,thing
face ,face
keymap ,map help-echo ,(concat prepend blurb)
mouse-face mode-line-highlight))))
finally (return (propertize
thing
'face face
'keymap map 'help-echo (concat prepend blurb)
'mouse-face 'mode-line-highlight))))
(defconst eglot--main-menu-map
(let ((map (make-sparse-keymap)))
@ -2322,55 +2359,92 @@ Uses THING, FACE, DEFS and PREPEND."
(define-key map [mode-line down-mouse-1] eglot-server-menu)
map))
(defun eglot--mode-line-format ()
"Compose Eglot's mode-line."
(let* ((server (eglot-current-server))
(nick (and server (eglot-project-nickname server)))
(pending (and server (jsonrpc-continuation-count server)))
(last-error (and server (jsonrpc-last-error server))))
(append
`(,(propertize
eglot-menu-string
'face 'eglot-mode-line
'mouse-face 'mode-line-highlight
'help-echo "Eglot: Emacs LSP client\nmouse-1: Display minor mode menu"
'keymap eglot--main-menu-map))
(when nick
`(":"
,(propertize
nick
'face 'eglot-mode-line
'mouse-face 'mode-line-highlight
'help-echo (format "Project '%s'\nmouse-1: LSP server control menu" nick)
'keymap eglot--server-menu-map)
,@(when last-error
`("/" ,(eglot--mode-line-props
"error" 'compilation-mode-line-fail
'((mouse-3 eglot-clear-status "Clear this status"))
(format "An error occurred: %s\n" (plist-get last-error
:message)))))
,@(when (cl-plusp pending)
`("/" ,(eglot--mode-line-props
(format "%d" pending) 'warning
'((mouse-3 eglot-forget-pending-continuations
"Forget pending continuations"))
"Number of outgoing, \
still unanswered LSP requests to the server\n")))
,@(cl-loop for pr hash-values of (eglot--progress-reporters server)
when (eq (car pr) 'eglot--mode-line-reporter)
append `("/" ,(eglot--mode-line-props
(format "%s%%%%" (or (nth 4 pr) "?"))
'eglot-mode-line
nil
(format "(%s) %s %s" (nth 1 pr)
(nth 2 pr) (nth 3 pr)))))
,@(when (and
(memq 'mode-line eglot-code-action-indications)
(overlay-buffer eglot--suggestion-overlay))
`("/" ,(overlay-get eglot--suggestion-overlay 'eglot--suggestion-tooltip))))))))
(defcustom eglot-menu-string "eglot"
"String displayed in mode line when Eglot is active."
:type 'string)
(add-to-list 'mode-line-misc-info
`(eglot--managed-mode (" [" eglot--mode-line-format "] ")))
(defconst eglot-mode-line-menu
(propertize
eglot-menu-string
'face 'eglot-mode-line
'mouse-face 'mode-line-highlight
'help-echo "Eglot: Emacs LSP client\nmouse-1: Display minor mode menu"
'keymap eglot--main-menu-map)
"Eglot mode line construct for Eglot's main menu.")
(defconst eglot-mode-line-session
'(:eval (when-let* ((server (eglot-current-server))
(nick (eglot-project-nickname server)))
(propertize
nick
'face 'eglot-mode-line
'mouse-face 'mode-line-highlight
'help-echo (format "Project '%s'\nmouse-1: LSP server control menu" nick)
'keymap eglot--server-menu-map)))
"Eglot mode line construct for project/LSP session.")
(defconst eglot-mode-line-error
'(:eval (when-let* ((server (eglot-current-server))
(last-error (and server (jsonrpc-last-error server))))
(eglot--mode-line-props
"error" 'compilation-mode-line-fail
'((mouse-3 eglot-clear-status "Clear this status"))
(format "An error occurred: %s\n" (plist-get last-error
:message)))))
"Eglot mode line construct for LSP errors.")
(defconst eglot-mode-line-pending-requests
'(:eval (when-let* ((server (eglot-current-server))
(pending (jsonrpc-continuation-count server)))
(when (cl-plusp pending)
(eglot--mode-line-props
(format "%d" pending) 'warning
'((mouse-3 eglot-forget-pending-continuations
"Forget pending continuations"))
"Number of outgoing, \
still unanswered LSP requests to the server\n"))))
"Eglot mode line construct for number of pending LSP requests.")
(defconst eglot-mode-line-progress
'(:eval
(when-let ((server (eglot-current-server)))
(cl-loop
for pr hash-values of (eglot--progress-reporters server)
when (eq (car pr) 'eglot--mode-line-reporter)
collect (eglot--mode-line-props
(format "%s%%%%" (or (nth 4 pr) "?"))
'eglot-mode-line
nil
(format "(%s) %s %s" (nth 1 pr)
(nth 2 pr) (nth 3 pr)))
into reports
finally (return (mapconcat #'identity reports " /")))))
"Eglot mode line construct for LSP progress reports.")
(defconst eglot-mode-line-action-suggestion
'(:eval
(when (and (memq 'mode-line eglot-code-action-indications)
(overlay-buffer eglot--suggestion-overlay))
(overlay-get eglot--suggestion-overlay 'eglot--suggestion-tooltip)))
"Eglot mode line construct for at-point code actions.")
(add-to-list
'mode-line-misc-info
`(eglot--managed-mode
(" ["
(:eval
(cl-loop for e in eglot-mode-line-format
for render = (format-mode-line e)
unless (eq render "")
collect (cons render
(eq e 'eglot-mode-line-menu))
into rendered
finally
(return (cl-loop for (rspec . rest) on rendered
for (r . titlep) = rspec
concat r
when rest concat (if titlep ":" "/")))))
"] ")))
;;; Flymake customization