summary refs log tree commit diff stats
path: root/compiler/suggest.nim
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/suggest.nim')
-rw-r--r--compiler/suggest.nim141
1 files changed, 94 insertions, 47 deletions
diff --git a/compiler/suggest.nim b/compiler/suggest.nim
index 56210d6db..ebabed465 100644
--- a/compiler/suggest.nim
+++ b/compiler/suggest.nim
@@ -32,7 +32,7 @@
 
 # included from sigmatch.nim
 
-import algorithm, sequtils, prefixmatches
+import algorithm, prefixmatches
 
 when defined(nimsuggest):
   import passes, tables # importer
@@ -41,9 +41,11 @@ const
   sep = '\t'
 
 type
-  Suggest* = object
+  Suggest* = ref object
     section*: IdeCmd
     qualifiedPath*: seq[string]
+    name*: PIdent                # not used beyond sorting purposes; name is also
+                                 # part of 'qualifiedPath'
     filePath*: string
     line*: int                   # Starts at 1
     column*: int                 # Starts at 0
@@ -54,8 +56,9 @@ type
     isGlobal*: bool # is a global variable
     contextFits*: bool # type/non-type context matches
     prefix*: PrefixMatch
-    localUsages*, globalUsages*: int # more usages is better
+    scope*, localUsages*, globalUsages*: int # more usages is better
     tokenLen*: int
+  Suggestions* = seq[Suggest]
 
 var
   suggestionResultHook*: proc (result: Suggest) {.closure.}
@@ -68,26 +71,51 @@ template origModuleName(m: PSym): string = m.name.s
 proc findDocComment(n: PNode): PNode =
   if n == nil: return nil
   if not isNil(n.comment): return n
-  for i in countup(0, safeLen(n)-1):
-    result = findDocComment(n.sons[i])
+  if n.kind in {nkStmtList, nkStmtListExpr} and n.len > 0:
+    result = findDocComment(n.sons[0])
     if result != nil: return
+    if n.len > 1:
+      result = findDocComment(n.sons[1])
+  elif n.kind in {nkAsgn, nkFastAsgn} and n.len == 2:
+    result = findDocComment(n.sons[1])
 
 proc extractDocComment(s: PSym): string =
-  let n = findDocComment(s.ast)
+  var n = findDocComment(s.ast)
+  if n.isNil and s.kind in routineKinds and s.ast != nil:
+    n = findDocComment(s.ast[bodyPos])
   if not n.isNil:
     result = n.comment.replace("\n##", "\n").strip
   else:
     result = ""
 
+proc cmpSuggestions(a, b: Suggest): int =
+  template cf(field) {.dirty.} =
+    result = b.field.int - a.field.int
+    if result != 0: return result
+
+  cf scope
+  cf prefix
+  # when the first type matches, it's better when it's a generic match:
+  cf quality
+  cf contextFits
+  cf localUsages
+  cf globalUsages
+  # if all is equal, sort alphabetically for deterministic output,
+  # independent of hashing order:
+  result = cmp(a.name.s, b.name.s)
+
 proc symToSuggest(s: PSym, isLocal: bool, section: string, li: TLineInfo;
                   quality: range[0..100]; prefix: PrefixMatch;
-                  inTypeContext: bool): Suggest =
+                  inTypeContext: bool; scope: int): Suggest =
+  new(result)
   result.section = parseIdeCmd(section)
   result.quality = quality
   result.isGlobal = sfGlobal in s.flags
   result.tokenLen = s.name.s.len
   result.prefix = prefix
   result.contextFits = inTypeContext == (s.kind in {skType, skGenericParam})
+  result.scope = scope
+  result.name = s.name
   when defined(nimsuggest):
     result.globalUsages = s.allUsages.len
     var c = 0
@@ -161,8 +189,9 @@ proc `$`*(suggest: Suggest): string =
         result.add($suggest.prefix)
 
 proc symToSuggest(s: PSym, isLocal: bool, section: string;
-                  quality: range[0..100], prefix: PrefixMatch; inTypeContext: bool): Suggest =
-  result = symToSuggest(s, isLocal, section, s.info, quality, prefix, inTypeContext)
+                  quality: range[0..100], prefix: PrefixMatch; inTypeContext: bool;
+                  scope: int): Suggest =
+  result = symToSuggest(s, isLocal, section, s.info, quality, prefix, inTypeContext, scope)
 
 proc suggestResult(s: Suggest) =
   if not isNil(suggestionResultHook):
