Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Refactor so it doesnt pass around Future[NimSuggest] around but Project. #238

Merged
merged 2 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 16 additions & 18 deletions ls.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -627,25 +627,24 @@ 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}"

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}",
Expand All @@ -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:
Expand Down Expand Up @@ -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!"

Expand All @@ -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
Expand Down
16 changes: 8 additions & 8 deletions routes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}")
Expand Down
63 changes: 35 additions & 28 deletions suggestapi.nim
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ type
paddingRight*: bool
allowInsert*: bool
tooltip*: string

NimsuggestImpl* = object
failed*: bool
errorMessage*: string
Expand All @@ -80,7 +80,6 @@ type
openFiles*: OrderedSet[string]
successfullCall*: bool
errorCallback*: NimsuggestCallback
process*: AsyncProcessRef
port*: int
root: string
requestQueue: Deque[SuggestCall]
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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:
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions tests/textensions.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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

10 changes: 8 additions & 2 deletions tests/tnimlangserver.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down Expand Up @@ -42,7 +50,6 @@ suite "Suggest API selection":
"window/workDoneProgress/create",
"workspace/configuration",
"extension/statusUpdate",
"extension/statusUpdate",
"textDocument/publishDiagnostics",
"$/progress"
)
Expand Down Expand Up @@ -88,7 +95,6 @@ suite "LSP features":
"window/workDoneProgress/create",
"workspace/configuration",
"extension/statusUpdate",
"extension/statusUpdate",
"textDocument/publishDiagnostics",
"$/progress"
)
Expand Down
2 changes: 1 addition & 1 deletion tests/tprojectsetup.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)}")
Expand Down
2 changes: 1 addition & 1 deletion tests/tsuggestapi.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
Loading