Skip to content
Merged
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
9 changes: 8 additions & 1 deletion .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ jobs:
# other-langs: grab-bag + scripting languages + haskell + qml. Haskell (GHC/HLS) and qml (qmlls) are
# installed only on Linux -- too slow/expensive to build on the macOS runner (Haskell), or not worth
# the cost of a cross-OS Qt pull (qmlls) -- and skip on Windows+macOS via the central conftest guard.
MARKERS_OTHER_LANGS: "ruby or php or lua or luau or bash or powershell or elixir or erlang or dart or haxe or haskell or terraform or rego or ansible or yaml or toml or markdown or latex or crystal or cue or fortran or ada or matlab or systemverilog or hlsl or msl or al or qml"
MARKERS_OTHER_LANGS: "ruby or php or lua or luau or bash or powershell or elixir or erlang or dart or deno or haxe or haskell or terraform or rego or ansible or yaml or toml or markdown or latex or crystal or cue or fortran or ada or matlab or systemverilog or hlsl or msl or al or qml"
# niche: slow, mostly-cached toolchains. ocaml + haskell are the two slowest -- kept in separate
# batches (ocaml here, haskell in other-langs). nix + perl skip on Windows via the guard.
MARKERS_NICHE: "julia or r or perl or lean4 or nix or ocaml"
Expand Down Expand Up @@ -219,6 +219,13 @@ jobs:
with:
terraform_version: "1.5.0"
terraform_wrapper: false
# Deno ships its language server with the CLI (`deno lsp`), so installing the
# runtime is all that is needed for the deno-marked tests.
- name: Install Deno
if: matrix.batch == 'other-langs'
uses: denoland/setup-deno@v2
with:
deno-version: v2.x
# - name: Install swift
# if: runner.os != 'Windows'
# uses: swift-actions/setup-swift@v2
Expand Down
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ Status of the `main` branch. Changes prior to the next official version change w
or `textDocument/didChange`), as this is always the intention of calling the method.
If files were kept open in the language server (which the Svelte and Vue language servers did),
the language server was not necessarily informed about updated contents.
- Add Deno support (experimental; language server `deno`, backed by the Deno CLI's built-in
`deno lsp`). Understands Deno module resolution (`npm:` / `jsr:` / `https:` imports) and the
`Deno.*` globals, which the plain TypeScript language server does not. Overlaps TypeScript on
file extensions, so it is not auto-detected and must be selected explicitly; requires the
`deno` CLI on PATH

* JetBrains:
- `jet_brains_find_symbol`: Disallow wildcard-only search, delegating to overview tool if request is for file
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ Serena incorporates a powerful abstraction layer for the integration of language
The underlying language servers are typically open-source projects or at least freely available for use.

When using Serena's language server backend, we provide **support for over 40 programming languages**, including
Ada / SPARK, AL, Angular, Ansible, Bash, BSL, C#, C/C++, Clojure, Crystal, CUE, Dart, Elixir, Elm, Erlang, Fortran, F#, GDScript, GLSL, Go, Groovy, Haskell, Haxe, HLSL, HTML, Java, JavaScript, JSON, Julia, Kotlin, LaTeX, Lean 4, Lua, Luau, Markdown, MATLAB, mSL, Nix, OCaml, Pascal, Perl, PHP, PowerShell, Python, QML, R, Rego, Ruby, Rust, Scala, SCSS / Sass / CSS, Solidity, Svelte, Swift, SystemVerilog, Terraform, TOML, TypeScript, Vue, WGSL, YAML, and Zig.
Ada / SPARK, AL, Angular, Ansible, Bash, BSL, C#, C/C++, Clojure, Crystal, CUE, Dart, Deno, Elixir, Elm, Erlang, Fortran, F#, GDScript, GLSL, Go, Groovy, Haskell, Haxe, HLSL, HTML, Java, JavaScript, JSON, Julia, Kotlin, LaTeX, Lean 4, Lua, Luau, Markdown, MATLAB, mSL, Nix, OCaml, Pascal, Perl, PHP, PowerShell, Python, QML, R, Rego, Ruby, Rust, Scala, SCSS / Sass / CSS, Solidity, Svelte, Swift, SystemVerilog, Terraform, TOML, TypeScript, Vue, WGSL, YAML, and Zig.

