nimsuggest supports prefix matching (first version)

This commit is contained in:
Araq
2017-02-28 09:03:40 +01:00
parent e78bd69619
commit 3d46600a90
6 changed files with 126 additions and 174 deletions

View File

@@ -519,12 +519,17 @@ proc myProcess(context: PPassContext, n: PNode): PNode =
recoverContext(c)
c.inGenericInst = oldInGenericInst
msgs.setInfoContextLen(oldContextLen)
if getCurrentException() of ESuggestDone: result = nil
else: result = ast.emptyNode
if getCurrentException() of ESuggestDone:
c.suggestionsMade = true
result = nil
else:
result = ast.emptyNode
#if gCmd == cmdIdeTools: findSuggest(c, n)
proc myClose(graph: ModuleGraph; context: PPassContext, n: PNode): PNode =
var c = PContext(context)
if gCmd == cmdIdeTools and not c.suggestionsMade:
suggestSentinel(c)
closeScope(c) # close module's scope
rawCloseScope(c) # imported symbols; don't check for unused ones!
result = newNode(nkStmtList)

View File

@@ -111,6 +111,7 @@ type
graph*: ModuleGraph
signatures*: TStrTable
recursiveDep*: string
suggestionsMade*: bool
proc makeInstPair*(s: PSym, inst: PInstantiation): TInstantiationPair =
result.genericSym = s

View File

