feat(lsp): leave moved variables and their members uncolored

Semantic tokens now drop a variable token whose name was moved before that
line, and a property token whose base (`x` in `x.a`) is such a name. The
transfer statement itself keeps its colors (moves on the same line are
ignored for this filter), so only later uses turn plain/white.

With
  _U2: own U = _U
  _U3: own U = _U2
  print(_U2.a)
line 3 emits only `print`; `_U2` and `a` have no semantic token. The
move diagnostic still marks `_U2` separately.

Completion filtering is unchanged (`_` offers only `_U3`).
This commit is contained in:
TermiNexus committed 2026-11-02 20:09:54 +08:00
1 parent 6e6b94fdfb
commit 3b8dc1377d
1 file changed
+53 -4
+53 -4
View File
@@ -9,6 +9,7 @@ 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.LspChecks as LspChecks
import lib.lsp.LspTransport as LspTransport
import lib.lsp.LspRpc as LspRpc
@@ -329,8 +330,56 @@ def _SymbolTokenValid(pool: memhub.MemManager*, a: LspSymbols.LspAnalysis*,
return 1 if string.strcmp(txt, name) == 0 else 0
_sem_moved_line: int = -1
_sem_moved: list[str]* = None
def _IsIdentByte(ch: char) -> int:
if ch >= 'a' and ch <= 'z': return 1
if ch >= 'A' and ch <= 'Z': return 1
if ch >= '0' and ch <= '9': return 1
if ch == '_': return 1
return 0
def _MovedDrop(pool: memhub.MemManager*, a: LspSymbols.LspAnalysis*,
t: LspSemToken*) -> int:
"""1 when this token names a moved variable (or a member of one): keep it uncolored."""
global _sem_moved_line, _sem_moved
if t is None or t.typ < 0 or a is None or a.doc is None: return 0
if t.typ != ST_VARIABLE and t.typ != ST_PROPERTY: return 0
# Moves on the token's own line keep their color (the transfer statement itself).
if t.sl != _sem_moved_line:
_sem_moved = LspChecks.MovedBefore(pool, a, t.sl - 1)
_sem_moved_line = t.sl
if _sem_moved is None or int(len(_sem_moved)) == 0: return 0
sb: int = LspSource.Utf16ColToByte(a.doc, t.sl, t.sc)
eb: int = LspSource.Utf16ColToByte(a.doc, t.sl, t.ec)
txt: str = LspSource.SliceBytes(pool, a.doc, t.sl, sb, t.sl, eb)
if txt is not None and LspChecks._NameListFind(_sem_moved, txt) >= 0: return 1
if t.typ != ST_PROPERTY: return 0
line_start: int = int(a.doc.index.LineStart(t.sl))
if sb <= 0 or a.doc.text[line_start + sizet(sb - 1)] != '.': return 0
p: int = sb - 2
while p >= 0:
ch: char = a.doc.text[line_start + sizet(p)]
if ch == ' ' or ch == '\t': p -= 1
else: break
e: int = p
while p >= 0:
if _IsIdentByte(a.doc.text[line_start + sizet(p)]) == 0: break
p -= 1
if e < 0 or e <= p: return 0
base: str = LspSource.SliceBytes(pool, a.doc, t.sl, p + 1, t.sl, e + 1)
if base is not None and LspChecks._NameListFind(_sem_moved, base) >= 0: return 1
return 0
def _AstStream(pool: memhub.MemManager*, a: LspSymbols.LspAnalysis*,
ctx: LspSemCtx*) -> list[LspSemToken*]*:
global _sem_moved_line, _sem_moved
_sem_moved_line = -1
_sem_moved = None
out: list[LspSemToken*]* = list[LspSemToken*](pool, 64)
syms: list[LspSymbols.LspSymbol*]* = a.symbols
occs: list[LspSymbols.LspOccurrence*]* = a.occs
@@ -362,16 +411,16 @@ def _AstStream(pool: memhub.MemManager*, a: LspSymbols.LspAnalysis*,
if sym_t is None and occ_t is None:
break
if sym_t is None:
out.append(occ_t)
if _MovedDrop(pool, a, occ_t) == 0: out.append(occ_t)
continue
if occ_t is None:
out.append(sym_t)
if _MovedDrop(pool, a, sym_t) == 0: out.append(sym_t)
continue
if _Before(sym_t, occ_t) != 0:
out.append(sym_t)
if _MovedDrop(pool, a, sym_t) == 0: out.append(sym_t)
oi -= 1
else:
out.append(occ_t)
if _MovedDrop(pool, a, occ_t) == 0: out.append(occ_t)
si -= 1
return out