summary refs log tree commit diff stats
path: root/nimsuggest
diff options
context:
space:
mode:
Diffstat (limited to 'nimsuggest')
-rw-r--r--nimsuggest/config.nims2
-rw-r--r--nimsuggest/crashtester.nim2
-rw-r--r--nimsuggest/nimsuggest.nim744
-rw-r--r--nimsuggest/nimsuggest.nimble17
-rw-r--r--nimsuggest/procmonitor.nim34
-rw-r--r--nimsuggest/sexp.nim43
-rw-r--r--nimsuggest/tester.nim80
-rw-r--r--nimsuggest/tests/fixtures/mclass_macro.nim164
-rw-r--r--nimsuggest/tests/fixtures/mdep_v1.nim (renamed from nimsuggest/tests/dep_v1.nim)0
-rw-r--r--nimsuggest/tests/fixtures/mdep_v2.nim (renamed from nimsuggest/tests/dep_v2.nim)0
-rw-r--r--nimsuggest/tests/fixtures/mfakeassert.nim5
-rw-r--r--nimsuggest/tests/fixtures/minclude_import.nim15
-rw-r--r--nimsuggest/tests/fixtures/minclude_include.nim4
-rw-r--r--nimsuggest/tests/fixtures/minclude_types.nim6
-rw-r--r--nimsuggest/tests/fixtures/mstrutils.nim19
-rw-r--r--nimsuggest/tests/module_20265.nim6
-rw-r--r--nimsuggest/tests/t20265_1.nim8
-rw-r--r--nimsuggest/tests/t20265_2.nim8
-rw-r--r--nimsuggest/tests/t20440.nim7
-rw-r--r--nimsuggest/tests/t20440.nims1
-rw-r--r--nimsuggest/tests/t21185.nim18
-rw-r--r--nimsuggest/tests/t22448.nim11
-rw-r--r--nimsuggest/tests/tarrowcrash.nim20
-rw-r--r--nimsuggest/tests/tchk1.nim11
-rw-r--r--nimsuggest/tests/tchk2.nim35
-rw-r--r--nimsuggest/tests/tchk_compiles.nim8
-rw-r--r--nimsuggest/tests/tcon1.nim34
-rw-r--r--nimsuggest/tests/tconcept1.nim12
-rw-r--r--nimsuggest/tests/tconcept2.nim15
-rw-r--r--nimsuggest/tests/tdef1.nim10
-rw-r--r--nimsuggest/tests/tdef2.nim13
-rw-r--r--nimsuggest/tests/tdef_forward.nim13
-rw-r--r--nimsuggest/tests/tdef_let.nim7
-rw-r--r--nimsuggest/tests/tdot3.nim4
-rw-r--r--nimsuggest/tests/tdot4.nim22
-rw-r--r--nimsuggest/tests/tenum_field.nim17
-rw-r--r--nimsuggest/tests/tfatal1.nim15
-rw-r--r--nimsuggest/tests/tgeneric_highlight.nim7
-rw-r--r--nimsuggest/tests/tgenerics.nim18
-rw-r--r--nimsuggest/tests/tic.nim20
-rw-r--r--nimsuggest/tests/timport_highlight.nim12
-rw-r--r--nimsuggest/tests/tinclude.nim29
-rw-r--r--nimsuggest/tests/top_highlight.nim11
-rw-r--r--nimsuggest/tests/tqualified_highlight.nim2
-rw-r--r--nimsuggest/tests/tstrutils.nim10
-rw-r--r--nimsuggest/tests/tsug_enum.nim18
-rw-r--r--nimsuggest/tests/tsug_pragmas.nim40
-rw-r--r--nimsuggest/tests/tsug_recursive.nim8
-rw-r--r--nimsuggest/tests/tsug_regression.nim25
-rw-r--r--nimsuggest/tests/tsug_template.nim6
-rw-r--r--nimsuggest/tests/tsug_typedecl.nim26
-rw-r--r--nimsuggest/tests/ttempl_inst.nim13
-rw-r--r--nimsuggest/tests/ttemplate_highlight.nim (renamed from nimsuggest/tests/disabled_ttemplate_highlight.nim)0
-rw-r--r--nimsuggest/tests/ttype_decl.nim9
-rw-r--r--nimsuggest/tests/tuse.nim22
-rw-r--r--nimsuggest/tests/tuse_enum.nim15
-rw-r--r--nimsuggest/tests/tuse_structure.nim15
-rw-r--r--nimsuggest/tests/tv3.nim27
-rw-r--r--nimsuggest/tests/tv3_con.nim13
-rw-r--r--nimsuggest/tests/tv3_definition.nim9
-rw-r--r--nimsuggest/tests/tv3_forward_definition.nim23
-rw-r--r--nimsuggest/tests/tv3_generics.nim18
-rw-r--r--nimsuggest/tests/tv3_globalSymbols.nim14
-rw-r--r--nimsuggest/tests/tv3_import.nim7
-rw-r--r--nimsuggest/tests/tv3_outline.nim45
-rw-r--r--nimsuggest/tests/tv3_typeDefinition.nim32
-rw-r--r--nimsuggest/tests/twithin_macro.nim179
-rw-r--r--nimsuggest/tests/twithin_macro_prefix.nim170
68 files changed, 1751 insertions, 522 deletions
diff --git a/nimsuggest/config.nims b/nimsuggest/config.nims
new file mode 100644
index 000000000..ee19f9893
--- /dev/null
+++ b/nimsuggest/config.nims
@@ -0,0 +1,2 @@
+# xxx not sure why this flag isn't needed: switch("processing", "filenames")
+switch("filenames", "canonical")
diff --git a/nimsuggest/crashtester.nim b/nimsuggest/crashtester.nim
index 10fef2b23..ead97adec 100644
--- a/nimsuggest/crashtester.nim
+++ b/nimsuggest/crashtester.nim
@@ -32,7 +32,7 @@ proc callNimsuggest() =
       if a == DummyEof: break
 
     var line = 0
-    for i in 0..< contents.len:
+    for i in 0 ..< contents.len:
       let slic = contents[0..i]
       writeFile("temp000.nim", slic)
       let (line, col) = getPosition(slic)
diff --git a/nimsuggest/nimsuggest.nim b/nimsuggest/nimsuggest.nim
index a48e9443d..04bae08c1 100644
--- a/nimsuggest/nimsuggest.nim
+++ b/nimsuggest/nimsuggest.nim
@@ -7,6 +7,24 @@
 #    distribution, for details about the copyright.
 #
 
+import compiler/renderer
+import compiler/types
+import compiler/trees
+import compiler/wordrecg
+import compiler/sempass2
+import strformat
+import algorithm
+import tables
+import times
+import procmonitor
+
+template tryImport(module) = import module
+
+when compiles tryImport ../dist/checksums/src/checksums/sha1:
+  import ../dist/checksums/src/checksums/sha1
+else:
+  import checksums/sha1
+
 ## Nimsuggest is a tool that helps to give editors IDE like capabilities.
 
 when not defined(nimcore):
@@ -16,17 +34,21 @@ import strutils, os, parseopt, parseutils, sequtils, net, rdstdin, sexp
 # Do NOT import suggest. It will lead to weird bugs with
 # suggestionResultHook, because suggest.nim is included by sigmatch.
 # So we import that one instead.
-import compiler / [options, commands, modules, sem,
+import compiler / [options, commands, modules,
   passes, passaux, msgs,
   sigmatch, ast,
   idents, modulegraphs, prefixmatches, lineinfos, cmdlinehelper,
-  pathutils]
+  pathutils, condsyms, syntaxes, suggestsymdb]
+
+when defined(nimPreviewSlimSystem):
+  import std/typedthreads
 
 when defined(windows):
   import winlean
 else:
   import posix
 
