Skip to content

Commit 3f2b9c8

Browse files
nickysnAraq
andauthored
Inlay hints support (#22896)
This adds inlay hints support to nimsuggest. It adds a new command to nimsuggest, called 'inlayHints'. Currently, it provides type information to 'var' and 'let' variables. In the future, inlay hints can also be added for 'const' and for function parameters. The protocol also reserves space for a tooltip field, which is not used, yet, but support for it can be added in the future, without further changing the protocol. The change includes refactoring to allow the 'inlayHints' command to return a completely different structure, compared to the other nimsuggest commands. This will allow other future commands to have custom return types as well. All the previous commands return the same structure as before, so perfect backwards compatibility is maintained. To use this feature, an update to the nim language server, as well as the VS code extension is needed. Related PRs: nimlangserver: nim-lang/langserver#53 VS code extension: saem/vscode-nim#134 --------- Co-authored-by: Andreas Rumpf <[email protected]>
1 parent 95e5ad6 commit 3f2b9c8

File tree

6 files changed

+149
-44
lines changed

6 files changed

+149
-44
lines changed

compiler/ast.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,7 @@ type
903903
info*: TLineInfo
904904
when defined(nimsuggest):
905905
endInfo*: TLineInfo
906+
hasUserSpecifiedType*: bool # used for determining whether to display inlay type hints
906907
owner*: PSym
907908
flags*: TSymFlags
908909
ast*: PNode # syntax tree of proc, iterator, etc.:

compiler/modulegraphs.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type
5757
SymInfoPair* = object
5858
sym*: PSym
5959
info*: TLineInfo
60+
isDecl*: bool
6061

6162
PipelinePass* = enum
6263
NonePass

compiler/options.nim

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ type
199199
IdeCmd* = enum
200200
ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideChkFile, ideMod,
201201
ideHighlight, ideOutline, ideKnown, ideMsg, ideProject, ideGlobalSymbols,
202-
ideRecompile, ideChanged, ideType, ideDeclaration, ideExpand
202+
ideRecompile, ideChanged, ideType, ideDeclaration, ideExpand, ideInlayHints
203203

204204
Feature* = enum ## experimental features; DO NOT RENAME THESE!
205205
dotOperators,
@@ -288,9 +288,24 @@ type
288288
version*: int
289289
endLine*: uint16
290290
endCol*: int
291+
inlayHintInfo*: SuggestInlayHint
291292

292293
Suggestions* = seq[Suggest]
293294

295+
SuggestInlayHintKind* = enum
296+
sihkType = "Type",
297+
sihkParameter = "Parameter"
298+
299+
SuggestInlayHint* = ref object
300+
kind*: SuggestInlayHintKind
301+
line*: int # Starts at 1
302+
column*: int # Starts at 0
303+
label*: string
304+
paddingLeft*: bool
305+
paddingRight*: bool
306+
allowInsert*: bool
307+
tooltip*: string
308+
294309
ProfileInfo* = object
295310
time*: float
296311
count*: int
@@ -1071,6 +1086,7 @@ proc `$`*(c: IdeCmd): string =
10711086
of ideRecompile: "recompile"
10721087
of ideChanged: "changed"
10731088
of ideType: "type"
1089+
of ideInlayHints: "inlayHints"
10741090

10751091
proc floatInt64Align*(conf: ConfigRef): int16 =
10761092
## Returns either 4 or 8 depending on reasons.

compiler/semstmts.nim

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,9 +672,11 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
672672
addToVarSection(c, result, b)
673673
continue
674674

675+
var hasUserSpecifiedType = false
675676
var typ: PType = nil
676677
if a[^2].kind != nkEmpty:
677678
typ = semTypeNode(c, a[^2], nil)
679+
hasUserSpecifiedType = true
678680

679681
var typFlags: TTypeAllowedFlags = {}
680682

@@ -746,6 +748,8 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
746748
addToVarSection(c, result, n, a)
747749
continue
748750
var v = semIdentDef(c, a[j], symkind, false)
751+
when defined(nimsuggest):
752+
v.hasUserSpecifiedType = hasUserSpecifiedType
749753
styleCheckDef(c, v)
750754
onDef(a[j].info, v)
751755
if sfGenSym notin v.flags:

compiler/suggest.nim

Lines changed: 73 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ proc getTokenLenFromSource(conf: ConfigRef; ident: string; info: TLineInfo): int
107107
result = 0
108108
elif ident[0] in linter.Letters and ident[^1] != '=':
109109
result = identLen(line, column)
110-
if cmpIgnoreStyle(line[column..column + result - 1], ident) != 0:
110+
if cmpIgnoreStyle(line[column..column + result - 1], ident[0..min(result-1,len(ident)-1)]) != 0:
111111
result = 0
112112
else:
113113
var sourceIdent: string = ""
@@ -177,58 +177,90 @@ proc symToSuggest*(g: ModuleGraph; s: PSym, isLocal: bool, section: IdeCmd, info
177177
result.filePath = toFullPath(g.config, infox)
178178
result.line = toLinenumber(infox)
179179
result.column = toColumn(infox)
180-
result.tokenLen = if section != ideHighlight:
180+
result.tokenLen = if section notin {ideHighlight, ideInlayHints}:
181181
s.name.s.len
182182
else:
183183
getTokenLenFromSource(g.config, s.name.s, infox)
184184
result.version = g.config.suggestVersion
185185
result.endLine = endLine
186186
result.endCol = endCol
187187

188-
proc `$`*(suggest: Suggest): string =
189-
result = $suggest.section
188+
proc `$`*(suggest: SuggestInlayHint): string =
189+
result = $suggest.kind
190190
result.add(sep)
191-
if suggest.section == ideHighlight:
192-
if suggest.symkind.TSymKind == skVar and suggest.isGlobal:
193-
result.add("skGlobalVar")
194-
elif suggest.symkind.TSymKind == skLet and suggest.isGlobal:
195-
result.add("skGlobalLet")
196-
else:
197-
result.add($suggest.symkind.TSymKind)
198-
result.add(sep)
199-
result.add($suggest.line)
200-
result.add(sep)
201-
result.add($suggest.column)
202-
result.add(sep)
203-
result.add($suggest.tokenLen)
191+
result.add($suggest.line)
192+
result.add(sep)
193+
result.add($suggest.column)
194+
result.add(sep)
195+
result.add(suggest.label)
196+
result.add(sep)
197+
result.add($suggest.paddingLeft)
198+
result.add(sep)
199+
result.add($suggest.paddingRight)
200+
result.add(sep)
201+
result.add($suggest.allowInsert)
202+
result.add(sep)
203+
result.add(suggest.tooltip)
204+
205+
proc `$`*(suggest: Suggest): string =
206+
if suggest.section == ideInlayHints:
207+
result = $suggest.inlayHintInfo
204208
else:
205-
result.add($suggest.symkind.TSymKind)
206-
result.add(sep)
207-
if suggest.qualifiedPath.len != 0:
208-
result.add(suggest.qualifiedPath.join("."))
209-
result.add(sep)
210-
result.add(suggest.forth)
211-
result.add(sep)
212-
result.add(suggest.filePath)
209+
result = $suggest.section
213210
result.add(sep)
214-
result.add($suggest.line)
215-
result.add(sep)
216-
result.add($suggest.column)
217-
result.add(sep)
218-
when defined(nimsuggest) and not defined(noDocgen) and not defined(leanCompiler):
219-
result.add(suggest.doc.escape)
220-
if suggest.version == 0 or suggest.version == 3:
211+
if suggest.section == ideHighlight:
212+
if suggest.symkind.TSymKind == skVar and suggest.isGlobal:
213+
result.add("skGlobalVar")
214+
elif suggest.symkind.TSymKind == skLet and suggest.isGlobal:
215+
result.add("skGlobalLet")
216+
else:
217+
result.add($suggest.symkind.TSymKind)
218+
result.add(sep)
219+
result.add($suggest.line)
220+
result.add(sep)
221+
result.add($suggest.column)
222+
result.add(sep)
223+
result.add($suggest.tokenLen)
224+
else:
225+
result.add($suggest.symkind.TSymKind)
221226
result.add(sep)
222-
result.add($suggest.quality)
223-
if suggest.section == ideSug:
227+
if suggest.qualifiedPath.len != 0:
228+
result.add(suggest.qualifiedPath.join("."))
229+
result.add(sep)
230+
result.add(suggest.forth)
231+
result.add(sep)
232+
result.add(suggest.filePath)
233+
result.add(sep)
234+
result.add($suggest.line)
235+
result.add(sep)
236+
result.add($suggest.column)
237+
result.add(sep)
238+
when defined(nimsuggest) and not defined(noDocgen) and not defined(leanCompiler):
239+
result.add(suggest.doc.escape)
240+
if suggest.version == 0 or suggest.version == 3:
224241
result.add(sep)
225-
result.add($suggest.prefix)
242+
result.add($suggest.quality)
243+
if suggest.section == ideSug:
244+
result.add(sep)
245+
result.add($suggest.prefix)
226246

227-
if (suggest.version == 3 and suggest.section in {ideOutline, ideExpand}):
228-
result.add(sep)
229-
result.add($suggest.endLine)
230-
result.add(sep)
231-
result.add($suggest.endCol)
247+
if (suggest.version == 3 and suggest.section in {ideOutline, ideExpand}):
248+
result.add(sep)
249+
result.add($suggest.endLine)
250+
result.add(sep)
251+
result.add($suggest.endCol)
252+
253+
proc suggestToSuggestInlayHint*(sug: Suggest): SuggestInlayHint =
254+
SuggestInlayHint(
255+
kind: sihkType,
256+
line: sug.line,
257+
column: sug.column + sug.tokenLen,
258+
label: ": " & sug.forth,
259+
paddingLeft: false,
260+
paddingRight: false,
261+
allowInsert: true,
262+
tooltip: ""
263+
)
232264

233265
proc suggestResult*(conf: ConfigRef; s: Suggest) =
234266
if not isNil(conf.suggestionResultHook):
@@ -537,7 +569,7 @@ proc suggestSym*(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym; i
537569
## misnamed: should be 'symDeclared'
538570
let conf = g.config
539571
when defined(nimsuggest):
540-
g.suggestSymbols.mgetOrPut(info.fileIndex, @[]).add SymInfoPair(sym: s, info: info)
572+
g.suggestSymbols.mgetOrPut(info.fileIndex, @[]).add SymInfoPair(sym: s, info: info, isDecl: isDecl)
541573
542574
if conf.suggestVersion == 0:
543575
if s.allUsages.len == 0:

nimsuggest/nimsuggest.nim

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ proc listEpc(): SexpNode =
161161
argspecs = sexp("file line column dirtyfile".split(" ").map(newSSymbol))
162162
docstring = sexp("line starts at 1, column at 0, dirtyfile is optional")
163163
result = newSList()
164-
for command in ["sug", "con", "def", "use", "dus", "chk", "mod", "globalSymbols", "recompile", "saved", "chkFile", "declaration"]:
164+
for command in ["sug", "con", "def", "use", "dus", "chk", "mod", "globalSymbols", "recompile", "saved", "chkFile", "declaration", "inlayHints"]:
165165
let
166166
cmd = sexp(command)
167167
methodDesc = newSList()
@@ -506,6 +506,7 @@ proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) =
506506
of "chkfile": conf.ideCmd = ideChkFile
507507
of "recompile": conf.ideCmd = ideRecompile
508508
of "type": conf.ideCmd = ideType
509+
of "inlayhints": conf.ideCmd = ideInlayHints
509510
else: err()
510511
var dirtyfile = ""
511512
var orig = ""
@@ -774,13 +775,33 @@ proc findSymData(graph: ModuleGraph, trackPos: TLineInfo):
774775
result[] = s
775776
break
776777
778+
func isInRange*(current, startPos, endPos: TLineInfo, tokenLen: int): bool =
779+
result = current.fileIndex == startPos.fileIndex and
780+
(current.line > startPos.line or (current.line == startPos.line and current.col>=startPos.col)) and
781+
(current.line < endPos.line or (current.line == endPos.line and current.col <= endPos.col))
782+
783+
proc findSymDataInRange(graph: ModuleGraph, startPos, endPos: TLineInfo):
784+
seq[SymInfoPair] =
785+
result = newSeq[SymInfoPair]()
786+
for s in graph.fileSymbols(startPos.fileIndex).deduplicateSymInfoPair:
787+
if isInRange(s.info, startPos, endPos, s.sym.name.s.len):
788+
result.add(s)
789+
777790
proc findSymData(graph: ModuleGraph, file: AbsoluteFile; line, col: int):
778791
ref SymInfoPair =
779792
let
780793
fileIdx = fileInfoIdx(graph.config, file)
781794
trackPos = newLineInfo(fileIdx, line, col)
782795
result = findSymData(graph, trackPos)
783796
797+
proc findSymDataInRange(graph: ModuleGraph, file: AbsoluteFile; startLine, startCol, endLine, endCol: int):
798+
seq[SymInfoPair] =
799+
let
800+
fileIdx = fileInfoIdx(graph.config, file)
801+
startPos = newLineInfo(fileIdx, startLine, startCol)
802+
endPos = newLineInfo(fileIdx, endLine, endCol)
803+
result = findSymDataInRange(graph, startPos, endPos)
804+
784805
proc markDirtyIfNeeded(graph: ModuleGraph, file: string, originalFileIdx: FileIndex) =
785806
let sha = $sha1.secureHashFile(file)
786807
if graph.config.m.fileInfos[originalFileIdx.int32].hash != sha or graph.config.ideCmd == ideSug:
@@ -803,6 +824,23 @@ proc suggestResult(graph: ModuleGraph, sym: PSym, info: TLineInfo,
803824
endLine = endLine, endCol = endCol)
804825
suggestResult(graph.config, suggest)
805826
827+
proc suggestInlayHintResult(graph: ModuleGraph, sym: PSym, info: TLineInfo,
828+
defaultSection = ideNone, endLine: uint16 = 0, endCol = 0) =
829+
let section = if defaultSection != ideNone:
830+
defaultSection
831+
elif sym.info.exactEquals(info):
832+
ideDef
833+
else:
834+
ideUse
835+
var suggestDef = symToSuggest(graph, sym, isLocal=false, section,
836+
info, 100, PrefixMatch.None, false, 0, true,
837+
endLine = endLine, endCol = endCol)
838+
suggestDef.inlayHintInfo = suggestToSuggestInlayHint(suggestDef)
839+
suggestDef.section = ideInlayHints
840+
if sym.kind == skForVar:
841+
suggestDef.inlayHintInfo.allowInsert = false
842+
suggestResult(graph.config, suggestDef)
843+
806844
const
807845
# kinds for ideOutline and ideGlobalSymbols
808846
searchableSymKinds = {skField, skEnumField, skIterator, skMethod, skFunc, skProc, skConverter, skTemplate}
@@ -910,7 +948,7 @@ proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile,
910948
graph.markDirtyIfNeeded(dirtyFile.string, fileInfoIdx(conf, file))
911949

912950
# these commands require fully compiled project
913-
if cmd in {ideUse, ideDus, ideGlobalSymbols, ideChk} and graph.needsCompilation():
951+
if cmd in {ideUse, ideDus, ideGlobalSymbols, ideChk, ideInlayHints} and graph.needsCompilation():
914952
graph.recompilePartially()
915953
# when doing incremental build for the project root we should make sure that
916954
# everything is unmarked as no longer beeing dirty in case there is no
@@ -1066,6 +1104,19 @@ proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile,
10661104

10671105
graph.markDirty fileIndex
10681106
graph.markClientsDirty fileIndex
1107+
of ideInlayHints:
1108+
myLog fmt "Executing inlayHints"
1109+
var endLine = 0
1110+
var endCol = -1
1111+
var i = 0
1112+
i += skipWhile(tag, seps, i)
1113+
i += parseInt(tag, endLine, i)
1114+
i += skipWhile(tag, seps, i)
1115+
i += parseInt(tag, endCol, i)
1116+
let s = graph.findSymDataInRange(file, line, col, endLine, endCol)
1117+
for q in s:
1118+
if q.sym.kind in {skLet, skVar, skForVar} and q.isDecl and not q.sym.hasUserSpecifiedType:
1119+
graph.suggestInlayHintResult(q.sym, q.info, ideInlayHints)
10691120
else:
10701121
myLog fmt "Discarding {cmd}"
10711122

0 commit comments

Comments
 (0)