From 4599dd6abbd0da5c72f4e03648449b3221061670 Mon Sep 17 00:00:00 2001 From: jmgomez Date: Wed, 12 Jun 2024 11:41:24 +0100 Subject: [PATCH] Introduces `extension/status` It's useful for asserting in tests in a reliable way as it exposes the langserver and nimsuggest instances current status (i.e. main file, known files, etc.) It can also be useful to create a specific window in extension to quickly inspect the current status for a given project --- nimlangserver.nim | 71 ++++++++++++++++++++++++++++------------- protocol/types.nim | 16 ++++++++++ suggestapi.nim | 12 ++++++- tests/tprojectsetup.nim | 9 ++++++ 4 files changed, 85 insertions(+), 23 deletions(-) diff --git a/nimlangserver.nim b/nimlangserver.nim index 8ced2c0..386e1cc 100644 --- a/nimlangserver.nim +++ b/nimlangserver.nim @@ -101,6 +101,16 @@ type nimDir: Option[string] nimblePath: Option[string] +proc getVersionFromNimble(): string = + #We should static run nimble dump instead + const content = staticRead("nimlangserver.nimble") + for v in content.splitLines: + if v.startsWith("version"): + return v.split("=")[^1].strip(chars = {' ', '"'}) + return "unknown" + +const LSPVersion = getVersionFromNimble() + createJsonFlavor(LSPFlavour, omitOptionalFields = true) Option.useDefaultSerializationIn LSPFlavour @@ -700,27 +710,30 @@ proc getNimVersion(nimDir: string): string = if line.startsWith(NimCompilerVersion): return line -proc getNimSuggestPath(ls: LanguageServer, conf: NlsConfig, workingDir: string): string = +proc getNimSuggestPathAndVersion(ls: LanguageServer, conf: NlsConfig, workingDir: string): (string, string) = #Attempting to see if the project is using a custom Nim version, if it's the case this will be slower than usual let nimbleDumpInfo = ls.getNimbleDumpInfo("") let nimDir = nimbleDumpInfo.nimDir.get "" - result = expandTilde(conf.nimsuggestPath.get("")) + var nimsuggestPath = expandTilde(conf.nimsuggestPath.get("")) var nimVersion = "" - if result == "": + if nimsuggestPath == "": if nimDir != "" and nimDir.dirExists: nimVersion = getNimVersion(nimDir) & " from " & nimDir - result = nimDir / "nimsuggest" + nimsuggestPath = nimDir / "nimsuggest" else: nimVersion = getNimVersion("") - result = findExe "nimsuggest" - ls.showMessage(fmt "Using {nimVersion}", MessageType.Info) + nimsuggestPath = findExe "nimsuggest" + else: + nimVersion = getNimVersion(nimsuggestPath.parentDir) + ls.showMessage(fmt "Using {nimVersion}", MessageType.Info) + (nimsuggestPath, nimVersion) proc createOrRestartNimsuggest(ls: LanguageServer, projectFile: string, uri = ""): void {.gcsafe.} = let configuration = ls.getWorkspaceConfiguration().waitFor() workingDir = ls.getWorkingDir(projectFile).waitFor() - nimsuggestPath = ls.getNimSuggestPath(configuration, workingDir) + (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 @@ -735,7 +748,7 @@ proc createOrRestartNimsuggest(ls: LanguageServer, projectFile: string, uri = "" ls.showMessage(fmt "Server failed with {ns.errorMessage}.", MessageType.Error) - nimsuggestFut = createNimsuggest(projectFile, nimsuggestPath, + nimsuggestFut = createNimsuggest(projectFile, nimsuggestPath, version, timeout, restartCallback, errorCallback, workingDir, configuration.logNimsuggest.get(false), configuration.exceptionHintsEnabled) token = fmt "Creating nimsuggest for {projectFile}" @@ -772,11 +785,11 @@ proc restartAllNimsuggestInstances(ls: LanguageServer) = proc warnIfUnknown(ls: LanguageServer, ns: Nimsuggest, uri: string, projectFile: string): Future[void] {.async, gcsafe.} = let path = uri.uriToPath - let sug = await ns.known(path) - if sug[0].forth == "false": - ls.showMessage(fmt """{path} is not compiled as part of project {projectFile}. -In orde to get the IDE features working you must either configure nim.projectMapping or import the module.""", - MessageType.Warning) + let isFileKnown = await ns.isKnown(path) + if not isFileKnown: #TODO only warn when ns doesnt have the unknownFile capability + ls.showMessage(fmt """{path} is not compiled as part of project {projectFile}. + In orde to get the IDE features working you must either configure nim.projectMapping or import the module.""", + MessageType.Warning) proc didOpen(ls: LanguageServer, params: DidOpenTextDocumentParams): Future[void] {.async, gcsafe.} = @@ -979,6 +992,27 @@ proc expand(ls: LanguageServer, params: ExpandTextDocumentPositionParams): result = ExpandResult(content: expand[0].doc.fixIdentation(character), range: expand[0].createRangeFromSuggest()) +proc status(ls: LanguageServer, params: NimLangServerStatusParams): Future[NimLangServerStatus] {.async.} = + var status = NimLangServerStatus() + status.version = LSPVersion + for projectFile in ls.projectFiles.keys: + let ns = await ls.projectFiles[projectFile] + var nsStatus = NimSuggestStatus( + projectFile: projectFile, + capabilities: ns.capabilities.toSeq.mapIt($it), + version: ns.version, + path: ns.nimsuggestPath + ) + for openFile in ns.openFiles: + let openFilePath = openFile.uriToPath + let isKnown = await ns.isKnown(openFilePath) + if isKnown: + nsStatus.knownFiles.add openFilePath + else: + nsStatus.unknownFiles.add openFilePath + status.nimsuggestInstances.add nsStatus + status + proc typeDefinition(ls: LanguageServer, params: TextDocumentPositionParams, id: int): Future[seq[Location]] {.async.} = with (params.position, params.textDocument): @@ -1367,6 +1401,7 @@ proc registerHandlers*(connection: StreamConnection, connection.register("workspace/symbol", partial(workspaceSymbol, ls)) connection.register("textDocument/documentHighlight", partial(documentHighlight, ls)) connection.register("extension/macroExpand", partial(expand, ls)) + connection.register("extension/status", partial(status, ls)) connection.register("shutdown", partial(shutdown, ls)) connection.register("exit", partial(exit, (ls: ls, pipeInput: pipeInput))) @@ -1390,17 +1425,9 @@ var when isMainModule: - proc getVersionFromNimble(): string = - const content = staticRead("nimlangserver.nimble") - for v in content.splitLines: - if v.startsWith("version"): - return v.split("=")[^1].strip(chars = {' ', '"'}) - return "unknown" - proc handleParams(): CommandLineParams = if paramCount() > 0 and paramStr(1) in ["-v", "--version"]: - const version = getVersionFromNimble() - echo version + echo LSPVersion quit() var i = 1 while i <= paramCount(): diff --git a/protocol/types.nim b/protocol/types.nim index e150535..be84092 100644 --- a/protocol/types.nim +++ b/protocol/types.nim @@ -971,3 +971,19 @@ type paddingLeft*: Option[bool] paddingRight*: Option[bool] #data*: OptionalNode + + NimSuggestStatus* = object + projectFile*: string + capabilities*: seq[string] + version*: string + path*: string + pid*: int + knownFiles*: seq[string] + unknownFiles*: seq[string] + + NimLangServerStatus* = ref object + version*: string + nimsuggestInstances*: seq[NimSuggestStatus] + + NimLangServerStatusParams* = ref object + \ No newline at end of file diff --git a/suggestapi.nim b/suggestapi.nim index 08ee6c3..77f9cac 100644 --- a/suggestapi.nim +++ b/suggestapi.nim @@ -96,6 +96,8 @@ type timeoutCallback: NimsuggestCallback protocolVersion*: int capabilities*: set[NimSuggestCapability] + nimSuggestPath*: string + version*: string template benchmark(benchmarkName: string, code: untyped) = @@ -309,6 +311,7 @@ proc getNimsuggestCapabilities*(nimsuggestPath: string): proc createNimsuggest*(root: string, nimsuggestPath: string, + version: string, timeout: int, timeoutCallback: NimsuggestCallback, errorCallback: NimsuggestCallback, @@ -330,6 +333,8 @@ proc createNimsuggest*(root: string, result.timeout = timeout result.timeoutCallback = timeoutCallback result.errorCallback = errorCallback + result.nimSuggestPath = nimsuggestPath + result.version = version if nimsuggestPath != "": result.protocolVersion = detectNimsuggestVersion(root, nimsuggestPath, workingDir) @@ -342,6 +347,7 @@ proc createNimsuggest*(root: string, if enableLog: args.add("--log") result.capabilities = getNimsuggestCapabilities(nimsuggestPath) + debug "Nimsuggest Capabilities", capabilities = result.capabilities if nsExceptionInlayHints in result.capabilities: if enableExceptionInlayHints: args.add("--exceptionInlayHints:on") @@ -370,7 +376,7 @@ proc createNimsuggest*(root: string, result.markFailed fmt "Unable to start nimsuggest. `{nimsuggestPath}` is not present on the PATH" proc createNimsuggest*(root: string): Future[Nimsuggest] {.gcsafe.} = - result = createNimsuggest(root, "nimsuggest", REQUEST_TIMEOUT, + result = createNimsuggest(root, "nimsuggest", "", REQUEST_TIMEOUT, proc (ns: Nimsuggest) = discard, proc (ns: Nimsuggest) = discard) @@ -495,3 +501,7 @@ createRangeCommand(inlayHints) proc `mod`*(nimsuggest: Nimsuggest, file: string, dirtyfile = ""): Future[seq[Suggest]] = return nimsuggest.call("ideMod", file, dirtyfile, 0, 0) + +proc isKnown*(nimsuggest: Nimsuggest, filePath: string): Future[bool] {.async.} = + let sug = await nimsuggest.known(filePath) + return sug.len > 0 and sug[0].forth == "true" \ No newline at end of file diff --git a/tests/tprojectsetup.nim b/tests/tprojectsetup.nim index 141544f..f0f7653 100644 --- a/tests/tprojectsetup.nim +++ b/tests/tprojectsetup.nim @@ -199,6 +199,15 @@ suite "nimble setup": res = client.call("textDocument/completion", %completionParams).waitFor let completionList = res.to(seq[CompletionItem]).mapIt(it.label) check completionList.len > 0 + + var resStatus = client.call("extension/status", %()).waitFor + let status = resStatus.to(NimLangServerStatus)[] + check status.nimsuggestInstances.len == 1 + let nsInfo = status.nimsuggestInstances[0] + check nsInfo.projectFile == entryPoint + check nsInfo.knownFiles.len == 1 + check nsInfo.knownFiles[0] == entryPoint + check nsInfo.unknownFiles.len == 0 # test "`submodule.nim` should not be part of the nimble project file": # let testProjectDir = absolutePath "tests" / "projects" / "testproject"