@@ -8,6 +8,20 @@
#
## This file implements features required for IDE support.
##
## Due to Nim's natures and the fact that ``system.nim`` is always imported,
## there are lots of potential symbols. Furthermore thanks to templates and
## macros even context based analysis does not help much: In a context like
## ``let x: |`` where a type has to follow, that type might be constructed from
## a template like ``extractField(MyObject, fieldName)``. We deal with this
## problem by smart sorting so that the likely symbols come first. This sorting
## is done this way:
##
## - If there is a prefix (foo|), symbols starting with this prefix come first.
## - If the prefix is part of the name (but the name doesn't start with it),
## these symbols come second.
## - Otherwise consider the context. We currently distinguish between type
## and non-type contexts.
# included from sigmatch.nim
@@ -133,11 +147,20 @@ proc suggestResult(s: Suggest) =
else:
suggestWriteln($s)
proc filterSym(s: PSym): bool {.inline.} =
result = s.kind != skModule
proc filterSym(s: PSym; prefix: PNode): bool {.inline.} =
proc prefixMatch(s: PSym; n: PNode): bool =
case n.kind
of nkIdent: result = s.name.s.startsWith(n.ident.s)
of nkSym: result = s.name.s.startsWith(n.sym.name.s)
of nkOpenSymChoice, nkClosedSymChoice, nkAccQuoted:
if n.len > 0:
result = prefixMatch(s, n[0])
else: discard
if s.kind != skModule:
result = prefix.isNil or prefixMatch(s, prefix)
proc filterSymNoOpr(s: PSym): bool {.inline.} =
result = s.kind != skModule and s.name.s[0] in lexer.SymChars and
proc filterSymNoOpr(s: PSym; prefix: PNode): bool {.inline.} =
result = filterSym(s, prefix) and s.name.s[0] in lexer.SymChars and
not isKeyword(s.name)
proc fieldVisible*(c: PContext, f: PSym): bool {.inline.} =
@@ -148,8 +171,8 @@ proc fieldVisible*(c: PContext, f: PSym): bool {.inline.} =
result = true
break
proc suggestField(c: PContext, s: PSym, outputs: var int) =
if filterSym(s) and fieldVisible(c, s):
proc suggestField(c: PContext, s: PSym; f: PNode; outputs: var int) =
if filterSym(s, f) and fieldVisible(c, s):
suggestResult(symToSuggest(s, isLocal=true, $ideSug, 100))
inc outputs
@@ -166,22 +189,22 @@ template wholeSymTab(cond, section: untyped) =
suggestResult(symToSuggest(it, isLocal = isLocal, section, 100))
inc outputs
proc suggestSymList(c: PContext, list: PNode, outputs: var int) =
proc suggestSymList(c: PContext, list, f: PNode, outputs: var int) =
for i in countup(0, sonsLen(list) - 1):
if list.sons[i].kind == nkSym:
suggestField(c, list.sons[i].sym, outputs)
suggestField(c, list.sons[i].sym, f, outputs)
#else: InternalError(list.info, "getSymFromList")
proc suggestObject(c: PContext, n: PNode, outputs: var int) =
proc suggestObject(c: PContext, n, f: PNode, outputs: var int) =
case n.kind
of nkRecList:
for i in countup(0, sonsLen(n)-1): suggestObject(c, n.sons[i], outputs)
for i in countup(0, sonsLen(n)-1): suggestObject(c, n.sons[i], f, outputs)
of nkRecCase:
var L = sonsLen(n)
if L > 0:
suggestObject(c, n.sons[0], outputs)
for i in countup(1, L-1): suggestObject(c, lastSon(n.sons[i]), outputs)
of nkSym: suggestField(c, n.sym, outputs)
suggestObject(c, n.sons[0], f, outputs)
for i in countup(1, L-1): suggestObject(c, lastSon(n.sons[i]), f, outputs)
of nkSym: suggestField(c, n.sym, f, outputs)
else: discard
proc nameFits(c: PContext, s: PSym, n: PNode): bool =
@@ -205,7 +228,7 @@ proc argsFit(c: PContext, candidate: PSym, n, nOrig: PNode): bool =
result = false
proc suggestCall(c: PContext, n, nOrig: PNode, outputs: var int) =
wholeSymTab(filterSym(it) and nameFits(c, it, n) and argsFit(c, it, n, nOrig),
wholeSymTab(filterSym(it, nil) and nameFits(c, it, n) and argsFit(c, it, n, nOrig),
$ideCon)
proc typeFits(c: PContext, s: PSym, firstArg: PType): bool {.inline.} =
@@ -221,22 +244,22 @@ proc typeFits(c: PContext, s: PSym, firstArg: PType): bool {.inline.} =
if exp.kind in {tyExpr, tyStmt, tyGenericParam, tyAnything}: return
result = sigmatch.argtypeMatches(c, s.typ.sons[1], firstArg)
proc suggestOperations(c: PContext, n: PNode, typ: PType, outputs: var int) =
proc suggestOperations(c: PContext, n, f: PNode, typ: PType, outputs: var int) =
assert typ != nil
wholeSymTab(filterSymNoOpr(it) and typeFits(c, it, typ), $ideSug)
wholeSymTab(filterSymNoOpr(it, f) and typeFits(c, it, typ), $ideSug)
proc suggestEverything(c: PContext, n: PNode, outputs: var int) =
proc suggestEverything(c: PContext, n, f: PNode, outputs: var int) =
# do not produce too many symbols:
var isLocal = true
for scope in walkScopes(c.currentScope):
if scope == c.topLevelScope: isLocal = false
for it in items(scope.symbols):
if filterSym(it):
if filterSym(it, f):
suggestResult(symToSuggest(it, isLocal = isLocal, $ideSug, 0))
inc outputs
if scope == c.topLevelScope: break
if scope == c.topLevelScope and f.isNil: break
proc suggestFieldAccess(c: PContext, n: PNode, outputs: var int) =
proc suggestFieldAccess(c: PContext, n, field: PNode, outputs: var int) =
# special code that deals with ``myObj.``. `n` is NOT the nkDotExpr-node, but
# ``myObj``.
var typ = n.typ
@@ -252,7 +275,7 @@ proc suggestFieldAccess(c: PContext, n: PNode, outputs: var int) =
if m == nil: typ = nil
else:
for it in items(n.sym.tab):
if filterSym(it):
if filterSym(it, field):
suggestResult(symToSuggest(it, isLocal=false, $ideSug, 100))
inc outputs
suggestResult(symToSuggest(m, isLocal=false, $ideMod, 100))
@@ -263,38 +286,38 @@ proc suggestFieldAccess(c: PContext, n: PNode, outputs: var int) =
if n.sym == c.module:
# all symbols accessible, because we are in the current module:
for it in items(c.topLevelScope.symbols):
if filterSym(it):
if filterSym(it, field):
suggestResult(symToSuggest(it, isLocal=false, $ideSug, 100))
inc outputs
else:
for it in items(n.sym.tab):
if filterSym(it):
if filterSym(it, field):
suggestResult(symToSuggest(it, isLocal=false, $ideSug, 100))
inc outputs
else:
# fallback:
suggestEverything(c, n, outputs)
suggestEverything(c, n, field, outputs)
elif typ.kind == tyEnum and n.kind == nkSym and n.sym.kind == skType:
# look up if the identifier belongs to the enum:
var t = typ
while t != nil:
suggestSymList(c, t.n, outputs)
suggestSymList(c, t.n, field, outputs)
t = t.sons[0]
suggestOperations(c, n, typ, outputs)
suggestOperations(c, n, field, typ, outputs)
else:
let orig = skipTypes(typ, {tyGenericInst, tyAlias})
typ = skipTypes(typ, {tyGenericInst, tyVar, tyPtr, tyRef, tyAlias})
if typ.kind == tyObject:
var t = typ
while true:
suggestObject(c, t.n, outputs)
suggestObject(c, t.n, field, outputs)
if t.sons[0] == nil: break
t = skipTypes(t.sons[0], skipPtrs)
elif typ.kind == tyTuple and typ.n != nil:
suggestSymList(c, typ.n, outputs)
suggestOperations(c, n, orig, outputs)
suggestSymList(c, typ.n, field, outputs)
suggestOperations(c, n, field, orig, outputs)
if typ != orig:
suggestOperations(c, n, typ, outputs)
suggestOperations(c, n, field, typ, outputs)
type
TCheckPointResult* = enum
@@ -443,13 +466,19 @@ proc suggestExpr*(c: PContext, node: PNode) =
if n == nil: n = node
if n.kind == nkDotExpr:
var obj = safeSemExpr(c, n.sons[0])
suggestFieldAccess(c, obj, outputs)
let prefix = if n.len == 2: n[1] else: nil
suggestFieldAccess(c, obj, prefix, outputs)
#if optIdeDebug in gGlobalOptions:
# echo "expression ", renderTree(obj), " has type ", typeToString(obj.typ)
#writeStackTrace()
else:
suggestEverything(c, n, outputs)
#let m = findClosestSym(node)
#if m != nil:
# suggestPrefix(c, m, outputs)
#else:
let prefix = if cp == cpExact: n else: nil
suggestEverything(c, n, prefix, outputs)
elif gIdeCmd == ideCon:
var n = findClosestCall(node)
@@ -471,3 +500,17 @@ proc suggestExpr*(c: PContext, node: PNode) =
proc suggestStmt*(c: PContext, n: PNode) =
suggestExpr(c, n)
proc suggestSentinel*(c: PContext) =
if gIdeCmd != ideSug or c.module.position != gTrackPos.fileIndex: return
if c.compilesContextId > 0: return
inc(c.compilesContextId)
# suggest everything:
var isLocal = true
for scope in walkScopes(c.currentScope):
if scope == c.topLevelScope: isLocal = false
for it in items(scope.symbols):
if filterSymNoOpr(it, nil):
suggestResult(symToSuggest(it, isLocal = isLocal, $ideSug, 0))
dec(c.compilesContextId)

View File

@@ -380,8 +380,7 @@ macro expect*(exceptions: varargs[typed], body: untyped): untyped =
## expect IOError, OSError, ValueError, AssertionError:
## defectiveRobot()
let exp = callsite()
template expectBody(errorTypes, lineInfoLit: expr,
body: stmt): NimNode {.dirty.} =
template expectBody(errorTypes, lineInfoLit, body): NimNode {.dirty.} =
try:
body
checkpoint(lineInfoLit & ": Expect Failed, no exception was thrown.")

View File

@@ -1,7 +1,7 @@
#
#
# The Nim Compiler
# (c) Copyright 2016 Andreas Rumpf
# (c) Copyright 2017 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@@ -50,7 +50,7 @@ In addition, all command line options of Nim that do not affect code generation
are supported.
"""
type
Mode = enum mstdin, mtcp, mepc
Mode = enum mstdin, mtcp, mepc, mcmdline
var
gPort = 6000.Port
@@ -133,7 +133,8 @@ proc symFromInfo(graph: ModuleGraph; gTrackPos: TLineInfo): PSym =
proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int;
graph: ModuleGraph; cache: IdentCache) =
if gLogging:
logStr("cmd: " & $cmd & ", file: " & file & ", dirtyFile: " & dirtyfile & "[" & $line & ":" & $col & "]")
logStr("cmd: " & $cmd & ", file: " & file & ", dirtyFile: " & dirtyfile &
"[" & $line & ":" & $col & "]")
gIdeCmd = cmd
if cmd == ideUse and suggestVersion != 2:
graph.resetAllModules()
@@ -258,6 +259,12 @@ proc connectToNextFreePort(server: Socket, host: string): Port =
type
ThreadParams = tuple[port: Port; address: string]
proc replStdinSingleCmd(line: string) =
requests.send line
toStdout()
echo ""
flushFile(stdout)
proc replStdin(x: ThreadParams) {.thread.} =
if gEmitEof:
echo DummyEof
@@ -271,10 +278,11 @@ proc replStdin(x: ThreadParams) {.thread.} =
echo Help
var line = ""
while readLineFromStdin("> ", line):
requests.send line
toStdout()
echo ""
flushFile(stdout)
replStdinSingleCmd(line)
proc replCmdline(x: ThreadParams) {.thread.} =
replStdinSingleCmd(x.address)
requests.send "quit"
proc replTcp(x: ThreadParams) {.thread.} =
var server = newSocket()
@@ -313,6 +321,7 @@ proc replEpc(x: ThreadParams) {.thread.} =
let port = connectToNextFreePort(server, "localhost")
server.listen()
echo port
stdout.flushFile()
var client = newSocket()
# Wait for connection
@@ -461,92 +470,6 @@ proc mainThread(graph: ModuleGraph; cache: IdentCache) =
var
inputThread: Thread[ThreadParams]
proc serveStdin(graph: ModuleGraph; cache: IdentCache) {.deprecated.} =
if gEmitEof:
echo DummyEof
while true:
let line = readLine(stdin)
execCmd line, graph, cache
echo DummyEof
flushFile(stdout)
else:
echo Help
var line = ""
while readLineFromStdin("> ", line):
execCmd line, graph, cache
echo ""
flushFile(stdout)
proc serveTcp(graph: ModuleGraph; cache: IdentCache) {.deprecated.} =
var server = newSocket()
server.bindAddr(gPort, gAddress)
var inp = "".TaintedString
server.listen()
while true:
var stdoutSocket = newSocket()
msgs.writelnHook = proc (line: string) =
stdoutSocket.send(line & "\c\L")
accept(server, stdoutSocket)
stdoutSocket.readLine(inp)
execCmd inp.string, graph, cache
stdoutSocket.send("\c\L")
stdoutSocket.close()
proc serveEpc(server: Socket; graph: ModuleGraph; cache: IdentCache) {.deprecated.} =
var client = newSocket()
# Wait for connection
accept(server, client)
if gLogging:
for it in searchPaths:
logStr(it)
msgs.writelnHook = proc (line: string) = logStr(line)
while true:
var
sizeHex = ""
size = 0
messageBuffer = ""
checkSanity(client, sizeHex, size, messageBuffer)
let
message = parseSexp($messageBuffer)
epcAPI = message[0].getSymbol
case epcAPI:
of "call":
let
uid = message[1].getNum
args = message[3]
gIdeCmd = parseIdeCmd(message[2].getSymbol)
case gIdeCmd
of ideChk:
setVerbosity(1)
# Use full path because other emacs plugins depends it
gListFullPaths = true
incl(gGlobalOptions, optIdeDebug)
var hints_or_errors = ""
sendEpc(hints_or_errors, string, msgs.writelnHook)
of ideSug, ideCon, ideDef, ideUse, ideDus, ideOutline, ideHighlight:
setVerbosity(0)
var suggests: seq[Suggest] = @[]
sendEpc(suggests, Suggest, suggestionResultHook)
else: discard
of "methods":
returnEpc(client, message[1].getNum, listEPC())
of "epc-error":
stderr.writeline("recieved epc error: " & $messageBuffer)
raise newException(IOError, "epc error")
else:
let errMessage = case epcAPI
of "return", "return-error":
"no return expected"
else:
"unexpected call: " & epcAPI
raise newException(EUnexpectedCommand, errMessage)
proc mainCommand(graph: ModuleGraph; cache: IdentCache) =
clearPasses()
registerPass verbosePass
@@ -556,9 +479,6 @@ proc mainCommand(graph: ModuleGraph; cache: IdentCache) =
isServing = true
wantMainModule()
add(searchPaths, options.libpath)
#if gProjectFull.len != 0:
# current path is always looked first for modules
# prependStr(searchPaths, gProjectPath)
# do not stop after the first error:
msgs.gErrorMax = high(int)
@@ -573,32 +493,13 @@ proc mainCommand(graph: ModuleGraph; cache: IdentCache) =
of mstdin: createThread(inputThread, replStdin, (gPort, gAddress))
of mtcp: createThread(inputThread, replTcp, (gPort, gAddress))
of mepc: createThread(inputThread, replEpc, (gPort, gAddress))
of mcmdline: createThread(inputThread, replCmdline,
(gPort, "sug \"" & options.gProjectFull & "\":" & gAddress))
mainThread(graph, cache)
joinThread(inputThread)
close(requests)
close(results)
when false:
case gMode
of mstdin:
compileProject(graph, cache)
#modules.gFuzzyGraphChecking = false
serveStdin(graph, cache)
of mtcp:
# until somebody accepted the connection, produce no output (logging is too
# slow for big projects):
msgs.writelnHook = proc (msg: string) = discard
compileProject(graph, cache)
#modules.gFuzzyGraphChecking = false
serveTcp(graph, cache)
of mepc:
var server = newSocket()
let port = connectToNextFreePort(server, "localhost")
server.listen()
echo port
compileProject(graph, cache)
serveEpc(server, graph, cache)
proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
var p = parseopt.initOptParser(cmd)
while true:
@@ -614,6 +515,10 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
gAddress = p.val
gMode = mtcp
of "stdin": gMode = mstdin
of "cmdline":
gMode = mcmdline
suggestVersion = 2
gAddress = p.val
of "epc":
gMode = mepc
gVerbosity = 0 # Port number gotta be first.
@@ -678,12 +583,6 @@ proc handleCmdLine(cache: IdentCache; config: ConfigRef) =
graph.suggestMode = true
mainCommand(graph, cache)
when false:
proc quitCalled() {.noconv.} =
writeStackTrace()
addQuitProc(quitCalled)
condsyms.initDefines()
defineSymbol "nimsuggest"
handleCmdline(newIdentCache(), newConfigRef())

View File

@@ -233,22 +233,25 @@ proc runEpcTest(filename: string): int =
let outp = p.outputStream
let inp = p.inputStream
var report = ""
var a = newStringOfCap(120)
#var a = newStringOfCap(120)
try:
# read the port number:
if outp.readLine(a):
let port = parseInt(a)
var socket = newSocket()
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)
doReport(filename, answer, resp, report)
else:
raise newException(ValueError, "cannot read port number")
#discard outp.readLine(a)
var i = 0
while not osproc.hasData(p) and i < 100:
os.sleep(50)
inc i
let a = outp.readAll().strip()
let port = parseInt(a)
var socket = newSocket()
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)
doReport(filename, answer, resp, report)
finally:
close(p)
if report.len > 0:
@@ -302,7 +305,9 @@ proc main() =
for x in walkFiles(getAppDir() / "tests/t*.nim"):
echo "Test ", x
let xx = expandFilename x
failures += runTest(xx)
when not defined(windows):
# XXX Windows IO redirection seems bonkers:
failures += runTest(xx)
failures += runEpcTest(xx)
if failures > 0:
quit 1