diff options
-rw-r--r-- | compiler/prefixmatches.nim | 86 | ||||
-rw-r--r-- | compiler/suggest.nim | 94 |
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) |