+const HighestSuggestProtocolVersion = 4
 const DummyEof = "!EOF!"
 const Usage = """
 Nimsuggest - Tool to give every editor IDE like capabilities for Nim
@@ -39,15 +61,25 @@ Options:
   --address:HOST          binds to that address, by default ""
   --stdin                 read commands from stdin and write results to
                           stdout instead of using sockets
+  --clientProcessId:PID   shutdown nimsuggest in case this process dies
   --epc                   use emacs epc mode
   --debug                 enable debug output
   --log                   enable verbose logging to nimsuggest.log file
   --v1                    use version 1 of the protocol; for backwards compatibility
+  --v2                    use version 2(default) of the protocol
+  --v3                    use version 3 of the protocol
+  --v4                    use version 4 of the protocol
+  --info:X                information
+    --info:nimVer           return the Nim compiler version that nimsuggest uses internally
+    --info:protocolVer      return the newest protocol version that is supported
+    --info:capabilities     return the capabilities supported by nimsuggest
   --refresh               perform automatic refreshes to keep the analysis precise
   --maxresults:N          limit the number of suggestions to N
   --tester                implies --stdin and outputs a line
                           '""" & DummyEof & """' for the tester
   --find                  attempts to find the project file of the current project
+  --exceptionInlayHints:on|off
+                          globally turn exception inlay hints on|off
 
 The server then listens to the connection and takes line-based commands.
 
@@ -66,7 +98,7 @@ type
 
 var
   gPort = 6000.Port
-  gAddress = ""
+  gAddress = "127.0.0.1"
   gMode: Mode
   gEmitEof: bool # whether we write '!EOF!' dummy lines
   gLogging = defined(logging)
@@ -76,6 +108,9 @@ var
   requests: Channel[string]
   results: Channel[Suggest]
 
+proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile, line, col: int; tag: string,
+  graph: ModuleGraph);
+
 proc writelnToChannel(line: string) =
   results.send(Suggest(section: ideMsg, doc: line))
 
@@ -96,9 +131,12 @@ const
          "type 'quit' to quit\n" &
          "type 'debug' to toggle debug mode on/off\n" &
          "type 'terse' to toggle terse mode on/off"
-
-type
-  EUnexpectedCommand = object of Exception
+  #List of currently supported capabilities. So lang servers/ides can iterate over and check for what's enabled
+  Capabilities = [
+    "con", #current NimSuggest supports the `con` commmand
+    "exceptionInlayHints",
+    "unknownFile", #current NimSuggest can handle unknown files
+  ]
 
 proc parseQuoted(cmd: string; outp: var string; start: int): int =
   var i = start
@@ -128,6 +166,9 @@ proc sexp(s: Suggest): SexpNode =
   ])
   if s.section == ideSug:
     result.add convertSexp(s.prefix)
+  if s.section in {ideOutline, ideExpand} and s.version == 3:
+    result.add convertSexp(s.endLine.int)
+    result.add convertSexp(s.endCol)
 
 proc sexp(s: seq[Suggest]): SexpNode =
   result = newSList()
@@ -140,7 +181,7 @@ proc listEpc(): SexpNode =
     argspecs = sexp("file line column dirtyfile".split(" ").map(newSSymbol))
     docstring = sexp("line starts at 1, column at 0, dirtyfile is optional")
   result = newSList()
-  for command in ["sug", "con", "def", "use", "dus", "chk", "mod"]:
+  for command in ["sug", "con", "def", "use", "dus", "chk", "mod", "globalSymbols", "recompile", "saved", "chkFile", "declaration", "inlayHints"]:
     let
       cmd = sexp(command)
       methodDesc = newSList()
@@ -163,9 +204,51 @@ proc symFromInfo(graph: ModuleGraph; trackPos: TLineInfo): PSym =
   if m != nil and m.ast != nil:
     result = findNode(m.ast, trackPos)
 
-proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int;
+template benchmark(benchmarkName: untyped, code: untyped) =
+  block:
+    myLog "Started [" & benchmarkName & "]..."
+    let t0 = epochTime()
+    code
+    let elapsed = epochTime() - t0
+    let elapsedStr = elapsed.formatFloat(format = ffDecimal, precision = 3)
+    myLog "CPU Time [" & benchmarkName & "] " & elapsedStr & "s"
+
+proc clearInstCache(graph: ModuleGraph, projectFileIdx: FileIndex) =
+  if projectFileIdx == InvalidFileIdx:
+    graph.typeInstCache.clear()
+    graph.procInstCache.clear()
+    return
+  var typeIdsToDelete = newSeq[ItemId]()
+  for id in graph.typeInstCache.keys:
+    if id.module == projectFileIdx.int:
+      typeIdsToDelete.add id
+  for id in typeIdsToDelete:
+    graph.typeInstCache.del id
+  var procIdsToDelete = newSeq[ItemId]()
+  for id in graph.procInstCache.keys:
+    if id.module == projectFileIdx.int:
+      procIdsToDelete.add id
+  for id in procIdsToDelete:
+    graph.procInstCache.del id
+
+  for tbl in mitems(graph.attachedOps):
+    var attachedOpsToDelete = newSeq[ItemId]()
+    for id in tbl.keys:
+      if id.module == projectFileIdx.int and sfOverridden in resolveAttachedOp(graph, tbl[id]).flags:
+        attachedOpsToDelete.add id
+    for id in attachedOpsToDelete:
+      tbl.del id
+
+proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, tag: string,
              graph: ModuleGraph) =
   let conf = graph.config
+
+  if conf.suggestVersion >= 3:
+    let command = fmt "cmd = {cmd} {file}:{line}:{col}"
+    benchmark command:
+      executeNoHooksV3(cmd, file, dirtyfile, line, col, tag, graph)
+    return
+
   myLog("cmd: " & $cmd & ", file: " & file.string &
         ", dirtyFile: " & dirtyfile.string &
         "[" & $line & ":" & $col & "]")
@@ -184,6 +267,7 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int;
   if conf.suggestVersion == 1:
     graph.usageSym = nil
   if not isKnownFile:
+    graph.clearInstCache(dirtyIdx)
     graph.compileProject(dirtyIdx)
   if conf.suggestVersion == 0 and conf.ideCmd in {ideUse, ideDus} and
       dirtyfile.isEmpty:
@@ -194,15 +278,19 @@ proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int;
     graph.markClientsDirty dirtyIdx
     if conf.ideCmd != ideMod:
       if isKnownFile:
+        graph.clearInstCache(modIdx)
         graph.compileProject(modIdx)
   if conf.ideCmd in {ideUse, ideDus}:
     let u = if conf.suggestVersion != 1: graph.symFromInfo(conf.m.trackPos) else: graph.usageSym
     if u != nil:
-      listUsages(conf, u)
+      listUsages(graph, u)
     else:
       localError(conf, conf.m.trackPos, "found no symbol at this position " & (conf $ conf.m.trackPos))
 
-proc execute(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int;
+proc executeNoHooks(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int, graph: ModuleGraph) =
+  executeNoHooks(cmd, file, dirtyfile, line, col, "", graph)
+
+proc execute(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int; tag: string,
              graph: ModuleGraph) =
   if cmd == ideChk:
     graph.config.structuredErrorHook = errorHook
@@ -210,7 +298,7 @@ proc execute(cmd: IdeCmd, file, dirtyfile: AbsoluteFile, line, col: int;
   else:
     graph.config.structuredErrorHook = nil
     graph.config.writelnHook = myLog
-  executeNoHooks(cmd, file, dirtyfile, line, col, graph)
+  executeNoHooks(cmd, file, dirtyfile, line, col, tag, graph)
 
 proc executeEpc(cmd: IdeCmd, args: SexpNode;
                 graph: ModuleGraph) =
@@ -221,7 +309,7 @@ proc executeEpc(cmd: IdeCmd, args: SexpNode;
   var dirtyfile = AbsoluteFile""
   if len(args) > 3:
     dirtyfile = AbsoluteFile args[3].getStr("")
-  execute(cmd, file, dirtyfile, int(line), int(column), graph)
+  execute(cmd, file, dirtyfile, int(line), int(column), args[3].getStr, graph)
 
 proc returnEpc(socket: Socket, uid: BiggestInt, s: SexpNode|string,
                returnSymbol = "return") =
@@ -322,7 +410,7 @@ proc replTcp(x: ThreadParams) {.thread.} =
   else:
     server.bindAddr(x.port, x.address)
     server.listen()
-  var inp = "".TaintedString
+  var inp = ""
   var stdoutSocket: Socket
   while true:
     accept(server, stdoutSocket)
@@ -439,6 +527,18 @@ proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) =
   of "terse": toggle optIdeTerse
   of "known": conf.ideCmd = ideKnown
   of "project": conf.ideCmd = ideProject
+  of "changed": conf.ideCmd = ideChanged
+  of "globalsymbols": conf.ideCmd = ideGlobalSymbols
+  of "declaration": conf.ideCmd = ideDeclaration
+  of "expand": conf.ideCmd = ideExpand
+  of "chkfile": conf.ideCmd = ideChkFile
+  of "recompile": conf.ideCmd = ideRecompile
+  of "type": conf.ideCmd = ideType
+  of "inlayhints":
+    if conf.suggestVersion >= 4:
+      conf.ideCmd = ideInlayHints
+    else:
+      err()
   else: err()
   var dirtyfile = ""
   var orig = ""
@@ -455,6 +555,7 @@ proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) =
   i += parseInt(cmd, line, i)
   i += skipWhile(cmd, seps, i)
   i += parseInt(cmd, col, i)
+  let tag = substr(cmd, i)
 
   if conf.ideCmd == ideKnown:
     results.send(Suggest(section: ideKnown, quality: ord(fileInfoKnown(conf, AbsoluteFile orig))))
@@ -463,23 +564,23 @@ proc execCmd(cmd: string; graph: ModuleGraph; cachedMsgs: CachedMsgs) =
   else:
     if conf.ideCmd == ideChk:
       for cm in cachedMsgs: errorHook(conf, cm.info, cm.msg, cm.sev)
-    execute(conf.ideCmd, AbsoluteFile orig, AbsoluteFile dirtyfile, line, col, graph)
+    execute(conf.ideCmd, AbsoluteFile orig, AbsoluteFile dirtyfile, line, col, tag, graph)
   sentinel()
 
 proc recompileFullProject(graph: ModuleGraph) =
-  #echo "recompiling full project"
-  resetSystemArtifacts(graph)
-  graph.vm = nil
-  graph.resetAllModules()
-  GC_fullCollect()
-  compileProject(graph)
-  #echo GC_getStatistics()
+  benchmark "Recompilation(clean)":
+    graph.resetForBackend()
+    graph.resetSystemArtifacts()
+    graph.vm = nil
+    graph.resetAllModules()
+    GC_fullCollect()
+    graph.compileProject()
 
 proc mainThread(graph: ModuleGraph) =
   let conf = graph.config
-  if gLogging:
-    for it in conf.searchPaths:
-      log(it.string)
+  myLog "searchPaths: "
+  for it in conf.searchPaths:
+    myLog("  " & it.string)
 
   proc wrHook(line: string) {.closure.} =
     if gMode == mepc:
@@ -502,7 +603,7 @@ proc mainThread(graph: ModuleGraph) =
     else:
       os.sleep 250
       idle += 1
-    if idle == 20 and gRefresh:
+    if idle == 20 and gRefresh and conf.suggestVersion < 3:
       # we use some nimsuggest activity to enable a lazy recompile:
       conf.ideCmd = ideChk
       conf.writelnHook = proc (s: string) = discard
@@ -520,7 +621,8 @@ proc mainCommand(graph: ModuleGraph) =
   clearPasses(graph)
   registerPass graph, verbosePass
   registerPass graph, semPass
-  conf.cmd = cmdIdeTools
+  conf.setCmd cmdIdeTools
+  defineSymbol(conf.symbols, $conf.backend)
   wantMainModule(conf)
 
   if not fileExists(conf.projectFull):
@@ -528,19 +630,27 @@ proc mainCommand(graph: ModuleGraph) =
 
   add(conf.searchPaths, conf.libpath)
 
-  # do not stop after the first error:
-  conf.errorMax = high(int)
+  conf.setErrorMaxHighMaybe # honor --errorMax even if it may not make sense here
   # do not print errors, but log them
-  conf.writelnHook = myLog
-  conf.structuredErrorHook = nil
+  conf.writelnHook = proc (msg: string) = discard
+
+  if graph.config.suggestVersion >= 3:
+    graph.config.structuredErrorHook = proc (conf: ConfigRef; info: TLineInfo; msg: string; sev: Severity) =
+      let suggest = Suggest(section: ideChk, filePath: toFullPath(conf, info),
+        line: toLinenumber(info), column: toColumn(info), doc: msg, forth: $sev)
+      graph.suggestErrors.mgetOrPut(info.fileIndex, @[]).add suggest
 
   # compile the project before showing any input so that we already
   # can answer questions right away:
-  compileProject(graph)
+  benchmark "Initial compilation":
+    compileProject(graph)
 
   open(requests)
   open(results)
 
+  if graph.config.clientProcessId != 0:
+    hookProcMonitor(graph.config.clientProcessId)
+
   case gMode
   of mstdin: createThread(inputThread, replStdin, (gPort, gAddress))
   of mtcp: createThread(inputThread, replTcp, (gPort, gAddress))
@@ -588,8 +698,28 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string; conf: ConfigRef) =
         gMode = mepc
         conf.verbosity = 0          # Port number gotta be first.
       of "debug": incl(conf.globalOptions, optIdeDebug)
-      of "v2": conf.suggestVersion = 0
       of "v1": conf.suggestVersion = 1
+      of "v2": conf.suggestVersion = 0
+      of "v3": conf.suggestVersion = 3
+      of "v4": conf.suggestVersion = 4
+      of "info":
+        case p.val.normalize
+        of "protocolver":
+          stdout.writeLine(HighestSuggestProtocolVersion)
+          quit 0
+        of "nimver":
+          stdout.writeLine(system.NimVersion)
+          quit 0
+        of "capabilities":
+          stdout.writeLine(Capabilities.toSeq.mapIt($it).join(" "))
+          quit 0
+        else:
+          processSwitch(pass, p, conf)
+      of "exceptioninlayhints":
+        case p.val.normalize
+        of "", "on": incl(conf.globalOptions, optIdeExceptionInlayHints)
+        of "off": excl(conf.globalOptions, optIdeExceptionInlayHints)
+        else: processSwitch(pass, p, conf)
       of "tester":
         gMode = mstdin
         gEmitEof = true
@@ -604,6 +734,8 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string; conf: ConfigRef) =
         conf.suggestMaxResults = parseInt(p.val)
       of "find":
         findProject = true
+      of "clientprocessid":
+        conf.clientProcessId = parseInt(p.val)
       else: processSwitch(pass, p, conf)
     of cmdArgument:
       let a = unixToNativePath(p.key)
@@ -623,8 +755,7 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string; conf: ConfigRef) =
 proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
   let self = NimProg(
     suggestMode: true,
-    processCmdLine: processCmdLine,
-    mainCommand: mainCommand
+    processCmdLine: processCmdLine
   )
   self.initDefinesProg(conf, "nimsuggest")
 
@@ -636,20 +767,520 @@ proc handleCmdLine(cache: IdentCache; conf: ConfigRef) =
 
   if gMode != mstdin:
     conf.writelnHook = proc (msg: string) = discard
-  # Find Nim's prefix dir.
-  let binaryPath = findExe("nim")
-  if binaryPath == "":
-    raise newException(IOError,
-        "Cannot find Nim standard library: Nim compiler not in PATH")
-  conf.prefixDir = AbsoluteDir binaryPath.splitPath().head.parentDir()
-  if not dirExists(conf.prefixDir / RelativeDir"lib"):
-    conf.prefixDir = AbsoluteDir""
-
+  conf.prefixDir = conf.getPrefixDir()
   #msgs.writelnHook = proc (line: string) = log(line)
   myLog("START " & conf.projectFull.string)
 
-  discard self.loadConfigsAndRunMainCommand(cache, conf)
+  var graph = newModuleGraph(cache, conf)
+  if self.loadConfigsAndProcessCmdLine(cache, conf, graph):
+
+    if conf.selectedGC == gcUnselected and
+          conf.backend != backendJs:
+      initOrcDefines(conf)
+    mainCommand(graph)
+
+# v3 start
+
+proc recompilePartially(graph: ModuleGraph, projectFileIdx = InvalidFileIdx) =
+  if projectFileIdx == InvalidFileIdx:
+    myLog "Recompiling partially from root"
+  else:
+    myLog fmt "Recompiling partially starting from {graph.getModule(projectFileIdx)}"
+
+  # inst caches are breaking incremental compilation when the cache caches stuff
+  # from dirty buffer
+  graph.clearInstCache(projectFileIdx)
+
+  GC_fullCollect()
+
+  try:
+    benchmark "Recompilation":
+      graph.compileProject(projectFileIdx)
+  except Exception as e:
+    myLog fmt "Failed to recompile partially with the following error:\n {e.msg} \n\n {e.getStackTrace()}"
+    try:
+      graph.recompileFullProject()
+    except Exception as e:
+      myLog fmt "Failed clean recompilation:\n {e.msg} \n\n {e.getStackTrace()}"
+
+func deduplicateSymInfoPair[SymInfoPair](xs: seq[SymInfoPair]): seq[SymInfoPair] =
+  # xs contains duplicate items and we want to filter them by range because the
+  # sym may not match. This can happen when xs contains the same definition but
+  # with different signature because suggestSym might be called multiple times
+  # for the same symbol (e. g. including/excluding the pragma)
+  result = newSeqOfCap[SymInfoPair](xs.len)
+  for itm in xs.reversed:
+    var found = false
+    for res in result:
+      if res.info.exactEquals(itm.info):
+        found = true
+        break
+    if not found:
+      result.add(itm)
+  result.reverse()
+
+func deduplicateSymInfoPair(xs: SuggestFileSymbolDatabase): SuggestFileSymbolDatabase =
+  # xs contains duplicate items and we want to filter them by range because the
+  # sym may not match. This can happen when xs contains the same definition but
+  # with different signature because suggestSym might be called multiple times
+  # for the same symbol (e. g. including/excluding the pragma)
+  result = SuggestFileSymbolDatabase(
+    lineInfo: newSeqOfCap[TinyLineInfo](xs.lineInfo.len),
+    sym: newSeqOfCap[PSym](xs.sym.len),
+    isDecl: newPackedBoolArray(),
+    caughtExceptions: newSeqOfCap[seq[PType]](xs.caughtExceptions.len),
+    caughtExceptionsSet: newPackedBoolArray(),
+    fileIndex: xs.fileIndex,
+    trackCaughtExceptions: xs.trackCaughtExceptions,
+    isSorted: false
+  )
+  var i = xs.lineInfo.high
+  while i >= 0:
+    let itm = xs.lineInfo[i]
+    var found = false
+    for res in result.lineInfo:
+      if res.exactEquals(itm):
+        found = true
+        break
+    if not found:
+      result.add(xs.getSymInfoPair(i))
+    dec i
+  result.reverse()
+
+proc findSymData(graph: ModuleGraph, trackPos: TLineInfo):
+    ref SymInfoPair =
+  let db = graph.fileSymbols(trackPos.fileIndex).deduplicateSymInfoPair
+  doAssert(db.fileIndex == trackPos.fileIndex)
+  for i in db.lineInfo.low..db.lineInfo.high:
+    if isTracked(db.lineInfo[i], TinyLineInfo(line: trackPos.line, col: trackPos.col), db.sym[i].name.s.len):
+      var res = db.getSymInfoPair(i)
+      new(result)
+      result[] = res
+      break
+
+func isInRange*(current, startPos, endPos: TinyLineInfo, tokenLen: int): bool =
+  result =
+    (current.line > startPos.line or (current.line == startPos.line and current.col>=startPos.col)) and
+    (current.line < endPos.line or (current.line == endPos.line and current.col <= endPos.col))
+
+proc findSymDataInRange(graph: ModuleGraph, startPos, endPos: TLineInfo):
+    seq[SymInfoPair] =
+  result = newSeq[SymInfoPair]()
+  let db = graph.fileSymbols(startPos.fileIndex).deduplicateSymInfoPair
+  for i in db.lineInfo.low..db.lineInfo.high:
+    if isInRange(db.lineInfo[i], TinyLineInfo(line: startPos.line, col: startPos.col), TinyLineInfo(line: endPos.line, col: endPos.col), db.sym[i].name.s.len):
+      result.add(db.getSymInfoPair(i))
+
+proc findSymData(graph: ModuleGraph, file: AbsoluteFile; line, col: int):
+    ref SymInfoPair =
+  let
+    fileIdx = fileInfoIdx(graph.config, file)
+    trackPos = newLineInfo(fileIdx, line, col)
+  result = findSymData(graph, trackPos)
+
+proc findSymDataInRange(graph: ModuleGraph, file: AbsoluteFile; startLine, startCol, endLine, endCol: int):
+    seq[SymInfoPair] =
+  let
+    fileIdx = fileInfoIdx(graph.config, file)
+    startPos = newLineInfo(fileIdx, startLine, startCol)
+    endPos = newLineInfo(fileIdx, endLine, endCol)
+  result = findSymDataInRange(graph, startPos, endPos)
+
+proc markDirtyIfNeeded(graph: ModuleGraph, file: string, originalFileIdx: FileIndex) =
+  let sha = $sha1.secureHashFile(file)
+  if graph.config.m.fileInfos[originalFileIdx.int32].hash != sha or graph.config.ideCmd in {ideSug, ideCon}:
+    myLog fmt "{file} changed compared to last compilation"
+    graph.markDirty originalFileIdx
+    graph.markClientsDirty originalFileIdx
+  else:
+    myLog fmt "No changes in file {file} compared to last compilation"
+
+proc suggestResult(graph: ModuleGraph, sym: PSym, info: TLineInfo,
+                   defaultSection = ideNone, endLine: uint16 = 0, endCol = 0) =
+  let section = if defaultSection != ideNone:
+                  defaultSection
+                elif sym.info.exactEquals(info):
+                  ideDef
+                else:
+                  ideUse
+  let suggest = symToSuggest(graph, sym, isLocal=false, section,
+                             info, 100, PrefixMatch.None, false, 0,
+                             endLine = endLine, endCol = endCol)
+  suggestResult(graph.config, suggest)
+
+proc suggestInlayHintResultType(graph: ModuleGraph, sym: PSym, info: TLineInfo,
+                   defaultSection = ideNone, endLine: uint16 = 0, endCol = 0) =
+  let section = if defaultSection != ideNone:
+                  defaultSection
+                elif sym.info.exactEquals(info):
+                  ideDef
+                else:
+                  ideUse
+  var suggestDef = symToSuggest(graph, sym, isLocal=false, section,
+                                info, 100, PrefixMatch.None, false, 0, true,
+                                endLine = endLine, endCol = endCol)
+  suggestDef.inlayHintInfo = suggestToSuggestInlayTypeHint(suggestDef)
+  suggestDef.section = ideInlayHints
+  if sym.kind == skForVar:
+    suggestDef.inlayHintInfo.allowInsert = false
+  suggestResult(graph.config, suggestDef)
+
+proc suggestInlayHintResultException(graph: ModuleGraph, sym: PSym, info: TLineInfo,
+                   defaultSection = ideNone, caughtExceptions: seq[PType], caughtExceptionsSet: bool, endLine: uint16 = 0, endCol = 0) =
+  if not caughtExceptionsSet:
+    return
+
+  if sym.kind == skParam and sfEffectsDelayed in sym.flags:
+    return
+
+  var raisesList: seq[PType] = @[getEbase(graph, info)]
+
+  let t = sym.typ
+  if not isNil(t) and not isNil(t.n) and t.n.len > 0 and t.n[0].len > exceptionEffects:
+    let effects = t.n[0]
+    if effects.kind == nkEffectList and effects.len == effectListLen:
+      let effs = effects[exceptionEffects]
+      if not isNil(effs):
+        raisesList = @[]
+        for eff in items(effs):
+          if not isNil(eff):
+            raisesList.add(eff.typ)
+
+  var propagatedExceptionList: seq[PType] = @[]
+  for re in raisesList:
+    var exceptionIsPropagated = true
+    for ce in caughtExceptions:
+      if isNil(ce) or safeInheritanceDiff(re, ce) <= 0:
+        exceptionIsPropagated = false
+        break
+    if exceptionIsPropagated:
+      propagatedExceptionList.add(re)
+
+  if propagatedExceptionList.len == 0:
+    return
+
+  let section = if defaultSection != ideNone:
+                  defaultSection
+                elif sym.info.exactEquals(info):
+                  ideDef
+                else:
+                  ideUse
+  var suggestDef = symToSuggest(graph, sym, isLocal=false, section,
+                                info, 100, PrefixMatch.None, false, 0, true,
+                                endLine = endLine, endCol = endCol)
+  suggestDef.inlayHintInfo = suggestToSuggestInlayExceptionHintLeft(suggestDef, propagatedExceptionList)
+  suggestDef.section = ideInlayHints
+  suggestResult(graph.config, suggestDef)
+  suggestDef.inlayHintInfo = suggestToSuggestInlayExceptionHintRight(suggestDef, propagatedExceptionList)
+  suggestResult(graph.config, suggestDef)
+
+const
+  # kinds for ideOutline and ideGlobalSymbols
+  searchableSymKinds = {skField, skEnumField, skIterator, skMethod, skFunc, skProc, skConverter, skTemplate}
+
+proc symbolEqual(left, right: PSym): bool =
+  # More relaxed symbol comparison
+  return left.info.exactEquals(right.info) and left.name == right.name
+
+proc findDef(n: PNode, line: uint16, col: int16): PNode =
+  if n.kind in {nkProcDef, nkIteratorDef, nkTemplateDef, nkMethodDef, nkMacroDef}:
+    if n.info.line == line:
+      return n
+  else:
+    for i in 0 ..< safeLen(n):
+      let res = findDef(n[i], line, col)
+      if res != nil: return res
+
+proc findByTLineInfo(trackPos: TLineInfo, infoPairs: SuggestFileSymbolDatabase):
+    ref SymInfoPair =
+  result = nil
+  if infoPairs.fileIndex == trackPos.fileIndex:
+    for i in infoPairs.lineInfo.low..infoPairs.lineInfo.high:
+      let s = infoPairs.getSymInfoPair(i)
+      if s.info.exactEquals trackPos:
+        new(result)
+        result[] = s
+        break
+
+proc outlineNode(graph: ModuleGraph, n: PNode, endInfo: TLineInfo, infoPairs: SuggestFileSymbolDatabase): bool =
+  proc checkSymbol(sym: PSym, info: TLineInfo): bool =
+    result = (sym.owner.kind in {skModule, skType} or sym.kind in {skProc, skMethod, skIterator, skTemplate, skType})
+
+  if n.kind == nkSym and n.sym.checkSymbol(n.info):
+    graph.suggestResult(n.sym, n.sym.info, ideOutline, endInfo.line, endInfo.col)
+    return true
+  elif n.kind == nkIdent:
+    let symData = findByTLineInfo(n.info, infoPairs)
+    if symData != nil and symData.sym.checkSymbol(symData.info):
+       let sym = symData.sym
+       graph.suggestResult(sym, sym.info, ideOutline, endInfo.line, endInfo.col)
+       return true
+
+proc handleIdentOrSym(graph: ModuleGraph, n: PNode, endInfo: TLineInfo, infoPairs: SuggestFileSymbolDatabase): bool =
+  for child in n:
+    if child.kind in {nkIdent, nkSym}:
+      if graph.outlineNode(child, endInfo, infoPairs):
+        return true
+    elif child.kind == nkPostfix:
+      if graph.handleIdentOrSym(child, endInfo, infoPairs):
+        return true
+
+proc iterateOutlineNodes(graph: ModuleGraph, n: PNode, infoPairs: SuggestFileSymbolDatabase) =
+  var matched = true
+  if n.kind == nkIdent:
+    let symData = findByTLineInfo(n.info, infoPairs)
+    if symData != nil and symData.sym.kind == skEnumField and symData.info.exactEquals(symData.sym.info):
+       let sym = symData.sym
+       graph.suggestResult(sym, sym.info, ideOutline, n.endInfo.line, n.endInfo.col)
+  elif (n.kind in {nkFuncDef, nkProcDef, nkTypeDef, nkMacroDef, nkTemplateDef, nkConverterDef, nkEnumFieldDef, nkConstDef}):
+    matched = handleIdentOrSym(graph, n, n.endInfo, infoPairs)
+  else:
+    matched = false
+
+  if n.kind != nkFormalParams:
+    for child in n:
+      graph.iterateOutlineNodes(child, infoPairs)
+
+proc calculateExpandRange(n: PNode, info: TLineInfo): TLineInfo =
+  if ((n.kind in {nkFuncDef, nkProcDef, nkIteratorDef, nkTemplateDef, nkMethodDef, nkConverterDef} and
+          n.info.exactEquals(info)) or
+         (n.kind in {nkCall, nkCommand} and n[0].info.exactEquals(info))):
+    result = n.endInfo
+  else:
+    for child in n:
+      result = child.calculateExpandRange(info)
+      if result != unknownLineInfo:
+        return result
+    result = unknownLineInfo
+
+proc executeNoHooksV3(cmd: IdeCmd, file: AbsoluteFile, dirtyfile: AbsoluteFile, line, col: int; tag: string,
+    graph: ModuleGraph) =
+  let conf = graph.config
+  conf.writelnHook = proc (s: string) = discard
+  conf.structuredErrorHook = proc (conf: ConfigRef; info: TLineInfo;
+                                   msg: string; sev: Severity) =
+    let suggest = Suggest(section: ideChk, filePath: toFullPath(conf, info),
+      line: toLinenumber(info), column: toColumn(info), doc: msg, forth: $sev)
+    graph.suggestErrors.mgetOrPut(info.fileIndex, @[]).add suggest
+
+  conf.ideCmd = cmd
+
+  myLog fmt "cmd: {cmd}, file: {file}[{line}:{col}], dirtyFile: {dirtyfile}, tag: {tag}"
+
+  var fileIndex: FileIndex
+
+  if not (cmd in {ideRecompile, ideGlobalSymbols}):
+    fileIndex = fileInfoIdx(conf, file)
+    msgs.setDirtyFile(
+      conf,
+      fileIndex,
+      if dirtyfile.isEmpty: AbsoluteFile"" else: dirtyfile)
+
+    if not dirtyfile.isEmpty:
+      graph.markDirtyIfNeeded(dirtyFile.string, fileInfoIdx(conf, file))
+
+  # these commands require fully compiled project
+  if cmd in {ideUse, ideDus, ideGlobalSymbols, ideChk, ideInlayHints} and graph.needsCompilation():
+    graph.recompilePartially()
+    # when doing incremental build for the project root we should make sure that
+    # everything is unmarked as no longer beeing dirty in case there is no
+    # longer reference to a particular module. E. g. A depends on B, B is marked
+    # as dirty and A loses B import.
+    graph.unmarkAllDirty()
+
+  # these commands require partially compiled project
+  elif cmd in {ideSug, ideCon, ideOutline, ideHighlight, ideDef, ideChkFile, ideType, ideDeclaration, ideExpand} and
+       (graph.needsCompilation(fileIndex) or cmd in {ideSug, ideCon}):
+    # for ideSug use v2 implementation
+    if cmd in {ideSug, ideCon}:
+      conf.m.trackPos = newLineInfo(fileIndex, line, col)
+      conf.m.trackPosAttached = false
+    else:
+      conf.m.trackPos = default(TLineInfo)
+      graph.recompilePartially(fileIndex)
+
+  case cmd
+  of ideDef:
+    let s = graph.findSymData(file, line, col)
+    if not s.isNil:
+      graph.suggestResult(s.sym, s.sym.info)
+  of ideType:
+    let s = graph.findSymData(file, line, col)
+    if not s.isNil:
+      let typeSym = s.sym.typ.sym
+      if typeSym != nil:
+        graph.suggestResult(typeSym, typeSym.info, ideType)
+      elif s.sym.typ.len != 0:
+        let genericType = s.sym.typ[0].sym
+        graph.suggestResult(genericType, genericType.info, ideType)
+  of ideUse, ideDus:
+    let symbol = graph.findSymData(file, line, col)
+    if not symbol.isNil:
+      var res: seq[SymInfoPair] = @[]
+      for s in graph.suggestSymbolsIter:
+        if s.sym.symbolEqual(symbol.sym):
+          res.add(s)
+      for s in res.deduplicateSymInfoPair():
+        graph.suggestResult(s.sym, s.info)
+  of ideHighlight:
+    let sym = graph.findSymData(file, line, col)
+    if not sym.isNil:
+      let fs = graph.fileSymbols(fileIndex)
+      var usages: seq[SymInfoPair] = @[]
+      for i in fs.lineInfo.low..fs.lineInfo.high:
+        if fs.sym[i] == sym.sym:
+          usages.add(fs.getSymInfoPair(i))
+      myLog fmt "Found {usages.len} usages in {file.string}"
+      for s in usages:
+        graph.suggestResult(s.sym, s.info)
+  of ideRecompile:
+    graph.recompileFullProject()
+  of ideChanged:
+    graph.markDirtyIfNeeded(file.string, fileIndex)
+  of ideSug, ideCon:
+    # ideSug/ideCon performs partial build of the file, thus mark it dirty for the
+    # future calls.
+    graph.markDirtyIfNeeded(file.string, fileIndex)
+    graph.recompilePartially(fileIndex) 
+    let m = graph.getModule fileIndex
+    incl m.flags, sfDirty 
+  of ideOutline:
+    let n = parseFile(fileIndex, graph.cache, graph.config)
+    graph.iterateOutlineNodes(n, graph.fileSymbols(fileIndex).deduplicateSymInfoPair)
+  of ideChk:
+    myLog fmt "Reporting errors for {graph.suggestErrors.len} file(s)"
+    for sug in graph.suggestErrorsIter:
+      suggestResult(graph.config, sug)
+  of ideChkFile:
+    let errors = graph.suggestErrors.getOrDefault(fileIndex, @[])
+    myLog fmt "Reporting {errors.len} error(s) for {file.string}"
+    for error in errors:
+      suggestResult(graph.config, error)
+  of ideGlobalSymbols:
+    var
+      counter = 0
+      res: seq[SymInfoPair] = @[]
+
+    for s in graph.suggestSymbolsIter:
+      if (sfGlobal in s.sym.flags or s.sym.kind in searchableSymKinds) and
+          s.sym.info == s.info:
+        if contains(s.sym.name.s, file.string):
+          inc counter
+          res = res.filterIt(not it.info.exactEquals(s.info))
+          res.add s
+          # stop after first 1000 matches...
+          if counter > 1000:
+            break
+
+    # ... then sort them by weight ...
+    res.sort() do (left, right: SymInfoPair) -> int:
+      let
+        leftString = left.sym.name.s
+        rightString = right.sym.name.s
+        leftIndex = leftString.find(file.string)
+        rightIndex = rightString.find(file.string)
+
+      if leftIndex == rightIndex:
+        result = cmp(toLowerAscii(leftString),
+                     toLowerAscii(rightString))
+      else:
+        result = cmp(leftIndex, rightIndex)
+
+    # ... and send first 100 results
+    if res.len > 0:
+      for i in 0 .. min(100, res.len - 1):
+        let s = res[i]
+        graph.suggestResult(s.sym, s.info)
+
+  of ideDeclaration:
+    let s = graph.findSymData(file, line, col)
+    if not s.isNil:
+      # find first mention of the symbol in the file containing the definition.
+      # It is either the definition or the declaration.
+      var first: SymInfoPair
+      let db = graph.fileSymbols(s.sym.info.fileIndex).deduplicateSymInfoPair
+      for i in db.lineInfo.low..db.lineInfo.high:
+        if s.sym.symbolEqual(db.sym[i]):
+          first = db.getSymInfoPair(i)
+          break
+
+      if s.info.exactEquals(first.info):
+        # we are on declaration, go to definition
+        graph.suggestResult(first.sym, first.sym.info, ideDeclaration)
+      else:
+        # we are on definition or usage, look for declaration
+        graph.suggestResult(first.sym, first.info, ideDeclaration)
+  of ideExpand:
+    var level: int = high(int)
+    let index = skipWhitespace(tag, 0);
+    let trimmed = substr(tag, index)
+    if not (trimmed == "" or trimmed == "all"):
+      discard parseInt(trimmed, level, 0)
+
+    conf.expandPosition = newLineInfo(fileIndex, line, col)
+    conf.expandLevels = level
+    conf.expandProgress = false
+    conf.expandNodeResult = ""
+
+    graph.markDirty fileIndex
+    graph.markClientsDirty fileIndex
+    graph.recompilePartially()
+    var suggest = Suggest()
+    suggest.section = ideExpand
+    suggest.version = 3
+    suggest.line = line
+    suggest.column = col
+    suggest.doc = graph.config.expandNodeResult
+    if suggest.doc != "":
+      let
+        n = parseFile(fileIndex, graph.cache, graph.config)
+        endInfo = n.calculateExpandRange(conf.expandPosition)
+
+      suggest.endLine = endInfo.line
+      suggest.endCol = endInfo.col
+
+    suggestResult(graph.config, suggest)
+
+    graph.markDirty fileIndex
+    graph.markClientsDirty fileIndex
+  of ideInlayHints:
+    myLog fmt "Executing inlayHints"
+    var endLine = 0
+    var endCol = -1
+    var i = 0
+    i += skipWhile(tag, seps, i)
+    i += parseInt(tag, endLine, i)
+    i += skipWhile(tag, seps, i)
+    i += parseInt(tag, endCol, i)
+    i += skipWhile(tag, seps, i)
+    var typeHints = true
+    var exceptionHints = false
+    while i <= tag.high:
+      var token: string
+      i += parseUntil(tag, token, seps, i)
+      i += skipWhile(tag, seps, i)
+      case token:
+      of "+typeHints":
+        typeHints = true
+      of "-typeHints":
+        typeHints = false
+      of "+exceptionHints":
+        exceptionHints = true
+      of "-exceptionHints":
+        exceptionHints = false
+      else:
+        myLog fmt "Discarding unknown inlay hint parameter {token}"
+
+    let s = graph.findSymDataInRange(file, line, col, endLine, endCol)
+    for q in s:
+      if typeHints and q.sym.kind in {skLet, skVar, skForVar, skConst} and q.isDecl and not q.sym.hasUserSpecifiedType:
+        graph.suggestInlayHintResultType(q.sym, q.info, ideInlayHints)
+      if exceptionHints and q.sym.kind in {skProc, skFunc, skMethod, skVar, skLet, skParam} and not q.isDecl:
+        graph.suggestInlayHintResultException(q.sym, q.info, ideInlayHints, caughtExceptions = q.caughtExceptions, caughtExceptionsSet = q.caughtExceptionsSet)
+  else:
+    myLog fmt "Discarding {cmd}"
 
+# v3 end
 when isMainModule:
   handleCmdLine(newIdentCache(), newConfigRef())
 else:
@@ -666,10 +1297,12 @@ else:
     proc mockCommand(graph: ModuleGraph) =
       retval = graph
       let conf = graph.config
+      conf.setCmd cmdIdeTools
+      defineSymbol(conf.symbols, $conf.backend)
       clearPasses(graph)
       registerPass graph, verbosePass
       registerPass graph, semPass
-      conf.cmd = cmdIdeTools
+
       wantMainModule(conf)
 
       if not fileExists(conf.projectFull):
@@ -677,8 +1310,7 @@ else:
 
       add(conf.searchPaths, conf.libpath)
 
-      # do not stop after the first error:
-      conf.errorMax = high(int)
+      conf.setErrorMaxHighMaybe
       # do not print errors, but log them
       conf.writelnHook = myLog
       conf.structuredErrorHook = nil
@@ -703,8 +1335,7 @@ else:
       conf = newConfigRef()
       self = NimProg(
         suggestMode: true,
-        processCmdLine: mockCmdLine,
-        mainCommand: mockCommand
+        processCmdLine: mockCmdLine
       )
     self.initDefinesProg(conf, "nimsuggest")
 
@@ -714,23 +1345,20 @@ else:
       conf.writelnHook = proc (msg: string) = discard
     # Find Nim's prefix dir.
     if nimPath == "":
-      let binaryPath = findExe("nim")
-      if binaryPath == "":
-        raise newException(IOError,
-            "Cannot find Nim standard library: Nim compiler not in PATH")
-      conf.prefixDir = AbsoluteDir binaryPath.splitPath().head.parentDir()
-      if not dirExists(conf.prefixDir / RelativeDir"lib"):
-        conf.prefixDir = AbsoluteDir""
+      conf.prefixDir = conf.getPrefixDir()
     else:
       conf.prefixDir = AbsoluteDir nimPath
 
     #msgs.writelnHook = proc (line: string) = log(line)
     myLog("START " & conf.projectFull.string)
 
-    discard self.loadConfigsAndRunMainCommand(cache, conf)
+    var graph = newModuleGraph(cache, conf)
+    if self.loadConfigsAndProcessCmdLine(cache, conf, graph):
+      mockCommand(graph)
     if gLogging:
+      myLog("Search paths:")
       for it in conf.searchPaths:
-        log(it.string)
+        myLog(" " & it.string)
 
     retval.doStopCompile = proc (): bool = false
     return NimSuggest(graph: retval, idle: 0, cachedMsgs: @[])
diff --git a/nimsuggest/nimsuggest.nimble b/nimsuggest/nimsuggest.nimble
index 3651e12bd..b3790e116 100644
--- a/nimsuggest/nimsuggest.nimble
+++ b/nimsuggest/nimsuggest.nimble
@@ -1,11 +1,8 @@
-[Package]
-name          = "nimsuggest"
-version       = "0.1.0"
-author        = "Andreas Rumpf"
-description   = "Tool for providing auto completion data for Nim source code."
-license       = "MIT"
+include "../lib/system/compilation.nim"
+version = $NimMajor & "." & $NimMinor & "." & $NimPatch
+author = "Andreas Rumpf"
+description = "Tool for providing auto completion data for Nim source code."
+license = "MIT"
+bin = @["nimsuggest"]
 
-bin = "nimsuggest"
-
-[Deps]
-Requires: "nim >= 0.11.2, compiler#head"
+requires "compiler >= 1.9.0" , "checksums"
diff --git a/nimsuggest/procmonitor.nim b/nimsuggest/procmonitor.nim
new file mode 100644
index 000000000..0f1ba1e0d
--- /dev/null
+++ b/nimsuggest/procmonitor.nim
@@ -0,0 +1,34 @@
+# Monitor a client process and shutdown the current process, if the client
+# process is found to be dead
+
+import os
+
+when defined(posix):
+  import posix_utils
+  import posix
+
+when defined(windows):
+  import winlean
+
+when defined(posix):
+  proc monitorClientProcessIdThreadProc(pid: int) {.thread.} =
+    while true:
+      sleep(1000)
+      try:
+        sendSignal(Pid(pid), 0)
+      except:
+        discard kill(Pid(getCurrentProcessId()), cint(SIGTERM))
+
+when defined(windows):
+  proc monitorClientProcessIdThreadProc(pid: int) {.thread.} =
+    var process = openProcess(SYNCHRONIZE, 0, DWORD(pid))
+    if process != 0:
+      discard waitForSingleObject(process, INFINITE)
+      discard closeHandle(process)
+    quit(0)
+
+var tid: Thread[int]
+
+proc hookProcMonitor*(pid: int) =
+  when defined(posix) or defined(windows):
+    createThread(tid, monitorClientProcessIdThreadProc, pid)
diff --git a/nimsuggest/sexp.nim b/nimsuggest/sexp.nim
index 654640309..03369ccb7 100644
--- a/nimsuggest/sexp.nim
+++ b/nimsuggest/sexp.nim
@@ -12,6 +12,11 @@
 import
   hashes, strutils, lexbase, streams, unicode, macros
 
+import std/private/decode_helpers
+
+when defined(nimPreviewSlimSystem):
+  import std/[assertions, formatfloat]
+
 type
   SexpEventKind* = enum  ## enumeration of all events that may occur when parsing
     sexpError,           ## an error occurred during parsing
@@ -113,14 +118,6 @@ proc errorMsgExpected*(my: SexpParser, e: string): string =
   ## other error messages
   result = "($1, $2) Error: $3" % [$getLine(my), $getColumn(my), e & " expected"]
 
-proc handleHexChar(c: char, x: var int): bool =
-  result = true # Success
-  case c
-  of '0'..'9': x = (x shl 4) or (ord(c) - ord('0'))
-  of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10)
-  of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10)
-  else: result = false # error
-
 proc parseString(my: var SexpParser): TTokKind =
   result = tkString
   var pos = my.bufpos + 1
@@ -290,41 +287,33 @@ proc raiseParseErr*(p: SexpParser, msg: string) {.noinline, noreturn.} =
   ## raises an `ESexpParsingError` exception.
   raise newException(SexpParsingError, errorMsgExpected(p, msg))
 
-proc newSString*(s: string): SexpNode {.procvar.}=
+proc newSString*(s: string): SexpNode =
   ## Creates a new `SString SexpNode`.
   result = SexpNode(kind: SString, str: s)
 
-proc newSStringMove(s: string): SexpNode =
-  result = SexpNode(kind: SString)
-  shallowCopy(result.str, s)
-
-proc newSInt*(n: BiggestInt): SexpNode {.procvar.} =
+proc newSInt*(n: BiggestInt): SexpNode =
   ## Creates a new `SInt SexpNode`.
   result = SexpNode(kind: SInt, num: n)
 
-proc newSFloat*(n: float): SexpNode {.procvar.} =
+proc newSFloat*(n: float): SexpNode =
   ## Creates a new `SFloat SexpNode`.
   result = SexpNode(kind: SFloat, fnum: n)
 
-proc newSNil*(): SexpNode {.procvar.} =
+proc newSNil*(): SexpNode =
   ## Creates a new `SNil SexpNode`.
   result = SexpNode(kind: SNil)
 
-proc newSCons*(car, cdr: SexpNode): SexpNode {.procvar.} =
+proc newSCons*(car, cdr: SexpNode): SexpNode =
   ## Creates a new `SCons SexpNode`
   result = SexpNode(kind: SCons, car: car, cdr: cdr)
 
-proc newSList*(): SexpNode {.procvar.} =
+proc newSList*(): SexpNode =
   ## Creates a new `SList SexpNode`
   result = SexpNode(kind: SList, elems: @[])
 
-proc newSSymbol*(s: string): SexpNode {.procvar.} =
+proc newSSymbol*(s: string): SexpNode =
   result = SexpNode(kind: SSymbol, symbol: s)
 
-proc newSSymbolMove(s: string): SexpNode =
-  result = SexpNode(kind: SSymbol)
-  shallowCopy(result.symbol, s)
-
 proc getStr*(n: SexpNode, default: string = ""): string =
   ## Retrieves the string value of a `SString SexpNode`.
   ##
@@ -415,7 +404,7 @@ macro convertSexp*(x: untyped): untyped =
   ## `%` for every element.
   result = toSexp(x)
 
-proc `==`* (a,b: SexpNode): bool =
+func `==`* (a, b: SexpNode): bool =
   ## Check two nodes for equality
   if a.isNil:
     if b.isNil: return true
@@ -602,8 +591,7 @@ proc parseSexp(p: var SexpParser): SexpNode =
   case p.tok
   of tkString:
     # we capture 'p.a' here, so we need to give it a fresh buffer afterwards:
-    result = newSStringMove(p.a)
-    p.a = ""
+    result = SexpNode(kind: SString, str: move p.a)
     discard getTok(p)
   of tkInt:
     result = newSInt(parseBiggestInt(p.a))
@@ -615,8 +603,7 @@ proc parseSexp(p: var SexpParser): SexpNode =
     result = newSNil()
     discard getTok(p)
   of tkSymbol:
-    result = newSSymbolMove(p.a)
-    p.a = ""
+    result = SexpNode(kind: SSymbol, symbol: move p.a)
     discard getTok(p)
   of tkParensLe:
     result = newSList()
diff --git a/nimsuggest/tester.nim b/nimsuggest/tester.nim
index d0870347e..9b9488348 100644
--- a/nimsuggest/tester.nim
+++ b/nimsuggest/tester.nim
@@ -2,8 +2,11 @@
 # Every test file can have a #[!]# comment that is deleted from the input
 # before 'nimsuggest' is invoked to ensure this token doesn't make a
 # crucial difference for Nim's parser.
+# When debugging, to run a single test, use for e.g.:
+# `nim r nimsuggest/tester.nim nimsuggest/tests/tsug_accquote.nim`
 
 import os, osproc, strutils, streams, re, sexp, net
+from sequtils import toSeq
 
 type
   Test = object
@@ -13,15 +16,17 @@ type
     disabled: bool
 
 const
-  curDir = when defined(windows): "" else: ""
   DummyEof = "!EOF!"
+  tpath = "nimsuggest/tests"
+  # we could also use `stdtest/specialpaths`
 
-template tpath(): untyped = getAppDir() / "tests"
+import std/compilesettings
 
 proc parseTest(filename: string; epcMode=false): Test =
   const cursorMarker = "#[!]#"
-  let nimsug = curDir & addFileExt("nimsuggest", ExeExt)
-  let libpath = findExe("nim").splitFile().dir /../ "lib"
+  let nimsug = "bin" / addFileExt("nimsuggest_testing", ExeExt)
+  doAssert nimsug.fileExists, nimsug
+  const libpath = querySetting(libPath)
   result.filename = filename
   result.dest = getTempDir() / extractFilename(filename)
   result.cmd = nimsug & " --tester " & result.dest
@@ -61,7 +66,7 @@ proc parseTest(filename: string; epcMode=false): Test =
       elif x.startsWith(">"):
         # since 'markers' here are not complete yet, we do the $substitutions
         # afterwards
-        result.script.add((x.substr(1).replaceWord("$path", tpath()), ""))
+        result.script.add((x.substr(1).replaceWord("$path", tpath).replaceWord("$file", filename), ""))
       elif x.len > 0:
         # expected output line:
         let x = x % ["file", filename, "lib", libpath]
@@ -102,7 +107,7 @@ proc parseCmd(c: string): seq[string] =
 proc edit(tmpfile: string; x: seq[string]) =
   if x.len != 3 and x.len != 4:
     quit "!edit takes two or three arguments"
-  let f = if x.len >= 4: tpath() / x[3] else: tmpfile
+  let f = if x.len >= 4: tpath / x[3] else: tmpfile
   try:
     let content = readFile(f)
     let newcontent = content.replace(x[1], x[2])
@@ -119,12 +124,12 @@ proc exec(x: seq[string]) =
 
 proc copy(x: seq[string]) =
   if x.len != 3: quit "!copy takes two arguments"
-  let rel = tpath()
+  let rel = tpath
   copyFile(rel / x[1], rel / x[2])
 
 proc del(x: seq[string]) =
   if x.len != 2: quit "!del takes one argument"
-  removeFile(tpath() / x[1])
+  removeFile(tpath / x[1])
 
 proc runCmd(cmd, dest: string): bool =
   result = cmd[0] == '!'
@@ -206,14 +211,19 @@ proc sexpToAnswer(s: SexpNode): string =
       result.add '\t'
       result.add file
       result.add '\t'
-      result.add line
+      result.addInt line
       result.add '\t'
-      result.add col
+      result.addInt col
       result.add '\t'
       result.add doc
       result.add '\t'
-      result.add a[8].getNum
-      if a.len >= 10:
+      result.addInt a[8].getNum
+      if a.len >= 11:
+        result.add '\t'
+        result.addInt a[9].getNum
+        result.add '\t'
+        result.addInt a[10].getNum
+      elif a.len >= 10:
         result.add '\t'
         result.add a[9].getStr
     result.add '\L'
@@ -224,8 +234,8 @@ proc doReport(filename, answer, resp: string; report: var string) =
     var hasDiff = false
     for i in 0..min(resp.len-1, answer.len-1):
       if resp[i] != answer[i]:
-        report.add "\n  Expected:  " & resp.substr(i, i+200)
-        report.add "\n  But got:   " & answer.substr(i, i+200)
+        report.add "\n  Expected:\n" & resp
+        report.add "\n  But got:\n" & answer
         hasDiff = true
         break
     if not hasDiff:
@@ -247,13 +257,15 @@ proc runEpcTest(filename: string): int =
   for cmd in s.startup:
     if not runCmd(cmd, s.dest):
       quit "invalid command: " & cmd
-  let epccmd = s.cmd.replace("--tester", "--epc --v2 --log")
+  let epccmd = if s.cmd.contains("--v3"):
+    s.cmd.replace("--tester", "--epc --log")
+  else:
+    s.cmd.replace("--tester", "--epc --v2 --log")
   let cl = parseCmdLine(epccmd)
   var p = startProcess(command=cl[0], args=cl[1 .. ^1],
                        options={poStdErrToStdOut, poUsePath,
                        poInteractive, poDaemon})
   let outp = p.outputStream
-  let inp = p.inputStream
   var report = ""
   var socket = newSocket()
   try:
@@ -267,17 +279,28 @@ proc runEpcTest(filename: string): int =
         os.sleep(50)
         inc i
       let a = outp.readAll().strip()
-    let port = parseInt(a)
+    var port: int
+    try:
+      port = parseInt(a)
+    except ValueError:
+      echo "Error parsing port number: " & a
+      echo outp.readAll()
+      quit 1
     socket.connect("localhost", Port(port))
+
     for req, resp in items(s.script):
       if not runCmd(req, s.dest):
         socket.sendEpcStr(req)
         let sx = parseSexp(socket.recvEpc())
         if not req.startsWith("mod "):
-          let answer = sexpToAnswer(sx)
+          let answer = if sx[2].kind == SNil: "" else: sexpToAnswer(sx)
           doReport(filename, answer, resp, report)
-  finally:
+
     socket.sendEpcStr "return arg"
+      # bugfix: this was in `finally` block, causing the original error to be
+      # potentially masked by another one in case `socket.sendEpcStr` raises
+      # (e.g. if socket couldn't connect in the 1st place)
+  finally:
     close(p)
   if report.len > 0:
     echo "==== EPC ========================================"
@@ -313,8 +336,12 @@ proc runTest(filename: string): int =
           answer.add '\L'
         doReport(filename, answer, resp, report)
   finally:
-    inp.writeLine("quit")
-    inp.flush()
+    try:
+      inp.writeLine("quit")
+      inp.flush()
+    except IOError, OSError:
+      # assume it's SIGPIPE, ie, the child already died
+      discard
     close(p)
   if report.len > 0:
     echo "==== STDIN ======================================"
@@ -326,11 +353,16 @@ proc main() =
   if os.paramCount() > 0:
     let x = os.paramStr(1)
     let xx = expandFilename x
+    # run only stdio when running single test
     failures += runTest(xx)
-    failures += runEpcTest(xx)
   else:
-    for x in walkFiles(getAppDir() / "tests/t*.nim"):
-      echo "Test ", x
+    let files = toSeq(walkFiles(tpath / "t*.nim"))
+    for i, x in files:
+      echo "$#/$# test: $#" % [$i, $files.len, x]
+      when defined(i386):
+        if x == "nimsuggest/tests/tmacro_highlight.nim":
+          echo "skipping" # workaround bug #17945
+          continue
       let xx = expandFilename x
       when not defined(windows):
         # XXX Windows IO redirection seems bonkers:
diff --git a/nimsuggest/tests/fixtures/mclass_macro.nim b/nimsuggest/tests/fixtures/mclass_macro.nim
new file mode 100644
index 000000000..cfca0bf3f
--- /dev/null
+++ b/nimsuggest/tests/fixtures/mclass_macro.nim
@@ -0,0 +1,164 @@
+
+import macros
+
+macro class*(head, body: untyped): untyped =
+  # The macro is immediate, since all its parameters are untyped.
+  # This means, it doesn't resolve identifiers passed to it.
+
+  var typeName, baseName: NimNode
+
+  # flag if object should be exported
+  var exported: bool
+
+  if head.kind == nnkInfix and head[0].kind == nnkIdent and $head[0] == "of":
+    # `head` is expression `typeName of baseClass`
+    # echo head.treeRepr
+    # --------------------
+    # Infix
+    #   Ident !"of"
+    #   Ident !"Animal"
+    #   Ident !"RootObj"
+    typeName = head[1]
+    baseName = head[2]
+
+  elif head.kind == nnkInfix and head[0].kind == nnkIdent and
+       $head[0] == "*" and head[2].kind == nnkPrefix and
+       head[2][0].kind == nnkIdent and $head[2][0] == "of":
+    # `head` is expression `typeName* of baseClass`
+    # echo head.treeRepr
+    # --------------------
+    # Infix
+    #   Ident !"*"
+    #   Ident !"Animal"
+    #   Prefix
+    #     Ident !"of"
+    #     Ident !"RootObj"
+    typeName = head[1]
+    baseName = head[2][1]
+    exported = true
+
+  else:
+    quit "Invalid node: " & head.lispRepr
+
+  # The following prints out the AST structure:
+  #
+  # import macros
+  # dumptree:
+  #   type X = ref object of Y
+  #     z: int
+  # --------------------
+  # StmtList
+  #   TypeSection
+  #     TypeDef
+  #       Ident !"X"
+  #       Empty
+  #       RefTy
+  #         ObjectTy
+  #           Empty
+  #           OfInherit
+  #             Ident !"Y"
+  #           RecList
+  #             IdentDefs
+  #               Ident !"z"
+  #               Ident !"int"
+  #               Empty
+
+  # create a type section in the result
+  result = newNimNode(nnkStmtList)
+  result.add(
+    if exported:
+      # mark `typeName` with an asterisk
+      quote do:
+        type `typeName`* = ref object of `baseName`
+    else:
+      quote do:
+        type `typeName` = ref object of `baseName`
+  )
+
+  # echo treeRepr(body)
+  # --------------------
+  # StmtList
+  #   VarSection
+  #     IdentDefs
+  #       Ident !"name"
+  #       Ident !"string"
+  #       Empty
+  #     IdentDefs
+  #       Ident !"age"
+  #       Ident !"int"
+  #       Empty
+  #   MethodDef
+  #     Ident !"vocalize"
+  #     Empty
+  #     Empty
+  #     FormalParams
+  #       Ident !"string"
+  #     Empty
+  #     Empty
+  #     StmtList
+  #       StrLit ...
+  #   MethodDef
+  #     Ident !"age_human_yrs"
+  #     Empty
+  #     Empty
+  #     FormalParams
+  #       Ident !"int"
+  #     Empty
+  #     Empty
+  #     StmtList
+  #       DotExpr
+  #         Ident !"this"
+  #         Ident !"age"
+
+  # var declarations will be turned into object fields
+  var recList = newNimNode(nnkRecList)
+
+  # expected name of constructor
+  let ctorName = newIdentNode("new" & $typeName)
+
+  # Iterate over the statements, adding `this: T`
+  # to the parameters of functions, unless the
+  # function is a constructor
+  for node in body.children:
+    case node.kind:
+
+      of nnkMethodDef, nnkProcDef:
+        # check if it is the ctor proc
+        if node.name.kind != nnkAccQuoted and node.name.basename == ctorName:
+          # specify the return type of the ctor proc
+          node.params[0] = typeName
+        else:
+          # inject `self: T` into the arguments
+          node.params.insert(1, newIdentDefs(ident("self"), typeName))
+        result.add(node)
+
+      of nnkVarSection:
+        # variables get turned into fields of the type.
+        for n in node.children:
+          recList.add(n)
+
+      else:
+        result.add(node)
+
+  # Inspect the tree structure:
+  #
+  # echo result.treeRepr
+  # --------------------
+  # StmtList
+  #   TypeSection
+  #     TypeDef
+  #       Ident !"Animal"
+  #       Empty
+  #       RefTy
+  #         ObjectTy
+  #           Empty
+  #           OfInherit
+  #             Ident !"RootObj"
+  #           Empty   <= We want to replace this
+  # MethodDef
+  # ...
+
+  result[0][0][2][0][2] = recList
+
+  # Lets inspect the human-readable version of the output
+  #echo repr(result)
diff --git a/nimsuggest/tests/dep_v1.nim b/nimsuggest/tests/fixtures/mdep_v1.nim
index eae230e85..eae230e85 100644
--- a/nimsuggest/tests/dep_v1.nim
+++ b/nimsuggest/tests/fixtures/mdep_v1.nim
diff --git a/nimsuggest/tests/dep_v2.nim b/nimsuggest/tests/fixtures/mdep_v2.nim
index ab39721c4..ab39721c4 100644
--- a/nimsuggest/tests/dep_v2.nim
+++ b/nimsuggest/tests/fixtures/mdep_v2.nim
diff --git a/nimsuggest/tests/fixtures/mfakeassert.nim b/nimsuggest/tests/fixtures/mfakeassert.nim
new file mode 100644
index 000000000..765831ba7
--- /dev/null
+++ b/nimsuggest/tests/fixtures/mfakeassert.nim
@@ -0,0 +1,5 @@
+# Template for testing defs
+
+template fakeAssert*(cond: untyped, msg: string = "") =
+  ## template to allow def lookup testing
+  if not cond: quit(1)
diff --git a/nimsuggest/tests/fixtures/minclude_import.nim b/nimsuggest/tests/fixtures/minclude_import.nim
new file mode 100644
index 000000000..5fa9e5142
--- /dev/null
+++ b/nimsuggest/tests/fixtures/minclude_import.nim
@@ -0,0 +1,15 @@
+# Creates an awkward set of dependencies between this, import, and include.
+# This pattern appears in the compiler, compiler/(sem|ast|semexprs).nim.
+
+import mfakeassert
+import minclude_types
+
+proc say*(g: Greet): string =
+  fakeAssert(true, "always works")
+  g.greeting & ", " & g.subject & "!"
+
+include minclude_include
+
+proc say*(): string =
+  fakeAssert(1 + 1 == 2, "math works")
+  say(create())
diff --git a/nimsuggest/tests/fixtures/minclude_include.nim b/nimsuggest/tests/fixtures/minclude_include.nim
new file mode 100644
index 000000000..23f9892cc
--- /dev/null
+++ b/nimsuggest/tests/fixtures/minclude_include.nim
@@ -0,0 +1,4 @@
+# this file is included and relies on imports within the include
+
+proc create*(greeting: string = "Hello", subject: string = "World"): Greet =
+  Greet(greeting: greeting, subject: subject)
diff --git a/nimsuggest/tests/fixtures/minclude_types.nim b/nimsuggest/tests/fixtures/minclude_types.nim
new file mode 100644
index 000000000..3e85ee540
--- /dev/null
+++ b/nimsuggest/tests/fixtures/minclude_types.nim
@@ -0,0 +1,6 @@
+# types used by minclude_* (import or include), to find with def in include
+
+type
+  Greet* = object
+    greeting*: string
+    subject*: string
\ No newline at end of file
diff --git a/nimsuggest/tests/fixtures/mstrutils.nim b/nimsuggest/tests/fixtures/mstrutils.nim
new file mode 100644
index 000000000..d6f25571b
--- /dev/null
+++ b/nimsuggest/tests/fixtures/mstrutils.nim
@@ -0,0 +1,19 @@
+import mfakeassert
+
+func rereplace*(s, sub: string; by: string = ""): string {.used.} =
+  ## competes for priority in suggestion, here first, but never used in test
+
+  fakeAssert(true, "always works")
+  result = by
+
+func replace*(s, sub: string; by: string = ""): string =
+  ## this is a test version of strutils.replace, it simply returns `by`
+
+  fakeAssert("".len == 0, "empty string is empty")
+  result = by
+
+func rerereplace*(s, sub: string; by: string = ""): string {.used.} =
+  ## isn't used and appears last, lowest priority
+
+  fakeAssert(false, "never works")
+  result = by
diff --git a/nimsuggest/tests/module_20265.nim b/nimsuggest/tests/module_20265.nim
new file mode 100644
index 000000000..24b7d10c9
--- /dev/null
+++ b/nimsuggest/tests/module_20265.nim
@@ -0,0 +1,6 @@
+type A* = tuple
+  a: int
+  b: int
+
+var x*: A = (a: 2, b: 10)
+var y* = (a: 2, b: 10)
diff --git a/nimsuggest/tests/t20265_1.nim b/nimsuggest/tests/t20265_1.nim
new file mode 100644
index 000000000..553b3d545
--- /dev/null
+++ b/nimsuggest/tests/t20265_1.nim
@@ -0,0 +1,8 @@
+discard """
+$nimsuggest --tester $file
+>sug $1
+sug;;skField;;a;;int;;*module_20265.nim;;6;;10;;"";;100;;None
+sug;;skField;;b;;int;;*module_20265.nim;;6;;16;;"";;100;;None
+"""
+import module_20265
+y.#[!]#
diff --git a/nimsuggest/tests/t20265_2.nim b/nimsuggest/tests/t20265_2.nim
new file mode 100644
index 000000000..33edf2d9a
--- /dev/null
+++ b/nimsuggest/tests/t20265_2.nim
@@ -0,0 +1,8 @@
+discard """
+$nimsuggest --tester $file
+>sug $1
+sug;;skField;;a;;int;;*module_20265.nim;;2;;2;;"";;100;;None
+sug;;skField;;b;;int;;*module_20265.nim;;3;;2;;"";;100;;None
+"""
+import module_20265
+x.#[!]#
diff --git a/nimsuggest/tests/t20440.nim b/nimsuggest/tests/t20440.nim
new file mode 100644
index 000000000..0456aa074
--- /dev/null
+++ b/nimsuggest/tests/t20440.nim
@@ -0,0 +1,7 @@
+when not defined(js):
+  {.fatal: "Crash".}
+echo 4
+
+discard """
+$nimsuggest --v3 --tester $file
+"""
diff --git a/nimsuggest/tests/t20440.nims b/nimsuggest/tests/t20440.nims
new file mode 100644
index 000000000..1336be3d4
--- /dev/null
+++ b/nimsuggest/tests/t20440.nims
@@ -0,0 +1 @@
+switch("backend", "js")
diff --git a/nimsuggest/tests/t21185.nim b/nimsuggest/tests/t21185.nim
new file mode 100644
index 000000000..bf5a0e3cc
--- /dev/null
+++ b/nimsuggest/tests/t21185.nim
@@ -0,0 +1,18 @@
+
+# Reduced case of 21185. Issue was first parameter being static
+proc foo(x: static[int]) = discard
+
+type
+  Person = object
+    name: string
+    age: int
+
+let p = Person()
+p.#[!]#
+
+discard """
+$nimsuggest --tester --v3 --maxresults:2 $file
+>sug $1
+sug;;skField;;age;;int;;$file;;8;;4;;"";;100;;None
+sug;;skField;;name;;string;;$file;;7;;4;;"";;100;;None
+"""
diff --git a/nimsuggest/tests/t22448.nim b/nimsuggest/tests/t22448.nim
new file mode 100644
index 000000000..8664bbbc3
--- /dev/null
+++ b/nimsuggest/tests/t22448.nim
@@ -0,0 +1,11 @@
+proc fn(a: static float) = discard
+proc fn(a: int) = discard
+
+let x = 1
+fn(x)
+
+discard """
+$nimsuggest --tester --v3 $file
+>chk $file
+chk;;skUnknown;;;;Hint;;*
+"""
diff --git a/nimsuggest/tests/tarrowcrash.nim b/nimsuggest/tests/tarrowcrash.nim
new file mode 100644
index 000000000..a303e88f5
--- /dev/null
+++ b/nimsuggest/tests/tarrowcrash.nim
@@ -0,0 +1,20 @@
+# issue #24179
+
+import sugar
+
+type
+    Parser[T] = object
+    
+proc eatWhile[T](p: Parser[T], predicate: T -> bool): seq[T] =
+    return @[]
+
+proc skipWs(p: Parser[char]) =
+    discard p.eatWhile((c: char) => c == 'a')
+#[!]#
+    
+discard """
+$nimsuggest --tester $file
+>chk $1
+chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tarrowcrash.nim [Processing]";;0
+chk;;skUnknown;;;;Hint;;$file;;11;;5;;"\'skipWs\' is declared but not used [XDeclaredButNotUsed]";;0
+"""
diff --git a/nimsuggest/tests/tchk1.nim b/nimsuggest/tests/tchk1.nim
index 2b60ed094..be6115c1c 100644
--- a/nimsuggest/tests/tchk1.nim
+++ b/nimsuggest/tests/tchk1.nim
@@ -15,14 +15,13 @@ proc main =
 
 #[!]#
 discard """
