索引记录已解析出现点;references/rename 按文件分组,支持 from-import 与 module.Member;路径比较归一化;多开文档时跨文件重命名返回 null。
219 lines
5.9 KiB
Plaintext
219 lines
5.9 KiB
Plaintext
from stdint import *
|
|
import string
|
|
import os
|
|
import os.path
|
|
import io
|
|
import memhub
|
|
import lib.lsp.LspPools as LspPools
|
|
|
|
|
|
define LSP_SOURCE_CAP = 1048576
|
|
define LSP_LIST_CAP = 512
|
|
define LSP_WALK_DEPTH = 24
|
|
|
|
|
|
def IsSourceFile(name: str) -> int:
|
|
if name is None:
|
|
return 0
|
|
n: sizet = string.strlen(name)
|
|
if n > 3 and string.strncmp(name + (n - 3), ".vp", 3) == 0:
|
|
return 1
|
|
if n > 4 and string.strncmp(name + (n - 4), ".vpi", 4) == 0:
|
|
return 1
|
|
return 0
|
|
|
|
|
|
def SkipDir(name: str) -> int:
|
|
"""Directories that never contain workspace sources (build output and VCS data)."""
|
|
if name is None or name[0] == '.':
|
|
return 1
|
|
if string.strcmp(name, "__vpcbuild__") == 0:
|
|
return 1
|
|
if string.strcmp(name, "__vpcbuild_2__") == 0:
|
|
return 1
|
|
if string.strcmp(name, "temp") == 0:
|
|
return 1
|
|
if string.strcmp(name, "output") == 0:
|
|
return 1
|
|
if string.strcmp(name, "node_modules") == 0:
|
|
return 1
|
|
return 0
|
|
|
|
|
|
def Join(pool: memhub.MemManager*, a: str, b: str) -> str:
|
|
if a is None or b is None:
|
|
return None
|
|
p: str = os.path.join(a, b)
|
|
if p is None:
|
|
return None
|
|
return LspPools.DupStr(pool, p)
|
|
|
|
|
|
def Normalize(pool: memhub.MemManager*, path: str) -> str:
|
|
"""Copy path with '/' separators so identities compare consistently on Windows."""
|
|
if pool is None or path is None:
|
|
return None
|
|
n: sizet = string.strlen(path)
|
|
buf: bytes = pool.alloc(n + 1)
|
|
if buf is None:
|
|
return None
|
|
i: sizet = 0
|
|
while i < n:
|
|
ch: char = path[i]
|
|
buf[i] = '/' if ch == '\\' else ch
|
|
i += 1
|
|
buf[n] = 0
|
|
return str(buf)
|
|
|
|
|
|
def PathEqual(a: str, b: str) -> int:
|
|
"""Compare paths ignoring separator kind and ASCII case (Windows file paths)."""
|
|
if a is None or b is None:
|
|
return 0
|
|
i: sizet = 0
|
|
while True:
|
|
ca: char = a[i]
|
|
cb: char = b[i]
|
|
if ca == 0 and cb == 0:
|
|
return 1
|
|
if ca == 0 or cb == 0:
|
|
return 0
|
|
if ca == '\\':
|
|
ca = '/'
|
|
if cb == '\\':
|
|
cb = '/'
|
|
if ca >= 'A' and ca <= 'Z':
|
|
ca = char(int(ca) + 32)
|
|
if cb >= 'A' and cb <= 'Z':
|
|
cb = char(int(cb) + 32)
|
|
if ca != cb:
|
|
return 0
|
|
i += 1
|
|
|
|
|
|
def DirName(pool: memhub.MemManager*, path: str) -> str:
|
|
if path is None:
|
|
return None
|
|
p: str = os.path.dirname(path)
|
|
if p is None:
|
|
return None
|
|
return LspPools.DupStr(pool, p)
|
|
|
|
|
|
def BaseName(pool: memhub.MemManager*, path: str) -> str:
|
|
if path is None:
|
|
return None
|
|
p: str = os.path.basename(path)
|
|
if p is None:
|
|
return None
|
|
return LspPools.DupStr(pool, p)
|
|
|
|
|
|
def ReadSource(pool: memhub.MemManager*, path: str) -> str:
|
|
"""Read a source file into pool (cap 1 MiB); None when unreadable or too large."""
|
|
if pool is None or path is None:
|
|
return None
|
|
f: io.File* = io.File(path, io.MODE.R)
|
|
if f is None or f.closed:
|
|
if f is not None:
|
|
f.close()
|
|
return None
|
|
size: int64 = f.size()
|
|
if size < 0 or size >= int64(LSP_SOURCE_CAP):
|
|
f.close()
|
|
return None
|
|
cap: sizet = sizet(int(size)) + 1
|
|
buf: bytes = pool.alloc(cap)
|
|
if buf is None:
|
|
f.close()
|
|
return None
|
|
got: int = int(f.read_all(buf, cap))
|
|
f.close()
|
|
if got < 0:
|
|
return None
|
|
buf[sizet(int(size))] = 0
|
|
return str(buf)
|
|
|
|
|
|
def ModuleName(pool: memhub.MemManager*, root: str, path: str) -> str:
|
|
"""Dotted module name of path relative to root; None when path is outside root."""
|
|
if pool is None or root is None or path is None:
|
|
return None
|
|
rlen: sizet = string.strlen(root)
|
|
plen: sizet = string.strlen(path)
|
|
if plen <= rlen:
|
|
return None
|
|
rel: str = path + rlen
|
|
if rel[0] == '/' or rel[0] == '\\':
|
|
rel = rel + 1
|
|
n: sizet = string.strlen(rel)
|
|
if n <= 3:
|
|
return None
|
|
body: sizet = n
|
|
if IsSourceFile(rel) != 0:
|
|
# Strip the extension.
|
|
i: sizet = n
|
|
while i > 0 and rel[i - 1] != '.':
|
|
i -= 1
|
|
if i > 0:
|
|
body = i - 1
|
|
buf: bytes = pool.alloc(body + 1)
|
|
if buf is None:
|
|
return None
|
|
i2: sizet = 0
|
|
while i2 < body:
|
|
ch: char = rel[i2]
|
|
if ch == '/' or ch == '\\':
|
|
buf[i2] = '.'
|
|
else:
|
|
buf[i2] = ch
|
|
i2 += 1
|
|
buf[body] = 0
|
|
out: str = str(buf)
|
|
# `<pkg>.__init__` names the package itself.
|
|
olen: sizet = string.strlen(out)
|
|
if olen > 9 and string.strncmp(out + (olen - 9), ".__init__", 9) == 0:
|
|
out[olen - 9] = 0
|
|
return out
|
|
|
|
|
|
def CollectSources(pool: memhub.MemManager*, root: str,
|
|
out_paths: list[str]*, cap: int, depth: int) -> int:
|
|
"""Append absolute .vp/.vpi paths under root to out_paths (bounded)."""
|
|
if pool is None or root is None or out_paths is None:
|
|
return 0
|
|
if depth > LSP_WALK_DEPTH or int(len(out_paths)) >= cap:
|
|
return 0
|
|
if not os.isdir(root):
|
|
return 0
|
|
names: str* = (str*)(pool.alloc(sizet(LSP_LIST_CAP) * 8))
|
|
if names is None:
|
|
return 0
|
|
i: int = 0
|
|
while i < LSP_LIST_CAP:
|
|
names[i] = None
|
|
i += 1
|
|
count: int = int(os.listdir(root, names, ULONG(LSP_LIST_CAP)))
|
|
if count <= 0:
|
|
return 0
|
|
i = 0
|
|
while i < count:
|
|
name: str = names[i]
|
|
i += 1
|
|
if name is None or name[0] == 0:
|
|
continue
|
|
full: str = Join(pool, root, name)
|
|
if full is None:
|
|
continue
|
|
if os.isdir(full):
|
|
if SkipDir(name) == 0:
|
|
CollectSources(pool, full, out_paths, cap, depth + 1)
|
|
if int(len(out_paths)) >= cap:
|
|
return 0
|
|
continue
|
|
if IsSourceFile(name) != 0:
|
|
out_paths.append(full)
|
|
if int(len(out_paths)) >= cap:
|
|
return 0
|
|
return 0
|