@@ -170,11 +199,21 @@ proc suggestResult(s: Suggest) =
   else:
     suggestWriteln($s)
 
+proc produceOutput(a: var Suggestions) =
+  if gIdeCmd in {ideSug, ideCon}:
+    a.sort cmpSuggestions
+  if not isNil(suggestionResultHook):
+    for s in a:
+      suggestionResultHook(s)
+  else:
+    for s in a:
+      suggestWriteln($s)
+
 proc filterSym(s: PSym; prefix: PNode; res: var PrefixMatch): bool {.inline.} =
   proc prefixMatch(s: PSym; n: PNode): PrefixMatch =
     case n.kind
-    of nkIdent: result = s.name.s.prefixMatch(n.ident.s)
-    of nkSym: result = s.name.s.prefixMatch(n.sym.name.s)
+    of nkIdent: result = n.ident.s.prefixMatch(s.name.s)
+    of nkSym: result = n.sym.name.s.prefixMatch(s.name.s)
     of nkOpenSymChoice, nkClosedSymChoice, nkAccQuoted:
       if n.len > 0:
         result = prefixMatch(s, n[0])
@@ -198,33 +237,38 @@ proc fieldVisible*(c: PContext, f: PSym): bool {.inline.} =
       result = true
       break
 
-proc suggestField(c: PContext, s: PSym; f: PNode; outputs: var int) =
+proc suggestField(c: PContext, s: PSym; f: PNode; outputs: var Suggestions) =
   var pm: PrefixMatch
   if filterSym(s, f, pm) and fieldVisible(c, s):
-    suggestResult(symToSuggest(s, isLocal=true, $ideSug, 100, pm, c.inTypeContext > 0))
-    inc outputs
+    outputs.add(symToSuggest(s, isLocal=true, $ideSug, 100, pm, c.inTypeContext > 0, 0))
+
+proc getQuality(s: PSym): range[0..100] =
+  if s.typ != nil and s.typ.len > 1:
+    var exp = s.typ.sons[1].skipTypes({tyGenericInst, tyVar, tyAlias})
+    if exp.kind == tyVarargs: exp = elemType(exp)
+    if exp.kind in {tyExpr, tyStmt, tyGenericParam, tyAnything}: return 50
+  return 100
 
 template wholeSymTab(cond, section: untyped) =
   var isLocal = true
+  var scopeN = 0
   for scope in walkScopes(c.currentScope):
     if scope == c.topLevelScope: isLocal = false
-    var entries = sequtils.toSeq(items(scope.symbols))
-    sort(entries) do (a,b: PSym) -> int:
-      return cmp(a.name.s, b.name.s)
-    for item in entries:
+    dec scopeN
+    for item in scope.symbols:
       let it {.inject.} = item
       var pm {.inject.}: PrefixMatch
       if cond:
-        suggestResult(symToSuggest(it, isLocal = isLocal, section, 100, pm, c.inTypeContext > 0))
-        inc outputs
+        outputs.add(symToSuggest(it, isLocal = isLocal, section, getQuality(it),
+                                 pm, c.inTypeContext > 0, scopeN))
 
-proc suggestSymList(c: PContext, list, f: PNode, outputs: var int) =
+proc suggestSymList(c: PContext, list, f: PNode, outputs: var Suggestions) =
   for i in countup(0, sonsLen(list) - 1):
     if list.sons[i].kind == nkSym:
       suggestField(c, list.sons[i].sym, f, outputs)
     #else: InternalError(list.info, "getSymFromList")
 
-proc suggestObject(c: PContext, n, f: PNode, outputs: var int) =
+proc suggestObject(c: PContext, n, f: PNode, outputs: var Suggestions) =
   case n.kind
   of nkRecList:
     for i in countup(0, sonsLen(n)-1): suggestObject(c, n.sons[i], f, outputs)
@@ -256,7 +300,7 @@ proc argsFit(c: PContext, candidate: PSym, n, nOrig: PNode): bool =
   else:
     result = false
 
-proc suggestCall(c: PContext, n, nOrig: PNode, outputs: var int) =
+proc suggestCall(c: PContext, n, nOrig: PNode, outputs: var Suggestions) =
   wholeSymTab(filterSym(it, nil, pm) and nameFits(c, it, n) and argsFit(c, it, n, nOrig),
               $ideCon)
 
