summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/ast.nim1
-rw-r--r--compiler/modulegraphs.nim1
-rw-r--r--compiler/options.nim18
-rw-r--r--compiler/semstmts.nim4
-rw-r--r--compiler/suggest.nim114
-rw-r--r--nimsuggest/nimsuggest.nim55
6 files changed, 149 insertions, 44 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 5ee9afa02..661a82703 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -903,6 +903,7 @@ type
     info*: TLineInfo
     when defined(nimsuggest):
       endInfo*: TLineInfo
+      hasUserSpecifiedType*: bool  # used for determining whether to display inlay type hints
     owner*: PSym
     flags*: TSymFlags
     ast*: PNode               # syntax tree of proc, iterator, etc.:
diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim
index ba636eb5a..5c23325e8 100644
--- a/compiler/modulegraphs.nim
+++ b/compiler/modulegraphs.nim
@@ -57,6 +57,7 @@ type
   SymInfoPair* = object
     sym*: PSym
     info*: TLineInfo
+    isDecl*: bool
 
   PipelinePass* = enum
     NonePass
diff --git a/compiler/options.nim b/compiler/options.nim
index f2d93a9b3..704248d78 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -199,7 +199,7 @@ type
   IdeCmd* = enum
     ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideChkFile, ideMod,
     ideHighlight, ideOutline, ideKnown, ideMsg, ideProject, ideGlobalSymbols,
-    ideRecompile, ideChanged, ideType, ideDeclaration, ideExpand
+    ideRecompile, ideChanged, ideType, ideDeclaration, ideExpand, ideInlayHints
 
   Feature* = enum  ## experimental features; DO NOT RENAME THESE!
     dotOperators,
@@ -288,9 +288,24 @@ type
     version*: int
     endLine*: uint16
     endCol*: int
+    inlayHintInfo*: SuggestInlayHint
 
   Suggestions* = seq[Suggest]
 
+  SuggestInlayHintKind* = enum
+    sihkType = "Type",
+    sihkParameter = "Parameter"
+
+  SuggestInlayHint* = ref object
+    kind*: SuggestInlayHintKind
+    line*: int                   # Starts at 1
+    column*: int                 # Starts at 0
+    label*: string
+    paddingLeft*: bool
+    paddingRight*: bool
+    allowInsert*: bool
+    tooltip*: string
+
   ProfileInfo* = object
     time*: float
     count*: int
@@ -1071,6 +1086,7 @@ proc `$`*(c: IdeCmd): string =
   of ideRecompile: "recompile"
   of ideChanged: "changed"
   of ideType: "type"
+  of ideInlayHints: "inlayHints"
 
 proc floatInt64Align*(conf: ConfigRef): int16 =
   ## Returns either 4 or 8 depending on reasons.
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index a4de874ba..0196b9d03 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -672,9 +672,11 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
       addToVarSection(c, result, b)
       continue
 
+    var hasUserSpecifiedType = false
     var typ: PType = nil
     if a[^2].kind != nkEmpty:
       typ = semTypeNode(c, a[^2], nil)
+      hasUserSpecifiedType = true
 
     var typFlags: TTypeAllowedFlags = {}
 
@@ -746,6 +748,8 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode =
           addToVarSection(c, result, n, a)
           continue
         var v = semIdentDef(c, a[j], symkind, false)
+        when defined(nimsuggest):
+          v.hasUserSpecifiedType = hasUserSpecifiedType
         styleCheckDef(c, v)
         onDef(a[j].info, v)
         if sfGenSym notin v.flags:
diff --git a/compiler/suggest.nim b/compiler/suggest.nim
index 5714c6d21..1d84fada5 100644
--- a/compiler/suggest.nim
+++ b/compiler/suggest.nim
@@ -107,7 +107,7 @@ proc getTokenLenFromSource(conf: ConfigRef; ident: string; info: TLineInfo): int
       result = 0
   elif ident[0] in linter.Letters and ident[^1] != '=':
     result = identLen(line, column)
-    if cmpIgnoreStyle(line[column..column + result - 1], ident) != 0:
+    if cmpIgnoreStyle(line[column..column + result - 1], ident[0..min(result-1,len(ident)-1)]) != 0:
       result = 0
   else:
     var sourceIdent: string = ""
