Skip to content
Open
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
1ddab8b
Bump dependency versions.
moigagoo Jul 15, 2026
770331d
CI: Update Nimble to 0.22.3.
moigagoo Jul 15, 2026
911d077
Add config prop to toggle `nim track`-based goto def and find refs.
moigagoo Jul 16, 2026
b6e5590
LSP: Add `nim track` paths to definition and usages.
moigagoo Jul 16, 2026
9c4346a
Add `nim track` handling module.
moigagoo Jul 16, 2026
344988b
MCP: Add `nim track` path for find usages.
moigagoo Jul 16, 2026
643d3eb
Format witn nph 0.6.2.
moigagoo Jul 16, 2026
7d72b30
Tests: MCP: Cover `nim track` path.
moigagoo Jul 16, 2026
1f38107
Tests: LSP: Cover `nim track` path.
moigagoo Jul 16, 2026
a4844ee
Require Nim 2.3.1 for `nim track`.
moigagoo Jul 17, 2026
728cdc9
Require Nim#head for `nim track`.
moigagoo Jul 17, 2026
35fe171
CI: Update Nimble version.
moigagoo Jul 17, 2026
f931845
Remove lock file.
moigagoo Jul 17, 2026
4b38124
Hide `nim track` behind feature.
moigagoo Jul 17, 2026
85c153f
Format witn nph 0.6.2.
moigagoo Jul 17, 2026
0668507
Fix typo in define name.
moigagoo Jul 17, 2026
b1a4ff7
Remove feature usage.
moigagoo Jul 17, 2026
7f24905
Use nim binary from the project when possible.
moigagoo Jul 20, 2026
41958bc
Tests: Move track tests to a separate module.
moigagoo Jul 20, 2026
67f1735
Tests: Move track tests to a separate module.
moigagoo Jul 20, 2026
576cbe5
Tests: Add a track test project.
moigagoo Jul 20, 2026
68900b3
Use getNimPath to get the project Nim instead of using the global one.
moigagoo Jul 20, 2026
dd7fde1
CI: Install Nimble fork with nim#head fix.
moigagoo Jul 21, 2026
cec97cd
Format with nph.
moigagoo Jul 21, 2026
7235bb6
Remove workaround for `nimble dump` not handling nim#head.
moigagoo Jul 21, 2026
4609067
Tests: trackproject: Do no require unittest2.
moigagoo Jul 21, 2026
fea5411
ls: getWorkingDir: Return project dir by default.
moigagoo Jul 21, 2026
2267a4f
ls: getNimPath: Look for Nim binary in the project.
moigagoo Jul 21, 2026
4a77e45
LSP: Make getNimPath async.
moigagoo Jul 21, 2026
56b8283
ls: getNimSuggestPathAndVersion: Call `nimble dump` in the project di…
moigagoo Jul 21, 2026
e4956e1
ls: getWorkingDir: Export proc.
moigagoo Jul 21, 2026
4855a57
LSP: Make getNimPath async.
moigagoo Jul 21, 2026
0d5f64a
track: Use nim binary from the project when possible.
moigagoo Jul 21, 2026
2bdedce
Handle cancellation: kill child processes and re-reise CancellationEr…
moigagoo Jul 28, 2026
98e62df
Read from stdout and stderr concurrently with the `nim track` process.
moigagoo Jul 28, 2026
85c18f3
Revert "Bump dependency versions."
moigagoo Aug 10, 2026
20f24e2
Track API: Catch CancelledError as e.
moigagoo Aug 10, 2026
90e9292
Run `nimble dump` in the project dir instead of the cwd.
moigagoo Aug 10, 2026
3d9769d
LSP: Fall back to nimsuggest on find refs and goto def for unsaved fi…
moigagoo Aug 10, 2026
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
6 changes: 5 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- name: Setup Nimble
uses: nim-lang/setup-nimble-action@v1
with:
nimble-version: "0.20.1"
nimble-version: "latest"
repo-token: ${{ secrets.GITHUB_TOKEN }}

- name: Restore nimble dependencies from cache
Expand All @@ -35,6 +35,10 @@ jobs:
restore-keys: |
${{ runner.os }}-${{ env.cache_nonce }}-

- name: Install Nimble fork with the nim#head fix
shell: bash
run: nimble install https://github.com/moigagoo/nimble@#bugfix/nimble_dump_with_nim_head

- name: Install dependencies
shell: bash
run: nimble -y install -l
Expand Down
45 changes: 31 additions & 14 deletions ls.nim
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ type
nimExpandMacro*: Option[bool]
maxNimsuggestProcesses*: Option[int]
#max number of nimsuggest processes to keep alive. zero means unlimited
useNimTrack*: Option[bool]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens when it is enabled in a nim version that doesnt support track?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll get an error:

