summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/prefixmatches.nim86
-rw-r--r--compiler/suggest.nim94
2 files changed, 145 insertions, 35 deletions
diff --git a/compiler/prefixmatches.nim b/compiler/prefixmatches.nim
new file mode 100644
index 000000000..2630225de
--- /dev/null
+++ b/compiler/prefixmatches.nim
@@ -0,0 +1,86 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2017 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+from strutils import toLowerAscii
+
+type
+  PrefixMatch* {.pure.} = enum
+    None,   ## no prefix detected
+    Prefix, ## prefix does match the symbol
+    Substr, ## prefix is a substring of the symbol
+    Abbrev  ## prefix is an abbreviation of the symbol
+
+proc prefixMatch*(p, s: string): PrefixMatch =
+  template eq(a, b): bool = a.toLowerAscii == b.toLowerAscii
+  if p.len > s.len: return PrefixMatch.None
+  var i = 0
+  let L = s.len
+  # check for prefix/contains:
+  while i < L:
+    if s[i] == '_': inc i
+    if eq(s[i], p[0]):
+      var ii = i+1
+      var jj = 1
+      while ii < L and jj < p.len:
+        if p[jj] == '_': inc jj
+        if s[ii] == '_': inc ii
+        if not eq(s[ii], p[jj]): break
+        inc ii
+        inc jj
+
+      if jj >= p.len:
+        if i == 0: return PrefixMatch.Prefix
+        else: return PrefixMatch.Substr
+    inc i
+  # check for abbrev:
+  if eq(s[0], p[0]):
+    i = 1
+    var j = 1
+    while i < s.len:
+      if s[i] == '_' and i < s.len-1:
+        if j < p.len and eq(p[j], s[i+1]): inc j
+        else: return PrefixMatch.None
+      if s[i] in {'A'..'Z'} and s[i-1] notin {'A'..'Z'}:
+        if j < p.len and eq(p[j], s[i]): inc j
+        else: return PrefixMatch.None
+      inc i
+    return PrefixMatch.Abbrev
+  return PrefixMatch.None
+
+when isMainModule:
+  import macros
+
+  macro check(val, body: untyped): untyped =
+    result = newStmtList()
+    expectKind body, nnkStmtList
+    for b in body:
+      expectKind b, nnkPar
+      expectLen b, 2
+      let p = b[0]
+      let s = b[1]
+      result.add quote do:
+        echo prefixMatch(`p`, `s`) == `val`
+
+  check PrefixMatch.Prefix:
+    ("abc", "abc")
+    ("a", "abc")
+    ("xyz", "X_yzzzZe")
+
+  check PrefixMatch.Substr:
+    ("b", "abc")
+    ("abc", "fooabcabc")
+    ("abC", "foo_AB_c")
+
+  check PrefixMatch.Abbrev:
+    ("abc", "AxxxBxxxCxxx")
+    ("xyz", "X_yabcZe")
+
+  check PrefixMatch.None:
+    ("foobar", "afkslfjd_as")
+    ("xyz", "X_yuuZuuZe")
diff --git a/compiler/suggest.nim b/compiler/suggest.nim
index 6aaca4185..ff390dffd 100644
--- a/compiler/suggest.nim
+++ b/compiler/suggest.nim
@@ -32,7 +32,7 @@
 
 # included from sigmatch.nim
 
-import algorithm, sequtils
+import algorithm, sequtils, prefixmatches
 
 when defined(nimsuggest):
   import passes, tables # importer
@@ -52,6 +52,9 @@ type
     forth*: string               # type
     quality*: range[0..100]   # matching quality
     isGlobal*: bool # is a global variable
+    contextFits*: bool # type/non-type context matches
+    prefix*: PrefixMatch
+    localUsages*, globalUsages*: int # more usages is better
     tokenLen*: int
 
 var
@@ -77,11 +80,20 @@ proc extractDocComment(s: PSym): string =
     result = ""
 
 proc symToSuggest(s: PSym, isLocal: bool, section: string, li: TLineInfo;
-                  quality: range[0..100]): Suggest =
+                  quality: range[0..100]; prefix: PrefixMatch;
+                  inTypeContext: bool): Suggest =
   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})
+  when defined(nimsuggest):
+    result.globalUsages = s.allUsages.len
+    var c = 0
+    for u in s.allUsages:
+      if u.fileIndex == li.fileIndex: inc c
+    result.localUsages = c
   if optIdeTerse in gGlobalOptions:
     result.symkind = s.kind
     result.filePath = toFullPath(li)
@@ -144,10 +156,12 @@ proc `$`*(suggest: Suggest): string =
     if suggestVersion == 2:
       result.add(sep)
       result.add($suggest.quality)
