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:
1 parent
6baad4a6e6
commit
d1f84371f8
5 files changed
+745
-35
No files matched your search
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}})
|
||||
|
||||
Reference in new issue
Block a user