diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 2824f32f68..2a3a4d13d4 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -3540,6 +3540,7 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}, expectedType: PType of nkMacroDef: result = semMacroDef(c, n) of nkTemplateDef: result = semTemplateDef(c, n) of nkImportStmt: + trySuggestModuleNames(c, n) # this particular way allows 'import' in a 'compiles' context so that # template canImport(x): bool = # compiles: diff --git a/compiler/suggest.nim b/compiler/suggest.nim index a5213086bd..a1a477ec8a 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -35,7 +35,7 @@ import prefixmatches, suggestsymdb from wordrecg import wDeprecated, wError, wAddr, wYield -import std/[algorithm, sets, parseutils, tables] +import std/[algorithm, sets, parseutils, tables, os] when defined(nimsuggest): import pathutils # importer @@ -43,6 +43,12 @@ when defined(nimsuggest): const sep = '\t' +type + ImportContext = object + isMultiImport: bool # True if we're in a [...] context + baseDir: string # e.g., "folder/" in "import folder/[..." + partialModule: string # The actual module name being typed + #template sectionSuggest(): expr = "##begin\n" & getStackTrace() & "##end\n" template origModuleName(m: PSym): string = m.name.s @@ -746,6 +752,123 @@ proc sugExpr(c: PContext, n: PNode, outputs: var Suggestions) = let prefix = if c.config.m.trackPosAttached: nil else: n suggestEverything(c, n, prefix, outputs) +proc extractImportContextFromAst(n: PNode, cursorCol: int): ImportContext = + result = ImportContext() + if n.kind != nkImportStmt: return + for child in n: + case child.kind + of nkIdent: + # Single import, e.g. import foo + if child.info.col <= cursorCol: + result.baseDir = "" + result.partialModule = child.ident.s + result.isMultiImport = false + of nkInfix: + # Directory or multi-import, e.g. import std/[os, strutils] + if child.len == 3 and child[0].kind == nkIdent and child[0].ident.s == "/": + let dir = child[1].ident.s + if child[2].kind == nkBracket: + result.baseDir = dir + result.isMultiImport = true + for modNode in child[2]: + if modNode.kind == nkIdent and modNode.info.col <= cursorCol: + result.partialModule = modNode.ident.s + elif child[2].kind == nkIdent: + if child[2].info.col <= cursorCol: + result.baseDir = dir + result.partialModule = child[2].ident.s + result.isMultiImport = false + else: + discard + +proc findModuleFile(c: PContext, partialPath: string): seq[string] = + result = @[] + let currentModuleDir = parentDir(toFullPath(c.config, FileIndex(c.module.position))) + + proc tryAddModule(path, baseName: string) = + if fileExists(path & ".nim"): + result.add(baseName) + + proc addModulesFromDir(dir, file: string; result: var seq[string]) = + if dirExists(dir): + for kind, path in walkDir(dir): + if kind in {pcFile, pcDir}: + let (_, name, ext) = splitFile(path) + if kind == pcFile: + if ext == ".nim" and name.startsWith(file): + result.add(name) + + proc collectImportModulesFromDir(dir: string, result: var seq[string]) = + for kind, path in walkDir(dir): + if kind in {pcFile, pcDir}: + let (_, name, ext) = splitFile(path) + if kind == pcFile: + if ext == ".nim" and name.startsWith(partialPath): + result.add(name) + else: + if name.startsWith(partialPath): + result.add(name) + + if '/' in partialPath: + let parts = partialPath.split('/') + let dir = parts[0] + let file = parts[1] + addModulesFromDir(currentModuleDir / dir, file, result) + for searchPath in c.config.searchPaths: + let searchDir = searchPath.string / dir + addModulesFromDir(searchDir, file, result) + else: + collectImportModulesFromDir(currentModuleDir, result) + for searchPath in c.config.searchPaths: + collectImportModulesFromDir(searchPath.string, result) + +proc suggestModuleNames(c: PContext, n: PNode) = + var suggestions: Suggestions = @[] + let partialPath = if n.kind == nkIdent: n.ident.s else: "" + proc addModuleSuggestion(path: string) = + var suggest = Suggest( + section: ideSug, + qualifiedPath: @[path], + name: addr path, + filePath: path, + line: n.info.line.int, + column: n.info.col.int, + doc: "", + quality: 100, + contextFits: true, + prefix: if partialPath.len > 0: prefixMatch(path, partialPath) + else: PrefixMatch.None, + symkind: byte skModule + ) + suggestions.add(suggest) + + let importCtx = extractImportContextFromAst(n, c.config.m.trackPos.col) + var searchPath = "" + if importCtx.baseDir.len > 0: + searchPath = importCtx.baseDir & "/" + + let possibleModules = findModuleFile(c, searchPath & importCtx.partialModule) + for moduleName in possibleModules: + if moduleName != c.module.name.s: + addModuleSuggestion(moduleName) + + produceOutput(suggestions, c.config) + suggestQuit() + +proc findImportStmtOnLine(n: PNode, line: uint16): PNode = + if n.kind in {nkImportStmt, nkFromStmt} and n.info.line == line: + return n + for i in 0.. 0: return @@ -774,7 +897,7 @@ proc suggestExprNoCheck*(c: PContext, n: PNode) = if outputs.len > 0 and c.config.ideCmd in {ideSug, ideCon, ideDef}: produceOutput(outputs, c.config) suggestQuit() - + proc suggestExpr*(c: PContext, n: PNode) = if exactEquals(c.config.m.trackPos, n.info): suggestExprNoCheck(c, n) diff --git a/nimsuggest/tests/timport1.nim b/nimsuggest/tests/timport1.nim new file mode 100644 index 0000000000..d3847b83fa --- /dev/null +++ b/nimsuggest/tests/timport1.nim @@ -0,0 +1,7 @@ +import bito#[!]# + +discard """ +$nimsuggest --tester --v4 --maxresults:1 $file +>sug $1 +sug;;skModule;;bitops;;;;bitops;;1;;0;;"";;100;;None +""" \ No newline at end of file diff --git a/nimsuggest/tests/timport2.nim b/nimsuggest/tests/timport2.nim new file mode 100644 index 0000000000..e6386e0fe1 --- /dev/null +++ b/nimsuggest/tests/timport2.nim @@ -0,0 +1,9 @@ +import fixtures/mcl#[!]# +import fixtures/[mstrutils, mfak#[!]#] +discard """ +$nimsuggest --tester --v4 --maxresults:1 $file +>sug $1 +sug;;skModule;;mclass_macro;;;;mclass_macro;;1;;0;;"";;100;;None +>sug $2 +sug;;skModule;;mfakeassert;;;;mfakeassert;;2;;0;;"";;100;;None +""" diff --git a/nimsuggest/tests/timport3.nim b/nimsuggest/tests/timport3.nim new file mode 100644 index 0000000000..4a326f5769 --- /dev/null +++ b/nimsuggest/tests/timport3.nim @@ -0,0 +1,9 @@ +import fixtu#[!]# #Suggest folders +import nimpre#[!]# #Can suggest from search path (see cmd arg below) +discard """ +$nimsuggest --tester --v4 --maxresults:1 --path:nimpretty $file +>sug $1 +sug;;skModule;;fixtures;;;;fixtures;;1;;0;;"";;100;;None +>sug $2 +sug;;skModule;;nimpretty;;;;nimpretty;;2;;0;;"";;100;;None +""" \ No newline at end of file