### The Serena JetBrains Plugin

Expand Down
6 changes: 6 additions & 0 deletions docs/01-about/020_programming-languages.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,12 @@ Some languages require additional installations or setup steps, as noted.
* **TOML**
(experimental; uses Taplo 0.10.0, taken from PATH if present, otherwise downloaded automatically)
* **TypeScript**
* **Deno**
(experimental; requires the `deno` CLI on PATH — it bundles the language server used here;
serves Deno TypeScript/JavaScript and understands `npm:` / `jsr:` / `https:` imports and the `Deno.*`
globals, which the plain TypeScript language server does not; overlaps `typescript` on file extensions,
so it is not auto-detected and must be set as the language explicitly — do not also enable `typescript`
for the same files)
* **Vue**
(3.x with TypeScript; requires Node.js v18+ and npm; supports .vue Single File Components with monorepo detection)
* **YAML**
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@ markers = [
"groovy: language server running for Groovy",
"rust: language server running for Rust",
"typescript: language server running for TypeScript",
"deno: language server running for Deno (uses the built-in deno lsp)",
"svelte: language server running for Svelte (uses svelte-language-server)",
"vue: language server running for Vue (uses TypeScript LSP)",
"php: language server running for PHP",
Expand Down
25 changes: 13 additions & 12 deletions src/serena/resources/project.template.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ project_name: "project_name"
# list of language servers to start when using the LSP backend; choose from:
# ada al angular ansible bash
# bsl clojure cpp cpp_ccls crystal
# csharp csharp_omnisharp cue dart elixir
# elm erlang fortran fsharp gdscript
# go groovy haskell haxe hlsl
# html java json julia kotlin
# latex lean4 lua luau markdown
# matlab msl nix ocaml pascal
# perl php php_phpactor php_phpantom powershell
# python python_basedpyright python_jedi python_pyrefly python_ty
# qml r rego ruby ruby_solargraph
# rust scala scss solidity svelte
# swift systemverilog terraform toml typescript
# typescript_vts vue yaml zig
# csharp csharp_omnisharp cue dart deno
# elixir elm erlang fortran fsharp
# gdscript go groovy haskell haxe
# hlsl html java json julia
# kotlin latex lean4 lua luau
# markdown matlab msl nix ocaml
# pascal perl php php_phpactor php_phpantom
# powershell python python_basedpyright python_jedi python_pyrefly
# python_ty qml r rego ruby
# ruby_solargraph rust scala scss solidity
# svelte swift systemverilog terraform toml
# typescript typescript_vts vue yaml zig
# (This list may be outdated; generated with scripts/print_language_list.py;
# For the current list, see values of the LanguageServerId enum here:
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py)
Expand All @@ -25,6 +25,7 @@ project_name: "project_name"
# - For JavaScript, use typescript
# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root)
# - For Svelte projects, use svelte (subsumes typescript/javascript for .svelte projects; requires npm)
# - For Deno projects, use deno (serves the same .ts/.js files as typescript; requires the deno CLI on PATH)
# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three)
# - For Free Pascal/Lazarus, use pascal
# Special requirements:
Expand Down
152 changes: 152 additions & 0 deletions src/solidlsp/language_servers/deno_language_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
"""
Provides Deno-specific instantiation of the LanguageServer class, using the
language server built into the Deno CLI (``deno lsp``).
"""

import logging
import shutil

from overrides import override

from solidlsp.ls import LanguageServerDependencyProvider, LanguageServerDependencyProviderSinglePath, SolidLanguageServer
from solidlsp.ls_config import LanguageServerConfig
from solidlsp.settings import SolidLSPSettings

log = logging.getLogger(__name__)


class DenoLanguageServer(SolidLanguageServer):
"""
Deno instantiation of the LanguageServer class, backed by ``deno lsp``.

Serves TypeScript/JavaScript in Deno projects. Unlike the plain
typescript-language-server, ``deno lsp`` understands Deno-specific module
resolution (``npm:`` / ``jsr:`` / ``https:`` imports) and the ``Deno.*``
global namespace.

This server overlaps the TypeScript server on file extensions and is therefore
marked experimental: it is not auto-detected and must be selected explicitly via
``languages: [deno]`` in ``project.yml``.
"""

