IC navigator: added support for include files (#17784)

* ic fixed navigator crash when track wrong/missed

Also fixed an issue with getNimcacheDir not observing the outDir.

* closer, but not sure how to test[skip ci][ci skip]

* IC navigator: added support for include files

* update

* make posix happy via expandFilename

* update

Co-authored-by: Saem Ghani <saemghani+github@gmail.com>
This commit is contained in:
Andreas Rumpf 2021-04-19 22:37:09 +02:00 • committed by GitHub
commit 3b80f0dc8e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 111 additions and 33 deletions

View file

@ -28,7 +28,7 @@ bootSwitch(usedNoGC, defined(nogc), "--gc:none")
import import
os, msgs, options, nversion, condsyms, strutils, extccomp, platform, os, msgs, options, nversion, condsyms, strutils, extccomp, platform,
wordrecg, parseutils, nimblecmd, parseopt, sequtils, lineinfos, wordrecg, parseutils, nimblecmd, parseopt, sequtils, lineinfos,
pathutils, strtabs pathutils, strtabs, pathnorm
from ast import eqTypeFlags, tfGcSafe, tfNoSideEffect from ast import eqTypeFlags, tfGcSafe, tfNoSideEffect
@ -359,31 +359,52 @@ proc processCfgPath(conf: ConfigRef; path: string, info: TLineInfo): AbsoluteDir
const const
errInvalidNumber = "$1 is not a valid number" errInvalidNumber = "$1 is not a valid number"
proc makeAbsolute(s: string): AbsoluteFile =
if isAbsolute(s):
AbsoluteFile pathnorm.normalizePath(s)
else:
AbsoluteFile pathnorm.normalizePath(os.getCurrentDir() / s)
proc setTrackingInfo(conf: ConfigRef; dirty, file, line, column: string,
info: TLineInfo) =
## set tracking info, common code for track, trackDirty, & ideTrack
var ln, col: int
if parseUtils.parseInt(line, ln) <= 0:
localError(conf, info, errInvalidNumber % line)
if parseUtils.parseInt(column, col) <= 0:
localError(conf, info, errInvalidNumber % column)
let a = makeAbsolute(file)
if dirty == "":
conf.m.trackPos = newLineInfo(conf, a, ln, col)
else:
let dirtyOriginalIdx = fileInfoIdx(conf, a)
if dirtyOriginalIdx.int32 >= 0:
msgs.setDirtyFile(conf, dirtyOriginalIdx, makeAbsolute(dirty))
conf.m.trackPos = newLineInfo(dirtyOriginalIdx, ln, col)
proc trackDirty(conf: ConfigRef; arg: string, info: TLineInfo) = proc trackDirty(conf: ConfigRef; arg: string, info: TLineInfo) =
var a = arg.split(',') var a = arg.split(',')
if a.len != 4: localError(conf, info, if a.len != 4: localError(conf, info,
"DIRTY_BUFFER,ORIGINAL_FILE,LINE,COLUMN expected") "DIRTY_BUFFER,ORIGINAL_FILE,LINE,COLUMN expected")
var line, column: int setTrackingInfo(conf, a[0], a[1], a[2], a[3], info)
if parseUtils.parseInt(a[2], line) <= 0:
localError(conf, info, errInvalidNumber % a[1])
if parseUtils.parseInt(a[3], column) <= 0:
localError(conf, info, errInvalidNumber % a[2])
let dirtyOriginalIdx = fileInfoIdx(conf, AbsoluteFile a[1])
if dirtyOriginalIdx.int32 >= 0:
msgs.setDirtyFile(conf, dirtyOriginalIdx, AbsoluteFile a[0])
conf.m.trackPos = newLineInfo(dirtyOriginalIdx, line, column)
proc track(conf: ConfigRef; arg: string, info: TLineInfo) = proc track(conf: ConfigRef; arg: string, info: TLineInfo) =
var a = arg.split(',') var a = arg.split(',')
if a.len != 3: localError(conf, info, "FILE,LINE,COLUMN expected") if a.len != 3: localError(conf, info, "FILE,LINE,COLUMN expected")
var line, column: int setTrackingInfo(conf, "", a[0], a[1], a[2], info)
if parseUtils.parseInt(a[1], line) <= 0:
localError(conf, info, errInvalidNumber % a[1]) proc trackIde(conf: ConfigRef; cmd: IdeCmd, arg: string, info: TLineInfo) =
if parseUtils.parseInt(a[2], column) <= 0: ## set the tracking info related to an ide cmd, supports optional dirty file
localError(conf, info, errInvalidNumber % a[2]) var a = arg.split(',')
conf.m.trackPos = newLineInfo(conf, AbsoluteFile a[0], line, column) case a.len
of 4:
setTrackingInfo(conf, a[0], a[1], a[2], a[3], info)
of 3:
setTrackingInfo(conf, "", a[0], a[1], a[2], info)
else:
localError(conf, info, "[DIRTY_BUFFER,]ORIGINAL_FILE,LINE,COLUMN expected")
conf.ideCmd = cmd
proc dynlibOverride(conf: ConfigRef; switch, arg: string, pass: TCmdLinePass, info: TLineInfo) = proc dynlibOverride(conf: ConfigRef; switch, arg: string, pass: TCmdLinePass, info: TLineInfo) =
if pass in {passCmd2, passPP}: if pass in {passCmd2, passPP}:
@ -851,17 +872,17 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
expectNoArg(conf, switch, arg, pass, info) expectNoArg(conf, switch, arg, pass, info)
conf.ideCmd = ideSug conf.ideCmd = ideSug
of "def": of "def":
expectNoArg(conf, switch, arg, pass, info) expectArg(conf, switch, arg, pass, info)
conf.ideCmd = ideDef trackIde(conf, ideDef, arg, info)
of "context": of "context":
expectNoArg(conf, switch, arg, pass, info) expectNoArg(conf, switch, arg, pass, info)
conf.ideCmd = ideCon conf.ideCmd = ideCon
of "usages": of "usages":
expectNoArg(conf, switch, arg, pass, info) expectArg(conf, switch, arg, pass, info)
conf.ideCmd = ideUse trackIde(conf, ideUse, arg, info)
of "defusages": of "defusages":
expectNoArg(conf, switch, arg, pass, info) expectArg(conf, switch, arg, pass, info)
conf.ideCmd = ideDus trackIde(conf, ideDus, arg, info)
of "stdout": of "stdout":
processOnOffSwitchG(conf, {optStdout}, arg, pass, info) processOnOffSwitchG(conf, {optStdout}, arg, pass, info)
of "listfullpaths": of "listfullpaths":

View file

@ -29,7 +29,7 @@ type
PackedModule* = object ## the parts of a PackedEncoder that are part of the .rod file PackedModule* = object ## the parts of a PackedEncoder that are part of the .rod file
definedSymbols: string definedSymbols: string
moduleFlags: TSymFlags moduleFlags: TSymFlags
includes: seq[(LitId, string)] # first entry is the module filename itself includes*: seq[(LitId, string)] # first entry is the module filename itself
imports: seq[LitId] # the modules this module depends on imports: seq[LitId] # the modules this module depends on
toReplay*: PackedTree # pragmas and VM specific state to replay. toReplay*: PackedTree # pragmas and VM specific state to replay.
topLevel*: PackedTree # top level statements topLevel*: PackedTree # top level statements

View file

@ -98,11 +98,31 @@ proc list(c: var NavContext; tree: PackedTree; sym: ItemId) =
usage(c, tree.nodes[i].info, isDecl(tree, parent(NodePos i))) usage(c, tree.nodes[i].info, isDecl(tree, parent(NodePos i)))
else: discard else: discard
proc searchForIncludeFile(g: ModuleGraph; fullPath: string): int =
for i in 0..high(g.packed):
for k in 1..high(g.packed[i].fromDisk.includes):
# we start from 1 because the first "include" file is
# the module's filename.
if os.cmpPaths(g.packed[i].fromDisk.strings[g.packed[i].fromDisk.includes[k][0]], fullPath) == 0:
return i
return -1
proc nav(g: ModuleGraph) = proc nav(g: ModuleGraph) =
# translate the track position to a packed position: # translate the track position to a packed position:
let unpacked = g.config.m.trackPos let unpacked = g.config.m.trackPos
let mid = unpacked.fileIndex var mid = unpacked.fileIndex.int
let fileId = g.packed[int32 mid].fromDisk.strings.getKeyId(toFullPath(g.config, mid))
let fullPath = toFullPath(g.config, unpacked.fileIndex)
if g.packed[mid].status == undefined:
# check if 'mid' is an include file of some other module:
mid = searchForIncludeFile(g, fullPath)
if mid < 0:
localError(g.config, unpacked, "unknown file name: " & fullPath)
return
let fileId = g.packed[mid].fromDisk.strings.getKeyId(fullPath)
if fileId == LitId(0): if fileId == LitId(0):
internalError(g.config, unpacked, "cannot find a valid file ID") internalError(g.config, unpacked, "cannot find a valid file ID")
@ -114,9 +134,9 @@ proc nav(g: ModuleGraph) =
trackPos: PackedLineInfo(line: unpacked.line, col: unpacked.col, file: fileId), trackPos: PackedLineInfo(line: unpacked.line, col: unpacked.col, file: fileId),
outputSep: if isDefined(g.config, "nimIcNavigatorTests"): ' ' else: '\t' outputSep: if isDefined(g.config, "nimIcNavigatorTests"): ' ' else: '\t'
) )
var symId = search(c, g.packed[int32 mid].fromDisk.topLevel) var symId = search(c, g.packed[mid].fromDisk.topLevel)
if symId == EmptyItemId: if symId == EmptyItemId:
symId = search(c, g.packed[int32 mid].fromDisk.bodies) symId = search(c, g.packed[mid].fromDisk.bodies)
if symId == EmptyItemId: if symId == EmptyItemId:
localError(g.config, unpacked, "no symbol at this position") localError(g.config, unpacked, "no symbol at this position")

View file

@ -582,6 +582,9 @@ template globalError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "")
template globalError*(conf: ConfigRef; info: TLineInfo, arg: string) = template globalError*(conf: ConfigRef; info: TLineInfo, arg: string) =
liMessage(conf, info, errGenerated, arg, doRaise, instLoc()) liMessage(conf, info, errGenerated, arg, doRaise, instLoc())
template globalError*(conf: ConfigRef; format: string, params: openArray[string]) =
liMessage(conf, unknownLineInfo, errGenerated, format % params, doRaise, instLoc())
template localError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") = template localError*(conf: ConfigRef; info: TLineInfo, msg: TMsgKind, arg = "") =
liMessage(conf, info, msg, arg, doNothing, instLoc()) liMessage(conf, info, msg, arg, doNothing, instLoc())

View file

@ -701,7 +701,10 @@ proc getNimcacheDir*(conf: ConfigRef): AbsoluteDir =
result = if not conf.nimcacheDir.isEmpty: result = if not conf.nimcacheDir.isEmpty:
conf.nimcacheDir conf.nimcacheDir
elif conf.backend == backendJs: elif conf.backend == backendJs:
if conf.outDir.isEmpty:
conf.projectPath / genSubDir conf.projectPath / genSubDir
else:
conf.outDir / genSubDir
else: else:
AbsoluteDir(getOsCacheDir() / splitFile(conf.projectName).name & AbsoluteDir(getOsCacheDir() / splitFile(conf.projectName).name &
nimcacheSuffix(conf)) nimcacheSuffix(conf))