+      result.add(sep)
+      result.add($suggest.prefix)
 
 proc symToSuggest(s: PSym, isLocal: bool, section: string;
-                  quality: range[0..100]): Suggest =
-  result = symToSuggest(s, isLocal, section, s.info, quality)
+                  quality: range[0..100], prefix: PrefixMatch; inTypeContext: bool): Suggest =
+  result = symToSuggest(s, isLocal, section, s.info, quality, prefix, inTypeContext)
 
 proc suggestResult(s: Suggest) =
   if not isNil(suggestionResultHook):
@@ -155,20 +169,24 @@ proc suggestResult(s: Suggest) =
   else:
     suggestWriteln($s)
 
-proc filterSym(s: PSym; prefix: PNode): bool {.inline.} =
-  proc prefixMatch(s: PSym; n: PNode): bool =
+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.startsWith(n.ident.s)
-    of nkSym: result = s.name.s.startsWith(n.sym.name.s)
+    of nkIdent: result = s.name.s.prefixMatch(n.ident.s)
+    of nkSym: result = s.name.s.prefixMatch(n.sym.name.s)
     of nkOpenSymChoice, nkClosedSymChoice, nkAccQuoted:
       if n.len > 0:
         result = prefixMatch(s, n[0])
     else: discard
   if s.kind != skModule:
-    result = prefix.isNil or prefixMatch(s, prefix)
+    if prefix != nil:
+      res = prefixMatch(s, prefix)
+      result = res != PrefixMatch.None
+    else:
+      result = true
 
-proc filterSymNoOpr(s: PSym; prefix: PNode): bool {.inline.} =
-  result = filterSym(s, prefix) and s.name.s[0] in lexer.SymChars and
+proc filterSymNoOpr(s: PSym; prefix: PNode; res: var PrefixMatch): bool {.inline.} =
+  result = filterSym(s, prefix, res) and s.name.s[0] in lexer.SymChars and
      not isKeyword(s.name)
 
 proc fieldVisible*(c: PContext, f: PSym): bool {.inline.} =
@@ -180,8 +198,9 @@ proc fieldVisible*(c: PContext, f: PSym): bool {.inline.} =
       break
 
 proc suggestField(c: PContext, s: PSym; f: PNode; outputs: var int) =
-  if filterSym(s, f) and fieldVisible(c, s):
-    suggestResult(symToSuggest(s, isLocal=true, $ideSug, 100))
+  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
 
 template wholeSymTab(cond, section: untyped) =
@@ -193,8 +212,9 @@ template wholeSymTab(cond, section: untyped) =
       return cmp(a.name.s, b.name.s)
     for item in entries:
       let it {.inject.} = item
+      var pm {.inject.}: PrefixMatch
       if cond:
-        suggestResult(symToSuggest(it, isLocal = isLocal, section, 100))
+        suggestResult(symToSuggest(it, isLocal = isLocal, section, 100, pm, c.inTypeContext > 0))
         inc outputs
 
 proc suggestSymList(c: PContext, list, f: PNode, outputs: var int) =
@@ -236,7 +256,7 @@ proc argsFit(c: PContext, candidate: PSym, n, nOrig: PNode): bool =
     result = false
 
 proc suggestCall(c: PContext, n, nOrig: PNode, outputs: var int) =
-  wholeSymTab(filterSym(it, nil) and nameFits(c, it, n) and argsFit(c, it, n, nOrig),
+  wholeSymTab(filterSym(it, nil, pm) and nameFits(c, it, n) and argsFit(c, it, n, nOrig),
               $ideCon)
 
 proc typeFits(c: PContext, s: PSym, firstArg: PType): bool {.inline.} =
@@ -254,7 +274,7 @@ proc typeFits(c: PContext, s: PSym, firstArg: PType): bool {.inline.} =
 
 proc suggestOperations(c: PContext, n, f: PNode, typ: PType, outputs: var int) =
   assert typ != nil
-  wholeSymTab(filterSymNoOpr(it, f) and typeFits(c, it, typ), $ideSug)
+  wholeSymTab(filterSymNoOpr(it, f, pm) and typeFits(c, it, typ), $ideSug)
 
 proc suggestEverything(c: PContext, n, f: PNode, outputs: var int) =
   # do not produce too many symbols:
@@ -262,8 +282,9 @@ proc suggestEverything(c: PContext, n, f: PNode, outputs: var int) =
   for scope in walkScopes(c.currentScope):
     if scope == c.topLevelScope: isLocal = false
     for it in items(scope.symbols):
-      if filterSym(it, f):
-        suggestResult(symToSuggest(it, isLocal = isLocal, $ideSug, 0))
+      var pm: PrefixMatch
+      if filterSym(it, f, pm):
+        suggestResult(symToSuggest(it, isLocal = isLocal, $ideSug, 0, pm, c.inTypeContext > 0))
         inc outputs
     if scope == c.topLevelScope and f.isNil: break
 
@@ -271,6 +292,7 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var int) =
   # special code that deals with ``myObj.``. `n` is NOT the nkDotExpr-node, but
   # ``myObj``.
   var typ = n.typ
