navigator.nim 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2021 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Supports the "nim check --ic:on --defusages:FILE,LINE,COL"
  10. ## IDE-like features. It uses the set of .rod files to accomplish
  11. ## its task. The set must cover a complete Nim project.
  12. import sets
  13. from os import nil
  14. from std/private/miscdollars import toLocation
  15. when defined(nimPreviewSlimSystem):
  16. import std/assertions
  17. import ".." / [ast, modulegraphs, msgs, options]
  18. import packed_ast, bitabs, ic
  19. type
  20. NavContext = object
  21. g: ModuleGraph
  22. thisModule: int32
  23. trackPos: PackedLineInfo
  24. alreadyEmitted: HashSet[string]
  25. outputSep: char # for easier testing, use short filenames and spaces instead of tabs.
  26. proc isTracked(current, trackPos: PackedLineInfo, tokenLen: int): bool =
  27. if current.file == trackPos.file and current.line == trackPos.line:
  28. let col = trackPos.col
  29. if col >= current.col and col < current.col+tokenLen:
  30. result = true
  31. else:
  32. result = false
  33. else:
  34. result = false
  35. proc searchLocalSym(c: var NavContext; s: PackedSym; info: PackedLineInfo): bool =
  36. result = s.name != LitId(0) and
  37. isTracked(info, c.trackPos, c.g.packed[c.thisModule].fromDisk.strings[s.name].len)
  38. proc searchForeignSym(c: var NavContext; s: ItemId; info: PackedLineInfo): bool =
  39. let name = c.g.packed[s.module].fromDisk.syms[s.item].name
  40. result = name != LitId(0) and
  41. isTracked(info, c.trackPos, c.g.packed[s.module].fromDisk.strings[name].len)
  42. const
  43. EmptyItemId = ItemId(module: -1'i32, item: -1'i32)
  44. proc search(c: var NavContext; tree: PackedTree): ItemId =
  45. # We use the linear representation here directly:
  46. for i in 0..high(tree.nodes):
  47. case tree.nodes[i].kind
  48. of nkSym:
  49. let item = tree.nodes[i].operand
  50. if searchLocalSym(c, c.g.packed[c.thisModule].fromDisk.syms[item], tree.nodes[i].info):
  51. return ItemId(module: c.thisModule, item: item)
  52. of nkModuleRef:
  53. if tree.nodes[i].info.line == c.trackPos.line and tree.nodes[i].info.file == c.trackPos.file:
  54. let (n1, n2) = sons2(tree, NodePos i)
  55. assert n1.kind == nkInt32Lit
  56. assert n2.kind == nkInt32Lit
  57. let pId = PackedItemId(module: n1.litId, item: tree.nodes[n2.int].operand)
  58. let itemId = translateId(pId, c.g.packed, c.thisModule, c.g.config)
  59. if searchForeignSym(c, itemId, tree.nodes[i].info):
  60. return itemId
  61. else: discard
  62. return EmptyItemId
  63. proc isDecl(tree: PackedTree; n: NodePos): bool =
  64. # XXX This is not correct yet.
  65. const declarativeNodes = procDefs + {nkMacroDef, nkTemplateDef,
  66. nkLetSection, nkVarSection, nkUsingStmt, nkConstSection, nkTypeSection,
  67. nkIdentDefs, nkEnumTy, nkVarTuple}
  68. result = n.int >= 0 and tree[n.int].kind in declarativeNodes
  69. proc usage(c: var NavContext; info: PackedLineInfo; isDecl: bool) =
  70. var m = ""
  71. var file = c.g.packed[c.thisModule].fromDisk.strings[info.file]
  72. if c.outputSep == ' ':
  73. file = os.extractFilename file
  74. toLocation(m, file, info.line.int, info.col.int + ColOffset)
  75. if not c.alreadyEmitted.containsOrIncl(m):
  76. msgWriteln c.g.config, (if isDecl: "def" else: "usage") & c.outputSep & m
  77. proc list(c: var NavContext; tree: PackedTree; sym: ItemId) =
  78. for i in 0..high(tree.nodes):
  79. case tree.nodes[i].kind
  80. of nkSym:
  81. let item = tree.nodes[i].operand
  82. if sym.item == item and sym.module == c.thisModule:
  83. usage(c, tree.nodes[i].info, isDecl(tree, parent(NodePos i)))
  84. of nkModuleRef:
  85. let (n1, n2) = sons2(tree, NodePos i)
  86. assert n1.kind == nkInt32Lit
  87. assert n2.kind == nkInt32Lit
  88. let pId = PackedItemId(module: n1.litId, item: tree.nodes[n2.int].operand)
  89. let itemId = translateId(pId, c.g.packed, c.thisModule, c.g.config)
  90. if itemId.item == sym.item and sym.module == itemId.module:
  91. usage(c, tree.nodes[i].info, isDecl(tree, parent(NodePos i)))
  92. else: discard
  93. proc searchForIncludeFile(g: ModuleGraph; fullPath: string): int =
  94. for i in 0..high(g.packed):
  95. for k in 1..high(g.packed[i].fromDisk.includes):
  96. # we start from 1 because the first "include" file is
  97. # the module's filename.
  98. if os.cmpPaths(g.packed[i].fromDisk.strings[g.packed[i].fromDisk.includes[k][0]], fullPath) == 0:
  99. return i
  100. return -1
  101. proc nav(g: ModuleGraph) =
  102. # translate the track position to a packed position:
  103. let unpacked = g.config.m.trackPos
  104. var mid = unpacked.fileIndex.int
  105. let fullPath = toFullPath(g.config, unpacked.fileIndex)
  106. if g.packed[mid].status == undefined:
  107. # check if 'mid' is an include file of some other module:
  108. mid = searchForIncludeFile(g, fullPath)
  109. if mid < 0:
  110. localError(g.config, unpacked, "unknown file name: " & fullPath)
  111. return
  112. let fileId = g.packed[mid].fromDisk.strings.getKeyId(fullPath)
  113. if fileId == LitId(0):
  114. internalError(g.config, unpacked, "cannot find a valid file ID")
  115. return
  116. var c = NavContext(
  117. g: g,
  118. thisModule: int32 mid,
  119. trackPos: PackedLineInfo(line: unpacked.line, col: unpacked.col, file: fileId),
  120. outputSep: if isDefined(g.config, "nimIcNavigatorTests"): ' ' else: '\t'
  121. )
  122. var symId = search(c, g.packed[mid].fromDisk.topLevel)
  123. if symId == EmptyItemId:
  124. symId = search(c, g.packed[mid].fromDisk.bodies)
  125. if symId == EmptyItemId:
  126. localError(g.config, unpacked, "no symbol at this position")
  127. return
  128. for i in 0..high(g.packed):
  129. # case statement here to enforce exhaustive checks.
  130. case g.packed[i].status
  131. of undefined:
  132. discard "nothing to do"
  133. of loading:
  134. assert false, "cannot check integrity: Module still loading"
  135. of stored, storing, outdated, loaded:
  136. c.thisModule = int32 i
  137. list(c, g.packed[i].fromDisk.topLevel, symId)
  138. list(c, g.packed[i].fromDisk.bodies, symId)
  139. proc navDefinition*(g: ModuleGraph) = nav(g)
  140. proc navUsages*(g: ModuleGraph) = nav(g)
  141. proc navDefusages*(g: ModuleGraph) = nav(g)
  142. proc writeRodFiles*(g: ModuleGraph) =
  143. for i in 0..high(g.packed):
  144. case g.packed[i].status
  145. of undefined, loading, stored, loaded:
  146. discard "nothing to do"
  147. of storing, outdated:
  148. closeRodFile(g, g.packed[i].module)