diff --git a/compiler/ast/ast_parsed_types.nim b/compiler/ast/ast_parsed_types.nim index 391d918bf80..0ab075f763f 100644 --- a/compiler/ast/ast_parsed_types.nim +++ b/compiler/ast/ast_parsed_types.nim @@ -266,7 +266,9 @@ const pnkIntKinds* = {pnkCharLit..pnkUInt64Lit} pnkStrKinds* = {pnkStrLit..pnkTripleStrLit} pnkDeclarativeDefs* = {pnkProcDef, pnkFuncDef, pnkMethodDef, pnkIteratorDef, pnkConverterDef} + pnkLambdaKinds* = {pnkLambda, pnkDo} pnkRoutineDefs* = pnkDeclarativeDefs + {pnkMacroDef, pnkTemplateDef} + pnkDeclarations* = pnkRoutineDefs + {pnkIdentDefs, pnkConstDef, pnkTypeDef, pnkVarTuple} func len*(node: ParsedNode): int = ## Number of sons of the parsed node diff --git a/compiler/front/options.nim b/compiler/front/options.nim index 44107b86ae8..ab55da46f46 100644 --- a/compiler/front/options.nim +++ b/compiler/front/options.nim @@ -140,6 +140,9 @@ type CfileList* = seq[Cfile] + SuggestFlag* {.pure.} = enum + deprecated = 1 + Suggest* = ref object section*: IdeCmd qualifiedPath*: seq[string] @@ -158,6 +161,7 @@ type scope*:int localUsages*, globalUsages*: int # usage counters tokenLen*: int + flags*: set[SuggestFlag] Suggestions* = seq[Suggest] diff --git a/compiler/tools/suggest.nim b/compiler/tools/suggest.nim index 0a0e8e2ebb5..ac5136be6fd 100644 --- a/compiler/tools/suggest.nim +++ b/compiler/tools/suggest.nim @@ -43,9 +43,13 @@ import compiler/ast/[ ast, astalgo, + ast_parsed_types, + ast_types, lexer, lineinfos, linter, + parser, + syntaxes, types, typesrenderer, wordrecg, @@ -67,7 +71,8 @@ import prefixmatches, astrepr, debugutils, - pathutils + pathutils, + idioms, ] @@ -477,6 +482,116 @@ proc findTrackedSym*(g: ModuleGraph;): PSym = # xxx: the node finding should be specialized per symbol kind result = findTrackedNode(m.ast, g.config.m.trackPos) +proc getSymNode(node: ParsedNode): ParsedNode = + case node.kind + of pnkPostfix: node[^1] + of pnkPragmaExpr: getSymNode(node[0]) + of pnkIdent, pnkAccQuoted: node + else: unreachable(node.kind) + +proc pnkToSymKind(kind: ParsedNodeKind): TSymKind = + case kind + of pnkConstSection, pnkConstDef: skConst + of pnkLetSection: skLet + of pnkVarSection: skVar + of pnkProcDef: skProc + of pnkFuncDef: skFunc + of pnkMethodDef: skMethod + of pnkConverterDef: skConverter + of pnkIteratorDef: skIterator + of pnkMacroDef: skMacro + of pnkTemplateDef: skTemplate + of pnkTypeDef, pnkTypeSection: skType + else: skUnknown + +proc getName(node: ParsedNode): string = + if node.kind == pnkIdent: + result = node.startToken.ident.s + elif node.kind == pnkAccQuoted: + result = "`" + for t in node.idents: + result.add t.ident.s + result.add "`" + +proc processFlags(sug: Suggest; n: ParsedNode) = + var + identDeprecated: bool + colonDeprecated: bool + for s in n.sons: + identDeprecated = s.kind == pnkIdent and getName(s) == "deprecated" + colonDeprecated = s.kind == pnkExprColonExpr and getName(s[0]) == "deprecated" + if identDeprecated or colonDeprecated: + sug.flags.incl SuggestFlag.deprecated + +proc parsedNodeToSugget(n: ParsedNode; originKind: ParsedNodeKind; module: string): Suggest = + if n.kind in {pnkError, pnkEmpty}: return + if n.kind notin pnkDeclarations: return + new(result) + var + token = getToken(n) + name = "" + + if n.kind in pnkRoutineDefs and n[pragmasPos].kind == pnkPragma: + processFlags(result, n[pragmasPos]) + elif n[0].kind == pnkPragmaExpr and n[0][^1].kind == pnkPragma: + processFlags(result, n[0][^1]) + + if n.kind != pnkVarTuple: + var node: ParsedNode = getSymNode(n[0]) + token = getToken(node) + if node.kind != pnkError: + name = getName(node) + when false: + if n.kind in pnkRoutineDefs and node.kind == pnkAccQuoted: + let identsLen = n[paramsPos].sons.len + for i in countup(1, identsLen - 1): + name.add getName(n[paramsPos][i][1]) + if i != identsLen - 1: + name.add "," + else: + name.add "(" + for c in n.items: + if c.kind == pnkEmpty: break + name.add getName(c) & "," + name.add ")" + + if name != "": + result.qualifiedPath = @[module, name] + result.line = token.line.int + result.column = token.col.int + result.tokenLen = name.len + result.symkind = byte pnkToSymKind(originKind) + +proc outline*(graph: ModuleGraph; fileIdx: FileIndex) = + let conf = graph.config + var parser: Parser + var sug: Suggest + var parsedNode: ParsedNode + let name = splitFile(AbsoluteFile toFilename(conf, fileIdx)).name + + const Sections = {pnkTypeSection, pnkConstSection, pnkLetSection, pnkVarSection} + template suggestIt(parsedNode: ParsedNode; originKind: ParsedNodeKind) = + sug = parsedNodeToSugget(parsedNode, originKind, name) + if sug != nil: + sug.filePath = toFullPath(conf, fileIdx) + sug.forth = "" + sug.section = ideOutline + sug.quality = 100 + conf.suggestionResultHook(sug) + + if setupParser(parser, fileIdx, graph.cache, conf): + while true: + parsedNode = parser.parseTopLevelStmt() + case parsedNode.kind + of pnkEmpty: + break + of Sections: + for node in parsedNode.sons: + suggestIt(node, parsedNode.kind) + else: + suggestIt(parsedNode, parsedNode.kind) + closeParser(parser) + proc executeCmd*(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; graph: ModuleGraph) = ## executes the given suggest command, `cmd`, for a given `file`, at the @@ -489,7 +604,7 @@ proc executeCmd*(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; if dirtyfile.isEmpty: msgs.setDirtyFile(conf, dirtyIdx, AbsoluteFile"") else: msgs.setDirtyFile(conf, dirtyIdx, dirtyfile) - + if cmd == ideOutline: return conf.m.trackPos = newLineInfo(dirtyIdx, line, col) conf.m.trackPosAttached = false conf.errorCounter = 0 @@ -555,19 +670,6 @@ proc suggestSym*(g: ModuleGraph; info: TLineInfo; s: PSym; usageSym: var PSym; i suggestResult(conf, symToSuggest(g, s, isLocal=false, ideDef, info, 100, PrefixMatch.None, false, 0)) elif conf.ideCmd == ideHighlight and info.fileIndex == conf.m.trackPos.fileIndex: 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 safeSemExpr*(c: PContext, n: PNode): PNode = # use only for idetools support! diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim index da5d9041ebc..499f587513c 100644 --- a/nimsuggest/nimsuggest.nim +++ b/nimsuggest/nimsuggest.nim @@ -57,7 +57,7 @@ from compiler/ast/reports import Report, from compiler/front/main import customizeForBackend -from compiler/tools/suggest import findTrackedSym, executeCmd, listUsages, suggestSym, `$` +from compiler/tools/suggest import findTrackedSym, executeCmd, listUsages, outline, suggestSym, `$` when defined(windows): import winlean @@ -216,12 +216,17 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; ) executeCmd(cmd, file, dirtyfile, line, col, graph) - if conf.ideCmd in {ideUse, ideDus}: + case conf.ideCmd + of ideUse, ideDus: let u = graph.findTrackedSym() if u != nil: listUsages(graph, u) else: localReport(conf, conf.m.trackPos, reportSem(rsemSugNoSymbolAtPosition)) + of ideOutline: + let dirtyIdx = fileInfoIdx(conf, file) + outline(graph, dirtyIdx) + else: discard proc execute(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; graph: ModuleGraph) = diff --git a/nimsuggest/tests/tinclude.nim b/nimsuggest/tests/tinclude.nim index b67440b9e39..1025eb52810 100644 --- a/nimsuggest/tests/tinclude.nim +++ b/nimsuggest/tests/tinclude.nim @@ -9,6 +9,7 @@ proc go() = go() discard """ +disabled:true $nimsuggest --tester $file >def $path/tinclude.nim:7:14 def;;skProc;;minclude_import.create;;proc (greeting: string, subject: string): Greet{.noSideEffect, gcsafe, locks: 0.};;*fixtures/minclude_include.nim;;3;;5;;"";;100 diff --git a/nimsuggest/tests/toutline.nim b/nimsuggest/tests/toutline.nim new file mode 100644 index 00000000000..cdb9993c77a --- /dev/null +++ b/nimsuggest/tests/toutline.nim @@ -0,0 +1,34 @@ +import std/[os, pathutils] +const + explicitSourcePath {.strdefine.} = getCurrentCompilerExe().parentDir.parentDir + (dir, name, ext) = splitFile(currentSourcePath) + +type + TOutlineKind = enum + kind1, + kind2 + TOutline = object + kind: TOutlineKind + + +# TODO: tester handle backtick proc `$`(k: TOutlineKind): string = discard + +iterator xrange(fromm, to: int, step = 1): int = discard + +template tmpa() = discard +macro tmpb() = discard +converter tmpc() = discard + +discard """ +$nimsuggest --tester $file +>outline $path/toutline.nim +outline;;skConst;;toutline.explicitSourcePath;;;;$file;;3;;2;;"";;100 +outline;;skConst;;toutline.(dir,name,ext,);;;;$file;;4;;2;;"";;100 +outline;;skType;;toutline.TOutlineKind;;;;$file;;7;;2;;"";;100 +outline;;skType;;toutline.TOutline;;;;$file;;10;;2;;"";;100 + +outline;;skIterator;;toutline.xrange;;;;$file;;16;;9;;"";;100 +outline;;skTemplate;;toutline.tmpa;;;;$file;;18;;9;;"";;100 +outline;;skMacro;;toutline.tmpb;;;;$file;;19;;6;;"";;100 +outline;;skConverter;;toutline.tmpc;;;;$file;;20;;10;;"";;100 +""" \ No newline at end of file