"nim track not supported (requires nim >= 2.3.1)"

The reasoning behind this decision is, this is a very experimental feature, it requires development version of Nim and some dedication. You can't accidentally turn it on, if it's on, you know what you did. So I thought I shouldn't invest too much into handling this, the simplest logic will do. If invalid command: track is in the stderr, that means you're not using Nim#devel.


NlsFileInfo* = ref object of RootObj
projectFile*: Future[string]
Expand Down Expand Up @@ -279,7 +280,8 @@ proc getNimbleDumpInfo*(
try:
process = await startProcess(
"nimble",
arguments = @["dump", nimbleFile],
workingDir = nimbleFile.parentDir(),
arguments = @["dump"],
options = {UsePath},
stderrHandle = AsyncProcess.Pipe,
stdoutHandle = AsyncProcess.Pipe,
Expand Down Expand Up @@ -508,10 +510,15 @@ proc getNimVersion(nimDir: string): string =
proc getNimSuggestPathAndVersion(
ls: LanguageServer, conf: NlsConfig, workingDir: string
): Future[(string, string)] {.async.} =
#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 = await ls.getNimbleDumpInfo("")
let nimDir = nimbleDumpInfo.nimDir.get ""
let nimbleFiles = walkFiles(workingDir / "*.nimble").toSeq

let nimbleDumpInfo =
if nimbleFiles.len > 0:
await ls.getNimbleDumpInfo(nimbleFiles[0])
else:
await ls.getNimbleDumpInfo("")

let nimDir = nimbleDumpInfo.nimDir.get ""
var nimsuggestPath = expandTilde(conf.nimsuggestPath.get(""))
var nimVersion = ""
if nimsuggestPath == "":
Expand All @@ -527,11 +534,19 @@ proc getNimSuggestPathAndVersion(
debug "Using {nimVersion}", nimVersion = nimVersion
(nimsuggestPath, nimVersion)

proc getNimPath*(conf: NlsConfig): Option[string] =
proc getNimPath*(
ls: LanguageServer, conf: NlsConfig, workingDir = ""
): Future[Option[string]] {.async.} =
if conf.nimSuggestPath.isSome and conf.nimsuggestPath.get().fileExists():
some(conf.nimSuggestPath.get.parentDir / "nim")
else:
let path = findExe "nim"
let (nimsuggestPath, _) = await ls.getNimSuggestPathAndVersion(conf, workingDir)
let path =
if nimsuggestPath.fileExists():
nimsuggestPath.parentDir / "nim"
else:
findExe "nim"

if path != "":
some(path)
else:
Expand Down Expand Up @@ -597,7 +612,7 @@ proc getRootPath*(ip: LspInitializeParams): string =
proc getRootPath*(ip: McpInitializeParams): string =
getCurrentDir().pathToUri.uriToPath

proc getWorkingDir(ls: LanguageServer, path: string): Future[string] {.async.} =
proc getWorkingDir*(ls: LanguageServer, path: string): Future[string] {.async.} =
let rootPath =
case ls.serverMode
of lsp: ls.lspInitializeParams.getRootPath
Expand All @@ -607,7 +622,7 @@ proc getWorkingDir(ls: LanguageServer, path: string): Future[string] {.async.} =
pathRelativeToRoot = path.tryRelativeTo(rootPath)
mapping = ls.getWorkspaceConfiguration.await().workingDirectoryMapping.get(@[])

result = getCurrentDir()
result = rootPath

for m in mapping:
if pathRelativeToRoot.isSome and m.projectFile == pathRelativeToRoot.get():
Expand Down Expand Up @@ -945,7 +960,7 @@ proc checkProject*(ls: LanguageServer, uri: string): Future[void] {.async.} =
let conf = await ls.getAndWaitForWorkspaceConfiguration()
let useNimCheck = conf.useNimCheck.get(USE_NIM_CHECK_BY_DEFAULT)

let nimPath = getNimPath(conf)
let nimPath = await ls.getNimPath(conf)

if useNimCheck and nimPath.isSome:
proc getFilePath(c: CheckResult): string =
Expand Down Expand Up @@ -1097,7 +1112,7 @@ proc createOrRestartNimsuggest*(
project.stop()
ls.projectFiles[projectFile] = projectNext

projectNext.ns.addCallback do(fut: Future[Nimsuggest]):
projectNext.ns.addCallback do(fut: Future[Nimsuggest]) {.gcsafe.}:
if fut.failed:
let msg = fut.error.msg
error "Nimsuggest initialization failed", projectFile = projectFile, error = msg
Expand Down Expand Up @@ -1140,7 +1155,7 @@ proc maybeRegisterCapabilityDidChangeConfiguration*(ls: LanguageServer) =
gcsafe
.}:
debug "Got response for the didChangeConfiguration registration:",
res = res.read()
res = $res.read()

proc handleConfigurationChanges*(
ls: LanguageServer, oldConfiguration, newConfiguration: NlsConfig
Expand All @@ -1164,10 +1179,12 @@ proc maybeRequestConfigurationFromClient*(ls: LanguageServer) =
ls.prevWorkspaceConfiguration = ls.workspaceConfiguration

ls.workspaceConfiguration = ls.call("workspace/configuration", %configurationParams)
ls.workspaceConfiguration.addCallback do(futConfiguration: Future[JsonNode]):
ls.workspaceConfiguration.addCallback do(futConfiguration: Future[JsonNode]) {.
gcsafe
.}:
if futConfiguration.error.isNil:
debug "Received the following configuration",
configuration = futConfiguration.read()
configuration = $futConfiguration.read()
if not isNil(ls.prevWorkspaceConfiguration) and
ls.prevWorkspaceConfiguration.finished:
let
Expand Down Expand Up @@ -1258,7 +1275,7 @@ proc getProjectFile*(fileUri: string, ls: LanguageServer): Future[string] {.asyn
proc checkFile*(ls: LanguageServer, uri: string): Future[void] {.async.} =
let conf = await ls.getAndWaitForWorkspaceConfiguration()
let useNimCheck = conf.useNimCheck.get(USE_NIM_CHECK_BY_DEFAULT)
let nimPath = conf.getNimPath()
let nimPath = await ls.getNimPath(conf)
let token = fmt "Checking file {uri}"
ls.workDoneProgressCreate(token)
ls.progress(token, "begin", fmt "Checking {uri.uriToPath}")
Expand Down
17 changes: 12 additions & 5 deletions lstransports.nim
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ proc runRpc(ls: LanguageServer, req: RequestRx, rpc: RpcProc): Future[void] {.as
json["result"] = parseJson(res.string)
ls.writeOutput(json)
except CancelledError as ex:
debug "[RunRPC]Request cancelled", meth = req.meth
debug "[RunRPC]Request cancelled", meth = req.method.get("")
except CatchableError as ex:
error "[RunRPC] ", msg = ex.msg, req = req.`method`
writeStackTrace(ex = ex)
Expand All @@ -223,9 +223,16 @@ proc processMessage(ls: LanguageServer, message: string) {.raises: [].} =
#OPT oportunity reuse the same JSON already parsed
let isReq = "method" in contentJson
if isReq:
debug "[Processing Message]", request = contentJson["method"]
debug "[Processing Message]", request = contentJson["method"].getStr()
var fut = Future[JsonString]()
var req = JrpcSys.decode(message, RequestRx)
# LSP allows null or absent params; json_rpc 0.6+ decoder requires array/object
if contentJson.getOrDefault("params").kind == JNull:
contentJson["params"] = newJObject()
# Notifications have no id; RequestRx.id is non-optional so inject null
# to avoid raiseIncompleteObject during decode.
if "id" notin contentJson:
contentJson["id"] = newJNull()
var req = JrpcSys.decode($contentJson, RequestRx)
if req.params.kind == rpNamed and req.id.kind == riNumber:
#Some requests have no id but for others we need to pass the id to the wrapRpc as the id information is lost in the rpc proc
req.params.named.add ParamDescNamed(
Expand All @@ -234,9 +241,9 @@ proc processMessage(ls: LanguageServer, message: string) {.raises: [].} =
req.params.named.add ParamDescNamed(
name: "method", value: JsonString($(contentJson["method"]))
)
let rpc = ls.srv.router.procs.getOrDefault(req.meth.get)
let rpc = ls.srv.router.procs.getOrDefault(req.method.get(""))
if rpc.isNil:
error "[Processing Message] rpc method not found: ", msg = req.meth.get
error "[Processing Message] rpc method not found: ", msg = req.method.get("")
return
asyncSpawn ls.runRpc(req, rpc)
else: #Response
Expand Down
2 changes: 1 addition & 1 deletion nim.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
--define:"async_backend=asyncdispatch"
--define:"chronicles_default_output_device=stderr"
--define:"chronicles_colors=None"
--define:"chronicles_disable_thread_id"
--define:"chronicles_thread_ids=no"
--define:"debugLogging"
#--define:"chronicles_log_level=TRACE"
--define:"chronicles_log_level=DEBUG"
Expand Down
Loading