Subject: bug#79185: [PATCH] Escape literal % characters in Eglot URIs

From: Steven Allen <[email-masked]>
Date: Wed, 06 Aug 2025 11:19:29 -0700

Tags: patch

Without this, a literal % in a file-name will be interpreted (by the
language server) as if it were a part of a percent-encoded sequence.

See Bug#78984 for context on why `url-path-allowed-chars' cannot be
changed to escape literal % characters.

In GNU Emacs 31.0.50 (build 1, x86_64-pc-linux-gnu, cairo version
 1.18.4) of 2025-08-06 built on Laptop
Repository revision: ca38912a544c73f120fa1ad15233fb8cc9224a44
Repository branch: makepkg
Windowing system distributor 'The X.Org Foundation', version 11.0.12101018
System Description: Arch Linux

Configured using:
 'configure
 'CPPFLAGS=-I/run/user/1000/build/emacs-git/src/mps-git/build/include '
 'LDFLAGS=-L/run/user/1000/build/emacs-git/src/mps-git/build/lib -Wl,-O1
 -Wl,--sort-common -Wl,--as-needed -Wl,-z,relro -Wl,-z,now
 -Wl,-z,pack-relative-relocs -flto=auto' --prefix=/usr --sysconfdir=/etc
 --libexecdir=/usr/lib --localstatedir=/var --mandir=/usr/share/man
 --with-gameuser=:games --with-modules --without-m17n-flt
 --without-selinux --without-pop --without-gconf --disable-gc-mark-trace
 --with-mps=yes --enable-link-time-optimization
 --with-native-compilation=yes --with-xinput2 --with-x-toolkit=no
 --without-toolkit-scroll-bars --without-xaw3d --without-gsettings
 --with-cairo-xcb --without-xft --with-sound=no --with-tree-sitter
 --without-gpm --without-compress-install
 '--program-transform-name=s/\([ec]tags\)/\1.emacs/'
 'CFLAGS=-march=native -mtune=native -O3 -pipe -fno-plt -fexceptions
 -Wp,-D_FORTIFY_SOURCE=3 -Wformat -Werror=format-security
 -fstack-clash-protection -fcf-protection -fomit-frame-pointer
 -fno-math-errno -fno-trapping-math -fno-math-errno -fno-trapping-math
 -flto=auto''

>From 9ff0c35ab2a69b542f0dfeec76eefa499729db3d Mon Sep 17 00:00:00 2001
From: Steven Allen <[email-masked]>
Date: Sat, 26 Jul 2025 13:14:19 -0700
Subject: [PATCH] Escape literal % characters in Eglot URIs

Otherwise, a literal % in a file-name will be interpreted (by the
language server) as if it were a part of a percent-encoded sequence.

See Bug#78984 for context on why `url-path-allowed-chars' cannot be
changed to escape literal % characters.

* lisp/progmodes/eglot.el (eglot--uri-path-allowed-chars): escape %,
remove the redundant variable definition.
* test/lisp/progmodes/eglot-tests.el (eglot-test-path-to-uri-escape):
test it.
---
 lisp/progmodes/eglot.el            | 7 +------
 test/lisp/progmodes/eglot-tests.el | 4 ++++
 2 files changed, 5 insertions(+), 6 deletions(-)

diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 4a7c525003c..53976a81e11 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -675,6 +675,7 @@ eglot--accepted-formats
 (defconst eglot--uri-path-allowed-chars
   (let ((vec (copy-sequence url-path-allowed-chars)))
     (aset vec ?: nil) ;; see github#639
+    (aset vec ?% nil) ;; see bug#78984
     vec)
   "Like `url-path-allowed-chars' but more restrictive.")
 
@@ -2008,12 +2009,6 @@ eglot--lsp-position-to-point
 
 
 ;;; More helpers
-(defconst eglot--uri-path-allowed-chars
-  (let ((vec (copy-sequence url-path-allowed-chars)))
-    (aset vec ?: nil) ;; see github#639
-    vec)
-  "Like `url-path-allowed-chars' but more restrictive.")
-
 (defun eglot--snippet-expansion-fn ()
   "Compute a function to expand snippets.
 Doubles as an indicator of snippet support."
diff --git a/test/lisp/progmodes/eglot-tests.el b/test/lisp/progmodes/eglot-tests.el
index 2b98da4134b..269de422690 100644
--- a/test/lisp/progmodes/eglot-tests.el
+++ b/test/lisp/progmodes/eglot-tests.el
@@ -1468,6 +1468,10 @@ eglot-test-path-to-uri-windows
   (should (string-suffix-p "c%3A/Users/Foo/bar.lisp"
                            (eglot-path-to-uri "c:/Users/Foo/bar.lisp"))))
 
+(ert-deftest eglot-test-path-to-uri-escape ()
+  (should (equal "file:///path/with%20%25%20funny%20%3F%20characters"
+                 (eglot-path-to-uri "/path/with % funny ? characters"))))
+
 (ert-deftest eglot-test-same-server-multi-mode ()
   "Check single LSP instance manages multiple modes in same project."
   (skip-unless (executable-find "clangd"))
-- 
2.50.1