Subject: Re: bug#61726: [PATCH] Eglot: Support positionEncoding capability

From: Augusto Stoffel <[email-masked]>
Date: Fri, 24 Feb 2023 19:08:14 +0100

I've attached a minimal functioning version of the patch.  I would
prefer to merge this and do all possible refinements discussed here at a
later stage.

>From 9b6794a729ac27b02472d68f849066c3a10297d4 Mon Sep 17 00:00:00 2001
From: Augusto Stoffel <[email-masked]>
Date: Thu, 23 Feb 2023 08:55:58 +0100
Subject: [PATCH] Eglot: support positionEncoding LSP capability

* lisp/progmodes/eglot.el(eglot-client-capabilities):  Announce the
new capability.
(eglot-bytewise-column, eglot-move-to-bytewise-column): New functions.
(eglot--managed-mode): Set 'eglot-current-column-function' and
'eglot-move-to-bytewise-column' appropriately.
---
 lisp/progmodes/eglot.el | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index b569c03e8c2..9a4d5733d0b 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -816,6 +816,9 @@ eglot-client-capabilities
                                        `(:valueSet
                                          [,@(mapcar
                                              #'car eglot--tag-faces)])))
+            :general
+            (list
+             :positionEncodings ["utf-32" "utf-8" "utf-16"])
             :experimental eglot--{})))
 
 (cl-defgeneric eglot-workspace-folders (server)
@@ -1441,6 +1444,10 @@ eglot--warn
 
 (defun eglot-current-column () (- (point) (line-beginning-position)))
 
+(defun eglot-bytewise-column ()
+  "Calculate current column using the LSP `utf-8' criterion."
+  (- (position-bytes (point)) (position-bytes (line-beginning-position))))
+
 (defvar eglot-current-column-function #'eglot-lsp-abiding-column
   "Function to calculate the current column.
 
@@ -1505,6 +1512,12 @@ eglot-move-to-lsp-abiding-column
             (forward-char (/ (if (> diff 0) (1+ diff) (1- diff)) 2))
           (end-of-buffer (cl-return eob-err))))))
 
+(defun eglot-move-to-bytewise-column (column)
+  "Move to COLUMN as computed using the LSP `utf-8' criterion."
+  (goto-char (min (byte-to-position
+                   (+ (position-bytes (line-beginning-position)) column))
+                  (line-end-position))))
+
 (defun eglot--lsp-position-to-point (pos-plist &optional marker)
   "Convert LSP position POS-PLIST to Emacs point.
 If optional MARKER, return a marker instead"
@@ -1758,6 +1771,14 @@ eglot--managed-mode
   :init-value nil :lighter nil :keymap eglot-mode-map
   (cond
    (eglot--managed-mode
+    (pcase (plist-get (eglot--capabilities (eglot-current-server))
+                      :positionEncoding)
+      ("utf-32"
+       (eglot--setq-saving eglot-current-column-function #'eglot-current-column)
+       (eglot--setq-saving eglot-move-to-column-function #'eglot-move-to-column))
+      ("utf-8"
+       (eglot--setq-saving eglot-current-column-function #'eglot-bytewise-column)
+       (eglot--setq-saving eglot-move-to-column-function #'eglot-move-to-bytewise-column)))
     (add-hook 'after-change-functions 'eglot--after-change nil t)
     (add-hook 'before-change-functions 'eglot--before-change nil t)
     (add-hook 'kill-buffer-hook #'eglot--managed-mode-off nil t)
-- 
2.39.2

For the 2 new functions I've followed current naming scheme, replacing
"lsp-abiding" by "bytewise".  We can then get rid of all the suboptimal
nomenclature (lsp-abiding, column, bytewise) in one go at a later stage.

I also didn't attempt to make 'eglot-move-to-bytewise-column' and
'eglot-bytewise-column' "correct" in the cases where 'json-serialize'
throws an error.  I think it's clear that Eli and I have different
definitions "correctness" in this context, but Eli's proposal requires
substantial further work, including recovering from possible errors
raising from 'json-serialize'.

Lastly, I used 'line-beginning-position' naively in the 2 new function
because there are at least 4 other pre-existing naive uses in eglot.el.
This refinement deserves a patch of its own, probably using something on
the lines of

  (defalias eglot--pos-bol (if (fboundp 'pos-bol) 'pos-bol ...))