Skip to content
Open
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 @@ -326,6 +326,7 @@ markers = [
"toml: language server running for TOML",
"matlab: language server running for MATLAB (requires MATLAB R2021b+)",
"systemverilog: language server running for SystemVerilog (uses verible-verilog-ls)",
"nim: language server running for Nim",
]

[tool.codespell]
Expand Down
1,056 changes: 1,056 additions & 0 deletions src/solidlsp/language_servers/nim_language_server.py

Large diffs are not rendered by default.

28 changes: 20 additions & 8 deletions src/solidlsp/ls.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import threading
from abc import ABC, abstractmethod
from collections import defaultdict
from collections.abc import Hashable, Iterator
from collections.abc import Callable, Hashable, Iterator
from contextlib import contextmanager
from copy import copy
from pathlib import Path, PurePath
Expand Down Expand Up @@ -509,13 +509,7 @@ def logging_fn(source: str, target: str, msg: StringDict | str) -> None:
self._dependency_provider = self._create_dependency_provider()
process_launch_info = self._create_process_launch_info()
log.debug(f"Creating language server instance with {language_id=} and process launch info: {process_launch_info}")
self.server = LanguageServerProcess(
process_launch_info,
language=self.language,
determine_log_level=self._determine_log_level,
logger=logging_fn,
start_independent_lsp_process=config.start_independent_lsp_process,
)
self.server = self._create_server_process(process_launch_info, logging_fn, config)

# Set up the pathspec matcher for the ignored paths
# for all absolute paths in ignored_paths, convert them to relative paths
Expand Down Expand Up @@ -544,6 +538,24 @@ def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
f"{self.__class__.__name__} must implement _create_dependency_provider() or pass process_launch_info to __init__()"
)

def _create_server_process(
self,
process_launch_info: ProcessLaunchInfo,
logging_fn: Callable[[str, str, StringDict | str], None] | None,
config: LanguageServerConfig,
) -> LanguageServerProcess:
"""Creates the LanguageServerProcess instance.

Subclasses can override this to provide a custom process implementation.
"""
return LanguageServerProcess(
process_launch_info,
language=self.language,
determine_log_level=self._determine_log_level,
logger=logging_fn,
start_independent_lsp_process=config.start_independent_lsp_process,
)

def _create_process_launch_info(self) -> ProcessLaunchInfo:
assert self._dependency_provider is not None
cmd = self._dependency_provider.create_launch_command()
Expand Down
10 changes: 10 additions & 0 deletions src/solidlsp/ls_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ class Language(str, Enum):
Requires MATLAB R2021b or later and Node.js.
Set MATLAB_PATH environment variable or configure matlab_path in ls_specific_settings.
"""
NIM = "nim"
"""Nim Language Server (nimlangserver) for Nim projects.
Requires Nim and nimble installed. Automatically installs nimlangserver via nimble if not found.
"""
# Experimental or deprecated Language Servers
TYPESCRIPT_VTS = "typescript_vts"
"""Use the typescript language server through the natively bundled vscode extension via https://github.com/yioneko/vtsls"""
Expand Down Expand Up @@ -253,6 +257,8 @@ def get_source_fn_matcher(self) -> FilenameMatcher:
return FilenameMatcher("*.m", "*.mlx", "*.mlapp")
case self.SYSTEMVERILOG:
return FilenameMatcher("*.sv", "*.svh", "*.v", "*.vh")
case self.NIM:
return FilenameMatcher("*.nim", "*.nims", "*.nimble")
case _:
raise ValueError(f"Unhandled language: {self}")

Expand Down Expand Up @@ -438,6 +444,10 @@ def get_ls_class(self) -> type["SolidLanguageServer"]:
from solidlsp.language_servers.systemverilog_server import SystemVerilogLanguageServer

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

return NimLanguageServer
case _:
raise ValueError(f"Unhandled language: {self}")

Expand Down
22 changes: 22 additions & 0 deletions test/resources/repos/nim/test_repo/config.nims
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Nim configuration file

switch("warning", "UnusedImport:off")

when defined(release):
switch("opt", "speed")
switch("checks", "off")
switch("assertions", "off")
else:
switch("opt", "none")
switch("checks", "on")
switch("assertions", "on")

task test, "Run tests":
exec "nim c -r tests/test_main.nim"

task build, "Build the project":
exec "nim c -d:release main.nim"

task clean, "Clean build artifacts":
exec "rm -rf nimcache"
exec "rm -f main"
69 changes: 69 additions & 0 deletions test/resources/repos/nim/test_repo/main.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Main module for testing Nim language server functionality

import std/[strutils, sequtils, json]
import utils
import types

proc greet(name: string): string =
## Greets a person with their name
result = "Hello, " & name & "!"

proc calculate(a, b: int): int =
## Adds two integers
result = a + b

proc processData(data: JsonNode): string =
## Processes JSON data and returns a formatted string
let name = data["name"].getStr()
let age = data["age"].getInt()
result = "$1 is $2 years old" % [name, $age]

type
Person* = object
name*: string
age*: int
email*: string

proc newPerson*(name: string, age: int, email: string = ""): Person =
## Creates a new Person object
result = Person(name: name, age: age, email: email)

proc describe*(p: Person): string =
## Returns a description of a person
result = "$1 ($2 years old)" % [p.name, $p.age]
if p.email != "":
result.add(", email: " & p.email)

