feat(lsp): Gate 1 文档同步、UTF-16 位置与工作区
- LspPositions:UTF-8 字节列 / UTF-16 code unit 转换,支持 LF/CRLF/emoji - LspDocuments:didOpen/didChange/didClose/didSave,增量与全量替换,双池复用 - LspWorkspace:file URI 解码、project.vpj 向上查找、source_dir/includes - LspServer:请求池 mark/reset、textDocumentSync、viper/lspStats 与 viper/lspDocument - probe --gate1:文档编辑、UTF-16 代理对、10k 次编辑与池稳定性 - 依赖 includes/memhub 的 NewMemBuddy 堆构造工厂(单独提交 fa7591e)
This commit is contained in:
1 parent
3f54946af9
commit
c42ae889d1
9 files changed
+997
-23
No files matched your search
@@ -0,0 +1,196 @@
|
||||
from stdint import *
|
||||
import string
|
||||
import memhub
|
||||
import lib.lsp.LspPools as LspPools
|
||||
import lib.lsp.LspPositions as LspPositions
|
||||
|
||||
|
||||
define LSP_DOC_POOL_SIZE = 4194304
|
||||
|
||||
|
||||
class LspDocument:
|
||||
uri: str
|
||||
text: str
|
||||
version: int
|
||||
index: LspPositions.LspLineIndex*
|
||||
pool: LspPools.LspPool*
|
||||
spare: LspPools.LspPool*
|
||||
|
||||
|
||||
class LspDocuments:
|
||||
pool: memhub.MemManager*
|
||||
docs: list[LspDocument*]*
|
||||
count: int
|
||||
|
||||
def __new__(self, pool: memhub.MemManager*) -> rawptr:
|
||||
p: LspDocuments* = (LspDocuments*)(pool.alloc(LspDocuments.__sizeof__()))
|
||||
if p:
|
||||
string.memset(p, 0, LspDocuments.__sizeof__())
|
||||
return rawptr(p)
|
||||
|
||||
def __init__(self, pool: memhub.MemManager*):
|
||||
self.pool = pool
|
||||
self.docs = list[LspDocument*](pool, 8)
|
||||
self.count = 0
|
||||
|
||||
def Find(self, uri: str) -> LspDocument*:
|
||||
return self._Find(uri)
|
||||
|
||||
def _Find(self, uri: str) -> LspDocument*:
|
||||
if uri is None:
|
||||
return None
|
||||
i: int = 0
|
||||
while i < self.count:
|
||||
doc: LspDocument* = self.docs[i]
|
||||
if doc is not None and doc.uri is not None:
|
||||
if string.strcmp(doc.uri, uri) == 0:
|
||||
return doc
|
||||
i += 1
|
||||
return None
|
||||
|
||||
def _DestroyDoc(self, doc: LspDocument*) -> int:
|
||||
if doc is None:
|
||||
return -1
|
||||
if doc.pool is not None:
|
||||
doc.pool.Destroy()
|
||||
doc.pool = None
|
||||
if doc.spare is not None:
|
||||
doc.spare.Destroy()
|
||||
doc.spare = None
|
||||
return 0
|
||||
|
||||
def Open(self, uri: str, text: str, version: int) -> LspDocument*:
|
||||
if uri is None or text is None:
|
||||
return None
|
||||
existing: LspDocument* = self._Find(uri)
|
||||
if existing is not None:
|
||||
self.Close(uri)
|
||||
|
||||
doc: LspDocument* = (LspDocument*)(self.pool.alloc(LspDocument.__sizeof__()))
|
||||
if doc is None:
|
||||
return None
|
||||
string.memset(doc, 0, LspDocument.__sizeof__())
|
||||
doc.uri = LspPools.DupStr(self.pool, uri)
|
||||
if doc.uri is None:
|
||||
return None
|
||||
|
||||
p1: LspPools.LspPool* = LspPools.Create(self.pool, sizet(LSP_DOC_POOL_SIZE))
|
||||
p2: LspPools.LspPool* = LspPools.Create(self.pool, sizet(LSP_DOC_POOL_SIZE))
|
||||
if p1 is None or p2 is None:
|
||||
if p1 is not None:
|
||||
p1.Destroy()
|
||||
if p2 is not None:
|
||||
p2.Destroy()
|
||||
return None
|
||||
doc.pool = p1
|
||||
doc.spare = p2
|
||||
doc.text = LspPools.DupStr(p1.mb, text)
|
||||
if doc.text is None:
|
||||
self._DestroyDoc(doc)
|
||||
return None
|
||||
doc.index = LspPositions.LspLineIndex(p1.mb)
|
||||
if doc.index is None or doc.index.Build(doc.text) != 0:
|
||||
self._DestroyDoc(doc)
|
||||
return None
|
||||
doc.version = version
|
||||
self.docs.append(doc)
|
||||
self.count += 1
|
||||
return doc
|
||||
|
||||
def ApplyChange(self, uri: str, text: str, has_range: int,
|
||||
start_line: int, start_char: int,
|
||||
end_line: int, end_char: int,
|
||||
version: int) -> int:
|
||||
doc: LspDocument* = self._Find(uri)
|
||||
if doc is None or doc.index is None or doc.spare is None:
|
||||
return -1
|
||||
new_pool: LspPools.LspPool* = doc.spare
|
||||
if new_pool.Reset() < 0:
|
||||
return -1
|
||||
|
||||
old_len: sizet = doc.index.Length()
|
||||
start: sizet = 0
|
||||
end: sizet = old_len
|
||||
if has_range != 0:
|
||||
if doc.index.RangeToOffsets(start_line, start_char, end_line, end_char,
|
||||
&start, &end) != 0:
|
||||
return -1
|
||||
if end > old_len:
|
||||
return -1
|
||||
|
||||
add_len: sizet = 0
|
||||
if text is not None:
|
||||
add_len = string.strlen(text)
|
||||
new_len: sizet = start + add_len + (old_len - end)
|
||||
buf: bytes = new_pool.mb.alloc(new_len + 1)
|
||||
if buf is None:
|
||||
return -1
|
||||
if start > 0:
|
||||
string.memcpy(buf, doc.text, start)
|
||||
if add_len > 0:
|
||||
string.memcpy(buf + start, text, add_len)
|
||||
if old_len > end:
|
||||
string.memcpy(buf + start + add_len, doc.text + end, old_len - end)
|
||||
buf[new_len] = 0
|
||||
|
||||
new_index: LspPositions.LspLineIndex* = LspPositions.LspLineIndex(new_pool.mb)
|
||||
if new_index is None or new_index.Build(str(buf)) != 0:
|
||||
return -1
|
||||
|
||||
old_pool: LspPools.LspPool* = doc.pool
|
||||
doc.pool = new_pool
|
||||
doc.spare = old_pool
|
||||
doc.text = str(buf)
|
||||
doc.index = new_index
|
||||
doc.version = version
|
||||
if old_pool is not None:
|
||||
old_pool.Reset()
|
||||
return 0
|
||||
|
||||
def Close(self, uri: str) -> int:
|
||||
i: int = 0
|
||||
while i < self.count:
|
||||
doc: LspDocument* = self.docs[i]
|
||||
if doc is not None and doc.uri is not None:
|
||||
if string.strcmp(doc.uri, uri) == 0:
|
||||
self._DestroyDoc(doc)
|
||||
self.docs[i] = self.docs[self.count - 1]
|
||||
self.docs.pop()
|
||||
self.count -= 1
|
||||
return 0
|
||||
i += 1
|
||||
return -1
|
||||
|
||||
def Count(self) -> int:
|
||||
return self.count
|
||||
|
||||
def DocAt(self, index: int) -> LspDocument*:
|
||||
if index < 0 or index >= self.count:
|
||||
return None
|
||||
return self.docs[index]
|
||||
|
||||
def UsedBytes(self) -> sizet:
|
||||
total: sizet = 0
|
||||
i: int = 0
|
||||
while i < self.count:
|
||||
doc: LspDocument* = self.docs[i]
|
||||
if doc is not None:
|
||||
if doc.pool is not None:
|
||||
total += doc.pool.UsedBytes()
|
||||
if doc.spare is not None:
|
||||
total += doc.spare.UsedBytes()
|
||||
i += 1
|
||||
return total
|
||||
|
||||
def FreeBytes(self) -> sizet:
|
||||
total: sizet = 0
|
||||
i: int = 0
|
||||
while i < self.count:
|
||||
doc: LspDocument* = self.docs[i]
|
||||
if doc is not None:
|
||||
if doc.pool is not None:
|
||||
total += doc.pool.FreeBytes()
|
||||
if doc.spare is not None:
|
||||
total += doc.spare.FreeBytes()
|
||||
i += 1
|
||||
return total
|
||||
@@ -0,0 +1,72 @@
|
||||
from stdint import *
|
||||
import stdlib
|
||||
import string
|
||||
import memhub
|
||||
|
||||
|
||||
class LspPool:
|
||||
owner: memhub.MemManager*
|
||||
arena: bytes
|
||||
mb: memhub.MemBuddy*
|
||||
size: sizet
|
||||
|
||||
def __new__(self, owner: memhub.MemManager*, size: sizet) -> rawptr:
|
||||
p: LspPool* = (LspPool*)(owner.alloc(LspPool.__sizeof__()))
|
||||
if p:
|
||||
string.memset(p, 0, LspPool.__sizeof__())
|
||||
return rawptr(p)
|
||||
|
||||
def __init__(self, owner: memhub.MemManager*, size: sizet):
|
||||
self.owner = owner
|
||||
self.size = size
|
||||
self.arena = stdlib.malloc(size)
|
||||
self.mb = None
|
||||
if self.arena is not None:
|
||||
self.mb = memhub.NewMemBuddy(self.arena, size, 0, 0)
|
||||
|
||||
def IsValid(self) -> bool:
|
||||
return self.mb is not None
|
||||
|
||||
def Reset(self) -> int:
|
||||
if self.mb is None:
|
||||
return -1
|
||||
return self.mb.reset()
|
||||
|
||||
def Destroy(self) -> int:
|
||||
if self.arena is not None:
|
||||
stdlib.free(self.arena)
|
||||
if self.mb is not None:
|
||||
stdlib.free(self.mb)
|
||||
self.arena = None
|
||||
self.mb = None
|
||||
return 0
|
||||
|
||||
def UsedBytes(self) -> sizet:
|
||||
if self.mb is None:
|
||||
return 0
|
||||
return self.mb.used_bytes()
|
||||
|
||||
def FreeBytes(self) -> sizet:
|
||||
if self.mb is None:
|
||||
return 0
|
||||
return self.mb.free_bytes()
|
||||
|
||||
|
||||
def Create(owner: memhub.MemManager*, size: sizet) -> LspPool*:
|
||||
p: LspPool* = LspPool(owner, size)
|
||||
if p is None or not p.IsValid():
|
||||
if p is not None:
|
||||
p.Destroy()
|
||||
return None
|
||||
return p
|
||||
|
||||
|
||||
def DupStr(pool: memhub.MemManager*, s: str) -> str:
|
||||
if s is None:
|
||||
return None
|
||||
n: sizet = string.strlen(s)
|
||||
buf: bytes = pool.alloc(n + 1)
|
||||
if buf is None:
|
||||
return None
|
||||
string.memcpy(buf, s, n + 1)
|
||||
return str(buf)
|
||||
@@ -0,0 +1,209 @@
|
||||
from stdint import *
|
||||
import string
|
||||
import memhub
|
||||
|
||||
|
||||
def _Utf8Seq(b: uint8) -> int:
|
||||
if b < 0x80:
|
||||
return 1
|
||||
if (b & 0xE0) == 0xC0:
|
||||
return 2
|
||||
if (b & 0xF0) == 0xE0:
|
||||
return 3
|
||||
if (b & 0xF8) == 0xF0:
|
||||
return 4
|
||||
return 1
|
||||
|
||||
|
||||
def _Utf8Units(b: uint8) -> int:
|
||||
if b >= 0xF0:
|
||||
return 2
|
||||
return 1
|
||||
|
||||
|
||||
class LspLineIndex:
|
||||
pool: memhub.MemManager*
|
||||
text: str
|
||||
starts: sizet*
|
||||
ends: sizet*
|
||||
count: int
|
||||
length: sizet
|
||||
|
||||
def __new__(self, pool: memhub.MemManager*) -> rawptr:
|
||||
p: LspLineIndex* = (LspLineIndex*)(pool.alloc(LspLineIndex.__sizeof__()))
|
||||
if p:
|
||||
string.memset(p, 0, LspLineIndex.__sizeof__())
|
||||
return rawptr(p)
|
||||
|
||||
def __init__(self, pool: memhub.MemManager*):
|
||||
self.pool = pool
|
||||
self.text = None
|
||||
self.starts = None
|
||||
self.ends = None
|
||||
self.count = 0
|
||||
self.length = 0
|
||||
|
||||
def Build(self, text: str) -> int:
|
||||
if text is None:
|
||||
return -1
|
||||
self.text = text
|
||||
self.length = string.strlen(text)
|
||||
total: int = int(self.length)
|
||||
|
||||
count: int = 1
|
||||
i: int = 0
|
||||
while i < total:
|
||||
ch: char = self.text[i]
|
||||
if ch == '\n':
|
||||
count += 1
|
||||
i += 1
|
||||
elif ch == '\r':
|
||||
count += 1
|
||||
if i + 1 < total and self.text[i + 1] == '\n':
|
||||
i += 2
|
||||
else:
|
||||
i += 1
|
||||
else:
|
||||
i += 1
|
||||
self.count = count
|
||||
self.starts = (sizet*)(self.pool.alloc(sizet(count) * 8))
|
||||
self.ends = (sizet*)(self.pool.alloc(sizet(count) * 8))
|
||||
if self.starts is None or self.ends is None:
|
||||
return -1
|
||||
|
||||
idx: int = 0
|
||||
start: sizet = 0
|
||||
i = 0
|
||||
while i < total:
|
||||
ch = self.text[i]
|
||||
if ch == '\n':
|
||||
self.starts[idx] = start
|
||||
self.ends[idx] = sizet(i)
|
||||
idx += 1
|
||||
start = sizet(i + 1)
|
||||
i += 1
|
||||
elif ch == '\r':
|
||||
self.starts[idx] = start
|
||||
self.ends[idx] = sizet(i)
|
||||
idx += 1
|
||||
if i + 1 < total and self.text[i + 1] == '\n':
|
||||
i += 2
|
||||
else:
|
||||
i += 1
|
||||
start = sizet(i)
|
||||
else:
|
||||
i += 1
|
||||
self.starts[idx] = start
|
||||
self.ends[idx] = self.length
|
||||
return 0
|
||||
|
||||
def Count(self) -> int:
|
||||
return self.count
|
||||
|
||||
def Length(self) -> sizet:
|
||||
return self.length
|
||||
|
||||
def LineStart(self, line: int) -> sizet:
|
||||
if line < 0:
|
||||
line = 0
|
||||
if line >= self.count:
|
||||
line = self.count - 1
|
||||
return self.starts[line]
|
||||
|
||||
def LineEnd(self, line: int) -> sizet:
|
||||
if line < 0:
|
||||
line = 0
|
||||
if line >= self.count:
|
||||
line = self.count - 1
|
||||
return self.ends[line]
|
||||
|
||||
def ByteColToUtf16(self, line: int, byte_col: int) -> int:
|
||||
if line < 0:
|
||||
line = 0
|
||||
if line >= self.count:
|
||||
line = self.count - 1
|
||||
if byte_col < 0:
|
||||
byte_col = 0
|
||||
start: sizet = self.starts[line]
|
||||
end: sizet = self.ends[line]
|
||||
limit: sizet = start + sizet(byte_col)
|
||||
if limit > end:
|
||||
limit = end
|
||||
i: sizet = start
|
||||
units: int = 0
|
||||
while i < limit:
|
||||
b: uint8 = uint8(self.text[i])
|
||||
units += _Utf8Units(b)
|
||||
seq: int = _Utf8Seq(b)
|
||||
if i + sizet(seq) > limit:
|
||||
i += 1
|
||||
else:
|
||||
i += sizet(seq)
|
||||
return units
|
||||
|
||||
def Utf16ColToByte(self, line: int, utf16_col: int) -> int:
|
||||
if line < 0:
|
||||
line = 0
|
||||
if line >= self.count:
|
||||
line = self.count - 1
|
||||
if utf16_col < 0:
|
||||
utf16_col = 0
|
||||
start: sizet = self.starts[line]
|
||||
end: sizet = self.ends[line]
|
||||
i: sizet = start
|
||||
units: int = 0
|
||||
while i < end:
|
||||
if units >= utf16_col:
|
||||
break
|
||||
b: uint8 = uint8(self.text[i])
|
||||
u: int = _Utf8Units(b)
|
||||
if units + u > utf16_col:
|
||||
break
|
||||
units += u
|
||||
seq: int = _Utf8Seq(b)
|
||||
if i + sizet(seq) > end:
|
||||
i += 1
|
||||
else:
|
||||
i += sizet(seq)
|
||||
return int(i - start)
|
||||
|
||||
def OffsetToPosition(self, offset: sizet, out_line: int*, out_char: int*) -> int:
|
||||
if out_line is None or out_char is None:
|
||||
return -1
|
||||
if offset > self.length:
|
||||
offset = self.length
|
||||
lo: int = 0
|
||||
hi: int = self.count - 1
|
||||
line: int = 0
|
||||
while lo <= hi:
|
||||
mid: int = (lo + hi) >> 1
|
||||
if self.starts[mid] <= offset:
|
||||
line = mid
|
||||
lo = mid + 1
|
||||
else:
|
||||
hi = mid - 1
|
||||
byte_col: int = int(offset - self.starts[line])
|
||||
out_line[0] = line
|
||||
out_char[0] = self.ByteColToUtf16(line, byte_col)
|
||||
return 0
|
||||
|
||||
def PositionToOffset(self, line: int, utf16_col: int) -> sizet:
|
||||
if line < 0:
|
||||
line = 0
|
||||
if line >= self.count:
|
||||
line = self.count - 1
|
||||
byte_col: int = self.Utf16ColToByte(line, utf16_col)
|
||||
return self.starts[line] + sizet(byte_col)
|
||||
|
||||
def RangeToOffsets(self, start_line: int, start_char: int,
|
||||
end_line: int, end_char: int,
|
||||
out_start: sizet*, out_end: sizet*) -> int:
|
||||
if out_start is None or out_end is None:
|
||||
return -1
|
||||
start: sizet = self.PositionToOffset(start_line, start_char)
|
||||
end: sizet = self.PositionToOffset(end_line, end_char)
|
||||
if end < start:
|
||||
return -1
|
||||
out_start[0] = start
|
||||
out_end[0] = end
|
||||
return 0
|
||||
@@ -19,6 +19,19 @@ def GetStringField(obj: json.JsonValue*, key: str) -> str:
|
||||
return v.as_string()
|
||||
|
||||
|
||||
def GetIntField(obj: json.JsonValue*, key: str, default: int) -> int:
|
||||
if obj is None:
|
||||
return default
|
||||
v: json.JsonValue* = obj[key]
|
||||
if v is None:
|
||||
return default
|
||||
if v.is_int():
|
||||
return int(v.as_int())
|
||||
if v.is_float():
|
||||
return int(v.as_float())
|
||||
return default
|
||||
|
||||
|
||||
def _NewEnvelope(pool: memhub.MemManager*, id_node: json.JsonValue*) -> json.JsonValue*:
|
||||
resp: json.JsonValue* = json.object(pool)
|
||||
if resp is None:
|
||||
|
||||
+210
-16
@@ -2,13 +2,25 @@ from stdint import *
|
||||
import string
|
||||
import json
|
||||
import memhub
|
||||
import os
|
||||
import os._win32
|
||||
import os._posix
|
||||
import os.path
|
||||
import lib.lsp.LspProtocol as LspProtocol
|
||||
import lib.lsp.LspTransport as LspTransport
|
||||
import lib.lsp.LspRpc as LspRpc
|
||||
import lib.lsp.LspDocuments as LspDocuments
|
||||
import lib.lsp.LspWorkspace as LspWorkspace
|
||||
|
||||
|
||||
define LSP_SERVER_NAME = "viperc-lsp"
|
||||
define LSP_SERVER_VERSION = "0.1.0-gate0"
|
||||
define LSP_SERVER_VERSION = "0.1.0-gate1"
|
||||
|
||||
|
||||
def _StringOrNull(pool: memhub.MemManager*, s: str) -> json.JsonValue*:
|
||||
if s is None:
|
||||
return json.null(pool)
|
||||
return json.string_val(pool, s)
|
||||
|
||||
|
||||
def _BuildInitializeResult(pool: memhub.MemManager*) -> json.JsonValue*:
|
||||
@@ -18,11 +30,13 @@ def _BuildInitializeResult(pool: memhub.MemManager*) -> json.JsonValue*:
|
||||
enc: json.JsonValue* = json.string_val(pool, "utf-16")
|
||||
name: json.JsonValue* = json.string_val(pool, LSP_SERVER_NAME)
|
||||
ver: json.JsonValue* = json.string_val(pool, LSP_SERVER_VERSION)
|
||||
sync: json.JsonValue* = json.int_val(pool, 2)
|
||||
if result is None or caps is None or info is None:
|
||||
return None
|
||||
if enc is None or name is None or ver is None:
|
||||
if enc is None or name is None or ver is None or sync is None:
|
||||
return None
|
||||
json.object_set(pool, caps, "positionEncoding", enc)
|
||||
json.object_set(pool, caps, "textDocumentSync", sync)
|
||||
json.object_set(pool, result, "capabilities", caps)
|
||||
json.object_set(pool, info, "name", name)
|
||||
json.object_set(pool, info, "version", ver)
|
||||
@@ -30,16 +44,182 @@ def _BuildInitializeResult(pool: memhub.MemManager*) -> json.JsonValue*:
|
||||
return result
|
||||
|
||||
|
||||
def Run(pool: memhub.MemManager*) -> int:
|
||||
tr: LspTransport.LspTransport* = LspTransport.LspTransport(pool)
|
||||
def _HandleInitialize(pool: memhub.MemManager*, tr: LspTransport.LspTransport*,
|
||||
ws: LspWorkspace.LspWorkspace*, id_node: json.JsonValue*,
|
||||
params: json.JsonValue*) -> int:
|
||||
root_uri: str = None
|
||||
if params is not None:
|
||||
root_uri = LspRpc.GetStringField(params, "rootUri")
|
||||
if root_uri is None:
|
||||
folders: json.JsonValue* = params["workspaceFolders"]
|
||||
if folders is not None and folders.is_array() and len(folders) > 0:
|
||||
first: json.JsonValue* = folders.get_item(0)
|
||||
root_uri = LspRpc.GetStringField(first, "uri")
|
||||
if root_uri is None:
|
||||
root_uri = LspRpc.GetStringField(params, "rootPath")
|
||||
if ws.Init(root_uri) != 0:
|
||||
LspTransport.StderrWrite("viperc-lsp: workspace initialization failed; using no workspace\n")
|
||||
result: json.JsonValue* = _BuildInitializeResult(pool)
|
||||
if result is None:
|
||||
return -1
|
||||
return LspRpc.WriteResponse(pool, tr, id_node, result)
|
||||
|
||||
|
||||
def _HandleDidOpen(docs: LspDocuments.LspDocuments*, params: json.JsonValue*) -> int:
|
||||
if params is None:
|
||||
return -1
|
||||
td: json.JsonValue* = params["textDocument"]
|
||||
uri: str = LspRpc.GetStringField(td, "uri")
|
||||
text: str = LspRpc.GetStringField(td, "text")
|
||||
version: int = LspRpc.GetIntField(td, "version", 0)
|
||||
if uri is None or text is None:
|
||||
LspTransport.StderrWrite("viperc-lsp: didOpen missing uri or text\n")
|
||||
return -1
|
||||
if docs.Open(uri, text, version) is None:
|
||||
LspTransport.StderrWrite("viperc-lsp: didOpen failed to store document\n")
|
||||
return -1
|
||||
return 0
|
||||
|
||||
|
||||
def _HandleDidChange(docs: LspDocuments.LspDocuments*, params: json.JsonValue*) -> int:
|
||||
if params is None:
|
||||
return -1
|
||||
td: json.JsonValue* = params["textDocument"]
|
||||
uri: str = LspRpc.GetStringField(td, "uri")
|
||||
version: int = LspRpc.GetIntField(td, "version", 0)
|
||||
changes: json.JsonValue* = params["contentChanges"]
|
||||
if uri is None or changes is None or not changes.is_array():
|
||||
LspTransport.StderrWrite("viperc-lsp: didChange missing uri or contentChanges\n")
|
||||
return -1
|
||||
n: int = int(len(changes))
|
||||
i: int = 0
|
||||
while i < n:
|
||||
ch: json.JsonValue* = changes.get_item(sizet(i))
|
||||
if ch is None or not ch.is_object():
|
||||
return -1
|
||||
text: str = LspRpc.GetStringField(ch, "text")
|
||||
has_range: int = 0
|
||||
sl: int = 0
|
||||
sc: int = 0
|
||||
el: int = 0
|
||||
ec: int = 0
|
||||
rng: json.JsonValue* = ch["range"]
|
||||
if rng is not None:
|
||||
has_range = 1
|
||||
start: json.JsonValue* = rng["start"]
|
||||
end: json.JsonValue* = rng["end"]
|
||||
sl = LspRpc.GetIntField(start, "line", 0)
|
||||
sc = LspRpc.GetIntField(start, "character", 0)
|
||||
el = LspRpc.GetIntField(end, "line", 0)
|
||||
ec = LspRpc.GetIntField(end, "character", 0)
|
||||
if docs.ApplyChange(uri, text, has_range, sl, sc, el, ec, version) != 0:
|
||||
LspTransport.StderrWrite("viperc-lsp: didChange failed to apply a content change\n")
|
||||
return -1
|
||||
i += 1
|
||||
return 0
|
||||
|
||||
|
||||
def _HandleDidClose(docs: LspDocuments.LspDocuments*, params: json.JsonValue*) -> int:
|
||||
if params is None:
|
||||
return -1
|
||||
td: json.JsonValue* = params["textDocument"]
|
||||
uri: str = LspRpc.GetStringField(td, "uri")
|
||||
if uri is None:
|
||||
return -1
|
||||
docs.Close(uri)
|
||||
return 0
|
||||
|
||||
|
||||
def _StatsResult(pool: memhub.MemBuddy*, docs: LspDocuments.LspDocuments*,
|
||||
ws: LspWorkspace.LspWorkspace*) -> json.JsonValue*:
|
||||
result: json.JsonValue* = json.object(pool)
|
||||
if result is None:
|
||||
return None
|
||||
json.object_set(pool, result, "documentCount",
|
||||
json.int_val(pool, int64(docs.Count())))
|
||||
json.object_set(pool, result, "usedBytes",
|
||||
json.int_val(pool, int64(docs.UsedBytes())))
|
||||
json.object_set(pool, result, "freeBytes",
|
||||
json.int_val(pool, int64(docs.FreeBytes())))
|
||||
json.object_set(pool, result, "requestUsedBytes",
|
||||
json.int_val(pool, int64(pool.used_bytes())))
|
||||
json.object_set(pool, result, "requestFreeBytes",
|
||||
json.int_val(pool, int64(pool.free_bytes())))
|
||||
json.object_set(pool, result, "sourceDir", _StringOrNull(pool, ws.SourceDir()))
|
||||
json.object_set(pool, result, "includes", _StringOrNull(pool, ws.Includes()))
|
||||
json.object_set(pool, result, "project", _StringOrNull(pool, ws.ProjectPath()))
|
||||
|
||||
arr: json.JsonValue* = json.array(pool)
|
||||
if arr is None:
|
||||
return None
|
||||
i: int = 0
|
||||
while i < docs.Count():
|
||||
doc: LspDocuments.LspDocument* = docs.DocAt(i)
|
||||
if doc is not None:
|
||||
item: json.JsonValue* = json.object(pool)
|
||||
if item is not None:
|
||||
json.object_set(pool, item, "uri", _StringOrNull(pool, doc.uri))
|
||||
json.object_set(pool, item, "version", json.int_val(pool, int64(doc.version)))
|
||||
if doc.index is not None:
|
||||
json.object_set(pool, item, "textLength",
|
||||
json.int_val(pool, int64(doc.index.Length())))
|
||||
json.array_append(pool, arr, item)
|
||||
i += 1
|
||||
json.object_set(pool, result, "documents", arr)
|
||||
return result
|
||||
|
||||
|
||||
def _HandleStats(pool: memhub.MemBuddy*, tr: LspTransport.LspTransport*,
|
||||
docs: LspDocuments.LspDocuments*, ws: LspWorkspace.LspWorkspace*,
|
||||
id_node: json.JsonValue*) -> int:
|
||||
result: json.JsonValue* = _StatsResult(pool, docs, ws)
|
||||
if result is None:
|
||||
return -1
|
||||
return LspRpc.WriteResponse(pool, tr, id_node, result)
|
||||
|
||||
|
||||
def _HandleDocument(pool: memhub.MemManager*, tr: LspTransport.LspTransport*,
|
||||
docs: LspDocuments.LspDocuments*, id_node: json.JsonValue*,
|
||||
params: json.JsonValue*) -> int:
|
||||
uri: str = None
|
||||
if params is not None:
|
||||
uri = LspRpc.GetStringField(params, "uri")
|
||||
doc: LspDocuments.LspDocument* = docs.Find(uri)
|
||||
if doc is None:
|
||||
return LspRpc.WriteResponse(pool, tr, id_node, json.null(pool))
|
||||
result: json.JsonValue* = json.object(pool)
|
||||
if result is None:
|
||||
return -1
|
||||
json.object_set(pool, result, "uri", _StringOrNull(pool, doc.uri))
|
||||
json.object_set(pool, result, "version", json.int_val(pool, int64(doc.version)))
|
||||
if doc.index is not None:
|
||||
json.object_set(pool, result, "textLength", json.int_val(pool, int64(doc.index.Length())))
|
||||
json.object_set(pool, result, "text", _StringOrNull(pool, doc.text))
|
||||
return LspRpc.WriteResponse(pool, tr, id_node, result)
|
||||
|
||||
|
||||
def Run(pool: memhub.MemBuddy*, perm_pool: memhub.MemBuddy*) -> int:
|
||||
os._mbuddy = perm_pool
|
||||
os._win32._mbuddy = perm_pool
|
||||
os._posix._mbuddy = perm_pool
|
||||
os.path._mbuddy = perm_pool
|
||||
|
||||
tr: LspTransport.LspTransport* = LspTransport.LspTransport(perm_pool, pool)
|
||||
if tr is None or not tr.IsValid():
|
||||
LspTransport.StderrWrite("viperc-lsp: invalid stdio handles or transport allocation failed\n")
|
||||
return 1
|
||||
docs: LspDocuments.LspDocuments* = LspDocuments.LspDocuments(perm_pool)
|
||||
ws: LspWorkspace.LspWorkspace* = LspWorkspace.LspWorkspace(perm_pool)
|
||||
if docs is None or ws is None:
|
||||
LspTransport.StderrWrite("viperc-lsp: failed to allocate the document or workspace store\n")
|
||||
return 1
|
||||
|
||||
shutdown_requested: int = 0
|
||||
while True:
|
||||
mark: sizet = pool.mark()
|
||||
body: str = tr.ReadMessage()
|
||||
if body is None:
|
||||
pool.reset_to(mark)
|
||||
if tr.error != 0:
|
||||
LspTransport.StderrWrite("viperc-lsp: malformed message framing\n")
|
||||
return 1
|
||||
@@ -50,6 +230,7 @@ def Run(pool: memhub.MemManager*) -> int:
|
||||
if LspRpc.WriteError(pool, tr, None, LspProtocol.LSP_ERROR_PARSE,
|
||||
"Invalid JSON") != 0:
|
||||
return 1
|
||||
pool.reset_to(mark)
|
||||
continue
|
||||
|
||||
id_node: json.JsonValue* = msg["id"]
|
||||
@@ -60,26 +241,39 @@ def Run(pool: memhub.MemManager*) -> int:
|
||||
LspProtocol.LSP_ERROR_INVALID_REQUEST,
|
||||
"Invalid Request") != 0:
|
||||
return 1
|
||||
pool.reset_to(mark)
|
||||
continue
|
||||
|
||||
params: json.JsonValue* = msg["params"]
|
||||
rc: int = 0
|
||||
if string.strcmp(method, LspProtocol.LSP_METHOD_INITIALIZE) == 0:
|
||||
result: json.JsonValue* = _BuildInitializeResult(pool)
|
||||
if result is None:
|
||||
return 1
|
||||
if LspRpc.WriteResponse(pool, tr, id_node, result) != 0:
|
||||
return 1
|
||||
rc = _HandleInitialize(pool, tr, ws, id_node, params)
|
||||
elif string.strcmp(method, LspProtocol.LSP_METHOD_INITIALIZED) == 0:
|
||||
continue
|
||||
pass
|
||||
elif string.strcmp(method, "textDocument/didOpen") == 0:
|
||||
_HandleDidOpen(docs, params)
|
||||
elif string.strcmp(method, "textDocument/didChange") == 0:
|
||||
_HandleDidChange(docs, params)
|
||||
elif string.strcmp(method, "textDocument/didClose") == 0:
|
||||
_HandleDidClose(docs, params)
|
||||
elif string.strcmp(method, "textDocument/didSave") == 0:
|
||||
pass
|
||||
elif string.strcmp(method, "viper/lspStats") == 0:
|
||||
rc = _HandleStats(pool, tr, docs, ws, id_node)
|
||||
elif string.strcmp(method, "viper/lspDocument") == 0:
|
||||
rc = _HandleDocument(pool, tr, docs, id_node, params)
|
||||
elif string.strcmp(method, LspProtocol.LSP_METHOD_SHUTDOWN) == 0:
|
||||
shutdown_requested = 1
|
||||
if LspRpc.WriteResponse(pool, tr, id_node, json.null(pool)) != 0:
|
||||
return 1
|
||||
rc = LspRpc.WriteResponse(pool, tr, id_node, json.null(pool))
|
||||
elif string.strcmp(method, LspProtocol.LSP_METHOD_EXIT) == 0:
|
||||
return 0 if shutdown_requested != 0 else 1
|
||||
else:
|
||||
if id_node is not None:
|
||||
if LspRpc.WriteError(pool, tr, id_node,
|
||||
LspProtocol.LSP_ERROR_METHOD_NOT_FOUND,
|
||||
"Method not found") != 0:
|
||||
return 1
|
||||
rc = LspRpc.WriteError(pool, tr, id_node,
|
||||
LspProtocol.LSP_ERROR_METHOD_NOT_FOUND,
|
||||
"Method not found")
|
||||
|
||||
if rc != 0:
|
||||
return 1
|
||||
pool.reset_to(mark)
|
||||
return 0
|
||||
@@ -1,4 +1,5 @@
|
||||
from stdint import *
|
||||
import stdlib
|
||||
import string
|
||||
import viperlib
|
||||
import memhub
|
||||
@@ -22,18 +23,18 @@ class LspTransport:
|
||||
eof: int
|
||||
error: int
|
||||
|
||||
def __new__(self, pool: memhub.MemManager*) -> rawptr:
|
||||
ptr: LspTransport* = (LspTransport*)(pool.alloc(LspTransport.__sizeof__()))
|
||||
def __new__(self, owner: memhub.MemManager*, request: memhub.MemManager*) -> rawptr:
|
||||
ptr: LspTransport* = (LspTransport*)(owner.alloc(LspTransport.__sizeof__()))
|
||||
if ptr:
|
||||
string.memset(ptr, 0, LspTransport.__sizeof__())
|
||||
return rawptr(ptr)
|
||||
|
||||
def __init__(self, pool: memhub.MemManager*):
|
||||
self.pool = pool
|
||||
def __init__(self, owner: memhub.MemManager*, request: memhub.MemManager*):
|
||||
self.pool = request
|
||||
self.in_handle = win32.win32file.GetStdHandle(win32.win32file.STD_INPUT_HANDLE)
|
||||
self.out_handle = win32.win32file.GetStdHandle(win32.win32file.STD_OUTPUT_HANDLE)
|
||||
self.buf_cap = sizet(LSP_READ_BUFFER_SIZE)
|
||||
self.buf = pool.alloc(self.buf_cap)
|
||||
self.buf = stdlib.malloc(self.buf_cap)
|
||||
self.buf_len = 0
|
||||
self.buf_pos = 0
|
||||
self.eof = 0
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
from stdint import *
|
||||
import string
|
||||
import os
|
||||
import os._win32
|
||||
import os._posix
|
||||
import os.path
|
||||
import memhub
|
||||
import lib.lsp.LspPools as LspPools
|
||||
import lib.Projectrans.Config as Config
|
||||
|
||||
|
||||
def _HexVal(ch: char) -> int:
|
||||
if ch >= '0' and ch <= '9':
|
||||
return int(ch) - int('0')
|
||||
if ch >= 'a' and ch <= 'f':
|
||||
return int(ch) - int('a') + 10
|
||||
if ch >= 'A' and ch <= 'F':
|
||||
return int(ch) - int('A') + 10
|
||||
return -1
|
||||
|
||||
|
||||
def DecodeUri(pool: memhub.MemManager*, uri: str) -> str:
|
||||
if uri is None or uri[0] == 0:
|
||||
return None
|
||||
p: str = uri
|
||||
if string.strncmp(uri, "file://", 7) == 0:
|
||||
p = uri + 7
|
||||
if p[0] == '/' and len(p) >= 3 and p[2] == ':':
|
||||
p = p + 1
|
||||
n: sizet = string.strlen(p)
|
||||
out: bytes = pool.alloc(n + 1)
|
||||
if out is None:
|
||||
return None
|
||||
i: sizet = 0
|
||||
o: sizet = 0
|
||||
while i < n:
|
||||
ch: char = p[i]
|
||||
if ch == '%' and i + 2 < n:
|
||||
hi: int = _HexVal(p[i + 1])
|
||||
lo: int = _HexVal(p[i + 2])
|
||||
if hi >= 0 and lo >= 0:
|
||||
out[o] = char(hi * 16 + lo)
|
||||
o += 1
|
||||
i += 3
|
||||
continue
|
||||
out[o] = ch
|
||||
o += 1
|
||||
i += 1
|
||||
out[o] = 0
|
||||
return str(out)
|
||||
|
||||
|
||||
class LspWorkspace:
|
||||
pool: memhub.MemBuddy*
|
||||
root: str
|
||||
project: str
|
||||
source_dir: str
|
||||
includes: str
|
||||
ready: int
|
||||
|
||||
def __new__(self, pool: memhub.MemBuddy*) -> rawptr:
|
||||
p: LspWorkspace* = (LspWorkspace*)(pool.alloc(LspWorkspace.__sizeof__()))
|
||||
if p:
|
||||
string.memset(p, 0, LspWorkspace.__sizeof__())
|
||||
return rawptr(p)
|
||||
|
||||
def __init__(self, pool: memhub.MemBuddy*):
|
||||
self.pool = pool
|
||||
self.root = None
|
||||
self.project = None
|
||||
self.source_dir = None
|
||||
self.includes = None
|
||||
self.ready = 0
|
||||
|
||||
def FindProject(self, start: str) -> str:
|
||||
if start is None or start[0] == 0:
|
||||
return None
|
||||
current: str = start
|
||||
i: int = 0
|
||||
while i < 32:
|
||||
cand: str = os.path.join(current, "project.vpj")
|
||||
if cand is not None and os.isfile(cand):
|
||||
return cand
|
||||
parent: str = os.path.dirname(current)
|
||||
if parent is None or parent[0] == 0:
|
||||
break
|
||||
if string.strcmp(parent, current) == 0:
|
||||
break
|
||||
current = parent
|
||||
i += 1
|
||||
return None
|
||||
|
||||
def Init(self, root_uri: str) -> int:
|
||||
path: str = DecodeUri(self.pool, root_uri)
|
||||
if path is None or path[0] == 0:
|
||||
cwd: str = os.getcwd()
|
||||
if cwd is None:
|
||||
cwd = "."
|
||||
path = LspPools.DupStr(self.pool, cwd)
|
||||
if path is None:
|
||||
return -1
|
||||
self.root = path
|
||||
self.project = self.FindProject(path)
|
||||
|
||||
if self.project is not None:
|
||||
old_pool: memhub.MemBuddy* = Config._mbuddy
|
||||
Config._mbuddy = self.pool
|
||||
rc: int = Config.Load_project_config(self.project)
|
||||
if rc == 0:
|
||||
Config.resolve_paths(os.path.dirname(self.project))
|
||||
self.source_dir = Config.SourceDir
|
||||
self.includes = Config.IncludesDirs
|
||||
Config._mbuddy = old_pool
|
||||
|
||||
if self.source_dir is None or self.source_dir[0] == 0:
|
||||
self.source_dir = self.root
|
||||
|
||||
if self.includes is None or self.includes[0] == 0:
|
||||
sibling: str = os.path.join(self.root, "includes")
|
||||
if os.isdir(sibling):
|
||||
self.includes = sibling
|
||||
else:
|
||||
parent_sibling: str = os.path.join(self.root, "../includes")
|
||||
if os.isdir(parent_sibling):
|
||||
self.includes = parent_sibling
|
||||
else:
|
||||
self.includes = ""
|
||||
self.ready = 1
|
||||
return 0
|
||||
|
||||
def Root(self) -> str:
|
||||
return self.root
|
||||
|
||||
def ProjectPath(self) -> str:
|
||||
return self.project
|
||||
|
||||
def SourceDir(self) -> str:
|
||||
return self.source_dir
|
||||
|
||||
def Includes(self) -> str:
|
||||
return self.includes
|
||||
+1
-1
@@ -227,7 +227,7 @@ Target: %s
|
||||
argparse.release(args)
|
||||
return 2
|
||||
argparse.release(args)
|
||||
return LspServer.Run(mb)
|
||||
return LspServer.Run(mb, perm_pool)
|
||||
|
||||
# --build-self is a standalone mode: it compiles this project into __vpcbuild_2__,
|
||||
# then launches copyself to replace __vpcbuild__ after a delay.
|
||||
|
||||
+149
-1
@@ -10,6 +10,7 @@ import queue
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
@@ -138,9 +139,154 @@ def lifecycle(exe: str) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def documents(exe: str) -> int:
|
||||
root = Path(__file__).resolve().parents[2]
|
||||
root_uri = root.as_uri()
|
||||
uri = (root / "Tools" / "lsp" / "fixtures" / "gate1.vp").as_uri()
|
||||
client = Client(exe)
|
||||
response = client.request(
|
||||
"initialize",
|
||||
{"processId": os.getpid(), "rootUri": root_uri, "capabilities": {}},
|
||||
)
|
||||
result = response.get("result")
|
||||
if not isinstance(result, dict):
|
||||
raise AssertionError(f"initialize result is not an object: {response}")
|
||||
capabilities = result.get("capabilities")
|
||||
if not isinstance(capabilities, dict):
|
||||
raise AssertionError(f"missing capabilities: {response}")
|
||||
if capabilities.get("positionEncoding") != "utf-16":
|
||||
raise AssertionError(f"positionEncoding is not utf-16: {response}")
|
||||
if capabilities.get("textDocumentSync") != 2:
|
||||
raise AssertionError(f"textDocumentSync is not incremental: {response}")
|
||||
client.notify("initialized", {})
|
||||
|
||||
text = "def f(a: int) -> int:\r\n return a\r\n\r\n# 中文🙂\r\nx: int = 1\r\n"
|
||||
client.notify(
|
||||
"textDocument/didOpen",
|
||||
{"textDocument": {"uri": uri, "languageId": "viper", "version": 1, "text": text}},
|
||||
)
|
||||
|
||||
stats = client.request("viper/lspStats").get("result")
|
||||
if not isinstance(stats, dict) or stats.get("documentCount") != 1:
|
||||
raise AssertionError(f"didOpen was not stored: {stats}")
|
||||
source_dir = str(stats.get("sourceDir", "")).replace("\\", "/")
|
||||
if not source_dir.endswith("ViperC/App"):
|
||||
raise AssertionError(f"workspace sourceDir is wrong: {stats}")
|
||||
if "includes" not in str(stats.get("includes", "")):
|
||||
raise AssertionError(f"workspace includes is wrong: {stats}")
|
||||
if not str(stats.get("project", "")).endswith("project.vpj"):
|
||||
raise AssertionError(f"workspace project is wrong: {stats}")
|
||||
|
||||
doc = client.request("viper/lspDocument", {"uri": uri}).get("result")
|
||||
if not isinstance(doc, dict) or doc.get("text") != text or doc.get("version") != 1:
|
||||
raise AssertionError(f"initial document text/version mismatch: {doc}")
|
||||
|
||||
client.notify(
|
||||
"textDocument/didChange",
|
||||
{
|
||||
"textDocument": {"uri": uri, "version": 2},
|
||||
"contentChanges": [
|
||||
{
|
||||
"range": {
|
||||
"start": {"line": 1, "character": 11},
|
||||
"end": {"line": 1, "character": 12},
|
||||
},
|
||||
"text": "a + 1",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
doc = client.request("viper/lspDocument", {"uri": uri}).get("result")
|
||||
if not isinstance(doc, dict) or "return a + 1" not in doc.get("text", ""):
|
||||
raise AssertionError(f"incremental ASCII edit failed: {doc}")
|
||||
if doc.get("version") != 2:
|
||||
raise AssertionError(f"version was not updated: {doc}")
|
||||
|
||||
client.notify(
|
||||
"textDocument/didChange",
|
||||
{
|
||||
"textDocument": {"uri": uri, "version": 3},
|
||||
"contentChanges": [
|
||||
{
|
||||
"range": {
|
||||
"start": {"line": 3, "character": 4},
|
||||
"end": {"line": 3, "character": 6},
|
||||
},
|
||||
"text": "X",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
doc = client.request("viper/lspDocument", {"uri": uri}).get("result")
|
||||
if not isinstance(doc, dict) or "# 中文X" not in doc.get("text", ""):
|
||||
raise AssertionError(f"UTF-16 surrogate edit failed: {doc}")
|
||||
|
||||
replacement = "def f() -> int:\r\n return 42\r\n"
|
||||
client.notify(
|
||||
"textDocument/didChange",
|
||||
{
|
||||
"textDocument": {"uri": uri, "version": 4},
|
||||
"contentChanges": [{"text": replacement}],
|
||||
},
|
||||
)
|
||||
doc = client.request("viper/lspDocument", {"uri": uri}).get("result")
|
||||
if not isinstance(doc, dict) or doc.get("text") != replacement or doc.get("version") != 4:
|
||||
raise AssertionError(f"full replacement failed: {doc}")
|
||||
|
||||
client.notify("textDocument/didSave", {"textDocument": {"uri": uri}})
|
||||
client.notify("textDocument/didClose", {"textDocument": {"uri": uri}})
|
||||
stats = client.request("viper/lspStats").get("result")
|
||||
if not isinstance(stats, dict) or stats.get("documentCount") != 0:
|
||||
raise AssertionError(f"didClose was not applied: {stats}")
|
||||
|
||||
edit_text = "x = 0\n"
|
||||
client.notify(
|
||||
"textDocument/didOpen",
|
||||
{"textDocument": {"uri": uri, "languageId": "viper", "version": 1, "text": edit_text}},
|
||||
)
|
||||
for i in range(10000):
|
||||
digit = "1" if (i % 2) == 0 else "0"
|
||||
client.notify(
|
||||
"textDocument/didChange",
|
||||
{
|
||||
"textDocument": {"uri": uri, "version": 2 + i},
|
||||
"contentChanges": [
|
||||
{
|
||||
"range": {
|
||||
"start": {"line": 0, "character": 4},
|
||||
"end": {"line": 0, "character": 5},
|
||||
},
|
||||
"text": digit,
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
stats = client.request("viper/lspStats").get("result")
|
||||
if not isinstance(stats, dict) or stats.get("documentCount") != 1:
|
||||
raise AssertionError(f"10k edits lost the document: {stats}")
|
||||
doc = client.request("viper/lspDocument", {"uri": uri}).get("result")
|
||||
if not isinstance(doc, dict) or doc.get("text") != edit_text:
|
||||
raise AssertionError(f"10k edits produced unexpected text: {doc}")
|
||||
if doc.get("version") != 10001:
|
||||
raise AssertionError(f"10k edits produced unexpected version: {doc}")
|
||||
if int(stats.get("usedBytes", 0)) > 8 * 1024 * 1024:
|
||||
raise AssertionError(f"document pool grew unexpectedly: {stats}")
|
||||
|
||||
client.notify("textDocument/didClose", {"textDocument": {"uri": uri}})
|
||||
client.request("shutdown")
|
||||
code = client.close()
|
||||
if code != 0:
|
||||
raise AssertionError(f"exit code is {code}, expected 0")
|
||||
print("documents: OK")
|
||||
print("positions: OK")
|
||||
print("10k-edits: OK")
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="ViperC LSP probe (Gate 0)")
|
||||
parser = argparse.ArgumentParser(description="ViperC LSP probe")
|
||||
parser.add_argument("--selfcheck", action="store_true", help="run pure-Python framing checks")
|
||||
parser.add_argument("--gate1", action="store_true", help="run document/position checks")
|
||||
parser.add_argument("--exe", help="path to viperc.exe")
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -152,6 +298,8 @@ def main() -> int:
|
||||
if not os.path.isfile(exe):
|
||||
print(f"probe: executable not found: {exe}", file=sys.stderr)
|
||||
return 2
|
||||
if args.gate1:
|
||||
return documents(exe)
|
||||
return lifecycle(exe)
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user