Skip to content

Commit

Permalink
[green] initialize from the client should call initialized on the server
Browse files Browse the repository at this point in the history
  • Loading branch information
jmgomez committed Aug 30, 2024
1 parent 61dda44 commit cbde740
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 21 deletions.
1 change: 1 addition & 0 deletions ls.nim
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,7 @@ proc createOrRestartNimsuggest*(ls: LanguageServer, projectFile: string, uri = "
ls.createOrRestartNimsuggest(projectFile, uri)
ls.sendStatusChanged()
errorCallback = proc (ns: Nimsuggest) {.gcsafe, raises: [].} =
debug "NimSuggest needed to be restarted due to an error "
warn "Server stopped.", projectFile = projectFile
if configuration.autoRestart.get(true) and ns.successfullCall:
ls.createOrRestartNimsuggest(projectFile, uri)
Expand Down
9 changes: 5 additions & 4 deletions lstransports.nim
Original file line number Diff line number Diff line change
Expand Up @@ -130,12 +130,13 @@ proc readStdin2*(ctx: ptr ReadStdinContext) {.thread.} =
ctx.value = processContentLength(inputStream) & CRLF
discard ctx.onStdReadSignal.fireSync()
discard ctx.onMainReadSignal.waitSync()


proc wrapContentWithContentLenght*(content: string): string =
let contentLenght = content.len + 1
&"{CONTENT_LENGTH}{contentLenght}{CRLF}{CRLF}{content}\n"

proc writeOutput*(ls: LanguageServer, content: JsonNode) =
let responseStr = $content
let contentLenght = responseStr.len + 1
let res = &"{CONTENT_LENGTH}{contentLenght}{CRLF}{CRLF}{responseStr}\n"
let res = wrapContentWithContentLenght($content)
try:
case ls.transportMode:
of stdio:
Expand Down
24 changes: 12 additions & 12 deletions nimlangserver.nim
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ proc registerRoutes(srv: RpcSocketServer, ls: LanguageServer) =
srv.register("$/setTrace", wrapRpc(partial(setTrace, ls)))


proc getNextFreePort(): Port=
proc getNextFreePort*(): Port=
let s = newSocket()
s.bindAddr(Port(0), "localhost")
let (_, port) = s.getLocalAddr
Expand Down Expand Up @@ -122,10 +122,8 @@ proc registerProcMonitor(ls: LanguageServer) =
globalLS.stopNimsuggestProcessesP()
exitnow(1)

proc main() =
let cmdLineParams = handleParams()
proc main*(cmdLineParams: CommandLineParams) =
debug "Starting nimlangserver", params = cmdLineParams

#[
`nimlangserver` supports both transports: stdio and socket. By default it uses stdio transport.
But we do construct a RPC socket server even in stdio mode, so that we can reuse the same code for both transports.
Expand All @@ -140,11 +138,13 @@ proc main() =
globalLS = addr ls #TODO use partial instead inside the func
ls.srv.registerRoutes(ls)
ls.registerProcMonitor()

runForever()
try:
main()
except Exception as e:
error "Error in main"
writeStackTrace e
quit(1)

when isMainModule:
try:
main(handleParams())
runForever()

except Exception as e:
error "Error in main"
writeStackTrace e
quit(1)
3 changes: 2 additions & 1 deletion tests/all.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import
# tnimlangserver, #These tests need to be redone
tsuggestapi
# tsuggestapi
tnimlangserver2
# tprojectsetup
115 changes: 115 additions & 0 deletions tests/tnimlangserver2.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import ../[
nimlangserver, ls, lstransports
]
import ../protocol/types
import std/[options, unittest, json, os, jsonutils]
import json_rpc/[rpcclient]
import chronicles



method call2*(client: RpcSocketClient, name: string,
params: JsonNode): Future[JsonString] {.async, gcsafe.} =
## Remotely calls the specified RPC method.
let id = client.getNextId()
let reqJson = newJObject()
reqJson["jsonrpc"] = %"2.0"
reqJson["id"] = %id.num
reqJson["method"] = %name
reqJson["params"] = params
let reqContent = wrapContentWithContentLenght($reqJson)
# var jsonBytes = requestTxEncode(name, params, id) & "\r\n"
var jsonBytes = reqContent
echo "Sending ", jsonBytes
if client.transport.isNil:
raise newException(JsonRpcError,
"Transport is not initialised (missing a call to connect?)")
# completed by processMessage.
var newFut = newFuture[JsonString]()
# add to awaiting responses
client.awaiting[id] = newFut

let res = await client.transport.write(jsonBytes)
# TODO: Add actions when not full packet was send, e.g. disconnect peer.
doAssert(res == jsonBytes.len)

return await newFut


proc processData2(client: RpcSocketClient) {.async: (raises: []).} =
while true:
var localException: ref JsonRpcError
while true:
try:
# var value = await client.transport.readLine(defaultMaxRequestLength)
var value = await processContentLength(client.transport)
if value == "":
# transmission ends
await client.transport.closeWait()
break

let res = client.processMessage(value)
if res.isErr:
error "Error when processing RPC message", msg=res.error
localException = newException(JsonRpcError, res.error)
break
except TransportError as exc:
localException = newException(JsonRpcError, exc.msg)
await client.transport.closeWait()
break
except CancelledError as exc:
localException = newException(JsonRpcError, exc.msg)
await client.transport.closeWait()
break

if localException.isNil.not:
for _,fut in client.awaiting:
fut.fail(localException)
if client.batchFut.isNil.not and not client.batchFut.completed():
client.batchFut.fail(localException)

# async loop reconnection and waiting
try:
info "Reconnect to server", address=`$`(client.address)
client.transport = await connect(client.address)
except TransportError as exc:
error "Error when reconnecting to server", msg=exc.msg
break
except CancelledError as exc:
error "Error when reconnecting to server", msg=exc.msg
break



proc onProcessMessage(client: RpcClient, line: string): Result[bool, string] {.gcsafe, raises: [].} =
# echo "onProcessmessage ", line #line contains the json response from the server. From here one could implement an actual client
return ok(true)


proc connect2*(client: RpcSocketClient, address: string, port: Port) {.async.} =
let addresses = resolveTAddress(address, port)
client.transport = await connect(addresses[0])
client.address = addresses[0]
client.loop = processData2(client)
client.onProcessMessage = onProcessMessage


suite "Nimlangserver":
let cmdParams = CommandLineParams(transport: some socket, port: getNextFreePort())
main(cmdParams) #we could accesss to the ls here to test against its state
let client = newRpcSocketClient()
waitFor client.connect2("localhost", cmdParams.port)

test "initialize from the client should call initialized on the server":
let initParams = InitializeParams(
processId: some(%getCurrentProcessId()),
rootUri: some("file:///tmp/"),
capabilities: ClientCapabilities())
let initializeResult = client.call2("initialize", %initParams).waitFor.string.parseJson.jsonTo(InitializeResult)

check initializeResult.capabilities.textDocumentSync.isSome





8 changes: 4 additions & 4 deletions tests/tsuggestapi.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import
../suggestapi, unittest, os, std/asyncnet, strutils, chronos
../suggestapi, unittest, os, std/asyncnet, strutils, chronos, options

const inputLine = "def skProc hw.a proc (){.noSideEffect, gcsafe.} hw/hw.nim 1 5 \"\" 100"
const inputLineWithEndLine = "outline skEnumField system.bool.true bool basic_types.nim 46 15 \"\" 100 4 11"
Expand All @@ -14,7 +14,7 @@ suite "Nimsuggest tests":
doAssert parseQualifiedPath("system.`..<`") == @["system", "`..<`"]

test "Parsing suggest":
doAssert parseSuggestDef(inputLine)[] == Suggest(
doAssert parseSuggestDef(inputLine).get[] == Suggest(
filePath: "hw/hw.nim",
qualifiedPath: @["hw", "a"],
symKind: "skProc",
Expand All @@ -25,8 +25,8 @@ suite "Nimsuggest tests":
section: ideDef)[]

test "Parsing suggest with endLine":
let res = parseSuggestDef(inputLineWithEndLine)[]
doAssert res == Suggest(
let res = parseSuggestDef(inputLineWithEndLine).get
doAssert res[] == Suggest(
filePath: "basic_types.nim",
qualifiedPath: @["system", "bool", "true"],
symKind: "skEnumField",
Expand Down

0 comments on commit cbde740

Please sign in to comment.