nimsuggest: chk checks the full project lazily; much better suggestions orderings

This commit is contained in:
Andreas Rumpf 2017-03-08 00:38:48 +01:00
commit 91a4b448fd
8 changed files with 164 additions and 68 deletions

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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) =

View file

@ -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
* *
""" """

View 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
*
"""

View file

@ -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*
""" """

View file

@ -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
""" """