View file

@ -490,7 +490,7 @@ proc icTests(r: var TResults; testsDir: string, cat: Category, options: string;
writeOnly = " --incremental:writeonly " writeOnly = " --incremental:writeonly "
readOnly = " --incremental:readonly " readOnly = " --incremental:readonly "
incrementalOn = " --incremental:on -d:nimIcIntegrityChecks " incrementalOn = " --incremental:on -d:nimIcIntegrityChecks "
navTestConfig = " --ic:on --defusages -d:nimIcNavigatorTests --hint[Conf]:off --warnings:off " navTestConfig = " --ic:on -d:nimIcNavigatorTests --hint[Conf]:off --warnings:off "
template test(x: untyped) = template test(x: untyped) =
testSpecWithNimcache(r, makeRawTest(file, x & options, cat), nimcache) testSpecWithNimcache(r, makeRawTest(file, x & options, cat), nimcache)

View file

@ -0,0 +1,2 @@
# An include file.
foo(3)

View file

@ -0,0 +1,29 @@
discard """
cmd: "nim check $options --defusages:$file,12,7 $file"
nimout: '''def tincludefile_temp.nim(11, 10)
usage tincludefile_temp.nim(12, 8)
'''
"""
proc foo(x: int) =
echo x
foo(3)
echo "yes", 1 != 3
#!EDIT!#
discard """
cmd: "nim check $options --defusages:$file/../minclude.nim,2,2 $file"
nimout: '''def tincludefile_temp.nim(10, 6)
usage minclude.nim(2, 1)
'''
"""
proc foo(x: int) =
echo x
include minclude

View file

@ -1,5 +1,5 @@
discard """ discard """
cmd: "nim check $options --track:$file,12,7 $file" cmd: "nim check $options --defusages:$file,12,7 $file"
nimout: '''def tnav1_temp.nim(11, 10) nimout: '''def tnav1_temp.nim(11, 10)
usage tnav1_temp.nim(12, 8) usage tnav1_temp.nim(12, 8)
''' '''
@ -16,7 +16,7 @@ echo "yes", 1 != 3
#!EDIT!# #!EDIT!#
discard """ discard """
cmd: "nim check $options --track:$file,15,2 $file" cmd: "nim check $options --defusages:$file,15,2 $file"
nimout: '''def tnav1_temp.nim(12, 6) nimout: '''def tnav1_temp.nim(12, 6)
usage tnav1_temp.nim(15, 1) usage tnav1_temp.nim(15, 1)
''' '''