Skip to content

Add Nim-lang support via nim-langserver#658

Open
a5m0 wants to merge 6 commits intooraios:mainfrom
a5m0:feature/nim-langserver
Open

Add Nim-lang support via nim-langserver#658
a5m0 wants to merge 6 commits intooraios:mainfrom
a5m0:feature/nim-langserver

Conversation

@a5m0
Copy link
Copy Markdown

@a5m0 a5m0 commented Oct 7, 2025

Summary

  • Implement a NimLanguageServer class that handles downloading/installing nimlangserver, sets up environment variables, writes auxiliary Nim/nimsuggest config files, and improves logging/readiness checks so the process starts reliably.
  • Wire the new server into Serena’s language registry and document official Nim support in the README and changelog.
  • Provide pytest coverage plus a sample Nim project that exercises symbols, definitions, references, and completions, along with a dedicated nim pytest marker.

Testing

  • uv run pytest -m nim (passes locally)

Copy link
Copy Markdown
Contributor

@MischaPanch MischaPanch left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution! A good step towards adding nim support, but a bit of "polishing" work is still needed. I haven't reviewed all details yet (in particular, the logic of the while loop or the config writing), and will do so once these first comments are addressed.

# Test finding references to the Person type
references = language_server.request_references("main.nim", 20, 3) # Person type definition

if references:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since this is the most common point of failure, pls make sure cross-file references finding actually works by ensuring the expected references are found, not just their existence. Pls remove all assert isinstance and assert len(...) > 0, these are too generic and don't really add value. There's a dedicated section about this in the guide (memory) on how to add support for new languages



@pytest.mark.nim
class TestNimLanguageServerBasics:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

tests on windows time out - is the language server not supported for windows?

self._log(payload)

# Create simplified message for nimlangserver (only Content-Length header)
import json
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

pls use global imports (here and below)

self.logger("client", "logger", f"Failed to write to stdin: {e}")
return

def _read_ls_process_stderr(self) -> None:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

pls use @OVERRIDES for all overridden methods (importes from overrides, not typing)

max_errors = 10

try:
while self.process and self.process.stderr:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I suppose it is this what causes the hanging on windows - we shouldn't have a while loop forever, pls add some condition to terminate, even in case of failure

def _create_nim_config_if_needed(self):
"""Create or supplement Nim configuration to help nimsuggest work better."""
try:
nim_cfg_path = os.path.join(self.repository_root_path, "nim.cfg")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should we really create these files in the user's repository? I suppose this will even create files in nim/test_repo during test runs.

If there's really no way around it, pls factor out _NIMSUGGEST_CONTENT and other such things as multiline strings.

nimble_files = list(pathlib.Path(self.repository_root_path).glob("*.nimble"))
if nimble_files:
# Check common project structures to determine paths
has_src = os.path.exists(os.path.join(self.repository_root_path, "src"))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this seems very fragile - why is that needed? I suppose project structure is not enforced in nim, right?

cls._solidlsp_settings = solidlsp_settings

# First check if nimlangserver is already installed via nimble
nimble_bin = os.path.expanduser("~/.nimble/bin")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is this OS independent?