-disabled:true
 $nimsuggest --tester $file
 >chk $1
-chk;;skUnknown;;;;Hint;;???;;-1;;-1;;"tchk1 [Processing]";;0
-chk;;skUnknown;;;;Error;;$file;;12;;0;;"identifier expected, but found \'keyword template\'";;0
-chk;;skUnknown;;;;Error;;$file;;14;;0;;"complex statement requires indentation";;0
-chk;;skUnknown;;;;Error;;$file;;12;;0;;"implementation of \'foo\' expected";;0
+chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tchk1.nim [Processing]";;0
+chk;;skUnknown;;;;Error;;$file;;12;;0;;"identifier expected, but got \'keyword template\'";;0
+chk;;skUnknown;;;;Error;;$file;;14;;0;;"nestable statement requires indentation";;0
 chk;;skUnknown;;;;Error;;$file;;17;;0;;"invalid indentation";;0
+chk;;skUnknown;;;;Error;;$file;;12;;0;;"implementation of \'foo\' expected";;0
 chk;;skUnknown;;;;Hint;;$file;;12;;9;;"\'foo\' is declared but not used [XDeclaredButNotUsed]";;0
-chk;;skUnknown;;;;Hint;;$file;;14;;5;;"\'tchk1.main()[declared in tchk1.nim(14, 5)]\' is declared but not used [XDeclaredButNotUsed]";;0
+chk;;skUnknown;;;;Hint;;$file;;14;;5;;"\'main\' is declared but not used [XDeclaredButNotUsed]";;0
 """
diff --git a/nimsuggest/tests/tchk2.nim b/nimsuggest/tests/tchk2.nim
new file mode 100644
index 000000000..f5404368d
--- /dev/null
+++ b/nimsuggest/tests/tchk2.nim
@@ -0,0 +1,35 @@
+# bug #22794
+type O = object
+
+proc `=destroy`(x: O) = discard
+proc `=trace`(x: var O; env: pointer) = discard
+proc `=copy`(a: var O; b: O) = discard
+proc `=dup`(a: O): O {.nodestroy.} = a
+proc `=sink`(a: var O; b: O) = discard
+
+
+# bug #23316
+type SomeSturct = object
+
+proc `=destroy`(x: SomeSturct) =
+  echo "SomeSturct destroyed"
+
+# bug #23867
+type ObjStr = object
+  s: string
+
+let ostr = ObjStr() # <-- nimsuggest crashes
+discard ostr
+
+type ObjSeq = object
+  s: seq[int]
+
+let oseq = ObjSeq() # <-- nimsuggest crashes
+discard oseq
+
+#[!]#
+discard """
+$nimsuggest --tester $file
+>chk $1
+chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tchk2.nim [Processing]";;0
+"""
diff --git a/nimsuggest/tests/tchk_compiles.nim b/nimsuggest/tests/tchk_compiles.nim
new file mode 100644
index 000000000..c8a3daac4
--- /dev/null
+++ b/nimsuggest/tests/tchk_compiles.nim
@@ -0,0 +1,8 @@
+discard compiles(2 + "hello")
+
+#[!]#
+discard """
+$nimsuggest --tester $file
+>chk $1
+chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tchk_compiles.nim [Processing]";;0
+"""
diff --git a/nimsuggest/tests/tcon1.nim b/nimsuggest/tests/tcon1.nim
index 262dd5151..627e7f400 100644
--- a/nimsuggest/tests/tcon1.nim
+++ b/nimsuggest/tests/tcon1.nim
@@ -1,13 +1,43 @@
+## Test Invocation `con`text in various situations
+
+## various of this proc are used as the basis for these tests
 proc test(s: string; a: int) = discard
+
+## This overload should be used to ensure the lower airity `test` doesn't match
+proc test(s: string; a: string, b: int) = discard
+
+## similar signature but different name to ensure `con` doesn't get greedy
 proc testB(a, b: string) = discard
+
+# with a param already specified
 test("hello here", #[!]#)
+
+# as first param
 testB(#[!]#
 
+# dot expressions
+"from behind".test(#[!]#
+
+# two params matched, so disqualify the lower airity `test`
+# TODO: this doesn't work, because dot exprs, overloads, etc aren't currently
+#       handled by suggest.suggestCall. sigmatch.partialMatch by way of
+#       sigmatch.matchesAux. Doesn't use the operand before the dot as part of
+#       the formal parameters. Changing this is tricky because it's used by
+#       the proper compilation sem pass and that's a big change all in one go.
+"and again".test("more", #[!]#
+
 
 discard """
 $nimsuggest --tester $file
 >con $1
-con;;skProc;;tcon1.test;;proc (s: string, a: int);;$file;;1;;5;;"";;100
+con;;skProc;;tcon1.test;;proc (s: string, a: int);;$file;;4;;5;;"";;100
+con;;skProc;;tcon1.test;;proc (s: string, a: string, b: int);;$file;;7;;5;;"";;100
 >con $2
-con;;skProc;;tcon1.testB;;proc (a: string, b: string);;$file;;2;;5;;"";;100
+con;;skProc;;tcon1.testB;;proc (a: string, b: string);;$file;;10;;5;;"";;100
+>con $3
+con;;skProc;;tcon1.test;;proc (s: string, a: string, b: int);;$file;;7;;5;;"";;100
+con;;skProc;;tcon1.test;;proc (s: string, a: int);;$file;;4;;5;;"";;100
+>con $4
+con;;skProc;;tcon1.test;;proc (s: string, a: int);;$file;;4;;5;;"";;100
+con;;skProc;;tcon1.test;;proc (s: string, a: string, b: int);;$file;;7;;5;;"";;100
 """
diff --git a/nimsuggest/tests/tconcept1.nim b/nimsuggest/tests/tconcept1.nim
new file mode 100644
index 000000000..d81cd8120
--- /dev/null
+++ b/nimsuggest/tests/tconcept1.nim
@@ -0,0 +1,12 @@
+SomeNumber = concept
+
+#[!]#
+discard """
+$nimsuggest --tester $file
+>chk $1
+chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tconcept1.nim [Processing]";;0
+chk;;skUnknown;;;;Error;;$file;;1;;13;;"the \'concept\' keyword is only valid in \'type\' sections";;0
+chk;;skUnknown;;;;Error;;$file;;1;;13;;"invalid indentation";;0
+chk;;skUnknown;;;;Error;;$file;;1;;13;;"expression expected, but found \'keyword concept\'";;0
+chk;;skUnknown;;;;Error;;$file;;1;;0;;"\'SomeNumber\' cannot be assigned to";;0
+"""
diff --git a/nimsuggest/tests/tconcept2.nim b/nimsuggest/tests/tconcept2.nim
new file mode 100644
index 000000000..7f7d147f5
--- /dev/null
+++ b/nimsuggest/tests/tconcept2.nim
@@ -0,0 +1,15 @@
+  SomeNumber = concept a, type T
+    a.int is int
+    int.to(T) is type(a)
+
+#[!]#
+discard """
+$nimsuggest --tester $file
+>chk $1
+chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tconcept2.nim [Processing]";;0
+chk;;skUnknown;;;;Error;;$file;;1;;2;;"invalid indentation";;0
+chk;;skUnknown;;;;Error;;$file;;1;;15;;"the \'concept\' keyword is only valid in \'type\' sections";;0
+chk;;skUnknown;;;;Error;;$file;;1;;15;;"invalid indentation";;0
+chk;;skUnknown;;;;Error;;$file;;1;;15;;"expression expected, but found \'keyword concept\'";;0
+chk;;skUnknown;;;;Error;;$file;;1;;2;;"\'SomeNumber\' cannot be assigned to";;0
+"""
diff --git a/nimsuggest/tests/tdef1.nim b/nimsuggest/tests/tdef1.nim
index 2cd040ea1..49265bbc1 100644
--- a/nimsuggest/tests/tdef1.nim
+++ b/nimsuggest/tests/tdef1.nim
@@ -1,12 +1,14 @@
 discard """
 $nimsuggest --tester $file
 >def $1
