From 93cd0f1ea547ad72f865a6eebad3383d148967dd Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 26 Oct 2026 14:27:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(lsp):=20=E5=A2=9E=E5=8A=A0=20UTF-16=20?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E5=BE=80=E8=BF=94=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - viper/lspPosition:line/character -> offset -> line/character - probe Gate 1:随机 10k 个有效位置往返,覆盖 CRLF/中文/emoji --- App/lib/lsp/LspServer.vp | 27 +++++++++++++++++++++++++++ Tools/lsp/probe.py | 23 +++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/App/lib/lsp/LspServer.vp b/App/lib/lsp/LspServer.vp index 5a0d518..7b76504 100644 --- a/App/lib/lsp/LspServer.vp +++ b/App/lib/lsp/LspServer.vp @@ -198,6 +198,31 @@ def _HandleDocument(pool: memhub.MemManager*, tr: LspTransport.LspTransport*, return LspRpc.WriteResponse(pool, tr, id_node, result) +def _HandlePosition(pool: memhub.MemManager*, tr: LspTransport.LspTransport*, + docs: LspDocuments.LspDocuments*, id_node: json.JsonValue*, + params: json.JsonValue*) -> int: + uri: str = None + if params is not None: + uri = LspRpc.GetStringField(params, "uri") + doc: LspDocuments.LspDocument* = docs.Find(uri) + if doc is None or doc.index is None: + return LspRpc.WriteResponse(pool, tr, id_node, json.null(pool)) + line: int = LspRpc.GetIntField(params, "line", 0) + character: int = LspRpc.GetIntField(params, "character", 0) + offset: sizet = doc.index.PositionToOffset(line, character) + out_line: int = 0 + out_char: int = 0 + if doc.index.OffsetToPosition(offset, &out_line, &out_char) != 0: + return -1 + result: json.JsonValue* = json.object(pool) + if result is None: + return -1 + json.object_set(pool, result, "offset", json.int_val(pool, int64(offset))) + json.object_set(pool, result, "line", json.int_val(pool, int64(out_line))) + json.object_set(pool, result, "character", json.int_val(pool, int64(out_char))) + return LspRpc.WriteResponse(pool, tr, id_node, result) + + def Run(pool: memhub.MemBuddy*, perm_pool: memhub.MemBuddy*) -> int: os._mbuddy = perm_pool os._win32._mbuddy = perm_pool @@ -262,6 +287,8 @@ def Run(pool: memhub.MemBuddy*, perm_pool: memhub.MemBuddy*) -> int: rc = _HandleStats(pool, tr, docs, ws, id_node) elif string.strcmp(method, "viper/lspDocument") == 0: rc = _HandleDocument(pool, tr, docs, id_node, params) + elif string.strcmp(method, "viper/lspPosition") == 0: + rc = _HandlePosition(pool, tr, docs, id_node, params) elif string.strcmp(method, LspProtocol.LSP_METHOD_SHUTDOWN) == 0: shutdown_requested = 1 rc = LspRpc.WriteResponse(pool, tr, id_node, json.null(pool)) diff --git a/Tools/lsp/probe.py b/Tools/lsp/probe.py index f16a83f..3d112c3 100644 --- a/Tools/lsp/probe.py +++ b/Tools/lsp/probe.py @@ -7,6 +7,7 @@ import argparse import json import os import queue +import random import subprocess import sys import threading @@ -181,6 +182,28 @@ def documents(exe: str) -> int: if not isinstance(doc, dict) or doc.get("text") != text or doc.get("version") != 1: raise AssertionError(f"initial document text/version mismatch: {doc}") + lines = text.splitlines() + choices: list[tuple[int, int]] = [] + for line_no, line_text in enumerate(lines): + offsets = [0] + units = 0 + for ch in line_text: + units += 2 if ord(ch) > 0xFFFF else 1 + offsets.append(units) + for character in offsets: + choices.append((line_no, character)) + random.seed(1234) + for _ in range(10000): + line_no, character = random.choice(choices) + result = client.request( + "viper/lspPosition", + {"uri": uri, "line": line_no, "character": character}, + ).get("result") + if not isinstance(result, dict) or result.get("line") != line_no or result.get("character") != character: + raise AssertionError( + f"UTF-16 round-trip failed for ({line_no},{character}): {result}" + ) + client.notify( "textDocument/didChange", {