From 3b8dc1377d80507fb4b0b8fd7c0e694c0bdb1144 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 2 Nov 2026 20:09:54 +0800 Subject: [PATCH] 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`). --- App/lib/lsp/LspSemantic.vp | 57 +++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/App/lib/lsp/LspSemantic.vp b/App/lib/lsp/LspSemantic.vp index d5b860f..b8135e2 100644 --- a/App/lib/lsp/LspSemantic.vp +++ b/App/lib/lsp/LspSemantic.vp @@ -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