Files
ViperC/App/lib/lsp/LspCodeAction.vp
T
TermiNexus 08c817f5ec feat(lsp): 增加重命名、语义高亮与导入整理
prepareRename/rename 同文件全量出现点;semanticTokens full/range 的 AST+词法+注释 token;organize imports 排序去重。探针新增响应 envelope 校验。
2026-10-26 18:19:05 +08:00

278 lines
8.2 KiB
Plaintext

from stdint import *
import string
import json
import memhub
import ast
import lib.lsp.LspDocuments as LspDocuments
import lib.lsp.LspSource as LspSource
import lib.lsp.LspSymbols as LspSymbols
import lib.lsp.LspWorkspace as LspWorkspace
import lib.lsp.LspNavigation as LspNavigation
import lib.lsp.LspTransport as LspTransport
import lib.lsp.LspRpc as LspRpc
def _IsImportNode(node: ast.AST*) -> int:
if node is None:
return 0
k: int = node.kind()
if k == ast.ASTKind.Import:
return 1
if k == ast.ASTKind.ImportFrom:
return 1
return 0
def _IsDocstring(node: ast.AST*) -> int:
if node is None or node.kind() != ast.ASTKind.Expr:
return 0
ex: ast.Expr* = (ast.Expr*)(node)
if ex.value is None or ex.value.kind() != ast.ASTKind.Constant:
return 0
c: ast.Constant* = (ast.Constant*)(ex.value)
return 1 if c.const_kind == ast.CONST_STR else 0
def _LineText(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*,
line0: int) -> str:
if doc is None or doc.index is None or line0 < 0 or line0 >= doc.index.Count():
return None
sc: int = 0
ec: int = LspSource.LineByteLength(doc, line0)
return LspSource.SliceBytes(pool, doc, line0, sc, line0, ec)
def _TrimLine(pool: memhub.MemManager*, s: str) -> str:
if s is None:
return None
n: sizet = string.strlen(s)
start: sizet = 0
while start < n:
ch: char = s[start]
if ch == ' ' or ch == '\t' or ch == '\r' or ch == '\n':
start += 1
continue
break
end: sizet = n
while end > start:
ch: char = s[end - 1]
if ch == ' ' or ch == '\t' or ch == '\r' or ch == '\n':
end -= 1
continue
break
if end <= start:
return ""
return LspSymbols._CopyStr(pool, s + start, int(end - start))
def _HasUnbalancedParen(s: str) -> int:
if s is None:
return 0
depth: int = 0
i: sizet = 0
while s[i] != 0:
if s[i] == '(':
depth += 1
elif s[i] == ')':
if depth > 0:
depth -= 1
elif s[i] == '\\':
return 1
i += 1
return 1 if depth != 0 else 0
def _Lower(ch: char) -> char:
if ch >= 'A' and ch <= 'Z':
return char(int(ch) + 32)
return ch
def _CompareImport(a: str, b: str) -> int:
if a is None:
return 1 if b is not None else 0
if b is None:
return -1
n: sizet = string.strlen(a)
m: sizet = string.strlen(b)
i: sizet = 0
lim: sizet = n
if m < lim:
lim = m
while i < lim:
ca: char = _Lower(a[i])
cb: char = _Lower(b[i])
if ca < cb:
return -1
if ca > cb:
return 1
i += 1
if n < m:
return -1
if n > m:
return 1
return string.strcmp(a, b)
def _Contains(list_in: list[str]*, s: str) -> int:
if list_in is None or s is None:
return 0
i: int = 0
while i < int(len(list_in)):
if string.strcmp(list_in.get(sizet(i)), s) == 0:
return 1
i += 1
return 0
def _InsertSorted(pool: memhub.MemManager*, out: list[str]*,
s: str, cap: int) -> list[str]*:
new_list: list[str]* = list[str](pool, cap)
if new_list is None:
return out
n: int = int(len(out))
pos: int = 0
while pos < n:
if _CompareImport(s, out.get(sizet(pos))) < 0:
break
pos += 1
i: int = 0
inserted: int = 0
while i < n:
if i == pos:
new_list.append(s)
inserted = 1
new_list.append(out.get(sizet(i)))
i += 1
if inserted == 0:
new_list.append(s)
return new_list
def _Organize(pool: memhub.MemManager*, a: LspSymbols.LspAnalysis*,
uri: str) -> json.JsonValue*:
if a is None or a.tree is None or a.tree.children is None:
return None
body: list[ast.AST*]* = a.tree.children
n: int = int(len(body))
idx: int = 0
if n > 0 and _IsDocstring(body.get(0)) != 0:
idx = 1
first_line: int = -1
last_line: int = -1
originals: list[str]* = list[str](pool, 8)
while idx < n:
st: ast.AST* = body.get(sizet(idx))
if _IsImportNode(st) == 0:
break
if st.lineno <= 0:
return None
line0: int = st.lineno - 1
txt: str = _TrimLine(pool, _LineText(pool, a.doc, line0))
if txt is None or txt[0] == 0 or _HasUnbalancedParen(txt) != 0:
return None
if first_line < 0:
first_line = line0
last_line = line0
if _Contains(originals, txt) == 0:
originals.append(txt)
idx += 1
if first_line < 0:
return None
# Only organize a contiguous import block: reject imports later in the file.
while idx < n:
if _IsImportNode(body.get(sizet(idx))) != 0:
return None
idx += 1
sorted_imports: list[str]* = list[str](pool, 8)
i: int = 0
while i < int(len(originals)):
sorted_imports = _InsertSorted(pool, sorted_imports, originals.get(sizet(i)), 64)
i += 1
changed: int = 0
if int(len(sorted_imports)) != int(len(originals)):
changed = 1
else:
i = 0
while i < int(len(sorted_imports)):
if string.strcmp(sorted_imports.get(sizet(i)), originals.get(sizet(i))) != 0:
changed = 1
break
i += 1
if changed == 0:
return None
new_text: str = None
i = 0
while i < int(len(sorted_imports)):
if new_text is None:
new_text = LspSymbols._CopyStr(pool, sorted_imports.get(sizet(i)), 1024)
else:
new_text = _Join(pool, new_text, "\n",
sorted_imports.get(sizet(i)))
i += 1
if new_text is None:
return None
rng: LspSource.LspRange* = LspSource.SpanFromBytes(
pool, a.doc, first_line, 0, last_line, LspSource.LineByteLength(a.doc, last_line))
edit: json.JsonValue* = json.object(pool)
if edit is None:
return None
json.object_set(pool, edit, "range", LspSource.RangeToJson(pool, rng))
json.object_set(pool, edit, "newText", json.string_val(pool, new_text))
edits: json.JsonValue* = json.array(pool)
if edits is None:
return None
json.array_append(pool, edits, edit)
changes: json.JsonValue* = json.object(pool)
if changes is None:
return None
json.object_set(pool, changes, uri, edits)
we: json.JsonValue* = json.object(pool)
if we is None:
return None
json.object_set(pool, we, "changes", changes)
action: json.JsonValue* = json.object(pool)
if action is None:
return None
json.object_set(pool, action, "title", json.string_val(pool, "Organize imports"))
json.object_set(pool, action, "kind", json.string_val(pool, "source.organizeImports"))
json.object_set(pool, action, "edit", we)
return action
def _Join(pool: memhub.MemManager*, a: str, sep: str, b: str) -> str:
if a is None:
return b
if b is None:
return a
n1: sizet = string.strlen(a)
n2: sizet = string.strlen(sep)
n3: sizet = string.strlen(b)
buf: bytes = pool.alloc(n1 + n2 + n3 + 1)
if buf is None:
return None
string.memcpy(buf, a, n1)
string.memcpy(buf + n1, sep, n2)
string.memcpy(buf + n1 + n2, b, n3)
buf[n1 + n2 + n3] = 0
return str(buf)
def CodeAction(pool: memhub.MemManager*, tr: LspTransport.LspTransport*,
docs: LspDocuments.LspDocuments*, ws: LspWorkspace.LspWorkspace*,
id_node: json.JsonValue*, params: json.JsonValue*) -> int:
arr: json.JsonValue* = json.array(pool)
if arr is None:
return -1
if params is None:
return LspRpc.WriteResponse(pool, tr, id_node, arr)
doc: LspDocuments.LspDocument* = LspNavigation._Doc(pool, docs, params)
if doc is None:
return LspRpc.WriteResponse(pool, tr, id_node, arr)
uri: str = LspNavigation._Uri(params)
a: LspSymbols.LspAnalysis* = LspNavigation._Analyze(pool, docs, doc, uri)
action: json.JsonValue* = _Organize(pool, a, uri)
if action is not None:
json.array_append(pool, arr, action)
return LspRpc.WriteResponse(pool, tr, id_node, arr)