1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2026-05-30 17:22:17 -07:00

Eglot: prefer markdown-ts-view-mode for markup rendering (bug#80127)

Eglot previously needed gfm-view-mode from markdown-mode.el to render
Markdown from LSP servers.  It now prefers markdown-ts-view-mode when
available.

* lisp/progmodes/eglot.el (eglot--accepted-formats): Recognize
markdown-ts-view-mode as a Markdown renderer.
(eglot--format-markup): Rework with cl-labels; prefer
markdown-ts-view-mode over gfm-view-mode.

* doc/misc/eglot.texi (Eglot Features): Don't mention
markdown-mode directly.

* etc/EGLOT-NEWS: Mention change
This commit is contained in:
João Távora 2026-05-08 22:22:49 +01:00
parent 689c3bd508
commit aba60ad0c5
3 changed files with 56 additions and 44 deletions

View file

@ -488,12 +488,11 @@ completion package to instantiate these snippets using YASnippet.
(YASnippet can be installed from GNU ELPA.)
@item
If the popular third-party package @code{markdown-mode} is installed,
and the server provides at-point documentation formatted as Markdown in
When the server provides at-point documentation formatted as Markdown in
addition to plain text, Eglot arranges for the ElDoc package to enrich
this text with fontifications and other nice formatting before
displaying it to the user. This makes the documentation shown by ElDoc
look nicer on display.
this text with fontifications, hyperlinks and other nice formatting
before displaying it to the user. This makes the documentation shown by
ElDoc look nicer on display.
@item
In addition to enabling and enhancing other features and packages, Eglot

View file

@ -32,6 +32,12 @@ New key bindings: 'k' shuts down, 'r' reconnects, 'e' visits the events
buffer, 'w' shows workspace configuration, and 'RET' invokes
'eglot-describe-connection'.
** Eglot uses new built-in 'markdown-ts-mode' of Emacs 31 (bug#80127)
This means that on newer versions of Emacs the external
'markdown-mode.el' package does not need to be installed to render
Markdown content.
* Changes in Eglot 1.23 (2/4/2026)

View file

@ -721,8 +721,12 @@ This can be useful when using docker to run a language server.")
(executable-find command)))
(defun eglot--accepted-formats ()
(if (and (not eglot-prefer-plaintext) (fboundp 'gfm-view-mode))
["markdown" "plaintext"] ["plaintext"]))
(if (and (not eglot-prefer-plaintext)
(or (fboundp 'gfm-view-mode)
(and (fboundp 'markdown-ts-view-mode)
(treesit-grammar-location 'markdown))))
["markdown" "plaintext"]
["plaintext"]))
(defconst eglot--uri-path-allowed-chars
(let ((vec (copy-sequence url-path-allowed-chars)))
@ -2225,48 +2229,51 @@ Doubles as an indicator of snippet support."
(unless (bound-and-true-p yas-minor-mode) (yas-minor-mode 1))
(apply #'yas-expand-snippet args)))))
(defun eglot--format-markup (markup &optional mode)
(cl-defun eglot--format-markup
(markup &optional mode
&aux string lang render extract
(built-in (and (fboundp 'markdown-ts-view-mode)
(treesit-grammar-location 'markdown))))
"Format MARKUP according to LSP's spec.
MARKUP is either an LSP MarkedString or MarkupContent object."
(let (string render-mode language)
(cond ((stringp markup)
(setq string markup
render-mode (or mode 'gfm-view-mode)))
((setq language (plist-get markup :language))
;; Deprecated MarkedString
(setq string (concat "```" language "\n"
(plist-get markup :value) "\n```")
render-mode (or mode 'gfm-view-mode)))
(t
;; MarkupContent
(setq string (plist-get markup :value)
render-mode
(or mode
(pcase (plist-get markup :kind)
("markdown" 'gfm-view-mode)
("plaintext" 'text-mode)
(_ major-mode))))))
MARKUP is either an LSP MarkedString or MarkupContent object.
If MODE, force MODE to be used for fontifying MARKUP."
(cl-labels
((gfm-extract ()
;; For `gfm-view-mode', the `invisible' regions are set to
;; `markdown-markup'. Set them to 't' on extraction, since
;; this has actual meaning in the "*eldoc*" buffer where we're
;; taking this string (#bug79552).
(cl-loop with inhibit-read-only = t
for from = (point-min) then to
while (< from (point-max))
for inv = (get-text-property from 'invisible)
for to = (or (next-single-property-change from 'invisible)
(point-max))
when inv
do (put-text-property from to 'invisible t)))
(calc2 (forced-mode)
(cond
(forced-mode `(,forced-mode))
(built-in `(,#'markdown-ts-view-mode))
((fboundp 'gfm-view-mode) `(,#'gfm-view-mode #'gfm-extract))
(t `(#'text-mode))))
(calc (s &optional (forced-mode mode) &aux (x (calc2 forced-mode)))
(setq string s render (car x) extract (or (cadr x) #'buffer-string))))
(cond ((stringp markup) (calc string)) ; plain string
((setq lang (plist-get markup :language)) ; deprecated MarkedString
(calc (format "```%s\n%s\n```" lang (plist-get markup :value))))
(t (calc (plist-get markup :value) ; Assume MarkupContent
(or mode (pcase (plist-get markup :kind)
("markdown" nil)
("plaintext" 'text-mode)
(_ major-mode))))))
(with-temp-buffer
(setq-local markdown-fontify-code-blocks-natively t)
(insert string)
(let ((inhibit-message t)
(message-log-max nil))
(ignore-errors (delay-mode-hooks (funcall render-mode)))
(let ((inhibit-message t) (message-log-max nil))
(ignore-errors (delay-mode-hooks (funcall render)))
(font-lock-ensure)
(goto-char (point-min))
(let ((inhibit-read-only t))
;; If `render-mode' is `gfm-view-mode', the `invisible'
;; regions are set to `markdown-markup'. Set them to 't'
;; instead, since this has actual meaning in the "*eldoc*"
;; buffer where we're taking this string (#bug79552).
(cl-loop for from = (point) then to
while (< from (point-max))
for inv = (get-text-property from 'invisible)
for to = (or (next-single-property-change from 'invisible)
(point-max))
when inv
do (put-text-property from to 'invisible t)))
(string-trim (buffer-string))))))
(string-trim (funcall extract))))))
(defun eglot--read-server (prompt &optional dont-if-just-the-one)
"Read a running Eglot server from minibuffer using PROMPT.