@@ -273,23 +317,24 @@ proc typeFits(c: PContext, s: PSym, firstArg: PType): bool {.inline.} =
       if exp.kind in {tyExpr, tyStmt, tyGenericParam, tyAnything}: return
     result = sigmatch.argtypeMatches(c, s.typ.sons[1], firstArg)
 
-proc suggestOperations(c: PContext, n, f: PNode, typ: PType, outputs: var int) =
+proc suggestOperations(c: PContext, n, f: PNode, typ: PType, outputs: var Suggestions) =
   assert typ != nil
   wholeSymTab(filterSymNoOpr(it, f, pm) and typeFits(c, it, typ), $ideSug)
 
-proc suggestEverything(c: PContext, n, f: PNode, outputs: var int) =
+proc suggestEverything(c: PContext, n, f: PNode, outputs: var Suggestions) =
   # do not produce too many symbols:
   var isLocal = true
+  var scopeN = 0
   for scope in walkScopes(c.currentScope):
     if scope == c.topLevelScope: isLocal = false
+    dec scopeN
     for it in items(scope.symbols):
       var pm: PrefixMatch
       if filterSym(it, f, pm):
-        suggestResult(symToSuggest(it, isLocal = isLocal, $ideSug, 0, pm, c.inTypeContext > 0))
-        inc outputs
+        outputs.add(symToSuggest(it, isLocal = isLocal, $ideSug, 0, pm, c.inTypeContext > 0, scopeN))
     if scope == c.topLevelScope and f.isNil: break
 
-proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var int) =
+proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions) =
   # special code that deals with ``myObj.``. `n` is NOT the nkDotExpr-node, but
   # ``myObj``.
   var typ = n.typ
@@ -307,10 +352,9 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var int) =
         else:
           for it in items(n.sym.tab):
             if filterSym(it, field, pm):
-              suggestResult(symToSuggest(it, isLocal=false, $ideSug, 100, pm, c.inTypeContext > 0))
-              inc outputs
-          suggestResult(symToSuggest(m, isLocal=false, $ideMod, 100, PrefixMatch.None,
-            c.inTypeContext > 0))
+              outputs.add(symToSuggest(it, isLocal=false, $ideSug, 100, pm, c.inTypeContext > 0, -100))
+          outputs.add(symToSuggest(m, isLocal=false, $ideMod, 100, PrefixMatch.None,
+            c.inTypeContext > 0, -99))
 
   if typ == nil:
     # a module symbol has no type for example:
@@ -319,13 +363,11 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var int) =
         # all symbols accessible, because we are in the current module:
         for it in items(c.topLevelScope.symbols):
           if filterSym(it, field, pm):
-            suggestResult(symToSuggest(it, isLocal=false, $ideSug, 100, pm, c.inTypeContext > 0))
-            inc outputs
+            outputs.add(symToSuggest(it, isLocal=false, $ideSug, 100, pm, c.inTypeContext > 0, -99))
       else:
         for it in items(n.sym.tab):
           if filterSym(it, field, pm):
-            suggestResult(symToSuggest(it, isLocal=false, $ideSug, 100, pm, c.inTypeContext > 0))
-            inc outputs
+            outputs.add(symToSuggest(it, isLocal=false, $ideSug, 100, pm, c.inTypeContext > 0, -99))
     else:
       # fallback:
       suggestEverything(c, n, field, outputs)
@@ -408,17 +450,16 @@ when defined(nimsuggest):
     s.allUsages.add(info)
 
 var
-  #usageSym*: PSym
   lastLineInfo*: TLineInfo
 
 proc findUsages(info: TLineInfo; s: PSym; usageSym: var PSym) =
   if suggestVersion < 2:
     if usageSym == nil and isTracked(info, s.name.s.len):
       usageSym = s
-      suggestResult(symToSuggest(s, isLocal=false, $ideUse, 100, PrefixMatch.None, false))
+      suggestResult(symToSuggest(s, isLocal=false, $ideUse, 100, PrefixMatch.None, false, 0))
     elif s == usageSym:
       if lastLineInfo != info:
-        suggestResult(symToSuggest(s, isLocal=false, $ideUse, info, 100, PrefixMatch.None, false))
+        suggestResult(symToSuggest(s, isLocal=false, $ideUse, info, 100, PrefixMatch.None, false, 0))
       lastLineInfo = info
 
 when defined(nimsuggest):