+  var pm: PrefixMatch
   when defined(nimsuggest):
     if n.kind == nkSym and n.sym.kind == skError and suggestVersion == 2:
       # consider 'foo.|' where 'foo' is some not imported module.
@@ -283,10 +305,11 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var int) =
         if m == nil: typ = nil
         else:
           for it in items(n.sym.tab):
-            if filterSym(it, field):
-              suggestResult(symToSuggest(it, isLocal=false, $ideSug, 100))
+            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))
+          suggestResult(symToSuggest(m, isLocal=false, $ideMod, 100, PrefixMatch.None,
+            c.inTypeContext > 0))
 
   if typ == nil:
     # a module symbol has no type for example:
@@ -294,13 +317,13 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var int) =
       if n.sym == c.module:
         # all symbols accessible, because we are in the current module:
         for it in items(c.topLevelScope.symbols):
-          if filterSym(it, field):
-            suggestResult(symToSuggest(it, isLocal=false, $ideSug, 100))
+          if filterSym(it, field, pm):
+            suggestResult(symToSuggest(it, isLocal=false, $ideSug, 100, pm, c.inTypeContext > 0))
             inc outputs
       else:
         for it in items(n.sym.tab):
-          if filterSym(it, field):
-            suggestResult(symToSuggest(it, isLocal=false, $ideSug, 100))
+          if filterSym(it, field, pm):
+            suggestResult(symToSuggest(it, isLocal=false, $ideSug, 100, pm, c.inTypeContext > 0))
             inc outputs
     else:
       # fallback:
@@ -313,7 +336,7 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var int) =
       t = t.sons[0]
     suggestOperations(c, n, field, typ, outputs)
   else:
-    let orig = skipTypes(typ, {tyGenericInst, tyAlias})
+    let orig = typ # skipTypes(typ, {tyGenericInst, tyAlias})
     typ = skipTypes(typ, {tyGenericInst, tyVar, tyPtr, tyRef, tyAlias})
     if typ.kind == tyObject:
       var t = typ
@@ -391,10 +414,10 @@ 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))
+      suggestResult(symToSuggest(s, isLocal=false, $ideUse, 100, PrefixMatch.None, false))
     elif s == usageSym:
       if lastLineInfo != info:
-        suggestResult(symToSuggest(s, isLocal=false, $ideUse, info, 100))
+        suggestResult(symToSuggest(s, isLocal=false, $ideUse, info, 100, PrefixMatch.None, false))
       lastLineInfo = info
 
 when defined(nimsuggest):
@@ -402,12 +425,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))
+      suggestResult(symToSuggest(s, isLocal=false, x, info, 100, PrefixMatch.None, false))
 
 proc findDefinition(info: TLineInfo; s: PSym) =
   if s.isNil: return
   if isTracked(info, s.name.s.len):
-    suggestResult(symToSuggest(s, isLocal=false, $ideDef, 100))
+    suggestResult(symToSuggest(s, isLocal=false, $ideDef, 100, PrefixMatch.None, false))
     suggestQuit()
 
 proc ensureIdx[T](x: var T, y: int) =
@@ -431,13 +454,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))
+        suggestResult(symToSuggest(s, isLocal=false, $ideDef, 100, PrefixMatch.None, false))
       findUsages(info, s, usageSym)
     elif gIdeCmd == ideHighlight and info.fileIndex == gTrackPos.fileIndex:
-      suggestResult(symToSuggest(s, isLocal=false, $ideHighlight, info, 100))
+      suggestResult(symToSuggest(s, isLocal=false, $ideHighlight, info, 100, PrefixMatch.None, false))
     elif gIdeCmd == ideOutline and info.fileIndex == gTrackPos.fileIndex and
         isDecl:
-      suggestResult(symToSuggest(s, isLocal=false, $ideOutline, info, 100))
+      suggestResult(symToSuggest(s, isLocal=false, $ideOutline, info, 100, PrefixMatch.None, false))
 
 proc markUsed(info: TLineInfo; s: PSym; usageSym: var PSym) =
   incl(s.flags, sfUsed)
@@ -521,7 +544,8 @@ proc suggestSentinel*(c: PContext) =
   for scope in walkScopes(c.currentScope):
     if scope == c.topLevelScope: isLocal = false
     for it in items(scope.symbols):
-      if filterSymNoOpr(it, nil):
-        suggestResult(symToSuggest(it, isLocal = isLocal, $ideSug, 0))
+      var pm: PrefixMatch
+      if filterSymNoOpr(it, nil, pm):
+        suggestResult(symToSuggest(it, isLocal = isLocal, $ideSug, 0, PrefixMatch.None, false))
 
   dec(c.compilesContextId)