type
Animal* = object
name*: string
species*: string

proc newAnimal*(name: string, species: string): Animal =
## Creates a new Animal object
result = Animal(name: name, species: species)

proc speak*(self: Animal): string =
## Returns the sound the animal makes
case self.species
of "dog": "Woof!"
of "cat": "Meow!"
else: "..."

when isMainModule:
echo greet("World")
echo "2 + 3 = ", calculate(2, 3)

let john = newPerson("John Doe", 30, "[email protected]")
echo describe(john)

let jsonData = %* {"name": "Alice", "age": 25}
echo processData(jsonData)

# Test utils module
echo formatNumber(1234567)
echo reverseString("Hello")

# Test types module
let point = newPoint(10.0, 20.0)
echo "Point: ", point.toString()
10 changes: 10 additions & 0 deletions test/resources/repos/nim/test_repo/nim.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Auto-generated nim.cfg for nimsuggest/nimlangserver

--path:"."

--define:ssl
--define:useStdLib

--hint:XDeclaredButNotUsed:off
--hint:XCannotRaiseY:off
--hint:User:off
20 changes: 20 additions & 0 deletions test/resources/repos/nim/test_repo/nimsuggest.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Auto-generated nimsuggest.cfg for language server stability
# This supplements the project's nim.cfg without overriding it

# Define nimsuggest flag for conditional compilation
# Projects can use: when not defined(nimsuggest)
-d:nimsuggest
-d:nimSuggestSkipStatic
-d:nimscript

# Limit error reporting
--errorMax:100
--maxLoopIterationsVM:10000000

# Performance tuning
--skipProjCfg:off
--skipUserCfg:on
--skipParentCfg:on
--verbosity:0
--hints:off
--notes:off
8 changes: 8 additions & 0 deletions test/resources/repos/nim/test_repo/test_repo.nimble
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Package
version = "0.1.0"
author = "Test"
description = "Test repo for Nim language server integration"
license = "MIT"

# Dependencies
requires "nim >= 2.0.0"
130 changes: 130 additions & 0 deletions test/resources/repos/nim/test_repo/types.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# Type definitions module

import std/[strformat, tables, options, math]

type
Point* = object
x*, y*: float

Rectangle* = object
topLeft*: Point
width*, height*: float

Shape* = ref object of RootObj
color*: string

Circle* = ref object of Shape
center*: Point
radius*: float

Triangle* = ref object of Shape
a*, b*, c*: Point

Color* = enum
Red = "red"
Green = "green"
Blue = "blue"
Yellow = "yellow"
Purple = "purple"

Status* = enum
Pending
InProgress
Completed
Failed

Result*[T, E] = object
case kind*: bool
of true:
value*: T
of false:
error*: E

proc newPoint*(x, y: float): Point =
## Creates a new Point
Point(x: x, y: y)

proc toString*(p: Point): string =
## Converts a Point to string
fmt"({p.x:.2f}, {p.y:.2f})"

proc distance*(p1, p2: Point): float =
## Calculates distance between two points
let dx = p2.x - p1.x
let dy = p2.y - p1.y
sqrt(dx * dx + dy * dy)

proc newRectangle*(x, y, width, height: float): Rectangle =
## Creates a new Rectangle
Rectangle(topLeft: newPoint(x, y), width: width, height: height)

proc area*(r: Rectangle): float =
## Calculates area of a rectangle
r.width * r.height

proc perimeter*(r: Rectangle): float =
## Calculates perimeter of a rectangle
2 * (r.width + r.height)

proc contains*(r: Rectangle, p: Point): bool =
## Checks if a point is inside the rectangle
p.x >= r.topLeft.x and
p.x <= r.topLeft.x + r.width and
p.y >= r.topLeft.y and
p.y <= r.topLeft.y + r.height

method draw*(s: Shape): string {.base.} =
## Base draw method for shapes
"Drawing a shape with color: " & s.color

method draw*(c: Circle): string =
## Draw method for Circle
fmt"Drawing a circle at {c.center.toString()} with radius {c.radius:.2f}"

method draw*(t: Triangle): string =
## Draw method for Triangle
"Drawing a triangle with vertices at " &
t.a.toString() & ", " & t.b.toString() & ", " & t.c.toString()

proc ok*[T, E](value: T): Result[T, E] =
## Creates a successful Result
Result[T, E](kind: true, value: value)

proc err*[T, E](error: E): Result[T, E] =
## Creates an error Result
Result[T, E](kind: false, error: error)

proc isOk*[T, E](r: Result[T, E]): bool =
## Checks if Result is successful
r.kind

proc isErr*[T, E](r: Result[T, E]): bool =
## Checks if Result is an error
not r.kind

type
Database* = ref object
data: Table[string, string]

proc newDatabase*(): Database =
## Creates a new Database
Database(data: initTable[string, string]())

proc set*(db: Database, key, value: string) =
## Sets a value in the database
db.data[key] = value

proc get*(db: Database, key: string): Option[string] =
## Gets a value from the database
if key in db.data:
some(db.data[key])
else:
none(string)

proc delete*(db: Database, key: string): bool =
## Deletes a key from the database
if key in db.data:
db.data.del(key)
true
else:
false
Loading
Loading