diff --git a/asyncprocmonitor.nim b/asyncprocmonitor.nim index 2ec4c1f..72b3513 100644 --- a/asyncprocmonitor.nim +++ b/asyncprocmonitor.nim @@ -1,12 +1,15 @@ # Monitor a client process and shutdown the current process, if the client # process is found to be dead -import os, asyncdispatch +import os, chronos, utils when defined(posix): import posix_utils import posix +type Callback* = proc() {.closure, gcsafe, raises: [].} + + when defined(windows): import winlean @@ -19,14 +22,12 @@ when defined(posix): var processExitCallbackCalled = false - proc checkProcCallback(fd: AsyncFD): bool = + proc checkProcCallback(arg: pointer) = if not processExitCallbackCalled: try: sendSignal(Pid(pid), 0) except: processExitCallbackCalled = true - result = cb(fd) - else: - result = true - - addTimer(1000, false, checkProcCallback) + + + addTimer(1000.int64, checkProcCallback) diff --git a/ls.nim b/ls.nim index 67776e5..79c199b 100644 --- a/ls.nim +++ b/ls.nim @@ -1,5 +1,5 @@ import macros, strformat, - faststreams/async_backend, + chronos, os, sugar, sequtils, hashes, osproc, suggestapi, protocol/enums, protocol/types, with, tables, strutils, sets, ./utils, chronicles, std/re, uri, "$nim/compiler/pathutils", @@ -111,9 +111,9 @@ type nimblePath*: Option[string] entryPoints*: seq[string] #when it's empty, means the nimble version doesnt dump it. - OnExitCallback* = proc (): Future[void] {.gcsafe.} #To be called when the server is shutting down - NotifyAction* = proc (name: string, params: JsonNode) {. gcsafe.} #Send a notification to the client - CallAction* = proc (name: string, params: JsonNode): Future[JsonNode] {. gcsafe.} #Send a request to the client + OnExitCallback* = proc (): Future[void] {.gcsafe, raises: [].} #To be called when the server is shutting down + NotifyAction* = proc (name: string, params: JsonNode) {. gcsafe, raises: [].} #Send a notification to the client + CallAction* = proc (name: string, params: JsonNode): Future[JsonNode] {. gcsafe, raises: [].} #Send a request to the client macro `%*`*(t: untyped, inputStream: untyped): untyped = result = newCall(bindSym("to", brOpen), @@ -130,7 +130,7 @@ proc orCancelled*[T](fut: Future[T], ls: LanguageServer, id: int): Future[T] {.a raise fut.error else: debug "Future cancelled.", id = id - let ex = newException(Cancelled, fmt "Cancelled {id}") + let ex = newException(CancelledError, fmt "Cancelled {id}") fut.fail(ex) debug "Future cancelled, throwing...", id = id raise ex @@ -202,55 +202,65 @@ proc parseWorkspaceConfiguration*(conf: JsonNode): NlsConfig = debug "Failed to parse the configuration.", error = getCurrentExceptionMsg() result = NlsConfig() -proc getWorkspaceConfiguration*(ls: LanguageServer): Future[NlsConfig] {.async.} = - parseWorkspaceConfiguration(ls.workspaceConfiguration.await) +proc getWorkspaceConfiguration*(ls: LanguageServer): Future[NlsConfig] {.async: (raises:[]).} = + try: + let conf = await ls.workspaceConfiguration + return parseWorkspaceConfiguration(conf) + except CatchableError: + discard -proc showMessage*(ls: LanguageServer, message: string, typ: MessageType) = - proc notify() = - ls.notify( - "window/showMessage", - %* { - "type": typ.int, - "message": message - }) - let verbosity = - ls - .getWorkspaceConfiguration - .waitFor - .notificationVerbosity.get(NlsNotificationVerbosity.nvInfo) - debug "ShowMessage ", message = message - case verbosity: - of nvInfo: - notify() - of nvWarning: - if typ.int <= MessageType.Warning.int : - notify() - of nvError: - if typ == MessageType.Error: +proc showMessage*(ls: LanguageServer, message: string, typ: MessageType) {.raises: [].} = + try: + proc notify() = + ls.notify( + "window/showMessage", + %* { + "type": typ.int, + "message": message + }) + let verbosity = + ls + .getWorkspaceConfiguration + .waitFor + .notificationVerbosity.get(NlsNotificationVerbosity.nvInfo) + debug "ShowMessage ", message = message + case verbosity: + of nvInfo: notify() - else: discard + of nvWarning: + if typ.int <= MessageType.Warning.int : + notify() + of nvError: + if typ == MessageType.Error: + notify() + else: discard + except CatchableError: + discard -proc getLspStatus*(ls: LanguageServer): NimLangServerStatus = +proc getLspStatus*(ls: LanguageServer): NimLangServerStatus {.raises: [].} = result.version = LSPVersion for projectFile, futNs in ls.projectFiles: - let futNs = ls.projectFiles[projectFile] + let futNs = ls.projectFiles.getOrDefault(projectFile, nil) if futNs.finished: - var ns: NimSuggest = futNs.read - var nsStatus = NimSuggestStatus( - projectFile: projectFile, - capabilities: ns.capabilities.toSeq, - version: ns.version, - path: ns.nimsuggestPath, - port: ns.port, - ) - result.nimsuggestInstances.add nsStatus + try: + var ns: NimSuggest = futNs.read + var nsStatus = NimSuggestStatus( + projectFile: projectFile, + capabilities: ns.capabilities.toSeq, + version: ns.version, + path: ns.nimsuggestPath, + port: ns.port, + ) + result.nimsuggestInstances.add nsStatus + except CatchableError: + discard for openFile in ls.openFiles.keys: let openFilePath = openFile.uriToPath result.openFiles.add openFilePath -proc sendStatusChanged*(ls: LanguageServer) = +proc sendStatusChanged*(ls: LanguageServer) {.raises: [].} = let status = ls.getLspStatus() ls.notify("extension/statusUpdate", %* status) @@ -375,17 +385,21 @@ proc getRootPath*(ip: InitializeParams): string = return ip.rootUri.get.uriToPath proc getWorkingDir(ls: LanguageServer, path: string): Future[string] {.async.} = - let - rootPath = AbsoluteDir(ls.initializeParams.getRootPath) - pathRelativeToRoot = string(AbsoluteFile(path).relativeTo(rootPath)) - mapping = ls.getWorkspaceConfiguration.await().workingDirectoryMapping.get(@[]) - - result = getCurrentDir() - - for m in mapping: - if m.projectFile == pathRelativeToRoot: - result = rootPath.string / m.directory - break; + try: + let + rootPath = AbsoluteDir(ls.initializeParams.getRootPath) + pathRelativeToRoot = string(AbsoluteFile(path).relativeTo(rootPath)) + mapping = ls.getWorkspaceConfiguration.await().workingDirectoryMapping.get(@[]) + + result = getCurrentDir() + + for m in mapping: + if m.projectFile == pathRelativeToRoot: + result = rootPath.string / m.directory + break; + except Exception: + #TODO Add msg + discard proc progressSupported(ls: LanguageServer): bool = result = ls.initializeParams @@ -410,7 +424,7 @@ proc progress*(ls: LanguageServer; token, kind: string, title = "") = proc workDoneProgressCreate*(ls: LanguageServer, token: string) = if ls.progressSupported: discard ls.call("window/workDoneProgress/create", - %ProgressParams(token: token)) + %ProgressParams(token: token)) proc cancelPendingFileChecks*(ls: LanguageServer, nimsuggest: Nimsuggest) = # stop all checks on file level if we are going to run checks on project @@ -482,7 +496,7 @@ proc sendDiagnostics*(ls: LanguageServer, diagnostics: seq[Suggest], path: strin else: ls.filesWithDiags.excl path -proc checkProject*(ls: LanguageServer, uri: string): Future[void] {.async, gcsafe.} = +proc checkProject*(ls: LanguageServer, uri: string): Future[void] {.async: (raises:[CatchableError]), gcsafe.} = if not ls.getWorkspaceConfiguration.await().autoCheckProject.get(true): return debug "Running diagnostics", uri = uri @@ -528,59 +542,64 @@ proc checkProject*(ls: LanguageServer, uri: string): Future[void] {.async, gcsaf nimsuggest.needsCheckProject = false callSoon() do () {.gcsafe.}: debug "Running delayed check project...", uri = uri - traceAsyncErrors ls.checkProject(uri) + # traceAsyncErrors ls.checkProject(uri) + asyncCheck ls.checkProject(uri) -proc createOrRestartNimsuggest*(ls: LanguageServer, projectFile: string, uri = ""): void {.gcsafe.} = - let - configuration = ls.getWorkspaceConfiguration().waitFor() - workingDir = ls.getWorkingDir(projectFile).waitFor() - (nimsuggestPath, version) = ls.getNimSuggestPathAndVersion(configuration, workingDir) - timeout = configuration.timeout.get(REQUEST_TIMEOUT) - restartCallback = proc (ns: Nimsuggest) {.gcsafe.} = - warn "Restarting the server due to requests being to slow", projectFile = projectFile - ls.showMessage(fmt "Restarting nimsuggest for file {projectFile} due to timeout.", - MessageType.Warning) - ls.createOrRestartNimsuggest(projectFile, uri) - ls.sendStatusChanged() - errorCallback = proc (ns: Nimsuggest) {.gcsafe.} = - warn "Server stopped.", projectFile = projectFile - if configuration.autoRestart.get(true) and ns.successfullCall: +proc createOrRestartNimsuggest*(ls: LanguageServer, projectFile: string, uri = ""): void {.gcsafe, raises: [].} = + try: + let + configuration = ls.getWorkspaceConfiguration().waitFor() + workingDir = ls.getWorkingDir(projectFile).waitFor() + (nimsuggestPath, version) = ls.getNimSuggestPathAndVersion(configuration, workingDir) + timeout = configuration.timeout.get(REQUEST_TIMEOUT) + restartCallback = proc (ns: Nimsuggest) {.gcsafe, raises: [].} = + warn "Restarting the server due to requests being to slow", projectFile = projectFile + ls.showMessage(fmt "Restarting nimsuggest for file {projectFile} due to timeout.", + MessageType.Warning) ls.createOrRestartNimsuggest(projectFile, uri) + ls.sendStatusChanged() + errorCallback = proc (ns: Nimsuggest) {.gcsafe, raises: [].} = + warn "Server stopped.", projectFile = projectFile + if configuration.autoRestart.get(true) and ns.successfullCall: + ls.createOrRestartNimsuggest(projectFile, uri) + else: + ls.showMessage(fmt "Server failed with {ns.errorMessage}.", + MessageType.Error) + ls.sendStatusChanged() + + + nimsuggestFut = createNimsuggest(projectFile, nimsuggestPath, version, + timeout, restartCallback, errorCallback, workingDir, configuration.logNimsuggest.get(false), + configuration.exceptionHintsEnabled) + token = fmt "Creating nimsuggest for {projectFile}" + + ls.workDoneProgressCreate(token) + + if ls.projectFiles.hasKey(projectFile): + var nimsuggestData = ls.projectFiles[projectFile] + nimSuggestData.addCallback() do (fut: Future[Nimsuggest]) -> void: + fut.read.stop() + ls.projectFiles[projectFile] = nimsuggestFut + ls.progress(token, "begin", fmt "Restarting nimsuggest for {projectFile}") + else: + ls.progress(token, "begin", fmt "Creating nimsuggest for {projectFile}") + ls.projectFiles[projectFile] = nimsuggestFut + + nimsuggestFut.addCallback do (fut: Future[Nimsuggest]): + if fut.read.failed: + let msg = fut.read.errorMessage + ls.showMessage(fmt "Nimsuggest initialization for {projectFile} failed with: {msg}", + MessageType.Error) else: - ls.showMessage(fmt "Server failed with {ns.errorMessage}.", - MessageType.Error) + ls.showMessage(fmt "Nimsuggest initialized for {projectFile}", + MessageType.Info) + # traceAsyncErrors ls.checkProject(uri) + asyncCheck ls.checkProject(uri) + fut.read().openFiles.incl uri + ls.progress(token, "end") ls.sendStatusChanged() - - - nimsuggestFut = createNimsuggest(projectFile, nimsuggestPath, version, - timeout, restartCallback, errorCallback, workingDir, configuration.logNimsuggest.get(false), - configuration.exceptionHintsEnabled) - token = fmt "Creating nimsuggest for {projectFile}" - - ls.workDoneProgressCreate(token) - - if ls.projectFiles.hasKey(projectFile): - var nimsuggestData = ls.projectFiles[projectFile] - nimSuggestData.addCallback() do (fut: Future[Nimsuggest]) -> void: - fut.read.stop() - ls.projectFiles[projectFile] = nimsuggestFut - ls.progress(token, "begin", fmt "Restarting nimsuggest for {projectFile}") - else: - ls.progress(token, "begin", fmt "Creating nimsuggest for {projectFile}") - ls.projectFiles[projectFile] = nimsuggestFut - - nimsuggestFut.addCallback do (fut: Future[Nimsuggest]): - if fut.read.failed: - let msg = fut.read.errorMessage - ls.showMessage(fmt "Nimsuggest initialization for {projectFile} failed with: {msg}", - MessageType.Error) - else: - ls.showMessage(fmt "Nimsuggest initialized for {projectFile}", - MessageType.Info) - traceAsyncErrors ls.checkProject(uri) - fut.read().openFiles.incl uri - ls.progress(token, "end") - ls.sendStatusChanged() + except CatchableError: + discard proc getNimsuggest*(ls: LanguageServer, uri: string): Future[Nimsuggest] {.async.} = let projectFile = await ls.openFiles[uri].projectFile @@ -662,46 +681,49 @@ proc stopNimsuggestProcessesP*(ls: ptr LanguageServer) = waitFor stopNimsuggestProcesses(ls[]) proc getProjectFile*(fileUri: string, ls: LanguageServer): Future[string] {.async.} = - let - rootPath = AbsoluteDir(ls.initializeParams.getRootPath) - pathRelativeToRoot = string(AbsoluteFile(fileUri).relativeTo(rootPath)) - mappings = ls.getWorkspaceConfiguration.await().projectMapping.get(@[]) - - for mapping in mappings: - if find(cstring(pathRelativeToRoot), re(mapping.fileRegex), 0, pathRelativeToRoot.len) != -1: - result = string(rootPath) / mapping.projectFile - if fileExists(result): - trace "getProjectFile", project = result, uri = fileUri, matchedRegex = mapping.fileRegex - return result - else: - trace "getProjectFile does not match", uri = fileUri, matchedRegex = mapping.fileRegex - - once: #once we refactor the project to chronos, we may move this code into init. Right now it hangs for some odd reason - let rootPath = ls.initializeParams.getRootPath - if rootPath != "": - let nimbleFiles = walkFiles(rootPath / "*.nimble").toSeq - if nimbleFiles.len > 0: - let nimbleFile = nimbleFiles[0] - let nimbleDumpInfo = ls.getNimbleDumpInfo(nimbleFile) - ls.entryPoints = nimbleDumpInfo.getNimbleEntryPoints(ls.initializeParams.getRootPath) - # ls.showMessage(fmt "Found entry point {ls.entryPoints}?", MessageType.Info) - for entryPoint in ls.entryPoints: - debug "Starting nimsuggest for entry point ", entry = entryPoint - if entryPoint notin ls.projectFiles: - ls.createOrRestartNimsuggest(entryPoint) - - result = ls.getProjectFileAutoGuess(fileUri) - if result in ls.projectFiles: - let ns = await ls.projectFiles[result] - let isKnown = await ns.isKnown(fileUri) - if ns.canHandleUnknown and not isKnown: - debug "File is not known by nimsuggest", uri = fileUri, projectFile = result + try: + let + rootPath = AbsoluteDir(ls.initializeParams.getRootPath) + pathRelativeToRoot = string(AbsoluteFile(fileUri).relativeTo(rootPath)) + mappings = ls.getWorkspaceConfiguration.await().projectMapping.get(@[]) + + for mapping in mappings: + if find(cstring(pathRelativeToRoot), re(mapping.fileRegex), 0, pathRelativeToRoot.len) != -1: + result = string(rootPath) / mapping.projectFile + if fileExists(result): + trace "getProjectFile", project = result, uri = fileUri, matchedRegex = mapping.fileRegex + return result + else: + trace "getProjectFile does not match", uri = fileUri, matchedRegex = mapping.fileRegex + + once: #once we refactor the project to chronos, we may move this code into init. Right now it hangs for some odd reason + let rootPath = ls.initializeParams.getRootPath + if rootPath != "": + let nimbleFiles = walkFiles(rootPath / "*.nimble").toSeq + if nimbleFiles.len > 0: + let nimbleFile = nimbleFiles[0] + let nimbleDumpInfo = ls.getNimbleDumpInfo(nimbleFile) + ls.entryPoints = nimbleDumpInfo.getNimbleEntryPoints(ls.initializeParams.getRootPath) + # ls.showMessage(fmt "Found entry point {ls.entryPoints}?", MessageType.Info) + for entryPoint in ls.entryPoints: + debug "Starting nimsuggest for entry point ", entry = entryPoint + if entryPoint notin ls.projectFiles: + ls.createOrRestartNimsuggest(entryPoint) + + result = ls.getProjectFileAutoGuess(fileUri) + if result in ls.projectFiles: + let ns = await ls.projectFiles[result] + let isKnown = await ns.isKnown(fileUri) + if ns.canHandleUnknown and not isKnown: + debug "File is not known by nimsuggest", uri = fileUri, projectFile = result + result = fileUri + + if result == "": result = fileUri - if result == "": - result = fileUri - - debug "getProjectFile", project = result, fileUri = fileUri + debug "getProjectFile", project = result, fileUri = fileUri + except Exception: + discard proc warnIfUnknown*(ls: LanguageServer, ns: Nimsuggest, uri: string, projectFile: string): Future[void] {.async, gcsafe.} = diff --git a/nimlangserver.nimble b/nimlangserver.nimble index dfb929e..d2bc3b7 100644 --- a/nimlangserver.nimble +++ b/nimlangserver.nimble @@ -5,12 +5,13 @@ version = "1.5.0" author = "The core Nim team" description = "Nim language server for IDEs" license = "MIT" -bin = @["nimlangserver"] +bin = @["nimlangserver2"] skipDirs = @["tests"] -requires "nim >= 2.0.0", - "https://github.com/nickysn/asynctools#fixes_for_nimlangserver", - "https://github.com/yyoncho/nim-json-rpc#notif-changes", +requires "nim == 2.0.8", "chronos", + # "https://github.com/nickysn/asynctools#fixes_for_nimlangserver", + # "https://github.com/yyoncho/nim-json-rpc#notif-changes", + "json_rpc#head", "with", "chronicles", "serialization", diff --git a/routes.nim b/routes.nim index 7f42ef2..d2a5989 100644 --- a/routes.nim +++ b/routes.nim @@ -1,6 +1,5 @@ -import macros, strformat, - faststreams/async_backend, - json_rpc/streamconnection, json_rpc/server, os, sugar, sequtils, +import macros, strformat, chronos, + json_rpc/server, os, sugar, sequtils, suggestapi, protocol/enums, protocol/types, with, tables, strutils, ./utils, chronicles, asyncprocmonitor, std/strscans, json_serialization, @@ -11,15 +10,18 @@ import macros, strformat, proc initialize*(p: tuple[ls: LanguageServer, onExit: OnExitCallback], params: InitializeParams): Future[InitializeResult] {.async.} = - proc onClientProcessExitAsync(): Future[void] {.async.} = - debug "onClientProcessExitAsync" - await p.ls.stopNimsuggestProcesses - await p.onExit() - - proc onClientProcessExit(fd: AsyncFD): bool = - debug "onClientProcessExit" - waitFor onClientProcessExitAsync() - result = true + proc onClientProcessExitAsync(): Future[void] {.async:(raises: [Exception]).} = + debug "onClientProcessExitAsync" + await p.ls.stopNimsuggestProcesses + await p.onExit() + + proc onClientProcessExit() {.closure, gcsafe, raises: [].} = + {.cast(raises: []).}: + try: + debug "onClientProcessExit" + waitFor onClientProcessExitAsync() + except: + discard debug "Initialize received..." if params.processId.isSome: @@ -227,12 +229,12 @@ proc documentSymbols*(ls: LanguageServer, params: DocumentSymbolParams, id: int) .await() .map(toSymbolInformation) -proc scheduleFileCheck(ls: LanguageServer, uri: string) {.gcsafe.} = +proc scheduleFileCheck(ls: LanguageServer, uri: string) {.gcsafe, raises: [].} = if not ls.getWorkspaceConfiguration().waitFor().autoCheckFile.get(true): return # schedule file check after the file is modified - let fileData = ls.openFiles[uri] + let fileData = ls.openFiles.getOrDefault(uri) if fileData.cancelFileCheck != nil and not fileData.cancelFileCheck.finished: fileData.cancelFileCheck.complete() @@ -246,12 +248,16 @@ proc scheduleFileCheck(ls: LanguageServer, uri: string) {.gcsafe.} = sleepAsync(FILE_CHECK_DELAY).addCallback() do (): if not cancelFuture.finished: fileData.checkInProgress = true - ls.checkFile(uri).addCallback() do() {.gcsafe.}: - ls.openFiles[uri].checkInProgress = false - if fileData.needsChecking: - fileData.needsChecking = false - ls.scheduleFileCheck(uri) - + ls.checkFile(uri).addCallback() do() {.gcsafe, raises:[].}: + try: + ls.openFiles[uri].checkInProgress = false + if fileData.needsChecking: + fileData.needsChecking = false + ls.scheduleFileCheck(uri) + except KeyError: + discard + # except Exception: + # discard proc toMarkedStrings(suggest: Suggest): seq[MarkedStringOption] = @@ -317,7 +323,7 @@ proc prepareRename*(ls: LanguageServer, params: PrepareRenameParams, return newJNull() # Check if the symbol belongs to the project let projectDir = ls.initializeParams.getRootPath - if def[0].filePath.isRelativeTo(projectDir): + if def[0].filePath.isRelTo(projectDir): return %def[0].toLocation().range return newJNull() @@ -335,7 +341,7 @@ proc rename*(ls: LanguageServer, params: RenameParams, id: int): Future[Workspac for reference in references: # Only rename symbols in the project. # If client supports prepareRename then an error will already have been thrown - if reference.uri.uriToPath().isRelativeTo(projectDir): + if reference.uri.uriToPath().isRelTo(projectDir): if reference.uri notin edits: edits[reference.uri] = newJArray() edits[reference.uri] &= %TextEdit(range: reference.range, newText: params.newName) @@ -453,7 +459,8 @@ proc executeCommand*(ls: LanguageServer, params: ExecuteCommandParams): ls.createOrRestartNimsuggest(projectFile, projectFile.pathToUri) of CHECK_PROJECT_COMMAND: debug "Checking project", projectFile = projectFile - ls.checkProject(projectFile.pathToUri).traceAsyncErrors + # ls.checkProject(projectFile.pathToUri).traceAsyncErrors + asyncCheck ls.checkProject(projectFile.pathToUri) of RECOMPILE_COMMAND: debug "Clean build", projectFile = projectFile let @@ -466,7 +473,8 @@ proc executeCommand*(ls: LanguageServer, params: ExecuteCommandParams): .recompile() .addCallback() do (): ls.progress(token, "end") - ls.checkProject(projectFile.pathToUri).traceAsyncErrors + # ls.checkProject(projectFile.pathToUri).traceAsyncErrors + asyncCheck ls.checkProject(projectFile.pathToUri) result = newJNull() @@ -562,21 +570,21 @@ proc extractId (id: JsonNode): int = if id.kind == JString: discard parseInt(id.getStr, result) -proc shutdown*(ls: LanguageServer, input: JsonNode): Future[RpcResult] {.async, gcsafe, raises: [Defect, CatchableError, Exception].} = +proc shutdown*(ls: LanguageServer, input: JsonNode): Future[JsonNode] {.async, gcsafe, raises: [Defect, CatchableError, Exception].} = debug "Shutting down" await ls.stopNimsuggestProcesses() ls.isShutdown = true let id = input{"id"}.extractId - result = some(StringOfJson("null")) + result = newJNull() trace "Shutdown complete" proc exit*(p: tuple[ls: LanguageServer, onExit: OnExitCallback], _: JsonNode): - Future[RpcResult] {.async, gcsafe, raises: [Defect, CatchableError, Exception].} = + Future[JsonNode] {.async, gcsafe, raises: [Defect, CatchableError, Exception].} = if not p.ls.isShutdown: debug "Received an exit request without prior shutdown request" await p.ls.stopNimsuggestProcesses() debug "Quitting process" - result = none[StringOfJson]() + result = newJNull() await p.onExit() #Notifications @@ -629,7 +637,7 @@ proc didSave*(ls: LanguageServer, params: DidSaveTextDocumentParams): if ls.getWorkspaceConfiguration().await().checkOnSave.get(true): debug "Checking project", uri = uri - traceAsyncErrors ls.checkProject(uri) + # traceAsyncErrors ls.checkProject(uri) var toStop = newTable[string, Nimsuggest]() #We first get the project file for the current file so we can test if this file recently imported another project diff --git a/suggestapi.nim b/suggestapi.nim index 3f47810..a19789c 100644 --- a/suggestapi.nim +++ b/suggestapi.nim @@ -9,11 +9,11 @@ import osproc, sequtils, streams, protocol/enums, - asyncdispatch, ./utils, chronicles, protocol/types, - std/options + std/options, + chronos const REQUEST_TIMEOUT* = 120000 const HighestSupportedNimSuggestProtocolVersion = 4 @@ -29,7 +29,7 @@ type IdeCmd* = enum ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideMod, ideHighlight, ideOutline, ideKnown, ideMsg, ideProject, ideType, ideExpand - NimsuggestCallback = proc(self: Nimsuggest): void {.gcsafe.} + NimsuggestCallback = proc(self: Nimsuggest): void {.gcsafe, raises: [].} Suggest* = ref object section*: IdeCmd @@ -218,7 +218,7 @@ proc parseSuggestInlayHint*(line: string): SuggestInlayHint = proc name*(sug: Suggest): string = return sug.qualifiedPath[^1] -proc markFailed(self: Nimsuggest, errMessage: string) = +proc markFailed(self: Nimsuggest, errMessage: string) {.raises: [].} = self.failed = true self.errorMessage = errMessage if self.errorCallback != nil: @@ -377,6 +377,9 @@ proc createNimsuggest*(root: string): Future[Nimsuggest] {.gcsafe.} = proc (ns: Nimsuggest) = discard, proc (ns: Nimsuggest) = discard) +proc toString(bytes: openarray[byte]): string = + result = newString(bytes.len) + copyMem(result[0].addr, bytes[0].unsafeAddr, bytes.len) proc processQueue(self: Nimsuggest): Future[void] {.async.}= debug "processQueue", size = self.requestQueue.len @@ -391,30 +394,36 @@ proc processQueue(self: Nimsuggest): Future[void] {.async.}= req.future.complete @[] else: benchmark req.commandString: - let socket = newAsyncSocket() + # let socket = newAsyncSocket() var res: seq[Suggest] = @[] if not self.timeoutCallback.isNil: debug "timeoutCallback is set", timeout = self.timeout doWithTimeout(req.future, self.timeout, fmt "running {req.commandString}").addCallback do (f: Future[bool]): if not f.failed and not f.read(): - debug "Calling restart" - self.timeoutCallback(self) - - await socket.connect("127.0.0.1", Port(self.port)) - await socket.send(req.commandString & "\c\L") + debug "Calling restart" + self.timeoutCallback(self) + let ta = initTAddress(&"127.0.0.1:{self.port}") + let transport = await ta.connect() + discard await transport.write(req.commandString & "\c\L") + # await socket.connect("127.0.0.1", Port(self.port)) + # await socket.send(req.commandString & "\c\L") const bufferSize = 1024 * 1024 * 4 var buffer:seq[byte] = newSeq[byte](bufferSize); - var content = ""; - var received = await socket.recvInto(addr buffer[0], bufferSize) + # var content = ""; + # var received = await socket.recvInto(addr buffer[0], bufferSize) + var data = await transport.read() + let content = data.toString() + + # while received != 0: + # let chunk = newString(received) + # copyMem(chunk[0].unsafeAddr, buffer[0].unsafeAddr, received) + # content = content & chunk + # # received = await socket.recvInto(addr buffer[0], bufferSize) + # var received = await transport.readExactly(addr buffer[0], bufferSize) - while received != 0: - let chunk = newString(received) - copyMem(chunk[0].unsafeAddr, buffer[0].unsafeAddr, received) - content = content & chunk - received = await socket.recvInto(addr buffer[0], bufferSize) for lineStr in content.splitLines: if lineStr != "": @@ -439,10 +448,11 @@ proc processQueue(self: Nimsuggest): Future[void] {.async.}= debug "Sending result(s)", length = res.len req.future.complete res self.successfullCall = true - socket.close() + # socket.close() + transport.close() else: debug "Call was cancelled before sending the result", command = req.command - socket.close() + transport.close() self.processing = false proc call*(self: Nimsuggest, command: string, file: string, dirtyFile: string, diff --git a/utils.nim b/utils.nim index 350b52a..c8a0647 100644 --- a/utils.nim +++ b/utils.nim @@ -1,4 +1,5 @@ -import unicode, uri, strformat, os, strutils, faststreams/async_backend, chronicles, tables +import std/[unicode, uri, strformat, os, strutils] +import chronos, chronicles type FingerTable = seq[tuple[u16pos, offset: int]] @@ -125,14 +126,72 @@ proc catchOrQuit*(error: Exception) = quit 1 proc traceAsyncErrors*(fut: Future) = - fut.addCallback do (): + fut.addCallback do (data: pointer): if not fut.error.isNil: catchOrQuit fut.error[] -iterator groupBy*[T, U](s: openArray[T], f: proc(a: T): U {.gcsafe.}): tuple[k: U, v: seq[T]] = +iterator groupBy*[T, U](s: openArray[T], f: proc(a: T): U {.gcsafe, raises: [].}): tuple[k: U, v: seq[T]] = var t = initTable[U, seq[T]]() for x in s: let fx = f(x) t.mGetOrPut(fx, @[]).add(x) for x in t.pairs: yield x + +#Compatibility layer with asyncdispatch +proc callSoon*(cb: proc () {.gcsafe.}) {.gcsafe.} = + proc cbWrapper() {.gcsafe.} = + try: + {.cast(raises:[]).}: + cb() + except CatchableError: + discard #TODO handle + callSoon() do (data: pointer) {.gcsafe,.}: + cbWrapper() + +proc addCallback*(future: FutureBase, cb: proc() {.closure, gcsafe, raises: [].}) = + ## Adds the callbacks proc to be called when the future completes. + ## + ## If future has already completed then `cb` will be called immediately. + assert cb != nil + if future.finished: + callSoon() do (data: pointer) {.gcsafe,.}: + cb() + else: + future.addCallback() do (data: pointer) {.gcsafe,.}: + cb() + +proc addCallbackNoEffects*[T](future: Future[T], + cb: proc (future: Future[T]) {.closure, gcsafe, raises: [].}) = + ## Adds the callbacks proc to be called when the future completes. + ## + ## If future has already completed then `cb` will be called immediately. + future.addCallback( + proc() = + cb(future) + ) + +proc addCallback*[T](future: Future[T], + cb: proc (future: Future[T]) {.closure, gcsafe.}) = + ## Adds the callbacks proc to be called when the future completes. + ## + ## If future has already completed then `cb` will be called immediately. + proc cbWrapper(fut: Future[T]) {.closure, gcsafe, raises: [].} = + try: + {.cast(raises:[]).}: + cb(fut) + except CatchableError: + discard #TODO handle + + future.addCallbackNoEffects( + proc(fut: Future[T]) {.closure, gcsafe, raises: [].} = + cbWrapper(future) + + ) + +proc isRelTo*(path, base: string): bool {.raises:[].} = + ### isRelativeTo version that do not throws + try: + isRelativeTo(path, base) + except Exception: + false