From 7b4ed9736f8ab21adeee0205546d160313ab8b8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= Date: Mon, 12 Nov 2018 22:29:38 +0000 Subject: [PATCH] Fix #124: add ability to move to LSP-precise columns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also close #125. Idea and much of design contributed by MichaƂ Krzywkowski This introduces the variable eglot-move-to-column-function. According to the standard, LSP column/character offsets are based on a count of UTF-16 code units, not actual visual columns. So when LSP says position 3 of a line containing just \"aXbc\", where X is a multi-byte character, it actually means `b', not `c'. This is what the function `eglot-move-to-lsp-abiding-column' does. However, many servers don't follow the spec this closely, and thus this variable should be set to `move-to-column' in buffers managed by those servers. * eglot.el (eglot-move-to-column-function): New variable. (eglot-move-to-lsp-abiding-column): New function. (eglot--lsp-position-to-point): Use eglot-move-to-column-function. --- eglot.el | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/eglot.el b/eglot.el index 576d7f39..f7b9c869 100644 --- a/eglot.el +++ b/eglot.el @@ -728,16 +728,43 @@ CONNECT-ARGS are passed as additional arguments to :character (- (goto-char (or pos (point))) (line-beginning-position))))) +(defvar eglot-move-to-column-function #'move-to-column + "How to move to a column reported by the LSP server. + +According to the standard, LSP column/character offsets are based +on a count of UTF-16 code units, not actual visual columns. So +when LSP says position 3 of a line containing just \"aXbc\", +where X is a multi-byte character, it actually means `b', not +`c'. This is what the function +`eglot-move-to-lsp-abiding-column' does. + +However, many servers don't follow the spec this closely, and +thus this variable should be set to `move-to-column' in buffers +managed by those servers.") + +(defun eglot-move-to-lsp-abiding-column (column) + "Move to COLUMN abiding by the LSP spec." + (cl-loop + initially (move-to-column column) + with lbp = (line-beginning-position) + for diff = (- column + (/ (- (length (encode-coding-region lbp (point) 'utf-16 t)) + 2) + 2)) + until (zerop diff) + for offset = (max 1 (abs (/ diff 2))) + do (if (> diff 0) (forward-char offset) (backward-char offset)))) + (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" - (save-excursion (goto-char (point-min)) - (forward-line (min most-positive-fixnum - (plist-get pos-plist :line))) - (forward-char (min (plist-get pos-plist :character) - (- (line-end-position) - (line-beginning-position)))) - (if marker (copy-marker (point-marker)) (point)))) + (save-excursion + (goto-char (point-min)) + (forward-line (min most-positive-fixnum + (plist-get pos-plist :line))) + (unless (eobp) ;; if line was excessive leave point at eob + (funcall eglot-move-to-column-function (plist-get pos-plist :character))) + (if marker (copy-marker (point-marker)) (point)))) (defun eglot--path-to-uri (path) "URIfy PATH." @@ -1040,7 +1067,7 @@ Uses THING, FACE, DEFS and PREPEND." (priority . ,(+ 50 i)) (keymap . ,(let ((map (make-sparse-keymap))) (define-key map [mouse-1] - (eglot--mouse-call 'eglot-code-actions)) + (eglot--mouse-call 'eglot-code-actions)) map)))))