@@ -177,7 +177,7 @@ proc symToSuggest*(g: ModuleGraph; s: PSym, isLocal: bool, section: IdeCmd, info
     result.filePath = toFullPath(g.config, infox)
     result.line = toLinenumber(infox)
     result.column = toColumn(infox)
-    result.tokenLen = if section != ideHighlight:
+    result.tokenLen = if section notin {ideHighlight, ideInlayHints}:
                         s.name.s.len
                       else:
                         getTokenLenFromSource(g.config, s.name.s, infox)
@@ -185,50 +185,82 @@ proc symToSuggest*(g: ModuleGraph; s: PSym, isLocal: bool, section: IdeCmd, info
   result.endLine = endLine
   result.endCol = endCol
 
-proc `$`*(suggest: Suggest): string =
-  result = $suggest.section
+proc `$`*(suggest: SuggestInlayHint): string =
+  result = $suggest.kind
   result.add(sep)
-  if suggest.section == ideHighlight:
-    if suggest.symkind.TSymKind == skVar and suggest.isGlobal:
-      result.add("skGlobalVar")
-    elif suggest.symkind.TSymKind == skLet and suggest.isGlobal:
-      result.add("skGlobalLet")
-    else:
-      result.add($suggest.symkind.TSymKind)
-    result.add(sep)
-    result.add($suggest.line)
-    result.add(sep)
-    result.add($suggest.column)
-    result.add(sep)
-    result.add($suggest.tokenLen)
+  result.add($suggest.line)
+  result.add(sep)
+  result.add($suggest.column)
+  result.add(sep)
+  result.add(suggest.label)
+  result.add(sep)
+  result.add($suggest.paddingLeft)
+  result.add(sep)
+  result.add($suggest.paddingRight)
+  result.add(sep)
+  result.add($suggest.allowInsert)
+  result.add(sep)
+  result.add(suggest.tooltip)
+
+proc `$`*(suggest: Suggest): string =
+  if suggest.section == ideInlayHints:
+    result = $suggest.inlayHintInfo
   else:
-    result.add($suggest.symkind.TSymKind)
-    result.add(sep)
-    if suggest.qualifiedPath.len != 0:
-      result.add(suggest.qualifiedPath.join("."))
-    result.add(sep)
-    result.add(suggest.forth)
-    result.add(sep)
-    result.add(suggest.filePath)
+    result = $suggest.section
     result.add(sep)
-    result.add($suggest.line)
-    result.add(sep)
-    result.add($suggest.column)
-    result.add(sep)
-    when defined(nimsuggest) and not defined(noDocgen) and not defined(leanCompiler):
-      result.add(suggest.doc.escape)
-    if suggest.version == 0 or suggest.version == 3:
+    if suggest.section == ideHighlight:
+      if suggest.symkind.TSymKind == skVar and suggest.isGlobal:
+        result.add("skGlobalVar")
+      elif suggest.symkind.TSymKind == skLet and suggest.isGlobal:
+        result.add("skGlobalLet")
+      else:
+        result.add($suggest.symkind.TSymKind)
+      result.add(sep)
+      result.add($suggest.line)
+      result.add(sep)
+      result.add($suggest.column)
+      result.add(sep)
+      result.add($suggest.tokenLen)
+    else:
+      result.add($suggest.symkind.TSymKind)
       result.add(sep)
-      result.add($suggest.quality)
-      if suggest.section == ideSug:
+      if suggest.qualifiedPath.len != 0:
+        result.add(suggest.qualifiedPath.join("."))
+      result.add(sep)
+      result.add(suggest.forth)
+      result.add(sep)
+      result.add(suggest.filePath)
+      result.add(sep)
+      result.add($suggest.line)
+      result.add(sep)
+      result.add($suggest.column)
+      result.add(sep)
+      when defined(nimsuggest) and not defined(noDocgen) and not defined(leanCompiler):
+        result.add(suggest.doc.escape)
+      if suggest.version == 0 or suggest.version == 3:
         result.add(sep)
-        result.add($suggest.prefix)
+        result.add($suggest.quality)
+        if suggest.section == ideSug:
+          result.add(sep)
+          result.add($suggest.prefix)
 
-  if (suggest.version == 3 and suggest.section in {ideOutline, ideExpand}):
-    result.add(sep)
-    result.add($suggest.endLine)
-    result.add(sep)
-    result.add($suggest.endCol)
+    if (suggest.version == 3 and suggest.section in {ideOutline, ideExpand}):
+      result.add(sep)
+      result.add($suggest.endLine)
+      result.add(sep)
+      result.add($suggest.endCol)
+
+proc suggestToSuggestInlayHint*(sug: Suggest): SuggestInlayHint =
+  SuggestInlayHint(
+    kind: sihkType,
+    line: sug.line,
+    column: sug.column + sug.tokenLen,
+    label: ": " & sug.forth,
+    paddingLeft: false,
+    paddingRight: false,
+    allowInsert: true,
+    tooltip: ""
+  )
 
 proc suggestResult*(conf: ConfigRef; s: Suggest) =
   if not isNil(conf.suggestionResultHook):
@@ -537,7 +569,7 @@ proc suggestSym*(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym; i
   ## misnamed: should be 'symDeclared'
   let conf = g.config
   when defined(nimsuggest):
-    g.suggestSymbols.mgetOrPut(info.fileIndex, @[]).add SymInfoPair(sym: s, info: info)
+    g.suggestSymbols.mgetOrPut(info.fileIndex, @[]).add SymInfoPair(sym: s, info: info, isDecl: isDecl)
 
     if conf.suggestVersion == 0:
       if s.allUsages.len == 0:
diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim
index 28faf9e3c..3834acb93 100644
--- a/nimsuggest/nimsuggest.nim
+++ b/nimsuggest/nimsuggest.nim
@@ -161,7 +161,7 @@ proc listEpc(): SexpNode =
     argspecs = sexp("file line column dirtyfile".split(" ").map(newSSymbol))
     docstring = sexp("line starts at 1, column at 0, dirtyfile is optional")
   result = newSList()
-  for command in ["sug", "con", "def", "use", "dus", "chk", "mod", "globalSymbols", "recompile", "saved", "chkFile", "declaration"]:
+  for command in ["sug", "con", "def", "use", "dus", "chk", "mod", "globalSymbols", "recompile", "saved", "chkFile", "declaration", "inlayHints"]:
     let
       cmd = sexp(command)
       methodDesc = newSList()
@@ -506,6 +506,7 @@ proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) =
   of "chkfile": conf.ideCmd = ideChkFile
   of "recompile": conf.ideCmd = ideRecompile
   of "type": conf.ideCmd = ideType
+  of "inlayhints": conf.ideCmd = ideInlayHints
   else: err()
   var dirtyfile = ""
   var orig = ""
@@ -774,6 +775,18 @@ proc findSymData(graph: ModuleGraph, trackPos: TLineInfo):
       result[] = s
       break
 
+func isInRange*(current, startPos, endPos: TLineInfo, tokenLen: int): bool =
+  result = current.fileIndex == startPos.fileIndex and
+    (current.line > startPos.line or (current.line == startPos.line and current.col>=startPos.col)) and
+    (current.line < endPos.line or (current.line == endPos.line and current.col <= endPos.col))
+
+proc findSymDataInRange(graph: ModuleGraph, startPos, endPos: TLineInfo):
+    seq[SymInfoPair] =
+  result = newSeq[SymInfoPair]()
+  for s in graph.fileSymbols(startPos.fileIndex).deduplicateSymInfoPair:
+    if isInRange(s.info, startPos, endPos, s.sym.name.s.len):
+      result.add(s)
+
 proc findSymData(graph: ModuleGraph, file: AbsoluteFile; line, col: int):
     ref SymInfoPair =
   let
@@ -781,6 +794,14 @@ proc findSymData(graph: ModuleGraph, file: AbsoluteFile; line, col: int):
     trackPos = newLineInfo(fileIdx, line, col)
   result = findSymData(graph, trackPos)
 
+proc findSymDataInRange(graph: ModuleGraph, file: AbsoluteFile; startLine, startCol, endLine, endCol: int):
+    seq[SymInfoPair] =
+  let
+    fileIdx = fileInfoIdx(graph.config, file)
+    startPos = newLineInfo(fileIdx, startLine, startCol)
+    endPos = newLineInfo(fileIdx, endLine, endCol)
+  result = findSymDataInRange(graph, startPos, endPos)
+
 proc markDirtyIfNeeded(graph: ModuleGraph, file: string, originalFileIdx: FileIndex) =
   let sha = $sha1.secureHashFile(file)
   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,
                              endLine = endLine, endCol = endCol)
   suggestResult(graph.config, suggest)
 
+proc suggestInlayHintResult(graph: ModuleGraph, sym: PSym, info: TLineInfo,
+                   defaultSection = ideNone, endLine: uint16 = 0, endCol = 0) =
+  let section = if defaultSection != ideNone:
+                  defaultSection
+                elif sym.info.exactEquals(info):
+                  ideDef
+                else:
+                  ideUse
+  var suggestDef = symToSuggest(graph, sym, isLocal=false, section,
+                                info, 100, PrefixMatch.None, false, 0, true,
+                                endLine = endLine, endCol = endCol)
+  suggestDef.inlayHintInfo = suggestToSuggestInlayHint(suggestDef)
+  suggestDef.section = ideInlayHints
+  if sym.kind == skForVar:
+    suggestDef.inlayHintInfo.allowInsert = false
+  suggestResult(graph.config, suggestDef)
+
 const
   # kinds for ideOutline and ideGlobalSymbols
   searchableSymKinds = {skField, skEnumField, skIterator, skMethod, skFunc, skProc, skConverter, skTemplate}
@@ -910,7 +948,7 @@ proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile,
       graph.markDirtyIfNeeded(dirtyFile.string, fileInfoIdx(conf, file))
 
   # these commands require fully compiled project
-  if cmd in {ideUse, ideDus, ideGlobalSymbols, ideChk} and graph.needsCompilation():
+  if cmd in {ideUse, ideDus, ideGlobalSymbols, ideChk, ideInlayHints} and graph.needsCompilation():
     graph.recompilePartially()
     # when doing incremental build for the project root we should make sure that
     # 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,
 
     graph.markDirty fileIndex
     graph.markClientsDirty fileIndex
+  of ideInlayHints:
+    myLog fmt "Executing inlayHints"
+    var endLine = 0
+    var endCol = -1
+    var i = 0
+    i += skipWhile(tag, seps, i)
+    i += parseInt(tag, endLine, i)
+    i += skipWhile(tag, seps, i)
+    i += parseInt(tag, endCol, i)
+    let s = graph.findSymDataInRange(file, line, col, endLine, endCol)
+    for q in s:
+      if q.sym.kind in {skLet, skVar, skForVar} and q.isDecl and not q.sym.hasUserSpecifiedType:
+        graph.suggestInlayHintResult(q.sym, q.info, ideInlayHints)
   else:
     myLog fmt "Discarding {cmd}"