Skip to content
Closed
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
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ markers = [
"zig: language server running for Zig",
"lua: language server running for Lua",
"luau: language server running for Luau",
"nim: language server running for Nim",
"nix: language server running for Nix",
"dart: language server running for Dart",
"erlang: language server running for Erlang",
Expand Down
147 changes: 147 additions & 0 deletions src/solidlsp/language_servers/nim_language_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import logging
import os
import pathlib
from typing import cast

from solidlsp.ls import SolidLanguageServer
from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo
from solidlsp.settings import SolidLSPSettings

from ..ls_config import LanguageServerConfig
from ..lsp_protocol_handler.lsp_types import InitializeParams
from .common import RuntimeDependency, RuntimeDependencyCollection

log = logging.getLogger(__name__)


class NimLanguageServer(SolidLanguageServer):
"""
Provides Nim specific instantiation of the LanguageServer class.
Contains various configurations and settings specific to Nim.

Uses nimlangserver from https://github.com/nim-lang/langserver
"""

NIMLANGSERVER_VERSION = "1.12.0"

def is_ignored_dirname(self, dirname: str) -> bool:
nim_ignored_dirs = {"nimcache", "nimblecache", "htmldocs", "nimbledeps"}
return dirname in nim_ignored_dirs or super().is_ignored_dirname(dirname)

def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings) -> None:
"""
Creates a NimLanguageServer instance. This class is not meant to be instantiated directly.
Use LanguageServer.create() instead.
"""
executable_path = self._setup_runtime_dependencies(solidlsp_settings)
super().__init__(
config, repository_root_path, ProcessLaunchInfo(cmd=executable_path, cwd=repository_root_path), "nim", solidlsp_settings
)

@classmethod
def _setup_runtime_dependencies(cls, solidlsp_settings: SolidLSPSettings) -> str:
v = cls.NIMLANGSERVER_VERSION
base_url = f"https://github.com/nim-lang/langserver/releases/download/v{v}"
deps = RuntimeDependencyCollection(
[
RuntimeDependency(
id="NimLanguageServer",
description="Nim Language Server for Linux (x64)",
url=f"{base_url}/nimlangserver-linux-amd64.tar.gz",
platform_id="linux-x64",
archive_type="gztar",
binary_name="nimlangserver",
),
RuntimeDependency(
id="NimLanguageServer",
description="Nim Language Server for Linux (arm64)",
url=f"{base_url}/nimlangserver-linux-arm64.tar.gz",
platform_id="linux-arm64",
archive_type="gztar",
binary_name="nimlangserver",
),
RuntimeDependency(
id="NimLanguageServer",
description="Nim Language Server for macOS (x64)",
url=f"{base_url}/nimlangserver-macos-amd64.zip",
platform_id="osx-x64",
archive_type="zip",
binary_name="nimlangserver",
),
RuntimeDependency(
id="NimLanguageServer",
description="Nim Language Server for macOS (arm64)",
url=f"{base_url}/nimlangserver-macos-arm64.zip",
platform_id="osx-arm64",
archive_type="zip",
binary_name="nimlangserver",
),
RuntimeDependency(
id="NimLanguageServer",
description="Nim Language Server for Windows (x64)",
url=f"{base_url}/nimlangserver-windows-amd64.zip",
platform_id="win-x64",
archive_type="zip",
binary_name="nimlangserver.exe",
),
]
)

nim_ls_dir = cls.ls_resources_dir(solidlsp_settings)
executable_path = deps.binary_path(nim_ls_dir)

if not os.path.exists(executable_path):
deps.install(nim_ls_dir)

assert os.path.exists(executable_path)
os.chmod(executable_path, 0o755)

return executable_path

@staticmethod
def _get_initialize_params(repository_absolute_path: str) -> InitializeParams:
"""
Returns the initialize params for the Nim Language Server.
"""
root_uri = pathlib.Path(repository_absolute_path).as_uri()
initialize_params = {
"capabilities": {},
"initializationOptions": {},
"trace": "verbose",
"processId": os.getpid(),
"rootPath": repository_absolute_path,
"rootUri": root_uri,
"workspaceFolders": [
{
"uri": root_uri,
"name": os.path.basename(repository_absolute_path),
}
],
}

return cast(InitializeParams, initialize_params)

def _start_server(self) -> None:
"""
Start the Nim language server and yield when the server is ready.
"""

def do_nothing(params: dict) -> None:
return

def window_log_message(msg: dict) -> None:
log.info(f"LSP: window/logMessage: {msg}")

self.server.on_request("client/registerCapability", do_nothing)
self.server.on_notification("window/logMessage", window_log_message)
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)

log.info("Starting nimlangserver server process")
self.server.start()
initialize_params = self._get_initialize_params(self.repository_root_path)
log.debug("Sending initialize request to nimlangserver")
init_response = self.server.send_request("initialize", initialize_params) # type: ignore
log.info(f"Received initialize response from nimlangserver: {init_response}")