@classmethod
def supports_implementation_request(cls) -> bool:
return True

def __init__(self, config: LanguageServerConfig, repository_root_path: str, solidlsp_settings: SolidLSPSettings):
super().__init__(
config,
repository_root_path,
None,
"typescript",
solidlsp_settings,
)

def _create_dependency_provider(self) -> LanguageServerDependencyProvider:
return self.DependencyProvider(self._custom_settings, self._ls_resources_dir)

@override
def is_ignored_dirname(self, dirname: str) -> bool:
# node_modules appears in Deno projects using npm compatibility; vendor/ holds
# vendored remote dependencies. Neither should be indexed as project sources.
return super().is_ignored_dirname(dirname) or dirname in ["node_modules", "vendor", "dist", "build"]

class DependencyProvider(LanguageServerDependencyProviderSinglePath):
def _get_or_install_core_dependency(self) -> str:
"""Return the path to the ``deno`` executable (the language server ships with it)."""
deno_path = shutil.which("deno")
if deno_path is None:
raise FileNotFoundError(
"The 'deno' executable was not found on PATH. Install Deno "
"(https://docs.deno.com/runtime/getting_started/installation/) — it bundles "
"the language server used here — and ensure 'deno' is on your PATH."
)
return deno_path

def _create_launch_command(self, core_path: str) -> list[str]:
return [core_path, "lsp"]

def _get_language_id_for_file(self, relative_file_path: str) -> str:
# deno lsp relies on the correct languageId; .tsx/.jsx in particular must not
# be sent as plain "typescript" or symbol ranges get truncated at JSX expressions.
if relative_file_path.endswith(".tsx"):
return "typescriptreact"
if relative_file_path.endswith(".jsx"):
return "javascriptreact"
if relative_file_path.endswith((".js", ".mjs", ".cjs")):
return "javascript"
return "typescript"

def _create_base_initialize_params(self) -> dict:
return {
"locale": "en",
"capabilities": {
"textDocument": {
"synchronization": {"didSave": True, "dynamicRegistration": True},
"definition": {"dynamicRegistration": True},
"references": {"dynamicRegistration": True},
"documentSymbol": {
"dynamicRegistration": True,
"hierarchicalDocumentSymbolSupport": True,
"symbolKind": {"valueSet": list(range(1, 27))},
},
"hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
"completion": {"dynamicRegistration": True, "completionItem": {"snippetSupport": True}},
"rename": {"dynamicRegistration": True, "prepareSupport": True},
"publishDiagnostics": {"relatedInformation": True},
},
"workspace": {
"workspaceFolders": True,
"configuration": True,
"didChangeConfiguration": {"dynamicRegistration": True},
"symbol": {"dynamicRegistration": True},
},
},
# deno lsp reads its settings from initializationOptions; enabling the server
# and the linter mirrors the defaults of the official VS Code Deno extension.
"initializationOptions": {
"enable": True,
"lint": True,
"unstable": False,
},
}

def _start_server(self) -> None:
"""Start the ``deno lsp`` process and drive the LSP initialize handshake."""

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

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

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

def configuration_handler(params: dict) -> list:
# deno lsp requests workspace/configuration during startup; return an empty
# settings object per requested item so it proceeds with its defaults.
return [{} for _ in params.get("items", [])]

self.server.on_request("client/registerCapability", register_capability_handler)
self.server.on_request("workspace/configuration", configuration_handler)
self.server.on_notification("window/logMessage", window_log_message)
self.server.on_notification("$/progress", do_nothing)
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
# Deno-specific notifications emitted after config discovery; no action needed.
self.server.on_notification("deno/didRefreshDenoConfigurationTree", do_nothing)
self.server.on_notification("deno/didChangeDenoConfiguration", do_nothing)

log.info("Starting deno lsp server process")
self.server.start()
initialize_params = self._create_initialize_params()

log.info("Sending initialize request from LSP client to deno lsp and awaiting response")
init_response = self.server.send.initialize(initialize_params)

assert "textDocumentSync" in init_response["capabilities"]
assert "definitionProvider" in init_response["capabilities"]
assert "documentSymbolProvider" in init_response["capabilities"]
assert "referencesProvider" in init_response["capabilities"]

self.server.notify.initialized({})
21 changes: 21 additions & 0 deletions src/solidlsp/ls_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,14 @@ class LanguageServerId(str, Enum):
project.yml — Angular LS supersedes both for Angular projects.
Must be explicitly specified in project.yml.
"""
DENO = "deno"
"""Deno's built-in language server (``deno lsp``) for Deno TypeScript/JavaScript projects.
Understands Deno module resolution (``npm:`` / ``jsr:`` / ``https:`` imports) and the
``Deno.*`` globals, which the plain typescript-language-server does not. Overlaps the
TypeScript server on file extensions (.ts/.tsx/.js/.jsx/.mts/.cts/.mjs/.cjs), so it is
experimental and must be explicitly specified via ``languages: [deno]`` in project.yml;
do not also enable typescript for the same files. Requires the ``deno`` CLI on PATH.
"""

@classmethod
def iter_all(cls, include_experimental: bool = True, include_non_programming_languages: bool = True) -> Iterable[Self]:
Expand Down Expand Up @@ -321,6 +329,7 @@ def is_experimental(self) -> bool:
self.HTML,
self.SCSS,
self.ANGULAR,
self.DENO,
}

def is_programming_language(self) -> bool:
Expand Down Expand Up @@ -588,6 +597,14 @@ def get_source_fn_matcher(self) -> FilenameMatcher:
for postfix in ["x", ""]:
path_patterns.append(f".{prefix}ts{postfix}")
return FilenameMatcher(*path_patterns)
case self.DENO:
# Deno serves the same TS/JS family as the TypeScript server.
path_patterns = []
for prefix in ["c", "m", ""]:
for postfix in ["x", ""]:
for base_pattern in ["ts", "js"]:
path_patterns.append(f".{prefix}{base_pattern}{postfix}")
return FilenameMatcher(*path_patterns)
case _:
raise ValueError(f"Unhandled language: {self}")

Expand Down Expand Up @@ -871,6 +888,10 @@ def get_ls_class(self) -> type["SolidLanguageServer"]:
from solidlsp.language_servers.angular_language_server import AngularLanguageServer

return AngularLanguageServer
case self.DENO:
from solidlsp.language_servers.deno_language_server import DenoLanguageServer

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

Expand Down
3 changes: 3 additions & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ def project_with_ls(request: LanguageParamRequest) -> Iterator[Project]:
LanguageServerId.CPP_CCLS: [pytest.mark.cpp],
LanguageServerId.CUE: [pytest.mark.cue],
LanguageServerId.CSHARP: [pytest.mark.csharp],
LanguageServerId.DENO: [pytest.mark.deno],
LanguageServerId.FSHARP: [pytest.mark.fsharp],
LanguageServerId.GO: [pytest.mark.go],
LanguageServerId.HAXE: [pytest.mark.haxe],
Expand Down Expand Up @@ -477,6 +478,8 @@ def _determine_disabled_language_servers() -> list[LanguageServerId]:
result.append(LanguageServerId.OCAML)
if not _is_perl_language_server_available(): # perl ships with the OS; the LS module is the real signal
result.append(LanguageServerId.PERL)
if _sh.which("deno") is None: # deno bundles the language server (`deno lsp`); skip where the CLI is absent
result.append(LanguageServerId.DENO)

# === 4. Enabled everywhere: every language NOT listed in this function (python, go, java, ...) ===

Expand Down
7 changes: 7 additions & 0 deletions test/resources/repos/deno/test_repo/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"lint": {
"rules": {
"tags": ["recommended"]
}
}
}
15 changes: 15 additions & 0 deletions test/resources/repos/deno/test_repo/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { add } from "./util.ts";

export class Calculator {
sum(a: number, b: number): number {
return add(a, b);
}
}

// Uses the Deno global namespace, which the plain TypeScript language server does not know about.
export function describeCwd(): string {
return `cwd: ${Deno.cwd()}`;
}

const calc = new Calculator();
console.log(calc.sum(2, 3));
7 changes: 7 additions & 0 deletions test/resources/repos/deno/test_repo/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function add(a: number, b: number): number {
return a + b;
}

export function subtract(a: number, b: number): number {
return a - b;
}
Loading
Loading