nimsuggest.nim 38 KB

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