self.server.notify.initialized({})
8 changes: 8 additions & 0 deletions src/solidlsp/ls_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class Language(str, Enum):
Uses luau-lsp by JohnnyMorganz. Automatically downloads the binary if not found.
Supports .luau files. Configure via .luaurc in the project root.
"""
NIM = "nim"
NIX = "nix"
ERLANG = "erlang"
OCAML = "ocaml"
Expand Down Expand Up @@ -243,6 +244,8 @@ def get_source_fn_matcher(self) -> FilenameMatcher:
return FilenameMatcher("*.lua")
case self.LUAU:
return FilenameMatcher("*.luau")
case self.NIM:
return FilenameMatcher("*.nim", "*.nims")
case self.NIX:
return FilenameMatcher("*.nix")
case self.ERLANG:
Expand Down Expand Up @@ -434,6 +437,11 @@ def get_ls_class(self) -> type["SolidLanguageServer"]:

return LuaLanguageServer

case self.NIM:
from solidlsp.language_servers.nim_language_server import NimLanguageServer

return NimLanguageServer

case self.LUAU:
from solidlsp.language_servers.luau_lsp import LuauLanguageServer

Expand Down
35 changes: 35 additions & 0 deletions test/resources/repos/nim/test_repo/main.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
## Main entry point demonstrating cross-module usage.

import src/calculator
import src/utils

proc printBanner*() =
## Print a welcome banner.
echo repeat_string("=", 40)
echo " Nim Calculator Demo"
echo repeat_string("=", 40)

proc testCalculator*() =
## Run calculator tests.
echo "Testing calculator..."
echo "add(2, 3) = ", add(2.0, 3.0)
echo "subtract(10, 4) = ", subtract(10.0, 4.0)
echo "multiply(3, 7) = ", multiply(3.0, 7.0)
echo "divide(15, 3) = ", divide(15.0, 3.0)
echo "factorial(5) = ", factorial(5)
echo "mean(@[1.0, 2.0, 3.0, 4.0, 5.0]) = ", mean(@[1.0, 2.0, 3.0, 4.0, 5.0])

proc testUtils*() =
## Run utility tests.
let logger = newLogger("test")
logger.log("Testing utilities...")
echo "trim(' hello ') = '", trim(" hello "), "'"
echo "split_words('hello world') = ", split_words("hello world")
echo "starts_with('hello', 'he') = ", starts_with("hello", "he")
echo "ends_with('hello', 'lo') = ", ends_with("hello", "lo")
logger.info("Utility tests complete")

when isMainModule:
printBanner()
testCalculator()
testUtils()
36 changes: 36 additions & 0 deletions test/resources/repos/nim/test_repo/src/calculator.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## Calculator module with basic and advanced arithmetic operations.

proc add*(a, b: float): float =
## Add two numbers.
result = a + b

proc subtract*(a, b: float): float =
## Subtract b from a.
result = a - b

proc multiply*(a, b: float): float =
## Multiply two numbers.
result = a * b

proc divide*(a, b: float): float =
## Divide a by b. Raises DivByZeroDefect if b is zero.
if b == 0.0:
raise newException(DivByZeroDefect, "Cannot divide by zero")
result = a / b

proc factorial*(n: int): int =
## Compute factorial of a non-negative integer.
if n < 0:
raise newException(ValueError, "Factorial not defined for negative numbers")
if n <= 1:
return 1
result = n * factorial(n - 1)

proc mean*(values: seq[float]): float =
## Compute the arithmetic mean of a sequence of floats.
if values.len == 0:
raise newException(ValueError, "Cannot compute mean of empty sequence")
var total = 0.0
for v in values:
total += v
result = total / float(values.len)
48 changes: 48 additions & 0 deletions test/resources/repos/nim/test_repo/src/utils.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## Utility functions for string and sequence operations.

import std/strutils

proc trim*(s: string): string =
## Remove leading and trailing whitespace.
result = s.strip()

proc split_words*(s: string): seq[string] =
## Split a string into words by whitespace.
result = s.splitWhitespace()

proc starts_with*(s, prefix: string): bool =
## Check if s starts with the given prefix.
result = s.startsWith(prefix)

proc ends_with*(s, suffix: string): bool =
## Check if s ends with the given suffix.
result = s.endsWith(suffix)

proc repeat_string*(s: string, count: int): string =
## Repeat a string count times.
result = s.repeat(count)

type
Logger* = object
## A simple logger with a name and level.
name*: string
level*: int

proc newLogger*(name: string, level: int = 0): Logger =
## Create a new Logger instance.
result = Logger(name: name, level: level)

proc log*(logger: Logger, message: string) =
## Log a message if level is sufficient.
if logger.level >= 0:
echo "[" & logger.name & "] " & message

proc debug*(logger: Logger, message: string) =
## Log a debug message.
if logger.level <= 0:
echo "[DEBUG " & logger.name & "] " & message

proc info*(logger: Logger, message: string) =
## Log an info message.
if logger.level <= 1:
echo "[INFO " & logger.name & "] " & message
27 changes: 27 additions & 0 deletions test/resources/repos/nim/test_repo/tests/test_calculator.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Tests for the calculator module.

import ../src/calculator

proc testBasicOperations() =
assert add(2.0, 3.0) == 5.0
assert subtract(10.0, 4.0) == 6.0
assert multiply(3.0, 7.0) == 21.0
assert divide(15.0, 3.0) == 5.0
echo "Basic operations: PASSED"

proc testAdvancedOperations() =
assert factorial(0) == 1
assert factorial(1) == 1
assert factorial(5) == 120
echo "Advanced operations: PASSED"

proc testMean() =
assert mean(@[1.0, 2.0, 3.0]) == 2.0
assert mean(@[10.0]) == 10.0
echo "Mean operations: PASSED"

when isMainModule:
testBasicOperations()
testAdvancedOperations()
testMean()
echo "All tests passed!"
Empty file added test/solidlsp/nim/__init__.py
Empty file.
Loading
Loading