fix(lsp): 修正 define/extern 声明的名称偏移
声明节点的位置可能落在修饰符或行首;按行内整词重新定位名称,并让无冒号的 extern 头解析出函数名。语义高亮对不匹配源文本的声明 token 直接跳过。
This commit is contained in:
1 parent
08c817f5ec
commit
1183f92344
5 files changed
+103
-3
No files matched your search
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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",
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user