@@ -426,12 +467,12 @@ when defined(nimsuggest):
     #echo "usages ", len(s.allUsages)
     for info in s.allUsages:
       let x = if info == s.info and info.col == s.info.col: "def" else: "use"
-      suggestResult(symToSuggest(s, isLocal=false, x, info, 100, PrefixMatch.None, false))
+      suggestResult(symToSuggest(s, isLocal=false, x, info, 100, PrefixMatch.None, false, 0))
 
 proc findDefinition(info: TLineInfo; s: PSym) =
   if s.isNil: return
   if isTracked(info, s.name.s.len):
-    suggestResult(symToSuggest(s, isLocal=false, $ideDef, 100, PrefixMatch.None, false))
+    suggestResult(symToSuggest(s, isLocal=false, $ideDef, 100, PrefixMatch.None, false, 0))
     suggestQuit()
 
 proc ensureIdx[T](x: var T, y: int) =
@@ -455,13 +496,13 @@ proc suggestSym*(info: TLineInfo; s: PSym; usageSym: var PSym; isDecl=true) {.in
       findDefinition(info, s)
     elif gIdeCmd == ideDus and s != nil:
       if isTracked(info, s.name.s.len):
-        suggestResult(symToSuggest(s, isLocal=false, $ideDef, 100, PrefixMatch.None, false))
+        suggestResult(symToSuggest(s, isLocal=false, $ideDef, 100, PrefixMatch.None, false, 0))
       findUsages(info, s, usageSym)
     elif gIdeCmd == ideHighlight and info.fileIndex == gTrackPos.fileIndex:
-      suggestResult(symToSuggest(s, isLocal=false, $ideHighlight, info, 100, PrefixMatch.None, false))
+      suggestResult(symToSuggest(s, isLocal=false, $ideHighlight, info, 100, PrefixMatch.None, false, 0))
     elif gIdeCmd == ideOutline and info.fileIndex == gTrackPos.fileIndex and
         isDecl:
-      suggestResult(symToSuggest(s, isLocal=false, $ideOutline, info, 100, PrefixMatch.None, false))
+      suggestResult(symToSuggest(s, isLocal=false, $ideOutline, info, 100, PrefixMatch.None, false, 0))
 
 proc markUsed(info: TLineInfo; s: PSym; usageSym: var PSym) =
   incl(s.flags, sfUsed)
@@ -488,11 +529,11 @@ proc suggestExpr*(c: PContext, node: PNode) =
   if gTrackPos.line < 0: return
   var cp = inCheckpoint(node.info)
   if cp == cpNone: return
-  var outputs = 0
   # This keeps semExpr() from coming here recursively:
   if c.compilesContextId > 0: return
   inc(c.compilesContextId)
 
+  var outputs: Suggestions = @[]
   if gIdeCmd == ideSug:
     var n = findClosestDot(node)
     if n == nil: n = node
@@ -531,7 +572,9 @@ proc suggestExpr*(c: PContext, node: PNode) =
       suggestCall(c, a, n, outputs)
 
   dec(c.compilesContextId)
-  if outputs > 0 and gIdeCmd in {ideSug, ideCon, ideDef}: suggestQuit()
+  if outputs.len > 0 and gIdeCmd in {ideSug, ideCon, ideDef}:
+    produceOutput(outputs)
+    suggestQuit()
 
 proc suggestStmt*(c: PContext, n: PNode) =
   suggestExpr(c, n)
@@ -542,11 +585,15 @@ proc suggestSentinel*(c: PContext) =
   inc(c.compilesContextId)
   # suggest everything:
   var isLocal = true
+  var outputs: Suggestions = @[]
+  var scopeN = 0
   for scope in walkScopes(c.currentScope):
     if scope == c.topLevelScope: isLocal = false
+    dec scopeN
     for it in items(scope.symbols):
       var pm: PrefixMatch
       if filterSymNoOpr(it, nil, pm):
-        suggestResult(symToSuggest(it, isLocal = isLocal, $ideSug, 0, PrefixMatch.None, false))
+        outputs.add(symToSuggest(it, isLocal = isLocal, $ideSug, 0, PrefixMatch.None, false, scopeN))
 
+  produceOutput(outputs)
   dec(c.compilesContextId)