diff --git a/ls.nim b/ls.nim index 5175c8d..46de73f 100644 --- a/ls.nim +++ b/ls.nim @@ -107,7 +107,7 @@ type notify*: NotifyAction call*: CallAction onExit*: OnExitCallback - projectFiles*: Table[string, Future[Nimsuggest]] + projectFiles*: Table[string, Project] openFiles*: Table[string, NlsFileInfo] workspaceConfiguration*: Future[JsonNode] prevWorkspaceConfiguration*: Future[JsonNode] @@ -289,13 +289,13 @@ proc toPendingRequestStatus(pr: PendingRequest): PendingRequestStatus = proc getLspStatus*(ls: LanguageServer): NimLangServerStatus {.raises: [].} = result.version = LSPVersion result.extensionCapabilities = ls.extensionCapabilities.toSeq - for projectFile, futNs in ls.projectFiles: - let futNs = ls.projectFiles.getOrDefault(projectFile, nil) + for project in ls.projectFiles.values: + let futNs = project.ns if futNs.finished: try: var ns = futNs.read var nsStatus = NimSuggestStatus( - projectFile: projectFile, + projectFile: project.file, capabilities: ns.capabilities.toSeq, version: ns.version, path: ns.nimsuggestPath, @@ -627,8 +627,8 @@ proc createOrRestartNimsuggest*(ls: LanguageServer, projectFile: string, uri = " MessageType.Error) ls.sendStatusChanged() - - nimsuggestFut = createNimsuggest(projectFile, nimsuggestPath, version, + #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), configuration.exceptionHintsEnabled) token = fmt "Creating nimsuggest for {projectFile}" @@ -636,16 +636,15 @@ proc createOrRestartNimsuggest*(ls: LanguageServer, projectFile: string, uri = " 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 + var project = ls.projectFiles[projectFile] + project.stop() + ls.projectFiles[projectFile] = projectNext ls.progress(token, "begin", fmt "Restarting nimsuggest for {projectFile}") else: ls.progress(token, "begin", fmt "Creating nimsuggest for {projectFile}") - ls.projectFiles[projectFile] = nimsuggestFut + ls.projectFiles[projectFile] = projectNext - nimsuggestFut.addCallback do (fut: Future[Nimsuggest]): + projectNext.ns.addCallback do (fut: Future[Nimsuggest]): if fut.read.failed: let msg = fut.read.errorMessage ls.showMessage(fmt "Nimsuggest initialization for {projectFile} failed with: {msg}", @@ -667,8 +666,8 @@ proc getNimsuggestInner(ls: LanguageServer, uri: string): Future[Nimsuggest] {.a if not ls.projectFiles.hasKey(projectFile): ls.createOrRestartNimsuggest(projectFile, uri) - ls.lastNimsuggest = ls.projectFiles[projectFile] - return await ls.projectFiles[projectFile] + ls.lastNimsuggest = ls.projectFiles[projectFile].ns + return await ls.projectFiles[projectFile].ns proc tryGetNimsuggest*(ls: LanguageServer, uri: string): Future[Option[Nimsuggest]] {.async.} = if uri notin ls.openFiles: @@ -738,9 +737,8 @@ proc stopNimsuggestProcesses*(ls: LanguageServer) {.async.} = if not ls.childNimsuggestProcessesStopped: debug "stopping child nimsuggest processes" ls.childNimsuggestProcessesStopped = true - for ns in ls.projectFiles.values: - let ns = await ns - ns.stop() + for project in ls.projectFiles.values: + project.stop() else: debug "child nimsuggest processes already stopped: CHECK!" @@ -764,7 +762,7 @@ proc getProjectFile*(fileUri: string, ls: LanguageServer): Future[string] {.asyn result = ls.getProjectFileAutoGuess(fileUri) if result in ls.projectFiles: - let ns = await ls.projectFiles[result] + let ns = await ls.projectFiles[result].ns let isKnown = await ns.isKnown(fileUri) if ns.canHandleUnknown and not isKnown: debug "File is not known by nimsuggest", uri = fileUri, projectFile = result diff --git a/routes.nim b/routes.nim index bc2b330..e4bf6c8 100644 --- a/routes.nim +++ b/routes.nim @@ -236,23 +236,23 @@ proc extensionSuggest*(ls: LanguageServer, params: SuggestParams): Future[Sugges else: error "Project file must exists ", params = params return SuggestResult() - template restart(ls: LanguageServer, ns: NimSuggest) = + template restart(ls: LanguageServer, project: Project) = ls.showMessage(fmt "Restarting nimsuggest {projectFile}", MessageType.Info) - ns.errorCallback = nil - ns.stop() + # ns.errorCallback = nil TODO handle errors (they are going to be moved into Project) + project.stop() ls.createOrRestartNimsuggest(projectFile, projectFile.pathToUri) ls.sendStatusChanged() case params.action: of saRestart: - let ns = await ls.projectFiles[projectFile] - ls.restart(ns) + let project = ls.projectFiles[projectFile] + ls.restart(project) SuggestResult(actionPerformed: saRestart) of saRestartAll: let projectFiles = ls.projectFiles.keys.toSeq() for projectFile in projectFiles: - let ns = await ls.projectFiles[projectFile] - ls.restart(ns) + let project = ls.projectFiles[projectFile] + ls.restart(project) SuggestResult(actionPerformed: saRestartAll) of saNone: error "An action must be specified", params = params @@ -535,7 +535,7 @@ proc executeCommand*(ls: LanguageServer, params: ExecuteCommandParams): debug "Clean build", projectFile = projectFile let token = fmt "Compiling {projectFile}" - ns = ls.projectFiles.getOrDefault(projectFile) + ns = ls.projectFiles.getOrDefault(projectFile).ns if ns != nil: ls.workDoneProgressCreate(token) ls.progress(token, "begin", fmt "Compiling project {projectFile}") diff --git a/suggestapi.nim b/suggestapi.nim index c7e4f1b..bda0222 100644 --- a/suggestapi.nim +++ b/suggestapi.nim @@ -71,7 +71,7 @@ type paddingRight*: bool allowInsert*: bool tooltip*: string - + NimsuggestImpl* = object failed*: bool errorMessage*: string @@ -80,7 +80,6 @@ type openFiles*: OrderedSet[string] successfullCall*: bool errorCallback*: NimsuggestCallback - process*: AsyncProcessRef port*: int root: string requestQueue: Deque[SuggestCall] @@ -94,6 +93,11 @@ type NimSuggest* = ref NimsuggestImpl + Project* = ref object + ns*: Future[NimSuggest] + file*: string + process*: AsyncProcessRef + func canHandleUnknown*(ns: Nimsuggest): bool = nsUnknownFile in ns.capabilities @@ -226,8 +230,8 @@ proc markFailed(self: Nimsuggest, errMessage: string) {.raises: [].} = if self.errorCallback != nil: self.errorCallback(self) -proc stop*(self: Nimsuggest) = - debug "Stopping nimsuggest for ", root = self.root +proc stop*(self: Project) = + debug "Stopping nimsuggest for ", root = self.file try: let res = self.process.kill() debug "Stopped nimsuggest ", res = res @@ -289,10 +293,10 @@ proc getNimsuggestCapabilities*(nimsuggestPath: string): if cap.isSome: result.incl(cap.get) -proc logNsError(ns: NimSuggest) {.async.} = - let err = string.fromBytes(ns.process.stderrStream.read().await) +proc logNsError(project: Project) {.async.} = + let err = string.fromBytes(project.process.stderrStream.read().await) error "NimSuggest Error (stderr)", err = err - ns.markFailed(err) + # ns.markFailed(err) #TODO Error handling should be at the project level proc createNimsuggest*(root: string, nimsuggestPath: string, @@ -302,32 +306,35 @@ proc createNimsuggest*(root: string, errorCallback: NimsuggestCallback, workingDir = getCurrentDir(), enableLog: bool = false, - enableExceptionInlayHints: bool = false): Future[Nimsuggest] {.async, gcsafe.} = - result = Nimsuggest() - result.requestQueue = Deque[SuggestCall]() - result.root = root - result.timeout = timeout - result.timeoutCallback = timeoutCallback - result.errorCallback = errorCallback - result.nimSuggestPath = nimsuggestPath - result.version = version + enableExceptionInlayHints: bool = false): Future[Project] {.async, gcsafe.} = + result = Project(file: root) + result.ns = newFuture[NimSuggest]() + + 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 info "Starting nimsuggest", root = root, timeout = timeout, path = nimsuggestPath, workingDir = workingDir if nimsuggestPath != "": - result.protocolVersion = detectNimsuggestVersion(root, nimsuggestPath, workingDir) - if result.protocolVersion > HighestSupportedNimSuggestProtocolVersion: - result.protocolVersion = HighestSupportedNimSuggestProtocolVersion + ns.protocolVersion = detectNimsuggestVersion(root, nimsuggestPath, workingDir) + if ns.protocolVersion > HighestSupportedNimSuggestProtocolVersion: + ns.protocolVersion = HighestSupportedNimSuggestProtocolVersion var - args = @[root, "--v" & $result.protocolVersion, "--autobind"] - if result.protocolVersion >= 4: + args = @[root, "--v" & $ns.protocolVersion, "--autobind"] + if ns.protocolVersion >= 4: args.add("--clientProcessId:" & $getCurrentProcessId()) if enableLog: args.add("--log") - result.capabilities = getNimsuggestCapabilities(nimsuggestPath) - debug "Nimsuggest Capabilities", capabilities = result.capabilities - if nsExceptionInlayHints in result.capabilities: + ns.capabilities = getNimsuggestCapabilities(nimsuggestPath) + debug "Nimsuggest Capabilities", capabilities = ns.capabilities + if nsExceptionInlayHints in ns.capabilities: if enableExceptionInlayHints: args.add("--exceptionInlayHints:on") else: @@ -336,14 +343,14 @@ proc createNimsuggest*(root: string, await startProcess(nimsuggestPath, arguments = args, options = { UsePath }, stdoutHandle = AsyncProcess.Pipe, stderrHandle = AsyncProcess.Pipe) - asyncSpawn logNsError(result) - result.port = (await result.process.stdoutStream.readLine(sep="\n")).parseInt + ns.port = (await result.process.stdoutStream.readLine(sep="\n")).parseInt + result.ns.complete(ns) else: error "Unable to start nimsuggest. Unable to find binary on the $PATH", nimsuggestPath = nimsuggestPath - result.markFailed fmt "Unable to start nimsuggest. `{nimsuggestPath}` is not present on the PATH" + ns.markFailed fmt "Unable to start nimsuggest. `{nimsuggestPath}` is not present on the PATH" -proc createNimsuggest*(root: string): Future[Nimsuggest] {.gcsafe.} = +proc createNimsuggest*(root: string): Future[Project] {.gcsafe.} = result = createNimsuggest(root, "nimsuggest", "", REQUEST_TIMEOUT, proc (ns: Nimsuggest) = discard, proc (ns: Nimsuggest) = discard) diff --git a/tests/textensions.nim b/tests/textensions.nim index b0eabec..d7f0c10 100644 --- a/tests/textensions.nim +++ b/tests/textensions.nim @@ -51,10 +51,10 @@ suite "Nimlangserver": client.notify("textDocument/didOpen", %createDidOpenParams("projects/hw/useRoot.nim")) - let prevSuggestPid = ls.projectFiles[hwAbsFile].waitFor.process.pid + let prevSuggestPid = ls.projectFiles[hwAbsFile].process.pid let suggestParams = SuggestParams(action: saRestart, projectFile: hwAbsFile) let suggestRes = client.call("extension/suggest", %suggestParams).waitFor - let suggestPid = ls.projectFiles[hwAbsFile].waitFor.process.pid + let suggestPid = ls.projectFiles[hwAbsFile].process.pid check prevSuggestPid != suggestPid diff --git a/tests/tnimlangserver.nim b/tests/tnimlangserver.nim index 7d9782e..84bcbba 100644 --- a/tests/tnimlangserver.nim +++ b/tests/tnimlangserver.nim @@ -12,6 +12,14 @@ suite "Nimlangserver": let cmdParams = CommandLineParams(transport: some socket, port: getNextFreePort()) let ls = main(cmdParams) #we could accesss to the ls here to test against its state let client = newLspSocketClient() + client.registerNotification( + "window/showMessage", + "window/workDoneProgress/create", + "workspace/configuration", + "extension/statusUpdate", + "textDocument/publishDiagnostics", + "$/progress" + ) waitFor client.connect("localhost", cmdParams.port) test "initialize from the client should call initialized on the server": @@ -42,7 +50,6 @@ suite "Suggest API selection": "window/workDoneProgress/create", "workspace/configuration", "extension/statusUpdate", - "extension/statusUpdate", "textDocument/publishDiagnostics", "$/progress" ) @@ -88,7 +95,6 @@ suite "LSP features": "window/workDoneProgress/create", "workspace/configuration", "extension/statusUpdate", - "extension/statusUpdate", "textDocument/publishDiagnostics", "$/progress" ) diff --git a/tests/tprojectsetup.nim b/tests/tprojectsetup.nim index fbbe639..0276c59 100644 --- a/tests/tprojectsetup.nim +++ b/tests/tprojectsetup.nim @@ -110,7 +110,7 @@ suite "nimble setup": "uri": pathToUri(entryPoint) } } - let ns = waitFor ls.projectFiles[entryPoint] + let ns = waitFor ls.projectFiles[entryPoint].ns client.notify("textDocument/didOpen", %createDidOpenParams("projects/testproject/src/testproject.nim")) check waitFor client.waitForNotification("window/showMessage", (json: JsonNode) => json["message"].to(string) == &"Opening {pathToUri(entryPoint)}") diff --git a/tests/tsuggestapi.nim b/tests/tsuggestapi.nim index 0c0545c..6bc1f8c 100644 --- a/tests/tsuggestapi.nim +++ b/tests/tsuggestapi.nim @@ -7,7 +7,7 @@ const inputLineWithEndLine = "outline skEnumField system.bool.true bool basic_ty suite "Nimsuggest tests": let helloWorldFile = getCurrentDir() / "tests/projects/hw/hw.nim" - nimSuggest = createNimsuggest(helloWorldFile).waitFor + nimSuggest = createNimsuggest(helloWorldFile).waitFor.ns.waitFor test "Parsing qualified path": doAssert parseQualifiedPath("a.b.c") == @["a", "b", "c"]