Skip to content

Commit

Permalink
windows compatibility through multisync (#125)
Browse files Browse the repository at this point in the history
* windows compatibility through multisync

* re-organize all imports

* make multisyncTask beautiful
  • Loading branch information
bung87 authored May 23, 2022
1 parent 91c6340 commit ad8ef18
Show file tree
Hide file tree
Showing 11 changed files with 126 additions and 84 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
nimcache/
nimlsp
tests/tnimlsp
4 changes: 2 additions & 2 deletions nimlsp.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ bin = @["nimlsp"]

requires "nim >= 1.0.0"
requires "jsonschema >= 0.2.1"
requires "asynctools"

# nimble test does not work for me out of the box
#task test, "Runs the test suite":
Expand All @@ -21,8 +22,7 @@ task debug, "Builds the language server":
exec "nim c --threads:on -d:nimcore -d:nimsuggest -d:debugCommunication -d:debugLogging -o:nimlsp src/nimlsp"

before test:
if not fileExists("nimlsp"):
exec "nimble build"
exec "nimble build"

task findNim, "Tries to find the current Nim installation":
echo NimVersion
Expand Down
2 changes: 1 addition & 1 deletion src/config.nims
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import os
import std / [os]

switch "path", getCurrentCompilerExe().parentDir.parentDir
130 changes: 79 additions & 51 deletions src/nimlsp.nim
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
import std / [os, osproc, tables, sets, hashes, strutils, uri, algorithm, asyncfile, asyncdispatch, streams]
import nimlsppkg / [baseprotocol, utfmapping, suggestlib, logger]
include nimlsppkg / messages
import algorithm
import streams
import tables
import strutils
import os
import hashes
import sets
import uri
import osproc
import asyncfile, asyncdispatch

const
version = block:
Expand Down Expand Up @@ -37,8 +28,6 @@ for i in 1..paramCount():
infoLog("Argument " & $i & ": " & paramStr(i))

var
ins = newAsyncFile(stdin.getOsFileHandle().AsyncFD)
outs = newAsyncFile(stdout.getOsFileHandle().AsyncFD)
gotShutdown = false
initialized = false
projectFiles = initTable[string, tuple[nimsuggest: NimSuggest, openFiles: OrderedSet[string]]]()
Expand Down Expand Up @@ -133,14 +122,25 @@ proc parseId(node: JsonNode): int =
else:
raise newException(MalformedFrame, "Invalid id node: " & repr(node))

proc respond(request: RequestMessage, data: JsonNode) {.async.} =
await outs.sendJson create(ResponseMessage, "2.0", parseId(request["id"]), some(data), none(ResponseError)).JsonNode
macro multisyncTask(body): untyped =
let arg = body[0][1]
result = quote do:
when `arg` is Stream:
`body`
else:
await `body`

proc respond(outs: Stream | AsyncFile, request: RequestMessage, data: JsonNode) {.multisync.} =
let resp = create(ResponseMessage, "2.0", parseId(request["id"]), some(data), none(ResponseError)).JsonNode
multisyncTask: outs.sendJson resp

proc error(request: RequestMessage, errorCode: int, message: string, data: JsonNode) {.async.} =
await outs.sendJson create(ResponseMessage, "2.0", parseId(request["id"]), none(JsonNode), some(create(ResponseError, errorCode, message, data))).JsonNode
proc error(outs: Stream | AsyncFile,request: RequestMessage, errorCode: int, message: string, data: JsonNode) {.multisync.} =
let resp = create(ResponseMessage, "2.0", parseId(request["id"]), none(JsonNode), some(create(ResponseError, errorCode, message, data))).JsonNode
multisyncTask: outs.sendJson resp

proc notify(notification: string, data: JsonNode) {.async.} =
await outs.sendJson create(NotificationMessage, "2.0", notification, some(data)).JsonNode
proc notify(outs: Stream | AsyncFile,notification: string, data: JsonNode) {.multisync.} =
let resp = create(NotificationMessage, "2.0", notification, some(data)).JsonNode
multisyncTask: outs.sendJson resp

type Certainty = enum
None,
Expand Down Expand Up @@ -203,28 +203,31 @@ if not fileExists(nimpath / "config/nim.cfg"):
"Supply the Nim project folder by adding it as an argument.\n"
quit 1

proc main(){.async.} =
proc main(ins: Stream | AsyncFile, outs: Stream | AsyncFile) {.multisync.} =
while true:
try:
debugLog "Trying to read frame"
let frame = await ins.readFrame
let frame = multisyncTask: ins.readFrame
debugLog "Got frame:"
infoLog frame
let message = frame.parseJson
whenValidStrict(message, RequestMessage):
debugLog "Got valid Request message of type " & message["method"].getStr
if not initialized and message["method"].getStr != "initialize":
await message.error(-32002, "Unable to accept requests before being initialized", newJNull())
multisyncTask:
outs.error(message, -32002, "Unable to accept requests before being initialized", newJNull())
continue
case message["method"].getStr:
of "shutdown":
debugLog "Got shutdown request, answering"
await message.respond(newJNull())
let resp = newJNull()
multisyncTask:
outs.respond(message, resp)
gotShutdown = true
of "initialize":
debugLog "Got initialize request, answering"
initialized = true
await message.respond(create(InitializeResult, create(ServerCapabilities,
let resp = create(InitializeResult, create(ServerCapabilities,
textDocumentSync = some(create(TextDocumentSyncOptions,
openClose = some(true),
change = some(TextDocumentSyncKind.Full.int),
Expand Down Expand Up @@ -258,7 +261,9 @@ proc main(){.async.} =
executeCommandProvider = none(ExecuteCommandOptions), #?: ExecuteCommandOptions
workspace = none(WorkspaceCapability), #?: WorkspaceCapability
experimental = none(JsonNode) #?: any
)).JsonNode)
)).JsonNode
multisyncTask:
outs.respond(message,resp)
of "textDocument/completion":
message.textDocumentRequest(CompletionParams, compRequest):
debugLog "Running equivalent of: sug ", uriToPath(fileuri), ";", filestash, ":",
Expand Down Expand Up @@ -306,7 +311,8 @@ proc main(){.async.} =
command = none(Command),
data = none(JsonNode)
).JsonNode
await message.respond completionItems
multisyncTask:
outs.respond(message, completionItems)
of "textDocument/hover":
message.textDocumentRequest(TextDocumentPositionParams, hoverRequest):
debugLog "Running equivalent of: def ", uriToPath(fileuri), ";", filestash, ":",
Expand All @@ -319,8 +325,9 @@ proc main(){.async.} =
debugLog "Found suggestions: ",
suggestions[0..(if suggestions.len > 10: 10 else: suggestions.high)],
(if suggestions.len > 10: " and " & $(suggestions.len-10) & " more" else: "")
var resp: JsonNode
if suggestions.len == 0:
await message.respond newJNull()
resp = newJNull()
else:
var label = suggestions[0].qualifiedPath.join(".")
if suggestions[0].forth != "":
Expand All @@ -333,15 +340,17 @@ proc main(){.async.} =
))
markedString = create(MarkedStringOption, "nim", label)
if suggestions[0].doc != "":
await message.respond create(Hover,
resp = create(Hover,
@[
markedString,
create(MarkedStringOption, "", suggestions[0].nimDocstring),
],
rangeopt
).JsonNode
else:
await message.respond create(Hover, markedString, rangeopt).JsonNode
resp = create(Hover, markedString, rangeopt).JsonNode;
multisyncTask:
outs.respond(message, resp)
of "textDocument/references":
message.textDocumentRequest(ReferenceParams, referenceRequest):
debugLog "Running equivalent of: use ", uriToPath(fileuri), ";", filestash, ":",
Expand All @@ -365,9 +374,12 @@ proc main(){.async.} =
)
).JsonNode
if response.len == 0:
await message.respond newJNull()
multisyncTask:
outs.respond(message, newJNull())
else:
await message.respond response
multisyncTask:
outs.respond(message, response)

