nimsuggest: chk checks the full project lazily; much better suggestions orderings
This commit is contained in:
parent
538c6c9898
commit
91a4b448fd
8 changed files with 164 additions and 68 deletions
|
|
@ -12,9 +12,9 @@ from strutils import toLowerAscii
|
||||||
type
|
type
|
||||||
PrefixMatch* {.pure.} = enum
|
PrefixMatch* {.pure.} = enum
|
||||||
None, ## no prefix detected
|
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
|
Abbrev ## prefix is an abbreviation of the symbol
|
||||||
|
Substr, ## prefix is a substring of the symbol
|
||||||
|
Prefix, ## prefix does match the symbol
|
||||||
|
|
||||||
proc prefixMatch*(p, s: string): PrefixMatch =
|
proc prefixMatch*(p, s: string): PrefixMatch =
|
||||||
template eq(a, b): bool = a.toLowerAscii == b.toLowerAscii
|
template eq(a, b): bool = a.toLowerAscii == b.toLowerAscii
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@
|
||||||
|
|
||||||
# included from sigmatch.nim
|
# included from sigmatch.nim
|
||||||
|
|
||||||
import algorithm, sequtils, prefixmatches
|
import algorithm, prefixmatches
|
||||||
|
|
||||||
when defined(nimsuggest):
|
when defined(nimsuggest):
|
||||||
import passes, tables # importer
|
import passes, tables # importer
|
||||||
|
|
@ -41,9 +41,11 @@ const
|
||||||
sep = '\t'
|
sep = '\t'
|
||||||
|
|
||||||
type
|
type
|
||||||
Suggest* = object
|
Suggest* = ref object
|
||||||
section*: IdeCmd
|
section*: IdeCmd
|
||||||
qualifiedPath*: seq[string]
|
qualifiedPath*: seq[string]
|
||||||
|
name*: PIdent # not used beyond sorting purposes; name is also
|
||||||
|
# part of 'qualifiedPath'
|
||||||
filePath*: string
|
filePath*: string
|
||||||
line*: int # Starts at 1
|
line*: int # Starts at 1
|
||||||
column*: int # Starts at 0
|
column*: int # Starts at 0
|
||||||
|
|
@ -54,8 +56,9 @@ type
|
||||||
isGlobal*: bool # is a global variable
|
isGlobal*: bool # is a global variable
|
||||||
contextFits*: bool # type/non-type context matches
|
contextFits*: bool # type/non-type context matches
|
||||||
prefix*: PrefixMatch
|
prefix*: PrefixMatch
|
||||||
localUsages*, globalUsages*: int # more usages is better
|
scope*, localUsages*, globalUsages*: int # more usages is better
|
||||||
tokenLen*: int
|
tokenLen*: int
|
||||||
|
Suggestions* = seq[Suggest]
|
||||||
|
|
||||||
var
|
var
|
||||||
suggestionResultHook*: proc (result: Suggest) {.closure.}
|
suggestionResultHook*: proc (result: Suggest) {.closure.}
|
||||||
|
|
@ -68,26 +71,51 @@ template origModuleName(m: PSym): string = m.name.s
|
||||||
proc findDocComment(n: PNode): PNode =
|
proc findDocComment(n: PNode): PNode =
|
||||||
if n == nil: return nil
|
if n == nil: return nil
|
||||||
if not isNil(n.comment): return n
|
if not isNil(n.comment): return n
|
||||||
for i in countup(0, safeLen(n)-1):
|
if n.kind in {nkStmtList, nkStmtListExpr} and n.len > 0:
|
||||||
result = findDocComment(n.sons[i])
|
result = findDocComment(n.sons[0])
|
||||||
if result != nil: return
|
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 =
|
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:
|
if not n.isNil:
|
||||||
result = n.comment.replace("\n##", "\n").strip
|
result = n.comment.replace("\n##", "\n").strip
|
||||||
else:
|
else:
|
||||||
result = ""
|
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;
|
proc symToSuggest(s: PSym, isLocal: bool, section: string, li: TLineInfo;
|
||||||
quality: range[0..100]; prefix: PrefixMatch;
|
quality: range[0..100]; prefix: PrefixMatch;
|
||||||
inTypeContext: bool): Suggest =
|
inTypeContext: bool; scope: int): Suggest =
|
||||||
|
new(result)
|
||||||
result.section = parseIdeCmd(section)
|
result.section = parseIdeCmd(section)
|
||||||
result.quality = quality
|
result.quality = quality
|
||||||
result.isGlobal = sfGlobal in s.flags
|
result.isGlobal = sfGlobal in s.flags
|
||||||
result.tokenLen = s.name.s.len
|
result.tokenLen = s.name.s.len
|
||||||
result.prefix = prefix
|
result.prefix = prefix
|
||||||
result.contextFits = inTypeContext == (s.kind in {skType, skGenericParam})
|
result.contextFits = inTypeContext == (s.kind in {skType, skGenericParam})
|
||||||
|
result.scope = scope
|
||||||
|
result.name = s.name
|
||||||
when defined(nimsuggest):
|
when defined(nimsuggest):
|
||||||
result.globalUsages = s.allUsages.len
|
result.globalUsages = s.allUsages.len
|
||||||
var c = 0
|
var c = 0
|
||||||
|
|
@ -161,8 +189,9 @@ proc `$`*(suggest: Suggest): string =
|
||||||
result.add($suggest.prefix)
|
result.add($suggest.prefix)
|
||||||
|
|
||||||
proc symToSuggest(s: PSym, isLocal: bool, section: string;
|
proc symToSuggest(s: PSym, isLocal: bool, section: string;
|
||||||
quality: range[0..100], prefix: PrefixMatch; inTypeContext: bool): Suggest =
|
quality: range[0..100], prefix: PrefixMatch; inTypeContext: bool;
|
||||||
result = symToSuggest(s, isLocal, section, s.info, quality, prefix, inTypeContext)
|
scope: int): Suggest =
|
||||||
|
result = symToSuggest(s, isLocal, section, s.info, quality, prefix, inTypeContext, scope)
|
||||||
|
|
||||||
proc suggestResult(s: Suggest) =
|
proc suggestResult(s: Suggest) =
|
||||||
if not isNil(suggestionResultHook):
|
if not isNil(suggestionResultHook):
|
||||||
|
|
@ -170,11 +199,21 @@ proc suggestResult(s: Suggest) =
|
||||||
else:
|
else:
|
||||||
suggestWriteln($s)
|
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 filterSym(s: PSym; prefix: PNode; res: var PrefixMatch): bool {.inline.} =
|
||||||
proc prefixMatch(s: PSym; n: PNode): PrefixMatch =
|
proc prefixMatch(s: PSym; n: PNode): PrefixMatch =
|
||||||
case n.kind
|
case n.kind
|
||||||
of nkIdent: result = s.name.s.prefixMatch(n.ident.s)
|
of nkIdent: result = n.ident.s.prefixMatch(s.name.s)
|
||||||
of nkSym: result = s.name.s.prefixMatch(n.sym.name.s)
|
of nkSym: result = n.sym.name.s.prefixMatch(s.name.s)
|
||||||
of nkOpenSymChoice, nkClosedSymChoice, nkAccQuoted:
|
of nkOpenSymChoice, nkClosedSymChoice, nkAccQuoted:
|
||||||
if n.len > 0:
|
if n.len > 0:
|
||||||
result = prefixMatch(s, n[0])
|
result = prefixMatch(s, n[0])
|
||||||
|
|
@ -198,33 +237,38 @@ proc fieldVisible*(c: PContext, f: PSym): bool {.inline.} =
|
||||||
result = true
|
result = true
|
||||||
break
|
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
|
var pm: PrefixMatch
|
||||||
if filterSym(s, f, pm) and fieldVisible(c, s):
|
if filterSym(s, f, pm) and fieldVisible(c, s):
|
||||||
suggestResult(symToSuggest(s, isLocal=true, $ideSug, 100, pm, c.inTypeContext > 0))
|
outputs.add(symToSuggest(s, isLocal=true, $ideSug, 100, pm, c.inTypeContext > 0, 0))
|
||||||
inc outputs
|
|
||||||
|
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) =
|
template wholeSymTab(cond, section: untyped) =
|
||||||
var isLocal = true
|
var isLocal = true
|
||||||
|
var scopeN = 0
|
||||||
for scope in walkScopes(c.currentScope):
|
for scope in walkScopes(c.currentScope):
|
||||||
if scope == c.topLevelScope: isLocal = false
|
if scope == c.topLevelScope: isLocal = false
|
||||||
var entries = sequtils.toSeq(items(scope.symbols))
|
dec scopeN
|
||||||
sort(entries) do (a,b: PSym) -> int:
|
for item in scope.symbols:
|
||||||
return cmp(a.name.s, b.name.s)
|
|
||||||
for item in entries:
|
|
||||||
let it {.inject.} = item
|
let it {.inject.} = item
|
||||||
var pm {.inject.}: PrefixMatch
|
var pm {.inject.}: PrefixMatch
|
||||||
if cond:
|
if cond:
|
||||||
suggestResult(symToSuggest(it, isLocal = isLocal, section, 100, pm, c.inTypeContext > 0))
|
outputs.add(symToSuggest(it, isLocal = isLocal, section, getQuality(it),
|
||||||
inc outputs
|
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):
|
for i in countup(0, sonsLen(list) - 1):
|
||||||
if list.sons[i].kind == nkSym:
|
if list.sons[i].kind == nkSym:
|
||||||
suggestField(c, list.sons[i].sym, f, outputs)
|
suggestField(c, list.sons[i].sym, f, outputs)
|
||||||
#else: InternalError(list.info, "getSymFromList")
|
#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
|
case n.kind
|
||||||
of nkRecList:
|
of nkRecList:
|
||||||
for i in countup(0, sonsLen(n)-1): suggestObject(c, n.sons[i], f, outputs)
|
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:
|
else:
|
||||||
result = false
|
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),
|
wholeSymTab(filterSym(it, nil, pm) and nameFits(c, it, n) and argsFit(c, it, n, nOrig),
|
||||||
$ideCon)
|
$ideCon)
|
||||||
|
|
||||||
|
|
@ -273,23 +317,24 @@ proc typeFits(c: PContext, s: PSym, firstArg: PType): bool {.inline.} =
|
||||||
if exp.kind in {tyExpr, tyStmt, tyGenericParam, tyAnything}: return
|
if exp.kind in {tyExpr, tyStmt, tyGenericParam, tyAnything}: return
|
||||||
result = sigmatch.argtypeMatches(c, s.typ.sons[1], firstArg)
|
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
|
assert typ != nil
|
||||||
wholeSymTab(filterSymNoOpr(it, f, pm) 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) =
|
proc suggestEverything(c: PContext, n, f: PNode, outputs: var Suggestions) =
|
||||||
# do not produce too many symbols:
|
# do not produce too many symbols:
|
||||||
var isLocal = true
|
var isLocal = true
|
||||||
|
var scopeN = 0
|
||||||
for scope in walkScopes(c.currentScope):
|
for scope in walkScopes(c.currentScope):
|
||||||
if scope == c.topLevelScope: isLocal = false
|
if scope == c.topLevelScope: isLocal = false
|
||||||
|
dec scopeN
|
||||||
for it in items(scope.symbols):
|
for it in items(scope.symbols):
|
||||||
var pm: PrefixMatch
|
var pm: PrefixMatch
|
||||||
if filterSym(it, f, pm):
|
if filterSym(it, f, pm):
|
||||||
suggestResult(symToSuggest(it, isLocal = isLocal, $ideSug, 0, pm, c.inTypeContext > 0))
|
outputs.add(symToSuggest(it, isLocal = isLocal, $ideSug, 0, pm, c.inTypeContext > 0, scopeN))
|
||||||
inc outputs
|
|
||||||
if scope == c.topLevelScope and f.isNil: break
|
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
|
# special code that deals with ``myObj.``. `n` is NOT the nkDotExpr-node, but
|
||||||
# ``myObj``.
|
# ``myObj``.
|
||||||
var typ = n.typ
|
var typ = n.typ
|
||||||
|
|
@ -307,10 +352,9 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var int) =
|
||||||
else:
|
else:
|
||||||
for it in items(n.sym.tab):
|
for it in items(n.sym.tab):
|
||||||
if filterSym(it, field, pm):
|
if filterSym(it, field, pm):
|
||||||
suggestResult(symToSuggest(it, isLocal=false, $ideSug, 100, pm, c.inTypeContext > 0))
|
outputs.add(symToSuggest(it, isLocal=false, $ideSug, 100, pm, c.inTypeContext > 0, -100))
|
||||||
inc outputs
|
outputs.add(symToSuggest(m, isLocal=false, $ideMod, 100, PrefixMatch.None,
|
||||||
suggestResult(symToSuggest(m, isLocal=false, $ideMod, 100, PrefixMatch.None,
|
c.inTypeContext > 0, -99))
|
||||||
c.inTypeContext > 0))
|
|
||||||
|
|
||||||
if typ == nil:
|
if typ == nil:
|
||||||
# a module symbol has no type for example:
|
# 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:
|
# all symbols accessible, because we are in the current module:
|
||||||
for it in items(c.topLevelScope.symbols):
|
for it in items(c.topLevelScope.symbols):
|
||||||
if filterSym(it, field, pm):
|
if filterSym(it, field, pm):
|
||||||
suggestResult(symToSuggest(it, isLocal=false, $ideSug, 100, pm, c.inTypeContext > 0))
|
outputs.add(symToSuggest(it, isLocal=false, $ideSug, 100, pm, c.inTypeContext > 0, -99))
|
||||||
inc outputs
|
|
||||||
else:
|
else:
|
||||||
for it in items(n.sym.tab):
|
for it in items(n.sym.tab):
|
||||||
if filterSym(it, field, pm):
|
if filterSym(it, field, pm):
|
||||||
suggestResult(symToSuggest(it, isLocal=false, $ideSug, 100, pm, c.inTypeContext > 0))
|
outputs.add(symToSuggest(it, isLocal=false, $ideSug, 100, pm, c.inTypeContext > 0, -99))
|
||||||
inc outputs
|
|
||||||
else:
|
else:
|
||||||
# fallback:
|
# fallback:
|
||||||
suggestEverything(c, n, field, outputs)
|
suggestEverything(c, n, field, outputs)
|
||||||
|
|
@ -408,17 +450,16 @@ when defined(nimsuggest):
|
||||||
s.allUsages.add(info)
|
s.allUsages.add(info)
|
||||||
|
|
||||||
var
|
var
|
||||||
#usageSym*: PSym
|
|
||||||
lastLineInfo*: TLineInfo
|
lastLineInfo*: TLineInfo
|
||||||
|
|
||||||
proc findUsages(info: TLineInfo; s: PSym; usageSym: var PSym) =
|
proc findUsages(info: TLineInfo; s: PSym; usageSym: var PSym) =
|
||||||
if suggestVersion < 2:
|
if suggestVersion < 2:
|
||||||
if usageSym == nil and isTracked(info, s.name.s.len):
|
if usageSym == nil and isTracked(info, s.name.s.len):
|
||||||
usageSym = s
|
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:
|
elif s == usageSym:
|
||||||
if lastLineInfo != info:
|
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
|
lastLineInfo = info
|
||||||
|
|
||||||
when defined(nimsuggest):
|
when defined(nimsuggest):
|
||||||
|
|
@ -426,12 +467,12 @@ when defined(nimsuggest):
|
||||||
#echo "usages ", len(s.allUsages)
|
#echo "usages ", len(s.allUsages)
|
||||||
for info in s.allUsages:
|
for info in s.allUsages:
|
||||||
let x = if info == s.info and info.col == s.info.col: "def" else: "use"
|
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) =
|
proc findDefinition(info: TLineInfo; s: PSym) =
|
||||||
if s.isNil: return
|
if s.isNil: return
|
||||||
if isTracked(info, s.name.s.len):
|
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()
|
suggestQuit()
|
||||||
|
|
||||||
proc ensureIdx[T](x: var T, y: int) =
|
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)
|
findDefinition(info, s)
|
||||||
elif gIdeCmd == ideDus and s != nil:
|
elif gIdeCmd == ideDus and s != nil:
|
||||||
if isTracked(info, s.name.s.len):
|
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)
|
findUsages(info, s, usageSym)
|
||||||
elif gIdeCmd == ideHighlight and info.fileIndex == gTrackPos.fileIndex:
|
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
|
elif gIdeCmd == ideOutline and info.fileIndex == gTrackPos.fileIndex and
|
||||||
isDecl:
|
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) =
|
proc markUsed(info: TLineInfo; s: PSym; usageSym: var PSym) =
|
||||||
incl(s.flags, sfUsed)
|
incl(s.flags, sfUsed)
|
||||||
|
|
@ -488,11 +529,11 @@ proc suggestExpr*(c: PContext, node: PNode) =
|
||||||
if gTrackPos.line < 0: return
|
if gTrackPos.line < 0: return
|
||||||
var cp = inCheckpoint(node.info)
|
var cp = inCheckpoint(node.info)
|
||||||
if cp == cpNone: return
|
if cp == cpNone: return
|
||||||
var outputs = 0
|
|
||||||
# This keeps semExpr() from coming here recursively:
|
# This keeps semExpr() from coming here recursively:
|
||||||
if c.compilesContextId > 0: return
|
if c.compilesContextId > 0: return
|
||||||
inc(c.compilesContextId)
|
inc(c.compilesContextId)
|
||||||
|
|
||||||
|
var outputs: Suggestions = @[]
|
||||||
if gIdeCmd == ideSug:
|
if gIdeCmd == ideSug:
|
||||||
var n = findClosestDot(node)
|
var n = findClosestDot(node)
|
||||||
if n == nil: n = node
|
if n == nil: n = node
|
||||||
|
|
@ -531,7 +572,9 @@ proc suggestExpr*(c: PContext, node: PNode) =
|
||||||
suggestCall(c, a, n, outputs)
|
suggestCall(c, a, n, outputs)
|
||||||
|
|
||||||
dec(c.compilesContextId)
|
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) =
|
proc suggestStmt*(c: PContext, n: PNode) =
|
||||||
suggestExpr(c, n)
|
suggestExpr(c, n)
|
||||||
|
|
@ -542,11 +585,15 @@ proc suggestSentinel*(c: PContext) =
|
||||||
inc(c.compilesContextId)
|
inc(c.compilesContextId)
|
||||||
# suggest everything:
|
# suggest everything:
|
||||||
var isLocal = true
|
var isLocal = true
|
||||||
|
var outputs: Suggestions = @[]
|
||||||
|
var scopeN = 0
|
||||||
for scope in walkScopes(c.currentScope):
|
for scope in walkScopes(c.currentScope):
|
||||||
if scope == c.topLevelScope: isLocal = false
|
if scope == c.topLevelScope: isLocal = false
|
||||||
|
dec scopeN
|
||||||
for it in items(scope.symbols):
|
for it in items(scope.symbols):
|
||||||
var pm: PrefixMatch
|
var pm: PrefixMatch
|
||||||
if filterSymNoOpr(it, nil, pm):
|
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)
|
dec(c.compilesContextId)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import compiler / [options, commands, modules, sem,
|
||||||
passes, passaux, msgs, nimconf,
|
passes, passaux, msgs, nimconf,
|
||||||
extccomp, condsyms,
|
extccomp, condsyms,
|
||||||
sigmatch, ast, scriptconfig,
|
sigmatch, ast, scriptconfig,
|
||||||
idents, modulegraphs, vm]
|
idents, modulegraphs, vm, prefixmatches]
|
||||||
|
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
import winlean
|
import winlean
|
||||||
|
|
@ -51,6 +51,11 @@ are supported.
|
||||||
"""
|
"""
|
||||||
type
|
type
|
||||||
Mode = enum mstdin, mtcp, mepc, mcmdline
|
Mode = enum mstdin, mtcp, mepc, mcmdline
|
||||||
|
CachedMsg = object
|
||||||
|
info: TLineInfo
|
||||||
|
msg: string
|
||||||
|
sev: Severity
|
||||||
|
CachedMsgs = seq[CachedMsg]
|
||||||
|
|
||||||
var
|
var
|
||||||
gPort = 6000.Port
|
gPort = 6000.Port
|
||||||
|
|
@ -93,7 +98,7 @@ proc parseQuoted(cmd: string; outp: var string; start: int): int =
|
||||||
i += parseUntil(cmd, outp, seps, i)
|
i += parseUntil(cmd, outp, seps, i)
|
||||||
result = i
|
result = i
|
||||||
|
|
||||||
proc sexp(s: IdeCmd|TSymKind): SexpNode = sexp($s)
|
proc sexp(s: IdeCmd|TSymKind|PrefixMatch): SexpNode = sexp($s)
|
||||||
|
|
||||||
proc sexp(s: Suggest): SexpNode =
|
proc sexp(s: Suggest): SexpNode =
|
||||||
# If you change the order here, make sure to change it over in
|
# If you change the order here, make sure to change it over in
|
||||||
|
|
@ -110,6 +115,8 @@ proc sexp(s: Suggest): SexpNode =
|
||||||
s.doc,
|
s.doc,
|
||||||
s.quality
|
s.quality
|
||||||
])
|
])
|
||||||
|
if s.section == ideSug:
|
||||||
|
result.add convertSexp(s.prefix)
|
||||||
|
|
||||||
proc sexp(s: seq[Suggest]): SexpNode =
|
proc sexp(s: seq[Suggest]): SexpNode =
|
||||||
result = newSList()
|
result = newSList()
|
||||||
|
|
@ -363,7 +370,7 @@ proc replEpc(x: ThreadParams) {.thread.} =
|
||||||
"unexpected call: " & epcAPI
|
"unexpected call: " & epcAPI
|
||||||
quit errMessage
|
quit errMessage
|
||||||
|
|
||||||
proc execCmd(cmd: string; graph: ModuleGraph; cache: IdentCache) =
|
proc execCmd(cmd: string; graph: ModuleGraph; cache: IdentCache; cachedMsgs: CachedMsgs) =
|
||||||
template sentinel() =
|
template sentinel() =
|
||||||
# send sentinel for the input reading thread:
|
# send sentinel for the input reading thread:
|
||||||
results.send(Suggest(section: ideNone))
|
results.send(Suggest(section: ideNone))
|
||||||
|
|
@ -415,17 +422,19 @@ proc execCmd(cmd: string; graph: ModuleGraph; cache: IdentCache) =
|
||||||
if gIdeCmd == ideKnown:
|
if gIdeCmd == ideKnown:
|
||||||
results.send(Suggest(section: ideKnown, quality: ord(fileInfoKnown(orig))))
|
results.send(Suggest(section: ideKnown, quality: ord(fileInfoKnown(orig))))
|
||||||
else:
|
else:
|
||||||
|
if gIdeCmd == ideChk:
|
||||||
|
for cm in cachedMsgs: errorHook(cm.info, cm.msg, cm.sev)
|
||||||
execute(gIdeCmd, orig, dirtyfile, line, col-1, graph, cache)
|
execute(gIdeCmd, orig, dirtyfile, line, col-1, graph, cache)
|
||||||
sentinel()
|
sentinel()
|
||||||
|
|
||||||
proc recompileFullProject(graph: ModuleGraph; cache: IdentCache) =
|
proc recompileFullProject(graph: ModuleGraph; cache: IdentCache) =
|
||||||
echo "recompiling full project"
|
#echo "recompiling full project"
|
||||||
resetSystemArtifacts()
|
resetSystemArtifacts()
|
||||||
vm.globalCtx = nil
|
vm.globalCtx = nil
|
||||||
graph.resetAllModules()
|
graph.resetAllModules()
|
||||||
GC_fullcollect()
|
GC_fullcollect()
|
||||||
compileProject(graph, cache)
|
compileProject(graph, cache)
|
||||||
echo GC_getStatistics()
|
#echo GC_getStatistics()
|
||||||
|
|
||||||
proc mainThread(graph: ModuleGraph; cache: IdentCache) =
|
proc mainThread(graph: ModuleGraph; cache: IdentCache) =
|
||||||
if gLogging:
|
if gLogging:
|
||||||
|
|
@ -442,12 +451,13 @@ proc mainThread(graph: ModuleGraph; cache: IdentCache) =
|
||||||
suggestionResultHook = sugResultHook
|
suggestionResultHook = sugResultHook
|
||||||
graph.doStopCompile = proc (): bool = requests.peek() > 0
|
graph.doStopCompile = proc (): bool = requests.peek() > 0
|
||||||
var idle = 0
|
var idle = 0
|
||||||
|
var cachedMsgs: CachedMsgs = @[]
|
||||||
while true:
|
while true:
|
||||||
let (hasData, req) = requests.tryRecv()
|
let (hasData, req) = requests.tryRecv()
|
||||||
if hasData:
|
if hasData:
|
||||||
msgs.writelnHook = wrHook
|
msgs.writelnHook = wrHook
|
||||||
suggestionResultHook = sugResultHook
|
suggestionResultHook = sugResultHook
|
||||||
execCmd(req, graph, cache)
|
execCmd(req, graph, cache, cachedMsgs)
|
||||||
idle = 0
|
idle = 0
|
||||||
else:
|
else:
|
||||||
os.sleep 250
|
os.sleep 250
|
||||||
|
|
@ -456,7 +466,9 @@ proc mainThread(graph: ModuleGraph; cache: IdentCache) =
|
||||||
# we use some nimsuggest activity to enable a lazy recompile:
|
# we use some nimsuggest activity to enable a lazy recompile:
|
||||||
gIdeCmd = ideChk
|
gIdeCmd = ideChk
|
||||||
msgs.writelnHook = proc (s: string) = discard
|
msgs.writelnHook = proc (s: string) = discard
|
||||||
msgs.structuredErrorHook = nil
|
cachedMsgs.setLen 0
|
||||||
|
msgs.structuredErrorHook = proc (info: TLineInfo; msg: string; sev: Severity) =
|
||||||
|
cachedMsgs.add(CachedMsg(info: info, msg: msg, sev: sev))
|
||||||
suggestionResultHook = proc (s: Suggest) = discard
|
suggestionResultHook = proc (s: Suggest) = discard
|
||||||
recompileFullProject(graph, cache)
|
recompileFullProject(graph, cache)
|
||||||
|
|
||||||
|
|
@ -525,8 +537,13 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
|
||||||
suggestVersion = 2
|
suggestVersion = 2
|
||||||
gMode = mstdin
|
gMode = mstdin
|
||||||
gEmitEof = true
|
gEmitEof = true
|
||||||
|
gRefresh = false
|
||||||
of "log": gLogging = true
|
of "log": gLogging = true
|
||||||
of "refresh": gRefresh = true
|
of "refresh":
|
||||||
|
if p.val.len > 0:
|
||||||
|
gRefresh = parseBool(p.val)
|
||||||
|
else:
|
||||||
|
gRefresh = true
|
||||||
else: processSwitch(pass, p)
|
else: processSwitch(pass, p)
|
||||||
of cmdArgument:
|
of cmdArgument:
|
||||||
options.gProjectName = unixToNativePath(p.key)
|
options.gProjectName = unixToNativePath(p.key)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ template tpath(): untyped = getAppDir() / "tests"
|
||||||
proc parseTest(filename: string; epcMode=false): Test =
|
proc parseTest(filename: string; epcMode=false): Test =
|
||||||
const cursorMarker = "#[!]#"
|
const cursorMarker = "#[!]#"
|
||||||
let nimsug = curDir & addFileExt("nimsuggest", ExeExt)
|
let nimsug = curDir & addFileExt("nimsuggest", ExeExt)
|
||||||
|
let libpath = findExe("nim").splitFile().dir /../ "lib"
|
||||||
result.dest = getTempDir() / extractFilename(filename)
|
result.dest = getTempDir() / extractFilename(filename)
|
||||||
result.cmd = nimsug & " --tester " & result.dest
|
result.cmd = nimsug & " --tester " & result.dest
|
||||||
result.script = @[]
|
result.script = @[]
|
||||||
|
|
@ -42,7 +43,7 @@ proc parseTest(filename: string; epcMode=false): Test =
|
||||||
inc specSection
|
inc specSection
|
||||||
elif specSection == 1:
|
elif specSection == 1:
|
||||||
if x.startsWith("$nimsuggest"):
|
if x.startsWith("$nimsuggest"):
|
||||||
result.cmd = x % ["nimsuggest", nimsug, "file", filename]
|
result.cmd = x % ["nimsuggest", nimsug, "file", filename, "lib", libpath]
|
||||||
elif x.startsWith("!"):
|
elif x.startsWith("!"):
|
||||||
if result.cmd.len == 0:
|
if result.cmd.len == 0:
|
||||||
result.startup.add x
|
result.startup.add x
|
||||||
|
|
@ -54,7 +55,7 @@ proc parseTest(filename: string; epcMode=false): Test =
|
||||||
result.script.add((x.substr(1).replaceWord("$path", tpath()), ""))
|
result.script.add((x.substr(1).replaceWord("$path", tpath()), ""))
|
||||||
elif x.len > 0:
|
elif x.len > 0:
|
||||||
# expected output line:
|
# expected output line:
|
||||||
let x = x % ["file", filename]
|
let x = x % ["file", filename, "lib", libpath]
|
||||||
result.script[^1][1].add x.replace(";;", "\t") & '\L'
|
result.script[^1][1].add x.replace(";;", "\t") & '\L'
|
||||||
# else: ignore empty lines for better readability of the specs
|
# else: ignore empty lines for better readability of the specs
|
||||||
inc i
|
inc i
|
||||||
|
|
@ -203,6 +204,9 @@ proc sexpToAnswer(s: SexpNode): string =
|
||||||
result.add doc
|
result.add doc
|
||||||
result.add '\t'
|
result.add '\t'
|
||||||
result.add a[8].getNum
|
result.add a[8].getNum
|
||||||
|
if a.len >= 10:
|
||||||
|
result.add '\t'
|
||||||
|
result.add a[9].getStr
|
||||||
result.add '\L'
|
result.add '\L'
|
||||||
|
|
||||||
proc doReport(filename, answer, resp: string; report: var string) =
|
proc doReport(filename, answer, resp: string; report: var string) =
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,6 @@ x.#[!]#
|
||||||
discard """
|
discard """
|
||||||
$nimsuggest --tester $file
|
$nimsuggest --tester $file
|
||||||
>sug $1
|
>sug $1
|
||||||
sug;;skProc;;tno_deref.foo;;proc (y: ptr int)*;;$file;;4;;5;;"";;100
|
sug;;skProc;;tno_deref.foo;;proc (y: ptr int)*;;$file;;4;;5;;"";;100;;None
|
||||||
*
|
*
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
28
tools/nimsuggest/tests/tsug_regression.nim
Normal file
28
tools/nimsuggest/tests/tsug_regression.nim
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# test we only get suggestions, not error messages:
|
||||||
|
|
||||||
|
import tables, sets, parsecfg
|
||||||
|
|
||||||
|
type X = object
|
||||||
|
|
||||||
|
proc main =
|
||||||
|
# bug #52
|
||||||
|
var
|
||||||
|
set0 = initSet[int]()
|
||||||
|
set1 = initSet[X]()
|
||||||
|
set2 = initSet[ref int]()
|
||||||
|
|
||||||
|
map0 = initTable[int, int]()
|
||||||
|
map1 = initOrderedTable[string, int]()
|
||||||
|
cfg = loadConfig("file")
|
||||||
|
map0.#[!]#
|
||||||
|
|
||||||
|
discard """
|
||||||
|
$nimsuggest --tester $file
|
||||||
|
>sug $1
|
||||||
|
sug;;skProc;;tables.getOrDefault;;proc (t: Table[getOrDefault.A, getOrDefault.B], key: A): B;;$lib/pure/collections/tables.nim;;178;;5;;"";;100;;None
|
||||||
|
sug;;skProc;;tables.hasKey;;proc (t: Table[hasKey.A, hasKey.B], key: A): bool;;$lib/pure/collections/tables.nim;;233;;5;;"returns true iff `key` is in the table `t`.";;100;;None
|
||||||
|
sug;;skProc;;tables.add;;proc (t: var Table[add.A, add.B], key: A, val: B);;$lib/pure/collections/tables.nim;;297;;5;;"puts a new (key, value)-pair into `t` even if ``t[key]`` already exists.";;100;;None
|
||||||
|
sug;;skIterator;;tables.allValues;;iterator (t: Table[allValues.A, allValues.B], key: A): B{.inline.};;$lib/pure/collections/tables.nim;;225;;9;;"iterates over any value in the table `t` that belongs to the given `key`.";;100;;None
|
||||||
|
sug;;skProc;;tables.clear;;proc (t: var Table[clear.A, clear.B]);;$lib/pure/collections/tables.nim;;121;;5;;"Resets the table so that it is empty.";;100;;None
|
||||||
|
*
|
||||||
|
"""
|
||||||
|
|
@ -204,10 +204,10 @@ echo r
|
||||||
discard """
|
discard """
|
||||||
$nimsuggest --tester $file
|
$nimsuggest --tester $file
|
||||||
>sug $1
|
>sug $1
|
||||||
sug;;skField;;name;;string;;$file;;166;;6;;"";;100
|
sug;;skField;;age;;int;;$file;;167;;6;;"";;100;;None
|
||||||
sug;;skField;;age;;int;;$file;;167;;6;;"";;100
|
sug;;skField;;name;;string;;$file;;166;;6;;"";;100;;None
|
||||||
sug;;skMethod;;twithin_macro.age_human_yrs;;proc (self: Animal): int;;$file;;169;;9;;"";;100
|
sug;;skMethod;;twithin_macro.age_human_yrs;;proc (self: Animal): int;;$file;;169;;9;;"";;100;;None
|
||||||
sug;;skMacro;;twithin_macro.class;;proc (head: untyped, body: untyped): untyped{.gcsafe, locks: <unknown>.};;$file;;4;;6;;"Iterates over the children of the NimNode ``n``.";;100
|
sug;;skMethod;;twithin_macro.vocalize;;proc (self: Animal): string;;$file;;168;;9;;"";;100;;None
|
||||||
sug;;skMethod;;twithin_macro.vocalize;;proc (self: Animal): string;;$file;;168;;9;;"";;100
|
sug;;skMethod;;twithin_macro.vocalize;;proc (self: Rabbit): string;;$file;;184;;9;;"";;100;;None
|
||||||
sug;;skMethod;;twithin_macro.vocalize;;proc (self: Rabbit): string;;$file;;184;;9;;"";;100*
|
sug;;skMacro;;twithin_macro.class;;proc (head: untyped, body: untyped): untyped{.gcsafe, locks: <unknown>.};;$file;;4;;6;;"";;50;;None*
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,6 @@ echo r
|
||||||
discard """
|
discard """
|
||||||
$nimsuggest --tester $file
|
$nimsuggest --tester $file
|
||||||
>sug $1
|
>sug $1
|
||||||
sug;;skField;;age;;int;;$file;;167;;6;;"";;100
|
sug;;skField;;age;;int;;$file;;167;;6;;"";;100;;Prefix
|
||||||
sug;;skMethod;;twithin_macro_prefix.age_human_yrs;;proc (self: Animal): int;;$file;;169;;9;;"";;100
|
sug;;skMethod;;twithin_macro_prefix.age_human_yrs;;proc (self: Animal): int;;$file;;169;;9;;"";;100;;Prefix
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue