feat(lsp): hover 支持字面量、模块路径与位宽分析

新增 LspHover:目标 hover 保留原有签名/文档,模块补充工作区相对路径;光标在数字/字符串字面量上时显示十进制、十六进制、二进制、最小位宽、最小二幂整型宽度(u8..u128 / i8..i128)以及 f16/f32/f64 的精确性;True/False/None 也有 hover。十六进制与十进制改为手写 nibble/逐位输出,避开运行库变参格式化对宽值的问题。gate10 增加模块/整数/hex/bin/浮点/字符串 hover 断言。
This commit is contained in:
TermiNexus committed 2026-10-28 13:37:40 +08:00
1 parent 6baad4a6e6
commit d1f84371f8
5 files changed
+745 -35

No files matched your search

+686
View File
@@ -0,0 +1,686 @@
from stdint import *
import string
import json
import memhub
import ast
import math
import lib.lsp.LspDocuments as LspDocuments
import lib.lsp.LspSource as LspSource
import lib.lsp.LspSymbols as LspSymbols
import lib.lsp.LspIndex as LspIndex
import lib.lsp.LspWorkspace as LspWorkspace
import lib.lsp.LspTransport as LspTransport
import lib.lsp.LspRpc as LspRpc
import lib.lsp.LspNavigation as LspNavigation
# Longest literal text shown verbatim in a hover (a docstring can be huge).
define HOVER_LITERAL_TEXT_CAP = 160
define HOVER_U64_MAX = 18446744073709551615
# =============================================================================
# Small text helpers
# =============================================================================
def _Cat(pool: memhub.MemManager*, a: str, b: str) -> str:
return LspNavigation._Cat(pool, a, b)
def _CodeBlock(pool: memhub.MemManager*, body: str) -> str:
"""Wrap hover body text in a Viper fenced code block (alignment is preserved)."""
if body is None:
return None
return _Cat(pool, _Cat(pool, "```viper\n", body), "\n```")
def _LineStart(doc: LspDocuments.LspDocument*, line0: int) -> sizet:
return doc.index.LineStart(line0)
def _ByteAt(doc: LspDocuments.LspDocument*, line0: int, col: int) -> char:
if doc is None or doc.index is None:
return char(0)
if col < 0 or col >= LspSource.LineByteLength(doc, line0):
return char(0)
return doc.text[_LineStart(doc, line0) + sizet(col)]
def _IsDigit(ch: char) -> int:
return 1 if ch >= '0' and ch <= '9' else 0
def _IsLetter(ch: char) -> int:
if ch >= 'a' and ch <= 'z':
return 1
if ch >= 'A' and ch <= 'Z':
return 1
return 0
def _IsLitChar(ch: char) -> int:
if _IsDigit(ch) != 0 or _IsLetter(ch) != 0:
return 1
if ch == '_' or ch == '.':
return 1
return 0
# =============================================================================
# Literal lookup
# =============================================================================
def _InsideString(doc: LspDocuments.LspDocument*, line0: int, bcol: int) -> int:
"""1 when the cursor sits inside a single-line quoted literal (content or quote)."""
limit: int = LspSource.LineByteLength(doc, line0)
i: int = 0
quote: char = char(0)
escaped: int = 0
while i < limit:
ch: char = _ByteAt(doc, line0, i)
if quote == char(0):
if ch == '#':
return 0
if ch == '\'' or ch == '"':
if i == bcol:
return 1
quote = ch
else:
if i == bcol:
return 1
if escaped != 0:
escaped = 0
elif ch == '\\':
escaped = 1
elif ch == quote:
quote = char(0)
i += 1
return 0
def _LiteralCandidate(doc: LspDocuments.LspDocument*, line0: int, bcol: int) -> int:
"""Cheap pre-check before tokenizing: can the cursor be inside a literal?
Walking back over literal characters rejects plain identifiers (which are handled by the
symbol path) while still accepting every character of `0x1F`, `1e9`, `128_u8` and prefixed
strings such as `b"..."`. String content is handled separately because it may contain any
character.
"""
if doc is None or doc.index is None or line0 < 0 or line0 >= doc.index.Count():
return 0
limit: int = LspSource.LineByteLength(doc, line0)
if bcol < 0 or bcol >= limit:
return 0
if _InsideString(doc, line0, bcol) != 0:
return 1
start: int = bcol
while start > 0:
if _IsLitChar(_ByteAt(doc, line0, start - 1)) != 0:
start -= 1
else:
break
ch0: char = _ByteAt(doc, line0, start)
if _IsDigit(ch0) != 0:
return 1
if ch0 == '.':
return 1 if _IsDigit(_ByteAt(doc, line0, start + 1)) != 0 else 0
if ch0 == '\'' or ch0 == '"':
return 1
if ch0 == 'r' or ch0 == 'R' or ch0 == 'b' or ch0 == 'B' \
or ch0 == 'f' or ch0 == 'F' or ch0 == 'u' or ch0 == 'U':
nx: char = _ByteAt(doc, line0, start + 1)
if nx == '\'' or nx == '"':
return 1
return 0
def _TokenContains(tok: ast.Token*, line0: int, bcol: int) -> int:
if tok is None:
return 0
sl: int = tok.lineno - 1
el: int = tok.end_lineno - 1
if line0 < sl or line0 > el:
return 0
if line0 == sl and bcol < tok.col_offset:
return 0
if line0 == el and bcol >= tok.end_col_offset:
return 0
return 1
def _FindLiteral(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*,
line0: int, ch: int) -> ast.Token*:
"""Return the number/string token under the cursor, or None."""
if doc is None or doc.index is None:
return None
bcol: int = LspSource.Utf16ColToByte(doc, line0, ch)
if _LiteralCandidate(doc, line0, bcol) == 0:
return None
lx: ast.Lexer* = ast.new_lexer(pool)
if lx is None:
return None
ast._lexer_init(lx, doc.text, pool)
tok: ast.Token* = ast.tokenize(lx)
while tok is not None:
if tok.lineno - 1 > line0:
break
if _TokenContains(tok, line0, bcol) != 0:
if tok.type == ast.TokenType.Number or tok.type == ast.TokenType.String:
return tok
return None
tok = tok.next
return None
def _WordAt(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*,
line0: int, ch: int) -> str:
"""Identifier-shaped text under the cursor (used for True/False/None literals)."""
if doc is None or doc.index is None or line0 < 0 or line0 >= doc.index.Count():
return None
limit: int = LspSource.LineByteLength(doc, line0)
bcol: int = LspSource.Utf16ColToByte(doc, line0, ch)
if bcol < 0 or bcol >= limit:
return None
start: int = bcol
while start > 0:
if LspSource._IsIdentChar(_ByteAt(doc, line0, start - 1)) != 0:
start -= 1
else:
break
end: int = bcol
while end < limit:
if LspSource._IsIdentChar(_ByteAt(doc, line0, end)) != 0:
end += 1
else:
break
if end <= start:
return None
return LspSource.SliceBytes(pool, doc, line0, start, line0, end)
# =============================================================================
# Integer literal analysis
# =============================================================================
def _DigitValue(ch: char) -> int:
if ch >= '0' and ch <= '9':
return int(ch) - 48
if ch >= 'a' and ch <= 'f':
return int(ch) - 87
if ch >= 'A' and ch <= 'F':
return int(ch) - 55
return -1
def _ParseMagnitude(text: str, overflow: int*) -> uint64:
"""Parse an unsigned integer literal body (0x/0o/0b/decimal) before any width suffix."""
overflow[0] = 0
if text is None:
return uint64(0)
n: sizet = string.strlen(text)
if n == 0:
return uint64(0)
base: uint64 = uint64(10)
i: sizet = 0
if n >= 2 and text[0] == '0':
c: char = text[1]
if c == 'x' or c == 'X':
base = uint64(16)
i = 2
elif c == 'b' or c == 'B':
base = uint64(2)
i = 2
elif c == 'o' or c == 'O':
base = uint64(8)
i = 2
v: uint64 = uint64(0)
while i < n:
ch: char = text[i]
if ch == '_':
# `_` is either a digit separator or the start of an explicit width suffix.
if i + 1 >= n:
break
d2: int = _DigitValue(text[i + 1])
if d2 < 0 or uint64(d2) >= base:
break
i += 1
continue
d: int = _DigitValue(ch)
if d < 0 or uint64(d) >= base:
break
if v > (uint64(HOVER_U64_MAX) - uint64(d)) / base:
overflow[0] = 1
return uint64(0)
v = v * base + uint64(d)
i += 1
return v
def _U64Bits(v: uint64) -> int:
"""Number of significant bits; zero still needs one bit."""
if v == uint64(0):
return 1
n: int = 0
x: uint64 = v
while x != uint64(0):
n += 1
x = x >> uint64(1)
return n
def _TrailingZeros(v: uint64) -> int:
if v == uint64(0):
return 64
n: int = 0
x: uint64 = v
while (x & uint64(1)) == uint64(0):
n += 1
x = x >> uint64(1)
return n
def _PowTwoWidth(bits: int) -> int:
"""Smallest power-of-two width >= bits from the language's integer sizes (8..128)."""
if bits <= 8:
return 8
if bits <= 16:
return 16
if bits <= 32:
return 32
if bits <= 64:
return 64
return 128
def _HexU64Digits(pool: memhub.MemManager*, v: uint64, min_digits: int) -> str:
"""Uppercase hex with at least min_digits digits, written nibble by nibble.
snprintf's `%llX` is avoided: this runtime mis-prints wide values through the variadic path.
"""
digits: int = (_U64Bits(v) + 3) / 4
if digits < min_digits:
digits = min_digits
buf: bytes = pool.alloc(sizet(digits + 3))
if buf is None:
return None
buf[0] = '0'
buf[1] = 'x'
p: sizet = 2
i: int = digits - 1
while i >= 0:
nib: uint64 = (v >> uint64(i * 4)) & uint64(15)
if nib < uint64(10):
buf[p] = char(48 + int(nib))
else:
buf[p] = char(55 + int(nib))
p += 1
i -= 1
buf[p] = 0
return str(buf)
def _HexU64(pool: memhub.MemManager*, v: uint64) -> str:
return _HexU64Digits(pool, v, 1)
def _HexU64Fixed(pool: memhub.MemManager*, v: uint64) -> str:
"""16 hex digits: the canonical f64 bit-pattern spelling."""
return _HexU64Digits(pool, v, 16)
def _BinU64(pool: memhub.MemManager*, v: uint64) -> str:
"""Binary text grouped in nibbles from the least significant bit."""
bits: int = _U64Bits(v)
seps: int = (bits - 1) / 4
buf: bytes = pool.alloc(sizet(2 + bits + seps + 1))
if buf is None:
return None
buf[0] = '0'
buf[1] = 'b'
p: sizet = 2
i: int = bits - 1
while i >= 0:
# Group from the least significant bit so every 4-bit group matches a hex nibble.
if i != bits - 1 and (i + 1) % 4 == 0:
buf[p] = '_'
p += 1
if ((v >> uint64(i)) & uint64(1)) != uint64(0):
buf[p] = '1'
else:
buf[p] = '0'
p += 1
i -= 1
buf[p] = 0
return str(buf)
def _IntFloatExact(v: uint64, pbits: int, maxv: uint64) -> int:
"""Whether an integer is exactly representable in a binary float with pbits significand bits."""
if v == uint64(0):
return 1
if v > maxv:
return 0
return 1 if (_U64Bits(v) - _TrailingZeros(v)) <= pbits else 0
def _IntFloatLine(pool: memhub.MemManager*, v: uint64) -> str:
f16: str = "rounded"
if v > uint64(65520):
f16 = "overflow"
elif _IntFloatExact(v, 11, uint64(65504)) != 0:
f16 = "exact"
f32: str = "exact" if _IntFloatExact(v, 24, uint64(HOVER_U64_MAX)) != 0 else "rounded"
f64: str = "exact" if _IntFloatExact(v, 53, uint64(HOVER_U64_MAX)) != 0 else "rounded"
return _Cat(pool, _Cat(pool, _Cat(pool, _Cat(pool, _Cat(pool,
"f16 ", f16), ", f32 "), f32), ", f64 "), f64)
def _IntMarkdown(pool: memhub.MemManager*, tok: ast.Token*) -> str:
ovf: int = 0
v: uint64 = _ParseMagnitude(tok.str_val, &ovf)
head: str = tok.str_val
if head is None:
head = "0"
body: str = head
if ovf != 0:
return _CodeBlock(pool, _Cat(pool, head, "\nvalue exceeds 64 bits (unsigned)"))
body = _Cat(pool, body, _Cat(pool, "\ndec ", _DecU64(pool, v)))
body = _Cat(pool, body, _Cat(pool, "\nhex ", _HexU64(pool, v)))
body = _Cat(pool, body, _Cat(pool, "\nbinary ", _BinU64(pool, v)))
ub: int = _U64Bits(v)
sb: int = ub + 1
bits_line: str = _Cat(pool, _Cat(pool, _Cat(pool, _Cat(pool,
"\nbits ", _U64ToString(pool, uint64(ub))),
" unsigned, "),
_U64ToString(pool, uint64(sb))),
" signed")
body = _Cat(pool, body, bits_line)
uw: int = _PowTwoWidth(ub)
iw: int = _PowTwoWidth(sb)
body = _Cat(pool, body, _Cat(pool, "\nuint u", _U64ToString(pool, uint64(uw))))
body = _Cat(pool, body, _Cat(pool, "\nint i", _U64ToString(pool, uint64(iw))))
body = _Cat(pool, body, _Cat(pool, "\nfloat ", _IntFloatLine(pool, v)))
if tok.int_width > 0:
decl: str = "u" if tok.int_unsigned != 0 else "i"
decl = _Cat(pool, decl, _U64ToString(pool, uint64(tok.int_width)))
fits: int = 0
if tok.int_unsigned != 0:
fits = 1 if ub <= tok.int_width else 0
else:
fits = 1 if v <= (uint64(1) << uint64(tok.int_width - 1)) - uint64(1) else 0
state: str = "fits" if fits != 0 else "overflow"
body = _Cat(pool, body, _Cat(pool, _Cat(pool, _Cat(pool,
"\ndeclared ", decl), " ("), _Cat(pool, state, ")")))
return _CodeBlock(pool, body)
def _DecU64(pool: memhub.MemManager*, v: uint64) -> str:
"""Decimal text written by hand so wide values never go through the variadic formatter."""
if v == uint64(0):
return "0"
buf: bytes = pool.alloc(sizet(24))
if buf is None:
return None
pos: int = 23
buf[pos] = 0
x: uint64 = v
while x != uint64(0):
pos -= 1
buf[pos] = char(48 + int(x % uint64(10)))
x = x / uint64(10)
return str(buf + pos)
def _U64ToString(pool: memhub.MemManager*, v: uint64) -> str:
return _DecU64(pool, v)
# =============================================================================
# Floating point literal analysis
# =============================================================================
def _FloatScale(pbits: int) -> double:
"""2^pbits as a double (powers of two are exact)."""
s: double = 1.0
i: int = 0
while i < pbits:
s = s * 2.0
i += 1
return s
def _FloatState(v: double, width: int) -> int:
"""1 exact, 2 rounded, 3 overflow, 4 non-finite (binary-float representability)."""
if math.isnan(v) != 0 or math.isinf(v) != 0:
return 4
if width == 64:
return 1
a: double = v
if a < 0.0:
a = -a
if a == 0.0:
return 1
pbits: int = 10 if width == 16 else 23
min_exp: int = -14 if width == 16 else -126
max_exp: int = 15 if width == 16 else 127
max_finite: double = 65504.0 if width == 16 else 3.4028234663852886e38
overflow_at: double = 65520.0 if width == 16 else 3.4028235677973366e38
if a > overflow_at:
return 3
# Normalize to a = m * 2^e with m in [1, 2). Scaling by two is exact for doubles.
e: int = 0
m: double = a
while m >= 2.0:
m = m / 2.0
e += 1
while m < 1.0:
m = m * 2.0
e -= 1
if a > max_finite:
return 2
scale: double = _FloatScale(pbits)
t: double = m * scale
if t != math.floor(t):
return 2
if e >= min_exp:
return 1 if e <= max_exp else 2
# Subnormal: value must be an integral multiple of 2^(min_exp - pbits), below 2^pbits.
ksub: double = t
shift: int = min_exp - e
j: int = 0
while j < shift:
ksub = ksub / 2.0
j += 1
if ksub != math.floor(ksub):
return 2
return 1 if ksub < scale else 2
def _FloatStateName(state: int) -> str:
if state == 1:
return "exact"
if state == 2:
return "rounded"
if state == 3:
return "overflow"
return "non-finite"
def _FloatFitLine(pool: memhub.MemManager*, v: double) -> str:
s16: str = _FloatStateName(_FloatState(v, 16))
s32: str = _FloatStateName(_FloatState(v, 32))
s64: str = _FloatStateName(_FloatState(v, 64))
return _Cat(pool, _Cat(pool, _Cat(pool, _Cat(pool, _Cat(pool,
"f16 ", s16), ", f32 "), s32), ", f64 "), s64)
def _FloatMarkdown(pool: memhub.MemManager*, tok: ast.Token*) -> str:
v: double = tok.float_val
bits: uint64 = uint64(0)
*&bits = *&v
body: str = tok.str_val
if body is None:
body = "0.0"
body = _Cat(pool, body, _Cat(pool, "\nhex ", _HexU64Fixed(pool, bits)))
body = _Cat(pool, body, _Cat(pool, "\nbinary ", _BinU64(pool, bits)))
body = _Cat(pool, body, _Cat(pool, "\nfloat ", _FloatFitLine(pool, v)))
return _CodeBlock(pool, body)
# =============================================================================
# String and keyword literals
# =============================================================================
def _StringMarkdown(pool: memhub.MemManager*, doc: LspDocuments.LspDocument*,
tok: ast.Token*) -> str:
raw: str = LspSource.SliceBytes(pool, doc, tok.lineno - 1, tok.col_offset,
tok.end_lineno - 1, tok.end_col_offset)
shown: str = raw
if raw is not None and int(string.strlen(raw)) > HOVER_LITERAL_TEXT_CAP:
shown = _Cat(pool, LspSymbols._SliceStr(pool, raw, 0, HOVER_LITERAL_TEXT_CAP - 3), "...")
if shown is None:
shown = "\"\""
kind: str = "str"
if raw is not None and string.strlen(raw) > 0:
c0: char = raw[0]
if c0 == 'b' or c0 == 'B':
kind = "bytes"
elif c0 == 'f' or c0 == 'F':
kind = "f-string"
elif c0 == 'r' or c0 == 'R':
kind = "raw string"
elif c0 == 'u' or c0 == 'U':
kind = "str"
n: sizet = string.strlen(tok.str_val)
body: str = _Cat(pool, shown, _Cat(pool, "\ntype ", kind))
body = _Cat(pool, body, _Cat(pool, _Cat(pool, "\nlength ",
_U64ToString(pool, uint64(n))), " bytes"))
return _CodeBlock(pool, body)
def _KeywordMarkdown(pool: memhub.MemManager*, word: str) -> str:
if word is None:
return None
if string.strcmp(word, "True") == 0:
return _CodeBlock(pool, "True\nbool literal (true)")
if string.strcmp(word, "False") == 0:
return _CodeBlock(pool, "False\nbool literal (false)")
if string.strcmp(word, "None") == 0:
return _CodeBlock(pool, "None\nnull literal")
return None
# =============================================================================
# Symbol hover
# =============================================================================
def _PrefixInside(root: str, file: str) -> int:
"""1 when file is root plus a separator and a relative tail (case-insensitive)."""
if root is None or file is None:
return 0
nr: sizet = string.strlen(root)
nf: sizet = string.strlen(file)
if nr == 0 or nf <= nr:
return 0
i: sizet = 0
while i < nr:
a: char = root[i]
b: char = file[i]
if a == '\\':
a = '/'
if b == '\\':
b = '/'
if a >= 'A' and a <= 'Z':
a = char(int(a) + 32)
if b >= 'A' and b <= 'Z':
b = char(int(b) + 32)
if a != b:
return 0
i += 1
tail: char = file[nr]
return 1 if tail == '/' or tail == '\\' else 0
def _DisplayPath(pool: memhub.MemManager*, file: str) -> str:
"""Workspace-relative path when the file is under a known root, else the full path."""
if file is None:
return None
roots: list[str]* = LspIndex.Roots()
if roots is not None:
i: int = 0
while i < int(len(roots)):
root: str = roots.get(sizet(i))
i += 1
if _PrefixInside(root, file) != 0:
return LspSymbols._SliceStr(pool, file, int(string.strlen(root)) + 1,
int(string.strlen(file)))
return file
def _TargetMarkdown(pool: memhub.MemManager*, t: LspIndex.LspTarget*) -> str:
if t is None:
return None
code: str = t.detail
if code is None:
if t.kind == LspSymbols.LSP_KIND_MODULE:
code = _Cat(pool, "module ", t.name)
elif t.kind == LspSymbols.LSP_KIND_CONSTANT:
code = _Cat(pool, "const ", t.name)
else:
code = t.name
if code is None:
return None
text: str = _CodeBlock(pool, code)
if t.kind == LspSymbols.LSP_KIND_MODULE and t.file is not None:
text = _Cat(pool, text, _Cat(pool, _Cat(pool, "\n\n`",
_DisplayPath(pool, t.file)), "`"))
if text is None:
return None
if t.doc is not None and t.doc[0] != 0:
text = _Cat(pool, text, _Cat(pool, "\n\n---\n\n", t.doc))
return text
# =============================================================================
# Entry point
# =============================================================================
def Hover(pool: memhub.MemManager*, tr: LspTransport.LspTransport*,
docs: LspDocuments.LspDocuments*, ws: LspWorkspace.LspWorkspace*,
id_node: json.JsonValue*, params: json.JsonValue*) -> int:
doc: LspDocuments.LspDocument* = LspNavigation._Doc(pool, docs, params)
pos: json.JsonValue* = LspNavigation._Position(params)
if doc is None or pos is None:
return LspRpc.WriteResponse(pool, tr, id_node, json.null(pool))
line0: int = LspRpc.GetIntField(pos, "line", 0)
ch: int = LspRpc.GetIntField(pos, "character", 0)
uri: str = LspNavigation._Uri(params)
a: LspSymbols.LspAnalysis* = LspNavigation._Analyze(pool, docs, doc, uri)
text: str = None
t: LspIndex.LspTarget* = LspNavigation._TargetAt(
ws, pool, docs.pool, a, doc, line0, ch)
if t is not None:
text = _TargetMarkdown(pool, t)
if text is None:
tok: ast.Token* = _FindLiteral(pool, doc, line0, ch)
if tok is not None:
if tok.type == ast.TokenType.String:
text = _StringMarkdown(pool, doc, tok)
elif tok.is_complex != 0:
text = None
elif tok.is_float != 0:
text = _FloatMarkdown(pool, tok)
else:
text = _IntMarkdown(pool, tok)
if text is None:
text = _KeywordMarkdown(pool, _WordAt(pool, doc, line0, ch))
if text is None:
return LspRpc.WriteResponse(pool, tr, id_node, json.null(pool))
result: json.JsonValue* = json.object(pool)
contents: json.JsonValue* = json.object(pool)
if result is None or contents is None:
return -1
json.object_set(pool, contents, "kind", json.string_val(pool, "markdown"))
json.object_set(pool, contents, "value", json.string_val(pool, text))
json.object_set(pool, result, "contents", contents)
return LspRpc.WriteResponse(pool, tr, id_node, result)
-34
View File
@@ -688,37 +688,3 @@ def DocumentHighlight(pool: memhub.MemManager*, tr: LspTransport.LspTransport*,
json.object_set(pool, h2, "kind", json.int_val(pool, int64(2)))
json.array_append(pool, arr, h2)
return LspRpc.WriteResponse(pool, tr, id_node, arr)
def Hover(pool: memhub.MemManager*, tr: LspTransport.LspTransport*,
docs: LspDocuments.LspDocuments*, ws: LspWorkspace.LspWorkspace*,
id_node: json.JsonValue*, params: json.JsonValue*) -> int:
doc: LspDocuments.LspDocument* = _Doc(pool, docs, params)
pos: json.JsonValue* = _Position(params)
if doc is None or pos is None:
return LspRpc.WriteResponse(pool, tr, id_node, json.null(pool))
line0: int = LspRpc.GetIntField(pos, "line", 0)
ch: int = LspRpc.GetIntField(pos, "character", 0)
a: LspSymbols.LspAnalysis* = _Analyze(pool, docs, doc, _Uri(params))
t: LspIndex.LspTarget* = _TargetAt(ws, pool, docs.pool, a, doc, line0, ch)
if t is None:
return LspRpc.WriteResponse(pool, tr, id_node, json.null(pool))
code: str = t.detail
if code is None:
if t.kind == LspSymbols.LSP_KIND_MODULE:
code = _Cat(pool, "module ", t.name)
else:
code = t.name
text: str = _Cat(pool, "```viper\n", code)
text = _Cat(pool, text, "\n```")
if t.doc is not None and t.doc[0] != 0:
text = _Cat(pool, text, "\n\n---\n\n")
text = _Cat(pool, text, t.doc)
result: json.JsonValue* = json.object(pool)
contents: json.JsonValue* = json.object(pool)
if result is None or contents is None:
return -1
json.object_set(pool, contents, "kind", json.string_val(pool, "markdown"))
json.object_set(pool, contents, "value", json.string_val(pool, text))
json.object_set(pool, result, "contents", contents)
return LspRpc.WriteResponse(pool, tr, id_node, result)
+2 -1
View File
@@ -15,6 +15,7 @@ import lib.lsp.LspWorkspace as LspWorkspace
import lib.lsp.LspDiagnostics as LspDiagnostics
import lib.lsp.LspIndex as LspIndex
import lib.lsp.LspNavigation as LspNavigation
import lib.lsp.LspHover as LspHover
import lib.lsp.LspCompletion as LspCompletion
import lib.lsp.LspSignature as LspSignature
import lib.lsp.LspRename as LspRename
@@ -501,7 +502,7 @@ def Run(pool: memhub.MemBuddy*, perm_pool: memhub.MemBuddy*) -> int:
elif string.strcmp(method, "textDocument/documentHighlight") == 0:
rc = LspNavigation.DocumentHighlight(pool, tr, docs, ws, id_node, params)
elif string.strcmp(method, "textDocument/hover") == 0:
rc = LspNavigation.Hover(pool, tr, docs, ws, id_node, params)
rc = LspHover.Hover(pool, tr, docs, ws, id_node, params)
elif string.strcmp(method, "textDocument/completion") == 0:
rc = LspCompletion.Completion(pool, tr, docs, ws, id_node, params)
elif string.strcmp(method, "textDocument/signatureHelp") == 0:
+24
View File
@@ -58,6 +58,30 @@ def alias_use() -> int:
return aliased_fn(EXTRA_CONST)
def numeric_use() -> int:
return 128
def hex_use() -> int:
return 0x10
def binary_use() -> int:
return 0b1010
def float_use() -> float:
return 3.5
def float_round_use() -> float:
return 3.14
def string_use() -> int:
return len("hello")
def else_block(flag: int) -> int:
if flag != 0:
return 1
+33
View File
@@ -1737,6 +1737,39 @@ def gate10(exe: str) -> int:
if definition_of(at("StubThing.count", 10))[0] != "gate10_stub.vpi":
raise AssertionError("definition on .vpi class field chain failed")
# Literal hovers carry the numeric spelling, storage widths and float representability.
module_hover = hover_text(at("import gate10_types", 7))
if "module gate10_types" not in module_hover or "gate10_types.vp" not in module_hover:
raise AssertionError(f"module hover lacks name/path: {module_hover}")
int_hover = hover_text(at("return 128", 7))
for piece in ("dec 128", "hex 0x80", "binary 0b1000_0000",
"bits 8 unsigned, 9 signed", "uint u8", "int i16",
"float f16 exact, f32 exact, f64 exact"):
if piece not in int_hover:
raise AssertionError(f"integer hover lacks {piece!r}: {int_hover}")
hex_hover = hover_text(at("return 0x10", 7))
for piece in ("dec 16", "hex 0x10", "binary 0b1_0000"):
if piece not in hex_hover:
raise AssertionError(f"hex hover lacks {piece!r}: {hex_hover}")
bin_hover = hover_text(at("return 0b1010", 7))
for piece in ("dec 10", "hex 0xA", "binary 0b1010"):
if piece not in bin_hover:
raise AssertionError(f"binary hover lacks {piece!r}: {bin_hover}")
float_hover = hover_text(at("return 3.5", 7))
if "hex 0x400C000000000000" not in float_hover:
raise AssertionError(f"float hover has the wrong bit pattern: {float_hover}")
if "f16 exact, f32 exact, f64 exact" not in float_hover:
raise AssertionError(f"float hover misreports exactness: {float_hover}")
round_hover = hover_text(at("return 3.14", 7))
if "hex 0x40091EB851EB851F" not in round_hover:
raise AssertionError(f"float hover has the wrong bit pattern: {round_hover}")
if "f16 rounded, f32 rounded, f64 exact" not in round_hover:
raise AssertionError(f"float hover misreports exactness: {round_hover}")
string_hover = hover_text(at('return len("hello")', len("return len(")))
for piece in ("type str", "length 5 bytes"):
if piece not in string_hover:
raise AssertionError(f"string hover lacks {piece!r}: {string_hover}")
def reference_ranges(pos: dict[str, int]) -> list[tuple[str, int, int]]:
result = request("textDocument/references", pos,
{"context": {"includeDeclaration": True}})