diff --git a/ls.nim b/ls.nim index 46de73f..2183a0f 100644 --- a/ls.nim +++ b/ls.nim @@ -99,7 +99,7 @@ type startTime*: DateTime endTime*: DateTime state*: PendingRequestState - + LanguageServer* = ref object clientCapabilities*: ClientCapabilities extensionCapabilities*: set[LspExtensionCapability] @@ -129,7 +129,8 @@ type socketTransport*: StreamTransport of stdio: outStream*: FileStream - stdinContext*: ptr ReadStdinContext + stdinContext*: ptr ReadStdinContext + projectErrors*: seq[ProjectError] #List of errors (crashes) nimsuggest has had since the lsp session started Certainty* = enum None, @@ -311,6 +312,7 @@ proc getLspStatus*(ls: LanguageServer): NimLangServerStatus {.raises: [].} = result.openFiles.add openFilePath result.pendingRequests = ls.pendingRequests.values.toSeq.map(toPendingRequestStatus) + result.projectErrors = ls.projectErrors proc sendStatusChanged*(ls: LanguageServer) {.raises: [].} = let status: NimLangServerStatus = ls.getLspStatus() @@ -604,7 +606,30 @@ proc checkProject*(ls: LanguageServer, uri: string): Future[void] {.async, gcsaf debug "Running delayed check project...", uri = uri traceAsyncErrors ls.checkProject(uri) -proc createOrRestartNimsuggest*(ls: LanguageServer, projectFile: string, uri = ""): void {.gcsafe, raises: [].} = +proc createOrRestartNimsuggest*(ls: LanguageServer, projectFile: string, uri = "") {.gcsafe, raises: [].} + +proc onErrorCallback(args: (LanguageServer, string), project: Project) = + let + ls = args[0] + uri = args[1] + debug "NimSuggest needed to be restarted due to an error " + let configuration = ls.getWorkspaceConfiguration().waitFor() + warn "Server stopped.", projectFile = project.file + try: + if configuration.autoRestart.get(true) and project.ns.completed and project.ns.read.successfullCall: + ls.createOrRestartNimsuggest(project.file, uri) + else: + ls.showMessage(fmt "Server failed with {project.errorMessage}.", + MessageType.Error) + except CatchableError as ex: + error "An error has ocurred while handling nimsuggest err", msg = ex.msg + writeStacktrace(ex) + finally: + ls.projectErrors.add ProjectError(projectFile: project.file, errorMessage: project.errorMessage) + ls.sendStatusChanged() + + +proc createOrRestartNimsuggest*(ls: LanguageServer, projectFile: string, uri = "") {.gcsafe, raises: [].} = try: let configuration = ls.getWorkspaceConfiguration().waitFor() @@ -617,16 +642,7 @@ proc createOrRestartNimsuggest*(ls: LanguageServer, projectFile: string, uri = " MessageType.Warning) 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) - else: - ls.showMessage(fmt "Server failed with {ns.errorMessage}.", - MessageType.Error) - ls.sendStatusChanged() - + errorCallback = partial(onErrorCallback, (ls, uri)) #TODO instead of waiting here, this whole function should be async. projectNext = waitFor createNimsuggest(projectFile, nimsuggestPath, version, timeout, restartCallback, errorCallback, workingDir, configuration.logNimsuggest.get(false), @@ -645,8 +661,8 @@ proc createOrRestartNimsuggest*(ls: LanguageServer, projectFile: string, uri = " ls.projectFiles[projectFile] = projectNext projectNext.ns.addCallback do (fut: Future[Nimsuggest]): - if fut.read.failed: - let msg = fut.read.errorMessage + if fut.read.project.failed: + let msg = fut.read.project.errorMessage ls.showMessage(fmt "Nimsuggest initialization for {projectFile} failed with: {msg}", MessageType.Error) else: diff --git a/nimble.lock b/nimble.lock index 3bff38e..1ce7b0e 100644 --- a/nimble.lock +++ b/nimble.lock @@ -1,16 +1,6 @@ { "version": 2, "packages": { - "nim": { - "version": "2.0.8", - "vcsRevision": "5935c3bfa9fec6505394867b23510eb5cbab3dbf", - "url": "https://github.com/nim-lang/Nim.git", - "downloadMethod": "git", - "dependencies": [], - "checksums": { - "sha1": "46333e8f4bda41dd6d3852a3f5fa4975b96b66a2" - } - }, "unittest2": { "version": "0.2.2", "vcsRevision": "e96f3215030cbfa13abc2f5827069b6f8ba87e38", @@ -223,6 +213,16 @@ "sha1": "5c451e05b217e19d5a5ebaf087ecbb8576257a73" } }, + "nim": { + "version": "2.0.8", + "vcsRevision": "5935c3bfa9fec6505394867b23510eb5cbab3dbf", + "url": "https://github.com/nim-lang/Nim.git", + "downloadMethod": "git", + "dependencies": [], + "checksums": { + "sha1": "46333e8f4bda41dd6d3852a3f5fa4975b96b66a2" + } + }, "with": { "version": "0.5.0", "vcsRevision": "91c51ec1051bf0cb518cf9bb78114e2a84b03da7", diff --git a/suggestapi.nim b/suggestapi.nim index bda0222..72158ba 100644 --- a/suggestapi.nim +++ b/suggestapi.nim @@ -31,6 +31,7 @@ type ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideMod, ideHighlight, ideOutline, ideKnown, ideMsg, ideProject, ideType, ideExpand NimsuggestCallback = proc(self: Nimsuggest): void {.gcsafe, raises: [].} + ProjectCallback = proc(self: Project): void {.gcsafe, raises: [].} Suggest* = ref object section*: IdeCmd @@ -73,13 +74,10 @@ type tooltip*: string NimsuggestImpl* = object - failed*: bool - errorMessage*: string checkProjectInProgress*: bool needsCheckProject*: bool openFiles*: OrderedSet[string] successfullCall*: bool - errorCallback*: NimsuggestCallback port*: int root: string requestQueue: Deque[SuggestCall] @@ -90,6 +88,7 @@ type capabilities*: set[NimSuggestCapability] nimSuggestPath*: string version*: string + project*: Project NimSuggest* = ref NimsuggestImpl @@ -97,6 +96,9 @@ type ns*: Future[NimSuggest] file*: string process*: AsyncProcessRef + errorCallback*: ProjectCallback + errorMessage*: string + failed*: bool func canHandleUnknown*(ns: Nimsuggest): bool = nsUnknownFile in ns.capabilities @@ -224,7 +226,7 @@ proc parseSuggestInlayHint*(line: string): SuggestInlayHint = proc name*(sug: Suggest): string = return sug.qualifiedPath[^1] -proc markFailed(self: Nimsuggest, errMessage: string) {.raises: [].} = +proc markFailed(self: Project, errMessage: string) {.raises: [].} = self.failed = true self.errorMessage = errMessage if self.errorCallback != nil: @@ -296,28 +298,29 @@ proc getNimsuggestCapabilities*(nimsuggestPath: string): proc logNsError(project: Project) {.async.} = let err = string.fromBytes(project.process.stderrStream.read().await) error "NimSuggest Error (stderr)", err = err - # ns.markFailed(err) #TODO Error handling should be at the project level + project.markFailed(err) #TODO Error handling should be at the project level proc createNimsuggest*(root: string, nimsuggestPath: string, version: string, timeout: int, timeoutCallback: NimsuggestCallback, - errorCallback: NimsuggestCallback, + errorCallback: ProjectCallback, workingDir = getCurrentDir(), enableLog: bool = false, enableExceptionInlayHints: bool = false): Future[Project] {.async, gcsafe.} = result = Project(file: root) result.ns = newFuture[NimSuggest]() + result.errorCallback = errorCallback let ns = Nimsuggest() ns.requestQueue = Deque[SuggestCall]() ns.root = root ns.timeout = timeout ns.timeoutCallback = timeoutCallback - ns.errorCallback = errorCallback ns.nimSuggestPath = nimsuggestPath ns.version = version + ns.project = result info "Starting nimsuggest", root = root, timeout = timeout, path = nimsuggestPath, workingDir = workingDir @@ -348,12 +351,12 @@ proc createNimsuggest*(root: string, result.ns.complete(ns) else: error "Unable to start nimsuggest. Unable to find binary on the $PATH", nimsuggestPath = nimsuggestPath - ns.markFailed fmt "Unable to start nimsuggest. `{nimsuggestPath}` is not present on the PATH" + result.markFailed fmt "Unable to start nimsuggest. `{nimsuggestPath}` is not present on the PATH" proc createNimsuggest*(root: string): Future[Project] {.gcsafe.} = result = createNimsuggest(root, "nimsuggest", "", REQUEST_TIMEOUT, proc (ns: Nimsuggest) = discard, - proc (ns: Nimsuggest) = discard) + proc (pr: Project) = discard) proc toString*(bytes: openarray[byte]): string = result = newString(bytes.len) @@ -368,7 +371,7 @@ proc processQueue(self: Nimsuggest): Future[void] {.async.}= command = req.commandString if req.future.finished: debug "Call cancelled before executed", command = req.command - elif self.failed: + elif self.project.failed: debug "Nimsuggest is not working, returning empty result...", port = self.port req.future.complete @[] else: @@ -407,7 +410,7 @@ proc processQueue(self: Nimsuggest): Future[void] {.async.}= res.add sug.get if (content == ""): - self.markFailed "Server crashed/socket closed." + self.project.markFailed "Server crashed/socket closed." debug "Server socket closed" if not req.future.finished: debug "Call cancelled before sending error", command = req.command diff --git a/utils.nim b/utils.nim index 4dfa53f..9b08f87 100644 --- a/utils.nim +++ b/utils.nim @@ -247,6 +247,12 @@ proc partial*[A, B, C]( return proc(b: B): C {.gcsafe, raises: [].} = return fn(a, b) +proc partial*[A, B]( + fn: proc(a: A, b: B): void {.gcsafe, raises: [], nimcall.}, a: A +): proc(b: B): void {.gcsafe, raises: [].} = + return proc(b: B): void {.gcsafe, raises: [].} = + fn(a, b) + proc partial*[A, B, C, D]( fn: proc(a: A, b: B, c: C): D {.gcsafe, raises: [], nimcall.}, a: A ): proc(b: B, c: C): D {.gcsafe, raises: [].} =