except Exception as e:
raise RuntimeError(
f"Failed to install nimlangserver via nimble: {e}\nPlease try installing manually with: nimble install nimlangserver"
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

pls use raise ... from e

"""
Provides Nim specific instantiation of the LanguageServer class using nimlangserver.
Contains various configurations and settings specific to Nim language.
"""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There's a whole bunch of tricks used here that are typically not needed in an LS implementation, pls expand the docstring and mention the most unexpected ones (like the custom handler, the while loop, the writing of config files etc) and why they are needed

@a5m0
Copy link
Copy Markdown
Author

a5m0 commented Oct 12, 2025

Thank you for the feedback, I will work on all your suggestions. This pull request may have been a little premature, I have been using this implementation successfully but there are a lot of shortcomings with the nim-langserver that I'm not happy with and trying to work around rather than waiting for Nimony.

@a5m0 a5m0 marked this pull request as draft December 2, 2025 07:19
@a5m0 a5m0 force-pushed the feature/nim-langserver branch 2 times, most recently from d7ad14c to 10960e9 Compare February 21, 2026 17:46
@a5m0 a5m0 marked this pull request as ready for review February 21, 2026 19:02
@MischaPanch
Copy link
Copy Markdown
Contributor

@a5m0 thanks for the updates to the PR. Could you pls merge main into this and resolve the conflicts? It would also help with the review if you answered on my previous comments and briefly mention you addressed them.

a5m0 added 6 commits February 23, 2026 17:25
Rebuild Nim language support on current upstream (origin/main) using
the latest architecture patterns (LanguageServerProcess, self-wiring
Language enum, updated SolidLanguageServer init signature).

Includes a custom NimLanguageServerProcess that sends only
Content-Length headers — nimlangserver 1.12.0 still cannot parse
the Content-Type header that the standard implementation sends.

- NIM enum in Language with source matchers and factory wiring
- NimLanguageServer with nimsuggest config generation, port health
  tracking, and retry-with-backoff for document symbol requests
- Test suite (8 tests): symbols, types, references, goto-def,
  completions across main.nim, utils.nim, types.nim
- nim pytest marker in pyproject.toml
- Remove silent-pass guards in tests; use hard assertions
- Fix incorrect line numbers in goto_definition and completion tests
- Fix completion test to match actual SolidLanguageServer return format
- Use shutil.which() for cross-platform nimlangserver discovery
- Also check ~/.nimble/bin when nimble bin isn't on PATH
- Extract config file content to module-level constants
- Expand class and method docstrings explaining design decisions
Nim tests were failing with RuntimeError on CI runners (Windows,
Ubuntu, macOS) because the Nim toolchain is not pre-installed.
Add skipif decorator to gracefully skip when neither nim nor
nimlangserver is found in PATH.
Increase pyright startup timeout from 5s to 30s in
test_create_with_index_flag, and add Kotlin to the flaky language
server skip list in test_find_symbol, both of which were timing out
on Windows CI runners.
… and test improvements

- Extract _create_server_process() factory method in SolidLanguageServer
  so NimLanguageServer can override it cleanly instead of the
  stop-and-replace hack that created then immediately discarded a
  LanguageServerProcess
- Use threading.Event for _has_port_error and _nimsuggest_functional
  flags to ensure thread-safe reads/writes between notification
  handlers and request threads
- Upgrade config file creation logs from info to warning with
  .gitignore suggestion
- Reduce retry delays from [5, 15, 30]s to [2, 5, 10]s
- Replace hardcoded line/column numbers in goto_definition and
  completions tests with dynamic content lookup
- Add trailing newlines to all Nim test repo files
- Add unit tests for _send_payload (Content-Length without
  Content-Type) and _create_nim_config_if_needed (7 edge cases)
- Extract shared _retry_on_empty helper for hover/references/definition
  retries, replacing three near-identical retry loops
- Add _detect_nim_project_mapping to inject projectMapping into LSP init
  options, ensuring nimsuggest starts with the correct .nim entry point
  instead of config.nims (fixes flaky cross-file references/goto-def)
- Add request cap (200) and debug logging to goto-definition fallback
- Filter goto-definition fallback to .nim files only
- Add test_repo.nimble and nim.cfg to test repo for proper project detection
- Fix broken ../src path in test repo config.nims
- Add hover test, delete test_nim_unit.py (tested private methods)
@a5m0 a5m0 force-pushed the feature/nim-langserver branch from 10960e9 to bf85479 Compare February 23, 2026 17:38
@a5m0
Copy link
Copy Markdown
Author

a5m0 commented Feb 23, 2026

Rebased on latest origin/main, resolved conflicts and addressed comments, thanks!


log = logging.getLogger(__name__)

ENCODING = "utf-8"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

encoding should be read off the LS's field _encoding

is_document_symbol = "range" in first
location = first.get("location")
is_symbol_information = not is_document_symbol and isinstance(location, dict) and "range" in location
if not is_document_symbol and not is_symbol_information:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what is this case semantically? Why can we just return here?

Comment on lines +701 to +702

# If the range already spans multiple lines, assume it is correct
Copy link
Copy Markdown
Contributor

@MischaPanch MischaPanch Feb 23, 2026

Choose a reason for hiding this comment

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

The logic here doesn't seem very robust. It would be better to use a proper parser, like https://github.com/alaviss/tree-sitter-nim, to get the actual symbol range. I understand that it may be too much to ask from you, but in this state it's an experimental implementation that can only be a temporary step before a proper solution.

The best would be if there was an actually functioning LS for nim, but I guess that's outside the realm of possibilities, right?

return last_content_line

@override
def request_document_symbols(self, relative_file_path: str, file_buffer: "LSPFileBuffer | None" = None) -> "DocumentSymbols":
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You override both the public and the private method, is that really needed?

"""Override to add a goto-definition fallback for Nim.

nimsuggest's ``textDocument/references`` can return incomplete results,
especially for cross-file references. When the standard path returns
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this is really a major bug in the LS, have you considered fixing it there?


overview = self.request_overview("")

cap_reached = False
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I can't actually review this complicated logic. I guess having something for nim is better than having nothing, so if you say there's no better way than to hack around on almost every corner (repeating requests, adjusting selection ranges) and implementing essential LS functionality here in python, then I'll trust you on that. But we will have to flag nim as experimental, write a short document on the sorry status of the nim LS in our documentation (as warning to users) and keep an open issue to improve all of this or to wait for a better LS

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Unfortunately the tooling available is not good (part of what encouraged me to add Nim to Serena), and I agree that the current nim-langserver support should be marked as experimental. I will try to address your new comments, but ultimately I expect Nim support to be sub-par until https://github.com/nim-lang/nimony is ready.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ok, I get it. We can merge this best-effort support for Nim. My only criterion (apart from the tests) is that the problems and the shortcuts (and their limitations) taken to address them are clearly documented in the code, and that expectation management towards nim support is done in the user-facing documentation (like we have done for scala).

Copy link
Copy Markdown
Contributor

@zerone0x zerone0x left a comment

Choose a reason for hiding this comment

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

Pulled the branch locally to make sure nothing obvious regressed. The new NimLanguageServerProcess and config helpers are wired through Language.NIM, the factory override looks clean, and the docs/changelog entries are in place. Ran uv run pytest -m nim — everything skips on this machine because neither Nim nor nimlangserver is installed, but there were no unexpected failures. From my side this looks good; thanks for pushing the full compatibility layer over the finish line.

@MischaPanch
Copy link
Copy Markdown
Contributor

There are still some small things left to be fixed before it can be merged.

@a5m0 said

I will try to address your new comments

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.

4 participants