of "textDocument/rename":
message.textDocumentRequest(RenameParams, renameRequest):
debugLog "Running equivalent of: use ", uriToPath(fileuri), ";", filestash, ":",
Expand All @@ -380,8 +392,9 @@ proc main(){.async.} =
debugLog "Found suggestions: ",
suggestions[0..(if suggestions.len > 10: 10 else: suggestions.high)],
(if suggestions.len > 10: " and " & $(suggestions.len-10) & " more" else: "")
var resp: JsonNode
if suggestions.len == 0:
await message.respond newJNull()
resp = newJNull()
else:
var textEdits = newJObject()
for suggestion in suggestions:
Expand All @@ -394,10 +407,11 @@ proc main(){.async.} =
),
renameRequest["newName"].getStr
).JsonNode
await message.respond create(WorkspaceEdit,
resp = create(WorkspaceEdit,
some(textEdits),
none(seq[TextDocumentEdit])
).JsonNode
multisyncTask: outs.respond(message, resp)
of "textDocument/definition":
message.textDocumentRequest(TextDocumentPositionParams, definitionRequest):
debugLog "Running equivalent of: def ", uriToPath(fileuri), ";", filestash, ":",
Expand All @@ -410,34 +424,37 @@ proc main(){.async.} =
debugLog "Found suggestions: ",
declarations[0..(if declarations.len > 10: 10 else: declarations.high)],
(if declarations.len > 10: " and " & $(declarations.len-10) & " more" else: "")
var resp: JsonNode
if declarations.len == 0:
await message.respond newJNull()
resp = newJNull()
else:
var response = newJarray()
resp = newJarray()
for declaration in declarations:
response.add create(Location,
resp.add create(Location,
"file://" & pathToUri(declaration.filepath),
create(Range,
create(Position, declaration.line-1, declaration.column),
create(Position, declaration.line-1, declaration.column + declaration.qualifiedPath[^1].len)
)
).JsonNode
await message.respond response
multisyncTask:
outs.respond(message, resp)
of "textDocument/documentSymbol":
message.textDocumentRequest(DocumentSymbolParams, symbolRequest):
debugLog "Running equivalent of: outline ", uriToPath(fileuri), ";", filestash
let syms = getNimsuggest(fileuri).outline(uriToPath(fileuri), dirtyfile = filestash)
debugLog "Found outlines: ",
syms[0..(if syms.len > 10: 10 else: syms.high)],
(if syms.len > 10: " and " & $(syms.len-10) & " more" else: "")
var resp: JsonNode
if syms.len == 0:
await message.respond newJNull()
resp = newJNull()
else:
var response = newJarray()
resp = newJarray()
for sym in syms.sortedByIt((it.line,it.column,it.quality)):
if sym.qualifiedPath.len != 2:
continue
response.add create(
resp.add create(
SymbolInformation,
sym.name[],
nimSymToLSPKind(sym.symKind).int,
Expand All @@ -451,7 +468,8 @@ proc main(){.async.} =
),
none(string)
).JsonNode
await message.respond response
multisyncTask:
outs.respond(message, resp)
of "textDocument/signatureHelp":
message.textDocumentRequest(TextDocumentPositionParams, sigHelpRequest):
debugLog "Running equivalent of: con ", uriToPath(fileuri), ";", filestash, ":",
Expand All @@ -468,15 +486,17 @@ proc main(){.async.} =
documentation = some(suggestion.nimDocstring),
parameters = none(seq[ParameterInformation])
)

await message.respond create(SignatureHelp,
let resp = create(SignatureHelp,
signatures = signatures,
activeSignature = some(0),
activeParameter = some(0)
).JsonNode
multisyncTask:
outs.respond(message, resp)
else:
debugLog "Unknown request"
await message.error(errorCode = -32600, message = "Unknown request: " & frame, data = newJObject())
multisyncTask:
outs.error(message, errorCode = -32600, message = "Unknown request: " & frame, data = newJObject())
continue
whenValidStrict(message, NotificationMessage):
debugLog "Got valid Notification message of type " & message["method"].getStr
Expand Down Expand Up @@ -613,15 +633,14 @@ proc main(){.async.} =
message,
none(seq[DiagnosticRelatedInformation])
)

await notify(
"textDocument/publishDiagnostics",
create(PublishDiagnosticsParams, f, response).JsonNode
)
await notify("textDocument/publishDiagnostics", create(PublishDiagnosticsParams,
let resp = create(PublishDiagnosticsParams, f, response).JsonNode
multisyncTask:
outs.notify("textDocument/publishDiagnostics", resp)
let resp = create(PublishDiagnosticsParams,
fileuri,
response).JsonNode
)
multisyncTask:
outs.notify("textDocument/publishDiagnostics", resp)
else:
warnLog "Got unknown notification message"
continue
Expand All @@ -635,4 +654,13 @@ proc main(){.async.} =
warnLog "Got exception: ", e.msg
continue

waitFor main()
when defined(windows):
var
ins = newFileStream(stdin)
outs = newFileStream(stdout)
main(ins, outs)
else:
var
ins = newAsyncFile(stdin.getOsFileHandle().AsyncFD)
outs = newAsyncFile(stdout.getOsFileHandle().AsyncFD)
waitFor main(ins, outs)
45 changes: 30 additions & 15 deletions src/nimlsppkg/baseprotocol.nim
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import strutils, parseutils, json
import asyncfile, asyncdispatch
import std / [strutils, parseutils, json, asyncfile, asyncdispatch, streams]
import logger

type
BaseProtocolError* = object of Defect

Expand All @@ -12,23 +12,38 @@ proc skipWhitespace(x: string, pos: int): int =
while result < x.len and x[result] in Whitespace:
inc result

proc sendFrame*(s: AsyncFile, frame: string) {.async} =
proc sendFrame*(s: Stream | AsyncFile, frame: string) {.multisync} =
when defined(debugCommunication):
infoLog(frame)
await s.write "Content-Length: " & $frame.len & "\r\n\r\n" & frame
let content = "Content-Length: " & $frame.len & "\r\n\r\n" & frame
when s is Stream:
s.write content
s.flush
else:
await s.write content

proc formFrame*( data: JsonNode): string =
var frame = newStringOfCap(1024)
toUgly(frame, data)
result = "Content-Length: " & $frame.len & "\r\n\r\n" & frame

proc sendJson*(s: AsyncFile, data: JsonNode) {.async.} =
proc sendJson*(s: Stream | AsyncFile, data: JsonNode) {.multisync.} =
var frame = newStringOfCap(1024)
toUgly(frame, data)
await s.sendFrame(frame)
when s is Stream:
s.sendFrame(frame)
else:
await s.sendFrame(frame)

proc readFrame*(s: AsyncFile): Future[string] {.async.} =
proc readFrame*(s: Stream | AsyncFile): Future[string] {.multisync.} =
var contentLen = -1
var headerStarted = false

var ln: string
while true:
var ln = await s.readLine()

when s is Stream:
ln = s.readLine()
else:
ln = await s.readLine()
if ln.len != 0:
headerStarted = true
let sep = ln.find(':')
Expand All @@ -52,11 +67,11 @@ proc readFrame*(s: AsyncFile): Future[string] {.async.} =
continue
else:
if contentLen != -1:
var buf = newString(contentLen)
var i = 0
while i < contentLen:
let r = await s.readBuffer(buf[i].addr, contentLen - i)
i += r
when s is Stream:
var buf = s.readStr(contentLen)
else:
var buf = newString(contentLen)
discard await s.readBuffer(buf[0].addr, contentLen)
when defined(debugCommunication):
infoLog(buf)
return buf
Expand Down
Loading

0 comments on commit ad8ef18

Please sign in to comment.