-def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, locks: 0.};;$file;;9;;5;;"Return hello";;100
->def $1
-def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, locks: 0.};;$file;;9;;5;;"Return hello";;100
+def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;11;;5;;"Return hello";;100
+>def $2
+def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;11;;5;;"Return hello";;100
+>def $2
+def;;skProc;;tdef1.hello;;proc (): string{.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;11;;5;;"Return hello";;100
 """
 
-proc hello(): string =
+proc hel#[!]#lo(): string =
   ## Return hello
   "Hello"
 
diff --git a/nimsuggest/tests/tdef2.nim b/nimsuggest/tests/tdef2.nim
new file mode 100644
index 000000000..299b83a3d
--- /dev/null
+++ b/nimsuggest/tests/tdef2.nim
@@ -0,0 +1,13 @@
+# Test def with template and boundaries for the cursor
+
+import fixtures/mstrutils
+
+discard """
+$nimsuggest --tester $file
+>def $path/fixtures/mstrutils.nim:6:4
+def;;skTemplate;;mfakeassert.fakeAssert;;template (cond: untyped, msg: string);;*fixtures/mfakeassert.nim;;3;;9;;"template to allow def lookup testing";;100
+>def $path/fixtures/mstrutils.nim:12:3
+def;;skTemplate;;mfakeassert.fakeAssert;;template (cond: untyped, msg: string);;*fixtures/mfakeassert.nim;;3;;9;;"template to allow def lookup testing";;100
+>def $path/fixtures/mstrutils.nim:18:11
+def;;skTemplate;;mfakeassert.fakeAssert;;template (cond: untyped, msg: string);;*fixtures/mfakeassert.nim;;3;;9;;"template to allow def lookup testing";;100
+"""
diff --git a/nimsuggest/tests/tdef_forward.nim b/nimsuggest/tests/tdef_forward.nim
new file mode 100644
index 000000000..9bdd8b21d
--- /dev/null
+++ b/nimsuggest/tests/tdef_forward.nim
@@ -0,0 +1,13 @@
+discard """
+$nimsuggest --tester $file
+>def $1
+def;;skProc;;tdef_forward.hello;;proc (): string;;$file;;8;;5;;"";;100
+def;;skProc;;tdef_forward.hello;;proc (): string;;$file;;12;;5;;"";;100
+"""
+
+proc hello(): string
+
+hel#[!]#lo()
+
+proc hello(): string =
+  "Hello"
diff --git a/nimsuggest/tests/tdef_let.nim b/nimsuggest/tests/tdef_let.nim
new file mode 100644
index 000000000..3e9456d2f
--- /dev/null
+++ b/nimsuggest/tests/tdef_let.nim
@@ -0,0 +1,7 @@
+discard """
+$nimsuggest --tester $file
+>def $1
+def;;skLet;;tdef_let.intVar;;int;;$file;;7;;4;;"";;100
+"""
+
+let int#[!]#Var = 10
diff --git a/nimsuggest/tests/tdot3.nim b/nimsuggest/tests/tdot3.nim
index 15fc1cd1c..30dd60591 100644
--- a/nimsuggest/tests/tdot3.nim
+++ b/nimsuggest/tests/tdot3.nim
@@ -9,14 +9,14 @@ proc main(f: Foo) =
 # this way, the line numbers more often stay the same
 
 discard """
-!copy dep_v1.nim dep.nim
+!copy fixtures/mdep_v1.nim dep.nim
 $nimsuggest --tester $file
 >sug $1
 sug;;skField;;x;;int;;*dep.nim;;8;;4;;"";;100;;None
 sug;;skField;;y;;int;;*dep.nim;;8;;8;;"";;100;;None
 sug;;skProc;;tdot3.main;;proc (f: Foo);;$file;;5;;5;;"";;100;;None
 
-!copy dep_v2.nim dep.nim
+!copy fixtures/mdep_v2.nim dep.nim
 >mod $path/dep.nim
 >sug $1
 sug;;skField;;x;;int;;*dep.nim;;8;;4;;"";;100;;None
diff --git a/nimsuggest/tests/tdot4.nim b/nimsuggest/tests/tdot4.nim
index 762534310..f2c6c765f 100644
--- a/nimsuggest/tests/tdot4.nim
+++ b/nimsuggest/tests/tdot4.nim
@@ -1,17 +1,21 @@
-discard """
-disabled:true
-$nimsuggest --tester --maxresults:2 $file
->sug $1
-sug;;skProc;;tdot4.main;;proc (inp: string): string;;$file;;10;;5;;"";;100;;None
-sug;;skProc;;strutils.replace;;proc (s: string, sub: string, by: string): string{.noSideEffect, gcsafe, locks: 0.};;$lib/pure/strutils.nim;;1506;;5;;"Replaces `sub` in `s` by the string `by`.";;100;;None
-"""
+# Test that already used suggestions are prioritized
 
-import strutils
+from system import string, echo
+import fixtures/mstrutils
 
 proc main(inp: string): string =
   # use replace here and see if it occurs in the result, it should gain
   # priority:
   result = inp.replace(" ", "a").replace("b", "c")
 
-
 echo "string literal here".#[!]#
+
+# priority still tested, but limit results to avoid failures from other output
+discard """
+$nimsuggest --tester --maxresults:2 $file
+>sug $1
+sug;;skProc;;tdot4.main;;proc (inp: string): string;;$file;;6;;5;;"";;100;;None
+sug;;skFunc;;mstrutils.replace;;proc (s: string, sub: string, by: string): string{.noSideEffect, gcsafe, raises: <inferred> [].};;*fixtures/mstrutils.nim;;9;;5;;"this is a test version of strutils.replace, it simply returns `by`";;100;;None
+"""
+
+# TODO - determine appropriate behaviour for further suggest output and test it
diff --git a/nimsuggest/tests/tenum_field.nim b/nimsuggest/tests/tenum_field.nim
new file mode 100644
index 000000000..4ceb3e021
--- /dev/null
+++ b/nimsuggest/tests/tenum_field.nim
@@ -0,0 +1,17 @@
+discard """
+$nimsuggest --tester $file
+>sug $1
+>sug $2
+sug;;skConst;;tenum_field.BarFoo;;int literal(1);;$file;;10;;6;;"";;100;;Prefix
+"""
+
+proc something() = discard
+
+const BarFoo = 1
+
+type
+  Foo = enum
+    # Test that typing the name doesn't give suggestions
+    somethi#[!]#
+    # Test that the right hand side still gets suggestions
+    another = BarFo#[!]#
diff --git a/nimsuggest/tests/tfatal1.nim b/nimsuggest/tests/tfatal1.nim
new file mode 100644
index 000000000..19778f22e
--- /dev/null
+++ b/nimsuggest/tests/tfatal1.nim
@@ -0,0 +1,15 @@
+{.warning: "I'm a warning!".}
+{.error: "I'm an error!".}
+{.fatal: "I'm a fatal error!".}
+{.error: "I'm an error after fatal error!".}
+
+#[!]#
+discard """
+$nimsuggest --tester $file
+>chk $1
+chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/tfatal1.nim [Processing]";;0
+chk;;skUnknown;;;;Warning;;$file;;1;;9;;"I\'m a warning! [User]";;0
+chk;;skUnknown;;;;Error;;$file;;2;;7;;"I\'m an error!";;0
+chk;;skUnknown;;;;Error;;$file;;3;;7;;"fatal error: I\'m a fatal error!";;0
+chk;;skUnknown;;;;Error;;$file;;4;;7;;"I\'m an error after fatal error!";;0
+"""
diff --git a/nimsuggest/tests/tgeneric_highlight.nim b/nimsuggest/tests/tgeneric_highlight.nim
index 334323613..c7291d08b 100644
--- a/nimsuggest/tests/tgeneric_highlight.nim
+++ b/nimsuggest/tests/tgeneric_highlight.nim
@@ -7,14 +7,7 @@ $nimsuggest --tester $file
 >highlight $1
 highlight;;skType;;1;;7;;3
 highlight;;skProc;;1;;0;;6
-highlight;;skProc;;1;;0;;6
-highlight;;skType;;1;;7;;3
-highlight;;skProc;;1;;0;;6
-highlight;;skType;;2;;14;;3
-highlight;;skProc;;2;;7;;6
-highlight;;skProc;;2;;7;;6
 highlight;;skType;;2;;14;;3
 highlight;;skProc;;2;;7;;6
-highlight;;skTemplate;;3;;0;;8
 highlight;;skType;;3;;9;;3
 """
diff --git a/nimsuggest/tests/tgenerics.nim b/nimsuggest/tests/tgenerics.nim
new file mode 100644
index 000000000..7f490321c
--- /dev/null
+++ b/nimsuggest/tests/tgenerics.nim
@@ -0,0 +1,18 @@
+type
+  Hello[T] = object
+    value: T
+
+proc printHelloValue[T](hello: Hello[T]) =
+  echo hello.value
+
+proc main() =
+  let a = Hello[float]()
+  p#[!]#rintHelloValue(a)
+
+main()
+
+discard """
+$nimsuggest --tester $file
+>def $1
+def;;skProc;;tgenerics.printHelloValue;;proc (hello: Hello[printHelloValue.T]);;$file;;5;;5;;"";;100
+"""
diff --git a/nimsuggest/tests/tic.nim b/nimsuggest/tests/tic.nim
new file mode 100644
index 000000000..26e644f83
--- /dev/null
+++ b/nimsuggest/tests/tic.nim
@@ -0,0 +1,20 @@
+import std/[appdirs, assertions, cmdline, compilesettings, decls, 
+  dirs, editdistance, effecttraits, enumerate, enumutils, envvars, 
+  exitprocs, files, formatfloat, genasts, importutils, 
+  isolation, jsonutils, logic, monotimes, objectdollar, 
+  oserrors, outparams, packedsets, paths, private, setutils, sha1, 
+  socketstreams, stackframes, staticos, strbasics, symlinks, syncio, 
+  sysatomics, sysrand, tasks, tempfiles, time_t, typedthreads, varints, 
+  vmutils, widestrs, with, wordwrap, wrapnils]
+
+proc test(a: string, b:string) = discard
+proc test(a: int) = discard
+
+test(#[!]#
+
+discard """
+$nimsuggest --v3 --ic:off --tester $file 
+>con $1
+con;;skProc;;tic.test;;proc (a: string, b: string);;$file;;10;;5;;"";;100
+con;;skProc;;tic.test;;proc (a: int);;$file;;11;;5;;"";;100
+"""
\ No newline at end of file
diff --git a/nimsuggest/tests/timport_highlight.nim b/nimsuggest/tests/timport_highlight.nim
new file mode 100644
index 000000000..043f87d98
--- /dev/null
+++ b/nimsuggest/tests/timport_highlight.nim
@@ -0,0 +1,12 @@
+import std/paths
+import json as J
+import std/[os,streams]#[!]#
+
+discard """
+$nimsuggest --tester $file
+>highlight $1
+highlight;;skModule;;1;;11;;5
+highlight;;skModule;;2;;7;;4
+highlight;;skModule;;3;;12;;2
+highlight;;skModule;;3;;15;;7
+"""
diff --git a/nimsuggest/tests/tinclude.nim b/nimsuggest/tests/tinclude.nim
index 0fda43911..f5cbabf05 100644
--- a/nimsuggest/tests/tinclude.nim
+++ b/nimsuggest/tests/tinclude.nim
@@ -1,8 +1,25 @@
+# import that has an include:
+# * def calls must work into and out of includes
+# * outline calls on the import must show included members
+import fixtures/minclude_import
+
+proc go() =
+  discard create().say()
+
+go()
+
 discard """
-disabled:true
-$nimsuggest --tester compiler/nim.nim
->def compiler/semexprs.nim:25:50
-def;;skType;;ast.PSym;;PSym;;*ast.nim;;707;;2;;"";;100
->def compiler/semexprs.nim:25:50
-def;;skType;;ast.PSym;;PSym;;*ast.nim;;707;;2;;"";;100
+$nimsuggest --tester $file
+>def $path/tinclude.nim:7:14
+def;;skProc;;minclude_import.create;;proc (greeting: string, subject: string): Greet{.noSideEffect, gcsafe, raises: <inferred> [].};;*fixtures/minclude_include.nim;;3;;5;;"";;100
+>def $path/fixtures/minclude_include.nim:3:71
+def;;skType;;minclude_types.Greet;;Greet;;*fixtures/minclude_types.nim;;4;;2;;"";;100
+>def $path/fixtures/minclude_include.nim:3:71
+def;;skType;;minclude_types.Greet;;Greet;;*fixtures/minclude_types.nim;;4;;2;;"";;100
+>outline $path/fixtures/minclude_import.nim
+outline;;skProc;;minclude_import.say;;*fixtures/minclude_import.nim;;7;;5;;"";;100
+outline;;skProc;;minclude_import.create;;*fixtures/minclude_include.nim;;3;;5;;"";;100
+outline;;skProc;;minclude_import.say;;*fixtures/minclude_import.nim;;13;;5;;"";;100
 """
+
+# TODO test/fix if the first `def` is not first or repeated we get no results
diff --git a/nimsuggest/tests/top_highlight.nim b/nimsuggest/tests/top_highlight.nim
new file mode 100644
index 000000000..c3f6bb61d
--- /dev/null
+++ b/nimsuggest/tests/top_highlight.nim
@@ -0,0 +1,11 @@
+import json
+
+%*{}#[!]#
+
+discard """
+$nimsuggest --tester $file
+>highlight $1
+highlight;;skModule;;1;;7;;4
+highlight;;skMacro;;3;;0;;2
+highlight;;skMacro;;3;;0;;2
+"""
diff --git a/nimsuggest/tests/tqualified_highlight.nim b/nimsuggest/tests/tqualified_highlight.nim
index 3b521ecc1..b83669e72 100644
--- a/nimsuggest/tests/tqualified_highlight.nim
+++ b/nimsuggest/tests/tqualified_highlight.nim
@@ -10,5 +10,5 @@ highlight;;skProc;;1;;7;;4
 highlight;;skTemplate;;2;;7;;4
 highlight;;skTemplate;;2;;7;;4
 highlight;;skTemplate;;2;;7;;4
-highlight;;skProc;;3;;8;;1
+highlight;;skFunc;;3;;8;;1
 """
diff --git a/nimsuggest/tests/tstrutils.nim b/nimsuggest/tests/tstrutils.nim
deleted file mode 100644
index 9462c3d99..000000000
--- a/nimsuggest/tests/tstrutils.nim
+++ /dev/null
@@ -1,10 +0,0 @@
-discard """
-disabled:true
-$nimsuggest --tester lib/pure/strutils.nim
->def lib/pure/strutils.nim:2529:6
-def;;skTemplate;;system.doAssert;;proc (cond: bool, msg: string): typed;;*/lib/system.nim;;*;;9;;"same as `assert` but is always turned on and not affected by the\x0A``--assertions`` command line switch.";;100
-"""
-
-# Line 2529 in strutils.nim is doAssert and this is unlikely to change
-# soon since there are a whole lot of doAsserts there.
-
diff --git a/nimsuggest/tests/tsug_enum.nim b/nimsuggest/tests/tsug_enum.nim
new file mode 100644
index 000000000..97a225f16
--- /dev/null
+++ b/nimsuggest/tests/tsug_enum.nim
@@ -0,0 +1,18 @@
+## suggestions for enums
+
+type
+  LogLevel {.pure.} = enum
+    debug, log, warn, error
+  
+  FooBar = enum
+    fbFoo, fbBar
+
+echo fbFoo, fbBar
+
+echo LogLevel.deb#[!]#
+
+discard """
+$nimsuggest --tester $file
+>sug $1
+sug;;skEnumField;;debug;;LogLevel;;*nimsuggest/tests/tsug_enum.nim;;5;;4;;"";;100;;Prefix
+"""
\ No newline at end of file
diff --git a/nimsuggest/tests/tsug_pragmas.nim b/nimsuggest/tests/tsug_pragmas.nim
new file mode 100644
index 000000000..ce9c4e8f8
--- /dev/null
+++ b/nimsuggest/tests/tsug_pragmas.nim
@@ -0,0 +1,40 @@
+template fooBar1() {.pragma.}
+proc fooBar2() = discard
+macro fooBar3(x: untyped) = discard
+{.pragma: fooBar4 fooBar3.}
+
+proc test1() {.fooBar#[!]#.} = discard
+
+var test2 {.fooBar#[!]#.} = 9
+
+type
+  Person {.fooBar#[!]#.} = object
+    hello {.fooBar#[!]#.}: string
+  Callback = proc () {.fooBar#[!]#.}
+
+# Check only macros/templates/pragmas are suggested
+discard """
+$nimsuggest --tester $file
+>sug $1
+sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix
+sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix
+sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix
+>sug $2
+sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix
+sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix
+sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix
+>sug $3
+sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix
+sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix
+sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix
+>sug $4
+sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix
+sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix
+sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix
+>sug $5
+sug;;skTemplate;;fooBar4;;;;$file;;4;;8;;"";;100;;Prefix
+sug;;skTemplate;;tsug_pragmas.fooBar1;;template ();;$file;;1;;9;;"";;100;;Prefix
+sug;;skMacro;;tsug_pragmas.fooBar3;;macro (x: untyped){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;3;;6;;"";;50;;Prefix
+"""
+
+
diff --git a/nimsuggest/tests/tsug_recursive.nim b/nimsuggest/tests/tsug_recursive.nim
new file mode 100644
index 000000000..97ee5ca01
--- /dev/null
+++ b/nimsuggest/tests/tsug_recursive.nim
@@ -0,0 +1,8 @@
+discard """
+$nimsuggest --tester $file
+>sug $1
+sug;;skProc;;tsug_recursive.fooBar;;proc ();;$file;;7;;5;;"";;100;;Prefix
+"""
+
+proc fooBar() =
+  fooBa#[!]#
diff --git a/nimsuggest/tests/tsug_regression.nim b/nimsuggest/tests/tsug_regression.nim
index 1b23da806..2aff3fe94 100644
--- a/nimsuggest/tests/tsug_regression.nim
+++ b/nimsuggest/tests/tsug_regression.nim
@@ -7,23 +7,28 @@ type X = object
 proc main =
   # bug #52
   var
-    set0 = initSet[int]()
-    set1 = initSet[X]()
-    set2 = initSet[ref int]()
+    set0 = initHashSet[int]()
+    set1 = initHashSet[X]()
+    set2 = initHashSet[ref int]()
 
     map0 = initTable[int, int]()
     map1 = initOrderedTable[string, int]()
     cfg = loadConfig("file")
   map0.#[!]#
 
+# the maxresults are limited as it seems there is sort or some other
+# instability that causes the suggestions to slightly differ between 32 bit
+# and 64 bit versions of nimsuggest
+
 discard """
 disabled:true
-$nimsuggest --tester $file
+$nimsuggest --tester --maxresults:4 $file
 >sug $1
-sug;;skProc;;tables.getOrDefault;;proc (t: Table[getOrDefault.A, getOrDefault.B], key: A): B;;$lib/pure/collections/tables.nim;;178;;5;;"";;100;;None
-sug;;skProc;;tables.hasKey;;proc (t: Table[hasKey.A, hasKey.B], key: A): bool;;$lib/pure/collections/tables.nim;;233;;5;;"returns true iff `key` is in the table `t`.";;100;;None
-sug;;skProc;;tables.add;;proc (t: var Table[add.A, add.B], key: A, val: B);;$lib/pure/collections/tables.nim;;309;;5;;"puts a new (key, value)-pair into `t` even if ``t[key]`` already exists.";;100;;None
-sug;;skIterator;;tables.allValues;;iterator (t: Table[allValues.A, allValues.B], key: A): B{.inline.};;$lib/pure/collections/tables.nim;;225;;9;;"iterates over any value in the table `t` that belongs to the given `key`.";;100;;None
-sug;;skProc;;tables.clear;;proc (t: var Table[clear.A, clear.B]);;$lib/pure/collections/tables.nim;;121;;5;;"Resets the table so that it is empty.";;100;;None
-*
+sug;;skProc;;tables.hasKey;;proc (t: Table[hasKey.A, hasKey.B], key: A): bool;;*/lib/pure/collections/tables.nim;;374;;5;;"Returns true *";;100;;None
+sug;;skProc;;tables.clear;;proc (t: var Table[clear.A, clear.B]);;*/lib/pure/collections/tables.nim;;567;;5;;"Resets the table so that it is empty*";;100;;None
+sug;;skProc;;tables.contains;;proc (t: Table[contains.A, contains.B], key: A): bool;;*/lib/pure/collections/tables.nim;;*;;5;;"Alias of *";;100;;None
+sug;;skProc;;tables.del;;proc (t: var Table[del.A, del.B], key: A);;*/lib/pure/collections/tables.nim;;*;;5;;"*";;100;;None
 """
+
+# TODO enable the tests
+# TODO: test/fix suggestion sorting - deprecated suggestions should rank lower
diff --git a/nimsuggest/tests/tsug_template.nim b/nimsuggest/tests/tsug_template.nim
index 0f258fcc6..da494d279 100644
--- a/nimsuggest/tests/tsug_template.nim
+++ b/nimsuggest/tests/tsug_template.nim
@@ -6,7 +6,7 @@ tmp#[!]#
 discard """
 $nimsuggest --tester $file
 >sug $1
-sug;;skMacro;;tsug_template.tmpb;;macro (){.noSideEffect, gcsafe, locks: 0.};;$file;;2;;6;;"";;0;;Prefix
-sug;;skConverter;;tsug_template.tmpc;;converter ();;$file;;3;;10;;"";;0;;Prefix
-sug;;skTemplate;;tsug_template.tmpa;;template ();;$file;;1;;9;;"";;0;;Prefix
+sug;;skMacro;;tsug_template.tmpb;;macro (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;2;;6;;"";;100;;Prefix
+sug;;skConverter;;tsug_template.tmpc;;converter ();;$file;;3;;10;;"";;100;;Prefix
+sug;;skTemplate;;tsug_template.tmpa;;template ();;$file;;1;;9;;"";;100;;Prefix
 """
diff --git a/nimsuggest/tests/tsug_typedecl.nim b/nimsuggest/tests/tsug_typedecl.nim
new file mode 100644
index 000000000..2a510929d
--- /dev/null
+++ b/nimsuggest/tests/tsug_typedecl.nim
@@ -0,0 +1,26 @@
+# suggestions for type declarations
+
+from system import string, int, bool
+
+type
+  super = int
+  someType = bool
+
+let str = "hello"
+
+proc main() =
+  let a: s#[!]#
+
+# This output show seq, even though that's not imported. This is due to the
+# entire symbol table, regardless of import visibility is currently being
+# scanned. This is hardly ideal, but changing it with the current level of test
+# coverage is unwise as it might break more than it fixes.
+
+discard """
+$nimsuggest --tester $file
+>sug $1
+sug;;skType;;tsug_typedecl.someType;;someType;;*nimsuggest/tests/tsug_typedecl.nim;;7;;2;;"";;100;;Prefix
+sug;;skType;;tsug_typedecl.super;;super;;*nimsuggest/tests/tsug_typedecl.nim;;6;;2;;"";;100;;Prefix
+sug;;skType;;system.string;;string;;*lib/system/basic_types.nim;;*;;*;;*;;100;;Prefix
+sug;;skType;;system.seq;;seq;;*lib/system.nim;;*;;*;;*;;100;;Prefix
+"""
diff --git a/nimsuggest/tests/ttempl_inst.nim b/nimsuggest/tests/ttempl_inst.nim
new file mode 100644
index 000000000..5f5b10fe9
--- /dev/null
+++ b/nimsuggest/tests/ttempl_inst.nim
@@ -0,0 +1,13 @@
+template foo() =
+  {.warning: "foo".}
+  
+foo()
+
+#[!]#
+discard """
+$nimsuggest --tester $file
+>chk $1
+chk;;skUnknown;;;;Hint;;???;;0;;-1;;">> (toplevel): import(dirty): tests/ttempl_inst.nim [Processing]";;0
+chk;;skUnknown;;;;Hint;;$file;;4;;3;;"template/generic instantiation from here";;0
+chk;;skUnknown;;;;Warning;;$file;;2;;11;;"foo [User]";;0
+"""
diff --git a/nimsuggest/tests/disabled_ttemplate_highlight.nim b/nimsuggest/tests/ttemplate_highlight.nim
index 2cbac3be5..2cbac3be5 100644
--- a/nimsuggest/tests/disabled_ttemplate_highlight.nim
+++ b/nimsuggest/tests/ttemplate_highlight.nim
diff --git a/nimsuggest/tests/ttype_decl.nim b/nimsuggest/tests/ttype_decl.nim
index e66d39a1c..61d8c26cd 100644
--- a/nimsuggest/tests/ttype_decl.nim
+++ b/nimsuggest/tests/ttype_decl.nim
@@ -1,15 +1,16 @@
 discard """
-disabled:true
 $nimsuggest --tester --maxresults:3 $file
 >sug $1
-sug;;skType;;ttype_decl.Other;;Other;;$file;;10;;2;;"";;0;;None
-sug;;skType;;system.int;;int;;$lib/system.nim;;25;;2;;"";;0;;None
-sug;;skType;;system.string;;string;;$lib/system.nim;;48;;2;;"";;0;;None
+sug;;skType;;ttype_decl.Other;;Other;;$file;;10;;2;;"";;100;;None
+sug;;skType;;system.int;;int;;*lib/system/basic_types.nim;;2;;2;;"";;100;;None
+sug;;skType;;system.string;;string;;*lib/system/basic_types.nim;;23;;2;;"";;100;;None
 """
 import strutils
 type
   Other = object ## My other object.
   Foo = #[!]#
+  OldOne {.deprecated.} = object
+    x: int
 
 proc main(f: Foo) =
 
diff --git a/nimsuggest/tests/tuse.nim b/nimsuggest/tests/tuse.nim
new file mode 100644
index 000000000..7c1d1ad0c
--- /dev/null
+++ b/nimsuggest/tests/tuse.nim
@@ -0,0 +1,22 @@
+# basic tests for use
+
+# bug #58
+proc someOtherProc() =
+  discard
+
+someOtherProc()
+
+proc #[!]#someProc*() =
+  discard
+
+#[!]#someProc()
+
+discard """
+$nimsuggest --tester $file
+>use $1
+def;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;9;;5;;"";;100
+use;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;12;;0;;"";;100
+>use $2
+def;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;9;;5;;"";;100
+use;;skProc;;tuse.someProc;;proc (){.noSideEffect, gcsafe, raises: <inferred> [].};;$file;;12;;0;;"";;100
+"""
diff --git a/nimsuggest/tests/tuse_enum.nim b/nimsuggest/tests/tuse_enum.nim
new file mode 100644
index 000000000..8a40a8348
--- /dev/null
+++ b/nimsuggest/tests/tuse_enum.nim
@@ -0,0 +1,15 @@
+discard """
+$nimsuggest --tester $file
+>use $1
+def;;skEnumField;;tuse_enum.Colour.Red;;Colour;;$file;;10;;4;;"";;100
+use;;skEnumField;;tuse_enum.Colour.Red;;Colour;;$file;;14;;8;;"";;100
+"""
+
+type
+  Colour = enum
+    Red
+    Green
+    Blue
+
+discard #[!]#Red
+
diff --git a/nimsuggest/tests/tuse_structure.nim b/nimsuggest/tests/tuse_structure.nim
new file mode 100644
index 000000000..f65ab9060
--- /dev/null
+++ b/nimsuggest/tests/tuse_structure.nim
@@ -0,0 +1,15 @@
+# tests for use and structures
+
+type
+  Foo* = ref object of RootObj
+    bar*: string
+
+proc test(f: Foo) =
+  echo f.#[!]#bar
+
+discard """
+$nimsuggest --tester $file
+>use $1
+def	skField	tuse_structure.Foo.bar	string	$file	5	4	""	100
+use	skField	tuse_structure.Foo.bar	string	$file	8	9	""	100
+"""
diff --git a/nimsuggest/tests/tv3.nim b/nimsuggest/tests/tv3.nim
new file mode 100644
index 000000000..80e51e364
--- /dev/null
+++ b/nimsuggest/tests/tv3.nim
@@ -0,0 +1,27 @@
+# tests v3
+
+type
+  Foo* = ref object of RootObj
+    bar*: string
+
+proc test(f: Foo) =
+  echo f.ba#[!]#r
+
+#[!]#
+
+discard """
+$nimsuggest --v3 --tester $file
+>use $1
+def	skField	tv3.Foo.bar	string	$file	5	4	""	100
+use	skField	tv3.Foo.bar	string	$file	8	9	""	100
+>def $1
+def	skField	tv3.Foo.bar	string	$file	5	4	""	100
+>sug $1
+sug	skField	bar	string	$file	5	4	""	100	Prefix
+>globalSymbols test
+def	skProc	tv3.test	proc (f: Foo){.gcsafe, raises: <inferred> [].}	$file	7	5	""	100
+>globalSymbols Foo
+def	skType	tv3.Foo	Foo	$file	4	2	""	100
+>def $2
+>use $2
+"""
diff --git a/nimsuggest/tests/tv3_con.nim b/nimsuggest/tests/tv3_con.nim
new file mode 100644
index 000000000..4714c366b
--- /dev/null
+++ b/nimsuggest/tests/tv3_con.nim
@@ -0,0 +1,13 @@
+# tests v3
+
+proc test(a: string, b:string) = discard
+proc test(a: int) = discard
+
+test(#[!]#
+
+discard """
+$nimsuggest --v3 --tester $file
+>con $1
+con;;skProc;;tv3_con.test;;proc (a: string, b: string);;$file;;3;;5;;"";;100
+con;;skProc;;tv3_con.test;;proc (a: int);;$file;;4;;5;;"";;100
+"""
diff --git a/nimsuggest/tests/tv3_definition.nim b/nimsuggest/tests/tv3_definition.nim
new file mode 100644
index 000000000..03684b7cd
--- /dev/null
+++ b/nimsuggest/tests/tv3_definition.nim
@@ -0,0 +1,9 @@
+
+let foo = 30
+let bar = foo + fo#[!]#o + foo
+
+discard """
+$nimsuggest --v3 --tester $file
+>def $1
+def	skLet	tv3_definition.foo	int	$file	2	4	""	100
+"""
diff --git a/nimsuggest/tests/tv3_forward_definition.nim b/nimsuggest/tests/tv3_forward_definition.nim
new file mode 100644
index 000000000..7a16ea331
--- /dev/null
+++ b/nimsuggest/tests/tv3_forward_definition.nim
@@ -0,0 +1,23 @@
+proc de#[!]#mo(): int
+
+proc de#[!]#mo(): int = 5
+
+let a = de#[!]#mo()
+
+discard """
+$nimsuggest --v3 --tester $file
+>use $1
+use	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	1	5	""	100
+def	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	3	5	""	100
+use	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	5	8	""	100
+>use $2
+use	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	1	5	""	100
+def	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	3	5	""	100
+use	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	5	8	""	100
+>declaration $1
+declaration	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	3	5	""	100
+>declaration $2
+declaration	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	1	5	""	100
+>declaration $3
+declaration	skProc	tv3_forward_definition.demo	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	1	5	""	100
+"""
diff --git a/nimsuggest/tests/tv3_generics.nim b/nimsuggest/tests/tv3_generics.nim
new file mode 100644
index 000000000..2bfb2ca1d
--- /dev/null
+++ b/nimsuggest/tests/tv3_generics.nim
@@ -0,0 +1,18 @@
+type
+  Hello[T] = object
+    value: T
+
+proc printHelloValue[T](hello: Hello[T]) =
+  echo hello.value
+
+proc main() =
+  let a = Hello[float]()
+  p#[!]#rintHelloValue(a)
+
+main()
+
+discard """
+$nimsuggest --v3 --tester $file
+>def $1
+def;;skProc;;tv3_generics.printHelloValue;;proc (hello: Hello[printHelloValue.T]);;$file;;5;;5;;"";;100
+"""
diff --git a/nimsuggest/tests/tv3_globalSymbols.nim b/nimsuggest/tests/tv3_globalSymbols.nim
new file mode 100644
index 000000000..c3bb9933b
--- /dev/null
+++ b/nimsuggest/tests/tv3_globalSymbols.nim
@@ -0,0 +1,14 @@
+# Tests the order of the matches
+proc Btoken(): int = 5
+proc tokenA(): int = 5
+proc token(): int = 5
+proc BBtokenA(): int = 5
+
+discard """
+$nimsuggest --v3 --tester $file
+>globalSymbols token
+def	skProc	tv3_globalSymbols.token	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	4	5	""	100
+def	skProc	tv3_globalSymbols.tokenA	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	3	5	""	100
+def	skProc	tv3_globalSymbols.Btoken	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	2	5	""	100
+def	skProc	tv3_globalSymbols.BBtokenA	proc (): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	5	5	""	100
+"""
diff --git a/nimsuggest/tests/tv3_import.nim b/nimsuggest/tests/tv3_import.nim
new file mode 100644
index 000000000..3c128f85b
--- /dev/null
+++ b/nimsuggest/tests/tv3_import.nim
@@ -0,0 +1,7 @@
+import tv#[!]#3
+
+discard """
+$nimsuggest --v3 --tester $file
+>def $1
+def	skModule	tv3		*/tv3.nim	1	0	""	100
+"""
diff --git a/nimsuggest/tests/tv3_outline.nim b/nimsuggest/tests/tv3_outline.nim
new file mode 100644
index 000000000..518620c87
--- /dev/null
+++ b/nimsuggest/tests/tv3_outline.nim
@@ -0,0 +1,45 @@
+# tests v3 outline
+
+type
+  Foo* = ref object of RootObj
+    bar*: string
+  FooEnum = enum value1, value2
+  FooPrivate = ref object of RootObj
+    barPrivate: string
+
+macro m(arg: untyped): untyped = discard
+template t(arg: untyped): untyped = discard
+proc p(): void = discard
+iterator i(): int = discard
+converter c(s: string): int = discard
+method m(f: Foo): void = discard
+func f(): void = discard
+
+let a = 1
+var b = 2
+const con = 2
+
+proc outer(): void =
+  proc inner() = discard
+
+proc procWithLocal(): void =
+  let local = 10
+
+discard """
+$nimsuggest --v3 --tester $file
+>outline $file
+outline	skType	tv3_outline.Foo	Foo	$file	4	2	""	100	5	16
+outline	skType	tv3_outline.FooEnum	FooEnum	$file	6	2	""	100	6	31
+outline	skEnumField	tv3_outline.FooEnum.value1	FooEnum	$file	6	17	""	100	6	23
+outline	skEnumField	tv3_outline.FooEnum.value2	FooEnum	$file	6	25	""	100	6	31
+outline	skType	tv3_outline.FooPrivate	FooPrivate	$file	7	2	""	100	8	22
+outline	skMacro	tv3_outline.m	macro (arg: untyped): untyped{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	10	6	""	100	10	40
+outline	skTemplate	tv3_outline.t	template (arg: untyped): untyped	$file	11	9	""	100	11	43
+outline	skProc	tv3_outline.p	proc (){.noSideEffect, gcsafe, raises: <inferred> [].}	$file	12	5	""	100	12	24
+outline	skConverter	tv3_outline.c	converter (s: string): int{.noSideEffect, gcsafe, raises: <inferred> [].}	$file	14	10	""	100	14	37
+outline	skFunc	tv3_outline.f	proc (){.noSideEffect, gcsafe, raises: <inferred> [].}	$file	16	5	""	100	16	24
+outline	skConst	tv3_outline.con	int literal(2)	$file	20	6	""	100	20	13
+outline	skProc	tv3_outline.outer	proc (){.noSideEffect, gcsafe, raises: <inferred> [].}	$file	22	5	""	100	23	24
+outline	skProc	tv3_outline.outer.inner	proc (){.noSideEffect, gcsafe, raises: <inferred> [].}	$file	23	7	""	100	23	24
+outline	skProc	tv3_outline.procWithLocal	proc (){.noSideEffect, gcsafe, raises: <inferred> [].}	$file	25	5	""	100	26	16
+"""
diff --git a/nimsuggest/tests/tv3_typeDefinition.nim b/nimsuggest/tests/tv3_typeDefinition.nim
new file mode 100644
index 000000000..f86d12cc6
--- /dev/null
+++ b/nimsuggest/tests/tv3_typeDefinition.nim
@@ -0,0 +1,32 @@
+# tests v3
+
+type
+  Foo* = ref object of RootObj
+    bar*: string
+
+proc test(ff: Foo) =
+  echo f#[!]#f.bar
+
+type
+  Fo#[!]#o2* = ref object of RootObj
+
+type
+  FooGeneric[T] = ref object of RootObj
+    bar*: T
+
+let fooGeneric = FooGeneric[string]()
+echo fo#[!]#oGeneric.bar
+
+# bad type
+echo unde#[!]#fined
+
+discard """
+$nimsuggest --v3 --tester $file
+>type $1
+type	skType	tv3_typeDefinition.Foo	Foo	$file	4	2	""	100
+>type $2
+type	skType	tv3_typeDefinition.Foo2	Foo2	$file	11	2	""	100
+>type $3
+type	skType	tv3_typeDefinition.FooGeneric	FooGeneric	$file	14	2	""	100
+>type $4
+"""
diff --git a/nimsuggest/tests/twithin_macro.nim b/nimsuggest/tests/twithin_macro.nim
index 9c36ffd0f..98d58381f 100644
--- a/nimsuggest/tests/twithin_macro.nim
+++ b/nimsuggest/tests/twithin_macro.nim
@@ -1,166 +1,5 @@
-
-import macros
-
-macro class*(head, body: untyped): untyped =
-  # The macro is immediate, since all its parameters are untyped.
-  # This means, it doesn't resolve identifiers passed to it.
-
-  var typeName, baseName: NimNode
-
-  # flag if object should be exported
-  var exported: bool
-
-  if head.kind == nnkInfix and head[0].ident == !"of":
-    # `head` is expression `typeName of baseClass`
-    # echo head.treeRepr
-    # --------------------
-    # Infix
-    #   Ident !"of"
-    #   Ident !"Animal"
-    #   Ident !"RootObj"
-    typeName = head[1]
-    baseName = head[2]
-
-  elif head.kind == nnkInfix and head[0].ident == !"*" and
-       head[2].kind == nnkPrefix and head[2][0].ident == !"of":
-    # `head` is expression `typeName* of baseClass`
-    # echo head.treeRepr
-    # --------------------
-    # Infix
-    #   Ident !"*"
-    #   Ident !"Animal"
-    #   Prefix
-    #     Ident !"of"
-    #     Ident !"RootObj"
-    typeName = head[1]
-    baseName = head[2][1]
-    exported = true
-
-  else:
-    quit "Invalid node: " & head.lispRepr
-
-  # The following prints out the AST structure:
-  #
-  # import macros
-  # dumptree:
-  #   type X = ref object of Y
-  #     z: int
-  # --------------------
-  # StmtList
-  #   TypeSection
-  #     TypeDef
-  #       Ident !"X"
-  #       Empty
-  #       RefTy
-  #         ObjectTy
-  #           Empty
-  #           OfInherit
-  #             Ident !"Y"
-  #           RecList
-  #             IdentDefs
-  #               Ident !"z"
-  #               Ident !"int"
-  #               Empty
-
-  # create a type section in the result
-  result =
-    if exported:
-      # mark `typeName` with an asterisk
-      quote do:
-        type `typeName`* = ref object of `baseName`
-    else:
-      quote do:
-        type `typeName` = ref object of `baseName`
-
-  # echo treeRepr(body)
-  # --------------------
-  # StmtList
-  #   VarSection
-  #     IdentDefs
-  #       Ident !"name"
-  #       Ident !"string"
-  #       Empty
-  #     IdentDefs
-  #       Ident !"age"
-  #       Ident !"int"
-  #       Empty
-  #   MethodDef
-  #     Ident !"vocalize"
-  #     Empty
-  #     Empty
-  #     FormalParams
-  #       Ident !"string"
-  #     Empty
-  #     Empty
-  #     StmtList
-  #       StrLit ...
-  #   MethodDef
-  #     Ident !"age_human_yrs"
-  #     Empty
-  #     Empty
-  #     FormalParams
-  #       Ident !"int"
-  #     Empty
-  #     Empty
-  #     StmtList
-  #       DotExpr
-  #         Ident !"this"
-  #         Ident !"age"
-
-  # var declarations will be turned into object fields
-  var recList = newNimNode(nnkRecList)
-
-  # expected name of constructor
-  let ctorName = newIdentNode("new" & $typeName)
-
-  # Iterate over the statements, adding `this: T`
-  # to the parameters of functions, unless the
-  # function is a constructor
-  for node in body.children:
-    case node.kind:
-
-      of nnkMethodDef, nnkProcDef:
-        # check if it is the ctor proc
-        if node.name.kind != nnkAccQuoted and node.name.basename == ctorName:
-          # specify the return type of the ctor proc
-          node.params[0] = typeName
-        else:
-          # inject `self: T` into the arguments
-          node.params.insert(1, newIdentDefs(ident("self"), typeName))
-        result.add(node)
-
-      of nnkVarSection:
-        # variables get turned into fields of the type.
-        for n in node.children:
-          recList.add(n)
-
-      else:
-        result.add(node)
-
-  # Inspect the tree structure:
-  #
-  # echo result.treeRepr
-  # --------------------
-  # StmtList
-  #   TypeSection
-  #     TypeDef
-  #       Ident !"Animal"
-  #       Empty
-  #       RefTy
-  #         ObjectTy
-  #           Empty
-  #           OfInherit
-  #             Ident !"RootObj"
-  #           Empty   <= We want to replace this
-  # MethodDef
-  # ...
-
-  result[0][0][2][0][2] = recList
-
-  # Lets inspect the human-readable version of the output
-  #echo repr(result)
-
-# ---
+from system import string, int, seq, `&`, `$`, `*`, `@`, echo, add, items, RootObj
+import fixtures/mclass_macro
 
 class Animal of RootObj:
   var name: string
@@ -202,13 +41,11 @@ echo r.age_human_yrs()
 echo r
 
 discard """
-disabled:true
-$nimsuggest --tester $file
+$nimsuggest --tester --maxresults:5 $file
 >sug $1
-sug;;skField;;age;;int;;$file;;167;;6;;"";;100;;None
-sug;;skField;;name;;string;;$file;;166;;6;;"";;100;;None
-sug;;skMethod;;twithin_macro.age_human_yrs;;proc (self: Animal): int;;$file;;169;;9;;"";;100;;None
-sug;;skMethod;;twithin_macro.vocalize;;proc (self: Animal): string;;$file;;168;;9;;"";;100;;None
-sug;;skMethod;;twithin_macro.vocalize;;proc (self: Rabbit): string;;$file;;184;;9;;"";;100;;None
-sug;;skMacro;;twithin_macro.class;;proc (head: untyped, body: untyped): untyped{.gcsafe, locks: <unknown>.};;$file;;4;;6;;"";;50;;None*
+sug;;skField;;age;;int;;$file;;6;;6;;"";;100;;None
+sug;;skField;;name;;string;;$file;;5;;6;;"";;100;;None
+sug;;skMethod;;twithin_macro.age_human_yrs;;proc (self: Animal): int{.raises: <inferred> [].};;$file;;8;;9;;"";;100;;None
+sug;;skMethod;;twithin_macro.vocalize;;proc (self: Animal): string{.raises: <inferred> [].};;$file;;7;;9;;"";;100;;None
+sug;;skMethod;;twithin_macro.vocalize;;proc (self: Rabbit): string;;$file;;23;;9;;"";;100;;None
 """
diff --git a/nimsuggest/tests/twithin_macro_prefix.nim b/nimsuggest/tests/twithin_macro_prefix.nim
index 1402b762a..e89c8b942 100644
--- a/nimsuggest/tests/twithin_macro_prefix.nim
+++ b/nimsuggest/tests/twithin_macro_prefix.nim
@@ -1,166 +1,5 @@
-
-import macros
-
-macro class*(head, body: untyped): untyped =
-  # The macro is immediate, since all its parameters are untyped.
-  # This means, it doesn't resolve identifiers passed to it.
-
-  var typeName, baseName: NimNode
-
-  # flag if object should be exported
-  var exported: bool
-
-  if head.kind == nnkInfix and head[0].ident == !"of":
-    # `head` is expression `typeName of baseClass`
-    # echo head.treeRepr
-    # --------------------
-    # Infix
-    #   Ident !"of"
-    #   Ident !"Animal"
-    #   Ident !"RootObj"
-    typeName = head[1]
-    baseName = head[2]
-
-  elif head.kind == nnkInfix and head[0].ident == !"*" and
-       head[2].kind == nnkPrefix and head[2][0].ident == !"of":
-    # `head` is expression `typeName* of baseClass`
-    # echo head.treeRepr
-    # --------------------
-    # Infix
-    #   Ident !"*"
-    #   Ident !"Animal"
-    #   Prefix
-    #     Ident !"of"
-    #     Ident !"RootObj"
-    typeName = head[1]
-    baseName = head[2][1]
-    exported = true
-
-  else:
-    quit "Invalid node: " & head.lispRepr
-
-  # The following prints out the AST structure:
-  #
-  # import macros
-  # dumptree:
-  #   type X = ref object of Y
-  #     z: int
-  # --------------------
-  # StmtList
-  #   TypeSection
-  #     TypeDef
-  #       Ident !"X"
-  #       Empty
-  #       RefTy
-  #         ObjectTy
-  #           Empty
-  #           OfInherit
-  #             Ident !"Y"
-  #           RecList
-  #             IdentDefs
-  #               Ident !"z"
-  #               Ident !"int"
-  #               Empty
-
-  # create a type section in the result
-  result =
-    if exported:
-      # mark `typeName` with an asterisk
-      quote do:
-        type `typeName`* = ref object of `baseName`
-    else:
-      quote do:
-        type `typeName` = ref object of `baseName`
-
-  # echo treeRepr(body)
-  # --------------------
-  # StmtList
-  #   VarSection
-  #     IdentDefs
-  #       Ident !"name"
-  #       Ident !"string"
-  #       Empty
-  #     IdentDefs
-  #       Ident !"age"
-  #       Ident !"int"
-  #       Empty
-  #   MethodDef
-  #     Ident !"vocalize"
-  #     Empty
-  #     Empty
-  #     FormalParams
-  #       Ident !"string"
-  #     Empty
-  #     Empty
-  #     StmtList
-  #       StrLit ...
-  #   MethodDef
-  #     Ident !"age_human_yrs"
-  #     Empty
-  #     Empty
-  #     FormalParams
-  #       Ident !"int"
-  #     Empty
-  #     Empty
-  #     StmtList
-  #       DotExpr
-  #         Ident !"this"
-  #         Ident !"age"
-
-  # var declarations will be turned into object fields
-  var recList = newNimNode(nnkRecList)
-
-  # expected name of constructor
-  let ctorName = newIdentNode("new" & $typeName)
-
-  # Iterate over the statements, adding `this: T`
-  # to the parameters of functions, unless the
-  # function is a constructor
-  for node in body.children:
-    case node.kind:
-
-      of nnkMethodDef, nnkProcDef:
-        # check if it is the ctor proc
-        if node.name.kind != nnkAccQuoted and node.name.basename == ctorName:
-          # specify the return type of the ctor proc
-          node.params[0] = typeName
-        else:
-          # inject `self: T` into the arguments
-          node.params.insert(1, newIdentDefs(ident("self"), typeName))
-        result.add(node)
-
-      of nnkVarSection:
-        # variables get turned into fields of the type.
-        for n in node.children:
-          recList.add(n)
-
-      else:
-        result.add(node)
-
-  # Inspect the tree structure:
-  #
-  # echo result.treeRepr
-  # --------------------
-  # StmtList
-  #   TypeSection
-  #     TypeDef
-  #       Ident !"Animal"
-  #       Empty
-  #       RefTy
-  #         ObjectTy
-  #           Empty
-  #           OfInherit
-  #             Ident !"RootObj"
-  #           Empty   <= We want to replace this
-  # MethodDef
-  # ...
-
-  result[0][0][2][0][2] = recList
-
-  # Lets inspect the human-readable version of the output
-  #echo repr(result)
-
-# ---
+from system import string, int, seq, `&`, `$`, `*`, `@`, echo, add, RootObj
+import fixtures/mclass_macro
 
 class Animal of RootObj:
   var name: string
@@ -202,9 +41,8 @@ echo r.age_human_yrs()
 echo r
 
 discard """
-disabled:true
 $nimsuggest --tester $file
 >sug $1
-sug;;skField;;age;;int;;$file;;167;;6;;"";;100;;Prefix
-sug;;skMethod;;twithin_macro_prefix.age_human_yrs;;proc (self: Animal): int;;$file;;169;;9;;"";;100;;Prefix
+sug;;skField;;age;;int;;$file;;6;;6;;"";;100;;Prefix
+sug;;skMethod;;twithin_macro_prefix.age_human_yrs;;proc (self: Animal): int{.raises: <inferred> [].};;$file;;8;;9;;"";;100;;Prefix
 """