mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-28 17:04:41 +00:00
Initial implementation for nimsuggest import support (#24937)
Co-authored-by: Andreas Rumpf <araq4k@proton.me>
(cherry picked from commit 8080610248)
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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..<n.safeLen:
|
||||
let res = findImportStmtOnLine(n[i], line)
|
||||
if res != nil: return res
|
||||
return nil
|
||||
|
||||
template trySuggestModuleNames*(c: PContext, n: PNode) =
|
||||
if c.config.ideCmd == ideSug:
|
||||
let importNode = findImportStmtOnLine(n, c.config.m.trackPos.line)
|
||||
if importNode != nil:
|
||||
suggestModuleNames(c, importNode)
|
||||
|
||||
proc suggestExprNoCheck*(c: PContext, n: PNode) =
|
||||
# This keeps semExpr() from coming here recursively:
|
||||
if c.compilesContextId > 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)
|
||||
|
||||
|
||||
7
nimsuggest/tests/timport1.nim
Normal file
7
nimsuggest/tests/timport1.nim
Normal file
@@ -0,0 +1,7 @@
|
||||
import bito#[!]#
|
||||
|
||||
discard """
|
||||
$nimsuggest --tester --v4 --maxresults:1 $file
|
||||
>sug $1
|
||||
sug;;skModule;;bitops;;;;bitops;;1;;0;;"";;100;;None
|
||||
"""
|
||||
9
nimsuggest/tests/timport2.nim
Normal file
9
nimsuggest/tests/timport2.nim
Normal file
@@ -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
|
||||
"""
|
||||
9
nimsuggest/tests/timport3.nim
Normal file
9
nimsuggest/tests/timport3.nim
Normal file
@@ -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
|
||||
"""
|
||||
Reference in New Issue
Block a user