fix(lsp): 修正 define/extern 声明的名称偏移

声明节点的位置可能落在修饰符或行首;按行内整词重新定位名称,并让无冒号的 extern 头解析出函数名。语义高亮对不匹配源文本的声明 token 直接跳过。
This commit is contained in:
TermiNexus committed 2026-10-26 18:51:01 +08:00
1 parent 08c817f5ec
commit 1183f92344
5 files changed
+103 -3

No files matched your search

+19 -1
View File
@@ -136,6 +136,18 @@ def _Overlaps(last: LspSemToken*, t: LspSemToken*) -> int:
return 0
def _SymbolTokenValid(pool: memhub.MemManager*, a: LspSymbols.LspAnalysis*,
t: LspSemToken*, name: str) -> int:
if t is None or name is None:
return 0
sc: int = LspSource.Utf16ColToByte(a.doc, t.sl, t.sc)
ec: int = LspSource.Utf16ColToByte(a.doc, t.el, t.ec)
txt: str = LspSource.SliceBytes(pool, a.doc, t.sl, sc, t.el, ec)
if txt is None:
return 0
return 1 if string.strcmp(txt, name) == 0 else 0
def _AstStream(pool: memhub.MemManager*,
a: LspSymbols.LspAnalysis*) -> list[LspSemToken*]*:
out: list[LspSemToken*]* = list[LspSemToken*](pool, 64)
@@ -146,7 +158,11 @@ def _AstStream(pool: memhub.MemManager*,
while si < int(len(syms)) or oi < int(len(occs)):
sym_t: LspSemToken* = None
if si < int(len(syms)):
sym_t = _TokenForSymbol(pool, syms.get(sizet(si)))
s: LspSymbols.LspSymbol* = syms.get(sizet(si))
sym_t = _TokenForSymbol(pool, s)
if sym_t is not None and s is not None:
if _SymbolTokenValid(pool, a, sym_t, s.name) == 0:
sym_t.typ = -1
occ_t: LspSemToken* = None
if oi < int(len(occs)):
occ_t = _TokenForOcc(pool, occs.get(sizet(oi)))
@@ -301,6 +317,8 @@ def _Encode(pool: memhub.MemManager*, out: json.JsonValue*,
li += 1
if t is None:
continue
if t.typ < 0:
continue
if has_range != 0 and _InRange(t, start_line, end_line) == 0:
continue
if _Overlaps(last, t) != 0:
+9 -2
View File
@@ -281,13 +281,20 @@ def FindHeaderColon(doc: LspDocuments.LspDocument*, line: int, byte_col: int,
def FindDeclName(doc: LspDocuments.LspDocument*, line: int, byte_col: int,
name: str, want_class: int,
out_line: int*, out_col: int*) -> int:
"""Locate the declared identifier inside a def/class header; 1 when found."""
"""Locate the declared identifier inside a def/class header; 1 when found.
Declaration-only forms (e.g. `extern lib name(...) -> T`) have no header colon, so the scan
falls back to the end of the declaration line.
"""
if doc is None or doc.index is None or doc.index.Count() <= 0 or name is None:
return 0
if line < 0:
return 0
el: int = 0
ec: int = 0
if FindHeaderColon(doc, line, byte_col, &el, &ec) == 0:
return 0
el = line
ec = LineByteLength(doc, line)
start: sizet = doc.index.LineStart(line) + sizet(byte_col)
end: sizet = doc.index.LineStart(el) + sizet(ec)
if end <= start:
+54
View File
@@ -403,6 +403,57 @@ def _AddAttrOcc(pool: memhub.MemManager*, a: LspAnalysis*, scope: LspScope*,
return o
def _RangeTextIsName(pool: memhub.MemManager*, a: LspAnalysis*,
r: LspSource.LspRange*, name: str) -> int:
if r is None or name is None:
return 0
sc: int = LspSource.Utf16ColToByte(a.doc, r.sl, r.sc)
ec: int = LspSource.Utf16ColToByte(a.doc, r.el, r.ec)
txt: str = LspSource.SliceBytes(pool, a.doc, r.sl, sc, r.el, ec)
if txt is None:
return 0
return 1 if string.strcmp(txt, name) == 0 else 0
def _NameOnLine(pool: memhub.MemManager*, a: LspAnalysis*, line1: int,
start_col: int, name: str) -> LspSource.LspRange*:
"""Find the declared name as a whole word on its line.
Soft-keyword declarations (`define X = ...`, `static X`, ...) and `extern lib name(...)`
record the statement or modifier position, not the identifier position. The name is the
first whole-word match at/after the recorded column, else the first on the line.
"""
if a is None or a.doc is None or name is None or line1 <= 0:
return None
line0: int = line1 - 1
if line0 >= a.doc.index.Count():
return None
limit: int = LspSource.LineByteLength(a.doc, line0)
line_start: sizet = a.doc.index.LineStart(line0)
off: sizet = line_start
end: sizet = line_start + sizet(limit)
nlen: sizet = string.strlen(name)
first_col: int = -1
while off + nlen <= end:
ch: char = a.doc.text[off]
if LspSource._IsIdentStart(ch) != 0:
tok_start: sizet = off
while off < end and LspSource._IsIdentChar(a.doc.text[off]) != 0:
off += 1
if off - tok_start == nlen:
if string.strncmp(str(a.doc.text + tok_start), name, nlen) == 0:
col: int = int(tok_start - line_start)
if col >= start_col:
return LspSource.ByteSpan(pool, a.doc, line0, col, int(nlen))
if first_col < 0:
first_col = col
continue
off += 1
if first_col >= 0:
return LspSource.ByteSpan(pool, a.doc, line0, first_col, int(nlen))
return None
def _Declare(pool: memhub.MemManager*, a: LspAnalysis*, scope: LspScope*,
name: str, kind: int, decl: ast.AST*, line: int, col: int,
detail: str) -> LspSymbol*:
@@ -417,6 +468,9 @@ def _Declare(pool: memhub.MemManager*, a: LspAnalysis*, scope: LspScope*,
s.file = a.file
s.detail = detail
s.sel = _NameSpan(pool, a.doc, line, col, name)
if s.sel is None or _RangeTextIsName(pool, a, s.sel, name) == 0:
if decl is None or (decl.kind() != ast.ASTKind.Import and decl.kind() != ast.ASTKind.ImportFrom):
s.sel = _NameOnLine(pool, a, line, col, name)
s.rng = s.sel
_AddSymbol(a, scope, s)
return s
+5
View File
@@ -22,3 +22,8 @@ def use(x: int) -> int:
# gate5 comment
t: Thing = Thing()
return t.field + x
define APP_NAME = "Gate5"
extern add_c2 add(a: int, b: int) -> int
+16
View File
@@ -896,6 +896,22 @@ def gate5(exe: str) -> int:
if not _find_token(tokens, number_pos["line"], number_pos["character"], 10):
raise AssertionError(f"missing number token: {tokens[:20]}")
define_pos = _position(text, "APP_NAME")
if not _find_token(tokens, define_pos["line"], define_pos["character"], 5):
raise AssertionError(f"define name token is misplaced: {define_pos} {tokens}")
for token in tokens:
if token["line"] == define_pos["line"] and token["character"] == 0:
if token["length"] == len("APP_NAME"):
raise AssertionError(f"define name token is at column 0: {token}")
extern_pos = _position(text, "add(a: int, b: int)")
if not _find_token(tokens, extern_pos["line"], extern_pos["character"], 2):
raise AssertionError(f"extern name token is misplaced: {extern_pos} {tokens}")
for token in tokens:
if token["line"] == extern_pos["line"] and token["character"] == 0:
if token["length"] == len("add"):
raise AssertionError(f"extern name token is at column 0: {token}")
ranged = client.request(
"textDocument/semanticTokens/range",
{