navigator.nim 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  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 std/[sets, tables]
  13. from std/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 iclineinfos
  19. import packed_ast, bitabs, ic
  20. type
  21. UnpackedLineInfo = object
  22. file: LitId
  23. line, col: int
  24. NavContext = object
  25. g: ModuleGraph
  26. thisModule: int32
  27. trackPos: UnpackedLineInfo
  28. alreadyEmitted: HashSet[string]
  29. outputSep: char # for easier testing, use short filenames and spaces instead of tabs.
  30. proc isTracked(man: LineInfoManager; current: PackedLineInfo, trackPos: UnpackedLineInfo, tokenLen: int): bool =
  31. let (currentFile, currentLine, currentCol) = man.unpack(current)
  32. if currentFile == trackPos.file and currentLine == trackPos.line:
  33. let col = trackPos.col
  34. if col >= currentCol and col < currentCol+tokenLen:
  35. result = true
  36. else:
  37. result = false
  38. else:
  39. result = false
  40. proc searchLocalSym(c: var NavContext; s: PackedSym; info: PackedLineInfo): bool =
  41. result = s.name != LitId(0) and
  42. isTracked(c.g.packed[c.thisModule].fromDisk.man, info, c.trackPos, c.g.packed[c.thisModule].fromDisk.strings[s.name].len)
  43. proc searchForeignSym(c: var NavContext; s: ItemId; info: PackedLineInfo): bool =
  44. let name = c.g.packed[s.module].fromDisk.syms[s.item].name
  45. result = name != LitId(0) and
  46. isTracked(c.g.packed[c.thisModule].fromDisk.man, info, c.trackPos, c.g.packed[s.module].fromDisk.strings[name].len)
  47. const
  48. EmptyItemId = ItemId(module: -1'i32, item: -1'i32)
  49. proc search(c: var NavContext; tree: PackedTree): ItemId =
  50. # We use the linear representation here directly:
  51. for i in 0..<len(tree):
  52. let i = NodePos(i)
  53. case tree[i].kind
  54. of nkSym:
  55. let item = tree[i].soperand
  56. if searchLocalSym(c, c.g.packed[c.thisModule].fromDisk.syms[item], tree[i].info):
  57. return ItemId(module: c.thisModule, item: item)
  58. of nkModuleRef:
  59. let (currentFile, currentLine, currentCol) = c.g.packed[c.thisModule].fromDisk.man.unpack(tree[i].info)
  60. if currentLine == c.trackPos.line and currentFile == c.trackPos.file:
  61. let (n1, n2) = sons2(tree, i)
  62. assert n1.kind == nkInt32Lit
  63. assert n2.kind == nkInt32Lit
  64. let pId = PackedItemId(module: n1.litId, item: tree[n2].soperand)
  65. let itemId = translateId(pId, c.g.packed, c.thisModule, c.g.config)
  66. if searchForeignSym(c, itemId, tree[i].info):
  67. return itemId
  68. else: discard
  69. return EmptyItemId
  70. proc isDecl(tree: PackedTree; n: NodePos): bool =
  71. # XXX This is not correct yet.
  72. const declarativeNodes = procDefs + {nkMacroDef, nkTemplateDef,
  73. nkLetSection, nkVarSection, nkUsingStmt, nkConstSection, nkTypeSection,
  74. nkIdentDefs, nkEnumTy, nkVarTuple}
  75. result = n.int >= 0 and tree[n].kind in declarativeNodes
  76. proc usage(c: var NavContext; info: PackedLineInfo; isDecl: bool) =
  77. let (fileId, line, col) = unpack(c.g.packed[c.thisModule].fromDisk.man, info)
  78. var m = ""
  79. var file = c.g.packed[c.thisModule].fromDisk.strings[fileId]
  80. if c.outputSep == ' ':
  81. file = os.extractFilename file
  82. toLocation(m, file, line, col + ColOffset)
  83. if not c.alreadyEmitted.containsOrIncl(m):
  84. msgWriteln c.g.config, (if isDecl: "def" else: "usage") & c.outputSep & m
  85. proc list(c: var NavContext; tree: PackedTree; sym: ItemId) =
  86. for i in 0..<len(tree):
  87. let i = NodePos(i)
  88. case tree[i].kind
  89. of nkSym:
  90. let item = tree[i].soperand
  91. if sym.item == item and sym.module == c.thisModule:
  92. usage(c, tree[i].info, isDecl(tree, parent(i)))
  93. of nkModuleRef:
  94. let (n1, n2) = sons2(tree, i)
  95. assert n1.kind == nkNone
  96. assert n2.kind == nkNone
  97. let pId = PackedItemId(module: n1.litId, item: tree[n2].soperand)
  98. let itemId = translateId(pId, c.g.packed, c.thisModule, c.g.config)
  99. if itemId.item == sym.item and sym.module == itemId.module:
  100. usage(c, tree[i].info, isDecl(tree, parent(i)))
  101. else: discard
  102. proc searchForIncludeFile(g: ModuleGraph; fullPath: string): int =
  103. for i in 0..<len(g.packed):
  104. for k in 1..high(g.packed[i].fromDisk.includes):
  105. # we start from 1 because the first "include" file is
  106. # the module's filename.
  107. if os.cmpPaths(g.packed[i].fromDisk.strings[g.packed[i].fromDisk.includes[k][0]], fullPath) == 0:
  108. return i
  109. return -1
  110. proc nav(g: ModuleGraph) =
  111. # translate the track position to a packed position:
  112. let unpacked = g.config.m.trackPos
  113. var mid = unpacked.fileIndex.int
  114. let fullPath = toFullPath(g.config, unpacked.fileIndex)
  115. if g.packed[mid].status == undefined:
  116. # check if 'mid' is an include file of some other module:
  117. mid = searchForIncludeFile(g, fullPath)
  118. if mid < 0:
  119. localError(g.config, unpacked, "unknown file name: " & fullPath)
  120. return
  121. let fileId = g.packed[mid].fromDisk.strings.getKeyId(fullPath)
  122. if fileId == LitId(0):
  123. internalError(g.config, unpacked, "cannot find a valid file ID")
  124. return
  125. var c = NavContext(
  126. g: g,
  127. thisModule: int32 mid,
  128. trackPos: UnpackedLineInfo(line: unpacked.line.int, col: unpacked.col.int, file: fileId),
  129. outputSep: if isDefined(g.config, "nimIcNavigatorTests"): ' ' else: '\t'
  130. )
  131. var symId = search(c, g.packed[mid].fromDisk.topLevel)
  132. if symId == EmptyItemId:
  133. symId = search(c, g.packed[mid].fromDisk.bodies)
  134. if symId == EmptyItemId:
  135. localError(g.config, unpacked, "no symbol at this position")
  136. return
  137. for i in 0..<len(g.packed):
  138. # case statement here to enforce exhaustive checks.
  139. case g.packed[i].status
  140. of undefined:
  141. discard "nothing to do"
  142. of loading:
  143. assert false, "cannot check integrity: Module still loading"
  144. of stored, storing, outdated, loaded:
  145. c.thisModule = int32 i
  146. list(c, g.packed[i].fromDisk.topLevel, symId)
  147. list(c, g.packed[i].fromDisk.bodies, symId)
  148. proc navDefinition*(g: ModuleGraph) = nav(g)
  149. proc navUsages*(g: ModuleGraph) = nav(g)
  150. proc navDefusages*(g: ModuleGraph) = nav(g)
  151. proc writeRodFiles*(g: ModuleGraph) =
  152. for i in 0..<len(g.packed):
  153. case g.packed[i].status
  154. of undefined, loading, stored, loaded:
  155. discard "nothing to do"
  156. of storing, outdated:
  157. closeRodFile(g, g.packed[i].module)