Skip to content

Use nim track for goto definition and find usages - #434

Open
moigagoo wants to merge 39 commits into
masterfrom
feature/nim_track
Open

Use nim track for goto definition and find usages#434
moigagoo wants to merge 39 commits into
masterfrom
feature/nim_track

Conversation

@moigagoo

Copy link
Copy Markdown
Collaborator

Nim 2.4.0 adds a new command nim track --def|--usages|--defusages that replaces nimsuggest's def and use commands.

This PR adds an experimental implementation of goto def and find refs based on nim track.

@moigagoo
moigagoo marked this pull request as ready for review July 17, 2026 11:59
@moigagoo
moigagoo requested a review from jmgomez July 17, 2026 14:06
@moigagoo
moigagoo marked this pull request as draft July 17, 2026 16:12
moigagoo added 6 commits July 22, 2026 01:53
Previously, we would blindly call `nim` from PATH.

Also, the proc is called exclusively in async context so I made it
async.
…rectory.

Previously, we would call it in the callsite directory returning the
wrong nimsuggest (and subsequently nim).
@moigagoo
moigagoo marked this pull request as ready for review July 21, 2026 22:37
@moigagoo

moigagoo commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator Author

I had to rewrite some things not directly related to nim track to make nimlangserver correctly find the right Nim binary to call. That also involves fixing Nimble so I'm using my fork in the CI until the Nimble PR is merged.

To test the new mode locally:

  1. Install nimlangserver from feature/nim_track branch:
$ nimble install https://github.com/nim-lang/langserver@#feature/nim_track
  1. Install the patched Nimble version:
$ nimble install https://github.com/moigagoo/nimble@#bugfix/nimble_dump_with_nim_head
  1. In your langserver config, add useNimTrack = true. For example, here's how to do it in Helix:
[[language]]
name = "nim"
language-servers = ["nimlangserver"]

[language-server.nimlangserver]
command = "nimlangserver"
args = ["--lsp", "--stdio"]

[language-server.nimlangserver.config.nim]
useNimTrack = true
  1. Since nim track is available only in pre-release Nim now, the project you want to test it with must have nim#head in its .nimble file and compile with nim#head.

  2. After adding requires "nim#head" to .nimble file run nimble install -ly && nimble setup.

  3. To test that your project compiles with nim#head, run ./nimbledeps/pkg2/nim-#headsomehashhere/bin/nim ic src/yourfile.nim

If everything is fine so far, launch your editor and try doing goto def and find refs (gd and gr in Helix). You can check your LSP logs to see the nim track calls (:log-open in Helix; tail -f ~./cache/helix/helix.log is a convenient way to see the logs update in real time).

The commands are slow at first because nim ic is invoked under the hood but it gets better quickly.

Happy testing!

@jmgomez

jmgomez commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

This is what the AI says, verify and let me know so I can take a closer look. Thanks!

  • High — large reference results can deadlock. trackapi.nim:47-53
    (

    langserver/trackapi.nim

    Lines 47 to 53 in 0d5f64a

    try:
    let exitCode = await process.waitForExit(timeout.milliseconds)
    let stdoutBytes = process.stdoutStream.read().await
    var stderrStr = ""
    try:
    stderrStr = process.stderrStream.read().await.toString
    except CatchableError:
    )
    waits for nim track to exit before draining stdout or stderr. If either pipe fills, the
    compiler cannot exit and the request stalls until timeout. Both streams should be drained
    concurrently while the process runs.

  • Medium — navigation stops working in unsaved buffers. routes/lsp.nim:162-163
    (

    langserver/routes/lsp.nim

    Lines 162 to 163 in 0d5f64a

    if uri notin ls.openFiles or ls.openFiles[uri].changed:
    return @[]
    )
    and routes/lsp.nim:463-464
    (

    langserver/routes/lsp.nim

    Lines 463 to 464 in 0d5f64a

    if uri notin ls.openFiles or ls.openFiles[uri].changed:
    return @[]
    )
    return no results whenever the document has changed. Current Nim supports an optional
    dirty-buffer path for these switches; alternatively, this case could fall back to
    nimsuggest.

  • Medium — cancellation can leak nim track processes. trackapi.nim:61-63
    (

    langserver/trackapi.nim

    Lines 61 to 63 in 0d5f64a

    except CatchableError as e:
    debug "nim track exception", error = e.msg, name = e.name
    result = @[]
    )
    catches CancelledError as CatchableError, returns an empty result, and never terminates the
    child. Cleanup should run on cancellation, and cancellation should be re-raised.

@moigagoo

Copy link
Copy Markdown
Collaborator Author

High — large reference results can deadlock. trackapi.nim:47-53
Medium — cancellation can leak nim track processes. trackapi.nim:61-63

Fixed.

Medium — navigation stops working in unsaved buffers. routes/lsp.nim:162-163

This is interesting. The thing is, I deliberately chose to run nim track only against saved files. Nimsuggest accepts an optional dirty param and therefore can work with unsaved files (the checks are run against a temporary file on the disk but the hint positioning is reported relative to the dirty file). AFAIK nim track doesn't have that, it just runs against a file on the disk, there is no dirty param.

So my options were to only serve saved files, fallback to nimsuggest, or invent something to work around that limitation. I chose the first option because it was the simplest solution. Using nimsuggest kind of defeats the whole purpose of nim track (which is to replace nimsuggest).

Comment thread nimble.lock

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.

why lock is deleted?

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.

Initially, I got confused between project dependencies and langserver dependencies and so tried setting up Nim#head as a dependency to langserver. This didn't work with lock files.

Later on this all became irrelevant since it's the project that must depend on Nim#head, not langserver. But I forgot to revert these commits.

Reverted.

Comment thread nimlangserver.nimble Outdated
requires "nim == 2.0.8",
"chronos >= 4.0.4", "json_rpc >= 0.5.0", "with", "chronicles", "serialization",
"json_serialization", "stew", "regex", "unittest2 >= 0.2.4"
requires "nim >= 2.2.10",

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.

I feel like bumping deps doesnt belong to this pr

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.

Comment thread ls.nim
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.

@jmgomez

jmgomez commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

AFAIK nim track doesn't have that, it just runs against a file on the disk, there is no dirty param.

Then a follow up must be done where its supported, otherwise it wont work 99% of the times when working with code, no? Or am I missing something else? AFAIR a file becomes dirty when there is unsaved modification. For the time being, we should fallback to ns if thats the case

This resolves "Error: Exception can raise an unlisted exception:
Exception."
This was unintentionally removed in the previous commit.
@moigagoo

Copy link
Copy Markdown
Collaborator Author

Then a follow up must be done where its supported, otherwise it wont work 99% of the times when working with code, no? Or am I missing something else?

You are right, since most people don't use autosave this won't work for them.

AFAIR a file becomes dirty when there is unsaved modification.

When, exactly.

For the time being, we should fallback to ns if thats the case

Removed immediate return so that definition and references fall back to nimsuggest for unsaved files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants