diff --git a/eglot-tests.el b/eglot-tests.el index 6cea929b..3dfcc511 100644 --- a/eglot-tests.el +++ b/eglot-tests.el @@ -600,6 +600,15 @@ Pass TIMEOUT to `eglot--with-timeout'." (should-not (eglot--server-capable :foobarbaz)) (should-not (eglot--server-capable :textDocumentSync :foobarbaz)))) + +(ert-deftest issue-124 () + "Test LSP's UTF-16 quirks." + (with-current-buffer (find-file-noselect "fixtures/utf16.cpp") + (should (eql (eglot--lsp-position-to-point '(:character 8 :line 2)) 22)) + (should (eql (eglot--lsp-position-to-point '(:character 19 :line 2)) 33)) + (should (eql (eglot--lsp-position-to-point '(:character 50 :line 2)) 63)) + (should (eql (eglot--lsp-position-to-point '(:character 60 :line 2)) 73)))) + (provide 'eglot-tests) ;;; eglot-tests.el ends here diff --git a/eglot.el b/eglot.el index e1771745..8b556e26 100644 --- a/eglot.el +++ b/eglot.el @@ -728,16 +728,31 @@ CONNECT-ARGS are passed as additional arguments to :character (- (goto-char (or pos (point))) (line-beginning-position))))) +(defun eglot--lsp-char-to-column (lsp-charpos) + "Helper for `eglot--lsp-position-to-point'." + (let ((line (buffer-substring-no-properties (line-beginning-position) + (line-end-position)))) + (with-temp-buffer + (save-excursion (insert line)) + (cl-loop with start-filepos = + (bufferpos-to-filepos (point) 'exact 'utf-16-unix) + for current-pos = start-filepos + then (bufferpos-to-filepos (point) 'exact 'utf-16-unix) + while (and (not (eolp)) + (< (/ (- current-pos start-filepos) 2) + lsp-charpos)) + do (forward-char) + finally do (cl-return (1- (point))))))) + (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))) + (forward-char (eglot--lsp-char-to-column (plist-get pos-plist :character))) + (if marker (copy-marker (point-marker)) (point)))) (defun eglot--path-to-uri (path) "URIfy PATH." @@ -1392,12 +1407,12 @@ DUMMY is ignored." (let* ((rich-identifier (car (member identifier eglot--xref-known-symbols))) (definitions - (if rich-identifier - (get-text-property 0 :locations rich-identifier) - (jsonrpc-request (eglot--current-server-or-lose) - :textDocument/definition - (get-text-property - 0 :textDocumentPositionParams identifier)))) + (if rich-identifier + (get-text-property 0 :locations rich-identifier) + (jsonrpc-request (eglot--current-server-or-lose) + :textDocument/definition + (get-text-property + 0 :textDocumentPositionParams identifier)))) (locations (and definitions (if (vectorp definitions) definitions (vector definitions))))) diff --git a/fixtures/utf16.cpp b/fixtures/utf16.cpp new file mode 100644 index 00000000..91b8a28f Binary files /dev/null and b/fixtures/utf16.cpp differ