feat(lsp): 跨模块语义高亮与 project.vpj includes 生效
语义 token 通过索引解析导入符号/成员(class/function/method/property/constant/macro);采纳 project.vpj 后刷新索引 roots;语义 token 数组改为 O(1) 追加,避免大文件序列化退化。
This commit is contained in:
1 parent
7f90ec984c
commit
879cf7ab9f
7 files changed
+336
-30
No files matched your search
+85
-4
@@ -61,6 +61,7 @@ class LspTarget:
|
||||
container: str
|
||||
sel: LspSource.LspRange*
|
||||
has_sel: int
|
||||
is_macro: int
|
||||
|
||||
def __new__(self, pool: memhub.MemManager*) -> rawptr:
|
||||
p: LspTarget* = (LspTarget*)(pool.alloc(LspTarget.__sizeof__()))
|
||||
@@ -82,6 +83,8 @@ class LspIndex:
|
||||
refs_built: int
|
||||
last_path: str
|
||||
last_entry: LspFileEntry*
|
||||
mod_names: list[str]*
|
||||
mod_paths: list[str]*
|
||||
|
||||
def __new__(self, owner: memhub.MemManager*) -> rawptr:
|
||||
p: LspIndex* = (LspIndex*)(owner.alloc(LspIndex.__sizeof__()))
|
||||
@@ -94,6 +97,8 @@ _index: LspIndex* = None
|
||||
# During bulk occurrence indexing, resolve only through already-indexed files. On-demand parsing
|
||||
# here would repeat work per occurrence and exhaust the scratch arena.
|
||||
_bulk_resolve: int = 0
|
||||
# Semantic-token classification resolves many imports; it must not parse target files on demand.
|
||||
_resolve_only: int = 0
|
||||
|
||||
|
||||
def Init(owner: memhub.MemManager*) -> int:
|
||||
@@ -124,6 +129,17 @@ def Invalidate():
|
||||
_index.refs_built = 0
|
||||
|
||||
|
||||
def BeginResolveOnly():
|
||||
"""Resolve imports through the built index only (no on-demand parses)."""
|
||||
global _resolve_only
|
||||
_resolve_only = 1
|
||||
|
||||
|
||||
def EndResolveOnly():
|
||||
global _resolve_only
|
||||
_resolve_only = 0
|
||||
|
||||
|
||||
def _Suffix(pool: memhub.MemManager*, s: str, suffix: str) -> str:
|
||||
if s is None or suffix is None:
|
||||
return None
|
||||
@@ -233,6 +249,7 @@ def _CopySymbol(pool: memhub.MemManager*, s: LspSymbols.LspSymbol*,
|
||||
return None
|
||||
c.name = LspPools.DupStr(pool, s.name)
|
||||
c.kind = s.kind
|
||||
c.is_macro = s.is_macro
|
||||
c.detail = LspSymbols._CopyStr(pool, s.detail, LspSymbols.LSP_SIGNATURE_CAP)
|
||||
c.doc = LspSymbols._CopyStr(pool, s.doc, LspSymbols.LSP_DOC_CAP)
|
||||
c.file = file
|
||||
@@ -337,6 +354,8 @@ def Ensure(ws: LspWorkspace.LspWorkspace*, table_pool: memhub.MemManager*) -> in
|
||||
idx.stale = 0
|
||||
idx.last_path = None
|
||||
idx.last_entry = None
|
||||
idx.mod_names = list[str](idx.tree.mb, 16)
|
||||
idx.mod_paths = list[str](idx.tree.mb, 16)
|
||||
if idx.files is None or idx.roots is None:
|
||||
return -1
|
||||
if ws is not None:
|
||||
@@ -617,6 +636,27 @@ def SetRoots(ws: LspWorkspace.LspWorkspace*) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def RefreshRoots(ws: LspWorkspace.LspWorkspace*) -> int:
|
||||
"""Rebuild roots after the workspace adopts a project.vpj deeper in the tree."""
|
||||
idx: LspIndex* = _index
|
||||
if idx is None or ws is None:
|
||||
return -1
|
||||
if idx.roots is None:
|
||||
idx.roots = list[str](idx.tree.mb, 8)
|
||||
if idx.roots is None:
|
||||
return -1
|
||||
idx.roots.clear()
|
||||
if idx.mod_names is not None:
|
||||
idx.mod_names.clear()
|
||||
if idx.mod_paths is not None:
|
||||
idx.mod_paths.clear()
|
||||
_AddRoot(idx, ws.SourceDir())
|
||||
_AddRootsString(idx, ws.Includes())
|
||||
idx.stale = 1
|
||||
idx.refs_built = 0
|
||||
return 0
|
||||
|
||||
|
||||
def FileEntry(path: str) -> LspFileEntry*:
|
||||
idx: LspIndex* = _index
|
||||
if idx is None or path is None or idx.files is None:
|
||||
@@ -644,7 +684,7 @@ def ResolveInFile(pool: memhub.MemManager*, table_pool: memhub.MemManager*,
|
||||
s: LspSymbols.LspSymbol* = FindSymbol(path, container, name)
|
||||
if s is not None:
|
||||
return s
|
||||
if _bulk_resolve != 0:
|
||||
if _bulk_resolve != 0 or _resolve_only != 0:
|
||||
return None
|
||||
text: str = LspFiles.ReadSource(pool, path)
|
||||
if text is None:
|
||||
@@ -701,6 +741,7 @@ def _SymbolTarget(pool: memhub.MemManager*, s: LspSymbols.LspSymbol*) -> LspTarg
|
||||
t.file = s.file
|
||||
t.name = s.name
|
||||
t.kind = s.kind
|
||||
t.is_macro = s.is_macro
|
||||
t.detail = s.detail
|
||||
t.doc = s.doc
|
||||
t.container = s.container
|
||||
@@ -794,18 +835,32 @@ def _ModulePath(ws: LspWorkspace.LspWorkspace*, pool: memhub.MemManager*,
|
||||
idx: LspIndex* = _index
|
||||
if idx is None or module is None or idx.roots is None:
|
||||
return None
|
||||
if idx.mod_names is None:
|
||||
idx.mod_names = list[str](idx.tree.mb, 16)
|
||||
idx.mod_paths = list[str](idx.tree.mb, 16)
|
||||
if idx.mod_names is not None:
|
||||
i: int = 0
|
||||
while i < int(len(idx.mod_names)):
|
||||
if string.strcmp(idx.mod_names.get(sizet(i)), module) == 0:
|
||||
return idx.mod_paths.get(sizet(i))
|
||||
i += 1
|
||||
rel: str = _RelPath(pool, module)
|
||||
if rel is None:
|
||||
return None
|
||||
i: int = 0
|
||||
found: str = None
|
||||
i = 0
|
||||
while i < int(len(idx.roots)):
|
||||
root: str = idx.roots.get(sizet(i))
|
||||
i += 1
|
||||
p: str = None
|
||||
k: int = 0
|
||||
if _TryPath(pool, root, rel, &p, &k) != 0:
|
||||
return p
|
||||
return None
|
||||
found = p
|
||||
break
|
||||
if idx.mod_names is not None and int(len(idx.mod_names)) < 256:
|
||||
idx.mod_names.append(LspPools.DupStr(idx.tree.mb, module))
|
||||
idx.mod_paths.append(LspPools.DupStr(idx.tree.mb, found))
|
||||
return found
|
||||
|
||||
|
||||
def _ChainFromModule(ws: LspWorkspace.LspWorkspace*, pool: memhub.MemManager*,
|
||||
@@ -899,6 +954,32 @@ def ResolveSymbol(ws: LspWorkspace.LspWorkspace*, pool: memhub.MemManager*,
|
||||
return _SymbolTarget(pool, s)
|
||||
|
||||
|
||||
def ResolveMembers(pool: memhub.MemManager*, table_pool: memhub.MemManager*,
|
||||
base: LspTarget*, rest: str) -> LspTarget*:
|
||||
"""Walk class members from an already-resolved base target (no import re-resolution)."""
|
||||
if base is None or rest is None:
|
||||
return None
|
||||
if base.kind != LspSymbols.LSP_KIND_CLASS or base.file is None or base.name is None:
|
||||
return None
|
||||
container: str = base.name
|
||||
file: str = base.file
|
||||
segs: str = rest
|
||||
while segs is not None:
|
||||
h: str = None
|
||||
r: str = None
|
||||
LspSymbols._SplitHead(pool, segs, &h, &r)
|
||||
if h is None:
|
||||
return None
|
||||
m: LspSymbols.LspSymbol* = ResolveInFile(pool, table_pool, file, container, h)
|
||||
if m is None:
|
||||
return None
|
||||
if r is None:
|
||||
return _SymbolTarget(pool, m)
|
||||
container = m.name
|
||||
segs = r
|
||||
return None
|
||||
|
||||
|
||||
def ResolveChain(ws: LspWorkspace.LspWorkspace*, pool: memhub.MemManager*,
|
||||
table_pool: memhub.MemManager*,
|
||||
from_file: str, head: LspSymbols.LspSymbol*,
|
||||
|
||||
+182
-25
@@ -6,6 +6,7 @@ import ast
|
||||
import lib.lsp.LspDocuments as LspDocuments
|
||||
import lib.lsp.LspSource as LspSource
|
||||
import lib.lsp.LspSymbols as LspSymbols
|
||||
import lib.lsp.LspIndex as LspIndex
|
||||
import lib.lsp.LspWorkspace as LspWorkspace
|
||||
import lib.lsp.LspNavigation as LspNavigation
|
||||
import lib.lsp.LspTransport as LspTransport
|
||||
@@ -132,30 +133,145 @@ def _ModsFor(s: LspSymbols.LspSymbol*) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _TokenForSymbol(pool: memhub.MemManager*,
|
||||
class LspSemCtx:
|
||||
ws: LspWorkspace.LspWorkspace*
|
||||
table_pool: memhub.MemManager*
|
||||
file: str
|
||||
sym_keys: list[LspSymbols.LspSymbol*]*
|
||||
sym_tgts: list[LspIndex.LspTarget*]*
|
||||
chain_heads: list[LspSymbols.LspSymbol*]*
|
||||
chain_rests: list[str]*
|
||||
chain_tgts: list[LspIndex.LspTarget*]*
|
||||
|
||||
def __new__(self, pool: memhub.MemManager*) -> rawptr:
|
||||
p: LspSemCtx* = (LspSemCtx*)(pool.alloc(LspSemCtx.__sizeof__()))
|
||||
if p:
|
||||
string.memset(p, 0, LspSemCtx.__sizeof__())
|
||||
return rawptr(p)
|
||||
|
||||
|
||||
def _TypeForTarget(t: LspIndex.LspTarget*) -> int:
|
||||
if t is None:
|
||||
return -1
|
||||
if t.kind == LspSymbols.LSP_KIND_MODULE:
|
||||
return ST_NAMESPACE
|
||||
if t.kind == LspSymbols.LSP_KIND_CLASS:
|
||||
return ST_CLASS
|
||||
if t.kind == LspSymbols.LSP_KIND_FUNCTION:
|
||||
return ST_MACRO if t.is_macro != 0 else ST_FUNCTION
|
||||
if t.kind == LspSymbols.LSP_KIND_METHOD:
|
||||
return ST_MACRO if t.is_macro != 0 else ST_METHOD
|
||||
if t.kind == LspSymbols.LSP_KIND_FIELD:
|
||||
return ST_PROPERTY
|
||||
return ST_VARIABLE
|
||||
|
||||
|
||||
def _ModsForTarget(t: LspIndex.LspTarget*) -> int:
|
||||
if t is not None and t.kind == LspSymbols.LSP_KIND_CONSTANT:
|
||||
return 2
|
||||
return 0
|
||||
|
||||
|
||||
define LSP_SEM_CACHE_SIZE = 4096
|
||||
define LSP_SEM_CACHE_MASK = 4095
|
||||
|
||||
|
||||
def _SemHash(s: str) -> sizet:
|
||||
if s is None:
|
||||
return 0
|
||||
h: uint64 = uint64(1469598103934665603)
|
||||
i: sizet = 0
|
||||
while s[i] != 0:
|
||||
h = (h ^ uint64(s[i])) * uint64(1099511628211)
|
||||
i += 1
|
||||
return sizet(h & uint64(LSP_SEM_CACHE_MASK))
|
||||
|
||||
|
||||
def _CachedSymbol(ctx: LspSemCtx*, pool: memhub.MemManager*,
|
||||
sym: LspSymbols.LspSymbol*) -> LspIndex.LspTarget*:
|
||||
if ctx is None or sym is None:
|
||||
return None
|
||||
slot: sizet = _SemHash(sym.name)
|
||||
if sym.import_mod is not None:
|
||||
slot = (slot ^ _SemHash(sym.import_mod)) & sizet(LSP_SEM_CACHE_MASK)
|
||||
if ctx.sym_keys.get(slot) == sym:
|
||||
return ctx.sym_tgts.get(slot)
|
||||
t: LspIndex.LspTarget* = LspIndex.ResolveSymbol(
|
||||
ctx.ws, pool, ctx.table_pool, ctx.file, sym, 0)
|
||||
ctx.sym_keys.set(slot, sym)
|
||||
ctx.sym_tgts.set(slot, t)
|
||||
return t
|
||||
|
||||
|
||||
def _CachedChain(ctx: LspSemCtx*, pool: memhub.MemManager*,
|
||||
head: LspSymbols.LspSymbol*,
|
||||
rest: str) -> LspIndex.LspTarget*:
|
||||
if ctx is None or head is None or rest is None:
|
||||
return None
|
||||
slot: sizet = (_SemHash(rest) ^ _SemHash(head.name)) & sizet(LSP_SEM_CACHE_MASK)
|
||||
if ctx.chain_heads.get(slot) == head:
|
||||
cached: str = ctx.chain_rests.get(slot)
|
||||
if cached is not None and string.strcmp(cached, rest) == 0:
|
||||
return ctx.chain_tgts.get(slot)
|
||||
t: LspIndex.LspTarget* = None
|
||||
if head.import_kind == LspSymbols.LSP_IMPORT_FROM:
|
||||
base: LspIndex.LspTarget* = _CachedSymbol(ctx, pool, head)
|
||||
if base is not None:
|
||||
t = LspIndex.ResolveMembers(pool, ctx.table_pool, base, rest)
|
||||
else:
|
||||
t = LspIndex.ResolveChain(ctx.ws, pool, ctx.table_pool, ctx.file, head, rest)
|
||||
ctx.chain_heads.set(slot, head)
|
||||
ctx.chain_rests.set(slot, rest)
|
||||
ctx.chain_tgts.set(slot, t)
|
||||
return t
|
||||
|
||||
|
||||
def _TokenForSymbol(ctx: LspSemCtx*, pool: memhub.MemManager*,
|
||||
s: LspSymbols.LspSymbol*) -> LspSemToken*:
|
||||
if s is None or s.sel is None:
|
||||
return None
|
||||
t: LspSemToken* = _NewTok(pool, s.sel.sl, s.sel.sc, s.sel.el, s.sel.ec,
|
||||
_TypeFor(s))
|
||||
if t is not None:
|
||||
t.mods = _ModsFor(s)
|
||||
return t
|
||||
typ: int = _TypeFor(s)
|
||||
mods: int = _ModsFor(s)
|
||||
if s.import_kind != LspSymbols.LSP_IMPORT_NONE:
|
||||
# Import declarations are colored by the imported declaration, not the local alias kind.
|
||||
t: LspIndex.LspTarget* = _CachedSymbol(ctx, pool, s)
|
||||
if t is not None:
|
||||
typ = _TypeForTarget(t)
|
||||
mods = _ModsForTarget(t)
|
||||
tok: LspSemToken* = _NewTok(pool, s.sel.sl, s.sel.sc, s.sel.el, s.sel.ec, typ)
|
||||
if tok is not None:
|
||||
tok.mods = mods
|
||||
return tok
|
||||
|
||||
|
||||
def _TokenForOcc(pool: memhub.MemManager*,
|
||||
def _TokenForOcc(ctx: LspSemCtx*, pool: memhub.MemManager*,
|
||||
o: LspSymbols.LspOccurrence*) -> LspSemToken*:
|
||||
if o is None:
|
||||
return None
|
||||
if o.sym is None:
|
||||
typ: int = -1
|
||||
mods: int = 0
|
||||
if o.sym is not None:
|
||||
typ = _TypeFor(o.sym)
|
||||
mods = _ModsFor(o.sym)
|
||||
if o.sym.import_kind != LspSymbols.LSP_IMPORT_NONE:
|
||||
t: LspIndex.LspTarget* = _CachedSymbol(ctx, pool, o.sym)
|
||||
if t is not None:
|
||||
typ = _TypeForTarget(t)
|
||||
mods = _ModsForTarget(t)
|
||||
elif o.head is not None and o.rest is not None and o.head.import_kind != LspSymbols.LSP_IMPORT_NONE:
|
||||
t = _CachedChain(ctx, pool, o.head, o.rest)
|
||||
if t is not None:
|
||||
typ = _TypeForTarget(t)
|
||||
mods = _ModsForTarget(t)
|
||||
if typ < 0:
|
||||
# Builtin type names have no declaration; color them like class types.
|
||||
if o.builtin_type != 0:
|
||||
return _NewTok(pool, o.line, o.sc, o.line, o.ec, ST_CLASS)
|
||||
return None
|
||||
t: LspSemToken* = _NewTok(pool, o.line, o.sc, o.line, o.ec, _TypeFor(o.sym))
|
||||
if t is not None:
|
||||
t.mods = _ModsFor(o.sym)
|
||||
return t
|
||||
if o.builtin_type == 0:
|
||||
return None
|
||||
typ = ST_CLASS
|
||||
tok: LspSemToken* = _NewTok(pool, o.line, o.sc, o.line, o.ec, typ)
|
||||
if tok is not None:
|
||||
tok.mods = mods
|
||||
return tok
|
||||
|
||||
|
||||
def _Before(a: LspSemToken*, b: LspSemToken*) -> int:
|
||||
@@ -188,8 +304,8 @@ def _SymbolTokenValid(pool: memhub.MemManager*, a: LspSymbols.LspAnalysis*,
|
||||
return 1 if string.strcmp(txt, name) == 0 else 0
|
||||
|
||||
|
||||
def _AstStream(pool: memhub.MemManager*,
|
||||
a: LspSymbols.LspAnalysis*) -> list[LspSemToken*]*:
|
||||
def _AstStream(pool: memhub.MemManager*, a: LspSymbols.LspAnalysis*,
|
||||
ctx: LspSemCtx*) -> list[LspSemToken*]*:
|
||||
out: list[LspSemToken*]* = list[LspSemToken*](pool, 64)
|
||||
syms: list[LspSymbols.LspSymbol*]* = a.symbols
|
||||
occs: list[LspSymbols.LspOccurrence*]* = a.occs
|
||||
@@ -204,7 +320,7 @@ def _AstStream(pool: memhub.MemManager*,
|
||||
while si < n:
|
||||
s: LspSymbols.LspSymbol* = syms.get(sizet(si))
|
||||
si += 1
|
||||
t: LspSemToken* = _TokenForSymbol(pool, s)
|
||||
t: LspSemToken* = _TokenForSymbol(ctx, pool, s)
|
||||
if t is not None and s is not None:
|
||||
if _SymbolTokenValid(pool, a, t, s.name) == 0:
|
||||
t.typ = -1
|
||||
@@ -213,7 +329,7 @@ def _AstStream(pool: memhub.MemManager*,
|
||||
break
|
||||
occ_t: LspSemToken* = None
|
||||
while oi < m:
|
||||
t: LspSemToken* = _TokenForOcc(pool, occs.get(sizet(oi)))
|
||||
t: LspSemToken* = _TokenForOcc(ctx, pool, occs.get(sizet(oi)))
|
||||
oi += 1
|
||||
if t is not None and t.typ >= 0:
|
||||
occ_t = t
|
||||
@@ -361,6 +477,22 @@ def _InRange(t: LspSemToken*, start_line: int, end_line: int) -> int:
|
||||
return 1
|
||||
|
||||
|
||||
def _AppendInt(pool: memhub.MemManager*, arr: json.JsonValue*, value: int,
|
||||
tail: json.JsonValue*) -> json.JsonValue*:
|
||||
"""Append to a JSON array in O(1); json.array_append walks the whole list per call."""
|
||||
if arr is None:
|
||||
return tail
|
||||
node: json.JsonValue* = json.int_val(pool, int64(value))
|
||||
if node is None:
|
||||
return tail
|
||||
if tail is None:
|
||||
arr.child = node
|
||||
else:
|
||||
tail.next = node
|
||||
arr.child_count += 1
|
||||
return node
|
||||
|
||||
|
||||
def _Encode(pool: memhub.MemManager*, out: json.JsonValue*,
|
||||
ast_tokens: list[LspSemToken*]*, lex_tokens: list[LspSemToken*]*,
|
||||
has_range: int, start_line: int, end_line: int):
|
||||
@@ -368,6 +500,7 @@ def _Encode(pool: memhub.MemManager*, out: json.JsonValue*,
|
||||
prev_char: int = 0
|
||||
count: int = 0
|
||||
last: LspSemToken* = None
|
||||
tail: json.JsonValue* = None
|
||||
ai: int = 0
|
||||
li: int = 0
|
||||
an: int = int(len(ast_tokens))
|
||||
@@ -401,11 +534,11 @@ def _Encode(pool: memhub.MemManager*, out: json.JsonValue*,
|
||||
length: int = t.ec - t.sc
|
||||
if length <= 0:
|
||||
continue
|
||||
json.array_append(pool, out, json.int_val(pool, int64(dl)))
|
||||
json.array_append(pool, out, json.int_val(pool, int64(dc)))
|
||||
json.array_append(pool, out, json.int_val(pool, int64(length)))
|
||||
json.array_append(pool, out, json.int_val(pool, int64(t.typ)))
|
||||
json.array_append(pool, out, json.int_val(pool, int64(t.mods)))
|
||||
tail = _AppendInt(pool, out, dl, tail)
|
||||
tail = _AppendInt(pool, out, dc, tail)
|
||||
tail = _AppendInt(pool, out, length, tail)
|
||||
tail = _AppendInt(pool, out, t.typ, tail)
|
||||
tail = _AppendInt(pool, out, t.mods, tail)
|
||||
prev_line = t.sl
|
||||
prev_char = t.sc
|
||||
last = t
|
||||
@@ -426,7 +559,31 @@ def Tokens(pool: memhub.MemManager*, tr: LspTransport.LspTransport*,
|
||||
return LspRpc.WriteResponse(pool, tr, id_node, result)
|
||||
a: LspSymbols.LspAnalysis* = LspNavigation._Analyze(
|
||||
pool, docs, doc, LspNavigation._Uri(params))
|
||||
ast_tokens: list[LspSemToken*]* = _AstStream(pool, a)
|
||||
# Cross-module coloring resolves imported declarations; the workspace symbol index avoids
|
||||
# re-parsing the same library file for each occurrence.
|
||||
LspIndex.SetRoots(ws)
|
||||
LspIndex.Ensure(ws, docs.pool)
|
||||
ctx: LspSemCtx* = LspSemCtx(pool)
|
||||
if ctx is not None:
|
||||
ctx.ws = ws
|
||||
ctx.table_pool = docs.pool
|
||||
ctx.file = a.file
|
||||
ctx.sym_keys = list[LspSymbols.LspSymbol*](pool, 8)
|
||||
ctx.sym_tgts = list[LspIndex.LspTarget*](pool, 8)
|
||||
ctx.chain_heads = list[LspSymbols.LspSymbol*](pool, 8)
|
||||
ctx.chain_rests = list[str](pool, 8)
|
||||
ctx.chain_tgts = list[LspIndex.LspTarget*](pool, 8)
|
||||
ci: int = 0
|
||||
while ci < LSP_SEM_CACHE_SIZE:
|
||||
ctx.sym_keys.append(None)
|
||||
ctx.sym_tgts.append(None)
|
||||
ctx.chain_heads.append(None)
|
||||
ctx.chain_rests.append(None)
|
||||
ctx.chain_tgts.append(None)
|
||||
ci += 1
|
||||
LspIndex.BeginResolveOnly()
|
||||
ast_tokens: list[LspSemToken*]* = _AstStream(pool, a, ctx)
|
||||
LspIndex.EndResolveOnly()
|
||||
lex_tokens: list[LspSemToken*]* = _LexStream(pool, doc)
|
||||
has_range: int = 0
|
||||
start_line: int = 0
|
||||
|
||||
@@ -182,6 +182,7 @@ def _HandleDidOpen(pool: memhub.MemManager*, tr: LspTransport.LspTransport*,
|
||||
file: str = LspWorkspace.DecodeUri(pool, uri)
|
||||
if file is not None and ws is not None:
|
||||
if ws.AdoptFromFile(file) != 0:
|
||||
LspIndex.RefreshRoots(ws)
|
||||
LspIndex.Invalidate()
|
||||
if docs.Open(uri, text, version) is None:
|
||||
LspTransport.StderrWrite("viperc-lsp: didOpen failed to store document\n")
|
||||
|
||||
@@ -46,6 +46,8 @@ class LspScope:
|
||||
class LspSymbol:
|
||||
name: str
|
||||
kind: int
|
||||
# 1 for `define` macro functions (colored as macro), 0 otherwise.
|
||||
is_macro: int
|
||||
detail: str
|
||||
doc: str
|
||||
file: str
|
||||
@@ -630,6 +632,10 @@ def _Declare(pool: memhub.MemManager*, a: LspAnalysis*, scope: LspScope*,
|
||||
return None
|
||||
s.file = a.file
|
||||
s.detail = detail
|
||||
if decl is not None and decl.kind() == ast.ASTKind.FunctionDef:
|
||||
fd: ast.FunctionDef* = (ast.FunctionDef*)(decl)
|
||||
if (fd.flags & ast.FLAG_IS_DEFINE) != 0:
|
||||
s.is_macro = 1
|
||||
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):
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Gate 10 fixture: class members used through annotations and module chains."""
|
||||
|
||||
define DOUBLE(x): return x + x
|
||||
|
||||
|
||||
class Widget:
|
||||
value: int
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
"""Gate 10 fixture: annotated members, module chains and .vpi members."""
|
||||
|
||||
from .gate10_types import Widget
|
||||
from .gate10_types import Widget, DOUBLE
|
||||
from .gate10_stub import StubThing
|
||||
import gate10_types
|
||||
|
||||
|
||||
def macro_use(n: int) -> int:
|
||||
return DOUBLE(n)
|
||||
|
||||
|
||||
def by_import(s: Widget) -> int:
|
||||
v: int = s.value
|
||||
return s.resize(v)
|
||||
|
||||
@@ -536,6 +536,7 @@ def gate3(exe: str) -> int:
|
||||
"position": ref_pos,
|
||||
"context": {"includeDeclaration": True},
|
||||
},
|
||||
timeout=30.0,
|
||||
).get("result")
|
||||
if not isinstance(references, list) or len(references) < 3:
|
||||
raise AssertionError(f"references are incomplete: {references}")
|
||||
@@ -866,6 +867,7 @@ def gate5(exe: str) -> int:
|
||||
renamed = client.request(
|
||||
"textDocument/rename",
|
||||
{"textDocument": {"uri": uri}, "position": field_pos, "newName": "value"},
|
||||
timeout=30.0,
|
||||
).get("result")
|
||||
if not isinstance(renamed, dict):
|
||||
raise AssertionError(f"rename failed: {renamed}")
|
||||
@@ -1048,6 +1050,39 @@ def gate7(exe: str) -> int:
|
||||
if "includes" not in includes:
|
||||
raise AssertionError(f"includes is wrong after adoption: {stats}")
|
||||
|
||||
# Project-relative imports must resolve before any index rebuild: the adopted project's
|
||||
# source_dir/includes have to replace the repository-root fallback roots.
|
||||
app_doc = root / "App" / "lib" / "lsp" / "LspDocuments.vp"
|
||||
app_text = app_doc.read_text(encoding="utf-8")
|
||||
app_uri = app_doc.as_uri()
|
||||
client.notify(
|
||||
"textDocument/didOpen",
|
||||
{"textDocument": {"uri": app_uri, "languageId": "viper", "version": 1, "text": app_text}},
|
||||
)
|
||||
client.wait_notification("textDocument/publishDiagnostics")
|
||||
pool_pos = _position(app_text, "LspPools.LspPool")
|
||||
pool_pos["character"] += len("LspPools.")
|
||||
imported_def = client.request(
|
||||
"textDocument/definition", {"textDocument": {"uri": app_uri}, "position": pool_pos},
|
||||
timeout=60.0,
|
||||
).get("result")
|
||||
if not isinstance(imported_def, list) or not imported_def:
|
||||
raise AssertionError(f"project import did not resolve after adoption: {imported_def}")
|
||||
if not imported_def[0]["uri"].endswith("LspPools.vp"):
|
||||
raise AssertionError(f"project import resolved to the wrong file: {imported_def}")
|
||||
legend = response.get("result", {}).get("capabilities", {}).get(
|
||||
"semanticTokensProvider", {}).get("legend", {})
|
||||
sem_types = legend.get("tokenTypes", [])
|
||||
semantic = client.request(
|
||||
"textDocument/semanticTokens/full", {"textDocument": {"uri": app_uri}}, timeout=60.0
|
||||
).get("result")
|
||||
sem_tokens = _decode_tokens(semantic.get("data", [])) if isinstance(semantic, dict) else []
|
||||
if not _find_token(sem_tokens, pool_pos["line"], pool_pos["character"],
|
||||
sem_types.index("class")):
|
||||
raise AssertionError(f"imported class was not highlighted: {pool_pos}")
|
||||
client.notify("textDocument/didClose", {"textDocument": {"uri": app_uri}})
|
||||
client.wait_notification("textDocument/publishDiagnostics")
|
||||
|
||||
symbols = client.request(
|
||||
"workspace/symbol", {"query": "LspDocuments"}, timeout=60.0
|
||||
).get("result")
|
||||
@@ -1412,6 +1447,26 @@ def gate10(exe: str) -> int:
|
||||
if reported:
|
||||
raise AssertionError(f"gate10 fixture has diagnostics: {reported}")
|
||||
|
||||
legend = response.get("result", {}).get("capabilities", {}).get("semanticTokensProvider", {}).get("legend", {})
|
||||
sem_types = legend.get("tokenTypes", [])
|
||||
semantic = client.request(
|
||||
"textDocument/semanticTokens/full", {"textDocument": {"uri": uri}}, timeout=120.0
|
||||
).get("result")
|
||||
if not isinstance(semantic, dict):
|
||||
raise AssertionError(f"semanticTokens/full failed: {semantic}")
|
||||
sem_tokens = _decode_tokens(semantic.get("data", []))
|
||||
for label, needle, delta, token_name in (
|
||||
("imported class", "import Widget", 7, "class"),
|
||||
("imported macro", "import Widget", 15, "macro"),
|
||||
("macro call", "return DOUBLE", 7, "macro"),
|
||||
("member field", "s.value", 2, "property"),
|
||||
("member method", "s.resize", 2, "method"),
|
||||
):
|
||||
pos = _position(text, needle)
|
||||
pos["character"] += delta
|
||||
if not _find_token(sem_tokens, pos["line"], pos["character"], sem_types.index(token_name)):
|
||||
raise AssertionError(f"missing cross-module semantic token for {label}: {pos}")
|
||||
|
||||
def at(needle: str, delta: int = 0, occurrence: int = 0) -> dict[str, int]:
|
||||
pos = _position(text, needle, occurrence)
|
||||
pos["character"] += delta
|
||||
|
||||
Reference in new issue
Block a user