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 10## Nimsuggest is a tool that helps to give editors IDE like capabilities. 11 12when not defined(nimcore): 13 {.error: "nimcore MUST be defined for Nim's core tooling".} 14 15import strutils, os, parseopt, parseutils, sequtils, net, rdstdin, sexp 16# Do NOT import suggest. It will lead to weird bugs with 17# suggestionResultHook, because suggest.nim is included by sigmatch. 18# So we import that one instead. 19import compiler / [options, commands, modules, sem, 20 passes, passaux, msgs, 21 sigmatch, ast, 22 idents, modulegraphs, prefixmatches, lineinfos, cmdlinehelper, 23 pathutils] 24 25when defined(windows): 26 import winlean 27else: 28 import posix 29 30const DummyEof = "!EOF!" 31const Usage = """ 32Nimsuggest - Tool to give every editor IDE like capabilities for Nim 33Usage: 34 nimsuggest [options] projectfile.nim 35 36Options: 37 --autobind automatically binds into a free port 38 --port:PORT port, by default 6000 39 --address:HOST binds to that address, by default "" 40 --stdin read commands from stdin and write results to 41 stdout instead of using sockets 42 --epc use emacs epc mode 43 --debug enable debug output 44 --log enable verbose logging to nimsuggest.log file 45 --v1 use version 1 of the protocol; for backwards compatibility 46 --refresh perform automatic refreshes to keep the analysis precise 47 --maxresults:N limit the number of suggestions to N 48 --tester implies --stdin and outputs a line 49 '""" & DummyEof & """' for the tester 50 --find attempts to find the project file of the current project 51 52The server then listens to the connection and takes line-based commands. 53 54If --autobind is used, the binded port number will be printed to stdout. 55 56In addition, all command line options of Nim that do not affect code generation 57are supported. 58""" 59type 60 Mode = enum mstdin, mtcp, mepc, mcmdsug, mcmdcon 61 CachedMsg = object 62 info: TLineInfo 63 msg: string 64 sev: Severity 65 CachedMsgs = seq[CachedMsg] 66 67var 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 76 requests: Channel[string] 77 results: Channel[Suggest] 78 79proc writelnToChannel(line: string) = 80 results.send(Suggest(section: ideMsg, doc: line)) 81 82proc sugResultHook(s: Suggest) = 83 results.send(s) 84 85proc errorHook(conf: ConfigRef; info: TLineInfo; msg: string; sev: Severity) = 86 results.send(Suggest(section: ideChk, filePath: toFullPath(conf, info), 87 line: toLinenumber(info), column: toColumn(info), doc: msg, 88 forth: $sev)) 89 90proc myLog(s: string) = 91 if gLogging: log(s) 92 93const 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 100proc parseQuoted(cmd: string; outp: var string; start: int): int = 101 var i = start 102 i += skipWhitespace(cmd, i) 103 if i < cmd.len and cmd[i] == '"': 104 i += parseUntil(cmd, outp, '"', i+1)+2 105 else: 106 i += parseUntil(cmd, outp, seps, i) 107 result = i 108 109proc sexp(s: IdeCmd|TSymKind|PrefixMatch): SexpNode = sexp($s) 110 111proc sexp(s: Suggest): SexpNode = 112 # If you change the order here, make sure to change it over in 113 # nim-mode.el too. 114 let qp = if s.qualifiedPath.len == 0: @[] else: s.qualifiedPath 115 result = convertSexp([ 116 s.section, 117 TSymKind s.symkind, 118 qp.map(newSString), 119 s.filePath, 120 s.forth, 121 s.line, 122 s.column, 123 s.doc, 124 s.quality 125 ]) 126 if s.section == ideSug: 127 result.add convertSexp(s.prefix) 128 129proc sexp(s: seq[Suggest]): SexpNode = 130 result = newSList() 131 for sug in s: 132 result.add(sexp(sug)) 133 134proc listEpc(): SexpNode = 135 # This function is called from Emacs to show available options. 136 let 137 argspecs = sexp("file line column dirtyfile".split(" ").map(newSSymbol)) 138 docstring = sexp("line starts at 1, column at 0, dirtyfile is optional") 139 result = newSList() 140 for command in ["sug", "con", "def", "use", "dus", "chk", "mod"]: 141 let 142 cmd = sexp(command) 143 methodDesc = newSList() 144 methodDesc.add(cmd) 145 methodDesc.add(argspecs) 146 methodDesc.add(docstring) 147 result.add(methodDesc) 148 149proc findNode(n: PNode; trackPos: TLineInfo): PSym = 150 #echo "checking node ", n.info 151 if n.kind == nkSym: 152 if isTracked(n.info, trackPos, n.sym.name.s.len): return n.sym 153 else: 154 for i in 0 ..< safeLen(n): 155 let res = findNode(n[i], trackPos) 156 if res != nil: return res 157 158proc symFromInfo(graph: ModuleGraph; trackPos: TLineInfo): PSym = 159 let m = graph.getModule(trackPos.fileIndex) 160 if m != nil and m.ast != nil: 161 result = findNode(m.ast, trackPos) 162 163proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; 164 graph: ModuleGraph) = 165 let conf = graph.config 166 myLog("cmd: " & $cmd & ", file: " & file.string & 167 ", dirtyFile: " & dirtyfile.string & 168 "[" & $line & ":" & $col & "]") 169 conf.ideCmd = cmd 170 if cmd == ideUse and conf.suggestVersion != 0: 171 graph.resetAllModules() 172 var isKnownFile = true 173 let dirtyIdx = fileInfoIdx(conf, file, isKnownFile) 174 175 if not dirtyfile.isEmpty: msgs.setDirtyFile(conf, dirtyIdx, dirtyfile) 176 else: msgs.setDirtyFile(conf, dirtyIdx, AbsoluteFile"") 177 178 conf.m.trackPos = newLineInfo(dirtyIdx, line, col) 179 conf.m.trackPosAttached = false 180 conf.errorCounter = 0 181 if conf.suggestVersion == 1: 182 graph.usageSym = nil 183 if not isKnownFile: 184 graph.compileProject(dirtyIdx) 185 if conf.suggestVersion == 0 and conf.ideCmd in {ideUse, ideDus} and 186 dirtyfile.isEmpty: 187 discard "no need to recompile anything" 188 else: 189 let modIdx = graph.parentModule(dirtyIdx) 190 graph.markDirty dirtyIdx 191 graph.markClientsDirty dirtyIdx 192 if conf.ideCmd != ideMod: 193 if isKnownFile: 194 graph.compileProject(modIdx) 195 if conf.ideCmd in {ideUse, ideDus}: 196 let u = if conf.suggestVersion != 1: graph.symFromInfo(conf.m.trackPos) else: graph.usageSym 197 if u != nil: 198 listUsages(graph, u) 199 else: 200 localError(conf, conf.m.trackPos, "found no symbol at this position " & (conf $ conf.m.trackPos)) 201 202proc execute(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; 203 graph: ModuleGraph) = 204 if cmd == ideChk: 205 graph.config.structuredErrorHook = errorHook 206 graph.config.writelnHook = myLog 207 else: 208 graph.config.structuredErrorHook = nil 209 graph.config.writelnHook = myLog 210 executeNoHooks(cmd, file, dirtyfile, line, col, graph) 211 212proc executeEpc(cmd: IdeCmd, args: SexpNode; 213 graph: ModuleGraph) = 214 let 215 file = AbsoluteFile args[0].getStr 216 line = args[1].getNum 217 column = args[2].getNum 218 var dirtyfile = AbsoluteFile"" 219 if len(args) > 3: 220 dirtyfile = AbsoluteFile args[3].getStr("") 221 execute(cmd, file, dirtyfile, int(line), int(column), graph) 222 223proc returnEpc(socket: Socket, uid: BiggestInt, s: SexpNode|string, 224 returnSymbol = "return") = 225 let response = $convertSexp([newSSymbol(returnSymbol), uid, s]) 226 socket.send(toHex(len(response), 6)) 227 socket.send(response) 228 229template 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 237proc toStdout() {.gcsafe.} = 238 while true: 239 let res = results.recv() 240 case res.section 241 of ideNone: break 242 of ideMsg: echo res.doc 243 of ideKnown: echo res.quality == 1 244 of ideProject: echo res.filePath 245 else: echo res 246 247proc toSocket(stdoutSocket: Socket) {.gcsafe.} = 248 while true: 249 let res = results.recv() 250 case res.section 251 of ideNone: break 252 of ideMsg: stdoutSocket.send(res.doc & "\c\L") 253 of ideKnown: stdoutSocket.send($(res.quality == 1) & "\c\L") 254 of ideProject: stdoutSocket.send(res.filePath & "\c\L") 255 else: stdoutSocket.send($res & "\c\L") 256 257proc toEpc(client: Socket; uid: BiggestInt) {.gcsafe.} = 258 var list = newSList() 259 while true: 260 let res = results.recv() 261 case res.section 262 of ideNone: break 263 of ideMsg: 264 list.add sexp(res.doc) 265 of ideKnown: 266 list.add sexp(res.quality == 1) 267 of ideProject: 268 list.add sexp(res.filePath) 269 else: 270 list.add sexp(res) 271 returnEpc(client, uid, list) 272 273template setVerbosity(level: typed) = 274 gVerbosity = level 275 conf.notes = NotesVerbosity[gVerbosity] 276 277proc connectToNextFreePort(server: Socket, host: string): Port = 278 server.bindAddr(Port(0), host) 279 let (_, port) = server.getLocalAddr 280 result = port 281 282type 283 ThreadParams = tuple[port: Port; address: string] 284 285proc replStdinSingleCmd(line: string) = 286 requests.send line 287 toStdout() 288 echo "" 289 flushFile(stdout) 290 291proc replStdin(x: ThreadParams) {.thread.} = 292 if gEmitEof: 293 echo DummyEof 294 while true: 295 let line = readLine(stdin) 296 requests.send line 297 if line == "quit": break 298 toStdout() 299 echo DummyEof 300 flushFile(stdout) 301 else: 302 echo Help 303 var line = "" 304 while readLineFromStdin("> ", line): 305 replStdinSingleCmd(line) 306 requests.send "quit" 307 308proc replCmdline(x: ThreadParams) {.thread.} = 309 replStdinSingleCmd(x.address) 310 requests.send "quit" 311 312proc replTcp(x: ThreadParams) {.thread.} = 313 var server = newSocket() 314 if gAutoBind: 315 let port = server.connectToNextFreePort(x.address) 316 server.listen() 317 echo port 318 stdout.flushFile() 319 else: 320 server.bindAddr(x.port, x.address) 321 server.listen() 322 var inp = "" 323 var stdoutSocket: Socket 324 while true: 325 accept(server, stdoutSocket) 326 327 stdoutSocket.readLine(inp) 328 requests.send inp 329 toSocket(stdoutSocket) 330 stdoutSocket.send("\c\L") 331 stdoutSocket.close() 332 333proc argsToStr(x: SexpNode): string = 334 if x.kind != SList: return x.getStr 335 doAssert x.kind == SList 336 doAssert x.len >= 4 337 let file = x[0].getStr 338 let line = x[1].getNum 339 let col = x[2].getNum 340 let dirty = x[3].getStr 341 result = x[0].getStr.escape 342 if dirty.len > 0: 343 result.add ';' 344 result.add dirty.escape 345 result.add ':' 346 result.addInt line 347 result.add ':' 348 result.addInt col 349 350proc replEpc(x: ThreadParams) {.thread.} = 351 var server = newSocket() 352 let port = connectToNextFreePort(server, "localhost") 353 server.listen() 354 echo port 355 stdout.flushFile() 356 357 var client: Socket 358 # Wait for connection 359 accept(server, client) 360 while true: 361 var 362 sizeHex = "" 363 size = 0 364 messageBuffer = "" 365 checkSanity(client, sizeHex, size, messageBuffer) 366 let 367 message = parseSexp($messageBuffer) 368 epcApi = message[0].getSymbol 369 case epcApi 370 of "call": 371 let 372 uid = message[1].getNum 373 cmd = message[2].getSymbol 374 args = message[3] 375 376 when false: 377 x.ideCmd[] = parseIdeCmd(message[2].getSymbol) 378 case x.ideCmd[] 379 of ideSug, ideCon, ideDef, ideUse, ideDus, ideOutline, ideHighlight: 380 setVerbosity(0) 381 else: discard 382 let fullCmd = cmd & " " & args.argsToStr 383 myLog "MSG CMD: " & fullCmd 384 requests.send(fullCmd) 385 toEpc(client, uid) 386 of "methods": 387 returnEpc(client, message[1].getNum, listEpc()) 388 of "epc-error": 389 # an unhandled exception forces down the whole process anyway, so we 390 # use 'quit' here instead of 'raise' 391 quit("received epc error: " & $messageBuffer) 392 else: 393 let errMessage = case epcApi 394 of "return", "return-error": 395 "no return expected" 396 else: 397 "unexpected call: " & epcApi 398 quit errMessage 399 400proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) = 401 let conf = graph.config 402 403 template sentinel() = 404 # send sentinel for the input reading thread: 405 results.send(Suggest(section: ideNone)) 406 407 template toggle(sw) = 408 if sw in conf.globalOptions: 409 excl(conf.globalOptions, sw) 410 else: 411 incl(conf.globalOptions, sw) 412 sentinel() 413 return 414 415 template err() = 416 echo Help 417 sentinel() 418 return 419 420 var opc = "" 421 var i = parseIdent(cmd, opc, 0) 422 case opc.normalize 423 of "sug": conf.ideCmd = ideSug 424 of "con": conf.ideCmd = ideCon 425 of "def": conf.ideCmd = ideDef 426 of "use": conf.ideCmd = ideUse 427 of "dus": conf.ideCmd = ideDus 428 of "mod": conf.ideCmd = ideMod 429 of "chk": conf.ideCmd = ideChk 430 of "highlight": conf.ideCmd = ideHighlight 431 of "outline": conf.ideCmd = ideOutline 432 of "quit": 433 sentinel() 434 quit() 435 of "debug": toggle optIdeDebug 436 of "terse": toggle optIdeTerse 437 of "known": conf.ideCmd = ideKnown 438 of "project": conf.ideCmd = ideProject 439 else: err() 440 var dirtyfile = "" 441 var orig = "" 442 i += skipWhitespace(cmd, i) 443 if i < cmd.len and cmd[i] in {'0'..'9'}: 444 orig = string conf.projectFull 445 else: 446 i = parseQuoted(cmd, orig, i) 447 if i < cmd.len and cmd[i] == ';': 448 i = parseQuoted(cmd, dirtyfile, i+1) 449 i += skipWhile(cmd, seps, i) 450 var line = 0 451 var col = -1 452 i += parseInt(cmd, line, i) 453 i += skipWhile(cmd, seps, i) 454 i += parseInt(cmd, col, i) 455 456 if conf.ideCmd == ideKnown: 457 results.send(Suggest(section: ideKnown, quality: ord(fileInfoKnown(conf, AbsoluteFile orig)))) 458 elif conf.ideCmd == ideProject: 459 results.send(Suggest(section: ideProject, filePath: string conf.projectFull)) 460 else: 461 if conf.ideCmd == ideChk: 462 for cm in cachedMsgs: errorHook(conf, cm.info, cm.msg, cm.sev) 463 execute(conf.ideCmd, AbsoluteFile orig, AbsoluteFile dirtyfile, line, col, graph) 464 sentinel() 465 466proc recompileFullProject(graph: ModuleGraph) = 467 #echo "recompiling full project" 468 resetSystemArtifacts(graph) 469 graph.vm = nil 470 graph.resetAllModules() 471 GC_fullCollect() 472 compileProject(graph) 473 #echo GC_getStatistics() 474 475proc mainThread(graph: ModuleGraph) = 476 let conf = graph.config 477 if gLogging: 478 for it in conf.searchPaths: 479 log(it.string) 480 481 proc wrHook(line: string) {.closure.} = 482 if gMode == mepc: 483 if gLogging: log(line) 484 else: 485 writelnToChannel(line) 486 487 conf.writelnHook = wrHook 488 conf.suggestionResultHook = sugResultHook 489 graph.doStopCompile = proc (): bool = requests.peek() > 0 490 var idle = 0 491 var cachedMsgs: CachedMsgs = @[] 492 while true: 493 let (hasData, req) = requests.tryRecv() 494 if hasData: 495 conf.writelnHook = wrHook 496 conf.suggestionResultHook = sugResultHook 497 execCmd(req, graph, cachedMsgs) 498 idle = 0 499 else: 500 os.sleep 250 501 idle += 1 502 if idle == 20 and gRefresh: 503 # we use some nimsuggest activity to enable a lazy recompile: 504 conf.ideCmd = ideChk 505 conf.writelnHook = proc (s: string) = discard 506 cachedMsgs.setLen 0 507 conf.structuredErrorHook = proc (conf: ConfigRef; info: TLineInfo; msg: string; sev: Severity) = 508 cachedMsgs.add(CachedMsg(info: info, msg: msg, sev: sev)) 509 conf.suggestionResultHook = proc (s: Suggest) = discard 510 recompileFullProject(graph) 511 512var 513 inputThread: Thread[ThreadParams] 514 515proc mainCommand(graph: ModuleGraph) = 516 let conf = graph.config 517 clearPasses(graph) 518 registerPass graph, verbosePass 519 registerPass graph, semPass 520 conf.setCmd cmdIdeTools 521 wantMainModule(conf) 522 523 if not fileExists(conf.projectFull): 524 quit "cannot find file: " & conf.projectFull.string 525 526 add(conf.searchPaths, conf.libpath) 527 528 conf.setErrorMaxHighMaybe # honor --errorMax even if it may not make sense here 529 # do not print errors, but log them 530 conf.writelnHook = myLog 531 conf.structuredErrorHook = nil 532 533 # compile the project before showing any input so that we already 534 # can answer questions right away: 535 compileProject(graph) 536 537 open(requests) 538 open(results) 539 540 case gMode 541 of mstdin: createThread(inputThread, replStdin, (gPort, gAddress)) 542 of mtcp: createThread(inputThread, replTcp, (gPort, gAddress)) 543 of mepc: createThread(inputThread, replEpc, (gPort, gAddress)) 544 of mcmdsug: createThread(inputThread, replCmdline, 545 (gPort, "sug \"" & conf.projectFull.string & "\":" & gAddress)) 546 of mcmdcon: createThread(inputThread, replCmdline, 547 (gPort, "con \"" & conf.projectFull.string & "\":" & gAddress)) 548 mainThread(graph) 549 joinThread(inputThread) 550 close(requests) 551 close(results) 552 553proc processCmdLine*(pass: TCmdLinePass, cmd: string; conf: ConfigRef) = 554 var p = parseopt.initOptParser(cmd) 555 var findProject = false 556 while true: 557 parseopt.next(p) 558 case p.kind 559 of cmdEnd: break 560 of cmdLongOption, cmdShortOption: 561 case p.key.normalize 562 of "help", "h": 563 stdout.writeLine(Usage) 564 quit() 565 of "autobind": 566 gMode = mtcp 567 gAutoBind = true 568 of "port": 569 gPort = parseInt(p.val).Port 570 gMode = mtcp 571 of "address": 572 gAddress = p.val 573 gMode = mtcp 574 of "stdin": gMode = mstdin 575 of "cmdsug": 576 gMode = mcmdsug 577 gAddress = p.val 578 incl(conf.globalOptions, optIdeDebug) 579 of "cmdcon": 580 gMode = mcmdcon 581 gAddress = p.val 582 incl(conf.globalOptions, optIdeDebug) 583 of "epc": 584 gMode = mepc 585 conf.verbosity = 0 # Port number gotta be first. 586 of "debug": incl(conf.globalOptions, optIdeDebug) 587 of "v2": conf.suggestVersion = 0 588 of "v1": conf.suggestVersion = 1 589 of "tester": 590 gMode = mstdin 591 gEmitEof = true 592 gRefresh = false 593 of "log": gLogging = true 594 of "refresh": 595 if p.val.len > 0: 596 gRefresh = parseBool(p.val) 597 else: 598 gRefresh = true 599 of "maxresults": 600 conf.suggestMaxResults = parseInt(p.val) 601 of "find": 602 findProject = true 603 else: processSwitch(pass, p, conf) 604 of cmdArgument: 605 let a = unixToNativePath(p.key) 606 if dirExists(a) and not fileExists(a.addFileExt("nim")): 607 conf.projectName = findProjectNimFile(conf, a) 608 # don't make it worse, report the error the old way: 609 if conf.projectName.len == 0: conf.projectName = a 610 else: 611 if findProject: 612 conf.projectName = findProjectNimFile(conf, a.parentDir()) 613 if conf.projectName.len == 0: 614 conf.projectName = a 615 else: 616 conf.projectName = a 617 # if processArgument(pass, p, argsCount): break 618 619proc handleCmdLine(cache: IdentCache; conf: ConfigRef) = 620 let self = NimProg( 621 suggestMode: true, 622 processCmdLine: processCmdLine 623 ) 624 self.initDefinesProg(conf, "nimsuggest") 625 626 if paramCount() == 0: 627 stdout.writeLine(Usage) 628 return 629 630 self.processCmdLineAndProjectPath(conf) 631 632 if gMode != mstdin: 633 conf.writelnHook = proc (msg: string) = discard 634 # Find Nim's prefix dir. 635 let binaryPath = findExe("nim") 636 if binaryPath == "": 637 raise newException(IOError, 638 "Cannot find Nim standard library: Nim compiler not in PATH") 639 conf.prefixDir = AbsoluteDir binaryPath.splitPath().head.parentDir() 640 if not dirExists(conf.prefixDir / RelativeDir"lib"): 641 conf.prefixDir = AbsoluteDir"" 642 643 #msgs.writelnHook = proc (line: string) = log(line) 644 myLog("START " & conf.projectFull.string) 645 646 var graph = newModuleGraph(cache, conf) 647 if self.loadConfigsAndProcessCmdLine(cache, conf, graph): 648 mainCommand(graph) 649 650when isMainModule: 651 handleCmdLine(newIdentCache(), newConfigRef()) 652else: 653 export Suggest 654 export IdeCmd 655 export AbsoluteFile 656 type NimSuggest* = ref object 657 graph: ModuleGraph 658 idle: int 659 cachedMsgs: CachedMsgs 660 661 proc initNimSuggest*(project: string, nimPath: string = ""): NimSuggest = 662 var retval: ModuleGraph 663 proc mockCommand(graph: ModuleGraph) = 664 retval = graph 665 let conf = graph.config 666 clearPasses(graph) 667 registerPass graph, verbosePass 668 registerPass graph, semPass 669 conf.setCmd cmdIdeTools 670 wantMainModule(conf) 671 672 if not fileExists(conf.projectFull): 673 quit "cannot find file: " & conf.projectFull.string 674 675 add(conf.searchPaths, conf.libpath) 676 677 conf.setErrorMaxHighMaybe 678 # do not print errors, but log them 679 conf.writelnHook = myLog 680 conf.structuredErrorHook = nil 681 682 # compile the project before showing any input so that we already 683 # can answer questions right away: 684 compileProject(graph) 685 686 687 proc mockCmdLine(pass: TCmdLinePass, cmd: string; conf: ConfigRef) = 688 conf.suggestVersion = 0 689 let a = unixToNativePath(project) 690 if dirExists(a) and not fileExists(a.addFileExt("nim")): 691 conf.projectName = findProjectNimFile(conf, a) 692 # don't make it worse, report the error the old way: 693 if conf.projectName.len == 0: conf.projectName = a 694 else: 695 conf.projectName = a 696 # if processArgument(pass, p, argsCount): break 697 let 698 cache = newIdentCache() 699 conf = newConfigRef() 700 self = NimProg( 701 suggestMode: true, 702 processCmdLine: mockCmdLine 703 ) 704 self.initDefinesProg(conf, "nimsuggest") 705 706 self.processCmdLineAndProjectPath(conf) 707 708 if gMode != mstdin: 709 conf.writelnHook = proc (msg: string) = discard 710 # Find Nim's prefix dir. 711 if nimPath == "": 712 let binaryPath = findExe("nim") 713 if binaryPath == "": 714 raise newException(IOError, 715 "Cannot find Nim standard library: Nim compiler not in PATH") 716 conf.prefixDir = AbsoluteDir binaryPath.splitPath().head.parentDir() 717 if not dirExists(conf.prefixDir / RelativeDir"lib"): 718 conf.prefixDir = AbsoluteDir"" 719 else: 720 conf.prefixDir = AbsoluteDir nimPath 721 722 #msgs.writelnHook = proc (line: string) = log(line) 723 myLog("START " & conf.projectFull.string) 724 725 var graph = newModuleGraph(cache, conf) 726 if self.loadConfigsAndProcessCmdLine(cache, conf, graph): 727 mockCommand(graph) 728 if gLogging: 729 for it in conf.searchPaths: 730 log(it.string) 731 732 retval.doStopCompile = proc (): bool = false 733 return NimSuggest(graph: retval, idle: 0, cachedMsgs: @[]) 734 735 proc runCmd*(nimsuggest: NimSuggest, cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int): seq[Suggest] = 736 var retval: seq[Suggest] = @[] 737 let conf = nimsuggest.graph.config 738 conf.ideCmd = cmd 739 conf.writelnHook = proc (line: string) = 740 retval.add(Suggest(section: ideMsg, doc: line)) 741 conf.suggestionResultHook = proc (s: Suggest) = 742 retval.add(s) 743 conf.writelnHook = proc (s: string) = 744 stderr.write s & "\n" 745 if conf.ideCmd == ideKnown: 746 retval.add(Suggest(section: ideKnown, quality: ord(fileInfoKnown(conf, file)))) 747 elif conf.ideCmd == ideProject: 748 retval.add(Suggest(section: ideProject, filePath: string conf.projectFull)) 749 else: 750 if conf.ideCmd == ideChk: 751 for cm in nimsuggest.cachedMsgs: errorHook(conf, cm.info, cm.msg, cm.sev) 752 if conf.ideCmd == ideChk: 753 conf.structuredErrorHook = proc (conf: ConfigRef; info: TLineInfo; msg: string; sev: Severity) = 754 retval.add(Suggest(section: ideChk, filePath: toFullPath(conf, info), 755 line: toLinenumber(info), column: toColumn(info), doc: msg, 756 forth: $sev)) 757 758 else: 759 conf.structuredErrorHook = nil 760 executeNoHooks(conf.ideCmd, file, dirtyfile, line, col, nimsuggest.graph) 761 return retval 762