nimsuggest.nim 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2017 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. import compiler/renderer
  10. import strformat
  11. import algorithm
  12. import tables
  13. import std/sha1
  14. import times
  15. ## Nimsuggest is a tool that helps to give editors IDE like capabilities.
  16. when not defined(nimcore):
  17. {.error: "nimcore MUST be defined for Nim's core tooling".}
  18. import strutils, os, parseopt, parseutils, sequtils, net, rdstdin, sexp
  19. # Do NOT import suggest. It will lead to weird bugs with
  20. # suggestionResultHook, because suggest.nim is included by sigmatch.
  21. # So we import that one instead.
  22. import compiler / [options, commands, modules,
  23. passes, passaux, msgs,
  24. sigmatch, ast,
  25. idents, modulegraphs, prefixmatches, lineinfos, cmdlinehelper,
  26. pathutils, condsyms, syntaxes]
  27. when defined(nimPreviewSlimSystem):
  28. import std/typedthreads
  29. when defined(windows):
  30. import winlean
  31. else:
  32. import posix
  33. const DummyEof = "!EOF!"
  34. const Usage = """
  35. Nimsuggest - Tool to give every editor IDE like capabilities for Nim
  36. Usage:
  37. nimsuggest [options] projectfile.nim
  38. Options:
  39. --autobind automatically binds into a free port
  40. --port:PORT port, by default 6000
  41. --address:HOST binds to that address, by default ""
  42. --stdin read commands from stdin and write results to
  43. stdout instead of using sockets
  44. --epc use emacs epc mode
  45. --debug enable debug output
  46. --log enable verbose logging to nimsuggest.log file
  47. --v1 use version 1 of the protocol; for backwards compatibility
  48. --v2 use version 2(default) of the protocol
  49. --v3 use version 3 of the protocol
  50. --refresh perform automatic refreshes to keep the analysis precise
  51. --maxresults:N limit the number of suggestions to N
  52. --tester implies --stdin and outputs a line
  53. '""" & DummyEof & """' for the tester
  54. --find attempts to find the project file of the current project
  55. The server then listens to the connection and takes line-based commands.
  56. If --autobind is used, the binded port number will be printed to stdout.
  57. In addition, all command line options of Nim that do not affect code generation
  58. are supported.
  59. """
  60. type
  61. Mode = enum mstdin, mtcp, mepc, mcmdsug, mcmdcon
  62. CachedMsg = object
  63. info: TLineInfo
  64. msg: string
  65. sev: Severity
  66. CachedMsgs = seq[CachedMsg]
  67. var
  68. gPort = 6000.Port
  69. gAddress = ""
  70. gMode: Mode
  71. gEmitEof: bool # whether we write '!EOF!' dummy lines
  72. gLogging = defined(logging)
  73. gRefresh: bool
  74. gAutoBind = false
  75. requests: Channel[string]
  76. results: Channel[Suggest]
  77. proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile, line, col: int; tag: string,
  78. graph: ModuleGraph);
  79. proc writelnToChannel(line: string) =
  80. results.send(Suggest(section: ideMsg, doc: line))
  81. proc sugResultHook(s: Suggest) =
  82. results.send(s)
  83. proc errorHook(conf: ConfigRef; info: TLineInfo; msg: string; sev: Severity) =
  84. results.send(Suggest(section: ideChk, filePath: toFullPath(conf, info),
  85. line: toLinenumber(info), column: toColumn(info), doc: msg,
  86. forth: $sev))
  87. proc myLog(s: string) =
  88. if gLogging: log(s)
  89. const
  90. seps = {':', ';', ' ', '\t'}
  91. Help = "usage: sug|con|def|use|dus|chk|mod|highlight|outline|known|project file.nim[;dirtyfile.nim]:line:col\n" &
  92. "type 'quit' to quit\n" &
  93. "type 'debug' to toggle debug mode on/off\n" &
  94. "type 'terse' to toggle terse mode on/off"
  95. proc parseQuoted(cmd: string; outp: var string; start: int): int =
  96. var i = start
  97. i += skipWhitespace(cmd, i)
  98. if i < cmd.len and cmd[i] == '"':
  99. i += parseUntil(cmd, outp, '"', i+1)+2
  100. else:
  101. i += parseUntil(cmd, outp, seps, i)
  102. result = i
  103. proc sexp(s: IdeCmd|TSymKind|PrefixMatch): SexpNode = sexp($s)
  104. proc sexp(s: Suggest): SexpNode =
  105. # If you change the order here, make sure to change it over in
  106. # nim-mode.el too.
  107. let qp = if s.qualifiedPath.len == 0: @[] else: s.qualifiedPath
  108. result = convertSexp([
  109. s.section,
  110. TSymKind s.symkind,
  111. qp.map(newSString),
  112. s.filePath,
  113. s.forth,
  114. s.line,
  115. s.column,
  116. s.doc,
  117. s.quality
  118. ])
  119. if s.section == ideSug:
  120. result.add convertSexp(s.prefix)
  121. if s.section in {ideOutline, ideExpand} and s.version == 3:
  122. result.add convertSexp(s.endLine.int)
  123. result.add convertSexp(s.endCol)
  124. proc sexp(s: seq[Suggest]): SexpNode =
  125. result = newSList()
  126. for sug in s:
  127. result.add(sexp(sug))
  128. proc listEpc(): SexpNode =
  129. # This function is called from Emacs to show available options.
  130. let
  131. argspecs = sexp("file line column dirtyfile".split(" ").map(newSSymbol))
  132. docstring = sexp("line starts at 1, column at 0, dirtyfile is optional")
  133. result = newSList()
  134. for command in ["sug", "con", "def", "use", "dus", "chk", "mod", "globalSymbols", "recompile", "saved", "chkFile", "declaration"]:
  135. let
  136. cmd = sexp(command)
  137. methodDesc = newSList()
  138. methodDesc.add(cmd)
  139. methodDesc.add(argspecs)
  140. methodDesc.add(docstring)
  141. result.add(methodDesc)
  142. proc findNode(n: PNode; trackPos: TLineInfo): PSym =
  143. #echo "checking node ", n.info
  144. if n.kind == nkSym:
  145. if isTracked(n.info, trackPos, n.sym.name.s.len): return n.sym
  146. else:
  147. for i in 0 ..< safeLen(n):
  148. let res = findNode(n[i], trackPos)
  149. if res != nil: return res
  150. proc symFromInfo(graph: ModuleGraph; trackPos: TLineInfo): PSym =
  151. let m = graph.getModule(trackPos.fileIndex)
  152. if m != nil and m.ast != nil:
  153. result = findNode(m.ast, trackPos)
  154. template benchmark(benchmarkName: untyped, code: untyped) =
  155. block:
  156. myLog "Started [" & benchmarkName & "]..."
  157. let t0 = epochTime()
  158. code
  159. let elapsed = epochTime() - t0
  160. let elapsedStr = elapsed.formatFloat(format = ffDecimal, precision = 3)
  161. myLog "CPU Time [" & benchmarkName & "] " & elapsedStr & "s"
  162. proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, tag: string,
  163. graph: ModuleGraph) =
  164. let conf = graph.config
  165. if conf.suggestVersion == 3:
  166. let command = fmt "cmd = {cmd} {file}:{line}:{col}"
  167. benchmark command:
  168. executeNoHooksV3(cmd, file, dirtyfile, line, col, tag, graph)
  169. return
  170. myLog("cmd: " & $cmd & ", file: " & file.string &
  171. ", dirtyFile: " & dirtyfile.string &
  172. "[" & $line & ":" & $col & "]")
  173. conf.ideCmd = cmd
  174. if cmd == ideUse and conf.suggestVersion != 0:
  175. graph.resetAllModules()
  176. var isKnownFile = true
  177. let dirtyIdx = fileInfoIdx(conf, file, isKnownFile)
  178. if not dirtyfile.isEmpty: msgs.setDirtyFile(conf, dirtyIdx, dirtyfile)
  179. else: msgs.setDirtyFile(conf, dirtyIdx, AbsoluteFile"")
  180. conf.m.trackPos = newLineInfo(dirtyIdx, line, col)
  181. conf.m.trackPosAttached = false
  182. conf.errorCounter = 0
  183. if conf.suggestVersion == 1:
  184. graph.usageSym = nil
  185. if not isKnownFile:
  186. graph.compileProject(dirtyIdx)
  187. if conf.suggestVersion == 0 and conf.ideCmd in {ideUse, ideDus} and
  188. dirtyfile.isEmpty:
  189. discard "no need to recompile anything"
  190. else:
  191. let modIdx = graph.parentModule(dirtyIdx)
  192. graph.markDirty dirtyIdx
  193. graph.markClientsDirty dirtyIdx
  194. if conf.ideCmd != ideMod:
  195. if isKnownFile:
  196. graph.compileProject(modIdx)
  197. if conf.ideCmd in {ideUse, ideDus}:
  198. let u = if conf.suggestVersion != 1: graph.symFromInfo(conf.m.trackPos) else: graph.usageSym
  199. if u != nil:
  200. listUsages(graph, u)
  201. else:
  202. localError(conf, conf.m.trackPos, "found no symbol at this position " & (conf $ conf.m.trackPos))
  203. proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, graph: ModuleGraph) =
  204. executeNoHooks(cmd, file, dirtyfile, line, col, "", graph)
  205. proc execute(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; tag: string,
  206. graph: ModuleGraph) =
  207. if cmd == ideChk:
  208. graph.config.structuredErrorHook = errorHook
  209. graph.config.writelnHook = myLog
  210. else:
  211. graph.config.structuredErrorHook = nil
  212. graph.config.writelnHook = myLog
  213. executeNoHooks(cmd, file, dirtyfile, line, col, tag, graph)
  214. proc executeEpc(cmd: IdeCmd, args: SexpNode;
  215. graph: ModuleGraph) =
  216. let
  217. file = AbsoluteFile args[0].getStr
  218. line = args[1].getNum
  219. column = args[2].getNum
  220. var dirtyfile = AbsoluteFile""
  221. if len(args) > 3:
  222. dirtyfile = AbsoluteFile args[3].getStr("")
  223. execute(cmd, file, dirtyfile, int(line), int(column), args[3].getStr, graph)
  224. proc returnEpc(socket: Socket, uid: BiggestInt, s: SexpNode|string,
  225. returnSymbol = "return") =
  226. let response = $convertSexp([newSSymbol(returnSymbol), uid, s])
  227. socket.send(toHex(len(response), 6))
  228. socket.send(response)
  229. template checkSanity(client, sizeHex, size, messageBuffer: typed) =
  230. if client.recv(sizeHex, 6) != 6:
  231. raise newException(ValueError, "didn't get all the hexbytes")
  232. if parseHex(sizeHex, size) == 0:
  233. raise newException(ValueError, "invalid size hex: " & $sizeHex)
  234. if client.recv(messageBuffer, size) != size:
  235. raise newException(ValueError, "didn't get all the bytes")
  236. proc toStdout() {.gcsafe.} =
  237. while true:
  238. let res = results.recv()
  239. case res.section
  240. of ideNone: break
  241. of ideMsg: echo res.doc
  242. of ideKnown: echo res.quality == 1
  243. of ideProject: echo res.filePath
  244. else: echo res
  245. proc toSocket(stdoutSocket: Socket) {.gcsafe.} =
  246. while true:
  247. let res = results.recv()
  248. case res.section
  249. of ideNone: break
  250. of ideMsg: stdoutSocket.send(res.doc & "\c\L")
  251. of ideKnown: stdoutSocket.send($(res.quality == 1) & "\c\L")
  252. of ideProject: stdoutSocket.send(res.filePath & "\c\L")
  253. else: stdoutSocket.send($res & "\c\L")
  254. proc toEpc(client: Socket; uid: BiggestInt) {.gcsafe.} =
  255. var list = newSList()
  256. while true:
  257. let res = results.recv()
  258. case res.section
  259. of ideNone: break
  260. of ideMsg:
  261. list.add sexp(res.doc)
  262. of ideKnown:
  263. list.add sexp(res.quality == 1)
  264. of ideProject:
  265. list.add sexp(res.filePath)
  266. else:
  267. list.add sexp(res)
  268. returnEpc(client, uid, list)
  269. template setVerbosity(level: typed) =
  270. gVerbosity = level
  271. conf.notes = NotesVerbosity[gVerbosity]
  272. proc connectToNextFreePort(server: Socket, host: string): Port =
  273. server.bindAddr(Port(0), host)
  274. let (_, port) = server.getLocalAddr
  275. result = port
  276. type
  277. ThreadParams = tuple[port: Port; address: string]
  278. proc replStdinSingleCmd(line: string) =
  279. requests.send line
  280. toStdout()
  281. echo ""
  282. flushFile(stdout)
  283. proc replStdin(x: ThreadParams) {.thread.} =
  284. if gEmitEof:
  285. echo DummyEof
  286. while true:
  287. let line = readLine(stdin)
  288. requests.send line
  289. if line == "quit": break
  290. toStdout()
  291. echo DummyEof
  292. flushFile(stdout)
  293. else:
  294. echo Help
  295. var line = ""
  296. while readLineFromStdin("> ", line):
  297. replStdinSingleCmd(line)
  298. requests.send "quit"
  299. proc replCmdline(x: ThreadParams) {.thread.} =
  300. replStdinSingleCmd(x.address)
  301. requests.send "quit"
  302. proc replTcp(x: ThreadParams) {.thread.} =
  303. var server = newSocket()
  304. if gAutoBind:
  305. let port = server.connectToNextFreePort(x.address)
  306. server.listen()
  307. echo port
  308. stdout.flushFile()
  309. else:
  310. server.bindAddr(x.port, x.address)
  311. server.listen()
  312. var inp = ""
  313. var stdoutSocket: Socket
  314. while true:
  315. accept(server, stdoutSocket)
  316. stdoutSocket.readLine(inp)
  317. requests.send inp
  318. toSocket(stdoutSocket)
  319. stdoutSocket.send("\c\L")
  320. stdoutSocket.close()
  321. proc argsToStr(x: SexpNode): string =
  322. if x.kind != SList: return x.getStr
  323. doAssert x.kind == SList
  324. doAssert x.len >= 4
  325. let file = x[0].getStr
  326. let line = x[1].getNum
  327. let col = x[2].getNum
  328. let dirty = x[3].getStr
  329. result = x[0].getStr.escape
  330. if dirty.len > 0:
  331. result.add ';'
  332. result.add dirty.escape
  333. result.add ':'
  334. result.addInt line
  335. result.add ':'
  336. result.addInt col
  337. proc replEpc(x: ThreadParams) {.thread.} =
  338. var server = newSocket()
  339. let port = connectToNextFreePort(server, "localhost")
  340. server.listen()
  341. echo port
  342. stdout.flushFile()
  343. var client: Socket
  344. # Wait for connection
  345. accept(server, client)
  346. while true:
  347. var
  348. sizeHex = ""
  349. size = 0
  350. messageBuffer = ""
  351. checkSanity(client, sizeHex, size, messageBuffer)
  352. let
  353. message = parseSexp($messageBuffer)
  354. epcApi = message[0].getSymbol
  355. case epcApi
  356. of "call":
  357. let
  358. uid = message[1].getNum
  359. cmd = message[2].getSymbol
  360. args = message[3]
  361. when false:
  362. x.ideCmd[] = parseIdeCmd(message[2].getSymbol)
  363. case x.ideCmd[]
  364. of ideSug, ideCon, ideDef, ideUse, ideDus, ideOutline, ideHighlight:
  365. setVerbosity(0)
  366. else: discard
  367. let fullCmd = cmd & " " & args.argsToStr
  368. myLog "MSG CMD: " & fullCmd
  369. requests.send(fullCmd)
  370. toEpc(client, uid)
  371. of "methods":
  372. returnEpc(client, message[1].getNum, listEpc())
  373. of "epc-error":
  374. # an unhandled exception forces down the whole process anyway, so we
  375. # use 'quit' here instead of 'raise'
  376. quit("received epc error: " & $messageBuffer)
  377. else:
  378. let errMessage = case epcApi
  379. of "return", "return-error":
  380. "no return expected"
  381. else:
  382. "unexpected call: " & epcApi
  383. quit errMessage
  384. proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) =
  385. let conf = graph.config
  386. template sentinel() =
  387. # send sentinel for the input reading thread:
  388. results.send(Suggest(section: ideNone))
  389. template toggle(sw) =
  390. if sw in conf.globalOptions:
  391. excl(conf.globalOptions, sw)
  392. else:
  393. incl(conf.globalOptions, sw)
  394. sentinel()
  395. return
  396. template err() =
  397. echo Help
  398. sentinel()
  399. return
  400. var opc = ""
  401. var i = parseIdent(cmd, opc, 0)
  402. case opc.normalize
  403. of "sug": conf.ideCmd = ideSug
  404. of "con": conf.ideCmd = ideCon
  405. of "def": conf.ideCmd = ideDef
  406. of "use": conf.ideCmd = ideUse
  407. of "dus": conf.ideCmd = ideDus
  408. of "mod": conf.ideCmd = ideMod
  409. of "chk": conf.ideCmd = ideChk
  410. of "highlight": conf.ideCmd = ideHighlight
  411. of "outline": conf.ideCmd = ideOutline
  412. of "quit":
  413. sentinel()
  414. quit()
  415. of "debug": toggle optIdeDebug
  416. of "terse": toggle optIdeTerse
  417. of "known": conf.ideCmd = ideKnown
  418. of "project": conf.ideCmd = ideProject
  419. of "changed": conf.ideCmd = ideChanged
  420. of "globalsymbols": conf.ideCmd = ideGlobalSymbols
  421. of "declaration": conf.ideCmd = ideDeclaration
  422. of "expand": conf.ideCmd = ideExpand
  423. of "chkfile": conf.ideCmd = ideChkFile
  424. of "recompile": conf.ideCmd = ideRecompile
  425. of "type": conf.ideCmd = ideType
  426. else: err()
  427. var dirtyfile = ""
  428. var orig = ""
  429. i += skipWhitespace(cmd, i)
  430. if i < cmd.len and cmd[i] in {'0'..'9'}:
  431. orig = string conf.projectFull
  432. else:
  433. i = parseQuoted(cmd, orig, i)
  434. if i < cmd.len and cmd[i] == ';':
  435. i = parseQuoted(cmd, dirtyfile, i+1)
  436. i += skipWhile(cmd, seps, i)
  437. var line = 0
  438. var col = -1
  439. i += parseInt(cmd, line, i)
  440. i += skipWhile(cmd, seps, i)
  441. i += parseInt(cmd, col, i)
  442. let tag = substr(cmd, i)
  443. if conf.ideCmd == ideKnown:
  444. results.send(Suggest(section: ideKnown, quality: ord(fileInfoKnown(conf, AbsoluteFile orig))))
  445. elif conf.ideCmd == ideProject:
  446. results.send(Suggest(section: ideProject, filePath: string conf.projectFull))
  447. else:
  448. if conf.ideCmd == ideChk:
  449. for cm in cachedMsgs: errorHook(conf, cm.info, cm.msg, cm.sev)
  450. execute(conf.ideCmd, AbsoluteFile orig, AbsoluteFile dirtyfile, line, col, tag, graph)
  451. sentinel()
  452. proc recompileFullProject(graph: ModuleGraph) =
  453. benchmark "Recompilation(clean)":
  454. graph.resetForBackend()
  455. graph.resetSystemArtifacts()
  456. graph.vm = nil
  457. graph.resetAllModules()
  458. GC_fullCollect()
  459. graph.compileProject()
  460. proc mainThread(graph: ModuleGraph) =
  461. let conf = graph.config
  462. myLog "searchPaths: "
  463. for it in conf.searchPaths:
  464. myLog(" " & it.string)
  465. proc wrHook(line: string) {.closure.} =
  466. if gMode == mepc:
  467. if gLogging: log(line)
  468. else:
  469. writelnToChannel(line)
  470. conf.writelnHook = wrHook
  471. conf.suggestionResultHook = sugResultHook
  472. graph.doStopCompile = proc (): bool = requests.peek() > 0
  473. var idle = 0
  474. var cachedMsgs: CachedMsgs = @[]
  475. while true:
  476. let (hasData, req) = requests.tryRecv()
  477. if hasData:
  478. conf.writelnHook = wrHook
  479. conf.suggestionResultHook = sugResultHook
  480. execCmd(req, graph, cachedMsgs)
  481. idle = 0
  482. else:
  483. os.sleep 250
  484. idle += 1
  485. if idle == 20 and gRefresh and conf.suggestVersion != 3:
  486. # we use some nimsuggest activity to enable a lazy recompile:
  487. conf.ideCmd = ideChk
  488. conf.writelnHook = proc (s: string) = discard
  489. cachedMsgs.setLen 0
  490. conf.structuredErrorHook = proc (conf: ConfigRef; info: TLineInfo; msg: string; sev: Severity) =
  491. cachedMsgs.add(CachedMsg(info: info, msg: msg, sev: sev))
  492. conf.suggestionResultHook = proc (s: Suggest) = discard
  493. recompileFullProject(graph)
  494. var
  495. inputThread: Thread[ThreadParams]
  496. proc mainCommand(graph: ModuleGraph) =
  497. let conf = graph.config
  498. clearPasses(graph)
  499. registerPass graph, verbosePass
  500. registerPass graph, semPass
  501. conf.setCmd cmdIdeTools
  502. defineSymbol(conf.symbols, $conf.backend)
  503. wantMainModule(conf)
  504. if not fileExists(conf.projectFull):
  505. quit "cannot find file: " & conf.projectFull.string
  506. add(conf.searchPaths, conf.libpath)
  507. conf.setErrorMaxHighMaybe # honor --errorMax even if it may not make sense here
  508. # do not print errors, but log them
  509. conf.writelnHook = proc (msg: string) = discard
  510. if graph.config.suggestVersion == 3:
  511. graph.config.structuredErrorHook = proc (conf: ConfigRef; info: TLineInfo; msg: string; sev: Severity) =
  512. let suggest = Suggest(section: ideChk, filePath: toFullPath(conf, info),
  513. line: toLinenumber(info), column: toColumn(info), doc: msg, forth: $sev)
  514. graph.suggestErrors.mgetOrPut(info.fileIndex, @[]).add suggest
  515. # compile the project before showing any input so that we already
  516. # can answer questions right away:
  517. benchmark "Initial compilation":
  518. compileProject(graph)
  519. open(requests)
  520. open(results)
  521. case gMode
  522. of mstdin: createThread(inputThread, replStdin, (gPort, gAddress))
  523. of mtcp: createThread(inputThread, replTcp, (gPort, gAddress))
  524. of mepc: createThread(inputThread, replEpc, (gPort, gAddress))
  525. of mcmdsug: createThread(inputThread, replCmdline,
  526. (gPort, "sug \"" & conf.projectFull.string & "\":" & gAddress))
  527. of mcmdcon: createThread(inputThread, replCmdline,
  528. (gPort, "con \"" & conf.projectFull.string & "\":" & gAddress))
  529. mainThread(graph)
  530. joinThread(inputThread)
  531. close(requests)
  532. close(results)
  533. proc processCmdLine*(pass: TCmdLinePass, cmd: string; conf: ConfigRef) =
  534. var p = parseopt.initOptParser(cmd)
  535. var findProject = false
  536. while true:
  537. parseopt.next(p)
  538. case p.kind
  539. of cmdEnd: break
  540. of cmdLongOption, cmdShortOption:
  541. case p.key.normalize
  542. of "help", "h":
  543. stdout.writeLine(Usage)
  544. quit()
  545. of "autobind":
  546. gMode = mtcp
  547. gAutoBind = true
  548. of "port":
  549. gPort = parseInt(p.val).Port
  550. gMode = mtcp
  551. of "address":
  552. gAddress = p.val
  553. gMode = mtcp
  554. of "stdin": gMode = mstdin
  555. of "cmdsug":
  556. gMode = mcmdsug
  557. gAddress = p.val
  558. incl(conf.globalOptions, optIdeDebug)
  559. of "cmdcon":
  560. gMode = mcmdcon
  561. gAddress = p.val
  562. incl(conf.globalOptions, optIdeDebug)
  563. of "epc":
  564. gMode = mepc
  565. conf.verbosity = 0 # Port number gotta be first.
  566. of "debug": incl(conf.globalOptions, optIdeDebug)
  567. of "v1": conf.suggestVersion = 1
  568. of "v2": conf.suggestVersion = 0
  569. of "v3": conf.suggestVersion = 3
  570. of "tester":
  571. gMode = mstdin
  572. gEmitEof = true
  573. gRefresh = false
  574. of "log": gLogging = true
  575. of "refresh":
  576. if p.val.len > 0:
  577. gRefresh = parseBool(p.val)
  578. else:
  579. gRefresh = true
  580. of "maxresults":
  581. conf.suggestMaxResults = parseInt(p.val)
  582. of "find":
  583. findProject = true
  584. else: processSwitch(pass, p, conf)
  585. of cmdArgument:
  586. let a = unixToNativePath(p.key)
  587. if dirExists(a) and not fileExists(a.addFileExt("nim")):
  588. conf.projectName = findProjectNimFile(conf, a)
  589. # don't make it worse, report the error the old way:
  590. if conf.projectName.len == 0: conf.projectName = a
  591. else:
  592. if findProject:
  593. conf.projectName = findProjectNimFile(conf, a.parentDir())
  594. if conf.projectName.len == 0:
  595. conf.projectName = a
  596. else:
  597. conf.projectName = a
  598. # if processArgument(pass, p, argsCount): break
  599. proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
  600. let self = NimProg(
  601. suggestMode: true,
  602. processCmdLine: processCmdLine
  603. )
  604. self.initDefinesProg(conf, "nimsuggest")
  605. if paramCount() == 0:
  606. stdout.writeLine(Usage)
  607. return
  608. self.processCmdLineAndProjectPath(conf)
  609. if gMode != mstdin:
  610. conf.writelnHook = proc (msg: string) = discard
  611. # Find Nim's prefix dir.
  612. let binaryPath = findExe("nim")
  613. if binaryPath == "":
  614. raise newException(IOError,
  615. "Cannot find Nim standard library: Nim compiler not in PATH")
  616. conf.prefixDir = AbsoluteDir binaryPath.splitPath().head.parentDir()
  617. if not dirExists(conf.prefixDir / RelativeDir"lib"):
  618. conf.prefixDir = AbsoluteDir""
  619. #msgs.writelnHook = proc (line: string) = log(line)
  620. myLog("START " & conf.projectFull.string)
  621. var graph = newModuleGraph(cache, conf)
  622. if self.loadConfigsAndProcessCmdLine(cache, conf, graph):
  623. mainCommand(graph)
  624. # v3 start
  625. proc recompilePartially(graph: ModuleGraph, projectFileIdx = InvalidFileIdx) =
  626. if projectFileIdx == InvalidFileIdx:
  627. myLog "Recompiling partially from root"
  628. else:
  629. myLog fmt "Recompiling partially starting from {graph.getModule(projectFileIdx)}"
  630. # inst caches are breaking incremental compilation when the cache caches stuff
  631. # from dirty buffer
  632. # TODO: investigate more efficient way to achieve the same
  633. # graph.typeInstCache.clear()
  634. # graph.procInstCache.clear()
  635. GC_fullCollect()
  636. try:
  637. benchmark "Recompilation":
  638. graph.compileProject(projectFileIdx)
  639. except Exception as e:
  640. myLog fmt "Failed to recompile partially with the following error:\n {e.msg} \n\n {e.getStackTrace()}"
  641. try:
  642. graph.recompileFullProject()
  643. except Exception as e:
  644. myLog fmt "Failed clean recompilation:\n {e.msg} \n\n {e.getStackTrace()}"
  645. func deduplicateSymInfoPair[SymInfoPair](xs: seq[SymInfoPair]): seq[SymInfoPair] =
  646. # xs contains duplicate items and we want to filter them by range because the
  647. # sym may not match. This can happen when xs contains the same definition but
  648. # with different signature because suggestSym might be called multiple times
  649. # for the same symbol (e. g. including/excluding the pragma)
  650. result = @[]
  651. for itm in xs.reversed:
  652. var found = false
  653. for res in result:
  654. if res.info.exactEquals(itm.info):
  655. found = true
  656. break
  657. if not found:
  658. result.add(itm)
  659. result.reverse()
  660. proc findSymData(graph: ModuleGraph, trackPos: TLineInfo):
  661. ref SymInfoPair =
  662. for s in graph.fileSymbols(trackPos.fileIndex).deduplicateSymInfoPair:
  663. if isTracked(s.info, trackPos, s.sym.name.s.len):
  664. new(result)
  665. result[] = s
  666. break
  667. proc findSymData(graph: ModuleGraph, file: AbsoluteFile; line, col: int):
  668. ref SymInfoPair =
  669. let
  670. fileIdx = fileInfoIdx(graph.config, file)
  671. trackPos = newLineInfo(fileIdx, line, col)
  672. result = findSymData(graph, trackPos)
  673. proc markDirtyIfNeeded(graph: ModuleGraph, file: string, originalFileIdx: FileIndex) =
  674. let sha = $sha1.secureHashFile(file)
  675. if graph.config.m.fileInfos[originalFileIdx.int32].hash != sha or graph.config.ideCmd == ideSug:
  676. myLog fmt "{file} changed compared to last compilation"
  677. graph.markDirty originalFileIdx
  678. graph.markClientsDirty originalFileIdx
  679. else:
  680. myLog fmt "No changes in file {file} compared to last compilation"
  681. proc suggestResult(graph: ModuleGraph, sym: PSym, info: TLineInfo,
  682. defaultSection = ideNone, endLine: uint16 = 0, endCol = 0) =
  683. let section = if defaultSection != ideNone:
  684. defaultSection
  685. elif sym.info.exactEquals(info):
  686. ideDef
  687. else:
  688. ideUse
  689. let suggest = symToSuggest(graph, sym, isLocal=false, section,
  690. info, 100, PrefixMatch.None, false, 0,
  691. endLine = endLine, endCol = endCol)
  692. suggestResult(graph.config, suggest)
  693. const
  694. # kinds for ideOutline and ideGlobalSymbols
  695. searchableSymKinds = {skField, skEnumField, skIterator, skMethod, skFunc, skProc, skConverter, skTemplate}
  696. proc symbolEqual(left, right: PSym): bool =
  697. # More relaxed symbol comparison
  698. return left.info.exactEquals(right.info) and left.name == right.name
  699. proc findDef(n: PNode, line: uint16, col: int16): PNode =
  700. if n.kind in {nkProcDef, nkIteratorDef, nkTemplateDef, nkMethodDef, nkMacroDef}:
  701. if n.info.line == line:
  702. return n
  703. else:
  704. for i in 0 ..< safeLen(n):
  705. let res = findDef(n[i], line, col)
  706. if res != nil: return res
  707. proc findByTLineInfo(trackPos: TLineInfo, infoPairs: seq[SymInfoPair]):
  708. ref SymInfoPair =
  709. for s in infoPairs:
  710. if s.info.exactEquals trackPos:
  711. new(result)
  712. result[] = s
  713. break
  714. proc outlineNode(graph: ModuleGraph, n: PNode, endInfo: TLineInfo, infoPairs: seq[SymInfoPair]): bool =
  715. proc checkSymbol(sym: PSym, info: TLineInfo): bool =
  716. result = (sym.owner.kind in {skModule, skType} or sym.kind in {skProc, skMethod, skIterator, skTemplate, skType})
  717. if n.kind == nkSym and n.sym.checkSymbol(n.info):
  718. graph.suggestResult(n.sym, n.sym.info, ideOutline, endInfo.line, endInfo.col)
  719. return true
  720. elif n.kind == nkIdent:
  721. let symData = findByTLineInfo(n.info, infoPairs)
  722. if symData != nil and symData.sym.checkSymbol(symData.info):
  723. let sym = symData.sym
  724. graph.suggestResult(sym, sym.info, ideOutline, endInfo.line, endInfo.col)
  725. return true
  726. proc handleIdentOrSym(graph: ModuleGraph, n: PNode, endInfo: TLineInfo, infoPairs: seq[SymInfoPair]): bool =
  727. for child in n:
  728. if child.kind in {nkIdent, nkSym}:
  729. if graph.outlineNode(child, endInfo, infoPairs):
  730. return true
  731. elif child.kind == nkPostfix:
  732. if graph.handleIdentOrSym(child, endInfo, infoPairs):
  733. return true
  734. proc iterateOutlineNodes(graph: ModuleGraph, n: PNode, infoPairs: seq[SymInfoPair]) =
  735. var matched = true
  736. if n.kind == nkIdent:
  737. let symData = findByTLineInfo(n.info, infoPairs)
  738. if symData != nil and symData.sym.kind == skEnumField and symData.info.exactEquals(symData.sym.info):
  739. let sym = symData.sym
  740. graph.suggestResult(sym, sym.info, ideOutline, n.endInfo.line, n.endInfo.col)
  741. elif (n.kind in {nkFuncDef, nkProcDef, nkTypeDef, nkMacroDef, nkTemplateDef, nkConverterDef, nkEnumFieldDef, nkConstDef}):
  742. matched = handleIdentOrSym(graph, n, n.endInfo, infoPairs)
  743. else:
  744. matched = false
  745. if n.kind != nkFormalParams:
  746. for child in n:
  747. graph.iterateOutlineNodes(child, infoPairs)
  748. proc calculateExpandRange(n: PNode, info: TLineInfo): TLineInfo =
  749. if ((n.kind in {nkFuncDef, nkProcDef, nkIteratorDef, nkTemplateDef, nkMethodDef, nkConverterDef} and
  750. n.info.exactEquals(info)) or
  751. (n.kind in {nkCall, nkCommand} and n[0].info.exactEquals(info))):
  752. result = n.endInfo
  753. else:
  754. for child in n:
  755. result = child.calculateExpandRange(info)
  756. if result != unknownLineInfo:
  757. return result
  758. result = unknownLineInfo
  759. proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile, line, col: int; tag: string,
  760. graph: ModuleGraph) =
  761. let conf = graph.config
  762. conf.writelnHook = proc (s: string) = discard
  763. conf.structuredErrorHook = proc (conf: ConfigRef; info: TLineInfo;
  764. msg: string; sev: Severity) =
  765. let suggest = Suggest(section: ideChk, filePath: toFullPath(conf, info),
  766. line: toLinenumber(info), column: toColumn(info), doc: msg, forth: $sev)
  767. graph.suggestErrors.mgetOrPut(info.fileIndex, @[]).add suggest
  768. conf.ideCmd = cmd
  769. myLog fmt "cmd: {cmd}, file: {file}[{line}:{col}], dirtyFile: {dirtyfile}, tag: {tag}"
  770. var fileIndex: FileIndex
  771. if not (cmd in {ideRecompile, ideGlobalSymbols}):
  772. if not fileInfoKnown(conf, file):
  773. myLog fmt "{file} is unknown, returning no results"
  774. return
  775. fileIndex = fileInfoIdx(conf, file)
  776. msgs.setDirtyFile(
  777. conf,
  778. fileIndex,
  779. if dirtyfile.isEmpty: AbsoluteFile"" else: dirtyfile)
  780. if not dirtyfile.isEmpty:
  781. graph.markDirtyIfNeeded(dirtyFile.string, fileInfoIdx(conf, file))
  782. # these commands require fully compiled project
  783. if cmd in {ideUse, ideDus, ideGlobalSymbols, ideChk} and graph.needsCompilation():
  784. graph.recompilePartially()
  785. # when doing incremental build for the project root we should make sure that
  786. # everything is unmarked as no longer beeing dirty in case there is no
  787. # longer reference to a particular module. E. g. A depends on B, B is marked
  788. # as dirty and A loses B import.
  789. graph.unmarkAllDirty()
  790. # these commands require partially compiled project
  791. elif cmd in {ideSug, ideOutline, ideHighlight, ideDef, ideChkFile, ideType, ideDeclaration, ideExpand} and
  792. (graph.needsCompilation(fileIndex) or cmd == ideSug):
  793. # for ideSug use v2 implementation
  794. if cmd == ideSug:
  795. conf.m.trackPos = newLineInfo(fileIndex, line, col)
  796. conf.m.trackPosAttached = false
  797. else:
  798. conf.m.trackPos = default(TLineInfo)
  799. graph.recompilePartially(fileIndex)
  800. case cmd
  801. of ideDef:
  802. let s = graph.findSymData(file, line, col)
  803. if not s.isNil:
  804. graph.suggestResult(s.sym, s.sym.info)
  805. of ideType:
  806. let s = graph.findSymData(file, line, col)
  807. if not s.isNil:
  808. let typeSym = s.sym.typ.sym
  809. if typeSym != nil:
  810. graph.suggestResult(typeSym, typeSym.info, ideType)
  811. elif s.sym.typ.len != 0:
  812. let genericType = s.sym.typ[0].sym
  813. graph.suggestResult(genericType, genericType.info, ideType)
  814. of ideUse, ideDus:
  815. let symbol = graph.findSymData(file, line, col)
  816. if not symbol.isNil:
  817. var res: seq[SymInfoPair] = @[]
  818. for s in graph.suggestSymbolsIter:
  819. if s.sym.symbolEqual(symbol.sym):
  820. res.add(s)
  821. for s in res.deduplicateSymInfoPair():
  822. graph.suggestResult(s.sym, s.info)
  823. of ideHighlight:
  824. let sym = graph.findSymData(file, line, col)
  825. if not sym.isNil:
  826. let usages = graph.fileSymbols(fileIndex).filterIt(it.sym == sym.sym)
  827. myLog fmt "Found {usages.len} usages in {file.string}"
  828. for s in usages:
  829. graph.suggestResult(s.sym, s.info)
  830. of ideRecompile:
  831. graph.recompileFullProject()
  832. of ideChanged:
  833. graph.markDirtyIfNeeded(file.string, fileIndex)
  834. of ideSug:
  835. # ideSug performs partial build of the file, thus mark it dirty for the
  836. # future calls.
  837. graph.markDirtyIfNeeded(file.string, fileIndex)
  838. of ideOutline:
  839. let n = parseFile(fileIndex, graph.cache, graph.config)
  840. graph.iterateOutlineNodes(n, graph.fileSymbols(fileIndex).deduplicateSymInfoPair)
  841. of ideChk:
  842. myLog fmt "Reporting errors for {graph.suggestErrors.len} file(s)"
  843. for sug in graph.suggestErrorsIter:
  844. suggestResult(graph.config, sug)
  845. of ideChkFile:
  846. let errors = graph.suggestErrors.getOrDefault(fileIndex, @[])
  847. myLog fmt "Reporting {errors.len} error(s) for {file.string}"
  848. for error in errors:
  849. suggestResult(graph.config, error)
  850. of ideGlobalSymbols:
  851. var
  852. counter = 0
  853. res: seq[SymInfoPair] = @[]
  854. for s in graph.suggestSymbolsIter:
  855. if (sfGlobal in s.sym.flags or s.sym.kind in searchableSymKinds) and
  856. s.sym.info == s.info:
  857. if contains(s.sym.name.s, file.string):
  858. inc counter
  859. res = res.filterIt(not it.info.exactEquals(s.info))
  860. res.add s
  861. # stop after first 1000 matches...
  862. if counter > 1000:
  863. break
  864. # ... then sort them by weight ...
  865. res.sort() do (left, right: SymInfoPair) -> int:
  866. let
  867. leftString = left.sym.name.s
  868. rightString = right.sym.name.s
  869. leftIndex = leftString.find(file.string)
  870. rightIndex = rightString.find(file.string)
  871. if leftIndex == rightIndex:
  872. result = cmp(toLowerAscii(leftString),
  873. toLowerAscii(rightString))
  874. else:
  875. result = cmp(leftIndex, rightIndex)
  876. # ... and send first 100 results
  877. if res.len > 0:
  878. for i in 0 .. min(100, res.len - 1):
  879. let s = res[i]
  880. graph.suggestResult(s.sym, s.info)
  881. of ideDeclaration:
  882. let s = graph.findSymData(file, line, col)
  883. if not s.isNil:
  884. # find first mention of the symbol in the file containing the definition.
  885. # It is either the definition or the declaration.
  886. var first: SymInfoPair
  887. for symbol in graph.fileSymbols(s.sym.info.fileIndex).deduplicateSymInfoPair:
  888. if s.sym.symbolEqual(symbol.sym):
  889. first = symbol
  890. break
  891. if s.info.exactEquals(first.info):
  892. # we are on declaration, go to definition
  893. graph.suggestResult(first.sym, first.sym.info, ideDeclaration)
  894. else:
  895. # we are on definition or usage, look for declaration
  896. graph.suggestResult(first.sym, first.info, ideDeclaration)
  897. of ideExpand:
  898. var level: int = high(int)
  899. let index = skipWhitespace(tag, 0);
  900. let trimmed = substr(tag, index)
  901. if not (trimmed == "" or trimmed == "all"):
  902. discard parseInt(trimmed, level, 0)
  903. conf.expandPosition = newLineInfo(fileIndex, line, col)
  904. conf.expandLevels = level
  905. conf.expandProgress = false
  906. conf.expandNodeResult = ""
  907. graph.markDirty fileIndex
  908. graph.markClientsDirty fileIndex
  909. graph.recompilePartially()
  910. var suggest = Suggest()
  911. suggest.section = ideExpand
  912. suggest.version = 3
  913. suggest.line = line
  914. suggest.column = col
  915. suggest.doc = graph.config.expandNodeResult
  916. if suggest.doc != "":
  917. let
  918. n = parseFile(fileIndex, graph.cache, graph.config)
  919. endInfo = n.calculateExpandRange(conf.expandPosition)
  920. suggest.endLine = endInfo.line
  921. suggest.endCol = endInfo.col
  922. suggestResult(graph.config, suggest)
  923. graph.markDirty fileIndex
  924. graph.markClientsDirty fileIndex
  925. else:
  926. myLog fmt "Discarding {cmd}"
  927. # v3 end
  928. when isMainModule:
  929. handleCmdLine(newIdentCache(), newConfigRef())
  930. else:
  931. export Suggest
  932. export IdeCmd
  933. export AbsoluteFile
  934. type NimSuggest* = ref object
  935. graph: ModuleGraph
  936. idle: int
  937. cachedMsgs: CachedMsgs
  938. proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest =
  939. var retval: ModuleGraph
  940. proc mockCommand(graph: ModuleGraph) =
  941. retval = graph
  942. let conf = graph.config
  943. conf.setCmd cmdIdeTools
  944. defineSymbol(conf.symbols, $conf.backend)
  945. clearPasses(graph)
  946. registerPass graph, verbosePass
  947. registerPass graph, semPass
  948. wantMainModule(conf)
  949. if not fileExists(conf.projectFull):
  950. quit "cannot find file: " & conf.projectFull.string
  951. add(conf.searchPaths, conf.libpath)
  952. conf.setErrorMaxHighMaybe
  953. # do not print errors, but log them
  954. conf.writelnHook = myLog
  955. conf.structuredErrorHook = nil
  956. # compile the project before showing any input so that we already
  957. # can answer questions right away:
  958. compileProject(graph)
  959. proc mockCmdLine(pass: TCmdLinePass, cmd: string; conf: ConfigRef) =
  960. conf.suggestVersion = 0
  961. let a = unixToNativePath(project)
  962. if dirExists(a) and not fileExists(a.addFileExt("nim")):
  963. conf.projectName = findProjectNimFile(conf, a)
  964. # don't make it worse, report the error the old way:
  965. if conf.projectName.len == 0: conf.projectName = a
  966. else:
  967. conf.projectName = a
  968. # if processArgument(pass, p, argsCount): break
  969. let
  970. cache = newIdentCache()
  971. conf = newConfigRef()
  972. self = NimProg(
  973. suggestMode: true,
  974. processCmdLine: mockCmdLine
  975. )
  976. self.initDefinesProg(conf, "nimsuggest")
  977. self.processCmdLineAndProjectPath(conf)
  978. if gMode != mstdin:
  979. conf.writelnHook = proc (msg: string) = discard
  980. # Find Nim's prefix dir.
  981. if nimPath == "":
  982. let binaryPath = findExe("nim")
  983. if binaryPath == "":
  984. raise newException(IOError,
  985. "Cannot find Nim standard library: Nim compiler not in PATH")
  986. conf.prefixDir = AbsoluteDir binaryPath.splitPath().head.parentDir()
  987. if not dirExists(conf.prefixDir / RelativeDir"lib"):
  988. conf.prefixDir = AbsoluteDir""
  989. else:
  990. conf.prefixDir = AbsoluteDir nimPath
  991. #msgs.writelnHook = proc (line: string) = log(line)
  992. myLog("START " & conf.projectFull.string)
  993. var graph = newModuleGraph(cache, conf)
  994. if self.loadConfigsAndProcessCmdLine(cache, conf, graph):
  995. mockCommand(graph)
  996. if gLogging:
  997. myLog("Search paths:")
  998. for it in conf.searchPaths:
  999. myLog(" " & it.string)
  1000. retval.doStopCompile = proc (): bool = false
  1001. return NimSuggest(graph: retval, idle: 0, cachedMsgs: @[])
  1002. proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int): seq[Suggest] =
  1003. var retval: seq[Suggest] = @[]
  1004. let conf = nimsuggest.graph.config
  1005. conf.ideCmd = cmd
  1006. conf.writelnHook = proc (line: string) =
  1007. retval.add(Suggest(section: ideMsg, doc: line))
  1008. conf.suggestionResultHook = proc (s: Suggest) =
  1009. retval.add(s)
  1010. conf.writelnHook = proc (s: string) =
  1011. stderr.write s & "\n"
  1012. if conf.ideCmd == ideKnown:
  1013. retval.add(Suggest(section: ideKnown, quality: ord(fileInfoKnown(conf, file))))
  1014. elif conf.ideCmd == ideProject:
  1015. retval.add(Suggest(section: ideProject, filePath: string conf.projectFull))
  1016. else:
  1017. if conf.ideCmd == ideChk:
  1018. for cm in nimsuggest.cachedMsgs: errorHook(conf, cm.info, cm.msg, cm.sev)
  1019. if conf.ideCmd == ideChk:
  1020. conf.structuredErrorHook = proc (conf: ConfigRef; info: TLineInfo; msg: string; sev: Severity) =
  1021. retval.add(Suggest(section: ideChk, filePath: toFullPath(conf, info),
  1022. line: toLinenumber(info), column: toColumn(info), doc: msg,
  1023. forth: $sev))
  1024. else:
  1025. conf.structuredErrorHook = nil
  1026. executeNoHooks(conf.ideCmd, file, dirtyfile, line, col, nimsuggest.graph)
  1027. return retval