diff options
Diffstat (limited to 'compiler/suggest.nim')
-rw-r--r-- | compiler/suggest.nim | 555 |
1 files changed, 380 insertions, 175 deletions
diff --git a/compiler/suggest.nim b/compiler/suggest.nim index 11717c946..a5213086b 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -32,11 +32,13 @@ # included from sigmatch.nim -import algorithm, sets, prefixmatches, lineinfos, parseutils, linter -from wordrecg import wDeprecated, wError, wAddr, wYield, specialWords +import prefixmatches, suggestsymdb +from wordrecg import wDeprecated, wError, wAddr, wYield + +import std/[algorithm, sets, parseutils, tables] when defined(nimsuggest): - import passes, tables, pathutils # importer + import pathutils # importer const sep = '\t' @@ -53,13 +55,15 @@ proc findDocComment(n: PNode): PNode = if result != nil: return if n.len > 1: result = findDocComment(n[1]) - elif n.kind in {nkAsgn, nkFastAsgn} and n.len == 2: + elif n.kind in {nkAsgn, nkFastAsgn, nkSinkAsgn} and n.len == 2: result = findDocComment(n[1]) + else: + result = nil -proc extractDocComment(s: PSym): string = +proc extractDocComment(g: ModuleGraph; s: PSym): string = var n = findDocComment(s.ast) if n.isNil and s.kind in routineKinds and s.ast != nil: - n = findDocComment(s.ast[bodyPos]) + n = findDocComment(getBody(g, s)) if not n.isNil: result = n.comment.replace("\n##", "\n").strip else: @@ -70,18 +74,27 @@ proc cmpSuggestions(a, b: Suggest): int = result = b.field.int - a.field.int if result != 0: return result - cf scope cf prefix + cf contextFits + cf scope # 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[], b.name[]) -proc getTokenLenFromSource(conf: ConfigRef; ident: string; info: TLineInfo): int = +proc scanForTrailingAsterisk(line: string, start: int): int = + result = 0 + while start+result < line.len and line[start+result] in {' ', '\t'}: + inc result + if start+result < line.len and line[start+result] == '*': + inc result + else: + result = 0 + +proc getTokenLenFromSource(conf: ConfigRef; ident: string; info: TLineInfo; skipTrailingAsterisk: bool = false): int = let line = sourceLine(conf, info) column = toColumn(info) @@ -101,21 +114,34 @@ proc getTokenLenFromSource(conf: ConfigRef; ident: string; info: TLineInfo): int result = skipUntil(line, '`', column) if cmpIgnoreStyle(line[column..column + result - 1], ident) != 0: result = 0 + elif column >= 0 and line[column] == '`' and isOpeningBacktick(column): + result = skipUntil(line, '`', column + 1) + 2 + if cmpIgnoreStyle(line[column + 1..column + result - 2], ident) != 0: + result = 0 elif ident[0] in linter.Letters and ident[^1] != '=': result = identLen(line, column) - if cmpIgnoreStyle(line[column..column + result - 1], ident) != 0: + if cmpIgnoreStyle(line[column..column + result - 1], ident[0..min(result-1,len(ident)-1)]) != 0: result = 0 + if skipTrailingAsterisk and result > 0: + result += scanForTrailingAsterisk(line, column + result) else: - result = skipWhile(line, OpChars + {'[', '(', '{', ']', ')', '}'}, column) + var sourceIdent: string = "" + result = parseWhile(line, sourceIdent, + OpChars + {'[', '(', '{', ']', ')', '}'}, column) if ident[^1] == '=' and ident[0] in linter.Letters: - if line[column..column + result - 1] != "=": + if sourceIdent != "=": result = 0 - elif line[column..column + result - 1] != ident: + elif sourceIdent.len > ident.len and sourceIdent[0..ident.high] == ident: + result = ident.len + elif sourceIdent != ident: result = 0 -proc symToSuggest(conf: ConfigRef; s: PSym, isLocal: bool, section: IdeCmd, info: TLineInfo; +proc symToSuggest*(g: ModuleGraph; s: PSym, isLocal: bool, section: IdeCmd, info: TLineInfo; quality: range[0..100]; prefix: PrefixMatch; - inTypeContext: bool; scope: int): Suggest = + inTypeContext: bool; scope: int; + useSuppliedInfo = false, + endLine: uint16 = 0, + endCol = 0, extractDocs = true): Suggest = new(result) result.section = section result.quality = quality @@ -131,7 +157,7 @@ proc symToSuggest(conf: ConfigRef; s: PSym, isLocal: bool, section: IdeCmd, info if u.fileIndex == info.fileIndex: inc c result.localUsages = c result.symkind = byte s.kind - if optIdeTerse notin conf.globalOptions: + if optIdeTerse notin g.config.globalOptions: result.qualifiedPath = @[] if not isLocal and s.kind != skModule: let ow = s.owner @@ -147,61 +173,139 @@ proc symToSuggest(conf: ConfigRef; s: PSym, isLocal: bool, section: IdeCmd, info result.qualifiedPath.add(s.name.s) if s.typ != nil: - result.forth = typeToString(s.typ) + if section == ideInlayHints: + result.forth = typeToString(s.typ, preferInlayHint) + else: + result.forth = typeToString(s.typ, preferInferredEffects) else: result.forth = "" when defined(nimsuggest) and not defined(noDocgen) and not defined(leanCompiler): - result.doc = s.extractDocComment - let infox = if section in {ideUse, ideHighlight, ideOutline}: info else: s.info - result.filePath = toFullPath(conf, infox) - result.line = toLinenumber(infox) - result.column = toColumn(infox) - result.version = conf.suggestVersion - result.tokenLen = if section != ideHighlight: - s.name.s.len - else: - getTokenLenFromSource(conf, s.name.s, infox) + if extractDocs: + result.doc = extractDocComment(g, s) + if s.kind == skModule and s.ast.len != 0 and section != ideHighlight: + result.filePath = toFullPath(g.config, s.ast[0].info) + result.line = 1 + result.column = 0 + result.tokenLen = 0 + else: + let infox = + if useSuppliedInfo or section in {ideUse, ideHighlight, ideOutline, ideDeclaration}: + info + else: + s.info + result.filePath = toFullPath(g.config, infox) + result.line = toLinenumber(infox) + result.column = toColumn(infox) + result.tokenLen = if section notin {ideHighlight, ideInlayHints}: + s.name.s.len + else: + getTokenLenFromSource(g.config, s.name.s, infox, section == ideInlayHints) + result.version = g.config.suggestVersion + result.endLine = endLine + result.endCol = endCol + +proc `$`*(suggest: SuggestInlayHint): string = + result = $suggest.kind + result.add(sep) + result.add($suggest.line) + result.add(sep) + result.add($suggest.column) + result.add(sep) + result.add(suggest.label) + result.add(sep) + result.add($suggest.paddingLeft) + result.add(sep) + result.add($suggest.paddingRight) + result.add(sep) + result.add($suggest.allowInsert) + result.add(sep) + result.add(suggest.tooltip) proc `$`*(suggest: Suggest): string = - result = $suggest.section - result.add(sep) - if suggest.section == ideHighlight: - if suggest.symkind.TSymKind == skVar and suggest.isGlobal: - result.add("skGlobalVar") - elif suggest.symkind.TSymKind == skLet and suggest.isGlobal: - result.add("skGlobalLet") - else: - result.add($suggest.symkind.TSymKind) - result.add(sep) - result.add($suggest.line) - result.add(sep) - result.add($suggest.column) - result.add(sep) - result.add($suggest.tokenLen) + if suggest.section == ideInlayHints: + result = $suggest.inlayHintInfo else: - result.add($suggest.symkind.TSymKind) - result.add(sep) - if suggest.qualifiedPath.len != 0: - result.add(suggest.qualifiedPath.join(".")) - result.add(sep) - result.add(suggest.forth) - result.add(sep) - result.add(suggest.filePath) + result = $suggest.section result.add(sep) - result.add($suggest.line) - result.add(sep) - result.add($suggest.column) - result.add(sep) - when defined(nimsuggest) and not defined(noDocgen) and not defined(leanCompiler): - result.add(suggest.doc.escape) - if suggest.version == 0: + if suggest.section == ideHighlight: + if suggest.symkind.TSymKind == skVar and suggest.isGlobal: + result.add("skGlobalVar") + elif suggest.symkind.TSymKind == skLet and suggest.isGlobal: + result.add("skGlobalLet") + else: + result.add($suggest.symkind.TSymKind) + result.add(sep) + result.add($suggest.line) + result.add(sep) + result.add($suggest.column) result.add(sep) - result.add($suggest.quality) - if suggest.section == ideSug: + result.add($suggest.tokenLen) + else: + result.add($suggest.symkind.TSymKind) + result.add(sep) + if suggest.qualifiedPath.len != 0: + result.add(suggest.qualifiedPath.join(".")) + result.add(sep) + result.add(suggest.forth) + result.add(sep) + result.add(suggest.filePath) + result.add(sep) + result.add($suggest.line) + result.add(sep) + result.add($suggest.column) + result.add(sep) + when defined(nimsuggest) and not defined(noDocgen) and not defined(leanCompiler): + result.add(suggest.doc.escape) + if suggest.version == 0 or suggest.version == 3: result.add(sep) - result.add($suggest.prefix) + result.add($suggest.quality) + if suggest.section == ideSug: + result.add(sep) + result.add($suggest.prefix) -proc suggestResult(conf: ConfigRef; s: Suggest) = + if (suggest.version == 3 and suggest.section in {ideOutline, ideExpand}): + result.add(sep) + result.add($suggest.endLine) + result.add(sep) + result.add($suggest.endCol) + +proc suggestToSuggestInlayTypeHint*(sug: Suggest): SuggestInlayHint = + SuggestInlayHint( + kind: sihkType, + line: sug.line, + column: sug.column + sug.tokenLen, + label: ": " & sug.forth, + paddingLeft: false, + paddingRight: false, + allowInsert: true, + tooltip: "" + ) + +proc suggestToSuggestInlayExceptionHintLeft*(sug: Suggest, propagatedExceptions: seq[PType]): SuggestInlayHint = + SuggestInlayHint( + kind: sihkException, + line: sug.line, + column: sug.column, + label: "try ", + paddingLeft: false, + paddingRight: false, + allowInsert: false, + tooltip: "propagated exceptions: " & $propagatedExceptions + ) + +proc suggestToSuggestInlayExceptionHintRight*(sug: Suggest, propagatedExceptions: seq[PType]): SuggestInlayHint = + SuggestInlayHint( + kind: sihkException, + line: sug.line, + column: sug.column + sug.tokenLen, + label: "!", + paddingLeft: false, + paddingRight: false, + allowInsert: false, + tooltip: "propagated exceptions: " & $propagatedExceptions + ) + +proc suggestResult*(conf: ConfigRef; s: Suggest) = if not isNil(conf.suggestionResultHook): conf.suggestionResultHook(s) else: @@ -229,13 +333,17 @@ proc filterSym(s: PSym; prefix: PNode; res: var PrefixMatch): bool {.inline.} = of nkOpenSymChoice, nkClosedSymChoice, nkAccQuoted: if n.len > 0: result = prefixMatch(s, n[0]) - else: discard + else: + result = default(PrefixMatch) + else: result = default(PrefixMatch) if s.kind != skModule: if prefix != nil: res = prefixMatch(s, prefix) result = res != PrefixMatch.None else: result = true + else: + result = false 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 @@ -244,35 +352,41 @@ proc filterSymNoOpr(s: PSym; prefix: PNode; res: var PrefixMatch): bool {.inline proc fieldVisible*(c: PContext, f: PSym): bool {.inline.} = let fmoduleId = getModule(f).id result = sfExported in f.flags or fmoduleId == c.module.id - for module in c.friendModules: - if fmoduleId == module.id: - result = true - break -proc suggestField(c: PContext, s: PSym; f: PNode; info: TLineInfo; outputs: var Suggestions) = - var pm: PrefixMatch - if filterSym(s, f, pm) and fieldVisible(c, s): - outputs.add(symToSuggest(c.config, s, isLocal=true, ideSug, info, 100, pm, c.inTypeContext > 0, 0)) + if not result: + for module in c.friendModules: + if fmoduleId == module.id: return true + if f.kind == skField: + var symObj = f.owner.typ.toObjectFromRefPtrGeneric.sym + assert symObj != nil + for scope in allScopes(c.currentScope): + for sym in scope.allowPrivateAccess: + if symObj.id == sym.id: return true proc getQuality(s: PSym): range[0..100] = - if s.typ != nil and s.typ.len > 1: - var exp = s.typ[1].skipTypes({tyGenericInst, tyVar, tyLent, tyAlias, tySink}) + result = 100 + if s.typ != nil and s.typ.paramsLen > 0: + var exp = s.typ.firstParamType.skipTypes({tyGenericInst, tyVar, tyLent, tyAlias, tySink}) if exp.kind == tyVarargs: exp = elemType(exp) - if exp.kind in {tyUntyped, tyTyped, tyGenericParam, tyAnything}: return 50 - return 100 + if exp.kind in {tyUntyped, tyTyped, tyGenericParam, tyAnything}: result = 50 + + # penalize deprecated symbols + if sfDeprecated in s.flags: + result = result - 5 + +proc suggestField(c: PContext, s: PSym; f: PNode; info: TLineInfo; outputs: var Suggestions) = + var pm: PrefixMatch = default(PrefixMatch) + if filterSym(s, f, pm) and fieldVisible(c, s): + outputs.add(symToSuggest(c.graph, s, isLocal=true, ideSug, info, + s.getQuality, pm, c.inTypeContext > 0, 0)) template wholeSymTab(cond, section: untyped) {.dirty.} = - var isLocal = true - var scopeN = 0 - for scope in walkScopes(c.currentScope): - if scope == c.topLevelScope: isLocal = false - dec scopeN - for item in scope.symbols: - let it = item - var pm: PrefixMatch - if cond: - outputs.add(symToSuggest(c.config, it, isLocal = isLocal, section, info, getQuality(it), - pm, c.inTypeContext > 0, scopeN)) + for (item, scopeN, isLocal) in uniqueSyms(c): + let it = item + var pm: PrefixMatch = default(PrefixMatch) + if cond: + outputs.add(symToSuggest(c.graph, it, isLocal = isLocal, section, info, getQuality(it), + pm, c.inTypeContext > 0, scopeN)) proc suggestSymList(c: PContext, list, f: PNode; info: TLineInfo, outputs: var Suggestions) = for i in 0..<list.len: @@ -294,6 +408,7 @@ proc suggestObject(c: PContext, n, f: PNode; info: TLineInfo, outputs: var Sugge proc nameFits(c: PContext, s: PSym, n: PNode): bool = var op = if n.kind in nkCallKinds: n[0] else: n if op.kind in {nkOpenSymChoice, nkClosedSymChoice}: op = op[0] + if op.kind == nkDotExpr: op = op[1] var opr: PIdent case op.kind of nkSym: opr = op.sym.name @@ -320,17 +435,19 @@ proc suggestVar(c: PContext, n: PNode, outputs: var Suggestions) = wholeSymTab(nameFits(c, it, n), ideCon) proc typeFits(c: PContext, s: PSym, firstArg: PType): bool {.inline.} = - if s.typ != nil and s.typ.len > 1 and s.typ[1] != nil: + if s.typ != nil and s.typ.paramsLen > 0 and s.typ.firstParamType != nil: # special rule: if system and some weird generic match via 'tyUntyped' # or 'tyGenericParam' we won't list it either to reduce the noise (nobody # wants 'system.`-|` as suggestion let m = s.getModule() if m != nil and sfSystemModule in m.flags: if s.kind == skType: return - var exp = s.typ[1].skipTypes({tyGenericInst, tyVar, tyLent, tyAlias, tySink}) + var exp = s.typ.firstParamType.skipTypes({tyGenericInst, tyVar, tyLent, tyAlias, tySink}) if exp.kind == tyVarargs: exp = elemType(exp) if exp.kind in {tyUntyped, tyTyped, tyGenericParam, tyAnything}: return - result = sigmatch.argtypeMatches(c, s.typ[1], firstArg) + result = sigmatch.argtypeMatches(c, s.typ.firstParamType, firstArg) + else: + result = false proc suggestOperations(c: PContext, n, f: PNode, typ: PType, outputs: var Suggestions) = assert typ != nil @@ -339,23 +456,17 @@ proc suggestOperations(c: PContext, n, f: PNode, typ: PType, outputs: var Sugges 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): - outputs.add(symToSuggest(c.config, it, isLocal = isLocal, ideSug, n.info, 0, pm, - c.inTypeContext > 0, scopeN)) - #if scope == c.topLevelScope and f.isNil: break + for (it, scopeN, isLocal) in uniqueSyms(c): + var pm: PrefixMatch = default(PrefixMatch) + if filterSym(it, f, pm): + outputs.add(symToSuggest(c.graph, it, isLocal = isLocal, ideSug, n.info, + it.getQuality, pm, c.inTypeContext > 0, scopeN)) 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 - var pm: PrefixMatch + var pm: PrefixMatch = default(PrefixMatch) when defined(nimsuggest): if n.kind == nkSym and n.sym.kind == skError and c.config.suggestVersion == 0: # consider 'foo.|' where 'foo' is some not imported module. @@ -367,11 +478,14 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions) let m = c.graph.importModuleCallback(c.graph, c.module, fileInfoIdx(c.config, fullPath)) if m == nil: typ = nil else: - for it in items(n.sym.tab): + for it in allSyms(c.graph, n.sym): if filterSym(it, field, pm): - outputs.add(symToSuggest(c.config, it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -100)) - outputs.add(symToSuggest(c.config, m, isLocal=false, ideMod, n.info, 100, PrefixMatch.None, - c.inTypeContext > 0, -99)) + outputs.add(symToSuggest(c.graph, it, isLocal=false, ideSug, + n.info, it.getQuality, pm, + c.inTypeContext > 0, -100)) + outputs.add(symToSuggest(c.graph, m, isLocal=false, ideMod, n.info, + 100, PrefixMatch.None, c.inTypeContext > 0, + -99)) if typ == nil: # a module symbol has no type for example: @@ -380,32 +494,45 @@ proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var Suggestions) # all symbols accessible, because we are in the current module: for it in items(c.topLevelScope.symbols): if filterSym(it, field, pm): - outputs.add(symToSuggest(c.config, it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -99)) + outputs.add(symToSuggest(c.graph, it, isLocal=false, ideSug, + n.info, it.getQuality, pm, + c.inTypeContext > 0, -99)) else: - for it in items(n.sym.tab): + for it in allSyms(c.graph, n.sym): if filterSym(it, field, pm): - outputs.add(symToSuggest(c.config, it, isLocal=false, ideSug, n.info, 100, pm, c.inTypeContext > 0, -99)) + outputs.add(symToSuggest(c.graph, it, isLocal=false, ideSug, + n.info, it.getQuality, pm, + c.inTypeContext > 0, -99)) else: # fallback: suggestEverything(c, n, field, outputs) - elif typ.kind == tyEnum and n.kind == nkSym and n.sym.kind == skType: - # look up if the identifier belongs to the enum: - var t = typ - while t != nil: - suggestSymList(c, t.n, field, n.info, outputs) - t = t[0] - suggestOperations(c, n, field, typ, outputs) else: - let orig = typ # skipTypes(typ, {tyGenericInst, tyAlias, tySink}) - typ = skipTypes(typ, {tyGenericInst, tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink, tyOwned}) - if typ.kind == tyObject: + let orig = typ + typ = skipTypes(orig, {tyTypeDesc, tyGenericInst, tyVar, tyLent, tyPtr, tyRef, tyAlias, tySink, tyOwned}) + + if typ.kind == tyEnum and n.kind == nkSym and n.sym.kind == skType: + # look up if the identifier belongs to the enum: + var t = typ + while t != nil: + suggestSymList(c, t.n, field, n.info, outputs) + t = t.baseClass + elif typ.kind == tyObject: var t = typ while true: suggestObject(c, t.n, field, n.info, outputs) - if t[0] == nil: break - t = skipTypes(t[0], skipPtrs) + if t.baseClass == nil: break + t = skipTypes(t.baseClass, skipPtrs) elif typ.kind == tyTuple and typ.n != nil: - suggestSymList(c, typ.n, field, n.info, outputs) + # All tuple fields are in scope + # So go through each field and add it to the suggestions (If it passes the filter) + for node in typ.n: + if node.kind == nkSym: + let s = node.sym + var pm: PrefixMatch = default(PrefixMatch) + if filterSym(s, field, pm): + outputs.add(symToSuggest(c.graph, s, isLocal=true, ideSug, n.info, + s.getQuality, pm, c.inTypeContext > 0, 0)) + suggestOperations(c, n, field, orig, outputs) if typ != orig: suggestOperations(c, n, field, typ, outputs) @@ -416,17 +543,34 @@ type proc inCheckpoint*(current, trackPos: TLineInfo): TCheckPointResult = if current.fileIndex == trackPos.fileIndex: + result = cpNone if current.line == trackPos.line and abs(current.col-trackPos.col) < 4: return cpExact if current.line >= trackPos.line: return cpFuzzy + else: + result = cpNone proc isTracked*(current, trackPos: TLineInfo, tokenLen: int): bool = if current.fileIndex==trackPos.fileIndex and current.line==trackPos.line: let col = trackPos.col if col >= current.col and col <= current.col+tokenLen-1: - return true + result = true + else: + result = false + else: + result = false + +proc isTracked*(current, trackPos: TinyLineInfo, tokenLen: int): bool = + if current.line==trackPos.line: + let col = trackPos.col + if col >= current.col and col <= current.col+tokenLen-1: + result = true + else: + result = false + else: + result = false when defined(nimsuggest): # Since TLineInfo defined a == operator that doesn't include the column, @@ -442,28 +586,31 @@ when defined(nimsuggest): if infoB.infoToInt == infoAsInt: return s.allUsages.add(info) -proc findUsages(conf: ConfigRef; info: TLineInfo; s: PSym; usageSym: var PSym) = - if conf.suggestVersion == 1: - if usageSym == nil and isTracked(info, conf.m.trackPos, s.name.s.len): +proc findUsages(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym) = + if g.config.suggestVersion == 1: + if usageSym == nil and isTracked(info, g.config.m.trackPos, s.name.s.len): usageSym = s - suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideUse, info, 100, PrefixMatch.None, false, 0)) + suggestResult(g.config, symToSuggest(g, s, isLocal=false, ideUse, info, 100, PrefixMatch.None, false, 0)) elif s == usageSym: - if conf.lastLineInfo != info: - suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideUse, info, 100, PrefixMatch.None, false, 0)) - conf.lastLineInfo = info + if g.config.lastLineInfo != info: + suggestResult(g.config, symToSuggest(g, s, isLocal=false, ideUse, info, 100, PrefixMatch.None, false, 0)) + g.config.lastLineInfo = info when defined(nimsuggest): - proc listUsages*(conf: ConfigRef; s: PSym) = + proc listUsages*(g: ModuleGraph; s: PSym) = #echo "usages ", s.allUsages.len for info in s.allUsages: let x = if info == s.info and info.col == s.info.col: ideDef else: ideUse - suggestResult(conf, symToSuggest(conf, s, isLocal=false, x, info, 100, PrefixMatch.None, false, 0)) + suggestResult(g.config, symToSuggest(g, s, isLocal=false, x, info, 100, PrefixMatch.None, false, 0)) -proc findDefinition(conf: ConfigRef; info: TLineInfo; s: PSym) = +proc findDefinition(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym) = if s.isNil: return - if isTracked(info, conf.m.trackPos, s.name.s.len): - suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0)) - suggestQuit() + if isTracked(info, g.config.m.trackPos, s.name.s.len) or (s == usageSym and sfForward notin s.flags): + suggestResult(g.config, symToSuggest(g, s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0, useSuppliedInfo = s == usageSym)) + if sfForward notin s.flags and g.config.suggestVersion < 3: + suggestQuit() + else: + usageSym = s proc ensureIdx[T](x: var T, y: int) = if x.len <= y: x.setLen(y+1) @@ -471,9 +618,12 @@ proc ensureIdx[T](x: var T, y: int) = proc ensureSeq[T](x: var seq[T]) = if x == nil: newSeq(x, 0) -proc suggestSym*(conf: ConfigRef; info: TLineInfo; s: PSym; usageSym: var PSym; isDecl=true) {.inline.} = +proc suggestSym*(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym; isDecl=true) {.inline.} = ## misnamed: should be 'symDeclared' + let conf = g.config when defined(nimsuggest): + g.suggestSymbols.add SymInfoPair(sym: s, info: info, isDecl: isDecl), optIdeExceptionInlayHints in g.config.globalOptions + if conf.suggestVersion == 0: if s.allUsages.len == 0: s.allUsages = @[info] @@ -481,33 +631,32 @@ proc suggestSym*(conf: ConfigRef; info: TLineInfo; s: PSym; usageSym: var PSym; s.addNoDup(info) if conf.ideCmd == ideUse: - findUsages(conf, info, s, usageSym) + findUsages(g, info, s, usageSym) elif conf.ideCmd == ideDef: - findDefinition(conf, info, s) + findDefinition(g, info, s, usageSym) elif conf.ideCmd == ideDus and s != nil: if isTracked(info, conf.m.trackPos, s.name.s.len): - suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0)) - findUsages(conf, info, s, usageSym) + suggestResult(conf, symToSuggest(g, s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0)) + findUsages(g, info, s, usageSym) elif conf.ideCmd == ideHighlight and info.fileIndex == conf.m.trackPos.fileIndex: - suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideHighlight, info, 100, PrefixMatch.None, false, 0)) - elif conf.ideCmd == ideOutline and info.fileIndex == conf.m.trackPos.fileIndex and - isDecl: - suggestResult(conf, symToSuggest(conf, s, isLocal=false, ideOutline, info, 100, PrefixMatch.None, false, 0)) - -proc extractPragma(s: PSym): PNode = - if s.kind in routineKinds: - result = s.ast[pragmasPos] - elif s.kind in {skType, skVar, skLet} and s.ast[0].kind == nkPragmaExpr: - # s.ast = nkTypedef / nkPragmaExpr / [nkSym, nkPragma] - result = s.ast[0][1] - doAssert result == nil or result.kind == nkPragma + suggestResult(conf, symToSuggest(g, s, isLocal=false, ideHighlight, info, 100, PrefixMatch.None, false, 0)) + elif conf.ideCmd == ideOutline and isDecl: + # if a module is included then the info we have is inside the include and + # we need to walk up the owners until we find the outer most module, + # which will be the last skModule prior to an skPackage. + var + parentFileIndex = info.fileIndex # assume we're in the correct module + parentModule = s.owner + while parentModule != nil and parentModule.kind == skModule: + parentFileIndex = parentModule.info.fileIndex + parentModule = parentModule.owner + + if parentFileIndex == conf.m.trackPos.fileIndex: + suggestResult(conf, symToSuggest(g, s, isLocal=false, ideOutline, info, 100, PrefixMatch.None, false, 0)) proc warnAboutDeprecated(conf: ConfigRef; info: TLineInfo; s: PSym) = var pragmaNode: PNode - if optOldAst in conf.options and s.kind in {skVar, skLet}: - pragmaNode = nil - else: - pragmaNode = if s.kind == skEnumField: extractPragma(s.owner) else: extractPragma(s) + pragmaNode = if s.kind == skEnumField: extractPragma(s.owner) else: extractPragma(s) let name = if s.kind == skEnumField and sfDeprecated notin s.flags: "enum '" & s.owner.name.s & "' which contains field '" & s.name.s & "'" else: s.name.s @@ -521,14 +670,16 @@ proc warnAboutDeprecated(conf: ConfigRef; info: TLineInfo; s: PSym) = proc userError(conf: ConfigRef; info: TLineInfo; s: PSym) = let pragmaNode = extractPragma(s) - + template bail(prefix: string) = + localError(conf, info, "$1usage of '$2' is an {.error.} defined at $3" % + [prefix, s.name.s, toFileLineCol(conf, s.ast.info)]) if pragmaNode != nil: for it in pragmaNode: if whichPragma(it) == wError and it.safeLen == 2 and it[1].kind in {nkStrLit..nkTripleStrLit}: - localError(conf, info, it[1].strVal & "; usage of '$1' is a user-defined error" % s.name.s) + bail(it[1].strVal & "; ") return - localError(conf, info, "usage of '$1' is a user-defined error" % s.name.s) + bail("") proc markOwnerModuleAsUsed(c: PContext; s: PSym) = var module = s @@ -538,13 +689,14 @@ proc markOwnerModuleAsUsed(c: PContext; s: PSym) = var i = 0 while i <= high(c.unusedImports): let candidate = c.unusedImports[i][0] - if candidate == module or c.exportIndirections.contains((candidate.id, s.id)): + if candidate == module or c.importModuleMap.getOrDefault(candidate.id, int.low) == module.id or + c.exportIndirections.contains((candidate.id, s.id)): # mark it as used: c.unusedImports.del(i) else: inc i -proc markUsed(c: PContext; info: TLineInfo; s: PSym) = +proc markUsed(c: PContext; info: TLineInfo; s: PSym; checkStyle = true) = let conf = c.config incl(s.flags, sfUsed) if s.kind == skEnumField and s.owner != nil: @@ -552,12 +704,17 @@ proc markUsed(c: PContext; info: TLineInfo; s: PSym) = if sfDeprecated in s.owner.flags: warnAboutDeprecated(conf, info, s) if {sfDeprecated, sfError} * s.flags != {}: - if sfDeprecated in s.flags: warnAboutDeprecated(conf, info, s) + if sfDeprecated in s.flags: + if not (c.lastTLineInfo.line == info.line and + c.lastTLineInfo.col == info.col): + warnAboutDeprecated(conf, info, s) + c.lastTLineInfo = info + if sfError in s.flags: userError(conf, info, s) when defined(nimsuggest): - suggestSym(conf, info, s, c.graph.usageSym, false) - if {optStyleHint, optStyleError} * conf.globalOptions != {}: - styleCheckUse(conf, info, s) + suggestSym(c.graph, info, s, c.graph.usageSym, false) + if checkStyle: + styleCheckUse(c, info, s) markOwnerModuleAsUsed(c, s) proc safeSemExpr*(c: PContext, n: PNode): PNode = @@ -580,6 +737,11 @@ proc sugExpr(c: PContext, n: PNode, outputs: var Suggestions) = #if optIdeDebug in gGlobalOptions: # echo "expression ", renderTree(obj), " has type ", typeToString(obj.typ) #writeStackTrace() + elif n.kind == nkIdent: + let + prefix = if c.config.m.trackPosAttached: nil else: n + info = n.info + wholeSymTab(filterSym(it, prefix, pm), ideSug) else: let prefix = if c.config.m.trackPosAttached: nil else: n suggestEverything(c, n, prefix, outputs) @@ -621,6 +783,9 @@ proc suggestDecl*(c: PContext, n: PNode; s: PSym) = if attached: inc(c.inTypeContext) defer: if attached: dec(c.inTypeContext) + # If user is typing out an enum field, then don't provide suggestions + if s.kind == skEnumField and c.config.cmd == cmdIdeTools and exactEquals(c.config.m.trackPos, n.info): + suggestQuit() suggestExpr(c, n) proc suggestStmt*(c: PContext, n: PNode) = @@ -632,23 +797,63 @@ proc suggestEnum*(c: PContext; n: PNode; t: PType) = produceOutput(outputs, c.config) if outputs.len > 0: suggestQuit() +proc suggestPragmas*(c: PContext, n: PNode) = + ## Suggests anything that might be a pragma + ## - template that has {.pragma.} + ## - macros + ## - user pragmas + let info = n.info + var outputs: Suggestions = @[] + # First filter for template/macros + wholeSymTab(filterSym(it, n, pm) and + (sfCustomPragma in it.flags or it.kind == skMacro), + ideSug) + + # Now show suggestions for user pragmas + for pragma in c.userPragmas: + var pm = default(PrefixMatch) + if filterSym(pragma, n, pm): + outputs &= symToSuggest(c.graph, pragma, isLocal=true, ideSug, info, + pragma.getQuality, pm, c.inTypeContext > 0, 0, + extractDocs=false) + + produceOutput(outputs, c.config) + if outputs.len > 0: + suggestQuit() + +template trySuggestPragmas*(c: PContext, n: PNode) = + ## Runs [suggestPragmas] when compiling nimsuggest and + ## we are querying the node + when defined(nimsuggest): + let tmp = n + if c.config.ideCmd == ideSug and exactEquals(c.config.m.trackPos, tmp.info): + suggestPragmas(c, tmp) + proc suggestSentinel*(c: PContext) = if c.config.ideCmd != ideSug or c.module.position != c.config.m.trackPos.fileIndex.int32: return if c.compilesContextId > 0: return inc(c.compilesContextId) var outputs: Suggestions = @[] # suggest everything: - 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 filterSymNoOpr(it, nil, pm): - outputs.add(symToSuggest(c.config, it, isLocal = isLocal, ideSug, - newLineInfo(c.config.m.trackPos.fileIndex, 0, -1), 0, - PrefixMatch.None, false, scopeN)) + for (it, scopeN, isLocal) in uniqueSyms(c): + var pm: PrefixMatch = default(PrefixMatch) + if filterSymNoOpr(it, nil, pm): + outputs.add(symToSuggest(c.graph, it, isLocal = isLocal, ideSug, + newLineInfo(c.config.m.trackPos.fileIndex, 0, -1), it.getQuality, + PrefixMatch.None, false, scopeN)) dec(c.compilesContextId) produceOutput(outputs, c.config) + +when defined(nimsuggest): + proc onDef(graph: ModuleGraph, s: PSym, info: TLineInfo) = + if graph.config.suggestVersion >= 3 and info.exactEquals(s.info): + suggestSym(graph, info, s, graph.usageSym) + + template getPContext(): untyped = + when c is PContext: c + else: c.c + + template onDef*(info: TLineInfo; s: PSym) = + let c = getPContext() + onDef(c.graph, s, info) |